mutiny 0.2.4 → 0.2.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b3b90916fcddcbfd2b6d6b373927154110c8d1a0
4
- data.tar.gz: f8d277e9dc277bdde93a449e8a10018be5c96859
3
+ metadata.gz: ca31e6c61eac704333513e20a7ba33416eacb370
4
+ data.tar.gz: 2c08a4a4337d1d6c9a842f995baa993c9f7ea460
5
5
  SHA512:
6
- metadata.gz: 74a0c7772807505bc0d79f386fcd958d53ff1466cef6bb6053fe6ba2d978bf07b9fbfe17bb6db1c8243bf30a1750bcc9d8f8273fe902ab5360f62f305d45f7d8
7
- data.tar.gz: 3180029d832460442147ff1623b4f535318a1226b1312d41232a736546475fce1f793827a0f16a4ae12e354a7f77d0ad3099f487f0fd21700b048c9a0f60e218
6
+ metadata.gz: deb63bad6550131c72454459b7eafd301f7ad5b4641498e42b262a17dacf7f77636a790daa15addb55bf749f410845eea82843f5cacc5e8324606505d400988a
7
+ data.tar.gz: 649fdc273f4e3ab181e7cb0f806b78996754d08f9beb2e98627a7dbca59aae5f9cafde975e6351c9ecc2d97e1c94d8c5b968ec90287b043e631ba746c4aa61d1
data/.rubocop.yml CHANGED
@@ -3,7 +3,7 @@ AllCops:
3
3
  - Gemfile
4
4
  - Rakefile
5
5
  Exclude:
6
- - vendor/*
6
+ - vendor/**/*
7
7
 
8
8
  StringLiterals:
9
9
  Enabled: false
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mutiny (0.2.4)
4
+ mutiny (0.2.5)
5
5
  gli (~> 2.13.0)
6
6
  metamorpher (~> 0.2.2)
7
7
  parser (~> 2.2.2)
data/RELEASES.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Release History
2
2
 
3
+ ## v0.2.5 (16 February 2016)
4
+ * Fix bug in the --cached switch of the score command, which was preventing mutants being correctly loaded from disk on Linux.
5
+ * Fix bug in the --cached switch of the score command, which was causing mutiny to report the incorrect path to subject files.
6
+ * Improve extensibility by supporting different implementations of the analyser. This has removed the need to have test selection as an extension point (for now).
7
+
3
8
  ## v0.2.4 (10 February 2016)
4
9
  * Add --cached switch to the score command, which loads mutants from disk rather than generating them anew.
5
10
  * Various changes to improve extensibility (i.e., mutant storage, test selection) and capabilities (i.e., mutant location and test hooks).
@@ -0,0 +1,13 @@
1
+ require_relative "../analyser"
2
+
3
+ module Mutiny
4
+ module Analysis
5
+ class Analyser
6
+ class Default < self
7
+ def select_tests(mutant)
8
+ integration.tests.for(mutant.subject) # TODO: is this correctly minimal?
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,18 +1,20 @@
1
+ require_relative "../isolation"
1
2
  require_relative "results"
2
3
 
3
4
  module Mutiny
4
5
  module Analysis
5
6
  class Analyser
6
- attr_reader :mutant_set, :integration
7
+ attr_reader :integration
7
8
 
8
- def initialize(mutant_set:, integration:)
9
- @mutant_set = mutant_set
9
+ def initialize(integration:)
10
10
  @integration = integration
11
11
  end
12
12
 
13
- def call
13
+ def call(mutant_set)
14
14
  results = Results.new
15
15
 
16
+ before_all(mutant_set)
17
+
16
18
  mutant_set.mutants.each do |mutant|
17
19
  results.add(mutant, analyse(mutant))
18
20
  end
@@ -20,11 +22,26 @@ module Mutiny
20
22
  results
21
23
  end
22
24
 
25
+ protected
26
+
27
+ def before_all(_mutant_set)
28
+ end
29
+
30
+ def select_tests(_mutant)
31
+ fail "No implementation has been provided for select_tests"
32
+ end
33
+
23
34
  private
24
35
 
25
36
  def analyse(mutant)
26
37
  mutant.apply
27
- mutant.stillborn? ? nil : integration.test(mutant)
38
+ mutant.stillborn? ? nil : run_tests(select_tests(mutant))
39
+ end
40
+
41
+ def run_tests(test_set)
42
+ Isolation.call do
43
+ integration.run(test_set, fail_fast: true)
44
+ end
28
45
  end
