Commit:    c20cec1c0a4eea3ea4edfa2c22552f7d4aa57ee5
Author:    Matt Ficken <v-maf...@microsoft.com>         Wed, 20 Nov 2013 
12:36:19 -0800
Parents:   12b8407317b4abd66116e9c5ccd1f796d9818096
Branches:  master

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

Log:
Time Travel Tracing integration (Windows only)


Former-commit-id: 1c507b3440740050eb6a11e8f07b4b42646d478f

Changed paths:
  M  conf/task/snap_test.groovy
  M  src/com/mostc/pftt/host/AHost.java
  M  src/com/mostc/pftt/host/ExecOutput.java
  A  src/com/mostc/pftt/host/ICrashDetector.java
  M  src/com/mostc/pftt/host/LocalHost.java
  M  src/com/mostc/pftt/host/PosixLocalHost.java
  M  src/com/mostc/pftt/host/WindowsLocalHost.java
  A  src/com/mostc/pftt/main/CmpReport.java
  A  src/com/mostc/pftt/main/CmpReport2.java
  A  src/com/mostc/pftt/main/CmpReport2G.groovy
  A  src/com/mostc/pftt/main/PBCReportGen.groovy
  M  src/com/mostc/pftt/model/sapi/CliSAPIInstance.java
  M  src/com/mostc/pftt/results/AbstractPhpUnitRW.java
  M  src/com/mostc/pftt/results/AbstractPhptRW.java
  M  src/com/mostc/pftt/results/AbstractTestResultRW.java
  M  src/com/mostc/pftt/results/AbstractUITestRW.java
  M  src/com/mostc/pftt/results/LocalConsoleManager.java
  M  src/com/mostc/pftt/results/PhptResultWriter.java
  M  src/com/mostc/pftt/runner/CliPhptTestCaseRunner.java
  M  src/com/mostc/pftt/runner/LocalPhptTestPackRunner.java
  M  src/com/mostc/pftt/scenario/CLIScenario.java
  M  src/com/mostc/pftt/util/DebuggerManager.java
  M  src/com/mostc/pftt/util/GDBDebugManager.java
  A  src/com/mostc/pftt/util/TimeTravelTraceDebugManager.java
  M  src/com/mostc/pftt/util/TimerUtil.java
  M  src/com/mostc/pftt/util/ValgrindMemoryCheckManager.java
  M  src/com/mostc/pftt/util/WinDebugManager.java
  A  src/com/mostc/pftt/util/WindowsDebuggerToolsManager.java

