- // ==UserScript==
- // @name AtCoder Easy Test
- // @namespace http://atcoder.jp/
- // @version 1.8.1
- // @description Make testing sample cases easy
- // @author magurofly
- // @match https://atcoder.jp/contests/*/tasks/*
- // @grant unsafeWindow
- // ==/UserScript==
-
- // This script uses variables from page below:
- // * `$`
- // * `getSourceCode`
- // * `csrfToken`
-
- // This scripts consists of three modules:
- // * bottom menu
- // * code runner
- // * view
-
- // This scripts may load scripts below to run code:
- // * https://cdn.jsdelivr.net/gh/pythonpad/brython-runner/lib/brython-runner.bundle.js
-
- (function script() {
-
- const VERSION = "1.8.1";
-
- if (typeof unsafeWindow !== "undefined") {
- console.log(unsafeWindow);
- unsafeWindow.eval(`(${script})();`);
- console.log("Script run in unsafeWindow");
- return;
- }
- if (typeof window === "undefined") {
- this.window = this.unsafeWindow;
- }
- const $ = window.$;
- const getSourceCode = window.getSourceCode;
- const csrfToken = window.csrfToken;
-
- const $id = document.getElementById.bind(document);
- const $select = document.querySelector.bind(document);
- const $selectAll = document.querySelectorAll.bind(document);
- const $create = (tagName, attrs = {}, children = []) => {
- const e = document.createElement(tagName);
- for (const name in attrs) e.setAttribute(name, attrs[name]);
- for (const child of children) e.appendChild(child);
- return e;
- };
-
- // -- code saver --
- const codeSaver = {
- LIMIT: 10,
- get() {
- let data = localStorage.AtCoderEasyTest$lastCode;
- try {
- if (typeof data == "string") {
- data = JSON.parse(data);
- } else {
- data = [];
- }
- } catch(e) {
- data = [{
- path: localStorage.AtCoderEasyTest$lastPage,
- code: data,
- }];
- }
- return data;
- },
- set(data) {
- localStorage.AtCoderEasyTest$lastCode = JSON.stringify(data);
- },
- // @param code to save
- save(code) {
- let data = this.get();
- const idx = data.findIndex(({path}) => path == location.pathname);
- if (idx != -1) data.splice(idx, idx + 1);
- data.push({
- path: location.pathname,
- code,
- });
- while (data.length > this.LIMIT) data.shift();
- this.set(data);
- },
- // @return promise(code)
- restore() {
- const data = this.get();
- const idx = data.findIndex(({path}) => path == location.pathname);
- if (idx == -1 || !(data[idx] instanceof Object)) return Promise.reject(`no saved code found for ${location.pathname}`);
- return Promise.resolve(data[idx].code);
- },
- };
-
- // -- code runner --
- const codeRunner = (function() {
- 'use strict';
-
- function buildParams(data) {
- return Object.entries(data).map(([key, value]) => encodeURIComponent(key) + "=" + encodeURIComponent(value)).join("&");
- }
-
- function sleep(ms) {
- return new Promise(done => setTimeout(done, ms));
- }
-
- class CodeRunner {
- constructor(label, site) {
- this.label = `${label} [${site}]`;
- }
-
- async test(sourceCode, input, supposedOutput, options) {
- const result = await this.run(sourceCode, input);
- if (result.status != "OK" || typeof supposedOutput !== "string") return result;
- let output = result.stdout || "";
-
- if (options.trim) {
- supposedOutput = supposedOutput.trim();
- output = output.trim();
- }
-
- let equals = (x, y) => x === y;
-
- if ("allowableError" in options) {
- const floatPattern = /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/;
- const superEquals = equals;
- equals = (x, y) => {
- if (floatPattern.test(x) && floatPattern.test(y)) return Math.abs(parseFloat(x) - parseFloat(y)) <= options.allowableError;
- return superEquals(x, y);
- }
- }
-
- if (options.split) {
- const superEquals = equals;
- equals = (x, y) => {
- x = x.split(/\s+/);
- y = y.split(/\s+/);
- if (x.length != y.length) return false;
- const len = x.length;
- for (let i = 0; i < len; i++) {
- if (!superEquals(x[i], y[i])) return false;
- }
- return true;
- }
- }
-
- result.status = equals(output, supposedOutput) ? "AC" : "WA";
-
- return result;
- }
- }
-
- class CustomRunner extends CodeRunner {
- constructor(label, run) {
- super(label, "Browser");
- this.run = run;
- }
- }
-
- class WandboxRunner extends CodeRunner {
- constructor(name, label, options = {}) {
- super(label, "Wandbox");
- this.name = name;
- this.options = options;
- }
-
- run(sourceCode, input) {
- let options = this.options;
- if (typeof options == "function") options = options(sourceCode, input);
- return this.request(Object.assign(JSON.stringify({
- compiler: this.name,
- code: sourceCode,
- stdin: input,
- }), this.options));
- }
-
- async request(body) {
- const startTime = Date.now();
- let res;
- try {
- res = await fetch("https://wandbox.org/api/compile.json", {
- method: "POST",
- mode: "cors",
- headers: {
- "Content-Type": "application/json",
- },
- body,
- }).then(r => r.json());
- } catch (error) {
- console.error(error);
- return {
- status: "IE",
- stderr: error,
- };
- }
- const endTime = Date.now();
-
- const result = {
- status: "OK",
- exitCode: res.status,
- execTime: endTime - startTime,
- stdout: res.program_output,
- stderr: res.program_error,
- };
- if (res.status != 0) {
- if (res.signal) {
- result.exitCode += " (" + res.signal + ")";
- }
- result.stdout = (res.compiler_output || "") + (result.stdout || "");
- result.stderr = (res.compiler_error || "") + (result.stderr || "");
- if (res.compiler_output || res.compiler_error) {
- result.status = "CE";
- } else {
- result.status = "RE";
- }
- }
-
- return result;
- }
- }
-
- class PaizaIORunner extends CodeRunner {
- constructor(name, label) {
- super(label, "PaizaIO");
- this.name = name;
- }
-
- async run(sourceCode, input) {
- let id, status, error;
- try {
- const res = await fetch("https://api.paiza.io/runners/create?" + buildParams({
- source_code: sourceCode,
- language: this.name,
- input,
- longpoll: true,
- longpoll_timeout: 10,
- api_key: "guest",
- }), {
- method: "POST",
- mode: "cors",
- }).then(r => r.json());
- id = res.id;
- status = res.status;
- error = res.error;
- } catch (error) {
- return {
- status: "IE",
- stderr: error,
- };
- }
-
- while (status == "running") {
- const res = await (await fetch("https://api.paiza.io/runners/get_status?" + buildParams({
- id,
- api_key: "guest",
- }), {
- mode: "cors",
- })).json();
- status = res.status;
- error = res.error;
- }
-
- const res = await fetch("https://api.paiza.io/runners/get_details?" + buildParams({
- id,
- api_key: "guest",
- }), {
- mode: "cors",
- }).then(r => r.json());
-
- const result = {
- exitCode: res.exit_code,
- execTime: +res.time * 1e3,
- memory: +res.memory * 1e-3,
- };
-
- if (res.build_result == "failure") {
- result.status = "CE";
- result.exitCode = res.build_exit_code;
- result.stdout = res.build_stdout;
- result.stderr = res.build_stderr;
- } else {
- result.status = (res.result == "timeout") ? "TLE" : (res.result == "failure") ? "RE" : "OK";
- result.exitCode = res.exit_code;
- result.stdout = res.stdout;
- result.stderr = res.stderr;
- }
-
- return result;
- }
- }
-
- class WandboxCppRunner extends WandboxRunner {
- async run(sourceCode, input) {
- const ACLBase = "https://cdn.jsdelivr.net/gh/atcoder/ac-library/";
- const files = new Map();
- const includeHeader = async source => {
- const pattern = /^#\s*include\s*[<"]atcoder\/([^>"]+)[>"]/gm;
- const loaded = [];
- let match;
- while (match = pattern.exec(source)) {
- const file = "atcoder/" + match[1];
- if (files.has(file)) continue;
- files.set(file, null);
- loaded.push([file, fetch(ACLBase + file, { mode: "cors", cache: "force-cache", }).then(r => r.text())]);
- }
- const included = await Promise.all(loaded.map(async ([file, r]) => {
- const source = await r;
- files.set(file, source);
- return source;
- }));
- for (const source of included) {
- await includeHeader(source);
- }
- };
- await includeHeader(sourceCode);
- const codes = [];
- for (const [file, code] of files) {
- codes.push({ file, code, });
- }
- let options = this.options;
- if (typeof options == "function") options = options(sourceCode, input);
- return await this.request(JSON.stringify(Object.assign({
- compiler: this.name,
- code: sourceCode,
- stdin: input,
- codes,
- "compiler-option-raw": "-I.",
- }, options)));
- }
- }
-
- let waitAtCoderCustomTest = Promise.resolve();
- const AtCoderCustomTestBase = location.href.replace(/\/tasks\/.+$/, "/custom_test");
- const AtCoderCustomTestResultAPI = AtCoderCustomTestBase + "/json?reload=true";
- const AtCoderCustomTestSubmitAPI = AtCoderCustomTestBase + "/submit/json";
- class AtCoderRunner extends CodeRunner {
- constructor(languageId, label) {
- super(label, "AtCoder");
- this.languageId = languageId;
- }
-
- async run(sourceCode, input) {
- const promise = this.submit(sourceCode, input);
- waitAtCoderCustomTest = promise;
- return await promise;
- }
-
- async submit(sourceCode, input) {
- try {
- await waitAtCoderCustomTest;
- } catch (error) {
- console.error(error);
- }
-
- const error = await fetch(AtCoderCustomTestSubmitAPI, {
- method: "POST",
- credentials: "include",
- headers: {
- "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
- },
- body: buildParams({
- "data.LanguageId": this.languageId,
- sourceCode,
- input,
- csrf_token: csrfToken,
- }),
- }).then(r => r.text());
-
- if (error) {
- throw new Error(error)
- }
-
- await sleep(100);
-
- for (;;) {
- const data = await fetch(AtCoderCustomTestResultAPI, {
- method: "GET",
- credentials: "include",
- }).then(r => r.json());
-
- if (!("Result" in data)) continue;
- const result = data.Result;
-
- if ("Interval" in data) {
- await sleep(data.Interval);
- continue;
- }
-
- return {
- status: (result.ExitCode == 0) ? "OK" : (result.TimeConsumption == -1) ? "CE" : "RE",
- exitCode: result.ExitCode,
- execTime: result.TimeConsumption,
- memory: result.MemoryConsumption,
- stdout: data.Stdout,
- stderr: data.Stderr,
- };
- }
- }
- }
-
- let brythonRunnerLoaded = false;
- const brythonRunner = new CustomRunner("Brython", async (sourceCode, input) => {
- if (!brythonRunnerLoaded) {
- await new Promise((resolve) => {
- const script = $create("script");
- script.src = "https://cdn.jsdelivr.net/gh/pythonpad/brython-runner/lib/brython-runner.bundle.js";
- script.onload = () => {
- brythonRunnerLoaded = true;
- resolve();
- };
- document.head.appendChild(script);
- });
- }
-
- let stdout = "";
- let stderr = "";
- let stdinOffset = 0;
- const runner = new BrythonRunner({
- stdout: { write(content) { stdout += content; }, flush() {} },
- stderr: { write(content) { stderr += content; }, flush() {} },
- stdin: { async readline() {
- let index = input.indexOf("\n", stdinOffset) + 1;
- if (index == 0) index = input.length;
- const text = input.slice(stdinOffset, index);
- stdinOffset = index;
- return text;
- } },
- });
-
- const timeStart = Date.now();
- await runner.runCode(sourceCode);
- const timeEnd = Date.now();
-
- return {
- status: "OK",
- exitCode: 0,
- execTime: (timeEnd - timeStart),
- stdout,
- stderr,
- };
- });
-
- const runners = {
- 4001: [new WandboxRunner("gcc-10.1.0-c", "C (GCC 10.1.0)")],
- 4002: [new PaizaIORunner("c", "C (C17 / Clang 10.0.0)", )],
- 4003: [new WandboxCppRunner("gcc-10.1.0", "C++ (GCC 10.1.0)", {options: "warning,boost-1.73.0-gcc-9.2.0,gnu++17"})],
- 4004: [new WandboxCppRunner("clang-10.0.0", "C++ (Clang 10.0.0)", {options: "warning,boost-nothing-clang-10.0.0,c++17"})],
- 4006: [
- new PaizaIORunner("python3", "Python (3.8.2)"),
- brythonRunner,
- ],
- 4007: [new PaizaIORunner("bash", "Bash (5.0.17)")],
- 4010: [new WandboxRunner("csharp", "C# (.NET Core 6.0.100-alpha.1.20562.2)")],
- 4011: [new WandboxRunner("mono-head", "C# (Mono-mcs 5.19.0.0)")],
- 4013: [new PaizaIORunner("clojure", "Clojure (1.10.1-1)")],
- 4017: [new PaizaIORunner("d", "D (LDC 1.23.0)")],
- 4020: [new PaizaIORunner("erlang", "Erlang (10.6.4)")],
- 4021: [new PaizaIORunner("elixir", "Elixir (1.10.4)")],
- 4022: [new PaizaIORunner("fsharp", "F# (Interactive 4.0)")],
- 4023: [new PaizaIORunner("fsharp", "F# (Interactive 4.0)")],
- 4026: [new WandboxRunner("go-1.14.1", "Go (1.14.1)")],
- 4027: [new WandboxRunner("ghc-head", "Haskell (GHC 8.7.20181121)")],
- 4030: [new PaizaIORunner("javascript", "JavaScript (Node.js 12.18.3)")],
- 4032: [new PaizaIORunner("kotlin", "Kotlin (1.4.0)")],
- 4033: [new WandboxRunner("lua-5.3.4", "Lua (Lua 5.3.4)")],
- 4034: [new WandboxRunner("luajit-head", "Lua (LuaJIT 2.1.0-beta3)")],
- 4036: [new WandboxRunner("nim-1.0.6", "Nim (1.0.6)")],
- 4037: [new PaizaIORunner("objective-c", "Objective-C (Clang 10.0.0)")],
- 4039: [new WandboxRunner("ocaml-head", "OCaml (4.13.0+dev0-2020-10-19)")],
- 4041: [new WandboxRunner("fpc-3.0.2", "Pascal (FPC 3.0.2)")],
- 4042: [new PaizaIORunner("perl", "Perl (5.30.0)")],
- 4044: [
- new PaizaIORunner("php", "PHP (7.4.10)"),
- new WandboxRunner("php-7.3.3", "PHP (7.3.3)"),
- ],
- 4046: [new WandboxRunner("pypy-head", "PyPy2 (7.3.4-alpha0)")],
- 4047: [new WandboxRunner("pypy-7.2.0-3", "PyPy3 (7.2.0)")],
- 4049: [
- new PaizaIORunner("ruby", "Ruby (2.7.1)"),
- new WandboxRunner("ruby-head", "Ruby (HEAD 3.0.0dev)"),
- new WandboxRunner("ruby-2.7.0-preview1", "Ruby (2.7.0-preview1)"),
- ],
- 4050: [
- new AtCoderRunner(4050, "Rust (1.42.0)"),
- new WandboxRunner("rust-head", "Rust (1.37.0-dev)"),
- new PaizaIORunner("rust", "Rust (1.43.0)"),
- ],
- 4051: [new PaizaIORunner("scala", "Scala (2.13.3)")],
- 4053: [new PaizaIORunner("scheme", "Scheme (Gauche 0.9.6)")],
- 4055: [new PaizaIORunner("swift", "Swift (5.2.5)")],
- 4056: [new CustomRunner("Text",
- async (sourceCode, input) => {
- return {
- status: "OK",
- exitCode: 0,
- stdout: sourceCode,
- };
- }
- )],
- 4058: [new PaizaIORunner("vb", "Visual Basic (.NET Core 4.0.1)")],
- 4061: [new PaizaIORunner("cobol", "COBOL - Free (OpenCOBOL 2.2.0)")],
- 4101: [new WandboxCppRunner("gcc-9.2.0", "C++ (GCC 9.2.0)")],
- 4102: [new WandboxCppRunner("clang-10.0.0", "C++ (Clang 10.0.0)")],
- };
-
- for (const e of $selectAll("#select-lang option[value]")) {
- const languageId = e.value;
- if (!(languageId in runners)) runners[languageId] = [];
- if (runners[languageId].some(runner => runner instanceof AtCoderRunner)) continue;
- runners[languageId].push(new AtCoderRunner(languageId, e.textContent));
- }
-
- console.info("codeRunner OK");
-
- return {
- run(languageId, index, sourceCode, input, supposedOutput = null, options = { trim: true, split: true, }) {
- if (!(languageId in runners)) return Promise.reject("language not supported");
-
- // save last code
- codeSaver.save(sourceCode);
-
- // run
- return runners[languageId][index].test(sourceCode, input, supposedOutput, options);
- },
-
- getEnvironment(languageId) {
- if (!(languageId in runners)) return Promise.reject("language not supported");
- return Promise.resolve(runners[languageId].map(runner => runner.label));
- },
- };
- })();
-
-
- // -- bottom menu --
- const bottomMenu = (function () {
- 'use strict';
-
- const tabs = new Set();
-
- const bottomMenuKey = $(`<button id="bottom-menu-key" type="button" class="navbar-toggle collapsed glyphicon glyphicon-menu-down" data-toggle="collapse" data-target="#bottom-menu">`);
- const bottomMenuTabs = $(`<ul id="bottom-menu-tabs" class="nav nav-tabs">`);
- const bottomMenuContents = $(`<div id="bottom-menu-contents" class="tab-content">`);
-
- $(() => {
- const style = $create("style");
- style.textContent = `
-
- #bottom-menu-wrapper {
- background: transparent;
- border: none;
- pointer-events: none;
- padding: 0;
- }
-
- #bottom-menu-wrapper>.container {
- position: absolute;
- bottom: 0;
- width: 100%;
- padding: 0;
- }
-
- #bottom-menu-wrapper>.container>.navbar-header {
- float: none;
- }
-
- #bottom-menu-key {
- display: block;
- float: none;
- margin: 0 auto;
- padding: 10px 3em;
- border-radius: 5px 5px 0 0;
- background: #000;
- opacity: 0.5;
- color: #FFF;
- cursor: pointer;
- pointer-events: auto;
- text-align: center;
- }
-
- @media screen and (max-width: 767px) {
- #bottom-menu-key {
- opacity: 0.25;
- }
- }
-
- #bottom-menu-key.collapsed:before {
- content: "\\e260";
- }
-
- #bottom-menu-tabs {
- padding: 3px 0 0 10px;
- cursor: n-resize;
- }
-
- #bottom-menu-tabs a {
- pointer-events: auto;
- }
-
- #bottom-menu {
- pointer-events: auto;
- background: rgba(0, 0, 0, 0.8);
- color: #fff;
- max-height: unset;
- }
-
- #bottom-menu.collapse:not(.in) {
- display: none !important;
- }
-
- #bottom-menu-tabs>li>a {
- background: rgba(150, 150, 150, 0.5);
- color: #000;
- border: solid 1px #ccc;
- filter: brightness(0.75);
- }
-
- #bottom-menu-tabs>li>a:hover {
- background: rgba(150, 150, 150, 0.5);
- border: solid 1px #ccc;
- color: #111;
- filter: brightness(0.9);
- }
-
- #bottom-menu-tabs>li.active>a {
- background: #eee;
- border: solid 1px #ccc;
- color: #333;
- filter: none;
- }
-
- .bottom-menu-btn-close {
- font-size: 8pt;
- vertical-align: baseline;
- padding: 0 0 0 6px;
- margin-right: -6px;
- }
-
- #bottom-menu-contents {
- padding: 5px 15px;
- max-height: 50vh;
- overflow-y: auto;
- }
-
- #bottom-menu-contents .panel {
- color: #333;
- }
-
- `;
- document.head.appendChild(style);
- const bottomMenu = $(`<div id="bottom-menu" class="collapse navbar-collapse">`).append(bottomMenuTabs, bottomMenuContents);
- $(`<div id="bottom-menu-wrapper" class="navbar navbar-default navbar-fixed-bottom">`)
- .append($(`<div class="container">`)
- .append(
- $(`<div class="navbar-header">`).append(bottomMenuKey),
- bottomMenu))
- .appendTo("#main-div");
-
- let resizeStart = null;
- bottomMenuTabs.on({
- mousedown({target, pageY}) {
- if (target.id != "bottom-menu-tabs") return;
- resizeStart = {y: pageY, height: bottomMenuContents.height()};
- },
- mousemove(e) {
- if (!resizeStart) return;
- e.preventDefault();
- bottomMenuContents.height(resizeStart.height - (e.pageY - resizeStart.y));
- },
- });
- document.addEventListener("mouseup", () => { resizeStart = null; });
- document.addEventListener("mouseleave", () => { resizeStart = null; });
- });
-
- const menuController = {
- addTab(tabId, tabLabel, paneContent, options = {}) {
- console.log("addTab: %s (%s)", tabLabel, tabId, paneContent);
- const tab = $(`<a id="bottom-menu-tab-${tabId}" href="#" data-target="#bottom-menu-pane-${tabId}" data-toggle="tab">`)
- .click(e => {
- e.preventDefault();
- tab.tab("show");
- })
- .append(tabLabel);
- const tabLi = $(`<li>`).append(tab).appendTo(bottomMenuTabs);
- const pane = $(`<div class="tab-pane" id="bottom-menu-pane-${tabId}">`).append(paneContent).appendTo(bottomMenuContents);
- console.dirxml(bottomMenuContents);
- const controller = {
- close() {
- tabLi.remove();
- pane.remove();
- tabs.delete(tab);
- if (tabLi.hasClass("active") && tabs.size > 0) {
- tabs.values().next().value.tab("show");
- }
- },
-
- show() {
- menuController.show();
- tab.tab("show");
- },
-
- set color(color) {
- tab.css("background-color", color);
- },
- };
- tabs.add(tab);
- if (options.closeButton) tab.append($(`<a class="bottom-menu-btn-close btn btn-link glyphicon glyphicon-remove">`).click(() => controller.close()));
- if (options.active || tabs.size == 1) pane.ready(() => tab.tab("show"));
- return controller;
- },
-
- show() {
- if (bottomMenuKey.hasClass("collapsed")) bottomMenuKey.click();
- },
-
- toggle() {
- bottomMenuKey.click();
- },
- };
-
- console.info("bottomMenu OK");
-
- return menuController;
- })();
-
- $(() => {
- // returns [{input, output, anchor}]
- function getTestCases() {
- const selectors = [
- ["#task-statement p+pre.literal-block", ".section"], // utpc2011_1
- ["#task-statement pre.source-code-for-copy", ".part"],
- ["#task-statement .lang>*:nth-child(1) .div-btn-copy+pre", ".part"],
- ["#task-statement .div-btn-copy+pre", ".part"],
- ["#task-statement>.part pre.linenums", ".part"], // abc003_4
- ["#task-statement>.part:not(.io-style)>h3+section>pre", ".part"],
- ["#task-statement pre", ".part"],
- ];
-
- for (const [selector, closestSelector] of selectors) {
- const e = $selectAll(selector);
- if (e.length == 0) continue;
- const testcases = [];
- for (let i = 0; i < e.length; i += 2) {
- const container = e[i].closest(closestSelector) || e[i].parentElement;
- testcases.push({
- input: (e[i]||{}).textContent,
- output: (e[i+1]||{}).textContent,
- anchor: container.querySelector("h3"),
- });
- }
- return testcases;
- }
-
- return [];
- }
-
- async function runTest(title, input, output = null) {
- const uid = Date.now().toString();
- title = title ? "Result " + title : "Result";
- const content = $create("div", { class: "container" });
- content.innerHTML = `
- <div class="row">
- <div class="col-xs-12 ${(output == null) ? "" : "col-sm-6"}"><div class="form-group">
- <label class="control-label col-xs-12" for="atcoder-easy-test-${uid}-stdin">Standard Input</label>
- <div class="col-xs-12">
- <textarea id="atcoder-easy-test-${uid}-stdin" class="form-control" rows="3" readonly></textarea>
- </div>
- </div></div>${(output == null) ? "" : `
- <div class="col-xs-12 col-sm-6"><div class="form-group">
- <label class="control-label col-xs-12" for="atcoder-easy-test-${uid}-expected">Expected Output</label>
- <div class="col-xs-12">
- <textarea id="atcoder-easy-test-${uid}-expected" class="form-control" rows="3" readonly></textarea>
- </div>
- </div></div>
- `}
- </div>
- <div class="row"><div class="col-sm-6 col-sm-offset-3">
- <div class="panel panel-default"><table class="table table-condensed">
- <tr>
- <th class="text-center">Exit Code</th>
- <th class="text-center">Exec Time</th>
- <th class="text-center">Memory</th>
- </tr>
- <tr>
- <td id="atcoder-easy-test-${uid}-exit-code" class="text-center"></td>
- <td id="atcoder-easy-test-${uid}-exec-time" class="text-center"></td>
- <td id="atcoder-easy-test-${uid}-memory" class="text-center"></td>
- </tr>
- </table></div>
- </div></div>
- <div class="row">
- <div class="col-xs-12 col-md-6"><div class="form-group">
- <label class="control-label col-xs-12" for="atcoder-easy-test-${uid}-stdout">Standard Output</label>
- <div class="col-xs-12">
- <textarea id="atcoder-easy-test-${uid}-stdout" class="form-control" rows="5" readonly></textarea>
- </div>
- </div></div>
- <div class="col-xs-12 col-md-6"><div class="form-group">
- <label class="control-label col-xs-12" for="atcoder-easy-test-${uid}-stderr">Standard Error</label>
- <div class="col-xs-12">
- <textarea id="atcoder-easy-test-${uid}-stderr" class="form-control" rows="5" readonly></textarea>
- </div>
- </div></div>
- </div>
- `;
- const tab = bottomMenu.addTab("easy-test-result-" + uid, title, content, { active: true, closeButton: true });
- $id(`atcoder-easy-test-${uid}-stdin`).value = input;
- if (output != null) $id(`atcoder-easy-test-${uid}-expected`).value = output;
-
- const options = { trim: true, split: true, };
- if ($id("atcoder-easy-test-allowable-error-check").checked) {
- options.allowableError = parseFloat($id("atcoder-easy-test-allowable-error").value);
- }
-
- const result = await codeRunner.run($select("#select-lang>select").value, +$id("atcoder-easy-test-language").value, window.getSourceCode(), input, output, options);
-
- if (result.status == "AC") {
- tab.color = "#dff0d8";
- $id(`atcoder-easy-test-${uid}-stdout`).style.backgroundColor = "#dff0d8";
- } else if (result.status != "OK") {
- tab.color = "#fcf8e3";
- if (result.status == "WA") $id(`atcoder-easy-test-${uid}-stdout`).style.backgroundColor = "#fcf8e3";
- }
-
- const eExitCode = $id(`atcoder-easy-test-${uid}-exit-code`);
- eExitCode.textContent = result.exitCode;
- eExitCode.classList.toggle("bg-success", result.exitCode == 0);
- eExitCode.classList.toggle("bg-danger", result.exitCode != 0);
- if ("execTime" in result) $id(`atcoder-easy-test-${uid}-exec-time`).textContent = result.execTime + " ms";
- if ("memory" in result) $id(`atcoder-easy-test-${uid}-memory`).textContent = result.memory + " KB";
- $id(`atcoder-easy-test-${uid}-stdout`).value = result.stdout || "";
- $id(`atcoder-easy-test-${uid}-stderr`).value = result.stderr || "";
-
- result.uid = uid;
- result.tab = tab;
- return result;
- }
-
- console.log("bottomMenu", bottomMenu);
-
- bottomMenu.addTab("easy-test", "Easy Test", $(`<form id="atcoder-easy-test-container" class="form-horizontal">`)
- .html(`
- <small style="position: absolute; display: block; bottom: 0; right: 0; padding: 1% 4%; width: 95%; text-align: right;">AtCoder Easy Test v${VERSION}</small>
- <div class="row">
- <div class="col-xs-12 col-lg-8">
- <div class="form-group">
- <label class="control-label col-sm-2">Test Environment</label>
- <div class="col-sm-10">
- <select class="form-control" id="atcoder-easy-test-language"></select>
- </div>
- </div>
- <div class="form-group">
- <label class="control-label col-sm-2" for="atcoder-easy-test-input">Standard Input</label>
- <div class="col-sm-10">
- <textarea id="atcoder-easy-test-input" name="input" class="form-control" rows="3"></textarea>
- </div>
- </div>
- </div>
- <div class="col-xs-12 col-lg-4">
- <details close>
- <summary>Expected Output</summary>
- <div class="form-group">
- <label class="control-label col-sm-2" for="atcoder-easy-test-allowable-error-check">Allowable Error</label>
- <div class="col-sm-10">
- <div class="input-group">
- <span class="input-group-addon">
- <input id="atcoder-easy-test-allowable-error-check" type="checkbox" checked>
- </span>
- <input id="atcoder-easy-test-allowable-error" type="text" class="form-control" value="1e-6">
- </div>
- </div>
- </div>
- <div class="form-group">
- <label class="control-label col-sm-2" for="atcoder-easy-test-output">Expected Output</label>
- <div class="col-sm-10">
- <textarea id="atcoder-easy-test-output" name="output" class="form-control" rows="3"></textarea>
- </div>
- </div>
- </details>
- </div>
- <div class="col-xs-12">
- <div class="col-xs-11 col-xs-offset=1">
- <div class="form-group">
- <a id="atcoder-easy-test-run" class="btn btn-primary">Run</a>
- </div>
- </div>
- </div>
- </div>
- <style>
- #atcoder-easy-test-language {
- border: none;
- background: transparent;
- font: inherit;
- color: #fff;
- }
- #atcoder-easy-test-language option {
- border: none;
- color: #333;
- font: inherit;
- }
- </style>
- `).ready(() => {
- $id("atcoder-easy-test-run").addEventListener("click", () => {
- const title = "";
- const input = $id("atcoder-easy-test-input").value;
- const output = $id("atcoder-easy-test-output").value;
- runTest(title, input, output || null);
- });
- $("#select-lang>select").change(() => setLanguage()); //NOTE: This event is only for jQuery; do not replce with Vanilla
- $id("atcoder-easy-test-allowable-error").disabled = this.checked;
- $id("atcoder-easy-test-allowable-error-check").addEventListener("change", e => { $id("atcoder-easy-test-allowable-error").disabled = !e.target.checked; });
-
- async function setLanguage() {
- const languageId = $select("#select-lang>select").value;
- const eTestLanguage = $id("atcoder-easy-test-language");
- while (eTestLanguage.firstChild) eTestLanguage.removeChild(eTestLanguage.firstChild);
- try {
- const labels = await codeRunner.getEnvironment(languageId);
- console.log(`language: ${labels[0]} (${languageId})`);
- labels.forEach((label, index) => {
- const option = $create("option", {value: index});
- option.textContent = label;
- eTestLanguage.appendChild(option);
- });
- $id("atcoder-easy-test-run").classList.remove("disabled");
- $id("atcoder-easy-test-btn-test-all").disabled = false;
- } catch (error) {
- console.log(`language: ? (${languageId})`);
- const option = $create("option", { "class": "fg-danger" });
- option.textContent = error;
- eTestLanguage.appendChild(option);
- $id("atcoder-easy-test-run").classList.add("disabled");
- $id("atcoder-easy-test-btn-test-all").disabled = true;
- }
- }
-
- setLanguage();
- }), { active: true });
-
- try {
- const testfuncs = [];
- const runButtons = [];
-
- const testcases = getTestCases();
- for (const {input, output, anchor} of testcases) {
- const testfunc = async () => {
- const title = anchor.childNodes[0].data;
- const result = await runTest(title, input, output);
- if (result.status == "OK" || result.status == "AC") {
- $id(`atcoder-easy-test-${result.uid}-stdout`).classList.add("bg-success");
- }
- return result;
- };
- testfuncs.push(testfunc);
-
- const runButton = $(`<a class="btn btn-primary btn-sm" style="vertical-align: top; margin-left: 0.5em">`)
- .text("Run")
- .click(async () => {
- await testfunc();
- if ($id("bottom-menu-key").classList.contains("collapsed")) $id("bottom-menu-key").click();
- });
- anchor.appendChild(runButton[0]);
- runButtons.push(runButton);
- }
-
- const restoreLastPlayButton = $(`<a id="atcoder-easy-test-restore-last-play" class="btn btn-danger btn-sm">`)
- .text("Restore Last Play")
- .click(async () => {
- try {
- const lastCode = await codeSaver.restore();
- if (confirm("Your current code will be replaced. Are you sure?")) {
- $('.plain-textarea').val(lastCode);
- $('.editor').data('editor').doc.setValue(lastCode);
- }
- } catch (reason) {
- alert(reason);
- return;
- }
- })
- .appendTo(".editor-buttons");
-
- const fnTestAll = async () => {
- const statuses = testfuncs.map(_ => $(`<div class="label label-default" style="margin: 3px">`).text("WJ..."));
- const progress = $(`<div class="progress-bar">`).text(`0 / ${testfuncs.length}`);
- let finished = 0;
- const closeButton = $(`<button type="button" class="close" data-dismiss="alert" aria-label="close">`)
- .append($(`<span aria-hidden="true">`).text("\xd7"));
- const resultAlert = $(`<div class="alert alert-dismissible">`)
- .append(closeButton)
- .append($(`<div class="progress">`).append(progress))
- .append(...statuses)
- .prependTo(testAllResultRow);
- const results = await Promise.all(testfuncs.map(async (testfunc, i) => {
- const result = await testfunc();
- finished++;
- progress.text(`${finished} / ${statuses.length}`).css("width", `${finished/statuses.length*100}%`);
- statuses[i].toggleClass("label-success", result.status == "AC").toggleClass("label-warning", result.status != "AC").text(result.status).click(() => result.tab.show()).css("cursor", "pointer");
- return result;
- }));
- if (results.every(({status}) => status == "AC")) {
- resultAlert.addClass("alert-success");
- } else {
- resultAlert.addClass("alert-warning");
- }
- closeButton.click(() => {
- for (const {tab} of results) {
- tab.close();
- }
- });
- return results;
- };
-
- const testAllResultRow = $(`<div class="row">`);
- const testAndSubmitButton = $(`<a id="atcoder-easy-test-btn-test-and-submit" class="btn btn-info btn" style="margin-left: 1rem" title="Ctrl+Enter" data-toggle="tooltip">`)
- .text("Test & Submit")
- .click(async () => {
- if (testAndSubmitButton.hasClass("disabled")) throw new Error("Button is disabled");
- testAndSubmitButton.addClass("disabled");
- try {
- const results = await fnTestAll();
- if (results.every(({status}) => status == "AC")) {
- // submit
- $("#submit").click();
- } else {
- // failed to submit
- }
- } catch(e) {
- throw e;
- } finally {
- testAndSubmitButton.removeClass("disabled");
- }
- });
- const testAllButton = $(`<a id="atcoder-easy-test-btn-test-all" class="btn btn-default btn-sm" style="margin-left: 1rem" title="Alt+Enter" data-toggle="tooltip">`)
- .text("Test All Samples")
- .click(async () => {
- if (testAllButton.attr("disabled")) throw new Error("Button is disabled");
- await fnTestAll();
- });
- $("#submit").after(testAllButton).after(testAndSubmitButton).closest("form").append(testAllResultRow);
- document.addEventListener("keydown", e => {
- if (e.altKey) {
- switch (e.key) {
- case "Enter":
- testAllButton.click();
- break;
- case "Escape":
- bottomMenu.toggle();
- break;
- }
- }
- if (e.ctrlKey) {
- switch (e.key) {
- case "Enter":
- testAndSubmitButton.click();
- break;
- }
- }
- });
- } catch (e) {
- console.error(e);
- }
-
- document.addEventListener("keydown", e => {
- if (e.altKey) {
- switch (e.key) {
- case "Escape":
- bottomMenu.toggle();
- break;
- }
- }
- });
-
- console.info("view OK");
- });
-
- })();