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