mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
* fix(cli): fall back to node_modules for @uatu/spec resolution
Drop the hard failure when the uatu source tree is not reachable from
the spec file. Users integrating uatu in their own app have @uatu/spec
installed via npm; esbuild now resolves it from node_modules.
* build(gradle): drop :sample-app include from root settings
The sample now has its own Gradle project in examples/sample-app/android.
* build(sample-app): vendor gradle wrapper
Users running the sample build the APK via ./gradlew from inside the
sample-app's own android/ directory, no repo-root wrapper required.
* build(sample-app): make gradle project standalone
Drop the project(':sdk-android') dependency in favor of the Maven
Central coordinate io.github.priyanshujain:sdk-android. The sample now
owns its settings.gradle.kts and gradle.properties, so it builds
without any pieces of the uatu source tree.
* chore(sample-app): declare @uatu/spec npm dependency
Mirrors what a downstream user would put in their own package.json.
Uses file: for pre-release development; becomes a normal semver pin
once @uatu/spec ships to npm.
* docs(sample-app): rewrite justfile and add README
Justfile drops repo_root; all recipes run against the local gradle
wrapper and uatu from PATH. README is scoped to what a user needs to
run the sample against their own device.
* docs(manual): update sample install steps to standalone layout
./gradlew :sample-app:installDebug no longer exists; the sample owns
its own wrapper under android/.
* feat(sdk): log when Uatu.start succeeds
Silent SDK start makes the "SDK didn't connect" failure mode
impossible to debug. One INFO line at start time is enough.
* fix(sidecar): launch via am start -W instead of monkey
monkey -p <pkg> -c LAUNCHER 1 is unreliable on API 36+: it reports no
error but silently fails to start the activity, so the SDK never runs
and the CLI times out on the SDK-accept handshake.
Resolve the launcher activity via `cmd package resolve-activity
--brief` and launch it with `am start -W -n`. -W makes the call block
until the activity is up, which also makes the subsequent SDK
accept timing deterministic.
* feat(cli): auto-resolve Android device; boot AVD if none connected
--avd becomes optional. Resolution order:
- use any already-connected adb device;
- else if --avd names an existing AVD, boot it and wait for boot;
- else if --avd is missing or names no AVD, error with a clear message.
Falls back to $ANDROID_HOME/emulator/emulator when the binary is not on
PATH, so a standard Android SDK install works without extra shell setup.
* docs(sample-app): AVD is optional; document both paths
just test runs against any connected device. If none, pass AVD=<name>
to have uatu boot the emulator for you.
* feat(cli): auto-discover Android SDK; auto-pick the lone AVD
adb and emulator are looked up via PATH, then $ANDROID_HOME,
$ANDROID_SDK_ROOT, ~/Library/Android/sdk, ~/Android/Sdk, and the
Homebrew cask path. The discovered platform-tools directory is
prepended to the sidecar's PATH so its adb subprocess calls work too.
When --avd isn't passed and no device is connected, the CLI picks the
sole local AVD and boots it. Multiple AVDs → error listing them.
* build(make): add `make install` that go-installs uatu onto PATH
Puts `uatu` into $GOBIN (or $GOPATH/bin) so the sample and any local
dev flow can call it without PATH= prefixes.
* docs(sample-app): zero-config just test; dotenv-load for persistence
Drop the expectation that users prefix commands with PATH=, ANDROID_HOME=,
or AVD=. `just test` now works as-is; optional knobs can be pinned in a
.env file alongside the justfile.
* fix(sample-app): auto-detect ANDROID_HOME for Gradle tasks
The Go CLI finds the SDK itself, but AGP still needs ANDROID_HOME to
resolve `sdk.dir`. The justfile now resolves it from env or canonical
install paths before invoking ./gradlew, so `just install` works out
of the box on a standard Android SDK setup.
* gitignore runs directory for sample app
157 lines
4.5 KiB
Go
157 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestParseTestArgs_Defaults(t *testing.T) {
|
|
options, err := parseTestArgs([]string{
|
|
"--spec", "s.ts",
|
|
"--bundle-id", "com.example",
|
|
"--avd", "Pixel_5_API_33",
|
|
}, io.Discard)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if options.spec != "s.ts" || options.bundleID != "com.example" {
|
|
t.Errorf("unexpected options: %+v", options)
|
|
}
|
|
if options.platform != "android" {
|
|
t.Errorf("platform default: got %q, want android", options.platform)
|
|
}
|
|
if options.duration != 5*time.Minute {
|
|
t.Errorf("duration default: got %v, want 5m", options.duration)
|
|
}
|
|
if options.output != "./runs" {
|
|
t.Errorf("output default: got %q, want ./runs", options.output)
|
|
}
|
|
if options.seed != 0 {
|
|
t.Errorf("seed default: got %d, want 0", options.seed)
|
|
}
|
|
}
|
|
|
|
func TestParseTestArgs_AllFlags(t *testing.T) {
|
|
options, err := parseTestArgs([]string{
|
|
"--spec", "s.ts",
|
|
"--bundle-id", "com.example",
|
|
"--platform", "android",
|
|
"--avd", "Pixel_5_API_33",
|
|
"--duration", "10m",
|
|
"--seed", "42",
|
|
"--output", "./out",
|
|
}, io.Discard)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if options.avd != "Pixel_5_API_33" || options.duration != 10*time.Minute || options.seed != 42 || options.output != "./out" {
|
|
t.Errorf("unexpected options: %+v", options)
|
|
}
|
|
}
|
|
|
|
func TestParseTestArgs_RequiresSpec(t *testing.T) {
|
|
_, err := parseTestArgs([]string{"--bundle-id", "com.example", "--avd", "x"}, io.Discard)
|
|
if err == nil || !strings.Contains(err.Error(), "--spec") {
|
|
t.Fatalf("expected missing --spec error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestParseTestArgs_RequiresBundleID(t *testing.T) {
|
|
_, err := parseTestArgs([]string{"--spec", "s.ts", "--avd", "x"}, io.Discard)
|
|
if err == nil || !strings.Contains(err.Error(), "--bundle-id") {
|
|
t.Fatalf("expected missing --bundle-id error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestParseTestArgs_AVDIsOptional(t *testing.T) {
|
|
options, err := parseTestArgs([]string{"--spec", "s.ts", "--bundle-id", "com.example"}, io.Discard)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if options.avd != "" {
|
|
t.Fatalf("avd default: got %q, want empty", options.avd)
|
|
}
|
|
}
|
|
|
|
func TestParseTestArgs_RejectsNonAndroidPlatform(t *testing.T) {
|
|
_, err := parseTestArgs([]string{
|
|
"--spec", "s.ts",
|
|
"--bundle-id", "com.example",
|
|
"--platform", "ios",
|
|
"--avd", "x",
|
|
}, io.Discard)
|
|
if err == nil || !strings.Contains(err.Error(), "unsupported platform") {
|
|
t.Fatalf("expected unsupported-platform error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRun_HelpPrintsUsage(t *testing.T) {
|
|
var stdout bytes.Buffer
|
|
if err := run([]string{"uatu"}, &stdout, io.Discard); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(stdout.String(), "uatu <command>") {
|
|
t.Errorf("usage missing, got: %q", stdout.String())
|
|
}
|
|
}
|
|
|
|
func TestRun_VersionPrintsVersion(t *testing.T) {
|
|
prev := Version
|
|
Version = "1.2.3-test"
|
|
defer func() { Version = prev }()
|
|
|
|
for _, arg := range []string{"version", "--version", "-v"} {
|
|
var stdout bytes.Buffer
|
|
if err := run([]string{"uatu", arg}, &stdout, io.Discard); err != nil {
|
|
t.Fatalf("%s: %v", arg, err)
|
|
}
|
|
if strings.TrimSpace(stdout.String()) != "1.2.3-test" {
|
|
t.Errorf("%s: got %q, want 1.2.3-test", arg, stdout.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRun_UnknownCommand(t *testing.T) {
|
|
err := run([]string{"uatu", "wat"}, io.Discard, io.Discard)
|
|
if err == nil || !strings.Contains(err.Error(), "unknown command") {
|
|
t.Fatalf("expected unknown-command error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRun_Doctor(t *testing.T) {
|
|
var stdout bytes.Buffer
|
|
// Doctor may pass or fail depending on host environment; we just want to
|
|
// confirm it runs and emits per-check lines.
|
|
_ = run([]string{"uatu", "doctor"}, &stdout, io.Discard)
|
|
output := stdout.String()
|
|
if !strings.Contains(output, "OK") && !strings.Contains(output, "FAIL") {
|
|
t.Errorf("doctor output missing OK/FAIL lines: %q", output)
|
|
}
|
|
}
|
|
|
|
func TestRun_TestSubcommand_PipelineErrors(t *testing.T) {
|
|
// Without a real spec, a real device, or a bootable AVD the pipeline
|
|
// must surface a specific error rather than panicking — proves the flag
|
|
// wiring reaches the runner.
|
|
err := run([]string{
|
|
"uatu", "test",
|
|
"--spec", "definitely-missing-spec.ts",
|
|
"--bundle-id", "com.example",
|
|
"--avd", "definitely-missing-avd",
|
|
}, io.Discard, io.Discard)
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
message := err.Error()
|
|
ok := strings.Contains(message, "bundle") ||
|
|
strings.Contains(message, "device") ||
|
|
strings.Contains(message, "AVD") ||
|
|
strings.Contains(message, "emulator")
|
|
if !ok {
|
|
t.Errorf("expected pipeline error (bundle/device/AVD/emulator), got %v", err)
|
|
}
|
|
}
|