Browse Source

Add support for answer-submission problems.

master
t123yh 7 years ago
parent
commit
5d6ffb32d5
  1. 3
      daemon-config-example.json
  2. 305
      package-lock.json
  3. 1
      package.json
  4. 8
      src/daemon-frontend-syzoj/daemonRouter.ts
  5. 4
      src/daemon/config.ts
  6. 12
      src/daemon/index.ts
  7. 13
      src/daemon/interfaces.ts
  8. 38
      src/daemon/judge/index.ts
  9. 4
      src/daemon/judge/judger-base.ts
  10. 101
      src/daemon/judge/submit-answer.ts
  11. 29
      src/interfaces.ts
  12. 9
      src/runner/index.ts
  13. 54
      src/runner/judge.ts
  14. 11
      src/utils.ts

3
daemon-config-example.json

@ -3,5 +3,6 @@
"RedisUrl": "redis://127.0.0.1:6379",
"TestData": "/home/t123yh/sync2/syzoj/uploads/testdata",
"Priority": 1,
"DataDisplayLimit": 100
"DataDisplayLimit": 100,
"TempDirectory": "/tmp"
}

305
package-lock.json generated

@ -329,6 +329,11 @@
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
"integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg="
},
"base64-js": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
"integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg="
},
"base64id": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz",
@ -369,6 +374,38 @@
"buffer-more-ints": "0.0.2"
}
},
"bl": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz",
"integrity": "sha1-ysMo977kVzDUBLaSID/LWQ4XLV4=",
"requires": {
"readable-stream": "2.3.3"
},
"dependencies": {
"readable-stream": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
"requires": {
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "1.0.0",
"process-nextick-args": "1.0.7",
"safe-buffer": "5.1.1",
"string_decoder": "1.0.3",
"util-deprecate": "1.0.2"
}
},
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"requires": {
"safe-buffer": "5.1.1"
}
}
}
},
"blob": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz",
@ -404,6 +441,21 @@
"hoek": "2.16.3"
}
},
"buffer": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-3.6.0.tgz",
"integrity": "sha1-pyyTb3e5a/UvX357RnGAYoVR3vs=",
"requires": {
"base64-js": "0.0.8",
"ieee754": "1.1.8",
"isarray": "1.0.0"
}
},
"buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI="
},
"buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
@ -474,6 +526,14 @@
"typical": "2.6.1"
}
},
"commander": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
"integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=",
"requires": {
"graceful-readlink": "1.0.1"
}
},
"component-bind": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
@ -564,6 +624,78 @@
"ms": "2.0.0"
}
},
"decompress": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.0.tgz",
"integrity": "sha1-eu3YVCflqS2s/lVnSnxQXpbQH50=",
"requires": {
"decompress-tar": "4.1.1",
"decompress-tarbz2": "4.1.1",
"decompress-targz": "4.1.1",
"decompress-unzip": "4.0.1",
"graceful-fs": "4.1.11",
"make-dir": "1.0.0",
"pify": "2.3.0",
"strip-dirs": "2.0.0"
}
},
"decompress-tar": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz",
"integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==",
"requires": {
"file-type": "5.2.0",
"is-stream": "1.1.0",
"tar-stream": "1.5.4"
}
},
"decompress-tarbz2": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz",
"integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==",
"requires": {
"decompress-tar": "4.1.1",
"file-type": "6.1.0",
"is-stream": "1.1.0",
"seek-bzip": "1.0.5",
"unbzip2-stream": "1.2.5"
},
"dependencies": {
"file-type": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-6.1.0.tgz",
"integrity": "sha1-Wn26mBOPoKvsevxD5amgsqrHKfE="
}
}
},
"decompress-targz": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz",
"integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==",
"requires": {
"decompress-tar": "4.1.1",
"file-type": "5.2.0",
"is-stream": "1.1.0"
}
},
"decompress-unzip": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz",
"integrity": "sha1-3qrM39FK6vhVePczroIQ+bSEj2k=",
"requires": {
"file-type": "3.9.0",
"get-stream": "2.3.1",
"pify": "2.3.0",
"yauzl": "2.8.0"
},
"dependencies": {
"file-type": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
"integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek="
}
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@ -612,6 +744,14 @@
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz",
"integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA="
},
"end-of-stream": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz",
"integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=",
"requires": {
"once": "1.4.0"
}
},
"engine.io": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.0.tgz",
@ -752,6 +892,19 @@
"resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
"integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A="
},
"fd-slicer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
"integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
"requires": {
"pend": "1.2.0"
}
},
"file-type": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz",
"integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY="
},
"finalhandler": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.4.tgz",
@ -846,6 +999,15 @@
}
}
},
"get-stream": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
"integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=",
"requires": {
"object-assign": "4.1.1",
"pinkie-promise": "2.0.1"
}
},
"getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
@ -866,6 +1028,11 @@
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
},
"graceful-readlink": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
"integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU="
},
"har-schema": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz",
@ -982,6 +1149,16 @@
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.4.0.tgz",
"integrity": "sha1-KWrKh4qCGBbluF0KKFqZvP9FgvA="
},
"is-natural-number": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz",
"integrity": "sha1-q5124dtM7VHjXeDHLr7PCfc0zeg="
},
"is-stream": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
},
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
@ -1141,6 +1318,14 @@
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
},
"make-dir": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.0.0.tgz",
"integrity": "sha1-l6ARdR6R3YfPre9Ygy67BJNt6Xg=",
"requires": {
"pify": "2.3.0"
}
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@ -1264,6 +1449,14 @@
"ee-first": "1.1.1"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1.0.2"
}
},
"parsejson": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.3.tgz",
@ -1298,11 +1491,34 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
"pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
},
"performance-now": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz",
"integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU="
},
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
},
"pinkie": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
"integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA="
},
"pinkie-promise": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
"integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
"requires": {
"pinkie": "2.0.4"
}
},
"posix": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/posix/-/posix-4.1.1.tgz",
@ -1311,6 +1527,11 @@
"nan": "2.4.0"
}
},
"process-nextick-args": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
},
"proxy-addr": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.5.tgz",
@ -1452,6 +1673,14 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
},
"seek-bzip": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz",
"integrity": "sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=",
"requires": {
"commander": "2.8.1"
}
},
"send": {
"version": "0.15.4",
"resolved": "https://registry.npmjs.org/send/-/send-0.15.4.tgz",
@ -1663,6 +1892,14 @@
"ansi-regex": "0.2.1"
}
},
"strip-dirs": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.0.0.tgz",
"integrity": "sha1-YQzbKSggDaAAT0HcuQ/JXNkZoLY=",
"requires": {
"is-natural-number": "4.0.1"
}
},
"supports-color": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz",
@ -1680,6 +1917,41 @@
"yallist": "3.0.2"
}
},
"tar-stream": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.4.tgz",
"integrity": "sha1-NlSc8E7RrumyowwBQyUiONr5QBY=",
"requires": {
"bl": "1.2.1",
"end-of-stream": "1.4.0",
"readable-stream": "2.3.3",
"xtend": "4.0.1"
},
"dependencies": {
"readable-stream": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
"requires": {
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "1.0.0",
"process-nextick-args": "1.0.7",
"safe-buffer": "5.1.1",
"string_decoder": "1.0.3",
"util-deprecate": "1.0.2"
}
},
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"requires": {
"safe-buffer": "5.1.1"
}
}
}
},
"test-value": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/test-value/-/test-value-2.1.0.tgz",
@ -1699,6 +1971,11 @@
}
}
},
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
},
"to-array": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
@ -1759,6 +2036,15 @@
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.0.tgz",
"integrity": "sha1-sHoualQagV/Go0zNRTO67DB8qGQ="
},
"unbzip2-stream": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz",
"integrity": "sha512-izD3jxT8xkzwtXRUZjtmRwKnZoeECrfZ8ra/ketwOcusbZEp4mjULMnJOCfTDZBgGQAAY1AJ/IgxcwkavcX9Og==",
"requires": {
"buffer": "3.6.0",
"through": "2.3.8"
}
},
"universalify": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz",
@ -1769,6 +2055,11 @@
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"utils-merge": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz",
@ -1820,6 +2111,11 @@
"stack-trace": "0.0.10"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"ws": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-2.3.1.tgz",
@ -1851,6 +2147,15 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz",
"integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k="
},
"yauzl": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.8.0.tgz",
"integrity": "sha1-eUUK/yKyqcWkHvVOAtuQfM+/nuI=",
"requires": {
"buffer-crc32": "0.2.13",
"fd-slicer": "1.0.1"
}
},
"yeast": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",

