kimera 0.1.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 (171) hide show
  1. checksums.yaml +7 -0
  2. data/.kimera.yml.example +59 -0
  3. data/CHANGELOG.md +36 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +528 -0
  6. data/exe/kimera +22 -0
  7. data/lib/kimera/audit/operator_audit.rb +71 -0
  8. data/lib/kimera/cli/argv.rb +15 -0
  9. data/lib/kimera/cli/baseline.rb +87 -0
  10. data/lib/kimera/cli/changed.rb +27 -0
  11. data/lib/kimera/cli/ci.rb +27 -0
  12. data/lib/kimera/cli/completion.rb +37 -0
  13. data/lib/kimera/cli/doctor.rb +115 -0
  14. data/lib/kimera/cli/flag.rb +55 -0
  15. data/lib/kimera/cli/help_text.rb +45 -0
  16. data/lib/kimera/cli/init.rb +105 -0
  17. data/lib/kimera/cli/mutant.rb +82 -0
  18. data/lib/kimera/cli/report_file.rb +20 -0
  19. data/lib/kimera/cli/run/arguments.rb +58 -0
  20. data/lib/kimera/cli/run/cycle.rb +78 -0
  21. data/lib/kimera/cli/run/digest.rb +49 -0
  22. data/lib/kimera/cli/run/emission.rb +45 -0
  23. data/lib/kimera/cli/run/gate.rb +43 -0
  24. data/lib/kimera/cli/run/options.rb +85 -0
  25. data/lib/kimera/cli/run/pass.rb +100 -0
  26. data/lib/kimera/cli/run/sources.rb +56 -0
  27. data/lib/kimera/cli/run.rb +52 -0
  28. data/lib/kimera/cli/skill.rb +23 -0
  29. data/lib/kimera/cli/suggestion.rb +36 -0
  30. data/lib/kimera/cli/survivors/panel.rb +81 -0
  31. data/lib/kimera/cli/survivors.rb +89 -0
  32. data/lib/kimera/cli/synthesize.rb +66 -0
  33. data/lib/kimera/cli/workflows.rb +8 -0
  34. data/lib/kimera/cli.rb +132 -0
  35. data/lib/kimera/error.rb +3 -0
  36. data/lib/kimera/execution/baseline_failure.rb +47 -0
  37. data/lib/kimera/execution/baseline_pass.rb +123 -0
  38. data/lib/kimera/execution/boot.rb +50 -0
  39. data/lib/kimera/execution/callback.rb +33 -0
  40. data/lib/kimera/execution/child_process.rb +48 -0
  41. data/lib/kimera/execution/composite_isolation.rb +24 -0
  42. data/lib/kimera/execution/harness.rb +150 -0
  43. data/lib/kimera/execution/isolated.rb +64 -0
  44. data/lib/kimera/execution/isolated_child.rb +15 -0
  45. data/lib/kimera/execution/isolated_child_command.rb +44 -0
  46. data/lib/kimera/execution/isolated_child_minitest.rb +34 -0
  47. data/lib/kimera/execution/isolated_plan.rb +54 -0
  48. data/lib/kimera/execution/isolated_scheduling.rb +54 -0
  49. data/lib/kimera/execution/isolated_test_selection.rb +22 -0
  50. data/lib/kimera/execution/isolated_verdict.rb +72 -0
  51. data/lib/kimera/execution/isolated_watchdog.rb +59 -0
  52. data/lib/kimera/execution/isolation.rb +17 -0
  53. data/lib/kimera/execution/null_progress.rb +17 -0
  54. data/lib/kimera/execution/overlay_guard_modules.rb +95 -0
  55. data/lib/kimera/execution/overlay_guards.rb +56 -0
  56. data/lib/kimera/execution/parallel_test_databases.rb +64 -0
  57. data/lib/kimera/execution/pool_driver.rb +101 -0
  58. data/lib/kimera/execution/priority.rb +23 -0
  59. data/lib/kimera/execution/reload.rb +104 -0
  60. data/lib/kimera/execution/rig.rb +41 -0
  61. data/lib/kimera/execution/schedule.rb +105 -0
  62. data/lib/kimera/execution/schemata.rb +88 -0
  63. data/lib/kimera/execution/shift/attempt.rb +34 -0
  64. data/lib/kimera/execution/shift/coverage_channel.rb +43 -0
  65. data/lib/kimera/execution/shift/killer_memory.rb +20 -0
  66. data/lib/kimera/execution/shift/leak_guard.rb +34 -0
  67. data/lib/kimera/execution/shift.rb +137 -0
  68. data/lib/kimera/execution/sweep.rb +30 -0
  69. data/lib/kimera/execution/transaction_isolation.rb +32 -0
  70. data/lib/kimera/execution/trial.rb +16 -0
  71. data/lib/kimera/execution/verdicts.rb +78 -0
  72. data/lib/kimera/execution/worker_pool/fleet.rb +104 -0
  73. data/lib/kimera/execution/worker_pool/stillborn_guard.rb +40 -0
  74. data/lib/kimera/execution/worker_pool/worker.rb +32 -0
  75. data/lib/kimera/execution/worker_pool.rb +101 -0
  76. data/lib/kimera/frameworks/adapter.rb +82 -0
  77. data/lib/kimera/frameworks/minitest_adapter.rb +102 -0
  78. data/lib/kimera/frameworks/rspec_adapter.rb +123 -0
  79. data/lib/kimera/frameworks/rspec_group_index.rb +45 -0
  80. data/lib/kimera/incremental/git_diff.rb +98 -0
  81. data/lib/kimera/incremental/selection.rb +27 -0
  82. data/lib/kimera/incremental/session.rb +109 -0
  83. data/lib/kimera/memoization.rb +104 -0
  84. data/lib/kimera/operators/argument_drop.rb +19 -0
  85. data/lib/kimera/operators/arithmetic.rb +12 -0
  86. data/lib/kimera/operators/audit.rb +3 -0
  87. data/lib/kimera/operators/base.rb +40 -0
  88. data/lib/kimera/operators/binary_swap.rb +23 -0
  89. data/lib/kimera/operators/boolean.rb +26 -0
  90. data/lib/kimera/operators/chain_link_deletion.rb +25 -0
  91. data/lib/kimera/operators/collection_literal.rb +18 -0
  92. data/lib/kimera/operators/comparison.rb +13 -0
  93. data/lib/kimera/operators/conditional.rb +36 -0
  94. data/lib/kimera/operators/default_argument.rb +17 -0
  95. data/lib/kimera/operators/element_drop.rb +31 -0
  96. data/lib/kimera/operators/index_fetch.rb +15 -0
  97. data/lib/kimera/operators/kernel_coercion.rb +17 -0
  98. data/lib/kimera/operators/method_unwrap.rb +20 -0
  99. data/lib/kimera/operators/negation.rb +15 -0
  100. data/lib/kimera/operators/node_swap.rb +20 -0
  101. data/lib/kimera/operators/numeric_literal.rb +39 -0
  102. data/lib/kimera/operators/op_assign.rb +18 -0
  103. data/lib/kimera/operators/plugins.rb +27 -0
  104. data/lib/kimera/operators/rails_association.rb +24 -0
  105. data/lib/kimera/operators/rails_callback.rb +49 -0
  106. data/lib/kimera/operators/rails_declaration.rb +16 -0
  107. data/lib/kimera/operators/rails_permit.rb +34 -0
  108. data/lib/kimera/operators/rails_validation.rb +17 -0
  109. data/lib/kimera/operators/range.rb +15 -0
  110. data/lib/kimera/operators/regexp_literal.rb +16 -0
  111. data/lib/kimera/operators/respond_to_guard.rb +24 -0
  112. data/lib/kimera/operators/return_value.rb +36 -0
  113. data/lib/kimera/operators/safe_navigation.rb +15 -0
  114. data/lib/kimera/operators/selector_swap.rb +24 -0
  115. data/lib/kimera/operators/statement_deletion.rb +26 -0
  116. data/lib/kimera/operators/string_literal.rb +22 -0
  117. data/lib/kimera/operators/symbol_literal.rb +26 -0
  118. data/lib/kimera/operators/vocabulary.rb +82 -0
  119. data/lib/kimera/operators.rb +141 -0
  120. data/lib/kimera/plugins.rb +3 -0
  121. data/lib/kimera/registry/builder.rb +59 -0
  122. data/lib/kimera/registry/mutation_point.rb +146 -0
  123. data/lib/kimera/registry/numbering.rb +15 -0
  124. data/lib/kimera/registry/registry.rb +120 -0
  125. data/lib/kimera/registry/source_file.rb +71 -0
  126. data/lib/kimera/registry/tally.rb +11 -0
  127. data/lib/kimera/registry/walking.rb +55 -0
  128. data/lib/kimera/report/actions.rb +38 -0
  129. data/lib/kimera/report/coloring.rb +25 -0
  130. data/lib/kimera/report/formats.rb +78 -0
  131. data/lib/kimera/report/live.rb +56 -0
  132. data/lib/kimera/report/log.rb +46 -0
  133. data/lib/kimera/report/progress.rb +55 -0
  134. data/lib/kimera/report/screen.rb +44 -0
  135. data/lib/kimera/report/sections.rb +50 -0
  136. data/lib/kimera/report/tally.rb +84 -0
  137. data/lib/kimera/report/text.rb +104 -0
  138. data/lib/kimera/results/result.rb +72 -0
  139. data/lib/kimera/results/run_report.rb +114 -0
  140. data/lib/kimera/rewrite/ast_walk.rb +20 -0
  141. data/lib/kimera/rewrite/directive.rb +61 -0
  142. data/lib/kimera/rewrite/directive_extra_handlers.rb +52 -0
  143. data/lib/kimera/rewrite/directive_handlers.rb +138 -0
  144. data/lib/kimera/rewrite/directive_kwarg_handlers.rb +44 -0
  145. data/lib/kimera/rewrite/numbered_params.rb +50 -0
  146. data/lib/kimera/runtime.rb +51 -0
  147. data/lib/kimera/scope/config.rb +90 -0
  148. data/lib/kimera/scope/file_set.rb +51 -0
  149. data/lib/kimera/scope/ignore_list.rb +55 -0
  150. data/lib/kimera/self_protection.rb +56 -0
  151. data/lib/kimera/support/duration.rb +13 -0
  152. data/lib/kimera/support/errors.rb +8 -0
  153. data/lib/kimera/support/operator_protocol.rb +10 -0
  154. data/lib/kimera/support/syntax.rb +68 -0
  155. data/lib/kimera/support/syntax_types.rb +20 -0
  156. data/lib/kimera/support/unparse.rb +15 -0
  157. data/lib/kimera/support/version.rb +5 -0
  158. data/lib/kimera/support/warnings.rb +15 -0
  159. data/lib/kimera/synthesis/file_weave.rb +78 -0
  160. data/lib/kimera/synthesis/guard_weaver.rb +77 -0
  161. data/lib/kimera/synthesis/guardrail_dispatch.rb +68 -0
  162. data/lib/kimera/synthesis/guardrail_interpolation.rb +59 -0
  163. data/lib/kimera/synthesis/guardrail_value_objects.rb +46 -0
  164. data/lib/kimera/synthesis/overlay.rb +52 -0
  165. data/lib/kimera/synthesis/overlay_splice.rb +101 -0
  166. data/lib/kimera/synthesis/project.rb +58 -0
  167. data/lib/kimera/synthesis/source_map.rb +42 -0
  168. data/lib/kimera/version.rb +3 -0
  169. data/lib/kimera.rb +13 -0
  170. data/skills/kimera/SKILL.md +118 -0
  171. metadata +259 -0
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "isolation"
4
+
5
+ class Kimera::Execution::CompositeIsolation < Kimera::Execution::Isolation
6
+ def initialize(strategies)
7
+ super()
8
+ @given = strategies
9
+ end
10
+
11
+ def around(&block)
12
+ strategies.reverse.reduce(block) do |inner, strategy|
13
+ -> { strategy.around(&inner) }
14
+ end.call
15
+ end
16
+
17
+ def reset!
18
+ strategies.each(&:reset!)
19
+ end
20
+
21
+ private
22
+
23
+ def strategies = @_strategies ||= Array(@given)
24
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "english"
4
+ require "json"
5
+ require_relative "../error"
6
+ require_relative "../results/result"
7
+ require_relative "../results/run_report"
8
+ require_relative "../runtime"
9
+ require_relative "../self_protection"
10
+ require_relative "baseline_pass"
11
+ require_relative "boot"
12
+ require_relative "child_process"
13
+ require_relative "isolation"
14
+ require_relative "null_progress"
15
+ require_relative "parallel_test_databases"
16
+ require_relative "pool_driver"
17
+ require_relative "reload"
18
+ require_relative "rig"
19
+ require_relative "schedule"
20
+ require_relative "schemata"
21
+ require_relative "verdicts"
22
+ require_relative "shift"
23
+ require_relative "worker_pool"
24
+
25
+ class Kimera::Execution::Harness
26
+ include Kimera::Execution::ChildProcess
27
+
28
+ Context = Data.define(:registry, :adapter, :options)
29
+ State = Struct.new(:skipped, :irrelevant, :isolation)
30
+
31
+ class << self
32
+ def new(registry:, adapter:, **options)
33
+ super(Context.new(registry, adapter, options), State.new({}, [], nil))
34
+ end
35
+ end
36
+
37
+ def initialize(context, state)
38
+ @context = context
39
+ @state = state
40
+ end
41
+
42
+ def warm!(test_files)
43
+ overlay!(test_files).tap do
44
+ measure!
45
+ notice
46
+ end
47
+ end
48
+
49
+ def without_coverage!(test_files)
50
+ overlay!(test_files).tap do
51
+ verify!
52
+ end
53
+ end
54
+
55
+ def overlay!(test_files)
56
+ boot.suite(test_files)
57
+ loader = Kimera::Execution::Schemata.new(registry, root: root, errors: errors)
58
+ loader.overlay!.tap { complete(loader) }
59
+ end
60
+
61
+ def load! = boot.load!
62
+
63
+ def isolate! = (state.isolation = boot.isolate(isolation))
64
+
65
+ def run(ids: mutants, label: "mutants")
66
+ schedule.run(ids, coverage: coverage, label: label)
67
+ end
68
+
69
+ def coverage = options[:coverage]
70
+
71
+ def skipped = state.skipped
72
+
73
+ private
74
+
75
+ attr_reader :context, :state
76
+
77
+ def registry = context.registry
78
+ def adapter = context.adapter
79
+ def options = context.options
80
+
81
+ def root = options.fetch(:source_root, ".")
82
+
83
+ def errors = options.fetch(:errio, $stderr)
84
+
85
+ def isolation = state.isolation ||= options.fetch(:isolation) { Kimera::Execution::Isolation.new }
86
+
87
+ def soft = options.fetch(:soft_timeout, 5.0)
88
+
89
+ def hard = @_hard ||= options.fetch(:hard_timeout) { soft ? (soft * 3) + 1 : 30.0 }
90
+
91
+ def jobs = @_jobs ||= [Integer(options.fetch(:jobs, 1)), 1].max
92
+
93
+ def progress = options.fetch(:progress, Kimera::Execution::NullProgress)
94
+
95
+ def driver = rig.driver
96
+
97
+ def spawner = rig.spawner
98
+
99
+ def schedule = rig.schedule
100
+
101
+ def rig
102
+ @_rig ||= Kimera::Execution::Rig.new(
103
+ registry: registry, adapter: adapter, isolation: isolation, root: root,
104
+ soft: soft, hard: hard, leak: options.fetch(:leak_every, 10), jobs: jobs,
105
+ coverage: coverage, spawner: options[:spawner], progress: progress
106
+ )
107
+ end
108
+
109
+ def boot
110
+ @_boot ||= Kimera::Execution::Boot.new(adapter: adapter, isolate: options[:isolate_db], errors: errors)
111
+ end
112
+
113
+ def complete(loader)
114
+ state.skipped = loader.skipped
115
+ isolate!
116
+ end
117
+
118
+ def mutants = registry.each.map { |mutant, _path| mutant.id }
119
+
120
+ def measure!
121
+ pass = baseline
122
+ measured = jobs > 1 ? parallel(pass) : pass.measure!
123
+ options[:coverage] = measured.coverage
124
+ driver.coverage = coverage
125
+ state.irrelevant = measured.irrelevant
126
+ end
127
+
128
+ def parallel(pass)
129
+ pass.parallel! do |resolve:, lost:|
130
+ driver.drive(adapter.test_ids, driver.method(:channel), resolve: resolve, lost: lost)
131
+ end
132
+ end
133
+
134
+ def verify! = baseline.check!
135
+
136
+ def baseline
137
+ Kimera::Execution::BaselinePass.new(adapter: adapter, registry: registry, progress: progress)
138
+ end
139
+
140
+ def notice
141
+ count = state.irrelevant.size
142
+ return if count.zero?
143
+ errors.puts(message(count))
144
+ end
145
+
146
+ def message(count)
147
+ "kimera: #{count} failing test(s) cover no in-scope mutant and were " \
148
+ "excluded from the baseline (they gate nothing this run)"
149
+ end
150
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "tmpdir"
5
+ require_relative "../results/result"
6
+ require_relative "../results/run_report"
7
+ require_relative "../synthesis/overlay"
8
+ require_relative "isolated_plan"
9
+ require_relative "isolated_scheduling"
10
+ require_relative "isolated_verdict"
11
+ require_relative "isolated_watchdog"
12
+ require_relative "null_progress"
13
+
14
+ module Kimera
15
+ module Execution
16
+ end
17
+ end
18
+
19
+ class Kimera::Execution::IsolatedExecution
20
+ include Kimera::Execution::IsolatedExecutionScheduling
21
+ include Kimera::Execution::IsolatedExecutionVerdict
22
+ include Kimera::Execution::IsolatedExecutionWatchdog
23
+
24
+ DEFAULT_HARD_TIMEOUT = 300.0
25
+
26
+ def initialize(registry:, root:, tests:, **options)
27
+ @registry = registry
28
+ @root = root
29
+ @tests = tests
30
+ @options = options
31
+ end
32
+
33
+ def run(ids: self.ids, label: "mutants")
34
+ progress.start(ids.size, label)
35
+ report(ids)
36
+ ensure
37
+ progress.finish
38
+ end
39
+
40
+ private
41
+
42
+ def progress = @options.fetch(:progress, Kimera::Execution::NullProgress)
43
+
44
+ def limit = @options.fetch(:hard_timeout, DEFAULT_HARD_TIMEOUT)
45
+
46
+ def jobs = @_jobs ||= [Integer(@options.fetch(:jobs, 1)), 1].max
47
+
48
+ def errors = @options.fetch(:errio, $stderr)
49
+
50
+ def plan
51
+ @_plan ||= Kimera::Execution::IsolatedPlan.new(
52
+ registry: @registry, root: @root, tests: @tests,
53
+ coverage: @options.fetch(:coverage, {}), framework: @options.fetch(:framework, "rspec"),
54
+ test_files: @options.fetch(:test_files, [])
55
+ )
56
+ end
57
+
58
+ def report(ids)
59
+ Kimera::RunReport.new(
60
+ results: (jobs > 1 ? concurrently(ids) : serially(ids)),
61
+ leaks: [], registry: plan.registry
62
+ )
63
+ end
64
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec/core"
4
+ require "stringio"
5
+
6
+ begin
7
+ RSpec::Core::ConfigurationOptions.new([]).configure(RSpec.configuration)
8
+ rescue StandardError, ScriptError
9
+ nil
10
+ end
11
+ RSpec.configuration.output_stream = StringIO.new
12
+ RSpec.configuration.error_stream = StringIO.new
13
+ RSpec.configuration.deprecation_stream = StringIO.new
14
+
15
+ exit RSpec::Core::Runner.run(ARGV)
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kimera
4
+ module Execution
5
+ end
6
+ end
7
+
8
+ class Kimera::Execution::IsolatedPlan; end
9
+
10
+ class Kimera::Execution::IsolatedPlan::ChildCommand
11
+ CHILD = File.expand_path("isolated_child.rb", __dir__)
12
+ MINITEST_CHILD = File.expand_path("isolated_child_minitest.rb", __dir__)
13
+
14
+ def initialize(framework:, test_files:)
15
+ @framework = framework
16
+ @test_files = test_files
17
+ end
18
+
19
+ def command(mirror, locations, paths:)
20
+ bundled(mirror, {}, ["ruby", *paths.flat_map { |path| ["-I", path] }, "-I", helpers, *argv(locations)])
21
+ end
22
+
23
+ def argv(locations)
24
+ return [CHILD, *locations] unless minitest?
25
+ [MINITEST_CHILD, *@test_files, "--", *locations]
26
+ end
27
+
28
+ def helpers
29
+ minitest? ? "test" : "spec"
30
+ end
31
+
32
+ private
33
+
34
+ def bundled(mirror, env, argv)
35
+ gemfile = File.join(mirror, "Gemfile")
36
+ return [env, argv] unless File.file?(gemfile)
37
+ env["BUNDLE_GEMFILE"] = gemfile
38
+ [env, ["bundle", "exec", *argv]]
39
+ end
40
+
41
+ def minitest?
42
+ @framework.to_s == "minitest"
43
+ end
44
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest"
4
+
5
+ Minitest.seed ||= 1
6
+ begin
7
+ Minitest.class_variable_set(:@@installed_at_exit, :installed)
8
+ rescue NameError, ArgumentError
9
+ class << Minitest
10
+ def run(*) = true
11
+ end
12
+ end
13
+
14
+ split = ARGV.index("--") or abort("usage: <test files...> -- <ids...>")
15
+ files = ARGV[0...split]
16
+ ids = ARGV[(split + 1)..]
17
+
18
+ files.each { |f| load File.expand_path(f) }
19
+
20
+ methods = {}
21
+ Minitest::Runnable.runnables.each do |runnable|
22
+ next unless runnable.respond_to?(:runnable_methods)
23
+ runnable.runnable_methods.each { |name| methods["#{runnable}##{name}"] = [runnable, name] }
24
+ end
25
+
26
+ killed =
27
+ ids.any? do |id|
28
+ klass, name = methods[id]
29
+ next false unless klass
30
+ result = klass.new(name).run
31
+ !result.passed? && !result.skipped?
32
+ end
33
+
34
+ exit(killed ? 1 : 0)
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../self_protection"
4
+ require_relative "isolated_child_command"
5
+ require_relative "isolated_test_selection"
6
+
7
+ module Kimera
8
+ module Execution
9
+ end
10
+ end
11
+
12
+ class Kimera::Execution::IsolatedPlan
13
+ MIRROR_SKIP = %w[.git tmp vendor node_modules coverage log .bundle].freeze
14
+
15
+ attr_reader :registry
16
+
17
+ def initialize(registry:, root:, tests:, **options)
18
+ @registry = registry
19
+ @given = root
20
+ @tests = tests
21
+ @options = options
22
+ end
23
+
24
+ def root = @_root ||= File.expand_path(@given)
25
+
26
+ def tests(id, point)
27
+ all = selection.all
28
+ return all if Kimera::SelfProtection.protected?(File.join(root, point.file))
29
+ return all unless point.safe?
30
+ selection.recorded(id)
31
+ end
32
+
33
+ def command(mirror, locations)
34
+ child.command(mirror, locations, paths: mutables)
35
+ end
36
+
37
+ def mutables
38
+ @registry.files.map { |f| f.split(File::SEPARATOR).first }.uniq
39
+ end
40
+
41
+ class << self
42
+ def mirrors(entries)
43
+ entries.reject { |entry| MIRROR_SKIP.include?(entry) }
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def selection = @_selection ||= TestSelection.new(all_tests: @tests, coverage: @options.fetch(:coverage, {}))
50
+
51
+ def child
52
+ @_child ||= ChildCommand.new(framework: @options.fetch(:framework), test_files: @options.fetch(:test_files))
53
+ end
54
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "sweep"
4
+ require_relative "trial"
5
+
6
+ module Kimera
7
+ module Execution
8
+ module IsolatedExecutionScheduling
9
+ private
10
+
11
+ def ids
12
+ plan.registry.each.map { |m, _p| m.id }
13
+ end
14
+
15
+ def serially(ids)
16
+ sweep = Kimera::Execution::Sweep.new(ids, Mutex.new)
17
+ with_mirror { |mirror| scan(sweep, mirror) }
18
+ sweep.results
19
+ end
20
+
21
+ def scan(sweep, mirror)
22
+ synth = Overlay.new(plan.registry)
23
+ sweep.each { |id| charge(sweep, evaluate(Kimera::Execution::Trial.new(id, mirror, synth))) }
24
+ end
25
+
26
+ def concurrently(ids)
27
+ sweep = Kimera::Execution::Sweep.new(ids, Mutex.new)
28
+ workers(sweep).each(&:join)
29
+ sweep.results
30
+ end
31
+
32
+ def workers(sweep)
33
+ Array.new(jobs) { Thread.new { work(sweep) } }
34
+ end
35
+
36
+ def work(sweep)
37
+ synth = Overlay.new(plan.registry)
38
+ with_mirror { |mirror| drain(sweep, mirror, synth) }
39
+ end
40
+
41
+ def drain(sweep, mirror, synth)
42
+ while (id = sweep.pop)
43
+ charge(sweep, evaluate(Kimera::Execution::Trial.new(id, mirror, synth)))
44
+ end
45
+ end
46
+
47
+ def charge(sweep, result)
48
+ done = sweep.record(result)
49
+ progress.tick(result.status)
50
+ trace(result, done, sweep.total)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kimera
4
+ module Execution
5
+ end
6
+ end
7
+
8
+ class Kimera::Execution::IsolatedPlan; end
9
+
10
+ class Kimera::Execution::IsolatedPlan::TestSelection
11
+ attr_reader :all
12
+
13
+ def initialize(all_tests:, coverage:)
14
+ @all = all_tests
15
+ @coverage = coverage
16
+ end
17
+
18
+ def recorded(id)
19
+ tests = @coverage[id] || @coverage[id.to_s]
20
+ tests unless Array(tests).empty?
21
+ end
22
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kimera
4
+ module Execution
5
+ module IsolatedExecutionVerdict
6
+ private
7
+
8
+ def evaluate(trial)
9
+ point = plan.registry.index[trial.id]
10
+ return error(trial, "unknown mutant") unless point
11
+ bake(trial, point)
12
+ rescue StandardError => error
13
+ self.error(trial, "#{error.class}: #{error.message}")
14
+ end
15
+
16
+ def bake(trial, point)
17
+ trial.file = point.file
18
+ tests = plan.tests(trial.id, point)
19
+ return result(trial, :no_coverage, nil) unless tests
20
+ status, duration = measure(trial, tests)
21
+ result(trial, status, duration, tests)
22
+ end
23
+
24
+ def measure(trial, tests)
25
+ started = now
26
+ [with_mutant(trial) { verdict(trial.mirror, tests) }, now - started]
27
+ end
28
+
29
+ def with_mutant(trial)
30
+ original, target = trial.stage(plan.root)
31
+ yield
32
+ ensure
33
+ File.write(target, original) if original
34
+ end
35
+
36
+ def verdict(mirror, locations)
37
+ env, cmd = plan.command(mirror, locations)
38
+ status = waitfor(Process.spawn(env, *cmd, chdir: mirror, pgroup: true, out: File::NULL, err: File::NULL))
39
+ return :timeout if status == :timeout
40
+ status.success? ? :survived : :killed
41
+ end
42
+
43
+ def with_mirror
44
+ Dir.mktmpdir("kimera-isolated") do |mirror|
45
+ populate(mirror)
46
+ yield(mirror)
47
+ end
48
+ end
49
+
50
+ def populate(mirror)
51
+ root = plan.root
52
+ IsolatedPlan.mirrors(Dir.children(root)).each do |entry|
53
+ FileUtils.cp_r(File.join(root, entry), File.join(mirror, entry))
54
+ end
55
+ end
56
+
57
+ def result(trial, status, duration, cover = nil)
58
+ MutantResult.new(
59
+ mutant_id: trial.id, status: status, file: trial.file, duration: duration, covering_tests: cover
60
+ )
61
+ end
62
+
63
+ def error(trial, detail)
64
+ MutantResult.new(mutant_id: trial.id, status: :error, file: trial.file, detail: detail)
65
+ end
66
+
67
+ def now
68
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kimera
4
+ module Execution
5
+ module IsolatedExecutionWatchdog
6
+ private
7
+
8
+ def waitfor(pid)
9
+ deadline = now + limit
10
+ loop do
11
+ step = attempt(pid, deadline)
12
+ return step unless step == :continue
13
+ end
14
+ end
15
+
16
+ def attempt(pid, deadline)
17
+ done, status = Process.waitpid2(pid, Process::WNOHANG)
18
+ return status if done
19
+ return fail!(pid) unless (deadline - now).positive?
20
+ pause
21
+ :continue
22
+ end
23
+
24
+ def pause
25
+ sleep(@options.fetch(:poll_interval, 0.05))
26
+ end
27
+
28
+ def fail!(pid)
29
+ kill(pid)
30
+ reap(pid)
31
+ :timeout
32
+ end
33
+
34
+ def kill(pid)
35
+ Process.kill("KILL", -pid)
36
+ rescue Errno::ESRCH
37
+ nil
38
+ end
39
+
40
+ def reap(pid)
41
+ Process.waitpid(pid)
42
+ rescue Errno::ECHILD
43
+ nil
44
+ end
45
+
46
+ def trace(result, done, total)
47
+ return if progress.enabled?
48
+ errors.puts(line(result, done, total))
49
+ end
50
+
51
+ def line(result, done, total)
52
+ format(
53
+ "kimera: isolated %d/%d #%s %s %s (%.1fs)", done, total,
54
+ *result.to_h.values_at("mutant_id", "status", "file", "duration")
55
+ )
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kimera
4
+ module Execution
5
+ end
6
+ end
7
+
8
+ class Kimera::Execution::Isolation
9
+ def around
10
+ yield
11
+ end
12
+
13
+ def reset!; end
14
+ end
15
+
16
+ require_relative "composite_isolation"
17
+ require_relative "transaction_isolation"
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kimera
4
+ module Execution
5
+ module NullProgress
6
+ module_function
7
+
8
+ def start(_total, _label = nil); end
9
+
10
+ def tick(_status = nil); end
11
+
12
+ def finish; end
13
+
14
+ def enabled? = false
15
+ end
16
+ end
17
+ end