Skip to content

Latest commit

 

History

History
161 lines (125 loc) · 9.75 KB

development.md

File metadata and controls

161 lines (125 loc) · 9.75 KB

Developing mops

Build and test

./gradlew check
./gradlew installMops
./gradlew :cli:run --args="--mps-home /path/to/mps --java-home /path/to/jbr --project-root /path/to/project daemon ping"
./gradlew :cli:run --args=--help
./gradlew :daemon:run --args=--help

The repository is a Gradle-rooted Kotlin prototype with two application subprojects: cli/ and daemon/. The ping command requires an MPS project root containing .mps/. --java-home can be omitted when the MPS distribution bundles Java.

Commits and pull requests

Commit messages and pull request titles follow Conventional Commits. prek install installs a commit-msg hook that checks each local commit and lists the allowed types when it rejects a message.

Pull requests are squash-merged. The pull request title becomes the commit message on main, so the Pull request title workflow checks it with the same hook. GitHub release notes list the merged pull requests by title.

Integration tests

./gradlew :cli:integrationTest runs the full CLI integration suite against every version listed in supportedMpsVersions in gradle.properties. ./gradlew check includes this matrix. Each version has its own task:

./gradlew :cli:integrationTestMps2024.1.6
./gradlew :cli:integrationTestMps2025.1.4
./gradlew :cli:integrationTestMps2026.1.1

Each task resolves its MPS distribution and matching host-specific JBR through mps-platform-cache. Distributions are cached and reused. Reports are written separately under cli/build/reports/tests/<task-name>/; JUnit XML results are under cli/build/test-results/<task-name>/. Each task runs test classes in two parallel test JVMs, and every test starts its own MPS daemon with its own daemon home. CI runs each version in a separate Ubuntu matrix job. The matrix lists the versions in .github/workflows/ci.yml, and the verification job fails if that list differs from supportedMpsVersions. Each job uploads its test reports even on success, and test start/completion events appear in the job log to help identify slow or stalled tests. Apply --tests to a version-specific task to select tests:

./gradlew :cli:integrationTestMps2024.1.6 --tests '*CodeModeIntegrationTest'

PRs, pushes to main, and manual workflow dispatch run the full integration matrix, the CLI smoke suite on macOS and Windows, and an Ubuntu verification job. Linux has no separate smoke job: the integration job for the last supported version runs every smoke test on Ubuntu. The verification job runs check -x :cli:integrationTest, covering all subprojects' unit tests, daemon tests, Gradle discovery tests, and packaging checks, and then runs the prek hooks on all files. The separate integration matrix covers the excluded CLI integration suite. The CI passed job requires every job to succeed. Release validation runs the complete check, including the full integration matrix.

To test an already extracted distribution, use the separate local task:

./gradlew :cli:integrationTestLocal -PtestMpsHome=/path/to/mps -PtestJbrHome=/path/to/java/home

The local task is excluded from check and the supported-version matrix. These tests use the generic MPS archive through the daemon launcher, without IDE-plugin host-platform validation.

Cross-platform smoke tests

./gradlew :cli:smokeTest
./gradlew :cli:smokeUnitTest
./gradlew :cli:smokeIntegrationTest

smokeTest combines unit and integration tests tagged @Tag("smoke"). The unit task uses the normal unit-test classpath and starts no MPS daemon. The integration task shares runtime configuration with the full integration suite and uses the last version listed in supportedMpsVersions and its matching JBR. Both tasks have separate reports. Tag a test class or method to add focused cross-platform smoke coverage; no workflow filter needs updating. Tagged tests also run in their normal unit/integration suites. The smoke tasks are not added to check, which already runs those suites.

Code-mode runtime tests

The installed CLI integration test exercises model lookup, an edit, saving and reopening the project, extension dispatch, and Groovy class identity. It runs both with the selected MPS distribution and with a test copy that omits its Groovy JAR. CI includes code-mode tests for every supported MPS version.

Bundled agent skills live under skills/ and ship alongside the CLI in its skills/ directory. CodeSkillExamplesTest extracts every fenced groovy block from skills/mops/SKILL.md and executes it verbatim against a fresh fixture copy. When adding an example, add behavioral assertions for its heading to that test. Run the examples with:

./gradlew :daemon:test --tests '*CodeSkillExamplesTest'

Bundled task recipe checks

