mutant 0.8.10 → 0.8.11
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 +4 -4
- data/.rubocop.yml +1 -4
- data/Changelog.md +8 -0
- data/README.md +112 -43
- data/Rakefile +2 -16
- data/circle.yml +1 -1
- data/config/flay.yml +2 -2
- data/config/flog.yml +1 -1
- data/config/reek.yml +3 -4
- data/config/rubocop.yml +53 -16
- data/lib/mutant.rb +27 -6
- data/lib/mutant/ast/meta/const.rb +2 -0
- data/lib/mutant/ast/meta/optarg.rb +2 -0
- data/lib/mutant/ast/meta/resbody.rb +2 -0
- data/lib/mutant/ast/meta/restarg.rb +2 -0
- data/lib/mutant/ast/meta/send.rb +4 -0
- data/lib/mutant/ast/meta/symbol.rb +2 -0
- data/lib/mutant/ast/named_children.rb +14 -4
- data/lib/mutant/ast/nodes.rb +3 -1
- data/lib/mutant/ast/regexp.rb +53 -0
- data/lib/mutant/ast/regexp/transformer.rb +185 -0
- data/lib/mutant/ast/regexp/transformer/alternative.rb +39 -0
- data/lib/mutant/ast/regexp/transformer/character_set.rb +46 -0
- data/lib/mutant/ast/regexp/transformer/direct.rb +99 -0
- data/lib/mutant/ast/regexp/transformer/options_group.rb +66 -0
- data/lib/mutant/ast/regexp/transformer/quantifier.rb +112 -0
- data/lib/mutant/ast/regexp/transformer/recursive.rb +50 -0
- data/lib/mutant/ast/regexp/transformer/root.rb +29 -0
- data/lib/mutant/ast/regexp/transformer/text.rb +55 -0
- data/lib/mutant/ast/types.rb +92 -5
- data/lib/mutant/cli.rb +2 -14
- data/lib/mutant/color.rb +1 -1
- data/lib/mutant/config.rb +1 -3
- data/lib/mutant/expression/methods.rb +1 -1
- data/lib/mutant/expression/namespace.rb +2 -2
- data/lib/mutant/expression/parser.rb +1 -1
- data/lib/mutant/integration.rb +10 -28
- data/lib/mutant/isolation.rb +9 -60
- data/lib/mutant/isolation/fork.rb +72 -0
- data/lib/mutant/isolation/none.rb +25 -0
- data/lib/mutant/matcher/config.rb +2 -0
- data/lib/mutant/matcher/method/singleton.rb +5 -4
- data/lib/mutant/meta.rb +11 -4
- data/lib/mutant/meta/example.rb +2 -116
- data/lib/mutant/meta/example/dsl.rb +22 -19
- data/lib/mutant/meta/example/verification.rb +86 -0
- data/lib/mutant/mutator.rb +22 -49
- data/lib/mutant/mutator/node.rb +15 -19
- data/lib/mutant/mutator/node/and_asgn.rb +1 -1
- data/lib/mutant/mutator/node/argument.rb +10 -5
- data/lib/mutant/mutator/node/arguments.rb +5 -9
- data/lib/mutant/mutator/node/begin.rb +4 -17
- data/lib/mutant/mutator/node/block.rb +1 -1
- data/lib/mutant/mutator/node/break.rb +1 -1
- data/lib/mutant/mutator/node/class.rb +21 -0
- data/lib/mutant/mutator/node/conditional_loop.rb +1 -1
- data/lib/mutant/mutator/node/define.rb +1 -1
- data/lib/mutant/mutator/node/defined.rb +1 -1
- data/lib/mutant/mutator/node/dstr.rb +1 -1
- data/lib/mutant/mutator/node/dsym.rb +1 -1
- data/lib/mutant/mutator/node/generic.rb +3 -3
- data/lib/mutant/mutator/node/kwbegin.rb +1 -1
- data/lib/mutant/mutator/node/literal.rb +9 -0
- data/lib/mutant/mutator/node/literal/boolean.rb +1 -1
- data/lib/mutant/mutator/node/literal/fixnum.rb +2 -2
- data/lib/mutant/mutator/node/literal/float.rb +4 -6
- data/lib/mutant/mutator/node/literal/hash.rb +1 -1
- data/lib/mutant/mutator/node/literal/nil.rb +1 -1
- data/lib/mutant/mutator/node/literal/range.rb +2 -19
- data/lib/mutant/mutator/node/literal/regex.rb +43 -3
- data/lib/mutant/mutator/node/literal/string.rb +1 -1
- data/lib/mutant/mutator/node/literal/symbol.rb +2 -4
- data/lib/mutant/mutator/node/masgn.rb +1 -1
- data/lib/mutant/mutator/node/match_current_line.rb +1 -1
- data/lib/mutant/mutator/node/mlhs.rb +2 -3
- data/lib/mutant/mutator/node/named_value/access.rb +2 -2
- data/lib/mutant/mutator/node/named_value/constant_assignment.rb +4 -3
- data/lib/mutant/mutator/node/named_value/variable_assignment.rb +4 -4
- data/lib/mutant/mutator/node/next.rb +1 -1
- data/lib/mutant/mutator/node/nthref.rb +1 -1
- data/lib/mutant/mutator/node/or_asgn.rb +1 -1
- data/lib/mutant/mutator/node/regexp.rb +44 -0
- data/lib/mutant/mutator/node/regopt.rb +31 -0
- data/lib/mutant/mutator/node/resbody.rb +1 -1
- data/lib/mutant/mutator/node/rescue.rb +1 -3
- data/lib/mutant/mutator/node/return.rb +1 -1
- data/lib/mutant/mutator/node/send.rb +43 -3
- data/lib/mutant/mutator/node/send/attribute_assignment.rb +4 -1
- data/lib/mutant/mutator/node/send/conditional.rb +23 -0
- data/lib/mutant/mutator/node/send/index.rb +1 -1
- data/lib/mutant/mutator/node/splat.rb +1 -1
- data/lib/mutant/mutator/node/when.rb +1 -1
- data/lib/mutant/mutator/node/yield.rb +1 -1
- data/lib/mutant/mutator/util.rb +0 -30
- data/lib/mutant/mutator/util/array.rb +4 -16
- data/lib/mutant/parallel.rb +1 -1
- data/lib/mutant/parallel/worker.rb +1 -1
- data/lib/mutant/registry.rb +44 -0
- data/lib/mutant/reporter/cli/format.rb +2 -0
- data/lib/mutant/reporter/cli/printer.rb +2 -2
- data/lib/mutant/reporter/cli/printer/config.rb +0 -1
- data/lib/mutant/reporter/cli/printer/env_progress.rb +1 -11
- data/lib/mutant/reporter/cli/printer/mutation_progress_result.rb +1 -1
- data/lib/mutant/reporter/cli/printer/mutation_result.rb +2 -0
- data/lib/mutant/reporter/cli/tput.rb +1 -1
- data/lib/mutant/reporter/sequence.rb +3 -0
- data/lib/mutant/require_highjack.rb +6 -2
- data/lib/mutant/result.rb +1 -1
- data/lib/mutant/subject.rb +5 -5
- data/lib/mutant/subject/method/instance.rb +1 -2
- data/lib/mutant/util.rb +18 -0
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/zombifier.rb +5 -3
- data/meta/and.rb +1 -1
- data/meta/and_asgn.rb +1 -1
- data/meta/array.rb +2 -2
- data/meta/begin.rb +2 -2
- data/meta/block.rb +7 -7
- data/meta/block_pass.rb +1 -1
- data/meta/blockarg.rb +1 -1
- data/meta/break.rb +1 -1
- data/meta/case.rb +2 -2
- data/meta/casgn.rb +11 -2
- data/meta/cbase.rb +1 -1
- data/meta/class.rb +10 -0
- data/meta/const.rb +9 -1
- data/meta/csend.rb +8 -0
- data/meta/cvar.rb +1 -1
- data/meta/cvasgn.rb +1 -1
- data/meta/date.rb +4 -4
- data/meta/def.rb +14 -14
- data/meta/defined.rb +1 -1
- data/meta/dstr.rb +1 -1
- data/meta/dsym.rb +1 -1
- data/meta/ensure.rb +1 -1
- data/meta/false.rb +1 -1
- data/meta/float.rb +3 -3
- data/meta/gvar.rb +1 -1
- data/meta/gvasgn.rb +1 -1
- data/meta/hash.rb +1 -1
- data/meta/if.rb +17 -3
- data/meta/int.rb +1 -1
- data/meta/ivar.rb +1 -1
- data/meta/ivasgn.rb +14 -1
- data/meta/kwarg.rb +8 -0
- data/meta/kwbegin.rb +1 -1
- data/meta/kwoptarg.rb +11 -0
- data/meta/lvar.rb +1 -1
- data/meta/lvasgn.rb +1 -1
- data/meta/masgn.rb +1 -1
- data/meta/match_current_line.rb +2 -2
- data/meta/next.rb +1 -1
- data/meta/nil.rb +1 -1
- data/meta/nthref.rb +5 -5
- data/meta/op_assgn.rb +1 -1
- data/meta/or.rb +1 -1
- data/meta/or_asgn.rb +5 -5
- data/meta/range.rb +2 -8
- data/meta/redo.rb +1 -1
- data/meta/regexp.rb +106 -0
- data/meta/regexp/regexp_bol_anchor.rb +13 -0
- data/meta/regexp/regexp_bos_anchor.rb +26 -0
- data/meta/regexp/regexp_root_expression.rb +13 -0
- data/meta/regopt.rb +8 -0
- data/meta/rescue.rb +49 -4
- data/meta/restarg.rb +6 -9
- data/meta/return.rb +2 -2
- data/meta/self.rb +1 -1
- data/meta/send.rb +228 -55
- data/meta/str.rb +1 -1
- data/meta/super.rb +3 -3
- data/meta/{symbol.rb → sym.rb} +1 -1
- data/meta/true.rb +1 -1
- data/meta/until.rb +1 -1
- data/meta/while.rb +2 -2
- data/meta/yield.rb +1 -1
- data/mutant-rspec.gemspec +2 -2
- data/mutant.gemspec +6 -5
- data/spec/integration/mutant/isolation/fork_spec.rb +8 -0
- data/spec/integration/mutant/rspec_spec.rb +1 -1
- data/spec/integration/mutant/test_mutator_handles_types_spec.rb +1 -2
- data/spec/integrations.yml +93 -24
- data/spec/spec_helper.rb +12 -7
- data/spec/support/compress_helper.rb +1 -1
- data/spec/support/corpus.rb +115 -50
- data/spec/support/fake_actor.rb +5 -5
- data/spec/support/file_system.rb +1 -1
- data/spec/support/ice_nine_config.rb +3 -3
- data/spec/support/ruby_vm.rb +11 -12
- data/spec/support/shared_context.rb +22 -13
- data/spec/support/test_app.rb +1 -1
- data/spec/support/warning.rb +64 -0
- data/spec/support/warnings.yml +4 -0
- data/spec/support/xspec.rb +177 -0
- data/spec/unit/mutant/actor/env_spec.rb +2 -2
- data/spec/unit/mutant/actor/sender_spec.rb +1 -1
- data/spec/unit/mutant/ast/meta/send_spec.rb +1 -1
- data/spec/unit/mutant/ast/named_children_spec.rb +26 -0
- data/spec/unit/mutant/ast/regexp/parse_spec.rb +7 -0
- data/spec/unit/mutant/ast/regexp/supported_predicate_spec.rb +14 -0
- data/spec/unit/mutant/ast/regexp/transformer/lookup_table/table_spec.rb +19 -0
- data/spec/unit/mutant/ast/regexp/transformer/lookup_table_spec.rb +33 -0
- data/spec/unit/mutant/ast/regexp/transformer_spec.rb +19 -0
- data/spec/unit/mutant/ast/regexp_spec.rb +617 -0
- data/spec/unit/mutant/cli_spec.rb +7 -45
- data/spec/unit/mutant/context_spec.rb +4 -7
- data/spec/unit/mutant/env/{boostrap_spec.rb → bootstrap_spec.rb} +2 -2
- data/spec/unit/mutant/env_spec.rb +13 -16
- data/spec/unit/mutant/expression/namespace/{flat_spec.rb → exact_spec.rb} +0 -0
- data/spec/unit/mutant/integration/rspec_spec.rb +2 -2
- data/spec/unit/mutant/integration_spec.rb +14 -0
- data/spec/unit/mutant/isolation/fork_spec.rb +155 -0
- data/spec/unit/mutant/isolation/none_spec.rb +16 -0
- data/spec/unit/mutant/loader_spec.rb +1 -1
- data/spec/unit/mutant/matcher/methods/instance_spec.rb +2 -4
- data/spec/unit/mutant/meta/example/dsl_spec.rb +106 -0
- data/spec/unit/mutant/meta/example/verification_spec.rb +120 -0
- data/spec/unit/mutant/meta/example_spec.rb +32 -0
- data/spec/unit/mutant/mutator/node_spec.rb +37 -4
- data/spec/unit/mutant/mutator_spec.rb +21 -0
- data/spec/unit/mutant/{runner → parallel}/driver_spec.rb +0 -0
- data/spec/unit/mutant/parallel/master_spec.rb +13 -13
- data/spec/unit/mutant/registry_spec.rb +47 -0
- data/spec/unit/mutant/reporter/cli/printer/config_spec.rb +0 -4
- data/spec/unit/mutant/reporter/cli/printer/env_progress_spec.rb +0 -8
- data/spec/unit/mutant/reporter/cli/printer/env_result_spec.rb +0 -2
- data/spec/unit/mutant/reporter/cli/printer/status_progressive_spec.rb +0 -8
- data/spec/unit/mutant/reporter/cli/printer/status_spec.rb +0 -34
- data/spec/unit/mutant/reporter/cli_spec.rb +0 -22
- data/spec/unit/mutant/repository/diff_spec.rb +6 -6
- data/spec/unit/mutant/require_highjack_spec.rb +38 -14
- data/spec/unit/mutant/result/env_spec.rb +1 -4
- data/spec/unit/mutant/runner_spec.rb +1 -1
- data/spec/unit/mutant/subject/method/instance_spec.rb +1 -1
- data/spec/unit/mutant/subject_spec.rb +3 -3
- data/spec/unit/mutant/util/one_spec.rb +20 -0
- data/spec/unit/mutant/zombifier_spec.rb +18 -18
- data/test_app/{Gemfile.rspec3.3 → Gemfile.rspec3.5} +2 -2
- metadata +94 -24
- data/TODO +0 -21
- data/lib/mutant/mutator/node/blockarg.rb +0 -13
- data/lib/mutant/mutator/node/restarg.rb +0 -13
- data/lib/mutant/mutator/registry.rb +0 -49
- data/meta/boolean.rb +0 -13
- data/meta/regex.rb +0 -19
- data/spec/unit/mutant/isolation_spec.rb +0 -104
- data/spec/unit/mutant/mutator/registry_spec.rb +0 -57
data/spec/support/fake_actor.rb
CHANGED
@@ -16,7 +16,7 @@ module FakeActor
|
|
16
16
|
end
|
17
17
|
block.call(other.message) if block
|
18
18
|
end
|
19
|
-
end
|
19
|
+
end # Expectation
|
20
20
|
|
21
21
|
class MessageSequence
|
22
22
|
include Adamantium::Flat, Concord::Public.new(:messages)
|
@@ -47,7 +47,7 @@ module FakeActor
|
|
47
47
|
def consumed?
|
48
48
|
messages.empty?
|
49
49
|
end
|
50
|
-
end
|
50
|
+
end # MessageSequence
|
51
51
|
|
52
52
|
class Env
|
53
53
|
include Concord.new(:messages, :mailbox_names)
|
@@ -89,7 +89,7 @@ module FakeActor
|
|
89
89
|
def bind(sender)
|
90
90
|
Mutant::Actor::Binding.new(self, sender)
|
91
91
|
end
|
92
|
-
end
|
92
|
+
end # Mailbox
|
93
93
|
|
94
94
|
class Sender
|
95
95
|
include Concord.new(:name, :messages)
|
@@ -105,5 +105,5 @@ module FakeActor
|
|
105
105
|
def call
|
106
106
|
messages.receiving(name)
|
107
107
|
end
|
108
|
-
end
|
109
|
-
end
|
108
|
+
end # Receiver
|
109
|
+
end # FakeActor
|
data/spec/support/file_system.rb
CHANGED
data/spec/support/ruby_vm.rb
CHANGED
@@ -3,18 +3,19 @@ module MutantSpec
|
|
3
3
|
# require semantics Zombifier relies on in a way we can avoid having to
|
4
4
|
# mock around everywhere to test every detail.
|
5
5
|
class RubyVM
|
6
|
-
include Concord.new(:expected_events)
|
6
|
+
include Concord::Public.new(:expected_events)
|
7
7
|
|
8
8
|
# An event being observed by the VM handlers
|
9
9
|
class EventObservation
|
10
10
|
include Concord::Public.new(:type, :payload)
|
11
|
-
end
|
11
|
+
end # EventObservation
|
12
12
|
|
13
13
|
# An event being expected, can advance the VM
|
14
14
|
class EventExpectation
|
15
15
|
include AbstractType, Anima.new(
|
16
16
|
:expected_payload,
|
17
|
-
:trigger_requires
|
17
|
+
:trigger_requires,
|
18
|
+
:return_value
|
18
19
|
)
|
19
20
|
|
20
21
|
DEFAULTS = IceNine.deep_freeze(trigger_requires: [])
|
@@ -29,29 +30,28 @@ module MutantSpec
|
|
29
30
|
end
|
30
31
|
|
31
32
|
trigger_requires.each(&vm.method(:require))
|
33
|
+
|
34
|
+
self
|
32
35
|
end
|
33
36
|
|
34
37
|
private
|
35
38
|
|
36
|
-
abstract_method :advance_vm
|
37
|
-
|
38
39
|
def match?(observation)
|
39
40
|
observation.type.eql?(self.class) && observation.payload.eql?(expected_payload)
|
40
41
|
end
|
41
42
|
|
42
43
|
# Expectation and advance on require calls
|
43
44
|
class Require < self
|
44
|
-
end
|
45
|
+
end # Require
|
45
46
|
|
46
47
|
# Expectation and advance on eval calls
|
47
48
|
class Eval < self
|
48
|
-
end
|
49
|
-
end
|
49
|
+
end # Eval
|
50
|
+
end # EventExpectation
|
50
51
|
|
51
52
|
# A fake implementation of Kernel#require
|
52
53
|
def require(logical_name)
|
53
54
|
handle_event(EventObservation.new(EventExpectation::Require, logical_name: logical_name))
|
54
|
-
self
|
55
55
|
end
|
56
56
|
|
57
57
|
# A fake implementation of Kernel#eval
|
@@ -64,7 +64,6 @@ module MutantSpec
|
|
64
64
|
source_location: location
|
65
65
|
)
|
66
66
|
)
|
67
|
-
self
|
68
67
|
end
|
69
68
|
|
70
69
|
# Test if VM events where fully processed
|
@@ -77,7 +76,7 @@ module MutantSpec
|
|
77
76
|
def handle_event(observation)
|
78
77
|
fail "Unexpected event: #{observation.type} / #{observation.payload}" if expected_events.empty?
|
79
78
|
|
80
|
-
expected_events.slice!(0).handle(self, observation)
|
79
|
+
expected_events.slice!(0).handle(self, observation).return_value
|
81
80
|
end
|
82
|
-
end
|
81
|
+
end # RubyVM
|
83
82
|
end # MutantSpec
|
@@ -1,9 +1,18 @@
|
|
1
1
|
# rubocop:disable ModuleLength
|
2
2
|
module SharedContext
|
3
|
+
# Prepend an anonymous module with the new `with` method
|
4
|
+
#
|
5
|
+
# Using an anonymous module eliminates warnings where `setup_shared_context`
|
6
|
+
# is used and one of the shared methods is immediately redefined.
|
3
7
|
def with(name, &block)
|
4
|
-
|
5
|
-
|
6
|
-
|
8
|
+
new_definition =
|
9
|
+
Module.new do
|
10
|
+
define_method(name) do
|
11
|
+
super().with(instance_eval(&block))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
prepend(new_definition)
|
7
16
|
end
|
8
17
|
|
9
18
|
def messages(&block)
|
@@ -52,8 +61,8 @@ module SharedContext
|
|
52
61
|
|
53
62
|
let(:config) do
|
54
63
|
Mutant::Config::DEFAULT.with(
|
55
|
-
jobs:
|
56
|
-
reporter:
|
64
|
+
jobs: 1,
|
65
|
+
reporter: Mutant::Reporter::Null.new
|
57
66
|
)
|
58
67
|
end
|
59
68
|
|
@@ -94,19 +103,19 @@ module SharedContext
|
|
94
103
|
|
95
104
|
let(:mutation_a_test_result) do
|
96
105
|
Mutant::Result::Test.new(
|
97
|
-
tests:
|
98
|
-
passed:
|
99
|
-
runtime:
|
106
|
+
tests: [test_a],
|
107
|
+
passed: false,
|
108
|
+
runtime: 1.0,
|
100
109
|
output: 'mutation a test result output'
|
101
110
|
)
|
102
111
|
end
|
103
112
|
|
104
113
|
let(:mutation_b_test_result) do
|
105
114
|
Mutant::Result::Test.new(
|
106
|
-
tests:
|
107
|
-
passed:
|
108
|
-
runtime:
|
109
|
-
output:
|
115
|
+
tests: [test_a],
|
116
|
+
passed: false,
|
117
|
+
runtime: 1.0,
|
118
|
+
output: 'mutation b test result output'
|
110
119
|
)
|
111
120
|
end
|
112
121
|
|
@@ -126,4 +135,4 @@ module SharedContext
|
|
126
135
|
subject_a_result.with(mutation_results: [mutation_a_result])
|
127
136
|
end
|
128
137
|
end
|
129
|
-
end
|
138
|
+
end # SharedContext
|
data/spec/support/test_app.rb
CHANGED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'equalizer'
|
3
|
+
require 'memoizable'
|
4
|
+
require 'ice_nine'
|
5
|
+
|
6
|
+
module MutantSpec
|
7
|
+
class Warning
|
8
|
+
def self.assert_no_warnings
|
9
|
+
return if EXTRACTOR.warnings.empty?
|
10
|
+
|
11
|
+
fail UnexpectedWarnings, EXTRACTOR.warnings.to_a
|
12
|
+
end
|
13
|
+
|
14
|
+
class UnexpectedWarnings < StandardError
|
15
|
+
MSG = 'Unexpected warnings: %s'.freeze
|
16
|
+
|
17
|
+
def initialize(warnings)
|
18
|
+
super(MSG % warnings.join("\n"))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Extractor < DelegateClass(IO)
|
23
|
+
PATTERN = /\A(?:.+):(?:\d+): warning: (?:.+)\n\z/.freeze
|
24
|
+
|
25
|
+
include Equalizer.new(:whitelist, :seen, :io), Memoizable
|
26
|
+
|
27
|
+
def initialize(io, whitelist)
|
28
|
+
@whitelist = whitelist
|
29
|
+
@seen = Set.new
|
30
|
+
@io = io
|
31
|
+
|
32
|
+
super(io)
|
33
|
+
end
|
34
|
+
|
35
|
+
def write(message)
|
36
|
+
return super if PATTERN !~ message
|
37
|
+
|
38
|
+
add(message.chomp)
|
39
|
+
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def warnings
|
44
|
+
seen.dup
|
45
|
+
end
|
46
|
+
memoize :warnings
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def add(warning)
|
51
|
+
return if whitelist.any?(&warning.public_method(:end_with?))
|
52
|
+
|
53
|
+
seen << warning
|
54
|
+
end
|
55
|
+
|
56
|
+
attr_reader :whitelist, :seen, :io
|
57
|
+
end
|
58
|
+
|
59
|
+
warnings = Pathname.new(__dir__).join('warnings.yml').freeze
|
60
|
+
whitelist = IceNine.deep_freeze(YAML.load(warnings.read))
|
61
|
+
|
62
|
+
EXTRACTOR = Extractor.new(STDERR, whitelist)
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
module XSpec
|
2
|
+
class MessageReaction
|
3
|
+
include Concord.new(:event_list)
|
4
|
+
|
5
|
+
TERMINATE_EVENTS = IceNine.deep_freeze(%i[return exception].to_set)
|
6
|
+
VALID_EVENTS = IceNine.deep_freeze(%i[return exception yields].to_set)
|
7
|
+
|
8
|
+
private_constant(*constants(false))
|
9
|
+
|
10
|
+
def call(observation)
|
11
|
+
event_list.map do |event, object|
|
12
|
+
__send__(event, observation, object)
|
13
|
+
end.last
|
14
|
+
end
|
15
|
+
|
16
|
+
# Parse events into reaction
|
17
|
+
#
|
18
|
+
# @param [Array{Symbol,Object}, Hash{Symbol,Object}]
|
19
|
+
#
|
20
|
+
# @return [MessageReaction]
|
21
|
+
def self.parse(events)
|
22
|
+
event_list = events.to_a
|
23
|
+
assert_valid(event_list)
|
24
|
+
new(event_list)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def return(_, value)
|
30
|
+
value
|
31
|
+
end
|
32
|
+
|
33
|
+
def exception(_, exception)
|
34
|
+
fail exception
|
35
|
+
end
|
36
|
+
|
37
|
+
def yields(observation, yields)
|
38
|
+
block = observation.block or fail 'No block passed where expected'
|
39
|
+
|
40
|
+
validate_block_arity(observation, yields)
|
41
|
+
|
42
|
+
block.call(*yields)
|
43
|
+
end
|
44
|
+
|
45
|
+
def validate_block_arity(observation, yields)
|
46
|
+
expected, observed = yields.length, observation.block.arity
|
47
|
+
|
48
|
+
# block allows anything we can skip the check
|
49
|
+
return if observed.equal?(-1)
|
50
|
+
fail 'Optargs currently not supported' if observed < -1
|
51
|
+
block_arity_mismatch(observation, expected, observed) unless expected.equal?(observed)
|
52
|
+
end
|
53
|
+
|
54
|
+
def block_arity_mismatch(observation, expected, observed)
|
55
|
+
fail "block arity mismatch, expected #{expected} observed #{observed}\nobservation:\n#{observation.inspect}"
|
56
|
+
end
|
57
|
+
|
58
|
+
alias_method :yields_return, :yields
|
59
|
+
|
60
|
+
def self.assert_valid(event_list)
|
61
|
+
assert_not_empty(event_list)
|
62
|
+
assert_valid_events(event_list)
|
63
|
+
assert_total(event_list)
|
64
|
+
end
|
65
|
+
private_class_method :assert_valid
|
66
|
+
|
67
|
+
def self.assert_valid_events(event_list)
|
68
|
+
event_list.map(&:first).each do |event|
|
69
|
+
fail "Invalid event: #{event}" unless VALID_EVENTS.include?(event)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
private_class_method :assert_valid_events
|
73
|
+
|
74
|
+
def self.assert_not_empty(event_list)
|
75
|
+
fail 'no events' if event_list.empty?
|
76
|
+
end
|
77
|
+
private_class_method :assert_not_empty
|
78
|
+
|
79
|
+
def self.assert_total(event_list)
|
80
|
+
if event_list[0..-2].map(&:first).any?(&TERMINATE_EVENTS.method(:include?))
|
81
|
+
fail "Reaction not total: #{event_list}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
private_class_method :assert_total
|
85
|
+
end # MessageReaction
|
86
|
+
|
87
|
+
class MessageExpectation
|
88
|
+
include Anima.new(:receiver, :selector, :arguments, :reaction)
|
89
|
+
|
90
|
+
# rubocop:disable ParameterLists
|
91
|
+
def self.parse(receiver:, selector:, arguments: [], reaction: nil)
|
92
|
+
new(
|
93
|
+
receiver: receiver,
|
94
|
+
selector: selector,
|
95
|
+
arguments: arguments,
|
96
|
+
reaction: MessageReaction.parse(reaction || { return: nil })
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
def call(observation)
|
101
|
+
Verifier.new(self, observation).call
|
102
|
+
end
|
103
|
+
|
104
|
+
class Verifier
|
105
|
+
include Concord.new(:expectation, :observation)
|
106
|
+
|
107
|
+
VERIFIED_ATTRIBUTES = IceNine.deep_freeze(%i[receiver selector arguments])
|
108
|
+
|
109
|
+
def call
|
110
|
+
VERIFIED_ATTRIBUTES.each(&method(:assert_expected_attribute))
|
111
|
+
|
112
|
+
expectation.reaction.call(observation)
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def assert_expected_attribute(name)
|
118
|
+
error("#{name} mismatch") unless observation.public_send(name).eql?(expectation.public_send(name))
|
119
|
+
end
|
120
|
+
|
121
|
+
def error(message)
|
122
|
+
fail "#{message},\n observation:\n #{observation.inspect}\n expectation:\n #{expectation.inspect}"
|
123
|
+
end
|
124
|
+
|
125
|
+
def trigger_exception
|
126
|
+
exception = expectation.exception
|
127
|
+
fail exception if exception
|
128
|
+
end
|
129
|
+
|
130
|
+
end # Verifier
|
131
|
+
end # MessageExpectation
|
132
|
+
|
133
|
+
class MessageObservation
|
134
|
+
include Anima.new(:receiver, :selector, :arguments, :block)
|
135
|
+
end # MessageObservation
|
136
|
+
|
137
|
+
class ExpectationVerifier
|
138
|
+
include Concord.new(:expectations)
|
139
|
+
|
140
|
+
def call(observation)
|
141
|
+
expectation = expectations.shift or fail "No expected message but observed #{observation}"
|
142
|
+
expectation.call(observation)
|
143
|
+
end
|
144
|
+
|
145
|
+
def assert_done
|
146
|
+
expectations.empty? or fail "unconsumed expectations:\n#{expectations.map(&:inspect).join}"
|
147
|
+
end
|
148
|
+
|
149
|
+
# rubocop:disable MethodLength
|
150
|
+
def self.verify(rspec_context, expectations)
|
151
|
+
verifier = new(expectations)
|
152
|
+
|
153
|
+
hooks = expectations
|
154
|
+
.map { |expectation| [expectation.receiver, expectation.selector] }
|
155
|
+
.to_set
|
156
|
+
|
157
|
+
hooks.each do |receiver, selector|
|
158
|
+
rspec_context.instance_eval do
|
159
|
+
allow(receiver).to receive(selector) do |*arguments, &block|
|
160
|
+
verifier.call(
|
161
|
+
MessageObservation.new(
|
162
|
+
receiver: receiver,
|
163
|
+
selector: selector,
|
164
|
+
arguments: arguments,
|
165
|
+
block: block
|
166
|
+
)
|
167
|
+
)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
yield
|
173
|
+
|
174
|
+
verifier.assert_done
|
175
|
+
end
|
176
|
+
end # ExpectationVerifier
|
177
|
+
end # XSpec
|