Skip to content

Commit a69c92f

Browse files
authored
Merge pull request #895 from mbj/refactor/bootstrap
Refactor bootstrap
2 parents 13a7f29 + da69eca commit a69c92f

21 files changed

+189
-133
lines changed

lib/mutant.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ def self.ci?
161161
require 'mutant/integration'
162162
require 'mutant/selector'
163163
require 'mutant/selector/expression'
164+
require 'mutant/selector/null'
164165
require 'mutant/config'
165166
require 'mutant/cli'
166167
require 'mutant/color'

lib/mutant/env.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,25 @@ class Env
1818
"Fix your lib to follow normal ruby semantics!\n" \
1919
'{Module,Class}#name should return resolvable constant name as String or nil'
2020

21+
# Construct minimal empty env
22+
#
23+
# @param [World] world
24+
# @param [Config] config
25+
#
26+
# @return [Env]
27+
def self.empty(world, config)
28+
new(
29+
config: config,
30+
integration: Integration::Null.new(config),
31+
matchable_scopes: EMPTY_ARRAY,
32+
mutations: EMPTY_ARRAY,
33+
parser: Parser.new,
34+
selector: Selector::Null.new,
35+
subjects: EMPTY_ARRAY,
36+
world: world
37+
)
38+
end
39+
2140
# Kill mutation
2241
#
2342
# @param [Mutation] mutation

lib/mutant/env/bootstrap.rb

Lines changed: 51 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
module Mutant
44
class Env
55
# Bootstrap environment
6-
class Bootstrap
7-
include Adamantium::Flat, Concord::Public.new(:world, :config)
6+
module Bootstrap
7+
include Adamantium::Flat, Anima.new(:config, :parser, :world)
88

99
SEMANTICS_MESSAGE_FORMAT =
1010
"%<message>s. Fix your lib to follow normal ruby semantics!\n" \
@@ -18,72 +18,53 @@ class Bootstrap
1818

1919
private_constant(*constants(false))
2020

21-
# Scopes that are eligible for matching
22-
#
23-
# @return [Enumerable<Matcher::Scope>]
24-
attr_reader :matchable_scopes
25-
26-
# Parser for this environment
27-
#
28-
# @return [Parser]
29-
attr_reader :parser
30-
3121
# Run Bootstrap
3222
#
3323
# @param [World] world
3424
# @param [Config] config
3525
#
3626
# @return [Env]
3727
def self.call(world, config)
38-
new(world, config).env
28+
env = Env.empty(world, config).tap(&method(:infect))
29+
env = env.with(matchable_scopes: matchable_scopes(env))
30+
subjects = Matcher::Compiler.call(config.matcher).call(env)
31+
integration = config.integration.new(config).setup
32+
33+
env.with(
34+
integration: integration,
35+
mutations: subjects.flat_map(&:mutations),
36+
selector: Selector::Expression.new(integration),
37+
subjects: subjects
38+
)
3939
end
4040

41-
# Initialize object
41+
# Infect environment
4242
#
43-
# @return [Object]
44-
def initialize(*)
45-
super
46-
@parser = Parser.new
47-
infect
48-
initialize_matchable_scopes
49-
@integration = config.integration.new(config).setup
50-
end
43+
# @return [undefined]
44+
def self.infect(env)
45+
config, world = env.config, env.world
5146

52-
# Print warning message
53-
#
54-
# @param [String]
55-
#
56-
# @return [self]
57-
def warn(message)
58-
config.reporter.warn(message)
59-
self
47+
config.includes.each(&world.load_path.method(:<<))
48+
config.requires.each(&world.kernel.method(:require))
6049
end
50+
private_class_method :infect
6151

