mutant 0.11.34 → 0.12.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/mutant/cli/command/environment/run.rb +6 -9
- data/lib/mutant/cli/command/environment.rb +15 -0
- data/lib/mutant/cli/command/root.rb +1 -1
- data/lib/mutant/cli/command.rb +1 -9
- data/lib/mutant/config.rb +8 -2
- data/lib/mutant/integration/null.rb +4 -3
- data/lib/mutant/matcher.rb +1 -0
- data/lib/mutant/mutation/operators.rb +10 -0
- data/lib/mutant/parallel/connection.rb +3 -3
- data/lib/mutant/reporter/cli/printer/config.rb +1 -0
- data/lib/mutant/reporter/cli/printer/test.rb +22 -1
- data/lib/mutant/repository/diff.rb +7 -9
- data/lib/mutant/result.rb +5 -4
- data/lib/mutant/test/runner/sink.rb +6 -2
- data/lib/mutant/usage.rb +109 -0
- data/lib/mutant/util.rb +23 -2
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/world.rb +14 -8
- data/lib/mutant.rb +3 -7
- metadata +4 -9
- data/lib/mutant/cli/command/subscription.rb +0 -54
- data/lib/mutant/license/subscription/commercial.rb +0 -88
- data/lib/mutant/license/subscription/opensource.rb +0 -30
- data/lib/mutant/license/subscription/repository.rb +0 -72
- data/lib/mutant/license/subscription.rb +0 -69
- data/lib/mutant/license.rb +0 -46
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e1cf4d744b27691dff3986c4af5dddd278e6eadcc348a9cd0d4b5e16fa22514
|
4
|
+
data.tar.gz: 77913d65e176d5150b17ca03ec5b05398dee759dfea2ad89dd438eeaf72e34f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d8ec46587bb0b896b0322b57766a6e8bbe51ea00777e538cacd5f6a329d1a3e47c513ccaec720050a3a37049e65dc9be627f25006cc0404cb294fb0ba0a55d6
|
7
|
+
data.tar.gz: d0f457130db225049774066ad235cc00db9f3c1ca75c9994c618798a94567867fb21c84059c0a3878ae88736ad9b4d325b2e75f552bef583cd3cb846e48fc0c9
|
@@ -9,13 +9,6 @@ module Mutant
|
|
9
9
|
SHORT_DESCRIPTION = 'Run code analysis'
|
10
10
|
SUBCOMMANDS = EMPTY_ARRAY
|
11
11
|
|
12
|
-
UNLICENSED = <<~MESSAGE.lines.freeze
|
13
|
-
You are using mutant unlicensed.
|
14
|
-
|
15
|
-
See https://github.com/mbj/mutant#licensing to aquire a license.
|
16
|
-
Note: Its free for opensource use, which is recommended for trials.
|
17
|
-
MESSAGE
|
18
|
-
|
19
12
|
NO_TESTS_MESSAGE = <<~'MESSAGE'
|
20
13
|
===============
|
21
14
|
Mutant found no tests available for mutation testing.
|
@@ -34,8 +27,8 @@ module Mutant
|
|
34
27
|
private
|
35
28
|
|
36
29
|
def action
|
37
|
-
|
38
|
-
.bind
|
30
|
+
bootstrap
|
31
|
+
.bind(&method(:verify_usage))
|
39
32
|
.bind(&method(:validate_tests))
|
40
33
|
.bind(&Mutation::Runner.public_method(:call))
|
41
34
|
.bind(&method(:from_result))
|
@@ -56,6 +49,10 @@ module Mutant
|
|
56
49
|
Either::Left.new('Uncovered mutations detected, exiting nonzero!')
|
57
50
|
end
|
58
51
|
end
|
52
|
+
|
53
|
+
def verify_usage(environment)
|
54
|
+
environment.config.usage.verify.fmap { environment }
|
55
|
+
end
|
59
56
|
end # Run
|
60
57
|
end # Environment
|
61
58
|
end # Command
|
@@ -15,6 +15,7 @@ module Mutant
|
|
15
15
|
add_integration_options
|
16
16
|
add_matcher_options
|
17
17
|
add_reporter_options
|
18
|
+
add_usage_options
|
18
19
|
].freeze
|
19
20
|
|
20
21
|
private
|
@@ -136,6 +137,20 @@ module Mutant
|
|
136
137
|
set(reporter: @config.reporter.with(print_warnings: true))
|
137
138
|
end
|
138
139
|
end
|
140
|
+
|
141
|
+
def add_usage_options(parser)
|
142
|
+
parser.separator('Usage:')
|
143
|
+
|
144
|
+
parser.accept(Usage, Usage::CLI_REGEXP) do |value|
|
145
|
+
Usage.parse(value).from_right
|
146
|
+
end
|
147
|
+
|
148
|
+
parser.on(
|
149
|
+
'--usage USAGE_TYPE',
|
150
|
+
Usage,
|
151
|
+
'License usage: opensource|commercial'
|
152
|
+
) { |usage| set(usage: usage) }
|
153
|
+
end
|
139
154
|
end # Run
|
140
155
|
# rubocop:enable Metrics/ClassLength
|
141
156
|
end # Command
|
@@ -10,7 +10,7 @@ module Mutant
|
|
10
10
|
class Root < self
|
11
11
|
NAME = 'mutant'
|
12
12
|
SHORT_DESCRIPTION = 'mutation testing engine main command'
|
13
|
-
SUBCOMMANDS = [Environment::Run, Environment,
|
13
|
+
SUBCOMMANDS = [Environment::Run, Environment, Util].freeze
|
14
14
|
end # Root
|
15
15
|
end # Command
|
16
16
|
end # CLI
|
data/lib/mutant/cli/command.rb
CHANGED
@@ -122,7 +122,7 @@ module Mutant
|
|
122
122
|
|
123
123
|
def parse(arguments)
|
124
124
|
Either
|
125
|
-
.wrap_error(OptionParser::InvalidOption) { parser.order(arguments) }
|
125
|
+
.wrap_error(OptionParser::InvalidArgument, OptionParser::InvalidOption) { parser.order(arguments) }
|
126
126
|
.lmap(&method(:with_help))
|
127
127
|
.bind(&method(:parse_remaining))
|
128
128
|
end
|
@@ -176,14 +176,6 @@ module Mutant
|
|
176
176
|
end
|
177
177
|
end
|
178
178
|
|
179
|
-
def parse_remaining_arguments(remaining)
|
180
|
-
if remaining.any?
|
181
|
-
Either::Left.new("#{full_name}: Does not expect extra arguments")
|
182
|
-
else
|
183
|
-
Either::Right.new(self)
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
179
|
def parse_subcommand(arguments)
|
188
180
|
command_name, *arguments = arguments
|
189
181
|
|
data/lib/mutant/config.rb
CHANGED
@@ -21,7 +21,8 @@ module Mutant
|
|
21
21
|
:matcher,
|
22
22
|
:mutation,
|
23
23
|
:reporter,
|
24
|
-
:requires
|
24
|
+
:requires,
|
25
|
+
:usage
|
25
26
|
)
|
26
27
|
|
27
28
|
%i[fail_fast].each do |name|
|
@@ -87,7 +88,8 @@ module Mutant
|
|
87
88
|
jobs: other.jobs || jobs,
|
88
89
|
matcher: matcher.merge(other.matcher),
|
89
90
|
mutation: mutation.merge(other.mutation),
|
90
|
-
requires: requires + other.requires
|
91
|
+
requires: requires + other.requires,
|
92
|
+
usage: other.usage.merge(usage)
|
91
93
|
)
|
92
94
|
end
|
93
95
|
# rubocop:enable Metrics/AbcSize
|
@@ -247,6 +249,10 @@ module Mutant
|
|
247
249
|
Transform::Hash::Key.new(
|
248
250
|
transform: Transform::STRING_ARRAY,
|
249
251
|
value: 'requires'
|
252
|
+
),
|
253
|
+
Transform::Hash::Key.new(
|
254
|
+
transform: Usage::TRANSFORM,
|
255
|
+
value: 'usage'
|
250
256
|
)
|
251
257
|
],
|
252
258
|
required: []
|
data/lib/mutant/matcher.rb
CHANGED
@@ -21,14 +21,22 @@ module Mutant
|
|
21
21
|
all?: %i[any?],
|
22
22
|
any?: %i[all?],
|
23
23
|
at: %i[fetch key?],
|
24
|
+
detect: %i[first last],
|
24
25
|
fetch: %i[key?],
|
26
|
+
find: %i[first last],
|
27
|
+
first: %i[last],
|
25
28
|
flat_map: %i[map],
|
26
29
|
gsub: %i[sub],
|
27
30
|
is_a?: %i[instance_of?],
|
28
31
|
kind_of?: %i[instance_of?],
|
32
|
+
last: %i[first],
|
29
33
|
map: %i[each],
|
30
34
|
match: %i[match?],
|
35
|
+
max: %i[first last],
|
36
|
+
max_by: %i[first last],
|
31
37
|
method: %i[public_method],
|
38
|
+
min: %i[first last],
|
39
|
+
min_by: %i[first last],
|
32
40
|
reverse_each: %i[each],
|
33
41
|
reverse_map: %i[map each],
|
34
42
|
reverse_merge: %i[merge],
|
@@ -49,6 +57,8 @@ module Mutant
|
|
49
57
|
.tap do |replacements|
|
50
58
|
replacements.delete(:==)
|
51
59
|
replacements.delete(:eql?)
|
60
|
+
replacements.delete(:first)
|
61
|
+
replacements.delete(:last)
|
52
62
|
end
|
53
63
|
.freeze
|
54
64
|
end
|
@@ -21,11 +21,11 @@ module Mutant
|
|
21
21
|
attr_reader :log
|
22
22
|
|
23
23
|
def error
|
24
|
-
@errors
|
24
|
+
Util.max_one(@errors)
|
25
25
|
end
|
26
26
|
|
27
27
|
def result
|
28
|
-
@results
|
28
|
+
Util.max_one(@results)
|
29
29
|
end
|
30
30
|
|
31
31
|
def initialize(*)
|
@@ -96,7 +96,7 @@ module Mutant
|
|
96
96
|
end
|
97
97
|
|
98
98
|
def length
|
99
|
-
@lengths
|
99
|
+
Util.max_one(@lengths)
|
100
100
|
end
|
101
101
|
|
102
102
|
def advance_log
|
@@ -68,7 +68,7 @@ module Mutant
|
|
68
68
|
#
|
69
69
|
# @return [undefined]
|
70
70
|
def run
|
71
|
-
|
71
|
+
visit_failed
|
72
72
|
visit(Env, object.env)
|
73
73
|
FORMATS.each do |report, format, value|
|
74
74
|
__send__(report, format, __send__(value))
|
@@ -77,6 +77,27 @@ module Mutant
|
|
77
77
|
|
78
78
|
private
|
79
79
|
|
80
|
+
def visit_failed
|
81
|
+
failed = object.failed_test_results
|
82
|
+
|
83
|
+
if object.env.config.fail_fast
|
84
|
+
visit_failed_tests(failed.take(1))
|
85
|
+
visit_other_failed(failed.drop(1))
|
86
|
+
else
|
87
|
+
visit_failed_tests(failed)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def visit_other_failed(other)
|
92
|
+
return if other.empty?
|
93
|
+
|
94
|
+
puts('Other failed tests (report suppressed from fail fast): %d' % other.length)
|
95
|
+
end
|
96
|
+
|
97
|
+
def visit_failed_tests(failed)
|
98
|
+
visit_collection(Result, failed)
|
99
|
+
end
|
100
|
+
|
80
101
|
def efficiency_percent
|
81
102
|
(testtime / runtime) * 100
|
82
103
|
end
|
@@ -36,9 +36,8 @@ module Mutant
|
|
36
36
|
|
37
37
|
def repository_root
|
38
38
|
world
|
39
|
-
.
|
40
|
-
.fmap(
|
41
|
-
.fmap(&world.pathname.public_method(:new))
|
39
|
+
.capture_command(%w[git rev-parse --show-toplevel])
|
40
|
+
.fmap { |status| world.pathname.new(status.stdout.chomp) }
|
42
41
|
end
|
43
42
|
|
44
43
|
def touched_path(path, &block)
|
@@ -52,11 +51,10 @@ module Mutant
|
|
52
51
|
|
53
52
|
def diff_index(root)
|
54
53
|
world
|
55
|
-
.
|
56
|
-
.
|
57
|
-
.bind do |lines|
|
54
|
+
.capture_command(%W[git diff-index #{to}])
|
55
|
+
.bind do |status|
|
58
56
|
Mutant
|
59
|
-
.traverse(->(line) { parse_line(root, line) }, lines)
|
57
|
+
.traverse(->(line) { parse_line(root, line) }, status.stdout.lines)
|
60
58
|
.fmap do |paths|
|
61
59
|
paths.to_h { |path| [path.path, path] }
|
62
60
|
end
|
@@ -105,8 +103,8 @@ module Mutant
|
|
105
103
|
|
106
104
|
def diff_ranges
|
107
105
|
world
|
108
|
-
.
|
109
|
-
.fmap
|
106
|
+
.capture_command(%W[git diff --unified=0 #{to} -- #{path}])
|
107
|
+
.fmap { |status| Ranges.parse(status.stdout) }
|
110
108
|
.from_right
|
111
109
|
end
|
112
110
|
memoize :diff_ranges
|
data/lib/mutant/result.rb
CHANGED
@@ -158,7 +158,7 @@ module Mutant
|
|
158
158
|
|
159
159
|
# Test result
|
160
160
|
class Test
|
161
|
-
include Anima.new(:passed, :runtime, :output)
|
161
|
+
include Anima.new(:job_index, :passed, :runtime, :output)
|
162
162
|
|
163
163
|
alias_method :success?, :passed
|
164
164
|
|
@@ -170,9 +170,10 @@ module Mutant
|
|
170
170
|
# @return [undefined]
|
171
171
|
def initialize
|
172
172
|
super(
|
173
|
-
|
174
|
-
|
175
|
-
|
173
|
+
job_index: nil,
|
174
|
+
output: '',
|
175
|
+
passed: false,
|
176
|
+
runtime: 0.0
|
176
177
|
)
|
177
178
|
end
|
178
179
|
end # VoidValue
|
@@ -22,7 +22,7 @@ module Mutant
|
|
22
22
|
Result::TestEnv.new(
|
23
23
|
env: env,
|
24
24
|
runtime: env.world.timer.now - @start,
|
25
|
-
test_results: @test_results
|
25
|
+
test_results: @test_results.sort_by!(&:job_index)
|
26
26
|
)
|
27
27
|
end
|
28
28
|
|
@@ -42,7 +42,11 @@ module Mutant
|
|
42
42
|
fail response.error
|
43
43
|
end
|
44
44
|
|
45
|
-
@test_results << response.result.with(
|
45
|
+
@test_results << response.result.with(
|
46
|
+
job_index: response.job.index,
|
47
|
+
output: response.log
|
48
|
+
)
|
49
|
+
|
46
50
|
self
|
47
51
|
end
|
48
52
|
end # Sink
|
data/lib/mutant/usage.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mutant
|
4
|
+
class Usage
|
5
|
+
include Adamantium, Equalizer.new
|
6
|
+
|
7
|
+
def value
|
8
|
+
self.class::VALUE
|
9
|
+
end
|
10
|
+
|
11
|
+
def verify
|
12
|
+
Either::Right.new(nil)
|
13
|
+
end
|
14
|
+
|
15
|
+
def message
|
16
|
+
self.class::MESSAGE
|
17
|
+
end
|
18
|
+
|
19
|
+
def merge(_other)
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
class Commercial < self
|
24
|
+
VALUE = 'commercial'
|
25
|
+
|
26
|
+
MESSAGE = <<~'MESSAGE'
|
27
|
+
## Commercial use
|
28
|
+
|
29
|
+
`commercial` usage type requires [payment](https://github.com/mbj/mutant?tab=readme-ov-file#pricing),
|
30
|
+
If you are under an active payment plan you can use the commercial usage type on any
|
31
|
+
repository, including private ones.
|
32
|
+
|
33
|
+
To use `commercial` usage type either specify `--usage commercial` on the command
|
34
|
+
line or use the config file key `usage`:
|
35
|
+
|
36
|
+
```
|
37
|
+
# mutant.yml or config/mutant.yml
|
38
|
+
usage: commercial
|
39
|
+
```
|
40
|
+
MESSAGE
|
41
|
+
end
|
42
|
+
|
43
|
+
class Opensource < self
|
44
|
+
VALUE = 'opensource'
|
45
|
+
|
46
|
+
MESSAGE = <<~'MESSAGE'
|
47
|
+
## Opensource use
|
48
|
+
|
49
|
+
`opensource` usage is free while mutant is run on an opensource project.
|
50
|
+
Under that usage mutant does not require any kind of sign up or payment.
|
51
|
+
Set this usage type exclusively on public opensource projects. Any other
|
52
|
+
scenario requires payment.
|
53
|
+
Using the `opensource` usage type on private repotiories and or on commercial
|
54
|
+
code bases is not valid.
|
55
|
+
|
56
|
+
To use `opensource` usage type either specify `--usage opensource` on the command
|
57
|
+
line or use the config file key `usage`:
|
58
|
+
|
59
|
+
```
|
60
|
+
# mutant.yml or config/mutant.yml
|
61
|
+
usage: opensource
|
62
|
+
```
|
63
|
+
MESSAGE
|
64
|
+
end
|
65
|
+
|
66
|
+
class Unknown < self
|
67
|
+
VALUE = 'unknown'
|
68
|
+
|
69
|
+
MESSAGE = <<~"MESSAGE".freeze
|
70
|
+
# Unknown mutant usage type
|
71
|
+
|
72
|
+
Mutant license usage is unspecified. Valid usage types are `opensource` or `commercial`.
|
73
|
+
|
74
|
+
Usage can be specified via the `--usage` command line parameter or via the
|
75
|
+
config file under the `usage` key.
|
76
|
+
|
77
|
+
#{Commercial::MESSAGE}
|
78
|
+
#{Opensource::MESSAGE}
|
79
|
+
This is a breaking change for users of the 0.11.x / 0.10.x mutant releases.
|
80
|
+
Sorry for that but its going to make future adoption much easier.
|
81
|
+
License gem is gone entirely.
|
82
|
+
MESSAGE
|
83
|
+
|
84
|
+
def merge(other)
|
85
|
+
other
|
86
|
+
end
|
87
|
+
|
88
|
+
def verify
|
89
|
+
Either::Left.new(MESSAGE)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.parse(value)
|
94
|
+
{
|
95
|
+
'commercial' => Either::Right.new(Commercial.new),
|
96
|
+
'opensource' => Either::Right.new(Opensource.new)
|
97
|
+
}.fetch(value) { Either::Left.new("Unknown usage option: #{value.inspect}") }
|
98
|
+
end
|
99
|
+
|
100
|
+
CLI_REGEXP = /\A(?:commercial|opensource)\z/
|
101
|
+
|
102
|
+
TRANSFORM = Transform::Sequence.new(
|
103
|
+
steps: [
|
104
|
+
Transform::STRING,
|
105
|
+
Transform::Block.capture(:environment_variables, &method(:parse))
|
106
|
+
]
|
107
|
+
)
|
108
|
+
end # Usage
|
109
|
+
end # Mutant
|
data/lib/mutant/util.rb
CHANGED
@@ -12,9 +12,30 @@ module Mutant
|
|
12
12
|
#
|
13
13
|
# @return [Object] first entry
|
14
14
|
def self.one(array)
|
15
|
-
|
15
|
+
case array
|
16
|
+
in [value]
|
17
|
+
value
|
18
|
+
else
|
19
|
+
fail SizeError, "expected size to be exactly 1 but size was #{array.size}"
|
20
|
+
end
|
21
|
+
end
|
16
22
|
|
17
|
-
|
23
|
+
# Return only element in array if it contains max one member
|
24
|
+
#
|
25
|
+
# @param array [Array]
|
26
|
+
#
|
27
|
+
# @return [Object] first entry
|
28
|
+
# @return [nil] if empty
|
29
|
+
#
|
30
|
+
# rubocop:disable Lint/EmptyInPattern
|
31
|
+
def self.max_one(array)
|
32
|
+
case array
|
33
|
+
in []
|
34
|
+
in [value]
|
35
|
+
value
|
36
|
+
else
|
37
|
+
fail SizeError, "expected size to be max 1 but size was #{array.size}"
|
38
|
+
end
|
18
39
|
end
|
19
40
|
end # Util
|
20
41
|
end # Mutant
|
data/lib/mutant/version.rb
CHANGED
data/lib/mutant/world.rb
CHANGED
@@ -39,19 +39,25 @@ module Mutant
|
|
39
39
|
INSPECT
|
40
40
|
end
|
41
41
|
|
42
|
+
class CommandStatus
|
43
|
+
include Adamantium, Anima.new(:process_status, :stderr, :stdout)
|
44
|
+
end # CommandStatus
|
45
|
+
|
42
46
|
# Capture stdout of a command
|
43
47
|
#
|
44
48
|
# @param [Array<String>] command
|
45
49
|
#
|
46
|
-
# @return [Either<
|
47
|
-
def
|
48
|
-
stdout,
|
50
|
+
# @return [Either<CommandStatus,CommandStatus>]
|
51
|
+
def capture_command(command)
|
52
|
+
stdout, stderr, process_status = open3.capture3(*command, binmode: true)
|
49
53
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
54
|
+
(process_status.success? ? Either::Right : Either::Left).new(
|
55
|
+
CommandStatus.new(
|
56
|
+
process_status: process_status,
|
57
|
+
stderr: stderr,
|
58
|
+
stdout: stdout
|
59
|
+
)
|
60
|
+
)
|
55
61
|
end
|
56
62
|
|
57
63
|
# Try const get
|
data/lib/mutant.rb
CHANGED
@@ -109,6 +109,7 @@ module Mutant
|
|
109
109
|
require 'mutant/require_highjack'
|
110
110
|
require 'mutant/mutation'
|
111
111
|
require 'mutant/mutation/operators'
|
112
|
+
require 'mutant/usage'
|
112
113
|
require 'mutant/mutation/config'
|
113
114
|
require 'mutant/mutator'
|
114
115
|
require 'mutant/mutator/util'
|
@@ -221,7 +222,6 @@ module Mutant
|
|
221
222
|
require 'mutant/config/coverage_criteria'
|
222
223
|
require 'mutant/cli'
|
223
224
|
require 'mutant/cli/command'
|
224
|
-
require 'mutant/cli/command/subscription'
|
225
225
|
require 'mutant/cli/command/environment'
|
226
226
|
require 'mutant/cli/command/environment/irb'
|
227
227
|
require 'mutant/cli/command/environment/run'
|
@@ -255,11 +255,6 @@ module Mutant
|
|
255
255
|
require 'mutant/repository/diff/ranges'
|
256
256
|
require 'mutant/zombifier'
|
257
257
|
require 'mutant/range'
|
258
|
-
require 'mutant/license'
|
259
|
-
require 'mutant/license/subscription'
|
260
|
-
require 'mutant/license/subscription/commercial'
|
261
|
-
require 'mutant/license/subscription/opensource'
|
262
|
-
require 'mutant/license/subscription/repository'
|
263
258
|
require 'mutant/segment'
|
264
259
|
require 'mutant/segment/recorder'
|
265
260
|
end
|
@@ -363,7 +358,8 @@ module Mutant
|
|
363
358
|
matcher: Matcher::Config::DEFAULT,
|
364
359
|
mutation: Mutation::Config::EMPTY,
|
365
360
|
reporter: Reporter::CLI.build(WORLD.stdout),
|
366
|
-
requires: EMPTY_ARRAY
|
361
|
+
requires: EMPTY_ARRAY,
|
362
|
+
usage: Usage::Unknown.new
|
367
363
|
)
|
368
364
|
end # Config
|
369
365
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mutant
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Markus Schirp
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-05-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: diff-lcs
|
@@ -177,7 +177,6 @@ files:
|
|
177
177
|
- lib/mutant/cli/command/environment/subject.rb
|
178
178
|
- lib/mutant/cli/command/environment/test.rb
|
179
179
|
- lib/mutant/cli/command/root.rb
|
180
|
-
- lib/mutant/cli/command/subscription.rb
|
181
180
|
- lib/mutant/cli/command/util.rb
|
182
181
|
- lib/mutant/config.rb
|
183
182
|
- lib/mutant/config/coverage_criteria.rb
|
@@ -197,11 +196,6 @@ files:
|
|
197
196
|
- lib/mutant/isolation/exception.rb
|
198
197
|
- lib/mutant/isolation/fork.rb
|
199
198
|
- lib/mutant/isolation/none.rb
|
200
|
-
- lib/mutant/license.rb
|
201
|
-
- lib/mutant/license/subscription.rb
|
202
|
-
- lib/mutant/license/subscription/commercial.rb
|
203
|
-
- lib/mutant/license/subscription/opensource.rb
|
204
|
-
- lib/mutant/license/subscription/repository.rb
|
205
199
|
- lib/mutant/loader.rb
|
206
200
|
- lib/mutant/matcher.rb
|
207
201
|
- lib/mutant/matcher/chain.rb
|
@@ -341,6 +335,7 @@ files:
|
|
341
335
|
- lib/mutant/test/runner/sink.rb
|
342
336
|
- lib/mutant/timer.rb
|
343
337
|
- lib/mutant/transform.rb
|
338
|
+
- lib/mutant/usage.rb
|
344
339
|
- lib/mutant/util.rb
|
345
340
|
- lib/mutant/variable.rb
|
346
341
|
- lib/mutant/version.rb
|
@@ -366,7 +361,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
366
361
|
- !ruby/object:Gem::Version
|
367
362
|
version: '0'
|
368
363
|
requirements: []
|
369
|
-
rubygems_version: 3.3
|
364
|
+
rubygems_version: 3.5.3
|
370
365
|
signing_key:
|
371
366
|
specification_version: 4
|
372
367
|
summary: ''
|
@@ -1,54 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mutant
|
4
|
-
module CLI
|
5
|
-
class Command
|
6
|
-
class Subscription < self
|
7
|
-
NAME = 'subscription'
|
8
|
-
SHORT_DESCRIPTION = 'Subscription subcommands'
|
9
|
-
|
10
|
-
private
|
11
|
-
|
12
|
-
def license
|
13
|
-
License.call(world)
|
14
|
-
end
|
15
|
-
|
16
|
-
class Test < self
|
17
|
-
NAME = 'test'
|
18
|
-
SUBCOMMANDS = [].freeze
|
19
|
-
SHORT_DESCRIPTION = 'Silently validates subscription, exits accordingly'
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def execute
|
24
|
-
license.right?
|
25
|
-
end
|
26
|
-
end # Test
|
27
|
-
|
28
|
-
class Show < self
|
29
|
-
NAME = 'show'
|
30
|
-
SUBCOMMANDS = [].freeze
|
31
|
-
SHORT_DESCRIPTION = 'Show subscription status'
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
def execute
|
36
|
-
license.either(method(:unlicensed), method(:licensed))
|
37
|
-
end
|
38
|
-
|
39
|
-
def licensed(subscription)
|
40
|
-
world.stdout.puts(subscription.description)
|
41
|
-
true
|
42
|
-
end
|
43
|
-
|
44
|
-
def unlicensed(message)
|
45
|
-
world.stderr.puts(message)
|
46
|
-
false
|
47
|
-
end
|
48
|
-
end # Show
|
49
|
-
|
50
|
-
SUBCOMMANDS = [Show, Test].freeze
|
51
|
-
end # Subscription
|
52
|
-
end # Command
|
53
|
-
end # CLI
|
54
|
-
end # Mutant
|
@@ -1,88 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mutant
|
4
|
-
module License
|
5
|
-
class Subscription
|
6
|
-
class Commercial < self
|
7
|
-
include AbstractType
|
8
|
-
|
9
|
-
def self.from_json(value)
|
10
|
-
{
|
11
|
-
'individual' => Individual,
|
12
|
-
'organization' => Organization
|
13
|
-
}.fetch(value.fetch('type', 'individual')).from_json(value)
|
14
|
-
end
|
15
|
-
|
16
|
-
class Organization < self
|
17
|
-
SUBSCRIPTION_NAME = 'commercial organization'
|
18
|
-
|
19
|
-
def self.from_json(value)
|
20
|
-
new(licensed: value.fetch('repositories').map(&Repository.public_method(:parse)).to_set)
|
21
|
-
end
|
22
|
-
|
23
|
-
def call(world)
|
24
|
-
Repository.load_from_git(world).bind(&method(:check_subscription))
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def check_subscription(actual)
|
30
|
-
if licensed.any? { |repository| actual.any? { |other| repository.allow?(other) } }
|
31
|
-
success
|
32
|
-
else
|
33
|
-
failure(licensed, actual)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
class Individual < self
|
39
|
-
SUBSCRIPTION_NAME = 'commercial individual'
|
40
|
-
|
41
|
-
class Author
|
42
|
-
include Anima.new(:email)
|
43
|
-
|
44
|
-
alias_method :to_s, :email
|
45
|
-
public :to_s
|
46
|
-
end
|
47
|
-
|
48
|
-
def self.from_json(value)
|
49
|
-
new(licensed: value.fetch('authors').to_set { |email| Author.new(email: email) })
|
50
|
-
end
|
51
|
-
|
52
|
-
def call(world)
|
53
|
-
candidates = candidates(world)
|
54
|
-
|
55
|
-
if (licensed & candidates).any?
|
56
|
-
success
|
57
|
-
else
|
58
|
-
failure(licensed, candidates)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
private
|
63
|
-
|
64
|
-
def candidates(world)
|
65
|
-
git_author(world).merge(commit_author(world))
|
66
|
-
end
|
67
|
-
|
68
|
-
def git_author(world)
|
69
|
-
capture(world, %w[git config --get user.email])
|
70
|
-
end
|
71
|
-
|
72
|
-
def commit_author(world)
|
73
|
-
capture(world, %w[git show --quiet --pretty=format:%ae])
|
74
|
-
end
|
75
|
-
|
76
|
-
def capture(world, command)
|
77
|
-
world
|
78
|
-
.capture_stdout(command)
|
79
|
-
.fmap(&:chomp)
|
80
|
-
.fmap { |email| Author.new(email: email) }
|
81
|
-
.fmap { |value| Set.new([value]) }
|
82
|
-
.from_right { Set.new }
|
83
|
-
end
|
84
|
-
end # Individual
|
85
|
-
end # Commercial
|
86
|
-
end # Subscription
|
87
|
-
end # License
|
88
|
-
end # Mutant
|
@@ -1,30 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mutant
|
4
|
-
module License
|
5
|
-
class Subscription
|
6
|
-
class Opensource < self
|
7
|
-
SUBSCRIPTION_NAME = 'opensource repository'
|
8
|
-
|
9
|
-
def self.from_json(value)
|
10
|
-
new(licensed: value.fetch('repositories').map(&Repository.public_method(:parse)).to_set)
|
11
|
-
end
|
12
|
-
|
13
|
-
def call(world)
|
14
|
-
Repository.load_from_git(world).bind(&method(:check_subscription))
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
def check_subscription(actual)
|
20
|
-
if licensed.any? { |repository| actual.any? { |other| repository.allow?(other) } }
|
21
|
-
success
|
22
|
-
else
|
23
|
-
failure(licensed, actual)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
end # Opensource
|
28
|
-
end # Subscription
|
29
|
-
end # License
|
30
|
-
end # Mutant
|
@@ -1,72 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mutant
|
4
|
-
module License
|
5
|
-
class Subscription
|
6
|
-
class Repository
|
7
|
-
include Anima.new(:host, :path)
|
8
|
-
|
9
|
-
REMOTE_REGEXP = /\A[^\t]+\t(?<url>[^ ]+) \((?:fetch|push)\)\n\z/
|
10
|
-
GIT_SSH_REGEXP = %r{\A[^@]+@(?<host>[^:/]+)[:/](?<path>.+?)(?:\.git)?\z}
|
11
|
-
GIT_HTTPS_REGEXP = %r{\Ahttps://(?<host>[^/]+)/(?<path>.+?)(?:\.git)?\z}
|
12
|
-
WILDCARD = '/*'
|
13
|
-
WILDCARD_RANGE = (..-WILDCARD.length)
|
14
|
-
|
15
|
-
private_constant(*constants(false))
|
16
|
-
|
17
|
-
def to_s
|
18
|
-
[host, path].join('/')
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.load_from_git(world)
|
22
|
-
world
|
23
|
-
.capture_stdout(%w[git remote --verbose])
|
24
|
-
.fmap(&method(:parse_remotes))
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.parse_remotes(input)
|
28
|
-
input.lines.map(&method(:parse_remote)).to_set
|
29
|
-
end
|
30
|
-
private_class_method :parse_remotes
|
31
|
-
|
32
|
-
def self.parse(input)
|
33
|
-
host, path = *input.split('/', 2).map(&:downcase)
|
34
|
-
new(host: host, path: path)
|
35
|
-
end
|
36
|
-
|
37
|
-
def self.parse_remote(input)
|
38
|
-
match = REMOTE_REGEXP.match(input) or
|
39
|
-
fail "Unmatched remote line: #{input.inspect}"
|
40
|
-
|
41
|
-
parse_url(match[:url])
|
42
|
-
end
|
43
|
-
private_class_method :parse_remote
|
44
|
-
|
45
|
-
def self.parse_url(input)
|
46
|
-
match = GIT_SSH_REGEXP.match(input) || GIT_HTTPS_REGEXP.match(input)
|
47
|
-
|
48
|
-
unless match
|
49
|
-
fail "Unmatched git remote URL: #{input.inspect}"
|
50
|
-
end
|
51
|
-
|
52
|
-
new(host: match[:host], path: match[:path].downcase)
|
53
|
-
end
|
54
|
-
private_class_method :parse_url
|
55
|
-
|
56
|
-
def allow?(other)
|
57
|
-
other.host.eql?(host) && path_match?(other.path)
|
58
|
-
end
|
59
|
-
|
60
|
-
private
|
61
|
-
|
62
|
-
def path_match?(other_path)
|
63
|
-
path.eql?(other_path) || wildcard_match?(path, other_path) || wildcard_match?(other_path, path)
|
64
|
-
end
|
65
|
-
|
66
|
-
def wildcard_match?(left, right)
|
67
|
-
left.end_with?(WILDCARD) && right.start_with?(left[WILDCARD_RANGE])
|
68
|
-
end
|
69
|
-
end # Repository
|
70
|
-
end # Subscription
|
71
|
-
end # License
|
72
|
-
end # Mutant
|
@@ -1,69 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mutant
|
4
|
-
module License
|
5
|
-
class Subscription
|
6
|
-
include Anima.new(:licensed)
|
7
|
-
|
8
|
-
FORMAT = <<~'MESSAGE'
|
9
|
-
%<subscription_name>s subscription:
|
10
|
-
Licensed:
|
11
|
-
%<licensed>s
|
12
|
-
MESSAGE
|
13
|
-
|
14
|
-
FAILURE_FORMAT = <<~'MESSAGE'
|
15
|
-
Can not validate %<subscription_name>s license.
|
16
|
-
Licensed:
|
17
|
-
%<expected>s
|
18
|
-
Present:
|
19
|
-
%<actual>s
|
20
|
-
MESSAGE
|
21
|
-
|
22
|
-
# Load value into subscription
|
23
|
-
#
|
24
|
-
# @param [Object] value
|
25
|
-
#
|
26
|
-
# @return [Subscription]
|
27
|
-
def self.load(world, value)
|
28
|
-
{
|
29
|
-
'com' => Commercial,
|
30
|
-
'oss' => Opensource
|
31
|
-
}.fetch(value.fetch('type'))
|
32
|
-
.from_json(value.fetch('contents'))
|
33
|
-
.call(world)
|
34
|
-
end
|
35
|
-
|
36
|
-
# Subscription self description
|
37
|
-
#
|
38
|
-
# @return [String]
|
39
|
-
def description
|
40
|
-
FORMAT % {
|
41
|
-
licensed: licensed.to_a.join("\n"),
|
42
|
-
subscription_name: subscription_name
|
43
|
-
}
|
44
|
-
end
|
45
|
-
|
46
|
-
private
|
47
|
-
|
48
|
-
def failure(expected, actual)
|
49
|
-
Either::Left.new(failure_message(expected, actual))
|
50
|
-
end
|
51
|
-
|
52
|
-
def success
|
53
|
-
Either::Right.new(self)
|
54
|
-
end
|
55
|
-
|
56
|
-
def subscription_name
|
57
|
-
self.class::SUBSCRIPTION_NAME
|
58
|
-
end
|
59
|
-
|
60
|
-
def failure_message(expected, actual)
|
61
|
-
FAILURE_FORMAT % {
|
62
|
-
actual: actual.any? ? actual.map(&:to_s).join("\n") : '[none]',
|
63
|
-
expected: expected.map(&:to_s).join("\n"),
|
64
|
-
subscription_name: subscription_name
|
65
|
-
}
|
66
|
-
end
|
67
|
-
end # Subscription
|
68
|
-
end # License
|
69
|
-
end # Mutant
|
data/lib/mutant/license.rb
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mutant
|
4
|
-
module License
|
5
|
-
NAME = 'mutant-license'
|
6
|
-
VERSION = ['>= 0.1', '< 0.3'].freeze
|
7
|
-
|
8
|
-
# Load license
|
9
|
-
#
|
10
|
-
# @param [World] world
|
11
|
-
#
|
12
|
-
# @return [Either<String,Subscription>]
|
13
|
-
#
|
14
|
-
# @api private
|
15
|
-
def self.call(world)
|
16
|
-
load_mutant_license(world)
|
17
|
-
.fmap { license_path(world) }
|
18
|
-
.bind { |path| Subscription.load(world, world.json.load(path)) }
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.load_mutant_license(world)
|
22
|
-
Either
|
23
|
-
.wrap_error(LoadError) { world.gem_method.call(NAME, *VERSION) }
|
24
|
-
.lmap(&:message)
|
25
|
-
.lmap(&method(:check_for_rubygems_mutant_license))
|
26
|
-
end
|
27
|
-
private_class_method :load_mutant_license
|
28
|
-
|
29
|
-
def self.check_for_rubygems_mutant_license(message)
|
30
|
-
if message.include?('already activated mutant-license-0.0.0')
|
31
|
-
'mutant-license gem from rubygems.org is a dummy'
|
32
|
-
else
|
33
|
-
message
|
34
|
-
end
|
35
|
-
end
|
36
|
-
private_class_method :check_for_rubygems_mutant_license
|
37
|
-
|
38
|
-
def self.license_path(world)
|
39
|
-
world
|
40
|
-
.pathname
|
41
|
-
.new(world.gem.loaded_specs.fetch(NAME).full_gem_path)
|
42
|
-
.join('license.json')
|
43
|
-
end
|
44
|
-
private_class_method :license_path
|
45
|
-
end
|
46
|
-
end
|