Logo Search packages:      
Sourcecode: cacao-oj6 version File versions

TestResult.java

/*
 * $Id$
 *
 * Copyright 1996-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package com.sun.javatest;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.lang.ref.WeakReference;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Map;
import java.util.Vector;

import com.sun.javatest.util.BackupPolicy;
import com.sun.javatest.util.I18NResourceBundle;
import com.sun.javatest.util.Properties;
import com.sun.javatest.util.PropertyArray;
import com.sun.javatest.util.StringArray;
import com.sun.javatest.util.DynamicArray;

/** 
 * The TestResult object encapsulates the results from a test.
 * Test results are formatted in sections of command output,
 * comments and sometimes "streams" of output (<tt>stdout</tt> for example).
 * Each of these sections is represented by a (@link TestResult.Section Section).
 * Instances of this class are mutable until the result of the section is
 * set or until the result of the test itself is set.
 *
 * Test results are stored in a structured text files.
 * The TestResult class serves as the API for accessing the various 
 * components that make up the result of running a test.  
 * The status is cached as its size is small and it is accessed often.
 *
 * This class and inner classes will throw IllegalStateExceptions if an
 * attempt is made to modify the any part of the object that has been
 * marked immutable. 
 */

00077 public class TestResult {
    /**
     * This exception is to report problems using TestResult objects.
     */
00081     public static class Fault extends Exception
    {
      Fault(I18NResourceBundle i18n, String key) {
          super(i18n.getString(key));
      }

      Fault(I18NResourceBundle i18n, String key, Object arg) {
          super(i18n.getString(key, arg));
      }

      Fault(I18NResourceBundle i18n, String key, Object[] args) {
          super(i18n.getString(key, args));
      }
    }

    /**
     * This exception is thrown if the JTR file cannot be found.
     */
00099     public static class ResultFileNotFoundFault extends Fault {
      ResultFileNotFoundFault(I18NResourceBundle i18n, String key) {
          super(i18n, key);
      }

      ResultFileNotFoundFault(I18NResourceBundle i18n, String key, Object arg) {
          super(i18n, key, arg);
      }

      ResultFileNotFoundFault(I18NResourceBundle i18n, String key, Object[] args) {
          super(i18n, key, args);
      }
    }

    /** 
     * This exception ay occur anytime the JTR file is being read from the filesystem.
     * To optimize memory usage, the contents of a TestResult object are sometimes
     * discarded and then loaded on demand from the JTR file.  If a fault occurs
     * when reading the JTR file, this fault may occur.
     *
     * @see TestResult.ResultFileNotFoundFault
     */
00121     public static class ReloadFault extends Fault {
      ReloadFault(I18NResourceBundle i18n, String key) {
          super(i18n, key);
      }

      ReloadFault(I18NResourceBundle i18n, String key, Object arg) {
          super(i18n, key, arg);
      }

      ReloadFault(I18NResourceBundle i18n, String key, Object[] args) {
          super(i18n, key, args);
      }
    }

    /**
     * An interface to observe activity in a TestResult as it is created.
     */
00138     public interface Observer {
        /**
         * A new section has been created in the test result.
         *
         * @param tr The test result in which the section was created.
         * @param section The section that has been created
         */
        public void createdSection(TestResult tr, Section section);

        /**
         * A section has been been completed in the test result.
         *
         * @param tr The test result containing the section.
         * @param section The section that has been completed.
         */
        public void completedSection(TestResult tr, Section section);

        /**
         * New output has been created in a section of the test result.
         *
         * @param tr The test result containing the output.
         * @param section The section in which the output has been created.
         * @param outputName The name of the output.
         */
        public void createdOutput(TestResult tr, Section section, String outputName);

        /**
         * Output has been completed in a section of the test result.
         *
         * @param tr The test result containing the output.
         * @param section The section in which the output has been completed.
         * @param outputName The name of the output.
         */
        public void completedOutput(TestResult tr, Section section, String outputName);

        /**
         * The output for a section has been updated.
         *
         * @param tr The test result object being modified.
         * @param section The section in which the output is being produced.
         * @param outputName The name of the output.
       * @param start the start offset of the text that was changed
       * @param end the end offset of the text that was changed
         * @param text the text that replaced the specified range.
         */
        public void updatedOutput(TestResult tr, Section section, String outputName, int start, int end, String text);

      /**
       * A property of the test result has been updated.
       *
       * @param tr The test result containing the property that was modified.
       * @param name The key for the property that was modified.
       * @param value The new value for the property.
       *
       */
      public void updatedProperty(TestResult tr, String name, String value);

      /**
       * The test has completed, and the results are now immutable.
       * There will be no further observer calls.
       * @param tr The test result that has been completed.
       */
      public void completed(TestResult tr);

    }

    /**
     * This "section" is the logical combination of a single action during test
     * execution.  It is designed to hold multiple (or none) buffers of
     * output from test execution, such as stdout and stderr.  In addition,
     * it has a "comment" field for tracking the test run itself (progress).
     * This output is identified by the MSG_SECTION_NAME identifier.
     */
00211     public class Section {
      /**
       * Query if the section is still writable or not.
       * @return true if the section is still writable, and false otherwise
       */
00216         public boolean isMutable() {
          synchronized (TestResult.this) {
            synchronized (this) {
                return (TestResult.this.isMutable() && 
                      this.result == inProgress);
            }
          }
      }

        /**
         * Find out what the result of the execution of this section was.
       * @return the result of the execution of this section
       * @see #setStatus
         */
00230         public Status getStatus() {
            return result;
        }

        /**
         * Set the result of this section.  This action makes this section
         * immutable.
         *
         * @param result The status to set as the result of this section of the test
       * @see #getStatus
         */
00241         public void setStatus(Status result) {
          synchronized (TestResult.this) {
            synchronized (this) {
                checkMutable();
                for (int i = 0; i < buffers.length; i++) {
                  OutputBuffer b = buffers[i];
                  if (b instanceof WritableOutputBuffer) {
                      WritableOutputBuffer wb = (WritableOutputBuffer)b;
                      wb.getPrintWriter().close();
                  }
                }
                if (env == null)
                  env = emptyStringArray;
                this.result = result;
                if (env == null)
                  env = emptyStringArray;
                notifyCompletedSection(this);
            }
          }
        }

      /**
       * Get the title of this section, specified when the section
       * was created.
       * @return the title of this section
       */
00267         public String getTitle() {
            return title;
        }

        /**
         * Get the appropriate to writer to access the default message field.
       * @return a Writer to access the default message field
         */
00275         public PrintWriter getMessageWriter() {
          synchronized (TestResult.this) {
            synchronized (this) {
                checkMutable();
                // if it is mutable, it must have a message stream,
                // which will be the first entry
                return buffers[0].getPrintWriter();
            }
          }
        }

        /**
         * Find out how many output buffers this section has inside it.
         *
         * @return The number of output buffers in use (>=0).
         */
00291         public synchronized int getOutputCount() {
          return buffers.length;
        }

        /**
         * Add a new output buffer to the section; get PrintWriter access to it.
         *
         * @param name The symbolic name that will identify this new stream.
         * @return A PrintWriter that gives access to the new stream.
         */
00301         public PrintWriter createOutput(String name) {
          if (name == null)
            throw new NullPointerException();

          synchronized (TestResult.this) {
            synchronized (this) {
                checkMutable();
                
                OutputBuffer b = new WritableOutputBuffer(name);
                buffers = (OutputBuffer[])(DynamicArray.append(buffers, b));
                
                notifyCreatedOutput(this, name);
                
                return b.getPrintWriter();
            }
          }
        }

        /**
       * Get the content that was written to a specified output stream.
       * @param name the name of the stream in question
         * @return All the data that was written to the specified output,
         *         or null if nothing has been written.
         */
00325         public String getOutput(String name) {
          if (name == null)
            throw new NullPointerException();

          synchronized (TestResult.this) {
            synchronized (this) {
                OutputBuffer b = findOutputBuffer(name);
                return (b == null ? null : b.getOutput());
            }
          }
        }

        /** 
         * Find out the symbolic names of all the streams in this section.  You
         * can use getOutputCount to discover the number of items in
         * this enumeration (not a thread safe activity in the strictest
         * sense of course).
         *
         * @return A list of strings which are the symbolic names of the streams in this section.
         * @see #getOutputCount
         */
00346         public synchronized String[] getOutputNames() {
            String[] names = new String[buffers.length];

            for (int i = 0; i < buffers.length; i++) {
                names[i] = buffers[i].getName();
            if (names[i] == null)
                throw new IllegalStateException("BUFFER IS BROKEN");
          }

            return names;
        }

        // ---------- PACKAGE PRIVATE ----------

        Section(String title) {
          if (title == null)
            throw new NullPointerException();
          if (title.indexOf(' ') != -1)
            throw new IllegalArgumentException("space invalid in section title");
      
          this.title = title;
            result = inProgress;
        }

        /**
         * Could be used to reconstruct the section from a stream.
         * Reads from the souce until it finds a section header.  This is a JTR
         * version 2 method, don't use it for version 1 files.  The object
         * immediately immutable upon return from this constructor.
         *
         * @throws ReloadFault Probably an error while parsing the input stream.
         */
00378         Section(BufferedReader in) throws IOException, ReloadFault {
            String line = in.readLine();
            // find top of section and process it
            while (line != null) {
                if (line.startsWith(JTR_V2_SECTION)) {
                    title = extractSlice(line, 0, ":", null);
                    break;
                }
            else 
                    // don't know what this line is, may be empty
                    line = in.readLine();
            }

            if (title == null)
                throw new ReloadFault(i18n, "rslt.noSectionTitle");

            if (title.equals(MSG_SECTION_NAME)) {
            // use standard internal copy of string
                title = MSG_SECTION_NAME;
            }

          while ((line = in.readLine()).startsWith(JTR_V2_SECTSTREAM)) {
            OutputBuffer b = new FixedOutputBuffer(line, in);
            buffers = (OutputBuffer[])(DynamicArray.append(buffers, b));
          }

            // if not in the message section, line should have the section result
            if (title != MSG_SECTION_NAME) {
            if (line != null) {
                if (line.startsWith(JTR_V2_SECTRESULT))
                  result = Status.parse(line.substring(JTR_V2_SECTRESULT.length()));
                else 
                  throw new ReloadFault(i18n, "rslt.badLine", line);
            }
            if (result == null)
                // no test result
                throw new ReloadFault(i18n, "rslt.noSectionResult");
          }
        }

        void save(Writer out) throws IOException {
            out.write(JTR_V2_SECTION + getTitle());
            out.write(lineSeparator);

            for (int index = 0; index < buffers.length; index++) {
            String text = buffers[index].getOutput();
            int numLines = 0;
            int numBackslashes = 0;
            int numNonASCII = 0;
            boolean needsFinalNewline = false;
            boolean needsEscape;

            // scan for newlines and characters requiring escapes
            for (int i = 0; i < text.length(); i++) {
                char c = text.charAt(i);
                if (c < 32) {
                  if (c == '\n')
                      numLines++;
                  else if (c != '\t' && c != '\r')
                      numNonASCII++;            
                }
                else if (c < 127) {
                  if (c == '\\')
                      numBackslashes++;
                }
                else
                  numNonASCII++;
            }

            needsEscape = (numBackslashes > 0 || numNonASCII > 0);

            // Check the text ends with a final newline ('\n', not line.separator)
            // Note this must match the check when reading the text back in,
            // when we also check for just '\n' and not line.separator, because
            // line.separator now, and line.separator then, might be different.
            if (text.length() != 0 && !text.endsWith("\n")) {
                needsFinalNewline = true;
                numLines++;
            }

            out.write(JTR_V2_SECTSTREAM);
            out.write(buffers[index].getName());
            out.write(":");
              out.write('(');
            out.write(String.valueOf(numLines));
            out.write('/');
            if (needsEscape) {
                // count one per character, plus an additional one per \ (written as "\ \") and an
                // additional 5 per nonASCII (written as "\ u x x x x") 
                out.write(String.valueOf(text.length() + numBackslashes + 5*numNonASCII));
            }
            else
                out.write(String.valueOf(text.length()));
            out.write(')');
            if (needsEscape)
                out.write('*');
            out.write(JTR_V2_SECTSTREAM);
              out.write(lineSeparator);
                
            if (needsEscape) {
                for (int i = 0; i < text.length(); i++) {
                  char c = text.charAt(i);
                  if (32 <= c && c < 127 && c != '\\') 
                      out.write(c);
                  else {
                      switch (c) {
                      case '\n': case '\r': case '\t':
                        out.write(c);
                        break;
                      case '\\':
                        out.write("\\\\");
                        break;
                      default:
                        out.write("\\u");
                        out.write(Character.forDigit((c >> 12) & 0xF, 16));
                        out.write(Character.forDigit((c >>  8) & 0xF, 16));
                        out.write(Character.forDigit((c >>  4) & 0xF, 16));
                        out.write(Character.forDigit((c >>  0) & 0xF, 16));
                        break;
                      }
                  }
                }
            }
            else
                out.write(text);

                if (needsFinalNewline)
                    out.write(lineSeparator);
            }  

            // the default message section does not need a result line
            if (getTitle() != MSG_SECTION_NAME) {
                out.write(JTR_V2_SECTRESULT + result.toString());
                out.write(lineSeparator);
            }

            out.write(lineSeparator);
        }

        /**
         * Reload an output block. This method is called while reloading
       * a test result and so bypasses the normal immutability checks.
         */
00521         synchronized void reloadOutput(String name, String data) {
            if (name.equals(MESSAGE_OUTPUT_NAME))
                name = MESSAGE_OUTPUT_NAME;
          OutputBuffer b = new FixedOutputBuffer(name, data);
            buffers = (OutputBuffer[])(DynamicArray.append(buffers, b));
        }

      /**
       * Reload the status. This method is called while reloading
       * a test result and so bypasses the normal immutability checks.
       */
00532       synchronized void reloadStatus(Status s) {
          result = s;
      }

        // ---------- PRIVATE ----------

      private void checkMutable() {
          if (!isMutable())
            throw new IllegalStateException("This section of the test result is now immutable.");
      }

      private synchronized void makeOutputImmutable(OutputBuffer b, String name, String output) {
          for (int i = 0; i < buffers.length; i++) {
            if (buffers[i] == b) {
                buffers[i] = new FixedOutputBuffer(name, output);
                return;
            }
          }
      }

        private synchronized OutputBuffer findOutputBuffer(String name) {
            // search backwards
            // may help in some backward compatibility cases since the most
            // recent stream with that name will be found
            // performance of the search will still be constant
            for (int i = buffers.length-1; i >= 0 ; i--) {
                if (name.equals(buffers[i].getName())) 
                    return buffers[i];
            }  

            return null;
        }

        private OutputBuffer[] buffers = new OutputBuffer[0];
        private String title;
        private Status result;
      
      private class FixedOutputBuffer implements OutputBuffer {
          FixedOutputBuffer(String name, String output) {
            if (name == null || output == null)
                throw new NullPointerException();

            this.name = name;
            this.output = output;
          }
          
          public String getName() {
            return name;
          }
          
          public String getOutput() {
            return output;
          }
          
          public PrintWriter getPrintWriter() {
            throw new IllegalStateException("This section is immutable");
          }

          FixedOutputBuffer(String header, BufferedReader in) throws ReloadFault {
            String nm = extractSlice(header, JTR_V2_SECTSTREAM.length(), null, ":");
                if (nm == null)
                    throw new ReloadFault(i18n, "rslt.noOutputTitle");
            
                if (nm.equals(MESSAGE_OUTPUT_NAME ))
                    nm = MESSAGE_OUTPUT_NAME;
            
            try {
                int lines;
                int chars;
                boolean needsEscape;

                try {
                  int start = JTR_V2_SECTSTREAM.length();
                  lines = Integer.parseInt(extractSlice(header, start, "(", "/"));
                  chars = Integer.parseInt(extractSlice(header, start, "/", ")"));
                  int rp = header.indexOf(")", start);
                  if (rp >= 0 && rp < header.length() - 2)
                      needsEscape = (header.charAt(rp + 1) == '*');
                  else
                      needsEscape = false;
                } 
                catch (NumberFormatException e) {
                        // fatal parsing error
                        throw new ReloadFault(i18n, "rslt.badHeaderVersion", e);
                }
                
                StringBuffer buff = new StringBuffer(chars);
                
                if (needsEscape) {
                  for (int i = 0; i < chars; i++) {
                      int c = in.read();
                      if (c == -1)
                        throw new ReloadFault(i18n, "rslt.badEOF");
                      else if (c == '\\') {
                        c = in.read();
                        i++;
                        if (c == 'u') {
                            c =  Character.digit((char)in.read(), 16) << 12;
                            c += Character.digit((char)in.read(), 16) <<  8;
                            c += Character.digit((char)in.read(), 16) <<  4;
                            c += Character.digit((char)in.read(), 16);
                            i += 4;
                        }
                        // else drop through (for \\)
                      }
                      buff.append((char)c);
                  }
                }
                else {
                  char[] data = new char[Math.min(4096, chars)];
                  int charsRead = 0;
                  while (charsRead < chars) {
                      int n = in.read(data, 0, Math.min(data.length, chars-charsRead));
                      buff.append(data, 0, n);
                      charsRead += n;
                  }
                }
                
                /*NEW
                while (true) {
                  int c = in.read();
                  switch (c) {
                  case -1:
                      throw new ReloadFault(i18n, "rslt.badEOF");

                  case '\\':
                      if (needEscape) {
                        c = in.read();
                        if (c == 'u') {
                            c =  Character.digit((char)in.read(), 16) << 12;
                            c += Character.digit((char)in.read(), 16) <<  8;
                            c += Character.digit((char)in.read(), 16) <<  4;
                            c += Character.digit((char)in.read(), 16);
                        }
                        // else drop through (for \\)
                      }
                      buff.append((char)c);
                  }
                }
                */

                name = nm;
                output = buff.toString();

                if (buff.length() > 0 && buff.charAt(buff.length() - 1) != '\n') {
                  int c = in.read();
                  if (c == '\r')
                      c = in.read();
                  if (c != '\n') {
                      System.err.println("TR.badChars: output=" + (output.length() < 32 ? output : output.substring(0, 9) + " ... " + output.substring(output.length() - 10) ));
                      System.err.println("TR.badChars: '" + ((char)c) + "' (" + c + ")");
                      throw new ReloadFault(i18n, "rslt.badChars", name);
                  }
                }
            } 
            catch (IOException e) {
                // not enough data probably fatal parsing error
                throw new ReloadFault(i18n, "rslt.badFile", e);
            }
          }
          
          private final String name;
          private final String output;
      }

      private class WritableOutputBuffer extends Writer implements OutputBuffer {
          WritableOutputBuffer(String name) {
                super(TestResult.this);
            if (name == null)
                throw new NullPointerException();

            this.name = name;
            output = new StringBuffer();
                pw = new LockedWriter(this, TestResult.this);
          }
          
          public String getName() {
            return name;
          }
          
          public String getOutput() {
            return new String(output);
          }
          
          public PrintWriter getPrintWriter() {
            return pw;
          }

          public void write(char[] buf, int offset, int len) throws IOException {
            if (output == null) 
                throw new IOException("stream has been closed");

            int end = output.length();
            output.append(buf, offset, len);
            // want to avoid creating the string buf(offset..len) 
            // since likely case is no observers
            notifyUpdatedOutput(Section.this, name, end, end, buf, offset, len);

            if (output.length() > maxOutputSize) {
                int overflowEnd = maxOutputSize*2/3;
                if (overflowed) {
                  // output.delete(overflowStart, overflowEnd); 
                  // JDK 1.1--start
                  String s = output.toString();
                  output = new StringBuffer(s.substring(0, overflowStart) + s.substring(overflowEnd));
                  // JDK 1.1--end
                  notifyUpdatedOutput(Section.this, name, overflowStart, overflowEnd, "");
                }
                else {
                  String OVERFLOW_MESSAGE = 
                      "\n\n...\n"
                      + "Output overflow:\n" 
                      + "JT Harness has limited the test output to the text to that\n" 
                      + "at the beginning and the end, so that you can see how the\n"
                      + "test began, and how it completed.\n"
                      + "\n"
                      + "If you need to see more of the output from the test,\n"
                      + "set the system property javatest.maxOutputSize to a higher\n"
                      + "value. The current value is " + maxOutputSize 
                      + "\n...\n\n";
                  overflowStart = maxOutputSize/3;
                  //output.replace(overflowStart, maxOutputSize*2/3, OVERFLOW_MESSAGE);
                  // JDK 1.1--start
                  String s = output.toString();
                  output = new StringBuffer(s.substring(0, overflowStart) + OVERFLOW_MESSAGE + s.substring(overflowEnd));
                  // JDK 1.1--end
                  notifyUpdatedOutput(Section.this, name, overflowStart, overflowEnd, OVERFLOW_MESSAGE);
                  overflowStart += OVERFLOW_MESSAGE.length();
                  overflowed = true;
                }
            }
          }

          public void flush() {
            //no-op
          }

          public void close() {
            makeOutputImmutable(this, name, new String(output));
            notifyCompletedOutput(Section.this, name);
          }
          
          private boolean overflowed;
          private int overflowStart;
          private final String name;
          private /*final*/ StringBuffer output; // can't easily be final in JDK 1.1 because need to reassign to it
          private final PrintWriter pw;
      }
    }

    private class LockedWriter extends PrintWriter {
        public LockedWriter(Writer out, Object theLock) {
            super(out);
            lock = theLock;
        }
    }

    // Conceptually, this belongs in Section, but that is not legal Java.
    // (It is accepted in  1.1.x; rejected by 1.2)
    private interface OutputBuffer {
      String getName();
      String getOutput();
      PrintWriter getPrintWriter();
    }




    // ------------------------- PUBLIC CONSTRUCTORS -------------------------
    /**
     * Construct a test result object that will be built as the test runs.
     * The status string will be "running..." rather than "not run".
     *
     * @param td The test description to base this new object on.  Cannot be
     *        null.
     */
00808     public TestResult(TestDescription td) {
        desc = td;
        execStatus = inProgress;
      testURL = desc.getRootRelativeURL();

        createSection(MSG_SECTION_NAME);

        props = emptyStringArray;  // null implies it was discarded, not empty
    }


    /**
     * Reconstruct the results of a previously run test.
     *
     * @param workDir Work directory in which the tests were run
     * @param td      Description of the test that was run 
     * @throws TestResult.Fault if there is a problem recreating the results
     *                  from the appropriate file in the work directory
     */
00827     public TestResult(TestDescription td, WorkDirectory workDir) 
                      throws Fault {
      desc = td;
      testURL = desc.getRootRelativeURL();
      execStatus = inProgress;

      reloadFromWorkDir(workDir);
    }

    /**
     * Reconstruct the results of a previously run test.
     *
     * @param file File that the results have been stored into.
     * @throws     TestResult.ReloadFault if there is a problem recreating the results
     *                  from the given file
     * @throws     TestResult.ResultFileNotFoundFault if there is a problem locating
     *                  the given file
     */
00845     public TestResult(File file) 
      throws ResultFileNotFoundFault, ReloadFault 
    {
      resultsFile = file;
      reload();

      testURL = desc.getRootRelativeURL();

      execStatus = Status.parse(PropertyArray.get(props, EXEC_STATUS));
    }

    /**
     * Reconstruct the results of a previously run test.
     *
     * @param workDir The work directory where previous results for the guven
     *        test can be found.
     * @param workRelativePath The path to the JTR to reload, relative to the
     *        workdir.  
     * @throws TestResult.Fault if there is a problem recreating the results
     *              from the given file
     */
00866     public TestResult(WorkDirectory workDir, String workRelativePath) throws Fault {
      //resultsFile = workDir.getFile(workRelativePath.replace('/', File.separatorChar));
      resultsFile = workDir.getFile(workRelativePath);
      reload();

      testURL = desc.getRootRelativeURL();
      execStatus = Status.parse(PropertyArray.get(props, EXEC_STATUS));
    }

    /**
     * Create a temporary test result for which can be handed around
     * in situations where a reasonable test result can't be created.
     *
     * @param td     Description of the test
     * @param s      Status to associate with running the test... presumed
     *               to be of the Status.FAILED type.
     */
00883     public TestResult(TestDescription td, Status s) {
      desc = td;
      testURL = desc.getRootRelativeURL();
      resultsFile = null;
      execStatus = s;
        props = emptyStringArray;
    }

    /**
     * Create a placeholder TestResult for a test that has not yet been run.
     *
     * @param td     The test description for the test
     * @return         A test result that indicates that the test has not yet been run
     */
00897     public static TestResult notRun(TestDescription td) {
      return new TestResult(td, notRunStatus);
    }

    //------------------------ MODIFIER METHODS ------------------------------

    /** 
     * Create a new section inside this test result.
     *
     * @param name The symbolic name for this new section.
     * @return The new section that was created.
     */
00909     public synchronized TestResult.Section createSection(String name) {
        if (!isMutable()) {
            throw new IllegalStateException(
                        "This TestResult is no longer mutable!");
        }

        Section section = new Section(name);
        sections = (Section[])(DynamicArray.append(sections, section));
        notifyCreatedSection(section);
      // avoid creating output (which will cause observer messages)
      // before the createdSection has been notified
      section.createOutput(TestResult.MESSAGE_OUTPUT_NAME);

        return section;
    }
     

    /**
     * Set the environment used by this test. When the test is run,
     * those entries in the environment that are referenced are noted;
     * those entries will be recorded here in the test result object.
     * @param environment the test environment used by this test.
     * @see #getEnvironment
     */
00933     public synchronized void setEnvironment(TestEnvironment environment) {
        if (!isMutable()) {
            throw new IllegalStateException(
                        "This TestResult is no longer mutable!");
        }
      for (Iterator i = environment.elementsUsed().iterator(); i.hasNext(); ) {
          TestEnvironment.Element elem = (TestEnvironment.Element) (i.next());
          // this is stunningly inefficient and should be fixed
          env = PropertyArray.put(env, elem.getKey(), elem.getValue());
      }
    }

    /**
     * Set the result of this test.  This action makes this object immutable.
     * If a result comparison is needed, it will be done in here.
     * @param stat A status object representing the outcome of the test
     * @see #getStatus
     */
00951     public synchronized void setStatus(Status stat) {
        if (!isMutable()) {
            throw new IllegalStateException(
                        "This TestResult is no longer mutable!");
        }

        if (stat == null) {
            throw new IllegalArgumentException(
                        "TestResult status cannot be set to null!");
        }

      // close out message section
      sections[0].setStatus(null);

        execStatus = stat;

      if (execStatus == inProgress)
          execStatus = interrupted;
      
      // verify integrity of status in all sections
      for (int i = 0; i < sections.length; i++) {
          if (sections[i].isMutable()) {
            sections[i].setStatus(incomplete);
          }
      }
      
      props = PropertyArray.put(props, SECTIONS,
                          StringArray.join(getSectionTitles()));
      props = PropertyArray.put(props, EXEC_STATUS,
                          execStatus.toString());

      // end time now required
      // mainly for writing in the TRC for the Last Run Filter
      if (PropertyArray.get(props, END) == null) {
          props = PropertyArray.put(props, END,
                        (new Date()).toString());
      }

      // this object is now immutable
      notifyCompleted();
    }

    /**
     * Add a new property value to this TestResult.
     *
     * @param name The name of the property to be updated.
     * @param value The new value of the specified property.
     */
00999     public synchronized void putProperty(String name, String value) {
        // check mutability
        if (!isMutable()) {
            throw new IllegalStateException(
                "Cannot put property, the TestResult is no longer mutable!");
        }

        props = PropertyArray.put(props, name, value);
      notifyUpdatedProperty(name, value);
    }

    /**
     * Reconstruct the results of a previously run test.
     *
     * @param workDir Work directory in which the tests were run
     * @throws TestResult.Fault if an error occurs while reloading the results
     */
01016     public void reloadFromWorkDir(WorkDirectory workDir) throws Fault {
        // check mutability
        if (!isMutable()) {
            throw new IllegalStateException(
                "Cannot reload results, the TestResult is no longer mutable!");
        }


      try {
          resultsFile = workDir.getFile(getWorkRelativePath());
          props = null;
          sections = null;
          execStatus = null;

          reload(new FileReader(resultsFile));

          // this next line is dubious since the execStatus should have
          // been set during the reload
          execStatus = Status.parse(PropertyArray.get(props, EXEC_STATUS));
      }
      catch (FileNotFoundException e) {
          props = emptyStringArray;
          env = emptyStringArray;
          sections = emptySectionArray;
          execStatus = Status.notRun("no test result file found");
      }
      catch (IOException e) {
          props = emptyStringArray;
          env = emptyStringArray;
          sections = emptySectionArray;
          execStatus = Status.error("error opening result file: " + e);
          throw new Fault(i18n, "rslt.badFile", e.toString());
      }
      catch (Fault f) {
          props = emptyStringArray;
          env = emptyStringArray;
          sections = emptySectionArray;
          execStatus = Status.error(f.getMessage());
          throw f;
      }
          
    }

    //----------ACCESS FUNCTIONS (MISC)-----------------------------------------


    /**
     * A code indicating that no checksum was found in a .jtr file.
     * @see #getChecksumState
     */
01066     public static final int NO_CHECKSUM = 0;

    /**
     * A code indicating that an invalid checksum was found in a .jtr file.
     * @see #getChecksumState
     */
01072     public static final int BAD_CHECKSUM = 1;

    /**
     * A code indicating that a good checksum was found in a .jtr file.
     * @see #getChecksumState
     */
01078     public static final int GOOD_CHECKSUM = 2;

    /**
     * The number of different checksum states (none, good, bad).
     */
01083     public static final int NUM_CHECKSUM_STATES = 3;


    /**
     * Get info about the checksum in this object.
     * @return a value indicating the validity or otherwise of the checksum
     * found while reading this result object.
     * @see #NO_CHECKSUM
     * @see #BAD_CHECKSUM
     * @see #GOOD_CHECKSUM
     */
01094     public byte getChecksumState() {
      return checksumState;
    }

    /**
     * A way to write comments about the test execution into the results.
     *
     * @return If this is null, then the object is in a state in which it
     *         does not accept new messages.
     */
01104     public PrintWriter getTestCommentWriter() {
        return sections[0].getMessageWriter();
    }

    /** 
     * Get the test name, as given by the test URL defined by
     * TestDescription.getRootRelativeURL().  This method <em>always</em>
     * returns a useful string, representing the test name.
     *
     * @return the name of the test for which this is the result object
     * @see TestDescription#getRootRelativeURL
     */
01116     public String getTestName() {
      return testURL;
    }

    /** 
     * Check whether this test result can be reloaded from a file.
     * This method does not validate the contents of the file.
     * @return true if the result file for this object can be read
     */
01125     public boolean isReloadable() {
      return (resultsFile != null && resultsFile.canRead());
    }

    /**
     * Check whether this object has been "shrunk" to reduce its
     * memory footprint. If it has, some or all of the data will have 
     * to be reloaded.  This method is somewhat
     * orthogonal to <code>isReloadable()</code> and should not be used as a
     * substitute.
     *
     * @return True if this object is currently incomplete, false otherwise.
     * @see #isReloadable
     */
01139     public boolean isShrunk() {
      if (!isMutable() &&
          (desc == null ||
           props == null ||
           env == null ||
             (sections == null && execStatus != inProgress)))
          return true;
      else
          return false;
    }

    /**
     * Get the description of the test from which this result was created.
     * Depending on how the test result was created, this information may
     * not be immediately available, and may be recreated from the test
     * result file.
     *
     * @return the test description for this test result object
     * @throws TestResult.Fault if there is a problem recreating the description
     * from the results file.
     */
01160     public synchronized TestDescription getDescription() 
            throws Fault {
      if (desc == null) {
          // reconstitute description (probably from file)
            reload();
      }
      return desc;
    }

    /*
     * Get the title of this test. This info originally comes from the test
     * description, but is saved in the .jtr file as well.
     *
     * @deprecated Please query the test description for info.
    public String getTitle() {
      // hmm slight copout; would like to make sure never null in the first place
      String title = desc.getParameter("title");
      if (title == null)
          title = td.getRootRelativeURL();
    }
     */

    /**
     * Get the path name for the results file for this test, relative to the 
     * work directory.  The internal separator is '/'.
     * @return the path name for the results file for this test, 
     * relative to the work directory
     */
01188     public String getWorkRelativePath() {
      return getWorkRelativePath(testURL);
    }

    /**
     * Get the name, if any, for the result file for this object.
     * The path information contains platform specific path separators.
     * @return the name, if any, for the result file for this object
     */
01197     public File getFile() {
      return resultsFile;
    }

    public void resetFile() {
        resultsFile = null;
    }

    /**
     * Get the path name for the results file for a test, relative to the 
     * work directory.  The internal separator is '/'.
     * @param td the test description for the test in question
     * @return the path name for the results file for a test, relative to the 
     * work directory
     */
01212     public static String getWorkRelativePath(TestDescription td) {
      String baseURL = td.getRootRelativePath();
      
      // add in uniquifying id if 
      String id = td.getParameter("id");
      return getWorkRelativePath(baseURL, id);
    }

    /**
     * Get the path name for the results file for a test, relative to the 
     * work directory.  The internal separator is '/'.
     *
     * @param testURL May not be null;
     * @return The work relative path of the JTR for this test.  Null if the
     *         given URL is null.
     */
01228     public static String getWorkRelativePath(String testURL) {
      int pound = testURL.lastIndexOf("#");
      if (pound == -1)  // no test id
          return getWorkRelativePath(testURL, null);
      else
          return getWorkRelativePath(testURL.substring(0, pound),
                               testURL.substring(pound + 1));
    }

    /**
     * Get the path name for the results file for a test, relative to the 
     * work directory.  The internal separator is '/'.
     *
     * @param baseURL May not be null;
     * @param testId The test identifier that goes with the URL.  This may be null.
     * @return The work relative path of the JTR for this test.  Null if the
     *         given URL is null.
     */
01246     public static String getWorkRelativePath(String baseURL, String testId) {
      StringBuffer sb = new StringBuffer(baseURL);

      // strip off extension
    stripExtn:
      for (int i = sb.length() - 1; i >= 0; i--) {
          switch (sb.charAt(i)) {
          case '.':
            sb.setLength(i);
            break stripExtn;
          case '/':
            break stripExtn;
          }
      }

      // add in uniquifying id if 
      if (testId != null) {
          sb.append('_');
          sb.append(testId);
      }

      sb.append(EXTN);

      return sb.toString();
    }

    /**
     * Get the keys of the properties that this object has stored.
     * @return the keys of the properties that this object has stored
     */
01276     public synchronized Enumeration getPropertyNames() {
      return PropertyArray.enumerate(props);
    }

    /**
     * Get the value of a property of this test result.
     *
     * @param name The name of the property to be retrieved.
     * @return The value corresponding to the property name, null if not
     *          found.
     * @throws TestResult.Fault if there is a problem 
     *            recreating data from the results file.
     */
01289     public synchronized String getProperty(String name)
            throws Fault {
        if (props == null) {
          // reconstitute properties 
            // this may result in a Fault, which is okay
          reload();
        }

        return PropertyArray.get(props, name);
    }

    /**
     * Get a copy of the environment that this object has stored.
     * @return a copy of the environment that this object has stored
     * @throws TestResult.Fault if there is a problem 
     *            recreating data from the results file.
     * @see #setEnvironment
     */
01307     public synchronized Map getEnvironment() throws Fault {
        if (env == null) {
          // reconstitute environment 
            // this may result in a Fault, which is okay
          reload();
        }
      return PropertyArray.getProperties(env);
    }

    /**
     * Get the parent node in the test result table that
     * contains this test result object.
     * @return the parent node in the test result table that
     * contains this test result object.
     */
01322     public TestResultTable.TreeNode getParent() {
        return parent;
    }


    /**
     * Set the parent node in the test result table that
     * contains this test result object.
     * @param p the parent node in the test result table that
     * contains this test result object.
     * @see #getParent
     */
01334     void setParent(TestResultTable.TreeNode p) {
        parent = p;
    }

    //----------ACCESS FUNCTIONS (TEST STATUS)----------------------------------

    /**
     * Determine if the test result object is still mutable.
     * Test results are only mutable while they are being created, up to
     * the point that the final status is set.
     * @return true if the test result object is still mutable,
     * and false otherwise
     */
01347     public synchronized boolean isMutable() {
        // must be mutable during reload (internal operation)
      // mutable as long as possible, to allow max time for writing log messages
        return (execStatus == inProgress);
    }


    /**
     * Get the status for this test.
     * @return the status for this test
     * @see #setStatus
     */
01359     public synchronized Status getStatus() {
      return execStatus;
    }

    //----------ACCESS METHODS (TEST OUTPUT)----------------------------------

    /**
     * Find out how many sections this test result contains.
     *
     * @return The number of sections in this result.
     */
01370     public synchronized int getSectionCount() {
        if (sections != null) {
            return sections.length;
        }
      else if (PropertyArray.get(props, SECTIONS) != null) {
            return StringArray.split(PropertyArray.get(props, SECTIONS)).length;
        }
      else {
            // hum, test props are never discarded, so we have no sections
            return 0;
        }
    }

    /**
     * Get the section specified by index.
     * Remember that index 0 is the default message section.
     *
     * @param index The index of the section to be retrieved.
     * @return The requested section.  Will be null if the section does not exist.
     * @throws TestResult.ReloadFault Will occur if an error is encountered when reloading
     *         JTR data.  This may be the result of a corrupt or missing JTR file.
     * @see #MSG_SECTION_NAME
     */
01393     public synchronized Section getSection(int index) throws ReloadFault {
        Section target;

        if (sections == null && execStatus != inProgress) {
            // try to reload from file
            try {
                reload();
            } 
          catch (ReloadFault f) {
            throw f;
            }
          catch (Fault f) {
            throw new ReloadFault(i18n, "rslt.badFile",  f.getMessage());
            }
        }

        if (index >= sections.length) {
            target = null;
        }
      else {
            target = sections[index];
        }

        return target;
    }

    /**
     * Get the titles of all sections in this test result.
     * A null result probably indicates that there are no sections.  This is
     * improbable since most test result object automatically have one section.
     * 
     * @return The titles, one at a time in the array.  Null if the titles
     *          do not exist or could not be determined.
     */
01427     public synchronized String[] getSectionTitles() {
      if (props == null) {
          try {
            reload();
          }
          catch (Fault f) {
            // should this maybe be a JavaTestError?
            return null;
          }
      }

        // look for what we need from easiest to hardest source
        String names = PropertyArray.get(props, SECTIONS);

        if (names != null) {
            // it is cached
            return StringArray.split(names);
        }
      else if (sections != null) {
            // TR is not immutable yet, probably
            int numSections = getSectionCount();
            String[] data = new String[numSections];

            for (int i = 0; i < numSections; i++) {
                data[i] = sections[i].getTitle();
            }

            return data;
        }
      else {
            // hum, bad.  No sections exist and this data isn't cached
            // the test probably has not run
            return null;
        }
    }
 
    /**
     * Check if this file is or appears to be a result (.jtr) file,
     * according to its filename extension.
     * @param f the file to be checked
     * @return true if this file is or appears to be a result (.jtr) file.
     */
01469     public static boolean isResultFile(File f) {
      String p = f.getPath();
      return (p.endsWith(EXTN));
    }

    /**
     * Writes the TestResult into a version 2 jtr file.
     *
     * @param workDir The work directory in which to write the results
     * @param backupPolicy a policy object defining what to do if a file
     * already exists with the same name as that which is about to be written.
     * @throws IllegalStateException This will occur if you attempt to write a result
     *         which is still mutable.
     * @throws IOException Occurs when the output file cannot be created or written to.
     *         Under this condition, this object will change it status to an error.
     */
01485     public synchronized void writeResults(WorkDirectory workDir, BackupPolicy backupPolicy)
            throws IOException 
    {
        if (isMutable()) 
            throw new IllegalStateException("This TestResult is still mutable - set the status!");
       
        // could attempt a reload() I suppose
        if (props == null)
          props = emptyStringArray;

      String wrp = getWorkRelativePath(desc).replace('/', File.separatorChar);
      resultsFile = workDir.getFile(wrp);

      File resultsDir = resultsFile.getParentFile();
      resultsDir.mkdirs(); // ensure directory created for .jtr file

      File tempFile = createTempFile(workDir, backupPolicy);
      try {
          writeResults(tempFile, backupPolicy);
      }
      finally {
          if (tempFile.exists())
            tempFile.delete();
      } 
    }

    /**
     * Create a temporary file to which the results can be written, before being renamed
     * to its real name.
     */
    // don't use File.createTempFile because of issues with the internal locking there
01516     private File createTempFile(WorkDirectory workDir, BackupPolicy backupPolicy)
      throws IOException 
    {
      final int MAX_TRIES = 100; // absurdly big limit, but a limit nonetheless
      for (int i = 0; i < MAX_TRIES; i++) {
          File tempFile = new File(resultsFile.getPath() + "." + i + ".tmp");
          if (tempFile.createNewFile())
            return tempFile;
      }
      throw new IOException("could not create temp file for " + resultsFile + ": too many tries");
    }

    /**
     * Write the results to a temporary file, and when done, rename it to resultsFile
     */
01531     private void writeResults(File tempFile, BackupPolicy backupPolicy) 
      throws IOException 
    {
      FileWriter out;
      try {
          out = new FileWriter(tempFile);
      }
      catch (IOException e) {
          execStatus = Status.error("Problem writing result file for test: " + getTestName());
          resultsFile = null; // file not successfully written after all
          throw e;
      }

      try {
            // redundant, is done in setResult
            // needed though if setResult isn't being called
          props = PropertyArray.put(props, EXEC_STATUS, execStatus.toString());

            // file header
            out.write(JTR_V2_HEADER);
          out.write(lineSeparator);

            // date and time
            out.write("#" + (new Date()).toString());
          out.write(lineSeparator);

            // checksum header and data
            out.write(JTR_V2_CHECKSUM);
          out.write(Long.toHexString(computeChecksum()));
          out.write(lineSeparator);

          if (debug) {  // debugging code
            out.write("# debug: test desc checksum: ");
            out.write(Long.toHexString(computeChecksum(desc)));
            out.write(lineSeparator);

            for (Iterator iter = desc.getParameterKeys(); iter.hasNext(); ) {
                // don't rely on enumeration in a particular order
                // so simply add the checksum products together
                String KEY = (String) (iter.next());
                out.write("# debug: test desc checksum key " + KEY + ": ");
                out.write(Long.toHexString(computeChecksum(KEY) * computeChecksum(desc.getParameter(KEY))));
                out.write(lineSeparator);
            }

            out.write("# debug: test env checksum: ");
            if (env == null)
                out.write("null");
            else
                out.write(Long.toHexString(computeChecksum(env)));
            out.write(lineSeparator);

            out.write("# debug: test props checksum: ");
            out.write(Long.toHexString(computeChecksum(props)));
            out.write(lineSeparator);

            out.write("# debug: test sections checksum: ");
            out.write(Long.toHexString(computeChecksum(sections)));
            out.write(lineSeparator);

            for (int I = 0; I < sections.length; I++) {
                out.write("# debug: test section[" + I + "] checksum: ");
                out.write(Long.toHexString(computeChecksum(sections[I])));
                out.write(lineSeparator);

                String[] NAMES = sections[I].getOutputNames();
                for (int J = 0; J < NAMES.length; J++) {
                  out.write("# debug: test section[" + I + "] name=" + NAMES[J] + " checksum: ");
                  out.write(Long.toHexString(computeChecksum(NAMES[J])));
                  out.write(lineSeparator);

                  out.write("# debug: test section[" + I + "] name=" + NAMES[J] + " output checksum: ");
                  out.write(Long.toHexString(computeChecksum(sections[I].getOutput(NAMES[J]))));
                  out.write(lineSeparator);
                }
            }
          }

            // description header and data
            out.write(JTR_V2_TESTDESC);
          out.write(lineSeparator);

            Properties tdProps = new Properties();
            desc.save(tdProps);
            PropertyArray.save(PropertyArray.getArray(tdProps), out);
          out.write(lineSeparator);

            // test environment header and data
          if (env != null) {
            out.write(JTR_V2_ENVIRONMENT);
            out.write(lineSeparator);
            PropertyArray.save(env, out);
            out.write(lineSeparator);
          }

            // test result props header and data
            out.write(JTR_V2_RESPROPS);
          out.write(lineSeparator);
          PropertyArray.save(props, out);
          out.write(lineSeparator);

            // get sections into memory
            // I hope the out stream is not the same as the resultFile!
            if (sections == null) {
            throw new JavaTestError("Cannot write test result - it contains no sections.");
            }

            StringBuffer buffer = new StringBuffer();

            for (int i = 0; i < sections.length; i++) {
                sections[i].save(out);
            }

          out.write(lineSeparator);
            out.write(JTR_V2_TSTRESULT);
            out.write(execStatus.toString());
          out.write(lineSeparator);
          out.close();
      }   // try
      catch (IOException e) {
          // This exception could be raised when trying to create the directory
          // for the test results; opening the results file, or closing it.
          execStatus = Status.error("Write to temp. JTR file failed (old JTR intact): " +
                              tempFile.getPath());
          resultsFile = null; // file not successfully written after all
          throw e;
      }   // catch

      try {
          backupPolicy.backupAndRename(tempFile, resultsFile);

          // now that it has been successfully written out, make the object
          // a candidate for shrinking
          addToShrinkList();
      }   // try
      catch (IOException e) {
          // This exception could be raised when trying to create the directory
          // for the test results; opening the results file, or closing it.
          execStatus = Status.error("Problem writing result file: " +
                              resultsFile.getPath());
          resultsFile = null; // file not successfully written after all
          throw e;
      }   // catch
    }

    // -----observer methods ---------------------------------------------------
    /**
     * Add an observer to watch this test result for changes.
     * @param obs the observer to be added
     */
01681     public synchronized void addObserver(Observer obs) {
      if (isMutable()) {
          Observer[] observers = (Observer[])observersTable.get(this);

            if (observers == null) observers = new Observer[0];

          observers = (Observer[])(DynamicArray.append(observers, obs));
          observersTable.put(this, observers);
      }
    }

    /**
     * Remove an observer that was previously added.
     * @param obs the observer to be removed
     */
01696     public synchronized void removeObserver(Observer obs) {
      Observer[] observers = (Observer[])observersTable.get(this);
      if (observers == null)
          return;

        observers = (Observer[])(DynamicArray.remove(observers, obs));
      if (observers == null)
          observersTable.remove(this);
      else
          observersTable.put(this, observers);
    }

    /** 
     * Gets the time when the test was completed, or at least the time
     * when it's final status was set.  Be aware that if the information is
     * not available in memory, it will be loaded from disk.
     *
     * @return Time when this test acquired its final status setting.
     * @see #setStatus
     * @see java.util.Date
     */
01717     public long getEndTime() {
      if (endTime < 0) {
          try {
            String datestr = PropertyArray.get(props, END);

            if (datestr == null) {
                // this may be more expensive because it can cause a
                // reload from disk
                try {
                  datestr = getProperty(END);
                }
                catch (Fault f) {
                }
            }

            if (datestr != null) {
                // this is to parse Date.toString() output
                DateFormat df = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy",
                                         Locale.US);
                Date date = df.parse(datestr);
                endTime = date.getTime();
            }
            else {
                // info not available
            }
          }
          catch (ParseException e) {
          }
      }

      return endTime;
    }

    // ----- PACKAGE METHODS ---------------------------------------------------

    /**
     * Read a single minimal TestResult from a .jts stream.
     * The stream is not closed.
     * @deprecated JTS files are no longer supported
    TestResult(WorkDirectory workDir, DataInputStream in) throws IOException {
      workRelativePath = in.readUTF();

        // ** temp. fix ** XXX
        // make sure the path is in URL form with forward slashes
        // in the future all paths should already be of this form (TestDescription)
        int index = workRelativePath.indexOf('/');
        if (index == -1) workRelativePath = workRelativePath.replace('\\', '/');

      resultsFile = workDir.getFile(workRelativePath.replace('/', File.separatorChar));
      title = in.readUTF();
      int esc = in.readByte();
      String esr = in.readUTF();
      execStatus = new Status(esc, esr);
      boolean defIsExec = in.readBoolean();
      if (!defIsExec) {
          // have to read these, per protocol
          int dsc = in.readByte();
          String dsr = in.readUTF();
          //ignore dsc, dsr; they used to go in defStatus
      }
    }
     */

    /**
     * Read a single minimal TestResult which is capable of relaoding itself.
     * None of the parameters may be null.
     *
     * @param url The full URL of this test, including test id.
     * @param workDir The work directory location, platform specfic path.
     * @param status The status that will be found in the JTR.
     * @throws JavaTestError Will be thrown if any params are null.
     */