62-
# Environment after bootstraping
52+
# Matchable scopes
6353
#
64-
# @return [Env]
65-
# rubocop:disable MethodLength
54+
# @param [Env] env
6655
#
67-
def env
68-
subjects = matched_subjects
69-
Env.new(
70-
config: config,
71-
integration: integration,
72-
matchable_scopes: matchable_scopes,
73-
mutations: subjects.flat_map(&:mutations),
74-
parser: parser,
75-
selector: Selector::Expression.new(integration),
76-
subjects: subjects,
77-
world: world
78-
)
79-
end
56+
# @return [Array<Scope>]
57+
def self.matchable_scopes(env)
58+
config = env.config
8059

81-
private
60+
scopes = env.world.object_space.each_object(Module).each_with_object([]) do |scope, aggregate|
61+
expression = expression(config.reporter, config.expression_parser, scope) || next
62+
aggregate << Scope.new(scope, expression)
63+
end
8264

83-
# Configured mutant integration
84-
#
85-
# @return [Mutant::Integration]
86-
attr_reader :integration
65+
scopes.sort_by { |scope| scope.expression.syntax }
66+
end
67+
private_class_method :matchable_scopes
8768

8869
# Scope name from scoping object
8970
#
@@ -94,59 +75,40 @@ def env
9475
#
9576
# @return [nil]
9677
# otherwise
97-
def scope_name(scope)
78+
def self.scope_name(reporter, scope)
9879
scope.name
9980
rescue => exception
10081
semantics_warning(
82+
reporter,
10183
CLASS_NAME_RAISED_EXCEPTION,
10284
exception: exception.inspect,
10385
scope: scope,
10486
scope_class: scope.class
10587
)
10688
nil
10789
end
108-
109-
# Infect environment
110-
#
111-
# @return [undefined]
112-
def infect
113-
config.includes.each(&world.load_path.method(:<<))
114-
config.requires.each(&world.kernel.method(:require))
115-
end
116-
117-
# Matched subjects
118-
#
119-
# @return [Enumerable<Subject>]
120-
def matched_subjects
121-
Matcher::Compiler.call(config.matcher).call(self)
122-
end
123-
124-
# Initialize matchable scopes
125-
#
126-
# @return [undefined]
127-
def initialize_matchable_scopes
128-
scopes = world.object_space.each_object(Module).each_with_object([]) do |scope, aggregate|
129-
expression = expression(scope) || next
130-
aggregate << Scope.new(scope, expression)
131-
end
132-
133-
@matchable_scopes = scopes.sort_by { |scope| scope.expression.syntax }
134-
end
90+
private_class_method :scope_name
13591

13692
# Try to turn scope into expression
13793
#
94+
# @param [Expression::Parser] expression_parser
13895
# @param [Class, Module] scope
13996
#
14097
# @return [Expression]
14198
# if scope can be represented in an expression
14299
#
143100
# @return [nil]
144101
# otherwise
145-
def expression(scope)
146-
name = scope_name(scope) or return
102+
#
103+
# rubocop:disable Metrics/MethodLength
104+
#
105+
# ignore :reek:LongParameterList
106+
def self.expression(reporter, expression_parser, scope)
107+
name = scope_name(reporter, scope) or return
147108

