Skip to content

Commit 04f2573

Browse files
authored
Merge pull request #773 from rage/fixes
Fixed windows hash bug and improved CLI spawn errors
2 parents 6fb50fb + 0cf0f43 commit 04f2573

File tree

11 files changed

+111
-35
lines changed

11 files changed

+111
-35
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## [3.5.1] - 2025-11-20
4+
5+
- Fixed CLI hash comparison on Windows
6+
- Improved error messages on failure to run CLI
7+
38
## [3.5.0] - 2025-10-09
49

510
- Improved exercise submission packaging to avoid overly large archives

config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
const path = require("path");
55

6-
const TMC_LANGS_RUST_VERSION = "0.39.1";
6+
const TMC_LANGS_RUST_VERSION = "0.39.4";
77

88
const mockTmcLocalMooc = {
99
__TMC_BACKEND_URL__: JSON.stringify("http://localhost:4001"),

package-lock.json

Lines changed: 17 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "test-my-code",
33
"displayName": "TestMyCode",
4-
"version": "3.5.0",
4+
"version": "3.5.1",
55
"description": "TestMyCode extension for Visual Studio Code",
66
"categories": [
77
"Education",

src/actions/user.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,13 @@ export async function testExercise(
110110

111111
if (!course.perhapsExamMode) {
112112
const executablePath = getActiveEditorExecutablePath(actionContext);
113-
const [testRunner, testInterrupt] = tmc.val.runTests(exercise.uri.fsPath, executablePath);
114-
const [validationRunner, validationInterrupt] = tmc.val.runCheckstyle(exercise.uri.fsPath);
113+
const { process: testRunner, interrupt: testInterrupt } = tmc.val.runTests(
114+
exercise.uri.fsPath,
115+
executablePath,
116+
);
117+
const { process: validationRunner, interrupt: validationInterrupt } = tmc.val.runCheckstyle(
118+
exercise.uri.fsPath,
119+
);
115120
testInterrupts.set(testRunId, [testInterrupt, validationInterrupt]);
116121
const exerciseName = exercise.exerciseSlug;
117122

src/api/tmc.ts

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ import {
1212
ConnectionError,
1313
EmptyLangsResponseError,
1414
ForbiddenError,
15+
InitializationError,
1516
InvalidTokenError,
1617
ObsoleteClientError,
1718
RuntimeError,
19+
SpawnError,
1820
} from "../errors";
1921
import {
2022
CliOutput,
@@ -261,45 +263,59 @@ export default class TMC {
261263
exercisePath: string,
262264
pythonExecutablePath?: string,
263265
progressCallback?: (progressPct: number, message?: string) => void,
264-
): [Promise<Result<RunResult, BaseError>>, () => void] {
266+
): {
267+
process: Promise<Result<RunResult, BaseError | InitializationError>>;
268+
interrupt: () => void;
269+
} {
265270
const env: { [key: string]: string } = {};
266271
if (pythonExecutablePath) {
267272
env.TMC_LANGS_PYTHON_EXEC = pythonExecutablePath;
268273
}
269-
const { interrupt, result } = this._spawnLangsProcess({
274+
const process = this._spawnLangsProcess({
270275
args: ["run-tests", "--exercise-path", exercisePath],
271276
env,
272277
onStdout: (data) =>
273278
progressCallback?.(100 * data["percent-done"], data.message ?? undefined),
274279
onStderr: (data) => Logger.info("Rust Langs", data),
275280
processTimeout: CLI_PROCESS_TIMEOUT,
276281
});
282+
if (process.err) {
283+
return { process: Promise.resolve(process), interrupt: (): void => {} };
284+
}
285+
const { interrupt, result } = process.val;
277286
const postResult = result.then((res) =>
278287
res
279288
.andThen((x) => this._checkLangsResponse(x, "test-result"))
280289
.map((x) => x.data["output-data"]),
281290
);
282291

283-
return [postResult, interrupt];
292+
return { process: postResult, interrupt };
284293
}
285294

286295
public runCheckstyle(
287296
exercisePath: string,
288297
progressCallback?: (progressPct: number, message?: string) => void,
289-
): [Promise<Result<StyleValidationResult | null, BaseError>>, () => void] {
290-
const { interrupt, result } = this._spawnLangsProcess({
298+
): {
299+
process: Promise<Result<StyleValidationResult | null, BaseError>>;
300+
interrupt: () => void;
301+
} {
302+
const process = this._spawnLangsProcess({
291303
args: ["checkstyle", "--locale", "en", "--exercise-path", exercisePath],
292304
onStdout: (data) =>
293305
progressCallback?.(100 * data["percent-done"], data.message ?? undefined),
294306
onStderr: (data) => Logger.info("Rust Langs", data),
295307
processTimeout: CLI_PROCESS_TIMEOUT,
296308
});
309+
if (process.err) {
310+
return { process: Promise.resolve(process), interrupt: (): void => {} };
311+
}
312+
const { interrupt, result } = process.val;
297313
const checkstyleResult = result.then((res) =>
298314
res
299315
.andThen((x) => this._checkLangsResponse(x, "validation"))
300316
.map((x) => x.data["output-data"]),
301317
);
302-
return [checkstyleResult, interrupt];
318+
return { process: checkstyleResult, interrupt };
303319
}
304320

305321
// ---------------------------------------------------------------------------------------------
@@ -946,7 +962,11 @@ export default class TMC {
946962
}
947963
}
948964

949-
const res = await this._spawnLangsProcess(langsArgs).result;
965+
const process = this._spawnLangsProcess(langsArgs);
966+
if (process.err) {
967+
return process;
968+
}
969+
const res = await process.val.result;
950970
return res
951971
.andThen((x) => this._checkLangsResponse(x, outputDataKind))
952972
.andThen((x) => {
@@ -1020,7 +1040,9 @@ export default class TMC {
10201040
*
10211041
* @returns Rust process runner.
10221042
*/
1023-
private _spawnLangsProcess(commandArgs: LangsProcessArgs): LangsProcessRunner {
1043+
private _spawnLangsProcess(
1044+
commandArgs: LangsProcessArgs,
1045+
): Result<LangsProcessRunner, InitializationError | SpawnError> {
10241046
const { args, env, obfuscate, onStderr, onStdout, stdin, processTimeout } = commandArgs;
10251047

10261048
let theResult: OutputData | undefined;
@@ -1044,16 +1066,21 @@ export default class TMC {
10441066

10451067
let active = true;
10461068
let interrupted = false;
1047-
const cprocess = cp.spawn(this.cliPath, args, {
1048-
env: {
1049-
...process.env,
1050-
...env,
1051-
RUST_LOG: "debug,rustls=warn,reqwest=warn",
1052-
TMC_LANGS_TMC_ROOT_URL: tmcBackendUrl,
1053-
TMC_LANGS_MOOC_ROOT_URL: moocBackendUrl,
1054-
TMC_LANGS_CONFIG_DIR: tmcLangsConfigDir,
1055-
},
1056-
});
1069+
let cprocess;
1070+
try {
1071+
cprocess = cp.spawn(this.cliPath, args, {
1072+
env: {
1073+
...process.env,
1074+
...env,
1075+
RUST_LOG: "debug,rustls=warn,reqwest=warn",
1076+
TMC_LANGS_TMC_ROOT_URL: tmcBackendUrl,
1077+
TMC_LANGS_MOOC_ROOT_URL: moocBackendUrl,
1078+
TMC_LANGS_CONFIG_DIR: tmcLangsConfigDir,
1079+
},
1080+
});
1081+
} catch (error) {
1082+
return Err(new SpawnError(error, "Failed to run tmc-langs-cli"));
1083+
}
10571084
if (stdin) {
10581085
cprocess.stdin.write(stdin + "\n");
10591086
}
@@ -1190,7 +1217,8 @@ ${error.message}`;
11901217
kill(cprocess.pid as number);
11911218
}
11921219
};
1193-
return { interrupt, result };
1220+
const res = { interrupt, result };
1221+
return Ok(res);
11941222
}
11951223
}
11961224

src/errors.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,7 @@ export class FileSystemError extends BaseError {
6666
export class ExerciseMigrationError extends BaseError {
6767
public readonly name = "Exercise Migration Error";
6868
}
69+
70+
export class SpawnError extends BaseError {
71+
public readonly name = "Langs Spawn Error";
72+
}

src/extension.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ import {
1313
} from "./config/constants";
1414
import Settings from "./config/settings";
1515
import { UserData } from "./config/userdata";
16-
import { EmptyLangsResponseError, HaltForReloadError, InitializationError } from "./errors";
16+
import {
17+
EmptyLangsResponseError,
18+
HaltForReloadError,
19+
InitializationError,
20+
SpawnError,
21+
} from "./errors";
1722
import * as init from "./init";
1823
import { randomPanelId, TmcPanel } from "./panels/TmcPanel";
1924
import Storage from "./storage";
@@ -33,10 +38,11 @@ function initializationError(dialog: Dialog, step: string, error: Error, cliFold
3338
error,
3439
"If this issue is not resolved, the extension may not function properly.",
3540
);
36-
if (error instanceof EmptyLangsResponseError) {
37-
Logger.error(
38-
"The above error may have been caused by an interfering antivirus program. " +
39-
"Please add an exception for the following folder:",
41+
if (error instanceof EmptyLangsResponseError || error instanceof SpawnError) {
42+
Logger.errorWithDialog(
43+
dialog,
44+
"This error may have been caused by an interfering antivirus program. " +
45+
"Please try adding an exception for the following folder:",
4046
cliFolder,
4147
);
4248
}

src/init/ensureLangsUpdated.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,13 @@ async function ensureLangsUpdated(
4646
const cliHash = new Sha256();
4747
const cliData = fs.readFileSync(cliPath);
4848
cliHash.update(cliData);
49-
const cliDigest = Buffer.from(cliHash.digestSync()).toString("hex");
49+
// windows returns the calculated hash in uppercase for some reason...
50+
const cliDigest = Buffer.from(cliHash.digestSync()).toString("hex").toLowerCase();
5051

51-
const hashData = fs.readFileSync(shaPath, "utf-8").split(" ")[0];
52+
const hashData = fs.readFileSync(shaPath, "utf-8").split(" ")[0].trim();
5253
if (cliDigest !== hashData) {
5354
Logger.error("Mismatch between CLI and checksum, trying redownload");
55+
Logger.debug(`CLI "${cliDigest}", hash "${hashData}"`);
5456
deleteSync(cliFolder, { force: true });
5557
const result = await downloadLangs(
5658
cliFolder,

0 commit comments

Comments
 (0)