diff --git a/conf/task/snap_test.groovy b/conf/task/snap_test.groovy
index 06e5468..08a94e4 100644
--- a/conf/task/snap_test.groovy
+++ b/conf/task/snap_test.groovy
@@ -8,7 +8,7 @@ def processConsoleOptions(List options) {
        //
        options.add("-c")
        // test these SAPIs
-       options.add("apache,cli")//,builtin_web")
+       options.add("apache,cli")
        options.add("-c")
        // test with and without opcache 
        options.add("opcache,no_code_cache,not_opcache_builtin_web")
diff --git a/src/com/mostc/pftt/host/AHost.java 
b/src/com/mostc/pftt/host/AHost.java
index e9adfc4..e37a3ba 100644
--- a/src/com/mostc/pftt/host/AHost.java
+++ b/src/com/mostc/pftt/host/AHost.java
@@ -15,6 +15,7 @@ import com.github.mattficken.io.ByLineReader;
 import com.github.mattficken.io.CharsetDeciderDecoder;
 import com.github.mattficken.io.IOUtil;
 import com.github.mattficken.io.StringUtil;
+import com.mostc.pftt.results.AbstractTestResultRW;
 import com.mostc.pftt.results.ConsoleManager;
 import com.mostc.pftt.results.EPrintType;
 import com.mostc.pftt.runner.AbstractTestPackRunner.TestPackRunnerThread;
@@ -390,8 +391,18 @@ public abstract class AHost extends Host implements 
IProgramRunner {
        public ExecOutput execElevatedOut(String cmd, int timeout_sec, String 
chdir) throws Exception {
                return execElevatedOut(cmd, timeout_sec, chdir, false);
        }
+       public interface IExecHandleCleanupNotify {
+               public void cleanupNotify(ExecHandle eh, AbstractTestResultRW 
rw);
+       }
        @ThreadSafe
-       public abstract class ExecHandle {
+       public abstract class ExecHandle implements ICrashDetector {
+               public IExecHandleCleanupNotify cleanup_notify;
+               public void cleanup(AbstractTestResultRW rw) {
+                       if (cleanup_notify==null)
+                               return;
+                       
+                       cleanup_notify.cleanupNotify(this, rw);
+               }
                public abstract InputStream getSTDOUT();
                public abstract OutputStream getSTDIN();
                /** KILLs process
@@ -423,6 +434,7 @@ public abstract class AHost extends Host implements 
IProgramRunner {
                 * 
                 * @return
                 */
+               @Override
                public boolean isCrashed() {
                        return isCrashExitCode(AHost.this, getExitCode(), 
false);
                }
diff --git a/src/com/mostc/pftt/host/ExecOutput.java 
b/src/com/mostc/pftt/host/ExecOutput.java
index b2d49fb..a7f5b9b 100644
--- a/src/com/mostc/pftt/host/ExecOutput.java
+++ b/src/com/mostc/pftt/host/ExecOutput.java
@@ -7,7 +7,7 @@ import com.github.mattficken.io.StringUtil;
 import com.mostc.pftt.results.ConsoleManager;
 import com.mostc.pftt.results.EPrintType;
 
-public class ExecOutput {
+public class ExecOutput implements ICrashDetector {
        /** output of executed program */
        public String output;
        /** character the program used for its output */
@@ -36,6 +36,7 @@ public class ExecOutput {
        public boolean isSuccess() {
                return exit_code == 0;
        }
+       @Override
        public boolean isCrashed() {
                return exit_code < -1;
        }
diff --git a/src/com/mostc/pftt/host/ICrashDetector.java 
b/src/com/mostc/pftt/host/ICrashDetector.java
new file mode 100644
index 0000000..3851c64
--- /dev/null
+++ b/src/com/mostc/pftt/host/ICrashDetector.java
@@ -0,0 +1,5 @@
+package com.mostc.pftt.host;
+
+public interface ICrashDetector {
+       public boolean isCrashed();
+}
diff --git a/src/com/mostc/pftt/host/LocalHost.java 
b/src/com/mostc/pftt/host/LocalHost.java
index 21e26fd..3ef655f 100644
--- a/src/com/mostc/pftt/host/LocalHost.java
+++ b/src/com/mostc/pftt/host/LocalHost.java
@@ -350,9 +350,9 @@ public abstract class LocalHost extends AHost {
                                return;
                        // @see WindowsLocalHost#exec_copy_lines
                        run.set(false);
-                       synchronized(run) {
+                       /*synchronized(run) {
                                run.notifyAll();
-                       }
+                       }*/
                        
                        // sometimes it can take a while to #close a 
process(especially on Windows)... do it in a thread
                        // to avoid blocking for too long. however, we don't 
want to have too many threads
@@ -431,9 +431,13 @@ public abstract class LocalHost extends AHost {
                        // read process' output (block until #close or exit)
                        exec_copy_lines(output_sb, max_chars, stdout, charset);
                        // ignores STDERR
-                       
+                       for(;;) {
+                               if (!doIsRunning(p))
+                                       break;
+                               Thread.sleep(50);
+                       }
                        // wait for process exit (shouldn't get here until exit 
or #close though)
-                       for (int time = 50;wait.get();) {
+                       /*for (int time = 50;wait.get();) {
                                try {
                                        exit_code = p.exitValue();
                                        break;
@@ -458,7 +462,7 @@ public abstract class LocalHost extends AHost {
                                                break;
                                        }
                                }
-                       }
+                       }*/
                        //
                        
                        active_proc_counter.decrementAndGet();
@@ -511,10 +515,7 @@ public abstract class LocalHost extends AHost {
                        return exit_code;
                }
 
-               public int getProcessID() {
-                       final Process p = process.get();
-                       return p!=null && isLocalhostWindows() ? 
getWindowsProcessID(p) : 0;
-               }
+               public abstract int getProcessID();
 
                @Override
                public InputStream getSTDOUT() {
@@ -532,7 +533,8 @@ public abstract class LocalHost extends AHost {
                public void run(ConsoleManager cm, StringBuilder output_sb, 
Charset charset, int timeout_sec, @SuppressWarnings("rawtypes") 
TestPackRunnerThread thread, int thread_slow_sec, int suspend_seconds, int 
max_chars) throws IOException, InterruptedException {
                        TimerThread a = null, b = null;
                        if (thread!=null && thread_slow_sec>NO_TIMEOUT) {
-                               b = TimerUtil.waitSeconds(thread_slow_sec, new 
ThreadSlowTask(thread));
+                               // TODO get rid of thread_slow_sec feature - 
just use AbstractLocalTestPackRunner
+                               //b = TimerUtil.waitSeconds(thread_slow_sec, 
new ThreadSlowTask(thread));
                        }
                        
                        if (timeout_sec>NO_TIMEOUT) {
@@ -554,17 +556,6 @@ public abstract class LocalHost extends AHost {
                
        } // end public class LocalExecHandle
        
-       // TODO temp
-       public static int getWindowsProcessID(Process process) {
-               try {
-                       // clean way
-                       WinProcess wproc = new WinProcess(process);
-                       return wproc.getPid();
-               } catch ( Throwable wt ) {
-                       return getWindowsProcessIDReflection(process);
-               }
-       }
-       
        protected static int getWindowsProcessIDReflection(Process process) {
                // WinProcess native code couldn't be loaded
                // (maybe it wasn't included or maybe somebody didn't compile 
it)
diff --git a/src/com/mostc/pftt/host/PosixLocalHost.java 
b/src/com/mostc/pftt/host/PosixLocalHost.java
index 0b14b30..60e59c3 100644
--- a/src/com/mostc/pftt/host/PosixLocalHost.java
+++ b/src/com/mostc/pftt/host/PosixLocalHost.java
@@ -40,6 +40,11 @@ public class PosixLocalHost extends LocalHost {
                        return doIsRunning(p);
                }
                
+               public int getProcessID() {
+                       final Process p = process.get();
+                       return 0; // TODO
+               }
+               
                protected void exec_copy_lines(final StringBuilder sb, final 
int max_chars, final InputStream in, final Charset charset) throws IOException {
                        do_exec_copy_lines(sb, max_chars, in, charset);
                }
diff --git a/src/com/mostc/pftt/host/WindowsLocalHost.java 
b/src/com/mostc/pftt/host/WindowsLocalHost.java
index 0c160bd..225936c 100644
--- a/src/com/mostc/pftt/host/WindowsLocalHost.java
+++ b/src/com/mostc/pftt/host/WindowsLocalHost.java
@@ -13,7 +13,6 @@ import org.jvnet.winp.WinProcess;
 
 import com.github.mattficken.io.StringUtil;
 import com.mostc.pftt.host.CommonCommandManager.Win32ProcessInfo;
-import com.mostc.pftt.main.PfttMain;
 import com.mostc.pftt.runner.AbstractTestPackRunner.TestPackRunnerThread;
 import com.mostc.pftt.util.TimerUtil;
 import com.mostc.pftt.util.TimerUtil.ObjectRunnable;
@@ -93,11 +92,16 @@ public class WindowsLocalHost extends LocalHost {
                                // IMPORTANT: this is how its identified in the 
Windows process table
                                this.image_name = "cmd.exe"; 
                        } else {
-                               this.image_name = this.image_name.toLowerCase();
-                               if (this.image_name.equals("cmd"))
-                                       this.image_name = "cmd.exe";
+                               this.image_name = 
basename(this.image_name).toLowerCase();
+                               if (image_name.indexOf('.')==-1)
+                                       this.image_name += ".exe";
                        }
                }
+               
+               public int getProcessID() {
+                       final Process p = process.get();
+                       return p!=null ? getWindowsProcessID(p) : 0;
+               }
 
                @Override
                public boolean isRunning() {
@@ -147,24 +151,31 @@ public class WindowsLocalHost extends LocalHost {
                                                try {
                                                        do_exec_copy_lines(sb, 
max_chars, in, charset);
                                                        
copy_thread_lock.set(false);
-                                                       synchronized(run) {
+                                                       run.set(false);
+                                                       /*synchronized(run) {
                                                                run.notifyAll();
-                                                       }
-                                               } catch (IOException e) {
-                                                       e.printStackTrace();
+                                                       }*/
+                                               } catch (Throwable e) {
+                                                       //e.printStackTrace();
                                                }
                                        }
                                });
                        
copy_thread.setUncaughtExceptionHandler(IGNORE_EXCEPTION_HANDLER);
                        while (wait.get()) {
-                               synchronized(run) {
+                               //synchronized(run) {
                                        try {
-                                               run.wait(30000);
+                                               //run.wait(30000);
+                                               Thread.sleep(300);
                                        } catch ( InterruptedException ex ) {}
-                               }
+                               //}
                                if (!run.get()) {
                                        // try killing copy thread since its 
still running after it was supposed to stop
-                                       copy_thread.stop(new 
RuntimeException());
+                                       copy_thread.stop(new RuntimeException() 
{
+                                               @Override
+                                               public void printStackTrace() {
+                                                       
+                                               }
+                                       });
                                        break;
                                } else if (!copy_thread_lock.get()) {
                                        // stopped normally
@@ -208,7 +219,9 @@ public class WindowsLocalHost extends LocalHost {
                        // Windows BN?: if TerminateProcess() called with a PID 
that doesn't exist anymore (but might again soon)
                        //              does TerminateProcess() block forever 
(or does it appear that way because of Windows slow process
                        //              management?(Windows is optimized to run 
a few processes only (because thats what users did with it in the '90s)))
-                       if 
(image_name.equals("taskkill.exe")||image_name.equals("handle.exe")||image_name.equals("pskill.exe"))
 {
+                       String image_name = basename(this.image_name);
+                       if 
(image_name.equals("taskkill.exe")||image_name.equals("handle.exe")||image_name.equals("pskill.exe")
+                                       
||image_name.equals("taskkill")||image_name.equals("handle")||image_name.equals("pskill"))
 {
                                // can't use taskkill, could create an infinite 
loop
                                //
                                // @see https://github.com/kohsuke/winp
@@ -220,12 +233,16 @@ public class WindowsLocalHost extends LocalHost {
                        int pid = getWindowsProcessIDReflection(p); // NOT 
WinProcess#getPID?
                        // for closing handles and other special stuff to work 
here, must get the actual process
                        // not just cmd.exe (it won't have problems with stray 
handles, etc...)
-                       if (image_name.equals("cmd.exe")) {
+                       //
+                       // IMPORTANT: #doClose may be called multiple times, so 
don't change this#doClose
+                       if 
(image_name.equals("cmd.exe")||image_name.equals("cmd")) {
                                List<Win32ProcessInfo> table = 
ccm.getWindowsProcessTable(WindowsLocalHost.this);
                                for ( Win32ProcessInfo info : table ) {
                                        if (info.parent_pid==pid) {
                                                // found child
-                                               image_name = info.exe_path;
+                                               //
+                                               // IMPORTANT: basename() 
because TaskKill doesn't take paths for image name
+                                               image_name = 
basename(info.exe_path);
                                                pid = info.pid;
                                        }
                                }
@@ -250,28 +267,23 @@ public class WindowsLocalHost extends LocalHost {
                                        //    @see 
AbstractPhptTestCaseRunner2#doRunTestClean
                                        
ccm.winCloseAllHandles(WindowsLocalHost.this, pid);
                                }
-                               // can kill off windebug if running under PUTS 
(windebug shouldn't be running at all, sometimes does)
-                               if (!PfttMain.is_puts && 
ccm.ensureWinDebugIsNotRunning(WindowsLocalHost.this, pid)) {
-                                       
-                                       // do nothing, wait for windebug
-                               } else {
-                                       // provide TASKKILL the image name of 
the process to try avoiding killing the wrong process
-                                       try {
-                                               // /T => terminate child 
processes too
-                                               exec("TASKKILL /FI \"IMAGENAME 
eq "+image_name+"\" /FI \"PID eq "+pid+"\" /F /T", 20);
-                                               // also, 
WinProcess#killRecursively only checks by process id (not image/program name)
-                                               // while that should be enough, 
experience on Windows has shown that it isn't and somehow gets PFTT killed 
eventually
-                                               //
-                                               // Windows Note: Windows does 
NOT automatically terminate child processes when the parent gets killed
-                                               //               the only way 
that happens is if you search for the child processes FIRST yourself,
-                                               //               (and then 
their children, etc...) and then kill them.
-                                       } catch ( Exception ex ) {
-                                               // fallback
-                                               //
-                                               // @see 
https://github.com/kohsuke/winp
-                                               WinProcess wprocess = new 
WinProcess(p);
-                                               wprocess.kill();
-                                       }
+                               // provide TASKKILL the image name of the 
process to try avoiding killing the wrong process
+                               try {
+                                       // /T => terminate child processes too
+                                       exec("TASKKILL /FI \"IMAGENAME eq 
"+image_name+"\" /FI \"PID eq "+pid+"\" /F /T", 20);
+                                       // also, WinProcess#killRecursively 
only checks by process id (not image/program name)
+                                       // while that should be enough, 
experience on Windows has shown that it isn't and somehow gets PFTT killed 
eventually
+                                       //
+                                       // Windows Note: Windows does NOT 
automatically terminate child processes when the parent gets killed
+                                       //               the only way that 
happens is if you search for the child processes FIRST yourself,
+                                       //               (and then their 
children, etc...) and then kill them.
+                               } catch ( Exception ex ) {
+                                       ex.printStackTrace();
+                                       // fallback
+                                       //
+                                       // @see https://github.com/kohsuke/winp
+                                       WinProcess wprocess = new WinProcess(p);
+                                       wprocess.kill();
                                }
                        }
                } // end protected void doClose
@@ -346,4 +358,14 @@ public class WindowsLocalHost extends LocalHost {
                return true;
        } // end public boolean mkdirs
        
+       public static int getWindowsProcessID(Process process) {
+               try {
+                       // clean way
+                       WinProcess wproc = new WinProcess(process);
+                       return wproc.getPid();
+               } catch ( Throwable wt ) {
+                       return getWindowsProcessIDReflection(process);
+               }
+       }
+       
 } // end public class WindowsLocalHost
diff --git a/src/com/mostc/pftt/main/CmpReport.java 
b/src/com/mostc/pftt/main/CmpReport.java
new file mode 100644
index 0000000..2202046
--- /dev/null
+++ b/src/com/mostc/pftt/main/CmpReport.java
@@ -0,0 +1,553 @@
+package com.mostc.pftt.main;
+
+import groovy.xml.MarkupBuilder;
+
+import java.awt.Desktop;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.util.Collection;
+import java.util.LinkedList;
+
+import org.apache.commons.net.ftp.FTP;
+import org.apache.commons.net.ftp.FTPClient;
+import org.apache.commons.net.ftp.FTPSClient;
+import org.apache.commons.net.util.TrustManagerUtils;
+import org.columba.ristretto.message.Address;
+import org.columba.ristretto.parser.AddressParser;
+import org.columba.ristretto.parser.ParserException;
+import org.columba.ristretto.smtp.SMTPException;
+import org.columba.ristretto.smtp.SMTPProtocol;
+
+import com.github.mattficken.io.ArrayUtil;
+import com.github.mattficken.io.StringUtil;
+import com.mostc.pftt.host.AHost;
+import com.mostc.pftt.host.LocalHost;
+import com.mostc.pftt.model.core.EPhptTestStatus;
+import com.mostc.pftt.model.core.PhpBuildInfo;
+import com.mostc.pftt.results.AbstractPhpUnitRW;
+import com.mostc.pftt.results.AbstractPhptRW;
+import com.mostc.pftt.results.AbstractTestResultRW;
+import com.mostc.pftt.results.ConsoleManager;
+import com.mostc.pftt.results.LocalConsoleManager;
+import com.mostc.pftt.results.PhpResultPack;
+import com.mostc.pftt.results.PhpResultPackReader;
+import com.mostc.pftt.results.TextBuilder;
+import com.mostc.pftt.util.EMailUtil;
+import com.mostc.pftt.util.EMailUtil.ESMTPAuthMethod;
+import com.mostc.pftt.util.EMailUtil.ESMTPSSL;
+
+public class CmpReport {
+       static String generateFileName(AbstractPhptRW base, AbstractPhptRW 
test) {
+               String file_name = "PHPT_CMP_"
+                               
+base.getBuildInfo().getBuildBranch()+"-"+base.getBuildInfo().getVersionRevision()+"-"+base.getBuildInfo().getBuildType()+"-"+base.getBuildInfo().getCPUArch()+"-"+base.getBuildInfo().getCompiler()+"_"+base.getScenarioSetNameWithVersionInfo()+
+                               "_v_"
+                               
+test.getBuildInfo().getBuildBranch()+"-"+test.getBuildInfo().getVersionRevision()+"-"+test.getBuildInfo().getBuildType()+"-"+test.getBuildInfo().getCPUArch()+"-"+test.getBuildInfo().getCompiler()+"_"+test.getScenarioSetNameWithVersionInfo();
+               file_name = StringUtil.max(file_name, 100);
+               
+               return file_name + ".html";
+       }
+       static String generateFileName(AbstractPhpUnitRW base, 
AbstractPhpUnitRW test) {
+               String file_name = 
"PhpUnit_CMP_"+test.getTestPackNameAndVersionString()+"_"
+                               
+base.getBuildInfo().getBuildBranch()+"-"+base.getBuildInfo().getVersionRevision()+"-"+base.getBuildInfo().getBuildType()+"-"+base.getBuildInfo().getCPUArch()+"-"+base.getBuildInfo().getCompiler()+"_"+base.getScenarioSetNameWithVersionInfo()+
+                               "_v_"
+                               
+test.getBuildInfo().getBuildBranch()+"-"+test.getBuildInfo().getVersionRevision()+"-"+test.getBuildInfo().getBuildType()+"-"+test.getBuildInfo().getCPUArch()+"-"+test.getBuildInfo().getCompiler()+"_"+test.getScenarioSetNameWithVersionInfo();
+               file_name = StringUtil.max(file_name, 80);
+               
+               return file_name + ".html";
+       }
+       static class Verify implements IRecvr {
+
+               @Override
+               public void recv(AbstractPhptRW base, AbstractPhptRW test, 
PHPTMultiHostTwoBuildSingleScenarioSetReportGen phpt_report, String html_str) 
throws IOException {
+                       File html_file = new 
File("c:\\php-sdk\\"+generateFileName(base, test));
+                       FileWriter fw = new FileWriter(html_file);
+                       fw.write(html_str);
+                       fw.close();
+                       
+                       Desktop.getDesktop().browse(html_file.toURI());
+               }
+
+               @Override
+               public void recv(AbstractPhpUnitRW base, AbstractPhpUnitRW 
test, PhpUnitMultiHostTwoBuildSingleScenarioSetReportGen php_unit_report, 
String html_str) throws IOException {
+                       File html_file = new 
File("c:\\php-sdk\\"+generateFileName(base, test));
+                       FileWriter fw = new FileWriter(html_file);
+                       fw.write(html_str);
+                       fw.close();
+                       Desktop.getDesktop().browse(html_file.toURI());
+               }
+
+               @Override
+               public void start(PhpResultPack test_pack) throws Exception {}
+
+               @Override
+               public void stop(PhpResultPack test_pack) throws Exception {}
+               
+       }
+       static class Mailer {
+               final Address from, puts_admin;
+               final Address[] puts_users;
+               SMTPProtocol smtp;
+               final boolean debug, force;
+               LinkedList<String> message_bodies = new LinkedList<String>();
+               final String phpt_prefix;
+               
+               Mailer(boolean debug, boolean force, Address[] puts_users) 
throws ParserException {
+                       this("Core", debug, force, puts_users);
+               }
+               
+               Mailer(String phpt_prefix, boolean debug, boolean force, 
Address[] puts_users) throws ParserException {
+                       this.phpt_prefix = phpt_prefix;
+                       this.debug = debug;
+                       this.force = force;
+                       from = 
AddressParser.parseAddress("ostcp...@outlook.com");
+                       this.puts_users = puts_users;
+                       puts_admin = 
AddressParser.parseAddress("v-maf...@microsoft.com");
+               }
+               
+               protected void connect() throws IOException, SMTPException {
+                       smtp = EMailUtil.connect("smtp.live.com", 587, from, 
ESMTPSSL.IMPLICIT_SSL, ESMTPAuthMethod.PLAIN, "ostcp...@outlook.com", 
"php868841432".toCharArray());
+               }
+               
+               protected String getSubjectPrefix(Address to) {
+                       String ma = to.getMailAddress();
+                       int i = ma.indexOf('@');
+                       if (i==-1)
+                               return "";
+                       ma = ma.substring(0, i);
+                       ma = ma.trim();
+                       if (ma.length()==0)
+                               return "";
+                       else
+                               return "["+ma.toUpperCase()+"] ";
+               }
+               protected void sendMail(boolean too_much_change, String 
subject, String html_str) throws IOException, SMTPException, Exception {
+                       // prevent sending duplicate messages
+                       if (message_bodies.contains(html_str)) {
+                               return;
+                       }
+                       message_bodies.add(html_str);
+                       Collection<Address> recipients;
+                       if (!force && too_much_change) {
+                               System.err.println("Debug: too much change, 
sending to admin only");
+                               recipients = ArrayUtil.toList(puts_admin);
+                               subject = "[PUTS] Review " + subject;
+                       } else {
+                               // this is probably going to a mailing list
+                               // a common convention for mailing lists is for 
automated mail to
+                               // have a subject prefixed with the mailing 
list's name
+                               //subject = getSubjectPrefix(report_to)+subject;
+                               //
+                               // instead, promote the `PUTS` name
+                               subject = "[PUTS] "+subject;
+                               if (debug)
+                                       recipients = 
ArrayUtil.toList(puts_admin);
+                               else
+                                       recipients = 
ArrayUtil.toList(puts_users);
+                       }
+                       try {
+                               EMailUtil.sendHTMLMessage(
+                                               smtp, 
+                                               from, 
+                                               recipients, 
+                                               subject,
+                                               html_str
+                                       );
+                       } catch ( Exception ex ) {
+                               // errors seen:
+                               // 5.3.4 Requested action not taken; We noticed 
some unusual activity in your Hotmail account. To help protect you, we've 
temporarily blocked your account.
+                               //      -can't send email to puts_admin in this 
case (can't send email)
+                               // TODO should log these errors - especially 
5.3.4
+                               ex.printStackTrace();
+                               
+                               // reconnect and retry once
+                               smtp.dropConnection();
+                               
+                               connect();
+                               
+                               EMailUtil.sendHTMLMessage(
+                                               smtp, 
+                                               from, 
+                                               recipients, 
+                                               subject,
+                                               html_str
+                                       );
+                       }
+               }
+               
+               public String createSubject(PhpBuildInfo info) {
+                       return info.getBuildBranch()+" 
"+info.getVersionRevision()+"-"+info.getBuildType()+"-"+info.getCPUArch();
+               }
+       }
+       static class Mail extends Mailer implements IRecvr {
+               LinkedList<String> message_bodies = new LinkedList<String>();
+               
+               Mail(boolean debug, boolean force, Address[] puts_users) throws 
ParserException {
+                       super(debug, force, puts_users);
+               }
+               
+               Mail(String phpt_prefix, boolean debug, boolean force, 
Address[] puts_users) throws ParserException {
+                       super(phpt_prefix, debug, force, puts_users);
+               }
+               
+               @Override
+               public void recv(AbstractPhptRW base, AbstractPhptRW test, 
PHPTMultiHostTwoBuildSingleScenarioSetReportGen phpt_report, String html_str) 
throws IOException, SMTPException, Exception {
+                       if (test.count(EPhptTestStatus.PASS)<100)
+                               return;
+                       sendMail(
+                                       !force && test.isTooMuchChange(base),
+                                       phpt_prefix+" PHPT Report 
"+createSubject(test.getBuildInfo()),
+                                       html_str
+                               );
+               }
+
+               @Override
+               public void recv(AbstractPhpUnitRW base, AbstractPhpUnitRW 
test, PhpUnitMultiHostTwoBuildSingleScenarioSetReportGen php_unit_report, 
String html_str) throws IOException, SMTPException, Exception {
+                       sendMail(
+                                       !force && test.isTooMuchChange(base),
+                                       "Application PhpUnit Report 
"+createSubject(test.getBuildInfo()),
+                                       html_str
+                               );
+               }
+
+               @Override
+               public void start(PhpResultPack test_pack) throws Exception {
+                       connect();
+               }
+
+               @Override
+               public void stop(PhpResultPack test_pack) throws Exception {
+                       message_bodies.clear();
+               }
+       }
+       enum EFTPSSL {
+               NO_SSL,
+               REQUIRE_SSL,
+               DETECT_SSL
+       }
+       static class Upload implements IRecvr {
+               static FTPClient configureClient(FTPSClient ftps) {
+                       
ftps.setTrustManager(TrustManagerUtils.getAcceptAllTrustManager());
+                       return ftps;
+               }
+               
+               FTPClient ftp;
+               
+               @Override
+               public void start(PhpResultPack test_pack) throws IOException {
+                       connect();
+               }
+               
+               protected void connect() throws IOException {
+                       EFTPSSL use_ssl = EFTPSSL.NO_SSL;
+                       switch(use_ssl) {
+                       case REQUIRE_SSL:
+                               ftp = configureClient(new FTPSClient(true));
+                               break;
+                       case DETECT_SSL:
+                               ftp = configureClient(new FTPSClient(false));
+                               break;
+                       default:
+                               ftp = new FTPClient();
+                       }
+                       
+                       ftp.connect("131.107.220.66", 21); // TODO
+                       ftp.login("pftt", "1nter0pLAb!!");
+                       ftp.setFileType(FTP.BINARY_FILE_TYPE);
+               }
+               
+               static final String[] HOSTS86 = new String[]{"2008r2sp0", 
"2008r2sp1", "Win7sp0-x64", "Win7sp1-x64", "Win7sp0-x86", "Win7sp1-x86", 
"Vistasp2-x86", "Vistasp2-x64", "2008sp0-x86", "2008sp0-x64", "2008sp1-x86", 
"2008sp1-x64", "2012sp0", "8sp0-x64", "2012r2"};
+               static final String[] HOSTS64 = new String[]{"2008r2sp0", 
"2008r2sp1", "Win7sp0-x64", "Win7sp1-x64", "Vistasp2-x64", "2008sp0-x64", 
"2008sp1-x64", "2012sp0", "8sp0-x64", "2012r2"};
+               @Override
+               public void stop(PhpResultPack test_pack) throws 
IllegalStateException, Exception {
+                       LocalHost host = LocalHost.getInstance();
+                       // copy_hosts
+                       {
+                               File[] fhosts = 
test_pack.getResultPackPath().listFiles();
+                               for ( File fhost : fhosts ) {
+                                       if (fhost.isDirectory()) {
+                                               //
+                                               final String prefix = 
Character.toString((char)( 74 + new java.util.Random().nextInt(3)));
+                                               final String[] hosts = 
test_pack.getBuildInfo().isX64() ? HOSTS64 : HOSTS86;
+                                               
+                                               for ( int i=0 ; i < 
hosts.length ; i++ ) {
+                                                       final String hostname = 
prefix + "-" +hosts[i];
+                                                       
+                                                       final File target_file 
= new File(fhost.getParentFile(), hostname);
+                                                       
System.out.println(fhost+" "+target_file+" "+i);
+                                                       if (i==0) {
+                                                               
host.move(fhost.getAbsolutePath(), target_file.getAbsolutePath());
+                                                               fhost = 
target_file;
+                                                       } else {
+                                                               
host.copy(fhost.getAbsolutePath(), target_file.getAbsolutePath());
+                                                       }
+                                               }
+                                               break;
+                                       }
+                               }
+                       }
+                       
+                       final String temp_file = host.mktempname("Uploader", 
".7z");
+                       
+                       System.out.println("Compressing result-pack: 
"+test_pack.getResultPackPath());
+                       
+                       host.compress(null, host, 
test_pack.getResultPackPath().getAbsolutePath(), temp_file);
+                       
+                       final String remote_file = 
generateFolder(test_pack)+"/"+test_pack.getResultPackPath().getName()+".7z";
+                       
+                       System.out.println("Compressed. Uploading "+temp_file+" 
to "+remote_file);
+                       
+                       retryStore(generateFolder(test_pack), remote_file, new 
BufferedInputStream(new FileInputStream(temp_file)));
+                       
+                       System.out.println("Uploaded result-pack 
"+test_pack.getResultPackPath()+" to "+remote_file);
+                       
+                       ftp.logout();
+                       ftp.disconnect();
+                       
+                       host.delete(temp_file);
+               }
+               
+               protected void retryStore(String folder, String file, 
InputStream in) throws IOException {
+                       for ( int i=0 ; i < 10 ; i++ ) {
+                               try {
+                                       ftp.mkd(folder);
+                                       ftp.storeFile(file, in);
+                                       break;
+                               } catch ( Exception ex ) {
+                                       ex.printStackTrace();
+                                       // these methods can block forever if 
there was an exception
+                                       //ftp.logout();
+                                       //ftp.disconnect();
+                                       // this too? ftp.quit();
+                                       ftp = null;
+                                       connect();
+                               }
+                       }
+               }
+               
+               static String generateFolder(PhpResultPack test_pack) {
+                       return 
"/PFTT-Results/"+test_pack.getBuildInfo().getBuildBranch()+"/"+test_pack.getBuildInfo().getVersionRevision();
+               }
+               static String generateFolder(AbstractTestResultRW test) {
+                       return 
"/PFTT-Results/"+test.getBuildInfo().getBuildBranch()+"/"+test.getBuildInfo().getVersionRevision();
+               }
+               
+               @Override
+               public void recv(AbstractPhptRW base, AbstractPhptRW test, 
PHPTMultiHostTwoBuildSingleScenarioSetReportGen phpt_report, String html_str) 
throws IOException {
+                       final String folder = generateFolder(test);
+                       
+                       final String file = generateFileName(base, test);
+                       
+                       retryStore(folder, folder+"/"+file, new 
ByteArrayInputStream(html_str.getBytes()));
+               }
+
+               @Override
+               public void recv(AbstractPhpUnitRW base, AbstractPhpUnitRW 
test, PhpUnitMultiHostTwoBuildSingleScenarioSetReportGen php_unit_report, 
String html_str) throws IOException {
+                       final String folder = generateFolder(test);
+                       
+                       final String file = generateFileName(base, test);
+                       
+                       retryStore(folder, folder+"/"+file, new 
ByteArrayInputStream(html_str.getBytes()));
+               }
+               
+       }
+       interface IRecvr {
+               void recv(AbstractPhptRW base, AbstractPhptRW test, 
PHPTMultiHostTwoBuildSingleScenarioSetReportGen phpt_report, String html_str) 
throws IOException, SMTPException, Exception;
+               void recv(AbstractPhpUnitRW base, AbstractPhpUnitRW test, 
PhpUnitMultiHostTwoBuildSingleScenarioSetReportGen php_unit_report, String 
html_str) throws IOException, SMTPException, Exception;
+               void start(PhpResultPack test_pack) throws Exception;
+               void stop(PhpResultPack test_pack) throws Exception;
+       }
+       static void clean_hosts(AHost host, File pack) throws 
IllegalStateException, IOException {
+               File[] hosts = pack.listFiles();
+               boolean is_first = true;
+               for ( File fhost : hosts ) {
+                       if ( fhost.isDirectory() ) {
+                               if (is_first) {
+                                       // don't delete all, leave first folder 
found
+                                       is_first = false;
+                               } else {
+                                       System.err.println("delete "+fhost);
+                                       host.delete(fhost.getAbsolutePath());
+                               }
+                       }
+               }
+       }
+       public static void main(String[] args) throws Exception {
+               //IRecvr recvr = new Verify();
+               //
+               // mssql uses a different test-pack so reports only for that 
test-pack can be mailed
+               // whereas wincacheu is a bunch of scenarios for both core and 
mssql test-packs
+               //         therefore all reports must go to wincache
+               //         -shows how wincacheu scenarios compare to other 
scenarios
+               //         -shows the mssql driver with wincacheu
+               final Mailer summary_mailer = new Mailer(false, false, new 
Address[]{AddressParser.parseAddress("v-maf...@microsoft.com")}); 
+               final IRecvr CORE_PRODUCTION_SNAP = new Mail(false, false, new 
Address[]{AddressParser.parseAddress("winca...@microsoft.com"), 
AddressParser.parseAddress("ostc...@microsoft.com")});
+               //final IRecvr CORE_PRODUCTION_SNAP = new Mail(false, false, 
new Address[]{AddressParser.parseAddress("ostc...@microsoft.com")});
+               // TODO final IRecvr MSSQL_PRODUCTION_SNAP = new Mail("MSSQL", 
false, false, 
AddressParser.parseAddress("winca...@microsoft.com;jayk...@microsoft.com;ostc...@microsoft.com"),AddressParser.parseAddress("shekh...@microsoft.com"));
+               final IRecvr MSSQL_PRODUCTION_SNAP = new Mail("MSSQL", false, 
true, new Address[]{AddressParser.parseAddress("ostc...@microsoft.com"), 
AddressParser.parseAddress("jayk...@microsoft.com")});
+               // TODO final IRecvr CORE_PRODUCTION_RELEASE = new Mail(false, 
true, 
AddressParser.parseAddress("winca...@microsoft.com;jayk...@microsoft.com;ostc...@microsoft.com"));
+               final IRecvr CORE_PRODUCTION_RELEASE = new Mail(false, true, 
new Address[]{AddressParser.parseAddress("ostc...@microsoft.com")});
+               final IRecvr CORE_PRODUCTION_QA = new Mail(false, true, new 
Address[]{AddressParser.parseAddress("php-qa@lists.php.net")});
+               final IRecvr TEST = new Mail(true, false, new 
Address[]{AddressParser.parseAddress("v-maf...@microsoft.com")});
+               //IRecvr recvr = CORE_PRODUCTION_SNAP; // TODO
+               //IRecvr recvr = CORE_PRODUCTION_RELEASE;
+               //IRecvr recvr = MSSQL_PRODUCTION_SNAP;
+               IRecvr recvr = new Upload();
+               
+               LocalHost host = LocalHost.getInstance();
+               LocalConsoleManager cm = new LocalConsoleManager();
+               
+               // TODO check if a smoke test failed!
+               //File base_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_3-Result-Pack-5.3.27rc1-nTS-X86-VC9"));
+               //File test_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_3-Result-Pack-5.3.27-nTS-X86-VC9"));
+               //File base_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_3-Result-Pack-re2e002d-nTS-X86-VC9"));
+               //File test_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_3-Result-Pack-r7c9bb87-nTS-X86-VC9"));
+               //File base_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_3-Result-Pack-re2e002d-TS-X86-VC9"));
+               //File test_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_3-Result-Pack-r7c9bb87-TS-X86-VC9"));
+               //File base_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_4-Result-Pack-5.4.20rc1-TS-X86-VC9"));
+               //File test_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_4-Result-Pack-5.4.20-TS-X86-VC9"));
+               
+               //File base_dir = new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_4-Result-Pack-rd487f5e-TS-X86-VC9");
+               //File test_dir = new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_4-Result-Pack-r6c48c6b-TS-X86-VC9");
+               //File base_dir = new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_4-Result-Pack-ra03f094-nTS-X86-VC9");
+               //File test_dir = new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_4-Result-Pack-r72aacbf-nTS-X86-VC9");
+
+               //File base_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_5-Result-Pack-rc8b0da6-TS-X64-VC11"));
+               //File test_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_5-Result-Pack-rc8b0da6-TS-x64-VC11"));
+               //File base_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_5-Result-Pack-r82dd6b9-NTS-X64-VC11"));
+               //File test_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_5-Result-Pack-rc8b0da6-NTS-X64-VC11"));
+               //File base_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_5-Result-Pack-rb2ee1b6-NTS-X86-VC11"));
+               //File test_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_5-Result-Pack-rfc9d886-NTS-X86-VC11"));
+               //File base_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_5-Result-Pack-rc8b0da6-TS-X86-VC11"));
+               //File test_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_5-Result-Pack-rfc9d886-TS-X86-VC11"));
+               
+               //File base_dir = new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_Master-Result-Pack--TS-X86-VC11");
+               //File base_dir = new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_Master-Result-Pack-r43289d6-TS-X86-VC11");
+               //File test_dir = new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_6-Result-Pack-5.6.0-dev-TS-X86-VC11-keyword916");
+               //File base_dir = new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_Master-Result-Pack-r89c4aba-NTS-X64-VC11");
+               //File base_dir = new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_Master-Result-Pack-r82bb2a2-NTS-X86-VC11");
+               //File test_dir = new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_6-Result-Pack-5.6.0-dev-NTS-X86-VC11-keyword916");
+               //File base_dir = new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_Master-Result-Pack-r5e1ac55-NTS-X86-VC11");
+               //File test_dir = new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_Master-Result-Pack-r82bb2a2-NTS-X86-VC11");
+               //File base_dir = new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_Master-Result-Pack-rd515455-TS-X64-VC11");
+               //File test_dir = new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_Master-Result-Pack-r04fcf6a-TS-X64-VC11");
+               
+               File base_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_5-Result-Pack-5.5.6RC1-TS-X64-VC11"));
+               File test_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_5-Result-Pack-5.5.6-TS-X64-VC11"));
+               //File base_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_4-Result-Pack-5.4.22rc1-NTS-X86-VC9-SQLSVR"));
+               //File test_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_4-Result-Pack-5.4.22-NTS-X86-VC9-SQLSVR"));
+               //File base_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_4-Result-Pack-5.4.22rc1-TS-X86-VC9"));
+               //File test_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_4-Result-Pack-5.4.22-TS-X86-VC9"));
+               //File base_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_Master-Result-Pack-ra0244a6-NTS-X86-VC11"));
+               //File test_dir = (new 
File("C:\\php-sdk\\PFTT-Auto\\PHP_5_6-Result-Pack-5.6.0-50333-NTS-X86-VC11"));
+       
+               
+               // clean_hosts
+               clean_hosts(host, base_dir);
+               clean_hosts(host, test_dir);
+               PhpResultPackReader base_pack = PhpResultPackReader.open(cm, 
host, base_dir);
+               PhpResultPackReader test_pack = PhpResultPackReader.open(cm, 
host, test_dir);
+               
+               // TODO temp 
+               //report("Core", cm, recvr, base_pack, test_pack);
+               summary(summary_mailer, cm, base_pack, test_pack);
+               
+       }
+       static void summary(Mailer m, ConsoleManager cm, PhpResultPackReader 
base_pack, PhpResultPackReader test_pack) throws IOException, SMTPException, 
Exception {
+               CmpReport2 cmp = new CmpReport2();
+               cmp.add(base_pack);
+               cmp.add(test_pack);
+               
+               AHost host = LocalHost.getInstance();
+               
+               {
+                       StringWriter sw = new StringWriter();
+                       TextBuilder text = new TextBuilder(sw);
+               
+                       new CmpReport2G().run(text, cmp, cm);
+                       //m.connect();
+                       //m.sendMail(false, "Summary 
"+m.createSubject(test_pack.getBuildInfo()), sw.toString());
+                       host.saveTextFile("c:\\php-sdk\\test.txt", 
sw.toString());
+                       host.exec("notepad c:\\php-sdk\\test.txt", 
AHost.FOUR_HOURS);
+               }
+               {
+                       StringWriter sw = new StringWriter();
+                       MarkupBuilder html = new MarkupBuilder(sw);
+               
+                       new CmpReport2G().run(html, cmp, cm);
+                       //m.connect();
+                       //m.sendMail(false, "Summary 
"+m.createSubject(test_pack.getBuildInfo()), sw.toString());
+                       host.saveTextFile("c:\\php-sdk\\test.html", 
sw.toString());
+                       host.exec("start c:\\php-sdk\\test.html", 
AHost.FOUR_HOURS);
+               }
+       }
+       static void report(String phpt_prefix, ConsoleManager cm, IRecvr recvr, 
PhpResultPack base_pack, PhpResultPack test_pack) throws IOException, 
SMTPException, Exception {
+               recvr.start(test_pack);
+               
+               // TODO turn off phpt or phpunit reports or turn off all but a 
specific test-pack
+               for ( AbstractPhpUnitRW base : base_pack.getPhpUnit() ) {
+                       for ( AbstractPhpUnitRW test : test_pack.getPhpUnit() ) 
{
+                               System.out.println("PhpUnit 
"+base.getScenarioSetNameWithVersionInfo()+" 
"+test.getScenarioSetNameWithVersionInfo());
+                               //if 
(!base.getTestPackNameAndVersionString().contains("Wordpress"))
+                                       //continue;
+                               if (!eq(base.getTestPackNameAndVersionString(), 
test.getTestPackNameAndVersionString()))
+                                       continue;
+                               // TODO mysql
+                               if 
(!base.getScenarioSetNameWithVersionInfo().replace("MySQL-5.6_", 
"").replace("7.0.1", 
"7.0.2").equals(test.getScenarioSetNameWithVersionInfo().replace("MySQL-5.6_", 
"")))
+                                               /*
+                                               
!(base.getScenarioSetNameWithVersionInfo().toLowerCase().contains("opcache")==test.getScenarioSetNameWithVersionInfo().toLowerCase().contains("opcache")
+                                               &&(
+                                                               
test.getScenarioSetNameWithVersionInfo().toLowerCase().contains("cli")||
+                                                               
test.getScenarioSetNameWithVersionInfo().toLowerCase().contains("builtin")||
+                                                               
test.getScenarioSetNameWithVersionInfo().toLowerCase().contains("apache")
+                                                               ) 
+                                               
&&base.getScenarioSetNameWithVersionInfo().toLowerCase().contains("cli")==test.getScenarioSetNameWithVersionInfo().toLowerCase().contains("cli")
+                                               
&&base.getScenarioSetNameWithVersionInfo().toLowerCase().contains("builtin")==test.getScenarioSetNameWithVersionInfo().toLowerCase().contains("builtin")
+                                               
&&base.getScenarioSetNameWithVersionInfo().toLowerCase().contains("apache")==test.getScenarioSetNameWithVersionInfo().toLowerCase().contains("apache")
+                                               
&&base.getScenarioSetNameWithVersionInfo().toLowerCase().contains("local")==test.getScenarioSetNameWithVersionInfo().toLowerCase().contains("local")
+                                               
&&base.getScenarioSetNameWithVersionInfo().toLowerCase().contains("smb-dfs")==test.getScenarioSetNameWithVersionInfo().toLowerCase().contains("smb-dfs")
+                                       
&&base.getScenarioSetNameWithVersionInfo().toLowerCase().contains("smb-dedup")==test.getScenarioSetNameWithVersionInfo().toLowerCase().contains("smb-dedup")
+                                       
&&base.getScenarioSetNameWithVersionInfo().toLowerCase().contains("smb-basic")==test.getScenarioSetNameWithVersionInfo().toLowerCase().contains("smb-basic")
+                                               ))*/
+                                       continue;
+                               
+                               
PhpUnitMultiHostTwoBuildSingleScenarioSetReportGen php_unit_report = new 
PhpUnitMultiHostTwoBuildSingleScenarioSetReportGen(base, test);
+                               
+                               String html_str = 
php_unit_report.getHTMLString(cm, !(recvr instanceof Upload));
+                               
+                               recvr.recv(base, test, php_unit_report, 
html_str);
+                               
+                       }
+               }
+               //System.exit(0);
+               for ( AbstractPhptRW base : base_pack.getPHPT() ) {
+                       for ( AbstractPhptRW test : test_pack.getPHPT() ) {
+                               System.out.println("PHPT 
"+base.getScenarioSetNameWithVersionInfo()+" 
"+test.getScenarioSetNameWithVersionInfo()+" "+base+" "+test);
+                               if 
(!eq(base.getScenarioSetNameWithVersionInfo(), 
test.getScenarioSetNameWithVersionInfo()))
+                                       continue;
+                               
+                               PHPTMultiHostTwoBuildSingleScenarioSetReportGen 
phpt_report = new PHPTMultiHostTwoBuildSingleScenarioSetReportGen(phpt_prefix, 
base, test);
+                               String html_str = phpt_report.getHTMLString(cm, 
!(recvr instanceof Upload));
+
+                               recvr.recv(base, test, phpt_report, html_str);
+                       }
+               }
+               recvr.stop(test_pack);
+       }
+       static boolean eq(String base, String test) {
+               if (base==null||test==null)
+                       return false;
+               base = base.replace("MySQL-5.6_", "");
+               base = base.replace("7.0.1", "7.0.2");
+               base = base.replace("-1.3.4", "");
+               test = test.replace("7.0.1", "7.0.2");
+               test = test.replace("MySQL-5.6_", "");
+               test = test.replace("-1.3.4", "");
+               return base.startsWith(test)||test.startsWith(base);
+       }
+}
diff --git a/src/com/mostc/pftt/main/CmpReport2.java 
b/src/com/mostc/pftt/main/CmpReport2.java
new file mode 100644
index 0000000..ed43f62
--- /dev/null
+++ b/src/com/mostc/pftt/main/CmpReport2.java
@@ -0,0 +1,306 @@
+package com.mostc.pftt.main;
+
+import groovy.lang.GroovyObject;
+import groovy.xml.MarkupBuilder;
+
+import java.awt.Desktop;
+import java.io.File;
+import java.io.FileWriter;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.github.mattficken.io.ArrayUtil;
+import com.mostc.pftt.host.AHost;
+import com.mostc.pftt.host.LocalHost;
+import com.mostc.pftt.model.app.EPhpUnitTestStatus;
+import com.mostc.pftt.model.core.EPhptTestStatus;
+import com.mostc.pftt.model.core.PhpBuildInfo;
+import com.mostc.pftt.results.AbstractPhpUnitRW;
+import com.mostc.pftt.results.AbstractPhptRW;
+import com.mostc.pftt.results.LocalConsoleManager;
+import com.mostc.pftt.results.PhpResultPackReader;
+import com.mostc.pftt.scenario.ScenarioSet;
+
+public class CmpReport2 {
+       final HashMap<PhpBuildInfo,PhpResultPackReader> result_packs;
+       final LinkedList<String> phpt_test_packs, phpunit_test_packs;
+       final LinkedList<AHost> hosts;
+       final HashMap<String,LinkedList<ScenarioSet>> phpt_scenario_sets, 
phpunit_scenario_sets;
+       
+       public CmpReport2() {
+               result_packs = new HashMap<PhpBuildInfo,PhpResultPackReader>();
+               phpt_test_packs = new LinkedList<String>();
+               phpunit_test_packs = new LinkedList<String>();
+               phpt_scenario_sets = new 
HashMap<String,LinkedList<ScenarioSet>>();
+               phpunit_scenario_sets = new 
HashMap<String,LinkedList<ScenarioSet>>();
+               hosts = new LinkedList<AHost>();
+       }
+       
+       void add(PhpResultPackReader result_pack) {
+               PhpBuildInfo build_info = result_pack.getBuildInfo();
+               if (result_packs.containsKey(build_info))
+                       return;
+               result_packs.put(build_info, result_pack);
+               for ( AHost host : result_pack.getHosts() ) {
+                       if (!hosts.contains(host))
+                               hosts.add(host);
+                       for ( String phpt_test_pack : 
result_pack.getPhptTestPacks(host)) {
+                               // TODO temp if 
(!phpt_test_packs.contains(phpt_test_pack))
+                                       phpt_test_packs.add(phpt_test_pack);
+                               LinkedList<ScenarioSet> sets = 
phpt_scenario_sets.get(phpt_test_pack);
+                               if (sets==null) {
+                                       sets = new LinkedList<ScenarioSet>();
+                                       phpt_scenario_sets.put(phpt_test_pack, 
sets);
+                               }
+                               for ( ScenarioSet scenario_set : 
result_pack.getPhptScenarioSets(host, phpt_test_pack) ) {
+                                       boolean a = true;
+                                       for ( ScenarioSet other : sets ) {
+                                               if 
(other.toString().equals(scenario_set.toString())) {
+                                                       a = false;
+                                                       break;
+                                               }       
+                                       }
+                                       if (a)
+                                               sets.add(scenario_set);
+                               }
+                       }
+                       for ( String phpunit_test_pack : 
result_pack.getPhpUnitTestPacks(host)) {
+                               if 
(!phpunit_test_packs.contains(phpunit_test_pack))
+                                       
phpunit_test_packs.add(phpunit_test_pack);
+                               LinkedList<ScenarioSet> sets = 
phpunit_scenario_sets.get(phpunit_test_pack);
+                               if (sets==null) {
+                                       sets = new LinkedList<ScenarioSet>();
+                                       
phpunit_scenario_sets.put(phpunit_test_pack, sets);
+                               }
+                               for ( ScenarioSet scenario_set : 
result_pack.getPhpUnitScenarioSets(host, phpunit_test_pack) ) {
+                                       boolean a = true;
+                                       for ( ScenarioSet other : sets ) {
+                                               if 
(other.toString().equals(scenario_set.toString())) {
+                                                       a = false;
+                                                       break;
+                                               }       
+                                       }
+                                       if (a)
+                                               sets.add(scenario_set);
+                               }
+                       }
+               }
+               System.out.println("67 "+phpunit_scenario_sets);
+               System.out.println(phpt_test_packs);
+               System.out.println(phpunit_test_packs);
+       }
+       
+       final HashMap<String,String> color_map = new HashMap<String,String>();
+       String getColor(String scenario_set_str) {
+               String color = color_map.get(scenario_set_str);
+               if (color!=null)
+                       return color;
+               Collection<String> colors = color_map.values();
+               switch(colors.size()%20) {
+               case 0:
+                       color = "#ff1b1b";
+                       break;
+               case 1:
+                       color = "#1bff1b";
+                       break;
+               case 2:
+                       color = "#1bcece";
+                       break;
+               case 3:
+                       color = "#ffce1b";
+                       break;
+               case 4:
+                       color = "#aaff1b";
+                       break;
+               case 5:
+                       color = "#1baaff";
+                       break;
+               case 6:
+                       color = "#ff1baa";
+                       break;
+               case 7:
+                       color = "#eaea1b";
+                       break;
+               case 8:
+                       color = "#ea1bff";
+                       break;
+               case 9:
+                       color = "#ff1bea";
+                       break;
+               case 10:
+                       color = "#1beaff";
+                       break;
+               case 11:
+                       color = "#86ff1b";
+                       break;
+               case 12:
+                       color = "#ff1b86";
+                       break;
+               case 13:
+                       color = "#1b86ff";
+                       break;
+               case 14:
+                       color = "#861bff";
+                       break;
+               case 15:
+                       color = "#01ff1b";
+                       break;
+               case 16:
+                       color = "#ff011b";
+                       break;
+               case 17:
+                       color = "#ff1b01";
+                       break;
+               case 18:
+                       color = "#00aaff";
+                       break;
+               case 19:
+                       color = "#aa00ff";
+                       break;
+               }
+               color_map.put(scenario_set_str, color);
+               return color;
+       }
+               
+       PhpResultPackReader get(PhpBuildInfo build_info) {
+               return result_packs.get(build_info);
+       }
+       AbstractPhptRW getPhpt(AHost host, PhpBuildInfo build_info, ScenarioSet 
scenario_set, String test_pack_name) {
+               try {
+               return get(build_info).getPHPT(host, scenario_set, 
test_pack_name);
+               } catch ( Exception ex ) {
+                       ex.printStackTrace();
+                       return null;
+               }
+       }
+       AbstractPhpUnitRW getPhpUnit(AHost host, PhpBuildInfo build_info, 
ScenarioSet scenario_set, String test_pack_name_and_version) {
+               try {
+                       return get(build_info).getPhpUnit(host, 
test_pack_name_and_version, scenario_set);
+               } catch ( Exception ex ) {
+                       ex.printStackTrace();
+                       return null;
+               }
+       }
+       List<ScenarioSet> getPhptScenarioSets(String 
test_pack_name_and_version) {
+               return phpt_scenario_sets.get(test_pack_name_and_version);
+       }
+       List<ScenarioSet> getPhpUnitScenarioSets(String 
test_pack_name_and_version) {
+               return phpunit_scenario_sets.get(test_pack_name_and_version);
+       }
+       List<String> getUniquePhptTestNames(PhpBuildInfo build_info,  String 
test_pack_name_and_version, ScenarioSet scenario_set, EPhptTestStatus status) {
+               LinkedList<String> out = new LinkedList<String>();
+               for ( AHost host : hosts ) {
+                       for ( PhpResultPackReader result_pack : 
result_packs.values()) {
+                               for ( AbstractPhptRW r : 
result_pack.getPHPT(host) ) {
+                                       if 
(!r.getScenarioSetNameWithVersionInfo().equals(scenario_set.toString()))
+                                               continue;
+                                       if 
(!r.getTestPackVersion().equals(test_pack_name_and_version))
+                                               continue;
+                                       List<String> n = r.getTestNames(status);
+                                       for ( String a : n )
+                                               out.remove(a);
+                               }
+                       }
+               }
+               Collections.sort(out);
+               return out;
+       }
+       List<String> getPhptTestNames(String test_pack_name_and_version, 
EPhptTestStatus status) {
+               LinkedList<String> out = new LinkedList<String>();
+               for ( PhpResultPackReader result_pack : result_packs.values()) {
+                       for ( AHost host : hosts ) {
+                               for ( AbstractPhptRW r : 
result_pack.getPHPT(host, test_pack_name_and_version) )
+                                       
ArrayUtil.copyNoDuplicates(r.getTestNames(status), out);
+                       }
+               }
+               Collections.sort(out);
+               return out;
+       }
+       List<String> getPhptScenarioSets(PhpBuildInfo build_info, String 
test_pack_name_and_version, String test_name, EPhptTestStatus status) {
+               LinkedList<String> out = new LinkedList<String>();
+               String scenario_set_str;
+               for ( AHost host : hosts ) {
+                       for ( AbstractPhptRW r : get(build_info).getPHPT(host, 
test_pack_name_and_version) ) {
+                               if (r.isTestStatus(test_name, status)) {
+                                       scenario_set_str = 
r.getScenarioSetNameWithVersionInfo();
+                                       if (!out.contains(scenario_set_str))
+                                               out.add(scenario_set_str);
+                               }
+                       }
+               }
+               return out;
+       }
+       List<String> getUniquePhpUnitTestNames(PhpBuildInfo build_info, String 
test_pack_name_and_version, ScenarioSet scenario_set, EPhpUnitTestStatus 
status) {
+               LinkedList<String> out = new LinkedList<String>();
+               
+               for ( AHost host : hosts ) {
+                       for ( PhpResultPackReader result_pack : 
result_packs.values()) {
+                               for ( AbstractPhpUnitRW r : 
result_pack.getPhpUnit(host) ) {
+                                       if 
(!r.getScenarioSetNameWithVersionInfo().equals(scenario_set.toString()))
+                                               continue;
+                                       if 
(!r.getTestPackNameAndVersionString().equals(test_pack_name_and_version))
+                                               continue;
+                                       List<String> n = r.getTestNames(status);
+                                       ArrayUtil.copyNoDuplicates(n, out);
+                               }
+                       }
+               }
+               Collections.sort(out);
+               return out;
+       }
+       List<String> getPhpUnitTestNames(String test_pack_name_and_version, 
EPhpUnitTestStatus status) {
+               LinkedList<String> out = new LinkedList<String>();
+               for ( PhpResultPackReader result_pack : result_packs.values()) {
+                       for ( AHost host : hosts ) {
+                               for ( AbstractPhpUnitRW r : 
result_pack.getPhpUnit(host, test_pack_name_and_version) ) {
+                                       
ArrayUtil.copyNoDuplicates(r.getTestNames(status), out);
+                               }
+                       }
+               }
+               Collections.sort(out);
+               return out;
+       }
+       List<String> getPhpUnitScenarioSets(PhpBuildInfo build_info, String 
test_pack_name_and_version, String test_name, EPhpUnitTestStatus status) {
+               LinkedList<String> out = new LinkedList<String>();
+               String scenario_set_str;
+               for ( AHost host : hosts ) {
+                       for ( AbstractPhpUnitRW r : 
get(build_info).getPhpUnit(host, test_pack_name_and_version) ) {
+                               if (r.isTestStatus(test_name, status)) {
+                                       scenario_set_str = 
r.getScenarioSetNameWithVersionInfo();
+                                       if (!out.contains(scenario_set_str))
+                                               out.add(scenario_set_str);
+                               }
+                       }
+               }
+               return out;
+       }
+       public static void main(String[] args) throws Exception {
+               LocalConsoleManager cm = new LocalConsoleManager();
+               CmpReport2 cmp = new CmpReport2();
+               LocalHost localhost = LocalHost.getInstance();
+               //
+               /*cmp.add(PhpResultPackReader.open(cm, localhost, new 
File("C:\\php-sdk\\WinCacheU\\PHP_5_5-Result-Pack-5.5.2RC1-NTS-X86-VC11-2")));
+               cmp.add(PhpResultPackReader.open(cm, localhost, new 
File("C:\\php-sdk\\WinCacheU\\PHP_5_5-Result-Pack-5.5.3-NTS-X86-VC11")));
+               cmp.add(PhpResultPackReader.open(cm, localhost, new 
File("C:\\php-sdk\\WinCacheU\\PHP_5_4-Result-Pack-5.4.18RC2-NTS-X86-VC9-2")));*/
+               
+               cmp.add(PhpResultPackReader.open(cm, localhost, new 
File("C:\\php-sdk\\PHP_5_5-Result-Pack-5.5.3-NTS-X86-VC11")));
+               cmp.add(PhpResultPackReader.open(cm, localhost, new 
File("C:\\php-sdk\\PHP_5_5-Result-Pack-5.5.3-TS-X86-VC11")));
+               cmp.add(PhpResultPackReader.open(cm, localhost, new 
File("C:\\php-sdk\\PHP_5_4-Result-Pack-5.4.19-NTS-X86-VC9")));
+               cmp.add(PhpResultPackReader.open(cm, localhost, new 
File("C:\\php-sdk\\PHP_5_4-Result-Pack-5.4.19-TS-X86-VC9")));
+               cmp.add(PhpResultPackReader.open(cm, localhost, new 
File("C:\\php-sdk\\PHP_5_3-Result-Pack-5.3.27-NTS-X86-VC9")));
+               cmp.add(PhpResultPackReader.open(cm, localhost, new 
File("C:\\php-sdk\\PHP_5_3-Result-Pack-5.3.27-TS-X86-VC9")));
+               
+               File html_file = new File("c:\\php-sdk\\temp.html");
+               FileWriter fw = new FileWriter(html_file);
+               MarkupBuilder html = new MarkupBuilder(fw);
+               
+               new CmpReport2G().run(html, cmp, cm);
+               
+               fw.close();
+               
+               Desktop.getDesktop().browse(html_file.toURI());
+       }
+}
diff --git a/src/com/mostc/pftt/main/CmpReport2G.groovy 
b/src/com/mostc/pftt/main/CmpReport2G.groovy
new file mode 100644
index 0000000..06537ab
--- /dev/null
+++ b/src/com/mostc/pftt/main/CmpReport2G.groovy
@@ -0,0 +1,32 @@
+package com.mostc.pftt.main
+
+import groovy.lang.GroovyObject;
+import groovy.xml.MarkupBuilder;
+
+import com.mostc.pftt.results.ConsoleManager;
+import com.mostc.pftt.host.AHost;
+
+class CmpReport2G {
+       public void run(BuilderSupport html, CmpReport2 cmp, ConsoleManager cm) 
{
+               html.html {
+                       html.body {
+                               
+               for (AHost host : cmp.hosts) {
+                       // TODO detect multiple hosts and make separate reports 
for each host
+                       for (String test_pack_name : cmp.phpt_test_packs) {
+                               
PHPTSingleHostMultiBuildMultiScenarioSetReportGen phpt_report = new 
PHPTSingleHostMultiBuildMultiScenarioSetReportGen();
+                               phpt_report.run(host, test_pack_name, html, 
cmp, cmp.result_packs.keySet(), cm, false);
+                               html.br();
+                       }
+               
+                       for (String test_pack_name : cmp.phpunit_test_packs) {
+                               
PhpUnitSingleHostMultiBuildMultiScenarioSetReportGen phpunit_report = new 
PhpUnitSingleHostMultiBuildMultiScenarioSetReportGen();
+                               phpunit_report.run(host, test_pack_name, html, 
cmp, cmp.result_packs.keySet(), cm, false);
+                               html.br()
+                       }
+               }
+                       } // body
+               } // html
+               
+       }
+}
diff --git a/src/com/mostc/pftt/main/PBCReportGen.groovy 
b/src/com/mostc/pftt/main/PBCReportGen.groovy
new file mode 100644
index 0000000..561cf97
--- /dev/null
+++ b/src/com/mostc/pftt/main/PBCReportGen.groovy
@@ -0,0 +1,231 @@
+package com.mostc.pftt.main
+/*
+#%powershell1.0%
+#
+# File: results-template.ps1
+# Description: Output $data into html table.  This script is meant to be 
called from summarize-results.ps1.
+#
+
+Function gaincalc ( $a=0, $b=0 )  {
+       if ( ($a -ne 0) -and ($b -ne 0) )  {
+               $c = ($a / $b) - 1
+
+               switch ($c*100)  {
+                       {$_ -ge 0 -and $_ -le 3} {$script:gainclass="none"}
+                       {$_ -gt 3 -and $_ -le 7} 
{$script:gainclass="gainpossmall"}
+                       {$_ -ge 0 -and $_ -gt 7} {$script:gainclass="gainpos"}
+                       {$_ -lt 0 -and $_ -ge -3} {$script:gainclass="none"}
+                       {$_ -lt -3 -and $_ -ge -7} 
{$script:gainclass="gainnegsmall"}
+                       {$_ -lt 0 -and $_ -lt -7} {$script:gainclass="gainneg"}
+                       Default { $script:gainclass="none" }
+               }
+
+               "{0:P2}" -f $c
+       }
+       else  {
+               $script:gainclass="none"
+       }
+}
+
+
+write-output "
+
+<html>
+<head>
+ <style type=text/css>
+   .data td { border:1px solid black; }
+   .iis { background-color:#E6B8B7; }
+   .iiswincache { background-color: #DA9694; }
+   .apache { background-color: #C5D9F1; }
+   .apachenoigbinary { background-color: #8DB4E2; }
+   .apachewithigbinary { background-color: #538DD5; }
+   .gainpos { background-color: #00A84C; }
+   .gainpossmall { background-color: #00D661; }
+   .gainneg { background-color: #FF2929; }
+   .gainnegsmall { background-color: #DA9694; }
+ </style>
+ </head>
+
+<body>
+<table border=0 cellpadding=0 cellspacing=0 width=600>
+<tr>
+<td> <strong> PHP Performance </strong> </td>
+<td> <strong> Hardware & Environment </strong> </td>
+</tr><tr>
+<td>$PHP1 - $PHP2</td>
+<td>Dell R710</td>
+</tr><tr>
+<td> &nbsp; </td>
+<td>CPU - Intel Quad core @ 2.26Ghz (x2) L5520</td>
+</tr><tr>
+<td> &nbsp; </td>
+<td>Memory - 12GB RAM</td>
+</tr><tr>
+<td> &nbsp; </td>
+<td>HD - 147GB SAS RAID 1</td>
+</tr><tr>
+<td> &nbsp; </td>
+<td>NIC - 1Gbps Intel</td>
+</tr><tr>
+<td> &nbsp; </td>
+<td>Windows 2008 R2 - SP1</td>
+</tr><tr>
+<td> &nbsp; </td>
+<td>php-web01.ctac.nttest.microsoft.com</td>
+</tr>
+<table>
+<p> &nbsp; </p>
+
+<table border=0 cellpadding=0 cellspacing=0 width=1628 class=data>
+
+<!-- Header Labels -->
+ <tr>
+  <td colspan=4></td>
+  <td colspan=6 class=iis>IIS 7.5</td>
+  <td colspan=9 class=apache>Apache 2.2</td>
+ </tr>
+
+ <tr>
+  <td colspan=4></td>
+  <td colspan=3 rowspan=2 class=iis>No Cache</td>
+  <td colspan=3 rowspan=2 class=iiswincache>WinCache</td>
+  <td colspan=3 rowspan=2 class=apache>No Cache</td>
+  <td colspan=6 class=apachenoigbinary>APC</td>
+ </tr>
+ <tr>
+  <td></td>
+  <td colspan=3>Load Agents</td>
+  <td colspan=3 class=apachenoigbinary>-igbinary</td>
+  <td colspan=3 class=apachewithigbinary>+igbinary</td>
+ </tr>
+ <!-- Header Labels -->
+
+ <tr>
+  <td>Application</td>
+  <td>Physical</td>
+  <td>Virtual</td>
+  <td></td>
+
+<!-- IIS No Cache -->
+
+  <td>$PHP1</td>
+  <td>$PHP2</td>
+  <td>gain</td>
+
+<!-- IIS Cache -->
+  <td>$PHP1</td>
+  <td>$PHP2</td>
+  <td>gain</td>
+
+<!-- Apache No Cache -->
+  <td>$PHP1</td>
+  <td>$PHP2</td>
+  <td>gain</td>
+
+<!-- Apache Cache -igbinary -->
+  <td>$PHP1</td>
+  <td>$PHP2</td>
+  <td>gain</td>
+
+<!-- Apache Cache +igbinary -->
+  <td>$PHP1</td>
+  <td>$PHP2</td>
+   <td>gain</td>
+ </tr>
+ 
+<!-- Results -->
+"
+
+Foreach ( $app in $appnames )  {
+       ## Notes: 
$data[App_Name][Apache|IIS][cache|nocache|cachenoigbinary|cachewithigbinary][php1|php2][ver|tps8|tps16|tps32]
+
+       write-output "
+ <tr>
+  <td rowspan=3>$app</td>
+       "
+
+       Foreach ( $virt in $VIRTUAL )  {
+               $gain = ""
+               $gainclass = ""
+               if ( $virt -ne "8" )  {
+                       write-output "<tr>"
+               }
+
+               write-output "
+  <td>2</td>
+  <td>$virt</td>
+  <td>&nbsp;</td>
+
+  <!-- IIS - No Cache -->
+  <td class=iis>" $data[$app]["IIS"]["nocache"]["php1"]["tps$virt"] "</td>
+  <td class=iis>" $data[$app]["IIS"]["nocache"]["php2"]["tps$virt"] "</td>"
+
+               $gain = gaincalc 
$data[$app]["IIS"]["nocache"]["php2"]["tps$virt"] 
$data[$app]["IIS"]["nocache"]["php1"]["tps$virt"]
+               write-output "
+
+       <td class=$gainclass>&nbsp;$gain
+  </td> <!-- Gain -->
+
+  <!-- IIS - Cache -->
+  <td class=iiswincache>" $data[$app]["IIS"]["cache"]["php1"]["tps$virt"] 
"</td>
+  <td class=iiswincache>" $data[$app]["IIS"]["cache"]["php2"]["tps$virt"] 
"</td>"
+
+
+               $gain = gaincalc 
$data[$app]["IIS"]["cache"]["php2"]["tps$virt"] 
$data[$app]["IIS"]["cache"]["php1"]["tps$virt"]
+               write-output "
+
+  <td class=$gainclass>&nbsp;$gain
+  </td> <!-- Gain -->
+  
+  <!-- Apache - No Cache -->
+  <td class=apache>" $data[$app]["Apache"]["nocache"]["php1"]["tps$virt"] 
"</td>
+  <td class=apache>" $data[$app]["Apache"]["nocache"]["php2"]["tps$virt"] 
"</td>"
+  
+               $gain = gaincalc 
$data[$app]["Apache"]["nocache"]["php2"]["tps$virt"] 
$data[$app]["Apache"]["nocache"]["php1"]["tps$virt"]
+               write-output "
+
+  <td class=$gainclass>&nbsp;$gain
+  </td> <!-- Gain -->
+
+  <!-- Apache - Cache -igbinary -->
+  <td class=apachenoigbinary>" 
$data[$app]["Apache"]["cachenoigbinary"]["php1"]["tps$virt"] "</td>
+  <td class=apachenoigbinary>" 
$data[$app]["Apache"]["cachenoigbinary"]["php2"]["tps$virt"] "</td>"
+
+               $gain = gaincalc 
$data[$app]["Apache"]["cachenoigbinary"]["php2"]["tps$virt"] 
$data[$app]["Apache"]["cachenoigbinary"]["php1"]["tps$virt"]
+               write-output "
+               
+  <td class=$gainclass>&nbsp;$gain
+  </td> <!-- Gain -->
+
+  <!-- Apache - Cache +igbinary -->
+  <td class=apachewithigbinary>" 
$data[$app]["Apache"]["cachewithigbinary"]["php1"]["tps$virt"] "</td>
+  <td class=apachewithigbinary>" 
$data[$app]["Apache"]["cachewithigbinary"]["php2"]["tps$virt"] "</td>"
+  
+               $gain = gaincalc 
$data[$app]["Apache"]["cachewithigbinary"]["php2"]["tps$virt"] 
$data[$app]["Apache"]["cachewithigbinary"]["php1"]["tps$virt"]
+               write-output "
+
+  <td class=$gainclass>&nbsp;$gain
+  </td> <!-- Gain -->
+ </tr>
+               "
+       }  ## End Foreach
+}  ## End Foreach
+
+write-output "
+</table>
+<p> &nbsp; </p>
+<p> &nbsp; </p>
+"
+
+if ( $errlog -ne "" )  {
+       write-output "<strong>Error Log:</strong> <br/>"
+       write-output "$errlog"
+}
+
+write-output "
+</body>
+</html>
+"
+
+
+*/
diff --git a/src/com/mostc/pftt/model/sapi/CliSAPIInstance.java 
b/src/com/mostc/pftt/model/sapi/CliSAPIInstance.java
index 14a2555..a5ee18b 100644
--- a/src/com/mostc/pftt/model/sapi/CliSAPIInstance.java
+++ b/src/com/mostc/pftt/model/sapi/CliSAPIInstance.java
@@ -12,17 +12,21 @@ import com.mostc.pftt.model.core.PhpIni;
 import com.mostc.pftt.results.ConsoleManager;
 import com.mostc.pftt.scenario.ScenarioSet;
 import com.mostc.pftt.util.DebuggerManager;
+import com.mostc.pftt.util.DebuggerManager.Debugger;
 
 public class CliSAPIInstance extends SAPIInstance {
        protected final PhpBuild build;
        protected String ini_dir;
        protected DebuggerManager db_mgr;
+       protected Debugger dbg;
        
-       public CliSAPIInstance(ConsoleManager cm, AHost host, PhpBuild build, 
PhpIni ini) {
+       public CliSAPIInstance(ConsoleManager cm, AHost host, ScenarioSet 
scenario_set, PhpBuild build, PhpIni ini) {
                super(host, ini);
                this.build = build;
                
                db_mgr = cm.getDebuggerManager();
+               if (db_mgr!=null)
+                       dbg = db_mgr.newDebugger(cm, host, scenario_set, build);
        }
        
        @Override
@@ -68,17 +72,17 @@ public class CliSAPIInstance extends SAPIInstance {
        }
 
        public ExecOutput execute(EExecutableType exe_type, String name, String 
php_filename, String extra_args, int timeout_sec, Map<String,String> env, 
String chdir, boolean debugger_attached) throws Exception {
-               return db_mgr == null ?
+               return dbg == null ?
                                host.execOut(createPhpCommand(exe_type, 
php_filename, extra_args, debugger_attached), timeout_sec, env, chdir, true) :
-                               db_mgr.execOut(host, name, 
createPhpCommand(exe_type, php_filename, extra_args, debugger_attached), 
timeout_sec, env, chdir, true);
+                               dbg.execOut(createPhpCommand(exe_type, 
php_filename, extra_args, debugger_attached), timeout_sec, env, null, null);
        }
        
        public ExecHandle execThread(ConsoleManager cm, String name, 
ScenarioSet scenario_set, String cmd, String chdir, Map<String,String> env, 
byte[] stdin_data, boolean debugger_attached) throws Exception {
                // TODO use this with CliPhpUnitTestCaseRunner
                
-               ExecHandle eh = db_mgr == null ?
+               ExecHandle eh = dbg == null || !debugger_attached ?
                                host.execThread(cmd, env, chdir, stdin_data, 
!debugger_attached) :
-                               db_mgr.execThread(host, name, cmd, env, chdir, 
stdin_data, !debugger_attached);
+                               dbg.execThread(cmd, env, chdir, stdin_data);
                if (debugger_attached && eh instanceof LocalExecHandle) {
                        db_mgr.newDebugger(cm, host, scenario_set, name, build, 
(LocalExecHandle)eh);
                }
diff --git a/src/com/mostc/pftt/results/AbstractPhpUnitRW.java 
b/src/com/mostc/pftt/results/AbstractPhpUnitRW.java
index ae3710b..bd2e977 100644
--- a/src/com/mostc/pftt/results/AbstractPhpUnitRW.java
+++ b/src/com/mostc/pftt/results/AbstractPhpUnitRW.java
@@ -37,6 +37,5 @@ public abstract class AbstractPhpUnitRW extends 
AbstractTestResultRW {
        public boolean isTestStatus(String test_name, EPhpUnitTestStatus 
status) {
                return getTestNames(status).contains(test_name);
        }
-       public abstract String getPath();
        
 } // end public abstract class AbstractPhpUnitRW
diff --git a/src/com/mostc/pftt/results/AbstractPhptRW.java 
b/src/com/mostc/pftt/results/AbstractPhptRW.java
index 983801e..bd88604 100644
--- a/src/com/mostc/pftt/results/AbstractPhptRW.java
+++ b/src/com/mostc/pftt/results/AbstractPhptRW.java
@@ -25,7 +25,6 @@ public abstract class AbstractPhptRW extends 
AbstractTestResultRW {
        public boolean isTestStatus(String test_name, EPhptTestStatus status) {
                return getTestNames(status).contains(test_name);
        }
-       public abstract String getPath();
        
        protected void check(EPhptTestStatus status, List<String> names) {
                /*if (status==EPhptTestStatus.FAIL) {
diff --git a/src/com/mostc/pftt/results/AbstractTestResultRW.java 
b/src/com/mostc/pftt/results/AbstractTestResultRW.java
index 3fc9af9..f3b90cf 100644
--- a/src/com/mostc/pftt/results/AbstractTestResultRW.java
+++ b/src/com/mostc/pftt/results/AbstractTestResultRW.java
@@ -10,4 +10,5 @@ public abstract class AbstractTestResultRW {
        public abstract PhpBuildInfo getBuildInfo();
        public abstract void close() throws IOException;
        public abstract float passRate();
+       public abstract String getPath();
 }
diff --git a/src/com/mostc/pftt/results/AbstractUITestRW.java 
b/src/com/mostc/pftt/results/AbstractUITestRW.java
index 79c5a37..20849a6 100644
--- a/src/com/mostc/pftt/results/AbstractUITestRW.java
+++ b/src/com/mostc/pftt/results/AbstractUITestRW.java
@@ -31,6 +31,11 @@ public abstract class AbstractUITestRW extends 
AbstractTestResultRW {
                for (EUITestStatus status:EUITestStatus.values())
                        results_by_status.put(status, new 
LinkedList<UITestResult>());
        }
+       
+       @Override
+       public String getPath() {
+               return dir.getAbsolutePath();
+       }
                
        public String getWebBrowserNameAndVersion() {
                return web_browser_name_and_version;
diff --git a/src/com/mostc/pftt/results/LocalConsoleManager.java 
b/src/com/mostc/pftt/results/LocalConsoleManager.java
index 5173459..8675a0b 100644
--- a/src/com/mostc/pftt/results/LocalConsoleManager.java
+++ b/src/com/mostc/pftt/results/LocalConsoleManager.java
@@ -85,23 +85,39 @@ public class LocalConsoleManager implements ConsoleManager {
                this.debugger_name = debugger_name;
                
                if (LocalHost.getInstance().isWindows()) {
-                       if (debugger_name==null)
-                               db_mgr = isDebugAll()||isDebugList() ? new 
WinDebugManager() : null;
-                       else if (debugger_name.equalsIgnoreCase("windbg"))
+                       if (debugger_name==null) {
+                               if (isDebugAll()||isDebugList()) {
+                                       db_mgr = new WinDebugManager();
+                                       println(EPrintType.CLUE, 
"DebugManager", "Debug Using WinDbg (default)");
+                               } else {
+                                       db_mgr = null;
+                               }
+                       } else if (debugger_name.equalsIgnoreCase("windbg")) {
                                db_mgr = new WinDebugManager();
-                       else if (debugger_name.equalsIgnoreCase("ttt"))
+                               println(EPrintType.CLUE, "DebugManager", "Debug 
Using WinDbg");
+                       } else if (debugger_name.equalsIgnoreCase("ttt")) {
                                db_mgr = new TimeTravelTraceDebugManager();
-                       else
+                               println(EPrintType.CLUE, "DebugManager", "Debug 
Using Time Travel Tracing (TTT)");
+                       } else {
                                db_mgr = null;
+                       }
                } else {
-                       if (debugger_name==null)
-                               db_mgr = isDebugAll()||isDebugList() ? new 
GDBDebugManager() : null;
-                       else if (debugger_name.equalsIgnoreCase("gdb"))
+                       if (debugger_name==null) {
+                               if (isDebugAll()||isDebugList()) {
+                                       db_mgr = new GDBDebugManager();
+                                       println(EPrintType.CLUE, 
"DebugManager", "Debug Using GDB (default)");
+                               } else {
+                                       db_mgr = null;
+                               }
+                       } else if (debugger_name.equalsIgnoreCase("gdb")) {
                                db_mgr = new GDBDebugManager();
-                       else if (debugger_name.equalsIgnoreCase("valgrind"))
+                               println(EPrintType.CLUE, "DebugManager", "Debug 
Using GDB");
+                       } else if (debugger_name.equalsIgnoreCase("valgrind")) {
                                db_mgr = new ValgrindMemoryCheckManager();
-                       else
+                               println(EPrintType.CLUE, "DebugManager", "Debug 
Using Valgrind");
+                       } else {
                                db_mgr = null;
+                       }
                }
        }
        
diff --git a/src/com/mostc/pftt/results/PhptResultWriter.java 
b/src/com/mostc/pftt/results/PhptResultWriter.java
index 382a561..b065449 100644
--- a/src/com/mostc/pftt/results/PhptResultWriter.java
+++ b/src/com/mostc/pftt/results/PhptResultWriter.java
@@ -316,7 +316,7 @@ public class PhptResultWriter extends AbstractPhptRW {
                                serial.text(ext_name);
                                serial.endTag(null, "name");
                        }       
-                       reportGroups(ext.test_groups);
+                       // TODO temp reportGroups(ext.test_groups);
                        serial.endTag(null, "extension");
                }
                
diff --git a/src/com/mostc/pftt/runner/CliPhptTestCaseRunner.java 
b/src/com/mostc/pftt/runner/CliPhptTestCaseRunner.java
index f65335b..bceccd3 100644
--- a/src/com/mostc/pftt/runner/CliPhptTestCaseRunner.java
+++ b/src/com/mostc/pftt/runner/CliPhptTestCaseRunner.java
@@ -25,6 +25,7 @@ import com.mostc.pftt.model.core.PhptTestCase;
 import com.mostc.pftt.model.sapi.CliSAPIInstance;
 import com.mostc.pftt.results.ConsoleManager;
 import com.mostc.pftt.results.ITestResultReceiver;
+import com.mostc.pftt.results.PhpResultPack;
 import com.mostc.pftt.results.PhptTestResult;
 import com.mostc.pftt.runner.LocalPhptTestPackRunner.PhptThread;
 import com.mostc.pftt.runner.PhptTestPreparer.PreparedPhptTestCase;
@@ -270,17 +271,16 @@ public class CliPhptTestCaseRunner extends 
AbstractPhptTestCaseRunner2 {
                        is_timeout = true;
                }
                
+               if (running_test_handle!=null && 
running_test_handle.cleanup_notify!=null) {
+                       
running_test_handle.cleanup(((PhpResultPack)twriter).getPHPT(host, 
scenario_set, src_test_pack.getNameAndVersionString()));
+               }
                if (!is_timeout && running_test_handle != null && 
running_test_handle.isCrashed()) {
                        not_crashed = false; // @see #runTest
                        
-                       // TODO temp 
((LocalExecHandle)running_test_handle).copyTTT();
-                       
                        int exit_code = running_test_handle.getExitCode();
                        
                        twriter.addResult(host, scenario_set, src_test_pack, 
notifyNotPass(new PhptTestResult(host, EPhptTestStatus.CRASH, prep.test_case, 
"PFTT: exit_code="+exit_code+" status="+AHost.guessExitCodeStatus(host, 
exit_code)+"\n"+output_str, null, null, null, ini, env, null, stdin_post, null, 
null, null, null, output_str, null)));
-               } else if (running_test_handle!=null) {
-                       // TODO temp 
((LocalExecHandle)running_test_handle).deleteTTT();
-               }
+               } 
                
                running_test_handle = null;
                
diff --git a/src/com/mostc/pftt/runner/LocalPhptTestPackRunner.java 
b/src/com/mostc/pftt/runner/LocalPhptTestPackRunner.java
index 12743cf..51db3c7 100644
--- a/src/com/mostc/pftt/runner/LocalPhptTestPackRunner.java
+++ b/src/com/mostc/pftt/runner/LocalPhptTestPackRunner.java
@@ -47,6 +47,7 @@ import com.mostc.pftt.scenario.XDebugScenario;
 public class LocalPhptTestPackRunner extends 
AbstractLocalTestPackRunner<PhptActiveTestPack, PhptSourceTestPack, 
PhptTestCase> {
        protected final IENVINIFilter filter;
        protected final boolean xdebug;
+       protected final PhptTestPreparer preparer;
        
        public LocalPhptTestPackRunner(ConsoleManager cm, ITestResultReceiver 
twriter, ScenarioSet scenario_set, PhpBuild build, AHost storage_host, AHost 
runner_host, IENVINIFilter filter) {
                super(cm, twriter, scenario_set, build, storage_host, 
runner_host);
@@ -54,6 +55,8 @@ public class LocalPhptTestPackRunner extends 
AbstractLocalTestPackRunner<PhptAct
                
                // check once if this is using XDebug 
                xdebug = scenario_set.contains(XDebugScenario.class);
+               
+               preparer = new PhptTestPreparer(xdebug);
        }
        
        @Override
@@ -161,6 +164,8 @@ public class LocalPhptTestPackRunner extends 
AbstractLocalTestPackRunner<PhptAct
                        return null;
                }
                
+               test_case.prep = preparer.prepare(test_case, runner_host, 
active_test_pack);
+               
                return group_key;
        } // end protected TestCaseGroupKey createGroupKey
        
@@ -237,7 +242,7 @@ public class LocalPhptTestPackRunner extends 
AbstractLocalTestPackRunner<PhptAct
        
        protected void reportGroups() {
                PhptResultWriter phpt = (PhptResultWriter) 
((PhpResultPackWriter)twriter).getPHPT(runner_host, scenario_set_setup, 
src_test_pack.getNameAndVersionString());
-               phpt.reportGroups(thread_safe_groups, non_thread_safe_exts);
+               // TODO temp phpt.reportGroups(thread_safe_groups, 
non_thread_safe_exts);
        } 
        
        @Override
@@ -256,7 +261,7 @@ public class LocalPhptTestPackRunner extends 
AbstractLocalTestPackRunner<PhptAct
 
                @Override
                protected void runTest(TestCaseGroupKey group_key, PhptTestCase 
test_case, boolean debugger_attached) throws IOException, Exception, Throwable {
-                       r = sapi_scenario.createPhptTestCaseRunner(this, 
group_key, test_case, cm, twriter, runner_host, scenario_set_setup, build, 
src_test_pack, active_test_pack, xdebug, debugger_attached);
+                       r = sapi_scenario.createPhptTestCaseRunner(this, 
group_key, test_case.prep, cm, twriter, runner_host, scenario_set_setup, build, 
src_test_pack, active_test_pack, xdebug, debugger_attached);
                        twriter.notifyStart(runner_host, scenario_set_setup, 
src_test_pack, test_case);
                        r.runTest(cm, this, LocalPhptTestPackRunner.this);
                }
diff --git a/src/com/mostc/pftt/scenario/CLIScenario.java 
b/src/com/mostc/pftt/scenario/CLIScenario.java
index 9cbcab9..30c81aa 100644
--- a/src/com/mostc/pftt/scenario/CLIScenario.java
+++ b/src/com/mostc/pftt/scenario/CLIScenario.java
@@ -118,7 +118,7 @@ public class CliScenario extends SAPIScenario {
                        //      -for WEB SERVERS, have to set ENV vars on each 
web server instance
                        // @see CliPhptTestCaseRunner#prepare
                        //
-                       CliSAPIInstance sapi = new CliSAPIInstance(cm, host, 
build, ini);
+                       CliSAPIInstance sapi = new CliSAPIInstance(cm, host, 
scenario_set_setup.getScenarioSet(), build, ini);
                        
                        return new CliTestCaseGroupKey(sapi, ini, null);
                } else if (group_key!=null && 
group_key.getPhpIni().isDefault()) {
@@ -128,7 +128,7 @@ public class CliScenario extends SAPIScenario {
                        
                        filter.prepareIni(cm, ini);
                        
-                       CliSAPIInstance sapi = new CliSAPIInstance(cm, host, 
build, ini);
+                       CliSAPIInstance sapi = new CliSAPIInstance(cm, host, 
scenario_set_setup.getScenarioSet(), build, ini);
                        
                        return new CliTestCaseGroupKey(sapi, ini, null);
                }
diff --git a/src/com/mostc/pftt/util/DebuggerManager.java 
b/src/com/mostc/pftt/util/DebuggerManager.java
index a717e80..212fe71 100644
--- a/src/com/mostc/pftt/util/DebuggerManager.java
+++ b/src/com/mostc/pftt/util/DebuggerManager.java
@@ -15,6 +15,7 @@ import com.mostc.pftt.model.core.PhpBuild;
 import com.mostc.pftt.model.core.PhptTestCase;
 import com.mostc.pftt.results.ConsoleManager;
 import com.mostc.pftt.results.EPrintType;
+import com.mostc.pftt.runner.AbstractTestPackRunner.TestPackRunnerThread;
 import com.mostc.pftt.scenario.Scenario;
 import com.mostc.pftt.scenario.ScenarioSet;
 
@@ -24,7 +25,6 @@ import com.mostc.pftt.scenario.ScenarioSet;
  *
  */
 
-// XXX support for GDB on Linux
 public abstract class DebuggerManager {
        protected String src_path, debug_path;
        
@@ -200,57 +200,46 @@ public abstract class DebuggerManager {
                                int timeout_sec, Map<String, String> env, 
byte[] stdin_post,
                                Charset charset, String current_dir)
                                throws IllegalStateException, Exception {
-                       // TODO Auto-generated method stub
-                       return false;
+                       return execOut(cmd, timeout_sec, env, stdin_post, 
charset).isSuccess();
                }
                
                @Override
-               public ExecHandle execThread(String commandline) throws 
Exception {
-                       // TODO Auto-generated method stub
-                       return null;
+               public boolean exec(ConsoleManager cm, String ctx_str,
+                               String commandline, int timeout, Map<String, 
String> env,
+                               byte[] stdin, Charset charset, String chdir,
+                               TestPackRunnerThread thread, int 
thread_slow_sec)
+                               throws Exception {
+                       return execOut(commandline, timeout, env, stdin, 
charset).isSuccess();
                }
-
+               
                @Override
-               public ExecHandle execThread(String commandline, byte[] 
stdin_data)
-                               throws Exception {
-                       // TODO Auto-generated method stub
-                       return null;
+               public ExecHandle execThread(String commandline) throws 
Exception {
+                       return execThread(commandline, null, null, null);
                }
 
                @Override
-               public ExecHandle execThread(String commandline, String chdir)
-                               throws Exception {
-                       // TODO Auto-generated method stub
-                       return null;
+               public ExecHandle execThread(String commandline, byte[] 
stdin_data) throws Exception {
+                       return execThread(commandline, null, null, stdin_data);
                }
 
                @Override
-               public ExecHandle execThread(String commandline, String chdir,
-                               byte[] stdin_data) throws Exception {
-                       // TODO Auto-generated method stub
-                       return null;
+               public ExecHandle execThread(String commandline, String chdir) 
throws Exception {
+                       return execThread(commandline, null, chdir, null);
                }
 
                @Override
-               public ExecHandle execThread(String commandline,
-                               Map<String, String> env, byte[] stdin_data) 
throws Exception {
-                       // TODO Auto-generated method stub
-                       return null;
+               public ExecHandle execThread(String commandline, String chdir, 
byte[] stdin_data) throws Exception {
+                       return execThread(commandline, null, chdir, stdin_data);
                }
 
                @Override
-               public ExecHandle execThread(String commandline,
-                               Map<String, String> env, String chdir) throws 
Exception {
-                       // TODO Auto-generated method stub
-                       return null;
+               public ExecHandle execThread(String commandline, Map<String, 
String> env, byte[] stdin_data) throws Exception {
+                       return execThread(commandline, env, null, stdin_data);
                }
 
                @Override
-               public ExecHandle execThread(String commandline,
-                               Map<String, String> env, String chdir, byte[] 
stdin_data)
-                               throws Exception {
-                       // TODO Auto-generated method stub
-                       return null;
+               public ExecHandle execThread(String commandline, Map<String, 
String> env, String chdir) throws Exception {
+                       return execThread(commandline, env, chdir, null);
                }
                
                @Override
@@ -268,6 +257,11 @@ public abstract class DebuggerManager {
                        return createRunRequest(cm, 
ctx_clazz==null?null:ctx_clazz.getSimpleName());
                }
                
+       } // end public static abstract class Debugger
+
+       public Debugger newDebugger(ConsoleManager cm, AHost host,
+                       ScenarioSet scenario_set, PhpBuild build) {
+               return null;
        }
        
 } // end public abstract class DebuggerManager
diff --git a/src/com/mostc/pftt/util/GDBDebugManager.java 
b/src/com/mostc/pftt/util/GDBDebugManager.java
index 111a3c9..b8c833a 100644
--- a/src/com/mostc/pftt/util/GDBDebugManager.java
+++ b/src/com/mostc/pftt/util/GDBDebugManager.java
@@ -67,6 +67,14 @@ public class GDBDebugManager extends DebuggerManager {
                        // TODO Auto-generated method stub
                        return false;
                }
+
+               @Override
+               public ExecHandle execThread(String commandline,
+                               Map<String, String> env, String chdir, byte[] 
stdin_data)
+                               throws Exception {
+                       // TODO Auto-generated method stub
+                       return null;
+               }
                
        }
 
diff --git a/src/com/mostc/pftt/util/TimeTravelTraceDebugManager.java 
b/src/com/mostc/pftt/util/TimeTravelTraceDebugManager.java
new file mode 100644
index 0000000..3b78f10
--- /dev/null
+++ b/src/com/mostc/pftt/util/TimeTravelTraceDebugManager.java
@@ -0,0 +1,179 @@
+package com.mostc.pftt.util;
+
+import java.nio.charset.Charset;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.mostc.pftt.host.AHost;
+import com.mostc.pftt.host.AHost.ExecHandle;
+import com.mostc.pftt.host.AHost.IExecHandleCleanupNotify;
+import com.mostc.pftt.host.ExecOutput;
+import com.mostc.pftt.host.Host;
+import com.mostc.pftt.host.ICrashDetector;
+import com.mostc.pftt.model.core.PhpBuild;
+import com.mostc.pftt.results.AbstractTestResultRW;
+import com.mostc.pftt.results.ConsoleManager;
+import com.mostc.pftt.results.EPrintType;
+import com.mostc.pftt.scenario.ScenarioSet;
+
+/** Handles integration with Time-Travel-Tracing, a debugging feature on 
Windows. 
+ * 
+ * For info on how to use a TTT trace (PFTT takes care of recording them for 
you)
+ * @see http://sharepoint/sites/cse/tttwiki/ (internal Microsoft only :()
+ *
+ */
+
+public class TimeTravelTraceDebugManager extends WindowsDebuggerToolsManager {
+
+       @Override
+       public Debugger newDebugger(ConsoleManager cm, AHost host, ScenarioSet 
scenario_set, Object server_name, PhpBuild build, int process_id, ExecHandle 
process) {
+               return null;
+       }
+       
+       protected boolean displayed_tips = false;
+       protected void displayTips(ConsoleManager cm) {
+               if (cm==null)
+                       return;
+               if (displayed_tips)
+                       return;
+               displayed_tips = true;
+               cm.println(EPrintType.CLUE, getClass(), "PFTT is recording 
Time-Travel Traces of crashed processes in Result-Pack");
+               cm.println(EPrintType.CLUE, getClass(), "");
+               cm.println(EPrintType.CLUE, getClass(), "Common TTT Replay 
commands");
+               cm.println(EPrintType.CLUE, getClass(), "");
+               cm.println(EPrintType.CLUE, getClass(), "Open TTT in WinDebug 
using File > Open Crash Dump");
+               cm.println(EPrintType.CLUE, getClass(), "!idna.events - shows 
event timeline including thread creation");
+               cm.println(EPrintType.CLUE, getClass(), "gu - run selected 
thread until unhandled exception");
+               cm.println(EPrintType.CLUE, getClass(), "k - stack trace");
+               cm.println(EPrintType.CLUE, getClass(), "");
+               cm.println(EPrintType.CLUE, getClass(), "Problems loading idna 
DLL (WinDebug extension) into WinDebug:");
+               cm.println(EPrintType.CLUE, getClass(), "1. copy C:\\Program 
Files\\Debugging Tools to c:\\debuggers (windbg commands can't have ` ` or 
\")");
+               cm.println(EPrintType.CLUE, getClass(), "2. .extpath + 
c:\\debuggers\\TTT");
+               cm.println(EPrintType.CLUE, getClass(), "3. 
`!c:\\debuggers\\TTT\\idna` instead of `!idna`");
+               cm.println(EPrintType.CLUE, getClass(), "fe 
!c:\\debuggers\\TTT\\idna.position");
+       }
+       
+       public Debugger newDebugger(ConsoleManager cm, AHost host, ScenarioSet 
scenario_set, PhpBuild build) {
+               String ttt_exe = null;
+               for ( String path : getToolPaths(host, build, 
"TTT\\TTTracer.exe") ) {
+                       if (host.exists(path)) {
+                               ttt_exe = path;
+                               break;
+                       }
+               }
+               if (ttt_exe==null)
+                       return null;
+               
+               displayTips(cm);
+               return new TTTDebugger(host, ttt_exe);
+       }
+       
+       protected class TTTDebugger extends Debugger {
+               protected final AHost host;
+               // c:\\Program Files (x86)\\Debugging Tools for Windows 
(x86)\\TTT\\TTTracer.exe
+               protected final String ttt_exe;
+               
+               protected TTTDebugger(AHost host, String ttt_exe) {
+                       this.host = host;
+                       this.ttt_exe = ttt_exe;
+               }
+
+               @Override
+               public ExecOutput execOut(String cmd, int timeout_sec,
+                               Map<String, String> object, byte[] stdin_post, 
Charset charset)
+                               throws IllegalStateException, Exception {
+                       final String ttt_file = 
createTTTFilenameFromCommand(host, cmd);
+                       ExecOutput eo = host.execOut(cmd, timeout_sec, object, 
stdin_post, charset);
+                       handleCleanup(ttt_file, eo, null);
+                       return eo;
+               }
+
+               @Override
+               public RunRequest createRunRequest(ConsoleManager cm, String 
ctx_str) {
+                       return host.createRunRequest(cm, ctx_str);
+               }
+
+               @Override
+               public ExecOutput execOut(RunRequest req) {
+                       final String ttt_file = 
createTTTFilenameFromCommand(host, req.getCommandline());
+                       ExecOutput eo = host.execOut(req);
+                       handleCleanup(ttt_file, eo, null);
+                       return eo;
+               }
+
+               @Override
+               public ExecHandle execThread(RunRequest req) {
+                       final String ttt_file = 
createTTTFilenameFromCommand(host, req.getCommandline());
+                       return handleThread(ttt_file, host.execThread(req));
+               }
+
+               @Override
+               public void close(ConsoleManager cm) {
+                       
+               }
+
+               @Override
+               public boolean isRunning() {
+                       return host.isOpen();
+               }
+
+               @Override
+               public ExecHandle execThread(String commandline,
+                               Map<String, String> env, String chdir, byte[] 
stdin_data)
+                               throws Exception {
+                       final String ttt_file = 
createTTTFilenameFromCommand(host, commandline);
+                       return handleThread(ttt_file, 
host.execThread(wrapCommand(ttt_exe, ttt_file, commandline), env, chdir, 
stdin_data));
+               }
+               
+               protected ExecHandle handleThread(final String ttt_file, 
ExecHandle eh) {
+                       eh.cleanup_notify = new IExecHandleCleanupNotify() {
+                                       @Override
+                                       public void cleanupNotify(ExecHandle 
eh, AbstractTestResultRW rw) {
+                                               handleCleanup(ttt_file, eh, rw);
+                                       }
+                               };
+                       return eh;
+               }
+               
+               protected void handleCleanup(String ttt_file, ICrashDetector 
eh, AbstractTestResultRW rw) {
+                       if (eh.isCrashed()) {
+                               if (rw!=null) {
+                                       try {
+                                               // move TTT file to result-pack 
(we want it, its a crash)
+                                               // and rename it to .run
+                                               host.move(ttt_file, 
rw.getPath()+"/"+Host.basename(ttt_file)+".run");
+                                       } catch ( Exception ex ) {
+                                               ex.printStackTrace();
+                                       }
+                               }
+                       } else {
+                               try {
+                                       host.delete(ttt_file);
+                               } catch ( Exception ex ) {}
+                       }
+               }
+               
+       } // end protected class TTTDebugger
+       
+       AtomicInteger i = new AtomicInteger(0);
+       protected String createTTTFilenameFromCommand(AHost host, String cmd) {
+               if (cmd.startsWith("\"")) {
+                       int i = cmd.indexOf('"', 1);
+                       cmd = cmd.substring(1, i);
+               } else {
+                       int i = cmd.indexOf(' ');
+                       cmd = cmd.substring(1, i);
+               }
+               String name = Host.basename(cmd);
+               
+               
+               return host.getTempDir()+"\\"+name+i.incrementAndGet()+".run";
+       }
+       
+       protected String wrapCommand(String ttt_exe, String ttt_file, String 
cmd) {
+               // passThroughExit => critical to detect crash
+               // launch => critical to actually run the process
+               return "\""+ttt_exe+"\" -passThroughExit -noUI -timer 60 
-autoStart -saveCrash "+ttt_file+" -launch \"" + cmd +"\"";
+       }
+       
+} // end public class TimeTravelTraceDebugManager
diff --git a/src/com/mostc/pftt/util/TimerUtil.java 
b/src/com/mostc/pftt/util/TimerUtil.java
index c5d4c5c..c9675a3 100644
--- a/src/com/mostc/pftt/util/TimerUtil.java
+++ b/src/com/mostc/pftt/util/TimerUtil.java
@@ -73,8 +73,8 @@ public final class TimerUtil {
         * many handles, it will wait a long time before allocating more, which 
delays thread creation
         * (which in turn can delay things like killing off timed out 
processes, which in turn frees up handles).
         * 
-        * Instead, a bunch of threads are preallocated in a pool at startup. 
This gets one of
-        * those threads and has it run the given Runnable.
+        * Instead, some threads are preallocated in a pool at startup to help 
ensure that a thread
+        * can be created when its needed.
         * 
         * Note: you may not call #start or #setDaemon on the returned Thread. 
you will get an IllegalThreadStateException if you do.
         * 
diff --git a/src/com/mostc/pftt/util/ValgrindMemoryCheckManager.java 
b/src/com/mostc/pftt/util/ValgrindMemoryCheckManager.java
index be952bf..ee1281a 100644
--- a/src/com/mostc/pftt/util/ValgrindMemoryCheckManager.java
+++ b/src/com/mostc/pftt/util/ValgrindMemoryCheckManager.java
@@ -101,6 +101,14 @@ public class ValgrindMemoryCheckManager extends 
DebuggerManager {
                        
                        return null; // TODO temp
                }
+
+               @Override
+               public ExecHandle execThread(String commandline,
+                               Map<String, String> env, String chdir, byte[] 
stdin_data)
+                               throws Exception {
+                       // TODO Auto-generated method stub
+                       return null;
+               }
                
        }
 
diff --git a/src/com/mostc/pftt/util/WinDebugManager.java 
b/src/com/mostc/pftt/util/WinDebugManager.java
index a8fb35c..d95b2b1 100644
--- a/src/com/mostc/pftt/util/WinDebugManager.java
+++ b/src/com/mostc/pftt/util/WinDebugManager.java
@@ -31,7 +31,7 @@ import com.mostc.pftt.scenario.ScenarioSet;
  *
  */
 
-public class WinDebugManager extends DebuggerManager {
+public class WinDebugManager extends WindowsDebuggerToolsManager {
        private String win_dbg_exe;
        private AHost win_dbg_host;
        private boolean displayed_windbg_tips = false;
@@ -76,9 +76,10 @@ public class WinDebugManager extends DebuggerManager {
        
        protected void displayWindebugTips(ConsoleManager cm) {
                cm.println(EPrintType.TIP, getClass(), "  WinDebug Command 
Referrence: http://www.windbg.info/doc/1-common-cmds.html";);
-               cm.println(EPrintType.TIP, getClass(), "  WinDebug command: k   
    - show callstack");
-               cm.println(EPrintType.TIP, getClass(), "  WinDebug command: g   
    - go (until next exception)");
-               cm.println(EPrintType.TIP, getClass(), "  WinDebug command: 
<F9>    - set breakpoint");
+               cm.println(EPrintType.TIP, getClass(), "  WinDebug command: k   
                    - show callstack");
+               cm.println(EPrintType.TIP, getClass(), "  WinDebug command: g   
                    - go (until next exception)");
+               cm.println(EPrintType.TIP, getClass(), "  WinDebug command: 
.dump /ma <filename>    - create coredump file");
+               cm.println(EPrintType.TIP, getClass(), "  WinDebug command: 
<F9>                    - set breakpoint");
        }
 
        public static class WinDebug extends Debugger {
@@ -203,6 +204,14 @@ public class WinDebugManager extends DebuggerManager {
                        // TODO Auto-generated method stub
                        return null;
                }
+
+               @Override
+               public ExecHandle execThread(String commandline,
+                               Map<String, String> env, String chdir, byte[] 
stdin_data)
+                               throws Exception {
+                       // TODO Auto-generated method stub
+                       return null;
+               }
                
        } // end public static class WinDebug
        
@@ -215,24 +224,9 @@ public class WinDebugManager extends DebuggerManager {
         * @return
         */
        public static String[] getWinDebugPaths(Host host, PhpBuild build) {
-               // use x86 windebug for x86 builds and x64 windebug edition for 
x64 builds!
-               // (can debug with different windebug editions, but WER popup 
requires that the architectures match)
-               // @see HostEnvUtil
-               if (build.isX86()) {
-                       // 
-                       return new String[] {
-                                       host.getSystemDrive()+"\\Program Files 
(x86)\\Debugging Tools for Windows\\WinDbg.exe",
-                                       host.getSystemDrive()+"\\Program Files 
(x86)\\Debugging Tools for Windows (x86)\\WinDbg.exe"
-                               };
-               } else {
-                       return new String[] {
-                                       host.getSystemDrive()+"\\Program 
Files\\Debugging Tools for Windows (x64)\\WinDbg.exe",
-                                       host.getSystemDrive()+"\\Program 
Files\\Debugging Tools for Windows\\WinDbg.exe",
-                                       host.getSystemDrive()+"\\Program 
Files\\Debugging Tools for Windows (x86)\\WinDbg.exe"
-                               };
-               }
+               return getToolPaths(host, build, "windbg.exe");
        }
-
+       
        /** returns the path that WinDebug is installed at, or returns null if 
windebug is not found.
         * 
         * @see #getWinDebugPaths
diff --git a/src/com/mostc/pftt/util/WindowsDebuggerToolsManager.java 
b/src/com/mostc/pftt/util/WindowsDebuggerToolsManager.java
new file mode 100644
index 0000000..844a3f7
--- /dev/null
+++ b/src/com/mostc/pftt/util/WindowsDebuggerToolsManager.java
@@ -0,0 +1,27 @@
+package com.mostc.pftt.util;
+
+import com.mostc.pftt.host.Host;
+import com.mostc.pftt.model.core.PhpBuild;
+
+public abstract class WindowsDebuggerToolsManager extends DebuggerManager {
+       
+       public static String[] getToolPaths(Host host, PhpBuild build, String 
exe_file) {
+               // use x86 windebug for x86 builds and x64 windebug edition for 
x64 builds!
+               // (can debug with different windebug editions, but WER popup 
requires that the architectures match)
+               // @see HostEnvUtil
+               if (build.isX86()) {
+                       // 
+                       return new String[] {
+                                       host.getSystemDrive()+"\\Program Files 
(x86)\\Debugging Tools for Windows\\"+exe_file,
+                                       host.getSystemDrive()+"\\Program Files 
(x86)\\Debugging Tools for Windows (x86)\\"+exe_file
+                               };
+               } else {
+                       return new String[] {
+                                       host.getSystemDrive()+"\\Program 
Files\\Debugging Tools for Windows (x64)\\"+exe_file,
+                                       host.getSystemDrive()+"\\Program 
Files\\Debugging Tools for Windows\\"+exe_file,
+                                       host.getSystemDrive()+"\\Program 
Files\\Debugging Tools for Windows (x86)\\"+exe_file
+                               };
+               }
+       }
+       
+}

Reply via email to