Skip to content

Commit 1227de7

Browse files
committed
Add mutant
1 parent 8aac7fb commit 1227de7

File tree

5 files changed

+128
-0
lines changed

5 files changed

+128
-0
lines changed

.mutant.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
integration: rspec

Gemfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
source 'https://rubygems.org'
22
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
33

4+
gem 'mutant'
5+
gem 'mutant-rspec'
6+
gem 'mutant-license', source: 'https://oss:IG9NKHkepT1VE6CyvJiYH14GUsCHPtog@gem.mutant.dev'
7+
48
gemspec name: 'jsonapi_parameters'

Rakefile

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,119 @@ require 'bundler/gem_tasks'
1818
require 'rspec/core/rake_task'
1919

2020
task :default => :spec
21+
22+
23+
# Pass a match expression as an optional argument to only run mutant
24+
# on classes that match. Example: `rake mutant TaxTribs::ZendeskSender`
25+
#
26+
task :mutant do
27+
current_ci_branch = ENV['CIRCLE_BRANCH']
28+
29+
# Do not run mutant on CI master or develop branches.
30+
# Mutant will only run on pull requests against modified files.
31+
#
32+
if %w(master develop).include?(current_ci_branch)
33+
puts "> CI #{current_ci_branch} branch -- mutant will not run"
34+
exit
35+
end
36+
37+
mutation_type = ARGV[1]
38+
39+
vars = 'RAILS_ENV=test NOCOVERAGE=true'.freeze
40+
flags = ['--use rspec', '--fail-fast']
41+
42+
# When running on CI, a very high number of concurrent jobs seem to make some
43+
# mutations fail randomly. Reducing the number of jobs minimise this problem.
44+
flags.push('--jobs 24') if current_ci_branch
45+
46+
# This is to avoid running the mutant with flag `--since master` when
47+
# we are already on master, as otherwise it will not work as expected.
48+
current_branch = current_ci_branch || `git rev-parse --abbrev-ref HEAD`
49+
50+
if mutation_type == 'master'
51+
puts "> current branch: #{current_branch}"
52+
53+
if current_branch != 'master'
54+
puts "> running complete mutant testing on all changes since 'origin/master'"
55+
flags.push('--since origin/master')
56+
else
57+
# As we are already in master, let's not use the --since, and fallback to
58+
# running the quick randomised sample
59+
puts "> already on master, overriding --since flag"
60+
mutation_type = nil
61+
end
62+
end
63+
64+
unless system("#{vars} mutant #{flags.join(' ')} #{classes_to_mutate(mutation_type).join(' ')}")
65+
raise 'Mutation testing failed'
66+
end
67+
68+
exit
69+
end
70+
71+
private
72+
73+
def classes_to_mutate(option)
74+
75+
case option
76+
when nil
77+
# Quicker run, reduced testing scope (random sample), default option
78+
puts '> running quick sample mutant testing'
79+
form_objects.sample(5) + decision_trees_and_services.sample(3) + models
80+
when 'all', 'master'
81+
# Complete coverage (very long run time) or only changed classes
82+
puts '> running complete mutant testing'
83+
form_objects + decision_trees_and_services + models + jobs
84+
else
85+
# Individual class testing, very quick
86+
# we'll take all arguments after the first (which is 'mutant')
87+
_foo, *classes = ARGV
88+
classes
89+
end
90+
end
91+
92+
# Classes that, for different reasons, mutant can't cope with or gives
93+
# false positives (for example, due to multi-threading tests, etc.)
94+
#
95+
def excluded_constants
96+
[
97+
C100App::PaymentsFlowControl,
98+
].freeze
99+
end
100+
101+
# As the current models are just empty shells for ActiveRecord relationships,
102+
# and we don't even have corresponding spec tests for those, there is no point
103+
# in including these in the mutation test, and thus we can save some time.
104+
# Only include in this collection the models that matter and have specs.
105+
#
106+
def models
107+
%w(
108+
C100Application
109+
Applicant
110+
Solicitor
111+
User
112+
PaymentIntent
113+
CompletedApplicationsAudit
114+
).freeze
115+
end
116+
117+
# Everything inheriting from `BaseForm` and inside namespace `Steps`
118+
# i.e. all classes in `/app/forms/steps/**/*`
119+
#
120+
def form_objects
121+
BaseForm.descendants.map(&:name).grep(/^Steps::/)
122+
end
123+
124+
# Everything inside `C100App` namespace, unless included in `#excluded_constants`
125+
# i.e. all classes in `/app/services/c100_app/*`
126+
#
127+
def decision_trees_and_services
128+
C100App.constants.map { |symbol| C100App.const_get(symbol) } - excluded_constants
129+
end
130+
131+
# Everything inheriting from `ApplicationJob`
132+
# i.e. all jobs in `/app/jobs/*`
133+
#
134+
def jobs
135+
ApplicationJob.descendants
136+
end

bin/ci

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ set -e
44

55
bundle exec bin/quality
66
bundle exec bin/spec
7+
bundle exec bin/mutant

bin/mutant

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env sh
2+
3+
set -e
4+
5+
bundle exec mutant

0 commit comments

Comments
 (0)