29
46
  end
30
47
  end
@@ -1,13 +1,14 @@
1
1
  require_relative 'pattern'
2
2
  require_relative 'reporter/stdout'
3
- require_relative 'tests/selection/default'
4
3
  require_relative 'integration/rspec'
5
4
  require_relative 'mutants/ruby'
6
5
  require_relative 'mutants/storage'
6
+ require_relative 'analysis/analyser/default'
7
7
 
8
8
  module Mutiny
9
9
  class Configuration
10
- attr_reader :loads, :requires, :patterns, :reporter, :mutants, :tests
10
+ attr_reader :loads, :requires, :patterns
11
+ attr_reader :reporter, :integration, :mutator, :mutant_storage, :analyser
11
12
 
12
13
  def initialize(loads: [], requires: [], patterns: [])
13
14
  @loads = loads
@@ -16,8 +17,10 @@ module Mutiny
16
17
  @patterns.map!(&Pattern.method(:new))
17
18
 
18
19
  @reporter = Reporter::Stdout.new
19
- @mutants = Mutants.new
20
- @tests = Tests.new
20
+ @integration = Integration::RSpec.new
21
+ @mutator = Mutants::Ruby.new
22
+ @mutant_storage = Mutants::Storage.new
23
+ @analyser = Analysis::Analyser::Default.new(integration: @integration)
21
24
  end
22
25
 
23
26
  def load_paths
@@ -27,23 +30,5 @@ module Mutiny
27
30
  def can_load?(source_path)
28
31
  load_paths.any? { |load_path| source_path.start_with?(load_path) }
29
32
  end
30
-
31
- class Mutants
32
- attr_reader :mutator, :storage
33
-
34
- def initialize
35
- @mutator = Mutiny::Mutants::Ruby.new
36
- @storage = Mutiny::Mutants::Storage.new
37
- end
38
- end
39
-
40
- class Tests
41
- attr_reader :selection, :integration
42
-
43
- def initialize
44
- @selection = Mutiny::Tests::Selection::Default.new
45
- @integration = Mutiny::Integration::RSpec.new(@selection)
46
- end
47
- end
48
33
  end
49
34
  end
@@ -51,11 +51,11 @@ module Mutiny
51
51
  end
52
52
 
53
53
  def test_set
54
- @test_set ||= configuration.tests.integration.tests.for_subjects(environment.subjects)
54
+ @test_set ||= configuration.integration.tests.for_all(environment.subjects)
55
55
  end
56
56
 
57
57
  def test_run
58
- @test_run ||= configuration.tests.integration.run(test_set)
58
+ @test_run ||= configuration.integration.run(test_set)
59
59
  end
60
60
  end
61
61
  end
@@ -22,11 +22,11 @@ module Mutiny
22
22
  end
23
23
 
24
24
  def mutant_set
25
- @mutant_set ||= configuration.mutants.mutator.mutants_for(environment.subjects)
25
+ @mutant_set ||= configuration.mutator.mutants_for(environment.subjects)
26
26
  end
27
27
 
28
28
  def mutant_storage
29
- @store ||= configuration.mutants.storage
29
+ @store ||= configuration.mutant_storage
30
30
  end
31
31
  end
32
32
  end
@@ -1,4 +1,3 @@
1
- require_relative "../analysis/analyser"
2
1
  require_relative "../output/table"
3
2
 
4
3
  module Mutiny
@@ -54,14 +53,7 @@ module Mutiny
54
53
  end
55
54
 
56
55
  def results
57
- @results ||= analyser.call
58
- end
59
-
60
- def analyser
61
- Analysis::Analyser.new(
62
- mutant_set: mutant_set,
63
- integration: configuration.tests.integration
64
- )
56
+ @results ||= configuration.analyser.call(mutant_set)
65
57
  end
66
58
 
67
59
  def mutant_set
@@ -70,9 +62,9 @@ module Mutiny
70
62
 
71
63
  def initialize_mutant_set
72
64
  if options[:cached]
73
- configuration.mutants.storage.load
65
+ configuration.mutant_storage.load_for(environment.subjects)
74
66
  else
75
- configuration.mutants.mutator.mutants_for(environment.subjects)
67
+ configuration.mutator.mutants_for(environment.subjects)
76
68
  end
