polyamory 0.0.5 → 0.6.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/README.md +37 -29
- data/bin/polyamory +22 -15
- data/lib/polyamory.rb +75 -210
- data/lib/polyamory/command.rb +25 -0
- data/lib/polyamory/cucumber.rb +50 -0
- data/lib/polyamory/rooted_pathname.rb +51 -0
- data/lib/polyamory/rspec.rb +137 -0
- data/lib/polyamory/runner.rb +135 -0
- data/lib/polyamory/test_unit.rb +196 -0
- data/test/test_runner.rb +250 -0
- metadata +13 -6
data/README.md
CHANGED
@@ -1,35 +1,43 @@
|
|
1
|
-
# Polyamory
|
1
|
+
# Polyamory – the promiscuous test runner
|
2
2
|
|
3
|
-
Polyamory
|
3
|
+
Polyamory is a command-line tool that knows how to run your tests regardless of
|
4
|
+
the framework. It can either run the whole test suite or filter by keywords,
|
5
|
+
test case names, or tags. It remembers the differences between arguments for
|
6
|
+
different testing frameworks so you don't have to.
|
7
|
+
|
8
|
+
Frameworks supported:
|
9
|
+
|
10
|
+
* Cucumber in `features/**/*.feature`
|
11
|
+
* RSpec + Shoulda in `spec/**/*_spec.rb`
|
12
|
+
* test/unit, Shoulda, or anything else in `test/**/*_test.rb` or `test/**/test*.rb`
|
4
13
|
|
5
14
|
Features:
|
6
15
|
|
7
|
-
*
|
8
|
-
|
9
|
-
* use a keyword to run all test files which contain that word
|
10
|
-
* Bundler support
|
16
|
+
* `polyamory` - Runs the full test suite for any project. For example, it will
|
17
|
+
run all of the following:
|
11
18
|
|
12
|
-
|
19
|
+
rspec spec
|
20
|
+
cucumber features
|
21
|
+
ruby -e 'ARGV.each {|f| require f }' test/**/*_test.rb
|
22
|
+
|
23
|
+
* `polyamory <dirname>` - Runs all tests inside a subdirectory. For example:
|
24
|
+
|
25
|
+
polyamory models
|
26
|
+
-> runs test/models/**/*_test.rb
|
27
|
+
-> runs spec/models/**/*_spec.rb
|
28
|
+
|
29
|
+
* `polyamory <keyword>` - Runs all test files that match a keyword. For example:
|
30
|
+
|
31
|
+
polyamory search
|
32
|
+
-> runs test/models/user_search_test.rb
|
33
|
+
-> runs spec/controllers/search_controller_spec.rb
|
34
|
+
-> runs features/site_search.feature
|
35
|
+
|
36
|
+
* `polyamory <file>:<line>` - Runs focused test. Provides this feature for
|
37
|
+
test/unit and minitest which don't support it.
|
38
|
+
|
39
|
+
* `polyamory -n <pattern>` - Runs only tests whose names match given patterns.
|
13
40
|
|
14
|
-
* Cucumber
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
## Examples
|
19
|
-
|
20
|
-
Here, `polyamory` is aliased as `pam` for brevity.
|
21
|
-
|
22
|
-
# run everything
|
23
|
-
$ pam
|
24
|
-
> rspec spec &&
|
25
|
-
cucumber -f progress -t ~@wip features &&
|
26
|
-
polyamory -t test
|
27
|
-
|
28
|
-
# everyting inside a single directory
|
29
|
-
$ pam test/unit
|
30
|
-
> polyamory -t test/unit
|
31
|
-
|
32
|
-
# run test files matching keyword
|
33
|
-
$ pam user
|
34
|
-
> polyamory -t spec/models/user_spec.rb spec/controllers/user_controller.rb &&
|
35
|
-
cucumber -f progress -t ~@wip features/user_registration.feature
|
41
|
+
* `polyamory -t <tag>` - Runs RSpec/Cucumber tests that match given tags.
|
42
|
+
Tag exclusion is done with `~<tag>`. Tag names are normalized for Cucumber
|
43
|
+
(which expects them in form of `@<tag>`).
|
data/bin/polyamory
CHANGED
@@ -1,24 +1,31 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
require 'polyamory'
|
3
|
+
|
2
4
|
if ARGV.delete '-t'
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
root = Pathname.pwd
|
6
|
+
|
7
|
+
if idx = ARGV.index('--')
|
8
|
+
names = ARGV[0...idx]
|
9
|
+
ARGV.slice! 0..idx
|
10
|
+
else
|
11
|
+
names = ARGV.dup
|
12
|
+
ARGV.clear
|
13
|
+
end
|
14
|
+
|
15
|
+
test_files = names.map { |arg|
|
16
|
+
if File.directory? arg
|
17
|
+
locator = Polyamory.new(names, root)
|
18
|
+
locator.find_test_files(root + arg)
|
19
|
+
else
|
20
|
+
arg
|
10
21
|
end
|
11
22
|
}.compact.flatten
|
12
|
-
|
23
|
+
|
13
24
|
if test_files.empty?
|
14
|
-
|
15
|
-
exit 1
|
25
|
+
abort "polyamory: nothing to load"
|
16
26
|
else
|
17
|
-
test_files.each { |f|
|
27
|
+
test_files.each { |f| require root + f }
|
18
28
|
end
|
19
29
|
else
|
20
|
-
|
21
|
-
options = {}
|
22
|
-
options[:noop] = !!ARGV.delete('-n')
|
23
|
-
Polyamory.run ARGV, Dir.pwd, options
|
30
|
+
Polyamory.run ARGV, Dir.pwd
|
24
31
|
end
|
data/lib/polyamory.rb
CHANGED
@@ -1,217 +1,82 @@
|
|
1
|
-
require '
|
1
|
+
require 'optparse'
|
2
|
+
require 'polyamory/runner'
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
@names = names
|
10
|
-
@root = Pathname.new(root).expand_path
|
11
|
-
@options = options
|
12
|
-
end
|
13
|
-
|
14
|
-
def noop?
|
15
|
-
@options[:noop]
|
16
|
-
end
|
17
|
-
|
18
|
-
def file_exists?(path)
|
19
|
-
(@root + path).exist?
|
20
|
-
end
|
21
|
-
|
22
|
-
def bundler?
|
23
|
-
file_exists? 'Gemfile'
|
24
|
-
end
|
25
|
-
|
26
|
-
def test_dir
|
27
|
-
@root + 'test'
|
28
|
-
end
|
29
|
-
|
30
|
-
def test_glob(dir = test_dir)
|
31
|
-
["#{dir}/**/*_test.rb", "#{dir}/**/test_*.rb"]
|
32
|
-
end
|
33
|
-
|
34
|
-
def spec_dir
|
35
|
-
@root + 'spec'
|
36
|
-
end
|
37
|
-
|
38
|
-
def spec_glob(dir = spec_dir)
|
39
|
-
"#{dir}/**/*_spec.rb"
|
40
|
-
end
|
41
|
-
|
42
|
-
def features_dir
|
43
|
-
@root + 'features'
|
44
|
-
end
|
45
|
-
|
46
|
-
def features_glob(dir = features_dir)
|
47
|
-
"#{dir}/**/*.feature"
|
4
|
+
module Polyamory
|
5
|
+
VERSION = '0.6.0'
|
6
|
+
|
7
|
+
def self.run(args, dir)
|
8
|
+
options = parse_options! args
|
9
|
+
Runner.new(args, dir, options).run
|
48
10
|
end
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
11
|
+
|
12
|
+
def self.parse_options!(args)
|
13
|
+
options = {
|
14
|
+
:warnings => false,
|
15
|
+
:verbose => false,
|
16
|
+
:backtrace => nil,
|
17
|
+
:test_seed => nil,
|
18
|
+
:bundler => nil,
|
19
|
+
:name_filters => [],
|
20
|
+
:tag_filters => [],
|
21
|
+
:load_paths => [],
|
22
|
+
}
|
23
|
+
|
24
|
+
OptionParser.new do |opts|
|
25
|
+
opts.banner = 'Usage: polyamory [<dirname>] [<file>[:<line>]] [-n <pattern>] [-t <tag>]'
|
26
|
+
opts.version = VERSION
|
27
|
+
|
28
|
+
opts.summary_indent = " " * 4
|
29
|
+
|
30
|
+
opts.separator "\n Ruby options:"
|
31
|
+
|
32
|
+
opts.on '-w', "Turn on Ruby warnings" do
|
33
|
+
options[:warnings] = true
|
57
34
|
end
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
super(path)
|
62
|
-
self.root = root_path
|
63
|
-
end
|
64
|
-
|
65
|
-
def root=(path)
|
66
|
-
@relativized = nil
|
67
|
-
@root = path
|
68
|
-
end
|
69
|
-
|
70
|
-
def relative
|
71
|
-
@relativized ||= relative_path_from root
|
72
|
-
end
|
73
|
-
|
74
|
-
def =~(pattern)
|
75
|
-
relative.to_s =~ pattern
|
76
|
-
end
|
77
|
-
|
78
|
-
def +(other)
|
79
|
-
result = self.class.new(plus(@path, other.to_s))
|
80
|
-
result.root ||= self
|
81
|
-
result
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def find_files
|
86
|
-
all_paths = Pathname.glob([test_glob, spec_glob, features_glob].flatten, @root)
|
87
|
-
|
88
|
-
if @names.any?
|
89
|
-
@names.map { |name|
|
90
|
-
path = @root + name
|
91
|
-
pattern = /\b#{Regexp.escape name}(\b|_)/
|
92
|
-
|
93
|
-
if path.directory? or not path.extname.empty?
|
94
|
-
path
|
95
|
-
else
|
96
|
-
all_paths.select { |p| p =~ pattern }
|
97
|
-
end
|
98
|
-
}.flatten
|
99
|
-
else
|
100
|
-
[test_dir, spec_dir, features_dir].select { |p| p.directory? }
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
def relativize(paths)
|
105
|
-
Array(paths).map { |p| p.relative }
|
106
|
-
end
|
107
|
-
|
108
|
-
def run
|
109
|
-
paths = relativize(find_files)
|
110
|
-
if paths.empty?
|
111
|
-
warn "nothing found to run"
|
112
|
-
exit 1
|
113
|
-
end
|
114
|
-
|
115
|
-
jobs = index_by_path_prefix(paths).map do |prefix, files|
|
116
|
-
[runner_for_prefix(prefix), *files].flatten
|
117
|
-
end
|
118
|
-
|
119
|
-
prepare_env
|
120
|
-
execute_jobs jobs
|
121
|
-
end
|
122
|
-
|
123
|
-
def runner_for_prefix(prefix)
|
124
|
-
case prefix
|
125
|
-
when 'features' then %w[cucumber -f progress -t ~@wip]
|
126
|
-
when 'spec' then detect_rspec_version
|
127
|
-
when 'test' then %w[polyamory -t]
|
128
|
-
else
|
129
|
-
raise ArgumentError, "don't know a runner for #{prefix}"
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
def detect_rspec_version
|
134
|
-
helper = 'spec/spec_helper.rb'
|
135
|
-
|
136
|
-
if file_exists? 'spec/spec.opts' or file_exists? 'lib/tasks/rspec.rake'
|
137
|
-
'spec'
|
138
|
-
elsif file_exists? '.rspec'
|
139
|
-
'rspec'
|
140
|
-
elsif file_exists? helper
|
141
|
-
File.open(helper) do |file|
|
142
|
-
while file.gets
|
143
|
-
return $&.downcase if $_ =~ /\bR?Spec\b/
|
144
|
-
end
|
35
|
+
|
36
|
+
opts.on '-I PATH', "Directory to load on $LOAD_PATH" do |str|
|
37
|
+
options[:load_paths] << str
|
145
38
|
end
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
exit $?.exitstatus unless $?.success?
|
185
|
-
else
|
186
|
-
exec(*args)
|
187
|
-
end
|
188
|
-
end
|
39
|
+
|
40
|
+
opts.separator "\n Test options:"
|
41
|
+
|
42
|
+
opts.on '-s', '--seed SEED', Integer, "Set random seed" do |m|
|
43
|
+
options[:test_seed] = m.to_i
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on '-b', '--[no-]backtrace', "Show full backtrace" do |set|
|
47
|
+
options[:backtrace] = set
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.on '-n', '--name PATTERN', "Filter test names on pattern" do |str|
|
51
|
+
options[:name_filters] << str
|
52
|
+
end
|
53
|
+
|
54
|
+
opts.on '-t', '--tag TAG', "Filter tests on tag" do |str|
|
55
|
+
options[:tag_filters] << str
|
56
|
+
end
|
57
|
+
|
58
|
+
opts.on '-v', '--verbose', "Show progress processing files" do
|
59
|
+
options[:verbose] = true
|
60
|
+
end
|
61
|
+
|
62
|
+
opts.on '--[no-]bundler', "Use `bundle exec' for running tests" do |set|
|
63
|
+
options[:bundler] = set
|
64
|
+
end
|
65
|
+
|
66
|
+
opts.separator "\n Other options:"
|
67
|
+
|
68
|
+
opts.on_tail '-h', '--help', 'Display this help' do
|
69
|
+
puts opts
|
70
|
+
exit
|
71
|
+
end
|
72
|
+
|
73
|
+
begin
|
74
|
+
opts.parse! args
|
75
|
+
rescue OptionParser::InvalidOption
|
76
|
+
abort opts.banner
|
189
77
|
end
|
190
78
|
end
|
79
|
+
|
80
|
+
options
|
191
81
|
end
|
192
|
-
|
193
|
-
def with_rubyopt(value)
|
194
|
-
with_env('RUBYOPT', "#{value} %s") { yield }
|
195
|
-
end
|
196
|
-
|
197
|
-
def with_rubylib(*values)
|
198
|
-
value = values.flatten.compact.join(':')
|
199
|
-
with_env('RUBYLIB', "#{value}:%s") { yield }
|
200
|
-
end
|
201
|
-
|
202
|
-
def with_env(key, value)
|
203
|
-
old_value = ENV[key]
|
204
|
-
ENV[key] = value % old_value
|
205
|
-
|
206
|
-
begin
|
207
|
-
yield
|
208
|
-
ensure
|
209
|
-
ENV[key] = old_value
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
def prepare_env
|
214
|
-
# TODO: make this per-job
|
215
|
-
ENV['RUBYOPT'] = ENV['RUBYOPT'].gsub(/(^| )-w( |$)/, '\1\2') if ENV['RUBYOPT']
|
216
|
-
end
|
217
|
-
end
|
82
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Polyamory
|
4
|
+
# Internal: Represents a single command to run.
|
5
|
+
class Command
|
6
|
+
attr_reader :env
|
7
|
+
|
8
|
+
def initialize cmd
|
9
|
+
@args = Array(cmd)
|
10
|
+
@env = Hash.new
|
11
|
+
yield self if block_given?
|
12
|
+
end
|
13
|
+
|
14
|
+
extend Forwardable
|
15
|
+
def_delegators :@args, :<<, :concat
|
16
|
+
|
17
|
+
def to_exec
|
18
|
+
@args.map {|a| a.to_s }
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
to_exec.join(' ')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'polyamory/rspec'
|
2
|
+
require 'polyamory/command'
|
3
|
+
|
4
|
+
module Polyamory
|
5
|
+
# Internal: Deals with finding specs to test
|
6
|
+
class Cucumber < RSpec
|
7
|
+
def test_dir_name
|
8
|
+
'features'
|
9
|
+
end
|
10
|
+
|
11
|
+
def file_pattern dir
|
12
|
+
"#{dir}/**/*.feature"
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_command paths
|
16
|
+
Command.new 'cucumber' do |test_job|
|
17
|
+
add_ruby_options test_job
|
18
|
+
test_job.concat cucumber_options
|
19
|
+
test_job.concat paths.map {|p| p.relative }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_ruby_options cmd
|
24
|
+
opts = []
|
25
|
+
opts << '-w' if context.warnings?
|
26
|
+
for path in context.load_paths
|
27
|
+
opts << "-I#{path}"
|
28
|
+
end
|
29
|
+
opts << '%'
|
30
|
+
cmd.env['RUBYOPT'] = opts.join(' ')
|
31
|
+
end
|
32
|
+
|
33
|
+
def cucumber_options
|
34
|
+
opts = []
|
35
|
+
opts << '-b' if context.full_backtrace?
|
36
|
+
for filter in context.name_filters
|
37
|
+
opts << '-n' << filter
|
38
|
+
end
|
39
|
+
for tag in context.tag_filters
|
40
|
+
opts << '-t' << normalize_tag(tag)
|
41
|
+
end
|
42
|
+
opts
|
43
|
+
end
|
44
|
+
|
45
|
+
def normalize_tag tag
|
46
|
+
tag = "#$1@#$2" if tag =~ /^(~)?(\w+)$/
|
47
|
+
tag
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Polyamory
|
4
|
+
# A kind of Pathname that keeps a reference to a root directory and is able to
|
5
|
+
# return a relativized pathname from that particular root.
|
6
|
+
class RootedPathname < ::Pathname
|
7
|
+
attr_reader :root
|
8
|
+
|
9
|
+
# Find pathnames matching the glob pattern and assign to them a root
|
10
|
+
def self.glob(patterns, root)
|
11
|
+
patterns = Array(patterns)
|
12
|
+
Dir[*patterns].map do |path|
|
13
|
+
self.new(path, root)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(path, root_path = nil)
|
18
|
+
super(path)
|
19
|
+
self.root = root_path
|
20
|
+
end
|
21
|
+
|
22
|
+
def root=(path)
|
23
|
+
@relativized = nil
|
24
|
+
@root = path
|
25
|
+
end
|
26
|
+
|
27
|
+
# Return the relative portion of the path from root
|
28
|
+
def relative
|
29
|
+
return self if relative?
|
30
|
+
@relativized ||= relative_path_from root
|
31
|
+
end
|
32
|
+
|
33
|
+
# Check if current path is contained in directory
|
34
|
+
def in_dir? dir
|
35
|
+
self == dir or
|
36
|
+
self.to_s.index(File.join(dir, '')) == 0
|
37
|
+
end
|
38
|
+
|
39
|
+
# Perform a regex match only on the relative portion of the path
|
40
|
+
def =~(pattern)
|
41
|
+
relative.to_s =~ pattern
|
42
|
+
end
|
43
|
+
|
44
|
+
# Add to the current path; the result has the current path as root
|
45
|
+
def +(other)
|
46
|
+
result = self.class.new(plus(@path, other.to_s))
|
47
|
+
result.root ||= self
|
48
|
+
result
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'polyamory/rooted_pathname'
|
2
|
+
require 'polyamory/command'
|
3
|
+
|
4
|
+
module Polyamory
|
5
|
+
# Internal: Deals with finding specs to test
|
6
|
+
class RSpec
|
7
|
+
attr_reader :context
|
8
|
+
|
9
|
+
def initialize context
|
10
|
+
@context = context
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_dir_name
|
14
|
+
'spec'
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_dir
|
18
|
+
@test_dir ||= context.root + test_dir_name
|
19
|
+
end
|
20
|
+
|
21
|
+
def file_pattern dir
|
22
|
+
"#{dir}/**/*_spec.rb"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Internal: Finds test files matching glob pattern
|
26
|
+
def find_files dir = test_dir
|
27
|
+
glob file_pattern(dir)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Internal: Memoized find_files in primary dir
|
31
|
+
def all_matching_files
|
32
|
+
@all_matching_files ||= find_files
|
33
|
+
end
|
34
|
+
|
35
|
+
# Public: Resolve a set of files, directories, and patterns to a list of
|
36
|
+
# paths to test.
|
37
|
+
def resolve_paths names
|
38
|
+
if names.any?
|
39
|
+
paths = []
|
40
|
+
for name in names
|
41
|
+
paths.concat Array(resolve_name name)
|
42
|
+
end
|
43
|
+
paths
|
44
|
+
else
|
45
|
+
Array(resolve_as_directory test_dir_name)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def handle? path
|
50
|
+
path.in_dir? test_dir
|
51
|
+
end
|
52
|
+
|
53
|
+
def resolve_name name
|
54
|
+
resolve_as_directory(name) or
|
55
|
+
resolve_as_filename(name) or
|
56
|
+
resolve_as_file_pattern(name) or
|
57
|
+
raise "nothing resolved from #{name}"
|
58
|
+
end
|
59
|
+
|
60
|
+
# "functional" => "test/functional/**"
|
61
|
+
# "test/functional" => "test/functional/**"
|
62
|
+
def resolve_as_directory name
|
63
|
+
[test_dir + name, context.root + name].detect { |dir|
|
64
|
+
dir.directory? and handle? dir
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
# "test/unit/test_user.rb:42"
|
69
|
+
def resolve_as_filename name
|
70
|
+
filename = name.sub(/:(\d+)$/, '')
|
71
|
+
file = context.root + filename
|
72
|
+
|
73
|
+
if file.file? and handle? file
|
74
|
+
context.root + name
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# "word" => "test/**" that match "word"
|
79
|
+
def resolve_as_file_pattern name
|
80
|
+
pattern = /(?:\b|_)#{Regexp.escape name}(?:\b|_)/
|
81
|
+
all_matching_files.select {|p| p =~ pattern }
|
82
|
+
end
|
83
|
+
|
84
|
+
# Public: From a list of paths, yank the ones that this knows how to handle,
|
85
|
+
# and build test jobs from it.
|
86
|
+
def pick_jobs paths
|
87
|
+
to_test = []
|
88
|
+
paths.reject! do |path|
|
89
|
+
if handle? path
|
90
|
+
to_test << path
|
91
|
+
true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
unless to_test.empty?
|
96
|
+
[test_command(to_test)]
|
97
|
+
else
|
98
|
+
[]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_command paths
|
103
|
+
Command.new 'rspec' do |test_job|
|
104
|
+
add_ruby_options test_job
|
105
|
+
test_job.concat rspec_options
|
106
|
+
test_job.concat paths.map {|p| p.relative }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def glob pattern
|
111
|
+
RootedPathname.glob pattern, context.root
|
112
|
+
end
|
113
|
+
|
114
|
+
def add_ruby_options cmd
|
115
|
+
opts = []
|
116
|
+
opts << '-w' if context.warnings?
|
117
|
+
opts << '%'
|
118
|
+
cmd.env['RUBYOPT'] = opts.join(' ')
|
119
|
+
end
|
120
|
+
|
121
|
+
def rspec_options
|
122
|
+
opts = []
|
123
|
+
opts << '-b' if context.full_backtrace?
|
124
|
+
opts << '--seed' << context.test_seed if context.test_seed
|
125
|
+
for path in context.load_paths
|
126
|
+
opts << "-I#{path}"
|
127
|
+
end
|
128
|
+
for filter in context.name_filters
|
129
|
+
opts << '-e' << filter
|
130
|
+
end
|
131
|
+
for tag in context.tag_filters
|
132
|
+
opts << '-t' << tag
|
133
|
+
end
|
134
|
+
opts
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'polyamory/rooted_pathname'
|
2
|
+
require 'polyamory/test_unit'
|
3
|
+
require 'polyamory/rspec'
|
4
|
+
require 'polyamory/cucumber'
|
5
|
+
|
6
|
+
module Polyamory
|
7
|
+
# Public: Collects test jobs in the root directory and runs them.
|
8
|
+
class Runner
|
9
|
+
attr_reader :root, :options
|
10
|
+
|
11
|
+
def initialize(names, root, options = {})
|
12
|
+
@names = names
|
13
|
+
@root = RootedPathname.new(root).expand_path
|
14
|
+
@options = options
|
15
|
+
end
|
16
|
+
|
17
|
+
def warnings?
|
18
|
+
options.fetch(:warnings)
|
19
|
+
end
|
20
|
+
|
21
|
+
def verbose?
|
22
|
+
options.fetch(:verbose)
|
23
|
+
end
|
24
|
+
|
25
|
+
def full_backtrace?
|
26
|
+
options.fetch(:backtrace)
|
27
|
+
end
|
28
|
+
|
29
|
+
def name_filters
|
30
|
+
options.fetch(:name_filters)
|
31
|
+
end
|
32
|
+
|
33
|
+
def tag_filters
|
34
|
+
options.fetch(:tag_filters)
|
35
|
+
end
|
36
|
+
|
37
|
+
def load_paths
|
38
|
+
options.fetch(:load_paths)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_seed
|
42
|
+
options.fetch(:test_seed)
|
43
|
+
end
|
44
|
+
|
45
|
+
def bundle_exec?
|
46
|
+
return @bundle_exec if defined? @bundle_exec
|
47
|
+
if (setting = options.fetch(:bundler)).nil?
|
48
|
+
setting = !ENV['BUNDLE_GEMFILE'].to_s.empty? ||
|
49
|
+
(root + 'Gemfile').exist?
|
50
|
+
end
|
51
|
+
@bundle_exec = setting
|
52
|
+
end
|
53
|
+
|
54
|
+
BundlerJob = Struct.new(:job) do
|
55
|
+
def env() job.env end
|
56
|
+
def to_exec() ['bundle', 'exec', *job.to_exec] end
|
57
|
+
def to_s() "bundle exec #{job.to_s}" end
|
58
|
+
end
|
59
|
+
|
60
|
+
def run
|
61
|
+
jobs = collect_jobs
|
62
|
+
|
63
|
+
unless jobs.empty?
|
64
|
+
for job in jobs
|
65
|
+
job = BundlerJob.new(job) if bundle_exec?
|
66
|
+
exec_job job
|
67
|
+
end
|
68
|
+
else
|
69
|
+
abort "nothing to run."
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def collect_jobs
|
74
|
+
[TestUnit, RSpec, Cucumber].inject([]) do |jobs, klass|
|
75
|
+
framework = klass.new self
|
76
|
+
paths = framework.resolve_paths @names
|
77
|
+
jobs.concat framework.pick_jobs(paths)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def exec_job job
|
82
|
+
with_env job.env do |env_keys|
|
83
|
+
display_job job, env_keys
|
84
|
+
system(*job.to_exec)
|
85
|
+
exit $?.exitstatus unless $?.success?
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def display_job job, env_keys
|
90
|
+
display_env env_keys
|
91
|
+
puts job
|
92
|
+
end
|
93
|
+
|
94
|
+
def display_env env_keys
|
95
|
+
env_keys.each do |name|
|
96
|
+
value = ENV[name].strip
|
97
|
+
next if value.empty?
|
98
|
+
value = %("#{value}") if value.index(' ')
|
99
|
+
print "#{name}=#{value} "
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def rbenv_clear
|
104
|
+
rbenv_root = `rbenv root 2>/dev/null`.chomp
|
105
|
+
unless rbenv_root.empty?
|
106
|
+
re = /^#{Regexp.escape rbenv_root}\/(versions|plugins|libexec)\b/
|
107
|
+
paths = ENV["PATH"].split(":")
|
108
|
+
paths.reject! {|p| p =~ re }
|
109
|
+
update_env 'PATH' => paths.join(":")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def with_env env
|
114
|
+
rbenv_clear
|
115
|
+
saved = update_env env
|
116
|
+
begin
|
117
|
+
yield saved.keys
|
118
|
+
ensure
|
119
|
+
restore_env saved
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def update_env env
|
124
|
+
env.inject({}) { |saved, (name, value)|
|
125
|
+
saved[name] = ENV[name]
|
126
|
+
ENV[name] = value.sub('%', saved[name].to_s)
|
127
|
+
saved
|
128
|
+
}
|
129
|
+
end
|
130
|
+
|
131
|
+
def restore_env env
|
132
|
+
env.each {|name, value| ENV[name] = value }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
require 'polyamory/rooted_pathname'
|
2
|
+
require 'polyamory/command'
|
3
|
+
|
4
|
+
module Polyamory
|
5
|
+
# Internal: Deals with finding Test::Unit or MiniTest files to test.
|
6
|
+
class TestUnit
|
7
|
+
attr_reader :context, :test_filters
|
8
|
+
|
9
|
+
def initialize context
|
10
|
+
@context = context
|
11
|
+
@test_filters = context.name_filters.dup
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_dir
|
15
|
+
@test_dir ||= context.root + 'test'
|
16
|
+
end
|
17
|
+
|
18
|
+
def file_pattern dir
|
19
|
+
"#{dir}/**/*_test.rb"
|
20
|
+
end
|
21
|
+
|
22
|
+
def file_pattern_alt dir
|
23
|
+
"#{dir}/**/test*.rb"
|
24
|
+
end
|
25
|
+
|
26
|
+
# Internal: Finds test files with one glob pattern or the other. The pattern
|
27
|
+
# that matches the most files wins.
|
28
|
+
def find_files dir = test_dir
|
29
|
+
paths = glob file_pattern(dir)
|
30
|
+
paths_alt = glob file_pattern_alt(dir)
|
31
|
+
paths.size > paths_alt.size ? paths : paths_alt
|
32
|
+
end
|
33
|
+
|
34
|
+
# Internal: Memoized find_files in primary dir
|
35
|
+
def all_matching_files
|
36
|
+
@all_matching_files ||= find_files
|
37
|
+
end
|
38
|
+
|
39
|
+
# Public: Resolve a set of files, directories, and patterns to a list of
|
40
|
+
# paths to test.
|
41
|
+
def resolve_paths names
|
42
|
+
if context.tag_filters.any?
|
43
|
+
# test/unit and minitest don't support tags
|
44
|
+
[]
|
45
|
+
elsif names.any?
|
46
|
+
paths = []
|
47
|
+
for name in names
|
48
|
+
paths.concat Array(resolve_name name)
|
49
|
+
end
|
50
|
+
paths
|
51
|
+
else
|
52
|
+
all_matching_files
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def handle? path
|
57
|
+
path.in_dir? test_dir
|
58
|
+
end
|
59
|
+
|
60
|
+
def resolve_name name
|
61
|
+
resolve_as_directory(name) or
|
62
|
+
resolve_as_filename(name) or
|
63
|
+
resolve_as_file_pattern(name) or
|
64
|
+
raise "nothing resolved from #{name}"
|
65
|
+
end
|
66
|
+
|
67
|
+
# "functional" => "test/functional/**"
|
68
|
+
# "test/functional" => "test/functional/**"
|
69
|
+
def resolve_as_directory name
|
70
|
+
dir = [test_dir + name, context.root + name].detect { |dir|
|
71
|
+
dir.directory? and handle? dir
|
72
|
+
}
|
73
|
+
find_files(dir) if dir
|
74
|
+
end
|
75
|
+
|
76
|
+
# "test/unit/test_user.rb:42"
|
77
|
+
def resolve_as_filename name
|
78
|
+
filename = name.sub(/:(\d+)$/, '')
|
79
|
+
line_number = $1
|
80
|
+
file = context.root + filename
|
81
|
+
|
82
|
+
if file.file? and handle? file
|
83
|
+
add_test_filter_for_line(file, line_number) if line_number
|
84
|
+
file
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# "word" => "test/**" that match "word"
|
89
|
+
def resolve_as_file_pattern name
|
90
|
+
pattern = /(?:\b|_)#{Regexp.escape name}(?:\b|_)/
|
91
|
+
all_matching_files.select {|p| p =~ pattern }
|
92
|
+
end
|
93
|
+
|
94
|
+
# Public: From a list of paths, yank the ones that this knows how to handle,
|
95
|
+
# and build test jobs from it.
|
96
|
+
def pick_jobs paths
|
97
|
+
to_test = []
|
98
|
+
paths.reject! do |path|
|
99
|
+
if handle? path
|
100
|
+
to_test << path
|
101
|
+
true
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
unless to_test.empty?
|
106
|
+
[test_command(to_test)]
|
107
|
+
else
|
108
|
+
[]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_command paths
|
113
|
+
Command.new %w'polyamory -t' do |test_job|
|
114
|
+
add_ruby_options test_job
|
115
|
+
test_job.concat paths.map {|p| p.relative }
|
116
|
+
|
117
|
+
tunit_opts = testunit_options
|
118
|
+
if tunit_opts.any?
|
119
|
+
test_job << '--'
|
120
|
+
test_job.concat tunit_opts
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def glob pattern
|
126
|
+
RootedPathname.glob pattern, context.root
|
127
|
+
end
|
128
|
+
|
129
|
+
def add_ruby_options cmd
|
130
|
+
opts = []
|
131
|
+
opts << '-w' if context.warnings?
|
132
|
+
opts << '-Ilib:test'
|
133
|
+
for path in context.load_paths
|
134
|
+
opts << "-I#{path}"
|
135
|
+
end
|
136
|
+
opts << '%'
|
137
|
+
cmd.env['RUBYOPT'] = opts.join(' ')
|
138
|
+
end
|
139
|
+
|
140
|
+
def testunit_options
|
141
|
+
opts = []
|
142
|
+
opts << '-n' << test_filter_regexp(test_filters) if test_filters.any?
|
143
|
+
opts << '-s' << context.test_seed if context.test_seed
|
144
|
+
opts << '-v' if context.verbose?
|
145
|
+
opts
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_filter_regexp filters
|
149
|
+
if filters.size == 1
|
150
|
+
'/%s/' % filters.first
|
151
|
+
else
|
152
|
+
'/(%s)/' % filters.join('|')
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def add_test_filter_for_line file, linenum
|
157
|
+
@test_filters << find_test_filter_for_line(file, linenum)
|
158
|
+
end
|
159
|
+
|
160
|
+
def find_test_filter_for_line file, linenum
|
161
|
+
focused_test_finder.call(file, linenum) or
|
162
|
+
raise "test method not found (#{file.relative}:#{linenum})"
|
163
|
+
end
|
164
|
+
|
165
|
+
def focused_test_finder() FocusedTestFinder end
|
166
|
+
|
167
|
+
FocusedTestFinder = Struct.new(:file, :line) do
|
168
|
+
def self.call *args
|
169
|
+
new(*args).scan
|
170
|
+
end
|
171
|
+
|
172
|
+
def readlines
|
173
|
+
file.readlines[0, line.to_i]
|
174
|
+
end
|
175
|
+
|
176
|
+
def scan
|
177
|
+
readlines.reverse.each do |line|
|
178
|
+
found = test_from_line line
|
179
|
+
return found if found
|
180
|
+
end
|
181
|
+
nil
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_from_line line
|
185
|
+
case line
|
186
|
+
when /^\s*def\s+(test_\w+)/
|
187
|
+
$1
|
188
|
+
when /^\s*(test|it|specify)[\s(]+(['"])((.*?)[^\\])\2/
|
189
|
+
if 'test' == $1 then $3.gsub(/\s+/, '_') # ActiveSupport::TestCase
|
190
|
+
else $3 # minitest/spec
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
data/test/test_runner.rb
ADDED
@@ -0,0 +1,250 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'minitest/pride'
|
3
|
+
require 'polyamory/runner'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
describe Polyamory::Runner do
|
7
|
+
|
8
|
+
let(:default_options) {
|
9
|
+
options = {
|
10
|
+
:warnings => false,
|
11
|
+
:verbose => false,
|
12
|
+
:backtrace => false,
|
13
|
+
:test_seed => nil,
|
14
|
+
:name_filters => [],
|
15
|
+
:tag_filters => [],
|
16
|
+
:load_paths => [],
|
17
|
+
}
|
18
|
+
}
|
19
|
+
subject { Polyamory::Runner.new(names, root, default_options.merge(options)) }
|
20
|
+
|
21
|
+
let(:options) { Hash.new }
|
22
|
+
let(:names) { [] }
|
23
|
+
let(:root) { File.join(ENV['TMPDIR'] || '/tmp', 'polyamory') }
|
24
|
+
|
25
|
+
before { FileUtils.rm_rf root }
|
26
|
+
|
27
|
+
it "finds no jobs when directory doesn't exist" do
|
28
|
+
subject.collect_jobs.must_be_empty
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "test/unit project" do
|
32
|
+
before {
|
33
|
+
%w[ app/models/user.rb
|
34
|
+
lib/sync.rb
|
35
|
+
test/unit/user_test.rb
|
36
|
+
test/unit/blog_test.rb
|
37
|
+
test/functional/lib_user_test.rb
|
38
|
+
].each do |path|
|
39
|
+
file = File.join(root, path)
|
40
|
+
FileUtils.mkdir_p File.dirname(file)
|
41
|
+
FileUtils.touch file
|
42
|
+
end
|
43
|
+
}
|
44
|
+
|
45
|
+
let(:job) { subject.collect_jobs.first }
|
46
|
+
let(:job_files) {
|
47
|
+
files = job.to_exec
|
48
|
+
end_at = files.index('--').to_i - 1
|
49
|
+
files[2..end_at]
|
50
|
+
}
|
51
|
+
|
52
|
+
it "finds one job" do
|
53
|
+
subject.collect_jobs.size.must_equal 1
|
54
|
+
end
|
55
|
+
|
56
|
+
it "tests all files" do
|
57
|
+
job_files.must_equal %w[
|
58
|
+
test/functional/lib_user_test.rb
|
59
|
+
test/unit/blog_test.rb
|
60
|
+
test/unit/user_test.rb
|
61
|
+
]
|
62
|
+
end
|
63
|
+
|
64
|
+
it "sets ruby options" do
|
65
|
+
job.env['RUBYOPT'].must_equal "-Ilib:test %"
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "with verbose" do
|
69
|
+
let(:options) { {:warnings => true} }
|
70
|
+
|
71
|
+
it "sets warning option" do
|
72
|
+
job.env['RUBYOPT'].must_equal "-w -Ilib:test %"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "with pattern" do
|
77
|
+
let(:names) { %w[user] }
|
78
|
+
|
79
|
+
it "finds files by pattern" do
|
80
|
+
job_files.must_equal %w[
|
81
|
+
test/functional/lib_user_test.rb
|
82
|
+
test/unit/user_test.rb
|
83
|
+
]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "with directory" do
|
88
|
+
let(:names) { %w[unit] }
|
89
|
+
|
90
|
+
it "finds files in dir" do
|
91
|
+
job_files.must_equal %w[
|
92
|
+
test/unit/blog_test.rb
|
93
|
+
test/unit/user_test.rb
|
94
|
+
]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "with non-matching name" do
|
99
|
+
let(:names) { %w[nonexist] }
|
100
|
+
|
101
|
+
it "finds no jobs" do
|
102
|
+
subject.collect_jobs.must_be_empty
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "test filters" do
|
107
|
+
describe "from option" do
|
108
|
+
let(:options) { {:name_filters => %w'filly'} }
|
109
|
+
|
110
|
+
it "generates test/unit argument" do
|
111
|
+
job.to_s.must_include "-- -n /filly/"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "from line numbers" do
|
116
|
+
before {
|
117
|
+
File.open(File.join(root, 'test/unit/blog_test.rb'), 'w') do |file|
|
118
|
+
file.write <<-RUBY
|
119
|
+
require 'moo'
|
120
|
+
def test_blog
|
121
|
+
# normal
|
122
|
+
test "has feed"
|
123
|
+
# ActiveSupport::TestCase
|
124
|
+
it "needs posts" do
|
125
|
+
# minitest/spec
|
126
|
+
end
|
127
|
+
specify('no comments') { ... }
|
128
|
+
RUBY
|
129
|
+
end
|
130
|
+
}
|
131
|
+
|
132
|
+
describe "normal syntax" do
|
133
|
+
let(:names) { %w[ test/unit/blog_test.rb:3 ] }
|
134
|
+
it("finds method") { job.to_s.must_include "-n /test_blog/" }
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "ActiveSupport syntax" do
|
138
|
+
let(:names) { %w[ test/unit/blog_test.rb:5 ] }
|
139
|
+
it("finds method") { job.to_s.must_include "-n /has_feed/" }
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "normal syntax" do
|
143
|
+
let(:names) { %w[ test/unit/blog_test.rb:7 ] }
|
144
|
+
it("finds method") { job.to_s.must_include "-n /needs posts/" }
|
145
|
+
end
|
146
|
+
|
147
|
+
describe "normal syntax" do
|
148
|
+
let(:names) { %w[ test/unit/blog_test.rb:9 ] }
|
149
|
+
it("finds method") { job.to_s.must_include "-n /no comments/" }
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "RSpec project" do
|
156
|
+
before {
|
157
|
+
%w[ app/models/user.rb
|
158
|
+
lib/sync.rb
|
159
|
+
spec/models/user_spec.rb
|
160
|
+
spec/models/blog_spec.rb
|
161
|
+
spec/integration/sync_spec.rb
|
162
|
+
].each do |path|
|
163
|
+
file = File.join(root, path)
|
164
|
+
FileUtils.mkdir_p File.dirname(file)
|
165
|
+
FileUtils.touch file
|
166
|
+
end
|
167
|
+
}
|
168
|
+
|
169
|
+
let(:job) { subject.collect_jobs.first }
|
170
|
+
let(:job_files) {
|
171
|
+
files = job.to_exec
|
172
|
+
files[1..-1]
|
173
|
+
}
|
174
|
+
|
175
|
+
it "finds one job" do
|
176
|
+
subject.collect_jobs.size.must_equal 1
|
177
|
+
end
|
178
|
+
|
179
|
+
it "tests all files" do
|
180
|
+
job_files.must_equal %w[spec]
|
181
|
+
end
|
182
|
+
|
183
|
+
describe "name args" do
|
184
|
+
let(:names) { %w[user blog] }
|
185
|
+
|
186
|
+
it "tests some files" do
|
187
|
+
job_files.must_equal %w[
|
188
|
+
spec/models/user_spec.rb
|
189
|
+
spec/models/blog_spec.rb
|
190
|
+
]
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
it "sets no ruby options" do
|
195
|
+
job.env['RUBYOPT'].must_equal "%"
|
196
|
+
end
|
197
|
+
|
198
|
+
describe "example filters" do
|
199
|
+
let(:options) { {:name_filters => %w'willy filly'} }
|
200
|
+
|
201
|
+
it "generates arguments" do
|
202
|
+
job.to_s.must_include " -e willy -e filly "
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
describe "tag filters" do
|
207
|
+
let(:options) { {:tag_filters => %w'willy filly'} }
|
208
|
+
|
209
|
+
it "generates arguments" do
|
210
|
+
job.to_s.must_include " -t willy -t filly "
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
describe "mixed project" do
|
216
|
+
before {
|
217
|
+
%w[ app/models/user.rb
|
218
|
+
lib/sync.rb
|
219
|
+
test/unit/test_user.rb
|
220
|
+
spec/models/blog_spec.rb
|
221
|
+
features/sync.feature
|
222
|
+
].each do |path|
|
223
|
+
file = File.join(root, path)
|
224
|
+
FileUtils.mkdir_p File.dirname(file)
|
225
|
+
FileUtils.touch file
|
226
|
+
end
|
227
|
+
}
|
228
|
+
|
229
|
+
let(:jobs) { subject.collect_jobs }
|
230
|
+
|
231
|
+
it "finds three jobs" do
|
232
|
+
jobs.map {|j| j.to_s }.must_equal [
|
233
|
+
'polyamory -t test/unit/test_user.rb',
|
234
|
+
'rspec spec',
|
235
|
+
'cucumber features',
|
236
|
+
]
|
237
|
+
end
|
238
|
+
|
239
|
+
describe "with tags" do
|
240
|
+
let(:options) { {:tag_filters => %w[willy ~nilly]} }
|
241
|
+
it "is filtered by tags" do
|
242
|
+
jobs.map {|j| j.to_s }.must_equal [
|
243
|
+
'rspec -t willy -t ~nilly spec',
|
244
|
+
'cucumber -t @willy -t ~@nilly features',
|
245
|
+
]
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: polyamory
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.5
|
5
4
|
prerelease:
|
5
|
+
version: 0.6.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Mislav Marohnić
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-03-08 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
|
-
description: A
|
14
|
+
description: A cli runner for all tests regardless of framework
|
15
15
|
email: mislav.marohnic@gmail.com
|
16
16
|
executables:
|
17
17
|
- polyamory
|
@@ -19,9 +19,16 @@ extensions: []
|
|
19
19
|
extra_rdoc_files: []
|
20
20
|
files:
|
21
21
|
- bin/polyamory
|
22
|
+
- lib/polyamory/command.rb
|
23
|
+
- lib/polyamory/cucumber.rb
|
24
|
+
- lib/polyamory/rooted_pathname.rb
|
25
|
+
- lib/polyamory/rspec.rb
|
26
|
+
- lib/polyamory/runner.rb
|
27
|
+
- lib/polyamory/test_unit.rb
|
22
28
|
- lib/polyamory.rb
|
29
|
+
- test/test_runner.rb
|
23
30
|
- README.md
|
24
|
-
homepage: https://github.com/mislav/polyamory
|
31
|
+
homepage: https://github.com/mislav/polyamory#readme
|
25
32
|
licenses: []
|
26
33
|
post_install_message:
|
27
34
|
rdoc_options: []
|
@@ -41,8 +48,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
41
48
|
version: '0'
|
42
49
|
requirements: []
|
43
50
|
rubyforge_project:
|
44
|
-
rubygems_version: 1.8.
|
51
|
+
rubygems_version: 1.8.23
|
45
52
|
signing_key:
|
46
53
|
specification_version: 3
|
47
|
-
summary:
|
54
|
+
summary: The promiscuous test runner
|
48
55
|
test_files: []
|