The Dart suite spent 77% of its cost compiling one isolate per test file while `flutter test` used half the cores, and every Maestro flow replayed a full Jellyfin onboarding before its first real assertion. - Add scripts/run_tests.sh, which runs `flutter test` with -j set to the cores the process may actually use instead of the ncpu/2 default. Measured on 8 cores: 190s -> 136s; -j 12 regresses to 165s, so it scales to the core count rather than hard-coding one. CI and CONTRIBUTING use it. A cgroup v2 quota, a cgroup v1 quota, and the cpuset/affinity nproc reports can each be the binding limit independently, so the detector takes the smallest; trusting whichever it found first would oversubscribe 4x on a container holding an 8-CPU quota while pinned to 2. Covered by scripts/test_run_tests.py, which the ci_guard_checks.sh glob picks up. - Add .maestro/subflows/ensure_onboarded.yaml: cold-start the app and only onboard when no session is stored. Flows that just need a signed-in Home use it; 02_onboarding_home, 08_logout, 09_download_offline_playback and the profile regressions keep clearing state. 59s -> 16s per flow. - Guard onboarding's two optional taps behind visibility checks. A missed `optional: true` tap still runs the full element search, costing 3.0s and 7.8s per onboarding to find nothing. - Disable device animation scales in run_maestro.py, restored by the existing cleanup path. CI's emulator got this from the runner flag; physical devices never did. - Shorten the watch_together setup-timeout replacement from 500ms to the 10ms the same file already proves sufficient, and shorten the retry backoff at the one site that missed it: 8.04s -> 1.59s of execution. - Make the LAN discovery waits deadline-based and resend the beacon while polling. Loopback UDP drops datagrams under load, which timed out a wait that could never be satisfied; this was the suite's one flaky test. - Fix 08_logout, which searched for "Logout" and "Are you sure you want to logout?" after both strings became "Log out". The flow had been failing and aborting the suite before 09 ever ran. flutter test 190s -> 131s. Maestro's Android suite 621s -> 385s across the eight flows the baseline reached, and now runs all nine green.
145 lines
6.6 KiB
Markdown
145 lines
6.6 KiB
Markdown
# Contributing
|
|
|
|
## Getting Started
|
|
|
|
1. Fork and clone the repository
|
|
2. Run `flutter pub get` to install dependencies
|
|
3. Run `scripts/codegen.sh` to generate translations and Dart model code
|
|
4. Start developing!
|
|
|
|
## Development
|
|
|
|
- Follow Dart/Flutter conventions
|
|
- Run `dart format .` to format Dart code (note: generated files like `*.g.dart` are excluded from CI checks)
|
|
- Run `scripts/format_native.sh --fix` to format Kotlin, Swift, C++, C, Objective-C, and native headers
|
|
- Run `flutter analyze` before submitting to check for issues
|
|
- Run `scripts/run_tests.sh` to run the test suite (same as `flutter test`, but scaled to your core count)
|
|
- Test your changes thoroughly
|
|
|
|
### Code Quality Checks
|
|
|
|
The project includes automated CI checks that run on all pull requests:
|
|
|
|
1. **Code Formatting**: Ensures code follows Dart and native formatting standards
|
|
- Run locally: `dart format .` to format Dart files
|
|
- Run locally: `scripts/format_native.sh --fix` to format native files
|
|
- Note: CI only checks non-generated files (excludes `.g.dart`, `.freezed.dart`)
|
|
- Generated files are reformatted automatically by build tools
|
|
|
|
2. **Static Analysis**: Checks for code issues and potential bugs
|
|
- Run locally: `flutter analyze`
|
|
- Note: CI excludes generated files from analysis (configured in `analysis_options.yaml`)
|
|
|
|
3. **Generated Code**: Ensures generated translations and model files are current
|
|
- Run locally: `scripts/codegen.sh --check`
|
|
|
|
4. **Tests**: Runs unit and widget tests (when available)
|
|
- Run locally: `scripts/run_tests.sh`
|
|
- This is `flutter test` with `-j` set to the core count. The default is half your cores, which
|
|
leaves most of the machine idle because the suite is dominated by per-file compilation.
|
|
Arguments are forwarded, so `scripts/run_tests.sh test/widgets/some_test.dart` works.
|
|
|
|
All these checks must pass before your changes can be merged.
|
|
|
|
### Maestro end-to-end tests
|
|
|
|
Android E2E tests use [Maestro](https://maestro.mobile.dev/) against a disposable, pre-seeded Jellyfin container.
|
|
|
|
Prerequisites: Java 17, Flutter and Android SDK/platform tools, a running Android emulator, Docker, and the
|
|
[Maestro CLI](https://docs.maestro.dev/getting-started/installing-maestro).
|
|
|
|
Run the suites from the repository root (`py -3` can replace `python3` on Windows):
|
|
|
|
```bash
|
|
python3 scripts/run_maestro.py basic # Basic user flows
|
|
python3 scripts/run_maestro.py catalog # Catalog and music flows
|
|
python3 scripts/run_maestro.py media # Codec playback and track selection
|
|
```
|
|
|
|
Run one flow with `--flow`:
|
|
|
|
```bash
|
|
python3 scripts/run_maestro.py basic --flow .maestro/flows/04_search.yaml
|
|
```
|
|
|
|
Use `--skip-build` to reuse the debug APK and `--skip-jellyfin-build` to reuse the Jellyfin image. Set
|
|
`--device <adb-serial>` when multiple devices are connected; physical devices also require `--adb-reverse`.
|
|
|
|
Top-level flows live in `.maestro/flows/`, shared setup in `.maestro/subflows/`, and focused regressions in
|
|
`.maestro/regression_flows/`. Automatic PR groups are declared in `scripts/run_maestro_ci.py::GROUPS`. Every top-level
|
|
regression flow must be registered either there or in `DESTRUCTIVE_MANUAL_TARGETS`; reusable subflows are not
|
|
independent tests. A manual-only classification must state why the flow cannot run automatically and must not be
|
|
described as CI coverage. `.github/workflows/e2e.yml` runs only automatic targets and uploads diagnostics on failure.
|
|
|
|
The profile-isolation and profile-teardown regressions create and remove profile connections, so they are a destructive
|
|
manual target rather than automatic PR coverage. Run them only against the pre-seeded Jellyfin fixture and a disposable
|
|
emulator, using the required opt-in:
|
|
|
|
```bash
|
|
python3 scripts/run_maestro_ci.py profile-regressions --disposable-emulator
|
|
```
|
|
|
|
The target refuses to start without `--disposable-emulator`. Each profile flow writes to its own Jellyfin log and
|
|
diagnostics directory under `build/maestro-profile-regressions/`.
|
|
|
|
### Production container image updates
|
|
|
|
Production images in `server/Dockerfile` and `server/docker-compose.yml` use a readable version or source-revision tag
|
|
plus an authoritative multi-platform index digest. The adjacent `Platforms` declaration records the supported
|
|
`linux/amd64` and `linux/arm64` variants. Never replace these references with a mutable tag or a single-platform child
|
|
manifest.
|
|
|
|
Update a production image only through a reviewed change:
|
|
|
|
1. For the Bugs service, first record the running container's image ID, repository digest, platform, and OCI source
|
|
revision without printing its environment. Prefer that reviewed running identity; selecting anything else is a
|
|
service upgrade, not a routine pin refresh.
|
|
2. Review the upstream source revision and changelog, provenance, vulnerability results, and manifest contents. Resolve
|
|
the readable tag and digest-qualified reference independently and confirm they identify the same OCI index in two
|
|
clean caches. The index must contain both declared platforms; provenance/attestation descriptors do not count as
|
|
runnable platforms.
|
|
3. Change the readable tag, full `sha256` index digest, and adjacent platform declaration together. Include the old and
|
|
new identities, manifest/platform evidence, review findings, smoke results, and rollback notes in the change.
|
|
4. Before changing the Bugs digest, exercise it with non-production configuration and a disposable volume. Review
|
|
migrations, take a restorable `bugs_data` backup, then validate a cloned volume. A forward-only migration rolls back
|
|
with the prior digest and pre-change backup, not by changing the image reference alone.
|
|
5. Run `python3 scripts/check_container_image_pins.py`, `python3 scripts/test_check_container_image_pins.py`, and
|
|
`(cd server && go test ./...)`. Inspect the rendered Compose configuration and rebuilt images locally without
|
|
exposing configuration values. Do not publish or deploy from a review checkout, and never fall back to `latest` when
|
|
a digest is unavailable.
|
|
|
|
## Internationalization (i18n)
|
|
|
|
This project uses `slang` for internationalization with JSON files.
|
|
|
|
### Adding New Strings
|
|
|
|
1. Add your string to `lib/i18n/strings.i18n.json`:
|
|
```json
|
|
{
|
|
"section": {
|
|
"myNewString": "My new text"
|
|
}
|
|
}
|
|
```
|
|
|
|
2. Run `dart run slang` to regenerate translation files
|
|
|
|
3. Use in your code:
|
|
```dart
|
|
Text(t.section.myNewString)
|
|
```
|
|
|
|
### Adding New Languages
|
|
|
|
1. Create new JSON file: `lib/i18n/[locale].i18n.json`
|
|
2. Copy structure from `en.i18n.json` and translate values
|
|
3. Run `dart run slang` to regenerate files
|
|
|
|
### Guidelines
|
|
|
|
- Organize strings logically in nested objects
|
|
- Use camelCase for keys
|
|
- Keep strings concise and clear
|
|
- Always run `dart run slang` after changes
|