01789     TestResult(String url, WorkDirectory workDir, Status status) {
      if (url == null)
          throw new JavaTestError(i18n, "rslt.badTestUrl");

      if (workDir == null)
          throw new JavaTestError(i18n, "rslt.badWorkdir");

      if (status == null)
          throw new JavaTestError(i18n, "rslt.badStatus"); 

      testURL = url;
      resultsFile = workDir.getFile(getWorkRelativePath());
      execStatus = status;
    }

    /**
     * Read a single minimal TestResult which is capable of relaoding itself.
     * None of the parameters may be null.
     *
     * @param url The full URL of this test, including test id.
     * @param workDir The work directory location, platform specfic path.
     * @param status The status that will be found in the JTR.
     * @param endTime The time when that test finished execution.
     * @throws JavaTestError Will be thrown if any params are null.
     * @see #getEndTime()
     */
01815     TestResult(String url, WorkDirectory workDir, Status status, long endTime) {
      if (url == null)
          throw new JavaTestError(i18n, "rslt.badTestUrl");

      if (workDir == null)
          throw new JavaTestError(i18n, "rslt.badWorkdir");

      if (status == null)
          throw new JavaTestError(i18n, "rslt.badStatus"); 

      testURL = url;
      resultsFile = workDir.getFile(getWorkRelativePath());
      execStatus = status;
      this.endTime = endTime;
    }

    void shareStatus(Hashtable[] tables) {
      execStatus = shareStatus(tables, execStatus);
    }

    /*
     * @deprecated JTS files no longer supported
    void writeSummary(DataOutputStream out) throws IOException {
      out.writeUTF(workRelativePath);
      out.writeUTF(title);
      out.writeByte(execStatus.getType());
      out.writeUTF(execStatus.getReason());
      out.writeBoolean(true); // defStatus == execStatus
    }
    */

    String[] getTags() {
        // Script or someone else could possibly do this w/the observer
        if (sections == null) {
            return null;
        }

        Vector tagV = new Vector(sections.length * 2);

        for (int i = 0; i < sections.length; i++) {
            String[] names = sections[i].getOutputNames();

            for (int j = 0; j < names.length; j++) {
                tagV.addElement(names[j]);
            }   // inner for
        } // outer for

        String[] tagA = new String[tagV.size()];
        tagV.copyInto(tagA);

        return tagA;
    }

    /**
     * Insert a test description into this test results.
     * This will only work if the test description is currently not available.
     * The name in the test description must match the name of this test.
     * @param td The new test description, a null value will have no effect.
     * @see #isShrunk()
     * @throws IllegalStateException If the state of this object fobiu
     */
