evilution 0.13.0 → 0.14.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.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/.beads/.migration-hint-ts +1 -1
  3. data/.beads/issues.jsonl +8 -8
  4. data/CHANGELOG.md +17 -0
  5. data/lib/evilution/ast/parser.rb +69 -68
  6. data/lib/evilution/ast/source_surgeon.rb +7 -9
  7. data/lib/evilution/ast.rb +4 -0
  8. data/lib/evilution/baseline.rb +73 -75
  9. data/lib/evilution/cache.rb +75 -77
  10. data/lib/evilution/cli.rb +408 -173
  11. data/lib/evilution/config.rb +141 -136
  12. data/lib/evilution/equivalent/detector.rb +25 -27
  13. data/lib/evilution/equivalent/heuristic/alias_swap.rb +29 -33
  14. data/lib/evilution/equivalent/heuristic/dead_code.rb +41 -45
  15. data/lib/evilution/equivalent/heuristic/method_body_nil.rb +11 -15
  16. data/lib/evilution/equivalent/heuristic/noop_source.rb +5 -9
  17. data/lib/evilution/equivalent/heuristic.rb +6 -0
  18. data/lib/evilution/equivalent.rb +4 -0
  19. data/lib/evilution/git/changed_files.rb +35 -37
  20. data/lib/evilution/git.rb +4 -0
  21. data/lib/evilution/integration/base.rb +5 -7
  22. data/lib/evilution/integration/rspec.rb +114 -116
  23. data/lib/evilution/integration.rb +4 -0
  24. data/lib/evilution/isolation/fork.rb +98 -100
  25. data/lib/evilution/isolation/in_process.rb +59 -61
  26. data/lib/evilution/isolation.rb +4 -0
  27. data/lib/evilution/mcp/mutate_tool.rb +172 -143
  28. data/lib/evilution/mcp/server.rb +12 -11
  29. data/lib/evilution/mcp/session_diff_tool.rb +89 -0
  30. data/lib/evilution/mcp/session_list_tool.rb +46 -0
  31. data/lib/evilution/mcp/session_show_tool.rb +53 -0
  32. data/lib/evilution/mcp.rb +4 -0
  33. data/lib/evilution/memory/leak_check.rb +80 -84
  34. data/lib/evilution/memory.rb +34 -36
  35. data/lib/evilution/mutation.rb +40 -42
  36. data/lib/evilution/mutator/base.rb +46 -48
  37. data/lib/evilution/mutator/operator/argument_nil_substitution.rb +32 -36
  38. data/lib/evilution/mutator/operator/argument_removal.rb +32 -36
  39. data/lib/evilution/mutator/operator/arithmetic_replacement.rb +26 -30
  40. data/lib/evilution/mutator/operator/array_literal.rb +18 -22
  41. data/lib/evilution/mutator/operator/block_removal.rb +16 -20
  42. data/lib/evilution/mutator/operator/boolean_literal_replacement.rb +38 -42
  43. data/lib/evilution/mutator/operator/boolean_operator_replacement.rb +41 -45
  44. data/lib/evilution/mutator/operator/collection_replacement.rb +32 -36
  45. data/lib/evilution/mutator/operator/comparison_replacement.rb +24 -28
  46. data/lib/evilution/mutator/operator/compound_assignment.rb +114 -118
  47. data/lib/evilution/mutator/operator/conditional_branch.rb +25 -29
  48. data/lib/evilution/mutator/operator/conditional_flip.rb +26 -30
  49. data/lib/evilution/mutator/operator/conditional_negation.rb +25 -29
  50. data/lib/evilution/mutator/operator/float_literal.rb +22 -26
  51. data/lib/evilution/mutator/operator/hash_literal.rb +18 -22
  52. data/lib/evilution/mutator/operator/integer_literal.rb +2 -0
  53. data/lib/evilution/mutator/operator/method_body_replacement.rb +12 -16
  54. data/lib/evilution/mutator/operator/method_call_removal.rb +12 -16
  55. data/lib/evilution/mutator/operator/negation_insertion.rb +12 -16
  56. data/lib/evilution/mutator/operator/nil_replacement.rb +13 -17
  57. data/lib/evilution/mutator/operator/range_replacement.rb +12 -16
  58. data/lib/evilution/mutator/operator/receiver_replacement.rb +16 -20
  59. data/lib/evilution/mutator/operator/regexp_mutation.rb +15 -19
  60. data/lib/evilution/mutator/operator/return_value_removal.rb +12 -16
  61. data/lib/evilution/mutator/operator/send_mutation.rb +36 -40
  62. data/lib/evilution/mutator/operator/statement_deletion.rb +13 -17
  63. data/lib/evilution/mutator/operator/string_literal.rb +18 -22
  64. data/lib/evilution/mutator/operator/symbol_literal.rb +17 -21
  65. data/lib/evilution/mutator/operator.rb +6 -0
  66. data/lib/evilution/mutator/registry.rb +54 -56
  67. data/lib/evilution/mutator.rb +4 -0
  68. data/lib/evilution/parallel/pool.rb +56 -58
  69. data/lib/evilution/parallel.rb +4 -0
  70. data/lib/evilution/reporter/cli.rb +99 -101
  71. data/lib/evilution/reporter/html.rb +242 -244
  72. data/lib/evilution/reporter/json.rb +57 -59
  73. data/lib/evilution/reporter/suggestion.rb +326 -328
  74. data/lib/evilution/reporter.rb +4 -0
  75. data/lib/evilution/result/mutation_result.rb +43 -46
  76. data/lib/evilution/result/summary.rb +80 -81
  77. data/lib/evilution/result.rb +4 -0
  78. data/lib/evilution/runner.rb +334 -323
  79. data/lib/evilution/session/store.rb +147 -0
  80. data/lib/evilution/session.rb +4 -0
  81. data/lib/evilution/spec_resolver.rb +49 -47
  82. data/lib/evilution/subject.rb +14 -16
  83. data/lib/evilution/version.rb +1 -1
  84. data/lib/evilution.rb +13 -0
  85. metadata +19 -2
