mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-04 20:17:09 +00:00
perf(ioscompanion): count read spans toward settle and capture snapshots concurrently
This commit is contained in:
1 parent
6c90681b83
commit
398a05278b
3 files changed
+51
-25
No files matched your search
@@ -741,17 +741,27 @@ func (d *Driver) Screenshot(ctx context.Context) (driver.Image, error) {
|
|||||||
func (d *Driver) Snapshot(ctx context.Context) (string, driver.Image, error) {
|
func (d *Driver) Snapshot(ctx context.Context) (string, driver.Image, error) {
|
||||||
d.mu.Lock()
|
d.mu.Lock()
|
||||||
defer d.mu.Unlock()
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
|
// The hierarchy and the screenshot ride different transports on the
|
||||||
|
// hybrid path, so they are captured concurrently. Only the hierarchy leg
|
||||||
|
// runs under withRecovery: two concurrent recoveries would race the
|
||||||
|
// restart bookkeeping, and a screenshot connection failure surfaces as a
|
||||||
|
// plain error that the next serialized call recovers from.
|
||||||
|
var data []byte
|
||||||
|
screenshotDone := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
imageData, _, callErr := d.companion.Screenshot(ctx)
|
||||||
|
data = imageData
|
||||||
|
screenshotDone <- callErr
|
||||||
|
}()
|
||||||
|
|
||||||
dump, err := d.describeAll(ctx)
|
dump, err := d.describeAll(ctx)
|
||||||
|
screenshotErr := <-screenshotDone
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", driver.Image{}, err
|
return "", driver.Image{}, err
|
||||||
}
|
}
|
||||||
var data []byte
|
if screenshotErr != nil {
|
||||||
if err := d.withRecovery(ctx, func() error {
|
return "", driver.Image{}, fmt.Errorf("screenshot: %w", screenshotErr)
|
||||||
var screenshotErr error
|
|
||||||
data, _, screenshotErr = d.companion.Screenshot(ctx)
|
|
||||||
return screenshotErr
|
|
||||||
}); err != nil {
|
|
||||||
return "", driver.Image{}, fmt.Errorf("screenshot: %w", err)
|
|
||||||
}
|
}
|
||||||
mapped, err := MapHierarchy(dump, d.screenWidth, d.screenHeight)
|
mapped, err := MapHierarchy(dump, d.screenWidth, d.screenHeight)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -27,6 +28,9 @@ import (
|
|||||||
// the order of calls and returns scripted results, so the driver's decision
|
// the order of calls and returns scripted results, so the driver's decision
|
||||||
// logic is testable without a live simulator.
|
// logic is testable without a live simulator.
|
||||||
type fakeCompanion struct {
|
type fakeCompanion struct {
|
||||||
|
// callsMutex guards calls: Snapshot captures the hierarchy and the
|
||||||
|
// screenshot concurrently.
|
||||||
|
callsMutex sync.Mutex
|
||||||
calls []string
|
calls []string
|
||||||
|
|
||||||
accessibilityJSON string
|
accessibilityJSON string
|
||||||
@@ -40,7 +44,19 @@ type fakeCompanion struct {
|
|||||||
hidErr error
|
hidErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeCompanion) record(name string) { f.calls = append(f.calls, name) }
|
func (f *fakeCompanion) record(name string) {
|
||||||
|
f.callsMutex.Lock()
|
||||||
|
defer f.callsMutex.Unlock()
|
||||||
|
f.calls = append(f.calls, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeCompanion) recorded() []string {
|
||||||
|
f.callsMutex.Lock()
|
||||||
|
defer f.callsMutex.Unlock()
|
||||||
|
out := make([]string, len(f.calls))
|
||||||
|
copy(out, f.calls)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func (f *fakeCompanion) AccessibilityInfo(context.Context) (string, error) {
|
func (f *fakeCompanion) AccessibilityInfo(context.Context) (string, error) {
|
||||||
f.record("accessibility")
|
f.record("accessibility")
|
||||||
@@ -218,7 +234,7 @@ func TestLaunchRejectsEnvironment(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSnapshotPairsHierarchyThenScreenshot(t *testing.T) {
|
func TestSnapshotPairsHierarchyAndScreenshot(t *testing.T) {
|
||||||
companion := &fakeCompanion{
|
companion := &fakeCompanion{
|
||||||
accessibilityJSON: "[]",
|
accessibilityJSON: "[]",
|
||||||
screenshotData: samplePNG(t, 390, 844),
|
screenshotData: samplePNG(t, 390, 844),
|
||||||
@@ -231,8 +247,11 @@ func TestSnapshotPairsHierarchyThenScreenshot(t *testing.T) {
|
|||||||
if image.Width != 390 || image.Height != 844 {
|
if image.Width != 390 || image.Height != 844 {
|
||||||
t.Fatalf("image dims = %dx%d, want 390x844", image.Width, image.Height)
|
t.Fatalf("image dims = %dx%d, want 390x844", image.Width, image.Height)
|
||||||
}
|
}
|
||||||
if indexOf(companion.calls, "accessibility") > indexOf(companion.calls, "screenshot") {
|
// The two captures run concurrently (they ride different transports on
|
||||||
t.Fatalf("accessibility must precede screenshot; got %v", companion.calls)
|
// the hybrid path), so both must happen but in no particular order.
|
||||||
|
calls := companion.recorded()
|
||||||
|
if indexOf(calls, "accessibility") < 0 || indexOf(calls, "screenshot") < 0 {
|
||||||
|
t.Fatalf("snapshot must capture hierarchy and screenshot; got %v", calls)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,33 +56,30 @@ func SystemClock() Clock { return systemClock{} }
|
|||||||
// capped at StabilityPollCap. fetch returns the current hierarchy tree (nil on
|
// capped at StabilityPollCap. fetch returns the current hierarchy tree (nil on
|
||||||
// fetch failure, treated like a transitional snapshot so the streak resets).
|
// fetch failure, treated like a transitional snapshot so the streak resets).
|
||||||
//
|
//
|
||||||
// The loop mirrors the companion's JVM implementation: it samples a prior
|
// The stable stretch is measured from the start of the earliest read in the
|
||||||
// snapshot, then on each tick sleeps the poll interval, fetches a fresh
|
// current run of identical snapshots: a fetch is not instantaneous (a runner
|
||||||
// snapshot, and grows the streak only while consecutive snapshots are both
|
// snapshot takes a fair fraction of the streak itself), and the UI changing
|
||||||
// non-transitional and structurally identical. A transitional snapshot, a
|
// mid-read would change the snapshot, so the read's own duration is evidence
|
||||||
// changed snapshot, or a fetch failure resets the streak. The function returns
|
// of stability. A transitional snapshot, a changed snapshot, or a fetch
|
||||||
// when the streak reaches MinStableStreak or the cap elapses, and respects
|
// failure resets the run. The function returns when the stretch reaches
|
||||||
// context cancellation.
|
// MinStableStreak or the cap elapses, and respects context cancellation.
|
||||||
func PollUntilStable(ctx context.Context, clock Clock, fetch func() *hierarchy.Tree) {
|
func PollUntilStable(ctx context.Context, clock Clock, fetch func() *hierarchy.Tree) {
|
||||||
deadline := clock.Now().Add(StabilityPollCap)
|
deadline := clock.Now().Add(StabilityPollCap)
|
||||||
|
runStart := clock.Now()
|
||||||
prior := snapshot(fetch)
|
prior := snapshot(fetch)
|
||||||
var streakStart time.Time
|
|
||||||
for clock.Now().Before(deadline) {
|
for clock.Now().Before(deadline) {
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
clock.Sleep(StabilityPollInterval)
|
clock.Sleep(StabilityPollInterval)
|
||||||
|
currentStart := clock.Now()
|
||||||
current := snapshot(fetch)
|
current := snapshot(fetch)
|
||||||
now := clock.Now()
|
|
||||||
if prior.valid && current.valid && prior.hash == current.hash {
|
if prior.valid && current.valid && prior.hash == current.hash {
|
||||||
if streakStart.IsZero() {
|
if clock.Now().Sub(runStart) >= MinStableStreak {
|
||||||
streakStart = now
|
|
||||||
}
|
|
||||||
if now.Sub(streakStart) >= MinStableStreak {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
streakStart = time.Time{}
|
runStart = currentStart
|
||||||
}
|
}
|
||||||
prior = current
|
prior = current
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user