core.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. 'use strict';
  2. import * as vscode from 'vscode';
  3. import { mkdirSync, existsSync, copyFileSync, openSync, readSync, readdirSync, writeSync, closeSync } from "fs";
  4. import { dirname, join, extname, basename } from "path";
  5. import * as child_process from 'child_process';
  6. import * as gwen from './gwen';
  7. import { TestcaseResult, Veredict, SolutionResult, Problem, Contest, SiteDescription } from "./types";
  8. export const TESTCASES = 'testcases';
  9. export const ATTIC = 'attic';
  10. const SRC = dirname(__filename);
  11. export function getTimeout(){
  12. let timeout: number|undefined = vscode.workspace.getConfiguration('acmx.run').get('timeLimit');
  13. timeout = timeout! * 1000;
  14. return timeout;
  15. }
  16. /**
  17. * Can only handle testcases of at most 512MB
  18. */
  19. function getMaxSizeInput(){
  20. return 512 * 1024;
  21. }
  22. function isProblemFolder(path: string) {
  23. return existsSync(join(path, 'sol.cpp')) &&
  24. existsSync(join(path, 'attic'));
  25. }
  26. function isTestcase(path: string){
  27. let ext = extname(path);
  28. return ext === '.in' || ext === '.out' || ext === '.real';
  29. }
  30. export function currentTestcase() {
  31. let answer: string | undefined = undefined;
  32. // Try to find an open testcase
  33. if (vscode.window.activeTextEditor){
  34. let path = vscode.window.activeTextEditor.document.uri.path;
  35. if (isTestcase(path)){
  36. answer = removeExtension(basename(path));
  37. }
  38. }
  39. // Try to find the test case watching the current open workspace folder
  40. if (vscode.workspace.workspaceFolders !== undefined){
  41. vscode.workspace.workspaceFolders.forEach(function(fd){
  42. if (answer === undefined && isTestcase(fd.uri.path)){
  43. answer = removeExtension(basename(fd.uri.path));
  44. }
  45. });
  46. }
  47. // Test case not found
  48. return answer;
  49. }
  50. export function currentProblem() {
  51. // Try to find the problem using current open file
  52. if (vscode.window.activeTextEditor){
  53. let path = vscode.window.activeTextEditor.document.uri.path;
  54. const MAX_DEPTH = 3;
  55. for (let i = 0; i < MAX_DEPTH && !isProblemFolder(path); i++) {
  56. path = dirname(path);
  57. }
  58. if (isProblemFolder(path)){
  59. return path;
  60. }
  61. }
  62. // Try to find the problem using the current open workspace folder
  63. if (vscode.workspace.workspaceFolders !== undefined){
  64. let path = vscode.workspace.workspaceFolders[0].uri.path;
  65. const MAX_DEPTH = 1;
  66. for (let i = 0; i < MAX_DEPTH && !isProblemFolder(path); i++) {
  67. path = dirname(path);
  68. }
  69. if (isProblemFolder(path)){
  70. return path;
  71. }
  72. }
  73. // Problem not found
  74. return undefined;
  75. }
  76. function createFolder(path: string){
  77. if (!existsSync(path)){
  78. createFolder(dirname(path));
  79. mkdirSync(path);
  80. }
  81. }
  82. export function newArena(path: string){
  83. createFolder(path);
  84. let testcases = join(path, TESTCASES);
  85. createFolder(testcases);
  86. let attic = join(path, ATTIC);
  87. createFolder(attic);
  88. let templatePath: string | undefined = vscode.workspace.getConfiguration('acmx.configuration').get('templatePath');
  89. if (templatePath! === ""){
  90. templatePath = join(SRC, 'static', 'sol.cpp');
  91. }
  92. copyFileSync(templatePath!, join(path, 'sol.cpp'));
  93. copyFileSync(join(SRC, 'static', 'checker'), join(path, ATTIC, 'checker'));
  94. }
  95. export function removeExtension(name: string){
  96. let split = name.split('.');
  97. if (split.length === 0){
  98. return name;
  99. }
  100. else{
  101. split.pop(); // drop extension
  102. return split.join('.');
  103. }
  104. }
  105. export function testcasesName(path: string){
  106. return readdirSync(join(path, TESTCASES)).
  107. filter( function (tcpath) {
  108. return extname(tcpath) === '.in';
  109. }).
  110. map( function(tcpath) { return removeExtension(tcpath); });
  111. }
  112. function testcases(path: string){
  113. return testcasesName(path).map(function (name){
  114. let inp_fd = openSync(join(path, TESTCASES, `${name}.in`), 'r');
  115. let out_fd = openSync(join(path, TESTCASES, `${name}.out`), 'r');
  116. let inp_buffer = new Buffer(getMaxSizeInput());
  117. let out_buffer = new Buffer(getMaxSizeInput());
  118. readSync(inp_fd, inp_buffer, 0, getMaxSizeInput(), 0);
  119. readSync(out_fd, out_buffer, 0, getMaxSizeInput(), 0);
  120. return [
  121. inp_buffer.toString(),
  122. out_buffer.toString()
  123. ];
  124. });
  125. }
  126. export function upgradeArena(path: string) {
  127. let brute = join(path, 'brute.cpp');
  128. if (!existsSync(brute)){
  129. // Create brute.cpp file
  130. copyFileSync(join(SRC, 'static', 'sol.cpp'), brute);
  131. }
  132. let generator = join(path, ATTIC, 'gen.py');
  133. if (!existsSync(generator)){
  134. // Create generator
  135. let inputs: string[] = [];
  136. let outputs: string[] = [];
  137. testcases(path).forEach(function(testcases){
  138. inputs.push(testcases[0]);
  139. outputs.push(testcases[1]);
  140. });
  141. let generator_template = gwen.create(inputs, outputs);
  142. let generator_fd = openSync(generator, 'w');
  143. writeSync(generator_fd, generator_template);
  144. }
  145. }
  146. function newProblem(path: string, problem: Problem){
  147. newArena(path);
  148. problem.inputs!.forEach((value, index) => {
  149. let fd = openSync(join(path, TESTCASES, `${index}.in`), 'w');
  150. writeSync(fd, value);
  151. });
  152. problem.outputs!.forEach((value, index) => {
  153. let fd = openSync(join(path, TESTCASES, `${index}.out`), 'w');
  154. writeSync(fd, value);
  155. });
  156. }
  157. export async function newProblemFromId(path: string, site: SiteDescription, problemId: string){
  158. let problem = await site.problemParser(problemId);
  159. path = join(path, problem.identifier!);
  160. newProblem(path, problem);
  161. return path;
  162. }
  163. function newContest(path: string, contest: Contest){
  164. contest.problems!.forEach(problem => {
  165. newProblem(join(path, problem.identifier!), problem);
  166. });
  167. }
  168. /**
  169. * Create a contest
  170. *
  171. * @param contestId Can be a number if the site is `personal` and this number denote number of problems
  172. */
  173. export async function newContestFromId(path: string, site: SiteDescription, contestId: string){
  174. createFolder(path);
  175. let contest = await site.contestParser(contestId);
  176. newContest(path, contest);
  177. }
  178. /**
  179. *
  180. * @param path
  181. * @param tcName
  182. * @param timeout in miliseconds
  183. */
  184. export function timedRun(path: string, tcName: string, timeout: number){
  185. let tcInput = join(path, TESTCASES, `${tcName}.in`);
  186. let tcOutput = join(path, TESTCASES, `${tcName}.out`);
  187. let tcCurrent = join(path, TESTCASES, `${tcName}.real`);
  188. let inputFd = openSync(tcInput, 'r');
  189. let buffer = new Buffer(getMaxSizeInput());
  190. readSync(inputFd, buffer, 0, getMaxSizeInput(), 0);
  191. let tcData = buffer.toString();
  192. closeSync(inputFd);
  193. let startTime = new Date().getTime();
  194. let command = `${join(path, ATTIC, "sol")}`;
  195. let xresult = child_process.spawnSync(command, {
  196. input: tcData,
  197. timeout,
  198. killSignal: "SIGTERM"
  199. });
  200. // Check if an error happened
  201. if (xresult.status !== 0){
  202. if (xresult.error === undefined){
  203. return new TestcaseResult(Veredict.RTE);
  204. }
  205. else{
  206. return new TestcaseResult(Veredict.TLE);
  207. }
  208. }
  209. let spanTime = new Date().getTime() - startTime;
  210. // Check output is ok
  211. let currentFd = openSync(tcCurrent, 'w');
  212. writeSync(currentFd, xresult.stdout);
  213. closeSync(currentFd);
  214. let checker_result = child_process.spawnSync(join(path, ATTIC, 'checker'), [tcInput, tcCurrent, tcOutput]);
  215. if (checker_result.status !== 0){
  216. return new TestcaseResult(Veredict.WA);
  217. }
  218. else{
  219. return new TestcaseResult(Veredict.OK, spanTime);
  220. }
  221. }
  222. export function compileCode(pathCode: string, pathOutput: string){
  223. let instruction: string | undefined = vscode.workspace.getConfiguration('acmx.execution').get('compileCpp');
  224. let splitedInstruction = instruction!.split(' ');
  225. for (let i = 0; i < splitedInstruction.length; ++i){
  226. splitedInstruction[i] = splitedInstruction[i].replace('$PROGRAM', pathCode).replace('$OUTPUT', pathOutput);
  227. }
  228. let program = splitedInstruction[0];
  229. let args = splitedInstruction.slice(1);
  230. return child_process.spawnSync(program, args);
  231. }
  232. export function testSolution(path: string){
  233. let sol = join(path, 'sol.cpp');
  234. let out = join(path, ATTIC, 'sol');
  235. if (!existsSync(sol)){
  236. throw new Error("Open a coding environment first.");
  237. }
  238. // Compile solution
  239. let xresult = compileCode(sol, out);
  240. if (xresult.status !== 0){
  241. throw new Error("Compilation Error. sol.cpp");
  242. }
  243. let testcasesId = testcasesName(path);
  244. // Proccess all testcases in sorted order
  245. testcasesId.sort();
  246. // Run current test case first (if it exists)
  247. let startTc = currentTestcase();
  248. if (startTc !== undefined){
  249. testcasesId = testcasesId.reverse().filter(name => name !== startTc);
  250. testcasesId.push(startTc);
  251. testcasesId = testcasesId.reverse();
  252. }
  253. let results: TestcaseResult[] = [];
  254. let fail: SolutionResult | undefined = undefined;
  255. testcasesId.forEach(tcId => {
  256. // Run while there none have failed already
  257. if (fail === undefined){
  258. let tcResult = timedRun(path, tcId, getTimeout());
  259. if (tcResult.status !== Veredict.OK){
  260. fail = new SolutionResult(tcResult.status, tcId);
  261. }
  262. results.push(tcResult);
  263. }
  264. });
  265. if (fail === undefined){
  266. let maxTime = 0;
  267. for (let i = 0; i < results.length; i++){
  268. if (results[i].spanTime! > maxTime){
  269. maxTime = results[i].spanTime!;
  270. }
  271. }
  272. return new SolutionResult(Veredict.OK, undefined, maxTime);
  273. }
  274. else{
  275. return fail;
  276. }
  277. }
  278. function generateTestcase(path: string){
  279. let python: string | undefined = vscode.workspace.getConfiguration('acmx.execution').get('pythonPath');
  280. let genResult = child_process.spawnSync(python!, [join(path, ATTIC, 'gen.py')]);
  281. let currentFd = openSync(join(path, TESTCASES, 'gen.in'), 'w');
  282. writeSync(currentFd, genResult.stdout);
  283. closeSync(currentFd);
  284. }
  285. export function stressSolution(path: string, times: number = 10){
  286. let sol = join(path, 'sol.cpp');
  287. let out = join(path, ATTIC, 'sol');
  288. let brute = join(path, 'brute.cpp');
  289. if (!existsSync(sol)){
  290. throw new Error("Open a coding environment first.");
  291. }
  292. if (!existsSync(brute)){
  293. throw new Error("Upgrade environment first.");
  294. }
  295. let brout = join(path, ATTIC, 'brout');
  296. let solCompileResult = compileCode(sol, out);
  297. if (solCompileResult.status !== 0){
  298. throw new Error("Compilation Error. sol.cpp");
  299. }
  300. let bruteCompileResult = compileCode(brute, brout);
  301. if (bruteCompileResult.status !== 0){
  302. throw new Error("Compilation Error. brute.cpp");
  303. }
  304. let results = [];
  305. for (let index = 0; index < times; index++) {
  306. // Generate input testcase
  307. generateTestcase(path);
  308. // Generate output testcase from brute.cpp
  309. let inputFd = openSync(join(path, TESTCASES, 'gen.in'), 'r');
  310. let buffer = new Buffer(getMaxSizeInput());
  311. readSync(inputFd, buffer, 0, getMaxSizeInput(), 0);
  312. let tcData = buffer.toString();
  313. closeSync(inputFd);
  314. // Run without restrictions
  315. // TODO: 005
  316. let runResult = child_process.spawnSync(brout, {
  317. input: tcData,
  318. });
  319. // Finally write .out
  320. let currentFd = openSync(join(path, TESTCASES, 'gen.out'), 'w');
  321. writeSync(currentFd, runResult.stdout);
  322. closeSync(currentFd);
  323. // Check sol.cpp report same result than brute.cpp
  324. let result = timedRun(path, 'gen', getTimeout());
  325. if (result.status !== Veredict.OK){
  326. return new SolutionResult(result.status, 'gen');
  327. }
  328. results.push(result);
  329. }
  330. let maxTime = 0;
  331. for (let i = 0; i < results.length; i++){
  332. if (results[i].spanTime! > maxTime){
  333. maxTime = results[i].spanTime!;
  334. }
  335. }
  336. return new SolutionResult(Veredict.OK, undefined, maxTime);
  337. }
  338. export function veredictName(veredict: Veredict){
  339. switch (veredict) {
  340. case Veredict.OK:
  341. return "OK";
  342. case Veredict.WA:
  343. return "WA";
  344. case Veredict.TLE:
  345. return "TLE";
  346. case Veredict.RTE:
  347. return "RTE";
  348. case Veredict.CE:
  349. return "CE";
  350. default:
  351. throw new Error("Invalid Veredict");
  352. }
  353. }