Skip to content

Separate Terraform's tool-dependent tests into a new Calamari.ExternalTools.Tests project - #2107

Draft
zentron wants to merge 16 commits into
mainfrom
robe/external-tool-test-separation
Draft

Separate Terraform's tool-dependent tests into a new Calamari.ExternalTools.Tests project#2107
zentron wants to merge 16 commits into
mainfrom
robe/external-tool-test-separation

Conversation

@zentron

@zentron zentron commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Background

Terraform's integration tests lived in Calamari.Terraform.Tests and ran in the default pipeline, but most need a real terraform binary plus network/cloud access — slow, and not reproducible (whichever terraform was installed got used, silently). Some of TerraformCliExecutor/ApplyBehaviour/PlanBehaviour's CLI argument construction and output parsing also had no unit coverage.

Results

  • New Calamari.ExternalTools.Tests project: reusable tool-manifest/resolver/downloader infrastructure, [Category("ExternalTool")]. ExternalToolFixture resolves via env var pin → CALAMARI_TOOL_SKIP_DOWNLOAD=true (PATH-only) → download the manifest's pinned version by default.
  • Terraform's integration tests moved there. Calamari.Terraform.Tests trimmed to pure-unit fixtures, stays in the default pipeline.
  • New unit tests: -get-plugins version gate, "untested version" warning, env-var JSON parsing, ApplyBehaviour's apply-arg construction and output/sensitivity parsing, PlanBehaviour's detailed-exit-code handling — all against a mocked ICommandLineRunner, no real terraform involved.
  • TerraformCommandsFixture trimmed further: WithOutputSensitiveVariables and PlanDetailedExitCode removed now that the new mocked tests cover the same Calamari-side logic. Left: one e2e smoke test, 2 wiring tests, 3 cloud tests.
  • Build: Calamari.ExternalTools.Tests is now published/zipped by PackageCalamariProjects (no signing, same as other .Tests projects) so a build breakage is caught on every commit. New TestCalamariExternalTools Nuke target runs it — same shape as TestCalamariFlavourProject, filtered to TestCategory=ExternalTool — meant to be invoked as its own nightly/on-demand TeamCity build, not part of BuildCi.

Testing

dotnet build: clean, including the Nuke build project itself (dotnet build build/_build.csproj) and ./build.sh --help confirms the new target is registered. Calamari.Terraform.Tests: 34/34. New infra unit tests: 12/12. TerraformCommandsFixture non-cloud: pass; cloud ones need credentials this environment lacks; AWS is [Ignore]d (S3 ACL issue).

How to review

