mutineer 1.7.0 → 1.8.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +432 -0
  3. data/README.md +157 -747
  4. data/lib/mutineer/baseline.rb +1 -1
  5. data/lib/mutineer/changed_lines.rb +2 -1
  6. data/lib/mutineer/cli.rb +119 -27
  7. data/lib/mutineer/concern_blocks.rb +168 -5
  8. data/lib/mutineer/config.rb +6 -0
  9. data/lib/mutineer/coverage_map.rb +92 -17
  10. data/lib/mutineer/daemon_backend.rb +75 -32
  11. data/lib/mutineer/daemon_client.rb +50 -17
  12. data/lib/mutineer/daemon_server.rb +92 -40
  13. data/lib/mutineer/external_backend.rb +150 -17
  14. data/lib/mutineer/isolation.rb +67 -25
  15. data/lib/mutineer/job_plan.rb +117 -33
  16. data/lib/mutineer/kill_channel.rb +24 -14
  17. data/lib/mutineer/migrate.rb +10 -8
  18. data/lib/mutineer/minitest_integration/in_test_database_error.rb +184 -0
  19. data/lib/mutineer/minitest_integration/kill_recorder.rb +25 -17
  20. data/lib/mutineer/minitest_integration.rb +44 -14
  21. data/lib/mutineer/mutators/array_literal.rb +4 -2
  22. data/lib/mutineer/mutators/base.rb +22 -0
  23. data/lib/mutineer/mutators/call_to_receiver.rb +20 -7
  24. data/lib/mutineer/mutators/chain_link.rb +6 -6
  25. data/lib/mutineer/mutators/condition_forcing.rb +6 -3
  26. data/lib/mutineer/mutators/operand_removal.rb +4 -2
  27. data/lib/mutineer/mutators/range_literal.rb +105 -4
  28. data/lib/mutineer/mutators/return_nil.rb +1 -0
  29. data/lib/mutineer/orphan_guard.rb +18 -6
  30. data/lib/mutineer/pairing.rb +62 -1
  31. data/lib/mutineer/project.rb +226 -12
  32. data/lib/mutineer/rails_worker_db.rb +200 -16
  33. data/lib/mutineer/redaction.rb +112 -11
  34. data/lib/mutineer/reporter.rb +109 -21
  35. data/lib/mutineer/result.rb +33 -5
  36. data/lib/mutineer/runner.rb +126 -18
  37. data/lib/mutineer/statement_lines.rb +14 -8
  38. data/lib/mutineer/subject.rb +13 -5
  39. data/lib/mutineer/test_runners/minitest.rb +2 -1
  40. data/lib/mutineer/test_runners.rb +4 -2
  41. data/lib/mutineer/triage.rb +1 -1
  42. data/lib/mutineer/version.rb +1 -1
  43. data/lib/mutineer/worker_pool.rb +2 -1
  44. data/skills/mutineer/SKILL.md +84 -0
  45. metadata +5 -1
@@ -4,7 +4,7 @@ require_relative "project_path"
4
4
 
5
5
  module Mutineer
6
6
  # Source -> test pairing by path convention (#11). Pure stdlib path logic:
7
- # no Rails, no class loading, no process. Two jobs:
7
+ # no Rails, no class loading, no process. Three jobs:
8
8
  # * expand_sources — a directory argument becomes its sorted **/*.rb files;
9
9
  # a file inside the root becomes its root-relative path.
10
10
  # * infer_tests — a source's test files by convention (app/ and lib/
@@ -13,6 +13,9 @@ module Mutineer
13
13
  # namespaced subdirectories. The first EXISTING exact
14
14
  # candidate stays first. Split Minitest files join a
15
15
  # Minitest match. A matched spec is left as that one file.
16
+ # * test_given_as_source? — whether a positional argument is a test file
17
+ # by these same names, given as a source by mistake
18
+ # after `--test` (#293).
16
19
  #
17
20
  # Independently unit-testable: every method is pure in/out over the
18
21
  # filesystem, so the pairing contract is exercised with plain fixtures, no
@@ -144,6 +147,64 @@ module Mutineer
144
147
  prefer == "rspec" ? rspec + minitest : minitest + rspec
145
148
  end
146
149
 
