Skip to content

Commit d69a81e

Browse files
committed
Merge branch 'develop' of github.com:topcoder-platform/marathon-match-api-v6 into develop
2 parents c440751 + fbff1f8 commit d69a81e

10 files changed

Lines changed: 147 additions & 17 deletions

ECS_RUNNER_LIFETIME.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ For `PROVISIONAL` and `SYSTEM`, the runner posts progress to `POST /v6/marathon-
195195
- `timed_out: true` for SYSTEM timeout failures
196196

197197
Treat `IN PROGRESS` summations as unavailable, even when they carry a placeholder score.
198+
Completed scoring may report `SUCCESS` with nonzero `failedTests` when individual testcases timed out or crashed; `FAILED` is reserved for explicit scorer or skipped-scoring failures.
198199

199200
## Stopping or killing runners
200201

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ Auth model in code:
238238
| `POST` | `/v6/marathon-match/internal/system-score` | `administrator` OR `update:marathon-match` |
239239

240240
`POST /v6/marathon-match/internal/scoring-results` rejects callbacks whose `challengeId` does not map to an existing Marathon Match config.
241-
`POST /v6/marathon-match/internal/scoring-progress` stores runner progress in review summation metadata as `testProcess` (`provisional` or `system`), `testProgress` (`0` to `1`), and `testStatus` (`IN PROGRESS`, `SUCCESS`, or `FAILED`).
241+
`POST /v6/marathon-match/internal/scoring-progress` stores runner progress in review summation metadata as `testProcess` (`provisional` or `system`), `testProgress` (`0` to `1`), and `testStatus` (`IN PROGRESS`, `SUCCESS`, or `FAILED`). Completed scoring can still report `SUCCESS` when individual testcases timed out or crashed; those counts stay in `testProgressDetails.failedTests`.
242242

243243
## How to set up a challenge for marathon match scoring
244244

docs/marathon-processor-specification-and-scoring-terminology.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,8 @@ For Provisional and System scoring, the runner posts progress updates while test
315315
| `testProgressDetails.failedTests` | testcase count with errors |
316316
| `testProgressDetails.message` | latest runner progress or failure message |
317317

318+
Completed scoring may report `testStatus = SUCCESS` with nonzero `testProgressDetails.failedTests` when individual testcases timed out or crashed. `FAILED` is reserved for explicit scorer or skipped-scoring failures.
319+
318320
Operators can also inspect ECS runner logs. Runner log mappings are stored for each launched scoring task and can be exposed through:
319321

320322
```text
@@ -376,7 +378,7 @@ For each testcase:
376378
- the best raw score receives `100`
377379
- all other valid scores receive `(lower score / higher score) * 100`
378380

379-
The final relative score is the average of those per-testcase relative scores. If every testcase fails, the aggregate score is `-1`.
381+
The final relative score is the average of those per-testcase relative scores. If every testcase fails, the aggregate score is `-1` while completed test status remains `SUCCESS`.
380382

381383
### Score direction
382384

docs/review-phase-scoring.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ sequenceDiagram
6666

6767
After `ScoringResultService` writes the SYSTEM review summation, `completeSystemReviewIfNeeded` patches the originating review to `COMPLETED` and writes the final score back to Review API.
6868

69-
While SYSTEM tests are running, the ECS runner updates the phase review summation metadata with `testProcess` (`provisional` or `system`), `testProgress` (`0` to `1`), and `testStatus` (`IN PROGRESS`, `SUCCESS`, or `FAILED`). These fields are returned by Review API under `reviewSummation.metadata` when metadata is requested. In-progress summations keep a neutral placeholder score and should be displayed as unavailable from `testStatus`; failed progress uses the failed-score sentinel.
69+
While SYSTEM tests are running, the ECS runner updates the phase review summation metadata with `testProcess` (`provisional` or `system`), `testProgress` (`0` to `1`), and `testStatus` (`IN PROGRESS`, `SUCCESS`, or `FAILED`). These fields are returned by Review API under `reviewSummation.metadata` when metadata is requested. In-progress summations keep a neutral placeholder score and should be displayed as unavailable from `testStatus`; explicit failed progress uses the failed-score sentinel. Completed scoring can report `testStatus = SUCCESS` with nonzero `testProgressDetails.failedTests` when individual testcases timed out or crashed.
7070

