agentleFS
DenisovAVDenisovAV/flutter_gemma/.claude/skills/ddp-device-test/SKILL.md

ddp-device-test

Skill654 starsChanged 27 days agoMITView on GitHub

Run an Android integration test on a real phone through Google's Developer Device Platform (DDP) — reserve the device, forward adb, stage models, run with am instrument, read the result, cancel. The only Android device lab for this repo; Firebase Test Lab is no longer used.

  • Installs packages

The file

We split this skill into the rules it gives an agent

5 rules. About 0 are common, 1 is distinctive, and 4 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Every Android test that needs a real phone — GPU (OpenCL on Adreno and Mali), Qualcomm NPU, memory, a release gate — runs on DDP.

    line 9
  2. Very specific:

    When gcloud says "Reauthentication required", ask the owner to run !

    line 25
  3. Very specific:

    Use run_instrumented.sh. Dart defines go through -Pdart-defines as base64, which the script does for KEY=VALUE pairs.

    line 115
  4. Very specific:

    zsh: write "${name}:cancel", never "$name:cancel" — :c is a history modifier and the request 404s.

    line 131
  5. Very specific:

    If it is lost anyway, or reserve failed after the request went out, ddp.sh sessions lists the project's latest sessions from the server and …

    line 135

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Android device tests on DDP

Every Android test that needs a real phone — GPU (OpenCL on Adreno and Mali), Qualcomm NPU, memory, a release gate — runs on DDP. Firebase Test Lab is not used any more (owner, 2026-10-14). The pieces here:

FileWhat it does
ddp.shOne session at a time: devices, reserve, status, extend, forward, cancel
run_instrumented.shBuilds the example's app + androidTest APKs for one integration_test/ file, installs, runs am instrument, saves logcat and the crash buffer

DDP bills per minute on krups-develop. Tell the owner before reserving, say roughly how long, keep one session open at a time, and ddp.sh cancel the moment the run is done — also after a failure.

One-time setup

  1. gcloud components install beta and, in the browser, gcloud auth login. When gcloud says "Reauthentication required", ask the owner to run ! gcloud auth login: the scripts use gcloud auth print-access-token, so expired Application Default Credentials do not matter.
  2. Google's forwarder lives outside the repo, in ~/.ddp (it ships with Google's developer-device-platform-basics skill, without a licence header, so it is not vendored here):
    mkdir -p ~/.ddp && cd ~/.ddp   # put demo_adb_forwarder.py + requirements.txt here
    python3 -m venv .venv && .venv/bin/pip install -r requirements.txt

Which phone

NeedDDP idPhoneSoC / GPU
Adreno GPUe1q-34Galaxy S24SM8650, Adreno 750
Mali GPUakita-35Pixel 8aTensor G3, Mali-G715
Mali GPU, the #545 crash devicea34x-36Galaxy A34Dimensity 1080, Mali-G68
Qualcomm NPU, HTP V75e1q-34Galaxy S24SM8650
Qualcomm NPU, HTP V79pa3q-35Galaxy S25 UltraSM8750
Qualcomm NPU, HTP V81m2q-36Galaxy S26+SM8850

