1 /* 2 * $Id$ 3 * 4 * Copyright (c) 2002, 2018, 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.audit; 28 29 import java.io.PrintStream; 30 import java.text.DateFormat; 31 import java.text.ParseException; 32 import java.text.SimpleDateFormat; 33 import java.util.Arrays; 34 import java.util.Date; 35 import java.util.Enumeration; 36 import java.util.Hashtable; 37 import java.util.Iterator; 38 import java.util.Locale; 39 import java.util.Map; 40 import java.util.SortedSet; 41 import java.util.TreeSet; 42 import java.util.Vector; 43 44 import com.sun.javatest.ExcludeList; 45 import com.sun.javatest.Parameters; 46 import com.sun.javatest.Status; 47 import com.sun.javatest.TestDescription; 48 import com.sun.javatest.TestFilter; 49 import com.sun.javatest.TestFinder; 50 import com.sun.javatest.TestFinderQueue; 51 import com.sun.javatest.TestResult; 52 import com.sun.javatest.TestSuite; 53 import com.sun.javatest.WorkDirectory; 54 import com.sun.javatest.util.I18NResourceBundle; 55 import com.sun.javatest.util.StringArray; 56 57 /** 58 * Analyze a set of test results for validity. 59 * Based on the given parameters, a test finder is run over the test suite 60 * to determine which tests are supposed to have been run, and the work 61 * directory is checked to see that the corresponding test has been run, 62 * successfully. Various statistics are collected and can be printed or 63 * accessed for external analysis. 64 */ 65 public class Audit 66 { 67 /** 68 * Analyze a set of test results for validity, based on the given parameters. 69 * @param params Parameters to define the test finder and work directory 70 * used in the analysis. 71 * @throws TestSuite.Fault if there is a problem opening the test suite given in the parameters 72 */ 73 public Audit(Parameters params) 74 { 75 this(getTestFinderQueue(params), 76 params.getExcludeList(), 77 params.getWorkDirectory()); 78 } 79 80 /** 81 * Create a test finder queue based on the info in the parameters 82 */ 83 private static TestFinderQueue getTestFinderQueue(Parameters params) 84 { 85 TestSuite ts = params.getTestSuite(); 86 TestFinder tf = ts.getTestFinder(); 87 88 TestFinderQueue tfq = new TestFinderQueue(); 89 tfq.setTestFinder(tf); 90 91 String[] tests = params.getTests(); 92 tfq.setTests(tests); 93 94 TestFilter[] filters = params.getFilters(); 95 tfq.setFilters(filters); 96 97 return tfq; 98 } 99 100 101 /** 102 * Analyze a set of test results for validity, based on the given parameters. 103 * @param tfq An enumerator for the set of tests to be run 104 * @param excludeList The excludeList against which to check excluded test cases 105 * @param workDir The set of results 106 */ 107 public Audit(TestFinderQueue tfq, ExcludeList excludeList, WorkDirectory workDir) 108 { 109 Vector<TestResult> badChecksumTestsV = new Vector<>(); 110 Vector<TestResult> badTestDescriptionsV = new Vector<>(); 111 Vector<TestResult> badTestCaseTestsV = new Vector<>(); 112 Vector<TestDescription> badTestsV = new Vector<>(); 113 114 workDir.getTestResultTable().waitUntilReady(); 115 116 TestDescription td; 117 while ((td = tfq.next()) != null) { 118 try { 119 TestResult tr = new TestResult(workDir, TestResult.getWorkRelativePath(td)); 120 121 testCount++; 122 statusCounts[tr.getStatus().getType()]++; 123 checksumCounts[tr.getChecksumState()]++; 124 125 if (tr.getChecksumState() == TestResult.BAD_CHECKSUM) 126 badChecksumTestsV.addElement(tr); 127 128 if (!equal(td, tr.getDescription())) 129 badTestDescriptionsV.addElement(tr); 130 131 if (!checkTestCases(tr, excludeList)) 132 badTestCaseTestsV.addElement(tr); 133 134 Map<String, String> trEnv = tr.getEnvironment(); 135 for (Map.Entry<String, String> e : trEnv.entrySet() ) { 136 String key = e.getKey(); 137 String value = e.getValue(); 138 Vector<String> allValuesForKey = envTable.get(key); 139 if (allValuesForKey == null) { 140 allValuesForKey = new Vector<>(); 141 envTable.put(key, allValuesForKey); 142 } 143 if (!allValuesForKey.contains(value)) 144 allValuesForKey.addElement(value); 145 } 146 147 String start = tr.getProperty(TestResult.START); 148 if (start == null) { 149 badDates = true; 150 } 151 else { 152 Date d = parseDate(start); 153 if (d == null) 154 badDates = true; 155 else { 156 if (earliestStart == null || d.before(earliestStart)) 157 earliestStart = d; 158 if (latestStart == null || d.after(latestStart)) 159 latestStart = d; 160 } 161 } 162 } 163 catch (TestResult.Fault e) { 164 //System.err.println(td.getRootRelativeURL() + " " + TestResult.getWorkRelativePath(td) + " " + e); 165 badTestsV.addElement(td); 166 } 167 } 168 169 for (Enumeration<String> e = envTable.keys(); e.hasMoreElements(); ) { 170 String key = e.nextElement(); 171 Vector<String> allValuesForKey = envTable.get(key); 172 envCounts[allValuesForKey.size() == 1 ? 0 : 1]++; 173 } 174 175 if (badChecksumTestsV.size() > 0) { 176 badChecksumTests = new TestResult[badChecksumTestsV.size()]; 177 badChecksumTestsV.copyInto(badChecksumTests); 178 } 179 180 if (badTestDescriptionsV.size() > 0) { 181 badTestDescriptions = new TestResult[badTestDescriptionsV.size()]; 182 badTestDescriptionsV.copyInto(badTestDescriptions); 183 } 184 185 if (badTestCaseTestsV.size() > 0) { 186 badTestCaseTests = new TestResult[badTestCaseTestsV.size()]; 187 badTestCaseTestsV.copyInto(badTestCaseTests); 188 } 189 190 if (badTestsV.size() > 0) { 191 badTests = new TestDescription[badTestsV.size()]; 192 badTestsV.copyInto(badTests); 193 } 194 } 195 196 /** 197 * Get a list of tests that had bad checksums during the analysis. 198 * @return A set of tests, or null if none. 199 */ 200 public TestResult[] getBadChecksumTests() { 201 return badChecksumTests; 202 } 203 204 /** 205 * Get a list of tests that had bad test descriptions during the analysis. 206 * @return A set of tests, or null if none. 207 */ 208 public TestResult[] getBadTestDescriptions() { 209 return badTestDescriptions; 210 } 211 212 /** 213 * Get a list of tests that gave problems during the analysis. 214 * @return A set of tests, or null if none. 215 */ 216 public TestDescription[] getBadTests() { 217 return badTests; 218 } 219 220 /** 221 * Get a list of tests that were executed with too many test cases 222 * excluded. 223 * @return A set of tests, or null if none. 224 */ 225 public TestResult[] getBadTestCaseTests() { 226 return badTestCaseTests; 227 } 228 229 /** 230 * Get the statistics about the test result checksums. 231 * @return An array of counts, indexed by 232 * TestResult.GOOD_CHECKSUM, TestResult.BAD_CHECKSUM, etc 233 */ 234 public int[] getChecksumCounts() { 235 return checksumCounts; 236 } 237 238 /** 239 * Get the statistics about the environment values used by the test results. 240 * @return An array of two integers; the first gives the number of env 241 * values that were uniquely defined across all test results, the second gives 242 * the number that were multiply defined across the results. 243 */ 244 public int[] getEnvCounts() { 245 return envCounts; 246 } 247 248 /** 249 * Get the composite set of environment values used by the tests. 250 * @return A table of values. 251 * The keys to the table are strings; the values are vectors of strings 252 * containing the various values for that key. 253 */ 254 public Hashtable<String, Vector<String>> getEnvTable() { 255 return envTable; 256 } 257 258 /** 259 * Get the statistics about the test results. 260 * @return An array of counts, indexed by Status.PASSED, Status.FAILED, etc 261 */ 262 public int[] getStatusCounts() { 263 return statusCounts; 264 } 265 266 /** 267 * Get earliest recorded start time for a test. 268 * @return The earliest recorded valid start time for a test, 269 * or null, if none was found. 270 */ 271 public Date getEarliestStartTime() { 272 return earliestStart; 273 } 274 275 /** 276 * Get latest recorded start time for a test. 277 * @return The latest recorded valid start time for a test, 278 * or null, if none was found. 279 */ 280 public Date getLatestStartTime() { 281 return latestStart; 282 } 283 284 public boolean hasBadStartTimes() { 285 return badDates; 286 } 287 288 /** 289 * Get an overall thumps-up/thumbs-down for the analysis. 290 * It is a composite of the other isXXXOK() methods. 291 * @return true if all checks are OK 292 */ 293 public boolean isOK() { 294 return (isStatusCountsOK() 295 && isChecksumCountsOK() 296 && isDateStampsOK() 297 && isAllTestsOK() 298 && isAllTestCasesOK()) 299 && isAllTestDescriptionsOK(); 300 } 301 302 /** 303 * Determine if all tests were analyzed successfully. 304 * @return true if all tests were analyzed successfully. 305 */ 306 public boolean isAllTestsOK() { 307 return (badTests == null); 308 } 309 310 /** 311 * Determine if all test cases were correctly executed for those 312 * tests that support test cases. 313 * @return true if all test cases were correctly executed for those 314 * tests that support test cases. 315 */ 316 public boolean isAllTestCasesOK() { 317 return (badTestCaseTests == null); 318 } 319 320 /** 321 * Determine if all test descriptions were OK. 322 * @return true is all test descriptions were OK. 323 */ 324 public boolean isAllTestDescriptionsOK() { 325 return (badTestDescriptions == null); 326 } 327 328 /** 329 * Determine if the checksum counts are acceptable. 330 * This is currently defined as "no bad checksums"; 331 * in time, it should become "all good checksums". 332 * @return true is there were no test results with bad checksums. 333 */ 334 public boolean isChecksumCountsOK() { 335 return (checksumCounts[TestResult.BAD_CHECKSUM] == 0); 336 } 337 338 /** 339 * Determine if all the test results have valid date stamps. 340 * "Valid" just means present and parseable dates: no restriction 341 * on the value is currently imposed. 342 * @return true if no dates have invalid datestamps. 343 */ 344 public boolean isDateStampsOK() { 345 return (earliestStart != null && latestStart != null && !badDates); 346 } 347 348 /** 349 * Determine if the test result outcomes are acceptable. 350 * All must pass, none must fail. 351 * @return true if all necessary tests passed. 352 */ 353 public boolean isStatusCountsOK() { 354 if (testCount == 0) 355 return false; 356 357 for (int i = 0; i < statusCounts.length; i++) { 358 if (i != Status.PASSED && statusCounts[i] != 0) 359 return false; 360 } 361 return (statusCounts[Status.PASSED] == testCount); 362 } 363 364 /** 365 * Print out a report of the analysis. 366 * @param out A stream to which to write the output. 367 * @param showAllEnvValues Include a listing of all environment values used 368 * by the collected set of tests. 369 * @param showMultipleEnvValues Include a listing of those environment values 370 * which were multiply defined by the collected set of tests. 371 */ 372 public synchronized void report(PrintStream out, 373 boolean showAllEnvValues, 374 boolean showMultipleEnvValues) { 375 this.out = out; 376 377 showResultCounts(); 378 showChecksumCounts(); 379 showDateStampInfo(); 380 showEnvCounts(); 381 382 showBadChecksums(); 383 showBadTestDescriptions(); 384 showBadTestCaseTests(); 385 showBadTests(); 386 387 if (showAllEnvValues || showMultipleEnvValues) 388 showEnvValues(showAllEnvValues); 389 } 390 391 /** 392 * Print out a short summary about any tests with bad checksums 393 */ 394 private void showBadChecksums() { 395 if (badChecksumTests != null) { 396 out.println("The following " + badChecksumTests.length + " tests had bad checksums."); 397 for (int i = 0; i < badChecksumTests.length; i++) { 398 TestResult tr = badChecksumTests[i]; 399 out.println(tr.getWorkRelativePath()); 400 } 401 } 402 } 403 404 /** 405 * Print out a short summary about any tests with bad test descriptions 406 */ 407 private void showBadTestDescriptions() { 408 if (badTestDescriptions != null) { 409 out.println("The following " + badTestDescriptions.length + " tests had bad test descriptions."); 410 for (int i = 0; i < badTestDescriptions.length; i++) { 411 TestResult tr = badTestDescriptions[i]; 412 out.println(tr.getWorkRelativePath()); 413 } 414 } 415 } 416 417 /** 418 * Print out a short summary report about any tests with too many test cases 419 * excluded. 420 */ 421 private void showBadTestCaseTests() { 422 if (badTestCaseTests != null) { 423 out.println(i18n.getString("adt.tooManyTestCases", new Integer(badTestCaseTests.length))); 424 for (int i = 0; i < badTestCaseTests.length; i++) { 425 TestResult tr = badTestCaseTests[i]; 426 out.println(tr.getWorkRelativePath()); 427 } 428 } 429 } 430 431 /** 432 * Print out a short summary report about any tests that gave problems 433 * during the analysis. 434 */ 435 private void showBadTests() { 436 if (badTests != null) { 437 out.println(i18n.getString("adt.badTests", new Integer(badTests.length))); 438 for (int i = 0; i < badTests.length; i++) { 439 TestDescription td = badTests[i]; 440 out.println(TestResult.getWorkRelativePath(td)); 441 } 442 } 443 } 444 445 /** 446 * Print out a short summary report about the earliest and latest 447 * start times for the test results. 448 */ 449 private void showDateStampInfo() { 450 if (earliestStart == null || latestStart == null) { 451 out.println(i18n.getString("adt.noDateStamps")); 452 } 453 else { 454 Integer b = new Integer(badDates ? 1 : 0); 455 out.println(i18n.getString("adt.earliestResult", 456 new Object[] {earliestStart, b})); 457 out.println(i18n.getString("adt.latestResult", 458 new Object[] {latestStart, b})); 459 if (badDates) 460 out.println(i18n.getString("adt.badDateStamps")); 461 } 462 } 463 464 /** 465 * Print out a short summary report of the environment statistics. 466 * @param out A stream to which to write the output. 467 */ 468 private void showEnvCounts() { 469 int u = envCounts[0]; 470 int m = envCounts[1]; 471 if (u + m > 0) { 472 if (m == 0) 473 out.println(i18n.getString("adt.env.allOK")); 474 else { 475 out.println(i18n.getString("adt.env.count", 476 new Object[] { 477 new Integer(u), 478 new Integer((u > 0 && m > 0) ? 1 : 0), 479 new Integer(m) 480 } )); 481 } 482 } 483 } 484 485 /** 486 * Print out a listing of some or all of the env values used by the tests. 487 * @param showAll show all environment values (uniquely and multiply defined.) 488 * The default is to just show the multiple defined values. 489 */ 490 private void showEnvValues(boolean showAll) { 491 out.println(); 492 out.print(i18n.getString("adt.envList.title")); 493 494 SortedSet<String> ss = new TreeSet<>(); 495 for (Enumeration<String> e = envTable.keys(); e.hasMoreElements(); ) { 496 String key = (e.nextElement()); 497 ss.add(key); 498 } 499 500 for (Iterator<String> iter = ss.iterator(); iter.hasNext(); ) { 501 String key = (iter.next()); 502 Vector<String> allValuesForKey = envTable.get(key); 503 if (allValuesForKey.size() == 1) { 504 if (showAll) 505 out.println(i18n.getString("adt.envKeyValue", 506 new Object[] {key, allValuesForKey.elementAt(0)})); 507 } 508 else { 509 out.println(i18n.getString("adt.envKey", key)); 510 for (int j = 0; j < allValuesForKey.size(); j++) { 511 out.println(i18n.getString("adt.envValue", 512 allValuesForKey.elementAt(j))); 513 } 514 } 515 } 516 } 517 518 /** 519 * Print out a short summary report of the checksum statistics. 520 */ 521 private void showChecksumCounts() { 522 if (testCount > 0) { 523 int g = checksumCounts[TestResult.GOOD_CHECKSUM]; 524 int b = checksumCounts[TestResult.BAD_CHECKSUM]; 525 int n = checksumCounts[TestResult.NO_CHECKSUM]; 526 if (b == 0 && n == 0) 527 out.println(i18n.getString("adt.cs.allOK")); 528 else 529 out.println(i18n.getString("adt.cs.count", 530 new Object[] { 531 new Integer(g), 532 new Integer((g > 0) && (b + n > 0) ? 1 : 0), 533 new Integer(b), 534 new Integer((b > 0) && (n > 0) ? 1 : 0), 535 new Integer(n) 536 })); 537 } 538 } 539 540 /** 541 * Print out a short summary report of the test result status statistics. 542 */ 543 private void showResultCounts() { 544 if (testCount == 0) 545 out.println(i18n.getString("adt.status.noTests")); 546 else { 547 int p = statusCounts[Status.PASSED]; 548 int f = statusCounts[Status.FAILED]; 549 int e = statusCounts[Status.ERROR]; 550 int nr = statusCounts[Status.NOT_RUN]; 551 552 if (p == testCount) 553 out.println(i18n.getString("adt.status.allOK")); 554 else 555 out.println(i18n.getString("adt.status.count", 556 new Object[] { 557 new Integer(p), 558 new Integer((p > 0) && (f + e + nr > 0) ? 1 : 0), 559 new Integer(f), 560 new Integer((f > 0) && (e + nr > 0) ? 1 : 0), 561 new Integer(e), 562 new Integer((e > 0) && (nr > 0) ? 1 : 0), 563 new Integer(nr) 564 })); 565 } 566 } 567 568 /** 569 * Print out a labelled count if non-zero 570 * @param needSep Need a leading separator (comma) 571 * @param label The label to display 572 * @param count The count to display, if non-zero 573 * @return true if a separator will be needed for the next value to be shown 574 */ 575 private boolean showCount(String msg, boolean needSep, int count) { 576 if (count == 0) 577 return needSep; 578 else { 579 out.print(i18n.getString(msg, 580 new Object[] {new Integer(needSep ? 1 : 0), new Integer(count)})); 581 return true; 582 } 583 } 584 585 private boolean checkTestCases(TestResult tr, ExcludeList excludeList) 586 throws TestResult.Fault { 587 // If no test cases excluded when test was run, then OK. 588 // (This is the typical case.) 589 String[] etcTest; 590 String etcTestProp = tr.getProperty("excludedTestCases"); 591 if (etcTestProp == null) 592 return true; 593 else 594 etcTest = StringArray.split(etcTestProp); 595 596 // This next test is probably redundant, assuming the excludeList is 597 // the same as that used to locate the tests, but check anyway: 598 // if test is now completely excluded, then OK. 599 if (excludeList.excludesAllOf(tr.getDescription())) 600 return true; 601 602 String[] etcTable = excludeList.getTestCases(tr.getDescription()); 603 // null indicates test not found in table, or the entire test is excluded 604 // (without specifying test cases.) Because of the previous check, the 605 // latter cannot be the case, so null means the test is not present 606 // in the exclude list. Since we also have checked that the test has 607 // excluded test cases that means we have a problem... 608 if (etcTable == null) 609 return false; 610 611 // now check that etcTest is a subset of etcTable 612 nextTestCase: 613 for (int i = 0; i < etcTest.length; i++) { 614 for (int j = 0; j < etcTable.length; j++) { 615 if (etcTest[i].equals(etcTable[j])) 616 continue nextTestCase; 617 } 618 // etcTest[i] was not found in etcTable; 619 // that means we have a problem 620 return false; 621 } 622 623 // if we're here, we found all the test cases that were actually 624 // excluded were all validly excluded, according to excludedTestCases. 625 return true; 626 } 627 628 private static boolean equal(TestDescription a, TestDescription b) { 629 if (a == null || b == null) 630 return (a == b); 631 632 //if (!a.rootRelativeFile.equals(b.rootRelativeFile)) 633 // return false; 634 635 Iterator<String> eA = a.getParameterKeys(); 636 Iterator<String> eB = b.getParameterKeys(); 637 while (eA.hasNext() && eB.hasNext()) { 638 String keyA = eA.next(); 639 String keyB = eB.next(); 640 if (!keyA.equals(keyB)) { 641 //System.err.println("mismatch " + a.getRootRelativePath() + " a:" + keyA + " b:" + keyB); 642 return false; 643 } 644 645 String valA = a.getParameter(keyA); 646 String valB = a.getParameter(keyB); 647 if (!(valA.equals(valB) || (keyA.equals("keywords") && keywordMatch(valA, valB)))) { 648 //System.err.println("mismatch " + a.getRootRelativePath() + " key:" + keyA + " a:" + valA + " b:" + valB); 649 return false; 650 } 651 } 652 653 return true; 654 } 655 656 private final static boolean keywordMatch(String a, String b) { 657 // eek, not very efficient! 658 String[] aa = StringArray.split(a); 659 Arrays.sort(aa); 660 String[] bb = StringArray.split(b); 661 Arrays.sort(bb); 662 return Arrays.equals(aa, bb); 663 } 664 665 private Date parseDate(String s) { 666 if (dateFormats == null) 667 initDateFormats(); 668 669 for (int i = 0; i < dateFormats.length; i++) { 670 try { 671 Date d = dateFormats[i].parse(s); 672 // successfully parsed the date; shuffle the format to the front 673 // to speed up future parses, assuming dates will likely be similar 674 if (i > 0) { 675 DateFormat tmp = dateFormats[i]; 676 System.arraycopy(dateFormats, 0, dateFormats, 1, i); 677 dateFormats[0] = tmp; 678 } 679 return d; 680 } 681 catch (ParseException e) { 682 //System.err.println("pattern: " + ((SimpleDateFormat)dateFormats[i]).toPattern()); 683 //System.err.println(" value: " + s); 684 //System.err.println("example: " + dateFormats[i].format(new Date())); 685 } 686 } 687 return null; 688 } 689 690 private void initDateFormats() { 691 // Create an array of possible date formats to parse dates in .jtr files. 692 // Most likely is Unix C time in English; the array will be reordered in use 693 // by moving the recently used entries to the front of the array. 694 Vector<DateFormat> v = new Vector<>(); 695 696 // generic Java default 697 // 10-Sep-99 3:25:11 PM 698 v.addElement(DateFormat.getDateTimeInstance()); 699 v.addElement(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, 700 DateFormat.DEFAULT, 701 Locale.ENGLISH)); 702 703 // standard IETF date syntax 704 // Fri, 10 September 1999 03:25:12 PDT 705 v.addElement(new SimpleDateFormat("EEE, dd MMMM yyyy HH:mm:ss zzz")); 706 v.addElement(new SimpleDateFormat("EEE, dd MMMM yyyy HH:mm:ss zzz", Locale.ENGLISH)); 707 708 // Unix C time 709 // Fri Sep 10 14:41:37 PDT 1999 710 v.addElement(new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy")); 711 v.addElement(new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.ENGLISH)); 712 713 // allow user-specified format 714 String s = System.getProperty("javatest.date.format"); 715 if (s != null) 716 v.addElement(new SimpleDateFormat(s)); 717 718 dateFormats = new DateFormat[v.size()]; 719 v.copyInto(dateFormats); 720 } 721 722 private int testCount; 723 private int[] checksumCounts = new int[TestResult.NUM_CHECKSUM_STATES]; 724 private int[] envCounts = new int[2]; 725 private int[] statusCounts = new int[5]; 726 private boolean badDates = false; 727 private TestDescription[] badTests; 728 private TestResult[] badTestCaseTests; 729 private TestResult[] badTestDescriptions; 730 private TestResult[] badChecksumTests; 731 private Date earliestStart; 732 private Date latestStart; 733 private DateFormat[] dateFormats; 734 735 private Hashtable<String, Vector<String>> envTable = new Hashtable<>(); 736 private PrintStream out; 737 private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(Audit.class); 738 }