AtCoder Easy Test

Make testing sample cases easy

Zainstaluj skrypt?
Skrypt zaproponowany przez autora

Może Ci się również spodobać. ac-predictor

Zainstaluj skrypt

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// ==UserScript==
// @name         AtCoder Easy Test
// @namespace    http://atcoder.jp/
// @version      1.8.2
// @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.2";

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)].filter(e => {
                if ($(e).closest(".io-style").length) return false;
                return true;
            });
            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");
});

})();