A GPU gate is one Adreno and one Mali: they take different paths through the same accelerator (#545 crashed only on Mali). Write the DDP id and the GPU in every result row; "Galaxy S24" alone does not say which GPU ran. Gemma 4 needs 6 GB+ of RAM. ddp.sh devices <pattern> shows the catalog and how available each phone is (LOW can mean a long wait).

A run

D=.claude/skills/ddp-device-test
$D/ddp.sh reserve e1q-34 60          # waits for ACTIVE, then sets a 60-minute lifetime
$D/ddp.sh forward > ~/.ddp/forwarder.log 2>&1 & FWD=$!   # in the background
for _ in $(seq 60); do                 # the port appears within ~1 min
  SERIAL=$(grep -m1 -o 'localhost:[0-9]*' ~/.ddp/forwarder.log) && break
  kill -0 $FWD 2>/dev/null || break    # the forwarder died: read its log
  sleep 2
done
export SERIAL=${SERIAL:?no port: read ~/.ddp/forwarder.log}   # it ran `adb connect` itself
adb devices                            # localhost:<port>  device
adb -s $SERIAL shell getprop ro.soc.model

# Stage models where the tests look for them: the example's tests read
# /data/local/tmp/flutter_gemma_test/ (stagedModelPath), the hybrid-AI
# codelab's smoke test reads /data/local/tmp/flutter_edge_ai_test/.
adb -s $SERIAL shell mkdir -p /data/local/tmp/flutter_gemma_test
adb -s $SERIAL shell test -x /system/bin/curl && echo curl || echo no-curl   # Pixels have none
# with curl (Samsung):
adb -s $SERIAL shell "nohup /system/bin/curl --capath /system/etc/security/cacerts -sSL -C - \
  -o /data/local/tmp/flutter_gemma_test/<file> '<public URL>' \
  > /data/local/tmp/curl_<file>.log 2>&1 &"
adb -s $SERIAL shell ls -l /data/local/tmp/flutter_gemma_test/   # poll the size; pidof curl must stay set
# without curl (Pixel): from a local copy
adb -s $SERIAL push <local file> /data/local/tmp/flutter_gemma_test/<file>

$D/run_instrumented.sh <run-name> integration_test/<file>.dart [KEY=VALUE ...]
$D/ddp.sh cancel

run_instrumented.sh exits 0 only on a passing Dart summary with no native crash in the app's process.

What goes wrong

  • The forwarder serves one adb client. It runs adb connect itself; do not add your own adb connect or adb wait-for-device. If its log shows two Client connected lines and then ConnectionResetError: Connection lost, the local adb server is stale (seen 2026-10-14 with a server up for days): adb kill-server && adb start-server, then ddp.sh forward again.
  • The default session lifetime can be 15 minutes. reserve sets it; for a longer run ddp.sh extend <minutes>, then restart forward, since the forwarder's own lifetime is fixed when it starts.
  • The forwarder can drop after ~30 minutes, and it takes a running adb push with it (1.4–5.4 MB/s measured). Where the phone has curl, download with it under nohup as above (3–7 MB/s, survives a drop, -C - resumes). Use public URLs only: never put an HF token on a shared phone; for a gated file use a short-lived signed URL.
  • No secrets as KEY=VALUE. A dart-define is compiled into the APK, and the APK stays on a phone other people use after us. run_instrumented.sh refuses a name containing TOKEN, KEY, SECRET or PASSWORD. Stage gated models instead; a test that needs a cloud key (the hybrid-AI codelab's cloud half) runs on a device of your own.
  • Not every phone has curl. Samsung images ship /system/bin/curl; the Pixel 8a (akita-35) does not, and nohup only says so in the log file while a size poll waits for nothing (2026-10-14, 16 billed minutes). Check test -x /system/bin/curl first, poll pidof curl along with the size, and on a Pixel adb push a local copy: 284 MB took 50 s (5.4 MB/s), well inside the forwarder's ~30.
  • flutter test -d cannot attach through the forwarder (no Dart VM service). Use run_instrumented.sh. Dart defines go through -Pdart-defines as base64, which the script does for KEY=VALUE pairs.
  • am instrument can wait forever. Any flutter command rewrites GeneratedPluginRegistrant.java, and a release or non-Android one drops dev_dependency plugins, integration_test among them. The Dart log then ends with "integration_test plugin was not detected", FlutterTestRunner never gets a result, and the session bills until it expires (2026-10-14, 23 minutes). run_instrumented.sh regenerates the registrant in debug, refuses to run without IntegrationTestPlugin, and stops waiting a minute after the Dart summary.
  • am instrument says OK when a Dart setUpAll failed. The verdict is the I/flutter summary line, which the script prints; Rule 6b (no setUp that can throw) applies here too.
  • A native crash ends the process with no Dart error. The script saves logcat -b crash to crash.txt and flags Fatal signal/SIGSEGV; a signal is not an exception, so the engine's CPU fallback never sees it.
  • zsh: write "${name}:cancel", never "$name:cancel" — :c is a history modifier and the request 404s. ddp.sh already does this.
  • A session left open keeps billing. ddp.sh status before reserving; the state file is ~/.ddp/session.json, and a failed call leaves it as it was. If it is lost anyway, or reserve failed after the request went out, ddp.sh sessions lists the project's latest sessions from the server and ddp.sh cancel <name> ends one by name.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.