1 /*
2 This file is part of GNU APL, a free implementation of the
3 ISO/IEC Standard 13751, "Programming Language APL, Extended"
4
5 Copyright (C) 2008-2016 Dr. Jürgen Sauermann
6
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include <stdlib.h>
22
23 #include "Common.hh"
24 #include "Command.hh"
25 #include "IndexExpr.hh"
26 #include "InputFile.hh"
27 #include "IO_Files.hh"
28 #include "Output.hh"
29 #include "UTF8_string.hh"
30 #include "UserPreferences.hh"
31 #include "Workspace.hh"
32
33 int IO_Files::testcase_count = 0;
34 int IO_Files::testcases_done = 0;
35 int IO_Files::total_errors = 0;
36 int IO_Files::apl_errors = 0;
37 const char * IO_Files::last_apl_error_loc = "";
38 int IO_Files::last_apl_error_line = -1;
39 int IO_Files::assert_errors = 0;
40 int IO_Files::diff_errors = 0;
41 int IO_Files::parse_errors = 0;
42 IO_Files::TestMode IO_Files::test_mode = TM_EXIT_AFTER_LAST;
43 bool IO_Files::need_total = false;
44 ofstream IO_Files::current_testreport;
45
46 //-----------------------------------------------------------------------------
47 void
get_file_line(UTF8_string & line,bool & eof)48 IO_Files::get_file_line(UTF8_string & line, bool & eof)
49 {
50 while (InputFile::current_file()) // as long as we have input files
51 {
52 if (!InputFile::current_file()->file)
53 {
54 open_next_file();
55 if (!InputFile::current_file()) break; // no more files
56 if (!InputFile::current_file()->file) break; // no more files
57 }
58
59 // At this point current_file and current_file->file are valid
60 // read a line with CR and LF removed.
61 //
62 eof = false;
63 read_file_line(line, eof);
64 if (eof) // end of file reached: do some global checks
65 {
66 if (InputFile::current_file()->with_LX == do_LX)
67 {
68 InputFile::current_file()->with_LX = no_LX;
69 InputFile::current_file()->echo = true;
70 UCS_string LX = Workspace::get_LX();
71 if (LX.size()) // ⎕LX pending
72 {
73 Command::process_line(LX);
74 eof = false;
75 return;
76 }
77 }
78 if (end_of_current_file()) continue; // try again.
79 else break; // done
80 }
81
82 current_testreport << "----> " << line.c_str() << endl;
83 return;
84 }
85
86 // arrive here when all testfiles have been read.
87 // Maybe print a testcase total summary
88 //
89 if (need_total) // we had a -T option
90 {
91 print_summary();
92 need_total = false; // forget the -T option
93
94 if ((test_mode == TM_EXIT_AFTER_LAST) ||
95 (test_mode == TM_EXIT_AFTER_LAST_IF_OK && !error_count()))
96 {
97 CERR << "Exiting (test_mode " << test_mode << ")" << endl;
98 cleanup(true);
99 if (total_errors) Command::cmd_OFF(1);
100 else Command::cmd_OFF(0);
101 }
102 }
103
104 eof = true;
105 }
106 //-----------------------------------------------------------------------------
107 void
read_file_line(UTF8_string & file_line,bool & eof)108 IO_Files::read_file_line(UTF8_string & file_line, bool & eof)
109 {
110 InputFile * input = InputFile::current_file();
111 for (;;)
112 {
113 const int cc = fgetc(input->file);
114 if (cc == EOF) // end of file
115 {
116 if (file_line.size()) break; // EOF, but we have chars
117
118 eof = true;
119 return;
120 }
121
122 if (cc == '\n' || cc == 2) // end of line or ^B
123 {
124 if (input->current_line_no() == 1 &&
125 file_line.starts_with("<!"))
126 {
127 // first line of the file starts with <! (so we assume that
128 // this file is a HTML tagged file
129 //
130 input->set_html(1);
131 }
132
133 if (input->get_html() > 0) // HTML file
134 {
135 input->set_html(file_line.un_HTML(input->get_html()));
136 if (file_line.size() == 0) continue; // line with tag(s)
137 }
138
139 if (input->has_object_filter())
140 {
141 const bool allowed = input->check_filter(file_line);
142 if (!allowed)
143 {
144 file_line.clear();
145 continue;
146 }
147 }
148 break;
149 }
150
151 if (cc == '\r') continue; // ignore carriage returns
152
153 file_line += cc;
154 }
155
156 Log(LOG_test_execution)
157 CERR << "read_file_line() -> " << file_line << endl;
158 }
159 //-----------------------------------------------------------------------------
160 void
next_file()161 IO_Files::next_file()
162 {
163 if (InputFile::current_file() &&
164 InputFile::current_file()->file)
165 {
166 end_of_current_file();
167 }
168 else
169 {
170 CERR << "]NEXTFILE: no file" << endl;
171 }
172 open_next_file();
173 }
174 //-----------------------------------------------------------------------------
175 bool
end_of_current_file()176 IO_Files::end_of_current_file()
177 {
178 if (InputFile::is_validating()) // running a .tc file
179 {
180 // we expect )SI to be clear after a testcase has finished and
181 // complain if it is not.
182 //
183 if (Workspace::SI_entry_count() > 1)
184 {
185 CERR << endl << ")SI not cleared at the end of "
186 << InputFile::current_filename() << ":" << endl;
187 Workspace::list_SI(CERR, SIM_SIS);
188 CERR << endl;
189
190 if (current_testreport.is_open())
191 {
192 current_testreport << endl
193 << ")SI not cleared at the end of "
194 << InputFile::current_filename()<< ":"
195 << endl;
196 Workspace::list_SI(current_testreport, SIM_SIS);
197 current_testreport << endl;
198 }
199 apl_error(LOC);
200 }
201
202 // check for stale values and indices
203 //
204 if (current_testreport.is_open())
205 {
206 if (Value::print_incomplete(current_testreport))
207 {
208 current_testreport
209 << " (automatic check for incomplete values failed)"
210 << endl;
211 apl_error(LOC);
212 }
213
214 if (Value::print_stale(current_testreport))
215 {
216 current_testreport
217 << " (automatic check for stale values failed,"
218 " offending Value erased)." << endl;
219
220 apl_error(LOC);
221 Value::erase_stale(LOC);
222 }
223
224 if (IndexExpr::print_stale(current_testreport))
225 {
226 current_testreport
227 << " (automatic check for stale indices failed,"
228 " offending IndexExpr erased)." << endl;
229 apl_error(LOC);
230 IndexExpr::erase_stale(LOC);
231 }
232 }
233 }
234
235 InputFile::close_current_file();
236 ++testcases_done;
237
238 Log(LOG_test_execution)
239 CERR << "closed testcase file " << InputFile::current_filename()
240 << endl;
241
242 if (InputFile::is_validating()) // running a .tc file
243 {
244 ofstream summary("testcases/summary.log", ios_base::app);
245 summary << error_count() << " ";
246
247 if (error_count())
248 {
249 total_errors += error_count();
250 summary << "(" << apl_errors << " APL, ";
251 if (apl_errors)
252 summary << " loc=" << last_apl_error_loc
253 << " .tc line=" << last_apl_error_line << endl
254 << " ";
255
256 summary << assert_errors << " assert, "
257 << diff_errors << " diff, "
258 << parse_errors << " parse) ";
259 }
260
261 summary << InputFile::current_filename() << endl;
262
263 if ((test_mode == TM_STOP_AFTER_ERROR ||
264 test_mode == TM_EXIT_AFTER_ERROR) && error_count())
265 {
266 CERR << endl
267 << "Stopping test execution since an error has occurred"
268 << endl
269 << "The error count is " << error_count() << endl
270 << "Failed testcase is " << InputFile::current_filename()
271 << endl
272 << endl;
273
274 InputFile::files_todo.clear();
275 return false;
276 }
277 }
278
279 InputFile::files_todo.erase(InputFile::files_todo.begin());
280
281 if (uprefs.auto_OFF && !InputFile::files_todo.size()) Command::cmd_OFF(0);
282
283 Output::reset_dout();
284 reset_errors();
285 return true; // continue processing
286 }
287 //-----------------------------------------------------------------------------
288 void
print_summary()289 IO_Files::print_summary()
290 {
291 ofstream summary("testcases/summary.log", ios_base::app);
292
293 int done = testcases_done;
294 if (done > testcase_count) done = testcase_count;
295
296 summary << "======================================="
297 "=======================================" << endl
298 << total_errors << " errors in " << done
299 << "(" << testcase_count << ")"
300 << " testcase files" << endl;
301
302 CERR << endl
303 << "======================================="
304 "=======================================" << endl
305 << total_errors << " errors in " << done
306 << "(" << testcase_count << ")"
307 << " testcase files" << endl;
308 }
309 //-----------------------------------------------------------------------------
310 void
open_next_file()311 IO_Files::open_next_file()
312 {
313 if (InputFile::current_file() == 0)
314 {
315 CERR << "IO_Files::open_next_file(): no more files" << endl;
316 return;
317 }
318
319 if (InputFile::current_file()->file)
320 {
321 CERR << "IO_Files::open_next_file(): already open" << endl;
322 return;
323 }
324
325 for (;;)
326 {
327 if (!InputFile::current_file()) break; // no more files
328
329 char log_name[FILENAME_MAX];
330 snprintf(log_name, sizeof(log_name) - 1, "%s.log",
331 InputFile::current_filename());
332
333 if (InputFile::current_file()->test)
334 {
335 CERR << " #######################################"
336 "#####################################\n"
337 << " ########################################"
338 "######################################\n"
339 << " ## Testfile: " << left << setw(60)
340 << InputFile::current_filename() << "##\n"
341 << " ## Log file: " << setw(60) << log_name << "##\n"
342 << " ########################################"
343 "######################################" << endl
344 << " #######################################"
345 "#####################################\n" << endl
346 << right;
347 }
348
349 InputFile::open_current_file();
350 if (InputFile::current_file()->file == 0)
351 {
352 CERR << "could not open "
353 << InputFile::current_filename() << endl;
354 InputFile::files_todo.erase(InputFile::files_todo.begin());
355 continue;
356 }
357
358 Log(LOG_test_execution)
359 CERR << "opened testcase file "
360 << InputFile::current_filename() << endl;
361
362 Output::reset_dout();
363 reset_errors();
364
365 current_testreport.close();
366
367 if (InputFile::current_file()->test)
368 {
369 current_testreport.open(log_name,
370 ofstream::out | ofstream::trunc);
371 if (!current_testreport.is_open())
372 {
373 CERR << "could not open testcase log file " << log_name
374 << "; producing no .log file" << endl;
375 }
376 }
377
378 return;
379 }
380 }
381 //-----------------------------------------------------------------------------
382 void
expect_apl_errors(const UCS_string & arg)383 IO_Files::expect_apl_errors(const UCS_string & arg)
384 {
385 const int cnt = arg.atoi();
386 if (apl_errors == cnt)
387 {
388 Log(LOG_test_execution)
389 CERR << "APL errors reset from (expected) " << apl_errors
390 << " to 0" << endl;
391 apl_errors = 0;
392 last_apl_error_line = -1;
393 last_apl_error_loc = "";
394 }
395 else
396 {
397 Log(LOG_test_execution)
398 CERR << "*** Not reseting APL errors (got " << apl_errors
399 << " expecing " << cnt << endl;
400 }
401 }
402 //-----------------------------------------------------------------------------
403 void
syntax_error()404 IO_Files::syntax_error()
405 {
406 if (!InputFile::current_file()) return;
407 if (!InputFile::current_file()->file) return;
408
409 ++parse_errors;
410
411 Log(LOG_test_execution)
412 CERR << "parse errors incremented to " << parse_errors << endl;
413
414 current_testreport << "**\n** Parse Error ********\n**" << endl;
415 }
416 //-----------------------------------------------------------------------------
417 void
apl_error(const char * loc)418 IO_Files::apl_error(const char * loc)
419 {
420 if (!InputFile::current_file()) return;
421 if (!InputFile::current_file()->file) return;
422
423 ++apl_errors;
424 last_apl_error_loc = loc;
425 last_apl_error_line = InputFile::current_line_no();
426
427 Log(LOG_test_execution)
428 CERR << "APL errors incremented to " << apl_errors << endl;
429 }
430 //-----------------------------------------------------------------------------
431 void
assert_error()432 IO_Files::assert_error()
433 {
434 if (!InputFile::current_file()) return;
435 if (!InputFile::current_file()->file) return;
436
437 ++assert_errors;
438 Log(LOG_test_execution)
439 CERR << "Assert errors incremented to " << assert_errors << endl;
440 }
441 //-----------------------------------------------------------------------------
442 void
diff_error()443 IO_Files::diff_error()
444 {
445 if (!InputFile::current_file()) return;
446 if (!InputFile::current_file()->file) return;
447
448 ++diff_errors;
449 Log(LOG_test_execution)
450 CERR << "Diff errors incremented to " << diff_errors << endl;
451 }
452 //-----------------------------------------------------------------------------
453