150
+ # Basenames that pair as tests in {candidates}: `*_test.rb`, `*_spec.rb`
151
+ # and Minitest's `test_*.rb`, but not its `test_helper.rb` support file.
152
+ TEST_NAME = /\A(?!test_helper\.rb\z)(?:.+_(?:test|spec)|test_.+)\.rb\z/
153
+
154
+ # Whether the `run` guard rejects a positional argument as a test file
155
+ # given as a source (#293). Segments are read from the path relative to
156
+ # the root, so a `test/` folder above the project never counts. A
157
+ # `*_test.rb` / `*_spec.rb` path is flagged when it has a `test/` or
158
+ # `spec/` segment (the guard's original rule), or when it sits in the same
159
+ # directory as a given test, which catches root-level tests. A
160
+ # `test_*.rb` path is flagged only under the
161
+ # project's top-level `test/` or `spec/`, because real sources use the
162
+ # prefix (`lib/mutineer/test_runners.rb`, `lib/foo/test/test_app.rb`). A
163
+ # source whose own test (`<stem>_test.rb`, `test_<stem>.rb`,
164
+ # `<stem>_spec.rb`, or a split `<stem>_*_test.rb` not claimed by a longer
165
+ # source on disk) sits beside it in the test set is not flagged. Paths
166
+ # compare relative to the root, as in
167
+ # {expand_sources} (the root's real path counts too, for macOS `/var` vs
168
+ # `/private/var`), so `./a_test.rb` and `a_test.rb` match. No file content
169
+ # is read.
170
+ #
171
+ # @param path [String] positional argument as typed.
172
+ # @param project_root [String] repository root for relative expansion.
173
+ # @param tests [Array<String>] the explicit test files.
174
+ # @return [Boolean] true when the argument looks like a misplaced test.
175
+ def test_given_as_source?(path, project_root:, tests:)
176
+ name = File.basename(path)
177
+ return false unless name.match?(TEST_NAME)
178
+
179
+ root = File.expand_path(project_root)
180
+ roots = [root, ProjectPath.root_real(root)].uniq
181
+ relative = lambda do |p|
182
+ abs = File.expand_path(p, root)
183
+ root_relative(abs, roots) || abs
184
+ end
185
+ rel = relative.call(path)
186
+ # Outside the root, read segments from a relative path as typed
187
+ # (`../test/b_test.rb`) and not from an absolute one, so a `test/`
188
+ # folder above the root never counts.
189
+ segments = root_relative(File.expand_path(path, root), roots)
190
+ segments ||= path.start_with?(File::SEPARATOR) ? "" : path
191
+ return segments.match?(%r{\A(test|spec)/}) unless name.match?(/_(test|spec)\.rb\z/)
192
+ return true if segments.match?(%r{(\A|/)(test|spec)/})
193
+
194
+ dir = File.dirname(rel)
195
+ beside = Array(tests).map(&relative)
196
+ .select { |t| File.dirname(t) == dir }
197
+ .map { |t| File.basename(t) }
198
+ stem = File.basename(name, ".rb")
199
+ own = ["test_#{stem}.rb", "#{stem}_spec.rb"]
200
+ base = rel.delete_suffix(".rb")
201
+ return false if beside.any? do |t|
202
+ own.include?(t) || (longer_owns_entry?(t, stem) && !claimed_by_longer_source?(root, base, t))
203
+ end
204
+
205
+ beside.any?
206
+ end
207
+
147
208
  # `<basename>_*_test.rb` files in each mirrored test directory (#87). The
148
209
  # exact `<basename>_test.rb` name is not included. `lib/` sources also look
149
210
  # under `test/lib/`. Names are matched as text, not as a glob, so a basename
@@ -17,14 +17,151 @@ module Mutineer
17
17
  def self.discover(paths, only: nil)
18
18
  subjects = Array(paths).flat_map do |path|
19
19
  result = Parser.parse_file(path)
20
- visitor = SubjectVisitor.new(path)
20
+ visitor = SubjectVisitor.new(path, plain_path_calls(result.value))
21
21
  visitor.visit(result.value)
22
22
  visitor.promote_module_functions!
23
+ exclude_ambiguous_hook_defs!(visitor.subjects, result.value)
23
24
  visitor.subjects
24
25
  end
25
26
  only ? subjects.select { |s| s.qualified_name == only } : subjects
26
27
  end
