Skip to content

Commit 7b59cab

Browse files
committed
Update instrumentor to better handle nested modules
1 parent dff25ac commit 7b59cab

22 files changed

Lines changed: 457 additions & 44 deletions

File tree

CHANGELOG.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
# Changelog
22

3+
## 0.7.2 - 2026-05-13
4+
5+
Fix `PathFromBaseDirectory` mispathing multi-level submodules. Its
6+
`filepath.Match` pattern was `baseDir/*`, which does not cross path
7+
separators — so any customer submodule nested two or more levels deep
8+
had its modified `go.mod` written to `customer/<input-abspath>/...`
9+
instead of `customer/<rel-path>/...`. Now uses `filepath.Rel`. Affected
10+
any non-trivial Go monorepo (etcd, k8s, etc.) since the instrumented
11+
output of deep submodules was non-functional. (ENG-3940)
12+
13+
## 0.7.1 - 2026-05-13
14+
15+
Fix the instrumentor leaking the host's Go version into the notifier
16+
module's `go.mod`, which caused `go mod tidy` to bump every customer
17+
module's `go` directive (and drop `toolchain`) just because it now
18+
required the notifier. The notifier's `go` directive is now pinned to
19+
the minimum across the customer modules the instrumentor touches, and
20+
`toolchain` is omitted.
21+
322
## 0.7.0 - 2026-03-20
423

524
Fix assertion cataloging for Go modules that produce multiple binaries.
@@ -57,4 +76,3 @@ Improvements to assertion cataloging and to documentation.
5776
## 0.3.5 - 2024-05-02
5877

5978
Fixing a bug where instrumentor `cp` didn't work on MacOS.
60-

assert/assert.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//
77
// These functions are no-ops with minimal performance overhead when called outside of the Antithesis environment. However, if the environment variable ANTITHESIS_SDK_LOCAL_OUTPUT is set, these functions will log to the file pointed to by that variable using a structured JSON format defined [here]. This allows you to make use of the Antithesis assertions package in your regular testing, or even in production. In particular, very few assertions frameworks offer a convenient way to define [Sometimes assertions], but they can be quite useful even outside Antithesis.
88
//
9-
// Each function in this package takes a parameter called message, which is a human readable identifier used to aggregate assertions. Antithesis generates one test property per unique message and this test property will be named "<message>" in the [triage report].
9+
// Each function in this package takes a parameter called message, which is a human readable identifier used to aggregate assertions. Antithesis generates one test property per unique message and this test property will be named "<message>" in the [triage report]. Message must be provided as a string literal.
1010
//
1111
// This test property either passes or fails, which depends upon the evaluation of every assertion that shares its message. Different assertions in different parts of the code should have different message, but the same assertion should always have the same message even if it is moved to a different file.
1212
//

internal/emit.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type libHandler interface {
3838
}
3939

4040
const (
41-
errorLogLinePrefix = "[* antithesis-sdk-go *]"
41+
errorLogLinePrefix = "[* antithesis-sdk-go *]"
4242
)
4343

4444
var handler libHandler

internal/sdk_const.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package internal
33
// --------------------------------------------------------------------------------
44
// Versions
55
// --------------------------------------------------------------------------------
6-
const SDK_Version = "0.7.0"
6+
const SDK_Version = "0.7.2"
77
const Protocol_Version = "1.1.0"
88

99
// --------------------------------------------------------------------------------

internal/voidstar_handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ package internal
44

55
import (
66
"fmt"
7-
"unsafe"
87
"os"
8+
"unsafe"
99
)
1010

1111
// --------------------------------------------------------------------------------

