• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the  "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 /*
19  * $Id$
20  */
21 package org.apache.qetest.xsl;
22 
23 import org.apache.qetest.FileBasedTest;
24 import org.apache.qetest.Logger;
25 import org.apache.qetest.QetestUtils;
26 import org.apache.tools.ant.BuildException;
27 import org.apache.tools.ant.Project;
28 import org.apache.tools.ant.Task;
29 import org.apache.tools.ant.types.Commandline;
30 import org.apache.tools.ant.types.CommandlineJava;
31 import org.apache.tools.ant.types.Environment;
32 import org.apache.tools.ant.types.Path;
33 import org.apache.tools.ant.types.Reference;
34 import org.apache.tools.ant.taskdefs.Execute;
35 import org.apache.tools.ant.taskdefs.ExecuteJava;
36 import org.apache.tools.ant.taskdefs.LogStreamHandler;
37 import org.apache.tools.ant.taskdefs.PumpStreamHandler;
38 
39 import java.io.File;
40 import java.io.FileOutputStream;
41 import java.io.IOException;
42 import java.io.PrintStream;
43 
44 import java.util.Enumeration;
45 import java.util.Hashtable;
46 import java.util.Properties;
47 import java.util.Vector;
48 
49 /**
50  * Execute an instance of an org.apache.qetest.xsl.XSLProcessorTestBase.
51  *
52  * Cheap-o (for now) way to run qetest or Xalan tests directly
53  * from an Ant build file.  Current usage:
54  * <code>
55  * &lt;taskdef name="QetestTask" classname="org.apache.qetest.xsl.XSLTestAntTask"/>
56  *  &lt;target name="test">
57  *      &lt;QetestTask
58  *          test="Minitest"
59  *          loggingLevel="50"
60  *          consoleLoggingLevel="40"
61  *          inputDir="../tests/api"
62  *          goldDir="../tests/api-gold"
63  *          outputDir="../tests/minitest"
64  *          logFile="../tests/minitest/log.xml"
65  *          flavor="trax"
66  *       />
67  * </code>
68  * To be improved: I'd like to basically convert XSLTestHarness
69  * into an Ant task, so you can run multiple tests at once.
70  * Other obvious improvements include an AntLogger implementation
71  * of Logger and better integration with the Project and
72  * the various ways build scripts use properties.
73  * Also, various properties should really have default values.
74  *
75  * Blatantly ripped off from org.apache.tools.ant.taskdefs.Java Ant 1.3
76  *
77  * @author <a href="mailto:shane_curcuru@lotus.com">Shane Curcuru</a>
78  * @author Stefano Mazzocchi <a href="mailto:stefano@apache.org">stefano@apache.org</a>
79  * @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
80  * @version $Id$
81  */
82 public class XSLTestAntTask extends Task
83 {
84 
85     /** Representation of command line to run for our test.  */
86     protected CommandlineJava commandLineJava = new CommandlineJava();
87 
88     /** If we should fork another JVM to execute the test.  */
89     protected boolean fork = false;
90 
91     /** If forking, current dir to set new JVM in.  */
92     protected File dir = null;
93 
94     /** Alternate Ant output file to use.  */
95     protected File out;
96 
97     /**
98      * If Ant errors/problems should throw a BuildException.
99      * Note: This does not fail if the test fails, only on a
100      * serious error or problem running the test.
101      */
102     protected boolean failOnError = false;
103 
104     /** Normal constants for testType: API tests.  */
105     public static final String TESTTYPE_API = "api.";
106 
107     /** Normal constants for testType: API tests.  */
108     public static final String TESTTYPE_CONF = "conf.";
109 
110     /** Normal constants for testType: API tests.  */
111     public static final String TESTTYPE_PERF = "perf.";
112 
113     /** Normal constants for testType: API tests.  */
114     public static final String TESTTYPE_CONTRIB = "contrib.";
115 
116     /**
117      * Type of test we're executing.
118      * Used by passThruProps to determine which kind of prefixed
119      * properties from the Ant file to pass thru to the test.
120      * Normally one of api|conf|perf|contrib, etc.
121      */
122     protected String testType = TESTTYPE_API;
123 
124     /**
125      * Name of the class to execute as the test.
126      */
127     protected String testClass = null;
128 
129     /**
130      * Cheap-o way to pass properties to the underlying test.
131      * This task simply writes out needed properties to this file,
132      * then tells the test to -load them when executing.
133      */
134     protected String passThruProps = "XSLTestAntTask.properties";
135 
136     //-----------------------------------------------------
137     //-------- Implementations for test-related parameters --------
138     //-----------------------------------------------------
139 
140     /**
141      * Test parameter: Set the type of this test.
142      *
143      * @param ll loggingLevel passed to test for all
144      * non-console output; 0=very little output, 99=lots
145      * @see org.apache.qetest.Reporter#setLoggingLevel(int)
146      */
setTestType(String type)147     public void setTestType(String type)
148     {
149         log("setTestType(" + type + ")", Project.MSG_VERBOSE);
150         testType = type;
151     }
152 
153 
154     /**
155      * Test parameter: Name of test class to execute.
156      *
157      * Replacement for Java's setClassname property; accepts the
158      * name of a specific Test subclass.  Note that we use
159      * {@link org.apache.qetest.QetestUtils.testClassForName(String, String[], String) QetestUtils.testClassForName}
160      * to actually get the FQCN of the class to run; this allows
161      * users to just specify the name of the class itself
162      * (e.g. SystemIdTest) and have it work properly.
163      * We search a number of default packages in order if needed:
164      * as seen in QetestUtils.testClassForName().
165      * Note! Due to Ant's interesting handling of classpaths, we
166      * cannot actually resolve the testname here - we simply store
167      * the string, and then let QetestUtils resolve it later
168      * when we're actually executing.  That's because any classpaths
169      * that were set in the build.xml file aren't valid here, when
170      * the task is setting properties - they're only valid when the
171      * task actually executes later on.
172      *
173      * @param testClassname FQCN or just bare classname
174      * of test to run
175      */
setTest(String testClassname)176     public void setTest(String testClassname)
177     {
178         log("setTest(" + testClassname + ")", Project.MSG_VERBOSE);
179         testClass = testClassname;
180 
181         // Force the actual class being executed to be a 'launcher' class
182         commandLineJava.setClassname("org.apache.qetest.QetestUtils");
183         // Note this needs to be the first argument in the line,
184         //  thus this should be the first property set
185         //@todo fix this so users can actually use other properties
186         //  first without the ordering problem
187         commandLineJava.createArgument().setLine(testClass);
188     }
189 
190 
191     /**
192      * Test parameter: Set the loggingLevel used in this test.
193      * //@todo deprecate: should use passThruProps instead
194      *
195      * @param ll loggingLevel passed to test for all
196      * non-console output; 0=very little output, 99=lots
197      * @see org.apache.qetest.Reporter#setLoggingLevel(int)
198      */
setLoggingLevel(int ll)199     public void setLoggingLevel(int ll)
200     {
201 
202         // Is this really the simplest way to stuff data into
203         //  objects in the 'proper Ant way'?
204         commandLineJava.createArgument().setLine("-"
205                                                  + Logger.OPT_LOGGINGLEVEL
206                                                  + " "
207                                                  + Integer.toString(ll));
208     }
209 
210 
211     /**
212      * Test parameter: Set the consoleLoggingLevel used in this test.
213      * //@todo deprecate: should use passThruProps instead
214      *
215      * @param ll loggingLevel used just for console output; here,
216      * the default log going to Ant's console
217      * @see org.apache.qetest.ConsoleLogger
218      */
setConsoleLoggingLevel(int ll)219     public void setConsoleLoggingLevel(int ll)
220     {
221         commandLineJava.createArgument().setLine(
222             "-ConsoleLogger.loggingLevel " + Integer.toString(ll));
223     }
224 
225 
226     /**
227      * Test parameter: inputDir, root of input files tree (required).
228      * //@todo deprecate: should use passThruProps instead
229      *
230      * //@todo this should have a default, since without a valid
231      * value most tests will just return an error
232      * @param d Path to look for input files in: should be the
233      * root of the applicable tests/api, tests/conf, etc. tree
234      * @see org.apache.qetest.FileBasedTest#OPT_INPUTDIR
235      */
setInputDir(Path d)236     public void setInputDir(Path d)
237     {
238 
239         commandLineJava.createArgument().setValue(
240             "-" + FileBasedTest.OPT_INPUTDIR);
241         commandLineJava.createArgument().setPath(d);
242     }
243 
244 
245     /**
246      * Test parameter: outputDir, dir to put outputs in.
247      * //@todo deprecate: should use passThruProps instead
248      * @param d where the test will put it's output files
249      * @see org.apache.qetest.FileBasedTest#OPT_OUTPUTDIR
250      */
setOutputDir(Path d)251     public void setOutputDir(Path d)
252     {
253 
254         commandLineJava.createArgument().setValue(
255             "-" + FileBasedTest.OPT_OUTPUTDIR);
256         commandLineJava.createArgument().setPath(d);
257     }
258 
259 
260     /**
261      * Test parameter: goldDir, root of gold files tree.
262      * //@todo deprecate: should use passThruProps instead
263      * @param d Path to look for gold files in: should be the
264      * root of the applicable tests/api-gold, tests/conf-gold, etc. tree
265      * @see org.apache.qetest.FileBasedTest#OPT_GOLDDIR
266      */
setGoldDir(Path d)267     public void setGoldDir(Path d)
268     {
269 
270         commandLineJava.createArgument().setValue(
271             "-" + FileBasedTest.OPT_GOLDDIR);
272         commandLineJava.createArgument().setPath(d);
273     }
274 
275 
276     /**
277      * Test parameter: logFile, where to put XMLFileLogger output.
278      * //@todo deprecate: should use passThruProps instead
279      * @param f File(name) to send our 'official' results to via
280      * an {@link org.apache.qetest.XMLFileLogger XMLFileLogger}
281      */
setLogFile(File f)282     public void setLogFile(File f)
283     {
284         commandLineJava.createArgument().setValue("-" + Logger.OPT_LOGFILE);
285         commandLineJava.createArgument().setFile(f);  // Check if this is what the test is expecting
286     }
287 
288 
289     /**
290      * Default prefix of Ant properties to passThru to the test.
291      * Note that testType is also a dynamic prefix that's also used.
292      */
293     public static final String ANT_PASSTHRU_PREFIX = "qetest.";
294 
295 
296     /**
297      * Worker method to write out properties file for test.
298      * Simply translates any properties in your Ant build file that
299      * begin with the prefix, and puts them in a Properties block.
300      * This block is then written out to disk, so that the test can
301      * later read them in via -load.
302      * //@todo NEEDS IMPROVEMENT: make more robust; check for write
303      * access to local dir; support dir-switching attribute
304      * when forking from Ant task; etc.
305      * @param altPrefix alternate prefix of Ant properties to also
306      * pass thru in addition to ANT_PASSTHRU_PREFIX; these will
307      * override any of the default prefix ones
308      */
writePassThruProps(String altPrefix)309     protected void writePassThruProps(String altPrefix)
310     {
311         Hashtable antProps = this.getProject().getProperties();
312 
313         Properties passThru = new Properties();
314         // Passthru any of the default prefixed properties..
315         for (Enumeration keys = antProps.keys();
316                 keys.hasMoreElements();
317                 /* no increment portion */ )
318         {
319             String key = keys.nextElement().toString();
320             if (key.startsWith(ANT_PASSTHRU_PREFIX))
321             {
322                 // Move any of these properties into the test;
323                 //  rip off the prefix first
324                 passThru.put(key.substring(ANT_PASSTHRU_PREFIX.length()), antProps.get(key));
325             }
326         }
327         //.. Then also passthru any alternate prefix properties
328         //  this ensures alternate prefixes will overwrite default ones
329         for (Enumeration keys = antProps.keys();
330                 keys.hasMoreElements();
331                 /* no increment portion */ )
332         {
333             String key = keys.nextElement().toString();
334             if (key.startsWith(altPrefix))
335             {
336                 // Also move alternate prefixed properties too
337                 passThru.put(key.substring(altPrefix.length()), antProps.get(key));
338             }
339         }
340         // Make sure to write to the basedir of the project!
341         File baseDir = this.getProject().getBaseDir();
342         String propFileName = baseDir.getPath() + File.separator + passThruProps;
343         log("writePassThruProps attempting to write to " + propFileName, Project.MSG_VERBOSE);
344         try
345         {
346             // If we can write the props out to disk...
347             passThru.save(new FileOutputStream(propFileName),
348                     "XSLTestAntTask.writePassThruProps() generated for use by test " + testClass);
349 
350             // ... then also force -load of this file into test's command line
351             commandLineJava.createArgument().setLine("-load " + passThruProps);
352         }
353         catch (IOException ioe)
354         {
355             throw new BuildException("writePassThruProps could not write to " + propFileName + ", threw: "
356                                      + ioe.toString(), location);
357         }
358     }
359 
360 
361     //-----------------------------------------------------
362     //-------- Implementations from Java task --------
363     //-----------------------------------------------------
364 
365     /**
366      * Execute this task.
367      *
368      * Basically just calls the
369      * {@link #executeJava() executeJava() worker method} to do
370      * all the work of executing the task.  Then checks the
371      * failOnError member to see if we should throw an exception.
372      *
373      * @throws BuildException
374      */
execute()375     public void execute() throws BuildException
376     {
377         // Log out our version info: useful for debugging, since
378         //  the wrong version of this class can easily get loaded
379         log("XSLTestAntTask: $Id$", Project.MSG_VERBOSE);
380 
381         // Call worker method to create and write prop file
382         // This passes thru both default 'qetest.' properties as
383         //  well as properties associated with testType
384         writePassThruProps(testType);
385 
386         int err = -1;
387 
388         if ((err = executeJava()) != 0)
389         {
390             if (failOnError)
391             {
392                 throw new BuildException("XSLTestAntTask execution returned: "
393                                          + err, location);
394             }
395             else
396             {
397                 log("XSLTestAntTask Result: " + err, Project.MSG_ERR);
398             }
399         }
400     }
401 
402 
403     /**
404      * Worker method to do the execution and return a return code.
405      *
406      * @return the return code from the execute java class if it
407      * was executed in a separate VM (fork = "yes").
408      *
409      * @throws BuildException
410      */
executeJava()411     public int executeJava() throws BuildException
412     {
413 
414         String classname = commandLineJava.getClassname();
415 
416 
417         if (classname == null)
418         {
419             throw new BuildException("Classname must not be null.");
420         }
421 
422         if (fork)
423         {
424             log("Forking " + commandLineJava.toString(), Project.MSG_VERBOSE);
425 
426             return run(commandLineJava.getCommandline());
427         }
428         else
429         {
430             if (commandLineJava.getVmCommand().size() > 1)
431             {
432                 log("JVM args ignored when same JVM is used.",
433                     Project.MSG_WARN);
434             }
435 
436             if (dir != null)
437             {
438                 log("Working directory ignored when same JVM is used.",
439                     Project.MSG_WARN);
440             }
441 
442             log("Running in same VM "
443                 + commandLineJava.getJavaCommand().toString(), Project.MSG_VERBOSE);
444             run(commandLineJava);
445 
446             return 0;
447         }
448     }
449 
450 
451     /**
452      * Set the classpath to be used for this test.
453      *
454      * @param s classpath used for running the test
455      */
setClasspath(Path s)456     public void setClasspath(Path s)
457     {
458         createClasspath().append(s);
459     }
460 
461 
462     /**
463      * Creates a nested classpath element
464      *
465      * @return classpath element to set for this test
466      */
createClasspath()467     public Path createClasspath()
468     {
469         return commandLineJava.createClasspath(project).createPath();
470     }
471 
472 
473     /**
474      * Adds a reference to a CLASSPATH defined elsewhere.
475      *
476      * @param r reference to the CLASSPATH
477      */
setClasspathRef(Reference r)478     public void setClasspathRef(Reference r)
479     {
480         createClasspath().setRefid(r);
481     }
482 
483 
484     /**
485      * Creates a nested arg element.
486      *
487      * @return Argument to send to our test
488      */
createArg()489     public Commandline.Argument createArg()
490     {
491         return commandLineJava.createArgument();
492     }
493 
494 
495     /**
496      * Set the forking flag.
497      *
498      * @param s true if we should fork; false otherwise
499      */
setFork(boolean s)500     public void setFork(boolean s)
501     {
502         this.fork = s;
503     }
504 
505 
506     /**
507      * Creates a nested jvmarg element.
508      *
509      * @return Argument to send to our JVM if forking
510      */
createJvmarg()511     public Commandline.Argument createJvmarg()
512     {
513         return commandLineJava.createVmArgument();
514     }
515 
516 
517     /**
518      * Set the command used to start the VM (only if fork==false).
519      *
520      * @param s vm command used
521      */
setJvm(String s)522     public void setJvm(String s)
523     {
524         commandLineJava.setVm(s);
525     }
526 
527 
528     /**
529      * Add a nested sysproperty element.
530      *
531      * @param sysp to send to our test/JVM
532      */
addSysproperty(Environment.Variable sysp)533     public void addSysproperty(Environment.Variable sysp)
534     {
535         commandLineJava.addSysproperty(sysp);
536     }
537 
538 
539     /**
540      * Throw a BuildException if process returns non 0.
541      *
542      * @param fail if we should fail on serious errors
543      */
setFailonerror(boolean fail)544     public void setFailonerror(boolean fail)
545     {
546         failOnError = fail;
547     }
548 
549 
550     /**
551      * The working directory of the process, if forked.
552      *
553      * @param d current directory for test, if forked
554      */
setDir(File d)555     public void setDir(File d)
556     {
557         this.dir = d;
558     }
559 
560 
561     /**
562      * File the output of the process is redirected to.
563      *
564      * @param out output file for Ant output (not just test output)
565      */
setOutput(File out)566     public void setOutput(File out)
567     {
568         this.out = out;
569     }
570 
571 
572     /**
573      * -mx or -Xmx depending on VM version
574      *
575      * @param max max Java memory to use for test execution
576      */
setMaxmemory(String max)577     public void setMaxmemory(String max)
578     {
579 
580         if (Project.getJavaVersion().startsWith("1.1"))
581         {
582             createJvmarg().setValue("-mx" + max);
583         }
584         else
585         {
586             createJvmarg().setValue("-Xmx" + max);
587         }
588     }
589 
590 
591     /**
592      * Executes the given classname with the given arguments as if
593      * it was a command line application.
594      * Explicitly adds test-specific args from our members.
595      *
596      * @param command object to execute
597      *
598      * @throws BuildException thrown if IOException thrown internally
599      */
run(CommandlineJava command)600     private void run(CommandlineJava command) throws BuildException
601     {
602 
603         ExecuteJava exe = new ExecuteJava();
604 
605 
606         exe.setJavaCommand(command.getJavaCommand());
607         exe.setClasspath(command.getClasspath());
608         exe.setSystemProperties(command.getSystemProperties());
609 
610         if (out != null)
611         {
612             try
613             {
614                 exe.setOutput(new PrintStream(new FileOutputStream(out)));
615             }
616             catch (IOException io)
617             {
618                 throw new BuildException(io, location);
619             }
620         }
621 
622         exe.execute(project);
623     }
624 
625 
626     /**
627      * Executes the given classname with the given arguments in a separate VM.
628      *
629      * @param command line args to execute
630      *
631      * @return status from VM execution
632      *
633      * @throws BuildException thrown if IOException thrown internally
634      */
run(String[] command)635     private int run(String[] command) throws BuildException
636     {
637 
638         FileOutputStream fos = null;
639 
640 
641         try
642         {
643             Execute exe = null;
644 
645 
646             if (out == null)
647             {
648                 exe = new Execute(
649                     new LogStreamHandler(
650                     this, Project.MSG_INFO, Project.MSG_WARN), null);
651             }
652             else
653             {
654                 fos = new FileOutputStream(out);
655                 exe = new Execute(new PumpStreamHandler(fos), null);
656             }
657 
658             exe.setAntRun(project);
659 
660             if (dir == null)
661             {
662                 dir = project.getBaseDir();
663             }
664             else if (!dir.exists() ||!dir.isDirectory())
665             {
666                 throw new BuildException(
667                     dir.getAbsolutePath() + " is not a valid directory",
668                     location);
669             }
670 
671             exe.setWorkingDirectory(dir);
672             exe.setCommandline(command);
673 
674             try
675             {
676                 return exe.execute();
677             }
678             catch (IOException e)
679             {
680                 throw new BuildException(e, location);
681             }
682         }
683         catch (IOException io)
684         {
685             throw new BuildException(io, location);
686         }
687         finally
688         {
689             if (fos != null)
690             {
691                 try
692                 {
693                     fos.close();
694                 }
695                 catch (IOException io){}
696             }
697         }
698     }
699 
700 
701     /**
702      * Executes the given classname with the given arguments as if it
703      * was a command line application.
704      *
705      * @param classname of Java class to execute
706      * @param args for Java class
707      *
708      * @throws BuildException not thrown
709      */
run(String classname, Vector args)710     protected void run(String classname, Vector args) throws BuildException
711     {
712 
713         CommandlineJava cmdj = new CommandlineJava();
714 
715 
716         cmdj.setClassname(classname);
717 
718         for (int i = 0; i < args.size(); i++)
719         {
720             cmdj.createArgument().setValue((String) args.elementAt(i));
721         }
722 
723         run(cmdj);
724     }
725 
726 
727     /**
728      * Clear out the arguments to this java task.
729      */
clearArgs()730     public void clearArgs()
731     {
732         commandLineJava.clearJavaArgs();
733     }
734 
735    /**
736      * Set the bootclasspathref to be used for this test.
737      *
738      * @param s bootclasspathref used for running the test
739      */
setBootclasspathref(Reference r)740     public void setBootclasspathref(Reference r)
741     {
742         // This is a hack.
743         // On JDK 1.4.x or later we need to override bootclasspath
744         // the Xalan/Xerces in rt.jar.
745         String jdkRelease =
746                    System.getProperty("java.version", "0.0").substring(0,3);
747         if (!jdkRelease.equals("1.1")
748                 && !jdkRelease.equals("1.2")
749                 && !jdkRelease.equals("1.3")) {
750             Path p = (Path)r.getReferencedObject(this.getProject());
751             log("Bootclasspath: " + p);
752             createJvmarg().setValue("-Xbootclasspath/p:" + p);
753         }
754     }
755 }
756