test-runner 0.9.2
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.
- checksums.yaml +7 -0
- data/bin/test-runner +8 -0
- data/lib/test_runner.rb +38 -0
- data/lib/test_runner/assert.rb +15 -0
- data/lib/test_runner/assert/assertion.rb +116 -0
- data/lib/test_runner/assert/check.rb +53 -0
- data/lib/test_runner/assert/checks.rb +76 -0
- data/lib/test_runner/assert/checks/registry.rb +31 -0
- data/lib/test_runner/assert/errors.rb +46 -0
- data/lib/test_runner/assert/syntax.rb +94 -0
- data/lib/test_runner/backtrace_filter.rb +25 -0
- data/lib/test_runner/cli.rb +196 -0
- data/lib/test_runner/colored_logger.rb +62 -0
- data/lib/test_runner/config.rb +77 -0
- data/lib/test_runner/runner.rb +146 -0
- data/lib/test_runner/script.rb +2 -0
- data/lib/test_runner/util.rb +66 -0
- metadata +74 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 1a7128fa65cf1e03aeb992c6e20a26ae50a3d1bc
|
|
4
|
+
data.tar.gz: cd3992292f6c60cb9916877f7512095c22a536fc
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e2971ffd2f61a4315928da41273c2c55f9c9a113ac90f27b2133425dbbf7e0897203886b25a0f447c98e9f06270c1f93e77617a05e319be73c5038ae7c353842
|
|
7
|
+
data.tar.gz: 226c1c6b629d0ccd23d43ed502b97dd232d9c0043dea6dded2f0469976b7a5b591e4d48e4cbd97fd57357937df13cf2c53ab60a65f9ff6d88ba83230419c0ca2
|
data/bin/test-runner
ADDED
data/lib/test_runner.rb
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require "logger"
|
|
2
|
+
require "logger/logging"
|
|
3
|
+
|
|
4
|
+
module TestRunner
|
|
5
|
+
autoload :Assert, "test_runner/assert"
|
|
6
|
+
autoload :BacktraceFilter, "test_runner/backtrace_filter"
|
|
7
|
+
autoload :ColoredLogger, "test_runner/colored_logger"
|
|
8
|
+
autoload :Config, "test_runner/config"
|
|
9
|
+
autoload :CLI, "test_runner/cli"
|
|
10
|
+
autoload :Runner, "test_runner/runner"
|
|
11
|
+
autoload :Util, "test_runner/util"
|
|
12
|
+
|
|
13
|
+
# TestRunner can be included in scripts as a mixin, e.g.
|
|
14
|
+
#
|
|
15
|
+
# require "test_runner"
|
|
16
|
+
# include TestRunner
|
|
17
|
+
#
|
|
18
|
+
# logger.info "hi"
|
|
19
|
+
# assert true
|
|
20
|
+
#
|
|
21
|
+
# You can also require "test_runner/script" to automatically include TestRunner
|
|
22
|
+
def logger
|
|
23
|
+
Config.logger
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Describe, context, it, and specify can help break up a larger test script
|
|
27
|
+
# into chunks. Blocks are run # immediately; this is cosmetic enhancement
|
|
28
|
+
# that also enables some degree of compatibility with other frameworks.
|
|
29
|
+
%i(context describe it specify).each do |method_name|
|
|
30
|
+
define_method method_name do |msg, &blk|
|
|
31
|
+
blk.() if blk
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.included target
|
|
36
|
+
Assert::Syntax.infect target
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require_relative "assert/assertion"
|
|
2
|
+
require_relative "assert/check"
|
|
3
|
+
require_relative "assert/checks"
|
|
4
|
+
require_relative "assert/errors"
|
|
5
|
+
require_relative "assert/syntax"
|
|
6
|
+
|
|
7
|
+
module TestRunner
|
|
8
|
+
module Assert
|
|
9
|
+
def self.inspect object, truncate = 100
|
|
10
|
+
raw = object.inspect
|
|
11
|
+
return raw if raw.size <= truncate
|
|
12
|
+
"#{raw[0..truncate - 2]} …\""
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
module TestRunner
|
|
2
|
+
module Assert
|
|
3
|
+
class Assertion
|
|
4
|
+
attr_reader :fails
|
|
5
|
+
attr_reader :passes
|
|
6
|
+
attr_accessor :source
|
|
7
|
+
|
|
8
|
+
def initialize subject_proc, checks
|
|
9
|
+
@subject_proc = subject_proc
|
|
10
|
+
@checks = checks
|
|
11
|
+
@passes = []
|
|
12
|
+
@fails = []
|
|
13
|
+
@trace = caller_locations
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
perform_checks
|
|
18
|
+
freeze
|
|
19
|
+
raise to_error if failed?
|
|
20
|
+
subject
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def build_checks
|
|
24
|
+
@checks.map do |check_name, argument|
|
|
25
|
+
TestRunner::Config.internal_logger.debug "Resolving check #{check_name.inspect}, arg=#{argument.inspect}"
|
|
26
|
+
resolve_check check_name, argument
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def failed?
|
|
31
|
+
fails.any?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def file
|
|
35
|
+
@trace[0].path
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def freeze
|
|
39
|
+
passes.freeze
|
|
40
|
+
fails.freeze
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def line
|
|
44
|
+
@trace[0].lineno
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def passed?
|
|
48
|
+
not failed?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def perform_checks
|
|
52
|
+
build_checks.each do |check|
|
|
53
|
+
check.evaluate
|
|
54
|
+
ary = if check.passed? then passes else fails end
|
|
55
|
+
ary.push check
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def resolve_check check_name, argument
|
|
60
|
+
check = Checks.resolve check_name do
|
|
61
|
+
check_name = :include if check_name == :includes
|
|
62
|
+
argument = [check_name, *argument]
|
|
63
|
+
Checks[:predicate]
|
|
64
|
+
end
|
|
65
|
+
check.new subject_thunk, argument
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def subject
|
|
69
|
+
subject_thunk.call
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def subject_thunk
|
|
73
|
+
@subject_thunk ||= SubjectThunk.new @subject_proc
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def to_error
|
|
77
|
+
AssertionFailed.new self
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def trace
|
|
81
|
+
BacktraceFilter.(@trace)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
class Refutation < Assertion
|
|
85
|
+
def build_checks
|
|
86
|
+
super.each &:negate
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
class SubjectThunk
|
|
91
|
+
def initialize block
|
|
92
|
+
@block = block
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def call
|
|
96
|
+
return @subject if subject_resolved?
|
|
97
|
+
@subject = @block.call
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def expect_error
|
|
101
|
+
raise "called after initially fetched" if subject_resolved?
|
|
102
|
+
@block.call
|
|
103
|
+
nothing_raised = true
|
|
104
|
+
rescue => error
|
|
105
|
+
@subject = error
|
|
106
|
+
ensure
|
|
107
|
+
raise NothingRaised.new if nothing_raised
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def subject_resolved?
|
|
111
|
+
instance_variable_defined? :@subject
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module TestRunner
|
|
2
|
+
module Assert
|
|
3
|
+
class Check
|
|
4
|
+
singleton_class.send :attr_reader, :block, :name
|
|
5
|
+
|
|
6
|
+
attr :argument
|
|
7
|
+
|
|
8
|
+
def initialize subject_thunk, argument
|
|
9
|
+
@argument = argument
|
|
10
|
+
@subject_thunk = subject_thunk
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def evaluate
|
|
14
|
+
self.class.block.call self, @argument
|
|
15
|
+
freeze
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def expect_error
|
|
19
|
+
@subject_thunk.expect_error
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def fail message
|
|
23
|
+
passed = negated? ^ yield
|
|
24
|
+
@fail_message = message unless passed
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def fail_message
|
|
28
|
+
"expected #{Assert.inspect subject} to#{" not" if negated?} #{@fail_message}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def failed?
|
|
32
|
+
@fail_message ? true : false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def negated?
|
|
36
|
+
@negated ? true : false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def negate
|
|
40
|
+
@negated = !@negated
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def passed?
|
|
44
|
+
not failed?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def subject
|
|
48
|
+
@expected_error or @subject_thunk.call
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module TestRunner
|
|
2
|
+
module Assert
|
|
3
|
+
module Checks
|
|
4
|
+
require_relative "checks/registry"
|
|
5
|
+
extend Registry
|
|
6
|
+
|
|
7
|
+
register :equals do |check, object|
|
|
8
|
+
obj_inspect = Assert.inspect object
|
|
9
|
+
|
|
10
|
+
if Assert.inspect(check.subject) == obj_inspect and not check.negated?
|
|
11
|
+
fail_message = "equal other object (no difference in #inspect output)"
|
|
12
|
+
else
|
|
13
|
+
fail_message = "equal #{obj_inspect}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
check.fail fail_message do check.subject == object end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
register :included_in do |check, list|
|
|
20
|
+
check.fail "be included in #{Assert.inspect list}" do
|
|
21
|
+
list.include? check.subject
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
register :kind_of do |check, type|
|
|
26
|
+
check.fail "be a kind of #{type}" do
|
|
27
|
+
check.subject.kind_of? type
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
register :matches do |check, matcher|
|
|
32
|
+
check.fail "match #{Assert.inspect matcher}" do
|
|
33
|
+
check.subject.match matcher
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
register :predicate do |check, argument|
|
|
38
|
+
name, *args = argument
|
|
39
|
+
method_name = "#{name}?"
|
|
40
|
+
method = check.subject.method method_name
|
|
41
|
+
|
|
42
|
+
if method.arity == 0
|
|
43
|
+
expect_truth = args.all?
|
|
44
|
+
message = "be #{name}"
|
|
45
|
+
result = check.subject.public_send method_name
|
|
46
|
+
result = result ^ !expect_truth
|
|
47
|
+
else
|
|
48
|
+
result = check.subject.public_send method_name, *args
|
|
49
|
+
args = args.map &Assert.method(:inspect)
|
|
50
|
+
message = "#{name} #{args * ', '}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
check.fail message do result end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
register :raises do |check, error_type|
|
|
57
|
+
check.expect_error
|
|
58
|
+
check.fail "be a #{Assert.inspect error_type}" do
|
|
59
|
+
check.subject.is_a? error_type
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
register :responds_to do |check, method_name|
|
|
64
|
+
check.fail "respond to ##{method_name}" do
|
|
65
|
+
check.subject.respond_to? method_name
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
register :truthy do |check, arg|
|
|
70
|
+
check.fail "be #{arg ? "truthy" : "falsey"}" do
|
|
71
|
+
!arg ^ check.subject
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module TestRunner
|
|
2
|
+
module Assert
|
|
3
|
+
module Checks
|
|
4
|
+
module Registry
|
|
5
|
+
def self.extended base
|
|
6
|
+
base.instance_variable_set :@registry, {}
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.define check_name, block
|
|
10
|
+
klass = Class.new Check
|
|
11
|
+
klass.instance_variable_set :@block, block
|
|
12
|
+
constant_name = Util.to_camel_case check_name
|
|
13
|
+
const_set constant_name, klass
|
|
14
|
+
klass
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr :registry
|
|
18
|
+
|
|
19
|
+
def register check_name, &block
|
|
20
|
+
registry[check_name] = Registry.define check_name, block
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def resolve check_name, &block
|
|
24
|
+
block ||= -> * do raise MissingCheck.new check_name end
|
|
25
|
+
registry.fetch check_name, &block
|
|
26
|
+
end
|
|
27
|
+
alias_method :[], :resolve
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module TestRunner
|
|
2
|
+
module Assert
|
|
3
|
+
Error = Class.new StandardError
|
|
4
|
+
|
|
5
|
+
class AssertionFailed < Error
|
|
6
|
+
attr :assertion
|
|
7
|
+
|
|
8
|
+
def initialize assertion
|
|
9
|
+
@assertion = assertion
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def backtrace
|
|
13
|
+
assertion.trace.map &:to_s
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_s
|
|
17
|
+
failures = assertion.fails.map do |failed_check|
|
|
18
|
+
failed_check.fail_message
|
|
19
|
+
end
|
|
20
|
+
if failures.size > 1
|
|
21
|
+
"Assertion failure:\n\n * #{failures * "\n * "}\n"
|
|
22
|
+
else
|
|
23
|
+
"Assertion failure: #{failures * ", "}"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class MissingCheck < Error
|
|
29
|
+
attr :check_name
|
|
30
|
+
|
|
31
|
+
def initialize check_name
|
|
32
|
+
@check_name = check_name
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_s
|
|
36
|
+
"could not resolve check #{check_name.inspect}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class NothingRaised < Error
|
|
41
|
+
def to_s
|
|
42
|
+
"expected subject block to raise an error"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
module TestRunner
|
|
2
|
+
module Assert
|
|
3
|
+
class Syntax < Module
|
|
4
|
+
MISSING = :__test_runner_assert_arg_missing__
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
def infect target
|
|
8
|
+
instance = new
|
|
9
|
+
if target.is_a? Class
|
|
10
|
+
target.send :include, instance
|
|
11
|
+
else
|
|
12
|
+
target.extend instance
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Public: assert is designed to be called a number of different ways,
|
|
17
|
+
# from 0 to 2 positional arguments, and maybe a block.
|
|
18
|
+
#
|
|
19
|
+
# subject_or_checks - the first optional argument passed in
|
|
20
|
+
# checks - the second optional argument passed in
|
|
21
|
+
# block - the &block parameter
|
|
22
|
+
#
|
|
23
|
+
# Valid examples:
|
|
24
|
+
#
|
|
25
|
+
# assert :raises => Error do … end
|
|
26
|
+
# assert "foo"
|
|
27
|
+
# assert 3, :included_in => [6, 3]
|
|
28
|
+
# assert [6, 3], :includes => 3
|
|
29
|
+
# assert :equal => 4 do 2 + 2 end
|
|
30
|
+
#
|
|
31
|
+
# Invalid examples:
|
|
32
|
+
#
|
|
33
|
+
# # Can't determine if the block or 2 is the subject
|
|
34
|
+
# assert 2, :equals => 4 do ̒… end
|
|
35
|
+
# # There's no subject at all
|
|
36
|
+
# assert :incuded_in => 4
|
|
37
|
+
#
|
|
38
|
+
# Returns two arguments, the subject, and a checks hash. If the checks
|
|
39
|
+
# would be empty, returns { :truthy => true }. The subject will always be
|
|
40
|
+
# a Proc that gets lazy evaluated when the assertion is checked.
|
|
41
|
+
def decode_assert_arguments subject_or_checks, checks, block
|
|
42
|
+
if checks == MISSING
|
|
43
|
+
if subject_or_checks == MISSING
|
|
44
|
+
missing_subject! unless block
|
|
45
|
+
subject_thunk = block
|
|
46
|
+
checks = { :truthy => true }
|
|
47
|
+
elsif block
|
|
48
|
+
ambiguous_subject! unless subject_or_checks.is_a? Hash
|
|
49
|
+
subject_thunk = block
|
|
50
|
+
checks = subject_or_checks
|
|
51
|
+
else
|
|
52
|
+
subject_thunk = -> do subject_or_checks end
|
|
53
|
+
checks = { :truthy => true }
|
|
54
|
+
end
|
|
55
|
+
else
|
|
56
|
+
ambiguous_subject! if block
|
|
57
|
+
subject_thunk = -> do subject_or_checks end
|
|
58
|
+
end
|
|
59
|
+
[subject_thunk, checks]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def ambiguous_subject!
|
|
63
|
+
raise ArgumentError, "cannot supply a block subject *and* a positional subject"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def missing_subject!
|
|
67
|
+
raise ArgumentError, "must supply either a positional subject *or* a block subject (but not both)"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def extended base
|
|
72
|
+
graft base.singleton_class
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def included base
|
|
76
|
+
graft base
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def graft base
|
|
80
|
+
method_body = -> method_name, assertion_class, syntax do
|
|
81
|
+
define_method method_name do |arg1 = MISSING, arg2 = MISSING, &block|
|
|
82
|
+
subject_thunk, checks = syntax.decode_assert_arguments arg1, arg2, block
|
|
83
|
+
assertion = assertion_class.new subject_thunk, checks
|
|
84
|
+
assertion.source = self
|
|
85
|
+
assertion.()
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
base.class_exec :assert, Assertion, self.class, &method_body
|
|
90
|
+
base.class_exec :refute, Assertion::Refutation, self.class, &method_body
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module TestRunner
|
|
2
|
+
module BacktraceFilter
|
|
3
|
+
extend self
|
|
4
|
+
|
|
5
|
+
def call trace
|
|
6
|
+
return trace unless Config.trim_backtraces
|
|
7
|
+
|
|
8
|
+
entry_point = trace.last
|
|
9
|
+
test_runner_root = File.expand_path "../..", __FILE__
|
|
10
|
+
|
|
11
|
+
first_pass = trace.drop_while do |location|
|
|
12
|
+
full_path = File.expand_path location.to_s
|
|
13
|
+
full_path.start_with? test_runner_root
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
second_pass = first_pass.take_while do |location|
|
|
17
|
+
full_path = File.expand_path location.to_s
|
|
18
|
+
not full_path.start_with? test_runner_root
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
second_pass << entry_point unless second_pass.last == entry_point
|
|
22
|
+
second_pass
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
require "optparse"
|
|
2
|
+
|
|
3
|
+
module TestRunner
|
|
4
|
+
class CLI
|
|
5
|
+
def self.run argv
|
|
6
|
+
instance = new argv, $stdout
|
|
7
|
+
instance.()
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
attr_reader :options
|
|
11
|
+
|
|
12
|
+
def initialize argv, stdout
|
|
13
|
+
@argv = argv
|
|
14
|
+
@stdout = stdout
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
@options = ArgvParser.(@argv)
|
|
19
|
+
setup_config
|
|
20
|
+
Runner.(filter_files) or exit 1
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def setup_config
|
|
24
|
+
Config.child_count = options.child_count
|
|
25
|
+
Config.fail_fast = options.fail_fast?
|
|
26
|
+
Config.logger = build_logger
|
|
27
|
+
Config.reverse_backtraces = options.reverse_backtraces?
|
|
28
|
+
Config.trim_backtraces = options.trim_backtraces?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def build_logger
|
|
32
|
+
logger = Logger.new @stdout
|
|
33
|
+
logger.level = options.log_level
|
|
34
|
+
logger.progname = "test_runner"
|
|
35
|
+
logger
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def filter_files
|
|
39
|
+
resolve_files.reject do |path|
|
|
40
|
+
path.end_with? "init.rb"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def resolve_files
|
|
45
|
+
options.paths.flat_map do |path|
|
|
46
|
+
if path.end_with? ".rb"
|
|
47
|
+
[path]
|
|
48
|
+
else
|
|
49
|
+
Dir[File.join path, "**/*.rb"]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class Options
|
|
55
|
+
def self.build
|
|
56
|
+
if ENV["TEST_RUNNER_VERBOSE"]
|
|
57
|
+
log_level = Logger::DEBUG
|
|
58
|
+
else
|
|
59
|
+
log_level = Logger::INFO
|
|
60
|
+
end
|
|
61
|
+
new log_level
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
attr_reader :child_count
|
|
65
|
+
attr_reader :log_level
|
|
66
|
+
attr_reader :paths
|
|
67
|
+
|
|
68
|
+
def initialize log_level
|
|
69
|
+
@paths = []
|
|
70
|
+
@log_level = log_level
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def add_path path
|
|
74
|
+
paths << path
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def child_count= number
|
|
78
|
+
@child_count = number.to_i
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def fail_fast
|
|
82
|
+
@fail_fast = !@fail_fast
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def fail_fast?
|
|
86
|
+
@fail_fast
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def full_backtrace
|
|
90
|
+
@full_backtrace = !@full_backtrace
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def trim_backtraces?
|
|
94
|
+
return nil if @full_backtrace.nil?
|
|
95
|
+
not @full_backtrace
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def quiet
|
|
99
|
+
@log_level += 1
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def reverse_backtraces
|
|
103
|
+
@reverse_backtraces = !@reverse_backtraces
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def reverse_backtraces?
|
|
107
|
+
@reverse_backtraces
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def verbose
|
|
111
|
+
@log_level -= 1
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
class ArgvParser
|
|
116
|
+
def self.call argv
|
|
117
|
+
options = Options.build
|
|
118
|
+
parser = ArgvParser.new argv, options
|
|
119
|
+
parser.()
|
|
120
|
+
options
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def initialize argv, options
|
|
124
|
+
@argv = argv
|
|
125
|
+
@options = options
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def call
|
|
129
|
+
parse_options
|
|
130
|
+
add_paths
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def add_paths
|
|
134
|
+
@argv << "tests" if @argv.empty?
|
|
135
|
+
@argv.each do |path|
|
|
136
|
+
@options.add_path path
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def parse_options
|
|
141
|
+
OptionParser.new do |opts|
|
|
142
|
+
opts.banner = <<-BANNER
|
|
143
|
+
Usage: #{program_name} [options] [PATH1] [PATH2] … [PATHn]
|
|
144
|
+
|
|
145
|
+
If no PATH(s) are specified, ./tests is assumed
|
|
146
|
+
|
|
147
|
+
BANNER
|
|
148
|
+
|
|
149
|
+
opts.on "-b", "--full-backtrace", "Do not filter assertion stack traces" do
|
|
150
|
+
@options.full_backtrace
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
opts.on "-f", "--fail-fast", "When any test script fails, exit immediately" do
|
|
154
|
+
@options.fail_fast
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
opts.on "-h", "--help", "Print this message and exit successfully" do
|
|
158
|
+
puts opts
|
|
159
|
+
exit 0
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
opts.on "-m", "--monkeypatch", "Turn on monkeypatching (equivalent to `require \"test_runner/script\"'" do
|
|
163
|
+
require "test_runner/script"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
opts.on "-n", "--num=COUNT", "Max number of sub processes to run concurrently" do |num|
|
|
167
|
+
@options.child_count = num
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
opts.on "-q", "--quiet", "Reduces log verbosity" do
|
|
171
|
+
@options.quiet
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
opts.on "-r", "--reverse-traces", "Reverse the order of stack traces" do
|
|
175
|
+
@options.reverse_backtraces
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
opts.on "-V", "--version", "Print version and exit successfully" do
|
|
179
|
+
spec = Gem.loaded_specs["test_runner"]
|
|
180
|
+
version = if spec then spec.version else "(local)" end
|
|
181
|
+
puts "Version: #{version}"
|
|
182
|
+
exit 0
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
opts.on "-v", "--verbose", "Increases log verbosity" do
|
|
186
|
+
@options.verbose
|
|
187
|
+
end
|
|
188
|
+
end.parse! @argv
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def program_name
|
|
192
|
+
File.basename $PROGRAM_NAME
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module TestRunner
|
|
2
|
+
class ColoredLogger
|
|
3
|
+
ANSI_COLORS = %i(black red green yellow blue magenta cyan white)
|
|
4
|
+
|
|
5
|
+
DEFAULT_PALETTE = {
|
|
6
|
+
:unknown => -> msg { bg(:blue, :bright, fg(:white, :bright, msg)) },
|
|
7
|
+
:fatal => -> msg { bg(:red, :bright, fg(:white, :bright, msg)) },
|
|
8
|
+
:error => -> msg { fg(:red, :bright, msg) },
|
|
9
|
+
:warn => -> msg { fg(:yellow, :bright, msg) },
|
|
10
|
+
:info => -> msg { fg(:default, :normal, msg) },
|
|
11
|
+
:debug => -> msg { fg(:cyan, :bright, msg) },
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
attr_reader :palette
|
|
15
|
+
|
|
16
|
+
def initialize io, palette = DEFAULT_PALETTE
|
|
17
|
+
@logger = Logger.new io
|
|
18
|
+
@palette = palette
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
%i(level= progname=).each do |method_name|
|
|
22
|
+
define_method method_name do |*args, &block|
|
|
23
|
+
@logger.public_send method_name, *args, &block
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
%i(unknown fatal error warn info debug).each do |log_level|
|
|
28
|
+
define_method log_level do |msg_or_progname = nil, &orig_block|
|
|
29
|
+
colored_msg = format log_level, msg_or_progname
|
|
30
|
+
|
|
31
|
+
if orig_block
|
|
32
|
+
progname = colored_msg
|
|
33
|
+
block = -> do format log_level, orig_block.() end
|
|
34
|
+
else
|
|
35
|
+
block = -> do colored_msg end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
@logger.public_send log_level, progname, &block
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def format log_level, msg
|
|
43
|
+
formatter = palette.fetch log_level
|
|
44
|
+
instance_exec msg, &formatter
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def fg *args
|
|
48
|
+
col :fg, *args
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def bg *args
|
|
52
|
+
col :bg, *args
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def col fgbg, color_code, intensity_code, str
|
|
56
|
+
color_num = ANSI_COLORS.index color_code
|
|
57
|
+
intensity_num = { :normal => 0, :bright => 1 }.fetch intensity_code
|
|
58
|
+
fgbg_num = { :fg => 3, :bg => 4 }.fetch fgbg
|
|
59
|
+
"\e[#{fgbg_num}#{color_num};#{intensity_num}m#{str}\e[0m"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
module TestRunner
|
|
2
|
+
module Config
|
|
3
|
+
extend self
|
|
4
|
+
|
|
5
|
+
extend Logger::Logging
|
|
6
|
+
|
|
7
|
+
attr_writer :child_count
|
|
8
|
+
attr_writer :fail_fast
|
|
9
|
+
attr_writer :internal_logger
|
|
10
|
+
attr_writer :reverse_backtraces
|
|
11
|
+
attr_writer :trim_backtraces
|
|
12
|
+
|
|
13
|
+
def child_count
|
|
14
|
+
Option::Number.evaluate @child_count do
|
|
15
|
+
ENV.fetch "TEST_RUNNER_CHILD_COUNT", 1
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def fail_fast
|
|
20
|
+
Option::Boolean.evaluate @fail_fast do
|
|
21
|
+
ENV["TEST_RUNNER_FAIL_FAST"]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def internal_logger
|
|
26
|
+
if ENV["TEST_RUNNER_INTERNAL_LOGGING"]
|
|
27
|
+
logger
|
|
28
|
+
else
|
|
29
|
+
Logger::Logging::NullLogger
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def reverse_backtraces
|
|
34
|
+
Option::Boolean.evaluate @reverse_backtraces do
|
|
35
|
+
ENV["TEST_RUNNER_REVERSE_BACKTRACES"]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def trim_backtraces
|
|
40
|
+
Option::Boolean.evaluate @trim_backtraces do
|
|
41
|
+
ENV.fetch "TEST_RUNNER_TRIM_BACKTRACES", true
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class Option
|
|
46
|
+
def self.evaluate value, &block
|
|
47
|
+
instance = new value
|
|
48
|
+
instance.fetch &block
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def initialize value
|
|
52
|
+
@value = value
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def fetch
|
|
56
|
+
@value = yield unless set?
|
|
57
|
+
value
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def set?
|
|
61
|
+
not @value.nil?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class Number < Option
|
|
65
|
+
def value
|
|
66
|
+
@value.to_i
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class Boolean < Option
|
|
71
|
+
def value
|
|
72
|
+
not [nil, false, "", "0", "n", "no", "false"].include? @value
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
module TestRunner
|
|
2
|
+
class Runner
|
|
3
|
+
def self.call paths
|
|
4
|
+
instance = new paths
|
|
5
|
+
instance.call
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
attr_reader :files
|
|
9
|
+
attr_reader :pids
|
|
10
|
+
|
|
11
|
+
def initialize files
|
|
12
|
+
@files = files
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call
|
|
16
|
+
Config.internal_logger.debug "test_runner found #{files.size} files: #{files * ", "}"
|
|
17
|
+
return if files.empty?
|
|
18
|
+
result = run_all
|
|
19
|
+
|
|
20
|
+
log_msg = "finished executing files; success=#{result.inspect}"
|
|
21
|
+
if result
|
|
22
|
+
Config.logger.info log_msg
|
|
23
|
+
else
|
|
24
|
+
Config.logger.warn log_msg
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
result
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def run_all
|
|
31
|
+
set = ProcessSet.new
|
|
32
|
+
Signal.trap "INT" do set.shutdown end
|
|
33
|
+
set << files.shift until files.empty?
|
|
34
|
+
set.finish
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class ProcessSet
|
|
38
|
+
def initialize
|
|
39
|
+
@set = []
|
|
40
|
+
@passed = true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def << file
|
|
44
|
+
wait Config.child_count - 1
|
|
45
|
+
@set.<< spawn_child file
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def finish
|
|
49
|
+
wait 0
|
|
50
|
+
@passed
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def wait max_count
|
|
54
|
+
tick while @set.size > max_count
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def tick
|
|
58
|
+
loop do
|
|
59
|
+
reads, _, _ = IO.select @set.map(&:fd), [], [], 1
|
|
60
|
+
return reap reads if reads
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def spawn_child file
|
|
65
|
+
process = Process.new file
|
|
66
|
+
process.start
|
|
67
|
+
process
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def reap reads
|
|
71
|
+
@set.delete_if do |process|
|
|
72
|
+
next unless reads.include? process.fd
|
|
73
|
+
Config.internal_logger.debug "Reaping #{process.file}:#{process.pid}"
|
|
74
|
+
process.finish or @passed = false
|
|
75
|
+
true
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
shutdown if failed? and Config.fail_fast
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def shutdown
|
|
82
|
+
@set.each do |process|
|
|
83
|
+
::Process.kill "TERM", process.pid
|
|
84
|
+
end
|
|
85
|
+
::Process.waitall
|
|
86
|
+
exit 1
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def failed?
|
|
90
|
+
not success?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def success?
|
|
94
|
+
@passed
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
class Process
|
|
98
|
+
attr_reader :fd
|
|
99
|
+
attr_reader :file
|
|
100
|
+
attr_reader :pid
|
|
101
|
+
|
|
102
|
+
def initialize file
|
|
103
|
+
@file = file
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def start
|
|
107
|
+
@fd, wr = IO.pipe
|
|
108
|
+
|
|
109
|
+
@pid = fork do
|
|
110
|
+
@fd.close
|
|
111
|
+
begin
|
|
112
|
+
load file
|
|
113
|
+
rescue => error
|
|
114
|
+
print_stacktrace error
|
|
115
|
+
exit 1
|
|
116
|
+
ensure
|
|
117
|
+
wr.write "\x00"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
freeze
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def print_stacktrace error
|
|
125
|
+
locations = error.backtrace
|
|
126
|
+
final_location = locations.shift
|
|
127
|
+
locations = TestRunner::BacktraceFilter.(locations)
|
|
128
|
+
|
|
129
|
+
lines = locations.map do |loc| "\tfrom #{loc}" end
|
|
130
|
+
lines.unshift "#{final_location}: #{error.message} (#{error.class.name})"
|
|
131
|
+
|
|
132
|
+
lines.reverse! if Config.reverse_backtraces
|
|
133
|
+
Config.logger.error "Exception:\n#{lines * "\n"}"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def finish
|
|
137
|
+
fd.read 1
|
|
138
|
+
_, status = ::Process.wait2 pid
|
|
139
|
+
status = status.exitstatus
|
|
140
|
+
Config.internal_logger.debug "finished script #{@file}; status=#{status.inspect}"
|
|
141
|
+
status == 0
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module TestRunner
|
|
2
|
+
module Util
|
|
3
|
+
extend self
|
|
4
|
+
|
|
5
|
+
def extract_key_args hsh, *args
|
|
6
|
+
defaults, args = extract_hash args
|
|
7
|
+
unknown_args = hsh.keys - (args + defaults.keys)
|
|
8
|
+
missing_args = args - hsh.keys
|
|
9
|
+
unless unknown_args.empty? and missing_args.empty?
|
|
10
|
+
raise ArgumentError, key_arg_error(unknown_args, missing_args)
|
|
11
|
+
end
|
|
12
|
+
(args + defaults.keys).map do |arg|
|
|
13
|
+
hsh.fetch arg do defaults.fetch arg end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def extract_hash ary
|
|
18
|
+
if ary.last.is_a? Hash
|
|
19
|
+
hsh = ary.pop
|
|
20
|
+
else
|
|
21
|
+
hsh = {}
|
|
22
|
+
end
|
|
23
|
+
[hsh, ary]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_camel_case str
|
|
27
|
+
str = "_#{str}"
|
|
28
|
+
str.gsub!(%r{_[a-z]}) { |snake| snake.slice(1).upcase }
|
|
29
|
+
str.gsub!('/', '::')
|
|
30
|
+
str
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def to_snake_case str
|
|
34
|
+
str = str.gsub '::', '/'
|
|
35
|
+
# Convert FOOBar => FooBar
|
|
36
|
+
str.gsub! %r{[[:upper:]]{2,}} do |uppercase|
|
|
37
|
+
bit = uppercase[0]
|
|
38
|
+
bit << uppercase[1...-1].downcase
|
|
39
|
+
bit << uppercase[-1]
|
|
40
|
+
bit
|
|
41
|
+
end
|
|
42
|
+
# Convert FooBar => foo_bar
|
|
43
|
+
str.gsub! %r{[[:lower:]][[:upper:]]+[[:lower:]]} do |camel|
|
|
44
|
+
bit = camel[0]
|
|
45
|
+
bit << '_'
|
|
46
|
+
bit << camel[1..-1].downcase
|
|
47
|
+
end
|
|
48
|
+
str.downcase!
|
|
49
|
+
str
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def key_arg_error unknown, missing
|
|
55
|
+
str = "bad arguments. "
|
|
56
|
+
if unknown.any?
|
|
57
|
+
str.concat " unknown: #{unknown.join ', '}"
|
|
58
|
+
str.concat "; " if missing.any?
|
|
59
|
+
end
|
|
60
|
+
if missing.any?
|
|
61
|
+
str.concat " missing: #{missing.join ', '}"
|
|
62
|
+
end
|
|
63
|
+
str
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: test-runner
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.9.2
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Nathan Ladd
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2015-09-19 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: logger-logging
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
description: Fork based runner for tests written as simple ruby scripts
|
|
28
|
+
email: nathanladd+github@gmail.com
|
|
29
|
+
executables:
|
|
30
|
+
- test-runner
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- bin/test-runner
|
|
35
|
+
- lib/test_runner.rb
|
|
36
|
+
- lib/test_runner/assert.rb
|
|
37
|
+
- lib/test_runner/assert/assertion.rb
|
|
38
|
+
- lib/test_runner/assert/check.rb
|
|
39
|
+
- lib/test_runner/assert/checks.rb
|
|
40
|
+
- lib/test_runner/assert/checks/registry.rb
|
|
41
|
+
- lib/test_runner/assert/errors.rb
|
|
42
|
+
- lib/test_runner/assert/syntax.rb
|
|
43
|
+
- lib/test_runner/backtrace_filter.rb
|
|
44
|
+
- lib/test_runner/cli.rb
|
|
45
|
+
- lib/test_runner/colored_logger.rb
|
|
46
|
+
- lib/test_runner/config.rb
|
|
47
|
+
- lib/test_runner/runner.rb
|
|
48
|
+
- lib/test_runner/script.rb
|
|
49
|
+
- lib/test_runner/util.rb
|
|
50
|
+
homepage: https://github.com/ntl/ftest
|
|
51
|
+
licenses:
|
|
52
|
+
- MIT
|
|
53
|
+
metadata: {}
|
|
54
|
+
post_install_message:
|
|
55
|
+
rdoc_options: []
|
|
56
|
+
require_paths:
|
|
57
|
+
- lib
|
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - ">="
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '0'
|
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
requirements: []
|
|
69
|
+
rubyforge_project:
|
|
70
|
+
rubygems_version: 2.4.5.1
|
|
71
|
+
signing_key:
|
|
72
|
+
specification_version: 4
|
|
73
|
+
summary: Fork based runner for tests written as simple ruby scripts
|
|
74
|
+
test_files: []
|