77
69
  end
78
70
  end
@@ -13,6 +13,10 @@ module Mutiny
13
13
  @mutants = mutants
14
14
  end
15
15
 
16
+ def subjects
17
+ mutants.map(&:subject).uniq
18
+ end
19
+
16
20
  def group_by_subject
17
21
  mutants.group_by(&:subject).dup
18
22
  end
@@ -19,7 +19,7 @@ module Mutiny
19
19
 
20
20
  def load_all
21
21
  absolute_paths = Dir.glob(File.join(mutant_directory, "**", "*.rb"))
22
- absolute_paths.map { |path| strategy.load(path) }
22
+ absolute_paths.sort.map { |path| strategy.load(path) }
23
23
  end
24
24
  end
25
25
  end
@@ -15,9 +15,7 @@ module Mutiny
15
15
 
16
16
  def load(absolute_path)
17
17
  path = Path.from_absolute(path: absolute_path, root: mutant_directory)
18
- deserialise
19
- .merge(deserialised_contents(path)) { |_, left, right| left.merge(right) }
20
- .merge(deserialised_filename(path)) { |_, left, right| left.merge(right) }
18
+ deep_merge(deserialised_contents(path), deserialised_filename(path))
21
19
  end
22
20
 
23
21
  def store(mutant)
@@ -28,10 +26,8 @@ module Mutiny
28
26
 
29
27
  private
30
28
 
31
- def deserialise
32
- {
33
- subject: { root: mutant_directory }
34
- }
29
+ def deep_merge(hash1, hash2)
30
+ hash1.merge(hash2) { |_, h1_member, h2_member| h1_member.merge(h2_member) }
35
31
  end
36
32
 
37
33
  def deserialised_filename(path)
@@ -13,15 +13,20 @@ module Mutiny
13
13
  store.save_all(mutants)
14
14
  end
15
15
 
16
- def load
17
- mutants = store.load_all.map do |mutant_specification|
18
- subject = Subjects::Subject.new(**mutant_specification[:subject])
19
- mutant_specification[:subject] = subject
20
- Mutant.new(**mutant_specification)
16
+ def load_for(subjects)
17
+ mutants = store.load_all.map do |mutant_spec|
18
+ mutant_spec[:subject] = resolve_subject(subjects, **mutant_spec[:subject])
19
+ Mutant.new(**mutant_spec)
21
20
  end
22
21
 
23
22
  MutantSet.new(*mutants)
24
23
  end
24
+
25
+ private
26
+
27
+ def resolve_subject(subjects, name:, path:)
28
+ subjects.find { |subject| subject.name == name && subject.relative_path == path }
29
+ end
25
30
  end
26
31
  end
27
32
  end
@@ -18,12 +18,12 @@ module Mutiny
18
18
  tests.map(&:location)
19
19
  end
20
20
 
21
- def for_subject(subject)
22
- subset { |test| subject.name == test.expression }
21
+ def for_all(subject_set)
22
+ subset { |test| subject_set.names.include?(test.expression) }
23
23
  end
24
24
 
25
- def for_subjects(subjects)
26
- subset { |test| subjects.names.include?(test.expression) }
25
+ def for(subject)
26
+ subset { |test| subject.name == test.expression }
27
27
  end
28
28
 
29
29
  def subset(&block)
@@ -1,3 +1,3 @@
1
1
  module Mutiny
2
- VERSION = "0.2.4"
2
+ VERSION = "0.2.5"
3
3
  end
data/lib/mutiny.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require_relative "mutiny/version"
2
- require_relative "mutiny/integration"
3
2
  require_relative "mutiny/configuration"
4
3
  require_relative "mutiny/subjects"
5
4
  require_relative "mutiny/tests"
@@ -5,42 +5,41 @@ module Mutiny
5
5
  store = instance_double(Storage::FileStore)
6
6
  allow(store).to receive(:load_all).and_return(loaded_data)
7
7
 
8
+ first_subject = Subjects::Subject.new(name: "Two", path: "two.rb", root: ".")
9
+ second_subject = Subjects::Subject.new(name: "Four", path: "four.rb", root: ".")
10
+ subjects = Subjects::SubjectSet.new([first_subject, second_subject])
11
+
12
+ expected_mutant_set = MutantSet.new(
13
+ Mutant.new(subject: first_subject, code: "2 - 2", index: 0),
14
+ Mutant.new(subject: first_subject, code: "2 * 2", index: 1),
15
+ Mutant.new(subject: second_subject, code: "4 - 4", index: 2)
16
+ )
17
+
8
18
  storage = Storage.new(store: store)
