Skip to content

Commit 4a8142f

Browse files
committed
Run checkstyle when running tests
1 parent eefa7c1 commit 4a8142f

File tree

9 files changed

+107
-24
lines changed

9 files changed

+107
-24
lines changed

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.37.0";
6+
const TMC_LANGS_RUST_VERSION = "0.37.1";
77

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

shared/langsSchema.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export type CliOutput =
99

1010
export type DataKind =
1111
| { "output-data-kind": "error"; "output-data": { kind: Kind; trace: Array<string> } }
12-
| { "output-data-kind": "validation"; "output-data": StyleValidationResult }
12+
| { "output-data-kind": "validation"; "output-data": StyleValidationResult | null }
1313
| { "output-data-kind": "available-points"; "output-data": Array<string> }
1414
| { "output-data-kind": "exercises"; "output-data": Array<string> }
1515
| {
@@ -517,14 +517,14 @@ export type SubmissionStatus = "processing" | "fail" | "ok" | "error" | "hidden"
517517

518518
export type TmcStyleValidationResult = {
519519
strategy: TmcStyleValidationStrategy;
520-
validation_errors: Record<string, Array<TmcStyleValidationError>> | null;
520+
validationErrors: Record<string, Array<TmcStyleValidationError>> | null;
521521
};
522522

523523
export type TmcStyleValidationError = {
524524
column: number;
525525
line: number;
526526
message: string;
527-
source_name: string;
527+
sourceName: string;
528528
};
529529

530530
export type TmcStyleValidationStrategy = "FAIL" | "WARN" | "DISABLED";

shared/lib.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@
55
import { Uri } from "vscode";
66
import * as util from "node:util";
77

8-
import { Course, Organization, RunResult, SubmissionFinished } from "./langsSchema";
8+
import {
9+
Course,
10+
Organization,
11+
RunResult,
12+
StyleValidationResult,
13+
SubmissionFinished,
14+
} from "./langsSchema";
915
import { createIs } from "typia";
1016

1117
/**
@@ -483,6 +489,7 @@ export type TestResultData = {
483489
};
484490
pasteLink?: string;
485491
disabled?: boolean;
492+
styleValidationResult?: StyleValidationResult | null;
486493
};
487494

488495
export type TestCourse = {

src/actions/user.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { downloadNewExercisesForCourse } from "./downloadNewExercisesForCourse";
2222
import { ActionContext } from "./types";
2323
import { updateCourse } from "./updateCourse";
2424

25-
export const testInterrupts: Map<number, () => void> = new Map();
25+
export const testInterrupts: Map<number, Array<() => void>> = new Map();
2626

2727
/**
2828
* Authenticates and logs the user in if credentials are correct.
@@ -104,11 +104,12 @@ export async function testExercise(
104104

105105
if (!course.perhapsExamMode) {
106106
const executablePath = getActiveEditorExecutablePath(actionContext);
107-
const [testRunner, interrupt] = tmc.runTests(exercise.uri.fsPath, executablePath);
108-
testInterrupts.set(testRunId, interrupt);
107+
const [testRunner, testInterrupt] = tmc.runTests(exercise.uri.fsPath, executablePath);
108+
const [validationRunner, validationInterrupt] = tmc.runCheckstyle(exercise.uri.fsPath);
109+
testInterrupts.set(testRunId, [testInterrupt, validationInterrupt]);
109110
const exerciseName = exercise.exerciseSlug;
110111

111-
Logger.info(`Running local tests for ${exerciseName}`);
112+
Logger.info(`Running local tests and validations for ${exerciseName}`);
112113
const testResults = await testRunner;
113114
Logger.info(`Tests finished for ${exerciseName}`);
114115

@@ -121,13 +122,26 @@ export async function testExercise(
121122
return Ok.EMPTY;
122123
}
123124

125+
const validationResults = await validationRunner;
126+
Logger.info(`Validations finished for ${exerciseName}`);
127+
128+
if (validationResults.err) {
129+
TmcPanel.postMessage({
130+
type: "testError",
131+
target: panel,
132+
error: validationResults.val,
133+
});
134+
return Ok.EMPTY;
135+
}
136+
124137
data = {
125138
testResult: testResults.val,
126139
id: courseExercise.id,
127140
courseSlug: course.name,
128141
exerciseName,
129142
tmcLogs: testResults.val.logs,
130143
disabled: course.disabled,
144+
styleValidationResult: validationResults.val,
131145
};
132146

133147
if (TmcPanel.sidePanel === undefined) {

src/api/tmc.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
OutputData,
3737
RunResult,
3838
StatusUpdateData,
39+
StyleValidationResult,
3940
Submission,
4041
SubmissionFeedbackResponse,
4142
SubmissionFinished,
@@ -284,6 +285,25 @@ export default class TMC {
284285
return [postResult, interrupt];
285286
}
286287

288+
public runCheckstyle(
289+
exercisePath: string,
290+
progressCallback?: (progressPct: number, message?: string) => void,
291+
): [Promise<Result<StyleValidationResult | null, BaseError>>, () => void] {
292+
const { interrupt, result } = this._spawnLangsProcess({
293+
args: ["checkstyle", "--locale", "en", "--exercise-path", exercisePath],
294+
onStdout: (data) =>
295+
progressCallback?.(100 * data["percent-done"], data.message ?? undefined),
296+
onStderr: (data) => Logger.info("Rust Langs", data),
297+
processTimeout: CLI_PROCESS_TIMEOUT,
298+
});
299+
const checkstyleResult = result.then((res) =>
300+
res
301+
.andThen((x) => this._checkLangsResponse(x, "validation"))
302+
.map((x) => x.data["output-data"]),
303+
);
304+
return [checkstyleResult, interrupt];
305+
}
306+
287307
// ---------------------------------------------------------------------------------------------
288308
// Settings commands
289309
// ---------------------------------------------------------------------------------------------

src/panels/TmcPanel.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -709,10 +709,12 @@ export class TmcPanel {
709709
break;
710710
}
711711
case "cancelTests": {
712-
const interrupt = testInterrupts.get(message.testRunId);
713-
if (interrupt) {
714-
interrupt();
715-
testInterrupts.delete(message.testRunId);
712+
const interrupts = testInterrupts.get(message.testRunId);
713+
if (interrupts) {
714+
for (const interrupt of interrupts) {
715+
interrupt();
716+
testInterrupts.delete(message.testRunId);
717+
}
716718
}
717719
break;
718720
}

webview-ui/src/components/TestResults.svelte

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,32 @@
11
<script lang="ts">
22
import { derived, writable } from "svelte/store";
3-
import { TestCase, TestResult } from "../shared/langsSchema";
3+
import {
4+
TestCase,
5+
TestResult,
6+
TmcStyleValidationResult,
7+
TmcStyleValidationStrategy,
8+
} from "../shared/langsSchema";
49
import Checkbox from "./Checkbox.svelte";
510
import ProgressBar from "./ProgressBar.svelte";
611
import { vscode } from "../utilities/vscode";
712
813
export let totalPoints: number;
914
export let successPoints: number;
1015
export let testResults: Array<TestResult | TestCase>;
16+
export let validationResult: TmcStyleValidationResult | null;
1117
export let solutionUrl: string | null;
1218
19+
const validationStrategy: TmcStyleValidationStrategy = validationResult?.strategy ?? "DISABLED";
20+
const validationErrors = validationResult?.validationErrors ?? {};
21+
const validationErrorsEntries = Object.entries(validationErrors);
22+
// validations pass if strategy is not set to fail, or if there are no validation errors
23+
const validationsPassed = validationStrategy !== "FAIL" || validationErrorsEntries.length === 0;
24+
1325
const allTestsFailed = testResults.find((tr) => tr.successful) === undefined;
1426
const allTestsPassed = testResults.find((tr) => !tr.successful) === undefined;
27+
const exercisePassed = allTestsPassed && validationsPassed;
1528
// if all tests failed or passed, no need to show the checkbox
16-
const alwaysShowPassedTests = allTestsFailed || allTestsPassed;
29+
const alwaysShowPassedTests = allTestsFailed || exercisePassed;
1730
1831
const showPassedTestsChecked = writable<boolean>(false);
1932
const showPassedTests = derived(showPassedTestsChecked, ($showPassedTestsChecked) => {
@@ -53,15 +66,25 @@
5366
</div>
5467

5568
<div class="test-results-container">
69+
{#each validationErrorsEntries as [path, pathValidationErrors]}
70+
<div class="test failed-container">
71+
<h2 class="failed">Code quality issue found</h2>
72+
<h3>File: {path}</h3>
73+
{#each pathValidationErrors as pathValidationError}
74+
<pre
75+
class="test-message">Line {pathValidationError.line}, column {pathValidationError.column}: {pathValidationError.message}</pre>
76+
{/each}
77+
</div>
78+
{/each}
5679
{#each testResults as testResult}
5780
{#if testResult.successful}
5881
<div class="test passed-container" hidden={!$showPassedTests}>
59-
<h2 class="passed">PASS:</h2>
82+
<h2 class="passed">Test passed!</h2>
6083
<h3>{testResult.name}</h3>
6184
</div>
6285
{:else}
6386
<div class="test failed-container">
64-
<h2 class="failed">FAIL:</h2>
87+
<h2 class="failed">Test failed</h2>
6588
<h3>{testResult.name}</h3>
6689
<pre class="test-message">{testResult.message}</pre>
6790
</div>

webview-ui/src/panels/ExerciseSubmission.svelte

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,12 @@
8989
{/if}
9090
{:else if $submissionResult.status === "hidden"}
9191
<h1>Processing the submission finished</h1>
92+
{:else if $submissionResult.status === "fail"}
93+
<!-- validation failure etc. -->
94+
<h1>Some tests failed on the server</h1>
9295
{:else}
93-
<h1>Something went wrong</h1>
96+
<h1>Something went wrong...</h1>
97+
<div>Submission status: {$submissionResult.status}</div>
9498
{/if}
9599

96100
<vscode-button
@@ -159,6 +163,7 @@
159163
totalPoints={panel.exercise.availablePoints}
160164
successPoints={$submissionResult.points.length}
161165
testResults={$submissionResult.test_cases ?? []}
166+
validationResult={$submissionResult.validations}
162167
solutionUrl={$submissionResult.solution_url}
163168
/>
164169
{/if}

webview-ui/src/panels/ExerciseTests.svelte

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@
3737
$testResults.testResult.testResults.find((tr) => !tr.successful) === undefined
3838
);
3939
});
40+
const validationsFailed = derived(testResults, ($testResults) => {
41+
const validationStrategy = $testResults?.styleValidationResult?.strategy;
42+
const validationErrors = Object.entries(
43+
$testResults?.styleValidationResult?.validationErrors ?? {},
44+
).length;
45+
return validationStrategy === "FAIL" && validationErrors > 0;
46+
});
4047
4148
onMount(() => {
4249
vscode.postMessage({
@@ -91,21 +98,25 @@
9198
</script>
9299

93100
{#if !$tryingToRunTestsForExam && !$testError}
101+
<h1>{panel.exercise.name}</h1>
94102
{#if $testResults === undefined}
95-
<h1>{panel.exercise.name}: Running tests</h1>
103+
<h2>Running tests</h2>
96104
{:else if $testResults.testResult.status === "PASSED"}
97-
<h1>{panel.exercise.name}: Tests passed</h1>
105+
<h2>Tests passed</h2>
98106
{:else if $testResults.testResult.status === "TESTS_FAILED"}
99-
<h1>{panel.exercise.name}: Tests failed</h1>
107+
<h2>Tests failed</h2>
100108
{:else if $testResults.testResult.status === "COMPILE_FAILED"}
101-
<h1>{panel.exercise.name}: Compilation failed</h1>
109+
<h2>Compilation failed</h2>
102110
{:else if $testResults.testResult.status === "TESTRUN_INTERRUPTED"}
103-
<h1>{panel.exercise.name}: The test run was interrupted</h1>
111+
<h2>The test run was interrupted</h2>
104112
{:else if $testResults.testResult.status === "GENERIC_ERROR"}
105-
<h1>{panel.exercise.name}: An error occurred during the test run</h1>
113+
<h2>An error occurred during the test run</h2>
106114
{:else}
107115
{assertUnreachable($testResults.testResult.status)}
108116
{/if}
117+
{#if $validationsFailed}
118+
<h2>Code quality checks failed</h2>
119+
{/if}
109120

110121
<vscode-button
111122
role="button"
@@ -167,6 +178,7 @@
167178
totalPoints={$totalPoints}
168179
successPoints={$successPoints}
169180
testResults={$testResults.testResult.testResults}
181+
validationResult={$testResults.styleValidationResult ?? null}
170182
solutionUrl={null}
171183
/>
172184
{/if}

0 commit comments

Comments
 (0)