The source catalog lives in examples/. Groovy maps hold the snippets and their assertions; Markdown templates supply prose and table-group slots. Catalogs contain named groups of ID-less examples and named standalone snippets; group members supply their row titles. generateExamples produces the CLI pages and a test manifest from that catalog. updateExamples refreshes the checked-in skill references; checkExamples rejects stale references and runs in both check and smokeTest.

./gradlew :cli:updateExamples :cli:checkExamples
./gradlew :cli:test --tests '*ExamplesCommandTest'

ExamplesCommandTest parses CLI recipes directly from the catalog with the current command tree, including commands in shell pipelines, without executing them. It validates JSON edit batches with the protocol serializer and generated schema, and checks bundled reference links. These checks catch syntax and protocol drift without starting MPS. The pre-commit CI job generates the pages first; Markdown lint includes the generated build output as well as checked-in references. Line-length checks apply to prose, excluding code blocks and tables globally.

BundledExamplesIntegrationTest creates a named JUnit dynamic test for each executable Groovy spec. It starts one daemon and gives every example fresh models in a disposable BaseLanguage project. Each spec declares its placeholder bindings, optional preparation, and assertions on CLI output or saved model data. The runner executes the same snippet used to generate the Markdown. Examples requiring other fixtures declare an explicit untested reason. The PR smoke suite uses the last supported MPS version, and the full integration matrix runs the specs on each version.

./gradlew :cli:smokeIntegrationTest --tests '*BundledExamplesIntegrationTest'

Test-running integration tests

TestFamiliesIntegrationTest checks native BaseLanguage, language, generator, Jupiter, parameterized, and legacy JUnit results. Its test methods share immutable reports from a completed full-project run and a parameterized-case selection run. The fixture daemon stops before any method inspects those reports. Slow and crashing models are removed from that fixture copy. Each request still executes in a separate worker process.

Integration JUnit output includes MOPS_CLI_TIMING for calls through the runCommandLine test helper. The stopDaemons helper emits MOPS_DAEMON_STOP_TIMING for daemon shutdown including its exit wait. CLI duration includes daemon startup when a call starts a daemon. Before deleting fixtures, stopDaemons copies test-run reports and preparation/Ant logs to cli/build/test-results/test-run-diagnostics/<MPS version>/<run ID>/, included in CI's test-report artifact. A job killed before fixture teardown may not retain these files.

Each test report's timingsMillis records process-local monotonic durations: preparationStartup measures preparation entry to project opening (or the no-build shortcut), build includes generation/compilation and saving, antStartupAndDiscovery includes Ant setup and worker startup through discovery, execution ends when the test plan finishes, and antShutdown ends when Ant exits. Event collection has 50 ms polling resolution. workerLifetime is the parent's enclosing spawn-to-exit measurement and overlaps the worker phases; do not add it to them. Missing phases were not reached or could not be saved before a process was killed. These timings do not separately measure plugin loading or indexing. Per-event report writes checkpoint the active duration, and orderly failures/timeouts close it.

TestRunningIntegrationTest uses fresh project copies for root test-case/model/module/project selection, multiple test modules, preparation failures, and worker lifecycle behavior. The fixtures live in test-projects/testing. The lifecycle test deliberately waits for two 90-second deadlines; allow several minutes per version. Runtime reload and daemon lifecycle tests also retain their independent process boundaries.

./gradlew :cli:integrationTestMps2024.1.6 --tests '*TestRunningIntegrationTest' --tests '*TestFamiliesIntegrationTest' \
  :cli:integrationTestMps2025.1.4 --tests '*TestRunningIntegrationTest' --tests '*TestFamiliesIntegrationTest' \
  :cli:integrationTestMps2026.1.1 --tests '*TestRunningIntegrationTest' --tests '*TestFamiliesIntegrationTest'

The build pins MPS 2026.1.1 in gradle/libs.versions.toml. To test an MPS 2026.2 development distribution, use integrationTestLocal with --tests '*TestRunningIntegrationTest' --tests '*TestFamiliesIntegrationTest' and the extracted distribution/JBR paths. For distributions containing lib/intellij.libraries.junit5.jar, the integration fixture adjusts Jupiter API imports to the JUnit module in its temporary copy.

On macOS, MPS's IDEA environment still initializes AWT in headless mode. A sandbox that prevents application registration can abort the JVM before tests start. Run with --no-daemon outside that sandbox so an existing sandboxed Gradle daemon is not reused.