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 javax.servlet.*;
013import javax.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  
091  if (files.length == 0)
092    log.warn("No *.mp test files found in directory: ", testdir.getAbsolutePath());
093    
094  Watch wp = new Watch("parse");
095  Watch wc = new Watch("compile");
096  
097  int crap_count = 0;
098  int generate_count = 0;
099  for (int n = 0; n < files.length; n++) 
100    {
101    File test = files[n];
102    log.bug("Processing file: ", test.getName()); 
103        
104    //some files are not expected to compile (as part of the test)
105    //those files start with "crap..."
106    boolean crap = test.getName().toLowerCase().startsWith("crap");
107    if (crap) {
108      crap_count++;
109      }
110      
111    String result_file_str = test.getName();
112    int i = result_file_str.lastIndexOf(".");
113    String outbase = result_file_str.substring(0, i);
114    String outname = outbase + ".java";
115    
116    File expected = new File(testdir, outname);
117    File out      = new File(tmpdir, outname);
118    
119    PageParser p;
120    PageCompiler pc;
121    CharArrayWriter buf = new CharArrayWriter();
122    
123    //generate
124    if (generate) 
125      {
126      try {
127        buf.reset();
128        buf.append("Generate: ").append(
129          String.format("%-20s",test.getName()));
130
131        wp.start();
132        p = new PageParser(testdir, test, expected, outbase);
133        p.parse();
134        wp.stop();
135        
136        buf.append(" [parsed in:").
137          append(
138            String.format("%3s",
139            String.valueOf(wp.getTime())))
140          .append( " ms, "); 
141        //successful parse, no parse exceptions
142        //there may be compile time errors though (expected for
143        //crap files)
144        wc.start();
145        pc = new PageCompiler(expected, System.getProperty("java.class.path"), null);
146        boolean compiled = pc.compile();
147        wc.stop();
148
149        if (! compiled) {
150          buf.append(" compile error...possibly expected]"); 
151          log.bug(pc.getError());
152          }
153        else{
154          buf.append(" compiled in: ").
155            append(
156              String.format("%5s",
157              String.valueOf(wc.getTime())))
158            .append(" ms]"); 
159          //remove class file
160          File f = new File(testdir, outbase + ".class");
161          if (f.exists()) {
162            f.delete();
163            }
164          //compiled ok too.
165          generate_count++;
166          }
167        }
168      catch (Exception e) 
169        {
170        buf.append(" [parse error...possibly expected]"); 
171//        if (! crap) { //crap files are expected to have errors
172          msg(test.getName(),  "Parse Error: " + e.toString());
173//          }
174        }
175
176      log.info(buf.toString());
177      continue;
178      } //end if (generate)
179  
180    //test
181    
182    //we need an expected file to compare with
183    if (! expected.exists()) 
184      {
185      if (! crap) {  //wont exist for crap files cause parse error
186        msg(test.getName(), "FAILED [expected file: " +  expected.getAbsolutePath() + " does not exist]");
187        continue;
188        }
189      }
190      
191    p = new PageParser(testdir, test, out, outbase);
192    
193    try {
194      p.parse();
195      //crap files will cause a parse exception
196      //if here, then parse successful
197      if (! compare(expected, out)) {
198        msg(test.getName(), "FAIL [expected != actual]");
199        }
200      else{
201        msg(test.getName(), "OK");
202        }
203      }
204    catch (Exception e) {
205      if (crap) {
206        //we wanted the expected parse exception
207        msg(test.getName(), "OK");
208        }
209      else{
210        msg(test.getName(), "FAIL [ParseException] " + e.toString());
211        }
212      }
213    
214    } //end for loop
215  
216  if (generate) {
217    System.out.println();
218    System.out.println((files.length - crap_count) + " total (non-crap) *.mp files in " + testdir.getAbsolutePath());
219    System.out.println(generate_count + " expected files were generated.");
220    }
221  
222  //print results
223  System.out.println();
224  TablePrinter.PrintConfig pconfig = new TablePrinter.PrintConfig();
225  pconfig.setAutoFit(false).setPrintBorders(true).setCellPadding(1);
226  if (generate) {
227    pconfig.setCellWidthForColumn(1,55);
228    System.out.println("The following errors are expected!");
229    }
230  TablePrinter printer = new TablePrinter(2, System.out, pconfig);
231  printer.startTable();
232  for (int n = 0; n < results.size(); n++) {
233    printer.startRow();
234    Object[] arr = (Object[]) results.get(n);
235    printer.printCell(arr[0].toString());
236    printer.printCell(arr[1].toString());
237    printer.endRow();
238    }
239  printer.endTable();
240  }
241
242static void msg(Object testName, Object result)
243  {
244  results.add(new Object[] {testName, result});
245  }
246
247static boolean compare(File expected, File output) throws Exception
248  {
249  if (! output.exists())
250    return false;
251    
252  byte[] expected_b = IOUtil.fileToByteArray(expected);
253  byte[] output_b   = IOUtil.fileToByteArray(output);
254  
255  return Arrays.equals(expected_b, output_b);
256  }
257
258}