mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
docs: correct the false statements on DeviceDriver and in the runner
Snapshot is not atomic on any driver, only the sidecar takes a lock. An empty minLevel is not a default: the three implementations genuinely disagree, so the comment names that and says the runner always passes one. HeapBytes is RSS only on Android. The startup gate uses FocusedWindowChecker on top of ForegroundChecker, never instead. WebAction names no type in the tree. Run relaunches the app it says the caller must launch, and the errgroup propagates no error because all three goroutines return nil.
This commit is contained in:
1 parent
3caf53cfb6
commit
658574b568
2 files changed
+30
-37
No files matched your search
+18
-18
@@ -52,16 +52,16 @@ type DeviceDriver interface {
|
|||||||
|
|
||||||
Hierarchy(ctx context.Context) (string, error)
|
Hierarchy(ctx context.Context) (string, error)
|
||||||
Screenshot(ctx context.Context) (Image, error)
|
Screenshot(ctx context.Context) (Image, error)
|
||||||
// Snapshot returns the hierarchy and screenshot captured back-to-back
|
// Snapshot returns the hierarchy and screenshot as one paired capture,
|
||||||
// under a backend-side mutex, so the pair describes the same on-device
|
// the closest a driver can put them and the reason to prefer it over
|
||||||
// frame. Prefer this over calling Hierarchy and Screenshot separately:
|
// separate Hierarchy and Screenshot reads. It is not atomic: no driver
|
||||||
// independent reads can land on different frames during transitions.
|
// freezes the frame while the two reads run.
|
||||||
Snapshot(ctx context.Context) (string, Image, error)
|
Snapshot(ctx context.Context) (string, Image, error)
|
||||||
// RecentLogs returns log entries at or after `since`, filtered to
|
// RecentLogs returns log entries at or after `since`, filtered to
|
||||||
// `minLevel` or above. An empty minLevel defaults to "E". A driver with
|
// `minLevel` or above. Drivers disagree on an empty minLevel (sidecar
|
||||||
// no log source returns ErrNotSupported rather than an empty slice: the
|
// defaults to "E", Chrome returns every level), so the runner always
|
||||||
// two are the same answer to a spec reading state.logs, and only one of
|
// passes one. A driver with no log source returns ErrNotSupported, not
|
||||||
// them means the app logged nothing.
|
// an empty slice: only one of the two means the app logged nothing.
|
||||||
RecentLogs(ctx context.Context, since time.Time, minLevel string) ([]LogEntry, error)
|
RecentLogs(ctx context.Context, since time.Time, minLevel string) ([]LogEntry, error)
|
||||||
|
|
||||||
WaitForIdle(ctx context.Context, duration time.Duration) error
|
WaitForIdle(ctx context.Context, duration time.Duration) error
|
||||||
@@ -70,10 +70,11 @@ type DeviceDriver interface {
|
|||||||
// verdict, so reporting it true without one tells the caller a check
|
// verdict, so reporting it true without one tells the caller a check
|
||||||
// passed that never ran.
|
// passed that never ran.
|
||||||
Health(ctx context.Context) (Health, error)
|
Health(ctx context.Context) (Health, error)
|
||||||
// Metrics samples the app's CPU and memory at the time of the call.
|
// Metrics samples the app's memory, plus CPU where the platform exposes
|
||||||
// CPUPercent is percent of a single core (multi-core apps can exceed
|
// it (Chrome does not). CPUPercent is percent of a single core, so
|
||||||
// 100). HeapBytes is resident set size; TotalMemoryBytes includes
|
// multi-core apps can exceed 100. HeapBytes and TotalMemoryBytes are the
|
||||||
// native allocations. A driver that cannot sample returns
|
// platform's nearest pair: RSS and virtual size on Android, used and
|
||||||
|
// total JS heap on Chrome. A driver that cannot sample returns
|
||||||
// ErrNotSupported rather than a zeroed sample.
|
// ErrNotSupported rather than a zeroed sample.
|
||||||
Metrics(ctx context.Context, bundleID string) (Metrics, error)
|
Metrics(ctx context.Context, bundleID string) (Metrics, error)
|
||||||
}
|
}
|
||||||
@@ -115,11 +116,10 @@ type TextReplacer interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FocusedWindowChecker is the optional capability for reporting which app owns
|
// FocusedWindowChecker is the optional capability for reporting which app owns
|
||||||
// the focused (on-screen) window. The startup gate prefers it over
|
// the focused (on-screen) window. The startup gate uses it on top of
|
||||||
// ForegroundChecker: the resumed-activity signal flips to a freshly launched
|
// ForegroundChecker, never instead: the resumed-activity signal flips to a
|
||||||
// app before its first frame draws, so observing on it alone can capture the
|
// freshly launched app before its first frame draws, so a gate on that alone
|
||||||
// previous app's screen. The focused window only names the app once its window
|
// can let the first observe read the previous app's screen.
|
||||||
// is actually up.
|
|
||||||
type FocusedWindowChecker interface {
|
type FocusedWindowChecker interface {
|
||||||
// FocusedWindowApp returns the package owning the focused window, or ""
|
// FocusedWindowApp returns the package owning the focused window, or ""
|
||||||
// when no window is focused yet (e.g. mid-launch transition).
|
// when no window is focused yet (e.g. mid-launch transition).
|
||||||
@@ -193,7 +193,7 @@ type Metrics struct {
|
|||||||
// mobile drivers stay binary-compatible by simply not implementing it.
|
// mobile drivers stay binary-compatible by simply not implementing it.
|
||||||
//
|
//
|
||||||
// Element references never cross V8/host. V8 serializes targets as {x, y}
|
// Element references never cross V8/host. V8 serializes targets as {x, y}
|
||||||
// (or bounds) into the returned WebAction JSON; the host dispatches via the
|
// (or bounds) into the returned action JSON; the host dispatches via the
|
||||||
// normal DeviceDriver methods (Tap, InputText, etc.).
|
// normal DeviceDriver methods (Tap, InputText, etc.).
|
||||||
type WebDriver interface {
|
type WebDriver interface {
|
||||||
// InstallBundle injects the given JS source so it runs once per
|
// InstallBundle injects the given JS source so it runs once per
|
||||||
|
|||||||
+12
-19
@@ -98,8 +98,8 @@ type ViolationRecord struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run drives the evaluate/act loop until the duration elapses or the context
|
// Run drives the evaluate/act loop until the duration elapses or the context
|
||||||
// is canceled. The caller is responsible for launching the app before Run is
|
// is canceled. It relaunches the app whenever the foreground check says it is
|
||||||
// called and for terminating it afterwards.
|
// not on top; the caller terminates it afterwards.
|
||||||
func Run(ctx context.Context, options Options) (Summary, error) {
|
func Run(ctx context.Context, options Options) (Summary, error) {
|
||||||
if err := validate(options); err != nil {
|
if err := validate(options); err != nil {
|
||||||
return Summary{}, err
|
return Summary{}, err
|
||||||
@@ -192,16 +192,11 @@ func Run(ctx context.Context, options Options) (Summary, error) {
|
|||||||
var logs []verifier.LogEntry
|
var logs []verifier.LogEntry
|
||||||
var logsRead, metricsRead bool
|
var logsRead, metricsRead bool
|
||||||
|
|
||||||
// gctx is bound to the errgroup so a returned error (or outer
|
// gctx carries observationTimeout so a read that never answers ends
|
||||||
// cancellation) propagates to every sibling read rather than leaving
|
// the step instead of the run.
|
||||||
// one blocked on a hung device, and to observationTimeout so a read
|
|
||||||
// that never answers ends the step instead of the run.
|
|
||||||
observeCtx, observeCancel := context.WithTimeout(ctx, observationTimeout)
|
observeCtx, observeCancel := context.WithTimeout(ctx, observationTimeout)
|
||||||
g, gctx := errgroup.WithContext(observeCtx)
|
g, gctx := errgroup.WithContext(observeCtx)
|
||||||
si := stepIndex
|
si := stepIndex
|
||||||
// fetchSyncedState issues a single Snapshot RPC so hierarchy and
|
|
||||||
// screenshot describe the same frame, then re-fetches the pair
|
|
||||||
// while the tree still looks transitional.
|
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
tree, screenshotPNG, transitional, hierarchyErr = fetchSyncedState(
|
tree, screenshotPNG, transitional, hierarchyErr = fetchSyncedState(
|
||||||
gctx, options, logger, si, rereadHierarchy)
|
gctx, options, logger, si, rereadHierarchy)
|
||||||
@@ -242,7 +237,7 @@ func Run(ctx context.Context, options Options) (Summary, error) {
|
|||||||
if tree != nil {
|
if tree != nil {
|
||||||
treeSize = len(tree.Elements)
|
treeSize = len(tree.Elements)
|
||||||
}
|
}
|
||||||
// A nil or empty tree means the sidecar's hierarchy fetch failed or
|
// A nil or empty tree means the driver's hierarchy fetch failed or
|
||||||
// returned nothing (e.g. transient device-side timeout). Pushing it
|
// returned nothing (e.g. transient device-side timeout). Pushing it
|
||||||
// would let spec extractors call findAll() and chain .map() on a null
|
// would let spec extractors call findAll() and chain .map() on a null
|
||||||
// result; treat it like a transitional capture so the verifier is
|
// result; treat it like a transitional capture so the verifier is
|
||||||
@@ -1467,12 +1462,11 @@ const (
|
|||||||
// pair shows the same UI moment. If the hierarchy looks like a NavHost
|
// pair shows the same UI moment. If the hierarchy looks like a NavHost
|
||||||
// cross-fade (multiple route-level *Screen tags), the function waits briefly
|
// cross-fade (multiple route-level *Screen tags), the function waits briefly
|
||||||
// and re-fetches the pair, up to transitionalRetryAttempts times. This
|
// and re-fetches the pair, up to transitionalRetryAttempts times. This
|
||||||
// handles transitions whose async work begins after the sidecar's settle
|
// handles transitions whose async work begins after the driver's settle
|
||||||
// poll has already exited.
|
// poll has already exited.
|
||||||
//
|
//
|
||||||
// The driver's Snapshot RPC captures both reads under a backend-side mutex
|
// Snapshot pairs the two reads as closely as the driver can, not atomically;
|
||||||
// so they describe the same on-device frame; the retry exists for the
|
// the retry exists for the orthogonal case where the frame is transitional.
|
||||||
// orthogonal case where the frame itself is transitional.
|
|
||||||
//
|
//
|
||||||
// The transitional return reports whether the retry budget was exhausted
|
// The transitional return reports whether the retry budget was exhausted
|
||||||
// on a still-transitional tree, or (when reread is set) whether a second
|
// on a still-transitional tree, or (when reread is set) whether a second
|
||||||
@@ -1879,11 +1873,10 @@ func applyBound(action verifier.Action) time.Duration {
|
|||||||
return applyTimeout + time.Duration(action.DurationMillis)*time.Millisecond
|
return applyTimeout + time.Duration(action.DurationMillis)*time.Millisecond
|
||||||
}
|
}
|
||||||
|
|
||||||
// isWDADrop reports that the sidecar could not restart the iOS XCTest
|
// isWDADrop matches the phrase the JVM sidecar's WdaRecovery threw when it
|
||||||
// runner: the channel is gone for good and the run must abort. Transient
|
// could not restart the iOS XCTest runner. iOS no longer routes through the
|
||||||
// drops are classified by the sidecar itself (it reconnects and surfaces
|
// sidecar and nothing constructs WdaRecovery any more, so no run reaches this
|
||||||
// UNAVAILABLE), so matching on raw exception text like "ConnectException"
|
// path; the iOS driver restarts its own companion instead (withRecovery).
|
||||||
// here would kill runs the sidecar already recovered.
|
|
||||||
func isWDADrop(err error) bool {
|
func isWDADrop(err error) bool {
|
||||||
return strings.Contains(err.Error(), "WDA reconnect failed")
|
return strings.Contains(err.Error(), "WDA reconnect failed")
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user