Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
service_test.go 10.49 KiB
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Huawei Inc.

package service_test

import (
	"errors"
	"testing"

	"booting.oniroproject.org/distro/components/sysota/boot"
	"booting.oniroproject.org/distro/components/sysota/boot/testboot"
	"booting.oniroproject.org/distro/components/sysota/dbusutil"
	"booting.oniroproject.org/distro/components/sysota/dbusutil/dbustest"
	"booting.oniroproject.org/distro/components/sysota/ota"
	"booting.oniroproject.org/distro/components/sysota/service"
	"github.com/godbus/dbus/v5"
	. "gopkg.in/check.v1"
)

func Test(t *testing.T) { TestingT(t) }

var errBoom = errors.New("boom")

type serviceSuite struct {
	dbustest.Suite

	config ota.Config
	state  ota.SystemState

	svc  *service.Service
	conn *dbus.Conn
}

var _ = Suite(&serviceSuite{})

func (s *serviceSuite) EnsureConn(c *C) *dbus.Conn {
	var err error

	if s.conn == nil {
		s.conn, err = dbusutil.SystemBus()
		c.Assert(err, IsNil)
	}

	return s.conn
}

type stater struct {
	config *ota.Config
	state  *ota.SystemState
}

func (c *stater) LoadConfig() (*ota.Config, error) {
	return c.config, nil
}

func (c *stater) SaveConfig(config *ota.Config) error {
	return nil
}

func (c *stater) LoadState() (*ota.SystemState, error) {
	return c.state, nil
}

func (c *stater) SaveState(state *ota.SystemState) error {
	return nil
}

func (s *serviceSuite) EnsureService(c *C) {
	var err error
	if s.svc == nil {
		s.EnsureConn(c)

		s.svc, err = service.New(&stater{config: &s.config, state: &s.state}, s.conn)
		c.Assert(err, IsNil)

		err := s.svc.Export()
		c.Assert(err, IsNil)

		err = service.RequestBusName(s.conn)
		c.Assert(err, IsNil)
	}
}

func (s *serviceSuite) SetUpTest(c *C) {
	s.config = ota.Config{
		DeviceMaker:  "test-maker",
		DeviceModel:  "test-model",
		UpdateStream: "test-stream",
	}
	s.state = ota.SystemState{}
}

func (s *serviceSuite) TearDownTest(c *C) {
	if s.svc != nil {
		err := s.svc.Unexport()
		c.Assert(err, IsNil)

		s.svc = nil
	}

	if s.conn != nil {
		err := s.conn.Close()
		c.Assert(err, IsNil)

		s.conn = nil
	}
}

func (s *serviceSuite) TestSmokeAccessWithoutBootProtocolAcrossDBus(c *C) {
	s.config.DebugBootAPI = true
	s.EnsureService(c)

	svcObject := s.conn.Object("org.oniroproject.sysota1", "/org/oniroproject/sysota1/Service")

	var slotName string

	err := svcObject.Call("org.oniroproject.sysota1.BootLoader.QueryActive", 0).Store(&slotName)
	c.Assert(err, ErrorMatches, `boot protocol is not set`)
	err = svcObject.Call("org.oniroproject.sysota1.BootLoader.QueryInactive", 0).Store(&slotName)
	c.Assert(err, ErrorMatches, `boot protocol is not set`)
	err = svcObject.Call("org.oniroproject.sysota1.BootLoader.TrySwitch", 0, "B").Store(&slotName)
	c.Assert(err, ErrorMatches, `boot protocol is not set`)
	err = svcObject.Call("org.oniroproject.sysota1.BootLoader.CommitSwitch", 0).Store(&slotName)
	c.Assert(err, ErrorMatches, `boot protocol is not set`)
	err = svcObject.Call("org.oniroproject.sysota1.BootLoader.CancelSwitch", 0).Store(&slotName)
	c.Assert(err, ErrorMatches, `boot protocol is not set`)
	err = svcObject.Call("org.oniroproject.sysota1.BootLoader.Reboot", 0, 0).Store(&slotName)
	c.Assert(err, ErrorMatches, `boot protocol is not set`)
}

