baretest 0.1.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/MANIFEST.txt +38 -0
- data/README.markdown +229 -0
- data/bin/baretest +102 -0
- data/examples/test.rake +37 -0
- data/examples/test.rb +93 -0
- data/lib/test/assertion/failure.rb +14 -0
- data/lib/test/assertion/support.rb +268 -0
- data/lib/test/assertion.rb +117 -0
- data/lib/test/debug.rb +34 -0
- data/lib/test/irb_mode.rb +104 -0
- data/lib/test/run/cli.rb +79 -0
- data/lib/test/run/errors.rb +42 -0
- data/lib/test/run/interactive.rb +60 -0
- data/lib/test/run/minimal.rb +31 -0
- data/lib/test/run/spec.rb +32 -0
- data/lib/test/run/tap.rb +32 -0
- data/lib/test/run/xml.rb +56 -0
- data/lib/test/run.rb +137 -0
- data/lib/test/suite.rb +95 -0
- data/lib/test/version.rb +19 -0
- data/lib/test.rb +118 -0
- data/test/external/bootstraptest.rb +5 -0
- data/test/external/bootstrapwrap.rb +2 -0
- data/test/helper/mocks.rb +0 -0
- data/test/lib/test/assertion/support.rb +240 -0
- data/test/lib/test/assertion.rb +142 -0
- data/test/lib/test/debug.rb +63 -0
- data/test/lib/test/irb_mode.rb +10 -0
- data/test/lib/test/run/cli.rb +9 -0
- data/test/lib/test/run/errors.rb +9 -0
- data/test/lib/test/run/interactive.rb +9 -0
- data/test/lib/test/run/spec.rb +9 -0
- data/test/lib/test/run/tap.rb +9 -0
- data/test/lib/test/run/xml.rb +9 -0
- data/test/lib/test/run.rb +235 -0
- data/test/lib/test/suite.rb +275 -0
- data/test/lib/test.rb +227 -0
- data/test/setup.rb +2 -0
- metadata +99 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2009 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
module Test
|
10
|
+
class Run
|
11
|
+
module Minimal
|
12
|
+
def run_all(*args)
|
13
|
+
start = Time.now
|
14
|
+
super # run all suites
|
15
|
+
stop = Time.now
|
16
|
+
values = @count.values_at(:test, :success, :pending, :failure, :error)
|
17
|
+
values.push(stop-start, global_status)
|
18
|
+
printf "Tests: %d\n" \
|
19
|
+
"Success: %d\n" \
|
20
|
+
"Pending: %d\n" \
|
21
|
+
"Failures: %d\n" \
|
22
|
+
"Errors: %d\n" \
|
23
|
+
"Time: %f\n" \
|
24
|
+
"Status: %s\n",
|
25
|
+
*values
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
@format["test/run/minimal"] = Run::Minimal # register the extender
|
31
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2009 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
module Test
|
10
|
+
class Run
|
11
|
+
module Spec
|
12
|
+
def run_all
|
13
|
+
@depth = 0
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def run_suite(suite)
|
18
|
+
return super unless suite.description
|
19
|
+
puts("\n"+' '*@depth+suite.description)
|
20
|
+
@depth += 1
|
21
|
+
super
|
22
|
+
@depth -= 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def run_test(assertion)
|
26
|
+
puts(' '*@depth+assertion.description)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
@format["test/run/spec"] = Run::Spec
|
32
|
+
end
|
data/lib/test/run/tap.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2009 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
module Test
|
10
|
+
class Run
|
11
|
+
module TAP
|
12
|
+
def run_all
|
13
|
+
puts "TAP version 13"
|
14
|
+
count = proc { |acc,csuite| acc+csuite.tests.size+csuite.suites.inject(0, &count) }
|
15
|
+
puts "1..#{count[0, suite]}"
|
16
|
+
@current = 0
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def run_test(assertion)
|
21
|
+
rv = super
|
22
|
+
printf "%sok %d - %s%s\n",
|
23
|
+
rv.status == :success ? '' : 'not ',
|
24
|
+
@current+=1,
|
25
|
+
rv.description,
|
26
|
+
rv.status == :success ? '' : " # #{rv.status}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
@format["test/run/tap"] = Run::TAP
|
32
|
+
end
|
data/lib/test/run/xml.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2009 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
module Test
|
10
|
+
class Run
|
11
|
+
module XML
|
12
|
+
def run_all
|
13
|
+
@depth = 1
|
14
|
+
|
15
|
+
puts '<?xml version="1.0" encoding="utf-8"?>'
|
16
|
+
puts '<tests>'
|
17
|
+
start = Time.now
|
18
|
+
super
|
19
|
+
stop = Time.now
|
20
|
+
status = case
|
21
|
+
when @count[:error] > 0 then 'error'
|
22
|
+
when @count[:failure] > 0 then 'failure'
|
23
|
+
when @count[:pending] > 0 then 'incomplete'
|
24
|
+
when @count[:skipped] > 0 then 'incomplete'
|
25
|
+
else 'success'
|
26
|
+
end
|
27
|
+
puts %{</tests>}
|
28
|
+
puts %{<report>}
|
29
|
+
puts %{\t<duration>#{stop-start}</duration>}
|
30
|
+
@count.each { |key, value|
|
31
|
+
puts %{\t<count type="#{key}">#{value}</count>}
|
32
|
+
}
|
33
|
+
puts %{</report>}
|
34
|
+
puts %{<status>#{status}</status>}
|
35
|
+
end
|
36
|
+
|
37
|
+
def run_suite(suite)
|
38
|
+
puts %{#{"\t"*@depth}<suite description="#{suite.description}">}
|
39
|
+
@depth += 1
|
40
|
+
super
|
41
|
+
@depth -= 1
|
42
|
+
puts %{#{"\t"*@depth}</suite>}
|
43
|
+
end
|
44
|
+
|
45
|
+
def run_test(assertion)
|
46
|
+
rv = super
|
47
|
+
puts %{#{"\t"*@depth}<test>}
|
48
|
+
puts %{#{"\t"*@depth}\t<status>#{rv.status}</status>}
|
49
|
+
puts %{#{"\t"*@depth}\t<description>#{rv.description}</description>}
|
50
|
+
puts %{#{"\t"*@depth}</test>}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
@format["test/run/xml"] = Run::XML
|
56
|
+
end
|
data/lib/test/run.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2009 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
module Test
|
10
|
+
|
11
|
+
# Run is the envorionment in which the suites and asserts are executed.
|
12
|
+
# Prior to the execution, the Run instance extends itself with the
|
13
|
+
# formatter given.
|
14
|
+
# Your formatter can override:
|
15
|
+
# * run_all
|
16
|
+
# * run_suite
|
17
|
+
# * run_test
|
18
|
+
class Run
|
19
|
+
# The toplevel suite.
|
20
|
+
attr_reader :suite
|
21
|
+
|
22
|
+
# The initialisation blocks of extenders
|
23
|
+
attr_reader :inits
|
24
|
+
|
25
|
+
# Some statistics, standard count keys are:
|
26
|
+
# * :test - the number of tests executed until now
|
27
|
+
# * :suite - the number of suites executed until now
|
28
|
+
# * :success - the number of tests with status :success
|
29
|
+
# * :failure - the number of tests with status :failure
|
30
|
+
# * :pending - the number of tests with status :pending
|
31
|
+
# * :skipped - the number of tests with status :skipped
|
32
|
+
# * :error - the number of tests with status :error
|
33
|
+
attr_reader :count
|
34
|
+
|
35
|
+
# Run the passed suite.
|
36
|
+
# Calls run_all with the toplevel suite as argument and a block that
|
37
|
+
# calls run_suite with the yielded argument (which should be the toplevel
|
38
|
+
# suite).
|
39
|
+
# Options accepted:
|
40
|
+
# * :extenders: An Array of Modules, will be used as argument to self.extend, useful e.g. for
|
41
|
+
# mock integration
|
42
|
+
# * :format: A string with the basename (without suffix) of the formatter to use - or a
|
43
|
+
# Module
|
44
|
+
# * :interactive: true/false, will switch this Test::Run instance into IRB mode, where an error
|
45
|
+
# will cause an irb session to be started in the context of a clean copy of
|
46
|
+
# the assertion with all setup callbacks invoked
|
47
|
+
#
|
48
|
+
# The order of extensions is:
|
49
|
+
# * :extender
|
50
|
+
# * :format (extends with the formatter module)
|
51
|
+
# * :interactive (extends with IRBMode)
|
52
|
+
def initialize(suite, opts=nil)
|
53
|
+
@suite = suite
|
54
|
+
@inits = []
|
55
|
+
@options = opts || {}
|
56
|
+
@count = @options[:count] || Hash.new(0)
|
57
|
+
|
58
|
+
(Test.extender+Array(@options[:extender])).each do |extender|
|
59
|
+
extend(extender)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Extend with the output formatter
|
63
|
+
if format = @options[:format] then
|
64
|
+
require "test/run/#{format}" if String === format
|
65
|
+
extend(String === format ? Test.format["test/run/#{format}"] : format)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Extend with irb dropout code
|
69
|
+
extend(Test::IRBMode) if @options[:interactive]
|
70
|
+
|
71
|
+
# Initialize extenders
|
72
|
+
@inits.each { |init| instance_eval(&init) }
|
73
|
+
end
|
74
|
+
|
75
|
+
# Hook initializers for extenders.
|
76
|
+
# Blocks passed to init will be instance_eval'd at the end of initialize.
|
77
|
+
# Example usage:
|
78
|
+
# module ExtenderForRun
|
79
|
+
# def self.extended(run_obj)
|
80
|
+
# run_obj.init do
|
81
|
+
# # do some initialization stuff for this module
|
82
|
+
# end
|
83
|
+
# end
|
84
|
+
# end
|
85
|
+
def init(&block)
|
86
|
+
@inits << block
|
87
|
+
end
|
88
|
+
|
89
|
+
# Formatter callback.
|
90
|
+
# Invoked once at the beginning.
|
91
|
+
# Gets the toplevel suite as single argument.
|
92
|
+
def run_all
|
93
|
+
run_suite(@suite)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Formatter callback.
|
97
|
+
# Invoked once for every suite.
|
98
|
+
# Gets the suite to run as single argument.
|
99
|
+
# Runs all assertions and nested suites.
|
100
|
+
def run_suite(suite)
|
101
|
+
suite.tests.each do |test|
|
102
|
+
run_test(test)
|
103
|
+
end
|
104
|
+
suite.suites.each do |suite|
|
105
|
+
run_suite(suite)
|
106
|
+
end
|
107
|
+
@count[:suite] += 1
|
108
|
+
end
|
109
|
+
|
110
|
+
# Formatter callback.
|
111
|
+
# Invoked once for every assertion.
|
112
|
+
# Gets the assertion to run as single argument.
|
113
|
+
def run_test(assertion)
|
114
|
+
rv = assertion.execute
|
115
|
+
@count[:test] += 1
|
116
|
+
@count[assertion.status] += 1
|
117
|
+
rv
|
118
|
+
end
|
119
|
+
|
120
|
+
# Status over all tests ran up to now
|
121
|
+
# Can be :error, :failure, :incomplete or :success
|
122
|
+
# The algorithm is a simple fall through:
|
123
|
+
# if any test errored, then global_status is :error,
|
124
|
+
# if not, then if any test failed, global_status is :failure,
|
125
|
+
# if not, then if any test was pending or skipped, global_status is :incomplete,
|
126
|
+
# if not, then global_status is success
|
127
|
+
def global_status
|
128
|
+
case
|
129
|
+
when @count[:error] > 0 then :error
|
130
|
+
when @count[:failure] > 0 then :failure
|
131
|
+
when @count[:pending] > 0 then :incomplete
|
132
|
+
when @count[:skipped] > 0 then :incomplete
|
133
|
+
else :success
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
data/lib/test/suite.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2009 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
module Test
|
10
|
+
|
11
|
+
# A Suite is a container for multiple assertions.
|
12
|
+
# You can give a suite a description, also a suite can contain
|
13
|
+
# setup and teardown blocks that are executed before (setup) and after
|
14
|
+
# (teardown) every assertion.
|
15
|
+
# Suites can also be nested. Nested suites will inherit setup and teardown.
|
16
|
+
class Suite
|
17
|
+
|
18
|
+
# Nested suites
|
19
|
+
attr_reader :suites
|
20
|
+
|
21
|
+
# All assertions in this suite
|
22
|
+
attr_reader :tests
|
23
|
+
|
24
|
+
# This suites description. Toplevel suites usually don't have a description.
|
25
|
+
attr_reader :description
|
26
|
+
|
27
|
+
# This suites direct parent. Nil if toplevel suite.
|
28
|
+
attr_reader :parent
|
29
|
+
|
30
|
+
# An Array containing the suite itself (first element), then its direct
|
31
|
+
# parent suite, then that suite's parent and so on
|
32
|
+
attr_reader :ancestors
|
33
|
+
|
34
|
+
def self.create(description=nil, parent=nil, opts={}, &block)
|
35
|
+
Array(opts[:requires]).each { |file| require file } if opts[:requires]
|
36
|
+
rescue LoadError
|
37
|
+
# A suite is skipped if requirements are not met
|
38
|
+
Skipped::Suite.new(description, parent, &block)
|
39
|
+
else
|
40
|
+
# All suites within Skipped::Suite are Skipped::Suite
|
41
|
+
(block ? self : Skipped::Suite).new(description, parent, &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize(description=nil, parent=nil, &block)
|
45
|
+
@description = description
|
46
|
+
@parent = parent
|
47
|
+
@suites = []
|
48
|
+
@tests = []
|
49
|
+
@setup = []
|
50
|
+
@teardown = []
|
51
|
+
@ancestors = [self] + (@parent ? @parent.ancestors : [])
|
52
|
+
instance_eval(&block) if block
|
53
|
+
end
|
54
|
+
|
55
|
+
# Define a nested suite.
|
56
|
+
# Nested suites inherit setup & teardown methods.
|
57
|
+
# Also if an outer suite is skipped, all inner suites are skipped too.
|
58
|
+
# Valid values for opts:
|
59
|
+
# requires
|
60
|
+
# : A list of files to require, if one of the requires fails, the suite
|
61
|
+
# will be skipped. Accepts a String or an Array
|
62
|
+
def suite(description=nil, opts={}, &block)
|
63
|
+
@suites << self.class.create(description, self, opts, &block)
|
64
|
+
end
|
65
|
+
|
66
|
+
# All setups in the order of their nesting (outermost first, innermost last)
|
67
|
+
def ancestry_setup
|
68
|
+
ancestors.map { |suite| suite.setup }.flatten.reverse
|
69
|
+
end
|
70
|
+
|
71
|
+
# All teardowns in the order of their nesting (innermost first, outermost last)
|
72
|
+
def ancestry_teardown
|
73
|
+
ancestors.map { |suite| suite.teardown }.flatten
|
74
|
+
end
|
75
|
+
|
76
|
+
# Define a setup block for this suite. The block will be ran before every
|
77
|
+
# assertion once, even for nested suites.
|
78
|
+
def setup(&block)
|
79
|
+
block ? @setup << block : @setup
|
80
|
+
end
|
81
|
+
|
82
|
+
# Define a teardown block for this suite. The block will be ran after every
|
83
|
+
# assertion once, even for nested suites.
|
84
|
+
def teardown(&block)
|
85
|
+
block ? @teardown << block : @teardown
|
86
|
+
end
|
87
|
+
|
88
|
+
# Define an assertion. The block is supposed to return a trueish value
|
89
|
+
# (anything but nil or false).
|
90
|
+
# See Assertion for more info.
|
91
|
+
def assert(description=nil, &block)
|
92
|
+
@tests << Assertion.new(self, description, &block)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/test/version.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2009 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
module Test
|
10
|
+
module VERSION
|
11
|
+
MAJOR = 0
|
12
|
+
MINOR = 1
|
13
|
+
TINY = 0
|
14
|
+
|
15
|
+
def self.to_s
|
16
|
+
"#{MAJOR}.#{MINOR||0}.#{TINY||0}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/test.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2009 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
require 'test/irb_mode'
|
10
|
+
require 'test/run'
|
11
|
+
require 'test/suite'
|
12
|
+
require 'test/assertion'
|
13
|
+
# See bottom for more requires
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
module Test
|
18
|
+
class <<self
|
19
|
+
# A hash of formatters (require-string => module) to be used with Test::Run.
|
20
|
+
attr_reader :format
|
21
|
+
|
22
|
+
# For mock integration and others, append modules that should extend the Test::Run instance.
|
23
|
+
attr_reader :extender
|
24
|
+
|
25
|
+
# The toplevel suite. That's the one run_if_mainfile and define add suites
|
26
|
+
# and assertions to.
|
27
|
+
attr_reader :toplevel_suite
|
28
|
+
|
29
|
+
# The full path to this file
|
30
|
+
attr_reader :required_file
|
31
|
+
end
|
32
|
+
|
33
|
+
# For bootstrapped selftest
|
34
|
+
def self.init
|
35
|
+
@format = {}
|
36
|
+
@extender = []
|
37
|
+
@toplevel_suite = Suite.new
|
38
|
+
@required_file = ["", *$LOAD_PATH].map { |path|
|
39
|
+
File.expand_path(File.join(path, __FILE__))
|
40
|
+
}.find { |full| File.exist?(full) }
|
41
|
+
end
|
42
|
+
init
|
43
|
+
|
44
|
+
# Adds the contained assertions and suites to the toplevel suite
|
45
|
+
def self.define(name=nil, opts={}, &block)
|
46
|
+
if name then
|
47
|
+
@toplevel_suite.suite(name, opts, &block)
|
48
|
+
elsif opts && !opts.empty?
|
49
|
+
raise ArgumentError, "Suites with options must have names"
|
50
|
+
else
|
51
|
+
@toplevel_suite.instance_eval(&block)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Creates a Test::Run instance, adds the assertions and suites defined in its
|
56
|
+
# own block to that Test::Run instance's toplevel suite and if $PROGRAM_NAME
|
57
|
+
# (aka $0) is equal to __FILE__ (means the current file is the file directly
|
58
|
+
# executed by ruby, and not just required/loaded/evaled by another file),
|
59
|
+
# subsequently also runs that suite.
|
60
|
+
def self.run_if_mainfile(name=nil, opts={}, &block)
|
61
|
+
define(name, opts, &block)
|
62
|
+
if caller.first[/^[^:]*/] == $0 then # if is mainfile
|
63
|
+
run(:format => ENV['FORMAT'], :interactive => ENV['INTERACTIVE'])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.run(opts=nil)
|
68
|
+
Run.new(@toplevel_suite, opts).run_all
|
69
|
+
end
|
70
|
+
|
71
|
+
# Skipped contains variants of Suite and Assertion.
|
72
|
+
# See Skipped::Suite and Skipped::Assertion
|
73
|
+
module Skipped
|
74
|
+
# Like Test::Suite, but all Assertions are defined as Skipped::Assertion
|
75
|
+
class Suite < ::Test::Suite
|
76
|
+
# :nodoc:
|
77
|
+
# All Assertions use Skipped::Assertion instead of Test::Assertion.
|
78
|
+
def assert(description=nil, &block)
|
79
|
+
@tests << Skipped::Assertion.new(self, description, &block)
|
80
|
+
end
|
81
|
+
|
82
|
+
# :nodoc:
|
83
|
+
# All setup blocks are disabled
|
84
|
+
def ancestry_setup
|
85
|
+
[]
|
86
|
+
end
|
87
|
+
|
88
|
+
# :nodoc:
|
89
|
+
# All teardown blocks are disabled
|
90
|
+
def ancestry_teardown
|
91
|
+
[]
|
92
|
+
end
|
93
|
+
|
94
|
+
# :nodoc:
|
95
|
+
# All setup blocks are disabled
|
96
|
+
def setup(&block)
|
97
|
+
[]
|
98
|
+
end
|
99
|
+
|
100
|
+
# :nodoc:
|
101
|
+
# All teardown blocks are disabled
|
102
|
+
def teardown(&block)
|
103
|
+
[]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Like Test::Assertion, but fakes execution and sets status always to
|
108
|
+
# skipped.
|
109
|
+
class Assertion < ::Test::Assertion
|
110
|
+
def execute() @status = :skipped and self end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
|
117
|
+
# At bottom due to dependencies
|
118
|
+
require 'test/assertion/support' # Needs Test.extender to be defined
|
File without changes
|