01876     void setTestDescription(TestDescription td) {
      if (td == null)
          return;

      String name = td.getRootRelativeURL();
      if (!testURL.equals(name))
          throw new IllegalStateException();

      if (desc != null) {           // compare if possible
          if (!desc.equals(td)) {   // test descriptions are not the same
            // accept new TD, reset this TR
            // reset status to a special one
            execStatus = tdMismatch;
            desc = td;

            props = emptyStringArray;
            resultsFile = null;
            env = emptyStringArray;
            sections = emptySectionArray;

            if (isMutable())
                createSection(MSG_SECTION_NAME);
          }
          else {
            // TDs are equal, no action, drop thru and return
          }
      }
      else {
          desc = td;
      }
    }

    // ----- PRIVATE METHODS ---------------------------------------------------

    /**
     * @deprecated Use the Section API to accomplish your task.
     */
01913     private static Reader getLastRefOutput(TestResult tr) {
      try {
          Section lastBlk = tr.getSection(tr.getSectionCount() - 1);
          return new StringReader(lastBlk.getOutput("ref"));
      }
        catch (ReloadFault f) {
            // not the best, but this method is deprecated and hopefully never
            // called
            return null;
        }
    }

    private long computeChecksum() {
      long cs = 0;
      cs = cs * 37 + computeChecksum(desc);
      // in JT2.1.1a, environment was not included in checksum,
      // so allow that for backward compatibility
      String jtv = PropertyArray.get(props, VERSION);
      if (env != null) {
          if (jtv == null || !jtv.equals("JT_2.1.1a"))
            cs = cs * 37 + computeChecksum(env);
      }
      cs = cs * 37 + computeChecksum(props);
      if (sections != null)
          cs = cs * 37 + computeChecksum(sections);
      cs = cs * 37 + execStatus.getType() + computeChecksum(execStatus.getReason());
      return Math.abs(cs);  // ensure +ve, to avoid sign issues!
    }

    private static long computeChecksum(TestDescription td) {
      long cs = 0;
      for (Iterator i = td.getParameterKeys(); i.hasNext(); ) {
          // don't rely on enumeration in a particular order
          // so simply add the checksum products together
          String key = (String) (i.next());
          cs += computeChecksum(key) * computeChecksum(td.getParameter(key));
      }
      return cs;
    }

    private static long computeChecksum(Section[] sections) {
      long cs = sections.length;
      for (int i = 0; i < sections.length; i++) {
          cs = cs * 37 + computeChecksum(sections[i]);
      }
      return cs;
    }

    private static long computeChecksum(Section s) {
      long cs = computeChecksum(s.getTitle());
      String[] names = s.getOutputNames();
      for (int i = 0; i <names.length; i++) {
          cs = cs * 37 + computeChecksum(names[i]);
          cs = cs * 37 + computeChecksum(s.getOutput(names[i]));
      }
      return cs;
    }

    private static long computeChecksum(String[] strings) {
      long cs = strings.length;
      for (int i = 0; i < strings.length; i++) {
          cs = cs * 37 + computeChecksum(strings[i]);
      }
      return cs;
    }

    private static long computeChecksum(String s) {
      long cs = 0;
      for (int i = 0; i < s.length(); i++) {
          char c = s.charAt(i);
          //if (!Character.isISOControl(c) || c == '\n')
          cs = cs * 37 + c;
      }
      return cs;
    }

    /** 
     * @throws ResultFileNotFoundFault May be thrown if the JTR file cannot be found.
     * @throws ReloadFault Generally describes any error which is encountered while
     *            reading or processing the input file.
     */
