From 5d6ffb32d521e486597953c02dac9ed5c72a228d Mon Sep 17 00:00:00 2001 From: t123yh Date: Tue, 22 Aug 2017 16:41:06 +0800 Subject: [PATCH] Add support for answer-submission problems. --- daemon-config-example.json | 3 +- package-lock.json | 305 ++++++++++++++++++++++ package.json | 1 + src/daemon-frontend-syzoj/daemonRouter.ts | 8 +- src/daemon/config.ts | 4 +- src/daemon/index.ts | 12 +- src/daemon/interfaces.ts | 13 +- src/daemon/judge/index.ts | 38 +-- src/daemon/judge/judger-base.ts | 4 +- src/daemon/judge/submit-answer.ts | 101 +++++++ src/interfaces.ts | 29 +- src/runner/index.ts | 9 +- src/runner/judge.ts | 54 +++- src/utils.ts | 11 +- 14 files changed, 532 insertions(+), 60 deletions(-) create mode 100644 src/daemon/judge/submit-answer.ts diff --git a/daemon-config-example.json b/daemon-config-example.json index 6abd4df..35ff6f7 100644 --- a/daemon-config-example.json +++ b/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" } diff --git a/package-lock.json b/package-lock.json index 35ec603..9e0c2f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 6fbce70..653f661 100644 --- a/package.json +++ b/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", diff --git a/src/daemon-frontend-syzoj/daemonRouter.ts b/src/daemon-frontend-syzoj/daemonRouter.ts index baa9553..e9f76f0 100644 --- a/src/daemon-frontend-syzoj/daemonRouter.ts +++ b/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 }); diff --git a/src/daemon/config.ts b/src/daemon/config.ts index 6648be0..f2369eb 100644 --- a/src/daemon/config.ts +++ b/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); \ No newline at end of file diff --git a/src/daemon/index.ts b/src/daemon/index.ts index aafa451..2cb1a0f 100644 --- a/src/daemon/index.ts +++ b/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); }); diff --git a/src/daemon/interfaces.ts b/src/daemon/interfaces.ts index 5c31769..891ae8f 100644 --- a/src/daemon/interfaces.ts +++ b/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; diff --git a/src/daemon/judge/index.ts b/src/daemon/judge/index.ts index 60d7867..1960b71 100644 --- a/src/daemon/judge/index.ts +++ b/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, reportCompileProgress: (p: CompilationResult) => Promise ): Promise { @@ -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 }; } \ No newline at end of file diff --git a/src/daemon/judge/judger-base.ts b/src/daemon/judge/judger-base.ts index 97a8a47..1f6c25b 100644 --- a/src/daemon/judge/judger-base.ts +++ b/src/daemon/judge/judger-base.ts @@ -26,7 +26,7 @@ export abstract class JudgerBase { this.testData = t; } - abstract preprocessTestData(): Promise; + async preprocessTestData(): Promise { } abstract compile(): Promise; @@ -114,4 +114,6 @@ export abstract class JudgerBase { return { subtasks: results }; } protected abstract judgeTestcase(curCase: TestcaseJudge, started: () => Promise): Promise; + + async cleanup() { } } \ No newline at end of file diff --git a/src/daemon/judge/submit-answer.ts b/src/daemon/judge/submit-answer.ts new file mode 100644 index 0000000..3b4311b --- /dev/null +++ b/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 { + 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 { + 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): Promise { + 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 { + await fse.remove(this.tempDirectory); + } +} \ No newline at end of file diff --git a/src/interfaces.ts b/src/interfaces.ts index c175d88..3264fdd 100644 --- a/src/interfaces.ts +++ b/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 { diff --git a/src/runner/index.ts b/src/runner/index.ts index 98acd15..976391f 100644 --- a/src/runner/index.ts +++ b/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); }); \ No newline at end of file +})().then(() => { winston.info("Initialization logic completed."); }, (err) => { winston.error(util.inspect(err)); process.exit(1); }); \ No newline at end of file diff --git a/src/runner/judge.ts b/src/runner/judge.ts index aac6085..1c2d954 100644 --- a/src/runner/judge.ts +++ b/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 spjFullScore) { return { @@ -74,7 +74,55 @@ async function runSpj(spjBinDir: string, spjLanguage: Language): Promise { +export async function judgeAnswerSubmission(task: AnswerSubmissionRunTask) + : Promise { + 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 { winston.debug("Standard judge task...", task); try { const testDataPath = pathLib.join(Cfg.testDataDirectory, task.testDataName); diff --git a/src/utils.ts b/src/utils.ts index edcc329..06a3ed3 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -42,6 +42,15 @@ export async function tryReadFile(path: string, encoding = 'utf8'): Promise lengthLimit) { + content += '\n' + appendPrompt(buf.length, lengthLimit); + } + return content; +} + export async function readFileLength(path: string, lengthLimit: number, appendPrompt = fileTooLongPrompt) : Promise { 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);