Skip to content

Commit f4f4c80

Browse files
committed
Fix for work app MM tester flow
1 parent d4d4970 commit f4f4c80

7 files changed

Lines changed: 249 additions & 15 deletions

File tree

ECS_RUNNER_LIFETIME.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ This is the normal path for `EXAMPLE` and `PROVISIONAL` scoring during the Submi
4141

4242
### Validation submission scoring
4343

44-
`POST /v6/marathon-match/challenge/:challengeId/test-submission` stores the uploaded ZIP as an isolated Marathon Match validation run, then launches one ECS runner for the requested `configType` (default `PROVISIONAL`). The runner downloads the ZIP from Marathon Match API using `VALIDATION_SUBMISSION_DOWNLOAD_URL`, includes `VALIDATION_RUN_ID` in progress/final callbacks, skips Submission API artifact uploads, and `ScoringResultService` stores progress/final score details on the validation run instead of creating Review API summations. The response includes `testSubmissionId`, `submissionId` (same validation-run id for runner compatibility), `status`, `taskArn`, `taskId`, and usually `cloudWatchLogsConsoleUrl`.
44+
`POST /v6/marathon-match/challenge/:challengeId/test-submission` stores the uploaded ZIP as an isolated Marathon Match validation run, then launches one ECS runner for the requested `configType` (default `PROVISIONAL`). The runner downloads the ZIP from Marathon Match API using `VALIDATION_SUBMISSION_DOWNLOAD_URL`, which is resolved against the normalized `/v6/marathon-match` API base, includes `VALIDATION_RUN_ID` in progress/final callbacks, skips Submission API artifact uploads, and `ScoringResultService` stores progress/final score details on the validation run instead of creating Review API summations. The response includes `testSubmissionId`, `submissionId` (same validation-run id for runner compatibility), `status`, `taskArn`, `taskId`, and usually `cloudWatchLogsConsoleUrl`.
4545

4646
Use `GET /v6/marathon-match/challenge/:challengeId/test-submission/:testSubmissionId` to poll for completion and display the score/metadata. We do this to support admins / copilots having the ability to test the scorer before the challenge launches without adding a real submission or review to the challenge.
4747

docs/submission-phase-scoring.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ Runner task logs are written to CloudWatch using the submission runner log strea
101101

102102
For `PROVISIONAL` and `SYSTEM` scoring, the runner writes progress to the phase review summation metadata through `POST /v6/marathon-match/internal/scoring-progress`. Review API exposes this as `reviewSummation.metadata.testProcess` (`provisional` or `system`), `reviewSummation.metadata.testProgress` (`0` to `1`), and `reviewSummation.metadata.testStatus` (`IN PROGRESS`, `SUCCESS`, or `FAILED`) when metadata is included in the response. In-progress summations keep a neutral placeholder score and must be rendered as unavailable based on `testStatus`; only `FAILED` progress uses the failed-score sentinel. SYSTEM timeout failures also include `reviewSummation.metadata.timed_out = true`.
103103

104-
For generic runner artifacts, only EXAMPLE scoring uploads the member-visible public artifact. Its public `output.txt` includes each testcase ordinal, actual seed, seed score, runtime, runner/tester errors, and submitted-solution stderr, while submitted-solution stdout stays private. Every generic scoring phase uploads private internal artifacts: per-test score details remain in `reviews.json` with testcase ordinals and actual seeds, callback metadata keeps member-visible testcase ordinals only, compile and execution logs are preserved as `compile_log.txt`, `execution-{submissionId}.log`, and optional `error-{submissionId}.log`, and captured submitted-solution output is split into `stdout/{seed}.txt` and `stderr/{seed}.txt` files for each actual seed. PROVISIONAL and SYSTEM scoring do not publish competitor-visible artifacts.
104+
For generic runner artifacts, only EXAMPLE scoring uploads the member-visible public artifact. Its public `output.txt` starts with the captured compilation output, then includes each testcase ordinal, actual seed, seed score, runtime, runner/tester errors, and submitted-solution stderr, while submitted-solution stdout stays private. Every generic scoring phase uploads private internal artifacts: `reviews.json` includes `compilationOutput`, per-test score details with testcase ordinals and actual seeds, and top-level `testScores`; callback metadata keeps member-visible testcase ordinals only, compile and execution logs are preserved as `compile_log.txt`, `execution-{submissionId}.log`, and optional `error-{submissionId}.log`, and captured submitted-solution output is split into `stdout/{seed}.txt` and `stderr/{seed}.txt` files for each actual seed. PROVISIONAL and SYSTEM scoring do not publish competitor-visible artifacts.
105105