01994     private synchronized void reload() 
      throws ResultFileNotFoundFault, ReloadFault
    {
        if (resultsFile == null)
            throw new ReloadFault(i18n, "rslt.noResultFile");
        
      if (isMutable())
            throw new IllegalStateException("Cannot do a reload of this object.");

        try {
            reload(new FileReader(resultsFile));

          // Well, we have successfully reloaded it, so the object is now taking
          // up a big footprint again ... put it back on the list to be shrunk again
          addToShrinkList();
        } 
      catch (FileNotFoundException e) {
            throw new ResultFileNotFoundFault(i18n, "rslt.fileNotFound", resultsFile);
        }
      catch (IOException e) {
            throw new ReloadFault(i18n, "rslt.badFile", e);
        }
    }

    /**
     * @throws ReloadFault Generally describes any error which is encountered while
     *            reading or processing the input file.  This may indicate
     *              an empty file or incorrectly formatted file.
     */
02023     private void reload(Reader r) 
      throws ReloadFault, IOException 
    {
      try {
          BufferedReader br = new BufferedReader(r);
          String line = br.readLine();
          
          // determine JTR version
          if (line == null) {
            throw new ReloadFault(i18n, "rslt.empty", resultsFile);
          }
          if (line.equals(JTR_V2_HEADER)) {
            reloadVersion2(br);
          }
          else if (line.equals(JTR_V1_HEADER)) {
            reloadVersion1(br);
          }
          else 
            throw new ReloadFault(i18n, "rslt.badHeader", resultsFile);
      }
      finally {
          r.close();
      }
    }

    private void reloadVersion1(BufferedReader in) 
      throws ReloadFault, IOException 
    {
        // grab property info
        StringBuffer buff = new StringBuffer();
        String line = in.readLine();
        while (!(line == null) && !(line.length() == 0)) {
            buff.append(line);
            buff.append(lineSeparator);
            line = in.readLine();
        }

        // store if needed
        Properties pairs = new Properties();
        if (props == null || desc == null) {
            StringReader sr = new StringReader(buff.toString());
            buff = null;
            line = null;

            pairs = new Properties();
            pairs.load(sr);
        }

        if (props == null) {
            // reload test result properties
            props = PropertyArray.getArray(pairs);
        }

        pairs = null;

        if (desc == null) {
            File path = new File((String)(PropertyArray.get(props, "testsuite")));
            if (!path.isDirectory())
                path = new File(path.getParent());
            File file = new File(PropertyArray.get(props, "file"));

            uniquifyStrings(props);

            desc = new TestDescription(path, file,
                                       PropertyArray.getProperties(props));
        }

        buff = new StringBuffer();
        line = in.readLine();
        while (!(line == null)) {
            if (line.startsWith("command: ")) {
                // a section
                Section blk = processOldSection(line, in);

                if (blk != null) {
                    sections = (Section[])(DynamicArray.append(sections, blk));
                }
            }
          else if (line.startsWith(JTR_V1_TSTRESULT)) {
                // test result
                if (line == null) {
                    // couldn't get the status text for some reason
                }
            else {
                    line = extractSlice(line, JTR_V1_TSTRESULT.length(), " ", null);
                    execStatus = Status.parse(line);
                }   // inner else

                break;
            }
          else {
                // message text
                buff.append(line);
                buff.append(lineSeparator);
            }   // else

            line = in.readLine();
        }   // while

        // create the test message section and put first in the array
        Section blk = new Section(MSG_SECTION_NAME);
        blk.reloadOutput(MESSAGE_OUTPUT_NAME, buff.toString());
        Section[] tempBlks = new Section[sections.length+1];
        tempBlks[0] = blk;
        System.arraycopy(sections, 0, tempBlks, 1, sections.length);
        sections = tempBlks;
    }

    private Section processOldSection(String line1, BufferedReader in)
      throws ReloadFault, IOException
    {
        StringBuffer sb = new StringBuffer();         // message stream
        Section section = null;
        String line = line1;
        while (!(line == null)) {
            if (line.startsWith("----------")) {
                String streamName = null;
                String sectionName = null;
                StringBuffer buff = new StringBuffer();
                int lines = 0;
                int chars = 0;
                try {
                    streamName = extractSlice(line, 10, null, ":");
                    sectionName = extractSlice(line, 10, ":", "(");
                    lines = Integer.parseInt(extractSlice(line, 10, "(", "/"));
                    chars = Integer.parseInt(extractSlice(line, 10, "/", ")"));

                for (int count = 0; count < lines; count++) {
                  buff.append(in.readLine());
                }
                } 
            catch (NumberFormatException e) {
                    // confused!
                    throw new ReloadFault(i18n, "rslt.badFile", e);
                }  

                if (section == null)
                    section = new Section(sectionName);

                section.reloadOutput(streamName, buff.toString());
            }
          else if (line.startsWith(JTR_V1_SECTRESULT)) {
                // set result
                if (section == null)
                    section = new Section("");

                // get the Status text
                line = extractSlice(line, JTR_V1_SECTRESULT.length(), " ", null);

                if (line == null)
                    // couldn't get the status text for some reason
                    throw new ReloadFault(i18n, "rslt.noSectionResult");
            else
                    section.reloadStatus(Status.parse(line));

                break;
            }
          else {
                // just a plain message
                sb.append(line);
                sb.append(lineSeparator);
            }

            line = in.readLine();
        }

        section.reloadOutput(MESSAGE_OUTPUT_NAME, sb.toString());
        return section;
    }

    private void reloadVersion2(BufferedReader in) 
      throws ReloadFault, IOException 
    {
      String checksumText = null;
      String line;

      // look for optional checksum and then test description,
      // skipping comments
      while ((line = in.readLine()) != null) {
          if (line.equals(JTR_V2_TESTDESC))
            break;
          else if (line.startsWith(JTR_V2_CHECKSUM)) {
            checksumText = line.substring(JTR_V2_CHECKSUM.length());
          }
          else if (!line.startsWith("#"))
            throw new ReloadFault(i18n, "rslt.badLine", line);
      }

        // this probably won't work with a normal Properties object
        String[] tdProps = PropertyArray.load(in);

        if (desc == null) {
            uniquifyStrings(tdProps);
            desc = TestDescription.load(tdProps);
        }
        tdProps = null;                // dump it

      // XXX compare to TD

        // remove comment lines and look for test env props
        while ((line = in.readLine()) != null) {
          if (line.startsWith(JTR_V2_RESPROPS))
            break;
          else if (line.startsWith(JTR_V2_ENVIRONMENT)) {
            env = PropertyArray.load(in);
            uniquifyStrings(env);
          }
          else if (!line.startsWith("#"))
            throw new ReloadFault(i18n, "rslt.badLine", line);
        }

      if (env == null)
          env = new String[] {};

        if (line == null) {
            throw new ReloadFault(i18n, "rslt.badFormat");
        }

        String[] trProps = PropertyArray.load(in);

        if (props == null) {
            // restore the properties of this result
            uniquifyStrings(trProps);
            props = trProps;
        }

        trProps = null;             // dump it

        // read the sections
        int sectionCount = getSectionCount();
        sections = new Section[sectionCount];
        for (int i = 0; i < getSectionCount(); i++) {
            sections[i] = new Section(in);
        }

        // get the final test status
        while ((line = in.readLine()) != null) {
            if (line.startsWith(JTR_V2_TSTRESULT)) {
                execStatus = Status.parse(line.substring(JTR_V2_TSTRESULT.length()));
                break;
            }
        }

      if (execStatus == null)
          execStatus = Status.error("NO STATUS RECORDED IN FILE");

      // check whether checksum was valid or not
      if (checksumText == null)
          checksumState = NO_CHECKSUM;
      else {
          try {
            long cs = Long.parseLong(checksumText, 16);
            if (cs == computeChecksum())
                checksumState = GOOD_CHECKSUM;
            else 
                checksumState = BAD_CHECKSUM;
          }
          catch (RuntimeException e) {
            checksumState = BAD_CHECKSUM;
          }
      }
    }

    void uniquifyStrings(String[] data) {
        for (int i = 0; i < data.length; i++) 
          data[i] = data[i].intern();
    }

    /**
     * Extract a substring specified by a start and end pattern (string).
     * The start and end strings must be single chars.
     * @param s String to do this operation on
     * @param where Position in the string to start at
     * @param start Beginning pattern for the slice, exclusive.
     * @param end Ending pattern for the slice, exclusive.  Null means
     *            to-end-of-string.
     * @return The requested substring or null if error.
     */
