AtCoder Easy Test

Make testing sample cases easy

Ekde 2020/11/20. Vidu La ĝisdata versio.

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