mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-04 20:17:09 +00:00
refactor(runner): remove agent SDK connection and snapshot step
This commit is contained in:
1 parent
71d88a6827
commit
6e06f54d90
1 file changed
+18
-105
+18
-105
@@ -11,7 +11,6 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
"github.com/priyanshujain/sanderling/internal/agent"
|
|
||||||
"github.com/priyanshujain/sanderling/internal/driver"
|
"github.com/priyanshujain/sanderling/internal/driver"
|
||||||
"github.com/priyanshujain/sanderling/internal/hierarchy"
|
"github.com/priyanshujain/sanderling/internal/hierarchy"
|
||||||
"github.com/priyanshujain/sanderling/internal/ltl"
|
"github.com/priyanshujain/sanderling/internal/ltl"
|
||||||
@@ -20,12 +19,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Duration time.Duration
|
Duration time.Duration
|
||||||
SnapshotTimeout time.Duration
|
IdleTimeout time.Duration
|
||||||
IdleTimeout time.Duration
|
|
||||||
|
|
||||||
BundleID string
|
BundleID string
|
||||||
Connection *agent.Conn
|
|
||||||
Driver driver.DeviceDriver
|
Driver driver.DeviceDriver
|
||||||
Verifier *verifier.Verifier
|
Verifier *verifier.Verifier
|
||||||
TraceWriter *trace.Writer
|
TraceWriter *trace.Writer
|
||||||
@@ -44,10 +41,9 @@ type ViolationRecord struct {
|
|||||||
Properties []string
|
Properties []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run drives the snapshot/evaluate/release/act loop until the duration
|
// Run drives the evaluate/act loop until the duration elapses or the context
|
||||||
// elapses or the context is canceled. The caller is responsible for
|
// is canceled. The caller is responsible for launching the app before Run is
|
||||||
// launching the app and connecting the SDK before Run is called, and for
|
// called and for terminating it afterwards.
|
||||||
// terminating the app 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
|
||||||
@@ -71,10 +67,8 @@ func Run(ctx context.Context, options Options) (Summary, error) {
|
|||||||
stepIndex++
|
stepIndex++
|
||||||
stepStart := time.Now()
|
stepStart := time.Now()
|
||||||
|
|
||||||
// Hierarchy, metrics, and logs are independent device reads. Run
|
// Hierarchy, metrics, and logs are independent device reads — run
|
||||||
// them concurrently so metrics+logs hide behind the hierarchy
|
// them concurrently so metrics+logs hide behind the hierarchy fetch.
|
||||||
// fetch (~2s). All three must finish before snapshotStep pauses
|
|
||||||
// the SDK.
|
|
||||||
var tree *hierarchy.Tree
|
var tree *hierarchy.Tree
|
||||||
var hierarchyErr error
|
var hierarchyErr error
|
||||||
var metrics *trace.Metrics
|
var metrics *trace.Metrics
|
||||||
@@ -115,31 +109,20 @@ func Run(ctx context.Context, options Options) (Summary, error) {
|
|||||||
if tree != nil {
|
if tree != nil {
|
||||||
treeSize = len(tree.Elements)
|
treeSize = len(tree.Elements)
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshot, err := snapshotStep(ctx, options)
|
|
||||||
if err != nil {
|
|
||||||
return summary, fmt.Errorf("step %d snapshot: %w", stepIndex, err)
|
|
||||||
}
|
|
||||||
lastLogTime = stepStart
|
lastLogTime = stepStart
|
||||||
|
|
||||||
exceptions := decodeExceptions(snapshot)
|
|
||||||
|
|
||||||
if err := options.Verifier.PushSnapshot(verifier.SnapshotInput{
|
if err := options.Verifier.PushSnapshot(verifier.SnapshotInput{
|
||||||
Snapshots: verifier.Snapshots(snapshot.Snapshots),
|
|
||||||
Tree: tree,
|
Tree: tree,
|
||||||
LastAction: lastAction,
|
LastAction: lastAction,
|
||||||
StepTime: stepStart,
|
StepTime: stepStart,
|
||||||
RunStart: summary.StartTime,
|
RunStart: summary.StartTime,
|
||||||
Logs: logs,
|
Logs: logs,
|
||||||
Exceptions: exceptions,
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return summary, fmt.Errorf("step %d push: %w", stepIndex, err)
|
return summary, fmt.Errorf("step %d push: %w", stepIndex, err)
|
||||||
}
|
}
|
||||||
screen, screenErr := screenFromSnapshot(snapshot.Snapshots)
|
|
||||||
if screenErr != nil {
|
screen := ""
|
||||||
logger.Warn("screen snapshot decode failed", "step", stepIndex, "err", screenErr)
|
if tree != nil && len(tree.Elements) > 0 {
|
||||||
}
|
|
||||||
if screen == "" && tree != nil && len(tree.Elements) > 0 {
|
|
||||||
screen = tree.Elements[0].Screen
|
screen = tree.Elements[0].Screen
|
||||||
}
|
}
|
||||||
logger.Info("step", "index", stepIndex, "screen", screen, "nodes", treeSize)
|
logger.Info("step", "index", stepIndex, "screen", screen, "nodes", treeSize)
|
||||||
@@ -165,16 +148,14 @@ func Run(ctx context.Context, options Options) (Summary, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
step := trace.Step{
|
step := trace.Step{
|
||||||
Index: stepIndex,
|
Index: stepIndex,
|
||||||
Timestamp: stepStart,
|
Timestamp: stepStart,
|
||||||
Screen: screen,
|
Screen: screen,
|
||||||
Snapshots: snapshot.Snapshots,
|
Action: traceAction,
|
||||||
Action: traceAction,
|
|
||||||
Exceptions: traceExceptions(exceptions),
|
|
||||||
Violations: violations,
|
Violations: violations,
|
||||||
Hierarchy: tree,
|
Hierarchy: tree,
|
||||||
Residuals: residuals,
|
Residuals: residuals,
|
||||||
Metrics: metrics,
|
Metrics: metrics,
|
||||||
}
|
}
|
||||||
if err := options.TraceWriter.WriteStep(step); err != nil {
|
if err := options.TraceWriter.WriteStep(step); err != nil {
|
||||||
return summary, fmt.Errorf("step %d trace: %w", stepIndex, err)
|
return summary, fmt.Errorf("step %d trace: %w", stepIndex, err)
|
||||||
@@ -188,12 +169,6 @@ func Run(ctx context.Context, options Options) (Summary, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.Connection != nil {
|
|
||||||
if err := options.Connection.Release(ctx); err != nil {
|
|
||||||
return summary, fmt.Errorf("step %d release: %w", stepIndex, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if nextErr == nil {
|
if nextErr == nil {
|
||||||
if err := applyAction(ctx, options.Driver, nextAction, tree); err != nil {
|
if err := applyAction(ctx, options.Driver, nextAction, tree); err != nil {
|
||||||
if isWDADrop(err) {
|
if isWDADrop(err) {
|
||||||
@@ -240,28 +215,12 @@ func validate(options Options) error {
|
|||||||
if options.Duration <= 0 {
|
if options.Duration <= 0 {
|
||||||
return errors.New("runner: Duration must be positive")
|
return errors.New("runner: Duration must be positive")
|
||||||
}
|
}
|
||||||
if options.SnapshotTimeout <= 0 {
|
|
||||||
options.SnapshotTimeout = 5 * time.Second
|
|
||||||
}
|
|
||||||
if options.IdleTimeout <= 0 {
|
if options.IdleTimeout <= 0 {
|
||||||
options.IdleTimeout = 2 * time.Second
|
options.IdleTimeout = 2 * time.Second
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func snapshotStep(ctx context.Context, options Options) (agent.Message, error) {
|
|
||||||
if options.Connection == nil {
|
|
||||||
return agent.Message{}, nil
|
|
||||||
}
|
|
||||||
snapshotTimeout := options.SnapshotTimeout
|
|
||||||
if snapshotTimeout <= 0 {
|
|
||||||
snapshotTimeout = 5 * time.Second
|
|
||||||
}
|
|
||||||
snapshotCtx, snapshotCancel := context.WithTimeout(ctx, snapshotTimeout)
|
|
||||||
defer snapshotCancel()
|
|
||||||
return options.Connection.Snapshot(snapshotCtx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func violationNames(verdicts map[string]ltl.Verdict) []string {
|
func violationNames(verdicts map[string]ltl.Verdict) []string {
|
||||||
var names []string
|
var names []string
|
||||||
for name, verdict := range verdicts {
|
for name, verdict := range verdicts {
|
||||||
@@ -272,18 +231,6 @@ func violationNames(verdicts map[string]ltl.Verdict) []string {
|
|||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
func screenFromSnapshot(snapshots map[string]json.RawMessage) (string, error) {
|
|
||||||
raw, ok := snapshots["screen"]
|
|
||||||
if !ok {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
var screen string
|
|
||||||
if err := json.Unmarshal(raw, &screen); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return screen, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyAction(ctx context.Context, drv driver.DeviceDriver, action verifier.Action, tree *hierarchy.Tree) error {
|
func applyAction(ctx context.Context, drv driver.DeviceDriver, action verifier.Action, tree *hierarchy.Tree) error {
|
||||||
switch action.Kind {
|
switch action.Kind {
|
||||||
case verifier.ActionKindTap:
|
case verifier.ActionKindTap:
|
||||||
@@ -355,22 +302,6 @@ func collectLogs(ctx context.Context, drv driver.DeviceDriver, since time.Time)
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeExceptions(snapshot agent.Message) []verifier.Exception {
|
|
||||||
if len(snapshot.Exceptions) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
result := make([]verifier.Exception, 0, len(snapshot.Exceptions))
|
|
||||||
for _, e := range snapshot.Exceptions {
|
|
||||||
result = append(result, verifier.Exception{
|
|
||||||
Class: e.Class,
|
|
||||||
Message: e.Message,
|
|
||||||
StackTrace: e.StackTrace,
|
|
||||||
UnixMillis: e.UnixMillis,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveCoordinates(action verifier.Action, tree *hierarchy.Tree) (int, int, bool) {
|
func resolveCoordinates(action verifier.Action, tree *hierarchy.Tree) (int, int, bool) {
|
||||||
if action.X > 0 && action.Y > 0 {
|
if action.X > 0 && action.Y > 0 {
|
||||||
return action.X, action.Y, true
|
return action.X, action.Y, true
|
||||||
@@ -421,9 +352,7 @@ func traceActionFor(action verifier.Action, tree *hierarchy.Tree) *trace.Action
|
|||||||
}
|
}
|
||||||
|
|
||||||
// stampSelectorTarget mirrors applyAction's coordinate-resolution rule so the
|
// stampSelectorTarget mirrors applyAction's coordinate-resolution rule so the
|
||||||
// trace records the same point the runner taps. When the spec passed an ax
|
// trace records the same point the runner taps.
|
||||||
// element directly, action.X/Y are already populated and we use them; when the
|
|
||||||
// spec passed a string selector, we resolve it against the captured hierarchy.
|
|
||||||
func stampSelectorTarget(traceAction *trace.Action, action verifier.Action, tree *hierarchy.Tree) {
|
func stampSelectorTarget(traceAction *trace.Action, action verifier.Action, tree *hierarchy.Tree) {
|
||||||
if action.X > 0 && action.Y > 0 {
|
if action.X > 0 && action.Y > 0 {
|
||||||
traceAction.TapPoint = &trace.PointRecord{X: action.X, Y: action.Y}
|
traceAction.TapPoint = &trace.PointRecord{X: action.X, Y: action.Y}
|
||||||
@@ -512,19 +441,3 @@ func isWDADrop(err error) bool {
|
|||||||
return strings.Contains(msg, "ConnectException") ||
|
return strings.Contains(msg, "ConnectException") ||
|
||||||
(strings.Contains(msg, "code = Internal") && strings.Contains(msg, "SocketException"))
|
(strings.Contains(msg, "code = Internal") && strings.Contains(msg, "SocketException"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func traceExceptions(exceptions []verifier.Exception) []trace.Exception {
|
|
||||||
if len(exceptions) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
result := make([]trace.Exception, 0, len(exceptions))
|
|
||||||
for _, e := range exceptions {
|
|
||||||
result = append(result, trace.Exception{
|
|
||||||
Class: e.Class,
|
|
||||||
Message: e.Message,
|
|
||||||
StackTrace: e.StackTrace,
|
|
||||||
UnixMillis: e.UnixMillis,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user