From e035c7cf07f3745ceb00e157f67197fca13e31c4 Mon Sep 17 00:00:00 2001 From: hovsep Date: Mon, 7 Oct 2024 16:23:02 +0300 Subject: [PATCH 1/8] Minor: comment fixed --- component/activation_result.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component/activation_result.go b/component/activation_result.go index 9a64eda..3b4982e 100644 --- a/component/activation_result.go +++ b/component/activation_result.go @@ -32,7 +32,7 @@ const ( // ActivationCodePanicked : component is activated, but panicked ActivationCodePanicked - // ActivationCodeWaitingForInputs : component waits for specific inputs, but all input signals in current activation cycle may be cleared (default behaviour) + // ActivationCodeWaitingForInputsClear : component waits for specific inputs, but all input signals in current activation cycle may be cleared (default behaviour) ActivationCodeWaitingForInputsClear // ActivationCodeWaitingForInputsKeep : component waits for specific inputs, but wants to keep current input signals for the next cycle From f63f2ea8e6bbe10eb52d3b295d909bf1df755b5b Mon Sep 17 00:00:00 2001 From: hovsep Date: Wed, 9 Oct 2024 15:09:47 +0300 Subject: [PATCH 2/8] Extract common name property --- common/named_entity.go | 15 +++++++++ common/named_entity_test.go | 65 ++++++++++++++++++++++++++++++++++++ component/collection_test.go | 9 +---- component/component.go | 14 +++----- component/component_test.go | 51 +++++----------------------- fmesh.go | 14 +++----- fmesh_test.go | 39 +++++----------------- port/collection_test.go | 44 ++++-------------------- port/port.go | 13 +++----- port/port_test.go | 47 ++++---------------------- 10 files changed, 124 insertions(+), 187 deletions(-) create mode 100644 common/named_entity.go create mode 100644 common/named_entity_test.go diff --git a/common/named_entity.go b/common/named_entity.go new file mode 100644 index 0000000..ea46bcd --- /dev/null +++ b/common/named_entity.go @@ -0,0 +1,15 @@ +package common + +type NamedEntity struct { + name string +} + +// NewNamedEntity constructor +func NewNamedEntity(name string) NamedEntity { + return NamedEntity{name: name} +} + +// Name getter +func (n NamedEntity) Name() string { + return n.name +} diff --git a/common/named_entity_test.go b/common/named_entity_test.go new file mode 100644 index 0000000..eb94d59 --- /dev/null +++ b/common/named_entity_test.go @@ -0,0 +1,65 @@ +package common + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewNamedEntity(t *testing.T) { + type args struct { + name string + } + tests := []struct { + name string + args args + want NamedEntity + }{ + { + name: "empty name is valid", + args: args{ + name: "", + }, + want: NamedEntity{ + name: "", + }, + }, + { + name: "with name", + args: args{ + name: "component1", + }, + want: NamedEntity{ + name: "component1", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, NewNamedEntity(tt.args.name)) + }) + } +} + +func TestNamedEntity_Name(t *testing.T) { + tests := []struct { + name string + namedEntity NamedEntity + want string + }{ + { + name: "empty name", + namedEntity: NewNamedEntity(""), + want: "", + }, + { + name: "with name", + namedEntity: NewNamedEntity("port2"), + want: "port2", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.namedEntity.Name()) + }) + } +} diff --git a/component/collection_test.go b/component/collection_test.go index 306b53d..f891ac1 100644 --- a/component/collection_test.go +++ b/component/collection_test.go @@ -1,7 +1,6 @@ package component import ( - "github.com/hovsep/fmesh/port" "github.com/stretchr/testify/assert" "testing" ) @@ -22,13 +21,7 @@ func TestCollection_ByName(t *testing.T) { args: args{ name: "c2", }, - want: &Component{ - name: "c2", - description: "", - inputs: port.Collection{}, - outputs: port.Collection{}, - f: nil, - }, + want: New("c2"), }, { name: "component not found", diff --git a/component/component.go b/component/component.go index 2161d70..d3ef28b 100644 --- a/component/component.go +++ b/component/component.go @@ -3,6 +3,7 @@ package component import ( "errors" "fmt" + "github.com/hovsep/fmesh/common" "github.com/hovsep/fmesh/port" ) @@ -10,7 +11,7 @@ type ActivationFunc func(inputs port.Collection, outputs port.Collection) error // Component defines a main building block of FMesh type Component struct { - name string + common.NamedEntity description string inputs port.Collection outputs port.Collection @@ -20,9 +21,9 @@ type Component struct { // New creates initialized component func New(name string) *Component { return &Component{ - name: name, - inputs: port.NewCollection(), - outputs: port.NewCollection(), + NamedEntity: common.NewNamedEntity(name), + inputs: port.NewCollection(), + outputs: port.NewCollection(), } } @@ -62,11 +63,6 @@ func (c *Component) WithActivationFunc(f ActivationFunc) *Component { return c } -// Name getter -func (c *Component) Name() string { - return c.name -} - // Description getter func (c *Component) Description() string { return c.description diff --git a/component/component_test.go b/component/component_test.go index 3bbac18..83209cd 100644 --- a/component/component_test.go +++ b/component/component_test.go @@ -2,6 +2,7 @@ package component import ( "errors" + "github.com/hovsep/fmesh/common" "github.com/hovsep/fmesh/port" "github.com/hovsep/fmesh/signal" "github.com/stretchr/testify/assert" @@ -22,26 +23,14 @@ func TestNewComponent(t *testing.T) { args: args{ name: "", }, - want: &Component{ - name: "", - description: "", - inputs: port.Collection{}, - outputs: port.Collection{}, - f: nil, - }, + want: New(""), }, { name: "with name", args: args{ name: "multiplier", }, - want: &Component{ - name: "multiplier", - description: "", - inputs: port.Collection{}, - outputs: port.Collection{}, - f: nil, - }, + want: New("multiplier"), }, } for _, tt := range tests { @@ -51,30 +40,6 @@ func TestNewComponent(t *testing.T) { } } -func TestComponent_Name(t *testing.T) { - tests := []struct { - name string - component *Component - want string - }{ - { - name: "empty name", - component: New(""), - want: "", - }, - { - name: "with name", - component: New("c1"), - want: "c1", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, tt.component.Name()) - }) - } -} - func TestComponent_Description(t *testing.T) { tests := []struct { name string @@ -266,7 +231,7 @@ func TestComponent_WithDescription(t *testing.T) { description: "descr", }, want: &Component{ - name: "c1", + NamedEntity: common.NewNamedEntity("c1"), description: "descr", inputs: port.Collection{}, outputs: port.Collection{}, @@ -298,7 +263,7 @@ func TestComponent_WithInputs(t *testing.T) { portNames: []string{"p1", "p2"}, }, want: &Component{ - name: "c1", + NamedEntity: common.NewNamedEntity("c1"), description: "", inputs: port.Collection{ "p1": port.New("p1"), @@ -315,7 +280,7 @@ func TestComponent_WithInputs(t *testing.T) { portNames: nil, }, want: &Component{ - name: "c1", + NamedEntity: common.NewNamedEntity("c1"), description: "", inputs: port.Collection{}, outputs: port.Collection{}, @@ -347,7 +312,7 @@ func TestComponent_WithOutputs(t *testing.T) { portNames: []string{"p1", "p2"}, }, want: &Component{ - name: "c1", + NamedEntity: common.NewNamedEntity("c1"), description: "", inputs: port.Collection{}, outputs: port.Collection{ @@ -364,7 +329,7 @@ func TestComponent_WithOutputs(t *testing.T) { portNames: nil, }, want: &Component{ - name: "c1", + NamedEntity: common.NewNamedEntity("c1"), description: "", inputs: port.Collection{}, outputs: port.Collection{}, diff --git a/fmesh.go b/fmesh.go index 8c02f88..7538f93 100644 --- a/fmesh.go +++ b/fmesh.go @@ -1,6 +1,7 @@ package fmesh import ( + "github.com/hovsep/fmesh/common" "github.com/hovsep/fmesh/component" "github.com/hovsep/fmesh/cycle" "sync" @@ -22,7 +23,7 @@ var defaultConfig = Config{ // FMesh is the functional mesh type FMesh struct { - name string + common.NamedEntity description string components component.Collection config Config @@ -31,17 +32,12 @@ type FMesh struct { // New creates a new f-mesh func New(name string) *FMesh { return &FMesh{ - name: name, - components: component.NewCollection(), - config: defaultConfig, + NamedEntity: common.NewNamedEntity(name), + components: component.NewCollection(), + config: defaultConfig, } } -// Name getter -func (fm *FMesh) Name() string { - return fm.name -} - // Description getter func (fm *FMesh) Description() string { return fm.description diff --git a/fmesh_test.go b/fmesh_test.go index e582de9..b86f670 100644 --- a/fmesh_test.go +++ b/fmesh_test.go @@ -2,6 +2,7 @@ package fmesh import ( "errors" + "github.com/hovsep/fmesh/common" "github.com/hovsep/fmesh/component" "github.com/hovsep/fmesh/cycle" "github.com/hovsep/fmesh/port" @@ -35,9 +36,9 @@ func TestNew(t *testing.T) { name: "fm1", }, want: &FMesh{ - name: "fm1", - components: component.Collection{}, - config: defaultConfig, + NamedEntity: common.NewNamedEntity("fm1"), + components: component.Collection{}, + config: defaultConfig, }, }, } @@ -65,7 +66,7 @@ func TestFMesh_WithDescription(t *testing.T) { description: "", }, want: &FMesh{ - name: "fm1", + NamedEntity: common.NewNamedEntity("fm1"), description: "", components: component.Collection{}, config: defaultConfig, @@ -78,7 +79,7 @@ func TestFMesh_WithDescription(t *testing.T) { description: "descr", }, want: &FMesh{ - name: "fm1", + NamedEntity: common.NewNamedEntity("fm1"), description: "descr", components: component.Collection{}, config: defaultConfig, @@ -112,8 +113,8 @@ func TestFMesh_WithConfig(t *testing.T) { }, }, want: &FMesh{ - name: "fm1", - components: component.Collection{}, + NamedEntity: common.NewNamedEntity("fm1"), + components: component.Collection{}, config: Config{ ErrorHandlingStrategy: IgnoreAll, CyclesLimit: 9999, @@ -197,30 +198,6 @@ func TestFMesh_WithComponents(t *testing.T) { } } -func TestFMesh_Name(t *testing.T) { - tests := []struct { - name string - fm *FMesh - want string - }{ - { - name: "empty name is valid", - fm: New(""), - want: "", - }, - { - name: "with name", - fm: New("fm1"), - want: "fm1", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, tt.fm.Name()) - }) - } -} - func TestFMesh_Description(t *testing.T) { tests := []struct { name string diff --git a/port/collection_test.go b/port/collection_test.go index e33ebb3..eef3819 100644 --- a/port/collection_test.go +++ b/port/collection_test.go @@ -81,7 +81,7 @@ func TestCollection_ByName(t *testing.T) { args: args{ name: "p1", }, - want: &Port{name: "p1", pipes: Group{}, signals: signal.Group{}}, + want: New("p1"), }, { name: "port with signals found", @@ -89,11 +89,7 @@ func TestCollection_ByName(t *testing.T) { args: args{ name: "p2", }, - want: &Port{ - name: "p2", - signals: signal.NewGroup().With(signal.New(12)), - pipes: Group{}, - }, + want: New("p2").WithSignals(signal.New(12)), }, { name: "port not found", @@ -132,32 +128,15 @@ func TestCollection_ByNames(t *testing.T) { args: args{ names: []string{"p1"}, }, - want: Collection{ - "p1": &Port{ - name: "p1", - pipes: Group{}, - signals: signal.Group{}, - }, - }, + want: NewCollection().With(New("p1")), }, { name: "multiple ports found", - ports: NewCollection().With(NewGroup("p1", "p2")...), + ports: NewCollection().With(NewGroup("p1", "p2", "p3", "p4")...), args: args{ names: []string{"p1", "p2"}, }, - want: Collection{ - "p1": &Port{ - name: "p1", - pipes: Group{}, - signals: signal.Group{}, - }, - "p2": &Port{ - name: "p2", - pipes: Group{}, - signals: signal.Group{}, - }, - }, + want: NewCollection().With(NewGroup("p1", "p2")...), }, { name: "single port not found", @@ -173,18 +152,7 @@ func TestCollection_ByNames(t *testing.T) { args: args{ names: []string{"p1", "p2", "p3"}, }, - want: Collection{ - "p1": &Port{ - name: "p1", - pipes: Group{}, - signals: signal.Group{}, - }, - "p2": &Port{ - name: "p2", - pipes: Group{}, - signals: signal.Group{}, - }, - }, + want: NewCollection().With(NewGroup("p1", "p2")...), }, } for _, tt := range tests { diff --git a/port/port.go b/port/port.go index 5b26c4e..a9318d0 100644 --- a/port/port.go +++ b/port/port.go @@ -1,12 +1,13 @@ package port import ( + "github.com/hovsep/fmesh/common" "github.com/hovsep/fmesh/signal" ) // Port defines a connectivity point of a component type Port struct { - name string + common.NamedEntity signals signal.Group //Signal buffer pipes Group //Outbound pipes } @@ -14,15 +15,11 @@ type Port struct { // New creates a new port func New(name string) *Port { return &Port{ - name: name, - pipes: NewGroup(), - signals: signal.NewGroup(), + NamedEntity: common.NewNamedEntity(name), + pipes: NewGroup(), + signals: signal.NewGroup(), } -} -// Name getter -func (p *Port) Name() string { - return p.name } // Signals getter diff --git a/port/port_test.go b/port/port_test.go index 98b12cb..b82bb94 100644 --- a/port/port_test.go +++ b/port/port_test.go @@ -63,12 +63,12 @@ func TestPort_Clear(t *testing.T) { { name: "happy path", before: New("p").WithSignals(signal.New(111)), - after: &Port{name: "p", pipes: Group{}, signals: signal.Group{}}, + after: New("p"), }, { name: "cleaning empty port", before: New("emptyPort"), - after: &Port{name: "emptyPort", pipes: Group{}, signals: signal.Group{}}, + after: New("emptyPort"), }, } for _, tt := range tests { @@ -94,11 +94,7 @@ func TestPort_PipeTo(t *testing.T) { { name: "happy path", before: p1, - after: &Port{ - name: "p1", - pipes: Group{p2, p3}, - signals: signal.Group{}, - }, + after: New("p1").withPipes(p2, p3), args: args{ toPorts: []*Port{p2, p3}, }, @@ -106,11 +102,7 @@ func TestPort_PipeTo(t *testing.T) { { name: "invalid ports are ignored", before: p4, - after: &Port{ - name: "p4", - pipes: Group{p2}, - signals: signal.Group{}, - }, + after: New("p4").withPipes(p2), args: args{ toPorts: []*Port{p2, nil}, }, @@ -183,25 +175,6 @@ func TestPort_PutSignals(t *testing.T) { } } -func TestPort_Name(t *testing.T) { - tests := []struct { - name string - port *Port - want string - }{ - { - name: "happy path", - port: New("p777"), - want: "p777", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, tt.port.Name()) - }) - } -} - func TestNewPort(t *testing.T) { type args struct { name string @@ -216,22 +189,14 @@ func TestNewPort(t *testing.T) { args: args{ name: "", }, - want: &Port{ - name: "", - pipes: Group{}, - signals: signal.Group{}, - }, + want: New(""), }, { name: "with name", args: args{ name: "p1", }, - want: &Port{ - name: "p1", - pipes: Group{}, - signals: signal.Group{}, - }, + want: New("p1"), }, } for _, tt := range tests { From c0ec46810a5aac753abd7b7aa4c1172f4afbc6ce Mon Sep 17 00:00:00 2001 From: hovsep Date: Wed, 9 Oct 2024 15:44:52 +0300 Subject: [PATCH 3/8] Extract common description property --- common/described_entity.go | 15 ++++++++ common/described_entity_test.go | 65 +++++++++++++++++++++++++++++++++ component/component.go | 15 +++----- component/component_test.go | 64 ++++++++++---------------------- fmesh.go | 13 ++----- fmesh_test.go | 40 ++++---------------- 6 files changed, 117 insertions(+), 95 deletions(-) create mode 100644 common/described_entity.go create mode 100644 common/described_entity_test.go diff --git a/common/described_entity.go b/common/described_entity.go new file mode 100644 index 0000000..8696a49 --- /dev/null +++ b/common/described_entity.go @@ -0,0 +1,15 @@ +package common + +type DescribedEntity struct { + description string +} + +// NewDescribedEntity constructor +func NewDescribedEntity(description string) DescribedEntity { + return DescribedEntity{description: description} +} + +// Description getter +func (d DescribedEntity) Description() string { + return d.description +} diff --git a/common/described_entity_test.go b/common/described_entity_test.go new file mode 100644 index 0000000..91e5627 --- /dev/null +++ b/common/described_entity_test.go @@ -0,0 +1,65 @@ +package common + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewDescribedEntity(t *testing.T) { + type args struct { + description string + } + tests := []struct { + name string + args args + want DescribedEntity + }{ + { + name: "empty description", + args: args{ + description: "", + }, + want: DescribedEntity{ + description: "", + }, + }, + { + name: "with description", + args: args{ + description: "component1 is used to generate logs", + }, + want: DescribedEntity{ + description: "component1 is used to generate logs", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, NewDescribedEntity(tt.args.description)) + }) + } +} + +func TestDescribedEntity_Description(t *testing.T) { + tests := []struct { + name string + describedEntity DescribedEntity + want string + }{ + { + name: "empty description", + describedEntity: NewDescribedEntity(""), + want: "", + }, + { + name: "with description", + describedEntity: NewDescribedEntity("component2 is used to handle errors"), + want: "component2 is used to handle errors", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.describedEntity.Description()) + }) + } +} diff --git a/component/component.go b/component/component.go index d3ef28b..957bc32 100644 --- a/component/component.go +++ b/component/component.go @@ -12,10 +12,10 @@ type ActivationFunc func(inputs port.Collection, outputs port.Collection) error // Component defines a main building block of FMesh type Component struct { common.NamedEntity - description string - inputs port.Collection - outputs port.Collection - f ActivationFunc + common.DescribedEntity + inputs port.Collection + outputs port.Collection + f ActivationFunc } // New creates initialized component @@ -29,7 +29,7 @@ func New(name string) *Component { // WithDescription sets a description func (c *Component) WithDescription(description string) *Component { - c.description = description + c.DescribedEntity = common.NewDescribedEntity(description) return c } @@ -63,11 +63,6 @@ func (c *Component) WithActivationFunc(f ActivationFunc) *Component { return c } -// Description getter -func (c *Component) Description() string { - return c.description -} - // Inputs getter func (c *Component) Inputs() port.Collection { return c.inputs diff --git a/component/component_test.go b/component/component_test.go index 83209cd..ebf23fe 100644 --- a/component/component_test.go +++ b/component/component_test.go @@ -40,30 +40,6 @@ func TestNewComponent(t *testing.T) { } } -func TestComponent_Description(t *testing.T) { - tests := []struct { - name string - component *Component - want string - }{ - { - name: "no description", - component: New("c1"), - want: "", - }, - { - name: "with description", - component: New("c1").WithDescription("descr"), - want: "descr", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, tt.component.Description()) - }) - } -} - func TestComponent_FlushOutputs(t *testing.T) { sink := port.New("sink") @@ -231,11 +207,11 @@ func TestComponent_WithDescription(t *testing.T) { description: "descr", }, want: &Component{ - NamedEntity: common.NewNamedEntity("c1"), - description: "descr", - inputs: port.Collection{}, - outputs: port.Collection{}, - f: nil, + NamedEntity: common.NewNamedEntity("c1"), + DescribedEntity: common.NewDescribedEntity("descr"), + inputs: port.Collection{}, + outputs: port.Collection{}, + f: nil, }, }, } @@ -263,8 +239,8 @@ func TestComponent_WithInputs(t *testing.T) { portNames: []string{"p1", "p2"}, }, want: &Component{ - NamedEntity: common.NewNamedEntity("c1"), - description: "", + NamedEntity: common.NewNamedEntity("c1"), + DescribedEntity: common.NewDescribedEntity(""), inputs: port.Collection{ "p1": port.New("p1"), "p2": port.New("p2"), @@ -280,11 +256,11 @@ func TestComponent_WithInputs(t *testing.T) { portNames: nil, }, want: &Component{ - NamedEntity: common.NewNamedEntity("c1"), - description: "", - inputs: port.Collection{}, - outputs: port.Collection{}, - f: nil, + NamedEntity: common.NewNamedEntity("c1"), + DescribedEntity: common.NewDescribedEntity(""), + inputs: port.Collection{}, + outputs: port.Collection{}, + f: nil, }, }, } @@ -312,9 +288,9 @@ func TestComponent_WithOutputs(t *testing.T) { portNames: []string{"p1", "p2"}, }, want: &Component{ - NamedEntity: common.NewNamedEntity("c1"), - description: "", - inputs: port.Collection{}, + NamedEntity: common.NewNamedEntity("c1"), + DescribedEntity: common.NewDescribedEntity(""), + inputs: port.Collection{}, outputs: port.Collection{ "p1": port.New("p1"), "p2": port.New("p2"), @@ -329,11 +305,11 @@ func TestComponent_WithOutputs(t *testing.T) { portNames: nil, }, want: &Component{ - NamedEntity: common.NewNamedEntity("c1"), - description: "", - inputs: port.Collection{}, - outputs: port.Collection{}, - f: nil, + NamedEntity: common.NewNamedEntity("c1"), + DescribedEntity: common.NewDescribedEntity(""), + inputs: port.Collection{}, + outputs: port.Collection{}, + f: nil, }, }, } diff --git a/fmesh.go b/fmesh.go index 7538f93..1ef3cb0 100644 --- a/fmesh.go +++ b/fmesh.go @@ -24,9 +24,9 @@ var defaultConfig = Config{ // FMesh is the functional mesh type FMesh struct { common.NamedEntity - description string - components component.Collection - config Config + common.DescribedEntity + components component.Collection + config Config } // New creates a new f-mesh @@ -38,18 +38,13 @@ func New(name string) *FMesh { } } -// Description getter -func (fm *FMesh) Description() string { - return fm.description -} - func (fm *FMesh) Components() component.Collection { return fm.components } // WithDescription sets a description func (fm *FMesh) WithDescription(description string) *FMesh { - fm.description = description + fm.DescribedEntity = common.NewDescribedEntity(description) return fm } diff --git a/fmesh_test.go b/fmesh_test.go index b86f670..40bf0c9 100644 --- a/fmesh_test.go +++ b/fmesh_test.go @@ -66,10 +66,10 @@ func TestFMesh_WithDescription(t *testing.T) { description: "", }, want: &FMesh{ - NamedEntity: common.NewNamedEntity("fm1"), - description: "", - components: component.Collection{}, - config: defaultConfig, + NamedEntity: common.NewNamedEntity("fm1"), + DescribedEntity: common.NewDescribedEntity(""), + components: component.Collection{}, + config: defaultConfig, }, }, { @@ -79,10 +79,10 @@ func TestFMesh_WithDescription(t *testing.T) { description: "descr", }, want: &FMesh{ - NamedEntity: common.NewNamedEntity("fm1"), - description: "descr", - components: component.Collection{}, - config: defaultConfig, + NamedEntity: common.NewNamedEntity("fm1"), + DescribedEntity: common.NewDescribedEntity("descr"), + components: component.Collection{}, + config: defaultConfig, }, }, } @@ -198,30 +198,6 @@ func TestFMesh_WithComponents(t *testing.T) { } } -func TestFMesh_Description(t *testing.T) { - tests := []struct { - name string - fm *FMesh - want string - }{ - { - name: "empty description", - fm: New("fm1"), - want: "", - }, - { - name: "with description", - fm: New("fm1").WithDescription("descr"), - want: "descr", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, tt.fm.Description()) - }) - } -} - func TestFMesh_Run(t *testing.T) { tests := []struct { name string From 82fd291004184322de53e91b06109c03279127c4 Mon Sep 17 00:00:00 2001 From: hovsep Date: Wed, 9 Oct 2024 17:58:15 +0300 Subject: [PATCH 4/8] Minor: refactor traits --- common/described_entity.go | 4 ++-- common/named_entity.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/described_entity.go b/common/described_entity.go index 8696a49..df8fc7e 100644 --- a/common/described_entity.go +++ b/common/described_entity.go @@ -10,6 +10,6 @@ func NewDescribedEntity(description string) DescribedEntity { } // Description getter -func (d DescribedEntity) Description() string { - return d.description +func (e DescribedEntity) Description() string { + return e.description } diff --git a/common/named_entity.go b/common/named_entity.go index ea46bcd..55d3f8c 100644 --- a/common/named_entity.go +++ b/common/named_entity.go @@ -10,6 +10,6 @@ func NewNamedEntity(name string) NamedEntity { } // Name getter -func (n NamedEntity) Name() string { - return n.name +func (e NamedEntity) Name() string { + return e.name } From e02524b740e9fb825d2e8e653bf5fb373f976769 Mon Sep 17 00:00:00 2001 From: hovsep Date: Wed, 9 Oct 2024 17:58:29 +0300 Subject: [PATCH 5/8] New trait: labeled entity --- common/labeled_entity.go | 69 ++++++ common/labeled_entity_test.go | 419 ++++++++++++++++++++++++++++++++++ 2 files changed, 488 insertions(+) create mode 100644 common/labeled_entity.go create mode 100644 common/labeled_entity_test.go diff --git a/common/labeled_entity.go b/common/labeled_entity.go new file mode 100644 index 0000000..3df05d8 --- /dev/null +++ b/common/labeled_entity.go @@ -0,0 +1,69 @@ +package common + +type LabelsCollection map[string]string + +type LabeledEntity struct { + labels LabelsCollection +} + +// NewLabeledEntity constructor +func NewLabeledEntity(labels LabelsCollection) LabeledEntity { + + return LabeledEntity{labels: labels} +} + +// Labels getter +func (e *LabeledEntity) Labels() LabelsCollection { + return e.labels +} + +// SetLabels overwrites labels collection +func (e *LabeledEntity) SetLabels(labels LabelsCollection) { + e.labels = labels +} + +// AddLabel adds or updates(if label already exists) single label +func (e *LabeledEntity) AddLabel(label string, value string) { + if e.labels == nil { + e.labels = make(LabelsCollection) + } + e.labels[label] = value +} + +// AddLabels adds or updates(if label already exists) multiple labels +func (e *LabeledEntity) AddLabels(labels LabelsCollection) { + for label, value := range labels { + e.AddLabel(label, value) + } +} + +// DeleteLabel deletes given label +func (e *LabeledEntity) DeleteLabel(label string) { + delete(e.labels, label) +} + +// HasLabel returns true when entity has given label or false otherwise +func (e *LabeledEntity) HasLabel(label string) bool { + _, ok := e.labels[label] + return ok +} + +// HasAllLabels checks if entity has all labels +func (e *LabeledEntity) HasAllLabels(label ...string) bool { + for _, l := range label { + if !e.HasLabel(l) { + return false + } + } + return true +} + +// HasAnyLabel checks if entity has at least one of given labels +func (e *LabeledEntity) HasAnyLabel(label ...string) bool { + for _, l := range label { + if e.HasLabel(l) { + return true + } + } + return false +} diff --git a/common/labeled_entity_test.go b/common/labeled_entity_test.go new file mode 100644 index 0000000..6d45069 --- /dev/null +++ b/common/labeled_entity_test.go @@ -0,0 +1,419 @@ +package common + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewLabeledEntity(t *testing.T) { + type args struct { + labels LabelsCollection + } + tests := []struct { + name string + args args + want LabeledEntity + }{ + { + name: "empty labels", + args: args{ + labels: nil, + }, + want: LabeledEntity{ + labels: nil, + }, + }, + { + name: "with labels", + args: args{ + labels: LabelsCollection{ + "label1": "value1", + }, + }, + want: LabeledEntity{ + labels: LabelsCollection{ + "label1": "value1", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, NewLabeledEntity(tt.args.labels)) + }) + } +} + +func TestLabeledEntity_Labels(t *testing.T) { + tests := []struct { + name string + labeledEntity LabeledEntity + want LabelsCollection + }{ + { + name: "no labels", + labeledEntity: NewLabeledEntity(nil), + want: nil, + }, + { + name: "with labels", + labeledEntity: NewLabeledEntity(LabelsCollection{ + "l1": "v1", + "l2": "v2", + }), + want: LabelsCollection{ + "l1": "v1", + "l2": "v2", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.labeledEntity.Labels()) + }) + } +} + +func TestLabeledEntity_SetLabels(t *testing.T) { + type args struct { + labels LabelsCollection + } + tests := []struct { + name string + labeledEntity LabeledEntity + args args + assertions func(t *testing.T, labeledEntity LabeledEntity) + }{ + { + name: "setting to empty labels collection", + labeledEntity: NewLabeledEntity(nil), + args: args{ + labels: LabelsCollection{ + "l1": "v1", + "l2": "v2", + "l3": "v3", + }, + }, + assertions: func(t *testing.T, labeledEntity LabeledEntity) { + assert.Equal(t, LabelsCollection{ + "l1": "v1", + "l2": "v2", + "l3": "v3", + }, labeledEntity.Labels()) + }, + }, + { + name: "setting to non-empty labels collection", + labeledEntity: NewLabeledEntity(LabelsCollection{ + "l1": "v1", + "l2": "v2", + "l3": "v3", + "l99": "val1", + }), + args: args{ + labels: LabelsCollection{ + "l4": "v4", + "l5": "v5", + "l6": "v6", + "l99": "val2", + }, + }, + assertions: func(t *testing.T, labeledEntity LabeledEntity) { + assert.Equal(t, LabelsCollection{ + "l4": "v4", + "l5": "v5", + "l6": "v6", + "l99": "val2", + }, labeledEntity.Labels()) + }, + }, + { + name: "setting nil", + labeledEntity: NewLabeledEntity(LabelsCollection{ + "l1": "v1", + "l2": "v2", + "l3": "v3", + "l99": "val1", + }), + args: args{ + labels: nil, + }, + assertions: func(t *testing.T, labeledEntity LabeledEntity) { + assert.Nil(t, labeledEntity.Labels()) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.labeledEntity.SetLabels(tt.args.labels) + if tt.assertions != nil { + tt.assertions(t, tt.labeledEntity) + } + }) + } +} + +func TestLabeledEntity_AddLabel(t *testing.T) { + type args struct { + label string + value string + } + tests := []struct { + name string + labeledEntity LabeledEntity + args args + assertions func(t *testing.T, labeledEntity LabeledEntity) + }{ + { + name: "adding to empty labels collection", + labeledEntity: NewLabeledEntity(nil), + args: args{ + label: "l1", + value: "v1", + }, + assertions: func(t *testing.T, labeledEntity LabeledEntity) { + assert.Equal(t, LabelsCollection{ + "l1": "v1", + }, labeledEntity.Labels()) + }, + }, + { + name: "adding to non-empty labels collection", + labeledEntity: NewLabeledEntity(LabelsCollection{ + "l1": "v1", + }), + args: args{ + label: "l2", + value: "v2", + }, + assertions: func(t *testing.T, labeledEntity LabeledEntity) { + assert.Equal(t, LabelsCollection{ + "l1": "v1", + "l2": "v2", + }, labeledEntity.Labels()) + }, + }, + { + name: "overwriting a label", + labeledEntity: NewLabeledEntity(LabelsCollection{ + "l1": "v1", + "l2": "v2", + }), + args: args{ + label: "l2", + value: "v3", + }, + assertions: func(t *testing.T, labeledEntity LabeledEntity) { + assert.Equal(t, LabelsCollection{ + "l1": "v1", + "l2": "v3", + }, labeledEntity.Labels()) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.labeledEntity.AddLabel(tt.args.label, tt.args.value) + if tt.assertions != nil { + tt.assertions(t, tt.labeledEntity) + } + }) + } +} + +func TestLabeledEntity_AddLabels(t *testing.T) { + type args struct { + labels LabelsCollection + } + tests := []struct { + name string + labeledEntity LabeledEntity + args args + assertions func(t *testing.T, labeledEntity LabeledEntity) + }{ + { + name: "adding to non-empty labels collection", + labeledEntity: NewLabeledEntity(LabelsCollection{ + "l1": "v1", + "l2": "v2", + "l3": "v3", + }), + args: args{ + labels: LabelsCollection{ + "l3": "v100", + "l4": "v4", + "l5": "v5", + }, + }, + assertions: func(t *testing.T, labeledEntity LabeledEntity) { + assert.Equal(t, LabelsCollection{ + "l1": "v1", + "l2": "v2", + "l3": "v100", + "l4": "v4", + "l5": "v5", + }, labeledEntity.Labels()) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.labeledEntity.AddLabels(tt.args.labels) + if tt.assertions != nil { + tt.assertions(t, tt.labeledEntity) + } + }) + } +} + +func TestLabeledEntity_DeleteLabel(t *testing.T) { + type args struct { + label string + } + tests := []struct { + name string + labeledEntity LabeledEntity + args args + assertions func(t *testing.T, labeledEntity LabeledEntity) + }{ + { + name: "label found and deleted", + labeledEntity: NewLabeledEntity(LabelsCollection{ + "l1": "v1", + "l2": "v2", + }), + args: args{ + label: "l1", + }, + assertions: func(t *testing.T, labeledEntity LabeledEntity) { + assert.Equal(t, LabelsCollection{ + "l2": "v2", + }, labeledEntity.Labels()) + }, + }, + { + name: "label not found, no-op", + labeledEntity: NewLabeledEntity(LabelsCollection{ + "l1": "v1", + "l2": "v2", + }), + args: args{ + label: "l3", + }, + assertions: func(t *testing.T, labeledEntity LabeledEntity) { + assert.Equal(t, LabelsCollection{ + "l1": "v1", + "l2": "v2", + }, labeledEntity.Labels()) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.labeledEntity.DeleteLabel(tt.args.label) + if tt.assertions != nil { + tt.assertions(t, tt.labeledEntity) + } + }) + } +} + +func TestLabeledEntity_HasAllLabels(t *testing.T) { + type args struct { + label []string + } + tests := []struct { + name string + labeledEntity LabeledEntity + args args + want bool + }{ + { + name: "empty collection", + labeledEntity: NewLabeledEntity(nil), + args: args{ + label: []string{"l1"}, + }, + want: false, + }, + { + name: "has all labels", + labeledEntity: NewLabeledEntity(LabelsCollection{ + "l1": "v1", + "l2": "v2", + "l3": "v3", + }), + args: args{ + label: []string{"l1", "l2"}, + }, + want: true, + }, + { + name: "does not have all labels", + labeledEntity: NewLabeledEntity(LabelsCollection{ + "l1": "v1", + "l2": "v2", + "l3": "v3", + }), + args: args{ + label: []string{"l1", "l2", "l4"}, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.labeledEntity.HasAllLabels(tt.args.label...)) + }) + } +} + +func TestLabeledEntity_HasAnyLabel(t *testing.T) { + type args struct { + label []string + } + tests := []struct { + name string + labeledEntity LabeledEntity + args args + want bool + }{ + { + name: "empty collection", + labeledEntity: NewLabeledEntity(nil), + args: args{ + label: []string{"l1"}, + }, + want: false, + }, + { + name: "has some labels", + labeledEntity: NewLabeledEntity(LabelsCollection{ + "l1": "v1", + "l2": "v2", + "l3": "v3", + }), + args: args{ + label: []string{"l1", "l10"}, + }, + want: true, + }, + { + name: "does not have any of labels", + labeledEntity: NewLabeledEntity(LabelsCollection{ + "l1": "v1", + "l2": "v2", + "l3": "v3", + }), + args: args{ + label: []string{"l10", "l20", "l4"}, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.labeledEntity.HasAnyLabel(tt.args.label...)) + }) + } +} From f0bd98afb451cd04337a3a85ed20000cba7f3f7a Mon Sep 17 00:00:00 2001 From: hovsep Date: Wed, 9 Oct 2024 18:02:21 +0300 Subject: [PATCH 6/8] Add labels to ports --- port/port.go | 7 +++++++ port/port_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/port/port.go b/port/port.go index a9318d0..d988ef3 100644 --- a/port/port.go +++ b/port/port.go @@ -8,6 +8,7 @@ import ( // Port defines a connectivity point of a component type Port struct { common.NamedEntity + common.LabeledEntity signals signal.Group //Signal buffer pipes Group //Outbound pipes } @@ -92,6 +93,12 @@ func (p *Port) withPipes(destPorts ...*Port) *Port { return p } +// WithLabels sets labels and returns the port +func (p *Port) WithLabels(labels common.LabelsCollection) *Port { + p.LabeledEntity.SetLabels(labels) + return p +} + // ForwardSignals copies all signals from source port to destination port, without clearing the source port func ForwardSignals(source *Port, dest *Port) { dest.PutSignals(source.Signals()...) diff --git a/port/port_test.go b/port/port_test.go index b82bb94..3f5dba4 100644 --- a/port/port_test.go +++ b/port/port_test.go @@ -1,6 +1,7 @@ package port import ( + "github.com/hovsep/fmesh/common" "github.com/hovsep/fmesh/signal" "github.com/stretchr/testify/assert" "testing" @@ -299,3 +300,38 @@ func TestPort_Flush(t *testing.T) { }) } } + +func TestPort_WithLabels(t *testing.T) { + type args struct { + labels common.LabelsCollection + } + tests := []struct { + name string + port *Port + args args + assertions func(t *testing.T, port *Port) + }{ + { + name: "happy path", + port: New("p1"), + args: args{ + labels: common.LabelsCollection{ + "l1": "v1", + "l2": "v2", + }, + }, + assertions: func(t *testing.T, port *Port) { + assert.Len(t, port.Labels(), 2) + assert.True(t, port.HasAllLabels("l1", "l2")) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + portAfter := tt.port.WithLabels(tt.args.labels) + if tt.assertions != nil { + tt.assertions(t, portAfter) + } + }) + } +} From 2d8ddaac3ee47305fda195052e8cc69222fefc8c Mon Sep 17 00:00:00 2001 From: hovsep Date: Wed, 9 Oct 2024 18:05:13 +0300 Subject: [PATCH 7/8] Add labels to components --- component/component.go | 7 +++++++ component/component_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/component/component.go b/component/component.go index 957bc32..e1506a6 100644 --- a/component/component.go +++ b/component/component.go @@ -13,6 +13,7 @@ type ActivationFunc func(inputs port.Collection, outputs port.Collection) error type Component struct { common.NamedEntity common.DescribedEntity + common.LabeledEntity inputs port.Collection outputs port.Collection f ActivationFunc @@ -63,6 +64,12 @@ func (c *Component) WithActivationFunc(f ActivationFunc) *Component { return c } +// WithLabels sets labels and returns the component +func (c *Component) WithLabels(labels common.LabelsCollection) *Component { + c.LabeledEntity.SetLabels(labels) + return c +} + // Inputs getter func (c *Component) Inputs() port.Collection { return c.inputs diff --git a/component/component_test.go b/component/component_test.go index ebf23fe..fbbabfd 100644 --- a/component/component_test.go +++ b/component/component_test.go @@ -599,3 +599,38 @@ func TestComponent_WithOutputsIndexed(t *testing.T) { }) } } + +func TestComponent_WithLabels(t *testing.T) { + type args struct { + labels common.LabelsCollection + } + tests := []struct { + name string + component *Component + args args + assertions func(t *testing.T, component *Component) + }{ + { + name: "happy path", + component: New("c1"), + args: args{ + labels: common.LabelsCollection{ + "l1": "v1", + "l2": "v2", + }, + }, + assertions: func(t *testing.T, component *Component) { + assert.Len(t, component.Labels(), 2) + assert.True(t, component.HasAllLabels("l1", "l2")) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + componentAfter := tt.component.WithLabels(tt.args.labels) + if tt.assertions != nil { + tt.assertions(t, componentAfter) + } + }) + } +} From 8a7349ed0a07524c1e75a3aca5387b9d0f23f96f Mon Sep 17 00:00:00 2001 From: hovsep Date: Wed, 9 Oct 2024 18:12:12 +0300 Subject: [PATCH 8/8] LabeledEntity: add method --- common/labeled_entity.go | 15 +++++++++ common/labeled_entity_test.go | 62 +++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/common/labeled_entity.go b/common/labeled_entity.go index 3df05d8..1297ed9 100644 --- a/common/labeled_entity.go +++ b/common/labeled_entity.go @@ -1,11 +1,15 @@ package common +import "errors" + type LabelsCollection map[string]string type LabeledEntity struct { labels LabelsCollection } +var errLabelNotFound = errors.New("label not found") + // NewLabeledEntity constructor func NewLabeledEntity(labels LabelsCollection) LabeledEntity { @@ -17,6 +21,17 @@ func (e *LabeledEntity) Labels() LabelsCollection { return e.labels } +// Label returns the value of single label or nil if it is not found +func (e *LabeledEntity) Label(label string) (string, error) { + value, ok := e.labels[label] + + if !ok { + return "", errLabelNotFound + } + + return value, nil +} + // SetLabels overwrites labels collection func (e *LabeledEntity) SetLabels(labels LabelsCollection) { e.labels = labels diff --git a/common/labeled_entity_test.go b/common/labeled_entity_test.go index 6d45069..c514e2d 100644 --- a/common/labeled_entity_test.go +++ b/common/labeled_entity_test.go @@ -417,3 +417,65 @@ func TestLabeledEntity_HasAnyLabel(t *testing.T) { }) } } + +func TestLabeledEntity_Label(t *testing.T) { + type args struct { + label string + } + tests := []struct { + name string + labeledEntity LabeledEntity + args args + want string + wantErr bool + }{ + { + name: "no labels", + labeledEntity: LabeledEntity{ + labels: nil, + }, + args: args{ + label: "l1", + }, + want: "", + wantErr: true, + }, + { + name: "label found", + labeledEntity: NewLabeledEntity(LabelsCollection{ + "l1": "v1", + "l2": "v2", + }), + args: args{ + label: "l2", + }, + want: "v2", + wantErr: false, + }, + { + name: "label not found", + labeledEntity: NewLabeledEntity(LabelsCollection{ + "l1": "v1", + "l2": "v2", + }), + args: args{ + label: "l3", + }, + want: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.labeledEntity.Label(tt.args.label) + + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + + assert.Equal(t, tt.want, got) + }) + } +}