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 }