diff --git a/common/described_entity.go b/common/described_entity.go new file mode 100644 index 0000000..df8fc7e --- /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 (e DescribedEntity) Description() string { + return e.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/common/labeled_entity.go b/common/labeled_entity.go new file mode 100644 index 0000000..1297ed9 --- /dev/null +++ b/common/labeled_entity.go @@ -0,0 +1,84 @@ +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 { + + return LabeledEntity{labels: labels} +} + +// Labels getter +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 +} + +// 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..c514e2d --- /dev/null +++ b/common/labeled_entity_test.go @@ -0,0 +1,481 @@ +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...)) + }) + } +} + +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) + }) + } +} diff --git a/common/named_entity.go b/common/named_entity.go new file mode 100644 index 0000000..55d3f8c --- /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 (e NamedEntity) Name() string { + return e.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/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 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..e1506a6 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,25 +11,26 @@ type ActivationFunc func(inputs port.Collection, outputs port.Collection) error // Component defines a main building block of FMesh type Component struct { - name string - description string - inputs port.Collection - outputs port.Collection - f ActivationFunc + common.NamedEntity + common.DescribedEntity + common.LabeledEntity + inputs port.Collection + outputs port.Collection + f ActivationFunc } // 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(), } } // WithDescription sets a description func (c *Component) WithDescription(description string) *Component { - c.description = description + c.DescribedEntity = common.NewDescribedEntity(description) return c } @@ -62,14 +64,10 @@ 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 +// WithLabels sets labels and returns the component +func (c *Component) WithLabels(labels common.LabelsCollection) *Component { + c.LabeledEntity.SetLabels(labels) + return c } // Inputs getter diff --git a/component/component_test.go b/component/component_test.go index 3bbac18..fbbabfd 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,54 +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 - 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") @@ -266,11 +207,11 @@ func TestComponent_WithDescription(t *testing.T) { description: "descr", }, want: &Component{ - name: "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, }, }, } @@ -298,8 +239,8 @@ func TestComponent_WithInputs(t *testing.T) { portNames: []string{"p1", "p2"}, }, want: &Component{ - name: "c1", - description: "", + NamedEntity: common.NewNamedEntity("c1"), + DescribedEntity: common.NewDescribedEntity(""), inputs: port.Collection{ "p1": port.New("p1"), "p2": port.New("p2"), @@ -315,11 +256,11 @@ func TestComponent_WithInputs(t *testing.T) { portNames: nil, }, want: &Component{ - name: "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, }, }, } @@ -347,9 +288,9 @@ func TestComponent_WithOutputs(t *testing.T) { portNames: []string{"p1", "p2"}, }, want: &Component{ - name: "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"), @@ -364,11 +305,11 @@ func TestComponent_WithOutputs(t *testing.T) { portNames: nil, }, want: &Component{ - name: "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, }, }, } @@ -658,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) + } + }) + } +} diff --git a/fmesh.go b/fmesh.go index 8c02f88..1ef3cb0 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,38 +23,28 @@ var defaultConfig = Config{ // FMesh is the functional mesh type FMesh struct { - name string - description string - components component.Collection - config Config + common.NamedEntity + common.DescribedEntity + components component.Collection + config Config } // 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 -} - 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 e582de9..40bf0c9 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,10 +66,10 @@ func TestFMesh_WithDescription(t *testing.T) { description: "", }, want: &FMesh{ - name: "fm1", - description: "", - components: component.Collection{}, - config: defaultConfig, + NamedEntity: common.NewNamedEntity("fm1"), + DescribedEntity: common.NewDescribedEntity(""), + components: component.Collection{}, + config: defaultConfig, }, }, { @@ -78,10 +79,10 @@ func TestFMesh_WithDescription(t *testing.T) { description: "descr", }, want: &FMesh{ - name: "fm1", - description: "descr", - components: component.Collection{}, - config: defaultConfig, + NamedEntity: common.NewNamedEntity("fm1"), + DescribedEntity: common.NewDescribedEntity("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,54 +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 - 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 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..d988ef3 100644 --- a/port/port.go +++ b/port/port.go @@ -1,12 +1,14 @@ 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 + common.LabeledEntity signals signal.Group //Signal buffer pipes Group //Outbound pipes } @@ -14,15 +16,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 @@ -95,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 98b12cb..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" @@ -63,12 +64,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 +95,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 +103,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 +176,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 +190,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 { @@ -334,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) + } + }) + } +}