02301     String extractSlice(String s, int where, String start, String end) {
        int startInd;
        int endInd;
        
        if (start == null)
          startInd = where;
      else {
          int i = s.indexOf(start, where);
          if (i < 0)
            return null;
          startInd = i + start.length();
      }
      
        if (end == null) 
            endInd = s.length();
      else {
            endInd = s.indexOf(end, startInd);
          if (endInd == -1) 
            return null;
      }

        try {
            return s.substring(startInd, endInd);
        } 
      catch (StringIndexOutOfBoundsException e) {
            return null;
        }
    }


    private static boolean compare(Reader left, Reader right) 
            throws Fault {
      try {
          try {
            for (;;) {
                int l = left.read(), r = right.read();
                if (l != r) {
                    return false; // different content found
                }
                if (l == -1)
                  return true;
            }   
          }
          finally {
            left.close();
            right.close();
          }
      }
      catch (IOException e) {
          throw new Fault(i18n, "rslt.badCompare", e);
      }
    }

    private static Status shareStatus(Hashtable[] tables, Status s) {
      int type = s.getType();
      String reason = s.getReason();
      Status result = (Status)tables[type].get(reason);
      if (result == null) {
          tables[type].put(reason, s);
          result = s;
      } 

      return result;
    }

    // ------------------------ OBSERVER MAINTENANCE -------------------------

    /**
     * Notify observers that a new section has been created.
     *
     * @param section The section that was created.
     */
