blob: d80b60a04d9f4bd87fdf34c95c1d7c529ceef845 [file] [log] [blame]
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package shill
import (
"context"
"reflect"
"github.com/godbus/dbus"
"chromiumos/tast/errors"
"chromiumos/tast/local/dbusutil"
)
// PropertiesWatcher watches for Shill "PropertyChanged" signals.
type PropertiesWatcher struct {
watcher *dbusutil.SignalWatcher
}
// NewPropertiesWatcher returns a PropertiesWatcher to observe the shill specific "PropertyChanged" signal.
func NewPropertiesWatcher(ctx context.Context, obj *dbusutil.DBusObject) (*PropertiesWatcher, error) {
watcher, err := obj.CreateWatcher(ctx, "PropertyChanged")
if err != nil {
return nil, err
}
return &PropertiesWatcher{watcher: watcher}, nil
}
// Close stops watching for signals.
func (pw *PropertiesWatcher) Close(ctx context.Context) error {
return pw.watcher.Close(ctx)
}
// Wait waits for "PropertyChanged" signal and updates corresponding property value.
func (pw *PropertiesWatcher) Wait(ctx context.Context) (string, interface{}, dbus.Sequence, error) {
select {
case sig := <-pw.watcher.Signals:
if len(sig.Body) != 2 {
return "", nil, 0, errors.Errorf("signal body must contain 2 arguments: %v", sig.Body)
}
if prop, ok := sig.Body[0].(string); !ok {
return "", nil, 0, errors.Errorf("signal first argument must be a string: %v", sig.Body[0])
} else if variant, ok := sig.Body[1].(dbus.Variant); !ok {
return "", nil, 0, errors.Errorf("signal second argument must be a variant: %v", sig.Body[1])
} else {
return prop, variant.Value(), sig.Sequence, nil
}
case <-ctx.Done():
return "", nil, 0, errors.Errorf("didn't receive PropertyChanged signal: %v", ctx.Err())
}
}
// WaitAll waits for all expected properties were shown on at least one "PropertyChanged" signal and returns the last updated
// value of each property.
func (pw *PropertiesWatcher) WaitAll(ctx context.Context, props ...string) ([]interface{}, error) {
values := make([]interface{}, len(props))
seen := make([]bool, len(props))
unseen := len(props)
for unseen > 0 {
prop, val, _, err := pw.Wait(ctx)
if err != nil {
return nil, errors.Wrapf(err, "failed to wait for any property: %q", props)
}
for i, p := range props {
if p != prop {
continue
}
if !seen[i] {
seen[i] = true
unseen--
}
values[i] = val
}
}
return values, nil
}
// Expect waits for the expected value of a given property.
func (pw *PropertiesWatcher) Expect(ctx context.Context, prop string, expected interface{}) error {
_, err := pw.ExpectIn(ctx, prop, []interface{}{expected})
return err
}
// ExpectIn expects the prop's value to become one of the expected values and returns the first matched one.
func (pw *PropertiesWatcher) ExpectIn(ctx context.Context, prop string, expected []interface{}) (interface{}, error) {
for {
vals, err := pw.WaitAll(ctx, prop)
if err != nil {
return nil, err
}
for _, e := range expected {
if reflect.DeepEqual(e, vals[0]) {
return vals[0], nil
}
}
}
}