|
1 | 1 | package lib |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bytes" |
4 | 5 | "net" |
5 | 6 | "net/http" |
6 | 7 | "net/http/httptest" |
@@ -1277,3 +1278,134 @@ func writeTempScript(t *testing.T, content string) string { |
1277 | 1278 | } |
1278 | 1279 | return path |
1279 | 1280 | } |
| 1281 | + |
| 1282 | +// withRaidVar sets a raid variable for the duration of the test, restoring |
| 1283 | +// the previous raidVars map on cleanup. |
| 1284 | +func withRaidVar(t *testing.T, key, value string) { |
| 1285 | + t.Helper() |
| 1286 | + raidVarsMu.Lock() |
| 1287 | + prev := raidVars |
| 1288 | + raidVars = make(map[string]string, len(prev)+1) |
| 1289 | + for k, v := range prev { |
| 1290 | + raidVars[k] = v |
| 1291 | + } |
| 1292 | + raidVars[key] = value |
| 1293 | + raidVarsMu.Unlock() |
| 1294 | + t.Cleanup(func() { |
| 1295 | + raidVarsMu.Lock() |
| 1296 | + raidVars = prev |
| 1297 | + raidVarsMu.Unlock() |
| 1298 | + }) |
| 1299 | +} |
| 1300 | + |
| 1301 | +// captureCommandStdout swaps commandStdout for a buffer, returning a getter |
| 1302 | +// that returns the captured text and restores the previous writer on |
| 1303 | +// cleanup. |
| 1304 | +func captureCommandStdout(t *testing.T) func() string { |
| 1305 | + t.Helper() |
| 1306 | + var buf bytes.Buffer |
| 1307 | + prev := commandStdout |
| 1308 | + commandStdout = &buf |
| 1309 | + t.Cleanup(func() { commandStdout = prev }) |
| 1310 | + return func() string { return buf.String() } |
| 1311 | +} |
| 1312 | + |
| 1313 | +// --- Issue #20: Set variables reach Script + Shell subprocess env --------- |
| 1314 | + |
| 1315 | +// TestExecScript_inheritsRaidVar guards the issue-#20 fix: a Set task's |
| 1316 | +// variable must be visible in the env of a subsequent Script task's |
| 1317 | +// subprocess. |
| 1318 | +func TestExecScript_inheritsRaidVar(t *testing.T) { |
| 1319 | + if runtime.GOOS == "windows" { |
| 1320 | + t.Skip("direct .sh execution not supported on Windows") |
| 1321 | + } |
| 1322 | + withRaidVar(t, "ISSUE20_FOO", "from-raid-var") |
| 1323 | + getOut := captureCommandStdout(t) |
| 1324 | + |
| 1325 | + scriptPath := writeTempScript(t, "#!/bin/sh\nprintf 'got=%s' \"$ISSUE20_FOO\"\n") |
| 1326 | + |
| 1327 | + if err := execScript(Task{Type: Script, Path: scriptPath}); err != nil { |
| 1328 | + t.Fatalf("execScript: %v", err) |
| 1329 | + } |
| 1330 | + if got := strings.TrimSpace(getOut()); got != "got=from-raid-var" { |
| 1331 | + t.Errorf("script saw $ISSUE20_FOO = %q, want %q", got, "got=from-raid-var") |
| 1332 | + } |
| 1333 | +} |
| 1334 | + |
| 1335 | +// TestExecScript_raidVarOverridesOSEnv confirms the precedence promised by |
| 1336 | +// expandRaid: raidVars beat OS env when names collide. The fix appends |
| 1337 | +// raidVars last in cmd.Env so exec's last-occurrence-wins rule honors that. |
| 1338 | +func TestExecScript_raidVarOverridesOSEnv(t *testing.T) { |
| 1339 | + if runtime.GOOS == "windows" { |
| 1340 | + t.Skip("direct .sh execution not supported on Windows") |
| 1341 | + } |
| 1342 | + const key = "ISSUE20_OVERRIDE" |
| 1343 | + t.Setenv(key, "from-os") |
| 1344 | + withRaidVar(t, key, "from-raid") |
| 1345 | + getOut := captureCommandStdout(t) |
| 1346 | + |
| 1347 | + scriptPath := writeTempScript(t, "#!/bin/sh\nprintf 'got=%s' \"$ISSUE20_OVERRIDE\"\n") |
| 1348 | + |
| 1349 | + if err := execScript(Task{Type: Script, Path: scriptPath}); err != nil { |
| 1350 | + t.Fatalf("execScript: %v", err) |
| 1351 | + } |
| 1352 | + if got := strings.TrimSpace(getOut()); got != "got=from-raid" { |
| 1353 | + t.Errorf("script saw collision = %q, want raidVar to win (got=from-raid)", got) |
| 1354 | + } |
| 1355 | +} |
| 1356 | + |
| 1357 | +// TestExecShell_passesRaidVarToChildScript ensures the same fix applies to |
| 1358 | +// Shell tasks: a child process spawned by the shell (e.g. another script) |
| 1359 | +// sees the raidVar even though the shell itself didn't pre-expand it. |
| 1360 | +// `literal: true` skips raid's pre-expansion so resolution happens at the |
| 1361 | +// child level instead. |
| 1362 | +func TestExecShell_passesRaidVarToChildScript(t *testing.T) { |
| 1363 | + if runtime.GOOS == "windows" { |
| 1364 | + t.Skip("direct .sh execution not supported on Windows") |
| 1365 | + } |
| 1366 | + withRaidVar(t, "ISSUE20_BAR", "from-raid-bar") |
| 1367 | + getOut := captureCommandStdout(t) |
| 1368 | + |
| 1369 | + dir := t.TempDir() |
| 1370 | + childPath := filepath.Join(dir, "child.sh") |
| 1371 | + if err := os.WriteFile(childPath, []byte("#!/bin/sh\nprintf 'child=%s' \"$ISSUE20_BAR\"\n"), 0755); err != nil { |
| 1372 | + t.Fatalf("write child: %v", err) |
| 1373 | + } |
| 1374 | + |
| 1375 | + task := Task{Type: Shell, Cmd: childPath, Literal: true} |
| 1376 | + if err := execShell(task); err != nil { |
| 1377 | + t.Fatalf("execShell: %v", err) |
| 1378 | + } |
| 1379 | + if got := strings.TrimSpace(getOut()); got != "child=from-raid-bar" { |
| 1380 | + t.Errorf("child saw $ISSUE20_BAR = %q, want %q", got, "child=from-raid-bar") |
| 1381 | + } |
| 1382 | +} |
| 1383 | + |
| 1384 | +// TestBuildSubprocessEnv_orderRaidVarsLast directly exercises the helper to |
| 1385 | +// guard the precedence wiring (raidVars must appear AFTER OS env so exec's |
| 1386 | +// duplicate-key resolution gives them priority). |
| 1387 | +func TestBuildSubprocessEnv_orderRaidVarsLast(t *testing.T) { |
| 1388 | + const key = "ISSUE20_BUILD_ENV" |
| 1389 | + t.Setenv(key, "os-value") |
| 1390 | + withRaidVar(t, key, "raid-value") |
| 1391 | + |
| 1392 | + env := buildSubprocessEnv() |
| 1393 | + osIdx, raidIdx := -1, -1 |
| 1394 | + for i, kv := range env { |
| 1395 | + switch kv { |
| 1396 | + case key + "=os-value": |
| 1397 | + osIdx = i |
| 1398 | + case key + "=raid-value": |
| 1399 | + raidIdx = i |
| 1400 | + } |
| 1401 | + } |
| 1402 | + if osIdx < 0 { |
| 1403 | + t.Fatalf("buildSubprocessEnv missing OS-set %q", key) |
| 1404 | + } |
| 1405 | + if raidIdx < 0 { |
| 1406 | + t.Fatalf("buildSubprocessEnv missing raid-set %q", key) |
| 1407 | + } |
| 1408 | + if raidIdx < osIdx { |
| 1409 | + t.Errorf("raid-set entry at %d came before OS entry at %d; exec uses last-occurrence so raidVar must come last", raidIdx, osIdx) |
| 1410 | + } |
| 1411 | +} |
0 commit comments