02373     private synchronized void notifyCreatedSection(Section section) {
      Observer[] observers = (Observer[])(observersTable.get(this));
        if (observers != null)
            for (int i = 0; i < observers.length; i++)
                observers[i].createdSection(this, section);
    }

    /**
     * Notify observers that a section has been completed.
     *
     * @param section The section that was completed.
     */
02385     private synchronized void notifyCompletedSection(Section section) {
      Observer[] observers = (Observer[])(observersTable.get(this));
        if (observers != null)
            for (int i = 0; i < observers.length; i++)
                observers[i].completedSection(this, section);
    }

    /**
     * Notify observers that new output is being created.
     *
     * @param section The section that was created.
     * @param outputName The name of the output.
     */
02398     private synchronized void notifyCreatedOutput(Section section, String outputName) {
      Observer[] observers = (Observer[])(observersTable.get(this));
        if (observers != null)
            for (int i = 0; i < observers.length; i++)
                observers[i].createdOutput(this, section, outputName);
    }

    /**
     * Notify observers that a particular output has been completed.
     *
     * @param section The section that was completed.
     * @param outputName The name of the output.
     */
02411     private synchronized void notifyCompletedOutput(Section section, String outputName) {
      Observer[] observers = (Observer[])(observersTable.get(this));
        if (observers != null)
            for (int i = 0; i < observers.length; i++)
                observers[i].completedOutput(this, section, outputName);
    }

    /**
     * Notify all observers that new data has been written to some output.
     *
     * @param section The section being modified.
     * @param outputName The stream of the section that is being modified.
     * @param text The text that was added (appended).
     */
