ESLEngineering Software Lab
ESL — Engineering Software Lab
AI-Assisted Software Engineering · Code Diff Review

Terratest × Ampcode — The Diff

Every change Amp made to Gruntwork Terratest, reviewed line by line — the correctness fix, the CVE dependency bumps, and the local performance optimizations, shown as real git diffs against upstream.

Prepared by

ESL — Engineering Software Lab

Software Engineering Consultants

eswlab.com/contact-us →
Daniel Liezrowice
Author

Daniel Liezrowice

LinkedIn →
What changed

Three groups of changes

All diffs are measured against the original upstream origin/main (gruntwork-io/terratest). Two groups were submitted as focused PRs; the performance hoists were kept local to avoid flooding maintainers.

PR #1860

Dependency CVEs

go.mod · go.sum — security-only version bumps

committed · submitted
PR #1861

UniqueID fix

modules/random/random.go — correctness bug

committed · submitted
LOCAL

Perf hoists

4 files — regex compiled once + CI pin

local only · not submitted
7
Files changed
+46
Lines added
−45
Lines removed
Branch topology

How the changes are organized

origin/mainfix/dependency-cves · PR #1860
origin/mainfix/unique-id-collisions · PR #1861
working tree4 local perf files · uncommitted
GroupFiles+ / −Status
PR #1860go.mod, go.sum+27 / −27submitted
PR #1861modules/random/random.go+2 / −9submitted
Localpacker, parser, docker, CI+17 / −9not submitted
PR #1861 · the problem

UniqueID() could return duplicates

Root cause

  • Every call built a new RNG seeded with time.Now().UnixNano()
  • Calls within the same nanosecond received the same seed
  • → identical “unique” IDs under fast or parallel use

Why it matters in Terratest

UniqueID() namespaces real cloud resources. Collisions mean two parallel tests can target the same resource name — causing flaky failures or cross-test interference on live infrastructure.

PR #1861 · the fix

Use the auto-seeded global rand

diffmodules/random/random.go
@@ -4,12 +4,11 @@ package random import ( "bytes" "math/rand"- "time" ) // Random generates a random int between min and max, inclusive. func Random(min int, max int) int {- return newRand().Intn(max-min+1) + min+ return rand.Intn(max-min+1) + min } // RandomInt picks a random element in the slice of ints.@@ -33,9 +32,8 @@ const uniqueIDLength = 6 // Should be good for 62^6 = 56+ billion combinations func UniqueID() string { var out bytes.Buffer - generator := newRand() for i := 0; i < uniqueIDLength; i++ {- out.WriteByte(base62chars[generator.Intn(len(base62chars))])+ out.WriteByte(base62chars[rand.Intn(len(base62chars))]) } return out.String()@@ -49,8 +47,3 @@ func UniqueID() string { func UniqueId() string { return UniqueID() }--// newRand creates a new random number generator, seeding it with the current system time.-func newRand() *rand.Rand {- return rand.New(rand.NewSource(time.Now().UnixNano()))-}

Why it's correct

  • Go 1.20+ auto-seeds the global rand — manual seeding is unnecessary
  • The global rand top-level functions are goroutine-safe
  • Removes the newRand() helper and the time import
  • +2 / −9 lines · public API unchanged
PR #1860 · dependency CVEs

Security-only version bumps

