tork 18.2.4 → 19.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|