001 /* 002 * Copyright 2004-2005 Stephen McConnell 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.tools.tasks; 020 021 import java.io.File; 022 import java.io.IOException; 023 import java.io.InputStream; 024 import java.io.FileInputStream; 025 import java.net.URI; 026 import java.util.Enumeration; 027 import java.util.Properties; 028 import java.util.StringTokenizer; 029 030 import net.dpml.library.Type; 031 import net.dpml.library.Resource; 032 import net.dpml.library.info.Scope; 033 034 import net.dpml.tools.Context; 035 036 import net.dpml.transit.Transit; 037 import net.dpml.util.PropertyResolver; 038 039 import org.apache.tools.ant.BuildException; 040 import org.apache.tools.ant.Project; 041 import org.apache.tools.ant.taskdefs.Exit; 042 import org.apache.tools.ant.taskdefs.optional.junit.BatchTest; 043 import org.apache.tools.ant.taskdefs.optional.junit.FormatterElement; 044 import org.apache.tools.ant.taskdefs.optional.junit.JUnitTask; 045 import org.apache.tools.ant.types.Environment; 046 import org.apache.tools.ant.types.FileSet; 047 import org.apache.tools.ant.types.Path; 048 import org.apache.tools.ant.types.Commandline; 049 050 /** 051 * JUnit test execution. 052 * 053 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 054 * @version 1.1.3 055 */ 056 public class JUnitTestTask extends GenericTask 057 { 058 /** 059 * Constant for lookup of mx value. 060 */ 061 public static final String MX_KEY = "project.test.mx"; 062 063 /** 064 * Constant test enabled key. 065 */ 066 public static final String TEST_ENABLED_KEY = "project.test.enabled"; 067 068 /** 069 * Constant test src directory key. 070 */ 071 public static final String TEST_SRC_KEY = "project.test.src"; 072 073 /** 074 * Constant test env directory key. 075 */ 076 public static final String TEST_ENV_KEY = "project.test.env"; 077 078 /** 079 * Constant test debug key. 080 */ 081 public static final String DEBUG_KEY = "project.test.debug"; 082 083 /** 084 * Constant test fork key. 085 */ 086 public static final String FORK_KEY = "project.test.fork"; 087 088 /** 089 * Constant test fork mode key. 090 */ 091 public static final String TEST_FORK_MODE_KEY = "project.test.fork.mode"; 092 093 /** 094 * Constant test halt-on-error key (default is false). 095 */ 096 public static final String HALT_ON_ERROR_KEY = "project.test.halt-on-error"; 097 098 /** 099 * Constant test halt-on-failure key (default is false). 100 */ 101 public static final String HALT_ON_FAILURE_KEY = "project.test.halt-on-failure"; 102 103 /** 104 * Constant test abort on error key - if true (the default) the build will fail 105 * if a test error occurs. 106 */ 107 public static final String ABORT_ON_ERROR_KEY = "project.test.exit-on-error"; 108 109 /** 110 * Constant test abort on failure key - if true (the default) the build will fail 111 * if a test failure occurs. 112 */ 113 public static final String ABORT_ON_FAILURE_KEY = "project.test.exit-on-failure"; 114 115 /** 116 * Constant cache path key. 117 */ 118 public static final String CACHE_PATH_KEY = "dpml.cache"; 119 120 /** 121 * Constant work dir key. 122 */ 123 public static final String WORK_DIR_KEY = "project.test.dir"; 124 125 /** 126 * the key for the include pattern for test cases 127 */ 128 public static final String TEST_INCLUDES_KEY = "project.test.includes"; 129 130 /** 131 * default value 132 */ 133 public static final String TEST_INCLUDES_VALUE = "**/*TestCase.java, **/*Test.java"; 134 135 /** 136 * the key for the exclude pattern for test cases 137 */ 138 public static final String TEST_EXCLUDES_KEY = "project.test.excludes"; 139 140 /** 141 * default value 142 */ 143 public static final String TEST_EXCLUDES_VALUE = "**/Abstract*.java, **/AllTest*.java"; 144 145 /** 146 * the key for the exclude pattern for test cases 147 */ 148 public static final String VERBOSE_KEY = "project.test.verbose"; 149 150 private static final String ERROR_KEY = "project.test.error"; 151 private static final String FAILURE_KEY = "project.test.failure"; 152 private static final String TEST_SRC_VALUE = "test"; 153 private static final String TEST_ENV_VALUE = "env"; 154 private static final boolean DEBUG_VALUE = true; 155 private static final boolean FORK_VALUE = true; 156 private static final boolean HALT_ON_ERROR_VALUE = false; 157 private static final boolean HALT_ON_FAILURE_VALUE = false; 158 159 private File m_source; 160 private String m_classPathRef; 161 private Path m_classPath; 162 163 /** 164 * Task initialization. 165 * @exception BuildException if a build error occurs. 166 */ 167 public void init() throws BuildException 168 { 169 if( !isInitialized() ) 170 { 171 super.init(); 172 final Project project = getProject(); 173 project.setNewProperty( DEBUG_KEY, "" + DEBUG_VALUE ); 174 project.setNewProperty( FORK_KEY, "" + FORK_VALUE ); 175 project.setNewProperty( TEST_SRC_KEY, "" + TEST_SRC_VALUE ); 176 project.setNewProperty( TEST_ENV_KEY, "" + TEST_ENV_VALUE ); 177 project.setNewProperty( HALT_ON_ERROR_KEY, "" + HALT_ON_ERROR_VALUE ); 178 project.setNewProperty( HALT_ON_FAILURE_KEY, "" + HALT_ON_FAILURE_VALUE ); 179 getContext().getPath( Scope.TEST ); 180 } 181 } 182 183 /** 184 * Set the id of the compilation classpath. 185 * @param id the classpath reference 186 */ 187 public void setClasspathRef( String id ) 188 { 189 m_classPathRef = id; 190 } 191 192 /** 193 * Set the classpath. 194 * @param path the classpath 195 */ 196 public void setClasspath( Path path ) 197 { 198 m_classPath = path; 199 } 200 201 /** 202 * Set the directory containing the unit test source files. 203 * @param source the test source directory 204 */ 205 public void setSrc( File source ) 206 { 207 m_source = source; 208 } 209 210 private Path getClasspath() 211 { 212 if( null != m_classPath ) 213 { 214 return m_classPath; 215 } 216 else if( null != m_classPathRef ) 217 { 218 return (Path) getProject().getReference( m_classPathRef ); 219 } 220 else 221 { 222 final String error = 223 "Missing classpathref or classpath argument."; 224 throw new BuildException( error, getLocation() ); 225 } 226 } 227 228 /** 229 * Task execution. 230 * @exception BuildException if a build error occurs. 231 */ 232 public void execute() throws BuildException 233 { 234 if( !isTestingEnabled() ) 235 { 236 return; 237 } 238 239 final Context context = getContext(); 240 final Project project = getProject(); 241 final File src = context.getTargetBuildTestDirectory(); 242 if( src.exists() ) 243 { 244 final File working = context.getTargetTestDirectory(); 245 final Path classpath = getClasspath(); 246 247 executeUnitTests( src, classpath, working ); 248 249 if( getBooleanProperty( ABORT_ON_ERROR_KEY, true ) ) 250 { 251 final String error = project.getProperty( ERROR_KEY ); 252 if( null != error ) 253 { 254 final String message = 255 "One or more unit test errors occured."; 256 fail( message ); 257 } 258 } 259 260 if( getBooleanProperty( ABORT_ON_FAILURE_KEY, true ) ) 261 { 262 final String failure = project.getProperty( FAILURE_KEY ); 263 if( null != failure ) 264 { 265 final String message = 266 "One or more unit test failures occured."; 267 fail( message ); 268 } 269 } 270 } 271 } 272 273 private void executeUnitTests( final File src, final Path classpath, File working ) 274 { 275 final Project project = getProject(); 276 log( "Test classpath: " + classpath, Project.MSG_VERBOSE ); 277 final FileSet fileset = createFileSet( src ); 278 final JUnitTask junit = (JUnitTask) project.createTask( "junit" ); 279 junit.setTaskName( getTaskName() ); 280 281 final JUnitTask.SummaryAttribute summary = getSummaryAttribute(); 282 junit.setPrintsummary( summary ); 283 284 junit.setShowOutput( true ); 285 junit.setTempdir( working ); 286 junit.setReloading( true ); 287 junit.setFiltertrace( true ); 288 289 junit.createClasspath().add( classpath ); 290 291 Context context = getContext(); 292 String verbose = getVerboseArgument(); 293 if( null != verbose ) 294 { 295 Commandline.Argument arg = junit.createJvmarg(); 296 arg.setValue( "-verbose:" + verbose ); 297 } 298 299 final File reports = getContext().getTargetReportsTestDirectory(); 300 mkDir( reports ); 301 302 final BatchTest batch = junit.createBatchTest(); 303 batch.addFileSet( fileset ); 304 batch.setTodir( reports ); 305 306 final FormatterElement plain = newConfiguredFormatter( "plain" ); 307 junit.addFormatter( plain ); 308 309 final FormatterElement xml = newConfiguredFormatter( "xml" ); 310 junit.addFormatter( xml ); 311 312 final Environment.Variable work = new Environment.Variable(); 313 work.setKey( WORK_DIR_KEY ); 314 work.setValue( working.toString() ); 315 junit.addConfiguredSysproperty( work ); 316 317 final Environment.Variable testBaseDir = new Environment.Variable(); 318 testBaseDir.setKey( "project.test.dir" ); 319 testBaseDir.setValue( working.toString() ); 320 junit.addConfiguredSysproperty( testBaseDir ); 321 322 final Environment.Variable targetDir = new Environment.Variable(); 323 targetDir.setKey( "project.target.dir" ); 324 targetDir.setValue( getContext().getTargetDirectory().toString() ); 325 junit.addConfiguredSysproperty( targetDir ); 326 327 final Environment.Variable deliverablesDir = new Environment.Variable(); 328 deliverablesDir.setKey( "project.target.deliverables.dir" ); 329 deliverablesDir.setValue( getContext().getTargetDeliverablesDirectory().toString() ); 330 junit.addConfiguredSysproperty( deliverablesDir ); 331 332 final Environment.Variable basedir = new Environment.Variable(); 333 basedir.setKey( "basedir" ); 334 basedir.setValue( project.getBaseDir().toString() ); 335 junit.addConfiguredSysproperty( basedir ); 336 337 final Environment.Variable basedir2 = new Environment.Variable(); 338 basedir2.setKey( "project.basedir" ); 339 basedir2.setValue( project.getBaseDir().toString() ); 340 junit.addConfiguredSysproperty( basedir2 ); 341 342 final Environment.Variable cache = new Environment.Variable(); 343 cache.setKey( CACHE_PATH_KEY ); 344 cache.setValue( getCachePath() ); 345 junit.addConfiguredSysproperty( cache ); 346 347 final File policy = new File( working, "security.policy" ); 348 if( policy.exists() ) 349 { 350 final Environment.Variable security = new Environment.Variable(); 351 security.setKey( "java.security.policy" ); 352 security.setValue( policy.toString() ); 353 junit.addConfiguredSysproperty( security ); 354 } 355 356 setupTestProperties( junit, project, working ); 357 358 final File logProperties = new File( working, "logging.properties" ); 359 if( logProperties.exists() ) 360 { 361 final Environment.Variable log = new Environment.Variable(); 362 log.setKey( "dpml.logging.config" ); 363 try 364 { 365 URI logPropertiesURI = logProperties.toURI(); 366 String logPropertiesSpec = logPropertiesURI.toString(); 367 log.setValue( logPropertiesSpec ); 368 } 369 catch( Exception e ) 370 { 371 final String error = 372 "Unexpected file to url error." 373 + "\nFile: " + logProperties; 374 throw new BuildException( error, e ); 375 } 376 junit.addConfiguredSysproperty( log ); 377 } 378 379 String formatter = getResource().getProperty( "java.util.logging.config.class" ); 380 if( null != formatter ) 381 { 382 final Environment.Variable logging = new Environment.Variable(); 383 logging.setKey( "java.util.logging.config.class" ); 384 if( "dpml".equals( formatter ) ) 385 { 386 logging.setValue( "net.dpml.util.ConfigurationHandler" ); 387 } 388 else 389 { 390 logging.setValue( formatter ); 391 } 392 junit.addConfiguredSysproperty( logging ); 393 } 394 395 final Environment.Variable endorsed = new Environment.Variable(); 396 endorsed.setKey( "java.endorsed.dirs" ); 397 endorsed.setValue( new File( Transit.DPML_SYSTEM, "lib/endorsed" ).getAbsolutePath() ); 398 junit.addConfiguredSysproperty( endorsed ); 399 400 configureDeliverableSysProperties( junit ); 401 configureForExecution( junit ); 402 403 junit.setErrorProperty( ERROR_KEY ); 404 junit.setFailureProperty( FAILURE_KEY ); 405 final boolean haltOnErrorPolicy = getHaltOnErrorPolicy(); 406 if( haltOnErrorPolicy ) 407 { 408 junit.setHaltonerror( true ); 409 } 410 final boolean haltOnFailurePolicy = getHaltOnFailurePolicy(); 411 if( haltOnFailurePolicy ) 412 { 413 junit.setHaltonfailure( true ); 414 } 415 416 junit.init(); 417 junit.execute(); 418 } 419 420 private void setupTestProperties( JUnitTask junit, Project project, File working ) 421 { 422 File base = project.getBaseDir(); 423 final File properties = new File( base, "test.properties" ); 424 if( properties.exists() ) 425 { 426 Properties props = new Properties(); 427 try 428 { 429 props.setProperty( "project.test.dir", working.getCanonicalPath() ); 430 InputStream input = new FileInputStream( properties ); 431 props.load( input ); 432 Enumeration enumeration = props.propertyNames(); 433 props = PropertyResolver.resolve( props ); 434 while( enumeration.hasMoreElements() ) 435 { 436 String name = (String) enumeration.nextElement(); 437 final Environment.Variable v = new Environment.Variable(); 438 v.setKey( name ); 439 String propertyValue = props.getProperty( name ); 440 String parsedValue = getContext().getResource().resolve( propertyValue ); 441 v.setValue( parsedValue ); 442 junit.addConfiguredSysproperty( v ); 443 } 444 } 445 catch( IOException ioe ) 446 { 447 final String error = 448 "Unexpected IO error while reading " + properties.toString(); 449 throw new BuildException( error, ioe, getLocation() ); 450 } 451 } 452 } 453 454 private void configureForExecution( JUnitTask task ) 455 { 456 if( getForkProperty() ) 457 { 458 task.setFork( true ); 459 Project project = getProject(); 460 task.setDir( project.getBaseDir() ); 461 JUnitTask.ForkMode mode = getForkMode(); 462 if( null == mode ) 463 { 464 log( "Executing forked test." ); 465 } 466 else 467 { 468 log( "Executing forked test with mode: '" + mode + "'." ); 469 task.setForkMode( mode ); 470 } 471 String mx = getContext().getProperty( MX_KEY ); 472 if( null != mx ) 473 { 474 task.setMaxmemory( mx ); 475 } 476 } 477 else 478 { 479 log( "executing in local jvm" ); 480 JUnitTask.ForkMode mode = new JUnitTask.ForkMode( "once" ); 481 task.setForkMode( mode ); 482 task.setFork( false ); 483 } 484 } 485 486 private void configureDeliverableSysProperties( JUnitTask task ) 487 { 488 try 489 { 490 Context context = getContext(); 491 Resource resource = context.getResource(); 492 Type[] types = context.getResource().getTypes(); 493 for( int i=0; i<types.length; i++ ) 494 { 495 Type type = types[i]; 496 String id = type.getID(); 497 File file = context.getTargetDeliverable( id ); 498 String path = file.getCanonicalPath(); 499 final Environment.Variable variable = new Environment.Variable(); 500 variable.setKey( "project.deliverable." + id + ".path" ); 501 variable.setValue( path ); 502 task.addConfiguredSysproperty( variable ); 503 } 504 } 505 catch( IOException ioe ) 506 { 507 final String error = 508 "Unexpected IO error while building deliverable filename properties."; 509 throw new BuildException( error, ioe, getLocation() ); 510 } 511 } 512 513 private FileSet createFileSet( File src ) 514 { 515 final FileSet fileset = new FileSet(); 516 fileset.setDir( src ); 517 addIncludes( fileset ); 518 addExcludes( fileset ); 519 return fileset; 520 } 521 522 private void addIncludes( FileSet set ) 523 { 524 String pattern = getTestIncludes(); 525 log( "Test includes=" + pattern, Project.MSG_VERBOSE ); 526 StringTokenizer tokenizer = new StringTokenizer( pattern, ", ", false ); 527 while( tokenizer.hasMoreTokens() ) 528 { 529 String item = tokenizer.nextToken(); 530 set.createInclude().setName( item ); 531 } 532 } 533 534 private void addExcludes( FileSet set ) 535 { 536 String pattern = getTestExcludes(); 537 log( "Test excludes=" + pattern, Project.MSG_VERBOSE ); 538 StringTokenizer tokenizer = new StringTokenizer( pattern, ", ", false ); 539 while( tokenizer.hasMoreTokens() ) 540 { 541 String item = tokenizer.nextToken(); 542 set.createExclude().setName( item ); 543 } 544 } 545 546 private String getTestIncludes() 547 { 548 String includes = getContext().getProperty( TEST_INCLUDES_KEY ); 549 if( null != includes ) 550 { 551 return includes; 552 } 553 else 554 { 555 return TEST_INCLUDES_VALUE; 556 } 557 } 558 559 private String getTestExcludes() 560 { 561 String excludes = getContext().getProperty( TEST_EXCLUDES_KEY ); 562 if( null != excludes ) 563 { 564 return excludes; 565 } 566 else 567 { 568 return TEST_EXCLUDES_VALUE; 569 } 570 } 571 572 private String getCachePath() 573 { 574 final String value = getContext().getProperty( CACHE_PATH_KEY ); 575 if( null != value ) 576 { 577 return value; 578 } 579 else 580 { 581 File cache = (File) getProject().getReference( "dpml.cache" ); 582 return cache.toString(); 583 } 584 } 585 586 private boolean getDebugProperty() 587 { 588 return getBooleanProperty( DEBUG_KEY, DEBUG_VALUE ); 589 } 590 591 private boolean getForkProperty() 592 { 593 return getBooleanProperty( FORK_KEY, FORK_VALUE ); 594 } 595 596 private JUnitTask.ForkMode getForkMode() 597 { 598 final String value = getContext().getProperty( TEST_FORK_MODE_KEY ); 599 if( null == value ) 600 { 601 return null; 602 } 603 else 604 { 605 return new JUnitTask.ForkMode( value ); 606 } 607 } 608 609 private boolean getBooleanProperty( final String key, final boolean fallback ) 610 { 611 final String value = getContext().getProperty( key ); 612 if( null == value ) 613 { 614 return fallback; 615 } 616 else 617 { 618 return Project.toBoolean( value ); 619 } 620 } 621 622 private void fail( final String message ) 623 { 624 final Exit exit = (Exit) getProject().createTask( "fail" ); 625 exit.setMessage( message ); 626 exit.init(); 627 exit.execute(); 628 } 629 630 private boolean isTestingEnabled() 631 { 632 final String enabled = getContext().getProperty( TEST_ENABLED_KEY, "true" ); 633 return "true".equals( enabled ); 634 } 635 636 private JUnitTask.SummaryAttribute getSummaryAttribute() 637 { 638 final Project project = getProject(); 639 final JUnitTask.SummaryAttribute summary = new JUnitTask.SummaryAttribute(); 640 summary.setValue( "on" ); 641 return summary; 642 } 643 644 private boolean getHaltOnErrorPolicy() 645 { 646 return getBooleanProperty( 647 HALT_ON_ERROR_KEY, HALT_ON_ERROR_VALUE ); 648 } 649 650 private boolean getHaltOnFailurePolicy() 651 { 652 return getBooleanProperty( 653 HALT_ON_FAILURE_KEY, HALT_ON_FAILURE_VALUE ); 654 } 655 656 private String getVerboseArgument() 657 { 658 Context context = getContext(); 659 return context.getProperty( "project.test.verbose" ); 660 } 661 662 private FormatterElement newConfiguredFormatter( String type ) 663 { 664 final FormatterElement formatter = new FormatterElement(); 665 final FormatterElement.TypeAttribute attribute = new FormatterElement.TypeAttribute(); 666 attribute.setValue( type ); 667 formatter.setType( attribute ); 668 return formatter; 669 } 670 671 }