evilution 0.2.0 → 0.3.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 +19 -0
- data/README.md +6 -4
- data/lib/evilution/config.rb +3 -3
- data/lib/evilution/isolation/fork.rb +31 -2
- data/lib/evilution/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 646e7a441c39108d413a0b33cb28234b090e16c1614d343c40be84581fb45adf
|
|
4
|
+
data.tar.gz: fc65d99be854a51dc0354a77db413d90a8344611dc8d94a5e97ef61e91e764a9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 38954ff26a4f48a050ca7a5d4270307157f7482836718152e03d17642df566110144c2773a5ce62ea8877ed2bdc97e6b2ccc33d49e9521e2074ad7cfc2b46b1e
|
|
7
|
+
data.tar.gz: c4a4bcbf1f59e2f8ff01138bf690132dcb183330009340b76b4d8db1d8807411c04dcb8f3a52820857b9563158a49147903a7274e1395b0f4f76047f239d625e
|
data/.beads/.migration-hint-ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
1773300459
|
data/.beads/issues.jsonl
CHANGED
|
@@ -30,6 +30,15 @@
|
|
|
30
30
|
{"id":"EV-2.8","title":"Implement Evilution::Result::MutationResult and Summary","description":"MutationResult: value object with fields: mutation, status (:killed/:survived/:timeout/:error), duration, killing_test (optional). Summary: aggregates MutationResult array into total/killed/survived/timed_out/errors/score/duration. Files: lib/evilution/result/mutation_result.rb, lib/evilution/result/summary.rb + specs.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:05:51.022249568+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T10:51:08.482621898+07:00","closed_at":"2026-03-02T10:51:08.482621898+07:00","close_reason":"Closed","dependencies":[{"issue_id":"EV-2.8","depends_on_id":"EV-2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
31
31
|
{"id":"EV-2.9","title":"Implement Evilution::Isolation::Fork","description":"Fork-based process isolation. Method: run(mutation, test_command, timeout:) -> MutationResult. Flow: create pipe, fork child, child applies mutation via eval, child runs test command, marshal result back via pipe, parent reads with IO.select timeout, SIGKILL on deadline. File: lib/evilution/isolation/fork.rb + spec.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:05:51.142167275+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T10:54:08.819310594+07:00","closed_at":"2026-03-02T10:54:08.819310594+07:00","close_reason":"Fork isolation implemented with 6 passing specs","dependencies":[{"issue_id":"EV-2.9","depends_on_id":"EV-2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-2.9","depends_on_id":"EV-2.8","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
32
32
|
{"id":"EV-20","title":"Deprecate coverage filtering","description":"With line-range and method-name targeting, coverage collection adds overhead for little benefit. Deprecate --no-coverage flag, coverage config key, and remove coverage logic from runner.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T00:30:58.837659684+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-10T00:33:06.453669976+07:00","closed_at":"2026-03-10T00:33:06.453669976+07:00","close_reason":"Deprecated coverage filtering: CLI warns on --no-coverage, config warns on coverage key, runner no longer uses coverage logic"}
|
|
33
|
+
{"id":"EV-21","title":"Epic: Speed & Performance","description":"Reduce mutation testing wall-clock time for fast agent feedback loops. Includes fail-fast, parallel execution, and per-mutation spec targeting.","status":"open","priority":1,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:26.608316104+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-10T06:17:26.608316104+07:00","dependencies":[{"issue_id":"EV-21","depends_on_id":"EV-22","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-21","depends_on_id":"EV-23","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
34
|
+
{"id":"EV-22","title":"Add --fail-fast flag with survivor threshold","description":"Add --fail-fast [N] CLI flag and fail_fast config option to stop mutation testing after N surviving mutants (default N=1 when flag given without value). Agents fix gaps iteratively, so discovering all survivors upfront is wasted work. A threshold gives control over the speed/thoroughness tradeoff: --fail-fast 1 for quick checks, --fail-fast 5 for CI gates, omit for full scans. Runner#call should stop running mutations and return a partial Summary once the threshold is reached. JSON output should include a 'truncated: true' field when fail-fast triggered.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:28.018733235+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-10T10:29:42.228568154+07:00"}
|
|
35
|
+
{"id":"EV-23","title":"Per-mutation spec targeting","description":"Instead of running the full spec suite for every mutation, map each mutated source file to its relevant spec file(s) using convention-based resolution (e.g. lib/foo/bar.rb -> spec/foo/bar_spec.rb) and only run those. This dramatically reduces per-mutation test time. Depends on convention-based spec file resolution being implemented first.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:28.98620973+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-10T06:17:28.98620973+07:00","dependencies":[{"issue_id":"EV-23","depends_on_id":"EV-34","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
36
|
+
{"id":"EV-24","title":"Epic: JSON Output Improvements","description":"Make JSON output fully machine-parseable in all scenarios, including errors. Add diagnostic fields that help agents debug failures.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:37.450686472+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-10T06:17:37.450686472+07:00","dependencies":[{"issue_id":"EV-24","depends_on_id":"EV-25","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-24","depends_on_id":"EV-26","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
37
|
+
{"id":"EV-25","title":"Structured error responses in JSON mode","description":"When --format json is used and exit code is 2 (error), output a JSON object with error details instead of unstructured stderr text. Schema: { \"error\": { \"type\": \"config_error|parse_error|runtime_error\", \"message\": \"...\", \"file\": \"...\" } }. Agents currently have to regex-parse stderr which is fragile.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:38.283715502+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-10T06:17:38.283715502+07:00"}
|
|
38
|
+
{"id":"EV-26","title":"Include test command in mutation result JSON","description":"Add a 'test_command' field to each mutation result in JSON output showing the exact command that was run to test that mutation. Helps agents debug when a mutation errors out or times out.","status":"open","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:39.227881462+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-10T06:17:39.227881462+07:00"}
|
|
39
|
+
{"id":"EV-27","title":"Epic: Workflow Integration","description":"Make evilution easier to invoke from AI agent toolchains — MCP server for direct tool calls, stdin mode for piping.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:43.883767452+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-10T06:17:43.883767452+07:00","dependencies":[{"issue_id":"EV-27","depends_on_id":"EV-28","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-27","depends_on_id":"EV-29","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
40
|
+
{"id":"EV-28","title":"MCP server for direct tool invocation","description":"Implement a Model Context Protocol (MCP) server that exposes evilution as a tool. Agents could call evilution directly instead of shelling out and parsing output. The server should expose a 'mutate' tool that accepts target files, options, and returns structured results.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:45.29866593+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-10T06:17:45.29866593+07:00"}
|
|
41
|
+
{"id":"EV-29","title":"Add --stdin flag to accept file list from stdin","description":"Add a --stdin flag that reads target file paths (one per line) from stdin. Enables workflows like: git diff --name-only | evilution run --stdin --format json. Each line can include line-range syntax (e.g. lib/foo.rb:15-30).","status":"open","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:46.306306092+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-10T06:17:46.306306092+07:00"}
|
|
33
42
|
{"id":"EV-3","title":"Phase 2: Mutation Operators & CLI","description":"Implement remaining 17 mutation operators, build CLI with OptionParser, exe/evilution executable, human-readable reporter. Milestone: bundle exec evilution run lib/user.rb --format json","status":"closed","priority":2,"issue_type":"epic","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:05:00.492971295+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:21:32.168384165+07:00","closed_at":"2026-03-02T11:21:32.168384165+07:00","close_reason":"Phase 2 complete: all 18 operators, CLI, Reporter::CLI, Registry registration, executable","dependencies":[{"issue_id":"EV-3","depends_on_id":"EV-2","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
34
43
|
{"id":"EV-3.1","title":"Implement ArithmeticReplacement operator","description":"Targets CallNode where name is :+, :-, :*, :/, :%, :**. Replacements: + <-> -, * <-> /, % -> /, ** -> *. File: lib/evilution/mutator/operator/arithmetic_replacement.rb + spec.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:06:13.025649082+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:06:10.922412304+07:00","closed_at":"2026-03-02T11:06:10.922412304+07:00","close_reason":"All 3 operators implemented with passing specs","dependencies":[{"issue_id":"EV-3.1","depends_on_id":"EV-3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-3.1","depends_on_id":"EV-2.6","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
35
44
|
{"id":"EV-3.10","title":"Implement SymbolLiteral operator","description":"Targets SymbolNode. Mutation: :foo -> :__evilution_mutated__. File: lib/evilution/mutator/operator/symbol_literal.rb + spec.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:06:13.935877816+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:13:36.541040613+07:00","closed_at":"2026-03-02T11:13:36.541040613+07:00","close_reason":"All 3 operators implemented with passing specs","dependencies":[{"issue_id":"EV-3.10","depends_on_id":"EV-3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-3.10","depends_on_id":"EV-2.6","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
@@ -52,6 +61,16 @@
|
|
|
52
61
|
{"id":"EV-3.7","title":"Implement StringLiteral operator","description":"Targets StringNode. Mutations: non-empty -> empty string, empty -> 'mutation'. File: lib/evilution/mutator/operator/string_literal.rb + spec.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:06:13.633726423+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:10:56.39748015+07:00","closed_at":"2026-03-02T11:10:56.39748015+07:00","close_reason":"All 3 operators implemented with passing specs","dependencies":[{"issue_id":"EV-3.7","depends_on_id":"EV-3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-3.7","depends_on_id":"EV-2.6","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
53
62
|
{"id":"EV-3.8","title":"Implement ArrayLiteral operator","description":"Targets ArrayNode with elements. Mutation: [a, b, c] -> []. File: lib/evilution/mutator/operator/array_literal.rb + spec.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:06:13.734617709+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:10:56.397565925+07:00","closed_at":"2026-03-02T11:10:56.397565925+07:00","close_reason":"All 3 operators implemented with passing specs","dependencies":[{"issue_id":"EV-3.8","depends_on_id":"EV-3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-3.8","depends_on_id":"EV-2.6","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
54
63
|
{"id":"EV-3.9","title":"Implement HashLiteral operator","description":"Targets HashNode with pairs. Mutation: {k: v} -> {}. File: lib/evilution/mutator/operator/hash_literal.rb + spec.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:06:13.840779748+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:10:56.397570989+07:00","closed_at":"2026-03-02T11:10:56.397570989+07:00","close_reason":"All 3 operators implemented with passing specs","dependencies":[{"issue_id":"EV-3.9","depends_on_id":"EV-3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-3.9","depends_on_id":"EV-2.6","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
64
|
+
{"id":"EV-30","title":"Epic: Smarter Suggestions","description":"Improve the suggestion field in mutation results to provide concrete, actionable test code that agents can use directly.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:18:03.590579846+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-10T06:18:03.590579846+07:00","dependencies":[{"issue_id":"EV-30","depends_on_id":"EV-31","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
65
|
+
{"id":"EV-31","title":"Generate concrete RSpec test code in suggestions","description":"Replace prose suggestions like 'add a test that checks the boundary' with actual RSpec code snippets that would kill the mutant. Use the mutation's operator, file context, and method signature to generate a concrete test example. The suggestion should be a valid RSpec 'it' block that an agent can drop into a spec file.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:18:05.964542284+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-10T06:18:05.964542284+07:00"}
|
|
66
|
+
{"id":"EV-32","title":"Epic: Zero-friction Defaults","description":"Make evilution work well with zero configuration. Auto-detect what to mutate and which specs to run based on git state and file conventions.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:18:06.843446356+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-10T06:18:06.843446356+07:00","dependencies":[{"issue_id":"EV-32","depends_on_id":"EV-33","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-32","depends_on_id":"EV-34","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
67
|
+
{"id":"EV-33","title":"Auto-detect changed files from git merge base","description":"When evilution is run with no file arguments, default to mutating files changed since the merge base of the current branch (git merge-base HEAD main). This makes 'evilution run --format json' just work in a feature branch without specifying files. Should be skippable if explicit files are provided.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:18:08.235421922+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-10T06:18:08.235421922+07:00"}
|
|
68
|
+
{"id":"EV-34","title":"Convention-based spec file resolution","description":"Implement a mapping from source files to their spec files using Ruby/RSpec conventions: lib/foo/bar.rb -> spec/foo/bar_spec.rb, app/models/user.rb -> spec/models/user_spec.rb. This is a foundational piece used by both zero-friction defaults (auto-detect specs) and per-mutation spec targeting (run only relevant specs). Should handle common Rails and gem layouts.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:18:09.380269033+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-10T06:18:09.380269033+07:00"}
|
|
69
|
+
{"id":"EV-35","title":"File left in mutated state on timeout","description":"Epic: File left in mutated state on timeout. Root cause: SIGKILL (fork.rb:45) is untrappable — child's ensure block (rspec.rb:24-26) never runs, so restore_original never executes. Affects Strategy B (direct file write) with data corruption and Strategy A (temp dir) with resource leaks. Decomposed into EV-37, EV-38, EV-39.","status":"closed","priority":1,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-12T14:27:59.54425065+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-13T00:34:44.130203912+07:00","closed_at":"2026-03-13T00:34:44.130203912+07:00","close_reason":"All sub-issues (EV-37, EV-38, EV-39) completed","dependencies":[{"issue_id":"EV-35","depends_on_id":"EV-37","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-35","depends_on_id":"EV-38","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-35","depends_on_id":"EV-39","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
70
|
+
{"id":"EV-36","title":"Raise default timeout from 10s to 30s","description":"Default 10s timeout is too low when running the full test suite per mutation (no smart test selection yet). Agent testing showed 30s was needed. Raising the default prevents confusing timeout failures for new users.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-12T14:27:59.761882257+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-12T14:45:27.102490973+07:00","closed_at":"2026-03-12T14:45:27.102490973+07:00","close_reason":"Closed"}
|
|
71
|
+
{"id":"EV-37","title":"Parent-side backup & restore for direct-write mutations","description":"Root cause: SIGKILL is untrappable, so when a mutation times out the child's ensure block (rspec.rb:24-26) never runs. If Strategy B (direct file write, rspec.rb:50-58) was used, the original file is left in a mutated state on disk. Fix: before forking, the parent must save a backup of the original file content. After the child exits (normally or via kill), the parent verifies the file is restored and restores it if not. This is the primary fix for EV-35.","status":"closed","priority":0,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-12T14:32:49.590919391+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-12T23:04:21.990924732+07:00","closed_at":"2026-03-12T23:04:21.990924732+07:00","close_reason":"Implemented parent-side restore in Fork#call ensure block with read-before-write guard"}
|
|
72
|
+
{"id":"EV-38","title":"Use SIGTERM with grace period before SIGKILL on timeout","description":"Defense-in-depth for EV-35. Currently fork.rb:45 sends SIGKILL immediately on timeout. SIGKILL is untrappable so the child gets no chance to run ensure blocks. Fix: send SIGTERM first (trappable), wait a short grace period (1-2s), then SIGKILL if still alive. This lets the child's ensure block restore files naturally in most timeout cases.","status":"closed","priority":0,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-12T14:32:52.463953548+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-13T00:34:44.01894518+07:00","closed_at":"2026-03-13T00:34:44.01894518+07:00","close_reason":"Implemented SIGTERM with grace period before SIGKILL fallback"}
|
|
73
|
+
{"id":"EV-39","title":"Clean up leaked temp directories after child timeout (Strategy A)","description":"When Strategy A (temp dir, rspec.rb:44-49) is used and the child is killed on timeout, the temp directory is never cleaned up because the child's ensure block doesn't run. The parent should track and clean up any temp dirs created for mutation runs. Minor resource leak, not data corruption.","status":"closed","priority":0,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-12T14:32:54.466514555+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-13T00:34:31.932452641+07:00","closed_at":"2026-03-13T00:34:31.932452641+07:00","close_reason":"Implemented per-run sandbox TMPDIR for child processes, cleaned up by parent in ensure block"}
|
|
55
74
|
{"id":"EV-4","title":"Phase 3: Performance Features","description":"Parallel execution pool, coverage-based mutation filtering, git diff-based targeting. Milestone: evilution run --diff HEAD~1 --jobs 4","status":"closed","priority":2,"issue_type":"epic","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:05:02.252877807+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-06T11:02:29.997677574+07:00","closed_at":"2026-03-02T11:54:32.782829837+07:00","close_reason":"Closed","dependencies":[{"issue_id":"EV-4","depends_on_id":"EV-3","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
56
75
|
{"id":"EV-4.1","title":"Implement Evilution::Parallel::Pool","description":"Thread-based worker pool distributing fork workers. Initialize with job_count. Method: #run(mutations, test_command, timeout:) -> [MutationResult]. Uses Queue to distribute work to N threads, each thread calls Isolation::Fork. File: lib/evilution/parallel/pool.rb + spec.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:06:44.984077473+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:46:46.640997276+07:00","closed_at":"2026-03-02T11:46:46.640997276+07:00","close_reason":"Pool implementation and tests already exist and pass","dependencies":[{"issue_id":"EV-4.1","depends_on_id":"EV-4","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-4.1","depends_on_id":"EV-2.9","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
57
76
|
{"id":"EV-4.2","title":"Implement Evilution::Parallel::Worker","description":"Individual worker abstraction managing fork lifecycle. Wraps Isolation::Fork with queue consumption loop. File: lib/evilution/parallel/worker.rb + spec.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:06:45.08570827+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:54:21.51846223+07:00","closed_at":"2026-03-02T11:54:21.51846223+07:00","close_reason":"Closed","dependencies":[{"issue_id":"EV-4.2","depends_on_id":"EV-4","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-4.2","depends_on_id":"EV-4.1","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
data/README.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
[](https://badge.fury.io/rb/evilution)
|
|
2
|
+
|
|
1
3
|
# Evilution — Mutation Testing for Ruby
|
|
2
4
|
|
|
3
5
|
> **Purpose**: Validate test suite quality by injecting small code changes (mutations) and checking whether tests detect them. Surviving mutations indicate gaps in test coverage.
|
|
4
6
|
|
|
5
|
-
**License**: MIT (free, no commercial restrictions)
|
|
6
|
-
**Language**: Ruby >= 3.3
|
|
7
|
-
**Parser**: Prism (Ruby's official AST parser, ships with Ruby 3.3+)
|
|
8
|
-
**Test framework**: RSpec (currently the only supported integration)
|
|
7
|
+
* **License**: MIT (free, no commercial restrictions)
|
|
8
|
+
* **Language**: Ruby >= 3.3
|
|
9
|
+
* **Parser**: Prism (Ruby's official AST parser, ships with Ruby 3.3+)
|
|
10
|
+
* **Test framework**: RSpec (currently the only supported integration)
|
|
9
11
|
|
|
10
12
|
## Installation
|
|
11
13
|
|
data/lib/evilution/config.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Evilution
|
|
|
7
7
|
CONFIG_FILES = %w[.evilution.yml config/evilution.yml].freeze
|
|
8
8
|
|
|
9
9
|
DEFAULTS = {
|
|
10
|
-
timeout:
|
|
10
|
+
timeout: 30,
|
|
11
11
|
format: :text,
|
|
12
12
|
diff_base: nil,
|
|
13
13
|
target: nil,
|
|
@@ -69,8 +69,8 @@ module Evilution
|
|
|
69
69
|
# Evilution configuration
|
|
70
70
|
# See: https://github.com/marinazzio/evilution
|
|
71
71
|
|
|
72
|
-
# Per-mutation timeout in seconds (default:
|
|
73
|
-
# timeout:
|
|
72
|
+
# Per-mutation timeout in seconds (default: 30)
|
|
73
|
+
# timeout: 30
|
|
74
74
|
|
|
75
75
|
# Output format: text or json (default: text)
|
|
76
76
|
# format: text
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "tmpdir"
|
|
5
|
+
|
|
3
6
|
module Evilution
|
|
4
7
|
module Isolation
|
|
5
8
|
class Fork
|
|
9
|
+
GRACE_PERIOD = 2
|
|
10
|
+
|
|
6
11
|
def call(mutation:, test_command:, timeout:)
|
|
12
|
+
sandbox_dir = Dir.mktmpdir("evilution-run")
|
|
7
13
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
8
14
|
read_io, write_io = IO.pipe
|
|
9
15
|
|
|
10
16
|
pid = ::Process.fork do
|
|
17
|
+
ENV["TMPDIR"] = sandbox_dir
|
|
11
18
|
read_io.close
|
|
12
19
|
result = execute_in_child(mutation, test_command)
|
|
13
20
|
Marshal.dump(result, write_io)
|
|
@@ -23,10 +30,20 @@ module Evilution
|
|
|
23
30
|
ensure
|
|
24
31
|
read_io&.close
|
|
25
32
|
write_io&.close
|
|
33
|
+
restore_original_source(mutation)
|
|
34
|
+
FileUtils.rm_rf(sandbox_dir) if sandbox_dir
|
|
26
35
|
end
|
|
27
36
|
|
|
28
37
|
private
|
|
29
38
|
|
|
39
|
+
def restore_original_source(mutation)
|
|
40
|
+
return if File.read(mutation.file_path) == mutation.original_source
|
|
41
|
+
|
|
42
|
+
File.write(mutation.file_path, mutation.original_source)
|
|
43
|
+
rescue StandardError => e
|
|
44
|
+
warn("Warning: failed to restore #{mutation.file_path}: #{e.message}")
|
|
45
|
+
end
|
|
46
|
+
|
|
30
47
|
def execute_in_child(mutation, test_command)
|
|
31
48
|
test_command.call(mutation)
|
|
32
49
|
rescue StandardError => e
|
|
@@ -42,12 +59,24 @@ module Evilution
|
|
|
42
59
|
::Process.wait(pid) rescue nil # rubocop:disable Style/RescueModifier
|
|
43
60
|
{ timeout: false, passed: false, error: "empty result from child" }
|
|
44
61
|
else
|
|
45
|
-
|
|
46
|
-
::Process.wait(pid) rescue nil # rubocop:disable Style/RescueModifier
|
|
62
|
+
terminate_child(pid)
|
|
47
63
|
{ timeout: true }
|
|
48
64
|
end
|
|
49
65
|
end
|
|
50
66
|
|
|
67
|
+
def terminate_child(pid)
|
|
68
|
+
::Process.kill("TERM", pid) rescue nil # rubocop:disable Style/RescueModifier
|
|
69
|
+
_, status = ::Process.waitpid2(pid, ::Process::WNOHANG)
|
|
70
|
+
return if status
|
|
71
|
+
|
|
72
|
+
sleep(GRACE_PERIOD)
|
|
73
|
+
_, status = ::Process.waitpid2(pid, ::Process::WNOHANG)
|
|
74
|
+
return if status
|
|
75
|
+
|
|
76
|
+
::Process.kill("KILL", pid) rescue nil # rubocop:disable Style/RescueModifier
|
|
77
|
+
::Process.wait(pid) rescue nil # rubocop:disable Style/RescueModifier
|
|
78
|
+
end
|
|
79
|
+
|
|
51
80
|
def build_mutation_result(mutation, result, duration)
|
|
52
81
|
status = if result[:timeout]
|
|
53
82
|
:timeout
|
data/lib/evilution/version.rb
CHANGED
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.3.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-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: diff-lcs
|