aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/kobject
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-05-26 18:06:07 +0900
committerOphestra <cat@gensokyo.uk>2026-05-26 18:39:09 +0900
commitcaf3e0db4b92d859599ee199b32051fae8c99016 (patch)
treeaf7e9cb71d52b7f8b6a21ee6b9c1be417480a1fe /internal/kobject
parent12b9f51128b2683303bc9ea500c07d6f4417988f (diff)
internal/kobject: improve error JSON representation
This is now usable by internal/report. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/kobject')
-rw-r--r--internal/kobject/kobject.go184
-rw-r--r--internal/kobject/kobject_test.go168
2 files changed, 215 insertions, 137 deletions
diff --git a/internal/kobject/kobject.go b/internal/kobject/kobject.go
index 72d8f052..26359114 100644
--- a/internal/kobject/kobject.go
+++ b/internal/kobject/kobject.go
@@ -9,6 +9,7 @@ import (
"strconv"
"sync"
+ "hakurei.app/internal/report"
"hakurei.app/internal/uevent"
)
@@ -29,9 +30,9 @@ const (
// Object represents a kernel object.
type Object struct {
// Origin of the object.
- State int `json:"-"`
+ State int `json:"state,omitempty"`
// Set by [uevent.KOBJ_OFFLINE] and [uevent.KOBJ_ONLINE].
- Offline bool
+ Offline bool `json:"offline,omitempty"`
// alloc_uevent_skb: devpath
DevPath string `json:"devpath"`
@@ -48,11 +49,11 @@ type Object struct {
Env map[string]string `json:"env"`
}
-// Clone returns a copy of o.
-func (o *Object) Clone() Object {
+// Clone returns the address of a copy of o.
+func (o *Object) Clone() *Object {
v := *o
v.Env = maps.Clone(o.Env)
- return v
+ return &v
}
// GoString returns compound literal for the underlying value.
@@ -194,77 +195,94 @@ func (s *State) Range(ctx context.Context, f func(o *Object) bool) {
}
}
-// UnexpectedColdbootError is reported by [State.Consume] for a coldboot event
-// with action other than the expected [uevent.KOBJ_ADD].
-type UnexpectedColdbootError Event
-
-func (e UnexpectedColdbootError) Error() string {
- return "unexpected " + e.Action.String() + " coldboot event"
-}
-
-// DuplicateAddError is reported by [State.Consume] for a [uevent.KOBJ_ADD]
-// event on a still-existing entry that was not the result of a coldboot.
-type DuplicateAddError Event
-
-func (e DuplicateAddError) Error() string {
- return "duplicate add event on devpath " + strconv.Quote(e.DevPath)
+// An EventError describes a malformed or inconsistent [Event].
+type EventError struct {
+ Kind int `json:"fault"`
+ E Event `json:"event"`
+ O *Object `json:"object,omitempty"`
}
-// TargetError is reported by [State.Consume] for an event on a nonexistent
-// entry. This is generally only possible before coldboot completes.
-type TargetError Event
-
-func (e TargetError) Error() string {
- return "unexpected " + e.Action.String() +
- " event on devpath " + strconv.Quote(e.DevPath)
-}
-
-// RemoveStateError is reported by [State.Consume] for a [uevent.KOBJ_REMOVE]
-// event targeting an entry in a state other than [StateColdboot] and [StateNew].
-type RemoveStateError Object
-
-func (e RemoveStateError) Error() string {
- return "remove event targeting devpath " + strconv.Quote(e.DevPath) +
- " in state " + strconv.Itoa(e.State)
-}
-
-// BindStateError is reported by [State.Consume] for a [uevent.KOBJ_BIND] event
-// targeting an entry in a state other than [StateColdboot] and [StateNew].
-type BindStateError Object
-
-func (e BindStateError) Error() string {
- return "bind event targeting devpath " + strconv.Quote(e.DevPath) +
- " in state " + strconv.Itoa(e.State)
-}
+var _ report.RepresentableError = EventError{}
-// UnbindStateError is reported by [State.Consume] for a [uevent.KOBJ_UNBIND]
-// event targeting an entry in a state other than [StateBound].
-type UnbindStateError Object
+func (EventError) Representable() {}
-func (e UnbindStateError) Error() string {
- return "unbind event targeting devpath " + strconv.Quote(e.DevPath) +
- " in state " + strconv.Itoa(e.State)
-}
+const (
+ // EUnexpectedColdboot is reported for a coldboot event with action other
+ // than the expected [uevent.KOBJ_ADD].
+ EUnexpectedColdboot = iota
+ // EDuplicateAdd is reported for a [uevent.KOBJ_ADD] event on a
+ // still-existing entry that was not the result of a coldboot.
+ EDuplicateAdd
+ // EBadTarget is reported for an event on a nonexistent [Object]. This is
+ // generally only possible before coldboot completes.
+ EBadTarget
+ // ERemoveState is reported for a [uevent.KOBJ_REMOVE] event targeting an
+ // entry in a state other than [StateColdboot] and [StateNew].
+ ERemoveState
+ // EUnexpectedOffline is reported for a [uevent.KOBJ_OFFLINE] or
+ // [uevent.KOBJ_ONLINE] event targeting an already offline or online object.
+ EUnexpectedOffline
+ // EBindState is reported for a [uevent.KOBJ_BIND] event targeting an entry
+ // in a state other than [StateColdboot] and [StateNew].
+ EBindState
+ // EUnbindState is reported for a [uevent.KOBJ_UNBIND] event targeting an
+ // entry in a state other than [StateBound].
+ EUnbindState
+ // EMalformedMove is reported for a [uevent.KOBJ_MOVE] event missing the
+ // DEVPATH_OLD environment variable.
+ EMalformedMove
+)
-// MalformedMoveError is reported by [State.Consume] for a [uevent.KOBJ_MOVE]
-// event missing the DEVPATH_OLD environment variable.
-type MalformedMoveError Event
+func (e EventError) Error() string {
+ switch e.Kind {
+ case EUnexpectedColdboot:
+ return "unexpected " + e.E.Action.String() + " coldboot event"
+ case EDuplicateAdd:
+ return "duplicate add event on devpath " + strconv.Quote(e.E.DevPath)
+ case EBadTarget:
+ return "unexpected " + e.E.Action.String() + " event on devpath " +
+ strconv.Quote(e.E.DevPath)
+ case ERemoveState:
+ if e.O == nil {
+ return "invalid remove event error"
+ }
+ return "remove event targeting devpath " + strconv.Quote(e.E.DevPath) +
+ " in state " + strconv.Itoa(e.O.State)
+ case EUnexpectedOffline:
+ if e.O == nil {
+ return "invalid unexpected offline error"
+ }
+ if e.O.Offline {
+ return "offline event targeting devpath " + strconv.Quote(e.E.DevPath)
+ }
+ return "online event targeting devpath " + strconv.Quote(e.E.DevPath)
+ case EBindState:
+ if e.O == nil {
+ return "invalid bind state error"
+ }
+ return "bind event targeting devpath " + strconv.Quote(e.E.DevPath) +
+ " in state " + strconv.Itoa(e.O.State)
+ case EUnbindState:
+ if e.O == nil {
+ return "invalid unbind state error"
+ }
+ return "unbind event targeting devpath " + strconv.Quote(e.E.DevPath) +
+ " in state " + strconv.Itoa(e.O.State)
+ case EMalformedMove:
+ return "move event targeting devpath " + strconv.Quote(e.E.DevPath) +
+ " missing DEVPATH_OLD"
-func (e MalformedMoveError) Error() string {
- return "move event targeting devpath " + strconv.Quote(e.DevPath) +
- " missing DEVPATH_OLD"
+ default:
+ return "invalid event error kind " + strconv.Itoa(e.Kind)
+ }
}
-// UnexpectedOfflineError is reported by [State.Consume] for a
-// [uevent.KOBJ_OFFLINE] or [uevent.KOBJ_ONLINE] event targeting an already
-// offline or online object.
-type UnexpectedOfflineError Object
-
-func (e UnexpectedOfflineError) Error() string {
- if e.Offline {
- return "offline event targeting devpath " + strconv.Quote(e.DevPath)
+// NewError returns a new [EventError] for e and o.
+func (e *Event) NewError(kind int, o *Object) error {
+ if o != nil {
+ o = o.Clone()
}
- return "online event targeting devpath " + strconv.Quote(e.DevPath)
+ return EventError{kind, e.Clone(), o}
}
// processEvent merges an event into s.
@@ -274,7 +292,7 @@ func (s *State) processEvent(e *Event) {
coldboot := e.Synth != nil
if e.Action != uevent.KOBJ_ADD && coldboot {
- s.reportErr(UnexpectedColdbootError(e.Clone()))
+ s.reportErr(e.NewError(EUnexpectedColdboot, nil))
return
}
@@ -282,7 +300,7 @@ func (s *State) processEvent(e *Event) {
case uevent.KOBJ_ADD:
if e.Synth == nil {
if o, ok := s.uevent[e.DevPath]; ok {
- s.reportErr(DuplicateAddError(e.Clone()))
+ s.reportErr(e.NewError(EDuplicateAdd, o))
o.merge(e.Env)
s.dispatchIter(o)
return
@@ -299,10 +317,10 @@ func (s *State) processEvent(e *Event) {
case uevent.KOBJ_REMOVE:
if o, ok := s.uevent[e.DevPath]; !ok {
- s.reportErr(TargetError(e.Clone()))
+ s.reportErr(e.NewError(EBadTarget, nil))
return
} else if o.State != StateColdboot && o.State != StateNew {
- s.reportErr(RemoveStateError(o.Clone()))
+ s.reportErr(e.NewError(ERemoveState, o))
}
delete(s.uevent, e.DevPath)
return
@@ -310,7 +328,7 @@ func (s *State) processEvent(e *Event) {
case uevent.KOBJ_CHANGE:
o, ok := s.uevent[e.DevPath]
if !ok {
- s.reportErr(TargetError(e.Clone()))
+ s.reportErr(e.NewError(EBadTarget, nil))
// this suffers from the coldboot race window similar to KOBJ_MOVE,
// however this action combines driver-specific and change-specific
// environment variables and combines them with environment
@@ -333,11 +351,11 @@ func (s *State) processEvent(e *Event) {
case uevent.KOBJ_MOVE:
var o *Object
if old, ok := e.Env["DEVPATH_OLD"]; !ok {
- s.reportErr(MalformedMoveError(e.Clone()))
+ s.reportErr(e.NewError(EMalformedMove, nil))
// not reached
o = e.makeColdboot()
} else if o, ok = s.uevent[old]; !ok {
- s.reportErr(TargetError(e.Clone()))
+ s.reportErr(e.NewError(EBadTarget, nil))
// this generally happens during coldboot, dropping the event here
// may cause inconsistent state if the coldboot event for this
// object was generated before the bind event
@@ -356,14 +374,14 @@ func (s *State) processEvent(e *Event) {
case uevent.KOBJ_ONLINE:
o, ok := s.uevent[e.DevPath]
if !ok {
- s.reportErr(TargetError(e.Clone()))
+ s.reportErr(e.NewError(EBadTarget, nil))
// coldboot race window similar to an unexpected KOBJ_MOVE
o = e.makeColdboot()
s.uevent[e.DevPath] = o
o.merge(e.Env)
}
if !o.Offline {
- s.reportErr(UnexpectedOfflineError(o.Clone()))
+ s.reportErr(e.NewError(EUnexpectedOffline, o))
}
o.Offline = false
s.dispatchIter(o)
@@ -372,14 +390,14 @@ func (s *State) processEvent(e *Event) {
case uevent.KOBJ_OFFLINE:
o, ok := s.uevent[e.DevPath]
if !ok {
- s.reportErr(TargetError(e.Clone()))
+ s.reportErr(e.NewError(EBadTarget, nil))
// coldboot race window similar to an unexpected KOBJ_MOVE
o = e.makeColdboot()
s.uevent[e.DevPath] = o
o.merge(e.Env)
}
if o.Offline {
- s.reportErr(UnexpectedOfflineError(o.Clone()))
+ s.reportErr(e.NewError(EUnexpectedOffline, o))
}
o.Offline = true
s.dispatchIter(o)
@@ -388,13 +406,13 @@ func (s *State) processEvent(e *Event) {
case uevent.KOBJ_BIND:
o, ok := s.uevent[e.DevPath]
if !ok {
- s.reportErr(TargetError(e.Clone()))
+ s.reportErr(e.NewError(EBadTarget, nil))
// coldboot race window similar to an unexpected KOBJ_MOVE
o = e.makeColdboot()
s.uevent[e.DevPath] = o
}
if o.State != StateColdboot && o.State != StateNew {
- s.reportErr(BindStateError(o.Clone()))
+ s.reportErr(e.NewError(EBindState, o))
}
o.State = StateBound
o.merge(e.Env)
@@ -404,13 +422,13 @@ func (s *State) processEvent(e *Event) {
case uevent.KOBJ_UNBIND:
o, ok := s.uevent[e.DevPath]
if !ok {
- s.reportErr(TargetError(e.Clone()))
+ s.reportErr(e.NewError(EBadTarget, nil))
// coldboot race window similar to an unexpected KOBJ_MOVE, but does
// not result in inconsistent state if dropped
return
}
if o.State != StateBound {
- s.reportErr(UnbindStateError(o.Clone()))
+ s.reportErr(e.NewError(EUnbindState, o))
}
o.State = StateNew
o.Driver = ""
@@ -423,7 +441,7 @@ func (s *State) processEvent(e *Event) {
}
}
-// BadSequenceError is reported by [State.Consume] for an unexpected SEQNUM.
+// BadSequenceError is reported for an unexpected SEQNUM.
type BadSequenceError struct{ Got, Want uint64 }
func (e BadSequenceError) Error() string {
diff --git a/internal/kobject/kobject_test.go b/internal/kobject/kobject_test.go
index f04db2d1..b184e621 100644
--- a/internal/kobject/kobject_test.go
+++ b/internal/kobject/kobject_test.go
@@ -42,12 +42,12 @@ func TestConsume(t *testing.T) {
"SYNTH_UUID=fdfdfdfd-fdfd-fdfd-fdfd-fdfdfdfdfdfd",
}},
}, map[string]*Object{}, nil, []error{
- UnexpectedColdbootError{
+ (&Event{
Action: uevent.KOBJ_BIND,
DevPath: "\x00",
Env: map[string]string{},
Synth: &coldboot,
- },
+ }).NewError(EUnexpectedColdboot, nil),
}},
{"sequence", []*uevent.Message{
@@ -85,12 +85,16 @@ func TestConsume(t *testing.T) {
Env: map[string]string{"V": "\xfd"},
},
}, nil, []error{
- DuplicateAddError{
+ (&Event{
Action: uevent.KOBJ_ADD,
DevPath: "\x00",
Env: map[string]string{"V": "\xfd"},
Sequence: 2,
- },
+ }).NewError(EDuplicateAdd, &Object{
+ State: StateNew,
+ DevPath: "\x00",
+ Env: map[string]string{"V": "\xff"},
+ }),
}},
{"remove", []*uevent.Message{
@@ -109,16 +113,21 @@ func TestConsume(t *testing.T) {
"SEQNUM=3",
}},
}, map[string]*Object{}, nil, []error{
- TargetError{
+ (&Event{
Action: uevent.KOBJ_REMOVE,
DevPath: "\x00",
Env: map[string]string{},
- },
- RemoveStateError{
+ }).NewError(EBadTarget, nil),
+ (&Event{
+ Action: uevent.KOBJ_REMOVE,
+ DevPath: "\x00",
+ Env: map[string]string{},
+ Sequence: 3,
+ }).NewError(ERemoveState, &Object{
State: StateBound,
DevPath: "\x00",
Driver: "\xfd",
- },
+ }),
}},
{"change", []*uevent.Message{
@@ -133,14 +142,14 @@ func TestConsume(t *testing.T) {
Env: map[string]string{"V": "\xff"},
},
}, nil, []error{
- TargetError{
+ (&Event{
Action: uevent.KOBJ_CHANGE,
DevPath: "\x00",
Sequence: 9,
Env: map[string]string{
"V": "\xff",
},
- },
+ }).NewError(EBadTarget, nil),
}},
{"move", []*uevent.Message{
@@ -157,14 +166,14 @@ func TestConsume(t *testing.T) {
"\x0f": {DevPath: "\x0f", Env: map[string]string{"V": "\xfc"}},
"\x0e": {DevPath: "\x0e", Env: map[string]string{"V": "\xfd"}},
}, nil, []error{
- MalformedMoveError{
+ (&Event{
Action: uevent.KOBJ_MOVE,
DevPath: "\x0f",
Env: map[string]string{
"V": "\xfc",
},
- },
- TargetError{
+ }).NewError(EMalformedMove, nil),
+ (&Event{
Action: uevent.KOBJ_MOVE,
DevPath: "\x0e",
Env: map[string]string{
@@ -172,7 +181,7 @@ func TestConsume(t *testing.T) {
"DEVPATH_OLD": "\xff",
},
Sequence: 1,
- },
+ }).NewError(EBadTarget, nil),
}},
{"offline", []*uevent.Message{
@@ -203,31 +212,43 @@ func TestConsume(t *testing.T) {
Env: map[string]string{"V": "\xf0"},
},
}, nil, []error{
- TargetError{
+ (&Event{
Action: uevent.KOBJ_ONLINE,
DevPath: "\xfd",
Env: map[string]string{
"V": "\xff",
},
Sequence: 9,
- },
- UnexpectedOfflineError{
+ }).NewError(EBadTarget, nil),
+ (&Event{
+ Action: uevent.KOBJ_ONLINE,
DevPath: "\xfd",
Env: map[string]string{
"V": "\xff",
},
- },
- UnexpectedOfflineError{
+ Sequence: 9,
+ }).NewError(EUnexpectedOffline, &Object{
+ DevPath: "\xfd",
+ Env: map[string]string{
+ "V": "\xff",
+ },
+ }),
+ (&Event{
+ Action: uevent.KOBJ_OFFLINE,
+ DevPath: "\xfd",
+ Env: map[string]string{},
+ Sequence: 11,
+ }).NewError(EUnexpectedOffline, &Object{
Offline: true,
DevPath: "\xfd",
Env: map[string]string{"V": "\xff"},
- },
- TargetError{
+ }),
+ (&Event{
Action: uevent.KOBJ_OFFLINE,
DevPath: "\xfc",
Env: map[string]string{"V": "\xf0"},
Sequence: 12,
- },
+ }).NewError(EBadTarget, nil),
}},
{"bind", []*uevent.Message{
@@ -272,22 +293,32 @@ func TestConsume(t *testing.T) {
"V": "\xf0",
},
}}, nil, []error{
- UnbindStateError{
+ (&Event{
+ Action: uevent.KOBJ_UNBIND,
+ DevPath: "\x00",
+ Env: map[string]string{"_V": "\xfd"},
+ Sequence: 2,
+ }).NewError(EUnbindState, &Object{
State: StateNew,
DevPath: "\x00",
Env: map[string]string{"V": "\xff"},
- },
- BindStateError{
+ }),
+ (&Event{
+ Action: uevent.KOBJ_BIND,
+ DevPath: "\x00",
+ Env: map[string]string{"___V": "\xfb"},
+ Sequence: 4,
+ }).NewError(EBindState, &Object{
State: StateBound,
DevPath: "\x00",
Env: map[string]string{"V": "\xff", "__V": "\xfc"},
- },
- TargetError{
+ }),
+ (&Event{
Action: uevent.KOBJ_BIND,
DevPath: "\f",
Env: map[string]string{"V": "\xf0", "DRIVER": "\x01"},
Sequence: 5,
- },
+ }).NewError(EBadTarget, nil),
}},
{"unbind", []*uevent.Message{
@@ -295,12 +326,12 @@ func TestConsume(t *testing.T) {
"SEQNUM=9",
}},
}, map[string]*Object{}, nil, []error{
- TargetError{
+ (&Event{
Action: uevent.KOBJ_UNBIND,
DevPath: "\xfd",
Env: map[string]string{},
Sequence: 9,
- },
+ }).NewError(EBadTarget, nil),
}},
}
for _, tc := range testCases {
@@ -375,7 +406,7 @@ func TestIter(t *testing.T) {
var done bool
wg.Go(func() {
s.Range(ctx, func(o *Object) bool {
- got = append(got, new(o.Clone()))
+ got = append(got, o.Clone())
return !done
})
})
@@ -435,46 +466,75 @@ func TestErrors(t *testing.T) {
Want: 0xbabe,
}, "SEQNUM=51966, want 47806"},
- {"UnexpectedColdbootError", UnexpectedColdbootError{
+ {"EUnexpectedColdboot", (&Event{
Action: 0xbeef,
- }, "unexpected unsupported kobject_action 48879 coldboot event"},
+ }).NewError(EUnexpectedColdboot, nil),
+ "unexpected unsupported kobject_action 48879 coldboot event"},
- {"DuplicateAddError", DuplicateAddError{
+ {"EDuplicateAdd", (&Event{
Action: uevent.KOBJ_ADD,
DevPath: "\x00",
- }, `duplicate add event on devpath "\x00"`},
+ }).NewError(EDuplicateAdd, nil),
+ `duplicate add event on devpath "\x00"`},
- {"TargetError", TargetError{
+ {"EBadTarget", (&Event{
Action: uevent.KOBJ_UNBIND,
DevPath: "\x00",
- }, `unexpected unbind event on devpath "\x00"`},
+ }).NewError(EBadTarget, nil),
+ `unexpected unbind event on devpath "\x00"`},
- {"RemoveStateError", RemoveStateError{
- State: StateBound,
+ {"ERemoveState", (&Event{
+ DevPath: "\x00",
+ }).NewError(ERemoveState, &Object{
+ State: StateBound,
+ }), `remove event targeting devpath "\x00" in state 2`},
+ {"ERemoveState invalid", (&Event{
DevPath: "\x00",
- }, `remove event targeting devpath "\x00" in state 2`},
+ }).NewError(ERemoveState, nil),
+ "invalid remove event error"},
- {"BindStateError", BindStateError{
- State: StateBound,
+ {"EBindState", (&Event{
DevPath: "\x00",
- }, `bind event targeting devpath "\x00" in state 2`},
+ }).NewError(EBindState, &Object{
+ State: StateBound,
+ }), `bind event targeting devpath "\x00" in state 2`},
+ {"EBindState invalid", (&Event{
+ DevPath: "\x00",
+ }).NewError(EBindState, nil),
+ "invalid bind state error"},
- {"UnbindStateError", UnbindStateError{
- State: StateNew,
+ {"EUnbindState", (&Event{
+ DevPath: "\x00",
+ }).NewError(EUnbindState, &Object{
+ State: StateNew,
+ }), `unbind event targeting devpath "\x00" in state 1`},
+ {"EUnbindState invalid", (&Event{
DevPath: "\x00",
- }, `unbind event targeting devpath "\x00" in state 1`},
+ }).NewError(EUnbindState, nil),
+ "invalid unbind state error"},
- {"MalformedMoveError", MalformedMoveError{
+ {"EMalformedMove", (&Event{
DevPath: "\x00",
- }, `move event targeting devpath "\x00" missing DEVPATH_OLD`},
+ }).NewError(EMalformedMove, nil),
+ `move event targeting devpath "\x00" missing DEVPATH_OLD`},
- {"UnexpectedOfflineError", UnexpectedOfflineError{
+ {"EUnexpectedOffline", (&Event{
DevPath: "\x00",
- }, `online event targeting devpath "\x00"`},
- {"UnexpectedOfflineError offline", UnexpectedOfflineError{
+ }).NewError(EUnexpectedOffline, &Object{
+ Offline: false,
+ }), `online event targeting devpath "\x00"`},
+ {"EUnexpectedOffline offline", (&Event{
DevPath: "\x00",
+ }).NewError(EUnexpectedOffline, &Object{
Offline: true,
- }, `offline event targeting devpath "\x00"`},
+ }), `offline event targeting devpath "\x00"`},
+ {"EUnexpectedOffline invalid", (&Event{
+ DevPath: "\x00",
+ }).NewError(EUnexpectedOffline, nil),
+ "invalid unexpected offline error"},
+
+ {"EventError invalid", &EventError{Kind: 0xbad},
+ "invalid event error kind 2989"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
@@ -1648,13 +1708,13 @@ func TestConsumeSample(t *testing.T) {
delete(want, "/devices/virtual/net/lo")
o.DevPath = "/devices/virtual/net/_lo"
o.Env["INTERFACE"] = "_lo"
- want["/devices/virtual/net/_lo"] = &o
+ want["/devices/virtual/net/_lo"] = o
}, nil},
{"cpu", testdata02, func(want map[string]*Object) {
o := want["/devices/system/machinecheck/machinecheck0"].Clone()
o.State = StateNew
- want["/devices/system/machinecheck/machinecheck0"] = &o
+ want["/devices/system/machinecheck/machinecheck0"] = o
}, nil},
{"loop", testdata03, func(want map[string]*Object) {