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 +4 -4
- data/README.md +5 -3
- data/lib/testament/probe/version.rb +1 -1
- data/lib/testament/probe.rb +59 -85
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1ab715c89e0d43c163220f9f1633de46ad9e9e254d2fbff375ff8b54372de558
|
|
4
|
+
data.tar.gz: e915052f16dee7902514be50a7288b5073a89ade62e43a5b8231b031d58a92f9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
25
|
-
`
|
|
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.
|
data/lib/testament/probe.rb
CHANGED
|
@@ -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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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(
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
@tracepoint ||= TracePoint.new(
|
|
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
|
|
106
|
-
|
|
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
|
|
110
|
-
|
|
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
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def mutex
|
|
122
|
+
@mutex ||= Mutex.new
|
|
125
123
|
end
|
|
126
124
|
|
|
127
125
|
def record_trace_line(path, line)
|
|
128
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
157
|
+
Thread.current[THREAD_STATE_KEY]&.fetch(:assertion_depth, 0) || 0
|
|
156
158
|
end
|
|
157
159
|
|
|
158
160
|
def mark_recent_lines_checked
|
|
159
|
-
|
|
161
|
+
recording = Thread.current[THREAD_STATE_KEY]
|
|
162
|
+
return unless recording
|
|
160
163
|
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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: []
|