padded_cell 0.3.0 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d2b2f1c6d06b6cf4e72a67cfb6d511cc121fad2794d5fffc84bbf323c8431f1
4
- data.tar.gz: '08aacbd7f382259c1eee0b45e3508c7dba4522be0c6d57ba4ca13b496ba70a1b'
3
+ metadata.gz: 8c460bc9c30d1aceca5e6e4dd764503b7871971c2da3c8aaa7e497581717f92f
4
+ data.tar.gz: f72d0d43ff6ba9fe4505bc2c1293fefc073c89c905b0ae8fa4f8fb27784d597d
5
5
  SHA512:
6
- metadata.gz: d473da59b1f2de1ae4cee4d423c8361ffcf42060bd8b4fd264894a82c6d631cdddd8e8ee376716ae0d73db4ea4e3aefbf540fdbd42b20ed996ca024740448aba
7
- data.tar.gz: 4d7a2de1bad5aaf24df760011df2a9728193586640bb7f7f14a9dcd66d38750082995692736bdfc0a4ccb951fb174384d600ac723414a39e0c479d98203f2515
6
+ metadata.gz: c72c3a035714f8c70cb2a93461cde0c447e2417f954af98e23dc689e3b375bda3bd1132527fe0110b2e964ac33c45999e975146d214f7e71230e96e2eb383818
7
+ data.tar.gz: fb6a39de58a92cf0abb8fc3bedfb8f8d72fa3fa14179508e27d416c259e77c73fda818daee6d06ea7f83f95b72432776c1a63a8ba0d692cbb82df73454e28371
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 (2026-11-08)
4
+
5
+ - `PADDED_CELL_ASYLUM`: retries failures from the environment. N retries,
6
+ or `true`/`yes`/`on` for the default 2; `0`, `false`, `no` and `off` leave
7
+ it off. `--asylum` still wins.
8
+ - `--coverage-map PATH` (or `PADDED_CELL_COVERAGE_MAP`): the lines each test
9
+ ran, as JSON lines (`{"id":…,"file":…,"line":…,"covers":{path => lines}}`),
10
+ for CI that reruns only the tests a diff touches. Turns on
11
+ `--coverage=lines,methods` when coverage is off; never changes the summed
12
+ report. Free with `--isolation test`; with `--isolation worker`, two
13
+ coverage copies per test.
14
+
3
15
  ## 0.3.0 (2026-11-08)
4
16
 
5
17
  - `--budget SECONDS`: a deadline for the whole run, loading included. Once
data/Cargo.lock CHANGED
@@ -226,7 +226,7 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
226
226
 
227
227
  [[package]]
228
228
  name = "padded_cell"