diffgo.mod
@@ -27,8 +27,8 @@ require ( github.com/tmccombs/hcl2json v0.6.4 github.com/urfave/cli v1.22.16 github.com/zclconf/go-cty v1.15.0- golang.org/x/crypto v0.49.0- golang.org/x/net v0.52.0 // indirect+ golang.org/x/crypto v0.52.0+ golang.org/x/net v0.55.0 // indirect golang.org/x/oauth2 v0.36.0 google.golang.org/api v0.276.0 google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect@@ -96,7 +96,7 @@ require ( github.com/gonvenience/ytbx v1.4.4 github.com/hashicorp/go-getter/v2 v2.2.3 github.com/homeport/dyff v1.6.0- github.com/jackc/pgx/v5 v5.9.0+ github.com/jackc/pgx/v5 v5.9.2 github.com/lib/pq v1.10.9 github.com/microsoft/go-mssqldb v1.9.8 golang.org/x/sync v0.20.0@@ -205,7 +205,7 @@ require ( github.com/spf13/pflag v1.0.9 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect github.com/texttheater/golang-levenshtein v1.0.1 // indirect- github.com/ulikunitz/xz v0.5.10 // indirect+ github.com/ulikunitz/xz v0.5.15 // indirect github.com/vbatts/tar-split v0.11.3 // indirect github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 // indirect github.com/x448/float16 v0.8.4 // indirect@@ -222,12 +222,12 @@ require ( go.opentelemetry.io/otel/trace v1.43.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect- golang.org/x/mod v0.33.0 // indirect- golang.org/x/sys v0.42.0 // indirect- golang.org/x/term v0.41.0 // indirect- golang.org/x/text v0.35.0 // indirect+ golang.org/x/mod v0.35.0 // indirect+ golang.org/x/sys v0.45.0 // indirect+ golang.org/x/term v0.43.0 // indirect+ golang.org/x/text v0.37.0 // indirect golang.org/x/time v0.15.0 // indirect- golang.org/x/tools v0.42.0 // indirect+ golang.org/x/tools v0.44.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
23
Known CVEs before
go get · go mod tidy
0
CVEs after · Trivy & Grype
Local optimization · the pattern

Compile regexes once, not per call

Before · per call
func F(s string) string { re := regexp.MustCompile(`…`) return re.FindString(s)}

Recompiles the pattern on every call — wasted CPU and allocations on the hot path.

After · package load
var reF = regexp.MustCompile(`…`) func F(s string) string { return reF.FindString(s)}

Compiled once at package load; the hot path only matches.

Applied to packer.go, parser.go and docker_compose.go.

Local optimization · file 1

modules/packer/packer.go

diffExtractArtifactID · TrimPackerVersion
@@ -24,6 +24,13 @@ import ( // ErrArtifactIDNotFound is returned when the Packer output does not contain an artifact ID. var ErrArtifactIDNotFound = errors.New("could not find artifact ID pattern in packer output") +// artifactIDRegexp and packerVersionRegexp are compiled once at package load instead of on every+// call to ExtractArtifactID / TrimPackerVersion.+var (+ artifactIDRegexp = regexp.MustCompile(`.+artifact,\d+?,id,(?:.+?:|)(.+)`)+ packerVersionRegexp = regexp.MustCompile(`(?:Packer v?|)(\d+\.\d+\.\d+)`)+)+ // BuildNameNotFoundError is returned when the specified build name is not found in the manifest file. type BuildNameNotFoundError struct { BuildName string@@ -239,8 +246,7 @@ const artifactIDMatchLen = 2 // 1456332887,amazon-ebs,artifact,0,id,us-east-1:ami-b481b3de // 1533742764,googlecompute,artifact,0,id,terratest-packer-example-2018-08-08t15-35-19z func ExtractArtifactID(packerLogOutput string) (string, error) {- re := regexp.MustCompile(`.+artifact,\d+?,id,(?:.+?:|)(.+)`)- matches := re.FindStringSubmatch(packerLogOutput)+ matches := artifactIDRegexp.FindStringSubmatch(packerLogOutput) if len(matches) == artifactIDMatchLen { return matches[1], nil@@ -363,8 +369,7 @@ func FormatPackerArgs(options *Options) []string { // TrimPackerVersion extracts the version number from packer version output. // From packer 1.10 the -version command output is prefixed with "Packer v". func TrimPackerVersion(versionCmdOutput string) string {- re := regexp.MustCompile(`(?:Packer v?|)(\d+\.\d+\.\d+)`)- matches := re.FindStringSubmatch(versionCmdOutput)+ matches := packerVersionRegexp.FindStringSubmatch(versionCmdOutput) if len(matches) > 1 { return matches[1]
Local optimization · files 2 & 3

parser.go & docker_compose.go

logger/parser/parser.go
@@ -56,6 +56,7 @@ var ( regexStatus = regexp.MustCompile(`=== (RUN|PAUSE|CONT)\s+(.+)`) regexSummary = regexp.MustCompile(`(^FAIL$)|(^(ok|FAIL)\s+([^ ]+)\s+(?:(\d+\.\d+)s|\(cached\)|(\[\w+ failed]))(?:\s+coverage:\s+(\d+\.\d+)%\sof\sstatements(?:\sin\s.+)?)?$)`) regexPanic = regexp.MustCompile(`^panic:`)+ regexIndent = regexp.MustCompile(`^\s+`) ) // GetIndent takes a line and returns the indent string@@ -64,9 +65,7 @@ var ( // in: " --- FAIL: TestSnafu" // out: " " func GetIndent(data string) string {- re := regexp.MustCompile(`^\s+`)-- return re.FindString(data)+ return regexIndent.FindString(data) } // GetTestNameFromResultLine takes a go testing result line and extracts out the test name
docker/docker_compose.go
@@ -119,10 +119,14 @@ func runDockerComposeE(t testing.TestingT, ctx context.Context, stdout bool, opt return shell.RunCommandContextAndGetOutputE(t, ctx, cmd) } +// invalidDockerComposeProjectNameChars matches characters that are not allowed in a docker-compose+// project name. Compiled once at package load instead of on every call.+var invalidDockerComposeProjectNameChars = regexp.MustCompile(`[^a-zA-Z0-9 ]+`)+ // generateValidDockerComposeProjectName generates a valid project name for docker-compose. // Note: docker-compose command doesn't like lower case or special characters, other than -. func generateValidDockerComposeProjectName(str string) string { lowerStr := strings.ToLower(str) - return regexp.MustCompile(`[^a-zA-Z0-9 ]+`).ReplaceAllString(lowerStr, "-")+ return invalidDockerComposeProjectNameChars.ReplaceAllString(lowerStr, "-") }
Local optimization · CI hardening

Pin the release action

.github/workflows/build-and-release.yml
@@ -83,7 +83,7 @@ jobs: - uses: actions/checkout@v4 - name: Download all build artifacts- uses: actions/download-artifact@v4+ uses: actions/download-artifact@v4.1.3 with: path: cmd/bin/ pattern: binaries-*

Why pin it

  • A floating @v4 tag can silently change under you
  • Pinning @v4.1.3 makes CI builds reproducible
  • Standard supply-chain hygiene for third-party actions
Performance · latency

Nanoseconds per operation

before afterlower is better · scaled to max (9,025 ns)
GetIndent
before
921.5
after
118.3
ExtractArtifactID
before
5,610
after
1,172
TrimPackerVersion
before
4,581
after
185.9
UniqueID
before
9,025
after
85.1
Summary

Every file, every line

FileGroupAddedRemovedStatus
go.modPR #1860+9−9submitted
go.sumPR #1860+18−18submitted
modules/random/random.goPR #1861+2−9submitted
.github/workflows/build-and-release.ymlLocal+1−1not submitted
modules/docker/docker_compose.goLocal+5−1not submitted
modules/logger/parser/parser.goLocal+2−3not submitted
modules/packer/packer.goLocal+9−4not submitted
Total7 files+46−45
References

The two upstream pull requests

PR #1860

Dependency CVE bumps

github.com/…/pull/1860 →

PR #1861

UniqueID collision fix

github.com/…/pull/1861 →
github.com/gruntwork-io/terratest →
Daniel Liezrowice
Prepared by

Daniel Liezrowice · ESL

LinkedIn  ·  eswlab.com/contact-us
1 / 13
← Swipe to navigate →