mutineer-mongoid 2.0.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 (68) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1194 -0
  3. data/LICENSE +21 -0
  4. data/README.md +659 -0
  5. data/bin/mutineer +6 -0
  6. data/lib/mutineer/baseline.rb +185 -0
  7. data/lib/mutineer/changed_lines.rb +136 -0
  8. data/lib/mutineer/child_stdout.rb +35 -0
  9. data/lib/mutineer/cli.rb +681 -0
  10. data/lib/mutineer/config.rb +372 -0
  11. data/lib/mutineer/coverage_map.rb +1366 -0
  12. data/lib/mutineer/daemon_backend.rb +351 -0
  13. data/lib/mutineer/daemon_client.rb +407 -0
  14. data/lib/mutineer/daemon_server.rb +351 -0
  15. data/lib/mutineer/external_backend.rb +288 -0
  16. data/lib/mutineer/file_swap.rb +194 -0
  17. data/lib/mutineer/isolation.rb +321 -0
  18. data/lib/mutineer/job_plan.rb +352 -0
  19. data/lib/mutineer/kill_channel.rb +284 -0
  20. data/lib/mutineer/kill_matrix.rb +88 -0
  21. data/lib/mutineer/minitest_integration/kill_recorder.rb +301 -0
  22. data/lib/mutineer/minitest_integration/outer_reporter.rb +126 -0
  23. data/lib/mutineer/minitest_integration/stop_at_first_failure.rb +145 -0
  24. data/lib/mutineer/minitest_integration.rb +137 -0
  25. data/lib/mutineer/mongoid_failures.rb +82 -0
  26. data/lib/mutineer/mongoid_worker_db.rb +97 -0
  27. data/lib/mutineer/mutant_id.rb +126 -0
  28. data/lib/mutineer/mutation.rb +27 -0
  29. data/lib/mutineer/mutator_registry.rb +109 -0
  30. data/lib/mutineer/mutators/arithmetic.rb +35 -0
  31. data/lib/mutineer/mutators/array_literal.rb +44 -0
  32. data/lib/mutineer/mutators/base.rb +105 -0
  33. data/lib/mutineer/mutators/boolean_connector.rb +52 -0
  34. data/lib/mutineer/mutators/boolean_literal.rb +56 -0
  35. data/lib/mutineer/mutators/chain_link.rb +132 -0
  36. data/lib/mutineer/mutators/collection_method.rb +44 -0
  37. data/lib/mutineer/mutators/comparison.rb +35 -0
  38. data/lib/mutineer/mutators/condition_forcing.rb +134 -0
  39. data/lib/mutineer/mutators/condition_negation.rb +49 -0
  40. data/lib/mutineer/mutators/literal_mutation.rb +51 -0
  41. data/lib/mutineer/mutators/negation_removal.rb +44 -0
  42. data/lib/mutineer/mutators/operand_removal.rb +58 -0
  43. data/lib/mutineer/mutators/operator_assignment.rb +113 -0
  44. data/lib/mutineer/mutators/range_literal.rb +54 -0
  45. data/lib/mutineer/mutators/regex_literal.rb +72 -0
  46. data/lib/mutineer/mutators/return_nil.rb +72 -0
  47. data/lib/mutineer/mutators/safe_navigation.rb +76 -0
  48. data/lib/mutineer/mutators/statement_removal.rb +33 -0
  49. data/lib/mutineer/mutators/string_literal.rb +34 -0
  50. data/lib/mutineer/orphan_guard.rb +28 -0
  51. data/lib/mutineer/pairing.rb +298 -0
  52. data/lib/mutineer/parser.rb +45 -0
  53. data/lib/mutineer/progress.rb +46 -0
  54. data/lib/mutineer/project.rb +506 -0
  55. data/lib/mutineer/project_path.rb +57 -0
  56. data/lib/mutineer/rails_worker_db.rb +212 -0
  57. data/lib/mutineer/reporter.rb +926 -0
  58. data/lib/mutineer/result.rb +240 -0
  59. data/lib/mutineer/runner.rb +418 -0
  60. data/lib/mutineer/statement_lines.rb +183 -0
  61. data/lib/mutineer/subject.rb +38 -0
  62. data/lib/mutineer/test_runners/minitest.rb +22 -0
  63. data/lib/mutineer/test_runners/rspec.rb +330 -0
  64. data/lib/mutineer/test_runners.rb +30 -0
  65. data/lib/mutineer/version.rb +6 -0
  66. data/lib/mutineer/worker_pool.rb +147 -0
  67. data/lib/mutineer.rb +48 -0
  68. metadata +143 -0
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "result"
4
+
5
+ module Mutineer
6
+ # Fixed-size fork pool. `run` forks up to `size` children at once; each child
7
+ # runs the block on one work item, marshals its Result to a private pipe, and
8
+ # exits. The parent drains pipes with IO.select and reaps each finished child
9
+ # by known pid (never wait2(-1), which would steal the host suite's children),
10
+ # opening exactly one slot per reap, then refills. Results are returned in the
11
+ # SAME ORDER as `items` regardless of finish order, so verdicts are identical
12
+ # to a serial run and downstream output is stable.
13
+ #
14
+ # The block is run inside the child via `yield(*items[i])`; whatever it
15
+ # returns (a Result) is the marshaled payload. Per-mutant timeout is handled
16
+ # one level down by Isolation. The pool adds no separate wall clock.
17
+ class WorkerPool
18
+ # Builds a pool.
19
+ #
20
+ # @param size [Integer] desired pool size.
21
+ def initialize(size)
22
+ @size = [size.to_i, 1].max
23
+ end
24
+
25
+ # Runs the work items through the pool.
26
+ #
27
+ # @param items [Array<Array>] work items.
28
+ # @param stop_when [Proc, nil] called with each collected Result; when it
29
+ # returns truthy, no further items are scheduled and the run drains and
30
+ # returns early (--fail-fast). Unscheduled slots stay nil.
31
+ # @param on_result [Proc, nil] called in the parent with each collected
32
+ # Result as it is reaped, in finish order (progress reporting); its
33
+ # return value is ignored. Must be fast and non-blocking: it runs on the
34
+ # pool's single reap thread, so a slow callback stalls draining the other
35
+ # in-flight children's pipes (the #4 deadlock discipline).
36
+ # @yieldparam item [Array] one work item.
37
+ # @return [Array<Mutineer::Result>] results in input order (nil for any item
38
+ # left unscheduled by an early stop).
39
+ def run(items, stop_when: nil, on_result: nil)
40
+ results = Array.new(items.size)
41
+ queue = (0...items.size).to_a
42
+ running = {} # pid => [index, read_io, buffer]
43
+ stopping = false
44
+
45
+ until queue.empty? && running.empty?
46
+ fill(items, queue, running) { |*args| yield(*args) } unless stopping
47
+ result = reap(results, running)
48
+ on_result&.call(result) if result
49
+ if !stopping && stop_when && result && stop_when.call(result)
50
+ stopping = true
51
+ queue.clear # schedule no more; let in-flight workers drain
52
+ end
53
+ end
54
+
55
+ results
56
+ end
57
+
58
+ private
59
+
60
+ # The child must ALWAYS hard-exit. If yield raises, marshal an error Result
61
+ # and exit! in `ensure`. Otherwise the child unwinds normally and our
62
+ # Minitest at_exit autorun re-runs the parent suite inside the worker,
63
+ # losing the real error.
64
+ def fill(items, queue, running)
65
+ while running.size < @size && !queue.empty?
66
+ idx = queue.shift
67
+ rd, wr = IO.pipe
68
+ rd.binmode # Marshal output is binary: keep the pipe byte-exact
69
+ wr.binmode
70
+ begin
71
+ pid = fork do
72
+ rd.close
73
+ payload =
74
+ begin
75
+ yield(*items[idx])
76
+ rescue Exception => e # rubocop:disable Lint/RescueException
77
+ Result.error("worker crashed: #{e.class}: #{e.message}")
78
+ end
79
+ begin
80
+ wr.write(Marshal.dump(payload))
81
+ rescue StandardError # rubocop:disable Lint/SuppressedException
82
+ # pipe gone; parent will record "no result"
83
+ ensure
84
+ wr.close
85
+ exit!(0)
86
+ end
87
+ end
88
+ rescue Errno::EAGAIN
89
+ # Process table is full. Put the item back and reap before retrying;
90
+ # if nothing is running we cannot make progress, so re-raise.
91
+ rd.close
92
+ wr.close
93
+ raise if running.empty?
94
+
95
+ queue.unshift(idx)
96
+ return
97
+ end
98
+ wr.close
99
+ running[pid] = [idx, rd, +""] # idx, read end, accumulated bytes
100
+ end
101
+ end
102
+
103
+ # Drain pipes with IO.select and reap a child only on EOF. The old code
104
+ # reaped first and read after, but a child whose payload exceeds the OS pipe
105
+ # buffer (~64KB) blocks on `write` before it can exit, so it was never reaped
106
+ # and the pool deadlocked. Reading concurrently keeps the pipe drained so the
107
+ # child can finish and exit; EOF means it closed its write end (done writing).
108
+ # We waitpid only OUR known pids (never wait2(-1), which would steal the host
109
+ # suite's children).
110
+ def reap(results, running)
111
+ return if running.empty?
112
+
113
+ loop do
114
+ rds = running.values.map { |v| v[1] }
115
+ ready, = IO.select(rds, nil, nil)
116
+ ready.each do |rd|
117
+ pid, (idx, _io, buf) = running.find { |_, v| v[1].equal?(rd) }
118
+ chunk = rd.read_nonblock(65_536, exception: false)
119
+ next if chunk == :wait_readable
120
+
121
+ if chunk.nil? # EOF: child closed wr; it is done writing and exiting
122
+ rd.close
123
+ Process.waitpid(pid) # reap the now-finished child (no zombie)
124
+ running.delete(pid)
125
+ # Return the collected Result so the caller's stop_when can see it.
126
+ return results[idx] = decode(buf)
127
+ end
128
+ buf << chunk
129
+ end
130
+ end
131
+ end
132
+
133
+ # A partial/garbage Marshal stream (dead worker) must not crash the pool.
134
+ # Degrade to an error Result. The payload loads frozen, which also shares
135
+ # equal strings across payloads (the test names of a --matrix run repeat
136
+ # in every row).
137
+ # @param data [String] marshaled payload.
138
+ # @return [Mutineer::Result] decoded result or error result.
139
+ def decode(data)
140
+ return Result.error("worker produced no result") if data.empty?
141
+
142
+ Marshal.load(data, freeze: true)
143
+ rescue StandardError => e
144
+ Result.error("worker result unreadable: #{e.class}: #{e.message}")
145
+ end
146
+ end
147
+ end
data/lib/mutineer.rb ADDED
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "mutineer/version"
4
+ require_relative "mutineer/config"
5
+ require_relative "mutineer/parser"
6
+ require_relative "mutineer/subject"
7
+ require_relative "mutineer/mutation"
8
+ require_relative "mutineer/project"
9
+ require_relative "mutineer/pairing"
10
+ require_relative "mutineer/result"
11
+ require_relative "mutineer/coverage_map"
12
+ require_relative "mutineer/changed_lines"
13
+ require_relative "mutineer/isolation"
14
+ require_relative "mutineer/minitest_integration"
15
+ require_relative "mutineer/test_runners"
16
+ require_relative "mutineer/mutators/base"
17
+ require_relative "mutineer/mutators/arithmetic"
18
+ require_relative "mutineer/mutators/comparison"
19
+ require_relative "mutineer/mutators/boolean_connector"
20
+ require_relative "mutineer/mutators/boolean_literal"
21
+ require_relative "mutineer/mutators/statement_removal"
22
+ require_relative "mutineer/mutators/return_nil"
23
+ require_relative "mutineer/mutators/literal_mutation"
24
+ require_relative "mutineer/mutators/condition_negation"
25
+ require_relative "mutineer/mutators/string_literal"
26
+ require_relative "mutineer/mutators/regex_literal"
27
+ require_relative "mutineer/mutators/collection_method"
28
+ require_relative "mutineer/mutators/safe_navigation"
29
+ require_relative "mutineer/mutators/range_literal"
30
+ require_relative "mutineer/mutators/negation_removal"
31
+ require_relative "mutineer/mutators/chain_link"
32
+ require_relative "mutineer/mutators/operand_removal"
33
+ require_relative "mutineer/mutators/array_literal"
34
+ require_relative "mutineer/mutators/condition_forcing"
35
+ require_relative "mutineer/mutators/operator_assignment"
36
+ require_relative "mutineer/mutator_registry"
37
+ require_relative "mutineer/worker_pool"
38
+ require_relative "mutineer/progress"
39
+ require_relative "mutineer/job_plan"
40
+ require_relative "mutineer/runner"
41
+ require_relative "mutineer/kill_matrix"
42
+ require_relative "mutineer/reporter"
43
+ require_relative "mutineer/baseline"
44
+ require_relative "mutineer/cli"
45
+
46
+ # Mutineer is the top-level namespace for the mutation-testing library.
47
+ module Mutineer
48
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mutineer-mongoid
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - David Teren
8
+ - Justin Allen Marsh
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 1980-01-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '13.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '13.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: yard
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.9'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.9'
41
+ description: Mutineer mutates your source one change at a time and runs your Minitest
42
+ or RSpec suite against each mutant to find tests that don't actually test anything.
43
+ Prism-based, fork-isolated, zero runtime dependencies.
44
+ email:
45
+ - 48605824+justinallenmarsh@users.noreply.github.com
46
+ executables:
47
+ - mutineer
48
+ extensions: []
49
+ extra_rdoc_files: []
50
+ files:
51
+ - CHANGELOG.md
52
+ - LICENSE
53
+ - README.md
54
+ - bin/mutineer
55
+ - lib/mutineer.rb
56
+ - lib/mutineer/baseline.rb
57
+ - lib/mutineer/changed_lines.rb
58
+ - lib/mutineer/child_stdout.rb
59
+ - lib/mutineer/cli.rb
60
+ - lib/mutineer/config.rb
61
+ - lib/mutineer/coverage_map.rb
62
+ - lib/mutineer/daemon_backend.rb
63
+ - lib/mutineer/daemon_client.rb
64
+ - lib/mutineer/daemon_server.rb
65
+ - lib/mutineer/external_backend.rb
66
+ - lib/mutineer/file_swap.rb
67
+ - lib/mutineer/isolation.rb
68
+ - lib/mutineer/job_plan.rb
69
+ - lib/mutineer/kill_channel.rb
70
+ - lib/mutineer/kill_matrix.rb
71
+ - lib/mutineer/minitest_integration.rb
72
+ - lib/mutineer/minitest_integration/kill_recorder.rb
73
+ - lib/mutineer/minitest_integration/outer_reporter.rb
74
+ - lib/mutineer/minitest_integration/stop_at_first_failure.rb
75
+ - lib/mutineer/mongoid_failures.rb
76
+ - lib/mutineer/mongoid_worker_db.rb
77
+ - lib/mutineer/mutant_id.rb
78
+ - lib/mutineer/mutation.rb
79
+ - lib/mutineer/mutator_registry.rb
80
+ - lib/mutineer/mutators/arithmetic.rb
81
+ - lib/mutineer/mutators/array_literal.rb
82
+ - lib/mutineer/mutators/base.rb
83
+ - lib/mutineer/mutators/boolean_connector.rb
84
+ - lib/mutineer/mutators/boolean_literal.rb
85
+ - lib/mutineer/mutators/chain_link.rb
86
+ - lib/mutineer/mutators/collection_method.rb
87
+ - lib/mutineer/mutators/comparison.rb
88
+ - lib/mutineer/mutators/condition_forcing.rb
89
+ - lib/mutineer/mutators/condition_negation.rb
90
+ - lib/mutineer/mutators/literal_mutation.rb
91
+ - lib/mutineer/mutators/negation_removal.rb
92
+ - lib/mutineer/mutators/operand_removal.rb
93
+ - lib/mutineer/mutators/operator_assignment.rb
94
+ - lib/mutineer/mutators/range_literal.rb
95
+ - lib/mutineer/mutators/regex_literal.rb
96
+ - lib/mutineer/mutators/return_nil.rb
97
+ - lib/mutineer/mutators/safe_navigation.rb
98
+ - lib/mutineer/mutators/statement_removal.rb
99
+ - lib/mutineer/mutators/string_literal.rb
100
+ - lib/mutineer/orphan_guard.rb
101
+ - lib/mutineer/pairing.rb
102
+ - lib/mutineer/parser.rb
103
+ - lib/mutineer/progress.rb
104
+ - lib/mutineer/project.rb
105
+ - lib/mutineer/project_path.rb
106
+ - lib/mutineer/rails_worker_db.rb
107
+ - lib/mutineer/reporter.rb
108
+ - lib/mutineer/result.rb
109
+ - lib/mutineer/runner.rb
110
+ - lib/mutineer/statement_lines.rb
111
+ - lib/mutineer/subject.rb
112
+ - lib/mutineer/test_runners.rb
113
+ - lib/mutineer/test_runners/minitest.rb
114
+ - lib/mutineer/test_runners/rspec.rb
115
+ - lib/mutineer/version.rb
116
+ - lib/mutineer/worker_pool.rb
117
+ homepage: https://github.com/justinallenmarsh/mutineer-mongoid
118
+ licenses:
119
+ - MIT
120
+ metadata:
121
+ source_code_uri: https://github.com/justinallenmarsh/mutineer-mongoid
122
+ changelog_uri: https://github.com/justinallenmarsh/mutineer-mongoid/blob/main/CHANGELOG.md
123
+ bug_tracker_uri: https://github.com/justinallenmarsh/mutineer-mongoid/issues
124
+ documentation_uri: https://github.com/justinallenmarsh/mutineer-mongoid#readme
125
+ rubygems_mfa_required: 'true'
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '3.4'
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubygems_version: 3.6.9
141
+ specification_version: 4
142
+ summary: Mutation testing with parallel Mongoid/RSpec support, based on Mutineer.
143
+ test_files: []