148109
unless name.instance_of?(String)
149110
semantics_warning(
111+
reporter,
150112
CLASS_NAME_TYPE_MISMATCH_FORMAT,
151113
name: name,
152114
scope_class: scope.class,
@@ -155,16 +117,20 @@ def expression(scope)
155117
return
156118
end
157119

158-
config.expression_parser.try_parse(name)
120+
expression_parser.try_parse(name)
159121
end
122+
private_class_method :expression
123+
# rubocop:enable Metrics/MethodLength
160124

161125
# Write a semantics warning
162126
#
163127
# @return [undefined]
164-
def semantics_warning(format, options)
165-
message = format % options
166-
warn(SEMANTICS_MESSAGE_FORMAT % { message: message })
128+
#
129+
# ignore :reek:LongParameterList
130+
def self.semantics_warning(reporter, format, options)
131+
reporter.warn(SEMANTICS_MESSAGE_FORMAT % { message: format % options })
167132
end
133+
private_class_method :semantics_warning
168134
end # Bootstrap
169135
end # Env
170136
end # Mutant

lib/mutant/matcher.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class Matcher
77

88
# Call matcher
99
#
10-
# @param [Env::Bootstrap] env
10+
# @param [Env] env
1111
#
1212
# @return [Enumerable<Subject>]
1313
#

lib/mutant/matcher/chain.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class Chain < self
88

99
# Call matcher
1010
#
11-
# @param [Env::Bootstrap] env
11+
# @param [Env] env
1212
#
1313
# @return [Enumerable<Subject>]
1414
def call(env)

lib/mutant/matcher/filter.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class Filter < self
88

99
# Enumerate matches
1010
#
11-
# @param [Env::Bootstrap] env
11+
# @param [Env] env
1212
#
1313
# @return [Enumerable<Subject>]
1414
def call(env)

lib/mutant/matcher/method.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class Method < self
2020

2121
# Matched subjects
2222
#
23-
# @param [Env::Bootstrap] env
23+
# @param [Env] env
2424
#
2525
# @return [Enumerable<Subject>]
2626
def call(env)

lib/mutant/matcher/methods.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class Methods < self
1616

1717
# Enumerate subjects
1818
#
19-
# @param [Env::Bootstrap] env
19+
# @param [Env] env
2020
#
2121
# @return [Enumerable<Subject>]
2222
def call(env)

lib/mutant/matcher/namespace.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class Namespace < self
88

99
# Enumerate subjects
1010
#
11-
# @param [Env::Bootstrap] env
11+
# @param [Env] env
1212
#
1313
# @return [Enumerable<Subject>]
1414
def call(env)

lib/mutant/matcher/null.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class Null < self
88

99
# Enumerate subjects
1010
#
11-
# @param [Env::Bootstrap] env
11+
# @param [Env] env
1212
#
1313
# @return [Enumerable<Subject>]
1414
def call(_env)

lib/mutant/matcher/scope.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class Scope < self
2020

2121
# Matched subjects
2222
#
23-
# @param [Env::Bootstrap] env
23+
# @param [Env] env
2424
#
2525
# @return [Enumerable<Subject>]
2626
def call(env)

lib/mutant/reporter/cli.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def self.build(output)
2626

2727
# Report start
2828
#
29-
# @param [Env::Bootstrap] env
29+
# @param [Env] env
3030
#
3131
# @return [self]
3232
def start(env)

lib/mutant/reporter/cli/format.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class Format
1111

1212
# Start representation
1313
#
14-
# @param [Env::Bootstrap] env
14+
# @param [Env] env
1515
#
1616
# @return [String]
1717
abstract_method :start

lib/mutant/selector/null.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# frozen_string_literal: true
2+
3+
module Mutant
4+
class Selector
5+
# Selector that never returns tests
6+
class Null < self
7+
include Equalizer.new
8+
9+
# Tests for subject
10+
#
11+
# @param [Subject] subject
12+
#
13+
# @return [Enumerable<Test>]
14+
def call(_subject)
15+
EMPTY_ARRAY
16+
end
17+
end # Null
18+
end # Selector
19+
end # Mutant

spec/spec_helper.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ def verify_events
6161
yield
6262
end
6363
end
64+
65+
def undefined
66+
double('undefined')
67+
end
6468
end # XSpecHelper
6569

6670
RSpec.configure do |config|

spec/unit/mutant/cli_spec.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
let(:stderr) { instance_double(IO, 'stderr', puts: undefined) }
77
let(:stdout) { instance_double(IO, 'stdout', puts: undefined) }
88
let(:target_stream) { stdout }
9-
let(:undefined) { instance_double('undefined') }
109

1110
let(:config) do
1211
default_config.with(

0 commit comments

Comments
 (0)