mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 11:07:10 +00:00
refactor: rename project uatu -> sanderling (#24)
* refactor: rename Go module path uatu -> sanderling
Module path github.com/priyanshujain/uatu -> github.com/priyanshujain/sanderling,
including all imports and the proto go_package option. Generated .pb.go files
rewritten in-place; safe to regenerate with protoc later.
* chore(proto): regenerate driverpb after module path rename
The previous sed-based module rename corrupted the embedded descriptor
byte lengths. buf generate rewrites them cleanly.
* refactor: rename CLI binary uatu -> sanderling
Updates Makefile target + UATU_BIN var, .goreleaser project/build IDs,
.gitignore comment, and all user-facing strings in the CLI help text,
error messages, and tests. Binary is now bin/sanderling.
* refactor(sdk): rename Kotlin package dev.uatu.sdk -> dev.sanderling.sdk
Moves sdk/android/src/{main,test}/kotlin/dev/uatu -> dev/sanderling and
rewrites package declarations, imports, and the Gradle namespace. Class
names (Uatu, UatuRuntime) are renamed in a follow-up commit.
* refactor(sidecar): rename Kotlin package dev.uatu.sidecar -> dev.sanderling.sidecar
Moves sidecar/src/{main,test}/kotlin/dev/uatu -> dev/sanderling and
rewrites package declarations, imports, and the application mainClass.
* refactor: rename Uatu API surface -> Sanderling
- Kotlin: Uatu -> Sanderling, UatuRuntime -> SanderlingRuntime (+ files).
- JS host binding: globalThis.__uatu__ -> __sanderling__ (Go verifier,
spec-api, tests).
- TS interface: UatuRuntime -> SanderlingRuntime; internal tags
__uatuFormula / __uatuActionGenerator -> __sanderling* variants.
- Go trace: UatuVersion field + uatu_version JSON tag renamed.
- Socket naming: uatu-agent / uatu-agent-reader -> sanderling-agent*.
- Sample app, docs, inline-JS test strings updated to match.
* refactor(examples): rename examples/folio/uatu -> examples/folio/sanderling
Renames the example spec directory; updates justfile paths + gitignore
entries accordingly. Package.json name/description and @uatu/spec
dependency are renamed in the npm + docs commits.
* chore(build): rename gradle property + rootProject.name uatu -> sanderling
- Renames the uatu.version gradle property and all its -P references in
Makefile, build.gradle.kts files, and .github/workflows/release.yml.
- settings.gradle.kts rootProject.name = "sanderling".
- Renames .env.local.example header + release-cli workflow job name.
* refactor(proto): rename proto package uatu.driver.v1 -> sanderling.driver.v1
Updates the proto package and java_package, regenerates driver.pb.go +
driver_grpc.pb.go, rewrites Kotlin imports and the gRPC ServiceName
assertion in driver_test.go.
* refactor: rename npm package @uatu/spec -> @sanderling/spec
Renames package name in pkg/spec-api/package.json + lockfile, all
consumer imports (examples/folio spec, testdata, verifier tests), the
esbuild alias in cmd/sanderling/test_run.go, and related doc references.
* docs: rename uatu -> sanderling in README, docs, and URLs
- README + docs/{manual,development}/*: narrative + GitHub + Pages URLs.
- POM + npm package.json repo/homepage/bugs URLs.
- .gitignore + embed_stub + Makefile-comment references updated to
'make sanderling'.
- Minor narrative comments in cmd/sanderling/test_run.go and
internal/inspect/server.go.
* refactor: rename remaining internal uatu strings -> sanderling
- SANDERLING_TEST_PHONE/OTP env vars (cmd + bundler tests).
- sanderling-sidecar runtime tmp dir + extracted JAR filename.
- Inspect web UI: @sanderling/inspect-web package, title, theme
localStorage key, RunList empty-state copy, uatu_version TS field.
- Sample app storage key sanderling.ledger.v1.
- Test data: sanderling_test AVD name + com.example.sanderling_test.
- Release docs tarball name template.
This commit is contained in:
111 files changed
+514
-514
No files matched your search
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
# uatu release credentials (local dev only).
|
# sanderling release credentials (local dev only).
|
||||||
#
|
#
|
||||||
# Copy to `.env.local` (gitignored) and fill in values ONLY if you need to
|
# Copy to `.env.local` (gitignored) and fill in values ONLY if you need to
|
||||||
# fire a real release from your laptop. Day-to-day work and the `release-cli`
|
# fire a real release from your laptop. Day-to-day work and the `release-cli`
|
||||||
|
|||||||
@@ -55,10 +55,10 @@ jobs:
|
|||||||
gradle-${{ runner.os }}-
|
gradle-${{ runner.os }}-
|
||||||
|
|
||||||
- name: Publish to Maven Central
|
- name: Publish to Maven Central
|
||||||
run: ./gradlew :sdk-android:publishAndReleaseToMavenCentral -Puatu.version=${{ steps.ver.outputs.version }}
|
run: ./gradlew :sdk-android:publishAndReleaseToMavenCentral -Psanderling.version=${{ steps.ver.outputs.version }}
|
||||||
|
|
||||||
release-npm:
|
release-npm:
|
||||||
name: Publish @uatu/spec to npm
|
name: Publish @sanderling/spec to npm
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
@@ -91,7 +91,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
working-directory: pkg/spec-api
|
working-directory: pkg/spec-api
|
||||||
# npm tag pre-releases (e.g. 0.1.0-rc1) as "next" so npm install @uatu/spec
|
# npm tag pre-releases (e.g. 0.1.0-rc1) as "next" so npm install @sanderling/spec
|
||||||
# keeps resolving the latest stable.
|
# keeps resolving the latest stable.
|
||||||
run: |
|
run: |
|
||||||
if [[ "${{ steps.ver.outputs.version }}" == *-* ]]; then
|
if [[ "${{ steps.ver.outputs.version }}" == *-* ]]; then
|
||||||
@@ -101,7 +101,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
release-cli:
|
release-cli:
|
||||||
name: Publish uatu CLI to GitHub Releases
|
name: Publish sanderling CLI to GitHub Releases
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|||||||
+2
-2
@@ -17,7 +17,7 @@ local.properties
|
|||||||
*.test
|
*.test
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
# Uatu runs
|
# Sanderling runs
|
||||||
/runs/
|
/runs/
|
||||||
|
|
||||||
# Node
|
# Node
|
||||||
@@ -27,7 +27,7 @@ node_modules/
|
|||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
|
|
||||||
# Sidecar fat JAR. Build artifact copied in by `make uatu` before
|
# Sidecar fat JAR. Build artifact copied in by `make sanderling` before
|
||||||
# `go build -tags withsidecar`. Never commit: it's ~130 MB.
|
# `go build -tags withsidecar`. Never commit: it's ~130 MB.
|
||||||
internal/sidecar/assets/sidecar-all.jar
|
internal/sidecar/assets/sidecar-all.jar
|
||||||
|
|
||||||
|
|||||||
+8
-8
@@ -1,6 +1,6 @@
|
|||||||
version: 2
|
version: 2
|
||||||
|
|
||||||
project_name: uatu
|
project_name: sanderling
|
||||||
|
|
||||||
before:
|
before:
|
||||||
hooks:
|
hooks:
|
||||||
@@ -11,9 +11,9 @@ before:
|
|||||||
- sh -c 'mkdir -p internal/sidecar/assets && cp sidecar/build/libs/sidecar-all.jar internal/sidecar/assets/sidecar-all.jar'
|
- sh -c 'mkdir -p internal/sidecar/assets && cp sidecar/build/libs/sidecar-all.jar internal/sidecar/assets/sidecar-all.jar'
|
||||||
|
|
||||||
builds:
|
builds:
|
||||||
- id: uatu
|
- id: sanderling
|
||||||
main: ./cmd/uatu
|
main: ./cmd/sanderling
|
||||||
binary: uatu
|
binary: sanderling
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
flags:
|
flags:
|
||||||
@@ -28,11 +28,11 @@ builds:
|
|||||||
- arm64
|
- arm64
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- id: uatu
|
- id: sanderling
|
||||||
ids:
|
ids:
|
||||||
- uatu
|
- sanderling
|
||||||
formats: [tar.gz]
|
formats: [tar.gz]
|
||||||
name_template: "uatu_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
name_template: "sanderling_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
||||||
files:
|
files:
|
||||||
- README.md
|
- README.md
|
||||||
- LICENSE*
|
- LICENSE*
|
||||||
@@ -59,6 +59,6 @@ changelog:
|
|||||||
release:
|
release:
|
||||||
github:
|
github:
|
||||||
owner: priyanshujain
|
owner: priyanshujain
|
||||||
name: uatu
|
name: sanderling
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: auto
|
prerelease: auto
|
||||||
@@ -9,7 +9,7 @@ GO_PACKAGES := ./...
|
|||||||
SIDECAR_JAR := sidecar/build/libs/sidecar-all.jar
|
SIDECAR_JAR := sidecar/build/libs/sidecar-all.jar
|
||||||
SIDECAR_EMBED := internal/sidecar/assets/sidecar-all.jar
|
SIDECAR_EMBED := internal/sidecar/assets/sidecar-all.jar
|
||||||
SDK_AAR := sdk/android/build/outputs/aar/sdk-android-release.aar
|
SDK_AAR := sdk/android/build/outputs/aar/sdk-android-release.aar
|
||||||
UATU_BIN := bin/uatu
|
SANDERLING_BIN := bin/sanderling
|
||||||
|
|
||||||
DOCS_SRC := $(shell find docs -type f -name '*.md' -not -path 'docs/_*')
|
DOCS_SRC := $(shell find docs -type f -name '*.md' -not -path 'docs/_*')
|
||||||
DOCS_OUT := $(patsubst docs/%.md,build/site/%.html,$(DOCS_SRC))
|
DOCS_OUT := $(patsubst docs/%.md,build/site/%.html,$(DOCS_SRC))
|
||||||
@@ -18,7 +18,7 @@ DOCS_TEMPLATE := docs/_template/page.html
|
|||||||
INSPECT_DIST := internal/inspect/dist
|
INSPECT_DIST := internal/inspect/dist
|
||||||
WEB_DIST := web/dist
|
WEB_DIST := web/dist
|
||||||
|
|
||||||
.PHONY: bootstrap proto sidecar sdk-android sdk-android-publish uatu install test test-go test-kotlin test-spec-api web-typecheck web-build web-dev inspect-dev docs clean release-cli release-android-local release-npm-dry
|
.PHONY: bootstrap proto sidecar sdk-android sdk-android-publish sanderling install test test-go test-kotlin test-spec-api web-typecheck web-build web-dev inspect-dev docs clean release-cli release-android-local release-npm-dry
|
||||||
|
|
||||||
bootstrap:
|
bootstrap:
|
||||||
$(GO) mod download
|
$(GO) mod download
|
||||||
@@ -37,17 +37,17 @@ sdk-android:
|
|||||||
sdk-android-publish:
|
sdk-android-publish:
|
||||||
ANDROID_HOME=$(ANDROID_HOME) $(GRADLE) :sdk-android:publishToMavenLocal
|
ANDROID_HOME=$(ANDROID_HOME) $(GRADLE) :sdk-android:publishToMavenLocal
|
||||||
|
|
||||||
uatu: $(UATU_BIN)
|
sanderling: $(SANDERLING_BIN)
|
||||||
|
|
||||||
$(UATU_BIN): $(SIDECAR_EMBED) web-build
|
$(SANDERLING_BIN): $(SIDECAR_EMBED) web-build
|
||||||
mkdir -p bin
|
mkdir -p bin
|
||||||
$(GO) build -tags withsidecar -o $(UATU_BIN) ./cmd/uatu
|
$(GO) build -tags withsidecar -o $(SANDERLING_BIN) ./cmd/sanderling
|
||||||
|
|
||||||
# Installs `uatu` into $GOBIN (or $GOPATH/bin) so it's directly on PATH for
|
# Installs `sanderling` into $GOBIN (or $GOPATH/bin) so it's directly on PATH for
|
||||||
# anyone with a standard Go toolchain setup.
|
# anyone with a standard Go toolchain setup.
|
||||||
install: $(SIDECAR_EMBED) web-build
|
install: $(SIDECAR_EMBED) web-build
|
||||||
$(GO) install -tags withsidecar ./cmd/uatu
|
$(GO) install -tags withsidecar ./cmd/sanderling
|
||||||
@dest="$$($(GO) env GOBIN)"; [ -n "$$dest" ] || dest="$$($(GO) env GOPATH)/bin"; echo "installed uatu to $$dest"
|
@dest="$$($(GO) env GOBIN)"; [ -n "$$dest" ] || dest="$$($(GO) env GOPATH)/bin"; echo "installed sanderling to $$dest"
|
||||||
|
|
||||||
web-build:
|
web-build:
|
||||||
cd web && bun install --frozen-lockfile && bun run build
|
cd web && bun install --frozen-lockfile && bun run build
|
||||||
@@ -59,7 +59,7 @@ web-dev:
|
|||||||
cd web && bun run dev
|
cd web && bun run dev
|
||||||
|
|
||||||
inspect-dev: $(SIDECAR_EMBED)
|
inspect-dev: $(SIDECAR_EMBED)
|
||||||
$(GO) run -tags withsidecar ./cmd/uatu inspect --dev
|
$(GO) run -tags withsidecar ./cmd/sanderling inspect --dev
|
||||||
|
|
||||||
web-typecheck:
|
web-typecheck:
|
||||||
cd web && bun install --frozen-lockfile && bun run typecheck
|
cd web && bun install --frozen-lockfile && bun run typecheck
|
||||||
@@ -109,7 +109,7 @@ release-cli: $(SIDECAR_JAR)
|
|||||||
|
|
||||||
release-android-local:
|
release-android-local:
|
||||||
@if [ -f .env.local ]; then set -a; . ./.env.local; set +a; fi; \
|
@if [ -f .env.local ]; then set -a; . ./.env.local; set +a; fi; \
|
||||||
ANDROID_HOME=$(ANDROID_HOME) $(GRADLE) :sdk-android:publishToMavenLocal -Puatu.version=0.0.0-local
|
ANDROID_HOME=$(ANDROID_HOME) $(GRADLE) :sdk-android:publishToMavenLocal -Psanderling.version=0.0.0-local
|
||||||
|
|
||||||
release-npm-dry:
|
release-npm-dry:
|
||||||
cd pkg/spec-api && npm ci && npm run build && npm pack --dry-run
|
cd pkg/spec-api && npm ci && npm run build && npm pack --dry-run
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
# uatu
|
# sanderling
|
||||||
|
|
||||||
Autonomous property-based testing for mobile apps. Specs in TypeScript. Core in Go. Drives the app under test through Maestro and an in-app SDK.
|
Autonomous property-based testing for mobile apps. Specs in TypeScript. Core in Go. Drives the app under test through Maestro and an in-app SDK.
|
||||||
|
|
||||||
> Alpha. Android emulator only. Full scope in the [v0.1.0 roadmap](https://github.com/priyanshujain/uatu/issues/4).
|
> Alpha. Android emulator only. Full scope in the [v0.1.0 roadmap](https://github.com/priyanshujain/sanderling/issues/4).
|
||||||
|
|
||||||
## Docs
|
## Docs
|
||||||
|
|
||||||
- [Getting started](https://priyanshujain.github.io/uatu/manual/getting-started.html)
|
- [Getting started](https://priyanshujain.github.io/sanderling/manual/getting-started.html)
|
||||||
- [Writing specs](https://priyanshujain.github.io/uatu/manual/writing-specs.html)
|
- [Writing specs](https://priyanshujain.github.io/sanderling/manual/writing-specs.html)
|
||||||
- [`uatu inspect` UI](https://priyanshujain.github.io/uatu/manual/inspect.html)
|
- [`sanderling inspect` UI](https://priyanshujain.github.io/sanderling/manual/inspect.html)
|
||||||
- [Example](https://github.com/priyanshujain/uatu/tree/master/examples/folio)
|
- [Example](https://github.com/priyanshujain/sanderling/tree/master/examples/folio)
|
||||||
- [Architecture](https://priyanshujain.github.io/uatu/development/architecture.html)
|
- [Architecture](https://priyanshujain.github.io/sanderling/development/architecture.html)
|
||||||
|
|
||||||
After a `uatu test` run, browse traces locally with `uatu inspect`. It opens a web UI for stepping through actions, screenshots, snapshots, residual formulas, and exceptions.
|
After a `sanderling test` run, browse traces locally with `sanderling inspect`. It opens a web UI for stepping through actions, screenshots, snapshots, residual formulas, and exceptions.
|
||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/priyanshujain/uatu/internal/bundler"
|
"github.com/priyanshujain/sanderling/internal/bundler"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -31,8 +31,8 @@ func main() {
|
|||||||
result, err := bundler.Bundle(bundler.Options{
|
result, err := bundler.Bundle(bundler.Options{
|
||||||
EntryFile: entryFile,
|
EntryFile: entryFile,
|
||||||
Aliases: map[string]string{
|
Aliases: map[string]string{
|
||||||
"@uatu/spec": specApiPath,
|
"@sanderling/spec": specApiPath,
|
||||||
"@uatu/spec/defaults/properties": defaultPropertiesPath,
|
"@sanderling/spec/defaults/properties": defaultPropertiesPath,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/priyanshujain/uatu/internal/hierarchy"
|
"github.com/priyanshujain/sanderling/internal/hierarchy"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
File renamed without changes.
@@ -31,18 +31,18 @@ func TestParseAdbDevices_Empty(t *testing.T) {
|
|||||||
func TestParseAVDList_DropsInfoLines(t *testing.T) {
|
func TestParseAVDList_DropsInfoLines(t *testing.T) {
|
||||||
output := `INFO | Storing crashdata in: /tmp/x
|
output := `INFO | Storing crashdata in: /tmp/x
|
||||||
Medium_Phone_API_36.0
|
Medium_Phone_API_36.0
|
||||||
uatu_test
|
sanderling_test
|
||||||
`
|
`
|
||||||
|
|
||||||
got := parseAVDList(output)
|
got := parseAVDList(output)
|
||||||
want := []string{"Medium_Phone_API_36.0", "uatu_test"}
|
want := []string{"Medium_Phone_API_36.0", "sanderling_test"}
|
||||||
if !reflect.DeepEqual(got, want) {
|
if !reflect.DeepEqual(got, want) {
|
||||||
t.Fatalf("got %v, want %v", got, want)
|
t.Fatalf("got %v, want %v", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPickAVD_ExplicitName(t *testing.T) {
|
func TestPickAVD_ExplicitName(t *testing.T) {
|
||||||
got, err := pickAVD("Pixel_7", []string{"Pixel_7", "uatu_test"})
|
got, err := pickAVD("Pixel_7", []string{"Pixel_7", "sanderling_test"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -59,12 +59,12 @@ func TestPickAVD_ExplicitMissing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPickAVD_SingleAvailable(t *testing.T) {
|
func TestPickAVD_SingleAvailable(t *testing.T) {
|
||||||
got, err := pickAVD("", []string{"uatu_test"})
|
got, err := pickAVD("", []string{"sanderling_test"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if got != "uatu_test" {
|
if got != "sanderling_test" {
|
||||||
t.Fatalf("got %q, want uatu_test", got)
|
t.Fatalf("got %q, want sanderling_test", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/priyanshujain/uatu/internal/sidecar"
|
"github.com/priyanshujain/sanderling/internal/sidecar"
|
||||||
)
|
)
|
||||||
|
|
||||||
type doctorCheck struct {
|
type doctorCheck struct {
|
||||||
@@ -30,7 +30,7 @@ func defaultDoctorChecks() []doctorCheck {
|
|||||||
|
|
||||||
func checkSidecarJAR(_ context.Context) error {
|
func checkSidecarJAR(_ context.Context) error {
|
||||||
if sidecar.IsPlaceholder() {
|
if sidecar.IsPlaceholder() {
|
||||||
return fmt.Errorf("placeholder JAR embedded; run `make sidecar && make uatu` to embed the real fat JAR")
|
return fmt.Errorf("placeholder JAR embedded; run `make sidecar && make sanderling` to embed the real fat JAR")
|
||||||
}
|
}
|
||||||
if sidecar.EmbeddedSize() == 0 {
|
if sidecar.EmbeddedSize() == 0 {
|
||||||
return fmt.Errorf("embedded JAR is empty")
|
return fmt.Errorf("embedded JAR is empty")
|
||||||
File renamed without changes.
@@ -14,7 +14,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/priyanshujain/uatu/internal/inspect"
|
"github.com/priyanshujain/sanderling/internal/inspect"
|
||||||
)
|
)
|
||||||
|
|
||||||
type inspectOptions struct {
|
type inspectOptions struct {
|
||||||
@@ -67,7 +67,7 @@ func runInspect(options inspectOptions, stdout io.Writer) error {
|
|||||||
}
|
}
|
||||||
address := listener.Addr().(*net.TCPAddr)
|
address := listener.Addr().(*net.TCPAddr)
|
||||||
browseURL := buildBrowseURL(address, deepLinkID)
|
browseURL := buildBrowseURL(address, deepLinkID)
|
||||||
fmt.Fprintf(stdout, "uatu inspect listening on %s (runs=%s)\n", browseURL, runsDirectory)
|
fmt.Fprintf(stdout, "sanderling inspect listening on %s (runs=%s)\n", browseURL, runsDirectory)
|
||||||
|
|
||||||
context, cancel := context.WithCancel(context.Background())
|
context, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -36,7 +36,7 @@ func TestParseInspectArgs_RejectsTooManyPositional(t *testing.T) {
|
|||||||
|
|
||||||
func TestRun_HelpListsInspectCommand(t *testing.T) {
|
func TestRun_HelpListsInspectCommand(t *testing.T) {
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
if err := run([]string{"uatu"}, &stdout, io.Discard); err != nil {
|
if err := run([]string{"sanderling"}, &stdout, io.Discard); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if !strings.Contains(stdout.String(), "inspect") {
|
if !strings.Contains(stdout.String(), "inspect") {
|
||||||
@@ -25,18 +25,18 @@ type testOptions struct {
|
|||||||
output string
|
output string
|
||||||
}
|
}
|
||||||
|
|
||||||
const topUsage = `uatu is a property-based UI fuzzer for mobile apps.
|
const topUsage = `sanderling is a property-based UI fuzzer for mobile apps.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
uatu <command> [flags]
|
sanderling <command> [flags]
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
test Run a spec against an app for a fixed duration.
|
test Run a spec against an app for a fixed duration.
|
||||||
inspect Serve a local web UI for browsing runs/.
|
inspect Serve a local web UI for browsing runs/.
|
||||||
doctor Check that the host environment is ready to run uatu.
|
doctor Check that the host environment is ready to run sanderling.
|
||||||
version Print the uatu version.
|
version Print the sanderling version.
|
||||||
|
|
||||||
Run "uatu <command> -h" for command-specific flags.
|
Run "sanderling <command> -h" for command-specific flags.
|
||||||
`
|
`
|
||||||
|
|
||||||
func parseTestArgs(args []string, stderr io.Writer) (testOptions, error) {
|
func parseTestArgs(args []string, stderr io.Writer) (testOptions, error) {
|
||||||
@@ -100,7 +100,7 @@ func run(args []string, stdout, stderr io.Writer) error {
|
|||||||
fmt.Fprintln(stdout, Version)
|
fmt.Fprintln(stdout, Version)
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown command: %q (try 'uatu help')", args[1])
|
return fmt.Errorf("unknown command: %q (try 'sanderling help')", args[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,10 +90,10 @@ func TestParseTestArgs_RejectsNonAndroidPlatform(t *testing.T) {
|
|||||||
|
|
||||||
func TestRun_HelpPrintsUsage(t *testing.T) {
|
func TestRun_HelpPrintsUsage(t *testing.T) {
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
if err := run([]string{"uatu"}, &stdout, io.Discard); err != nil {
|
if err := run([]string{"sanderling"}, &stdout, io.Discard); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if !strings.Contains(stdout.String(), "uatu <command>") {
|
if !strings.Contains(stdout.String(), "sanderling <command>") {
|
||||||
t.Errorf("usage missing, got: %q", stdout.String())
|
t.Errorf("usage missing, got: %q", stdout.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,7 @@ func TestRun_VersionPrintsVersion(t *testing.T) {
|
|||||||
|
|
||||||
for _, arg := range []string{"version", "--version", "-v"} {
|
for _, arg := range []string{"version", "--version", "-v"} {
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
if err := run([]string{"uatu", arg}, &stdout, io.Discard); err != nil {
|
if err := run([]string{"sanderling", arg}, &stdout, io.Discard); err != nil {
|
||||||
t.Fatalf("%s: %v", arg, err)
|
t.Fatalf("%s: %v", arg, err)
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(stdout.String()) != "1.2.3-test" {
|
if strings.TrimSpace(stdout.String()) != "1.2.3-test" {
|
||||||
@@ -115,7 +115,7 @@ func TestRun_VersionPrintsVersion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRun_UnknownCommand(t *testing.T) {
|
func TestRun_UnknownCommand(t *testing.T) {
|
||||||
err := run([]string{"uatu", "wat"}, io.Discard, io.Discard)
|
err := run([]string{"sanderling", "wat"}, io.Discard, io.Discard)
|
||||||
if err == nil || !strings.Contains(err.Error(), "unknown command") {
|
if err == nil || !strings.Contains(err.Error(), "unknown command") {
|
||||||
t.Fatalf("expected unknown-command error, got %v", err)
|
t.Fatalf("expected unknown-command error, got %v", err)
|
||||||
}
|
}
|
||||||
@@ -125,7 +125,7 @@ func TestRun_Doctor(t *testing.T) {
|
|||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
// Doctor may pass or fail depending on host environment; we just want to
|
// Doctor may pass or fail depending on host environment; we just want to
|
||||||
// confirm it runs and emits per-check lines.
|
// confirm it runs and emits per-check lines.
|
||||||
_ = run([]string{"uatu", "doctor"}, &stdout, io.Discard)
|
_ = run([]string{"sanderling", "doctor"}, &stdout, io.Discard)
|
||||||
output := stdout.String()
|
output := stdout.String()
|
||||||
if !strings.Contains(output, "OK") && !strings.Contains(output, "FAIL") {
|
if !strings.Contains(output, "OK") && !strings.Contains(output, "FAIL") {
|
||||||
t.Errorf("doctor output missing OK/FAIL lines: %q", output)
|
t.Errorf("doctor output missing OK/FAIL lines: %q", output)
|
||||||
@@ -137,7 +137,7 @@ func TestRun_TestSubcommand_PipelineErrors(t *testing.T) {
|
|||||||
// must surface a specific error rather than panicking — proves the flag
|
// must surface a specific error rather than panicking — proves the flag
|
||||||
// wiring reaches the runner.
|
// wiring reaches the runner.
|
||||||
err := run([]string{
|
err := run([]string{
|
||||||
"uatu", "test",
|
"sanderling", "test",
|
||||||
"--spec", "definitely-missing-spec.ts",
|
"--spec", "definitely-missing-spec.ts",
|
||||||
"--bundle-id", "com.example",
|
"--bundle-id", "com.example",
|
||||||
"--avd", "definitely-missing-avd",
|
"--avd", "definitely-missing-avd",
|
||||||
File renamed without changes.
@@ -12,17 +12,17 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/priyanshujain/uatu/internal/agent"
|
"github.com/priyanshujain/sanderling/internal/agent"
|
||||||
"github.com/priyanshujain/uatu/internal/bundler"
|
"github.com/priyanshujain/sanderling/internal/bundler"
|
||||||
"github.com/priyanshujain/uatu/internal/driver/maestro"
|
"github.com/priyanshujain/sanderling/internal/driver/maestro"
|
||||||
"github.com/priyanshujain/uatu/internal/runner"
|
"github.com/priyanshujain/sanderling/internal/runner"
|
||||||
"github.com/priyanshujain/uatu/internal/sidecar"
|
"github.com/priyanshujain/sanderling/internal/sidecar"
|
||||||
"github.com/priyanshujain/uatu/internal/trace"
|
"github.com/priyanshujain/sanderling/internal/trace"
|
||||||
"github.com/priyanshujain/uatu/internal/verifier"
|
"github.com/priyanshujain/sanderling/internal/verifier"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
socketName = "uatu-agent"
|
socketName = "sanderling-agent"
|
||||||
sidecarStartupTimeout = 30 * time.Second
|
sidecarStartupTimeout = 30 * time.Second
|
||||||
sdkAcceptTimeout = 60 * time.Second
|
sdkAcceptTimeout = 60 * time.Second
|
||||||
)
|
)
|
||||||
@@ -35,17 +35,17 @@ func runTestPipeline(ctx context.Context, options testOptions, stdout io.Writer)
|
|||||||
}
|
}
|
||||||
aliases := map[string]string{}
|
aliases := map[string]string{}
|
||||||
if specApiPath := resolveSpecAPIPath(options.spec); specApiPath != "" {
|
if specApiPath := resolveSpecAPIPath(options.spec); specApiPath != "" {
|
||||||
aliases["@uatu/spec"] = specApiPath
|
aliases["@sanderling/spec"] = specApiPath
|
||||||
// Also alias published subpath exports so specs importing from
|
// Also alias published subpath exports so specs importing from
|
||||||
// "@uatu/spec/defaults/properties" resolve to the in-tree source.
|
// "@sanderling/spec/defaults/properties" resolve to the in-tree source.
|
||||||
base := filepath.Dir(specApiPath)
|
base := filepath.Dir(specApiPath)
|
||||||
aliases["@uatu/spec/defaults/properties"] = filepath.Join(base, "defaults/properties.ts")
|
aliases["@sanderling/spec/defaults/properties"] = filepath.Join(base, "defaults/properties.ts")
|
||||||
}
|
}
|
||||||
bundle, err := bundler.Bundle(bundler.Options{
|
bundle, err := bundler.Bundle(bundler.Options{
|
||||||
EntryFile: options.spec,
|
EntryFile: options.spec,
|
||||||
Defines: map[string]string{
|
Defines: map[string]string{
|
||||||
"UATU_TEST_PHONE": os.Getenv("UATU_TEST_PHONE"),
|
"SANDERLING_TEST_PHONE": os.Getenv("SANDERLING_TEST_PHONE"),
|
||||||
"UATU_TEST_OTP": os.Getenv("UATU_TEST_OTP"),
|
"SANDERLING_TEST_OTP": os.Getenv("SANDERLING_TEST_OTP"),
|
||||||
},
|
},
|
||||||
Aliases: aliases,
|
Aliases: aliases,
|
||||||
})
|
})
|
||||||
@@ -54,7 +54,7 @@ func runTestPipeline(ctx context.Context, options testOptions, stdout io.Writer)
|
|||||||
}
|
}
|
||||||
fmt.Fprintf(stdout, "bundled spec: %d bytes (sha256=%s)\n", len(bundle.JavaScript), bundle.SHA256[:12])
|
fmt.Fprintf(stdout, "bundled spec: %d bytes (sha256=%s)\n", len(bundle.JavaScript), bundle.SHA256[:12])
|
||||||
|
|
||||||
sidecarDirectory := filepath.Join(os.TempDir(), "uatu-sidecar")
|
sidecarDirectory := filepath.Join(os.TempDir(), "sanderling-sidecar")
|
||||||
jarPath, err := sidecar.Extract(sidecarDirectory)
|
jarPath, err := sidecar.Extract(sidecarDirectory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("extract sidecar: %w", err)
|
return fmt.Errorf("extract sidecar: %w", err)
|
||||||
@@ -166,7 +166,7 @@ func runTestPipeline(ctx context.Context, options testOptions, stdout io.Writer)
|
|||||||
Platform: options.platform,
|
Platform: options.platform,
|
||||||
BundleID: options.bundleID,
|
BundleID: options.bundleID,
|
||||||
StartedAt: time.Now().UTC(),
|
StartedAt: time.Now().UTC(),
|
||||||
UatuVersion: "0.0.1",
|
SanderlingVersion: "0.0.1",
|
||||||
}
|
}
|
||||||
if err := traceWriter.WriteMeta(meta); err != nil {
|
if err := traceWriter.WriteMeta(meta); err != nil {
|
||||||
return fmt.Errorf("trace meta: %w", err)
|
return fmt.Errorf("trace meta: %w", err)
|
||||||
@@ -212,8 +212,8 @@ func runTestPipeline(ctx context.Context, options testOptions, stdout io.Writer)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// resolveSpecAPIPath returns the path to pkg/spec-api/src/index.ts inside
|
// resolveSpecAPIPath returns the path to pkg/spec-api/src/index.ts inside
|
||||||
// a uatu source checkout, searched upward from the spec file and the cwd.
|
// a sanderling source checkout, searched upward from the spec file and the cwd.
|
||||||
// Returns "" when not found, in which case esbuild resolves @uatu/spec via
|
// Returns "" when not found, in which case esbuild resolves @sanderling/spec via
|
||||||
// node_modules the way a downstream user's project would.
|
// node_modules the way a downstream user's project would.
|
||||||
func resolveSpecAPIPath(specPath string) string {
|
func resolveSpecAPIPath(specPath string) string {
|
||||||
candidates := []string{}
|
candidates := []string{}
|
||||||
@@ -47,6 +47,6 @@ func TestResolveSpecAPIPath_ReturnsEmptyWhenMissing(t *testing.T) {
|
|||||||
|
|
||||||
got := resolveSpecAPIPath(specPath)
|
got := resolveSpecAPIPath(specPath)
|
||||||
if got != "" {
|
if got != "" {
|
||||||
t.Fatalf("got %q, want empty (no uatu source tree reachable)", got)
|
t.Fatalf("got %q, want empty (no sanderling source tree reachable)", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Vendored
+4
-4
@@ -3,14 +3,14 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<title>$if(title)$$title$ · $endif$uatu</title>
|
<title>$if(title)$$title$ · $endif$sanderling</title>
|
||||||
<link rel="stylesheet" href="__ROOT___assets/style.css">
|
<link rel="stylesheet" href="__ROOT___assets/style.css">
|
||||||
<style>$highlighting-css$</style>
|
<style>$highlighting-css$</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="layout">
|
<div class="layout">
|
||||||
<aside class="sidebar">
|
<aside class="sidebar">
|
||||||
<a class="brand" href="__ROOT__index.html">uatu</a>
|
<a class="brand" href="__ROOT__index.html">sanderling</a>
|
||||||
<nav>
|
<nav>
|
||||||
<h3>Manual</h3>
|
<h3>Manual</h3>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -31,8 +31,8 @@
|
|||||||
$body$
|
$body$
|
||||||
</article>
|
</article>
|
||||||
<footer>
|
<footer>
|
||||||
<a href="https://github.com/priyanshujain/uatu">github.com/priyanshujain/uatu</a>
|
<a href="https://github.com/priyanshujain/sanderling">github.com/priyanshujain/sanderling</a>
|
||||||
· <a href="https://github.com/priyanshujain/uatu/issues/4">v0.1.0 roadmap</a>
|
· <a href="https://github.com/priyanshujain/sanderling/issues/4">v0.1.0 roadmap</a>
|
||||||
</footer>
|
</footer>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Three processes, two transports.
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TB
|
flowchart TB
|
||||||
subgraph Go["uatu (Go)"]
|
subgraph Go["sanderling (Go)"]
|
||||||
Bundler[Bundler<br/>esbuild] --> Verifier[Verifier<br/>goja + LTL]
|
Bundler[Bundler<br/>esbuild] --> Verifier[Verifier<br/>goja + LTL]
|
||||||
Verifier <--> Runner[Runner]
|
Verifier <--> Runner[Runner]
|
||||||
Runner --> Trace[Trace writer<br/>JSONL + PNG]
|
Runner --> Trace[Trace writer<br/>JSONL + PNG]
|
||||||
@@ -21,7 +21,7 @@ flowchart TB
|
|||||||
|
|
||||||
subgraph Device["Emulator"]
|
subgraph Device["Emulator"]
|
||||||
subgraph App["Android app (debug)"]
|
subgraph App["Android app (debug)"]
|
||||||
SDK[uatu-sdk<br/>pause / hierarchy<br/>logs / coverage]
|
SDK[sanderling-sdk<br/>pause / hierarchy<br/>logs / coverage]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ flowchart TB
|
|||||||
|
|
||||||
## Processes
|
## Processes
|
||||||
|
|
||||||
**uatu (Go).** The top-level binary. Bundles the spec with esbuild, evaluates it in goja, runs the main loop, dispatches actions through the driver, writes the trace.
|
**sanderling (Go).** The top-level binary. Bundles the spec with esbuild, evaluates it in goja, runs the main loop, dispatches actions through the driver, writes the trace.
|
||||||
|
|
||||||
**Maestro sidecar (JVM).** A Kotlin process that wraps `maestro-client` and exposes a gRPC surface matching the `driver.Driver` interface. Handles UI input, screenshots, the system accessibility tree, and OS-level alerts.
|
**Maestro sidecar (JVM).** A Kotlin process that wraps `maestro-client` and exposes a gRPC surface matching the `driver.Driver` interface. Handles UI input, screenshots, the system accessibility tree, and OS-level alerts.
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ A 30-minute run is about 10,000 steps. Every step has at least one hierarchy dum
|
|||||||
|
|
||||||
A seeded PRNG drives action selection. Spec evaluation is pure given state and snapshots. The bundle hash and seed are recorded in `meta.json`.
|
A seeded PRNG drives action selection. Spec evaluation is pure given state and snapshots. The bundle hash and seed are recorded in `meta.json`.
|
||||||
|
|
||||||
uatu does not attempt byte-exact replay. Animation timing, keyboard popup timing, and system daemons are non-deterministic on mobile, and the cost of trying to suppress that is not worth the payoff. Same seed produces a similar trajectory, which is usually enough to reproduce the bug.
|
sanderling does not attempt byte-exact replay. Animation timing, keyboard popup timing, and system daemons are non-deterministic on mobile, and the cost of trying to suppress that is not worth the payoff. Same seed produces a similar trajectory, which is usually enough to reproduce the bug.
|
||||||
|
|
||||||
## 6. Fail honest
|
## 6. Fail honest
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ title: Development
|
|||||||
|
|
||||||
- [Design principles](./design-principles.html)
|
- [Design principles](./design-principles.html)
|
||||||
- [Architecture](./architecture.html)
|
- [Architecture](./architecture.html)
|
||||||
- v0.1.0 scope: [issue #4](https://github.com/priyanshujain/uatu/issues/4)
|
- v0.1.0 scope: [issue #4](https://github.com/priyanshujain/sanderling/issues/4)
|
||||||
|
|
||||||
## Building the docs site locally
|
## Building the docs site locally
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -1,12 +1,12 @@
|
|||||||
---
|
---
|
||||||
title: uatu
|
title: sanderling
|
||||||
---
|
---
|
||||||
|
|
||||||
# uatu
|
# sanderling
|
||||||
|
|
||||||
Autonomous property-based testing for mobile apps. Specs in TypeScript. Core in Go. Drives the app under test through Maestro and an in-app SDK.
|
Autonomous property-based testing for mobile apps. Specs in TypeScript. Core in Go. Drives the app under test through Maestro and an in-app SDK.
|
||||||
|
|
||||||
Alpha: Android emulator only. Scope of v0.1.0 is tracked in [issue #4](https://github.com/priyanshujain/uatu/issues/4).
|
Alpha: Android emulator only. Scope of v0.1.0 is tracked in [issue #4](https://github.com/priyanshujain/sanderling/issues/4).
|
||||||
|
|
||||||
## Manual
|
## Manual
|
||||||
|
|
||||||
|
|||||||
+7
-7
@@ -5,10 +5,10 @@ title: CLI reference
|
|||||||
# CLI reference
|
# CLI reference
|
||||||
|
|
||||||
```
|
```
|
||||||
uatu <command> [flags]
|
sanderling <command> [flags]
|
||||||
```
|
```
|
||||||
|
|
||||||
## `uatu test`
|
## `sanderling test`
|
||||||
|
|
||||||
Run a spec against an app for a fixed duration.
|
Run a spec against an app for a fixed duration.
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ Run a spec against an app for a fixed duration.
|
|||||||
| `--seed` | `0` | PRNG seed. `0` uses a random seed and records it in `meta.json`. |
|
| `--seed` | `0` | PRNG seed. `0` uses a random seed and records it in `meta.json`. |
|
||||||
| `--output` | `./runs` | Output directory for traces. |
|
| `--output` | `./runs` | Output directory for traces. |
|
||||||
|
|
||||||
## `uatu inspect [run-or-runs-dir]`
|
## `sanderling inspect [run-or-runs-dir]`
|
||||||
|
|
||||||
Serve a local web UI for browsing traces. The positional argument is optional and may point at either a runs directory (the parent of many runs) or a single run directory (auto-detected by the presence of `meta.json`). Defaults to `./runs`.
|
Serve a local web UI for browsing traces. The positional argument is optional and may point at either a runs directory (the parent of many runs) or a single run directory (auto-detected by the presence of `meta.json`). Defaults to `./runs`.
|
||||||
|
|
||||||
@@ -35,11 +35,11 @@ Serve a local web UI for browsing traces. The positional argument is optional an
|
|||||||
|
|
||||||
See [the inspect UI page](inspect.md) for the panel reference and keyboard shortcuts.
|
See [the inspect UI page](inspect.md) for the panel reference and keyboard shortcuts.
|
||||||
|
|
||||||
## `uatu doctor`
|
## `sanderling doctor`
|
||||||
|
|
||||||
Check the host environment for a working uatu setup: Go toolchain, JDK, Maestro availability, emulator reachability, SDK linkage hints.
|
Check the host environment for a working sanderling setup: Go toolchain, JDK, Maestro availability, emulator reachability, SDK linkage hints.
|
||||||
|
|
||||||
## `uatu version`
|
## `sanderling version`
|
||||||
|
|
||||||
Print the CLI version.
|
Print the CLI version.
|
||||||
|
|
||||||
@@ -49,4 +49,4 @@ Print the CLI version.
|
|||||||
- `--max-steps` hard cap on step count.
|
- `--max-steps` hard cap on step count.
|
||||||
- `--exit-on-violation` stop the run on the first property violation.
|
- `--exit-on-violation` stop the run on the first property violation.
|
||||||
|
|
||||||
Tracked in [issue #4](https://github.com/priyanshujain/uatu/issues/4).
|
Tracked in [issue #4](https://github.com/priyanshujain/sanderling/issues/4).
|
||||||
@@ -9,10 +9,10 @@ Install the CLI, link the SDK into your debug build, run a spec.
|
|||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- An Android emulator with API level 30 or newer.
|
- An Android emulator with API level 30 or newer.
|
||||||
- The app under test built as a debug variant with the uatu Android SDK linked in.
|
- The app under test built as a debug variant with the sanderling Android SDK linked in.
|
||||||
- `adb` on your PATH.
|
- `adb` on your PATH.
|
||||||
|
|
||||||
Run `uatu doctor` to check the host environment.
|
Run `sanderling doctor` to check the host environment.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
@@ -21,15 +21,15 @@ Run `uatu doctor` to check the host environment.
|
|||||||
macOS arm64:
|
macOS arm64:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl -L https://github.com/priyanshujain/uatu/releases/latest/download/uatu_<version>_darwin_arm64.tar.gz | tar xz
|
curl -L https://github.com/priyanshujain/sanderling/releases/latest/download/sanderling_<version>_darwin_arm64.tar.gz | tar xz
|
||||||
./uatu version
|
./sanderling version
|
||||||
```
|
```
|
||||||
|
|
||||||
Linux amd64:
|
Linux amd64:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl -L https://github.com/priyanshujain/uatu/releases/latest/download/uatu_<version>_linux_amd64.tar.gz | tar xz
|
curl -L https://github.com/priyanshujain/sanderling/releases/latest/download/sanderling_<version>_linux_amd64.tar.gz | tar xz
|
||||||
./uatu version
|
./sanderling version
|
||||||
```
|
```
|
||||||
|
|
||||||
Pre-built for `darwin/arm64`, `darwin/amd64`, `linux/amd64`, `linux/arm64`.
|
Pre-built for `darwin/arm64`, `darwin/amd64`, `linux/amd64`, `linux/arm64`.
|
||||||
@@ -37,7 +37,7 @@ Pre-built for `darwin/arm64`, `darwin/amd64`, `linux/amd64`, `linux/arm64`.
|
|||||||
### Spec package (npm)
|
### Spec package (npm)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm install --save-dev @uatu/spec
|
npm install --save-dev @sanderling/spec
|
||||||
```
|
```
|
||||||
|
|
||||||
### Android SDK (Maven Central)
|
### Android SDK (Maven Central)
|
||||||
@@ -55,14 +55,14 @@ The repo ships a working sample at `examples/folio`. From that directory:
|
|||||||
```sh
|
```sh
|
||||||
npm install
|
npm install
|
||||||
(cd android && ./gradlew installDebug)
|
(cd android && ./gradlew installDebug)
|
||||||
uatu test \
|
sanderling test \
|
||||||
--spec spec.ts \
|
--spec spec.ts \
|
||||||
--bundle-id app.folio \
|
--bundle-id app.folio \
|
||||||
--platform android \
|
--platform android \
|
||||||
--duration 2m
|
--duration 2m
|
||||||
```
|
```
|
||||||
|
|
||||||
Pass `--avd <name>` only when no device is connected and you have multiple AVDs; otherwise uatu uses the connected device or boots the single AVD it finds.
|
Pass `--avd <name>` only when no device is connected and you have multiple AVDs; otherwise sanderling uses the connected device or boots the single AVD it finds.
|
||||||
|
|
||||||
When the run ends, the trace lands in `runs/<timestamp>/`:
|
When the run ends, the trace lands in `runs/<timestamp>/`:
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
---
|
---
|
||||||
title: uatu inspect
|
title: sanderling inspect
|
||||||
---
|
---
|
||||||
|
|
||||||
# uatu inspect
|
# sanderling inspect
|
||||||
|
|
||||||
`uatu inspect` is a local web UI for exploring runs produced by `uatu test`. It reads `runs/<id>/meta.json` and `runs/<id>/trace.jsonl` and renders each step with its action, screenshot, snapshots, residual formulas, and exceptions.
|
`sanderling inspect` is a local web UI for exploring runs produced by `sanderling test`. It reads `runs/<id>/meta.json` and `runs/<id>/trace.jsonl` and renders each step with its action, screenshot, snapshots, residual formulas, and exceptions.
|
||||||
|
|
||||||
```
|
```
|
||||||
uatu inspect [run-or-runs-dir] [--port N] [--no-open] [--dev]
|
sanderling inspect [run-or-runs-dir] [--port N] [--no-open] [--dev]
|
||||||
```
|
```
|
||||||
|
|
||||||
The positional argument can be either a runs directory or a single run directory (auto-detected by the presence of `meta.json`). When omitted, it defaults to `./runs`.
|
The positional argument can be either a runs directory or a single run directory (auto-detected by the presence of `meta.json`). When omitted, it defaults to `./runs`.
|
||||||
@@ -51,11 +51,11 @@ Two-process loop:
|
|||||||
|
|
||||||
```
|
```
|
||||||
make web-dev # bun + vite, http://127.0.0.1:5173
|
make web-dev # bun + vite, http://127.0.0.1:5173
|
||||||
make inspect-dev # uatu inspect --dev, proxies non-API to 5173
|
make inspect-dev # sanderling inspect --dev, proxies non-API to 5173
|
||||||
```
|
```
|
||||||
|
|
||||||
For a single binary with embedded assets:
|
For a single binary with embedded assets:
|
||||||
|
|
||||||
```
|
```
|
||||||
make uatu # builds web/dist, copies to internal/inspect/dist, then go build
|
make sanderling # builds web/dist, copies to internal/inspect/dist, then go build
|
||||||
```
|
```
|
||||||
+4
-4
@@ -4,14 +4,14 @@ title: Runs
|
|||||||
|
|
||||||
# What is a run?
|
# What is a run?
|
||||||
|
|
||||||
One `uatu test` invocation. Fresh install, spec-driven exploration, then the trace lands in `runs/<timestamp>/`. Typically minutes to hours, not seconds.
|
One `sanderling test` invocation. Fresh install, spec-driven exploration, then the trace lands in `runs/<timestamp>/`. Typically minutes to hours, not seconds.
|
||||||
|
|
||||||
A run is not analogous to a unit test. A closer framing is: boot a fuzzer for an hour and see what breaks. Violations are recorded in the trace and exploration continues, so one run can surface many bugs.
|
A run is not analogous to a unit test. A closer framing is: boot a fuzzer for an hour and see what breaks. Violations are recorded in the trace and exploration continues, so one run can surface many bugs.
|
||||||
|
|
||||||
## Lifecycle
|
## Lifecycle
|
||||||
|
|
||||||
```
|
```
|
||||||
uatu test --spec spec.ts --bundle-id com.example.app --duration 30m
|
sanderling test --spec spec.ts --bundle-id com.example.app --duration 30m
|
||||||
│
|
│
|
||||||
├── uninstall and reinstall the app (clean slate, every run)
|
├── uninstall and reinstall the app (clean slate, every run)
|
||||||
├── boot the sidecar, connect the agent socket
|
├── boot the sidecar, connect the agent socket
|
||||||
@@ -28,7 +28,7 @@ uatu test --spec spec.ts --bundle-id com.example.app --duration 30m
|
|||||||
|
|
||||||
## Why runs are long and linear
|
## Why runs are long and linear
|
||||||
|
|
||||||
uatu does not restart the app every N steps. Each restart throws away two things.
|
sanderling does not restart the app every N steps. Each restart throws away two things.
|
||||||
|
|
||||||
**Novelty and coverage signal.** The exploration strategy weights actions by whether they reach previously unseen state. Restarting resets that history.
|
**Novelty and coverage signal.** The exploration strategy weights actions by whether they reach previously unseen state. Restarting resets that history.
|
||||||
|
|
||||||
@@ -62,4 +62,4 @@ A run ends when either of these happens.
|
|||||||
|
|
||||||
The trace is written incrementally, so an interrupted run is still fully inspectable.
|
The trace is written incrementally, so an interrupted run is still fully inspectable.
|
||||||
|
|
||||||
Additional termination conditions (`--max-steps`, `--exit-on-violation`, hard crash handling) land in [v0.1.0](https://github.com/priyanshujain/uatu/issues/4).
|
Additional termination conditions (`--max-steps`, `--exit-on-violation`, hard crash handling) land in [v0.1.0](https://github.com/priyanshujain/sanderling/issues/4).
|
||||||
@@ -7,7 +7,7 @@ title: Writing specs
|
|||||||
A spec has three parts: extractors, properties, and actions.
|
A spec has three parts: extractors, properties, and actions.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { extract, always, actions, weighted, Tap, taps, swipes } from "@uatu/spec";
|
import { extract, always, actions, weighted, Tap, taps, swipes } from "@sanderling/spec";
|
||||||
|
|
||||||
// 1. Extractors pull values from each observed state.
|
// 1. Extractors pull values from each observed state.
|
||||||
const loggedIn = extract((s) => !!s.ax.find("id:home-tab-bar"));
|
const loggedIn = extract((s) => !!s.ax.find("id:home-tab-bar"));
|
||||||
@@ -18,7 +18,7 @@ export const properties = {
|
|||||||
cartNeverNegative: always(() => cartCount.current >= 0),
|
cartNeverNegative: always(() => cartCount.current >= 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3. Actions are a weighted tree of what uatu is allowed to do.
|
// 3. Actions are a weighted tree of what sanderling is allowed to do.
|
||||||
export const actions = weighted(
|
export const actions = weighted(
|
||||||
[10, taps],
|
[10, taps],
|
||||||
[2, swipes],
|
[2, swipes],
|
||||||
@@ -45,11 +45,11 @@ interface State {
|
|||||||
|
|
||||||
`ax.find("text:Click me")`, `ax.find("id:login-form")`, `ax.findAll("role:todo-row")` are the common accessors. Prefer stable testID-style identifiers over positional selectors, for the same reason you would in Espresso or XCUITest.
|
`ax.find("text:Click me")`, `ax.find("id:login-form")`, `ax.findAll("role:todo-row")` are the common accessors. Prefer stable testID-style identifiers over positional selectors, for the same reason you would in Espresso or XCUITest.
|
||||||
|
|
||||||
`snapshots` is populated by the in-app SDK via `Uatu.extract("name") { value }`. Use it when the UI does not expose a value you need, such as business-logic state or hidden fields.
|
`snapshots` is populated by the in-app SDK via `Sanderling.extract("name") { value }`. Use it when the UI does not expose a value you need, such as business-logic state or hidden fields.
|
||||||
|
|
||||||
## Pattern: preconditions (login, onboarding)
|
## Pattern: preconditions (login, onboarding)
|
||||||
|
|
||||||
uatu has no setup phase and no fixtures. Preconditions are action generators with two properties:
|
sanderling has no setup phase and no fixtures. Preconditions are action generators with two properties:
|
||||||
|
|
||||||
1. High weight, so they fire whenever applicable.
|
1. High weight, so they fire whenever applicable.
|
||||||
2. Gated on a state extractor, so they return an empty tree when not applicable and self-disable once the precondition is met.
|
2. Gated on a state extractor, so they return an empty tree when not applicable and self-disable once the precondition is met.
|
||||||
@@ -145,7 +145,7 @@ When the UI does not expose a value but the app knows it, use the SDK's extracto
|
|||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
// in the app (Android)
|
// in the app (Android)
|
||||||
Uatu.extract("cart_count") { store.cart.size }
|
Sanderling.extract("cart_count") { store.cart.size }
|
||||||
```
|
```
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ iosApp/build/
|
|||||||
iosApp/iosApp.xcodeproj/
|
iosApp/iosApp.xcodeproj/
|
||||||
iosApp/iosApp.xcodeproj/**
|
iosApp/iosApp.xcodeproj/**
|
||||||
.DS_Store
|
.DS_Store
|
||||||
uatu/node_modules/
|
sanderling/node_modules/
|
||||||
uatu/runs/
|
sanderling/runs/
|
||||||
.playwright-mcp/
|
.playwright-mcp/
|
||||||
web-*.png
|
web-*.png
|
||||||
@@ -3,14 +3,14 @@
|
|||||||
A minimal Kotlin Multiplatform personal-ledger app: login with demo
|
A minimal Kotlin Multiplatform personal-ledger app: login with demo
|
||||||
credentials, create accounts, add credits and debits. Shared UI across
|
credentials, create accounts, add credits and debits. Shared UI across
|
||||||
Android, iOS, and Web via Compose Multiplatform. Doubles as the example
|
Android, iOS, and Web via Compose Multiplatform. Doubles as the example
|
||||||
uatu runs its property-based specs against.
|
sanderling runs its property-based specs against.
|
||||||
|
|
||||||
## Stack
|
## Stack
|
||||||
|
|
||||||
- Kotlin Multiplatform + Compose Multiplatform (shared UI)
|
- Kotlin Multiplatform + Compose Multiplatform (shared UI)
|
||||||
- kotlinx.serialization for file-backed persistence
|
- kotlinx.serialization for file-backed persistence
|
||||||
- kotlinx.coroutines for state flows
|
- kotlinx.coroutines for state flows
|
||||||
- uatu `sdk-android` for harness integration on Android
|
- sanderling `sdk-android` for harness integration on Android
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
@@ -46,13 +46,13 @@ email: [email protected]
|
|||||||
password: ledger123
|
password: ledger123
|
||||||
```
|
```
|
||||||
|
|
||||||
## Run a uatu test (Android)
|
## Run a sanderling test (Android)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
just test
|
just test
|
||||||
```
|
```
|
||||||
|
|
||||||
If no device is connected, uatu boots the single AVD it finds. With multiple
|
If no device is connected, sanderling boots the single AVD it finds. With multiple
|
||||||
AVDs, pick one:
|
AVDs, pick one:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -66,13 +66,13 @@ AVD=Pixel_7
|
|||||||
DURATION=5m
|
DURATION=5m
|
||||||
```
|
```
|
||||||
|
|
||||||
Traces land in `./uatu/runs/<timestamp>/`.
|
Traces land in `./sanderling/runs/<timestamp>/`.
|
||||||
|
|
||||||
## How it connects to uatu
|
## How it connects to sanderling
|
||||||
|
|
||||||
- `composeApp/src/androidMain/.../FolioApplication.kt` calls `Uatu.start(this)`
|
- `composeApp/src/androidMain/.../FolioApplication.kt` calls `Sanderling.start(this)`
|
||||||
and registers snapshot extractors (`logged_in`, `account_count`,
|
and registers snapshot extractors (`logged_in`, `account_count`,
|
||||||
`total_balance`, `route`)
|
`total_balance`, `route`)
|
||||||
- `uatu/spec.ts` imports `@uatu/spec`, reads those snapshots, asserts
|
- `sanderling/spec.ts` imports `@sanderling/spec`, reads those snapshots, asserts
|
||||||
properties, and weights the actions the fuzzer picks from
|
properties, and weights the actions the fuzzer picks from
|
||||||
- `just test` invokes `uatu test` against the installed APK
|
- `just test` invokes `sanderling test` against the installed APK
|
||||||
@@ -10,7 +10,7 @@ plugins {
|
|||||||
id("app.cash.sqldelight")
|
id("app.cash.sqldelight")
|
||||||
}
|
}
|
||||||
|
|
||||||
val uatuVersion = findProperty("uatu.version") as String? ?: "0.0.0-dev"
|
val sanderlingVersion = findProperty("sanderling.version") as String? ?: "0.0.0-dev"
|
||||||
val sqldelightVersion = "2.3.2"
|
val sqldelightVersion = "2.3.2"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
@@ -83,7 +83,7 @@ kotlin {
|
|||||||
androidMain.dependencies {
|
androidMain.dependencies {
|
||||||
implementation("androidx.activity:activity-compose:1.13.0")
|
implementation("androidx.activity:activity-compose:1.13.0")
|
||||||
implementation("app.cash.sqldelight:android-driver:$sqldelightVersion")
|
implementation("app.cash.sqldelight:android-driver:$sqldelightVersion")
|
||||||
implementation("io.github.priyanshujain:sdk-android:$uatuVersion")
|
implementation("io.github.priyanshujain:sdk-android:$sanderlingVersion")
|
||||||
}
|
}
|
||||||
|
|
||||||
iosMain.dependencies {
|
iosMain.dependencies {
|
||||||
@@ -101,7 +101,7 @@ android {
|
|||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = uatuVersion
|
versionName = sanderlingVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
|||||||
@@ -11,18 +11,18 @@ import app.folio.navigation.Navigator
|
|||||||
import app.folio.navigation.Route
|
import app.folio.navigation.Route
|
||||||
import app.folio.platform.balanceOf
|
import app.folio.platform.balanceOf
|
||||||
import app.folio.platform.signedAmount
|
import app.folio.platform.signedAmount
|
||||||
import dev.uatu.sdk.Uatu
|
import dev.sanderling.sdk.Sanderling
|
||||||
|
|
||||||
class FolioApplication : Application() {
|
class FolioApplication : Application() {
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
AndroidLedgerContext.context = applicationContext
|
AndroidLedgerContext.context = applicationContext
|
||||||
Repository.init()
|
Repository.init()
|
||||||
Uatu.start(this)
|
Sanderling.start(this)
|
||||||
Uatu.extract("logged_in") { Repository.session.value != null }
|
Sanderling.extract("logged_in") { Repository.session.value != null }
|
||||||
Uatu.extract("account_count") { Repository.accounts.value.size }
|
Sanderling.extract("account_count") { Repository.accounts.value.size }
|
||||||
Uatu.extract("total_balance") { balanceOf(Repository.transactions.value) }
|
Sanderling.extract("total_balance") { balanceOf(Repository.transactions.value) }
|
||||||
Uatu.extract("route") {
|
Sanderling.extract("route") {
|
||||||
when (Navigator.current.value) {
|
when (Navigator.current.value) {
|
||||||
Route.Login -> "login"
|
Route.Login -> "login"
|
||||||
Route.Home -> "home"
|
Route.Home -> "home"
|
||||||
@@ -31,10 +31,10 @@ class FolioApplication : Application() {
|
|||||||
is Route.AddTransaction -> "add-transaction"
|
is Route.AddTransaction -> "add-transaction"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Uatu.extract("auth_status") {
|
Sanderling.extract("auth_status") {
|
||||||
if (Repository.session.value != null) "logged-in" else "logged-out"
|
if (Repository.session.value != null) "logged-in" else "logged-out"
|
||||||
}
|
}
|
||||||
Uatu.extract("accounts") {
|
Sanderling.extract("accounts") {
|
||||||
val txns = Repository.transactions.value
|
val txns = Repository.transactions.value
|
||||||
Repository.accounts.value.map { a ->
|
Repository.accounts.value.map { a ->
|
||||||
val rows = txns.filter { it.accountId == a.id }
|
val rows = txns.filter { it.accountId == a.id }
|
||||||
@@ -46,14 +46,14 @@ class FolioApplication : Application() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Uatu.extract("active_account_id") {
|
Sanderling.extract("active_account_id") {
|
||||||
when (val r = Navigator.current.value) {
|
when (val r = Navigator.current.value) {
|
||||||
is Route.Ledger -> r.accountId
|
is Route.Ledger -> r.accountId
|
||||||
is Route.AddTransaction -> r.accountId
|
is Route.AddTransaction -> r.accountId
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Uatu.extract("ledger_rows") {
|
Sanderling.extract("ledger_rows") {
|
||||||
val active = when (val r = Navigator.current.value) {
|
val active = when (val r = Navigator.current.value) {
|
||||||
is Route.Ledger -> r.accountId
|
is Route.Ledger -> r.accountId
|
||||||
is Route.AddTransaction -> r.accountId
|
is Route.AddTransaction -> r.accountId
|
||||||
@@ -72,7 +72,7 @@ class FolioApplication : Application() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Uatu.extract("ledger_balance") {
|
Sanderling.extract("ledger_balance") {
|
||||||
val active = when (val r = Navigator.current.value) {
|
val active = when (val r = Navigator.current.value) {
|
||||||
is Route.Ledger -> r.accountId
|
is Route.Ledger -> r.accountId
|
||||||
is Route.AddTransaction -> r.accountId
|
is Route.AddTransaction -> r.accountId
|
||||||
@@ -81,13 +81,13 @@ class FolioApplication : Application() {
|
|||||||
if (active == null) 0L
|
if (active == null) 0L
|
||||||
else balanceOf(Repository.transactions.value.filter { it.accountId == active })
|
else balanceOf(Repository.transactions.value.filter { it.accountId == active })
|
||||||
}
|
}
|
||||||
Uatu.extract("focused_input") { FocusTracker.current.value }
|
Sanderling.extract("focused_input") { FocusTracker.current.value }
|
||||||
Uatu.extract("txn_form_type") { AddTransactionUiState.txnFormType.value }
|
Sanderling.extract("txn_form_type") { AddTransactionUiState.txnFormType.value }
|
||||||
Uatu.extract("txn_form_account_id") {
|
Sanderling.extract("txn_form_account_id") {
|
||||||
(Navigator.current.value as? Route.AddTransaction)?.accountId
|
(Navigator.current.value as? Route.AddTransaction)?.accountId
|
||||||
}
|
}
|
||||||
Uatu.extract("login_error") { LoginUiState.loginError.value }
|
Sanderling.extract("login_error") { LoginUiState.loginError.value }
|
||||||
Uatu.extract("add_account_error") { AddAccountUiState.addAccountError.value }
|
Sanderling.extract("add_account_error") { AddAccountUiState.addAccountError.value }
|
||||||
Uatu.extract("txn_error") { AddTransactionUiState.txnError.value }
|
Sanderling.extract("txn_error") { AddTransactionUiState.txnError.value }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@ import kotlinx.coroutines.flow.StateFlow
|
|||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.browser.localStorage
|
import kotlinx.browser.localStorage
|
||||||
|
|
||||||
private const val STORAGE_KEY = "uatu.ledger.v1"
|
private const val STORAGE_KEY = "sanderling.ledger.v1"
|
||||||
|
|
||||||
class WebLedgerStore : LedgerStore {
|
class WebLedgerStore : LedgerStore {
|
||||||
private val _accounts = MutableStateFlow<List<Account>>(emptyList())
|
private val _accounts = MutableStateFlow<List<Account>>(emptyList())
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ kotlin.code.style=official
|
|||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.nonTransitiveRClass=true
|
android.nonTransitiveRClass=true
|
||||||
|
|
||||||
# Pin the uatu SDK version consumed by this app. Override with
|
# Pin the sanderling SDK version consumed by this app. Override with
|
||||||
# `-Puatu.version=<version>`.
|
# `-Psanderling.version=<version>`.
|
||||||
uatu.version=0.0.0-dev
|
sanderling.version=0.0.0-dev
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
set shell := ["bash", "-cu"]
|
set shell := ["bash", "-cu"]
|
||||||
set dotenv-load := true
|
set dotenv-load := true
|
||||||
|
|
||||||
uatu := env_var_or_default("UATU", "uatu")
|
sanderling := env_var_or_default("SANDERLING", "sanderling")
|
||||||
avd := env_var_or_default("AVD", "")
|
avd := env_var_or_default("AVD", "")
|
||||||
duration := env_var_or_default("DURATION", "1m")
|
duration := env_var_or_default("DURATION", "1m")
|
||||||
seed := env_var_or_default("SEED", "0")
|
seed := env_var_or_default("SEED", "0")
|
||||||
output := env_var_or_default("OUTPUT", justfile_directory() / "uatu" / "runs")
|
output := env_var_or_default("OUTPUT", justfile_directory() / "sanderling" / "runs")
|
||||||
ios_device := env_var_or_default("IOS_DEVICE", "iPhone 17 Pro")
|
ios_device := env_var_or_default("IOS_DEVICE", "iPhone 17 Pro")
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -81,7 +81,7 @@ ios:
|
|||||||
xcrun simctl install booted iosApp/build/Build/Products/Debug-iphonesimulator/iosApp.app
|
xcrun simctl install booted iosApp/build/Build/Products/Debug-iphonesimulator/iosApp.app
|
||||||
xcrun simctl launch booted app.folio
|
xcrun simctl launch booted app.folio
|
||||||
|
|
||||||
# Run 'uatu test' against the folio app. Uses a connected device if one is
|
# Run 'sanderling test' against the folio app. Uses a connected device if one is
|
||||||
# online, otherwise boots AVD=<name> if provided.
|
# online, otherwise boots AVD=<name> if provided.
|
||||||
test:
|
test:
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
@@ -90,8 +90,8 @@ test:
|
|||||||
if [[ -n "{{avd}}" ]]; then
|
if [[ -n "{{avd}}" ]]; then
|
||||||
avd_flag=(--avd "{{avd}}")
|
avd_flag=(--avd "{{avd}}")
|
||||||
fi
|
fi
|
||||||
"{{uatu}}" test \
|
"{{sanderling}}" test \
|
||||||
--spec "{{justfile_directory()}}/uatu/spec.ts" \
|
--spec "{{justfile_directory()}}/sanderling/spec.ts" \
|
||||||
--bundle-id app.folio \
|
--bundle-id app.folio \
|
||||||
"${avd_flag[@]}" \
|
"${avd_flag[@]}" \
|
||||||
--duration "{{duration}}" \
|
--duration "{{duration}}" \
|
||||||
|
|||||||
+3
-3
@@ -6,12 +6,12 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "folio",
|
"name": "folio",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@uatu/spec": "next"
|
"@sanderling/spec": "next"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@uatu/spec": {
|
"node_modules/@sanderling/spec": {
|
||||||
"version": "0.0.1-rc3",
|
"version": "0.0.1-rc3",
|
||||||
"resolved": "https://registry.npmjs.org/@uatu/spec/-/spec-0.0.1-rc3.tgz",
|
"resolved": "https://registry.npmjs.org/@sanderling/spec/-/spec-0.0.1-rc3.tgz",
|
||||||
"integrity": "sha512-mCayssOqM2wfzrQDnFGE8FAlUlDzBl1fx2L/MLDIuPrQHBMlUC350xBB8Z7I/DYI98eCWJMqjtw3iU6yVWKtIQ==",
|
"integrity": "sha512-mCayssOqM2wfzrQDnFGE8FAlUlDzBl1fx2L/MLDIuPrQHBMlUC350xBB8Z7I/DYI98eCWJMqjtw3iU6yVWKtIQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "folio",
|
||||||
|
"private": true,
|
||||||
|
"description": "Minimal sanderling integration example.",
|
||||||
|
"devDependencies": {
|
||||||
|
"@sanderling/spec": "next"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,8 +13,8 @@ import {
|
|||||||
taps,
|
taps,
|
||||||
waitOnce,
|
waitOnce,
|
||||||
weighted,
|
weighted,
|
||||||
} from "@uatu/spec";
|
} from "@sanderling/spec";
|
||||||
import { noUncaughtExceptions } from "@uatu/spec/defaults/properties";
|
import { noUncaughtExceptions } from "@sanderling/spec/defaults/properties";
|
||||||
|
|
||||||
interface AccountSnapshot {
|
interface AccountSnapshot {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "folio",
|
|
||||||
"private": true,
|
|
||||||
"description": "Minimal uatu integration example.",
|
|
||||||
"devDependencies": {
|
|
||||||
"@uatu/spec": "next"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
module github.com/priyanshujain/uatu
|
module github.com/priyanshujain/sanderling
|
||||||
|
|
||||||
go 1.26.1
|
go 1.26.1
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -10,6 +10,6 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
|
|||||||
org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true
|
org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true
|
||||||
|
|
||||||
# Single source of truth at release time is the git tag (e.g. v0.1.0 → 0.1.0),
|
# Single source of truth at release time is the git tag (e.g. v0.1.0 → 0.1.0),
|
||||||
# forwarded via `-Puatu.version=...` or `ORG_GRADLE_PROJECT_uatu.version`.
|
# forwarded via `-Psanderling.version=...` or `ORG_GRADLE_PROJECT_sanderling.version`.
|
||||||
# The default below only applies to untagged local builds.
|
# The default below only applies to untagged local builds.
|
||||||
uatu.version=0.0.0-dev
|
sanderling.version=0.0.0-dev
|
||||||
@@ -47,15 +47,15 @@ func TestBundle_ProducesIIFE(t *testing.T) {
|
|||||||
|
|
||||||
func TestBundle_InlinesDefines(t *testing.T) {
|
func TestBundle_InlinesDefines(t *testing.T) {
|
||||||
entry := writeFixture(t, "spec.ts", `
|
entry := writeFixture(t, "spec.ts", `
|
||||||
const phone = process.env.UATU_TEST_PHONE;
|
const phone = process.env.SANDERLING_TEST_PHONE;
|
||||||
const otp = process.env.UATU_TEST_OTP;
|
const otp = process.env.SANDERLING_TEST_OTP;
|
||||||
export const credentials = { phone, otp };
|
export const credentials = { phone, otp };
|
||||||
`)
|
`)
|
||||||
result, err := Bundle(Options{
|
result, err := Bundle(Options{
|
||||||
EntryFile: entry,
|
EntryFile: entry,
|
||||||
Defines: map[string]string{
|
Defines: map[string]string{
|
||||||
"UATU_TEST_PHONE": "+919876543210",
|
"SANDERLING_TEST_PHONE": "+919876543210",
|
||||||
"UATU_TEST_OTP": "123456",
|
"SANDERLING_TEST_OTP": "123456",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -87,13 +87,13 @@ func TestBundle_DeterministicHash(t *testing.T) {
|
|||||||
|
|
||||||
func TestBundle_HashChangesWithDefines(t *testing.T) {
|
func TestBundle_HashChangesWithDefines(t *testing.T) {
|
||||||
entry := writeFixture(t, "spec.ts", `
|
entry := writeFixture(t, "spec.ts", `
|
||||||
export const phone = process.env.UATU_TEST_PHONE;
|
export const phone = process.env.SANDERLING_TEST_PHONE;
|
||||||
`)
|
`)
|
||||||
first, err := Bundle(Options{EntryFile: entry, Defines: map[string]string{"UATU_TEST_PHONE": "1111"}})
|
first, err := Bundle(Options{EntryFile: entry, Defines: map[string]string{"SANDERLING_TEST_PHONE": "1111"}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
second, err := Bundle(Options{EntryFile: entry, Defines: map[string]string{"UATU_TEST_PHONE": "2222"}})
|
second, err := Bundle(Options{EntryFile: entry, Defines: map[string]string{"SANDERLING_TEST_PHONE": "2222"}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
|
||||||
"github.com/priyanshujain/uatu/internal/driver"
|
"github.com/priyanshujain/sanderling/internal/driver"
|
||||||
driverpb "github.com/priyanshujain/uatu/proto/driverpb"
|
driverpb "github.com/priyanshujain/sanderling/proto/driverpb"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
driverpb "github.com/priyanshujain/uatu/proto/driverpb"
|
driverpb "github.com/priyanshujain/sanderling/proto/driverpb"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeServer struct {
|
type fakeServer struct {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/priyanshujain/uatu/internal/driver"
|
"github.com/priyanshujain/sanderling/internal/driver"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ActionKind string
|
type ActionKind string
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/priyanshujain/uatu/internal/driver"
|
"github.com/priyanshujain/sanderling/internal/driver"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNew_Defaults(t *testing.T) {
|
func TestNew_Defaults(t *testing.T) {
|
||||||
|
|||||||
Vendored
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>uatu inspect</title>
|
<title>sanderling inspect</title>
|
||||||
<script type="module" crossorigin src="/assets/index-DFpYQDqN.js"></script>
|
<script type="module" crossorigin src="/assets/index-DFpYQDqN.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-BSJUf7yI.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-BSJUf7yI.css">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/priyanshujain/uatu/internal/trace"
|
"github.com/priyanshujain/sanderling/internal/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Maximum size of a single trace.jsonl line. Hierarchies and snapshots
|
// Maximum size of a single trace.jsonl line. Hierarchies and snapshots
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/priyanshujain/uatu/internal/trace"
|
"github.com/priyanshujain/sanderling/internal/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestScan_OrdersByStartedAtDescendingAndCountsViolations(t *testing.T) {
|
func TestScan_OrdersByStartedAtDescendingAndCountsViolations(t *testing.T) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type ServerOptions struct {
|
|||||||
DevTarget string
|
DevTarget string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server holds the HTTP handlers for `uatu inspect`.
|
// Server holds the HTTP handlers for `sanderling inspect`.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
options ServerOptions
|
options ServerOptions
|
||||||
cache *Cache
|
cache *Cache
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/priyanshujain/uatu/internal/trace"
|
"github.com/priyanshujain/sanderling/internal/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newFixtureServer(t *testing.T) (*Server, string) {
|
func newFixtureServer(t *testing.T) (*Server, string) {
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/priyanshujain/uatu/internal/agent"
|
"github.com/priyanshujain/sanderling/internal/agent"
|
||||||
"github.com/priyanshujain/uatu/internal/driver"
|
"github.com/priyanshujain/sanderling/internal/driver"
|
||||||
"github.com/priyanshujain/uatu/internal/hierarchy"
|
"github.com/priyanshujain/sanderling/internal/hierarchy"
|
||||||
"github.com/priyanshujain/uatu/internal/ltl"
|
"github.com/priyanshujain/sanderling/internal/ltl"
|
||||||
"github.com/priyanshujain/uatu/internal/trace"
|
"github.com/priyanshujain/sanderling/internal/trace"
|
||||||
"github.com/priyanshujain/uatu/internal/verifier"
|
"github.com/priyanshujain/sanderling/internal/verifier"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
|
|||||||
@@ -15,19 +15,19 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/priyanshujain/uatu/internal/agent"
|
"github.com/priyanshujain/sanderling/internal/agent"
|
||||||
mockdriver "github.com/priyanshujain/uatu/internal/driver/mock"
|
mockdriver "github.com/priyanshujain/sanderling/internal/driver/mock"
|
||||||
"github.com/priyanshujain/uatu/internal/trace"
|
"github.com/priyanshujain/sanderling/internal/trace"
|
||||||
"github.com/priyanshujain/uatu/internal/verifier"
|
"github.com/priyanshujain/sanderling/internal/verifier"
|
||||||
)
|
)
|
||||||
|
|
||||||
const fixtureSpec = `
|
const fixtureSpec = `
|
||||||
const screen = __uatu__.extract(state => state.snapshots.screen ?? "");
|
const screen = __sanderling__.extract(state => state.snapshots.screen ?? "");
|
||||||
const balance = __uatu__.extract(state => state.snapshots.balance ?? 0);
|
const balance = __sanderling__.extract(state => state.snapshots.balance ?? 0);
|
||||||
globalThis.properties = {
|
globalThis.properties = {
|
||||||
balanceNonNegative: __uatu__.always(() => balance.current >= 0),
|
balanceNonNegative: __sanderling__.always(() => balance.current >= 0),
|
||||||
};
|
};
|
||||||
globalThis.actions = __uatu__.actions(() => [__uatu__.tap({ on: "id:next" })]);
|
globalThis.actions = __sanderling__.actions(() => [__sanderling__.tap({ on: "id:next" })]);
|
||||||
`
|
`
|
||||||
|
|
||||||
type harness struct {
|
type harness struct {
|
||||||
@@ -195,9 +195,9 @@ func TestRunner_ViolationSurfacesInSummary(t *testing.T) {
|
|||||||
func TestRunner_ThrowingPredicateIsLoggedNotPanic(t *testing.T) {
|
func TestRunner_ThrowingPredicateIsLoggedNotPanic(t *testing.T) {
|
||||||
const throwingSpec = `
|
const throwingSpec = `
|
||||||
globalThis.properties = {
|
globalThis.properties = {
|
||||||
broken: __uatu__.always(() => { throw new Error("bad predicate"); }),
|
broken: __sanderling__.always(() => { throw new Error("bad predicate"); }),
|
||||||
};
|
};
|
||||||
globalThis.actions = __uatu__.actions(() => [__uatu__.tap({ on: "id:next" })]);
|
globalThis.actions = __sanderling__.actions(() => [__sanderling__.tap({ on: "id:next" })]);
|
||||||
`
|
`
|
||||||
state := newHarnessWithSpec(t, []map[string]json.RawMessage{{}, {}}, throwingSpec)
|
state := newHarnessWithSpec(t, []map[string]json.RawMessage{{}, {}}, throwingSpec)
|
||||||
state.startSDK(t)
|
state.startSDK(t)
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ func EmbeddedSHA256() string {
|
|||||||
// matching checksum, no rewrite happens. Returns the JAR path.
|
// matching checksum, no rewrite happens. Returns the JAR path.
|
||||||
func Extract(dir string) (string, error) {
|
func Extract(dir string) (string, error) {
|
||||||
if len(embeddedJAR) == 0 {
|
if len(embeddedJAR) == 0 {
|
||||||
return "", errors.New("sidecar: binary built without -tags withsidecar; rebuild with `make uatu`")
|
return "", errors.New("sidecar: binary built without -tags withsidecar; rebuild with `make sanderling`")
|
||||||
}
|
}
|
||||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||||
return "", fmt.Errorf("mkdir %s: %w", dir, err)
|
return "", fmt.Errorf("mkdir %s: %w", dir, err)
|
||||||
}
|
}
|
||||||
jarPath := filepath.Join(dir, "uatu-sidecar.jar")
|
jarPath := filepath.Join(dir, "sanderling-sidecar.jar")
|
||||||
checksumPath := jarPath + ".sha256"
|
checksumPath := jarPath + ".sha256"
|
||||||
checksum := EmbeddedSHA256()
|
checksum := EmbeddedSHA256()
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ package sidecar
|
|||||||
var embeddedJAR []byte
|
var embeddedJAR []byte
|
||||||
|
|
||||||
// IsPlaceholder reports whether the binary was built without the real
|
// IsPlaceholder reports whether the binary was built without the real
|
||||||
// sidecar JAR embedded. Build with `make uatu` (which passes
|
// sidecar JAR embedded. Build with `make sanderling` (which passes
|
||||||
// -tags withsidecar) to embed the real fat JAR.
|
// -tags withsidecar) to embed the real fat JAR.
|
||||||
func IsPlaceholder() bool { return true }
|
func IsPlaceholder() bool { return true }
|
||||||
@@ -25,7 +25,7 @@ func TestExtract_WritesJARAndChecksum(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if path != filepath.Join(directory, "uatu-sidecar.jar") {
|
if path != filepath.Join(directory, "sanderling-sidecar.jar") {
|
||||||
t.Errorf("unexpected path: %s", path)
|
t.Errorf("unexpected path: %s", path)
|
||||||
}
|
}
|
||||||
body, err := os.ReadFile(path)
|
body, err := os.ReadFile(path)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/priyanshujain/uatu/internal/hierarchy"
|
"github.com/priyanshujain/sanderling/internal/hierarchy"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Step struct {
|
type Step struct {
|
||||||
@@ -74,7 +74,7 @@ type Meta struct {
|
|||||||
BundleID string `json:"bundle_id"`
|
BundleID string `json:"bundle_id"`
|
||||||
StartedAt time.Time `json:"started_at"`
|
StartedAt time.Time `json:"started_at"`
|
||||||
EndedAt *time.Time `json:"ended_at,omitempty"`
|
EndedAt *time.Time `json:"ended_at,omitempty"`
|
||||||
UatuVersion string `json:"uatu_version"`
|
SanderlingVersion string `json:"sanderling_version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Writer struct {
|
type Writer struct {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func TestWriteMeta_RoundTrip(t *testing.T) {
|
|||||||
Platform: "android",
|
Platform: "android",
|
||||||
BundleID: "in.okcredit.merchant",
|
BundleID: "in.okcredit.merchant",
|
||||||
StartedAt: time.Date(2026, 4, 17, 22, 30, 0, 0, time.UTC),
|
StartedAt: time.Date(2026, 4, 17, 22, 30, 0, 0, time.UTC),
|
||||||
UatuVersion: "0.0.1",
|
SanderlingVersion: "0.0.1",
|
||||||
}
|
}
|
||||||
if err := writer.WriteMeta(meta); err != nil {
|
if err := writer.WriteMeta(meta); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -60,7 +60,7 @@ func TestWriteMeta_EndedAtRoundTrip(t *testing.T) {
|
|||||||
BundleID: "in.test",
|
BundleID: "in.test",
|
||||||
StartedAt: time.Date(2026, 4, 17, 22, 30, 0, 0, time.UTC),
|
StartedAt: time.Date(2026, 4, 17, 22, 30, 0, 0, time.UTC),
|
||||||
EndedAt: &endedAt,
|
EndedAt: &endedAt,
|
||||||
UatuVersion: "0.0.1",
|
SanderlingVersion: "0.0.1",
|
||||||
}
|
}
|
||||||
if err := writer.WriteMeta(meta); err != nil {
|
if err := writer.WriteMeta(meta); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/priyanshujain/uatu/internal/hierarchy"
|
"github.com/priyanshujain/sanderling/internal/hierarchy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestStateAxFindWorks verifies that a Parse+PushSnapshot+extract round trip
|
// TestStateAxFindWorks verifies that a Parse+PushSnapshot+extract round trip
|
||||||
@@ -24,11 +24,11 @@ func TestStateAxFindWorks(t *testing.T) {
|
|||||||
|
|
||||||
verifier := newVerifier(t)
|
verifier := newVerifier(t)
|
||||||
mustLoad(t, verifier, `
|
mustLoad(t, verifier, `
|
||||||
globalThis.probe = __uatu__.extract(state => {
|
globalThis.probe = __sanderling__.extract(state => {
|
||||||
const element = state.ax.find("id:select_language");
|
const element = state.ax.find("id:select_language");
|
||||||
return element ? "matched:" + element.text : "miss";
|
return element ? "matched:" + element.text : "miss";
|
||||||
});
|
});
|
||||||
globalThis.count = __uatu__.extract(state => state.ax.findAll("id:select_language").length);
|
globalThis.count = __sanderling__.extract(state => state.ax.findAll("id:select_language").length);
|
||||||
`)
|
`)
|
||||||
|
|
||||||
if err := verifier.PushSnapshot(SnapshotInput{Snapshots: Snapshots{}, Tree: tree}); err != nil {
|
if err := verifier.PushSnapshot(SnapshotInput{Snapshots: Snapshots{}, Tree: tree}); err != nil {
|
||||||
|
|||||||
@@ -80,11 +80,11 @@ type formulaSpec struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tagFormula = "__uatuFormula"
|
tagFormula = "__sanderlingFormula"
|
||||||
tagFormulaSpecIndex = "__uatuFormulaSpec"
|
tagFormulaSpecIndex = "__sanderlingFormulaSpec"
|
||||||
tagActionGenerator = "__uatuActionGenerator"
|
tagActionGenerator = "__sanderlingActionGenerator"
|
||||||
tagInternalKind = "__uatuKind"
|
tagInternalKind = "__sanderlingKind"
|
||||||
tagSelector = "__uatuSelector"
|
tagSelector = "__sanderlingSelector"
|
||||||
|
|
||||||
internalKindActions = "actions"
|
internalKindActions = "actions"
|
||||||
internalKindWeighted = "weighted"
|
internalKindWeighted = "weighted"
|
||||||
@@ -94,63 +94,63 @@ const (
|
|||||||
internalKindBuiltinPressKey = "pressKey"
|
internalKindBuiltinPressKey = "pressKey"
|
||||||
)
|
)
|
||||||
|
|
||||||
// installRuntimeBindings exposes globalThis.__uatu__ to the loaded spec.
|
// installRuntimeBindings exposes globalThis.__sanderling__ to the loaded spec.
|
||||||
func (v *Verifier) installRuntimeBindings() error {
|
func (v *Verifier) installRuntimeBindings() error {
|
||||||
uatu := v.runtime.NewObject()
|
sanderling := v.runtime.NewObject()
|
||||||
|
|
||||||
if err := uatu.Set("extract", v.bindExtract); err != nil {
|
if err := sanderling.Set("extract", v.bindExtract); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := uatu.Set("always", v.bindAlways); err != nil {
|
if err := sanderling.Set("always", v.bindAlways); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := uatu.Set("now", v.bindNow); err != nil {
|
if err := sanderling.Set("now", v.bindNow); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := uatu.Set("next", v.bindNext); err != nil {
|
if err := sanderling.Set("next", v.bindNext); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := uatu.Set("eventually", v.bindEventually); err != nil {
|
if err := sanderling.Set("eventually", v.bindEventually); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := uatu.Set("actions", v.bindActions); err != nil {
|
if err := sanderling.Set("actions", v.bindActions); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := uatu.Set("weighted", v.bindWeighted); err != nil {
|
if err := sanderling.Set("weighted", v.bindWeighted); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := uatu.Set("from", v.bindFrom); err != nil {
|
if err := sanderling.Set("from", v.bindFrom); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := uatu.Set("tap", v.bindTap); err != nil {
|
if err := sanderling.Set("tap", v.bindTap); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := uatu.Set("inputText", v.bindInputText); err != nil {
|
if err := sanderling.Set("inputText", v.bindInputText); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := uatu.Set("swipe", v.bindSwipe); err != nil {
|
if err := sanderling.Set("swipe", v.bindSwipe); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := uatu.Set("pressKey", v.bindPressKey); err != nil {
|
if err := sanderling.Set("pressKey", v.bindPressKey); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := uatu.Set("wait", v.bindWait); err != nil {
|
if err := sanderling.Set("wait", v.bindWait); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := uatu.Set("taps", v.builtinGenerator(internalKindBuiltinTaps)); err != nil {
|
if err := sanderling.Set("taps", v.builtinGenerator(internalKindBuiltinTaps)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := uatu.Set("swipes", v.builtinGenerator(internalKindBuiltinSwipes)); err != nil {
|
if err := sanderling.Set("swipes", v.builtinGenerator(internalKindBuiltinSwipes)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := uatu.Set("waitOnce", v.builtinGenerator(internalKindBuiltinWaitOnce)); err != nil {
|
if err := sanderling.Set("waitOnce", v.builtinGenerator(internalKindBuiltinWaitOnce)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := uatu.Set("pressKeys", v.builtinGenerator(internalKindBuiltinPressKey)); err != nil {
|
if err := sanderling.Set("pressKeys", v.builtinGenerator(internalKindBuiltinPressKey)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return v.runtime.GlobalObject().Set("__uatu__", uatu)
|
return v.runtime.GlobalObject().Set("__sanderling__", sanderling)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Verifier) bindExtract(call goja.FunctionCall) goja.Value {
|
func (v *Verifier) bindExtract(call goja.FunctionCall) goja.Value {
|
||||||
@@ -172,7 +172,7 @@ func (v *Verifier) bindExtract(call goja.FunctionCall) goja.Value {
|
|||||||
|
|
||||||
// bindAlways accepts either a predicate function (legacy shape) or a formula
|
// bindAlways accepts either a predicate function (legacy shape) or a formula
|
||||||
// handle (new shape). Both produce a formula handle tagged with
|
// handle (new shape). Both produce a formula handle tagged with
|
||||||
// __uatuFormulaSpec.
|
// __sanderlingFormulaSpec.
|
||||||
func (v *Verifier) bindAlways(call goja.FunctionCall) goja.Value {
|
func (v *Verifier) bindAlways(call goja.FunctionCall) goja.Value {
|
||||||
if len(call.Arguments) != 1 {
|
if len(call.Arguments) != 1 {
|
||||||
panic(v.runtime.NewTypeError("always requires exactly one argument"))
|
panic(v.runtime.NewTypeError("always requires exactly one argument"))
|
||||||
@@ -258,9 +258,9 @@ func (v *Verifier) formulaHandle(kind specKind, index int) *goja.Object {
|
|||||||
handle := v.runtime.NewObject()
|
handle := v.runtime.NewObject()
|
||||||
_ = handle.Set(tagFormula, true)
|
_ = handle.Set(tagFormula, true)
|
||||||
_ = handle.Set(tagFormulaSpecIndex, index)
|
_ = handle.Set(tagFormulaSpecIndex, index)
|
||||||
// Keep __uatuIndex as an alias so older property shapes that read it keep
|
// Keep __sanderlingIndex as an alias so older property shapes that read it keep
|
||||||
// working during backward-compat transitions.
|
// working during backward-compat transitions.
|
||||||
_ = handle.Set("__uatuIndex", index)
|
_ = handle.Set("__sanderlingIndex", index)
|
||||||
|
|
||||||
_ = handle.Set("implies", v.binaryChain(index, specKindImplies))
|
_ = handle.Set("implies", v.binaryChain(index, specKindImplies))
|
||||||
_ = handle.Set("or", v.binaryChain(index, specKindOr))
|
_ = handle.Set("or", v.binaryChain(index, specKindOr))
|
||||||
@@ -326,7 +326,7 @@ func (v *Verifier) eventuallyWithin(selfIndex int) func(call goja.FunctionCall)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractSpecIndex reads __uatuFormulaSpec from a JS formula handle.
|
// extractSpecIndex reads __sanderlingFormulaSpec from a JS formula handle.
|
||||||
func (v *Verifier) extractSpecIndex(value goja.Value) (int, bool) {
|
func (v *Verifier) extractSpecIndex(value goja.Value) (int, bool) {
|
||||||
if value == nil || goja.IsNull(value) || goja.IsUndefined(value) {
|
if value == nil || goja.IsNull(value) || goja.IsUndefined(value) {
|
||||||
return 0, false
|
return 0, false
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/dop251/goja"
|
"github.com/dop251/goja"
|
||||||
|
|
||||||
"github.com/priyanshujain/uatu/internal/hierarchy"
|
"github.com/priyanshujain/sanderling/internal/hierarchy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Snapshots is the per-step extractor output forwarded by the SDK.
|
// Snapshots is the per-step extractor output forwarded by the SDK.
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/priyanshujain/uatu/internal/bundler"
|
"github.com/priyanshujain/sanderling/internal/bundler"
|
||||||
"github.com/priyanshujain/uatu/internal/hierarchy"
|
"github.com/priyanshujain/sanderling/internal/hierarchy"
|
||||||
"github.com/priyanshujain/uatu/internal/ltl"
|
"github.com/priyanshujain/sanderling/internal/ltl"
|
||||||
)
|
)
|
||||||
|
|
||||||
// bundleIntegrationSpec bundles testdata/integration_spec.ts via the real
|
// bundleIntegrationSpec bundles testdata/integration_spec.ts via the real
|
||||||
// @uatu/spec API so the integration test exercises the same path the CLI
|
// @sanderling/spec API so the integration test exercises the same path the CLI
|
||||||
// uses, with no reference to any specific example app.
|
// uses, with no reference to any specific example app.
|
||||||
func bundleIntegrationSpec(t *testing.T) string {
|
func bundleIntegrationSpec(t *testing.T) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@@ -30,8 +30,8 @@ func bundleIntegrationSpec(t *testing.T) string {
|
|||||||
bundle, err := bundler.Bundle(bundler.Options{
|
bundle, err := bundler.Bundle(bundler.Options{
|
||||||
EntryFile: specPath,
|
EntryFile: specPath,
|
||||||
Aliases: map[string]string{
|
Aliases: map[string]string{
|
||||||
"@uatu/spec": apiPath,
|
"@sanderling/spec": apiPath,
|
||||||
"@uatu/spec/defaults/properties": defaultsPath,
|
"@sanderling/spec/defaults/properties": defaultsPath,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+2
-2
@@ -6,8 +6,8 @@ import {
|
|||||||
eventually,
|
eventually,
|
||||||
extract,
|
extract,
|
||||||
weighted,
|
weighted,
|
||||||
} from "@uatu/spec";
|
} from "@sanderling/spec";
|
||||||
import { noUncaughtExceptions } from "@uatu/spec/defaults/properties";
|
import { noUncaughtExceptions } from "@sanderling/spec/defaults/properties";
|
||||||
|
|
||||||
const route = extract<string>(
|
const route = extract<string>(
|
||||||
(state) => (state.snapshots.route as string) ?? "",
|
(state) => (state.snapshots.route as string) ?? "",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/priyanshujain/uatu/internal/ltl"
|
"github.com/priyanshujain/sanderling/internal/ltl"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newVerifier(t *testing.T, options ...Option) *Verifier {
|
func newVerifier(t *testing.T, options ...Option) *Verifier {
|
||||||
@@ -27,18 +27,18 @@ func mustLoad(t *testing.T, verifier *Verifier, source string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const helloSpec = `
|
const helloSpec = `
|
||||||
const screen = __uatu__.extract(state => state.snapshots.screen ?? "");
|
const screen = __sanderling__.extract(state => state.snapshots.screen ?? "");
|
||||||
const balance = __uatu__.extract(state => state.snapshots["ledger.balance"] ?? 0);
|
const balance = __sanderling__.extract(state => state.snapshots["ledger.balance"] ?? 0);
|
||||||
|
|
||||||
globalThis.screen = screen;
|
globalThis.screen = screen;
|
||||||
globalThis.balance = balance;
|
globalThis.balance = balance;
|
||||||
|
|
||||||
globalThis.properties = {
|
globalThis.properties = {
|
||||||
balanceNonNegative: __uatu__.always(() => balance.current >= 0),
|
balanceNonNegative: __sanderling__.always(() => balance.current >= 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
globalThis.actions = __uatu__.actions(() => [
|
globalThis.actions = __sanderling__.actions(() => [
|
||||||
__uatu__.tap({ on: "id:home_button" }),
|
__sanderling__.tap({ on: "id:home_button" }),
|
||||||
]);
|
]);
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -135,9 +135,9 @@ func TestNextAction_FromActionsGenerator(t *testing.T) {
|
|||||||
func TestNextAction_WeightedSelectsByWeight(t *testing.T) {
|
func TestNextAction_WeightedSelectsByWeight(t *testing.T) {
|
||||||
verifier := newVerifier(t, WithRand(rand.New(rand.NewPCG(42, 0))))
|
verifier := newVerifier(t, WithRand(rand.New(rand.NewPCG(42, 0))))
|
||||||
mustLoad(t, verifier, `
|
mustLoad(t, verifier, `
|
||||||
const tapHome = __uatu__.actions(() => [__uatu__.tap({ on: "id:home" })]);
|
const tapHome = __sanderling__.actions(() => [__sanderling__.tap({ on: "id:home" })]);
|
||||||
const tapAway = __uatu__.actions(() => [__uatu__.tap({ on: "id:away" })]);
|
const tapAway = __sanderling__.actions(() => [__sanderling__.tap({ on: "id:away" })]);
|
||||||
globalThis.actions = __uatu__.weighted(
|
globalThis.actions = __sanderling__.weighted(
|
||||||
[1, tapHome],
|
[1, tapHome],
|
||||||
[99, tapAway],
|
[99, tapAway],
|
||||||
);
|
);
|
||||||
@@ -166,7 +166,7 @@ func TestNextAction_WeightedSelectsByWeight(t *testing.T) {
|
|||||||
func TestNextAction_EmptyGeneratorReturnsErrNoAction(t *testing.T) {
|
func TestNextAction_EmptyGeneratorReturnsErrNoAction(t *testing.T) {
|
||||||
verifier := newVerifier(t)
|
verifier := newVerifier(t)
|
||||||
mustLoad(t, verifier, `
|
mustLoad(t, verifier, `
|
||||||
globalThis.actions = __uatu__.actions(() => []);
|
globalThis.actions = __sanderling__.actions(() => []);
|
||||||
`)
|
`)
|
||||||
_ = verifier.PushSnapshot(SnapshotInput{Snapshots: Snapshots{}})
|
_ = verifier.PushSnapshot(SnapshotInput{Snapshots: Snapshots{}})
|
||||||
|
|
||||||
@@ -179,8 +179,8 @@ func TestNextAction_EmptyGeneratorReturnsErrNoAction(t *testing.T) {
|
|||||||
func TestInputText_RoundTrip(t *testing.T) {
|
func TestInputText_RoundTrip(t *testing.T) {
|
||||||
verifier := newVerifier(t)
|
verifier := newVerifier(t)
|
||||||
mustLoad(t, verifier, `
|
mustLoad(t, verifier, `
|
||||||
globalThis.actions = __uatu__.actions(() => [
|
globalThis.actions = __sanderling__.actions(() => [
|
||||||
__uatu__.inputText({ into: "id:phone", text: "+919876543210" }),
|
__sanderling__.inputText({ into: "id:phone", text: "+919876543210" }),
|
||||||
]);
|
]);
|
||||||
`)
|
`)
|
||||||
_ = verifier.PushSnapshot(SnapshotInput{Snapshots: Snapshots{}})
|
_ = verifier.PushSnapshot(SnapshotInput{Snapshots: Snapshots{}})
|
||||||
@@ -200,7 +200,7 @@ func TestInputText_RoundTrip(t *testing.T) {
|
|||||||
func TestPushSnapshot_FeedsSnapshotsToExtractorState(t *testing.T) {
|
func TestPushSnapshot_FeedsSnapshotsToExtractorState(t *testing.T) {
|
||||||
verifier := newVerifier(t)
|
verifier := newVerifier(t)
|
||||||
mustLoad(t, verifier, `
|
mustLoad(t, verifier, `
|
||||||
globalThis.captured = __uatu__.extract(state => state.snapshots["k"]);
|
globalThis.captured = __sanderling__.extract(state => state.snapshots["k"]);
|
||||||
`)
|
`)
|
||||||
if err := verifier.PushSnapshot(SnapshotInput{Snapshots: Snapshots{"k": json.RawMessage(`"hello"`)}}); err != nil {
|
if err := verifier.PushSnapshot(SnapshotInput{Snapshots: Snapshots{"k": json.RawMessage(`"hello"`)}}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -222,7 +222,7 @@ func TestLoad_PropagatesSyntaxError(t *testing.T) {
|
|||||||
func TestEvaluateProperties_ThrowingPredicateDoesNotPanic(t *testing.T) {
|
func TestEvaluateProperties_ThrowingPredicateDoesNotPanic(t *testing.T) {
|
||||||
const spec = `
|
const spec = `
|
||||||
globalThis.properties = {
|
globalThis.properties = {
|
||||||
broken: __uatu__.always(() => { throw new Error("bad predicate"); }),
|
broken: __sanderling__.always(() => { throw new Error("bad predicate"); }),
|
||||||
};
|
};
|
||||||
`
|
`
|
||||||
verifier := newVerifier(t)
|
verifier := newVerifier(t)
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
"github.com/dop251/goja"
|
"github.com/dop251/goja"
|
||||||
|
|
||||||
"github.com/priyanshujain/uatu/internal/hierarchy"
|
"github.com/priyanshujain/sanderling/internal/hierarchy"
|
||||||
"github.com/priyanshujain/uatu/internal/ltl"
|
"github.com/priyanshujain/sanderling/internal/ltl"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Verifier struct {
|
type Verifier struct {
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
# @uatu/spec
|
# @sanderling/spec
|
||||||
|
|
||||||
TypeScript spec API for [uatu](https://github.com/priyanshujain/uatu), a property-based UI fuzzer for mobile apps.
|
TypeScript spec API for [sanderling](https://github.com/priyanshujain/sanderling), a property-based UI fuzzer for mobile apps.
|
||||||
|
|
||||||
Spec authors write specs in TypeScript that describe what an app should *always* do (safety invariants), generate weighted actions to exercise the app, and extract structured state from the accessibility tree. The `uatu` CLI picks up the spec and drives the app under test.
|
Spec authors write specs in TypeScript that describe what an app should *always* do (safety invariants), generate weighted actions to exercise the app, and extract structured state from the accessibility tree. The `sanderling` CLI picks up the spec and drives the app under test.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm install --save-dev @uatu/spec
|
npm install --save-dev @sanderling/spec
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { extract, always, actions, Tap, weighted } from "@uatu/spec";
|
import { extract, always, actions, Tap, weighted } from "@sanderling/spec";
|
||||||
|
|
||||||
export const spec = {
|
export const spec = {
|
||||||
extract: extract((tree) => ({
|
extract: extract((tree) => ({
|
||||||
@@ -32,7 +32,7 @@ export const spec = {
|
|||||||
|
|
||||||
## Version compatibility
|
## Version compatibility
|
||||||
|
|
||||||
`@uatu/spec` is released in lockstep with the uatu CLI. Pin the same major/minor version as your installed `uatu` binary.
|
`@sanderling/spec` is released in lockstep with the sanderling CLI. Pin the same major/minor version as your installed `sanderling` binary.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "@uatu/spec",
|
"name": "@sanderling/spec",
|
||||||
"version": "0.0.0-dev",
|
"version": "0.0.0-dev",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@uatu/spec",
|
"name": "@sanderling/spec",
|
||||||
"version": "0.0.0-dev",
|
"version": "0.0.0-dev",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@uatu/spec",
|
"name": "@sanderling/spec",
|
||||||
"version": "0.0.0-dev",
|
"version": "0.0.0-dev",
|
||||||
"description": "TypeScript spec API for uatu, a property-based UI fuzzer for mobile apps.",
|
"description": "TypeScript spec API for sanderling, a property-based UI fuzzer for mobile apps.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
@@ -21,12 +21,12 @@
|
|||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/priyanshujain/uatu.git",
|
"url": "git+https://github.com/priyanshujain/sanderling.git",
|
||||||
"directory": "pkg/spec-api"
|
"directory": "pkg/spec-api"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/priyanshujain/uatu",
|
"homepage": "https://github.com/priyanshujain/sanderling",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/priyanshujain/uatu/issues"
|
"url": "https://github.com/priyanshujain/sanderling/issues"
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"author": "Priyanshu Jain",
|
"author": "Priyanshu Jain",
|
||||||
|
|||||||
@@ -14,26 +14,26 @@ import type {
|
|||||||
} from "./types.ts";
|
} from "./types.ts";
|
||||||
|
|
||||||
export function actions(generator: () => Action[]): ActionGenerator {
|
export function actions(generator: () => Action[]): ActionGenerator {
|
||||||
return globalThis.__uatu__.actions(generator);
|
return globalThis.__sanderling__.actions(generator);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function weighted(...entries: WeightedEntry[]): ActionGenerator {
|
export function weighted(...entries: WeightedEntry[]): ActionGenerator {
|
||||||
return globalThis.__uatu__.weighted(...entries);
|
return globalThis.__sanderling__.weighted(...entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function from<T>(items: readonly T[]): Sampler<T> {
|
export function from<T>(items: readonly T[]): Sampler<T> {
|
||||||
return globalThis.__uatu__.from(items);
|
return globalThis.__sanderling__.from(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Tap(parameters: { on: string | AccessibilityElement }): TapAction {
|
export function Tap(parameters: { on: string | AccessibilityElement }): TapAction {
|
||||||
return globalThis.__uatu__.tap(parameters);
|
return globalThis.__sanderling__.tap(parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InputText(parameters: {
|
export function InputText(parameters: {
|
||||||
into: string | AccessibilityElement;
|
into: string | AccessibilityElement;
|
||||||
text: string;
|
text: string;
|
||||||
}): InputTextAction {
|
}): InputTextAction {
|
||||||
return globalThis.__uatu__.inputText(parameters);
|
return globalThis.__sanderling__.inputText(parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Swipe(parameters: {
|
export function Swipe(parameters: {
|
||||||
@@ -41,21 +41,21 @@ export function Swipe(parameters: {
|
|||||||
to: Point | AccessibilityElement;
|
to: Point | AccessibilityElement;
|
||||||
durationMillis?: number;
|
durationMillis?: number;
|
||||||
}): SwipeAction {
|
}): SwipeAction {
|
||||||
return globalThis.__uatu__.swipe(parameters);
|
return globalThis.__sanderling__.swipe(parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PressKey(parameters: { key: Key }): PressKeyAction {
|
export function PressKey(parameters: { key: Key }): PressKeyAction {
|
||||||
return globalThis.__uatu__.pressKey(parameters);
|
return globalThis.__sanderling__.pressKey(parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Wait(parameters: { durationMillis: number }): WaitAction {
|
export function Wait(parameters: { durationMillis: number }): WaitAction {
|
||||||
return globalThis.__uatu__.wait(parameters);
|
return globalThis.__sanderling__.wait(parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
function builtinGenerator(name: "taps" | "swipes" | "waitOnce" | "pressKeys"): ActionGenerator {
|
function builtinGenerator(name: "taps" | "swipes" | "waitOnce" | "pressKeys"): ActionGenerator {
|
||||||
return new Proxy({} as ActionGenerator, {
|
return new Proxy({} as ActionGenerator, {
|
||||||
get(_target, property) {
|
get(_target, property) {
|
||||||
const runtime = globalThis.__uatu__[name] as unknown as Record<
|
const runtime = globalThis.__sanderling__[name] as unknown as Record<
|
||||||
string | symbol,
|
string | symbol,
|
||||||
unknown
|
unknown
|
||||||
>;
|
>;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { Formula } from "../types.ts";
|
|||||||
|
|
||||||
const exceptionCount = extract<number>((state) => state.exceptions.length);
|
const exceptionCount = extract<number>((state) => state.exceptions.length);
|
||||||
|
|
||||||
// Fails when the SDK captured an uncaught throwable or a Uatu.reportError
|
// Fails when the SDK captured an uncaught throwable or a Sanderling.reportError
|
||||||
// call surfaced one during the run.
|
// call surfaced one during the run.
|
||||||
export const noUncaughtExceptions: Formula = always(
|
export const noUncaughtExceptions: Formula = always(
|
||||||
() => exceptionCount.current === 0,
|
() => exceptionCount.current === 0,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Extracted, State } from "./types.ts";
|
import type { Extracted, State } from "./types.ts";
|
||||||
|
|
||||||
export function extract<T>(getter: (state: State) => T): Extracted<T> {
|
export function extract<T>(getter: (state: State) => T): Extracted<T> {
|
||||||
return globalThis.__uatu__.extract(getter);
|
return globalThis.__sanderling__.extract(getter);
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ export type {
|
|||||||
State,
|
State,
|
||||||
SwipeAction,
|
SwipeAction,
|
||||||
TapAction,
|
TapAction,
|
||||||
UatuRuntime,
|
SanderlingRuntime,
|
||||||
WaitAction,
|
WaitAction,
|
||||||
WeightedEntry,
|
WeightedEntry,
|
||||||
} from "./types.ts";
|
} from "./types.ts";
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import type { EventuallyFormula, Formula } from "./types.ts";
|
import type { EventuallyFormula, Formula } from "./types.ts";
|
||||||
|
|
||||||
export function always(predicateOrFormula: (() => boolean) | Formula): Formula {
|
export function always(predicateOrFormula: (() => boolean) | Formula): Formula {
|
||||||
return globalThis.__uatu__.always(predicateOrFormula);
|
return globalThis.__sanderling__.always(predicateOrFormula);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function now(predicate: () => boolean): Formula {
|
export function now(predicate: () => boolean): Formula {
|
||||||
return globalThis.__uatu__.now(predicate);
|
return globalThis.__sanderling__.now(predicate);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function next(predicate: () => boolean): Formula {
|
export function next(predicate: () => boolean): Formula {
|
||||||
return globalThis.__uatu__.next(predicate);
|
return globalThis.__sanderling__.next(predicate);
|
||||||
}
|
}
|
||||||
|
|
||||||
// An unbounded `eventually` never forces a violation within a finite run —
|
// An unbounded `eventually` never forces a violation within a finite run —
|
||||||
// prefer `.within(n, unit)` when you want the verifier to fail a property
|
// prefer `.within(n, unit)` when you want the verifier to fail a property
|
||||||
// that stalls.
|
// that stalls.
|
||||||
export function eventually(predicate: () => boolean): EventuallyFormula {
|
export function eventually(predicate: () => boolean): EventuallyFormula {
|
||||||
return globalThis.__uatu__.eventually(predicate);
|
return globalThis.__sanderling__.eventually(predicate);
|
||||||
}
|
}
|
||||||
@@ -78,12 +78,12 @@ export type Key =
|
|||||||
| "right";
|
| "right";
|
||||||
|
|
||||||
export interface ActionGenerator {
|
export interface ActionGenerator {
|
||||||
readonly __uatuActionGenerator: true;
|
readonly __sanderlingActionGenerator: true;
|
||||||
generate(): Action[];
|
generate(): Action[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Formula {
|
export interface Formula {
|
||||||
readonly __uatuFormula: true;
|
readonly __sanderlingFormula: true;
|
||||||
implies(other: Formula): Formula;
|
implies(other: Formula): Formula;
|
||||||
or(other: Formula): Formula;
|
or(other: Formula): Formula;
|
||||||
and(other: Formula): Formula;
|
and(other: Formula): Formula;
|
||||||
@@ -98,7 +98,7 @@ export interface Sampler<T> {
|
|||||||
generate(): T;
|
generate(): T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UatuRuntime {
|
export interface SanderlingRuntime {
|
||||||
extract: <T>(getter: (state: State) => T) => Extracted<T>;
|
extract: <T>(getter: (state: State) => T) => Extracted<T>;
|
||||||
always: (predicateOrFormula: (() => boolean) | Formula) => Formula;
|
always: (predicateOrFormula: (() => boolean) | Formula) => Formula;
|
||||||
now: (predicate: () => boolean) => Formula;
|
now: (predicate: () => boolean) => Formula;
|
||||||
@@ -129,5 +129,5 @@ export type WeightedEntry = readonly [number, ActionGenerator];
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// eslint-disable-next-line no-var
|
// eslint-disable-next-line no-var
|
||||||
var __uatu__: UatuRuntime;
|
var __sanderling__: SanderlingRuntime;
|
||||||
}
|
}
|
||||||
@@ -29,11 +29,11 @@ import type {
|
|||||||
Formula,
|
Formula,
|
||||||
Sampler,
|
Sampler,
|
||||||
State,
|
State,
|
||||||
UatuRuntime,
|
SanderlingRuntime,
|
||||||
WeightedEntry,
|
WeightedEntry,
|
||||||
} from "../src/types.ts";
|
} from "../src/types.ts";
|
||||||
|
|
||||||
interface RecordedRuntime extends UatuRuntime {
|
interface RecordedRuntime extends SanderlingRuntime {
|
||||||
extracts: Array<(state: State) => unknown>;
|
extracts: Array<(state: State) => unknown>;
|
||||||
alwaysArgs: Array<(() => boolean) | Formula>;
|
alwaysArgs: Array<(() => boolean) | Formula>;
|
||||||
nowPredicates: Array<() => boolean>;
|
nowPredicates: Array<() => boolean>;
|
||||||
@@ -51,7 +51,7 @@ interface RecordedRuntime extends UatuRuntime {
|
|||||||
|
|
||||||
function makeChainableFormula(record: RecordedRuntime): Formula {
|
function makeChainableFormula(record: RecordedRuntime): Formula {
|
||||||
const formula: Formula = {
|
const formula: Formula = {
|
||||||
__uatuFormula: true,
|
__sanderlingFormula: true,
|
||||||
implies(other: Formula): Formula {
|
implies(other: Formula): Formula {
|
||||||
record.impliesCalls++;
|
record.impliesCalls++;
|
||||||
void other;
|
void other;
|
||||||
@@ -125,11 +125,11 @@ function installFakeRuntime(): RecordedRuntime {
|
|||||||
},
|
},
|
||||||
actions: (generator: () => Action[]): ActionGenerator => {
|
actions: (generator: () => Action[]): ActionGenerator => {
|
||||||
calls.actionGenerators.push(generator);
|
calls.actionGenerators.push(generator);
|
||||||
return { __uatuActionGenerator: true, generate: generator };
|
return { __sanderlingActionGenerator: true, generate: generator };
|
||||||
},
|
},
|
||||||
weighted: (...entries: WeightedEntry[]): ActionGenerator => {
|
weighted: (...entries: WeightedEntry[]): ActionGenerator => {
|
||||||
calls.weightedCalls.push(entries);
|
calls.weightedCalls.push(entries);
|
||||||
return { __uatuActionGenerator: true, generate: () => [] };
|
return { __sanderlingActionGenerator: true, generate: () => [] };
|
||||||
},
|
},
|
||||||
from: <T>(items: readonly T[]): Sampler<T> => {
|
from: <T>(items: readonly T[]): Sampler<T> => {
|
||||||
calls.fromCalls.push(items as unknown[]);
|
calls.fromCalls.push(items as unknown[]);
|
||||||
@@ -145,13 +145,13 @@ function installFakeRuntime(): RecordedRuntime {
|
|||||||
}),
|
}),
|
||||||
pressKey: ({ key }) => ({ kind: "PressKey", key }),
|
pressKey: ({ key }) => ({ kind: "PressKey", key }),
|
||||||
wait: ({ durationMillis }) => ({ kind: "Wait", durationMillis }),
|
wait: ({ durationMillis }) => ({ kind: "Wait", durationMillis }),
|
||||||
taps: { __uatuActionGenerator: true, generate: () => [] },
|
taps: { __sanderlingActionGenerator: true, generate: () => [] },
|
||||||
swipes: { __uatuActionGenerator: true, generate: () => [] },
|
swipes: { __sanderlingActionGenerator: true, generate: () => [] },
|
||||||
waitOnce: { __uatuActionGenerator: true, generate: () => [] },
|
waitOnce: { __sanderlingActionGenerator: true, generate: () => [] },
|
||||||
pressKeys: { __uatuActionGenerator: true, generate: () => [] },
|
pressKeys: { __sanderlingActionGenerator: true, generate: () => [] },
|
||||||
} satisfies UatuRuntime;
|
} satisfies SanderlingRuntime;
|
||||||
const recorded = Object.assign(runtime, calls) as RecordedRuntime;
|
const recorded = Object.assign(runtime, calls) as RecordedRuntime;
|
||||||
globalThis.__uatu__ = recorded;
|
globalThis.__sanderling__ = recorded;
|
||||||
return recorded;
|
return recorded;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ test("always wraps a predicate into a formula via the runtime", () => {
|
|||||||
const predicate = () => true;
|
const predicate = () => true;
|
||||||
const formula = always(predicate);
|
const formula = always(predicate);
|
||||||
assert.equal(runtime.alwaysArgs[0], predicate);
|
assert.equal(runtime.alwaysArgs[0], predicate);
|
||||||
assert.equal(formula.__uatuFormula, true);
|
assert.equal(formula.__sanderlingFormula, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("always accepts a formula handle", () => {
|
test("always accepts a formula handle", () => {
|
||||||
@@ -176,7 +176,7 @@ test("always accepts a formula handle", () => {
|
|||||||
const inner = now(() => true);
|
const inner = now(() => true);
|
||||||
const wrapped = always(inner);
|
const wrapped = always(inner);
|
||||||
assert.equal(runtime.alwaysArgs.at(-1), inner);
|
assert.equal(runtime.alwaysArgs.at(-1), inner);
|
||||||
assert.equal(wrapped.__uatuFormula, true);
|
assert.equal(wrapped.__sanderlingFormula, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("now/next/eventually forward predicates", () => {
|
test("now/next/eventually forward predicates", () => {
|
||||||
@@ -257,7 +257,7 @@ test("actions wraps a generator into the runtime's ActionGenerator", () => {
|
|||||||
const generator = () => [Tap({ on: "id:x" })];
|
const generator = () => [Tap({ on: "id:x" })];
|
||||||
const wrapped = actions(generator);
|
const wrapped = actions(generator);
|
||||||
assert.equal(runtime.actionGenerators[0], generator);
|
assert.equal(runtime.actionGenerators[0], generator);
|
||||||
assert.equal(wrapped.__uatuActionGenerator, true);
|
assert.equal(wrapped.__sanderlingActionGenerator, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("weighted forwards weighted entries to the runtime", () => {
|
test("weighted forwards weighted entries to the runtime", () => {
|
||||||
@@ -279,8 +279,8 @@ test("from forwards items to the runtime", () => {
|
|||||||
|
|
||||||
test("default generators proxy through to the runtime", () => {
|
test("default generators proxy through to the runtime", () => {
|
||||||
installFakeRuntime();
|
installFakeRuntime();
|
||||||
assert.equal(taps.__uatuActionGenerator, true);
|
assert.equal(taps.__sanderlingActionGenerator, true);
|
||||||
assert.equal(swipes.__uatuActionGenerator, true);
|
assert.equal(swipes.__sanderlingActionGenerator, true);
|
||||||
assert.equal(waitOnce.__uatuActionGenerator, true);
|
assert.equal(waitOnce.__sanderlingActionGenerator, true);
|
||||||
assert.equal(pressKey.__uatuActionGenerator, true);
|
assert.equal(pressKey.__sanderlingActionGenerator, true);
|
||||||
});
|
});
|
||||||
@@ -9,11 +9,11 @@ import type {
|
|||||||
Formula,
|
Formula,
|
||||||
Sampler,
|
Sampler,
|
||||||
State,
|
State,
|
||||||
UatuRuntime,
|
SanderlingRuntime,
|
||||||
WeightedEntry,
|
WeightedEntry,
|
||||||
} from "../src/types.ts";
|
} from "../src/types.ts";
|
||||||
|
|
||||||
interface RecordedRuntime extends UatuRuntime {
|
interface RecordedRuntime extends SanderlingRuntime {
|
||||||
currentState: State;
|
currentState: State;
|
||||||
extractors: Array<(state: State) => unknown>;
|
extractors: Array<(state: State) => unknown>;
|
||||||
alwaysArgs: Array<(() => boolean) | Formula>;
|
alwaysArgs: Array<(() => boolean) | Formula>;
|
||||||
@@ -43,17 +43,17 @@ function installRuntime(initialState: State): RecordedRuntime {
|
|||||||
if (typeof predicateOrFormula === "function") {
|
if (typeof predicateOrFormula === "function") {
|
||||||
lastPredicate = predicateOrFormula;
|
lastPredicate = predicateOrFormula;
|
||||||
}
|
}
|
||||||
return { __uatuFormula: true } as Formula;
|
return { __sanderlingFormula: true } as Formula;
|
||||||
},
|
},
|
||||||
now: () => ({ __uatuFormula: true } as Formula),
|
now: () => ({ __sanderlingFormula: true } as Formula),
|
||||||
next: () => ({ __uatuFormula: true } as Formula),
|
next: () => ({ __sanderlingFormula: true } as Formula),
|
||||||
eventually: () => ({ __uatuFormula: true } as EventuallyFormula),
|
eventually: () => ({ __sanderlingFormula: true } as EventuallyFormula),
|
||||||
actions: (generator: () => Action[]): ActionGenerator => ({
|
actions: (generator: () => Action[]): ActionGenerator => ({
|
||||||
__uatuActionGenerator: true,
|
__sanderlingActionGenerator: true,
|
||||||
generate: generator,
|
generate: generator,
|
||||||
}),
|
}),
|
||||||
weighted: (..._entries: WeightedEntry[]): ActionGenerator => ({
|
weighted: (..._entries: WeightedEntry[]): ActionGenerator => ({
|
||||||
__uatuActionGenerator: true,
|
__sanderlingActionGenerator: true,
|
||||||
generate: () => [],
|
generate: () => [],
|
||||||
}),
|
}),
|
||||||
from: <T>(_items: readonly T[]): Sampler<T> => ({ generate: () => _items[0] as T }),
|
from: <T>(_items: readonly T[]): Sampler<T> => ({ generate: () => _items[0] as T }),
|
||||||
@@ -62,11 +62,11 @@ function installRuntime(initialState: State): RecordedRuntime {
|
|||||||
swipe: (p) => ({ kind: "Swipe", from: p.from, to: p.to, durationMillis: p.durationMillis }),
|
swipe: (p) => ({ kind: "Swipe", from: p.from, to: p.to, durationMillis: p.durationMillis }),
|
||||||
pressKey: ({ key }) => ({ kind: "PressKey", key }),
|
pressKey: ({ key }) => ({ kind: "PressKey", key }),
|
||||||
wait: ({ durationMillis }) => ({ kind: "Wait", durationMillis }),
|
wait: ({ durationMillis }) => ({ kind: "Wait", durationMillis }),
|
||||||
taps: { __uatuActionGenerator: true, generate: () => [] } as ActionGenerator,
|
taps: { __sanderlingActionGenerator: true, generate: () => [] } as ActionGenerator,
|
||||||
swipes: { __uatuActionGenerator: true, generate: () => [] } as ActionGenerator,
|
swipes: { __sanderlingActionGenerator: true, generate: () => [] } as ActionGenerator,
|
||||||
waitOnce: { __uatuActionGenerator: true, generate: () => [] } as ActionGenerator,
|
waitOnce: { __sanderlingActionGenerator: true, generate: () => [] } as ActionGenerator,
|
||||||
pressKeys: { __uatuActionGenerator: true, generate: () => [] } as ActionGenerator,
|
pressKeys: { __sanderlingActionGenerator: true, generate: () => [] } as ActionGenerator,
|
||||||
} satisfies UatuRuntime;
|
} satisfies SanderlingRuntime;
|
||||||
|
|
||||||
const state = { currentState: initialState };
|
const state = { currentState: initialState };
|
||||||
const recorded = Object.assign(runtime, {
|
const recorded = Object.assign(runtime, {
|
||||||
@@ -77,7 +77,7 @@ function installRuntime(initialState: State): RecordedRuntime {
|
|||||||
return lastPredicate;
|
return lastPredicate;
|
||||||
},
|
},
|
||||||
}) as unknown as RecordedRuntime;
|
}) as unknown as RecordedRuntime;
|
||||||
globalThis.__uatu__ = recorded;
|
globalThis.__sanderling__ = recorded;
|
||||||
// Re-bind state ref so subsequent extract() calls read the up-to-date state.
|
// Re-bind state ref so subsequent extract() calls read the up-to-date state.
|
||||||
Object.defineProperty(recorded, "currentState", {
|
Object.defineProperty(recorded, "currentState", {
|
||||||
get() {
|
get() {
|
||||||
@@ -105,6 +105,6 @@ test("defaults bundle exports formulas tagged as LTL properties", async () => {
|
|||||||
logs: [{ unixMillis: 1, level: "W", tag: "X", message: "warn" }],
|
logs: [{ unixMillis: 1, level: "W", tag: "X", message: "warn" }],
|
||||||
});
|
});
|
||||||
const defaults = await import("../src/defaults/properties.ts");
|
const defaults = await import("../src/defaults/properties.ts");
|
||||||
assert.equal(defaults.noUncaughtExceptions.__uatuFormula, true);
|
assert.equal(defaults.noUncaughtExceptions.__sanderlingFormula, true);
|
||||||
assert.equal(defaults.noLogcatErrors.__uatuFormula, true);
|
assert.equal(defaults.noLogcatErrors.__sanderlingFormula, true);
|
||||||
});
|
});
|
||||||
+69
-69
@@ -849,7 +849,7 @@ var File_driverpb_driver_proto protoreflect.FileDescriptor
|
|||||||
|
|
||||||
const file_driverpb_driver_proto_rawDesc = "" +
|
const file_driverpb_driver_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\x15driverpb/driver.proto\x12\x0euatu.driver.v1\"\a\n" +
|
"\x15driverpb/driver.proto\x12\x14sanderling.driver.v1\"\a\n" +
|
||||||
"\x05Empty\"z\n" +
|
"\x05Empty\"z\n" +
|
||||||
"\rLaunchRequest\x12\x1b\n" +
|
"\rLaunchRequest\x12\x1b\n" +
|
||||||
"\tbundle_id\x18\x01 \x01(\tR\bbundleId\x12\x1f\n" +
|
"\tbundle_id\x18\x01 \x01(\tR\bbundleId\x12\x1f\n" +
|
||||||
@@ -874,19 +874,19 @@ const file_driverpb_driver_proto_rawDesc = "" +
|
|||||||
"\fHealthStatus\x12\x14\n" +
|
"\fHealthStatus\x12\x14\n" +
|
||||||
"\x05ready\x18\x01 \x01(\bR\x05ready\x12\x18\n" +
|
"\x05ready\x18\x01 \x01(\bR\x05ready\x12\x18\n" +
|
||||||
"\aversion\x18\x02 \x01(\tR\aversion\x12\x1a\n" +
|
"\aversion\x18\x02 \x01(\tR\aversion\x12\x1a\n" +
|
||||||
"\bplatform\x18\x03 \x01(\tR\bplatform\"\x89\x01\n" +
|
"\bplatform\x18\x03 \x01(\tR\bplatform\"\x95\x01\n" +
|
||||||
"\fSwipeRequest\x12)\n" +
|
"\fSwipeRequest\x12/\n" +
|
||||||
"\x04from\x18\x01 \x01(\v2\x15.uatu.driver.v1.PointR\x04from\x12%\n" +
|
"\x04from\x18\x01 \x01(\v2\x1b.sanderling.driver.v1.PointR\x04from\x12+\n" +
|
||||||
"\x02to\x18\x02 \x01(\v2\x15.uatu.driver.v1.PointR\x02to\x12'\n" +
|
"\x02to\x18\x02 \x01(\v2\x1b.sanderling.driver.v1.PointR\x02to\x12'\n" +
|
||||||
"\x0fduration_millis\x18\x03 \x01(\x03R\x0edurationMillis\"#\n" +
|
"\x0fduration_millis\x18\x03 \x01(\x03R\x0edurationMillis\"#\n" +
|
||||||
"\x0fPressKeyRequest\x12\x10\n" +
|
"\x0fPressKeyRequest\x12\x10\n" +
|
||||||
"\x03key\x18\x01 \x01(\tR\x03key\"e\n" +
|
"\x03key\x18\x01 \x01(\tR\x03key\"e\n" +
|
||||||
"\x11RecentLogsRequest\x12*\n" +
|
"\x11RecentLogsRequest\x12*\n" +
|
||||||
"\x11since_unix_millis\x18\x01 \x01(\x03R\x0fsinceUnixMillis\x12$\n" +
|
"\x11since_unix_millis\x18\x01 \x01(\x03R\x0fsinceUnixMillis\x12$\n" +
|
||||||
"\x0elevel_at_least\x18\x02 \x01(\tR\flevelAtLeast\"@\n" +
|
"\x0elevel_at_least\x18\x02 \x01(\tR\flevelAtLeast\"F\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"LogEntries\x122\n" +
|
"LogEntries\x128\n" +
|
||||||
"\aentries\x18\x01 \x03(\v2\x18.uatu.driver.v1.LogEntryR\aentries\"m\n" +
|
"\aentries\x18\x01 \x03(\v2\x1e.sanderling.driver.v1.LogEntryR\aentries\"m\n" +
|
||||||
"\bLogEntry\x12\x1f\n" +
|
"\bLogEntry\x12\x1f\n" +
|
||||||
"\vunix_millis\x18\x01 \x01(\x03R\n" +
|
"\vunix_millis\x18\x01 \x01(\x03R\n" +
|
||||||
"unixMillis\x12\x14\n" +
|
"unixMillis\x12\x14\n" +
|
||||||
@@ -900,24 +900,24 @@ const file_driverpb_driver_proto_rawDesc = "" +
|
|||||||
"cpuPercent\x12\x1d\n" +
|
"cpuPercent\x12\x1d\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"heap_bytes\x18\x02 \x01(\x03R\theapBytes\x12,\n" +
|
"heap_bytes\x18\x02 \x01(\x03R\theapBytes\x12,\n" +
|
||||||
"\x12total_memory_bytes\x18\x03 \x01(\x03R\x10totalMemoryBytes2\xcb\x06\n" +
|
"\x12total_memory_bytes\x18\x03 \x01(\x03R\x10totalMemoryBytes2\xe7\a\n" +
|
||||||
"\x06Driver\x12>\n" +
|
"\x06Driver\x12J\n" +
|
||||||
"\x06Launch\x12\x1d.uatu.driver.v1.LaunchRequest\x1a\x15.uatu.driver.v1.Empty\x129\n" +
|
"\x06Launch\x12#.sanderling.driver.v1.LaunchRequest\x1a\x1b.sanderling.driver.v1.Empty\x12E\n" +
|
||||||
"\tTerminate\x12\x15.uatu.driver.v1.Empty\x1a\x15.uatu.driver.v1.Empty\x123\n" +
|
"\tTerminate\x12\x1b.sanderling.driver.v1.Empty\x1a\x1b.sanderling.driver.v1.Empty\x12?\n" +
|
||||||
"\x03Tap\x12\x15.uatu.driver.v1.Point\x1a\x15.uatu.driver.v1.Empty\x12>\n" +
|
"\x03Tap\x12\x1b.sanderling.driver.v1.Point\x1a\x1b.sanderling.driver.v1.Empty\x12J\n" +
|
||||||
"\vTapSelector\x12\x18.uatu.driver.v1.Selector\x1a\x15.uatu.driver.v1.Empty\x128\n" +
|
"\vTapSelector\x12\x1e.sanderling.driver.v1.Selector\x1a\x1b.sanderling.driver.v1.Empty\x12D\n" +
|
||||||
"\tInputText\x12\x14.uatu.driver.v1.Text\x1a\x15.uatu.driver.v1.Empty\x12<\n" +
|
"\tInputText\x12\x1a.sanderling.driver.v1.Text\x1a\x1b.sanderling.driver.v1.Empty\x12H\n" +
|
||||||
"\x05Swipe\x12\x1c.uatu.driver.v1.SwipeRequest\x1a\x15.uatu.driver.v1.Empty\x12B\n" +
|
"\x05Swipe\x12\".sanderling.driver.v1.SwipeRequest\x1a\x1b.sanderling.driver.v1.Empty\x12N\n" +
|
||||||
"\bPressKey\x12\x1f.uatu.driver.v1.PressKeyRequest\x1a\x15.uatu.driver.v1.Empty\x12:\n" +
|
"\bPressKey\x12%.sanderling.driver.v1.PressKeyRequest\x1a\x1b.sanderling.driver.v1.Empty\x12F\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"Screenshot\x12\x15.uatu.driver.v1.Empty\x1a\x15.uatu.driver.v1.Image\x12A\n" +
|
"Screenshot\x12\x1b.sanderling.driver.v1.Empty\x1a\x1b.sanderling.driver.v1.Image\x12M\n" +
|
||||||
"\tHierarchy\x12\x15.uatu.driver.v1.Empty\x1a\x1d.uatu.driver.v1.HierarchyJSON\x12K\n" +
|
"\tHierarchy\x12\x1b.sanderling.driver.v1.Empty\x1a#.sanderling.driver.v1.HierarchyJSON\x12W\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"RecentLogs\x12!.uatu.driver.v1.RecentLogsRequest\x1a\x1a.uatu.driver.v1.LogEntries\x12>\n" +
|
"RecentLogs\x12'.sanderling.driver.v1.RecentLogsRequest\x1a .sanderling.driver.v1.LogEntries\x12J\n" +
|
||||||
"\vWaitForIdle\x12\x18.uatu.driver.v1.Duration\x1a\x15.uatu.driver.v1.Empty\x12=\n" +
|
"\vWaitForIdle\x12\x1e.sanderling.driver.v1.Duration\x1a\x1b.sanderling.driver.v1.Empty\x12I\n" +
|
||||||
"\x06Health\x12\x15.uatu.driver.v1.Empty\x1a\x1c.uatu.driver.v1.HealthStatus\x12J\n" +
|
"\x06Health\x12\x1b.sanderling.driver.v1.Empty\x1a\".sanderling.driver.v1.HealthStatus\x12V\n" +
|
||||||
"\aMetrics\x12\x1e.uatu.driver.v1.MetricsRequest\x1a\x1f.uatu.driver.v1.MetricsResponseBM\n" +
|
"\aMetrics\x12$.sanderling.driver.v1.MetricsRequest\x1a%.sanderling.driver.v1.MetricsResponseBY\n" +
|
||||||
"\x12dev.uatu.driver.v1P\x01Z5github.com/priyanshujain/uatu/proto/driverpb;driverpbb\x06proto3"
|
"\x18dev.sanderling.driver.v1P\x01Z;github.com/priyanshujain/sanderling/proto/driverpb;driverpbb\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_driverpb_driver_proto_rawDescOnce sync.Once
|
file_driverpb_driver_proto_rawDescOnce sync.Once
|
||||||
@@ -933,53 +933,53 @@ func file_driverpb_driver_proto_rawDescGZIP() []byte {
|
|||||||
|
|
||||||
var file_driverpb_driver_proto_msgTypes = make([]protoimpl.MessageInfo, 16)
|
var file_driverpb_driver_proto_msgTypes = make([]protoimpl.MessageInfo, 16)
|
||||||
var file_driverpb_driver_proto_goTypes = []any{
|
var file_driverpb_driver_proto_goTypes = []any{
|
||||||
(*Empty)(nil), // 0: uatu.driver.v1.Empty
|
(*Empty)(nil), // 0: sanderling.driver.v1.Empty
|
||||||
(*LaunchRequest)(nil), // 1: uatu.driver.v1.LaunchRequest
|
(*LaunchRequest)(nil), // 1: sanderling.driver.v1.LaunchRequest
|
||||||
(*Point)(nil), // 2: uatu.driver.v1.Point
|
(*Point)(nil), // 2: sanderling.driver.v1.Point
|
||||||
(*Text)(nil), // 3: uatu.driver.v1.Text
|
(*Text)(nil), // 3: sanderling.driver.v1.Text
|
||||||
(*Selector)(nil), // 4: uatu.driver.v1.Selector
|
(*Selector)(nil), // 4: sanderling.driver.v1.Selector
|
||||||
(*Image)(nil), // 5: uatu.driver.v1.Image
|
(*Image)(nil), // 5: sanderling.driver.v1.Image
|
||||||
(*HierarchyJSON)(nil), // 6: uatu.driver.v1.HierarchyJSON
|
(*HierarchyJSON)(nil), // 6: sanderling.driver.v1.HierarchyJSON
|
||||||
(*Duration)(nil), // 7: uatu.driver.v1.Duration
|
(*Duration)(nil), // 7: sanderling.driver.v1.Duration
|
||||||
(*HealthStatus)(nil), // 8: uatu.driver.v1.HealthStatus
|
(*HealthStatus)(nil), // 8: sanderling.driver.v1.HealthStatus
|
||||||
(*SwipeRequest)(nil), // 9: uatu.driver.v1.SwipeRequest
|
(*SwipeRequest)(nil), // 9: sanderling.driver.v1.SwipeRequest
|
||||||
(*PressKeyRequest)(nil), // 10: uatu.driver.v1.PressKeyRequest
|
(*PressKeyRequest)(nil), // 10: sanderling.driver.v1.PressKeyRequest
|
||||||
(*RecentLogsRequest)(nil), // 11: uatu.driver.v1.RecentLogsRequest
|
(*RecentLogsRequest)(nil), // 11: sanderling.driver.v1.RecentLogsRequest
|
||||||
(*LogEntries)(nil), // 12: uatu.driver.v1.LogEntries
|
(*LogEntries)(nil), // 12: sanderling.driver.v1.LogEntries
|
||||||
(*LogEntry)(nil), // 13: uatu.driver.v1.LogEntry
|
(*LogEntry)(nil), // 13: sanderling.driver.v1.LogEntry
|
||||||
(*MetricsRequest)(nil), // 14: uatu.driver.v1.MetricsRequest
|
(*MetricsRequest)(nil), // 14: sanderling.driver.v1.MetricsRequest
|
||||||
(*MetricsResponse)(nil), // 15: uatu.driver.v1.MetricsResponse
|
(*MetricsResponse)(nil), // 15: sanderling.driver.v1.MetricsResponse
|
||||||
}
|
}
|
||||||
var file_driverpb_driver_proto_depIdxs = []int32{
|
var file_driverpb_driver_proto_depIdxs = []int32{
|
||||||
2, // 0: uatu.driver.v1.SwipeRequest.from:type_name -> uatu.driver.v1.Point
|
2, // 0: sanderling.driver.v1.SwipeRequest.from:type_name -> sanderling.driver.v1.Point
|
||||||
2, // 1: uatu.driver.v1.SwipeRequest.to:type_name -> uatu.driver.v1.Point
|
2, // 1: sanderling.driver.v1.SwipeRequest.to:type_name -> sanderling.driver.v1.Point
|
||||||
13, // 2: uatu.driver.v1.LogEntries.entries:type_name -> uatu.driver.v1.LogEntry
|
13, // 2: sanderling.driver.v1.LogEntries.entries:type_name -> sanderling.driver.v1.LogEntry
|
||||||
1, // 3: uatu.driver.v1.Driver.Launch:input_type -> uatu.driver.v1.LaunchRequest
|
1, // 3: sanderling.driver.v1.Driver.Launch:input_type -> sanderling.driver.v1.LaunchRequest
|
||||||
0, // 4: uatu.driver.v1.Driver.Terminate:input_type -> uatu.driver.v1.Empty
|
0, // 4: sanderling.driver.v1.Driver.Terminate:input_type -> sanderling.driver.v1.Empty
|
||||||
2, // 5: uatu.driver.v1.Driver.Tap:input_type -> uatu.driver.v1.Point
|
2, // 5: sanderling.driver.v1.Driver.Tap:input_type -> sanderling.driver.v1.Point
|
||||||
4, // 6: uatu.driver.v1.Driver.TapSelector:input_type -> uatu.driver.v1.Selector
|
4, // 6: sanderling.driver.v1.Driver.TapSelector:input_type -> sanderling.driver.v1.Selector
|
||||||
3, // 7: uatu.driver.v1.Driver.InputText:input_type -> uatu.driver.v1.Text
|
3, // 7: sanderling.driver.v1.Driver.InputText:input_type -> sanderling.driver.v1.Text
|
||||||
9, // 8: uatu.driver.v1.Driver.Swipe:input_type -> uatu.driver.v1.SwipeRequest
|
9, // 8: sanderling.driver.v1.Driver.Swipe:input_type -> sanderling.driver.v1.SwipeRequest
|
||||||
10, // 9: uatu.driver.v1.Driver.PressKey:input_type -> uatu.driver.v1.PressKeyRequest
|
10, // 9: sanderling.driver.v1.Driver.PressKey:input_type -> sanderling.driver.v1.PressKeyRequest
|
||||||
0, // 10: uatu.driver.v1.Driver.Screenshot:input_type -> uatu.driver.v1.Empty
|
0, // 10: sanderling.driver.v1.Driver.Screenshot:input_type -> sanderling.driver.v1.Empty
|
||||||
0, // 11: uatu.driver.v1.Driver.Hierarchy:input_type -> uatu.driver.v1.Empty
|
0, // 11: sanderling.driver.v1.Driver.Hierarchy:input_type -> sanderling.driver.v1.Empty
|
||||||
11, // 12: uatu.driver.v1.Driver.RecentLogs:input_type -> uatu.driver.v1.RecentLogsRequest
|
11, // 12: sanderling.driver.v1.Driver.RecentLogs:input_type -> sanderling.driver.v1.RecentLogsRequest
|
||||||
7, // 13: uatu.driver.v1.Driver.WaitForIdle:input_type -> uatu.driver.v1.Duration
|
7, // 13: sanderling.driver.v1.Driver.WaitForIdle:input_type -> sanderling.driver.v1.Duration
|
||||||
0, // 14: uatu.driver.v1.Driver.Health:input_type -> uatu.driver.v1.Empty
|
0, // 14: sanderling.driver.v1.Driver.Health:input_type -> sanderling.driver.v1.Empty
|
||||||
14, // 15: uatu.driver.v1.Driver.Metrics:input_type -> uatu.driver.v1.MetricsRequest
|
14, // 15: sanderling.driver.v1.Driver.Metrics:input_type -> sanderling.driver.v1.MetricsRequest
|
||||||
0, // 16: uatu.driver.v1.Driver.Launch:output_type -> uatu.driver.v1.Empty
|
0, // 16: sanderling.driver.v1.Driver.Launch:output_type -> sanderling.driver.v1.Empty
|
||||||
0, // 17: uatu.driver.v1.Driver.Terminate:output_type -> uatu.driver.v1.Empty
|
0, // 17: sanderling.driver.v1.Driver.Terminate:output_type -> sanderling.driver.v1.Empty
|
||||||
0, // 18: uatu.driver.v1.Driver.Tap:output_type -> uatu.driver.v1.Empty
|
0, // 18: sanderling.driver.v1.Driver.Tap:output_type -> sanderling.driver.v1.Empty
|
||||||
0, // 19: uatu.driver.v1.Driver.TapSelector:output_type -> uatu.driver.v1.Empty
|
0, // 19: sanderling.driver.v1.Driver.TapSelector:output_type -> sanderling.driver.v1.Empty
|
||||||
0, // 20: uatu.driver.v1.Driver.InputText:output_type -> uatu.driver.v1.Empty
|
0, // 20: sanderling.driver.v1.Driver.InputText:output_type -> sanderling.driver.v1.Empty
|
||||||
0, // 21: uatu.driver.v1.Driver.Swipe:output_type -> uatu.driver.v1.Empty
|
0, // 21: sanderling.driver.v1.Driver.Swipe:output_type -> sanderling.driver.v1.Empty
|
||||||
0, // 22: uatu.driver.v1.Driver.PressKey:output_type -> uatu.driver.v1.Empty
|
0, // 22: sanderling.driver.v1.Driver.PressKey:output_type -> sanderling.driver.v1.Empty
|
||||||
5, // 23: uatu.driver.v1.Driver.Screenshot:output_type -> uatu.driver.v1.Image
|
5, // 23: sanderling.driver.v1.Driver.Screenshot:output_type -> sanderling.driver.v1.Image
|
||||||
6, // 24: uatu.driver.v1.Driver.Hierarchy:output_type -> uatu.driver.v1.HierarchyJSON
|
6, // 24: sanderling.driver.v1.Driver.Hierarchy:output_type -> sanderling.driver.v1.HierarchyJSON
|
||||||
12, // 25: uatu.driver.v1.Driver.RecentLogs:output_type -> uatu.driver.v1.LogEntries
|
12, // 25: sanderling.driver.v1.Driver.RecentLogs:output_type -> sanderling.driver.v1.LogEntries
|
||||||
0, // 26: uatu.driver.v1.Driver.WaitForIdle:output_type -> uatu.driver.v1.Empty
|
0, // 26: sanderling.driver.v1.Driver.WaitForIdle:output_type -> sanderling.driver.v1.Empty
|
||||||
8, // 27: uatu.driver.v1.Driver.Health:output_type -> uatu.driver.v1.HealthStatus
|
8, // 27: sanderling.driver.v1.Driver.Health:output_type -> sanderling.driver.v1.HealthStatus
|
||||||
15, // 28: uatu.driver.v1.Driver.Metrics:output_type -> uatu.driver.v1.MetricsResponse
|
15, // 28: sanderling.driver.v1.Driver.Metrics:output_type -> sanderling.driver.v1.MetricsResponse
|
||||||
16, // [16:29] is the sub-list for method output_type
|
16, // [16:29] is the sub-list for method output_type
|
||||||
3, // [3:16] is the sub-list for method input_type
|
3, // [3:16] is the sub-list for method input_type
|
||||||
3, // [3:3] is the sub-list for extension type_name
|
3, // [3:3] is the sub-list for extension type_name
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
package uatu.driver.v1;
|
package sanderling.driver.v1;
|
||||||
|
|
||||||
option go_package = "github.com/priyanshujain/uatu/proto/driverpb;driverpb";
|
option go_package = "github.com/priyanshujain/sanderling/proto/driverpb;driverpb";
|
||||||
option java_package = "dev.uatu.driver.v1";
|
option java_package = "dev.sanderling.driver.v1";
|
||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
|
|
||||||
service Driver {
|
service Driver {
|
||||||
|
|||||||
@@ -19,19 +19,19 @@ import (
|
|||||||
const _ = grpc.SupportPackageIsVersion9
|
const _ = grpc.SupportPackageIsVersion9
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Driver_Launch_FullMethodName = "/uatu.driver.v1.Driver/Launch"
|
Driver_Launch_FullMethodName = "/sanderling.driver.v1.Driver/Launch"
|
||||||
Driver_Terminate_FullMethodName = "/uatu.driver.v1.Driver/Terminate"
|
Driver_Terminate_FullMethodName = "/sanderling.driver.v1.Driver/Terminate"
|
||||||
Driver_Tap_FullMethodName = "/uatu.driver.v1.Driver/Tap"
|
Driver_Tap_FullMethodName = "/sanderling.driver.v1.Driver/Tap"
|
||||||
Driver_TapSelector_FullMethodName = "/uatu.driver.v1.Driver/TapSelector"
|
Driver_TapSelector_FullMethodName = "/sanderling.driver.v1.Driver/TapSelector"
|
||||||
Driver_InputText_FullMethodName = "/uatu.driver.v1.Driver/InputText"
|
Driver_InputText_FullMethodName = "/sanderling.driver.v1.Driver/InputText"
|
||||||
Driver_Swipe_FullMethodName = "/uatu.driver.v1.Driver/Swipe"
|
Driver_Swipe_FullMethodName = "/sanderling.driver.v1.Driver/Swipe"
|
||||||
Driver_PressKey_FullMethodName = "/uatu.driver.v1.Driver/PressKey"
|
Driver_PressKey_FullMethodName = "/sanderling.driver.v1.Driver/PressKey"
|
||||||
Driver_Screenshot_FullMethodName = "/uatu.driver.v1.Driver/Screenshot"
|
Driver_Screenshot_FullMethodName = "/sanderling.driver.v1.Driver/Screenshot"
|
||||||
Driver_Hierarchy_FullMethodName = "/uatu.driver.v1.Driver/Hierarchy"
|
Driver_Hierarchy_FullMethodName = "/sanderling.driver.v1.Driver/Hierarchy"
|
||||||
Driver_RecentLogs_FullMethodName = "/uatu.driver.v1.Driver/RecentLogs"
|
Driver_RecentLogs_FullMethodName = "/sanderling.driver.v1.Driver/RecentLogs"
|
||||||
Driver_WaitForIdle_FullMethodName = "/uatu.driver.v1.Driver/WaitForIdle"
|
Driver_WaitForIdle_FullMethodName = "/sanderling.driver.v1.Driver/WaitForIdle"
|
||||||
Driver_Health_FullMethodName = "/uatu.driver.v1.Driver/Health"
|
Driver_Health_FullMethodName = "/sanderling.driver.v1.Driver/Health"
|
||||||
Driver_Metrics_FullMethodName = "/uatu.driver.v1.Driver/Metrics"
|
Driver_Metrics_FullMethodName = "/sanderling.driver.v1.Driver/Metrics"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DriverClient is the client API for Driver service.
|
// DriverClient is the client API for Driver service.
|
||||||
@@ -516,7 +516,7 @@ func _Driver_Metrics_Handler(srv interface{}, ctx context.Context, dec func(inte
|
|||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
var Driver_ServiceDesc = grpc.ServiceDesc{
|
var Driver_ServiceDesc = grpc.ServiceDesc{
|
||||||
ServiceName: "uatu.driver.v1.Driver",
|
ServiceName: "sanderling.driver.v1.Driver",
|
||||||
HandlerType: (*DriverServer)(nil),
|
HandlerType: (*DriverServer)(nil),
|
||||||
Methods: []grpc.MethodDesc{
|
Methods: []grpc.MethodDesc{
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
func TestDriverServiceDescriptor(t *testing.T) {
|
func TestDriverServiceDescriptor(t *testing.T) {
|
||||||
var sd grpc.ServiceDesc = Driver_ServiceDesc
|
var sd grpc.ServiceDesc = Driver_ServiceDesc
|
||||||
if sd.ServiceName != "uatu.driver.v1.Driver" {
|
if sd.ServiceName != "sanderling.driver.v1.Driver" {
|
||||||
t.Fatalf("unexpected service name: %q", sd.ServiceName)
|
t.Fatalf("unexpected service name: %q", sd.ServiceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ plugins {
|
|||||||
id("org.jetbrains.dokka-javadoc") version "2.2.0"
|
id("org.jetbrains.dokka-javadoc") version "2.2.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
version = findProperty("uatu.version") as String? ?: "0.0.0-dev"
|
version = findProperty("sanderling.version") as String? ?: "0.0.0-dev"
|
||||||
group = "io.github.priyanshujain"
|
group = "io.github.priyanshujain"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "dev.uatu.sdk"
|
namespace = "dev.sanderling.sdk"
|
||||||
compileSdk = 35
|
compileSdk = 35
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
@@ -61,12 +61,12 @@ mavenPublishing {
|
|||||||
)
|
)
|
||||||
|
|
||||||
pom {
|
pom {
|
||||||
name.set("uatu sdk-android")
|
name.set("sanderling sdk-android")
|
||||||
description.set(
|
description.set(
|
||||||
"Android runtime SDK for uatu, a property-based UI fuzzer for mobile apps. " +
|
"Android runtime SDK for sanderling, a property-based UI fuzzer for mobile apps. " +
|
||||||
"Exposes a content-provider accessibility bridge consumed by the uatu CLI at test time.",
|
"Exposes a content-provider accessibility bridge consumed by the sanderling CLI at test time.",
|
||||||
)
|
)
|
||||||
url.set("https://github.com/priyanshujain/uatu")
|
url.set("https://github.com/priyanshujain/sanderling")
|
||||||
|
|
||||||
licenses {
|
licenses {
|
||||||
license {
|
license {
|
||||||
@@ -85,9 +85,9 @@ mavenPublishing {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scm {
|
scm {
|
||||||
url.set("https://github.com/priyanshujain/uatu")
|
url.set("https://github.com/priyanshujain/sanderling")
|
||||||
connection.set("scm:git:git://github.com/priyanshujain/uatu.git")
|
connection.set("scm:git:git://github.com/priyanshujain/sanderling.git")
|
||||||
developerConnection.set("scm:git:ssh://[email protected]/priyanshujain/uatu.git")
|
developerConnection.set("scm:git:ssh://[email protected]/priyanshujain/sanderling.git")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
# ProGuard rules shipped to consumers of dev.uatu:sdk-android.
|
# ProGuard rules shipped to consumers of dev.sanderling:sdk-android.
|
||||||
# None needed for v0.1. Socket and semaphore APIs are all reflection-free.
|
# None needed for v0.1. Socket and semaphore APIs are all reflection-free.
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package dev.uatu.sdk
|
package dev.sanderling.sdk
|
||||||
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package dev.uatu.sdk
|
package dev.sanderling.sdk
|
||||||
|
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.io.StringWriter
|
import java.io.StringWriter
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package dev.uatu.sdk
|
package dev.sanderling.sdk
|
||||||
|
|
||||||
import android.net.LocalSocket
|
import android.net.LocalSocket
|
||||||
import android.net.LocalSocketAddress
|
import android.net.LocalSocketAddress
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package dev.uatu.sdk
|
package dev.sanderling.sdk
|
||||||
|
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
import java.util.concurrent.Semaphore
|
import java.util.concurrent.Semaphore
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package dev.uatu.sdk
|
package dev.sanderling.sdk
|
||||||
|
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
+7
-7
@@ -1,24 +1,24 @@
|
|||||||
package dev.uatu.sdk
|
package dev.sanderling.sdk
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
|
||||||
data class Configuration(
|
data class Configuration(
|
||||||
val socketName: String = "uatu-agent",
|
val socketName: String = "sanderling-agent",
|
||||||
val pauseTimeoutMillis: Long = 5_000L,
|
val pauseTimeoutMillis: Long = 5_000L,
|
||||||
)
|
)
|
||||||
|
|
||||||
object Uatu {
|
object Sanderling {
|
||||||
const val VERSION: String = "0.0.1"
|
const val VERSION: String = "0.0.1"
|
||||||
private const val LOG_TAG = "Uatu"
|
private const val LOG_TAG = "Sanderling"
|
||||||
|
|
||||||
@Volatile private var runtime: UatuRuntime? = null
|
@Volatile private var runtime: SanderlingRuntime? = null
|
||||||
|
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun start(application: Application, configuration: Configuration = Configuration()) {
|
fun start(application: Application, configuration: Configuration = Configuration()) {
|
||||||
if (runtime != null) return
|
if (runtime != null) return
|
||||||
val newRuntime = UatuRuntime(
|
val newRuntime = SanderlingRuntime(
|
||||||
transport = LocalAbstractTransport(configuration.socketName),
|
transport = LocalAbstractTransport(configuration.socketName),
|
||||||
pauser = Pauser(ChoreographerPoster(), configuration.pauseTimeoutMillis),
|
pauser = Pauser(ChoreographerPoster(), configuration.pauseTimeoutMillis),
|
||||||
version = VERSION,
|
version = VERSION,
|
||||||
@@ -32,7 +32,7 @@ object Uatu {
|
|||||||
|
|
||||||
fun extract(name: String, function: () -> Any?) {
|
fun extract(name: String, function: () -> Any?) {
|
||||||
val activeRuntime = runtime
|
val activeRuntime = runtime
|
||||||
?: throw IllegalStateException("Uatu.start must be called before registering extractors")
|
?: throw IllegalStateException("Sanderling.start must be called before registering extractors")
|
||||||
activeRuntime.register(name, function)
|
activeRuntime.register(name, function)
|
||||||
}
|
}
|
||||||
|
|
||||||
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
package dev.uatu.sdk
|
package dev.sanderling.sdk
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
|
||||||
internal class UatuRuntime(
|
internal class SanderlingRuntime(
|
||||||
transport: AgentTransport,
|
transport: AgentTransport,
|
||||||
private val pauser: Pauser,
|
private val pauser: Pauser,
|
||||||
private val version: String,
|
private val version: String,
|
||||||
@@ -45,7 +45,7 @@ internal class UatuRuntime(
|
|||||||
|
|
||||||
private inner class AgentHandler : SocketClient.Handler {
|
private inner class AgentHandler : SocketClient.Handler {
|
||||||
override fun onConnected(sender: SocketClient.MessageSender) {
|
override fun onConnected(sender: SocketClient.MessageSender) {
|
||||||
this@UatuRuntime.sender = sender
|
this@SanderlingRuntime.sender = sender
|
||||||
try {
|
try {
|
||||||
sender.send(Message.hello(version, platform, appPackage))
|
sender.send(Message.hello(version, platform, appPackage))
|
||||||
} catch (cause: Exception) {
|
} catch (cause: Exception) {
|
||||||
@@ -92,6 +92,6 @@ internal class UatuRuntime(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val LOG_TAG = "Uatu"
|
private const val LOG_TAG = "Sanderling"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
package dev.uatu.sdk
|
package dev.sanderling.sdk
|
||||||
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@@ -30,7 +30,7 @@ class SocketClient(
|
|||||||
private val transport: AgentTransport,
|
private val transport: AgentTransport,
|
||||||
private val handler: Handler,
|
private val handler: Handler,
|
||||||
private val backoff: Backoff = Backoff(),
|
private val backoff: Backoff = Backoff(),
|
||||||
private val threadFactory: (Runnable) -> Thread = { runnable -> Thread(runnable, "uatu-agent-reader") },
|
private val threadFactory: (Runnable) -> Thread = { runnable -> Thread(runnable, "sanderling-agent-reader") },
|
||||||
private val sleeper: (Long) -> Unit = { millis -> if (millis > 0L) Thread.sleep(millis) },
|
private val sleeper: (Long) -> Unit = { millis -> if (millis > 0L) Thread.sleep(millis) },
|
||||||
) {
|
) {
|
||||||
interface Handler {
|
interface Handler {
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package dev.uatu.sdk
|
package dev.sanderling.sdk
|
||||||
|
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package dev.uatu.sdk
|
package dev.sanderling.sdk
|
||||||
|
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package dev.uatu.sdk
|
package dev.sanderling.sdk
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
+7
-7
@@ -1,4 +1,4 @@
|
|||||||
package dev.uatu.sdk
|
package dev.sanderling.sdk
|
||||||
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
@@ -12,22 +12,22 @@ import org.junit.Assert.assertNotNull
|
|||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class UatuRuntimeTest {
|
class SanderlingRuntimeTest {
|
||||||
|
|
||||||
private lateinit var transport: SocketClientTest.FakeTransport
|
private lateinit var transport: SocketClientTest.FakeTransport
|
||||||
private lateinit var frameThread: PauserTest.FakeFrameThread
|
private lateinit var frameThread: PauserTest.FakeFrameThread
|
||||||
private lateinit var runtime: UatuRuntime
|
private lateinit var runtime: SanderlingRuntime
|
||||||
|
|
||||||
private fun newRuntime(): UatuRuntime {
|
private fun newRuntime(): SanderlingRuntime {
|
||||||
transport = SocketClientTest.FakeTransport()
|
transport = SocketClientTest.FakeTransport()
|
||||||
frameThread = PauserTest.FakeFrameThread()
|
frameThread = PauserTest.FakeFrameThread()
|
||||||
val pauser = Pauser(frameThread, pauseTimeoutMillis = 2_000L)
|
val pauser = Pauser(frameThread, pauseTimeoutMillis = 2_000L)
|
||||||
return UatuRuntime(
|
return SanderlingRuntime(
|
||||||
transport = transport,
|
transport = transport,
|
||||||
pauser = pauser,
|
pauser = pauser,
|
||||||
version = "0.0.1",
|
version = "0.0.1",
|
||||||
platform = "android",
|
platform = "android",
|
||||||
appPackage = "com.example.uatu_test",
|
appPackage = "com.example.sanderling_test",
|
||||||
).also { runtime = it }
|
).also { runtime = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ class UatuRuntimeTest {
|
|||||||
assertEquals(MessageType.HELLO, hello.type)
|
assertEquals(MessageType.HELLO, hello.type)
|
||||||
assertEquals("0.0.1", hello.version)
|
assertEquals("0.0.1", hello.version)
|
||||||
assertEquals("android", hello.platform)
|
assertEquals("android", hello.platform)
|
||||||
assertEquals("com.example.uatu_test", hello.appPackage)
|
assertEquals("com.example.sanderling_test", hello.appPackage)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun pauseTriggersExtractorsAndReturnsState() {
|
@Test fun pauseTriggersExtractorsAndReturnsState() {
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package dev.uatu.sdk
|
package dev.sanderling.sdk
|
||||||
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.PipedInputStream
|
import java.io.PipedInputStream
|
||||||
+1
-1
@@ -14,7 +14,7 @@ dependencyResolutionManagement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = "uatu"
|
rootProject.name = "sanderling"
|
||||||
|
|
||||||
include(":sidecar")
|
include(":sidecar")
|
||||||
include(":sdk-android")
|
include(":sdk-android")
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ plugins {
|
|||||||
id("com.google.protobuf") version "0.9.5"
|
id("com.google.protobuf") version "0.9.5"
|
||||||
}
|
}
|
||||||
|
|
||||||
version = findProperty("uatu.version") as String? ?: "0.0.0-dev"
|
version = findProperty("sanderling.version") as String? ?: "0.0.0-dev"
|
||||||
|
|
||||||
java {
|
java {
|
||||||
toolchain {
|
toolchain {
|
||||||
@@ -70,7 +70,7 @@ sourceSets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
mainClass.set("dev.uatu.sidecar.MainKt")
|
mainClass.set("dev.sanderling.sidecar.MainKt")
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Go binary embeds this fat JAR by fixed path (internal/sidecar/assets/
|
// The Go binary embeds this fat JAR by fixed path (internal/sidecar/assets/
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package dev.uatu.sidecar
|
package dev.sanderling.sidecar
|
||||||
|
|
||||||
interface DriverBackend {
|
interface DriverBackend {
|
||||||
fun launch(bundleId: String, launcherActivity: String, clearState: Boolean)
|
fun launch(bundleId: String, launcherActivity: String, clearState: Boolean)
|
||||||
+18
-18
@@ -1,23 +1,23 @@
|
|||||||
package dev.uatu.sidecar
|
package dev.sanderling.sidecar
|
||||||
|
|
||||||
import com.google.protobuf.ByteString
|
import com.google.protobuf.ByteString
|
||||||
import dev.uatu.driver.v1.DriverGrpc
|
import dev.sanderling.driver.v1.DriverGrpc
|
||||||
import dev.uatu.driver.v1.Duration
|
import dev.sanderling.driver.v1.Duration
|
||||||
import dev.uatu.driver.v1.Empty
|
import dev.sanderling.driver.v1.Empty
|
||||||
import dev.uatu.driver.v1.HealthStatus
|
import dev.sanderling.driver.v1.HealthStatus
|
||||||
import dev.uatu.driver.v1.HierarchyJSON
|
import dev.sanderling.driver.v1.HierarchyJSON
|
||||||
import dev.uatu.driver.v1.Image
|
import dev.sanderling.driver.v1.Image
|
||||||
import dev.uatu.driver.v1.LaunchRequest
|
import dev.sanderling.driver.v1.LaunchRequest
|
||||||
import dev.uatu.driver.v1.LogEntries
|
import dev.sanderling.driver.v1.LogEntries
|
||||||
import dev.uatu.driver.v1.LogEntry
|
import dev.sanderling.driver.v1.LogEntry
|
||||||
import dev.uatu.driver.v1.MetricsRequest
|
import dev.sanderling.driver.v1.MetricsRequest
|
||||||
import dev.uatu.driver.v1.MetricsResponse
|
import dev.sanderling.driver.v1.MetricsResponse
|
||||||
import dev.uatu.driver.v1.Point
|
import dev.sanderling.driver.v1.Point
|
||||||
import dev.uatu.driver.v1.PressKeyRequest
|
import dev.sanderling.driver.v1.PressKeyRequest
|
||||||
import dev.uatu.driver.v1.RecentLogsRequest
|
import dev.sanderling.driver.v1.RecentLogsRequest
|
||||||
import dev.uatu.driver.v1.Selector
|
import dev.sanderling.driver.v1.Selector
|
||||||
import dev.uatu.driver.v1.SwipeRequest
|
import dev.sanderling.driver.v1.SwipeRequest
|
||||||
import dev.uatu.driver.v1.Text
|
import dev.sanderling.driver.v1.Text
|
||||||
import io.grpc.stub.StreamObserver
|
import io.grpc.stub.StreamObserver
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
Loaded 100 of 111 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user