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 * <taskdef name="QetestTask" classname="org.apache.qetest.xsl.XSLTestAntTask"/> 56 * <target name="test"> 57 * <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