mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
* fix(runner): surface non-deadline WaitForIdle errors Previously the WaitForIdle return value was discarded entirely, hiding real driver failures (gRPC transport errors, sidecar crashes) behind the expected deadline-exceeded case. Log non-deadline errors so they are visible without changing control flow. * chore(sample-app): drop unused uptime_millis extractor Registered in SampleApplication but never consumed by spec.ts. * fix(sample-app): drop trivial appIsRunning property app_state was hardcoded to 'running' so the property was a tautology that could never fail. Removing both the extractor and the property is the simplest fix; demo-grade properties that can fail land next. * feat(sample-app): add Reset button that zeroes clickCount Pairs with the next commit's tap-reset action so the fuzzer can violate clickCountNeverDecreases and demonstrate uatu actually finding a property violation. * feat(sample-app): add tap-reset action to exercise Reset button Weighted at 10/122, fuzzer reaches it within a short run. Pairs with the Reset button to demonstrate uatu detecting the clickCountNeverDecreases violation. * fix(runner): filter WaitForIdle errors via context state, not errors.Is errors.Is(err, context.DeadlineExceeded) misses gRPC's wrapped status.DeadlineExceeded, so every step under the maestro driver logged a spurious warning. Check idleCtx.Err() instead — captures both deadline-fired and parent-canceled cases regardless of how the driver wraps them. * chore(sample-app): tune action weights so demo violates in ~30s Prior weights left tap-reset rare enough that short demo runs missed the violation by chance. Bumped to 30/107, with typeUsername reduced since username noise doesn't help exercise clickCount. * refactor(runner): route warnings through slog Adds Options.Logger (defaults to slog.Default()) and converts the three warning sites that were using fmt.Printf. Progress line stays on Printf since it's user-facing UI, not a log. Makes the warnings testable via a capturing handler. * test(runner): assert WaitForIdle driver errors are logged Captures slog output via TextHandler into a buffer and asserts the warning message + injected error text appear when the mock driver returns a non-context error from WaitForIdle. Guards against a regression of the silent-error swallow.
64 lines
2.2 KiB
TypeScript
64 lines
2.2 KiB
TypeScript
import {
|
|
extract,
|
|
always,
|
|
actions,
|
|
weighted,
|
|
Tap,
|
|
InputText,
|
|
taps,
|
|
swipes,
|
|
} from "@uatu/spec";
|
|
|
|
// ── Snapshot extractors (fed by SampleApplication.kt) ──────────
|
|
// See ./android/src/main/kotlin/dev/uatu/sample/SampleApplication.kt
|
|
const clickCount = extract<number>(
|
|
(state) => (state.snapshots.click_count as number) ?? 0,
|
|
);
|
|
const username = extract<string>(
|
|
(state) => (state.snapshots.username as string) ?? "",
|
|
);
|
|
|
|
// ── UI elements ────────────────────────────────────────────────
|
|
const clickButton = extract((state) => state.ax.find("text:Click me"));
|
|
const resetButton = extract((state) => state.ax.find("text:Reset"));
|
|
const usernameField = extract((state) => state.ax.find("desc:username_field"));
|
|
|
|
// ── Properties ─────────────────────────────────────────────────
|
|
export const properties = {
|
|
clickCountNonNegative: always(() => clickCount.current >= 0),
|
|
clickCountNeverDecreases: always(() => {
|
|
const previous = clickCount.previous;
|
|
return previous === undefined || clickCount.current >= previous;
|
|
}),
|
|
usernameNeverShrinks: always(() => {
|
|
const previous = username.previous;
|
|
return previous === undefined || username.current.length >= previous.length;
|
|
}),
|
|
};
|
|
|
|
// ── Actions ────────────────────────────────────────────────────
|
|
const tapClickMe = actions(() => {
|
|
return clickButton.current ? [Tap({ on: clickButton.current })] : [];
|
|
});
|
|
|
|
const typeUsername = actions(() => {
|
|
return usernameField.current
|
|
? [InputText({ into: usernameField.current, text: "alice" })]
|
|
: [];
|
|
});
|
|
|
|
const tapReset = actions(() => {
|
|
return resetButton.current ? [Tap({ on: resetButton.current })] : [];
|
|
});
|
|
|
|
export const actionsRoot = weighted(
|
|
[50, tapClickMe],
|
|
[20, typeUsername],
|
|
[30, tapReset],
|
|
[5, taps],
|
|
[2, swipes],
|
|
);
|
|
|
|
(globalThis as { actions?: unknown; properties?: unknown }).actions = actionsRoot;
|
|
(globalThis as { properties?: unknown }).properties = properties;
|