mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
fix(runner): mark nil/empty hierarchy as transitional
A failed or empty sidecar hierarchy fetch was pushed straight to the verifier, letting spec extractors crash with "Cannot read property 'map' of undefined" when findAll returned null. Treat that case like a transitional capture: skip the verifier push, still record the step, and keep the loop progressing.
This commit is contained in:
1 parent
211f3fbf96
commit
ff1a8928dc
2 files changed
+81
No files matched your search
@@ -139,6 +139,14 @@ func Run(ctx context.Context, options Options) (Summary, error) {
|
||||
if tree != nil {
|
||||
treeSize = len(tree.Elements)
|
||||
}
|
||||
// A nil or empty tree means the sidecar's hierarchy fetch failed or
|
||||
// returned nothing (e.g. transient device-side timeout). Pushing it
|
||||
// would let spec extractors call findAll() and chain .map() on a null
|
||||
// result; treat it like a transitional capture so the verifier is
|
||||
// skipped, the step is still recorded, and the loop progresses.
|
||||
if treeSize == 0 {
|
||||
transitional = true
|
||||
}
|
||||
lastLogTime = stepStart
|
||||
|
||||
screen := ""
|
||||
|
||||
@@ -773,6 +773,79 @@ func TestRunner_CleanTreeStillVerified(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// snapshotFailFirst wraps a mock driver so the first Snapshot call returns an
|
||||
// error (mimicking a sidecar timeout while fetching view hierarchy), then
|
||||
// delegates every subsequent call back to the mock.
|
||||
type snapshotFailFirst struct {
|
||||
*mockdriver.Driver
|
||||
calls int
|
||||
}
|
||||
|
||||
func (d *snapshotFailFirst) Snapshot(ctx context.Context) (string, driver.Image, error) {
|
||||
d.calls++
|
||||
if d.calls == 1 {
|
||||
return "", driver.Image{}, errors.New("Timeout while fetching view hierarchy")
|
||||
}
|
||||
return d.Driver.Snapshot(ctx)
|
||||
}
|
||||
|
||||
// TestRunner_NilHierarchyMarksTransitional verifies that when the sidecar's
|
||||
// hierarchy fetch fails (nil tree), the runner marks the step transitional and
|
||||
// skips the verifier instead of pushing a nil tree that would crash the spec.
|
||||
// Subsequent steps with a clean tree still drive the verifier normally.
|
||||
func TestRunner_NilHierarchyMarksTransitional(t *testing.T) {
|
||||
state := newHarnessWithSpec(t, violationSpec)
|
||||
state.mock.HierarchyJSON = `{"attributes":{"resource-id":"HomeScreen"},"children":[]}`
|
||||
wrapped := &snapshotFailFirst{Driver: state.mock}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
summary, err := Run(ctx, Options{
|
||||
Duration: 200 * time.Millisecond,
|
||||
IdleTimeout: 20 * time.Millisecond,
|
||||
Driver: wrapped,
|
||||
Verifier: state.verifier,
|
||||
TraceWriter: state.writer,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Run: %v", err)
|
||||
}
|
||||
if summary.Steps < 2 {
|
||||
t.Fatalf("need at least 2 steps to verify the first is skipped and the second runs, got %d", summary.Steps)
|
||||
}
|
||||
// violationSpec always() => false fires on the first verifier push. With
|
||||
// step 1's verifier skipped, onset moves to step 2.
|
||||
if len(summary.Violations) != 1 {
|
||||
t.Fatalf("expected exactly one onset record, got %d: %v", len(summary.Violations), summary.Violations)
|
||||
}
|
||||
if summary.Violations[0].StepIndex != 2 {
|
||||
t.Errorf("onset step: got %d, want 2 (step 1 verifier skipped due to nil tree)", summary.Violations[0].StepIndex)
|
||||
}
|
||||
|
||||
type traceLine struct {
|
||||
Step int `json:"step"`
|
||||
Transitional bool `json:"transitional"`
|
||||
Violations []string `json:"violations"`
|
||||
}
|
||||
body, err := os.ReadFile(filepath.Join(state.writer.Directory(), "trace.jsonl"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var first traceLine
|
||||
if err := json.Unmarshal(bytes.SplitN(bytes.TrimSpace(body), []byte("\n"), 2)[0], &first); err != nil {
|
||||
t.Fatalf("decode first trace line: %v", err)
|
||||
}
|
||||
if first.Step != 1 {
|
||||
t.Fatalf("first trace line step: got %d, want 1", first.Step)
|
||||
}
|
||||
if !first.Transitional {
|
||||
t.Error("first step must be marked transitional when the hierarchy fetch failed")
|
||||
}
|
||||
if len(first.Violations) != 0 {
|
||||
t.Errorf("step 1 must skip the verifier; got violations %v", first.Violations)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunner_WaitActionSkipsIdle ensures the runner does not call WaitForIdle
|
||||
// after a Wait action - the action already provides settling time.
|
||||
func TestRunner_WaitActionSkipsIdle(t *testing.T) {
|
||||
|
||||
Reference in new issue
Block a user