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>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-01 00:46:41 -04:00
1 parent 7eaf79370e
commit 4641b9ec9b
4 files changed
+84 -5

No files matched your search

+8 -5
View File
@@ -28,10 +28,12 @@ anything the scan does not reach.
## The `app/` directory each one builds into ## The `app/` directory each one builds into
`find_apks` matches APKs up to **two** directories below a project root, and `find_apks` matches APKs up to **two** directories below where it is
it is run against the *project* path, not a component's `cwd`. A test anchored, and it is anchored at a component's own directory --
project building into `test-projects/<name>/build/outputs/apk/` would `project.join(cwd)`, which is the project path itself for a component
therefore also be found from the repository root -- offered as a build of 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 Dev Updater itself, and, being the newest, served as the default. Somebody
pressing Update on the Dev Updater card would get a stub app. pressing Update on the Dev Updater card would get a stub app.
@@ -57,8 +59,9 @@ appear.
| `two-variants` | Builds `debug` and `release`, so the phone gets a variant picker and `resolve_apk` has something to fall back from. | | `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. | | `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. | | `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 four are unaccepted when first added, so each is also a run through the All five are unaccepted when first added, so each is also a run through the
acceptance gate. acceptance gate.
## The apps themselves ## The apps themselves
@@ -0,0 +1,45 @@
// A test project for Dev Updater, not a real app. See ../README.md.
//
// Two independent Android clients in one checkout, each with its own `cwd`
// and its own package. That is the shape tdep-survey has, and the one no
// other fixture here covers: `service-and-app` also has two components, but
// only one of them produces an APK, so everything that goes wrong when
// *two* do is invisible to it.
//
// Three things it exists to keep honest, all of which were real bugs:
//
// - Each component's builds are found under its own directory.
// `APK_PATTERNS` is anchored at `project.join(cwd)`; anchored at the
// project root instead, its two-level pattern reaches both clients'
// outputs and whichever matched first was reported as *every*
// component's package, size, variants and mtime. Two clients installing
// over different packages is what makes that wrong rather than merely
// redundant -- the card would check one app's installed state and
// report it as the other's.
//
// - "Never built at all" is asked per component. Because the
// root-anchored scan sees both, building either client once made the
// whole project read as "something is built here", and the other one --
// never built -- silently stopped being offered its own first build.
//
// - Building one component instead of every one. `tablet` is slow on
// purpose so that `?component=` is *visible*: pressing Update on
// `phone` finishes in about two seconds, and paying for the tablet's
// build to get it is the whole complaint that scoping exists to answer.
label: "Test: Two Clients",
components: [
Apk(
name: "phone",
// The command resolves against the project root while `cwd` says
// where to run it, so this is not `./build.sh` -- same split as
// Dev Updater's own declaration.
build: "phone/build.sh",
cwd: "phone",
),
Apk(
name: "tablet",
build: "tablet/build.sh",
cwd: "tablet",
),
],
+11
View File
@@ -0,0 +1,11 @@
#!/bin/sh
# Builds this project's phone client -- the fast half of the pair, so that
# the two components finishing at visibly different times is the fixture's
# own evidence that only one of them ran.
#
# Run from `two-clients/phone`, which is this component's `cwd`, so the
# shared builder writes into `phone/app/build/outputs/apk/` and the APK is
# reachable from this component's directory but not from the project root's
# two-level patterns.
set -eu
../../lib/build-apk.sh --package com.example.dutest.phone --label "Test Phone"
+20
View File
@@ -0,0 +1,20 @@
#!/bin/sh
# Builds this project's tablet client -- the slow half of the pair, on
# purpose. A real second client is slow for a real reason (an ARM
# cross-compile, an NDK step); here it is six seconds of pretending, which
# is enough to see that pressing Update on `phone` did not wait for it.
#
# Reports its own progress, so the sleep also exercises a per-component bar
# next to a component that finishes before the bar has moved.
set -eu
STEPS=6
i=0
while [ "$i" -lt "$STEPS" ]; do
i=$((i + 1))
echo "@@progress $i/$STEPS"
echo "==> Cross-compiling native bits, part $i of $STEPS"
sleep 1
done
../../lib/build-apk.sh --package com.example.dutest.tablet --label "Test Tablet"