Class: SpecGuard::RSpec::ValidatorBackend::Runner
- Inherits:
-
Object
- Object
- SpecGuard::RSpec::ValidatorBackend::Runner
- Defined in:
- lib/specguard/rspec/validator_backend.rb
Overview
Invokes the binary and turns its report into Linter::Results.
Constant Summary collapse
- KINDS =
kind-> the gem's equivalent.no-matchhas no equivalent of its own and folds into KIND_READ; see the module comment. { "schema" => Finding::KIND_SCHEMA, "extraction" => Finding::KIND_EXTRACTION, "parse" => Finding::KIND_PARSE, "read" => Finding::KIND_READ, "no-match" => Finding::KIND_READ }.freeze
- NON_ANNOTATION_KINDS =
Kinds that are a statement about a FILE rather than about an annotation site. They contribute nothing to
summary.annotations, exactly asCLI#summary_linekeeps unread files out of the gem's own annotation count. %w[read no-match].freeze
- MAX_ARG_BYTES =
A full audit passes every spec file in the repository — thousands of arguments on a large suite — and an argument vector has two separate kernel limits on Linux:
ARG_MAXcaps the TOTAL vector (a quarter of the stack rlimit, so typically ~2 MiB) andMAX_ARG_STRLENcaps each SINGLE argument at 128 KiB. Exceeding either isE2BIG. This budget is for the first, and is deliberately well under the typical value rather than derived from it: the limit is not portable (it is smaller on some kernels and much smaller on other Unixes), and there is nothing to gain from crowding it. The batches are an implementation detail: #check concatenates their findings in argument order, so the caller still gets one list and CLI still prints one selection line and one summary line. 96 * 1024
- MAX_BATCH_FILES =
A second, independent bound. Some kernels cap the argument count (and
MAX_ARG_BYTESalone would allow ~90k one-character paths). 1_000- IDENTITY_MAX_BYTES =
The
--versionprobe's own guard rail, and the only thing it asserts about the answer's SHAPE. The identity is passed through verbatim, so the question is not "is this the format I expect" — a future build may word it differently and still be telling the truth — but "can this be rendered as the one line CLI promises per run". A binary answering--versionwith a report document, a stack trace, or a megabyte of anything is answering a different question, and its output would break the line rather than fill it. 200- SCHEMA_DIGEST_PATTERN =
The one token of the identity line this file interprets, matched exactly as
cmd/validate-intent/version.gowrites it: the literalschema sha256:followed by 64 hex digits. Everything around it stays opaque — the version, the toolchain, the platform and anything a future build appends are still the binary's business.Anchored at both ends. Without the trailing boundary a 65-digit token would match its first 64 and be compared as if it were a digest; with it, such a token matches nothing and the run lands in the "not reported" band, which is the right answer for a line this gem cannot read rather than one it disagrees with.
Case-insensitive, and the capture is compared downcased. Go's
hex.EncodeToStringis lower case and Digest::SHA256#hexdigest is too, so today this changes nothing; if a future build shouts, that is a spelling of the same digest and must not be reported as divergence. /\bschema sha256:(\h{64})\b/i- SCHEMA_SOURCE_FLAG =
The flag that answers what a run ENFORCES. Named as a constant for the reason
schemaSourceFlagis one on the other side of the seam: the probe passes it, the diagnostics quote it, and the specs assert the argument vector — three places that must spell it the same way. "--schema-source"- SCHEMA_SOURCE_PATTERN =
--schema-sourcewrites ONE line:schema <origin> sha256:<64-hex>, where the origin is either an absolute path or the literal<embedded schema>(cmd/validate-intent/schemasource.go,SchemaSourceLine).This is a SECOND pattern rather than a reuse of SCHEMA_DIGEST_PATTERN, and the difference is not cosmetic: that one requires
schemaIMMEDIATELY followed bysha256:, which is how--versionwrites it, and the origin interposed here means it matches this surface never. Reusing it would have made every probe unreadable, every run fall back to the carried digest, and nothing go red — see the module comment.Anchored to the WHOLE line, unlike the identity token. There, the digest is one token inside a line that is otherwise the binary's own business; here the line IS the answer, so anything around it means this is not the surface being read and the run belongs in the unavailable band. Both captures matter: the digest is compared, and the origin is what lets #provenance say WHICH schema was enforced rather than that one was.
The trailing anchor is what makes the digest the LAST
sha256:token on the line rather than the first — the same reading Go's own comment prescribes for the shell (${line##* }"yieldssha256:<hex>whatever the origin contains"). A path may contain spaces, and one may even be NAMED like this tail; ending the match at the end of the line is what keeps the answer the last token in both cases. (The origin is also captured greedily, but that is not what decides it: only one token can end the line.) /\Aschema (?<origin>\S(?:.*\S)?) sha256:(?<digest>\h{64})\z/i- SCHEMA_SOURCE_MAX_BYTES =
The
--schema-sourceprobe's renderability budget, and the reason it is not IDENTITY_MAX_BYTES. This line carries a filesystem path, which POSIX allows up toPATH_MAX(4096 on Linux) all by itself, so the 200-byte budget that suits a one-line version string would reject legitimate answers from correctly-installed binaries. It still bounds the line, for the same reason the other one does: a binary answering with a report document or a megabyte of anything is answering a different question, and its output would break the line rather than fill it. 8 * 1024
Instance Attribute Summary collapse
-
#enforced_schema ⇒ Hash{Symbol => String}?
readonly
What
--schema-sourceanswered:{origin:, digest:}, or nil when the binary could not answer it — a build predating open-test-intent slice 19, a schema that exists and will not load, or output this cannot read. -
#identity ⇒ String?
readonly
The binary's own
--versionline, or nil when it could not report one. -
#path ⇒ Object
readonly
Returns the value of attribute path.
-
#schema_contract ⇒ Symbol?
readonly
The result of the schema-contract comparison, one of:.
Instance Method Summary collapse
-
#check(paths) ⇒ Array<Linter::Result>
In argument order.
-
#initialize(path) ⇒ Runner
constructor
A new instance of Runner.
-
#provenance ⇒ String
The active arm of CLI's one-line-per-run provenance statement.
-
#verify! ⇒ Object
Fails before anything is selected or scanned, so "the backend you asked for is not there" is never mistaken for a verdict about anyone's annotations.
Constructor Details
#initialize(path) ⇒ Runner
Returns a new instance of Runner.
500 501 502 |
# File 'lib/specguard/rspec/validator_backend.rb', line 500 def initialize(path) @path = path end |
Instance Attribute Details
#enforced_schema ⇒ Hash{Symbol => String}? (readonly)
What --schema-source answered: {origin:, digest:}, or nil when the
binary could not answer it — a build predating open-test-intent slice
19, a schema that exists and will not load, or output this cannot
read. Populated by #verify! beside #identity.
The digest is the schema a run STARTED at that moment would enforce. It is a second process from #identity, so the two are not guaranteed to describe the same instant — see the module comment for what that does and does not cost.
497 498 499 |
# File 'lib/specguard/rspec/validator_backend.rb', line 497 def enforced_schema @enforced_schema end |
#identity ⇒ String? (readonly)
The binary's own --version line, or nil when it could not report
one. Populated by #verify!; nil before it runs, which is why nothing
constructs a Runner without it (see SpecGuard::RSpec::ValidatorBackend.resolve).
Assigned unconditionally rather than memoized: a second #verify! re-probes, in step with the file checks it sits among, which also re-run. "Once per run" is a property of the single #verify! call SpecGuard::RSpec::ValidatorBackend.resolve makes, not of a cache here.
464 465 466 |
# File 'lib/specguard/rspec/validator_backend.rb', line 464 def identity @identity end |
#path ⇒ Object (readonly)
Returns the value of attribute path.
452 453 454 |
# File 'lib/specguard/rspec/validator_backend.rb', line 452 def path @path end |
#schema_contract ⇒ Symbol? (readonly)
The result of the schema-contract comparison, one of:
:enforced — the binary reported the schema its runs LOAD, and
it is ours. The strong arm: this one is an answer
about the contract a verdict would be produced
under, not about the bytes the artifact carries
:matched — `--schema-source` did not answer, so the carried
digest was compared instead, and it is ours
:unreported — it named itself but carries no digest token
:unidentified — it could not name itself at all
:unreadable — we could not read our own vendored schema
There is no :diverged member: both comparisons raise out of
#verify! on a difference, so no Runner ever exists holding one.
Populated by #verify! beside #identity, and nil before it runs for
the same reason.
484 485 486 |
# File 'lib/specguard/rspec/validator_backend.rb', line 484 def schema_contract @schema_contract end |
Instance Method Details
#check(paths) ⇒ Array<Linter::Result>
Returns in argument order.
561 562 563 564 565 566 567 568 569 |
# File 'lib/specguard/rspec/validator_backend.rb', line 561 def check(paths) paths = paths.to_a # `validate-intent --source` with no argument is a usage error (exit # 2) — and there is nothing to ask it. `CLI#report_selection` has # already warned about the empty selection. return [] if paths.empty? batch(paths).flat_map { |group| check_batch(group) } end |
#provenance ⇒ String
The active arm of CLI's one-line-per-run provenance statement.
The identity half is the binary's own words, uninterpreted. The path is spelled exactly as every diagnostic in this file spells it, so the line naming the validator and any error about it name the same thing.
The clause after it is the gem's own, because it is a statement about a COMPARISON the gem made and not about the binary. Each of the five states it can report is worded distinctly: three of them mean the contract was not checked, for three different reasons, and collapsing them into one sentence would leave an operator unable to tell a build too old to answer from an installation missing its own schema. The remaining two are both "checked and clean" and must NOT be collapsed either — one of them answers what the run enforces and the other only what the binary carries, which is the whole distinction this file turns on.
Composed from three pieces rather than branched on, because the
identity and the schema contract are answers to two different
questions and every combination of them is reachable: a binary can
fail --version and answer --schema-source, and the line then has
to say both things rather than the first one only.
553 554 555 556 |
# File 'lib/specguard/rspec/validator_backend.rb', line 553 def provenance "validated by #{@identity || 'the binary'} at #{@path} (#{ENV_VAR})" \ "#{identity_clause}#{schema_contract_clause}" end |
#verify! ⇒ Object
Fails before anything is selected or scanned, so "the backend you asked for is not there" is never mistaken for a verdict about anyone's annotations.
The two probes ride along for the placement rather than for the check: asking here is what makes them once-per-run and ahead of selection. Neither can fail the run — see the module comment.
The schema-contract comparison reads the answers the probes already obtained, so it costs no third process, and it CAN fail the run — see the module comment for why that one band belongs in exit 2 while the identity itself does not.
518 519 520 521 522 523 524 525 526 527 |
# File 'lib/specguard/rspec/validator_backend.rb', line 518 def verify! raise ValidatorError, "#{describe} does not exist#{path_hint}" unless File.exist?(@path) raise ValidatorError, "#{describe} is not a file" unless File.file?(@path) raise ValidatorError, "#{describe} is not executable" unless File.executable?(@path) @identity = probe_identity @enforced_schema = probe_schema_source @schema_contract = verify_schema_contract! self end |