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
+511
-511
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
|
||||
# fire a real release from your laptop. Day-to-day work and the `release-cli`
|
||||
|
||||
@@ -55,10 +55,10 @@ jobs:
|
||||
gradle-${{ runner.os }}-
|
||||
|
||||
- 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:
|
||||
name: Publish @uatu/spec to npm
|
||||
name: Publish @sanderling/spec to npm
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
|
||||
- name: Publish
|
||||
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.
|
||||
run: |
|
||||
if [[ "${{ steps.ver.outputs.version }}" == *-* ]]; then
|
||||
@@ -101,7 +101,7 @@ jobs:
|
||||
fi
|
||||
|
||||
release-cli:
|
||||
name: Publish uatu CLI to GitHub Releases
|
||||
name: Publish sanderling CLI to GitHub Releases
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@ local.properties
|
||||
*.test
|
||||
*.out
|
||||
|
||||
# Uatu runs
|
||||
# Sanderling runs
|
||||
/runs/
|
||||
|
||||
# Node
|
||||
@@ -27,7 +27,7 @@ node_modules/
|
||||
.env
|
||||
.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.
|
||||
internal/sidecar/assets/sidecar-all.jar
|
||||
|
||||
|
||||
+8
-8
@@ -1,6 +1,6 @@
|
||||
version: 2
|
||||
|
||||
project_name: uatu
|
||||
project_name: sanderling
|
||||
|
||||
before:
|
||||
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'
|
||||
|
||||
builds:
|
||||
- id: uatu
|
||||
main: ./cmd/uatu
|
||||
binary: uatu
|
||||
- id: sanderling
|
||||
main: ./cmd/sanderling
|
||||
binary: sanderling
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
flags:
|
||||
@@ -28,11 +28,11 @@ builds:
|
||||
- arm64
|
||||
|
||||
archives:
|
||||
- id: uatu
|
||||
- id: sanderling
|
||||
ids:
|
||||
- uatu
|
||||
- sanderling
|
||||
formats: [tar.gz]
|
||||
name_template: "uatu_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
||||
name_template: "sanderling_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
||||
files:
|
||||
- README.md
|
||||
- LICENSE*
|
||||
@@ -59,6 +59,6 @@ changelog:
|
||||
release:
|
||||
github:
|
||||
owner: priyanshujain
|
||||
name: uatu
|
||||
name: sanderling
|
||||
draft: false
|
||||
prerelease: auto
|
||||
@@ -9,7 +9,7 @@ GO_PACKAGES := ./...
|
||||
SIDECAR_JAR := sidecar/build/libs/sidecar-all.jar
|
||||
SIDECAR_EMBED := internal/sidecar/assets/sidecar-all.jar
|
||||
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_OUT := $(patsubst docs/%.md,build/site/%.html,$(DOCS_SRC))
|
||||
@@ -18,7 +18,7 @@ DOCS_TEMPLATE := docs/_template/page.html
|
||||
INSPECT_DIST := internal/inspect/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:
|
||||
$(GO) mod download
|
||||
@@ -37,17 +37,17 @@ sdk-android:
|
||||
sdk-android-publish:
|
||||
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
|
||||
$(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.
|
||||
install: $(SIDECAR_EMBED) web-build
|
||||
$(GO) install -tags withsidecar ./cmd/uatu
|
||||
@dest="$$($(GO) env GOBIN)"; [ -n "$$dest" ] || dest="$$($(GO) env GOPATH)/bin"; echo "installed uatu to $$dest"
|
||||
$(GO) install -tags withsidecar ./cmd/sanderling
|
||||
@dest="$$($(GO) env GOBIN)"; [ -n "$$dest" ] || dest="$$($(GO) env GOPATH)/bin"; echo "installed sanderling to $$dest"
|
||||
|
||||
web-build:
|
||||
cd web && bun install --frozen-lockfile && bun run build
|
||||
@@ -59,7 +59,7 @@ web-dev:
|
||||
cd web && bun run dev
|
||||
|
||||
inspect-dev: $(SIDECAR_EMBED)
|
||||
$(GO) run -tags withsidecar ./cmd/uatu inspect --dev
|
||||
$(GO) run -tags withsidecar ./cmd/sanderling inspect --dev
|
||||
|
||||
web-typecheck:
|
||||
cd web && bun install --frozen-lockfile && bun run typecheck
|
||||
@@ -109,7 +109,7 @@ release-cli: $(SIDECAR_JAR)
|
||||
|
||||
release-android-local:
|
||||
@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:
|
||||
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.
|
||||
|
||||
> 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
|
||||
|
||||
- [Getting started](https://priyanshujain.github.io/uatu/manual/getting-started.html)
|
||||
- [Writing specs](https://priyanshujain.github.io/uatu/manual/writing-specs.html)
|
||||
- [`uatu inspect` UI](https://priyanshujain.github.io/uatu/manual/inspect.html)
|
||||
- [Example](https://github.com/priyanshujain/uatu/tree/master/examples/folio)
|
||||
- [Architecture](https://priyanshujain.github.io/uatu/development/architecture.html)
|
||||
- [Getting started](https://priyanshujain.github.io/sanderling/manual/getting-started.html)
|
||||
- [Writing specs](https://priyanshujain.github.io/sanderling/manual/writing-specs.html)
|
||||
- [`sanderling inspect` UI](https://priyanshujain.github.io/sanderling/manual/inspect.html)
|
||||
- [Example](https://github.com/priyanshujain/sanderling/tree/master/examples/folio)
|
||||
- [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"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/priyanshujain/uatu/internal/bundler"
|
||||
"github.com/priyanshujain/sanderling/internal/bundler"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -31,8 +31,8 @@ func main() {
|
||||
result, err := bundler.Bundle(bundler.Options{
|
||||
EntryFile: entryFile,
|
||||
Aliases: map[string]string{
|
||||
"@uatu/spec": specApiPath,
|
||||
"@uatu/spec/defaults/properties": defaultPropertiesPath,
|
||||
"@sanderling/spec": specApiPath,
|
||||
"@sanderling/spec/defaults/properties": defaultPropertiesPath,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/priyanshujain/uatu/internal/hierarchy"
|
||||
"github.com/priyanshujain/sanderling/internal/hierarchy"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
File renamed without changes.
@@ -31,18 +31,18 @@ func TestParseAdbDevices_Empty(t *testing.T) {
|
||||
func TestParseAVDList_DropsInfoLines(t *testing.T) {
|
||||
output := `INFO | Storing crashdata in: /tmp/x
|
||||
Medium_Phone_API_36.0
|
||||
uatu_test
|
||||
sanderling_test
|
||||
`
|
||||
|
||||
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) {
|
||||
t.Fatalf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -59,12 +59,12 @@ func TestPickAVD_ExplicitMissing(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPickAVD_SingleAvailable(t *testing.T) {
|
||||
got, err := pickAVD("", []string{"uatu_test"})
|
||||
got, err := pickAVD("", []string{"sanderling_test"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != "uatu_test" {
|
||||
t.Fatalf("got %q, want uatu_test", got)
|
||||
if got != "sanderling_test" {
|
||||
t.Fatalf("got %q, want sanderling_test", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/priyanshujain/uatu/internal/sidecar"
|
||||
"github.com/priyanshujain/sanderling/internal/sidecar"
|
||||
)
|
||||
|
||||
type doctorCheck struct {
|
||||
@@ -30,7 +30,7 @@ func defaultDoctorChecks() []doctorCheck {
|
||||
|
||||
func checkSidecarJAR(_ context.Context) error {
|
||||
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 {
|
||||
return fmt.Errorf("embedded JAR is empty")
|
||||
File renamed without changes.
@@ -14,7 +14,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/priyanshujain/uatu/internal/inspect"
|
||||
"github.com/priyanshujain/sanderling/internal/inspect"
|
||||
)
|
||||
|
||||
type inspectOptions struct {
|
||||
@@ -67,7 +67,7 @@ func runInspect(options inspectOptions, stdout io.Writer) error {
|
||||
}
|
||||
address := listener.Addr().(*net.TCPAddr)
|
||||
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())
|
||||
defer cancel()
|
||||
@@ -36,7 +36,7 @@ func TestParseInspectArgs_RejectsTooManyPositional(t *testing.T) {
|
||||
|
||||
func TestRun_HelpListsInspectCommand(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
if !strings.Contains(stdout.String(), "inspect") {
|
||||
@@ -25,18 +25,18 @@ type testOptions struct {
|
||||
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:
|
||||
uatu <command> [flags]
|
||||
sanderling <command> [flags]
|
||||
|
||||
Commands:
|
||||
test Run a spec against an app for a fixed duration.
|
||||
inspect Serve a local web UI for browsing runs/.
|
||||
doctor Check that the host environment is ready to run uatu.
|
||||
version Print the uatu version.
|
||||
doctor Check that the host environment is ready to run sanderling.
|
||||
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) {
|
||||
@@ -100,7 +100,7 @@ func run(args []string, stdout, stderr io.Writer) error {
|
||||
fmt.Fprintln(stdout, Version)
|
||||
return nil
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
if !strings.Contains(stdout.String(), "uatu <command>") {
|
||||
if !strings.Contains(stdout.String(), "sanderling <command>") {
|
||||
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"} {
|
||||
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)
|
||||
}
|
||||
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) {
|
||||
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") {
|
||||
t.Fatalf("expected unknown-command error, got %v", err)
|
||||
}
|
||||
@@ -125,7 +125,7 @@ func TestRun_Doctor(t *testing.T) {
|
||||
var stdout bytes.Buffer
|
||||
// Doctor may pass or fail depending on host environment; we just want to
|
||||
// confirm it runs and emits per-check lines.
|
||||
_ = run([]string{"uatu", "doctor"}, &stdout, io.Discard)
|
||||
_ = run([]string{"sanderling", "doctor"}, &stdout, io.Discard)
|
||||
output := stdout.String()
|
||||
if !strings.Contains(output, "OK") && !strings.Contains(output, "FAIL") {
|
||||
t.Errorf("doctor output missing OK/FAIL lines: %q", output)
|
||||
@@ -137,7 +137,7 @@ func TestRun_TestSubcommand_PipelineErrors(t *testing.T) {
|
||||
// must surface a specific error rather than panicking — proves the flag
|
||||
// wiring reaches the runner.
|
||||
err := run([]string{
|
||||
"uatu", "test",
|
||||
"sanderling", "test",
|
||||
"--spec", "definitely-missing-spec.ts",
|
||||
"--bundle-id", "com.example",
|
||||
"--avd", "definitely-missing-avd",
|
||||
File renamed without changes.
@@ -12,17 +12,17 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/priyanshujain/uatu/internal/agent"
|
||||
"github.com/priyanshujain/uatu/internal/bundler"
|
||||
"github.com/priyanshujain/uatu/internal/driver/maestro"
|
||||
"github.com/priyanshujain/uatu/internal/runner"
|
||||
"github.com/priyanshujain/uatu/internal/sidecar"
|
||||
"github.com/priyanshujain/uatu/internal/trace"
|
||||
"github.com/priyanshujain/uatu/internal/verifier"
|
||||
"github.com/priyanshujain/sanderling/internal/agent"
|
||||
"github.com/priyanshujain/sanderling/internal/bundler"
|
||||
"github.com/priyanshujain/sanderling/internal/driver/maestro"
|
||||
"github.com/priyanshujain/sanderling/internal/runner"
|
||||
"github.com/priyanshujain/sanderling/internal/sidecar"
|
||||
"github.com/priyanshujain/sanderling/internal/trace"
|
||||
"github.com/priyanshujain/sanderling/internal/verifier"
|
||||
)
|
||||
|
||||
const (
|
||||
socketName = "uatu-agent"
|
||||
socketName = "sanderling-agent"
|
||||
sidecarStartupTimeout = 30 * time.Second
|
||||
sdkAcceptTimeout = 60 * time.Second
|
||||
)
|
||||
@@ -35,17 +35,17 @@ func runTestPipeline(ctx context.Context, options testOptions, stdout io.Writer)
|
||||
}
|
||||
aliases := map[string]string{}
|
||||
if specApiPath := resolveSpecAPIPath(options.spec); specApiPath != "" {
|
||||
aliases["@uatu/spec"] = specApiPath
|
||||
aliases["@sanderling/spec"] = specApiPath
|
||||
// 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)
|
||||
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{
|
||||
EntryFile: options.spec,
|
||||
Defines: map[string]string{
|
||||
"UATU_TEST_PHONE": os.Getenv("UATU_TEST_PHONE"),
|
||||
"UATU_TEST_OTP": os.Getenv("UATU_TEST_OTP"),
|
||||
"SANDERLING_TEST_PHONE": os.Getenv("SANDERLING_TEST_PHONE"),
|
||||
"SANDERLING_TEST_OTP": os.Getenv("SANDERLING_TEST_OTP"),
|
||||
},
|
||||
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])
|
||||
|
||||
sidecarDirectory := filepath.Join(os.TempDir(), "uatu-sidecar")
|
||||
sidecarDirectory := filepath.Join(os.TempDir(), "sanderling-sidecar")
|
||||
jarPath, err := sidecar.Extract(sidecarDirectory)
|
||||
if err != nil {
|
||||
return fmt.Errorf("extract sidecar: %w", err)
|
||||
@@ -166,7 +166,7 @@ func runTestPipeline(ctx context.Context, options testOptions, stdout io.Writer)
|
||||
Platform: options.platform,
|
||||
BundleID: options.bundleID,
|
||||
StartedAt: time.Now().UTC(),
|
||||
UatuVersion: "0.0.1",
|
||||
SanderlingVersion: "0.0.1",
|
||||
}
|
||||
if err := traceWriter.WriteMeta(meta); err != nil {
|
||||
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
|
||||
// a uatu source checkout, searched upward from the spec file and the cwd.
|
||||
// Returns "" when not found, in which case esbuild resolves @uatu/spec via
|
||||
// a sanderling source checkout, searched upward from the spec file and the cwd.
|
||||
// Returns "" when not found, in which case esbuild resolves @sanderling/spec via
|
||||
// node_modules the way a downstream user's project would.
|
||||
func resolveSpecAPIPath(specPath string) string {
|
||||
candidates := []string{}
|
||||
@@ -47,6 +47,6 @@ func TestResolveSpecAPIPath_ReturnsEmptyWhenMissing(t *testing.T) {
|
||||
|
||||
got := resolveSpecAPIPath(specPath)
|
||||
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>
|
||||
<meta charset="utf-8">
|
||||
<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">
|
||||
<style>$highlighting-css$</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="layout">
|
||||
<aside class="sidebar">
|
||||
<a class="brand" href="__ROOT__index.html">uatu</a>
|
||||
<a class="brand" href="__ROOT__index.html">sanderling</a>
|
||||
<nav>
|
||||
<h3>Manual</h3>
|
||||
<ul>
|
||||
@@ -31,8 +31,8 @@
|
||||
$body$
|
||||
</article>
|
||||
<footer>
|
||||
<a href="https://github.com/priyanshujain/uatu">github.com/priyanshujain/uatu</a>
|
||||
· <a href="https://github.com/priyanshujain/uatu/issues/4">v0.1.0 roadmap</a>
|
||||
<a href="https://github.com/priyanshujain/sanderling">github.com/priyanshujain/sanderling</a>
|
||||
· <a href="https://github.com/priyanshujain/sanderling/issues/4">v0.1.0 roadmap</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ Three processes, two transports.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Go["uatu (Go)"]
|
||||
subgraph Go["sanderling (Go)"]
|
||||
Bundler[Bundler<br/>esbuild] --> Verifier[Verifier<br/>goja + LTL]
|
||||
Verifier <--> Runner[Runner]
|
||||
Runner --> Trace[Trace writer<br/>JSONL + PNG]
|
||||
@@ -21,7 +21,7 @@ flowchart TB
|
||||
|
||||
subgraph Device["Emulator"]
|
||||
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
|
||||
|
||||
@@ -33,7 +33,7 @@ flowchart TB
|
||||
|
||||
## 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.
|
||||
|
||||
|
||||
@@ -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`.
|
||||
|
||||
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,7 +6,7 @@ title: Development
|
||||
|
||||
- [Design principles](./design-principles.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
|
||||
|
||||
|
||||
+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.
|
||||
|
||||
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
|
||||
|
||||
|
||||
+7
-7
@@ -5,10 +5,10 @@ title: CLI reference
|
||||
# CLI reference
|
||||
|
||||
```
|
||||
uatu <command> [flags]
|
||||
sanderling <command> [flags]
|
||||
```
|
||||
|
||||
## `uatu test`
|
||||
## `sanderling test`
|
||||
|
||||
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`. |
|
||||
| `--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`.
|
||||
|
||||
@@ -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.
|
||||
|
||||
## `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.
|
||||
|
||||
@@ -49,4 +49,4 @@ Print the CLI version.
|
||||
- `--max-steps` hard cap on step count.
|
||||
- `--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
|
||||
|
||||
- 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.
|
||||
|
||||
Run `uatu doctor` to check the host environment.
|
||||
Run `sanderling doctor` to check the host environment.
|
||||
|
||||
## Install
|
||||
|
||||
@@ -21,15 +21,15 @@ Run `uatu doctor` to check the host environment.
|
||||
macOS arm64:
|
||||
|
||||
```sh
|
||||
curl -L https://github.com/priyanshujain/uatu/releases/latest/download/uatu_<version>_darwin_arm64.tar.gz | tar xz
|
||||
./uatu version
|
||||
curl -L https://github.com/priyanshujain/sanderling/releases/latest/download/sanderling_<version>_darwin_arm64.tar.gz | tar xz
|
||||
./sanderling version
|
||||
```
|
||||
|
||||
Linux amd64:
|
||||
|
||||
```sh
|
||||
curl -L https://github.com/priyanshujain/uatu/releases/latest/download/uatu_<version>_linux_amd64.tar.gz | tar xz
|
||||
./uatu version
|
||||
curl -L https://github.com/priyanshujain/sanderling/releases/latest/download/sanderling_<version>_linux_amd64.tar.gz | tar xz
|
||||
./sanderling version
|
||||
```
|
||||
|
||||
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)
|
||||
|
||||
```sh
|
||||
npm install --save-dev @uatu/spec
|
||||
npm install --save-dev @sanderling/spec
|
||||
```
|
||||
|
||||
### Android SDK (Maven Central)
|
||||
@@ -55,14 +55,14 @@ The repo ships a working sample at `examples/folio`. From that directory:
|
||||
```sh
|
||||
npm install
|
||||
(cd android && ./gradlew installDebug)
|
||||
uatu test \
|
||||
sanderling test \
|
||||
--spec spec.ts \
|
||||
--bundle-id app.folio \
|
||||
--platform android \
|
||||
--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>/`:
|
||||
|
||||
|
||||
@@ -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`.
|
||||
@@ -51,11 +51,11 @@ Two-process loop:
|
||||
|
||||
```
|
||||
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:
|
||||
|
||||
```
|
||||
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?
|
||||
|
||||
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.
|
||||
|
||||
## 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)
|
||||
├── 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
|
||||
|
||||
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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
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.
|
||||
|
||||
```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.
|
||||
const loggedIn = extract((s) => !!s.ax.find("id:home-tab-bar"));
|
||||
@@ -18,7 +18,7 @@ export const properties = {
|
||||
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(
|
||||
[10, taps],
|
||||
[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.
|
||||
|
||||
`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)
|
||||
|
||||
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.
|
||||
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
|
||||
// in the app (Android)
|
||||
Uatu.extract("cart_count") { store.cart.size }
|
||||
Sanderling.extract("cart_count") { store.cart.size }
|
||||
```
|
||||
|
||||
```ts
|
||||
|
||||
@@ -6,7 +6,7 @@ iosApp/build/
|
||||
iosApp/iosApp.xcodeproj/
|
||||
iosApp/iosApp.xcodeproj/**
|
||||
.DS_Store
|
||||
uatu/node_modules/
|
||||
uatu/runs/
|
||||
sanderling/node_modules/
|
||||
sanderling/runs/
|
||||
.playwright-mcp/
|
||||
web-*.png
|
||||
@@ -3,14 +3,14 @@
|
||||
A minimal Kotlin Multiplatform personal-ledger app: login with demo
|
||||
credentials, create accounts, add credits and debits. Shared UI across
|
||||
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
|
||||
|
||||
- Kotlin Multiplatform + Compose Multiplatform (shared UI)
|
||||
- kotlinx.serialization for file-backed persistence
|
||||
- kotlinx.coroutines for state flows
|
||||
- uatu `sdk-android` for harness integration on Android
|
||||
- sanderling `sdk-android` for harness integration on Android
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -46,13 +46,13 @@ email: [email protected]
|
||||
password: ledger123
|
||||
```
|
||||
|
||||
## Run a uatu test (Android)
|
||||
## Run a sanderling test (Android)
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```sh
|
||||
@@ -66,13 +66,13 @@ AVD=Pixel_7
|
||||
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`,
|
||||
`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
|
||||
- `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")
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
kotlin {
|
||||
@@ -83,7 +83,7 @@ kotlin {
|
||||
androidMain.dependencies {
|
||||
implementation("androidx.activity:activity-compose:1.13.0")
|
||||
implementation("app.cash.sqldelight:android-driver:$sqldelightVersion")
|
||||
implementation("io.github.priyanshujain:sdk-android:$uatuVersion")
|
||||
implementation("io.github.priyanshujain:sdk-android:$sanderlingVersion")
|
||||
}
|
||||
|
||||
iosMain.dependencies {
|
||||
@@ -101,7 +101,7 @@ android {
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
versionCode = 1
|
||||
versionName = uatuVersion
|
||||
versionName = sanderlingVersion
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
|
||||
@@ -11,18 +11,18 @@ import app.folio.navigation.Navigator
|
||||
import app.folio.navigation.Route
|
||||
import app.folio.platform.balanceOf
|
||||
import app.folio.platform.signedAmount
|
||||
import dev.uatu.sdk.Uatu
|
||||
import dev.sanderling.sdk.Sanderling
|
||||
|
||||
class FolioApplication : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
AndroidLedgerContext.context = applicationContext
|
||||
Repository.init()
|
||||
Uatu.start(this)
|
||||
Uatu.extract("logged_in") { Repository.session.value != null }
|
||||
Uatu.extract("account_count") { Repository.accounts.value.size }
|
||||
Uatu.extract("total_balance") { balanceOf(Repository.transactions.value) }
|
||||
Uatu.extract("route") {
|
||||
Sanderling.start(this)
|
||||
Sanderling.extract("logged_in") { Repository.session.value != null }
|
||||
Sanderling.extract("account_count") { Repository.accounts.value.size }
|
||||
Sanderling.extract("total_balance") { balanceOf(Repository.transactions.value) }
|
||||
Sanderling.extract("route") {
|
||||
when (Navigator.current.value) {
|
||||
Route.Login -> "login"
|
||||
Route.Home -> "home"
|
||||
@@ -31,10 +31,10 @@ class FolioApplication : Application() {
|
||||
is Route.AddTransaction -> "add-transaction"
|
||||
}
|
||||
}
|
||||
Uatu.extract("auth_status") {
|
||||
Sanderling.extract("auth_status") {
|
||||
if (Repository.session.value != null) "logged-in" else "logged-out"
|
||||
}
|
||||
Uatu.extract("accounts") {
|
||||
Sanderling.extract("accounts") {
|
||||
val txns = Repository.transactions.value
|
||||
Repository.accounts.value.map { a ->
|
||||
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) {
|
||||
is Route.Ledger -> r.accountId
|
||||
is Route.AddTransaction -> r.accountId
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
Uatu.extract("ledger_rows") {
|
||||
Sanderling.extract("ledger_rows") {
|
||||
val active = when (val r = Navigator.current.value) {
|
||||
is Route.Ledger -> 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) {
|
||||
is Route.Ledger -> r.accountId
|
||||
is Route.AddTransaction -> r.accountId
|
||||
@@ -81,13 +81,13 @@ class FolioApplication : Application() {
|
||||
if (active == null) 0L
|
||||
else balanceOf(Repository.transactions.value.filter { it.accountId == active })
|
||||
}
|
||||
Uatu.extract("focused_input") { FocusTracker.current.value }
|
||||
Uatu.extract("txn_form_type") { AddTransactionUiState.txnFormType.value }
|
||||
Uatu.extract("txn_form_account_id") {
|
||||
Sanderling.extract("focused_input") { FocusTracker.current.value }
|
||||
Sanderling.extract("txn_form_type") { AddTransactionUiState.txnFormType.value }
|
||||
Sanderling.extract("txn_form_account_id") {
|
||||
(Navigator.current.value as? Route.AddTransaction)?.accountId
|
||||
}
|
||||
Uatu.extract("login_error") { LoginUiState.loginError.value }
|
||||
Uatu.extract("add_account_error") { AddAccountUiState.addAccountError.value }
|
||||
Uatu.extract("txn_error") { AddTransactionUiState.txnError.value }
|
||||
Sanderling.extract("login_error") { LoginUiState.loginError.value }
|
||||
Sanderling.extract("add_account_error") { AddAccountUiState.addAccountError.value }
|
||||
Sanderling.extract("txn_error") { AddTransactionUiState.txnError.value }
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.browser.localStorage
|
||||
|
||||
private const val STORAGE_KEY = "uatu.ledger.v1"
|
||||
private const val STORAGE_KEY = "sanderling.ledger.v1"
|
||||
|
||||
class WebLedgerStore : LedgerStore {
|
||||
private val _accounts = MutableStateFlow<List<Account>>(emptyList())
|
||||
|
||||
@@ -5,6 +5,6 @@ kotlin.code.style=official
|
||||
android.useAndroidX=true
|
||||
android.nonTransitiveRClass=true
|
||||
|
||||
# Pin the uatu SDK version consumed by this app. Override with
|
||||
# `-Puatu.version=<version>`.
|
||||
uatu.version=0.0.0-dev
|
||||
# Pin the sanderling SDK version consumed by this app. Override with
|
||||
# `-Psanderling.version=<version>`.
|
||||
sanderling.version=0.0.0-dev
|
||||
@@ -1,11 +1,11 @@
|
||||
set shell := ["bash", "-cu"]
|
||||
set dotenv-load := true
|
||||
|
||||
uatu := env_var_or_default("UATU", "uatu")
|
||||
sanderling := env_var_or_default("SANDERLING", "sanderling")
|
||||
avd := env_var_or_default("AVD", "")
|
||||
duration := env_var_or_default("DURATION", "1m")
|
||||
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")
|
||||
|
||||
default:
|
||||
@@ -81,7 +81,7 @@ ios:
|
||||
xcrun simctl install booted iosApp/build/Build/Products/Debug-iphonesimulator/iosApp.app
|
||||
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.
|
||||
test:
|
||||
#!/usr/bin/env bash
|
||||
@@ -90,8 +90,8 @@ test:
|
||||
if [[ -n "{{avd}}" ]]; then
|
||||
avd_flag=(--avd "{{avd}}")
|
||||
fi
|
||||
"{{uatu}}" test \
|
||||
--spec "{{justfile_directory()}}/uatu/spec.ts" \
|
||||
"{{sanderling}}" test \
|
||||
--spec "{{justfile_directory()}}/sanderling/spec.ts" \
|
||||
--bundle-id app.folio \
|
||||
"${avd_flag[@]}" \
|
||||
--duration "{{duration}}" \
|
||||
|
||||
+3
-3
@@ -6,12 +6,12 @@
|
||||
"": {
|
||||
"name": "folio",
|
||||
"devDependencies": {
|
||||
"@uatu/spec": "next"
|
||||
"@sanderling/spec": "next"
|
||||
}
|
||||
},
|
||||
"node_modules/@uatu/spec": {
|
||||
"node_modules/@sanderling/spec": {
|
||||
"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==",
|
||||
"dev": true,
|
||||
"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,
|
||||
waitOnce,
|
||||
weighted,
|
||||
} from "@uatu/spec";
|
||||
import { noUncaughtExceptions } from "@uatu/spec/defaults/properties";
|
||||
} from "@sanderling/spec";
|
||||
import { noUncaughtExceptions } from "@sanderling/spec/defaults/properties";
|
||||
|
||||
interface AccountSnapshot {
|
||||
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
|
||||
|
||||
|
||||
+2
-2
@@ -10,6 +10,6 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
|
||||
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),
|
||||
# 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.
|
||||
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) {
|
||||
entry := writeFixture(t, "spec.ts", `
|
||||
const phone = process.env.UATU_TEST_PHONE;
|
||||
const otp = process.env.UATU_TEST_OTP;
|
||||
const phone = process.env.SANDERLING_TEST_PHONE;
|
||||
const otp = process.env.SANDERLING_TEST_OTP;
|
||||
export const credentials = { phone, otp };
|
||||
`)
|
||||
result, err := Bundle(Options{
|
||||
EntryFile: entry,
|
||||
Defines: map[string]string{
|
||||
"UATU_TEST_PHONE": "+919876543210",
|
||||
"UATU_TEST_OTP": "123456",
|
||||
"SANDERLING_TEST_PHONE": "+919876543210",
|
||||
"SANDERLING_TEST_OTP": "123456",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -87,13 +87,13 @@ func TestBundle_DeterministicHash(t *testing.T) {
|
||||
|
||||
func TestBundle_HashChangesWithDefines(t *testing.T) {
|
||||
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 {
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
"github.com/priyanshujain/uatu/internal/driver"
|
||||
driverpb "github.com/priyanshujain/uatu/proto/driverpb"
|
||||
"github.com/priyanshujain/sanderling/internal/driver"
|
||||
driverpb "github.com/priyanshujain/sanderling/proto/driverpb"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
driverpb "github.com/priyanshujain/uatu/proto/driverpb"
|
||||
driverpb "github.com/priyanshujain/sanderling/proto/driverpb"
|
||||
)
|
||||
|
||||
type fakeServer struct {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/priyanshujain/uatu/internal/driver"
|
||||
"github.com/priyanshujain/sanderling/internal/driver"
|
||||
)
|
||||
|
||||
type ActionKind string
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/priyanshujain/uatu/internal/driver"
|
||||
"github.com/priyanshujain/sanderling/internal/driver"
|
||||
)
|
||||
|
||||
func TestNew_Defaults(t *testing.T) {
|
||||
|
||||
Vendored
+1
-1
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<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>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BSJUf7yI.css">
|
||||
</head>
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/priyanshujain/uatu/internal/trace"
|
||||
"github.com/priyanshujain/sanderling/internal/trace"
|
||||
)
|
||||
|
||||
// Maximum size of a single trace.jsonl line. Hierarchies and snapshots
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/priyanshujain/uatu/internal/trace"
|
||||
"github.com/priyanshujain/sanderling/internal/trace"
|
||||
)
|
||||
|
||||
func TestScan_OrdersByStartedAtDescendingAndCountsViolations(t *testing.T) {
|
||||
|
||||
@@ -20,7 +20,7 @@ type ServerOptions struct {
|
||||
DevTarget string
|
||||
}
|
||||
|
||||
// Server holds the HTTP handlers for `uatu inspect`.
|
||||
// Server holds the HTTP handlers for `sanderling inspect`.
|
||||
type Server struct {
|
||||
options ServerOptions
|
||||
cache *Cache
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/priyanshujain/uatu/internal/trace"
|
||||
"github.com/priyanshujain/sanderling/internal/trace"
|
||||
)
|
||||
|
||||
func newFixtureServer(t *testing.T) (*Server, string) {
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/priyanshujain/uatu/internal/agent"
|
||||
"github.com/priyanshujain/uatu/internal/driver"
|
||||
"github.com/priyanshujain/uatu/internal/hierarchy"
|
||||
"github.com/priyanshujain/uatu/internal/ltl"
|
||||
"github.com/priyanshujain/uatu/internal/trace"
|
||||
"github.com/priyanshujain/uatu/internal/verifier"
|
||||
"github.com/priyanshujain/sanderling/internal/agent"
|
||||
"github.com/priyanshujain/sanderling/internal/driver"
|
||||
"github.com/priyanshujain/sanderling/internal/hierarchy"
|
||||
"github.com/priyanshujain/sanderling/internal/ltl"
|
||||
"github.com/priyanshujain/sanderling/internal/trace"
|
||||
"github.com/priyanshujain/sanderling/internal/verifier"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
|
||||
@@ -15,19 +15,19 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/priyanshujain/uatu/internal/agent"
|
||||
mockdriver "github.com/priyanshujain/uatu/internal/driver/mock"
|
||||
"github.com/priyanshujain/uatu/internal/trace"
|
||||
"github.com/priyanshujain/uatu/internal/verifier"
|
||||
"github.com/priyanshujain/sanderling/internal/agent"
|
||||
mockdriver "github.com/priyanshujain/sanderling/internal/driver/mock"
|
||||
"github.com/priyanshujain/sanderling/internal/trace"
|
||||
"github.com/priyanshujain/sanderling/internal/verifier"
|
||||
)
|
||||
|
||||
const fixtureSpec = `
|
||||
const screen = __uatu__.extract(state => state.snapshots.screen ?? "");
|
||||
const balance = __uatu__.extract(state => state.snapshots.balance ?? 0);
|
||||
const screen = __sanderling__.extract(state => state.snapshots.screen ?? "");
|
||||
const balance = __sanderling__.extract(state => state.snapshots.balance ?? 0);
|
||||
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 {
|
||||
@@ -195,9 +195,9 @@ func TestRunner_ViolationSurfacesInSummary(t *testing.T) {
|
||||
func TestRunner_ThrowingPredicateIsLoggedNotPanic(t *testing.T) {
|
||||
const throwingSpec = `
|
||||
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.startSDK(t)
|
||||
|
||||
@@ -23,12 +23,12 @@ func EmbeddedSHA256() string {
|
||||
// matching checksum, no rewrite happens. Returns the JAR path.
|
||||
func Extract(dir string) (string, error) {
|
||||
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 {
|
||||
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"
|
||||
checksum := EmbeddedSHA256()
|
||||
|
||||
|
||||
@@ -5,6 +5,6 @@ package sidecar
|
||||
var embeddedJAR []byte
|
||||
|
||||
// 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.
|
||||
func IsPlaceholder() bool { return true }
|
||||
@@ -25,7 +25,7 @@ func TestExtract_WritesJARAndChecksum(t *testing.T) {
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
body, err := os.ReadFile(path)
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/priyanshujain/uatu/internal/hierarchy"
|
||||
"github.com/priyanshujain/sanderling/internal/hierarchy"
|
||||
)
|
||||
|
||||
type Step struct {
|
||||
@@ -74,7 +74,7 @@ type Meta struct {
|
||||
BundleID string `json:"bundle_id"`
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
EndedAt *time.Time `json:"ended_at,omitempty"`
|
||||
UatuVersion string `json:"uatu_version"`
|
||||
SanderlingVersion string `json:"sanderling_version"`
|
||||
}
|
||||
|
||||
type Writer struct {
|
||||
|
||||
@@ -25,7 +25,7 @@ func TestWriteMeta_RoundTrip(t *testing.T) {
|
||||
Platform: "android",
|
||||
BundleID: "in.okcredit.merchant",
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
@@ -60,7 +60,7 @@ func TestWriteMeta_EndedAtRoundTrip(t *testing.T) {
|
||||
BundleID: "in.test",
|
||||
StartedAt: time.Date(2026, 4, 17, 22, 30, 0, 0, time.UTC),
|
||||
EndedAt: &endedAt,
|
||||
UatuVersion: "0.0.1",
|
||||
SanderlingVersion: "0.0.1",
|
||||
}
|
||||
if err := writer.WriteMeta(meta); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/priyanshujain/uatu/internal/hierarchy"
|
||||
"github.com/priyanshujain/sanderling/internal/hierarchy"
|
||||
)
|
||||
|
||||
// TestStateAxFindWorks verifies that a Parse+PushSnapshot+extract round trip
|
||||
@@ -24,11 +24,11 @@ func TestStateAxFindWorks(t *testing.T) {
|
||||
|
||||
verifier := newVerifier(t)
|
||||
mustLoad(t, verifier, `
|
||||
globalThis.probe = __uatu__.extract(state => {
|
||||
globalThis.probe = __sanderling__.extract(state => {
|
||||
const element = state.ax.find("id:select_language");
|
||||
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 {
|
||||
|
||||
@@ -80,11 +80,11 @@ type formulaSpec struct {
|
||||
}
|
||||
|
||||
const (
|
||||
tagFormula = "__uatuFormula"
|
||||
tagFormulaSpecIndex = "__uatuFormulaSpec"
|
||||
tagActionGenerator = "__uatuActionGenerator"
|
||||
tagInternalKind = "__uatuKind"
|
||||
tagSelector = "__uatuSelector"
|
||||
tagFormula = "__sanderlingFormula"
|
||||
tagFormulaSpecIndex = "__sanderlingFormulaSpec"
|
||||
tagActionGenerator = "__sanderlingActionGenerator"
|
||||
tagInternalKind = "__sanderlingKind"
|
||||
tagSelector = "__sanderlingSelector"
|
||||
|
||||
internalKindActions = "actions"
|
||||
internalKindWeighted = "weighted"
|
||||
@@ -94,63 +94,63 @@ const (
|
||||
internalKindBuiltinPressKey = "pressKey"
|
||||
)
|
||||
|
||||
// installRuntimeBindings exposes globalThis.__uatu__ to the loaded spec.
|
||||
// installRuntimeBindings exposes globalThis.__sanderling__ to the loaded spec.
|
||||
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
|
||||
}
|
||||
if err := uatu.Set("always", v.bindAlways); err != nil {
|
||||
if err := sanderling.Set("always", v.bindAlways); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("now", v.bindNow); err != nil {
|
||||
if err := sanderling.Set("now", v.bindNow); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("next", v.bindNext); err != nil {
|
||||
if err := sanderling.Set("next", v.bindNext); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("eventually", v.bindEventually); err != nil {
|
||||
if err := sanderling.Set("eventually", v.bindEventually); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("actions", v.bindActions); err != nil {
|
||||
if err := sanderling.Set("actions", v.bindActions); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("weighted", v.bindWeighted); err != nil {
|
||||
if err := sanderling.Set("weighted", v.bindWeighted); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("from", v.bindFrom); err != nil {
|
||||
if err := sanderling.Set("from", v.bindFrom); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("tap", v.bindTap); err != nil {
|
||||
if err := sanderling.Set("tap", v.bindTap); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("inputText", v.bindInputText); err != nil {
|
||||
if err := sanderling.Set("inputText", v.bindInputText); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("swipe", v.bindSwipe); err != nil {
|
||||
if err := sanderling.Set("swipe", v.bindSwipe); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("pressKey", v.bindPressKey); err != nil {
|
||||
if err := sanderling.Set("pressKey", v.bindPressKey); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("wait", v.bindWait); err != nil {
|
||||
if err := sanderling.Set("wait", v.bindWait); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("taps", v.builtinGenerator(internalKindBuiltinTaps)); err != nil {
|
||||
if err := sanderling.Set("taps", v.builtinGenerator(internalKindBuiltinTaps)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("swipes", v.builtinGenerator(internalKindBuiltinSwipes)); err != nil {
|
||||
if err := sanderling.Set("swipes", v.builtinGenerator(internalKindBuiltinSwipes)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uatu.Set("waitOnce", v.builtinGenerator(internalKindBuiltinWaitOnce)); err != nil {
|
||||
if err := sanderling.Set("waitOnce", v.builtinGenerator(internalKindBuiltinWaitOnce)); err != nil {
|
||||
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 v.runtime.GlobalObject().Set("__uatu__", uatu)
|
||||
return v.runtime.GlobalObject().Set("__sanderling__", sanderling)
|
||||
}
|
||||
|
||||
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
|
||||
// handle (new shape). Both produce a formula handle tagged with
|
||||
// __uatuFormulaSpec.
|
||||
// __sanderlingFormulaSpec.
|
||||
func (v *Verifier) bindAlways(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) != 1 {
|
||||
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.Set(tagFormula, true)
|
||||
_ = 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.
|
||||
_ = handle.Set("__uatuIndex", index)
|
||||
_ = handle.Set("__sanderlingIndex", index)
|
||||
|
||||
_ = handle.Set("implies", v.binaryChain(index, specKindImplies))
|
||||
_ = 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) {
|
||||
if value == nil || goja.IsNull(value) || goja.IsUndefined(value) {
|
||||
return 0, false
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"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.
|
||||
|
||||
@@ -5,13 +5,13 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/priyanshujain/uatu/internal/bundler"
|
||||
"github.com/priyanshujain/uatu/internal/hierarchy"
|
||||
"github.com/priyanshujain/uatu/internal/ltl"
|
||||
"github.com/priyanshujain/sanderling/internal/bundler"
|
||||
"github.com/priyanshujain/sanderling/internal/hierarchy"
|
||||
"github.com/priyanshujain/sanderling/internal/ltl"
|
||||
)
|
||||
|
||||
// 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.
|
||||
func bundleIntegrationSpec(t *testing.T) string {
|
||||
t.Helper()
|
||||
@@ -30,8 +30,8 @@ func bundleIntegrationSpec(t *testing.T) string {
|
||||
bundle, err := bundler.Bundle(bundler.Options{
|
||||
EntryFile: specPath,
|
||||
Aliases: map[string]string{
|
||||
"@uatu/spec": apiPath,
|
||||
"@uatu/spec/defaults/properties": defaultsPath,
|
||||
"@sanderling/spec": apiPath,
|
||||
"@sanderling/spec/defaults/properties": defaultsPath,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ import {
|
||||
eventually,
|
||||
extract,
|
||||
weighted,
|
||||
} from "@uatu/spec";
|
||||
import { noUncaughtExceptions } from "@uatu/spec/defaults/properties";
|
||||
} from "@sanderling/spec";
|
||||
import { noUncaughtExceptions } from "@sanderling/spec/defaults/properties";
|
||||
|
||||
const route = extract<string>(
|
||||
(state) => (state.snapshots.route as string) ?? "",
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/priyanshujain/uatu/internal/ltl"
|
||||
"github.com/priyanshujain/sanderling/internal/ltl"
|
||||
)
|
||||
|
||||
func newVerifier(t *testing.T, options ...Option) *Verifier {
|
||||
@@ -27,18 +27,18 @@ func mustLoad(t *testing.T, verifier *Verifier, source string) {
|
||||
}
|
||||
|
||||
const helloSpec = `
|
||||
const screen = __uatu__.extract(state => state.snapshots.screen ?? "");
|
||||
const balance = __uatu__.extract(state => state.snapshots["ledger.balance"] ?? 0);
|
||||
const screen = __sanderling__.extract(state => state.snapshots.screen ?? "");
|
||||
const balance = __sanderling__.extract(state => state.snapshots["ledger.balance"] ?? 0);
|
||||
|
||||
globalThis.screen = screen;
|
||||
globalThis.balance = balance;
|
||||
|
||||
globalThis.properties = {
|
||||
balanceNonNegative: __uatu__.always(() => balance.current >= 0),
|
||||
balanceNonNegative: __sanderling__.always(() => balance.current >= 0),
|
||||
};
|
||||
|
||||
globalThis.actions = __uatu__.actions(() => [
|
||||
__uatu__.tap({ on: "id:home_button" }),
|
||||
globalThis.actions = __sanderling__.actions(() => [
|
||||
__sanderling__.tap({ on: "id:home_button" }),
|
||||
]);
|
||||
`
|
||||
|
||||
@@ -135,9 +135,9 @@ func TestNextAction_FromActionsGenerator(t *testing.T) {
|
||||
func TestNextAction_WeightedSelectsByWeight(t *testing.T) {
|
||||
verifier := newVerifier(t, WithRand(rand.New(rand.NewPCG(42, 0))))
|
||||
mustLoad(t, verifier, `
|
||||
const tapHome = __uatu__.actions(() => [__uatu__.tap({ on: "id:home" })]);
|
||||
const tapAway = __uatu__.actions(() => [__uatu__.tap({ on: "id:away" })]);
|
||||
globalThis.actions = __uatu__.weighted(
|
||||
const tapHome = __sanderling__.actions(() => [__sanderling__.tap({ on: "id:home" })]);
|
||||
const tapAway = __sanderling__.actions(() => [__sanderling__.tap({ on: "id:away" })]);
|
||||
globalThis.actions = __sanderling__.weighted(
|
||||
[1, tapHome],
|
||||
[99, tapAway],
|
||||
);
|
||||
@@ -166,7 +166,7 @@ func TestNextAction_WeightedSelectsByWeight(t *testing.T) {
|
||||
func TestNextAction_EmptyGeneratorReturnsErrNoAction(t *testing.T) {
|
||||
verifier := newVerifier(t)
|
||||
mustLoad(t, verifier, `
|
||||
globalThis.actions = __uatu__.actions(() => []);
|
||||
globalThis.actions = __sanderling__.actions(() => []);
|
||||
`)
|
||||
_ = verifier.PushSnapshot(SnapshotInput{Snapshots: Snapshots{}})
|
||||
|
||||
@@ -179,8 +179,8 @@ func TestNextAction_EmptyGeneratorReturnsErrNoAction(t *testing.T) {
|
||||
func TestInputText_RoundTrip(t *testing.T) {
|
||||
verifier := newVerifier(t)
|
||||
mustLoad(t, verifier, `
|
||||
globalThis.actions = __uatu__.actions(() => [
|
||||
__uatu__.inputText({ into: "id:phone", text: "+919876543210" }),
|
||||
globalThis.actions = __sanderling__.actions(() => [
|
||||
__sanderling__.inputText({ into: "id:phone", text: "+919876543210" }),
|
||||
]);
|
||||
`)
|
||||
_ = verifier.PushSnapshot(SnapshotInput{Snapshots: Snapshots{}})
|
||||
@@ -200,7 +200,7 @@ func TestInputText_RoundTrip(t *testing.T) {
|
||||
func TestPushSnapshot_FeedsSnapshotsToExtractorState(t *testing.T) {
|
||||
verifier := newVerifier(t)
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
@@ -222,7 +222,7 @@ func TestLoad_PropagatesSyntaxError(t *testing.T) {
|
||||
func TestEvaluateProperties_ThrowingPredicateDoesNotPanic(t *testing.T) {
|
||||
const spec = `
|
||||
globalThis.properties = {
|
||||
broken: __uatu__.always(() => { throw new Error("bad predicate"); }),
|
||||
broken: __sanderling__.always(() => { throw new Error("bad predicate"); }),
|
||||
};
|
||||
`
|
||||
verifier := newVerifier(t)
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
|
||||
"github.com/dop251/goja"
|
||||
|
||||
"github.com/priyanshujain/uatu/internal/hierarchy"
|
||||
"github.com/priyanshujain/uatu/internal/ltl"
|
||||
"github.com/priyanshujain/sanderling/internal/hierarchy"
|
||||
"github.com/priyanshujain/sanderling/internal/ltl"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
```sh
|
||||
npm install --save-dev @uatu/spec
|
||||
npm install --save-dev @sanderling/spec
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { extract, always, actions, Tap, weighted } from "@uatu/spec";
|
||||
import { extract, always, actions, Tap, weighted } from "@sanderling/spec";
|
||||
|
||||
export const spec = {
|
||||
extract: extract((tree) => ({
|
||||
@@ -32,7 +32,7 @@ export const spec = {
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "@uatu/spec",
|
||||
"name": "@sanderling/spec",
|
||||
"version": "0.0.0-dev",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@uatu/spec",
|
||||
"name": "@sanderling/spec",
|
||||
"version": "0.0.0-dev",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@uatu/spec",
|
||||
"name": "@sanderling/spec",
|
||||
"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",
|
||||
"types": "./dist/index.d.ts",
|
||||
"main": "./dist/index.js",
|
||||
@@ -21,12 +21,12 @@
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/priyanshujain/uatu.git",
|
||||
"url": "git+https://github.com/priyanshujain/sanderling.git",
|
||||
"directory": "pkg/spec-api"
|
||||
},
|
||||
"homepage": "https://github.com/priyanshujain/uatu",
|
||||
"homepage": "https://github.com/priyanshujain/sanderling",
|
||||
"bugs": {
|
||||
"url": "https://github.com/priyanshujain/uatu/issues"
|
||||
"url": "https://github.com/priyanshujain/sanderling/issues"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"author": "Priyanshu Jain",
|
||||
|
||||
@@ -14,26 +14,26 @@ import type {
|
||||
} from "./types.ts";
|
||||
|
||||
export function actions(generator: () => Action[]): ActionGenerator {
|
||||
return globalThis.__uatu__.actions(generator);
|
||||
return globalThis.__sanderling__.actions(generator);
|
||||
}
|
||||
|
||||
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> {
|
||||
return globalThis.__uatu__.from(items);
|
||||
return globalThis.__sanderling__.from(items);
|
||||
}
|
||||
|
||||
export function Tap(parameters: { on: string | AccessibilityElement }): TapAction {
|
||||
return globalThis.__uatu__.tap(parameters);
|
||||
return globalThis.__sanderling__.tap(parameters);
|
||||
}
|
||||
|
||||
export function InputText(parameters: {
|
||||
into: string | AccessibilityElement;
|
||||
text: string;
|
||||
}): InputTextAction {
|
||||
return globalThis.__uatu__.inputText(parameters);
|
||||
return globalThis.__sanderling__.inputText(parameters);
|
||||
}
|
||||
|
||||
export function Swipe(parameters: {
|
||||
@@ -41,21 +41,21 @@ export function Swipe(parameters: {
|
||||
to: Point | AccessibilityElement;
|
||||
durationMillis?: number;
|
||||
}): SwipeAction {
|
||||
return globalThis.__uatu__.swipe(parameters);
|
||||
return globalThis.__sanderling__.swipe(parameters);
|
||||
}
|
||||
|
||||
export function PressKey(parameters: { key: Key }): PressKeyAction {
|
||||
return globalThis.__uatu__.pressKey(parameters);
|
||||
return globalThis.__sanderling__.pressKey(parameters);
|
||||
}
|
||||
|
||||
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 {
|
||||
return new Proxy({} as ActionGenerator, {
|
||||
get(_target, property) {
|
||||
const runtime = globalThis.__uatu__[name] as unknown as Record<
|
||||
const runtime = globalThis.__sanderling__[name] as unknown as Record<
|
||||
string | symbol,
|
||||
unknown
|
||||
>;
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { Formula } from "../types.ts";
|
||||
|
||||
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.
|
||||
export const noUncaughtExceptions: Formula = always(
|
||||
() => exceptionCount.current === 0,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Extracted, State } from "./types.ts";
|
||||
|
||||
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,
|
||||
SwipeAction,
|
||||
TapAction,
|
||||
UatuRuntime,
|
||||
SanderlingRuntime,
|
||||
WaitAction,
|
||||
WeightedEntry,
|
||||
} from "./types.ts";
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import type { EventuallyFormula, Formula } from "./types.ts";
|
||||
|
||||
export function always(predicateOrFormula: (() => boolean) | Formula): Formula {
|
||||
return globalThis.__uatu__.always(predicateOrFormula);
|
||||
return globalThis.__sanderling__.always(predicateOrFormula);
|
||||
}
|
||||
|
||||
export function now(predicate: () => boolean): Formula {
|
||||
return globalThis.__uatu__.now(predicate);
|
||||
return globalThis.__sanderling__.now(predicate);
|
||||
}
|
||||
|
||||
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 —
|
||||
// prefer `.within(n, unit)` when you want the verifier to fail a property
|
||||
// that stalls.
|
||||
export function eventually(predicate: () => boolean): EventuallyFormula {
|
||||
return globalThis.__uatu__.eventually(predicate);
|
||||
return globalThis.__sanderling__.eventually(predicate);
|
||||
}
|
||||
@@ -78,12 +78,12 @@ export type Key =
|
||||
| "right";
|
||||
|
||||
export interface ActionGenerator {
|
||||
readonly __uatuActionGenerator: true;
|
||||
readonly __sanderlingActionGenerator: true;
|
||||
generate(): Action[];
|
||||
}
|
||||
|
||||
export interface Formula {
|
||||
readonly __uatuFormula: true;
|
||||
readonly __sanderlingFormula: true;
|
||||
implies(other: Formula): Formula;
|
||||
or(other: Formula): Formula;
|
||||
and(other: Formula): Formula;
|
||||
@@ -98,7 +98,7 @@ export interface Sampler<T> {
|
||||
generate(): T;
|
||||
}
|
||||
|
||||
export interface UatuRuntime {
|
||||
export interface SanderlingRuntime {
|
||||
extract: <T>(getter: (state: State) => T) => Extracted<T>;
|
||||
always: (predicateOrFormula: (() => boolean) | Formula) => Formula;
|
||||
now: (predicate: () => boolean) => Formula;
|
||||
@@ -129,5 +129,5 @@ export type WeightedEntry = readonly [number, ActionGenerator];
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var __uatu__: UatuRuntime;
|
||||
var __sanderling__: SanderlingRuntime;
|
||||
}
|
||||
@@ -29,11 +29,11 @@ import type {
|
||||
Formula,
|
||||
Sampler,
|
||||
State,
|
||||
UatuRuntime,
|
||||
SanderlingRuntime,
|
||||
WeightedEntry,
|
||||
} from "../src/types.ts";
|
||||
|
||||
interface RecordedRuntime extends UatuRuntime {
|
||||
interface RecordedRuntime extends SanderlingRuntime {
|
||||
extracts: Array<(state: State) => unknown>;
|
||||
alwaysArgs: Array<(() => boolean) | Formula>;
|
||||
nowPredicates: Array<() => boolean>;
|
||||
@@ -51,7 +51,7 @@ interface RecordedRuntime extends UatuRuntime {
|
||||
|
||||
function makeChainableFormula(record: RecordedRuntime): Formula {
|
||||
const formula: Formula = {
|
||||
__uatuFormula: true,
|
||||
__sanderlingFormula: true,
|
||||
implies(other: Formula): Formula {
|
||||
record.impliesCalls++;
|
||||
void other;
|
||||
@@ -125,11 +125,11 @@ function installFakeRuntime(): RecordedRuntime {
|
||||
},
|
||||
actions: (generator: () => Action[]): ActionGenerator => {
|
||||
calls.actionGenerators.push(generator);
|
||||
return { __uatuActionGenerator: true, generate: generator };
|
||||
return { __sanderlingActionGenerator: true, generate: generator };
|
||||
},
|
||||
weighted: (...entries: WeightedEntry[]): ActionGenerator => {
|
||||
calls.weightedCalls.push(entries);
|
||||
return { __uatuActionGenerator: true, generate: () => [] };
|
||||
return { __sanderlingActionGenerator: true, generate: () => [] };
|
||||
},
|
||||
from: <T>(items: readonly T[]): Sampler<T> => {
|
||||
calls.fromCalls.push(items as unknown[]);
|
||||
@@ -145,13 +145,13 @@ function installFakeRuntime(): RecordedRuntime {
|
||||
}),
|
||||
pressKey: ({ key }) => ({ kind: "PressKey", key }),
|
||||
wait: ({ durationMillis }) => ({ kind: "Wait", durationMillis }),
|
||||
taps: { __uatuActionGenerator: true, generate: () => [] },
|
||||
swipes: { __uatuActionGenerator: true, generate: () => [] },
|
||||
waitOnce: { __uatuActionGenerator: true, generate: () => [] },
|
||||
pressKeys: { __uatuActionGenerator: true, generate: () => [] },
|
||||
} satisfies UatuRuntime;
|
||||
taps: { __sanderlingActionGenerator: true, generate: () => [] },
|
||||
swipes: { __sanderlingActionGenerator: true, generate: () => [] },
|
||||
waitOnce: { __sanderlingActionGenerator: true, generate: () => [] },
|
||||
pressKeys: { __sanderlingActionGenerator: true, generate: () => [] },
|
||||
} satisfies SanderlingRuntime;
|
||||
const recorded = Object.assign(runtime, calls) as RecordedRuntime;
|
||||
globalThis.__uatu__ = recorded;
|
||||
globalThis.__sanderling__ = recorded;
|
||||
return recorded;
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ test("always wraps a predicate into a formula via the runtime", () => {
|
||||
const predicate = () => true;
|
||||
const formula = always(predicate);
|
||||
assert.equal(runtime.alwaysArgs[0], predicate);
|
||||
assert.equal(formula.__uatuFormula, true);
|
||||
assert.equal(formula.__sanderlingFormula, true);
|
||||
});
|
||||
|
||||
test("always accepts a formula handle", () => {
|
||||
@@ -176,7 +176,7 @@ test("always accepts a formula handle", () => {
|
||||
const inner = now(() => true);
|
||||
const wrapped = always(inner);
|
||||
assert.equal(runtime.alwaysArgs.at(-1), inner);
|
||||
assert.equal(wrapped.__uatuFormula, true);
|
||||
assert.equal(wrapped.__sanderlingFormula, true);
|
||||
});
|
||||
|
||||
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 wrapped = actions(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", () => {
|
||||
@@ -279,8 +279,8 @@ test("from forwards items to the runtime", () => {
|
||||
|
||||
test("default generators proxy through to the runtime", () => {
|
||||
installFakeRuntime();
|
||||
assert.equal(taps.__uatuActionGenerator, true);
|
||||
assert.equal(swipes.__uatuActionGenerator, true);
|
||||
assert.equal(waitOnce.__uatuActionGenerator, true);
|
||||
assert.equal(pressKey.__uatuActionGenerator, true);
|
||||
assert.equal(taps.__sanderlingActionGenerator, true);
|
||||
assert.equal(swipes.__sanderlingActionGenerator, true);
|
||||
assert.equal(waitOnce.__sanderlingActionGenerator, true);
|
||||
assert.equal(pressKey.__sanderlingActionGenerator, true);
|
||||
});
|
||||
@@ -9,11 +9,11 @@ import type {
|
||||
Formula,
|
||||
Sampler,
|
||||
State,
|
||||
UatuRuntime,
|
||||
SanderlingRuntime,
|
||||
WeightedEntry,
|
||||
} from "../src/types.ts";
|
||||
|
||||
interface RecordedRuntime extends UatuRuntime {
|
||||
interface RecordedRuntime extends SanderlingRuntime {
|
||||
currentState: State;
|
||||
extractors: Array<(state: State) => unknown>;
|
||||
alwaysArgs: Array<(() => boolean) | Formula>;
|
||||
@@ -43,17 +43,17 @@ function installRuntime(initialState: State): RecordedRuntime {
|
||||
if (typeof predicateOrFormula === "function") {
|
||||
lastPredicate = predicateOrFormula;
|
||||
}
|
||||
return { __uatuFormula: true } as Formula;
|
||||
return { __sanderlingFormula: true } as Formula;
|
||||
},
|
||||
now: () => ({ __uatuFormula: true } as Formula),
|
||||
next: () => ({ __uatuFormula: true } as Formula),
|
||||
eventually: () => ({ __uatuFormula: true } as EventuallyFormula),
|
||||
now: () => ({ __sanderlingFormula: true } as Formula),
|
||||
next: () => ({ __sanderlingFormula: true } as Formula),
|
||||
eventually: () => ({ __sanderlingFormula: true } as EventuallyFormula),
|
||||
actions: (generator: () => Action[]): ActionGenerator => ({
|
||||
__uatuActionGenerator: true,
|
||||
__sanderlingActionGenerator: true,
|
||||
generate: generator,
|
||||
}),
|
||||
weighted: (..._entries: WeightedEntry[]): ActionGenerator => ({
|
||||
__uatuActionGenerator: true,
|
||||
__sanderlingActionGenerator: true,
|
||||
generate: () => [],
|
||||
}),
|
||||
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 }),
|
||||
pressKey: ({ key }) => ({ kind: "PressKey", key }),
|
||||
wait: ({ durationMillis }) => ({ kind: "Wait", durationMillis }),
|
||||
taps: { __uatuActionGenerator: true, generate: () => [] } as ActionGenerator,
|
||||
swipes: { __uatuActionGenerator: true, generate: () => [] } as ActionGenerator,
|
||||
waitOnce: { __uatuActionGenerator: true, generate: () => [] } as ActionGenerator,
|
||||
pressKeys: { __uatuActionGenerator: true, generate: () => [] } as ActionGenerator,
|
||||
} satisfies UatuRuntime;
|
||||
taps: { __sanderlingActionGenerator: true, generate: () => [] } as ActionGenerator,
|
||||
swipes: { __sanderlingActionGenerator: true, generate: () => [] } as ActionGenerator,
|
||||
waitOnce: { __sanderlingActionGenerator: true, generate: () => [] } as ActionGenerator,
|
||||
pressKeys: { __sanderlingActionGenerator: true, generate: () => [] } as ActionGenerator,
|
||||
} satisfies SanderlingRuntime;
|
||||
|
||||
const state = { currentState: initialState };
|
||||
const recorded = Object.assign(runtime, {
|
||||
@@ -77,7 +77,7 @@ function installRuntime(initialState: State): RecordedRuntime {
|
||||
return lastPredicate;
|
||||
},
|
||||
}) as unknown as RecordedRuntime;
|
||||
globalThis.__uatu__ = recorded;
|
||||
globalThis.__sanderling__ = recorded;
|
||||
// Re-bind state ref so subsequent extract() calls read the up-to-date state.
|
||||
Object.defineProperty(recorded, "currentState", {
|
||||
get() {
|
||||
@@ -105,6 +105,6 @@ test("defaults bundle exports formulas tagged as LTL properties", async () => {
|
||||
logs: [{ unixMillis: 1, level: "W", tag: "X", message: "warn" }],
|
||||
});
|
||||
const defaults = await import("../src/defaults/properties.ts");
|
||||
assert.equal(defaults.noUncaughtExceptions.__uatuFormula, true);
|
||||
assert.equal(defaults.noLogcatErrors.__uatuFormula, true);
|
||||
assert.equal(defaults.noUncaughtExceptions.__sanderlingFormula, 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 = "" +
|
||||
"\n" +
|
||||
"\x15driverpb/driver.proto\x12\x0euatu.driver.v1\"\a\n" +
|
||||
"\x15driverpb/driver.proto\x12\x14sanderling.driver.v1\"\a\n" +
|
||||
"\x05Empty\"z\n" +
|
||||
"\rLaunchRequest\x12\x1b\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" +
|
||||
"\x05ready\x18\x01 \x01(\bR\x05ready\x12\x18\n" +
|
||||
"\aversion\x18\x02 \x01(\tR\aversion\x12\x1a\n" +
|
||||
"\bplatform\x18\x03 \x01(\tR\bplatform\"\x89\x01\n" +
|
||||
"\fSwipeRequest\x12)\n" +
|
||||
"\x04from\x18\x01 \x01(\v2\x15.uatu.driver.v1.PointR\x04from\x12%\n" +
|
||||
"\x02to\x18\x02 \x01(\v2\x15.uatu.driver.v1.PointR\x02to\x12'\n" +
|
||||
"\bplatform\x18\x03 \x01(\tR\bplatform\"\x95\x01\n" +
|
||||
"\fSwipeRequest\x12/\n" +
|
||||
"\x04from\x18\x01 \x01(\v2\x1b.sanderling.driver.v1.PointR\x04from\x12+\n" +
|
||||
"\x02to\x18\x02 \x01(\v2\x1b.sanderling.driver.v1.PointR\x02to\x12'\n" +
|
||||
"\x0fduration_millis\x18\x03 \x01(\x03R\x0edurationMillis\"#\n" +
|
||||
"\x0fPressKeyRequest\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\"e\n" +
|
||||
"\x11RecentLogsRequest\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" +
|
||||
"LogEntries\x122\n" +
|
||||
"\aentries\x18\x01 \x03(\v2\x18.uatu.driver.v1.LogEntryR\aentries\"m\n" +
|
||||
"LogEntries\x128\n" +
|
||||
"\aentries\x18\x01 \x03(\v2\x1e.sanderling.driver.v1.LogEntryR\aentries\"m\n" +
|
||||
"\bLogEntry\x12\x1f\n" +
|
||||
"\vunix_millis\x18\x01 \x01(\x03R\n" +
|
||||
"unixMillis\x12\x14\n" +
|
||||
@@ -900,24 +900,24 @@ const file_driverpb_driver_proto_rawDesc = "" +
|
||||
"cpuPercent\x12\x1d\n" +
|
||||
"\n" +
|
||||
"heap_bytes\x18\x02 \x01(\x03R\theapBytes\x12,\n" +
|
||||
"\x12total_memory_bytes\x18\x03 \x01(\x03R\x10totalMemoryBytes2\xcb\x06\n" +
|
||||
"\x06Driver\x12>\n" +
|
||||
"\x06Launch\x12\x1d.uatu.driver.v1.LaunchRequest\x1a\x15.uatu.driver.v1.Empty\x129\n" +
|
||||
"\tTerminate\x12\x15.uatu.driver.v1.Empty\x1a\x15.uatu.driver.v1.Empty\x123\n" +
|
||||
"\x03Tap\x12\x15.uatu.driver.v1.Point\x1a\x15.uatu.driver.v1.Empty\x12>\n" +
|
||||
"\vTapSelector\x12\x18.uatu.driver.v1.Selector\x1a\x15.uatu.driver.v1.Empty\x128\n" +
|
||||
"\tInputText\x12\x14.uatu.driver.v1.Text\x1a\x15.uatu.driver.v1.Empty\x12<\n" +
|
||||
"\x05Swipe\x12\x1c.uatu.driver.v1.SwipeRequest\x1a\x15.uatu.driver.v1.Empty\x12B\n" +
|
||||
"\bPressKey\x12\x1f.uatu.driver.v1.PressKeyRequest\x1a\x15.uatu.driver.v1.Empty\x12:\n" +
|
||||
"\x12total_memory_bytes\x18\x03 \x01(\x03R\x10totalMemoryBytes2\xe7\a\n" +
|
||||
"\x06Driver\x12J\n" +
|
||||
"\x06Launch\x12#.sanderling.driver.v1.LaunchRequest\x1a\x1b.sanderling.driver.v1.Empty\x12E\n" +
|
||||
"\tTerminate\x12\x1b.sanderling.driver.v1.Empty\x1a\x1b.sanderling.driver.v1.Empty\x12?\n" +
|
||||
"\x03Tap\x12\x1b.sanderling.driver.v1.Point\x1a\x1b.sanderling.driver.v1.Empty\x12J\n" +
|
||||
"\vTapSelector\x12\x1e.sanderling.driver.v1.Selector\x1a\x1b.sanderling.driver.v1.Empty\x12D\n" +
|
||||
"\tInputText\x12\x1a.sanderling.driver.v1.Text\x1a\x1b.sanderling.driver.v1.Empty\x12H\n" +
|
||||
"\x05Swipe\x12\".sanderling.driver.v1.SwipeRequest\x1a\x1b.sanderling.driver.v1.Empty\x12N\n" +
|
||||
"\bPressKey\x12%.sanderling.driver.v1.PressKeyRequest\x1a\x1b.sanderling.driver.v1.Empty\x12F\n" +
|
||||
"\n" +
|
||||
"Screenshot\x12\x15.uatu.driver.v1.Empty\x1a\x15.uatu.driver.v1.Image\x12A\n" +
|
||||
"\tHierarchy\x12\x15.uatu.driver.v1.Empty\x1a\x1d.uatu.driver.v1.HierarchyJSON\x12K\n" +
|
||||
"Screenshot\x12\x1b.sanderling.driver.v1.Empty\x1a\x1b.sanderling.driver.v1.Image\x12M\n" +
|
||||
"\tHierarchy\x12\x1b.sanderling.driver.v1.Empty\x1a#.sanderling.driver.v1.HierarchyJSON\x12W\n" +
|
||||
"\n" +
|
||||
"RecentLogs\x12!.uatu.driver.v1.RecentLogsRequest\x1a\x1a.uatu.driver.v1.LogEntries\x12>\n" +
|
||||
"\vWaitForIdle\x12\x18.uatu.driver.v1.Duration\x1a\x15.uatu.driver.v1.Empty\x12=\n" +
|
||||
"\x06Health\x12\x15.uatu.driver.v1.Empty\x1a\x1c.uatu.driver.v1.HealthStatus\x12J\n" +
|
||||
"\aMetrics\x12\x1e.uatu.driver.v1.MetricsRequest\x1a\x1f.uatu.driver.v1.MetricsResponseBM\n" +
|
||||
"\x12dev.uatu.driver.v1P\x01Z5github.com/priyanshujain/uatu/proto/driverpb;driverpbb\x06proto3"
|
||||
"RecentLogs\x12'.sanderling.driver.v1.RecentLogsRequest\x1a .sanderling.driver.v1.LogEntries\x12J\n" +
|
||||
"\vWaitForIdle\x12\x1e.sanderling.driver.v1.Duration\x1a\x1b.sanderling.driver.v1.Empty\x12I\n" +
|
||||
"\x06Health\x12\x1b.sanderling.driver.v1.Empty\x1a\".sanderling.driver.v1.HealthStatus\x12V\n" +
|
||||
"\aMetrics\x12$.sanderling.driver.v1.MetricsRequest\x1a%.sanderling.driver.v1.MetricsResponseBY\n" +
|
||||
"\x18dev.sanderling.driver.v1P\x01Z;github.com/priyanshujain/sanderling/proto/driverpb;driverpbb\x06proto3"
|
||||
|
||||
var (
|
||||
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_goTypes = []any{
|
||||
(*Empty)(nil), // 0: uatu.driver.v1.Empty
|
||||
(*LaunchRequest)(nil), // 1: uatu.driver.v1.LaunchRequest
|
||||
(*Point)(nil), // 2: uatu.driver.v1.Point
|
||||
(*Text)(nil), // 3: uatu.driver.v1.Text
|
||||
(*Selector)(nil), // 4: uatu.driver.v1.Selector
|
||||
(*Image)(nil), // 5: uatu.driver.v1.Image
|
||||
(*HierarchyJSON)(nil), // 6: uatu.driver.v1.HierarchyJSON
|
||||
(*Duration)(nil), // 7: uatu.driver.v1.Duration
|
||||
(*HealthStatus)(nil), // 8: uatu.driver.v1.HealthStatus
|
||||
(*SwipeRequest)(nil), // 9: uatu.driver.v1.SwipeRequest
|
||||
(*PressKeyRequest)(nil), // 10: uatu.driver.v1.PressKeyRequest
|
||||
(*RecentLogsRequest)(nil), // 11: uatu.driver.v1.RecentLogsRequest
|
||||
(*LogEntries)(nil), // 12: uatu.driver.v1.LogEntries
|
||||
(*LogEntry)(nil), // 13: uatu.driver.v1.LogEntry
|
||||
(*MetricsRequest)(nil), // 14: uatu.driver.v1.MetricsRequest
|
||||
(*MetricsResponse)(nil), // 15: uatu.driver.v1.MetricsResponse
|
||||
(*Empty)(nil), // 0: sanderling.driver.v1.Empty
|
||||
(*LaunchRequest)(nil), // 1: sanderling.driver.v1.LaunchRequest
|
||||
(*Point)(nil), // 2: sanderling.driver.v1.Point
|
||||
(*Text)(nil), // 3: sanderling.driver.v1.Text
|
||||
(*Selector)(nil), // 4: sanderling.driver.v1.Selector
|
||||
(*Image)(nil), // 5: sanderling.driver.v1.Image
|
||||
(*HierarchyJSON)(nil), // 6: sanderling.driver.v1.HierarchyJSON
|
||||
(*Duration)(nil), // 7: sanderling.driver.v1.Duration
|
||||
(*HealthStatus)(nil), // 8: sanderling.driver.v1.HealthStatus
|
||||
(*SwipeRequest)(nil), // 9: sanderling.driver.v1.SwipeRequest
|
||||
(*PressKeyRequest)(nil), // 10: sanderling.driver.v1.PressKeyRequest
|
||||
(*RecentLogsRequest)(nil), // 11: sanderling.driver.v1.RecentLogsRequest
|
||||
(*LogEntries)(nil), // 12: sanderling.driver.v1.LogEntries
|
||||
(*LogEntry)(nil), // 13: sanderling.driver.v1.LogEntry
|
||||
(*MetricsRequest)(nil), // 14: sanderling.driver.v1.MetricsRequest
|
||||
(*MetricsResponse)(nil), // 15: sanderling.driver.v1.MetricsResponse
|
||||
}
|
||||
var file_driverpb_driver_proto_depIdxs = []int32{
|
||||
2, // 0: uatu.driver.v1.SwipeRequest.from:type_name -> uatu.driver.v1.Point
|
||||
2, // 1: uatu.driver.v1.SwipeRequest.to:type_name -> uatu.driver.v1.Point
|
||||
13, // 2: uatu.driver.v1.LogEntries.entries:type_name -> uatu.driver.v1.LogEntry
|
||||
1, // 3: uatu.driver.v1.Driver.Launch:input_type -> uatu.driver.v1.LaunchRequest
|
||||
0, // 4: uatu.driver.v1.Driver.Terminate:input_type -> uatu.driver.v1.Empty
|
||||
2, // 5: uatu.driver.v1.Driver.Tap:input_type -> uatu.driver.v1.Point
|
||||
4, // 6: uatu.driver.v1.Driver.TapSelector:input_type -> uatu.driver.v1.Selector
|
||||
3, // 7: uatu.driver.v1.Driver.InputText:input_type -> uatu.driver.v1.Text
|
||||
9, // 8: uatu.driver.v1.Driver.Swipe:input_type -> uatu.driver.v1.SwipeRequest
|
||||
10, // 9: uatu.driver.v1.Driver.PressKey:input_type -> uatu.driver.v1.PressKeyRequest
|
||||
0, // 10: uatu.driver.v1.Driver.Screenshot:input_type -> uatu.driver.v1.Empty
|
||||
0, // 11: uatu.driver.v1.Driver.Hierarchy:input_type -> uatu.driver.v1.Empty
|
||||
11, // 12: uatu.driver.v1.Driver.RecentLogs:input_type -> uatu.driver.v1.RecentLogsRequest
|
||||
7, // 13: uatu.driver.v1.Driver.WaitForIdle:input_type -> uatu.driver.v1.Duration
|
||||
0, // 14: uatu.driver.v1.Driver.Health:input_type -> uatu.driver.v1.Empty
|
||||
14, // 15: uatu.driver.v1.Driver.Metrics:input_type -> uatu.driver.v1.MetricsRequest
|
||||
0, // 16: uatu.driver.v1.Driver.Launch:output_type -> uatu.driver.v1.Empty
|
||||
0, // 17: uatu.driver.v1.Driver.Terminate:output_type -> uatu.driver.v1.Empty
|
||||
0, // 18: uatu.driver.v1.Driver.Tap:output_type -> uatu.driver.v1.Empty
|
||||
0, // 19: uatu.driver.v1.Driver.TapSelector:output_type -> uatu.driver.v1.Empty
|
||||
0, // 20: uatu.driver.v1.Driver.InputText:output_type -> uatu.driver.v1.Empty
|
||||
0, // 21: uatu.driver.v1.Driver.Swipe:output_type -> uatu.driver.v1.Empty
|
||||
0, // 22: uatu.driver.v1.Driver.PressKey:output_type -> uatu.driver.v1.Empty
|
||||
5, // 23: uatu.driver.v1.Driver.Screenshot:output_type -> uatu.driver.v1.Image
|
||||
6, // 24: uatu.driver.v1.Driver.Hierarchy:output_type -> uatu.driver.v1.HierarchyJSON
|
||||
12, // 25: uatu.driver.v1.Driver.RecentLogs:output_type -> uatu.driver.v1.LogEntries
|
||||
0, // 26: uatu.driver.v1.Driver.WaitForIdle:output_type -> uatu.driver.v1.Empty
|
||||
8, // 27: uatu.driver.v1.Driver.Health:output_type -> uatu.driver.v1.HealthStatus
|
||||
15, // 28: uatu.driver.v1.Driver.Metrics:output_type -> uatu.driver.v1.MetricsResponse
|
||||
2, // 0: sanderling.driver.v1.SwipeRequest.from:type_name -> sanderling.driver.v1.Point
|
||||
2, // 1: sanderling.driver.v1.SwipeRequest.to:type_name -> sanderling.driver.v1.Point
|
||||
13, // 2: sanderling.driver.v1.LogEntries.entries:type_name -> sanderling.driver.v1.LogEntry
|
||||
1, // 3: sanderling.driver.v1.Driver.Launch:input_type -> sanderling.driver.v1.LaunchRequest
|
||||
0, // 4: sanderling.driver.v1.Driver.Terminate:input_type -> sanderling.driver.v1.Empty
|
||||
2, // 5: sanderling.driver.v1.Driver.Tap:input_type -> sanderling.driver.v1.Point
|
||||
4, // 6: sanderling.driver.v1.Driver.TapSelector:input_type -> sanderling.driver.v1.Selector
|
||||
3, // 7: sanderling.driver.v1.Driver.InputText:input_type -> sanderling.driver.v1.Text
|
||||
9, // 8: sanderling.driver.v1.Driver.Swipe:input_type -> sanderling.driver.v1.SwipeRequest
|
||||
10, // 9: sanderling.driver.v1.Driver.PressKey:input_type -> sanderling.driver.v1.PressKeyRequest
|
||||
0, // 10: sanderling.driver.v1.Driver.Screenshot:input_type -> sanderling.driver.v1.Empty
|
||||
0, // 11: sanderling.driver.v1.Driver.Hierarchy:input_type -> sanderling.driver.v1.Empty
|
||||
11, // 12: sanderling.driver.v1.Driver.RecentLogs:input_type -> sanderling.driver.v1.RecentLogsRequest
|
||||
7, // 13: sanderling.driver.v1.Driver.WaitForIdle:input_type -> sanderling.driver.v1.Duration
|
||||
0, // 14: sanderling.driver.v1.Driver.Health:input_type -> sanderling.driver.v1.Empty
|
||||
14, // 15: sanderling.driver.v1.Driver.Metrics:input_type -> sanderling.driver.v1.MetricsRequest
|
||||
0, // 16: sanderling.driver.v1.Driver.Launch:output_type -> sanderling.driver.v1.Empty
|
||||
0, // 17: sanderling.driver.v1.Driver.Terminate:output_type -> sanderling.driver.v1.Empty
|
||||
0, // 18: sanderling.driver.v1.Driver.Tap:output_type -> sanderling.driver.v1.Empty
|
||||
0, // 19: sanderling.driver.v1.Driver.TapSelector:output_type -> sanderling.driver.v1.Empty
|
||||
0, // 20: sanderling.driver.v1.Driver.InputText:output_type -> sanderling.driver.v1.Empty
|
||||
0, // 21: sanderling.driver.v1.Driver.Swipe:output_type -> sanderling.driver.v1.Empty
|
||||
0, // 22: sanderling.driver.v1.Driver.PressKey:output_type -> sanderling.driver.v1.Empty
|
||||
5, // 23: sanderling.driver.v1.Driver.Screenshot:output_type -> sanderling.driver.v1.Image
|
||||
6, // 24: sanderling.driver.v1.Driver.Hierarchy:output_type -> sanderling.driver.v1.HierarchyJSON
|
||||
12, // 25: sanderling.driver.v1.Driver.RecentLogs:output_type -> sanderling.driver.v1.LogEntries
|
||||
0, // 26: sanderling.driver.v1.Driver.WaitForIdle:output_type -> sanderling.driver.v1.Empty
|
||||
8, // 27: sanderling.driver.v1.Driver.Health:output_type -> sanderling.driver.v1.HealthStatus
|
||||
15, // 28: sanderling.driver.v1.Driver.Metrics:output_type -> sanderling.driver.v1.MetricsResponse
|
||||
16, // [16:29] is the sub-list for method output_type
|
||||
3, // [3:16] is the sub-list for method input_type
|
||||
3, // [3:3] is the sub-list for extension type_name
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package uatu.driver.v1;
|
||||
package sanderling.driver.v1;
|
||||
|
||||
option go_package = "github.com/priyanshujain/uatu/proto/driverpb;driverpb";
|
||||
option java_package = "dev.uatu.driver.v1";
|
||||
option go_package = "github.com/priyanshujain/sanderling/proto/driverpb;driverpb";
|
||||
option java_package = "dev.sanderling.driver.v1";
|
||||
option java_multiple_files = true;
|
||||
|
||||
service Driver {
|
||||
|
||||
@@ -19,19 +19,19 @@ import (
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
Driver_Launch_FullMethodName = "/uatu.driver.v1.Driver/Launch"
|
||||
Driver_Terminate_FullMethodName = "/uatu.driver.v1.Driver/Terminate"
|
||||
Driver_Tap_FullMethodName = "/uatu.driver.v1.Driver/Tap"
|
||||
Driver_TapSelector_FullMethodName = "/uatu.driver.v1.Driver/TapSelector"
|
||||
Driver_InputText_FullMethodName = "/uatu.driver.v1.Driver/InputText"
|
||||
Driver_Swipe_FullMethodName = "/uatu.driver.v1.Driver/Swipe"
|
||||
Driver_PressKey_FullMethodName = "/uatu.driver.v1.Driver/PressKey"
|
||||
Driver_Screenshot_FullMethodName = "/uatu.driver.v1.Driver/Screenshot"
|
||||
Driver_Hierarchy_FullMethodName = "/uatu.driver.v1.Driver/Hierarchy"
|
||||
Driver_RecentLogs_FullMethodName = "/uatu.driver.v1.Driver/RecentLogs"
|
||||
Driver_WaitForIdle_FullMethodName = "/uatu.driver.v1.Driver/WaitForIdle"
|
||||
Driver_Health_FullMethodName = "/uatu.driver.v1.Driver/Health"
|
||||
Driver_Metrics_FullMethodName = "/uatu.driver.v1.Driver/Metrics"
|
||||
Driver_Launch_FullMethodName = "/sanderling.driver.v1.Driver/Launch"
|
||||
Driver_Terminate_FullMethodName = "/sanderling.driver.v1.Driver/Terminate"
|
||||
Driver_Tap_FullMethodName = "/sanderling.driver.v1.Driver/Tap"
|
||||
Driver_TapSelector_FullMethodName = "/sanderling.driver.v1.Driver/TapSelector"
|
||||
Driver_InputText_FullMethodName = "/sanderling.driver.v1.Driver/InputText"
|
||||
Driver_Swipe_FullMethodName = "/sanderling.driver.v1.Driver/Swipe"
|
||||
Driver_PressKey_FullMethodName = "/sanderling.driver.v1.Driver/PressKey"
|
||||
Driver_Screenshot_FullMethodName = "/sanderling.driver.v1.Driver/Screenshot"
|
||||
Driver_Hierarchy_FullMethodName = "/sanderling.driver.v1.Driver/Hierarchy"
|
||||
Driver_RecentLogs_FullMethodName = "/sanderling.driver.v1.Driver/RecentLogs"
|
||||
Driver_WaitForIdle_FullMethodName = "/sanderling.driver.v1.Driver/WaitForIdle"
|
||||
Driver_Health_FullMethodName = "/sanderling.driver.v1.Driver/Health"
|
||||
Driver_Metrics_FullMethodName = "/sanderling.driver.v1.Driver/Metrics"
|
||||
)
|
||||
|
||||
// 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,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var Driver_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "uatu.driver.v1.Driver",
|
||||
ServiceName: "sanderling.driver.v1.Driver",
|
||||
HandlerType: (*DriverServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
func TestDriverServiceDescriptor(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,11 @@ plugins {
|
||||
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"
|
||||
|
||||
android {
|
||||
namespace = "dev.uatu.sdk"
|
||||
namespace = "dev.sanderling.sdk"
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
@@ -61,12 +61,12 @@ mavenPublishing {
|
||||
)
|
||||
|
||||
pom {
|
||||
name.set("uatu sdk-android")
|
||||
name.set("sanderling sdk-android")
|
||||
description.set(
|
||||
"Android runtime SDK for uatu, a property-based UI fuzzer for mobile apps. " +
|
||||
"Exposes a content-provider accessibility bridge consumed by the uatu CLI at test time.",
|
||||
"Android runtime SDK for sanderling, a property-based UI fuzzer for mobile apps. " +
|
||||
"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 {
|
||||
license {
|
||||
@@ -85,9 +85,9 @@ mavenPublishing {
|
||||
}
|
||||
|
||||
scm {
|
||||
url.set("https://github.com/priyanshujain/uatu")
|
||||
connection.set("scm:git:git://github.com/priyanshujain/uatu.git")
|
||||
developerConnection.set("scm:git:ssh://[email protected]/priyanshujain/uatu.git")
|
||||
url.set("https://github.com/priyanshujain/sanderling")
|
||||
connection.set("scm:git:git://github.com/priyanshujain/sanderling.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.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package dev.uatu.sdk
|
||||
package dev.sanderling.sdk
|
||||
|
||||
import android.os.Handler
|
||||
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.StringWriter
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package dev.uatu.sdk
|
||||
package dev.sanderling.sdk
|
||||
|
||||
import android.net.LocalSocket
|
||||
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.Semaphore
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package dev.uatu.sdk
|
||||
package dev.sanderling.sdk
|
||||
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
+7
-7
@@ -1,24 +1,24 @@
|
||||
package dev.uatu.sdk
|
||||
package dev.sanderling.sdk
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
|
||||
data class Configuration(
|
||||
val socketName: String = "uatu-agent",
|
||||
val socketName: String = "sanderling-agent",
|
||||
val pauseTimeoutMillis: Long = 5_000L,
|
||||
)
|
||||
|
||||
object Uatu {
|
||||
object Sanderling {
|
||||
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
|
||||
@Synchronized
|
||||
fun start(application: Application, configuration: Configuration = Configuration()) {
|
||||
if (runtime != null) return
|
||||
val newRuntime = UatuRuntime(
|
||||
val newRuntime = SanderlingRuntime(
|
||||
transport = LocalAbstractTransport(configuration.socketName),
|
||||
pauser = Pauser(ChoreographerPoster(), configuration.pauseTimeoutMillis),
|
||||
version = VERSION,
|
||||
@@ -32,7 +32,7 @@ object Uatu {
|
||||
|
||||
fun extract(name: String, function: () -> Any?) {
|
||||
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)
|
||||
}
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
package dev.uatu.sdk
|
||||
package dev.sanderling.sdk
|
||||
|
||||
import android.util.Log
|
||||
|
||||
internal class UatuRuntime(
|
||||
internal class SanderlingRuntime(
|
||||
transport: AgentTransport,
|
||||
private val pauser: Pauser,
|
||||
private val version: String,
|
||||
@@ -45,7 +45,7 @@ internal class UatuRuntime(
|
||||
|
||||
private inner class AgentHandler : SocketClient.Handler {
|
||||
override fun onConnected(sender: SocketClient.MessageSender) {
|
||||
this@UatuRuntime.sender = sender
|
||||
this@SanderlingRuntime.sender = sender
|
||||
try {
|
||||
sender.send(Message.hello(version, platform, appPackage))
|
||||
} catch (cause: Exception) {
|
||||
@@ -92,6 +92,6 @@ internal class UatuRuntime(
|
||||
}
|
||||
|
||||
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.InputStream
|
||||
@@ -30,7 +30,7 @@ class SocketClient(
|
||||
private val transport: AgentTransport,
|
||||
private val handler: Handler,
|
||||
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) },
|
||||
) {
|
||||
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.assertTrue
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package dev.uatu.sdk
|
||||
package dev.sanderling.sdk
|
||||
|
||||
import java.util.concurrent.CountDownLatch
|
||||
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.ByteArrayOutputStream
|
||||
+7
-7
@@ -1,4 +1,4 @@
|
||||
package dev.uatu.sdk
|
||||
package dev.sanderling.sdk
|
||||
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.CountDownLatch
|
||||
@@ -12,22 +12,22 @@ import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class UatuRuntimeTest {
|
||||
class SanderlingRuntimeTest {
|
||||
|
||||
private lateinit var transport: SocketClientTest.FakeTransport
|
||||
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()
|
||||
frameThread = PauserTest.FakeFrameThread()
|
||||
val pauser = Pauser(frameThread, pauseTimeoutMillis = 2_000L)
|
||||
return UatuRuntime(
|
||||
return SanderlingRuntime(
|
||||
transport = transport,
|
||||
pauser = pauser,
|
||||
version = "0.0.1",
|
||||
platform = "android",
|
||||
appPackage = "com.example.uatu_test",
|
||||
appPackage = "com.example.sanderling_test",
|
||||
).also { runtime = it }
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class UatuRuntimeTest {
|
||||
assertEquals(MessageType.HELLO, hello.type)
|
||||
assertEquals("0.0.1", hello.version)
|
||||
assertEquals("android", hello.platform)
|
||||
assertEquals("com.example.uatu_test", hello.appPackage)
|
||||
assertEquals("com.example.sanderling_test", hello.appPackage)
|
||||
}
|
||||
|
||||
@Test fun pauseTriggersExtractorsAndReturnsState() {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package dev.uatu.sdk
|
||||
package dev.sanderling.sdk
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.PipedInputStream
|
||||
+1
-1
@@ -14,7 +14,7 @@ dependencyResolutionManagement {
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "uatu"
|
||||
rootProject.name = "sanderling"
|
||||
|
||||
include(":sidecar")
|
||||
include(":sdk-android")
|
||||
|
||||
@@ -7,7 +7,7 @@ plugins {
|
||||
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 {
|
||||
toolchain {
|
||||
@@ -70,7 +70,7 @@ sourceSets {
|
||||
}
|
||||
|
||||
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/
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package dev.uatu.sidecar
|
||||
package dev.sanderling.sidecar
|
||||
|
||||
interface DriverBackend {
|
||||
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 dev.uatu.driver.v1.DriverGrpc
|
||||
import dev.uatu.driver.v1.Duration
|
||||
import dev.uatu.driver.v1.Empty
|
||||
import dev.uatu.driver.v1.HealthStatus
|
||||
import dev.uatu.driver.v1.HierarchyJSON
|
||||
import dev.uatu.driver.v1.Image
|
||||
import dev.uatu.driver.v1.LaunchRequest
|
||||
import dev.uatu.driver.v1.LogEntries
|
||||
import dev.uatu.driver.v1.LogEntry
|
||||
import dev.uatu.driver.v1.MetricsRequest
|
||||
import dev.uatu.driver.v1.MetricsResponse
|
||||
import dev.uatu.driver.v1.Point
|
||||
import dev.uatu.driver.v1.PressKeyRequest
|
||||
import dev.uatu.driver.v1.RecentLogsRequest
|
||||
import dev.uatu.driver.v1.Selector
|
||||
import dev.uatu.driver.v1.SwipeRequest
|
||||
import dev.uatu.driver.v1.Text
|
||||
import dev.sanderling.driver.v1.DriverGrpc
|
||||
import dev.sanderling.driver.v1.Duration
|
||||
import dev.sanderling.driver.v1.Empty
|
||||
import dev.sanderling.driver.v1.HealthStatus
|
||||
import dev.sanderling.driver.v1.HierarchyJSON
|
||||
import dev.sanderling.driver.v1.Image
|
||||
import dev.sanderling.driver.v1.LaunchRequest
|
||||
import dev.sanderling.driver.v1.LogEntries
|
||||
import dev.sanderling.driver.v1.LogEntry
|
||||
import dev.sanderling.driver.v1.MetricsRequest
|
||||
import dev.sanderling.driver.v1.MetricsResponse
|
||||
import dev.sanderling.driver.v1.Point
|
||||
import dev.sanderling.driver.v1.PressKeyRequest
|
||||
import dev.sanderling.driver.v1.RecentLogsRequest
|
||||
import dev.sanderling.driver.v1.Selector
|
||||
import dev.sanderling.driver.v1.SwipeRequest
|
||||
import dev.sanderling.driver.v1.Text
|
||||
import io.grpc.stub.StreamObserver
|
||||
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