testament-probe-ruby 0.1.0 → 0.1.1

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: 337fe8994c7ab64268d451d7b9f91e671251036eade4ed01905f42a10dd366f8
4
- data.tar.gz: 5faf2fb1f40a1df3cbed28907b6871940ec05d498a284d955fa1b06fc2f78c18
3
+ metadata.gz: 1ab715c89e0d43c163220f9f1633de46ad9e9e254d2fbff375ff8b54372de558
4
+ data.tar.gz: e915052f16dee7902514be50a7288b5073a89ade62e43a5b8231b031d58a92f9
5
5
  SHA512:
6
- metadata.gz: 55f1e5ff0529789880754d15acd90be7ee2a58d4104983800d5c592175f74de4a97597e3e49f1009dcc0c6f2114ada4ca4c1687ff00e1730ab841cdacdd6763c
7
- data.tar.gz: 4c6d77433a6e09c18b446672af24937812a71c7a49c0cdd31213d2a89b544c5ca9634b576806d74027a0107db3328dc5b6b68d705442d35fc8b0102d2b3d05d9
6
+ metadata.gz: 8bcd3d5d9076e7b8fcc933792b78330fce86e4eda94b7e4cf0eefc5df0fcea37d1981c214201b17e05c1947f692329bef0d652c126e24918d46969186f8738df
7
+ data.tar.gz: db97ce291dfdf7342216d1977d7c0bc941ebeeb28c378f7a316c81c9a7fcf6591211ea39d4615ffb4a3735795eb4875b459d0971817484e24a89326b8dfa1244
data/README.md CHANGED
@@ -19,10 +19,12 @@ The trace output records executed project lines, lines executed while assertion
19
19
  methods are active, and recent executed lines observed when assertions begin.
20
20
  `TESTAMENT_PROJECT_ROOT` controls the project-root filter, and
21
21
  `TESTAMENT_TRACE_WINDOW` controls how many recent lines are attributed to an
22
- assertion.
22
+ assertion. The trace JSON records this window and the temporal approximation
23
+ method alongside the cases.
23
24
 
24
- Line/call tracing adds noticeable overhead to the test run. Set
25
- `TESTAMENT_TRACE=0` to collect per-test line coverage without trace evidence.
25
+ Line tracing collects per-test coverage without changing Ruby's process-wide
26
+ `Coverage` counters. Call tracing adds further overhead for assertion evidence;
27
+ set `TESTAMENT_TRACE=0` to disable that evidence while retaining line coverage.
26
28
 
27
29
  The probe installs hooks for RSpec and Minitest when those constants are loaded.
28
30
  Load the probe after the test framework is required.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Testament
4
4
  module Probe
5
- VERSION = "0.1.0"
5
+ VERSION = "0.1.1"
6
6
  end
7
7
  end
@@ -1,4 +1,3 @@
1
- require "coverage"
2
1
  require "fileutils"
3
2
  require "json"
4
3
  require_relative "probe/version"
@@ -8,6 +7,7 @@ module Testament
8
7
  DEFAULT_OUTPUT = ".testament/per-test-coverage.json"
9
8
  DEFAULT_TRACE_OUTPUT = ".testament/trace.json"
10
9
  DEFAULT_TRACE_WINDOW = 200
