Commit:    5a47517085698cd53e7b7a3c67a9a172803345ba
Author:    Matt Ficken <v-maf...@microsoft.com>         Mon, 12 Nov 2012 
15:42:31 -0800
Parents:   d784ea41a3e0a0dad768054bf90ed81de5b92d5e
Branches:  master

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

Log:
Multi-Scenario configuration, permutation and testing support


Former-commit-id: 76c03a4888ac9e3218af681afafc5adea82078dc

Changed paths:
  M  README.mdown
  A  conf/all_databases.groovy
  A  conf/all_smb.groovy
  A  conf/other_network_services.groovy
  M  doc/index.html
  M  src/com/mostc/pftt/host/Host.java
  M  src/com/mostc/pftt/host/LocalHost.java
  M  src/com/mostc/pftt/host/SSHHost.java
  A  src/com/mostc/pftt/main/Config.java
  D  src/com/mostc/pftt/main/ConfigUtil.java
  M  src/com/mostc/pftt/main/PfttMain.java
  M  src/com/mostc/pftt/model/phpt/PhpBuild.java
  M  src/com/mostc/pftt/model/phpt/PhpIni.java
  M  src/com/mostc/pftt/model/phpt/PhptSourceTestPack.java
  M  src/com/mostc/pftt/model/phpt/PhptTestCase.java
  M  src/com/mostc/pftt/report/AbstractReportGen.groovy
  M  src/com/mostc/pftt/runner/AbstractPhptTestCaseRunner.java
  M  src/com/mostc/pftt/runner/AbstractPhptTestCaseRunner2.java
  M  src/com/mostc/pftt/runner/CliPhptTestCaseRunner.java
  M  src/com/mostc/pftt/runner/HttpTestCaseRunner.java
  M  src/com/mostc/pftt/runner/PhptTestPackRunner.java
  M  src/com/mostc/pftt/scenario/AbstractCodeCacheScenario.java
  M  src/com/mostc/pftt/scenario/AbstractFileSystemScenario.java
  M  src/com/mostc/pftt/scenario/AbstractParallelScenario.java
  M  src/com/mostc/pftt/scenario/AbstractSAPIScenario.java
  M  src/com/mostc/pftt/scenario/AbstractSMBScenario.java
  M  src/com/mostc/pftt/scenario/AbstractSerialScenario.java
  M  src/com/mostc/pftt/scenario/AbstractSocketScenario.java
  M  src/com/mostc/pftt/scenario/CLIScenario.java
  M  src/com/mostc/pftt/scenario/LocalFileSystemScenario.java
  M  src/com/mostc/pftt/scenario/SMBCSCOptionScenario.java
  M  src/com/mostc/pftt/scenario/SMBDFSRScenario.java
  M  src/com/mostc/pftt/scenario/SMBDeduplicationScenario.java
  M  src/com/mostc/pftt/scenario/Scenario.java
  M  src/com/mostc/pftt/scenario/ScenarioSet.java
  M  src/com/mostc/pftt/telemetry/ConsoleManager.java
  M  src/com/mostc/pftt/telemetry/PhptTestResult.java
  M  src/com/mostc/pftt/ui/ExpectedActualDiffPHPTDisplay.java
  M  src/com/mostc/pftt/ui/PhptDebuggerFrame.java
  M  src/com/mostc/pftt/util/HostEnvUtil.java

diff --git a/README.mdown b/README.mdown
index b4ed406..43734eb 100644
--- a/README.mdown
+++ b/README.mdown
@@ -144,11 +144,11 @@ PFTT will automatically generate an FBC report and 
display it using system defau
 
 ## Adding Scenarios
 
-To add any test [Scenario](doc/apidoc/com/mostc/pftt/scenario/Scenario.html), 
implement [Scenario](doc/apidoc/com/mostc/pftt/scenario/Scenario.html) and 
instantiate it in Scenario#getAllScenarios.
+To add any test [Scenario](doc/apidoc/com/mostc/pftt/scenario/Scenario.html), 
implement [Scenario](doc/apidoc/com/mostc/pftt/scenario/Scenario.html) and 
instantiate it in Scenario#getAllDefaultScenarios or a config file if it needs 
special configuration (like connecting to a remote host, database, etc... that 
the user would have to setup).
 
 ### Database Scenario
 
-More specifically, to add a database scenario (MySQL, Firebird, etc...) 
implement 
[AbstractDatabaseScenario](doc/apidoc/com/mostc/pftt/scenario/AbstractDatabaseScenario.html)
 and add it to Scenario#getAllScenarios.
+More specifically, to add a database scenario (MySQL, Firebird, etc...) 
implement 
[AbstractDatabaseScenario](doc/apidoc/com/mostc/pftt/scenario/AbstractDatabaseScenario.html)
 and add it to Scenario#getAllDefaultScenarios or a config file if it needs 
special configuration (like connecting to a remote host, database, etc... that 
the user would have to setup).
 
 ### SAPI Scenario
 
