Skip to content

fix github actions build#41

Open
Tsyklop wants to merge 15 commits into
mainfrom
fix-github-build
Open

fix github actions build#41
Tsyklop wants to merge 15 commits into
mainfrom
fix-github-build

Conversation

@Tsyklop

@Tsyklop Tsyklop commented May 20, 2026

Copy link
Copy Markdown
Collaborator

This branch overhauls how framework lessons persist and propagate learner changes across stages, replacing the legacy diff-based storage with a git-like content-addressable history (snapshots / commits / refs), and fixes the resulting navigation, course-update, and submission flows. It also gets CI and the :intellij-plugin:test suite back to green.

Why
The legacy diff-based framework storage lost or mis-propagated learner code across stages (ALT-10961, ALT-10998, ALT-10961 follow-ups). A self-contained snapshot history makes navigation/update deterministic and restart-safe, and fixes the data-loss bugs the failing tests captured.

What changed

  1. Framework lesson storage — git-like architecture (ALT-10961)
    FrameworkLessonManagerImpl reworked to store full per-stage snapshots as content-addressable blobs with commits and stage_ / step_ refs and a HEAD (see framework/impl/FRAMEWORK_STORAGE.md). Full contents (not diffs) are stored so state survives IDE restart / offline.
    FrameworkStorage / LegacyFrameworkStorage / framework/storage/UserChanges updated; legacy records are migrated on the fly.
    Navigation propagation reworked: snapshot-based target state, ancestor checks to decide when a merge is needed, and a Keep / Replace conflict path (PropagationConflictDialog) with auto-Keep/auto-Replace heuristics based on whether each side has learner changes.
  2. Course update preserves learner changes
    FrameworkTaskUpdateInfo, FrameworkLessonHistory, UpdateUtils: when a course update adds/removes/changes a framework task file, the learner's local edits are preserved instead of being overwritten; author content is kept in the task model for later revert.
  3. Supporting changes
    SolutionLoaderBase, TaskNavigationAction, handlers/handlersUtils, HyperskillUtils, HyperskillOpenInIdeRequestHandler, submissions/utils, ext/TaskExt/StudyItemExt, VirtualFileExt adjusted for the new storage model (original test/template file caching, ref handling, rename/move).

@meanmail meanmail left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of the framework-lesson storage rework.

The change looks well thought out and is generously commented, and the RemoveTaskFile.apply fix (state -= path instead of state[path] = text) is a real, correct bug fix. A few cross-cutting points in addition to the inline comments:

  • Mixed line endings. FrameworkLessonManagerImpl.kt now mixes CRLF and LF (base had ~1689 CRLF lines, this branch ~1479), so many -/+ pairs in the diff are byte-for-byte identical and only differ by EOL. This roughly doubles the diff and hides the real changes. Suggest adding a .gitattributes (*.kt text eol=lf) and normalizing EOLs in a separate, content-free commit.
  • LOG.warn for debug output. Several LOG.warn("saveExternalChanges: ... keys=...") / ChangeFile.apply: ... messages are debug-level detail logged at WARN. This will be noisy in production logs; please downgrade to debug/trace.

