Browse Source

Support interaction problems.

master
t123yh 7 years ago
parent
commit
e4c75f2a3f
  1. 8
      package-lock.json
  2. 1
      package.json
  3. 5
      src/daemon/judge/index.ts
  4. 90
      src/daemon/judge/interaction.ts
  5. 10
      src/interfaces.ts
  6. 4
      src/runner/index.ts
  7. 163
      src/runner/judge.ts
  8. 27
      src/runner/run.ts

8
package-lock.json generated

@ -1905,6 +1905,14 @@
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz",
"integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=" "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo="
}, },
"syspipe": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/syspipe/-/syspipe-0.1.5.tgz",
"integrity": "sha1-TNs0IP5ZhV8w+9jwp8/bIoFPCC4=",
"requires": {
"nan": "2.4.0"
}
},
"tar": { "tar": {
"version": "3.2.1", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-3.2.1.tgz", "resolved": "https://registry.npmjs.org/tar/-/tar-3.2.1.tgz",

1
package.json

@ -33,6 +33,7 @@
"simple-sandbox": "^0.3.5", "simple-sandbox": "^0.3.5",
"socket.io": "^2.0.3", "socket.io": "^2.0.3",
"source-map-support": "^0.4.16", "source-map-support": "^0.4.16",
"syspipe": "^0.1.5",
"tar": "^3.2.1", "tar": "^3.2.1",
"uuid": "^3.1.0", "uuid": "^3.1.0",
"winston": "^2.3.1" "winston": "^2.3.1"

5
src/daemon/judge/index.ts

@ -1,13 +1,14 @@
import winston = require('winston'); import winston = require('winston');
import rmq = require('../rmq'); import rmq = require('../rmq');
import { JudgeTaskContent, JudgeTask, ProblemType, TestData, StandardJudgeParameter } from '../interfaces'; import { JudgeTaskContent, JudgeTask, ProblemType, TestData, StandardJudgeParameter, InteractionJudgeParameter } from '../interfaces';
import { StandardJudger } from './standard'; import { StandardJudger } from './standard';
import { JudgerBase } from './judger-base'; import { JudgerBase } from './judger-base';
import { JudgeResult, ErrorType, OverallResult, CompilationResult, TaskStatus, ProgressReportType } from '../../interfaces'; import { JudgeResult, ErrorType, OverallResult, CompilationResult, TaskStatus, ProgressReportType } from '../../interfaces';
import { readRulesFile } from '../testData'; import { readRulesFile } from '../testData';
import { filterPath } from '../../utils'; import { filterPath } from '../../utils';
import { AnswerSubmissionJudger } from './submit-answer'; import { AnswerSubmissionJudger } from './submit-answer';
import { InteractionJudger } from './interaction';
export async function judge( export async function judge(
task: JudgeTaskContent, task: JudgeTaskContent,
@ -35,6 +36,8 @@ export async function judge(
judger = new StandardJudger(testData, task.param as StandardJudgeParameter, task.priority); judger = new StandardJudger(testData, task.param as StandardJudgeParameter, task.priority);
} else if (task.type === ProblemType.AnswerSubmission) { } else if (task.type === ProblemType.AnswerSubmission) {
judger = new AnswerSubmissionJudger(testData, extraData, task.priority); judger = new AnswerSubmissionJudger(testData, extraData, task.priority);
} else if (task.type === ProblemType.Interaction) {
judger = new InteractionJudger(testData, task.param as InteractionJudgeParameter, task.priority);
} else { } else {
throw new Error(`Task type not supported`); throw new Error(`Task type not supported`);
} }

90
src/daemon/judge/interaction.ts

@ -0,0 +1,90 @@
import { TestData, InteractionJudgeParameter, TestcaseJudge } from '../interfaces';
import { TaskStatus, ErrorType, TestcaseDetails, CompilationResult, JudgeResult, TestcaseResult, InteractionRunTask, StandardRunResult, RPCTaskType } from '../../interfaces';
import { globalConfig as Cfg } from '../config';
import { cloneObject, readFileLength } from '../../utils';
import { compile } from './compile';
import { Language, getLanguage } from '../../languages';
import { runTask } from '../rmq';
import { JudgerBase } from './judger-base';
import pathLib = require('path');
import winston = require('winston');
export class InteractionJudger extends JudgerBase {
parameters: InteractionJudgeParameter;
userCodeLanguage: Language;
interactorExecutableName: string = null;
userCodeExecuableName: string = null;
constructor(testData: TestData,
param: InteractionJudgeParameter,
priority: number) {
super(testData, priority);
this.parameters = param;
this.userCodeLanguage = getLanguage(param.language);
}
async preprocessTestData(): Promise<void> {
if (this.testData.interactor != null) {
winston.verbose("Compiling special judge.");
const [interactorExecutableName, interactorResult] = await compile(this.testData.interactor.sourceCode,
this.testData.interactor.language, null, this.priority);
if (interactorResult.status !== TaskStatus.Done) {
winston.verbose("Special judge CE: " + interactorResult.message);
let message = null;
if (interactorResult.message != null && interactorResult.message !== "") {
message = "===== Interactor Compilation Message =====" + interactorResult.message;
}
throw new Error(message);
} else {
this.interactorExecutableName = interactorExecutableName;
}
} else {
this.interactorExecutableName = null;
}
}
async compile(): Promise<CompilationResult> {
const language = getLanguage(this.parameters.language);
const [executableName, compilationResult] = await compile(
this.parameters.code,
language,
this.testData.extraSourceFiles[language.name],
this.priority
);
this.userCodeExecuableName = executableName;
return compilationResult;
}
async judgeTestcase(curCase: TestcaseJudge, started: () => Promise<void>): Promise<TestcaseDetails> {
const task: InteractionRunTask = {
testDataName: this.testData.name,
inputData: curCase.input,
answerData: curCase.output,
time: this.parameters.timeLimit,
memory: this.parameters.memoryLimit,
userExecutableName: this.userCodeExecuableName,
interactorExecutableName: this.interactorExecutableName
};
// We do not have to create a InteractionRunResult
const [inputContent, outputContent, runResult]: [string, string, StandardRunResult] = await Promise.all([
readFileLength(pathLib.join(Cfg.testDataDirectory, this.testData.name, curCase.input), Cfg.dataDisplayLimit),
readFileLength(pathLib.join(Cfg.testDataDirectory, this.testData.name, curCase.output), Cfg.dataDisplayLimit),
runTask({ type: RPCTaskType.RunStandard, task: task }, this.priority, started)
]) as any;
return {
type: runResult.result,
time: runResult.time,
memory: runResult.memory,
userError: runResult.userError,
userOutput: null,
scoringRate: runResult.scoringRate,
spjMessage: runResult.spjMessage,
input: { name: curCase.input, content: inputContent },
output: { name: curCase.output, content: outputContent },
systemMessage: runResult.systemMessage
};
}
}

10
src/interfaces.ts

@ -85,6 +85,16 @@ export interface StandardRunTask {
spjExecutableName?: string; spjExecutableName?: string;
} }
export interface InteractionRunTask {
testDataName: string;
inputData: string;
answerData: string;
time: number;
memory: number;
userExecutableName: string;
interactorExecutableName: string;
}
export interface AnswerSubmissionRunTask { export interface AnswerSubmissionRunTask {
testDataName: string; testDataName: string;
inputData: string; inputData: string;

4
src/runner/index.ts

@ -6,7 +6,7 @@ import util = require('util');
import rmq = require('./rmq'); import rmq = require('./rmq');
import { RPCRequest, RPCTaskType } from '../interfaces'; import { RPCRequest, RPCTaskType } from '../interfaces';
import { compile } from './compile'; import { compile } from './compile';
import { judgeStandard, judgeAnswerSubmission } from './judge'; import { judgeStandard, judgeAnswerSubmission, judgeInteraction } from './judge';
(async function () { (async function () {
winston.info("Runner starts."); winston.info("Runner starts.");
@ -21,6 +21,8 @@ import { judgeStandard, judgeAnswerSubmission } from './judge';
return await judgeStandard(task.task); return await judgeStandard(task.task);
} else if (task.type === RPCTaskType.RunSubmitAnswer) { } else if (task.type === RPCTaskType.RunSubmitAnswer) {
return await judgeAnswerSubmission(task.task); return await judgeAnswerSubmission(task.task);
} else if (task.type === RPCTaskType.RunInteraction) {
return await judgeInteraction(task.task);
} else { } else {
winston.warn("Task type unsupported"); winston.warn("Task type unsupported");
throw new Error(`Task type ${task.type} not supported!`); throw new Error(`Task type ${task.type} not supported!`);

163
src/runner/judge.ts

@ -2,9 +2,10 @@ import pathLib = require('path');
import randomString = require('randomstring'); import randomString = require('randomstring');
import fse = require('fs-extra'); import fse = require('fs-extra');
import winston = require('winston'); import winston = require('winston');
import syspipe = require('syspipe');
import { SandboxStatus } from 'simple-sandbox/lib/interfaces'; import { SandboxStatus } from 'simple-sandbox/lib/interfaces';
import { TestcaseResultType, StandardRunTask, StandardRunResult, AnswerSubmissionRunTask, AnswerSubmissionRunResult } from '../interfaces'; import { TestcaseResultType, StandardRunTask, StandardRunResult, InteractionRunTask, AnswerSubmissionRunTask, AnswerSubmissionRunResult } from '../interfaces';
import { createOrEmptyDir, tryEmptyDir } from './utils'; import { createOrEmptyDir, tryEmptyDir } from './utils';
import { readFileLength, tryReadFile } from '../utils'; import { readFileLength, tryReadFile } from '../utils';
import { globalConfig as Cfg } from './config'; import { globalConfig as Cfg } from './config';
@ -23,10 +24,22 @@ interface SpjResult {
} }
const spjFullScore = 100; const spjFullScore = 100;
function getStatusByScore(score: number): TestcaseResultType {
switch (score) {
case spjFullScore:
return TestcaseResultType.Accepted;
case 0:
return TestcaseResultType.WrongAnswer;
default:
return TestcaseResultType.PartiallyCorrect;
}
}
async function runSpj(spjBinDir: string, spjLanguage: Language): Promise<SpjResult> { async function runSpj(spjBinDir: string, spjLanguage: Language): Promise<SpjResult> {
const scoreFileName = 'score.txt'; const scoreFileName = 'score.txt';
const messageFileName = 'message.txt'; const messageFileName = 'message.txt';
const [spjRunResult] = await runProgram(spjLanguage, const [resultPromise] = await runProgram(spjLanguage,
spjBinDir, spjBinDir,
spjWorkingDir, spjWorkingDir,
Cfg.spjTimeLimit, Cfg.spjTimeLimit,
@ -34,6 +47,7 @@ async function runSpj(spjBinDir: string, spjLanguage: Language): Promise<SpjResu
null, null,
scoreFileName, scoreFileName,
messageFileName); messageFileName);
const spjRunResult = await resultPromise;
if (spjRunResult.result.status !== SandboxStatus.OK) { if (spjRunResult.result.status !== SandboxStatus.OK) {
return { return {
@ -53,20 +67,8 @@ async function runSpj(spjBinDir: string, spjLanguage: Language): Promise<SpjResu
score: 0 score: 0
}; };
} else { } else {
let status: TestcaseResultType;
switch (score) {
case spjFullScore:
status = TestcaseResultType.Accepted;
break;
case 0:
status = TestcaseResultType.WrongAnswer;
break;
default:
status = TestcaseResultType.PartiallyCorrect;
break;
}
return { return {
status: status, status: getStatusByScore(score),
message: messageString, message: messageString,
score: score / spjFullScore score: score / spjFullScore
}; };
@ -165,7 +167,7 @@ export async function judgeStandard(task: StandardRunTask)
const [binaryDirectory, language, userCode] = await fetchBinary(task.userExecutableName); const [binaryDirectory, language, userCode] = await fetchBinary(task.userExecutableName);
winston.debug("Running user program..."); winston.debug("Running user program...");
const [runResult] = await runProgram(language, const [resultPromise] = await runProgram(language,
binaryDirectory, binaryDirectory,
workingDir, workingDir,
task.time, task.time,
@ -173,6 +175,7 @@ export async function judgeStandard(task: StandardRunTask)
stdinRedirectionName, stdinRedirectionName,
stdoutRedirectionName, stdoutRedirectionName,
tempErrFile); tempErrFile);
const runResult = await resultPromise;
winston.verbose((task.inputData || "<none> ") + " Run result: " + JSON.stringify(runResult)); winston.verbose((task.inputData || "<none> ") + " Run result: " + JSON.stringify(runResult));
@ -252,3 +255,131 @@ export async function judgeStandard(task: StandardRunTask)
tryEmptyDir(spjWorkingDir); tryEmptyDir(spjWorkingDir);
} }
} }
export async function judgeInteraction(task: InteractionRunTask)
: Promise<StandardRunResult> {
let pipe1 = null, pipe2 = null;
try {
const testDataPath = pathLib.join(Cfg.testDataDirectory, task.testDataName);
const inputFilePath = task.inputData != null ?
pathLib.join(testDataPath, task.inputData) : null;
const answerFilePath = task.answerData != null ?
pathLib.join(testDataPath, task.answerData) : null;
winston.debug("Creating directories...");
await Promise.all([createOrEmptyDir(workingDir), createOrEmptyDir(spjWorkingDir)]);
const tempErrFile = randomString.generate(10) + ".err";
if (inputFilePath != null) {
await fse.copy(inputFilePath,
pathLib.join(spjWorkingDir, 'input'));
}
if (answerFilePath != null) {
await fse.copy(answerFilePath,
pathLib.join(spjWorkingDir, 'answer'));
}
await fse.writeFile(pathLib.join(spjWorkingDir, 'code'), task);
winston.debug("Fetching user binary...");
const [userBinaryDirectory, userLanguage, userCode] = await fetchBinary(task.userExecutableName);
winston.debug("Fetching interactor binary...");
const [interactorBinaryDirectory, interactorLanguage] = await fetchBinary(task.userExecutableName);
pipe1 = syspipe.pipe(),
pipe2 = syspipe.pipe();
const [userProgramTaskPromise, stopUser] = await runProgram(userLanguage,
userBinaryDirectory,
workingDir,
task.time,
task.memory * 1024 * 1024,
pipe1.read,
pipe2.write,
tempErrFile);
const [interactorTaskPromise] = await runProgram(interactorLanguage,
interactorBinaryDirectory,
spjWorkingDir,
task.time * 2,
task.memory * 1024 * 1024,
pipe2.read,
pipe1.write,
tempErrFile);
const [interactorResult, runResult] = await Promise.all([interactorTaskPromise
.then((result) => { stopUser(); return result; }, (err) => { stopUser(); return Promise.reject(err); }), userProgramTaskPromise]);
const userError = await readFileLength(pathLib.join(workingDir, tempErrFile), Cfg.stderrDisplayLimit);
const time = Math.round(runResult.result.time / 1e6),
memory = runResult.result.memory / 1024;
let status: TestcaseResultType = null, message = null;
if (runResult.outputLimitExceeded) {
status = TestcaseResultType.OutputLimitExceeded;
} else if (runResult.result.status === SandboxStatus.TimeLimitExceeded) {
status = TestcaseResultType.TimeLimitExceeded;
} else if (runResult.result.status === SandboxStatus.MemoryLimitExceeded) {
status = TestcaseResultType.MemoryLimitExceeded;
} else if (runResult.result.status === SandboxStatus.RuntimeError) {
message = `Killed: ${signals[runResult.result.code]}`;
status = TestcaseResultType.RuntimeError;
} else if (runResult.result.status !== SandboxStatus.OK) {
status = TestcaseResultType.RuntimeError;
} else {
message = `Exited with return code ${runResult.result.code}`;
}
if (interactorResult.result.status !== SandboxStatus.OK) {
if (interactorResult.result.status === SandboxStatus.TimeLimitExceeded) {
message = 'Interactor Time Limit Exceeded. This is likely to happen if your program stuck.';
status = TestcaseResultType.TimeLimitExceeded;
} else {
message = `A ${SandboxStatus[interactorResult.result.status]} encountered while running interactor`;
status = TestcaseResultType.JudgementFailed;
}
}
const partialResult = {
time: time,
memory: time,
userOutput: null,
userError: userError,
spjMessage: await readFileLength(pathLib.join(spjWorkingDir, tempErrFile), Cfg.stderrDisplayLimit)
};
// If interactor exited normally
let score = 0;
if (status == null) {
const scoreString = await tryReadFile(spjWorkingDir + '/score.txt');
let score = Number(scoreString);
if ((!scoreString) || isNaN(score)) {
status = TestcaseResultType.JudgementFailed;
message = `Interactor returned a non-number score ${scoreString}`;
} else if (score === -1) {
score = 0;
status = TestcaseResultType.InvalidInteraction;
} else {
score = score;
status = getStatusByScore(score);
}
}
return Object.assign(partialResult, {
result: status,
scoringRate: score / spjFullScore
});
} finally {
const closePipe = async (p) => {
try {
if (p) {
await fse.close(p.read);
await fse.close(p.write);
}
} catch (e) { }
}
await closePipe(pipe1);
await closePipe(pipe2);
await tryEmptyDir(spjWorkingDir);
await tryEmptyDir(workingDir);
}
}

27
src/runner/run.ts

@ -57,7 +57,8 @@ export async function runProgram(language: Language,
memory: number, memory: number,
stdinFile?: string | number, stdinFile?: string | number,
stdoutFile?: string | number, stdoutFile?: string | number,
stderrFile?: string | number): Promise<[RunResult, () => void]> { stderrFile?: string | number): Promise<[Promise<RunResult>, () => void]> {
await setWriteAccess(binDir, false); await setWriteAccess(binDir, false);
await setWriteAccess(dataDir, true); await setWriteAccess(dataDir, true);
@ -77,17 +78,19 @@ export async function runProgram(language: Language,
let result: SandboxResult = null; let result: SandboxResult = null;
const sandbox = await startSandbox(sandboxParam); const sandbox = await startSandbox(sandboxParam);
result = await sandbox.waitForStop(); return [(async () => {
result = await sandbox.waitForStop();
let ole = false; let ole = false;
const outputSize = await getSize(binDir); const outputSize = await getSize(binDir);
if (outputSize > Cfg.outputLimit) { if (outputSize > Cfg.outputLimit) {
await fse.emptyDir(dataDir); await fse.emptyDir(dataDir);
ole = true; ole = true;
} }
return [{ return {
outputLimitExceeded: ole, outputLimitExceeded: ole,
result: result result: result
}, () => { sandbox.stop(); }]; };
})(), () => { sandbox.stop() }];
} }

Loading…
Cancel
Save