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