1 /* 2 * $Id$ 3 * 4 * Copyright (c) 1996, 2016, 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.*; 30 import java.lang.ref.WeakReference; 31 import java.nio.charset.StandardCharsets; 32 import java.text.DateFormat; 33 import java.text.ParseException; 34 import java.text.SimpleDateFormat; 35 import java.util.*; 36 37 import com.sun.javatest.util.*; 38 import com.sun.javatest.util.Properties; 39 40 /** 41 * The TestResult object encapsulates the results from a test. 42 * Test results are formatted in sections of command output, 43 * comments and sometimes "streams" of output (<tt>stdout</tt> for example). 44 * Each of these sections is represented by a (@link TestResult.Section Section). 45 * Instances of this class are mutable until the result of the section is 46 * set or until the result of the test itself is set. 47 * 48 * Test results are stored in a structured text files. 49 * The TestResult class serves as the API for accessing the various 50 * components that make up the result of running a test. 51 * The status is cached as its size is small and it is accessed often. 52 * 53 * This class and inner classes will throw IllegalStateExceptions if an 54 * attempt is made to modify the any part of the object that has been 55 * marked immutable. 56 */ 57 58 public class TestResult { 59 /** 60 * This exception is to report problems using TestResult objects. 61 */ 62 public static class Fault extends Exception 63 { 64 Fault(I18NResourceBundle i18n, String key) { 65 super(i18n.getString(key)); 66 } 67 68 Fault(I18NResourceBundle i18n, String key, Object arg) { 69 super(i18n.getString(key, arg)); 70 } 71 72 Fault(I18NResourceBundle i18n, String key, Object[] args) { 73 super(i18n.getString(key, args)); 74 } 75 } 76 77 /** 78 * This exception is thrown if the JTR file cannot be found. 79 */ 80 public static class ResultFileNotFoundFault extends Fault { 81 ResultFileNotFoundFault(I18NResourceBundle i18n, String key) { 82 super(i18n, key); 83 } 84 85 ResultFileNotFoundFault(I18NResourceBundle i18n, String key, Object arg) { 86 super(i18n, key, arg); 87 } 88 89 ResultFileNotFoundFault(I18NResourceBundle i18n, String key, Object[] args) { 90 super(i18n, key, args); 91 } 92 } 93 94 /** 95 * This exception ay occur anytime the JTR file is being read from the filesystem. 96 * To optimize memory usage, the contents of a TestResult object are sometimes 97 * discarded and then loaded on demand from the JTR file. If a fault occurs 98 * when reading the JTR file, this fault may occur. 99 * 100 * @see TestResult.ResultFileNotFoundFault 101 */ 102 public static class ReloadFault extends Fault { 103 ReloadFault(I18NResourceBundle i18n, String key) { 104 super(i18n, key); 105 } 106 107 ReloadFault(I18NResourceBundle i18n, String key, Object arg) { 108 super(i18n, key, arg); 109 } 110 111 ReloadFault(I18NResourceBundle i18n, String key, Object[] args) { 112 super(i18n, key, args); 113 } 114 } 115 116 /** 117 * An interface to observe activity in a TestResult as it is created. 118 */ 119 public interface Observer { 120 /** 121 * A new section has been created in the test result. 122 * 123 * @param tr The test result in which the section was created. 124 * @param section The section that has been created 125 */ 126 public void createdSection(TestResult tr, Section section); 127 128 /** 129 * A section has been been completed in the test result. 130 * 131 * @param tr The test result containing the section. 132 * @param section The section that has been completed. 133 */ 134 public void completedSection(TestResult tr, Section section); 135 136 /** 137 * New output has been created in a section of the test result. 138 * 139 * @param tr The test result containing the output. 140 * @param section The section in which the output has been created. 141 * @param outputName The name of the output. 142 */ 143 public void createdOutput(TestResult tr, Section section, String outputName); 144 145 /** 146 * Output has been completed in a section of the test result. 147 * 148 * @param tr The test result containing the output. 149 * @param section The section in which the output has been completed. 150 * @param outputName The name of the output. 151 */ 152 public void completedOutput(TestResult tr, Section section, String outputName); 153 154 /** 155 * The output for a section has been updated. 156 * 157 * @param tr The test result object being modified. 158 * @param section The section in which the output is being produced. 159 * @param outputName The name of the output. 160 * @param start the start offset of the text that was changed 161 * @param end the end offset of the text that was changed 162 * @param text the text that replaced the specified range. 163 */ 164 public void updatedOutput(TestResult tr, Section section, String outputName, int start, int end, String text); 165 166 /** 167 * A property of the test result has been updated. 168 * 169 * @param tr The test result containing the property that was modified. 170 * @param name The key for the property that was modified. 171 * @param value The new value for the property. 172 * 173 */ 174 public void updatedProperty(TestResult tr, String name, String value); 175 176 /** 177 * The test has completed, and the results are now immutable. 178 * There will be no further observer calls. 179 * @param tr The test result that has been completed. 180 */ 181 public void completed(TestResult tr); 182 183 } 184 185 /** 186 * This "section" is the logical combination of a single action during test 187 * execution. It is designed to hold multiple (or none) buffers of 188 * output from test execution, such as stdout and stderr. In addition, 189 * it has a "comment" field for tracking the test run itself (progress). 190 * This output is identified by the MSG_SECTION_NAME identifier. 191 */ 192 public class Section { 193 /** 194 * Query if the section is still writable or not. 195 * @return true if the section is still writable, and false otherwise 196 */ 197 public boolean isMutable() { 198 synchronized (TestResult.this) { 199 synchronized (this) { 200 return (TestResult.this.isMutable() && 201 this.result == inProgress); 202 } 203 } 204 } 205 206 /** 207 * Find out what the result of the execution of this section was. 208 * @return the result of the execution of this section 209 * @see #setStatus 210 */ 211 public Status getStatus() { 212 return result; 213 } 214 215 /** 216 * Set the result of this section. This action makes this section 217 * immutable. 218 * 219 * @param result The status to set as the result of this section of the test 220 * @see #getStatus 221 */ 222 public void setStatus(Status result) { 223 synchronized (TestResult.this) { 224 synchronized (this) { 225 checkMutable(); 226 for (int i = 0; i < buffers.length; i++) { 227 OutputBuffer b = buffers[i]; 228 if (b instanceof WritableOutputBuffer) { 229 WritableOutputBuffer wb = (WritableOutputBuffer)b; 230 wb.getPrintWriter().close(); 231 } 232 } 233 if (env == null) 234 env = emptyStringArray; 235 this.result = result; 236 if (env == null) 237 env = emptyStringArray; 238 notifyCompletedSection(this); 239 } 240 } 241 } 242 243 /** 244 * Get the title of this section, specified when the section 245 * was created. 246 * @return the title of this section 247 */ 248 public String getTitle() { 249 return title; 250 } 251 252 /** 253 * Get the appropriate to writer to access the default message field. 254 * @return a Writer to access the default message field 255 */ 256 public PrintWriter getMessageWriter() { 257 synchronized (TestResult.this) { 258 synchronized (this) { 259 checkMutable(); 260 // if it is mutable, it must have a message stream, 261 // which will be the first entry 262 return buffers[0].getPrintWriter(); 263 } 264 } 265 } 266 267 /** 268 * Find out how many output buffers this section has inside it. 269 * 270 * @return The number of output buffers in use (>=0). 271 */ 272 public synchronized int getOutputCount() { 273 return buffers.length; 274 } 275 276 /** 277 * Add a new output buffer to the section; get PrintWriter access to it. 278 * 279 * @param name The symbolic name that will identify this new stream. 280 * @return A PrintWriter that gives access to the new stream. 281 */ 282 public PrintWriter createOutput(String name) { 283 if (name == null) 284 throw new NullPointerException(); 285 286 synchronized (TestResult.this) { 287 synchronized (this) { 288 checkMutable(); 289 290 OutputBuffer b = new WritableOutputBuffer(name); 291 buffers = DynamicArray.append(buffers, b); 292 293 notifyCreatedOutput(this, name); 294 295 return b.getPrintWriter(); 296 } 297 } 298 } 299 300 /** 301 * Get the content that was written to a specified output stream. 302 * @param name the name of the stream in question 303 * @return All the data that was written to the specified output, 304 * or null if nothing has been written. 305 */ 306 public String getOutput(String name) { 307 if (name == null) 308 throw new NullPointerException(); 309 310 synchronized (TestResult.this) { 311 synchronized (this) { 312 OutputBuffer b = findOutputBuffer(name); 313 return (b == null ? null : b.getOutput()); 314 } 315 } 316 } 317 318 /** 319 * Find out the symbolic names of all the streams in this section. You 320 * can use getOutputCount to discover the number of items in 321 * this enumeration (not a thread safe activity in the strictest 322 * sense of course). 323 * 324 * @return A list of strings which are the symbolic names of the streams in this section. 325 * @see #getOutputCount 326 */ 327 public synchronized String[] getOutputNames() { 328 String[] names = new String[buffers.length]; 329 330 for (int i = 0; i < buffers.length; i++) { 331 names[i] = buffers[i].getName(); 332 if (names[i] == null) 333 throw new IllegalStateException("BUFFER IS BROKEN"); 334 } 335 336 return names; 337 } 338 339 /** 340 * Removes any data added to the named output up to this point, resetting 341 * it to an empty state. 342 * @param name The output name to erase the content of. 343 * @since 4.2.1 344 */ 345 public synchronized void deleteOutputData(String name) { 346 if (name == null) 347 throw new NullPointerException(); 348 349 synchronized (TestResult.this) { 350 synchronized (this) { 351 OutputBuffer b = findOutputBuffer(name); 352 if (b != null && b instanceof WritableOutputBuffer) 353 ((WritableOutputBuffer)b).deleteAllOutput(); 354 } 355 } 356 } 357 358 // ---------- PACKAGE PRIVATE ---------- 359 360 public Section(String title) { 361 if (title == null) 362 throw new NullPointerException(); 363 if (title.indexOf(' ') != -1) 364 throw new IllegalArgumentException("space invalid in section title"); 365 366 this.title = title; 367 result = inProgress; 368 } 369 370 /** 371 * Could be used to reconstruct the section from a stream. 372 * Reads from the source until it finds a section header. This is a JTR 373 * version 2 method, don't use it for version 1 files. The object 374 * immediately immutable upon return from this constructor. 375 * 376 * @throws ReloadFault Probably an error while parsing the input stream. 377 */ 378 Section(BufferedReader in) throws IOException, ReloadFault { 379 String line = in.readLine(); 380 // find top of section and process it 381 while (line != null) { 382 if (line.startsWith(JTR_V2_SECTION)) { 383 title = extractSlice(line, 0, ":", null); 384 break; 385 } 386 else 387 // don't know what this line is, may be empty 388 line = in.readLine(); 389 } 390 391 if (title == null) 392 throw new ReloadFault(i18n, "rslt.noSectionTitle"); 393 394 if (title.equals(MSG_SECTION_NAME)) { 395 // use standard internal copy of string 396 title = MSG_SECTION_NAME; 397 } 398 399 while ((line = in.readLine()).startsWith(JTR_V2_SECTSTREAM)) { 400 OutputBuffer b = new FixedOutputBuffer(line, in); 401 buffers = DynamicArray.append(buffers, b); 402 } 403 404 // if not in the message section, line should have the section result 405 if (title != MSG_SECTION_NAME) { 406 if (line != null) { 407 if (line.startsWith(JTR_V2_SECTRESULT)) 408 result = Status.parse(line.substring(JTR_V2_SECTRESULT.length())); 409 else 410 throw new ReloadFault(i18n, "rslt.badLine", line); 411 } 412 if (result == null) 413 // no test result 414 throw new ReloadFault(i18n, "rslt.noSectionResult"); 415 } 416 } 417 418 void save(Writer out) throws IOException { 419 out.write(JTR_V2_SECTION + getTitle()); 420 out.write(lineSeparator); 421 422 for (int index = 0; index < buffers.length; index++) { 423 String text = buffers[index].getOutput(); 424 int numLines = 0; 425 int numBackslashes = 0; 426 int numNonASCII = 0; 427 boolean needsFinalNewline = false; 428 boolean needsEscape; 429 430 // scan for newlines and characters requiring escapes 431 for (int i = 0; i < text.length(); i++) { 432 char c = text.charAt(i); 433 if (c < 32) { 434 if (c == '\n') 435 numLines++; 436 else if (c != '\t' && c != '\r') 437 numNonASCII++; 438 } 439 else if (c < 127) { 440 if (c == '\\') 441 numBackslashes++; 442 } 443 else 444 numNonASCII++; 445 } 446 447 needsEscape = (numBackslashes > 0 || numNonASCII > 0); 448 449 // Check the text ends with a final newline ('\n', not line.separator) 450 // Note this must match the check when reading the text back in, 451 // when we also check for just '\n' and not line.separator, because 452 // line.separator now, and line.separator then, might be different. 453 if (text.length() != 0 && !text.endsWith("\n")) { 454 needsFinalNewline = true; 455 numLines++; 456 } 457 458 out.write(JTR_V2_SECTSTREAM); 459 out.write(buffers[index].getName()); 460 out.write(":"); 461 out.write('('); 462 out.write(String.valueOf(numLines)); 463 out.write('/'); 464 if (needsEscape) { 465 // count one per character, plus an additional one per \ (written as "\ \") and an 466 // additional 5 per nonASCII (written as "\ u x x x x") 467 out.write(String.valueOf(text.length() + numBackslashes + 5*numNonASCII)); 468 } 469 else 470 out.write(String.valueOf(text.length())); 471 out.write(')'); 472 if (needsEscape) 473 out.write('*'); 474 out.write(JTR_V2_SECTSTREAM); 475 out.write(lineSeparator); 476 477 if (needsEscape) { 478 for (int i = 0; i < text.length(); i++) { 479 char c = text.charAt(i); 480 if (32 <= c && c < 127 && c != '\\') 481 out.write(c); 482 else { 483 switch (c) { 484 case '\n': case '\r': case '\t': 485 out.write(c); 486 break; 487 case '\\': 488 out.write("\\\\"); 489 break; 490 default: 491 out.write("\\u"); 492 out.write(Character.forDigit((c >> 12) & 0xF, 16)); 493 out.write(Character.forDigit((c >> 8) & 0xF, 16)); 494 out.write(Character.forDigit((c >> 4) & 0xF, 16)); 495 out.write(Character.forDigit((c >> 0) & 0xF, 16)); 496 break; 497 } 498 } 499 } 500 } 501 else 502 out.write(text); 503 504 if (needsFinalNewline) 505 out.write(lineSeparator); 506 } 507 508 // the default message section does not need a result line 509 if (getTitle() != MSG_SECTION_NAME) { 510 out.write(JTR_V2_SECTRESULT + result.toString()); 511 out.write(lineSeparator); 512 } 513 514 out.write(lineSeparator); 515 } 516 517 /** 518 * Reload an output block. This method is called while reloading 519 * a test result and so bypasses the normal immutability checks. 520 */ 521 synchronized void reloadOutput(String name, String data) { 522 if (name.equals(MESSAGE_OUTPUT_NAME)) 523 name = MESSAGE_OUTPUT_NAME; 524 OutputBuffer b = new FixedOutputBuffer(name, data); 525 buffers = DynamicArray.append(buffers, b); 526 } 527 528 /** 529 * Reload the status. This method is called while reloading 530 * a test result and so bypasses the normal immutability checks. 531 */ 532 synchronized void reloadStatus(Status s) { 533 result = s; 534 } 535 536 // ---------- PRIVATE ---------- 537 538 private void checkMutable() { 539 if (!isMutable()) 540 throw new IllegalStateException("This section of the test result is now immutable."); 541 } 542 543 private synchronized void makeOutputImmutable(OutputBuffer b, String name, String output) { 544 for (int i = 0; i < buffers.length; i++) { 545 if (buffers[i] == b) { 546 buffers[i] = new FixedOutputBuffer(name, output); 547 return; 548 } 549 } 550 } 551 552 private synchronized OutputBuffer findOutputBuffer(String name) { 553 // search backwards 554 // may help in some backward compatibility cases since the most 555 // recent stream with that name will be found 556 // performance of the search will still be constant 557 for (int i = buffers.length-1; i >= 0 ; i--) { 558 if (name.equals(buffers[i].getName())) 559 return buffers[i]; 560 } 561 562 return null; 563 } 564 565 private OutputBuffer[] buffers = new OutputBuffer[0]; 566 private String title; 567 private Status result; 568 569 private class FixedOutputBuffer implements OutputBuffer { 570 FixedOutputBuffer(String name, String output) { 571 if (name == null || output == null) 572 throw new NullPointerException(); 573 574 this.name = name; 575 this.output = output; 576 } 577 578 public String getName() { 579 return name; 580 } 581 582 public String getOutput() { 583 return output; 584 } 585 586 public PrintWriter getPrintWriter() { 587 throw new IllegalStateException("This section is immutable"); 588 } 589 590 FixedOutputBuffer(String header, BufferedReader in) throws ReloadFault { 591 String nm = extractSlice(header, JTR_V2_SECTSTREAM.length(), null, ":"); 592 if (nm == null) 593 throw new ReloadFault(i18n, "rslt.noOutputTitle"); 594 595 if (nm.equals(MESSAGE_OUTPUT_NAME )) 596 nm = MESSAGE_OUTPUT_NAME; 597 598 try { 599 int lines; 600 int chars; 601 boolean needsEscape; 602 603 try { 604 int start = JTR_V2_SECTSTREAM.length(); 605 lines = Integer.parseInt(extractSlice(header, start, "(", "/")); 606 chars = Integer.parseInt(extractSlice(header, start, "/", ")")); 607 int rp = header.indexOf(")", start); 608 if (rp >= 0 && rp < header.length() - 2) 609 needsEscape = (header.charAt(rp + 1) == '*'); 610 else 611 needsEscape = false; 612 } 613 catch (NumberFormatException e) { 614 // fatal parsing error 615 throw new ReloadFault(i18n, "rslt.badHeaderVersion", e); 616 } 617 618 StringBuffer buff = new StringBuffer(chars); 619 620 if (needsEscape) { 621 for (int i = 0; i < chars; i++) { 622 int c = in.read(); 623 if (c == -1) 624 throw new ReloadFault(i18n, "rslt.badEOF"); 625 else if (c == '\\') { 626 c = in.read(); 627 i++; 628 if (c == 'u') { 629 c = Character.digit((char)in.read(), 16) << 12; 630 c += Character.digit((char)in.read(), 16) << 8; 631 c += Character.digit((char)in.read(), 16) << 4; 632 c += Character.digit((char)in.read(), 16); 633 i += 4; 634 } 635 // else drop through (for \\) 636 } 637 buff.append((char)c); 638 } 639 } 640 else { 641 char[] data = new char[Math.min(4096, chars)]; 642 int charsRead = 0; 643 while (charsRead < chars) { 644 int n = in.read(data, 0, Math.min(data.length, chars-charsRead)); 645 646 // sanity check, may be truncated file 647 if (n < 0) { 648 throw new ReloadFault(i18n, "rslt.badRuntimeErr", new Object[] 649 {resultsFile, Integer.toString(n)}); 650 } 651 652 buff.append(data, 0, n); 653 charsRead += n; 654 } 655 } 656 657 /*NEW 658 while (true) { 659 int c = in.read(); 660 switch (c) { 661 case -1: 662 throw new ReloadFault(i18n, "rslt.badEOF"); 663 664 case '\\': 665 if (needEscape) { 666 c = in.read(); 667 if (c == 'u') { 668 c = Character.digit((char)in.read(), 16) << 12; 669 c += Character.digit((char)in.read(), 16) << 8; 670 c += Character.digit((char)in.read(), 16) << 4; 671 c += Character.digit((char)in.read(), 16); 672 } 673 // else drop through (for \\) 674 } 675 buff.append((char)c); 676 } 677 } 678 */ 679 680 name = nm; 681 output = buff.toString(); 682 683 if (buff.length() > 0 && buff.charAt(buff.length() - 1) != '\n') { 684 int c = in.read(); 685 if (c == '\r') 686 c = in.read(); 687 if (c != '\n') { 688 System.err.println("TR.badChars: output=" + (output.length() < 32 ? output : output.substring(0, 9) + " ... " + output.substring(output.length() - 10) )); 689 System.err.println("TR.badChars: '" + ((char)c) + "' (" + c + ")"); 690 throw new ReloadFault(i18n, "rslt.badChars", name); 691 } 692 } 693 } 694 catch (IOException e) { 695 // not enough data probably fatal parsing error 696 throw new ReloadFault(i18n, "rslt.badFile", e); 697 } 698 } 699 700 private final String name; 701 private final String output; 702 } 703 704 private class WritableOutputBuffer extends Writer implements OutputBuffer { 705 WritableOutputBuffer(String name) { 706 super(TestResult.this); 707 if (name == null) 708 throw new NullPointerException(); 709 710 this.name = name; 711 output = new StringBuffer(); 712 pw = new LockedWriter(this, TestResult.this); 713 } 714 715 public String getName() { 716 return name; 717 } 718 719 public String getOutput() { 720 return new String(output); 721 } 722 723 public PrintWriter getPrintWriter() { 724 return pw; 725 } 726 727 public void write(char[] buf, int offset, int len) throws IOException { 728 if (output == null) 729 throw new IOException("stream has been closed"); 730 731 int end = output.length(); 732 output.append(buf, offset, len); 733 // want to avoid creating the string buf(offset..len) 734 // since likely case is no observers 735 notifyUpdatedOutput(Section.this, name, end, end, buf, offset, len); 736 737 int maxOutputSize = maxTROutputSize > 0 ? maxTROutputSize : commonOutputSize; 738 if (output.length() > maxOutputSize) { 739 int overflowEnd = maxOutputSize/3; 740 if (overflowed) { 741 // output.delete(overflowStart, overflowEnd); 742 // JDK 1.1--start 743 String s = output.toString(); 744 output = new StringBuffer(s.substring(0, overflowStart) + s.substring(output.length()-overflowEnd)); 745 // JDK 1.1--end 746 notifyUpdatedOutput(Section.this, name, overflowStart, overflowEnd, ""); 747 } 748 else { 749 String OVERFLOW_MESSAGE = 750 "\n\n...\n" 751 + "Output overflow:\n" 752 + "JT Harness has limited the test output to the text to that\n" 753 + "at the beginning and the end, so that you can see how the\n" 754 + "test began, and how it completed.\n" 755 + "\n" 756 + "If you need to see more of the output from the test,\n" 757 + "set the system property javatest.maxOutputSize to a higher\n" 758 + "value. The current value is " + maxOutputSize 759 + "\n...\n\n"; 760 overflowStart = maxOutputSize/3; 761 //output.replace(overflowStart, maxOutputSize*2/3, OVERFLOW_MESSAGE); 762 // JDK 1.1--start 763 String s = output.toString(); 764 output = new StringBuffer(s.substring(0, overflowStart) + OVERFLOW_MESSAGE + s.substring(overflowEnd)); 765 // JDK 1.1--end 766 notifyUpdatedOutput(Section.this, name, overflowStart, overflowEnd, OVERFLOW_MESSAGE); 767 overflowStart += OVERFLOW_MESSAGE.length(); 768 overflowed = true; 769 } 770 } 771 } 772 773 public void flush() { 774 //no-op 775 } 776 777 public void deleteAllOutput() { 778 pw.flush(); 779 output.setLength(0); 780 overflowStart = -1; 781 overflowed = false; 782 } 783 784 public void close() { 785 makeOutputImmutable(this, name, new String(output)); 786 notifyCompletedOutput(Section.this, name); 787 } 788 789 private boolean overflowed; 790 private int overflowStart; 791 private final String name; 792 private /*final*/ StringBuffer output; // can't easily be final in JDK 1.1 because need to reassign to it 793 private final PrintWriter pw; 794 } 795 } 796 797 private class LockedWriter extends PrintWriter { 798 public LockedWriter(Writer out, Object theLock) { 799 super(out); 800 lock = theLock; 801 } 802 } 803 804 // Conceptually, this belongs in Section, but that is not legal Java. 805 // (It is accepted in 1.1.x; rejected by 1.2) 806 private interface OutputBuffer { 807 String getName(); 808 String getOutput(); 809 PrintWriter getPrintWriter(); 810 } 811 812 813 814 815 // ------------------------- PUBLIC CONSTRUCTORS ------------------------- 816 /** 817 * Construct a test result object that will be built as the test runs. 818 * The status string will be "running..." rather than "not run". 819 * 820 * @param td The test description to base this new object on. Cannot be 821 * null. 822 */ 823 public TestResult(TestDescription td) { 824 desc = td; 825 execStatus = inProgress; 826 testURL = desc.getRootRelativeURL(); 827 828 createSection(MSG_SECTION_NAME); 829 830 props = emptyStringArray; // null implies it was discarded, not empty 831 } 832 833 834 /** 835 * Reconstruct the results of a previously run test. 836 * 837 * @param workDir Work directory in which the tests were run 838 * @param td Description of the test that was run 839 * @throws TestResult.Fault if there is a problem recreating the results 840 * from the appropriate file in the work directory 841 */ 842 public TestResult(TestDescription td, WorkDirectory workDir) 843 throws Fault { 844 desc = td; 845 testURL = desc.getRootRelativeURL(); 846 execStatus = inProgress; 847 848 reloadFromWorkDir(workDir); 849 } 850 851 /** 852 * Reconstruct the results of a previously run test. 853 * 854 * @param file File that the results have been stored into. 855 * @throws TestResult.ReloadFault if there is a problem recreating the results 856 * from the given file 857 * @throws TestResult.ResultFileNotFoundFault if there is a problem locating 858 * the given file 859 */ 860 public TestResult(File file) 861 throws ResultFileNotFoundFault, ReloadFault 862 { 863 resultsFile = file; 864 reload(); 865 866 testURL = desc.getRootRelativeURL(); 867 868 execStatus = Status.parse(PropertyArray.get(props, EXEC_STATUS)); 869 } 870 871 /** 872 * Reconstruct the results of a previously run test. 873 * 874 * @param workDir The work directory where previous results for the guven 875 * test can be found. 876 * @param workRelativePath The path to the JTR to reload, relative to the 877 * workdir. 878 * @throws TestResult.Fault if there is a problem recreating the results 879 * from the given file 880 */ 881 public TestResult(WorkDirectory workDir, String workRelativePath) throws Fault { 882 //resultsFile = workDir.getFile(workRelativePath.replace('/', File.separatorChar)); 883 resultsFile = workDir.getFile(workRelativePath); 884 reload(); 885 886 testURL = desc.getRootRelativeURL(); 887 execStatus = Status.parse(PropertyArray.get(props, EXEC_STATUS)); 888 } 889 890 /** 891 * Create a temporary test result for which can be handed around 892 * in situations where a reasonable test result can't be created. 893 * 894 * @param td Description of the test 895 * @param s Status to associate with running the test... presumed 896 * to be of the Status.FAILED type. 897 */ 898 public TestResult(TestDescription td, Status s) { 899 desc = td; 900 testURL = desc.getRootRelativeURL(); 901 resultsFile = null; 902 execStatus = s; 903 props = emptyStringArray; 904 } 905 906 /** 907 * Create a placeholder TestResult for a test that has not yet been run. 908 * 909 * @param td The test description for the test 910 * @return A test result that indicates that the test has not yet been run 911 */ 912 public static TestResult notRun(TestDescription td) { 913 return new TestResult(td, notRunStatus); 914 } 915 916 //------------------------ MODIFIER METHODS ------------------------------ 917 918 /** 919 * Create a new section inside this test result. 920 * 921 * @param name The symbolic name for this new section. 922 * @return The new section that was created. 923 */ 924 public synchronized TestResult.Section createSection(String name) { 925 if (!isMutable()) { 926 throw new IllegalStateException( 927 "This TestResult is no longer mutable!"); 928 } 929 930 Section section = new Section(name); 931 sections = DynamicArray.append(sections, section); 932 notifyCreatedSection(section); 933 // avoid creating output (which will cause observer messages) 934 // before the createdSection has been notified 935 section.createOutput(TestResult.MESSAGE_OUTPUT_NAME); 936 937 return section; 938 } 939 940 941 /** 942 * Set the environment used by this test. When the test is run, 943 * those entries in the environment that are referenced are noted; 944 * those entries will be recorded here in the test result object. 945 * @param environment the test environment used by this test. 946 * @see #getEnvironment 947 */ 948 public synchronized void setEnvironment(TestEnvironment environment) { 949 if (!isMutable()) { 950 throw new IllegalStateException( 951 "This TestResult is no longer mutable!"); 952 } 953 for (Iterator i = environment.elementsUsed().iterator(); i.hasNext(); ) { 954 TestEnvironment.Element elem = (TestEnvironment.Element) (i.next()); 955 // this is stunningly inefficient and should be fixed 956 env = PropertyArray.put(env, elem.getKey(), elem.getValue()); 957 } 958 } 959 960 /** 961 * Set the result of this test. This action makes this object immutable. 962 * If a result comparison is needed, it will be done in here. 963 * @param stat A status object representing the outcome of the test 964 * @see #getStatus 965 */ 966 public synchronized void setStatus(Status stat) { 967 if (!isMutable()) { 968 throw new IllegalStateException( 969 "This TestResult is no longer mutable!"); 970 } 971 972 if (stat == null) { 973 throw new IllegalArgumentException( 974 "TestResult status cannot be set to null!"); 975 } 976 977 // close out message section 978 sections[0].setStatus(null); 979 980 execStatus = stat; 981 982 if (execStatus == inProgress) 983 execStatus = interrupted; 984 985 // verify integrity of status in all sections 986 for (int i = 0; i < sections.length; i++) { 987 if (sections[i].isMutable()) { 988 sections[i].setStatus(incomplete); 989 } 990 } 991 992 props = PropertyArray.put(props, SECTIONS, 993 StringArray.join(getSectionTitles())); 994 props = PropertyArray.put(props, EXEC_STATUS, 995 execStatus.toString()); 996 997 // end time now required 998 // mainly for writing in the TRC for the Last Run Filter 999 if (PropertyArray.get(props, END) == null) { 1000 props = PropertyArray.put(props, END, formatDate(new Date())); 1001 } 1002 1003 // this object is now immutable 1004 notifyCompleted(); 1005 } 1006 1007 /** 1008 * Add a new property value to this TestResult. 1009 * 1010 * @param name The name of the property to be updated. 1011 * @param value The new value of the specified property. 1012 */ 1013 public synchronized void putProperty(String name, String value) { 1014 // check mutability 1015 if (!isMutable()) { 1016 throw new IllegalStateException( 1017 "Cannot put property, the TestResult is no longer mutable!"); 1018 } 1019 1020 props = PropertyArray.put(props, name, value); 1021 notifyUpdatedProperty(name, value); 1022 } 1023 1024 /** 1025 * Sets the maximum output size for the current TestResult. 1026 * The value will be used instead of the value specified 1027 * by the system property javatest.maxOutputSize. 1028 * @param size the maximum number of characters. 1029 */ 1030 public synchronized void setMaxOutputSize(int size){ 1031 if (!isMutable()) { 1032 throw new IllegalStateException( 1033 "This TestResult is no longer mutable!"); 1034 } 1035 maxTROutputSize = size; 1036 } 1037 1038 /** 1039 * Reconstruct the results of a previously run test. 1040 * 1041 * @param workDir Work directory in which the tests were run 1042 * @throws TestResult.Fault if an error occurs while reloading the results 1043 */ 1044 public void reloadFromWorkDir(WorkDirectory workDir) throws Fault { 1045 // check mutability 1046 if (!isMutable()) { 1047 throw new IllegalStateException( 1048 "Cannot reload results, the TestResult is no longer mutable!"); 1049 } 1050 1051 1052 try { 1053 resultsFile = workDir.getFile(getWorkRelativePath()); 1054 props = null; 1055 sections = null; 1056 execStatus = null; 1057 1058 reload(new InputStreamReader(new FileInputStream(resultsFile), StandardCharsets.UTF_8)); 1059 1060 // this next line is dubious since the execStatus should have 1061 // been set during the reload 1062 execStatus = Status.parse(PropertyArray.get(props, EXEC_STATUS)); 1063 } 1064 catch (FileNotFoundException e) { 1065 props = emptyStringArray; 1066 env = emptyStringArray; 1067 sections = emptySectionArray; 1068 execStatus = Status.notRun("no test result file found"); 1069 } 1070 catch (IOException e) { 1071 props = emptyStringArray; 1072 env = emptyStringArray; 1073 sections = emptySectionArray; 1074 execStatus = Status.error("error opening result file: " + e); 1075 throw new Fault(i18n, "rslt.badFile", e.toString()); 1076 } 1077 catch (Fault f) { 1078 props = emptyStringArray; 1079 env = emptyStringArray; 1080 sections = emptySectionArray; 1081 execStatus = Status.error(f.getMessage()); 1082 throw f; 1083 } 1084 1085 } 1086 1087 //----------ACCESS FUNCTIONS (MISC)----------------------------------------- 1088 1089 1090 /** 1091 * A code indicating that no checksum was found in a .jtr file. 1092 * @see #getChecksumState 1093 */ 1094 public static final int NO_CHECKSUM = 0; 1095 1096 /** 1097 * A code indicating that an invalid checksum was found in a .jtr file. 1098 * @see #getChecksumState 1099 */ 1100 public static final int BAD_CHECKSUM = 1; 1101 1102 /** 1103 * A code indicating that a good checksum was found in a .jtr file. 1104 * @see #getChecksumState 1105 */ 1106 public static final int GOOD_CHECKSUM = 2; 1107 1108 /** 1109 * The number of different checksum states (none, good, bad). 1110 */ 1111 public static final int NUM_CHECKSUM_STATES = 3; 1112 1113 1114 /** 1115 * Get info about the checksum in this object. 1116 * @return a value indicating the validity or otherwise of the checksum 1117 * found while reading this result object. 1118 * @see #NO_CHECKSUM 1119 * @see #BAD_CHECKSUM 1120 * @see #GOOD_CHECKSUM 1121 */ 1122 public byte getChecksumState() { 1123 return checksumState; 1124 } 1125 1126 /** 1127 * A way to write comments about the test execution into the results. 1128 * 1129 * @return If this is null, then the object is in a state in which it 1130 * does not accept new messages. 1131 */ 1132 public PrintWriter getTestCommentWriter() { 1133 return sections[0].getMessageWriter(); 1134 } 1135 1136 /** 1137 * Get the test name, as given by the test URL defined by 1138 * TestDescription.getRootRelativeURL(). This method <em>always</em> 1139 * returns a useful string, representing the test name. 1140 * 1141 * @return the name of the test for which this is the result object 1142 * @see TestDescription#getRootRelativeURL 1143 */ 1144 public String getTestName() { 1145 return testURL; 1146 } 1147 1148 /** 1149 * Check whether this test result can be reloaded from a file. 1150 * This method does not validate the contents of the file. 1151 * @return true if the result file for this object can be read 1152 */ 1153 public boolean isReloadable() { 1154 return (resultsFile != null && resultsFile.canRead()); 1155 } 1156 1157 /** 1158 * Check whether this object has been "shrunk" to reduce its 1159 * memory footprint. If it has, some or all of the data will have 1160 * to be reloaded. This method is somewhat 1161 * orthogonal to <code>isReloadable()</code> and should not be used as a 1162 * substitute. 1163 * 1164 * @return True if this object is currently incomplete, false otherwise. 1165 * @see #isReloadable 1166 */ 1167 public boolean isShrunk() { 1168 if (!isMutable() && 1169 (desc == null || 1170 props == null || 1171 env == null || 1172 (sections == null && execStatus != inProgress))) 1173 return true; 1174 else 1175 return false; 1176 } 1177 1178 /** 1179 * Get the description of the test from which this result was created. 1180 * Depending on how the test result was created, this information may 1181 * not be immediately available, and may be recreated from the test 1182 * result file. 1183 * 1184 * @return the test description for this test result object 1185 * @throws TestResult.Fault if there is a problem recreating the description 1186 * from the results file. 1187 */ 1188 public synchronized TestDescription getDescription() 1189 throws Fault { 1190 if (desc == null) { 1191 // reconstitute description (probably from file) 1192 reload(); 1193 } 1194 return desc; 1195 } 1196 1197 /* 1198 * Get the title of this test. This info originally comes from the test 1199 * description, but is saved in the .jtr file as well. 1200 * 1201 * @deprecated Please query the test description for info. 1202 public String getTitle() { 1203 // hmm slight copout; would like to make sure never null in the first place 1204 String title = desc.getParameter("title"); 1205 if (title == null) 1206 title = td.getRootRelativeURL(); 1207 } 1208 */ 1209 1210 /** 1211 * Get the path name for the results file for this test, relative to the 1212 * work directory. The internal separator is '/'. 1213 * @return the path name for the results file for this test, 1214 * relative to the work directory 1215 */ 1216 public String getWorkRelativePath() { 1217 return getWorkRelativePath(testURL); 1218 } 1219 1220 /** 1221 * Get the name, if any, for the result file for this object. 1222 * The path information contains platform specific path separators. 1223 * @return the name, if any, for the result file for this object 1224 */ 1225 public File getFile() { 1226 return resultsFile; 1227 } 1228 1229 public void resetFile() { 1230 resultsFile = null; 1231 } 1232 1233 /** 1234 * Get the path name for the results file for a test, relative to the 1235 * work directory. The internal separator is '/'. 1236 * @param td the test description for the test in question 1237 * @return the path name for the results file for a test, relative to the 1238 * work directory 1239 */ 1240 public static String getWorkRelativePath(TestDescription td) { 1241 String baseURL = td.getRootRelativePath(); 1242 1243 // add in uniquifying id if 1244 String id = td.getParameter("id"); 1245 return getWorkRelativePath(baseURL, id); 1246 } 1247 1248 /** 1249 * Get the path name for the results file for a test, relative to the 1250 * work directory. The internal separator is '/'. 1251 * 1252 * @param testURL May not be null; 1253 * @return The work relative path of the JTR for this test. Null if the 1254 * given URL is null. 1255 */ 1256 public static String getWorkRelativePath(String testURL) { 1257 int pound = testURL.lastIndexOf("#"); 1258 if (pound == -1) // no test id 1259 return getWorkRelativePath(testURL, null); 1260 else 1261 return getWorkRelativePath(testURL.substring(0, pound), 1262 testURL.substring(pound + 1)); 1263 } 1264 1265 /** 1266 * Get the path name for the results file for a test, relative to the 1267 * work directory. The internal separator is '/'. 1268 * 1269 * @param baseURL May not be null; 1270 * @param testId The test identifier that goes with the URL. This may be null. 1271 * @return The work relative path of the JTR for this test. Null if the 1272 * given URL is null. 1273 */ 1274 public static String getWorkRelativePath(String baseURL, String testId) { 1275 StringBuffer sb = new StringBuffer(baseURL); 1276 1277 // strip off extension 1278 stripExtn: 1279 for (int i = sb.length() - 1; i >= 0; i--) { 1280 switch (sb.charAt(i)) { 1281 case '.': 1282 sb.setLength(i); 1283 break stripExtn; 1284 case '/': 1285 break stripExtn; 1286 } 1287 } 1288 1289 // add in uniquifying id if 1290 if (testId != null) { 1291 sb.append('_'); 1292 sb.append(testId); 1293 } 1294 1295 sb.append(EXTN); 1296 1297 return sb.toString(); 1298 } 1299 1300 /** 1301 * Get the keys of the properties that this object has stored. 1302 * @return the keys of the properties that this object has stored 1303 */ 1304 public synchronized Enumeration getPropertyNames() { 1305 return PropertyArray.enumerate(props); 1306 } 1307 1308 /** 1309 * Get the value of a property of this test result. 1310 * 1311 * @param name The name of the property to be retrieved. 1312 * @return The value corresponding to the property name, null if not 1313 * found. 1314 * @throws TestResult.Fault if there is a problem 1315 * recreating data from the results file. 1316 */ 1317 public synchronized String getProperty(String name) 1318 throws Fault { 1319 if (props == null) { 1320 // reconstitute properties 1321 // this may result in a Fault, which is okay 1322 reload(); 1323 } 1324 1325 return PropertyArray.get(props, name); 1326 } 1327 1328 /** 1329 * Get a copy of the environment that this object has stored. 1330 * @return a copy of the environment that this object has stored 1331 * @throws TestResult.Fault if there is a problem 1332 * recreating data from the results file. 1333 * @see #setEnvironment 1334 */ 1335 public synchronized Map getEnvironment() throws Fault { 1336 if (env == null) { 1337 // reconstitute environment 1338 // this may result in a Fault, which is okay 1339 reload(); 1340 } 1341 return PropertyArray.getProperties(env); 1342 } 1343 1344 /** 1345 * Get the parent node in the test result table that 1346 * contains this test result object. 1347 * @return the parent node in the test result table that 1348 * contains this test result object. 1349 */ 1350 public TestResultTable.TreeNode getParent() { 1351 return parent; 1352 } 1353 1354 1355 /** 1356 * Set the parent node in the test result table that 1357 * contains this test result object. 1358 * @param p the parent node in the test result table that 1359 * contains this test result object. 1360 * @see #getParent 1361 */ 1362 void setParent(TestResultTable.TreeNode p) { 1363 parent = p; 1364 } 1365 1366 //----------ACCESS FUNCTIONS (TEST STATUS)---------------------------------- 1367 1368 /** 1369 * Determine if the test result object is still mutable. 1370 * Test results are only mutable while they are being created, up to 1371 * the point that the final status is set. 1372 * @return true if the test result object is still mutable, 1373 * and false otherwise 1374 */ 1375 public synchronized boolean isMutable() { 1376 // must be mutable during reload (internal operation) 1377 // mutable as long as possible, to allow max time for writing log messages 1378 return (execStatus == inProgress); 1379 } 1380 1381 1382 /** 1383 * Get the status for this test. 1384 * @return the status for this test 1385 * @see #setStatus 1386 */ 1387 public synchronized Status getStatus() { 1388 return execStatus; 1389 } 1390 1391 //----------ACCESS METHODS (TEST OUTPUT)---------------------------------- 1392 1393 /** 1394 * Find out how many sections this test result contains. 1395 * 1396 * @return The number of sections in this result. 1397 */ 1398 public synchronized int getSectionCount() { 1399 if (sections != null) { 1400 return sections.length; 1401 } 1402 else if (PropertyArray.get(props, SECTIONS) != null) { 1403 return parseSectionCount(PropertyArray.get(props, SECTIONS)); 1404 } 1405 else { 1406 // hum, test props are never discarded, so we have no sections 1407 return 0; 1408 } 1409 } 1410 1411 /** 1412 * Get the section specified by index. 1413 * Remember that index 0 is the default message section. 1414 * 1415 * @param index The index of the section to be retrieved. 1416 * @return The requested section. Will be null if the section does not exist. 1417 * @throws TestResult.ReloadFault Will occur if an error is encountered when reloading 1418 * JTR data. This may be the result of a corrupt or missing JTR file. 1419 * @see #MSG_SECTION_NAME 1420 */ 1421 public synchronized Section getSection(int index) throws ReloadFault { 1422 Section target; 1423 1424 if (sections == null && execStatus != inProgress) { 1425 // try to reload from file 1426 try { 1427 reload(); 1428 } 1429 catch (ReloadFault f) { 1430 throw f; 1431 } 1432 catch (Fault f) { 1433 throw new ReloadFault(i18n, "rslt.badFile", f.getMessage()); 1434 } 1435 } 1436 1437 if (index >= sections.length) { 1438 target = null; 1439 } 1440 else { 1441 target = sections[index]; 1442 } 1443 1444 return target; 1445 } 1446 1447 /** 1448 * Get the titles of all sections in this test result. 1449 * A null result probably indicates that there are no sections. This is 1450 * improbable since most test result object automatically have one section. 1451 * 1452 * @return The titles, one at a time in the array. Null if the titles 1453 * do not exist or could not be determined. 1454 */ 1455 public synchronized String[] getSectionTitles() { 1456 if (props == null) { 1457 try { 1458 reload(); 1459 } 1460 catch (Fault f) { 1461 // should this maybe be a JavaTestError? 1462 return null; 1463 } 1464 } 1465 1466 // look for what we need from easiest to hardest source 1467 String names = PropertyArray.get(props, SECTIONS); 1468 1469 if (names != null) { 1470 // it is cached 1471 return StringArray.split(names); 1472 } 1473 else if (sections != null) { 1474 // TR is not immutable yet, probably 1475 int numSections = getSectionCount(); 1476 String[] data = new String[numSections]; 1477 1478 for (int i = 0; i < numSections; i++) { 1479 data[i] = sections[i].getTitle(); 1480 } 1481 1482 return data; 1483 } 1484 else { 1485 // hum, bad. No sections exist and this data isn't cached 1486 // the test probably has not run 1487 return null; 1488 } 1489 } 1490 1491 /** 1492 * Check if this file is or appears to be a result (.jtr) file, 1493 * according to its filename extension. 1494 * @param f the file to be checked 1495 * @return true if this file is or appears to be a result (.jtr) file. 1496 */ 1497 public static boolean isResultFile(File f) { 1498 String p = f.getPath(); 1499 return (p.endsWith(EXTN)); 1500 } 1501 1502 /** 1503 * Writes the TestResult into a version 2 jtr file. 1504 * 1505 * @param workDir The work directory in which to write the results 1506 * @param backupPolicy a policy object defining what to do if a file 1507 * already exists with the same name as that which is about to be written. 1508 * @throws IllegalStateException This will occur if you attempt to write a result 1509 * which is still mutable. 1510 * @throws IOException Occurs when the output file cannot be created or written to. 1511 * Under this condition, this object will change it status to an error. 1512 */ 1513 public synchronized void writeResults(WorkDirectory workDir, BackupPolicy backupPolicy) 1514 throws IOException 1515 { 1516 if (isMutable()) 1517 throw new IllegalStateException("This TestResult is still mutable - set the status!"); 1518 1519 // could attempt a reload() I suppose 1520 if (props == null) 1521 props = emptyStringArray; 1522 1523 String wrp = getWorkRelativePath(desc).replace('/', File.separatorChar); 1524 resultsFile = workDir.getFile(wrp); 1525 1526 File resultsDir = resultsFile.getParentFile(); 1527 resultsDir.mkdirs(); // ensure directory created for .jtr file 1528 1529 File tempFile = createTempFile(workDir, backupPolicy); 1530 try { 1531 writeResults(tempFile, backupPolicy); 1532 } 1533 finally { 1534 if (tempFile.exists()) 1535 tempFile.delete(); 1536 } 1537 } 1538 1539 /** 1540 * Create a temporary file to which the results can be written, before being renamed 1541 * to its real name. 1542 */ 1543 // don't use File.createTempFile because of issues with the internal locking there 1544 private File createTempFile(WorkDirectory workDir, BackupPolicy backupPolicy) 1545 throws IOException 1546 { 1547 final int MAX_TRIES = 100; // absurdly big limit, but a limit nonetheless 1548 for (int i = 0; i < MAX_TRIES; i++) { 1549 File tempFile = new File(resultsFile.getPath() + "." + i + ".tmp"); 1550 if (tempFile.createNewFile()) 1551 return tempFile; 1552 } 1553 throw new IOException("could not create temp file for " + resultsFile + ": too many tries"); 1554 } 1555 1556 /** 1557 * Write the results to a temporary file, and when done, rename it to resultsFile 1558 */ 1559 private void writeResults(File tempFile, BackupPolicy backupPolicy) 1560 throws IOException 1561 { 1562 Writer out; 1563 try { 1564 out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(tempFile), StandardCharsets.UTF_8)); 1565 } 1566 catch (IOException e) { 1567 execStatus = Status.error("Problem writing result file for test: " + getTestName()); 1568 resultsFile = null; // file not successfully written after all 1569 throw e; 1570 } 1571 1572 try { 1573 // redundant, is done in setResult 1574 // needed though if setResult isn't being called 1575 props = PropertyArray.put(props, EXEC_STATUS, execStatus.toString()); 1576 1577 // file header 1578 out.write(JTR_V2_HEADER); 1579 out.write(lineSeparator); 1580 1581 // date and time 1582 out.write("#" + (new Date()).toString()); 1583 out.write(lineSeparator); 1584 1585 // checksum header and data 1586 //out.write(JTR_V2_CHECKSUM); 1587 //out.write(Long.toHexString(computeChecksum())); 1588 //out.write(lineSeparator); 1589 /* 1590 if (debug) { // debugging code 1591 out.write("# debug: test desc checksum: "); 1592 out.write(Long.toHexString(computeChecksum(desc))); 1593 out.write(lineSeparator); 1594 1595 for (Iterator iter = desc.getParameterKeys(); iter.hasNext(); ) { 1596 // don't rely on enumeration in a particular order 1597 // so simply add the checksum products together 1598 String KEY = (String) (iter.next()); 1599 out.write("# debug: test desc checksum key " + KEY + ": "); 1600 out.write(Long.toHexString(computeChecksum(KEY) * computeChecksum(desc.getParameter(KEY)))); 1601 out.write(lineSeparator); 1602 } 1603 1604 out.write("# debug: test env checksum: "); 1605 if (env == null) 1606 out.write("null"); 1607 else 1608 out.write(Long.toHexString(computeChecksum(env))); 1609 out.write(lineSeparator); 1610 1611 out.write("# debug: test props checksum: "); 1612 out.write(Long.toHexString(computeChecksum(props))); 1613 out.write(lineSeparator); 1614 1615 out.write("# debug: test sections checksum: "); 1616 out.write(Long.toHexString(computeChecksum(sections))); 1617 out.write(lineSeparator); 1618 1619 for (int I = 0; I < sections.length; I++) { 1620 out.write("# debug: test section[" + I + "] checksum: "); 1621 out.write(Long.toHexString(computeChecksum(sections[I]))); 1622 out.write(lineSeparator); 1623 1624 String[] NAMES = sections[I].getOutputNames(); 1625 for (int J = 0; J < NAMES.length; J++) { 1626 out.write("# debug: test section[" + I + "] name=" + NAMES[J] + " checksum: "); 1627 out.write(Long.toHexString(computeChecksum(NAMES[J]))); 1628 out.write(lineSeparator); 1629 1630 out.write("# debug: test section[" + I + "] name=" + NAMES[J] + " output checksum: "); 1631 out.write(Long.toHexString(computeChecksum(sections[I].getOutput(NAMES[J])))); 1632 out.write(lineSeparator); 1633 } 1634 } 1635 }*/ 1636 1637 // description header and data 1638 out.write(JTR_V2_TESTDESC); 1639 out.write(lineSeparator); 1640 1641 Map<String, String> tdProps = new HashMap<>(); 1642 desc.save(tdProps); 1643 PropertyArray.save(PropertyArray.getArray(tdProps), out); 1644 out.write(lineSeparator); 1645 1646 // test environment header and data 1647 if (env != null) { 1648 out.write(JTR_V2_ENVIRONMENT); 1649 out.write(lineSeparator); 1650 PropertyArray.save(env, out); 1651 out.write(lineSeparator); 1652 } 1653 1654 // test result props header and data 1655 out.write(JTR_V2_RESPROPS); 1656 out.write(lineSeparator); 1657 PropertyArray.save(props, out); 1658 out.write(lineSeparator); 1659 1660 // get sections into memory 1661 // I hope the out stream is not the same as the resultFile! 1662 if (sections == null) { 1663 throw new JavaTestError("Cannot write test result - it contains no sections."); 1664 } 1665 1666 for (int i = 0; i < sections.length; i++) { 1667 sections[i].save(out); 1668 } 1669 1670 out.write(lineSeparator); 1671 out.write(JTR_V2_TSTRESULT); 1672 out.write(execStatus.toString()); 1673 out.write(lineSeparator); 1674 out.close(); 1675 } // try 1676 catch (IOException e) { 1677 // This exception could be raised when trying to create the directory 1678 // for the test results; opening the results file, or closing it. 1679 execStatus = Status.error("Write to temp. JTR file failed (old JTR intact): " + 1680 tempFile.getPath()); 1681 resultsFile = null; // file not successfully written after all 1682 throw e; 1683 } // catch 1684 1685 try { 1686 backupPolicy.backupAndRename(tempFile, resultsFile); 1687 1688 // now that it has been successfully written out, make the object 1689 // a candidate for shrinking 1690 addToShrinkList(); 1691 } // try 1692 catch (IOException e) { 1693 // This exception could be raised when trying to create the directory 1694 // for the test results; opening the results file, or closing it. 1695 execStatus = Status.error("Problem writing result file: " + 1696 resultsFile.getPath()); 1697 resultsFile = null; // file not successfully written after all 1698 throw e; 1699 } // catch 1700 } 1701 1702 // -----observer methods --------------------------------------------------- 1703 /** 1704 * Add an observer to watch this test result for changes. 1705 * @param obs the observer to be added 1706 */ 1707 public synchronized void addObserver(Observer obs) { 1708 if (isMutable()) { 1709 Observer[] observers = observersTable.get(this); 1710 1711 if (observers == null) observers = new Observer[0]; 1712 1713 observers = DynamicArray.append(observers, obs); 1714 observersTable.put(this, observers); 1715 } 1716 } 1717 1718 /** 1719 * Remove an observer that was previously added. 1720 * @param obs the observer to be removed 1721 */ 1722 public synchronized void removeObserver(Observer obs) { 1723 Observer[] observers = observersTable.get(this); 1724 if (observers == null) 1725 return; 1726 1727 observers = DynamicArray.remove(observers, obs); 1728 if (observers == null) 1729 observersTable.remove(this); 1730 else 1731 observersTable.put(this, observers); 1732 } 1733 1734 /** 1735 * Gets the time when the test was completed, or at least the time 1736 * when it's final status was set. Be aware that if the information is 1737 * not available in memory, it will be loaded from disk. 1738 * 1739 * @return Time when this test acquired its final status setting. 1740 * @see #setStatus 1741 * @see java.util.Date 1742 */ 1743 public long getEndTime() { 1744 if (endTime < 0) { 1745 try { 1746 String datestr = PropertyArray.get(props, END); 1747 1748 if (datestr == null) { 1749 // this may be more expensive because it can cause a 1750 // reload from disk 1751 try { 1752 datestr = getProperty(END); 1753 } 1754 catch (Fault f) { 1755 } 1756 } 1757 1758 if (datestr != null) { 1759 Date date = parseDate(datestr); 1760 endTime = date.getTime(); 1761 } 1762 else { 1763 // info not available 1764 } 1765 } 1766 catch (ParseException e) { 1767 } 1768 } 1769 1770 return endTime; 1771 } 1772 1773 /** 1774 * Parse the date format used for timestamps, such as the start/stop timestamp. 1775 * @param s The string containing the date to be restored. 1776 * @see #formatDate 1777 */ 1778 public static synchronized Date parseDate(String s) throws ParseException { 1779 return dateFormat.parse(s); 1780 } 1781 1782 /** 1783 * Format the date format used for timestamps, such as the start/stop timestamp. 1784 * @param d The date object to be formatted into a string. 1785 * @see #parseDate 1786 */ 1787 public static synchronized String formatDate(Date d) { 1788 return dateFormat.format(d); 1789 } 1790 1791 // ----- PACKAGE METHODS --------------------------------------------------- 1792 1793 /** 1794 * Read a single minimal TestResult from a .jts stream. 1795 * The stream is not closed. 1796 * @deprecated JTS files are no longer supported 1797 TestResult(WorkDirectory workDir, DataInputStream in) throws IOException { 1798 workRelativePath = in.readUTF(); 1799 1800 // ** temp. fix ** XXX 1801 // make sure the path is in URL form with forward slashes 1802 // in the future all paths should already be of this form (TestDescription) 1803 int index = workRelativePath.indexOf('/'); 1804 if (index == -1) workRelativePath = workRelativePath.replace('\\', '/'); 1805 1806 resultsFile = workDir.getFile(workRelativePath.replace('/', File.separatorChar)); 1807 title = in.readUTF(); 1808 int esc = in.readByte(); 1809 String esr = in.readUTF(); 1810 execStatus = new Status(esc, esr); 1811 boolean defIsExec = in.readBoolean(); 1812 if (!defIsExec) { 1813 // have to read these, per protocol 1814 int dsc = in.readByte(); 1815 String dsr = in.readUTF(); 1816 //ignore dsc, dsr; they used to go in defStatus 1817 } 1818 } 1819 */ 1820 1821 /** 1822 * Read a single minimal TestResult which is capable of reloading itself. 1823 * None of the parameters may be null. 1824 * 1825 * @param url The full URL of this test, including test id. 1826 * @param workDir The work directory location, platform specfic path. 1827 * @param status The status that will be found in the JTR. 1828 * @throws JavaTestError Will be thrown if any params are null. 1829 */ 1830 TestResult(String url, WorkDirectory workDir, Status status) { 1831 if (url == null) 1832 throw new JavaTestError(i18n, "rslt.badTestUrl"); 1833 1834 if (workDir == null) 1835 throw new JavaTestError(i18n, "rslt.badWorkdir"); 1836 1837 if (status == null) 1838 throw new JavaTestError(i18n, "rslt.badStatus"); 1839 1840 testURL = url; 1841 resultsFile = workDir.getFile(getWorkRelativePath()); 1842 execStatus = status; 1843 } 1844 1845 /** 1846 * Read a single minimal TestResult which is capable of reloading itself. 1847 * None of the parameters may be null. 1848 * 1849 * @param url The full URL of this test, including test id. 1850 * @param workDir The work directory location, platform specific path. 1851 * @param status The status that will be found in the JTR. 1852 * @param endTime The time when that test finished execution. 1853 * @throws JavaTestError Will be thrown if any params are null. 1854 * @see #getEndTime() 1855 */ 1856 TestResult(String url, WorkDirectory workDir, Status status, long endTime) { 1857 if (url == null) 1858 throw new JavaTestError(i18n, "rslt.badTestUrl"); 1859 1860 if (workDir == null) 1861 throw new JavaTestError(i18n, "rslt.badWorkdir"); 1862 1863 if (status == null) 1864 throw new JavaTestError(i18n, "rslt.badStatus"); 1865 1866 testURL = url; 1867 resultsFile = workDir.getFile(getWorkRelativePath()); 1868 execStatus = status; 1869 this.endTime = endTime; 1870 } 1871 1872 void shareStatus(Map<String, Status>[] tables) { 1873 execStatus = shareStatus(tables, execStatus); 1874 } 1875 1876 /* 1877 * @deprecated JTS files no longer supported 1878 void writeSummary(DataOutputStream out) throws IOException { 1879 out.writeUTF(workRelativePath); 1880 out.writeUTF(title); 1881 out.writeByte(execStatus.getType()); 1882 out.writeUTF(execStatus.getReason()); 1883 out.writeBoolean(true); // defStatus == execStatus 1884 } 1885 */ 1886 1887 String[] getTags() { 1888 // Script or someone else could possibly do this w/the observer 1889 if (sections == null) { 1890 return null; 1891 } 1892 1893 Vector<String> tagV = new Vector<>(sections.length * 2); 1894 1895 for (int i = 0; i < sections.length; i++) { 1896 String[] names = sections[i].getOutputNames(); 1897 1898 for (int j = 0; j < names.length; j++) { 1899 tagV.addElement(names[j]); 1900 } // inner for 1901 } // outer for 1902 1903 String[] tagA = new String[tagV.size()]; 1904 tagV.copyInto(tagA); 1905 1906 return tagA; 1907 } 1908 1909 /** 1910 * Insert a test description into this test results. 1911 * This will only work if the test description is currently not available. 1912 * The name in the test description must match the name of this test. 1913 * @param td The new test description, a null value will have no effect. 1914 * @see #isShrunk() 1915 * @throws IllegalStateException If the state of this object fobiu 1916 */ 1917 void setTestDescription(TestDescription td) { 1918 if (td == null) 1919 return; 1920 1921 String name = td.getRootRelativeURL(); 1922 if (!testURL.equals(name)) 1923 throw new IllegalStateException(); 1924 1925 if (desc != null) { // compare if possible 1926 if (!desc.equals(td)) { // test descriptions are not the same 1927 // accept new TD, reset this TR 1928 // reset status to a special one 1929 execStatus = tdMismatch; 1930 desc = td; 1931 1932 props = emptyStringArray; 1933 resultsFile = null; 1934 env = emptyStringArray; 1935 sections = emptySectionArray; 1936 1937 if (isMutable()) 1938 createSection(MSG_SECTION_NAME); 1939 } 1940 else { 1941 // TDs are equal, no action, drop thru and return 1942 } 1943 } 1944 else { 1945 desc = td; 1946 } 1947 } 1948 1949 // ----- PRIVATE METHODS --------------------------------------------------- 1950 1951 /** 1952 * @deprecated Use the Section API to accomplish your task. 1953 */ 1954 private static Reader getLastRefOutput(TestResult tr) { 1955 try { 1956 Section lastBlk = tr.getSection(tr.getSectionCount() - 1); 1957 return new StringReader(lastBlk.getOutput("ref")); 1958 } 1959 catch (ReloadFault f) { 1960 // not the best, but this method is deprecated and hopefully never 1961 // called 1962 return null; 1963 } 1964 } 1965 1966 private long computeChecksum() { 1967 long cs = 0; 1968 cs = cs * 37 + computeChecksum(desc); 1969 // in JT2.1.1a, environment was not included in checksum, 1970 // so allow that for backward compatibility 1971 String jtv = PropertyArray.get(props, VERSION); 1972 if (env != null) { 1973 if (jtv == null || !jtv.equals("JT_2.1.1a")) 1974 cs = cs * 37 + computeChecksum(env); 1975 } 1976 cs = cs * 37 + computeChecksum(props); 1977 if (sections != null) 1978 cs = cs * 37 + computeChecksum(sections); 1979 cs = cs * 37 + execStatus.getType() + computeChecksum(execStatus.getReason()); 1980 return Math.abs(cs); // ensure +ve, to avoid sign issues! 1981 } 1982 1983 private static long computeChecksum(TestDescription td) { 1984 long cs = 0; 1985 for (Iterator i = td.getParameterKeys(); i.hasNext(); ) { 1986 // don't rely on enumeration in a particular order 1987 // so simply add the checksum products together 1988 String key = (String) (i.next()); 1989 cs += computeChecksum(key) * computeChecksum(td.getParameter(key)); 1990 } 1991 return cs; 1992 } 1993 1994 private static long computeChecksum(Section[] sections) { 1995 long cs = sections.length; 1996 for (int i = 0; i < sections.length; i++) { 1997 cs = cs * 37 + computeChecksum(sections[i]); 1998 } 1999 return cs; 2000 } 2001 2002 private static long computeChecksum(Section s) { 2003 long cs = computeChecksum(s.getTitle()); 2004 String[] names = s.getOutputNames(); 2005 for (int i = 0; i <names.length; i++) { 2006 cs = cs * 37 + computeChecksum(names[i]); 2007 cs = cs * 37 + computeChecksum(s.getOutput(names[i])); 2008 } 2009 return cs; 2010 } 2011 2012 private static long computeChecksum(String[] strings) { 2013 long cs = strings.length; 2014 for (int i = 0; i < strings.length; i++) { 2015 cs = cs * 37 + computeChecksum(strings[i]); 2016 } 2017 return cs; 2018 } 2019 2020 private static long computeChecksum(String s) { 2021 long cs = 0; 2022 for (int i = 0; i < s.length(); i++) { 2023 char c = s.charAt(i); 2024 //if (!Character.isISOControl(c) || c == '\n') 2025 cs = cs * 37 + c; 2026 } 2027 return cs; 2028 } 2029 2030 /** 2031 * @throws ResultFileNotFoundFault May be thrown if the JTR file cannot be found. 2032 * @throws ReloadFault Generally describes any error which is encountered while 2033 * reading or processing the input file. 2034 */ 2035 private synchronized void reload() 2036 throws ResultFileNotFoundFault, ReloadFault 2037 { 2038 if (resultsFile == null) 2039 throw new ReloadFault(i18n, "rslt.noResultFile"); 2040 2041 if (isMutable()) 2042 throw new IllegalStateException("Cannot do a reload of this object."); 2043 2044 try { 2045 reload(new BufferedReader(new InputStreamReader(new FileInputStream(resultsFile), StandardCharsets.UTF_8))); 2046 2047 // Well, we have successfully reloaded it, so the object is now taking 2048 // up a big footprint again ... put it back on the list to be shrunk again 2049 addToShrinkList(); 2050 } 2051 catch (FileNotFoundException e) { 2052 throw new ResultFileNotFoundFault(i18n, "rslt.fileNotFound", resultsFile); 2053 } 2054 catch (IOException e) { 2055 throw new ReloadFault(i18n, "rslt.badFile", e); 2056 } 2057 } 2058 2059 /** 2060 * @throws ReloadFault Generally describes any error which is encountered while 2061 * reading or processing the input file. This may indicate 2062 * an empty file or incorrectly formatted file. 2063 */ 2064 private void reload(Reader r) 2065 throws ReloadFault, IOException 2066 { 2067 try { 2068 BufferedReader br = new BufferedReader(r); 2069 String line = br.readLine(); 2070 2071 // determine JTR version 2072 if (line == null) { 2073 throw new ReloadFault(i18n, "rslt.empty", resultsFile); 2074 } 2075 if (line.equals(JTR_V2_HEADER)) { 2076 reloadVersion2(br); 2077 } 2078 else if (line.equals(JTR_V1_HEADER)) { 2079 reloadVersion1(br); 2080 } 2081 else 2082 throw new ReloadFault(i18n, "rslt.badHeader", resultsFile); 2083 } 2084 catch (RuntimeException e) { 2085 throw new ReloadFault(i18n, "rslt.badRuntimeErr", 2086 new String[] {resultsFile.getPath(), e.getLocalizedMessage()}); 2087 } 2088 finally { 2089 r.close(); 2090 } 2091 } 2092 2093 private void reloadVersion1(BufferedReader in) 2094 throws ReloadFault, IOException 2095 { 2096 // grab property info 2097 StringBuffer buff = new StringBuffer(); 2098 String line = in.readLine(); 2099 while (!(line == null) && !(line.length() == 0)) { 2100 buff.append(line); 2101 buff.append(lineSeparator); 2102 line = in.readLine(); 2103 } 2104 2105 // store if needed 2106 Properties pairs = new Properties(); 2107 if (props == null || desc == null) { 2108 StringReader sr = new StringReader(buff.toString()); 2109 buff = null; 2110 line = null; 2111 2112 pairs = new Properties(); 2113 pairs.load(sr); 2114 } 2115 2116 if (props == null) { 2117 // reload test result properties 2118 props = PropertyArray.getArray(pairs); 2119 } 2120 2121 pairs = null; 2122 2123 if (desc == null) { 2124 File path = new File(PropertyArray.get(props, "testsuite")); 2125 if (!path.isDirectory()) 2126 path = new File(path.getParent()); 2127 File file = new File(PropertyArray.get(props, "file")); 2128 2129 uniquifyStrings(props); 2130 2131 desc = new TestDescription(path, file, 2132 PropertyArray.getProperties(props)); 2133 } 2134 2135 buff = new StringBuffer(); 2136 line = in.readLine(); 2137 while (!(line == null)) { 2138 if (line.startsWith("command: ")) { 2139 // a section 2140 Section blk = processOldSection(line, in); 2141 2142 if (blk != null) { 2143 sections = DynamicArray.append(sections, blk); 2144 } 2145 } 2146 else if (line.startsWith(JTR_V1_TSTRESULT)) { 2147 // test result 2148 if (line == null) { 2149 // couldn't get the status text for some reason 2150 } 2151 else { 2152 line = extractSlice(line, JTR_V1_TSTRESULT.length(), " ", null); 2153 execStatus = Status.parse(line); 2154 } // inner else 2155 2156 break; 2157 } 2158 else { 2159 // message text 2160 buff.append(line); 2161 buff.append(lineSeparator); 2162 } // else 2163 2164 line = in.readLine(); 2165 } // while 2166 2167 // create the test message section and put first in the array 2168 Section blk = new Section(MSG_SECTION_NAME); 2169 blk.reloadOutput(MESSAGE_OUTPUT_NAME, buff.toString()); 2170 Section[] tempBlks = new Section[sections.length+1]; 2171 tempBlks[0] = blk; 2172 System.arraycopy(sections, 0, tempBlks, 1, sections.length); 2173 sections = tempBlks; 2174 } 2175 2176 private Section processOldSection(String line1, BufferedReader in) 2177 throws ReloadFault, IOException 2178 { 2179 StringBuffer sb = new StringBuffer(); // message stream 2180 Section section = null; 2181 String line = line1; 2182 while (!(line == null)) { 2183 if (line.startsWith("----------")) { 2184 String streamName = null; 2185 String sectionName = null; 2186 StringBuffer buff = new StringBuffer(); 2187 int lines = 0; 2188 int chars = 0; 2189 try { 2190 streamName = extractSlice(line, 10, null, ":"); 2191 sectionName = extractSlice(line, 10, ":", "("); 2192 lines = Integer.parseInt(extractSlice(line, 10, "(", "/")); 2193 chars = Integer.parseInt(extractSlice(line, 10, "/", ")")); 2194 2195 for (int count = 0; count < lines; count++) { 2196 buff.append(in.readLine()); 2197 } 2198 } 2199 catch (NumberFormatException e) { 2200 // confused! 2201 throw new ReloadFault(i18n, "rslt.badFile", e); 2202 } 2203 2204 if (section == null) 2205 section = new Section(sectionName); 2206 2207 section.reloadOutput(streamName, buff.toString()); 2208 } 2209 else if (line.startsWith(JTR_V1_SECTRESULT)) { 2210 // set result 2211 if (section == null) 2212 section = new Section(""); 2213 2214 // get the Status text 2215 line = extractSlice(line, JTR_V1_SECTRESULT.length(), " ", null); 2216 2217 if (line == null) 2218 // couldn't get the status text for some reason 2219 throw new ReloadFault(i18n, "rslt.noSectionResult"); 2220 else 2221 section.reloadStatus(Status.parse(line)); 2222 2223 break; 2224 } 2225 else { 2226 // just a plain message 2227 sb.append(line); 2228 sb.append(lineSeparator); 2229 } 2230 2231 line = in.readLine(); 2232 } 2233 2234 if (section != null) 2235 section.reloadOutput(MESSAGE_OUTPUT_NAME, sb.toString()); 2236 2237 return section; 2238 } 2239 2240 private void reloadVersion2(BufferedReader in) 2241 throws ReloadFault, IOException 2242 { 2243 //String checksumText = null; 2244 String line; 2245 2246 // look for optional checksum and then test description, 2247 // skipping comments 2248 while ((line = in.readLine()) != null) { 2249 if (line.equals(JTR_V2_TESTDESC)) 2250 break; 2251 //else if (line.startsWith(JTR_V2_CHECKSUM)) { 2252 //checksumText = line.substring(JTR_V2_CHECKSUM.length()); 2253 //} 2254 else if (!line.startsWith("#")) 2255 throw new ReloadFault(i18n, "rslt.badLine", line); 2256 } 2257 2258 // this probably won't work with a normal Properties object 2259 String[] tdProps = PropertyArray.load(in); 2260 2261 if (desc == null) { 2262 uniquifyStrings(tdProps); 2263 desc = TestDescription.load(tdProps); 2264 } 2265 tdProps = null; // dump it 2266 2267 // XXX compare to TD 2268 2269 // remove comment lines and look for test env props 2270 while ((line = in.readLine()) != null) { 2271 if (line.startsWith(JTR_V2_RESPROPS)) 2272 break; 2273 else if (line.startsWith(JTR_V2_ENVIRONMENT)) { 2274 env = PropertyArray.load(in); 2275 uniquifyStrings(env); 2276 } 2277 else if (!line.startsWith("#")) 2278 throw new ReloadFault(i18n, "rslt.badLine", line); 2279 } 2280 2281 if (env == null) 2282 env = new String[] {}; 2283 2284 if (line == null) { 2285 throw new ReloadFault(i18n, "rslt.badFormat"); 2286 } 2287 2288 String[] trProps = PropertyArray.load(in); 2289 2290 if (props == null) { 2291 // restore the properties of this result 2292 uniquifyStrings(trProps); 2293 props = trProps; 2294 } 2295 2296 trProps = null; // dump it 2297 2298 // read the sections 2299 int sectionCount = parseSectionCount(PropertyArray.get(props, SECTIONS)); 2300 sections = new Section[sectionCount]; 2301 for (int i = 0; i < getSectionCount(); i++) { 2302 sections[i] = new Section(in); 2303 } 2304 2305 // get the final test status 2306 while ((line = in.readLine()) != null) { 2307 if (line.startsWith(JTR_V2_TSTRESULT)) { 2308 execStatus = Status.parse(line.substring(JTR_V2_TSTRESULT.length())); 2309 break; 2310 } 2311 } 2312 2313 if (execStatus == null) 2314 execStatus = Status.error("NO STATUS RECORDED IN FILE"); 2315 2316 // checksum support removed 2317 checksumState = NO_CHECKSUM; 2318 } 2319 2320 /** 2321 * This method tolerates null. It expects a list of section names - basically 2322 * a space separated list and returns the number of items there. 2323 * @param s The section name list string to parse and count. May be null. 2324 * @return Number of sections listed in the string. Will be zero if the 2325 * input was null. 2326 */ 2327 int parseSectionCount(String s) { 2328 if (s == null || s.length() == 0) { 2329 return 0; 2330 } 2331 2332 return StringArray.split(s).length; 2333 } 2334 2335 void uniquifyStrings(String[] data) { 2336 for (int i = 0; i < data.length; i++) 2337 // don't do this for large strings 2338 if (data[i] != null && data[i].length() < 30) 2339 data[i] = data[i].intern(); 2340 } 2341 2342 /** 2343 * Extract a substring specified by a start and end pattern (string). 2344 * The start and end strings must be single chars. 2345 * @param s String to do this operation on 2346 * @param where Position in the string to start at 2347 * @param start Beginning pattern for the slice, exclusive. 2348 * @param end Ending pattern for the slice, exclusive. Null means 2349 * to-end-of-string. 2350 * @return The requested substring or null if error. 2351 */ 2352 String extractSlice(String s, int where, String start, String end) { 2353 int startInd; 2354 int endInd; 2355 2356 if (start == null) 2357 startInd = where; 2358 else { 2359 int i = s.indexOf(start, where); 2360 if (i < 0) 2361 return null; 2362 startInd = i + start.length(); 2363 } 2364 2365 if (end == null) 2366 endInd = s.length(); 2367 else { 2368 endInd = s.indexOf(end, startInd); 2369 if (endInd == -1) 2370 return null; 2371 } 2372 2373 try { 2374 return s.substring(startInd, endInd); 2375 } 2376 catch (StringIndexOutOfBoundsException e) { 2377 return null; 2378 } 2379 } 2380 2381 2382 private static boolean compare(Reader left, Reader right) 2383 throws Fault { 2384 try { 2385 try { 2386 for (;;) { 2387 int l = left.read(), r = right.read(); 2388 if (l != r) { 2389 return false; // different content found 2390 } 2391 if (l == -1) 2392 return true; 2393 } 2394 } 2395 finally { 2396 left.close(); 2397 right.close(); 2398 } 2399 } 2400 catch (IOException e) { 2401 throw new Fault(i18n, "rslt.badCompare", e); 2402 } 2403 } 2404 2405 private static Status shareStatus(Map<String, Status>[] tables, Status s) { 2406 int type = s.getType(); 2407 String reason = s.getReason(); 2408 Status result = tables[type].get(reason); 2409 if (result == null) { 2410 tables[type].put(reason, s); 2411 result = s; 2412 } 2413 2414 return result; 2415 } 2416 2417 // ------------------------ OBSERVER MAINTENANCE ------------------------- 2418 2419 /** 2420 * Notify observers that a new section has been created. 2421 * 2422 * @param section The section that was created. 2423 */ 2424 private synchronized void notifyCreatedSection(Section section) { 2425 Observer[] observers = observersTable.get(this); 2426 if (observers != null) 2427 for (int i = 0; i < observers.length; i++) 2428 observers[i].createdSection(this, section); 2429 } 2430 2431 /** 2432 * Notify observers that a section has been completed. 2433 * 2434 * @param section The section that was completed. 2435 */ 2436 private synchronized void notifyCompletedSection(Section section) { 2437 Observer[] observers = observersTable.get(this); 2438 if (observers != null) 2439 for (int i = 0; i < observers.length; i++) 2440 observers[i].completedSection(this, section); 2441 } 2442 2443 /** 2444 * Notify observers that new output is being created. 2445 * 2446 * @param section The section that was created. 2447 * @param outputName The name of the output. 2448 */ 2449 private synchronized void notifyCreatedOutput(Section section, String outputName) { 2450 Observer[] observers = observersTable.get(this); 2451 if (observers != null) 2452 for (int i = 0; i < observers.length; i++) 2453 observers[i].createdOutput(this, section, outputName); 2454 } 2455 2456 /** 2457 * Notify observers that a particular output has been completed. 2458 * 2459 * @param section The section that was completed. 2460 * @param outputName The name of the output. 2461 */ 2462 private synchronized void notifyCompletedOutput(Section section, String outputName) { 2463 Observer[] observers = observersTable.get(this); 2464 if (observers != null) 2465 for (int i = 0; i < observers.length; i++) 2466 observers[i].completedOutput(this, section, outputName); 2467 } 2468 2469 /** 2470 * Notify all observers that new data has been written to some output. 2471 * 2472 * @param section The section being modified. 2473 * @param outputName The stream of the section that is being modified. 2474 * @param text The text that was added (appended). 2475 */ 2476 private synchronized void notifyUpdatedOutput(Section section, String outputName, int start, int end, String text) { 2477 Observer[] observers = observersTable.get(this); 2478 if (observers != null) 2479 for (int i = 0; i < observers.length; i++) 2480 observers[i].updatedOutput(this, section, outputName, start, end, text); 2481 } 2482 2483 /** 2484 * Notify all observers that new data has been written to some output. 2485 * 2486 * @param section The section being modified. 2487 * @param outputName The stream of the section that is being modified. 2488 */ 2489 private synchronized void notifyUpdatedOutput(Section section, String outputName, int start, int end, 2490 char[] buf, int offset, int len) { 2491 Observer[] observers = observersTable.get(this); 2492 if (observers != null) { 2493 // only create string if there are really observers who want to see it 2494 String text = new String(buf, offset, len); 2495 for (int i = 0; i < observers.length; i++) 2496 observers[i].updatedOutput(this, section, outputName, start, end, text); 2497 } 2498 } 2499 2500 /** 2501 * Notify all observers that a property has been updated. 2502 * 2503 * @param key The key for the property that was modified. 2504 * @param value The new value for the property. 2505 */ 2506 private synchronized void notifyUpdatedProperty(String key, String value) { 2507 Observer[] observers = observersTable.get(this); 2508 if (observers != null) 2509 for (int i = 0; i < observers.length; i++) 2510 observers[i].updatedProperty(this, key, value); 2511 } 2512 2513 /** 2514 * Notify observers the test has completed. 2515 */ 2516 private synchronized void notifyCompleted() { 2517 // since there will be no more observer messages after this, there 2518 // is no need to keep any observers registered after we finish here 2519 // so get the observers one last time, and at the same time 2520 // remove them from the table 2521 Observer[] observers = observersTable.remove(this); 2522 if (observers != null) { 2523 for (int i = 0; i < observers.length; i++) 2524 observers[i].completed(this); 2525 observersTable.remove(this); 2526 } 2527 2528 } 2529 2530 /** 2531 * @return Position of the specified section, or -1 if not found. 2532 */ 2533 private synchronized int findSection(String name) { 2534 int location; 2535 2536 if (sections == null || sections.length == 0) { 2537 return -1; 2538 } 2539 2540 for (location = 0; location < sections.length; location++) { 2541 if (sections[location].getTitle().equals(name)) { 2542 // found 2543 break; 2544 } 2545 } // for 2546 2547 // loop exited because of counter, not a hit 2548 if (location == sections.length) { 2549 location = -1; 2550 } 2551 2552 return location; 2553 } 2554 2555 private void addToShrinkList() { 2556 synchronized (shrinkList) { 2557 // if this object is in the list; remove it; 2558 // if there are dead weak refs, remove them 2559 for (Iterator<WeakReference<TestResult>> iter = shrinkList.iterator(); iter.hasNext(); ) { 2560 WeakReference wref = iter.next(); 2561 Object o = wref.get(); 2562 if (o == null || o == this) 2563 iter.remove(); 2564 } 2565 while (shrinkList.size() >= maxShrinkListSize) { 2566 WeakReference wref = shrinkList.removeFirst(); 2567 TestResult tr = (TestResult) (wref.get()); 2568 if (tr != null) 2569 tr.shrink(); 2570 } 2571 shrinkList.addLast(new WeakReference<>(this)); 2572 } 2573 } 2574 2575 /** 2576 * Tells the object that it can optimize itself for a small memory footprint. 2577 * Doing this may sacrifice performance when accessing object data. This 2578 * only works on results that are immutable. 2579 */ 2580 private synchronized void shrink() { 2581 if (isMutable()) { 2582 throw new IllegalStateException("Can't shrink a mutable test result!"); 2583 } 2584 2585 // Should ensure we have a resultsFile. 2586 sections = null; 2587 2588 // NOTE: if either of these are discarded, it may be a good idea to 2589 // optimize reload() to not read the section/stream data since 2590 // a small property lookup could incur a huge overhead 2591 //props = null; // works, may or may-not improve memory usage 2592 //desc = null; // doesn't work in current implementation 2593 } 2594 2595 // the following fields should be valid for all test results 2596 private File resultsFile; // if set, location where test results are stored 2597 private Status execStatus; // pre-compare result 2598 private String testURL; // URL for this test, equal to the one in TD.getRootRelativeURL 2599 private long endTime = -1; // when test finished 2600 private byte checksumState; // checksum state 2601 // the following fields are candidates for shrinking although not currently done 2602 private TestDescription desc; // test description for which this is the result 2603 private String[] props; // table of values written during test execution 2604 private String[] env; 2605 // this field is cleared when the test result is shrunk 2606 private Section[] sections; // sections of output written during test execution 2607 private int maxTROutputSize = 0; // maximum output size for this test result 2608 2609 // only valid when this TR is in a TRT, should remain when shrunk 2610 private TestResultTable.TreeNode parent; 2611 2612 // because so few test results will typically be observed (e.g. at most one) 2613 // we don't use a per-instance array of observers, but instead associate any 2614 // such arrays here. 2615 private static Map<TestResult, Observer[]> observersTable = new Hashtable<>(16); 2616 2617 /** 2618 * The name of the default output that all Sections are equipped with. 2619 */ 2620 public static final String MESSAGE_OUTPUT_NAME = "messages"; 2621 2622 /** 2623 * The name of the default section that all TestResult objects are equipped with. 2624 */ 2625 public static final String MSG_SECTION_NAME = "script_messages"; 2626 2627 /** 2628 * The name of the property that defines the test description file. 2629 */ 2630 public static final String DESCRIPTION = "description"; 2631 2632 /** 2633 * The name of the property that defines the time at which the test 2634 * execution finished. 2635 */ 2636 public static final String END = "end"; 2637 2638 /** 2639 * The name of the property that defines the environment name. 2640 */ 2641 public static final String ENVIRONMENT = "environment"; 2642 2643 /** 2644 * The name of the property that defines the test execution status. 2645 */ 2646 public static final String EXEC_STATUS = "execStatus"; 2647 2648 /** 2649 * The name of the property that defines the OS on which JT Harness 2650 * was running when the test was run. 2651 */ 2652 public static final String JAVATEST_OS = "javatestOS"; 2653 2654 /** 2655 * The name of the property that defines the script that ran the test. 2656 */ 2657 public static final String SCRIPT = "script"; 2658 2659 /** 2660 * The name of the property that defines the test output sections 2661 * that were recorded when the test ran. 2662 */ 2663 public static final String SECTIONS = "sections"; 2664 2665 /** 2666 * The name of the property that defines the time at which the test 2667 * execution started. 2668 */ 2669 public static final String START = "start"; 2670 2671 /** 2672 * The name of the property that defines the test for which this is 2673 * the result object. 2674 */ 2675 public static final String TEST = "test"; 2676 2677 /** 2678 * The name of the property that defines which version of JT Harness 2679 * was used to run the test. 2680 */ 2681 public static final String VERSION = "javatestVersion"; 2682 2683 /** 2684 * The name of the property that defines the work directory for the test. 2685 */ 2686 public static final String WORK = "work"; 2687 2688 /** 2689 * The name of the property that defines the variety of harness in use. 2690 * Generally the full harness or the lite version. 2691 */ 2692 public static final String VARIETY = "harnessVariety"; 2693 2694 /** 2695 * The name of the property that defines the type of class loader used when 2696 * running the harness (classpath mode or module mode generally). 2697 */ 2698 public static final String LOADER = "harnessLoaderMode"; 2699 2700 /** 2701 * DateFormat, that is used to store date into TestResult 2702 */ 2703 public static final DateFormat dateFormat = 2704 new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US); 2705 2706 static final String EXTN = ".jtr"; 2707 2708 private static final Status 2709 filesSame = Status.passed("Output file and reference file matched"), 2710 filesDifferent = Status.failed("Output file and reference file were different"), 2711 fileError = Status.failed("Error occurred during comparison"), 2712 interrupted = Status.failed("interrupted"), 2713 inProgress = Status.notRun("Test running..."), 2714 incomplete = Status.notRun("Section not closed, may be incomplete"), 2715 tdMismatch = Status.notRun("Old test flushed, new test description located"), 2716 notRunStatus = Status.notRun(""); 2717 2718 private static final String[] emptyStringArray = new String[0]; 2719 private static final Section[] emptySectionArray = new Section[0]; 2720 2721 private static final String defaultClassDir = "classes"; 2722 2723 // info for reading/writing JTR files (version 1) 2724 private static final String JTR_V1_HEADER = "#Test Results"; 2725 private static final String JTR_V1_SECTRESULT = "command result:"; 2726 private static final String JTR_V1_TSTRESULT = "test result:"; 2727 2728 // info for reading/writing JTR files (version 2) 2729 private static final String JTR_V2_HEADER = "#Test Results (version 2)"; 2730 private static final String JTR_V2_SECTION = "#section:"; 2731 private static final String JTR_V2_CHECKSUM = "#checksum:"; 2732 private static final String JTR_V2_TESTDESC = "#-----testdescription-----"; 2733 private static final String JTR_V2_RESPROPS = "#-----testresult-----"; 2734 private static final String JTR_V2_ENVIRONMENT = "#-----environment-----"; 2735 private static final String JTR_V2_SECTRESULT = "result: "; 2736 private static final String JTR_V2_TSTRESULT = "test result: "; 2737 private static final String JTR_V2_SECTSTREAM = "----------"; 2738 2739 private static final String lineSeparator = System.getProperty("line.separator"); 2740 2741 private static final int DEFAULT_MAX_SHRINK_LIST_SIZE = 128; 2742 private static final int maxShrinkListSize = 2743 Integer.getInteger("javatest.numCachedResults", DEFAULT_MAX_SHRINK_LIST_SIZE).intValue(); 2744 private static LinkedList<WeakReference<TestResult>> shrinkList = new LinkedList<>(); 2745 2746 private static final int DEFAULT_MAX_OUTPUT_SIZE = 100000; 2747 private static final int commonOutputSize = 2748 Integer.getInteger("javatest.maxOutputSize", DEFAULT_MAX_OUTPUT_SIZE).intValue(); 2749 2750 private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(TestResult.class); 2751 2752 private static boolean debug = Boolean.getBoolean("debug." + TestResult.class.getName()); 2753 2754 }