229
- version = "0.3.0"
229
+ version = "0.4.0"
230
230
  dependencies = [
231
231
  "ruby-prism",
232
232
  "supervisor-machines",
@@ -236,7 +236,7 @@ dependencies = [
236
236
 
237
237
  [[package]]
238
238
  name = "padded_cell_native"
239
- version = "0.3.0"
239
+ version = "0.4.0"
240
240
  dependencies = [
241
241
  "deadline-machines",
242
242
  "magnus",
data/README.md CHANGED
@@ -72,6 +72,7 @@ $ padded_cell --timeout 30 # kill and fail anything running 30s
72
72
  $ padded_cell --budget 600 # the whole run gets 10 minutes; what's left out fails it
73
73
  $ padded_cell --fail-fast
74
74
  $ padded_cell --asylum # retry failures; report the flaky ones (see below)
75
+ $ PADDED_CELL_ASYLUM=2 padded_cell # same, from a CI runner's env
75
76
  $ padded_cell --list # what would run, without loading anything
76
77
  $ padded_cell --shard 2/4 # CI: the second quarter of the classes
77
78
  $ padded_cell --junit tmp/junit.xml --json tmp/results.jsonl
@@ -271,6 +272,38 @@ The merge refuses files that differ between shards or from the checkout,
271
272
  and checks thresholds when every shard of the run is there. Lines that run
272
273
  while the suite loads count once per shard.
273
274
 
275
+ ### Coverage map
276
+
277
+ Which lines each test ran, so CI can rerun only the tests touching a diff:
278
+
279
+ ```console
280
+ $ padded_cell --coverage-map tmp/coverage_map.jsonl
281
+ $ PADDED_CELL_COVERAGE_MAP=tmp/coverage_map.jsonl padded_cell # same; the flag wins
282
+ ```
283
+
284
+ One JSON object per test, in id order, written at the end of the run:
285
+
286
+ ```json
287
+ {"id":"SignTest#test_zero","file":"test/calc_test.rb","line":28,"covers":{"lib/calc.rb":[[7,8],10,13]}}
288
+ ```
289
+
290
+ `covers` holds the files the coverage report shows (filters apply, `test/`
291
+ stays out), each with its line numbers ascending, `[first, last]` for a run
292
+ of consecutive ones. Lines a test runs while loading a file count for it.
293
+
294
+ Ruby's line coverage never sees a call to `def add(a, b) = a + b`: with
295
+ `methods` measured, every method a test called adds its def line. Without
296
+ `--coverage`, the map turns on `lines,methods`. With it, the map adds no
297
+ criterion, so the report is byte for byte the same as without the map:
298
+ include `methods`, or one-liners go unmapped.
299
+
300
+ First attempts only, like the report. A test whose cell or worker died is
301
+ left out: it was not measured, so a selector should run it anyway.
302
+
303
+ A cell's measure is already its test's: free under `--isolation test`. With
304
+ `--isolation worker`, Ruby copies the coverage of every loaded file twice per
305
+ test (about 24 ms with 800 files loaded, more in a big Rails app).
306
+
274
307
  ### Cost
275
308
 
276
309
  Measuring is opt-in: Ruby's instrumentation slows tests down. On a Rails
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "padded_cell"
3
- version = "0.3.0"
3
+ version = "0.4.0"
4
4
  description = "Static Minitest discovery and test scheduling for the padded_cell gem"
5
5
  publish = false
6
6
  edition.workspace = true
@@ -51,15 +51,24 @@ impl Source {
51
51
  /// The 1-based line and the 0-based byte column of `offset`.
52
52
  #[must_use]
53
53
  pub fn position(&self, offset: usize) -> (u32, u32) {
54
- let line = self.ends.partition_point(|&end| end <= offset);
54
+ let line = self.line_index(offset);
55
55
  let number = |value: usize| u32::try_from(value).unwrap_or(u32::MAX);
56
- (number(line.strict_add(1)), number(offset.strict_sub(self.line_start(offset))))
56
+ (number(line.strict_add(1)), number(offset.strict_sub(self.start_of(line))))
57
57
  }
58
58
 
59
59
  /// Where the line holding `offset` starts.
60
60
  #[must_use]
61
61
  pub fn line_start(&self, offset: usize) -> usize {
62
- let line = self.ends.partition_point(|&end| end <= offset);
62
+ self.start_of(self.line_index(offset))
63
+ }
64
+
65
+ /// The 0-based line holding `offset`.
66
+ fn line_index(&self, offset: usize) -> usize {
67
+ self.ends.partition_point(|&end| end <= offset)
68
+ }
69
+
70
+ /// Where the 0-based `line` starts.
71
+ fn start_of(&self, line: usize) -> usize {
63
72
  line.checked_sub(1).map_or(0, |previous| self.ends[previous])
64
73
  }
65
74
 
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "padded_cell_native"
3
- version = "0.3.0"
3
+ version = "0.4.0"
4
4
  description = "Ruby bindings for the padded_cell crate"
5
5
  publish = false
6
6
  edition.workspace = true
@@ -24,7 +24,7 @@ module PaddedCell
24
24
  pid = fork_cell(klass, name, seed, reader, writer, close, probe)
25
25
  writer.close
26
26
  delta, result = collect(pid, reader, klass, name, started, deadline) { limits.message(deadline) }
27
- delta ? probe.absorb(delta) : probe&.lost
27
+ delta ? probe.absorb(delta, result) : probe&.lost
28
28
  result
29
29
  ensure
30
30
  reader&.close
@@ -13,11 +13,14 @@ module PaddedCell
13
13
  attr_reader :criteria
14
14
 
15
15
  # criteria: what Ruby's Coverage measures, :lines always among them
16
- def initialize(criteria)
16
+ # map: each test's lines go on its result too (--coverage-map)
17
+ def initialize(criteria, map: false)
17
18
  @criteria = criteria
19
+ @map = map
18
20
  @root = File.realpath(Dir.pwd)
19
21
  @roots = [Dir.pwd, @root].uniq.map { "#{it}/" }
20
22
  @kept = {}
23
+ @names = {}
21
24
  @lost = 0
22
25
  @known = Set.new
23
26
  end
@@ -39,7 +42,16 @@ module PaddedCell
39
42
  # For a worker, right after its fork.
40
43
  def probe
41
44
  Probe.new(file: File.join(@frames, "#{Process.pid}.cov"), known: @known, keep: method(:keep?),
42
- methods: @criteria.include?(:methods), interval: Chart.config.interval)
45
+ methods: @criteria.include?(:methods), interval: Chart.config.interval, map: @map)
46
+ end
47
+
48
+ # A measured file's name in the report, or nil for a file the report
49
+ # leaves out (filtered, outside the root, deleted).
50
+ def project_file(path)
51
+ @names.fetch(path) do
52
+ name = relative(path)
53
+ @names[path] = (name if name && !Chart.config.skipped?(path, name))
54
+ end
43
55
  end
44
56
 
45
57
  # A worker died: what it measured since its last flush is gone.
@@ -85,19 +97,25 @@ module PaddedCell
85
97
  end
86
98
  end
87
99
 
88
- # Keys by real path relative to the root: a file required through a
89
- # symlink and through its real path is one file. A file deleted during
90
- # the run is not reported.
100
+ # Keys by real path relative to the root.
91
101
  def normalize(total)
92
102
  total.each_with_object({}) do |(path, entry), files|
93
- relative = File.realpath(path).delete_prefix("#{@root}/")
94
- next if relative.start_with?('/')
103
+ relative = relative(path)
104
+ next unless relative
95
105
 
96
106
  files[relative] ? Delta.merge!(files, { relative => entry }) : files[relative] = entry
97
- rescue SystemCallError
98
- next
99
107
  end
100
108
  end
109
+
110
+ # By real path: a file required through a symlink and through its real
111
+ # path is one file. nil outside the root, or for a file deleted during
112
+ # the run.
113
+ def relative(path)
114
+ relative = File.realpath(path).delete_prefix("#{@root}/")
115
+ relative unless relative.start_with?('/')
116
+ rescue SystemCallError
117
+ nil
118
+ end
101
119
  end
102
120
  end
103
121
  end
@@ -65,6 +65,12 @@ module PaddedCell
65
65
  end
66
66
  alias filter skip
67
67
 
68
+ # Whether a filter leaves the file out (path absolute, name relative).
69
+ def skipped?(path, name)
70
+ file = SourceFile.new(path, name)
71
+ @filters.any? { it.call(file) }
72
+ end
73
+
68
74
  # Removes every filter, the defaults included.
69
75
  def clear_filters = @filters.clear
70
76
 
@@ -51,6 +51,19 @@ module PaddedCell
51
51
  total
52
52
  end
53
53
 
54
+ # {path => [line number, ...]}: the lines that ran, ascending, by file.
55
+ # A call to `def add(a, b) = a + b` runs no line Ruby sees: methods,
56
+ # when measured, add the first line of every method that ran.
57
+ def ran(delta)
58
+ delta.each_with_object({}) do |(path, entry), ran|
59
+ lines = entry[:lines]
60
+ indices = lines.is_a?(Hash) ? lines.keys : lines.each_index.select { lines[it]&.positive? }
61
+ numbers = indices.map { it + 1 }
62
+ entry[:methods]&.each { |(_, first), (_, hits)| numbers << first if hits.positive? }
63
+ ran[path] = numbers.uniq.sort unless numbers.empty?
64
+ end
65
+ end
66
+
54
67
  # Coverage's arrays are frozen.
55
68
  def dense(data)
56
69
  {
@@ -16,15 +16,22 @@ module PaddedCell
16
16
  # A flush copies the coverage of every file Ruby loaded, gems included: a
17
17
  # few milliseconds in a small app, a few hundred in a big Rails one. The
18
18
  # interval stretches to keep flushes under 1% of a worker's time.
19
+ #
20
+ # With the map on (--coverage-map), each test's result also carries the
21
+ # lines it ran, in its metadata under :covers. A cell's delta is already
22
+ # its test's; a worker running tests itself clears Coverage around each
23
+ # one, two more copies per test. What it clears goes into its measure, so
24
+ # the sum does not change.
19
25
  class Probe
20
26
  INTERVAL = 10.0
21
27
  BUDGET = 100
22
28
 
23
- def initialize(file:, known:, keep:, methods:, interval: INTERVAL)
29
+ def initialize(file:, known:, keep:, methods:, interval: INTERVAL, map: false)
24
30
  @file = file
25
31
  @known = known
26
32
  @keep = keep
27
33
  @methods = methods
34
+ @map = map
28
35
  @interval = interval
29
36
  @wait = interval
30
37
  @pending = {}
@@ -40,8 +47,22 @@ module PaddedCell
40
47
  # The delta of a cell, at its end.
41
48
  def capture = Delta.capture(::Coverage.peek_result, @known, @keep)
42
49
 
43
- # A cell's delta, in its worker.
44
- def absorb(delta) = Delta.merge!(@pending, delta)
50
+ # A cell's delta, in its worker; with the map on, result gets the lines
51
+ # its test ran.
52
+ def absorb(delta, result = nil)
53
+ result.metadata[:covers] = Delta.ran(delta) if @map && result
54
+ Delta.merge!(@pending, delta)
55
+ end
56
+
57
+ # Runs a test in the worker (isolation :worker), which yields its result.
58
+ def measure
59
+ return yield unless @map
60
+
61
+ absorb(clear)
62
+ result = yield
63
+ absorb(clear, result)
64
+ result
65
+ end
45
66
 
46
67
  # A cell that died before reporting.
47
68
  def lost = @lost += 1
@@ -53,7 +74,7 @@ module PaddedCell
53
74
 
54
75
  def flush
55
76
  started = Minitest.clock_time
56
- delta = Delta.merge!(Delta.capture(::Coverage.result(stop: false, clear: true), @known, @keep), @pending)
77
+ delta = Delta.merge!(clear, @pending)
57
78
  frame = Marshal.dump({ delta:, lost: @lost })
58
79
  File.open(@file, 'ab') { it.write([frame.bytesize].pack('N') + frame) }
59
80
  # Dense entries brought their structure to the runner: from now on,
@@ -65,6 +86,11 @@ module PaddedCell
65
86
  # 0: after every test, whatever it costs.
66
87
  @wait = @interval.zero? ? 0 : [@interval, (@flushed - started) * BUDGET].max
67
88
  end
89
+
90
+ private
91
+
92
+ # What ran since the last clear, taken out of Coverage.
93
+ def clear = Delta.capture(::Coverage.result(stop: false, clear: true), @known, @keep)
68
94
  end
69
95
  end
70
96
  end
@@ -19,7 +19,7 @@ module PaddedCell
19
19
  loaded = loaded_features
20
20
  never, @unmeasured = tracked(files).partition { !loaded.include?(File.join(root, it)) }
21
21
  @rows = (files.map { |name, entry| row(name, entry) } + stubs(never))
22
- .reject { |path, name| skipped?(path, name) }
22
+ .reject { |path, name| @config.skipped?(path, name) }
23
23
  .sort_by { it[1] }
24
24
  end
25
25
 
@@ -72,11 +72,6 @@ module PaddedCell
72
72
  end
73
73
  end
74
74
 
75
- def skipped?(path, name)
76
- file = SourceFile.new(path, name)
77
- @config.filters.any? { it.call(file) }
78
- end
79
-
80
75
  # Required files, also under the root's real path.
81
76
  def loaded_features
82
77
  pwd = "#{Dir.pwd}/"
@@ -22,7 +22,8 @@ module PaddedCell
22
22
  # Exit status: 0 passed, 1 failed, 2 usage or load error, 3 coverage below
23
23
  # a minimum, 130 interrupted.
24
24
  def start(argv, io: $stdout)
25
- options = { io:, coverage: coverage_criteria(ENV.fetch('PADDED_CELL_COVERAGE', nil)), **report_paths }
25
+ options = { io:, coverage: coverage_criteria(ENV.fetch('PADDED_CELL_COVERAGE', nil)), **report_paths,
26
+ **asylum_from_env }
26
27
  parser(options).parse!(argv)
27
28
  return merge(options) if options[:coverage_merge]
28
29
 
@@ -34,12 +35,22 @@ module PaddedCell
34
35
  end
35
36
 
36
37
  # Report paths a CI runner can set without touching the command line;
37
- # --json and --junit still win.
38
+ # --json, --junit and --coverage-map still win.
38
39
  def report_paths
39
- { json: ENV.fetch('PADDED_CELL_JSON', nil), junit: ENV.fetch('PADDED_CELL_JUNIT', nil) }
40
+ { json: ENV.fetch('PADDED_CELL_JSON', nil), junit: ENV.fetch('PADDED_CELL_JUNIT', nil),
41
+ coverage_map: ENV.fetch('PADDED_CELL_COVERAGE_MAP', nil) }
40
42
  .reject { |_, path| path.nil? || path.empty? }
41
43
  end
42
44
 
45
+ # PADDED_CELL_ASYLUM=N, or 1/true for the default 2 retries; --asylum wins.
46
+ def asylum_from_env
47
+ value = ENV.fetch('PADDED_CELL_ASYLUM', '').strip
48
+ return {} if value.empty? || %w[0 false no off].include?(value.downcase)
49
+ return { asylum: 2 } if %w[true yes on].include?(value.downcase)
50
+
51
+ { asylum: positive(Integer(value, 10)) }
52
+ end
53
+
43
54
  def parser(options)
44
55
  OptionParser.new do |opts|
45
56
  opts.banner = BANNER
@@ -98,6 +109,10 @@ module PaddedCell
98
109
  opts.on('--coverage-dir DIR', 'Write coverage reports to DIR (default: coverage, or the test helper\'s)') do
99
110
  options[:coverage_dir] = it
100
111
  end
112
+ opts.on('--coverage-map PATH', 'Write the lines each test ran to PATH, as JSON lines (measures ' \
113
+ 'lines,methods if coverage is off; or $PADDED_CELL_COVERAGE_MAP)') do
114
+ options[:coverage_map] = it
115
+ end
101
116
  opts.on('--coverage-merge DIR,DIR', Array, 'Merge the coverage of shards (their --coverage-dir) ' \
102
117
  'into one report; runs no test') do
103
118
  options[:coverage_merge] = it
@@ -103,6 +103,52 @@ module PaddedCell
103
103
  def escape(text) = text.to_s.scrub.gsub(INVALID, '').gsub(/[&<>"']/, ESCAPES)
104
104
  end
105
105
 
106
+ # --coverage-map: per test, the lines of the project it ran, one JSON
107
+ # object per line, in id order:
108
+ #
109
+ # {"id":"SignTest#test_zero","file":"test/calc_test.rb","line":28,
110
+ # "covers":{"lib/calc.rb":[[7,8],10,13]}}
111
+ #
112
+ # Line numbers ascend; [first, last] stands for a run of consecutive
113
+ # ones. Files are those the coverage report would show. A test that was
114
+ # not measured (its cell or worker died) is left out.
115
+ class CoverageMap < FileReporter
116
+ # coverage: the run's Chart::Accumulator
117
+ def initialize(path, coverage:)
118
+ super(path)
119
+ @coverage = coverage
120
+ end
121
+
122
+ # A first attempt, as it arrives: what it ran comes off its metadata.
123
+ def record(result)
124
+ covers = result.metadata? && result.metadata.delete(:covers)
125
+ @results << entry(result, covers) if covers
126
+ end
127
+
128
+ private
129
+
130
+ def render(entries) = entries.sort_by { it[:id] }.map { "#{JSON.generate(it)}\n" }.join
131
+
132
+ def entry(result, covers)
133
+ file, line = file_and_line(result)
134
+ { id: "#{result.klass}##{result.name}", file:, line:, covers: files(covers) }
135
+ end
136
+
137
+ # Two paths can name one file (a symlink): their lines are merged.
138
+ def files(covers)
139
+ files = Hash.new { |hash, name| hash[name] = [] }
140
+ covers.each do |path, lines|
141
+ name = @coverage.project_file(path)
142
+ files[name].concat(lines) if name
143
+ end
144
+ files.sort.to_h { |name, lines| [name, ranges(lines.uniq.sort)] }
145
+ end
146
+
147
+ def ranges(lines)
148
+ lines.slice_when { |previous, line| line != previous + 1 }.map { it.size == 1 ? it[0] : [it[0], it[-1]] }
149
+ end
150
+ end
151
+
106
152
  # One JSON object per test, one per line.
107
153
  class JSONLines < FileReporter
108
154
  private
@@ -88,12 +88,13 @@ module PaddedCell
88
88
  end
89
89
 
90
90
  # Marshal.dump, after replacing failures that cannot cross a pipe (an
91
- # exception holding a Proc, an IO...) with plain copies.
91
+ # exception holding a Proc, an IO...) with plain copies. Of the metadata,
92
+ # only the coverage map's plain data survives.
92
93
  def dump(result)
93
94
  Marshal.dump(result)
94
95
  rescue TypeError, ArgumentError
95
96
  result.failures = result.failures.map { portable(it) }
96
- result.metadata = {} if result.metadata?
97
+ result.metadata = result.metadata.slice(:covers) if result.metadata?
97
98
  Marshal.dump(result)
98
99
  end
99
100
 
@@ -23,7 +23,7 @@ module PaddedCell
23
23
  paths: [], jobs: Etc.nprocessors, seed: nil, include: nil, exclude: nil, shard: [0, 1],
24
24
  isolation: nil, timeout: nil, budget: nil, asylum: nil, fail_fast: false, verbose: false, show_skips: false,
25
25
  list: false, junit: nil, json: nil, timings: 'tmp/padded_cell/timings.tsv', coverage: nil,
26
- coverage_dir: nil, io: $stdout
26
+ coverage_dir: nil, coverage_map: nil, io: $stdout
27
27
  }.freeze
28
28
 
29
29
  attr_reader :options
@@ -31,6 +31,9 @@ module PaddedCell
31
31
  def initialize(**options)
32
32
  @options = DEFAULTS.merge(options)
33
33
  @options[:seed] ||= (ENV['SEED'] || Random.new_seed).to_i % 0xFFFF
34
+ # The map is made of lines, and of methods: a call to a one-line
35
+ # method runs no line Ruby sees.
36
+ @options[:coverage] ||= %i[lines methods] if @options[:coverage_map]
34
37
  end
35
38
 
36
39
  # Exit status: 0 when everything passed, 1 otherwise, 3 when only
@@ -62,7 +65,7 @@ module PaddedCell
62
65
  # Before the suite loads: nothing loaded earlier is measured.
63
66
  def start_coverage
64
67
  require_relative 'chart'
65
- Chart::Accumulator.new(options[:coverage]).start
68
+ Chart::Accumulator.new(options[:coverage], map: !options[:coverage_map].nil?).start
66
69
  end
67
70
 
68
71
  # Two tools cannot share Ruby's Coverage.
@@ -97,10 +100,16 @@ module PaddedCell
97
100
  nothing_ran = batches.empty?
98
101
  reporter = build_reporter
99
102
  reporter.start
103
+ # First attempts only, as with the coverage it is made of.
104
+ map = Reporters::CoverageMap.new(options[:coverage_map], coverage: @coverage) if options[:coverage_map]
100
105
  retried = []
101
106
  @coverage&.snapshot
102
- status = dispatch(tests, batches, reporter, retried) { |index, result| timings[ids[index]] = result.time }
107
+ status = dispatch(tests, batches, reporter, retried) do |index, result|
108
+ timings[ids[index]] = result.time
109
+ map&.record(result)
110
+ end
103
111
  reporter.report
112
+ map&.report
104
113
  report_asylum(retried)
105
114
  covered = @coverage ? report_coverage(status, reporter) : true
106
115
  Timings.save(options[:timings], timings)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PaddedCell
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.0'
5
5
  end
@@ -55,6 +55,8 @@ module PaddedCell
55
55
  Results.crashed(klass, name, "after_fork hook failed: #{setup_error.class}: #{setup_error.message}")
56
56
  elsif @isolation == :test
57
57
  Cell.run(klass, name, seed: @seed, limits: @limits, close: [socket], probe: @probe)
58
+ elsif @probe
59
+ @probe.measure { Cell.execute(klass, name) }
58
60
  else
59
61
  Cell.execute(klass, name)
60
62
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: padded_cell
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih