mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
test(runner): cover apply-time foreground skip and appIsForeground table
Adds a Run-level test asserting no tap reaches the driver while a system overlay holds focus (guards against the skip branch being dead-coded), plus a decision-table test for appIsForeground. Adds ForegroundErr/FocusedWindowErr to the mock driver so the guard's transient-read paths are exercised.
This commit is contained in:
1 parent
663f9e7180
commit
d45f5d4869
2 files changed
+84
No files matched your search
@@ -73,6 +73,11 @@ type Driver struct {
|
||||
ForegroundResults []string
|
||||
foregroundIndex int
|
||||
|
||||
// ForegroundErr and FocusedWindowErr, when set, make the respective check
|
||||
// return that error so tests can cover the guard's transient-read paths.
|
||||
ForegroundErr error
|
||||
FocusedWindowErr error
|
||||
|
||||
// FocusedWindowResults is consumed one entry per FocusedWindowApp call
|
||||
// (the last entry repeats). When empty, FocusedWindowApp mirrors the
|
||||
// last ForegroundApp result, so the startup gate treats the window as
|
||||
@@ -125,6 +130,9 @@ func (d *Driver) Launch(_ context.Context, bundleID string, clearState bool, _ m
|
||||
func (d *Driver) ForegroundApp(_ context.Context) (string, error) {
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
if d.ForegroundErr != nil {
|
||||
return "", d.ForegroundErr
|
||||
}
|
||||
if len(d.ForegroundResults) == 0 {
|
||||
d.lastForeground = ""
|
||||
return "", nil
|
||||
@@ -142,6 +150,9 @@ func (d *Driver) FocusedWindowApp(_ context.Context) (string, error) {
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
d.focusedWindowCalls++
|
||||
if d.FocusedWindowErr != nil {
|
||||
return "", d.FocusedWindowErr
|
||||
}
|
||||
if len(d.FocusedWindowResults) == 0 {
|
||||
return d.lastForeground, nil
|
||||
}
|
||||
|
||||
@@ -1951,3 +1951,76 @@ func TestEnsureForeground_DismissesSystemOverlay(t *testing.T) {
|
||||
t.Fatalf("a resumed-but-obscured app must not be relaunched, got %d relaunches", relaunches)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAppIsForeground covers the apply-time guard's decision table, including
|
||||
// the bug fix: the app being the resumed activity is not enough; a system
|
||||
// overlay owning the focused window means the action must be skipped. Unknown
|
||||
// signals (no bundle, empty foreground, a transient read error) return true so
|
||||
// the run is never blocked where the signal is unavailable.
|
||||
func TestAppIsForeground(t *testing.T) {
|
||||
readErr := errors.New("adb read failed")
|
||||
cases := []struct {
|
||||
name string
|
||||
bundleID string
|
||||
foreground []string
|
||||
foregErr error
|
||||
focused []string
|
||||
focusErr error
|
||||
want bool
|
||||
}{
|
||||
{name: "no bundle id", bundleID: "", foreground: []string{"app.folio"}, want: true},
|
||||
{name: "foreground unknown", bundleID: "app.folio", foreground: nil, want: true},
|
||||
{name: "foreground read error", bundleID: "app.folio", foregErr: readErr, want: true},
|
||||
{name: "foreign foreground", bundleID: "app.folio", foreground: []string{"com.android.chrome"}, want: false},
|
||||
{name: "app resumed and focused", bundleID: "app.folio", foreground: []string{"app.folio"}, focused: []string{"app.folio"}, want: true},
|
||||
{name: "app resumed but overlay focused", bundleID: "app.folio", foreground: []string{"app.folio"}, focused: []string{"com.android.systemui"}, want: false},
|
||||
{name: "app resumed, focus unknown", bundleID: "app.folio", foreground: []string{"app.folio"}, focused: []string{""}, want: true},
|
||||
{name: "app resumed, focus read error", bundleID: "app.folio", foreground: []string{"app.folio"}, focusErr: readErr, want: true},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
m := mockdriver.New()
|
||||
m.ForegroundResults = tc.foreground
|
||||
m.ForegroundErr = tc.foregErr
|
||||
m.FocusedWindowResults = tc.focused
|
||||
m.FocusedWindowErr = tc.focusErr
|
||||
options := Options{BundleID: tc.bundleID, Driver: m}
|
||||
if got := appIsForeground(context.Background(), options); got != tc.want {
|
||||
t.Errorf("appIsForeground = %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunner_SkipsActionWhenOverlayStealsFocusAtApplyTime locks the apply-time
|
||||
// half of the scope guard end to end: when a system overlay owns the focused
|
||||
// window while the app stays the resumed activity, the chosen action must be
|
||||
// dropped rather than dispatched onto the overlay. The fixture spec taps id:next
|
||||
// every step; with the guard working, none of those taps reach the driver.
|
||||
func TestRunner_SkipsActionWhenOverlayStealsFocusAtApplyTime(t *testing.T) {
|
||||
state := newHarness(t)
|
||||
state.mock.ForegroundResults = []string{"app.folio"}
|
||||
state.mock.FocusedWindowResults = []string{"app.folio", "com.android.systemui"}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
summary, err := Run(ctx, Options{
|
||||
Duration: 100 * time.Millisecond,
|
||||
IdleTimeout: 20 * time.Millisecond,
|
||||
BundleID: "app.folio",
|
||||
Driver: state.mock,
|
||||
Verifier: state.verifier,
|
||||
TraceWriter: state.writer,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Run: %v", err)
|
||||
}
|
||||
if summary.Steps == 0 {
|
||||
t.Fatal("expected the loop to run steps")
|
||||
}
|
||||
// The only thing standing between the picked action and the driver is the
|
||||
// apply-time guard; a dispatched tap means it failed (e.g. dead-coded).
|
||||
if containsAction(state.mock.Actions(), mockdriver.ActionTapSelector, "id:next") {
|
||||
t.Error("apply-time guard failed: a tap fired while a system overlay held focus")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user