Commit:    3aca4906da0f27644e1e17039316358ff03b6404
Author:    Matt Ficken <v-maf...@microsoft.com>         Fri, 2 Nov 2012 
09:58:37 -0700
Parents:   9f9b1aba45852e608087bb9481307947df3b2223
Branches:  master

Link:       
http://git.php.net/?p=pftt2.git;a=commitdiff;h=3aca4906da0f27644e1e17039316358ff03b6404

Log:
php builtin web server testing support


Former-commit-id: 92565d723da8b9e71b596ab3798b64e7652b5d56

Changed paths:
  M  bin/pftt
  M  bin/pftt.cmd
  M  src/com/mostc/pftt/host/Host.java
  M  src/com/mostc/pftt/host/LocalHost.java
  M  src/com/mostc/pftt/model/phpt/EPhptSection.java
  M  src/com/mostc/pftt/model/phpt/PhpIni.java
  M  src/com/mostc/pftt/model/phpt/PhptTestCase.java
  M  src/com/mostc/pftt/model/sapi/BuiltinWebServerManager.java
  M  src/com/mostc/pftt/model/sapi/CrashedWebServerInstance.java
  M  src/com/mostc/pftt/model/sapi/WebServerInstance.java
  M  src/com/mostc/pftt/model/sapi/WebServerManager.java
  M  src/com/mostc/pftt/runner/AbstractPhptTestCaseRunner2.java
  M  src/com/mostc/pftt/runner/HttpTestCaseRunner.java
  M  src/com/mostc/pftt/runner/PhptTestPackRunner.java
  M  src/com/mostc/pftt/scenario/AbstractWebServerScenario.java
  M  src/com/mostc/pftt/scenario/BuiltinWebServerScenario.java
  A  src/com/mostc/pftt/telemetry/ConsoleManager.java
  M  src/com/mostc/pftt/telemetry/PhptTestResult.java
  M  src/com/mostc/pftt/ui/PhptHostTab.java
  M  src/com/mostc/pftt/util/HostEnvUtil.java
  M  src/com/mostc/pftt/util/StringUtil.java

diff --git a/bin/pftt b/bin/pftt
index dd82dd0..7004187 100644
--- a/bin/pftt
+++ b/bin/pftt
@@ -5,6 +5,6 @@
 export PFTT_HOME=/home/matt/workspace/PFTT
 export PFTT_LIB=$PFTT_HOME/lib
 
-export 
CLASSPATH=$PFTT_HOME/build:$PFTT_LIB/htmlcleaner-2.2.jar:$PFTT_LIB/groovy-1.8.6.jar:$PFTT_LIB/icu4j-49_1.jar:$PFTT_LIB/icudata.jar:$PFTT_LIB/icutzdata.jar:$PFTT_LIB/j2ssh-common-0.2.9.jar:$PFTT_LIB/j2ssh-core-0.2.9.jar:$PFTT_LIB/jansi-1.7.jar:$PFTT_LIB/jline-0.9.94.jar:$PFTT_LIB/jzlib-1.0.7.jar:$PFTT_LIB/selenium-server-standalone-2.19.0.jar:$PFTT_LIB/xercesImpl.jar:$PFTT_LIB/xmlpull-1.1.3.1.jar:$PFTT_LIB/commons-net-3.1.jar:$PFTT_LIB/commons-cli-1.2.jar:$PFTT_LIB/antlr-2.7.7.jar:$PFTT_LIB/asm-3.2.jar:$PFTT_LIB/asm-analysis-3.2.jar:$PFTT_LIB/asm-commons-3.2.jar:$PFTT_LIB/asm-tree-3.2.jar:$PFTT_LIB/asm-util-3.2.jar
+export 
CLASSPATH=$PFTT_HOME/build:$PFTT_LIB/winp-1.14.jar:$PFTT_LIB/htmlcleaner-2.2.jar:$PFTT_LIB/groovy-1.8.6.jar:$PFTT_LIB/icu4j-49_1.jar:$PFTT_LIB/icudata.jar:$PFTT_LIB/icutzdata.jar:$PFTT_LIB/j2ssh-common-0.2.9.jar:$PFTT_LIB/j2ssh-core-0.2.9.jar:$PFTT_LIB/jansi-1.7.jar:$PFTT_LIB/jline-0.9.94.jar:$PFTT_LIB/jzlib-1.0.7.jar:$PFTT_LIB/selenium-server-standalone-2.19.0.jar:$PFTT_LIB/xercesImpl.jar:$PFTT_LIB/xmlpull-1.1.3.1.jar:$PFTT_LIB/commons-net-3.1.jar:$PFTT_LIB/commons-cli-1.2.jar:$PFTT_LIB/antlr-2.7.7.jar:$PFTT_LIB/asm-3.2.jar:$PFTT_LIB/asm-analysis-3.2.jar:$PFTT_LIB/asm-commons-3.2.jar:$PFTT_LIB/asm-tree-3.2.jar:$PFTT_LIB/asm-util-3.2.jar
 
 java -classpath $CLASSPATH com.mostc.pftt.main.PfttMain $*
diff --git a/bin/pftt.cmd b/bin/pftt.cmd
index a8b576c..1125891 100644
--- a/bin/pftt.cmd
+++ b/bin/pftt.cmd
@@ -4,7 +4,7 @@ REM script for running PFTT on Windows
 SET PFTT_HOME=C:\php-sdk\PFTT\current
 SET PFTT_LIB=%PFTT_HOME%\lib
 
-SET 
CLASSPATH=%PFTT_HOME%\build;%PFTT_LIB%\htmlcleaner-2.2.jar;%PFTT_LIB%\groovy-1.8.6.jar;%PFTT_LIB%\icu4j-49_1.jar;%PFTT_LIB%\icudata.jar;%PFTT_LIB%\icutzdata.jar;%PFTT_LIB%\j2ssh-common-0.2.9.jar;%PFTT_LIB%\j2ssh-core-0.2.9.jar;%PFTT_LIB%\jansi-1.7.jar;%PFTT_LIB%\jline-0.9.94.jar;%PFTT_LIB%\jzlib-1.0.7.jar;%PFTT_LIB%\selenium-server-standalone-2.19.0.jar;%PFTT_LIB%\xercesImpl.jar;%PFTT_LIB%\xmlpull-1.1.3.1.jar;%PFTT_LIB%\commons-net-3.1.jar;%PFTT_LIB%\commons-cli-1.2.jar;%PFTT_LIB%\antlr-2.7.7.jar;%PFTT_LIB%\asm-3.2.jar;%PFTT_LIB%\asm-analysis-3.2.jar;%PFTT_LIB%\asm-commons-3.2.jar;%PFTT_LIB%\asm-tree-3.2.jar;%PFTT_LIB%\asm-util-3.2.jar
+SET 
CLASSPATH=%PFTT_HOME%\build;%PFTT_LIB%\winp-1.14.jar;%PFTT_LIB%\htmlcleaner-2.2.jar;%PFTT_LIB%\groovy-1.8.6.jar;%PFTT_LIB%\icu4j-49_1.jar;%PFTT_LIB%\icudata.jar;%PFTT_LIB%\icutzdata.jar;%PFTT_LIB%\j2ssh-common-0.2.9.jar;%PFTT_LIB%\j2ssh-core-0.2.9.jar;%PFTT_LIB%\jansi-1.7.jar;%PFTT_LIB%\jline-0.9.94.jar;%PFTT_LIB%\jzlib-1.0.7.jar;%PFTT_LIB%\selenium-server-standalone-2.19.0.jar;%PFTT_LIB%\xercesImpl.jar;%PFTT_LIB%\xmlpull-1.1.3.1.jar;%PFTT_LIB%\commons-net-3.1.jar;%PFTT_LIB%\commons-cli-1.2.jar;%PFTT_LIB%\antlr-2.7.7.jar;%PFTT_LIB%\asm-3.2.jar;%PFTT_LIB%\asm-analysis-3.2.jar;%PFTT_LIB%\asm-commons-3.2.jar;%PFTT_LIB%\asm-tree-3.2.jar;%PFTT_LIB%\asm-util-3.2.jar
 
 REM find java and execute PfttMain
 REM search %PATH% for java
diff --git a/src/com/mostc/pftt/host/Host.java 
b/src/com/mostc/pftt/host/Host.java
index 9bb148e..8c3cae9 100644
--- a/src/com/mostc/pftt/host/Host.java
+++ b/src/com/mostc/pftt/host/Host.java
@@ -9,6 +9,7 @@ import java.util.Random;
 import java.util.regex.Pattern;
 
 import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
 
 
 import com.github.mattficken.io.ByLineReader;