02425     private synchronized void notifyUpdatedOutput(Section section, String outputName, int start, int end, String text) {
      Observer[] observers = (Observer[])(observersTable.get(this));
        if (observers != null) 
            for (int i = 0; i < observers.length; i++) 
                observers[i].updatedOutput(this, section, outputName, start, end, text);
    }

    /**
     * Notify all observers that new data has been written to some output.
     *
     * @param section The section being modified.
     * @param outputName The stream of the section that is being modified.
     * @param text The text that was added (appended).
     */
02439     private synchronized void notifyUpdatedOutput(Section section, String outputName, int start, int end, 
                                      char[] buf, int offset, int len) {
      Observer[] observers = (Observer[])(observersTable.get(this));
        if (observers != null) {
          // only create string if there are really observers who want to see it
          String text = new String(buf, offset, len);
            for (int i = 0; i < observers.length; i++) 
                observers[i].updatedOutput(this, section, outputName, start, end, text);
      }
    }

    /**
     * Notify all observers that a property has been updated.
     *
     * @param key The key for the property that was modified.
     * @param value The new value for the property.
     */
02456     private synchronized void notifyUpdatedProperty(String key, String value) {
      Observer[] observers = (Observer[])(observersTable.get(this));
        if (observers != null) 
            for (int i = 0; i < observers.length; i++) 
                observers[i].updatedProperty(this, key, value);
    }

    /**
     * Notify observers the test has completed.
     */