106106
When more than one phase config matches the currently open challenge phases, the handler launches one scorer task per match. This is the supported way to run both `EXAMPLE` and `PROVISIONAL` scoring from the same Submission phase.
107107

ecs-runner/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ Once this is saved and the config is active, new scoring launches use the new EC
142142

143143
The runner rejects output once generated public `output.txt` content or a public/private artifact archive would exceed `MM_RUNNER_MAX_OUTPUT_BYTES`. The default is `10000000` bytes. The trusted parent runner passes the resolved limit into the isolated tester child, so generated generic-runner output and uploaded artifact zips use the same cap.
144144

145-
Generic runner member-visible artifacts are uploaded only for EXAMPLE scoring. Their public `output.txt` entries show testcase ordinal, actual seed, seed score, runtime, runner/tester errors, and submitted-solution stderr, while intentionally omitting submitted-solution stdout. Every generic scoring phase uploads private internal artifacts in the `*-internal` zip: `reviews.json` carries per-test `metadata.testScores` and top-level `testScores` with testcase ordinals and actual seeds, `compile_log.txt` and `execution-{submissionId}.log` preserve runner diagnostics, `error-{submissionId}.log` is included when present, and `stdout/{seed}.txt` plus `stderr/{seed}.txt` carry captured submitted-solution output for each actual seed. PROVISIONAL and SYSTEM scoring do not publish competitor-visible artifacts.
145+
Generic runner member-visible artifacts are uploaded only for EXAMPLE scoring. Their public `output.txt` starts with the captured compilation output, then shows testcase ordinal, actual seed, seed score, runtime, runner/tester errors, and submitted-solution stderr, while intentionally omitting submitted-solution stdout. Every generic scoring phase uploads private internal artifacts in the `*-internal` zip: `reviews.json` carries `compilationOutput`, per-test `metadata.testScores`, and top-level `testScores` with testcase ordinals and actual seeds, `compile_log.txt` and `execution-{submissionId}.log` preserve runner diagnostics, `error-{submissionId}.log` is included when present, and `stdout/{seed}.txt` plus `stderr/{seed}.txt` carry captured submitted-solution output for each actual seed. PROVISIONAL and SYSTEM scoring do not publish competitor-visible artifacts.
146146

147147
## Local smoke test
148148

ecs-runner/src/main/java/com/topcoder/runner/EcsRunnerMain.java

