specguard-ruby 0.3.21 → 0.3.22
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 +24 -1
- data/lib/specguard/rspec/ingest_cli.rb +71 -1
- data/lib/specguard/rspec/ingest_reporter.rb +24 -2
- data/lib/specguard/rspec/scanner.rb +21 -7
- data/lib/specguard/version.rb +1 -1
- 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: bd0ffc9c5541eedd7981fab7f1c213c1cc2196c57465933a7d2ebcf02caf3d57
|
|
4
|
+
data.tar.gz: 0cc713e7625792a198fec8988f9f7f54e15fa364793cefc91dc029e3f5ba8e21
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 616d5a1bf1568db6f3b94227273f69df69a31a6a4f6ea9af762300fc063f29ec57b5a50e95f396e1c4af221ea6917cf06c78755d916b20618c379f9630697ddb
|
|
7
|
+
data.tar.gz: 8c3c74d0a97ab9728ff1d02f42fb8d0bf089c4f023051139fd7f405746918c9448923e976ef1865a6b52cfaba825e53e94a562f5e76e738415fef9c176ecfaab
|
data/README.md
CHANGED
|
@@ -704,6 +704,28 @@ The held-back lines are **counted and reported**, exactly as `--from-line`'s and
|
|
|
704
704
|
the blank ones are — a summary that quietly narrowed what it was summarising
|
|
705
705
|
would be worse than no summary.
|
|
706
706
|
|
|
707
|
+
A number you typed that the file **does not have** is the same rule pointed the
|
|
708
|
+
other way, and it gets its own clause rather than being left to the held-back
|
|
709
|
+
count. On a file that stops short of what the spec named:
|
|
710
|
+
|
|
711
|
+
```bash
|
|
712
|
+
bundle exec specguard-ingest --list --lines 3,7,12-15,33-40 log/test_results.jsonl
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
```
|
|
716
|
+
line 3: branch main, commit_sha 41f2c9b8…, ci_run_id 17442, 12 examples, 8.4s
|
|
717
|
+
…
|
|
718
|
+
specguard-ingest: listed 6 lines from log/test_results.jsonl; 26 lines not selected by --lines; --lines named 33-40, which the file does not have; nothing was delivered
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
A range that is only **half** answered says so on the same terms — the clause
|
|
722
|
+
names the portion past the end of the file, in the shorthand you typed, because
|
|
723
|
+
what you act on is the numbers you wrote. A line the file *does* have and that
|
|
724
|
+
is blank is a blank line and is reported as one; only numbers past the end of
|
|
725
|
+
the file reach this clause, so no line is ever named under two causes. The
|
|
726
|
+
clause is additive: a file that is at once short, blank-bearing and
|
|
727
|
+
selected-away states each cause and drops none of them.
|
|
728
|
+
|
|
707
729
|
A spec is read strictly, and a bad one is a `2` rather than a fallback to the
|
|
708
730
|
whole file — which is the one outcome a selector exists to prevent. `--lines 0`,
|
|
709
731
|
`--lines 5-2`, `--lines abc`, `--lines 12-`, an empty spec and an empty entry
|
|
@@ -811,7 +833,7 @@ bundle exec specguard-ingest --json log/test_results.jsonl
|
|
|
811
833
|
"file": "log/test_results.jsonl",
|
|
812
834
|
"summary": { "lines": 3, "attempted": 3, "accepted": 2, "refused": 1,
|
|
813
835
|
"undelivered": 0, "unparseable": 0, "blank": 0, "skipped": 0,
|
|
814
|
-
"selector": null },
|
|
836
|
+
"absent": null, "selector": null },
|
|
815
837
|
"lines": [
|
|
816
838
|
{ "number": 1, "status": "accepted", "code": 202, "reasons": [],
|
|
817
839
|
"test_run_id": "41f2c9b8", "ci_run_id": "17442" },
|
|
@@ -839,6 +861,7 @@ bundle exec specguard-ingest --json log/test_results.jsonl
|
|
|
839
861
|
| `summary.attempted` | how many of those were offered to the endpoint — always `0` under `--list`, and `lines` minus the unparseable ones otherwise |
|
|
840
862
|
| `summary.accepted` / `refused` / `undelivered` / `unparseable` | the same four counts the text summary line states, computed once for both renderers so they cannot disagree |
|
|
841
863
|
| `summary.blank` / `skipped` | the two ways a line of the file is not a row here, counted rather than dropped |
|
|
864
|
+
| `summary.absent` | the `--lines` numbers the file does not have, in the shorthand you typed them — a number or an `N-M` range per entry — or `null` when the selector was fully satisfied; never `[]`, on `selector`'s terms |
|
|
842
865
|
| `summary.selector` | `"--lines"`, `"--from-line"`, or `null` when nothing was held back |
|
|
843
866
|
| `lines[]` | one entry per row, in the file's order |
|
|
844
867
|
| `foldings[]` | folding, **observed**: the lines that went out with one `ci_run_id` and came back with one `test_run_id`. The same statement the text report makes as a sentence |
|
|
@@ -349,7 +349,19 @@ module SpecGuard
|
|
|
349
349
|
# the summary can say. The blanks and the skips are counted rather than
|
|
350
350
|
# dropped because a summary that quietly narrows what it is summarising is
|
|
351
351
|
# the failure this project keeps finding.
|
|
352
|
-
|
|
352
|
+
#
|
|
353
|
+
# `absent` is the same discipline applied one layer later, to the lines
|
|
354
|
+
# that were *typed* rather than to the lines that were read. A `--lines`
|
|
355
|
+
# entry can name a line past the end of the file, and such a number is not
|
|
356
|
+
# held back — there was nothing there to hold — so `skipped` cannot carry
|
|
357
|
+
# it and the file's own counts cannot state it. It is derived from the
|
|
358
|
+
# file's length in {#read_source} and rendered in the shorthand it was
|
|
359
|
+
# typed in, because the user acts on what they typed. Empty when the
|
|
360
|
+
# selector was fully satisfied, and empty under `--from-line`, whose
|
|
361
|
+
# past-the-end case is already a suffix that selected nothing.
|
|
362
|
+
#
|
|
363
|
+
# @return [Array<String>] `absent`, each entry a number or an `N-M` range
|
|
364
|
+
Source = Struct.new(:path, :lines, :blank, :skipped, :absent, :selector, keyword_init: true)
|
|
353
365
|
|
|
354
366
|
# What the command line asked for. A struct rather than a bare path,
|
|
355
367
|
# because `--from-line` is the second half of the same question — which
|
|
@@ -550,6 +562,7 @@ module SpecGuard
|
|
|
550
562
|
|
|
551
563
|
parts << blank_clause(source) if source.blank.positive?
|
|
552
564
|
parts << skipped_clause(source) if source.skipped.positive?
|
|
565
|
+
parts << absent_clause(source) if source.absent.any?
|
|
553
566
|
parts << "nothing was delivered"
|
|
554
567
|
|
|
555
568
|
parts.join("; ")
|
|
@@ -655,6 +668,12 @@ module SpecGuard
|
|
|
655
668
|
lines = []
|
|
656
669
|
blank = 0
|
|
657
670
|
skipped = 0
|
|
671
|
+
# The file's length, counted rather than inferred: `with_index` is
|
|
672
|
+
# already running and discards its last `number`, and a counter that
|
|
673
|
+
# starts at 0 says "no lines" about an empty file where a nil would say
|
|
674
|
+
# "not known". It is what {#absent_entries} measures the typed numbers
|
|
675
|
+
# against.
|
|
676
|
+
length = 0
|
|
658
677
|
|
|
659
678
|
# Numbered from the file, not from the payloads: a blank line still
|
|
660
679
|
# advances the count, so line 12 in this report is line 12 in an editor.
|
|
@@ -670,6 +689,8 @@ module SpecGuard
|
|
|
670
689
|
# the file still delivers. Swallowing it here would lose a line silently
|
|
671
690
|
# and letting it raise would report a bug in this tool.
|
|
672
691
|
File.foreach(path).with_index(1) do |text, number|
|
|
692
|
+
length = number
|
|
693
|
+
|
|
673
694
|
if held_back?(number, options)
|
|
674
695
|
skipped += 1
|
|
675
696
|
elsif text.valid_encoding? && text.strip.empty?
|
|
@@ -680,6 +701,7 @@ module SpecGuard
|
|
|
680
701
|
end
|
|
681
702
|
|
|
682
703
|
Source.new(path: path, lines: lines, blank: blank, skipped: skipped,
|
|
704
|
+
absent: absent_entries(options, length),
|
|
683
705
|
selector: options.line_set ? :line_set : :from_line)
|
|
684
706
|
rescue SystemCallError, IOError => e
|
|
685
707
|
raise UsageError, "could not read #{path}: #{e.message}"
|
|
@@ -695,6 +717,39 @@ module SpecGuard
|
|
|
695
717
|
number < options.from_line
|
|
696
718
|
end
|
|
697
719
|
|
|
720
|
+
# The typed numbers the file does not have, in the shorthand they were
|
|
721
|
+
# typed in.
|
|
722
|
+
#
|
|
723
|
+
# Computed over line NUMBERS rather than over entries, which is what makes
|
|
724
|
+
# a half-satisfied range the same fact as a wholly unsatisfied one: a
|
|
725
|
+
# range running past the end of the file has named lines that were typed
|
|
726
|
+
# and delivered nothing, exactly as a lone out-of-range number has. Each
|
|
727
|
+
# entry contributes only the portion above the file's length, so the
|
|
728
|
+
# clause names the unanswered part rather than the entry that was half
|
|
729
|
+
# honoured, and a range stays a range rather than becoming a list of
|
|
730
|
+
# numbers the user would have to read back into what they wrote.
|
|
731
|
+
#
|
|
732
|
+
# `--from-line` contributes nothing: it is a suffix, and a suffix past the
|
|
733
|
+
# end of the file already says so through the lines it held back.
|
|
734
|
+
#
|
|
735
|
+
# A line the file HAS and that is blank is not here and must never be —
|
|
736
|
+
# `blank` already says "you named a line that exists and holds nothing",
|
|
737
|
+
# and stating both would be one line reported under two causes. The
|
|
738
|
+
# comparison is against the file's length alone, which is what keeps that
|
|
739
|
+
# true rather than merely usually true.
|
|
740
|
+
#
|
|
741
|
+
# @return [Array<String>]
|
|
742
|
+
def absent_entries(options, length)
|
|
743
|
+
return [] unless options.line_set
|
|
744
|
+
|
|
745
|
+
options.line_set.filter_map do |range|
|
|
746
|
+
first = [range.first, length + 1].max
|
|
747
|
+
next if first > range.last
|
|
748
|
+
|
|
749
|
+
first == range.last ? first.to_s : "#{first}-#{range.last}"
|
|
750
|
+
end.uniq
|
|
751
|
+
end
|
|
752
|
+
|
|
698
753
|
# The per-line report on stdout — it is the product — with the diagnostics
|
|
699
754
|
# about this tool's own situation on stderr, which is the split
|
|
700
755
|
# `specguard-lint` already makes. `--json` moves the product and leaves the
|
|
@@ -748,10 +803,14 @@ module SpecGuard
|
|
|
748
803
|
# is empty". A `--from-line` past the end of the file, a `--lines` that
|
|
749
804
|
# names only lines the file does not have, and a genuinely empty file are
|
|
750
805
|
# the same silence otherwise, and only two of them are the user's mistake.
|
|
806
|
+
# The third clause is what tells the second of those from the first: a
|
|
807
|
+
# held-back count alone is the file's own length read back, which a reader
|
|
808
|
+
# can mistake for a selector that did something.
|
|
751
809
|
def empty_detail(source)
|
|
752
810
|
parts = []
|
|
753
811
|
parts << "#{source.blank} blank line#{'s' unless source.blank == 1}" if source.blank.positive?
|
|
754
812
|
parts << skipped_clause(source) if source.skipped.positive?
|
|
813
|
+
parts << absent_clause(source) if source.absent.any?
|
|
755
814
|
|
|
756
815
|
parts.empty? ? "" : " (#{parts.join('; ')})"
|
|
757
816
|
end
|
|
@@ -768,6 +827,16 @@ module SpecGuard
|
|
|
768
827
|
"#{count} earlier line#{'s' unless count == 1} skipped by --from-line"
|
|
769
828
|
end
|
|
770
829
|
|
|
830
|
+
# The typed numbers the file does not have, named rather than counted.
|
|
831
|
+
# {#skipped_clause}'s counterpart for the other direction: that one says
|
|
832
|
+
# how much of the FILE the selector held back, and this one says how much
|
|
833
|
+
# of the SELECTOR the file could not answer. A count would be the wrong
|
|
834
|
+
# shape here — the reader's next action is editing the numbers they typed,
|
|
835
|
+
# so the clause hands those numbers back in the form they wrote them.
|
|
836
|
+
def absent_clause(source)
|
|
837
|
+
"--lines named #{source.absent.join(', ')}, which the file does not have"
|
|
838
|
+
end
|
|
839
|
+
|
|
771
840
|
def blank_clause(source)
|
|
772
841
|
"#{source.blank} blank line#{'s' unless source.blank == 1} skipped"
|
|
773
842
|
end
|
|
@@ -806,6 +875,7 @@ module SpecGuard
|
|
|
806
875
|
parts << "#{counts[:unparseable]} could not be parsed" if counts[:unparseable]
|
|
807
876
|
parts << blank_clause(source) if source.blank.positive?
|
|
808
877
|
parts << skipped_clause(source) if source.skipped.positive?
|
|
878
|
+
parts << absent_clause(source) if source.absent.any?
|
|
809
879
|
|
|
810
880
|
parts.join("; ")
|
|
811
881
|
end
|
|
@@ -105,7 +105,8 @@ module SpecGuard
|
|
|
105
105
|
STATUS_LISTED = "listed"
|
|
106
106
|
STATUS_UNPARSEABLE = "unparseable"
|
|
107
107
|
|
|
108
|
-
# @param source [IngestCLI::Source] the file, its blanks
|
|
108
|
+
# @param source [IngestCLI::Source] the file, its blanks, its skips and
|
|
109
|
+
# the typed lines it does not have
|
|
109
110
|
# @param results [Array<IngestCLI::LineResult>] one per line delivered,
|
|
110
111
|
# in the file's order
|
|
111
112
|
# @param counts [Hash{Symbol=>Integer}] status counts, computed once by
|
|
@@ -167,6 +168,15 @@ module SpecGuard
|
|
|
167
168
|
# here. They are stated always, and for the reason {IngestCLI::Source}
|
|
168
169
|
# counts rather than drops them: a summary that quietly narrows what it is
|
|
169
170
|
# summarising is the failure this command is arranged against.
|
|
171
|
+
#
|
|
172
|
+
# `absent` is that same rule pointed the other way — at the numbers that
|
|
173
|
+
# were typed rather than at the lines that were read. A `--lines` entry
|
|
174
|
+
# naming past the end of the file is held back by nothing and read as
|
|
175
|
+
# nothing, so `skipped` (which counts lines of <file>) structurally cannot
|
|
176
|
+
# carry it, and without this key a satisfied selector and a phantom one
|
|
177
|
+
# render the identical document. It is `null` rather than `[]` where the
|
|
178
|
+
# selector was fully satisfied, on {#selector}'s terms: a fact that does
|
|
179
|
+
# not apply is absent, never a fabricated empty.
|
|
170
180
|
def self.summary(source, lines:, counts:, attempted:)
|
|
171
181
|
{
|
|
172
182
|
"lines" => lines,
|
|
@@ -177,10 +187,21 @@ module SpecGuard
|
|
|
177
187
|
"unparseable" => counts.fetch(:unparseable, 0),
|
|
178
188
|
"blank" => source.blank,
|
|
179
189
|
"skipped" => source.skipped,
|
|
190
|
+
"absent" => absent(source),
|
|
180
191
|
"selector" => selector(source)
|
|
181
192
|
}
|
|
182
193
|
end
|
|
183
194
|
|
|
195
|
+
# The typed line numbers the file does not have, in the shorthand they
|
|
196
|
+
# were typed in and computed by {IngestCLI} once for both renderers — the
|
|
197
|
+
# discipline the counts above are held to, applied to the one fact the
|
|
198
|
+
# text summary and this document could otherwise disagree about.
|
|
199
|
+
#
|
|
200
|
+
# @return [Array<String>, nil]
|
|
201
|
+
def self.absent(source)
|
|
202
|
+
source.absent.empty? ? nil : source.absent
|
|
203
|
+
end
|
|
204
|
+
|
|
184
205
|
# Every line that reached the endpoint, which is every line except the
|
|
185
206
|
# ones that were never a run.
|
|
186
207
|
def self.attempted(counts)
|
|
@@ -275,7 +296,8 @@ module SpecGuard
|
|
|
275
296
|
Array(values).filter_map { |value| value.scrub("") if value.is_a?(String) }
|
|
276
297
|
end
|
|
277
298
|
|
|
278
|
-
private_class_method :document, :summary, :attempted, :selector, :delivered, :listed, :folded,
|
|
299
|
+
private_class_method :document, :summary, :attempted, :absent, :selector, :delivered, :listed, :folded,
|
|
300
|
+
:reasons
|
|
279
301
|
end
|
|
280
302
|
end
|
|
281
303
|
end
|
|
@@ -45,13 +45,27 @@ module SpecGuard
|
|
|
45
45
|
# that matters more — a missing file does not stop either tool checking
|
|
46
46
|
# the good files named beside it.
|
|
47
47
|
#
|
|
48
|
-
# A path that
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
48
|
+
# A path that EXISTS and is not a regular file is the same ratified
|
|
49
|
+
# difference one step further out. The binary's arguments are glob
|
|
50
|
+
# PATTERNS whose matches are filtered to regular files, so such a path
|
|
51
|
+
# folds into the answer a name matching nothing gets — the backend has
|
|
52
|
+
# nothing else to say about it. This linter does no globbing and hands
|
|
53
|
+
# the path to `File.read` as given, so what comes back is whatever
|
|
54
|
+
# reading THAT path does, and never what a nonexistent path gets: the
|
|
55
|
+
# path is there to be opened, so the failure it does or does not produce
|
|
56
|
+
# is a fact about the path rather than about a pattern. Ruby tells the
|
|
57
|
+
# two apart and the backend cannot — asserted under "a path that is not
|
|
58
|
+
# a regular file" in that same spec, whose subject is a path that
|
|
59
|
+
# exists, is not a regular file, and is not a directory.
|
|
60
|
+
#
|
|
61
|
+
# A DIRECTORY is NOT an example of this difference, on either side.
|
|
62
|
+
# `CLI#select` refuses one named as an explicit path (SPGD-1303) and
|
|
63
|
+
# `--changed` derives its list from git, so a directory is settled
|
|
64
|
+
# before this rescue is reached and never reaches the backend either.
|
|
65
|
+
# What the shared binary makes of a bare directory argument has moved
|
|
66
|
+
# upstream more than once and is deliberately not restated here: the
|
|
67
|
+
# difference above is a property of the two sides' path semantics, and
|
|
68
|
+
# nothing about a directory is needed to state it.
|
|
55
69
|
def scan_file(path)
|
|
56
70
|
begin
|
|
57
71
|
text = File.read(path, encoding: "UTF-8")
|
data/lib/specguard/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: specguard-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.22
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- specguard Agent
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|