- // ==UserScript==
- // @name AtCoder Easy Test
- // @namespace http://atcoder.jp/
- // @version 0.1
- // @description Make testing sample cases easy
- // @author magurofly
- // @match https://atcoder.jp/contests/*/tasks/*
- // @grant none
- // ==/UserScript==
-
- // This script uses variables from page below:
- // * `$`
- // * `getSourceCode`
-
- if (!window.bottomMenu) { var bottomMenu = (function () {
- 'use strict';
-
- const tabs = new Set();
-
- $(() => {
- $(`<style>`)
- .text(`
-
- #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.85;
- color: #FFF;
- cursor: pointer;
- pointer-events: auto;
- text-align: center;
- }
-
- #bottom-menu-key.collapsed:before {
- content: "\\e260";
- }
-
- #bottom-menu-tabs {
- padding: 3px 0 0 10px;
- }
-
- #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(100, 100, 100, 0.5);
- border: solid 1px #ccc;
- color: #fff;
- }
-
- #bottom-menu-tabs>li>a:hover {
- background: rgba(150, 150, 150, 0.5);
- border: solid 1px #ccc;
- color: #333;
- }
-
- #bottom-menu-tabs>li.active>a {
- background: #eee;
- border: solid 1px #ccc;
- color: #333;
- }
-
- .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;
- }
-
-
-
- #atcoder-easy-test-language {
- border: none;
- background: transparent;
- font: inherit;
- color: #fff;
- }
-
- `)
- .appendTo("head");
- $(`<div id="bottom-menu-wrapper" class="navbar navbar-default navbar-fixed-bottom">`)
- .html(`
- <div class="container">
- <div class="navbar-header">
- <button id="bottom-menu-key" type="button" class="navbar-toggle collapsed glyphicon glyphicon-menu-down" data-toggle="collapse" data-target="#bottom-menu">
- </button>
- </div>
- <div id="bottom-menu" class="collapse navbar-collapse">
- <ul id="bottom-menu-tabs" class="nav nav-tabs">
- </ul>
- <div id="bottom-menu-contents" class="tab-content">
- </div>
- </div>
- </div>
- `)
- .appendTo("#main-div");
- });
-
- return {
- addTab(tabId, tabLabel, paneContent, options = {}) {
- 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("#bottom-menu-tabs");
- const pane = $(`<div class="tab-pane" id="bottom-menu-pane-${tabId}">`).append(paneContent).appendTo("#bottom-menu-contents");
- const controller = {
- close() {
- tabLi.remove();
- pane.remove();
- tabs.delete(tab);
- if (tabLi.hasClass("active") && tabs.size > 0) {
- tabs.values().next().value.tab("show");
- }
- }
- };
- 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) tab.tab("show");
- return controller;
- }
- };
- })(); }
-
- (function() {
- 'use strict';
-
- let languageName = null;
-
- const languageIdMap = {
- 4001: "c", 4002: "c",
- 4003: "cpp", 4004: "cpp",
- 4005: "java", 4052: "java",
- 4046: "python",
- 4006: "python3", 4047: "python3",
- 4007: "bash", 4035: "bash",
- 4010: "csharp", 4011: "csharp", 4012: "csharp",
- 4013: "clojure",
- 4015: "d", 4016: "d", 4017: "d",
- 4020: "erlang",
- 4021: "elixir",
- 4022: "fsharp", 4023: "fsharp",
- 4026: "go",
- 4027: "haskell",
- 4030: "javascript",
- 4032: "kotlin",
- 4037: "objective-c",
- 4042: "perl", 4043: "perl",
- 4044: "php",
- 4049: "ruby",
- 4050: "rust",
- 4051: "scala",
- 4053: "scheme",
- 4055: "swift",
- 4058: "vb",
- 4060: "cobol", 4061: "cobol",
- };
-
- const languageLabelMap = {
- c: "C (C17 / Clang 10.0.0)",
- cpp: "C++ (C17++ / Clang 10.0.0)",
- java: "Java (OpenJDK 15)",
- python: "Python (2.7.18rc1)",
- python3: "Python (3.8.2)",
- bash: "Bash (5.0.17)",
- csharp: "C# (Mono-mcs 6.8.0.105)",
- clojure: "Clojure (1.10.1-1)",
- d: "D (LDC 1.23.0)",
- erlang: "Erlang (10.6.4)",
- elixir: "Elixir (1.10.4)",
- fsharp: "F# (Interactive 4.0)",
- go: "Go (1.15)",
- haskell: "Haskell (GHC 8.6.5)",
- javascript: "JavaScript (Node.js 12.18.3)",
- kotlin: "Kotlin (1.4.0)",
- "objective-c": "Objective-C (Clang 10.0.0)",
- perl: "Perl (5.30.0)",
- php: "PHP (7.4.10)",
- ruby: "Ruby (2.7.1)",
- rust: "Rust (1.43.0)",
- scala: "Scala (2.13.3)",
- scheme: "Scheme (Gauche 0.9.6)",
- swift: "Swift (5.2.5)",
- vb: "Visual Basic (.NET Core 4.0.1)",
- cobol: "COBOL - Free (OpenCOBOL 2.2.0)",
- };
-
- function setLanguage() {
- const languageId = $("#select-lang>select").val();
- if (languageId in languageIdMap) {
- languageName = languageIdMap[languageId];
- $("#atcoder-easy-test-language").css("color", "#fff").val(languageLabelMap[languageName]);
- $("#atcoder-easy-test-run").removeClass("disabled");
- } else {
- $("#atcoder-easy-test-language").css("color", "#f55").val("language not supported on paiza.io");
- $("#atcoder-easy-test-run").addClass("disabled");
- }
- console.log(languageId);
- }
-
- async function getJSON(method, url, data) {
- const params = Object.entries(data).map(([key, value]) =>
- encodeURIComponent(key) + "=" + encodeURIComponent(value)).join("&");
- const response = await fetch(url + "?" + params, {
- method,
- mode: "cors",
- headers: {
- "Accept": "application/json",
- },
- });
- return await response.json();
- }
-
- async function submitTest(input) {
- let {id, status, error} = await getJSON("POST", "https://api.paiza.io/runners/create", {
- source_code: getSourceCode(),
- language: languageName,
- input,
- longpoll: true,
- longpoll_timeout: 10,
- api_key: "guest",
- });
- console.log("runner id: %s: %s", id, status);
-
- while (status != "completed") {
- let response = await getJSON("GET", "https://api.paiza.io/runners/get_status", {
- id,
- api_key: "guest",
- });
- console.log("%s: %s" ,id, status);
- status = response.status;
- error = response.error;
- }
-
- if (error) console.error("%s: %s", id, error);
-
- let {build_stderr, build_exit_code, build_result, stdout, stderr, exit_code, time, memory, result} = await getJSON("GET", "https://api.paiza.io/runners/get_details", {
- id,
- api_key: "guest",
- });
-
- console.info("%s: %s", id, result);
-
- if (build_exit_code != 0) {
- return {
- status: "CE",
- exitCode: build_exit_code,
- stderr: build_stderr,
- };
- }
-
- return {
- status: exit_code == 0 ? "OK" : result == "timeout" ? "TLE" : "RE",
- exitCode: exit_code,
- execTime: +time * 1e3,
- memory: memory * 1e-3,
- stdout,
- stderr,
- };
- }
-
- async function runTest(input, title = "") {
- const uid = Date.now().toString();
- title = title ? "Result " + title : "Result";
- const content = $(`<div class="container">`)
- .html(`
- <div class="row"><div class="form-group">
- <label class="control-label col-sm-2" for="atcoder-easy-test-${uid}-stdin">Standard Input</label>
- <div class="col-sm-8">
- <textarea id="atcoder-easy-test-${uid}-stdin" class="form-control" rows="5" readonly></textarea>
- </div>
- </div></div>
- <div class="row"><div class="col-sm-4 col-sm-offset-4">
- <div class="panel panel-default"><table class="table table-bordered">
- <tr id="atcoder-easy-test-${uid}-row-exit-code">
- <th class="text-center">Exit Code</th>
- <td id="atcoder-easy-test-${uid}-exit-code" class="text-right"></td>
- </tr>
- <tr id="atcoder-easy-test-${uid}-row-exec-time">
- <th class="text-center">Exec Time</th>
- <td id="atcoder-easy-test-${uid}-exec-time" class="text-right"></td>
- </tr>
- <tr id="atcoder-easy-test-${uid}-row-memory">
- <th class="text-center">Memory</th>
- <td id="atcoder-easy-test-${uid}-memory" class="text-right"></td>
- </tr>
- </table></div>
- </div></div>
- <div class="row"><div class="form-group">
- <label class="control-label col-sm-2" for="atcoder-easy-test-${uid}-stdout">Standard Output</label>
- <div class="col-sm-8">
- <textarea id="atcoder-easy-test-${uid}-stdout" class="form-control" rows="5" readonly></textarea>
- </div>
- </div></div>
- <div class="row"><div class="form-group">
- <label class="control-label col-sm-2" for="atcoder-easy-test-${uid}-stderr">Standard Error</label>
- <div class="col-sm-8">
- <textarea id="atcoder-easy-test-${uid}-stderr" class="form-control" rows="5" readonly></textarea>
- </div>
- </div></div>
- `);
- bottomMenu.addTab("easy-test-result-" + uid, title, content, { active: true, closeButton: true });
- $(`#atcoder-easy-test-${uid}-stdin`).val(input);
-
- const result = await submitTest(input);
-
- $(`#atcoder-easy-test-${uid}-row-exit-code`).toggleClass("bg-danger", result.exitCode != 0);
- $(`#atcoder-easy-test-${uid}-row-exit-code`).toggleClass("bg-success", result.exitCode == 0);
- $(`#atcoder-easy-test-${uid}-exit-code`).text(result.exitCode);
- if ("execTime" in result) $(`#atcoder-easy-test-${uid}-exec-time`).text(result.execTime + " ms");
- if ("memory" in result) $(`#atcoder-easy-test-${uid}-memory`).text(result.memory + " KB");
- $(`#atcoder-easy-test-${uid}-stdout`).val(result.stdout);
- $(`#atcoder-easy-test-${uid}-stderr`).val(result.stderr);
-
- result.uid = uid;
- return result;
- }
-
- bottomMenu.addTab("easy-test", "Easy Test", $(`<form id="atcoder-easy-test-container" class="form-horizontal">`)
- .html(`
- <div class="row">
- <div class="col-12 col-md-10">
- <div class="form-group">
- <label class="control-label col-sm-2">Test Environment</label>
- <div class="col-sm-8">
- <input id="atcoder-easy-test-language" class="form-control" readonly>
- </div>
- </div>
- </div>
- </div>
- <div class="row">
- <div class="col-12 col-md-10">
- <div class="form-group">
- <label class="control-label col-sm-2" for="atcoder-easy-test-input">Standard Input</label>
- <div class="col-sm-8">
- <textarea id="atcoder-easy-test-input" name="input" class="form-control" rows="5"></textarea>
- </div>
- </div>
- </div>
- <div class="col-12 col-md-4">
- <label class="control-label col-sm-2"></label>
- <div class="form-group">
- <div class="col-sm-8">
- <a id="atcoder-easy-test-run" class="btn btn-primary">Run</a>
- </div>
- </div>
- </div>
- </div>
- `), { active: true });
- $("#atcoder-easy-test-run").click(() => runTest($("#atcoder-easy-test-input").val()));
- $("#select-lang>select").on("change", () => setLanguage());
-
- const testfuncs = [];
-
- const testcases = $(".lang>span:nth-child(1) .div-btn-copy+pre[id]").toArray();
- for (let i = 0; i < testcases.length; i += 2) {
- const input = $(testcases[i]), output = $(testcases[i+1]);
- const testfunc = async () => {
- const title = input.closest(".part").find("h3")[0].childNodes[0].data;
- const result = await runTest(input.text(), title);
- if (result.status == "OK") {
- if (result.stdout.trim() == output.text().trim()) {
- $(`#atcoder-easy-test-${result.uid}-stdout`).addClass("bg-success");
- return "AC";
- } else {
- return "WA";
- }
- }
- return result.status;
- };
- 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 ($("#bottom-menu-key").hasClass("collapsed")) $("#bottom-menu-key").click();
- });
- input.closest(".part").find(".btn-copy").eq(0).after(runButton);
- }
-
- const testAllResultRow = $(`<div class="row">`);
- const testAllButton = $(`<a class="btn btn-default btn-sm" style="margin-left: 5px">`)
- .text("Test All Samples")
- .click(async () => {
- const statuses = testfuncs.map(_ => $(`<div class="label label-default" style="margin: 3px">`).text("WJ..."));
- const progress = $(`<div class="progress-bar">`).text(`0 / ${statuses.length}`);
- let finished = 0;
- const resultAlert = $(`<div class="alert alert-dismissible">`)
- .append($(`<button type="button" class="close" data-dismiss="alert" aria-label="close">`)
- .append($(`<span aria-hidden="true">`).text("\xd7")))
- .append($(`<div class="progress">`).append(progress))
- .append(...statuses)
- .appendTo(testAllResultRow);
- const waitee = testfuncs.map(async (testfunc, i) => {
- const status = await testfunc();
- finished++;
- progress.text(`${finished} / ${statuses.length}`).css("width", `${finished/statuses.length*100}%`);
- statuses[i].toggleClass("label-success", status == "AC").toggleClass("label-warning", status != "AC").text(status);
- return status;
- });
- if ((await Promise.all(waitee)).every(status => status == "AC")) {
- resultAlert.addClass("alert-success");
- } else {
- resultAlert.addClass("alert-warning");
- }
- });
- $("#submit").after(testAllButton).closest("form").append(testAllResultRow);
- })();