Skip to content

Harden acceptance-test mode guards - #3602

Open
rilical wants to merge 17 commits into
integrations:mainfrom
rilical:rilical-terraform-tester-discovery
Open

Harden acceptance-test mode guards#3602
rilical wants to merge 17 commits into
integrations:mainfrom
rilical:rilical-terraform-tester-discovery

Conversation

@rilical

@rilical rilical commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What this changes

Acceptance tests now stop safely before touching credentials, organization setup, or provider metadata that the selected test mode cannot use.

Two concrete provider bugs are fixed:

  1. Anonymous acceptance tests wrongly required credentials. Provider test setup (getTestMeta and friends) requested a token / GitHub App even when GH_TEST_AUTH_MODE=anonymous, so credential-free tests like TestAccGithubIpRangesDataSource exited before running.
  2. Organization/team/enterprise setup could run before its guard. Some must* setup helpers and direct testAccConf reads execute before resource.Test, i.e. before PreCheck. With TF_ACC unset that panics on a nil testAccConf; with an individual-account PAT it fails against the wrong account. These now fail closed with an inline mode guard.

User-visible result: anonymous acceptance tests run without credentials, and org/team/enterprise tests can no longer panic or create resources in the wrong account when run outside their mode.

Heads-up for reviewers — what "Terraform Provider Tester" is. It's an internal acceptance-test harness we use to run this provider's TestAcc* suites safely (mode selection, preflight, orphan cleanup). It lives in a separate repo and is not part of this change. It's only mentioned below as how these bugs were found. This PR touches only this provider's own test scaffolding and stands on its own — you do not need that tool to review, run, or benefit from the fix.

How this was found

We ran this provider's acceptance suite through that harness in anonymous mode. The run failed before any test executed because setup still demanded credentials (bug 1). Auditing that path surfaced the pre-resource.Test guard gap (bug 2) across a set of setup helpers and testAccConf reads.

The fix is enforced two ways:

  • nil-safe runtime guards for anonymous and credentialed modes
  • a recursive AST regression test (TestModeGuardPrecedesOrgScopedSetup_Static) that rejects unsafe setup added by future contributors, including nested control flow and closures, so this can't silently regress

Validation

  • make test
  • go vet ./...
  • CGO_ENABLED=0 go build ./...
  • gofmt -l . is empty
  • git diff --check origin/main...HEAD
  • a live read-only run of TestAccGithubIpRangesDataSource in anonymous mode (no credentials), which now runs instead of exiting early

Files

  • github/acc_test.go: nil-safe acceptance and mode guards
  • github/acc_mode_guard_static_test.go: recursive static enforcement of the guard-before-setup rule
  • github/acc_mode_guard_test.go: hermetic subprocess regressions
  • guarded github/**/*_test.go call sites: inline mode checks before org/team/enterprise setup and testAccConf reads
  • .github/instructions/tests.instructions.md: documents the rule for contributors and reviewers

Scope and limitations

  • No provider schema, CRUD, resource, or Terraform state semantics change. This is test-scaffolding only.
  • No destructive cleanup is performed by anything in this PR.
  • Mode-agnostic setup helpers (e.g. mustCreateTestRepository) can still panic if a contributor directly invokes a matching TestAcc... with TF_ACC unset. Normal CI excludes acceptance tests (-skip '^TestAcc') and the new scanner covers the mode-sensitive helpers; the narrower direct-invocation case can be handled separately if wanted.

Supersedes closed draft #3595 with the complete guard set and current main merged.

rilical and others added 16 commits August 5, 2026 13:02
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…egression harness

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…lper

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…e tests

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The static completeness guard's extractCalls/scanBlock only inspected
direct ExprStmt/AssignStmt statements and only recursed into direct
t.Run(...) closures. This let org-scoped must*/skip-precedence
violations nested inside RangeStmt/ForStmt/IfStmt/SwitchStmt bodies,
or inside func literals embedded in composite-literal or call
arguments (e.g. resource.TestCase{CheckDestroy: func() error {...}}),
evade detection entirely.