@@ -4,82 +4,80 @@ require "digest"
4
4
  require "json"
5
5
  require "fileutils"
6
6
 
7
- module Evilution
8
- class Cache
9
- DEFAULT_DIR = "tmp/evilution_cache"
10
-
11
- def initialize(cache_dir: DEFAULT_DIR)
12
- @cache_dir = cache_dir
13
- end
14
-
15
- def fetch(mutation)
16
- return nil if mutation.original_source.nil?
17
-
18
- file_key = file_key(mutation)
19
- entry_key = entry_key(mutation)
20
- data = read_file(file_key)
21
- return nil unless data
22
-
23
- entry = data[entry_key]
24
- return nil unless entry.is_a?(Hash) && entry["status"].is_a?(String)
25
-
26
- { status: entry["status"].to_sym, duration: entry["duration"],
27
- killing_test: entry["killing_test"], test_command: entry["test_command"] }
28
- end
29
-
30
- def store(mutation, result_data)
31
- file_key = file_key(mutation)
32
- entry_key = entry_key(mutation)
33
- data = read_file(file_key) || {}
34
-
35
- data[entry_key] = {
36
- "status" => result_data[:status].to_s,
37
- "duration" => result_data[:duration],
38
- "killing_test" => result_data[:killing_test],
39
- "test_command" => result_data[:test_command]
40
- }
41
-
42
- write_file(file_key, data)
43
- end
44
-
45
- def clear
46
- FileUtils.rm_rf(@cache_dir)
47
- end
48
-
49
- private
50
-
51
- def file_key(mutation)
52
- content_hash = Digest::SHA256.hexdigest(mutation.original_source)
53
- "#{safe_filename(mutation.file_path)}_#{content_hash[0, 16]}"
54
- end
55
-
56
- def entry_key(mutation)
57
- "#{mutation.operator_name}:#{mutation.line}:#{mutation.column}"
58
- end
59
-
60
- def safe_filename(path)
61
- path.gsub(%r{[/\\]}, "_").gsub(/[^a-zA-Z0-9._-]/, "")
62
- end
63
-
64
- def read_file(file_key)
65
- path = cache_path(file_key)
66
- return nil unless File.exist?(path)
67
-
68
- JSON.parse(File.read(path))
69
- rescue JSON::ParserError
70
- nil
71
- end
72
-
73
- def write_file(file_key, data)
74
- FileUtils.mkdir_p(@cache_dir)
75
- path = cache_path(file_key)
76
- tmp = "#{path}.#{Process.pid}.tmp"
77
- File.write(tmp, JSON.generate(data))
78
- File.rename(tmp, path)
79
- end
80
-
81
- def cache_path(file_key)
82
- File.join(@cache_dir, "#{file_key}.json")
83
- end
7
+ class Evilution::Cache
8
+ DEFAULT_DIR = "tmp/evilution_cache"
9
+
10
+ def initialize(cache_dir: DEFAULT_DIR)
11
+ @cache_dir = cache_dir
12
+ end
13
+
14
+ def fetch(mutation)
15
+ return nil if mutation.original_source.nil?
16
+
17
+ file_key = file_key(mutation)
18
+ entry_key = entry_key(mutation)
19
+ data = read_file(file_key)
20
+ return nil unless data
21
+
22
+ entry = data[entry_key]
23
+ return nil unless entry.is_a?(Hash) && entry["status"].is_a?(String)
24
+
25
+ { status: entry["status"].to_sym, duration: entry["duration"],
26
+ killing_test: entry["killing_test"], test_command: entry["test_command"] }
27
+ end
28
+
29
+ def store(mutation, result_data)
30
+ file_key = file_key(mutation)
31
+ entry_key = entry_key(mutation)
32
+ data = read_file(file_key) || {}
33
+
34
+ data[entry_key] = {
35
+ "status" => result_data[:status].to_s,
36
+ "duration" => result_data[:duration],
37
+ "killing_test" => result_data[:killing_test],
38
+ "test_command" => result_data[:test_command]
39
+ }
40
+
41
+ write_file(file_key, data)
42
+ end
43
+
44
+ def clear
45
+ FileUtils.rm_rf(@cache_dir)
46
+ end
47
+
48
+ private
49
+
50
+ def file_key(mutation)
51
+ content_hash = Digest::SHA256.hexdigest(mutation.original_source)
52
+ "#{safe_filename(mutation.file_path)}_#{content_hash[0, 16]}"
53
+ end
54
+
55
+ def entry_key(mutation)
56
+ "#{mutation.operator_name}:#{mutation.line}:#{mutation.column}"
57
+ end
58
+
59
+ def safe_filename(path)
60
+ path.gsub(%r{[/\\]}, "_").gsub(/[^a-zA-Z0-9._-]/, "")
61
+ end
62
+
63
+ def read_file(file_key)
64
+ path = cache_path(file_key)
65
+ return nil unless File.exist?(path)
66
+
67
+ JSON.parse(File.read(path))
68
+ rescue JSON::ParserError
69
+ nil
70
+ end
71
+
72
+ def write_file(file_key, data)
73
+ FileUtils.mkdir_p(@cache_dir)
74
+ path = cache_path(file_key)
75
+ tmp = "#{path}.#{Process.pid}.tmp"
76
+ File.write(tmp, JSON.generate(data))
77
+ File.rename(tmp, path)
78
+ end
79
+
80
+ def cache_path(file_key)
81
+ File.join(@cache_dir, "#{file_key}.json")
84
82
  end
85
83
  end