AtCoder Easy Test

Make testing sample cases easy

当前为 2021-09-19 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name AtCoder Easy Test
  3. // @namespace http://atcoder.jp/
  4. // @version 1.7.0
  5. // @description Make testing sample cases easy
  6. // @author magurofly
  7. // @match https://atcoder.jp/contests/*/tasks/*
  8. // @grant unsafeWindow
  9. // ==/UserScript==
  10.  
  11. // This script uses variables from page below:
  12. // * `$`
  13. // * `getSourceCode`
  14. // * `csrfToken`
  15.  
  16. // This scripts consists of three modules:
  17. // * bottom menu
  18. // * code runner
  19. // * view
  20.  
  21. // This scripts may load scripts below to run code:
  22. // * https://cdn.jsdelivr.net/gh/pythonpad/brython-runner/lib/brython-runner.bundle.js
  23.  
  24. (function script() {
  25.  
  26. const VERSION = "1.7.0";
  27.  
  28. if (typeof unsafeWindow !== "undefined") {
  29. console.log(unsafeWindow);
  30. unsafeWindow.eval(`(${script})();`);
  31. console.log("Script run in unsafeWindow");
  32. return;
  33. }
  34. if (typeof window === "undefined") {
  35. this.window = this.unsafeWindow;
  36. }
  37. const $ = window.$;
  38. const getSourceCode = window.getSourceCode;
  39. const csrfToken = window.csrfToken;
  40.  
  41. const $id = document.getElementById.bind(document);
  42. const $select = document.querySelector.bind(document);
  43. const $selectAll = document.querySelectorAll.bind(document);
  44. const $create = (tagName, attrs = {}, children = []) => {
  45. const e = document.createElement(tagName);
  46. for (const name in attrs) e.setAttribute(name, attrs[name]);
  47. for (const child of children) e.appendChild(child);
  48. return e;
  49. };
  50.  
  51. // -- code runner --
  52. const codeRunner = (function() {
  53. 'use strict';
  54.  
  55. function buildParams(data) {
  56. return Object.entries(data).map(([key, value]) => encodeURIComponent(key) + "=" + encodeURIComponent(value)).join("&");
  57. }
  58.  
  59. function sleep(ms) {
  60. return new Promise(done => setTimeout(done, ms));
  61. }
  62.  
  63. class CodeRunner {
  64. constructor(label, site) {
  65. this.label = `${label} [${site}]`;
  66. }
  67.  
  68. async test(sourceCode, input, supposedOutput, options) {
  69. const result = await this.run(sourceCode, input);
  70. if (result.status != "OK" || typeof supposedOutput !== "string") return result;
  71. let output = result.stdout || "";
  72.  
  73. if (options.trim) {
  74. supposedOutput = supposedOutput.trim();
  75. output = output.trim();
  76. }
  77.  
  78. let equals = (x, y) => x === y;
  79.  
  80. if ("allowableError" in options) {
  81. const floatPattern = /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/;
  82. const superEquals = equals;
  83. equals = (x, y) => {
  84. if (floatPattern.test(x) && floatPattern.test(y)) return Math.abs(parseFloat(x) - parseFloat(y)) <= options.allowableError;
  85. return superEquals(x, y);
  86. }
  87. }
  88.  
  89. if (options.split) {
  90. const superEquals = equals;
  91. equals = (x, y) => {
  92. x = x.split(/\s+/);
  93. y = y.split(/\s+/);
  94. if (x.length != y.length) return false;
  95. const len = x.length;
  96. for (let i = 0; i < len; i++) {
  97. if (!superEquals(x[i], y[i])) return false;
  98. }
  99. return true;
  100. }
  101. }
  102.  
  103. result.status = equals(output, supposedOutput) ? "AC" : "WA";
  104.  
  105. return result;
  106. }
  107. }
  108.  
  109. class CustomRunner extends CodeRunner {
  110. constructor(label, run) {
  111. super(label, "Browser");
  112. this.run = run;
  113. }
  114. }
  115.  
  116. class WandboxRunner extends CodeRunner {
  117. constructor(name, label, options = {}) {
  118. super(label, "Wandbox");
  119. this.name = name;
  120. this.options = options;
  121. }
  122.  
  123. run(sourceCode, input) {
  124. let options = this.options;
  125. if (typeof options == "function") options = options(sourceCode, input);
  126. return this.request(Object.assign(JSON.stringify({
  127. compiler: this.name,
  128. code: sourceCode,
  129. stdin: input,
  130. }), this.options));
  131. }
  132.  
  133. async request(body) {
  134. const startTime = Date.now();
  135. let res;
  136. try {
  137. res = await fetch("https://wandbox.org/api/compile.json", {
  138. method: "POST",
  139. mode: "cors",
  140. headers: {
  141. "Content-Type": "application/json",
  142. },
  143. body,
  144. }).then(r => r.json());
  145. } catch (error) {
  146. console.error(error);
  147. return {
  148. status: "IE",
  149. stderr: error,
  150. };
  151. }
  152. const endTime = Date.now();
  153.  
  154. const result = {
  155. status: "OK",
  156. exitCode: res.status,
  157. execTime: endTime - startTime,
  158. stdout: res.program_output,
  159. stderr: res.program_error,
  160. };
  161. if (res.status != 0) {
  162. if (res.signal) {
  163. result.exitCode += " (" + res.signal + ")";
  164. }
  165. result.stdout = (res.compiler_output || "") + (result.stdout || "");
  166. result.stderr = (res.compiler_error || "") + (result.stderr || "");
  167. if (res.compiler_output || res.compiler_error) {
  168. result.status = "CE";
  169. } else {
  170. result.status = "RE";
  171. }
  172. }
  173.  
  174. return result;
  175. }
  176. }
  177.  
  178. class PaizaIORunner extends CodeRunner {
  179. constructor(name, label) {
  180. super(label, "PaizaIO");
  181. this.name = name;
  182. }
  183.  
  184. async run(sourceCode, input) {
  185. let id, status, error;
  186. try {
  187. const res = await fetch("https://api.paiza.io/runners/create?" + buildParams({
  188. source_code: sourceCode,
  189. language: this.name,
  190. input,
  191. longpoll: true,
  192. longpoll_timeout: 10,
  193. api_key: "guest",
  194. }), {
  195. method: "POST",
  196. mode: "cors",
  197. }).then(r => r.json());
  198. id = res.id;
  199. status = res.status;
  200. error = res.error;
  201. } catch (error) {
  202. return {
  203. status: "IE",
  204. stderr: error,
  205. };
  206. }
  207.  
  208. while (status == "running") {
  209. const res = await (await fetch("https://api.paiza.io/runners/get_status?" + buildParams({
  210. id,
  211. api_key: "guest",
  212. }), {
  213. mode: "cors",
  214. })).json();
  215. status = res.status;
  216. error = res.error;
  217. }
  218.  
  219. const res = await fetch("https://api.paiza.io/runners/get_details?" + buildParams({
  220. id,
  221. api_key: "guest",
  222. }), {
  223. mode: "cors",
  224. }).then(r => r.json());
  225.  
  226. const result = {
  227. exitCode: res.exit_code,
  228. execTime: +res.time * 1e3,
  229. memory: +res.memory * 1e-3,
  230. };
  231.  
  232. if (res.build_result == "failure") {
  233. result.status = "CE";
  234. result.exitCode = res.build_exit_code;
  235. result.stdout = res.build_stdout;
  236. result.stderr = res.build_stderr;
  237. } else {
  238. result.status = (res.result == "timeout") ? "TLE" : (res.result == "failure") ? "RE" : "OK";
  239. result.exitCode = res.exit_code;
  240. result.stdout = res.stdout;
  241. result.stderr = res.stderr;
  242. }
  243.  
  244. return result;
  245. }
  246. }
  247.  
  248. class WandboxCppRunner extends WandboxRunner {
  249. async run(sourceCode, input) {
  250. const ACLBase = "https://cdn.jsdelivr.net/gh/atcoder/ac-library/";
  251. const files = new Map();
  252. const includeHeader = async source => {
  253. const pattern = /^#\s*include\s*[<"]atcoder\/([^>"]+)[>"]/gm;
  254. const loaded = [];
  255. let match;
  256. while (match = pattern.exec(source)) {
  257. const file = "atcoder/" + match[1];
  258. if (files.has(file)) continue;
  259. files.set(file, null);
  260. loaded.push([file, fetch(ACLBase + file, { mode: "cors", cache: "force-cache", }).then(r => r.text())]);
  261. }
  262. const included = await Promise.all(loaded.map(async ([file, r]) => {
  263. const source = await r;
  264. files.set(file, source);
  265. return source;
  266. }));
  267. for (const source of included) {
  268. await includeHeader(source);
  269. }
  270. };
  271. await includeHeader(sourceCode);
  272. const codes = [];
  273. for (const [file, code] of files) {
  274. codes.push({ file, code, });
  275. }
  276. let options = this.options;
  277. if (typeof options == "function") options = options(sourceCode, input);
  278. return await this.request(JSON.stringify(Object.assign({
  279. compiler: this.name,
  280. code: sourceCode,
  281. stdin: input,
  282. codes,
  283. "compiler-option-raw": "-I.",
  284. }, options)));
  285. }
  286. }
  287.  
  288. let waitAtCoderCustomTest = Promise.resolve();
  289. const AtCoderCustomTestBase = location.href.replace(/\/tasks\/.+$/, "/custom_test");
  290. const AtCoderCustomTestResultAPI = AtCoderCustomTestBase + "/json?reload=true";
  291. const AtCoderCustomTestSubmitAPI = AtCoderCustomTestBase + "/submit/json";
  292. class AtCoderRunner extends CodeRunner {
  293. constructor(languageId, label) {
  294. super(label, "AtCoder");
  295. this.languageId = languageId;
  296. }
  297.  
  298. async run(sourceCode, input) {
  299. const promise = this.submit(sourceCode, input);
  300. waitAtCoderCustomTest = promise;
  301. return await promise;
  302. }
  303.  
  304. async submit(sourceCode, input) {
  305. try {
  306. await waitAtCoderCustomTest;
  307. } catch (error) {
  308. console.error(error);
  309. }
  310.  
  311. const error = await fetch(AtCoderCustomTestSubmitAPI, {
  312. method: "POST",
  313. credentials: "include",
  314. headers: {
  315. "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
  316. },
  317. body: buildParams({
  318. "data.LanguageId": this.languageId,
  319. sourceCode,
  320. input,
  321. csrf_token: csrfToken,
  322. }),
  323. }).then(r => r.text());
  324.  
  325. if (error) {
  326. throw new Error(error)
  327. }
  328.  
  329. await sleep(100);
  330.  
  331. for (;;) {
  332. const data = await fetch(AtCoderCustomTestResultAPI, {
  333. method: "GET",
  334. credentials: "include",
  335. }).then(r => r.json());
  336.  
  337. if (!("Result" in data)) continue;
  338. const result = data.Result;
  339.  
  340. if ("Interval" in data) {
  341. await sleep(data.Interval);
  342. continue;
  343. }
  344.  
  345. return {
  346. status: (result.ExitCode == 0) ? "OK" : (result.TimeConsumption == -1) ? "CE" : "RE",
  347. exitCode: result.ExitCode,
  348. execTime: result.TimeConsumption,
  349. memory: result.MemoryConsumption,
  350. stdout: data.Stdout,
  351. stderr: data.Stderr,
  352. };
  353. }
  354. }
  355. }
  356.  
  357. let brythonRunnerLoaded = false;
  358. const brythonRunner = new CustomRunner("Brython", async (sourceCode, input) => {
  359. if (!brythonRunnerLoaded) {
  360. await new Promise((resolve) => {
  361. const script = $create("script");
  362. script.src = "https://cdn.jsdelivr.net/gh/pythonpad/brython-runner/lib/brython-runner.bundle.js";
  363. script.onload = () => {
  364. brythonRunnerLoaded = true;
  365. resolve();
  366. };
  367. document.head.appendChild(script);
  368. });
  369. }
  370.  
  371. let stdout = "";
  372. let stderr = "";
  373. let stdinOffset = 0;
  374. const runner = new BrythonRunner({
  375. stdout: { write(content) { stdout += content; }, flush() {} },
  376. stderr: { write(content) { stderr += content; }, flush() {} },
  377. stdin: { async readline() {
  378. let index = input.indexOf("\n", stdinOffset) + 1;
  379. if (index == 0) index = input.length;
  380. const text = input.slice(stdinOffset, index);
  381. stdinOffset = index;
  382. return text;
  383. } },
  384. });
  385.  
  386. const timeStart = Date.now();
  387. await runner.runCode(sourceCode);
  388. const timeEnd = Date.now();
  389.  
  390. return {
  391. status: "OK",
  392. exitCode: 0,
  393. execTime: (timeEnd - timeStart),
  394. stdout,
  395. stderr,
  396. };
  397. });
  398.  
  399. const runners = {
  400. 4001: [new WandboxRunner("gcc-10.1.0-c", "C (GCC 10.1.0)")],
  401. 4002: [new PaizaIORunner("c", "C (C17 / Clang 10.0.0)", )],
  402. 4003: [new WandboxCppRunner("gcc-10.1.0", "C++ (GCC 10.1.0)", {options: "warning,boost-1.73.0-gcc-9.2.0,gnu++17"})],
  403. 4004: [new WandboxCppRunner("clang-10.0.0", "C++ (Clang 10.0.0)", {options: "warning,boost-nothing-clang-10.0.0,c++17"})],
  404. 4006: [
  405. new PaizaIORunner("python3", "Python (3.8.2)"),
  406. brythonRunner,
  407. ],
  408. 4007: [new PaizaIORunner("bash", "Bash (5.0.17)")],
  409. 4010: [new WandboxRunner("csharp", "C# (.NET Core 6.0.100-alpha.1.20562.2)")],
  410. 4011: [new WandboxRunner("mono-head", "C# (Mono-mcs 5.19.0.0)")],
  411. 4013: [new PaizaIORunner("clojure", "Clojure (1.10.1-1)")],
  412. 4017: [new PaizaIORunner("d", "D (LDC 1.23.0)")],
  413. 4020: [new PaizaIORunner("erlang", "Erlang (10.6.4)")],
  414. 4021: [new PaizaIORunner("elixir", "Elixir (1.10.4)")],
  415. 4022: [new PaizaIORunner("fsharp", "F# (Interactive 4.0)")],
  416. 4023: [new PaizaIORunner("fsharp", "F# (Interactive 4.0)")],
  417. 4026: [new WandboxRunner("go-1.14.1", "Go (1.14.1)")],
  418. 4027: [new WandboxRunner("ghc-head", "Haskell (GHC 8.7.20181121)")],
  419. 4030: [new PaizaIORunner("javascript", "JavaScript (Node.js 12.18.3)")],
  420. 4032: [new PaizaIORunner("kotlin", "Kotlin (1.4.0)")],
  421. 4033: [new WandboxRunner("lua-5.3.4", "Lua (Lua 5.3.4)")],
  422. 4034: [new WandboxRunner("luajit-head", "Lua (LuaJIT 2.1.0-beta3)")],
  423. 4036: [new WandboxRunner("nim-1.0.6", "Nim (1.0.6)")],
  424. 4037: [new PaizaIORunner("objective-c", "Objective-C (Clang 10.0.0)")],
  425. 4039: [new WandboxRunner("ocaml-head", "OCaml (4.13.0+dev0-2020-10-19)")],
  426. 4041: [new WandboxRunner("fpc-3.0.2", "Pascal (FPC 3.0.2)")],
  427. 4042: [new PaizaIORunner("perl", "Perl (5.30.0)")],
  428. 4044: [
  429. new PaizaIORunner("php", "PHP (7.4.10)"),
  430. new WandboxRunner("php-7.3.3", "PHP (7.3.3)"),
  431. ],
  432. 4046: [new WandboxRunner("pypy-head", "PyPy2 (7.3.4-alpha0)")],
  433. 4047: [new WandboxRunner("pypy-7.2.0-3", "PyPy3 (7.2.0)")],
  434. 4049: [
  435. new PaizaIORunner("ruby", "Ruby (2.7.1)"),
  436. new WandboxRunner("ruby-head", "Ruby (HEAD 3.0.0dev)"),
  437. new WandboxRunner("ruby-2.7.0-preview1", "Ruby (2.7.0-preview1)"),
  438. ],
  439. 4050: [
  440. new AtCoderRunner(4050, "Rust (1.42.0)"),
  441. new WandboxRunner("rust-head", "Rust (1.37.0-dev)"),
  442. new PaizaIORunner("rust", "Rust (1.43.0)"),
  443. ],
  444. 4051: [new PaizaIORunner("scala", "Scala (2.13.3)")],
  445. 4053: [new PaizaIORunner("scheme", "Scheme (Gauche 0.9.6)")],
  446. 4055: [new PaizaIORunner("swift", "Swift (5.2.5)")],
  447. 4056: [new CustomRunner("Text",
  448. async (sourceCode, input) => {
  449. return {
  450. status: "OK",
  451. exitCode: 0,
  452. stdout: sourceCode,
  453. };
  454. }
  455. )],
  456. 4058: [new PaizaIORunner("vb", "Visual Basic (.NET Core 4.0.1)")],
  457. 4061: [new PaizaIORunner("cobol", "COBOL - Free (OpenCOBOL 2.2.0)")],
  458. 4101: [new WandboxCppRunner("gcc-9.2.0", "C++ (GCC 9.2.0)")],
  459. 4102: [new WandboxCppRunner("clang-10.0.0", "C++ (Clang 10.0.0)")],
  460. };
  461.  
  462. for (const e of $selectAll("#select-lang option[value]")) {
  463. const languageId = e.value;
  464. if (!(languageId in runners)) runners[languageId] = [];
  465. if (runners[languageId].some(runner => runner instanceof AtCoderRunner)) continue;
  466. runners[languageId].push(new AtCoderRunner(languageId, e.textContent));
  467. }
  468.  
  469. console.info("codeRunner OK");
  470.  
  471. return {
  472. run(languageId, index, sourceCode, input, supposedOutput = null, options = { trim: true, split: true, }) {
  473. if (!(languageId in runners)) return Promise.reject("language not supported");
  474. return runners[languageId][index].test(sourceCode, input, supposedOutput, options);
  475. },
  476.  
  477. getEnvironment(languageId) {
  478. if (!(languageId in runners)) return Promise.reject("language not supported");
  479. return Promise.resolve(runners[languageId].map(runner => runner.label));
  480. },
  481. };
  482. })();
  483.  
  484.  
  485. // -- bottom menu --
  486. const bottomMenu = (function () {
  487. 'use strict';
  488.  
  489. const tabs = new Set();
  490.  
  491. const bottomMenuKey = $(`<button id="bottom-menu-key" type="button" class="navbar-toggle collapsed glyphicon glyphicon-menu-down" data-toggle="collapse" data-target="#bottom-menu">`);
  492. const bottomMenuTabs = $(`<ul id="bottom-menu-tabs" class="nav nav-tabs">`);
  493. const bottomMenuContents = $(`<div id="bottom-menu-contents" class="tab-content">`);
  494.  
  495. $(() => {
  496. const style = $create("style");
  497. style.textContent = `
  498.  
  499. #bottom-menu-wrapper {
  500. background: transparent;
  501. border: none;
  502. pointer-events: none;
  503. padding: 0;
  504. }
  505.  
  506. #bottom-menu-wrapper>.container {
  507. position: absolute;
  508. bottom: 0;
  509. width: 100%;
  510. padding: 0;
  511. }
  512.  
  513. #bottom-menu-wrapper>.container>.navbar-header {
  514. float: none;
  515. }
  516.  
  517. #bottom-menu-key {
  518. display: block;
  519. float: none;
  520. margin: 0 auto;
  521. padding: 10px 3em;
  522. border-radius: 5px 5px 0 0;
  523. background: #000;
  524. opacity: 0.5;
  525. color: #FFF;
  526. cursor: pointer;
  527. pointer-events: auto;
  528. text-align: center;
  529. }
  530.  
  531. @media screen and (max-width: 767px) {
  532. #bottom-menu-key {
  533. opacity: 0.25;
  534. }
  535. }
  536.  
  537. #bottom-menu-key.collapsed:before {
  538. content: "\\e260";
  539. }
  540.  
  541. #bottom-menu-tabs {
  542. padding: 3px 0 0 10px;
  543. cursor: n-resize;
  544. }
  545.  
  546. #bottom-menu-tabs a {
  547. pointer-events: auto;
  548. }
  549.  
  550. #bottom-menu {
  551. pointer-events: auto;
  552. background: rgba(0, 0, 0, 0.8);
  553. color: #fff;
  554. max-height: unset;
  555. }
  556.  
  557. #bottom-menu.collapse:not(.in) {
  558. display: none !important;
  559. }
  560.  
  561. #bottom-menu-tabs>li>a {
  562. background: rgba(150, 150, 150, 0.5);
  563. color: #000;
  564. border: solid 1px #ccc;
  565. filter: brightness(0.75);
  566. }
  567.  
  568. #bottom-menu-tabs>li>a:hover {
  569. background: rgba(150, 150, 150, 0.5);
  570. border: solid 1px #ccc;
  571. color: #111;
  572. filter: brightness(0.9);
  573. }
  574.  
  575. #bottom-menu-tabs>li.active>a {
  576. background: #eee;
  577. border: solid 1px #ccc;
  578. color: #333;
  579. filter: none;
  580. }
  581.  
  582. .bottom-menu-btn-close {
  583. font-size: 8pt;
  584. vertical-align: baseline;
  585. padding: 0 0 0 6px;
  586. margin-right: -6px;
  587. }
  588.  
  589. #bottom-menu-contents {
  590. padding: 5px 15px;
  591. max-height: 50vh;
  592. overflow-y: auto;
  593. }
  594.  
  595. #bottom-menu-contents .panel {
  596. color: #333;
  597. }
  598.  
  599. `;
  600. document.head.appendChild(style);
  601. const bottomMenu = $(`<div id="bottom-menu" class="collapse navbar-collapse">`).append(bottomMenuTabs, bottomMenuContents);
  602. $(`<div id="bottom-menu-wrapper" class="navbar navbar-default navbar-fixed-bottom">`)
  603. .append($(`<div class="container">`)
  604. .append(
  605. $(`<div class="navbar-header">`).append(bottomMenuKey),
  606. bottomMenu))
  607. .appendTo("#main-div");
  608.  
  609. let resizeStart = null;
  610. bottomMenuTabs.on({
  611. mousedown({target, pageY}) {
  612. if (target.id != "bottom-menu-tabs") return;
  613. resizeStart = {y: pageY, height: bottomMenuContents.height()};
  614. },
  615. mousemove(e) {
  616. if (!resizeStart) return;
  617. e.preventDefault();
  618. bottomMenuContents.height(resizeStart.height - (e.pageY - resizeStart.y));
  619. },
  620. });
  621. document.addEventListener("mouseup", () => { resizeStart = null; });
  622. document.addEventListener("mouseleave", () => { resizeStart = null; });
  623. });
  624.  
  625. const menuController = {
  626. addTab(tabId, tabLabel, paneContent, options = {}) {
  627. console.log("addTab: %s (%s)", tabLabel, tabId, paneContent);
  628. const tab = $(`<a id="bottom-menu-tab-${tabId}" href="#" data-target="#bottom-menu-pane-${tabId}" data-toggle="tab">`)
  629. .click(e => {
  630. e.preventDefault();
  631. tab.tab("show");
  632. })
  633. .append(tabLabel);
  634. const tabLi = $(`<li>`).append(tab).appendTo(bottomMenuTabs);
  635. const pane = $(`<div class="tab-pane" id="bottom-menu-pane-${tabId}">`).append(paneContent).appendTo(bottomMenuContents);
  636. console.dirxml(bottomMenuContents);
  637. const controller = {
  638. close() {
  639. tabLi.remove();
  640. pane.remove();
  641. tabs.delete(tab);
  642. if (tabLi.hasClass("active") && tabs.size > 0) {
  643. tabs.values().next().value.tab("show");
  644. }
  645. },
  646.  
  647. show() {
  648. menuController.show();
  649. tab.tab("show");
  650. },
  651.  
  652. set color(color) {
  653. tab.css("background-color", color);
  654. },
  655. };
  656. tabs.add(tab);
  657. if (options.closeButton) tab.append($(`<a class="bottom-menu-btn-close btn btn-link glyphicon glyphicon-remove">`).click(() => controller.close()));
  658. if (options.active || tabs.size == 1) pane.ready(() => tab.tab("show"));
  659. return controller;
  660. },
  661.  
  662. show() {
  663. if (bottomMenuKey.hasClass("collapsed")) bottomMenuKey.click();
  664. },
  665.  
  666. toggle() {
  667. bottomMenuKey.click();
  668. },
  669. };
  670.  
  671. console.info("bottomMenu OK");
  672.  
  673. return menuController;
  674. })();
  675.  
  676. $(() => {
  677. // returns [{input, output, anchor}]
  678. function getTestCases() {
  679. const selectors = [
  680. ["#task-statement p+pre.literal-block", ".section"], // utpc2011_1
  681. ["#task-statement pre.source-code-for-copy", ".part"],
  682. ["#task-statement .lang>*:nth-child(1) .div-btn-copy+pre", ".part"],
  683. ["#task-statement .div-btn-copy+pre", ".part"],
  684. ["#task-statement>.part pre.linenums", ".part"], // abc003_4
  685. ["#task-statement>.part:not(.io-style)>h3+section>pre", ".part"],
  686. ["#task-statement pre", ".part"],
  687. ];
  688.  
  689. for (const [selector, closestSelector] of selectors) {
  690. const e = $selectAll(selector);
  691. if (e.length == 0) continue;
  692. const testcases = [];
  693. for (let i = 0; i < e.length; i += 2) {
  694. const container = e[i].closest(closestSelector) || e[i].parentElement;
  695. testcases.push({
  696. input: (e[i]||{}).textContent,
  697. output: (e[i+1]||{}).textContent,
  698. anchor: container.querySelector("h3"),
  699. });
  700. }
  701. return testcases;
  702. }
  703.  
  704. return [];
  705. }
  706.  
  707. async function runTest(title, input, output = null) {
  708. const uid = Date.now().toString();
  709. title = title ? "Result " + title : "Result";
  710. const content = $create("div", { class: "container" });
  711. content.innerHTML = `
  712. <div class="row">
  713. <div class="col-xs-12 ${(output == null) ? "" : "col-sm-6"}"><div class="form-group">
  714. <label class="control-label col-xs-12" for="atcoder-easy-test-${uid}-stdin">Standard Input</label>
  715. <div class="col-xs-12">
  716. <textarea id="atcoder-easy-test-${uid}-stdin" class="form-control" rows="3" readonly></textarea>
  717. </div>
  718. </div></div>${(output == null) ? "" : `
  719. <div class="col-xs-12 col-sm-6"><div class="form-group">
  720. <label class="control-label col-xs-12" for="atcoder-easy-test-${uid}-expected">Expected Output</label>
  721. <div class="col-xs-12">
  722. <textarea id="atcoder-easy-test-${uid}-expected" class="form-control" rows="3" readonly></textarea>
  723. </div>
  724. </div></div>
  725. `}
  726. </div>
  727. <div class="row"><div class="col-sm-6 col-sm-offset-3">
  728. <div class="panel panel-default"><table class="table table-condensed">
  729. <tr>
  730. <th class="text-center">Exit Code</th>
  731. <th class="text-center">Exec Time</th>
  732. <th class="text-center">Memory</th>
  733. </tr>
  734. <tr>
  735. <td id="atcoder-easy-test-${uid}-exit-code" class="text-center"></td>
  736. <td id="atcoder-easy-test-${uid}-exec-time" class="text-center"></td>
  737. <td id="atcoder-easy-test-${uid}-memory" class="text-center"></td>
  738. </tr>
  739. </table></div>
  740. </div></div>
  741. <div class="row">
  742. <div class="col-xs-12 col-md-6"><div class="form-group">
  743. <label class="control-label col-xs-12" for="atcoder-easy-test-${uid}-stdout">Standard Output</label>
  744. <div class="col-xs-12">
  745. <textarea id="atcoder-easy-test-${uid}-stdout" class="form-control" rows="5" readonly></textarea>
  746. </div>
  747. </div></div>
  748. <div class="col-xs-12 col-md-6"><div class="form-group">
  749. <label class="control-label col-xs-12" for="atcoder-easy-test-${uid}-stderr">Standard Error</label>
  750. <div class="col-xs-12">
  751. <textarea id="atcoder-easy-test-${uid}-stderr" class="form-control" rows="5" readonly></textarea>
  752. </div>
  753. </div></div>
  754. </div>
  755. `;
  756. const tab = bottomMenu.addTab("easy-test-result-" + uid, title, content, { active: true, closeButton: true });
  757. $id(`atcoder-easy-test-${uid}-stdin`).value = input;
  758. if (output != null) $id(`atcoder-easy-test-${uid}-expected`).value = output;
  759.  
  760. const options = { trim: true, split: true, };
  761. if ($id("atcoder-easy-test-allowable-error-check").checked) {
  762. options.allowableError = parseFloat($id("atcoder-easy-test-allowable-error").value);
  763. }
  764.  
  765. const result = await codeRunner.run($select("#select-lang>select").value, +$id("atcoder-easy-test-language").value, window.getSourceCode(), input, output, options);
  766.  
  767. if (result.status == "AC") {
  768. tab.color = "#dff0d8";
  769. $id(`atcoder-easy-test-${uid}-stdout`).style.backgroundColor = "#dff0d8";
  770. } else if (result.status != "OK") {
  771. tab.color = "#fcf8e3";
  772. if (result.status == "WA") $id(`atcoder-easy-test-${uid}-stdout`).style.backgroundColor = "#fcf8e3";
  773. }
  774.  
  775. const eExitCode = $id(`atcoder-easy-test-${uid}-exit-code`);
  776. eExitCode.textContent = result.exitCode;
  777. eExitCode.classList.toggle("bg-success", result.exitCode == 0);
  778. eExitCode.classList.toggle("bg-danger", result.exitCode != 0);
  779. if ("execTime" in result) $id(`atcoder-easy-test-${uid}-exec-time`).textContent = result.execTime + " ms";
  780. if ("memory" in result) $id(`atcoder-easy-test-${uid}-memory`).textContent = result.memory + " KB";
  781. $id(`atcoder-easy-test-${uid}-stdout`).value = result.stdout || "";
  782. $id(`atcoder-easy-test-${uid}-stderr`).value = result.stderr || "";
  783.  
  784. result.uid = uid;
  785. result.tab = tab;
  786. return result;
  787. }
  788.  
  789. console.log("bottomMenu", bottomMenu);
  790.  
  791. bottomMenu.addTab("easy-test", "Easy Test", $(`<form id="atcoder-easy-test-container" class="form-horizontal">`)
  792. .html(`
  793. <small style="position: absolute; display: block; bottom: 0; right: 0; padding: 1% 4%; width: 95%; text-align: right;">AtCoder Easy Test v${VERSION}</small>
  794. <div class="row">
  795. <div class="col-xs-12 col-lg-8">
  796. <div class="form-group">
  797. <label class="control-label col-sm-2">Test Environment</label>
  798. <div class="col-sm-10">
  799. <select class="form-control" id="atcoder-easy-test-language"></select>
  800. </div>
  801. </div>
  802. <div class="form-group">
  803. <label class="control-label col-sm-2" for="atcoder-easy-test-input">Standard Input</label>
  804. <div class="col-sm-10">
  805. <textarea id="atcoder-easy-test-input" name="input" class="form-control" rows="3"></textarea>
  806. </div>
  807. </div>
  808. </div>
  809. <div class="col-xs-12 col-lg-4">
  810. <details close>
  811. <summary>Expected Output</summary>
  812. <div class="form-group">
  813. <label class="control-label col-sm-2" for="atcoder-easy-test-allowable-error-check">Allowable Error</label>
  814. <div class="col-sm-10">
  815. <div class="input-group">
  816. <span class="input-group-addon">
  817. <input id="atcoder-easy-test-allowable-error-check" type="checkbox" checked>
  818. </span>
  819. <input id="atcoder-easy-test-allowable-error" type="text" class="form-control" value="1e-6">
  820. </div>
  821. </div>
  822. </div>
  823. <div class="form-group">
  824. <label class="control-label col-sm-2" for="atcoder-easy-test-output">Expected Output</label>
  825. <div class="col-sm-10">
  826. <textarea id="atcoder-easy-test-output" name="output" class="form-control" rows="3"></textarea>
  827. </div>
  828. </div>
  829. </details>
  830. </div>
  831. <div class="col-xs-12">
  832. <div class="col-xs-11 col-xs-offset=1">
  833. <div class="form-group">
  834. <a id="atcoder-easy-test-run" class="btn btn-primary">Run</a>
  835. </div>
  836. </div>
  837. </div>
  838. </div>
  839. <style>
  840. #atcoder-easy-test-language {
  841. border: none;
  842. background: transparent;
  843. font: inherit;
  844. color: #fff;
  845. }
  846. #atcoder-easy-test-language option {
  847. border: none;
  848. color: #333;
  849. font: inherit;
  850. }
  851. </style>
  852. `).ready(() => {
  853. $id("atcoder-easy-test-run").addEventListener("click", () => {
  854. const title = "";
  855. const input = $id("atcoder-easy-test-input").value;
  856. const output = $id("atcoder-easy-test-output").value;
  857. runTest(title, input, output || null);
  858. });
  859. $("#select-lang>select").change(() => setLanguage()); //NOTE: This event is only for jQuery; do not replce with Vanilla
  860. $id("atcoder-easy-test-allowable-error").disabled = this.checked;
  861. $id("atcoder-easy-test-allowable-error-check").addEventListener("change", e => { $id("atcoder-easy-test-allowable-error").disabled = !e.target.checked; });
  862.  
  863. async function setLanguage() {
  864. const languageId = $select("#select-lang>select").value;
  865. const eTestLanguage = $id("atcoder-easy-test-language");
  866. while (eTestLanguage.firstChild) eTestLanguage.removeChild(eTestLanguage.firstChild);
  867. try {
  868. const labels = await codeRunner.getEnvironment(languageId);
  869. console.log(`language: ${labels[0]} (${languageId})`);
  870. labels.forEach((label, index) => {
  871. const option = $create("option", {value: index});
  872. option.textContent = label;
  873. eTestLanguage.appendChild(option);
  874. });
  875. $id("atcoder-easy-test-run").classList.remove("disabled");
  876. $id("atcoder-easy-test-btn-test-all").disabled = false;
  877. } catch (error) {
  878. console.log(`language: ? (${languageId})`);
  879. const option = $create("option", { "class": "fg-danger" });
  880. option.textContent = error;
  881. eTestLanguage.appendChild(option);
  882. $id("atcoder-easy-test-run").classList.add("disabled");
  883. $id("atcoder-easy-test-btn-test-all").disabled = true;
  884. }
  885. }
  886.  
  887. setLanguage();
  888. }), { active: true });
  889.  
  890. try {
  891. const testfuncs = [];
  892. const runButtons = [];
  893.  
  894. const testcases = getTestCases();
  895. for (const {input, output, anchor} of testcases) {
  896. const testfunc = async () => {
  897. const title = anchor.childNodes[0].data;
  898. const result = await runTest(title, input, output);
  899. if (result.status == "OK" || result.status == "AC") {
  900. $id(`atcoder-easy-test-${result.uid}-stdout`).classList.add("bg-success");
  901. }
  902. return result;
  903. };
  904. testfuncs.push(testfunc);
  905.  
  906. const runButton = $(`<a class="btn btn-primary btn-sm" style="vertical-align: top; margin-left: 0.5em">`)
  907. .text("Run")
  908. .click(async () => {
  909. await testfunc();
  910. if ($id("bottom-menu-key").classList.contains("collapsed")) $id("bottom-menu-key").click();
  911. });
  912. anchor.appendChild(runButton[0]);
  913. runButtons.push(runButton);
  914. }
  915.  
  916. const fnTestAll = async () => {
  917. const statuses = testfuncs.map(_ => $(`<div class="label label-default" style="margin: 3px">`).text("WJ..."));
  918. const progress = $(`<div class="progress-bar">`).text(`0 / ${testfuncs.length}`);
  919. let finished = 0;
  920. const closeButton = $(`<button type="button" class="close" data-dismiss="alert" aria-label="close">`)
  921. .append($(`<span aria-hidden="true">`).text("\xd7"));
  922. const resultAlert = $(`<div class="alert alert-dismissible">`)
  923. .append(closeButton)
  924. .append($(`<div class="progress">`).append(progress))
  925. .append(...statuses)
  926. .prependTo(testAllResultRow);
  927. const results = await Promise.all(testfuncs.map(async (testfunc, i) => {
  928. const result = await testfunc();
  929. finished++;
  930. progress.text(`${finished} / ${statuses.length}`).css("width", `${finished/statuses.length*100}%`);
  931. statuses[i].toggleClass("label-success", result.status == "AC").toggleClass("label-warning", result.status != "AC").text(result.status).click(() => result.tab.show()).css("cursor", "pointer");
  932. return result;
  933. }));
  934. if (results.every(({status}) => status == "AC")) {
  935. resultAlert.addClass("alert-success");
  936. } else {
  937. resultAlert.addClass("alert-warning");
  938. }
  939. closeButton.click(() => {
  940. for (const {tab} of results) {
  941. tab.close();
  942. }
  943. });
  944. return results;
  945. };
  946.  
  947. const testAllResultRow = $(`<div class="row">`);
  948. const testAndSubmitButton = $(`<a id="atcoder-easy-test-btn-test-and-submit" class="btn btn-info btn" style="margin-left: 5px" title="Ctrl+Enter" data-toggle="tooltip">`)
  949. .text("Test & Submit")
  950. .click(async () => {
  951. if (testAndSubmitButton.hasClass("disabled")) throw new Error("Button is disabled");
  952. testAndSubmitButton.addClass("disabled");
  953. try {
  954. const results = await fnTestAll();
  955. if (results.every(({status}) => status == "AC")) {
  956. // submit
  957. $("#submit").click();
  958. } else {
  959. // failed to submit
  960. }
  961. } catch(e) {
  962. throw e;
  963. } finally {
  964. testAndSubmitButton.removeClass("disabled");
  965. }
  966. });
  967. const testAllButton = $(`<a id="atcoder-easy-test-btn-test-all" class="btn btn-default btn-sm" style="margin-left: 5px" title="Alt+Enter" data-toggle="tooltip">`)
  968. .text("Test All Samples")
  969. .click(async () => {
  970. if (testAllButton.attr("disabled")) throw new Error("Button is disabled");
  971. await fnTestAll();
  972. });
  973. $("#submit").after(testAllButton).after(testAndSubmitButton).closest("form").append(testAllResultRow);
  974. document.addEventListener("keydown", e => {
  975. if (e.altKey) {
  976. switch (e.key) {
  977. case "Enter":
  978. testAllButton.click();
  979. break;
  980. case "Escape":
  981. bottomMenu.toggle();
  982. break;
  983. }
  984. }
  985. if (e.ctrlKey) {
  986. switch (e.key) {
  987. case "Enter":
  988. testAndSubmitButton.click();
  989. break;
  990. }
  991. }
  992. });
  993. } catch (e) {
  994. console.error(e);
  995. }
  996.  
  997. document.addEventListener("keydown", e => {
  998. if (e.altKey) {
  999. switch (e.key) {
  1000. case "Escape":
  1001. bottomMenu.toggle();
  1002. break;
  1003. }
  1004. }
  1005. });
  1006.  
  1007. console.info("view OK");
  1008. });
  1009.  
  1010. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址