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        }