1
package.json

@ -13,6 +13,7 @@
"command-line-args": "^4.0.7",
"cors": "^2.8.4",
"crypto-js": "^3.1.9-1",
"decompress": "^4.2.0",
"express": "^4.15.4",
"fs-extra": "^3.0.1",
"get-folder-size": "^1.0.0",

8
src/daemon-frontend-syzoj/daemonRouter.ts

@ -10,7 +10,7 @@ const taskRouter: express.Router = express.Router();
interface JudgeTask {
content: any;
extraDataLocation?: string;
extraFileLocation?: string;
}
taskRouter.use((req, res, next) => {
@ -29,9 +29,9 @@ taskRouter.put('/task', async (req, res) => {
winston.info("Got task: " + JSON.stringify(req.body));
const task = req.body as JudgeTask;
let extraData: Buffer = null;
if (task.extraDataLocation != null) {
winston.verbose("Have extra data, download...");
extraData = await rp(urlLib.resolve(Cfg.remoteUrl, task.extraDataLocation), {
if (task.extraFileLocation != null) {
winston.verbose(`Have extra data, downloading from '${task.extraFileLocation}'...`);
extraData = await rp(urlLib.resolve(Cfg.remoteUrl, task.extraFileLocation), {
encoding: null,
simple: true
});

4
src/daemon/config.ts

@ -9,6 +9,7 @@ export interface ConfigStructure {
priority: number;
redis: string;
dataDisplayLimit: number;
tempDirectory: string;
}
const optionDefinitions = [
@ -28,7 +29,8 @@ export const globalConfig: ConfigStructure = {
testDataDirectory: configJSON.TestData,
priority: configJSON.Priority,
redis: configJSON.RedisUrl,
dataDisplayLimit: configJSON.DataDisplayLimit
dataDisplayLimit: configJSON.DataDisplayLimit,
tempDirectory: configJSON.TempDirectory
}
configureWinston(options.verbose);

12
src/daemon/index.ts

@ -14,19 +14,19 @@ import { JudgeResult, ErrorType, ProgressReportType, OverallResult } from '../in
await rmq.waitForTask(async (task) => {
let result: OverallResult;
try {
await rmq.reportProgress({ taskId: task.taskId, type: ProgressReportType.Started, progress: null });
result = await judge(task, async (progress) => {
await rmq.reportProgress({ taskId: task.taskId, type: ProgressReportType.Progress, progress: progress });
await rmq.reportProgress({ taskId: task.content.taskId, type: ProgressReportType.Started, progress: null });
result = await judge(task.content, task.extraData, async (progress) => {
await rmq.reportProgress({ taskId: task.content.taskId, type: ProgressReportType.Progress, progress: progress });
}, async (progress) => {
const data = { taskId: task.taskId, type: ProgressReportType.Compiled, progress: progress };
const data = { taskId: task.content.taskId, type: ProgressReportType.Compiled, progress: progress };
await rmq.reportProgress(data);
await rmq.reportResult(data);
});
} catch (err) {
winston.warn(`Judge error!!! TaskId: ${task.taskId}`, err);
winston.warn(`Judge error!!! TaskId: ${task.content.taskId}`, err);
result = { error: ErrorType.SystemError, systemMessage: `An error occurred.\n${err.toString()}` };
}
const resultReport = { taskId: task.taskId, type: ProgressReportType.Finished, progress: result };
const resultReport = { taskId: task.content.taskId, type: ProgressReportType.Finished, progress: result };
await rmq.reportProgress(resultReport);
await rmq.reportResult(resultReport);
});

13
src/daemon/interfaces.ts

@ -7,12 +7,17 @@ export enum ProblemType {
Interaction = 3
}
export interface JudgeTask {
export interface JudgeTaskContent {
taskId: number;
testData: string;
type: ProblemType;
priority: number;
param: StandardJudgeParameter | AnswerSubmissionJudgeParameter | InteractionJudgeParameter;
param: StandardJudgeParameter | InteractionJudgeParameter;
}
export interface JudgeTask {
content: JudgeTaskContent;
extraData?: Buffer;
}
export interface StandardJudgeParameter {
@ -24,10 +29,6 @@ export interface StandardJudgeParameter {
fileIOOutput?: string;
}
export interface AnswerSubmissionJudgeParameter {
answerFile: Buffer;
}
export interface InteractionJudgeParameter {
timeLimit: number;
memoryLimit: number;

38
src/daemon/judge/index.ts

@ -1,34 +1,17 @@
import { JudgeTask, ProblemType, TestData, StandardJudgeParameter } from '../interfaces';
import winston = require('winston');
import rmq = require('../rmq');
import { JudgeTaskContent, JudgeTask, ProblemType, TestData, StandardJudgeParameter } from '../interfaces';
import { StandardJudger } from './standard';
import { JudgerBase } from './judger-base';
import { JudgeResult, ErrorType, OverallResult, CompilationResult, TaskStatus, ProgressReportType } from '../../interfaces';
import { readRulesFile } from '../testData';
import { filterPath } from '../../utils';
import winston = require('winston');
import rmq = require('../rmq');
export async function listen() {
await rmq.waitForTask(async (task) => {
let result: OverallResult;
try {
await rmq.reportProgress({ taskId: task.taskId, type: ProgressReportType.Started, progress: null });
result = await judge(task, async (progress) => {
await rmq.reportProgress({ taskId: task.taskId, type: ProgressReportType.Progress, progress: progress });
}, async (compileResult) => {
const cresult = { taskId: task.taskId, type: ProgressReportType.Compiled, progress: compileResult };
await rmq.reportResult(cresult);
await rmq.reportProgress(cresult);
});
} catch (err) {
winston.warn(`Judge error!!! TaskId: ${task.taskId}`, err);
result = { error: ErrorType.SystemError, systemMessage: `An error occurred.\n${err.toString()}` };
}
const resultReport = { taskId: task.taskId, type: ProgressReportType.Finished, progress: result };
await rmq.reportResult(resultReport);
await rmq.reportProgress(resultReport);
});
}
import { AnswerSubmissionJudger } from './submit-answer';
export async function judge(
task: JudgeTask,
task: JudgeTaskContent,
extraData: Buffer,
reportProgress: (p: OverallResult) => Promise<void>,
reportCompileProgress: (p: CompilationResult) => Promise<void>
): Promise<OverallResult> {
@ -50,6 +33,8 @@ export async function judge(
let judger: JudgerBase;
if (task.type === ProblemType.Standard) {
judger = new StandardJudger(testData, task.param as StandardJudgeParameter, task.priority);
} else if (task.type === ProblemType.AnswerSubmission) {
judger = new AnswerSubmissionJudger(testData, extraData, task.priority);
} else {
throw new Error(`Task type not supported`);
}
@ -74,6 +59,7 @@ export async function judge(
}
winston.debug(`Judging...`);
const judgeResult = await judger.judge(r => reportProgress({ compile: compileResult, judge: r }));
await judger.cleanup();
return { compile: compileResult, judge: judgeResult };
}

4
src/daemon/judge/judger-base.ts

@ -26,7 +26,7 @@ export abstract class JudgerBase {
this.testData = t;
}
abstract preprocessTestData(): Promise<void>;
async preprocessTestData(): Promise<void> { }
abstract compile(): Promise<CompilationResult>;
@ -114,4 +114,6 @@ export abstract class JudgerBase {
return { subtasks: results };
}
protected abstract judgeTestcase(curCase: TestcaseJudge, started: () => Promise<void>): Promise<TestcaseDetails>;
async cleanup() { }
}

101
src/daemon/judge/submit-answer.ts

@ -0,0 +1,101 @@
import winston = require('winston');
import pathLib = require('path');
import decompress = require('decompress');
import randomstring = require('randomstring');
import fse = require('fs-extra');
import { RPCTaskType, TestcaseResultType, TestcaseDetails, TaskStatus, CompilationResult, AnswerSubmissionRunTask, AnswerSubmissionRunResult } from '../../interfaces';
import { TestData, TestcaseJudge } from '../interfaces';
import { JudgerBase } from './judger-base';
import { compile } from './compile';
import { globalConfig as Cfg } from '../config';
import { runTask } from '../rmq';
import { readFileLength, readBufferLength } from '../../utils';
export class AnswerSubmissionJudger extends JudgerBase {
submissionContent: Buffer;
spjExecutableName: string = null;
tempDirectory: string;
constructor(testData: TestData, userSubmission: Buffer, priority: number) {
super(testData, priority);
winston.debug(`Submission size: ${userSubmission.length}`);
this.submissionContent = userSubmission;
this.tempDirectory = pathLib.join(Cfg.tempDirectory, 'SYZOJ-tmp-' + randomstring.generate(10));
}
async preprocessTestData(): Promise<void> {
if (this.testData.spj != null) {
winston.verbose("Compiling special judge.");
const [spjExecutableName, spjResult] = await compile(this.testData.spj.sourceCode,
this.testData.spj.language, null, this.priority);
if (spjResult.status !== TaskStatus.Done) {
winston.verbose("Special judge CE: " + spjResult.message);
let message = null;
if (spjResult.message != null && spjResult.message !== "") {
message = "===== Special Judge Compilation Message =====" + spjResult.message;
}
throw new Error(message);
} else {
this.spjExecutableName = spjExecutableName;
}
} else {
this.spjExecutableName = null;
}
}
async compile(): Promise<CompilationResult> {
await fse.mkdir(this.tempDirectory);
try {
await decompress(this.submissionContent, this.tempDirectory);
return { status: TaskStatus.Done };
} catch (err) {
return { status: TaskStatus.Failed, message: `Unable to decompress your answer` + err.toString() };
}
}
async judgeTestcase(curCase: TestcaseJudge, started: () => Promise<void>): Promise<TestcaseDetails> {
let userOutput: Buffer;
try {
userOutput = await fse.readFile(pathLib.join(this.tempDirectory, curCase.userOutputFile));
} catch (err) {
return {
type: TestcaseResultType.FileError,
time: NaN,
memory: NaN,
scoringRate: 0,
systemMessage: `Unable to open your answer: ${err.toString()}`
}
}
const task: AnswerSubmissionRunTask = {
testDataName: this.testData.name,
inputData: curCase.input,
answerData: curCase.output,
userAnswer: userOutput,
spjExecutableName: this.spjExecutableName
}
const [inputContent, outputContent, runResult]: [string, string, AnswerSubmissionRunResult] = 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.RunSubmitAnswer, task: task }, this.priority, started)
]) as any;
return {
type: runResult.result,
time: NaN,
memory: NaN,
input: { name: curCase.input, content: inputContent },
output: { name: curCase.output, content: outputContent },
scoringRate: runResult.scoringRate,
userOutput: readBufferLength(userOutput, Cfg.dataDisplayLimit),
spjMessage: runResult.spjMessage,
}
}
async cleanup(): Promise<void> {
await fse.remove(this.tempDirectory);
}
}

29
src/interfaces.ts

@ -21,13 +21,13 @@ export interface TestcaseDetails {
type: TestcaseResultType;
time: number;
memory: number;
input: FileContent;
output: FileContent; // Output in test data
input?: FileContent;
output?: FileContent; // Output in test data
scoringRate: number; // e.g. 0.5
userOutput: string;
userError: string;
spjMessage: string;
systemMessage: string;
userOutput?: string;
userError?: string;
spjMessage?: string;
systemMessage?: string;
};
export interface TestcaseResult {
@ -69,7 +69,7 @@ export interface StandardRunResult {
userError: string;
scoringRate: number;
spjMessage: string;
systemMessage: string;
systemMessage?: string;
result: TestcaseResultType;
}
@ -85,6 +85,20 @@ export interface StandardRunTask {
spjExecutableName?: string;
}
export interface AnswerSubmissionRunTask {
testDataName: string;
inputData: string;
answerData: string;
userAnswer: Buffer;
spjExecutableName?: string;
}
export interface AnswerSubmissionRunResult {
scoringRate: number;
spjMessage: string;
result: TestcaseResultType;
}
export enum TaskStatus {
Waiting = 0,
Running = 1,
@ -122,6 +136,7 @@ export enum ProgressReportType {
Compiled = 2,
Progress = 3,
Finished = 4,
Reported = 5,
}
export interface ProgressReportData {

9
src/runner/index.ts

@ -6,7 +6,7 @@ import util = require('util');
import rmq = require('./rmq');
import { RPCRequest, RPCTaskType } from '../interfaces';
import { compile } from './compile';
import { judgeStandard } from './judge';
import { judgeStandard, judgeAnswerSubmission } from './judge';
(async function () {
winston.info("Runner starts.");
@ -18,11 +18,12 @@ import { judgeStandard } from './judge';
winston.debug("Task type is compile");
return await compile(task.task);
} else if (task.type === RPCTaskType.RunStandard) {
winston.debug("Task type is judge standard");
return await judgeStandard(task.task);
} else if (task.type === RPCTaskType.RunSubmitAnswer) {
return await judgeAnswerSubmission(task.task);
} else {
winston.debug("Task type unsupported");
winston.warn("Task type unsupported");
throw new Error(`Task type ${task.type} not supported!`);
}
});
})().then(() => { winston.info("Initialization logic completed."); }, (err) => { winston.error(util.inspect(err)); process.exit(1); });
})().then(() => { winston.info("Initialization logic completed."); }, (err) => { winston.error(util.inspect(err)); process.exit(1); });

54
src/runner/judge.ts

@ -4,7 +4,7 @@ import fse = require('fs-extra');
import winston = require('winston');
import { SandboxStatus } from 'simple-sandbox/lib/interfaces';
import { TestcaseResultType, StandardRunTask, StandardRunResult } from '../interfaces';
import { TestcaseResultType, StandardRunTask, StandardRunResult, AnswerSubmissionRunTask, AnswerSubmissionRunResult } from '../interfaces';
import { createOrEmptyDir, tryEmptyDir } from './utils';
import { readFileLength, tryReadFile } from '../utils';
import { globalConfig as Cfg } from './config';
@ -44,7 +44,7 @@ async function runSpj(spjBinDir: string, spjLanguage: Language): Promise<SpjResu
} else {
const scoreString = await tryReadFile(pathLib.join(spjWorkingDir, scoreFileName)),
score = Number(scoreString);
const messageString = await readFileLength(pathLib.join(spjWorkingDir + messageFileName), Cfg.stderrDisplayLimit);
const messageString = await readFileLength(pathLib.join(spjWorkingDir, messageFileName), Cfg.stderrDisplayLimit);
if ((!scoreString) || score === NaN || score < 0 || score > spjFullScore) {
return {
@ -74,7 +74,55 @@ async function runSpj(spjBinDir: string, spjLanguage: Language): Promise<SpjResu
}
}
export async function judgeStandard(task: StandardRunTask): Promise<StandardRunResult> {
export async function judgeAnswerSubmission(task: AnswerSubmissionRunTask)
: Promise<AnswerSubmissionRunResult> {
try {
await createOrEmptyDir(spjWorkingDir);
const testDataPath = pathLib.join(Cfg.testDataDirectory, task.testDataName);
const inputFilePath = task.inputData != null ?
pathLib.join(testDataPath, task.inputData) : null;
if (inputFilePath != null)
await fse.copy(inputFilePath, pathLib.join(spjWorkingDir, 'input'));
const answerFilePath = task.answerData != null ?
pathLib.join(testDataPath, task.answerData) : null;
if (answerFilePath != null)
await fse.copy(answerFilePath, pathLib.join(spjWorkingDir, 'answer'));
await fse.writeFile(pathLib.join(spjWorkingDir, "user_out"), task.userAnswer);
if (task.spjExecutableName != null) {
const [spjBinDir, spjLanguage] = await fetchBinary(task.spjExecutableName);
winston.debug(`Using spj, language: ${spjLanguage.name}`);
if (inputFilePath != null)
await fse.copy(inputFilePath, pathLib.join(spjWorkingDir, 'input'));
winston.debug(`Running spj`);
const spjResult = await runSpj(spjBinDir, spjLanguage);
winston.debug('Judgement done!!');
return {
result: spjResult.status,
scoringRate: spjResult.score,
spjMessage: spjResult.message,
};
} else {
winston.debug(`Running diff`);
const diffResult = await runDiff(spjWorkingDir, 'user_out', 'answer');
winston.debug('Judgement done!!');
return {
result: diffResult.pass ? TestcaseResultType.Accepted : TestcaseResultType.WrongAnswer,
scoringRate: diffResult.pass ? 1 : 0,
spjMessage: diffResult.message,
};
}
} finally {
await tryEmptyDir(spjWorkingDir);
}
}
export async function judgeStandard(task: StandardRunTask)
: Promise<StandardRunResult> {
winston.debug("Standard judge task...", task);
try {
const testDataPath = pathLib.join(Cfg.testDataDirectory, task.testDataName);

11
src/utils.ts

@ -42,6 +42,15 @@ export async function tryReadFile(path: string, encoding = 'utf8'): Promise<stri
return fileContent;
}
export function readBufferLength(buf: Buffer, lengthLimit: number, appendPrompt = fileTooLongPrompt)
: string {
let content = buf.toString('utf8', 0, lengthLimit);
if (buf.length > lengthLimit) {
content += '\n' + appendPrompt(buf.length, lengthLimit);
}
return content;
}
export async function readFileLength(path: string, lengthLimit: number, appendPrompt = fileTooLongPrompt)
: Promise<string> {
let file = -1;
@ -56,7 +65,7 @@ export async function readFileLength(path: string, lengthLimit: number, appendPr
}
return ret;
} catch (e) {
return "";
return null;
} finally {
if (file != -1) {
await fse.close(file);

Loading…
Cancel
Save