AtCoder Easy Test

Make testing sample cases easy

Version vom 14.11.2020. Aktuellste Version

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