core.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  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. import { ceTerminal, stderrTerminal } from './terminal';
  9. const md5File = require('md5-file');
  10. export const TESTCASES = 'testcases';
  11. export const ATTIC = 'attic';
  12. export const SRC = dirname(__filename);
  13. /**
  14. * Name of program file. Take extension dynamically from configuration
  15. */
  16. export function solFile(){
  17. let extension: string|undefined = vscode.workspace.getConfiguration('acmx.configuration', null).get('extension');
  18. return 'sol.' + extension;
  19. }
  20. export function getTimeout(){
  21. let timeout: number|undefined = vscode.workspace.getConfiguration('acmx.run', null).get('timeLimit');
  22. timeout = timeout! * 1000;
  23. return timeout;
  24. }
  25. /**
  26. * Can only handle testcases of at most 512MB
  27. */
  28. function getMaxSizeInput(){
  29. return 512 * 1024;
  30. }
  31. function isProblemFolder(path: string) {
  32. return existsSync(join(path, solFile())) &&
  33. existsSync(join(path, 'attic'));
  34. }
  35. function isTestcase(path: string){
  36. let ext = extname(path);
  37. return ext === '.in' || ext === '.out' || ext === '.real';
  38. }
  39. export function currentTestcase() {
  40. let answer: string | undefined = undefined;
  41. // Try to find an open testcase
  42. if (vscode.window.activeTextEditor){
  43. let path = vscode.window.activeTextEditor.document.uri.fsPath;
  44. if (isTestcase(path)){
  45. answer = removeExtension(basename(path));
  46. }
  47. }
  48. // Try to find the test case watching the current open workspace folder
  49. if (vscode.workspace.workspaceFolders !== undefined){
  50. vscode.workspace.workspaceFolders.forEach(function(fd){
  51. if (answer === undefined && isTestcase(fd.uri.fsPath)){
  52. answer = removeExtension(basename(fd.uri.fsPath));
  53. }
  54. });
  55. }
  56. // Test case not found
  57. return answer;
  58. }
  59. export function currentProblem() {
  60. // Try to find the problem using current open file
  61. if (vscode.window.activeTextEditor){
  62. let path = vscode.window.activeTextEditor.document.uri.fsPath;
  63. const MAX_DEPTH = 3;
  64. for (let i = 0; i < MAX_DEPTH && !isProblemFolder(path); i++) {
  65. path = dirname(path);
  66. }
  67. if (isProblemFolder(path)){
  68. return path;
  69. }
  70. }
  71. // Try to find the problem using the current open workspace folder
  72. if (vscode.workspace.workspaceFolders !== undefined){
  73. let path = vscode.workspace.workspaceFolders[0].uri.fsPath;
  74. const MAX_DEPTH = 1;
  75. for (let i = 0; i < MAX_DEPTH && !isProblemFolder(path); i++) {
  76. path = dirname(path);
  77. }
  78. if (isProblemFolder(path)){
  79. return path;
  80. }
  81. }
  82. // Problem not found
  83. return undefined;
  84. }
  85. function createFolder(path: string){
  86. if (!existsSync(path)){
  87. createFolder(dirname(path));
  88. mkdirSync(path);
  89. }
  90. }
  91. /**
  92. * Path to common attic for every problem.
  93. *
  94. * @param testingPath Use for unit tests
  95. */
  96. function globalAtticPath(testingPath: string | undefined = undefined){
  97. let path: string | undefined = vscode.workspace.getConfiguration('acmx.configuration', null).get('solutionPath');
  98. return join(path!, ATTIC);
  99. }
  100. /**
  101. * Create default environment that let acmX run properly
  102. */
  103. export function initAcmX(){
  104. // Create global attic.
  105. let globalAttic = globalAtticPath();
  106. createFolder(globalAttic);
  107. // Create checker folder
  108. let checkerFolder = join(globalAttic, 'checkers');
  109. createFolder(checkerFolder);
  110. // Copy testlib
  111. let testlib = 'testlib.h';
  112. if (!existsSync(join(checkerFolder, testlib))){
  113. copyFileSync(join(SRC, 'static', 'checkers', testlib),
  114. join(checkerFolder, testlib));
  115. }
  116. // Create wcmp checker
  117. let checkerName = 'wcmp.cpp';
  118. if (!existsSync(join(checkerFolder, checkerName))){
  119. copyFileSync(join(SRC, 'static', 'checkers', checkerName),
  120. join(checkerFolder, checkerName));
  121. }
  122. // Compile checker
  123. let compiledName = 'wcmp.exe';
  124. if (!existsSync(join(checkerFolder, compiledName))){
  125. let checkerPath = join(checkerFolder, checkerName);
  126. let compiledPath = join(checkerFolder, compiledName);
  127. child_process.spawnSync("g++", ["-std=c++11", `${checkerPath}`, "-o", `${compiledPath}`]);
  128. }
  129. }
  130. export function newArena(path: string){
  131. createFolder(path);
  132. let testcases = join(path, TESTCASES);
  133. createFolder(testcases);
  134. let attic = join(path, ATTIC);
  135. createFolder(attic);
  136. let templatePath: string | undefined = vscode.workspace.getConfiguration('acmx.configuration', null).get('templatePath');
  137. if (templatePath! === ""){
  138. templatePath = join(SRC, 'static', 'template.cpp');
  139. }
  140. let solution = join(path, solFile());
  141. if (!existsSync(solution)){
  142. copyFileSync(templatePath!, join(path, solFile()));
  143. }
  144. }
  145. export function removeExtension(name: string){
  146. let split = name.split('.');
  147. if (split.length === 0){
  148. return name;
  149. }
  150. else{
  151. split.pop(); // drop extension
  152. return split.join('.');
  153. }
  154. }
  155. export function testcasesName(path: string){
  156. return readdirSync(join(path, TESTCASES)).
  157. filter( function (tcpath) {
  158. return extname(tcpath) === '.in';
  159. }).
  160. map( function(tcpath) { return removeExtension(tcpath); });
  161. }
  162. // function testcases(path: string){
  163. // return testcasesName(path).map(function (name){
  164. // let inp_fd = openSync(join(path, TESTCASES, `${name}.in`), 'r');
  165. // let out_fd = openSync(join(path, TESTCASES, `${name}.out`), 'r');
  166. // let inp_buffer = new Buffer(getMaxSizeInput());
  167. // let out_buffer = new Buffer(getMaxSizeInput());
  168. // readSync(inp_fd, inp_buffer, 0, getMaxSizeInput(), 0);
  169. // readSync(out_fd, out_buffer, 0, getMaxSizeInput(), 0);
  170. // return [
  171. // inp_buffer.toString(),
  172. // out_buffer.toString()
  173. // ];
  174. // });
  175. // }
  176. export function upgradeArena(path: string) {
  177. // Create brute force solution
  178. let brute = join(path, 'brute.cpp');
  179. if (!existsSync(brute)){
  180. // Create brute.cpp file
  181. copyFileSync(join(SRC, 'static', 'template.cpp'), brute);
  182. }
  183. // Create test case generator
  184. let generator = join(path, 'gen.py');
  185. if (!existsSync(generator)){
  186. // TODO: If generator already exist ask whether to overwrite or not.
  187. gwen.create(path, generator);
  188. }
  189. // Create checker for multiple answers.
  190. let checker = join(path, ATTIC, 'checker.cpp');
  191. if (!existsSync(checker)){
  192. let testlib_path = join(path, ATTIC, 'testlib.h');
  193. copyFileSync(join(SRC, 'static', 'checkers', 'wcmp.cpp'), checker);
  194. copyFileSync(join(SRC, 'static', 'checkers', 'testlib.h'), testlib_path);
  195. }
  196. }
  197. function newProblem(path: string, problem: Problem){
  198. newArena(path);
  199. problem.inputs!.forEach((value, index) => {
  200. let fd = openSync(join(path, TESTCASES, `${index}.in`), 'w');
  201. writeSync(fd, value);
  202. });
  203. problem.outputs!.forEach((value, index) => {
  204. let fd = openSync(join(path, TESTCASES, `${index}.out`), 'w');
  205. writeSync(fd, value);
  206. });
  207. }
  208. export async function newProblemFromId(path: string, site: SiteDescription, problemId: string){
  209. let problem = await site.problemParser(problemId);
  210. path = join(path, problem.identifier!);
  211. newProblem(path, problem);
  212. return path;
  213. }
  214. function newContest(path: string, contest: Contest){
  215. contest.problems!.forEach(problem => {
  216. newProblem(join(path, problem.identifier!), problem);
  217. });
  218. }
  219. export function newProblemFromCompanion(config: any){
  220. console.log(config);
  221. let _path: string | undefined = vscode.workspace.getConfiguration('acmx.configuration', null).get('solutionPath');
  222. let path = _path!;
  223. let contestPath = join(path, config.group);
  224. createFolder(contestPath);
  225. let problemPath = join(contestPath, config.name);
  226. let inputs: string[] = [];
  227. let outputs: string[] = [];
  228. config.tests.forEach(function(testcase: any){
  229. inputs.push(testcase.input);
  230. outputs.push(testcase.output);
  231. });
  232. newProblem(problemPath, new Problem(config.name, config.name, inputs, outputs));
  233. return contestPath;
  234. }
  235. /**
  236. * Create a contest
  237. *
  238. * @param contestId Id of the contest that user want to retrieve.
  239. */
  240. export async function newContestFromId(path: string, site: SiteDescription, contestId: string){
  241. let contest = await site.contestParser(contestId);
  242. let contestPath = join(path, site.name, contest.name);
  243. createFolder(contestPath);
  244. newContest(contestPath, contest);
  245. return contestPath;
  246. }
  247. function get_checker_path() {
  248. let path = currentProblem();
  249. let default_checker = join(globalAtticPath(), 'checkers', 'wcmp.exe');
  250. if (path === undefined) {
  251. return default_checker;
  252. }
  253. let potential_checker_path = join(path, ATTIC, 'checker.cpp');
  254. if (existsSync(potential_checker_path)) {
  255. let checker_output = join(path, ATTIC, 'checker.exe');
  256. compileCode(potential_checker_path, checker_output);
  257. return checker_output;
  258. }
  259. return default_checker;
  260. }
  261. /**
  262. *
  263. * @param path
  264. * @param tcName
  265. * @param timeout in miliseconds
  266. */
  267. export function timedRun(path: string, tcName: string, timeout: number){
  268. let tcInput = join(path, TESTCASES, `${tcName}.in`);
  269. let tcOutput = join(path, TESTCASES, `${tcName}.out`);
  270. let tcCurrent = join(path, TESTCASES, `${tcName}.real`);
  271. // TODO: Don't create Buffer from constructor `new Buffer()`. See warning:
  272. // (node:17458) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead.
  273. let inputFd = openSync(tcInput, 'r');
  274. let buffer = new Buffer(getMaxSizeInput());
  275. readSync(inputFd, buffer, 0, getMaxSizeInput(), 0);
  276. let tcData = buffer.toString();
  277. closeSync(inputFd);
  278. let startTime = new Date().getTime();
  279. let command = `${join(path, ATTIC, "sol")}`;
  280. let xresult = child_process.spawnSync(command, {
  281. input: tcData,
  282. timeout,
  283. killSignal: "SIGTERM"
  284. });
  285. let spanTime = new Date().getTime() - startTime;
  286. if (xresult.stderr.length > 0) {
  287. let stderrTer = stderrTerminal();
  288. let escaped_output = escape_double_ticks(xresult.stderr.toString());
  289. stderrTer.sendText(`echo "${escaped_output}"`);
  290. stderrTer.show();
  291. }
  292. // Check if an error happened
  293. if (xresult.status !== 0){
  294. if (spanTime < timeout){
  295. return new TestcaseResult(Veredict.RTE);
  296. }
  297. else{
  298. return new TestcaseResult(Veredict.TLE);
  299. }
  300. }
  301. // Check output is ok
  302. let currentFd = openSync(tcCurrent, 'w');
  303. writeSync(currentFd, xresult.stdout);
  304. closeSync(currentFd);
  305. let checker_path = get_checker_path();
  306. let checker_result = child_process.spawnSync(checker_path, [tcInput, tcOutput, tcCurrent]);
  307. if (checker_result.status !== 0){
  308. return new TestcaseResult(Veredict.WA);
  309. }
  310. else{
  311. return new TestcaseResult(Veredict.OK, spanTime);
  312. }
  313. }
  314. function escape_double_ticks(text: string) {
  315. text = text.toString();
  316. // text = text.replace('"', '\"');
  317. console.log(text);
  318. return text;
  319. }
  320. export function compileCode(pathCode: string, pathOutput: string){
  321. let pathCodeMD5 = pathCode + '.md5';
  322. let md5data = "";
  323. if (existsSync(pathCodeMD5)) {
  324. let codeMD5fd = openSync(pathCodeMD5, 'r');
  325. let buffer = new Buffer(getMaxSizeInput());
  326. readSync(codeMD5fd, buffer, 0, 32, 0);
  327. md5data = buffer.toString().slice(0, 32);
  328. closeSync(codeMD5fd);
  329. }
  330. let codeMD5 = md5File.sync(pathCode);
  331. if (codeMD5 === md5data) {
  332. return {
  333. 'status' : 0
  334. };
  335. }
  336. let codeMD5fd = openSync(pathCodeMD5, 'w');
  337. writeSync(codeMD5fd, codeMD5 + '\n');
  338. closeSync(codeMD5fd);
  339. let instruction: string | undefined = vscode.workspace.getConfiguration('acmx.execution', null).get('compile');
  340. let splitedInstruction = instruction!.split(' ');
  341. for (let i = 0; i < splitedInstruction.length; ++i){
  342. splitedInstruction[i] = splitedInstruction[i].replace('$PROGRAM', pathCode).replace('$OUTPUT', pathOutput);
  343. }
  344. let program = splitedInstruction[0];
  345. let args = splitedInstruction.slice(1);
  346. let result = child_process.spawnSync(program, args);
  347. if (result.status !== 0) {
  348. // Write to the compile error terminal
  349. let ter = ceTerminal();
  350. let escaped_output = escape_double_ticks(result.stderr);
  351. ter.sendText(`echo "${escaped_output}"`);
  352. ter.show();
  353. }
  354. return result;
  355. }
  356. export function testSolution(path: string){
  357. let sol = join(path, solFile());
  358. let out = join(path, ATTIC, 'sol');
  359. if (!existsSync(sol)){
  360. throw new Error("Open a coding environment first.");
  361. }
  362. // Compile solution
  363. let xresult = compileCode(sol, out);
  364. if (xresult.status !== 0){
  365. throw new Error(`Compilation Error. ${sol}`);
  366. }
  367. let testcasesId = testcasesName(path);
  368. // Proccess all testcases in sorted order
  369. testcasesId.sort();
  370. // Run current test case first (if it exists)
  371. let startTc = currentTestcase();
  372. if (startTc !== undefined){
  373. testcasesId = testcasesId.reverse().filter(name => name !== startTc);
  374. testcasesId.push(startTc);
  375. testcasesId = testcasesId.reverse();
  376. }
  377. let results: TestcaseResult[] = [];
  378. let fail: SolutionResult | undefined = undefined;
  379. testcasesId.forEach(tcId => {
  380. // Run while there none have failed already
  381. if (fail === undefined){
  382. let tcResult = timedRun(path, tcId, getTimeout());
  383. if (tcResult.status !== Veredict.OK){
  384. fail = new SolutionResult(tcResult.status, tcId);
  385. }
  386. results.push(tcResult);
  387. }
  388. });
  389. if (fail === undefined){
  390. let maxTime = 0;
  391. for (let i = 0; i < results.length; i++){
  392. if (results[i].spanTime! > maxTime){
  393. maxTime = results[i].spanTime!;
  394. }
  395. }
  396. return new SolutionResult(Veredict.OK, undefined, maxTime);
  397. }
  398. else{
  399. return fail;
  400. }
  401. }
  402. function generateTestcase(path: string){
  403. let python: string | undefined = vscode.workspace.getConfiguration('acmx.execution', null).get('pythonPath');
  404. let genResult = child_process.spawnSync(python!, [join(path, 'gen.py')]);
  405. let currentFd = openSync(join(path, TESTCASES, 'gen.in'), 'w');
  406. writeSync(currentFd, genResult.stdout);
  407. closeSync(currentFd);
  408. }
  409. export function stressSolution(path: string, times: number){
  410. let sol = join(path, solFile());
  411. let out = join(path, ATTIC, 'sol');
  412. let brute = join(path, 'brute.cpp');
  413. if (!existsSync(sol)){
  414. throw new Error("Open a coding environment first.");
  415. }
  416. if (!existsSync(brute)){
  417. throw new Error("Upgrade environment first.");
  418. }
  419. let brout = join(path, ATTIC, 'brout');
  420. let solCompileResult = compileCode(sol, out);
  421. if (solCompileResult.status !== 0){
  422. throw new Error(`Compilation Error. ${sol}`);
  423. }
  424. let bruteCompileResult = compileCode(brute, brout);
  425. if (bruteCompileResult.status !== 0){
  426. throw new Error(`Compilation Error. ${brute}`);
  427. }
  428. let results = [];
  429. for (let index = 0; index < times; index++) {
  430. // Generate input testcase
  431. generateTestcase(path);
  432. // Generate output testcase from brute.cpp
  433. let inputFd = openSync(join(path, TESTCASES, 'gen.in'), 'r');
  434. let buffer = new Buffer(getMaxSizeInput());
  435. readSync(inputFd, buffer, 0, getMaxSizeInput(), 0);
  436. let tcData = buffer.toString();
  437. closeSync(inputFd);
  438. // Run without restrictions
  439. // TODO: 005
  440. let runResult = child_process.spawnSync(brout, {
  441. input: tcData,
  442. });
  443. // Finally write .out
  444. let currentFd = openSync(join(path, TESTCASES, 'gen.out'), 'w');
  445. writeSync(currentFd, runResult.stdout);
  446. closeSync(currentFd);
  447. // Check sol report same result than brute
  448. let result = timedRun(path, 'gen', getTimeout());
  449. if (result.status !== Veredict.OK){
  450. return new SolutionResult(result.status, 'gen');
  451. }
  452. results.push(result);
  453. }
  454. let maxTime = 0;
  455. for (let i = 0; i < results.length; i++){
  456. if (results[i].spanTime! > maxTime){
  457. maxTime = results[i].spanTime!;
  458. }
  459. }
  460. return new SolutionResult(Veredict.OK, undefined, maxTime);
  461. }
  462. export function veredictName(veredict: Veredict){
  463. switch (veredict) {
  464. case Veredict.OK:
  465. return "OK";
  466. case Veredict.WA:
  467. return "WA";
  468. case Veredict.TLE:
  469. return "TLE";
  470. case Veredict.RTE:
  471. return "RTE";
  472. case Veredict.CE:
  473. return "CE";
  474. default:
  475. throw new Error("Invalid Veredict");
  476. }
  477. }