polyrun 2.1.3 → 2.2.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 +4 -4
- data/CHANGELOG.md +10 -1
- data/CONTRIBUTING.md +22 -0
- data/docs/SETUP_PROFILE.md +1 -1
- data/ext/polyrun_coverage_merge/coverage_merge.c +492 -0
- data/ext/polyrun_coverage_merge/extconf.rb +3 -0
- data/lib/polyrun/benchmark/profile.rb +191 -0
- data/lib/polyrun/benchmark/report.rb +87 -0
- data/lib/polyrun/cli/benchmark_commands.rb +66 -0
- data/lib/polyrun/cli/coverage_commands.rb +4 -4
- data/lib/polyrun/cli/coverage_merge_io.rb +30 -4
- data/lib/polyrun/cli/failure_commands.rb +10 -3
- data/lib/polyrun/cli/help.rb +12 -8
- data/lib/polyrun/cli/report_commands.rb +25 -11
- data/lib/polyrun/cli/run_shards_parallel_children.rb +1 -1
- data/lib/polyrun/cli/run_shards_plan_options.rb +1 -1
- data/lib/polyrun/cli/spec_quality_commands.rb +16 -7
- data/lib/polyrun/cli.rb +8 -1
- data/lib/polyrun/coverage/example_diff.rb +127 -60
- data/lib/polyrun/coverage/example_diff_snapshot.rb +44 -0
- data/lib/polyrun/coverage/example_diff_track_filter.rb +51 -0
- data/lib/polyrun/coverage/file_stats_report.rb +36 -0
- data/lib/polyrun/coverage/formatter.rb +22 -1
- data/lib/polyrun/coverage/merge/formatters.rb +11 -3
- data/lib/polyrun/coverage/merge_merge_two.rb +112 -63
- data/lib/polyrun/coverage/merge_native.rb +37 -0
- data/lib/polyrun/coverage/reporting.rb +1 -1
- data/lib/polyrun/export/csv.rb +25 -0
- data/lib/polyrun/export/markdown.rb +47 -0
- data/lib/polyrun/hooks/shell_runner.rb +33 -0
- data/lib/polyrun/hooks.rb +4 -7
- data/lib/polyrun/reporting/failure_merge.rb +48 -12
- data/lib/polyrun/reporting/junit.rb +8 -7
- data/lib/polyrun/reporting/junit_report.rb +87 -0
- data/lib/polyrun/rspec/example_debug.rb +1 -1
- data/lib/polyrun/rspec/sharded_formatter_compat.rb +30 -7
- data/lib/polyrun/spec_quality/profile.rb +24 -12
- data/lib/polyrun/spec_quality/report.rb +93 -0
- data/lib/polyrun/spec_quality.rb +19 -11
- data/lib/polyrun/timing/summary.rb +55 -5
- data/lib/polyrun/version.rb +1 -1
- data/polyrun.gemspec +8 -1
- metadata +58 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8b7bedc5bda9c9ee341927dab2ba6aedafaebe4c26ad73750d8ed7ad4845f4e3
|
|
4
|
+
data.tar.gz: bac9cd71c5ab3372c0ff5dda891dd354a0ceaac3555f5949870ce8611646debc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fe09eaccf1eaf3fba5a998d49f2f2df9cfd6a1da6b1eef960e6cbe894f2269b05777be459fe394a67c3038d72750a8401ae8e224726901ac4bd437e3d01a93ed
|
|
7
|
+
data.tar.gz: f8090b77fba080b7ca1996afeec0c2680ca26e0d0a4571453210f421f4921685bd8c5bcfeb79724556e377ff26968a914b52bf24b4a1a433a9500d656aa43f41
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 2.2.0 (2026-07-13)
|
|
4
|
+
|
|
5
|
+
- Fix `install_example_rails_logging!` raising `NameError` when example-debug Rails logging runs under `POLYRUN_EXAMPLE_DEBUG=1`.
|
|
6
|
+
- Improve spec-quality per-example coverage diff: sparse scoped snapshots, track-aware diff without a second full blob copy, and optional lazy profile dimensions.
|
|
7
|
+
- Add CSV and Markdown exports for coverage, timing, spec-quality, and failure merge reports.
|
|
8
|
+
- Add `report-junit --format xml|csv|markdown` (XML remains default for CI).
|
|
9
|
+
- Add `Polyrun::Benchmark::Profile` / `report-benchmark` / `polyrun bench`; benchmark runs write JSON sidecars under `tmp/benchmarks/` with optional `POLYRUN_BENCH_FORMATS`.
|
|
10
|
+
- Add optional native coverage merge acceleration (`ext/polyrun_coverage_merge`: `merge_line_arrays`, `merge_two`, `line_counts`) with Ruby fallbacks.
|
|
11
|
+
- Add RSpec performance benchmarks (`rake bench_performance`, `spec/performance/`) with profile logs under `tmp/benchmarks/`.
|
|
12
|
+
- Include `csv` and `markdown` in default post-merge coverage formats (`POLYRUN_MERGE_FORMATS`).
|
|
4
13
|
|
|
5
14
|
## 2.1.3 (2026-07-10)
|
|
6
15
|
|
data/CONTRIBUTING.md
CHANGED
|
@@ -17,11 +17,33 @@ bundle exec rake spec
|
|
|
17
17
|
|
|
18
18
|
Coverage merge performance (large synthetic payloads):
|
|
19
19
|
|
|
20
|
+
Native extension (optional; compiles via `bundle install` or `cd ext/polyrun_coverage_merge && ruby extconf.rb && make`):
|
|
21
|
+
|
|
22
|
+
After pulling changes under `ext/**/*.c` or `ext/**/extconf.rb`, rebuild the extension before running specs or benchmarks:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
make native-extension
|
|
26
|
+
# or: bundle exec rake native_extension
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
If merge specs segfault or native acceleration behaves oddly after a pull, run `make clean` in `ext/polyrun_coverage_merge` and `make native-extension` again (or `bundle install` to trigger a full compile).
|
|
30
|
+
|
|
31
|
+
CI compiles the extension on Linux and macOS (`.github/workflows/native-extension.yml`).
|
|
32
|
+
|
|
20
33
|
```bash
|
|
21
34
|
bundle exec rake bench_merge
|
|
22
35
|
# or: ruby benchmark/merge_coverage.rb
|
|
23
36
|
```
|
|
24
37
|
|
|
38
|
+
RSpec performance benchmarks (coverage merge + spec-quality peek; writes `tmp/benchmarks/profile_<sha>.log`):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
bundle exec rake bench_performance
|
|
42
|
+
# optional: STACKPROF=1 or BENCHMARK_IPS=1 for spec/performance/profiling_spec.rb
|
|
43
|
+
# optional: BENCH_MEMORY=1 bundle exec rspec spec/performance/benchmark_merge_spec.rb spec/performance/benchmark_spec.rb --tag benchmark
|
|
44
|
+
# optional env: BENCH_LINE_COUNT_REPS, BENCH_MEMORY_REPS
|
|
45
|
+
```
|
|
46
|
+
|
|
25
47
|
Run the suite under alternate Ruby constraints (see `Appraisals` and `gemfiles/`):
|
|
26
48
|
|
|
27
49
|
```bash
|
data/docs/SETUP_PROFILE.md
CHANGED
|
@@ -76,7 +76,7 @@ Orchestration tracing stays on `DEBUG=1` / `POLYRUN_DEBUG=1`. Per-example toolin
|
|
|
76
76
|
| `DEBUG_LOG_LEVEL` | Rails / ActiveRecord logger severity: `debug`, `info`, `warn`, `error`, `fatal`, or Ruby Logger integers (`0` debug … `4` fatal); default `debug` |
|
|
77
77
|
| `RSPEC_EXAMPLE_TIMEOUT_SEC` | Per-example timeout (disabled while example debug is on) |
|
|
78
78
|
|
|
79
|
-
In `spec_helper` / `spec/support`: `require "polyrun/rspec"` then `Polyrun::RSpec.
|
|
79
|
+
In `spec_helper` / `spec/support`: `require "polyrun/rspec"` then `Polyrun::RSpec.install_sharded_formatter_compat!` (silences per-worker seed/summary/pending under `POLYRUN_SHARD_*`), `install_example_debug!`, `install_example_rails_logging!`, `install_example_timeout!`. Pair with `install_worker_ping!` for idle timeouts.
|
|
80
80
|
|
|
81
81
|
## 6. Coverage and CI reports
|
|
82
82
|
|
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
#include <ruby.h>
|
|
2
|
+
#include <string.h>
|
|
3
|
+
|
|
4
|
+
static VALUE str_lines;
|
|
5
|
+
static VALUE str_branches;
|
|
6
|
+
static VALUE str_type;
|
|
7
|
+
static VALUE str_start_line;
|
|
8
|
+
static VALUE str_end_line;
|
|
9
|
+
static VALUE str_coverage;
|
|
10
|
+
static ID id_lines;
|
|
11
|
+
static ID id_branches;
|
|
12
|
+
static ID id_type;
|
|
13
|
+
static ID id_start_line;
|
|
14
|
+
static ID id_end_line;
|
|
15
|
+
static ID id_coverage;
|
|
16
|
+
static ID id_relevant;
|
|
17
|
+
static ID id_covered;
|
|
18
|
+
static ID id_sort_branches_for_native;
|
|
19
|
+
|
|
20
|
+
static VALUE
|
|
21
|
+
ignored_string(void)
|
|
22
|
+
{
|
|
23
|
+
return rb_str_new_cstr("ignored");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static int
|
|
27
|
+
ignored_hit_p(VALUE value)
|
|
28
|
+
{
|
|
29
|
+
if (!RB_TYPE_P(value, T_STRING)) {
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return strcmp(StringValueCStr(value), "ignored") == 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static VALUE
|
|
37
|
+
line_hit_to_i(VALUE value)
|
|
38
|
+
{
|
|
39
|
+
if (NIL_P(value)) {
|
|
40
|
+
return Qnil;
|
|
41
|
+
}
|
|
42
|
+
if (RB_TYPE_P(value, T_FIXNUM) || RB_TYPE_P(value, T_BIGNUM)) {
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
if (RB_TYPE_P(value, T_STRING)) {
|
|
46
|
+
const char *text = StringValueCStr(value);
|
|
47
|
+
char *end = NULL;
|
|
48
|
+
long number = strtol(text, &end, 10);
|
|
49
|
+
if (end != text && *end == '\0') {
|
|
50
|
+
return LONG2FIX(number);
|
|
51
|
+
}
|
|
52
|
+
return Qnil;
|
|
53
|
+
}
|
|
54
|
+
if (RB_TYPE_P(value, T_SYMBOL)) {
|
|
55
|
+
VALUE as_string = rb_sym2str(value);
|
|
56
|
+
return line_hit_to_i(as_string);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return Qnil;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static int
|
|
63
|
+
line_hit_positive_p(VALUE value)
|
|
64
|
+
{
|
|
65
|
+
VALUE as_int = line_hit_to_i(value);
|
|
66
|
+
if (NIL_P(as_int)) {
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
VALUE comparison = rb_funcall(as_int, rb_intern(">"), 1, LONG2FIX(0));
|
|
71
|
+
return RTEST(comparison);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
static VALUE
|
|
75
|
+
coerce_line_array(VALUE value)
|
|
76
|
+
{
|
|
77
|
+
if (NIL_P(value)) {
|
|
78
|
+
return rb_ary_new();
|
|
79
|
+
}
|
|
80
|
+
if (!RB_TYPE_P(value, T_ARRAY)) {
|
|
81
|
+
rb_raise(rb_eTypeError, "lines must be an Array");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static VALUE
|
|
88
|
+
line_array_for_count(VALUE value)
|
|
89
|
+
{
|
|
90
|
+
if (NIL_P(value) || !RB_TYPE_P(value, T_ARRAY)) {
|
|
91
|
+
return rb_ary_new();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static VALUE
|
|
98
|
+
merge_line_hits(VALUE left, VALUE right)
|
|
99
|
+
{
|
|
100
|
+
if (NIL_P(left)) {
|
|
101
|
+
return right;
|
|
102
|
+
}
|
|
103
|
+
if (NIL_P(right)) {
|
|
104
|
+
return left;
|
|
105
|
+
}
|
|
106
|
+
if (ignored_hit_p(left) || ignored_hit_p(right)) {
|
|
107
|
+
return ignored_string();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
VALUE left_i = line_hit_to_i(left);
|
|
111
|
+
VALUE right_i = line_hit_to_i(right);
|
|
112
|
+
|
|
113
|
+
if (!NIL_P(left_i) && !NIL_P(right_i)) {
|
|
114
|
+
return rb_funcall(left_i, rb_intern("+"), 1, right_i);
|
|
115
|
+
}
|
|
116
|
+
if (NIL_P(left_i) && !NIL_P(right_i)) {
|
|
117
|
+
return right_i;
|
|
118
|
+
}
|
|
119
|
+
if (!NIL_P(left_i) && NIL_P(right_i)) {
|
|
120
|
+
return left_i;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return left;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
static VALUE
|
|
127
|
+
merge_line_arrays(VALUE left, VALUE right)
|
|
128
|
+
{
|
|
129
|
+
left = coerce_line_array(left);
|
|
130
|
+
right = coerce_line_array(right);
|
|
131
|
+
|
|
132
|
+
long left_len = RARRAY_LEN(left);
|
|
133
|
+
long right_len = RARRAY_LEN(right);
|
|
134
|
+
long max_len = left_len > right_len ? left_len : right_len;
|
|
135
|
+
VALUE out = rb_ary_new2(max_len);
|
|
136
|
+
|
|
137
|
+
for (long index = 0; index < max_len; index++) {
|
|
138
|
+
VALUE left_hit = index < left_len ? rb_ary_entry(left, index) : Qnil;
|
|
139
|
+
VALUE right_hit = index < right_len ? rb_ary_entry(right, index) : Qnil;
|
|
140
|
+
rb_ary_store(out, index, merge_line_hits(left_hit, right_hit));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
static VALUE
|
|
147
|
+
hash_field(VALUE hash, VALUE string_key, ID symbol_key)
|
|
148
|
+
{
|
|
149
|
+
VALUE value = rb_hash_aref(hash, string_key);
|
|
150
|
+
if (NIL_P(value)) {
|
|
151
|
+
value = rb_hash_aref(hash, ID2SYM(symbol_key));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
static VALUE
|
|
158
|
+
branch_key(VALUE branch)
|
|
159
|
+
{
|
|
160
|
+
if (!RB_TYPE_P(branch, T_HASH)) {
|
|
161
|
+
branch = rb_hash_new();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return rb_ary_new3(
|
|
165
|
+
3,
|
|
166
|
+
hash_field(branch, str_type, id_type),
|
|
167
|
+
hash_field(branch, str_start_line, id_start_line),
|
|
168
|
+
hash_field(branch, str_end_line, id_end_line)
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
static VALUE
|
|
173
|
+
coverage_count(VALUE branch)
|
|
174
|
+
{
|
|
175
|
+
VALUE coverage = hash_field(branch, str_coverage, id_coverage);
|
|
176
|
+
VALUE as_int = line_hit_to_i(coverage);
|
|
177
|
+
if (NIL_P(as_int)) {
|
|
178
|
+
return LONG2FIX(0);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return as_int;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
static VALUE
|
|
185
|
+
merge_branch_entries(VALUE left, VALUE right)
|
|
186
|
+
{
|
|
187
|
+
VALUE out = rb_hash_dup(left);
|
|
188
|
+
VALUE sum = rb_funcall(coverage_count(left), rb_intern("+"), 1, coverage_count(right));
|
|
189
|
+
rb_hash_aset(out, str_coverage, sum);
|
|
190
|
+
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
static VALUE
|
|
195
|
+
merge_module_value(void)
|
|
196
|
+
{
|
|
197
|
+
VALUE polyrun = rb_const_get(rb_cObject, rb_intern("Polyrun"));
|
|
198
|
+
VALUE coverage = rb_const_get(polyrun, rb_intern("Coverage"));
|
|
199
|
+
return rb_const_get(coverage, rb_intern("Merge"));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
static VALUE
|
|
203
|
+
hash_keys(VALUE hash)
|
|
204
|
+
{
|
|
205
|
+
return rb_funcall(hash, rb_intern("keys"), 0);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
static VALUE
|
|
209
|
+
sort_branches(VALUE branches)
|
|
210
|
+
{
|
|
211
|
+
if (!RB_TYPE_P(branches, T_ARRAY)) {
|
|
212
|
+
return rb_ary_new();
|
|
213
|
+
}
|
|
214
|
+
if (RARRAY_LEN(branches) < 2) {
|
|
215
|
+
return branches;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return rb_funcall(merge_module_value(), id_sort_branches_for_native, 1, branches);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
static VALUE
|
|
222
|
+
duplicate_branch_side(VALUE value)
|
|
223
|
+
{
|
|
224
|
+
if (NIL_P(value)) {
|
|
225
|
+
return Qnil;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return rb_obj_dup(value);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
static VALUE
|
|
232
|
+
coerce_branch_array(VALUE value)
|
|
233
|
+
{
|
|
234
|
+
if (NIL_P(value)) {
|
|
235
|
+
return Qnil;
|
|
236
|
+
}
|
|
237
|
+
if (!RB_TYPE_P(value, T_ARRAY)) {
|
|
238
|
+
rb_raise(rb_eTypeError, "branches must be an Array");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return value;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
static VALUE
|
|
245
|
+
merge_branch_arrays(VALUE left, VALUE right)
|
|
246
|
+
{
|
|
247
|
+
if (NIL_P(left) && NIL_P(right)) {
|
|
248
|
+
return Qnil;
|
|
249
|
+
}
|
|
250
|
+
if (NIL_P(left)) {
|
|
251
|
+
return duplicate_branch_side(right);
|
|
252
|
+
}
|
|
253
|
+
if (NIL_P(right)) {
|
|
254
|
+
return duplicate_branch_side(left);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
left = coerce_branch_array(left);
|
|
258
|
+
right = coerce_branch_array(right);
|
|
259
|
+
|
|
260
|
+
VALUE index = rb_hash_new();
|
|
261
|
+
VALUE sides[2] = {left, right};
|
|
262
|
+
|
|
263
|
+
for (int side_index = 0; side_index < 2; side_index++) {
|
|
264
|
+
VALUE array = sides[side_index];
|
|
265
|
+
long length = RARRAY_LEN(array);
|
|
266
|
+
|
|
267
|
+
for (long branch_index = 0; branch_index < length; branch_index++) {
|
|
268
|
+
VALUE branch = rb_ary_entry(array, branch_index);
|
|
269
|
+
if (!RB_TYPE_P(branch, T_HASH)) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
VALUE key = branch_key(branch);
|
|
274
|
+
VALUE existing = rb_hash_aref(index, key);
|
|
275
|
+
VALUE merged = NIL_P(existing) ? rb_hash_dup(branch) : merge_branch_entries(existing, branch);
|
|
276
|
+
rb_hash_aset(index, key, merged);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
VALUE values = rb_ary_new();
|
|
281
|
+
VALUE branch_keys = hash_keys(index);
|
|
282
|
+
long branch_count = RARRAY_LEN(branch_keys);
|
|
283
|
+
for (long branch_index = 0; branch_index < branch_count; branch_index++) {
|
|
284
|
+
VALUE key = rb_ary_entry(branch_keys, branch_index);
|
|
285
|
+
rb_ary_push(values, rb_hash_aref(index, key));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return sort_branches(values);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
static VALUE
|
|
292
|
+
normalize_file_entry(VALUE value)
|
|
293
|
+
{
|
|
294
|
+
if (NIL_P(value)) {
|
|
295
|
+
return Qnil;
|
|
296
|
+
}
|
|
297
|
+
if (RB_TYPE_P(value, T_ARRAY)) {
|
|
298
|
+
VALUE entry = rb_hash_new();
|
|
299
|
+
rb_hash_aset(entry, str_lines, value);
|
|
300
|
+
return entry;
|
|
301
|
+
}
|
|
302
|
+
if (RB_TYPE_P(value, T_HASH)) {
|
|
303
|
+
return value;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return Qnil;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
static VALUE
|
|
310
|
+
entry_lines(VALUE entry)
|
|
311
|
+
{
|
|
312
|
+
VALUE lines = rb_hash_aref(entry, str_lines);
|
|
313
|
+
if (NIL_P(lines)) {
|
|
314
|
+
lines = rb_hash_aref(entry, ID2SYM(id_lines));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return coerce_line_array(lines);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
static VALUE
|
|
321
|
+
entry_lines_for_count(VALUE entry)
|
|
322
|
+
{
|
|
323
|
+
VALUE lines = rb_hash_aref(entry, str_lines);
|
|
324
|
+
if (NIL_P(lines)) {
|
|
325
|
+
lines = rb_hash_aref(entry, ID2SYM(id_lines));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return line_array_for_count(lines);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
static VALUE
|
|
332
|
+
entry_branches(VALUE entry)
|
|
333
|
+
{
|
|
334
|
+
VALUE branches = rb_hash_aref(entry, str_branches);
|
|
335
|
+
if (NIL_P(branches)) {
|
|
336
|
+
branches = rb_hash_aref(entry, ID2SYM(id_branches));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return branches;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
static VALUE
|
|
343
|
+
merge_file_entry(VALUE left, VALUE right)
|
|
344
|
+
{
|
|
345
|
+
left = normalize_file_entry(left);
|
|
346
|
+
right = normalize_file_entry(right);
|
|
347
|
+
if (NIL_P(left)) {
|
|
348
|
+
return right;
|
|
349
|
+
}
|
|
350
|
+
if (NIL_P(right)) {
|
|
351
|
+
return left;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
VALUE merged_lines = merge_line_arrays(entry_lines(left), entry_lines(right));
|
|
355
|
+
VALUE entry = rb_hash_new();
|
|
356
|
+
rb_hash_aset(entry, str_lines, merged_lines);
|
|
357
|
+
|
|
358
|
+
VALUE left_branches = entry_branches(left);
|
|
359
|
+
VALUE right_branches = entry_branches(right);
|
|
360
|
+
if (!NIL_P(left_branches) || !NIL_P(right_branches)) {
|
|
361
|
+
VALUE branches = merge_branch_arrays(left_branches, right_branches);
|
|
362
|
+
if (!NIL_P(branches)) {
|
|
363
|
+
rb_hash_aset(entry, str_branches, branches);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return entry;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
static VALUE
|
|
371
|
+
ensure_hash(VALUE value, const char *name)
|
|
372
|
+
{
|
|
373
|
+
if (NIL_P(value)) {
|
|
374
|
+
return rb_hash_new();
|
|
375
|
+
}
|
|
376
|
+
if (!RB_TYPE_P(value, T_HASH)) {
|
|
377
|
+
rb_raise(rb_eTypeError, "%s must be a Hash", name);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return value;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
static VALUE
|
|
384
|
+
merge_two(VALUE module, VALUE left, VALUE right)
|
|
385
|
+
{
|
|
386
|
+
(void)module;
|
|
387
|
+
|
|
388
|
+
left = ensure_hash(left, "left");
|
|
389
|
+
right = ensure_hash(right, "right");
|
|
390
|
+
|
|
391
|
+
VALUE primary = left;
|
|
392
|
+
VALUE secondary = right;
|
|
393
|
+
if (RHASH_SIZE(right) > RHASH_SIZE(left)) {
|
|
394
|
+
primary = right;
|
|
395
|
+
secondary = left;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
VALUE out = rb_hash_new();
|
|
399
|
+
VALUE primary_keys = hash_keys(primary);
|
|
400
|
+
long primary_length = RARRAY_LEN(primary_keys);
|
|
401
|
+
|
|
402
|
+
for (long index = 0; index < primary_length; index++) {
|
|
403
|
+
VALUE key = rb_ary_entry(primary_keys, index);
|
|
404
|
+
VALUE value = rb_hash_aref(primary, key);
|
|
405
|
+
VALUE right_entry = rb_hash_aref(secondary, key);
|
|
406
|
+
rb_hash_aset(out, key, merge_file_entry(value, right_entry));
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
VALUE secondary_keys = hash_keys(secondary);
|
|
410
|
+
long secondary_length = RARRAY_LEN(secondary_keys);
|
|
411
|
+
|
|
412
|
+
for (long index = 0; index < secondary_length; index++) {
|
|
413
|
+
VALUE key = rb_ary_entry(secondary_keys, index);
|
|
414
|
+
if (NIL_P(rb_hash_aref(out, key))) {
|
|
415
|
+
rb_hash_aset(out, key, rb_hash_aref(secondary, key));
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return out;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
static VALUE
|
|
423
|
+
line_counts(VALUE module, VALUE file_entry)
|
|
424
|
+
{
|
|
425
|
+
(void)module;
|
|
426
|
+
|
|
427
|
+
file_entry = normalize_file_entry(file_entry);
|
|
428
|
+
long relevant = 0;
|
|
429
|
+
long covered = 0;
|
|
430
|
+
|
|
431
|
+
if (!NIL_P(file_entry)) {
|
|
432
|
+
VALUE lines = entry_lines_for_count(file_entry);
|
|
433
|
+
long length = RARRAY_LEN(lines);
|
|
434
|
+
|
|
435
|
+
for (long index = 0; index < length; index++) {
|
|
436
|
+
VALUE hit = rb_ary_entry(lines, index);
|
|
437
|
+
if (NIL_P(hit) || ignored_hit_p(hit)) {
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
relevant += 1;
|
|
442
|
+
if (line_hit_positive_p(hit)) {
|
|
443
|
+
covered += 1;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
VALUE counts = rb_hash_new();
|
|
449
|
+
rb_hash_aset(counts, ID2SYM(id_relevant), LONG2FIX(relevant));
|
|
450
|
+
rb_hash_aset(counts, ID2SYM(id_covered), LONG2FIX(covered));
|
|
451
|
+
|
|
452
|
+
return counts;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
static VALUE
|
|
456
|
+
merge_line_arrays_method(VALUE module, VALUE left, VALUE right)
|
|
457
|
+
{
|
|
458
|
+
(void)module;
|
|
459
|
+
|
|
460
|
+
return merge_line_arrays(left, right);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
void
|
|
464
|
+
Init_polyrun_coverage_merge(void)
|
|
465
|
+
{
|
|
466
|
+
id_lines = rb_intern("lines");
|
|
467
|
+
id_branches = rb_intern("branches");
|
|
468
|
+
id_type = rb_intern("type");
|
|
469
|
+
id_start_line = rb_intern("start_line");
|
|
470
|
+
id_end_line = rb_intern("end_line");
|
|
471
|
+
id_coverage = rb_intern("coverage");
|
|
472
|
+
id_relevant = rb_intern("relevant");
|
|
473
|
+
id_covered = rb_intern("covered");
|
|
474
|
+
id_sort_branches_for_native = rb_intern("sort_branches_for_native");
|
|
475
|
+
str_lines = rb_obj_freeze(rb_str_new_cstr("lines"));
|
|
476
|
+
str_branches = rb_obj_freeze(rb_str_new_cstr("branches"));
|
|
477
|
+
str_type = rb_obj_freeze(rb_str_new_cstr("type"));
|
|
478
|
+
str_start_line = rb_obj_freeze(rb_str_new_cstr("start_line"));
|
|
479
|
+
str_end_line = rb_obj_freeze(rb_str_new_cstr("end_line"));
|
|
480
|
+
str_coverage = rb_obj_freeze(rb_str_new_cstr("coverage"));
|
|
481
|
+
rb_global_variable(&str_lines);
|
|
482
|
+
rb_global_variable(&str_branches);
|
|
483
|
+
rb_global_variable(&str_type);
|
|
484
|
+
rb_global_variable(&str_start_line);
|
|
485
|
+
rb_global_variable(&str_end_line);
|
|
486
|
+
rb_global_variable(&str_coverage);
|
|
487
|
+
|
|
488
|
+
VALUE module = rb_define_module("PolyrunCoverageMerge");
|
|
489
|
+
rb_define_singleton_method(module, "merge_line_arrays", merge_line_arrays_method, 2);
|
|
490
|
+
rb_define_singleton_method(module, "merge_two", merge_two, 2);
|
|
491
|
+
rb_define_singleton_method(module, "line_counts", line_counts, 1);
|
|
492
|
+
}
|