henitai 0.2.1 → 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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +80 -1
  3. data/README.md +123 -2
  4. data/assets/schema/henitai.schema.json +35 -0
  5. data/lib/henitai/available_cpu_count.rb +16 -23
  6. data/lib/henitai/canonical_report_merger.rb +107 -0
  7. data/lib/henitai/canonical_report_writer.rb +22 -0
  8. data/lib/henitai/checkpoint_reporter.rb +79 -0
  9. data/lib/henitai/cli/clean_command.rb +14 -8
  10. data/lib/henitai/cli/command_support.rb +2 -1
  11. data/lib/henitai/cli/operator_command.rb +2 -1
  12. data/lib/henitai/cli/options.rb +21 -57
  13. data/lib/henitai/cli/run_command.rb +36 -3
  14. data/lib/henitai/cli/run_options.rb +108 -0
  15. data/lib/henitai/cli.rb +12 -4
  16. data/lib/henitai/composite_progress_reporter.rb +42 -0
  17. data/lib/henitai/configuration.rb +41 -9
  18. data/lib/henitai/configuration_validator/rules.rb +18 -0
  19. data/lib/henitai/configuration_validator/scalars.rb +46 -0
  20. data/lib/henitai/configuration_validator.rb +8 -1
  21. data/lib/henitai/coverage_bootstrapper.rb +54 -5
  22. data/lib/henitai/coverage_formatter.rb +5 -1
  23. data/lib/henitai/coverage_report_reader.rb +28 -5
  24. data/lib/henitai/execution_engine/env_scope.rb +67 -0
  25. data/lib/henitai/execution_engine.rb +111 -50
  26. data/lib/henitai/generated_artifacts.rb +58 -0
  27. data/lib/henitai/incremental_filter.rb +100 -0
  28. data/lib/henitai/integration/child_debug_support.rb +6 -2
  29. data/lib/henitai/integration/coverage_suppression.rb +9 -0
  30. data/lib/henitai/integration/minitest.rb +11 -43
  31. data/lib/henitai/integration/minitest_load_path.rb +14 -0
  32. data/lib/henitai/integration/minitest_suite_command.rb +17 -0
  33. data/lib/henitai/integration/minitest_test_runner.rb +39 -0
  34. data/lib/henitai/integration/rails_environment_preloader.rb +14 -0
  35. data/lib/henitai/integration/rspec_child_runner.rb +3 -3
  36. data/lib/henitai/integration/rspec_test_selection.rb +3 -0
  37. data/lib/henitai/integration/scenario_log_support.rb +64 -20
  38. data/lib/henitai/minitest_coverage_reporter.rb +4 -1
  39. data/lib/henitai/mutant/activator.rb +14 -0
  40. data/lib/henitai/mutant.rb +13 -6
  41. data/lib/henitai/mutant_history_store/sql.rb +21 -3
  42. data/lib/henitai/mutant_history_store/verdict_cache.rb +84 -0
  43. data/lib/henitai/mutant_history_store.rb +62 -12
  44. data/lib/henitai/mutant_identity.rb +33 -2
  45. data/lib/henitai/mutation_skip_directives.rb +227 -0
  46. data/lib/henitai/operator.rb +3 -1
  47. data/lib/henitai/operators/equality_identity_operator.rb +51 -0
  48. data/lib/henitai/operators/equality_operator.rb +8 -3
  49. data/lib/henitai/operators.rb +1 -0
  50. data/lib/henitai/per_test_coverage.rb +66 -0
  51. data/lib/henitai/per_test_coverage_collector.rb +20 -5
  52. data/lib/henitai/per_test_coverage_selector.rb +11 -33
  53. data/lib/henitai/reporter/dashboard_metadata_provider.rb +113 -0
  54. data/lib/henitai/reporter.rb +238 -117
  55. data/lib/henitai/reports_directory_lock.rb +76 -0
  56. data/lib/henitai/result.rb +72 -9
  57. data/lib/henitai/runner.rb +82 -40
  58. data/lib/henitai/scenario_execution_result.rb +12 -0
  59. data/lib/henitai/slot_scheduler/draining.rb +21 -15
  60. data/lib/henitai/slot_scheduler.rb +73 -15
  61. data/lib/henitai/static_filter.rb +16 -6
  62. data/lib/henitai/survivor_rerun_strategy.rb +1 -1
  63. data/lib/henitai/survivor_test_filter.rb +1 -1
  64. data/lib/henitai/test_prioritizer.rb +28 -2
  65. data/lib/henitai/timeout_calibrator.rb +44 -0
  66. data/lib/henitai/verdict_fingerprint.rb +155 -0
  67. data/lib/henitai/version.rb +1 -1
  68. data/lib/henitai.rb +14 -1
  69. data/sig/henitai.rbs +230 -25
  70. metadata +22 -3
  71. data/lib/henitai/parallel_execution_runner.rb +0 -153
