tork 15.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+ =begin
3
+
4
+ TORK-HERALD 1 2012-01-23 15.0.0
5
+ ==============================================================================
6
+
7
+ NAME
8
+ ------------------------------------------------------------------------------
9
+
10
+ tork-herald - reports modified files
11
+
12
+ SYNOPSIS
13
+ ------------------------------------------------------------------------------
14
+
15
+ `tork-herald` [*OPTION*]...
16
+
17
+ DESCRIPTION
18
+ ------------------------------------------------------------------------------
19
+
20
+ This program monitors the current working directory and prints relative paths
21
+ of modified files, one per line, to the standard output stream.
22
+
23
+ OPTIONS
24
+ ------------------------------------------------------------------------------
25
+
26
+ `-h`, `--help`
27
+ Display this help manual using man(1).
28
+
29
+ SEE ALSO
30
+ ------------------------------------------------------------------------------
31
+
32
+ tork(1), tork-driver(1), tork-master(1), tork-herald(1)
33
+
34
+ =end =========================================================================
35
+
36
+ $0 = File.basename(__FILE__) # for easier indentification in ps(1) output
37
+
38
+ require 'binman'
39
+ BinMan.help
40
+
41
+ require 'guard'
42
+ require 'guard/listener'
43
+
44
+ listener = Guard::Listener.select_and_init(:watch_all_modifications => true)
45
+ listener.on_change {|files| puts files }
46
+ STDOUT.sync = true # don't buffer puts()
47
+ listener.start
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env ruby
2
+ =begin
3
+
4
+ TORK-MASTER 1 2012-01-23 15.0.0
5
+ ==============================================================================
6
+
7
+ NAME
8
+ ------------------------------------------------------------------------------
9
+
10
+ tork-master - absorbs overhead and runs tests
11
+
12
+ SYNOPSIS
13
+ ------------------------------------------------------------------------------
14
+
15
+ `tork-master` [*OPTION*]...
16
+
17
+ DESCRIPTION
18
+ ------------------------------------------------------------------------------
19
+
20
+ This program reads the following single-line commands (JSON arrays) from its
21
+ standard input stream and performs the respective actions as described below.
22
+
23
+ `["load",` *paths*`,` *files*`]`
24
+ Adds the given array of *paths* to Ruby's $LOAD_PATH, loads the given array
25
+ of *files* after removing their ".rb" file extension if present, and prints
26
+ the given command line to the standard output stream.
27
+
28
+ `["test",` *test_file*`,` *test_names*`]`
29
+ Runs the given *test_file* in a forked child process while instructing your
30
+ chosen unit testing framework (loaded by your test execution overhead) to
31
+ only run those tests that are named in the given array of *test_names*.
32
+
33
+ Prints the given command line to the standard output stream immediately
34
+ after forking the child process.
35
+
36
+ Prints the given command line, modified with `"pass"` (if the test passed)
37
+ or `"fail"` (if the test failed) in place of `"test"`, to the standard
38
+ output stream after the forked child process finishes.
39
+
40
+ The standard output and error streams of the forked child process are
41
+ redirected to a file whose path and name are the same as that of the test
42
+ file being run by the forked child process but with ".log" appended.
43
+
44
+ `["stop"]`
45
+ Stops all tests that are currently running and prints the given command line
46
+ to the standard output stream.
47
+
48
+ `["quit"]`
49
+ Stops all tests that are currently running and exits.
50
+
51
+ OPTIONS
52
+ ------------------------------------------------------------------------------
53
+
54
+ `-h`, `--help`
55
+ Display this help manual using man(1).
56
+
57
+ FILES
58
+ ------------------------------------------------------------------------------
59
+
60
+ *.tork.rb*
61
+ Optional Ruby script for configuring tork(1).
62
+
63
+ ENVIRONMENT
64
+ ------------------------------------------------------------------------------
65
+
66
+ `TORK_CONFIGS`
67
+ A single-line JSON array containing paths to actual files or names of
68
+ helper libraries in the tork/config/ namespace of Ruby's load path.
69
+ These configuration files are loaded just before *.tork.rb* is loaded.
70
+
71
+ SEE ALSO
72
+ ------------------------------------------------------------------------------
73
+
74
+ tork(1), tork-driver(1), tork-master(1), tork-herald(1)
75
+
76
+ =end =========================================================================
77
+
78
+ $0 = File.basename(__FILE__) # for easier indentification in ps(1) output
79
+
80
+ require 'binman'
81
+ BinMan.help
82
+
83
+ require 'tork/master'
84
+ Tork::Master.loop
85
+
86
+ Process.waitall
87
+ raise SystemExit # prevent empty test suite from running in the master process
@@ -0,0 +1,34 @@
1
+ module Tork
2
+ module Client
3
+
4
+ class Receiver < Thread
5
+ def initialize *popen_args
6
+ (@io = IO.popen(*popen_args)).sync = true
7
+ super() { loop { yield @io.gets } }
8
+ end
9
+
10
+ def quit
11
+ kill # stop receive loop
12
+ Process.kill :SIGTERM, @io.pid
13
+ Process.wait @io.pid # reap zombie
14
+ @io.close # prevent further I/O
15
+ end
16
+ end
17
+
18
+ class Transceiver < Receiver
19
+ def initialize *popen_args
20
+ @io_write_lock = Mutex.new
21
+ popen_args[1] = 'w+'
22
+ super
23
+ end
24
+
25
+ def send command
26
+ @io_write_lock.synchronize do
27
+ warn "#{caller[2]} SEND #{command.inspect}" if $DEBUG
28
+ @io.puts JSON.dump(command)
29
+ end
30
+ end
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,83 @@
1
+ require 'ostruct'
2
+
3
+ module Tork
4
+ _user_config_file = '.tork.rb'
5
+
6
+ Config = OpenStruct.new
7
+
8
+ #---------------------------------------------------------------------------
9
+ # defaults
10
+ #---------------------------------------------------------------------------
11
+
12
+ Config.max_forked_workers = [
13
+ # http://stackoverflow.com/questions/891537#6420817
14
+ 'fgrep -c processor /proc/cpuinfo', # Linux
15
+ 'sysctl -n hw.ncpu', # BSD
16
+ 'hwprefs cpu_count', # Darwin 9
17
+ 'hwprefs thread_count', # Darwin 10
18
+ ].map {|cmd| `#{cmd} 2>/dev/null`.to_i }.push(1).max
19
+
20
+ Config.overhead_load_paths = ['lib', 'test', 'spec']
21
+
22
+ Config.overhead_file_globs = ['{test,spec}/{test,spec}_helper.rb']
23
+
24
+ Config.reabsorb_file_greps = [/^#{Regexp.quote(_user_config_file)}$/,
25
+ %r<(test|spec)/\1_helper\.rb>]
26
+
27
+ Config.all_test_file_globs = ['{test,spec}/**/*_{test,spec}.rb']
28
+
29
+ Config.test_file_globbers = {
30
+ # source files that correspond to test files
31
+ %r<^lib/.+\.rb$> => lambda do |path|
32
+ base = File.basename(path, '.rb')
33
+ "{test,spec}/**/#{base}_{test,spec}.rb"
34
+ end,
35
+
36
+ # the actual test files themselves
37
+ %r<^(test|spec)/.+_\1\.rb$> => lambda {|path| path }
38
+ }
39
+
40
+ Config.test_name_extractor = lambda do |line|
41
+ case line
42
+ when /^\s*def\s+test_(\w+)/ then $1
43
+ when /^\s*(test|context|should|describe|it)\b.+?(['"])(.*?)\2/ then $3
44
+ end
45
+ end
46
+
47
+ Config.before_fork_hooks = []
48
+
49
+ Config.after_fork_hooks = [
50
+ # tell testing framework to only run the named tests inside the test file
51
+ lambda do |worker_number, log_file, test_file, test_names|
52
+ unless test_names.empty?
53
+ case File.basename(test_file)
54
+ when /(\b|_)test(\b|_)/ # Test::Unit
55
+ ARGV.push '--name', "/(?i:#{
56
+ test_names.map do |name|
57
+ # elide string interpolation and invalid method name characters
58
+ name.gsub(/\#\{.*?\}/, ' ').strip.gsub(/\W+/, '.*')
59
+ end.join('|')
60
+ })/"
61
+ when /(\b|_)spec(\b|_)/ # RSpec
62
+ test_names.each {|name| ARGV.push '--example', name }
63
+ end
64
+ end
65
+ end
66
+ ]
67
+
68
+ #---------------------------------------------------------------------------
69
+ # overrides
70
+ #---------------------------------------------------------------------------
71
+
72
+ if ENV.key? 'TORK_CONFIGS'
73
+ JSON.load(ENV['TORK_CONFIGS']).each do |config|
74
+ if File.exist? config
75
+ load File.expand_path(config)
76
+ else
77
+ require "tork/config/#{config}"
78
+ end
79
+ end
80
+ end
81
+
82
+ load _user_config_file if File.exist? _user_config_file
83
+ end
@@ -0,0 +1,7 @@
1
+ require 'tork/config'
2
+
3
+ Tork::Config.after_fork_hooks << proc do |worker_number|
4
+ # for compatitibilty with parallel_tests gem,
5
+ # store numbers as strings: "", "2", "3", "4"
6
+ ENV['TEST_ENV_NUMBER'] = (worker_number + 1).to_s if worker_number > 0
7
+ end
@@ -0,0 +1,43 @@
1
+ require 'tork/config'
2
+ require 'active_support/inflector'
3
+
4
+ Tork::Config.reabsorb_file_greps.push(
5
+ %r<^config/.+\.(rb|yml)$>,
6
+ %r<^db/schema\.rb$>,
7
+ %r<^Gemfile\.lock$>
8
+ )
9
+
10
+ Tork::Config.test_file_globbers[%r<^(app|lib|test|spec)/.+\.rb$>] =
11
+ lambda do |path|
12
+ base = File.basename(path, '.rb')
13
+ poly = ActiveSupport::Inflector.pluralize(base)
14
+ "{test,spec}/**/{#{base},#{poly}_*}_{test,spec}.rb"
15
+ end
16
+
17
+ Tork::Config.test_file_globbers[%r<^(test|spec)/factories/.+_factory\.rb$>] =
18
+ lambda do |path|
19
+ base = File.basename(path, '_factory.rb')
20
+ poly = ActiveSupport::Inflector.pluralize(base)
21
+ "{test,spec}/**/{#{base},#{poly}_*}_{test,spec}.rb"
22
+ end
23
+
24
+ Tork::Config.after_fork_hooks << proc do
25
+ unless ActiveRecord::Base.connection_config[:database] == ':memory:'
26
+ ActiveRecord::Base.connection.reconnect!
27
+ end
28
+ end
29
+
30
+ begin
31
+ require 'rails/railtie'
32
+ Class.new Rails::Railtie do
33
+ config.before_initialize do |app|
34
+ if app.config.cache_classes
35
+ warn "tork/config/rails: Setting #{app.class}.config.cache_classes = false"
36
+ app.config.cache_classes = false
37
+ end
38
+ end
39
+ end
40
+ rescue LoadError
41
+ warn "tork/config/rails: Railtie not available; please manually set:\n\t"\
42
+ "config.cache_classes = false"
43
+ end
@@ -0,0 +1,136 @@
1
+ require 'json'
2
+ require 'diff/lcs'
3
+ require 'tork/client'
4
+ require 'tork/server'
5
+ require 'tork/config'
6
+
7
+ module Tork
8
+ module Driver
9
+
10
+ extend Server
11
+ extend self
12
+
13
+ def run_all_test_files
14
+ run_test_files Dir[*Config.all_test_file_globs]
15
+ end
16
+
17
+ def stop_running_test_files
18
+ @master.send [:stop]
19
+ @running_test_files.clear
20
+ end
21
+
22
+ def rerun_passed_test_files
23
+ run_test_files @passed_test_files
24
+ end
25
+
26
+ def rerun_failed_test_files
27
+ run_test_files @failed_test_files
28
+ end
29
+
30
+ def reabsorb_overhead_files
31
+ @master.quit if defined? @master
32
+
33
+ @master = Client::Transceiver.new('tork-master') do |line|
34
+ event, file, tests = JSON.load(line)
35
+
36
+ case event.to_sym
37
+ when :test
38
+ @waiting_test_files.delete file
39
+ @running_test_files.push file
40
+
41
+ when :pass
42
+ @running_test_files.delete file
43
+ @failed_test_files.delete file
44
+ if tests.empty? and not @passed_test_files.include? file
45
+ @passed_test_files.push file
46
+ end
47
+
48
+ when :fail
49
+ @running_test_files.delete file
50
+ @passed_test_files.delete file
51
+ @failed_test_files.push file unless @failed_test_files.include? file
52
+ end
53
+
54
+ @client.print line
55
+ end
56
+
57
+ @master.send [:load, Config.overhead_load_paths,
58
+ Dir[*Config.overhead_file_globs]]
59
+
60
+ rerun_running_test_files
61
+ end
62
+
63
+ def loop
64
+ reabsorb_overhead_files
65
+
66
+ @herald = Client::Receiver.new('tork-herald') do |line|
67
+ changed_file = line.chomp
68
+ warn "tork-driver: herald: #{changed_file}" if $DEBUG
69
+
70
+ # find and run the tests that correspond to the changed file
71
+ Config.test_file_globbers.each do |regexp, globber|
72
+ if regexp =~ changed_file and glob = globber.call(changed_file)
73
+ run_test_files Dir[glob]
74
+ end
75
+ end
76
+
77
+ # reabsorb text execution overhead if overhead files changed
78
+ if Config.reabsorb_file_greps.any? {|r| r =~ changed_file }
79
+ @client.puts JSON.dump([:over, changed_file])
80
+ # NOTE: new thread because reabsorb_overhead_files will kill this one
81
+ Thread.new { reabsorb_overhead_files }.join
82
+ end
83
+ end
84
+
85
+ super
86
+
87
+ @herald.quit
88
+ @master.quit
89
+ end
90
+
91
+ private
92
+
93
+ @waiting_test_files = []
94
+ @running_test_files = []
95
+ @passed_test_files = []
96
+ @failed_test_files = []
97
+
98
+ def rerun_running_test_files
99
+ run_test_files @running_test_files
100
+ end
101
+
102
+ def run_test_files files
103
+ files.each {|f| run_test_file f }
104
+ end
105
+
106
+ def run_test_file file
107
+ if File.exist? file and not @waiting_test_files.include? file
108
+ @waiting_test_files.push file
109
+ @master.send [:test, file, find_changed_test_names(file)]
110
+ end
111
+ end
112
+
113
+ @lines_by_file = {}
114
+
115
+ def find_changed_test_names test_file
116
+ # cache the contents of the test file for diffing below
117
+ new_lines = File.readlines(test_file)
118
+ old_lines = @lines_by_file[test_file] || new_lines
119
+ @lines_by_file[test_file] = new_lines
120
+
121
+ # find which tests have changed inside the given test file
122
+ Diff::LCS.diff(old_lines, new_lines).flatten.map do |change|
123
+ catch :found do
124
+ # search backwards from the line that changed up to
125
+ # the first line in the file for test definitions
126
+ change.position.downto(0) do |i|
127
+ if test_name = Config.test_name_extractor.call(new_lines[i])
128
+ throw :found, test_name
129
+ end
130
+ end; nil # prevent unsuccessful search from returning an integer
131
+ end
132
+ end.compact.uniq
133
+ end
134
+
135
+ end
136
+ end
@@ -0,0 +1,99 @@
1
+ require 'json'
2
+ require 'tork/server'
3
+ require 'tork/config'
4
+
5
+ module Tork
6
+ module Master
7
+
8
+ extend Server
9
+ extend self
10
+
11
+ def load paths, files
12
+ $LOAD_PATH.unshift(*paths)
13
+
14
+ files.each do |file|
15
+ branch, leaf = File.split(file)
16
+ file = leaf if paths.include? branch
17
+ require file.sub(/\.rb$/, '')
18
+ end
19
+
20
+ @client.print @command_line
21
+ end
22
+
23
+ def test test_file, test_names
24
+ # throttle forking rate to meet the maximum concurrent workers limit
25
+ sleep 1 until @command_by_worker_pid.size < Config.max_forked_workers
26
+
27
+ log_file = test_file + '.log'
28
+ worker_number = @worker_number_pool.shift
29
+
30
+ Config.before_fork_hooks.each do |hook|
31
+ hook.call worker_number, log_file, test_file, test_names
32
+ end
33
+
34
+ worker_pid = fork do
35
+ # make the process title Test::Unit friendly and ps(1) searchable
36
+ $0 = "tork-worker[#{worker_number}] #{test_file}"
37
+
38
+ # detach worker process from master process' group for kill -pgrp
39
+ Process.setsid
40
+
41
+ # detach worker process from master process' standard input stream
42
+ STDIN.reopen IO.pipe.first
43
+
44
+ # capture test output in log file because tests are run in parallel
45
+ # which makes it difficult to understand interleaved output thereof
46
+ STDERR.reopen(STDOUT.reopen(log_file, 'w')).sync = true
47
+
48
+ Config.after_fork_hooks.each do |hook|
49
+ hook.call worker_number, log_file, test_file, test_names
50
+ end
51
+
52
+ # after loading the user's test file, the at_exit() hook of the user's
53
+ # testing framework will take care of running the tests and reflecting
54
+ # any failures in the worker process' exit status, which will then be
55
+ # handled by the SIGCHLD trap registered in the master process (below)
56
+ Kernel.load test_file
57
+ end
58
+
59
+ @command_by_worker_pid[worker_pid] = @command.push(worker_number)
60
+ @client.print @command_line
61
+ end
62
+
63
+ def stop
64
+ # NOTE: the SIGCHLD handler will reap these killed worker processes
65
+ Process.kill :SIGTERM, *@command_by_worker_pid.keys.map {|pid| -pid }
66
+ rescue ArgumentError, SystemCallError
67
+ # some workers might have already exited before we sent them the signal
68
+ end
69
+
70
+ def loop
71
+ super
72
+ stop
73
+ end
74
+
75
+ private
76
+
77
+ @worker_number_pool = (0 ... Config.max_forked_workers).to_a
78
+ @command_by_worker_pid = {}
79
+
80
+ # process exited child processes and report finished workers to client
81
+ trap :SIGCHLD do
82
+ begin
83
+ while wait2_array = Process.wait2(-1, Process::WNOHANG)
84
+ child_pid, child_status = wait2_array
85
+ if command = @command_by_worker_pid.delete(child_pid)
86
+ @worker_number_pool.push command.pop
87
+ command[0] = child_status.success? ? 'pass' : 'fail'
88
+ @client.puts JSON.dump(command.push(child_status))
89
+ else
90
+ warn "tork-master: unknown child exited: #{wait2_array.inspect}"
91
+ end
92
+ end
93
+ rescue SystemCallError
94
+ # raised by wait2() when there are currently no child processes
95
+ end
96
+ end
97
+
98
+ end
99
+ end