9
- expect(storage.load).to eq(expected_mutant_set)
19
+ expect(storage.load_for(subjects)).to eq(expected_mutant_set)
10
20
  end
11
21
 
12
22
  # rubocop:disable MethodLength
13
23
  def loaded_data
14
24
  [
15
25
  {
16
- subject: { name: "Two", path: "two.rb", root: "~/Code/sums" },
26
+ subject: { name: "Two", path: "two.rb" },
17
27
  code: "2 - 2",
18
28
  index: 0
19
29
  },
20
30
  {
21
- subject: { name: "Two", path: "two.rb", root: "~/Code/sums" },
31
+ subject: { name: "Two", path: "two.rb" },
22
32
  code: "2 * 2",
23
33
  index: 1
24
34
  },
25
35
  {
26
- subject: { name: "Four", path: "four.rb", root: "~/Code/sums" },
36
+ subject: { name: "Four", path: "four.rb" },
27
37
  code: "4 - 4",
28
38
  index: 0
29
39
  }
30
40
  ]
31
41
  end
32
42
  # rubocop:enable MethodLength
33
-
34
- def expected_mutant_set
35
- first_subject = Subjects::Subject.new(name: "Two", path: "two.rb", root: "~/Code/sums")
36
- second_subject = Subjects::Subject.new(name: "Four", path: "four.rb", root: "~/Code/sums")
37
-
38
- MutantSet.new(
39
- Mutant.new(subject: first_subject, code: "2 - 2", index: 0),
40
- Mutant.new(subject: first_subject, code: "2 * 2", index: 1),
41
- Mutant.new(subject: second_subject, code: "4 - 4", index: 2)
42
- )
43
- end
44
43
  end
45
44
  end
46
45
  end
@@ -33,84 +33,42 @@ module Mutiny
33
33
  end
34
34
  end
35
35
 
36
- context "for all" do
36
+ context "for" do
37
37
  it "should return only those tests (whose expression) matches a subject" do
38
38
  subjects = subject_set_for("Max", "Min")
39
39
  test_set = test_set_for("Subtract", "Min", "Add")
40
- selected = test_set.for_subjects(subjects)
41
40
 
42
- expect(selected).to eq(test_set.subset { |t| t.expression == "Min" })
41
+ expect(test_set.for_all(subjects)).to eq(test_set.subset { |t| t.expression == "Min" })
43
42
  end
44
43
 
45
44
  it "should return multiple tests for a single subject" do
46
45
  subjects = subject_set_for("Min")
47
46
  test_set = test_set_for("Min", "Max", "Min", "Max", "Min")
48
- selected = test_set.for_subjects(subjects)
49
47
 
50
- expect(selected).to eq(test_set.subset { |t| t.expression == "Min" })
48
+ expect(test_set.for_all(subjects)).to eq(test_set.subset { |t| t.expression == "Min" })
51
49
  end
52
50
 
53
51
  it "should return no tests when there are no tests" do
54
52
  subjects = subject_set_for("Max", "Min")
55
53
  test_set = TestSet.empty
56
- selected = test_set.for_subjects(subjects)
57
54
 
58
- expect(selected).to eq(TestSet.empty)
55
+ expect(test_set.for_all(subjects)).to eq(TestSet.empty)
59
56
  end
60
57
 
61
58
  it "should return no tests when there are no relevant subjects" do
62
59
  subjects = subject_set_for("Max", "Min")
63
60
  test_set = test_set_for("Subtract", "Add")
64
- selected = test_set.for_subjects(subjects)
65
61
 
66
- expect(selected).to eq(TestSet.empty)
62
+ expect(test_set.for_all(subjects)).to eq(TestSet.empty)
67
63
  end
68
64
 
69
65
  def subject_set_for(*names)
70
66
  Subjects::SubjectSet.new(names.map { |n| Subjects::Subject.new(name: n) })
71
67
  end
