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