mutiny 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
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