Replace extractCalls/scanBlock with a full recursive walk:
  - scanExpr: examines CallExpr/FuncLit/CompositeLit/KeyValueExpr/
    UnaryExpr/ParenExpr/BinaryExpr, recognizing t.Run subtests,
    guard-func calls, org-scoped violation calls, and recursing into
    any nested func literal reachable through call args or
    composite-literal element values.
  - scanStmt: walks every statement kind (ExprStmt, AssignStmt,
    GoStmt, DeferStmt, ReturnStmt, SendStmt, LabeledStmt, BlockStmt,
    IfStmt, ForStmt, RangeStmt, SwitchStmt, TypeSwitchStmt), threading
    a guarded bool through in lexical order. Unconditionally executed
    constructs (Init clauses) propagate a guard forward;
    conditionally/repeatedly executed bodies (if/for/range/switch
    case bodies) do not leak a guard found inside them back out to
    the enclosing scope, since those bodies are not guaranteed to run
    before, or even run at all relative to, later sibling statements.
  - scanStmts: threads guarded sequentially across a statement list.

Added two failing-first fixture regression tests proving the gap and
then the fix:
  - TestFindModeGuardViolations_DetectsRangeLoopNestedTRun: an
    unguarded mustCreateTestTeam inside a t.Run nested in a for-range
    loop.
  - TestFindModeGuardViolations_DetectsFuncLitInCompositeLiteral: an
    unguarded mustCreateTestTeam inside a func literal assigned as a
    composite-literal field value.

Both were confirmed RED (0 violations detected) against the old
scanner and GREEN after the rewrite. The original fixture test
(TestFindModeGuardViolations_DetectsUnguardedOrgSetup) and the
completeness guard against the real github/*_test.go tree
(TestModeGuardPrecedesOrgScopedSetup_Static) both continue to pass
with no new false positives or newly-discovered real violations.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Independent re-review requested explicit fixture coverage for five
unguarded forms: range-loop-nested t.Run, composite-literal func
closure, if-body, switch-case, and var-declaration initializer.

Adding fixture tests for all five against the current scanner showed
four already pass (range/composite-literal were fixed previously;
if-body/switch-case were already covered by the existing IfStmt/
SwitchStmt handling in scanStmt). The fifth genuinely failed:

  TestFindModeGuardViolations_DetectsVarDeclInitializer
    acc_mode_guard_static_test.go:633: expected exactly 1 violation
    (from fixture_vardecl_bad_test.go), got 0: []

scanStmt had no case for *ast.DeclStmt, so a call like
`var team = mustCreateTestTeam(t)` inside a function body fell
through to the default case and was silently skipped.

Fix: add a DeclStmt case to scanStmt that walks each GenDecl's
ValueSpec.Values and scans them as expressions, propagating the
guarded state forward exactly like AssignStmt (a var declaration
executes unconditionally in its enclosing block, same as any other
direct statement).

All five fixture tests now pass, plus the original two fixture tests
and the completeness guard against the real github/*_test.go tree
(still 0 violations — no new false positives).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Extend the lexical AST scanner to distinguish general acceptance configuration guards from mode/capability guards while detecting testAccConf-rooted reads. Guard every surfaced acceptance-test site and cover the 37 affected TestAcc functions with hermetic subprocess regressions.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

👋 Hi, and thank you for this contribution!

This repo is maintained by GitHub and community members on a best-effort basis. We'll get to this as soon as we can.

You can help us prioritize by joining the discussion on open issues and PRs, sharing details on the changes you need, and reviewing other contributions.


🤖 This is an automated message.

@rilical

rilical commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

@copilot review

Remove the agent scratch plan committed under docs/superpowers/ and add
the directory to .gitignore so planning artifacts stay out of the repo.
Make the acc mode-guard allowlist comment self-contained now that the
referenced plan file is gone.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant