Commit: 7f972b820fb9e0f5dd8f37cb2c39d941480e8d29 Author: v-maf...@microsoft.com <a...@a.com> Fri, 2 Dec 2011 21:57:09 -0800 Parents: 0c5cddd28769cf96b3d11073e0d89fecdf56c384 Branches: master
Link: http://git.php.net/?p=pftt2.git;a=commitdiff;h=7f972b820fb9e0f5dd8f37cb2c39d941480e8d29 Log: improvements to Diff Engine support searching, managing and filtering Diff files from PHPT results Former-commit-id: 01c5eb16c74924b591e2947a456f48c8ac4e6fc5 Changed paths: A PFTT/lib/diff/engine.rb A PFTT/lib/diff/zd.rb A PFTT/lib/diff/zd/all_test_cases.rb M PFTT/lib/host.rb M PFTT/lib/host/local.rb M PFTT/lib/host/remote/psc/client.rb M PFTT/lib/host/remote/psc/recovery_manager.rb M PFTT/lib/host/remote/psc/remote_host.rb M PFTT/lib/host/remote/ssh.rb M PFTT/lib/middleware.rb M PFTT/lib/middleware/cli.rb M PFTT/lib/test/case/phpt.rb M PFTT/lib/test/result/phpt.rb M PFTT/lib/test/runner/stage/phpt/package.rb M PFTT/lib/test/runner/stage/phpt/upload.rb M PFTT/lib/tracing/context.rb M PFTT/lib/tracing/prompt/diff.rb M PFTT/lib/util/package.rb A PFTT/src/se/datadosen/component/RiverLayout.class A PFTT/src/se/datadosen/component/RiverLayout.java A PFTT/src/se/datadosen/component/Ruler.class
diff --git a/PFTT/lib/diff/engine.rb b/PFTT/lib/diff/engine.rb new file mode 100644 index 0000000..e69de29 diff --git a/PFTT/lib/diff/zd.rb b/PFTT/lib/diff/zd.rb new file mode 100644 index 0000000..d5b1b5d --- /dev/null +++ b/PFTT/lib/diff/zd.rb @@ -0,0 +1,108 @@ + +module Diff + module ZD + +class BaseZD + + attr_reader :diff + + def initialize(diff) + unless [:expected, :actual, :added, :removed, :added_and_removed, :org_expected, :org_actual].include?(diff) + raise ArgumentError, diff + end + + @diff = diff + end + + def zd_label + "#{level_label()} #{diff_label()}" + end + + def diff_label + # Override + end + + def sym + # Override + end + + def has_sym? + sym.length > 0 + end + + def level_label + # Override + end + + def level + # Override + end + + def is_diff?(o_diff) + diff()==o_diff + end + + def is_level?(o_level) + level() == o_level + end + + def save_chunk_replacements + end + + def load_chunk_replacements + end + + def save_diff + end + + def delete(id) + end + + def add(id) + end + + def pass(id) + end + + def change(id, to) + end + + def find(needle) + # Override + end + + def iterate + # Override + # DiffIterator.new + end + +end # class ZD + +class DiffIterator + + def zd + end + + def has_next? + end + + def next + end + + def delete + end + + def add + end + + def pass + end + + def change(to) + end + +end # class DiffIterator + + + end # module ZD +end # module Diff \ No newline at end of file diff --git a/PFTT/lib/diff/zd/all_test_cases.rb b/PFTT/lib/diff/zd/all_test_cases.rb new file mode 100644 index 0000000..231204b --- /dev/null +++ b/PFTT/lib/diff/zd/all_test_cases.rb @@ -0,0 +1,262 @@ + +module Diff + module ZD + module AllTestCases + +class BaseAllTestCases < BaseZD + + def level + :all + end + + def level_label + 'All' + end + + def find(needle) + down_needle = needle.downcase + up_needle = needle.upcase + + dirs().each do |dir| + files().each do |file_ext| + Dir.glob("#{dir}/**/*#{file_ext}") do |file_name| + # TODO document + # + unless accept_file(dir, Host.sub(dir, file_name)) + next + end + + # search file for needle + IO.readlines(file_name).each do |line| + if true#accept_line(line) + if check_line(line, down_needle, up_needle) + + # TODO report match + puts line + + end + end + end + end + end + end + end # def find + + protected + + def accept_file(dir, file_name) + # can Override + true + end + + def dirs + # Override + [] + end + + def check_line(line, down_needle, up_needle) + return ( line.downcase.include?(down_needle) or line.upcase.include?(up_needle) ) + end + + def files + if [:added, :removed, :added_and_removed].include?(diff()) + return ['.diff'] + elsif [:org_expected, :expected].include?(diff()) + return ['.expectf'] # TODO complete list? + elsif [:actual, :org_actual].include?(diff()) + return ['.result'] + else + return [] # shouldn't happen + end + end + + def accept_line(line) + case diff() + when :added + return line.starts_with?('+') + when :removed + return line.starts_with?('-') + when :added_and_removed + return ( line.starts_with?('+') or line.starts_with?('-') ) + else + # search any line of Expected output, actual output, etc... files + return true + end + end + +end # class BaseAllTestCases + +class BaseSingleRun < BaseAllTestCases + attr_reader :dir, :rev + + def initialize(diff, dir, rev=nil) + super(diff) + @dir = dir + @rev = rev + end + + def dirs + [@dir] + end + + def sym + '' + end + +end # class SingleRun + +class BaseRun < BaseSingleRun + def diff_label + if @rev + @rev # LESSON BaseRun and TestRun DRY not normalized + else + 'Base' + end + end +end + +class TestRun < BaseSingleRun + def diff_label + if @rev + @rev + else + 'Test' + end + end +end + +class Base2Runs < BaseAllTestCases + attr_reader :base_dir, :test_dir, :base_rev, :test_rev + + def initialize(diff, base_dir, test_dir, base_rev=nil, test_rev=nil) + super(diff) + @base_dir = base_dir + @test_dir = test_dir + @base_rev = base_rev + @test_rev = test_rev + end + + protected + + def dirs + [@base_dir, @test_dir] + end + + def get_contents(dir, file_name) + file_name = File.join(dir, file_name) + unless File.exist?(file_name) + return nil + end + + # TODO should only have to read a file in once + # comparing arrays of lines instead of the file contents as a string + # eliminates comparison problems due to line ending characters + IO.readlines(file_name) + end + +end # class Base2Runs + +class BaseTestRun < Base2Runs + def diff_label + if @base_rev and @test_rev + "#{@base_rev}#{sym()}#{@test_rev}" + else + "Base#{sym()}Test" + end + end +end + +class TestMinusBase < Base2Runs + # Test-Base => only results from Test not matching those in Base run + + def diff_label + if @base_rev and @test_rev + "#{@test_rev}#{sym()}#{@base_rev}" + else + "Test#{sym()}Base" + end + end + + def sym + '-' + end + + protected + + def accept_file(dir, file_name) + if dir == @base_dir + test_file = get_contents(@test_dir, file_name) + base_file = get_contents(@base_dir, file_name) + return ( base_file.nil? or test_file != base_file ) + end + return false + end + +end # class TestMinusBase + +class BaseMinusTest < BaseTestRun + # Base-Test => only results from Test not matching those in Base run + + def sym + '-' + end + + protected + + def accept_file(dir, file_name) + if dir == @test_dir + base_file = get_contents(@base_dir, file_name) + test_file = get_contents(@test_dir, file_name) + return ( test_file.nil? or test_file != base_file ) + end + return false + end + +end # class BaseMinusTest + +class BasePlusTest < BaseTestRun + # Base+Test => all results from Base and Test (duplicates removed) + + def sym + '+' + end + + protected + + def accept_file(dir, file_name) + if dir == @test_dir + # TODO == (no duplicate) + test_file = get_contents(@test_dir, file_name) + base_file = get_contents(@base_dir, file_name) + return ( base_file.nil? or test_file == base_file ) + end + return false + end + +end # class BasePlusTest + +class BaseEqTest < BaseTestRun + # Base=Test => only results from Base and Test that match + + def sym + '=' + end + + protected + + def accept_file(dir, file_name) + if dir == @test_dir + # TODO == + test_file = get_contents(@test_dir, file_name) + base_file = get_contents(@base_dir, file_name) + return ( base_file.nil? or test_file != base_file ) + end + return false + end + +end # class BaseEqTest + + + end # module AllTestCases + end +end diff --git a/PFTT/lib/host.rb b/PFTT/lib/host.rb index b66cb09..0ac446b 100644 --- a/PFTT/lib/host.rb +++ b/PFTT/lib/host.rb @@ -17,6 +17,17 @@ module Host @@hosts||={} end end + + def self.sub base, path + if path.starts_with?(base) + return path[base.length..path.length] + end + return path + end + + def self.join *path_array + path_array.join('/') + end def self.administrator_user (platform) if platform == :windows @@ -44,11 +55,15 @@ module Host def self.to_windows_path!(path) # remove \\ from path too. they may cause problems on some Windows SKUs - return path.gsub!('/', '\\').gsub!('\\\\', '\\').gsub!('\\\\', '\\') + path.gsub!('/', '\\') + path.gsub!('\\\\', '\\') + path.gsub!('\\\\', '\\') end def self.to_posix_path!(path) path.gsub!('\\', '/') + path.gsub!('//', '/') + path.gsub!('//', '/') end def self.fs_op_to_cmd(fs_op, src, dst) @@ -109,27 +124,23 @@ module Host end def no_trailing_slash(path) - if path.ends_with?('/') or path.ends_with?('\\') - path = path[0..path.length-1] - end - return path + Host.no_trailing_slash(path) end def to_windows_path(path) - # remove \\ from path too. they may cause problems on some Windows SKUs - return path.gsub('/', '\\').gsub('\\\\', '\\').gsub('\\\\', '\\') + Host.to_windows_path(path) end def to_posix_path(path) - return path.gsub('\\', '/') + Host.to_posix_path(path) end def to_windows_path!(path) - path.gsub!('/', '\\') + Host.to_windows_path!(path) end def to_posix_path!(path) - path.gsub!('\\', '/') + Host.to_posix_path!(path) end def number_of_processors(ctx=nil) @@ -212,14 +223,14 @@ module Host end end - cmd!(case - when posix? then %Q{cp -R "#{from}" "#{to}"} - else - to_windows_path!(from) - to_windows_path!(to) - - %Q{xcopy /Y /s /i /q "#{from}" "#{to}"} - end, ctx) + if !directory?(from) + copy_file(from, to, ctx, mk) + return + elsif mk + mkdir(File.dirname(to), ctx) + end + + copy_cmd(from, to, ctx) end def move from, to, ctx @@ -229,17 +240,12 @@ module Host end end - from = no_trailing_slash(from) - to = no_trailing_slash(to) + if !directory?(from) + move_file(from, to, ctx) + return + end - cmd!(case - when posix? then %Q{mv "#{from}" "#{to}"} - else - to_windows_path!(from) - to_windows_path!(to) - - %Q{move "#{from}" "#{to}"} - end, ctx) + move_cmd(from, to, ctx) end def time=(time) @@ -290,6 +296,9 @@ module Host end def userprofile(ctx=nil) + unless @_userprofile.nil? + return @_userprofile + end p = nil if posix? p = env_value('HOME', ctx) @@ -298,44 +307,50 @@ module Host end if exists?(p, ctx) - return p + return @_userprofile = p else - return nil + return @_userprofile = systemdrive(ctx) end end def appdata(ctx=nil) + unless @_appdata.nil? + return @_appdata + end if posix? p = env_value('HOME', ctx) if p and exists?(p, ctx) - return p + return @_appdata = p end else p = env_value('USERPROFILE', ctx) if p q = p + '\\AppData\\' if exists?(q, ctx) - return q + return @_appdata = q elsif exists?(p, ctx) mkdir(q, ctx) - return q + return @_appdata = q end end end - return systemdrive(ctx) + return @_appdata = systemdrive(ctx) end # def appdata def appdata_local(ctx=nil) + unless @_appdata_local.nil? + return @_appdata_local + end if posix? p = env_value('HOME', ctx) if p q = p + '/PFTT' if exists?(q, ctx) - return q + return @_appdata_local = q elsif exists?(p, ctx) mkdir(q, ctx) - return q + return @_appdata_local = q end end else @@ -343,34 +358,37 @@ module Host if p q = p + '\\AppData\\Local' if exists?(q, ctx) - return q + return @_appdata_local = q elsif exists?(p, ctx) mkdir(q, ctx) - return q + return @_appdata_local = q end end end - return systemdrive(ctx) + return @_appdata_local = systemdrive(ctx) end # def appdata_local def tempdir ctx + unless @_tempdir.nil? + return @_tempdir + end if posix? p = '/usr/local/tmp' q = p + '/PFTT' if exists?(q, ctx) - return q + return @_tempdir = q elsif exists?(p, ctx) mkdir(q, ctx) - return q + return @_tempdir = q end p = '/tmp' q = p + '/PFTT' if exists?(q, ctx) - return q + return @_tempdir = q elsif exists?(p, ctx) mkdir(q, ctx) - return q + return @_tempdir = q end else # try %TEMP%\\PFTT @@ -378,10 +396,10 @@ module Host if p q = p + '\\PFTT' if exists?(q, ctx) - return q + return @_tempdir = q elsif exists?(p, ctx) mkdir(q, ctx) - return q + return @_tempdir = q end end @@ -390,10 +408,10 @@ module Host if p q = p + '\\PFTT' if exists?(q, ctx) - return q + return @_tempdir = q elsif exists?(p, ctx) mkdir(q, ctx) - return q + return @_tempdir = q end end @@ -403,10 +421,10 @@ module Host p = '\\AppData\\Local\\Temp\\' + p q = p + '\\PFTT' if exists?(q, ctx) - return q + return @_tempdir = q elsif exists?(p, ctx) mkdir(q, ctx) - return q + return @_tempdir = q end end @@ -414,15 +432,15 @@ module Host p = systemdrive(ctx)+'\\temp' q = p + '\\PFTT' if exists?(q, ctx) - return q + return @_tempdir = q elsif exists?(p, ctx) mkdir(q, ctx) - return q + return @_tempdir = q end end - return systemdrive(ctx) + return @_tempdir = systemdrive(ctx) end # def tempdir alias :tmpdir :tempdir @@ -592,7 +610,7 @@ module Host # 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 '' + # :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 @@ -606,6 +624,7 @@ module Host # :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) + # LATER :elevate and :sudo support for windows and posix # other options are silently ignored # # @@ -676,7 +695,7 @@ module Host # 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) + @is_windows = _exist?('C:\\', ctx) if ctx # cool stuff: allow user to override OS detection @@ -687,7 +706,6 @@ module Host end def posix?(ctx=nil) - return false # TODO TUE unless @posix.nil? return @posix end @@ -696,7 +714,7 @@ module Host end ctx = ctx==nil ? nil : ctx.new(Tracing::Context::Dependency::Detect::OS::Type) - @posix = exist?('/usr', ctx) + @posix = _exist?('/usr', ctx) if ctx @posix = ctx.check_os_type_detect(:posix, @posix) @@ -707,13 +725,32 @@ module Host def make_absolute! *paths paths.map do |path| + # support for Windows drive letters + # (if drive letter present, path is absolute) return path if !posix? && path =~ /\A[A-Za-z]:\// - return path if path =~ /\A[A-Za-z]:\// + return path if path =~ /\A[A-Za-z]:\// + # path.replace( File.absolute_path( path, cwd() ) ) path end end + + def exist? path, ctx=nil + make_absolute! path + + if ctx + ctx.fs_op1(self, :exist, path) do |path| + return exist?(path, ctx) + end + end + + _exist?(path, ctx) + end + + alias :exists? :exist? + alias :exist :exist? + alias :exists :exist? def format_path path, ctx=nil case @@ -753,10 +790,14 @@ module Host else return '/' end + end + + def sub base, path + Host.sub(base, path) end def join *path_array - path_array.join(separator) + Host.join(path_array) end def upload_if_not(local, remote, ctx) @@ -832,13 +873,16 @@ module Host _mkdir(path, ctx) unless directory? path end - def mktmpdir path, ctx - ctx = ctx==nil ? nil : ctx.new(SystemSetup::TempDirectory) + def mktmpdir ctx, path=nil, suffix='' + ctx = ctx==nil ? nil : ctx.new(Tracing::Context::SystemSetup::TempDirectory) + unless path + path = tempdir(ctx) + end make_absolute! path tries = 10 begin - dir = File.join( path, String.random(4) ) + dir = File.join( path, String.random(6)+suffix ) raise 'exists' if directory? dir mkdir(dir, ctx) rescue @@ -865,6 +909,9 @@ module Host raise $! end end + + alias :mktempfile :mktmpfile + alias :mktempdir :mktmpdir def sane? path make_absolute! path @@ -894,6 +941,7 @@ module Host end def name ctx=nil + return 'OI1-PHP-FUNC-15' # TODO TUE unless @_name # find a name that other hosts on the network will use to reference localhost if windows?(ctx) @@ -907,6 +955,31 @@ module Host protected + def move_cmd(from, to, ctx) + from = no_trailing_slash(from) + to = no_trailing_slash(to) + + cmd!(case + when posix? then %Q{mv "#{from}" "#{to}"} + else + from = to_windows_path(from) + to = to_windows_path(to) + + %Q{move "#{from}" "#{to}"} + end, ctx) + end + + def copy_cmd(from, to, ctx) + cmd!(case + when posix? then %Q{cp -R "#{from}" "#{to}"} + else + from = to_windows_path(from) + to = to_windows_path(to) + + %Q{xcopy /Y /s /i /q "#{from}" "#{to}"} + end, ctx) + end + def _exec in_thread, command, opts, ctx, block @cwd = nil # clear cwd cache @@ -940,7 +1013,7 @@ module Host end # - if !opts.has_key?(:max_len) or opts[:max_len].is_a?(Integer) or opts[:max_len] < 0 + if !opts.has_key?(:max_len) or !opts[:max_len].is_a?(Integer) or opts[:max_len] < 0 opts[:max_len] = 128*1024 end @@ -949,8 +1022,6 @@ module Host command = debug_wrap(command) end - stdin_data = (opts.has_key?(:stdin))? opts[:stdin] : nil - if in_thread Thread.start do ret = _exec_thread(command, opts, ctx, block) @@ -1042,7 +1113,7 @@ module Host return [stdout, stderr, exit_code] end # def _exec_thread - attr_accessor :_systeminfo, :_name, :_osname, :_systeminfo, :_systemdrive, :_systemroot, :posix, :is_windows + attr_accessor :_systeminfo, :_name, :_osname, :_systeminfo, :_systemdrive, :_systemroot, :posix, :is_windows, :_appdata, :_appdata_local, :_tempdir, :userprofile def clone(clone) clone._systeminfo = @systeminfo @@ -1054,6 +1125,10 @@ module Host 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 end diff --git a/PFTT/lib/host/local.rb b/PFTT/lib/host/local.rb index 5f9fe67..200c5ad 100644 --- a/PFTT/lib/host/local.rb +++ b/PFTT/lib/host/local.rb @@ -2,6 +2,7 @@ require 'java' include_class 'java.util.Timer' include_class 'java.util.TimerTask' +require 'FileUtils' module Host class Local < HostBase @@ -102,20 +103,6 @@ module Host alias :upload :copy alias :download :copy - def exist? file, ctx=nil - if ctx - ctx.fs_op1(self, :exist, file) do |file| - return exist?(file, ctx) - end - end - make_absolute! file - File.exist? file - end - - alias :exists? :exist? - alias :exist :exist? - alias :exists :exist? - def directory? path, ctx=nil if ctx ctx.fs_op1(self, :is_dir, path) do |path| @@ -154,8 +141,29 @@ module Host false end + def mtime(file) + # TODO implement for ssh + File.mtime(file).to_i + end + protected + def _exist?(file, ctx) + File.exist? file + end + + def move_file(from, to, ctx) + FileUtils.move_file(from, to) + end + + def copy_file(from, to, ctx, mk) + if mk + mkdir(File.dirname(to), ctx) + end + + FileUtils.copy_file(from, to) + end + class LocalExecHandle < ExecHandle def initialize(stdout, stderr, process=nil) @stdout = stdout diff --git a/PFTT/lib/host/remote/psc/client.rb b/PFTT/lib/host/remote/psc/client.rb index efba21c..fb7fbd5 100644 --- a/PFTT/lib/host/remote/psc/client.rb +++ b/PFTT/lib/host/remote/psc/client.rb @@ -25,8 +25,8 @@ class Client < BaseRemoteHostAndClient # # TODO temp do we really need to do this anymore? - @host.exec!('taskkill /im:jruby.exe /f', ctx) - @host.exec!('taskkill /im:jruby.exe /f', ctx) + @host.exec!('taskkill /im:jruby.exe /f', ctx, {:success_exit_code=>128}) + @host.exec!('taskkill /im:jruby.exe /f', ctx, {:success_exit_code=>128}) rescue puts @host.name+" "+$!.inspect+" "+$!.backtrace.inspect end @@ -97,30 +97,21 @@ class Client < BaseRemoteHostAndClient # end # + # TODO + send_php(PhpBuild.new('g:/php-sdk/PFTT-PHPS/'+$php_build_path)) @running = true @wait_lock.synchronize do begin + + # set JAVA_HOME=\\jruby-1.6.5\\jre - @host.exec!('_pftt_hc.cmd ""', Tracing::Context::Phpt::RunHost.new(), {:chdir=>@host.systemdrive+'\\php-sdk\\1\\PFTT', :stdin=>@stdin}) do |handle| - if handle.has_stderr? - recv_ssh_block(handle.read_stderr) - end - end + do_it() + rescue puts @host.name+" "+$!.inspect+" "+$!.backtrace.inspect - # TODO be able to resume: rerun _pftt_hc, but limit the list of tests to those that results weren't received for + do_it() #again - # retry - begin - @host.exec!('_pftt_hc.cmd ""', Tracing::Context::Phpt::RunHost.new(), {:chdir=>@host.systemdrive+'\\php-sdk\\1\\PFTT', :stdin=>@stdin}) do |handle| - if handle.has_stderr? - recv_ssh_block(handle.read_stderr) - end - end - rescue - puts @host.name+" "+$!.inspect+" "+$!.backtrace.inspect - end ensure @running = false @started = false @@ -155,6 +146,13 @@ class Client < BaseRemoteHostAndClient end end # thread end + def do_it + @host.exec!(@host.systemdrive+'/php-sdk/1/pftt/_pftt_hc.cmd', Tracing::Context::Phpt::RunHost.new(), {:stdin_data=>@stdin, :max_len=>0, :chdir=>@host.systemdrive+'\\php-sdk\\1\\PFTT', :stdin=>@stdin}) do |handle| + if handle.has_stderr? + recv_ssh_block(handle.read_stderr) + end + end + end def hosted_client_failed # terminate _pftt_hc.rb if its still running @host.close @@ -166,6 +164,7 @@ class Client < BaseRemoteHostAndClient def send_full_block(block) block += "<Boundary>\n" + puts block @stdin += block end def dispatch_recvd_xml(xml) @@ -194,7 +193,7 @@ class Client < BaseRemoteHostAndClient end end def send_start - send_xml({}, 'start') + # TODO TUE send_xml({}, 'start') end def send_stop send_xml({}, 'stop') diff --git a/PFTT/lib/host/remote/psc/recovery_manager.rb b/PFTT/lib/host/remote/psc/recovery_manager.rb index eeeba62..68bedee 100644 --- a/PFTT/lib/host/remote/psc/recovery_manager.rb +++ b/PFTT/lib/host/remote/psc/recovery_manager.rb @@ -8,12 +8,12 @@ module Host class HostRecoveryManager def initialize(hosts, php, middleware, scn_set) #hosts = [hosts.first] - php = PhpBuild.new('C:/php-sdk/builds/php-5.4-nts-windows-vc9-x86-r319120') + php = PhpBuild.new('C:\\php-sdk\\builds\\php-5.4.0rc2-nts-Win32-VC9-x86') threads = [] hosts.each do |host| - file_name = 'C:/php-sdk/PFTT-PSCC/r319120/'+host.name + file_name = 'C:/php-sdk/PFTT-PSCC/540rc2'# r319120/'+host.name if File.exists?(file_name) #t = Thread.start do recover(file_name, host, php, middleware, scn_set) @@ -136,13 +136,17 @@ def to_simple(raw_xml) when 4#Xml.TEXT text = parser.getText(); - +# if text.include?('+Warning: strtotime()') +# puts text +# end if t['text'] t['text'] += text else t['text'] = text end - + if text.include?('+Warning: strtotime()') + puts t.inspect + end when 3#Xml.END_TAG if s.length > 1 s.pop # double pop diff --git a/PFTT/lib/host/remote/psc/remote_host.rb b/PFTT/lib/host/remote/psc/remote_host.rb index ce6a4b2..4c9cd4f 100644 --- a/PFTT/lib/host/remote/psc/remote_host.rb +++ b/PFTT/lib/host/remote/psc/remote_host.rb @@ -39,7 +39,10 @@ class RemoteHost < BaseRemoteHostAndClient while @run line = STDIN.gets() - recv_ssh_block(line) + # TODO fucked up recv_ssh_block(line) + xml = to_simple(line) + dispatch_recvd_xml(xml) + dispatch_recvd_xml({'@msg_type'=>'start'}) # TODO end end def send_result(result) diff --git a/PFTT/lib/host/remote/ssh.rb b/PFTT/lib/host/remote/ssh.rb index 7b16a89..942104a 100644 --- a/PFTT/lib/host/remote/ssh.rb +++ b/PFTT/lib/host/remote/ssh.rb @@ -152,35 +152,6 @@ module Host end end - def exist? path, ctx=nil - if ctx - ctx.fs_op1(self, :exist, path) do |path| - return exist?(path, ctx) - end - end - - # see T_* constants in Net::SFTP::Protocol::V01::Attributes - # v04 and v06 attributes don't have a directory? or file? method (which v01 does) - # doing it this way will work for all 3 (v01, v04, v06 attributes) - begin - a = wait_for(sftp(ctx).stat(path), :attrs) - # types: regular(1), directory(2), symlink, special, unknown, socket, char_device, block_device, fifo - # # if type is any of those, then path exists - if a.nil? - return false - else - return ( a.type > 0 and a.type < 10 ) - end - rescue - if_closed - return false - end - end - - alias :exists? :exist? - alias :exist :exist? - alias :exists :exist? - def list(path, ctx) if ctx ctx.fs_op1(self, :list, path) do |path| @@ -267,11 +238,11 @@ module Host # LATER remove this gotcha/rule/limitation (implement using File.basename) # if windows? - to_windows_path!(local_file) - to_windows_path!(remote_path) + local_file = to_windows_path(local_file) + remote_path = to_windows_path(remote_path) else - to_posix_path!(local_file) - to_posix_path!(remote_path) + local_file = to_posix_path(local_file) + remote_path = to_posix_path(remote_path) end # @@ -318,6 +289,38 @@ module Host protected + def _exist?(path, ctx) + # see T_* constants in Net::SFTP::Protocol::V01::Attributes + # v04 and v06 attributes don't have a directory? or file? method (which v01 does) + # doing it this way will work for all 3 (v01, v04, v06 attributes) + begin + a = wait_for(sftp(ctx).stat(path), :attrs) + # types: regular(1), directory(2), symlink, special, unknown, socket, char_device, block_device, fifo + # # if type is any of those, then path exists + if a.nil? + return false + else + return ( a.type > 0 and a.type < 10 ) + end + rescue + if_closed + return false + end + end + + def move_file(from, to, ctx) + move_cmd(from, to, ctx) + end + + def copy_file(from, to, ctx, mk) + to = File.dirname(to) + if mk + mkdir(to, ctx) + end + + copy_cmd(from, to, ctx) + end + # for #clone() attr_accessor :rebooting, :rebooting_reconnect_tries @@ -355,11 +358,21 @@ module Host def write_stdin(stdin_data) @channel.send_data(stdin_data) end + def has_stderr? + @stderr.length > 0 + end + def has_stdout? + @stdout.length > 0 + end def read_stderr - @stderr + x = @stderr + @stderr = '' + return x end def read_stdout - @stdout + x = @stdout + @stdout = '' + return x end def post_stdout(data) @stdout = data @@ -388,7 +401,7 @@ module Host stdout, stderr = '','' stdin_data = opts[:stdin_data] exit_code = -254 # assume error unless success - + ssh(ctx).open_channel do |channel| channel.exec(command) do |channel, success| unless success @@ -404,7 +417,7 @@ module Host if stdin_data ch.send_data(stdin_data) stdin_data = nil - end + end if block sh.post_stdout(data) block.call(sh) @@ -470,11 +483,11 @@ module Host def _delete path, ctx if windows? - to_windows_path!(path) + path = to_windows_path(path) cmd!("DEL /Q /F \"#{path}\"", ctx) else - to_posix_path!(path) + path = to_posix_path(path) exec!("rm -rf \"#{path}\"", ctx) end diff --git a/PFTT/lib/middleware.rb b/PFTT/lib/middleware.rb index 734ae25..06c2331 100644 --- a/PFTT/lib/middleware.rb +++ b/PFTT/lib/middleware.rb @@ -98,14 +98,19 @@ module Middleware # if $force_deploy, make a new directory! otherwise, reuse existing directory (for quick manual testing can't take the time # to copy everything again) - @deployed_php ||= @host.join(deploy_to, ( @php_build[:version] + ((@php_build[:threadsafe])?'-TS':'-NTS') + ( $force_deploy ? '_'+String.random(4) : '' ) ) ) + @deployed_php ||= @php_build.path # TODO @host.join(deploy_to, ( @php_build[:version] + ((@php_build[:threadsafe])?'-TS':'-NTS') + ( $force_deploy ? '_'+String.random(4) : '' ) ) ) # # TODO TUE if $force_deploy or not File.exists?(php_binary()) or File.mtime(@php_build.path) >= File.mtime(php_binary()) unless $hosted_int puts "PFTT:deploy: uploading... "+@deployed_php - host.upload_force("c:/php-sdk/5.4.0beta2-NTS.7z", host.systemdrive(ctx)+'/5.4.0beta2-NTS.7z', false, ctx) # critical: false + + zip_name = package_php_build(Host::Local.new(), 'c:/php-sdk/builds/'+$php_build_path) + + # TODO package_php_build + host.upload_force(zip_name, host.systemdrive(ctx)+'/5.4.0beta2-NTS.7z', false, ctx) # critical: false + # TODO check if build already on remote host (so compression and upload can be skipped) sd = host.systemdrive ctx = Tracing::Context::Dependency::Check.new # TODO ctx.new host.delete_if("#{sd}\\php-sdk\\PFTT-PHPs\\5.4.0beta2-NTS", ctx) diff --git a/PFTT/lib/middleware/cli.rb b/PFTT/lib/middleware/cli.rb index 622283b..8189aae 100644 --- a/PFTT/lib/middleware/cli.rb +++ b/PFTT/lib/middleware/cli.rb @@ -20,10 +20,10 @@ module Middleware # phpt thread) config_ctx = Tracing::Context::Middleware::Config.new() - @host.exec!('REG DELETE "HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug" /v Debugger /f', config_ctx) + # TODO @host.exec!('REG DELETE "HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug" /v Debugger /f', config_ctx) # disable Hard Error Popup Dialog boxes (will still get this even without a debugger) # see http://support.microsoft.com/kb/128642 - @host.exec!('REG ADD "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Windows" /v ErrorMode /d 2 /t REG_DWORD /f', config_ctx) + # TODO @host.exec!('REG ADD "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Windows" /v ErrorMode /d 2 /t REG_DWORD /f', config_ctx) # disable windows firewall # LATER edit firewall rules instead (what if on public network, ex: Azure) diff --git a/PFTT/lib/test/case/phpt.rb b/PFTT/lib/test/case/phpt.rb index 8bc78c7..cdc3ad7 100644 --- a/PFTT/lib/test/case/phpt.rb +++ b/PFTT/lib/test/case/phpt.rb @@ -497,7 +497,9 @@ class Phpt @parts[:file]=@parts.delete(:fileeof).gsub(/\r?\n\Z/,'') elsif @parts.has_key? :file_external context = File.dirname( @phpt_path ) - external_file = File.absolute_path( @parts.delete(:file_external).gsub(/\r?\n\Z/,''), context ) + external_file = File.absolute_path( @parts.delete(:file_external).gsub(/\r?\n\Z/,''), context ) + # TODO TUE c:/abc/ + # ('c:/abc/'+external_file).gsub('C:/php-sdk/0/PFTT2/PFTT', '') @parts[:file]= IO.read( external_file ).lines do |line| parse_line line, context end diff --git a/PFTT/lib/test/result/phpt.rb b/PFTT/lib/test/result/phpt.rb index 014a09e..42db006 100644 --- a/PFTT/lib/test/result/phpt.rb +++ b/PFTT/lib/test/result/phpt.rb @@ -17,20 +17,33 @@ module Test end def self.from_xml(xml, test_bench, deploydir, php) - rclass = case xml['@result_type'] - when 'XSkip' + rclass = case xml['@status'] + when 'xskip' Test::Result::Phpt::XSkip - when 'Skip' + when 'skip' Test::Result::Phpt::Skip - when 'Bork' + when 'bork' Test::Result::Phpt::Bork - when 'Unsupported' + when 'unsupported' Test::Result::Phpt::Unsupported - when 'Mean' + when 'xfail' + Test::Result::Phpt::Meaningful + when 'fail' + Test::Result::Phpt::Meaningful + when 'works' + Test::Result::Phpt::Meaningful + when 'pass' Test::Result::Phpt::Meaningful end - if xml['@status'] == 'unsupported' + if rclass.nil? +# puts rclass +# puts xml['@result_type'] +# puts xml.inspect + end + + if xml['@status'] == 'unsupported' or xml['@status'] == 'bork' r = rclass.new(Test::Case::Phpt.from_xml(xml['test_case'][0]), test_bench, deploydir, php) + # TODO included borked reasons array (field) elsif xml.has_key?('@reason') r = rclass.new(Test::Case::Phpt.from_xml(xml['test_case'][0]), test_bench, deploydir, php, xml['@reason']) else @@ -51,6 +64,7 @@ module Test result_str = result_str.to_s end r = rclass.new(Test::Case::Phpt.from_xml(xml['test_case'][0]), test_bench, deploydir, php, result_str) + end case xml['@status'] when 'pass' @@ -70,11 +84,14 @@ module Test when 'xfail' r.status = :xfail end + #puts xml.inspect if xml.has_key?('diff') + #r.diff = xml['diff'][0]['result_str'][0]['text'] r.diff = xml['diff'][0] unless r.diff.is_a?(String) r.diff = '' end + #puts xml.inspect end r.set_files return r @@ -82,24 +99,24 @@ module Test def to_xml # - result_type = case self.class - when Test::Result::Phpt::XSkip - 'XSkip' - when Test::Result::Phpt::Skip - 'Skip' - when Test::Result::Phpt::Bork - 'Bork' - when Test::Result::Phpt::Unsupported - 'Unsupported' - when Test::Result::Phpt::Meaningful - 'Meaningful' - else - '' - end +# result_type = case self.class +# when Test::Result::Phpt::XSkip +# 'XSkip' +# when Test::Result::Phpt::Skip +# 'Skip' +# when Test::Result::Phpt::Bork +# 'Bork' +# when Test::Result::Phpt::Unsupported +# 'Unsupported' +# when Test::Result::Phpt::Meaningful +# 'Meaningful' +# else +# '' +# end # xml = { - '@result_type' => result_type, + #'@result_type' => result_type, 'test_case' => @test_case.to_xml, '@status' => @status } @@ -277,8 +294,10 @@ module Test unless @diff.is_a?(String) @diff = @diff.to_s end + #puts @diff.length.to_s if @diff.length > 0 files['diff']= @diff + end end attr_accessor :diff diff --git a/PFTT/lib/test/runner/stage/phpt/package.rb b/PFTT/lib/test/runner/stage/phpt/package.rb index 36813f1..440f58c 100644 --- a/PFTT/lib/test/runner/stage/phpt/package.rb +++ b/PFTT/lib/test/runner/stage/phpt/package.rb @@ -7,10 +7,10 @@ module Test class Package < Tracing::Stage def run() - notify_start + notify_start puts 'PFTT:compress: compressing PHPTs...' - local_phpt_zip = package_svn(Host::Local.new(), 'c:/php-sdk/svn/branches/PHP_5_4') + local_phpt_zip = package_svn(Host::Local.new(), $phpt_path) puts 'PFTT:compress: compressed PHPTs...' notify_end(true) @@ -18,7 +18,7 @@ class Package < Tracing::Stage return local_phpt_zip end -end +end # class Package end # module PHPT end # module Stage diff --git a/PFTT/lib/test/runner/stage/phpt/upload.rb b/PFTT/lib/test/runner/stage/phpt/upload.rb index 33d36c9..be758be 100644 --- a/PFTT/lib/test/runner/stage/phpt/upload.rb +++ b/PFTT/lib/test/runner/stage/phpt/upload.rb @@ -12,20 +12,20 @@ class Upload < Tracing::Stage::ByHostMiddleware local_host = Host::Local.new() - upload_7zip(local_host, host) - remote_phpt_zip = host.systemdrive+'/PHP_5_4.7z' - host.upload_force(local_phpt_zip, remote_phpt_zip, Tracing::Context::Phpt::Upload.new) - host.delete_if(host.systemdrive+'/abc', Tracing::Context::Phpt::Decompress.new) - host.delete_if(host.systemdrive+'/PHP_5_4', Tracing::Context::Phpt::Decompress.new) - # TODO unpackage(host, host.systemdrive, remote_phpt_zip) - sd = host.systemdrive - #sleep(20) - # critical: must chdir to output directory or directory where 7zip file is stored!!! - host.exec!("#{sd}\\php-sdk\\bin\\7za.exe x -o#{sd}\\ #{sd}\\PHP_5_4.7z ", Tracing::Context::Phpt::Decompress.new, {:chdir=>"#{sd}\\", :null_output=>true}) - # TODO host.move(host.systemdrive+'/PHP_5_4', host.systemdrive+'/abc') - #sleep(20) - # TODO use test_ctx.new - host.cmd!("move #{sd}\\php_5_4 #{sd}\\abc", Tracing::Context::PhpBuild::Compress.new) + upload_7zip(local_host, host) + remote_phpt_zip = host.systemdrive+'/PHP_5_4.7z' + host.upload_force(local_phpt_zip, remote_phpt_zip, Tracing::Context::Phpt::Upload.new) + host.delete_if(host.systemdrive+'/abc', Tracing::Context::Phpt::Decompress.new) + host.delete_if(host.systemdrive+'/PHP_5_4', Tracing::Context::Phpt::Decompress.new) + # TODO unpackage(host, host.systemdrive, remote_phpt_zip) + sd = host.systemdrive + #sleep(20) + # critical: must chdir to output directory or directory where 7zip file is stored!!! + host.exec!("#{sd}\\php-sdk\\bin\\7za.exe x -o#{sd}\\ #{sd}\\PHP_5_4.7z ", Tracing::Context::Phpt::Decompress.new, {:chdir=>"#{sd}\\", :null_output=>true}) + # TODO host.move(host.systemdrive+'/PHP_5_4', host.systemdrive+'/abc') + #sleep(20) + # TODO use test_ctx.new + host.cmd!("move #{sd}\\php_5_4 #{sd}\\abc", Tracing::Context::PhpBuild::Compress.new) notify_end(true) end # def run diff --git a/PFTT/lib/tracing/context.rb b/PFTT/lib/tracing/context.rb index fd1a514..9d7fd8a 100644 --- a/PFTT/lib/tracing/context.rb +++ b/PFTT/lib/tracing/context.rb @@ -112,7 +112,7 @@ module Tracing while true do # TODO empty all STDIN chars now (before user is prompted to enter a char we shouldn't ignore) - + # show the prompt line to the user STDOUT.write(host.name+'('+host.osname_short+')'+prompt.prompt_str) diff --git a/PFTT/lib/tracing/prompt/diff.rb b/PFTT/lib/tracing/prompt/diff.rb index 5c7e5b3..57a7651 100644 --- a/PFTT/lib/tracing/prompt/diff.rb +++ b/PFTT/lib/tracing/prompt/diff.rb @@ -3,37 +3,144 @@ # TODO be able to print a list of all insertions or all insertions contianing 'Warning', etc... module Tracing module Prompt - -class Diff < TestCaseRunPrompt + module Diff + + + +#class BaseDiff < TestCaseRunPrompt +# +# def axis_label +# +# end +# +# def prompt_str +# axis_label+'::Diff> ' +# end +# +# +# def help +# super +# puts +# puts 'Axes: [Run] [Host] [[Zoom] [Diff]]' +# puts ' [Run] - Base - only Base run Test - only Test run' +# puts ' Base+Test - all results from Base and Test (duplicates removed)' +# puts ' Base=Test - only results from Base and Test that match' +# puts ' Test-Base - only results from Base not matching those in Test run' +# puts ' Base-Test - only results from Test not matching those in Base run' +# puts ' [Host] - All - all selected hosts {Hostname} - named host' +# puts ' [Zoom] - A - All tests E - One extension' +# puts ' T - One test case L - One line from a test case' +# puts ' C - One chunk from one line' +# puts ' [Diff] - E - original Expected output A - original Actual output' +# puts ' + - only added output - - only removed output' +# puts ' d - + and -' +# puts ' e - Expected output (changed?) a - Actual (changed?)' +# puts +# help_diff_cmds +# puts ' c - change' +# puts ' C - change (&next)' +# puts ' t - Triage' +# puts ' h - help' +# puts ' v - view diff' +# puts ' V - view PHPT documentation' +# puts ' y - save chunk replacements' +# puts ' Y - load chunk replacements' +# puts ' k - save diff to file' +# puts ' l - locate - os middleware build and other info' +# help_zoom_out +# puts ' F - find' +# puts ' R - run' +# puts ' N - network/host' +# puts ' Z - zoom' +# puts ' D - diff' +# +# end # def help +# +# def help_zoom_out +# # top level can't zoom out +# end +# +# def confirm_find +# true +# end +# +# def help_diff_cmds +# end +# +#end # class BaseDiff +# +#class All < BaseDiff +# +# +# def confirm_find +# # LATER ask user to confirm searching everything (b/c it can be slow) +# end +#end +# +#class BaseNotAll < BaseDiff +# def help_zoom_out +# puts ' o - zoom out'# +# end +# +# def execute(ans) +# if ans == 'o' +# end +# end +#end +# +#class Ext < BaseNotAll +#end +# +#class TestCaseLineChunk < BaseNotAll +# def help_diff_cmds +# puts ' d - delete (&next)' +# puts ' a - add (&next)' +# puts ' s - skip (&next)' +# puts ' p - pass (&next)' +# end +# +# def execute(ans) +# if ans == 'd' or ans == '-' +# elsif ans == 'a' or ans == '+' +# elsif ans == 's' +# elsif ans == 'p' +# end +# end +#end +# +#class TestCase < TestCaseLineChunk +#end +# +#class Line < TestCaseLineChunk +#end +# +#class Chunk < TestCaseLineChunk +#end +# +class Diff def initialize(dlm) @dlm = dlm # TODO end - - def help - super - puts ' d - (or -) delete: modify expect' - puts ' a - (or +) add: modify expect' - puts ' i - ignore: remove from diffs' # TODO - puts ' s - skip line, count' - puts ' m - display more commands (this whole list)' - puts ' t - Triage diffs in this test' - puts ' T - Triage all diffs from all tests' - puts ' r - replace expect with regex to match actual' - puts ' R - replace all in file' - puts ' A - replace all in test case set' - puts ' l - show modified expect line (or original if not modified)' - puts ' L - show original expect line' - puts ' P - show original expect section' - puts ' p - show modified expect section (or original if not modified)' - puts ' v - show PHPT file format documentation (HTML)' - puts ' H - highlight diff (Swing UI)' - puts ' y - save chunk replacements to file' - puts ' Y - load chunk replacements from a file' - puts ' k - save diff to file (insertions and deletions)' - puts ' K - save all inserted chunks to file' - end # def help + + + + def show_diff + if host.windows? + if host.exist?('winmerge') + host.exec!("winmerge #{expected} #{result}") + return + end + else + if host.exec('winmeld') + host.exec("winmeld #{expected} #{result}") + return + end + end + + # LATER fallback swing display + end def execute(ans) if ans=='-' or ans=='d' diff --git a/PFTT/lib/util/package.rb b/PFTT/lib/util/package.rb index 414347f..b41b302 100644 --- a/PFTT/lib/util/package.rb +++ b/PFTT/lib/util/package.rb @@ -17,12 +17,21 @@ end def package_php_build(local_host, build_path) ctx = Tracing::Context::PhpBuild::Compress.new - zip_name = File.basename(build_path)+'.7z' + cached_zip_name = 'c:/php-sdk/0/PFTT2/cache/PHP_5_4.7z' + if local_host.exist?(cached_zip_name) + puts "PFTT: compress: reusing #{cached_zip_name}" + return cached_zip_name + end + + zip_name = local_host.mktempdir(ctx) + '/' + File.basename(build_path)+'.7z' local_host.format_path!(zip_name, ctx) local_host.format_path!(build_path, ctx) - local_host.exec!(host.systemdrive+"/php-sdk/0/PFTT2/PFTT/7za #{zip_name} #{build_path}", ctx) + local_host.exec!(local_host.systemdrive+"/php-sdk/0/PFTT2/PFTT/7za a #{zip_name} #{build_path}", ctx) + + # cache archive for next time + local_host.copy(zip_name, cached_zip_name, ctx) return zip_name end @@ -40,33 +49,73 @@ end def package_svn(local_host, path) ctx = Tracing::Context::PhpBuild::Compress.new - tmp_dir = File.join(local_host.tmpdir(ctx), File.basename(path)) + tmp_dir = File.join(local_host.mktmpdir(ctx), File.basename(path)) + + # put the PHPTs in a sub-folder of tmp_dir (tmp_dir will also store the archive) + package_tmp_dir = tmp_dir+'/PHP_5_4' # TODO - local_host.format_path!(tmp_dir, ctx) + test_dirs = {} + greatest_mtime = 0 - # there's a bunch of files we should remove/not include in zip, copy folder to a temporary folder - # and then remove the files from that and then compress it - local_host.copy(path, tmp_dir, ctx) + # copy all files, sub folders and files from any directory named 'test' (in ext, sapi, tests, or Zend, or others that get added) + # (don't need the complete source code, just the PHPTs and files they might need) + # (the fewer the files, the smaller the archive, the faster the test cycle => greater SDE/SDET productivity) + # TODO local_host.glob(path, '**/*', ctx) do |file| + Dir.glob(path+'/**/*') do |file| + # for each file + s_file = Host.sub(path, file) + + if s_file.include?('/tests/') or s_file.include?('/test/') + + mtime = local_host.mtime(file) + if mtime > greatest_mtime + greatest_mtime = mtime + end + + # TODO cache test_files copy and only add to test_dirs if the cached file is older or missing + + # save the list of dirs... we could copy each test file, buts its fewer copy operations + # to copy entire directories => so the copy process is faster + test_dirs[File.dirname(file)] = File.dirname(Host.join(package_tmp_dir, s_file)) + end + end + # - # remove any .svn directories - if local_host.windows?(ctx) - # FOR /F "tokens=*" %G IN ('DIR /B /AD /S *.svn*') DO RMDIR /S /Q "%G" - local_host.cmd!("FOR /F \"tokens=*\" %%G IN ('DIR /B /AD /S *.svn*') DO RMDIR /S /Q \"%%G\"'", ctx) - else - local_host.exec!("rm -rf `find . -type d -name .svn`", ctx) + # TODO (also have separate cache folders for test files, builds, etc...) + cached_zip_name = 'c:/php-sdk/0/PFTT2/cache/PHPT_5_4.7z' + + if local_host.exists?(cached_zip_name) + # if none of the test files has been modified since creation of cached copy of archive, then + # there is no need to copy and archive the test files a second time (just use the cached archive) + if local_host.mtime(cached_zip_name) > greatest_mtime + # don't need to copy+compress again + puts "PFTT: reusing cached tests #{cached_zip_name}" + return cached_zip_name + # TODO check copy on remote_host + end + puts "PFTT: tests updated, re-creating #{cached_zip_name}" end + # go ahead and create a new archive of test_files + # - # remove Release and Release_TS (maybe this svn copy was compiled?) - local_host.delete_if(File.join(tmp_dir, 'Release'), ctx) - local_host.delete_if(File.join(tmp_dir, 'Release_TS'), ctx) - # LATER local_host.delete_if(File.join(tmp_dir, 'php_test_results_*')) + # copy test dirs + test_dirs.each do |entry| + local_host.copy(entry[0], entry[1], ctx) + end + + # LATER check path for any PHPTs that didn't get included (PHPTs in wrong place) zip_name = tmp_dir+'.7z' - # we've removed as much as we really can, compress it - local_host.exec!(local_host.systemdrive+"/php-sdk/0/PFTT2/PFTT/7za a #{zip_name} #{tmp_dir}", ctx) + # make it even smaller, compress it! (copying lots of little files takes a lot of overhead bandwidth) + local_host.exec!(local_host.systemdrive+"/php-sdk/0/PFTT2/PFTT/7za a #{zip_name} #{package_tmp_dir}", ctx) + + # cleanup temp dir + local_host.delete(package_tmp_dir, ctx) - local_host.delete(tmp_dir, ctx) + # cache archive for next time + local_host.copy(zip_name, cached_zip_name, ctx) + # return archive name (LATER delete tmp_dir and zip_name when no longer needed) return zip_name end diff --git a/PFTT/src/se/datadosen/component/RiverLayout.class b/PFTT/src/se/datadosen/component/RiverLayout.class new file mode 100644 index 0000000..44c7c16 Binary files /dev/null and b/PFTT/src/se/datadosen/component/RiverLayout.class differ diff --git a/PFTT/src/se/datadosen/component/RiverLayout.java b/PFTT/src/se/datadosen/component/RiverLayout.java new file mode 100644 index 0000000..2c24489 --- /dev/null +++ b/PFTT/src/se/datadosen/component/RiverLayout.java @@ -0,0 +1,492 @@ +package se.datadosen.component; + +import java.io.ObjectInputStream; +import java.io.IOException; +import java.awt.*; +import java.util.*; + +/** + * <p>RiverLayout makes it very simple to construct user interfaces as components + * are laid out similar to how text is added to a word processor (Components flow + * like a "river". RiverLayout is however much more powerful than FlowLayout: + * Components added with the add() method generally gets laid out horizontally, + * but one may add a string before the component being added to specify "constraints" + * like this: + * add("br hfill", new JTextField("Your name here"); + * The code above forces a "line break" and extends the added component horizontally. + * Without the "hfill" constraint, the component would take on its preferred size. + *</p> + * <p> + * List of constraints:<ul> + * <li>br - Add a line break + * <li>p - Add a paragraph break + * <li>tab - Add a tab stop (handy for constructing forms with labels followed by fields) + * <li>hfill - Extend component horizontally + * <li>vfill - Extent component vertically (currently only one allowed) + * <li>left - Align following components to the left (default) + * <li>center - Align following components horizontally centered + * <li>right - Align following components to the right + * <li>vtop - Align following components vertically top aligned + * <li>vcenter - Align following components vertically centered (default) + * </ul> + * </p> + * RiverLayout is LGPL licenced - use it freely in free and commercial programs + * + * @author David Ekholm + * @version 1.1 (2005-05-23) -Bugfix: JScrollPanes were oversized (sized to their containing component) + * if the container containing the JScrollPane was resized. + */ +public class RiverLayout + extends FlowLayout + implements LayoutManager, java.io.Serializable { + + public static final String LINE_BREAK = "br"; + public static final String PARAGRAPH_BREAK = "p"; + public static final String TAB_STOP = "tab"; + public static final String HFILL = "hfill"; + public static final String VFILL = "vfill"; + public static final String LEFT = "left"; + public static final String RIGHT = "right"; + public static final String CENTER = "center"; + public static final String VTOP = "vtop"; + public static final String VCENTER = "vcenter"; + + Map constraints = new HashMap(); + String valign = VCENTER; + int hgap; + int vgap; + Insets extraInsets; + Insets totalInsets = new Insets(0, 0, 0, 0);// Dummy values. Set by getInsets() + + + public RiverLayout() { + this(10, 5); + } + + public RiverLayout(int hgap, int vgap) { + this.hgap = hgap; + this.vgap = vgap; + setExtraInsets(new Insets(0, hgap, hgap, hgap)); + } + + /** + * Gets the horizontal gap between components. + */ + public int getHgap() { + return hgap; + } + + /** + * Sets the horizontal gap between components. + */ + public void setHgap(int hgap) { + this.hgap = hgap; + } + + /** + * Gets the vertical gap between components. + */ + public int getVgap() { + return vgap; + } + + public Insets getExtraInsets() { + return extraInsets; + } + + public void setExtraInsets(Insets newExtraInsets) { + extraInsets = newExtraInsets; + } + + protected Insets getInsets(Container target) { + Insets insets = target.getInsets(); + totalInsets.top = insets.top + extraInsets.top; + totalInsets.left = insets.left + extraInsets.left; + totalInsets.bottom = insets.bottom + extraInsets.bottom; + totalInsets.right = insets.right + extraInsets.right; + return totalInsets; + } + + /** + * Sets the vertical gap between components. + */ + public void setVgap(int vgap) { + this.vgap = vgap; + } + + + /** + * @param name the name of the component + * @param comp the component to be added + */ + public void addLayoutComponent(String name, Component comp) { + constraints.put(comp, name); + } + + /** + * Removes the specified component from the layout. Not used by + * this class. + * @param comp the component to remove + * @see java.awt.Container#removeAll + */ + public void removeLayoutComponent(Component comp) { + constraints.remove(comp); + } + + boolean isFirstInRow(Component comp) { + String cons = (String) constraints.get(comp); + return cons != null && (cons.indexOf(RiverLayout.LINE_BREAK) != -1 || + cons.indexOf(RiverLayout.PARAGRAPH_BREAK) != -1); + } + + boolean hasHfill(Component comp) { + return hasConstraint(comp, RiverLayout.HFILL); + } + + boolean hasVfill(Component comp) { + return hasConstraint(comp, RiverLayout.VFILL); + } + + boolean hasConstraint(Component comp, String test) { + String cons = (String) constraints.get(comp); + if (cons == null) return false; + StringTokenizer tokens = new StringTokenizer(cons); + while (tokens.hasMoreTokens()) + if (tokens.nextToken().equals(test)) return true; + return false; + } + + /** + * Figure out tab stop x-positions + */ + protected Ruler calcTabs(Container target) { + Ruler ruler = new Ruler(); + int nmembers = target.getComponentCount(); + + int x = 0; + int tabIndex = 0; // First tab stop + for (int i = 0; i < nmembers; i++) { + Component m = target.getComponent(i); +// if (m.isVisible()) { + if (isFirstInRow(m) || i == 0) { + x = 0; + tabIndex = 0; + } + else x+= hgap; + if (hasConstraint(m, TAB_STOP)) { + ruler.setTab(tabIndex, x); // Will only increase + x = ruler.getTab(tabIndex++); // Jump forward if neccesary + } + Dimension d = m.getPreferredSize(); + x += d.width; + } +// } + return ruler; + } + + /** + * Returns the preferred dimensions for this layout given the + * <i>visible</i> components in the specified target container. + * @param target the component which needs to be laid out + * @return the preferred dimensions to lay out the + * subcomponents of the specified container + * @see Container + * @see #minimumLayoutSize + * @see java.awt.Container#getPreferredSize + */ + public Dimension preferredLayoutSize(Container target) { + synchronized (target.getTreeLock()) { + Dimension dim = new Dimension(0, 0); + Dimension rowDim = new Dimension(0, 0); + int nmembers = target.getComponentCount(); + boolean firstVisibleComponent = true; + int tabIndex = 0; + Ruler ruler = calcTabs(target); + + for (int i = 0; i < nmembers; i++) { + Component m = target.getComponent(i); +// if (m.isVisible()) { + if (isFirstInRow(m)) { + tabIndex = 0; + dim.width = Math.max(dim.width, rowDim.width); + dim.height += rowDim.height + vgap; + if (hasConstraint(m, PARAGRAPH_BREAK)) dim.height += 2*vgap; + rowDim = new Dimension(0, 0); + } + if (hasConstraint(m, TAB_STOP)) rowDim.width = ruler.getTab(tabIndex++); + Dimension d = m.getPreferredSize(); + rowDim.height = Math.max(rowDim.height, d.height); + if (firstVisibleComponent) { + firstVisibleComponent = false; + } + else { + rowDim.width += hgap; + } + rowDim.width += d.width; + // } + } + dim.width = Math.max(dim.width, rowDim.width); + dim.height += rowDim.height; + + Insets insets = getInsets(target); + dim.width += insets.left + insets.right;// + hgap * 2; + dim.height += insets.top + insets.bottom;// + vgap * 2; + return dim; + } + } + + /** + * Returns the minimum dimensions needed to layout the <i>visible</i> + * components contained in the specified target container. + * @param target the component which needs to be laid out + * @return the minimum dimensions to lay out the + * subcomponents of the specified container + * @see #preferredLayoutSize + * @see java.awt.Container + * @see java.awt.Container#doLayout + */ + public Dimension minimumLayoutSize(Container target) { + synchronized (target.getTreeLock()) { + Dimension dim = new Dimension(0, 0); + Dimension rowDim = new Dimension(0, 0); + int nmembers = target.getComponentCount(); + boolean firstVisibleComponent = true; + int tabIndex = 0; + Ruler ruler = calcTabs(target); + + for (int i = 0; i < nmembers; i++) { + Component m = target.getComponent(i); + // if (m.isVisible()) { + if (isFirstInRow(m)) { + tabIndex = 0; + dim.width = Math.max(dim.width, rowDim.width); + dim.height += rowDim.height + vgap; + if (hasConstraint(m, PARAGRAPH_BREAK)) dim.height += 2*vgap; + rowDim = new Dimension(0, 0); + } + if (hasConstraint(m, TAB_STOP)) rowDim.width = ruler.getTab(tabIndex++); + Dimension d = m.getMinimumSize(); + rowDim.height = Math.max(rowDim.height, d.height); + if (firstVisibleComponent) { + firstVisibleComponent = false; + } + else { + rowDim.width += hgap; + } + rowDim.width += d.width; +// } + } + dim.width = Math.max(dim.width, rowDim.width); + dim.height += rowDim.height; + + Insets insets = getInsets(target); + dim.width += insets.left + insets.right;// + hgap * 2; + dim.height += insets.top + insets.bottom;// + vgap * 2; + return dim; + } + } + + /** + * Centers the elements in the specified row, if there is any slack. + * @param target the component which needs to be moved + * @param x the x coordinate + * @param y the y coordinate + * @param width the width dimensions + * @param height the height dimensions + * @param rowStart the beginning of the row + * @param rowEnd the the ending of the row + */ + protected void moveComponents(Container target, int x, int y, int width, + int height, + int rowStart, int rowEnd, boolean ltr, Ruler ruler) { + synchronized (target.getTreeLock()) { + switch (getAlignment()) { + case FlowLayout.LEFT: + x += ltr ? 0 : width; + break; + case FlowLayout.CENTER: + x += width / 2; + break; + case FlowLayout.RIGHT: + x += ltr ? width : 0; + break; + case LEADING: + break; + case TRAILING: + x += width; + break; + } + int tabIndex = 0; + for (int i = rowStart; i < rowEnd; i++) { + Component m = target.getComponent(i); +// if (m.isVisible()) { + if (hasConstraint(m, TAB_STOP)) x = getInsets(target).left + ruler.getTab(tabIndex++); + int dy = (valign == VTOP) ? 0 : (height - m.getHeight()) / 2; + if (ltr) { + m.setLocation(x, y + dy); + } + else { + m.setLocation(target.getWidth() - x - m.getWidth(), + y + dy); + } + x += m.getWidth() + hgap; +// } + } + } + } + + + protected void relMove(Container target, int dx, int dy, int rowStart, + int rowEnd) { + synchronized (target.getTreeLock()) { + for (int i = rowStart; i < rowEnd; i++) { + Component m = target.getComponent(i); +// if (m.isVisible()) { + m.setLocation(m.getX() + dx, m.getY() + dy); +// } + } + + } + } + + protected void adjustAlignment(Component m) { + if (hasConstraint(m, RiverLayout.LEFT)) setAlignment(FlowLayout.LEFT); + else if (hasConstraint(m, RiverLayout.RIGHT)) setAlignment(FlowLayout.RIGHT); + else if (hasConstraint(m, RiverLayout.CENTER)) setAlignment(FlowLayout.CENTER); + if (hasConstraint(m, RiverLayout.VTOP)) valign = VTOP; + else if (hasConstraint(m, RiverLayout.VCENTER)) valign = VCENTER; + + } + /** + * Lays out the container. This method lets each component take + * its preferred size by reshaping the components in the + * target container in order to satisfy the constraints of + * this <code>FlowLayout</code> object. + * @param target the specified component being laid out + * @see Container + * @see java.awt.Container#doLayout + */ + public void layoutContainer(Container target) { + setAlignment(FlowLayout.LEFT); + synchronized (target.getTreeLock()) { + Insets insets = getInsets(target); + int maxwidth = target.getWidth() - + (insets.left + insets.right); + int maxheight = target.getHeight() - + (insets.top + insets.bottom); + + int nmembers = target.getComponentCount(); + int x = 0, y = insets.top + vgap; + int rowh = 0, start = 0, moveDownStart = 0; + + boolean ltr = target.getComponentOrientation().isLeftToRight(); + Component toHfill = null; + Component toVfill = null; + Ruler ruler = calcTabs(target); + int tabIndex = 0; + + for (int i = 0; i < nmembers; i++) { + Component m = target.getComponent(i); + //if (m.isVisible()) { + Dimension d = m.getPreferredSize(); + m.setSize(d.width, d.height); + + if (isFirstInRow(m)) tabIndex = 0; + if (hasConstraint(m, TAB_STOP)) x = ruler.getTab(tabIndex++); + if (!isFirstInRow(m)) { + if (i > 0 && !hasConstraint(m, TAB_STOP)) { + x += hgap; + } + x += d.width; + rowh = Math.max(rowh, d.height); + } + else { + if (toVfill != null && moveDownStart == 0) { + moveDownStart = i; + } + if (toHfill != null) { + toHfill.setSize(toHfill.getWidth() + maxwidth - x, + toHfill.getHeight()); + x = maxwidth; + } + moveComponents(target, insets.left, y, + maxwidth - x, + rowh, start, i, ltr, ruler); + x = d.width; + y += vgap + rowh; + if (hasConstraint(m, PARAGRAPH_BREAK)) y += 2*vgap; + rowh = d.height; + start = i; + toHfill = null; + } + //} + if (hasHfill(m)) { + toHfill = m; + } + if (hasVfill(m)) { + toVfill = m; + } + adjustAlignment(m); + } + + if (toVfill != null && moveDownStart == 0) { // Don't move anything if hfill component is last component + moveDownStart = nmembers; + } + if (toHfill != null) { // last component + toHfill.setSize(toHfill.getWidth() + maxwidth - x, + toHfill.getHeight()); + x = maxwidth; + } + moveComponents(target, insets.left, y, maxwidth - x, rowh, + start, nmembers, ltr, ruler); + int yslack = maxheight - (y+rowh); + if (yslack != 0 && toVfill != null) { + toVfill.setSize(toVfill.getWidth(), yslack + toVfill.getHeight()); + relMove(target, 0, yslack, moveDownStart, nmembers); + } + } + } + +} + +class Ruler { + private Vector tabs = new Vector(); + + public void setTab(int num, int xpos) { + if (num >= tabs.size()) tabs.add(num, new Integer(xpos)); + else { + // Transpose all tabs from this tab stop and onwards + int delta = xpos - getTab(num); + if (delta > 0) { + for (int i = num; i < tabs.size(); i++) { + tabs.set(i, new Integer(getTab(i) + delta)); + } + } + } + } + + public int getTab(int num) { + return ((Integer)tabs.get(num)).intValue(); + } + + public String toString() { + StringBuffer ret = new StringBuffer(getClass().getName() + " {"); + for (int i=0; i<tabs.size(); i++) { + ret.append(tabs.get(i)); + if (i < tabs.size()-1) ret.append(','); + } + ret.append('}'); + return ret.toString(); + } + + public static void main(String[] args) { + Ruler r = new Ruler(); + r.setTab(0,10); + r.setTab(1,20); + r.setTab(2,30); + System.out.println(r); + r.setTab(1,25); + System.out.println(r); + System.out.println(r.getTab(0)); + } +} diff --git a/PFTT/src/se/datadosen/component/Ruler.class b/PFTT/src/se/datadosen/component/Ruler.class new file mode 100644 index 0000000..98aaf53 Binary files /dev/null and b/PFTT/src/se/datadosen/component/Ruler.class differ