27
28
 
29
+ # Keeps hook defs unplaceable when the raw AST can define another method
30
+ # with the same name and source line. Count all defs, including bodies that
31
+ # SubjectVisitor does not select, and Ruby's explicit method creators (see
32
+ # {.created_method_locations}). Indirect callable bodies may come from any
33
+ # block or lambda in this file, including a stored hook block. An unknown
34
+ # creator name blocks only those possible source lines. Instance and
35
+ # singleton definitions can both reach singleton-class hosts, so their
36
+ # shared name and line are ambiguous too. Run before `only` so selecting
37
+ # one subject cannot hide a collision.
38
+ #
39
+ # @param subjects [Array<Mutineer::Subject>] subjects from one source file.
40
+ # @param program [Prism::ProgramNode] the complete parsed file.
41
+ # @return [void]
42
+ def self.exclude_ambiguous_hook_defs!(subjects, program)
43
+ return unless subjects.any?(&:reload_hook_call)
44
+
45
+ counts = Hash.new(0)
46
+ body_lines = Set.new
47
+ indirect_names = Set.new
48
+ nodes = [program]
49
+ while (node = nodes.pop)
50
+ nodes.concat(node.compact_child_nodes)
51
+ if node.is_a?(Prism::LambdaNode) || node.is_a?(Prism::BlockNode)
52
+ body_lines << node.location.start_line
53
+ end
54
+ location = if node.is_a?(Prism::DefNode)
55
+ [node.location.start_line, [node.name]]
56
+ elsif node.is_a?(Prism::CallNode)
57
+ created_method_locations(node)
58
+ end
59
+ next unless location
60
+
61
+ line, names = location
62
+ if line
63
+ names.each { |name| counts[[line, name]] += 1 }
64
+ else
65
+ names.each { |name| indirect_names << name }
66
+ end
67
+ end
68
+ indirect_names.each { |name| body_lines.each { |line| counts[[line, name]] += 1 } }
69
+ subjects.each do |subject|
70
+ line = subject.def_node.location.start_line
71
+ subject.reload_hook_call = nil if counts[[line, subject.name]] > 1 || counts[[line, nil]].positive?
72
+ end
73
+ end
74
+ private_class_method :exclude_ambiguous_hook_defs!
75
+
76
+ # The source line and names Ruby assigns to explicit method creators. A
77
+ # block-defined method starts at the block opening and a literal lambda at
78
+ # its arrow. A positional callable takes precedence over a supplied block.
79
+ # Other positional bodies and block passes have an indirect source: the
80
+ # caller checks possible block/lambda lines instead of the call line.
81
+ # An attribute method starts at the message token, not its receiver line.
82
+ # Literal symbol and string names are exact. A dynamic name, splat, or attr
83
+ # writer flag may name any method on that line, represented by nil.
84
+ #
85
+ # @param node [Prism::CallNode] call to inspect.
86
+ # @return [Array, nil] source line and names (a nil line means an indirect
87
+ # callable body), or nil for a call outside this built-in family.
88
+ def self.created_method_locations(node)
89
+ arguments = node.arguments&.arguments || []
90
+ case node.name
91
+ when :define_method, :define_singleton_method
92
+ body = arguments[1] || node.block
93
+ body = body.expression if body.is_a?(Prism::BlockArgumentNode)
94
+ line = body.location.start_line if body.is_a?(Prism::BlockNode) || body.is_a?(Prism::LambdaNode)
95
+ return [line, [literal_method_name(arguments.first)]]
96
+ when :attr_reader, :attr_writer, :attr_accessor, :attr
97
+ mode = { attr_reader: :reader, attr_writer: :writer, attr_accessor: :both, attr: :reader }.fetch(node.name)
98
+ if node.name == :attr && arguments.size == 2 &&
99
+ (arguments.last.is_a?(Prism::TrueNode) || arguments.last.is_a?(Prism::FalseNode))
100
+ mode = arguments.last.is_a?(Prism::TrueNode) ? :both : :reader
101
+ arguments = arguments.first(1)
102
+ end
103
+ names = arguments.flat_map do |argument|
104
+ name = literal_method_name(argument)
105
+ next [nil] unless name
106
+
107
+ case mode
108
+ when :reader then [name]
109
+ when :writer then [:"#{name}="]
110
+ else [name, :"#{name}="]
111
+ end
112
+ end
113
+ [node.message_loc.start_line, names]
114
+ end
115
+ end
116
+ private_class_method :created_method_locations
117
+
118
+ # A literal method name, or nil when it depends on a runtime value.
119
+ #
120
+ # @param node [Prism::Node, nil] name argument.
121
+ # @return [Symbol, nil]
122
+ def self.literal_method_name(node)
123
+ node.unescaped.to_sym if node.is_a?(Prism::SymbolNode) || node.is_a?(Prism::StringNode)
124
+ end
125
+ private_class_method :literal_method_name
126
+
127
+ # The call statements that run each time the file loads, in order: each one
128
+ # sits in the file's top-level statements or in a `module` or `class` body
129
+ # reached only through such statements. A call under any `if`, `unless`,
130
+ # `case`, `&&`, `rescue` or loop, an assignment, an argument, a def or a block
131
+ # is not on this path. A file with a `return` outside any def or block may
132
+ # stop before a call, so it has none (#297).
133
+ #
134
+ # @param program [Prism::ProgramNode] the parsed file.
135
+ # @return [Set<Prism::CallNode>] compared by identity.
136
+ def self.plain_path_calls(program)
137
+ calls = Set.new.compare_by_identity
138
+ return calls if top_level_return?(program)
139
+
140
+ bodies = [program.statements]
141
+ while (body = bodies.pop)
142
+ body.body.each do |node|
143
+ calls << node if node.is_a?(Prism::CallNode)
144
+ bodies << node.body if (node.is_a?(Prism::ModuleNode) || node.is_a?(Prism::ClassNode)) &&
145
+ node.body.is_a?(Prism::StatementsNode)
146
+ end
147
+ end
148
+ calls
149
+ end
150
+ private_class_method :plain_path_calls
151
+
152
+ # True when the node holds a `return` that is not inside a def, a block or a
153
+ # lambda, so loading the file can stop early.
154
+ #
155
+ # @param node [Prism::Node] node to search.
156
+ # @return [Boolean]
157
+ def self.top_level_return?(node)
158
+ return true if node.is_a?(Prism::ReturnNode)
159
+ return false if node.is_a?(Prism::DefNode) || node.is_a?(Prism::BlockNode) || node.is_a?(Prism::LambdaNode)
160
+
161
+ node.compact_child_nodes.any? { |child| top_level_return?(child) }
162
+ end
163
+ private_class_method :top_level_return?
164
+
28
165
  # Walks an AST, maintaining a namespace stack, emitting Subjects.
