001// Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 
002// The Molly framework is freely distributable under the terms of an
003// MIT-style license. For details, see the molly pages web site at:
004// http://www.mollypages.org/. Use, modify, have fun !
005
006package fc.web.page;
007
008import java.io.*;
009import java.util.*;
010import java.net.URL;
011
012import jakarta.servlet.*;
013import jakarta.servlet.http.*;
014
015import fc.util.*;
016import fc.io.*;
017import fc.io.fileselectors.*;
018
019/** 
020Regression testing since eyeballing parser output just ain't cutting it. 
021For developers only, useful when the molly parser is hacked/changed and
022we want to ensure that the new parser does not break any existing behavior.
023<p>
024All regression tests are in the <pre>test</pre> subdirectory (relative to the java
025source of this class). These tests exist as *.mp files. This program will
026run the parser on each file and a) either check actual output with expected
027output and/or b) see if an expected error occurs.
028<p>
029The expected/canonical output (generated by the good working parser) also
030always exists in the <pre>test</pre> subdirectory. The <i>actual</i>
031output generated by the latest hacked parser is output in a temporary
032directory (the location of which is specified on the command line). The
033output are then compared and should be identical.
034<p>
035The expected output can be regenerated via the <pre>generateExpected</pre>
036argument but this should be used by caution. Future/actual output of the
037changed/hacked parser is compared with the last good generated expected
038output so the expected output should only be updated when the parser is 
039in a known working state.
040
041@author hursh jain
042*/
043public class PageParserTest
044{
045static  List results = new ArrayList();
046
047public static void main (String args[]) throws Exception
048  { 
049  Args myargs = new Args(args);
050  Log log = Log.getDefault();
051  myargs.setUsage("java " + myargs.getMainClassName() 
052        + "\n---- Specify ---"
053        + "\n   -test   <if specified, run all unit tests with the latest parser>"
054        + "\n   -tmpdir <location to a tmp dir used for generated files"
055        + "\n----- or ----"
056        + "\n   -generateExpected if specified, expected/canonical parser output "
057        + "\n                     is generated. Use with caution. The servlet API"
058        + "\n                     along with all the molly classes must be on the"
059        + "\n                     system classpath for this option to work fully."
060    );
061
062  boolean testing  = myargs.flagExists("test");
063  boolean generate = myargs.flagExists("generateExpected");
064  File  tmpdir = null;
065  
066  if (! testing && ! generate) 
067    myargs.showError();
068
069  if (testing) 
070    {
071    tmpdir = new File(myargs.getRequired("tmpdir"));
072    if (! tmpdir.exists()) {
073      log.error("The specified temp directory: " + tmpdir.getAbsolutePath() + " does not exist");
074      return;
075      }
076    }
077
078  Class c = new PageParserTest().getClass();
079  URL url = c.getResource("test");
080  //System.out.println(url + ";" + url.getFile());  
081  File testdir = new File(url.toURI());
082  if (! testdir.isDirectory()) {
083    log.error(testdir + " is not a directory. This test does not work from within jars. For development, you need to explode the jar");
084    return;
085    }
086    
087  log.bug("Regression test directory: " + testdir.getAbsolutePath());
088
089  File[] files = testdir.listFiles(new SuffixSelector(".mp"));
090  Arrays.sort(files);
091  
092  if (files.length == 0)
093    log.warn("No *.mp test files found in directory: ", testdir.getAbsolutePath());
094    
095  Watch wp = new Watch("parse");
096  Watch wc = new Watch("compile");
097  
098  int crap_count = 0;
099  int generate_count = 0;
100  for (int n = 0; n < files.length; n++) 
101    {
102    File test = files[n];
103    log.bug("Processing file: ", test.getName()); 
104        
105    //some files are not expected to compile (as part of the test)
106    //those files start with "crap..."
107    boolean crap = test.getName().toLowerCase().startsWith("crap");
108    if (crap) {
109      crap_count++;
110      }
111      
112    String result_file_str = test.getName();
113    int i = result_file_str.lastIndexOf(".");
114    String outbase = result_file_str.substring(0, i);
115    String outname = outbase + ".java";
116    
117    File expected = new File(testdir, outname);
118    File out      = new File(tmpdir, outname);
119    
120    PageParser p;
121    PageCompiler pc;
122    CharArrayWriter buf = new CharArrayWriter();
123    
124    //generate
125    if (generate) 
126      {
127      try {
128        buf.reset();
129        buf.append("Generate: ").append(
130          String.format("%-20s",test.getName()));
131
132        wp.start();
133        p = new PageParser(testdir, test, expected, outbase);
134        p.parse();
135        wp.stop();
136        
137        buf.append(" [parsed in:").
138          append(
139            String.format("%3s",
140            String.valueOf(wp.getTime())))
141          .append( " ms, "); 
142        //successful parse, no parse exceptions
143        //there may be compile time errors though (expected for
144        //crap files)
145        wc.start();
146        pc = new PageCompiler(expected, System.getProperty("java.class.path"), null);
147        boolean compiled = pc.compile();
148        wc.stop();
149
150        if (! compiled) {
151          buf.append(" compile error...possibly expected]"); 
152          log.bug(pc.getError());
153          }
154        else{
155          buf.append(" compiled in: ").
156            append(
157              String.format("%5s",
158              String.valueOf(wc.getTime())))
159            .append(" ms]"); 
160          //remove class file
161          File f = new File(testdir, outbase + ".class");
162          if (f.exists()) {
163            f.delete();
164            }
165          //compiled ok too.
166          generate_count++;
167          }
168        }
169      catch (Exception e) 
170        {
171        buf.append(" [parse error...possibly expected]"); 
172//        if (! crap) { //crap files are expected to have errors
173          msg(test.getName(),  "Parse Error: " + e.toString());
174//          }
175        }
176
177      log.info(buf.toString());
178      continue;
179      } //end if (generate)
180  
181    //test
182    
183    //we need an expected file to compare with
184    if (! expected.exists()) 
185      {
186      if (! crap) {  //wont exist for crap files cause parse error
187        msg(test.getName(), "FAILED [expected file: " +  expected.getAbsolutePath() + " does not exist]");
188        continue;
189        }
190      }
191      
192    p = new PageParser(testdir, test, out, outbase);
193    
194    try {
195      p.parse();
196      //crap files will cause a parse exception
197      //if here, then parse successful
198      if (! compare(expected, out)) {
199        msg(test.getName(), "FAIL [expected != actual]");
200        }
201      else{
202        msg(test.getName(), "OK");
203        }
204      }
205    catch (Exception e) {
206      if (crap) {
207        //we wanted the expected parse exception
208        msg(test.getName(), "OK");
209        }
210      else{
211        msg(test.getName(), "FAIL [ParseException] " + e.toString());
212        }
213      }
214    
215    } //end for loop
216  
217  if (generate) {
218    System.out.println();
219    System.out.println((files.length - crap_count) + " total (non-crap) *.mp files in " + testdir.getAbsolutePath());
220    System.out.println(generate_count + " expected files were generated.");
221    }
222  
223  //print results
224  System.out.println();
225  TablePrinter.PrintConfig pconfig = new TablePrinter.PrintConfig();
226  pconfig.setAutoFit(false).setPrintBorders(true).setCellPadding(1);
227  if (generate) {
228    pconfig.setCellWidthForColumn(1,55);
229    System.out.println("The following errors are expected!");
230    }
231  TablePrinter printer = new TablePrinter(2, System.out, pconfig);
232  printer.startTable();
233  for (int n = 0; n < results.size(); n++) {
234    printer.startRow();
235    Object[] arr = (Object[]) results.get(n);
236    printer.printCell(arr[0].toString());
237    printer.printCell(arr[1].toString());
238    printer.endRow();
239    }
240  printer.endTable();
241  }
242
243static void msg(Object testName, Object result)
244  {
245  results.add(new Object[] {testName, result});
246  }
247
248static boolean compare(File expected, File output) throws Exception
249  {
250  if (! output.exists())
251    return false;
252    
253  byte[] expected_b = IOUtil.fileToByteArray(expected);
254  byte[] output_b   = IOUtil.fileToByteArray(output);
255  
256  return Arrays.equals(expected_b, output_b);
257  }
258
259}