| ... | ... | | @@ -1081,3 +1082,147 @@ func TestGenerateJobIndex_WalksSubdirectories(t *testing.T) { |
| 1081 | 1082 | | t.Errorf("txt index missing nested artifact build/output.json: %s", txtContent) |
| 1082 | 1083 | | } |
| 1083 | 1084 | | } |
| 1085 | + |
|
| 1086 | + | func TestWaitAndReport_ExitCodeFailure(t *testing.T) { |
| 1087 | + | if _, err := exec.LookPath("zmx"); err != nil { |
| 1088 | + | t.Skip("zmx not found, skipping test") |
| 1089 | + | } |
| 1090 | + |
|
| 1091 | + | cfg := &Cfg{ |
| 1092 | + | MonitorInterval: 100 * time.Millisecond, |
| 1093 | + | ArtifactDir: t.TempDir(), |
| 1094 | + | } |
| 1095 | + |
|
| 1096 | + | jobID := fmt.Sprintf("failtest-%d", time.Now().UnixNano()) |
| 1097 | + | prefix := "local.testrepo." + jobID + "." |
| 1098 | + | runnerSession := prefix + "runner" |
| 1099 | + |
|
| 1100 | + | t.Cleanup(func() { |
| 1101 | + | _ = exec.Command("zmx", "kill", "-f", runnerSession).Run() |
| 1102 | + | }) |
| 1103 | + |
|
| 1104 | + | // Start a zmx session that exits with code 42
|
| 1105 | + | cmd := exec.Command("zmx", "run", runnerSession, "-d", "bash", "-c", "exit 42") |
| 1106 | + | if err := cmd.Run(); err != nil { |
| 1107 | + | t.Fatalf("failed to start zmx session: %v", err) |
| 1108 | + | } |
| 1109 | + |
|
| 1110 | + | err := waitAndReport(cfg, nil, "testrepo", jobID, "local") |
| 1111 | + | if err == nil { |
| 1112 | + | t.Fatal("expected error from waitAndReport for failed job, got nil") |
| 1113 | + | } |
| 1114 | + |
|
| 1115 | + | var jobFailedErr *JobFailedError |
| 1116 | + | if !errors.As(err, &jobFailedErr) { |
| 1117 | + | t.Fatalf("expected error of type *JobFailedError, got %T: %v", err, err) |
| 1118 | + | } |
| 1119 | + | if jobFailedErr.ExitCode != 42 { |
| 1120 | + | t.Errorf("expected exit code 42, got %d", jobFailedErr.ExitCode) |
| 1121 | + | } |
| 1122 | + | } |
| 1123 | + |
|
| 1124 | + | func TestWaitAndReport_Success(t *testing.T) { |
| 1125 | + | if _, err := exec.LookPath("zmx"); err != nil { |
| 1126 | + | t.Skip("zmx not found, skipping test") |
| 1127 | + | } |
| 1128 | + |
|
| 1129 | + | cfg := &Cfg{ |
| 1130 | + | MonitorInterval: 100 * time.Millisecond, |
| 1131 | + | ArtifactDir: t.TempDir(), |
| 1132 | + | } |
| 1133 | + |
|
| 1134 | + | jobID := fmt.Sprintf("successtest-%d", time.Now().UnixNano()) |
| 1135 | + | prefix := "local.testrepo." + jobID + "." |
| 1136 | + | runnerSession := prefix + "runner" |
| 1137 | + |
|
| 1138 | + | t.Cleanup(func() { |
| 1139 | + | _ = exec.Command("zmx", "kill", "-f", runnerSession).Run() |
| 1140 | + | }) |
| 1141 | + |
|
| 1142 | + | // Start a zmx session that exits with code 0
|
| 1143 | + | cmd := exec.Command("zmx", "run", runnerSession, "-d", "bash", "-c", "exit 0") |
| 1144 | + | if err := cmd.Run(); err != nil { |
| 1145 | + | t.Fatalf("failed to start zmx session: %v", err) |
| 1146 | + | } |
| 1147 | + |
|
| 1148 | + | err := waitAndReport(cfg, nil, "testrepo", jobID, "local") |
| 1149 | + | if err != nil { |
| 1150 | + | t.Fatalf("expected nil error for successful job, got %v", err) |
| 1151 | + | } |
| 1152 | + | } |
| 1153 | + |
|
| 1154 | + | func TestRunLocal_ExitCodeFailure(t *testing.T) { |
| 1155 | + | if _, err := exec.LookPath("zmx"); err != nil { |
| 1156 | + | t.Skip("zmx not found, skipping test") |
| 1157 | + | } |
| 1158 | + |
|
| 1159 | + | tempDir := t.TempDir() |
| 1160 | + | origWd, _ := os.Getwd() |
| 1161 | + | defer func() { _ = os.Chdir(origWd) }() |
| 1162 | + | _ = os.Chdir(tempDir) |
| 1163 | + |
|
| 1164 | + | picoContent := `#!/usr/bin/env bash |
| 1165 | + | exit 7 |
| 1166 | + | ` |
| 1167 | + | if err := os.WriteFile("pico.sh", []byte(picoContent), 0755); err != nil { |
| 1168 | + | t.Fatal(err) |
| 1169 | + | } |
| 1170 | + |
|
| 1171 | + | artifactDir := t.TempDir() |
| 1172 | + | cfg := &Cfg{ |
| 1173 | + | ArtifactDir: artifactDir, |
| 1174 | + | MonitorInterval: 100 * time.Millisecond, |
| 1175 | + | } |
| 1176 | + |
|
| 1177 | + | err := runLocal(cfg, "") |
| 1178 | + | if err == nil { |
| 1179 | + | t.Fatal("expected error from runLocal when pico.sh exits non-zero, got nil") |
| 1180 | + | } |
| 1181 | + |
|
| 1182 | + | var jobFailedErr *JobFailedError |
| 1183 | + | if !errors.As(err, &jobFailedErr) { |
| 1184 | + | t.Fatalf("expected error of type *JobFailedError, got %T: %v", err, err) |
| 1185 | + | } |
| 1186 | + | if jobFailedErr.ExitCode != 7 { |
| 1187 | + | t.Errorf("expected exit code 7, got %d", jobFailedErr.ExitCode) |
| 1188 | + | } |
| 1189 | + | } |
| 1190 | + |
|
| 1191 | + | func TestRunRunner_Wait_ExitCodeFailure(t *testing.T) { |
| 1192 | + | if _, err := exec.LookPath("zmx"); err != nil { |
| 1193 | + | t.Skip("zmx not found, skipping test") |
| 1194 | + | } |
| 1195 | + |
|
| 1196 | + | workspaceDir := t.TempDir() |
| 1197 | + | picoContent := `#!/usr/bin/env bash |
| 1198 | + | exit 13 |
| 1199 | + | ` |
| 1200 | + | if err := os.WriteFile(filepath.Join(workspaceDir, "pico.sh"), []byte(picoContent), 0755); err != nil { |
| 1201 | + | t.Fatal(err) |
| 1202 | + | } |
| 1203 | + |
|
| 1204 | + | eventJSON := fmt.Sprintf(`{"type":"local","name":"runner-fail-repo","workspace":%q}`, workspaceDir) |
| 1205 | + |
|
| 1206 | + | artifactDir := t.TempDir() |
| 1207 | + | cfg := &Cfg{ |
| 1208 | + | ArtifactDir: artifactDir, |
| 1209 | + | MonitorInterval: 100 * time.Millisecond, |
| 1210 | + | Wait: true, |
| 1211 | + | Event: eventJSON, |
| 1212 | + | Logger: slog.New(slog.NewTextHandler(io.Discard, nil)), |
| 1213 | + | NewWorkspace: defaultWorkspaceFactory, |
| 1214 | + | } |
| 1215 | + |
|
| 1216 | + | err := RunRunner(cfg) |
| 1217 | + | if err == nil { |
| 1218 | + | t.Fatal("expected error from RunRunner --wait on failed job, got nil") |
| 1219 | + | } |
| 1220 | + |
|
| 1221 | + | var jobFailedErr *JobFailedError |
| 1222 | + | if !errors.As(err, &jobFailedErr) { |
| 1223 | + | t.Fatalf("expected wrapped *JobFailedError, got %T: %v", err, err) |
| 1224 | + | } |
| 1225 | + | if jobFailedErr.ExitCode != 13 { |
| 1226 | + | t.Errorf("expected exit code 13, got %d", jobFailedErr.ExitCode) |
| 1227 | + | } |
| 1228 | + | } |