testr 14.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/.gitignore +4 -0
- data/Gemfile +4 -0
- data/HISTORY.md +768 -0
- data/LICENSE +19 -0
- data/README.md +269 -0
- data/Rakefile +1 -0
- data/bin/testr +55 -0
- data/bin/testr-driver +4 -0
- data/bin/testr-herald +9 -0
- data/bin/testr-master +4 -0
- data/lib/testr/client.rb +39 -0
- data/lib/testr/config.rb +63 -0
- data/lib/testr/config/parallel_tests.rb +9 -0
- data/lib/testr/config/rails.rb +37 -0
- data/lib/testr/driver.rb +129 -0
- data/lib/testr/master.rb +100 -0
- data/lib/testr/server.rb +32 -0
- data/lib/testr/version.rb +3 -0
- data/testr.gemspec +25 -0
- metadata +109 -0
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'testr/config'
|
2
|
+
|
3
|
+
TestR::Config.after_fork_hooks << lambda {
|
4
|
+
|worker_number, log_file, test_file, test_names|
|
5
|
+
|
6
|
+
# for compatitibilty with parallel_tests gem,
|
7
|
+
# store numbers as strings: "", "2", "3", "4"
|
8
|
+
ENV['TEST_ENV_NUMBER'] = (worker_number + 1).to_s if worker_number > 0
|
9
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'testr/config'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
|
4
|
+
TestR::Config.reabsorb_file_globs.push(
|
5
|
+
'config/**/*.{rb,yml}',
|
6
|
+
'db/schema.rb',
|
7
|
+
'Gemfile.lock'
|
8
|
+
)
|
9
|
+
|
10
|
+
TestR::Config.test_file_matchers[%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
|
+
TestR::Config.test_file_matchers[%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
|
+
begin
|
25
|
+
require 'rails/railtie'
|
26
|
+
Class.new Rails::Railtie do
|
27
|
+
config.before_initialize do |app|
|
28
|
+
if app.config.cache_classes
|
29
|
+
warn "testr/config/rails: Setting #{app.class}.config.cache_classes = false"
|
30
|
+
app.config.cache_classes = false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
rescue LoadError
|
35
|
+
warn "testr/config/rails: Railtie not available; please manually set:\n\t"\
|
36
|
+
"config.cache_classes = false"
|
37
|
+
end
|
data/lib/testr/driver.rb
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'diff/lcs'
|
3
|
+
require 'testr/client'
|
4
|
+
require 'testr/server'
|
5
|
+
require 'testr/config'
|
6
|
+
|
7
|
+
module TestR
|
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 very_first_time = false
|
31
|
+
quit_herald_and_master unless very_first_time
|
32
|
+
|
33
|
+
@master = Client::Transceiver.new('testr-master') do |line|
|
34
|
+
event, file = JSON.load(line)
|
35
|
+
|
36
|
+
case event.to_sym
|
37
|
+
when :test
|
38
|
+
@running_test_files.push file
|
39
|
+
|
40
|
+
when :pass
|
41
|
+
@passed_test_files.push file unless @passed_test_files.include? file
|
42
|
+
@running_test_files.delete file
|
43
|
+
|
44
|
+
when :fail
|
45
|
+
@failed_test_files.push file unless @failed_test_files.include? file
|
46
|
+
@running_test_files.delete file
|
47
|
+
end
|
48
|
+
|
49
|
+
@upstream.print line
|
50
|
+
end
|
51
|
+
|
52
|
+
@master.send [:load, Config.overhead_load_paths,
|
53
|
+
Dir[*Config.overhead_file_globs]]
|
54
|
+
|
55
|
+
@herald = Client::Receiver.new('testr-herald') do |line|
|
56
|
+
changed_file = line.chomp
|
57
|
+
warn "testr-driver: herald: #{changed_file}" if $DEBUG
|
58
|
+
|
59
|
+
# find and run the tests that correspond to the changed file
|
60
|
+
Config.test_file_globbers.each do |source_regexp, test_globber|
|
61
|
+
if source_regexp =~ changed_file
|
62
|
+
run_test_files Dir[test_globber.call(changed_file).to_s]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# reabsorb text execution overhead if overhead files changed
|
67
|
+
if Config.reabsorb_file_greps.any? {|r| r =~ changed_file }
|
68
|
+
@upstream.puts JSON.dump([:over, changed_file])
|
69
|
+
# NOTE: new thread because reabsorb_overhead_files will kill this one
|
70
|
+
Thread.new { reabsorb_overhead_files }.join
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
rerun_running_test_files
|
75
|
+
end
|
76
|
+
|
77
|
+
def loop
|
78
|
+
reabsorb_overhead_files true
|
79
|
+
super
|
80
|
+
quit_herald_and_master
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def quit_herald_and_master
|
86
|
+
@herald.quit
|
87
|
+
@master.quit
|
88
|
+
end
|
89
|
+
|
90
|
+
@running_test_files = []
|
91
|
+
@passed_test_files = []
|
92
|
+
@failed_test_files = []
|
93
|
+
|
94
|
+
def rerun_running_test_files
|
95
|
+
run_test_files @running_test_files
|
96
|
+
end
|
97
|
+
|
98
|
+
def run_test_files files
|
99
|
+
files.each {|f| run_test_file f }
|
100
|
+
end
|
101
|
+
|
102
|
+
def run_test_file file
|
103
|
+
@master.send [:test, file, find_changed_test_names(file)]
|
104
|
+
end
|
105
|
+
|
106
|
+
@lines_by_file = {}
|
107
|
+
|
108
|
+
def find_changed_test_names test_file
|
109
|
+
# cache the contents of the test file for diffing below
|
110
|
+
new_lines = File.readlines(test_file)
|
111
|
+
old_lines = @lines_by_file[test_file] || new_lines
|
112
|
+
@lines_by_file[test_file] = new_lines
|
113
|
+
|
114
|
+
# find which tests have changed inside the given test file
|
115
|
+
Diff::LCS.diff(old_lines, new_lines).flatten.map do |change|
|
116
|
+
catch :found do
|
117
|
+
# search backwards from the line that changed up to
|
118
|
+
# the first line in the file for test definitions
|
119
|
+
change.position.downto(0) do |i|
|
120
|
+
if test_name = Config.test_name_extractor.call(new_lines[i])
|
121
|
+
throw :found, test_name
|
122
|
+
end
|
123
|
+
end; nil # prevent unsuccessful search from returning an integer
|
124
|
+
end
|
125
|
+
end.compact.uniq
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
data/lib/testr/master.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'testr/server'
|
3
|
+
require 'testr/config'
|
4
|
+
|
5
|
+
module TestR
|
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
|
+
@upstream.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
|
+
# NOTE: the next SIGCHLD signal will wake us from this eternal sleep
|
26
|
+
sleep until @command_by_worker_pid.size < Config.max_forked_workers
|
27
|
+
|
28
|
+
log_file = test_file + '.log'
|
29
|
+
worker_number = @worker_number_pool.shift
|
30
|
+
|
31
|
+
Config.before_fork_hooks.each do |hook|
|
32
|
+
hook.call worker_number, log_file, test_file, test_names
|
33
|
+
end
|
34
|
+
|
35
|
+
worker_pid = fork do
|
36
|
+
# make the process title Test::Unit friendly and ps(1) searchable
|
37
|
+
$0 = "testr-worker[#{worker_number}] #{test_file}"
|
38
|
+
|
39
|
+
# detach worker process from master process' group for kill -pgrp
|
40
|
+
Process.setsid
|
41
|
+
|
42
|
+
# detach worker process from master process' standard input stream
|
43
|
+
STDIN.reopen IO.pipe.first
|
44
|
+
|
45
|
+
# capture test output in log file because tests are run in parallel
|
46
|
+
# which makes it difficult to understand interleaved output thereof
|
47
|
+
STDERR.reopen(STDOUT.reopen(log_file, 'w')).sync = true
|
48
|
+
|
49
|
+
Config.after_fork_hooks.each do |hook|
|
50
|
+
hook.call worker_number, log_file, test_file, test_names
|
51
|
+
end
|
52
|
+
|
53
|
+
# after loading the user's test file, the at_exit() hook of the user's
|
54
|
+
# testing framework will take care of running the tests and reflecting
|
55
|
+
# any failures in the worker process' exit status, which will then be
|
56
|
+
# handled by the SIGCHLD trap registered in the master process (above)
|
57
|
+
Kernel.load test_file
|
58
|
+
end
|
59
|
+
|
60
|
+
@command_by_worker_pid[worker_pid] = @command.push(worker_number)
|
61
|
+
@upstream.print @command_line
|
62
|
+
end
|
63
|
+
|
64
|
+
def stop
|
65
|
+
# NOTE: the SIGCHLD handler will reap these killed worker processes
|
66
|
+
Process.kill :SIGTERM, *@command_by_worker_pid.keys.map {|pid| -pid }
|
67
|
+
rescue ArgumentError, SystemCallError
|
68
|
+
# some workers might have already exited before we sent them the signal
|
69
|
+
end
|
70
|
+
|
71
|
+
def loop
|
72
|
+
super
|
73
|
+
stop
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
@worker_number_pool = (0 ... Config.max_forked_workers).to_a
|
79
|
+
@command_by_worker_pid = {}
|
80
|
+
|
81
|
+
# process exited child processes and report finished workers to upstream
|
82
|
+
trap :SIGCHLD do
|
83
|
+
begin
|
84
|
+
while wait2_array = Process.wait2(-1, Process::WNOHANG)
|
85
|
+
child_pid, child_status = wait2_array
|
86
|
+
if command = @command_by_worker_pid.delete(child_pid)
|
87
|
+
@worker_number_pool.push command.pop
|
88
|
+
command[0] = child_status.success? ? 'pass' : 'fail'
|
89
|
+
@upstream.puts JSON.dump(command.push(child_status))
|
90
|
+
else
|
91
|
+
warn "testr-master: unknown child exited: #{wait2_array.inspect}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
rescue SystemCallError
|
95
|
+
# raised by wait2() when there are currently no child processes
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
data/lib/testr/server.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module TestR
|
4
|
+
module Server
|
5
|
+
|
6
|
+
def quit
|
7
|
+
throw :testr_server_quit
|
8
|
+
end
|
9
|
+
|
10
|
+
def loop
|
11
|
+
(@upstream = STDOUT.dup).sync = true
|
12
|
+
STDOUT.reopen(STDERR).sync = true
|
13
|
+
|
14
|
+
catch :testr_server_quit do
|
15
|
+
while line = STDIN.gets
|
16
|
+
warn "#{caller[2]} RECV #{line.chomp}" if $DEBUG
|
17
|
+
|
18
|
+
command = JSON.load(line)
|
19
|
+
method = command.first
|
20
|
+
|
21
|
+
if respond_to? method and method != __method__ # prevent loops
|
22
|
+
@command, @command_line = command, line
|
23
|
+
__send__(*command)
|
24
|
+
else
|
25
|
+
warn "#{self}: bad command: #{method}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
data/testr.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "testr/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "testr"
|
7
|
+
s.version = TestR::VERSION
|
8
|
+
s.authors,
|
9
|
+
s.email = File.read('LICENSE').scan(/Copyright \d+ (.+) <(.+?)>/).transpose
|
10
|
+
s.homepage = "http://github.com/sunaku/testr"
|
11
|
+
s.summary = "Continuous testing tool for Ruby"
|
12
|
+
s.description = nil
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
# specify any dependencies here; for example:
|
20
|
+
# s.add_development_dependency "rspec"
|
21
|
+
# s.add_runtime_dependency "rest-client"
|
22
|
+
s.add_runtime_dependency "json", ">= 1.6.1"
|
23
|
+
s.add_runtime_dependency "guard", ">= 0.8.4"
|
24
|
+
s.add_runtime_dependency "diff-lcs", ">= 1.1.2"
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: testr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 14.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Suraj N. Kurapati
|
9
|
+
- Brian D. Burns
|
10
|
+
- Daniel Pittman
|
11
|
+
- Jacob Helwig
|
12
|
+
- Corné Verbruggen
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
date: 2011-10-09 00:00:00.000000000 Z
|
17
|
+
dependencies:
|
18
|
+
- !ruby/object:Gem::Dependency
|
19
|
+
name: json
|
20
|
+
requirement: &20744840 !ruby/object:Gem::Requirement
|
21
|
+
none: false
|
22
|
+
requirements:
|
23
|
+
- - ! '>='
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: 1.6.1
|
26
|
+
type: :runtime
|
27
|
+
prerelease: false
|
28
|
+
version_requirements: *20744840
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: guard
|
31
|
+
requirement: &20743380 !ruby/object:Gem::Requirement
|
32
|
+
none: false
|
33
|
+
requirements:
|
34
|
+
- - ! '>='
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 0.8.4
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: *20743380
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: diff-lcs
|
42
|
+
requirement: &20742260 !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.1.2
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: *20742260
|
51
|
+
description: ''
|
52
|
+
email:
|
53
|
+
- sunaku@gmail.com
|
54
|
+
- burns180@gmail.com
|
55
|
+
- daniel@rimspace.net
|
56
|
+
- jacob@technosorcery.net
|
57
|
+
- corne@g-majeur.nl
|
58
|
+
executables:
|
59
|
+
- testr
|
60
|
+
- testr-driver
|
61
|
+
- testr-herald
|
62
|
+
- testr-master
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- .gitignore
|
67
|
+
- Gemfile
|
68
|
+
- HISTORY.md
|
69
|
+
- LICENSE
|
70
|
+
- README.md
|
71
|
+
- Rakefile
|
72
|
+
- bin/testr
|
73
|
+
- bin/testr-driver
|
74
|
+
- bin/testr-herald
|
75
|
+
- bin/testr-master
|
76
|
+
- lib/testr/client.rb
|
77
|
+
- lib/testr/config.rb
|
78
|
+
- lib/testr/config/parallel_tests.rb
|
79
|
+
- lib/testr/config/rails.rb
|
80
|
+
- lib/testr/driver.rb
|
81
|
+
- lib/testr/master.rb
|
82
|
+
- lib/testr/server.rb
|
83
|
+
- lib/testr/version.rb
|
84
|
+
- testr.gemspec
|
85
|
+
homepage: http://github.com/sunaku/testr
|
86
|
+
licenses: []
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ! '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
requirements: []
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 1.8.11
|
106
|
+
signing_key:
|
107
|
+
specification_version: 3
|
108
|
+
summary: Continuous testing tool for Ruby
|
109
|
+
test_files: []
|