currentState: Map<String, String>,
targetState: Map<String, String>
): Result {
if (ApplicationManager.getApplication().isUnitTestMode) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test-mode branch substitutes a different dialog implementation, so the real PropagationConflictDialog.result path is never exercised by tests. Acceptable for headless runs, but worth abstracting the conflict decision behind an interface that can be replaced in tests.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one still looks unaddressed in the latest revision (isUnitTestMode branch at PropagationConflictDialog.kt:198). Same idea as the Hyperskill handler fix — could the conflict decision be injected so tests exercise the real result path instead of a test-only fork? Leaving open.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test-mode branch uses Messages.showYesNoDialog, which routes through IntelliJ's TestDialogManager. Tests can already drive the decision deterministically via our withTestDialog(TestDialog.YES/NO) helper, so the conflict choice is replaceable in tests without a dedicated interface. The custom DialogWrapper UI can't be rendered headless regardless, so a separate abstraction wouldn't add testable surface. Keeping the current idiomatic approach.

Comment thread intellij-plugin/build.gradle.kts
@get:JsonProperty(CHECK_PROFILE)
@set:JsonProperty(CHECK_PROFILE)
@get:JsonInclude(JsonInclude.Include.ALWAYS)
@get:JsonInclude(JsonInclude.Include.NON_EMPTY)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ALWAYS -> NON_EMPTY: an empty checkProfile will now be omitted from serialization. Please confirm the backend / YAML round-trip tolerates a missing check_profile field. Note EduTaskReply.score is changed in the opposite direction (to ALWAYS) in this same PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gentle follow-up: did you confirm the backend / YAML round-trip is fine with check_profile omitted when empty (no consumer expects the field to always be present)? Leaving open until confirmed.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed safe. A missing check_profile deserializes to the "" default (RemoteEduTask.checkProfile), and StudentTaskChangeApplier guards with isNotEmpty() before applying, so an omitted field is a no-op. The existing test remote edu task serialization test asserts no check_profile line for a profile-less task, which only holds under NON_EMPTY. This is local YAML round-trip only — the backend submission payload uses the separate hyperskillAPI/StepikAPI mixins. EduTaskReply.score → ALWAYS is the opposite direction intentionally because score is required in the submission payload.

Tsyklop added 2 commits June 18, 2026 13:28
LegacyFrameworkStorage:
- import java.io.ByteArrayInputStream instead of inline fully-qualified names
- document the deliberate version fall-through in both record readers

Logging:
- downgrade debug-level LOG.warn traces to LOG.debug in FrameworkLessonManagerImpl
  and UserChanges; keep genuine warnings/caught exceptions at WARN

Cleanup:
- drop the SlowOperations.knownIssue("EDU-XXXX") placeholder wrappers and the
  now-unused import (call YamlFormatSynchronizer.saveItem directly)

Remove isUnitTestMode test-only forks (use injectable seams so tests exercise
the real production path):
- FrameworkLessonManagerImpl: remove the test-only currentTask.record assignment;
  the update test now seeds the legacy record itself
- HyperskillOpenInIdeRequestHandler: inject the stages executor (and loader) so
  the CompletableFuture/timeout/cancellation path runs in tests instead of a
  synchronous short-circuit; add HyperskillLoadStagesWithTimeoutTest covering
  Ok / timeout / failure / ProcessCanceledException
Add a `*.kt text eol=lf` rule to .gitattributes (the repo previously disabled
normalization globally via `* -crlf`) and renormalize the two .kt files that
still had CRLF/mixed endings:
- FrameworkLessonManagerImpl.kt (was mixed CRLF/LF, which doubled its PR diff)
- FrameworkLessonUserFilesPropagationTest.kt

This is a content-free change (line endings only); no source was modified.
@Tsyklop Tsyklop requested a review from meanmail June 18, 2026 12:50

@meanmail meanmail left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review — approved ✅

Thanks for the thorough follow-up. I re-checked the changes against the new revision (49d0371) rather than just the commit messages, and the previous round of feedback is handled well.

Addressed (verified in code):

  • Removed the test-only currentTask.record fork in FrameworkLessonManagerImpl; the update test now seeds the legacy record itself.
  • Dropped the EDU-XXXX placeholder wrappers — YamlFormatSynchronizer.saveItem is called directly.
  • HyperskillOpenInIdeRequestHandler now injects the stages executor/loader, so the CompletableFuture / timeout / cancellation path runs under tests (HyperskillLoadStagesWithTimeoutTest covers Ok / timeout / failure / ProcessCanceledException).
  • LegacyFrameworkStorage: java.io.ByteArrayInputStream imported; the deliberate version fall-through documented in both readers.
  • Debug-level LOG.warn traces downgraded to LOG.debug; remaining WARN logs are genuine failures/caught exceptions.
  • .kt line endings normalized to LF in a dedicated commit — the diff is reviewable now.
  • pluginVerification.failureLevel: accepted the rationale (keep the marketplace-validated build green).

The RemoveTaskFile.apply fix (state -= path) remains the core correctness win.

Non-blocking, left open for your call:

  • PropagationConflictDialog.kt:198 still has an isUnitTestMode branch — same injectable-seam idea as the Hyperskill handler would let tests exercise the real result path. A test-quality nit, not a correctness issue.
  • RemoteEduTaskYamlMixin.checkProfile switched ALWAYSNON_EMPTY: just confirm no backend/round-trip consumer requires check_profile to always be present.

Neither is a blocker, so approving. Could you resolve the addressed threads on your side? I don't have permission to resolve them in this repo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants