Skip to content

Commit d81fcfd

Browse files
committed
test: add artifacts-many-paths to validate ARG_MAX fix
- Add test case with 9000 files of 255-char names (~2.3 MB total path size) to verify rsync --files-from handles large artifact sets that would exceed ARG_MAX (~2 MB) as CLI arguments - Assert "exported artifacts" in stdout to catch silent rsync failures hidden by || true - Assert no FAIL in stderr to ensure consume-artifacts receives all 9000 files via artifact transfer - Requires --shell-isolation so artifact transfer is exercised rather than the shared cwd being used directly
1 parent c5c2056 commit d81fcfd

3 files changed

Lines changed: 54 additions & 0 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/artifact_*
2+
/.gcl-many-paths/
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
# Creates 9000 files whose names are 255 characters long.
3+
# With the old approach (paths passed as rsync arguments), the total argument
4+
# size (~2.3 MB) exceeded ARG_MAX (~2 MB) and rsync failed silently.
5+
# With the new approach (--files-from + bash for loop), there is no limit on
6+
# the number of files.
7+
produce-artifacts:
8+
stage: build
9+
script:
10+
- |
11+
python3 -c "
12+
import os
13+
padding = 'x' * 240
14+
for i in range(9000):
15+
name = f'artifact_{i:05d}_{padding}'
16+
fd = os.open(name, os.O_CREAT | os.O_WRONLY, 0o644)
17+
os.close(fd)
18+
"
19+
artifacts:
20+
paths:
21+
- artifact_*
22+
23+
consume-artifacts:
24+
stage: test
25+
script:
26+
- count=$(find . -maxdepth 1 -name 'artifact_*' | wc -l)
27+
- if [ "$count" -ne 9000 ]; then echo "FAIL expected 9000 artifacts, got $count"; exit 1; fi
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import {WriteStreamsMock} from "../../../src/write-streams.js";
2+
import {handler} from "../../../src/handler.js";
3+
import {initSpawnSpy} from "../../mocks/utils.mock.js";
4+
import {WhenStatics} from "../../mocks/when-statics.js";
5+
6+
beforeAll(() => {
7+
initSpawnSpy(WhenStatics.all);
8+
});
9+
10+
// Validates that --files-from works correctly when the number of artifact files
11+
// would exceed ARG_MAX if their paths were passed directly as rsync arguments
12+
// (9000 files × ~257 bytes ≈ 2.3 MB > ARG_MAX ~2 MB).
13+
test.concurrent("artifacts-many-paths --shell-isolation 9000 artifacts via --files-from", async () => {
14+
const writeStreams = new WriteStreamsMock();
15+
await handler({
16+
cwd: "tests/test-cases/artifacts-many-paths",
17+
shellIsolation: true,
18+
stateDir: ".gcl-many-paths",
19+
}, writeStreams);
20+
21+
// Ensures artifacts were actually exported (not silently swallowed by || true).
22+
expect(writeStreams.stdoutLines.join("\n")).toMatch(/produce-artifacts.*exported artifacts/);
23+
// Ensures consume-artifacts found all 9000 expected files.
24+
expect(writeStreams.stderrLines.join("\n")).not.toMatch(/FAIL/);
25+
}, 120_000);

0 commit comments

Comments
 (0)