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 }