02466     private synchronized void notifyCompleted() {
      // since there will be no more observer messages after this, there
      // is no need to keep any observers registered after we finish here
      // so get the observers one last time, and at the same time
      // remove them from the table
      Observer[] observers = (Observer[])(observersTable.remove(this));
        if (observers != null) {
            for (int i = 0; i < observers.length; i++)
                observers[i].completed(this);
          observersTable.remove(this);
      }

    }

    /**
     * @return Position of the specified section, or -1 if not found.
     */
02483     private synchronized int findSection(String name) {
        int location;
        
        if (sections == null || sections.length == 0) {
            return -1;
        }

        for (location = 0; location < sections.length; location++) {
            if (sections[location].getTitle().equals(name)) {
                // found
                break;
            }
        }   // for

        // loop exited because of counter, not a hit
        if (location == sections.length) {
            location = -1;
        }

        return location;
    }

    private void addToShrinkList() {
      synchronized (shrinkList) {
          // if this object is in the list; remove it;
          // if there are dead weak refs, remove them
          for (Iterator iter = shrinkList.iterator(); iter.hasNext(); ) {
            WeakReference wref = (WeakReference) (iter.next());
            Object o = wref.get();
            if (o == null || o == this)
                iter.remove();
          }
          while (shrinkList.size() >= maxShrinkListSize) {
            WeakReference wref = (WeakReference) (shrinkList.removeFirst());
            TestResult tr = (TestResult) (wref.get());
            if (tr != null)
                tr.shrink();
          }
          shrinkList.addLast(new WeakReference(this));
      }
    }

    /**
     * Tells the object that it can optimize itself for a small memory footprint.
     * Doing this may sacrifice performance when accessing object data.  This
     * only works on results that are immutable.
     */
02530     private synchronized void shrink() {
        if (isMutable()) {
            throw new IllegalStateException("Can't shrink a mutable test result!");
        }

      // Should ensure we have a resultsFile.
        sections = null;

        // NOTE: if either of these are discarded, it may be a good idea to
        //       optimize reload() to not read the section/stream data since
        //       a small property lookup could incur a huge overhead
      //props = null;         // works, may or may-not improve memory usage
        //desc = null;          // doesn't work in current implementation
    }

    // the following fields should be valid for all test results
    private File resultsFile;       // if set, location where test results are stored
    private Status execStatus;            // pre-compare result
    private String testURL;         // URL for this test, equal to the one in TD.getRootRelativeURL
    private long endTime = -1;            // when test finished
    private byte checksumState;           // checksum state
    // the following fields are candidates for shrinking although not currently done
    private TestDescription desc;   // test description for which this is the result
    private String[] props;         // table of values written during test execution
    private String[] env;
    // this field is cleared when the test result is shrunk
    private Section[] sections;           // sections of output written during test execution

    // only valid when this TR is in a TRT, should remain when shrunk
    private TestResultTable.TreeNode parent;

    // because so few test results will typically be observed (e.g. at most one)
    // we don't use a per-instance array of observers, but instead associate any
    // such arrays here.
    private static Hashtable observersTable = new Hashtable(16);

    /**
     * The name of the default output that all Sections are equipped with.
     */
02569     public static final String MESSAGE_OUTPUT_NAME = "messages";

    /**
     * The name of the default section that all TestResult objects are equipped with.
     */
02574     public static final String MSG_SECTION_NAME = "script_messages";

    /**
     * The name of the property that defines the test description file.
     */
02579     public static final String DESCRIPTION = "description";

    /**
     * The name of the property that defines the time at which the test
     * execution finished. 
    */
02585     public static final String END = "end";

    /**
     * The name of the property that defines the environment name.
     */
02590     public static final String ENVIRONMENT = "environment";
    
    /** 
     * The name of the property that defines the test execution status.
     */
02595     public static final String EXEC_STATUS = "execStatus";

    /**
     * The name of the property that defines the OS on which JT Harness
     * was running when the test was run.
     */
02601     public static final String JAVATEST_OS = "javatestOS";

    /** 
     * The name of the property that defines the script that ran the test.
     */
02606     public static final String SCRIPT = "script";

    /**
     * The name of the property that defines the test output sections
     * that were recorded when the test ran.
     */
02612     public static final String SECTIONS = "sections";

    /**
     * The name of the property that defines the time at which the test
     * execution started. 
    */
02618     public static final String START = "start";

    /**
     * The name of the property that defines the test for which this is 
     * the result object.
    */
02624     public static final String TEST = "test";

    /**
     * The name of the property that defines which version of JT Harness
     * was used to run the test.
     */
02630     public static final String VERSION = "javatestVersion";

    /**
     * The name of the property that defines the work directory for the test.
     */
02635     public static final String WORK = "work";

    static final String EXTN = ".jtr";

    private static final Status 
        filesSame       = Status.passed("Output file and reference file matched"),
        filesDifferent  = Status.failed("Output file and reference file were different"),
        fileError       = Status.failed("Error occurred during comparison"),
        interrupted     = Status.failed("interrupted"),
        inProgress      = Status.notRun("Test running..."),
        incomplete      = Status.notRun("Section not closed, may be incomplete"),
        tdMismatch      = Status.notRun("Old test flushed, new test description located"),
        notRunStatus    = Status.notRun("");

    private static final String[] emptyStringArray = new String[0];
    private static final Section[] emptySectionArray = new Section[0];

    private static final String defaultClassDir = "classes";

    // info for reading/writing JTR files (version 1)
    private static final String JTR_V1_HEADER = "#Test Results";
    private static final String JTR_V1_SECTRESULT = "command result:";
    private static final String JTR_V1_TSTRESULT = "test result:";

    // info for reading/writing JTR files (version 2)
    private static final String JTR_V2_HEADER = "#Test Results (version 2)";
    private static final String JTR_V2_SECTION = "#section:";
    private static final String JTR_V2_CHECKSUM = "#checksum:";
    private static final String JTR_V2_TESTDESC = "#-----testdescription-----";
    private static final String JTR_V2_RESPROPS = "#-----testresult-----";
    private static final String JTR_V2_ENVIRONMENT = "#-----environment-----";
    private static final String JTR_V2_SECTRESULT = "result: ";
    private static final String JTR_V2_TSTRESULT = "test result: ";
    private static final String JTR_V2_SECTSTREAM = "----------";
  
    private static final String lineSeparator = System.getProperty("line.separator");

    private static final int DEFAULT_MAX_SHRINK_LIST_SIZE = 128;
    private static final int maxShrinkListSize = 
      Integer.getInteger("javatest.numCachedResults", DEFAULT_MAX_SHRINK_LIST_SIZE).intValue();
    private static LinkedList shrinkList = new LinkedList();

    private static final int DEFAULT_MAX_OUTPUT_SIZE = 100000;
    private static final int maxOutputSize = 
      Integer.getInteger("javatest.maxOutputSize", DEFAULT_MAX_OUTPUT_SIZE).intValue();

    private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(TestResult.class);

    private static boolean debug = Boolean.getBoolean("debug." + TestResult.class.getName());

}

Generated by  Doxygen 1.6.0   Back to index