func (s *serviceSuite) TestSmokeAccessWithBootProtocolAcrossDBus(c *C) {
	var proto testboot.Protocol

	s.config.BootLoaderType = ota.TestBoot
	s.config.DebugBootAPI = true

	service.SetTestBootProtocol(&proto)

	s.EnsureService(c)

	svcObject := s.conn.Object("org.oniroproject.sysota1", "/org/oniroproject/sysota1/Service")

	var slotName string

	proto.QueryActiveFn = func() (boot.Slot, error) { return boot.SlotA, nil }
	err := svcObject.Call("org.oniroproject.sysota1.BootLoader.QueryActive", 0).Store(&slotName)
	c.Assert(err, IsNil)
	c.Check(slotName, Equals, "A")

	proto.QueryActiveFn = func() (boot.Slot, error) { return boot.InvalidSlot, errBoom }
	err = svcObject.Call("org.oniroproject.sysota1.BootLoader.QueryActive", 0).Store(&slotName)
	c.Assert(err, ErrorMatches, `boom`)

	proto.QueryInactiveFn = func() (boot.Slot, error) { return boot.SlotB, nil }
	err = svcObject.Call("org.oniroproject.sysota1.BootLoader.QueryInactive", 0).Store(&slotName)
	c.Assert(err, IsNil)
	c.Check(slotName, Equals, "B")

	proto.QueryInactiveFn = func() (boot.Slot, error) { return boot.InvalidSlot, errBoom }
	err = svcObject.Call("org.oniroproject.sysota1.BootLoader.QueryInactive", 0).Store(&slotName)
	c.Assert(err, ErrorMatches, `boom`)

	err = svcObject.Call("org.oniroproject.sysota1.BootLoader.TrySwitch", 0, "potato").Store()
	c.Assert(err, ErrorMatches, `invalid boot slot`)

	called := false
	proto.TrySwitchFn = func(slot boot.Slot) error {
		called = true

		c.Check(slot, Equals, boot.SlotB)

		return nil
	}
	err = svcObject.Call("org.oniroproject.sysota1.BootLoader.TrySwitch", 0, "B").Store()
	c.Assert(err, IsNil)
	c.Check(called, Equals, true)

	proto.TrySwitchFn = func(_ boot.Slot) error {
		return errBoom
	}
	err = svcObject.Call("org.oniroproject.sysota1.BootLoader.TrySwitch", 0, "B").Store()
	c.Assert(err, ErrorMatches, `boom`)

	called = false
	proto.CommitSwitchFn = func() error {
		called = true

		return nil
	}
	err = svcObject.Call("org.oniroproject.sysota1.BootLoader.CommitSwitch", 0).Store()
	c.Assert(err, IsNil)
	c.Check(called, Equals, true)

	proto.CommitSwitchFn = func() error {
		return errBoom
	}
	err = svcObject.Call("org.oniroproject.sysota1.BootLoader.CommitSwitch", 0).Store()
	c.Assert(err, ErrorMatches, `boom`)

	called = false
	proto.CancelSwitchFn = func() error {
		called = true

		return nil
	}
	err = svcObject.Call("org.oniroproject.sysota1.BootLoader.CancelSwitch", 0).Store()
	c.Assert(err, IsNil)
	c.Check(called, Equals, true)
	proto.CancelSwitchFn = func() error {
		return errBoom
	}
	err = svcObject.Call("org.oniroproject.sysota1.BootLoader.CancelSwitch", 0).Store()
	c.Assert(err, ErrorMatches, `boom`)

	called = false
	proto.RebootFn = func(flags boot.RebootFlags) error {
		called = true

		c.Check(flags, Equals, boot.RebootTryBoot)

		return nil
	}
	err = svcObject.Call("org.oniroproject.sysota1.BootLoader.Reboot", 0, boot.RebootTryBoot).Store()
	c.Assert(err, IsNil)
	c.Check(called, Equals, true)

	proto.RebootFn = func(flags boot.RebootFlags) error {
		return errBoom
	}
	err = svcObject.Call("org.oniroproject.sysota1.BootLoader.Reboot", 0, 0).Store()
	c.Assert(err, ErrorMatches, `boom`)
}