@@ -309,6 +310,14 @@ public abstract class Host {
        public boolean isLonghornExact() {
                return false; // TODO
        }
+       /** returns TRUE if host is Windows
+        * 
+        * this is a fast method which caches its values, so you don't have to 
do that in your code
+        * 
+        * generally if !isWindows == isPosix
+        * 
+        * @return
+        */
        public abstract boolean isWindows();
        public String getTempDir() {
                if (tmp_dir!=null)
@@ -400,6 +409,7 @@ public abstract class Host {
        public ExecOutput execElevated(String cmd, int timeout_sec, String 
chdir) throws Exception {
                return execElevated(cmd, timeout_sec, null, null, null, chdir, 
null, NO_TIMEOUT);
        }
+       @ThreadSafe
        public static abstract class ExecHandle {
                public abstract void close();
                public abstract boolean isRunning();
@@ -645,5 +655,17 @@ public abstract class Host {
                        return 1;
                }
        }
+       
+       /* TODO
+       public long getTotalPhysicalMemory() {
+               if (isWindows()) {
+                       // could use `wmic` but that won't work on winxp, 2003, 
2003r2
+                       this.getSystemInfo();
+                       // look for line like: `Total Physical Memory:     
4,096 MB`
+               } else {
+                       this.exec("free", NO_TIMEOUT);
+               }
+       }
+       */
 
 } // end public abstract class Host
diff --git a/src/com/mostc/pftt/host/LocalHost.java 
b/src/com/mostc/pftt/host/LocalHost.java
index efcc2e1..6ffd658 100644
--- a/src/com/mostc/pftt/host/LocalHost.java
+++ b/src/com/mostc/pftt/host/LocalHost.java
@@ -46,6 +46,15 @@ import com.sun.jna.platform.win32.WinNT.HANDLE;
 public class LocalHost extends Host {
        private static final Timer timer = new Timer();
        private static final boolean is_windows = 
System.getProperty("os.name").toLowerCase().contains("windows");
+       private static int self_process_id;
+       static {
+               try {
+                       // this works only on Windows
+                       self_process_id = 
Kernel32.INSTANCE.GetCurrentProcessId();
+               } catch ( Throwable t ) {
+                       t.printStackTrace(); // XXX fix this on Linux
+               }
+       }
        
        public static boolean isLocalhostWindows() {
                return is_windows;
@@ -195,16 +204,16 @@ public class LocalHost extends Host {
                protected OutputStream stdin;
                protected InputStream stdout, stderr;
                protected ExitMonitorTask task;
-               protected String cmdline;
+               protected String image_name;
                protected Charset charset;
                protected StringBuilder output_sb;
                
-               public LocalExecHandle(Process process, OutputStream stdin, 
InputStream stdout, InputStream stderr, String cmdline) {
+               public LocalExecHandle(Process process, OutputStream stdin, 
InputStream stdout, InputStream stderr, String[] cmd_array) {
                        this.process = process;
                        this.stdin = stdin;
                        this.stdout = stdout;
                        this.stderr = stderr;
-                       this.cmdline = cmdline;
+                       this.image_name = 
StringUtil.unquote(basename(cmd_array[0]));
                }
                
                @Override
@@ -212,20 +221,22 @@ public class LocalHost extends Host {
                        try {
                                process.exitValue();
                                return false;
-                       } catch ( Exception ex ) {
+                       } catch ( IllegalThreadStateException ex ) {
                                return true;
                        }
                }
 
                boolean run = true;
                @Override
-               public void close() {
+               public synchronized void close() {
                        run = false;
                        
                        // may take multiple tries to make it exit (lots of 
processes, certain OSes, etc...)
                        for ( int tries = 0 ; tries < 10 ; tries++ ) {
                                try {
                                        process.exitValue();
+                                       break; 
+                                       // process terminated, stop trying (or 
may terminate new process reusing the same id)
                                } catch ( Throwable t ) {
                                        // hasn't exited yet
                                        if (tries==0) {
@@ -249,17 +260,21 @@ public class LocalHost extends Host {
                                
                                        // kill it
                                        if (isLocalhostWindows()) {
+                                               // Windows BN: process trees on 
Windows won't get terminated correctly by calling Process#destroy
+                                               //
+                                               // have to do some ugly hacks 
on Windows to kill process trees
+                                               int process_id = 0;
                                                
+                                               // first: find the process id
                                                try {
+                                                       // clean way
                                                        WinProcess wproc = new 
WinProcess(process);
-                                                       wproc.killRecursively();
+                                                       process_id = 
wproc.getPid();
                                                } catch ( Throwable wt ) {
                                                        // WinProcess native 
code couldn't be loaded
                                                        // (maybe it wasn't 
included or maybe somebody didn't compile it)
                                                        //
                                                        // fallback on some old 
code using reflection, etc...
-                                                       //
-                                                       // process.destroy 
doesn't always work on windows, but TASKKILL does
                                                        try {
                                                                // 
process.getClass() != Process.class
                                                                //
@@ -274,16 +289,7 @@ public class LocalHost extends Host {
                                                                                
                                                                                
HANDLE h = new HANDLE();
                                                                                
h.setPointer(Pointer.createConstant(handle));
-                                                                               
long process_id = Kernel32.INSTANCE.GetProcessId(h);
-                                                                               
-                                                                               
-                                                                               
// TASKKILL!=TSKILL, TASKKILL is better than TSKILL
-                                                                               
//
-                                                                               
// /F => forcefully terminate ('kill')
-                                                                               
// /T => terminate all child processes (process is cmd.exe and PHP is a child)
-                                                                               
//      process.destory might not do this, so thats why its CRITICAL that 
TASKKILL
-                                                                               
//      be tried before process.destroy
-                                                                               
Runtime.getRuntime().exec("TASKKILL /PID:"+process_id+" /F /T");
+                                                                               
process_id = Kernel32.INSTANCE.GetProcessId(h);
                                                                                
                                                                                
break;
                                                                        }
@@ -292,6 +298,25 @@ public class LocalHost extends Host {
                                                                
t2.printStackTrace();
                                                        } // end try
                                                } // end try
+                                               
+                                               // second: make sure we found a 
process id (safety check: make sure its not our process id)
+                                               if (process_id != 0 && 
process_id!=self_process_id) {
+                                                       // also, 
WinProcess#killRecursively only checks by process id (not image/program name)
+                                                       // while that should be 
enough, experience on Windows has shown that it isn't and somehow gets PFTT 
killed eventually
+                                                       //
+                                                       // third:instead, run 
TASKKILL and provide it both the process id and image/program name
+                                                       //
+                                                       // image name: ex: 
`php.exe` 
+                                                       // /F => forcefully 
terminate ('kill')
+                                                       // /T => terminate all 
child processes (process is cmd.exe and PHP is a child)
+                                                       //      process.destory 
might not do this, so thats why its CRITICAL that TASKKILL
+                                                       //      be tried before 
process.destroy
+                                                       try {
+                                                               
Runtime.getRuntime().exec("TASKKIll /FI \"IMAGENAME eq "+image_name+"\" /FI 
\"PID eq "+process_id+"\" /F /T");
+                                                       } catch (Throwable t3) {
+                                                               
t3.printStackTrace();
+                                                       }
+                                               }
                                        } // end if
                                        //
                                        
@@ -441,7 +466,7 @@ public class LocalHost extends Host {
            InputStream stdout = process.getInputStream();
            InputStream stderr = process.getErrorStream();
 
-           LocalExecHandle h = new LocalExecHandle(process, stdin, stdout, 
stderr, StringUtil.toString(cmd_array));
+           LocalExecHandle h = new LocalExecHandle(process, stdin, stdout, 
stderr, cmd_array);
            
            if (timeout>NO_TIMEOUT) {
                h.task = new ExitMonitorTask(h);
diff --git a/src/com/mostc/pftt/model/phpt/EPhptSection.java 
b/src/com/mostc/pftt/model/phpt/EPhptSection.java
index e8dc93d..4c19044 100644
--- a/src/com/mostc/pftt/model/phpt/EPhptSection.java
+++ b/src/com/mostc/pftt/model/phpt/EPhptSection.java
@@ -20,48 +20,48 @@ public enum EPhptSection {
        DESCRIPTION {
                @Override
                public String prepareSection(boolean keep_all, String text) {
-                       return keep_all?text:PhpIni.EMPTY;
+                       return keep_all?text:StringUtil.EMPTY;
                }
        },
        CREDITS {
                @Override
                public String prepareSection(boolean keep_all, String text) {
-                       return keep_all?text:PhpIni.EMPTY;
+                       return keep_all?text:StringUtil.EMPTY;
                }
        },
        /** not in documentation, de-facto */
        FAIL {
                @Override
                public String prepareSection(boolean keep_all, String text) {
-                       return keep_all?text:PhpIni.EMPTY;
+                       return keep_all?text:StringUtil.EMPTY;
                }
        },
        /** not in documentation, de-facto */
        CREDIT {
                @Override
                public String prepareSection(boolean keep_all, String text) {
-                       return keep_all?text:PhpIni.EMPTY;
+                       return keep_all?text:StringUtil.EMPTY;
                }
        },
        /** not in documentation, de-facto */
        UEXPECTF {
                @Override
                public String prepareSection(boolean keep_all, String text) {
-                       return keep_all?text:PhpIni.EMPTY;
+                       return keep_all?text:StringUtil.EMPTY;
                }
        },
        /** not in documentation, de-facto */
        DONE {
                @Override
                public String prepareSection(boolean keep_all, String text) {
-                       return keep_all?text:PhpIni.EMPTY;
+                       return keep_all?text:StringUtil.EMPTY;
                }
        },
        /** not in documentation, de-facto */
        COMMENT {
                @Override
                public String prepareSection(boolean keep_all, String text) {
-                       return keep_all?text:PhpIni.EMPTY;
+                       return keep_all?text:StringUtil.EMPTY;
                }
        },
        SKIPIF,
@@ -130,13 +130,13 @@ public enum EPhptSection {
        CGI {
                @Override
                public String prepareSection(boolean keep_all, String text) {
-                       return keep_all?text:PhpIni.EMPTY;
+                       return keep_all?text:StringUtil.EMPTY;
                }
        },
        XFAIL {
                @Override
                public String prepareSection(boolean keep_all, String text) {
-                       return keep_all?text:PhpIni.EMPTY;
+                       return keep_all?text:StringUtil.EMPTY;
                }
        },
        EXPECTHEADERS,
diff --git a/src/com/mostc/pftt/model/phpt/PhpIni.java 
b/src/com/mostc/pftt/model/phpt/PhpIni.java
index 88dbd78..634d7cc 100644
--- a/src/com/mostc/pftt/model/phpt/PhpIni.java
+++ b/src/com/mostc/pftt/model/phpt/PhpIni.java
@@ -58,9 +58,6 @@ public class PhpIni extends TestCaseGroupKey {
        public static final String UNICODE_OUTPUT_ENCODING = 
"unicode.output_encoding";
        public static final String UNICODE_FROM_ERROR_MODE = 
"unicode.from_error_mode";
        public static final String SESSION_AUTO_START = "session.auto_start";
-       //
-       // values
-       public static final String EMPTY = "";
        public static final String ON = "On";
        public static final String OFF = "Off";
        public static final String UTF_8 = "UTF-8";
@@ -101,10 +98,10 @@ public class PhpIni extends TestCaseGroupKey {
        public static final String EXT_XSL = dllName("xsl");
        public static PhpIni createDefaultIniCopy(Host host) {
                PhpIni ini = new PhpIni();
-               ini.putMulti(OUTPUT_HANDLER, EMPTY);
-               ini.putMulti(OPEN_BASEDIR, EMPTY);
+               ini.putMulti(OUTPUT_HANDLER, StringUtil.EMPTY);
+               ini.putMulti(OPEN_BASEDIR, StringUtil.EMPTY);
                ini.putMulti(SAFE_MODE, 0);
-               ini.putMulti(DISABLE_DEFS, EMPTY);
+               ini.putMulti(DISABLE_DEFS, StringUtil.EMPTY);
                ini.putMulti(OUTPUT_BUFFERING, OFF);
                ini.putMulti(ERROR_REPORTING, E_ALL_OR_E_STRICT);
                // IMPORTANT: display_errors=0. doesn't affect test output. 
run-tests.php sets display_errors=1
@@ -116,12 +113,12 @@ public class PhpIni extends TestCaseGroupKey {
                ini.putMulti(TRACK_ERRORS, 1);
                ini.putMulti(REPORT_MEMLEAKS, 1);
                ini.putMulti(REPORT_ZEND_DEBUG, 0);
-               ini.putMulti(DOCREF_ROOT, EMPTY);
+               ini.putMulti(DOCREF_ROOT, StringUtil.EMPTY);
                ini.putMulti(DOCREF_EXT, DOT_HTML);
-               ini.putMulti(ERROR_PREPEND_STRING, EMPTY);
-               ini.putMulti(ERROR_APPEND_STRING, EMPTY);
-               ini.putMulti(AUTO_PREPEND_FILE, EMPTY);
-               ini.putMulti(AUTO_APPEND_FILE, EMPTY);
+               ini.putMulti(ERROR_PREPEND_STRING, StringUtil.EMPTY);
+               ini.putMulti(ERROR_APPEND_STRING, StringUtil.EMPTY);
+               ini.putMulti(AUTO_PREPEND_FILE, StringUtil.EMPTY);
+               ini.putMulti(AUTO_APPEND_FILE, StringUtil.EMPTY);
                ini.putMulti(MAGIC_QUOTES_RUNTIME, 0);
                ini.putMulti(IGNORE_REPEATED_ERRORS, 0);
                ini.putMulti(PRECISION, 14);
@@ -167,7 +164,7 @@ public class PhpIni extends TestCaseGroupKey {
                        int ini_i = line.indexOf("=");
                        if (ini_i!=-1) {
                                String ini_name = line.substring(0, 
ini_i).trim();
-                               String ini_value = ini_i+1>=line.length() ? 
EMPTY : line.substring(ini_i+1).trim();
+                               String ini_value = ini_i+1>=line.length() ? 
StringUtil.EMPTY : line.substring(ini_i+1).trim();
                                putMulti(ini_name, ini_value);
                        }
                }
diff --git a/src/com/mostc/pftt/model/phpt/PhptTestCase.java 
b/src/com/mostc/pftt/model/phpt/PhptTestCase.java
index f2a116d..a226017 100644
--- a/src/com/mostc/pftt/model/phpt/PhptTestCase.java
+++ b/src/com/mostc/pftt/model/phpt/PhptTestCase.java
@@ -9,6 +9,9 @@ import java.util.HashMap;
 import java.util.Set;
 import java.util.regex.Pattern;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
 import com.github.mattficken.io.AbstractDetectingCharsetReader;
 import com.github.mattficken.io.ByLineReader;
 import com.github.mattficken.io.CharsetDeciderDecoder;
@@ -451,16 +454,20 @@ public class PhptTestCase extends TestCase {
         * @return
         * @see #get
         */
+       @Nonnull
        public String getTrim(EPhptSection section) {
                String t = get(section);
-               return StringUtil.isEmpty(t) ? t : t.trim();
+               return StringUtil.isEmpty(t) ? StringUtil.EMPTY : t.trim();
        }
 
        /** returns the text of the section unmodified
         * 
+        * returns null if section not found
+        * 
         * @param section
         * @return
         */
+       @Nullable
        public String get(EPhptSection section) {
                return section_text.get(section);
        }
diff --git a/src/com/mostc/pftt/model/sapi/BuiltinWebServerManager.java 
b/src/com/mostc/pftt/model/sapi/BuiltinWebServerManager.java
index d7d3013..4fb8979 100644
--- a/src/com/mostc/pftt/model/sapi/BuiltinWebServerManager.java
+++ b/src/com/mostc/pftt/model/sapi/BuiltinWebServerManager.java
@@ -40,7 +40,11 @@ public class BuiltinWebServerManager extends 
WebServerManager {
                        Runtime.getRuntime().addShutdownHook(new Thread() {
                                @Override
                                public void run() {
+                                       System.out.println("PFTT: terminating 
builtin web server instances...");
                                        // Windows BN: may leave `php.exe -S` 
running if they aren't terminated here
+                                       // NOTE: if you click stop in Eclipse, 
you won't get here
+                                       // on Windows, that means that `php.exe 
-S` instances will still be running
+                                       // you'll need to do: taskkill 
/im:php.exe /f /t
                                        close();
                                }
                        });
@@ -52,7 +56,8 @@ public class BuiltinWebServerManager extends WebServerManager 
{
                String sapi_output = "";
                int port_attempts;
                boolean found_port;
-               for (int total_attempts = 0 ; total_attempts < 3 ; 
total_attempts++) {
+               int total_attempts = 0;
+               for ( ; total_attempts < 3 ; total_attempts++) {
                        
                        // find port number not currently in use
                        port_attempts = 0;
@@ -97,7 +102,7 @@ public class BuiltinWebServerManager extends 
WebServerManager {
                                //
                        
                                // provide a BuiltinWebServerInstance to handle 
the running web server instance
-                               final BuiltinWebServerInstance web = new 
BuiltinWebServerInstance(LocalHost.splitCmdString(cmd), ini, handle, hostname, 
last_port);
+                               final BuiltinWebServerInstance web = new 
BuiltinWebServerInstance(this, LocalHost.splitCmdString(cmd), ini, handle, 
hostname, last_port);
                                
                                // check web server periodically to see if it 
has crashed
                                timer.scheduleAtFixedRate(new TimerTask() {
@@ -130,19 +135,19 @@ public class BuiltinWebServerManager extends 
WebServerManager {
                } // end for
                
                // fallback
-               sapi_output += "PFTT: wasn't able to start web server instance 
(after many attempts)... giving up.\n";
+               sapi_output += "PFTT: wasn't able to start web server instance 
(after "+total_attempts+" attempts)... giving up.\n";
                
                // return this failure message to client code
-               return new CrashedWebServerInstance(ini, sapi_output);
+               return new CrashedWebServerInstance(this, ini, sapi_output);
        } // end protected synchronized WebServerInstance 
createWebServerInstance
        
-       public class BuiltinWebServerInstance extends WebServerInstance {
+       public static class BuiltinWebServerInstance extends WebServerInstance {
                protected final int port;
                protected final String hostname;
                protected final ExecHandle process;
                
-               public BuiltinWebServerInstance(String[] cmd_array, PhpIni ini, 
ExecHandle process, String hostname, int port) {
-                       super(cmd_array, ini);
+               public BuiltinWebServerInstance(BuiltinWebServerManager ws_mgr, 
String[] cmd_array, PhpIni ini, ExecHandle process, String hostname, int port) {
+                       super(ws_mgr, cmd_array, ini);
                        this.process = process;
                        this.hostname = hostname;
                        this.port = port;
@@ -159,19 +164,13 @@ public class BuiltinWebServerManager extends 
WebServerManager {
                }
 
                @Override
-               public void close() {
-                       try {
-                               process.close();
-                       } finally {
-                               
synchronized(BuiltinWebServerManager.this.instances) {
-                                       
BuiltinWebServerManager.this.instances.remove(this);
-                               }
-                       }
+               protected void do_close() {
+                       process.close();
                }
 
                @Override
                public boolean isRunning() {
-                       return process.isRunning();
+                       return process.isRunning() && !isCrashed();
                }
                
        } // end public class BuiltinWebServerInstance
diff --git a/src/com/mostc/pftt/model/sapi/CrashedWebServerInstance.java 
b/src/com/mostc/pftt/model/sapi/CrashedWebServerInstance.java
index 154ea27..1bf6a14 100644
--- a/src/com/mostc/pftt/model/sapi/CrashedWebServerInstance.java
+++ b/src/com/mostc/pftt/model/sapi/CrashedWebServerInstance.java
@@ -14,8 +14,8 @@ import com.mostc.pftt.model.phpt.PhpIni;
 public class CrashedWebServerInstance extends WebServerInstance {
        protected final String sapi_output;
        
-       public CrashedWebServerInstance(PhpIni ini, String sapi_output) {
-               super(null, ini);
+       public CrashedWebServerInstance(WebServerManager ws_mgr, PhpIni ini, 
String sapi_output) {
+               super(ws_mgr, null, ini);
                this.sapi_output = sapi_output;
        }
        
@@ -43,4 +43,9 @@ public class CrashedWebServerInstance extends 
WebServerInstance {
                return false;
        }
 
+       @Override
+       protected void do_close() {
+               // N/A
+       }
+
 }
diff --git a/src/com/mostc/pftt/model/sapi/WebServerInstance.java 
b/src/com/mostc/pftt/model/sapi/WebServerInstance.java
index f115cb9..0d821a7 100644
--- a/src/com/mostc/pftt/model/sapi/WebServerInstance.java
+++ b/src/com/mostc/pftt/model/sapi/WebServerInstance.java
@@ -1,6 +1,8 @@
 package com.mostc.pftt.model.sapi;
 
+import java.util.ArrayList;
 import java.util.LinkedList;
+import java.util.List;
 
 import javax.annotation.concurrent.ThreadSafe;
 
@@ -17,18 +19,21 @@ import com.mostc.pftt.util.StringUtil;
 
 @ThreadSafe
 public abstract class WebServerInstance extends SAPIInstance {
-       protected LinkedList<PhptTestCase> active_test_cases;
+       protected final List<PhptTestCase> active_test_cases, all_test_cases;
        private boolean crashed = false;
        private String sapi_output = "";
        private Object sync_lock = new Object();
        protected final PhpIni ini;
        protected final String[] cmd_array;
+       protected final WebServerManager ws_mgr;
        WebServerInstance replacement; // @see 
WebServerManager#getWebServerInstance
        
-       public WebServerInstance(String[] cmd_array, PhpIni ini) {
+       public WebServerInstance(WebServerManager ws_mgr, String[] cmd_array, 
PhpIni ini) {
+               this.ws_mgr = ws_mgr;
                this.cmd_array = cmd_array;
                this.ini = ini;
                active_test_cases = new LinkedList<PhptTestCase>();
+               all_test_cases = new ArrayList<PhptTestCase>(256);
        }
        
        @Override
@@ -60,6 +65,8 @@ public abstract class WebServerInstance extends SAPIInstance {
         * @param exit_code - exit code that was returned
         */
        public void notifyCrash(String output, int exit_code) {
+               // make sure it gets closed!!
+               // TODO temp vs close();
                synchronized(sync_lock) {
                        //
                        if (crashed) {
@@ -68,7 +75,7 @@ public abstract class WebServerInstance extends SAPIInstance {
                                        StringBuilder sb = new StringBuilder();
                                        if (sapi_output!=null)
                                                sb.append(sapi_output);
-                                       sb.append("PFTT: later web server 
returned exit code("+exit_code+") and output:\n");
+                                       sb.append("\nPFTT: later web server 
returned exit code("+exit_code+") and output:\n");
                                        sb.append(output);
                                        sapi_output = sb.toString();
                                }
@@ -81,8 +88,9 @@ public abstract class WebServerInstance extends SAPIInstance {
                        
                        StringBuilder sb = new StringBuilder(1024);
                        
-                       sb.append("PFTT: web server crashed with exit code: 
"+exit_code);
+                       sb.append("PFTT: web server crashed with exit code: 
"+exit_code+"\n");
                        getActiveTestListString(sb);
+                       getAllTestListString(sb);
                        if (StringUtil.isEmpty(output)) {
                                sb.append("PFTT: web server returned no output 
when it exited.\n");
                        } else {
@@ -94,7 +102,7 @@ public abstract class WebServerInstance extends SAPIInstance 
{
                } // end sync
        } // end protected void notifyCrash
        
-       protected void getActiveTestListString(StringBuilder sb) {
+       public void getActiveTestListString(StringBuilder sb) {
                synchronized(active_test_cases) {
                        sb.append("PFTT: while running these 
tests("+active_test_cases.size()+"):\n");
                        for (PhptTestCase test_case : active_test_cases ) {
@@ -111,11 +119,31 @@ public abstract class WebServerInstance extends 
SAPIInstance {
                return sb.toString();
        }
        
+       public void getAllTestListString(StringBuilder sb) {
+               synchronized(all_test_cases) {
+                       sb.append("PFTT: these tests were run against this web 
server instance during its lifetime("+all_test_cases.size()+"):\n");
+                       for (PhptTestCase test_case : all_test_cases ) {
+                               sb.append("PFTT: ");
+                               sb.append(test_case.getName());
+                               sb.append('\n');
+                       }
+               }
+       }
+       
+       public String getAllTestListString() {
+               StringBuilder sb = new StringBuilder(512);
+               getAllTestListString(sb);
+               return sb.toString();
+       }
+       
        /** called before HTTP request made to server for given test_case
         * 
         * @param test_case
         */
        public void notifyTestPreRequest(PhptTestCase test_case) {
+               synchronized(all_test_cases) {
+                       all_test_cases.add(test_case);
+               }
                synchronized(active_test_cases) {
                        active_test_cases.add(test_case);
                }
@@ -161,4 +189,20 @@ public abstract class WebServerInstance extends 
SAPIInstance {
                return cmd_array;
        }
        
+       @Override
+       public void close() {
+               try {
+                       do_close();
+               } finally {
+                       synchronized(ws_mgr.instances) {
+                               ws_mgr.instances.remove(this);
+                       }
+               }
+               // TODO temp
+               for ( WebServerInstance c=replacement ; c != null ; c = 
c.replacement )
+                       c.close();
+       }
+       
+       protected abstract void do_close();
+       
 } // end public abstract class WebServerInstance
diff --git a/src/com/mostc/pftt/model/sapi/WebServerManager.java 
b/src/com/mostc/pftt/model/sapi/WebServerManager.java
index 195c57c..1b060a9 100644
--- a/src/com/mostc/pftt/model/sapi/WebServerManager.java
+++ b/src/com/mostc/pftt/model/sapi/WebServerManager.java
@@ -2,7 +2,6 @@ package com.mostc.pftt.model.sapi;
 
 import java.io.IOException;
 import java.net.Socket;
-import java.util.Iterator;
 import java.util.LinkedList;
 
 import javax.annotation.concurrent.ThreadSafe;
@@ -32,11 +31,9 @@ public abstract class WebServerManager extends SAPIManager {
         */
        public void close() {
                synchronized(instances) {
-                       Iterator<WebServerInstance> it = instances.iterator();
-                       while (it.hasNext()) {
-                               it.next().close();
-                               it.remove(); 
-                       }
+                       for ( WebServerInstance wsi : instances )
+                               wsi.do_close();
+                       instances.clear();
                }
        }
        
@@ -60,12 +57,12 @@ public abstract class WebServerManager extends SAPIManager {
        public WebServerInstance getWebServerInstance(Host host, PhpBuild 
build, PhpIni ini, String docroot, WebServerInstance assigned) {
                WebServerInstance sapi;
                if (assigned!=null) {
-                       if (!assigned.isCrashed())
+                       if (assigned.isRunning())
                                return assigned;
                        synchronized(assigned) {
                                for (WebServerInstance c=assigned.replacement ; 
c != null ; c = c.replacement) {
                                        synchronized(c) {
-                                               if (!c.isCrashed())
+                                               if (assigned.isRunning())
                                                        return c;
                                        }
                                }
@@ -77,7 +74,7 @@ public abstract class WebServerManager extends SAPIManager {
                } else {
                        sapi = createWebServerInstance(host, build, ini, 
docroot);
                }
-               if (!sapi.isCrashed()) {
+               if (sapi.isRunning()) {
                        synchronized(instances) {
                                instances.add(sapi);
                        }
diff --git a/src/com/mostc/pftt/runner/AbstractPhptTestCaseRunner2.java 
b/src/com/mostc/pftt/runner/AbstractPhptTestCaseRunner2.java
index 041d692..5df370e 100644
--- a/src/com/mostc/pftt/runner/AbstractPhptTestCaseRunner2.java
+++ b/src/com/mostc/pftt/runner/AbstractPhptTestCaseRunner2.java
@@ -9,6 +9,8 @@ import java.util.regex.Pattern;
 import java.util.zip.Deflater;
 import java.util.zip.DeflaterOutputStream;
 
+import javax.annotation.Nonnull;
+
 import org.incava.util.diff.Diff;
 
 import com.mostc.pftt.host.Host;
@@ -122,7 +124,7 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
                //
                if (host.isWindows() && 
test_case.containsSection(EPhptSection.SKIPIF)) {
                        // do an early quick check
-                       // fixes problem with Sapi/cli/tests/021.phpt
+                       // fixes problem with sapi/cli/tests/021.phpt
                        if (test_case.get(EPhptSection.SKIPIF).contains("skip 
not for Windows")) {
                                twriter.addResult(new PhptTestResult(host, 
EPhptTestStatus.XSKIP, test_case, "skip not for Windows", null, null, null, 
null, null, null, null, null, null, null));
                                
@@ -458,10 +460,13 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
        protected abstract void prepareSTDIN() throws IOException;
        
        /** executes the test (the TEST section of PhptTestCase) and returns 
the actual output
+        *
+        * must not return null
         * 
         * @return
         * @throws Exception
         */
+       @Nonnull
        protected abstract String executeTest() throws Exception;
        
        /** executes CLEAN section of test to cleanup after execution
@@ -512,7 +517,7 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
                                twriter.show_exception(test_case, ex, expected);
                                throw ex;
                        }
-                       if (expected_re_match) {
+                       if (expected_re_match||check()) {
 
                                twriter.addResult(new PhptTestResult(host, 
test_case.isXFail()?EPhptTestStatus.XFAIL:EPhptTestStatus.PASS, test_case, 
output, null, null, charset, env, splitCmdString(), stdin_post, shell_script, 
null, null, null));
                                                
@@ -524,7 +529,7 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
                        output = remove_header_from_output(output);
                        output_trim = output.trim();
        
-                       if 
(output_trim.equals(expected)||output_trim.contains(expected)||expected.contains(output_trim))
 {
+                       if 
(output_trim.equals(expected)||output_trim.contains(expected)||expected.contains(output_trim)||check())
 {
                                
                                twriter.addResult(new PhptTestResult(host, 
test_case.isXFail()?EPhptTestStatus.XFAIL:EPhptTestStatus.PASS, test_case, 
output, null, null, charset, env, splitCmdString(), stdin_post, shell_script, 
null, null, null));
                                                
@@ -534,9 +539,13 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
                        preoverride_actual = output_trim;
                        output_trim = 
PhptOverrideManager.replaceWithExactOverrides(host, output_trim);
                                
-                       if (output_trim!=null) {
+                       if (output_trim==null) {
+                               // no output overrides for this phpt on this OS
+                               //
+                               // fall through to failing or xfailing the test
+                       } else {
                                // compare again
-                               if 
(output_trim.equals(expected)||output_trim.contains(expected)||expected.contains(output_trim)||(output_trim.length()>20&&expected.length()>20&&output_trim.substring(10,
 20).equals(expected.substring(10, 20)))) {
+                               if 
(output_trim.equals(expected)||output_trim.contains(expected)||expected.contains(output_trim)||(output_trim.length()>20&&expected.length()>20&&output_trim.substring(10,
 20).equals(expected.substring(10, 20)))||check()) {
                                        
                                        twriter.addResult(new 
PhptTestResult(host, 
test_case.isXFail()?EPhptTestStatus.XFAIL:EPhptTestStatus.PASS, test_case, 
output, null, null, charset, env, splitCmdString(), stdin_post, shell_script, 
null, null, null));
                                        
@@ -547,7 +556,7 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
                        output = remove_header_from_output(output);
                        output_trim = output.trim();
                        
-                       if (StringUtil.isEmpty(output_trim)) {
+                       if (StringUtil.isEmpty(output_trim)||check()) {
                                twriter.addResult(new PhptTestResult(host, 
test_case.isXFail()?EPhptTestStatus.XFAIL:EPhptTestStatus.PASS, test_case, 
output, null, null, charset, env, splitCmdString(), stdin_post, shell_script, 
null, null, null));
                                
                                return;
@@ -577,6 +586,11 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
                        twriter.addResult(new PhptTestResult(host, 
EPhptTestStatus.FAIL, test_case, output, actual_lines, expected_lines, charset, 
env, splitCmdString(), stdin_post, shell_script, diff, expectf, 
preoverride_actual, getCrashedSAPIOutput()));
                }
        } // end void evalTest
+       
+       protected boolean check() {
+               // TODO temp
+               return StringUtil.isEmpty(getCrashedSAPIOutput());
+       }
 
        protected abstract String[] splitCmdString();
        
diff --git a/src/com/mostc/pftt/runner/HttpTestCaseRunner.java 
b/src/com/mostc/pftt/runner/HttpTestCaseRunner.java
index 03aa9f2..311f071 100644
--- a/src/com/mostc/pftt/runner/HttpTestCaseRunner.java
+++ b/src/com/mostc/pftt/runner/HttpTestCaseRunner.java
@@ -3,30 +3,18 @@ package com.mostc.pftt.runner;
 import java.io.IOException;
 import java.net.Socket;
 
-import org.apache.http.ConnectionReuseStrategy;
 import org.apache.http.HttpHost;
-import org.apache.http.HttpRequestInterceptor;
 import org.apache.http.HttpResponse;
-import org.apache.http.HttpVersion;
 import org.apache.http.entity.ByteArrayEntity;
-import org.apache.http.impl.DefaultConnectionReuseStrategy;
 import org.apache.http.impl.DefaultHttpClientConnection;
 import org.apache.http.message.BasicHttpEntityEnclosingRequest;
 import org.apache.http.message.BasicHttpRequest;
 import org.apache.http.params.HttpParams;
-import org.apache.http.params.HttpProtocolParams;
-import org.apache.http.params.SyncBasicHttpParams;
 import org.apache.http.protocol.BasicHttpContext;
 import org.apache.http.protocol.ExecutionContext;
 import org.apache.http.protocol.HttpContext;
 import org.apache.http.protocol.HttpProcessor;
 import org.apache.http.protocol.HttpRequestExecutor;
-import org.apache.http.protocol.ImmutableHttpProcessor;
-import org.apache.http.protocol.RequestConnControl;
-import org.apache.http.protocol.RequestContent;
-import org.apache.http.protocol.RequestExpectContinue;
-import org.apache.http.protocol.RequestTargetHost;
-import org.apache.http.protocol.RequestUserAgent;
 
 import com.github.mattficken.io.IOUtil;
 import com.mostc.pftt.host.Host;
@@ -41,6 +29,7 @@ import com.mostc.pftt.runner.PhptTestPackRunner.PhptThread;
 import com.mostc.pftt.scenario.ScenarioSet;
 import com.mostc.pftt.telemetry.PhptTelemetryWriter;
 import com.mostc.pftt.telemetry.PhptTestResult;
+import com.mostc.pftt.util.ErrorUtil;
 
 /** Runs PHPT Test Cases against PHP while its running under a Web Server 
(builtin, IIS or Apache)
  * 
@@ -50,10 +39,16 @@ import com.mostc.pftt.telemetry.PhptTestResult;
 
 public class HttpTestCaseRunner extends AbstractPhptTestCaseRunner2 {
        protected final WebServerManager smgr;
+       protected final HttpParams params;
+       protected final HttpProcessor httpproc;
+       protected final HttpRequestExecutor httpexecutor;
        protected WebServerInstance web = null;
 
-       public HttpTestCaseRunner(WebServerManager smgr, WebServerInstance web, 
PhptThread thread, PhptTestCase test_case, PhptTelemetryWriter twriter, Host 
host, ScenarioSet scenario_set, PhpBuild build, PhptTestPack test_pack) {
+       public HttpTestCaseRunner(HttpParams params, HttpProcessor httpproc, 
HttpRequestExecutor httpexecutor, WebServerManager smgr, WebServerInstance web, 
PhptThread thread, PhptTestCase test_case, PhptTelemetryWriter twriter, Host 
host, ScenarioSet scenario_set, PhpBuild build, PhptTestPack test_pack) {
                super(web.getPhpIni(), thread, test_case, twriter, host, 
scenario_set, build, test_pack);
+               this.params = params;
+               this.httpproc = httpproc;
+               this.httpexecutor = httpexecutor;
                this.smgr = smgr;
                this.web = web;
        }
@@ -71,96 +66,95 @@ public class HttpTestCaseRunner extends 
AbstractPhptTestCaseRunner2 {
                return false;
        }
        
-       @Override
-       public boolean prepare() throws IOException, Exception {
-               if (!super.prepare())
-                       return false;
-               
-               // #super.prepare sets ini already
-               //
-               // all we need to do is manage the web server and http requests
-               //
-               // make sure a web server is running
-               web = smgr.getWebServerInstance(host, build, ini, 
test_pack.getTestPack(), web);
-               
-               return true;
-       }
-       
        /** executes SKIPIF, TEST or CLEAN over http.
         * 
         * retries request if it times out and restarts web server if it crashes
         * 
         * @param path
-        * @param is_test
+        * @param section
         * @return
         * @throws Exception
         */
-       protected String http_execute(String path, boolean is_test) throws 
Exception {
+       protected String http_execute(String path, EPhptSection section) throws 
Exception {
                try {
-                       // "PFTT: server failed to respond at all after 
ONE_MINUTE. server was restarted and failed to respond at all a second time 
after ONE_MINUTE";
-                       // "PFTT: server failed to send all of its response 
after ONE_MINUTE. server was restarted and failed to send all of its response a 
second time after ONE_MINUTE";
                        try {
-                               return do_http_execute(path, is_test);
+                               return do_http_execute(path, section, false);
                        } catch ( IOException ex1 ) { // SocketTimeoutException 
or ConnectException
+                               // notify of crash so it gets reported 
everywhere
+                               web.notifyCrash("PFTT: timeout during 
test("+section+" SECTION): "+test_case.getName()+"\n"+ErrorUtil.toString(ex1), 
0);
+                               // TODO temp VS
                                web.close();
-                               // stop web server, try again
-                               web = smgr.getWebServerInstance(host, build, 
ini, test_pack.getTestPack(), web);
-                               try {
-                                       return do_http_execute(path, is_test);
-                               } catch ( IOException ex2 ) { // 
SocketTimeoutException or ConnectException
-                                       web.close();
-                                       // stop web server, try again
-                                       web = smgr.getWebServerInstance(host, 
build, ini, test_pack.getTestPack(), web);
-                                       try {
-                                               return do_http_execute(path, 
is_test);
-                                       } catch ( IOException ex3 ) { // 
SocketTimeoutException or ConnectException
-                                               web.close();
-                                               // stop web server, try again
-                                               // TODO temp null
-                                               web = 
smgr.getWebServerInstance(host, build, ini, test_pack.getTestPack(), web);
-                                               try {
-                                                       return 
do_http_execute(path, is_test);
-                                               } finally {
-                                                       web.close();
-                                               }
-                                       }
-                               }
+                               
+                               System.out.println("RESTART_AND_RETRY 
"+test_case.getName());
+                               
+                               // get #do_http_execute to make a new server
+                               // this will make a new WebServerInstance that 
will only be used to run this 1 test
+                               // (so other tests will not interfere with this 
test at all)
+                               web = null; 
+                               return do_http_execute(path, section, true);
                        }
                } catch ( IOException ioe ) {
-                       // wrap IOException with list of tests that are running 
against this web server
-                       // so we can tell what test(s) may be causing server to 
not respond
+                       String ex_str = ErrorUtil.toString(ioe);
                        
-                       // TODO comment
+                       // notify web server that it crashed. it will record 
this, which will be accessible
+                       // with WebServerInstance#getSAPIOutput (will be 
recorded by PhptTelemetryWriter)
+                       web.notifyCrash("PFTT: IOException during 
test("+section+" SECTION): "+test_case.getName()+"\n"+ex_str, 0);
                        
-                       web.notifyCrash("", 0);
+                       // generate a failure string here too though, so that 
this TEST or SKIPIF section is marked as a failure
+                       StringBuilder sb = new StringBuilder(512);
+                       sb.append("PFTT: couldn't connect to server after One 
Minute\n");
+                       sb.append("PFTT: created new server only for running 
this test which did not respond after another One Minute timeout\n");
+                       sb.append("PFTT: was trying to run ("+section+" section 
of): ");
+                       sb.append(test_case.getName());
+                       sb.append("\n");
+                       sb.append("PFTT: these two lists refer only to second 
server (created for specifically for only this test)\n");
+                       web.getActiveTestListString(sb);
+                       web.getAllTestListString(sb);
                        
-                       String ex_str = "PFTT: couldn't connect to server after 
4 tries (and 3 restarts), assuming crashed.\nPFTT: was trying to test (TEST 
section of): "+test_case.getName()+"\n"+web.getActiveTestListString();
-                       
-                       if (is_test) {
-                               this.twriter.addResult(new PhptTestResult(host, 
EPhptTestStatus.EXCEPTION, test_case, ex_str, null, null, null, null, null, 
null, null, null, null, null, ex_str));
-                               
-                               return ex_str;
-                       }
-                       
-                       throw new IOException(ex_str, ioe);
+                       // if TEST, runner will evaluate this as a failure
+                       // if SKIPIF, runner will not skip test and will try to 
run it
+                       //
+                       // both are the most ideal behavior possible in this 
situation
+                       //
+                       // normally this shouldn't happen, so checking a string 
once in a while is faster than
+                       //     setting a flag here and checking that flag for 
every test in #evalTest
+                       return sb.toString();
                }
        } // end protected String http_execute
        
-       protected String do_http_execute(String path, boolean is_test) throws 
Exception {
-               // make sure a web server is running
-               web = smgr.getWebServerInstance(host, build, ini, 
test_pack.getTestPack(), web);
-               if (web.isCrashed())
-                       // test will fail 
-                       return "PFTT: server crashed already, didn't bother 
trying to execute test";
-               
-               if (stdin_post==null)
-                       return do_http_get(path);
-               else
-                       return do_http_post(path);
+       protected String do_http_execute(String path, EPhptSection section, 
boolean is_replacement) throws Exception {
+               {
+                       WebServerInstance _web = 
smgr.getWebServerInstance(host, build, ini, test_pack.getTestPack(), web);
+                       if (_web!=web) {
+                               this.web = _web;
+                               is_replacement = true;
+                               // make sure this test case is in the list
+                               _web.notifyTestPreRequest(test_case);
+                       }
+               }
+               try {
+                       if (web.isCrashed())
+                               // test will fail (because this(`PFTT: 
server...`) is the actual output which won't match the expected output)
+                               //
+                               // return server's crash output and an 
additional message about this test
+                               return web.getSAPIOutput() + "PFTT: server 
crashed already, didn't bother trying to execute test: "+test_case.getName();
+                       
+                       
+                       if (stdin_post==null || section != EPhptSection.TEST)
+                               return do_http_get(path);
+                       else
+                               // only do POST for TEST sections where 
stdin_post!=null
+                               return do_http_post(path);
+               } finally {
+                       if (is_replacement)
+                               // CRITICAL: if this WebServerInstance is a 
replacement, then it exists only within this specific HttpTestCaseRunner
+                               // instance. if it is not terminated here, it 
will keep running forever!
+                               web.close();
+               }
        }
                
        protected String do_http_get(String path) throws Exception {
-               HttpParams params = new SyncBasicHttpParams();
+               /*HttpParams params = new SyncBasicHttpParams();
                HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
                HttpProtocolParams.setContentCharset(params, "UTF-8");
                HttpProtocolParams.setUserAgent(params, "Mozilla/5.0 (Windows 
NT 6.1; rv:12.0) Gecko/ 20120405 Firefox/14.0.1");
@@ -175,13 +169,13 @@ public class HttpTestCaseRunner extends 
AbstractPhptTestCaseRunner2 {
                        new RequestUserAgent(),
                        new RequestExpectContinue()});
                
-               HttpRequestExecutor httpexecutor = new HttpRequestExecutor();
+               HttpRequestExecutor httpexecutor = new HttpRequestExecutor();*/
                
                HttpContext context = new BasicHttpContext(null);
                HttpHost http_host = new HttpHost(web.hostname(), web.port());
                
                DefaultHttpClientConnection conn = new 
DefaultHttpClientConnection();
-               ConnectionReuseStrategy connStrategy = new 
DefaultConnectionReuseStrategy();
+               //ConnectionReuseStrategy connStrategy = new 
DefaultConnectionReuseStrategy();
                
                context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
                context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, 
http_host);
@@ -213,13 +207,13 @@ public class HttpTestCaseRunner extends 
AbstractPhptTestCaseRunner2 {
        } // end protected String do_http_get
        
        protected String do_http_post(String path) throws Exception {
-               HttpParams params = new SyncBasicHttpParams();
+               /*HttpParams params = new SyncBasicHttpParams();
                HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
                HttpProtocolParams.setContentCharset(params, "UTF-8");
                HttpProtocolParams.setUserAgent(params, "Mozilla/5.0 (Windows 
NT 6.1; rv:12.0) Gecko/ 20120405 Firefox/14.0.1");
                HttpProtocolParams.setUseExpectContinue(params, true);
-               if (content_type!=null)
-                       params.setParameter("Content-Type", content_type);
+               // TODO if (content_type!=null)
+               //      params.setParameter("Content-Type", content_type);
                                
                HttpProcessor httpproc = new ImmutableHttpProcessor(new 
HttpRequestInterceptor[] {// XXX reuse
                        // Required protocol interceptors
@@ -230,13 +224,13 @@ public class HttpTestCaseRunner extends 
AbstractPhptTestCaseRunner2 {
                        new RequestUserAgent(),
                        new RequestExpectContinue()});
                
-               HttpRequestExecutor httpexecutor = new HttpRequestExecutor();
+               HttpRequestExecutor httpexecutor = new HttpRequestExecutor();*/
                
                HttpContext context = new BasicHttpContext(null);
                HttpHost http_host = new HttpHost(web.hostname(), web.port());
                
                DefaultHttpClientConnection conn = new 
DefaultHttpClientConnection();
-               ConnectionReuseStrategy connStrategy = new 
DefaultConnectionReuseStrategy();
+               //ConnectionReuseStrategy connStrategy = new 
DefaultConnectionReuseStrategy();
                
                context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
                context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, 
http_host);
@@ -280,17 +274,17 @@ public class HttpTestCaseRunner extends 
AbstractPhptTestCaseRunner2 {
        
        @Override
        protected String executeSkipIf() throws Exception {
-               return http_execute(test_skipif, false);
+               return http_execute(test_skipif, EPhptSection.SKIPIF);
        }
 
        @Override
        protected String executeTest() throws Exception {
-               return http_execute(test_file, true);
+               return http_execute(test_file, EPhptSection.TEST);
        }
 
        @Override
        protected void executeClean() throws Exception {
-               http_execute(test_clean, false);
+               http_execute(test_clean, EPhptSection.CLEAN);
        }
 
        @Override
diff --git a/src/com/mostc/pftt/runner/PhptTestPackRunner.java 
b/src/com/mostc/pftt/runner/PhptTestPackRunner.java
index 52e1ccd..44db4be 100644
--- a/src/com/mostc/pftt/runner/PhptTestPackRunner.java
+++ b/src/com/mostc/pftt/runner/PhptTestPackRunner.java
@@ -31,7 +31,7 @@ import com.mostc.pftt.util.HostEnvUtil;
  */
 
 public class PhptTestPackRunner extends AbstractTestPackRunner {
-       protected static final int MAX_THREAD_COUNT = 48;
+       protected static final int MAX_THREAD_COUNT = 64; // TODO 32
        protected final PhptTestPack test_pack;
        protected final PhptTelemetryWriter twriter;
        protected ETestPackRunnerState runner_state;
@@ -208,6 +208,8 @@ public class PhptTestPackRunner extends 
AbstractTestPackRunner {
                                // execute any remaining thread safe jobs
                                runThreadSafe();
                                
+                       } catch ( Exception ex ) {
+                               ex.printStackTrace();
                        } finally {
                                if (run_thread.get())
                                        // if #stopThisThread not called
@@ -293,16 +295,10 @@ public class PhptTestPackRunner extends 
AbstractTestPackRunner {
                                        twriter.show_exception(test_case, ex);
                                } 
                                
-                               if (counter>10) {
-                                       // TODO temp
-                                       ((WebServerInstance)ini).close();
-                                       
-                                       // critical: provide existing 
WebServerInstance to #getWebServerInstance
-                                       //           or will get multiple 
zombie php.exe web servers, etc...
-                                       ini = 
((AbstractWebServerScenario)sapi_scenario).smgr.getWebServerInstance(host, 
build, ((WebServerInstance)ini).getPhpIni(), test_pack.getTestPack(), 
((WebServerInstance)ini));
-                                       
-                                       counter = 0;
-                               }
+                               // @see HttpTestCaseRunner#http_execute which 
calls #notifyCrash
+                               // make sure a WebServerInstance is still 
running here, so it will be shared with each
+                               // test runner instance (otherwise each test 
runner will create its own instance, which is slow)
+                               ini = 
((AbstractWebServerScenario)sapi_scenario).smgr.getWebServerInstance(host, 
build, ((WebServerInstance)ini).getPhpIni(), test_pack.getTestPack(), 
((WebServerInstance)ini));
                                
                                counter++;
                                
diff --git a/src/com/mostc/pftt/scenario/AbstractWebServerScenario.java 
b/src/com/mostc/pftt/scenario/AbstractWebServerScenario.java
index f0e4afd..41bc6c4 100644
--- a/src/com/mostc/pftt/scenario/AbstractWebServerScenario.java
+++ b/src/com/mostc/pftt/scenario/AbstractWebServerScenario.java
@@ -1,5 +1,19 @@
 package com.mostc.pftt.scenario;
 
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.HttpVersion;
+import org.apache.http.params.HttpParams;
+import org.apache.http.params.HttpProtocolParams;
+import org.apache.http.params.SyncBasicHttpParams;
+import org.apache.http.protocol.HttpProcessor;
+import org.apache.http.protocol.HttpRequestExecutor;
+import org.apache.http.protocol.ImmutableHttpProcessor;
+import org.apache.http.protocol.RequestConnControl;
+import org.apache.http.protocol.RequestContent;
+import org.apache.http.protocol.RequestExpectContinue;
+import org.apache.http.protocol.RequestTargetHost;
+import org.apache.http.protocol.RequestUserAgent;
+
 import com.mostc.pftt.host.Host;
 import com.mostc.pftt.model.phpt.PhpBuild;
 import com.mostc.pftt.model.phpt.PhptTestCase;
@@ -21,29 +35,40 @@ import com.mostc.pftt.telemetry.PhptTelemetryWriter;
 
 public abstract class AbstractWebServerScenario extends AbstractSAPIScenario {
        public final WebServerManager smgr;
+       protected final HttpParams params;
+       protected final HttpProcessor httpproc;
+       protected final HttpRequestExecutor httpexecutor;
        
        protected AbstractWebServerScenario(WebServerManager smgr) {
                this.smgr = smgr;
+               
+               params = new SyncBasicHttpParams();
+               HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
+               HttpProtocolParams.setContentCharset(params, "UTF-8");
+               HttpProtocolParams.setUserAgent(params, "Mozilla/5.0 (Windows 
NT 6.1; rv:12.0) Gecko/ 20120405 Firefox/14.0.1");
+               HttpProtocolParams.setUseExpectContinue(params, true);
+               
+               httpproc = new ImmutableHttpProcessor(new 
HttpRequestInterceptor[] {// XXX reuse
+                       // Required protocol interceptors
+                       new RequestContent(),
+                       new RequestTargetHost(),
+                       // Recommended protocol interceptors
+                       new RequestConnControl(),
+                       new RequestUserAgent(),
+                       new RequestExpectContinue()});
+               
+               httpexecutor = new HttpRequestExecutor();
        }
        
        @Override
        public AbstractPhptTestCaseRunner createPhptTestCaseRunner(PhptThread 
thread, TestCaseGroupKey ini, PhptTestCase test_case, PhptTelemetryWriter 
twriter, Host host, ScenarioSet scenario_set, PhpBuild build, PhptTestPack 
test_pack) {
-               return new HttpTestCaseRunner(smgr, (WebServerInstance)ini, 
thread, test_case, twriter, host, scenario_set, build, test_pack);
+               return new HttpTestCaseRunner(params, httpproc, httpexecutor, 
smgr, (WebServerInstance)ini, thread, test_case, twriter, host, scenario_set, 
build, test_pack);
        }
        
        @Override
        public void close() {
                smgr.close();
        }
-
-       /*WebServerInstance web; // TODO temp
-       @Override
-       public synchronized TestCaseGroupKey createTestGroupKey(Host host, 
PhpBuild build, PhptTestPack test_pack, PhptTestCase test_case) {
-               // TODO use HttpTestCaseRunner to get docroot from test_pack
-               if (web!=null)
-                       return web;
-               return web = smgr.getWebServerInstance(host, build, 
AbstractPhptTestCaseRunner.createIniForTest(host, build, test_pack, test_case), 
test_pack.getTestPack(), null);
-       }*/
        
        public boolean willSkip(PhptTelemetryWriter twriter, Host host, 
PhpBuild build, PhptTestCase test_case) throws Exception {
                return HttpTestCaseRunner.willSkip(twriter, host, build, 
test_case);
diff --git a/src/com/mostc/pftt/scenario/BuiltinWebServerScenario.java 
b/src/com/mostc/pftt/scenario/BuiltinWebServerScenario.java
index c685de0..7eddeed 100644
--- a/src/com/mostc/pftt/scenario/BuiltinWebServerScenario.java
+++ b/src/com/mostc/pftt/scenario/BuiltinWebServerScenario.java
@@ -47,7 +47,7 @@ public class BuiltinWebServerScenario extends 
AbstractWebServerScenario {
        @Override
        public int getTestThreadCount(Host host) {
                // XXX update this calculation from time to time as this web 
server's performance improves
-               return 8 * (Math.max(2, host.getCPUCount())/2);
+               return 64; // TODO 8 * (Math.max(2, host.getCPUCount())/2);
        }
 
 }
diff --git a/src/com/mostc/pftt/telemetry/ConsoleManager.java 
b/src/com/mostc/pftt/telemetry/ConsoleManager.java
new file mode 100644
index 0000000..75ce99d
--- /dev/null
+++ b/src/com/mostc/pftt/telemetry/ConsoleManager.java
@@ -0,0 +1,23 @@
+package com.mostc.pftt.telemetry;
+
+import com.mostc.pftt.model.phpt.PhptTestCase;
+import com.mostc.pftt.model.phpt.EPhptTestStatus;
+
+public class ConsoleManager {
+       
+       public ConsoleManager(boolean result_only) {
+               
+       }
+
+       public void finishedTest(PhptTestCase test_case, EPhptTestStatus 
status) {
+               
+       }
+       
+       public void retryingTest(PhptTestCase test_case) {
+               
+       }
+       
+       public void restartingSAPI() {
+               
+       }
+}
diff --git a/src/com/mostc/pftt/telemetry/PhptTestResult.java 
b/src/com/mostc/pftt/telemetry/PhptTestResult.java
index 0c9d2e1..b869268 100644
--- a/src/com/mostc/pftt/telemetry/PhptTestResult.java
+++ b/src/com/mostc/pftt/telemetry/PhptTestResult.java
@@ -102,7 +102,7 @@ public class PhptTestResult {
        
        protected void write(File file, String text) throws IOException {
                try {
-                       file.mkdirs();
+                       file.getParentFile().mkdirs();
                        
                        FileWriter fw = new FileWriter(file);
                        fw.write(text, 0, text.length());
diff --git a/src/com/mostc/pftt/ui/PhptHostTab.java 
b/src/com/mostc/pftt/ui/PhptHostTab.java
index fafd770..a030392 100644
--- a/src/com/mostc/pftt/ui/PhptHostTab.java
+++ b/src/com/mostc/pftt/ui/PhptHostTab.java
@@ -111,7 +111,7 @@ public class PhptHostTab extends JSplitPane {
                panel.add(unsupported_label = new JLabel("0"));
                panel.add(new JLabel("Exceptions:"));
                panel.add(exceptions_label = new JLabel("0"));
-               panel.add(new JLabel("Crashes:"));
+               panel.add(new JLabel("Failed Retries:"));
                panel.add(crash_label = new JLabel("0"));
   
                ///////////////
@@ -214,7 +214,7 @@ public class PhptHostTab extends JSplitPane {
                                        showList(exceptions_list_model); 
                        } });
                list_button_group.add(list_exceptions_rb);
-               status_list_menu.add(list_crash_rb = new 
JRadioButtonMenuItem("Crash"));
+               status_list_menu.add(list_crash_rb = new 
JRadioButtonMenuItem("Failed Retries"));
                list_crash_rb.addActionListener(new ActionListener() { public 
void actionPerformed(ActionEvent e) { 
                                if (list_crash_rb.isSelected())
                                        showList(crash_list_model); 
diff --git a/src/com/mostc/pftt/util/HostEnvUtil.java 
b/src/com/mostc/pftt/util/HostEnvUtil.java
index 3d31011..a4876fa 100644
--- a/src/com/mostc/pftt/util/HostEnvUtil.java
+++ b/src/com/mostc/pftt/util/HostEnvUtil.java
@@ -33,8 +33,10 @@ public final class HostEnvUtil {
                // have to fix Windows Error Reporting from popping up and 
blocking execution:
                // silence reporting
                System.out.println("PFTT: HostEnvUtil: disabling Windows Error 
Reporting...");
-               boolean a = regQueryAdd(host, 
"HKCU\\Software\\Microsoft\\Windows\\Windows Error Reporting", "DontShowUI", 
"0x1", REG_DWORD);
-               boolean b = regQueryAdd(host, 
"HKCU\\Software\\Microsoft\\Windows\\Windows Error Reporting", "Disable", 
"0x1", REG_DWORD);
+               // TODO temp boolean a = regQueryAdd(host, 
"HKCU\\Software\\Microsoft\\Windows\\Windows Error Reporting", "DontShowUI", 
"0x1", REG_DWORD);
+               boolean a = regQueryAdd(host, 
"HKCU\\Software\\Microsoft\\Windows\\Windows Error Reporting", "DontShowUI", 
"0x0", REG_DWORD);
+               // TODO temp boolean b = regQueryAdd(host, 
"HKCU\\Software\\Microsoft\\Windows\\Windows Error Reporting", "Disable", 
"0x1", REG_DWORD);
+               boolean b = regQueryAdd(host, 
"HKCU\\Software\\Microsoft\\Windows\\Windows Error Reporting", "Disable", 
"0x0", REG_DWORD);
                // then, disable reporting
                if ( a || b ) {                 
                        // assume if registry had to be edited, that firewall 
has to be disabled (avoid doing this if possible because it requires user to 
approve elevation)
diff --git a/src/com/mostc/pftt/util/StringUtil.java 
b/src/com/mostc/pftt/util/StringUtil.java
index 559c57e..4b0c6e6 100644
--- a/src/com/mostc/pftt/util/StringUtil.java
+++ b/src/com/mostc/pftt/util/StringUtil.java
@@ -15,6 +15,17 @@ import com.mostc.pftt.util.apache.regexp.REProgram;
  */
 
 public final class StringUtil {
+       public static final String EMPTY = "";
+       
+       public static String unquote(String in) {
+               if (isEmpty(in))
+                       return in;
+               while (in.startsWith("\"") || in.startsWith("'"))
+                       in = in.substring(1);
+               while (in.endsWith("\"") || in.endsWith("'"))
+                       in = in.substring(0, in.length()-1);
+               return in;
+       }
        
        public static boolean startsWithCS(String a, String b) {
                return a==null||b==null? false : a.startsWith(b);
@@ -56,12 +67,18 @@ public final class StringUtil {
                return sb.toString();
        }
        
+       public static final String[] EMPTY_ARRAY = new String[]{};
        public static String[] splitLines(String str) {
+               if (str==null)
+                       return EMPTY_ARRAY;
                str = replaceAll(PATTERN_R, "", str);
                return PATTERN_N.split(str);  
        }
        
        public static String[] splitEqualsSign(String str) {
+               if (str==null)
+                       return EMPTY_ARRAY;
+               
                return PATTERN_EQ.split(str);
        }

Reply via email to