29
166
  # Nested inside Project to signal its private role.
30
167
  class SubjectVisitor < Prism::Visitor
@@ -34,6 +171,9 @@ module Mutineer
34
171
  # ActiveSupport::Concern calls whose block another module or class runs (see {#concern_block}).
35
172
  CONCERN_BLOCKS = %i[class_methods included prepended].freeze
36
173
 
174
+ # Calls whose first argument names a method they reach (see {#placeable_hook}).
175
+ NAME_TAKERS = %i[method instance_method send public_send __send__].freeze
176
+
37
177
  # A constant name, or a path of them; Ruby allows Unicode in both.
38
178
  CONSTANT_PATH = /\A[[:upper:]][[:word:]]*(?:::[[:upper:]][[:word:]]*)*\z/
39
179
 
@@ -61,8 +201,11 @@ module Mutineer
61
201
  # Builds a subject visitor.
62
202
  #
63
203
  # @param file [String] source file path being visited.
64
- def initialize(file)
204
+ # @param plain_calls [Set<Prism::CallNode>] the file's call statements on a
205
+ # plain path (see {Project.plain_path_calls}); only these hook calls can be placed.
206
+ def initialize(file, plain_calls = Set.new)
65
207
  @file = file
208
+ @plain_calls = plain_calls
66
209
  @namespace_stack = []
67
210
  @lexical_stack = [] # class/module names as written, `::X` kept (#145)
68
211
  @subjects = []
@@ -83,6 +226,9 @@ module Mutineer
83
226
  @singleton_uncertain = false # the open `class << self` was inside a def or a non-builder block
84
227
  @concern_modules = Set.new # the {#module_key} of each module that ran `extend ActiveSupport::Concern`
