1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 1996, 2016, Oracle and/or its affiliates. All rights reserved.
   5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   6  *
   7  * This code is free software; you can redistribute it and/or modify it
   8  * under the terms of the GNU General Public License version 2 only, as
   9  * published by the Free Software Foundation.  Oracle designates this
  10  * particular file as subject to the "Classpath" exception as provided
  11  * by Oracle in the LICENSE file that accompanied this code.
  12  *
  13  * This code is distributed in the hope that it will be useful, but WITHOUT
  14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  16  * version 2 for more details (a copy is included in the LICENSE file that
  17  * accompanied this code).
  18  *
  19  * You should have received a copy of the GNU General Public License version
  20  * 2 along with this work; if not, write to the Free Software Foundation,
  21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  22  *
  23  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  24  * or visit www.oracle.com if you need additional information or have any
  25  * questions.
  26  */
  27 package com.sun.javatest;
  28 
  29 import java.io.File;
  30 import java.io.IOException;
  31 import java.util.*;
  32 
  33 import com.sun.javatest.TestResultTable.TreeIterator;
  34 import com.sun.javatest.httpd.HttpdServer;
  35 import com.sun.javatest.httpd.RootRegistry;
  36 import com.sun.javatest.util.BackupPolicy;
  37 import com.sun.javatest.util.DynamicArray;
  38 import com.sun.javatest.util.I18NResourceBundle;
  39 import com.sun.javatest.util.ReadAheadIterator;
  40 
  41 /**
  42  * The object responsible for coordinating the execution of a test run.
  43  */
  44 public class Harness
  45 {
  46     /**
  47      * This exception is used to report problems while executing a test run.
  48      */
  49     public static class Fault extends Exception
  50     {
  51         Fault(I18NResourceBundle i18n, String s) {
  52             super(i18n.getString(s));
  53         }
  54 
  55         Fault(I18NResourceBundle i18n, String s, Throwable cause) {
  56            super(i18n.getString(s), cause);
  57         }
  58 
  59         Fault(I18NResourceBundle i18n, String s, Object o) {
  60             super(i18n.getString(s, o));
  61         }
  62 
  63         Fault(I18NResourceBundle i18n, String s, Object[] o) {
  64             super(i18n.getString(s, o));
  65         }
  66     }
  67 
  68     /**
  69      * This interface provides a means for Harness to report
  70      * on events that might be of interest as it executes.
  71      */
  72     public interface Observer
  73     {
  74         /**
  75          * The harness is beginning to execute tests.
  76          * @param params the parameters for the test run
  77          */
  78         void startingTestRun(Parameters params);
  79 
  80         /**
  81          * The harness is about to run the given test.
  82          *
  83          * @param tr The test result which is going to receive the data
  84          *        from the current execution of that test.
  85          */
  86         void startingTest(TestResult tr);
  87 
  88         /**
  89          * The harness has finished running the given test.
  90          * This message is sent without respect to the resulting test's
  91          * completion status (pass, fail, etc...).
  92          *
  93          * @param tr The result object containing the results from the
  94          *        execution which was just completed.
  95          */
  96         void finishedTest(TestResult tr);
  97 
  98         /**
  99          * The harness is about to stop a test run, before it has finished
 100          * executing all the specified tests. The method is not notified if
 101          * the test run completes normally, after executing all the specified
 102          * tests.
 103          */
 104         void stoppingTestRun();
 105 
 106         /**
 107          * The harness has finished running tests and is doing other activities
 108          * (writing the report, updating caches, etc...).  This message will
 109          * be broadcast both when error conditions terminate the run or when
 110          * a test completes normally. It may provide a reasonable opportunity
 111          * for a client to clean up any resources that were used during the test
 112          * run, before a new run is started.
 113          */
 114         void finishedTesting();
 115 
 116         /**
 117          * The test run has been completed, either because the user requested
 118          * that the harness stop, the harness decided to terminate the test run,
 119          * or all requested tests have been run.  The harness is now ready to
 120          * perform another test run. Note that since the actions of other observers
 121          * are undefined, a new test run may have already been started by the time
 122          * this method is called for any specific observer.
 123          *
 124          * @param allOK True if all tests passed, false otherwise.
 125          */
 126         void finishedTestRun(boolean allOK);
 127 
 128         /**
 129          * The given error occurred.
 130          *
 131          * @param msg A description of the error event.
 132          */
 133         void error(String msg);
 134     }
 135 
 136 
 137     /**
 138      * Instantiate a harness.
 139      * @param classDir  The class dir to put in the environment for otherJVM tests
 140      * @deprecated      Use Harness() instead
 141      * @see #Harness()
 142      * @see #setClassDir
 143      */
 144     public Harness(File classDir) {
 145         this();
 146         setClassDir(classDir);
 147     }
 148 
 149     /**
 150      * Instantiate a harness.
 151      */
 152     public Harness() {
 153         backupPolicy = BackupPolicy.noBackups();
 154 
 155         params = null;
 156 
 157         if (!Boolean.getBoolean("javatest.noTraceRequired")) {
 158             trace = new Trace(backupPolicy);
 159             addObserver(trace);
 160         }
 161 
 162         // web server
 163         if (HttpdServer.isActive()) {
 164             httpHandler = new HarnessHttpHandler(this);
 165             RootRegistry.getInstance().addHandler("/harness", "JT Harness",
 166                                                   httpHandler);
 167         }
 168     }
 169 
 170     //--------------------------------------------------------------------------
 171 
 172     /**
 173      * Get the backup policy object used by this harness, used to determine
 174      * the policy for backing up files before overwriting them.
 175      * @return the backup policy object used by this harness
 176      * @see #setBackupPolicy
 177      */
 178     public BackupPolicy getBackupPolicy() {
 179         return backupPolicy;
 180     }
 181 
 182     /**
 183      * Set the backup policy object to be used by this harness,
 184      * used to determine the policy for backing up files before
 185      * overwriting them.
 186      * @param bp the backup policy object used by this harness
 187      * @see #getBackupPolicy
 188      */
 189     public void setBackupPolicy(BackupPolicy bp) {
 190         backupPolicy = bp;
 191     }
 192 
 193     /**
 194      * Check if a trace file should be generated while performing a test run.
 195      * @return true if and only if a trace file should be generated
 196      * @see #setTracingRequired
 197      */
 198     public boolean isTracingRequired() {
 199         return (trace != null);
 200     }
 201 
 202     /**
 203      * Set whether a trace file should be generated while performing a test run.
 204      * @param b whether or not a trace file should be generated
 205      * @see #isTracingRequired
 206      */
 207     public void setTracingRequired(boolean b) {
 208         if (b && trace == null) {
 209             trace = new Trace(backupPolicy);
 210             addObserver(trace);
 211             //currentResults.addObserver(trace);
 212         }
 213         else if (!b && trace != null) {
 214             removeObserver(trace);
 215             //currentResults.removeObserver(trace);
 216             trace = null;
 217         }
 218     }
 219 
 220     /**
 221      * Get the class directory or jar file containing JT Harness.
 222      * @return the class directory or jar file containing JT Harness
 223      * @see #setClassDir
 224      */
 225     public static File getClassDir() {
 226         return classDir;
 227     }
 228 
 229     /**
 230      * Specify the class directory or jar file containing JT Harness.
 231      * @param classDir the class directory or jar file containing JT Harness
 232      * @see #getClassDir
 233      */
 234     public static void setClassDir(File classDir) {
 235         if (Harness.classDir != null && Harness.classDir != classDir)
 236             throw new IllegalStateException(i18n.getString("harness.classDirAlreadySet"));
 237         Harness.classDir = classDir;
 238     }
 239 
 240 
 241     //--------------------------------------------------------------------------
 242 
 243     /**
 244      * Get the current parameters of the harness.
 245      *
 246      * @return null if the parameters have not been set.
 247      */
 248     public Parameters getParameters() {
 249         return params;
 250     }
 251 
 252     //--------------------------------------------------------------------------
 253 
 254     /**
 255      * Get the current test environment being used by the harness.
 256      * This is similar to getParameters().getEnv(), except that the environment
 257      * returned here has some standard additional fields set by the harness
 258      * itself.
 259      *
 260      * @return null if the environment has not been set.
 261      */
 262     public TestEnvironment getEnv() {
 263         return env;
 264     }
 265 
 266     //--------------------------------------------------------------------------
 267 
 268     /**
 269      * Get the current set of results.  This will either be the set of results
 270      * from which are currently running, or the results from the last run.
 271      *
 272      * @return null if no results are currently available.  This will be the case
 273      *         if the Harness has not been run, or the parameters have been changed
 274      *         without doing a new run.
 275      */
 276     public TestResultTable getResultTable() {
 277         WorkDirectory wd = (params == null ? null : params.getWorkDirectory());
 278         return (wd == null ? null : wd.getTestResultTable());
 279     }
 280 
 281     //--------------------------------------------------------------------------
 282 
 283     /**
 284      * Add an observer to be notified during the execution of a test run.
 285      * Observers are notified of events in the reverse order they were added --
 286      * the most recently added observer gets notified first.
 287      * @param o the observer to be added
 288      * @see #removeObserver
 289      */
 290     public void addObserver(Observer o) {
 291         notifier.addObserver(o);
 292     }
 293 
 294     /**
 295      * Remove a previously registered observer so that it will no longer
 296      *  be notified during the execution of a test run.
 297      * It is safe for observers to remove themselves during a notification;
 298      * most obviously, an observer may remove itself during finishedTesting()
 299      * or finishedTestRun().
 300      * @param o the observer to be removed
 301      * @see #addObserver
 302      */
 303     public void removeObserver(Observer o) {
 304         notifier.removeObserver(o);
 305     }
 306 
 307     //--------------------------------------------------------------------------
 308 
 309     /**
 310      * Start running all the tests defined by a new set of parameters.
 311      * The tests are run asynchronously, in a separate worker thread.
 312      * @param p         The parameters to be set when the tests are run.
 313      *                  Any errors in the parameters are reported to
 314      *                  any registered observers.
 315      * @throws Harness.Fault if the harness is currently running tests
 316      *                  and so cannot start running any more tests right now.
 317      * @see #isRunning
 318      * @see #stop
 319      * @see #waitUntilDone
 320      */
 321     public void start(Parameters p) throws Fault {
 322         startWorker(p);
 323     }
 324 
 325     /**
 326      * Wait until the harness completes the current task.
 327      * @exception InterruptedException if the thread making the call is
 328      * interrupted.
 329      */
 330     public synchronized void waitUntilDone() throws InterruptedException {
 331         while (worker != null) {
 332             wait();
 333         }
 334     }
 335 
 336 
 337     /**
 338      * Stop the harness executing any tests. If no tests are running,
 339      * the method does nothing; otherwise it notifies any observers,
 340      * and interrupts the thread doing the work. The worker may carry
 341      * on for a short time after this method is called, while it waits
 342      * for all the related tasks to complete.
 343      * @see #waitUntilDone
 344      */
 345     public synchronized void stop() {
 346         if (worker != null) {
 347             if (!stopping) {
 348                 notifier.stoppingTestRun();
 349                 stopping = true;
 350             }
 351             worker.interrupt();
 352         }
 353     }
 354 
 355     /**
 356      * Run the tests defined by a new set of parameters.
 357      * @param params    The parameters to be used; they will be validated first.
 358      * @return true if and only if all the selected tests were executed successfully, and all passed
 359      * @throws Harness.Fault if the harness is currently running tests
 360      *                  and cannot start running any more tests right now.
 361      * @throws InterruptedException if the thread making the call is
 362      * interrupted, perhaps because of an asynchronous call of stop().
 363      * @see #isRunning
 364      * @see #stop
 365      * @see #waitUntilDone
 366      */
 367     public boolean batch(Parameters params)
 368         throws Fault, InterruptedException
 369     {
 370         isBatchRun = true;
 371         // allow full read-ahead by default now - as of 3.2.1
 372         // this allows the not run field of verbose mode to work
 373         if (Boolean.getBoolean("javatest.noReadAhead"))
 374             readAheadMode = ReadAheadIterator.NONE;
 375 
 376         synchronized (this) {
 377             if (worker != null)
 378                 throw new Fault(i18n, "harness.alreadyRunning");
 379             worker = Thread.currentThread();
 380         }
 381 
 382         // parameters will be checked later, in runTests, but check here
 383         // too to specifically verify that workDir is set
 384         if (!params.isValid())
 385             throw new Harness.Fault(i18n, "harness.incompleteParameters",
 386                                     params.getErrorMessage());
 387 
 388         boolean ok = false;
 389         try {
 390             workDir = params.getWorkDirectory();
 391             resultTable = workDir.getTestResultTable();
 392             // XXX this was a performance enhancer for 3.x
 393             //   it will not work if the user expects previous results
 394             //   to be erased/hidden if they no longer exist in the test
 395             //   suite.  that goal and this one are fundamentally opposed
 396             //resultTable.suppressFinderScan(true);
 397             ok = runTests(params, ZERO_TESTS_OK);
 398         }
 399         catch (TestSuite.Fault e) {
 400             throw new Fault(i18n, "harness.testsuiteError", e.getMessage());
 401         }
 402         finally {
 403             synchronized (this) {
 404                 worker = null;
 405                 notifyAll();
 406             }
 407 
 408             notifier.finishedTestRun(ok);
 409             isBatchRun = false;
 410         }
 411 
 412         return ok;
 413     }
 414 
 415     /**
 416      * Check if the harness is currently executing a test suite or not.
 417      * @return true if and only if the harness is currently executing a test suite.
 418      * @see #start
 419      * @see #batch
 420      * @see #stop
 421      * @see #waitUntilDone
 422      */
 423     public boolean isRunning() {
 424         return (worker != null);
 425     }
 426 
 427     /**
 428      * Was the harness invoked in batch mode?  If it is not in batch mode, this
 429      * typically implies that the user is using an interactive GUI interface.
 430      * @return True if the harness is running and was invoked in batch mode.
 431      * @throws IllegalStateException If the harness is not running, care should
 432      *         be taken to handle this in case the run terminates.
 433      */
 434     public synchronized boolean isBatchRun() throws IllegalStateException {
 435         if (!isRunning())
 436             throw new IllegalStateException();
 437 
 438         return isBatchRun;
 439     }
 440 
 441     /**
 442      * Indicates whether the harness has located all the tests it will execute.
 443      * If true, then <tt>getTestsFoundCount()</tt> will return the number of test
 444      * which will be executed during this test run; assuming the harness does not
 445      * halt for special cases (errors, user request, etc...).  If false,
 446      * <tt>getTestsFoundCount()</tt> returns the number of tests located so
 447      * far.
 448      * @return True if all tests have been located.  False if the harness is
 449      *         still looking for tests.  Always false if the harness is not
 450      *         running.
 451      * @see #isRunning()
 452      * @see #getTestsFoundCount()
 453      */
 454     public boolean isAllTestsFound() {
 455         if (isRunning() && raTestIter != null)
 456             return raTestIter.isSourceExhausted();
 457         else
 458             return false;
 459     }
 460 
 461     /**
 462      * Find time since the start of the current or last run.
 463      * If no run is in progress, this is the time it took to complete the
 464      * last run.
 465      *
 466      * @return Zero if no run has ever been started yet.  Elapsed time in
 467      *         milliseconds otherwise.
 468      */
 469     public long getElapsedTime() {
 470         long time = 0L;
 471 
 472         if (startTime == -1L)
 473             time = 0L;                  // no data avail.
 474         else if (cleanupFinishTime == -1L) {    // isRunning() isn't good enough
 475             long now = System.currentTimeMillis();
 476             time = now - startTime;     // we are still running
 477         }
 478         else
 479             time = cleanupFinishTime - startTime;
 480 
 481         return time;
 482     }
 483 
 484     /**
 485      * Get the time at which the last run start.
 486      *
 487      * @return Time when the last run started in milliseconds.  -1 if there is
 488      *         no previous run.
 489      * @see #getFinishTime
 490      */
 491     public long getStartTime() {
 492         return startTime;
 493     }
 494 
 495     /**
 496      * Get the time at which the last run finished.  This is the time when
 497      * the last test completed, and does not include post-run cleanup time.
 498      *
 499      * @return Time when the last run finished in milliseconds.  -1 if there is
 500      *         no previous run or a run is in progress.
 501      * @see #getStartTime
 502      */
 503     public long getFinishTime() {
 504         return finishTime;
 505     }
 506 
 507     /**
 508      * Get the time at which cleanup of the entire run was completed.
 509      * This is after the time when the last test completed.
 510      *
 511      * @return Time when the run finished in milliseconds.  -1 if there is
 512      *         no previous run or a run is in progress.
 513      * @see #getStartTime
 514      */
 515     public long getCleanupFinishTime() {
 516         return cleanupFinishTime;
 517     }
 518 
 519     public long getTotalCleanupTime() {
 520         if (cleanupFinishTime < finishTime || cleanupFinishTime == -1l)
 521             return -1l;
 522         else
 523             return cleanupFinishTime - finishTime;
 524     }
 525 
 526     public long getTotalSetupTime() {
 527         if (testsStartTime < startTime || testsStartTime == -1l)
 528             return -1l;
 529         else
 530             return testsStartTime - startTime;
 531     }
 532 
 533     /**
 534      * Find out the estimated time required to complete the remaining tests.
 535      *
 536      * @return A time estimate in milliseconds.  Zero if no run is in progress or
 537      *         no estimate is available.
 538      */
 539     public long getEstimatedTime() {
 540         if (isRunning() == false || numTestsDone == 0)
 541             return 0L;
 542 
 543         long estRemain = getElapsedTime() * (getTestsFoundCount() - numTestsDone) / numTestsDone;
 544         return estRemain;
 545     }
 546 
 547     /**
 548      * Find out how many tests to run have been located so far.  Data will pertain
 549      * to the previous run (if any) if isRunning() is false.  The return will be
 550      * zero if isRunning() is false and there is no previous run for this instance
 551      * of the Harness.
 552      *
 553      * @return Number of tests which the harness will try to run.  Greater than or
 554      *         equal to zero and less than or equal to the total number of tests in
 555      *         the testsuite.
 556      * @see #isRunning()
 557      */
 558     public int getTestsFoundCount() {
 559         if (raTestIter == null)
 560             return 0;
 561 
 562         synchronized (raTestIter) {
 563             return raTestIter.getUsedElementCount() + raTestIter.getOutputQueueSize();
 564         }
 565     }
 566 
 567     /**
 568      * Set the threshold for automatic halting of a test run.
 569      * The current algorithm is to begin at zero, add one for every failure,
 570      * five for every error and subtract two for each pass.  This value must be
 571      * set before the run begins, do not change it during a run.
 572      * @see #getAutostopThreshold
 573      */
 574     public void setAutostopThreshold(int n) {
 575         autostopThreshold = n;
 576     }
 577 
 578     /**
 579      * @see #setAutostopThreshold
 580      */
 581     public int getAutostopThreshold(int n) {
 582         return autostopThreshold;
 583     }
 584 
 585     /**
 586      * Start a worker thread going to perform run tests asynchronously.
 587      */
 588     private synchronized void startWorker(final Parameters p) throws Fault {
 589         if (worker != null)
 590             throw new Fault(i18n, "harness.alreadyRunning");
 591 
 592         worker = new Thread() {
 593             @Override
 594             public void run() {
 595                 boolean ok = false;
 596                 try {
 597                     ok = runTests(p, ZERO_TESTS_ERROR);
 598                 }
 599                 catch (Fault e) {
 600                     notifyLocalizedError(e.getMessage());
 601                 }
 602                 catch (TestSuite.Fault e) {
 603                     notifyLocalizedError(e.getMessage());
 604                 }
 605                 catch (InterruptedException e) {
 606                     notifyError(i18n, "harness.interrupted");
 607                 }
 608                 finally {
 609                     synchronized (Harness.this) {
 610                         worker = null;
 611                         Harness.this.notifyAll();
 612                     }
 613 
 614                     notifier.finishedTestRun(ok);
 615                 }
 616             }
 617         };
 618 
 619         worker.setName("Harness:Worker");
 620         worker.setPriority(Thread.NORM_PRIORITY - 2); // below AWT!
 621         worker.start();
 622     }
 623 
 624 
 625     /**
 626      * This method is the one that does the work and runs the tests. Any parameters
 627      * should have been set up in the constructor.
 628      * @return The result is `true' if and only if all tests passed.
 629      */
 630     // This methods notifies observers for startingTestRun and stoppingTestRun.
 631     // The caller should notify finishedTestRun when it is OK to run start again
 632     // (i.e. when worker has been reset to null.
 633     private boolean runTests(Parameters p, boolean zeroTestsOK)
 634         throws Fault, TestSuite.Fault, InterruptedException {
 635 
 636         boolean ok = true; // default return/finished notification value
 637         stopping = false;
 638         startTime = System.currentTimeMillis();
 639         testsStartTime = -1l;
 640         cleanupFinishTime= -1l;
 641         finishTime = -1l;
 642         numTestsDone = 0;
 643 
 644         if (!p.isValid())
 645             throw new Harness.Fault(i18n, "harness.incompleteParameters",
 646                                     p.getErrorMessage());
 647         params = p;
 648 
 649         // get lots of necessary values from parameters
 650         testSuite = params.getTestSuite();
 651         workDir = params.getWorkDirectory();
 652         resultTable = workDir.getTestResultTable();
 653         excludeList = params.getExcludeList();
 654 
 655         workDir.log(i18n, "harness.starting");
 656 
 657         // for compatibility with scripts that expect the timeout factor
 658         // to be an integer, we write out the timeout factor as both an
 659         // integer and a floating point number. The integer value is
 660         // determined by rounding up the floating point number.
 661         float tf = params.getTimeoutFactor();
 662         if (Float.isNaN(tf))
 663             tf = 1.0f;
 664 
 665         String[] timeoutFactors = {
 666             String.valueOf((int) (Math.ceil(tf))),
 667             String.valueOf(tf)
 668         };
 669 
 670         env = params.getEnv();
 671         env.put("javatestTimeoutFactor", timeoutFactors);
 672         env.putUrlAndFile("javatestClassDir", classDir);
 673         env.putUrlAndFile("harnessClassDir", classDir);  // backwards compatibility
 674         env.putUrlAndFile("javatestWorkDir", workDir.getRoot());
 675 
 676         // allow architect to reset root of TS in env. if needed
 677         // esp. for backwards compatibility with JT 2.x test suites
 678         String altTSRoot = testSuite.getTestSuiteInfo("env.tsRoot");
 679         // need to validate alt and change into File
 680         File atsr = (altTSRoot == null ? null : new File(altTSRoot));
 681 
 682         if (atsr != null && atsr.exists()) {
 683             env.putUrlAndFile("testSuiteRoot", atsr);
 684             env.putUrlAndFile("testSuiteRootDir",
 685                     (atsr.isDirectory() ? atsr : atsr.getParentFile()));
 686         }
 687         else {
 688             // normal case
 689             env.putUrlAndFile("testSuiteRoot", testSuite.getRoot());
 690             env.putUrlAndFile("testSuiteRootDir", testSuite.getRootDir());
 691         }
 692 
 693 
 694         // notify the test suite we are starting
 695         testSuite.starting(this);
 696         notifier.startingTestRun(params);
 697 
 698         testIter = createTreeIterator();
 699         raTestIter = getTestsIterator(testIter);
 700 
 701         // autostopThreshold is currently defined by a system property,
 702         // but could come from parameters
 703         if (autostopThreshold > 0)
 704             addObserver(new Autostop(autostopThreshold));
 705 
 706         TestRunner r = testSuite.createTestRunner();
 707         r.setWorkDirectory(workDir);
 708         r.setBackupPolicy(backupPolicy);
 709         r.setEnvironment(env);
 710         r.setExcludeList(excludeList);
 711 
 712         int concurrency = params.getConcurrency();
 713         concurrency = Math.max(1, Math.min(concurrency,
 714                                            Parameters.ConcurrencyParameters.MAX_CONCURRENCY));
 715         r.setConcurrency(concurrency);
 716 
 717         r.setNotifier(notifier);
 718 
 719         TestURLCollector testURLCollector = new TestURLCollector();
 720         notifier.addObserver(testURLCollector);
 721         testsStartTime = System.currentTimeMillis();
 722         try {
 723             ok = r.runTests(new Iterator() {
 724                     public boolean hasNext() {
 725                         return (stopping ? false : raTestIter.hasNext());
 726                     }
 727                     public Object next() {
 728                         TestResult tr = (TestResult) (raTestIter.next());
 729                         try {
 730                             return tr.getDescription();
 731                         }
 732                         catch (TestResult.Fault e) {
 733                             stopping = true;
 734                             throw new JavaTestError(i18n, "harness.trProb",  tr.getWorkRelativePath(), e);
 735                         }
 736                     }
 737                     public void remove() {
 738                         throw new UnsupportedOperationException();
 739                     }
 740                 });
 741         }
 742         catch (InterruptedException e) {
 743             // swallow interrupts, because we're just going to wind up the run
 744         }
 745         notifier.removeObserver(testURLCollector);
 746 
 747         finishTime = System.currentTimeMillis();
 748 
 749         notifier.finishedTesting();
 750 
 751         // calculate number of tests executed
 752         // NOTE: the stats here don't indicate what the results of the test run were
 753         int[] stats = testIter.getResultStats();
 754         int iteratorCount = 0;
 755         for (int i = 0; i < stats.length; i++)
 756             iteratorCount += stats[i];
 757 
 758         if (iteratorCount == 0 && !zeroTestsOK) {
 759             TestFilter[] filters = params.getFilters();
 760             // no tests are in the error, pass, fail categories -> none selected
 761             notifyError(i18n, "harness.noTests",
 762                     new Object[] {
 763                         formatFilterList(listFilterNames(filters)),
 764                         testIter.getRejectCount(),
 765                         formatFilterStats(params.getTests(), testIter)
 766                     });
 767             ok = false;
 768         }
 769         else {
 770             /* user is notified of this in real-time
 771                although it may not be evident in batch mode
 772             if (resultTable.getTestFinder().getErrorCount() > 0) {
 773                 notifyError(i18n, "harness.finderError");
 774                 ok = false;
 775             }
 776             */
 777         }
 778 
 779         if (ok && (notifier.getErrorCount() > 0 || notifier.getFailedCount() > 0))
 780             ok = false;
 781 
 782         try {
 783             LastRunInfo.writeInfo(workDir, startTime, finishTime,
 784                     env.getName(), testURLCollector.testURLs);
 785         }
 786         catch (IOException e) {
 787             // ignore
 788         }
 789 
 790         // TRT may need to reread the entire cache
 791         resultTable.waitUntilReady();
 792 
 793         workDir.log(i18n, "harness.done", new Integer(ok ? 0 : 1));
 794         cleanupFinishTime = System.currentTimeMillis();
 795         return ok;
 796     }
 797 
 798     public ReadAheadIterator getTestsIterator(TreeIterator iter) throws Fault {
 799         if (iter == null) {
 800             iter = createTreeIterator();
 801         }
 802         return new ReadAheadIterator(iter, readAheadMode, DEFAULT_READ_AHEAD);
 803     }
 804 
 805     private TreeIterator createTreeIterator() throws Fault {
 806         // get items required to select the tests to be run
 807         String[] tests = params.getTests();
 808         TestFilter[] filters = params.getFilters();
 809 
 810         resultTable.waitUntilReady();
 811 
 812         TreeIterator iter;
 813 
 814         // get the appropriate iterator from TRT
 815         if (tests == null || tests.length == 0)
 816             iter = resultTable.getIterator(filters);
 817         else {
 818             try {
 819                 // CLEANUP REQUIRED: validation only occurs on Files, not Strings
 820                 // resultTable.getIterator should validate strings too
 821                 File[] files = new File[tests.length];
 822                 for (int i = 0; i < tests.length; i++)
 823                     files[i] = new File(tests[i]);
 824                 iter = resultTable.getIterator(files, filters);
 825             }
 826             catch (TestResultTable.Fault err) {
 827                 throw new Harness.Fault(i18n, "harness.badInitFiles",
 828                                         err.getMessage());
 829             }
 830         }
 831 
 832         return iter;
 833     }
 834 
 835     private static ArrayList<String> listFilterNames(final TestFilter[] filters) {
 836         ArrayList<String> result = new ArrayList<>();
 837 
 838         if (filters == null || filters.length == 0)
 839             return result;      // i.e. empty
 840 
 841         for (TestFilter f: filters) {
 842             // we don't care about composite wrappers, recurse into them
 843             if (f instanceof CompositeFilter) {
 844                 result.addAll(listFilterNames(((CompositeFilter)f).getFilters()));
 845             }
 846             else if (f instanceof AllTestsFilter) {
 847                 continue;
 848             }
 849             else {
 850                 result.add(f.getName());
 851             }
 852         }
 853 
 854         // for convienence, null is never returned
 855         return result;
 856     }
 857 
 858     private static String formatFilterList(final ArrayList<String> names) {
 859         if (names == null || names.size() == 0)
 860             return "";
 861 
 862         StringBuilder sb = new StringBuilder();
 863         for (String s: names) {
 864             sb.append("- ");
 865             sb.append(s);
 866             sb.append("\n");
 867         }
 868 
 869         return sb.toString();
 870     }
 871 
 872     private static String formatFilterStats(final String[] tests,
 873             final TreeIterator iter) {
 874         TRT_Iterator treeit = null;
 875 
 876         if (iter == null || !(iter instanceof TRT_Iterator)) {
 877             return "";
 878         }
 879         else {
 880             treeit = ((TRT_Iterator)iter);
 881         }
 882 
 883         TestFilter[] filters = treeit.getFilters();
 884         HashMap<TestFilter, ArrayList<TestDescription>> map = treeit.getFilterStats();
 885         Set<TestFilter> keyset = map.keySet();
 886         StringBuilder sb = new StringBuilder();
 887 
 888         // special case for Tests to Run because there is no associated
 889         // TestFilter
 890         if (tests != null && tests.length > 0) {
 891             sb.append("- ");
 892             sb.append("Tests to Run (" + tests.length + " path(s) specified)");
 893             sb.append("\n");
 894         }
 895 
 896         for (TestFilter f: keyset) {
 897             ArrayList<TestDescription> tds = map.get(f);
 898             // this works, should consider switching to something which has more
 899             // rendering control - RTF
 900             sb.append("- ");
 901             sb.append(tds.size());
 902             sb.append(" due to ");        // could use upgrade for readability
 903             sb.append(f.getName());
 904             sb.append("\n");
 905             sb.append("    ");      // indentation
 906             sb.append(f.getReason());
 907             sb.append("\n");
 908         }   // for
 909 
 910         return sb.toString();
 911     }
 912 
 913 
 914     private void notifyError(I18NResourceBundle i18n, String key) {
 915         notifyLocalizedError(i18n.getString(key));
 916     }
 917 
 918     private void notifyError(I18NResourceBundle i18n, String key, Object arg) {
 919         notifyLocalizedError(i18n.getString(key, arg));
 920     }
 921 
 922     private void notifyError(I18NResourceBundle i18n, String key, Object[] args) {
 923         notifyLocalizedError(i18n.getString(key, args));
 924     }
 925 
 926     private void notifyLocalizedError(String msg) {
 927         notifier.error(msg);
 928     }
 929 
 930     //----------member variables-----------------------------------------------------
 931 
 932     private BackupPolicy backupPolicy;
 933     private int autostopThreshold;
 934     { Integer i = Integer.getInteger("javatest.autostop.threshold");
 935       autostopThreshold = (i == null ? 0 : i.intValue());
 936     }
 937 
 938     private HarnessHttpHandler httpHandler;
 939     private Trace trace;
 940 
 941     private Thread worker;
 942     private Parameters params;
 943     private TestSuite testSuite;
 944     private WorkDirectory workDir;
 945     private ExcludeList excludeList;
 946     private TestResultTable.TreeIterator testIter;
 947     private int readAheadMode = ReadAheadIterator.FULL;
 948     private ReadAheadIterator raTestIter;
 949     private int numTestsDone;
 950     private TestEnvironment env;
 951     private TestResultTable resultTable;
 952     private Notifier notifier = new Notifier();
 953 
 954     private long startTime = -1l;
 955     private long finishTime = -1l;
 956     private long cleanupFinishTime = -1l;
 957     private long testsStartTime = -1l;
 958     private boolean isBatchRun;
 959     private boolean stopping;
 960 
 961     private static File classDir;
 962     private static final boolean ZERO_TESTS_OK = true;
 963     private static final boolean ZERO_TESTS_ERROR = false;
 964     private static final int DEFAULT_READ_AHEAD = 100;
 965     private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(Harness.class);
 966 
 967     private class Notifier implements Harness.Observer
 968     {
 969         void addObserver(Observer o) {
 970             if (o == null)
 971                 throw new NullPointerException();
 972             observers = DynamicArray.append(observers, o);
 973         }
 974 
 975         void removeObserver(Observer o) {
 976             observers = DynamicArray.remove(observers, o);
 977         }
 978 
 979         public void startingTestRun(Parameters params) {
 980             resultTable.starting();
 981 
 982             // protect against removing observers during notification
 983             Observer[] stableObservers = observers;
 984             for (int i = stableObservers.length - 1; i >= 0; i--)
 985                 stableObservers[i].startingTestRun(params);
 986         }
 987 
 988         public void startingTest(TestResult tr) {
 989             // protect against removing observers during notification
 990             Observer[] stableObservers = observers;
 991             for (int i = stableObservers.length - 1; i >= 0; i--)
 992                 stableObservers[i].startingTest(tr);
 993         }
 994 
 995         public void finishedTest(TestResult tr) {
 996             numTestsDone++;
 997             resultTable.update(tr);
 998             // protect against removing observers during notification
 999             Observer[] stableObservers = observers;
1000             for (int i = stableObservers.length - 1; i >= 0; i--)
1001                 stableObservers[i].finishedTest(tr);
1002 
1003             switch (tr.getStatus().getType()) {
1004                 case Status.FAILED:
1005                     synchronized(this) {
1006                         failCount++;
1007                     }
1008                     break;
1009                 case Status.ERROR:
1010                     synchronized(this) {
1011                         errCount++;
1012                     }
1013                     break;
1014                 // XXX possibility exists for NOT_RUN, this should also
1015                 //     be recorded as a problem, or is it a "problem"?
1016                 default:
1017             }   // switch
1018         }
1019 
1020         public void stoppingTestRun() {
1021             // protect against removing observers during notification
1022             Observer[] stableObservers = observers;
1023             for (int i = stableObservers.length - 1; i >= 0; i--)
1024                 stableObservers[i].stoppingTestRun();
1025         }
1026 
1027         public void finishedTesting() {
1028             resultTable.finished();
1029 
1030             // protect against removing observers during notification
1031             Observer[] stableObservers = observers;
1032             for (int i = stableObservers.length - 1; i >= 0; i--)
1033                 stableObservers[i].finishedTesting();
1034         }
1035 
1036         public void finishedTestRun(boolean allOK) {
1037             // protect against removing observers during notification
1038             Observer[] stableObservers = observers;
1039             for (int i = stableObservers.length - 1; i >= 0; i--)
1040                 stableObservers[i].finishedTestRun(allOK);
1041         }
1042 
1043         public void error(String msg) {
1044             // protect against removing observers during notification
1045             Observer[] stableObservers = observers;
1046             for (int i = stableObservers.length - 1; i >= 0; i--)
1047                 stableObservers[i].error(msg);
1048         }
1049 
1050         synchronized int getErrorCount() {
1051             return errCount;
1052         }
1053 
1054         synchronized int getFailedCount() {
1055             return failCount;
1056         }
1057 
1058         private Observer[] observers = new Observer[0];
1059         private volatile int errCount, failCount;
1060     }
1061 
1062 
1063     class Autostop implements Harness.Observer {
1064         Autostop(int threshold) {
1065             this.threshold = threshold;
1066         }
1067 
1068         public void startingTestRun(Parameters p) { }
1069 
1070         public void startingTest(TestResult tr) { }
1071 
1072         public void finishedTest(TestResult tr) {
1073             switch (tr.getStatus().getType()) {
1074             case Status.FAILED:
1075                 level++;
1076                 break;
1077             case Status.ERROR:
1078                 level += 5;
1079                 break;
1080             default:
1081                 level = Math.max(level - 2, 0);
1082             }
1083             if (level >= threshold) {
1084                 Harness.this.notifyError(i18n, "harness.tooManyErrors");
1085                 stop();
1086             }
1087         }
1088         public void stoppingTestRun() { }
1089 
1090         public void finishedTesting() { }
1091 
1092         public void finishedTestRun(boolean allOK) { }
1093 
1094         public void error(String msg) { }
1095 
1096         private int level;
1097         private int threshold;
1098     }
1099 
1100     /**
1101      * Class that collects executed tests
1102      */
1103     class TestURLCollector implements Harness.Observer {
1104         TestURLCollector() {
1105         }
1106 
1107         final List<String> testURLs = new ArrayList<>();
1108 
1109         public void startingTestRun(Parameters p) { }
1110 
1111         public synchronized void startingTest(TestResult tr) {
1112             testURLs.add(tr.getTestName());
1113         }
1114 
1115         public void finishedTest(TestResult tr) { }
1116 
1117         public void stoppingTestRun() { }
1118 
1119         public void finishedTesting() { }
1120 
1121         public void finishedTestRun(boolean allOK) { }
1122 
1123         public void error(String msg) { }
1124     }
1125 }