ruptr 0.1.3
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/ruptr +10 -0
- data/lib/ruptr/adapters/assertions.rb +43 -0
- data/lib/ruptr/adapters/rr.rb +23 -0
- data/lib/ruptr/adapters/rspec_expect.rb +32 -0
- data/lib/ruptr/adapters/rspec_mocks.rb +29 -0
- data/lib/ruptr/adapters.rb +7 -0
- data/lib/ruptr/assertions.rb +493 -0
- data/lib/ruptr/autorun.rb +38 -0
- data/lib/ruptr/capture_output.rb +106 -0
- data/lib/ruptr/compat.rb +27 -0
- data/lib/ruptr/exceptions.rb +47 -0
- data/lib/ruptr/formatter.rb +78 -0
- data/lib/ruptr/golden_master.rb +143 -0
- data/lib/ruptr/instance.rb +37 -0
- data/lib/ruptr/main.rb +439 -0
- data/lib/ruptr/minitest/override.rb +4 -0
- data/lib/ruptr/minitest.rb +134 -0
- data/lib/ruptr/plain.rb +425 -0
- data/lib/ruptr/progress.rb +98 -0
- data/lib/ruptr/rake_task.rb +18 -0
- data/lib/ruptr/report.rb +104 -0
- data/lib/ruptr/result.rb +38 -0
- data/lib/ruptr/rspec/configuration.rb +191 -0
- data/lib/ruptr/rspec/example_group.rb +498 -0
- data/lib/ruptr/rspec/override.rb +4 -0
- data/lib/ruptr/rspec.rb +211 -0
- data/lib/ruptr/runner.rb +433 -0
- data/lib/ruptr/sink.rb +58 -0
- data/lib/ruptr/stringified.rb +57 -0
- data/lib/ruptr/suite.rb +188 -0
- data/lib/ruptr/surrogate_exception.rb +71 -0
- data/lib/ruptr/tabular.rb +21 -0
- data/lib/ruptr/tap.rb +51 -0
- data/lib/ruptr/testunit/override.rb +4 -0
- data/lib/ruptr/testunit.rb +117 -0
- data/lib/ruptr/timing_cache.rb +100 -0
- data/lib/ruptr/tty_colors.rb +60 -0
- data/lib/ruptr/utils.rb +57 -0
- metadata +77 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
require 'set'
|
|
5
|
+
|
|
6
|
+
module Ruptr
|
|
7
|
+
class Compat
|
|
8
|
+
class RSpec < self
|
|
9
|
+
class Adapter < Module
|
|
10
|
+
class Configuration
|
|
11
|
+
extend Forwardable
|
|
12
|
+
|
|
13
|
+
def initialize(adapter)
|
|
14
|
+
@adapter_module = adapter
|
|
15
|
+
@delayed_example_group_alterations = []
|
|
16
|
+
@delayed_example_alterations = []
|
|
17
|
+
@inclusion_filter = []
|
|
18
|
+
@exclusion_filter = []
|
|
19
|
+
@run_all_when_everything_filtered = false
|
|
20
|
+
@expectation_frameworks = Set.new
|
|
21
|
+
@mock_frameworks = Set.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
attr_reader :adapter_module
|
|
25
|
+
|
|
26
|
+
def disable_monkey_patching! = nil # TODO
|
|
27
|
+
|
|
28
|
+
def color_enabled? = false # used in RSpec::Expectations::Configuration
|
|
29
|
+
|
|
30
|
+
def full_backtrace=(v)
|
|
31
|
+
raise ArgumentError, "unsupported" if v
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def full_backtrace? = true
|
|
35
|
+
|
|
36
|
+
def order=(v)
|
|
37
|
+
raise ArgumentError, "unsupported" if v.to_sym != :random
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def threadsafe = false
|
|
41
|
+
|
|
42
|
+
def threadsafe=(v)
|
|
43
|
+
# NOTE: The framework supports running the examples in multiple threads, but the example
|
|
44
|
+
# blocks themselves should not call the framework from multiple threads. This can be a
|
|
45
|
+
# problem with memoized "let" helpers for example (but using "let!" helpers is fine).
|
|
46
|
+
raise ArgumentError, "unsupported" if v
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private def delayed_example_group_alteration(filter)
|
|
50
|
+
@delayed_example_group_alterations << lambda do |example_group|
|
|
51
|
+
next unless ExampleGroup.filter_matches?(filter, example_group.metadata)
|
|
52
|
+
yield example_group
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def apply_to_example_group(example_group = nil)
|
|
57
|
+
@delayed_example_group_alterations.each { |p| p.call(example_group ||= yield) }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private def delayed_example_alteration(filter)
|
|
61
|
+
@delayed_example_alterations << lambda do |example|
|
|
62
|
+
next unless ExampleGroup.filter_matches?(filter, example.metadata)
|
|
63
|
+
yield example
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def apply_to_example(example = nil)
|
|
68
|
+
@delayed_example_alterations.each { |p| p.call(example ||= yield) }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
%i[include prepend extend].each do |name|
|
|
72
|
+
define_method(name) do |m, *args, **opts|
|
|
73
|
+
delayed_example_group_alteration(ExampleGroup.get_args_filter(args, opts)) do |example_group|
|
|
74
|
+
example_group.send(name, m)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def include_context(include_name, *args, **opts)
|
|
80
|
+
delayed_example_group_alteration(ExampleGroup.get_args_filter(args, opts)) do |example_group|
|
|
81
|
+
example_group.include_context(include_name)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def on_example_group_definition(&)
|
|
86
|
+
delayed_example_group_alteration({}, &)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def when_first_matching_example_defined(*args, **opts)
|
|
90
|
+
first = true
|
|
91
|
+
delayed_example_alteration(ExampleGroup.get_args_filter(args, opts)) do |example|
|
|
92
|
+
next unless first
|
|
93
|
+
yield example
|
|
94
|
+
first = false
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
attr_accessor :inclusion_filter, :exclusion_filter,
|
|
99
|
+
:run_all_when_everything_filtered
|
|
100
|
+
|
|
101
|
+
def filter_run_including(*args, **opts)
|
|
102
|
+
inclusion_filter << ExampleGroup.get_args_filter(args, opts)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
alias filter_run filter_run_including
|
|
106
|
+
|
|
107
|
+
def filter_run_excluding(*args, **opts)
|
|
108
|
+
exclusion_filter << ExampleGroup.get_args_filter(args, opts)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def filter_run_when_matching(*args, **opts)
|
|
112
|
+
when_first_matching_example_defined(*args, **opts) { filter_run_including(*args, **opts) }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def silence_filter_announcements = true
|
|
116
|
+
|
|
117
|
+
def silence_filter_announcements=(v)
|
|
118
|
+
raise ArgumentError, "unsupported" if v
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
attr_reader :expectation_frameworks
|
|
122
|
+
|
|
123
|
+
private def expect_with_1(handler)
|
|
124
|
+
case handler
|
|
125
|
+
when Module
|
|
126
|
+
root_example_group.include(handler)
|
|
127
|
+
when :test_unit, :minitest
|
|
128
|
+
require 'ruptr/adapters/assertions'
|
|
129
|
+
root_example_group.include(Adapters::RuptrAssertions)
|
|
130
|
+
when :rspec
|
|
131
|
+
require 'ruptr/adapters/rspec_expect'
|
|
132
|
+
root_example_group.include(Adapters::RSpecExpect)
|
|
133
|
+
root_example_group.include(Adapters::RSpecExpect::Helpers)
|
|
134
|
+
else
|
|
135
|
+
raise ArgumentError, "#{handler} not supported"
|
|
136
|
+
end
|
|
137
|
+
@expectation_frameworks << handler
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def expect_with(*handlers)
|
|
141
|
+
handlers.each { |handler| expect_with_1(handler) }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
attr_reader :mock_frameworks
|
|
145
|
+
|
|
146
|
+
private def mock_with_1(handler)
|
|
147
|
+
case handler
|
|
148
|
+
when :rspec
|
|
149
|
+
require 'ruptr/adapters/rspec_mocks'
|
|
150
|
+
root_example_group.include(Adapters::RSpecMocks)
|
|
151
|
+
root_example_group.include(Adapters::RSpecMocks::Helpers)
|
|
152
|
+
else
|
|
153
|
+
raise ArgumentError, "#{handler} not supported"
|
|
154
|
+
end
|
|
155
|
+
@mock_frameworks << handler
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def mock_with(*handlers)
|
|
159
|
+
handlers.each { |handler| mock_with_1(handler) }
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def alias_example_group_to(name, *args, **opts)
|
|
163
|
+
metadata = ExampleGroup.get_args_filter(args, opts)
|
|
164
|
+
root_example_group.singleton_class.def_example_group_shortcut(name, metadata)
|
|
165
|
+
adapter_module.singleton_class.def_delegators(:root_example_group, name)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def alias_example_to(name, *args, **opts)
|
|
169
|
+
metadata = ExampleGroup.get_args_filter(args, opts)
|
|
170
|
+
root_example_group.singleton_class.def_example_shortcut(name, metadata)
|
|
171
|
+
adapter_module.singleton_class.def_delegators(:root_example_group, name)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def alias_it_behaves_like_to(name, label_prefix)
|
|
175
|
+
root_example_group.singleton_class.def_it_behaves_like_shortcut(
|
|
176
|
+
name, ->(example_group_name) { "#{label_prefix} #{example_group_name}" }
|
|
177
|
+
)
|
|
178
|
+
adapter_module.singleton_class.def_delegators(:root_example_group, name)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
alias alias_it_should_behave_like_to alias_it_behaves_like_to
|
|
182
|
+
|
|
183
|
+
def_delegators :adapter_module,
|
|
184
|
+
:root_example_group
|
|
185
|
+
def_delegators :root_example_group,
|
|
186
|
+
:before, :prepend_before, :after, :append_after, :around
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../adapters'
|
|
4
|
+
|
|
5
|
+
module Ruptr
|
|
6
|
+
class Compat
|
|
7
|
+
class RSpec < self
|
|
8
|
+
# Passed to example and hook blocks.
|
|
9
|
+
class Handle
|
|
10
|
+
def initialize(element, exception = nil, &run)
|
|
11
|
+
@element = element
|
|
12
|
+
@exception = exception
|
|
13
|
+
@run = run
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def metadata = @element.metadata
|
|
17
|
+
def exception = @exception
|
|
18
|
+
def run = @run.call(chain)
|
|
19
|
+
def to_proc = @run ? proc { run } : nil
|
|
20
|
+
def chain(&) = self.class.new(@element, @exception, &)
|
|
21
|
+
def caught(exception) = exception ? self.class.new(@element, exception) : self
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Common for both Examples and ExampleGroups.
|
|
25
|
+
module Element
|
|
26
|
+
DEFAULT_METADATA = {}.freeze
|
|
27
|
+
def metadata = DEFAULT_METADATA
|
|
28
|
+
def label = nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class Example
|
|
32
|
+
include Element
|
|
33
|
+
|
|
34
|
+
def initialize(label:, metadata:, block:)
|
|
35
|
+
@label = label
|
|
36
|
+
@metadata = metadata
|
|
37
|
+
@block = block
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
attr_reader :label, :metadata, :block
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
module ExampleGroupInstanceUserMethods
|
|
44
|
+
def skip(reason = nil) = raise SkippedException, reason
|
|
45
|
+
|
|
46
|
+
def pending? = @rspec_example_pending
|
|
47
|
+
def pending_reason = @rspec_example_pending_reason
|
|
48
|
+
|
|
49
|
+
def pending(reason = nil)
|
|
50
|
+
@rspec_example_pending = true
|
|
51
|
+
@rspec_example_pending_reason = reason
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
module ExampleGroupInstanceInternal
|
|
56
|
+
def wrap_context_handle(h) = yield h
|
|
57
|
+
def wrap_context(&) = wrap_context_handle(Handle.new(self.class), &)
|
|
58
|
+
|
|
59
|
+
def wrap_example_handle(h) = yield h
|
|
60
|
+
|
|
61
|
+
def run_example(example)
|
|
62
|
+
ran = false
|
|
63
|
+
wrap_example_handle(Handle.new(example)) do |h|
|
|
64
|
+
instance_exec(h, &example.block)
|
|
65
|
+
ran = true
|
|
66
|
+
end
|
|
67
|
+
skip unless ran # may happen if an "around" hook didn't call its handle's #run method
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
module ExampleGroupInternal
|
|
72
|
+
def carryover_instance_variables(from, to)
|
|
73
|
+
from.instance_variables.each do |name|
|
|
74
|
+
next if from.ruptr_internal_variable?(name)
|
|
75
|
+
to.instance_variable_set(name, from.instance_variable_get(name))
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def my_examples = @my_examples ||= []
|
|
80
|
+
def my_example_groups = @my_example_groups ||= []
|
|
81
|
+
|
|
82
|
+
def each_example(&) = my_examples.each(&)
|
|
83
|
+
def each_example_group(&) = my_example_groups.each(&)
|
|
84
|
+
|
|
85
|
+
def need_wrap_context? = false
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
module ExampleGroupHooks
|
|
89
|
+
def self.def_hook(name, setup_method_name)
|
|
90
|
+
iv_name = :"@#{name}"
|
|
91
|
+
attr_reader name
|
|
92
|
+
define_method(:"#{name}=") do |new|
|
|
93
|
+
instance_variable_set(iv_name, new)
|
|
94
|
+
send(setup_method_name)
|
|
95
|
+
new
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Context-level hooks are invoked by the runner. It will recursively call #wrap_context as
|
|
100
|
+
# it traverses the hierarchy of test groups.
|
|
101
|
+
|
|
102
|
+
def_hook :context_around_layer_hook, :setup_context_hooks
|
|
103
|
+
def_hook :context_before_hook, :setup_context_hooks
|
|
104
|
+
def_hook :context_after_hook, :setup_context_hooks
|
|
105
|
+
|
|
106
|
+
# For example-level hooks, "around" hooks are run in a separate phase that precedes all of
|
|
107
|
+
# the nested "before" and "after" hooks. This is not supported for context-level hooks, so
|
|
108
|
+
# use "around_layer" semantics for those instead. The behavior may not be exactly correct
|
|
109
|
+
# (though it seems to be deprecated in RSpec and I'm not sure what the correct behavior
|
|
110
|
+
# actually should be). This at least makes :suite "around" hooks work as expected (as they
|
|
111
|
+
# are added to the root example group, they will necessarily be run before all of the
|
|
112
|
+
# "before" and "after" hooks without needing a separate phase).
|
|
113
|
+
|
|
114
|
+
alias context_around_hook context_around_layer_hook
|
|
115
|
+
alias context_around_hook= context_around_layer_hook=
|
|
116
|
+
|
|
117
|
+
def need_wrap_context? = context_around_layer_hook || context_before_hook || context_after_hook
|
|
118
|
+
|
|
119
|
+
module ContextHooksInstanceMethods
|
|
120
|
+
def wrap_context_handle(h)
|
|
121
|
+
eg = self.class
|
|
122
|
+
# NOTE: Hooks for the parent example groups will already have been called by the runner.
|
|
123
|
+
k = lambda do |h|
|
|
124
|
+
instance_exec(h, &eg.context_before_hook) if eg.context_before_hook
|
|
125
|
+
yield h
|
|
126
|
+
ensure
|
|
127
|
+
instance_exec(h, &eg.context_after_hook) if eg.context_after_hook
|
|
128
|
+
end
|
|
129
|
+
if eg.context_around_layer_hook
|
|
130
|
+
instance_exec(h.chain(&k), &eg.context_around_layer_hook)
|
|
131
|
+
else
|
|
132
|
+
k.call(h)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def setup_context_hooks
|
|
138
|
+
include ContextHooksInstanceMethods
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Example-level hooks are invoked by the test case instance before each example is run.
|
|
142
|
+
# Method inheritance is used to call the hooks of the parent example groups (if any).
|
|
143
|
+
#
|
|
144
|
+
# Two phases are used: the "around" hooks are run in phase 1, the "before", "after" and
|
|
145
|
+
# "around_layer" hooks in phase 2.
|
|
146
|
+
#
|
|
147
|
+
# The "around_layer" hooks behave similarly to "around" but they only wrap around the
|
|
148
|
+
# "before" and "after" hooks of their own (and nested) example groups.
|
|
149
|
+
|
|
150
|
+
def_hook :example_around_hook, :setup_example_phase1_hooks
|
|
151
|
+
def_hook :example_around_layer_hook, :setup_example_phase2_hooks
|
|
152
|
+
def_hook :example_before_hook, :setup_example_phase2_hooks
|
|
153
|
+
def_hook :example_after_hook, :setup_example_phase2_hooks
|
|
154
|
+
|
|
155
|
+
module ExampleHooksBaseInstanceMethods
|
|
156
|
+
def wrap_example_phase1_hooks(h) = yield h
|
|
157
|
+
def wrap_example_phase2_hooks(h) = yield h
|
|
158
|
+
def wrap_example_handle(h, &) = wrap_example_phase1_hooks(h) { |h| wrap_example_phase2_hooks(h, &) }
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def setup_example_phase1_hooks
|
|
162
|
+
return if method_defined?(:wrap_example_phase1_hooks, false)
|
|
163
|
+
include ExampleHooksBaseInstanceMethods
|
|
164
|
+
eg = self
|
|
165
|
+
define_method(:wrap_example_phase1_hooks) do |down_h, &up|
|
|
166
|
+
super(down_h) { |h| instance_exec(h.chain(&up), &eg.example_around_hook) }
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def setup_example_phase2_hooks
|
|
171
|
+
return if method_defined?(:wrap_example_phase2_hooks, false)
|
|
172
|
+
include ExampleHooksBaseInstanceMethods
|
|
173
|
+
eg = self
|
|
174
|
+
define_method(:wrap_example_phase2_hooks) do |down_h, &up|
|
|
175
|
+
super(down_h) do |h|
|
|
176
|
+
k = if eg.example_before_hook || eg.example_after_hook
|
|
177
|
+
lambda do |h|
|
|
178
|
+
instance_exec(h, &eg.example_before_hook) if eg.example_before_hook
|
|
179
|
+
up.call(h)
|
|
180
|
+
ensure
|
|
181
|
+
instance_exec(h.caught($!), &eg.example_after_hook) if eg.example_after_hook
|
|
182
|
+
end
|
|
183
|
+
else
|
|
184
|
+
up
|
|
185
|
+
end
|
|
186
|
+
if eg.example_around_layer_hook
|
|
187
|
+
instance_exec(h.chain(&k), &eg.example_around_layer_hook)
|
|
188
|
+
else
|
|
189
|
+
k.call(h)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
module SharedContext
|
|
197
|
+
# This is almost identical to the implementation in RSpec 3 rspec-core's
|
|
198
|
+
# lib/rspec/core/shared_context.rb. This makes SharedContext modules behave very similarly
|
|
199
|
+
# to named #shared_examples/#shared_context blocks.
|
|
200
|
+
def included(from)
|
|
201
|
+
super
|
|
202
|
+
ruptr_shared_context_recordings.each do |method_name, args, opts, blk|
|
|
203
|
+
from.__send__(method_name, *args, **opts, &blk)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def ruptr_shared_context_recordings = @ruptr_shared_context_recordings ||= []
|
|
208
|
+
|
|
209
|
+
def self.record(method_name)
|
|
210
|
+
define_method(method_name) do |*args, **opts, &blk|
|
|
211
|
+
ruptr_shared_context_recordings << [method_name, args, opts, blk]
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
module ExampleGroupDSL
|
|
217
|
+
module Meta; end
|
|
218
|
+
extend Meta
|
|
219
|
+
|
|
220
|
+
def self.extended(from)
|
|
221
|
+
super
|
|
222
|
+
from.singleton_class.extend(Meta)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def get_args_filter(args, opts)
|
|
226
|
+
args.each { |key| opts[key] = true }
|
|
227
|
+
opts
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def filter_matches?(filter, metadata)
|
|
231
|
+
filter.all? { |k, v| metadata[k] == v }
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def get_args_metadata(extra, args, opts)
|
|
235
|
+
return metadata if (extra.nil? || extra.empty?) && args.empty? && opts.empty?
|
|
236
|
+
new_metadata = metadata.dup
|
|
237
|
+
new_metadata.merge!(extra) if extra
|
|
238
|
+
new_metadata.merge!(opts)
|
|
239
|
+
args.each { |key| new_metadata[key] = true }
|
|
240
|
+
new_metadata
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def example_with_metadata_1(label, metadata, &blk)
|
|
244
|
+
Example.new(
|
|
245
|
+
label: label&.to_s,
|
|
246
|
+
metadata:,
|
|
247
|
+
block: if (reason = metadata[:skip]) || !blk
|
|
248
|
+
reason = nil if reason == true
|
|
249
|
+
->(_h) { raise SkippedException, reason }
|
|
250
|
+
elsif (reason = metadata[:pending])
|
|
251
|
+
reason = nil if reason == true
|
|
252
|
+
->(h) { pending(reason); instance_exec(h, &blk) }
|
|
253
|
+
else
|
|
254
|
+
blk
|
|
255
|
+
end
|
|
256
|
+
).tap do |example|
|
|
257
|
+
configuration.apply_to_example(example)
|
|
258
|
+
my_examples << example
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def example_with_metadata(label, metadata, &)
|
|
263
|
+
unless metadata == self.metadata
|
|
264
|
+
done = false
|
|
265
|
+
configuration.apply_to_example_group do
|
|
266
|
+
example_group_with_metadata(nil, metadata, apply_configuration: false) do
|
|
267
|
+
example_with_metadata_1(label, metadata, &)
|
|
268
|
+
done = true
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
example_with_metadata_1(label, metadata, &) unless done
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# FIXME: This won't necessarily be unique with examples included from SharedContext modules.
|
|
276
|
+
def default_example_name = "example ##{my_examples.size + 1}"
|
|
277
|
+
|
|
278
|
+
module Meta
|
|
279
|
+
def recordable(method_name) = SharedContext.record(method_name)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
recordable def example(label = default_example_name, *args, **opts, &)
|
|
283
|
+
example_with_metadata(label, get_args_metadata(nil, args, opts).freeze, &)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
recordable alias_method :specify, :example
|
|
287
|
+
recordable alias_method :it, :example
|
|
288
|
+
|
|
289
|
+
module Meta
|
|
290
|
+
def def_example_shortcut(name, extra_metadata)
|
|
291
|
+
recordable name
|
|
292
|
+
define_method(name) do |label = default_example_name, *args, **opts, &blk|
|
|
293
|
+
example_with_metadata(label, get_args_metadata(extra_metadata, args, opts).freeze, &blk)
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def_example_shortcut :focus, { focus: true }
|
|
299
|
+
def_example_shortcut :fexample, { focus: true }
|
|
300
|
+
def_example_shortcut :fspecify, { focus: true }
|
|
301
|
+
def_example_shortcut :fit, { focus: true }
|
|
302
|
+
|
|
303
|
+
def_example_shortcut :skip, { skip: true }
|
|
304
|
+
def_example_shortcut :xexample, { skip: "Temporarily skipped with xexample" }
|
|
305
|
+
def_example_shortcut :xspecify, { skip: "Temporarily skipped with xspecify" }
|
|
306
|
+
def_example_shortcut :xit, { skip: "Temporarily skipped with xit" }
|
|
307
|
+
|
|
308
|
+
def_example_shortcut :pending, { pending: true }
|
|
309
|
+
|
|
310
|
+
def example_group_with_metadata(label, metadata, apply_configuration: true, &)
|
|
311
|
+
Class.new(self) do
|
|
312
|
+
define_singleton_method(:metadata) { metadata }
|
|
313
|
+
define_singleton_method(:label) { label&.to_s }
|
|
314
|
+
if label.is_a?(Module)
|
|
315
|
+
define_singleton_method(:described_class) { label }
|
|
316
|
+
define_method(:described_class) { label }
|
|
317
|
+
if label.is_a?(Class)
|
|
318
|
+
let(:subject) { label.new }
|
|
319
|
+
else
|
|
320
|
+
let(:subject) { label }
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
configuration.apply_to_example_group(self) if apply_configuration
|
|
324
|
+
class_exec(&) if block_given?
|
|
325
|
+
end.tap { |example_group| my_example_groups << example_group }
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
recordable def example_group(label = nil, *args, **opts, &)
|
|
329
|
+
example_group_with_metadata(label, get_args_metadata(nil, args, opts).freeze, &)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
recordable alias_method :describe, :example_group
|
|
333
|
+
recordable alias_method :context, :example_group
|
|
334
|
+
|
|
335
|
+
module Meta
|
|
336
|
+
def def_example_group_shortcut(name, extra_metadata)
|
|
337
|
+
recordable name
|
|
338
|
+
define_method(name) do |label, *args, **opts, &blk|
|
|
339
|
+
example_group_with_metadata(label, get_args_metadata(extra_metadata, args, opts).freeze, &blk)
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def_example_group_shortcut :fdescribe, { focus: true }
|
|
345
|
+
def_example_group_shortcut :fcontext, { focus: true }
|
|
346
|
+
def_example_group_shortcut :xdescribe, { skip: "Temporarily skipped with xdescribe" }
|
|
347
|
+
def_example_group_shortcut :xcontext, { skip: "Temporarily skipped with xcontext" }
|
|
348
|
+
|
|
349
|
+
def lookup_shared_examples(_name) = nil
|
|
350
|
+
def shared_examples_stash = nil
|
|
351
|
+
|
|
352
|
+
recordable def shared_examples(name, *args, **opts, &body)
|
|
353
|
+
_child_metadata = get_args_metadata(nil, args, opts).freeze # TODO
|
|
354
|
+
unless singleton_class.method_defined?(:shared_examples_stash, false)
|
|
355
|
+
stash = {}
|
|
356
|
+
define_singleton_method(:shared_examples_stash) { stash }
|
|
357
|
+
define_singleton_method(:lookup_shared_examples) { |name| stash[name] || super(name) }
|
|
358
|
+
end
|
|
359
|
+
shared_examples_stash[name] = body
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
recordable alias_method :shared_examples_for, :shared_examples
|
|
363
|
+
recordable alias_method :shared_context, :shared_examples
|
|
364
|
+
|
|
365
|
+
def examples_included?(_name) = false
|
|
366
|
+
def included_examples = nil
|
|
367
|
+
|
|
368
|
+
recordable def include_examples(name, *args, **opts, &)
|
|
369
|
+
return if examples_included?(name)
|
|
370
|
+
unless singleton_class.method_defined?(:included_examples, false)
|
|
371
|
+
included = []
|
|
372
|
+
define_singleton_method(:examples_included?) { |name| included.include?(name) || super(name) }
|
|
373
|
+
define_singleton_method(:included_examples) { included }
|
|
374
|
+
end
|
|
375
|
+
class_exec(*args, **opts, &lookup_shared_examples(name))
|
|
376
|
+
included_examples << name
|
|
377
|
+
class_exec(&) if block_given?
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
recordable alias_method :include_context, :include_examples
|
|
381
|
+
|
|
382
|
+
recordable def it_behaves_like_with_label(name, label, *args, **opts, &)
|
|
383
|
+
context(label) do
|
|
384
|
+
include_examples(name, *args, **opts)
|
|
385
|
+
class_exec(&) if block_given?
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
module Meta
|
|
390
|
+
def def_it_behaves_like_shortcut(shortcut_name, make_label = ->(name) { "it behaves like #{name}" })
|
|
391
|
+
recordable shortcut_name
|
|
392
|
+
define_method(shortcut_name) do |name, *args, **opts, &blk|
|
|
393
|
+
it_behaves_like_with_label(name, make_label.call(name), *args, **opts, &blk)
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def_it_behaves_like_shortcut :it_behaves_like
|
|
399
|
+
recordable alias_method :it_should_behave_like, :it_behaves_like
|
|
400
|
+
|
|
401
|
+
recordable def let(method_name, &)
|
|
402
|
+
name = case
|
|
403
|
+
when method_name.end_with?('?') then :"rspec__#{method_name.name.chop}__p"
|
|
404
|
+
when method_name.end_with?('!') then :"rspec__#{method_name.name.chop}__d"
|
|
405
|
+
else :"rspec__#{method_name.name}"
|
|
406
|
+
end
|
|
407
|
+
define_method(:"#{name}__uncached", &)
|
|
408
|
+
module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
|
409
|
+
def #{method_name} = defined?(@#{name}) ? @#{name} : (@#{name} = #{name}__uncached)
|
|
410
|
+
RUBY
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
recordable def let!(name, &)
|
|
414
|
+
let(name, &)
|
|
415
|
+
before { public_send(name) }
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
recordable def subject(name = :subject, &)
|
|
419
|
+
let(name, &)
|
|
420
|
+
alias_method(:subject, name) if name != :subject
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
recordable def subject!(name = :subject, &)
|
|
424
|
+
subject(name, &)
|
|
425
|
+
before { public_send(name) }
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def wrap_hook_block_with_metadata_test(args, opts, blk, around: false)
|
|
429
|
+
filter = get_args_filter(args, opts)
|
|
430
|
+
return blk if filter.empty?
|
|
431
|
+
if around
|
|
432
|
+
->(h) { ExampleGroup.filter_matches?(filter, h.metadata) ? instance_exec(h, &blk) : h.run }
|
|
433
|
+
else
|
|
434
|
+
->(h) { instance_exec(h, &blk) if ExampleGroup.filter_matches?(filter, h.metadata) }
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def self.def_hook_adder(method_name, hook_name, around: false)
|
|
439
|
+
example_get_name = :"example_#{hook_name}"
|
|
440
|
+
example_set_name = :"example_#{hook_name}="
|
|
441
|
+
context_get_name = :"context_#{hook_name}"
|
|
442
|
+
context_set_name = :"context_#{hook_name}="
|
|
443
|
+
recordable method_name
|
|
444
|
+
define_method(method_name) do |scope = :example, *args, **opts, &new|
|
|
445
|
+
new = wrap_hook_block_with_metadata_test(args, opts, new, around:)
|
|
446
|
+
eg = self
|
|
447
|
+
case scope
|
|
448
|
+
when :example, :each
|
|
449
|
+
get_name, set_name = example_get_name, example_set_name
|
|
450
|
+
when :context, :all
|
|
451
|
+
get_name, set_name = context_get_name, context_set_name
|
|
452
|
+
when :suite
|
|
453
|
+
get_name, set_name = context_get_name, context_set_name
|
|
454
|
+
eg = superclass while eg >= ExampleGroup
|
|
455
|
+
else
|
|
456
|
+
raise ArgumentError, "unsupported scope: #{scope}"
|
|
457
|
+
end
|
|
458
|
+
eg.public_send(set_name, (old = public_send(get_name)) ? yield(new, old) : new)
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
def_hook_adder(:around, :around_hook, around: true) do |new, old|
|
|
463
|
+
->(h) { instance_exec(h.chain { instance_exec(h, &new) }, &old) }
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
def_hook_adder(:around_layer, :around_layer_hook, around: true) do |new, old|
|
|
467
|
+
->(h) { instance_exec(h.chain { instance_exec(h, &new) }, &old) }
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def_hook_adder(:before, :before_hook) do |new, old|
|
|
471
|
+
->(h) { instance_exec(h, &old); instance_exec(h, &new) }
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def_hook_adder(:prepend_before, :before_hook) do |new, old|
|
|
475
|
+
->(h) { instance_exec(h, &new); instance_exec(h, &old) }
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def_hook_adder(:after, :after_hook) do |new, old|
|
|
479
|
+
->(h) { begin instance_exec(h, &new) ensure instance_exec(h.caught($!), &old) end }
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def_hook_adder(:append_after, :after_hook) do |new, old|
|
|
483
|
+
->(h) { begin instance_exec(h, &old) ensure instance_exec(h.caught($!), &new) end }
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
class ExampleGroup
|
|
488
|
+
extend Element
|
|
489
|
+
extend ExampleGroupInternal
|
|
490
|
+
extend ExampleGroupHooks
|
|
491
|
+
extend ExampleGroupDSL
|
|
492
|
+
include ExampleGroupInstanceInternal
|
|
493
|
+
include Ruptr::TestInstance
|
|
494
|
+
include ExampleGroupInstanceUserMethods
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
end
|
|
498
|
+
end
|