85
228
  @hook_block = false # inside a concern's `included` or `prepended` block (see {#hook_block?})
229
+ @enclosing_subject = nil # the subject of the def being visited, for a nested def (#290)
230
+ @hook_call = nil # that block's call node, when reload can place its defs (see {#placeable_hook})
231
+ @hook_uses = Set.new # the names that block calls outside its def bodies
86
232
  super()
87
233
  end
88
234
 
@@ -96,7 +242,12 @@ module Mutineer
96
242
  return if @module_function_names.empty?
97
243
 
98
244
  named = @module_function_names.to_set
99
- @subjects.each_with_index { |s, i| s.singleton = true if named.include?([@subject_keys[i], s.name]) }
245
+ @subjects.each_with_index do |s, i|
246
+ next unless named.include?([@subject_keys[i], s.name])
247
+
248
+ s.singleton = true
249
+ s.reload_hook_call = nil # a module function has a second, private copy no hook def placement makes
250
+ end
100
251
  end
101
252
 
102
253
  # Visits class nodes and tracks namespace nesting.
@@ -156,6 +307,7 @@ module Mutineer
156
307
  hook = hook_block?(node)
157
308
  return preserving_scope do
158
309
  @hook_block ||= hook
310
+ @hook_call, @hook_uses = placeable_hook(node) if hook
159
311
  @owner_unknown = true
160
312
  super
161
313
  end
@@ -244,25 +396,29 @@ module Mutineer
244
396
  # @param node [Prism::DefNode] method definition node.
245
397
  # @return [void]
246
398
  def visit_def_node(node)
247
- @subjects << Subject.new(
399
+ subject = Subject.new(
248
400
  file: @file,
249
401
  namespace: (@block_namespace || @namespace_stack).dup,
250
402
  lexical: @lexical_stack.dup,
251
403
  block_owner: @block_owner,
252
404
  owner_unknown: @owner_unknown,
253
405
  hook_block: @hook_block,
406
+ reload_hook_call: reload_hook_call_for(node),
254
407
  name: node.name,
255
408
  singleton: !node.receiver.nil? || @singleton_depth.positive? || @module_function_active,
256
- def_node: node
409
+ def_node: node,
410
+ enclosing_subject: @enclosing_subject
257
411
  )
412
+ @subjects << subject
258
413
  @subject_keys << module_key
259
414
  saved_active = @module_function_active
260
- saved_self = [@self_unknown, @self_scope]
415
+ saved_self = [@self_unknown, @self_scope, @enclosing_subject]
261
416
  @self_unknown = true # `self` in a method body is the receiver it is called on
262
417
  @self_scope = node
418
+ @enclosing_subject = subject
263
419
  super
264
420
  @module_function_active = saved_active # a visibility call in a method body runs only when it is called
265
- @self_unknown, @self_scope = saved_self
421
+ @self_unknown, @self_scope, @enclosing_subject = saved_self
266
422
  end
267
423
 
268
424
  private
@@ -347,7 +503,7 @@ module Mutineer
347
503
  end
348
504
  @lexical_stack += [root ? "::#{name}" : name]
349
505
  @module_function_active = false
350
- @block_owner = @block_namespace = nil
506
+ @block_owner = @block_namespace = @hook_call = nil
351
507
  # redefine reopens the lexical chain, which does not pass through the block's `self`
352
508
  @namespace_unknown = @owner_unknown = in_singleton || self_in_block || @namespace_unknown
353
509
  @singleton_depth = 0
@@ -361,7 +517,7 @@ module Mutineer
361
517
  # Visitor state a class/module body or builder block sets for what it contains.
362
518
  SCOPE_STATE = %i[@namespace_stack @lexical_stack @module_function_active @block_owner @block_namespace
363
519
  @owner_unknown @singleton_depth @singleton_cref @namespace_unknown @anonymous_block @body
364
- @self_unknown @self_scope @singleton_uncertain @hook_block].freeze
520
+ @self_unknown @self_scope @singleton_uncertain @hook_block @hook_call @hook_uses].freeze
365
521
 
366
522
  # Runs the block and then restores every {SCOPE_STATE} variable, so a
367
523
  # nested body's state never leaks out of it.
@@ -512,9 +668,10 @@ module Mutineer
512
668
  # ActiveSupport::Concern` earlier in this file (see {#concern_block}).
