core.ts 15 KB

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