Lines changed: 129 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ public class EcsRunnerMain {
105105
private static final int DEFAULT_TEST_TIMEOUT_MS = 10000;
106106
private static final int DEFAULT_COMPILE_TIMEOUT_MS = 30000;
107107
private static final String GENERIC_SOLUTION_BASE_NAME = "Solution";
108+
private static final String COMPILATION_OUTPUT_METADATA_KEY = "compilationOutput";
108109
private static final String JAVA_SUBMISSION_RELEASE = "11";
109110
private static final String CXX_MARCH_FLAG = "-march=x86-64";
110111
private static final String CXX_MTUNE_FLAG = "-mtune=generic";
@@ -2816,8 +2817,9 @@ private static Map<String, Object> buildCallbackMetadata(
28162817
}
28172818

28182819
/**
2819-
* Removes configured seed values from metadata that may be persisted or rendered to members.
2820-
* Per-test entries keep a stable 1-based testcase ordinal so relative scoring still works.
2820+
* Removes configured seed values and verbose compile output from metadata that may be
2821+
* persisted or rendered through scoring callbacks. Per-test entries keep a stable 1-based
2822+
* testcase ordinal so relative scoring still works.
28212823
*
28222824
* @param metadata Metadata returned by tester execution.
28232825
* @return Copy with seed-bearing fields removed or replaced, or {@code null}.
@@ -2836,6 +2838,9 @@ private static Map<String, Object> sanitizeMemberVisibleMetadata(
28362838
if (isSensitiveSeedMetadataKey(key)) {
28372839
continue;
28382840
}
2841+
if (COMPILATION_OUTPUT_METADATA_KEY.equals(key)) {
2842+
continue;
2843+
}
28392844

28402845
Object value = entry.getValue();
28412846
if ("testScores".equals(key) && value instanceof List) {
@@ -3203,7 +3208,8 @@ private static void writeSeedOutputArtifact(
32033208
/**
32043209
* Builds the JSON payload stored in the internal {@code reviews.json} artifact.
32053210
* The payload includes sanitized top-level {@code testScores} so private
3206-
* consumers can inspect per-test scores for every scoring phase.
3211+
* consumers can inspect per-test scores for every scoring phase. Generic runner
3212+
* payloads also carry compiler diagnostics in {@code compilationOutput}.
32073213
*
32083214
* @param submissionId Submission whose review payload is archived.
32093215
* @param testPhase Scoring phase represented by the payload.
@@ -3224,6 +3230,7 @@ private static Map<String, Object> buildInternalReviewArtifactPayload(
32243230
Map<String, Object> internalMetadata = buildInternalArtifactMetadata(
32253231
testerExecution.getMetadata()
32263232
);
3233+
String compilationOutput = getCompilationOutputFromMetadata(internalMetadata);
32273234
Map<String, Object> currentReview = withDefaultReviewFields(
32283235
testerExecution.getCurrentReview(),
32293236
submissionId,
@@ -3250,6 +3257,9 @@ private static Map<String, Object> buildInternalReviewArtifactPayload(
32503257
payload.put("scorecardId", scorecardId);
32513258
payload.put("score", testerExecution.getScore());
32523259
payload.put("metadata", internalMetadata);
3260+
if (compilationOutput != null) {
3261+
payload.put(COMPILATION_OUTPUT_METADATA_KEY, compilationOutput);
3262+
}
32533263
payload.put(
32543264
"testScores",
32553265
buildInternalArtifactScoreEntries(testerExecution.getMetadata())
@@ -3260,6 +3270,21 @@ private static Map<String, Object> buildInternalReviewArtifactPayload(
32603270
return payload;
32613271
}
32623272

3273+
/**
3274+
* Reads generic-runner compilation output from metadata for top-level private artifacts.
3275+
*
3276+
* @param metadata Internal artifact metadata, usually copied from tester execution metadata.
3277+
* @return Compilation output text, or {@code null} when none was produced.
3278+
*/
3279+
private static String getCompilationOutputFromMetadata(Map<String, Object> metadata) {
3280+
if (metadata == null) {
3281+
return null;
3282+
}
3283+
3284+
Object compilationOutput = metadata.get(COMPILATION_OUTPUT_METADATA_KEY);
3285+
return compilationOutput instanceof String ? (String) compilationOutput : null;
3286+
}
3287+
32633288
/**
32643289
* Builds private review artifact metadata from raw tester execution metadata.
32653290
*
@@ -4531,8 +4556,9 @@ private static Object invokeCustomRunTester(
45314556
* Runs a standard Topcoder Marathon tester without requiring tester-specific ECS code.
45324557
*
45334558
* <p>The method creates artifact directories, selects a supported submission source file,
4534-
* compiles it when needed, runs the configured tester for every seed in the phase range, and
4535-
* returns the aggregate score plus per-test metadata used by relative scoring.
4559+
* compiles it when needed, writes compilation diagnostics to artifacts, runs the configured
4560+
* tester for every seed in the phase range, and returns the aggregate score plus per-test
4561+
* metadata used by relative scoring and internal diagnostics.
45364562
*
45374563
* @param testerClassName Fully qualified Marathon tester class name.
45384564
* @param submissionPath Extracted submission directory.
@@ -4613,6 +4639,7 @@ private static TesterExecutionResult runGenericMarathonTester(
46134639
Path compileWorkDir = Files.createTempDirectory("mm-submission-solution-");
46144640
boolean cleanupDeferred = deferIsolatedChildCleanup(compileWorkDir);
46154641
Path compileLogPath = artifactsPublicDir.resolve("compile_log.txt");
4642+
Path outputPath = artifactsPublicDir.resolve("output.txt");
46164643

46174644
try {
46184645
CompiledSubmission compiledSubmission = compileAndBuildExecutionCommand(
@@ -4622,14 +4649,20 @@ private static TesterExecutionResult runGenericMarathonTester(
46224649
expectedSolutionBaseName,
46234650
compileLogPath
46244651
);
4652+
String compilationOutput = readCompilationOutput(compileLogPath);
4653+
StringBuilder outputText = new StringBuilder();
4654+
long outputBytes = appendLimitedOutput(
4655+
outputText,
4656+
buildMemberVisibleCompilationOutput(compilationOutput),
4657+
0L,
4658+
"artifacts/public/output.txt"
4659+
);
46254660
grantScorerReadExecuteAccess(compileWorkDir);
46264661

46274662
MarathonController controller = new MarathonController();
46284663
List<Map<String, Object>> testScores = new ArrayList<Map<String, Object>>();
46294664
double totalScore = 0.0;
46304665
int failedTests = 0;
4631-
StringBuilder outputText = new StringBuilder();
4632-
long outputBytes = 0L;
46334666

46344667
long endSeed = startSeed + numberOfTests - 1L;
46354668
for (long seed = startSeed; seed <= endSeed; seed++) {
@@ -4717,7 +4750,7 @@ private static TesterExecutionResult runGenericMarathonTester(
47174750
}
47184751

47194752
try (BufferedWriter writer = Files.newBufferedWriter(
4720-
artifactsPublicDir.resolve("output.txt"),
4753+
outputPath,
47214754
StandardCharsets.UTF_8
47224755
)) {
47234756
writer.write(outputText.toString());
@@ -4731,6 +4764,7 @@ private static TesterExecutionResult runGenericMarathonTester(
47314764
metadata.put("numberOfTests", numberOfTests);
47324765
metadata.put("timeLimitMs", timeLimitMs);
47334766
metadata.put("compileTimeoutMs", compileTimeoutMs);
4767+
metadata.put(COMPILATION_OUTPUT_METADATA_KEY, compilationOutput);
47344768
metadata.put("aggregateMode", "average");
47354769
metadata.put("testScores", testScores);
47364770

@@ -4745,13 +4779,100 @@ private static TesterExecutionResult runGenericMarathonTester(
47454779
currentReview,
47464780
new ArrayList<Map<String, Object>>()
47474781
);
4782+
} catch (Exception error) {
4783+
writePreResultMemberOutput(outputPath, compileLogPath, error);
4784+
throw error;
47484785
} finally {
47494786
if (!cleanupDeferred) {
47504787
deletePathRecursively(compileWorkDir);
47514788
}
47524789
}
47534790
}
47544791

4792+
/**
4793+
* Reads the compiler log that should be embedded in member-visible and private outputs.
4794+
*
4795+
* @param compileLogPath Public compile log written by compiler commands.
4796+
* @return Compiler output text, or a short no-output marker when no compiler command ran.
4797+
* @throws IOException When an existing compile log cannot be read safely.
4798+
*/
4799+
private static String readCompilationOutput(Path compileLogPath) throws IOException {
4800+
if (!isNonSymlinkRegularFile(compileLogPath)) {
4801+
return "No compilation step was run for this submission.";
4802+
}
4803+
4804+
String compilationOutput = new String(
4805+
readRegularFileBytesNoFollow(compileLogPath),
4806+
StandardCharsets.UTF_8
4807+
).trim();
4808+
if (compilationOutput.isEmpty()) {
4809+
return "Compilation completed without output.";
4810+
}
4811+
return compilationOutput;
4812+
}
4813+
4814+
/**
4815+
* Builds the public {@code output.txt} compilation section shown before test cases.
4816+
*
4817+
* @param compilationOutput Compiler diagnostics captured for the submission.
4818+
* @return Member-visible compilation text block ending with a blank line.
4819+
*/
4820+
private static String buildMemberVisibleCompilationOutput(
4821+
String compilationOutput
4822+
) {
4823+
StringBuilder outputText = new StringBuilder();
4824+
outputText.append("Compilation Output:\n");
4825+
if (compilationOutput == null || compilationOutput.trim().isEmpty()) {
4826+
outputText.append("No compilation output was produced.");
4827+
} else {
4828+
outputText.append(compilationOutput.trim());
4829+
}
4830+
outputText.append("\n\n");
4831+
return outputText.toString();
4832+
}
4833+
4834+
/**
4835+
* Writes a partial member-visible output file when the generic runner fails
4836+
* before normal score output can be finalized.
4837+
*
4838+
* @param outputPath Public {@code output.txt} artifact path.
4839+
* @param compileLogPath Public compile log path to embed.
4840+
* @param error Failure that prevented normal output generation.
4841+
*/
4842+
private static void writePreResultMemberOutput(
4843+
Path outputPath,
4844+
Path compileLogPath,
4845+
Throwable error
4846+
) {
4847+
if (outputPath == null || isNonSymlinkRegularFile(outputPath)) {
4848+
return;
4849+
}
4850+
4851+
try {
4852+
StringBuilder outputText = new StringBuilder();
4853+
long outputBytes = appendLimitedOutput(
4854+
outputText,
4855+
buildMemberVisibleCompilationOutput(readCompilationOutput(compileLogPath)),
4856+
0L,
4857+
"artifacts/public/output.txt"
4858+
);
4859+
appendLimitedOutput(
4860+
outputText,
4861+
"Runner Error:\n" + safeLogValue(error == null ? null : error.getMessage()) + "\n",
4862+
outputBytes,
4863+
"artifacts/public/output.txt"
4864+
);
4865+
4866+
Files.createDirectories(outputPath.getParent());
4867+
Files.write(outputPath, outputText.toString().getBytes(StandardCharsets.UTF_8));
4868+
} catch (Exception outputError) {
4869+
logWarn(
4870+
"artifacts.public-output",
4871+
"Unable to write partial member output: " + outputError.getMessage()
4872+
);
4873+
}
4874+
}
4875+
47554876
/**
47564877
* Builds the public {@code output.txt} section for one generic Marathon test case.
47574878
*

0 commit comments

Comments
 (0)