7171
Each dispatched SYSTEM task also schedules a pg-boss timeout check using the config's `systemTestTimeout` value. The default is 24 hours (`86400000` ms). When the delayed check runs, the API describes the ECS task and checks the SYSTEM summation; if the task is still active and the summation is not complete, it stops the ECS task, writes a failed SYSTEM summation with score `-1`, and includes `metadata.timed_out = true`.
7272

docs/submission-phase-scoring.md

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

100100
`GET /v6/marathon-match/submissions/:submissionId/runner-logs`
101101

102-
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`.
102+
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 explicit scorer or skipped-scoring failures use `FAILED` progress and the failed-score sentinel. Completed scoring with timed-out or crashed testcases keeps those counts in `testProgressDetails.failedTests` while reporting `testStatus = SUCCESS`. SYSTEM timeout failures also include `reviewSummation.metadata.timed_out = true`.
103103

104104
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 and compilation diagnostics stay out of that file. The separate `compile_log.txt` file preserves compilation output. 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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4727,7 +4727,7 @@ private static TesterExecutionResult runGenericMarathonTester(
47274727
testScores.size(),
47284728
numberOfTests,
47294729
failedTests,
4730-
failedTests > 0 ? TEST_STATUS_FAILED : TEST_STATUS_IN_PROGRESS,
4730+
TEST_STATUS_IN_PROGRESS,
47314731
"Completed test " + testCaseNumber + " of " + numberOfTests
47324732
);
47334733
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"jsonwebtoken": "^9.0.2",
4949
"jwks-rsa": "^3.2.0",
5050
"lodash": "^4.17.21",
51-
"multer": "^2.1.1",
51+
"multer": "^2.2.0",
5252
"nanoid": "~5.1.2",
5353
"pg": "^8.16.3",
5454
"pg-boss": "^11.0.5",

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/api/scoring-result/scoring-result.service.spec.ts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,68 @@ describe('ScoringResultService', () => {
339339
);
340340
});
341341

342+
it('keeps completed scoring successful when all individual tests fail', async () => {
343+
const { service, m2mService, prisma } = createService();
344+
const payloadWithFailedSeed: ScoringResultCallbackPayload = {
345+
...basePayload,
346+
score: -1,
347+
metadata: {
348+
numberOfTests: 2,
349+
testScores: [
350+
{
351+
error: 'Timed out.',
352+
score: -1,
353+
testcase: '753388858',
354+
},
355+
{
356+
error: 'Crashed.',
357+
score: -1,
358+
testcase: '753388859',
359+
},
360+
],
361+
},
362+
};
363+
364+
prisma.marathonMatchConfig.findUnique.mockResolvedValue({
365+
challengeId: basePayload.challengeId,
366+
name: 'Blocks',
367+
submissionApiUrl: 'https://api.topcoder-dev.com/v6',
368+
relativeScoringEnabled: false,
369+
scoreDirection: ScoreDirection.MAXIMIZE,
370+
});
371+
m2mService.getM2MToken.mockResolvedValue('m2m-token');
372+
373+
const createReviewSummationSpy = jest
374+
.spyOn(service as any, 'createReviewSummation')
375+
.mockResolvedValue(undefined);
376+
jest
377+
.spyOn(service as any, 'findExistingReviewSummations')
378+
.mockResolvedValue([]);
379+
jest
380+
.spyOn(service as any, 'completeSystemReviewIfNeeded')
381+
.mockResolvedValue(undefined);
382+
383+
await expect(
384+
service.processScoringResult(payloadWithFailedSeed),
385+
).resolves.toBe(undefined);
386+
387+
expect(createReviewSummationSpy).toHaveBeenCalledWith(
388+
'm2m-token',
389+
expect.objectContaining({
390+
aggregateScore: payloadWithFailedSeed.score,
391+
isPassing: false,
392+
metadata: expect.objectContaining({
393+
testProgress: 1,
394+
testStatus: ScoringTestStatus.Success,
395+
testProgressDetails: expect.objectContaining({
396+
failedTests: 2,
397+
status: ScoringTestStatus.Success,
398+
}),
399+
}),
400+
}),
401+
);
402+
});
403+
342404
it('persists finite relative scores when the maximize best testcase score is zero', async () => {
343405
const { service, m2mService, prisma } = createService();
344406
const zeroScorePayload: ScoringResultCallbackPayload = {
@@ -1639,6 +1701,73 @@ describe('ScoringResultService', () => {
16391701
expect(result.payload.reviewedDate).toBe(reviewedDate);
16401702
});
16411703

1704+
it('keeps relative scoring status successful when all individual tests fail', () => {
1705+
const { service } = createService();
1706+
const buildRelativeReviewPayload = (
1707+
service as any
1708+
).buildRelativeReviewPayload.bind(service) as (
1709+
reviewRecord: {
1710+
submissionId: string;
1711+
reviewObject: Record<string, unknown>;
1712+
metadata: Record<string, unknown>;
1713+
rawTestScores: Array<{
1714+
error?: string;
1715+
score: number;
1716+
testcase: string;
1717+
}>;
1718+
},
1719+
bestScores: Map<string, number>,
1720+
scoreDirection: ScoreDirection,
1721+
fallbackScorecardId: string | undefined,
1722+
testPhase: string,
1723+
) => {
1724+
payload: {
1725+
aggregateScore: number;
1726+
metadata: {
1727+
testProgressDetails: {
1728+
failedTests: number;
1729+
status: ScoringTestStatus;
1730+
};
1731+
testStatus: ScoringTestStatus;
1732+
};
1733+
};
1734+
};
1735+
1736+
const result = buildRelativeReviewPayload(
1737+
{
1738+
submissionId: 'failed-testcase-submission',
1739+
reviewObject: {
1740+
id: 'review-summation-1',
1741+
isProvisional: true,
1742+
},
1743+
metadata: {
1744+
testScores: [
1745+
{ error: 'Timed out.', score: -1, testcase: '1' },
1746+
{ error: 'Crashed.', score: -1, testcase: '2' },
1747+
],
1748+
testType: 'provisional',
1749+
},
1750+
rawTestScores: [
1751+
{ error: 'Timed out.', score: -1, testcase: '1' },
1752+
{ error: 'Crashed.', score: -1, testcase: '2' },
1753+
],
1754+
},
1755+
new Map(),
1756+
ScoreDirection.MAXIMIZE,
1757+
undefined,
1758+
'provisional',
1759+
);
1760+
1761+
expect(result.payload.aggregateScore).toBe(-1);
1762+
expect(result.payload.metadata.testStatus).toBe(ScoringTestStatus.Success);
1763+
expect(result.payload.metadata.testProgressDetails).toEqual(
1764+
expect.objectContaining({
1765+
failedTests: 2,
1766+
status: ScoringTestStatus.Success,
1767+
}),
1768+
);
1769+
});
1770+
16421771
it('marks timed-out system tests as failed with timed_out metadata', async () => {
16431772
const { service } = createService();
16441773
const processScoringResultSpy = jest

src/api/scoring-result/scoring-result.service.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2183,9 +2183,7 @@ export class ScoringResultService {
21832183
failedTests,
21842184
progress: 1,
21852185
status:
2186-
failedTests > 0
2187-
? ScoringTestStatus.Failed
2188-
: ScoringTestStatus.Success,
2186+
totalTests > 0 ? ScoringTestStatus.Success : ScoringTestStatus.Failed,
21892187
totalTests,
21902188
},
21912189
);
@@ -2687,11 +2685,11 @@ export class ScoringResultService {
26872685
finalScore: number,
26882686
): Record<string, unknown> {
26892687
const totalTests = this.resolveTotalTests(metadata);
2690-
const completedTests =
2691-
totalTests ?? this.countCompletedTestScores(metadata);
2688+
const completedTestScores = this.countCompletedTestScores(metadata);
2689+
const completedTests = totalTests ?? completedTestScores;
26922690
const failedTests = this.countFailedTestScores(metadata);
26932691
const status =
2694-
finalScore < 0 || failedTests > 0
2692+
finalScore < 0 && completedTestScores === 0
26952693
? ScoringTestStatus.Failed
26962694
: ScoringTestStatus.Success;
26972695

0 commit comments

Comments
 (0)