mutiny 0.1.0
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/.gitignore +6 -0
- data/.rspec +3 -0
- data/.rubocop.yml +18 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +113 -0
- data/LICENSE.txt +22 -0
- data/README.md +16 -0
- data/RELEASES.md +8 -0
- data/Rakefile +23 -0
- data/TODO.md +55 -0
- data/bin/mutiny +35 -0
- data/examples/buggy_calculator/.rspec +2 -0
- data/examples/buggy_calculator/lib/calculator/max.rb +9 -0
- data/examples/buggy_calculator/lib/calculator.rb +4 -0
- data/examples/buggy_calculator/spec/calculator/max_spec.rb +17 -0
- data/examples/buggy_calculator/spec/spec_helper.rb +89 -0
- data/examples/calculator/.rspec +2 -0
- data/examples/calculator/lib/calculator/max.rb +9 -0
- data/examples/calculator/lib/calculator/min.rb +9 -0
- data/examples/calculator/lib/calculator.rb +5 -0
- data/examples/calculator/spec/calculator/max_spec.rb +17 -0
- data/examples/calculator/spec/calculator/min_spec.rb +17 -0
- data/examples/calculator/spec/spec_helper.rb +89 -0
- data/examples/untested_calculator/.rspec +2 -0
- data/examples/untested_calculator/lib/calculator/max.rb +9 -0
- data/examples/untested_calculator/lib/calculator.rb +4 -0
- data/examples/untested_calculator/spec/spec_helper.rb +89 -0
- data/lib/mutiny/configuration.rb +25 -0
- data/lib/mutiny/integration/rspec/context.rb +26 -0
- data/lib/mutiny/integration/rspec/parser.rb +36 -0
- data/lib/mutiny/integration/rspec/runner.rb +58 -0
- data/lib/mutiny/integration/rspec/test.rb +16 -0
- data/lib/mutiny/integration/rspec/test_set.rb +17 -0
- data/lib/mutiny/integration/rspec.rb +25 -0
- data/lib/mutiny/mode/check.rb +62 -0
- data/lib/mutiny/mode/mutate.rb +29 -0
- data/lib/mutiny/mode.rb +19 -0
- data/lib/mutiny/mutants/mutant.rb +32 -0
- data/lib/mutiny/mutants/mutant_set.rb +42 -0
- data/lib/mutiny/mutants/mutation/method/binary_arithmetic_operator_replacement.rb +15 -0
- data/lib/mutiny/mutants/mutation/method/conditional_operator_deletion.rb +19 -0
- data/lib/mutiny/mutants/mutation/method/conditional_operator_insertion.rb +25 -0
- data/lib/mutiny/mutants/mutation/method/conditional_operator_replacement.rb +19 -0
- data/lib/mutiny/mutants/mutation/method/helpers/infix_operator_replacement.rb +17 -0
- data/lib/mutiny/mutants/mutation/method/helpers/operator_replacement.rb +81 -0
- data/lib/mutiny/mutants/mutation/method/logical_operator_deletion.rb +19 -0
- data/lib/mutiny/mutants/mutation/method/logical_operator_insertion.rb +24 -0
- data/lib/mutiny/mutants/mutation/method/logical_operator_replacement.rb +15 -0
- data/lib/mutiny/mutants/mutation/method/relational_expression_replacement.rb +19 -0
- data/lib/mutiny/mutants/mutation/method/relational_operator_replacement.rb +15 -0
- data/lib/mutiny/mutants/mutation/method/shortcut_assignment_operator_replacement.rb +23 -0
- data/lib/mutiny/mutants/mutation/method/unary_arithmetic_operator_deletion.rb +29 -0
- data/lib/mutiny/mutants/mutation/method/unary_arithmetic_operator_insertion.rb +29 -0
- data/lib/mutiny/mutants/mutation.rb +10 -0
- data/lib/mutiny/mutants/mutation_set.rb +24 -0
- data/lib/mutiny/mutants/ruby.rb +42 -0
- data/lib/mutiny/pattern.rb +17 -0
- data/lib/mutiny/reporter/stdout.rb +9 -0
- data/lib/mutiny/subjects/environment/type.rb +54 -0
- data/lib/mutiny/subjects/environment.rb +25 -0
- data/lib/mutiny/subjects/subject.rb +32 -0
- data/lib/mutiny/subjects/subject_set.rb +17 -0
- data/lib/mutiny/subjects.rb +3 -0
- data/lib/mutiny/tests/test.rb +12 -0
- data/lib/mutiny/tests/test_run.rb +18 -0
- data/lib/mutiny/tests/test_set.rb +44 -0
- data/lib/mutiny/tests.rb +3 -0
- data/lib/mutiny/version.rb +3 -0
- data/lib/mutiny.rb +4 -0
- data/mutiny.gemspec +32 -0
- data/spec/integration/check_spec.rb +39 -0
- data/spec/integration/mutate_spec.rb +35 -0
- data/spec/spec_helper.rb +38 -0
- data/spec/support/aruba.rb +12 -0
- data/spec/support/in_example_project.rb +17 -0
- data/spec/support/shared_examples/shared_examples_for_an_operator_replacement_mutation.rb +26 -0
- data/spec/unit/integration/rspec/parser_spec.rb +23 -0
- data/spec/unit/integration/rspec/runner_spec.rb +47 -0
- data/spec/unit/mutants/mutant_set_spec.rb +57 -0
- data/spec/unit/mutants/mutations/method/binary_operator_replacement_spec.rb +11 -0
- data/spec/unit/mutants/mutations/method/conditional_operator_deletion_spec.rb +17 -0
- data/spec/unit/mutants/mutations/method/conditional_operator_insertion_spec.rb +17 -0
- data/spec/unit/mutants/mutations/method/conditional_operator_replacement_spec.rb +16 -0
- data/spec/unit/mutants/mutations/method/logical_operator_deletion_spec.rb +17 -0
- data/spec/unit/mutants/mutations/method/logical_operator_insertion_spec.rb +17 -0
- data/spec/unit/mutants/mutations/method/logical_operator_replacement_spec.rb +11 -0
- data/spec/unit/mutants/mutations/method/relational_expression_replacement_spec.rb +18 -0
- data/spec/unit/mutants/mutations/method/relational_operator_replacement_spec.rb +11 -0
- data/spec/unit/mutants/mutations/method/shortcut_assignment_operator_replacement_spec.rb +12 -0
- data/spec/unit/mutants/mutations/method/unary_arithmetic_operator_deletion_spec.rb +21 -0
- data/spec/unit/mutants/mutations/method/unary_arithmetic_operator_insertion_spec.rb +21 -0
- data/spec/unit/pattern_spec.rb +28 -0
- data/spec/unit/subjects/environment/type_spec.rb +54 -0
- data/spec/unit/subjects/environment_spec.rb +71 -0
- data/spec/unit/subjects/subject_spec.rb +25 -0
- data/spec/unit/subjects/test_set_spec.rb +75 -0
- metadata +310 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'aruba/api'
|
2
|
+
require 'aruba/reporting'
|
3
|
+
|
4
|
+
module InExampleProject
|
5
|
+
def in_example_project(example_name, should_require = false)
|
6
|
+
in_sub_process do
|
7
|
+
$LOAD_PATH << File.join(Dir.pwd, "examples", example_name, "lib")
|
8
|
+
Dir.chdir(File.join("examples", example_name))
|
9
|
+
require example_name if should_require
|
10
|
+
yield
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
RSpec.configure do |config|
|
16
|
+
config.include InExampleProject
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
shared_examples "an operator replacement mutation" do |operators|
|
2
|
+
operators.each do |original_operator|
|
3
|
+
it "mutates #{original_operator} to every other operator" do
|
4
|
+
program = "foo #{original_operator} bar"
|
5
|
+
mutants = subject.mutate(program)
|
6
|
+
expected_mutants = operators
|
7
|
+
.reject { |op| op == original_operator }
|
8
|
+
.map { |op| program.gsub(original_operator.to_s, op.to_s) }
|
9
|
+
|
10
|
+
expect(mutants).to eq(expected_mutants)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
shared_examples "an operator replacement mutation with extra replacements" do |replacements|
|
16
|
+
replacements.each do |original_operator, replacement_operators|
|
17
|
+
it "mutates #{original_operator} to every other operator" do
|
18
|
+
program = "foo #{original_operator} bar"
|
19
|
+
mutants = subject.mutate(program)
|
20
|
+
expected_mutants = replacement_operators
|
21
|
+
.map { |op| program.gsub(original_operator.to_s, op.to_s) }
|
22
|
+
|
23
|
+
expect(mutants).to eq(expected_mutants)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Mutiny
|
2
|
+
class Integration
|
3
|
+
class RSpec
|
4
|
+
describe Parser do
|
5
|
+
let(:test_set) { subject.call }
|
6
|
+
|
7
|
+
it "should be able to count tests" do
|
8
|
+
in_example_project("calculator") do
|
9
|
+
expect(test_set.size).to eq(6)
|
10
|
+
expect(test_set.empty?).to be_falsey
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be able to report that there are no tests" do
|
15
|
+
in_example_project("untested_calculator") do
|
16
|
+
expect(test_set.size).to eq(0)
|
17
|
+
expect(test_set.empty?).to be_truthy
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Mutiny
|
2
|
+
class Integration
|
3
|
+
class RSpec
|
4
|
+
describe Runner do
|
5
|
+
let(:test_set) { Parser.new.call }
|
6
|
+
let(:test_run) { subject.call }
|
7
|
+
subject { Runner.new(test_set) }
|
8
|
+
|
9
|
+
it "should be able to run a passing test set" do
|
10
|
+
in_example_project("calculator") do
|
11
|
+
expect(test_run.passed?).to be_truthy
|
12
|
+
expect(test_run.failed_tests.empty?).to be_truthy
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should be able to run a failing test set" do
|
17
|
+
in_example_project("buggy_calculator") do
|
18
|
+
expect(test_run.passed?).to be_falsey
|
19
|
+
expect(test_run.failed_tests.locations).to eq([
|
20
|
+
"./examples/buggy_calculator/spec/calculator/max_spec.rb:9",
|
21
|
+
"./examples/buggy_calculator/spec/calculator/max_spec.rb:13"
|
22
|
+
])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should be able to run a partial test set" do
|
27
|
+
in_example_project("buggy_calculator") do
|
28
|
+
partial_test_set = test_set.take(1)
|
29
|
+
test_run = Runner.new(partial_test_set).call
|
30
|
+
|
31
|
+
# Note: This would be false if the whole test set was run
|
32
|
+
expect(test_run.passed?).to be_truthy
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should be able to run an empty test set" do
|
37
|
+
in_example_project("calculator") do
|
38
|
+
test_run = Runner.new(TestSet.empty).call
|
39
|
+
|
40
|
+
expect(test_run.passed?).to be_truthy
|
41
|
+
expect(test_run.failed_tests.empty?).to be_truthy
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Mutiny
|
2
|
+
module Mutants
|
3
|
+
class ObservableMutantSet < MutantSet
|
4
|
+
def create_mutant(subject, code)
|
5
|
+
MutantSpy.new(subject: subject, code: code)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class MutantSpy < Mutant
|
10
|
+
attr_reader :directory, :index
|
11
|
+
|
12
|
+
def store(directory, index)
|
13
|
+
@directory = directory
|
14
|
+
@index = index
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe MutantSet do
|
19
|
+
subject(:mutant_set) { ObservableMutantSet.new }
|
20
|
+
|
21
|
+
before(:each) do
|
22
|
+
mutant_set.add(:min, [:min_mutant_1, :min_mutant_2])
|
23
|
+
mutant_set.add(:max, [:max_mutant_1])
|
24
|
+
mutant_set.add(:min, [:min_mutant_3])
|
25
|
+
end
|
26
|
+
|
27
|
+
it "groups mutants by subject" do
|
28
|
+
groups = mutant_set.group_by_subject.to_a
|
29
|
+
first = groups.first
|
30
|
+
second = groups.last
|
31
|
+
|
32
|
+
expect(first).to eq(mutants_for(:min, :min_mutant_1, :min_mutant_2, :min_mutant_3))
|
33
|
+
expect(second).to eq(mutants_for(:max, :max_mutant_1))
|
34
|
+
end
|
35
|
+
|
36
|
+
it "counts mutants" do
|
37
|
+
expect(mutant_set.size).to eq(4)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "stores by delegating to mutants" do
|
41
|
+
mutant_set.store(:mutant_dir)
|
42
|
+
|
43
|
+
mutant_set.group_by_subject.each do |_, mutants|
|
44
|
+
mutants.each_with_index do |mutant, index|
|
45
|
+
expect(mutant.directory).to eq(:mutant_dir)
|
46
|
+
expect(mutant.index).to eq(index)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def mutants_for(subject, *code)
|
52
|
+
mutants = code.map { |c| Mutant.new(subject: subject, code: c) }
|
53
|
+
[subject, mutants]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Mutiny
|
2
|
+
module Mutants
|
3
|
+
class Mutation
|
4
|
+
module Method
|
5
|
+
describe ConditionalOperatorDeletion do
|
6
|
+
it "mutates negated literal to literal" do
|
7
|
+
expect(subject.mutate("a = !true")).to eq(["a = true"])
|
8
|
+
end
|
9
|
+
|
10
|
+
it "mutates negated variable to variable" do
|
11
|
+
expect(subject.mutate("a = !b")).to eq(["a = b"])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Mutiny
|
2
|
+
module Mutants
|
3
|
+
class Mutation
|
4
|
+
module Method
|
5
|
+
describe ConditionalOperatorInsertion do
|
6
|
+
it "mutates literal to negated literal" do
|
7
|
+
expect(subject.mutate("return true")).to eq(["return !true"])
|
8
|
+
end
|
9
|
+
|
10
|
+
it "mutates variable to negated variable" do
|
11
|
+
expect(subject.mutate("return a")).to eq(["return !a"])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Mutiny
|
2
|
+
module Mutants
|
3
|
+
class Mutation
|
4
|
+
module Method
|
5
|
+
describe ConditionalOperatorReplacement do
|
6
|
+
it_behaves_like "an operator replacement mutation with extra replacements",
|
7
|
+
'^': %i(&& ||),
|
8
|
+
'&&': %i(^ ||),
|
9
|
+
'||': %i(^ &&),
|
10
|
+
and: %i(^ ||),
|
11
|
+
or: %i(^ &&)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Mutiny
|
2
|
+
module Mutants
|
3
|
+
class Mutation
|
4
|
+
module Method
|
5
|
+
describe LogicalOperatorDeletion do
|
6
|
+
it "mutates inverted integer to integer" do
|
7
|
+
expect(subject.mutate("a = ~42")).to eq(["a = 42"])
|
8
|
+
end
|
9
|
+
|
10
|
+
it "mutates inverted variable to variable" do
|
11
|
+
expect(subject.mutate("a = ~b")).to eq(["a = b"])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Mutiny
|
2
|
+
module Mutants
|
3
|
+
class Mutation
|
4
|
+
module Method
|
5
|
+
describe LogicalOperatorInsertion do
|
6
|
+
it "mutates integer to inverted integer" do
|
7
|
+
expect(subject.mutate("return 42")).to eq(["return ~42"])
|
8
|
+
end
|
9
|
+
|
10
|
+
it "mutates variable to inverted variable" do
|
11
|
+
expect(subject.mutate("return a")).to eq(["return ~a"])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Mutiny
|
2
|
+
module Mutants
|
3
|
+
class Mutation
|
4
|
+
module Method
|
5
|
+
describe RelationalExpressionReplacement do
|
6
|
+
%i(< <= == != >= >).each do |operator|
|
7
|
+
it "mutates #{operator} to true and false" do
|
8
|
+
program = "foo #{operator} bar"
|
9
|
+
mutants = subject.mutate(program)
|
10
|
+
|
11
|
+
expect(mutants).to eq(%w(true false))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Mutiny
|
2
|
+
module Mutants
|
3
|
+
class Mutation
|
4
|
+
module Method
|
5
|
+
describe UnaryArithmeticOperatorDeletion do
|
6
|
+
it "mutates negative integer to positive integer" do
|
7
|
+
expect(subject.mutate("a = -42")).to eq(["a = 42"])
|
8
|
+
end
|
9
|
+
|
10
|
+
it "mutates negative float to positive float" do
|
11
|
+
expect(subject.mutate("a = -4.2")).to eq(["a = 4.2"])
|
12
|
+
end
|
13
|
+
|
14
|
+
it "mutates negative variable to positive variable" do
|
15
|
+
expect(subject.mutate("a = -b")).to eq(["a = b"])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Mutiny
|
2
|
+
module Mutants
|
3
|
+
class Mutation
|
4
|
+
module Method
|
5
|
+
describe UnaryArithmeticOperatorInsertion do
|
6
|
+
it "mutates positive integer to negative integer" do
|
7
|
+
expect(subject.mutate("a = 42")).to eq(["a = -42"])
|
8
|
+
end
|
9
|
+
|
10
|
+
it "mutates positive float to negative float" do
|
11
|
+
expect(subject.mutate("a = 4.2")).to eq(["a = -4.2"])
|
12
|
+
end
|
13
|
+
|
14
|
+
it "mutates positive variable to negative variable" do
|
15
|
+
expect(subject.mutate("a = b")).to eq(["a = -b"])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Mutiny
|
2
|
+
describe Pattern do
|
3
|
+
context "wildcard" do
|
4
|
+
subject { Pattern.new("*") }
|
5
|
+
|
6
|
+
it "should match all" do
|
7
|
+
expect(subject.match?("Calculator")).to be_truthy
|
8
|
+
expect(subject.match?("Calculator::Max")).to be_truthy
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context "fully qualified name" do
|
13
|
+
subject { Pattern.new("Calculator::Max") }
|
14
|
+
|
15
|
+
it "should match same name" do
|
16
|
+
expect(subject.match?("Calculator::Max")).to be_truthy
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should match a more specific name" do
|
20
|
+
expect(subject.match?("Calculator::Max::Float")).to be_truthy
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should not match a less specific name" do
|
24
|
+
expect(subject.match?("Calculator")).to be_falsey
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Mutiny
|
2
|
+
module Subjects
|
3
|
+
class Environment
|
4
|
+
describe Type do
|
5
|
+
context "relevance" do
|
6
|
+
it "should include named class that are in scope and that are on the load path" do
|
7
|
+
configuration = Configuration.new(patterns: ["*"], loads: ["spec"])
|
8
|
+
type = Type.new(Person, configuration)
|
9
|
+
|
10
|
+
expect(type.relevant?).to be_truthy
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should exclude anonymous classes" do
|
14
|
+
type = Type.new(Class.new, Configuration.new(patterns: ["*"], loads: ["spec"]))
|
15
|
+
|
16
|
+
expect(type.relevant?).to be_falsey
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should exclude classes that are not in scope" do
|
20
|
+
configuration = Configuration.new(patterns: ["Department"], loads: ["spec"])
|
21
|
+
type = Type.new(Person, configuration)
|
22
|
+
|
23
|
+
expect(type.relevant?).to be_falsey
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should exclude classes that are not on the load path" do
|
27
|
+
configuration = Configuration.new(patterns: ["*"], loads: ["lib"])
|
28
|
+
type = Type.new(Person, configuration)
|
29
|
+
|
30
|
+
expect(type.relevant?).to be_falsey
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "to_subject" do
|
35
|
+
it "should build a valid subject" do
|
36
|
+
configuration = Configuration.new(patterns: ["*"], loads: ["spec"])
|
37
|
+
type = Type.new(Person, configuration)
|
38
|
+
expected_subject = Subject.new(
|
39
|
+
name: "Mutiny::Subjects::Environment::Person",
|
40
|
+
path: __FILE__,
|
41
|
+
root: __FILE__.match(%r{.*/spec})[0]
|
42
|
+
)
|
43
|
+
|
44
|
+
expect(type.to_subject).to eq(expected_subject)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class Person
|
49
|
+
attr_accessor :name
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Mutiny
|
2
|
+
module Subjects
|
3
|
+
describe Environment do
|
4
|
+
let(:module_names) { %w(Calculator Calculator::Max Calculator::Min) }
|
5
|
+
|
6
|
+
context "initialization" do
|
7
|
+
it "should add the required class and all dependences to available modules" do
|
8
|
+
in_sub_process do
|
9
|
+
configuration = Configuration.new(
|
10
|
+
loads: ["examples/calculator/lib"],
|
11
|
+
requires: ["calculator"]
|
12
|
+
)
|
13
|
+
|
14
|
+
expect { Environment.new(configuration) }
|
15
|
+
.to change { ObjectSpace.each_object(Module).map(&:name).reject(&:nil?).sort }
|
16
|
+
.by(module_names)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should not add any classes without a require" do
|
21
|
+
in_sub_process do
|
22
|
+
configuration = Configuration.new(
|
23
|
+
loads: ["examples/calculator/lib/calculator"]
|
24
|
+
)
|
25
|
+
|
26
|
+
expect { Environment.new(configuration) }
|
27
|
+
.not_to change { ObjectSpace.each_object(Module).map(&:name) }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should raise when require not found" do
|
32
|
+
in_sub_process do
|
33
|
+
configuration = Configuration.new(
|
34
|
+
requires: ["calculator"]
|
35
|
+
)
|
36
|
+
|
37
|
+
expect { Environment.new(configuration) }
|
38
|
+
.to raise_error(LoadError)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "subjects" do
|
44
|
+
let(:mutatable_module_names) { %w(Calculator::Max Calculator::Min) }
|
45
|
+
|
46
|
+
let(:configuration) do
|
47
|
+
Configuration.new(
|
48
|
+
loads: ["examples/calculator/lib"],
|
49
|
+
requires: ["calculator"],
|
50
|
+
patterns: ["Calculator"]
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
let(:environment) { Environment.new(configuration) }
|
55
|
+
|
56
|
+
it "should return all subjects matching pattern" do
|
57
|
+
in_sub_process do
|
58
|
+
expect(environment.subjects.names).to eq(mutatable_module_names)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should function in the presence of anonymous modules" do
|
63
|
+
in_sub_process do
|
64
|
+
Class.new # create anonymous class
|
65
|
+
expect(environment.subjects.names).to eq(mutatable_module_names)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Mutiny
|
2
|
+
module Subjects
|
3
|
+
describe Subject do
|
4
|
+
context "relative_path" do
|
5
|
+
it "should return path relative from root" do
|
6
|
+
subject = Subject.new(name: "Foo::Bar::Baz", path: "/foo/bar/baz.rb", root: "/foo")
|
7
|
+
|
8
|
+
expect(subject.relative_path).to eq("bar/baz.rb")
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should return path relative from nested root" do
|
12
|
+
subject = Subject.new(name: "Foo::Bar::Baz", path: "/foo/bar/baz.rb", root: "/foo/bar")
|
13
|
+
|
14
|
+
expect(subject.relative_path).to eq("baz.rb")
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should return path relative from root with trailing slash" do
|
18
|
+
subject = Subject.new(name: "Foo::Bar::Baz", path: "/foo/bar/baz.rb", root: "/foo/")
|
19
|
+
|
20
|
+
expect(subject.relative_path).to eq("bar/baz.rb")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Mutiny
|
2
|
+
module Tests
|
3
|
+
describe TestSet do
|
4
|
+
let(:tests) { %w(test1 test2 test3) }
|
5
|
+
subject { TestSet.new(tests) }
|
6
|
+
|
7
|
+
context "take" do
|
8
|
+
it "(n) should return TestSet containing first n tests" do
|
9
|
+
expect(subject.take(2)).to eq(TestSet.new(tests.take(2)))
|
10
|
+
end
|
11
|
+
|
12
|
+
it "(0) should return an empty TestSet" do
|
13
|
+
expect(subject.take(0)).to eq(TestSet.empty)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "(n) of an empty TestSet should return an empty TestSet" do
|
17
|
+
expect(TestSet.empty.take(2)).to eq(TestSet.empty)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "subset" do
|
22
|
+
it "should return TestSet containing tests that match condition" do
|
23
|
+
subset = subject.subset { |t| t.end_with? "2" }
|
24
|
+
expected = TestSet.new(tests.select { |t| t.end_with? "2" })
|
25
|
+
|
26
|
+
expect(subset).to eq(expected)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should return empty TestSet when no tests match condition" do
|
30
|
+
subset = subject.subset { |_| false }
|
31
|
+
|
32
|
+
expect(subset).to eq(TestSet.empty)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "for" do
|
37
|
+
it "should return only those tests (whose expression) matches a subject" do
|
38
|
+
subject_set = subject_set_for("Max", "Min")
|
39
|
+
test_set = test_set_for("Subtract", "Min", "Add")
|
40
|
+
|
41
|
+
expect(test_set.for(subject_set)).to eq(test_set.subset { |t| t.expression == "Min" })
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should return multiple tests for a single subject" do
|
45
|
+
subject_set = subject_set_for("Min")
|
46
|
+
test_set = test_set_for("Min", "Max", "Min", "Max", "Min")
|
47
|
+
|
48
|
+
expect(test_set.for(subject_set)).to eq(test_set.subset { |t| t.expression == "Min" })
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should return no tests when there are no tests" do
|
52
|
+
subject_set = subject_set_for("Max", "Min")
|
53
|
+
test_set = TestSet.empty
|
54
|
+
|
55
|
+
expect(test_set.for(subject_set)).to eq(TestSet.empty)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should return no tests when there are no relevant subjects" do
|
59
|
+
subject_set = subject_set_for("Max", "Min")
|
60
|
+
test_set = test_set_for("Subtract", "Add")
|
61
|
+
|
62
|
+
expect(test_set.for(subject_set)).to eq(TestSet.empty)
|
63
|
+
end
|
64
|
+
|
65
|
+
def subject_set_for(*names)
|
66
|
+
Subjects::SubjectSet.new(names.map { |n| Subjects::Subject.new(name: n) })
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_set_for(*expressions)
|
70
|
+
TestSet.new(expressions.map { |e| Test.new(expression: e) })
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|