513
669
  # ActiveSupport runs the block only when a class first includes or prepends
514
670
  # the concern, so loading the file again stores the new block but never
515
- # runs it in a class that already has the concern (#256). Everything the
516
- # block defines, a nested class or a builder block included, stays as the
517
- # original wrote it under `--strategy reload`.
671
+ # runs it in a class that already has the concern (#256). Reload can still
672
+ # place a def that sits directly in such a block (see {#placeable_hook} and
673
+ # {#reload_hook_call_for}, #297); anything else the block defines, a nested
674
+ # class or a builder block included, stays as the original wrote it.
518
675
  #
519
676
  # @param node [Prism::CallNode] a call {#concern_block} sorted as a concern block.
520
677
  # @return [Boolean]
@@ -522,6 +679,63 @@ module Mutineer
522
679
  %i[included prepended].include?(node.name) && @concern_modules.include?(module_key)
523
680
  end
524
681
 
682
+ # The hook call and the names its block calls outside its def bodies, when
683
+ # reload can place the block's defs (#297), else nil and an empty set. The
684
+ # call must be on a plain path ({Project.plain_path_calls}), so the statement
685
+ # the mutant file adds before it runs exactly when the call runs. An alias in
686
+ # the block keeps the original body, so a block with any `alias` or
687
+ # `alias_method` places nothing. The names come from bare calls, `self.`
688
+ # calls and the name given to `method`, `instance_method`, `send`,
689
+ # `public_send` or `__send__`, in every nested block and lambda too:
690
+ # discovery cannot tell a block that runs at once from one that runs
691
+ # later. A name such a call computes at runtime places nothing.
692
+ #
693
+ # @param node [Prism::CallNode] the hook call.
694
+ # @return [Array(Prism::CallNode, Set<Symbol>), Array(nil, Set<Symbol>)]
695
+ def placeable_hook(node)
696
+ uses = Set.new
697
+ return [nil, uses] unless @plain_calls.include?(node)
698
+
699
+ nodes = [node.block.body].compact
700
+ while (current = nodes.pop)
701
+ next if current.is_a?(Prism::DefNode)
702
+ return [nil, Set.new] if current.is_a?(Prism::AliasMethodNode)
703
+
704
+ if current.is_a?(Prism::CallNode) && (current.receiver.nil? || current.receiver.is_a?(Prism::SelfNode))
705
+ return [nil, Set.new] if current.name == :alias_method
706
+
707
+ uses << current.name
708
+ if NAME_TAKERS.include?(current.name) && (name_arg = current.arguments&.arguments&.first)
709
+ # A runtime name may be any def in the block.
710
+ return [nil, Set.new] unless name_arg.is_a?(Prism::SymbolNode) || name_arg.is_a?(Prism::StringNode)
711
+
712
+ uses << name_arg.unescaped.to_sym
713
+ end
714
+ end
715
+ nodes.concat(current.compact_child_nodes)
716
+ end
717
+ [node, uses]
718
+ end
719
+
720
+ # The hook call of the block this def sits in directly, when reload can
721
+ # place the def (#297), else nil. The def must be in the block itself, or
722
+ # in its `class << self` without a receiver: not in a def, a nested class
723
+ # or module, or another block. `module_function` gives the def a second,
724
+ # private copy, and a name the block itself uses kept the original's
725
+ # result, so neither is placeable. An explicit receiver other than `self`
726
+ # can be bound only inside the hook, which the extracted def does not keep.
727
+ #
728
+ # @param node [Prism::DefNode] the def being recorded.
729
+ # @return [Prism::CallNode, nil]
730
+ def reload_hook_call_for(node)
731
+ return unless @hook_call && @self_scope.equal?(@hook_call.block) && !@module_function_active
732
+ return if node.receiver && !node.receiver.is_a?(Prism::SelfNode)
733
+ return if @singleton_depth > 1 || (@singleton_depth == 1 && node.receiver)
734
+ return if @hook_uses.include?(node.name)
735
+
736
+ @hook_call
737
+ end
738
+
525
739
  # Runs the block with the defs it visits owned by the class a builder block builds.
526
740
  # The block body defines instance methods of that class, even inside `class << self`,
527
741
  # but its constants still land where the enclosing `class << self` puts them.