1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 1996, 2009, 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.util.Hashtable;
  31 import java.util.Map;
  32 import java.util.Vector;
  33 
  34 import com.sun.javatest.util.DynamicArray;
  35 import com.sun.javatest.util.Fifo;
  36 import com.sun.javatest.util.I18NResourceBundle;
  37 
  38 /**
  39  * An iterator-based interface to the tests in a test suite, as read by a test finder.
  40  */
  41 public class TestFinderQueue {
  42     /**
  43      * This interface provides a means for TestFinder to report on events that
  44      * might be of interest as it executes.
  45      */
  46     public static interface Observer
  47     {
  48         /**
  49          * Another file which needs to be read has been found.
  50          * @param file the file which was found
  51          */
  52         void found(File file);
  53 
  54         /**
  55          * A file is being read.
  56          * @param file the file being read
  57          */
  58         void reading(File file);
  59 
  60         /**
  61          * A file has been read.
  62          * @param file the file which was read
  63          */
  64         void done(File file);
  65 
  66         /**
  67          * A test description has been found.
  68          * @param td the test description which was found
  69          */
  70         void found(TestDescription td);
  71 
  72         /**
  73          * A test description which was previously found, has been rejected by
  74          * a test filter, and so has not been put in the queue of tests to be executed.
  75          * @param td the test description which was rejected by the filter
  76          * @param f the filter which rejected the test
  77          */
  78         void ignored(TestDescription td, TestFilter f);
  79 
  80         /**
  81          * A test description that was previously put in the test finder queue
  82          * has been taken from the queue and passed back to the client caller.
  83          * @param td the test description which was taken from the queue
  84          */
  85         void done(TestDescription td);
  86 
  87         /**
  88          * The queue of tests has been flushed.
  89          */
  90         void flushed();
  91 
  92         /**
  93          * An error was reported by the test finder while reading a file.
  94          * @param msg a detail message describing the error
  95          */
  96         void error(String msg);
  97 
  98         /**
  99          * An error was reported by the test finder while reading a file.
 100          * @param td the test description to which the error applies
 101          * @param msg a detail message describing the error
 102          */
 103         void error(TestDescription td, String msg);
 104     }
 105 
 106     /**
 107      * Create a test finder queue.
 108      */
 109     public TestFinderQueue() {
 110     }
 111 
 112     /**
 113      * Create a test finder queue, using a specified test finder.
 114      * @param finder the test finder to be used to read the tests
 115      */
 116     public TestFinderQueue(TestFinder finder) {
 117         setTestFinder(finder);
 118     }
 119 
 120     /**
 121      * Get the test finder being used by this object.
 122      * @return the test finder being used by this object
 123      * @see #setTestFinder
 124      */
 125     public TestFinder getTestFinder() {
 126         return testFinder;
 127     }
 128 
 129     /**
 130      * Set the test finder to be used by this object.
 131      * It may only be set once.
 132      * @param finder the test finder to be used by this object
 133      * @throws NullPointerException if the finder is null
 134      * @throws IllegalStateException if the finder has already been set
 135      * @see #getTestFinder
 136      */
 137     public void setTestFinder(TestFinder finder) {
 138         if (finder == null)
 139             throw new NullPointerException();
 140 
 141         if (testFinder != null && testFinder != finder)
 142             throw new IllegalStateException();
 143 
 144         this.testFinder = finder;
 145         testFinder.setErrorHandler(new TestFinder.ErrorHandler() {
 146             public void error(String msg) {
 147                 errorCount++;
 148                 notifier.error(msg);
 149             }
 150         });
 151     }
 152 
 153     /**
 154      * Set an array of filters that will be used to filter the tests read by the
 155      * test finder. Each test must be accepted by all the filters to be put in
 156      * the queue.
 157      * @param filters the filters to be used.
 158      */
 159     public void setFilters(TestFilter[] filters) {
 160         this.filters = filters;
 161     }
 162 
 163     /**
 164      * Set the initial set of files to be read by the test finder.
 165      * Additional files may be read as a result of reading these and subsequent files.
 166      * @param initTests the initial set of files to be read by the test finder
 167      */
 168     public synchronized void setTests(String[] initTests) {
 169         File testSuiteRoot = testFinder.getRoot();
 170 
 171         // make canonical copy of tests
 172         // really ought not to be using File, since the tests may contain a trailing #xxx
 173         File[] files;
 174         if (initTests == null)
 175             // ensure not null
 176             files = new File[] {testSuiteRoot};
 177         else {
 178             files = new File[initTests.length];
 179             for (int i = 0; i < initTests.length; i++) {
 180                 files[i] = new File(initTests[i]);
 181             }
 182         }
 183 
 184         rootDir = (testSuiteRoot.isDirectory() ?
 185                    testSuiteRoot : new File(testSuiteRoot.getParent()));
 186 
 187         // build up the fifo of tests to be used by readNextFile
 188 
 189         tests = new Fifo<>();
 190         currInitialFile = null;
 191 
 192         for (int pass = 0; pass < 2; pass++) {
 193             for (int i = 0; i < files.length; i++) {
 194                 File f = files[i];
 195                 String n = f.getName();
 196                 // in pass 0, only select initial files without #
 197                 // in pass 1, only select initial files with #
 198                 if ((n.indexOf("#") != -1) == (pass == 0))
 199                     continue;
 200 
 201                 // ensure all absolute, or if relative, make them relative
 202                 // to rootDir
 203                 if (!f.isAbsolute())
 204                     f = new File(rootDir, f.getPath());
 205                 // ensure no trailing file separator
 206                 String p = f.getPath();
 207                 if (p.endsWith(File.separator))
 208                     f = new File(p.substring(0, p.length() - 1));
 209                 tests.insert(f);
 210             }
 211         }
 212 
 213         filesRemainingCount = filesToRead.size() + tests.size();
 214     }
 215 
 216     /**
 217      * Set a flag indicating whether it is OK to find no tests in the
 218      * specified set of files. If set to false, and if no tests have
 219      * been found by the time the last file has been read, an error
 220      * will be notified to any observers.
 221      * @param zeroTestsOK set to true to suppress an error being generated
 222      * if no tests are found by the time that all files have been read
 223      */
 224     public void setZeroTestsOK(boolean zeroTestsOK) {
 225         this.zeroTestsOK = zeroTestsOK;
 226     }
 227 
 228     /**
 229      * Set the queue to "repeat" a set of test descriptions by putting
 230      * them in the test found queue again.
 231      * @param tds the test descriptions to be "found again".
 232      * @deprecated retained for historical purposes
 233      */
 234     public void repeat(TestDescription[] tds) {
 235         if (tests == null)
 236             tests = new Fifo<>(); // for now
 237         for (int i = 0; i < tds.length; i++) {
 238             TestDescription td = tds[i];
 239             testDescsFound.insert(td);
 240             testsFoundCount++;
 241             notifier.found(td);
 242         }
 243     }
 244 
 245 
 246 
 247     /**
 248      * Get the next test description if one is available, or null when all have
 249      * been returned.
 250      *
 251      * @return     A test description or null.
 252      */
 253     public TestDescription next() {
 254         TestDescription td;
 255 
 256         synchronized (this) {
 257             while (needReadAhead() && readNextFile()) /*NO-OP*/;
 258 
 259             // read files until there is a test description available or there
 260             // are no more files.
 261             while ((td = testDescsFound.remove()) == null) {
 262                 boolean ok = readNextFile();
 263                 if (!ok)
 264                     return null;
 265             }
 266 
 267             // note testsDone, for readAhead
 268             testsDoneCount++;
 269         }
 270 
 271         notifier.done(td);
 272         return td;
 273     }
 274 
 275 
 276     //--------------------------------------------------------------------------
 277 
 278     /**
 279      * Get the root directory for the test finder.
 280      * @return the root directory, as set in the test finder
 281      */
 282     public File getRoot() {
 283         return rootDir;
 284     }
 285 
 286     //--------------------------------------------------------------------------
 287     //
 288     // these are all carefully arranges to not need to be synchronized
 289 
 290     /**
 291      * Get the number of files that have been found so far.
 292      * @return the number of files that have been found so far
 293      */
 294     public int getFilesFoundCount() {
 295         return filesFound.size();
 296     }
 297 
 298     /**
 299      * Get the number of files that have been found and read so far.
 300      * @return the number of files that have been found and read so far
 301      */
 302     public int getFilesDoneCount() {
 303         return filesDoneCount;
 304     }
 305 
 306     /**
 307      * Get the number of files that have been found but not yet read so far.
 308      * @return the number of files that have been found but not yet read so far
 309      */
 310     public int getFilesRemainingCount() {
 311         return filesRemainingCount;
 312     }
 313 
 314     /**
 315      * Get the number of tests that have been found so far.
 316      * @return the number of tests that have been found so far
 317      */
 318     public int getTestsFoundCount() {
 319         return testsFoundCount;
 320     }
 321 
 322     /**
 323      * Get the number of tests that have been read from this object so far.
 324      * @return the number of tests that have been read from this object so far
 325      */
 326     public int getTestsDoneCount() {
 327         return testsDoneCount;
 328     }
 329 
 330     /**
 331      * Get the number of tests which have been found but not yet from this
 332      * object so far.
 333      * @return the number of tests which have been found but not yet read
 334      * from this object so far
 335      */
 336     public int getTestsRemainingCount() {
 337         return testDescsFound.size();
 338     }
 339 
 340     /**
 341      * Get the number of errors that have been found so far by the test finder
 342      * while reading the tests.
 343      * @return the number of errors that have been found so far by the test finder
 344      * while reading the tests.
 345      */
 346     public int getErrorCount() {
 347         return errorCount;
 348     }
 349 
 350     //--------------------------------------------------------------------------
 351 
 352     /**
 353      * Add an observer to monitor the progress of the TestFinder.
 354      * @param o the observer
 355      */
 356     public void addObserver(Observer o) {
 357         notifier.addObserver(o);
 358     }
 359 
 360     /**
 361      * Remove an observer form the set currently monitoring the progress
 362      * of the TestFinder.
 363      * @param o the observer
 364      */
 365     public void removeObserver(Observer o) {
 366         notifier.removeObserver(o);
 367     }
 368 
 369     //--------------------------------------------------------------------------
 370 
 371 
 372     /**
 373      * Set the amount of read-ahead done by the finder.
 374      * @param mode      acceptable values are as follows:
 375      * <dl> <dt> 0: no read ahead
 376      * <dd> Files are not read ahead more than necessary
 377      * <dt> 1: low read ahead
 378      * <dd> A low priority thread is created to read the test files
 379      * when the system is otherwise idle
 380      * <dt> 2: medium read ahead
 381      * <dd> A low priority thread is created to read the test files
 382      * when the system is otherwise idle. In addition, if the number
 383      * of tests done approaches the number of tests read, then more
 384      * tests will be read.
 385      * <dt> 3: full and immediate read ahead
 386      * <dd> All the tests will be read now
 387      * </dl>
 388      */
 389     public synchronized void setReadAheadMode(byte mode) {
 390         switch (mode) {
 391         case NO_READ_AHEAD:
 392         case FULL_READ_AHEAD:
 393             readAheadMode = mode;
 394             readAheadWorker = null; // worker will note this and go away
 395             break;
 396 
 397         case LOW_READ_AHEAD:
 398         case MEDIUM_READ_AHEAD:
 399             readAheadMode = mode;
 400             if (readAheadWorker == null) {
 401                 readAheadWorker = new Thread() {
 402                     public void run() {
 403                         // This is intended to be interruptible and
 404                         // relies on safe atomic access to worker
 405                         while ((readAheadWorker == this) && readNextFile()) /*NO-OP*/;
 406                         // when thread exits; flatten pointer if still current
 407                         synchronized (TestFinderQueue.this) {
 408                             if (readAheadWorker == this)
 409                                 readAheadWorker = null;
 410                         }
 411                     }
 412                 };
 413                 readAheadWorker.setName("TestFinderQueue:Worker:" + workerIndex++);
 414                 readAheadWorker.setPriority(Thread.MIN_PRIORITY);
 415                 readAheadWorker.start();
 416             }
 417             break;
 418 
 419         default:
 420             throw new IllegalArgumentException("invalid value for mode");
 421         }
 422     }
 423 
 424     /**
 425      * A constant specifying that the test finder queue should not perform
 426      * any read ahead.
 427      */
 428     public static final byte NO_READ_AHEAD = 0;
 429 
 430     /**
 431      * A constant specifying the test finder queue should perform minimal
 432      * read ahead.
 433      */
 434     public static final byte LOW_READ_AHEAD = 1;
 435 
 436     /**
 437      * A constant specifying the test finder queue should perform medium
 438      * (typical) read ahead.
 439      */
 440     public static final byte MEDIUM_READ_AHEAD = 2;
 441 
 442     /**
 443      * A constant specifying the test finder queue should perform complete
 444      * read ahead, reading all tests from the test finder before returning any
 445      * from this object.
 446      */
 447     public static final byte FULL_READ_AHEAD = 3;
 448 
 449     /**
 450      * Flush all readahead.
 451      */
 452     public void flush() {
 453         synchronized (this) {
 454             filesToRead.setSize(0);
 455             tests.flush();
 456             testDescsFound.flush();
 457             filesRemainingCount = 0;
 458         }
 459         notifier.flushed();
 460     }
 461 
 462 
 463     /**
 464      * This method is called from next() to determine if any readAhead
 465      * should be done there and then, before getting the next TestDescription
 466      * for the client.
 467      */
 468     private boolean needReadAhead() {
 469         switch (readAheadMode) {
 470         case FULL_READ_AHEAD:
 471             return true;
 472         case MEDIUM_READ_AHEAD:
 473             // return true if not many tests read yet, or if testsDoneCount
 474             // is greater than a certain percentage of testsFoundCount.
 475             // This percentage increases inverse exponentially on the
 476             // number of tests found. The intent is to try and keep
 477             // progress meters based on testsDoneCount and testsFoundCount helpful,
 478             // while permitting readAhead to be done.
 479             // The formula has the following datapoints:
 480             // testsFoundCount: 1000   percent: 18%
 481             // testsFoundCount: 10000  percent: 86%
 482             if (testsFoundCount < 100)
 483                 // don't let tests start until at least 100 have been read
 484                 return true;
 485             else {
 486                 double percent = 1 - Math.exp(-0.0002 * testsFoundCount);
 487                 return (testsDoneCount > (testsFoundCount * percent));
 488             }
 489         default:
 490             return false;
 491         }
 492     }
 493 
 494     //---------------------------------------------------------------
 495 
 496     private synchronized boolean readNextFile() {
 497         if (filesToRead.isEmpty()) {
 498             // have we finished reading an initial file and found no test descriptions in it?
 499             // if so, inform the caller
 500             if (currInitialFile != null
 501                 && testsFoundCountBeforeCurrInitialFile == testsFoundCount
 502                 && !zeroTestsOK) {
 503                 errorCount++;
 504                 notifier.error(i18n.getString("finder.noTests", currInitialFile));
 505             }
 506 
 507             // are there any more tests that have not been read?
 508             // check until we find one (just one).
 509             while (filesToRead.isEmpty() && !tests.isEmpty()) {
 510                 currInitialFile = tests.remove();
 511                 foundFile(currInitialFile);
 512             }
 513 
 514             // if we didn't find any more initial files, there is nothing more to do
 515             if (filesToRead.isEmpty()) {
 516                 currInitialFile = null;
 517                 return false;
 518             }
 519             else
 520                 testsFoundCountBeforeCurrInitialFile = testsFoundCount;
 521         }
 522 
 523 
 524         File f = filesToRead.lastElement();
 525         filesToRead.setSize(filesToRead.size() - 1);
 526         filesRemainingCount = filesToRead.size() + tests.size();
 527 
 528         String path = f.getPath();
 529         int index = path.indexOf('#');
 530         if (index != -1) {
 531             selectedId = path.substring(index + 1);
 532             f = new File(path.substring(0, index));
 533         }
 534 
 535         // The filesToRead are maintained in a vector that approximates a stack:
 536         // new entries are added at or near the end, and entries are taken from
 537         // the end. The subtlety is to add the new files found in file in reverse
 538         // order, so that when removed from the end, they are used in the
 539         // correct order. This is done by inserting each new file found at a fixed
 540         // place, corresponding to the end of the vector as it is when scan() starts.
 541         // The net effect is to push up files added earlier in this scan, so they
 542         // they will be used first.   The overall net effect is that of a depth-first
 543         // search for test descriptions.
 544         fileInsertPosn = filesToRead.size();
 545 
 546         notifier.reading(f);
 547         try {
 548             testFinder.read(f);
 549         }
 550         finally {
 551             TestDescription[] tds = testFinder.getTests();
 552             for (int i = 0; i < tds.length; i++) {
 553                 foundTestDescription(tds[i]);
 554             }
 555 
 556             File[] files = testFinder.getFiles();
 557             for (int i = 0; i < files.length; i++) {
 558                 foundFile(files[i]);
 559             }
 560 
 561             // done limiting tests to this id
 562             selectedId = null;
 563             filesDoneCount++;
 564             notifier.done(f);
 565 
 566             /*
 567             if (filesToRead.isEmpty()) {
 568                 // we have read all the files we can
 569                 // flush various tables to free up any space used
 570                 filesFound.clear();
 571                 testsInFile.clear();
 572             }
 573             */
 574 
 575         }
 576 
 577         // read a file OK
 578         return true;
 579     }
 580 
 581     /**
 582      * Add a file to the queue of files waiting to be read.
 583      * It will be added to the queue if it has not already been read or is
 584      * on the queue waiting to be read, and if the finder is not looking for
 585      * a specific test in the current file.
 586      * @param newFile The file to be queued
 587      */
 588     private void foundFile(File newFile) {
 589         // only accept new files if not looking for a specific test in the
 590         // current file
 591         if (selectedId == null) {
 592             Object prev = filesFound.put(newFile.getPath(), newFile);
 593             if (prev == null) {
 594                 filesToRead.insertElementAt(newFile, fileInsertPosn);
 595                 notifier.found(newFile);
 596             }
 597         }
 598     }
 599 
 600     private void foundTestDescription(TestDescription td) {
 601         // if we are not searching for a specific test, or if we are and we have
 602         // found it, then add the test to the list of tests we have found
 603         if (selectedId == null || selectedId.equals(td.getId())) {
 604             if (filters != null) {
 605                 for (int i = 0; i < filters.length; i++) {
 606                     TestFilter filter = filters[i];
 607                     try {
 608                         if (!filter.accepts(td)) {
 609                             notifier.ignored(td, filter);
 610                             return;
 611                         }
 612                     }
 613                     catch (TestFilter.Fault e) {
 614                         errorCount++;
 615                         notifier.error(td, e.getMessage());
 616                         return;
 617                     }
 618                 }
 619             }
 620 
 621             testDescsFound.insert(td);
 622             testsFoundCount++;
 623             notifier.found(td);
 624         }
 625     }
 626 
 627     //---------------------------------------------------------------
 628 
 629     private static class Notifier implements Observer {
 630         public synchronized void addObserver(Observer o) {
 631             observers = DynamicArray.append(observers, o);
 632         }
 633 
 634         public synchronized void removeObserver(Observer o) {
 635             observers = DynamicArray.remove(observers, o);
 636         }
 637 
 638         public synchronized void found(File file) {
 639             for (int i = 0; i < observers.length; i++)
 640                 observers[i].found(file);
 641         }
 642 
 643         public synchronized void reading(File file) {
 644             for (int i = 0; i < observers.length; i++)
 645                 observers[i].reading(file);
 646         }
 647 
 648         public synchronized void done(File file) {
 649             for (int i = 0; i < observers.length; i++)
 650                 observers[i].done(file);
 651         }
 652 
 653         public synchronized void found(TestDescription td) {
 654             for (int i = 0; i < observers.length; i++)
 655                 observers[i].found(td);
 656         }
 657 
 658         public synchronized void ignored(TestDescription td, TestFilter f) {
 659             for (int i = 0; i < observers.length; i++)
 660                 observers[i].ignored(td, f);
 661         }
 662 
 663         public synchronized void done(TestDescription td) {
 664             for (int i = 0; i < observers.length; i++)
 665                 observers[i].done(td);
 666         }
 667 
 668         public synchronized void flushed() {
 669             for (int i = 0; i < observers.length; i++)
 670                 observers[i].flushed();
 671         }
 672 
 673         public synchronized void error(String msg) {
 674             for (int i = 0; i < observers.length; i++)
 675                 observers[i].error(msg);
 676         }
 677 
 678         public synchronized void error(TestDescription td, String msg) {
 679             for (int i = 0; i < observers.length; i++)
 680                 observers[i].error(td, msg);
 681         }
 682 
 683         private Observer[] observers = new Observer[0];
 684     }
 685 
 686     //----------member variables------------------------------------------------
 687 
 688 
 689     private TestFinder testFinder;
 690     private Fifo<File> tests;
 691     private TestFilter[] filters;
 692     private String selectedId;
 693     private File rootDir;
 694     private File currInitialFile;
 695     private int testsFoundCountBeforeCurrInitialFile;
 696     private boolean zeroTestsOK;
 697 
 698     private Vector<File> filesToRead  = new Vector<>(32, 8);
 699     private int fileInsertPosn;
 700     private Fifo<TestDescription> testDescsFound = new Fifo<>();
 701     private int filesRemainingCount;
 702     private int filesDoneCount;
 703     private int testsDoneCount;
 704     private int testsFoundCount;
 705     private int errorCount;
 706 
 707     private Map<String, File> filesFound  = new Hashtable<>();
 708 
 709     private byte readAheadMode;
 710     private Thread readAheadWorker;
 711     private static int workerIndex;
 712 
 713     private Notifier notifier = new Notifier();
 714     private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(TestFinder.class);
 715 }