func (s *serviceSuite) TestAccessPropertiesAcrossDBus(c *C) {
	s.EnsureService(c)

	svcObject := s.conn.Object("org.oniroproject.sysota1", "/org/oniroproject/sysota1/Service")

	var propValue dbus.Variant

	err := svcObject.Call("org.freedesktop.DBus.Properties.Get", 0, "org.oniroproject.sysota1.Service", "Maker").Store(&propValue)
	c.Assert(err, IsNil)
	c.Check(propValue, Equals, dbus.MakeVariant("test-maker"))

	err = svcObject.Call("org.freedesktop.DBus.Properties.Get", 0, "org.oniroproject.sysota1.Service", "Model").Store(&propValue)
	c.Assert(err, IsNil)
	c.Check(propValue, Equals, dbus.MakeVariant("test-model"))

	err = svcObject.Call("org.freedesktop.DBus.Properties.Get", 0, "org.oniroproject.sysota1.Service", "Stream").Store(&propValue)
	c.Assert(err, IsNil)
	c.Check(propValue, Equals, dbus.MakeVariant("test-stream"))

	err = svcObject.Call("org.freedesktop.DBus.Properties.Get", 0, "org.oniroproject.sysota1.Service", "Potato").Store(&propValue)
	c.Assert(err, ErrorMatches, `org.freedesktop.DBus.Properties.Error.PropertyNotFound`)

	err = svcObject.Call("org.freedesktop.DBus.Properties.Get", 0, "org.oniroproject.sysota1.Potato", "Property").Store(&propValue)
	c.Assert(err, ErrorMatches, `org.freedesktop.DBus.Properties.Error.InterfaceNotFound`)

	var propMap map[string]dbus.Variant

	err = svcObject.Call("org.freedesktop.DBus.Properties.GetAll", 0, "org.oniroproject.sysota1.Service").Store(&propMap)
	c.Assert(err, IsNil)
	c.Check(propMap, DeepEquals, map[string]dbus.Variant{
		"Maker":  dbus.MakeVariant("test-maker"),
		"Model":  dbus.MakeVariant("test-model"),
		"Stream": dbus.MakeVariant("test-stream"),
	})

	err = svcObject.Call("org.freedesktop.DBus.Properties.GetAll", 0, "org.oniroproject.sysota1.Potato").Store(&propMap)
	c.Assert(err, ErrorMatches, `org.freedesktop.DBus.Properties.Error.InterfaceNotFound`)

	err = svcObject.Call("org.freedesktop.DBus.Properties.Set", 0, "org.oniroproject.sysota1.Service", "Maker", "potato").Store()
	c.Assert(err, ErrorMatches, `org.freedesktop.DBus.Properties.Error.ReadOnly`)

	err = svcObject.Call("org.freedesktop.DBus.Properties.Set", 0, "org.oniroproject.sysota1.Service", "Model", "potato").Store()
	c.Assert(err, ErrorMatches, `org.freedesktop.DBus.Properties.Error.ReadOnly`)

	// Stream can be changed.
	// TODO(zyga): observe the property changed signal.
	err = svcObject.Call("org.freedesktop.DBus.Properties.Set", 0, "org.oniroproject.sysota1.Service", "Stream", "new-stream").Store()
	c.Assert(err, IsNil)
	err = svcObject.Call("org.freedesktop.DBus.Properties.Get", 0, "org.oniroproject.sysota1.Service", "Stream").Store(&propValue)
	c.Assert(err, IsNil)
	c.Check(propValue, Equals, dbus.MakeVariant("new-stream"))

	err = svcObject.Call("org.freedesktop.DBus.Properties.Set", 0, "org.oniroproject.sysota1.Service", "Stream", 42).Store()
	c.Assert(err, ErrorMatches, `org.freedesktop.DBus.Properties.Error.InvalidArg`)

	err = svcObject.Call("org.freedesktop.DBus.Properties.Set", 0, "org.oniroproject.sysota1.Service", "Potato", "value").Store()
	c.Assert(err, ErrorMatches, `org.freedesktop.DBus.Properties.Error.PropertyNotFound`)

	err = svcObject.Call("org.freedesktop.DBus.Properties.Set", 0, "org.oniroproject.sysota1.Potato", "Property", "value").Store()
	c.Assert(err, ErrorMatches, `org.freedesktop.DBus.Properties.Error.InterfaceNotFound`)
}

func (s *serviceSuite) TestIsIdle(c *C) {
	s.EnsureService(c)

	// Service without operations is idle.
	c.Check(s.svc.IsIdle(), Equals, true)
}

func (s *serviceSuite) TestGetManagedObjects(c *C) {
	s.EnsureService(c)

	svcObject := s.conn.Object("org.oniroproject.sysota1", "/org/oniroproject/sysota1/Service")

	var objs map[dbus.ObjectPath]map[string]map[string]dbus.Variant

	err := svcObject.Call("org.freedesktop.DBus.ObjectManager.GetManagedObjects", 0).Store(&objs)
	c.Assert(err, IsNil)
	c.Check(objs, DeepEquals, map[dbus.ObjectPath]map[string]map[string]dbus.Variant{})
}

func (s *serviceSuite) TestBootMode(c *C) {
	s.EnsureService(c)

	c.Check(s.svc.BootMode(), Equals, boot.Normal)
	// TODO(zyga): check that PropertiesChanged signal was *not* sent.
	c.Check(s.svc.SetBootMode(boot.Mode(boot.Normal)), IsNil)
	// TODO(zyga): check that PropertiesChanged signal was sent.
	c.Check(s.svc.SetBootMode(boot.Mode(ota.TestBoot)), IsNil)
	c.Check(s.svc.BootMode(), Equals, boot.Try)
}