evilution 0.6.0 → 0.8.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 +4 -4
- data/.beads/.migration-hint-ts +1 -1
- data/.beads/issues.jsonl +42 -12
- data/CHANGELOG.md +27 -0
- data/README.md +51 -4
- data/Rakefile +2 -0
- data/lib/evilution/baseline.rb +100 -0
- data/lib/evilution/cli.rb +14 -0
- data/lib/evilution/config.rb +36 -15
- data/lib/evilution/isolation/fork.rb +5 -2
- data/lib/evilution/isolation/in_process.rb +78 -0
- data/lib/evilution/memory/leak_check.rb +91 -0
- data/lib/evilution/memory.rb +40 -0
- data/lib/evilution/mutation.rb +15 -1
- data/lib/evilution/mutator/operator/argument_removal.rb +42 -0
- data/lib/evilution/mutator/operator/block_removal.rb +27 -0
- data/lib/evilution/mutator/operator/conditional_flip.rb +39 -0
- data/lib/evilution/mutator/operator/range_replacement.rb +22 -0
- data/lib/evilution/mutator/operator/regexp_mutation.rb +22 -0
- data/lib/evilution/mutator/registry.rb +6 -1
- data/lib/evilution/reporter/cli.rb +34 -12
- data/lib/evilution/reporter/json.rb +6 -0
- data/lib/evilution/reporter/suggestion.rb +2 -1
- data/lib/evilution/result/mutation_result.rb +11 -3
- data/lib/evilution/result/summary.rb +20 -1
- data/lib/evilution/runner.rb +178 -29
- data/lib/evilution/subject.rb +4 -1
- data/lib/evilution/version.rb +1 -1
- data/lib/evilution.rb +8 -0
- data/lib/tasks/memory_check.rake +9 -0
- data/script/memory_check +94 -0
- metadata +13 -2
data/lib/evilution/runner.rb
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "config"
|
|
4
4
|
require_relative "ast/parser"
|
|
5
|
+
require_relative "memory"
|
|
5
6
|
require_relative "mutator/registry"
|
|
6
7
|
require_relative "isolation/fork"
|
|
8
|
+
require_relative "isolation/in_process"
|
|
7
9
|
require_relative "integration/rspec"
|
|
8
10
|
require_relative "reporter/json"
|
|
9
11
|
require_relative "reporter/cli"
|
|
@@ -13,6 +15,7 @@ require_relative "diff/file_filter"
|
|
|
13
15
|
require_relative "git/changed_files"
|
|
14
16
|
require_relative "result/mutation_result"
|
|
15
17
|
require_relative "result/summary"
|
|
18
|
+
require_relative "baseline"
|
|
16
19
|
require_relative "parallel/pool"
|
|
17
20
|
|
|
18
21
|
module Evilution
|
|
@@ -23,7 +26,7 @@ module Evilution
|
|
|
23
26
|
@config = config
|
|
24
27
|
@parser = AST::Parser.new
|
|
25
28
|
@registry = Mutator::Registry.default
|
|
26
|
-
@isolator =
|
|
29
|
+
@isolator = build_isolator
|
|
27
30
|
end
|
|
28
31
|
|
|
29
32
|
def call
|
|
@@ -33,8 +36,14 @@ module Evilution
|
|
|
33
36
|
subjects = filter_by_target(subjects) if config.target?
|
|
34
37
|
subjects = filter_by_line_ranges(subjects) if config.line_ranges?
|
|
35
38
|
subjects = filter_by_diff(subjects) if config.diff?
|
|
39
|
+
log_memory("after parse_subjects", "#{subjects.length} subjects")
|
|
40
|
+
|
|
41
|
+
baseline_result = run_baseline(subjects)
|
|
42
|
+
|
|
36
43
|
mutations = generate_mutations(subjects)
|
|
37
|
-
results, truncated = run_mutations(mutations)
|
|
44
|
+
results, truncated = run_mutations(mutations, baseline_result)
|
|
45
|
+
log_memory("after run_mutations", "#{results.length} results")
|
|
46
|
+
|
|
38
47
|
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
|
|
39
48
|
|
|
40
49
|
summary = Result::Summary.new(results: results, duration: duration, truncated: truncated)
|
|
@@ -83,19 +92,34 @@ module Evilution
|
|
|
83
92
|
end
|
|
84
93
|
|
|
85
94
|
def generate_mutations(subjects)
|
|
86
|
-
subjects.flat_map
|
|
95
|
+
subjects.flat_map do |subject|
|
|
96
|
+
mutations = registry.mutations_for(subject)
|
|
97
|
+
subject.release_node!
|
|
98
|
+
mutations
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def run_baseline(subjects)
|
|
103
|
+
return nil unless config.baseline? && subjects.any?
|
|
104
|
+
|
|
105
|
+
log_baseline_start
|
|
106
|
+
baseline = Baseline.new(timeout: config.timeout)
|
|
107
|
+
result = baseline.call(subjects)
|
|
108
|
+
log_baseline_complete(result)
|
|
109
|
+
result
|
|
87
110
|
end
|
|
88
111
|
|
|
89
|
-
def run_mutations(mutations)
|
|
112
|
+
def run_mutations(mutations, baseline_result = nil)
|
|
90
113
|
if config.jobs > 1
|
|
91
|
-
run_mutations_parallel(mutations)
|
|
114
|
+
run_mutations_parallel(mutations, baseline_result)
|
|
92
115
|
else
|
|
93
|
-
run_mutations_sequential(mutations)
|
|
116
|
+
run_mutations_sequential(mutations, baseline_result)
|
|
94
117
|
end
|
|
95
118
|
end
|
|
96
119
|
|
|
97
|
-
def run_mutations_sequential(mutations)
|
|
120
|
+
def run_mutations_sequential(mutations, baseline_result = nil)
|
|
98
121
|
integration = build_integration
|
|
122
|
+
spec_resolver = baseline_result&.failed? ? SpecResolver.new : nil
|
|
99
123
|
results = []
|
|
100
124
|
survived_count = 0
|
|
101
125
|
truncated = false
|
|
@@ -107,11 +131,14 @@ module Evilution
|
|
|
107
131
|
test_command: test_command,
|
|
108
132
|
timeout: config.timeout
|
|
109
133
|
)
|
|
134
|
+
mutation.strip_sources!
|
|
135
|
+
result = neutralize_if_baseline_failed(result, baseline_result, spec_resolver)
|
|
110
136
|
results << result
|
|
111
137
|
survived_count += 1 if result.survived?
|
|
112
|
-
log_progress(index + 1,
|
|
138
|
+
log_progress(index + 1, result.status)
|
|
139
|
+
log_mutation_diagnostics(result)
|
|
113
140
|
|
|
114
|
-
if config.fail_fast? && survived_count >= config.fail_fast
|
|
141
|
+
if config.fail_fast? && survived_count >= config.fail_fast
|
|
115
142
|
truncated = true
|
|
116
143
|
break
|
|
117
144
|
end
|
|
@@ -120,37 +147,105 @@ module Evilution
|
|
|
120
147
|
[results, truncated]
|
|
121
148
|
end
|
|
122
149
|
|
|
123
|
-
def run_mutations_parallel(mutations)
|
|
150
|
+
def run_mutations_parallel(mutations, baseline_result = nil)
|
|
124
151
|
integration = build_integration
|
|
125
152
|
pool = Parallel::Pool.new(size: config.jobs)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
completed = 0
|
|
153
|
+
worker_isolator = Isolation::InProcess.new
|
|
154
|
+
spec_resolver = baseline_result&.failed? ? SpecResolver.new : nil
|
|
155
|
+
state = { results: [], survived_count: 0, truncated: false, completed: 0 }
|
|
130
156
|
|
|
131
157
|
mutations.each_slice(config.jobs) do |batch|
|
|
132
|
-
break if truncated
|
|
158
|
+
break if state[:truncated]
|
|
133
159
|
|
|
134
|
-
|
|
160
|
+
compact_results = pool.map(batch) do |mutation|
|
|
135
161
|
test_command = ->(m) { integration.call(m) }
|
|
136
|
-
|
|
162
|
+
result = worker_isolator.call(mutation: mutation, test_command: test_command, timeout: config.timeout)
|
|
163
|
+
compact_result(result)
|
|
137
164
|
end
|
|
138
165
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
166
|
+
batch.each(&:strip_sources!)
|
|
167
|
+
batch_results = rebuild_results(batch, compact_results)
|
|
168
|
+
process_batch(batch_results, baseline_result, spec_resolver, state)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
[state[:results], state[:truncated]]
|
|
172
|
+
end
|
|
145
173
|
|
|
146
|
-
|
|
174
|
+
def process_batch(batch_results, baseline_result, spec_resolver, state)
|
|
175
|
+
batch_results.each do |result|
|
|
176
|
+
result = neutralize_if_baseline_failed(result, baseline_result, spec_resolver)
|
|
177
|
+
state[:results] << result
|
|
178
|
+
state[:survived_count] += 1 if result.survived?
|
|
179
|
+
state[:completed] += 1
|
|
180
|
+
log_progress(state[:completed], result.status)
|
|
181
|
+
log_mutation_diagnostics(result)
|
|
147
182
|
end
|
|
148
183
|
|
|
149
|
-
|
|
184
|
+
log_memory("after batch", "#{state[:completed]} complete")
|
|
185
|
+
state[:truncated] = true if should_truncate?(state[:survived_count])
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def neutralize_if_baseline_failed(result, baseline_result, spec_resolver)
|
|
189
|
+
return result unless result.survived? && baseline_result && baseline_result.failed?
|
|
190
|
+
|
|
191
|
+
if config.spec_files.any?
|
|
192
|
+
neutralize = true
|
|
193
|
+
else
|
|
194
|
+
spec_file = spec_resolver.call(result.mutation.file_path) || "spec"
|
|
195
|
+
neutralize = baseline_result.failed_spec_files.include?(spec_file)
|
|
196
|
+
end
|
|
197
|
+
return result unless neutralize
|
|
198
|
+
|
|
199
|
+
Result::MutationResult.new(
|
|
200
|
+
mutation: result.mutation,
|
|
201
|
+
status: :neutral,
|
|
202
|
+
duration: result.duration,
|
|
203
|
+
test_command: result.test_command,
|
|
204
|
+
child_rss_kb: result.child_rss_kb,
|
|
205
|
+
memory_delta_kb: result.memory_delta_kb
|
|
206
|
+
)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def compact_result(result)
|
|
210
|
+
{
|
|
211
|
+
status: result.status,
|
|
212
|
+
duration: result.duration,
|
|
213
|
+
killing_test: result.killing_test,
|
|
214
|
+
test_command: result.test_command,
|
|
215
|
+
child_rss_kb: result.child_rss_kb,
|
|
216
|
+
memory_delta_kb: result.memory_delta_kb
|
|
217
|
+
}
|
|
150
218
|
end
|
|
151
219
|
|
|
152
|
-
def
|
|
153
|
-
|
|
220
|
+
def rebuild_results(batch, compact_results)
|
|
221
|
+
batch.zip(compact_results).map do |mutation, data|
|
|
222
|
+
Result::MutationResult.new(
|
|
223
|
+
mutation: mutation,
|
|
224
|
+
status: data[:status],
|
|
225
|
+
duration: data[:duration],
|
|
226
|
+
killing_test: data[:killing_test],
|
|
227
|
+
test_command: data[:test_command],
|
|
228
|
+
child_rss_kb: data[:child_rss_kb],
|
|
229
|
+
memory_delta_kb: data[:memory_delta_kb]
|
|
230
|
+
)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def should_truncate?(survived_count)
|
|
235
|
+
config.fail_fast? && survived_count >= config.fail_fast
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def build_isolator
|
|
239
|
+
case resolve_isolation
|
|
240
|
+
when :fork then Isolation::Fork.new
|
|
241
|
+
when :in_process then Isolation::InProcess.new
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def resolve_isolation
|
|
246
|
+
return :fork if config.isolation == :fork
|
|
247
|
+
|
|
248
|
+
:in_process
|
|
154
249
|
end
|
|
155
250
|
|
|
156
251
|
def build_integration
|
|
@@ -171,10 +266,64 @@ module Evilution
|
|
|
171
266
|
$stdout.puts(output) unless config.quiet
|
|
172
267
|
end
|
|
173
268
|
|
|
174
|
-
def
|
|
269
|
+
def log_baseline_start
|
|
270
|
+
return if config.quiet || !config.text? || !$stderr.tty?
|
|
271
|
+
|
|
272
|
+
$stderr.write("Running baseline test suite...\n")
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def log_baseline_complete(result)
|
|
175
276
|
return if config.quiet || !config.text? || !$stderr.tty?
|
|
176
277
|
|
|
177
|
-
|
|
278
|
+
count = result.failed_spec_files.size
|
|
279
|
+
$stderr.write("Baseline complete: #{count} failing spec file#{"s" unless count == 1}\n")
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def log_progress(current, status)
|
|
283
|
+
return if config.quiet || !config.text? || !$stderr.tty?
|
|
284
|
+
|
|
285
|
+
$stderr.write("mutation #{current} #{status}\n")
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def log_memory(phase, context = nil)
|
|
289
|
+
return unless config.verbose && !config.quiet
|
|
290
|
+
|
|
291
|
+
rss = Memory.rss_mb
|
|
292
|
+
return unless rss
|
|
293
|
+
|
|
294
|
+
gc = gc_stats_string
|
|
295
|
+
msg = format("[memory] %<phase>s: %<rss>.1f MB", phase: phase, rss: rss)
|
|
296
|
+
context = [context, gc].compact.join(", ")
|
|
297
|
+
msg += " (#{context})" unless context.empty?
|
|
298
|
+
$stderr.write("#{msg}\n")
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def log_mutation_diagnostics(result)
|
|
302
|
+
return unless config.verbose && !config.quiet
|
|
303
|
+
|
|
304
|
+
parts = []
|
|
305
|
+
parts << format("child_rss: %<mb>.1f MB", mb: result.child_rss_kb / 1024.0) if result.child_rss_kb
|
|
306
|
+
|
|
307
|
+
if result.memory_delta_kb
|
|
308
|
+
sign = result.memory_delta_kb.negative? ? "" : "+"
|
|
309
|
+
parts << format("delta: %<sign>s%<mb>.1f MB", sign: sign, mb: result.memory_delta_kb / 1024.0)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
parts << gc_stats_string
|
|
313
|
+
|
|
314
|
+
return if parts.empty?
|
|
315
|
+
|
|
316
|
+
$stderr.write("[verbose] #{result.mutation}: #{parts.join(", ")}\n")
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def gc_stats_string
|
|
320
|
+
stats = GC.stat
|
|
321
|
+
format(
|
|
322
|
+
"heap_live_slots: %<live>d, allocated: %<alloc>d, freed: %<freed>d",
|
|
323
|
+
live: stats[:heap_live_slots],
|
|
324
|
+
alloc: stats[:total_allocated_objects],
|
|
325
|
+
freed: stats[:total_freed_objects]
|
|
326
|
+
)
|
|
178
327
|
end
|
|
179
328
|
|
|
180
329
|
def build_reporter
|
data/lib/evilution/subject.rb
CHANGED
data/lib/evilution/version.rb
CHANGED
data/lib/evilution.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "evilution/version"
|
|
4
|
+
require_relative "evilution/memory"
|
|
4
5
|
require_relative "evilution/config"
|
|
5
6
|
require_relative "evilution/subject"
|
|
6
7
|
require_relative "evilution/mutation"
|
|
@@ -26,8 +27,14 @@ require_relative "evilution/mutator/operator/method_body_replacement"
|
|
|
26
27
|
require_relative "evilution/mutator/operator/return_value_removal"
|
|
27
28
|
require_relative "evilution/mutator/operator/collection_replacement"
|
|
28
29
|
require_relative "evilution/mutator/operator/method_call_removal"
|
|
30
|
+
require_relative "evilution/mutator/operator/argument_removal"
|
|
31
|
+
require_relative "evilution/mutator/operator/block_removal"
|
|
32
|
+
require_relative "evilution/mutator/operator/conditional_flip"
|
|
33
|
+
require_relative "evilution/mutator/operator/range_replacement"
|
|
34
|
+
require_relative "evilution/mutator/operator/regexp_mutation"
|
|
29
35
|
require_relative "evilution/mutator/registry"
|
|
30
36
|
require_relative "evilution/isolation/fork"
|
|
37
|
+
require_relative "evilution/isolation/in_process"
|
|
31
38
|
require_relative "evilution/parallel/pool"
|
|
32
39
|
require_relative "evilution/diff/parser"
|
|
33
40
|
require_relative "evilution/diff/file_filter"
|
|
@@ -42,6 +49,7 @@ require_relative "evilution/reporter/suggestion"
|
|
|
42
49
|
require_relative "evilution/coverage/collector"
|
|
43
50
|
require_relative "evilution/coverage/test_map"
|
|
44
51
|
require_relative "evilution/spec_resolver"
|
|
52
|
+
require_relative "evilution/baseline"
|
|
45
53
|
require_relative "evilution/cli"
|
|
46
54
|
require_relative "evilution/runner"
|
|
47
55
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :memory do
|
|
4
|
+
desc "Run memory leak checks against fixture workload"
|
|
5
|
+
task :check do
|
|
6
|
+
script = File.expand_path("../../script/memory_check", __dir__)
|
|
7
|
+
system("ruby", script) || abort("Memory check failed!")
|
|
8
|
+
end
|
|
9
|
+
end
|
data/script/memory_check
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "../lib/evilution"
|
|
5
|
+
require_relative "../lib/evilution/memory/leak_check"
|
|
6
|
+
|
|
7
|
+
FIXTURE = File.expand_path("../spec/support/fixtures/simple_class.rb", __dir__)
|
|
8
|
+
ITERATIONS = Integer(ENV.fetch("MEMORY_CHECK_ITERATIONS", 50))
|
|
9
|
+
MAX_GROWTH_KB = Integer(ENV.fetch("MEMORY_CHECK_MAX_GROWTH_KB", 10_240))
|
|
10
|
+
|
|
11
|
+
def setup_workload
|
|
12
|
+
parser = Evilution::AST::Parser.new
|
|
13
|
+
registry = Evilution::Mutator::Registry.default
|
|
14
|
+
subjects = parser.call(FIXTURE)
|
|
15
|
+
mutations = subjects.flat_map { |s| registry.mutations_for(s) }
|
|
16
|
+
abort("No mutations found in fixture") if mutations.empty?
|
|
17
|
+
mutations
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def run_check(name, iterations: ITERATIONS, max_growth_kb: MAX_GROWTH_KB, &)
|
|
21
|
+
check = Evilution::Memory::LeakCheck.new(iterations: iterations, max_growth_kb: max_growth_kb)
|
|
22
|
+
result = check.run(&)
|
|
23
|
+
report(name, result)
|
|
24
|
+
result[:passed]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def report(name, result)
|
|
28
|
+
status = result[:passed] ? "PASS" : "FAIL"
|
|
29
|
+
growth = result[:growth_mb] ? format("%.1f MB", result[:growth_mb]) : "N/A"
|
|
30
|
+
max = format("%.1f MB", result[:max_growth_kb] / 1024.0)
|
|
31
|
+
samples = result[:samples].map { |s| s ? format("%.1f", s / 1024.0) : "N/A" }.join(" -> ")
|
|
32
|
+
|
|
33
|
+
puts "[#{status}] #{name}"
|
|
34
|
+
puts " Growth: #{growth} (max: #{max})"
|
|
35
|
+
puts " Samples (MB): #{samples}"
|
|
36
|
+
puts
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
abort("RSS measurement unavailable (requires /proc filesystem)") unless Evilution::Memory.rss_kb
|
|
40
|
+
|
|
41
|
+
mutations = setup_workload
|
|
42
|
+
stub_test_command = ->(_m) { { passed: false } }
|
|
43
|
+
all_passed = true
|
|
44
|
+
|
|
45
|
+
# 1. InProcess isolation
|
|
46
|
+
all_passed &= run_check("InProcess isolation") do
|
|
47
|
+
mutation = mutations.sample
|
|
48
|
+
isolator = Evilution::Isolation::InProcess.new
|
|
49
|
+
isolator.call(mutation: mutation, test_command: stub_test_command, timeout: 5)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# 2. Fork isolation
|
|
53
|
+
all_passed &= run_check("Fork isolation") do
|
|
54
|
+
mutation = mutations.sample
|
|
55
|
+
isolator = Evilution::Isolation::Fork.new
|
|
56
|
+
isolator.call(mutation: mutation, test_command: stub_test_command, timeout: 5)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# 3. Mutation generation + stripping
|
|
60
|
+
parser = Evilution::AST::Parser.new
|
|
61
|
+
registry = Evilution::Mutator::Registry.default
|
|
62
|
+
|
|
63
|
+
all_passed &= run_check("Mutation generation + stripping") do
|
|
64
|
+
subjects = parser.call(FIXTURE)
|
|
65
|
+
new_mutations = subjects.flat_map { |s| registry.mutations_for(s) }
|
|
66
|
+
subjects.each(&:release_node!)
|
|
67
|
+
new_mutations.each(&:strip_sources!)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# 4. Parallel pool with compact serialization
|
|
71
|
+
if mutations.size >= 2
|
|
72
|
+
all_passed &= run_check("Parallel pool (compact)", iterations: 20) do
|
|
73
|
+
pool = Evilution::Parallel::Pool.new(size: 2)
|
|
74
|
+
batch = mutations.first(2)
|
|
75
|
+
worker_isolator = Evilution::Isolation::InProcess.new
|
|
76
|
+
|
|
77
|
+
compact_results = pool.map(batch) do |mutation|
|
|
78
|
+
result = worker_isolator.call(mutation: mutation, test_command: stub_test_command, timeout: 5)
|
|
79
|
+
{ status: result.status, duration: result.duration,
|
|
80
|
+
child_rss_kb: result.child_rss_kb, memory_delta_kb: result.memory_delta_kb }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
batch.each(&:strip_sources!)
|
|
84
|
+
batch.zip(compact_results).map do |mutation, data|
|
|
85
|
+
Evilution::Result::MutationResult.new(
|
|
86
|
+
mutation: mutation, status: data[:status], duration: data[:duration],
|
|
87
|
+
child_rss_kb: data[:child_rss_kb], memory_delta_kb: data[:memory_delta_kb]
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
puts all_passed ? "All memory checks passed." : "Some memory checks failed!"
|
|
94
|
+
exit(all_passed ? 0 : 1)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: evilution
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Denis Kiselev
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: diff-lcs
|
|
@@ -80,6 +80,7 @@ files:
|
|
|
80
80
|
- lib/evilution.rb
|
|
81
81
|
- lib/evilution/ast/parser.rb
|
|
82
82
|
- lib/evilution/ast/source_surgeon.rb
|
|
83
|
+
- lib/evilution/baseline.rb
|
|
83
84
|
- lib/evilution/cli.rb
|
|
84
85
|
- lib/evilution/config.rb
|
|
85
86
|
- lib/evilution/coverage/collector.rb
|
|
@@ -90,17 +91,23 @@ files:
|
|
|
90
91
|
- lib/evilution/integration/base.rb
|
|
91
92
|
- lib/evilution/integration/rspec.rb
|
|
92
93
|
- lib/evilution/isolation/fork.rb
|
|
94
|
+
- lib/evilution/isolation/in_process.rb
|
|
93
95
|
- lib/evilution/mcp/mutate_tool.rb
|
|
94
96
|
- lib/evilution/mcp/server.rb
|
|
97
|
+
- lib/evilution/memory.rb
|
|
98
|
+
- lib/evilution/memory/leak_check.rb
|
|
95
99
|
- lib/evilution/mutation.rb
|
|
96
100
|
- lib/evilution/mutator/base.rb
|
|
101
|
+
- lib/evilution/mutator/operator/argument_removal.rb
|
|
97
102
|
- lib/evilution/mutator/operator/arithmetic_replacement.rb
|
|
98
103
|
- lib/evilution/mutator/operator/array_literal.rb
|
|
104
|
+
- lib/evilution/mutator/operator/block_removal.rb
|
|
99
105
|
- lib/evilution/mutator/operator/boolean_literal_replacement.rb
|
|
100
106
|
- lib/evilution/mutator/operator/boolean_operator_replacement.rb
|
|
101
107
|
- lib/evilution/mutator/operator/collection_replacement.rb
|
|
102
108
|
- lib/evilution/mutator/operator/comparison_replacement.rb
|
|
103
109
|
- lib/evilution/mutator/operator/conditional_branch.rb
|
|
110
|
+
- lib/evilution/mutator/operator/conditional_flip.rb
|
|
104
111
|
- lib/evilution/mutator/operator/conditional_negation.rb
|
|
105
112
|
- lib/evilution/mutator/operator/float_literal.rb
|
|
106
113
|
- lib/evilution/mutator/operator/hash_literal.rb
|
|
@@ -109,6 +116,8 @@ files:
|
|
|
109
116
|
- lib/evilution/mutator/operator/method_call_removal.rb
|
|
110
117
|
- lib/evilution/mutator/operator/negation_insertion.rb
|
|
111
118
|
- lib/evilution/mutator/operator/nil_replacement.rb
|
|
119
|
+
- lib/evilution/mutator/operator/range_replacement.rb
|
|
120
|
+
- lib/evilution/mutator/operator/regexp_mutation.rb
|
|
112
121
|
- lib/evilution/mutator/operator/return_value_removal.rb
|
|
113
122
|
- lib/evilution/mutator/operator/statement_deletion.rb
|
|
114
123
|
- lib/evilution/mutator/operator/string_literal.rb
|
|
@@ -124,6 +133,8 @@ files:
|
|
|
124
133
|
- lib/evilution/spec_resolver.rb
|
|
125
134
|
- lib/evilution/subject.rb
|
|
126
135
|
- lib/evilution/version.rb
|
|
136
|
+
- lib/tasks/memory_check.rake
|
|
137
|
+
- script/memory_check
|
|
127
138
|
- sig/evilution.rbs
|
|
128
139
|
homepage: https://github.com/marinazzio/evilution
|
|
129
140
|
licenses:
|