mirror of
https://github.com/renbaoshuo/S2OJ.git
synced 2024-11-22 18:08:42 +00:00
refactor(remote_judger): waitForSubmission
This commit is contained in:
parent
00fea4675f
commit
4c0167ee6e
@ -20,10 +20,10 @@ export interface IBasicProvider {
|
|||||||
end: NextFunction
|
end: NextFunction
|
||||||
): Promise<string | void>;
|
): Promise<string | void>;
|
||||||
waitForSubmission(
|
waitForSubmission(
|
||||||
problem_id: string,
|
|
||||||
id: string,
|
id: string,
|
||||||
next: NextFunction,
|
next: NextFunction,
|
||||||
end: NextFunction
|
end: NextFunction,
|
||||||
|
problem_id?: string
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import Logger from '../utils/logger';
|
|||||||
proxy(superagent);
|
proxy(superagent);
|
||||||
const logger = new Logger('remote/atcoder');
|
const logger = new Logger('remote/atcoder');
|
||||||
|
|
||||||
const langs_map = {
|
const LANGS_MAP = {
|
||||||
C: {
|
C: {
|
||||||
name: 'C (GCC 9.2.1)',
|
name: 'C (GCC 9.2.1)',
|
||||||
id: 4001,
|
id: 4001,
|
||||||
@ -182,7 +182,7 @@ export default class AtcoderProvider implements IBasicProvider {
|
|||||||
next,
|
next,
|
||||||
end
|
end
|
||||||
) {
|
) {
|
||||||
const programType = langs_map[lang] || langs_map['C++'];
|
const programType = LANGS_MAP[lang] || LANGS_MAP['C++'];
|
||||||
const comment = programType.comment;
|
const comment = programType.comment;
|
||||||
|
|
||||||
if (comment) {
|
if (comment) {
|
||||||
@ -244,90 +244,96 @@ export default class AtcoderProvider implements IBasicProvider {
|
|||||||
.getAttribute('data-id');
|
.getAttribute('data-id');
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForSubmission(problem_id: string, id: string, next, end) {
|
async waitForSubmission(id: string, next, end, problem_id: string) {
|
||||||
let i = 0;
|
let count = 0;
|
||||||
|
let fail = 0;
|
||||||
|
|
||||||
const [contestId] = parseProblemId(problem_id);
|
const [contestId] = parseProblemId(problem_id);
|
||||||
const status_url = `/contests/${contestId}/submissions/me/status/json?reload=true&sids[]=${id}`;
|
const status_url = `/contests/${contestId}/submissions/me/status/json?reload=true&sids[]=${id}`;
|
||||||
|
|
||||||
while (true) {
|
while (count < 180 && fail < 10) {
|
||||||
if (++i > 180) {
|
count++;
|
||||||
return await end({
|
|
||||||
id,
|
|
||||||
error: true,
|
|
||||||
status: 'Judgment Failed',
|
|
||||||
message: 'Failed to fetch submission details.',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
const { body, error, header } = await this.get(status_url).retry(3);
|
|
||||||
|
|
||||||
if (header['set-cookie']) {
|
try {
|
||||||
this.cookie = header['set-cookie'];
|
const { body, header } = await this.get(status_url).retry(3);
|
||||||
}
|
|
||||||
|
|
||||||
if (error) continue;
|
if (header['set-cookie']) {
|
||||||
|
this.cookie = header['set-cookie'];
|
||||||
|
}
|
||||||
|
|
||||||
const result = body.Result[id];
|
const result = body.Result[id];
|
||||||
const {
|
const {
|
||||||
window: { document },
|
window: { document },
|
||||||
} = new JSDOM(`<table>${result.Html}</table>`);
|
} = new JSDOM(`<table>${result.Html}</table>`);
|
||||||
|
|
||||||
const elements = document.querySelectorAll('td');
|
const elements = document.querySelectorAll('td');
|
||||||
const statusTd = elements[0];
|
const statusTd = elements[0];
|
||||||
const statusElem = statusTd.querySelector('span');
|
const statusElem = statusTd.querySelector('span');
|
||||||
|
|
||||||
if (
|
if (
|
||||||
statusElem.title === 'Waiting for Judging' ||
|
statusElem.title === 'Waiting for Judging' ||
|
||||||
statusElem.title === 'Waiting for Re-judging' ||
|
statusElem.title === 'Waiting for Re-judging' ||
|
||||||
['WJ', 'WR'].includes(statusElem.innerHTML.trim())
|
['WJ', 'WR'].includes(statusElem.innerHTML.trim())
|
||||||
) {
|
) {
|
||||||
await next({ test_id: 0 });
|
await next({ test_id: 0 });
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
statusElem.title === 'Judging' ||
|
statusElem.title === 'Judging' ||
|
||||||
(statusTd.colSpan == 3 && statusTd.className.includes('waiting-judge'))
|
(statusTd.colSpan == 3 &&
|
||||||
) {
|
statusTd.className.includes('waiting-judge'))
|
||||||
await next({ test_id: /(\d+)/.exec(statusElem.innerHTML)[1] || 0 });
|
) {
|
||||||
|
await next({ test_id: /(\d+)/.exec(statusElem.innerHTML)[1] || 0 });
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (statusElem.title === 'Compilation Error') {
|
||||||
|
return await end({
|
||||||
|
id,
|
||||||
|
error: true,
|
||||||
|
status: 'Compile Error',
|
||||||
|
message: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusElem.title === 'Internal Error') {
|
||||||
|
return await end({
|
||||||
|
error: true,
|
||||||
|
status: 'Judgment Failed',
|
||||||
|
message: 'AtCoder Internal Error.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const time = parseInt(elements[1].innerHTML.trim());
|
||||||
|
const memory = parseInt(elements[2].innerHTML.trim());
|
||||||
|
|
||||||
if (statusElem.title === 'Compilation Error') {
|
|
||||||
return await end({
|
return await end({
|
||||||
id,
|
id,
|
||||||
error: true,
|
status: statusElem.title || 'None',
|
||||||
status: 'Compile Error',
|
score:
|
||||||
message: '',
|
statusElem.title === 'Accepted' ||
|
||||||
|
statusElem.innerHTML.trim() === 'AC'
|
||||||
|
? 100
|
||||||
|
: 0,
|
||||||
|
time,
|
||||||
|
memory,
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(e);
|
||||||
|
|
||||||
|
fail++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statusElem.title === 'Internal Error') {
|
|
||||||
return await end({
|
|
||||||
error: true,
|
|
||||||
status: 'Judgment Failed',
|
|
||||||
message: 'AtCoder Internal Error.',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const time = parseInt(elements[1].innerHTML.trim());
|
|
||||||
const memory = parseInt(elements[2].innerHTML.trim());
|
|
||||||
|
|
||||||
return await end({
|
|
||||||
id,
|
|
||||||
status: statusElem.title || 'None',
|
|
||||||
score:
|
|
||||||
statusElem.title === 'Accepted' ||
|
|
||||||
statusElem.innerHTML.trim() === 'AC'
|
|
||||||
? 100
|
|
||||||
: 0,
|
|
||||||
time,
|
|
||||||
memory,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return await end({
|
||||||
|
id,
|
||||||
|
error: true,
|
||||||
|
status: 'Judgment Failed',
|
||||||
|
message: 'Failed to fetch submission details.',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -344,104 +344,113 @@ export default class CodeforcesProvider implements IBasicProvider {
|
|||||||
.getAttribute('data-submission-id');
|
.getAttribute('data-submission-id');
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForSubmission(problem_id: string, id: string, next, end) {
|
async waitForSubmission(id: string, next, end) {
|
||||||
let i = 0;
|
let count = 0;
|
||||||
|
let fail = 0;
|
||||||
while (true) {
|
|
||||||
if (++i > 180) {
|
|
||||||
return await end({
|
|
||||||
id,
|
|
||||||
error: true,
|
|
||||||
status: 'Judgment Failed',
|
|
||||||
message: 'Failed to fetch submission details.',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
while (count < 180 && fail < 10) {
|
||||||
|
count++;
|
||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
const { body, error } = await this.post('/data/submitSource')
|
|
||||||
.send({
|
try {
|
||||||
csrf_token: this.csrf,
|
const { body } = await this.post('/data/submitSource')
|
||||||
submissionId: id,
|
.send({
|
||||||
})
|
csrf_token: this.csrf,
|
||||||
.retry(3);
|
submissionId: id,
|
||||||
if (error) continue;
|
})
|
||||||
if (body.compilationError === 'true') {
|
.retry(3);
|
||||||
|
|
||||||
|
if (body.compilationError === 'true') {
|
||||||
|
return await end({
|
||||||
|
id,
|
||||||
|
error: true,
|
||||||
|
status: 'Compile Error',
|
||||||
|
message: crlf(body['checkerStdoutAndStderr#1'], LF),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const time = mathSum(
|
||||||
|
Object.keys(body)
|
||||||
|
.filter(k => k.startsWith('timeConsumed#'))
|
||||||
|
.map(k => +body[k])
|
||||||
|
);
|
||||||
|
const memory =
|
||||||
|
Math.max(
|
||||||
|
...Object.keys(body)
|
||||||
|
.filter(k => k.startsWith('memoryConsumed#'))
|
||||||
|
.map(k => +body[k])
|
||||||
|
) / 1024;
|
||||||
|
await next({ test_id: body.testCount });
|
||||||
|
|
||||||
|
if (body.waiting === 'true') continue;
|
||||||
|
|
||||||
|
const testCount = +body.testCount;
|
||||||
|
const status =
|
||||||
|
VERDICT[
|
||||||
|
Object.keys(VERDICT).find(k => normalize(body.verdict).includes(k))
|
||||||
|
];
|
||||||
|
let tests: string[] = [];
|
||||||
|
|
||||||
|
for (let i = 1; i <= testCount; i++) {
|
||||||
|
let test_info = '';
|
||||||
|
let info_text =
|
||||||
|
VERDICT[
|
||||||
|
Object.keys(VERDICT).find(k =>
|
||||||
|
normalize(body[`verdict#${i}`]).includes(k)
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
test_info += `<test num="${i}" info="${info_text}" time="${
|
||||||
|
body[`timeConsumed#${i}`]
|
||||||
|
}" memory="${+body[`memoryConsumed#${i}`] / 1024}">`;
|
||||||
|
|
||||||
|
const parse = (id: string) => crlf(body[id], LF);
|
||||||
|
|
||||||
|
test_info += `<in>${parse(`input#${i}`)}</in>\n`;
|
||||||
|
test_info += `<out>${parse(`output#${i}`)}</out>\n`;
|
||||||
|
test_info += `<ans>${parse(`answer#${i}`)}</ans>\n`;
|
||||||
|
test_info += `<res>${parse(`checkerStdoutAndStderr#${i}`)}</res>\n`;
|
||||||
|
|
||||||
|
test_info += '</test>';
|
||||||
|
|
||||||
|
tests.push(test_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
const remote_handle = stripHtml(body.partyName).result;
|
||||||
|
const details =
|
||||||
|
'<div>' +
|
||||||
|
'<div class="border-bottom p-3">' +
|
||||||
|
`<p><b>Contest:</b> ${stripHtml(body.contestName).result}</p>` +
|
||||||
|
`<p><b>Problem:</b> ${stripHtml(body.problemName).result}</p>` +
|
||||||
|
`<p><b>Remote submission:</b> <a href="https://codeforces.com${body.href}" target="_blank">${id}</a></p>` +
|
||||||
|
`<p><b>Remote account:</b> <a href="https://codeforces.com/profile/${remote_handle}" target="_blank">${remote_handle}</a></p>` +
|
||||||
|
`<p class="mb-0"><b>Verdict:</b> ${
|
||||||
|
stripHtml(body.verdict).result
|
||||||
|
}</p>` +
|
||||||
|
'</div>' +
|
||||||
|
`<tests>${tests.join('\n')}</tests>` +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
return await end({
|
return await end({
|
||||||
id,
|
id,
|
||||||
error: true,
|
status,
|
||||||
status: 'Compile Error',
|
score: status === 'Accepted' ? 100 : 0,
|
||||||
message: crlf(body['checkerStdoutAndStderr#1'], LF),
|
time,
|
||||||
|
memory,
|
||||||
|
details,
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(e);
|
||||||
|
|
||||||
|
fail++;
|
||||||
}
|
}
|
||||||
const time = mathSum(
|
|
||||||
Object.keys(body)
|
|
||||||
.filter(k => k.startsWith('timeConsumed#'))
|
|
||||||
.map(k => +body[k])
|
|
||||||
);
|
|
||||||
const memory =
|
|
||||||
Math.max(
|
|
||||||
...Object.keys(body)
|
|
||||||
.filter(k => k.startsWith('memoryConsumed#'))
|
|
||||||
.map(k => +body[k])
|
|
||||||
) / 1024;
|
|
||||||
await next({ test_id: body.testCount });
|
|
||||||
if (body.waiting === 'true') continue;
|
|
||||||
|
|
||||||
const testCount = +body.testCount;
|
|
||||||
const status =
|
|
||||||
VERDICT[
|
|
||||||
Object.keys(VERDICT).find(k => normalize(body.verdict).includes(k))
|
|
||||||
];
|
|
||||||
let tests: string[] = [];
|
|
||||||
|
|
||||||
for (let i = 1; i <= testCount; i++) {
|
|
||||||
let test_info = '';
|
|
||||||
let info_text =
|
|
||||||
VERDICT[
|
|
||||||
Object.keys(VERDICT).find(k =>
|
|
||||||
normalize(body[`verdict#${i}`]).includes(k)
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
test_info += `<test num="${i}" info="${info_text}" time="${
|
|
||||||
body[`timeConsumed#${i}`]
|
|
||||||
}" memory="${+body[`memoryConsumed#${i}`] / 1024}">`;
|
|
||||||
|
|
||||||
const parse = (id: string) => crlf(body[id], LF);
|
|
||||||
|
|
||||||
test_info += `<in>${parse(`input#${i}`)}</in>\n`;
|
|
||||||
test_info += `<out>${parse(`output#${i}`)}</out>\n`;
|
|
||||||
test_info += `<ans>${parse(`answer#${i}`)}</ans>\n`;
|
|
||||||
test_info += `<res>${parse(`checkerStdoutAndStderr#${i}`)}</res>\n`;
|
|
||||||
|
|
||||||
test_info += '</test>';
|
|
||||||
|
|
||||||
tests.push(test_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
const remote_handle = stripHtml(body.partyName).result;
|
|
||||||
const details =
|
|
||||||
'<div>' +
|
|
||||||
'<div class="border-bottom p-3">' +
|
|
||||||
`<p><b>Contest:</b> ${stripHtml(body.contestName).result}</p>` +
|
|
||||||
`<p><b>Problem:</b> ${stripHtml(body.problemName).result}</p>` +
|
|
||||||
`<p><b>Remote submission:</b> <a href="https://codeforces.com${body.href}" target="_blank">${id}</a></p>` +
|
|
||||||
`<p><b>Remote account:</b> <a href="https://codeforces.com/profile/${remote_handle}" target="_blank">${remote_handle}</a></p>` +
|
|
||||||
`<p class="mb-0"><b>Verdict:</b> ${
|
|
||||||
stripHtml(body.verdict).result
|
|
||||||
}</p>` +
|
|
||||||
'</div>' +
|
|
||||||
`<tests>${tests.join('\n')}</tests>` +
|
|
||||||
'</div>';
|
|
||||||
|
|
||||||
return await end({
|
|
||||||
id,
|
|
||||||
status,
|
|
||||||
score: status === 'Accepted' ? 100 : 0,
|
|
||||||
time,
|
|
||||||
memory,
|
|
||||||
details,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return await end({
|
||||||
|
id,
|
||||||
|
error: true,
|
||||||
|
status: 'Judgment Failed',
|
||||||
|
message: 'Failed to fetch submission details.',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -285,7 +285,7 @@ export default class LibreojProvider implements IBasicProvider {
|
|||||||
return user_id === submission_user_id;
|
return user_id === submission_user_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForSubmission(problem_id: string, id: string, next, end) {
|
async waitForSubmission(id: string, next, end, problem_id: string) {
|
||||||
if (!(await this.ensureLogin())) {
|
if (!(await this.ensureLogin())) {
|
||||||
await end({
|
await end({
|
||||||
error: true,
|
error: true,
|
||||||
@ -296,226 +296,234 @@ export default class LibreojProvider implements IBasicProvider {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let i = 0;
|
let count = 0;
|
||||||
|
let fail = 0;
|
||||||
while (true) {
|
|
||||||
if (++i > 180) {
|
|
||||||
return await end({
|
|
||||||
id,
|
|
||||||
error: true,
|
|
||||||
status: 'Judgment Failed',
|
|
||||||
message: 'Failed to fetch submission details.',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
while (count < 180 && fail < 10) {
|
||||||
|
count++;
|
||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
const { body, error } = await this.post('/submission/getSubmissionDetail')
|
|
||||||
.send({ submissionId: String(id), locale: 'zh_CN' })
|
|
||||||
.retry(3);
|
|
||||||
|
|
||||||
if (error) continue;
|
try {
|
||||||
|
const { body } = await this.post('/submission/getSubmissionDetail')
|
||||||
|
.send({ submissionId: String(id), locale: 'zh_CN' })
|
||||||
|
.retry(3);
|
||||||
|
|
||||||
if (body.meta.problem.displayId != problem_id) {
|
if (body.meta.problem.displayId != problem_id) {
|
||||||
return await end({
|
return await end({
|
||||||
id,
|
id,
|
||||||
error: true,
|
error: true,
|
||||||
status: 'Judgment Failed',
|
status: 'Judgment Failed',
|
||||||
message: 'Submission does not match current problem.',
|
message: 'Submission does not match current problem.',
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (!body.progress) {
|
|
||||||
await next({ status: 'Waiting for Remote Judge' });
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await next({
|
|
||||||
status: `${body.progress.progressType}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (body.progress.progressType !== 'Finished') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const status =
|
|
||||||
VERDICT[
|
|
||||||
Object.keys(VERDICT).find(k =>
|
|
||||||
normalize(body.meta.status).includes(k)
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
if (status === 'Compile Error') {
|
|
||||||
await end({
|
|
||||||
error: true,
|
|
||||||
id,
|
|
||||||
status: 'Compile Error',
|
|
||||||
message: stripVTControlCharacters(body.progress.compile.message),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status === 'Judgment Failed') {
|
|
||||||
await end({
|
|
||||||
error: true,
|
|
||||||
id,
|
|
||||||
status: 'Judgment Failed',
|
|
||||||
message: 'Error occurred on remote online judge.',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const parse = (str: string) => crlf(str, LF);
|
|
||||||
|
|
||||||
const getSubtaskStatusDisplayText = (testcases: any): string => {
|
|
||||||
let result: string = null;
|
|
||||||
|
|
||||||
for (const testcase of testcases) {
|
|
||||||
if (!testcase.testcaseHash) {
|
|
||||||
result = 'Skipped';
|
|
||||||
|
|
||||||
break;
|
|
||||||
} else if (
|
|
||||||
body.progress.testcaseResult[testcase.testcaseHash].status !==
|
|
||||||
'Accepted'
|
|
||||||
) {
|
|
||||||
result = body.progress.testcaseResult[testcase.testcaseHash].status;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result ||= 'Accepted';
|
if (!body.progress) {
|
||||||
result =
|
await next({ status: 'Waiting for Remote Judge' });
|
||||||
VERDICT[
|
|
||||||
Object.keys(VERDICT).find(k => normalize(result).includes(k))
|
|
||||||
];
|
|
||||||
|
|
||||||
return result;
|
continue;
|
||||||
};
|
}
|
||||||
|
|
||||||
const getTestcaseBlock = (id: string, index: number): string => {
|
await next({
|
||||||
const testcase = body.progress.testcaseResult[id];
|
status: `${body.progress.progressType}`,
|
||||||
|
});
|
||||||
|
|
||||||
if (!testcase) return '';
|
if (body.progress.progressType !== 'Finished') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const status =
|
const status =
|
||||||
VERDICT[
|
VERDICT[
|
||||||
Object.keys(VERDICT).find(k =>
|
Object.keys(VERDICT).find(k =>
|
||||||
normalize(testcase.status).includes(k)
|
normalize(body.meta.status).includes(k)
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
let test_info = '';
|
|
||||||
|
|
||||||
test_info += `<test num="${
|
if (status === 'Compile Error') {
|
||||||
index + 1
|
await end({
|
||||||
}" info="${status}" time="${Math.round(testcase.time || 0)}" memory="${
|
error: true,
|
||||||
testcase.memory
|
id,
|
||||||
}">`;
|
status: 'Compile Error',
|
||||||
|
message: stripVTControlCharacters(body.progress.compile.message),
|
||||||
if (testcase.input) {
|
});
|
||||||
if (typeof testcase.input === 'string') {
|
|
||||||
test_info += `<in>${parse(testcase.input)}</in>\n`;
|
|
||||||
} else {
|
|
||||||
test_info += `<in>${parse(testcase.input.data)}\n\n(${
|
|
||||||
testcase.input.omittedLength
|
|
||||||
} bytes omitted)</in>\n`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (testcase.userOutput) {
|
if (status === 'Judgment Failed') {
|
||||||
if (typeof testcase.userOutput === 'string') {
|
await end({
|
||||||
test_info += `<out>${parse(testcase.userOutput)}</out>\n`;
|
error: true,
|
||||||
} else {
|
id,
|
||||||
test_info += `<out>${parse(testcase.userOutput.data)}\n\n(${
|
status: 'Judgment Failed',
|
||||||
testcase.userOutput.omittedLength
|
message: 'Error occurred on remote online judge.',
|
||||||
} bytes omitted)</out>\n`;
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (testcase.output) {
|
const parse = (str: string) => crlf(str, LF);
|
||||||
if (typeof testcase.output === 'string') {
|
|
||||||
test_info += `<ans>${parse(testcase.output)}</ans>\n`;
|
const getSubtaskStatusDisplayText = (testcases: any): string => {
|
||||||
} else {
|
let result: string = null;
|
||||||
test_info += `<ans>${parse(testcase.output.data)}\n\n(${
|
|
||||||
testcase.output.omittedLength
|
for (const testcase of testcases) {
|
||||||
} bytes omitted)</ans>\n`;
|
if (!testcase.testcaseHash) {
|
||||||
|
result = 'Skipped';
|
||||||
|
|
||||||
|
break;
|
||||||
|
} else if (
|
||||||
|
body.progress.testcaseResult[testcase.testcaseHash].status !==
|
||||||
|
'Accepted'
|
||||||
|
) {
|
||||||
|
result =
|
||||||
|
body.progress.testcaseResult[testcase.testcaseHash].status;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result ||= 'Accepted';
|
||||||
|
result =
|
||||||
|
VERDICT[
|
||||||
|
Object.keys(VERDICT).find(k => normalize(result).includes(k))
|
||||||
|
];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTestcaseBlock = (id: string, index: number): string => {
|
||||||
|
const testcase = body.progress.testcaseResult[id];
|
||||||
|
|
||||||
|
if (!testcase) return '';
|
||||||
|
|
||||||
|
const status =
|
||||||
|
VERDICT[
|
||||||
|
Object.keys(VERDICT).find(k =>
|
||||||
|
normalize(testcase.status).includes(k)
|
||||||
|
)
|
||||||
|
];
|
||||||
|
let test_info = '';
|
||||||
|
|
||||||
|
test_info += `<test num="${
|
||||||
|
index + 1
|
||||||
|
}" info="${status}" time="${Math.round(
|
||||||
|
testcase.time || 0
|
||||||
|
)}" memory="${testcase.memory}">`;
|
||||||
|
|
||||||
|
if (testcase.input) {
|
||||||
|
if (typeof testcase.input === 'string') {
|
||||||
|
test_info += `<in>${parse(testcase.input)}</in>\n`;
|
||||||
|
} else {
|
||||||
|
test_info += `<in>${parse(testcase.input.data)}\n\n(${
|
||||||
|
testcase.input.omittedLength
|
||||||
|
} bytes omitted)</in>\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (testcase.userOutput) {
|
||||||
|
if (typeof testcase.userOutput === 'string') {
|
||||||
|
test_info += `<out>${parse(testcase.userOutput)}</out>\n`;
|
||||||
|
} else {
|
||||||
|
test_info += `<out>${parse(testcase.userOutput.data)}\n\n(${
|
||||||
|
testcase.userOutput.omittedLength
|
||||||
|
} bytes omitted)</out>\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (testcase.output) {
|
||||||
|
if (typeof testcase.output === 'string') {
|
||||||
|
test_info += `<ans>${parse(testcase.output)}</ans>\n`;
|
||||||
|
} else {
|
||||||
|
test_info += `<ans>${parse(testcase.output.data)}\n\n(${
|
||||||
|
testcase.output.omittedLength
|
||||||
|
} bytes omitted)</ans>\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (testcase.checkerMessage) {
|
||||||
|
if (typeof testcase.checkerMessage === 'string') {
|
||||||
|
test_info += `<res>${parse(testcase.checkerMessage)}</res>\n`;
|
||||||
|
} else {
|
||||||
|
test_info += `<res>${parse(testcase.checkerMessage.data)}\n\n(${
|
||||||
|
testcase.checkerMessage.omittedLength
|
||||||
|
} bytes omitted)</res>\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_info += '</test>';
|
||||||
|
|
||||||
|
return test_info;
|
||||||
|
};
|
||||||
|
|
||||||
|
let details = '';
|
||||||
|
|
||||||
|
details +=
|
||||||
|
'<div class="border-bottom p-3">' +
|
||||||
|
`<p><b>Problem:</b> #${body.meta.problem.displayId}. ${body.meta.problemTitle}</p>` +
|
||||||
|
`<p><b>Remote submission:</b> <a href="https://loj.ac/s/${id}" target="_blank">${id}</a></p>` +
|
||||||
|
`<p><b>Remote submit time:</b> ${new Date(
|
||||||
|
body.meta.submitTime
|
||||||
|
).toLocaleString('zh-CN')}</p>` +
|
||||||
|
`<p><b>Remote account:</b> <a href="https://loj.ac/user/${body.meta.submitter.id}" target="_blank">${body.meta.submitter.username}</a></p>` +
|
||||||
|
`<p class="mb-0"><b>Verdict:</b> ${status}</p>` +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
// Samples
|
||||||
|
if (body.progress.samples) {
|
||||||
|
details += `<subtask title="Samples" info="${getSubtaskStatusDisplayText(
|
||||||
|
body.progress.samples
|
||||||
|
)}" num="0">${body.progress.samples
|
||||||
|
.map((item, index) =>
|
||||||
|
item.testcaseHash
|
||||||
|
? getTestcaseBlock(item.testcaseHash, index)
|
||||||
|
: `<test num="${index + 1}" info="Skipped"></test>`
|
||||||
|
)
|
||||||
|
.join('\n')}</subtask>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (testcase.checkerMessage) {
|
// Tests
|
||||||
if (typeof testcase.checkerMessage === 'string') {
|
if (body.progress.subtasks.length === 1) {
|
||||||
test_info += `<res>${parse(testcase.checkerMessage)}</res>\n`;
|
details += `<tests>${body.progress.subtasks[0].testcases
|
||||||
} else {
|
.map((item, index) =>
|
||||||
test_info += `<res>${parse(testcase.checkerMessage.data)}\n\n(${
|
item.testcaseHash
|
||||||
testcase.checkerMessage.omittedLength
|
? getTestcaseBlock(item.testcaseHash, index)
|
||||||
} bytes omitted)</res>\n`;
|
: `<test num="${index + 1}" info="Skipped"></test>`
|
||||||
}
|
)
|
||||||
|
.join('\n')}</tests>`;
|
||||||
|
} else {
|
||||||
|
details += `<tests>${body.progress.subtasks
|
||||||
|
.map(
|
||||||
|
(subtask, index) =>
|
||||||
|
`<subtask num="${
|
||||||
|
index + 1
|
||||||
|
}" info="${getSubtaskStatusDisplayText(
|
||||||
|
subtask.testcases
|
||||||
|
)}">${subtask.testcases
|
||||||
|
.map((item, index) =>
|
||||||
|
item.testcaseHash
|
||||||
|
? getTestcaseBlock(item.testcaseHash, index)
|
||||||
|
: `<test num="${index + 1}" info="Skipped"></test>`
|
||||||
|
)
|
||||||
|
.join('\n')}</subtask>`
|
||||||
|
)
|
||||||
|
.join('\n')}</tests>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
test_info += '</test>';
|
return await end({
|
||||||
|
id,
|
||||||
|
status: body.meta.status,
|
||||||
|
score: body.meta.score,
|
||||||
|
time: body.meta.timeUsed,
|
||||||
|
memory: body.meta.memoryUsed,
|
||||||
|
details: `<div>${details}</div>`,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(e);
|
||||||
|
|
||||||
return test_info;
|
fail++;
|
||||||
};
|
|
||||||
|
|
||||||
let details = '';
|
|
||||||
|
|
||||||
details +=
|
|
||||||
'<div class="border-bottom p-3">' +
|
|
||||||
`<p><b>Problem:</b> #${body.meta.problem.displayId}. ${body.meta.problemTitle}</p>` +
|
|
||||||
`<p><b>Remote submission:</b> <a href="https://loj.ac/s/${id}" target="_blank">${id}</a></p>` +
|
|
||||||
`<p><b>Remote submit time:</b> ${new Date(
|
|
||||||
body.meta.submitTime
|
|
||||||
).toLocaleString('zh-CN')}</p>` +
|
|
||||||
`<p><b>Remote account:</b> <a href="https://loj.ac/user/${body.meta.submitter.id}" target="_blank">${body.meta.submitter.username}</a></p>` +
|
|
||||||
`<p class="mb-0"><b>Verdict:</b> ${status}</p>` +
|
|
||||||
'</div>';
|
|
||||||
|
|
||||||
// Samples
|
|
||||||
if (body.progress.samples) {
|
|
||||||
details += `<subtask title="Samples" info="${getSubtaskStatusDisplayText(
|
|
||||||
body.progress.samples
|
|
||||||
)}" num="0">${body.progress.samples
|
|
||||||
.map((item, index) =>
|
|
||||||
item.testcaseHash
|
|
||||||
? getTestcaseBlock(item.testcaseHash, index)
|
|
||||||
: `<test num="${index + 1}" info="Skipped"></test>`
|
|
||||||
)
|
|
||||||
.join('\n')}</subtask>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests
|
|
||||||
if (body.progress.subtasks.length === 1) {
|
|
||||||
details += `<tests>${body.progress.subtasks[0].testcases
|
|
||||||
.map((item, index) =>
|
|
||||||
item.testcaseHash
|
|
||||||
? getTestcaseBlock(item.testcaseHash, index)
|
|
||||||
: `<test num="${index + 1}" info="Skipped"></test>`
|
|
||||||
)
|
|
||||||
.join('\n')}</tests>`;
|
|
||||||
} else {
|
|
||||||
details += `<tests>${body.progress.subtasks
|
|
||||||
.map(
|
|
||||||
(subtask, index) =>
|
|
||||||
`<subtask num="${index + 1}" info="${getSubtaskStatusDisplayText(
|
|
||||||
subtask.testcases
|
|
||||||
)}">${subtask.testcases
|
|
||||||
.map((item, index) =>
|
|
||||||
item.testcaseHash
|
|
||||||
? getTestcaseBlock(item.testcaseHash, index)
|
|
||||||
: `<test num="${index + 1}" info="Skipped"></test>`
|
|
||||||
)
|
|
||||||
.join('\n')}</subtask>`
|
|
||||||
)
|
|
||||||
.join('\n')}</tests>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await end({
|
|
||||||
id,
|
|
||||||
status: body.meta.status,
|
|
||||||
score: body.meta.score,
|
|
||||||
time: body.meta.timeUsed,
|
|
||||||
memory: body.meta.memoryUsed,
|
|
||||||
details: `<div>${details}</div>`,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return await end({
|
||||||
|
id,
|
||||||
|
error: true,
|
||||||
|
status: 'Judgment Failed',
|
||||||
|
message: 'Failed to fetch submission details.',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -250,11 +250,11 @@ export default class LuoguProvider implements IBasicProvider {
|
|||||||
return result.body.rid;
|
return result.body.rid;
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForSubmission(problem_id: string, id: string, next, end) {
|
async waitForSubmission(id: string, next, end) {
|
||||||
let fail = 0;
|
let fail = 0;
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
while (count < 180 && fail < 5) {
|
while (count < 180 && fail < 10) {
|
||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
count++;
|
count++;
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import sleep from '../utils/sleep';
|
|||||||
proxy(superagent);
|
proxy(superagent);
|
||||||
const logger = new Logger('remote/uoj');
|
const logger = new Logger('remote/uoj');
|
||||||
|
|
||||||
const langs_map = {
|
const LANGS_MAP = {
|
||||||
C: {
|
C: {
|
||||||
name: 'C',
|
name: 'C',
|
||||||
id: 'C',
|
id: 'C',
|
||||||
@ -175,7 +175,7 @@ export default class UOJProvider implements IBasicProvider {
|
|||||||
next,
|
next,
|
||||||
end
|
end
|
||||||
) {
|
) {
|
||||||
const programType = langs_map[lang] || langs_map['C++'];
|
const programType = LANGS_MAP[lang] || LANGS_MAP['C++'];
|
||||||
const comment = programType.comment;
|
const comment = programType.comment;
|
||||||
|
|
||||||
if (comment) {
|
if (comment) {
|
||||||
@ -206,65 +206,72 @@ export default class UOJProvider implements IBasicProvider {
|
|||||||
.innerHTML.split('#')[1];
|
.innerHTML.split('#')[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForSubmission(problem_id: string, id: string, next, end) {
|
async waitForSubmission(id: string, next, end) {
|
||||||
let i = 0;
|
let count = 0;
|
||||||
|
let fail = 0;
|
||||||
while (true) {
|
|
||||||
if (++i > 180) {
|
|
||||||
return await end({
|
|
||||||
id,
|
|
||||||
error: true,
|
|
||||||
status: 'Judgment Failed',
|
|
||||||
message: 'Failed to fetch submission details.',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
while (count < 180 && fail < 10) {
|
||||||
|
count++;
|
||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
const { text } = await this.get(`/submission/${id}`);
|
|
||||||
const {
|
try {
|
||||||
window: { document },
|
const { text } = await this.get(`/submission/${id}`);
|
||||||
} = new JSDOM(text);
|
const {
|
||||||
const find = (content: string) =>
|
window: { document },
|
||||||
Array.from(
|
} = new JSDOM(text);
|
||||||
document.querySelectorAll('.panel-heading>.panel-title')
|
const find = (content: string) =>
|
||||||
).find(n => n.innerHTML === content).parentElement.parentElement
|
Array.from(
|
||||||
.children[1];
|
document.querySelectorAll('.panel-heading>.panel-title')
|
||||||
if (text.includes('Compile Error')) {
|
).find(n => n.innerHTML === content).parentElement.parentElement
|
||||||
|
.children[1];
|
||||||
|
if (text.includes('Compile Error')) {
|
||||||
|
return await end({
|
||||||
|
error: true,
|
||||||
|
id,
|
||||||
|
status: 'Compile Error',
|
||||||
|
message: find('详细').children[0].innerHTML,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await next({});
|
||||||
|
|
||||||
|
const summary = document.querySelector('tbody>tr');
|
||||||
|
if (!summary) continue;
|
||||||
|
const time = parseTimeMS(summary.children[4].innerHTML);
|
||||||
|
const memory = parseMemoryMB(summary.children[5].innerHTML) * 1024;
|
||||||
|
let panel = document.getElementById(
|
||||||
|
'details_details_accordion_collapse_subtask_1'
|
||||||
|
);
|
||||||
|
if (!panel) {
|
||||||
|
panel = document.getElementById('details_details_accordion');
|
||||||
|
if (!panel) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.querySelector('tbody').innerHTML.includes('Judging'))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const score = +summary.children[3]?.children[0]?.innerHTML || 0;
|
||||||
|
const status = score === 100 ? 'Accepted' : 'Unaccepted';
|
||||||
|
|
||||||
return await end({
|
return await end({
|
||||||
error: true,
|
|
||||||
id,
|
id,
|
||||||
status: 'Compile Error',
|
status,
|
||||||
message: find('详细').children[0].innerHTML,
|
score,
|
||||||
|
time,
|
||||||
|
memory,
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(e);
|
||||||
|
|
||||||
|
fail++;
|
||||||
}
|
}
|
||||||
|
|
||||||
await next({});
|
|
||||||
|
|
||||||
const summary = document.querySelector('tbody>tr');
|
|
||||||
if (!summary) continue;
|
|
||||||
const time = parseTimeMS(summary.children[4].innerHTML);
|
|
||||||
const memory = parseMemoryMB(summary.children[5].innerHTML) * 1024;
|
|
||||||
let panel = document.getElementById(
|
|
||||||
'details_details_accordion_collapse_subtask_1'
|
|
||||||
);
|
|
||||||
if (!panel) {
|
|
||||||
panel = document.getElementById('details_details_accordion');
|
|
||||||
if (!panel) continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.querySelector('tbody').innerHTML.includes('Judging'))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const score = +summary.children[3]?.children[0]?.innerHTML || 0;
|
|
||||||
const status = score === 100 ? 'Accepted' : 'Unaccepted';
|
|
||||||
|
|
||||||
return await end({
|
|
||||||
id,
|
|
||||||
status,
|
|
||||||
score,
|
|
||||||
time,
|
|
||||||
memory,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return await end({
|
||||||
|
id,
|
||||||
|
error: true,
|
||||||
|
status: 'Judgment Failed',
|
||||||
|
message: 'Failed to fetch submission details.',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ class AccountService {
|
|||||||
|
|
||||||
if (!rid) return;
|
if (!rid) return;
|
||||||
|
|
||||||
await this.api.waitForSubmission(problem_id, rid, next, end);
|
await this.api.waitForSubmission(rid, next, end, problem_id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(e);
|
logger.error(e);
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ class VJudge {
|
|||||||
|
|
||||||
if (!rid) return;
|
if (!rid) return;
|
||||||
|
|
||||||
await provider.waitForSubmission(problem_id, rid, next, end);
|
await provider.waitForSubmission(rid, next, end, problem_id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(e);
|
logger.error(e);
|
||||||
|
|
||||||
@ -204,10 +204,10 @@ class VJudge {
|
|||||||
|
|
||||||
if (await provider.ensureIsOwnSubmission(config.remote_submission_id)) {
|
if (await provider.ensureIsOwnSubmission(config.remote_submission_id)) {
|
||||||
await provider.waitForSubmission(
|
await provider.waitForSubmission(
|
||||||
problem_id,
|
|
||||||
config.remote_submission_id,
|
config.remote_submission_id,
|
||||||
next,
|
next,
|
||||||
end
|
end,
|
||||||
|
problem_id
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return await end({
|
return await end({
|
||||||
|
Loading…
Reference in New Issue
Block a user