test(lint-once): pin the two-build and later-flag shapes
All checks were successful
check / check (push) Successful in 15s
All checks were successful
check / check (push) Successful in 15s
The false green: script/lint chaining a second docker build after the lint image ran prettier twice and counted once. The misresolution: a bare docker build followed by cp -f resolved to the cp's file. Pin both, and each separator that bounds an invocation.
This commit is contained in:
15
README.md
15
README.md
@@ -129,13 +129,14 @@ invocation graph, so a second pass cannot creep back in unnoticed.
|
|||||||
`script/fmt-check` remains as a standalone entrypoint for asking the formatting
|
`script/fmt-check` remains as a standalone entrypoint for asking the formatting
|
||||||
question on its own, without docker and without the rest of lint. It is not the
|
question on its own, without docker and without the rest of lint. It is not the
|
||||||
last formatting path on the host — `script/fmt` writes with the host prettier,
|
last formatting path on the host — `script/fmt` writes with the host prettier,
|
||||||
and `package.json` exposes `fmt` and `fmt-check` as direct prettier calls — it is
|
and `package.json` exposes `fmt` and `fmt-check` as direct prettier calls — it
|
||||||
the one of them that the check graph could plausibly have reached, and nothing
|
is the one of them that the check graph could plausibly have reached, and
|
||||||
reaches it: neither `script/check` nor `script/precommit` calls it, so it never
|
nothing reaches it: neither `script/check` nor `script/precommit` calls it, so
|
||||||
contributes to their verdicts. Its own verdict cannot drift from the container's: prettier is pinned
|
it never contributes to their verdicts. Its own verdict cannot drift from the
|
||||||
to an exact version, installed from `yarn.lock` under `--frozen-lockfile` in
|
container's: prettier is pinned to an exact version, installed from `yarn.lock`
|
||||||
both places, and reads `.gitignore` as its default ignore file — which is why
|
under `--frozen-lockfile` in both places, and reads `.gitignore` as its default
|
||||||
`.dockerignore` deliberately keeps `.gitignore` in the build context.
|
ignore file — which is why `.dockerignore` deliberately keeps `.gitignore` in
|
||||||
|
the build context.
|
||||||
|
|
||||||
Lint happens in exactly one place in that graph, which constrains the rest of
|
Lint happens in exactly one place in that graph, which constrains the rest of
|
||||||
the build. `script/check` calls `script/lint`, so `make check` cannot run inside
|
the build. `script/check` calls `script/lint`, so `make check` cannot run inside
|
||||||
|
|||||||
@@ -1094,6 +1094,43 @@ describe("the resolver reads what the shell would run", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The false green this file exists to prevent, in the one shape it still
|
||||||
|
// had: `script/lint` chaining a second `docker build` after the lint image
|
||||||
|
// built prettier twice and counted once, because a single `test` and a
|
||||||
|
// single `exec` over the line produced exactly one edge and resolved it to
|
||||||
|
// the first file named. That is the same failure as the original duplicate
|
||||||
|
// pass — two invocations, one verdict — reached through the walker instead
|
||||||
|
// of through the repo.
|
||||||
|
it("follows both docker builds when one line chains two", () => {
|
||||||
|
expect(
|
||||||
|
edgesOf(
|
||||||
|
'docker build --build-arg LINT_EPOCH="$(date +%s)" -f Dockerfile.lint . && docker build -f Dockerfile.extra .',
|
||||||
|
),
|
||||||
|
).toEqual(["docker:Dockerfile.lint", "docker:Dockerfile.extra"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// The other half: a flag search over the whole line reads a *later*
|
||||||
|
// command's `-f` as the build's own. A bare `docker build .` followed by
|
||||||
|
// `cp -f` resolved to `Dockerfile.lint`, which both counts an invocation
|
||||||
|
// that never happens and drops the `Dockerfile` edge — the half of the CI
|
||||||
|
// graph the second entrypoint exists to cover. Each separator that bounds
|
||||||
|
// an invocation is pinned, since the bounding is a split rather than a
|
||||||
|
// shell parse and dropping one of them would be silent.
|
||||||
|
it("does not read a later command's -f as the build's", () => {
|
||||||
|
expect(
|
||||||
|
edgesOf("docker build . && cp -f Dockerfile.lint /tmp/x"),
|
||||||
|
).toEqual(["docker:Dockerfile"]);
|
||||||
|
expect(
|
||||||
|
edgesOf("docker build . || cp -f Dockerfile.lint /tmp/x"),
|
||||||
|
).toEqual(["docker:Dockerfile"]);
|
||||||
|
expect(
|
||||||
|
edgesOf("docker build . ; cp -f Dockerfile.lint /tmp/x"),
|
||||||
|
).toEqual(["docker:Dockerfile"]);
|
||||||
|
expect(edgesOf("docker build . | tee -f Dockerfile.lint")).toEqual([
|
||||||
|
"docker:Dockerfile",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
// This exact-equality assertion is also the block-scalar guard, which is
|
// This exact-equality assertion is also the block-scalar guard, which is
|
||||||
// not obvious from its name: a `run: |` step resolves to the bare `|`,
|
// not obvious from its name: a `run: |` step resolves to the bare `|`,
|
||||||
// which reaches nothing, so the prettier count would stay 1 no matter what
|
// which reaches nothing, so the prettier count would stay 1 no matter what
|
||||||
|
|||||||
Reference in New Issue
Block a user