├── .cargo └── config.toml ├── .github └── workflows │ ├── build.yaml │ └── rust.yml ├── .gitignore ├── .gitmodules ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── example ├── etc │ ├── a_plus_b.cpp │ ├── a_plus_b.go │ ├── a_plus_b.java │ ├── a_plus_b.py │ ├── a_plus_b.rs │ ├── a_plus_b_MLE.cpp │ ├── a_plus_b_MLE.py │ ├── a_plus_b_MLE.rs │ ├── a_plus_b_RE.cpp │ ├── a_plus_b_RE.py │ ├── a_plus_b_RE.rs │ ├── a_plus_b_SG.cpp │ ├── a_plus_b_SG.rs │ ├── a_plus_b_TLE.cpp │ ├── a_plus_b_TLE.py │ ├── a_plus_b_TLE.rs │ ├── a_plus_b_h.cpp │ ├── log_mle.txt │ ├── log_ok.txt │ ├── log_re.txt │ ├── log_sg.txt │ ├── log_to.txt │ └── log_xx.txt ├── scripts │ ├── compile_scripts │ │ ├── c │ │ ├── cpp │ │ ├── go │ │ ├── java │ │ ├── python │ │ └── rust │ ├── config.yaml │ ├── grouper_scripts │ │ ├── avg │ │ └── min │ └── runner_scripts │ │ ├── c │ │ ├── cpp │ │ ├── go │ │ ├── java │ │ ├── python │ │ └── rust └── tasks │ ├── a_plus_b │ ├── manifest.yaml │ └── testcases │ │ ├── 1.in │ │ ├── 1.sol │ │ ├── 2.in │ │ ├── 2.sol │ │ ├── 3.in │ │ ├── 3.sol │ │ ├── 4.in │ │ ├── 4.sol │ │ ├── 5.in │ │ └── 5.sol │ └── a_plus_b_h │ ├── compile_files │ ├── code_0.h │ └── grader.cpp │ ├── manifest.yaml │ └── testcases │ ├── 1.in │ ├── 1.sol │ ├── 2.in │ ├── 2.sol │ ├── 3.in │ ├── 3.sol │ ├── 4.in │ ├── 4.sol │ ├── 5.in │ └── 5.sol ├── grader ├── Cargo.toml └── src │ ├── errors.rs │ ├── instance │ ├── mod.rs │ └── tests.rs │ ├── lib.rs │ ├── macros.rs │ ├── submission │ ├── manifest.rs │ ├── mod.rs │ ├── result.rs │ └── tests.rs │ └── utils.rs ├── interface ├── Cargo.toml └── src │ ├── cfg.rs │ ├── connection.rs │ ├── constants.rs │ ├── error.rs │ ├── main.rs │ ├── rmq.rs │ └── runner.rs ├── rustfmt.toml └── setup.sh /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [env] 2 | DATABASE_HOST = "localhost" 3 | DATABASE_PORT = "5432" 4 | DATABASE_USERNAME = "postgres" 5 | DATABASE_PASSWORD = "password" 6 | DATABASE_NAME = "db" 7 | RABBITMQ_HOST = "localhost" 8 | RABBITMQ_VHOST = "/" 9 | RABBITMQ_PORT = "5672" 10 | RABBITMQ_USERNAME = "user" 11 | RABBITMQ_PASSWORD = "password" 12 | RABBITMQ_ENV = "prod" 13 | RUST_LOG = "debug" 14 | 15 | ISOLATE_PATH="/usr/local/bin/isolate" 16 | ALTERNATIVE_PATH="/etc/alternatives" 17 | TEMPORARY_PATH="/tmp" 18 | BASE_PATH="./example" -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - dev 8 | tags: 9 | - "v*" 10 | 11 | env: 12 | ARTIFACT_NAME: rusty-grader-${{ github.ref_name }}.tar.gz 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - uses: dtolnay/rust-toolchain@stable 22 | with: 23 | targets: x86_64-unknown-linux-gnu 24 | 25 | - run: cargo build --release --target x86_64-unknown-linux-gnu && cp target/x86_64-unknown-linux-gnu/release/interface rusty-grader 26 | 27 | - uses: actions/upload-artifact@v3 28 | with: 29 | name: rusty-grader 30 | path: rusty-grader 31 | 32 | - name: Prepare tarball for release 33 | if: startsWith(github.ref, 'refs/tags/') 34 | run: tar zcvf $ARTIFACT_NAME rusty-grader 35 | 36 | - name: Release 37 | uses: softprops/action-gh-release@v1 38 | if: startsWith(github.ref, 'refs/tags/') 39 | with: 40 | files: ${{ env.ARTIFACT_NAME }} 41 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | workflow_dispatch: 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | 13 | jobs: 14 | tests: 15 | runs-on: ubuntu-20.04 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Checkout submodules 20 | run: git submodule update --init --recursive 21 | - name: Setup environment 22 | run: GITHUB_ACTIONS=1 ./setup.sh 23 | - name: Run tests 24 | run: cargo test --verbose 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /example/scripts/checker_scripts 3 | .env 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "isolate"] 2 | path = isolate 3 | url = https://github.com/ioi/isolate.git 4 | [submodule "testlib"] 5 | path = testlib 6 | url = https://github.com/programming-in-th/testlib.git 7 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.20.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "ahash" 22 | version = "0.7.6" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" 25 | dependencies = [ 26 | "getrandom", 27 | "once_cell", 28 | "version_check", 29 | ] 30 | 31 | [[package]] 32 | name = "aho-corasick" 33 | version = "1.0.2" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" 36 | dependencies = [ 37 | "memchr", 38 | ] 39 | 40 | [[package]] 41 | name = "alloc-no-stdlib" 42 | version = "2.0.4" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" 45 | 46 | [[package]] 47 | name = "alloc-stdlib" 48 | version = "0.2.2" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" 51 | dependencies = [ 52 | "alloc-no-stdlib", 53 | ] 54 | 55 | [[package]] 56 | name = "amq-protocol" 57 | version = "7.1.2" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "1d40d8b2465c7959dd40cee32ba6ac334b5de57e9fca0cc756759894a4152a5d" 60 | dependencies = [ 61 | "amq-protocol-tcp", 62 | "amq-protocol-types", 63 | "amq-protocol-uri", 64 | "cookie-factory", 65 | "nom", 66 | "serde", 67 | ] 68 | 69 | [[package]] 70 | name = "amq-protocol-tcp" 71 | version = "7.1.2" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "9cb2100adae7da61953a2c3a01935d86caae13329fadce3333f524d6d6ce12e2" 74 | dependencies = [ 75 | "amq-protocol-uri", 76 | "tcp-stream", 77 | "tracing", 78 | ] 79 | 80 | [[package]] 81 | name = "amq-protocol-types" 82 | version = "7.1.2" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "156ff13c8a3ced600b4e54ed826a2ae6242b6069d00dd98466827cef07d3daff" 85 | dependencies = [ 86 | "cookie-factory", 87 | "nom", 88 | "serde", 89 | "serde_json", 90 | ] 91 | 92 | [[package]] 93 | name = "amq-protocol-uri" 94 | version = "7.1.2" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "751bbd7d440576066233e740576f1b31fdc6ab86cfabfbd48c548de77eca73e4" 97 | dependencies = [ 98 | "amq-protocol-types", 99 | "percent-encoding", 100 | "url", 101 | ] 102 | 103 | [[package]] 104 | name = "anyhow" 105 | version = "1.0.71" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" 108 | 109 | [[package]] 110 | name = "async-channel" 111 | version = "1.8.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" 114 | dependencies = [ 115 | "concurrent-queue", 116 | "event-listener", 117 | "futures-core", 118 | ] 119 | 120 | [[package]] 121 | name = "async-executor" 122 | version = "1.5.1" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" 125 | dependencies = [ 126 | "async-lock", 127 | "async-task", 128 | "concurrent-queue", 129 | "fastrand", 130 | "futures-lite", 131 | "slab", 132 | ] 133 | 134 | [[package]] 135 | name = "async-global-executor" 136 | version = "2.3.1" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" 139 | dependencies = [ 140 | "async-channel", 141 | "async-executor", 142 | "async-io", 143 | "async-lock", 144 | "blocking", 145 | "futures-lite", 146 | "once_cell", 147 | ] 148 | 149 | [[package]] 150 | name = "async-global-executor-trait" 151 | version = "2.1.0" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "33dd14c5a15affd2abcff50d84efd4009ada28a860f01c14f9d654f3e81b3f75" 154 | dependencies = [ 155 | "async-global-executor", 156 | "async-trait", 157 | "executor-trait", 158 | ] 159 | 160 | [[package]] 161 | name = "async-io" 162 | version = "1.13.0" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" 165 | dependencies = [ 166 | "async-lock", 167 | "autocfg", 168 | "cfg-if", 169 | "concurrent-queue", 170 | "futures-lite", 171 | "log", 172 | "parking", 173 | "polling", 174 | "rustix", 175 | "slab", 176 | "socket2 0.4.9", 177 | "waker-fn", 178 | ] 179 | 180 | [[package]] 181 | name = "async-lock" 182 | version = "2.7.0" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" 185 | dependencies = [ 186 | "event-listener", 187 | ] 188 | 189 | [[package]] 190 | name = "async-reactor-trait" 191 | version = "1.1.0" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "7a6012d170ad00de56c9ee354aef2e358359deb1ec504254e0e5a3774771de0e" 194 | dependencies = [ 195 | "async-io", 196 | "async-trait", 197 | "futures-core", 198 | "reactor-trait", 199 | ] 200 | 201 | [[package]] 202 | name = "async-task" 203 | version = "4.4.0" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" 206 | 207 | [[package]] 208 | name = "async-trait" 209 | version = "0.1.71" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" 212 | dependencies = [ 213 | "proc-macro2", 214 | "quote", 215 | "syn", 216 | ] 217 | 218 | [[package]] 219 | name = "atomic-waker" 220 | version = "1.1.1" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" 223 | 224 | [[package]] 225 | name = "atty" 226 | version = "0.2.14" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 229 | dependencies = [ 230 | "hermit-abi 0.1.19", 231 | "libc", 232 | "winapi", 233 | ] 234 | 235 | [[package]] 236 | name = "autocfg" 237 | version = "1.1.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 240 | 241 | [[package]] 242 | name = "backtrace" 243 | version = "0.3.68" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" 246 | dependencies = [ 247 | "addr2line", 248 | "cc", 249 | "cfg-if", 250 | "libc", 251 | "miniz_oxide", 252 | "object", 253 | "rustc-demangle", 254 | ] 255 | 256 | [[package]] 257 | name = "base64" 258 | version = "0.13.1" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 261 | 262 | [[package]] 263 | name = "base64" 264 | version = "0.21.2" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" 267 | 268 | [[package]] 269 | name = "bitflags" 270 | version = "1.3.2" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 273 | 274 | [[package]] 275 | name = "block-buffer" 276 | version = "0.10.4" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 279 | dependencies = [ 280 | "generic-array", 281 | ] 282 | 283 | [[package]] 284 | name = "block-padding" 285 | version = "0.3.3" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" 288 | dependencies = [ 289 | "generic-array", 290 | ] 291 | 292 | [[package]] 293 | name = "blocking" 294 | version = "1.3.1" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" 297 | dependencies = [ 298 | "async-channel", 299 | "async-lock", 300 | "async-task", 301 | "atomic-waker", 302 | "fastrand", 303 | "futures-lite", 304 | "log", 305 | ] 306 | 307 | [[package]] 308 | name = "brotli" 309 | version = "3.3.4" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" 312 | dependencies = [ 313 | "alloc-no-stdlib", 314 | "alloc-stdlib", 315 | "brotli-decompressor", 316 | ] 317 | 318 | [[package]] 319 | name = "brotli-decompressor" 320 | version = "2.3.4" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" 323 | dependencies = [ 324 | "alloc-no-stdlib", 325 | "alloc-stdlib", 326 | ] 327 | 328 | [[package]] 329 | name = "bumpalo" 330 | version = "3.13.0" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" 333 | 334 | [[package]] 335 | name = "byteorder" 336 | version = "1.4.3" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 339 | 340 | [[package]] 341 | name = "bytes" 342 | version = "1.4.0" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 345 | 346 | [[package]] 347 | name = "cbc" 348 | version = "0.1.2" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" 351 | dependencies = [ 352 | "cipher", 353 | ] 354 | 355 | [[package]] 356 | name = "cc" 357 | version = "1.0.79" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 360 | 361 | [[package]] 362 | name = "cfg-if" 363 | version = "1.0.0" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 366 | 367 | [[package]] 368 | name = "cipher" 369 | version = "0.4.4" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 372 | dependencies = [ 373 | "crypto-common", 374 | "inout", 375 | ] 376 | 377 | [[package]] 378 | name = "concurrent-queue" 379 | version = "2.2.0" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" 382 | dependencies = [ 383 | "crossbeam-utils", 384 | ] 385 | 386 | [[package]] 387 | name = "config" 388 | version = "0.13.3" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" 391 | dependencies = [ 392 | "async-trait", 393 | "json5", 394 | "lazy_static", 395 | "nom", 396 | "pathdiff", 397 | "ron", 398 | "rust-ini", 399 | "serde", 400 | "serde_json", 401 | "toml", 402 | "yaml-rust", 403 | ] 404 | 405 | [[package]] 406 | name = "cookie-factory" 407 | version = "0.3.2" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" 410 | 411 | [[package]] 412 | name = "core-foundation" 413 | version = "0.9.3" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 416 | dependencies = [ 417 | "core-foundation-sys", 418 | "libc", 419 | ] 420 | 421 | [[package]] 422 | name = "core-foundation-sys" 423 | version = "0.8.4" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" 426 | 427 | [[package]] 428 | name = "cpufeatures" 429 | version = "0.2.9" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" 432 | dependencies = [ 433 | "libc", 434 | ] 435 | 436 | [[package]] 437 | name = "crossbeam-utils" 438 | version = "0.8.16" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" 441 | dependencies = [ 442 | "cfg-if", 443 | ] 444 | 445 | [[package]] 446 | name = "crypto-common" 447 | version = "0.1.6" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 450 | dependencies = [ 451 | "generic-array", 452 | "typenum", 453 | ] 454 | 455 | [[package]] 456 | name = "des" 457 | version = "0.8.1" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" 460 | dependencies = [ 461 | "cipher", 462 | ] 463 | 464 | [[package]] 465 | name = "digest" 466 | version = "0.10.7" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 469 | dependencies = [ 470 | "block-buffer", 471 | "crypto-common", 472 | "subtle", 473 | ] 474 | 475 | [[package]] 476 | name = "dlv-list" 477 | version = "0.3.0" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" 480 | 481 | [[package]] 482 | name = "doc-comment" 483 | version = "0.3.3" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 486 | 487 | [[package]] 488 | name = "dotenv" 489 | version = "0.15.0" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" 492 | 493 | [[package]] 494 | name = "env_logger" 495 | version = "0.7.1" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 498 | dependencies = [ 499 | "atty", 500 | "humantime", 501 | "log", 502 | "regex", 503 | "termcolor", 504 | ] 505 | 506 | [[package]] 507 | name = "errno" 508 | version = "0.3.1" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" 511 | dependencies = [ 512 | "errno-dragonfly", 513 | "libc", 514 | "windows-sys", 515 | ] 516 | 517 | [[package]] 518 | name = "errno-dragonfly" 519 | version = "0.1.2" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 522 | dependencies = [ 523 | "cc", 524 | "libc", 525 | ] 526 | 527 | [[package]] 528 | name = "event-listener" 529 | version = "2.5.3" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" 532 | 533 | [[package]] 534 | name = "executor-trait" 535 | version = "2.1.0" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "1a1052dd43212a7777ec6a69b117da52f5e52f07aec47d00c1a2b33b85d06b08" 538 | dependencies = [ 539 | "async-trait", 540 | ] 541 | 542 | [[package]] 543 | name = "fallible-iterator" 544 | version = "0.2.0" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 547 | 548 | [[package]] 549 | name = "fastrand" 550 | version = "1.9.0" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" 553 | dependencies = [ 554 | "instant", 555 | ] 556 | 557 | [[package]] 558 | name = "flume" 559 | version = "0.10.14" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" 562 | dependencies = [ 563 | "futures-core", 564 | "futures-sink", 565 | "pin-project", 566 | "spin 0.9.8", 567 | ] 568 | 569 | [[package]] 570 | name = "foreign-types" 571 | version = "0.3.2" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 574 | dependencies = [ 575 | "foreign-types-shared", 576 | ] 577 | 578 | [[package]] 579 | name = "foreign-types-shared" 580 | version = "0.1.1" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 583 | 584 | [[package]] 585 | name = "form_urlencoded" 586 | version = "1.2.0" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" 589 | dependencies = [ 590 | "percent-encoding", 591 | ] 592 | 593 | [[package]] 594 | name = "futures" 595 | version = "0.3.28" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" 598 | dependencies = [ 599 | "futures-channel", 600 | "futures-core", 601 | "futures-executor", 602 | "futures-io", 603 | "futures-sink", 604 | "futures-task", 605 | "futures-util", 606 | ] 607 | 608 | [[package]] 609 | name = "futures-channel" 610 | version = "0.3.28" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" 613 | dependencies = [ 614 | "futures-core", 615 | "futures-sink", 616 | ] 617 | 618 | [[package]] 619 | name = "futures-core" 620 | version = "0.3.28" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 623 | 624 | [[package]] 625 | name = "futures-executor" 626 | version = "0.3.28" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" 629 | dependencies = [ 630 | "futures-core", 631 | "futures-task", 632 | "futures-util", 633 | ] 634 | 635 | [[package]] 636 | name = "futures-io" 637 | version = "0.3.28" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" 640 | 641 | [[package]] 642 | name = "futures-lite" 643 | version = "1.13.0" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" 646 | dependencies = [ 647 | "fastrand", 648 | "futures-core", 649 | "futures-io", 650 | "memchr", 651 | "parking", 652 | "pin-project-lite", 653 | "waker-fn", 654 | ] 655 | 656 | [[package]] 657 | name = "futures-macro" 658 | version = "0.3.28" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" 661 | dependencies = [ 662 | "proc-macro2", 663 | "quote", 664 | "syn", 665 | ] 666 | 667 | [[package]] 668 | name = "futures-sink" 669 | version = "0.3.28" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" 672 | 673 | [[package]] 674 | name = "futures-task" 675 | version = "0.3.28" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" 678 | 679 | [[package]] 680 | name = "futures-util" 681 | version = "0.3.28" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" 684 | dependencies = [ 685 | "futures-channel", 686 | "futures-core", 687 | "futures-io", 688 | "futures-macro", 689 | "futures-sink", 690 | "futures-task", 691 | "memchr", 692 | "pin-project-lite", 693 | "pin-utils", 694 | "slab", 695 | ] 696 | 697 | [[package]] 698 | name = "generic-array" 699 | version = "0.14.7" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 702 | dependencies = [ 703 | "typenum", 704 | "version_check", 705 | ] 706 | 707 | [[package]] 708 | name = "getrandom" 709 | version = "0.2.10" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 712 | dependencies = [ 713 | "cfg-if", 714 | "libc", 715 | "wasi", 716 | ] 717 | 718 | [[package]] 719 | name = "gimli" 720 | version = "0.27.3" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" 723 | 724 | [[package]] 725 | name = "grader" 726 | version = "0.1.0" 727 | dependencies = [ 728 | "anyhow", 729 | "dotenv", 730 | "futures", 731 | "log", 732 | "serde", 733 | "thiserror", 734 | "tokio", 735 | "yaml-rust", 736 | ] 737 | 738 | [[package]] 739 | name = "hashbrown" 740 | version = "0.12.3" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 743 | dependencies = [ 744 | "ahash", 745 | ] 746 | 747 | [[package]] 748 | name = "hermit-abi" 749 | version = "0.1.19" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 752 | dependencies = [ 753 | "libc", 754 | ] 755 | 756 | [[package]] 757 | name = "hermit-abi" 758 | version = "0.3.2" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" 761 | 762 | [[package]] 763 | name = "hmac" 764 | version = "0.12.1" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 767 | dependencies = [ 768 | "digest", 769 | ] 770 | 771 | [[package]] 772 | name = "humantime" 773 | version = "1.3.0" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 776 | dependencies = [ 777 | "quick-error", 778 | ] 779 | 780 | [[package]] 781 | name = "idna" 782 | version = "0.4.0" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" 785 | dependencies = [ 786 | "unicode-bidi", 787 | "unicode-normalization", 788 | ] 789 | 790 | [[package]] 791 | name = "inout" 792 | version = "0.1.3" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" 795 | dependencies = [ 796 | "block-padding", 797 | "generic-array", 798 | ] 799 | 800 | [[package]] 801 | name = "instant" 802 | version = "0.1.12" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 805 | dependencies = [ 806 | "cfg-if", 807 | ] 808 | 809 | [[package]] 810 | name = "interface" 811 | version = "0.1.0" 812 | dependencies = [ 813 | "brotli", 814 | "config", 815 | "futures", 816 | "futures-util", 817 | "grader", 818 | "lapin", 819 | "log", 820 | "openssl", 821 | "postgres-openssl", 822 | "pretty_env_logger", 823 | "serde", 824 | "serde_json", 825 | "thiserror", 826 | "tokio", 827 | "tokio-postgres", 828 | ] 829 | 830 | [[package]] 831 | name = "io-lifetimes" 832 | version = "1.0.11" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" 835 | dependencies = [ 836 | "hermit-abi 0.3.2", 837 | "libc", 838 | "windows-sys", 839 | ] 840 | 841 | [[package]] 842 | name = "itoa" 843 | version = "1.0.8" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" 846 | 847 | [[package]] 848 | name = "js-sys" 849 | version = "0.3.64" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" 852 | dependencies = [ 853 | "wasm-bindgen", 854 | ] 855 | 856 | [[package]] 857 | name = "json5" 858 | version = "0.4.1" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" 861 | dependencies = [ 862 | "pest", 863 | "pest_derive", 864 | "serde", 865 | ] 866 | 867 | [[package]] 868 | name = "lapin" 869 | version = "2.2.1" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "acc13beaa09eed710f406201f46b961345b4d061dd90ec3d3ccc70721e70342a" 872 | dependencies = [ 873 | "amq-protocol", 874 | "async-global-executor-trait", 875 | "async-reactor-trait", 876 | "async-trait", 877 | "executor-trait", 878 | "flume", 879 | "futures-core", 880 | "futures-io", 881 | "parking_lot", 882 | "pinky-swear", 883 | "reactor-trait", 884 | "serde", 885 | "tracing", 886 | "waker-fn", 887 | ] 888 | 889 | [[package]] 890 | name = "lazy_static" 891 | version = "1.4.0" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 894 | 895 | [[package]] 896 | name = "libc" 897 | version = "0.2.147" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 900 | 901 | [[package]] 902 | name = "linked-hash-map" 903 | version = "0.5.6" 904 | source = "registry+https://github.com/rust-lang/crates.io-index" 905 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 906 | 907 | [[package]] 908 | name = "linux-raw-sys" 909 | version = "0.3.8" 910 | source = "registry+https://github.com/rust-lang/crates.io-index" 911 | checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" 912 | 913 | [[package]] 914 | name = "lock_api" 915 | version = "0.4.10" 916 | source = "registry+https://github.com/rust-lang/crates.io-index" 917 | checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" 918 | dependencies = [ 919 | "autocfg", 920 | "scopeguard", 921 | ] 922 | 923 | [[package]] 924 | name = "log" 925 | version = "0.4.19" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" 928 | 929 | [[package]] 930 | name = "md-5" 931 | version = "0.10.5" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" 934 | dependencies = [ 935 | "digest", 936 | ] 937 | 938 | [[package]] 939 | name = "memchr" 940 | version = "2.5.0" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 943 | 944 | [[package]] 945 | name = "minimal-lexical" 946 | version = "0.2.1" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 949 | 950 | [[package]] 951 | name = "miniz_oxide" 952 | version = "0.7.1" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 955 | dependencies = [ 956 | "adler", 957 | ] 958 | 959 | [[package]] 960 | name = "mio" 961 | version = "0.8.8" 962 | source = "registry+https://github.com/rust-lang/crates.io-index" 963 | checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" 964 | dependencies = [ 965 | "libc", 966 | "wasi", 967 | "windows-sys", 968 | ] 969 | 970 | [[package]] 971 | name = "nom" 972 | version = "7.1.3" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 975 | dependencies = [ 976 | "memchr", 977 | "minimal-lexical", 978 | ] 979 | 980 | [[package]] 981 | name = "num_cpus" 982 | version = "1.16.0" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 985 | dependencies = [ 986 | "hermit-abi 0.3.2", 987 | "libc", 988 | ] 989 | 990 | [[package]] 991 | name = "object" 992 | version = "0.31.1" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" 995 | dependencies = [ 996 | "memchr", 997 | ] 998 | 999 | [[package]] 1000 | name = "once_cell" 1001 | version = "1.18.0" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 1004 | 1005 | [[package]] 1006 | name = "openssl" 1007 | version = "0.10.55" 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" 1009 | checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" 1010 | dependencies = [ 1011 | "bitflags", 1012 | "cfg-if", 1013 | "foreign-types", 1014 | "libc", 1015 | "once_cell", 1016 | "openssl-macros", 1017 | "openssl-sys", 1018 | ] 1019 | 1020 | [[package]] 1021 | name = "openssl-macros" 1022 | version = "0.1.1" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1025 | dependencies = [ 1026 | "proc-macro2", 1027 | "quote", 1028 | "syn", 1029 | ] 1030 | 1031 | [[package]] 1032 | name = "openssl-probe" 1033 | version = "0.1.5" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 1036 | 1037 | [[package]] 1038 | name = "openssl-sys" 1039 | version = "0.9.90" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" 1042 | dependencies = [ 1043 | "cc", 1044 | "libc", 1045 | "pkg-config", 1046 | "vcpkg", 1047 | ] 1048 | 1049 | [[package]] 1050 | name = "ordered-multimap" 1051 | version = "0.4.3" 1052 | source = "registry+https://github.com/rust-lang/crates.io-index" 1053 | checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" 1054 | dependencies = [ 1055 | "dlv-list", 1056 | "hashbrown", 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "p12" 1061 | version = "0.6.3" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "d4873306de53fe82e7e484df31e1e947d61514b6ea2ed6cd7b45d63006fd9224" 1064 | dependencies = [ 1065 | "cbc", 1066 | "cipher", 1067 | "des", 1068 | "getrandom", 1069 | "hmac", 1070 | "lazy_static", 1071 | "rc2", 1072 | "sha1", 1073 | "yasna", 1074 | ] 1075 | 1076 | [[package]] 1077 | name = "parking" 1078 | version = "2.1.0" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" 1081 | 1082 | [[package]] 1083 | name = "parking_lot" 1084 | version = "0.12.1" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 1087 | dependencies = [ 1088 | "lock_api", 1089 | "parking_lot_core", 1090 | ] 1091 | 1092 | [[package]] 1093 | name = "parking_lot_core" 1094 | version = "0.9.8" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" 1097 | dependencies = [ 1098 | "cfg-if", 1099 | "libc", 1100 | "redox_syscall", 1101 | "smallvec", 1102 | "windows-targets", 1103 | ] 1104 | 1105 | [[package]] 1106 | name = "pathdiff" 1107 | version = "0.2.1" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" 1110 | 1111 | [[package]] 1112 | name = "percent-encoding" 1113 | version = "2.3.0" 1114 | source = "registry+https://github.com/rust-lang/crates.io-index" 1115 | checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" 1116 | 1117 | [[package]] 1118 | name = "pest" 1119 | version = "2.7.0" 1120 | source = "registry+https://github.com/rust-lang/crates.io-index" 1121 | checksum = "f73935e4d55e2abf7f130186537b19e7a4abc886a0252380b59248af473a3fc9" 1122 | dependencies = [ 1123 | "thiserror", 1124 | "ucd-trie", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "pest_derive" 1129 | version = "2.7.0" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "aef623c9bbfa0eedf5a0efba11a5ee83209c326653ca31ff019bec3a95bfff2b" 1132 | dependencies = [ 1133 | "pest", 1134 | "pest_generator", 1135 | ] 1136 | 1137 | [[package]] 1138 | name = "pest_generator" 1139 | version = "2.7.0" 1140 | source = "registry+https://github.com/rust-lang/crates.io-index" 1141 | checksum = "b3e8cba4ec22bada7fc55ffe51e2deb6a0e0db2d0b7ab0b103acc80d2510c190" 1142 | dependencies = [ 1143 | "pest", 1144 | "pest_meta", 1145 | "proc-macro2", 1146 | "quote", 1147 | "syn", 1148 | ] 1149 | 1150 | [[package]] 1151 | name = "pest_meta" 1152 | version = "2.7.0" 1153 | source = "registry+https://github.com/rust-lang/crates.io-index" 1154 | checksum = "a01f71cb40bd8bb94232df14b946909e14660e33fc05db3e50ae2a82d7ea0ca0" 1155 | dependencies = [ 1156 | "once_cell", 1157 | "pest", 1158 | "sha2", 1159 | ] 1160 | 1161 | [[package]] 1162 | name = "phf" 1163 | version = "0.11.2" 1164 | source = "registry+https://github.com/rust-lang/crates.io-index" 1165 | checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" 1166 | dependencies = [ 1167 | "phf_shared", 1168 | ] 1169 | 1170 | [[package]] 1171 | name = "phf_shared" 1172 | version = "0.11.2" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" 1175 | dependencies = [ 1176 | "siphasher", 1177 | ] 1178 | 1179 | [[package]] 1180 | name = "pin-project" 1181 | version = "1.1.2" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" 1184 | dependencies = [ 1185 | "pin-project-internal", 1186 | ] 1187 | 1188 | [[package]] 1189 | name = "pin-project-internal" 1190 | version = "1.1.2" 1191 | source = "registry+https://github.com/rust-lang/crates.io-index" 1192 | checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" 1193 | dependencies = [ 1194 | "proc-macro2", 1195 | "quote", 1196 | "syn", 1197 | ] 1198 | 1199 | [[package]] 1200 | name = "pin-project-lite" 1201 | version = "0.2.10" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" 1204 | 1205 | [[package]] 1206 | name = "pin-utils" 1207 | version = "0.1.0" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1210 | 1211 | [[package]] 1212 | name = "pinky-swear" 1213 | version = "6.1.0" 1214 | source = "registry+https://github.com/rust-lang/crates.io-index" 1215 | checksum = "d894b67aa7a4bf295db5e85349078c604edaa6fa5c8721e8eca3c7729a27f2ac" 1216 | dependencies = [ 1217 | "doc-comment", 1218 | "flume", 1219 | "parking_lot", 1220 | "tracing", 1221 | ] 1222 | 1223 | [[package]] 1224 | name = "pkg-config" 1225 | version = "0.3.27" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" 1228 | 1229 | [[package]] 1230 | name = "polling" 1231 | version = "2.8.0" 1232 | source = "registry+https://github.com/rust-lang/crates.io-index" 1233 | checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" 1234 | dependencies = [ 1235 | "autocfg", 1236 | "bitflags", 1237 | "cfg-if", 1238 | "concurrent-queue", 1239 | "libc", 1240 | "log", 1241 | "pin-project-lite", 1242 | "windows-sys", 1243 | ] 1244 | 1245 | [[package]] 1246 | name = "postgres-openssl" 1247 | version = "0.5.0" 1248 | source = "registry+https://github.com/rust-lang/crates.io-index" 1249 | checksum = "1de0ea6504e07ca78355a6fb88ad0f36cafe9e696cbc6717f16a207f3a60be72" 1250 | dependencies = [ 1251 | "futures", 1252 | "openssl", 1253 | "tokio", 1254 | "tokio-openssl", 1255 | "tokio-postgres", 1256 | ] 1257 | 1258 | [[package]] 1259 | name = "postgres-protocol" 1260 | version = "0.6.5" 1261 | source = "registry+https://github.com/rust-lang/crates.io-index" 1262 | checksum = "78b7fa9f396f51dffd61546fd8573ee20592287996568e6175ceb0f8699ad75d" 1263 | dependencies = [ 1264 | "base64 0.21.2", 1265 | "byteorder", 1266 | "bytes", 1267 | "fallible-iterator", 1268 | "hmac", 1269 | "md-5", 1270 | "memchr", 1271 | "rand", 1272 | "sha2", 1273 | "stringprep", 1274 | ] 1275 | 1276 | [[package]] 1277 | name = "postgres-types" 1278 | version = "0.2.5" 1279 | source = "registry+https://github.com/rust-lang/crates.io-index" 1280 | checksum = "f028f05971fe20f512bcc679e2c10227e57809a3af86a7606304435bc8896cd6" 1281 | dependencies = [ 1282 | "bytes", 1283 | "fallible-iterator", 1284 | "postgres-protocol", 1285 | "serde", 1286 | "serde_json", 1287 | ] 1288 | 1289 | [[package]] 1290 | name = "ppv-lite86" 1291 | version = "0.2.17" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 1294 | 1295 | [[package]] 1296 | name = "pretty_env_logger" 1297 | version = "0.4.0" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" 1300 | dependencies = [ 1301 | "env_logger", 1302 | "log", 1303 | ] 1304 | 1305 | [[package]] 1306 | name = "proc-macro2" 1307 | version = "1.0.63" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" 1310 | dependencies = [ 1311 | "unicode-ident", 1312 | ] 1313 | 1314 | [[package]] 1315 | name = "quick-error" 1316 | version = "1.2.3" 1317 | source = "registry+https://github.com/rust-lang/crates.io-index" 1318 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 1319 | 1320 | [[package]] 1321 | name = "quote" 1322 | version = "1.0.29" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" 1325 | dependencies = [ 1326 | "proc-macro2", 1327 | ] 1328 | 1329 | [[package]] 1330 | name = "rand" 1331 | version = "0.8.5" 1332 | source = "registry+https://github.com/rust-lang/crates.io-index" 1333 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1334 | dependencies = [ 1335 | "libc", 1336 | "rand_chacha", 1337 | "rand_core", 1338 | ] 1339 | 1340 | [[package]] 1341 | name = "rand_chacha" 1342 | version = "0.3.1" 1343 | source = "registry+https://github.com/rust-lang/crates.io-index" 1344 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1345 | dependencies = [ 1346 | "ppv-lite86", 1347 | "rand_core", 1348 | ] 1349 | 1350 | [[package]] 1351 | name = "rand_core" 1352 | version = "0.6.4" 1353 | source = "registry+https://github.com/rust-lang/crates.io-index" 1354 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1355 | dependencies = [ 1356 | "getrandom", 1357 | ] 1358 | 1359 | [[package]] 1360 | name = "rc2" 1361 | version = "0.8.1" 1362 | source = "registry+https://github.com/rust-lang/crates.io-index" 1363 | checksum = "62c64daa8e9438b84aaae55010a93f396f8e60e3911590fcba770d04643fc1dd" 1364 | dependencies = [ 1365 | "cipher", 1366 | ] 1367 | 1368 | [[package]] 1369 | name = "reactor-trait" 1370 | version = "1.1.0" 1371 | source = "registry+https://github.com/rust-lang/crates.io-index" 1372 | checksum = "438a4293e4d097556730f4711998189416232f009c137389e0f961d2bc0ddc58" 1373 | dependencies = [ 1374 | "async-trait", 1375 | "futures-core", 1376 | "futures-io", 1377 | ] 1378 | 1379 | [[package]] 1380 | name = "redox_syscall" 1381 | version = "0.3.5" 1382 | source = "registry+https://github.com/rust-lang/crates.io-index" 1383 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 1384 | dependencies = [ 1385 | "bitflags", 1386 | ] 1387 | 1388 | [[package]] 1389 | name = "regex" 1390 | version = "1.9.0" 1391 | source = "registry+https://github.com/rust-lang/crates.io-index" 1392 | checksum = "89089e897c013b3deb627116ae56a6955a72b8bed395c9526af31c9fe528b484" 1393 | dependencies = [ 1394 | "aho-corasick", 1395 | "memchr", 1396 | "regex-automata", 1397 | "regex-syntax", 1398 | ] 1399 | 1400 | [[package]] 1401 | name = "regex-automata" 1402 | version = "0.3.0" 1403 | source = "registry+https://github.com/rust-lang/crates.io-index" 1404 | checksum = "fa250384981ea14565685dea16a9ccc4d1c541a13f82b9c168572264d1df8c56" 1405 | dependencies = [ 1406 | "aho-corasick", 1407 | "memchr", 1408 | "regex-syntax", 1409 | ] 1410 | 1411 | [[package]] 1412 | name = "regex-syntax" 1413 | version = "0.7.3" 1414 | source = "registry+https://github.com/rust-lang/crates.io-index" 1415 | checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" 1416 | 1417 | [[package]] 1418 | name = "ring" 1419 | version = "0.16.20" 1420 | source = "registry+https://github.com/rust-lang/crates.io-index" 1421 | checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" 1422 | dependencies = [ 1423 | "cc", 1424 | "libc", 1425 | "once_cell", 1426 | "spin 0.5.2", 1427 | "untrusted", 1428 | "web-sys", 1429 | "winapi", 1430 | ] 1431 | 1432 | [[package]] 1433 | name = "ron" 1434 | version = "0.7.1" 1435 | source = "registry+https://github.com/rust-lang/crates.io-index" 1436 | checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" 1437 | dependencies = [ 1438 | "base64 0.13.1", 1439 | "bitflags", 1440 | "serde", 1441 | ] 1442 | 1443 | [[package]] 1444 | name = "rust-ini" 1445 | version = "0.18.0" 1446 | source = "registry+https://github.com/rust-lang/crates.io-index" 1447 | checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" 1448 | dependencies = [ 1449 | "cfg-if", 1450 | "ordered-multimap", 1451 | ] 1452 | 1453 | [[package]] 1454 | name = "rustc-demangle" 1455 | version = "0.1.23" 1456 | source = "registry+https://github.com/rust-lang/crates.io-index" 1457 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 1458 | 1459 | [[package]] 1460 | name = "rustix" 1461 | version = "0.37.23" 1462 | source = "registry+https://github.com/rust-lang/crates.io-index" 1463 | checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" 1464 | dependencies = [ 1465 | "bitflags", 1466 | "errno", 1467 | "io-lifetimes", 1468 | "libc", 1469 | "linux-raw-sys", 1470 | "windows-sys", 1471 | ] 1472 | 1473 | [[package]] 1474 | name = "rustls" 1475 | version = "0.21.3" 1476 | source = "registry+https://github.com/rust-lang/crates.io-index" 1477 | checksum = "b19faa85ecb5197342b54f987b142fb3e30d0c90da40f80ef4fa9a726e6676ed" 1478 | dependencies = [ 1479 | "log", 1480 | "ring", 1481 | "rustls-webpki 0.101.1", 1482 | "sct", 1483 | ] 1484 | 1485 | [[package]] 1486 | name = "rustls-connector" 1487 | version = "0.18.1" 1488 | source = "registry+https://github.com/rust-lang/crates.io-index" 1489 | checksum = "67c8d6cf0e464eff7cee6ba0419f56a65d29999fc164dd719c8633fbb401365f" 1490 | dependencies = [ 1491 | "log", 1492 | "rustls", 1493 | "rustls-native-certs", 1494 | "rustls-webpki 0.100.1", 1495 | ] 1496 | 1497 | [[package]] 1498 | name = "rustls-native-certs" 1499 | version = "0.6.3" 1500 | source = "registry+https://github.com/rust-lang/crates.io-index" 1501 | checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" 1502 | dependencies = [ 1503 | "openssl-probe", 1504 | "rustls-pemfile", 1505 | "schannel", 1506 | "security-framework", 1507 | ] 1508 | 1509 | [[package]] 1510 | name = "rustls-pemfile" 1511 | version = "1.0.3" 1512 | source = "registry+https://github.com/rust-lang/crates.io-index" 1513 | checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" 1514 | dependencies = [ 1515 | "base64 0.21.2", 1516 | ] 1517 | 1518 | [[package]] 1519 | name = "rustls-webpki" 1520 | version = "0.100.1" 1521 | source = "registry+https://github.com/rust-lang/crates.io-index" 1522 | checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" 1523 | dependencies = [ 1524 | "ring", 1525 | "untrusted", 1526 | ] 1527 | 1528 | [[package]] 1529 | name = "rustls-webpki" 1530 | version = "0.101.1" 1531 | source = "registry+https://github.com/rust-lang/crates.io-index" 1532 | checksum = "15f36a6828982f422756984e47912a7a51dcbc2a197aa791158f8ca61cd8204e" 1533 | dependencies = [ 1534 | "ring", 1535 | "untrusted", 1536 | ] 1537 | 1538 | [[package]] 1539 | name = "ryu" 1540 | version = "1.0.14" 1541 | source = "registry+https://github.com/rust-lang/crates.io-index" 1542 | checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" 1543 | 1544 | [[package]] 1545 | name = "schannel" 1546 | version = "0.1.22" 1547 | source = "registry+https://github.com/rust-lang/crates.io-index" 1548 | checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" 1549 | dependencies = [ 1550 | "windows-sys", 1551 | ] 1552 | 1553 | [[package]] 1554 | name = "scopeguard" 1555 | version = "1.1.0" 1556 | source = "registry+https://github.com/rust-lang/crates.io-index" 1557 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1558 | 1559 | [[package]] 1560 | name = "sct" 1561 | version = "0.7.0" 1562 | source = "registry+https://github.com/rust-lang/crates.io-index" 1563 | checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" 1564 | dependencies = [ 1565 | "ring", 1566 | "untrusted", 1567 | ] 1568 | 1569 | [[package]] 1570 | name = "security-framework" 1571 | version = "2.9.1" 1572 | source = "registry+https://github.com/rust-lang/crates.io-index" 1573 | checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" 1574 | dependencies = [ 1575 | "bitflags", 1576 | "core-foundation", 1577 | "core-foundation-sys", 1578 | "libc", 1579 | "security-framework-sys", 1580 | ] 1581 | 1582 | [[package]] 1583 | name = "security-framework-sys" 1584 | version = "2.9.0" 1585 | source = "registry+https://github.com/rust-lang/crates.io-index" 1586 | checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" 1587 | dependencies = [ 1588 | "core-foundation-sys", 1589 | "libc", 1590 | ] 1591 | 1592 | [[package]] 1593 | name = "serde" 1594 | version = "1.0.167" 1595 | source = "registry+https://github.com/rust-lang/crates.io-index" 1596 | checksum = "7daf513456463b42aa1d94cff7e0c24d682b429f020b9afa4f5ba5c40a22b237" 1597 | dependencies = [ 1598 | "serde_derive", 1599 | ] 1600 | 1601 | [[package]] 1602 | name = "serde_derive" 1603 | version = "1.0.167" 1604 | source = "registry+https://github.com/rust-lang/crates.io-index" 1605 | checksum = "b69b106b68bc8054f0e974e70d19984040f8a5cf9215ca82626ea4853f82c4b9" 1606 | dependencies = [ 1607 | "proc-macro2", 1608 | "quote", 1609 | "syn", 1610 | ] 1611 | 1612 | [[package]] 1613 | name = "serde_json" 1614 | version = "1.0.100" 1615 | source = "registry+https://github.com/rust-lang/crates.io-index" 1616 | checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" 1617 | dependencies = [ 1618 | "itoa", 1619 | "ryu", 1620 | "serde", 1621 | ] 1622 | 1623 | [[package]] 1624 | name = "sha1" 1625 | version = "0.10.5" 1626 | source = "registry+https://github.com/rust-lang/crates.io-index" 1627 | checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" 1628 | dependencies = [ 1629 | "cfg-if", 1630 | "cpufeatures", 1631 | "digest", 1632 | ] 1633 | 1634 | [[package]] 1635 | name = "sha2" 1636 | version = "0.10.7" 1637 | source = "registry+https://github.com/rust-lang/crates.io-index" 1638 | checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" 1639 | dependencies = [ 1640 | "cfg-if", 1641 | "cpufeatures", 1642 | "digest", 1643 | ] 1644 | 1645 | [[package]] 1646 | name = "signal-hook-registry" 1647 | version = "1.4.1" 1648 | source = "registry+https://github.com/rust-lang/crates.io-index" 1649 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 1650 | dependencies = [ 1651 | "libc", 1652 | ] 1653 | 1654 | [[package]] 1655 | name = "siphasher" 1656 | version = "0.3.10" 1657 | source = "registry+https://github.com/rust-lang/crates.io-index" 1658 | checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" 1659 | 1660 | [[package]] 1661 | name = "slab" 1662 | version = "0.4.8" 1663 | source = "registry+https://github.com/rust-lang/crates.io-index" 1664 | checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" 1665 | dependencies = [ 1666 | "autocfg", 1667 | ] 1668 | 1669 | [[package]] 1670 | name = "smallvec" 1671 | version = "1.11.0" 1672 | source = "registry+https://github.com/rust-lang/crates.io-index" 1673 | checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" 1674 | 1675 | [[package]] 1676 | name = "socket2" 1677 | version = "0.4.9" 1678 | source = "registry+https://github.com/rust-lang/crates.io-index" 1679 | checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" 1680 | dependencies = [ 1681 | "libc", 1682 | "winapi", 1683 | ] 1684 | 1685 | [[package]] 1686 | name = "socket2" 1687 | version = "0.5.3" 1688 | source = "registry+https://github.com/rust-lang/crates.io-index" 1689 | checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" 1690 | dependencies = [ 1691 | "libc", 1692 | "windows-sys", 1693 | ] 1694 | 1695 | [[package]] 1696 | name = "spin" 1697 | version = "0.5.2" 1698 | source = "registry+https://github.com/rust-lang/crates.io-index" 1699 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 1700 | 1701 | [[package]] 1702 | name = "spin" 1703 | version = "0.9.8" 1704 | source = "registry+https://github.com/rust-lang/crates.io-index" 1705 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1706 | dependencies = [ 1707 | "lock_api", 1708 | ] 1709 | 1710 | [[package]] 1711 | name = "stringprep" 1712 | version = "0.1.2" 1713 | source = "registry+https://github.com/rust-lang/crates.io-index" 1714 | checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" 1715 | dependencies = [ 1716 | "unicode-bidi", 1717 | "unicode-normalization", 1718 | ] 1719 | 1720 | [[package]] 1721 | name = "subtle" 1722 | version = "2.5.0" 1723 | source = "registry+https://github.com/rust-lang/crates.io-index" 1724 | checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 1725 | 1726 | [[package]] 1727 | name = "syn" 1728 | version = "2.0.23" 1729 | source = "registry+https://github.com/rust-lang/crates.io-index" 1730 | checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" 1731 | dependencies = [ 1732 | "proc-macro2", 1733 | "quote", 1734 | "unicode-ident", 1735 | ] 1736 | 1737 | [[package]] 1738 | name = "tcp-stream" 1739 | version = "0.26.0" 1740 | source = "registry+https://github.com/rust-lang/crates.io-index" 1741 | checksum = "1322b18a9e329ba45e4430b19543045b85cd1dcb2892e77d27ab471ba2039bd1" 1742 | dependencies = [ 1743 | "cfg-if", 1744 | "p12", 1745 | "rustls-connector", 1746 | "rustls-pemfile", 1747 | ] 1748 | 1749 | [[package]] 1750 | name = "termcolor" 1751 | version = "1.2.0" 1752 | source = "registry+https://github.com/rust-lang/crates.io-index" 1753 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 1754 | dependencies = [ 1755 | "winapi-util", 1756 | ] 1757 | 1758 | [[package]] 1759 | name = "thiserror" 1760 | version = "1.0.43" 1761 | source = "registry+https://github.com/rust-lang/crates.io-index" 1762 | checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" 1763 | dependencies = [ 1764 | "thiserror-impl", 1765 | ] 1766 | 1767 | [[package]] 1768 | name = "thiserror-impl" 1769 | version = "1.0.43" 1770 | source = "registry+https://github.com/rust-lang/crates.io-index" 1771 | checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" 1772 | dependencies = [ 1773 | "proc-macro2", 1774 | "quote", 1775 | "syn", 1776 | ] 1777 | 1778 | [[package]] 1779 | name = "tinyvec" 1780 | version = "1.6.0" 1781 | source = "registry+https://github.com/rust-lang/crates.io-index" 1782 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1783 | dependencies = [ 1784 | "tinyvec_macros", 1785 | ] 1786 | 1787 | [[package]] 1788 | name = "tinyvec_macros" 1789 | version = "0.1.1" 1790 | source = "registry+https://github.com/rust-lang/crates.io-index" 1791 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1792 | 1793 | [[package]] 1794 | name = "tokio" 1795 | version = "1.29.1" 1796 | source = "registry+https://github.com/rust-lang/crates.io-index" 1797 | checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" 1798 | dependencies = [ 1799 | "autocfg", 1800 | "backtrace", 1801 | "bytes", 1802 | "libc", 1803 | "mio", 1804 | "num_cpus", 1805 | "parking_lot", 1806 | "pin-project-lite", 1807 | "signal-hook-registry", 1808 | "socket2 0.4.9", 1809 | "tokio-macros", 1810 | "windows-sys", 1811 | ] 1812 | 1813 | [[package]] 1814 | name = "tokio-macros" 1815 | version = "2.1.0" 1816 | source = "registry+https://github.com/rust-lang/crates.io-index" 1817 | checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" 1818 | dependencies = [ 1819 | "proc-macro2", 1820 | "quote", 1821 | "syn", 1822 | ] 1823 | 1824 | [[package]] 1825 | name = "tokio-openssl" 1826 | version = "0.6.3" 1827 | source = "registry+https://github.com/rust-lang/crates.io-index" 1828 | checksum = "c08f9ffb7809f1b20c1b398d92acf4cc719874b3b2b2d9ea2f09b4a80350878a" 1829 | dependencies = [ 1830 | "futures-util", 1831 | "openssl", 1832 | "openssl-sys", 1833 | "tokio", 1834 | ] 1835 | 1836 | [[package]] 1837 | name = "tokio-postgres" 1838 | version = "0.7.8" 1839 | source = "registry+https://github.com/rust-lang/crates.io-index" 1840 | checksum = "6e89f6234aa8fd43779746012fcf53603cdb91fdd8399aa0de868c2d56b6dde1" 1841 | dependencies = [ 1842 | "async-trait", 1843 | "byteorder", 1844 | "bytes", 1845 | "fallible-iterator", 1846 | "futures-channel", 1847 | "futures-util", 1848 | "log", 1849 | "parking_lot", 1850 | "percent-encoding", 1851 | "phf", 1852 | "pin-project-lite", 1853 | "postgres-protocol", 1854 | "postgres-types", 1855 | "socket2 0.5.3", 1856 | "tokio", 1857 | "tokio-util", 1858 | ] 1859 | 1860 | [[package]] 1861 | name = "tokio-util" 1862 | version = "0.7.8" 1863 | source = "registry+https://github.com/rust-lang/crates.io-index" 1864 | checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" 1865 | dependencies = [ 1866 | "bytes", 1867 | "futures-core", 1868 | "futures-sink", 1869 | "pin-project-lite", 1870 | "tokio", 1871 | "tracing", 1872 | ] 1873 | 1874 | [[package]] 1875 | name = "toml" 1876 | version = "0.5.11" 1877 | source = "registry+https://github.com/rust-lang/crates.io-index" 1878 | checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" 1879 | dependencies = [ 1880 | "serde", 1881 | ] 1882 | 1883 | [[package]] 1884 | name = "tracing" 1885 | version = "0.1.37" 1886 | source = "registry+https://github.com/rust-lang/crates.io-index" 1887 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 1888 | dependencies = [ 1889 | "cfg-if", 1890 | "pin-project-lite", 1891 | "tracing-core", 1892 | ] 1893 | 1894 | [[package]] 1895 | name = "tracing-core" 1896 | version = "0.1.31" 1897 | source = "registry+https://github.com/rust-lang/crates.io-index" 1898 | checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" 1899 | dependencies = [ 1900 | "once_cell", 1901 | ] 1902 | 1903 | [[package]] 1904 | name = "typenum" 1905 | version = "1.16.0" 1906 | source = "registry+https://github.com/rust-lang/crates.io-index" 1907 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 1908 | 1909 | [[package]] 1910 | name = "ucd-trie" 1911 | version = "0.1.5" 1912 | source = "registry+https://github.com/rust-lang/crates.io-index" 1913 | checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" 1914 | 1915 | [[package]] 1916 | name = "unicode-bidi" 1917 | version = "0.3.13" 1918 | source = "registry+https://github.com/rust-lang/crates.io-index" 1919 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 1920 | 1921 | [[package]] 1922 | name = "unicode-ident" 1923 | version = "1.0.10" 1924 | source = "registry+https://github.com/rust-lang/crates.io-index" 1925 | checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" 1926 | 1927 | [[package]] 1928 | name = "unicode-normalization" 1929 | version = "0.1.22" 1930 | source = "registry+https://github.com/rust-lang/crates.io-index" 1931 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 1932 | dependencies = [ 1933 | "tinyvec", 1934 | ] 1935 | 1936 | [[package]] 1937 | name = "untrusted" 1938 | version = "0.7.1" 1939 | source = "registry+https://github.com/rust-lang/crates.io-index" 1940 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 1941 | 1942 | [[package]] 1943 | name = "url" 1944 | version = "2.4.0" 1945 | source = "registry+https://github.com/rust-lang/crates.io-index" 1946 | checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" 1947 | dependencies = [ 1948 | "form_urlencoded", 1949 | "idna", 1950 | "percent-encoding", 1951 | ] 1952 | 1953 | [[package]] 1954 | name = "vcpkg" 1955 | version = "0.2.15" 1956 | source = "registry+https://github.com/rust-lang/crates.io-index" 1957 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1958 | 1959 | [[package]] 1960 | name = "version_check" 1961 | version = "0.9.4" 1962 | source = "registry+https://github.com/rust-lang/crates.io-index" 1963 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1964 | 1965 | [[package]] 1966 | name = "waker-fn" 1967 | version = "1.1.0" 1968 | source = "registry+https://github.com/rust-lang/crates.io-index" 1969 | checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" 1970 | 1971 | [[package]] 1972 | name = "wasi" 1973 | version = "0.11.0+wasi-snapshot-preview1" 1974 | source = "registry+https://github.com/rust-lang/crates.io-index" 1975 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1976 | 1977 | [[package]] 1978 | name = "wasm-bindgen" 1979 | version = "0.2.87" 1980 | source = "registry+https://github.com/rust-lang/crates.io-index" 1981 | checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" 1982 | dependencies = [ 1983 | "cfg-if", 1984 | "wasm-bindgen-macro", 1985 | ] 1986 | 1987 | [[package]] 1988 | name = "wasm-bindgen-backend" 1989 | version = "0.2.87" 1990 | source = "registry+https://github.com/rust-lang/crates.io-index" 1991 | checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" 1992 | dependencies = [ 1993 | "bumpalo", 1994 | "log", 1995 | "once_cell", 1996 | "proc-macro2", 1997 | "quote", 1998 | "syn", 1999 | "wasm-bindgen-shared", 2000 | ] 2001 | 2002 | [[package]] 2003 | name = "wasm-bindgen-macro" 2004 | version = "0.2.87" 2005 | source = "registry+https://github.com/rust-lang/crates.io-index" 2006 | checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" 2007 | dependencies = [ 2008 | "quote", 2009 | "wasm-bindgen-macro-support", 2010 | ] 2011 | 2012 | [[package]] 2013 | name = "wasm-bindgen-macro-support" 2014 | version = "0.2.87" 2015 | source = "registry+https://github.com/rust-lang/crates.io-index" 2016 | checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" 2017 | dependencies = [ 2018 | "proc-macro2", 2019 | "quote", 2020 | "syn", 2021 | "wasm-bindgen-backend", 2022 | "wasm-bindgen-shared", 2023 | ] 2024 | 2025 | [[package]] 2026 | name = "wasm-bindgen-shared" 2027 | version = "0.2.87" 2028 | source = "registry+https://github.com/rust-lang/crates.io-index" 2029 | checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" 2030 | 2031 | [[package]] 2032 | name = "web-sys" 2033 | version = "0.3.64" 2034 | source = "registry+https://github.com/rust-lang/crates.io-index" 2035 | checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" 2036 | dependencies = [ 2037 | "js-sys", 2038 | "wasm-bindgen", 2039 | ] 2040 | 2041 | [[package]] 2042 | name = "winapi" 2043 | version = "0.3.9" 2044 | source = "registry+https://github.com/rust-lang/crates.io-index" 2045 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2046 | dependencies = [ 2047 | "winapi-i686-pc-windows-gnu", 2048 | "winapi-x86_64-pc-windows-gnu", 2049 | ] 2050 | 2051 | [[package]] 2052 | name = "winapi-i686-pc-windows-gnu" 2053 | version = "0.4.0" 2054 | source = "registry+https://github.com/rust-lang/crates.io-index" 2055 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2056 | 2057 | [[package]] 2058 | name = "winapi-util" 2059 | version = "0.1.5" 2060 | source = "registry+https://github.com/rust-lang/crates.io-index" 2061 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 2062 | dependencies = [ 2063 | "winapi", 2064 | ] 2065 | 2066 | [[package]] 2067 | name = "winapi-x86_64-pc-windows-gnu" 2068 | version = "0.4.0" 2069 | source = "registry+https://github.com/rust-lang/crates.io-index" 2070 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2071 | 2072 | [[package]] 2073 | name = "windows-sys" 2074 | version = "0.48.0" 2075 | source = "registry+https://github.com/rust-lang/crates.io-index" 2076 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2077 | dependencies = [ 2078 | "windows-targets", 2079 | ] 2080 | 2081 | [[package]] 2082 | name = "windows-targets" 2083 | version = "0.48.1" 2084 | source = "registry+https://github.com/rust-lang/crates.io-index" 2085 | checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" 2086 | dependencies = [ 2087 | "windows_aarch64_gnullvm", 2088 | "windows_aarch64_msvc", 2089 | "windows_i686_gnu", 2090 | "windows_i686_msvc", 2091 | "windows_x86_64_gnu", 2092 | "windows_x86_64_gnullvm", 2093 | "windows_x86_64_msvc", 2094 | ] 2095 | 2096 | [[package]] 2097 | name = "windows_aarch64_gnullvm" 2098 | version = "0.48.0" 2099 | source = "registry+https://github.com/rust-lang/crates.io-index" 2100 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 2101 | 2102 | [[package]] 2103 | name = "windows_aarch64_msvc" 2104 | version = "0.48.0" 2105 | source = "registry+https://github.com/rust-lang/crates.io-index" 2106 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 2107 | 2108 | [[package]] 2109 | name = "windows_i686_gnu" 2110 | version = "0.48.0" 2111 | source = "registry+https://github.com/rust-lang/crates.io-index" 2112 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 2113 | 2114 | [[package]] 2115 | name = "windows_i686_msvc" 2116 | version = "0.48.0" 2117 | source = "registry+https://github.com/rust-lang/crates.io-index" 2118 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 2119 | 2120 | [[package]] 2121 | name = "windows_x86_64_gnu" 2122 | version = "0.48.0" 2123 | source = "registry+https://github.com/rust-lang/crates.io-index" 2124 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 2125 | 2126 | [[package]] 2127 | name = "windows_x86_64_gnullvm" 2128 | version = "0.48.0" 2129 | source = "registry+https://github.com/rust-lang/crates.io-index" 2130 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 2131 | 2132 | [[package]] 2133 | name = "windows_x86_64_msvc" 2134 | version = "0.48.0" 2135 | source = "registry+https://github.com/rust-lang/crates.io-index" 2136 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 2137 | 2138 | [[package]] 2139 | name = "yaml-rust" 2140 | version = "0.4.5" 2141 | source = "registry+https://github.com/rust-lang/crates.io-index" 2142 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" 2143 | dependencies = [ 2144 | "linked-hash-map", 2145 | ] 2146 | 2147 | [[package]] 2148 | name = "yasna" 2149 | version = "0.5.2" 2150 | source = "registry+https://github.com/rust-lang/crates.io-index" 2151 | checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" 2152 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "grader", 5 | "interface" 6 | ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 PROGRAMMING.IN.TH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rusty Grader 2 | [![programming.in.th](https://github.com/programming-in-th/artworks/blob/fb0bdd8587b9379b9b8e2a306bc9216e6ecaee62/png/readme-rust.png)](https://beta.programming.in.th) 3 | 4 | ## Checker 5 | 6 | To utilize the custom checker, please ensure that the `manifest.yaml` file does not include the checker attribute. 7 | 8 | The custom checker, which should be named `checker`, must be stored in the main directory of the task. 9 | 10 | The checker script must be provided by the user and takes in the following command-line arguments: 11 | 12 | 1. **Absolute** path to the input file of the test case 13 | 2. **Absolute** path to the output file generated by the user's program for the test case 14 | 3. **Absolute** path to the solution file of the test case 15 | 16 | Of course, the checker script is passed to itself as the 0-th argument, but it can be safely ignored. 17 | 18 | The checker must then write two lines to standard output. The first line denotes the verdict of the user's program, which one be either of the following: 19 | 20 | - Correct 21 | - Partially correct 22 | - Incorrect 23 | - Time Limit Exceeded 24 | - Memory Limit Exceeded 25 | - Runtime Error 26 | - Signal Error 27 | - Judge Error 28 | 29 | In a custom checker, metrics about the user's program on the current test case must be printed on the second line. If a custom grouper is used, then any string can be printed on the second line. Otherwise, if one of the default groupers is used, you must conform to its protocol (see Default Groupers for more information). 30 | In a custom checker, the score of the user's program on the current test case must then be printed on the second line. Finally, on the last line, the checker can **optionally** output a message describing the result of the test case. If no message is provided, the default message specified in the global configuration (see Global Configuration) will be automatically added instead if it exists. 31 | 32 | For example, the following are valid outputs from a custom checker: 33 | 34 | ```plaintext 35 | Correct 36 | 20 37 | Target reached in 25 moves 38 | ``` 39 | 40 | ```plaintext 41 | Partially Correct 42 | [s1]asdf 43 | ``` 44 | 45 | ```plaintext 46 | Incorrect 47 | !!! 48 | Wrong format 49 | ``` 50 | 51 | The "Judge Error" verdict can be output from both the grader and a custom checker. The grader will output "Judge Error" when there is an internal error of the grader. On the other hand, the custom checker should output "Judge Error" when there is an internal problem of the custom checker. In the case that the judging error comes from the grader, the following will be output: 52 | 53 | ```plaintext 54 | Judge Error 55 | 0 56 | {DEFAULT_MESSAGE} 57 | ``` 58 | 59 | If you intend to utilize C++ code without the need for compilation, you can incorporate the provided code snippet into the `checker` file: 60 | 61 | ```bash 62 | #!/bin/bash 63 | 64 | FILENAME="checker.cpp" 65 | 66 | BINNAME="output" 67 | 68 | DIR="$( cd "$( dirname "$0" )" && pwd )" 69 | 70 | CHECKER="${DIR}/${BINNAME}" 71 | 72 | LAST_CHECKER="$(stat -c%Y ${DIR}/${FILENAME})" 73 | LAST_BIN="$(stat -c%Y ${CHECKER} 2> /dev/null)" 74 | 75 | if [[ ! -f "${CHECKER}" ]] || [[ $LAST_CHECKER -ge $LAST_BIN ]]; then 76 | ( cd "${DIR}" && g++ -std=c++11 -O2 -Wall -Wextra -pedantic -o "${BINNAME}" "${FILENAME}") 77 | fi 78 | 79 | ( cd "${DIR}" && "./${BINNAME}" $1 $2 $3 ) 80 | ``` 81 | 82 | Additionally, remember to place the `checker.cpp` file inside the main directory of the task. 83 | 84 | ### Default Checkers 85 | 86 | Default checkers are provided with the grader that can easily be used by specifying them as the checker in task manifests. This removes the hassle of having to write checkers for typical tasks. All checkers output the verdict in the first line, a score out of 100 for the second line, and the default message of the corresponding verdict on the third line. If first line "Correct", then the second line will be 100. Otherwise, it will be 0. Note that default checkers will never emit the "Partially Correct" verdict. Each default checker will only emit the "Correct" verdict if all tokens match between the user's output and the solution's output. 87 | 88 | Each default checker will split output into tokens as follows: 89 | 90 | - `ncmp`: single or more 64-bit integers, ignores whitespaces 91 | - `wcmp`: sequence of tokens 92 | - `nyesno`: single or more yes/no tokens, case insensitive 93 | - `lcmp`: lines, ignores whitespaces 94 | - `fcmp`: lines, doesn't ignore whitespaces 95 | - `rcmp6`: single or more floating point numbers, maximum error $10^{-6}$ 96 | - `rcmp9`: single or more floating point numbers, maximum error $10^{-9}$ 97 | -------------------------------------------------------------------------------- /example/etc/a_plus_b.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | int a, b; 5 | scanf("%d %d", &a, &b); 6 | printf("%d\n", a + b); 7 | } -------------------------------------------------------------------------------- /example/etc/a_plus_b.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var a int64 7 | var b int64 8 | fmt.Scanf("%d%d", &a, &b) 9 | fmt.Println(a + b) 10 | } -------------------------------------------------------------------------------- /example/etc/a_plus_b.java: -------------------------------------------------------------------------------- 1 | import java.util.Scanner; 2 | public class a_plus_b { 3 | public static void main(String[] args) { 4 | Scanner sc = new Scanner(System.in); 5 | System.out.println(sc.nextLong() + sc.nextLong()); 6 | } 7 | } -------------------------------------------------------------------------------- /example/etc/a_plus_b.py: -------------------------------------------------------------------------------- 1 | a, b = map(int, input().split()) 2 | print(a + b) -------------------------------------------------------------------------------- /example/etc/a_plus_b.rs: -------------------------------------------------------------------------------- 1 | //spnauti-rusT {{{ 2 | #[allow(unused_imports)] 3 | use std::{ 4 | cmp::*, 5 | collections::*, 6 | fmt::Debug, 7 | io::*, 8 | iter::{self, *}, 9 | ops::{self, *}, 10 | str::{self, *}, 11 | }; 12 | 13 | macro_rules!rp{{[$c:expr]$($s:tt)+}=>(for _ in 0..$c{$($s)+})} 14 | 15 | macro_rules!l{ 16 | ($($v:ident),+:$t:ty=$e:expr)=>{$(let$v:$t=$e;)+};(mut $($v:ident),+ =$e:expr)=>{$(let mut$v=$e;)+}; 17 | (mut $($v:ident),+:$t:ty=$e:expr)=>{$(let mut$v:$t=$e;)+};($($v:ident),+ =$e:expr)=>{$(let$v=$e;)+};} 18 | 19 | macro_rules!v{ 20 | ($(:$t:ty)?=$e:expr)=>{$e$(as$t)?};([$d:expr]$(:$t:ty)?)=>{Vec::$(<$t>::)?with_capacity($d)}; 21 | ([]$(:$t:ty)?)=>{Vec::$(<$t>::)?new()};([$d:expr]$($s:tt)+)=>{vec![v!($($s)+);$d]};} 22 | 23 | fn rio() -> (Reader, BufWriter) { 24 | (Reader::new(), BufWriter::new(stdout())) 25 | } 26 | 27 | struct Reader { 28 | buf: Vec, 29 | pos: usize, 30 | x: *mut Stdin, 31 | q: StdinLock<'static>, 32 | } //' 33 | 34 | #[allow(dead_code)] 35 | impl Reader { 36 | fn new() -> Self { 37 | let x = Box::into_raw(Box::new(stdin())); 38 | let q = unsafe { &*x }.lock(); 39 | Self { 40 | x, 41 | q, 42 | buf: v!([]), 43 | pos: 0, 44 | } 45 | } 46 | 47 | fn next_line(&mut self) -> bool { 48 | self.buf.clear(); 49 | self.pos = 0; 50 | self.q.read_until(b'\n', &mut self.buf).unwrap_or(0) > 0 51 | } 52 | 53 | fn byte(&mut self) -> Option { 54 | if self.pos == self.buf.len() { 55 | if !self.next_line() { 56 | return None; 57 | } 58 | } 59 | self.pos += 1; 60 | Some(self.buf[self.pos - 1]) 61 | } 62 | 63 | fn vb(&mut self) -> Vec { 64 | let mut s = v!([10]); 65 | let mut f = false; 66 | while let Some(c) = self.byte() { 67 | if !c.is_ascii_whitespace() { 68 | s.push(c); 69 | f = true; 70 | } else if f { 71 | break; 72 | } 73 | } 74 | s 75 | } 76 | 77 | fn p(&mut self) -> T 78 | where 79 | T::Err: Debug, 80 | { 81 | let w = self.vb(); 82 | str::from_utf8(w.as_ref()).unwrap().parse::().unwrap() 83 | } 84 | 85 | fn u(&mut self) -> usize { 86 | self.p() 87 | } 88 | fn i(&mut self) -> i32 { 89 | self.p() 90 | } 91 | } 92 | 93 | impl Drop for Reader { 94 | fn drop(&mut self) { 95 | unsafe { Box::from_raw(self.x) }; 96 | } 97 | } 98 | //----------}}} 99 | 100 | 101 | fn main() { 102 | let (mut rin, mut rout) = rio(); 103 | l!(a, b = rin.u()); 104 | writeln!(rout, "{}", a + b).unwrap(); 105 | } 106 | -------------------------------------------------------------------------------- /example/etc/a_plus_b_MLE.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define long long long 4 | 5 | using namespace std; 6 | 7 | long a, b; 8 | long dp[10000000]; 9 | 10 | int main() { 11 | scanf("%lld %lld", &a, &b); 12 | printf("%lld\n", a + b); 13 | for(int i = 0; i < 10000000; i++) 14 | dp[i] = 1; 15 | 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /example/etc/a_plus_b_MLE.py: -------------------------------------------------------------------------------- 1 | a, b = map(int, input().split()) 2 | print(a + b) 3 | a = [] 4 | for i in range(0, 1000000): 5 | a.append(i) -------------------------------------------------------------------------------- /example/etc/a_plus_b_MLE.rs: -------------------------------------------------------------------------------- 1 | //spnauti-rusT {{{ 2 | #[allow(unused_imports)] 3 | use std::{ 4 | cmp::*, 5 | collections::*, 6 | fmt::Debug, 7 | io::*, 8 | iter::{self, *}, 9 | ops::{self, *}, 10 | str::{self, *}, 11 | }; 12 | 13 | macro_rules!rp{{[$c:expr]$($s:tt)+}=>(for _ in 0..$c{$($s)+})} 14 | 15 | macro_rules!l{ 16 | ($($v:ident),+:$t:ty=$e:expr)=>{$(let$v:$t=$e;)+};(mut $($v:ident),+ =$e:expr)=>{$(let mut$v=$e;)+}; 17 | (mut $($v:ident),+:$t:ty=$e:expr)=>{$(let mut$v:$t=$e;)+};($($v:ident),+ =$e:expr)=>{$(let$v=$e;)+};} 18 | 19 | macro_rules!v{ 20 | ($(:$t:ty)?=$e:expr)=>{$e$(as$t)?};([$d:expr]$(:$t:ty)?)=>{Vec::$(<$t>::)?with_capacity($d)}; 21 | ([]$(:$t:ty)?)=>{Vec::$(<$t>::)?new()};([$d:expr]$($s:tt)+)=>{vec![v!($($s)+);$d]};} 22 | 23 | fn rio() -> (Reader, BufWriter) { 24 | (Reader::new(), BufWriter::new(stdout())) 25 | } 26 | 27 | struct Reader { 28 | buf: Vec, 29 | pos: usize, 30 | x: *mut Stdin, 31 | q: StdinLock<'static>, 32 | } //' 33 | 34 | #[allow(dead_code)] 35 | impl Reader { 36 | fn new() -> Self { 37 | let x = Box::into_raw(Box::new(stdin())); 38 | let q = unsafe { &*x }.lock(); 39 | Self { 40 | x, 41 | q, 42 | buf: v!([]), 43 | pos: 0, 44 | } 45 | } 46 | 47 | fn next_line(&mut self) -> bool { 48 | self.buf.clear(); 49 | self.pos = 0; 50 | self.q.read_until(b'\n', &mut self.buf).unwrap_or(0) > 0 51 | } 52 | 53 | fn byte(&mut self) -> Option { 54 | if self.pos == self.buf.len() { 55 | if !self.next_line() { 56 | return None; 57 | } 58 | } 59 | self.pos += 1; 60 | Some(self.buf[self.pos - 1]) 61 | } 62 | 63 | fn vb(&mut self) -> Vec { 64 | let mut s = v!([10]); 65 | let mut f = false; 66 | while let Some(c) = self.byte() { 67 | if !c.is_ascii_whitespace() { 68 | s.push(c); 69 | f = true; 70 | } else if f { 71 | break; 72 | } 73 | } 74 | s 75 | } 76 | 77 | fn p(&mut self) -> T 78 | where 79 | T::Err: Debug, 80 | { 81 | let w = self.vb(); 82 | str::from_utf8(w.as_ref()).unwrap().parse::().unwrap() 83 | } 84 | 85 | fn u(&mut self) -> usize { 86 | self.p() 87 | } 88 | fn i(&mut self) -> i32 { 89 | self.p() 90 | } 91 | } 92 | 93 | impl Drop for Reader { 94 | fn drop(&mut self) { 95 | unsafe { Box::from_raw(self.x) }; 96 | } 97 | } 98 | //----------}}} 99 | 100 | 101 | fn main() { 102 | let (mut rin, mut rout) = rio(); 103 | l!(a, b = rin.u()); 104 | writeln!(rout, "{}", a + b).unwrap(); 105 | let mut vec = vec![]; 106 | for i in 0..10000000 { 107 | vec.push(i); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /example/etc/a_plus_b_RE.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | int a, b; 5 | 6 | scanf("%d %d", &a, &b); 7 | printf("%d\n", a + b); 8 | 9 | return 1; 10 | } -------------------------------------------------------------------------------- /example/etc/a_plus_b_RE.py: -------------------------------------------------------------------------------- 1 | a, b = map(int, input().split()) 2 | print(a + b) 3 | exit(1) -------------------------------------------------------------------------------- /example/etc/a_plus_b_RE.rs: -------------------------------------------------------------------------------- 1 | //spnauti-rusT {{{ 2 | #[allow(unused_imports)] 3 | use std::{ 4 | cmp::*, 5 | collections::*, 6 | fmt::Debug, 7 | io::*, 8 | iter::{self, *}, 9 | ops::{self, *}, 10 | str::{self, *}, 11 | }; 12 | 13 | macro_rules!rp{{[$c:expr]$($s:tt)+}=>(for _ in 0..$c{$($s)+})} 14 | 15 | macro_rules!l{ 16 | ($($v:ident),+:$t:ty=$e:expr)=>{$(let$v:$t=$e;)+};(mut $($v:ident),+ =$e:expr)=>{$(let mut$v=$e;)+}; 17 | (mut $($v:ident),+:$t:ty=$e:expr)=>{$(let mut$v:$t=$e;)+};($($v:ident),+ =$e:expr)=>{$(let$v=$e;)+};} 18 | 19 | macro_rules!v{ 20 | ($(:$t:ty)?=$e:expr)=>{$e$(as$t)?};([$d:expr]$(:$t:ty)?)=>{Vec::$(<$t>::)?with_capacity($d)}; 21 | ([]$(:$t:ty)?)=>{Vec::$(<$t>::)?new()};([$d:expr]$($s:tt)+)=>{vec![v!($($s)+);$d]};} 22 | 23 | fn rio() -> (Reader, BufWriter) { 24 | (Reader::new(), BufWriter::new(stdout())) 25 | } 26 | 27 | struct Reader { 28 | buf: Vec, 29 | pos: usize, 30 | x: *mut Stdin, 31 | q: StdinLock<'static>, 32 | } //' 33 | 34 | #[allow(dead_code)] 35 | impl Reader { 36 | fn new() -> Self { 37 | let x = Box::into_raw(Box::new(stdin())); 38 | let q = unsafe { &*x }.lock(); 39 | Self { 40 | x, 41 | q, 42 | buf: v!([]), 43 | pos: 0, 44 | } 45 | } 46 | 47 | fn next_line(&mut self) -> bool { 48 | self.buf.clear(); 49 | self.pos = 0; 50 | self.q.read_until(b'\n', &mut self.buf).unwrap_or(0) > 0 51 | } 52 | 53 | fn byte(&mut self) -> Option { 54 | if self.pos == self.buf.len() { 55 | if !self.next_line() { 56 | return None; 57 | } 58 | } 59 | self.pos += 1; 60 | Some(self.buf[self.pos - 1]) 61 | } 62 | 63 | fn vb(&mut self) -> Vec { 64 | let mut s = v!([10]); 65 | let mut f = false; 66 | while let Some(c) = self.byte() { 67 | if !c.is_ascii_whitespace() { 68 | s.push(c); 69 | f = true; 70 | } else if f { 71 | break; 72 | } 73 | } 74 | s 75 | } 76 | 77 | fn p(&mut self) -> T 78 | where 79 | T::Err: Debug, 80 | { 81 | let w = self.vb(); 82 | str::from_utf8(w.as_ref()).unwrap().parse::().unwrap() 83 | } 84 | 85 | fn u(&mut self) -> usize { 86 | self.p() 87 | } 88 | fn i(&mut self) -> i32 { 89 | self.p() 90 | } 91 | } 92 | 93 | impl Drop for Reader { 94 | fn drop(&mut self) { 95 | unsafe { Box::from_raw(self.x) }; 96 | } 97 | } 98 | //----------}}} 99 | 100 | 101 | fn main() { 102 | let (mut rin, mut rout) = rio(); 103 | l!(a, b = rin.u()); 104 | writeln!(rout, "{}", a + b).unwrap(); 105 | panic!(); 106 | } 107 | -------------------------------------------------------------------------------- /example/etc/a_plus_b_SG.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define long long long 4 | 5 | using namespace std; 6 | 7 | struct test { 8 | test *p; 9 | }; 10 | 11 | long a, b; 12 | test *ptr; 13 | 14 | int main() { 15 | scanf("%lld %lld", &a, &b); 16 | printf("%lld\n", a + b); 17 | a = (long) (ptr->p->p); 18 | 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /example/etc/a_plus_b_SG.rs: -------------------------------------------------------------------------------- 1 | //spnauti-rusT {{{ 2 | #[allow(unused_imports)] 3 | use std::{ 4 | cmp::*, 5 | collections::*, 6 | fmt::Debug, 7 | io::*, 8 | iter::{self, *}, 9 | ops::{self, *}, 10 | str::{self, *}, 11 | }; 12 | 13 | macro_rules!rp{{[$c:expr]$($s:tt)+}=>(for _ in 0..$c{$($s)+})} 14 | 15 | macro_rules!l{ 16 | ($($v:ident),+:$t:ty=$e:expr)=>{$(let$v:$t=$e;)+};(mut $($v:ident),+ =$e:expr)=>{$(let mut$v=$e;)+}; 17 | (mut $($v:ident),+:$t:ty=$e:expr)=>{$(let mut$v:$t=$e;)+};($($v:ident),+ =$e:expr)=>{$(let$v=$e;)+};} 18 | 19 | macro_rules!v{ 20 | ($(:$t:ty)?=$e:expr)=>{$e$(as$t)?};([$d:expr]$(:$t:ty)?)=>{Vec::$(<$t>::)?with_capacity($d)}; 21 | ([]$(:$t:ty)?)=>{Vec::$(<$t>::)?new()};([$d:expr]$($s:tt)+)=>{vec![v!($($s)+);$d]};} 22 | 23 | fn rio() -> (Reader, BufWriter) { 24 | (Reader::new(), BufWriter::new(stdout())) 25 | } 26 | 27 | struct Reader { 28 | buf: Vec, 29 | pos: usize, 30 | x: *mut Stdin, 31 | q: StdinLock<'static>, 32 | } //' 33 | 34 | #[allow(dead_code)] 35 | impl Reader { 36 | fn new() -> Self { 37 | let x = Box::into_raw(Box::new(stdin())); 38 | let q = unsafe { &*x }.lock(); 39 | Self { 40 | x, 41 | q, 42 | buf: v!([]), 43 | pos: 0, 44 | } 45 | } 46 | 47 | fn next_line(&mut self) -> bool { 48 | self.buf.clear(); 49 | self.pos = 0; 50 | self.q.read_until(b'\n', &mut self.buf).unwrap_or(0) > 0 51 | } 52 | 53 | fn byte(&mut self) -> Option { 54 | if self.pos == self.buf.len() { 55 | if !self.next_line() { 56 | return None; 57 | } 58 | } 59 | self.pos += 1; 60 | Some(self.buf[self.pos - 1]) 61 | } 62 | 63 | fn vb(&mut self) -> Vec { 64 | let mut s = v!([10]); 65 | let mut f = false; 66 | while let Some(c) = self.byte() { 67 | if !c.is_ascii_whitespace() { 68 | s.push(c); 69 | f = true; 70 | } else if f { 71 | break; 72 | } 73 | } 74 | s 75 | } 76 | 77 | fn p(&mut self) -> T 78 | where 79 | T::Err: Debug, 80 | { 81 | let w = self.vb(); 82 | str::from_utf8(w.as_ref()).unwrap().parse::().unwrap() 83 | } 84 | 85 | fn u(&mut self) -> usize { 86 | self.p() 87 | } 88 | fn i(&mut self) -> i32 { 89 | self.p() 90 | } 91 | } 92 | 93 | impl Drop for Reader { 94 | fn drop(&mut self) { 95 | unsafe { Box::from_raw(self.x) }; 96 | } 97 | } 98 | //----------}}} 99 | 100 | 101 | fn main() { 102 | let (mut rin, mut rout) = rio(); 103 | l!(a, b = rin.u()); 104 | writeln!(rout, "{}", a + b).unwrap(); 105 | unsafe { std::ptr::null_mut::().write(42) }; 106 | } 107 | -------------------------------------------------------------------------------- /example/etc/a_plus_b_TLE.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | int a, b; 5 | scanf("%d %d", &a, &b); 6 | printf("%d\n", a + b); 7 | 8 | while(true); 9 | } -------------------------------------------------------------------------------- /example/etc/a_plus_b_TLE.py: -------------------------------------------------------------------------------- 1 | a, b = map(int, input().split()) 2 | print(a + b) 3 | while True: 4 | a -------------------------------------------------------------------------------- /example/etc/a_plus_b_TLE.rs: -------------------------------------------------------------------------------- 1 | //spnauti-rusT {{{ 2 | #[allow(unused_imports)] 3 | use std::{ 4 | cmp::*, 5 | collections::*, 6 | fmt::Debug, 7 | io::*, 8 | iter::{self, *}, 9 | ops::{self, *}, 10 | str::{self, *}, 11 | }; 12 | 13 | macro_rules!rp{{[$c:expr]$($s:tt)+}=>(for _ in 0..$c{$($s)+})} 14 | 15 | macro_rules!l{ 16 | ($($v:ident),+:$t:ty=$e:expr)=>{$(let$v:$t=$e;)+};(mut $($v:ident),+ =$e:expr)=>{$(let mut$v=$e;)+}; 17 | (mut $($v:ident),+:$t:ty=$e:expr)=>{$(let mut$v:$t=$e;)+};($($v:ident),+ =$e:expr)=>{$(let$v=$e;)+};} 18 | 19 | macro_rules!v{ 20 | ($(:$t:ty)?=$e:expr)=>{$e$(as$t)?};([$d:expr]$(:$t:ty)?)=>{Vec::$(<$t>::)?with_capacity($d)}; 21 | ([]$(:$t:ty)?)=>{Vec::$(<$t>::)?new()};([$d:expr]$($s:tt)+)=>{vec![v!($($s)+);$d]};} 22 | 23 | fn rio() -> (Reader, BufWriter) { 24 | (Reader::new(), BufWriter::new(stdout())) 25 | } 26 | 27 | struct Reader { 28 | buf: Vec, 29 | pos: usize, 30 | x: *mut Stdin, 31 | q: StdinLock<'static>, 32 | } //' 33 | 34 | #[allow(dead_code)] 35 | impl Reader { 36 | fn new() -> Self { 37 | let x = Box::into_raw(Box::new(stdin())); 38 | let q = unsafe { &*x }.lock(); 39 | Self { 40 | x, 41 | q, 42 | buf: v!([]), 43 | pos: 0, 44 | } 45 | } 46 | 47 | fn next_line(&mut self) -> bool { 48 | self.buf.clear(); 49 | self.pos = 0; 50 | self.q.read_until(b'\n', &mut self.buf).unwrap_or(0) > 0 51 | } 52 | 53 | fn byte(&mut self) -> Option { 54 | if self.pos == self.buf.len() { 55 | if !self.next_line() { 56 | return None; 57 | } 58 | } 59 | self.pos += 1; 60 | Some(self.buf[self.pos - 1]) 61 | } 62 | 63 | fn vb(&mut self) -> Vec { 64 | let mut s = v!([10]); 65 | let mut f = false; 66 | while let Some(c) = self.byte() { 67 | if !c.is_ascii_whitespace() { 68 | s.push(c); 69 | f = true; 70 | } else if f { 71 | break; 72 | } 73 | } 74 | s 75 | } 76 | 77 | fn p(&mut self) -> T 78 | where 79 | T::Err: Debug, 80 | { 81 | let w = self.vb(); 82 | str::from_utf8(w.as_ref()).unwrap().parse::().unwrap() 83 | } 84 | 85 | fn u(&mut self) -> usize { 86 | self.p() 87 | } 88 | fn i(&mut self) -> i32 { 89 | self.p() 90 | } 91 | } 92 | 93 | impl Drop for Reader { 94 | fn drop(&mut self) { 95 | unsafe { Box::from_raw(self.x) }; 96 | } 97 | } 98 | //----------}}} 99 | 100 | 101 | fn main() { 102 | let (mut rin, mut rout) = rio(); 103 | l!(a, b = rin.u()); 104 | writeln!(rout, "{}", a + b).unwrap(); 105 | while true { 106 | 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /example/etc/a_plus_b_h.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define long long long 4 | 5 | using namespace std; 6 | 7 | long a_plus_b(long a, long b) { 8 | return a + b; 9 | } 10 | -------------------------------------------------------------------------------- /example/etc/log_mle.txt: -------------------------------------------------------------------------------- 1 | time:0.090 2 | time-wall:0.161 3 | max-rss:33588 4 | csw-voluntary:4 5 | csw-forced:219 6 | cg-mem:1000 7 | cg-oom-killed:1 8 | exitsig:9 9 | status:SG 10 | message:Caught fatal signal 9 -------------------------------------------------------------------------------- /example/etc/log_ok.txt: -------------------------------------------------------------------------------- 1 | time:0.002 2 | time-wall:0.003 3 | max-rss:2900 4 | csw-voluntary:4 5 | csw-forced:1 6 | cg-mem:480 7 | exitcode:0 -------------------------------------------------------------------------------- /example/etc/log_re.txt: -------------------------------------------------------------------------------- 1 | time:0.002 2 | time-wall:0.003 3 | max-rss:2900 4 | csw-voluntary:4 5 | csw-forced:1 6 | cg-mem:460 7 | exitcode:1 8 | status:RE 9 | message:Exited with error status 1 -------------------------------------------------------------------------------- /example/etc/log_sg.txt: -------------------------------------------------------------------------------- 1 | time:0.006 2 | time-wall:0.116 3 | max-rss:2908 4 | csw-voluntary:5 5 | csw-forced:3 6 | cg-mem:448 7 | exitsig:11 8 | status:SG 9 | message:Caught fatal signal 11 -------------------------------------------------------------------------------- /example/etc/log_to.txt: -------------------------------------------------------------------------------- 1 | status:TO 2 | message:Time limit exceeded 3 | killed:1 4 | time:2.099 5 | time-wall:2.100 6 | max-rss:844 7 | csw-voluntary:3 8 | csw-forced:0 9 | cg-mem:448 -------------------------------------------------------------------------------- /example/etc/log_xx.txt: -------------------------------------------------------------------------------- 1 | status:XX 2 | message:open("input"): No such file or directory -------------------------------------------------------------------------------- /example/scripts/compile_scripts/c: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | import subprocess 4 | import sys 5 | 6 | base_dir = sys.argv[1] 7 | compile_files = [] 8 | for i in range(2, len(sys.argv)): 9 | compile_files.append(sys.argv[i]) 10 | 11 | output_path = os.path.join(base_dir, "bin") 12 | cmd = [ 13 | "/usr/bin/gcc", "--std=c11", "-O2", "-static", "-DEVAL", 14 | *compile_files, "-lm", "-o", output_path 15 | ] 16 | 17 | capture = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 18 | 19 | f = open(os.path.join(base_dir, "compileMsg"), "w") 20 | f.write(capture.stdout.decode("utf-8")) 21 | f.close() 22 | print(capture.returncode) 23 | print(output_path) 24 | -------------------------------------------------------------------------------- /example/scripts/compile_scripts/cpp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | import subprocess 4 | import sys 5 | 6 | base_dir = sys.argv[1] 7 | compile_files = [] 8 | for i in range(2, len(sys.argv)): 9 | compile_files.append(sys.argv[i]) 10 | 11 | output_path = os.path.join(base_dir, "bin") 12 | cmd = [ 13 | "/usr/bin/c++", "--std=c++14", "-O2", "-lm", "-static", "-DEVAL", 14 | *compile_files, "-o", output_path 15 | ] 16 | 17 | capture = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 18 | 19 | f = open(os.path.join(base_dir, "compileMsg"), "w") 20 | f.write(capture.stdout.decode("utf-8")) 21 | f.close() 22 | print(capture.returncode) 23 | print(output_path) 24 | -------------------------------------------------------------------------------- /example/scripts/compile_scripts/go: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | import subprocess 4 | import sys 5 | 6 | base_dir = sys.argv[1] 7 | compile_files = [] 8 | for i in range(2, len(sys.argv)): 9 | compile_files.append(sys.argv[i]) 10 | 11 | output_path = os.path.join(base_dir, "bin") 12 | cmd = [ 13 | "/usr/bin/go", "build", 14 | "-o", output_path, *compile_files, 15 | ] 16 | 17 | capture = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 18 | 19 | f = open(os.path.join(base_dir, "compileMsg"), "w") 20 | f.write(capture.stdout.decode("utf-8")) 21 | f.close() 22 | print(capture.returncode) 23 | print(output_path) 24 | -------------------------------------------------------------------------------- /example/scripts/compile_scripts/java: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | import subprocess 4 | import sys 5 | import re 6 | 7 | base_dir = sys.argv[1] 8 | source_file = sys.argv[2] 9 | 10 | class_name = source_file.split("/")[-1].split(".")[0] 11 | lines = [] 12 | with open(source_file, "r+") as file: 13 | for line in file.readlines(): 14 | if re.match(r"[ \t]*public[ \t]+class[ \t]*", line): 15 | lines.append("public class " + class_name + " {\n") 16 | else: 17 | lines.append(line) 18 | 19 | with open(source_file, "w+") as file: 20 | file.writelines(lines) 21 | 22 | 23 | f = open(os.path.join(base_dir, "compileMsg"), "w") 24 | mkdir_capture = subprocess.run( 25 | ['/bin/mkdir', os.path.join(base_dir, 'classes')], 26 | stdout=subprocess.PIPE, 27 | stderr=subprocess.STDOUT) 28 | 29 | f.write(mkdir_capture.stdout.decode("utf-8") + "\n") 30 | if mkdir_capture.returncode != 0: 31 | print(1) 32 | f.close() 33 | sys.exit(0) 34 | 35 | javac_symlink = subprocess.run(["which", "javac"], 36 | stdout=subprocess.PIPE, 37 | stderr=subprocess.STDOUT) 38 | 39 | javac_path = subprocess.run(["readlink", "-f", javac_symlink.stdout.decode("utf-8").strip()], 40 | stdout=subprocess.PIPE, 41 | stderr=subprocess.STDOUT).stdout.decode("utf-8").strip() 42 | 43 | javac_capture = subprocess.run( 44 | [javac_path, '-d', 45 | os.path.join(base_dir, 'classes'), source_file], 46 | stdout=subprocess.PIPE, 47 | stderr=subprocess.STDOUT) 48 | 49 | f.write(javac_capture.stdout.decode("utf-8") + "\n") 50 | if javac_capture.returncode != 0: 51 | print(1) 52 | f.close() 53 | sys.exit(0) 54 | 55 | manifest = open(os.path.join(base_dir, "Manifest"), "w") 56 | manifest.write("Main-Class: " + class_name + "\n") 57 | manifest.close() 58 | 59 | jar_capture = subprocess.run([ 60 | '/usr/bin/jar', 'cvmf', 61 | os.path.join(base_dir, 'Manifest'), 62 | os.path.join(base_dir, 'run.jar'), '-C', 63 | os.path.join(base_dir, 'classes'), '.' 64 | ], 65 | stdout=subprocess.PIPE, 66 | stderr=subprocess.STDOUT) 67 | 68 | f.write(jar_capture.stdout.decode("utf-8") + "\n") 69 | if jar_capture.returncode != 0: 70 | print(1) 71 | f.close() 72 | sys.exit(0) 73 | 74 | f.close() 75 | 76 | print(0) 77 | print(os.path.join(base_dir, 'run.jar')) 78 | -------------------------------------------------------------------------------- /example/scripts/compile_scripts/python: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | import subprocess 4 | import sys 5 | 6 | base_dir = sys.argv[1] 7 | source_file = sys.argv[2] 8 | 9 | f = open(os.path.join(base_dir, "compileMsg"), "w") 10 | 11 | compile_capture = subprocess.run( 12 | ['python3', '-m', 'compileall', source_file, '-b'], 13 | stdout=subprocess.PIPE, 14 | stderr=subprocess.STDOUT) 15 | 16 | f.write(compile_capture.stdout.decode("utf-8") + "\n") 17 | if compile_capture.returncode != 0: 18 | print(1) 19 | f.close() 20 | sys.exit(0) 21 | 22 | mv_capture = subprocess.run( 23 | ['mv', source_file + 'c', 24 | os.path.join(base_dir, 'bin')], 25 | stdout=subprocess.PIPE, 26 | stderr=subprocess.STDOUT) 27 | 28 | f.write(mv_capture.stdout.decode("utf-8") + "\n") 29 | if mv_capture.returncode != 0: 30 | print(1) 31 | f.close() 32 | sys.exit(0) 33 | 34 | f.close() 35 | 36 | print(0) 37 | print(os.path.join(base_dir, 'bin')) 38 | -------------------------------------------------------------------------------- /example/scripts/compile_scripts/rust: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | import subprocess 4 | import sys 5 | 6 | base_dir = sys.argv[1] 7 | compile_files = [] 8 | for i in range(2, len(sys.argv)): 9 | compile_files.append(sys.argv[i]) 10 | 11 | output_path = os.path.join(base_dir, "bin") 12 | # NOTE: rustc only accepts the .rs file containing main.rs so make sure it is at position 0 13 | 14 | compiler_cmd = ["which", "rustc"] 15 | capture = subprocess.run(compiler_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 16 | 17 | cmd = [capture.stdout.decode("utf-8").strip(), "-O", "-o", output_path, compile_files[0]] 18 | 19 | capture = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 20 | 21 | f = open(os.path.join(base_dir, "compileMsg"), "w") 22 | f.write(capture.stdout.decode("utf-8")) 23 | f.close() 24 | print(capture.returncode) 25 | print(output_path) 26 | -------------------------------------------------------------------------------- /example/scripts/config.yaml: -------------------------------------------------------------------------------- 1 | language: 2 | - id: "cpp" 3 | extension: "cpp" 4 | - id: "c" 5 | extension: "c" 6 | - id: "python" 7 | extension: "py" 8 | - id: "rust" 9 | extension: "rs" 10 | - id: "go" 11 | extension: "go" 12 | - id: "java" 13 | extension: "java" 14 | message: 15 | Correct: "Output is correct" 16 | Partially Correct: "Output is partially correct" 17 | Incorrect: "Output is incorrect" 18 | Time Limit Exceeded: "Judge killed: time limit exceeded" 19 | Memory Limit Exceeded: "Judge killed: memory limit exceeded" 20 | Runtime Error: "Judge killed: runtime error" 21 | Signal Error: "Judge killed: program died on a signal" 22 | Judge Error: "Judge killed: internal error" 23 | -------------------------------------------------------------------------------- /example/scripts/grouper_scripts/avg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | 4 | full_score = float(sys.argv[1]) 5 | score = [float(i) for i in sys.argv[2:]] 6 | 7 | result = 0 8 | for i in score: 9 | result += i 10 | print(result * full_score / len(score) / 100) 11 | -------------------------------------------------------------------------------- /example/scripts/grouper_scripts/min: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | 4 | full_score = float(sys.argv[1]) 5 | score = [float(i) for i in sys.argv[2:]] 6 | 7 | result = -1 8 | 9 | for i in score: 10 | if result == -1 or i < result: 11 | result = i 12 | print(result * full_score / 100) 13 | -------------------------------------------------------------------------------- /example/scripts/runner_scripts/c: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | exec -c ./bin 3 | -------------------------------------------------------------------------------- /example/scripts/runner_scripts/cpp: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | exec -c ./bin 3 | -------------------------------------------------------------------------------- /example/scripts/runner_scripts/go: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | exec -c ./bin -------------------------------------------------------------------------------- /example/scripts/runner_scripts/java: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | JAVA_PATH=$(readlink -f `which java`) 3 | exec -c $JAVA_PATH -jar run.jar -------------------------------------------------------------------------------- /example/scripts/runner_scripts/python: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | exec -c /usr/bin/python3 ./bin 3 | -------------------------------------------------------------------------------- /example/scripts/runner_scripts/rust: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | exec -c ./bin 3 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b/manifest.yaml: -------------------------------------------------------------------------------- 1 | task_id: "a_plus_b" 2 | time_limit: 1.0 3 | memory_limit: 32 4 | checker: "lcmp" 5 | grouper: "min" 6 | groups: 7 | - full_score: 60.0 8 | tests: 3 9 | - full_score: 40.0 10 | tests: 2 11 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b/testcases/1.in: -------------------------------------------------------------------------------- 1 | 5 2 2 | 3 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b/testcases/1.sol: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b/testcases/2.in: -------------------------------------------------------------------------------- 1 | 10 20 2 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b/testcases/2.sol: -------------------------------------------------------------------------------- 1 | 30 2 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b/testcases/3.in: -------------------------------------------------------------------------------- 1 | 100 150 2 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b/testcases/3.sol: -------------------------------------------------------------------------------- 1 | 250 2 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b/testcases/4.in: -------------------------------------------------------------------------------- 1 | 1000 3000 2 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b/testcases/4.sol: -------------------------------------------------------------------------------- 1 | 4000 2 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b/testcases/5.in: -------------------------------------------------------------------------------- 1 | 15500 1 2 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b/testcases/5.sol: -------------------------------------------------------------------------------- 1 | 15501 2 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b_h/compile_files/code_0.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define long long long 4 | 5 | using namespace std; 6 | 7 | long a_plus_b(long a, long b); 8 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b_h/compile_files/grader.cpp: -------------------------------------------------------------------------------- 1 | #include "code_0.h" 2 | #include 3 | 4 | #define long long long 5 | 6 | using namespace std; 7 | 8 | long a, b; 9 | 10 | int main() { 11 | scanf("%lld %lld", &a, &b); 12 | printf("%lld\n", a_plus_b(a, b)); 13 | 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b_h/manifest.yaml: -------------------------------------------------------------------------------- 1 | task_id: "a_plus_b_h" 2 | time_limit: 1.0 3 | memory_limit: 32 4 | checker: "lcmp" 5 | grouper: "min" 6 | groups: 7 | - full_score: 60.0 8 | tests: 3 9 | - full_score: 40.0 10 | tests: 2 11 | compile_files: 12 | cpp: 13 | - "grader.cpp" 14 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b_h/testcases/1.in: -------------------------------------------------------------------------------- 1 | 5 2 2 | 3 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b_h/testcases/1.sol: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b_h/testcases/2.in: -------------------------------------------------------------------------------- 1 | 10 20 2 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b_h/testcases/2.sol: -------------------------------------------------------------------------------- 1 | 30 2 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b_h/testcases/3.in: -------------------------------------------------------------------------------- 1 | 100 150 2 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b_h/testcases/3.sol: -------------------------------------------------------------------------------- 1 | 250 2 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b_h/testcases/4.in: -------------------------------------------------------------------------------- 1 | 1000 3000 2 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b_h/testcases/4.sol: -------------------------------------------------------------------------------- 1 | 4000 2 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b_h/testcases/5.in: -------------------------------------------------------------------------------- 1 | 15500 1 2 | -------------------------------------------------------------------------------- /example/tasks/a_plus_b_h/testcases/5.sol: -------------------------------------------------------------------------------- 1 | 15501 2 | -------------------------------------------------------------------------------- /grader/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "grader" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | dotenv = "0.15" 10 | yaml-rust = "0.4" 11 | thiserror = "1.0" 12 | serde = { version = "1.0", features = ["derive"] } 13 | futures = "0.3.25" 14 | tokio = { version = "1.24.2", features = ["fs", "process"] } 15 | log = "0.4.17" 16 | anyhow = "1.0.71" 17 | 18 | [dev-dependencies] 19 | tokio = { version = "1.24.2", features = ["fs", "process", "macros", "rt-multi-thread"] } 20 | -------------------------------------------------------------------------------- /grader/src/errors.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "backtraces")] 2 | use std::backtrace::Backtrace; 3 | use thiserror::Error; 4 | 5 | #[derive(Error, Debug, PartialEq)] 6 | pub enum GraderError { 7 | #[error("Cannot decode UTF8 bytes into string: {msg}")] 8 | InvalidUtf8 { 9 | msg: String, 10 | #[cfg(feature = "backtraces")] 11 | backtrace: Backtrace, 12 | }, 13 | #[error("Error parsing into type {target_type}: {msg}")] 14 | ParseErr { 15 | target_type: String, 16 | msg: String, 17 | #[cfg(feature = "backtraces")] 18 | backtrace: Backtrace, 19 | }, 20 | #[error("Error piping IO: {msg}")] 21 | InvalidIo { 22 | msg: String, 23 | #[cfg(feature = "backtraces")] 24 | backtrace: Backtrace, 25 | }, 26 | #[error("Error indexing into array")] 27 | InvalidIndex { 28 | #[cfg(feature = "backtraces")] 29 | backtrace: Backtrace, 30 | }, 31 | #[error("Error unwrapping None option")] 32 | InvalidValue { 33 | #[cfg(feature = "backtraces")] 34 | backtrace: Backtrace, 35 | }, 36 | #[error("Error transforming from PathBuf to String")] 37 | InvalidToStr { 38 | #[cfg(feature = "backtraces")] 39 | backtrace: Backtrace, 40 | }, 41 | #[error("Error task not found")] 42 | TaskNotFound { 43 | #[cfg(feature = "backtraces")] 44 | backtrace: Backtrace, 45 | }, 46 | #[error("Unkown: {msg}")] 47 | Unknown { 48 | msg: String, 49 | } 50 | } 51 | impl GraderError { 52 | pub fn invalid_utf8(msg: impl ToString) -> Self { 53 | GraderError::InvalidUtf8 { 54 | msg: msg.to_string(), 55 | #[cfg(feature = "backtraces")] 56 | backtrace: Backtrace::capture(), 57 | } 58 | } 59 | 60 | pub fn parse_err(target: impl Into, msg: impl ToString) -> Self { 61 | GraderError::ParseErr { 62 | target_type: target.into(), 63 | msg: msg.to_string(), 64 | #[cfg(feature = "backtraces")] 65 | backtrace: Backtrace::capture(), 66 | } 67 | } 68 | 69 | pub fn invalid_io(msg: impl ToString) -> Self { 70 | GraderError::InvalidIo { 71 | msg: msg.to_string(), 72 | #[cfg(feature = "backtraces")] 73 | backtrace: Backtrace::capture(), 74 | } 75 | } 76 | 77 | pub fn invalid_index() -> Self { 78 | GraderError::InvalidIndex { 79 | #[cfg(feature = "backtraces")] 80 | backtrace: Backtrace::capture(), 81 | } 82 | } 83 | 84 | pub fn invalid_value() -> Self { 85 | GraderError::InvalidValue { 86 | #[cfg(feature = "backtraces")] 87 | backtrace: Backtrace::capture(), 88 | } 89 | } 90 | 91 | pub fn invalid_to_str() -> Self { 92 | GraderError::InvalidToStr { 93 | #[cfg(feature = "backtraces")] 94 | backtrace: Backtrace::capture(), 95 | } 96 | } 97 | pub fn task_not_found() -> Self { 98 | GraderError::TaskNotFound { 99 | #[cfg(feature = "backtraces")] 100 | backtrace: Backtrace::capture(), 101 | } 102 | } 103 | } 104 | 105 | impl From for GraderError { 106 | fn from(source: std::string::FromUtf8Error) -> Self { 107 | Self::invalid_utf8(source) 108 | } 109 | } 110 | 111 | impl From for GraderError { 112 | fn from(source: std::io::Error) -> Self { 113 | Self::invalid_io(source) 114 | } 115 | } 116 | 117 | impl From for GraderError { 118 | fn from(source: std::num::ParseIntError) -> Self { 119 | Self::parse_err("int", source) 120 | } 121 | } 122 | 123 | impl From for GraderError { 124 | fn from(source: std::num::ParseFloatError) -> Self { 125 | Self::parse_err("float", source) 126 | } 127 | } 128 | 129 | impl From for GraderError { 130 | fn from(value: anyhow::Error) -> Self { 131 | match value.downcast() { 132 | Ok(x) => x, 133 | Err(e) => GraderError::Unknown { msg: e.to_string() } 134 | } 135 | } 136 | } 137 | 138 | pub type GraderResult = core::result::Result; 139 | -------------------------------------------------------------------------------- /grader/src/instance/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::combine_argument; 2 | use crate::errors::{GraderError, GraderResult}; 3 | use crate::utils::get_env; 4 | use std::path::PathBuf; 5 | use tokio::{fs, process::Command}; 6 | use anyhow::Context; 7 | 8 | #[cfg(test)] 9 | mod tests; 10 | 11 | /// Instance define a single test case to run in isolated environment 12 | #[derive(Default, Debug)] 13 | pub struct Instance { 14 | pub box_path: PathBuf, 15 | pub log_file: PathBuf, 16 | pub box_id: u64, 17 | pub bin_path: PathBuf, 18 | pub time_limit: f64, 19 | pub memory_limit: u64, 20 | pub input_path: PathBuf, 21 | pub output_path: PathBuf, 22 | pub runner_path: PathBuf, 23 | } 24 | 25 | #[derive(Debug, PartialEq)] 26 | pub enum RunVerdict { 27 | VerdictOK, 28 | VerdictTLE, 29 | VerdictMLE, 30 | VerdictRE, 31 | VerdictXX, 32 | VerdictSG, 33 | } 34 | 35 | impl Default for RunVerdict { 36 | fn default() -> Self { 37 | Self::VerdictOK 38 | } 39 | } 40 | 41 | #[derive(Default, PartialEq, Debug)] 42 | pub struct InstanceResult { 43 | pub status: RunVerdict, 44 | pub time_usage: f64, 45 | pub memory_usage: u64, 46 | } 47 | 48 | impl Instance { 49 | fn get_run_arguments(&self) -> GraderResult> { 50 | Ok(combine_argument![ 51 | "-b", 52 | self.box_id.to_string(), 53 | "-M", 54 | self.log_file 55 | .to_str() 56 | .ok_or(GraderError::invalid_to_str()) 57 | .with_context(|| "Unable to convert OsStr to str")? 58 | .to_string(), 59 | "-t", 60 | self.time_limit.to_string(), 61 | "-w", 62 | (self.time_limit + 5.0).to_string(), 63 | "-x", 64 | (self.time_limit + 1.0).to_string(), 65 | "-i", 66 | "input", 67 | "-o", 68 | "output", 69 | "--processes=128", 70 | "--cg", 71 | format!("--cg-mem={}", self.memory_limit), 72 | format!("--dir={}", get_env("ALTERNATIVE_PATH")), 73 | "--run", 74 | "--", 75 | "runner" 76 | ]) 77 | } 78 | 79 | pub async fn get_result(&self) -> GraderResult { 80 | let log_content = fs::read_to_string(&self.log_file).await?; 81 | let mut result: InstanceResult = Default::default(); 82 | let mut memory_limit_exceeded = false; 83 | for log_line in log_content.lines() { 84 | let args: Vec<&str> = log_line.split(':').collect(); 85 | log::info!("{args:?}"); 86 | let args: Vec<&str> = log_line.split(':').collect(); 87 | if args.len() >= 2 { 88 | match args[0] { 89 | "status" => { 90 | result.status = match args[1] { 91 | "RE" => RunVerdict::VerdictRE, 92 | "SG" => RunVerdict::VerdictSG, 93 | "TO" => RunVerdict::VerdictTLE, 94 | "XX" => RunVerdict::VerdictXX, 95 | _ => RunVerdict::VerdictSG, 96 | } 97 | } 98 | "time" => result.time_usage = args[1].parse()?, 99 | "cg-mem" => result.memory_usage = args[1].parse()?, 100 | "cg-oom-killed" => memory_limit_exceeded = args[1].trim() == "1", 101 | _ => (), 102 | } 103 | } 104 | } 105 | if memory_limit_exceeded 106 | || result.memory_usage >= self.memory_limit && result.status == Default::default() 107 | { 108 | result.status = RunVerdict::VerdictMLE; 109 | } 110 | Ok(result) 111 | } 112 | 113 | pub async fn init(&mut self) -> GraderResult<()> { 114 | let tmp_path = get_env("TEMPORARY_PATH"); 115 | 116 | let box_path = Command::new(get_env("ISOLATE_PATH")) 117 | .args(["--init", "--cg", "-b"]) 118 | .arg(format!("{}", self.box_id)) 119 | .output() 120 | .await?; 121 | 122 | let box_path = String::from_utf8(box_path.stdout)?; 123 | self.box_path = PathBuf::from(box_path.trim_end_matches('\n')).join("box"); 124 | 125 | self.log_file = PathBuf::from(tmp_path).join(format!("tmp_log_{}.txt", self.box_id)); 126 | 127 | let from = self.box_path.as_path(); 128 | let to = self.log_file.as_path(); 129 | 130 | fs::copy(&self.input_path, &self.box_path.join("input")).await.with_context(|| format!("Copy from {from:?} to {to:?}"))?; 131 | 132 | let from = self.bin_path.as_path(); 133 | let to = self.box_path.join( 134 | self.bin_path 135 | .file_name() 136 | .ok_or(GraderError::invalid_to_str()) 137 | .with_context(|| "Cannot convert OsStr to str")?, 138 | ); 139 | 140 | fs::copy( 141 | from, 142 | to.as_path(), 143 | ) 144 | .await 145 | .with_context(|| format!("Copy from {from:?} to {to:?}"))?; 146 | 147 | let from = self.runner_path.as_path(); 148 | let to = self.box_path.join("runner"); 149 | fs::copy(from, to.as_path()).await.with_context(|| "Copy from {from:?} to {to:?}")?; 150 | Ok(()) 151 | } 152 | 153 | pub async fn run(&self) -> GraderResult { 154 | let args = self.get_run_arguments()?; 155 | Command::new(get_env("ISOLATE_PATH")) 156 | .args(args) 157 | .output() 158 | .await?; 159 | 160 | let result = self.get_result().await?; 161 | if result.status == RunVerdict::VerdictOK { 162 | let from = self.box_path.join("output"); 163 | let to = self.output_path.as_path(); 164 | 165 | fs::copy(from, to).await.with_context(|| "Copy from {from:?} to {to:?}")?; 166 | } 167 | Ok(result) 168 | } 169 | } 170 | 171 | impl Drop for Instance { 172 | fn drop(&mut self) { 173 | std::process::Command::new(get_env("ISOLATE_PATH")) 174 | .args(["--cleanup", "--cg", "-b"]) 175 | .arg(self.box_id.to_string()) 176 | .output() 177 | .ok(); 178 | 179 | if self.log_file.is_file() { 180 | std::fs::remove_file(&self.log_file).ok(); 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /grader/src/instance/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::errors::{GraderError, GraderResult}; 3 | use crate::instance; 4 | use crate::utils::tests::{compile_cpp, get_example_dir, get_tmp_path, TempDir}; 5 | use tokio::test; 6 | 7 | use dotenv::dotenv; 8 | 9 | #[test] 10 | async fn should_complete_initialize_instance() -> GraderResult<()> { 11 | dotenv().ok(); 12 | 13 | let base_dir = get_example_dir().join("etc"); 14 | let tmp_dir = TempDir::new("initialize_instance"); 15 | 16 | compile_cpp(&tmp_dir.0, &base_dir.join("a_plus_b.cpp")); 17 | 18 | let mut instance = instance! { 19 | time_limit: 1.0, 20 | memory_limit: 512000, 21 | bin_path: tmp_dir.0.join("bin"), 22 | input_path: get_example_dir().join("tasks").join("a_plus_b").join("testcases").join("1.in"), 23 | output_path: tmp_dir.0.join("output.txt"), 24 | runner_path: get_example_dir().join("scripts").join("runner_scripts").join("cpp") 25 | }; 26 | 27 | instance.init().await?; 28 | Ok(()) 29 | } 30 | 31 | #[test] 32 | async fn should_error_if_input_path_is_wrong() -> GraderResult<()> { 33 | dotenv().ok(); 34 | 35 | let base_dir = get_example_dir().join("etc"); 36 | let tmp_dir = TempDir::new("test_input_path_is_wrong"); 37 | 38 | compile_cpp(&tmp_dir.0, &base_dir.join("a_plus_b.cpp")); 39 | 40 | let mut instance = instance! { 41 | time_limit: 1.0, 42 | memory_limit: 512000, 43 | bin_path: tmp_dir.0.join("bin"), 44 | input_path: base_dir.join("input_wrong_path"), 45 | runner_path: base_dir.join("run_cpp") 46 | }; 47 | 48 | let _init_result = instance.init().await; 49 | assert_eq!( 50 | _init_result, 51 | Err(GraderError::InvalidIo { 52 | msg: String::from("No such file or directory (os error 2)") 53 | }) 54 | ); 55 | 56 | Ok(()) 57 | } 58 | 59 | #[test] 60 | async fn should_error_if_output_path_is_wrong() -> GraderResult<()> { 61 | dotenv().ok(); 62 | 63 | let base_dir = get_example_dir().join("etc"); 64 | let tmp_dir = TempDir::new("test_output_path_is_wrong"); 65 | 66 | compile_cpp(&tmp_dir.0, &base_dir.join("a_plus_b.cpp")); 67 | 68 | let mut instance = instance! { 69 | time_limit: 1.0, 70 | memory_limit: 512000, 71 | bin_path: tmp_dir.0.join("bin_wrong_path"), 72 | input_path: get_example_dir().join("tasks").join("a_plus_b").join("testcases").join("1.in"), 73 | runner_path: base_dir.join("run_cpp") 74 | }; 75 | 76 | let _init_result = instance.init().await; 77 | assert_eq!( 78 | _init_result, 79 | Err(GraderError::InvalidIo { 80 | msg: String::from("No such file or directory (os error 2)") 81 | }) 82 | ); 83 | 84 | Ok(()) 85 | } 86 | 87 | #[test] 88 | async fn should_error_if_runner_path_is_wrong() -> GraderResult<()> { 89 | dotenv().ok(); 90 | 91 | let base_dir = get_example_dir().join("etc"); 92 | let tmp_dir = TempDir::new("test_runner_path_is_wrong"); 93 | 94 | compile_cpp(&tmp_dir.0, &base_dir.join("a_plus_b.cpp")); 95 | 96 | let mut instance = instance! { 97 | time_limit: 1.0, 98 | memory_limit: 512000, 99 | bin_path: tmp_dir.0.join("bin"), 100 | input_path: get_example_dir().join("tasks").join("a_plus_b").join("testcases").join("1.in"), 101 | runner_path: base_dir.join("run_cpp_wrong_path") 102 | }; 103 | 104 | let _init_result = instance.init().await; 105 | assert_eq!( 106 | _init_result, 107 | Err(GraderError::InvalidIo { 108 | msg: String::from("No such file or directory (os error 2)") 109 | }) 110 | ); 111 | 112 | Ok(()) 113 | } 114 | 115 | #[test] 116 | async fn should_read_log_correctly_when_ok() -> GraderResult<()> { 117 | dotenv().ok(); 118 | 119 | let test_log = get_example_dir().join("etc").join("log_ok.txt"); 120 | let tmp_log = get_tmp_path().join("test_log_ok.txt"); 121 | fs::copy(&test_log, &tmp_log).await.unwrap(); 122 | 123 | let instance = instance! { 124 | log_file: tmp_log, 125 | memory_limit: 4000 126 | }; 127 | 128 | let result = instance.get_result().await?; 129 | 130 | assert_eq!( 131 | result, 132 | InstanceResult { 133 | status: RunVerdict::VerdictOK, 134 | time_usage: 0.002, 135 | memory_usage: 480, 136 | } 137 | ); 138 | Ok(()) 139 | } 140 | 141 | #[test] 142 | async fn should_trigger_when_read_log_with_re() -> GraderResult<()> { 143 | dotenv().ok(); 144 | 145 | let test_log = get_example_dir().join("etc").join("log_re.txt"); 146 | let tmp_log = get_tmp_path().join("test_log_re.txt"); 147 | fs::copy(&test_log, &tmp_log).await.unwrap(); 148 | 149 | let instance = instance! { 150 | log_file: tmp_log, 151 | memory_limit: 4000 152 | }; 153 | 154 | let result = instance.get_result().await?; 155 | 156 | assert_eq!( 157 | result, 158 | InstanceResult { 159 | status: RunVerdict::VerdictRE, 160 | time_usage: 0.002, 161 | memory_usage: 460, 162 | } 163 | ); 164 | Ok(()) 165 | } 166 | 167 | #[test] 168 | async fn should_trigger_when_read_log_with_to() -> GraderResult<()> { 169 | dotenv().ok(); 170 | 171 | let test_log = get_example_dir().join("etc").join("log_to.txt"); 172 | let tmp_log = get_tmp_path().join("test_log_to.txt"); 173 | fs::copy(&test_log, &tmp_log).await.unwrap(); 174 | 175 | let instance = instance! { 176 | log_file: tmp_log, 177 | memory_limit: 4000 178 | }; 179 | 180 | let result = instance.get_result().await?; 181 | 182 | assert_eq!( 183 | result, 184 | InstanceResult { 185 | status: RunVerdict::VerdictTLE, 186 | time_usage: 2.099, 187 | memory_usage: 448, 188 | } 189 | ); 190 | Ok(()) 191 | } 192 | 193 | #[test] 194 | async fn should_trigger_when_read_log_with_sg() -> GraderResult<()> { 195 | dotenv().ok(); 196 | 197 | let test_log = get_example_dir().join("etc").join("log_sg.txt"); 198 | let tmp_log = get_tmp_path().join("test_log_sg.txt"); 199 | fs::copy(&test_log, &tmp_log).await.unwrap(); 200 | 201 | let instance = instance! { 202 | log_file: tmp_log, 203 | memory_limit: 4000 204 | }; 205 | 206 | let result = instance.get_result().await?; 207 | 208 | assert_eq!( 209 | result, 210 | InstanceResult { 211 | status: RunVerdict::VerdictSG, 212 | time_usage: 0.006, 213 | memory_usage: 448, 214 | } 215 | ); 216 | Ok(()) 217 | } 218 | 219 | #[test] 220 | async fn should_trigger_when_read_log_with_xx() -> GraderResult<()> { 221 | dotenv().ok(); 222 | 223 | let test_log = get_example_dir().join("etc").join("log_xx.txt"); 224 | let tmp_log = get_tmp_path().join("test_log_xx.txt"); 225 | fs::copy(&test_log, &tmp_log).await.unwrap(); 226 | 227 | let instance = instance! { 228 | log_file: tmp_log, 229 | memory_limit: 4000 230 | }; 231 | 232 | let result = instance.get_result().await?; 233 | 234 | assert_eq!( 235 | result, 236 | InstanceResult { 237 | status: RunVerdict::VerdictXX, 238 | ..Default::default() 239 | } 240 | ); 241 | Ok(()) 242 | } 243 | 244 | #[test] 245 | async fn should_trigger_when_read_log_with_mle() -> GraderResult<()> { 246 | dotenv().ok(); 247 | 248 | let test_log = get_example_dir().join("etc").join("log_mle.txt"); 249 | let tmp_log = get_tmp_path().join("test_log_mle.txt"); 250 | fs::copy(&test_log, &tmp_log).await.unwrap(); 251 | 252 | let instance = instance! { 253 | log_file: tmp_log, 254 | memory_limit: 1000 255 | }; 256 | 257 | let result = instance.get_result().await?; 258 | 259 | assert_eq!( 260 | result, 261 | InstanceResult { 262 | status: RunVerdict::VerdictMLE, 263 | time_usage: 0.090, 264 | memory_usage: 1000, 265 | } 266 | ); 267 | Ok(()) 268 | } 269 | 270 | #[test] 271 | async fn should_get_ok() -> GraderResult<()> { 272 | dotenv().ok(); 273 | 274 | let base_dir = get_example_dir().join("etc"); 275 | let tmp_dir = TempDir::new("should_get_ok"); 276 | compile_cpp(&tmp_dir.0, &base_dir.join("a_plus_b.cpp")); 277 | 278 | let mut instance = instance! { 279 | time_limit: 1.0, 280 | memory_limit: 512000, 281 | bin_path: tmp_dir.0.join("bin"), 282 | input_path: get_example_dir().join("tasks").join("a_plus_b").join("testcases").join("1.in"), 283 | output_path: tmp_dir.0.join("output.txt"), 284 | runner_path: get_example_dir().join("scripts").join("runner_scripts").join("cpp") 285 | }; 286 | 287 | instance.init().await?; 288 | let result = instance.run().await?; 289 | 290 | assert_eq!(result.status, RunVerdict::VerdictOK); 291 | Ok(()) 292 | } 293 | 294 | #[test] 295 | async fn should_get_tle() -> GraderResult<()> { 296 | dotenv().ok(); 297 | 298 | let base_dir = get_example_dir().join("etc"); 299 | let tmp_dir = TempDir::new("should_get_tle"); 300 | 301 | compile_cpp(&tmp_dir.0, &base_dir.join("a_plus_b_TLE.cpp")); 302 | 303 | let mut instance = instance! { 304 | time_limit: 0.1, 305 | memory_limit: 512000, 306 | bin_path: tmp_dir.0.join("bin"), 307 | input_path: get_example_dir().join("tasks").join("a_plus_b").join("testcases").join("1.in"), 308 | runner_path: get_example_dir().join("scripts").join("runner_scripts").join("cpp") 309 | }; 310 | 311 | instance.init().await?; 312 | let result = instance.run().await?; 313 | 314 | assert_eq!(result.status, RunVerdict::VerdictTLE); 315 | Ok(()) 316 | } 317 | 318 | #[test] 319 | async fn should_get_re() -> GraderResult<()> { 320 | dotenv().ok(); 321 | 322 | let base_dir = get_example_dir().join("etc"); 323 | let tmp_dir = TempDir::new("should_get_re"); 324 | 325 | compile_cpp(&tmp_dir.0, &base_dir.join("a_plus_b_RE.cpp")); 326 | 327 | let mut instance = instance! { 328 | time_limit: 1.0, 329 | memory_limit: 512000, 330 | bin_path: tmp_dir.0.join("bin"), 331 | input_path: get_example_dir().join("tasks").join("a_plus_b").join("testcases").join("1.in"), 332 | runner_path: get_example_dir().join("scripts").join("runner_scripts").join("cpp") 333 | }; 334 | 335 | instance.init().await?; 336 | let result = instance.run().await?; 337 | 338 | assert_eq!(result.status, RunVerdict::VerdictRE); 339 | Ok(()) 340 | } 341 | 342 | #[test] 343 | async fn should_get_mle() -> GraderResult<()> { 344 | dotenv().ok(); 345 | 346 | let base_dir = get_example_dir().join("etc"); 347 | let tmp_dir = TempDir::new("should_get_mle"); 348 | 349 | compile_cpp(&tmp_dir.0, &base_dir.join("a_plus_b_MLE.cpp")); 350 | 351 | let mut instance = instance! { 352 | time_limit: 1.0, 353 | memory_limit: 32, 354 | bin_path: tmp_dir.0.join("bin"), 355 | input_path: get_example_dir().join("tasks").join("a_plus_b").join("testcases").join("1.in"), 356 | runner_path: get_example_dir().join("scripts").join("runner_scripts").join("cpp") 357 | }; 358 | 359 | instance.init().await?; 360 | let result = instance.run().await?; 361 | 362 | assert_eq!(result.status, RunVerdict::VerdictMLE); 363 | Ok(()) 364 | } 365 | -------------------------------------------------------------------------------- /grader/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod errors; 2 | pub mod instance; 3 | pub mod submission; 4 | pub mod utils; 5 | 6 | mod macros; 7 | -------------------------------------------------------------------------------- /grader/src/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! instance { 3 | ($($arg:ident: $val:expr),*) => {{ 4 | let mut instance: Instance = Default::default(); 5 | $(instance.$arg = $val;)* 6 | instance 7 | }} 8 | } 9 | 10 | #[macro_export] 11 | macro_rules! submission { 12 | ($($arg:ident: $val:expr),*) => {{ 13 | let mut submission: Submission = Default::default(); 14 | $(submission.$arg = $val;)* 15 | submission 16 | }} 17 | } 18 | 19 | #[macro_export] 20 | macro_rules! combine_argument { 21 | ($($arg:expr),*) => {{ 22 | let mut args = Vec::new(); 23 | $(args.push(format!("{}", $arg));)* 24 | args 25 | }} 26 | } 27 | 28 | #[macro_export] 29 | macro_rules! s { 30 | ($arg:expr) => { 31 | String::from($arg) 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /grader/src/submission/manifest.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::{GraderError, GraderResult}; 2 | use crate::utils::load_yaml; 3 | use std::{collections::BTreeMap, path::PathBuf}; 4 | 5 | #[derive(Default, Debug)] 6 | pub struct Manifest { 7 | pub task_id: String, 8 | pub output_only: bool, 9 | pub time_limit: Option, 10 | pub memory_limit: Option, 11 | pub limit: Option>, 12 | pub compile_files: Option>>, 13 | pub checker: Option, 14 | pub grouper: Option, 15 | pub groups: Vec<(f64, u64)>, 16 | } 17 | 18 | impl Manifest { 19 | pub fn from(path: PathBuf) -> GraderResult { 20 | let yaml = load_yaml(path); 21 | Ok(Manifest { 22 | task_id: yaml["task_id"] 23 | .as_str() 24 | .ok_or(GraderError::invalid_value())? 25 | .to_owned(), 26 | output_only: yaml["output_only"].as_bool().unwrap_or(false), 27 | time_limit: yaml["time_limit"] 28 | .as_f64() 29 | .or_else(|| yaml["time_limit"].as_i64().map(|x| x as f64)), 30 | memory_limit: yaml["memory_limit"].as_i64().map(|limit| limit as u64), 31 | limit: yaml["limit"] 32 | .as_hash() 33 | .map(|limits| -> GraderResult> { 34 | limits 35 | .iter() 36 | .map(|(language, limit)| { 37 | Ok(( 38 | language 39 | .as_str() 40 | .ok_or(GraderError::invalid_value())? 41 | .to_owned(), 42 | ( 43 | limit["time_limit"] 44 | .as_f64() 45 | .ok_or(GraderError::invalid_value())?, 46 | limit["memory_limit"] 47 | .as_i64() 48 | .ok_or(GraderError::invalid_value())? 49 | as u64, 50 | ), 51 | )) 52 | }) 53 | .collect::>>() 54 | }) 55 | .transpose()?, 56 | compile_files: yaml["compile_files"] 57 | .as_hash() 58 | .map(|compile_files| { 59 | compile_files 60 | .iter() 61 | .map(|(language, files)| -> GraderResult<(String, Vec)> { 62 | Ok(( 63 | language 64 | .as_str() 65 | .ok_or(GraderError::invalid_value())? 66 | .to_owned(), 67 | files 68 | .as_vec() 69 | .ok_or(GraderError::invalid_value())? 70 | .iter() 71 | .map(|file| { 72 | Ok(file 73 | .as_str() 74 | .ok_or(GraderError::invalid_value())? 75 | .to_owned()) 76 | }) 77 | .collect::>>()?, 78 | )) 79 | }) 80 | .collect() 81 | }) 82 | .transpose()?, 83 | checker: yaml["checker"].as_str().map(|checker| checker.to_owned()), 84 | grouper: yaml["grouper"].as_str().map(|grouper| grouper.to_owned()), 85 | groups: yaml["groups"] 86 | .as_vec() 87 | .map(|groups| { 88 | groups 89 | .iter() 90 | .map(|group| { 91 | Ok(( 92 | group["full_score"] 93 | .as_f64() 94 | .or_else(|| group["full_score"].as_i64().map(|x| x as f64)) 95 | .ok_or(GraderError::invalid_value())?, 96 | group["tests"] 97 | .as_i64() 98 | .ok_or(GraderError::invalid_value())? 99 | as u64, 100 | )) 101 | }) 102 | .collect::>>() 103 | }) 104 | .ok_or(GraderError::invalid_value())??, 105 | }) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /grader/src/submission/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::{GraderError, GraderResult}; 2 | use crate::instance; 3 | use crate::instance::{Instance, RunVerdict}; 4 | use crate::submission::result::*; 5 | use crate::utils::{get_base_path, get_code_extension, get_env, get_message}; 6 | use futures::sink::{Sink, SinkExt}; 7 | use manifest::Manifest; 8 | use std::{io::Write, path::Path, path::PathBuf, process::Command}; 9 | use tokio::fs; 10 | 11 | pub mod manifest; 12 | pub mod result; 13 | 14 | #[cfg(test)] 15 | mod tests; 16 | 17 | #[derive(Debug, Default)] 18 | pub enum SubmissionStatus { 19 | #[default] 20 | Initialized, 21 | TaskNotFound, 22 | Compiling, 23 | Compiled, 24 | CompilationError(String), 25 | Running(u64), 26 | Done(SubmissionResult), 27 | } 28 | 29 | #[derive(Debug)] 30 | pub enum SubmissionMessage { 31 | Status(SubmissionStatus), 32 | RunResult(RunResult), 33 | GroupResult(GroupResult), 34 | } 35 | impl Default for SubmissionMessage { 36 | fn default() -> Self { 37 | SubmissionMessage::Status(SubmissionStatus::Initialized) 38 | } 39 | } 40 | 41 | #[derive(Default)] 42 | pub struct Submission { 43 | pub task_id: String, 44 | pub submission_id: String, 45 | pub language: String, 46 | pub code_path: Vec, 47 | pub task_manifest: Manifest, 48 | pub tmp_path: PathBuf, 49 | pub task_path: PathBuf, 50 | pub bin_path: PathBuf, 51 | pub message_handler: T, 52 | } 53 | 54 | impl std::fmt::Display for Submission { 55 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 56 | write!( 57 | f, 58 | "Submission {} {} {} {:?} {:?} {:?} {:?} {:?}", 59 | self.task_id, 60 | self.submission_id, 61 | self.language, 62 | self.code_path, 63 | self.task_manifest, 64 | self.tmp_path, 65 | self.task_path, 66 | self.bin_path 67 | ) 68 | } 69 | } 70 | 71 | impl std::fmt::Debug for Submission { 72 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 73 | write!( 74 | f, 75 | "Submission {} {} {} {:?} {:?} {:?} {:?} {:?}", 76 | self.task_id, 77 | self.submission_id, 78 | self.language, 79 | self.code_path, 80 | self.task_manifest, 81 | self.tmp_path, 82 | self.task_path, 83 | self.bin_path 84 | ) 85 | } 86 | } 87 | 88 | impl Submission { 89 | pub async fn try_from( 90 | task_id: impl ToString, 91 | submission_id: impl ToString, 92 | language: impl ToString, 93 | code: &[String], 94 | mut message_handler: T, 95 | ) -> GraderResult 96 | where 97 | T: Sink + std::marker::Unpin, 98 | { 99 | let task_id = task_id.to_string(); 100 | let submission_id = submission_id.to_string(); 101 | let language = language.to_string(); 102 | let tmp_path = PathBuf::from(get_env("TEMPORARY_PATH")).join(&submission_id); 103 | fs::remove_dir_all(&tmp_path).await.ok(); 104 | fs::create_dir(&tmp_path).await?; 105 | let extension = get_code_extension(&language); 106 | let task_path = get_base_path().join("tasks").join(&task_id); 107 | 108 | if !task_path.is_dir() { 109 | _ = message_handler 110 | .send(SubmissionMessage::Status(SubmissionStatus::TaskNotFound)) 111 | .await; 112 | return Err(GraderError::task_not_found()); 113 | } 114 | 115 | if task_path.join("compile_files").is_dir() { 116 | let mut entries = fs::read_dir(task_path.join("compile_files")).await?; 117 | while let Some(entry) = entries.next_entry().await? { 118 | let path = entry; 119 | fs::copy(&path.path(), tmp_path.join(&path.file_name())).await?; 120 | } 121 | } 122 | Ok(Submission { 123 | task_id, 124 | submission_id, 125 | language, 126 | code_path: code 127 | .iter() 128 | .enumerate() 129 | .map(|(idx, val)| { 130 | let code_path = 131 | tmp_path.join(format!("code_{}.{}", &idx.to_string(), &extension)); 132 | let mut file = std::fs::File::create(&code_path)?; 133 | file.write_all(val.as_bytes())?; 134 | 135 | Ok(code_path) 136 | }) 137 | .collect::>>()?, 138 | task_manifest: Manifest::from(task_path.join("manifest.yaml"))?, 139 | tmp_path, 140 | task_path, 141 | bin_path: PathBuf::new(), 142 | message_handler, 143 | }) 144 | } 145 | 146 | pub async fn compile(&mut self) -> GraderResult 147 | where 148 | T: Sink + std::marker::Unpin, 149 | { 150 | _ = self 151 | .message_handler 152 | .send(SubmissionMessage::Status(SubmissionStatus::Compiling)) 153 | .await; 154 | 155 | let compiler_path = get_base_path() 156 | .join("scripts") 157 | .join("compile_scripts") 158 | .join(&self.language); 159 | 160 | let mut args = vec![&self.tmp_path]; 161 | self.code_path.iter().for_each(|path| { 162 | args.push(path); 163 | }); 164 | 165 | let mut tmp_compile_files = vec![]; 166 | 167 | if let Some(compile_files) = &self.task_manifest.compile_files { 168 | for compile_file in compile_files 169 | .get(&self.language) 170 | .ok_or(GraderError::invalid_index())? 171 | { 172 | tmp_compile_files.push(self.tmp_path.join(compile_file)); 173 | } 174 | } 175 | 176 | tmp_compile_files.iter().for_each(|path| { 177 | args.push(path); 178 | }); 179 | 180 | log::debug!("compiler path: {compiler_path:?} args: {args:?}"); 181 | let compile_output = Command::new(compiler_path).args(args).output()?; 182 | log::debug!("compile output {compile_output:?}"); 183 | let compile_output_args = String::from_utf8(compile_output.stdout.clone())? 184 | .lines() 185 | .map(|s| s.to_string()) 186 | .collect::>(); 187 | 188 | let return_code: i32 = compile_output_args 189 | .get(0) 190 | .map_or(1, |s| s.parse::().unwrap_or(1)); 191 | 192 | match return_code { 193 | 0 => { 194 | _ = self 195 | .message_handler 196 | .send(SubmissionMessage::Status(SubmissionStatus::Compiled)) 197 | .await; 198 | } 199 | _ => { 200 | _ = self 201 | .message_handler 202 | .send(SubmissionMessage::Status( 203 | SubmissionStatus::CompilationError(String::from_utf8( 204 | compile_output.stdout, 205 | )?), 206 | )) 207 | .await; 208 | } 209 | } 210 | 211 | if return_code == 0 { 212 | self.bin_path = PathBuf::from( 213 | compile_output_args 214 | .get(1) 215 | .ok_or(GraderError::invalid_index())?, 216 | ); 217 | Ok(true) 218 | } else { 219 | Ok(false) 220 | } 221 | } 222 | 223 | pub async fn run_each( 224 | &mut self, 225 | checker: &Path, 226 | runner: &Path, 227 | index: u64, 228 | ) -> GraderResult 229 | where 230 | T: Sink + std::marker::Unpin, 231 | { 232 | _ = self 233 | .message_handler 234 | .send(SubmissionMessage::Status(SubmissionStatus::Running(index))) 235 | .await; 236 | let input_path = self 237 | .task_path 238 | .join("testcases") 239 | .join(format!("{}.in", index)); 240 | let output_path = self.tmp_path.join(format!("output_{}", index)); 241 | let sol_path = self 242 | .task_path 243 | .join("testcases") 244 | .join(format!("{}.sol", index)); 245 | 246 | let mut instance = instance! { 247 | time_limit: self.task_manifest.time_limit.ok_or(GraderError::invalid_value())?, 248 | memory_limit: self.task_manifest.memory_limit.ok_or(GraderError::invalid_value())? * 1000, 249 | bin_path: self.bin_path.clone(), 250 | input_path: input_path.clone(), 251 | output_path: output_path.clone(), 252 | runner_path: runner.to_path_buf(), 253 | box_id: self.submission_id.clone().parse::().unwrap() % 1000 254 | }; 255 | 256 | instance.init().await?; 257 | 258 | let instance_result = instance.run().await?; 259 | 260 | let mut run_result = RunResult::from( 261 | self.submission_id.to_owned(), 262 | index, 263 | instance_result.time_usage, 264 | instance_result.memory_usage, 265 | ); 266 | 267 | run_result.status = match instance_result.status { 268 | RunVerdict::VerdictOK => { 269 | let args = vec![&input_path, &output_path, &sol_path]; 270 | log::debug!("{input_path:?}, {output_path:?}, {sol_path:?}"); 271 | let checker_result = Command::new(checker).args(args).output()?; 272 | log::debug!("{checker_result:?}\n"); 273 | let checker_output = String::from_utf8(checker_result.stdout)? 274 | .trim_end_matches('\n') 275 | .lines() 276 | .map(|s| s.to_string()) 277 | .collect::>(); 278 | 279 | run_result.score = checker_output 280 | .get(1) 281 | .ok_or(GraderError::invalid_index())? 282 | .parse()?; 283 | run_result.message = checker_output 284 | .get(2) 285 | .map_or(String::new(), |v| v.to_owned()); 286 | 287 | checker_output 288 | .get(0) 289 | .ok_or(GraderError::invalid_index())? 290 | .as_str() 291 | .to_owned() 292 | } 293 | RunVerdict::VerdictTLE => String::from("Time Limit Exceeded"), 294 | RunVerdict::VerdictMLE => String::from("Memory Limit Exceeded"), 295 | RunVerdict::VerdictRE => String::from("Runtime Error"), 296 | RunVerdict::VerdictSG => String::from("Signal Error"), 297 | _ => String::from("Judge Error"), 298 | }; 299 | 300 | if run_result.message.is_empty() { 301 | run_result.message = get_message(&run_result.status); 302 | } 303 | 304 | _ = self 305 | .message_handler 306 | .send(SubmissionMessage::RunResult(run_result.clone())) 307 | .await; 308 | Ok(run_result) 309 | } 310 | 311 | pub async fn run(&mut self) -> GraderResult 312 | where 313 | T: Sink + std::marker::Unpin, 314 | { 315 | if self.bin_path == PathBuf::new() { 316 | return Ok(SubmissionResult { 317 | score: 0.0, 318 | full_score: 0.0, 319 | submission_id: self.submission_id.clone(), 320 | group_result: vec![], 321 | }); 322 | } 323 | 324 | let checker = 325 | self.task_manifest 326 | .checker 327 | .as_ref() 328 | .map_or(self.task_path.join("checker"), |file| { 329 | get_base_path() 330 | .join("scripts") 331 | .join("checker_scripts") 332 | .join(file) 333 | }); 334 | let grouper = 335 | self.task_manifest 336 | .grouper 337 | .as_ref() 338 | .map_or(self.task_path.join("grouper"), |file| { 339 | get_base_path() 340 | .join("scripts") 341 | .join("grouper_scripts") 342 | .join(file) 343 | }); 344 | let runner = get_base_path() 345 | .join("scripts") 346 | .join("runner_scripts") 347 | .join(&self.language); 348 | 349 | let mut last_test = 1; 350 | let mut total_score: f64 = 0.0; 351 | let mut total_full_score: f64 = 0.0; 352 | let mut group_results = Vec::new(); 353 | for (group_index, (full_score, tests)) in 354 | self.task_manifest.groups.clone().iter().enumerate() 355 | { 356 | total_full_score += full_score; 357 | 358 | let mut skip = false; 359 | let mut args = vec![full_score.to_string()]; 360 | 361 | let mut group_result = GroupResult::from( 362 | *full_score, 363 | self.submission_id.to_owned(), 364 | (group_index + 1) as u64, 365 | ); 366 | for index in last_test..(last_test + tests) { 367 | let run_result = if skip { 368 | RunResult::from(self.submission_id.to_owned(), index, 0.0, 0) 369 | } else { 370 | self.run_each(&checker, &runner, index).await? 371 | }; 372 | args.push(run_result.score.to_string()); 373 | skip = &run_result.status != "Correct" && &run_result.status != "Partially Correct"; 374 | 375 | group_result.run_result.push(run_result); 376 | } 377 | if !skip { 378 | let grouper_result = Command::new(&grouper).args(args).output()?; 379 | group_result.score = String::from_utf8(grouper_result.stdout)? 380 | .trim_end_matches('\n') 381 | .parse()?; 382 | 383 | total_score += group_result.score; 384 | } 385 | _ = self 386 | .message_handler 387 | .send(SubmissionMessage::GroupResult(group_result.clone())) 388 | .await; 389 | 390 | group_results.push(group_result); 391 | 392 | last_test += tests; 393 | } 394 | 395 | let submission_result = SubmissionResult { 396 | score: total_score, 397 | full_score: total_full_score, 398 | submission_id: self.submission_id.to_owned(), 399 | group_result: group_results, 400 | }; 401 | _ = self 402 | .message_handler 403 | .send(SubmissionMessage::Status(SubmissionStatus::Done( 404 | submission_result.clone(), 405 | ))) 406 | .await; 407 | Ok(submission_result) 408 | } 409 | } 410 | 411 | impl Drop for Submission { 412 | fn drop(&mut self) { 413 | std::fs::remove_dir_all(&self.tmp_path).ok(); 414 | } 415 | } 416 | -------------------------------------------------------------------------------- /grader/src/submission/result.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Default, Debug, PartialEq, Clone, Deserialize, Serialize)] 4 | pub struct RunResult { 5 | pub submission_id: String, 6 | pub test_index: u64, 7 | pub status: String, 8 | pub time_usage: f64, 9 | pub memory_usage: u64, 10 | pub score: f64, 11 | pub message: String, 12 | } 13 | 14 | impl RunResult { 15 | pub fn from(submission_id: String, index: u64, time_usage: f64, memory_usage: u64) -> Self { 16 | RunResult { 17 | submission_id, 18 | test_index: index, 19 | time_usage, 20 | memory_usage, 21 | ..Default::default() 22 | } 23 | } 24 | } 25 | 26 | #[derive(Default, Debug, Clone, Serialize, Deserialize)] 27 | pub struct GroupResult { 28 | pub score: f64, 29 | pub full_score: f64, 30 | pub submission_id: String, 31 | pub group_index: u64, 32 | pub run_result: Vec, 33 | } 34 | 35 | impl GroupResult { 36 | pub fn from(full_score: f64, submission_id: String, index: u64) -> Self { 37 | GroupResult { 38 | full_score, 39 | submission_id, 40 | group_index: index, 41 | ..Default::default() 42 | } 43 | } 44 | } 45 | 46 | #[derive(Default, Debug, Clone)] 47 | pub struct SubmissionResult { 48 | pub score: f64, 49 | pub full_score: f64, 50 | pub submission_id: String, 51 | pub group_result: Vec, 52 | } 53 | -------------------------------------------------------------------------------- /grader/src/submission/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | use crate::errors::GraderResult; 4 | use crate::utils::tests::get_example_dir; 5 | use dotenv::dotenv; 6 | use std::convert::Infallible; 7 | use tokio::fs; 8 | use tokio::test; 9 | 10 | struct MessageSink; 11 | 12 | const _: () = { 13 | use std::pin::Pin; 14 | use std::task::{Context, Poll}; 15 | 16 | impl futures::Sink for MessageSink { 17 | type Error = Infallible; 18 | 19 | fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { 20 | Poll::Ready(Ok(())) 21 | } 22 | fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { 23 | Poll::Ready(Ok(())) 24 | } 25 | fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { 26 | Poll::Ready(Ok(())) 27 | } 28 | fn start_send(self: Pin<&mut Self>, _: T) -> Result<(), Self::Error> { 29 | Ok(()) 30 | } 31 | } 32 | }; 33 | 34 | #[test] 35 | async fn should_complete_initialize_submission() { 36 | dotenv().ok(); 37 | 38 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b.cpp")) 39 | .await 40 | .unwrap(); 41 | 42 | let _submission = Submission::try_from("a_plus_b", "000000", "cpp", &[code], MessageSink).await; 43 | } 44 | 45 | #[test] 46 | async fn should_compile_cpp_successfully() -> GraderResult<()> { 47 | dotenv().ok(); 48 | 49 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b.cpp")) 50 | .await 51 | .unwrap(); 52 | 53 | let mut submission = 54 | Submission::try_from("a_plus_b", "000001", "cpp", &vec![code], MessageSink).await?; 55 | assert!(submission.compile().await?); 56 | 57 | Ok(()) 58 | } 59 | 60 | #[test] 61 | async fn should_compile_python_successfully() -> GraderResult<()> { 62 | dotenv().ok(); 63 | 64 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b.py")) 65 | .await 66 | .unwrap(); 67 | 68 | let mut submission = 69 | Submission::try_from("a_plus_b", "000002", "python", &vec![code], MessageSink).await?; 70 | assert!(submission.compile().await?); 71 | 72 | Ok(()) 73 | } 74 | 75 | #[test] 76 | async fn should_compile_rust_successfully() -> GraderResult<()> { 77 | dotenv().ok(); 78 | 79 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b.rs")) 80 | .await 81 | .unwrap(); 82 | 83 | let mut submission = 84 | Submission::try_from("a_plus_b", "000003", "rust", &vec![code], MessageSink).await?; 85 | assert!(submission.compile().await?); 86 | 87 | Ok(()) 88 | } 89 | 90 | #[test] 91 | async fn should_remove_tmp_dir_after_out_of_scope() -> GraderResult<()> { 92 | dotenv().ok(); 93 | 94 | let tmp_path; 95 | { 96 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b.cpp")) 97 | .await 98 | .unwrap(); 99 | 100 | let mut submission = 101 | Submission::try_from("a_plus_b", "000004", "cpp", &vec![code], MessageSink).await?; 102 | assert!(submission.compile().await?); 103 | tmp_path = submission.tmp_path.clone(); 104 | } 105 | 106 | assert!(!tmp_path.exists()); 107 | 108 | Ok(()) 109 | } 110 | 111 | #[test] 112 | async fn should_run_cpp_successfully() -> GraderResult<()> { 113 | dotenv().ok(); 114 | 115 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b.cpp")) 116 | .await 117 | .unwrap(); 118 | 119 | let mut submission = 120 | Submission::try_from("a_plus_b", "000005", "cpp", &vec![code], MessageSink).await?; 121 | assert!(submission.compile().await?); 122 | 123 | let _result = submission.run().await?; 124 | assert_eq!(_result.score, 100.0); 125 | 126 | Ok(()) 127 | } 128 | 129 | #[test] 130 | async fn should_run_cpp_tle_skipped() -> GraderResult<()> { 131 | dotenv().ok(); 132 | 133 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b_TLE.cpp")) 134 | .await 135 | .unwrap(); 136 | 137 | let mut submission = 138 | Submission::try_from("a_plus_b", "000006", "cpp", &vec![code], MessageSink).await?; 139 | assert!(submission.compile().await?); 140 | 141 | let _result = submission.run().await?; 142 | 143 | assert_eq!(_result.score, 0.0); 144 | 145 | assert_eq!(_result.group_result[0].score, 0.0); 146 | assert_eq!( 147 | _result.group_result[0].run_result[0].status, 148 | "Time Limit Exceeded" 149 | ); 150 | assert_eq!(_result.group_result[0].run_result[1].status, ""); 151 | 152 | assert_eq!(_result.group_result[1].score, 0.0); 153 | assert_eq!( 154 | _result.group_result[1].run_result[0].status, 155 | "Time Limit Exceeded" 156 | ); 157 | assert_eq!(_result.group_result[1].run_result[1].status, ""); 158 | 159 | Ok(()) 160 | } 161 | 162 | #[test] 163 | async fn should_run_cpp_mle_skipped() -> GraderResult<()> { 164 | dotenv().ok(); 165 | 166 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b_MLE.cpp")) 167 | .await 168 | .unwrap(); 169 | 170 | let mut submission = 171 | Submission::try_from("a_plus_b", "000007", "cpp", &vec![code], MessageSink).await?; 172 | assert!(submission.compile().await?); 173 | 174 | let _result = submission.run().await?; 175 | 176 | assert_eq!(_result.score, 0.0); 177 | 178 | assert_eq!(_result.group_result[0].score, 0.0); 179 | assert_eq!( 180 | _result.group_result[0].run_result[0].status, 181 | "Memory Limit Exceeded" 182 | ); 183 | assert_eq!(_result.group_result[0].run_result[1].status, ""); 184 | 185 | assert_eq!(_result.group_result[1].score, 0.0); 186 | assert_eq!( 187 | _result.group_result[1].run_result[0].status, 188 | "Memory Limit Exceeded" 189 | ); 190 | assert_eq!(_result.group_result[1].run_result[1].status, ""); 191 | 192 | Ok(()) 193 | } 194 | 195 | #[test] 196 | async fn should_run_cpp_re_skipped() -> GraderResult<()> { 197 | dotenv().ok(); 198 | 199 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b_RE.cpp")) 200 | .await 201 | .unwrap(); 202 | 203 | let mut submission = 204 | Submission::try_from("a_plus_b", "000008", "cpp", &vec![code], MessageSink).await?; 205 | assert!(submission.compile().await?); 206 | 207 | let _result = submission.run().await?; 208 | 209 | assert_eq!(_result.score, 0.0); 210 | 211 | assert_eq!(_result.group_result[0].score, 0.0); 212 | assert_eq!( 213 | _result.group_result[0].run_result[0].status, 214 | "Runtime Error" 215 | ); 216 | assert_eq!(_result.group_result[0].run_result[1].status, ""); 217 | 218 | assert_eq!(_result.group_result[1].score, 0.0); 219 | assert_eq!( 220 | _result.group_result[1].run_result[0].status, 221 | "Runtime Error" 222 | ); 223 | assert_eq!(_result.group_result[1].run_result[1].status, ""); 224 | 225 | Ok(()) 226 | } 227 | 228 | #[test] 229 | async fn should_run_cpp_sg_skipped() -> GraderResult<()> { 230 | dotenv().ok(); 231 | 232 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b_SG.cpp")) 233 | .await 234 | .unwrap(); 235 | 236 | let mut submission = 237 | Submission::try_from("a_plus_b", "000009", "cpp", &vec![code], MessageSink).await?; 238 | assert!(submission.compile().await?); 239 | 240 | let _result = submission.run().await?; 241 | 242 | assert_eq!(_result.score, 0.0); 243 | 244 | assert_eq!(_result.group_result[0].score, 0.0); 245 | assert_eq!(_result.group_result[0].run_result[0].status, "Signal Error"); 246 | assert_eq!(_result.group_result[0].run_result[1].status, ""); 247 | 248 | assert_eq!(_result.group_result[1].score, 0.0); 249 | assert_eq!(_result.group_result[1].run_result[0].status, "Signal Error"); 250 | assert_eq!(_result.group_result[1].run_result[1].status, ""); 251 | 252 | Ok(()) 253 | } 254 | 255 | #[test] 256 | async fn should_run_cpp_with_header_successfully() -> GraderResult<()> { 257 | dotenv().ok(); 258 | 259 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b_h.cpp")) 260 | .await 261 | .unwrap(); 262 | 263 | let mut submission = 264 | Submission::try_from("a_plus_b_h", "000010", "cpp", &vec![code], MessageSink).await?; 265 | assert!(submission.compile().await?); 266 | 267 | let _result = submission.run().await?; 268 | assert_eq!(_result.score, 100.0); 269 | 270 | Ok(()) 271 | } 272 | 273 | #[test] 274 | async fn should_run_python_successfully() -> GraderResult<()> { 275 | dotenv().ok(); 276 | 277 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b.py")) 278 | .await 279 | .unwrap(); 280 | 281 | let mut submission = 282 | Submission::try_from("a_plus_b", "000011", "python", &vec![code], MessageSink).await?; 283 | assert!(submission.compile().await?); 284 | 285 | let _result = submission.run().await?; 286 | assert_eq!(_result.score, 100.0); 287 | 288 | Ok(()) 289 | } 290 | 291 | #[test] 292 | async fn should_run_python_tle_skipped() -> GraderResult<()> { 293 | dotenv().ok(); 294 | 295 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b_TLE.py")) 296 | .await 297 | .unwrap(); 298 | 299 | let mut submission = 300 | Submission::try_from("a_plus_b", "000012", "python", &vec![code], MessageSink).await?; 301 | assert!(submission.compile().await?); 302 | 303 | let _result = submission.run().await?; 304 | 305 | assert_eq!(_result.score, 0.0); 306 | 307 | assert_eq!(_result.group_result[0].score, 0.0); 308 | assert_eq!( 309 | _result.group_result[0].run_result[0].status, 310 | "Time Limit Exceeded" 311 | ); 312 | assert_eq!(_result.group_result[0].run_result[1].status, ""); 313 | 314 | assert_eq!(_result.group_result[1].score, 0.0); 315 | assert_eq!( 316 | _result.group_result[1].run_result[0].status, 317 | "Time Limit Exceeded" 318 | ); 319 | assert_eq!(_result.group_result[1].run_result[1].status, ""); 320 | 321 | Ok(()) 322 | } 323 | 324 | #[test] 325 | async fn should_run_python_mle_skipped() -> GraderResult<()> { 326 | dotenv().ok(); 327 | 328 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b_MLE.py")) 329 | .await 330 | .unwrap(); 331 | 332 | let mut submission = 333 | Submission::try_from("a_plus_b", "000013", "python", &vec![code], MessageSink).await?; 334 | assert!(submission.compile().await?); 335 | 336 | let _result = submission.run().await?; 337 | 338 | assert_eq!(_result.score, 0.0); 339 | 340 | assert_eq!(_result.group_result[0].score, 0.0); 341 | assert_eq!( 342 | _result.group_result[0].run_result[0].status, 343 | "Memory Limit Exceeded" 344 | ); 345 | assert_eq!(_result.group_result[0].run_result[1].status, ""); 346 | 347 | assert_eq!(_result.group_result[1].score, 0.0); 348 | assert_eq!( 349 | _result.group_result[1].run_result[0].status, 350 | "Memory Limit Exceeded" 351 | ); 352 | assert_eq!(_result.group_result[1].run_result[1].status, ""); 353 | 354 | Ok(()) 355 | } 356 | 357 | #[test] 358 | async fn should_run_python_re_skipped() -> GraderResult<()> { 359 | dotenv().ok(); 360 | 361 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b_RE.py")) 362 | .await 363 | .unwrap(); 364 | 365 | let mut submission = 366 | Submission::try_from("a_plus_b", "000014", "python", &vec![code], MessageSink).await?; 367 | assert!(submission.compile().await?); 368 | 369 | let _result = submission.run().await?; 370 | 371 | assert_eq!(_result.score, 0.0); 372 | 373 | assert_eq!(_result.group_result[0].score, 0.0); 374 | assert_eq!( 375 | _result.group_result[0].run_result[0].status, 376 | "Runtime Error" 377 | ); 378 | assert_eq!(_result.group_result[0].run_result[1].status, ""); 379 | 380 | assert_eq!(_result.group_result[1].score, 0.0); 381 | assert_eq!( 382 | _result.group_result[1].run_result[0].status, 383 | "Runtime Error" 384 | ); 385 | assert_eq!(_result.group_result[1].run_result[1].status, ""); 386 | 387 | Ok(()) 388 | } 389 | 390 | #[test] 391 | async fn should_run_rust_successfully() -> GraderResult<()> { 392 | dotenv().ok(); 393 | 394 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b.rs")) 395 | .await 396 | .unwrap(); 397 | 398 | let mut submission = 399 | Submission::try_from("a_plus_b", "000015", "rust", &vec![code], MessageSink).await?; 400 | assert!(submission.compile().await?); 401 | 402 | let _result = submission.run().await?; 403 | assert_eq!(_result.score, 100.0); 404 | 405 | Ok(()) 406 | } 407 | 408 | #[test] 409 | async fn should_run_rust_tle_skipped() -> GraderResult<()> { 410 | dotenv().ok(); 411 | 412 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b_TLE.rs")) 413 | .await 414 | .unwrap(); 415 | 416 | let mut submission = 417 | Submission::try_from("a_plus_b", "000016", "rust", &vec![code], MessageSink).await?; 418 | assert!(submission.compile().await?); 419 | 420 | let _result = submission.run().await?; 421 | 422 | assert_eq!(_result.score, 0.0); 423 | 424 | assert_eq!(_result.group_result[0].score, 0.0); 425 | assert_eq!( 426 | _result.group_result[0].run_result[0].status, 427 | "Time Limit Exceeded" 428 | ); 429 | assert_eq!(_result.group_result[0].run_result[1].status, ""); 430 | 431 | assert_eq!(_result.group_result[1].score, 0.0); 432 | assert_eq!( 433 | _result.group_result[1].run_result[0].status, 434 | "Time Limit Exceeded" 435 | ); 436 | assert_eq!(_result.group_result[1].run_result[1].status, ""); 437 | 438 | Ok(()) 439 | } 440 | 441 | #[test] 442 | async fn should_run_rust_mle_skipped() -> GraderResult<()> { 443 | dotenv().ok(); 444 | 445 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b_MLE.rs")) 446 | .await 447 | .unwrap(); 448 | 449 | let mut submission = 450 | Submission::try_from("a_plus_b", "000017", "rust", &vec![code], MessageSink).await?; 451 | assert!(submission.compile().await?); 452 | 453 | let _result = submission.run().await?; 454 | 455 | assert_eq!(_result.score, 0.0); 456 | 457 | assert_eq!(_result.group_result[0].score, 0.0); 458 | assert_eq!( 459 | _result.group_result[0].run_result[0].status, 460 | "Memory Limit Exceeded" 461 | ); 462 | assert_eq!(_result.group_result[0].run_result[1].status, ""); 463 | 464 | assert_eq!(_result.group_result[1].score, 0.0); 465 | assert_eq!( 466 | _result.group_result[1].run_result[0].status, 467 | "Memory Limit Exceeded" 468 | ); 469 | assert_eq!(_result.group_result[1].run_result[1].status, ""); 470 | 471 | Ok(()) 472 | } 473 | 474 | #[test] 475 | async fn should_run_rust_re_skipped() -> GraderResult<()> { 476 | dotenv().ok(); 477 | 478 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b_RE.rs")) 479 | .await 480 | .unwrap(); 481 | 482 | let mut submission = 483 | Submission::try_from("a_plus_b", "000018", "rust", &vec![code], MessageSink).await?; 484 | assert!(submission.compile().await?); 485 | 486 | let _result = submission.run().await?; 487 | 488 | assert_eq!(_result.score, 0.0); 489 | 490 | assert_eq!(_result.group_result[0].score, 0.0); 491 | assert_eq!( 492 | _result.group_result[0].run_result[0].status, 493 | "Runtime Error" 494 | ); 495 | assert_eq!(_result.group_result[0].run_result[1].status, ""); 496 | 497 | assert_eq!(_result.group_result[1].score, 0.0); 498 | assert_eq!( 499 | _result.group_result[1].run_result[0].status, 500 | "Runtime Error" 501 | ); 502 | assert_eq!(_result.group_result[1].run_result[1].status, ""); 503 | 504 | Ok(()) 505 | } 506 | 507 | #[test] 508 | async fn should_run_rust_sg_skipped() -> GraderResult<()> { 509 | dotenv().ok(); 510 | 511 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b_SG.rs")) 512 | .await 513 | .unwrap(); 514 | 515 | let mut submission = 516 | Submission::try_from("a_plus_b", "000019", "rust", &vec![code], MessageSink).await?; 517 | assert!(submission.compile().await?); 518 | 519 | let _result = submission.run().await?; 520 | 521 | assert_eq!(_result.score, 0.0); 522 | 523 | assert_eq!(_result.group_result[0].score, 0.0); 524 | assert_eq!(_result.group_result[0].run_result[0].status, "Signal Error"); 525 | assert_eq!(_result.group_result[0].run_result[1].status, ""); 526 | 527 | assert_eq!(_result.group_result[1].score, 0.0); 528 | assert_eq!(_result.group_result[1].run_result[0].status, "Signal Error"); 529 | assert_eq!(_result.group_result[1].run_result[1].status, ""); 530 | 531 | Ok(()) 532 | } 533 | 534 | #[test] 535 | async fn should_compile_go_successfully() -> GraderResult<()> { 536 | dotenv().ok(); 537 | 538 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b.go")) 539 | .await 540 | .unwrap(); 541 | 542 | let mut submission = 543 | Submission::try_from("a_plus_b", "000020", "go", &vec![code], MessageSink).await?; 544 | assert!(submission.compile().await?); 545 | 546 | Ok(()) 547 | } 548 | 549 | #[test] 550 | async fn should_run_go_successfully() -> GraderResult<()> { 551 | dotenv().ok(); 552 | 553 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b.go")) 554 | .await 555 | .unwrap(); 556 | 557 | let mut submission = 558 | Submission::try_from("a_plus_b", "000021", "go", &vec![code], MessageSink).await?; 559 | assert!(submission.compile().await?); 560 | 561 | let _result = submission.run().await?; 562 | assert_eq!(_result.score, 100.0); 563 | 564 | Ok(()) 565 | } 566 | 567 | #[test] 568 | async fn should_compile_java_successfully() -> GraderResult<()> { 569 | dotenv().ok(); 570 | 571 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b.java")) 572 | .await 573 | .unwrap(); 574 | 575 | let mut submission = 576 | Submission::try_from("a_plus_b", "000022", "java", &vec![code], MessageSink).await?; 577 | assert!(submission.compile().await?); 578 | 579 | Ok(()) 580 | } 581 | 582 | #[test] 583 | async fn should_run_java_successfully() -> GraderResult<()> { 584 | dotenv().ok(); 585 | 586 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b.java")) 587 | .await 588 | .unwrap(); 589 | 590 | let mut submission = 591 | Submission::try_from("a_plus_b", "000023", "java", &vec![code], MessageSink).await?; 592 | assert!(submission.compile().await?); 593 | 594 | let _result = submission.run().await?; 595 | assert_eq!(_result.score, 100.0); 596 | 597 | Ok(()) 598 | } 599 | 600 | #[test] 601 | async fn should_handle_messaging() -> GraderResult<()> { 602 | use futures::StreamExt; 603 | 604 | dotenv().ok(); 605 | 606 | let (tx, rx) = futures::channel::mpsc::unbounded(); 607 | 608 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b.cpp")) 609 | .await 610 | .unwrap(); 611 | { 612 | let mut submission = 613 | Submission::try_from("a_plus_b", "000024", "cpp", &vec![code], tx).await?; 614 | assert!(submission.compile().await?); 615 | 616 | let _result = submission.run().await?; 617 | assert_eq!(_result.score, 100.0); 618 | } 619 | 620 | let msg: Vec<_> = rx.collect().await; 621 | 622 | assert!(matches!( 623 | msg[0], 624 | SubmissionMessage::Status(SubmissionStatus::Compiling) 625 | )); 626 | assert!(matches!( 627 | msg[1], 628 | SubmissionMessage::Status(SubmissionStatus::Compiled) 629 | )); 630 | 631 | Ok(()) 632 | } 633 | 634 | #[test] 635 | async fn should_compile_error_cpp() -> GraderResult<()> { 636 | dotenv().ok(); 637 | 638 | let code = "hello(".to_string(); 639 | 640 | let mut submission = 641 | Submission::try_from("a_plus_b", "000025", "cpp", &vec![code], MessageSink).await?; 642 | let result = submission.compile().await?; 643 | 644 | assert!(result == false); 645 | 646 | Ok(()) 647 | } 648 | 649 | #[test] 650 | async fn should_compile_error_python() -> GraderResult<()> { 651 | dotenv().ok(); 652 | 653 | let code = "hello(".to_string(); 654 | 655 | let mut submission = 656 | Submission::try_from("a_plus_b", "000026", "python", &vec![code], MessageSink).await?; 657 | let result = submission.compile().await?; 658 | 659 | assert!(result == false); 660 | 661 | Ok(()) 662 | } 663 | 664 | #[test] 665 | async fn should_error_when_task_not_found() -> GraderResult<()> { 666 | dotenv().ok(); 667 | 668 | let code = fs::read_to_string(get_example_dir().join("etc").join("a_plus_b.cpp")) 669 | .await 670 | .unwrap(); 671 | 672 | let submission = Submission::try_from("hello", "000027", "cpp", &vec![code], MessageSink).await; 673 | 674 | let error_msg = submission.unwrap_err(); 675 | 676 | assert_eq!(error_msg, GraderError::TaskNotFound {}); 677 | 678 | Ok(()) 679 | } 680 | -------------------------------------------------------------------------------- /grader/src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::s; 2 | use std::{env, fs, path::PathBuf}; 3 | use yaml_rust::{Yaml, YamlLoader}; 4 | 5 | use log::info; 6 | 7 | pub fn get_env(name: &'static str) -> String { 8 | env::var(name).unwrap() 9 | } 10 | 11 | pub fn get_base_path() -> PathBuf { 12 | PathBuf::from(env::var("BASE_PATH").unwrap()) 13 | } 14 | 15 | pub fn load_yaml(path: PathBuf) -> Yaml { 16 | info!("finding yaml at path: {path:?}"); 17 | let file = fs::read_to_string(path).expect("Unable to read yaml file"); 18 | YamlLoader::load_from_str(&file) 19 | .unwrap() 20 | .into_iter() 21 | .next() 22 | .unwrap() 23 | } 24 | 25 | fn yaml_unwrap_hash(yaml: Yaml, arg: &str) -> Option { 26 | yaml.into_hash().unwrap().remove(&Yaml::String(s!(arg))) 27 | } 28 | 29 | pub fn get_code_extension(language: &str) -> String { 30 | let config = load_yaml(get_base_path().join("scripts").join("config.yaml")); 31 | 32 | for lang in yaml_unwrap_hash(config, "language") 33 | .unwrap() 34 | .into_vec() 35 | .unwrap() 36 | { 37 | if Some(language) == lang["id"].as_str() { 38 | return yaml_unwrap_hash(lang, "extension") 39 | .unwrap() 40 | .into_string() 41 | .unwrap(); 42 | } 43 | } 44 | 45 | String::new() 46 | } 47 | 48 | pub fn get_message(status: &str) -> String { 49 | let config = load_yaml(get_base_path().join("scripts").join("config.yaml")); 50 | yaml_unwrap_hash(yaml_unwrap_hash(config, "message").unwrap(), status) 51 | .map_or(String::new(), |value| value.into_string().unwrap()) 52 | } 53 | 54 | #[cfg(test)] 55 | pub mod tests { 56 | use crate::utils::get_env; 57 | use std::{env, fs, path::PathBuf, process::Command}; 58 | 59 | pub struct TempDir(pub PathBuf); 60 | 61 | impl Drop for TempDir { 62 | fn drop(&mut self) { 63 | fs::remove_dir_all(&self.0).expect("Unable to remove tmp directory"); 64 | } 65 | } 66 | 67 | impl TempDir { 68 | pub fn new(tmp_name: &'static str) -> Self { 69 | let tmp_path = PathBuf::from(get_env("TEMPORARY_PATH")).join(tmp_name); 70 | fs::create_dir(&tmp_path).expect("Unable to create tmp directory"); 71 | Self(tmp_path) 72 | } 73 | } 74 | 75 | pub fn get_example_dir() -> PathBuf { 76 | PathBuf::from(env::current_dir().unwrap()) 77 | .parent() 78 | .unwrap() 79 | .join("example") 80 | } 81 | 82 | pub fn get_tmp_path() -> PathBuf { 83 | PathBuf::from(get_env("TEMPORARY_PATH")) 84 | } 85 | 86 | pub fn compile_cpp(tmp_dir: &PathBuf, prog_file: &PathBuf) { 87 | Command::new( 88 | &get_example_dir() 89 | .join("scripts") 90 | .join("compile_scripts") 91 | .join("cpp"), 92 | ) 93 | .arg(&tmp_dir) 94 | .arg(&prog_file) 95 | .output() 96 | .expect("Unable to compile file"); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "interface" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | tokio = { version = "1", features = ["full"] } 10 | tokio-postgres = {version = "0.7.6", features = ["with-serde_json-1"]} 11 | grader = { path = "../grader" } 12 | postgres-openssl = "0.5.0" 13 | openssl = "0.10.41" 14 | serde_json = "1.0.82" 15 | futures = "0.3" 16 | futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] } 17 | brotli = "3.3.4" 18 | thiserror = "1.0.38" 19 | log = "0.4.17" 20 | pretty_env_logger = "0.4.0" 21 | lapin = "2.2.1" 22 | config = "0.13.3" 23 | serde = { version = "1.0.166", features = ["derive"] } 24 | -------------------------------------------------------------------------------- /interface/src/cfg.rs: -------------------------------------------------------------------------------- 1 | use config::Environment; 2 | use serde::Deserialize; 3 | 4 | #[derive(Debug, Clone, Deserialize)] 5 | pub struct AppConfig { 6 | pub database_config: DatabaseConfig, 7 | pub rmq_config: RabbitMqConfig, 8 | } 9 | 10 | #[derive(Debug, Clone, Deserialize)] 11 | pub struct DatabaseConfig { 12 | pub host: String, 13 | pub port: i32, 14 | pub username: String, 15 | pub password: String, 16 | pub name: String, 17 | } 18 | 19 | #[derive(Debug, Clone, Deserialize)] 20 | pub struct RabbitMqConfig { 21 | pub host: String, 22 | pub port: i32, 23 | pub username: String, 24 | pub password: String, 25 | pub vhost: String, 26 | pub env: String, 27 | } 28 | 29 | pub fn read_config() -> Result> { 30 | let database_config = config::Config::builder() 31 | .add_source(Environment::with_prefix("DATABASE").prefix_separator("_")) 32 | .build()? 33 | .try_deserialize()?; 34 | 35 | let rmq_config = config::Config::builder() 36 | .add_source(Environment::with_prefix("RABBITMQ").prefix_separator("_")) 37 | .build()? 38 | .try_deserialize()?; 39 | 40 | Ok(AppConfig { 41 | database_config, 42 | rmq_config, 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /interface/src/connection.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use grader::submission::result::GroupResult; 4 | use lapin::{options::BasicPublishOptions, BasicProperties, Channel}; 5 | use openssl::ssl::{SslConnector, SslMethod}; 6 | use postgres_openssl::{MakeTlsConnector, TlsStream}; 7 | use tokio::sync::Mutex; 8 | use tokio_postgres::{Client, Connection, Socket}; 9 | 10 | use crate::{cfg::{RabbitMqConfig, DatabaseConfig}, error::Error, runner::JudgeState}; 11 | use log::*; 12 | 13 | const EPS: f64 = 1e-6; 14 | 15 | #[derive(Clone)] 16 | pub struct SharedClient { 17 | pub db_client: Arc, 18 | pub rmq_channel: Arc, 19 | update_routing_key: String, 20 | } 21 | 22 | impl SharedClient { 23 | pub fn new( 24 | db_client: Arc, 25 | rmq_channel: Arc, 26 | rmq_config: &RabbitMqConfig, 27 | ) -> Self { 28 | let update_routing_key = format!("submission.update.{}", rmq_config.env); 29 | 30 | Self { 31 | db_client, 32 | rmq_channel, 33 | update_routing_key, 34 | } 35 | } 36 | 37 | pub async fn update_result( 38 | &self, 39 | submission_id: &str, 40 | state: &Mutex, 41 | group: GroupResult, 42 | ) -> Result<(), Error> { 43 | debug!("received new group result for {submission_id}"); 44 | let new_score = group.score; 45 | let new_time = group 46 | .run_result 47 | .iter() 48 | .map(|r| (r.time_usage * 1000.0) as i32) 49 | .max() 50 | .unwrap_or(0); 51 | let new_memory = group 52 | .run_result 53 | .iter() 54 | .map(|r| r.memory_usage) 55 | .max() 56 | .unwrap_or(0) as i32; 57 | 58 | let mut lock = state.lock().await; 59 | 60 | lock.score += new_score; 61 | lock.time = std::cmp::max(lock.time, new_time); 62 | lock.memory = std::cmp::max(lock.memory, new_memory); 63 | lock.result.push(group); 64 | 65 | let score = lock.score + EPS; 66 | let time = lock.time; 67 | let memory = lock.memory; 68 | 69 | let data = serde_json::to_value(&lock.result).unwrap(); 70 | drop(lock); 71 | 72 | debug!("update {submission_id} to (score: {score}, time: {time}, memory: {memory})"); 73 | let row = self 74 | .db_client 75 | .query_one( 76 | "UPDATE submission SET \ 77 | groups = $1, score = $2, time = $3, \ 78 | memory = $4 WHERE id = $5 \ 79 | RETURNING id, groups, status, score", 80 | &[ 81 | &data, 82 | &(score as i32), 83 | &time, 84 | &memory, 85 | &submission_id.parse::().unwrap(), 86 | ], 87 | ) 88 | .await?; 89 | 90 | let id: i32 = row.get(0); 91 | let groups: serde_json::Value = row.get(1); 92 | let status: String = row.get(2); 93 | let score: i32 = row.get(3); 94 | 95 | let payload = serde_json::json!({ 96 | "id": id, 97 | "groups": groups, 98 | "status": status, 99 | "score": score, 100 | }) 101 | .to_string(); 102 | 103 | let payload = payload.as_bytes(); 104 | 105 | if let Err(e) = self 106 | .rmq_channel 107 | .basic_publish( 108 | "", 109 | &self.update_routing_key, 110 | BasicPublishOptions::default(), 111 | payload, 112 | BasicProperties::default(), 113 | ) 114 | .await 115 | { 116 | log::error!("Unable to publish message: {e}"); 117 | } 118 | 119 | Ok(()) 120 | } 121 | 122 | pub async fn update_status(&self, submission_id: &str, msg: String) -> Result<(), Error> { 123 | debug!("change {submission_id}'s status to {msg}"); 124 | let row = self.db_client 125 | .query_one( 126 | "UPDATE submission SET status = $1 WHERE id = $2 RETURNING id, groups, status, score", 127 | &[&msg, &submission_id.parse::().unwrap()], 128 | ) 129 | .await?; 130 | 131 | let id: i32 = row.get(0); 132 | let groups: serde_json::Value = row.get(1); 133 | let status: String = row.get(2); 134 | let score: i32 = row.get(3); 135 | 136 | let payload = serde_json::json!({ 137 | "id": id, 138 | "groups": groups, 139 | "status": status, 140 | "score": score, 141 | }) 142 | .to_string(); 143 | 144 | let payload = payload.as_bytes(); 145 | 146 | if let Err(e) = self 147 | .rmq_channel 148 | .basic_publish( 149 | "", 150 | &self.update_routing_key, 151 | BasicPublishOptions::default(), 152 | payload, 153 | BasicProperties::default(), 154 | ) 155 | .await 156 | { 157 | log::error!("Unable to publish message: {e}"); 158 | } 159 | 160 | Ok(()) 161 | } 162 | } 163 | 164 | pub async fn connect_db(db_config: &DatabaseConfig) -> (Client, Connection>) { 165 | let addr = format!( 166 | "postgres://{username}:{password}@{host}:{port}/{dbname}", 167 | username = db_config.username, 168 | password = db_config.password, 169 | host = db_config.host, 170 | port = db_config.port, 171 | dbname = db_config.name, 172 | ); 173 | 174 | let builder = SslConnector::builder(SslMethod::tls()).unwrap(); 175 | let mut connector = MakeTlsConnector::new(builder.build()); 176 | connector.set_callback(|config, _| { 177 | config.set_verify_hostname(false); 178 | Ok(()) 179 | }); 180 | 181 | let (client, connection) = tokio_postgres::connect(&addr, connector).await.unwrap(); 182 | 183 | (client, connection) 184 | } 185 | -------------------------------------------------------------------------------- /interface/src/constants.rs: -------------------------------------------------------------------------------- 1 | use grader::submission::SubmissionStatus; 2 | 3 | pub static PULL_MSG: &str = "In Queue"; 4 | pub static ERROR_MSG: &str = "Judge Error"; 5 | 6 | pub fn parse_submission_status(status: SubmissionStatus) -> String { 7 | match status { 8 | SubmissionStatus::Compiling => "Compiling".to_string(), 9 | SubmissionStatus::Compiled => "Compiled".to_string(), 10 | SubmissionStatus::CompilationError(_) => "Compilation Error".to_string(), 11 | SubmissionStatus::Running(idx) => format!("Running on test #{}", idx), 12 | SubmissionStatus::Done(_) => "Completed".to_string(), 13 | _ => ERROR_MSG.to_string(), 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /interface/src/error.rs: -------------------------------------------------------------------------------- 1 | use grader::errors::GraderError; 2 | 3 | #[derive(Debug, thiserror::Error)] 4 | pub enum Error { 5 | #[error("submission not found")] 6 | SubmissionNotFound, 7 | #[error("submission is already judged")] 8 | AlreadyJudge, 9 | #[error("db error: {0}")] 10 | DbError(#[from] tokio_postgres::Error), 11 | #[error("decompress error: {0}")] 12 | DecompressError(#[from] std::io::Error), 13 | #[error("from utf-8 error: {0}")] 14 | FromUtf8Error(#[from] std::string::FromUtf8Error), 15 | #[error("json parse error: {0}")] 16 | JsonParseError(#[from] serde_json::Error), 17 | #[error("expected int, found {0}")] 18 | InvalidSubmissionId(String), 19 | #[error("invalid code, expectd array")] 20 | InvalidCode, 21 | #[error("grader error: {0}")] 22 | GraderError(#[from] GraderError), 23 | } 24 | -------------------------------------------------------------------------------- /interface/src/main.rs: -------------------------------------------------------------------------------- 1 | use cfg::DatabaseConfig; 2 | use futures::{Sink, Stream, StreamExt}; 3 | use serde_json::Value; 4 | use std::{io::Cursor, sync::Arc}; 5 | use tokio::task::JoinHandle; 6 | 7 | mod cfg; 8 | mod connection; 9 | mod constants; 10 | mod error; 11 | mod rmq; 12 | mod runner; 13 | 14 | use connection::SharedClient; 15 | use error::Error; 16 | 17 | type SubmissionId = String; 18 | 19 | async fn pull_and_judge(id: SubmissionId, client: SharedClient) -> Result<(), Error> { 20 | log::debug!("start judging {id}"); 21 | 22 | let lookup_id = id; 23 | 24 | let lookup_id_as_query_args = match lookup_id.parse::() { 25 | Ok(x) => x, 26 | Err(_) => return Err(Error::InvalidSubmissionId(lookup_id)), 27 | }; 28 | 29 | let rows = client 30 | .db_client 31 | .query( 32 | "SELECT task_id, language, \ 33 | code, status FROM submission WHERE id = $1", 34 | &[&lookup_id_as_query_args], 35 | ) 36 | .await?; 37 | 38 | if rows.is_empty() { 39 | return Err(Error::SubmissionNotFound); 40 | } 41 | 42 | let task_id: String = rows[0].get(0); 43 | let language: String = rows[0].get(1); 44 | let code: Vec = rows[0].get(2); 45 | let status: String = rows[0].get(3); 46 | 47 | if status != constants::PULL_MSG { 48 | return Err(Error::AlreadyJudge); 49 | } 50 | 51 | let mut cursor = Cursor::new(Vec::new()); 52 | brotli::BrotliDecompress(&mut Cursor::new(code), &mut cursor)?; 53 | let code = String::from_utf8(cursor.into_inner())?; 54 | let code: Value = serde_json::from_str(&code)?; 55 | let code = code 56 | .as_array() 57 | .ok_or(Error::InvalidCode)? 58 | .iter() 59 | .map(|x| x.as_str().ok_or(Error::InvalidCode).map(|x| x.to_string())) 60 | .collect::, Error>>()?; 61 | 62 | let val: i32 = 0; 63 | let empty_data = serde_json::to_value(Vec::new() as Vec)?; 64 | client 65 | .db_client 66 | .execute( 67 | "UPDATE submission SET \ 68 | groups = $1, score = $2, time = $3, \ 69 | memory = $4, status = $5 WHERE id = $6", 70 | &[ 71 | &empty_data, 72 | &val, 73 | &val, 74 | &val, 75 | &"Pending", 76 | &lookup_id_as_query_args, 77 | ], 78 | ) 79 | .await?; 80 | 81 | log::debug!("start judging submission {lookup_id}"); 82 | let result = runner::judge(task_id, &lookup_id, language, &code, client.clone()).await; 83 | match result { 84 | Ok(_) => Ok(()), 85 | Err(e) => { 86 | if (client 87 | .update_status(&lookup_id, constants::ERROR_MSG.to_string()) 88 | .await) 89 | .is_err() 90 | { 91 | log::warn!("failed to update status to server"); 92 | } 93 | Err(Error::GraderError(e)) 94 | } 95 | } 96 | } 97 | 98 | #[tokio::main] 99 | async fn main() { 100 | pretty_env_logger::init(); 101 | 102 | let config = match cfg::read_config() { 103 | Ok(x) => x, 104 | Err(e) => { 105 | log::error!("Unable to read config file: {e}"); 106 | return; 107 | } 108 | }; 109 | 110 | log::info!("starting..."); 111 | 112 | let (tx, rx) = futures::channel::mpsc::unbounded::(); 113 | 114 | let (client, connection) = connection::connect_db(&config.database_config).await; 115 | 116 | let client = Arc::new(client); 117 | let rmq_channel = Arc::new( 118 | rmq::get_channel(&config.rmq_config) 119 | .await 120 | .expect("Unable to create rabbitmq channel"), 121 | ); 122 | 123 | let shared_client = SharedClient::new(client, rmq_channel, &config.rmq_config); 124 | 125 | let db_connection_handler = tokio::spawn(async move { 126 | if let Err(e) = connection.await { 127 | log::error!("connection error: {}", e); 128 | } 129 | }); 130 | runner::clear_in_queue(shared_client.clone(), tx.clone()).await; 131 | 132 | let db_notification_handler = handle_db_notification(&config.database_config, tx.clone()).await; 133 | log::info!("start listening for database notification"); 134 | 135 | let submission_handler = handle_message(shared_client.clone(), rx); 136 | log::info!("start listening for submission through channel"); 137 | 138 | tokio::select! { 139 | _ = submission_handler => { 140 | log::warn!("submission handler died, exiting..."); 141 | std::process::exit(1); 142 | }, 143 | _ = db_notification_handler => { 144 | log::warn!("db notification handler died, exiting..."); 145 | std::process::exit(1); 146 | }, 147 | _ = db_connection_handler => { 148 | log::warn!("db connection handler died, exiting..."); 149 | std::process::exit(1); 150 | }, 151 | }; 152 | } 153 | 154 | async fn handle_message(client: SharedClient, mut reader: T) 155 | where 156 | T: Stream + std::marker::Unpin, 157 | { 158 | while let Some(id) = reader.next().await { 159 | let client = client.clone(); 160 | tokio::spawn(async move { 161 | if let Err(e) = pull_and_judge(id.clone(), client).await { 162 | log::warn!("failed to judge submission '{id}'\nreason: {e:?}"); 163 | } 164 | }); 165 | } 166 | } 167 | 168 | async fn handle_db_notification(db_config: &DatabaseConfig, tx: T) -> JoinHandle<()> 169 | where 170 | T: Sink + Send + Sync + 'static, 171 | >::Error: std::fmt::Debug + Send + Sync + 'static, 172 | { 173 | let (listen_client, listen_connection) = connection::connect_db(db_config).await; 174 | let listen_client = Arc::new(listen_client); 175 | let listen = runner::listen_new_submission(listen_client, listen_connection, tx); 176 | tokio::spawn(listen) 177 | } 178 | -------------------------------------------------------------------------------- /interface/src/rmq.rs: -------------------------------------------------------------------------------- 1 | use lapin::{Channel, ConnectionProperties}; 2 | 3 | use crate::cfg::RabbitMqConfig; 4 | 5 | pub async fn get_channel( 6 | rmq_config: &RabbitMqConfig, 7 | ) -> Result> { 8 | let addr = if rmq_config.vhost == "/" { 9 | format!( 10 | "amqp://{username}:{password}@{host}:{port}", 11 | username = rmq_config.username, 12 | password = rmq_config.password, 13 | host = rmq_config.host, 14 | port = rmq_config.port 15 | ) 16 | } else { 17 | format!( 18 | "amqp://{username}:{password}@{host}:{port}/{vhost}", 19 | username = rmq_config.username, 20 | password = rmq_config.password, 21 | host = rmq_config.host, 22 | port = rmq_config.port, 23 | vhost = rmq_config.vhost 24 | ) 25 | }; 26 | 27 | let conn = lapin::Connection::connect(&addr, ConnectionProperties::default()).await?; 28 | 29 | let channel = conn.create_channel().await?; 30 | 31 | Ok(channel) 32 | } 33 | -------------------------------------------------------------------------------- /interface/src/runner.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::{ 4 | constants::{parse_submission_status, PULL_MSG}, 5 | SubmissionId, 6 | }; 7 | use futures::TryStreamExt; 8 | use futures::{channel::mpsc::UnboundedSender, StreamExt}; 9 | use futures::{Sink, Stream}; 10 | use grader::{ 11 | errors::GraderResult, 12 | submission::{ 13 | result::{GroupResult, SubmissionResult}, 14 | Submission, SubmissionMessage, SubmissionStatus, 15 | }, 16 | }; 17 | use postgres_openssl::TlsStream; 18 | use tokio::sync::Mutex; 19 | use tokio_postgres::{Client, Connection, Socket}; 20 | 21 | use super::SharedClient; 22 | 23 | use log::{debug, error, info, warn}; 24 | 25 | pub struct JudgeState { 26 | pub result: Vec, 27 | pub score: f64, 28 | pub time: i32, 29 | pub memory: i32, 30 | } 31 | 32 | pub async fn judge( 33 | task_id: impl ToString, 34 | submission_id: impl ToString, 35 | language: impl ToString, 36 | code: &[String], 37 | client: SharedClient, 38 | ) -> GraderResult { 39 | let task_id = task_id.to_string(); 40 | let submission_id = submission_id.to_string(); 41 | let language = language.to_string(); 42 | 43 | let result = vec![]; 44 | let score = 0.0; 45 | let time = 0; 46 | let memory = 0; 47 | 48 | let state = Mutex::new(JudgeState { 49 | result, 50 | score, 51 | time, 52 | memory, 53 | }); 54 | 55 | let (tx, rx) = futures::channel::mpsc::unbounded::(); 56 | 57 | let mut submission = 58 | Submission::try_from(task_id, submission_id.clone(), language, code, tx).await?; 59 | 60 | tokio::spawn(handle_update_message( 61 | client.clone(), 62 | rx, 63 | submission_id.clone(), 64 | state, 65 | )); 66 | 67 | debug!("compiling {submission_id}"); 68 | submission.compile().await?; 69 | debug!("running {submission_id}"); 70 | let result = submission.run().await?; 71 | debug!( 72 | "finished running {} with result {}/{}", 73 | result.submission_id, result.score, result.full_score 74 | ); 75 | 76 | Ok(result) 77 | } 78 | 79 | pub async fn clear_in_queue(client: SharedClient, tx: UnboundedSender) { 80 | let rows = client 81 | .db_client 82 | .query("SELECT id FROM submission WHERE status = $1", &[&PULL_MSG]) 83 | .await 84 | .unwrap(); 85 | 86 | for row in rows.iter() { 87 | let id: i32 = row.get(0); 88 | let id = id.to_string(); 89 | tx.unbounded_send(id).unwrap(); 90 | } 91 | } 92 | 93 | pub async fn listen_new_submission( 94 | client: Arc, 95 | mut connection: Connection>, 96 | writer: U, 97 | ) where 98 | U: Sink + Sync + Send + 'static, 99 | >::Error: std::fmt::Debug + Send + Sync + 'static, 100 | { 101 | debug!("start listen_new_submission"); 102 | let stream = 103 | futures::stream::poll_fn(move |cx| connection.poll_message(cx)).map_err(|x| panic!("{x}")); 104 | 105 | let stream = stream.and_then(|msg| async { 106 | match msg { 107 | tokio_postgres::AsyncMessage::Notification(msg) => { 108 | info!("{msg:?}"); 109 | Ok(msg.payload().to_string()) 110 | } 111 | _ => panic!(), 112 | } 113 | }); 114 | 115 | let stream = stream.forward(writer); 116 | 117 | let handle = tokio::spawn(stream); 118 | 119 | if client.batch_execute("LISTEN submit;").await.is_err() { 120 | error!("Unable to listen to database"); 121 | panic!("Unable to listen to database"); 122 | } 123 | 124 | if let Err(e) = handle.await { 125 | if e.is_cancelled() { 126 | warn!("Listen new submission got cancelled"); 127 | } else if e.is_panic() { 128 | warn!("Listen new submisison panic"); 129 | } 130 | } 131 | } 132 | 133 | async fn handle_update_message( 134 | client: SharedClient, 135 | mut rx: T, 136 | submission_id: SubmissionId, 137 | state: Mutex, 138 | ) where 139 | T: Stream + std::marker::Unpin, 140 | { 141 | debug!("start handle_update_message for {submission_id}"); 142 | while let Some(message) = rx.next().await { 143 | match message { 144 | SubmissionMessage::Status(status @ SubmissionStatus::Done(..)) => { 145 | if let Err(e) = client 146 | .update_status(&submission_id, parse_submission_status(status)) 147 | .await 148 | { 149 | warn!("unable to update status to database: {e}"); 150 | } 151 | break; 152 | } 153 | SubmissionMessage::Status(status) => { 154 | if let Err(e) = client 155 | .update_status(&submission_id, parse_submission_status(status)) 156 | .await 157 | { 158 | warn!("unable to update status to database: {e}"); 159 | } 160 | } 161 | SubmissionMessage::GroupResult(group_result) => { 162 | log::info!("Group result"); 163 | if let Err(e) = client 164 | .update_result(&submission_id, &state, group_result) 165 | .await 166 | { 167 | warn!("unable to update status to database: {e}"); 168 | } 169 | } 170 | _ => {} 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | red=$(tput setaf 1) 4 | green=$(tput setaf 2) 5 | blue=$(tput setaf 4) 6 | normal=$(tput sgr0) 7 | 8 | SCRIPT=`realpath $0` 9 | SCRIPTPATH=`dirname $SCRIPT` 10 | ISOLATE_GROUPNAME="isolate" 11 | 12 | echo "${green}Cloning Submodule${normal}" 13 | git -C ${SCRIPTPATH} submodule update --init --recursive --depth 1 14 | 15 | echo "${green}Setting up compilers and dependencies${normal}" 16 | sudo apt-get update -y 17 | sudo apt-get install --no-install-recommends -y build-essential openjdk-17-jdk libcap-dev sysfsutils golang libsystemd-dev pkg-config libssl-dev 18 | 19 | echo "${green}Setting up isolate...${green}" 20 | sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space" 21 | sudo sh -c "echo never > /sys/kernel/mm/transparent_hugepage/enabled" 22 | sudo sh -c "echo never > /sys/kernel/mm/transparent_hugepage/defrag" 23 | sudo sh -c "echo 0 > /sys/kernel/mm/transparent_hugepage/khugepaged/defrag" 24 | 25 | if ! grep -Fxq "kernel.randomize_va_space = 0" /etc/sysctl.d/10-isolate.conf; then 26 | sudo sh -c 'echo "kernel.randomize_va_space = 0" >> /etc/sysctl.d/10-isolate.conf' 27 | fi 28 | if ! grep -Fxq "kernel/mm/transparent_hugepage/enabled = never" /etc/sysfs.conf; then 29 | sudo sh -c 'echo "kernel/mm/transparent_hugepage/enabled = never" >> /etc/sysfs.conf' 30 | fi 31 | if ! grep -Fxq "kernel/mm/transparent_hugepage/defrag = never" /etc/sysfs.conf; then 32 | sudo sh -c 'echo "kernel/mm/transparent_hugepage/defrag = never" >> /etc/sysfs.conf' 33 | fi 34 | if ! grep -Fxq "kernel/mm/transparent_hugepage/khugepaged/defrag = 0" /etc/sysfs.conf; then 35 | sudo sh -c 'echo "kernel/mm/transparent_hugepage/khugepaged/defrag = 0" >> /etc/sysfs.conf' 36 | fi 37 | 38 | sudo systemctl enable --now sysfsutils.service 39 | 40 | make -C ${SCRIPTPATH}/isolate isolate 41 | sudo make -C ${SCRIPTPATH}/isolate install 42 | 43 | sudo groupadd ${ISOLATE_GROUPNAME} 44 | sudo chown root:${ISOLATE_GROUPNAME} /usr/local/bin/isolate 45 | if [ -z ${GITHUB_ACTIONS} ]; then 46 | echo "${green}Adding ${USER} to ${ISOLATE_GROUPNAME} group${green}" 47 | sudo chmod 4750 /usr/local/bin/isolate 48 | sudo usermod -aG ${ISOLATE_GROUPNAME} ${USER} 49 | else 50 | echo "${green}Setting isolate permissions to 777 for GitHub Actions${green}" 51 | sudo chmod 4777 /usr/local/bin/isolate 52 | fi 53 | 54 | sudo cp isolate/systemd/* /etc/systemd/system 55 | sudo systemctl daemon-reload 56 | sudo systemctl enable --now isolate.service 57 | 58 | echo "${green}Setting up .env${normal}" 59 | 60 | echo ISOLATE_PATH=\"/usr/local/bin/isolate\" >> .env 61 | echo ALTERNATIVE_PATH=\"/etc/alternatives\" >> .env 62 | echo TEMPORARY_PATH=\"/tmp\" >> .env 63 | echo BASE_PATH=\"$(pwd)/example\" >> .env 64 | 65 | echo "${green}Compiling checkers${normal}" 66 | 67 | CHECKER_PATH=${SCRIPTPATH}/example/scripts/checker_scripts 68 | 69 | mkdir -p ${CHECKER_PATH} 70 | 71 | for file in ${SCRIPTPATH}/testlib/* 72 | do 73 | filename_ex=${file##*/} 74 | ex=${filename_ex#*.} 75 | if [ "${ex}" = "cpp" ]; 76 | then 77 | echo "${blue}Compiling ${filename_ex}${normal}" 78 | filename=${filename_ex%.*} 79 | g++ -std=c++11 ${file} -O2 -o ${CHECKER_PATH}/${filename} -I ${SCRIPTPATH}/testlib 80 | fi 81 | done 82 | 83 | echo "${red}You should reboot your system to apply the kernel paremeters change for isolate${normal}" 84 | --------------------------------------------------------------------------------