AtCoder Easy Test

Make testing sample cases easy

As of 2020-11-14. See the latest version.

  1. // ==UserScript==
  2. // @name AtCoder Easy Test
  3. // @namespace http://atcoder.jp/
  4. // @version 1.1.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. if (typeof unsafeWindow !== "undefined") {
  24. console.log(unsafeWindow);
  25. unsafeWindow.eval(`(${script})();`);
  26. console.log("Script run in unsafeWindow");
  27. return;
  28. }
  29. const $ = window.$;
  30. const getSourceCode = window.getSourceCode;
  31. const csrfToken = window.csrfToken;
  32.  
  33. // -- code runner --
  34. const codeRunner = (function() {
  35. 'use strict';
  36.  
  37. function buildParams(data) {
  38. return Object.entries(data).map(([key, value]) =>
  39. encodeURIComponent(key) + "=" + encodeURIComponent(value)).join("&");
  40. }
  41.  
  42. function sleep(ms) {
  43. return new Promise(done => setTimeout(done, ms));
  44. }
  45.  
  46. class WandboxRunner {
  47. constructor(name, label, options = {}) {
  48. this.name = name;
  49. this.label = label + " [Wandbox]";
  50. }
  51.  
  52. run(sourceCode, input) {
  53. let options = this.options;
  54. if (typeof options == "function") options = options(sourceCode, input);
  55. return this.request(Object.assign(JSON.stringify({
  56. compiler: this.name,
  57. code: sourceCode,
  58. stdin: input,
  59. }), this.options));
  60. }
  61.  
  62. async request(body) {
  63. const startTime = Date.now();
  64. let res;
  65. try {
  66. res = await fetch("https://wandbox.org/api/compile.json", {
  67. method: "POST",
  68. mode: "cors",
  69. headers: {
  70. "Content-Type": "application/json",
  71. },
  72. body,
  73. }).then(r => r.json());
  74. } catch (error) {
  75. return {
  76. status: "IE",
  77. stderr: error,
  78. };
  79. }
  80. const endTime = Date.now();
  81.  
  82. const result = {
  83. status: "OK",
  84. exitCode: res.status,
  85. execTime: endTime - startTime,
  86. stdout: res.program_output,
  87. stderr: res.program_error,
  88. };
  89. if (res.status != 0) {
  90. if (res.signal) {
  91. result.exitCode += " (" + res.signal + ")";
  92. }
  93. result.stdout = (res.compiler_output || "") + (result.stdout || "");
  94. result.stderr = (res.compiler_error || "") + (result.stderr || "");
  95. if (res.compiler_output || res.compiler_error) {
  96. result.status = "CE";
  97. } else {
  98. result.status = "RE";
  99. }
  100. }
  101.  
  102. return result;
  103. }
  104. }
  105.  
  106. class PaizaIORunner {
  107. constructor(name, label) {
  108. this.name = name;
  109. this.label = label + " [PaizaIO]";
  110. }
  111.  
  112. async run(sourceCode, input) {
  113. let id, status, error;
  114. try {
  115. const res = await fetch("https://api.paiza.io/runners/create?" + buildParams({
  116. source_code: sourceCode,
  117. language: this.name,
  118. input,
  119. longpoll: true,
  120. longpoll_timeout: 10,
  121. api_key: "guest",
  122. }), {
  123. method: "POST",
  124. mode: "cors",
  125. }).then(r => r.json());
  126. id = res.id;
  127. status = res.status;
  128. error = res.error;
  129. } catch (error) {
  130. return {
  131. status: "IE",
  132. stderr: error,
  133. };
  134. }
  135.  
  136. while (status == "running") {
  137. const res = await (await fetch("https://api.paiza.io/runners/get_status?" + buildParams({
  138. id,
  139. api_key: "guest",
  140. }), {
  141. mode: "cors",
  142. })).json();
  143. status = res.status;
  144. error = res.error;
  145. }
  146.  
  147. const res = await fetch("https://api.paiza.io/runners/get_details?" + buildParams({
  148. id,
  149. api_key: "guest",
  150. }), {
  151. mode: "cors",
  152. }).then(r => r.json());
  153.  
  154. const result = {
  155. exitCode: res.exit_code,
  156. execTime: +res.time * 1e3,
  157. memory: +res.memory * 1e-3,
  158. };
  159.  
  160. if (res.build_result == "failure") {
  161. result.status = "CE";
  162. result.exitCode = res.build_exit_code;
  163. result.stdout = res.build_stdout;
  164. result.stderr = res.build_stderr;
  165. } else {
  166. result.status = (res.result == "timeout") ? "TLE" : (res.result == "failure") ? "RE" : "OK";
  167. result.exitCode = res.exit_code;
  168. result.stdout = res.stdout;
  169. result.stderr = res.stderr;
  170. }
  171.  
  172. return result;
  173. }
  174. }
  175.  
  176. const loader = {
  177. loaded: {},
  178. async load(url, options = { mode: "cors", }) {
  179. if (!(url in this.loaded)) {
  180. this.loaded[url] = await fetch(url, options);
  181. }
  182. return this.loaded[url];
  183. },
  184. };
  185.  
  186. class WandboxCppRunner extends WandboxRunner {
  187. async run(sourceCode, input) {
  188. 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"];
  189. const files = new Set(["all"].concat(allFiles).filter(file => new RegExp(String.raw`^#\s*include\s*<atcoder/${file}>`, "m").test(sourceCode)));
  190. let allHeaders = false;
  191. if (files.has("all")) {
  192. allHeaders = true;
  193. files.delete("all");
  194. for (const file of allFiles) {
  195. files.add(file);
  196. }
  197. }
  198.  
  199. if (files.has("convolution")) {
  200. files.add("modint");
  201. }
  202. if (files.has("convolution") || files.has("lazysegtree") || files.has("segtree")) {
  203. files.add("internal_bit");
  204. }
  205. if (files.has("maxflow")) {
  206. files.add("internal_queue");
  207. }
  208. if (files.has("scc") || files.has("twosat")) {
  209. files.add("internal_scc");
  210. }
  211. if (files.has("math") || files.has("modint")) {
  212. files.add("internal_math");
  213. }
  214. if (files.has("fenwicktree") || files.has("fenwicktree")) {
  215. files.add("internal_type_traits");
  216. }
  217.  
  218. const ACLBase = "https://cdn.jsdelivr.net/gh/atcoder/ac-library/atcoder/";
  219.  
  220. const codePromises = [];
  221. const codes = [];
  222. for (const file of files) {
  223. codePromises.push(fetch(ACLBase + file + ".hpp", {
  224. mode: "cors",
  225. cache: "force-cache",
  226. })
  227. .then(r => r.text())
  228. .then(code => codes.push({ file: "atcoder/" + file, code })));
  229. }
  230. if (allHeaders) {
  231. codePromises.push(fetch(ACLBase + "all", {
  232. mode: "cors",
  233. cache: "force-cache",
  234. })
  235. .then(r => r.text())
  236. .then(code => codes.push({ file: "atcoder/all", code })));
  237. }
  238.  
  239. await Promise.all(codePromises);
  240.  
  241.  
  242. let options = this.options;
  243. if (typeof options == "function") options = options(sourceCode, input);
  244. return await this.request(Object.assign(JSON.stringify({
  245. compiler: this.name,
  246. code: sourceCode,
  247. stdin: input,
  248. codes,
  249. "compiler-option-raw": "-I.",
  250. }), this.options));
  251. }
  252. }
  253.  
  254. let waitAtCoderCustomTest = Promise.resolve();
  255. const AtCoderCustomTestBase = location.href.replace(/\/tasks\/.+$/, "/custom_test");
  256. const AtCoderCustomTestResultAPI = AtCoderCustomTestBase + "/json?reload=true";
  257. const AtCoderCustomTestSubmitAPI = AtCoderCustomTestBase + "/submit/json";
  258. class AtCoderRunner {
  259. constructor(languageId, label) {
  260. this.languageId = languageId;
  261. this.label = label + " [AtCoder]";
  262. }
  263.  
  264. async run(sourceCode, input) {
  265. const promise = this.submit(sourceCode, input);
  266. waitAtCoderCustomTest = promise;
  267. return await promise;
  268. }
  269.  
  270. async submit(sourceCode, input) {
  271. try {
  272. await waitAtCoderCustomTest;
  273. } catch (error) {
  274. console.error(error);
  275. }
  276.  
  277. const error = await fetch(AtCoderCustomTestSubmitAPI, {
  278. method: "POST",
  279. credentials: "include",
  280. headers: {
  281. "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
  282. },
  283. body: buildParams({
  284. "data.LanguageId": this.languageId,
  285. sourceCode,
  286. input,
  287. csrf_token: csrfToken,
  288. }),
  289. }).then(r => r.text());
  290.  
  291. if (error) {
  292. throw new Error(error)
  293. }
  294.  
  295. await sleep(100);
  296.  
  297. for (;;) {
  298. const data = await fetch(AtCoderCustomTestResultAPI, {
  299. method: "GET",
  300. credentials: "include",
  301. }).then(r => r.json());
  302.  
  303. if (!("Result" in data)) continue;
  304. const result = data.Result;
  305.  
  306. if ("Interval" in data) {
  307. await sleep(data.Interval);
  308. continue;
  309. }
  310.  
  311. return {
  312. status: (result.ExitCode == 0) ? "OK" : (result.TimeConsumption == -1) ? "CE" : "RE",
  313. exitCode: result.ExitCode,
  314. execTime: result.TimeConsumption,
  315. memory: result.MemoryConsumption,
  316. stdout: data.Stdout,
  317. stderr: data.Stderr,
  318. };
  319. }
  320. }
  321. }
  322.  
  323. const runners = {
  324. 4001: new WandboxRunner("gcc-9.2.0-c", "C (GCC 9.2.0)"),
  325. 4002: new PaizaIORunner("c", "C (C17 / Clang 10.0.0)"),
  326. 4003: new WandboxCppRunner("gcc-9.2.0", "C++ (GCC 9.2.0)"),
  327. 4004: new WandboxCppRunner("clang-10.0.0", "C++ (Clang 10.0.0)"),
  328. 4006: new PaizaIORunner("python3", "Python (3.8.2)"),
  329. 4007: new PaizaIORunner("bash", "Bash (5.0.17)"),
  330. 4010: new WandboxRunner("csharp", "C# (.NET Core 6.0.100-alpha.1.20562.2)"),
  331. 4011: new WandboxRunner("mono-head", "C# (Mono-mcs 5.19.0.0)"),
  332. 4013: new PaizaIORunner("clojure", "Clojure (1.10.1-1)"),
  333. 4017: new PaizaIORunner("d", "D (LDC 1.23.0)"),
  334. 4020: new PaizaIORunner("erlang", "Erlang (10.6.4)"),
  335. 4021: new PaizaIORunner("elixir", "Elixir (1.10.4)"),
  336. 4022: new PaizaIORunner("fsharp", "F# (Interactive 4.0)"),
  337. 4023: new PaizaIORunner("fsharp", "F# (Interactive 4.0)"),
  338. 4026: new WandboxRunner("go-1.14.1", "Go (1.14.1)"),
  339. 4027: new WandboxRunner("ghc-head", "Haskell (GHC 8.7.20181121)"),
  340. 4030: new PaizaIORunner("javascript", "JavaScript (Node.js 12.18.3)"),
  341. 4032: new PaizaIORunner("kotlin", "Kotlin (1.4.0)"),
  342. 4033: new WandboxRunner("lua-5.3.4", "Lua (Lua 5.3.4)"),
  343. 4034: new WandboxRunner("luajit-head", "Lua (LuaJIT 2.1.0-beta3)"),
  344. 4036: new WandboxRunner("nim-1.0.6", "Nim (1.0.6)"),
  345. 4037: new PaizaIORunner("objective-c", "Objective-C (Clang 10.0.0)"),
  346. 4039: new WandboxRunner("ocaml-head", "OCaml (4.13.0+dev0-2020-10-19)"),
  347. 4041: new WandboxRunner("fpc-3.0.2", "Pascal (FPC 3.0.2)"),
  348. 4042: new PaizaIORunner("perl", "Perl (5.30.0)"),
  349. 4044: new PaizaIORunner("php", "PHP (7.4.10)"),
  350. 4046: new WandboxRunner("pypy-head", "PyPy2 (7.3.4-alpha0)"),
  351. 4047: new WandboxRunner("pypy-7.2.0-3", "PyPy3 (7.2.0)"),
  352. 4049: new PaizaIORunner("ruby", "Ruby (2.7.1)"),
  353. 4050: new WandboxRunner("rust-head", "Rust (1.37.0-dev)"),
  354. 4051: new PaizaIORunner("scala", "Scala (2.13.3)"),
  355. 4053: new PaizaIORunner("scheme", "Scheme (Gauche 0.9.6)"),
  356. 4055: new PaizaIORunner("swift", "Swift (5.2.5)"),
  357. 4056: {
  358. label: "Text (JavaScript)",
  359. name: "text",
  360. async run(sourceCode, input) {
  361. return {
  362. status: "OK",
  363. exitCode: 0,
  364. stdout: sourceCode,
  365. };
  366. },
  367. },
  368. 4058: new PaizaIORunner("vb", "Visual Basic (.NET Core 4.0.1)"),
  369. 4061: new PaizaIORunner("cobol", "COBOL - Free (OpenCOBOL 2.2.0)"),
  370. 4101: new WandboxCppRunner("gcc-9.2.0", "C++ (GCC 9.2.0)"),
  371. 4102: new WandboxCppRunner("clang-10.0.0", "C++ (Clang 10.0.0)"),
  372. };
  373.  
  374. $("#select-lang option[value]").each((_, e) => {
  375. const elem = $(e);
  376. const languageId = elem.val();
  377. if (languageId in runners) return;
  378. runners[languageId] = new AtCoderRunner(languageId, elem.text());
  379. });
  380.  
  381. console.info("codeRunner OK");
  382.  
  383. return {
  384. run(languageId, sourceCode, input) {
  385. if (!(languageId in runners)) {
  386. return Promise.reject("language not supported");
  387. }
  388. return runners[languageId].run(sourceCode, input);
  389. },
  390.  
  391. getEnvironment(languageId) {
  392. if (!(languageId in runners)) {
  393. return Promise.reject("language not supported");
  394. }
  395. return Promise.resolve(runners[languageId].label);
  396. },
  397. };
  398. })();
  399.  
  400.  
  401. // -- bottom menu --
  402. const bottomMenu = (function () {
  403. 'use strict';
  404.  
  405. const tabs = new Set();
  406.  
  407. const bottomMenuKey = $(`<button id="bottom-menu-key" type="button" class="navbar-toggle collapsed glyphicon glyphicon-menu-down" data-toggle="collapse" data-target="#bottom-menu">`);
  408. const bottomMenuTabs = $(`<ul id="bottom-menu-tabs" class="nav nav-tabs">`);
  409. const bottomMenuContents = $(`<div id="bottom-menu-contents" class="tab-content">`);
  410.  
  411. $(() => {
  412. $(`<style>`)
  413. .text(`
  414.  
  415. #bottom-menu-wrapper {
  416. background: transparent;
  417. border: none;
  418. pointer-events: none;
  419. padding: 0;
  420. }
  421.  
  422. #bottom-menu-wrapper>.container {
  423. position: absolute;
  424. bottom: 0;
  425. width: 100%;
  426. padding: 0;
  427. }
  428.  
  429. #bottom-menu-wrapper>.container>.navbar-header {
  430. float: none;
  431. }
  432.  
  433. #bottom-menu-key {
  434. display: block;
  435. float: none;
  436. margin: 0 auto;
  437. padding: 10px 3em;
  438. border-radius: 5px 5px 0 0;
  439. background: #000;
  440. opacity: 0.85;
  441. color: #FFF;
  442. cursor: pointer;
  443. pointer-events: auto;
  444. text-align: center;
  445. }
  446.  
  447. #bottom-menu-key.collapsed:before {
  448. content: "\\e260";
  449. }
  450.  
  451. #bottom-menu-tabs {
  452. padding: 3px 0 0 10px;
  453. }
  454.  
  455. #bottom-menu-tabs a {
  456. pointer-events: auto;
  457. }
  458.  
  459. #bottom-menu {
  460. pointer-events: auto;
  461. background: rgba(0, 0, 0, 0.8);
  462. color: #fff;
  463. max-height: unset;
  464. }
  465.  
  466. #bottom-menu.collapse:not(.in) {
  467. display: none !important;
  468. }
  469.  
  470. #bottom-menu-tabs>li>a {
  471. background: rgba(100, 100, 100, 0.5);
  472. border: solid 1px #ccc;
  473. color: #fff;
  474. }
  475.  
  476. #bottom-menu-tabs>li>a:hover {
  477. background: rgba(150, 150, 150, 0.5);
  478. border: solid 1px #ccc;
  479. color: #333;
  480. }
  481.  
  482. #bottom-menu-tabs>li.active>a {
  483. background: #eee;
  484. border: solid 1px #ccc;
  485. color: #333;
  486. }
  487.  
  488. .bottom-menu-btn-close {
  489. font-size: 8pt;
  490. vertical-align: baseline;
  491. padding: 0 0 0 6px;
  492. margin-right: -6px;
  493. }
  494.  
  495. #bottom-menu-contents {
  496. padding: 5px 15px;
  497. max-height: 50vh;
  498. overflow-y: auto;
  499. }
  500.  
  501. #bottom-menu-contents .panel {
  502. color: #333;
  503. }
  504.  
  505.  
  506.  
  507. #atcoder-easy-test-language {
  508. border: none;
  509. background: transparent;
  510. font: inherit;
  511. color: #fff;
  512. }
  513.  
  514. `)
  515. .appendTo("head");
  516. const bottomMenu = $(`<div id="bottom-menu" class="collapse navbar-collapse">`).append(bottomMenuTabs, bottomMenuContents);
  517. $(`<div id="bottom-menu-wrapper" class="navbar navbar-default navbar-fixed-bottom">`)
  518. .append($(`<div class="container">`)
  519. .append(
  520. $(`<div class="navbar-header">`).append(bottomMenuKey),
  521. bottomMenu))
  522. .appendTo("#main-div");
  523. });
  524.  
  525. const menuController = {
  526. addTab(tabId, tabLabel, paneContent, options = {}) {
  527. console.log("addTab: %s (%s)", tabLabel, tabId, paneContent);
  528. const tab = $(`<a id="bottom-menu-tab-${tabId}" href="#" data-target="#bottom-menu-pane-${tabId}" data-toggle="tab">`)
  529. .click(e => {
  530. e.preventDefault();
  531. tab.tab("show");
  532. })
  533. .append(tabLabel);
  534. const tabLi = $(`<li>`).append(tab).appendTo(bottomMenuTabs);
  535. const pane = $(`<div class="tab-pane" id="bottom-menu-pane-${tabId}">`).append(paneContent).appendTo(bottomMenuContents);
  536. console.dirxml(bottomMenuContents);
  537. const controller = {
  538. close() {
  539. tabLi.remove();
  540. pane.remove();
  541. tabs.delete(tab);
  542. if (tabLi.hasClass("active") && tabs.size > 0) {
  543. tabs.values().next().value.tab("show");
  544. }
  545. },
  546.  
  547. show() {
  548. menuController.show();
  549. tab.tab("show");
  550. }
  551. };
  552. tabs.add(tab);
  553. if (options.closeButton) tab.append($(`<a class="bottom-menu-btn-close btn btn-link glyphicon glyphicon-remove">`).click(() => controller.close()));
  554. if (options.active || tabs.size == 1) pane.ready(() => tab.tab("show"));
  555. return controller;
  556. },
  557.  
  558. show() {
  559. if (bottomMenuKey.hasClass("collapsed")) bottomMenuKey.click();
  560. },
  561. };
  562.  
  563. console.info("bottomMenu OK");
  564.  
  565. return menuController;
  566. })();
  567.  
  568. $(() => {
  569. function setLanguage() {
  570. const languageId = $("#select-lang>select").val();
  571. codeRunner.getEnvironment(languageId).then(label => {
  572. $("#atcoder-easy-test-language").css("color", "#fff").val(label);
  573. $("#atcoder-easy-test-run").removeClass("disabled");
  574. $("#atcoder-easy-test-btn-test-all").attr("disabled", false);
  575. }, error => {
  576. $("#atcoder-easy-test-language").css("color", "#f55").val(error);
  577. $("#atcoder-easy-test-run").addClass("disabled");
  578. $("#atcoder-easy-test-btn-test-all").attr("disabled", true);
  579. });
  580. }
  581. setLanguage();
  582.  
  583. async function runTest(input, title = "") {
  584. const uid = Date.now().toString();
  585. title = title ? "Result " + title : "Result";
  586. const content = $(`<div class="container">`)
  587. .html(`
  588. <div class="row"><div class="form-group">
  589. <label class="control-label col-sm-2" for="atcoder-easy-test-${uid}-stdin">Standard Input</label>
  590. <div class="col-sm-8">
  591. <textarea id="atcoder-easy-test-${uid}-stdin" class="form-control" rows="5" readonly></textarea>
  592. </div>
  593. </div></div>
  594. <div class="row"><div class="col-sm-4 col-sm-offset-4">
  595. <div class="panel panel-default"><table class="table table-bordered">
  596. <tr id="atcoder-easy-test-${uid}-row-exit-code">
  597. <th class="text-center">Exit Code</th>
  598. <td id="atcoder-easy-test-${uid}-exit-code" class="text-right"></td>
  599. </tr>
  600. <tr id="atcoder-easy-test-${uid}-row-exec-time">
  601. <th class="text-center">Exec Time</th>
  602. <td id="atcoder-easy-test-${uid}-exec-time" class="text-right"></td>
  603. </tr>
  604. <tr id="atcoder-easy-test-${uid}-row-memory">
  605. <th class="text-center">Memory</th>
  606. <td id="atcoder-easy-test-${uid}-memory" class="text-right"></td>
  607. </tr>
  608. </table></div>
  609. </div></div>
  610. <div class="row"><div class="form-group">
  611. <label class="control-label col-sm-2" for="atcoder-easy-test-${uid}-stdout">Standard Output</label>
  612. <div class="col-sm-8">
  613. <textarea id="atcoder-easy-test-${uid}-stdout" class="form-control" rows="5" readonly></textarea>
  614. </div>
  615. </div></div>
  616. <div class="row"><div class="form-group">
  617. <label class="control-label col-sm-2" for="atcoder-easy-test-${uid}-stderr">Standard Error</label>
  618. <div class="col-sm-8">
  619. <textarea id="atcoder-easy-test-${uid}-stderr" class="form-control" rows="5" readonly></textarea>
  620. </div>
  621. </div></div>
  622. `);
  623. const tab = bottomMenu.addTab("easy-test-result-" + uid, title, content, { active: true, closeButton: true });
  624. $(`#atcoder-easy-test-${uid}-stdin`).val(input);
  625.  
  626. const result = await codeRunner.run($("#select-lang>select").val(), window.getSourceCode(), input);
  627.  
  628. $(`#atcoder-easy-test-${uid}-row-exit-code`).toggleClass("bg-danger", result.exitCode != 0).toggleClass("bg-success", result.exitCode == 0);
  629. $(`#atcoder-easy-test-${uid}-exit-code`).text(result.exitCode);
  630. if ("execTime" in result) $(`#atcoder-easy-test-${uid}-exec-time`).text(result.execTime + " ms");
  631. if ("memory" in result) $(`#atcoder-easy-test-${uid}-memory`).text(result.memory + " KB");
  632. $(`#atcoder-easy-test-${uid}-stdout`).val(result.stdout);
  633. $(`#atcoder-easy-test-${uid}-stderr`).val(result.stderr);
  634.  
  635. result.uid = uid;
  636. result.tab = tab;
  637. return result;
  638. }
  639.  
  640. console.log("bottomMenu", bottomMenu);
  641.  
  642. bottomMenu.addTab("easy-test", "Easy Test", $(`<form id="atcoder-easy-test-container" class="form-horizontal">`)
  643. .html(`
  644. <div class="row">
  645. <div class="col-12 col-md-10">
  646. <div class="form-group">
  647. <label class="control-label col-sm-2">Test Environment</label>
  648. <div class="col-sm-8">
  649. <input id="atcoder-easy-test-language" class="form-control" readonly>
  650. </div>
  651. </div>
  652. </div>
  653. </div>
  654. <div class="row">
  655. <div class="col-12 col-md-10">
  656. <div class="form-group">
  657. <label class="control-label col-sm-2" for="atcoder-easy-test-input">Standard Input</label>
  658. <div class="col-sm-8">
  659. <textarea id="atcoder-easy-test-input" name="input" class="form-control" rows="5"></textarea>
  660. </div>
  661. </div>
  662. </div>
  663. <div class="col-12 col-md-4">
  664. <label class="control-label col-sm-2"></label>
  665. <div class="form-group">
  666. <div class="col-sm-8">
  667. <a id="atcoder-easy-test-run" class="btn btn-primary">Run</a>
  668. </div>
  669. </div>
  670. </div>
  671. </div>
  672. `).ready(() => {
  673. $("#atcoder-easy-test-run").click(() => runTest($("#atcoder-easy-test-input").val()));
  674. $("#select-lang>select").on("change", () => setLanguage());
  675. }), { active: true });
  676.  
  677. const testfuncs = [];
  678.  
  679. const testcases = $(".lang>span:nth-child(1) .div-btn-copy+pre[id]").toArray();
  680. for (let i = 0; i < testcases.length; i += 2) {
  681. const input = $(testcases[i]), output = $(testcases[i+1]);
  682. const testfunc = async () => {
  683. const title = input.closest(".part").find("h3")[0].childNodes[0].data;
  684. const result = await runTest(input.text(), title);
  685. if (result.status == "OK") {
  686. if (result.stdout.trim() == output.text().trim()) {
  687. $(`#atcoder-easy-test-${result.uid}-stdout`).addClass("bg-success");
  688. result.status = "AC";
  689. } else {
  690. result.status = "WA";
  691. }
  692. }
  693. return result;
  694. };
  695. testfuncs.push(testfunc);
  696.  
  697. const runButton = $(`<a class="btn btn-primary btn-sm" style="vertical-align: top; margin-left: 0.5em">`)
  698. .text("Run")
  699. .click(async () => {
  700. await testfunc();
  701. if ($("#bottom-menu-key").hasClass("collapsed")) $("#bottom-menu-key").click();
  702. });
  703. input.closest(".part").find(".btn-copy").eq(0).after(runButton);
  704. }
  705.  
  706. const testAllResultRow = $(`<div class="row">`);
  707. const testAllButton = $(`<a id="atcoder-easy-test-btn-test-all" class="btn btn-default btn-sm" style="margin-left: 5px">`)
  708. .text("Test All Samples")
  709. .click(async () => {
  710. if (testAllButton.attr("disabled")) throw new Error("Button is disabled");
  711. const statuses = testfuncs.map(_ => $(`<div class="label label-default" style="margin: 3px">`).text("WJ..."));
  712. const progress = $(`<div class="progress-bar">`).text(`0 / ${testfuncs.length}`);
  713. let finished = 0;
  714. const closeButton = $(`<button type="button" class="close" data-dismiss="alert" aria-label="close">`)
  715. .append($(`<span aria-hidden="true">`).text("\xd7"));
  716. const resultAlert = $(`<div class="alert alert-dismissible">`)
  717. .append(closeButton)
  718. .append($(`<div class="progress">`).append(progress))
  719. .append(...statuses)
  720. .appendTo(testAllResultRow);
  721. const results = await Promise.all(testfuncs.map(async (testfunc, i) => {
  722. const result = await testfunc();
  723. finished++;
  724. progress.text(`${finished} / ${statuses.length}`).css("width", `${finished/statuses.length*100}%`);
  725. statuses[i].toggleClass("label-success", result.status == "AC").toggleClass("label-warning", result.status != "AC").text(result.status).click(() => result.tab.show()).css("cursor", "pointer");
  726. return result;
  727. }));
  728. if (results.every(({status}) => status == "AC")) {
  729. resultAlert.addClass("alert-success");
  730. } else {
  731. resultAlert.addClass("alert-warning");
  732. }
  733. closeButton.click(() => {
  734. for (const {tab} of results) {
  735. tab.close();
  736. }
  737. });
  738. });
  739. $("#submit").after(testAllButton).closest("form").append(testAllResultRow);
  740.  
  741. console.info("view OK");
  742. });
  743.  
  744. })();