Commit:    623984c0551588670aa414b298fa256957b49bf5
Author:    Matt Ficken <v-maf...@microsoft.com>         Thu, 12 Sep 2013 
15:21:36 -0700
Parents:   57572767543b8d116466755f855ec09f611b25df
Branches:  master

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

Log:
update required-features smoke test


Former-commit-id: 8e14ef49b15d9abfa2b1ecc9718126c79df3d0c0

Changed paths:
  M  src/com/mostc/pftt/model/app/PhpUnitDist.java
  M  src/com/mostc/pftt/model/app/PhpUnitTemplate.groovy
  M  src/com/mostc/pftt/model/core/PhpIni.java
  M  src/com/mostc/pftt/model/core/PhpParser.java
  M  src/com/mostc/pftt/model/core/PhptSourceTestPack.java
  M  src/com/mostc/pftt/model/core/PhptTestCase.java
  M  src/com/mostc/pftt/model/sapi/AbstractManagedProcessesWebServerManager.java
  M  src/com/mostc/pftt/model/sapi/ApacheManager.java
  M  src/com/mostc/pftt/model/sapi/BuiltinWebServerManager.java
  M  src/com/mostc/pftt/model/sapi/CliSAPIInstance.java
  M  src/com/mostc/pftt/model/sapi/CrashedWebServerInstance.java
  M  src/com/mostc/pftt/model/sapi/IISManager.java
  M  src/com/mostc/pftt/model/sapi/SAPIInstance.java
  M  src/com/mostc/pftt/model/sapi/TestCaseGroupKey.java
  M  src/com/mostc/pftt/model/sapi/WebServerInstance.java
  M  src/com/mostc/pftt/model/sapi/WebServerManager.java
  M  src/com/mostc/pftt/model/smoke/RequiredFeaturesSmokeTest.java
  M  src/com/mostc/pftt/results/ConsoleManager.java
  M  src/com/mostc/pftt/runner/BuiltinWebHttpPhptTestCaseRunner.java
  M  src/com/mostc/pftt/runner/CliPhptTestCaseRunner.java
  M  src/com/mostc/pftt/runner/HttpPhpUnitTestCaseRunner.java
  M  src/com/mostc/pftt/runner/HttpPhptTestCaseRunner.java
  M  src/com/mostc/pftt/runner/LocalPhpUnitTestPackRunner.java
  M  src/com/mostc/pftt/runner/LocalPhptTestPackRunner.java
  M  src/com/mostc/pftt/scenario/BuiltinWebServerScenario.java
  M  src/com/mostc/pftt/scenario/CLIScenario.java
  M  src/com/mostc/pftt/scenario/DatabaseScenario.java
  M  src/com/mostc/pftt/scenario/IScenarioSetup.java
  M  src/com/mostc/pftt/scenario/MSSQLScenario.java
  M  src/com/mostc/pftt/scenario/MySQLScenario.java
  M  src/com/mostc/pftt/scenario/NginxScenario.java
  M  src/com/mostc/pftt/scenario/ODBCScenario.java
  M  src/com/mostc/pftt/scenario/PostgresSQLScenario.java
  M  src/com/mostc/pftt/scenario/ProductionWebServerScenario.java
  M  src/com/mostc/pftt/scenario/SAPIScenario.java
  M  src/com/mostc/pftt/scenario/SimpleScenarioSetup.java
  M  src/com/mostc/pftt/scenario/WebServerScenario.java

diff --git a/src/com/mostc/pftt/model/app/PhpUnitDist.java 
b/src/com/mostc/pftt/model/app/PhpUnitDist.java
index c0b6dc2..66fb61e 100644
--- a/src/com/mostc/pftt/model/app/PhpUnitDist.java
+++ b/src/com/mostc/pftt/model/app/PhpUnitDist.java
@@ -85,7 +85,7 @@ public class PhpUnitDist {
                if (_include_path!=null)
                        return _include_path;
                
-               String pear_path = host.joinIntoOnePath(host.getPfttDir(), 
"/cache/util/PEAR/pear");
+               String pear_path = host.joinIntoOnePath(host.getPfttCacheDir(), 
"/util/PEAR/pear");
                
                return _include_path = 
host.joinMultiplePaths(host.joinMultiplePaths(src_test_pack.include_dirs, 
pear_path), path.getAbsolutePath());
        }
diff --git a/src/com/mostc/pftt/model/app/PhpUnitTemplate.groovy 
b/src/com/mostc/pftt/model/app/PhpUnitTemplate.groovy
index 7cc4233..75ccef1 100644
--- a/src/com/mostc/pftt/model/app/PhpUnitTemplate.groovy
+++ b/src/com/mostc/pftt/model/app/PhpUnitTemplate.groovy
@@ -484,4 +484,29 @@ ob_end_clean();
  * 
  */
        
+       public static String renderXDebugPhptTemplate(String php_code) {
+               // add php code to collect code coverage data and output it
+               return """<?php
+xdebug_start_code_coverage( XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE );
+function dump_coverage() {
+       echo PHP_EOL; 
+       foreach ( xdebug_get_code_coverage() as \$filename => \$coverage ) {
+               echo "file=\$filename"; echo PHP_EOL;
+               foreach ( \$coverage as \$line_num => \$type ) {
+                       if (\$type==1) {
+                               echo "exe=\$line_num"; echo PHP_EOL;
+                       } else if (\$type==-1) {
+                               echo "didnt_exe=\$line_num"; echo PHP_EOL;
+                       } else if (\$type==-2) {
+                               echo "no_exe=\$line_num"; echo PHP_EOL;
+                       }
+               }
+               xdebug_stop_code_coverage(TRUE);
+       }
+}
+register_shutdown_function('dump_coverage');
+?>""".replace("\r", "").replace("\n", " ") + php_code
+               // reduce the added php code to 1 extra line (remove \n)
+       }
+       
 } // end class PhpUnitTemplate
diff --git a/src/com/mostc/pftt/model/core/PhpIni.java 
b/src/com/mostc/pftt/model/core/PhpIni.java
index fb0803b..dc4182d 100644
--- a/src/com/mostc/pftt/model/core/PhpIni.java
+++ b/src/com/mostc/pftt/model/core/PhpIni.java
@@ -594,4 +594,12 @@ public class PhpIni {
        static final Pattern PAT_pipe = Pattern.compile("\\|");
        static final Pattern PAT_per = Pattern.compile("\\%");
        
+       public boolean containsAny(String ...directives) {
+               for ( String d : directives ) {
+                       if (containsKey(d))
+                               return true;
+               }
+               return false;
+       }
+       
 } // end public class PhpIni
