Files
irisandClaude Opus 5 4641b9ec9b A test project with two Android clients
The two-APK case had no fixture: it was verified against a scratch project
that was not kept, so the shape three recent bugs came out of was covered
only by a tempfile unit test. test-projects/two-clients is two Apk
components in one checkout, each with its own cwd and its own package --
what tdep-survey looks like, and what service-and-app cannot stand in for,
since only one of its two components produces an APK.

tablet is slow on purpose (six seconds of pretending to cross-compile,
reporting its own progress) beside a phone that builds in two, because that
is what makes ?component= visible rather than merely asserted: building one
finishes while the other has not started.

Verified live against a throwaway server rather than reasoned about.
build?component=phone ran phone alone; afterwards phone reported built and
tablet did not, which is the sibling-masking bug -- under the root-anchored
"never built at all" check the tablet would have read as current and been
refused its own first build. prepare?component=tablet then ran tablet
alone and it did build. Each component resolved its own APK under its own
directory, with its own size and mtime, so nothing reports the first
match for both.

Also corrects a sentence in that README that went stale when discovery
moved per-component: find_apks is anchored at project.join(cwd), not at
the project path. The conclusion it supports is unchanged -- every other
fixture declares no cwd, so the anchor is still the project root for them,
and building into app/ is still what keeps a stub APK from being offered
as a build of Dev Updater itself. Re-ran the check command in that section:
only the updater's own APK is reachable from the repository root.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 00:46:41 -04:00

89 lines
4.9 KiB
Markdown

# Test projects
Small fake projects for exercising Dev Updater against something other than
Dev Updater. They are apps in the sense the server cares about -- a path
with a declaration and a build that produces an APK -- and nothing else.
The point of having them is control. Dev Updater's job is installing *other*
projects' builds, so trying it out needs something to install, and a real
project only ever does what it happens to do: it builds, or it doesn't, and
you cannot ask it to fail on the third component, produce two variants, or
run a service that writes a coloured log. These can be asked for any of
that.
## Why they are not picked up automatically
`scan_roots` in `server/src/discover.rs` descends two levels below each
configured repo root, **and stops at any directory carrying a
`.dev-updater.ron`** -- a declaration says "this directory is the project",
so what is underneath is that project's own layout rather than more projects
to offer. This checkout carries one at its root, so with `/home/bob/repos`
as a root the scan considers `dev-updater` itself and descends no further.
Nothing in here is ever suggested.
To work with them, add `.../dev-updater/test-projects` as a repo root of its
own from the app's settings; each directory below is then a suggestion. Or
type one project's path into the Add screen, which is the escape hatch for
anything the scan does not reach.
## The `app/` directory each one builds into
`find_apks` matches APKs up to **two** directories below where it is
anchored, and it is anchored at a component's own directory --
`project.join(cwd)`, which is the project path itself for a component
declaring no `cwd`, as all of these do except `two-clients`. A test project
building into `test-projects/<name>/build/outputs/apk/` would therefore also
be found from the repository root -- offered as a build of
Dev Updater itself, and, being the newest, served as the default. Somebody
pressing Update on the Dev Updater card would get a stub app.
One more level down (`test-projects/<name>/app/build/outputs/apk/`) is out of
reach from the root while staying within reach from the test project, which
is why `lib/build-apk.sh` writes there. It is also what a real Gradle project
looks like, so nothing about it reads as a workaround. Check it after
changing any of this:
```sh
cd <repo root> && for p in build/outputs/apk/*/*.apk */build/outputs/apk/*/*.apk \
*/*/build/outputs/apk/*/*.apk; do [ -f "$p" ] && echo "$p"; done
```
Only `app/androidApp/build/outputs/apk/debug/androidApp-debug.apk` should
appear.
## What is here
| project | what it is for |
|---|---|
| `hello-app` | The plain case: one APK, one variant. Declares **no** `resources:`, so Uninstall has to show its data and config toggles disabled and say why. |
| `two-variants` | Builds `debug` and `release`, so the phone gets a variant picker and `resolve_apk` has something to fall back from. |
| `breakable` | `touch breakable/break-the-build` and its next build fails, in colour, on stderr. `rm` it and it stops. |
| `service-and-app` | A `Server` beside an `Apk`: two components building at once, the whole service contract, a runtime log that is not this server's own, and a `resources:` declaration giving Uninstall real paths. |
| `two-clients` | Two `Apk` components in one checkout, each with its own `cwd` and package. The only fixture where *two* components produce an APK, which is what per-component discovery, the per-component "never built at all" check, and `?component=` all need to be exercised against. `tablet` is slow on purpose so that building one rather than both is visible. |
All five are unaccepted when first added, so each is also a run through the
acceptance gate.
## The apps themselves
One screen showing the app's label, its package, and when the installed copy
landed -- `lib/StubActivity.java`, shared by all of them, because what
differs between these projects is what Dev Updater has to do with them, not
what the app is. The install time is `PackageInfo.lastUpdateTime`, which is
the exact value the freshness check compares an APK's mtime against, so the
screen shows what the card is reasoning about.
They are built by `lib/build-apk.sh` -- aapt2, javac, d8, apksigner, about
two seconds -- rather than by Gradle. A Gradle daemon is around a gigabyte
resident and four of these would be four daemons, which is how this machine
ran itself out of memory on 2026-08-30; and a fixture you wait forty seconds
for stops getting used. The cost is that the script is a small build system,
which is why it is one file and should stay one: a test project that needs
more than a screen with its own name on it wants a real project.
The signing key is generated once at
`$XDG_DATA_HOME/dev-updater/test-projects/debug.keystore`, outside the
repository -- the checkout is a mount shared with the host, and a stable key
is what lets a rebuild install over the copy already on a device. Delete it
to produce a signature-mismatch install failure on purpose.