72
- end
73
-
74
- context "for" do
75
- it "should return only those tests (whose expression) matches the mutant's subject" do
76
- subject = subject_for("Min")
77
- test_set = test_set_for("Subtract", "Min", "Add")
78
- selected = test_set.for_subject(subject)
79
-
80
- expect(selected).to eq(test_set.subset { |t| t.expression == "Min" })
81
- end
82
-
83
- it "should return multiple tests for a single mutant" do
84
- subject = subject_for("Min")
85
- test_set = test_set_for("Min", "Max", "Min", "Max", "Min")
86
- selected = test_set.for_subject(subject)
87
-
88
- expect(selected).to eq(test_set.subset { |t| t.expression == "Min" })
89
- end
90
68
 
91
- it "should return no tests when there are no tests" do
92
- subject = subject_for("Min")
93
- test_set = TestSet.empty
94
- selected = test_set.for_subject(subject)
95
-
96
- expect(selected).to eq(TestSet.empty)
69
+ def test_set_for(*expressions)
70
+ TestSet.new(expressions.map { |e| Test.new(expression: e) })
97
71
  end
98
-
99
- it "should return no tests when mutant is irrelevant" do
100
- subject = subject_for("Min")
101
- test_set = test_set_for("Subtract", "Add")
102
- selected = test_set.for_subject(subject)
103
-
104
- expect(selected).to eq(TestSet.empty)
105
- end
106
-
107
- def subject_for(name)
108
- Subjects::Subject.new(name: name)
109
- end
110
- end
111
-
112
- def test_set_for(*expressions)
113
- TestSet.new(expressions.map { |e| Test.new(expression: e) })
114
72
  end
115
73
  end
116
74
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mutiny
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Louis Rose
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-10 00:00:00.000000000 Z
11
+ date: 2016-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parser
@@ -191,9 +191,9 @@ files:
191
191
  - examples/untested_calculator/spec/spec_helper.rb
192
192
  - lib/mutiny.rb
193
193
  - lib/mutiny/analysis/analyser.rb
194
+ - lib/mutiny/analysis/analyser/default.rb
194
195
  - lib/mutiny/analysis/results.rb
195
196
  - lib/mutiny/configuration.rb
196
- - lib/mutiny/integration.rb
197
197
  - lib/mutiny/integration/hook.rb
198
198
  - lib/mutiny/integration/rspec.rb
199
199
  - lib/mutiny/integration/rspec/context.rb
@@ -245,7 +245,6 @@ files:
245
245
  - lib/mutiny/subjects/subject.rb
246
246
  - lib/mutiny/subjects/subject_set.rb
247
247
  - lib/mutiny/tests.rb
248
- - lib/mutiny/tests/selection/default.rb
249
248
  - lib/mutiny/tests/test.rb
250
249
  - lib/mutiny/tests/test_run.rb
251
250
  - lib/mutiny/tests/test_set.rb
@@ -286,7 +285,7 @@ files:
286
285
  - spec/unit/subjects/environment/type_spec.rb
287
286
  - spec/unit/subjects/environment_spec.rb
288
287
  - spec/unit/subjects/subject_spec.rb
289
- - spec/unit/tests/test_set_spec.rb
288
+ - spec/unit/subjects/test_set_spec.rb
290
289
  homepage: https://github.com/mutiny/mutiny
291
290
  licenses:
292
291
  - MIT
@@ -347,4 +346,4 @@ test_files:
347
346
  - spec/unit/subjects/environment/type_spec.rb
348
347
  - spec/unit/subjects/environment_spec.rb
349
348
  - spec/unit/subjects/subject_spec.rb
350
- - spec/unit/tests/test_set_spec.rb
349
+ - spec/unit/subjects/test_set_spec.rb
@@ -1,19 +0,0 @@
1
- require_relative "tests/selection/default"
2
- require_relative "isolation"
3
-
4
- module Mutiny
5
- class Integration
6
- attr_reader :test_selection
7
-
8
- def initialize(test_selection = Tests::Selection::Default.new)
9
- @test_selection = test_selection
10
- end
11
-
12
- def test(mutant)
13
- Isolation.call do
14
- selected_tests = test_selection.for(mutant, from: tests)
15
- run(selected_tests, fail_fast: true)
16
- end
17
- end
18
- end
19
- end
@@ -1,11 +0,0 @@
1
- module Mutiny
2
- module Tests
3
- module Selection
4
- class Default
5
- def for(mutant, from:)
6
- from.for_subject(mutant.subject) # TODO: is this correctly minimal?
7
- end
8
- end
9
- end
10
- end
11
- end