10
+ THREAD_STATE_KEY = :testament_probe_recording
11
11
  ASSERTION_METHODS = %i[
12
12
  expect should should_not to not_to to_not
13
13
  assert assert_equal assert_same assert_nil assert_not_nil assert_empty
@@ -32,29 +32,27 @@ module Testament
32
32
  @probe_file = File.expand_path(__FILE__)
33
33
  @trace_window = ENV.fetch("TESTAMENT_TRACE_WINDOW", DEFAULT_TRACE_WINDOW).to_i
34
34
  @trace_enabled = ENV.fetch("TESTAMENT_TRACE", "1") != "0"
35
- start_coverage
36
- capture_coverage
37
35
  install_rspec if defined?(RSpec)
38
36
  install_minitest if defined?(Minitest::Test)
39
37
  at_exit { write! }
40
38
  end
41
39
 
42
40
  def record(case_id)
43
- start_coverage
44
41
  start_trace
45
- previous_case = @current_case
46
- previous_recent_lines = @recent_lines
47
- previous_assertion_depth = @assertion_depth
48
- @recent_lines = []
49
- @assertion_depth = 0
50
- @current_case = case_id
51
- trace_cases[case_id]
42
+ previous = Thread.current[THREAD_STATE_KEY]
43
+ recording = {
44
+ case_id: case_id,
45
+ coverage: {},
46
+ executed: {},
47
+ checked: {},
48
+ recent_lines: [],
49
+ assertion_depth: 0
50
+ }
51
+ Thread.current[THREAD_STATE_KEY] = recording
52
52
  yield
53
53
  ensure
54
- merge_case(case_id, capture_coverage) if case_id
55
- @recent_lines = previous_recent_lines || []
56
- @assertion_depth = previous_assertion_depth || 0
57
- @current_case = previous_case
54
+ merge_recording(recording) if case_id && recording
55
+ Thread.current[THREAD_STATE_KEY] = previous
58
56
  end
59
57
 
60
58
  def write!
@@ -64,7 +62,11 @@ module Testament
64
62
 
65
63
  trace_output = @trace_output || DEFAULT_TRACE_OUTPUT
66
64
  FileUtils.mkdir_p(File.dirname(trace_output))
67
- File.write(trace_output, JSON.pretty_generate("cases" => trace_cases))
65
+ File.write(trace_output, JSON.pretty_generate(
66
+ "method" => "temporal-recent-line-window",
67
+ "window" => trace_window,
68
+ "cases" => trace_cases
69
+ ))
68
70
  end
69
71
 
70
72
  private
@@ -79,17 +81,10 @@ module Testament
79
81
  end
80
82
  end
81
83
 
82
- def start_coverage
83
- return false if coverage_running?
84
-
85
- Coverage.start(lines: true)
86
- true
87
- end
88
-
89
84
  def start_trace
90
- return unless @trace_enabled
91
-
92
- @tracepoint ||= TracePoint.new(:line, :call, :c_call, :return, :c_return) do |event|
85
+ events = [:line]
86
+ events.concat([:call, :c_call, :return, :c_return]) if @trace_enabled
87
+ @tracepoint ||= TracePoint.new(*events) do |event|
93
88
  case event.event
94
89
  when :line
95
90
  record_trace_line(event.path, event.lineno)
@@ -102,49 +97,56 @@ module Testament
102
97
  @tracepoint.enable unless @tracepoint.enabled?
103
98
  end
104
99
 
105
- def coverage_running?
106
- Coverage.respond_to?(:running?) && Coverage.running?
100
+ def merge_recording(recording)
101
+ mutex.synchronize do
102
+ merge_lines(cases, recording[:case_id], recording[:coverage])
103
+ if @trace_enabled
104
+ trace = trace_cases[recording[:case_id]]
105
+ merge_files(trace["executed"], recording[:executed])
106
+ merge_files(trace["checked"], recording[:checked])
107
+ end
108
+ end
107
109
  end
108
110
 
109
- def capture_coverage
110
- Coverage.result(stop: false, clear: true)
111
- rescue ArgumentError
112
- Coverage.result
111
+ def merge_lines(collection, case_id, files)
112
+ merge_files(collection[case_id] ||= {}, files)
113
113
  end
114
114
 
115
- def merge_case(case_id, coverage)
116
- normalized = normalize_coverage(coverage)
117
- return if normalized.empty?
118
-
119
- existing = cases.fetch(case_id, {})
120
- normalized.each do |path, lines|
121
- merged = (existing.fetch(path, []) + lines).uniq.sort
122
- existing[path] = merged
115
+ def merge_files(existing, additions)
116
+ additions.each do |path, lines|
117
+ existing[path] = (existing.fetch(path, []) + lines).uniq.sort
123
118
  end
124
- cases[case_id] = existing
119
+ end
120
+
121
+ def mutex
122
+ @mutex ||= Mutex.new
125
123
  end
126
124
 
127
125
  def record_trace_line(path, line)
128
- return unless @current_case
126
+ recording = Thread.current[THREAD_STATE_KEY]
127
+ return unless recording
129
128
 
130
129
  path = normalize_trace_path(path)
131
130
  return unless path
132
131
 
133
- trace = trace_cases[@current_case]
134
- append_trace_line(trace["executed"], path, line)
135
- append_trace_line(trace["checked"], path, line) if assertion_active?
136
- @recent_lines ||= []
137
- @recent_lines << [path, line]
138
- @recent_lines.shift while @recent_lines.length > trace_window
132
+ append_trace_line(recording[:coverage], path, line)
133
+ return unless @trace_enabled
134
+
135
+ append_trace_line(recording[:executed], path, line)
136
+ append_trace_line(recording[:checked], path, line) if assertion_active?
137
+ recording[:recent_lines] << [path, line]
138
+ recording[:recent_lines].shift while recording[:recent_lines].length > trace_window
139
139
  end
140
140
 
141
141
  def enter_assertion
142
142
  mark_recent_lines_checked
143
- @assertion_depth = assertion_depth + 1
143
+ recording = Thread.current[THREAD_STATE_KEY]
144
+ recording[:assertion_depth] = assertion_depth + 1 if recording
144
145
  end
145
146
 
146
147
  def leave_assertion
147
- @assertion_depth = [assertion_depth - 1, 0].max
148
+ recording = Thread.current[THREAD_STATE_KEY]
149
+ recording[:assertion_depth] = [assertion_depth - 1, 0].max if recording
148
150
  end
149
151
 
150
152
  def assertion_active?
@@ -152,15 +154,15 @@ module Testament
152
154
  end
153
155
 
154
156
  def assertion_depth
155
- @assertion_depth ||= 0
157
+ Thread.current[THREAD_STATE_KEY]&.fetch(:assertion_depth, 0) || 0
156
158
  end
157
159
 
158
160
  def mark_recent_lines_checked
159
- return unless @current_case
161
+ recording = Thread.current[THREAD_STATE_KEY]
162
+ return unless recording
160
163
 
161
- checked = trace_cases[@current_case]["checked"]
162
- @recent_lines.each do |path, line|
163
- append_trace_line(checked, path, line)
164
+ recording[:recent_lines].each do |path, line|
165
+ append_trace_line(recording[:checked], path, line)
164
166
  end
165
167
  end
166
168
 
@@ -193,39 +195,11 @@ module Testament
193
195
  [@trace_window || DEFAULT_TRACE_WINDOW, 1].max
194
196
  end
195
197
 
196
- def normalize_coverage(coverage)
197
- coverage.each_with_object({}) do |(path, value), result|
198
- lines = line_hits(value)
199
- covered = lines.each_with_index.filter_map do |hits, index|
200
- index + 1 if hits && hits.positive?
201
- end
202
- result[path] = covered unless covered.empty?
203
- end
204
- end
205
-
206
- def line_hits(value)
207
- if value.is_a?(Hash)
208
- unless value.key?(:lines) || value.key?("lines")
209
- warn_missing_line_coverage
210
- return []
211
- end
212
- value.fetch(:lines, value.fetch("lines", []))
213
- else
214
- value || []
215
- end
216
- end
217
-
218
- def warn_missing_line_coverage
219
- return if @warned_missing_line_coverage
220
-
221
- warn "testament probe: Coverage is active without lines mode; per-test coverage will be empty"
222
- @warned_missing_line_coverage = true
223
- end
224
-
225
198
  def install_rspec
226
199
  RSpec.configure do |config|
227
200
  config.around(:each) do |example|
228
- Testament::Probe.record(example.full_description) { example.run }
201
+ location = example.metadata[:location].to_s.sub(%r{\A\./}, "")
202
+ Testament::Probe.record("#{location}::#{example.full_description}") { example.run }
229
203
  end
230
204
  end
231
205
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: testament-probe-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yudai Takada
@@ -44,7 +44,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
44
44
  - !ruby/object:Gem::Version
45
45
  version: '0'
46
46
  requirements: []
47
- rubygems_version: 4.0.6
47
+ rubygems_version: 4.0.19
48
48
  specification_version: 4
49
49
  summary: Per-test Ruby coverage and trace probe for testament
50
50
  test_files: []