tork 18.2.4 → 19.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.markdown +65 -0
- data/README.markdown +60 -298
- data/bin/tork +99 -62
- data/bin/tork-driver +124 -25
- data/bin/tork-engine +45 -23
- data/bin/tork-herald +5 -5
- data/bin/tork-master +79 -32
- data/bin/tork-notify +70 -0
- data/bin/tork-remote +80 -0
- data/lib/tork/cliapp.rb +73 -0
- data/lib/tork/config.rb +14 -97
- data/lib/tork/config/coverage/master.rb +29 -0
- data/lib/tork/config/coverage/worker.rb +1 -0
- data/lib/tork/config/cucumber/driver.rb +11 -0
- data/lib/tork/config/cucumber/worker.rb +14 -0
- data/lib/tork/config/default/config.rb +5 -0
- data/lib/tork/config/dotlog/onfork.rb +2 -0
- data/lib/tork/config/factory_girl/onfork.rb +2 -0
- data/lib/tork/config/factory_girl/worker.rb +1 -0
- data/lib/tork/config/logdir/onfork.rb +4 -0
- data/lib/tork/config/parallel_tests/worker.rb +4 -0
- data/lib/tork/config/rails/driver.rb +15 -0
- data/lib/tork/config/rails/master.rb +12 -0
- data/lib/tork/config/rails/worker.rb +21 -0
- data/lib/tork/config/spec/driver.rb +17 -0
- data/lib/tork/config/spec/master.rb +3 -0
- data/lib/tork/config/spec/worker.rb +3 -0
- data/lib/tork/config/test/driver.rb +17 -0
- data/lib/tork/config/test/master.rb +3 -0
- data/lib/tork/config/test/worker.rb +23 -0
- data/lib/tork/driver.rb +68 -36
- data/lib/tork/engine.rb +57 -44
- data/lib/tork/master.rb +34 -34
- data/lib/tork/server.rb +118 -17
- data/lib/tork/version.rb +1 -1
- data/man/man1/tork-driver.1 +165 -37
- data/man/man1/tork-engine.1 +50 -32
- data/man/man1/tork-herald.1 +5 -8
- data/man/man1/tork-master.1 +95 -42
- data/man/man1/tork-notify.1 +27 -0
- data/man/man1/tork-remote.1 +38 -0
- data/man/man1/tork.1 +116 -8
- data/tork.gemspec +3 -3
- metadata +36 -25
- data/lib/tork/client.rb +0 -53
- data/lib/tork/config/coverage.rb +0 -40
- data/lib/tork/config/cucumber.rb +0 -32
- data/lib/tork/config/dotlog.rb +0 -8
- data/lib/tork/config/factory_girl.rb +0 -10
- data/lib/tork/config/logdir.rb +0 -10
- data/lib/tork/config/notify.rb +0 -34
- data/lib/tork/config/parallel_tests.rb +0 -9
- data/lib/tork/config/rails.rb +0 -39
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'coverage'
|
2
|
+
at_exit do
|
3
|
+
if not $! or ($!.kind_of? SystemExit and $!.success?) and
|
4
|
+
coverage_by_file = Coverage.result rescue nil
|
5
|
+
then
|
6
|
+
report = {}
|
7
|
+
coverage_by_file.each do |file, coverage|
|
8
|
+
# ignore files outside working directory
|
9
|
+
if file.start_with? Dir.pwd
|
10
|
+
nsloc = 0
|
11
|
+
holes = []
|
12
|
+
coverage.each_with_index do |hits, index|
|
13
|
+
# ignore non-source lines of code
|
14
|
+
unless hits.nil?
|
15
|
+
nsloc += 1
|
16
|
+
# +1 because line numbers go 1..N
|
17
|
+
holes << index + 1 if hits.zero?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
grade = ((nsloc - holes.length) / nsloc.to_f) * 100
|
22
|
+
report[file] = { grade: grade, nsloc: nsloc, holes: holes }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'yaml'
|
27
|
+
YAML.dump report, STDOUT
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Coverage.start
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Tork::Driver::ALL_TEST_FILE_GLOBS.push 'features/**/*.feature'
|
2
|
+
|
3
|
+
Tork::Driver::TEST_FILE_GLOBBERS.update(
|
4
|
+
# source files that correspond to test files
|
5
|
+
%r{^(features/(.+/)?)step_definitions/.+\.rb$} => lambda do |matches|
|
6
|
+
matches[1] + '*.feature'
|
7
|
+
end,
|
8
|
+
|
9
|
+
# the actual test files themselves
|
10
|
+
%r{^features/.+\.feature$} => lambda {|matches| matches[0] }
|
11
|
+
)
|
@@ -0,0 +1,14 @@
|
|
1
|
+
if $tork_test_file.end_with? '.feature'
|
2
|
+
original_argv = ARGV.dup
|
3
|
+
begin
|
4
|
+
# pass the feature file to cucumber(1) in ARGV
|
5
|
+
ARGV.push [$tork_test_file, *$tork_line_numbers].join(':')
|
6
|
+
require 'rubygems'
|
7
|
+
require 'cucumber'
|
8
|
+
load Gem.bin_path('cucumber', 'cucumber')
|
9
|
+
ensure
|
10
|
+
# restore ARGV for other at_exit hooks. otherwise, RSpec's hook will
|
11
|
+
# try to load the non-Ruby feature file from ARGV and fail accordingly.
|
12
|
+
ARGV.replace original_argv
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,5 @@
|
|
1
|
+
ENV['TORK_CONFIGS'] += ':rails' if Dir['script/{rails,console}'].any?
|
2
|
+
ENV['TORK_CONFIGS'] += ':test' if File.directory? 'test'
|
3
|
+
ENV['TORK_CONFIGS'] += ':spec' if File.directory? 'spec'
|
4
|
+
ENV['TORK_CONFIGS'] += ':cucumber' if File.directory? 'features'
|
5
|
+
ENV['TORK_CONFIGS'] += ':factory_girl' if Dir['{test,spec}/factories/'].any?
|
@@ -0,0 +1 @@
|
|
1
|
+
FactoryGirl.find_definitions
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
|
3
|
+
Tork::Driver::REABSORB_FILE_GREPS.push(
|
4
|
+
%r{^config/.+\.(rb|yml)$},
|
5
|
+
%r{^db/schema\.rb$},
|
6
|
+
%r{^Gemfile\.lock$}
|
7
|
+
)
|
8
|
+
|
9
|
+
Tork::Driver::TEST_FILE_GLOBBERS.update(
|
10
|
+
%r{^(app|lib|test|spec)/.*?([^/]+?)(_factory)?\.rb$} => lambda do |matches|
|
11
|
+
single = matches[2]
|
12
|
+
plural = ActiveSupport::Inflector.pluralize(single)
|
13
|
+
"{test,spec}/**/{#{single},#{plural}_*}_{test,spec}.rb"
|
14
|
+
end
|
15
|
+
)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
begin
|
2
|
+
require 'rails/railtie'
|
3
|
+
class Tork::Railtie < Rails::Railtie
|
4
|
+
config.before_initialize do |app|
|
5
|
+
app.config.cache_classes = false
|
6
|
+
end
|
7
|
+
end
|
8
|
+
rescue LoadError => error
|
9
|
+
warn "tork/config/rails/master: could not set configuration using railties;\n"\
|
10
|
+
"you will have to add the following to your test environment manually:\n\t"\
|
11
|
+
'config.cache_classes = false'
|
12
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# although inherited as a file descriptor during the fork(), the database
|
2
|
+
# connection cannot be shared between the master and its workers or amongst
|
3
|
+
# the workers themselves because access to the file descriptor isn't mutexed
|
4
|
+
if defined? ActiveRecord::Base
|
5
|
+
base = ActiveRecord::Base
|
6
|
+
|
7
|
+
info =
|
8
|
+
if base.respond_to? :connection_info # rails >= 3.1.0
|
9
|
+
base.connection_info
|
10
|
+
elsif base.respond_to? :connection_pool # rails >= 2.2.1
|
11
|
+
base.connection_pool.spec.config
|
12
|
+
else
|
13
|
+
warn "#{$0}: config/rails/worker: couldn't read connection information"
|
14
|
+
{}
|
15
|
+
end
|
16
|
+
|
17
|
+
# in-memory databases are an exception; they are simply fork()ed along
|
18
|
+
if info[:database] != ':memory:'
|
19
|
+
base.connection.reconnect!
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Tork::Driver::REABSORB_FILE_GREPS.push 'spec/spec_helper.rb'
|
2
|
+
|
3
|
+
Tork::Driver::ALL_TEST_FILE_GLOBS.push 'spec/**/{spec_*,*_spec}.rb'
|
4
|
+
|
5
|
+
Tork::Driver::TEST_FILE_GLOBBERS.update(
|
6
|
+
# source files that correspond to test files
|
7
|
+
%r{^lib/.*?([^/]+)\.rb$} => lambda do |matches|
|
8
|
+
target = matches[1]
|
9
|
+
"spec/**/{spec_#{target},#{target}_spec}.rb"
|
10
|
+
end,
|
11
|
+
|
12
|
+
# the actual test files themselves
|
13
|
+
%r{^spec/.+\.rb$} => lambda do |matches|
|
14
|
+
target = matches[0]
|
15
|
+
target if File.basename(target) =~ /^spec_|_spec\./
|
16
|
+
end
|
17
|
+
)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Tork::Driver::REABSORB_FILE_GREPS.push 'test/test_helper.rb'
|
2
|
+
|
3
|
+
Tork::Driver::ALL_TEST_FILE_GLOBS.push 'test/**/{test_*,*_test}.rb'
|
4
|
+
|
5
|
+
Tork::Driver::TEST_FILE_GLOBBERS.update(
|
6
|
+
# source files that correspond to test files
|
7
|
+
%r{^lib/.*?([^/]+)\.rb$} => lambda do |matches|
|
8
|
+
target = matches[1]
|
9
|
+
"test/**/{test_#{target},#{target}_test}.rb"
|
10
|
+
end,
|
11
|
+
|
12
|
+
# the actual test files themselves
|
13
|
+
%r{^test/.+\.rb$} => lambda do |matches|
|
14
|
+
target = matches[0]
|
15
|
+
target if File.basename(target) =~ /^test_|_test\./
|
16
|
+
end
|
17
|
+
)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
unless $tork_line_numbers.empty?
|
2
|
+
test_file_lines = File.readlines($tork_test_file)
|
3
|
+
test_names = $tork_line_numbers.map do |line|
|
4
|
+
catch :found do
|
5
|
+
# search backwards from the desired line number to
|
6
|
+
# the first line in the file for test definitions
|
7
|
+
line.downto(0) do |i|
|
8
|
+
test_name =
|
9
|
+
case test_file_lines[i]
|
10
|
+
when /^\s*def\s+test_(\w+)/ then $1
|
11
|
+
when /^\s*(test|context|should|describe|it)\b.+?(['"])(.*?)\2/
|
12
|
+
# elide string interpolation and invalid method name characters
|
13
|
+
$3.gsub(/\#\{.*?\}/, ' ').strip.gsub(/\W+/, '.*')
|
14
|
+
end \
|
15
|
+
and throw :found, test_name
|
16
|
+
end; nil # prevent unsuccessful search from returning an integer
|
17
|
+
end
|
18
|
+
end.compact.uniq
|
19
|
+
|
20
|
+
unless test_names.empty?
|
21
|
+
ARGV.push '--name', "/(?i:#{test_names.join('|')})/"
|
22
|
+
end
|
23
|
+
end
|
data/lib/tork/driver.rb
CHANGED
@@ -1,59 +1,91 @@
|
|
1
1
|
require 'set'
|
2
|
-
require 'tork/client'
|
3
2
|
require 'tork/engine'
|
3
|
+
require 'tork/server'
|
4
4
|
require 'tork/config'
|
5
5
|
|
6
6
|
module Tork
|
7
|
-
class Driver <
|
7
|
+
class Driver < Server
|
8
|
+
|
9
|
+
REABSORB_FILE_GREPS = []
|
10
|
+
ALL_TEST_FILE_GLOBS = []
|
11
|
+
TEST_FILE_GLOBBERS = {}
|
8
12
|
|
9
13
|
def initialize
|
10
14
|
super
|
11
|
-
|
12
|
-
@herald = Client::Transceiver.new('tork-herald') do |changed_files|
|
13
|
-
changed_files.each do |changed_file|
|
14
|
-
# find and run the tests that correspond to the changed file
|
15
|
-
visited = Set.new
|
16
|
-
visitor = lambda do |source_file|
|
17
|
-
Config.test_file_globbers.each do |regexp, globber|
|
18
|
-
if regexp =~ source_file and globs = globber.call($~)
|
19
|
-
Dir[*globs].each do |test_file|
|
20
|
-
if visited.add? test_file
|
21
|
-
run_test_file test_file
|
22
|
-
visitor.call test_file
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
visitor.call changed_file
|
29
|
-
|
30
|
-
# reabsorb text execution overhead if overhead files changed
|
31
|
-
if Config.reabsorb_file_greps.any? {|r| r =~ changed_file }
|
32
|
-
@client.send [:over, changed_file]
|
33
|
-
reabsorb_overhead_files
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
reabsorb_overhead_files
|
15
|
+
Tork.config :driver
|
39
16
|
end
|
40
17
|
|
41
|
-
def
|
42
|
-
@herald
|
18
|
+
def loop
|
19
|
+
@herald = popen('tork-herald')
|
20
|
+
@engine = popen('tork-engine')
|
43
21
|
super
|
22
|
+
ensure
|
23
|
+
pclose @herald
|
24
|
+
pclose @engine
|
44
25
|
end
|
45
26
|
|
46
27
|
def run_all_test_files
|
47
|
-
all_test_files = Dir[*
|
28
|
+
all_test_files = Dir[*ALL_TEST_FILE_GLOBS]
|
48
29
|
if all_test_files.empty?
|
49
|
-
|
30
|
+
tell @client, 'There are no test files to run.'
|
50
31
|
else
|
51
32
|
run_test_files all_test_files
|
52
33
|
end
|
53
34
|
end
|
54
35
|
|
55
|
-
|
56
|
-
|
36
|
+
# accept and delegate tork-engine(1) commands
|
37
|
+
Engine.public_instance_methods(false).each do |name|
|
38
|
+
unless method_defined? name
|
39
|
+
define_method name do |*args|
|
40
|
+
send @engine, [name, *args]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def recv client, message
|
48
|
+
case client
|
49
|
+
when @engine
|
50
|
+
send @clients, message # propagate downstream
|
51
|
+
|
52
|
+
when @herald
|
53
|
+
message.each do |changed_file|
|
54
|
+
# reabsorb text execution overhead if overhead files changed
|
55
|
+
overhead_changed = REABSORB_FILE_GREPS.any? do |pattern|
|
56
|
+
if pattern.kind_of? Regexp
|
57
|
+
pattern =~ changed_file
|
58
|
+
else
|
59
|
+
pattern == changed_file
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
if overhead_changed
|
64
|
+
send @clients, [:reabsorb, changed_file]
|
65
|
+
reabsorb_overhead
|
66
|
+
else
|
67
|
+
run_test_files find_dependent_test_files(changed_file).to_a
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
else
|
72
|
+
super
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def find_dependent_test_files source_file, results=Set.new
|
79
|
+
TEST_FILE_GLOBBERS.each do |regexp, globber|
|
80
|
+
if regexp =~ source_file and globs = globber.call($~)
|
81
|
+
Dir[*globs].each do |dependent_file|
|
82
|
+
if results.add? dependent_file
|
83
|
+
find_dependent_test_files dependent_file, results
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
results
|
57
89
|
end
|
58
90
|
|
59
91
|
end
|
data/lib/tork/engine.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'set'
|
2
2
|
require 'diff/lcs'
|
3
|
-
require 'tork/client'
|
4
3
|
require 'tork/server'
|
5
4
|
require 'tork/config'
|
6
5
|
|
@@ -9,49 +8,59 @@ class Engine < Server
|
|
9
8
|
|
10
9
|
def initialize
|
11
10
|
super
|
12
|
-
|
13
|
-
|
11
|
+
Tork.config :engine
|
12
|
+
|
13
|
+
@queued_test_files = Set.new
|
14
14
|
@passed_test_files = Set.new
|
15
15
|
@failed_test_files = Set.new
|
16
16
|
@lines_by_file = {}
|
17
|
-
@master = create_master_process
|
18
17
|
end
|
19
18
|
|
20
|
-
def
|
21
|
-
|
19
|
+
def loop
|
20
|
+
create_master
|
22
21
|
super
|
22
|
+
ensure
|
23
|
+
destroy_master
|
23
24
|
end
|
24
25
|
|
25
|
-
def reabsorb_overhead
|
26
|
-
|
27
|
-
|
28
|
-
@master.send [:load, load_paths, overhead_files]
|
26
|
+
def reabsorb_overhead
|
27
|
+
destroy_master
|
28
|
+
create_master
|
29
29
|
|
30
30
|
# re-dispatch the previously dispatched files to the new master
|
31
|
-
|
32
|
-
@
|
33
|
-
run_test_files
|
31
|
+
previous = @queued_test_files.to_a
|
32
|
+
@queued_test_files.clear
|
33
|
+
run_test_files previous
|
34
34
|
end
|
35
35
|
|
36
|
-
def run_test_file test_file, line_numbers
|
37
|
-
if File.exist? test_file and @
|
38
|
-
line_numbers
|
39
|
-
|
36
|
+
def run_test_file test_file, *line_numbers
|
37
|
+
if File.exist? test_file and @queued_test_files.add? test_file
|
38
|
+
if line_numbers.empty?
|
39
|
+
line_numbers = find_changed_line_numbers(test_file)
|
40
|
+
else
|
41
|
+
line_numbers.map!(&:to_i)
|
42
|
+
line_numbers.clear if line_numbers.any?(&:zero?)
|
43
|
+
end
|
44
|
+
send @master, [:test, test_file, line_numbers]
|
40
45
|
end
|
41
46
|
end
|
42
47
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
48
|
+
def run_test_files test_files_with_optional_line_numbers
|
49
|
+
test_files_with_optional_line_numbers.each {|f| run_test_file(*f) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def stop_running_test_files signal=nil
|
53
|
+
if @queued_test_files.empty?
|
54
|
+
tell @client, 'There are no running test files to stop.'
|
46
55
|
else
|
47
|
-
@master
|
48
|
-
@
|
56
|
+
send @master, [:stop, signal].compact
|
57
|
+
@queued_test_files.clear
|
49
58
|
end
|
50
59
|
end
|
51
60
|
|
52
61
|
def rerun_passed_test_files
|
53
62
|
if @passed_test_files.empty?
|
54
|
-
|
63
|
+
tell @client, 'There are no passed test files to re-run.'
|
55
64
|
else
|
56
65
|
run_test_files @passed_test_files
|
57
66
|
end
|
@@ -59,7 +68,7 @@ class Engine < Server
|
|
59
68
|
|
60
69
|
def rerun_failed_test_files
|
61
70
|
if @failed_test_files.empty?
|
62
|
-
|
71
|
+
tell @client, 'There are no failed test files to re-run.'
|
63
72
|
else
|
64
73
|
run_test_files @failed_test_files
|
65
74
|
end
|
@@ -67,41 +76,37 @@ class Engine < Server
|
|
67
76
|
|
68
77
|
protected
|
69
78
|
|
70
|
-
def
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
private
|
75
|
-
|
76
|
-
def create_master_process
|
77
|
-
Client::Transceiver.new('tork-master') do |message|
|
78
|
-
@client.send message # propagate output downstream
|
79
|
+
def recv client, message
|
80
|
+
case client
|
81
|
+
when @master
|
82
|
+
send @clients, message # propagate downstream
|
79
83
|
|
80
84
|
event, file, line_numbers = message
|
81
85
|
case event.to_sym
|
82
86
|
when :test
|
83
|
-
@
|
84
|
-
@running_test_files.add file
|
87
|
+
@queued_test_files.delete file
|
85
88
|
|
86
89
|
when :pass
|
87
|
-
|
88
|
-
|
89
|
-
# only whole test file runs qualify as pass
|
90
|
+
# only whole test file runs should qualify as pass
|
90
91
|
if line_numbers.empty?
|
91
|
-
@failed_test_files.delete file
|
92
|
-
@passed_test_files.add file
|
92
|
+
was_fail = @failed_test_files.delete? file
|
93
|
+
now_pass = @passed_test_files.add? file
|
94
|
+
send @clients, [:fail_now_pass, file, message] if was_fail and now_pass
|
93
95
|
end
|
94
96
|
|
95
97
|
when :fail
|
96
|
-
@
|
97
|
-
@failed_test_files.add file
|
98
|
-
@
|
98
|
+
was_pass = @passed_test_files.delete? file
|
99
|
+
now_fail = @failed_test_files.add? file
|
100
|
+
send @clients, [:pass_now_fail, file, message] if was_pass and now_fail
|
99
101
|
end
|
100
102
|
|
101
|
-
|
103
|
+
else
|
104
|
+
super
|
102
105
|
end
|
103
106
|
end
|
104
107
|
|
108
|
+
private
|
109
|
+
|
105
110
|
def find_changed_line_numbers test_file
|
106
111
|
# cache test file contents for diffing below
|
107
112
|
new_lines = File.readlines(test_file)
|
@@ -114,5 +119,13 @@ private
|
|
114
119
|
map {|change| change.position + 1 }.uniq
|
115
120
|
end
|
116
121
|
|
122
|
+
def create_master
|
123
|
+
@master = popen('tork-master')
|
124
|
+
end
|
125
|
+
|
126
|
+
def destroy_master
|
127
|
+
pclose @master
|
128
|
+
end
|
129
|
+
|
117
130
|
end
|
118
131
|
end
|