Skip to content

Commit c85a7ab

Browse files
[ALT-10963] Change check_profile , opening project view. (#39)
* Don't overwrite `check_profile` from server with an empty string. ^ALT-10963 * add logging * fix opening project * fix opening projects. * potential fix python checker error * review fix * bump version --------- Co-authored-by: Oleksandr Liemiahov <tsyklop@gmail.com>
1 parent db8ce0d commit c85a7ab

22 files changed

Lines changed: 723 additions & 48 deletions

File tree

documentation/CheckAction.md

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
## CheckAction
2+
3+
This document explains how `org.hyperskill.academy.learning.actions.CheckAction` works, what code paths it executes, and how local and remote checking are combined.
4+
5+
### Entry point
6+
7+
The action is registered in `intellij-plugin/hs-core/resources/META-INF/hs-core.xml`:
8+
9+
```xml
10+
<action id="HyperskillEducational.Check" class="org.hyperskill.academy.learning.actions.CheckAction"/>
11+
```
12+
13+
The implementation is in `intellij-plugin/hs-core/src/org/hyperskill/academy/learning/actions/CheckAction.kt`.
14+
15+
### High-level flow
16+
17+
`CheckAction.actionPerformed(...)` does the following:
18+
19+
1. Retrieves `Project` from the action event.
20+
2. Refuses to run while indexing is active (`DumbService.isDumb(project)`).
21+
3. Clears the check details view.
22+
4. Saves all open documents.
23+
5. Reads the current task from `TaskToolWindowView`.
24+
6. Acquires a per-project lock so only one check can run at a time.
25+
7. Calls all registered `CheckListener.beforeCheck(...)`.
26+
8. Starts a background task `StudyCheckTask`.
27+
28+
Relevant code:
29+
30+
- `CheckAction.kt`, `actionPerformed`
31+
- `CheckAction.kt`, `CheckActionState`
32+
33+
### Core execution graph
34+
35+
```text
36+
CheckAction.actionPerformed
37+
-> CheckListener.beforeCheck(project, task)
38+
-> StudyCheckTask.run(indicator)
39+
-> localCheck(indicator)
40+
-> recreateTestFiles(taskDir)
41+
-> maybe createTests(invisibleTestFiles)
42+
-> checker.check(indicator)
43+
-> if local result is Failed: stop
44+
-> remoteCheckerForTask(project, task)
45+
-> remoteChecker?.check(project, task, indicator) ?: localResult
46+
-> onSuccess()
47+
-> update task.status
48+
-> update task.feedback
49+
-> saveItem(task)
50+
-> checker.onTaskSolved() / checker.onTaskFailed()
51+
-> TaskToolWindowView.checkFinished(...)
52+
-> CheckListener.afterCheck(project, task, result)
53+
```
54+
55+
### How the local checker is chosen
56+
57+
`StudyCheckTask` creates a local checker through the course configurator:
58+
59+
```kotlin
60+
val configurator = task.course.configurator
61+
checker = configurator?.taskCheckerProvider?.getTaskChecker(task, project)
62+
```
63+
64+
The generic selection logic is in `intellij-plugin/hs-core/src/org/hyperskill/academy/learning/checker/TaskCheckerProvider.kt`.
65+
66+
Important behavior:
67+
68+
- `RemoteEduTask` -> no local checker
69+
- `CodeTask` -> no local checker
70+
- `TheoryTask` -> no local checker
71+
- `UnsupportedTask` -> no local checker
72+
- `EduTask` -> configurator-specific local checker
73+
- `OutputTask` -> `OutputTaskChecker`
74+
- `IdeTask` -> `IdeTaskChecker`
75+
76+
This is why not every task goes through local tests.
77+
78+
### What `localCheck(...)` does
79+
80+
`StudyCheckTask.localCheck(...)` performs the local phase:
81+
82+
1. If no checker exists, returns `CheckResult.NO_LOCAL_CHECK`.
83+
2. Resolves the task directory.
84+
3. Recreates test files from task metadata before running checks.
85+
4. For non-Hyperskill courses, restores invisible test files into the project tree.
86+
5. Calls `checker.check(indicator)`.
87+
88+
Relevant methods:
89+
90+
- `CheckAction.kt`, `localCheck`
91+
- `CheckAction.kt`, `recreateTestFiles`
92+
- `CheckAction.kt`, `createTests`
93+
94+
### Why test files are recreated
95+
96+
Before running a local check, the plugin restores author-provided test files from the task model. This prevents a learner from changing, deleting, or corrupting tests to fake a successful check.
97+
98+
For framework lessons, the plugin uses `FrameworkLessonManager` cached original test files rather than the current `task.taskFiles`, because framework tasks may otherwise carry stale test data from another stage.
99+
100+
### How local test execution works
101+
102+
The standard base implementation for local `EduTask` checking is `EduTaskCheckerBase` in:
103+
104+
`intellij-plugin/hs-core/src/org/hyperskill/academy/learning/checker/EduTaskCheckerBase.kt`
105+
106+
Its `check(...)` method:
107+
108+
1. Hides the Run tool window.
109+
2. Calls `EnvironmentChecker.getEnvironmentError(project, task)`.
110+
3. Builds run configurations.
111+
4. Validates each configuration.
112+
5. Executes them using IntelliJ run infrastructure.
113+
6. Collects test results.
114+
7. Produces a `CheckResult`.
115+
116+
If execution starts but tests do not actually run, subclasses may translate stderr into a more specific error result.
117+
118+
### How run configurations are built and executed
119+
120+
The utility layer is in:
121+
122+
`intellij-plugin/hs-core/src/org/hyperskill/academy/learning/checker/CheckUtils.kt`
123+
124+
Key pieces:
125+
126+
- `getCustomRunConfigurationForChecker(...)`
127+
- `createDefaultRunConfiguration(...)`
128+
- `executeRunConfigurations(...)`
129+
130+
Execution details:
131+
132+
- A custom task-specific run configuration in `.idea/runConfigurations` is preferred if present.
133+
- Otherwise, IntelliJ derives temporary run configurations from PSI context.
134+
- Configurations run through `ProgramRunner` and `ExecutionEnvironmentBuilder`.
135+
- The plugin tracks all started environments and waits on a `CountDownLatch`.
136+
- Test results are collected through a `TestResultCollector`.
137+
138+
### Local-vs-remote ordering
139+
140+
`StudyCheckTask.run(...)` always does the local phase first:
141+
142+
```kotlin
143+
val localCheckResult = localCheck(indicator)
144+
if (localCheckResult.status === CheckStatus.Failed) {
145+
result = localCheckResult
146+
return
147+
}
148+
val remoteChecker = remoteCheckerForTask(project, task)
149+
result = remoteChecker?.check(project, task, indicator) ?: localCheckResult
150+
```
151+
152+
This means:
153+
154+
- A local `Failed` result stops the pipeline immediately.
155+
- Remote checking is attempted only if local checking did not fail.
156+
- If there is no remote checker, the final result is the local result.
157+
158+
### How the remote checker is chosen
159+
160+
Remote checkers are selected through the extension point:
161+
162+
`HyperskillEducational.remoteTaskChecker`
163+
164+
The manager is:
165+
166+
`intellij-plugin/hs-core/src/org/hyperskill/academy/learning/checker/remote/RemoteTaskCheckerManager.kt`
167+
168+
It:
169+
170+
1. Collects all registered remote checkers.
171+
2. Filters them by `canCheck(project, task)`.
172+
3. Returns exactly one checker or `null`.
173+
4. Throws if more than one checker matches.
174+
175+
### Hyperskill remote checker
176+
177+
Hyperskill registers:
178+
179+
```xml
180+
<remoteTaskChecker implementation="org.hyperskill.academy.learning.stepik.hyperskill.checker.HyperskillRemoteTaskChecker"/>
181+
```
182+
183+
Implementation:
184+
185+
`intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/checker/HyperskillRemoteTaskChecker.kt`
186+
187+
It can check only when:
188+
189+
- `task.course is HyperskillCourse`
190+
- `HyperskillCheckConnector.isRemotelyChecked(task)` is true
191+
192+
That remote set is:
193+
194+
- `CodeTask`
195+
- `RemoteEduTask`
196+
- `UnsupportedTask`
197+
198+
### Hyperskill remote task behavior by type
199+
200+
#### CodeTask
201+
202+
Path:
203+
204+
- `HyperskillRemoteTaskChecker.check(...)`
205+
- `HyperskillCheckConnector.checkCodeTask(...)`
206+
207+
Behavior:
208+
209+
1. Validates that task id exists.
210+
2. Tries websocket-based check session.
211+
3. If websocket path fails, falls back to HTTP submission.
212+
4. Polls the submission until status changes from `evaluation`.
213+
214+
#### RemoteEduTask
215+
216+
Path:
217+
218+
- `HyperskillRemoteTaskChecker.check(...)`
219+
- `HyperskillCheckConnector.checkRemoteEduTask(...)`
220+
221+
Behavior:
222+
223+
1. Validates that task id exists.
224+
2. Collects solution files.
225+
3. Creates an attempt.
226+
4. Creates and posts a submission.
227+
5. Polls until final status is received.
228+
229+
#### UnsupportedTask
230+
231+
Path:
232+
233+
- `HyperskillRemoteTaskChecker.check(...)`
234+
- `HyperskillCheckConnector.checkUnsupportedTask(...)`
235+
236+
Behavior:
237+
238+
1. Does not create a new submission.
239+
2. Loads existing submissions from the platform.
240+
3. Derives solved/failed state from the latest known submissions.
241+
242+
### Hyperskill local EduTask behavior
243+
244+
Hyperskill `EduTask` is special:
245+
246+
- It is checked locally.
247+
- It does not use the remote checker.
248+
- After the local result is finalized, `HyperskillCheckListener.afterCheck(...)` may asynchronously post the solution to Hyperskill.
249+
250+
This listener is registered in `META-INF/Hyperskill.xml`.
251+
252+
Implementation:
253+
254+
`intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/checker/HyperskillCheckListener.kt`
255+
256+
Behavior:
257+
258+
- `beforeCheck(...)` updates Hyperskill metrics.
259+
- `afterCheck(...)` restarts metrics for unsolved current tasks.
260+
- For non-remote, non-theory Hyperskill tasks, it posts the local solution to Hyperskill in background if the user is logged in.
261+
262+
This postback is not the same thing as remote checking. It is a follow-up side effect after local checking has already completed.
263+
264+
### Result finalization
265+
266+
When background execution succeeds, `StudyCheckTask.onSuccess()`:
267+
268+
1. Stores `task.status = checkResult.status`
269+
2. Stores `task.feedback`
270+
3. Persists the task via `saveItem(task)`
271+
4. Calls checker lifecycle hooks
272+
5. Updates the tool window
273+
6. Refreshes course progress and project view
274+
7. Calls all `CheckListener.afterCheck(...)`
275+
276+
### Error and cancel behavior
277+
278+
If the background task is cancelled:
279+
280+
- the tool window is reset to `readyToCheck()`
281+
282+
If an exception happens:
283+
284+
- refresh-token failure is converted to `failedToSubmit(...)`
285+
- everything else becomes generic `failedToCheck`
286+
287+
### Practical summary
288+
289+
For the common Hyperskill task classes:
290+
291+
- `EduTask`: local tests first, then optional async postback to Hyperskill
292+
- `RemoteEduTask`: remote-only check
293+
- `CodeTask`: remote-only check
294+
- `UnsupportedTask`: remote state lookup only, no fresh submission
295+
296+
This local-first and then maybe-remote pattern is the most important rule to keep in mind when debugging `CheckAction`.

0 commit comments

Comments
 (0)