001 /*
002 Copyright (c) 1996-2012, Damon Hart-Davis
003 All rights reserved.
004
005 Redistribution and use in source and binary forms, with or without
006 modification, are permitted provided that the following conditions are
007 met:
008
009 * Redistributions of source code must retain the above copyright
010 notice, this list of conditions and the following disclaimer.
011
012 * Redistributions in binary form must reproduce the above copyright
013 notice, this list of conditions and the following disclaimer in the
014 documentation and/or other materials provided with the
015 distribution.
016
017 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028 */
029 package org.hd.d.pg2k.test.dev;
030
031 import java.io.IOException;
032 import java.io.OutputStream;
033 import java.io.PrintStream;
034 import java.io.Writer;
035 import java.net.URL;
036 import java.util.Enumeration;
037 import java.util.concurrent.ArrayBlockingQueue;
038 import java.util.concurrent.ExecutorService;
039 import java.util.concurrent.Executors;
040 import java.util.concurrent.ThreadFactory;
041 import java.util.concurrent.ThreadPoolExecutor;
042 import java.util.concurrent.TimeUnit;
043 import java.util.concurrent.atomic.AtomicInteger;
044 import java.util.concurrent.locks.Lock;
045 import java.util.concurrent.locks.ReentrantLock;
046
047 import junit.framework.Test;
048 import junit.framework.TestCase;
049 import junit.framework.TestFailure;
050 import junit.framework.TestResult;
051 import junit.framework.TestSuite;
052
053 import org.hd.d.pg2k.svrCore.props.LocalProps;
054
055 /**This is the main entry point for development-time tests.
056 * This is also a valid top-level JUnit test class.
057 * <p>
058 * They can be run from the command-line or IDE to test most self-contained features,
059 * or from a WAR to allow self-test in an HTTP environment.
060 * <p>
061 * (In a WAR environment the out/err values and exitWhenDone/inWAR flags
062 * should be set appropriately.)
063 */
064 public final class Main extends TestCase
065 {
066 /**If true, this indicates that we have access to the filesystem for tests that need it. */
067 private static final boolean _accessToFilesystem =
068 (LocalProps.getTimestamp() > 0);
069
070 /**If true, this indicates that we have access to the filesystem for tests that need it.
071 * We deduce this by testing if we can load the local properties file,
072 * which we only test at most once per run to avoid lots of log messages.
073 * Access to the file can be arranged by having the tests run in the correct
074 * working directory, or by setting a system property to indicate its
075 * location.
076 * <p>
077 * Tests which expect access to configuration files, test data, etc,
078 * should avoid running when this does not return true.
079 */
080 public static boolean isAccessToFilesystem()
081 {
082 return(_accessToFilesystem);
083 }
084
085 /**Note availability of filesystem test data. */
086 static
087 {
088 System.out.println("isAccessToFilesystem for tests: " + _accessToFilesystem);
089 }
090
091 /**Get URL for our mount-point if running as a WAR; or null if none.
092 * This is used for loop-back testing when running in a WAR.
093 * <p>
094 * Usually a top-level URL such as "http://localhost:8080/".
095 */
096 public static URL getLoopbackURL()
097 {
098 return(loopbackURL);
099 }
100
101 /**Set URL for our mount-point if running as a WAR; or null if none.
102 * This is used for loop-back testing when running in a WAR.
103 */
104 public static void setLoopbackURL(final URL loopbackURL)
105 {
106 Main.loopbackURL = loopbackURL;
107 }
108
109 /**URL for our mount-point if running as a WAR; null if none.
110 * This is used for loop-back testing when running in a WAR.
111 * <p>
112 * Marked volatile for thread-safe lock-free access.
113 */
114 private volatile static URL loopbackURL;
115
116 /**Get current ServletContext if running in a WAR; null if none.
117 * Declared as an Object to avoid making Main depend on Servlet API.
118 */
119 public static Object getServletContext()
120 {
121 return(servletContext);
122 }
123
124 /**Set current ServletContext if running in a WAR; null if none.
125 * Declared as an Object to avoid making Main depend on Servlet API.
126 */
127 public static void setServletContext(final Object servletContext)
128 {
129 Main.servletContext = servletContext;
130 }
131
132 /**Current ServletContext if running in a WAR; null if none.
133 * Declared as an Object to avoid making Main depend on Servlet API.
134 * <p>
135 * Marked volatile for thread-safe lock-free access.
136 */
137 private volatile static Object servletContext;
138
139 /**Get stream to use in lieu of System.out for jUnit tests; never null. */
140 public static PrintStream getOut()
141 {
142 return out;
143 }
144
145 /**Set the out stream; never null. */
146 private static void setOut(final PrintStream o)
147 {
148 if(o == null) { throw new IllegalArgumentException(); }
149 out = o;
150 }
151
152 /**Get stream to use in lieu of System.err for jUnit tests; never null. */
153 public static PrintStream getErr()
154 {
155 return err;
156 }
157
158 /**Set the err stream; never null. */
159 private static void setErr(final PrintStream e)
160 {
161 if(e == null) { throw new IllegalArgumentException(); }
162 err = e;
163 }
164
165 /**The <code>PrintStream</code> to direct standard output to.
166 * Defaults to <code>System.out</code>.
167 * Could be over-ridden by a servlet etc to send output back to the client.
168 * <p>
169 * This is package-visible only for safety.
170 * <p>
171 * Marked volatile for thread-safe lock-free access.
172 */
173 private volatile static PrintStream out = System.out;
174
175 /**The <code>PrintStream</code> to direct standard output to.
176 * Defaults to <code>System.out</code>.
177 * Could be over-ridden by a servlet etc to send output back to the client.
178 * <p>
179 * This is package-visible only for safety.
180 * <p>
181 * Marked volatile for thread-safe lock-free access.
182 */
183 private volatile static PrintStream err = System.err;
184
185
186 /**Main entry point from command-line.
187 * Normally we try to run all tests and see how many produced errors.
188 * <p>
189 * If the first arg is -forever then the tests are run continuously
190 * until the process is killed.
191 * <p>
192 * If one or more test classes are given on the command-line then
193 * those tests alone are run, else all tests are run.
194 * <p>
195 * Only one top-level test routine may run at any one time.
196 */
197 public static final void main(final String args[])
198 {
199 Main.getOut().println("STARTED DEV TESTS FROM COMMAND-LINE...");
200 org.apache.log4j.BasicConfigurator.configure();
201
202 // Do we run the tests forever?
203 final boolean forever = (args.length > 0) &&
204 "-forever".equals(args[0]);
205
206 final boolean allOK = runTests(forever, System.out, System.err, null, null);
207
208 // Exit with an error return value if the error count is not zero.
209 System.exit((allOK) ? 0 : 1);
210 }
211
212 /**Run the unit tests from a servlet.
213 * Takes a Writer argument (assumed to be the servlet output stream)
214 * and wraps it to take output from both err and out streams.
215 */
216 public static void runTestsFromServlet(final Writer writer,
217 final URL loopbackURL,
218 final Object servletContext)
219 {
220 if((writer == null) ||
221 (loopbackURL == null) ||
222 (servletContext == null))
223 { throw new IllegalArgumentException(); }
224
225 // Make a minimal (and fairly inefficient) wrapper for the Writer.
226 final PrintStream ps = new PrintStream(new OutputStream()
227 {
228 /**Writes the specified byte to this output stream.
229 * @param b the <code>byte</code>.
230 * @exception IOException if an I/O error occurs
231 */
232 @Override
233 public void write(final int b) throws IOException
234 { writer.write(b); }
235
236 /**Flushes this output stream.
237 * @exception IOException if an I/O error occurs.
238 */
239 @Override
240 public void flush() throws IOException
241 { writer.flush(); }
242
243 }, true); // Force a flush on newline...
244
245 runTests(false, ps, ps, loopbackURL, servletContext);
246 }
247
248 /**Flattens test case structure from src to dest.
249 */
250 private static void flattenSuite(final TestSuite dest,
251 final TestSuite src)
252 {
253 final Enumeration<?> en = src.tests();
254 while(en.hasMoreElements())
255 {
256 final Test t = (Test) en.nextElement();
257 if(t instanceof TestSuite)
258 {
259 flattenSuite(dest, (TestSuite) t);
260 }
261 else
262 {
263 dest.addTest(t);
264 }
265 }
266 }
267
268 /**Lock to prevent re-entering the runTests() method; never null. */
269 private static final Lock _rtLock = new ReentrantLock();
270
271 /**Actually runs the unit tests.
272 * This is synchronized to only allow one set of tests to be run at once
273 * to ensure consistent behaviour and limit resource usage if run from
274 * a servlet.
275 * <p>
276 * Output and error streams supplied replace the normal ones for the
277 * duration of the run; they must not be null. The previous values
278 * are restored upon completion.
279 *
280 * @param forever if true, tests are run repeatedly until an error is found
281 * @return true if all tests ran OK
282 */
283 public static boolean runTests(final boolean forever,
284 final PrintStream o,
285 final PrintStream e,
286 final URL loopbackURL,
287 final Object servletContext)
288 {
289 final PrintStream originalOut = getOut();
290 final PrintStream originalErr = getErr();
291 final URL originalLoopbackURL = getLoopbackURL();
292 final Object originalServletContext = getServletContext();
293
294 if(!_rtLock.tryLock())
295 { throw new Error("cannot re-enter runTests()"); }
296 try
297 {
298 // Set the requested streams to run the tests.
299 setOut(o);
300 setErr(e);
301 setLoopbackURL(loopbackURL);
302 setServletContext(servletContext);
303
304 org.apache.log4j.BasicConfigurator.configure(); // Force basic log4j configuration (to console).
305
306 boolean allOK = false; // Assume failed.
307 int errorCount = 0;
308
309 do
310 {
311 // Run all the sub-tests.
312 final TestSuite s = suite();
313
314 getOut().println("Tests to run: " + s.countTestCases());
315
316 final Enumeration<?> te = s.tests();
317 for(int i = 1; te.hasMoreElements(); ++i)
318 {
319 final TestResult r = new TestResult();
320 final TestCase test = (TestCase) te.nextElement();
321
322 getOut().println();
323 getOut().println("RUNNING TEST "+i+"; " + test.getName());
324 // Flush any output pending...
325 getOut().flush();
326 getErr().flush();
327
328 // Run the test...
329 test.run(r);
330
331 // Report any errors and failures.
332 if(r.wasSuccessful())
333 {
334 allOK = true;
335 getOut().println("SUCCEEDED");
336 }
337 if(r.errorCount() > 0)
338 {
339 allOK = false;
340 errorCount += r.errorCount();
341 Main.getErr().println("Errors: " + r.errorCount());
342 for(final Enumeration<?> en = r.errors(); en.hasMoreElements(); )
343 {
344 final TestFailure tf = (TestFailure) en.nextElement();
345 Main.getErr().println(tf);
346 final Throwable t = tf.thrownException();
347 if(t != null)
348 { t.printStackTrace(Main.getErr()); }
349 }
350 }
351 if(r.failureCount() > 0)
352 {
353 allOK = false;
354 errorCount += r.failureCount();
355 Main.getErr().println("Failures: " + r.failureCount());
356 for(final Enumeration<?> en = r.failures(); en.hasMoreElements(); )
357 {
358 Main.getErr().println(en.nextElement());
359 }
360 }
361 }
362
363 // Flush any output pending...
364 getOut().flush();
365 getErr().flush();
366
367 } while(forever && allOK); // Repeat tests forever if necessary.
368
369 if(errorCount > 0)
370 {
371 getErr().println();
372 getErr().println("TOTAL ERRORS: " + errorCount);
373 }
374
375 return(allOK);
376 }
377 finally
378 {
379 // Restore previous stream values.
380 setOut(originalOut);
381 setErr(originalErr);
382 setLoopbackURL(originalLoopbackURL);
383 setServletContext(originalServletContext);
384 // Release the lock...
385 _rtLock.unlock();
386 }
387 }
388
389
390 /**A factory for creating daemon pool threads.
391 * Adapted (purloined) from Doug Lea / Sun JVM code.
392 */
393 public static final class TestThreadFactory implements ThreadFactory
394 {
395 private final ThreadGroup group;
396 private final AtomicInteger threadNumber = new AtomicInteger(1);
397 private final String namePrefix;
398 private final boolean lowPriority;
399
400 public TestThreadFactory(final String poolName,
401 final boolean lowPriority)
402 {
403 final SecurityManager s = System.getSecurityManager();
404 group = (s != null)? s.getThreadGroup() :
405 Thread.currentThread().getThreadGroup();
406 namePrefix = "pool-" + poolName + "-thread-";
407 this.lowPriority = lowPriority;
408 }
409
410 public Thread newThread(final Runnable r)
411 {
412 final Thread t = new Thread(group, r,
413 namePrefix + threadNumber.getAndIncrement(),
414 0);
415 t.setDaemon(true);
416 if(lowPriority)
417 { t.setPriority(Thread.MIN_PRIORITY); }
418 else if(t.getPriority() != Thread.NORM_PRIORITY)
419 { t.setPriority(Thread.NORM_PRIORITY); }
420 return(t);
421 }
422 }
423
424 /**Number of available CPUs. */
425 private static final int AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
426
427 /**Shared thread pool for compute-bound tests.
428 * Suitable for almost-entirely CPU-bound threads
429 * with the pool size exactly the number of CPUs available to this JVM
430 * (at creation of this pool).
431 * <p>
432 * Some work can be queued for this pool to maximise throughput
433 * and excess work (with no free pool threads or queue space available)
434 * will be run directly in the caller's thread.
435 * In simple uses this will allow for a little parallel slackness.
436 * <p>
437 * This puts an upper bound on Thread resources (eg memory for stack)
438 * consumed by threads in this pool.
439 * <p>
440 * The threads in the pool are daemon threads,
441 * so should not prevent the JVM from exiting.
442 * <p>
443 * Only available to test routines in this package.
444 */
445 static final ExecutorService computeIntensiveTestThreadPool =
446 Executors.unconfigurableExecutorService(
447 new ThreadPoolExecutor(AVAILABLE_PROCESSORS, AVAILABLE_PROCESSORS,
448 600L, TimeUnit.SECONDS, // Keep worker threads alive for 10 minutes...
449 new ArrayBlockingQueue<Runnable>(AVAILABLE_PROCESSORS),
450 new TestThreadFactory("Test Main.computeIntensiveThreadPool", false),
451 new ThreadPoolExecutor.CallerRunsPolicy()));
452
453
454 /**List of classes containing test suites to be run.
455 * They should be run in the order specified by default.
456 */
457 private static final Class<?> testClasses[] =
458 {
459 // Put the internal sanity tests first...
460 TinyTest.class,
461
462 // Bulk of the tests (alphabetic order by class)...
463 AdTest.class,
464 AddrToolsTest.class,
465 AlohaEarthTest.class,
466 BackCompatTest.class,
467 CloudFilesTest.class,
468 ConfigFilesTest.class,
469 ExhibitFilterTest.class,
470 ExhibitNameTest.class,
471 ExhibitUploadTest.class,
472 HTTPTunnelTest.class,
473 I18NTest.class,
474 IndexTest.class,
475 LocationTest.class,
476 PaginationBeanTest.class,
477 ScorerTest.class,
478 SearchPageJavaBeanTest.class,
479 SerializationTest.class,
480 ServletTest.class,
481 SimpleCacheTest.class,
482 SystemVariablesTest.class,
483 TreeFilterBeanTest.class,
484 TunnelTest.class,
485 VirtualHostTest.class,
486 WebUtilsTest.class,
487
488 // Put the slowest tests towards the end...
489 MemoryToolsTest.class,
490 MediaHandlerTest.class,
491
492 // Leave this MiscTest set until last to encourage creation of specific test sets.
493 MiscTest.class
494 };
495
496 /**This returns a suite of all the top-level tests that we want to perform.
497 * This returns a flattened suite of tests.
498 */
499 public static TestSuite suite()
500 {
501 final TestSuite suite = new TestSuite();
502
503 for(int i = 0; i < testClasses.length; ++i)
504 { flattenSuite(suite, new TestSuite(testClasses[i])); }
505
506 return(suite);
507 }
508 }