AtCoder Easy Test

Make testing sample cases easy

Versione datata 30/12/2020. Vedi la nuova versione l'ultima versione.

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