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 }