From 3712074675f2522ce125ecf534ff14dc94d0a812 Mon Sep 17 00:00:00 2001 From: Hans Moog <3293976+hmoog@users.noreply.github.com> Date: Fri, 1 Mar 2024 01:54:57 +0100 Subject: [PATCH 1/4] Feat: Runtime inspection framework --- runtime/inspection/inspected_object.go | 176 +++++++++++++++++++++++++ runtime/inspection/map_inspector.go | 17 +++ runtime/inspection/session.go | 15 +++ runtime/inspection/set_inspector.go | 17 +++ 4 files changed, 225 insertions(+) create mode 100644 runtime/inspection/inspected_object.go create mode 100644 runtime/inspection/map_inspector.go create mode 100644 runtime/inspection/session.go create mode 100644 runtime/inspection/set_inspector.go diff --git a/runtime/inspection/inspected_object.go b/runtime/inspection/inspected_object.go new file mode 100644 index 00000000..f2fdc79a --- /dev/null +++ b/runtime/inspection/inspected_object.go @@ -0,0 +1,176 @@ +package inspection + +import ( + "fmt" + "regexp" + "strings" + "unicode" + + "github.com/iotaledger/hive.go/ds/orderedmap" + "github.com/iotaledger/hive.go/lo" + "github.com/iotaledger/hive.go/stringify" +) + +// InspectedObject is an interface that is used to represent an object that has been inspected. +type InspectedObject interface { + // Type returns the type of the object. + Type() string + + // InstanceID returns the instance identifier of the object. + InstanceID() string + + // Add adds a child object to the inspected object. + Add(name string, instance any, inspectManually ...func(object InspectedObject)) + + // Get returns the child object with the given name. + Get(name string) (object InspectedObject, exists bool) + + // ForEach iterates through all child objects of the inspected object. + ForEach(callback func(name string, child InspectedObject)) + + // ReachableObjectsCount returns the number of reachable objects from the inspected object. + ReachableObjectsCount() int + + // String returns a string representation of the inspected object. + String() string +} + +// NewInspectedObject creates a new inspected object from the given instance and inspect function. +func NewInspectedObject(instance any, inspect func(InspectedObject), session ...Session) InspectedObject { + o := &inspectedObject{ + instance: instance, + childObjects: orderedmap.New[string, InspectedObject](), + session: lo.First(session, make(Session)), + } + + if o.inspected = o.session.FirstOccurrence(instance); o.inspected { + inspect(o) + } + + return o +} + +// inspectedObject is an implementation of the InspectedObject interface. +type inspectedObject struct { + instance any + childObjects *orderedmap.OrderedMap[string, InspectedObject] + session Session + inspected bool +} + +// Type returns the type of the object. +func (i *inspectedObject) Type() string { + runes := []rune(regexp.MustCompile(`[^.]+\.([^[]+).*`).ReplaceAllString(fmt.Sprintf("%T", i.instance), "${1}")) + runes[0] = unicode.ToUpper(runes[0]) + + return string(runes) +} + +// InstanceID returns the instance identifier of the object. +func (i *inspectedObject) InstanceID() string { + type named interface { + LogName() string + } + + if namedInstance, isNamed := i.instance.(named); isNamed { + return namedInstance.LogName() + } + + return fmt.Sprintf("%p", i.instance) +} + +// Add adds a child object to the inspected object. +func (i *inspectedObject) Add(name string, instance any, inspectManually ...func(object InspectedObject)) { + type inspectable interface { + Inspect(session ...Session) InspectedObject + } + + if stringify.IsInterfaceNil(instance) { + i.childObjects.Set(name, nil) + } else if len(inspectManually) >= 1 { + i.childObjects.Set(name, NewInspectedObject(instance, inspectManually[0], i.session)) + } else if inspectableInstance, isInspectable := instance.(inspectable); isInspectable { + i.childObjects.Set(name, inspectableInstance.Inspect(i.session)) + } else { + panic("added object does not have an 'Inspect(session ...Session) InspectedObject' method - please provide a manual inspection function") + } +} + +// Get returns the child object with the given name. +func (i *inspectedObject) Get(name string) (object InspectedObject, exists bool) { + value, exists := i.childObjects.Get(name) + if exists && value != nil { + return value.(InspectedObject), true + } + + return nil, false +} + +// ForEach iterates through all child objects of the inspected object. +func (i *inspectedObject) ForEach(callback func(name string, child InspectedObject)) { + i.childObjects.ForEach(func(key string, value InspectedObject) bool { + callback(key, value) + + return true + }) +} + +// ReachableObjectsCount returns the number of reachable objects from the inspected object. +func (i *inspectedObject) ReachableObjectsCount() int { + count := 1 + + i.childObjects.ForEach(func(_ string, childObject InspectedObject) bool { + if childObject != nil { + count += childObject.ReachableObjectsCount() + } + + return true + }) + + return count +} + +// String returns a string representation of the inspected object. +func (i *inspectedObject) String() string { + return i.indentedString(0) +} + +// indentedString returns a string representation of the inspected object with the given indentation. +func (i *inspectedObject) indentedString(indent int) string { + if i == nil { + return "nil" + } + + var typeString string + if instanceID, typeName := i.InstanceID(), i.Type(); typeName == instanceID { + typeString = typeName + } else { + typeString = typeName + "(" + instanceID + ")" + } + + if !i.inspected { + return typeString + " {...}" + } + + childOutputs := make([]string, 0) + i.ForEach(func(key string, value InspectedObject) { + if value == nil { + childOutputs = append(childOutputs, strings.Repeat(" ", (indent+1)*indentationSize)+key+": nil") + } else if objectValue, ok := value.(*inspectedObject); !ok { + panic("this should never happen but linter requires type cast check") + } else if value.Type() == key || value.InstanceID() == key { + childOutputs = append(childOutputs, strings.Repeat(" ", (indent+1)*indentationSize)+objectValue.indentedString(indent+1)) + } else { + childOutputs = append(childOutputs, strings.Repeat(" ", (indent+1)*indentationSize)+key+": "+objectValue.indentedString(indent+1)) + } + }) + + if len(childOutputs) == 0 { + return typeString + " {}" + } + + return typeString + " {\n" + strings.Join(childOutputs, ",\n") + "\n" + strings.Repeat(" ", (indent)*indentationSize) + "}" +} + +// indentationSize defines the size of the indentation. +const indentationSize = 2 diff --git a/runtime/inspection/map_inspector.go b/runtime/inspection/map_inspector.go new file mode 100644 index 00000000..4c57d384 --- /dev/null +++ b/runtime/inspection/map_inspector.go @@ -0,0 +1,17 @@ +package inspection + +// MapInspector is a utility function that can be used to create a manual inspection function for a map. +func MapInspector[K comparable, V any](mapToInspect mapInterface[K, V], inspect func(inspectedMap InspectedObject, key K, value V)) func(inspectedMap InspectedObject) { + return func(inspectedMap InspectedObject) { + mapToInspect.ForEach(func(key K, value V) bool { + inspect(inspectedMap, key, value) + + return true + }) + } +} + +// mapInterface is an interface that is used to iterate over a map of elements. +type mapInterface[K comparable, V any] interface { + ForEach(consumer func(key K, value V) bool) +} diff --git a/runtime/inspection/session.go b/runtime/inspection/session.go new file mode 100644 index 00000000..be03ae4f --- /dev/null +++ b/runtime/inspection/session.go @@ -0,0 +1,15 @@ +package inspection + +// Session is used to track instances of objects that have already been inspected. +type Session map[any]bool + +// FirstOccurrence checks if the given instance has already been inspected. +func (s Session) FirstOccurrence(instance any) bool { + if s[instance] { + return false + } + + s[instance] = true + + return true +} diff --git a/runtime/inspection/set_inspector.go b/runtime/inspection/set_inspector.go new file mode 100644 index 00000000..24947f1c --- /dev/null +++ b/runtime/inspection/set_inspector.go @@ -0,0 +1,17 @@ +package inspection + +// SetInspector is a utility function that can be used to create a manual inspection function for a set. +func SetInspector[T comparable](setToInspect setInterface[T], inspect func(inspectedSet InspectedObject, element T)) func(inspectedSet InspectedObject) { + return func(inspectedSet InspectedObject) { + _ = setToInspect.ForEach(func(element T) error { + inspect(inspectedSet, element) + + return nil + }) + } +} + +// setInterface is an interface that is used to iterate over a setInterface of elements. +type setInterface[T comparable] interface { + ForEach(consumer func(element T) error) error +} From d7d3948e758a9355211db7a25ff472e36424d8ac Mon Sep 17 00:00:00 2001 From: Hans Moog <3293976+hmoog@users.noreply.github.com> Date: Fri, 1 Mar 2024 02:21:32 +0100 Subject: [PATCH 2/4] Fix: fixed linter error --- runtime/inspection/inspected_object.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/runtime/inspection/inspected_object.go b/runtime/inspection/inspected_object.go index f2fdc79a..b69eefa2 100644 --- a/runtime/inspection/inspected_object.go +++ b/runtime/inspection/inspected_object.go @@ -98,9 +98,10 @@ func (i *inspectedObject) Add(name string, instance any, inspectManually ...func // Get returns the child object with the given name. func (i *inspectedObject) Get(name string) (object InspectedObject, exists bool) { - value, exists := i.childObjects.Get(name) - if exists && value != nil { - return value.(InspectedObject), true + if value, valueExists := i.childObjects.Get(name); valueExists && value != nil { + object, exists = value.(InspectedObject) + + return object, exists } return nil, false From 9db2c7d71a150aca718277118c118421e6515f31 Mon Sep 17 00:00:00 2001 From: Hans Moog <3293976+hmoog@users.noreply.github.com> Date: Fri, 1 Mar 2024 02:24:09 +0100 Subject: [PATCH 3/4] Fix: fix linter --- runtime/inspection/inspected_object.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/runtime/inspection/inspected_object.go b/runtime/inspection/inspected_object.go index b69eefa2..a36b013a 100644 --- a/runtime/inspection/inspected_object.go +++ b/runtime/inspection/inspected_object.go @@ -98,13 +98,7 @@ func (i *inspectedObject) Add(name string, instance any, inspectManually ...func // Get returns the child object with the given name. func (i *inspectedObject) Get(name string) (object InspectedObject, exists bool) { - if value, valueExists := i.childObjects.Get(name); valueExists && value != nil { - object, exists = value.(InspectedObject) - - return object, exists - } - - return nil, false + return i.childObjects.Get(name) } // ForEach iterates through all child objects of the inspected object. From eea9bb54a25606c4a638eb53dd6ecaa21a872194 Mon Sep 17 00:00:00 2001 From: Hans Moog <3293976+hmoog@users.noreply.github.com> Date: Fri, 1 Mar 2024 02:51:24 +0100 Subject: [PATCH 4/4] Feat: refactored library --- runtime/inspection/inspectable.go | 7 +++++++ runtime/inspection/inspected_object.go | 6 +----- 2 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 runtime/inspection/inspectable.go diff --git a/runtime/inspection/inspectable.go b/runtime/inspection/inspectable.go new file mode 100644 index 00000000..467bcb2f --- /dev/null +++ b/runtime/inspection/inspectable.go @@ -0,0 +1,7 @@ +package inspection + +// Inspectable is an interface that is used to represent an object that can be automatically inspected. +type Inspectable interface { + // Inspect returns the inspected version of the object. + Inspect(session ...Session) InspectedObject +} diff --git a/runtime/inspection/inspected_object.go b/runtime/inspection/inspected_object.go index a36b013a..6e4e901a 100644 --- a/runtime/inspection/inspected_object.go +++ b/runtime/inspection/inspected_object.go @@ -81,15 +81,11 @@ func (i *inspectedObject) InstanceID() string { // Add adds a child object to the inspected object. func (i *inspectedObject) Add(name string, instance any, inspectManually ...func(object InspectedObject)) { - type inspectable interface { - Inspect(session ...Session) InspectedObject - } - if stringify.IsInterfaceNil(instance) { i.childObjects.Set(name, nil) } else if len(inspectManually) >= 1 { i.childObjects.Set(name, NewInspectedObject(instance, inspectManually[0], i.session)) - } else if inspectableInstance, isInspectable := instance.(inspectable); isInspectable { + } else if inspectableInstance, isInspectable := instance.(Inspectable); isInspectable { i.childObjects.Set(name, inspectableInstance.Inspect(i.session)) } else { panic("added object does not have an 'Inspect(session ...Session) InspectedObject' method - please provide a manual inspection function")