Most of the diff is git mv. Real logic is in Infrastructure/*.cs (5 small files), the new/updated tests in TerraformCliExecutorFixture.cs/ApplyBehaviourFixture.cs/PlanBehaviourFixture.cs, and the two build/*.cs changes for the nightly test target. Calamari.sln has unrelated indentation noise from dotnet sln add.

Caveats

TestCalamariExternalTools isn't wired into any TeamCity build config yet — that's a follow-up (needs a config that runs on a schedule and knows to fetch the new Calamari.ExternalTools.Tests.zip artifact into CalamariTests/, mirroring how TestCalamariFlavourProject consumes each flavour's zip).

⚠️ Does this change require a corresponding Server Change?
No.

@zentron zentron changed the title Robe/external tool test separation Separate Terraform's tool-dependent tests into a new Calamari.ExternalTools.Tests project Aug 6, 2026

@zentron zentron left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a few pointers on things worth knowing before/while reviewing.

Comment thread source/Calamari.sln
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Calamari.DockerCredentialHelper", "Calamari.DockerCredentialHelper\Calamari.DockerCredentialHelper.csproj", "{B34DBEEC-7AC2-4BFE-ACDD-1788828925BD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Calamari.ExternalTools.Tests", "Calamari.ExternalTools.Tests\Calamari.ExternalTools.Tests.csproj", "{028A8E26-0E75-48C2-A639-2FE0D8B45DEE}"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch was rebased onto a main-line commit that separately removed Calamari.AiAgent/AiAgent.Tests (consolidated into core Calamari, #2073). The rebase's text-merge briefly resurrected the AiAgent.Tests entries as an artifact; they're cleaned up in a later commit on this branch. This file should now only differ from main by this one added entry plus unrelated indentation reflow further down.

Comment on lines +103 to +119
static void AddExecutePermission(string exePath)
{
if (CalamariEnvironment.IsRunningOnWindows || string.IsNullOrEmpty(exePath))
return;

SilentProcessRunner.ExecuteCommand(
"chmod", $"+x {exePath}",
Path.GetDirectoryName(exePath) ?? ".",
_ => { }, _ => { });
}

static HttpClient CreateHttpClient()
{
var client = new HttpClient();
client.DefaultRequestHeaders.UserAgent.ParseAdd(
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36");
return client;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Known gaps carried over from the old fixture, not fixed here: chmod's exit code isn't checked (a failure surfaces later as a confusing permission error), and there's no checksum verification against HashiCorp's published SHA256SUMS before extracting/executing the archive. Worth closing before more tools lean on this shared code — scoping it out of this PR to keep the diff focused.

return null;
}

public static string? GetInstalledVersion(string executablePath, string versionArg = "--version")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returns the raw multi-line --version banner, not a clean semver string. Currently harmless — the only consumer (CopyAllFiles's version-named-subfolder lookup) just no-ops when nothing matches — but if a future tool's tests rely on this for real branching, it'll need --version --json plus parsing.

@gitguardian

gitguardian Bot commented Aug 6, 2026

Copy link
Copy Markdown

️✅ There are no secrets present in this pull request anymore.

If these secrets were true positive and are still valid, we highly recommend you to revoke them.
While these secrets were previously flagged, we no longer have a reference to the
specific commits where they were detected. Once a secret has been leaked into a git
repository, you should consider it compromised, even if it was deleted immediately.
Find here more information about risks.


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@zentron
zentron marked this pull request as ready for review August 7, 2026 01:02
@zentron
zentron marked this pull request as draft August 7, 2026 01:02
zentron added 16 commits August 10, 2026 19:56
Reimplements the custom tooling mechanism from feature/external-tool-test-separation
against current main, scoped to infrastructure + Terraform. Supersedes the in-place
categorisation branches as the canonical approach.
…rraform)

Task-by-task TDD plan reimplementing the tool-manifest/resolver/downloader
mechanism against current main, plus the Terraform migration. Adds real
unit coverage to the existing Calamari.Terraform.Tests fixtures (which
already have internals access) instead of duplicating logic elsewhere.
Rebasing onto main (which removed Calamari.AiAgent/.Tests as part of
the Claude consolidation, #2073) reintroduced the AiAgent.Tests sln
entries as a side effect of the original malformed-EndProject fix in
Task 1. The project no longer exists on disk; drop the dangling entry.
Closes a final-review finding: ExternalToolFixture checked PATH
unconditionally before downloading, so any tool found on PATH silently
won over the manifest-pinned version regardless of
CALAMARI_TOOL_SKIP_DOWNLOAD, defeating reproducibility. PATH is now
only consulted when the flag is set, and downloading is the default;
if the flag is set but the tool isn't on PATH, resolution now fails
loudly instead of falling through to download.
Neither had unit coverage of their Execute() logic - apply arg
construction, output-JSON parsing and sensitivity flagging, and the
plan detailed-exit-code handling. Mocks ICommandLineRunner the same
way TerraformCliExecutorFixture does, so no real terraform binary is
needed.
…ests

WithOutputSensitiveVariables and PlanDetailedExitCode only exercised
Calamari's own output/exit-code handling, not terraform's behaviour -
that's now covered by ApplyBehaviourFixture/PlanBehaviourFixture
without needing a real terraform binary.
…rget

Folds it into the existing publish/zip pipeline (no signing, same as
other .Tests projects) so a build breakage is caught on every commit
instead of only when the nightly TestCalamariExternalTools run
happens to build it. That new target follows TestCalamariFlavourProject's
shape - points at the pre-built CalamariTests/ dll, filtered to
TestCategory=ExternalTool.
@zentron
zentron force-pushed the robe/external-tool-test-separation branch from 61aeda1 to d888df6 Compare August 10, 2026 09:56
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