tork 18.2.4 → 19.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/HISTORY.markdown +65 -0
  2. data/README.markdown +60 -298
  3. data/bin/tork +99 -62
  4. data/bin/tork-driver +124 -25
  5. data/bin/tork-engine +45 -23
  6. data/bin/tork-herald +5 -5
  7. data/bin/tork-master +79 -32
  8. data/bin/tork-notify +70 -0
  9. data/bin/tork-remote +80 -0
  10. data/lib/tork/cliapp.rb +73 -0
  11. data/lib/tork/config.rb +14 -97
  12. data/lib/tork/config/coverage/master.rb +29 -0
  13. data/lib/tork/config/coverage/worker.rb +1 -0
  14. data/lib/tork/config/cucumber/driver.rb +11 -0
  15. data/lib/tork/config/cucumber/worker.rb +14 -0
  16. data/lib/tork/config/default/config.rb +5 -0
  17. data/lib/tork/config/dotlog/onfork.rb +2 -0
  18. data/lib/tork/config/factory_girl/onfork.rb +2 -0
  19. data/lib/tork/config/factory_girl/worker.rb +1 -0
  20. data/lib/tork/config/logdir/onfork.rb +4 -0
  21. data/lib/tork/config/parallel_tests/worker.rb +4 -0
  22. data/lib/tork/config/rails/driver.rb +15 -0
  23. data/lib/tork/config/rails/master.rb +12 -0
  24. data/lib/tork/config/rails/worker.rb +21 -0
  25. data/lib/tork/config/spec/driver.rb +17 -0
  26. data/lib/tork/config/spec/master.rb +3 -0
  27. data/lib/tork/config/spec/worker.rb +3 -0
  28. data/lib/tork/config/test/driver.rb +17 -0
  29. data/lib/tork/config/test/master.rb +3 -0
  30. data/lib/tork/config/test/worker.rb +23 -0
  31. data/lib/tork/driver.rb +68 -36
  32. data/lib/tork/engine.rb +57 -44
  33. data/lib/tork/master.rb +34 -34
  34. data/lib/tork/server.rb +118 -17
  35. data/lib/tork/version.rb +1 -1
  36. data/man/man1/tork-driver.1 +165 -37
  37. data/man/man1/tork-engine.1 +50 -32
  38. data/man/man1/tork-herald.1 +5 -8
  39. data/man/man1/tork-master.1 +95 -42
  40. data/man/man1/tork-notify.1 +27 -0
  41. data/man/man1/tork-remote.1 +38 -0
  42. data/man/man1/tork.1 +116 -8
  43. data/tork.gemspec +3 -3
  44. metadata +36 -25
  45. data/lib/tork/client.rb +0 -53
  46. data/lib/tork/config/coverage.rb +0 -40
  47. data/lib/tork/config/cucumber.rb +0 -32
  48. data/lib/tork/config/dotlog.rb +0 -8
  49. data/lib/tork/config/factory_girl.rb +0 -10
  50. data/lib/tork/config/logdir.rb +0 -10
  51. data/lib/tork/config/notify.rb +0 -34
  52. data/lib/tork/config/parallel_tests.rb +0 -9
  53. 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,2 @@
1
+ dirname, basename = File.split($tork_log_file)
2
+ $tork_log_file.replace File.join(dirname, '.' + basename)
@@ -0,0 +1,2 @@
1
+ require 'factory_girl'
2
+ FactoryGirl.factories.clear
@@ -0,0 +1 @@
1
+ FactoryGirl.find_definitions
@@ -0,0 +1,4 @@
1
+ require 'fileutils'
2
+ dirname, basename = File.split($tork_log_file)
3
+ FileUtils.mkdir_p log_dir = File.join('log', dirname)
4
+ $tork_log_file.replace File.join(log_dir, basename)
@@ -0,0 +1,4 @@
1
+ # parallel_tests expects the sequence "", "2", "3", "4", ...
2
+ if $tork_worker_number > 0
3
+ ENV['TEST_ENV_NUMBER'] = ($tork_worker_number + 1).to_s
4
+ end
@@ -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,3 @@
1
+ $LOAD_PATH.unshift 'spec' unless $LOAD_PATH.include? 'spec'
2
+ $LOAD_PATH.unshift 'lib' unless $LOAD_PATH.include? 'lib'
3
+ require 'spec_helper' if File.exist? 'spec/spec_helper.rb'
@@ -0,0 +1,3 @@
1
+ $tork_line_numbers.each do |line|
2
+ ARGV.push '--line_number', line.to_s
3
+ end
@@ -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,3 @@
1
+ $LOAD_PATH.unshift 'test' unless $LOAD_PATH.include? 'test'
2
+ $LOAD_PATH.unshift 'lib' unless $LOAD_PATH.include? 'lib'
3
+ require 'test_helper' if File.exist? 'test/test_helper.rb'
@@ -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 < Engine
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 quit
42
- @herald.quit
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[*Config.all_test_file_globs]
28
+ all_test_files = Dir[*ALL_TEST_FILE_GLOBS]
48
29
  if all_test_files.empty?
49
- warn "#{$0}: There are no test files to run."
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
- def reabsorb_overhead_files
56
- reabsorb_overhead Config.overhead_load_paths, Dir[*Config.overhead_file_globs]
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
- @waiting_test_files = Set.new # dispatched to master but not yet running
13
- @running_test_files = Set.new # dispatched to master and started running
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 quit
21
- @master.quit
19
+ def loop
20
+ create_master
22
21
  super
22
+ ensure
23
+ destroy_master
23
24
  end
24
25
 
25
- def reabsorb_overhead load_paths, overhead_files
26
- @master.quit
27
- @master = create_master_process
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
- dispatched_test_files = @running_test_files + @waiting_test_files
32
- @waiting_test_files.clear
33
- run_test_files dispatched_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=nil
37
- if File.exist? test_file and @waiting_test_files.add? test_file
38
- line_numbers ||= find_changed_line_numbers(test_file)
39
- @master.send [:test, test_file, line_numbers]
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 stop_running_test_files
44
- if @running_test_files.empty?
45
- warn "#{$0}: There are no running test files to stop."
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.send [:stop]
48
- @running_test_files.clear
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
- warn "#{$0}: There are no passed test files to re-run."
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
- warn "#{$0}: There are no failed test files to re-run."
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 run_test_files files
71
- files.each {|f| run_test_file f }
72
- end
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
- @waiting_test_files.delete file
84
- @running_test_files.add file
87
+ @queued_test_files.delete file
85
88
 
86
89
  when :pass
87
- @running_test_files.delete file
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
- @running_test_files.delete file
97
- @failed_test_files.add file
98
- @passed_test_files.delete file
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
- Config.test_event_hooks.each {|hook| hook.call message }
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