@@ -1,153 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Henitai
4
- # Runs pending mutants across worker threads with signal and stdin handling.
5
- class ParallelExecutionRunner
6
- ParallelExecutionContext = Struct.new(
7
- :queue, :integration, :config, :progress_reporter,
8
- :mutex, :state, :old_handlers, :stdin_watcher
9
- )
10
-
11
- def initialize(worker_count:)
12
- @worker_count = worker_count
13
- end
14
-
15
- def run(mutants, integration, config, progress_reporter, options = {})
16
- context = build_parallel_context(
17
- mutants,
18
- integration,
19
- config,
20
- progress_reporter
21
- )
22
- execute_parallel_execution(
23
- context,
24
- stdin_pipe: options.fetch(:stdin_pipe, false),
25
- process_mutant: options.fetch(:process_mutant)
26
- )
27
- end
28
-
29
- def execute_parallel_execution(context, stdin_pipe:, process_mutant:)
30
- install_parallel_signal_traps(context)
31
- start_parallel_stdin_watcher(context, stdin_pipe)
32
- parallel_workers(context, process_mutant).each(&:join)
33
- ensure
34
- teardown_parallel_execution(context)
35
- end
36
-
37
- private
38
-
39
- attr_reader :worker_count
40
-
41
- def teardown_parallel_execution(context)
42
- stop_parallel_stdin_watcher(context)
43
- restore_parallel_signal_traps(context)
44
- emit_scheduler_diagnostics if Integration::SchedulerDiagnostics.enabled?
45
- raise context.state[:error] if context&.state&.fetch(:error, nil)
46
- raise Interrupt if context&.state&.fetch(:stopping, false)
47
- end
48
-
49
- def emit_scheduler_diagnostics
50
- summary = Integration::SchedulerDiagnostics.summary
51
- warn "[henitai-scheduler] max_concurrent_children=#{summary[:max_concurrent]}"
52
- warn "[henitai-scheduler] child_intervals=#{summary[:intervals].inspect}"
53
- end
54
-
55
- def build_parallel_queue(mutants)
56
- Queue.new.tap { |queue| mutants.each { |mutant| queue << mutant } }
57
- end
58
-
59
- def build_parallel_context(mutants, integration, config, progress_reporter)
60
- ParallelExecutionContext.new(
61
- build_parallel_queue(mutants),
62
- integration,
63
- config,
64
- progress_reporter,
65
- Mutex.new,
66
- { stopping: false }
67
- )
68
- end
69
-
70
- def install_parallel_signal_traps(context)
71
- context.old_handlers = {
72
- int: trap(:INT) { stop_parallel_execution(context) },
73
- term: trap(:TERM) { stop_parallel_execution(context) },
74
- hup: trap(:HUP) { stop_parallel_execution(context) }
75
- }
76
- end
77
-
78
- def stop_parallel_execution(context)
79
- context.state[:stopping] = true
80
- context.queue.clear
81
- end
82
-
83
- def start_parallel_stdin_watcher(context, stdin_pipe)
84
- return unless stdin_pipe
85
- # CI runners expose stdin as a non-interactive pipe, so EOF there should
86
- # not be treated as a user disconnect.
87
- return if ci_environment?
88
-
89
- context.stdin_watcher = Thread.new do
90
- $stdin.read
91
- stop_parallel_execution(context)
92
- rescue IOError, Errno::EBADF
93
- nil
94
- end
95
- end
96
-
97
- def parallel_workers(context, process_mutant)
98
- Array.new(worker_count) do
99
- Thread.new { process_parallel_worker(context, process_mutant) }
100
- end
101
- end
102
-
103
- def process_parallel_worker(context, process_mutant)
104
- loop do
105
- break if context.state[:stopping]
106
-
107
- run_one_mutant(context, process_mutant)
108
- rescue ThreadError
109
- break
110
- rescue StandardError => e
111
- record_parallel_error(context, e)
112
- break
113
- end
114
- end
115
-
116
- def run_one_mutant(context, process_mutant)
117
- mutant = context.queue.pop(true)
118
- process_mutant.call(
119
- mutant,
120
- context.integration,
121
- context.config,
122
- context.progress_reporter,
123
- context.mutex
124
- )
125
- end
126
-
127
- def stop_parallel_stdin_watcher(context)
128
- context&.stdin_watcher&.kill
129
- end
130
-
131
- def restore_parallel_signal_traps(context)
132
- handlers = context&.old_handlers
133
- return unless handlers
134
-
135
- trap(:INT, handlers[:int] || "DEFAULT")
136
- trap(:TERM, handlers[:term] || "DEFAULT")
137
- trap(:HUP, handlers[:hup] || "DEFAULT")
138
- end
139
-
140
- def record_parallel_error(context, error)
141
- context.mutex.synchronize do
142
- context.state[:error] ||= error
143
- context.state[:stopping] = true
144
- context.queue.clear
145
- end
146
- end
147
-
148
- def ci_environment?
149
- value = ENV.fetch("CI", nil)
150
- value && !%w[0 false].include?(value.downcase)
151
- end
152
- end
153
- end