test(lint): pin block-style branch/path scoping for WP001 (oleks/pipetree#22) #23
@@ -208,6 +208,8 @@ func TestWP001_CleanCases(t *testing.T) {
|
|||||||
"testdata/wp001_clean.yaml",
|
"testdata/wp001_clean.yaml",
|
||||||
"testdata/wp001_eventlist_clean.yaml",
|
"testdata/wp001_eventlist_clean.yaml",
|
||||||
"testdata/wp001_steplevel_clean.yaml",
|
"testdata/wp001_steplevel_clean.yaml",
|
||||||
|
"testdata/wp001_blockbranch_clean.yaml",
|
||||||
|
"testdata/wp001_blockpath_clean.yaml",
|
||||||
} {
|
} {
|
||||||
if got := findingsFor(lintFixture(t, file), "WP001"); len(got) != 0 {
|
if got := findingsFor(lintFixture(t, file), "WP001"); len(got) != 0 {
|
||||||
t.Errorf("%s: expected no WP001 finding, got %+v", file, got)
|
t.Errorf("%s: expected no WP001 finding, got %+v", file, got)
|
||||||
@@ -215,6 +217,57 @@ func TestWP001_CleanCases(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestWP001_BlockStyleScopingParsesAsScoped pins the PARSE, not just the
|
||||||
|
// verdict (oleks/pipetree#22). WP001 staying quiet is necessary but not
|
||||||
|
// sufficient: a parser that dropped `branch:` entirely would also produce a
|
||||||
|
// bare push entry, which WP001 would then be right to flag - so silence alone
|
||||||
|
// cannot distinguish "scoping understood" from "scoping lost". Assert the
|
||||||
|
// constraint actually landed in the model.
|
||||||
|
//
|
||||||
|
// This is the exact shape that defeated the xonsh predecessor this linter
|
||||||
|
// replaced (oleks/emmett#385): its key regex demanded a same-line value, so a
|
||||||
|
// block-style `branch:` vanished from the entry and a correctly-scoped config
|
||||||
|
// hard-failed the push. That check is now deleted (oleks/emmett 278a567),
|
||||||
|
// leaving pipetree as the only thing standing between this bug class and
|
||||||
|
// everyone's pre-push hook.
|
||||||
|
func TestWP001_BlockStyleScopingParsesAsScoped(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
file string
|
||||||
|
field string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"testdata/wp001_blockbranch_clean.yaml", "branch", "main"},
|
||||||
|
{"testdata/wp001_blockpath_clean.yaml", "path", "src/**"},
|
||||||
|
} {
|
||||||
|
pl, err := parse.File(tc.file)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse.File(%s): %v", tc.file, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pushConds []model.Condition
|
||||||
|
for _, c := range pl.When {
|
||||||
|
if c.Fields["event"] == "push" {
|
||||||
|
pushConds = append(pushConds, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(pushConds) != 1 {
|
||||||
|
t.Fatalf("%s: expected exactly 1 push condition, got %d (%+v)",
|
||||||
|
tc.file, len(pushConds), pl.When)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, ok := pushConds[0].Fields[tc.field]
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s: block-style %q was dropped from the push condition; "+
|
||||||
|
"fields were %+v", tc.file, tc.field, pushConds[0].Fields)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.Contains(got, tc.want) {
|
||||||
|
t.Errorf("%s: push condition %q = %q, want it to contain %q",
|
||||||
|
tc.file, tc.field, got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestWP001_RemediationCarriesClass2Caveat is the constitution-V check in
|
// TestWP001_RemediationCarriesClass2Caveat is the constitution-V check in
|
||||||
// test form (FR-002): the redundancy advice must never ship without the
|
// test form (FR-002): the redundancy advice must never ship without the
|
||||||
// warning about what that advice can create.
|
// warning about what that advice can create.
|
||||||
|
|||||||
Vendored
+14
@@ -18,6 +18,20 @@ naming a different rule, and one anchored to a line that has no finding.
|
|||||||
All four assert the fail-open contract — the finding survives and still
|
All four assert the fail-open contract — the finding survives and still
|
||||||
blocks.
|
blocks.
|
||||||
|
|
||||||
|
The `wp001_block*_clean.yaml` pair pins BLOCK-style scoping — a `branch:` or
|
||||||
|
`path:` whose list is indented under it rather than written inline as
|
||||||
|
`[main]` (oleks/pipetree#22). Both spellings are the same YAML, so both must
|
||||||
|
get the same verdict; they are separated here because the check pipetree
|
||||||
|
replaced got exactly this wrong (oleks/emmett#385), and since that check was
|
||||||
|
deleted (oleks/emmett 278a567) pipetree is the only remaining guard.
|
||||||
|
|
||||||
|
Note these two are pinned by `TestWP001_BlockStyleScopingParsesAsScoped`, not
|
||||||
|
only by the `_clean` verdict. Rule silence alone cannot distinguish "scoping
|
||||||
|
understood" from "scoping lost": a parser that drops the value still leaves
|
||||||
|
the `branch` KEY in the condition, so WP001 sees a non-`event` key and stays
|
||||||
|
quiet either way. Verified by mutation — a deliberately style-sensitive
|
||||||
|
parser leaves every other WP001 test passing and fails only that one.
|
||||||
|
|
||||||
Class-2 fixtures are a *pair of files*, because the rules are pure
|
Class-2 fixtures are a *pair of files*, because the rules are pure
|
||||||
functions of `(model, protection data)`: a config directory plus a JSON
|
functions of `(model, protection data)`: a config directory plus a JSON
|
||||||
snapshot of what the Gitea API returns for
|
snapshot of what the Gitea API returns for
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Same scoping as wp001_clean.yaml, written as a BLOCK-style list instead of
|
||||||
|
# the flow-style `branch: [main]`. The two spellings are the same YAML, so
|
||||||
|
# they must get the same verdict: quiet.
|
||||||
|
#
|
||||||
|
# This is the shape that broke the xonsh predecessor (oleks/emmett#385). Its
|
||||||
|
# key regex required a value on the SAME line as the key, so a `branch:` whose
|
||||||
|
# value lives on the following lines never entered the entry's key set, the
|
||||||
|
# entry read as bare, and a correctly-scoped config got a hard-failing false
|
||||||
|
# positive. pipetree is now the sole guard for this class (the xonsh check was
|
||||||
|
# deleted in oleks/emmett 278a567), so the style is pinned here deliberately.
|
||||||
|
#
|
||||||
|
# Modelled on the real config that surfaced it: ~/projects/gitea-mcp.
|
||||||
|
when:
|
||||||
|
- event: pull_request
|
||||||
|
- event: push
|
||||||
|
branch:
|
||||||
|
- main
|
||||||
|
- event: tag
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: unit
|
||||||
|
image: alpine
|
||||||
|
commands:
|
||||||
|
- echo running unit tests
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Block-style `path:` scoping — the other key WP001 accepts as narrowing, and
|
||||||
|
# the other half of oleks/emmett#385's blind spot. Multi-item on purpose: a
|
||||||
|
# single-element block list can be parsed correctly by accident (first item
|
||||||
|
# wins), a two-element one cannot.
|
||||||
|
#
|
||||||
|
# Must stay quiet: the push entry is scoped, so it is not the same check
|
||||||
|
# running twice on the same commit.
|
||||||
|
when:
|
||||||
|
- event: push
|
||||||
|
path:
|
||||||
|
- src/**
|
||||||
|
- internal/**
|
||||||
|
- event: pull_request
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: unit
|
||||||
|
image: alpine
|
||||||
|
commands:
|
||||||
|
- echo running unit tests
|
||||||
+12
@@ -29,6 +29,18 @@
|
|||||||
"steps": 1,
|
"steps": 1,
|
||||||
"has_when": true
|
"has_when": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file": "../analyze/testdata/wp001_blockbranch_clean.yaml",
|
||||||
|
"valid": true,
|
||||||
|
"steps": 1,
|
||||||
|
"has_when": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "../analyze/testdata/wp001_blockpath_clean.yaml",
|
||||||
|
"valid": true,
|
||||||
|
"steps": 1,
|
||||||
|
"has_when": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file": "../analyze/testdata/wp001_clean.yaml",
|
"file": "../analyze/testdata/wp001_clean.yaml",
|
||||||
"valid": true,
|
"valid": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user