tools/antithesis-go-instrumentor/args/args.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,20 @@ import (
1313

1414
// Args holds the parsed command-line arguments.
1515
type Args struct {
16-
ExcludeFile string
17-
SymPrefix string
18-
InputDir string
19-
OutputDir string
16+
ExcludeFile string
17+
SymPrefix string
18+
InputDir string
19+
OutputDir string
2020
InstrumentorVersion string
21-
LocalSDKPath string
22-
VersionText string
23-
LogFile string
24-
VerbosityLevel common.Verbosity
25-
ShowVersion bool
26-
InvalidArgs bool
27-
WantsInstrumentor bool
28-
SkipTestFiles bool
29-
SkipProtoBufFiles bool
21+
LocalSDKPath string
22+
VersionText string
23+
LogFile string
24+
VerbosityLevel common.Verbosity
25+
ShowVersion bool
26+
InvalidArgs bool
27+
WantsInstrumentor bool
28+
SkipTestFiles bool
29+
SkipProtoBufFiles bool
3030
}
3131

3232
func ParseArgs(versionText string, thisVersion string) *Args {

tools/antithesis-go-instrumentor/common/files.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ import (
1313
)
1414

1515
const (
16-
HashBitsUsed = 48
17-
HashBytesUsed = HashBitsUsed / 8
18-
EncodedHashByteLength = HashBytesUsed * 2
16+
HashBitsUsed = 48
17+
HashBytesUsed = HashBitsUsed / 8
18+
EncodedHashByteLength = HashBytesUsed * 2
19+
NotifierMinimumGoVersion = "1.24.0"
1920
)
2021

2122
// HashFileContent reads the binary content of
@@ -289,9 +290,10 @@ func NotifierDependencies(notifierOutputDirectory, notifierModuleName, instrumen
289290
ANTITHESIS_SDK_MODULE, ANTITHESIS_SDK_MODULE, localSDKPath)
290291
}
291292

292-
commandLine := fmt.Sprintf("(cd %s; go mod init %s; %s; go mod tidy)",
293+
commandLine := fmt.Sprintf("(cd %s; go mod init %s; go mod edit -go=%s; go mod edit -toolchain=none; %s; go mod tidy; go mod edit -toolchain=none)",
293294
notifierOutputDirectory,
294295
notifierModuleName,
296+
NotifierMinimumGoVersion,
295297
dependencyRef)
296298

297299
cmd := exec.Command("bash", "-c", commandLine)
@@ -373,26 +375,24 @@ func ValidateDirectories(input, output string) (err error) {
373375
return
374376
}
375377

376-
// PathFromBaseDirectory gets the path of someDir relative to baseDir
378+
// PathFromBaseDirectory returns the path of someDir relative to baseDir.
379+
// Returns "" when baseDir == someDir, or when someDir is not under baseDir
380+
// (the caller in scanners/coverage/config uses an empty result to skip a
381+
// dependent module — see ENG-3940 for the history).
377382
//
378383
// Example:
379-
// PathFromBaseDirectory("/home/ricky/etcd", "/home/ricky/etcd/server/test")
380384
//
385+
// PathFromBaseDirectory("/home/ricky/etcd", "/home/ricky/etcd/server/test")
381386
// ==> "server/test"
382387
func PathFromBaseDirectory(baseDir, someDir string) string {
383388
baseNorm := CanonicalizeDirectory(baseDir)
384389
someNorm := CanonicalizeDirectory(someDir)
385390
if baseNorm == someNorm {
386391
return ""
387392
}
388-
someOffset := someNorm
389-
pattern := filepath.Join(baseNorm, "*")
390-
if didMatch, _ := filepath.Match(pattern, someNorm); didMatch {
391-
lx := len(baseNorm)
392-
idx := lx + 1
393-
if idx < len(someNorm) {
394-
someOffset = someNorm[idx:]
395-
}
393+
rel, err := filepath.Rel(baseNorm, someNorm)
394+
if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
395+
return ""
396396
}
397-
return someOffset
397+
return rel
398398
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package common
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
)
8+
9+
func TestPathFromBaseDirectory(t *testing.T) {
10+
// Use a temp dir so CanonicalizeDirectory's filepath.EvalSymlinks
11+
// has real paths to walk. Build a small tree:
12+
// <tmp>/repo/
13+
// <tmp>/repo/server/
14+
// <tmp>/repo/server/test/
15+
// <tmp>/repo/tools/inner/leaf/
16+
// <tmp>/other/ (sibling, not under repo)
17+
tmp := t.TempDir()
18+
repo := filepath.Join(tmp, "repo")
19+
for _, d := range []string{
20+
filepath.Join(repo, "server", "test"),
21+
filepath.Join(repo, "tools", "inner", "leaf"),
22+
filepath.Join(tmp, "other"),
23+
} {
24+
if err := os.MkdirAll(d, 0755); err != nil {
25+
t.Fatal(err)
26+
}
27+
}
28+
29+
tests := []struct {
30+
name string
31+
base string
32+
some string
33+
wantRel string
34+
}{
35+
{
36+
name: "same_dir_returns_empty",
37+
base: repo,
38+
some: repo,
39+
wantRel: "",
40+
},
41+
{
42+
name: "direct_child",
43+
base: repo,
44+
some: filepath.Join(repo, "server"),
45+
wantRel: "server",
46+
},
47+
{
48+
name: "two_level_child",
49+
base: repo,
50+
some: filepath.Join(repo, "server", "test"),
51+
wantRel: filepath.Join("server", "test"),
52+
},
53+
{
54+
name: "three_level_child",
55+
base: repo,
56+
some: filepath.Join(repo, "tools", "inner", "leaf"),
57+
wantRel: filepath.Join("tools", "inner", "leaf"),
58+
},
59+
{
60+
name: "not_a_child_returns_empty",
61+
base: repo,
62+
some: filepath.Join(tmp, "other"),
63+
wantRel: "",
64+
},
65+
{
66+
name: "trailing_slash_on_base_is_normalized",
67+
base: repo + string(filepath.Separator),
68+
some: filepath.Join(repo, "server", "test"),
69+
wantRel: filepath.Join("server", "test"),
70+
},
71+
}
72+
73+
for _, tc := range tests {
74+
t.Run(tc.name, func(t *testing.T) {
75+
got := PathFromBaseDirectory(tc.base, tc.some)
76+
if got != tc.wantRel {
77+
t.Errorf("PathFromBaseDirectory(%q, %q) = %q, want %q", tc.base, tc.some, got, tc.wantRel)
78+
}
79+
})
80+
}
81+
}
82+
83+
// Regression test for ENG-3940: the previous implementation used
84+
// filepath.Match with a `baseDir/*` pattern, which does not cross path
85+
// separators. Multi-level submodules silently returned the *absolute* path
86+
// of someDir as the "offset", which the caller then joined onto the
87+
// customer output dir producing customer/<input-abspath>/... directories.
88+
func TestPathFromBaseDirectory_DeepSubmodule_NoAbsolutePath(t *testing.T) {
89+
tmp := t.TempDir()
90+
repo := filepath.Join(tmp, "repo")
91+
deep := filepath.Join(repo, "tools", "rw-heatmaps")
92+
if err := os.MkdirAll(deep, 0755); err != nil {
93+
t.Fatal(err)
94+
}
95+
96+
got := PathFromBaseDirectory(repo, deep)
97+
want := filepath.Join("tools", "rw-heatmaps")
98+
if got != want {
99+
t.Fatalf("PathFromBaseDirectory(%q, %q) = %q, want %q (regression: returning the absolute path instead of a relative offset)",
100+
repo, deep, got, want)
101+
}
102+
if filepath.IsAbs(got) {
103+
t.Fatalf("PathFromBaseDirectory returned an absolute path %q for a child under base (this is the ENG-3940 bug)", got)
104+
}
105+
}
106+
107+
func TestPathFromBaseDirectory_Symlink(t *testing.T) {
108+
tmp := t.TempDir()
109+
repo := filepath.Join(tmp, "repo")
110+
real := filepath.Join(repo, "real", "deep")
111+
if err := os.MkdirAll(real, 0755); err != nil {
112+
t.Fatal(err)
113+
}
114+
link := filepath.Join(repo, "alias")
115+
if err := os.Symlink(filepath.Join(repo, "real"), link); err != nil {
116+
t.Skipf("symlink unsupported: %v", err)
117+
}
118+
119+
// Passing the symlinked path should resolve to the real path and
120+
// produce a relative offset against the base.
121+
got := PathFromBaseDirectory(repo, filepath.Join(link, "deep"))
122+
want := filepath.Join("real", "deep")
123+
if got != want {
124+
t.Errorf("PathFromBaseDirectory(%q, %q) = %q, want %q", repo, filepath.Join(link, "deep"), got, want)
125+
}
126+
}

tools/antithesis-go-instrumentor/common/logger.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ type Verbosity int
2020

2121
const (
2222
Normal Verbosity = 0
23-
Info Verbosity = 1
24-
Debug Verbosity = 2
25-
Trace Verbosity = 3
23+
Info Verbosity = 1
24+
Debug Verbosity = 2
25+
Trace Verbosity = 3
2626
)
2727

2828
type LogWriter struct {

0 commit comments

Comments
 (0)