AtCoder Easy Test

Make testing sample cases easy

当前为 2020-11-12 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name AtCoder Easy Test
  3. // @namespace http://atcoder.jp/
  4. // @version 0.1.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.  
  15. if (!window.bottomMenu) { var bottomMenu = (function () {
  16. 'use strict';
  17.  
  18. const tabs = new Set();
  19.  
  20. $(() => {
  21. $(`<style>`)
  22. .text(`
  23.  
  24. #bottom-menu-wrapper {
  25. background: transparent;
  26. border: none;
  27. pointer-events: none;
  28. padding: 0;
  29. }
  30.  
  31. #bottom-menu-wrapper>.container {
  32. position: absolute;
  33. bottom: 0;
  34. width: 100%;
  35. padding: 0;
  36. }
  37.  
  38. #bottom-menu-wrapper>.container>.navbar-header {
  39. float: none;
  40. }
  41.  
  42. #bottom-menu-key {
  43. display: block;
  44. float: none;
  45. margin: 0 auto;
  46. padding: 10px 3em;
  47. border-radius: 5px 5px 0 0;
  48. background: #000;
  49. opacity: 0.85;
  50. color: #FFF;
  51. cursor: pointer;
  52. pointer-events: auto;
  53. text-align: center;
  54. }
  55.  
  56. #bottom-menu-key.collapsed:before {
  57. content: "\\e260";
  58. }
  59.  
  60. #bottom-menu-tabs {
  61. padding: 3px 0 0 10px;
  62. }
  63.  
  64. #bottom-menu-tabs a {
  65. pointer-events: auto;
  66. }
  67.  
  68. #bottom-menu {
  69. pointer-events: auto;
  70. background: rgba(0, 0, 0, 0.8);
  71. color: #fff;
  72. max-height: unset;
  73. }
  74.  
  75. #bottom-menu.collapse:not(.in) {
  76. display: none !important;
  77. }
  78.  
  79. #bottom-menu-tabs>li>a {
  80. background: rgba(100, 100, 100, 0.5);
  81. border: solid 1px #ccc;
  82. color: #fff;
  83. }
  84.  
  85. #bottom-menu-tabs>li>a:hover {
  86. background: rgba(150, 150, 150, 0.5);
  87. border: solid 1px #ccc;
  88. color: #333;
  89. }
  90.  
  91. #bottom-menu-tabs>li.active>a {
  92. background: #eee;
  93. border: solid 1px #ccc;
  94. color: #333;
  95. }
  96.  
  97. .bottom-menu-btn-close {
  98. font-size: 8pt;
  99. vertical-align: baseline;
  100. padding: 0 0 0 6px;
  101. margin-right: -6px;
  102. }
  103.  
  104. #bottom-menu-contents {
  105. padding: 5px 15px;
  106. max-height: 50vh;
  107. overflow-y: auto;
  108. }
  109.  
  110. #bottom-menu-contents .panel {
  111. color: #333;
  112. }
  113.  
  114.  
  115.  
  116. #atcoder-easy-test-language {
  117. border: none;
  118. background: transparent;
  119. font: inherit;
  120. color: #fff;
  121. }
  122.  
  123. `)
  124. .appendTo("head");
  125. $(`<div id="bottom-menu-wrapper" class="navbar navbar-default navbar-fixed-bottom">`)
  126. .html(`
  127. <div class="container">
  128. <div class="navbar-header">
  129. <button id="bottom-menu-key" type="button" class="navbar-toggle collapsed glyphicon glyphicon-menu-down" data-toggle="collapse" data-target="#bottom-menu">
  130. </button>
  131. </div>
  132. <div id="bottom-menu" class="collapse navbar-collapse">
  133. <ul id="bottom-menu-tabs" class="nav nav-tabs">
  134. </ul>
  135. <div id="bottom-menu-contents" class="tab-content">
  136. </div>
  137. </div>
  138. </div>
  139. `)
  140. .appendTo("#main-div");
  141. });
  142.  
  143. const menuController = {
  144. addTab(tabId, tabLabel, paneContent, options = {}) {
  145. const tab = $(`<a id="bottom-menu-tab-${tabId}" href="#" data-target="#bottom-menu-pane-${tabId}" data-toggle="tab">`)
  146. .click(e => {
  147. e.preventDefault();
  148. tab.tab("show");
  149. })
  150. .append(tabLabel);
  151. const tabLi = $(`<li>`).append(tab).appendTo("#bottom-menu-tabs");
  152. const pane = $(`<div class="tab-pane" id="bottom-menu-pane-${tabId}">`).append(paneContent).appendTo("#bottom-menu-contents");
  153. const controller = {
  154. close() {
  155. tabLi.remove();
  156. pane.remove();
  157. tabs.delete(tab);
  158. if (tabLi.hasClass("active") && tabs.size > 0) {
  159. tabs.values().next().value.tab("show");
  160. }
  161. },
  162.  
  163. show() {
  164. menuController.show();
  165. tab.tab("show");
  166. }
  167. };
  168. tabs.add(tab);
  169. if (options.closeButton) tab.append($(`<a class="bottom-menu-btn-close btn btn-link glyphicon glyphicon-remove">`).click(() => controller.close()));
  170. if (options.active || tabs.size == 1) tab.tab("show");
  171. return controller;
  172. },
  173.  
  174. show() {
  175. if ($("#bottom-menu-key").hasClass("collapsed")) $("#bottom-menu-key").click();
  176. },
  177. };
  178.  
  179. return menuController;
  180. })(); }
  181.  
  182. (function() {
  183. 'use strict';
  184.  
  185. let languageName = null;
  186.  
  187. const languageIdMap = {
  188. 4001: "c", 4002: "c",
  189. 4003: "cpp", 4004: "cpp",
  190. 4005: "java", 4052: "java",
  191. 4046: "python",
  192. 4006: "python3", 4047: "python3",
  193. 4007: "bash", 4035: "bash",
  194. 4010: "csharp", 4011: "csharp", 4012: "csharp",
  195. 4013: "clojure",
  196. 4015: "d", 4016: "d", 4017: "d",
  197. 4020: "erlang",
  198. 4021: "elixir",
  199. 4022: "fsharp", 4023: "fsharp",
  200. 4026: "go",
  201. 4027: "haskell",
  202. 4030: "javascript",
  203. 4032: "kotlin",
  204. 4037: "objective-c",
  205. 4042: "perl", 4043: "perl",
  206. 4044: "php",
  207. 4049: "ruby",
  208. 4050: "rust",
  209. 4051: "scala",
  210. 4053: "scheme",
  211. 4055: "swift",
  212. 4058: "vb",
  213. 4060: "cobol", 4061: "cobol",
  214. };
  215.  
  216. const languageLabelMap = {
  217. c: "C (C17 / Clang 10.0.0)",
  218. cpp: "C++ (C17++ / Clang 10.0.0)",
  219. java: "Java (OpenJDK 15)",
  220. python: "Python (2.7.18rc1)",
  221. python3: "Python (3.8.2)",
  222. bash: "Bash (5.0.17)",
  223. csharp: "C# (Mono-mcs 6.8.0.105)",
  224. clojure: "Clojure (1.10.1-1)",
  225. d: "D (LDC 1.23.0)",
  226. erlang: "Erlang (10.6.4)",
  227. elixir: "Elixir (1.10.4)",
  228. fsharp: "F# (Interactive 4.0)",
  229. go: "Go (1.15)",
  230. haskell: "Haskell (GHC 8.6.5)",
  231. javascript: "JavaScript (Node.js 12.18.3)",
  232. kotlin: "Kotlin (1.4.0)",
  233. "objective-c": "Objective-C (Clang 10.0.0)",
  234. perl: "Perl (5.30.0)",
  235. php: "PHP (7.4.10)",
  236. ruby: "Ruby (2.7.1)",
  237. rust: "Rust (1.43.0)",
  238. scala: "Scala (2.13.3)",
  239. scheme: "Scheme (Gauche 0.9.6)",
  240. swift: "Swift (5.2.5)",
  241. vb: "Visual Basic (.NET Core 4.0.1)",
  242. cobol: "COBOL - Free (OpenCOBOL 2.2.0)",
  243. };
  244.  
  245. function setLanguage() {
  246. const languageId = $("#select-lang>select").val();
  247. if (languageId in languageIdMap) {
  248. languageName = languageIdMap[languageId];
  249. $("#atcoder-easy-test-language").css("color", "#fff").val(languageLabelMap[languageName]);
  250. $("#atcoder-easy-test-run").removeClass("disabled");
  251. } else {
  252. $("#atcoder-easy-test-language").css("color", "#f55").val("language not supported on paiza.io");
  253. $("#atcoder-easy-test-run").addClass("disabled");
  254. }
  255. console.log(languageId);
  256. }
  257.  
  258. async function getJSON(method, url, data) {
  259. const params = Object.entries(data).map(([key, value]) =>
  260. encodeURIComponent(key) + "=" + encodeURIComponent(value)).join("&");
  261. const response = await fetch(url + "?" + params, {
  262. method,
  263. mode: "cors",
  264. headers: {
  265. "Accept": "application/json",
  266. },
  267. });
  268. return await response.json();
  269. }
  270.  
  271. async function submitTest(input) {
  272. let id, status, error;
  273. try {
  274. const response = await getJSON("POST", "https://api.paiza.io/runners/create", {
  275. source_code: getSourceCode(),
  276. language: languageName,
  277. input,
  278. longpoll: true,
  279. longpoll_timeout: 10,
  280. api_key: "guest",
  281. });
  282. id = response.id;
  283. status = response.status;
  284. error = response.error;
  285. } catch (error) {
  286. status = "completed";
  287. return {
  288. status: "IE",
  289. exitCode: error,
  290. };
  291. }
  292. console.log("runner id: %s: %s", id, status);
  293.  
  294. while (status != "completed") {
  295. let response = await getJSON("GET", "https://api.paiza.io/runners/get_status", {
  296. id,
  297. api_key: "guest",
  298. });
  299. console.log("%s: %s" ,id, status);
  300. status = response.status;
  301. error = response.error;
  302. }
  303.  
  304. if (error) console.error("%s: %s", id, error);
  305.  
  306. 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", {
  307. id,
  308. api_key: "guest",
  309. });
  310.  
  311. console.info("%s: %s", id, result);
  312.  
  313. if (build_exit_code != 0) {
  314. return {
  315. status: "CE",
  316. exitCode: build_exit_code,
  317. stderr: build_stderr,
  318. };
  319. }
  320.  
  321. return {
  322. status: exit_code == 0 ? "OK" : result == "timeout" ? "TLE" : "RE",
  323. exitCode: exit_code,
  324. execTime: +time * 1e3,
  325. memory: memory * 1e-3,
  326. stdout,
  327. stderr,
  328. };
  329. }
  330.  
  331. async function runTest(input, title = "") {
  332. const uid = Date.now().toString();
  333. title = title ? "Result " + title : "Result";
  334. const content = $(`<div class="container">`)
  335. .html(`
  336. <div class="row"><div class="form-group">
  337. <label class="control-label col-sm-2" for="atcoder-easy-test-${uid}-stdin">Standard Input</label>
  338. <div class="col-sm-8">
  339. <textarea id="atcoder-easy-test-${uid}-stdin" class="form-control" rows="5" readonly></textarea>
  340. </div>
  341. </div></div>
  342. <div class="row"><div class="col-sm-4 col-sm-offset-4">
  343. <div class="panel panel-default"><table class="table table-bordered">
  344. <tr id="atcoder-easy-test-${uid}-row-exit-code">
  345. <th class="text-center">Exit Code</th>
  346. <td id="atcoder-easy-test-${uid}-exit-code" class="text-right"></td>
  347. </tr>
  348. <tr id="atcoder-easy-test-${uid}-row-exec-time">
  349. <th class="text-center">Exec Time</th>
  350. <td id="atcoder-easy-test-${uid}-exec-time" class="text-right"></td>
  351. </tr>
  352. <tr id="atcoder-easy-test-${uid}-row-memory">
  353. <th class="text-center">Memory</th>
  354. <td id="atcoder-easy-test-${uid}-memory" class="text-right"></td>
  355. </tr>
  356. </table></div>
  357. </div></div>
  358. <div class="row"><div class="form-group">
  359. <label class="control-label col-sm-2" for="atcoder-easy-test-${uid}-stdout">Standard Output</label>
  360. <div class="col-sm-8">
  361. <textarea id="atcoder-easy-test-${uid}-stdout" class="form-control" rows="5" readonly></textarea>
  362. </div>
  363. </div></div>
  364. <div class="row"><div class="form-group">
  365. <label class="control-label col-sm-2" for="atcoder-easy-test-${uid}-stderr">Standard Error</label>
  366. <div class="col-sm-8">
  367. <textarea id="atcoder-easy-test-${uid}-stderr" class="form-control" rows="5" readonly></textarea>
  368. </div>
  369. </div></div>
  370. `);
  371. const tab = bottomMenu.addTab("easy-test-result-" + uid, title, content, { active: true, closeButton: true });
  372. $(`#atcoder-easy-test-${uid}-stdin`).val(input);
  373.  
  374. const result = await submitTest(input);
  375.  
  376. $(`#atcoder-easy-test-${uid}-row-exit-code`).toggleClass("bg-danger", result.exitCode != 0).toggleClass("bg-success", result.exitCode == 0);
  377. $(`#atcoder-easy-test-${uid}-exit-code`).text(result.exitCode);
  378. if ("execTime" in result) $(`#atcoder-easy-test-${uid}-exec-time`).text(result.execTime + " ms");
  379. if ("memory" in result) $(`#atcoder-easy-test-${uid}-memory`).text(result.memory + " KB");
  380. $(`#atcoder-easy-test-${uid}-stdout`).val(result.stdout);
  381. $(`#atcoder-easy-test-${uid}-stderr`).val(result.stderr);
  382.  
  383. result.uid = uid;
  384. result.tab = tab;
  385. return result;
  386. }
  387.  
  388. bottomMenu.addTab("easy-test", "Easy Test", $(`<form id="atcoder-easy-test-container" class="form-horizontal">`)
  389. .html(`
  390. <div class="row">
  391. <div class="col-12 col-md-10">
  392. <div class="form-group">
  393. <label class="control-label col-sm-2">Test Environment</label>
  394. <div class="col-sm-8">
  395. <input id="atcoder-easy-test-language" class="form-control" readonly>
  396. </div>
  397. </div>
  398. </div>
  399. </div>
  400. <div class="row">
  401. <div class="col-12 col-md-10">
  402. <div class="form-group">
  403. <label class="control-label col-sm-2" for="atcoder-easy-test-input">Standard Input</label>
  404. <div class="col-sm-8">
  405. <textarea id="atcoder-easy-test-input" name="input" class="form-control" rows="5"></textarea>
  406. </div>
  407. </div>
  408. </div>
  409. <div class="col-12 col-md-4">
  410. <label class="control-label col-sm-2"></label>
  411. <div class="form-group">
  412. <div class="col-sm-8">
  413. <a id="atcoder-easy-test-run" class="btn btn-primary">Run</a>
  414. </div>
  415. </div>
  416. </div>
  417. </div>
  418. `), { active: true });
  419. $("#atcoder-easy-test-run").click(() => runTest($("#atcoder-easy-test-input").val()));
  420. $("#select-lang>select").on("change", () => setLanguage());
  421.  
  422. const testfuncs = [];
  423.  
  424. const testcases = $(".lang>span:nth-child(1) .div-btn-copy+pre[id]").toArray();
  425. for (let i = 0; i < testcases.length; i += 2) {
  426. const input = $(testcases[i]), output = $(testcases[i+1]);
  427. const testfunc = async () => {
  428. const title = input.closest(".part").find("h3")[0].childNodes[0].data;
  429. const result = await runTest(input.text(), title);
  430. if (result.status == "OK") {
  431. if (result.stdout.trim() == output.text().trim()) {
  432. $(`#atcoder-easy-test-${result.uid}-stdout`).addClass("bg-success");
  433. result.status = "AC";
  434. } else {
  435. result.status = "WA";
  436. }
  437. }
  438. return result;
  439. };
  440. testfuncs.push(testfunc);
  441.  
  442. const runButton = $(`<a class="btn btn-primary btn-sm" style="vertical-align: top; margin-left: 0.5em">`)
  443. .text("Run")
  444. .click(async () => {
  445. await testfunc();
  446. if ($("#bottom-menu-key").hasClass("collapsed")) $("#bottom-menu-key").click();
  447. });
  448. input.closest(".part").find(".btn-copy").eq(0).after(runButton);
  449. }
  450.  
  451. const testAllResultRow = $(`<div class="row">`);
  452. const testAllButton = $(`<a class="btn btn-default btn-sm" style="margin-left: 5px">`)
  453. .text("Test All Samples")
  454. .click(async () => {
  455. const statuses = testfuncs.map(_ => $(`<div class="label label-default" style="margin: 3px">`).text("WJ..."));
  456. const progress = $(`<div class="progress-bar">`).text(`0 / ${testfuncs.length}`);
  457. let finished = 0;
  458. const closeButton = $(`<button type="button" class="close" data-dismiss="alert" aria-label="close">`)
  459. .append($(`<span aria-hidden="true">`).text("\xd7"));
  460. const resultAlert = $(`<div class="alert alert-dismissible">`)
  461. .append(closeButton)
  462. .append($(`<div class="progress">`).append(progress))
  463. .append(...statuses)
  464. .appendTo(testAllResultRow);
  465. const results = await Promise.all(testfuncs.map(async (testfunc, i) => {
  466. const result = await testfunc();
  467. finished++;
  468. progress.text(`${finished} / ${statuses.length}`).css("width", `${finished/statuses.length*100}%`);
  469. statuses[i].toggleClass("label-success", result.status == "AC").toggleClass("label-warning", result.status != "AC").text(result.status).click(() => result.tab.show()).css("cursor", "pointer");
  470. return result;
  471. }));
  472. if (results.every(status => status == "AC")) {
  473. resultAlert.addClass("alert-success");
  474. } else {
  475. resultAlert.addClass("alert-warning");
  476. }
  477. closeButton.click(() => {
  478. for (const {tab} of results) {
  479. tab.close();
  480. }
  481. });
  482. });
  483. $("#submit").after(testAllButton).closest("form").append(testAllResultRow);
  484. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址