Commit: 156640260ed9029d9e185f3f2f6a2d511158a41c
Author: matt <[email protected]> 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='[email protected]+'\\php-sdk
/Grant:"'[email protected][: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