diff --git a/src/com/mostc/pftt/model/core/PhpParser.java 
b/src/com/mostc/pftt/model/core/PhpParser.java
index 9776bc1..d7b2923 100644
--- a/src/com/mostc/pftt/model/core/PhpParser.java
+++ b/src/com/mostc/pftt/model/core/PhpParser.java
@@ -153,8 +153,9 @@ public class PhpParser {
                                return new ClassDefinition[]{};
                        } else {
                                Collection<InterpretedClassDef> clazzes = 
prog.getClasses();
-                               ClassDefinition[] defs = new 
ClassDefinition[clazzes.size()];
+                               ClassDefinition[] defs = new 
ClassDefinition[clazzes.size()+1];
                                int i=0;
+                               defs[i++] = new MainClassDefinition(env, this, 
prog.getStatement());
                                for ( InterpretedClassDef clazz : clazzes )
                                        defs[i++] = new ClassDefinition(env, 
this, clazz);
                                return defs;
@@ -687,6 +688,47 @@ public class PhpParser {
                
        } // end public static abstract class CodeBlock
        
+       public static class MainClassDefinition extends ClassDefinition {
+               protected final Statement stmt;
+               protected MainClassDefinition(Env env, PhpScript ps, Statement 
stmt) {
+                       super(env, ps, null);
+                       this.stmt = stmt;
+               }
+               public String getName() {
+                       return "<main>";
+               }
+               public Field[] getFields() {
+                       return new Field[]{};
+               }
+               public FunctionDefinition[] getFunctions() {
+                       return new FunctionDefinition[] {
+                               new FunctionDefinition(env, this, stmt)
+                       };
+               }
+               public boolean isInterface() {
+                       return false;
+               }
+               public boolean isAbstract() {
+                       return false;
+               }
+               public boolean isFinal() {
+                       return false;
+               }
+               @Override
+               public boolean equals(Object o) {
+                       if (o==this)
+                               return true;
+                       else if (o instanceof MainClassDefinition)
+                               return true;
+                       else
+                               return toString().equals(o.toString());
+               }
+               @Override
+               public String toString() {
+                       return getName();
+               }
+       }
+       
        public static class ClassDefinition extends CodeBlock {
                protected final ClassDef clazz;
                protected final PhpScript ps;
@@ -887,23 +929,33 @@ public class PhpParser {
        public static class FunctionDefinition extends CodeBlock {
                protected final AbstractFunction def;
                protected final ClassDefinition clazz;
+               protected final BlockStatement stmt;
                
                protected FunctionDefinition(Env env, ClassDefinition clazz, 
AbstractFunction def) {
                        super(env);
                        this.def = def;
                        this.clazz = clazz;
+                       this.stmt = def instanceof Function && 
((Function)def)._statement instanceof BlockStatement ?
+                                       
((BlockStatement)((Function)def)._statement) : null;
                }
                
+               public FunctionDefinition(Env env, MainClassDefinition clazz, 
Statement stmt) {
+                       super(env);
+                       this.def = null;
+                       this.clazz = clazz;
+                       this.stmt = stmt instanceof BlockStatement ? 
(BlockStatement) stmt : null;
+               }
+
                public int getLineNumber() {
-                       return def.getLocation().getLineNumber();
+                       return def==null?0:def.getLocation().getLineNumber();
                }
                
                public String getClassName() {
-                       return def.getLocation().getClassName();
+                       return def==null?"":def.getLocation().getClassName();
                }
 
                public String getFileName() {
-                       return def.getLocation().getFileName();
+                       return def==null?"":def.getLocation().getFileName();
                }
 
                public ClassDefinition getClassDefinition() {
@@ -916,11 +968,11 @@ public class PhpParser {
                 */
                @Override
                public String getName() {
-                       return def.getName();
+                       return def==null?"":def.getName();
                }
                
                public int getArgumentCount() {
-                       return def.getArgs().length;
+                       return def==null?0:def.getArgs().length;
                }
                
                /** returns the arguments this function accepts
@@ -928,6 +980,8 @@ public class PhpParser {
                 * @return
                 */
                public VariableValue[] getArguments() {
+                       if (def==null)
+                               return new VariableValue[]{};
                        Arg[] args = def.getArgs();
                        VariableValue[] val = new VariableValue[args.length];
                        int i=0;
@@ -946,12 +1000,12 @@ public class PhpParser {
                 */
                public FunctionCall[] getCalls() {
                        ArrayList<FunctionCall> out = new 
ArrayList<FunctionCall>(2);
-                       if (def instanceof Function && 
((Function)def)._statement instanceof BlockStatement) {
+                       if (stmt != null) {
                                String call_name, class_name;
                                int i;
                                AbstractFunction f;
                                QuercusClass qc;
-                               for ( Statement st : 
((BlockStatement)((Function)def)._statement)._statements ) {
+                               for ( Statement st : stmt._statements ) {
                                        if (st instanceof ExprStatement) {
                                                Expr expr = 
((ExprStatement)st)._expr;
                                                if (expr instanceof CallExpr) {
diff --git a/src/com/mostc/pftt/model/core/PhptSourceTestPack.java 
b/src/com/mostc/pftt/model/core/PhptSourceTestPack.java
index a1c7119..230301d 100644
--- a/src/com/mostc/pftt/model/core/PhptSourceTestPack.java
+++ b/src/com/mostc/pftt/model/core/PhptSourceTestPack.java
@@ -51,7 +51,11 @@ public class PhptSourceTestPack implements 
SourceTestPack<PhptActiveTestPack, Ph
                return getSourceDirectory();
        }
        
-       public boolean open(ConsoleManager cm, AHost host) {
+       public String getTestPackConfigFilePath() {
+               return getSourceDirectory()+"/config.pftt.groovy";
+       }
+       
+       public boolean open(ConsoleManager cm, Config config, AHost host) {
                if (StringUtil.endsWithIC(this.test_pack, ".zip")) {
                        // automatically decompress build
                        String zip_file = test_pack;
@@ -63,7 +67,20 @@ public class PhptSourceTestPack implements 
SourceTestPack<PhptActiveTestPack, Ph
                
                this.host = host;
                this.test_pack = host.fixPath(test_pack);
-               return host.exists(this.test_pack);
+               if (host.exists(this.test_pack)) {
+                       String config_file = getTestPackConfigFilePath();
+                       if (host.exists(config_file)) {
+                               try {
+                                       config.addConfigFile(cm, new 
File(config_file));
+                               } catch ( Exception ex ) {
+                                       cm.addGlobalException(EPrintType.CLUE, 
getClass(), "open", ex, "Unable to load test-pack configuration");
+                               }
+                       }
+                       
+                       return true;
+               } else {
+                       return false;
+               }
        }
        
        @Override
@@ -86,6 +103,8 @@ public class PhptSourceTestPack implements 
SourceTestPack<PhptActiveTestPack, Ph
                // these are symlinks(junctions) which may cause an infinite 
loop
                //
                // normally, they are deleted, but if certain tests were 
interrupted, they may still be there
+               host.deleteIfExists(test_pack+"/ext/standard/tests/file/a_dir");
+               
host.deleteIfExists(test_pack+"/ext/standard/tests/file/a_jdir");
                host.deleteIfExists(test_pack+"/ext/standard/tests/file/12345");
                
host.deleteIfExists(test_pack+"/ext/standard/tests/file/clearstatcache_001.php_link1");
                
host.deleteIfExists(test_pack+"/ext/standard/tests/file/clearstatcache_001.php_link2");
diff --git a/src/com/mostc/pftt/model/core/PhptTestCase.java 
b/src/com/mostc/pftt/model/core/PhptTestCase.java
index 4b47f14..cc06a13 100644
--- a/src/com/mostc/pftt/model/core/PhptTestCase.java
+++ b/src/com/mostc/pftt/model/core/PhptTestCase.java
@@ -66,6 +66,7 @@ public class PhptTestCase extends TestCase {
         * 
         * Reminder: if -no-nts console option is used, this list is ignored 
(that option allows tests to be run on any thread regardless of thread-safety)
         * */
+       // TODO SOMEDAY store this list in the test-pack's PFTT configuration 
file
        public static final String[][] NON_THREAD_SAFE_EXTENSIONS = new 
String[][]{
                        // split up the ext/standard/tests/file PHPTs
                        // they can be run in 1 thread, but split them into 
several threads so they'll all finish faster
@@ -81,12 +82,13 @@ public class PhptTestCase extends TestCase {
                        new String[]{"ext/standard/tests/file/windows_acls/", 
"ext/standard/tests/file/windows_links/"},
                        // note: this array is processed in order, so this 
entry will catch any remaining /file/ phpts
                        new String[]{"ext/standard/tests/dir/"},
+                       new String[]{"ext/standard/tests/string/fprint"},
                        new String[]{"ext/standard/tests/streams/stream_get_"},
                        new String[]{"ext/standard/tests/streams/stream_set_"},
                        new String[]{"ext/standard/tests/streams/"},
                        new String[]{"ext/standard/tests/sockets/", 
"ext/sockets/"},
                        new String[]{"ext/mysqli/tests/0", 
"ext/mysqli/tests/bug"},
-                       new String[]{"ext/phar/tests/frontcontroller", 
"ext/phar/tests/cache_list/copyonwrite", "ext/phar/tests/zip/copyonwrite", 
"ext/phar/tests/tar/copyonwrite", "ext/phar/cache_list/frontcontroller", 
"ext/phar/zip/frontcontroller", "ext/phar/tar/frontcontroller"},
+                       new String[]{"ext/mbstring/tests/mb_output_"},
                        new String[]{"ext/pgsql/"},
                        new String[]{"ext/pdo_pgsql/"},
                        // several 61367 tests that aren't thread-safe (temp 
files)
@@ -104,8 +106,10 @@ public class PhptTestCase extends TestCase {
                        new String[]{"ext/soap/"},
                        new String[]{"ext/fileinfo/"},
                        new String[]{"ext/ldap/"},
-                       new String[]{"ext/spl/tests/splfileobject_fputcsv_"}, 
// TODO
-                       new String[]{"ext/spl/tests/splfileobject_fgetcsv_"}
+                       new String[]{"ext/spl/tests/splfileobject_fputcsv_"},
+                       new String[]{"ext/spl/tests/splfileobject_fgetcsv_"},
+                       new String[]{"ext/pdo_sqlsrv/tests/"},
+                       new String[]{"ext/sqlsrv/tests/"},
                };
        // PHPT test files end with .phpt
        public static final String PHPT_FILE_EXTENSION = ".phpt";
@@ -1034,8 +1038,16 @@ public class PhptTestCase extends TestCase {
         * @return
         */
        public boolean isSlowTest() {
-               return isExtension("phar") || 
isExtension("standard/tests/streams") || isNamed(SLOW_TESTS);
+               return isExtension(SLOW_EXTS) || isNamed(SLOW_TESTS);
        }
+       public static Trie SLOW_EXTS = createExtensions(
+                       "tests/security", 
+                       "phar", 
+                       "ctype",
+                       "spl/tests/spl_autoload_", 
+                       "session",
+                       "standard/tests/streams"
+               );
        public static Trie SLOW_TESTS = createNamed(
                                // tests that check the SKIP_SLOW_TESTS env var 
(ie tests considered slow by their authors)
                                //
@@ -1107,6 +1119,67 @@ public class PhptTestCase extends TestCase {
                                "ext/session/tests/020.phpt",
                                "zend/tests/unset_cv05.phpt"
                        );
+       public static int hashCode(PhpIni ini) {
+               if (ini==null)
+                       return 1;
+               int hc = 1;
+               for ( String d : DECISIVE_DIRECTIVES ) {
+                       String v = ini.get(d);
+                       if (v!=null)
+                               hc += v.hashCode();
+               }
+               return hc;
+       }
+       public static final String[] DECISIVE_DIRECTIVES = new String[]{
+                       //"filter.default",
+                       //"mbstring.internal_encoding",
+                       //"mbstring.http_output_conv_mimetypes",
+                       //"output_handler",
+                       "output_buffering",
+                       "precision",
+                       "error_reporting",
+                       //"session.serialize_handler",
+                       //"html_errors",
+                       //"open_basedir",
+                       //"unicode.output_encoding",
+                       //"session.save_handler",
+                       //"session.auto_start"
+                       //"phar.require_hash",
+                       "phar.readonly",
+                       //"allow_url_fopen"
+               };
+       /** some INI directives will affect the result of a test case (`the 
decisive directives`)
+        * 
+        * This determines if the two PhpInis are either equal (the same) or
+        * have the `decisive directives` are at least equal.
+        * 
+        * @param a
+        * @param b
+        * @return
+        */
+       public static boolean isEquivalentForTestCase(PhpIni a, PhpIni b) {
+               if (a==null)
+                       return b==null || !b.containsAny(DECISIVE_DIRECTIVES);
+               else if (b==null)
+                       return a==null || !a.containsAny(DECISIVE_DIRECTIVES);
+               else if (a.equals(b))
+                       return true;
+               String sa, sb;
+               for ( String d : DECISIVE_DIRECTIVES ) {
+                       sa = a.get(d);
+                       sb = b.get(d);
+                       if (sa==null) {
+                               if (StringUtil.isNotEmpty(sb))
+                                       return false;
+                       } else if (sb==null) {
+                               if (StringUtil.isNotEmpty(sa))
+                                       return false;
+                       } else if (!sa.equalsIgnoreCase(sb)) {
+                               return false;
+                       }
+               }
+               return true;
+       }
 
        public static DefaultCharsetDeciderDecoder newCharsetDeciderDecoder() {
                return new 
DefaultCharsetDeciderDecoder(CharsetDeciderDecoder.EXPRESS_RECOGNIZERS);
diff --git 
a/src/com/mostc/pftt/model/sapi/AbstractManagedProcessesWebServerManager.java 
b/src/com/mostc/pftt/model/sapi/AbstractManagedProcessesWebServerManager.java
index db1506e..1f0b75e 100644
--- 
a/src/com/mostc/pftt/model/sapi/AbstractManagedProcessesWebServerManager.java
+++ 
b/src/com/mostc/pftt/model/sapi/AbstractManagedProcessesWebServerManager.java
@@ -228,7 +228,7 @@ public abstract class 
AbstractManagedProcessesWebServerManager extends WebServer
                                                                        }
                                                                } finally {
                                                                        // 
don't need to check any more
-                                                                       
thread.cancel();
+                                                                       
thread.close();
                                                                }
                                                        }
                                                } // end public void run
@@ -249,7 +249,7 @@ public abstract class 
AbstractManagedProcessesWebServerManager extends WebServer
                sapi_output = "PFTT: could not start web server instance (after 
"+total_attempts+" attempts)... giving up.\n" + sapi_output;
                
                // return this failure message to client code
-               return new CrashedWebServerInstance(this, ini, env, 
sapi_output);
+               return new CrashedWebServerInstance(host, this, ini, env, 
sapi_output);
        } // end protected WebServerInstance createWebServerInstance
        
        @Overridable
@@ -266,8 +266,8 @@ public abstract class 
AbstractManagedProcessesWebServerManager extends WebServer
                protected final String docroot;
                protected ExecHandle process;
                
-               public 
ManagedProcessWebServerInstance(AbstractManagedProcessesWebServerManager 
ws_mgr, String docroot, String cmd, PhpIni ini, Map<String,String> env, String 
hostname, int port) {
-                       super(ws_mgr, LocalHost.splitCmdString(cmd), ini, env);
+               public ManagedProcessWebServerInstance(AHost host, 
AbstractManagedProcessesWebServerManager ws_mgr, String docroot, String cmd, 
PhpIni ini, Map<String,String> env, String hostname, int port) {
+                       super(host, ws_mgr, LocalHost.splitCmdString(cmd), ini, 
env);
                        this.docroot = docroot;
                        this.cmd = cmd;
                        this.hostname = hostname;
diff --git a/src/com/mostc/pftt/model/sapi/ApacheManager.java 
b/src/com/mostc/pftt/model/sapi/ApacheManager.java
index 56a2e59..4f405e7 100644
--- a/src/com/mostc/pftt/model/sapi/ApacheManager.java
+++ b/src/com/mostc/pftt/model/sapi/ApacheManager.java
@@ -247,16 +247,14 @@ public class ApacheManager extends 
AbstractManagedProcessesWebServerManager {
        
        public class ApacheWebServerInstance extends 
ManagedProcessWebServerInstance {
                protected final String conf_dir, apache_conf_file, conf_str, 
error_log, apache_version_str;
-               protected final AHost host;
                protected final PhpBuild build;
                protected final EApacheVersion apache_version;
                protected SoftReference<String> log_ref;
                
                public ApacheWebServerInstance(EApacheVersion apache_version, 
PhpBuild build, ApacheManager ws_mgr, String docroot, String cmd, PhpIni ini, 
Map<String,String> env, String hostname, int port, AHost host, String conf_dir, 
String apache_conf_file, String error_log, String conf_str, String 
apache_version_str) {
-                       super(ws_mgr, docroot, cmd, ini, env, hostname, port);
+                       super(host, ws_mgr, docroot, cmd, ini, env, hostname, 
port);
                        this.build = build;
                        this.apache_version = apache_version;
-                       this.host = host;
                        this.conf_dir = conf_dir;
                        this.apache_conf_file = apache_conf_file;
                        this.error_log = error_log;
diff --git a/src/com/mostc/pftt/model/sapi/BuiltinWebServerManager.java 
b/src/com/mostc/pftt/model/sapi/BuiltinWebServerManager.java
index 6ba3279..e3d5d0e 100644
--- a/src/com/mostc/pftt/model/sapi/BuiltinWebServerManager.java
+++ b/src/com/mostc/pftt/model/sapi/BuiltinWebServerManager.java
@@ -68,11 +68,9 @@ public class BuiltinWebServerManager extends 
AbstractManagedProcessesWebServerMa
        
        public class BuiltinWebServerInstance extends 
ManagedProcessWebServerInstance {
                protected final PhpBuild build;
-               protected final AHost host;
                
                public BuiltinWebServerInstance(BuiltinWebServerManager ws_mgr, 
AHost host, PhpBuild build, String docroot, String cmd, PhpIni ini, 
Map<String,String> env, String hostname, int port) {
-                       super(ws_mgr, docroot, cmd, ini, env, hostname, port);
-                       this.host = host;
+                       super(host, ws_mgr, docroot, cmd, ini, env, hostname, 
port);
                        this.build = build;
                }
                
diff --git a/src/com/mostc/pftt/model/sapi/CliSAPIInstance.java 
b/src/com/mostc/pftt/model/sapi/CliSAPIInstance.java
index 50926af..0843d4c 100644
--- a/src/com/mostc/pftt/model/sapi/CliSAPIInstance.java
+++ b/src/com/mostc/pftt/model/sapi/CliSAPIInstance.java
@@ -11,15 +11,20 @@ import com.mostc.pftt.model.core.PhpIni;
 import com.mostc.pftt.results.ConsoleManager;
 
 public class CliSAPIInstance extends SAPIInstance {
-       protected final AHost host;
        protected final PhpBuild build;
-       protected final PhpIni ini;
        protected String ini_dir;
        
        public CliSAPIInstance(AHost host, PhpBuild build, PhpIni ini) {
-               this.host = host;
+               super(host, ini);
                this.build = build;
-               this.ini = ini;
+       }
+       
+       @Override
+       protected String doGetIniActual(String php_code) throws Exception {
+               return build.eval(host, php_code)
+                       .printHasFatalError(getClass().getSimpleName(), 
(ConsoleManager)null)
+                       .cleanupSuccess(host)
+                       .output;
        }
        
        public void prepare() throws Exception {
diff --git a/src/com/mostc/pftt/model/sapi/CrashedWebServerInstance.java 
b/src/com/mostc/pftt/model/sapi/CrashedWebServerInstance.java
index 8b2b3e1..1753d79 100644
--- a/src/com/mostc/pftt/model/sapi/CrashedWebServerInstance.java
+++ b/src/com/mostc/pftt/model/sapi/CrashedWebServerInstance.java
@@ -2,6 +2,7 @@ package com.mostc.pftt.model.sapi;
 
 import java.util.Map;
 
+import com.mostc.pftt.host.AHost;
 import com.mostc.pftt.model.core.PhpIni;
 import com.mostc.pftt.results.ConsoleManager;
 
@@ -18,12 +19,12 @@ public class CrashedWebServerInstance extends 
WebServerInstance {
        protected final String sapi_output;
        protected final String instance_info;
        
-       public CrashedWebServerInstance(WebServerManager ws_mgr, PhpIni ini, 
Map<String,String> env, String sapi_output) {
-               this(ws_mgr, ini, env, sapi_output, null);
+       public CrashedWebServerInstance(AHost host, WebServerManager ws_mgr, 
PhpIni ini, Map<String,String> env, String sapi_output) {
+               this(host, ws_mgr, ini, env, sapi_output, null);
        }
        
-       public CrashedWebServerInstance(WebServerManager ws_mgr, PhpIni ini, 
Map<String,String> env, String sapi_output, String instance_info) {
-               super(ws_mgr, null, ini, env);
+       public CrashedWebServerInstance(AHost host, WebServerManager ws_mgr, 
PhpIni ini, Map<String,String> env, String sapi_output, String instance_info) {
+               super(host, ws_mgr, null, ini, env);
                this.sapi_output = sapi_output;
                this.instance_info = instance_info;
        }
diff --git a/src/com/mostc/pftt/model/sapi/IISManager.java 
b/src/com/mostc/pftt/model/sapi/IISManager.java
index 4c2e71b..fdd5c55 100644
--- a/src/com/mostc/pftt/model/sapi/IISManager.java
+++ b/src/com/mostc/pftt/model/sapi/IISManager.java
@@ -107,7 +107,7 @@ public class IISManager extends 
AbstractManagedProcessesWebServerManager {
                env = prepareENV(env, prep.php_conf_file, build, scenario_set, 
build.getPhpCgiExe());
                
                
-               final String cmdline = 
host.getPfttDir()+"/cache/dep/IIS/IISRunner "+host.fixPath(prep.iis_conf_file);
+               final String cmdline = 
host.getPfttCacheDir()+"/dep/IIS/IISRunner "+host.fixPath(prep.iis_conf_file);
                
                // @see #createWebServerInstance for where command is executed 
to create httpd.exe process
                return new IISWebServerInstance(this, docroot, cmdline, env, 
ini, listen_address, port, prep, host, build);
@@ -115,14 +115,12 @@ public class IISManager extends 
AbstractManagedProcessesWebServerManager {
        
        public class IISWebServerInstance extends 
ManagedProcessWebServerInstance {
                protected final PreparedIIS prep;
-               protected final AHost host;
                protected final PhpBuild build;
                protected SoftReference<String> log_ref;
                
                public IISWebServerInstance(IISManager ws_mgr, String docroot, 
String cmd, Map<String,String> env, PhpIni ini, String hostname, int port, 
PreparedIIS prep, AHost host, PhpBuild build) {
-                       super(ws_mgr, docroot, cmd, ini, env, hostname, port);
+                       super(host, ws_mgr, docroot, cmd, ini, env, hostname, 
port);
                        this.build = build;
-                       this.host = host;
                        this.prep = prep;
                }
                
diff --git a/src/com/mostc/pftt/model/sapi/SAPIInstance.java 
b/src/com/mostc/pftt/model/sapi/SAPIInstance.java
index 8541ffa..972306c 100644
--- a/src/com/mostc/pftt/model/sapi/SAPIInstance.java
+++ b/src/com/mostc/pftt/model/sapi/SAPIInstance.java
@@ -1,5 +1,9 @@
 package com.mostc.pftt.model.sapi;
 
+import java.io.IOException;
+
+import com.mostc.pftt.host.AHost;
+import com.mostc.pftt.model.core.PhpIni;
 import com.mostc.pftt.results.ConsoleManager;
 import com.mostc.pftt.scenario.SimpleScenarioSetup;
 
@@ -10,9 +14,39 @@ import com.mostc.pftt.scenario.SimpleScenarioSetup;
  */
 
 public abstract class SAPIInstance extends SimpleScenarioSetup {
+       protected final AHost host;
+       protected final PhpIni ini;
+       protected String ini_actual;
+       
+       public SAPIInstance(AHost host, PhpIni ini) {
+               this.host = host;
+               this.ini = ini;
+       }
+       
        public abstract String getSAPIOutput();
        public abstract boolean isRunning();
        public abstract String getInstanceInfo(ConsoleManager cm);
        public abstract boolean isCrashedOrDebuggedAndClosed();
        public abstract String getSAPIConfig();
+       protected abstract String doGetIniActual(String php_code) throws 
IllegalStateException, IOException, Exception;
+       
+       public synchronized String getIniActual() {
+               if (ini_actual!=null)
+                       return ini_actual;
+               if (host.isBusy())
+                       // leave ini_actual NULL so we'll check next time
+                       return null; 
+               try {
+                       ini_actual = doGetIniActual("<?php 
var_dump($argv);\nvar_dump(ini_get_all()); ?>");
+               } catch ( Exception ex ) {
+                       ex.printStackTrace();
+               }
+               if (ini_actual==null)
+                       ini_actual = "";
+               return ini_actual;
+       }
+       
+       public boolean isBusy() {
+               return host.isBusy();
+       }
 }
diff --git a/src/com/mostc/pftt/model/sapi/TestCaseGroupKey.java 
b/src/com/mostc/pftt/model/sapi/TestCaseGroupKey.java
index 3c89ff1..5a00e58 100644
--- a/src/com/mostc/pftt/model/sapi/TestCaseGroupKey.java
+++ b/src/com/mostc/pftt/model/sapi/TestCaseGroupKey.java
@@ -1,17 +1,13 @@
 package com.mostc.pftt.model.sapi;
 
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
 import java.util.Map;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import com.mostc.pftt.model.core.PhpIni;
+import com.mostc.pftt.model.core.PhptTestCase;
 import com.mostc.pftt.results.ConsoleManager;
-import com.mostc.pftt.scenario.IScenarioSetup;
-import com.mostc.pftt.scenario.ScenarioSet;
 import com.mostc.pftt.util.IClosable;
 
 /**
@@ -23,7 +19,6 @@ import com.mostc.pftt.util.IClosable;
 public class TestCaseGroupKey implements IClosable {
        protected final Map<String,String> env;
        protected final PhpIni ini;
-       protected List<IScenarioSetup> setups;
        
        public TestCaseGroupKey(
                        @Nonnull PhpIni ini, 
@@ -32,14 +27,6 @@ public class TestCaseGroupKey implements IClosable {
                this.env = env;
        }
        
-       public TestCaseGroupKey(
-                       @Nonnull PhpIni ini, 
-                       @Nullable Map<String,String> env,
-                       ScenarioSet scenario_set) {
-               this(ini, env);
-               setups = new ArrayList<IScenarioSetup>(scenario_set.size());
-       }
-       
        @Override
        public boolean equals(Object o) {
                if (o==this) {
@@ -47,7 +34,7 @@ public class TestCaseGroupKey implements IClosable {
                } else if (o instanceof TestCaseGroupKey) {
                        TestCaseGroupKey c = (TestCaseGroupKey) o;
                        return 
(this.env==null?c.env==null||c.env.isEmpty():this.env.equals(c.env)) &&
-                                       
(this.ini==null?c.ini==null||c.ini.isEmpty():this.ini.equals(c.ini));
+                                       
PhptTestCase.isEquivalentForTestCase(this.ini, c.ini);
                } else {
                        return false;
                }
@@ -55,7 +42,7 @@ public class TestCaseGroupKey implements IClosable {
        
        @Override
        public int hashCode() {
-               return (env==null?1:env.hashCode()) | 
(ini==null?1:ini.hashCode());
+               return (env==null?1:env.hashCode()) | 
PhptTestCase.hashCode(ini);
        }
        
        @Nullable
@@ -73,18 +60,7 @@ public class TestCaseGroupKey implements IClosable {
 
        @Override
        public void close(ConsoleManager cm) {
-               if (setups==null)
-                       return;
-               for ( IScenarioSetup setup : setups ) {
-                       setup.close(cm);
-               }
-       }
-       
-       public void addSetup(IScenarioSetup setup) {
-               if (setups==null)
-                       setups = new LinkedList<IScenarioSetup>();
                
-               setups.add(setup);
        }
        
 } // end public class TestCaseGroupKey
diff --git a/src/com/mostc/pftt/model/sapi/WebServerInstance.java 
b/src/com/mostc/pftt/model/sapi/WebServerInstance.java
index 8d15c75..58d1b0a 100644
--- a/src/com/mostc/pftt/model/sapi/WebServerInstance.java
+++ b/src/com/mostc/pftt/model/sapi/WebServerInstance.java
@@ -1,5 +1,7 @@
 package com.mostc.pftt.model.sapi;
 
+import java.io.IOException;
+import java.net.URL;
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
@@ -7,6 +9,7 @@ import java.util.Map;
 
 import javax.annotation.concurrent.ThreadSafe;
 
+import com.github.mattficken.io.IOUtil;
 import com.github.mattficken.io.StringUtil;
 import com.mostc.pftt.host.AHost;
 import com.mostc.pftt.model.TestCase;
@@ -32,7 +35,8 @@ public abstract class WebServerInstance extends SAPIInstance 
implements IWebServ
        protected final WebServerManager ws_mgr;
        WebServerInstance replacement; // @see 
WebServerManager#getWebServerInstance
        
-       public WebServerInstance(WebServerManager ws_mgr, String[] cmd_array, 
PhpIni ini, Map<String,String> env) {
+       public WebServerInstance(AHost host, WebServerManager ws_mgr, String[] 
cmd_array, PhpIni ini, Map<String,String> env) {
+               super(host, ini);
                this.ws_mgr = ws_mgr;
                this.cmd_array = cmd_array;
                this.ini = ini;
@@ -231,4 +235,20 @@ public abstract class WebServerInstance extends 
SAPIInstance implements IWebServ
 
        public abstract String getDocroot();
        
+       protected String httpGet(String url_str, String php_code) throws 
IllegalStateException, IOException {
+               String temp_file = host.joinIntoOnePath(getDocroot(), url_str);
+               if (!host.saveTextFile(temp_file, php_code))
+                       return "";
+               
+               URL url = new URL("http", getHostname(), getPort(), url_str);
+               String output = IOUtil.toString(url.openStream(), 
IOUtil.QUARTER_MEGABYTE);
+               host.delete(temp_file);
+               return output;
+       }
+       
+       @Override
+       protected String doGetIniActual(String php_code) throws 
IllegalStateException, IOException {
+               return httpGet("ini_get_all.php", php_code);
+       }
+       
 } // 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 63daa54..aa64cdb 100644
--- a/src/com/mostc/pftt/model/sapi/WebServerManager.java
+++ b/src/com/mostc/pftt/model/sapi/WebServerManager.java
@@ -8,10 +8,12 @@ import java.util.Map;
 
 import javax.annotation.concurrent.ThreadSafe;
 
+import com.github.mattficken.io.StringUtil;
 import com.mostc.pftt.host.AHost;
 import com.mostc.pftt.host.Host;
 import com.mostc.pftt.model.core.PhpBuild;
 import com.mostc.pftt.model.core.PhpIni;
+import com.mostc.pftt.model.core.PhptTestCase;
 import com.mostc.pftt.results.ConsoleManager;
 import com.mostc.pftt.runner.AbstractPhptTestCaseRunner;
 import com.mostc.pftt.scenario.IScenarioSetup;
@@ -68,22 +70,41 @@ public abstract class WebServerManager extends SAPIManager {
        }
        
        private static boolean eq(WebServerInstance c, boolean 
debugger_attached, PhpIni ini, Map<String,String> env) {
-               if (c.isRunning())
-                       return true; // TODO temp 5/30
-               
-               // TODO note - AbstractManagedProcessesWebServerManager, 
BuiltinWebServerManager and ApacheManager
-               //             all add to `env` which means this `env` might 
not match (because its missing some extra keys)
-               //             (what about checking just the values of the keys 
they both have in common?)
                if (c.isRunning() && 
(!debugger_attached||c.isDebuggerAttached())) {
-                       if 
(c.getPhpIni()!=null&&ini!=null&&c.getPhpIni().equals(ini))
+                       if (PhptTestCase.isEquivalentForTestCase(c.getPhpIni(), 
ini))
                                return true;
-                       if (c.getEnv()!=null&&env!=null&&c.getEnv().equals(env))
+                       // the two ENVs may not be exactly the same. if 1 ENV 
has some additional
+                       // variables that the other does not, that is ok. Only 
the variables they both have MUST match.
+                       else if (equalsOrCommonValues(c.getEnv(), env))
                                return true;
-                       //return true;
                }
                return false;
        }
        
+       protected static boolean equalsOrCommonValues(Map<String, String> a, 
Map<String, String> b) {
+               if (a==null)
+                       return b == null || b.isEmpty();
+               else if (b==null)
+                       return a == null || a.isEmpty();
+               else if (a.equals(b))
+                       return true;
+               String va, vb;
+               for ( String key : a.keySet() ) {
+                       va = a.get(key);
+                       vb = b.get(key);
+                       if (va==null) {
+                               if (StringUtil.isNotEmpty(vb))
+                                       return false;
+                       } else if (vb==null) {
+                               if (StringUtil.isNotEmpty(va))
+                                       return false;
+                       } else if (!vb.equals(va)) {
+                               return false;
+                       }
+               }
+               return true;
+       }
+       
        /** gets a running WebServerInstance
         * 
         * if given an existing assigned WebServerInstance that hasn't crashed 
and is running,
@@ -119,6 +140,8 @@ public abstract class WebServerManager extends SAPIManager {
                                        synchronized(c) {
                                                if (eq(c, debugger_attached, 
ini, env))
                                                        return c;
+                                               else
+                                                       c.close(cm);
                                        }
                                }
                        }
diff --git a/src/com/mostc/pftt/model/smoke/RequiredFeaturesSmokeTest.java 
b/src/com/mostc/pftt/model/smoke/RequiredFeaturesSmokeTest.java
index 6d21719..e1860a8 100644
--- a/src/com/mostc/pftt/model/smoke/RequiredFeaturesSmokeTest.java
+++ b/src/com/mostc/pftt/model/smoke/RequiredFeaturesSmokeTest.java
@@ -487,7 +487,7 @@ public class RequiredFeaturesSmokeTest extends SmokeTest {
 "%s" +
 "Session Support => enabled%s" +
 "Registered save handlers => files user %s" +
-"Registered serializer handlers => php php_binary wddx%s" +
+"Registered serializer handlers => %sphp php_binary wddx%s" +
 "%s" +
 "Directive => Local Value => Master Value%s" +
 "session.auto_start => Off => Off%s" +
@@ -1075,7 +1075,7 @@ public class RequiredFeaturesSmokeTest extends SmokeTest {
 "%s" +
 "Session Support => enabled%s" +
 "Registered save handlers => files user %s" +
-"Registered serializer handlers => php php_binary wddx%s" +
+"Registered serializer handlers => %sphp php_binary wddx%s" +
 "%s" +
 "Directive => Local Value => Master Value%s" +
 "session.auto_start => Off => Off%s" +
diff --git a/src/com/mostc/pftt/results/ConsoleManager.java 
b/src/com/mostc/pftt/results/ConsoleManager.java
index 5fb6951..13780da 100644
--- a/src/com/mostc/pftt/results/ConsoleManager.java
+++ b/src/com/mostc/pftt/results/ConsoleManager.java
@@ -100,5 +100,6 @@ public interface ConsoleManager {
        public int getRunCount();
        public int getSuspendSeconds();
        public boolean isGetActualIniAll();
+       public long getMaxRunTimeMillis();
        
 } // end public class ConsoleManager
diff --git a/src/com/mostc/pftt/runner/BuiltinWebHttpPhptTestCaseRunner.java 
b/src/com/mostc/pftt/runner/BuiltinWebHttpPhptTestCaseRunner.java
index 72d28dd..6f9e8c4 100644
--- a/src/com/mostc/pftt/runner/BuiltinWebHttpPhptTestCaseRunner.java
+++ b/src/com/mostc/pftt/runner/BuiltinWebHttpPhptTestCaseRunner.java
@@ -25,7 +25,7 @@ import com.mostc.pftt.scenario.ScenarioSetSetup;
 
 public class BuiltinWebHttpPhptTestCaseRunner extends HttpPhptTestCaseRunner {
        
-       public BuiltinWebHttpPhptTestCaseRunner(BuiltinWebServerScenario 
sapi_scenario, PhpIni ini,
+       public BuiltinWebHttpPhptTestCaseRunner(boolean xdebug, 
BuiltinWebServerScenario sapi_scenario, PhpIni ini,
                        Map<String, String> env, HttpParams params, 
HttpProcessor httpproc,
                        HttpRequestExecutor httpexecutor, WebServerManager smgr,
                        WebServerInstance web, PhptThread thread, PhptTestCase 
test_case,
@@ -33,7 +33,7 @@ public class BuiltinWebHttpPhptTestCaseRunner extends 
HttpPhptTestCaseRunner {
                        ScenarioSetSetup scenario_set_setup, PhpBuild build,
                        PhptSourceTestPack src_test_pack,
                        PhptActiveTestPack active_test_pack) {
-               super(sapi_scenario, ini, env, params, httpproc, httpexecutor, 
smgr, web, thread, test_case,
+               super(xdebug, sapi_scenario, ini, env, params, httpproc, 
httpexecutor, smgr, web, thread, test_case,
                                cm, twriter, host, scenario_set_setup, build, 
src_test_pack, active_test_pack);
        }
        
diff --git a/src/com/mostc/pftt/runner/CliPhptTestCaseRunner.java 
b/src/com/mostc/pftt/runner/CliPhptTestCaseRunner.java
index 9fd871f..579df29 100644
--- a/src/com/mostc/pftt/runner/CliPhptTestCaseRunner.java
+++ b/src/com/mostc/pftt/runner/CliPhptTestCaseRunner.java
@@ -44,8 +44,8 @@ public class CliPhptTestCaseRunner extends 
AbstractPhptTestCaseRunner2 {
        protected ExecOutput output;
        protected String query_string, shell_script, test_cmd, shell_file;
        
-       public CliPhptTestCaseRunner(CliScenario sapi_scenario, CliSAPIInstance 
sapi, PhpIni ini, PhptThread thread, PhptTestCase test_case, ConsoleManager cm, 
ITestResultReceiver twriter, AHost host, ScenarioSetSetup scenario_set_setup, 
PhpBuild build, PhptSourceTestPack src_test_pack, PhptActiveTestPack 
active_test_pack) {
-               super(sapi_scenario, ini, thread, test_case, cm, twriter, host, 
scenario_set_setup, build, src_test_pack, active_test_pack);
+       public CliPhptTestCaseRunner(boolean xdebug, CliScenario sapi_scenario, 
CliSAPIInstance sapi, PhpIni ini, PhptThread thread, PhptTestCase test_case, 
ConsoleManager cm, ITestResultReceiver twriter, AHost host, ScenarioSetSetup 
scenario_set_setup, PhpBuild build, PhptSourceTestPack src_test_pack, 
PhptActiveTestPack active_test_pack) {
+               super(xdebug, sapi_scenario, ini, thread, test_case, cm, 
twriter, host, scenario_set_setup, build, src_test_pack, active_test_pack);
                this.sapi = sapi;
        }
        
@@ -72,11 +72,7 @@ public class CliPhptTestCaseRunner extends 
AbstractPhptTestCaseRunner2 {
                
        @Override
        public String getIniActual() throws Exception {
-               // @see #prepareTest
-               
-               String ini_get_all_path = get_ini_get_all_path();
-               
-               return ini_get_all_path+sapi.execute(exe_type, 
ini_get_all_path, null, env, AHost.ONE_MINUTE).output;
+               return sapi.getIniActual();
        }
        
        @Override
diff --git a/src/com/mostc/pftt/runner/HttpPhpUnitTestCaseRunner.java 
b/src/com/mostc/pftt/runner/HttpPhpUnitTestCaseRunner.java
index e489060..74a7982 100644
--- a/src/com/mostc/pftt/runner/HttpPhpUnitTestCaseRunner.java
+++ b/src/com/mostc/pftt/runner/HttpPhpUnitTestCaseRunner.java
@@ -325,7 +325,7 @@ public class HttpPhpUnitTestCaseRunner extends 
AbstractPhpUnitTestCaseRunner {
                        response.setParams(params);
                        httpexecutor.postProcess(response, httpproc, context);
                        
-                       timeout_task.cancel();
+                       timeout_task.close();
                        
                        //
                        // support for HTTP redirects
diff --git a/src/com/mostc/pftt/runner/HttpPhptTestCaseRunner.java 
b/src/com/mostc/pftt/runner/HttpPhptTestCaseRunner.java
index 0794afd..fafcd4f 100644
--- a/src/com/mostc/pftt/runner/HttpPhptTestCaseRunner.java
+++ b/src/com/mostc/pftt/runner/HttpPhptTestCaseRunner.java
@@ -62,8 +62,8 @@ public class HttpPhptTestCaseRunner extends 
AbstractPhptTestCaseRunner2 {
        protected final HttpRequestExecutor httpexecutor;
        protected Socket test_socket;
 
-       public HttpPhptTestCaseRunner(WebServerScenario sapi_scenario, PhpIni 
ini, Map<String,String> env, HttpParams params, HttpProcessor httpproc, 
HttpRequestExecutor httpexecutor, WebServerManager smgr, WebServerInstance web, 
PhptThread thread, PhptTestCase test_case, ConsoleManager cm, 
ITestResultReceiver twriter, AHost host, ScenarioSetSetup scenario_set, 
PhpBuild build, PhptSourceTestPack src_test_pack, PhptActiveTestPack 
active_test_pack) {
-               super(sapi_scenario, ini, thread, test_case, cm, twriter, host, 
scenario_set, build, src_test_pack, active_test_pack);
+       public HttpPhptTestCaseRunner(boolean xdebug, WebServerScenario 
sapi_scenario, PhpIni ini, Map<String,String> env, HttpParams params, 
HttpProcessor httpproc, HttpRequestExecutor httpexecutor, WebServerManager 
smgr, WebServerInstance web, PhptThread thread, PhptTestCase test_case, 
ConsoleManager cm, ITestResultReceiver twriter, AHost host, ScenarioSetSetup 
scenario_set, PhpBuild build, PhptSourceTestPack src_test_pack, 
PhptActiveTestPack active_test_pack) {
+               super(xdebug, sapi_scenario, ini, thread, test_case, cm, 
twriter, host, scenario_set, build, src_test_pack, active_test_pack);
                this.params = params;
                this.httpproc = httpproc;
                this.httpexecutor = httpexecutor;
@@ -230,6 +230,7 @@ public class HttpPhptTestCaseRunner extends 
AbstractPhptTestCaseRunner2 {
                                synchronized(web) {
                                        WebServerInstance _web = 
smgr.getWebServerInstance(cm, host, scenario_set.getScenarioSet(), build, ini, 
env, active_test_pack.getStorageDirectory(), web, false, test_case);
                                        if (_web!=this.web) {
+                                               this.web.close(cm);
                                                this.web = _web;
                                                is_replacement = true;
                                                                                
        
@@ -291,24 +292,9 @@ public class HttpPhptTestCaseRunner extends 
AbstractPhptTestCaseRunner2 {
                return do_http_get(path, 0);
        }
        
-       @Override // TODO
-       protected String get_ini_get_all_path() throws IllegalStateException, 
IOException {
-               // ensure ini_get_all.php exists
-               // TODO temp 5/15 - should store this once only and delete it 
later
-               String ini_get_all_path = 
host.joinIntoOnePath(active_test_pack.getStorageDirectory(), "ini_get_all.php");
-               if (!host.exists(ini_get_all_path)) {
-                       // TODO locking?
-                       host.saveTextFile(ini_get_all_path, "<?php 
var_dump($argv);\nvar_dump(ini_get_all()); ?>");
-               }
-               return ini_get_all_path;
-       }
-       
        @Override
        public String getIniActual() throws Exception {
-               // ensure ini_get_all.php has been created
-               get_ini_get_all_path();
-               
-               return do_http_get("/ini_get_all.php", 0);
+               return web == null ? null : web.getIniActual();
        }
        
        @Override
@@ -400,7 +386,7 @@ public class HttpPhptTestCaseRunner extends 
AbstractPhptTestCaseRunner2 {
                        response.setParams(params);
                        httpexecutor.postProcess(response, httpproc, context);
                        
-                       timeout_task.cancel();
+                       timeout_task.close();
                        
                        //
                        // support for HTTP redirects: used by some PHAR tests
@@ -514,7 +500,7 @@ public class HttpPhptTestCaseRunner extends 
AbstractPhptTestCaseRunner2 {
                        response.setParams(params);
                        httpexecutor.postProcess(response, httpproc, context);
                        
-                       timeout_task.cancel();
+                       timeout_task.close();
                        
                        //
                        // support for HTTP redirects: used by some PHAR tests
diff --git a/src/com/mostc/pftt/runner/LocalPhpUnitTestPackRunner.java 
b/src/com/mostc/pftt/runner/LocalPhpUnitTestPackRunner.java
index 5ee834c..8cc3444 100644
--- a/src/com/mostc/pftt/runner/LocalPhpUnitTestPackRunner.java
+++ b/src/com/mostc/pftt/runner/LocalPhpUnitTestPackRunner.java
@@ -23,6 +23,7 @@ import com.mostc.pftt.results.PhpUnitTestResult;
 import com.mostc.pftt.scenario.CodeCacheScenario;
 import com.mostc.pftt.scenario.EScenarioSetPermutationLayer;
 import com.mostc.pftt.scenario.FileSystemScenario.ITestPackStorageDir;
+import com.mostc.pftt.scenario.IScenarioSetup;
 import com.mostc.pftt.scenario.SMBScenario.SMBStorageDir;
 import com.mostc.pftt.scenario.WebServerScenario;
 import com.mostc.pftt.scenario.PhpUnitReflectionOnlyScenario;
@@ -70,8 +71,10 @@ public class LocalPhpUnitTestPackRunner extends 
AbstractLocalTestPackRunner<PhpU
                        long millis = System.currentTimeMillis();
                        for ( int i=0 ; i < 131070 ; i++ ) {
                                // try to include version, branch info etc... 
from name of test-pack
-                               local_test_pack_dir = local_path + "/PFTT-" + 
src_test_pack.getName() + (i==0?"":"-" + millis) + "/";
-                               remote_test_pack_dir = remote_path + "/PFTT-" + 
src_test_pack.getName() + (i==0?"":"-" + millis) + "/";
+                               //
+                               // don't want long directory paths or lots of 
nesting, just put in /php-sdk
+                               local_test_pack_dir = local_path + "/TEMP-" + 
src_test_pack.getName() + (i==0?"":"-" + millis) + "/";
+                               remote_test_pack_dir = remote_path + "/TEMP-" + 
src_test_pack.getName() + (i==0?"":"-" + millis) + "/";
                                if (!storage_host.exists(remote_test_pack_dir) 
|| !runner_host.exists(local_test_pack_dir))
                                        break;
                                millis++;
@@ -103,11 +106,15 @@ public class LocalPhpUnitTestPackRunner extends 
AbstractLocalTestPackRunner<PhpU
        } // end protected void setupStorageAndTestPack
        
        @Override
-       protected int decideThreadCount() {
-               int default_thread_count = super.decideThreadCount() * 2;
+       protected void decideThreadCount() {
+               super.decideThreadCount();
+               //init_thread_count *= 2;
                
                // some test-packs need different numbers of threads - ask
-               return Math.max(1, src_test_pack.getThreadCount(runner_host, 
scenario_set, default_thread_count));
+               init_thread_count = Math.max(1, 
src_test_pack.getThreadCount(runner_host, scenario_set, init_thread_count));
+               max_thread_count = init_thread_count * 2;
+               
+               checkThreadCountLimit();
        }
        
        @Override
@@ -165,6 +172,12 @@ public class LocalPhpUnitTestPackRunner extends 
AbstractLocalTestPackRunner<PhpU
                }
                
                @Override
+               protected long getMaxRunTimeMillis() {
+                       // phpunit tests are faster
+                       return super.getMaxRunTimeMillis() / 3;
+               }
+               
+               @Override
                public void run() {
                        super.run();
                        
@@ -211,6 +224,18 @@ public class LocalPhpUnitTestPackRunner extends 
AbstractLocalTestPackRunner<PhpU
                protected void recordSkipped(PhpUnitTestCase test_case) {
                        twriter.addResult(runner_host, scenario_set_setup, new 
PhpUnitTestResult(test_case, EPhpUnitTestStatus.TIMEOUT, scenario_set_setup, 
runner_host, null, null, 0, null, "PFTT: Test Timed Out", null));
                }
+
+               @Override
+               protected void prepareExec(TestCaseGroupKey group_key, PhpIni 
ini, Map<String,String> env, IScenarioSetup s) {
+                       if (!s.isNeededPhpUnitWriter())
+                               return;
+                       AbstractPhpUnitRW phpunit = 
((PhpResultPackWriter)twriter).getPhpUnit(
+                                       runner_host, 
+                                       
src_test_pack.getNameAndVersionString(), 
+                                       scenario_set_setup
+                               );
+                       s.setPhpUnitWriter(runner_host, scenario_set_setup, 
build, ini, phpunit);
+               }
                
        } // end public class PhpUnitThread
 
diff --git a/src/com/mostc/pftt/runner/LocalPhptTestPackRunner.java 
b/src/com/mostc/pftt/runner/LocalPhptTestPackRunner.java
index 952bff7..045773e 100644
--- a/src/com/mostc/pftt/runner/LocalPhptTestPackRunner.java
+++ b/src/com/mostc/pftt/runner/LocalPhptTestPackRunner.java
@@ -4,16 +4,20 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 
 import javax.annotation.Nullable;
 
 import com.mostc.pftt.host.AHost;
+import com.mostc.pftt.main.Config;
 import com.mostc.pftt.main.IENVINIFilter;
 import com.mostc.pftt.model.core.EPhptTestStatus;
 import com.mostc.pftt.model.core.ESAPIType;
 import com.mostc.pftt.model.core.PhpBuild;
+import com.mostc.pftt.model.core.PhpIni;
 import com.mostc.pftt.model.core.PhptActiveTestPack;
 import com.mostc.pftt.model.core.PhptSourceTestPack;
 import com.mostc.pftt.model.core.PhptTestCase;
@@ -26,9 +30,11 @@ import com.mostc.pftt.results.PhpResultPackWriter;
 import com.mostc.pftt.results.PhptResultWriter;
 import com.mostc.pftt.results.PhptTestResult;
 import com.mostc.pftt.scenario.EScenarioSetPermutationLayer;
+import com.mostc.pftt.scenario.IScenarioSetup;
 import com.mostc.pftt.scenario.Scenario;
 import com.mostc.pftt.scenario.ScenarioSet;
 import com.mostc.pftt.scenario.FileSystemScenario.ITestPackStorageDir;
+import com.mostc.pftt.scenario.XDebugScenario;
 
 /** Runs PHPTs from a given PhptTestPack.
  * 
@@ -40,10 +46,14 @@ import 
com.mostc.pftt.scenario.FileSystemScenario.ITestPackStorageDir;
 
 public class LocalPhptTestPackRunner extends 
AbstractLocalTestPackRunner<PhptActiveTestPack, PhptSourceTestPack, 
PhptTestCase> {
        protected final IENVINIFilter filter;
+       protected final boolean xdebug;
        
        public LocalPhptTestPackRunner(ConsoleManager cm, ITestResultReceiver 
twriter, ScenarioSet scenario_set, PhpBuild build, AHost storage_host, AHost 
runner_host, IENVINIFilter filter) {
                super(cm, twriter, scenario_set, build, storage_host, 
runner_host);
                this.filter = filter;
+               
+               // check once if this is using XDebug 
+               xdebug = scenario_set.contains(XDebugScenario.class);
        }
        
        @Override
@@ -63,9 +73,11 @@ public class LocalPhptTestPackRunner extends 
AbstractLocalTestPackRunner<PhptAct
                        long millis = System.currentTimeMillis();
                        for ( int i=0 ; i < 131070 ; i++ ) {
                                // try to include version, branch info etc... 
from name of test-pack
+                               //
                                // CRITICAL: that directory paths end with / 
(@see {PWD} in PhpIni)
-                               local_test_pack_dir = local_path + "/PFTT-" + 
AHost.basename(src_test_pack.getSourceDirectory()) + (i==0?"":"-" + millis) + 
"/";
-                               remote_test_pack_dir = remote_path + "/PFTT-" + 
AHost.basename(src_test_pack.getSourceDirectory()) + (i==0?"":"-" + millis) + 
"/";
+                               // don't want long directory paths or lots of 
nesting, just put in /php-sdk (breaks some PHPTs)
+                               local_test_pack_dir = local_path + "/TEMP-" + 
AHost.basename(src_test_pack.getSourceDirectory()) + (i==0?"":"-" + millis) + 
"/";
+                               remote_test_pack_dir = remote_path + "/TEMP-" + 
AHost.basename(src_test_pack.getSourceDirectory()) + (i==0?"":"-" + millis) + 
"/";
                                if (!storage_host.exists(remote_test_pack_dir) 
|| !runner_host.exists(local_test_pack_dir))
                                        break;
                                millis++;
@@ -105,6 +117,9 @@ public class LocalPhptTestPackRunner extends 
AbstractLocalTestPackRunner<PhptAct
                        }
                        
                        cm.println(EPrintType.IN_PROGRESS, getClass(), 
"installed tests("+test_cases.size()+") from test-pack onto storage: 
local="+local_test_pack_dir+" remote="+remote_test_pack_dir);
+                       
+                       // TODO
+                       ((Config)filter).prepareTestPack(cm, runner_host, 
scenario_set_setup, build, src_test_pack);
                }
        } // end protected void setupStorageAndTestPack
 
@@ -163,13 +178,37 @@ public class LocalPhptTestPackRunner extends 
AbstractLocalTestPackRunner<PhptAct
        
        @Override
        protected void postGroup(LinkedList<TestCaseGroup<PhptTestCase>> 
thread_safe_list, List<PhptTestCase> test_cases) {
-               // run larger groups first
-               Collections.sort(thread_safe_list, new 
Comparator<TestCaseGroup<PhptTestCase>>() {
-                               @Override
-                               public int compare(TestCaseGroup<PhptTestCase> 
a, TestCaseGroup<PhptTestCase> b) {
-                                       return b.test_cases.size() - 
a.test_cases.size();
+               // evenly mix up large and small groups
+               {
+                       
HashMap<Integer,LinkedList<TestCaseGroup<PhptTestCase>>> map = new 
HashMap<Integer,LinkedList<TestCaseGroup<PhptTestCase>>>();
+                       Integer key;
+                       LinkedList<TestCaseGroup<PhptTestCase>> l;
+                       for ( TestCaseGroup<PhptTestCase> tg : thread_safe_list 
) {
+                               key = new Integer(tg.test_cases.size());
+                               l = map.get(key);
+                               if (l==null) {
+                                       l = new 
LinkedList<TestCaseGroup<PhptTestCase>>();
+                                       map.put(key, l);
                                }
-                       });
+                               l.add(tg);
+                       }
+                       LinkedList<Integer> c = new LinkedList<Integer>();
+                       c.addAll(map.keySet());
+                       Collections.sort(c);
+                       // reverse - run larger groups first
+                       Collections.reverse(c);
+                       thread_safe_list.clear();
+                       for ( Integer j : c )
+                               thread_safe_list.addAll(map.get(j));
+                       for ( Integer k : c ) {
+                               l = map.get(k);
+                               if (l==null)
+                                       continue;
+                               thread_safe_list.add(l.removeFirst());
+                               if (l.isEmpty())
+                                       map.remove(k);
+                       } // end for
+               }
                ArrayList<PhptTestCase> buf;
                for ( TestCaseGroup<PhptTestCase> a : thread_safe_list ) {
                        buf = new ArrayList<PhptTestCase>(a.test_cases.size());
@@ -179,6 +218,21 @@ public class LocalPhptTestPackRunner extends 
AbstractLocalTestPackRunner<PhptAct
                        for ( PhptTestCase t : buf )
                                a.test_cases.add(t);
                }
+               LinkedList<NonThreadSafeExt<PhptTestCase>> b = new 
LinkedList<NonThreadSafeExt<PhptTestCase>>();
+               b.addAll(non_thread_safe_exts);
+               Collections.sort(b, new 
Comparator<NonThreadSafeExt<PhptTestCase>>() {
+                               @Override
+                               public int 
compare(NonThreadSafeExt<PhptTestCase> a, NonThreadSafeExt<PhptTestCase> b) {
+                                       int ca = 0, cb = 0;
+                                       for ( TestCaseGroup<PhptTestCase> g : 
a.test_groups )
+                                               ca += g.test_cases.size();
+                                       for ( TestCaseGroup<PhptTestCase> g : 
b.test_groups )
+                                               cb += g.test_cases.size();
+                                       return cb - ca;
+                               }
+                       });
+               non_thread_safe_exts.clear();
+               non_thread_safe_exts.addAll(b);
        } // end protected void postGroup
        
        protected void reportGroups() {
@@ -188,7 +242,9 @@ public class LocalPhptTestPackRunner extends 
AbstractLocalTestPackRunner<PhptAct
        
        @Override
        protected TestPackThread<PhptTestCase> createTestPackThread(boolean 
parallel) {
-               return new PhptThread(parallel);
+               PhptThread thread = new PhptThread(parallel);
+               ((Config)filter).prepareTestPackPerThread(cm, runner_host, 
thread, scenario_set_setup, build, src_test_pack);
+               return thread;
        }
        
        public class PhptThread extends TestPackThread<PhptTestCase> {
@@ -200,7 +256,7 @@ public class LocalPhptTestPackRunner extends 
AbstractLocalTestPackRunner<PhptAct
 
                @Override
                protected void runTest(TestCaseGroupKey group_key, PhptTestCase 
test_case) throws IOException, Exception, Throwable {
-                       r = sapi_scenario.createPhptTestCaseRunner(this, 
group_key, test_case, cm, twriter, runner_host, scenario_set_setup, build, 
src_test_pack, active_test_pack);
+                       r = sapi_scenario.createPhptTestCaseRunner(this, 
group_key, test_case, cm, twriter, runner_host, scenario_set_setup, build, 
src_test_pack, active_test_pack, xdebug);
                        twriter.notifyStart(runner_host, scenario_set_setup, 
src_test_pack, test_case);
                        r.runTest(cm, this, LocalPhptTestPackRunner.this);
                }
@@ -220,6 +276,18 @@ public class LocalPhptTestPackRunner extends 
AbstractLocalTestPackRunner<PhptAct
                protected void recordSkipped(PhptTestCase test_case) {
                        twriter.addResult(runner_host, scenario_set_setup, 
src_test_pack, new PhptTestResult(runner_host, EPhptTestStatus.TIMEOUT, 
test_case, "test timed out", null, null, null, null, null, null, null, null, 
null, null, null));
                }
+
+               @Override
+               protected void prepareExec(TestCaseGroupKey group_key, PhpIni 
ini, Map<String,String> env, IScenarioSetup s) {
+                       if (!s.isNeededPhptWriter())
+                               return;
+                       AbstractPhptRW phpt = 
((PhpResultPackWriter)twriter).getPHPT(
+                                       runner_host, 
+                                       scenario_set_setup, 
+                                       src_test_pack.getNameAndVersionString()
+                               );
+                       s.setPHPTWriter(runner_host, scenario_set_setup, build, 
ini, phpt);
+               }
                
        } // end public class PhptThread
 
diff --git a/src/com/mostc/pftt/scenario/BuiltinWebServerScenario.java 
b/src/com/mostc/pftt/scenario/BuiltinWebServerScenario.java
index 0fd5613..287a955 100644
--- a/src/com/mostc/pftt/scenario/BuiltinWebServerScenario.java
+++ b/src/com/mostc/pftt/scenario/BuiltinWebServerScenario.java
@@ -63,9 +63,14 @@ public class BuiltinWebServerScenario extends 
WebServerScenario {
        }
        
        @Override
-       public int getTestThreadCount(AHost host) {
+       public int getApprovedInitialThreadPoolSize(AHost host, int threads) {
                // XXX update this calculation from time to time as this web 
server's performance improves (probably decrease number)
-               return 8 * host.getCPUCount();
+               return host.getCPUCount() * 6;//8;
+       }
+       
+       @Override
+       public int getApprovedMaximumThreadPoolSize(AHost host, int threads) {
+               return host.getCPUCount() * 8;// TODO 10;
        }
        
        @Override
@@ -248,8 +253,8 @@ public class BuiltinWebServerScenario extends 
WebServerScenario {
        }
        
        @Override
-       public AbstractPhptTestCaseRunner createPhptTestCaseRunner(PhptThread 
thread, TestCaseGroupKey group_key, PhptTestCase test_case, ConsoleManager cm, 
ITestResultReceiver twriter, AHost host, ScenarioSetSetup scenario_set_setup, 
PhpBuild build, PhptSourceTestPack src_test_pack, PhptActiveTestPack 
active_test_pack) {
-               return new BuiltinWebHttpPhptTestCaseRunner(this, 
group_key.getPhpIni(), group_key.getEnv(), params, httpproc, httpexecutor, 
smgr, thread.getThreadWebServerInstance(), thread, test_case, cm, twriter, 
host, scenario_set_setup, build, src_test_pack, active_test_pack);
+       public AbstractPhptTestCaseRunner createPhptTestCaseRunner(PhptThread 
thread, TestCaseGroupKey group_key, PhptTestCase test_case, ConsoleManager cm, 
ITestResultReceiver twriter, AHost host, ScenarioSetSetup scenario_set_setup, 
PhpBuild build, PhptSourceTestPack src_test_pack, PhptActiveTestPack 
active_test_pack, boolean xdebug) {
+               return new BuiltinWebHttpPhptTestCaseRunner(xdebug, this, 
group_key.getPhpIni(), group_key.getEnv(), params, httpproc, httpexecutor, 
smgr, thread.getThreadWebServerInstance(), thread, test_case, cm, twriter, 
host, scenario_set_setup, build, src_test_pack, active_test_pack);
        }
        
        
diff --git a/src/com/mostc/pftt/scenario/CLIScenario.java 
b/src/com/mostc/pftt/scenario/CLIScenario.java
index 3a1a597..9b44e97 100644
--- a/src/com/mostc/pftt/scenario/CLIScenario.java
+++ b/src/com/mostc/pftt/scenario/CLIScenario.java
@@ -52,13 +52,18 @@ public class CliScenario extends SAPIScenario {
        public AbstractPhptTestCaseRunner createPhptTestCaseRunner(
                        PhptThread thread, TestCaseGroupKey group_key, 
PhptTestCase test_case,
                        ConsoleManager cm, ITestResultReceiver twriter, AHost 
host, ScenarioSetSetup scenario_set_setup,
-                       PhpBuild build, PhptSourceTestPack src_test_pack, 
PhptActiveTestPack active_test_pack) {
-               return new CliPhptTestCaseRunner(this, 
((CliTestCaseGroupKey)group_key).getCliSAPIInstance(), group_key.getPhpIni(), 
thread, test_case, cm, twriter, host, scenario_set_setup, build, src_test_pack, 
active_test_pack);
+                       PhpBuild build, PhptSourceTestPack src_test_pack, 
PhptActiveTestPack active_test_pack, boolean xdebug) {
+               return new CliPhptTestCaseRunner(xdebug, this, 
((CliTestCaseGroupKey)group_key).getCliSAPIInstance(), group_key.getPhpIni(), 
thread, test_case, cm, twriter, host, scenario_set_setup, build, src_test_pack, 
active_test_pack);
        }
        
        @Override
-       public int getTestThreadCount(AHost host) {
-               return 16 * host.getCPUCount();
+       public int getApprovedInitialThreadPoolSize(AHost host, int threads) {
+               return host.getCPUCount() * 4;// TODO 12;
+       }
+       
+       @Override
+       public int getApprovedMaximumThreadPoolSize(AHost host, int threads) {
+               return host.getCPUCount() * 8;//16;
        }
        
        @Override
diff --git a/src/com/mostc/pftt/scenario/DatabaseScenario.java 
b/src/com/mostc/pftt/scenario/DatabaseScenario.java
index 9cb9c0e..cf75735 100644
--- a/src/com/mostc/pftt/scenario/DatabaseScenario.java
+++ b/src/com/mostc/pftt/scenario/DatabaseScenario.java
@@ -17,6 +17,8 @@ import com.mostc.pftt.results.ConsoleManager;
 import com.mostc.pftt.results.EPrintType;
 import com.mostc.pftt.runner.AbstractPhpUnitTestCaseRunner;
 import com.mostc.pftt.util.TimerUtil;
+import com.mostc.pftt.util.TimerUtil.ObjectRunnable;
+import com.mostc.pftt.util.TimerUtil.WaitableRunnable;
 
 /** A Scenario that sets up a database service for (an) extension(s) to test.
  * 
@@ -28,10 +30,12 @@ public abstract class DatabaseScenario extends 
NetworkedServiceScenario {
        protected final AHost host;
        protected final String default_username, default_password;
        protected final LinkedList<DatabaseScenarioSetup> setups;
+       protected final IDatabaseVersion version;
        protected static final Object production_setup_lock = new Object();
        protected DatabaseScenarioSetup production_setup;
        
-       public DatabaseScenario(AHost host, String default_username, String 
default_password) {
+       public DatabaseScenario(IDatabaseVersion version, AHost host, String 
default_username, String default_password) {
+               this.version = version;
                this.host = host;
                this.default_username = default_username;
                this.default_password = default_password;
@@ -66,13 +70,22 @@ public abstract class DatabaseScenario extends 
NetworkedServiceScenario {
                return true;
        }
        
+       public IDatabaseVersion getDatabaseVersion() {
+               return version;
+       }
+       
+       public interface IDatabaseVersion {
+               public String getNameWithVersionInfo();
+
+               
+       }
+       
        @Override
        public Class<?> getSerialKey(EScenarioSetPermutationLayer layer) {
                switch(layer) {
                // IMPORTANT: when running a web application, it can only have 
1 database scenario
-               case FUNCTIONAL_TEST_APPLICATION:
-               case FUNCTIONAL_TEST_DATABASE:
                case PRODUCTION_OR_ALL_UP_TEST:
+               case FUNCTIONAL_TEST_DATABASE:
                        return DatabaseScenario.class;
                default:
                        // whereas, when testing PHP Core, you can run multiple 
database scenarios at the same time (faster)
@@ -80,6 +93,10 @@ public abstract class DatabaseScenario extends 
NetworkedServiceScenario {
                        //     which wouldn't/shouldn't be done in production
                        //     -however, when changing which DLLs are loaded, 
problems are only likely introduced when removing a DLL or changing order
                        //       so this is ok (trading this for substantial 
speed increase)
+                       //
+                       //
+                       // this also handles if multiple versions of the same 
database scenario are being permuted
+                       //  ... there will be 1 version for each ScenarioSet
                        return super.getSerialKey(layer);
                }
        }
@@ -109,7 +126,7 @@ public abstract class DatabaseScenario extends 
NetworkedServiceScenario {
                                if (production_setup==null)
                                        return production_setup;
                                
-                               return production_setup = doSetup(cm, host, 
layer, is_production_database_server);
+                               return production_setup = doSetup(cm, host, 
build, scenario_set, layer, is_production_database_server);
                        }
                } else {
                        // reuse existing setup if one is currently running
@@ -125,14 +142,14 @@ public abstract class DatabaseScenario extends 
NetworkedServiceScenario {
                                        }
                                        if (s.isRunning()) {
                                                cm.println(EPrintType.CLUE, 
getClass(), "Reusing existing MySQL server");
-                                               // TODO
+                                               // TODO comment
                                                ProxyDatabaseScenarioSetup p = 
new ProxyDatabaseScenarioSetup(s);
                                                s.proxies.add(p);
                                                return p;
                                        }
                                }
                        }
-                       return doSetup(cm, host, layer, 
is_production_database_server);
+                       return doSetup(cm, host, build, scenario_set, layer, 
is_production_database_server);
                }
        }
        
@@ -233,15 +250,25 @@ public abstract class DatabaseScenario extends 
NetworkedServiceScenario {
                public ResultSet executeQuery(String sql) {
                        return r.executeQuery(sql);
                }
+
+               @Override
+               protected boolean 
cleanupServerAfterFailedStarted(ConsoleManager cm, boolean 
is_production_database_server) {
+                       return r.cleanupServerAfterFailedStarted(cm, 
is_production_database_server);
+               }
+
+               @Override
+               public void getENV(Map<String, String> env) {
+                       r.getENV(env);
+               }
+               
        } // end protected class ProxyDatabaseScenarioSetup
        
-       protected DatabaseScenarioSetup doSetup(ConsoleManager cm, Host host, 
EScenarioSetPermutationLayer layer, boolean is_production_database_server) {
+       protected DatabaseScenarioSetup doSetup(ConsoleManager cm, Host host, 
PhpBuild build, ScenarioSet scenario_set, EScenarioSetPermutationLayer layer, 
boolean is_production_database_server) {
                DatabaseScenarioSetup setup = 
createScenarioSetup(is_production_database_server);
                        
-               if (setup==null||!setup.ensureServerStarted(cm, 
is_production_database_server)||!ensureDriverLoaded()||!setup.connect(cm))
+               if 
(setup==null||!ensureDriverLoaded()||!setup.ensureServerStarted(cm, host, 
build, scenario_set, layer, is_production_database_server)||!setup.connect(cm))
                        return null;
                
-               
                for ( int i=0 ; i < 30 ; i++ ) {
                        setup.db_name = generateDatabaseName();
                        if (!setup.databaseExists(setup.db_name)) {
@@ -261,11 +288,32 @@ public abstract class DatabaseScenario extends 
NetworkedServiceScenario {
        
        @Overridable
        protected String generateDatabaseName() {
-               return "pftt_"+getName()+"_"+StringUtil.randomLettersStr(5, 10);
+               return "pftt_"+StringUtil.randomLettersStr(5, 10);
        }
        
        protected abstract DatabaseScenarioSetup createScenarioSetup(boolean 
is_production_server);
        
+       public abstract class DefaultUnmanagedDatabaseScenarioSetup extends 
DefaultDatabaseScenarioSetup {
+               
+               @Override
+               public abstract int getPort();
+               
+               @Override
+               protected boolean startServer(ConsoleManager cm, boolean 
is_production_database_server) {
+                       return true;
+               }
+
+               @Override
+               protected boolean stopServer(ConsoleManager cm, boolean 
is_production_database_server) {
+                       return true;
+               }
+
+               @Override
+               protected boolean 
cleanupServerAfterFailedStarted(ConsoleManager cm, boolean 
is_production_database_server) {
+                       return true;
+               }
+       }
+       
        public abstract class DefaultDatabaseScenarioSetup extends 
DatabaseScenarioSetup {
                protected Connection connection;
                protected int port;
@@ -273,6 +321,11 @@ public abstract class DatabaseScenario extends 
NetworkedServiceScenario {
                protected abstract Connection createConnection() throws 
SQLException;
                
                @Override
+               public String getNameWithVersionInfo() {
+                       return version.getNameWithVersionInfo();
+               }
+               
+               @Override
                public boolean isRunning() {
                        if (server_started && connection != null) {
                                try {
@@ -305,8 +358,11 @@ public abstract class DatabaseScenario extends 
NetworkedServiceScenario {
                
                @Override
                protected boolean disconnect() {
+                       if (connection==null)
+                               return true;
                        try {
                                connection.close();
+                               connection = null;
                                return true;
                        } catch (SQLException ex) {
                                ex.printStackTrace();
@@ -340,41 +396,6 @@ public abstract class DatabaseScenario extends 
NetworkedServiceScenario {
                }
                
                @Override
-               public boolean dropDatabase(String db_name) {
-                       return execute("DROP DATABASE "+db_name);
-               }
-               
-               @Override
-               public boolean createDatabase(String db_name) {
-                       return execute("CREATE DATABASE "+db_name);
-               }
-               
-               @Override
-               public boolean databaseExists(String db_name) {
-                       return empty(executeQuery("SHOW DATABASES LIKE 
'"+db_name+"'"));
-               }
-               
-               @Override
-               public boolean createDatabaseWithUser(String db_name, String 
user, String password) {
-                       return createDatabase(db_name) &&
-                                       execute("GRANT ALL ON "+db_name+".* TO 
`"+user+"`@`localhost` IDENTIFIED BY '"+password+"'") &&
-                                       execute("GRANT ALL ON "+db_name+".* TO 
`"+user+"` IDENTIFIED BY '"+password+"'");
-               }
-               
-               @Override
-               public boolean createDatabaseReplaceOk(String db_name) {
-                       execute("DROP DATABASE IF EXISTS "+db_name);
-                       return createDatabase(db_name);
-               }
-               
-               @Override
-               public boolean createDatabaseWithUserReplaceOk(String db_name, 
String user, String password) {
-                       return createDatabaseReplaceOk(db_name) &&
-                                       execute("GRANT ALL ON "+db_name+".* TO 
`"+user+"`@`localhost` IDENTIFIED BY '"+password+"'") &&
-                                       execute("GRANT ALL ON "+db_name+".* TO 
`"+user+"` IDENTIFIED BY '"+password+"'");
-               }
-               
-               @Override
                public boolean execute(String sql) {
                        try {
                                connection.createStatement().execute(sql);
@@ -420,18 +441,44 @@ public abstract class DatabaseScenario extends 
NetworkedServiceScenario {
                @Override
                public abstract boolean isRunning();
                
-               protected boolean ensureServerStarted(ConsoleManager cm, 
boolean is_production_database_server) {
-                       if (!server_started && cm != null)
+               protected void setupBuild(ConsoleManager cm, AHost host, 
PhpBuild build, ScenarioSet scenario_set, EScenarioSetPermutationLayer layer) 
throws IllegalStateException, Exception {
+                       
+               }
+               
+               protected boolean ensureServerStarted(final ConsoleManager cm, 
Host host, PhpBuild build, ScenarioSet scenario_set, 
EScenarioSetPermutationLayer layer, final boolean 
is_production_database_server) {
+                       if (server_started)
+                               return true;
+                       if (cm != null)
                                cm.println(EPrintType.CLUE, getClass(), 
"Starting database server: "+getNameWithVersionInfo());
-                       return server_started = startServer(cm, 
is_production_database_server);
+                       
+                       try {
+                               setupBuild(cm, (AHost)host, build, 
scenario_set, layer);
+                       } catch ( Exception ex ) {
+                               cm.addGlobalException(EPrintType.CLUE, 
getClass(), "ensureServerStarted", ex, "Problem setting up PhpBuild for this 
database scenario");
+                       }
+                       
+                       WaitableRunnable<Boolean> starth = 
TimerUtil.runWaitSeconds("DatabaseServerStart", 60, new 
ObjectRunnable<Boolean>() {
+                                       public Boolean run() {
+                                               return server_started = 
startServer(cm, is_production_database_server);
+                                       }
+                               });
+                       if (!server_started) {
+                               if (starth.getException()!=null)
+                                       starth.getException().printStackTrace();
+                               
+                               // cleanup database server process
+                               cleanupServerAfterFailedStarted(cm, 
is_production_database_server);
+                       }
+                       return server_started;
                }
                
                protected abstract boolean startServer(ConsoleManager cm, 
boolean is_production_database_server);
                protected abstract boolean stopServer(ConsoleManager cm, 
boolean is_production_database_server);
+               protected abstract boolean 
cleanupServerAfterFailedStarted(ConsoleManager cm, boolean 
is_production_database_server);
                
                private boolean close_called = false;
                @Override
-               public final synchronized void close(ConsoleManager cm) {
+               public final synchronized void close(final ConsoleManager cm) {
                        if (this instanceof ProxyDatabaseScenarioSetup) {
                                
synchronized(((ProxyDatabaseScenarioSetup)this).r.proxies) {
                                        
((ProxyDatabaseScenarioSetup)this).r.proxies.remove(this);
@@ -461,7 +508,14 @@ public abstract class DatabaseScenario extends 
NetworkedServiceScenario {
                                setups.remove(this);
                        }
                        cm.println(EPrintType.IN_PROGRESS, getClass(), 
"Stopping database server...");
-                       if (stopServer(cm, production_setup == this)) {
+                       final boolean is_production_server = production_setup 
== this;
+                       // sometimes #stopServer can take too long. call it in 
thread so it can be timed out if it takes too long
+                       WaitableRunnable<Boolean> r = 
TimerUtil.runWaitSeconds("DatabaseServerStop", 30, new 
ObjectRunnable<Boolean>() {
+                                       public Boolean run() {
+                                               return stopServer(cm, 
is_production_server);
+                                       }
+                               });
+                       if (r.getResult()) {
                                server_started = false;
                                cm.println(EPrintType.CLUE, getClass(), 
"Stopped database server");
                        } else {
@@ -480,6 +534,8 @@ public abstract class DatabaseScenario extends 
NetworkedServiceScenario {
                public boolean hasENV() {
                        return true;
                }
+               @Override
+               public abstract void getENV(Map<String, String> env);
                
                @Override
                public void setGlobals(Map<String, String> globals) {
@@ -510,8 +566,12 @@ public abstract class DatabaseScenario extends 
NetworkedServiceScenario {
                public abstract int getPort();
 
                public abstract String getDataSourceName();
-               public abstract boolean dropDatabase(String db_name);
-               public abstract boolean createDatabase(String db_name);
+               public boolean dropDatabase(String db_name) {
+                       return execute("DROP DATABASE "+db_name);
+               }
+               public boolean createDatabase(String db_name) {
+                       return execute("CREATE DATABASE "+db_name);
+               }
                public abstract boolean createDatabaseWithUser(String db_name, 
String user, String password);
                public abstract boolean createDatabaseReplaceOk(String db_name);
                public abstract boolean createDatabaseWithUserReplaceOk(String 
db_name, String user, String password);
diff --git a/src/com/mostc/pftt/scenario/IScenarioSetup.java 
b/src/com/mostc/pftt/scenario/IScenarioSetup.java
index 89fc0f4..e55105c 100644
--- a/src/com/mostc/pftt/scenario/IScenarioSetup.java
+++ b/src/com/mostc/pftt/scenario/IScenarioSetup.java
@@ -5,6 +5,8 @@ import java.util.Map;
 import com.mostc.pftt.host.AHost;
 import com.mostc.pftt.model.core.PhpBuild;
 import com.mostc.pftt.model.core.PhpIni;
+import com.mostc.pftt.results.AbstractPhpUnitRW;
+import com.mostc.pftt.results.AbstractPhptRW;
 import com.mostc.pftt.results.ConsoleManager;
 import com.mostc.pftt.util.IClosable;
 
@@ -17,4 +19,8 @@ public interface IScenarioSetup extends IClosable {
        public boolean hasENV();
        public void notifyScenarioSetSetup(ScenarioSetSetup setup);
        public boolean isRunning();
-}
\ No newline at end of file
+       public boolean isNeededPhpUnitWriter();
+       public void setPhpUnitWriter(AHost runner_host, ScenarioSetSetup 
scenario_set_setup, PhpBuild build, PhpIni ini, AbstractPhpUnitRW phpunit);
+       public void setPHPTWriter(AHost runner_host, ScenarioSetSetup 
scenario_set_setup, PhpBuild build, PhpIni ini, AbstractPhptRW phpt);
+       public boolean isNeededPhptWriter();
+}
diff --git a/src/com/mostc/pftt/scenario/MSSQLScenario.java 
b/src/com/mostc/pftt/scenario/MSSQLScenario.java
index 0d43f89..3b4df71 100644
--- a/src/com/mostc/pftt/scenario/MSSQLScenario.java
+++ b/src/com/mostc/pftt/scenario/MSSQLScenario.java
@@ -1,6 +1,18 @@
 package com.mostc.pftt.scenario;
 
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.Map;
+
 import com.mostc.pftt.host.AHost;
+import com.mostc.pftt.host.Host;
+import com.mostc.pftt.host.SSHHost;
+import com.mostc.pftt.model.core.EBuildBranch;
+import com.mostc.pftt.model.core.EBuildType;
+import com.mostc.pftt.model.core.PhpBuild;
+import com.mostc.pftt.model.core.PhpIni;
+import com.mostc.pftt.results.ConsoleManager;
 
 /** Tests the mssql and pdo_mssql extensions against a Microsoft SQL Server. 
(NOT IMPLEMENTED)
  * 
@@ -8,31 +20,203 @@ import com.mostc.pftt.host.AHost;
  *
  */
 
+// TODO need to install sqlncli2008r2-x64.msi or sqlncli2012sp1-x64.msi or 
-x86 (need -x64 for php-x86 builds on Windows-x64)
 public class MSSQLScenario extends DatabaseScenario {
-
-       public MSSQLScenario(AHost host, String default_username, String 
default_password) {
-               super(host, default_username, default_password);
+       public static final int DEFAULT_MSSQL_PORT = 1433;
+       protected final String host_address;
+       
+       public MSSQLScenario(EMSSQLVersion version, AHost host, String 
default_username, String default_password) {
+               super(version, host, default_username, default_password);
+               this.host_address = host.getLocalhostListenAddress();
        }
-
+       
+       public MSSQLScenario(EMSSQLVersion version, String host_address, String 
default_username, String default_password) {
+               super(version, new SSHHost(host_address, default_username, 
default_password), default_username, default_password);
+               this.host_address = host_address;
+       }
+       
+       public static enum EMSSQLVersion implements IDatabaseVersion {
+               DRIVER10 {
+                               @Override
+                               public String getNameWithVersionInfo() {
+                                       return "MSSQL-Driver-10";
+                               }
+                               @Override
+                               public String getODBCDriverName() {
+                                       return "SQL Server Native Client 10.0";
+                               }
+                       },
+               DRIVER11 {
+                               @Override
+                               public String getNameWithVersionInfo() {
+                                       return "MSSQL-Driver-11";
+                               }
+                               @Override
+                               public String getODBCDriverName() {
+                                       return "SQL Server Native Client 11.0";
+                               }
+                       };
+               
+               public abstract String getNameWithVersionInfo();
+               public abstract String getODBCDriverName();
+               
+               public String getPhpPdoDllName(EBuildBranch branch, EBuildType 
type, String base_dir) {
+                       switch(branch) {
+                       case PHP_5_3:
+                               return type == EBuildType.NTS ? 
"php_pdo_sqlsrv_53_nts.dll" : "php_pdo_sqlsrv_53_ts.dll";
+                       case PHP_5_4:
+                               return type == EBuildType.NTS ? 
"php_pdo_sqlsrv_54_nts.dll" : "php_pdo_sqlsrv_54_ts.dll";
+                       case PHP_5_5:
+                       default:
+                               return type == EBuildType.NTS ? 
"php_pdo_sqlsrv_55_nts.dll" : "php_pdo_sqlsrv_55_ts.dll";
+                       }
+               }
+               public String getPhpDllName(EBuildBranch branch, EBuildType 
type, String base_dir) {
+                       switch(branch) {
+                       case PHP_5_3:
+                               return type == EBuildType.NTS ? 
"php_sqlsrv_53_nts.dll" : "php_sqlsrv_53_ts.dll";
+                       case PHP_5_4:
+                               return type == EBuildType.NTS ? 
"php_sqlsrv_54_nts.dll" : "php_sqlsrv_54_ts.dll";
+                       case PHP_5_5:
+                       default:
+                               return type == EBuildType.NTS ? 
"php_sqlsrv_55_nts.dll" : "php_sqlsrv_55_ts.dll";
+                       }
+               }
+       } // end public static enum EMSSQLVersion
+       
+       @Override
+       public boolean isSupported(ConsoleManager cm, Host host, PhpBuild 
build, ScenarioSet scenario_set) {
+               // PHP driver for MS SQL currently only supported on Windows
+               return host.isWindows();
+       }
+       
+       @Override
+       public boolean ignoreForShortName(EScenarioSetPermutationLayer layer) {
+               // make sure version is always included in the name of the 
ScenarioSet
+               return false;
+       }
+       
+       public boolean isPlaceholder(EScenarioSetPermutationLayer layer) {
+               return false; // TODO sometimes should return true?
+       }
+       
        @Override
        protected DatabaseScenarioSetup createScenarioSetup(boolean 
is_production_server) {
-               return null;
+               return new MSSQLDatabaseScenarioSetup();
        }
+       
+       public class MSSQLDatabaseScenarioSetup extends 
DefaultUnmanagedDatabaseScenarioSetup {
+               
+               @Override
+               protected void setupBuild(ConsoleManager cm, AHost host, 
PhpBuild build, ScenarioSet scenario_set, EScenarioSetPermutationLayer layer) 
throws IllegalStateException, Exception {
+                       String base_dir = host.getPfttCacheDir()+"/dep/MSSQL/";
+                       
+                       String dll1 = 
((EMSSQLVersion)version).getPhpPdoDllName(build.getVersionBranch(cm, host), 
build.getBuildType(host), base_dir);
+                       String dll2 = 
((EMSSQLVersion)version).getPhpDllName(build.getVersionBranch(cm, host), 
build.getBuildType(host), base_dir);
+                       
+                       host.copy(dll1, 
build.getDefaultExtensionDir()+"/php_pdo_sqlsrv.dll");
+                       host.copy(dll2, 
build.getDefaultExtensionDir()+"/php_sqlsrv.dll");
+               }
+               
+               @Override
+               public String getHostname() {
+                       return host_address;
+               }
+               
+               @Override
+               public int getPort() {
+                       return DEFAULT_MSSQL_PORT;
+               }
+               
+               @Override
+               public String getName() {
+                       return version.getNameWithVersionInfo();
+               }
+               
+               @Override
+               public void getENV(Map<String, String> env) {
+                       // @see ext/pdo_sqlsrv/tests/MsSetup.inc
+                       // @see ext/sqlsrv/tests/MsSetup.inc
+                       env.put("MSSQL_SERVER", getHostname());
+                       env.put("MSSQL_USER", getUsername());
+                       env.put("MSSQL_DATABASE_NAME", getDatabaseName());
+                       env.put("MSSQL_PASSWORD", getPassword());
+                       env.put("MSSQL_DRIVER_NAME", 
((EMSSQLVersion)version).getODBCDriverName());
+                       if (getPort()!=DEFAULT_MSSQL_PORT) {
+                               throw new IllegalStateException("Non-default 
port NOT supported!: "+getPort());
+                       }
+               }
+
+               @Override
+               public void prepareINI(ConsoleManager cm, AHost host, PhpBuild 
build, ScenarioSet scenario_set, PhpIni ini) {
+                       ini.addExtension("php_sqlsrv.dll");
+                       ini.addExtension("php_pdo_sqlsrv.dll");
+               }
+
+               @Override
+               public String getPdoDbType() {
+                       return "pdo_sqlsrv";
+               }
+
+               @Override
+               public String getDataSourceName() {
+                       return 
"odbc:Driver={"+((EMSSQLVersion)version).getODBCDriverName()+"};Server="+getHostname();
+               }
+
+               @Override
+               protected Connection createConnection() throws SQLException {
+                       // @see http://jtds.sourceforge.net/faq.html
+                       final String url_str = 
"jdbc:sqlserver://"+getHostname()+":"+getPort()+";user="+getUsername()+";password="+getPassword()+";integratedSecurity=false";
+                       System.out.println("url_str "+url_str);
+                       return DriverManager.getConnection(url_str);
+               }
+
+               @Override
+               public boolean databaseExists(String db_name) {
+                       return empty(executeQuery("SELECT * FROM sys.databases 
WHERE name='"+db_name+"'"));
+               }
+               
+               @Override
+               public boolean createDatabaseWithUser(String db_name, String 
user, String password) {
+                       return createDatabase(db_name) && 
createGrantUser(db_name, user, password);
+               }
+
+               @Override
+               public boolean createDatabaseReplaceOk(String db_name) {
+                       return dropDatabase(db_name) && createDatabase(db_name);
+               }
+
+               @Override
+               public boolean createDatabaseWithUserReplaceOk(String db_name, 
String user, String password) {
+                       return createDatabase(db_name) && 
createGrantUser(db_name, user, password);
+               }
+               
+               protected boolean createGrantUser(String db_name, String user, 
String password) {
+                       executeQuery("IF NOT EXISTS(SELECT * FROM 
sys.database_principals WHERE name = '"+user+"') CREATE LOGIN "+user+" WITH 
PASSWORD '"+password+"'");
+                       executeQuery("IF NOT EXISTS(SELECT * FROM 
sys.server_principals WHERE name = '"+user+"' ) CREATE USER "+user+" FOR LOGIN 
"+user);
+                       executeQuery("GRANT ALL ON "+db_name+".* TO "+user);
+                       return true;
+               }
+               
+       } // end public class MSSQLDatabaseScenarioSetup
 
        @Override
        public String getName() {
-               return "MSSQL";
+               // TODO temp DatabaseScenario should do this for all subclasses
+               return version.getNameWithVersionInfo();
        }
 
        @Override
        public boolean isImplemented() {
-               return false;
+               return true;
        }
 
        @Override
        protected String getDriverClassName() {
-               // TODO Auto-generated method stub
-               return null;
+               // @see 
http://msdn.microsoft.com/en-us/library/ms378623%28v=sql.110%29.aspx
+               // @see http://msdn.microsoft.com/en-US/data/ff928484
+               // @see 
http://msdn.microsoft.com/en-us/library/ms378914%28v=sql.110%29.aspx
+               return "com.microsoft.sqlserver.jdbc.SQLServerDriver";
        }
        
-}
+} // end public class MSSQLScenario
diff --git a/src/com/mostc/pftt/scenario/MySQLScenario.java 
b/src/com/mostc/pftt/scenario/MySQLScenario.java
index c841d9f..50d14ba 100644
--- a/src/com/mostc/pftt/scenario/MySQLScenario.java
+++ b/src/com/mostc/pftt/scenario/MySQLScenario.java
@@ -19,20 +19,36 @@ import com.mostc.pftt.results.EPrintType;
  * @author Matt Ficken
  *
  */
-
+ 
 public class MySQLScenario extends DatabaseScenario {
        public static final int DEFAULT_MYSQL_PORT = 3306;
        public static final String DEFAULT_USERNAME = "root";
        public static final String DEFAULT_PASSWORD = "password01!";
        
+       public MySQLScenario(EMySQLVersion version, AHost host, String 
default_username, String default_password) {
+               super(version, host, default_username, default_password);
+       }
+               
+       public MySQLScenario(EMySQLVersion version, AHost host) {
+               this(version, host, DEFAULT_USERNAME, DEFAULT_PASSWORD);
+       }
+       
        public MySQLScenario(AHost host, String default_username, String 
default_password) {
-               super(host, default_username, default_password);
+               this(EMySQLVersion.DEFAULT, host, default_username, 
default_password);
        }
                
        public MySQLScenario(AHost host) {
                this(host, DEFAULT_USERNAME, DEFAULT_PASSWORD);
        }
        
+       public static enum EMySQLVersion implements IDatabaseVersion {
+               DEFAULT {
+                       public String getNameWithVersionInfo() {
+                               return "MySQL-5.6"; // TODO autodetect
+                       }
+               };
+       }
+       
        @Override
        public boolean isImplemented() {
                return true;
@@ -53,14 +69,34 @@ public class MySQLScenario extends DatabaseScenario {
                protected String datadir, hostname;
                
                @Override
-               protected Connection createConnection() throws SQLException {
-                       String url = 
"jdbc:mysql://"+getHostname()+":"+getPort()+"/?user="+getUsername()+"&password="+getPassword();
-                       return DriverManager.getConnection(url);
+               public boolean databaseExists(String db_name) {
+                       return empty(executeQuery("SHOW DATABASES LIKE 
'"+db_name+"'"));
+               }
+               
+               @Override
+               public boolean createDatabaseWithUser(String db_name, String 
user, String password) {
+                       return createDatabase(db_name) &&
+                                       execute("GRANT ALL ON "+db_name+".* TO 
`"+user+"`@`localhost` IDENTIFIED BY '"+password+"'") &&
+                                       execute("GRANT ALL ON "+db_name+".* TO 
`"+user+"` IDENTIFIED BY '"+password+"'");
+               }
+               
+               @Override
+               public boolean createDatabaseReplaceOk(String db_name) {
+                       execute("DROP DATABASE IF EXISTS "+db_name);
+                       return createDatabase(db_name);
+               }
+               
+               @Override
+               public boolean createDatabaseWithUserReplaceOk(String db_name, 
String user, String password) {
+                       return createDatabaseReplaceOk(db_name) &&
+                                       execute("GRANT ALL ON "+db_name+".* TO 
`"+user+"`@`localhost` IDENTIFIED BY '"+password+"'") &&
+                                       execute("GRANT ALL ON "+db_name+".* TO 
`"+user+"` IDENTIFIED BY '"+password+"'");
                }
                
                @Override
-               public String getNameWithVersionInfo() {
-                       return "MySQL-5.6"; // TODO detect
+               protected Connection createConnection() throws SQLException {
+                       String url = 
"jdbc:mysql://"+getHostname()+":"+getPort()+"/?user="+getUsername()+"&password="+getPassword();
+                       return DriverManager.getConnection(url);
                }
 
                @Override
@@ -208,18 +244,31 @@ public class MySQLScenario extends DatabaseScenario {
                        cm.println(EPrintType.CANT_CONTINUE, getClass(), 
"Failed to start MySQL server");
                        return false;
                } // end protected boolean startServer
-
+               
+               protected boolean 
cleanupServerAfterFailedStarted(ConsoleManager cm, boolean 
is_production_server) {
+                       try {
+                               stopServerEx(cm, is_production_server);
+                               return true;
+                       } catch ( Exception ex ) {
+                               ex.printStackTrace();
+                       }
+                       return false;
+               }
+               
+               protected void stopServerEx(ConsoleManager cm, boolean 
is_production_server) throws Exception {
+                       if (is_production_server) {
+                               cm.println(EPrintType.IN_PROGRESS, getClass(), 
"Stopping production MySQL Windows Service...");
+                               host.execElevated(cm, getClass(), "net stop 
MySQL56", AHost.ONE_MINUTE);
+                       } else {
+                               cm.println(EPrintType.IN_PROGRESS, getClass(), 
"Stopping MySQL stanadlone process");
+                               mysqld_handle.close(cm, true);
+                       }
+               }
+               
                @Override
                protected boolean stopServer(ConsoleManager cm, boolean 
is_production_server) {
                        try {
-                               if (is_production_server) {
-                                       cm.println(EPrintType.IN_PROGRESS, 
getClass(), "Stopping production MySQL Windows Service...");
-                                       host.execElevated(cm, getClass(), "net 
stop MySQL56", AHost.ONE_MINUTE);
-                               } else {
-                                       cm.println(EPrintType.IN_PROGRESS, 
getClass(), "Stopping MySQL stanadlone process");
-                                       mysqld_handle.close(cm, true);
-                                       
-                               }
+                               stopServerEx(cm, is_production_server);
                                
                                // wait until MySQL can't be connected to
                                if 
(!WebServerManager.isLocalhostTCPPortUsed(getPort())) {
diff --git a/src/com/mostc/pftt/scenario/NginxScenario.java 
b/src/com/mostc/pftt/scenario/NginxScenario.java
index b51e4ad..fdf9538 100644
--- a/src/com/mostc/pftt/scenario/NginxScenario.java
+++ b/src/com/mostc/pftt/scenario/NginxScenario.java
@@ -1,6 +1,5 @@
 package com.mostc.pftt.scenario;
 
-import com.mostc.pftt.host.AHost;
 import com.mostc.pftt.model.core.ESAPIType;
 import com.mostc.pftt.model.sapi.WebServerManager;
 
@@ -26,12 +25,6 @@ public class NginxScenario extends 
ProductionWebServerScenario {
        }
 
        @Override
-       public int getTestThreadCount(AHost host) {
-               // TODO Auto-generated method stub
-               return 0;
-       }
-
-       @Override
        public ESAPIType getSAPIType() {
                return ESAPIType.FAST_CGI;
        }
diff --git a/src/com/mostc/pftt/scenario/ODBCScenario.java 
b/src/com/mostc/pftt/scenario/ODBCScenario.java
index f02e756..7d7a731 100644
--- a/src/com/mostc/pftt/scenario/ODBCScenario.java
+++ b/src/com/mostc/pftt/scenario/ODBCScenario.java
@@ -4,8 +4,20 @@ import com.mostc.pftt.host.AHost;
 
 public abstract class ODBCScenario extends DatabaseScenario {
 
+       public ODBCScenario(EODBCVersion version, AHost host, String 
default_username, String default_password) {
+               super(version, host, default_username, default_password);
+       }
+       
        public ODBCScenario(AHost host, String default_username, String 
default_password) {
-               super(host, default_username, default_password);
+               this(EODBCVersion.DEFAULT, host, default_username, 
default_password);
+       }
+       
+       public static enum EODBCVersion implements IDatabaseVersion {
+               DEFAULT {
+                       public String getNameWithVersionInfo() {
+                               return "Default";
+                       }
+               };
        }
 
 }
diff --git a/src/com/mostc/pftt/scenario/PostgresSQLScenario.java 
b/src/com/mostc/pftt/scenario/PostgresSQLScenario.java
index 6585de0..41a92e3 100644
--- a/src/com/mostc/pftt/scenario/PostgresSQLScenario.java
+++ b/src/com/mostc/pftt/scenario/PostgresSQLScenario.java
@@ -17,8 +17,20 @@ import com.mostc.pftt.results.ConsoleManager;
 
 public class PostgresSQLScenario extends DatabaseScenario {
 
+       public PostgresSQLScenario(EPostgresSQLVersion version, AHost host, 
String default_username, String default_password) {
+               super(version, host, default_username, default_password);
+       }
+       
        public PostgresSQLScenario(AHost host, String default_username, String 
default_password) {
-               super(host, default_username, default_password);
+               this(EPostgresSQLVersion.DEFAULT, host, default_username, 
default_password);
+       }
+       
+       public static enum EPostgresSQLVersion implements IDatabaseVersion {
+               DEFAULT {
+                       public String getNameWithVersionInfo() {
+                               return "PostgresSQL"; 
+                       }
+               };
        }
 
        @Override
@@ -29,11 +41,6 @@ public class PostgresSQLScenario extends DatabaseScenario {
        public class PostgresSQLScenarioSetup extends 
DefaultDatabaseScenarioSetup {
 
                @Override
-               public String getNameWithVersionInfo() {
-                       return getName(); // TODO
-               }
-
-               @Override
                public void prepareINI(ConsoleManager cm, AHost host, PhpBuild 
build, ScenarioSet scenario_set, PhpIni ini) {
                        // TODO Auto-generated method stub
                        
@@ -75,6 +82,39 @@ public class PostgresSQLScenario extends DatabaseScenario {
                        // TODO Auto-generated method stub
                        return false;
                }
+
+               @Override
+               protected boolean 
cleanupServerAfterFailedStarted(ConsoleManager cm,
+                               boolean is_production_database_server) {
+                       // TODO Auto-generated method stub
+                       return false;
+               }
+
+               @Override
+               public boolean databaseExists(String db_name) {
+                       // TODO Auto-generated method stub
+                       return false;
+               }
+
+               @Override
+               public boolean createDatabaseWithUser(String db_name, String 
user,
+                               String password) {
+                       // TODO Auto-generated method stub
+                       return false;
+               }
+
+               @Override
+               public boolean createDatabaseReplaceOk(String db_name) {
+                       // TODO Auto-generated method stub
+                       return false;
+               }
+
+               @Override
+               public boolean createDatabaseWithUserReplaceOk(String db_name,
+                               String user, String password) {
+                       // TODO Auto-generated method stub
+                       return false;
+               }
                
        } // end public class PostgresSQLScenarioSetup
 
diff --git a/src/com/mostc/pftt/scenario/ProductionWebServerScenario.java 
b/src/com/mostc/pftt/scenario/ProductionWebServerScenario.java
index 10045be..d98737d 100644
--- a/src/com/mostc/pftt/scenario/ProductionWebServerScenario.java
+++ b/src/com/mostc/pftt/scenario/ProductionWebServerScenario.java
@@ -20,18 +20,23 @@ public abstract class ProductionWebServerScenario extends 
WebServerScenario {
        }
        
        @Override
-       public int getTestThreadCount(AHost host) {
-               return 4 * host.getCPUCount();
+       public int getApprovedInitialThreadPoolSize(AHost host, int threads) {
+               return host.getCPUCount() * 8;
+       }
+       
+       @Override
+       public int getApprovedMaximumThreadPoolSize(AHost host, int threads) {
+               return host.getCPUCount() * 16;
        }
        
        @Override
        public int getSlowTestTimeSeconds() {
-               return 15;
+               return 12;
        }
        
        @Override
        public long getFastTestTimeSeconds() {
-               return 10;
+               return 8;
        }
        
        @Override
@@ -40,8 +45,8 @@ public abstract class ProductionWebServerScenario extends 
WebServerScenario {
                Collections.sort(test_cases, new Comparator<PhptTestCase>() {
                                @Override
                                public int compare(PhptTestCase a, PhptTestCase 
b) {
-                                       final boolean as = isSlowTest(a);
-                                       final boolean bs = isSlowTest(b);
+                                       final boolean as = !isSlowTest(a);
+                                       final boolean bs = !isSlowTest(b);
                                        return ( as ^ bs ) ? ( as ^ true  ? -1 
: +1 ) : 0;
                                }
                        });
diff --git a/src/com/mostc/pftt/scenario/SAPIScenario.java 
b/src/com/mostc/pftt/scenario/SAPIScenario.java
index 4198105..7a024bb 100644
--- a/src/com/mostc/pftt/scenario/SAPIScenario.java
+++ b/src/com/mostc/pftt/scenario/SAPIScenario.java
@@ -80,15 +80,19 @@ public abstract class SAPIScenario extends 
AbstractSerialScenario {
         * @param build
         * @param src_test_pack
         * @param active_test_pack
+        * @param xdebug TODO
         * @return
         */
-       public abstract AbstractPhptTestCaseRunner 
createPhptTestCaseRunner(PhptThread thread, TestCaseGroupKey group_key, 
PhptTestCase test_case, ConsoleManager cm, ITestResultReceiver twriter, AHost 
host, ScenarioSetSetup scenario_set_setup, PhpBuild build, PhptSourceTestPack 
src_test_pack, PhptActiveTestPack active_test_pack);
+       public abstract AbstractPhptTestCaseRunner 
createPhptTestCaseRunner(PhptThread thread, TestCaseGroupKey group_key, 
PhptTestCase test_case, ConsoleManager cm, ITestResultReceiver twriter, AHost 
host, ScenarioSetSetup scenario_set_setup, PhpBuild build, PhptSourceTestPack 
src_test_pack, PhptActiveTestPack active_test_pack, boolean xdebug);
        
        public void close(ConsoleManager cm, boolean debug) {
                
        }
 
-       public abstract int getTestThreadCount(AHost host);
+       @Override
+       public abstract int getApprovedInitialThreadPoolSize(AHost host, int 
threads);
+       @Override
+       public abstract int getApprovedMaximumThreadPoolSize(AHost host, int 
threads);
 
        public abstract ESAPIType getSAPIType();
 
@@ -163,7 +167,6 @@ public abstract class SAPIScenario extends 
AbstractSerialScenario {
                        "tests/basic/req60524-win.phpt",
                        "tests/func/011.phpt",
                        "zend/tests/unset_cv10.phpt",
-                       // TODO
                        //
                        "ext/pdo_mysql/tests/pdo_mysql___construct_ini.phpt",
                        "ext/pcre/tests/backtrack_limit.phpt",
@@ -171,7 +174,6 @@ public abstract class SAPIScenario extends 
AbstractSerialScenario {
                        "ext/phar/tests/bug45218_slowtest.phpt",
                        "ext/phar/tests/phar_buildfromdirectory6.phpt",
                        "ext/reflection/tests/015.phpt",
-                       "ext/session/tests/bug60860.phpt",
                        "ext/standard/tests/file/bug24482.phpt",
                        "ext/standard/tests/file/bug41655_1.phpt",
                        "ext/standard/tests/strings/htmlentities10.phpt",
@@ -183,7 +185,25 @@ public abstract class SAPIScenario extends 
AbstractSerialScenario {
                        "ext/standard/tests/network/udp4loop.phpt",
                        "ext/standard/tests/network/udp6loop.phpt",
                        "zend/tests/bug52041.phpt",
-                       "zend/tests/halt_compiler4.phpt"
+                       "tests/func/bug64523.phpt",
+                       "zend/tests/halt_compiler4.phpt",
+                       "ext/filter/tests/004.phpt",
+                       "ext/mbstring/tests/mb_output_handler_pattern-01.phpt",
+                       "ext/mbstring/tests/mb_output_handler_pattern-02.phpt",
+                       "ext/mbstring/tests/mb_output_handler_pattern-03.phpt",
+                       "ext/mbstring/tests/mb_output_handler_pattern-05.phpt",
+                       "ext/mbstring/tests/mb_output_handler_pattern-06.phpt",
+                       "ext/mbstring/tests/mb_output_handler_pattern-07.phpt",
+                       "ext/mbstring/tests/mb_output_handler_pattern-08.phpt",
+                       "ext/mbstring/tests/mb_output_handler_pattern-09.phpt",
+                       "ext/mbstring/tests/mb_output_handler_pattern-11.phpt",
+                       "ext/mbstring/tests/mb_output_handler_pattern-12.phpt",
+                       
"ext/mbstring/tests/mb_output_handler_runtime_ini_alteration-01.phpt",
+                       "ext/session/tests/bug60860.phpt",
+                       "ext/standard/tests/strings/htmlentities05.phpt",
+                       "ext/wddx/tests/004.phpt",
+                       "ext/wddx/tests/005.phpt",
+                       "ext/zlib/tests/bug65391.phpt"
                );
        public static Trie NON_WINDOWS_EXTS = 
PhptTestCase.createExtensions("sysvsem", "sysvmsg", "sysvshm", "gettext", 
"exif", "readline", "posix", "shmop");
        public static Trie SCENARIO_EXTS = PhptTestCase.createExtensions("dba", 
"sybase", "snmp", "interbase", "ldap", "imap", "oci8", "pcntl", "soap", 
"xmlrpc", "pdo", "odbc", "pdo_mssql", "mssql", "pdo_pgsql", "sybase_ct", "ftp", 
"curl");
diff --git a/src/com/mostc/pftt/scenario/SimpleScenarioSetup.java 
b/src/com/mostc/pftt/scenario/SimpleScenarioSetup.java
index 479732b..780724e 100644
--- a/src/com/mostc/pftt/scenario/SimpleScenarioSetup.java
+++ b/src/com/mostc/pftt/scenario/SimpleScenarioSetup.java
@@ -5,11 +5,33 @@ import java.util.Map;
 import com.mostc.pftt.host.AHost;
 import com.mostc.pftt.model.core.PhpBuild;
 import com.mostc.pftt.model.core.PhpIni;
+import com.mostc.pftt.results.AbstractPhpUnitRW;
+import com.mostc.pftt.results.AbstractPhptRW;
 import com.mostc.pftt.results.ConsoleManager;
 
 public abstract class SimpleScenarioSetup implements IScenarioSetup {
 
        @Override
+       public boolean isNeededPhpUnitWriter() {
+               return false;
+       }
+       
+       @Override
+       public void setPhpUnitWriter(AHost runner_host, ScenarioSetSetup 
scenario_set_setup, PhpBuild build, PhpIni ini, AbstractPhpUnitRW phpunit) {
+               
+       }
+       
+       @Override
+       public void setPHPTWriter(AHost runner_host, ScenarioSetSetup 
scenario_set_setup, PhpBuild build, PhpIni ini, AbstractPhptRW phpt) {
+               
+       }
+       
+       @Override
+       public boolean isNeededPhptWriter() {
+               return false;
+       }
+       
+       @Override
        public void notifyScenarioSetSetup(ScenarioSetSetup setup) {
                
        }
diff --git a/src/com/mostc/pftt/scenario/WebServerScenario.java 
b/src/com/mostc/pftt/scenario/WebServerScenario.java
index 198b8c2..ef27f62 100644
--- a/src/com/mostc/pftt/scenario/WebServerScenario.java
+++ b/src/com/mostc/pftt/scenario/WebServerScenario.java
@@ -101,8 +101,8 @@ public abstract class WebServerScenario extends 
SAPIScenario {
        }
        
        @Override
-       public AbstractPhptTestCaseRunner createPhptTestCaseRunner(PhptThread 
thread, TestCaseGroupKey group_key, PhptTestCase test_case, ConsoleManager cm, 
ITestResultReceiver twriter, AHost host, ScenarioSetSetup scenario_set_setup, 
PhpBuild build, PhptSourceTestPack src_test_pack, PhptActiveTestPack 
active_test_pack) {
-               return new HttpPhptTestCaseRunner(this, group_key.getPhpIni(), 
group_key.getEnv(), params, httpproc, httpexecutor, smgr, 
thread.getThreadWebServerInstance(), thread, test_case, cm, twriter, host, 
scenario_set_setup, build, src_test_pack, active_test_pack);
+       public AbstractPhptTestCaseRunner createPhptTestCaseRunner(PhptThread 
thread, TestCaseGroupKey group_key, PhptTestCase test_case, ConsoleManager cm, 
ITestResultReceiver twriter, AHost host, ScenarioSetSetup scenario_set_setup, 
PhpBuild build, PhptSourceTestPack src_test_pack, PhptActiveTestPack 
active_test_pack, boolean xdebug) {
+               return new HttpPhptTestCaseRunner(xdebug, this, 
group_key.getPhpIni(), group_key.getEnv(), params, httpproc, httpexecutor, 
smgr, thread.getThreadWebServerInstance(), thread, test_case, cm, twriter, 
host, scenario_set_setup, build, src_test_pack, active_test_pack);
        }
        
        @Override

Reply via email to