mutant 0.16.0 → 0.16.2
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 +4 -4
- data/VERSION +1 -1
- data/lib/mutant/ast/pattern/lexer.rb +119 -47
- data/lib/mutant/cli/command/session.rb +1 -1
- data/lib/mutant/integration/null.rb +1 -1
- data/lib/mutant/isolation/fork.rb +1 -1
- data/lib/mutant/isolation/none.rb +1 -1
- data/lib/mutant/isolation.rb +9 -9
- data/lib/mutant/log_capture.rb +89 -0
- data/lib/mutant/parallel/connection.rb +2 -2
- data/lib/mutant/reporter/cli/printer/isolation_result.rb +3 -6
- data/lib/mutant/reporter/cli/printer/test.rb +1 -1
- data/lib/mutant/result/exception.rb +1 -1
- data/lib/mutant/result/json_writer.rb +1 -1
- data/lib/mutant/result/process_status.rb +1 -1
- data/lib/mutant/result/session.rb +3 -3
- data/lib/mutant/result/test.rb +29 -2
- data/lib/mutant/result.rb +12 -12
- data/lib/mutant/test/runner/sink.rb +1 -1
- data/lib/mutant/transform/codec.rb +45 -0
- data/lib/mutant.rb +2 -1
- metadata +4 -3
- data/lib/mutant/transform/json.rb +0 -68
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8f5176c82dd4c6a57785deebd566c3b29b14b4496d35c06e15c20ff3b98a0b37
|
|
4
|
+
data.tar.gz: 9f29a560a6c144480d8808cf24ec2ec2127b78bc7f9ef88e8e8a3e0f541e0974
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a5e05c7b33041fb609ebcc89683461dba406c53592801d69603fa9003e0d8ee923f54d7b6f0bc9abcfd811ade64054ef3a8b9bf2bc30dbd44f37ca1f1ae2d4b6
|
|
7
|
+
data.tar.gz: 659e58f9ce856224a2a8f67d073c9006e340bcd838b0f6543cc57f8a12cbf6cc29c6dccad248744748a9f176750dae5240b719b797c7339a8552532e10d4ce22
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.16.
|
|
1
|
+
0.16.2
|
|
@@ -3,20 +3,43 @@
|
|
|
3
3
|
module Mutant
|
|
4
4
|
class AST
|
|
5
5
|
class Pattern
|
|
6
|
+
# rubocop:disable Metrics/ClassLength
|
|
6
7
|
class Lexer
|
|
7
|
-
WHITESPACE
|
|
8
|
-
STRING_PATTERN = /\A[a-zA-Z][_a-zA-Z0-9]*\z/
|
|
8
|
+
WHITESPACE = [' ', "\t", "\n"].to_set.freeze
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
STRUCTURAL =
|
|
11
11
|
{
|
|
12
12
|
'(' => :group_start,
|
|
13
13
|
')' => :group_end,
|
|
14
14
|
',' => :delimiter,
|
|
15
|
-
'=' => :eq,
|
|
16
15
|
'{' => :properties_start,
|
|
17
16
|
'}' => :properties_end
|
|
18
17
|
}.freeze
|
|
19
18
|
|
|
19
|
+
EQ_OPERATORS = %w[=== == =~].freeze
|
|
20
|
+
|
|
21
|
+
OPERATORS_BY_START =
|
|
22
|
+
{
|
|
23
|
+
'!' => %w[!= !~ !].freeze,
|
|
24
|
+
'<' => %w[<=> << <= <].freeze,
|
|
25
|
+
'>' => %w[>> >= >].freeze,
|
|
26
|
+
'+' => %w[+@ +].freeze,
|
|
27
|
+
'-' => %w[-@ -].freeze,
|
|
28
|
+
'*' => %w[** *].freeze,
|
|
29
|
+
'[' => ['[]=', '[]'].freeze,
|
|
30
|
+
'/' => ['/'].freeze,
|
|
31
|
+
'%' => ['%'].freeze,
|
|
32
|
+
'&' => ['&'].freeze,
|
|
33
|
+
'|' => ['|'].freeze,
|
|
34
|
+
'^' => ['^'].freeze,
|
|
35
|
+
'~' => ['~'].freeze
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
IDENTIFIER_START = /[a-zA-Z]/
|
|
39
|
+
IDENTIFIER_CONTINUE = /[a-zA-Z0-9_]/
|
|
40
|
+
|
|
41
|
+
SETTER_TERMINATORS = (WHITESPACE + [',', ')', '}']).freeze
|
|
42
|
+
|
|
20
43
|
def self.call(string)
|
|
21
44
|
new(string).__send__(:run)
|
|
22
45
|
end
|
|
@@ -31,7 +54,7 @@ module Mutant
|
|
|
31
54
|
#{token.display_location}
|
|
32
55
|
MESSAGE
|
|
33
56
|
end
|
|
34
|
-
end #
|
|
57
|
+
end # InvalidToken
|
|
35
58
|
end # Error
|
|
36
59
|
|
|
37
60
|
private_class_method :new
|
|
@@ -58,71 +81,123 @@ module Mutant
|
|
|
58
81
|
end
|
|
59
82
|
|
|
60
83
|
def consume
|
|
61
|
-
|
|
84
|
+
loop do
|
|
62
85
|
skip_whitespace
|
|
86
|
+
break unless next? && !instance_variable_defined?(:@error)
|
|
63
87
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
88
|
+
consume_structural \
|
|
89
|
+
|| consume_eq \
|
|
90
|
+
|| consume_operator \
|
|
91
|
+
|| consume_identifier \
|
|
92
|
+
|| consume_invalid
|
|
67
93
|
end
|
|
68
94
|
end
|
|
69
95
|
|
|
70
|
-
def
|
|
96
|
+
def consume_structural
|
|
97
|
+
char = peek
|
|
98
|
+
type = STRUCTURAL.fetch(char) { return }
|
|
71
99
|
start_position = @next_position
|
|
100
|
+
advance_position
|
|
101
|
+
@tokens << token(type:, start_position:)
|
|
102
|
+
end
|
|
72
103
|
|
|
73
|
-
|
|
104
|
+
def consume_eq
|
|
105
|
+
return unless peek.eql?('=')
|
|
106
|
+
|
|
107
|
+
start_position = @next_position
|
|
74
108
|
|
|
75
|
-
|
|
109
|
+
EQ_OPERATORS.each do |op|
|
|
110
|
+
next unless matches?(op)
|
|
111
|
+
|
|
112
|
+
advance_positions(op.length)
|
|
113
|
+
@tokens << token(type: :string, start_position:, value: op)
|
|
114
|
+
return true
|
|
115
|
+
end
|
|
76
116
|
|
|
77
117
|
advance_position
|
|
118
|
+
@tokens << token(type: :eq, start_position:)
|
|
119
|
+
end
|
|
78
120
|
|
|
79
|
-
|
|
121
|
+
def consume_operator
|
|
122
|
+
operators = OPERATORS_BY_START[peek] or return
|
|
123
|
+
match = operators.detect { |op| matches?(op) } or return
|
|
124
|
+
|
|
125
|
+
start_position = @next_position
|
|
126
|
+
advance_positions(match.length)
|
|
127
|
+
@tokens << token(type: :string, start_position:, value: match)
|
|
80
128
|
end
|
|
81
129
|
|
|
82
|
-
def
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
130
|
+
def consume_identifier
|
|
131
|
+
return unless IDENTIFIER_START.match?(peek)
|
|
132
|
+
|
|
133
|
+
start_position = @next_position
|
|
134
|
+
advance_position while IDENTIFIER_CONTINUE.match?(peek)
|
|
135
|
+
consume_identifier_suffix
|
|
136
|
+
|
|
137
|
+
@tokens << token(
|
|
138
|
+
type: :string,
|
|
139
|
+
start_position:,
|
|
140
|
+
value: @string[range_from(start_position)]
|
|
92
141
|
)
|
|
93
142
|
end
|
|
94
143
|
|
|
95
|
-
def
|
|
96
|
-
|
|
144
|
+
def consume_identifier_suffix
|
|
145
|
+
advance_position if suffix_char?(peek)
|
|
146
|
+
end
|
|
97
147
|
|
|
98
|
-
|
|
148
|
+
def suffix_char?(char)
|
|
149
|
+
char.eql?('!') || char.eql?('?') || (char.eql?('=') && setter_suffix_follows?)
|
|
150
|
+
end
|
|
99
151
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
end
|
|
152
|
+
def setter_suffix_follows?
|
|
153
|
+
next_char = @string[@next_position.succ]
|
|
154
|
+
|
|
155
|
+
next_char.nil? || SETTER_TERMINATORS.include?(next_char)
|
|
105
156
|
end
|
|
106
157
|
|
|
107
|
-
def
|
|
108
|
-
|
|
158
|
+
def consume_invalid
|
|
159
|
+
start_position = @next_position
|
|
160
|
+
advance_invalid
|
|
161
|
+
@error = Error::InvalidToken.new(
|
|
162
|
+
token: token(
|
|
163
|
+
type: :string,
|
|
164
|
+
start_position:,
|
|
165
|
+
value: @string[range_from(start_position)]
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
end
|
|
109
169
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
break
|
|
170
|
+
def advance_invalid
|
|
171
|
+
loop do
|
|
172
|
+
break unless next?
|
|
173
|
+
break if terminates_invalid?(peek)
|
|
113
174
|
|
|
114
|
-
string << char
|
|
115
175
|
advance_position
|
|
116
176
|
end
|
|
177
|
+
end
|
|
117
178
|
|
|
118
|
-
|
|
179
|
+
def terminates_invalid?(char)
|
|
180
|
+
STRUCTURAL.key?(char) || whitespace?(char)
|
|
119
181
|
end
|
|
120
182
|
|
|
121
|
-
def
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
183
|
+
def matches?(string)
|
|
184
|
+
@string[@next_position, string.length].eql?(string)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def advance_positions(count)
|
|
188
|
+
count.times { advance_position }
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def token(type:, start_position:, value: nil)
|
|
192
|
+
Token.new(
|
|
193
|
+
type:,
|
|
194
|
+
value:,
|
|
195
|
+
location: Source::Location.new(
|
|
196
|
+
source: @source,
|
|
197
|
+
line_index: @line_index,
|
|
198
|
+
line_start: @line_start,
|
|
199
|
+
range: range_from(start_position)
|
|
200
|
+
)
|
|
126
201
|
)
|
|
127
202
|
end
|
|
128
203
|
|
|
@@ -130,10 +205,6 @@ module Mutant
|
|
|
130
205
|
start_position...@next_position
|
|
131
206
|
end
|
|
132
207
|
|
|
133
|
-
def valid_string?(string)
|
|
134
|
-
STRING_PATTERN.match?(string)
|
|
135
|
-
end
|
|
136
|
-
|
|
137
208
|
def advance_position
|
|
138
209
|
@next_position += 1
|
|
139
210
|
end
|
|
@@ -165,6 +236,7 @@ module Mutant
|
|
|
165
236
|
@next_position < @string.length
|
|
166
237
|
end
|
|
167
238
|
end # Lexer
|
|
239
|
+
# rubocop:enable Metrics/ClassLength
|
|
168
240
|
end # Pattern
|
|
169
241
|
end # AST
|
|
170
242
|
end # Mutant
|
|
@@ -21,7 +21,7 @@ module Mutant
|
|
|
21
21
|
|
|
22
22
|
def load_session_file(path)
|
|
23
23
|
world.parse_json(path.read)
|
|
24
|
-
.bind(&Result::Session::
|
|
24
|
+
.bind(&Result::Session::CODEC.load_transform.public_method(:call))
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# Shared base for commands that operate on a session
|
data/lib/mutant/isolation.rb
CHANGED
|
@@ -25,11 +25,11 @@ module Mutant
|
|
|
25
25
|
dump = Transform::Success.new(
|
|
26
26
|
block: lambda do |object|
|
|
27
27
|
{
|
|
28
|
-
'exception' => object.exception && Mutant::Result::Exception::
|
|
29
|
-
'log' => object.log,
|
|
30
|
-
'process_status' => object.process_status && Mutant::Result::ProcessStatus::
|
|
28
|
+
'exception' => object.exception && Mutant::Result::Exception::CODEC.dump(object.exception).from_right,
|
|
29
|
+
'log' => LogCapture::CODEC.dump(object.log).from_right,
|
|
30
|
+
'process_status' => object.process_status && Mutant::Result::ProcessStatus::CODEC.dump(object.process_status).from_right,
|
|
31
31
|
'timeout' => object.timeout,
|
|
32
|
-
'value' => object.value && Mutant::Result::Test::
|
|
32
|
+
'value' => object.value && Mutant::Result::Test::CODEC.dump(object.value).from_right
|
|
33
33
|
}
|
|
34
34
|
end
|
|
35
35
|
)
|
|
@@ -38,11 +38,11 @@ module Mutant
|
|
|
38
38
|
steps: [
|
|
39
39
|
Transform::Hash.new(
|
|
40
40
|
required: [
|
|
41
|
-
Transform::Hash::Key.new(value: 'exception', transform: Transform::Nullable.new(transform: Mutant::Result::Exception::
|
|
42
|
-
Transform::Hash::Key.new(value: 'log', transform:
|
|
43
|
-
Transform::Hash::Key.new(value: 'process_status', transform: Transform::Nullable.new(transform: Mutant::Result::ProcessStatus::
|
|
41
|
+
Transform::Hash::Key.new(value: 'exception', transform: Transform::Nullable.new(transform: Mutant::Result::Exception::CODEC.load_transform)),
|
|
42
|
+
Transform::Hash::Key.new(value: 'log', transform: LogCapture::CODEC.load_transform),
|
|
43
|
+
Transform::Hash::Key.new(value: 'process_status', transform: Transform::Nullable.new(transform: Mutant::Result::ProcessStatus::CODEC.load_transform)),
|
|
44
44
|
Transform::Hash::Key.new(value: 'timeout', transform: Transform::Nullable.new(transform: Transform::FLOAT)),
|
|
45
|
-
Transform::Hash::Key.new(value: 'value', transform: Transform::Nullable.new(transform: Mutant::Result::Test::
|
|
45
|
+
Transform::Hash::Key.new(value: 'value', transform: Transform::Nullable.new(transform: Mutant::Result::Test::CODEC.load_transform))
|
|
46
46
|
],
|
|
47
47
|
optional: []
|
|
48
48
|
),
|
|
@@ -51,7 +51,7 @@ module Mutant
|
|
|
51
51
|
]
|
|
52
52
|
)
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
CODEC = Transform::Codec.new(dump_transform: dump, load_transform: load)
|
|
55
55
|
end # Result
|
|
56
56
|
|
|
57
57
|
# Call block in isolation
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mutant
|
|
4
|
+
# Captured log output from a worker or forked process.
|
|
5
|
+
#
|
|
6
|
+
# Raw worker log bytes may or may not be valid UTF-8 — they commonly contain
|
|
7
|
+
# terminal control sequences emitted by test frameworks. This class preserves
|
|
8
|
+
# those bytes unaltered for terminal emission while distinguishing UTF-8 text
|
|
9
|
+
# from arbitrary bytes in the codec representation.
|
|
10
|
+
class LogCapture
|
|
11
|
+
include AbstractType, Anima.new(:content)
|
|
12
|
+
|
|
13
|
+
# Build the appropriate subclass from raw bytes.
|
|
14
|
+
#
|
|
15
|
+
# Takes ownership of +bytes+; encoding is mutated in place.
|
|
16
|
+
#
|
|
17
|
+
# @param [::String] bytes
|
|
18
|
+
#
|
|
19
|
+
# @return [LogCapture]
|
|
20
|
+
def self.from_binary(bytes)
|
|
21
|
+
if bytes.force_encoding(Encoding::UTF_8).valid_encoding?
|
|
22
|
+
String.new(content: bytes)
|
|
23
|
+
else
|
|
24
|
+
Binary.new(content: bytes.force_encoding(Encoding::ASCII_8BIT))
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# UTF-8 valid log capture
|
|
29
|
+
class String < self
|
|
30
|
+
TYPE = 'string'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Non UTF-8 log capture, preserved as raw bytes
|
|
34
|
+
class Binary < self
|
|
35
|
+
TYPE = 'binary'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
dump = Transform::Success.new(
|
|
39
|
+
block: lambda do |object|
|
|
40
|
+
case object
|
|
41
|
+
when String
|
|
42
|
+
{ 'type' => String::TYPE, 'content' => object.content }
|
|
43
|
+
when Binary
|
|
44
|
+
{ 'type' => Binary::TYPE, 'content' => [object.content].pack('m0') }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Normalize legacy plain-string log representation into the tagged form.
|
|
50
|
+
# Session JSON files written before LogCapture store +log+ and +output+
|
|
51
|
+
# as plain strings; promote those to string-typed captures on load.
|
|
52
|
+
legacy = Transform::Block.capture('log_capture_legacy') do |input|
|
|
53
|
+
case input
|
|
54
|
+
when ::String
|
|
55
|
+
Either::Right.new('type' => String::TYPE, 'content' => input)
|
|
56
|
+
else
|
|
57
|
+
Either::Right.new(input)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
load = Transform::Sequence.new(
|
|
62
|
+
steps: [
|
|
63
|
+
legacy,
|
|
64
|
+
Transform::Hash.new(
|
|
65
|
+
required: [
|
|
66
|
+
Transform::Hash::Key.new(value: 'type', transform: Transform::STRING),
|
|
67
|
+
Transform::Hash::Key.new(value: 'content', transform: Transform::STRING)
|
|
68
|
+
],
|
|
69
|
+
optional: []
|
|
70
|
+
),
|
|
71
|
+
Transform::Block.capture('log_capture') do |hash|
|
|
72
|
+
type = hash.fetch('type')
|
|
73
|
+
content = hash.fetch('content')
|
|
74
|
+
|
|
75
|
+
case type
|
|
76
|
+
when String::TYPE
|
|
77
|
+
Either::Right.new(String.new(content: content))
|
|
78
|
+
when Binary::TYPE
|
|
79
|
+
Either::Right.new(Binary.new(content: content.unpack1('m0').force_encoding(Encoding::ASCII_8BIT)))
|
|
80
|
+
else
|
|
81
|
+
Either::Left.new("Unknown log capture type: #{type.inspect}")
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
]
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
CODEC = Transform::Codec.new(dump_transform: dump, load_transform: load)
|
|
88
|
+
end # LogCapture
|
|
89
|
+
end # Mutant
|
|
@@ -51,15 +51,12 @@ module Mutant
|
|
|
51
51
|
private
|
|
52
52
|
|
|
53
53
|
def print_log_messages
|
|
54
|
-
log = object.log
|
|
54
|
+
log = object.log.content
|
|
55
55
|
|
|
56
56
|
return if log.empty?
|
|
57
57
|
|
|
58
|
-
puts('
|
|
59
|
-
|
|
60
|
-
log.each_line do |line|
|
|
61
|
-
puts('[killfork] %<line>s' % { line: })
|
|
62
|
-
end
|
|
58
|
+
puts('Killfork log (combined stderr and stdout):')
|
|
59
|
+
puts(log)
|
|
63
60
|
end
|
|
64
61
|
|
|
65
62
|
def print_process_status
|
|
@@ -32,7 +32,7 @@ module Mutant
|
|
|
32
32
|
'ruby_version' => object.ruby_version,
|
|
33
33
|
'runtime' => object.runtime,
|
|
34
34
|
'session_id' => object.session_id,
|
|
35
|
-
'subject_results' => object.subject_results.map { |subject_result| Subject::
|
|
35
|
+
'subject_results' => object.subject_results.map { |subject_result| Subject::CODEC.dump(subject_result).from_right }
|
|
36
36
|
}
|
|
37
37
|
end
|
|
38
38
|
)
|
|
@@ -47,7 +47,7 @@ module Mutant
|
|
|
47
47
|
Transform::Hash::Key.new(value: 'ruby_version', transform: Transform::STRING),
|
|
48
48
|
Transform::Hash::Key.new(value: 'runtime', transform: Transform::FLOAT),
|
|
49
49
|
Transform::Hash::Key.new(value: 'session_id', transform: Transform::STRING),
|
|
50
|
-
Transform::Hash::Key.new(value: 'subject_results', transform: Transform::Array.new(transform: Subject::
|
|
50
|
+
Transform::Hash::Key.new(value: 'subject_results', transform: Transform::Array.new(transform: Subject::CODEC.load_transform))
|
|
51
51
|
],
|
|
52
52
|
optional: []
|
|
53
53
|
),
|
|
@@ -56,7 +56,7 @@ module Mutant
|
|
|
56
56
|
]
|
|
57
57
|
)
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
CODEC = Transform::Codec.new(dump_transform: dump, load_transform: load)
|
|
60
60
|
|
|
61
61
|
end # Session
|
|
62
62
|
end # Result
|
data/lib/mutant/result/test.rb
CHANGED
|
@@ -17,14 +17,41 @@ module Mutant
|
|
|
17
17
|
def initialize
|
|
18
18
|
super(
|
|
19
19
|
job_index: nil,
|
|
20
|
-
output: '',
|
|
20
|
+
output: LogCapture.from_binary(+''),
|
|
21
21
|
passed: false,
|
|
22
22
|
runtime: 0.0
|
|
23
23
|
)
|
|
24
24
|
end
|
|
25
25
|
end # VoidValue
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
dump = Transform::Success.new(
|
|
28
|
+
block: lambda do |object|
|
|
29
|
+
{
|
|
30
|
+
'job_index' => object.job_index,
|
|
31
|
+
'output' => LogCapture::CODEC.dump(object.output).from_right,
|
|
32
|
+
'passed' => object.passed,
|
|
33
|
+
'runtime' => object.runtime
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
load = Transform::Sequence.new(
|
|
39
|
+
steps: [
|
|
40
|
+
Transform::Hash.new(
|
|
41
|
+
required: [
|
|
42
|
+
Transform::Hash::Key.new(value: 'job_index', transform: Transform::Nullable.new(transform: Transform::INTEGER)),
|
|
43
|
+
Transform::Hash::Key.new(value: 'output', transform: LogCapture::CODEC.load_transform),
|
|
44
|
+
Transform::Hash::Key.new(value: 'passed', transform: Transform::BOOLEAN),
|
|
45
|
+
Transform::Hash::Key.new(value: 'runtime', transform: Transform::FLOAT)
|
|
46
|
+
],
|
|
47
|
+
optional: []
|
|
48
|
+
),
|
|
49
|
+
Transform::Hash::Symbolize.new,
|
|
50
|
+
Transform::Success.new(block: method(:new).to_proc)
|
|
51
|
+
]
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
CODEC = Transform::Codec.new(dump_transform: dump, load_transform: load)
|
|
28
55
|
end # Test
|
|
29
56
|
end # Result
|
|
30
57
|
end # Mutant
|
data/lib/mutant/result.rb
CHANGED
|
@@ -150,7 +150,7 @@ module Mutant
|
|
|
150
150
|
process_abort || test_result || timeout
|
|
151
151
|
end
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
CODEC = Transform::Codec.for_anima(self)
|
|
154
154
|
end
|
|
155
155
|
|
|
156
156
|
class MutationIndex
|
|
@@ -226,7 +226,7 @@ module Mutant
|
|
|
226
226
|
dump = Transform::Success.new(
|
|
227
227
|
block: lambda do |object|
|
|
228
228
|
{
|
|
229
|
-
'isolation_result' => Isolation::Result::
|
|
229
|
+
'isolation_result' => Isolation::Result::CODEC.dump(object.isolation_result).from_right,
|
|
230
230
|
'mutation_diff' => object.mutation_diff,
|
|
231
231
|
'mutation_identification' => object.mutation_identification,
|
|
232
232
|
'mutation_source' => object.mutation_source,
|
|
@@ -244,7 +244,7 @@ module Mutant
|
|
|
244
244
|
steps: [
|
|
245
245
|
Transform::Hash.new(
|
|
246
246
|
required: [
|
|
247
|
-
Transform::Hash::Key.new(value: 'isolation_result', transform: Isolation::Result::
|
|
247
|
+
Transform::Hash::Key.new(value: 'isolation_result', transform: Isolation::Result::CODEC.load_transform),
|
|
248
248
|
Transform::Hash::Key.new(value: 'mutation_diff', transform: Transform::OPTIONAL_STRING),
|
|
249
249
|
Transform::Hash::Key.new(value: 'mutation_identification', transform: Transform::STRING),
|
|
250
250
|
Transform::Hash::Key.new(value: 'mutation_source', transform: Transform::STRING),
|
|
@@ -259,7 +259,7 @@ module Mutant
|
|
|
259
259
|
]
|
|
260
260
|
)
|
|
261
261
|
|
|
262
|
-
|
|
262
|
+
CODEC = Transform::Codec.new(dump_transform: dump, load_transform: load)
|
|
263
263
|
end # Mutation
|
|
264
264
|
|
|
265
265
|
# Coverage of a mutation against criteria
|
|
@@ -277,8 +277,8 @@ module Mutant
|
|
|
277
277
|
dump = Transform::Success.new(
|
|
278
278
|
block: lambda do |object|
|
|
279
279
|
{
|
|
280
|
-
'mutation_result' => Mutation::
|
|
281
|
-
'criteria_result' => CoverageCriteria::
|
|
280
|
+
'mutation_result' => Mutation::CODEC.dump(object.mutation_result).from_right,
|
|
281
|
+
'criteria_result' => CoverageCriteria::CODEC.dump(object.criteria_result).from_right
|
|
282
282
|
}
|
|
283
283
|
end
|
|
284
284
|
)
|
|
@@ -287,8 +287,8 @@ module Mutant
|
|
|
287
287
|
steps: [
|
|
288
288
|
Transform::Hash.new(
|
|
289
289
|
required: [
|
|
290
|
-
Transform::Hash::Key.new(value: 'mutation_result', transform: Mutation::
|
|
291
|
-
Transform::Hash::Key.new(value: 'criteria_result', transform: CoverageCriteria::
|
|
290
|
+
Transform::Hash::Key.new(value: 'mutation_result', transform: Mutation::CODEC.load_transform),
|
|
291
|
+
Transform::Hash::Key.new(value: 'criteria_result', transform: CoverageCriteria::CODEC.load_transform)
|
|
292
292
|
],
|
|
293
293
|
optional: []
|
|
294
294
|
),
|
|
@@ -297,7 +297,7 @@ module Mutant
|
|
|
297
297
|
]
|
|
298
298
|
)
|
|
299
299
|
|
|
300
|
-
|
|
300
|
+
CODEC = Transform::Codec.new(dump_transform: dump, load_transform: load)
|
|
301
301
|
end # Coverage
|
|
302
302
|
|
|
303
303
|
# Subject result
|
|
@@ -359,7 +359,7 @@ module Mutant
|
|
|
359
359
|
{
|
|
360
360
|
'amount_mutations' => object.amount_mutations,
|
|
361
361
|
'coverage_results' => object.coverage_results
|
|
362
|
-
.map { |coverage_result| Coverage::
|
|
362
|
+
.map { |coverage_result| Coverage::CODEC.dump(coverage_result).from_right },
|
|
363
363
|
'expression_syntax' => object.expression_syntax,
|
|
364
364
|
'identification' => object.identification,
|
|
365
365
|
'source' => object.source,
|
|
@@ -387,7 +387,7 @@ module Mutant
|
|
|
387
387
|
Transform::Hash.new(
|
|
388
388
|
required: [
|
|
389
389
|
Transform::Hash::Key.new(value: 'amount_mutations', transform: Transform::INTEGER),
|
|
390
|
-
Transform::Hash::Key.new(value: 'coverage_results', transform: Transform::Array.new(transform: Coverage::
|
|
390
|
+
Transform::Hash::Key.new(value: 'coverage_results', transform: Transform::Array.new(transform: Coverage::CODEC.load_transform)),
|
|
391
391
|
Transform::Hash::Key.new(value: 'expression_syntax', transform: Transform::STRING),
|
|
392
392
|
Transform::Hash::Key.new(value: 'identification', transform: Transform::STRING),
|
|
393
393
|
Transform::Hash::Key.new(value: 'source', transform: Transform::STRING),
|
|
@@ -405,7 +405,7 @@ module Mutant
|
|
|
405
405
|
]
|
|
406
406
|
)
|
|
407
407
|
|
|
408
|
-
|
|
408
|
+
CODEC = Transform::Codec.new(dump_transform: dump, load_transform: load)
|
|
409
409
|
end # Subject
|
|
410
410
|
end # Result
|
|
411
411
|
end # Mutant
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mutant
|
|
4
|
+
class Transform
|
|
5
|
+
# Bidirectional codec over Ruby objects.
|
|
6
|
+
#
|
|
7
|
+
# Wraps a pair of dump/load transforms that convert between domain
|
|
8
|
+
# objects and a JSON-compatible Ruby structure (hashes, arrays,
|
|
9
|
+
# primitives). The outer layer that converts the structure to/from
|
|
10
|
+
# a JSON string is handled by callers as needed.
|
|
11
|
+
class Codec
|
|
12
|
+
include Anima.new(:dump_transform, :load_transform)
|
|
13
|
+
|
|
14
|
+
# Build a codec for simple Anima objects with primitive fields
|
|
15
|
+
#
|
|
16
|
+
# @param [Class] klass
|
|
17
|
+
#
|
|
18
|
+
# @return [Codec]
|
|
19
|
+
def self.for_anima(klass)
|
|
20
|
+
new(
|
|
21
|
+
dump_transform: Success.new(block: ->(object) { object.to_h.transform_keys(&:to_s) }),
|
|
22
|
+
load_transform: Success.new(block: ->(hash) { klass.new(**hash.transform_keys(&:to_sym)) })
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Dump object to Ruby structure
|
|
27
|
+
#
|
|
28
|
+
# @param [Object] object
|
|
29
|
+
#
|
|
30
|
+
# @return [Either<Error, Object>]
|
|
31
|
+
def dump(object)
|
|
32
|
+
dump_transform.call(object)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Load object from Ruby structure
|
|
36
|
+
#
|
|
37
|
+
# @param [Object] input
|
|
38
|
+
#
|
|
39
|
+
# @return [Either<Error, Object>]
|
|
40
|
+
def load(input)
|
|
41
|
+
load_transform.call(input)
|
|
42
|
+
end
|
|
43
|
+
end # Codec
|
|
44
|
+
end # Transform
|
|
45
|
+
end # Mutant
|
data/lib/mutant.rb
CHANGED
|
@@ -69,7 +69,8 @@ module Mutant
|
|
|
69
69
|
record.call(:require_mutant_lib) do
|
|
70
70
|
require 'mutant/procto'
|
|
71
71
|
require 'mutant/transform'
|
|
72
|
-
require 'mutant/transform/
|
|
72
|
+
require 'mutant/transform/codec'
|
|
73
|
+
require 'mutant/log_capture'
|
|
73
74
|
require 'mutant/variable'
|
|
74
75
|
require 'mutant/bootstrap'
|
|
75
76
|
require 'mutant/version'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mutant
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.16.
|
|
4
|
+
version: 0.16.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Markus Schirp
|
|
@@ -252,6 +252,7 @@ files:
|
|
|
252
252
|
- lib/mutant/isolation/fork.rb
|
|
253
253
|
- lib/mutant/isolation/none.rb
|
|
254
254
|
- lib/mutant/loader.rb
|
|
255
|
+
- lib/mutant/log_capture.rb
|
|
255
256
|
- lib/mutant/matcher.rb
|
|
256
257
|
- lib/mutant/matcher/chain.rb
|
|
257
258
|
- lib/mutant/matcher/config.rb
|
|
@@ -403,7 +404,7 @@ files:
|
|
|
403
404
|
- lib/mutant/test/runner/sink.rb
|
|
404
405
|
- lib/mutant/timer.rb
|
|
405
406
|
- lib/mutant/transform.rb
|
|
406
|
-
- lib/mutant/transform/
|
|
407
|
+
- lib/mutant/transform/codec.rb
|
|
407
408
|
- lib/mutant/usage.rb
|
|
408
409
|
- lib/mutant/util.rb
|
|
409
410
|
- lib/mutant/variable.rb
|
|
@@ -430,7 +431,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
430
431
|
- !ruby/object:Gem::Version
|
|
431
432
|
version: '0'
|
|
432
433
|
requirements: []
|
|
433
|
-
rubygems_version:
|
|
434
|
+
rubygems_version: 4.0.6
|
|
434
435
|
specification_version: 4
|
|
435
436
|
summary: ''
|
|
436
437
|
test_files: []
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Mutant
|
|
4
|
-
class Transform
|
|
5
|
-
# Bidirectional JSON transform
|
|
6
|
-
#
|
|
7
|
-
# Wraps a pair of dump/load transforms and optionally adds
|
|
8
|
-
# JSON string serialization via .build
|
|
9
|
-
class JSON
|
|
10
|
-
include Anima.new(:dump_transform, :load_transform)
|
|
11
|
-
|
|
12
|
-
# Build a JSON transform that wraps raw transforms with JSON.parse/generate
|
|
13
|
-
#
|
|
14
|
-
# @param [Transform] dump
|
|
15
|
-
# @param [Transform] load
|
|
16
|
-
#
|
|
17
|
-
# @return [JSON]
|
|
18
|
-
# rubocop:disable Metrics/MethodLength
|
|
19
|
-
def self.build(dump:, load:)
|
|
20
|
-
new(
|
|
21
|
-
dump_transform: Sequence.new(
|
|
22
|
-
steps: [
|
|
23
|
-
dump,
|
|
24
|
-
Success.new(block: ::JSON.public_method(:generate))
|
|
25
|
-
]
|
|
26
|
-
),
|
|
27
|
-
load_transform: Sequence.new(
|
|
28
|
-
steps: [
|
|
29
|
-
Exception.new(error_class: ::JSON::ParserError, block: ::JSON.public_method(:parse)),
|
|
30
|
-
load
|
|
31
|
-
]
|
|
32
|
-
)
|
|
33
|
-
)
|
|
34
|
-
end
|
|
35
|
-
# rubocop:enable Metrics/MethodLength
|
|
36
|
-
|
|
37
|
-
# Build a JSON transform for simple Anima objects with JSON-primitive fields
|
|
38
|
-
#
|
|
39
|
-
# @param [Class] klass
|
|
40
|
-
#
|
|
41
|
-
# @return [JSON]
|
|
42
|
-
def self.for_anima(klass)
|
|
43
|
-
new(
|
|
44
|
-
dump_transform: Success.new(block: ->(object) { object.to_h.transform_keys(&:to_s) }),
|
|
45
|
-
load_transform: Success.new(block: ->(hash) { klass.new(**hash.transform_keys(&:to_sym)) })
|
|
46
|
-
)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Dump object to hash or JSON string
|
|
50
|
-
#
|
|
51
|
-
# @param [Object] object
|
|
52
|
-
#
|
|
53
|
-
# @return [Either<Error, Object>]
|
|
54
|
-
def dump(object)
|
|
55
|
-
dump_transform.call(object)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Load object from hash or JSON string
|
|
59
|
-
#
|
|
60
|
-
# @param [Object] input
|
|
61
|
-
#
|
|
62
|
-
# @return [Either<Error, Object>]
|
|
63
|
-
def load(input)
|
|
64
|
-
load_transform.call(input)
|
|
65
|
-
end
|
|
66
|
-
end # JSON
|
|
67
|
-
end # Transform
|
|
68
|
-
end # Mutant
|