Commit:    156640260ed9029d9e185f3f2f6a2d511158a41c
Author:    matt <matt@mattfickenlaptop.local>         Mon, 5 Mar 2012 16:22:32 
-0800
Parents:   53f7b2745bbd67b3c41b495fb9f2caf4559c28a9
Branches:  master

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

Log:
updating with recent changes


Former-commit-id: 096834badedd11b759feba673808ff2080328c5a

Changed paths:
  A  PFTT/lib/AbstractHostList.groovy
  A  PFTT/lib/Build.groovy
  A  PFTT/lib/Command.groovy
  A  PFTT/lib/Host.groovy
  A  PFTT/lib/HostList.groovy
  A  PFTT/lib/HostsManager.groovy
  A  PFTT/lib/LocalHost.groovy
  A  PFTT/lib/Middleware.groovy
  A  PFTT/lib/RemoteHost.groovy
  A  PFTT/lib/SSHHost.groovy
  A  PFTT/lib/Scenario.groovy
  A  PFTT/lib/base.groovy
  A  PFTT/lib/case_runner.groovy

diff --git a/PFTT/lib/AbstractHostList.groovy b/PFTT/lib/AbstractHostList.groovy
new file mode 100644
index 0000000..b89b54b
--- /dev/null
+++ b/PFTT/lib/AbstractHostList.groovy
@@ -0,0 +1,557 @@
+package com.mostc.pftt
+
+import groovy.lang.Closure;
+
+import java.util.ArrayList;
+
+
+abstract class AbstractHostList extends ArrayList {
+       
+//     def size(value=null) {
+//     //length() returns number of hosts
+//     // //length(value) returns number of hosts that returned given value
+//     case value.null?
+//     when true
+//     super
+//     when false
+//     count_values(value)
+//     end
+//     }
+
+//     def count_values(value) {
+//             // count number of hosts that returned given value
+//             c = 0
+//             each { k, v ->
+//             if eq_value(v, value)
+//             c += 1
+//             end
+//             end
+//             c
+//     }
+
+//     def keys(value=null) {
+//     // //keys() returns an array of hosts
+//     // //keys(value) returns a Host::Array of hosts that returned given 
value
+//     case value.null?
+//     when true
+//     super
+//     when false
+//     slice_value(value)
+//     end
+//     }
+
+//     def slice_value(value) {
+//     // returns a Host::Array of hosts that returned given value
+//     ret = Host::Array.new
+//     each do |k, v|
+//     if eq_value(v, value)
+//     ret.push(k)
+//     end
+//     end
+//     ret
+//     }
+
+       def eq_value(a, b) {
+       a == b
+       }
+               
+       def which(cmd, ctx=null) {
+               each_ret(cmd) { host, sub_cmd ->
+                       host.which(sub_cmd, ctx)
+               }
+       }
+
+       def hasCmd(cmd, ctx=null) {
+               each_ret(cmd) { host, sub_cmd ->
+                       host.hasCmd(sub_cmd, ctx)
+               }
+       }
+
+       def exec_pw(posix_cmd, windows_cmd, ctx, opts={}) {
+               each_ret { host ->
+                       if (host.isPosix(ctx))
+                               host.exec(sub_cmd(host, posix_cmd), ctx, opts)
+                       else
+                               host.exec(sub_cmd(host, windows_cmd), ctx, opts)
+               }
+       }
+       
+       def exec_ok(cmd, ctx, opts={}) {
+               each_ret(cmd) { host, sub_cmd ->
+                       host.exec_ok(sub_cmd, ctx, opts)
+               }
+       }
+       
+       def exec_pw_ok(posix_cmd, windows_cmd, ctx, opts={}) {
+               each_ret { host ->
+                       if (host.isPosix(ctx))
+                               host.exec_ok(sub_cmd(host, posix_cmd), ctx, 
opts)
+                       else
+                               host.exec_ok(sub_cmd(host, windows_cmd), ctx, 
opts)
+               }
+       }
+       
+       def cmd_pw(posix_cmd, windows_cmd, ctx, opts={}) {
+               each_thread { host ->
+                       if (host.isPosix(ctx))
+                               host.cmd(sub_cmd(host, posix_cmd), ctx, opts)
+                       else
+                               host.cmd(sub_cmd(host, windows_cmd), ctx, opts)
+               }
+       }
+       
+       def exec(command, ctx, opts={}) {
+               each_ret(command) { host, sub_cmd ->
+                       host.exec(sub_cmd, ctx, opts)
+               }
+       }
+       
+       def cmd(cmdline, ctx) {
+               each_ret(cmdline) { host, sub_cmdline ->
+                       host.cmd(sub_cmdline, ctx)
+               }
+       }
+       
+       def line(cmdline, ctx) {
+               each_ret(cmdline) { host, sub_cmdline ->
+                       host.line(sub_cmdline, ctx)
+               }
+       }
+       
+       def isPosix(ctx=null) {
+               each_ret { host ->
+                       host.isPosix(ctx)
+               }
+       }
+       
+       def isWindows(ctx=null) {
+               each_ret { host ->
+                       host.isWindows(ctx)
+               }
+       }
+       
+       def isLonghorn(ctx=null) {
+               each_ret { host ->
+                       host.isLonghorn(ctx)
+               }
+       }
+       
+       def isBSD(ctx=null) {
+               each_ret { host ->
+                       host.isBSD(ctx)
+               }
+       }
+       
+       def isFreeBSD(ctx=null) {
+               each_ret { host ->
+                       host.isFreeBSD(ctx)
+               }
+       }
+       
+       def isLinux(ctx=null) {
+               each_ret { host ->
+                       host.isLinux(ctx)
+               }
+       }
+       
+       def isRedhat(ctx=null) {
+               each_ret { host ->
+                       host.isRedhat(ctx)
+               }
+       }
+       
+       def isFedora(ctx=null) {
+               each_ret { host ->
+                       host.isFedora(ctx)
+               }
+       }
+       
+       def isDebian(ctx=null) {
+               each_ret { host ->
+                       host.isDebian(ctx)
+               }
+       }
+       
+       def isUbuntu(ctx=null) {
+               each_ret { host ->
+                       host.isUbuntu(ctx)
+               }
+       }
+       
+       def isGentoo(ctx=null) {
+               each_ret { host ->
+                       host.isGentoo(ctx)
+               }
+       }
+       
+       def isSUSE(ctx=null) {
+               each_ret { host ->
+                       host.isSUSE(ctx)
+               }
+       }
+       
+       def isSolaris(ctx=null) {
+               each_ret { host ->
+                       host.isSolaris(ctx)
+               }
+       }
+       
+       //    def utype? ctx=null
+       //      each_ret do |host|
+       //        host.utype?(ctx)
+       //      end
+       //    end
+       //
+       //    def windows_and_utype ctx=null
+       //      // TODO
+       //    end
+       
+       def unquote_line(cmdline, ctx) {
+               each_ret(cmdline) { host, sub_cmdline ->
+                       host.unquote_line(sub_cmdline, ctx)
+               }
+       }
+       
+       def line_prefix(prefix, cmd, ctx) {
+               each_ret(cmd) { host, sub_cmd ->
+                       host.line_prefix(prefix, sub_cmd, ctx)
+               }
+       }
+               
+       def reboot(ctx=null) {
+               each_ret { host ->
+                       host.reboot(ctx)
+               }
+       }
+       
+       def reboot_wait(seconds, ctx=null) {
+               each_ret { host ->
+                       host.reboot_wait(seconds, ctx)
+               }
+       }
+       
+       def isRebooting(ctx=null) {
+               each_ret { host ->
+                       host.rebooting(ctx)
+               }
+       }
+       
+       def env_values(ctx=null) {
+               each_ret { host ->
+                       host.env_values(ctx)
+               }
+       }
+       
+       def env_value(name, ctx=null) {
+               each_ret { host ->
+                       host.env_value(name, ctx)
+               }
+       }
+       
+       def name(ctx=null) {
+               each_ret { host ->
+                       host.name(ctx)
+               }
+       }
+       
+       def mkdir(path, ctx) {
+               each_ret(path) { host, sub_path ->
+                       host.mkdir(sub_path, ctx)
+               }
+       }
+       
+       def mktmpdir(ctx, path=null, suffix='') {
+               each_ret(path) { host, sub_path ->
+                       host.mktmpdir(ctx, sub_path, suffix)
+               }
+       }
+       
+       def mktmpfile(suffix, ctx, content=null) {
+               each_ret { host ->
+                       host.mktmpfile(suffix, ctx, content)
+               }
+       }
+       
+       //    alias :mktempfile :mktmpfile
+       //    alias :mktempdir :mktmpdir
+       
+       def delete_if(path, ctx) {
+               each_ret(path) { host, sub_path ->
+                       host.delete_if(sub_path, ctx)
+               }
+       }
+       
+       def upload(from, to, ctx, opts={}) {
+               each_ret(to) { host, sub_to ->
+                       host.upload(from, sub_to, ctx, opts)
+               }
+       }
+       
+       def download(from, to, ctx) {
+               each_ret(to) { host, sub_to ->
+                       host.download(from, sub_to, ctx)
+               }
+       }
+       
+       def upload_force(local, remote, ctx, opts={}) {
+               each_ret(remote) { host, sub_remote ->
+                       host.upload_force(local, sub_remote, ctx, opts)
+               }
+       }
+       
+       def upload_if_not(local, remote, ctx) {
+               each_ret(remote) { host, sub_remote ->
+                       host.upload_if_not(local, sub_remote, ctx)
+               }
+       }
+       
+       def glob(path, spec, ctx) {
+               each_ret(path) { host, sub_path ->
+                       host.glob(sub_path, spec, ctx)
+               }
+       }
+       
+       def list(path, ctx) {
+               each_ret(path) { host, sub_path ->
+                       host.list(sub_path, ctx)
+               }
+       }
+       
+       def mtime(file, ctx=null) {
+               each_ret(file) { host, sub_file ->
+                       host.mtime(sub_file, ctx)
+               }
+       }
+       
+       def write(string, path, ctx) {
+               // TODO
+               each_ret(path) { host, sub_path ->
+                       host.write(string, sub_path, ctx)
+               }
+       }
+       
+       def read(path, ctx) {
+               each_ret(path) { host, sub_path ->
+                       host.read(sub_path, ctx)
+               }
+       }
+       
+       def cwd(ctx=null) {
+               each_ret { host ->
+                       host.cwd(ctx)
+               }
+       }
+       
+       def cd(path, hsh, ctx=null) {
+               // TODO
+               each_ret(path) { host, sub_path ->
+                       host.cd(sub_path, hsh, ctx)
+               }
+       }
+       
+       def directory(path, ctx=null) {
+               each_ret(path) { host, sub_path ->
+                       host.directory(sub_path, ctx)
+               }
+       }
+       
+       def open_file(path, flags='r', ctx=null, Closure block=null) {
+               each_ret(path) { host, sub_path ->
+                       host.open_file(sub_path, flags, ctx, block)
+               }
+       }
+       
+       def isRemote() {
+               each_ret { host ->
+                       host.isRemote()
+               }
+       }
+       
+       def delete(path, ctx=null) {
+               // TODO
+               each_ret(path) { host, sub_path ->
+                       host.delete(sub_path, ctx)
+               }
+       }
+       
+       def exist(path, ctx=null) {
+               each_ret(path) { host, sub_path ->
+                       host.exist(sub_path, ctx)
+               }
+       }
+       
+       //    alias :exist :exist?
+       //    alias :exists :exist?
+       //    alias :exists? :exist?
+       
+       def copy(from, to, ctx, opts={}) {
+               each_ret(to) { host, sub_to ->
+                       host.copy(from, sub_to, ctx, opts)
+               }
+       }
+       
+       def move(from, to, ctx) {
+               each_ret(to) { host, sub_to ->
+                       host.move(from, sub_to, ctx)
+               }
+       }
+       
+       def time(ctx=null) {
+               each_ret { host ->
+                       host.time(ctx)
+               }
+       }
+       
+       def setTime(time, ctx=null) {
+//     // TODO
+//     ret = VSA.new
+//     each_thread(ret) do |host|
+//     ret[host] = host.time = time
+//     end
+//     ret
+       }
+       
+       def shell(ctx=null) {
+               each_ret { host ->
+                       host.shell(ctx)
+               }
+       }
+       
+       def systeminfo(ctx=null) {
+               each_ret { host ->
+                       host.systeminfo(ctx)
+               }
+       }
+       
+       def processor(ctx=null) {
+               each_ret { host ->
+                       host.processor(ctx)
+               }
+       }
+       
+       def isX86(ctx=null) {
+               each_ret { host ->
+                       host.isX86(ctx)
+               }
+       }
+       
+       def isX64(ctx=null) {
+               each_ret { host ->
+                       host.isX64(ctx)
+               }
+       }
+       
+       def isARM(ctx=null) {
+               each_ret { host ->
+                       host.isARM(ctx)
+               }
+       }
+       
+       def number_of_processors(ctx=null) {
+               each_ret { host ->
+                       host.number_of_processors(ctx)
+               }
+       }
+       
+       def systemroot(ctx=null) {
+               each_ret { host ->
+                       host.systemroot(ctx)
+               }
+       }
+       
+       def systemdrive(ctx=null) {
+               each_ret { host ->
+                       host.systemdrive(ctx)
+               }
+       }
+       
+       def desktop(ctx=null) {
+               each_ret { host ->
+                       host.desktop(ctx)
+               }
+       }
+       
+       def userprofile(ctx=null) {
+               each_ret { host ->
+                       host.userprofile(ctx)
+               }
+       }
+       
+       def appdata(ctx=null) {
+               each_ret { host ->
+                       host.appdata(ctx)
+               }
+       }
+       
+       def appdata_local(ctx=null) {
+               each_ret { host ->
+                       host.appdata_local(ctx)
+               }
+       }
+       
+       def tempdir(ctx) {
+               each_ret { host ->
+                       host.tempdir(ctx)
+               }
+       }
+       
+       //    alias :tmpdir :tempdir
+       
+       def osname(ctx=null) {
+               each_ret { host ->
+                       host.osname(ctx)
+               }
+       }
+       
+       //    alias :os_name osname
+       
+       def hasDebugger(ctx) {
+               each_ret { host ->
+                       host.hasDebugger(ctx)
+               }
+       }
+       
+       def on_error(host, ex) {
+       }
+       
+       def sub_cmd(host, cmd) {
+               // important that 'host' variable is named 'host'
+               eval("$cmd")
+       }
+       
+       def each_ret(path=null) {// TODO , &block) {
+//     ret = VSA.new
+//     each_thread do |host|
+//     ret[host] = block.call(host, path.null? ? null : sub_cmd(host, path))
+//     end
+//     ret
+       }
+       
+       protected def each_thread(ret=null) {// TODO , &block) {
+//     tq = @thread_manager.new_queue
+//
+//     this.each do |host|
+//     // TODO coordinate with the threads in exec! system
+//     //
+//     // TODO @thread_queue
+//     tq.add_task do
+//     begin
+//     block.call(host)
+//     rescue
+//     if !ret.null? and !ret.has_key?(host)
+//     // caller is supposed to return true|false for host
+//     // to indicate success|failure
+//     //
+//     // make sure this gets set to false (since this failed)
+//     ret[host] = false
+//     end
+//     on_error(host, $!)
+//     end // begin
+//     end // add_task
+//
+//     end // each
+//
+//     tq.execute
+       } // def each_thread
+       
+} // class AbstractHostList
diff --git a/PFTT/lib/Build.groovy b/PFTT/lib/Build.groovy
new file mode 100644
index 0000000..34a6c08
--- /dev/null
+++ b/PFTT/lib/Build.groovy
@@ -0,0 +1,5 @@
+package com.mostc.pftt
+
+abstract class Build {
+
+}
diff --git a/PFTT/lib/Command.groovy b/PFTT/lib/Command.groovy
new file mode 100644
index 0000000..7ea4d42
--- /dev/null
+++ b/PFTT/lib/Command.groovy
@@ -0,0 +1,105 @@
+package com.mostc.pftt
+
+import com.mostc.pftt.util.StringUtil
+
+/** Handles generation and manipulation of command line commands without ugly 
String parsing.
+ * 
+ * Also allows custom evaluation of the result (success|failure, etc....) of 
command execution.
+ * 
+ * 
+ */
+
+class Command {
+       public static final int SUCCESS = 0
+       String program, args=[], arg_sep=[], arg2_sep=[], args_args2_sep=[], 
args2=[]
+       /** the output expected in the STDOUT and STDERR streams from the 
program, for
+        * the run to be considered successful.
+        * 
+        * @see #isSuccess
+        * 
+        * if an empty string then STDOUT|STDERR is expected to be empty
+        * if null, then run is successful regardless of STDOUT|STDERR being 
empty or not empty
+        */
+       String expect_stdout, expect_stderr
+       int expect_exit_code = SUCCESS
+       def exe_opts
+       
+       def ExpectedCommand(String program=null) {
+               this.program = program
+       }
+       
+       /** decides if Actual run of the command was successful.
+        * 
+        * by default, this is based on the Actual command matching the 
expected STDOUT, STDERR and exit code.
+        * 
+        * this may be overridden to evaluate success differently.
+        * 
+        * @param actual
+        * @return
+        */
+       def isSuccess(actual) {
+               actual.cmd_str == toString() && 
(expect_stdout==null||expect_stdout == actual.stdout) && 
(expect_stderr==null||expect_stderr == actual.stderr) && expect_exit_code == 
actual.exit_code
+       }
+       
+       /** creates an Actual instance to store information about an instance 
of a run of this Command.
+        * 
+        * @param cmd_line
+        * @param stdout
+        * @param stderr
+        * @param exit_code
+        * @return
+        */
+       def createActual(String cmd_line, String stdout, String stderr, int 
exit_code) {
+               new Actual(cmd_line, stdout, stderr, exit_code)
+       }
+       
+       public class Actual {
+               String cmd_line, stdout, stderr
+               int exit_code
+               
+               def Actual(String cmd_line, String stdout, String stderr, int 
exit_code) {
+                       this.cmd_line = cmd_line
+                       this.stdout = stdout
+                       this.stderr = stderr
+                       this.exit_code = exit_code
+               }
+               
+               def isCmdNotFound() {
+                       return ( stderr.contains('bash:') && exit_code == 127 )
+               }
+               
+               def isSuccess() {
+                       Command.this.isSuccess(Actual.this)
+               }
+               
+               @Override
+               String toString() {
+                       cmd_line
+               }
+       }
+       
+       @Override
+       String toString() {
+               def str = program
+               if (!args.isEmpty()) {
+                       str += ' '
+                       str += args_str
+               }
+               if (!args2.isEmpty()) {
+                       str += arg2_sep
+                       str += args2_str
+               }
+               return str
+       }
+         
+       def args_str() {
+               StringUtil.flatten(args)
+               StringUtil.join(arg_sep, args)
+       }
+         
+       def args2_str() {
+               StringUtil.flatten(args2)
+               StringUtil.join(arg2_sep, args2)
+       }
+               
+} // end class Command
diff --git a/PFTT/lib/Host.groovy b/PFTT/lib/Host.groovy
new file mode 100644
index 0000000..be33246
--- /dev/null
+++ b/PFTT/lib/Host.groovy
@@ -0,0 +1,1695 @@
+package com.mostc.pftt
+
+/** Host handles file system, system info and program execution for local and 
remote hosts abstracting
+ * and automatically handling differences between operating systems. 
+
+1. use #systemdrive in place of C:
+
+ Windows 2003r2 and above will use a different drive letter (not C)
+ if its not installed on the first *FAT or NTFS Partition (first partition 
with type set to
+ FAT or NTFS during installation process).
+
+ host.exec_pw!("/usr/bin/unzip", "$host.systemdrive/php-sdk/bin/unzip")
+ host.exec_pw!("/usr/bin/p7zip", "$host.programfiles/7-Zip/7zFM")
+ host.exec_pw!("/usr/bin/p7zip", "$host.programfiles86/7-Zip/7zFM")
+
+2. Multiple hosts variable substitution
+
+ when using host.systemdrive, host.programfiles, host.tempdir, etc... with 
Hosts::Array, pass in using ' ' not " "
+ and name the variable 'host'. then it will be automatically substituted for 
each host.
+
+ hosts.exec_pw!("/usr/bin/unzip", '$host.systemdrive/php-sdk/bin/unzip')
+ hosts.exec_pw!("/usr/bin/p7zip", '$host.programfiles/7-Zip/7zFM')
+ hosts.exec_pw!("/usr/bin/p7zip", '$host.programfiles86/7-Zip/7zFM')
+
+3. Important Methods
+
+Host#systemdir
+Host#programfiles
+Host#tempdir
+Host#homedir
+Host#documents
+Host#downloads
+Host#desktop
+Host#exec_pw
+Host#exec
+Host#cmd_pw
+Host#exist
+Host#directory
+Host#mktempfile
+Host#mktempdir
+Host#copy
+Host#move
+Host#upload
+Host#download
+Host#list
+Host#glob
+
+
+*/
+
+abstract class Host {
+
+       static def sub(String base, String path) {
+               if ( path.startsWith(base) ) {
+                       return path.substring(base.length()+1);
+               }
+               return path;
+       }
+
+       static def join(String... array) {
+               StringBuilder sb = new StringBuilder()
+               for (def e : array ) {
+                       if (sb.length() > 0)
+                               sb.append('/')
+                       sb.append(e.toString())
+               }
+               sb.toString()
+       }
+//       def this.join *path_array
+//             path_array.join('/')
+//       end
+
+//       static def administrator_user (platform) {
+//             if platform == :windows {
+//               return 'administrator'
+//             } else {
+//               return 'root'
+//             }
+//       }
+
+       static def no_trailing_slash(path) {
+               if (path.endsWith('/') || path.endsWith('\\')) {
+                       path = path[0..path.length-1]
+               }
+               return path
+       }
+
+       static def to_windows_path(path) {
+               // remove \\ from path too. they may cause problems on some 
Windows SKUs
+               path = path.replaceAll('/', '\\').replaceAll('\\\\', 
'\\').replaceAll('\\\\', '\\')
+               if (path[0] == "\\" || path[0] == '/') {
+                       // TODO
+                       path = path[1..path.length]
+               }
+               path
+       }
+
+       static def to_posix_path(path) {
+               path.replaceAll('\\\\', '/')
+               path.replaceAll('//', '/')
+               path.replaceAll('//', '/')
+       }
+
+//       static def fs_op_to_cmd(fs_op, src, dst) {
+//             // TODO
+//             return case fs_op
+//             when :move
+//             when :copy
+//             when :delete
+//             when :mkdir
+//             when :list
+//             when :glob
+//             when :cwd
+//             when :cd
+//             when :exist
+//             when :is_dir
+//             when :open
+//             when :upload
+//               null
+//             when :download
+//               null
+//             else
+//               null
+//             end
+//       }
+
+       def registry_query(key, value, type, ctx) {
+               for ( def line : host.lines('REG QUERY "'+key+'" /v '+value, 
ctx) ) {
+                       if ( line.trim().startsWith("$value ")) {
+                               def parts = line.split("$type ")
+                               if ( parts.length > 1 ) {
+                                       return parts[1].trim()
+                               }
+                       }
+               }
+               null
+       }
+       
+       def registry_update(key, value, data, type, ctx) {
+               registry_add(key, value, data, type, ctx)
+       }
+       
+       def registry_add(key, value, data, type, ctx) {
+               host.exec('REG ADD "'+key+'" /v '+value+' /d '+data+' /t 
'+type+' /f', ctx)
+       }
+       
+       def registry_delete(key, value, ctx) {
+               host.exec('REG DELETE "'+key+'" /v '+value+' /f', ctx)
+       }
+       
+       def read_config(path, ctx=null) {
+       // /etc/[program]/
+       // %ProgramFiles%/[program]/
+       // TODO
+       }
+       
+       class Config {
+       def write() {
+       }
+       }
+       
+       def write_config(path, config, ctx=null) {
+       // TODO
+       }
+       
+       def registry_import(file, ctx=null) {
+       // TODO
+       }
+       
+       def registry(key, ctx=null) {
+       // TODO
+       }
+       
+       class RegistryKey {
+       def hive() {
+       }
+       def export(file) {
+       }
+       def add() {
+       }
+       def query() {
+       }
+       def delete() {
+       }
+       }
+       
+       def isWDWOS() {
+       false // LATER
+       }
+       
+       def getWDWOS() {
+       // OS::WDW::MS::Win::Win7::x64::SP1
+       // OS::WDW::MS::Win::Win7::x86::SP0
+       null // LATER
+       }
+       
+       def isVMGuest() {
+       false // LATER
+       }
+       
+       def getVMHost() {
+       null // LATER
+       }
+       
+       def getVMHostManager() {
+               if (isVMGuest()) {
+               // LATER how to share vm_host() instances amongst guest host 
instances?
+               def h = getVMHost()
+               if (h) {
+               return h.vm_host_mgr()
+               }
+               }
+               // Host::VMManager.new (save and share w/ //clone too!)
+               null // LATER
+       }
+       
+       // LATER int findProcess(String, svc_host=false)
+       // LATER kill(String) and kill(int)
+
+       def isRunning(exe, ctx=null, svc_host=false) {
+               // checks if the named process is running (if 1+ processes are 
running that match the name)
+               //
+               // windows note: some processes(ex: Internet Information 
Services) are run within a 'service host'
+               // in which case the process will show up as 'svchost.exe'. 
these processes are 'windows services'.
+               // if the process you're checking for is a 'windows service', 
set svc_host=true.
+               //        note: service name is case sensitive!
+               //        note: most/all services don't include .exe in the 
name (checking will fail if this doesn't match up)
+               //        note: svc_host is ignored on posix. you can set it to 
true in case of windows without causing a problem on posix.
+               //
+               // windows note: if svc_host=false, and if you ommit the .exe 
from the process name, this will
+               // check for both processes named with the given name and the 
given name + '.exe' (returns
+               // true if either are running).
+               //
+               if (isWindows(ctx)) {
+                       if (svc_host) {
+                               return exec("tasklist /FI \"SERVICES eq 
$exe\"").output.contains(exe)
+                       }
+                       def r = exec("tasklist /FI \"IMAGENAME eq 
$exe\"").output.contains(exe)
+                       if (!r && !exe.endsWith('.exe')) {
+                               return isRunning("$exe.exe", ctx)
+                       }
+                       return r
+               } else {
+                       return exec("pgrep $exe", ctx).output.length() > 1
+               }
+       }
+
+       enum EProcessor {
+               x64, x86, arm, mips, alpha, ppc, sparc, unknown
+       }
+
+       def processor(ctx=null) {
+               // TODO cache result
+               def a = isPosix(ctx) ? cmd('uname -a', ctx).output : 
env_value('PROCESSOR_ARCHITECTURE', ctx)
+               if (a == null||a.length()==0) {
+                       return EProcessor.unknown
+               }
+               a = a.toLowerCase()
+               if (a.contains('x86_64') || a.contains('i86pc'))
+                       EProcessor.x64
+               else if (a.contains('x86'))
+                       EProcessor.x86
+               else if (a.contains('arm'))
+                       EProcessor.arm
+               else if (a.contains('mips'))
+                       EProcessor.mips
+               else if (a.contains('alpha'))
+                       EProcessor.alpha
+               else if (a.contains('ppc'))
+                       EProcessor.ppc
+               else if (a.contains('sparc'))
+                       EProcessor.sparc
+               else
+                       EProcessor.unknown
+       }
+
+       def isX86(ctx=null) {
+               // x64 also supports x86
+               isX86Only(ctx) || isX64(ctx)
+       }
+
+       def isX86Only(ctx=null) {
+               processor(ctx) == EProcessor.x86
+       }
+
+       def isX64(ctx=null) {
+               processor(ctx) == EProcessor.x64
+       }
+
+       def isARM(ctx=null) {
+               processor(ctx) == EProcessor.arm
+       }
+
+       def number_of_processors(ctx=null) {
+               // TODO cache result
+               // counts number of CPUs in host
+               def p = 0
+               if (isWindows(ctx)) {
+                       p = env_value('NUMBER_OF_PROCESSORS', ctx)
+                       if (p) {
+                               // TODO get env_value to parse integer, float, 
bool
+                               p = Integer.parseInt(p)
+                       }
+               } else {
+                       def cpuinfo = read_lines('/proc/cpuinfo', ctx)
+                       
+                       p = 0
+                       // each processor will have a line like 'processor   : 
//', followed by lines of info
+                       // about that processor
+                       //
+                       // count number of those lines == number of processors
+                       for (def line : cpuinfo) {
+                               if (line.startsWith('processor')) {
+                                       p += 1
+                               }
+                       }       
+               }
+
+               return p > 0 ? p : 1 // ensure > 0 returned
+       } // end def number_of_processors
+       
+       def username(ctx=null) {
+               if (isPosix(ctx))
+                       env_value('USER', ctx)
+               else
+                       env_value('USERNAME', ctx)      
+       }
+
+       abstract def upload(local_file, remote_path, ctx, opts=[])
+       abstract def cwd(ctx=null)
+       abstract def cd(path, hsh, ctx=null)
+       abstract def read_lines(path, ctx=null, max_lines=16384)
+       abstract def read(path)
+       abstract def directory(path, ctx=null)
+       abstract def list(path, ctx)
+       abstract def isRemote()
+       abstract def mtime(file, ctx=null)
+       abstract def write(string, path, ctx)
+       abstract def isAlive()
+       abstract def env_values(ctx=null)
+       abstract def env_value(name, ctx=null)
+       abstract def close()
+       
+       def write_lines(lines, path, ctx) {
+               write(lines.join("\n"), path, ctx)
+       }
+
+       def isRebooting() {
+               false
+       }
+
+       def reboot(ctx) {
+               // reboots host and waits 120 seconds for it to become 
available again
+               reboot_wait(120, ctx)
+       }
+
+       def reboot_wait(seconds, ctx) {
+               //
+               if (isWindows(ctx)) {
+                       exec("shutdown /r /t 0", ctx)
+               } else {
+                       exec("shutdown -r -t 0", ctx)
+               }       
+       }
+
+       def nt_version(ctx) {
+               if (isWindows(ctx)) {
+                       return null
+               }
+
+               def nt_version = systeminfo_line('OS Version', ctx)
+
+               return nt_version ? nt_version.to_f : 5 // 5 (aka Windows 2000) 
is earliest supported NT Version
+       }
+
+       def eol(ctx) {
+               (isWindows(ctx)) ? "\r\n" : "\n"
+       }
+
+       def eol_escaped(ctx) {
+               (isWindows(ctx)) ? "\\r\\n" : "\\n"
+       }
+
+       def upload_force(local, remote, ctx, mkdir=true) {
+               delete_if(remote, ctx)
+
+               upload(local, remote, ctx, mkdir)
+       }
+
+       def copy(from, to, ctx, opts=[]) {
+               if (ctx) {
+                       ctx.fs_op2(this, EFSOp.copy, from, to) |new_from; 
new_to| {
+                               return copy(new_from, new_to, ctx, opts)
+                       }
+               }
+
+               if (opts.hasProperty('mkdir')&&opts.mkdir!=false) {
+                       mkdir(dirname(to), ctx)
+               }
+
+               copy_cmd(from, to, ctx)
+       }
+
+       def trash(path, ctx) {
+               move(path, join(trashdir(ctx), basename(path)), ctx)
+       }
+       
+       def basename(path) {
+               new File(path).getName()
+       }
+       
+       def dirname(path) {
+               new File(path).getParent()
+       }
+
+       def move(from, to, ctx) {
+               if (ctx) {
+                       ctx.fs_op2(this, EFSOp.move, from, to) |new_from; 
new_to| {
+                               return move(new_from, new_to, ctx)
+                       }
+               }
+
+               if (!directory(from)) {
+                       move_file(from, to, ctx)
+                       return
+               }
+
+               move_cmd(from, to, ctx)
+       }
+
+       def setTime(time, ctx=null) {
+               // sets the host's time/date
+               // TODO posix support
+               if (isPosix(ctx)) {
+                       exec('date --set="'+time+'"', ctx)
+               } else {
+                       exec("date $time.month-$time.day-$time.year && time 
$time.hour:$time.minute-$time.second", ctx)
+               }
+       }
+
+       abstract def getTime(ctx=null)
+
+       def systemroot(ctx=null) {
+               // get's the file system path pointing to where the host's 
operating system is stored
+               if (isPosix(ctx)) {
+                       return '/'
+               } else if (_systemroot) {
+                       return _systemroot
+               } else {
+                       _systemroot = env_value('SYSTEMROOT', ctx)
+                       return _systemroot
+               }
+       }
+
+       def systemdrive_or_homedir(ctx=null) {
+               // returns systemdrive on windows and the user's home directory 
on posix
+               isWindows(ctx) ? systemdrive(ctx) : homedir(ctx)
+       }
+
+       def systemdrive(ctx=null) {
+               // gets the file system path to the drive where OS and other 
software are stored
+               if (isPosix(ctx)) {
+                       return '/'
+               } else if (_systemdrive) {
+               return _systemdrive
+               } else {
+               _systemdrive = env_value('SYSTEMDRIVE', ctx)
+               return _systemdrive
+               }
+       }
+
+       def desktop(ctx=null) {
+               return join(userprofile(ctx), 'Desktop')
+       }
+
+       def userprofile(ctx=null) {
+               if (isPosix(ctx)) {
+                       return homedir(ctx)
+               }
+
+               if (_userprofile) {
+                       return _userprofile
+               }
+
+               def p = env_value('USERPROFILE', ctx)
+
+               if (exists(p, ctx)) {
+                       return _userprofile = p
+               } else if (!_homedir) {
+                       return _userprofile = _homedir
+               } else {
+                       return _userprofile = systemdrive(ctx)
+               }
+       }
+
+       def programfiles(ctx=null) {
+               if (isPosix(ctx)) {
+                       return '/usr/local/bin'
+               } else if (!_programfiles) {
+                       _program_files = env_value('PROGRAMFILES', ctx)
+                       if (_program_files) {
+                               _program_files = systemdrive(ctx)+'/Program 
Files/'
+                       }
+               }
+               return _programfiles
+       }
+
+       def programfilesx86(ctx=null) {
+               if (isPosix()) {
+                       return '/usr/local/bin'
+               } else if (!_program_files_x86) {
+                       _program_files_x86 = env_value('PROGRAMFILES(x86)', ctx)
+                       if (!exists(_program_files_x86, ctx)) {
+                       _program_files_x86 = env_value('PROGRAMFILES', ctx)
+                       }
+                       if (_program_files_x86) {
+                               _program_files = systemdrive(ctx)+'/Program 
Files/'
+                       }
+               }
+               return _programfiles
+       }
+
+       def homedir(ctx=null) {
+               if (!_homedir) {
+                       if (isPosix(ctx)) {
+                               // on linux/unix, its simple, just get $HOME
+                               _homedir = env_value('HOME', ctx)
+                       } else {
+                               // on WIndows, home directory is 
%HOMEDRIVE%%HOMEPATH%
+                               // (though those variables may be undefined for 
a Windows user)
+                               _homedir = env_value('HOMEDRIVE', ctx)
+                               if (_homedir) {
+                                       // fallback to user profile (or null if 
not set)
+                                       _homedir = _userprofile
+                               } else {
+                                       def a = env_value('HOMEPATH', ctx)
+                                       if (a) {
+                                               if (!_userprofile) {
+                                                       // fallback to 
%USERPROFILE%
+                                                       _homedir = _userprofile
+                                               // else: fallback to %HOMEDRIVE%
+                                               }
+                                       } else {
+                                               // add %HOMEPATH% to %HOMEDRIVE%
+                                               _homedir += a
+                                       }
+                               }
+                       }
+               }
+               _homedir
+       } // end def homedir
+
+       def documents(ctx=null) {
+               return userprofile(ctx) + '/Documents'
+       }
+       
+       def downloads(ctx=null) {
+               return userprofile(ctx) + '/Downloads'
+       }
+       
+       def trashdir(ctx=null, drive=null) {
+               if (isPosix(ctx)) {
+                       return desktop(ctx) + '/Trash'
+               }
+       
+               if (drive) {
+                       drive = systemdrive(ctx)
+               }
+       
+               return drive + '\\$Recycle.Bin'
+       }
+
+       def appdata(ctx=null) {
+               if (_appdata) {
+                       return _appdata
+               }
+               if (isPosix(ctx)) {
+                       def p = env_value('HOME', ctx)
+                       if (p && exists(p, ctx)) {
+                               return _appdata = p
+                       }
+               } else {
+                       def p = env_value('USERPROFILE', ctx)
+                       if (p) {
+                               def q = p + '\\AppData\\'
+                               if (exists(q, ctx)) {
+                                       return _appdata = q
+                               } else if (exists(p, ctx)) {
+                                       mkdir(q, ctx)
+                                       return _appdata = q
+                               }
+                       }
+               }
+               _appdata = systemdrive(ctx)
+       } // end def appdata
+       
+       def appdata_local(ctx=null) {
+               if (_appdata_local) {
+                       return _appdata_local
+               }
+               if (isPosix()) {
+                       def p = env_value('HOME', ctx)
+                       if (p) {
+                               def q = p + '/PFTT' 
+                               if (exists(q, ctx)) {
+                                       return _appdata_local = q
+                               } else if (exists(p, ctx)) {
+                                       mkdir(q, ctx)
+                                       return _appdata_local = q
+                               }
+                       }
+               } else {
+                       def p = env_value('USERPROFILE', ctx)
+                       if (p) {
+                               def q = p + '\\AppData\\Local'
+                               if (exists(q, ctx)) {
+                                       return _appdata_local = q
+                               } else if (exists(p, ctx)) {
+                                       mkdir(q, ctx)
+                                       return _appdata_local = q
+                               }
+                       }
+               }       
+               _appdata_local = systemdrive(ctx)
+       } // end def appdata_local
+
+       def tempdir(ctx) {
+               if (_tempdir) {
+                       return _tempdir
+               }
+               if (isPosix()) {
+                       def p = '/usr/local/tmp'
+                       def q = p + '/PFTT'
+                       if (exists(q, ctx)) {
+                               return _tempdir = q
+                       } else if (exists(p, ctx)) {
+                               mkdir(q, ctx)
+                               return _tempdir = q
+                       }
+                       p = '/tmp'
+                       q = p + '/PFTT'
+                       if (exists(q, ctx)) {
+                               return _tempdir = q
+                       } else if (exists(p, ctx)) {
+                               mkdir(q, ctx)
+                               return _tempdir = q
+                       }
+               } else {
+                       // try %TEMP%\\PFTT
+                       def p = env_value('TEMP', ctx)
+                       if (p) {
+                               def q = p + '\\PFTT'
+                               if (exists(q, ctx)) {
+                                       return _tempdir = q
+                               } else if (exists(p, ctx)) {
+                                       mkdir(q, ctx)
+                                       return _tempdir = q
+                               }
+                       }
+       
+                       // try %TMP%\\PFTT
+                       p = env_value('TMP', ctx)
+                       if (p) {
+                               def q = p + '\\PFTT'
+                               if (exists(q, ctx)) {
+                                       return _tempdir = q
+                               } else if (exists(p, ctx)) {
+                                       mkdir(q, ctx)
+                                       return _tempdir = q
+                               }
+                       }
+       
+                       // try %USERPROFILE%\\AppData\\Local\\Temp\\PFTT
+                       p = env_value('USERPROFILE', ctx)
+                       if (p) {
+                               p = '\\AppData\\Local\\Temp\\' + p
+                               def q = p + '\\PFTT'
+                               if (exists(q, ctx)) {
+                                       return _tempdir = q
+                               } else if (exists(p, ctx)) {
+                                       mkdir(q, ctx)
+                                       return _tempdir = q
+                               }
+                       }
+       
+                       // try %SYSTEMDRIVE%\\temp\\PFTT
+                       p = systemdrive(ctx)+'\\temp'
+                       q = p + '\\PFTT'
+                       if (exists(q, ctx)) {
+                               return _tempdir = q
+                       } else if (exists(p, ctx)) {
+                               mkdir(q, ctx)
+                               return _tempdir = q
+                       }
+       
+               }
+               
+               _tempdir = systemdrive(ctx)
+       } // end def tempdir
+
+
+       def systeminfo(ctx=null) {
+               // gets information about the host (as a string) including 
CPUs, memory, operating system (OS dependent format)
+               if (!_systeminfo) {
+                       if (isPosix(ctx))
+                               _systeminfo = exec('uname -a', ctx).output + 
"\n" + exec('cat /proc/meminfo', ctx).output +"\n" + exec('cat /proc/cpuinfo', 
ctx).output // LATER?? glibc version
+                       else
+                               _systeminfo = exec('systeminfo', ctx).output
+               }
+               return _systeminfo
+       }
+       
+       static def os_short_name(os) {
+         os = os.replaceAll('Windowsr', 'Win')
+         os = os.replaceAll('Microsoft', '')
+         os = os.replaceAll('Server', '')
+         os = os.replaceAll('Developer Preview', 'Win 8')
+         os = os.replaceAll('Win 8 Win 8', 'Win 8')
+         os = os.replaceAll('Full', '')
+         os = os.replaceAll('Installation', '')
+         os = os.replaceAll("\\(", '')
+         os = os.replaceAll("\\)", '')
+         os = os.replaceAll('tm', '')
+         os = os.replaceAll('VistaT', 'Vista')
+       
+         os = os.replaceAll('Windows', 'Win')
+         os = os.replaceAll('/', '')
+       
+         // remove common words
+         os = os.replaceAll('Professional', '')
+         os = os.replaceAll('Standard', '')
+         os = os.replaceAll('Enterprise', '')
+         os = os.replaceAll('Basic', '')
+         os = os.replaceAll('Premium', '')
+         os = os.replaceAll('Ultimate', '')
+         os = os.replaceAll('GNU', '')
+         if (!os.contains('XP')) {
+           // XP Home != XP Pro
+           os = os.replaceAll('Home', '')
+         }
+         os = os.replaceAll('Win Win', 'Win')
+         os = os.replaceAll("\\(R\\)", '')
+         os = os.replaceAll(',', '')
+         os = os.replaceAll('Edition', '')
+         os = os.replaceAll('2008 R2', '2008r2')
+         os = os.replaceAll('2003 R2', '2003r2')
+         os = os.replaceAll('RTM', '')
+         os = os.replaceAll('Service Pack 1', '')
+         os = os.replaceAll('Service Pack 2', '')
+         os = os.replaceAll('Service Pack 3', '')
+         os = os.replaceAll('Service Pack 4', '')
+         os = os.replaceAll('Service Pack 5', '')
+         os = os.replaceAll('Service Pack 6', '')
+         os = os.replaceAll('Microsoft', '')
+         os = os.replaceAll('N/A', '')
+         os = os.replaceAll('PC', '')
+         os = os.replaceAll('Server', '')
+         os = os.replaceAll('-based', '')
+         os = os.replaceAll('Build', '')
+         //
+         os = os.replaceAll('6.1.7600', '')
+         os = os.replaceAll('6.1.7601', '')
+         os = os.replaceAll('6.0.6000', '')
+         os = os.replaceAll('6.0.6001', '')
+         os = os.replaceAll('6.0.6002', '')
+         os = os.replaceAll('5.1.3786', '')
+         os = os.replaceAll('5.1.3787', '')
+         os = os.replaceAll('5.1.3788', '')
+         os = os.replaceAll('5.1.3789', '')
+         os = os.replaceAll('5.1.3790', '')
+         os = os.replaceAll('5.0.2600', '')
+         os = os.replaceAll('5.0.2599', '')
+         os = os.replaceAll('5.2.SP2', '')
+         os = os.replaceAll('5.2', '')
+         os = os.replaceAll('7600', 'SP0') // win7/win2k8r2 sp0
+         os = os.replaceAll('7601', 'SP1') // win7/win2k8r2 sp1
+         os = os.replaceAll('6000', 'SP0') // winvista/win2k8 sp0
+         os = os.replaceAll('6001', 'SP1') // winvista/win2k8 sp1
+         os = os.replaceAll('6002', 'SP2') // winvista/win2k8 sp2
+         os = os.replaceAll('3786', 'SP0') // win2k3 sp0?
+         os = os.replaceAll('3787', 'SP1') // win2k3 sp1?
+         os = os.replaceAll('3788', 'SP0') // win2k3r2 sp0?
+         os = os.replaceAll('3789', 'SP1') // win2k3r2 sp1?
+         os = os.replaceAll('3790', 'SP2') // win2k3r2 sp2
+         os = os.replaceAll('2600', 'SP3') // windows xp sp3
+         os = os.replaceAll('2599', 'SP2') // windows xp sp2?
+         //
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+         os = os.replaceAll('  ', ' ')
+       
+         return os.trim()
+       } // end static def os_short_name
+
+       def os_name_short(ctx=null) {
+               osname_short(ctx)
+       }
+       
+       def osname_short(ctx=null) {
+               return Host.os_short_name(osname(ctx))
+       }
+       
+       def os_name(ctx=null) {
+               osname(ctx)
+       }
+
+       def osname(ctx=null) {
+               // returns the name, version and hardware architecture of the 
Host's operating system
+               // ex: Windows 2008r2 SP1 x64
+               if (!this._osname) {
+                       if (isPosix(ctx)) {
+                               this._osname = line('uname -om', ctx)
+                       } else {
+                               osname = systeminfo_line('OS Name', ctx)
+                               osname += ' '
+                               // get service pack too
+                               osname += systeminfo_line('OS Version', ctx)
+                               osname += ' '
+                               // and cpu arch
+                               osname += systeminfo_line('System Type', ctx) 
// x86 or x64
+                               this._osname = osname
+                               
+                       }
+               }
+               this._osname
+       }
+
+       def line_prefix(prefix, cmd, ctx) {
+               // executes the given cmd, splits the STDOUT output by lines 
and then returns
+               // only the (last) line that starts with the given prefix
+               //
+               line = line(cmd, ctx)
+               if (line.startsWith(prefix)) {
+                       line = line.substring(prefix.length())
+               }
+               return line.strip()
+       }
+
+       def silence_stderr(str) {
+               isPosix() ? "$str 2> /dev/null" : "$str 2> NUL"
+       }
+       
+       def silence_stdout(str) {
+               isPosix() ? "$str > /dev/null" : "$str 1> NUL"
+       }
+       
+       def silence(str) {
+               isPosix() ? "$str 1> /dev/null 2>&1" : "$str 1> NUL 2>&1"
+       }
+       
+       def devnull() {
+               isPosix() ? '/dev/null' : 'NUL'
+       }
+
+       def hasDebugger(ctx) {
+               return debugger(ctx) != null
+       }
+
+       def debug_wrap(cmd_line, ctx) {
+               def dbg = debugger(ctx)
+               if (dbg) {
+                       if (isPosix()) {
+                               return dbg+" --args "+cmd_line
+                       } else if (isWindows()) {
+                       return dbg+" "+cmd_line
+                       }
+               }
+               return cmd_line
+       }
+
+       def debugger(ctx) {
+               if (isPosix(ctx)) {
+                       if (exists('/usr/bin/gdb', ctx) || 
exists('/usr/local/gdb', ctx)) {
+                               return 'gdb'
+                       }
+               } else if (isWindows(ctx)) {
+                       // windbg must be the x86 edition (not x64) because php 
is only compiled for x86
+                       // TODO use //programfiles !
+                       // LATER allow specifying which debugger to use (!php 
or x64 php)
+                       if (exists("%ProgramFiles(x86)%\\Debugging Tools For 
Windows (x86)\\windbg.exe", ctx))
+                               return '%ProgramFiles(x86)%\\Debugging Tools 
For Windows (x86)\\windbg.exe'
+                       else if (exists('%ProgramFiles(x86)%\\Debugging Tools 
For Windows\\windbg.exe', ctx))
+                               return '%ProgramFiles(x86)%\\Debugging Tools 
For Windows\\windbg.exe'
+                       else if (exists('%ProgramFiles%\\Debugging Tools For 
Windows (x86)\\windbg.exe', ctx))
+                               return '%ProgramFiles%\\Debugging Tools For 
Windows (x86)\\windbg.exe'
+                       else if (exists('%ProgramFiles%\\Debugging Tools For 
Windows (x86)\\windbg.exe', ctx))
+                               return '%ProgramFiles%\\Debugging Tools For 
Windows (x86)\\windbg.exe'
+               }
+               if (ctx) {
+                       // prompt context to get debugger for this host (or 
null)
+                       return ctx.find_debugger(this)
+               } else {
+                       return null // signal that host has no debugger
+               }
+       }
+
+//attr_accessor :manager // Host::Manager
+
+def Host(opts={}) {
+       // TODO
+//
+////set the opts as properties in the Test::Factor sense
+////      opts.each_pair do |key,value|
+////        property key => value
+////        // LATER merge Test::Factor and host info (name, os_version)
+////        // so both Test::Factor and host info can access each other
+////      end
+//
+//// allow override what //name() returns
+//// so at least that can be used even
+//// when a host is not accessible
+//if (opts.has_key(hostname)) {
+//_name = opts[hostname]
+//}
+////
+//
+       dir_stack = []
+       cwd = null
+} // def initialize
+
+def describe() {
+//description ||= this.properties.values.join('-').downcase
+}
+
+       def shell(ctx=null) {
+               // returns the name of the host's shell
+               // ex: /bin/bash /bin/sh /bin/csh /bin/tcsh cmd.exe command.com
+               if (isPosix(ctx)) { 
+                       return env_value('SHELL', ctx)
+               } else {
+                       return basename(env_value('ComSpec', ctx))
+               }
+       }
+
+       def which(cmd, ctx=null) {
+               cmd_pw("which $cmd", "where $cmd", ctx).output.trim()
+       }
+
+       def hasCmd(cmd, ctx=null) {
+               def w = which(cmd, ctx)
+               return !w && w.length > 0
+       }
+
+       def exec_pw(posix_cmd, windows_cmd, ctx, opts={}) {
+               if (isPosix(ctx)) {
+                       exec(posix_cmd, ctx, opts)
+               } else {
+                       exec(windows_cmd, ctx, opts)
+               }
+       }
+
+       def exec_ok(command, ctx, opts={}) {
+               exec(command, ctx, opts).exit_code
+       }
+
+       def exec_pw_ok(posix_cmd, windows_cmd, ctx, opts={}) {
+               if (isPosix()) {
+                       exec_ok(posix_cmd, ctx, opts)
+               } else {
+                       exec_ok(windows_cmd, ctx, opts)
+               }
+       }
+
+       class ExecHandle {
+               def write_stdin(stdin_data) {
+               }
+               def read_stderr() {
+               ''
+               }
+               def read_stdout() {
+               ''
+               }
+               def has_stderr() {
+                       read_stdout.length > 0
+               }
+               def has_stdout() {
+                       read_stderr.length > 0
+               }
+       }
+
+       // command:
+       //   command line to run. program name and arguments to program as one 
string
+       //   this may be a string or Tracing::Command::Expected
+       //
+       // options: a hash of
+       //  :env
+       //     keys and values of ENV must be strings (not ints, or anything 
else!)
+       //  :binmode  true|false
+       //     will set the binmode of STDOUT and STDERR. binmode=false on 
Windows may affect STDOUT or STDERR output
+       //  :by_line  true|false
+       //     if true, will read input line by line, otherwise, reads input by 
blocks
+       //  :chdir
+       //     the current directory of the command to run
+       //  :debug   true|false
+       //     runs the command with the host's debugger. if host has no 
debugger installed, command will be run normally
+       //  :stdin_data   ''
+       //     feeds given string to the commands Standard Input
+       //  :null_output true|false
+       //     if true, returns '' for both STDOUT and STDERR. (if host is 
remote, STDOUT and STDERR are not sent over
+       //     the network)
+       //  :max_len  0+ bytes   default=128 kilobytes (128*1024)
+       //     maximum length of STDOUT or STDERR streams(limit for either, 
STDERR.length+STDOUT.length <= :max_len*2).
+       //     0 = unlimited
+       //  :timeout  0+ seconds  default=0 seconds
+       //     maximum run time of process (in seconds)
+       //     process will be sent SIGKILL if it is still running after that 
amount of time
+       //  :success_exit_code int, or [int] array   default=0
+       //     what exit code(s) defines success
+       //     note: this is ignored if Command::Expected is used (which 
evaluates success internally)
+       //  :ignore_failure true|false
+       //     ignores exit code and always assumes command was successful 
unless
+       //     there was an internal PFTT exception (ex: connection to host 
failed)
+       //  :exec_handle true|false, default=false
+       //     if true, returns a handle to the process which allows you to 
close(sigkill|sigterm|sigint) the process later.
+       //     if true, all options (including :timeout) may be used except 
:success_exit_code and :ignore_failure, as interpretting exit status code
+       //     will then be up to your own code, since you're controlling the 
process.
+       // LATER :elevate and :sudo support for windows and posix
+       // other options are silently ignored
+       //
+       //
+       // returns once command has finished executing
+       //
+       // if command is a string returns array of 3 elements. 0=> STDOUT 
output as string 1=>STDERR 2=>command's exit code (0==success)
+       // if command is a Command::Expected, returns an Command::Actual
+       def exec(command, ctx, opts=[], Closure block=null) {
+               _exec(false, command, opts, ctx, block)
+       }
+       
+       // same as exec! except it returns immediately
+       def exec_async(command, ctx, opts=[], Closure block=null) {
+               _exec(true, command, opts, ctx, block)
+       }
+
+       def cmd_pw(posix_cmd, windows_cmd, ctx, opts={}) {
+               if (isPosix(ctx))
+                       cmd(posix_cmd, ctx, opts)
+               else
+                       cmd(windows_cmd, ctx, opts)
+       }
+       
+       // executes command or program on the host
+       //
+       // can be a DOS command, Shell command or a program to run with options 
to pass to it
+       //
+       // some DOS commands (for Windows OSes) are not actual programs, but 
rather just commands
+       // to the command processor(cmd.exe or command.com). those commands 
can't be run through
+       // exec!( since exec! is only for actual programs).
+       //
+       def cmd(cmdline, ctx, opts={}) {
+               if (isWindows(ctx)) {
+                       cmdline = "CMD /C $cmdline"
+               }
+               return exec(cmdline, ctx, opts)
+       }
+
+       // executes command using cmd! returning the output (STDOUT) from the 
command,
+       // with the new line character(s) chomped off
+       def line(cmdline, ctx=null) {
+               lines(cmdline, ctx).split("\n")[0]
+       }
+       
+       def lines(cmdline, ctx) {
+               cmd(cmdline, ctx).output
+       }
+       
+       def unquote_line(cmdline, ctx) {
+               line(cmdline, ctx).replaceAll(/\"/, '')
+       }
+       
+       def isLonghorn(ctx=null) {
+               // checks if its a longhorn(Windows Vista/2008) or newer 
version of Windows
+               // (longhorn added new stuff not available on win2003 and winxp)
+               if (is_longhorn) {
+                       return is_longhorn
+               }
+               
+               is_longhorn = nt_version(ctx) >= 6
+               
+               if (ctx) {
+                       is_longhorn = ctx.check_os_generation_detect(longhorn, 
is_longhorn)
+               }
+               
+               return is_longhorn
+       }
+
+       def isWindows(ctx=null) {
+               // returns true if this host is Windows OS
+               if (is_windows) {
+                       return is_windows
+               }
+               if (posix) {
+                       return is_windows = false
+               }
+               
+               // Windows will always have a C:\ even if the C:\ drive is not 
the systemdrive
+               // posix doesn't have C: D: etc... drives
+               is_windows = _exist("C:\\", ctx)
+               
+               if (ctx) { 
+                       // cool stuff: allow user to override OS detection
+                       is_windows = ctx.check_os_type_detect(windows, 
is_windows)
+               }
+               
+               return is_windows
+       }
+
+       def isBSD(ctx=null) {
+               osname(ctx).contains('BSD')
+       }
+       
+       def isFreeBSD(ctx=null) {
+               osname(ctx).contains('FreeBSD')
+       }
+       
+       def isLinux(ctx=null) {
+               osname(ctx).contains('Linux')
+       }
+       
+       def isRedhat(ctx=null) {
+               isLinux(ctx) and exist('/etc/redhat-release', ctx)
+       }
+       
+       def isFedora(ctx=null) {
+               isLinux(ctx) and exist('/etc/fedora-release', ctx)
+       }
+       
+       def isDebian(ctx=null) {
+               isLinux(ctx) and exist('/etc/debian_release', ctx)
+       }
+       
+       def isUbuntu(ctx=null) {
+               isLinux(ctx) and exist('/etc/lsb-release', ctx)
+       }
+       
+       def isGentoo(ctx=null) {
+               isLinux(ctx) and exist('/etc/gentoo-release', ctx)
+       }
+       
+       def isSUSE(ctx=null) {
+               isLinux(ctx) and exist('/etc/SUSE-release', ctx)
+       }
+       
+       def isSolaris(ctx=null) {
+               osname(ctx).contains('Solaris')
+       }
+
+//def utype? ctx=null
+//if linux?(ctx)
+//:linux
+//elsif solaris?(ctx)
+//:solaris
+//elsif freebsd?(ctx)
+//:freebsd
+//else
+//null
+//end
+//end
+
+       def isPosix(ctx=null) {
+               if (posix) {
+                       return posix
+               }
+               if (is_windows) {
+                       return posix = false
+               }
+                       
+               posix = _exist('/usr', ctx)
+       
+               if (ctx) {
+                       // TODO do this for utype too
+                       posix = ctx.check_os_type_detect(posix, posix)
+               }
+       
+               return posix
+       }
+
+       def make_absolute(path) {
+               if (isWindows()) {
+                       // support for Windows drive letters
+                       if (path.contains(':'))
+                               return path
+               } else if (isPosix()) {
+                       if (path.startsWith("/"))
+                               return path
+               }
+               _make_absolute(path)
+       }
+
+       def exist(path, ctx=null) {
+               path = format_path(path, ctx)
+               path = make_absolute(path)
+       
+               if (ctx) {
+                       ctx.fs_op1(this, EFsOp.exist, path) { new_path ->
+                               return exist(new_path, ctx)
+                       }
+               }
+       
+               _exist(path, ctx)
+       }
+       
+       def exists(path, ctx=null) {
+               exist(path, ctx)
+       }
+
+       def format_path(path, ctx=null) {
+               if (isWindows())
+                       to_windows_path(path)
+               else
+                       to_posix_path(path)
+       }
+
+//     def format_path! path, ctx=null
+//     case
+//     when windows?(ctx) then to_windows_path!(path)
+//     else to_posix_path!(path)
+//     end
+//     end
+
+       def pushd(path, ctx=null) {
+               cd(path, ctx, no_clear=true)
+               dir_stack.push(path)
+       }
+
+       def popd(ctx=null) {
+               def popped = dir_stack.pop
+               if (popped)
+                       cd(popped, ctx, no_clear=true)
+       }
+
+       def peekd() { 
+               dir_stack.last
+       }
+
+       def separator(ctx=null) {
+               isWindows(ctx) ? '\\' : '/'
+       }
+
+       def upload_if_not(local, remote, ctx) {
+               if (exist(remote, ctx)) {
+                       return false
+               } else {
+                       upload(local, remote, ctx)
+                       return true
+               }
+       }
+       
+       def delete_if(path, ctx) {
+               if (exist(path, ctx)) {
+                       delete(path, ctx)
+                       true
+               } else {
+                       false
+               }
+       }
+
+       def delete(glob_or_path, ctx) {
+               if (ctx) {
+                       ctx.fs_op1(this, EFsOp.delete, glob_or_path) { 
new_glob_or_path ->
+                               return delete(new_glob_or_path, ctx)
+                       }
+               }
+
+               glob_or_path = make_absolute(glob_or_path)
+
+               if (!isSafe(glob_or_path))
+                       throw new IllegalArgumentException()
+
+               if (directory(glob_or_path, ctx)) {
+                       if (isPosix(ctx))
+                               exec("rm -rf \"$glob_or_path\"", ctx)
+                       else
+                               exec("cmd /C rmdir /S /Q \"$glob_or_path\"", 
ctx)       
+               } else {
+                       _delete(glob_or_path, ctx)
+               }
+       }
+
+       def escape(str, quote=true) {
+               if (isWindows()) {
+                       s = str.dup
+//                     if (quote) {
+//                             s.replace %Q{"//{s}"} unless 
s.replaceAll(/(["])/,'\\\\\1').null?
+//                     }
+//                     s.replaceAll(/[\^&|><]/,'^\\1')
+                       s
+               } else {
+                       s
+               }
+       }
+
+       def mkdir(path, ctx) {
+               path = make_absolute(path)
+               def parent = dirname(path)
+               if (!directory(parent)) {
+                       mkdir(parent, ctx)
+               }
+               if (!directory(path)) {
+                       _mkdir(path, ctx)
+               }
+       }
+
+       def mktmpdir(ctx, path=null, suffix='') {
+               if (!path) {
+                       path = tempdir(ctx)
+               }
+               
+               path = make_absolute(path)
+               tries = 10
+               try {
+                       dir = File.join( path, String.random(6)+suffix )
+//                     raise 'exists' if directory? dir
+                       mkdir(dir, ctx)
+               } catch ( Exception ex ) {
+//                     retry if (tries -= 1) > 0
+                       throw ex
+               }
+               dir
+       }
+
+       def mktmpfile(suffix, ctx, content=null) {
+               tries = 10
+               try {
+                       path = File.join( tmpdir(ctx), String.random(6) + 
suffix )
+               
+//             raise 'exists' if exists?(path, ctx)
+               
+                       if (content) {
+                               write(content, path, ctx)
+                       }
+               
+                       return path
+               } catch (Exception ex) {
+//             retry if (tries -= 1) > 0
+                       throw ex
+               }
+       }
+
+       def isSafe(path) {
+               true
+       //make_absolute! path
+       //insane = case
+       //when posix?
+       ///\A\/(bin|var|etc|dev|usr)\Z/
+       //else
+       ///\A[A-Z]:(\/(Windows)?)?\Z/
+       //end =~ path
+       //!insane
+       }
+
+       def administrator_user(ctx=null) {
+               if (isWindows(ctx)) {
+                       // LATER? should actually look this up??(b/c you can 
change it)
+                       return 'administrator'
+               } else {
+                       return 'root'
+               }
+       }
+
+       def name(ctx=null) {
+               if (!_name) {
+                       // find a name that other hosts on the network will use 
to reference localhost
+                       if (isWindows(ctx))
+                               _name = env_value('COMPUTERNAME', ctx)
+                       else
+                               _name = env_value('HOSTNAME', ctx)
+               }
+               _name
+       }
+
+       protected def move_cmd(from, to, ctx) {
+               from = no_trailing_slash(from)
+               to = no_trailing_slash(to)
+               if (isPosix(ctx)) {
+                       cmd("mv \"$from\" \"$to\"", ctx)
+               } else {
+                       from = to_windows_path(from)
+                       to = to_windows_path(to)
+                       
+                       cmd("move \"$from\" \"$to\"", ctx)
+               }
+       }
+       
+       protected def copy_cmd(from, to, ctx) {
+               if (isPosix(ctx)) {
+                       cmd("cp -R \"$from\" \"$to\"", ctx)
+               } else {
+                       from = to_windows_path(from)
+                       to = to_windows_path(to)                
+                       cmd("xcopy /Y /s /i /q \"$from\" \"$to", ctx)
+               }
+       }
+
+       protected def _exec(in_thread, command, opts, ctx, block) {
+               opts = [env:[]] // TODO
+               cwd = null // clear cwd cache
+
+               if (opts.hasProperty('exec_handle') && !in_thread)
+                       opts.exec_handle = null
+
+               def orig_cmd = command
+// TODO                if (command instanceof Command) 
+//                     // get CmdString for this host
+//                     command = command.to_cmd_string(this)
+                       
+               // for CmdString
+               command = command.toString()
+               // TODO      if command.is_a?(Tracing::Command::Expected)
+               //        command = command.cmd_line
+               //      end
+
+               if (ctx) {
+                       ctx.cmd_exe_start(this, command, opts) { new_command ->
+                               return _exec(in_thread, new_command, opts, ctx, 
block)
+                       }
+               }
+
+               // begin preprocessing command and options 
+
+               // if the program being run in this command (the part of the 
command before " ")
+               // has / in the path, convert to \\  for Windows (or Windows 
might not be able to find the program otherwise)
+               if (isWindows(ctx)) {
+                       def i
+                       if (command.startsWith('"'))
+                               i = command.index('"', 1)
+                       else
+                               i = command.index(' ', 1)
+                       if (i>0)
+                               command = to_windows_path(command(0, i)) + 
command.substring(i+1)
+               }
+               //
+
+               //
+               if (opts.hasProperty('null_output') && opts.null_output)
+                       command = silence(command)
+               //
+                       
+               if (!opts.hasProperty('env'))
+                       opts.env = false //[]
+
+               if (opts.hasProperty('chdir') && opts.chdir) {
+                       // NOTE: chdir seems to be ignored|invalid on 
Windows(even Win7) if / is NOT converted to \ !!
+                       // convert the \ / to the correct for this host
+                       opts.chdir = format_path(opts.chdir, ctx)
+               }
+
+               if (!opts.hasProperty('max_len') || opts.max_len < 0)
+                       opts.max_len = 128*1024
+                               
+               // run command in platform debugger
+               if (opts.hasProperty('debug') && opts.debug)
+                       command = debug_wrap(command)
+
+               // end preprocessing command and options 
+
+               def ret
+               if (in_thread && !(opts.hasProperty('exec_handle') && 
opts.exec_handle)) {
+                       new Thread() {
+                               void run() {
+                                       ret = _exec_thread(command, opts, ctx, 
block)
+                               }
+                       }.start()
+               } else {
+                       ret = _exec_thread(command, opts, ctx, block)
+               }
+
+               if (opts.exec_handle)
+                       // ret is a ExecHandle (like LocalExecHandle or 
SshExecHandle)
+                       return ret
+               else if (orig_cmd instanceof Command)
+                       // ret is a Command.Actual
+                       return ret
+               else
+                       return ret//[ret[0], ret[1], ret[2]]
+       } // def _exec
+
+       def _exec_thread(command, opts, ctx, block) {
+               def stdout, stderr, exit_code, ret=null
+               try {
+                       ret = _exec_impl(command, opts, ctx, block)
+
+                       if (opts.hasProperty('exec_handle') && opts.exec_handle)
+                               return ret
+
+                       stdout = ret.output
+                       stderr = ret.error
+                       exit_code = ret.exit_code
+
+                       //
+                       // don't let output get too large
+                       if (opts.hasProperty('max_len') && opts.max_len > 0) {
+                               if (stdout.length() > opts.max_len)
+                                       stdout = stdout.substring(0, 
opts.max_len)
+                               if (stderr.length() > opts.max_len)
+                                       stderr = stderr.substring(0, 
opts.max_len)
+                       }
+                       //
+       
+                       // execution done... evaluate and report success/failure
+                       if (ctx) {                              
+                               //
+                               // decide if command was successful
+                               //
+                               def success = 
opts.hasProperty('ignore_failure') && opts.ignore_failure ? true : false
+                               if (!success) {
+                                       // default evaluation
+                                       success = 0 == exit_code
+                                       if (command instanceof Command) {
+                                               // custom evaluation
+                                               //
+                                               ret = 
command.createActual(command.cmd_line, stdout, stderr, exit_code)
+                                               // exit_code => share with _exec
+                                               success = ret.isSuccess()
+       
+                                       } else if 
(opts.hasProperty('success_exit_code')) {
+                                               //
+                                               if (opts.success_exit_code 
instanceof List) {
+                                                       // an array of 
succesful values
+                                                       //
+                                                       success = false // 
override exit_code==0 above
+                                                       for ( def sec : 
opts.success_exit_code ) {
+                                                               if (exit_code 
== sec) {
+                                                                       success 
= true
+                                                                       break
+                                                               }
+                                                       }
+                                               } else if 
(opts.success_exit_code instanceof Integer) {
+                                                       // a single successful 
value
+                                                       success = exit_code == 
opts.success_exit_code
+                                               }
+                                       
+                                       }
+                               }
+                               //
+                       
+                       // TODO         if success
+                       //            ctx.cmd_exe_success(this, command, opts, 
c_exit_code, stdout+stderr) do |command|
+                       //              return _exec_thread(command, opts, ctx, 
block)
+                       //            end
+                       //          else
+                       //            ctx.cmd_exe_failure(this, command, opts, 
c_exit_code, stdout+stderr) do |command|
+                       //              return _exec_thread(command, opts, ctx, 
block)
+                       //            end
+                       //          end
+                       
+                       } // end if (ctx)
+       
+               } catch ( Exception ex ) {
+                       ex.printStackTrace();
+                       // try to include host name (don't call #name() b/c 
that may exec! again which could fail)
+               stderr = command+" "+_name+" "+ex.getMessage()
+               exit_code = -253
+                       
+                       def sw = new java.io.StringWriter()
+                       def pw = new java.io.PrintWriter(sw)
+                       
+                       ex.printStrackTrace(pw)
+                       
+                       pw.flush()
+                       
+                       stderr += " "+sw.toString()
+
+               throw ex
+               }
+               // ret could be set to be a Command.Actual already. otherwise 
return STDOUT, STDERR, exit-code
+               if (ret==null)
+                       ret = [output:stdout, error:stderr, exit_code:exit_code]
+               return ret
+       } // def _exec_thread
+       
+       static def exec_copy_stream(src, dst, type, lh, block, max_len) {
+               def buf = new byte[128]
+               def len = 0
+               def total_len = 0
+               
+               try {
+                       while ( ( len = src.read(buf, 0, 128)) != -1 ) {
+                               dst.write(buf, 0, len)
+               
+                               if (block) {
+                                       lh.post(type, buf)
+                                       block.call(lh)
+                               }
+               
+                               if (max_len > 0) {
+                                       total_len += len
+                                       // when output limit reached, stop 
copying automatically
+                                       if (total_len > max_len)
+                                               break
+                               }
+                       }
+               } finally {
+                       src.close()
+               }
+       } // end def exec_copy_stream
+
+       static def exec_copy_lines(input, type, lh, block, max_len) {
+               def o = ''
+               
+               input = new BufferedReader(input)
+//             
+//             while true do
+//             try {
+//             line = input.readLine()
+//             } catch ( Ex)
+//             break
+//             end
+//             
+//             if line.null?
+//             break
+//             end
+//             
+//             line += "\n"
+//             
+//             o += line
+//             
+//             if block
+//             lh.post(type, line)
+//             block.call(lh)
+//             end
+//             
+//             if max_len > 0
+//             // when output limit reached, stop copying automatically
+//             if o.length > max_len
+//             break
+//             end
+//             end
+//             end
+//             begin
+//             input.close
+//             rescue
+//             end
+               
+               o
+       } // end def exec_copy_line
+
+       // cache of information about the host (this info is only retrieved 
once from the actual host)
+       protected def dir_stack, cwd, _systeminfo, _name, _osname, 
_systemdrive, _systemroot, posix, is_windows, _appdata, _appdata_local, 
_tempdir, _userprofile, _programfiles, _programfilesx86, _homedir
+
+//def clone(clone)
+//// copy host information cache to the clone
+//// TODO clone._systeminfo = @_systeminfo
+////      //clone.lock = @lock
+////      clone._osname = @_osname
+////      clone._systemroot = @_systemroot
+////      clone._systeminfo = @_systeminfo
+////      clone._systemdrive = @_systemdrive
+////      clone.posix = @posix
+////      clone.is_windows = @is_windows
+////      clone._name = @_name
+////      clone._appdata = @_appdata
+////      clone._appdata_local = @_appdata_local
+////      clone._tempdir = @_tempdir
+////      clone._userprofile = @_userprofile
+////      clone._programfiles = @_programfiles
+////      clone._programfilesx86 = @_programfilesx86
+////      clone._homedir = @_homedir
+//clone
+//end
+
+       def systeminfo_line(target, ctx) {
+               def out_err = systeminfo(ctx)
+               
+               out_err.split("\n").each { line ->
+                       if (line.startsWith("$target:")) {
+                               line = line.substr("$target:".length())
+
+                return line.strip()
+                       }
+               }
+               return null
+       }
+
+} // end abstract class Host
diff --git a/PFTT/lib/HostList.groovy b/PFTT/lib/HostList.groovy
new file mode 100644
index 0000000..a4e9351
--- /dev/null
+++ b/PFTT/lib/HostList.groovy
@@ -0,0 +1,5 @@
+package com.mostc.pftt
+
+class HostList extends AbstractHostList {
+       
+} 
diff --git a/PFTT/lib/HostsManager.groovy b/PFTT/lib/HostsManager.groovy
new file mode 100644
index 0000000..96cd04b
--- /dev/null
+++ b/PFTT/lib/HostsManager.groovy
@@ -0,0 +1,5 @@
+package com.mostc.pftt
+
+class HostManager extends HostList {
+
+} // end class HostManager
diff --git a/PFTT/lib/LocalHost.groovy b/PFTT/lib/LocalHost.groovy
new file mode 100644
index 0000000..e367272
--- /dev/null
+++ b/PFTT/lib/LocalHost.groovy
@@ -0,0 +1,273 @@
+package com.mostc.pftt
+
+import java.util.Timer
+import java.util.TimerTask
+import java.io.*
+import java.lang.ProcessBuilder
+import java.io.BufferedReader
+import java.io.FileReader
+import java.io.ByteArrayOutputStream
+import java.io.BufferedOutputStream
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.io.BufferedInputStream
+import java.util.HashMap
+import java.lang.Byte
+
+import com.STDIN;
+import com.mostc.pftt.*
+
+//LocalHost.metaClass {
+//     upload = {copy}
+//     download = {copy}       
+//}
+class LocalHost extends Host {
+       
+       @Override
+       def upload(local_file, remote_path, ctx, opts=[]) {
+               copy(local_file,  remote_path, ctx)
+       }
+       
+       @Override
+       def env_values(ctx=null) {
+      //Hash.new(System.getenv()) //ENV.keys
+       }
+    
+       @Override
+    def env_value(name, ctx=null) {
+               if (name=='HOSTNAME' && isPosix(ctx)) {
+                       // Linux: for some reason this env var is not available
+                       //
+                       // execute program 'hostname' instead
+                       return line('hostname', ctx)
+               }
+               System.getenv(name) // LATER ENV[name]
+    } // end def env_value
+    
+       @Override
+    def reboot_wait(seconds, ctx) {
+//      if (ctx) {
+//        // get approval before rebooting localhost
+//        if 
(ctx.new(Tracing::Context::SystemSetup::Reboot).approve_reboot_localhost()) {
+//          return super.reboot_wait(seconds, ctx)          
+//             }
+//      } else {
+        return super.reboot_wait(seconds, ctx)
+//      }
+    }
+    
+//    def clone
+//      clone = Host::Local.new()
+//      super(clone)
+//    end
+    
+       @Override
+    def close() {
+      // nothing to close
+    }
+    
+//    def toString() {
+//      if (isPosix())
+//        return 'Localhost (Posix)'
+//      else if (isWindows())
+//        return 'Localhost (Windows)'
+//      else
+//        return 'Localhost (Platform-Unknown)'
+//      }
+    
+       @Override        
+    def isAlive() {
+               // was able to call this method therefore must be true (if 
false, couldn't have called this method)
+      true
+    }
+    
+       @Override
+    def write(string, path, ctx) {
+      // writes the given string to the given file/path
+      // overwrites the file if it exists or creates it if it doesn't exist
+      //
+//      if (ctx) {
+//        ctx.write_file(self, string, path) |new_string, new_path| {
+//          return write(new_string, new_path, ctx)
+//        }
+//      }
+      
+      mkdir(File.dirname(path), ctx)
+            
+      output = BufferedOutputStream.new(FileOutputStream.new(path))
+      output.write(string, 0, string.length)
+      output.close
+    }
+            
+       @Override
+    def read_lines(path, ctx=null, max_lines=16384) {
+      def lines = []
+         def line
+        
+      def reader = new BufferedReader(new FileReader(path))
+         while ( ( line = reader.readLine() ) != null && !(lines.size()> 
max_lines)) {
+                 lines.add(line)
+      }
+      reader.close()
+      
+      lines
+    }
+    
+       @Override
+    def read(path) {
+      def output = new ByteArrayOutputStream(1024)
+      def input = new BufferedInputStream(new FileInputStream(path))
+
+      copy_stream(input, output)      
+      
+      input.close
+      
+      output.toString
+    }
+       
+       @Override
+       def getTime(ctx=null) {
+               new Date()      
+       }
+
+       @Override
+    def cwd(ctx=null) {
+      if (ctx) { 
+        ctx.fs_op0(self, EFSOp.cwd) {
+          return cwd(ctx)
+        }
+      }
+      return Dir.getwd()
+    }
+        
+       @Override
+    def cd(path, hsh, ctx=null) {
+      if (ctx) {
+        ctx.fs_op1(self, EFSOp.cd, path) |new_path| {
+          return cd(new_path, hsh, ctx)
+        }
+      }
+         
+      make_absolute(path)
+         
+      if (!path) {
+        // popd may have been called when @dir_stack empty
+        throw new IllegalArgumentException("path not specified")
+      }
+      
+//      Dir.chdir(path)
+          
+         // TODO dir_stack.clear unless hsh.delete(:no_clear) || false
+          
+      return path
+    } // end def cd
+
+       @Override
+    def directory(path, ctx=null) {
+      if (ctx) {
+        ctx.fs_op1(self, EFSOp.is_dir, path) |new_path| {
+          return directory(new_path, ctx)
+        }
+      }
+         
+      make_absolute(path)
+         
+      new File(path).isDirectory()
+    }
+
+    // list the immediate children of the given path
+       @Override
+    def list(path, ctx) {
+      if (ctx) {
+        ctx.fs_op1(self, EFSOp.list, new_path) |new_path| {
+          return list(new_path, ctx)
+        }
+      }
+         
+      make_absolute(path)
+         
+//      Dir.entries( path ).map! do |entry|
+//        next null if ['.','..'].include? entry
+//        entry
+//      end.compact
+      }
+    
+//    def glob(path, spec, ctx, &blk) {
+//      if (ctx) {
+//        ctx.fs_op2(self, :glob, path, spec) |new_path, new_spec| {
+//          return glob(new_path, new_spec, ctx, blk)
+//        }
+//      }
+//       
+//      make_absolute(path)
+//       
+//      Dir.glob("//{path}///{spec}", &blk)
+//    }
+    
+       @Override
+    def isRemote() {
+      false
+    }
+    
+       @Override
+    def mtime(file, ctx=null) {
+      File.mtime(file).to_i
+    }
+    
+    static def copy_stream(input, output) {
+      def tmp = new  byte[1024]
+      while (true) {
+        //input.available
+        def len = input.read(tmp, 0, 1024)
+        if (len < 0) {
+          break
+        }
+        output.write(tmp, 0, len)
+//          len = 0// TODO input.read(tmp, 0, 1024)
+//          if len < 0
+            break
+//          end
+//          // TODO output.write(tmp, 0, len)
+//        end
+      }
+    } // end def copy_stream
+    
+    protected
+    
+    def _exist(file, ctx) {
+      new File(file).exists()
+    }
+    
+    def move_file(from, to, ctx) {
+      new File(from).renameTo(new File(to))
+    }
+    
+    def _exec_impl(command, opts, ctx, block) {
+               // TODO
+      def lh = STDIN.exec_impl(command, null, null, 0, null)
+        
+      if (opts.exec_handle) {
+        return lh
+      } else {
+        def ret = lh.run_read_streams()
+        return [output:ret[0], error:ret[1], exit_code:ret[2]]
+      }
+    } // end def _exec_impl
+        
+    def _delete(path, ctx) {
+      make_absolute(path)
+
+      new File(path).delete()
+    }
+
+    def _mkdir(path, ctx) {
+      make_absolute(path)
+
+      new File(path).mkdir()
+    }
+       
+       def _make_absolute(path) {
+               new File(path).getAbsolutePath()
+       }
+    
+} // end public class LocalHost
diff --git a/PFTT/lib/Middleware.groovy b/PFTT/lib/Middleware.groovy
new file mode 100644
index 0000000..1b3c839
--- /dev/null
+++ b/PFTT/lib/Middleware.groovy
@@ -0,0 +1,132 @@
+package com.mostc.pftt
+
+abstract class Middleware {
+    Host host
+    Build build
+    Scenario scenario
+    
+    def Middleware(Host host, Build build, Scenario scenario) {
+      this.host = host
+      this.build = build
+      this.scenario = scenario
+    }
+    
+    def start_test_case_group(scenario, test_case_group) {
+    }
+    
+    def thread_pool_size(test_case_base_class=null) {
+      1
+    }
+        
+    def isInstalled(ctx=null) {
+      // Override
+      true
+    }
+    
+    def uninstall(ctx=null) {
+      // Override
+    }
+    
+    def ensure_installed(ctx=null) {
+      unless(isInstalled(ctx)) {
+        install(ctx)
+      }
+    }
+    
+    def start(ctx=null) {
+      // Override
+      ensure_installed(ctx)
+    }
+    
+    def stop(ctx=null) {
+      // Override
+               host.close()
+    }
+       
+       def close(ctx=null) {
+               stop(ctx)
+       }
+    
+    def restart(ctx=null) {
+      stop(ctx)
+      start(ctx)
+    }
+    
+    def group_test_cases(test_cases, scenario) {
+      [test_cases]
+    }
+       
+       protected def shouldDisableAEDebug() {
+               true
+       }
+       
+       def disableFirewall(ctx=null) {
+       }
+    
+    def install(ctx=null) {
+               // Override
+               // client should do install of all middlewares BEFORE running 
pftt-host
+               // because installation may require rebooting and pftt-host 
doesn't support reboots
+               //
+               // (could get around this for php or other projects by running 
pftt-host between reboots if only one middleware can be installed at
+               //  a time, but for php, all middlewares can be installed at 
once, so just do it all at once (in 1 stage) because it'll be less bad if any 
install fails)
+               //
+               if (host.isWindows(ctx)) {
+               // turn on file sharing... (add PFTT) share
+               // make it easy for user to share files with this windows 
machine
+               // (during cleanup or analysis or triage, after tests have run)
+               //
+               // user can access PHP_SDK and the system drive ( \\hostname\C$ 
\\hostname\G$ etc...)
+               //                      
+                       host.exec("NET SHARE 
PHP_SDK=$host.systemdrive()\\php-sdk /Grant:$host.username(),Full", ctx)
+                       
+                       //
+                       def error_mode_change = false
+                       if (shouldDisableAEDebug()) {
+                               // remove application exception debuggers (Dr 
Watson, Visual Studio, etc...)
+                               // otherwise, if php crashes a dialog box will 
appear and prevent PHP from exiting
+                               
host.registry_delete('HKLM\\Software\\Microsoft\\Windows 
NT\\CurrentVersion\\AeDebug', 'Debugger', ctx)
+                               // disable Hard Error Popup Dialog boxes (will 
still get this even without a debugger)
+                               // see http://support.microsoft.com/kb/128642
+                               //
+                               def query = 
host.registry_query('HKLM\\SYSTEM\\CurrentControlSet\\Control\\Windows', 
'ErrorMode', 'REG_DWORD', ctx)
+                               if (query['REG_DWORD'] != '0x2') {
+                                       // check if registry value is already 
changed (so we don't reboot if we don't have to)
+                                       
host.registry_add('HKLM\\SYSTEM\\CurrentControlSet\\Control\\Windows', 
'ErrorMode', '2', 'REG_DWORD', ctx)
+                                                                               
+                                       // for ErrorMode change to take effect, 
host must be rebooted!
+                                       error_mode_change = true
+                               }
+                               
+                       }
+                       //
+                       
+                       // disable windows firewall
+                       disableFirewall(ctx)
+                       
+                       // show filename extensions
+                       host.exec('REG ADD 
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced" /v 
HideFileExt /d 0 /t REG_DWORD /f', ctx)
+
+                       if (error_mode_change) {
+                               // for ErrorMode change to take effect, host 
must be rebooted!
+                               host.reboot(ctx)
+                       }
+
+               } else {
+                       //  LATER add share to samba if posix and samba already 
installed
+               }
+//      if @host.windows?(ctx)
+
+//        if @host.credentials // TODO
+//          @host.exec!('NET SHARE PFTT='+@host.systemdrive+'\\php-sdk 
/Grant:"'+@host.credentials[:username]+'",Full', ctx)
+//        end
+//        
+      
+//      end 
+    } // end def install
+       
+    def isRunning(ctx=null) {
+      true
+    }
+
+} // end public abstract class Middleware
diff --git a/PFTT/lib/RemoteHost.groovy b/PFTT/lib/RemoteHost.groovy
new file mode 100644
index 0000000..20f450b
--- /dev/null
+++ b/PFTT/lib/RemoteHost.groovy
@@ -0,0 +1,77 @@
+package com.mostc.pftt
+ 
+abstract class RemoteHost extends Host {
+      
+      def isRemote() {
+        true
+      }
+         
+         @Override
+         def getTime(ctx=null) {
+                 if (isPosix(ctx)) {
+                         new Date(line('date', ctx))
+                 } else {
+                         // gets the host's time/date
+                         new Date(unquote_line('date /T', ctx)+' 
'+unquote_line('time /T', ctx))
+                 }
+         }
+      
+      def canStream() {
+        false // ssh can
+      }
+      
+      def ensure_7zip_installed(ctx) {
+        if (tried_install_7zip) {
+          return installed_7zip
+        }
+        
+        // TODO Util::Install::7zip.install(self)
+        
+        installed_7zip = false
+      } // end def ensure_7zip_installed ctx
+      
+      def isRebooting() {
+        rebooting
+      }
+      
+      def reboot_wait(seconds, ctx) {
+        super.reboot_wait(seconds, ctx)
+        
+        reboot_reconnect_tries = ( seconds / 60 ).to_i + 1
+        if (reboot_reconnect_tries < 3) {
+          reboot_reconnect_tries = 3
+        }
+        
+        rebooting = true
+        // will have to recreate sockets when it comes back up
+        // session() and sftp() will block until then (so any method 
+        // LATER using sftp() or session() will be automatically blocked 
during reboot(good))
+        close
+      }
+      
+      def RemoteHost(opts={}) {
+        reconnected_after_reboot
+      }
+      
+//      def clone(clone) {
+//        clone.rebooting                              = rebooting
+//        clone.rebooting_reconnect_tries = rebooting_reconnect_tries
+//        clone.tried_install_7zip                 = tried_install_7zip
+//        clone.installed_7zip                       = installed_7zip
+//        super(clone)
+//      }
+      
+      protected
+      
+      // for //clone()
+//      attr_accessor :rebooting, :rebooting_reconnect_tries, 
:tried_install_7zip, :installed_7zip
+      
+      def reconnected_after_reboot() {
+        tried_install_7zip = false
+        // maybe reverted to a snapshot (if host is a virtual machine)
+        installed_7zip = false
+        rebooting = false
+        rebooting_reconnect_tries = 0
+        }
+      
+} // end public class RemoteHost
diff --git a/PFTT/lib/SSHHost.groovy b/PFTT/lib/SSHHost.groovy
new file mode 100644
index 0000000..5bf8e82
--- /dev/null
+++ b/PFTT/lib/SSHHost.groovy
@@ -0,0 +1,690 @@
+package com.mostc.pftt;
+
+// SSH - Secure Shell
+// for Linux/UNIX and Windows systems 
+//
+// Supported SSH Servers:
+// * OpenSSH
+// * Apache SSHD (Apache MINA)
+// * SSH Tools
+//
+// Note:
+// * KTS-SSH may have occasional problems with some SFTP operations
+
+import java.io.*
+import java.util.ArrayList
+
+import com.sshtools.j2ssh.transport.HostKeyVerification
+import com.sshtools.j2ssh.SshClient
+import com.sshtools.j2ssh.authentication.AuthenticationProtocolState
+import com.sshtools.j2ssh.authentication.PasswordAuthenticationClient
+
+class SSHHost extends RemoteHost {
+
+       @Override
+    def canStream() {
+      true
+    }
+
+       @Override
+    def close() {
+      try {
+        ssh.close()
+        sftp.close()
+      } finally {
+        ssh = null
+        sftp = null
+      }
+    }
+    
+//    attr_reader :credentials
+//    
+//    def SSHHost (opts={}) {
+//      @sftp            = opts[:sftp]
+//      @session     = opts[:sftp_client]
+//      @sftp_client = opts[:sftp_client]
+//      
+//      @credentials = {
+//        :address     => opts[:address],
+//        :port            => opts[:port]||22,
+//        :username => opts[:username],
+//        :password  => opts[:password],
+//      }
+//      
+//      super
+//    }
+    
+//    def clone() {
+//      clone = Host::Remote::Ssh.new(
+//        // pass session to clone so it'll share the ssh client (if its 
already connected)
+//          :session      => @session,
+//          :address     => @credentials[:address],
+//          :port            => @credentials[:port],
+//          :username => @credentials[:username],
+//          :password   => @credentials[:password]
+//        )
+//        super(clone)
+//    }
+        
+       @Override
+    def isAlive(ctx=null) {
+      try {
+        return exist(cwd(ctx), ctx)
+      } catch (Throwable t) {
+        if_closed()
+        return false
+      }
+    }
+    
+//    def toString() {
+//      if (isPosix()) {
+//        "Remote Posix //{@credentials[:address] || name()}"
+//      } else {
+//        "Remote Windows //{@credentials[:address] || name()}"
+//      }
+//    }
+
+    // checks for the current working directory
+    // be aware that a command being executed may possibly change this value 
at some point
+    // during its execution (in which case, PFTT will only detect that after 
the command has
+    //  finished execution)
+       @Override
+    def cwd(ctx=null) {
+      if (ctx) {
+        ctx.fs_op0(self, EFSOp.cwd) {
+          return cwd(ctx)
+        }
+      }
+      
+//      cwd ||= format_path(sftp_client(ctx).pwd)
+    }
+//    alias :pwd :cwd
+    
+       @Override
+       def cd(path, ctx=null) {
+               if (!path) {
+                       // popd may have been called when dir_stack empty
+                       throw new NullPointerException("path not specified")
+               }
+      if (ctx) {
+        ctx.fs_op1(self, EFSOp.cd, path) |new_path| {
+          return cd(new_path, hsh, ctx)
+        }
+      }
+      
+         path = format_path(path)
+      path = make_absolute(path)
+      
+      
+      // e-z same command on both
+      cmd("cd \"$path\"", ctx)
+        
+      // cwd is cleared at start of exec, so while in exec, cwd will be empty 
unless cwd() called in another thread
+      cwd = path
+        
+// TODO      dir_stack.clear unless hsh.delete(:no_clear) || false
+        
+      return path
+    }
+    
+       @Override
+    def env_values(ctx=null) {
+      env_str = cmd('set', ctx).output
+      
+      def env = []
+      for (def line : env_str.split("\n")) {
+        def i = line.index('=')
+        if (i) {
+          def name = line.substring(0, i)
+          def value = line.substring(i+1, line.length())
+          
+          if (isPosix(ctx)) {
+                         if (value.startsWith('"')||value.startsWith("'"))
+                               value = value.substring(1, value.length()-2)
+          }
+          
+          env[name] = value
+        }
+      }
+      
+      return env
+    } // end def env_values
+    
+       @Override
+    def env_value(name, ctx=null) {
+      // get the value of the named environment variable from the host
+      if (isPosix(ctx)) {
+        return unquote_line('echo $'+name, ctx)
+      } else {
+        def out = unquote_line("echo %$name%", ctx)
+        if (out == "%$name%") {
+          // variable is not defined
+          return ''
+        } else {
+          return out
+        }
+      }
+    } // end def env_value
+    
+       @Override
+    def mtime(file, ctx=null) {
+      if (ctx) {
+        ctx.fs_op1(self, EFSOp.mtime, file) |new_path| {
+          return mtime(new_path, ctx)
+        }
+      }
+      
+      try {
+        return sftp(ctx).getAttributes(file).getModifiedTime().longValue()
+      } catch (Exception ex) {
+        return 0
+      }
+    }
+
+       @Override
+    def directory(path, ctx=null) {
+      if (ctx) {
+        ctx.fs_op1(self, EFSOp.is_dir, path) |new_path| {
+          return directory(new_path, ctx)
+        }
+      }
+      
+      try {
+        return sftp(ctx).getAttributes(file).isDir
+      } catch(Exception ex) {
+        if_closed()
+        return false
+      }
+    }
+    
+//    def glob(path, spec, ctx, &blk) {
+//      if (ctx) {
+//        ctx.fs_op2(self, :glob, path, spec) |new_path, new_spec| {
+//          return glob(new_path, new_spec, ctx, blk)
+//        }
+//      }
+//                     
+//      l = list(path, ctx)
+//      unless spec.nil? or spec.length == 0
+//        l.delete_if do |e|
+//          !(e.include?(spec))
+//        end
+//      end      
+//      return l
+//    }
+    
+       @Override
+    def list(path, ctx) {
+      if (ctx) {
+        ctx.fs_op1(self, EFSOp.list, path) |new_path| {
+          return list(new_path, ctx)
+        }
+      }
+      
+      try {
+        list = []
+        _sftp = sftp(ctx)
+        dir = _sftp.openDirectory(path)
+        children = ArrayList.new()
+      
+        while (_sftp.listChildren(dir, children) > -1) {
+        }
+              
+        dir.close()
+        
+        i = 0 
+        while (i < children.size) {
+          list.push(format_path(children.get(i).getAbsolutePath()))
+            i += 1
+        }
+        
+        return list
+      } catch (Exception ex) { 
+//        if_closed()
+////        if (ctx) {
+////          ctx.pftt_exception(self, $!, self)
+////        } else {
+////          // TODO Tracing::Context::Base.show_exception($!)
+////        }
+        throw ex
+      }
+    } // end def list
+    
+       @Override
+    def read_lines(path, ctx=null, max_lines=16384) {
+      def output = new ByteArrayOutputStream()
+      
+      sftp(ctx).get(path, output)
+      
+      def reader = new BufferedReader(new InputStreamReader(output))
+         while ( ( line = reader.readLine() ) != null && !(lines.size()> 
max_lines)) {
+                 lines.add(line)
+         }
+         reader.close()
+         
+         lines
+    }
+    
+       @Override
+    def read(path, ctx=null) {
+      def output = new ByteArrayOutputStream()
+      sftp(ctx).get(path, output)
+            
+      if (ctx) {
+        ctx.read_file(self, out, path) |new_path| {
+          return read(new_path, ctx)
+        }
+      }
+      
+      output.toString()
+    }
+
+       @Override
+    def write(string, path, ctx) {
+      if (ctx) {
+        ctx.write_file(self, string, path) |new_string; new_path| {
+          return write(new_string, new_path, ctx)
+        }
+      }
+      
+      mkdir(File.dirname(path), ctx)
+      
+      // TODO
+      try {
+        def input = new ByteArrayInputStream(string.length)
+        input.write(string, 0, string.length)
+        sftp(ctx).put(input, path)
+        return true
+      } catch (Exception ex) { 
+        if_closed()
+//        if (ctx) {
+//          ctx.pftt_exception(self, $!, self)
+//        } else {
+//          Tracing::Context::Base.show_exception($!)
+//        }
+        throw ex
+      }
+    }
+
+    // uploads file/folder to remote host
+    //
+    // local_file - local file/folder to copy
+    // remote_path - remote path to store at
+    // ctx - context
+    // opts - hash of
+    //
+    // :mkdir - true|false - default=true
+    //           makes a directory to contain multiple files or a folder when 
uploading
+    //
+    // :transport_archive - true|false - default=false
+    //           will (attempt to) archive the local files/folders into a 7zip 
archive file, 
+    //           upload it to the remote host and then decompress it. will 
install 7zip on
+    //           the remote host if it is not present. If can't upload and 
execute 7zip on
+    //           remote host, operation will fail. 
+    //             see transport_normal_if_decompress_failed
+    //
+    // :skip_cached_archive_mtime_check - true|false - default=false
+    //           if true, if archive is already in cache, will use it no 
matter what.
+    //           if false, will check if the original file(s)/folder(s) have 
been modified,
+    //             and if yes, will replace the cached archive with the 
changes.
+    //
+    // :transport_normal_if_decompress_failed - true|false - default=true
+    //           if can't decompress fail on remote host, automatically falls 
back on
+    //           uploading it without archiving
+    //
+    // :prearchived - true|false - default=false
+    //           indicates local file has already been archived AOT. archive 
will be
+    //           uploaded and decompressed IF :transport_archive=true
+    //
+    // :transport_archive_no_local_cache - true|false - default=false
+    //           if true, doesn't keep archive in local cache. otherwise, will 
keep archive in
+    //           local cache to avoid recompressing it for next time.
+    //
+       @Override
+    def upload(local_file, remote_path, ctx, opts=[]) {
+      if (ctx) {
+        ctx.fs_op2(self, EFSOp.upload, local_file, remote_path) 
|new_local_file; new_remote_path| {
+          return upload(new_local_file, new_remote_path, ctx, opts)
+        }
+      }
+      
+      // if remote_path exists operation will fail!
+      //   therefore, you should check if it exists first
+      //    then, depending on need, delete() or skip the upload
+      //
+      // if local_file is a file, remote_path MUST be a file too!!!!!!!!!!!!!
+      // (if local_file is a dir, remote_path can be a dir)
+      // LATER remove this gotcha/rule/limitation (implement using 
File.basename)
+      //
+      if (manager && manager.local_host.isWindows()) {
+        local_file = to_windows_path(local_file)
+      } else {
+        local_file = to_posix_path(local_file)
+      }
+      if (isWindows()) {
+        remote_path = to_windows_path(remote_path)
+      } else {
+        remote_path = to_posix_path(remote_path)
+      }
+      //
+      
+      remote_path = no_trailing_slash(remote_path)
+      
+      //
+      transport_archive = false
+      if (opts.transport_archive) {
+        // ensure 7zip is installed on remote host so it can be decompressed
+        if (ensure_7zip_installed(ctx)) {
+          unless (opts.prearchived) {
+            // archive file
+            local_path = manager.cache_archive(local_path, remote_path, ctx, 
opts)
+          }
+          transport_archive = true
+        }
+      }
+      //
+      
+      // ensure the target directory exists (or we'll get an error)
+      if (opts.mkdir!=false) {
+        mkdir(File.dirname(remote_path), ctx)
+      }
+      try {
+        
+        // TODO
+        
sftp(ctx).put(BufferedInputStream.new(FileInputStream.new(local_file)), 
remote_path)
+        
+        return true
+      } catch (Exception ex) { 
+        if_closed()
+//        if ctx
+//          ctx.pftt_exception(self, $!, self)
+//        else
+//          Tracing::Context::Base.show_exception($!)
+        throw ex
+      }
+      
+      //
+      if (transport_archive) {
+        // decompress archive
+        decompress_ret = exec!("7za a -y -o//{File.dirname(remote_path)} 
//{remote_path}")
+        
+//        if (opts[:transport_archive_no_local_cache]) {
+//          unless (opts[:prearchived]) {
+//            if (!opts.has_key?(:multi_host) or is_last?) {
+//              manager.local_host.delete(local_cache_archive, ctx)
+//            }
+//            // TODO sync
+//            
+//          }
+//        // else: leave archive in cache for next time
+//        }
+        
+//        if (!decompress_ret[2]) {
+//          if (opts[:transport_normal_if_decompress_failed]) {
+//            // upload normal file
+//            return upload(local_file, remote_path, ctx, 
{:mkdir=>opts[:mkdir]!=false})
+//          } else {
+//            raise 'RemoteDecompressFail', decompress_ret
+//          }
+//        }
+        
+        // file uploaded and decompressed ok
+        
+        // leave file on remote server if decompression failed for manual 
triage purposes later
+        delete(remote_archive, ctx)
+      }
+      //
+      
+    } // end def upload
+    
+    def download(remote_file, local_path, ctx) {
+      if (ctx) {
+//        ctx.fs_op2(self, EFsOp.download, remote_file, local_path) do 
|new_remote_file, new_local_path| {
+//          return download(new_remote_file, new_local_path, ctx)
+//        }
+      }
+      
+      //
+      try {
+        // TODO
+        sftp(ctx).get(remote_file, 
BufferedOutputStream.new(FileOutputStream.new(local_path)))
+        
+        return true
+      } catch (Exception ex) {
+        if_closed()
+//        if ctx
+//          ctx.pftt_exception(self, $!, self)
+//        else
+//          Tracing::Context::Base.show_exception($!)
+        throw ex
+      }
+      //
+    } // end def download
+
+    protected
+    
+    def _exist(path, ctx) {
+      try {
+        def a = sftp(ctx).getAttributes(path)
+        return ( a.isFile() || a.isDirectory() )
+      } catch (Exception ex) {
+        if_closed()
+        return false
+      }
+    }
+    
+    def move_file(from, to, ctx) {
+      move_cmd(from, to, ctx)
+    }
+    
+    def copy_file(from, to, ctx, mkdir) {
+      to = dirname(to)
+      if (mkdir) {
+        mkdir(to, ctx)
+      }
+      
+      copy_cmd(from, to, ctx)
+    }
+    
+    def if_closed() {
+      try {
+        if (session && session.isConnected()) {
+          session.disconnect()
+          session = null
+        }
+        if (sftp_client && sftp_client.isClosed()) {
+          sftp_client.quit()
+          sftp_client = null
+        }
+        if (sftp && ( sftp.isClosed() || !sftp.isOpen() ) {
+          sftp.close()
+          sftp = null
+        }
+      } catch (Exception ex) {
+      }
+    } // end def is_closed
+    
+    class SshExecHandle extends ExecHandle {
+               def channel, stderr, stdout, stderr_len, stdout_len
+      def SshExecHandle(channel) {
+        channel = channel
+        stderr = ''
+        stdout = ''
+        stderr_len = 0
+        stdout_len = 0
+               }
+      
+      def isOpen() {
+       channel.isOpen
+      }
+      
+      def close() {
+        // LATER begin
+        channel.close
+        // rescue
+        // end
+      }
+      
+      def write_stdin(stdin_data) {
+        channel.send_data(stdin_data)
+      }
+      def hasStderr() {
+        stderr.length() > 0
+      }
+      def hasStdout() {
+        stdout.length() > 0
+      }
+      def read_stderr() {
+        def x = stderr
+        stderr = ''
+        return x
+      }
+      def read_stdout() {
+        def x = stdout
+        stdout = ''
+        return x
+      }
+      def post_stdout(data) {
+        stdout = data
+        stdout_len += data.length
+      }
+      def post_stderr(data) {
+        stderr = data
+        stderr_len += data.length
+      }
+      def stdout_full(opts) {
+        opts.max_len > 0 && stdout_len >= opts.max_len
+      }
+      def stderr_full(opts) {
+        opts.max_len > 0 && stderr_len >= opts.max_len
+      }
+    } // end class SshExecHandle
+    
+    def _exec_impl(command, opts, ctx, block) {
+      //
+      if (opts.hasProperty('chdir') && opts.chdir) {
+        if (isWindows(ctx))
+          command = "CMD /C pushd $opts[:chdir] & $command & popd"
+        else
+          command = "pushd $opts[:chdir] && $command && popd"
+      }
+      
+      // type com.sshtools.j2ssh.session.SessionChannelClient
+      exec = session(ctx).openSessionChannel
+      
+         if (opts.hasProperty('env')) {
+                 for (def k:opts.env.keySet()) {
+                         exec.setEnvironmentVariable(k, opts.env[k])
+                 }
+         }
+      
+      def output = new ByteArrayOutputStream(1024)
+      def error = new ByteArrayOutputStream(1024)
+      
+      exec.executeCommand(command)
+      
+         if (opts.hasProperty('stdin_data')) {
+             stdin_data = opts.stdin_data
+             if (stdin_data) {
+               exec.getOutputStream().write(stdin_data, 0, stdin_data.length)
+             }
+         }
+            
+      //
+      def lh = new SshExecHandle(exec)
+      
+      //
+         def o, e
+      if (opts.hasProperty('by_line') && opts.by_line) {
+        o = exec_copy_lines(new InputStreamReader(exec.getInputStream()), 
false, lh, block, opts.max_len)
+        e = exec_copy_lines(new 
InputStreamReader(exec.getStderrInputStream()), true, lh, block, opts.max_len)
+      } else {
+        exec_copy_stream(new BufferedInputStream(exec.getInputStream()), 
output, false, lh, block, opts.max_len)
+        exec_copy_stream(new BufferedInputStream(exec.getStderrInputStream()), 
error, true, lh, block, opts.max_len)
+            
+        o = output.toString()
+        e = error.toString()
+      }
+      //
+      
+      exec.close()
+      
+      exit_code = exec.getExitCode()
+                      
+      if (opts.hasProperty('exec_handle') && opts.exec_handle)
+               lh
+      else
+        [output:output.toString(), error: error.toString(), exit_code: 
exit_code]
+    } // end def _exec_impl
+    
+    def _delete(path, ctx) {
+      if (isWindows(ctx)) {
+        path = to_windows_path(path)
+        
+        cmd("DEL /Q /F \"//{path}\"", ctx)
+      } else {
+        path = to_posix_path(path)
+        
+        exec("rm -rf \"//{path}\"", ctx)
+      }
+    }
+
+    def _mkdir(path, ctx) {
+      try {
+        sftp_client(ctx).mkdir(path)
+        true
+      } catch (Exception ex) { 
+        if_closed
+//        if (ctx) {
+//          ctx.pftt_exception(self, ex, self)
+//        } else {
+//          Tracing::Context::Base.show_exception(ex)
+//        }
+               throw ex
+      }
+    }
+    
+    class AllowAnyHostKeyVerification implements HostKeyVerification {
+      def verifyHost(host, pk) {
+        true
+      }
+    }
+    
+    def session(ctx) {
+      if (session) {
+        return session
+      }
+      
+      session = new SshClient()
+      session.connect(credentials[:address], @credentials[:port], 
AllowAnyHostKeyVerification.new)
+      def pwd = new PasswordAuthenticationClient()
+      pwd.setUsername(credentials[:username])
+      pwd.setPassword(credentials[:password])
+
+      def result = session.authenticate(pwd)
+      
+      if (result != AuthenticationProtocolState.COMPLETE) {
+        throw new IllegalStateException('WrongPassword')
+      }
+      
+      session
+    } // end def session
+    
+    def sftp_client(ctx) {
+      if (sftp_client) {
+        return sftp_client
+      }
+      
+      sftp_client = session(ctx).openSftpClient
+    }
+    
+    def sftp(ctx) {
+      if (sftp) {
+        return sftp
+      }
+      
+      sftp = session(ctx).openSftpChannel //SftpSubsystemClient      
+    } // end def sftp
+
+} // end public class SSHHost
+                       
\ No newline at end of file
diff --git a/PFTT/lib/Scenario.groovy b/PFTT/lib/Scenario.groovy
new file mode 100644
index 0000000..f360cc2
--- /dev/null
+++ b/PFTT/lib/Scenario.groovy
@@ -0,0 +1,136 @@
+package com.mostc.pftt
+
+//
+//class Scenario {
+//  
+//  attr_accessor :working_fs, :remote_fs, :date, :database
+//  attr_reader :id
+//  
+//  def initialize(id, working_filesystem_scenario, *optional_other_scenarios)
+//    @id = id
+//    @working_fs = working_filesystem_scenario
+//    optional_other_scenarios.each do |scenario|
+//      unless (scenario) { 
+//        next // for from_xml
+//      end
+//      case scenario.scn_type
+//      when :remote_file_system
+//        @remote_fs = scenario
+//      when :date
+//        @date = scenario
+//      when :database
+//        @database = scenario
+//      end
+//    end
+//  end
+//  
+//  //////////////// start message encoding/decoding //////////////////////
+//  def toXML serial
+//    // TODO 
+//  end
+//  
+//  def self.msg_type
+//    'scenario'
+//  end
+//  
+//  def self.fromXML
+//  end
+//  //////////////// end message encoding/decoding //////////////////////
+//  
+//  def execute_script_start(env, test, script_type, deployed_script, 
php_binary, php_build, current_ini, host)
+//    values.each do |ctx|
+//      ctx.execute_script_start(env, test, script_type, deployed_script, 
php_binary, php_build, current_ini, host)
+//    end
+//  end
+//        
+//  def execute_script_stop(test, script_type, deployed_script, php_binary, 
php_build, host)
+//    values.each do |ctx|
+//      ctx.execute_script_stop(test, script_type, deployed_script, 
self.php_binary, php_build, host)
+//    end
+//  end
+//        
+//  def create_ini(platform, ini=nil)
+//    if platform != :windows and platform != :posix
+//      raise ArgumentError, 'platform must be :windows or :posix'
+//    end
+//    
+//    values.each do |scn|
+//      ini = scn.create_ini(platform, ini)
+//    end
+//    
+//    return ini
+//  end
+//  
+//  def values
+//    list = [@working_fs]
+//    if @remote_fs
+//      list << @remote_fs
+//    end
+//    if @date
+//      list << @date
+//    end
+//    if @database
+//      list << @database
+//    end
+//    return list  
+//  end
+//  
+//  def teardown(host)
+//    values.each do |scn|
+//      scn.teardown(host)
+//    end
+//  end
+//  
+//  def deploy(host)
+//    values.each do |scn|
+//      scn.deploy(host)
+//    end
+//  end
+//  
+//  def to_s
+//    "[Set //{@id} //{values.inspect}]"
+//  end
+//  
+//  def == (o)
+//    o.instance_of?(Scenario::Set) and o.id == @id 
+//  end
+//      
+//  class Part
+//    // TODO include PhpIni::Inheritable
+//    
+//    def deploy(host_info)
+//    end
+//    
+//    def teardown(host_info)
+//    end
+//    
+//    def execute_script_start(env, test, script_type, deployed_script, 
deployed_php, php_build_info, php_ini, host, platform)
+//    end
+//    
+//    def execute_script_stop(test, script_type, deployed_script, 
deployed_php, php_build_info, host_info)
+//    end
+//    
+//    def docroot middleware
+//      middleware.docroot
+//    end
+//    
+//    def deployed_php(middleware)
+//      nil
+//    end
+//    
+//    // subclasses should override this!!
+//    def scn_type
+//      return :unknown
+//    end
+//    
+//    def to_s
+//      scn_name
+//    end
+//    
+//    def self.instantiable
+//      All << self
+//    end
+//  end // class Part
+//  
+//} // end class Scenario
+//
diff --git a/PFTT/lib/base.groovy b/PFTT/lib/base.groovy
new file mode 100644
index 0000000..a6274e5
--- /dev/null
+++ b/PFTT/lib/base.groovy
@@ -0,0 +1,97 @@
+package com.mostc.pftt
+
+//
+//require 'tracing.rb'
+//
+//module Base
+//  
+//class RunOptions
+//  
+//  def self.msg_type
+//    'run_options'
+//  end
+//  
+//end
+//  
+//class TestPack < Tracing::FileManager
+//  
+//  attr_reader :correct_test_count
+//  
+//  def initialize correct_test_count
+//    @correct_test_count = correct_test_count
+//  end
+//  
+//  def self.msg_type
+//    'test_pack'
+//  end
+//  
+//  def load_test_case_by_name case_name
+//    []
+//  end
+//  
+//  def load_all_test_cases
+//    []
+//  end
+//  
+//end # class TestPack
+//  
+//  class CaseName
+//    
+//    attr_reader :name_pattern
+//    
+//    def initialize name_pattern
+//      @name_pattern = name_pattern
+//    end
+//    
+//    def self.msg_type
+//      'case_name'
+//    end
+//    
+//    def self.fromXML
+//    end
+//    
+//    def toXML
+//    end
+//    
+//  end # class CaseName
+//  
+//  class AllCases
+//    
+//    attr_reader :correct_test_case_count
+//    
+//    def initialize correct_test_case_count
+//      @correct_test_case_count = correct_test_case_count
+//    end
+//    
+//    def self.msg_type
+//      'all_cases'
+//    end
+//    
+//    def self.fromXML
+//    end
+//    
+//    def toXML
+//    end
+//    
+//  end # class AllCases
+//
+//class Build < Tracing::FileManager
+//  
+//  def self.msg_type
+//    'build'
+//  end
+//  def msg_type # TODO
+//    'build'
+//  end
+//  
+//end # class Build
+//
+//class Configuration < Tracing::FileManager
+//  
+//  def self.msg_type
+//    'configuration'
+//  end
+//  
+//end # class Configuration
+//  
+//end # module Base
diff --git a/PFTT/lib/case_runner.groovy b/PFTT/lib/case_runner.groovy
new file mode 100644
index 0000000..fd58354
--- /dev/null
+++ b/PFTT/lib/case_runner.groovy
@@ -0,0 +1,5 @@
+package com.mostc.pftt
+
+abstract class AbstractCaseRunner {
+     
+} // abstract class AbstractCaseRunner

Reply via email to