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 }