diff --git a/conf/all_databases.groovy b/conf/all_databases.groovy
new file mode 100644
index 0000000..f8c1ccc
--- /dev/null
+++ b/conf/all_databases.groovy
@@ -0,0 +1,11 @@
+
+def scenarios() {
+[
+       new MSAccessScenario(),
+       new MSSQLODBCScenario(),
+       new MSSQLScenario(),
+       new MySQLScenario(),
+       new PostgresSQLScenario(),
+       new SQLite3Scenario()
+]
+}
diff --git a/conf/all_smb.groovy b/conf/all_smb.groovy
new file mode 100644
index 0000000..cf6f3e4
--- /dev/null
+++ b/conf/all_smb.groovy
@@ -0,0 +1,12 @@
+
+def scenarios() {
+       SSHHost win8_server = new SSHHost("192.168.1.1", "administrator", 
"password01!");
+[
+       new SMBBasicScenario(win8_server),
+       new SMBDeduplicationScenario(win8_server, "E:"),
+       /* XXX new SMBDFSScenario(),
+       new SMBCAScenario(),*/
+       // probably don't need to test branch cache, but including it for 
completeness
+       //new SMBBranchCacheScenario()
+]
+}
diff --git a/conf/other_network_services.groovy 
b/conf/other_network_services.groovy
new file mode 100644
index 0000000..ea8d1f5
--- /dev/null
+++ b/conf/other_network_services.groovy
@@ -0,0 +1,11 @@
+
+def scenarios() {
+[
+       // streams
+       new FTPScenario(),
+       new HTTPScenario(),
+       // web services
+       new SOAPScenario(),
+       new XMLRPCScenario()
+]
+}
diff --git a/doc/index.html b/doc/index.html
index 949fbfc..f704f21 100644
--- a/doc/index.html
+++ b/doc/index.html
@@ -180,11 +180,11 @@ That should be it (PFTT comes with eclipse .project and 
.classpath files which a
 
 <h2>Adding Scenarios</h2>
 
-<p>To add any test Scenario, implement <a 
href="apidoc/com/mostc/pftt/scenario/Scenario.html">Scenario</a> and 
instantiate it in Scenario#getAllScenarios.</p>
+<p>To add any test Scenario, implement <a 
href="apidoc/com/mostc/pftt/scenario/Scenario.html">Scenario</a> and 
instantiate it in Scenario#getAllDefaultScenarios or a config file if it needs 
special configuration (like connecting to a remote host, database, etc... that 
the user would have to setup).</p>
 
 <h3>Database Scenario</h3>
 
-<p>More specifically, to add a database scenario (MySQL, Firebird, etc...) 
implement <a 
href="apidoc/com/mostc/pftt/scenario/AbstractDatabaseScenario.html">AbstractDatabaseScenario</a>
 and add it to Scenario#getAllScenarios.</p>
+<p>More specifically, to add a database scenario (MySQL, Firebird, etc...) 
implement <a 
href="apidoc/com/mostc/pftt/scenario/AbstractDatabaseScenario.html">AbstractDatabaseScenario</a>
 and add it to Scenario#getAllDefaultScenarios or a config file if it needs 
special configuration (like connecting to a remote host, database, etc... that 
the user would have to setup).</p>
 
 <h3>SAPI Scenario</h3> 
 
diff --git a/src/com/mostc/pftt/host/Host.java 
b/src/com/mostc/pftt/host/Host.java
index d5c0790..3860be8 100644
--- a/src/com/mostc/pftt/host/Host.java
+++ b/src/com/mostc/pftt/host/Host.java
@@ -19,7 +19,7 @@ import com.mostc.pftt.util.StringUtil;
 /** Abstracts host management so client code doesn't need to care if host is 
local or remote(ssh).
  * 
  * @see #exec
- * @see #cmd
+ * @see #test_cmd
  * @see Host#DEV
  * @see #isRemote
  * @see #readFile
@@ -87,7 +87,7 @@ public abstract class Host {
                if (path.length() >= 2) {
                        if (path.charAt(1)==':') {
                                if (Character.isLetter(path.charAt(0)))
-                                       return path.substring(0, 1);
+                                       return path.substring(0, 
1).toUpperCase();
                        }
                }
                return null;
@@ -170,8 +170,8 @@ public abstract class Host {
         * @throws IllegalStateException
         * @throws IOException
         */
-       public abstract void saveFile(String filename, String text) throws 
IllegalStateException, IOException;
-       public abstract void saveFile(String filename, String text, Charset 
charset) throws IllegalStateException, IOException;
+       public abstract void saveTextFile(String filename, String text) throws 
IllegalStateException, IOException;
+       public abstract void saveTextFile(String filename, String text, Charset 
charset) throws IllegalStateException, IOException;
        public abstract void delete(String path) throws IllegalStateException, 
IOException;
        public void deleteIfExists(String path) {
                try {
@@ -752,5 +752,26 @@ public abstract class Host {
                }
                return 0L;
        } // end public long getTotalPhysicalMemoryK
+
+       /** on Windows, returns the directory where windows is stored, 
typically C:\Windows.
+        * 
+        * on other OSes, it returns /
+        * 
+        * @return
+        */
+       public String getSystemRoot() {
+               if (!isWindows())
+                       return "/";
+               String sr = getEnvValue("SYSTEMROOT");
+               if (StringUtil.isNotEmpty(sr))
+                       return sr;
+               else
+                       // fallback
+                       return getSystemDrive()+"\\Windows";
+       }
+       
+       public abstract boolean dirContainsExact(String path, String name);
+       public abstract boolean dirContainsFragment(String path, String 
name_fragment);
+       public abstract String[] list(String path);
        
 } // end public abstract class Host
diff --git a/src/com/mostc/pftt/host/LocalHost.java 
b/src/com/mostc/pftt/host/LocalHost.java
index 029ce73..8b87fba 100644
--- a/src/com/mostc/pftt/host/LocalHost.java
+++ b/src/com/mostc/pftt/host/LocalHost.java
@@ -126,12 +126,12 @@ public class LocalHost extends Host {
        }
 
        @Override
-       public void saveFile(String filename, String text) throws IOException {
-               saveFile(filename, text, null);
+       public void saveTextFile(String filename, String text) throws 
IOException {
+               saveTextFile(filename, text, null);
        }
 
        @Override
-       public void saveFile(String filename, String text, Charset charset) 
throws IOException {
+       public void saveTextFile(String filename, String text, Charset charset) 
throws IOException {
                if (text==null)
                        text = "";
                FileOutputStream fos = new FileOutputStream(filename);
@@ -602,5 +602,37 @@ public class LocalHost extends Host {
        protected String getOSNameOnWindows() {
                return getOSNameLong();
        }
+
+       @Override
+       public boolean dirContainsExact(String path, String name) {
+               for ( File file : new File(path).listFiles() ) {
+                       if (file.getName().equalsIgnoreCase(name))
+                               return true;
+               }
+               return false;
+       }
+
+       @Override
+       public boolean dirContainsFragment(String path, String name_fragment) {
+               name_fragment = name_fragment.toLowerCase();
+               for ( File file : new File(path).listFiles() ) {
+                       if 
(file.getName().toLowerCase().contains(name_fragment))
+                               return true;
+               }
+               return false;
+       }
+
+       @Override
+       public String[] list(String path) {
+               return new File(path).list();
+       }
+       
+       public static String getLocalPfttDir() {
+               if (DEV > 0) {
+                       return isLocalhostWindows() ? 
"C:\\php-sdk\\PFTT\\Dev-"+DEV+"\\" : 
System.getenv("HOME")+"/php-sdk/PFTT/dev-"+DEV+"/";
+               } else {
+                       return isLocalhostWindows() ? 
"C:\\php-sdk\\PFTT\\Current\\" : System.getenv("HOME")+"/php-sdk/PFTT/current/";
+               }
+       }
        
 } // end public class Host
diff --git a/src/com/mostc/pftt/host/SSHHost.java 
b/src/com/mostc/pftt/host/SSHHost.java
index 14bd262..949ee31 100644
--- a/src/com/mostc/pftt/host/SSHHost.java
+++ b/src/com/mostc/pftt/host/SSHHost.java
@@ -14,6 +14,7 @@ import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.nio.charset.Charset;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.Timer;
 import java.util.TimerTask;
@@ -240,7 +241,7 @@ public class SSHHost extends RemoteHost {
        }
        
        @Override
-       public void saveFile(String filename, String text, Charset charset) 
throws IOException {
+       public void saveTextFile(String filename, String text, Charset charset) 
throws IOException {
                if (text==null)
                        text = "";
                ensureScpOpen();
@@ -249,8 +250,8 @@ public class SSHHost extends RemoteHost {
        }
 
        @Override
-       public void saveFile(String filename, String text) throws IOException {
-               saveFile(filename, text, null);
+       public void saveTextFile(String filename, String text) throws 
IOException {
+               saveTextFile(filename, text, null);
        }
        
        protected SessionChannelClient do_exec(String cmd, Map<String, String> 
env, String chdir, byte[] stdin_post, OutputStream out) throws IOException, 
IllegalStateException {
@@ -531,4 +532,35 @@ public class SSHHost extends RemoteHost {
                return false;
        }
 
+       @Override
+       public boolean dirContainsExact(String path, String name) {
+               for ( String a : list(path) ) {
+                       if (a.equalsIgnoreCase(name))
+                               return true;
+               }
+               return false;
+       }
+
+       @Override
+       public boolean dirContainsFragment(String path, String name_fragment) {
+               for ( String a : list(path) ) {
+                       if (a.contains(name_fragment))
+                               return true;
+               }
+               return false;
+       }
+
+       @SuppressWarnings({ "rawtypes", "unchecked" })
+       @Override
+       public String[] list(String path) {
+               try {
+                       ensureSftpOpen();
+                       List list = sftp.ls(path);
+                       return (String[]) list.toArray(new String[list.size()]);
+               } catch ( Exception ex ) {
+                       ex.printStackTrace();
+               }
+               return StringUtil.EMPTY_ARRAY;
+       }
+
 } // end public class SSHHost
diff --git a/src/com/mostc/pftt/main/Config.java 
b/src/com/mostc/pftt/main/Config.java
new file mode 100644
index 0000000..ed752fa
--- /dev/null
+++ b/src/com/mostc/pftt/main/Config.java
@@ -0,0 +1,272 @@
+package com.mostc.pftt.main;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.net.ftp.FTPClient;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.columba.ristretto.smtp.SMTPProtocol;
+
+import com.github.mattficken.io.IOUtil;
+import com.mostc.pftt.host.Host;
+import com.mostc.pftt.scenario.Scenario;
+import com.mostc.pftt.scenario.ScenarioSet;
+import com.mostc.pftt.telemetry.ConsoleManager;
+
+import groovy.lang.GroovyClassLoader;
+import groovy.lang.GroovyObject;
+
+/** Handles loading a configuration (hosts, scenarios, credentials, etc...) 
from a groovy script.
+ * 
+ * This allows for loading scenarios and hosts without creating a special 
configuration format, its just executable code.
+ * 
+ * @see Config#getHosts
+ * @see Config#loadConfigFromFile
+ * 
+ * An example configuration file:
+ * def hosts() {
+ *             [
+ *                     // connect to a host using ssh
+ *         //
+ *         // can test multiple operating systems, one host for each OS (so 
multiple hosts)
+ *                     new SSHHost("192.168.1.1", "administrator", 
"password01!")
+ *             ]
+ * }
+ * def scenario_sets() {
+ *             [
+ *                     // provide address of a MySQL server
+ *                     new MySQLScenario()
+ *             ]
+ * }
+ * def configure_smtp(def smtp_client) {
+ *             // specify smtp server and credentials 
+ * }
+ * def configure_ftp_client(def ftp_client) {
+ *             // specify ftp server and credentials
+ * }
+ * 
+ * @author Matt Ficken
+ * 
+ */
+
+public final class Config {
+       public static final String HOSTS_METHOD = "hosts";
+       public static final String SCENARIO_SETS_METHOD = "scenario_sets";
+       public static final String SCENARIOS_METHOD = "scenarios";
+       public static final String CONFIGURE_SMTP_METHOD = "configure_smtp";
+       public static final String CONFIGURE_FTP_CLIENT_METHOD = 
"configure_ftp_client";
+       //
+       protected final LinkedList<Host> hosts;
+       protected final LinkedList<ScenarioSet> scenario_sets;
+       protected GroovyObject configure_smtp_method, 
configure_ftp_client_method;
+       
+       protected Config() {
+               hosts = new LinkedList<Host>();
+               scenario_sets = new LinkedList<ScenarioSet>();
+       }
+       
+       public List<Host> getHosts() {
+               return hosts;
+       }
+       
+       public List<ScenarioSet> getScenarioSets() {
+               return scenario_sets;
+       }
+       
+       public boolean configureSMTP(SMTPProtocol smtp) {
+               return configureSMTP(null, smtp);
+       }
+       
+       public boolean configureSMTP(ConsoleManager cm, SMTPProtocol smtp) {
+               if (configure_smtp_method==null)
+                       return false;
+               try {
+                       
configure_smtp_method.invokeMethod(CONFIGURE_SMTP_METHOD, smtp);
+                       
+                       return true;
+               } catch ( Exception ex ) {
+                       if (cm==null)
+                               ex.printStackTrace();
+                       else
+                               cm.printStackTrace(ex);
+               }
+               return false;
+       }
+       
+       public boolean configureFTPClient(FTPClient ftp) {
+               return configureFTPClient(null, ftp);
+       }
+       
+       public boolean configureFTPClient(ConsoleManager cm, FTPClient ftp) {
+               if (configure_ftp_client_method==null)
+                       return false;
+               try {
+                       
configure_ftp_client_method.invokeMethod(CONFIGURE_FTP_CLIENT_METHOD, ftp);
+                       
+                       return true;
+               } catch ( Exception ex ) {
+                       if (cm==null)
+                               ex.printStackTrace();
+                       else
+                               cm.printStackTrace(ex);
+               }
+               return false;
+       }
+       
+       public static Config loadConfigFromStreams(ConsoleManager cm, 
InputStream... ins) throws CompilationFailedException, InstantiationException, 
IllegalAccessException, IOException {
+               GroovyClassLoader loader = new 
GroovyClassLoader(Config.class.getClassLoader());
+               
+               Config config = new Config();
+               
+               LinkedList<Scenario> scenarios = new LinkedList<Scenario>();
+               
scenarios.addAll(Arrays.asList(Scenario.getAllDefaultScenarios()));
+               
+               // load each configuration streams
+               GroovyObject go;
+               Class<?> clazz;
+               int i=1;
+               for (InputStream in : ins) {
+                       clazz = 
loader.parseClass(importString(IOUtil.toString(in)));
+                       
+                       go = (GroovyObject) clazz.newInstance();
+                       
+                       // call methods in file to get configuration (hosts, 
etc...)
+                       loadObjectToConfig(cm, config, go, scenarios, 
"InputStream #"+(i++)+" ("+in+")");
+               }
+               
+               return loadConfigCommon(cm, scenarios, config);
+       } // end public static Config loadConfigFromStreams
+       
+       public static Config loadConfigFromFiles(ConsoleManager cm, File... 
files) throws CompilationFailedException, InstantiationException, 
IllegalAccessException, IOException {
+               GroovyClassLoader loader = new 
GroovyClassLoader(Config.class.getClassLoader());
+               
+               Config config = new Config();
+               
+               LinkedList<Scenario> scenarios = new LinkedList<Scenario>();
+               
scenarios.addAll(Arrays.asList(Scenario.getAllDefaultScenarios()));
+               
+               // load each configuration file
+               GroovyObject go;
+               Class<?> clazz;
+               for (File file : files) {
+                       clazz = 
loader.parseClass(importString(IOUtil.toString(new FileInputStream(file))));
+                       
+                       go = (GroovyObject) clazz.newInstance();
+                       
+                       // call methods in file to get configuration (hosts, 
etc...)
+                       loadObjectToConfig(cm, config, go, scenarios, 
file.getAbsolutePath());
+               }
+               
+               return loadConfigCommon(cm, scenarios, config);
+       } // end public static Config loadConfigFromFiles
+       
+       protected static String importString(String code) {
+               // a hack to import common classes for configuration files (XXX 
do this a better way)
+               StringBuilder sb = new StringBuilder(128+code.length());
+               // import all standard Scenarios and Host types
+               sb.append("import 
");sb.append(Scenario.class.getPackage().getName());sb.append(".*;\n");
+               sb.append("import 
");sb.append(Host.class.getPackage().getName());sb.append(".*;\n");
+               sb.append("import 
");sb.append(SMTPProtocol.class.getName());sb.append(";\n");
+               sb.append("import 
");sb.append(FTPClient.class.getName());sb.append(";\n");
+               sb.append(code);
+               return sb.toString();
+       }
+       
+       protected static Config loadConfigCommon(ConsoleManager cm, 
LinkedList<Scenario> scenarios, Config config) {
+               // configs may specify individual scenarios, in addition or in 
place of whole sets
+               //
+               // permute the given individual scenarios and add them to the 
list of scenario sets
+               for (ScenarioSet scenario_set : 
ScenarioSet.permuteScenarioSets(scenarios) ) {
+                       if (!config.scenario_sets.contains(scenario_set))
+                               config.scenario_sets.add(scenario_set);
+               }
+               //
+               
+               if (cm!=null) {
+                       // report progress
+                       if (config.hosts.size()>0) {
+                               cm.println("Config", "Loaded 
"+config.hosts.size()+" hosts");
+                       }
+                       if (config.scenario_sets.size()>0) {
+                               cm.println("Config", "Loaded 
"+config.scenario_sets.size()+" Scenario-Sets");
+                       }
+               }
+               
+               
+               return config;
+       } // end protected static Config loadConfigCommon
+       
+       protected static void loadObjectToConfig(ConsoleManager cm, Config 
config, GroovyObject go, List<Scenario> scenarios, String file_name) {
+               Object ret;
+               try {
+                       ret = go.invokeMethod(HOSTS_METHOD, null);
+                       if (ret instanceof List) {
+                               for (Object o : (List<?>)ret) {
+                                       if (o instanceof Host) {
+                                               if (!config.hosts.contains(o))
+                                                       
config.hosts.add((Host)o);
+                                       } else {
+                                               cm.println("Config", "List 
returned by hosts() must only contain Host objects, not: "+o.getClass()+" see: 
"+file_name);
+                                       }
+                               }
+                       } else {
+                               cm.println("Config", "hosts() must return List 
of Hosts, not: "+(ret==null?"null":ret.getClass())+" see: "+file_name);
+                       }
+               } catch ( Exception ex ) {
+               }
+               try {
+                       ret = go.invokeMethod(SCENARIO_SETS_METHOD, null);
+                       if (ret instanceof List) {
+                               for (Object o : (List<?>)ret) {
+                                       if (o instanceof ScenarioSet) {
+                                               if 
(!config.scenario_sets.contains(o))
+                                                       
config.scenario_sets.add((ScenarioSet)o);
+                                       } else {
+                                               cm.println("Config", "List 
returned by scenario_sets() must only contain ScenarioSet objects, not: 
"+o.getClass()+" see: "+file_name);
+                                       }
+                               }
+                       } else {
+                               cm.println("Config", "scenario_sets() must 
return List of ScenarioSets, not: "+(ret==null?"null":ret.getClass())+" see: 
"+file_name);
+                       }
+               } catch ( Exception ex ) {
+               }
+               try {
+                       ret = go.invokeMethod(SCENARIOS_METHOD, null);
+                       if (ret instanceof List) {
+                               for (Object o : (List<?>)ret) {
+                                       if (o instanceof Scenario) {
+                                               if (!scenarios.contains(o))
+                                                       
scenarios.add((Scenario)o);
+                                       } else {
+                                               cm.println("Config", "List 
returned by scenarios() must only contain Scenario objects, not: 
"+o.getClass()+" see: "+file_name);
+                                       }
+                               }
+                       } else {
+                               cm.println("Config", "scenarios() must return 
List of Scenarios, not: "+(ret==null?"null":ret.getClass())+" see: "+file_name);
+                       }
+               } catch ( Exception ex ) {
+               }
+               try {
+                       if (go.getProperty(CONFIGURE_SMTP_METHOD)!=null) {
+                               if (config.configure_smtp_method!=null)
+                                       cm.println("Config", 
CONFIGURE_SMTP_METHOD+"() overriden by : "+file_name);
+                               config.configure_smtp_method = go;
+                       }
+               } catch ( Exception ex ) {
+               }
+               try {
+                       if (go.getProperty(CONFIGURE_FTP_CLIENT_METHOD)!=null) {
+                               if (config.configure_ftp_client_method!=null)
+                                       cm.println("Config", 
CONFIGURE_FTP_CLIENT_METHOD+"() overriden by : "+file_name);
+                               config.configure_ftp_client_method = go;
+                       }
+               } catch ( Exception ex ) {
+               }
+       } // end protected static void loadObjectToConfig
+       
+} // end public final class ConfigUtil
diff --git a/src/com/mostc/pftt/main/ConfigUtil.java 
b/src/com/mostc/pftt/main/ConfigUtil.java
deleted file mode 100644
index 4d4eece..0000000
--- a/src/com/mostc/pftt/main/ConfigUtil.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package com.mostc.pftt.main;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-
-import org.apache.commons.net.ftp.FTPClient;
-import org.codehaus.groovy.control.CompilationFailedException;
-import org.columba.ristretto.smtp.SMTPProtocol;
-
-import com.mostc.pftt.host.Host;
-import com.mostc.pftt.scenario.ScenarioSet;
-
-import groovy.lang.GroovyClassLoader;
-import groovy.lang.GroovyObject;
-
-/** Handles loading a configuration (hosts, scenarios, credentials, etc...) 
from a groovy script.
- * 
- * This allows for loading scenarios and hosts without creating a special 
configuration format, its just executable code.
- * 
- * @see ConfigUtil#getHosts
- * @see ConfigUtil#loadConfigFromFile
- * 
- * An example configuration file:
- * hosts() {
- *             [
- *                     // connect to a host using ssh
- *         //
- *         // can test multiple operating systems, one host for each OS (so 
multiple hosts)
- *                     new SSHHost("192.168.1.1", "administrator", 
"password01!")
- *             ]
- * }
- * scenario_sets() {
- *             [
- *                     // provide address of a MySQL server
- *                     new MySQLScenario()
- *             ]
- * }
- * configure_smtp(def smtp_client) {
- *             // specify smtp server and credentials 
- * }
- * configure_ftp_client(def ftp_client) {
- *             // specify ftp server and credentials
- * }
- * 
- * @author Matt Ficken
- * 
- */
-
-public final class ConfigUtil {
-       public static final String HOSTS_METHOD = "hosts";
-       public static final String SCENARIO_SETS_METHOD = "scenario_sets";
-       public static final String CONFIGURE_SMTP_METHOD = "configure_smtp";
-       public static final String CONFIGURE_FTP_CLIENT_METHOD = 
"configure_ftp_client";
-       
-       @SuppressWarnings("unchecked")
-       public static List<Host> getHosts(GroovyObject gobj) {
-               return (List<Host>) gobj.invokeMethod(HOSTS_METHOD, null);
-       }
-       
-       @SuppressWarnings("unchecked")
-       public static List<ScenarioSet> getScenarioSets(GroovyObject gobj) {
-               return (List<ScenarioSet>) 
gobj.invokeMethod(SCENARIO_SETS_METHOD, null);
-       }
-       
-       public static void configureSMTPProtocol(GroovyObject gobj, 
SMTPProtocol smtp) {
-               gobj.invokeMethod(CONFIGURE_SMTP_METHOD, smtp);
-       }
-       
-       public static void configureFTPClient(GroovyObject gobj, FTPClient ftp) 
{
-               gobj.invokeMethod(CONFIGURE_FTP_CLIENT_METHOD, ftp);
-       }
-       
-       @SuppressWarnings("deprecation")
-       public static GroovyObject loadConfigFromStream(InputStream in) throws 
CompilationFailedException, InstantiationException, IllegalAccessException {
-               GroovyClassLoader gcl = new 
GroovyClassLoader(ConfigUtil.class.getClassLoader());
-               return (GroovyObject) gcl.parseClass(in).newInstance();
-       }
-       
-       public static GroovyObject loadConfigFromFile(File file) throws 
CompilationFailedException, InstantiationException, IllegalAccessException, 
IOException {
-               GroovyClassLoader gcl = new 
GroovyClassLoader(ConfigUtil.class.getClassLoader());
-               return (GroovyObject) gcl.parseClass(file).newInstance();       
        
-       }
-       
-       private ConfigUtil() {}
-       
-} // end public final class ConfigUtil
diff --git a/src/com/mostc/pftt/main/PfttMain.java 
b/src/com/mostc/pftt/main/PfttMain.java
index 94965ae..c708bf9 100644
--- a/src/com/mostc/pftt/main/PfttMain.java
+++ b/src/com/mostc/pftt/main/PfttMain.java
@@ -1,7 +1,6 @@
 package com.mostc.pftt.main;
 
 import groovy.lang.Binding;
-import groovy.lang.GroovyObject;
 import groovy.ui.Console;
 
 import java.awt.Desktop;
@@ -113,26 +112,28 @@ public class PfttMain {
                return last_file == null ? null : 
PhptTelemetryReader.open(host, last_file);
        }
 
-       public void run_all(ConsoleManager cm, PhpBuild build, 
PhptSourceTestPack test_pack, ScenarioSet scenario_set) throws Exception {
-               LinkedList<PhptTestCase> test_cases = new 
LinkedList<PhptTestCase>();
-               
-               PhptTelemetryWriter tmgr = new PhptTelemetryWriter(host, cm, 
telem_dir(), build, test_pack, scenario_set);
-               test_pack.cleanup();
-               cm.println("PhptTestPack", "enumerating test cases from 
test-pack...");
-               test_pack.add_test_files(test_cases, tmgr, build);
-               cm.println("PhptTestPack", "enumerated test cases.");
-               
-               PhptTestPackRunner test_pack_runner = new 
PhptTestPackRunner(tmgr, test_pack, scenario_set, build, host);
-               cm.showGUI(test_pack_runner);
-               
-               test_pack_runner.runTestList(test_cases);
-               
-               tmgr.close();
-               
-               new PhptTestCountsMatchSmokeTest();
-               
-               phpt_report(tmgr);
-       }
+       public void run_all(ConsoleManager cm, PhpBuild build, 
PhptSourceTestPack test_pack, List<ScenarioSet> scenario_sets) throws Exception 
{
+               for ( ScenarioSet scenario_set : scenario_sets ) {
+                       ArrayList<PhptTestCase> test_cases = new 
ArrayList<PhptTestCase>(12600);
+                       
+                       PhptTelemetryWriter tmgr = new 
PhptTelemetryWriter(host, cm, telem_dir(), build, test_pack, scenario_set);
+                       test_pack.cleanup();
+                       cm.println("PhptTestPack", "enumerating test cases from 
test-pack...");
+                       test_pack.read(test_cases, tmgr, build);
+                       cm.println("PhptTestPack", "enumerated test cases.");
+                       
+                       PhptTestPackRunner test_pack_runner = new 
PhptTestPackRunner(tmgr, test_pack, scenario_set, build, host);
+                       cm.showGUI(test_pack_runner);
+                       
+                       test_pack_runner.runTestList(test_cases);
+                       
+                       tmgr.close();
+                       
+                       new PhptTestCountsMatchSmokeTest();
+                       
+                       phpt_report(tmgr);
+               }
+       } // end public void run_all
        
        protected void phpt_report(PhptTelemetryWriter test_telem) throws 
FileNotFoundException {       
                PhptTelemetryReader base_telem = last_telem(test_telem);
@@ -157,25 +158,27 @@ public class PfttMain {
                }
        }
 
-       public void run_named_tests(ConsoleManager cm, PhpBuild build, 
PhptSourceTestPack test_pack, ScenarioSet scenario_set, List<String> names) 
throws Exception {
-               LinkedList<PhptTestCase> test_cases = new 
LinkedList<PhptTestCase>();
-               
-               PhptTelemetryWriter tmgr = new PhptTelemetryWriter(host, cm, 
telem_dir(), build, test_pack, scenario_set);
-               test_pack.cleanup();
-               cm.println("PhptTestPack", "enumerating test cases from 
test-pack...");
-               test_pack.add_named_tests(test_cases, names, tmgr, build);
-               cm.println("PhptTestPack", "enumerated test cases.");
-               
-               PhptTestPackRunner test_pack_runner = new 
PhptTestPackRunner(tmgr, test_pack, scenario_set, build, host);
-               cm.showGUI(test_pack_runner);
-               
-               test_pack_runner.runTestList(test_cases);
-               
-               tmgr.close();
-               
-               new PhptTestCountsMatchSmokeTest();
-               
-               phpt_report(tmgr);
+       public void run_named_tests(ConsoleManager cm, PhpBuild build, 
PhptSourceTestPack test_pack, List<ScenarioSet> scenario_sets, List<String> 
names) throws Exception {
+               for ( ScenarioSet scenario_set : scenario_sets ) {
+                       LinkedList<PhptTestCase> test_cases = new 
LinkedList<PhptTestCase>();
+                       
+                       PhptTelemetryWriter tmgr = new 
PhptTelemetryWriter(host, cm, telem_dir(), build, test_pack, scenario_set);
+                       test_pack.cleanup();
+                       cm.println("PhptTestPack", "enumerating test cases from 
test-pack...");
+                       test_pack.read(test_cases, names, tmgr, build);
+                       cm.println("PhptTestPack", "enumerated test cases.");
+                       
+                       PhptTestPackRunner test_pack_runner = new 
PhptTestPackRunner(tmgr, test_pack, scenario_set, build, host);
+                       cm.showGUI(test_pack_runner);
+                       
+                       test_pack_runner.runTestList(test_cases);
+                       
+                       tmgr.close();
+                       
+                       new PhptTestCountsMatchSmokeTest();
+                       
+                       phpt_report(tmgr);
+               } // end for
        }
        
        /* -------------------------------------------------- */
@@ -205,12 +208,15 @@ public class PfttMain {
                System.out.println();
                System.out.println("Options:");
                System.out.println("-gui - show gui for certain commands");
-               System.out.println("-config <file> - configuration file");
+               System.out.println("-config <file1,file2> - load 1+ 
configuration file(s)");
                System.out.println("-force - disables confirmation dialogs and 
forces proceeding anyway");
                System.out.println("-stress_each <0+> - runs each test-case N 
times consecutively");
                System.out.println("-stress_all <0+> - runs all tests N times 
in loop");
                System.out.println("-results_only - displays only test results 
and no other information (for automation).");
                System.out.println("-disable_debug_prompt - disables asking you 
if you want to debug PHP crashes (for automation. default=enabled)");
+               System.out.println("-phpt-not-in-place - copies PHPTs to a 
temporary dir and runs PHPTs from there (default=disabled, test in-place)");
+               System.out.println("-dont-cleanup-test-pack - doesn't delete 
temp dir created by -phpt-not-in-place or SMB scenario (default=delete)");
+               System.out.println("-auto - changes default options for 
automated testing");
                System.out.println("(note: stress options not useful against 
CLI without code caching)");
                System.out.println();
        } // end protected static void cmd_help
@@ -263,11 +269,11 @@ public class PfttMain {
                console.run();
        }
        
-       protected static void cmd_phpt_all(PfttMain rt, ConsoleManager cm, 
GroovyObject config_obj, PhpBuild build, PhptSourceTestPack test_pack) throws 
Exception {
-               rt.run_all(cm, build, test_pack, 
ScenarioSet.getScenarioSets().iterator().next());
+       protected static void cmd_phpt_all(PfttMain rt, ConsoleManager cm, 
Config config, PhpBuild build, PhptSourceTestPack test_pack) throws Exception {
+               rt.run_all(cm, build, test_pack, 
config==null?ScenarioSet.getDefaultScenarioSets():config.getScenarioSets());
        }
        
-       protected static void cmd_phpt_list(PfttMain rt, ConsoleManager cm, 
GroovyObject config_obj, PhpBuild build, PhptSourceTestPack test_pack, File 
list_file) throws Exception {
+       protected static void cmd_phpt_list(PfttMain rt, ConsoleManager cm, 
Config config, PhpBuild build, PhptSourceTestPack test_pack, File list_file) 
throws Exception {
                BufferedReader fr = new BufferedReader(new 
FileReader(list_file));
                LinkedList<String> tests = new LinkedList<String>();
                String line;
@@ -279,11 +285,11 @@ public class PfttMain {
                                tests.add(line);
                }
                
-               rt.run_named_tests(cm, build, test_pack, 
ScenarioSet.getScenarioSets().iterator().next(), tests);
+               rt.run_named_tests(cm, build, test_pack, 
config==null?ScenarioSet.getDefaultScenarioSets():config.getScenarioSets(), 
tests);
        }
        
-       protected static void cmd_phpt_named(PfttMain rt, ConsoleManager cm, 
GroovyObject config_obj, PhpBuild build, PhptSourceTestPack test_pack, 
List<String> names) throws Exception {
-               rt.run_named_tests(cm, build, test_pack, 
ScenarioSet.getScenarioSets().iterator().next(), names);
+       protected static void cmd_phpt_named(PfttMain rt, ConsoleManager cm, 
Config config, PhpBuild build, PhptSourceTestPack test_pack, List<String> 
names) throws Exception {
+               rt.run_named_tests(cm, build, test_pack, 
config==null?ScenarioSet.getDefaultScenarioSets():config.getScenarioSets(), 
names);
        }
 
        protected static void cmd_ui() {
@@ -375,7 +381,7 @@ public class PfttMain {
                HttpProtocolParams.setUserAgent(params, "Mozilla/5.0 (Windows 
NT 6.1; rv:12.0) Gecko/ 20120405 Firefox/14.0.1");
                HttpProtocolParams.setUseExpectContinue(params, true);
                
-               HttpProcessor httpproc = new ImmutableHttpProcessor(new 
HttpRequestInterceptor[] {// XXX reuse
+               HttpProcessor httpproc = new ImmutableHttpProcessor(new 
HttpRequestInterceptor[] {
                        // Required protocol interceptors
                        new RequestContent(),
                        new RequestTargetHost(),
@@ -568,8 +574,9 @@ public class PfttMain {
                //
                int args_i = 0;
                
-               GroovyObject config_obj = null;
-               boolean show_gui = false, force = false, disable_debug_prompt = 
false, results_only = false;
+               Config config = null;
+               boolean show_gui = false, force = false, disable_debug_prompt = 
false, results_only = false, dont_cleanup_test_pack = false, phpt_not_in_place 
= false;
+               LinkedList<File> config_files = new LinkedList<File>();
                int stress_all = 0, stress_each = 0;
                
                //
@@ -579,15 +586,46 @@ public class PfttMain {
                        } else if (args[args_i].equals("-force")) {
                                force = true;
                        } else if (args[args_i].equals("-config")) {
-                               File config_file = new File(args[args_i++]);
-                               if (!config_file.isFile()) {
-                                       System.err.println("User Error: config 
file not found: "+config_file);
-                                       System.exit(-255);
-                                       return;
-                               }                               
-                               
-                               // load configuration
-                               config_obj = 
ConfigUtil.loadConfigFromFile(config_file);
+                               args_i++;
+                               for ( String part : 
args[args_i].split("[;|:|,]") ) {
+                                       File config_file = new File(part);
+                                       if (config_file.exists()) {
+                                               if 
(!config_files.contains(config_file))
+                                                       
config_files.add(config_file);
+                                       } else {
+                                               config_file = new 
File(part+".groovy");
+                                               if (config_file.exists()) {
+                                                       if 
(!config_files.contains(config_file))
+                                                               
config_files.add(config_file);
+                                               } else {
+                                                       config_file = new 
File(LocalHost.getLocalPfttDir()+"/conf/"+part);
+                                                       if 
(config_file.exists()) {
+                                                               if 
(!config_files.contains(config_file))
+                                                                       
config_files.add(config_file);
+                                                       } else {
+                                                               config_file = 
new File(LocalHost.getLocalPfttDir()+"/conf/"+part+".groovy");
+                                                               if 
(config_file.exists()) {
+                                                                       if 
(!config_files.contains(config_file))
+                                                                               
config_files.add(config_file);
+                                                               } else {
+                                                                       
System.err.println("User Error: config file not found: "+config_file);
+                                                                       
System.exit(-255);
+                                                                       break;
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               } // end for
+                       } else if (args[args_i].equals("-phpt-not-in-place")) {
+                               phpt_not_in_place = false;
+                       } else if 
(args[args_i].equals("-dont-cleanup-test-pack")) {
+                               dont_cleanup_test_pack = true;
+                       } else if (args[args_i].equals("-auto")) {
+                               // change these defaults for automated testing
+                               disable_debug_prompt = true;
+                               results_only = false;
+                               dont_cleanup_test_pack = false;
+                               phpt_not_in_place = true;
                        } else if (args[args_i].equals("-stress_each")) {
                                stress_each = Integer.parseInt(args[args_i++]);
                        } else if (args[args_i].equals("-stress_all")) {
@@ -619,7 +657,9 @@ public class PfttMain {
                        System.err.println("PFTT: not implemented: 
stress_each="+stress_each+" stress_all="+stress_all+" ignored");
                }
                
-               ConsoleManager cm = new ConsoleManager(results_only, show_gui, 
disable_debug_prompt);
+               ConsoleManager cm = new ConsoleManager(results_only, show_gui, 
disable_debug_prompt, dont_cleanup_test_pack, phpt_not_in_place);
+               
+               config = Config.loadConfigFromFiles(cm, 
(File[])config_files.toArray(new File[config_files.size()]));
                
                if (command!=null) {
                        if 
(command.equals("phpt_named")||command.equals("phptnamed")||command.equals("phptn")||command.equals("pn"))
 {
@@ -653,8 +693,8 @@ public class PfttMain {
                                cm.println("Build", build.toString());
                                cm.println("Test-Pack", test_pack.toString());
                                
-                               HostEnvUtil.prepareHostEnv(rt.host, cm, 
!disable_debug_prompt);
-                               cmd_phpt_named(rt, cm, config_obj, build, 
test_pack, names);
+                               HostEnvUtil.prepareHostEnv(rt.host, cm, 
!cm.isDisableDebugPrompt());
+                               cmd_phpt_named(rt, cm, config, build, 
test_pack, names);
                                
                                System.out.println("PFTT: finished");
                        } else if 
(command.equals("phpt_list")||command.equals("phptlist")||command.equals("phptl")||command.equals("pl"))
 {
@@ -691,7 +731,7 @@ public class PfttMain {
                                cm.println("Test-Pack", test_pack.toString());
                                
                                HostEnvUtil.prepareHostEnv(rt.host, cm, 
!disable_debug_prompt);
-                               cmd_phpt_list(rt, cm, config_obj, build, 
test_pack, list_file);         
+                               cmd_phpt_list(rt, cm, config, build, test_pack, 
list_file);             
                                
                                System.out.println("PFTT: finished");
                        } else if 
(command.equals("phpt_all")||command.equals("phptall")||command.equals("phpta")||command.equals("pa"))
 {
@@ -722,7 +762,7 @@ public class PfttMain {
                                
                                // run all tests
                                HostEnvUtil.prepareHostEnv(rt.host, cm, 
!disable_debug_prompt);
-                               cmd_phpt_all(rt, cm, config_obj, build, 
test_pack);
+                               cmd_phpt_all(rt, cm, config, build, test_pack);
                                
                                System.out.println("PFTT: finished");
                        } else if (command.equals("aut")) {
@@ -735,7 +775,7 @@ public class PfttMain {
                                }
                                
                                no_show_gui(show_gui, command);
-                               cmd_aut(cm, rt, rt.host, build, 
ScenarioSet.getScenarioSets());
+                               cmd_aut(cm, rt, rt.host, build, 
config==null?ScenarioSet.getDefaultScenarioSets():config.getScenarioSets());
                        } else if (command.equals("shell_ui")||(show_gui && 
command.equals("shell"))) {
                                cmd_shell_ui();
                        } else if (command.equals("shell")) {
diff --git a/src/com/mostc/pftt/model/phpt/PhpBuild.java 
b/src/com/mostc/pftt/model/phpt/PhpBuild.java
index b972d79..7bd1b3f 100644
--- a/src/com/mostc/pftt/model/phpt/PhpBuild.java
+++ b/src/com/mostc/pftt/model/phpt/PhpBuild.java
@@ -261,11 +261,11 @@ public class PhpBuild extends Build {
        public void setDefaultPhpIni(Host host, PhpIni ini) throws IOException {
                this.php_ini = new WeakReference<PhpIni>(ini);
                
-               host.saveFile(getDefaultPhpIniPath(host), ini.toString());
+               host.saveTextFile(getDefaultPhpIniPath(host), ini.toString());
                
                if (!host.isWindows()) {
-                       host.saveFile("/etc/php/cli/php.ini", ini.toString());
-                       host.saveFile("/etc/php/cgi/php.ini", ini.toString());
+                       host.saveTextFile("/etc/php/cli/php.ini", 
ini.toString());
+                       host.saveTextFile("/etc/php/cgi/php.ini", 
ini.toString());
                }
        }
                
@@ -298,7 +298,7 @@ public class PhpBuild extends Build {
                                
                String php_filename = host.mktempname("Build", ".php");
                
-               host.saveFile(php_filename, code);
+               host.saveTextFile(php_filename, code);
                
                PHPOutput output = new PHPOutput(php_filename, 
host.exec(php_exe+" "+php_filename, timeout_seconds, new 
HashMap<String,String>(), null, Host.dirname(php_filename)));
                if (auto_cleanup && !output.hasFatalError())
diff --git a/src/com/mostc/pftt/model/phpt/PhpIni.java 
b/src/com/mostc/pftt/model/phpt/PhpIni.java
index 634d7cc..44f042f 100644
--- a/src/com/mostc/pftt/model/phpt/PhpIni.java
+++ b/src/com/mostc/pftt/model/phpt/PhpIni.java
@@ -104,9 +104,7 @@ public class PhpIni extends TestCaseGroupKey {
                ini.putMulti(DISABLE_DEFS, StringUtil.EMPTY);
                ini.putMulti(OUTPUT_BUFFERING, OFF);
                ini.putMulti(ERROR_REPORTING, E_ALL_OR_E_STRICT);
-               // IMPORTANT: display_errors=0. doesn't affect test output. 
run-tests.php sets display_errors=1
-               //            but on Windows, that will cause a blocking 
Winpopup message (bad)
-               ini.putMulti(DISPLAY_ERRORS, 0);
+               ini.putMulti(DISPLAY_ERRORS, 1);
                ini.putMulti(DISPLAY_STARTUP_ERRORS, 0);
                ini.putMulti(LOG_ERRORS, 0);
                ini.putMulti(HTML_ERRORS, 0);
diff --git a/src/com/mostc/pftt/model/phpt/PhptSourceTestPack.java 
b/src/com/mostc/pftt/model/phpt/PhptSourceTestPack.java
index 93a32e0..c47aee8 100644
--- a/src/com/mostc/pftt/model/phpt/PhptSourceTestPack.java
+++ b/src/com/mostc/pftt/model/phpt/PhptSourceTestPack.java
@@ -53,18 +53,17 @@ public class PhptSourceTestPack extends SourceTestPack {
                // 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("ext/standard/tests/file/windows_links/mklink_junction");
-               
host.deleteIfExists("ext/standard/tests/file/windows_links/directory");
-               
host.deleteIfExists("ext/standard/tests/file/windows_links/mounted_volume");
-               
host.deleteIfExists("ext/standard/tests/file/windows_links/mnt");
+               
host.deleteIfExists(test_pack+"/ext/standard/tests/file/windows_links/mklink_junction");
+               
host.deleteIfExists(test_pack+"/ext/standard/tests/file/windows_links/directory");
+               
host.deleteIfExists(test_pack+"/ext/standard/tests/file/windows_links/mounted_volume");
+               
host.deleteIfExists(test_pack+"/ext/standard/tests/file/windows_links/mnt");
        }
        
-       public void add_named_tests(List<PhptTestCase> test_files, List<String> 
names, PhptTelemetryWriter twriter, PhpBuild build) throws 
FileNotFoundException, IOException, Exception {
-               add_named_tests(test_files, names, twriter, build, false);
+       public void read(List<PhptTestCase> test_files, List<String> names, 
PhptTelemetryWriter twriter, PhpBuild build) throws FileNotFoundException, 
IOException, Exception {
+               read(test_files, names, twriter, build, false);
        }
        
-       // TODO rename this - always call before (only once) before using 
PhptTestPack
-       public void add_named_tests(List<PhptTestCase> test_files, List<String> 
names, PhptTelemetryWriter twriter, PhpBuild build, boolean ignore_missing) 
throws FileNotFoundException, IOException, Exception {
+       public void read(List<PhptTestCase> test_files, List<String> names, 
PhptTelemetryWriter twriter, PhpBuild build, boolean ignore_missing) throws 
FileNotFoundException, IOException, Exception {
                test_pack_file = new File(test_pack);
                test_pack = test_pack_file.getAbsolutePath(); // normalize path
                
@@ -114,7 +113,7 @@ public class PhptSourceTestPack extends SourceTestPack {
                twriter.setTotalCount(test_files.size());
        }
 
-       public void add_test_files(List<PhptTestCase> test_files, 
PhptTelemetryWriter twriter, PhpBuild build) throws FileNotFoundException, 
IOException, Exception {
+       public void read(List<PhptTestCase> test_files, PhptTelemetryWriter 
twriter, PhpBuild build) throws FileNotFoundException, IOException, Exception {
                test_pack_file = new File(test_pack);
                test_pack = test_pack_file.getAbsolutePath(); // normalize path
                add_test_files(test_pack_file.listFiles(), test_files, null, 
twriter, build, null, new LinkedList<PhptTestCase>());
@@ -140,9 +139,6 @@ public class PhptSourceTestPack extends SourceTestPack {
                                if (test_name.startsWith("/") || 
test_name.startsWith("\\"))
                                        test_name = test_name.substring(1);
                                
-//                             if (test_name.contains("a_dir"))
-//                                     continue; // TODO
-                               
                                PhptTestCase test_case = 
PhptTestCase.load(host, this, false, test_name, twriter, redirect_parent);
                                
                                add_test_case(test_case, test_files, names, 
twriter, build, redirect_parent, redirect_targets);
@@ -166,12 +162,8 @@ public class PhptSourceTestPack extends SourceTestPack {
                                                add_test_files(dir.listFiles(), 
test_files, names, twriter, build, redirect_parent, redirect_targets);
                                                
                                        } else {
-                                               // test refers to a specific 
test, try to load it
-                                               //try {
-                                                       test_case = 
PhptTestCase.load(host, this, false, target_test_name, twriter, 
redirect_parent);
-                                               /*} catch ( Exception ex ) {
-                                                       ex.printStackTrace(); 
// TODO temp
-                                               }*/
+                                               // test refers to a specific 
test, load it
+                                               test_case = 
PhptTestCase.load(host, this, false, target_test_name, twriter, 
redirect_parent);
                                                
                                                if 
(redirect_targets.contains(test_case))
                                                        // can only have 1 
level of redirection
@@ -197,6 +189,10 @@ public class PhptSourceTestPack extends SourceTestPack {
        public String getContents(Host host, String name) throws IOException {
                return host.getContentsDetectCharset(new File(test_pack_file, 
name).getAbsolutePath(), PhptTestCase.newCharsetDeciderDecoder());
        }
+       
+       public PhptActiveTestPack installInPlace() {
+               return new PhptActiveTestPack(this.getSourceDirectory());
+       }
 
        public PhptActiveTestPack install(Host host, String test_pack_dir) 
throws IllegalStateException, IOException, Exception {
                if (!this.host.isRemote() || this.host.equals(host)) {
diff --git a/src/com/mostc/pftt/model/phpt/PhptTestCase.java 
b/src/com/mostc/pftt/model/phpt/PhptTestCase.java
index d50ac6f..c26d93c 100644
--- a/src/com/mostc/pftt/model/phpt/PhptTestCase.java
+++ b/src/com/mostc/pftt/model/phpt/PhptTestCase.java
@@ -537,6 +537,15 @@ public class PhptTestCase extends TestCase {
                return name.equals(o);
        }
        
+       public boolean isNamed(String...names) {
+               // XXX someday, do a Trie structure here to speed up checking
+               for (String name:names) {
+                       if (this.name.equals(name))
+                               return true;
+               }
+               return false;
+       }
+       
        /** returns the name of this test.
         * 
         * for standardization/normalization, all backslashes(\) in test names 
are converted to unix/forwardslashes(/)
@@ -786,64 +795,60 @@ public class PhptTestCase extends TestCase {
         * @return
         */
        public boolean isSlowTest() {
-               // TODO start the slow tests first, so that all tests finish 
faster
-               return isSlowTest(getName());
-       }
-       public static boolean isSlowTest(String test_name) {
-               // TODO
-               return false;
+               return isNamed(
+                               "ext/date/tests/date_diff.phpt",
+                               "ext/oci8/tests/bug42496_1.phpt",
+                               "ext/oci8/tests/bug42496_2.phpt",
+                               "ext/oci8/tests/bug43497.phpt",
+                               "ext/oci8/tests/bug43497_92.phpt",
+                               "ext/oci8/tests/bug44113.phpt",
+                               "ext/oci8/tests/conn_attr_4.phpt",
+                               "ext/oci8/tests/error2.phpt",
+                               "ext/oci8/tests/extauth_01.phpt",
+                               "ext/oci8/tests/extauth_02.phpt",
+                               "ext/oci8/tests/extauth_03.phpt",
+                               "ext/oci8/tests/lob_043.phpt",
+                               "ext/oci8/tests/pecl_bug10194.phpt",
+                               "ext/oci8/tests/pecl_bug10194_blob.phpt",
+                               "ext/oci8/tests/pecl_bug10194_blob_64.phpt",
+                               "ext/phar/tests/bug13727.phpt",
+                               "ext/phar/tests/bug45218_SLOWTEST.phpt",
+                               "ext/phar/tests/bug45218_SLOWTESTU.phpt",
+                               "ext/phar/tests/bug46032.phpt",
+                               "ext/phar/tests/bug46060.phpt",
+                               "ext/standard/tests/file/001.phpt",
+                               "ext/standard/tests/file/005_variation.phpt",
+                               
"ext/standard/tests/file/file_get_contents_error001.phpt",
+                               "ext/standard/tests/file/lstat_stat_basic.phpt",
+                               
"ext/standard/tests/file/lstat_stat_variation10.phpt",
+                               
"ext/standard/tests/file/lstat_stat_variation11.phpt",
+                               
"ext/standard/tests/file/lstat_stat_variation12.phpt",
+                               
"ext/standard/tests/file/lstat_stat_variation13.phpt",
+                               
"ext/standard/tests/file/lstat_stat_variation15.phpt",
+                               
"ext/standard/tests/file/lstat_stat_variation16.phpt",
+                               
"ext/standard/tests/file/lstat_stat_variation17.phpt",
+                               
"ext/standard/tests/file/lstat_stat_variation21.phpt",
+                               
"ext/standard/tests/file/lstat_stat_variation4.phpt",
+                               
"ext/standard/tests/file/lstat_stat_variation5.phpt",
+                               
"ext/standard/tests/file/lstat_stat_variation6.phpt",
+                               
"ext/standard/tests/file/lstat_stat_variation8.phpt",
+                               "ext/standard/tests/file/touch_basic.phpt",
+                               
"ext/standard/tests/general_functions/bug39322.phpt",
+                               
"ext/standard/tests/general_functions/proc_open02.phpt",
+                               
"ext/standard/tests/general_functions/sleep_basic.phpt",
+                               
"ext/standard/tests/general_functions/usleep_basic.phpt",
+                               
"ext/standard/tests/misc/time_nanosleep_basic.phpt",
+                               
"ext/standard/tests/misc/time_sleep_until_basic.phpt",
+                               
"ext/standard/tests/network/gethostbyname_basic001.phpt",
+                               
"ext/standard/tests/network/gethostbyname_error004.phpt",
+                               "ext/standard/tests/network/getmxrr.phpt",
+                               "ext/standard/tests/network/http-stream.phpt",
+                               "tests/func/005a.phpt",
+                               "tests/func/010.phpt",
+                               "tests/lang/045.phpt",
+                               "Zend/tests/bug55509.phpt"
+                       );
        }
-       /* ext/date/tests/date_diff.phpt
-ext/oci8/tests/bug42496_1.phpt
-ext/oci8/tests/bug42496_2.phpt
-ext/oci8/tests/bug43497.phpt
-ext/oci8/tests/bug43497_92.phpt
-ext/oci8/tests/bug44113.phpt
-ext/oci8/tests/conn_attr_4.phpt
-ext/oci8/tests/error2.phpt
-ext/oci8/tests/extauth_01.phpt
-ext/oci8/tests/extauth_02.phpt
-ext/oci8/tests/extauth_03.phpt
-ext/oci8/tests/lob_043.phpt
-ext/oci8/tests/pecl_bug10194.phpt
-ext/oci8/tests/pecl_bug10194_blob.phpt
-ext/oci8/tests/pecl_bug10194_blob_64.phpt
-ext/phar/tests/bug13727.phpt
-ext/phar/tests/bug45218_SLOWTEST.phpt
-ext/phar/tests/bug45218_SLOWTESTU.phpt
-ext/phar/tests/bug46032.phpt
-ext/phar/tests/bug46060.phpt
-ext/standard/tests/file/001.phpt
-ext/standard/tests/file/005_variation.phpt
-ext/standard/tests/file/file_get_contents_error001.phpt
-ext/standard/tests/file/lstat_stat_basic.phpt
-ext/standard/tests/file/lstat_stat_variation10.phpt
-ext/standard/tests/file/lstat_stat_variation11.phpt
-ext/standard/tests/file/lstat_stat_variation12.phpt
-ext/standard/tests/file/lstat_stat_variation13.phpt
-ext/standard/tests/file/lstat_stat_variation15.phpt
-ext/standard/tests/file/lstat_stat_variation16.phpt
-ext/standard/tests/file/lstat_stat_variation17.phpt
-ext/standard/tests/file/lstat_stat_variation21.phpt
-ext/standard/tests/file/lstat_stat_variation4.phpt
-ext/standard/tests/file/lstat_stat_variation5.phpt
-ext/standard/tests/file/lstat_stat_variation6.phpt
-ext/standard/tests/file/lstat_stat_variation8.phpt
-ext/standard/tests/file/touch_basic.phpt
-ext/standard/tests/general_functions/bug39322.phpt
-ext/standard/tests/general_functions/proc_open02.phpt
-ext/standard/tests/general_functions/sleep_basic.phpt
-ext/standard/tests/general_functions/usleep_basic.phpt
-ext/standard/tests/misc/time_nanosleep_basic.phpt
-ext/standard/tests/misc/time_sleep_until_basic.phpt
-ext/standard/tests/network/gethostbyname_basic001.phpt
-ext/standard/tests/network/gethostbyname_error004.phpt
-ext/standard/tests/network/getmxrr.phpt
-ext/standard/tests/network/http-stream.phpt
-tests/func/005a.phpt
-tests/func/010.phpt
-tests/lang/045.phpt
-Zend/tests/bug55509.phpt */
 
        public static DefaultCharsetDeciderDecoder newCharsetDeciderDecoder() {
                return new 
DefaultCharsetDeciderDecoder(CharsetDeciderDecoder.EXPRESS_RECOGNIZERS);
diff --git a/src/com/mostc/pftt/report/AbstractReportGen.groovy 
b/src/com/mostc/pftt/report/AbstractReportGen.groovy
index be0f565..64549a5 100644
--- a/src/com/mostc/pftt/report/AbstractReportGen.groovy
+++ b/src/com/mostc/pftt/report/AbstractReportGen.groovy
@@ -13,7 +13,7 @@ abstract class AbstractReportGen implements Runnable {
                
                System.out.println(html_file);
                System.out.println(html_str);
-               host.saveFile(html_file, html_str);
+               host.saveTextFile(html_file, html_str);
                
                return html_file;
        }
diff --git a/src/com/mostc/pftt/runner/AbstractPhptTestCaseRunner.java 
b/src/com/mostc/pftt/runner/AbstractPhptTestCaseRunner.java
index 3e806c5..1359a64 100644
--- a/src/com/mostc/pftt/runner/AbstractPhptTestCaseRunner.java
+++ b/src/com/mostc/pftt/runner/AbstractPhptTestCaseRunner.java
@@ -55,7 +55,7 @@ public abstract class AbstractPhptTestCaseRunner {
                        
                        return true;
                // TODO openbasedir 
-               } else if 
(test_case.isNamed("tests/security/open_basedir_is_file.phpt")||test_case.isNamed("ext/standard/tests/php_ini_loaded_file.phpt")||test_case.isNamed("tests/run-test/test010.phpt")||test_case.isNamed("ext/standard/tests/misc/time_sleep_until_basic.phpt")
 || test_case.getName().contains("session") || 
test_case.isNamed("ext/standard/tests/misc/time_nanosleep_basic.phpt")) {
+               } else if 
(test_case.isNamed("tests/security/open_basedir_is_file.phpt", 
"ext/standard/tests/php_ini_loaded_file.phpt", "tests/run-test/test010.phpt", 
"ext/standard/tests/misc/time_sleep_until_basic.phpt", 
"ext/standard/tests/misc/time_nanosleep_basic.phpt")) {
                        twriter.addResult(new PhptTestResult(host, 
EPhptTestStatus.XSKIP, test_case, "test sometimes randomly fails, ignore it", 
null, null, null, null, null, null, null, null, null, null));
                        
                        return true;
diff --git a/src/com/mostc/pftt/runner/AbstractPhptTestCaseRunner2.java 
b/src/com/mostc/pftt/runner/AbstractPhptTestCaseRunner2.java
index a4a7b09..f9cfb7d 100644
--- a/src/com/mostc/pftt/runner/AbstractPhptTestCaseRunner2.java
+++ b/src/com/mostc/pftt/runner/AbstractPhptTestCaseRunner2.java
@@ -1,7 +1,6 @@
 package com.mostc.pftt.runner;
 
 import java.io.ByteArrayOutputStream;
-import java.io.File;
 import java.io.IOException;
 import java.nio.charset.Charset;
 import java.util.Map;
@@ -39,20 +38,14 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
        protected final PhptThread thread;
        protected final PhptActiveTestPack active_test_pack;
        protected byte[] stdin_post;
-       // TODO cleanup field names
        protected String skipif_file;
-       protected String cmd;
-       protected String shell_script;
-       protected String ini_settings;
-       protected String shell_file, selected_php_exe; 
-       protected String temp_target;
-       protected String temp_source;
        protected String test_dir;
        protected String base_file_name;
        protected String test_file;
        protected String test_clean;
-       protected String tmp_post, content_type;
-       protected final PhpIni ini;
+       protected String content_type;
+       protected PhpIni ini;
+       protected String tmp_post; // TODO move this to telemetry manager
        
        /** runs the test case and reports the results to the 
PhptTelemetryManager
         * 
@@ -61,13 +54,10 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
         */
        @Override
        public void runTest() throws IOException, Exception, Throwable {
-               // Default ini settings
-//             ini = PhpIni.createDefaultIniCopy(host);
                if (!prepare())
+                       // test is SKIP BORK EXCEPTION etc...
                        return;
                
-               // XXX check if prepare() has borked the test -> don't bother 
running SKIPIF leads to test_skipif=null -> exception
-               
                notifyStart();
                
                try {
@@ -120,19 +110,14 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
                }
                
                test_dir = 
active_test_pack.getDirectory()+host.dirSeparator()+Host.dirname(test_case.getName());
-               
-               /*if (temp_source!=null && temp_target!=null) {
-                       // XXX needed??
-                       test_dir = 
StringUtil.replaceAll(Pattern.compile(temp_source), temp_target, test_dir);
-               }*/
        
-               base_file_name = 
Host.basename(test_case.getName()).replaceAll(".phpt", ""); // TODO clean this 
up 
+               base_file_name = 
Host.basename(Host.removeFileExt(test_case.getName())); 
                
                //
                if (test_case.containsSection(EPhptSection.SKIPIF)) {
                        skipif_file = test_dir + host.dirSeparator() + 
base_file_name + ".skip.php";
                                
-                       host.saveFile(skipif_file, 
test_case.get(EPhptSection.SKIPIF));
+                       host.saveTextFile(skipif_file, 
test_case.get(EPhptSection.SKIPIF));
                } else {
                        // clearly flag that skipif isn't to be executed
                        skipif_file = null;
@@ -144,18 +129,13 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
                test_clean = test_dir + host.dirSeparator() + base_file_name + 
".clean.php";
                tmp_post = test_dir + host.dirSeparator() + base_file_name + 
".post.php";
                
-               /*if (temp_source!=null && temp_target!=null) {
-                       // TODO temp
-                       String copy_file = test_dir + host.dirSeparator() + 
Host.basename(test_case.getName()) + ".phps";
-       
-                       if (!new File(Host.dirname(copy_file)).isDirectory()) {
-                               new File(Host.dirname(copy_file)).mkdirs();
-                       }
-               }*/
-               
-               
                //
-               ini_settings = ini.toCliArgString(host);
+               if (test_case.containsSection(EPhptSection.INI)) {
+                       PhpIni ini = test_case.getINI(active_test_pack, host);
+                       ini.appendAll(this.ini);
+                       this.ini = ini;
+               }
+               //
                
                return true;
        } // end boolean prepare
@@ -215,13 +195,9 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
                        // open external file and copy to test_file (binary, no 
char conversion - which could break it - often this is a PHAR file - which will 
be broken if charset coversion is done)
                        
host.copy(src_test_pack.getSourceDirectory()+host.dirSeparator()+Host.dirname(test_case.getName())
 + "/" + test_case.get(EPhptSection.FILE_EXTERNAL), test_file);
                } else {
-                       host.saveFile(test_file, 
test_case.get(EPhptSection.FILE), test_case.getCommonCharset());
+                       host.saveTextFile(test_file, 
test_case.get(EPhptSection.FILE), test_case.getCommonCharset());
                }
-       
-               // copy CLI args to pass
-               String args = test_case.containsSection(EPhptSection.ARGS) ? " 
-- " + test_case.get(EPhptSection.ARGS) : "";
-                               
-       
+               
                // copy STDIN to pass (POST, POST_RAW, STDIN, etc...)
                if (test_case.containsSection(EPhptSection.POST_RAW)) { 
                        String post = test_case.getTrim(EPhptSection.POST_RAW);
@@ -251,10 +227,10 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
                                                request_sb.append(line);
                                                request_sb.append('\n');        
                                        }
-                               } else {
+                               } // TODO else {
                                        request_sb.append(line);
                                        request_sb.append('\n');
-                               }
+                               //}
                        }
                        
                        String request = request_sb.toString();
@@ -271,12 +247,10 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
                                
                                return;
                        }
-                       host.saveFile(tmp_post, request);
+                       host.saveTextFile(tmp_post, request);
                        
                        stdin_post = request.getBytes();
                        
-                       cmd = selected_php_exe+" "+ini_settings+" -f 
\""+test_file+"\"";
-                       
                } else if (test_case.containsSection(EPhptSection.PUT)) {
                        String post = test_case.getTrim(EPhptSection.PUT);
                        String[] raw_lines = StringUtil.splitLines(post);
@@ -311,7 +285,6 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
                        }
        
                        stdin_post = request.getBytes();
-                       cmd = selected_php_exe+" "+ini_settings+" -f 
\""+test_file+"\"";
                        
                } else if (test_case.containsSection(EPhptSection.POST)) {
        
@@ -319,7 +292,7 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
        
                        if (test_case.containsSection(EPhptSection.GZIP_POST)) {
                                // php's gzencode() => gzip compression => 
java's GZIPOutputStream 
-                               //post = gzencode(post, 9, FORCE_GZIP);
+                               // post = gzencode(post, 9, FORCE_GZIP);
                                ByteArrayOutputStream baos = new 
ByteArrayOutputStream();
                                GZIPOutputStreamLevel d = new 
GZIPOutputStreamLevel(baos, 9);
                                d.write(post.getBytes());
@@ -328,7 +301,7 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
                                setContentEncoding("gzip");
                        } else if 
(test_case.containsSection(EPhptSection.DEFLATE_POST)) {
                                // php's gzcompress() => zlib compression => 
java's DeflaterOutputStream
-                               //post = gzcompress(post, 9);
+                               // post = gzcompress(post, 9);
                                ByteArrayOutputStream baos = new 
ByteArrayOutputStream();
                                DeflaterOutputStream d = new 
DeflaterOutputStream(baos, new Deflater(9));
                                d.write(post.getBytes());
@@ -346,19 +319,13 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
                                
setContentType("application/x-www-form-urlencoded");
                        // critical: php-cgi won"t read more bytes than this 
(thus some input can go missing)
                        setContentLength(content_length);
-       
-                       cmd = selected_php_exe+" "+ini_settings+" -f 
\""+test_file+"\"";
-       
-               } else {
                        
+               } else {
                        setRequestMethod("GET");
                        if (!hasContentType())
                                setContentType(StringUtil.EMPTY);
                        setContentLength(0);
-       
-                       cmd = selected_php_exe+" "+ini_settings+" -f 
\""+test_file+"\" "+args;
                }
-       
        } // end void prepareTest
        
        protected void setContentEncoding(String encoding) {
@@ -434,9 +401,9 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
                                twriter.show_exception(test_case, ex, expected);
                                throw ex;
                        }
-                       if (expected_re_match||check()) {
+                       if (expected_re_match) {
 
-                               twriter.addResult(new PhptTestResult(host, 
test_case.isXFail()?EPhptTestStatus.XFAIL:EPhptTestStatus.PASS, test_case, 
output, null, null, charset, getEnv(), splitCmdString(), stdin_post, 
shell_script, null, null, null));
+                               twriter.addResult(new PhptTestResult(host, 
test_case.isXFail()?EPhptTestStatus.XFAIL:EPhptTestStatus.PASS, test_case, 
output, null, null, charset, getEnv(), splitCmdString(), stdin_post, 
getShellScript(), null, null, null));
                                                
                                return;
                        } 
@@ -446,9 +413,9 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
                        output = remove_header_from_output(output);
                        output_trim = output.trim();
        
-                       if 
(output_trim.equals(expected)||output_trim.contains(expected)||expected.contains(output_trim)||check())
 {
+                       if 
(output_trim.equals(expected)||output_trim.contains(expected)||expected.contains(output_trim))
 {
                                
-                               twriter.addResult(new PhptTestResult(host, 
test_case.isXFail()?EPhptTestStatus.XFAIL:EPhptTestStatus.PASS, test_case, 
output, null, null, charset, getEnv(), splitCmdString(), stdin_post, 
shell_script, null, null, null));
+                               twriter.addResult(new PhptTestResult(host, 
test_case.isXFail()?EPhptTestStatus.XFAIL:EPhptTestStatus.PASS, test_case, 
output, null, null, charset, getEnv(), splitCmdString(), stdin_post, 
getShellScript(), null, null, null));
                                                
                                return;
                        }
@@ -462,9 +429,9 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
                                // fall through to failing or xfailing the test
                        } else {
                                // compare again
-                               if 
(output_trim.equals(expected)||output_trim.contains(expected)||expected.contains(output_trim)||(output_trim.length()>20&&expected.length()>20&&output_trim.substring(10,
 20).equals(expected.substring(10, 20)))||check()) {
+                               if 
(output_trim.equals(expected)||output_trim.contains(expected)||expected.contains(output_trim)||(output_trim.length()>20&&expected.length()>20&&output_trim.substring(10,
 20).equals(expected.substring(10, 20)))) {
                                        
-                                       twriter.addResult(new 
PhptTestResult(host, 
test_case.isXFail()?EPhptTestStatus.XFAIL:EPhptTestStatus.PASS, test_case, 
output, null, null, charset, getEnv(), splitCmdString(), stdin_post, 
shell_script, null, null, null));
+                                       twriter.addResult(new 
PhptTestResult(host, 
test_case.isXFail()?EPhptTestStatus.XFAIL:EPhptTestStatus.PASS, test_case, 
output, null, null, charset, getEnv(), splitCmdString(), stdin_post, 
getShellScript(), null, null, null));
                                        
                                        return;
                                } // end if
@@ -473,8 +440,8 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
                        output = remove_header_from_output(output);
                        output_trim = output.trim();
                        
-                       if (StringUtil.isEmpty(output_trim)||check()) {
-                               twriter.addResult(new PhptTestResult(host, 
test_case.isXFail()?EPhptTestStatus.XFAIL:EPhptTestStatus.PASS, test_case, 
output, null, null, charset, getEnv(), splitCmdString(), stdin_post, 
shell_script, null, null, null));
+                       if (StringUtil.isEmpty(output_trim)) {
+                               twriter.addResult(new PhptTestResult(host, 
test_case.isXFail()?EPhptTestStatus.XFAIL:EPhptTestStatus.PASS, test_case, 
output, null, null, charset, getEnv(), splitCmdString(), stdin_post, 
getShellScript(), null, null, null));
                                
                                return;
                        }
@@ -483,7 +450,7 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
                // if here, test failed!
 
                if (test_case.isXFail()) {
-                       twriter.addResult(new PhptTestResult(host, 
EPhptTestStatus.XFAIL, test_case, output, null, null, charset, getEnv(), 
splitCmdString(), stdin_post, shell_script, null, null, preoverride_actual));
+                       twriter.addResult(new PhptTestResult(host, 
EPhptTestStatus.XFAIL, test_case, output, null, null, charset, getEnv(), 
splitCmdString(), stdin_post, getShellScript(), null, null, 
preoverride_actual));
                } else {
                        // test is FAIL
                        
@@ -500,18 +467,18 @@ public abstract class AbstractPhptTestCaseRunner2 extends 
AbstractPhptTestCaseRu
                                expectf = null;
                        }
                        
-                       twriter.addResult(new PhptTestResult(host, 
EPhptTestStatus.FAIL, test_case, output, actual_lines, expected_lines, charset, 
getEnv(), splitCmdString(), stdin_post, shell_script, diff, expectf, 
preoverride_actual, getCrashedSAPIOutput()));
+                       twriter.addResult(new PhptTestResult(host, 
EPhptTestStatus.FAIL, test_case, output, actual_lines, expected_lines, charset, 
getEnv(), splitCmdString(), stdin_post, getShellScript(), diff, expectf, 
preoverride_actual, getCrashedSAPIOutput()));
                }
        } // end void evalTest
        
        protected Map<String, String> getEnv() {
                return null;
        }
-       protected boolean check() {
-               // TODO temp
-               return StringUtil.isEmpty(getCrashedSAPIOutput());
+       
+       protected String getShellScript() {
+               return null;
        }
-
+       
        protected abstract String[] splitCmdString();
        
        protected static String remove_header_from_output(String output) {
diff --git a/src/com/mostc/pftt/runner/CliPhptTestCaseRunner.java 
b/src/com/mostc/pftt/runner/CliPhptTestCaseRunner.java
index 9b8991b..241652e 100644
--- a/src/com/mostc/pftt/runner/CliPhptTestCaseRunner.java
+++ b/src/com/mostc/pftt/runner/CliPhptTestCaseRunner.java
@@ -37,7 +37,7 @@ import com.mostc.pftt.util.StringUtil;
 public class CliPhptTestCaseRunner extends AbstractPhptTestCaseRunner2 {
        protected ExecOutput output;
        protected HashMap<String,String> env;
-       protected String skip_cmd;
+       protected String selected_php_exe, shell_script, test_cmd, skip_cmd, 
ini_settings, shell_file;
        
        public CliPhptTestCaseRunner(PhpIni ini, PhptThread thread, 
PhptTestCase test_case, PhptTelemetryWriter twriter, Host host, ScenarioSet 
scenario_set, PhpBuild build, PhptSourceTestPack src_test_pack, 
PhptActiveTestPack active_test_pack) {
                super(ini, thread, test_case, twriter, host, scenario_set, 
build, src_test_pack, active_test_pack);
@@ -48,6 +48,8 @@ public class CliPhptTestCaseRunner extends 
AbstractPhptTestCaseRunner2 {
        @Override
        protected boolean prepare() throws IOException, Exception {
                if (super.prepare()) {
+                       ini_settings = ini.toCliArgString(host);
+                       
                        // read ENV vars from test, from its parent (if a test 
redirected to this test), and merge from scenario
                        env = test_case.getENV(env, 
twriter.getConsoleManager(), host, build);
                        
@@ -77,6 +79,12 @@ public class CliPhptTestCaseRunner extends 
AbstractPhptTestCaseRunner2 {
        protected void prepareTest() throws Exception {
                super.prepareTest();
                
+               // copy CLI args to pass
+               String args = test_case.containsSection(EPhptSection.ARGS) ? " 
-- " + test_case.get(EPhptSection.ARGS) : "";
+               
+               // generate cmd string to run test_file with php.exe
+               test_cmd = selected_php_exe+" "+ini_settings+" -f 
\""+test_file+"\""+(args==null?"":" "+args);
+               
                if (test_case.containsSection(EPhptSection.STDIN)) {
                        if (host.isWindows()) {
                                // @see Zend/tests/multibyte*
@@ -145,11 +153,6 @@ public class CliPhptTestCaseRunner extends 
AbstractPhptTestCaseRunner2 {
        }
        
        @Override
-       protected Map<String, String> getEnv() {
-               return this.env;
-       }
-       
-       @Override
        protected String executeSkipIf() throws Exception {
                // Check if test should be skipped.
                if (skipif_file != null) {
@@ -182,7 +185,7 @@ public class CliPhptTestCaseRunner extends 
AbstractPhptTestCaseRunner2 {
        @Override
        protected void executeClean() throws Exception {
                if (test_case.containsSection(EPhptSection.CLEAN)) {
-                       host.saveFile(test_clean, 
test_case.getTrim(EPhptSection.CLEAN), null);
+                       host.saveTextFile(test_clean, 
test_case.getTrim(EPhptSection.CLEAN), null);
                
                        env.remove(ENV_REQUEST_METHOD);
                        env.remove(ENV_QUERY_STRING);
@@ -245,7 +248,7 @@ public class CliPhptTestCaseRunner extends 
AbstractPhptTestCaseRunner2 {
                        } else
                                fw.println("export "+name+"=\""+value+"\"");
                }
-               fw.println(cmd);
+               fw.println(test_cmd);
                fw.close();
                shell_script = sw.toString();
                FileWriter w = new FileWriter(shell_file);
@@ -279,7 +282,17 @@ public class CliPhptTestCaseRunner extends 
AbstractPhptTestCaseRunner2 {
 
        @Override
        protected String[] splitCmdString() {
-               return LocalHost.splitCmdString(cmd);
+               return LocalHost.splitCmdString(test_cmd);
+       }
+       
+       @Override
+       protected Map<String, String> getEnv() {
+               return env;
+       }
+       
+       @Override
+       protected String getShellScript() {
+               return shell_script;
        }
        
 } // end public class CliTestCaseRunner
diff --git a/src/com/mostc/pftt/runner/HttpTestCaseRunner.java 
b/src/com/mostc/pftt/runner/HttpTestCaseRunner.java
index 7d6aa3e..2d08c9d 100644
--- a/src/com/mostc/pftt/runner/HttpTestCaseRunner.java
+++ b/src/com/mostc/pftt/runner/HttpTestCaseRunner.java
@@ -72,6 +72,8 @@ public class HttpTestCaseRunner extends 
AbstractPhptTestCaseRunner2 {
                        twriter.addResult(new PhptTestResult(host, 
EPhptTestStatus.XSKIP, test_case, "ENV section not supported for testing 
against web servers", null, null, null, null, null, null, null, null, null, 
null));
                else if (test_case.containsSection(EPhptSection.STDIN))
                        twriter.addResult(new PhptTestResult(host, 
EPhptTestStatus.XSKIP, test_case, "STDIN section not supported for testing 
against web servers", null, null, null, null, null, null, null, null, null, 
null));
+               else if (test_case.containsSection(EPhptSection.ARGS))
+                       twriter.addResult(new PhptTestResult(host, 
EPhptTestStatus.XSKIP, test_case, "ARGS section not supported for testing 
against web servers", null, null, null, null, null, null, null, null, null, 
null));
                
                return false;
        }
diff --git a/src/com/mostc/pftt/runner/PhptTestPackRunner.java 
b/src/com/mostc/pftt/runner/PhptTestPackRunner.java
index f8aea39..8d73ace 100644
--- a/src/com/mostc/pftt/runner/PhptTestPackRunner.java
+++ b/src/com/mostc/pftt/runner/PhptTestPackRunner.java
@@ -6,6 +6,7 @@ import java.util.List;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
 
 import com.mostc.pftt.host.Host;
 import com.mostc.pftt.model.phpt.PhpBuild;
@@ -34,24 +35,32 @@ public class PhptTestPackRunner extends 
AbstractTestPackRunner {
        protected final PhptSourceTestPack src_test_pack;
        protected final PhptTelemetryWriter twriter;
        protected PhptActiveTestPack active_test_pack;
-       protected ETestPackRunnerState runner_state;
+       protected AtomicReference<ETestPackRunnerState> runner_state;
        protected AtomicInteger test_count, active_thread_count;
        protected HashMap<TestCaseGroupKey,LinkedBlockingQueue<PhptTestCase>> 
thread_safe_tests;
        protected 
HashMap<String,HashMap<TestCaseGroupKey,LinkedBlockingQueue<PhptTestCase>>> 
non_thread_safe_tests;
        protected AbstractSAPIScenario sapi_scenario;
+       protected AbstractFileSystemScenario file_scenario;
        protected LinkedBlockingQueue<TestCaseGroupKey> group_keys;
        
        public PhptTestPackRunner(PhptTelemetryWriter twriter, 
PhptSourceTestPack test_pack, ScenarioSet scenario_set, PhpBuild build, Host 
host) {
                super(scenario_set, build, host);
                this.twriter = twriter;
                this.src_test_pack = test_pack;
+               
+               runner_state = new AtomicReference<ETestPackRunnerState>();
        }       
        
        public void runTestList(List<PhptTestCase> test_cases) throws Exception 
{
-               // XXX if already running, wait
-               runner_state = ETestPackRunnerState.RUNNING;
+               // if already running, wait
+               while (runner_state.get()==ETestPackRunnerState.RUNNING) {
+                       Thread.sleep(100);
+               }
+               //
+               
+               runner_state.set(ETestPackRunnerState.RUNNING);
                sapi_scenario = ScenarioSet.getSAPIScenario(scenario_set);
-               AbstractFileSystemScenario file_scenario = 
ScenarioSet.getFileSystemScenario(scenario_set);
+               file_scenario = ScenarioSet.getFileSystemScenario(scenario_set);
                
                ////////////////// install test-pack onto the storage it will 
be run from
                // for local file system, this is just a file copy. for other 
scenarios, its more complicated (let the filesystem scenario deal with it)
@@ -89,10 +98,10 @@ public class PhptTestPackRunner extends 
AbstractTestPackRunner {
                active_test_pack = null;
                try {
                        // TODO console option (local filesystem scenario only) 
to use PhptSourceTestPack as PhptActiveTestPack
-                       //      -phpt-in-place
-                       //      if not -auto and local file system, do in-place 
by default
-                       //      if -auto, don't do in-place 
-                       active_test_pack = src_test_pack.install(host, 
test_pack_dir);
+                       if (twriter.getConsoleManager().isPhptNotInPlace() || 
!file_scenario.allowPhptInPlace())
+                               active_test_pack = src_test_pack.install(host, 
test_pack_dir);
+                       else
+                               active_test_pack = 
src_test_pack.installInPlace();
                } catch (Exception ex ) {
                        twriter.getConsoleManager().printStackTrace(ex);
                }
@@ -121,20 +130,23 @@ public class PhptTestPackRunner extends 
AbstractTestPackRunner {
                        // TODO serialSAPIInstance_executeTestCases();
                        parallelSAPIInstance_executeTestCases();
        
-                       // delete if successful (otherwise leave it behind for 
user to analyze the internal exception(s))
-                       // TODO console option to not cleanup
-                       
twriter.getConsoleManager().println("PhptTestPackRunner", "deleting up active 
test-pack: "+active_test_pack);
-                       host.delete(active_test_pack.getDirectory());
+                       // TODO delete if successful (otherwise leave it behind 
for user to analyze the internal exception(s))
+                       if 
(!twriter.getConsoleManager().isDontCleanupTestPack() && 
!active_test_pack.getDirectory().equals(src_test_pack.getSourceDirectory())) {
+                               
twriter.getConsoleManager().println("PhptTestPackRunner", "deleting/cleaning-up 
active test-pack: "+active_test_pack);
+                               host.delete(active_test_pack.getDirectory());
+                               
+                               // cleanup, disconnect storage, etc...
+                               
file_scenario.notifyFinishedTestPack(twriter.getConsoleManager(), host);
+                       }
+                       //
                } finally {
                        // be sure all running WebServerInstances, or other 
SAPIInstances are
-                       // closed by end of testing (otherwise php.exe -S will 
keep on running)
+                       // closed by end of testing (otherwise `php.exe -S` 
will keep on running)
                        close();
                }
        } // end public void runTestList
        
        public void close() {
-               if (sapi_scenario instanceof AbstractWebServerScenario) // TODO 
temp
-                       ((AbstractWebServerScenario)sapi_scenario).smgr.close();
                sapi_scenario.close();
        }
        
@@ -360,7 +372,7 @@ public class PhptTestPackRunner extends 
AbstractTestPackRunner {
                                        test_case = jobs.poll() 
                                        ) != null && 
                                        run_thread.get() && 
-                                       
runner_state==ETestPackRunnerState.RUNNING
+                                       
runner_state.get()==ETestPackRunnerState.RUNNING
                                        ) { 
                                // CRITICAL: catch exception so thread will 
always end normally
                                try {
@@ -378,8 +390,6 @@ public class PhptTestPackRunner extends 
AbstractTestPackRunner {
                                counter++;
                                
                                test_count.incrementAndGet();
-                               
-                               //Thread.yield()
                        }
                } // end protected void exec_jobs
 
@@ -405,12 +415,12 @@ public class PhptTestPackRunner extends 
AbstractTestPackRunner {
 
        @Override
        public void setState(ETestPackRunnerState state) throws 
IllegalStateException {
-               this.runner_state = state;
+               this.runner_state.set(state);
        }
 
        @Override
        public ETestPackRunnerState getState() {
-               return runner_state;
+               return runner_state.get();
        }
        
 } // end public class PhptTestPackRunner
diff --git a/src/com/mostc/pftt/scenario/AbstractCodeCacheScenario.java 
b/src/com/mostc/pftt/scenario/AbstractCodeCacheScenario.java
index 69aaef9..3dd3782 100644
--- a/src/com/mostc/pftt/scenario/AbstractCodeCacheScenario.java
+++ b/src/com/mostc/pftt/scenario/AbstractCodeCacheScenario.java
@@ -1,5 +1,8 @@
 package com.mostc.pftt.scenario;
 
 public abstract class AbstractCodeCacheScenario extends AbstractSerialScenario 
{
-       
+       @Override
+       public Class<?> getSerialKey() {
+               return AbstractCodeCacheScenario.class;
+       }
 }
diff --git a/src/com/mostc/pftt/scenario/AbstractFileSystemScenario.java 
b/src/com/mostc/pftt/scenario/AbstractFileSystemScenario.java
index 58bf174..ae9bf92 100644
--- a/src/com/mostc/pftt/scenario/AbstractFileSystemScenario.java
+++ b/src/com/mostc/pftt/scenario/AbstractFileSystemScenario.java
@@ -4,11 +4,23 @@ import com.mostc.pftt.host.Host;
 import com.mostc.pftt.telemetry.ConsoleManager;
 
 public abstract class AbstractFileSystemScenario extends 
AbstractSerialScenario {
-
+       @Override
+       public Class<?> getSerialKey() {
+               return AbstractFileSystemScenario.class;
+       }
        public abstract boolean notifyPrepareStorageDir(ConsoleManager cm, Host 
host);
        public abstract String getTestPackStorageDir(Host host);
        public boolean notifyTestPackInstalled(ConsoleManager cm, Host host) {
                return true; // proceed with using test-pack
        }
        
+       /** checks if -phpt-in-place console option can be ignored or not
+        * 
+        * @return FALSE - ignore -phpt-in-place, TRUE to follow it (if present)
+        */
+       public abstract boolean allowPhptInPlace();
+       public void notifyFinishedTestPack(ConsoleManager consoleManager, Host 
host) {
+               
+       }
+       
 }
diff --git a/src/com/mostc/pftt/scenario/AbstractParallelScenario.java 
b/src/com/mostc/pftt/scenario/AbstractParallelScenario.java
index eefa4ab..7e72338 100644
--- a/src/com/mostc/pftt/scenario/AbstractParallelScenario.java
+++ b/src/com/mostc/pftt/scenario/AbstractParallelScenario.java
@@ -7,8 +7,5 @@ package com.mostc.pftt.scenario;
  */
 
 public abstract class AbstractParallelScenario extends Scenario {
-       @Override
-       public boolean rejectOther(Scenario o) {
-               return o instanceof AbstractParallelScenario && 
!o.getClass().isInstance(this); 
-       }
+       
 }
diff --git a/src/com/mostc/pftt/scenario/AbstractSAPIScenario.java 
b/src/com/mostc/pftt/scenario/AbstractSAPIScenario.java
index 5d257f6..c8dc38d 100644
--- a/src/com/mostc/pftt/scenario/AbstractSAPIScenario.java
+++ b/src/com/mostc/pftt/scenario/AbstractSAPIScenario.java
@@ -24,6 +24,11 @@ import com.mostc.pftt.telemetry.PhptTelemetryWriter;
 
 public abstract class AbstractSAPIScenario extends AbstractSerialScenario {
 
+       @Override
+       public Class<?> getSerialKey() {
+               return AbstractSAPIScenario.class;
+       }
+       
        /** creates a runner to run a single PhptTestCase under this SAPI 
scenario
         * 
         * @param thread
diff --git a/src/com/mostc/pftt/scenario/AbstractSMBScenario.java 
b/src/com/mostc/pftt/scenario/AbstractSMBScenario.java
index 1936eea..898fd6e 100644
--- a/src/com/mostc/pftt/scenario/AbstractSMBScenario.java
+++ b/src/com/mostc/pftt/scenario/AbstractSMBScenario.java
@@ -39,6 +39,13 @@ public abstract class AbstractSMBScenario extends 
AbstractFileSystemScenario {
                this.base_share_name = base_share_name;
        }
        
+       @Override
+       public boolean allowPhptInPlace() {
+               // always make sure test-pack is installed onto SMB Share
+               // otherwise, there wouldn't be a point in testing on SMB
+               return false;
+       }
+       
        /** creates a File Share and connects to it.
         * 
         * a test-pack can then be installed on that File Share.
@@ -101,7 +108,7 @@ public abstract class AbstractSMBScenario extends 
AbstractFileSystemScenario {
                                return false;
                        }
                } else {
-                       // XXX
+                       // host is local, try using a local drive, normal file 
system operations, not SMB, etc...
                        local_drive = file_path;
                        
                        return true;
@@ -133,4 +140,34 @@ public abstract class AbstractSMBScenario extends 
AbstractFileSystemScenario {
                return local_drive; // H: I: J: ... Y:
        }
        
+       public boolean deleteShare(ConsoleManager cm, Host host) {
+               try {
+                       if (host.execElevated("NET SHARE "+file_path+" 
/DELETE", Host.ONE_MINUTE).isSuccess()) {
+                               host.delete(file_path);
+                       
+                               return true;
+                       }
+               } catch ( Exception ex ) {
+                       cm.printStackTrace(ex);
+               }
+               return false;
+       }
+       
+       public boolean disconnect(ConsoleManager cm, Host host) {
+               try {
+                       return host.exec("NET USE "+local_drive+" /DELETE", 
Host.ONE_MINUTE).isSuccess();
+               } catch ( Exception ex ) {
+                       cm.printStackTrace(ex);
+               }
+               return false;
+       }
+       
+       @Override
+       public void notifyFinishedTestPack(ConsoleManager cm, Host host) {
+               if (deleteShare(cm, host) && disconnect(cm, host)) {
+                       // reset
+                       share_name = file_path = unc_path = smb_path = 
local_drive = null;
+               }
+       }
+       
 } // end public abstract class AbstractSMBScenario
diff --git a/src/com/mostc/pftt/scenario/AbstractSerialScenario.java 
b/src/com/mostc/pftt/scenario/AbstractSerialScenario.java
index 68b73b3..da8358a 100644
--- a/src/com/mostc/pftt/scenario/AbstractSerialScenario.java
+++ b/src/com/mostc/pftt/scenario/AbstractSerialScenario.java
@@ -9,8 +9,7 @@ package com.mostc.pftt.scenario;
  */
 
 public abstract class AbstractSerialScenario extends Scenario {
+       
        @Override
-       public boolean rejectOther(Scenario o) {
-               return false;
-       }
+       public abstract Class<?> getSerialKey();
 }
diff --git a/src/com/mostc/pftt/scenario/AbstractSocketScenario.java 
b/src/com/mostc/pftt/scenario/AbstractSocketScenario.java
index 4c0cdc0..5def2a0 100644
--- a/src/com/mostc/pftt/scenario/AbstractSocketScenario.java
+++ b/src/com/mostc/pftt/scenario/AbstractSocketScenario.java
@@ -1,5 +1,8 @@
 package com.mostc.pftt.scenario;
 
 public abstract class AbstractSocketScenario extends AbstractOptionScenario {
-
+       @Override
+       public Class<?> getSerialKey() {
+               return AbstractSocketScenario.class;
+       }
 }
diff --git a/src/com/mostc/pftt/scenario/CLIScenario.java 
b/src/com/mostc/pftt/scenario/CLIScenario.java
index ce2b5db..63fa894 100644
--- a/src/com/mostc/pftt/scenario/CLIScenario.java
+++ b/src/com/mostc/pftt/scenario/CLIScenario.java
@@ -40,8 +40,7 @@ public class CliScenario extends AbstractSAPIScenario {
        
        @Override
        public int getTestThreadCount(Host host) {
-               // 4 => 395
-               return 2 * host.getCPUCount();
+               return 4 * host.getCPUCount();
        }
 
 } // end public class CliScenario
diff --git a/src/com/mostc/pftt/scenario/LocalFileSystemScenario.java 
b/src/com/mostc/pftt/scenario/LocalFileSystemScenario.java
index a555970..06c1c69 100644
--- a/src/com/mostc/pftt/scenario/LocalFileSystemScenario.java
+++ b/src/com/mostc/pftt/scenario/LocalFileSystemScenario.java
@@ -20,6 +20,11 @@ public class LocalFileSystemScenario extends 
AbstractFileSystemScenario {
        public boolean isImplemented() {
                return true;
        }
+       
+       @Override
+       public boolean allowPhptInPlace() {
+               return false;
+       }
 
        @Override
        public boolean notifyPrepareStorageDir(ConsoleManager cm, Host host) {
diff --git a/src/com/mostc/pftt/scenario/SMBCSCOptionScenario.java 
b/src/com/mostc/pftt/scenario/SMBCSCOptionScenario.java
index 969c1dd..887289d 100644
--- a/src/com/mostc/pftt/scenario/SMBCSCOptionScenario.java
+++ b/src/com/mostc/pftt/scenario/SMBCSCOptionScenario.java
@@ -5,6 +5,11 @@ import com.mostc.pftt.telemetry.ConsoleManager;
 
 public abstract class SMBCSCOptionScenario extends AbstractOptionScenario {
 
+       @Override
+       public Class<?> getSerialKey() {
+               return SMBCSCOptionScenario.class;
+       }
+       
        public abstract boolean isEnable();
        
        @Override
@@ -17,7 +22,7 @@ public abstract class SMBCSCOptionScenario extends 
AbstractOptionScenario {
                
                String ps_file = host.mktempname(getName(), "ps1");
                try {
-                       host.saveFile(ps_file, ps_sb.toString());
+                       host.saveTextFile(ps_file, ps_sb.toString());
                        
                        if (host.exec("powershell -File "+ps_file, 
Host.ONE_MINUTE).isSuccess()) {
                                host.delete(ps_file);
diff --git a/src/com/mostc/pftt/scenario/SMBDFSRScenario.java 
b/src/com/mostc/pftt/scenario/SMBDFSRScenario.java
index 737571e..24e6f46 100644
--- a/src/com/mostc/pftt/scenario/SMBDFSRScenario.java
+++ b/src/com/mostc/pftt/scenario/SMBDFSRScenario.java
@@ -38,7 +38,7 @@ public class SMBDFSRScenario extends AbstractSMBScenario {
                ps_sb.append("Add-WindowsFeature -name FS-DFS-Replication\n");
                
                String tmp_file = host.mktempname(getName(), "ps1");
-               host.saveFile(tmp_file, ps_sb.toString());
+               host.saveTextFile(tmp_file, ps_sb.toString());
                                
                if (host.exec("Powershell -File "+tmp_file, 
Host.NO_TIMEOUT).isSuccess()) {
                        host.delete(tmp_file);
diff --git a/src/com/mostc/pftt/scenario/SMBDeduplicationScenario.java 
b/src/com/mostc/pftt/scenario/SMBDeduplicationScenario.java
index 1d57030..8346af2 100644
--- a/src/com/mostc/pftt/scenario/SMBDeduplicationScenario.java
+++ b/src/com/mostc/pftt/scenario/SMBDeduplicationScenario.java
@@ -56,7 +56,7 @@ public class SMBDeduplicationScenario extends 
AbstractSMBScenario {
                if (!remote_host.isWin8OrLater()) {
                        cm.println(getName(), "Scenario can only be run against 
a Windows 8/2012+ host");
                        return false;
-               } else if 
(remote_host.getSystemDrive().equalsIgnoreCase(volume)) {
+               } else if 
(volume.equals("C:")||remote_host.getSystemDrive().equalsIgnoreCase(volume)) {
                        cm.println(getName(), "Can not use Deduplication on a 
Windows System Drive (ex: C:\\)");
                        return false;
                }
@@ -76,10 +76,10 @@ public class SMBDeduplicationScenario extends 
AbstractSMBScenario {
                
                // create PowerShell script to install and enable deduplication
                try {
-                       remote_host.saveFile(tmp_file, ps_sb.toString());
+                       remote_host.saveTextFile(tmp_file, ps_sb.toString());
                        
                        // 
-                       if (remote_host.exec("powershell -File "+tmp_file, 
Host.ONE_MINUTE * 10).isSuccess()) {
+                       if (remote_host.execElevated("powershell -File 
"+tmp_file, Host.ONE_MINUTE * 10).isSuccess()) {
                                // don't delete tmp_file if it failed to help 
user see why
                                remote_host.delete(tmp_file);
                        }
@@ -102,12 +102,15 @@ public class SMBDeduplicationScenario extends 
AbstractSMBScenario {
        public boolean notifyTestPackInstalled(ConsoleManager cm, Host host) {
                try {
                        // run deduplication job (on test-pack) -wait for 
completion
-                       remote_host.exec("powershell -Command {Start-Dedupjob 
-Volume "+volume+" -Type Optimization -Wait}", Host.NO_TIMEOUT);
-                       
-                       return true;
+                       cm.println(getName(), "Running deduplication job...");
+                       if (remote_host.exec("powershell -Command 
{Start-Dedupjob -Volume "+volume+" -Type Optimization -Wait}", 
Host.NO_TIMEOUT).isSuccess()) {
+                               cm.println(getName(), "Deduplication completed 
successfully.");
+                               return true;
+                       }
                } catch ( Exception ex ) {
                        cm.printStackTrace(ex);
                }
+               cm.println(getName(), "Deduplication failed");
                return false;
        }
        
diff --git a/src/com/mostc/pftt/scenario/Scenario.java 
b/src/com/mostc/pftt/scenario/Scenario.java
index 92c610e..47e30de 100644
--- a/src/com/mostc/pftt/scenario/Scenario.java
+++ b/src/com/mostc/pftt/scenario/Scenario.java
@@ -19,7 +19,10 @@ import com.mostc.pftt.telemetry.ConsoleManager;
 
 public abstract class Scenario {
        
-       public abstract boolean rejectOther(Scenario o);
+       public Class<?> getSerialKey() {
+               return getClass();
+       }
+       
        public abstract String getName();
        public abstract boolean isImplemented();
        
@@ -38,59 +41,29 @@ public abstract class Scenario {
        public static final AbstractFileSystemScenario 
DEFAULT_FILESYSTEM_SCENARIO = LOCALFILESYSTEM_SCENARIO;
        
        // 90 ScenarioSets => (APC, WinCache, No) * (CLI, Buitlin-WWW, Apache, 
IIS-Standard, IIS-Express) * ( local filesystem, the 5 types of SMB )
-       public static Scenario[][] getAllScenarios() {
-               return new Scenario[][] {
+       public static Scenario[] getAllDefaultScenarios() {
+               return new Scenario[]{
                                // sockets
-                               new Scenario[] {
                                new PlainSocketScenario(),
-                               new SSLSocketScenario() 
-                               },
+                               new SSLSocketScenario(),
                                // code caches
-                               new Scenario[] {
                                new NoCodeCacheScenario(),
                                new APCScenario(),
-                               new WinCacheScenario()
-                               },
+                               new WinCacheScenario(),
                                // SAPIs
-                               new Scenario[]{
-                               new CliScenario(),
-                               // TODO new BuiltinWebServerScenario(),
-                               /* TODO new ApacheModPHPScenario(),
+                               CLI_SCENARIO,
+                               new BuiltinWebServerScenario(),
+                               // if Apache or IIS not installed, will skip 
these scenarios
+                               new ApacheModPHPScenario(),
                                new IISExpressFastCGIScenario(),
-                               new IISStandardFastCGIScenario() */
-                               },
+                               new IISStandardFastCGIScenario(),
                                // filesystems
-                               new Scenario[] {
                                LOCALFILESYSTEM_SCENARIO,
-//                             new SMBBasicScenario(),
-//                             new SMBDeduplicationScenario(),
-                               /* XXX new SMBDFSScenario(),
-                               new SMBCAScenario(),
-                               // probably don't need to test branch cache, 
but including it for completeness
-                               new SMBBranchCacheScenario()*/
-                               },
                                // options for smb - can be applied to any type 
of smb
-                               new Scenario[] {
-                                               // default is CSC enabled
-                                               new CSCEnableScenario(),
-                                               new CSCDisableScenario(),
-                               },
-                               // databases
-                               new Scenario[]{
-                               new MSAccessScenario(),
-                               new MSSQLODBCScenario(),
-                               new MSSQLScenario(),
-                               new MySQLScenario(),
-                               new PostgresSQLScenario(),
-                               new SQLite3Scenario(),
-                               // streams
-                               new FTPScenario(),
-                               new HTTPScenario(),
-                               // web services
-                               new SOAPScenario(),
-                               new XMLRPCScenario()
-                               }
+                               // default is CSC enabled
+                               new CSCEnableScenario(),
+                               new CSCDisableScenario(),
                        };
-       }
+       } // end public static Scenario[] getAllDefaultScenarios
        
 } // end public abstract class Scenario
diff --git a/src/com/mostc/pftt/scenario/ScenarioSet.java 
b/src/com/mostc/pftt/scenario/ScenarioSet.java
index 15e229d..0b5aa6c 100644
--- a/src/com/mostc/pftt/scenario/ScenarioSet.java
+++ b/src/com/mostc/pftt/scenario/ScenarioSet.java
@@ -2,6 +2,9 @@ package com.mostc.pftt.scenario;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
 
 import com.mostc.pftt.host.Host;
@@ -34,7 +37,7 @@ import com.mostc.pftt.telemetry.ConsoleManager;
  *  Even so, being able to have fast test runs is critical. PHP builds 
couldn't be tested across all that otherwise.
  *
  * @see #isSupported
- * @see ScenarioSet#getScenarioSets()
+ * @see ScenarioSet#getDefaultScenarioSets()
  * @author Matt Ficken
  *
  */
@@ -42,6 +45,59 @@ import com.mostc.pftt.telemetry.ConsoleManager;
 @SuppressWarnings("serial")
 public class ScenarioSet extends ArrayList<Scenario> {
        
+       private boolean sorted = false, sorting = false;
+       private static Comparator<Scenario> COMPARATOR = new 
Comparator<Scenario>() {
+                       @Override
+                       public int compare(Scenario a, Scenario b) {
+                               return 
a.getSerialKey().getName().compareTo(b.getSerialKey().getName());
+                       }
+               };
+       private synchronized void sort() {
+               if (sorting)
+                       return;
+               sorting = true;
+               Collections.sort(this, COMPARATOR);
+               sorting = false;
+               sorted = true;
+       }
+       
+       protected void forceSort() {
+               if (sorting)
+                       return;
+               sort();
+       }
+       
+       protected void ensureSorted() {
+               if (sorted)
+                       return;
+               sort();
+       }
+       
+       @Override
+       public String toString() {
+               ensureSorted();
+               return super.toString();
+       }
+       
+       @Override
+       public boolean add(Scenario s) {
+               super.add(s);
+               forceSort();
+               return true;
+       }
+       
+       @Override
+       public boolean equals(Object o) {
+               ensureSorted();
+               return super.equals(o);
+       }
+       
+       @Override
+       public int hashCode() {
+               ensureSorted();
+               return super.hashCode();
+       }
+       
        /** finds the SAPI Scenario in the ScenarioSet or returns the default 
SAPI scenario (CLI) in case the ScenarioSet doesn't specify one.
         * 
         * @see AbstractSAPIScenario
@@ -86,59 +142,69 @@ public class ScenarioSet extends ArrayList<Scenario> {
                return (ScenarioSet) super.clone();
        }
        
-       /** returns all possible valid ScenarioSets for all Scenarios
+       
+       /** calculates all permutations/combinations of given Scenarios and 
returns them as ScenarioSets 
         * 
+        * @param scenarios
         * @return
         */
-       public static List<ScenarioSet> getScenarioSets() {
-               return getScenarioSets(Scenario.getAllScenarios());
-       }
-       
-       public static List<ScenarioSet> getScenarioSets(Scenario[][] scenarios) 
{
-               ArrayList<Scenario[]> s = new ArrayList<Scenario[]>(3);
-               s.add(scenarios[0]);
-               s.add(scenarios[1]);
-               s.add(scenarios[2]);
-               List<Scenario> b = Arrays.asList(scenarios[3]);
-               ArrayList<ScenarioSet> sets = permute(s);
-               for (ScenarioSet set:sets) {
-                       for (Scenario c: b) {
-                               if (c.isImplemented())
-                                       set.add(c);
+       public static List<ScenarioSet> permuteScenarioSets(List<Scenario> 
scenarios) {
+               HashMap<Class<?>,List<Scenario>> map = new 
HashMap<Class<?>,List<Scenario>>();
+               for ( Scenario scenario : scenarios ) {
+                       Class<?> clazz = scenario.getSerialKey();
+                       //
+                       List<Scenario> list = map.get(clazz);
+                       if (list==null) {
+                               list = new ArrayList<Scenario>(2);
+                               map.put(clazz, list);
                        }
+                       list.add(scenario);
                }
-               return sets.subList(0, 1);// TODO 2); // XXX
+               List<List<Scenario>> remap = new ArrayList<List<Scenario>>();
+               for ( List<Scenario> list : map.values() )
+                       remap.add(list);
+               return permute(remap);
        }
-       protected static ArrayList<ScenarioSet> permute(List<Scenario[]> input) 
{
+       protected static ArrayList<ScenarioSet> permute(List<List<Scenario>> 
input) {
                ArrayList<ScenarioSet> output = new ArrayList<ScenarioSet>();
-        if (input.isEmpty()) {
-            output.add(new ScenarioSet());
-            return output;
-        }
-        List<Scenario[]> list = new ArrayList<Scenario[]>(input);
-        Scenario[] head = list.get(0);
-        List<Scenario[]> rest = list.subList(1, list.size());
-        for (List<Scenario> permutations : permute(rest)) {
-            List<ScenarioSet> subLists = new ArrayList<ScenarioSet>();
-            for (int i = 0; i <= permutations.size(); i++) {
-               for (int j=0 ; j < head.length; j++) {
-                       if (!head[j].isImplemented())
-                               continue; // skip it
-                       ScenarioSet subList = new ScenarioSet();
-                       subList.addAll(permutations);
-                       subList.add(i, head[j]);
-                       subLists.add(subList);
-               }
-            }
-            output.addAll(subLists);
-        }
-        return output;
-    }
+               if (input.isEmpty()) {
+                       output.add(new ScenarioSet());
+                       return output;
+               }
+               List<List<Scenario>> list = new 
ArrayList<List<Scenario>>(input);
+               List<Scenario> head = list.get(0);
+               List<List<Scenario>> rest = list.subList(1, list.size());
+               for (List<Scenario> permutations : permute(rest)) {
+                       List<ScenarioSet> subLists = new 
ArrayList<ScenarioSet>();
+                       for (int i = 0; i <= permutations.size(); i++) {
+                               for (int j=0 ; j < head.size(); j++) {
+                                       if (!head.get(j).isImplemented())
+                                               continue; // skip it
+                                       ScenarioSet subList = new ScenarioSet();
+                                       subList.addAll(permutations);
+                                       subList.add(i, head.get(j));
+                                       if (!subList.contains(subList))
+                                               subLists.add(subList);
+                               }
+                       }
+                       for ( ScenarioSet a : subLists ) {
+                               if (!output.contains(a))
+                                       output.add(a);
+                       }
+               }
+               return output;
+       } // end protected static ArrayList<ScenarioSet> permute
        
-       public static List<ScenarioSet> toList(ScenarioSet set) {
-               ArrayList<ScenarioSet> list = new ArrayList<ScenarioSet>(1);
-               list.add(set);
-               return list;
+       private static List<ScenarioSet> scenario_sets;
+       /** returns all builtin ScenarioSets (ScenarioSets that don't require 
special configuration (ex: SMB scenarios require a remote SMB host))
+        * 
+        * @return
+        */
+       public static List<ScenarioSet> getDefaultScenarioSets() {
+               return scenario_sets;
+       }
+       static {
+               scenario_sets = 
permuteScenarioSets(Arrays.asList(Scenario.getAllDefaultScenarios()));
        }
        
 } // end public class ScenarioSet
diff --git a/src/com/mostc/pftt/telemetry/ConsoleManager.java 
b/src/com/mostc/pftt/telemetry/ConsoleManager.java
index 56d736c..facd7c7 100644
--- a/src/com/mostc/pftt/telemetry/ConsoleManager.java
+++ b/src/com/mostc/pftt/telemetry/ConsoleManager.java
@@ -12,14 +12,15 @@ import com.mostc.pftt.ui.PhptDebuggerFrame;
 import com.mostc.pftt.util.ErrorUtil;
 
 public class ConsoleManager {
-       protected final boolean results_only, show_gui, disable_debug_prompt;
+       protected final boolean results_only, show_gui, disable_debug_prompt, 
dont_cleanup_test_pack, phpt_not_in_place;
        protected PhptDebuggerFrame gui;
                
-       public ConsoleManager(boolean results_only, boolean show_gui, boolean 
disable_debug_prompt) {
+       public ConsoleManager(boolean results_only, boolean show_gui, boolean 
disable_debug_prompt, boolean dont_cleanup_test_pack, boolean 
phpt_not_in_place) {
                this.results_only = results_only;
                this.show_gui = show_gui;
                this.disable_debug_prompt = disable_debug_prompt;
-               
+               this.dont_cleanup_test_pack = dont_cleanup_test_pack;
+               this.phpt_not_in_place = phpt_not_in_place;
        }
        
        public void showGUI(PhptTestPackRunner test_pack_runner) {
@@ -76,5 +77,13 @@ public class ConsoleManager {
        public boolean isResultsOnly() {
                return results_only;
        }
+
+       public boolean isDontCleanupTestPack() {
+               return dont_cleanup_test_pack;
+       }
+
+       public boolean isPhptNotInPlace() {
+               return phpt_not_in_place;
+       }
        
 } // end public class ConsoleManager
diff --git a/src/com/mostc/pftt/telemetry/PhptTestResult.java 
b/src/com/mostc/pftt/telemetry/PhptTestResult.java
index b869268..44613ab 100644
--- a/src/com/mostc/pftt/telemetry/PhptTestResult.java
+++ b/src/com/mostc/pftt/telemetry/PhptTestResult.java
@@ -63,8 +63,6 @@ public class PhptTestResult {
        }
        
        public PhptTestResult(Host host, EPhptTestStatus status, PhptTestCase 
test_case, String actual, String[] actual_lines, String[] expected_lines, 
Charset actual_cs, Map<String,String> env, String[] cmd_array, byte[] 
stdin_data, String shell_script, Diff<String> diff, String expectf_output, 
String preoverride_actual, String sapi_output) {
-               actual = (""+actual_cs)+"\n"+actual; // TODO
-               
                this.sapi_output = sapi_output;
                
                this.host = host;
diff --git a/src/com/mostc/pftt/ui/ExpectedActualDiffPHPTDisplay.java 
b/src/com/mostc/pftt/ui/ExpectedActualDiffPHPTDisplay.java
index 438016d..9727337 100644
--- a/src/com/mostc/pftt/ui/ExpectedActualDiffPHPTDisplay.java
+++ b/src/com/mostc/pftt/ui/ExpectedActualDiffPHPTDisplay.java
@@ -11,11 +11,9 @@ import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 
-import javax.swing.DefaultListModel;
 import javax.swing.JButton;
 import javax.swing.JFrame;
 import javax.swing.JLabel;
-import javax.swing.JList;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
@@ -43,8 +41,6 @@ public class ExpectedActualDiffPHPTDisplay extends 
JScrollPane {
        protected TextDisplayPanel expected_display, diff_display, 
actual_display, test_display;
        protected DefaultTableModel env_table_model;
        protected JTable env_table;
-       protected DefaultListModel cmd_array_list_model;
-       protected JList cmd_array_list;
        protected JTextArea stdin_data_textarea, shell_script_textarea, 
expectf_textarea, pre_override_textarea, sapi_output_textarea;
        protected PhptTestResult test;
                        
@@ -102,9 +98,6 @@ public class ExpectedActualDiffPHPTDisplay extends 
JScrollPane {
                prepared_panel.add(new JScrollPane(env_table = new 
JTable(env_table_model = new DefaultTableModel())));
                env_table_model.addColumn("Name");
                env_table_model.addColumn("Value");
-               prepared_panel.add(new JScrollPane(cmd_array_list = new 
JList(cmd_array_list_model = new DefaultListModel())));
-               vertical_panel.add(prepared_panel);
-               
                
                vertical_panel.add(test_display.text_area);
        }
@@ -124,12 +117,6 @@ public class ExpectedActualDiffPHPTDisplay extends 
JScrollPane {
                                env_table_model.addRow(new Object[]{name, 
test.env.get(name)});
                        } 
                }
-               cmd_array_list_model.clear();
-               if (test.cmd_array!=null) {
-                       for (String cmd : test.cmd_array ) {
-                               cmd_array_list_model.addElement(cmd);
-                       }
-               }
                if (test.stdin_data!=null)
                        stdin_data_textarea.setText(new 
String(test.stdin_data));
                else
diff --git a/src/com/mostc/pftt/ui/PhptDebuggerFrame.java 
b/src/com/mostc/pftt/ui/PhptDebuggerFrame.java
index a1d7e9b..b753d29 100644
--- a/src/com/mostc/pftt/ui/PhptDebuggerFrame.java
+++ b/src/com/mostc/pftt/ui/PhptDebuggerFrame.java
@@ -60,7 +60,7 @@ public class PhptDebuggerFrame extends JPanel {
                jmb.add(new JMenu("Test:"));
                jmb.add(scenario_menu = new JMenu("Scenarios"));
                ButtonGroup scenario_bg = new ButtonGroup();
-               for ( ScenarioSet set : ScenarioSet.getScenarioSets() ) {
+               for ( ScenarioSet set : ScenarioSet.getDefaultScenarioSets() ) 
{ // TODO
                        JRadioButtonMenuItem rb = new 
JRadioButtonMenuItem(set.toString());
                        scenario_bg.add(rb);
                        scenario_menu.add(rb);
diff --git a/src/com/mostc/pftt/util/HostEnvUtil.java 
b/src/com/mostc/pftt/util/HostEnvUtil.java
index 3350657..8fc695c 100644
--- a/src/com/mostc/pftt/util/HostEnvUtil.java
+++ b/src/com/mostc/pftt/util/HostEnvUtil.java
@@ -2,6 +2,7 @@ package com.mostc.pftt.util;
 
 import com.mostc.pftt.host.ExecOutput;
 import com.mostc.pftt.host.Host;
+import com.mostc.pftt.host.LocalHost;
 import com.mostc.pftt.telemetry.ConsoleManager;
 
 /** Utilities for setting up the test environment and convenience settings on 
Hosts
@@ -54,20 +55,34 @@ public final class HostEnvUtil {
                        cm.println("HostEnvUtil", "disabling Windows 
Firewall...");
                        
                        // LATER edit firewall rules instead (what if on public 
network, ex: Azure)
-               host.execElevated("netsh firewall set opmode disable", 
Host.ONE_MINUTE);                        
-               
-               
-               cm.println("HostEnvUtil", "creating File Share for 
"+host.getPhpSdkDir()+"...");
-               // share PHP-SDK over network. this also will share C$, G$, 
etc...
-               host.execElevated("NET SHARE PHP_SDK="+host.getPhpSdkDir()+" 
/Grant:"+host.getUsername()+",Full", Host.ONE_MINUTE);
+                       host.execElevated("netsh firewall set opmode disable", 
Host.ONE_MINUTE);                        
+                       
+                       
+                       cm.println("HostEnvUtil", "creating File Share for 
"+host.getPhpSdkDir()+"...");
+                       // share PHP-SDK over network. this also will share C$, 
G$, etc...
+                       host.execElevated("NET SHARE 
PHP_SDK="+host.getPhpSdkDir()+" /Grant:"+host.getUsername()+",Full", 
Host.ONE_MINUTE);
                }
-               
-        
-        if (host.isVistaOrBefore()) {
+                       
+               if (host.isVistaOrBefore()) {
                        // install VC9 runtime (win7+ don't need this)
-                       // TODO
-        }
-        cm.println("HostEnvUtil", "Windows host prepared to run PHP.");
+                       if 
(host.dirContainsFragment(host.getSystemRoot()+"\\WinSxS", "vc9")) {
+                               cm.println("HostEnvUtil", "VC9 Runtime alread 
installed");
+                       } else {
+                               String local_file = 
LocalHost.getLocalPfttDir()+"/bin/vc9_vcredist_x86.exe";
+                               String remote_file = null;
+                               if (host.isRemote()) {
+                                       remote_file = 
host.mktempname("HostEnvUtil", ".exe");
+                                       
+                                       cm.println("HostEnvUtil", "Uploading 
VC9 Runtime");
+                                       host.upload(local_file, remote_file);
+                               }
+                               cm.println("HostEnvUtil", "Installing VC9 
Runtime");
+                               host.execElevated(remote_file+" /Q", 
Host.NO_TIMEOUT);
+                               if (remote_file!=null)
+                                       host.delete(remote_file);
+                       }
+               }
+               cm.println("HostEnvUtil", "Windows host prepared to run PHP.");
        } // end public static void prepareWindows
        
        public static final String REG_DWORD = "REG_DWORD";

Reply via email to