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    
006    package fc.web.page;
007    
008    import java.io.*;
009    import java.util.*;
010    import java.net.URL;
011    
012    import javax.servlet.*;
013    import javax.servlet.http.*;
014    
015    import fc.util.*;
016    import fc.io.*;
017    import fc.io.fileselectors.*;
018    
019    /** 
020    Regression testing since eyeballing parser output just ain't cutting it. 
021    For developers only, useful when the molly parser is hacked/changed and
022    we want to ensure that the new parser does not break any existing behavior.
023    <p>
024    All regression tests are in the <pre>test</pre> subdirectory (relative to the java
025    source of this class). These tests exist as *.mp files. This program will
026    run the parser on each file and a) either check actual output with expected
027    output and/or b) see if an expected error occurs.
028    <p>
029    The expected/canonical output (generated by the good working parser) also
030    always exists in the <pre>test</pre> subdirectory. The <i>actual</i>
031    output generated by the latest hacked parser is output in a temporary
032    directory (the location of which is specified on the command line). The
033    output are then compared and should be identical.
034    <p>
035    The expected output can be regenerated via the <pre>generateExpected</pre>
036    argument but this should be used by caution. Future/actual output of the
037    changed/hacked parser is compared with the last good generated expected
038    output so the expected output should only be updated when the parser is 
039    in a known working state.
040    
041    @author hursh jain
042    */
043    public class PageParserTest
044    {
045    static  List results = new ArrayList();
046    
047    public 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    
242    static void msg(Object testName, Object result)
243      {
244      results.add(new Object[] {testName, result});
245      }
246    
247    static 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    }