testr 14.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|