Skip to content

Add a GitHub Actions CI workflow - #173

Merged
webianks merged 7 commits into
developmentfrom
webianks/ci
Oct 5, 2026
Merged

webianks merged 7 commits into
developmentfrom
webianks/ci

Conversation

@webianks

@webianks webianks commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator

Add a GitHub Actions CI workflow

Adds CI for the project, plus the small fixes needed for it to pass.

What runs

On every pull request, and on pushes to development and release/**, three jobs run in parallel:

Job Command Why
Lint ./gradlew :app:lintRelease Android lint on the release variant
Unit and screenshot tests ./gradlew :app:testDebugUnitTest then :app:validateDebugScreenshotTest Unit suite and Compose preview screenshots
Release build ./gradlew :app:bundleRelease The build that runs R8, so shrinking breakage is caught

Reports are uploaded as artifacts when a job fails. A newer push to the same branch cancels the run in progress.

How it builds without secrets

secrets.properties and google-services.json are gitignored, and the build reads both at configuration time. A shared setup action (.github/actions/setup) writes placeholder versions of both, so:

  • no repository secrets are required, and
  • pull requests from forks can run CI.

Nothing built in CI is shipped or calls a real API. Because the placeholder Firebase project has nowhere to receive the R8 mapping file, app/build.gradle skips the Crashlytics mapping upload when the CI environment variable is set. Local release builds are unchanged.

Fixes included so CI passes

  • formatReleaseDate used java.time, which needs API 26 while minSdk is 24. Lint flagged it (NewApi), and on Android 7 it silently returned an empty date. It now uses SimpleDateFormat.
  • The rating sheet read Locale.getDefault() inside a composable (NonObservableLocale). It now uses LocalLocale.
  • MyListsViewModelTest had a race: it waited for the load call to start instead of for its result, so a late load could undo the delete under test. It now waits for the loaded state.

Testing

All four commands were run locally in a clean checkout using only the placeholder config and CI=true, and all passed. This pull request is the first run on GitHub's runners, so these are not yet confirmed there:

  • The screenshot references were generated on macOS and may differ slightly on Linux. If they do, the references should be regenerated in CI.
  • The runner has to download SDK platform 37.

@webianks
webianks requested review from salRoid and a balanced review from Copilot October 4, 2026 14:50
@webianks webianks self-assigned this Oct 4, 2026
@webianks
webianks added this pull request to stack #174 October 4, 2026 14:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Date parsing became less strict, and one synchronization timeout can still allow a false-positive test.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

Adds GitHub Actions CI and compatibility fixes required for successful Android builds and tests.

Changes:

  • Adds parallel lint, test, screenshot, and release-build jobs.
  • Creates placeholder CI configuration and disables Crashlytics mapping uploads in CI.
  • Fixes date formatting, locale observation, and asynchronous tests.
File Description
.github/​workflows/​ci.yml Defines CI jobs, triggers, artifacts, and concurrency.
.github/​actions/​setup/​action.yml Configures Java, Gradle, and placeholder credentials.
app/​build.gradle Disables CI Crashlytics mapping uploads.
DateFormatterUtil.kt Replaces API-26 date handling.
RateMediaSheet.kt Uses Compose’s observable locale.
MyListsViewModelTest.kt Improves asynchronous state synchronization.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@webianks
webianks force-pushed the webianks/ci branch 3 times, most recently from ab04336 to 3281018 Compare October 4, 2026 15:49
salRoid
salRoid previously approved these changes Oct 4, 2026
Base automatically changed from webianks/content-v4-improvements to development October 4, 2026 16:50
- formatReleaseDate used java.time, which needs API 26 or core library desugaring while minSdk is 24 (NewApi), and silently returned an empty string on Android 7. Use SimpleDateFormat instead.
- The rating sheet read Locale.getDefault() inside a composable (NonObservableLocale).
- MyListsViewModelTest synchronised on getLists() being called rather than on its result reaching the StateFlow, so a late load could undo the optimistic delete under test.
Runs on pull requests and on pushes to development and release/**, as three parallel jobs: release lint, unit and screenshot tests, and the release bundle (the build that exercises R8).

secrets.properties and google-services.json are gitignored, so a shared setup action writes placeholders; no repository secrets are needed, which keeps CI working for pull requests from forks. With a placeholder Firebase project there is nowhere to upload the R8 mapping file, so that upload is skipped when CI is set.
Review feedback:
- formatReleaseDate accepted variable-width fields such as 1999-1-5 or 99-10-15, which LocalDate.parse used to reject. Check the yyyy-MM-dd shape before parsing and cover the malformed cases.
- waitFor in MyListsViewModelTest returned normally on timeout, so a test could carry on from a state that never arrived and still pass. It now fails on timeout.

Races seen on the GitHub runner, where work on a real Dispatchers.IO thread finished before an intermediate state was asserted:
- LoginViewModelTest: logout's cleanup could finish before the collector saw isLoggingOut go true, so the StateFlow conflated true -> false and never emitted.
- MyListsViewModelTest: the rollback could land before the optimistic removal was asserted.
The background step is now held on a latch until the intermediate state has been asserted.

Also upload the rendered screenshots when the test job fails, so differences between the macOS-generated references and the Linux runner can be inspected.
The teardowns called viewModelScope.cancel(), which returns while coroutines on real threads are still winding down. On the GitHub runner one from a previous test class touched Dispatchers.Main after MainDispatcherRule had reset it, and the failure was reported against the next test to start (ShowsViewModelTest). Cancel and join instead, in all 15 teardowns that shared the pattern.
23 of the 48 references, generated on macOS, differed from the Linux CI runner's renders by at most 2 levels per channel - invisible, but spread over up to 24% of an image's pixels, so a pixel-count threshold could not absorb it without also hiding real regressions. Replace those 23 with the runner's own renders so CI compares like with like.

Validating locally on macOS will now report those screens as different.
Renders the references on the Linux runner and uploads them as an artifact, to be committed after an intended UI change.
@webianks
webianks dismissed salRoid’s stale review October 4, 2026 16:50

The merge-base changed after approval.

salRoid
salRoid previously approved these changes Oct 4, 2026
The GitHub runner failed two more tests of the same kind as before, where a state is asserted while work on a real Dispatchers.IO thread can already have moved past it:

- MovieDetailsViewModelTest: toggleListMembership's rollback could land before the optimistic flip was asserted.
- FranchiseViewModelTest: the failing fetch could finish before the collector saw isLoading go true, so the StateFlow conflated true -> false.

A sweep of the suite found the same shape in LoginViewModelTest (getRequestToken failure) and CollectionsViewModelTest (trySync), which had not failed yet. All four now hold the background step until the intermediate state has been asserted. MovieDetailsViewModelTest's waitFor also fails on timeout now.
@webianks
webianks merged commit 5ac5f3e into development Oct 5, 2026
3 checks passed
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.

3 participants