Browse code

internal/testutil/daemon.New: cleanup test paths

remove special characters (like double or single quotes) from name to prevent
creating filesystem paths that can cause errors when parsing through scripts.

Test-names (t.Name()) for sub-tests allow free-form names to be used, for example:

t.Run("engine restart shouldn't kill alive containers", func(t *testing.T) {

Which would result in the quote (') to be included in the filename,
which is valid, but can cause issues when processing with shell scripts;

find bundles -path '*/root/*overlay2' -prune -o -type f \( -name '*-report.json' -o -name '*.log' -o -name '*.out' -o -name '*.prof' -o -name '*-report.xml' \) -print | xargs sudo tar -czf /tmp/reports.tar.gz
[...]
xargs: unmatched single quote; by default quotes are special to xargs unless you use the -0 option

This patch adds a `sanitizedTestName` utility that replaces special characters
with an underscore to prevent such issues.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2026/04/05 18:43:40
Showing 2 changed files
... ...
@@ -218,7 +218,7 @@ func New(t testing.TB, ops ...Option) *Daemon {
218 218
 		dest = os.Getenv("DEST")
219 219
 	}
220 220
 	assert.Assert(t, dest != "", "Please set the DOCKER_INTEGRATION_DAEMON_DEST or the DEST environment variable")
221
-	dest = filepath.Join(dest, t.Name())
221
+	dest = filepath.Join(dest, sanitizedTestName(t))
222 222
 
223 223
 	if os.Getenv("DOCKER_ROOTLESS") != "" {
224 224
 		if os.Getenv("DOCKER_REMAP_ROOT") != "" {
... ...
@@ -242,6 +242,53 @@ func New(t testing.TB, ops ...Option) *Daemon {
242 242
 	return d
243 243
 }
244 244
 
245
+// sanitizedTestName removes special characters (like double or single quotes)
246
+// from name to prevent creating filesystem paths that can cause
247
+// errors when parsing through scripts.
248
+//
249
+// Test-names (t.Name()) for sub-tests allow free-form names to be used,
250
+// for example:
251
+//
252
+//	t.Run("engine restart shouldn't kill alive containers", func(t *testing.T) {
253
+//
254
+// Which would result in the quote (') to be included in the filename,
255
+// which is valid, but can cause issues when processing with shell scripts;
256
+//
257
+//	find bundles -path '*/root/*overlay2' -prune -o -type f \( -name '*-report.json' -o -name '*.log' -o -name '*.out' -o -name '*.prof' -o -name '*-report.xml' \) -print | xargs sudo tar -czf /tmp/reports.tar.gz
258
+//	[...]
259
+//	xargs: unmatched single quote; by default quotes are special to xargs unless you use the -0 option
260
+//
261
+// sanitizedTestName replaces special characters with an underscore to prevent
262
+// such issues.
263
+func sanitizedTestName(t testing.TB) string {
264
+	t.Helper()
265
+	parts := strings.Split(t.Name(), "/")
266
+	for i := range parts {
267
+		parts[i] = sanitizePathComponent(parts[i])
268
+	}
269
+	return filepath.Join(parts...)
270
+}
271
+
272
+func sanitizePathComponent(name string) string {
273
+	var b strings.Builder
274
+	for _, r := range name {
275
+		if (r >= 'a' && r <= 'z') ||
276
+			(r >= 'A' && r <= 'Z') ||
277
+			(r >= '0' && r <= '9') ||
278
+			r == '-' || r == '_' {
279
+			b.WriteRune(r)
280
+		} else {
281
+			b.WriteByte('_')
282
+		}
283
+	}
284
+
285
+	out := strings.TrimLeft(strings.TrimSpace(b.String()), "-")
286
+	if out == "" {
287
+		return "_"
288
+	}
289
+	return out
290
+}
291
+
245 292
 // BinaryPath returns the binary and its arguments.
246 293
 func (d *Daemon) BinaryPath() (string, error) {
247 294
 	dockerdBinary, err := exec.LookPath(d.dockerdBinary)
248 295
new file mode 100644
... ...
@@ -0,0 +1,93 @@
0
+package daemon
1
+
2
+import (
3
+	"path/filepath"
4
+	"testing"
5
+)
6
+
7
+// regression test for https://github.com/moby/moby/issues/52300
8
+func TestSanitizeTestName(t *testing.T) {
9
+	tests := []struct {
10
+		name string
11
+		want string
12
+	}{
13
+		{
14
+			name: "shouldn't do something",
15
+			want: "TestSanitizeTestName/shouldn_t_do_something",
16
+		},
17
+		{
18
+			name: `double "quotes"`,
19
+			want: "TestSanitizeTestName/double__quotes_",
20
+		},
21
+		{
22
+			name: "contains spaces",
23
+			want: "TestSanitizeTestName/contains_spaces",
24
+		},
25
+		{
26
+			name: "contains ..dots",
27
+			want: "TestSanitizeTestName/contains___dots",
28
+		},
29
+		{
30
+			name: "..starts-with-dots",
31
+			want: "TestSanitizeTestName/__starts-with-dots",
32
+		},
33
+		{
34
+			name: ".starts-with-dot",
35
+			want: "TestSanitizeTestName/_starts-with-dot",
36
+		},
37
+		{
38
+			name: "ends-with-dot.",
39
+			want: "TestSanitizeTestName/ends-with-dot_",
40
+		},
41
+		{
42
+			name: "--starts-with-dash",
43
+			want: "TestSanitizeTestName/starts-with-dash",
44
+		},
45
+		{
46
+			name: "_starts-with-underscore",
47
+			want: "TestSanitizeTestName/_starts-with-underscore",
48
+		},
49
+		{
50
+			name: "ends-with-dash-",
51
+			want: "TestSanitizeTestName/ends-with-dash-",
52
+		},
53
+		{
54
+			name: "foo/bar",
55
+			want: "TestSanitizeTestName/foo/bar",
56
+		},
57
+		{
58
+			name: `foo/"bar"`,
59
+			want: "TestSanitizeTestName/foo/_bar_",
60
+		},
61
+		{
62
+			name: "foo/..bar",
63
+			want: "TestSanitizeTestName/foo/__bar",
64
+		},
65
+		{
66
+			name: "../foo",
67
+			want: "TestSanitizeTestName/__/foo",
68
+		},
69
+		{
70
+			name: "foo/..",
71
+			want: "TestSanitizeTestName/foo/__",
72
+		},
73
+		{
74
+			name: "'",
75
+			want: "TestSanitizeTestName/_",
76
+		},
77
+		{
78
+			name: "...",
79
+			want: "TestSanitizeTestName/___",
80
+		},
81
+	}
82
+
83
+	for _, tc := range tests {
84
+		t.Run(tc.name, func(t *testing.T) {
85
+			out := sanitizedTestName(t)
86
+			want := filepath.FromSlash(tc.want) // let's be nice to Windows.
87
+			if out != want {
88
+				t.Errorf("got %q, want %q", out, want)
89
+			}
90
+		})
91
+	}
92
+}