Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unit tests for node package + small refactoring & fixes #5132

Merged
merged 15 commits into from
Apr 3, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions node/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package metrics

import (
"fmt"
"runtime/debug"
"sort"
"strconv"

Expand All @@ -10,6 +11,7 @@ import (
"github.com/multiversx/mx-chain-go/common"
"github.com/multiversx/mx-chain-go/config"
"github.com/multiversx/mx-chain-go/sharding"
logger "github.com/multiversx/mx-chain-logger-go"
)

const millisecondsInSecond = 1000
Expand All @@ -18,6 +20,8 @@ const initInt = int64(0)
const initString = ""
const initZeroString = "0"

var log = logger.GetOrCreate("node/metrics")

// InitBaseMetrics will initialize base, default metrics to 0 values
func InitBaseMetrics(appStatusHandler core.AppStatusHandler) error {
if check.IfNil(appStatusHandler) {
Expand Down Expand Up @@ -271,10 +275,20 @@ func InitMetrics(

// SaveUint64Metric will save an uint64 metric in status handler
func SaveUint64Metric(ash core.AppStatusHandler, key string, value uint64) {
if check.IfNil(ash) {
log.Error("programming error: nil AppStatusHandler in SaveUint64Metric", "stack", string(debug.Stack()))
return
}

ash.SetUInt64Value(key, value)
}

// SaveStringMetric will save a string metric in status handler
func SaveStringMetric(ash core.AppStatusHandler, key, value string) {
if check.IfNil(ash) {
log.Error("programming error: nil AppStatusHandler in SaveStringMetric", "stack", string(debug.Stack()))
return
}

ash.SetStringValue(key, value)
}
185 changes: 185 additions & 0 deletions node/metrics/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import (
"strconv"
"testing"

"github.com/multiversx/mx-chain-core-go/core"
"github.com/multiversx/mx-chain-go/common"
"github.com/multiversx/mx-chain-go/config"
"github.com/multiversx/mx-chain-go/sharding/nodesCoordinator"
"github.com/multiversx/mx-chain-go/testscommon"
"github.com/multiversx/mx-chain-go/testscommon/shardingMocks"
"github.com/multiversx/mx-chain-go/testscommon/statusHandler"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -329,3 +332,185 @@ func TestInitRatingsMetrics(t *testing.T) {
assert.Equal(t, v, keys[k])
}
}

func TestInitMetrics(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100% coverage can be reached on this component using the next 3 additions:
addition 1:

  • on L140, as part of TestInitConfigMetrics, add MaxNodesChangeEnableEpoch
		MaxNodesChangeEnableEpoch: []config.MaxNodesChangeConfig{
				{
					EpochEnable:            0,
					MaxNumNodes:            1,
					NodesToShufflePerShard: 2,
				},
			},

then on L190 we'll need new expected metrics:

		"erd_max_nodes_change_enable_epoch0_epoch_enable":               uint32(0),
		"erd_max_nodes_change_enable_epoch0_max_num_nodes":              uint32(1),
		"erd_max_nodes_change_enable_epoch0_nodes_to_shuffle_per_shard": uint32(2),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added, thanks.

t.Parallel()

appStatusHandler := &statusHandler.AppStatusHandlerStub{}
pubkeyString := "pub key"
nodeType := core.NodeTypeValidator
shardCoordinator := &testscommon.ShardsCoordinatorMock{
NoShards: 3,
SelfIDCalled: func() uint32 {
return 0
},
}
nodesSetup := &testscommon.NodesSetupStub{
GetShardConsensusGroupSizeCalled: func() uint32 {
return 63
},
GetMetaConsensusGroupSizeCalled: func() uint32 {
return 400
},
GetRoundDurationCalled: func() uint64 {
return 6000
},
MinNumberOfMetaNodesCalled: func() uint32 {
return 401
},
MinNumberOfShardNodesCalled: func() uint32 {
return 402
},
InitialNodesInfoCalled: func() (map[uint32][]nodesCoordinator.GenesisNodeInfoHandler, map[uint32][]nodesCoordinator.GenesisNodeInfoHandler) {
validators := map[uint32][]nodesCoordinator.GenesisNodeInfoHandler{
0: {
&shardingMocks.NodeInfoMock{},
&shardingMocks.NodeInfoMock{},
},
1: {
&shardingMocks.NodeInfoMock{},
},
}

return validators, make(map[uint32][]nodesCoordinator.GenesisNodeInfoHandler)
},
GetStartTimeCalled: func() int64 {
return 111111
},
}
version := "version"
economicsConfigs := &config.EconomicsConfig{
RewardsSettings: config.RewardsSettings{
RewardsConfigByEpoch: []config.EpochRewardSettings{
{
LeaderPercentage: 2,
},
Comment on lines +395 to +397
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addition 2: extra entry here:

Suggested change
{
LeaderPercentage: 2,
},
{
LeaderPercentage: 2,
},
{
LeaderPercentage: 2,
},

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

},
},
GlobalSettings: config.GlobalSettings{
Denomination: 4,
},
}
roundsPerEpoch := int64(200)
minTransactionVersion := uint32(1)

t.Run("nil app status handler should error", func(t *testing.T) {
t.Parallel()

err := InitMetrics(nil, pubkeyString, nodeType, shardCoordinator, nodesSetup, version, economicsConfigs, roundsPerEpoch, minTransactionVersion)
assert.Equal(t, ErrNilAppStatusHandler, err)
})
t.Run("nil shard coordinator should error", func(t *testing.T) {
t.Parallel()

expectedErrorString := "nil shard coordinator when initializing metrics"
err := InitMetrics(appStatusHandler, pubkeyString, nodeType, nil, nodesSetup, version, economicsConfigs, roundsPerEpoch, minTransactionVersion)
assert.Equal(t, expectedErrorString, err.Error())
})
t.Run("nil nodes configs should error", func(t *testing.T) {
t.Parallel()

expectedErrorString := "nil nodes config when initializing metrics"
err := InitMetrics(appStatusHandler, pubkeyString, nodeType, shardCoordinator, nil, version, economicsConfigs, roundsPerEpoch, minTransactionVersion)
assert.Equal(t, expectedErrorString, err.Error())
})
t.Run("nil economics configs should error", func(t *testing.T) {
t.Parallel()

expectedErrorString := "nil economics config when initializing metrics"
err := InitMetrics(appStatusHandler, pubkeyString, nodeType, shardCoordinator, nodesSetup, version, nil, roundsPerEpoch, minTransactionVersion)
assert.Equal(t, expectedErrorString, err.Error())
})
t.Run("should work", func(t *testing.T) {
t.Parallel()

keys := make(map[string]interface{})
localStatusHandler := &statusHandler.AppStatusHandlerStub{
SetUInt64ValueHandler: func(key string, value uint64) {
keys[key] = value
},
SetStringValueHandler: func(key string, value string) {
keys[key] = value
},
}

err := InitMetrics(localStatusHandler, pubkeyString, nodeType, shardCoordinator, nodesSetup, version, economicsConfigs, roundsPerEpoch, minTransactionVersion)
assert.Nil(t, err)

expectedValues := map[string]interface{}{
common.MetricPublicKeyBlockSign: pubkeyString,
common.MetricShardId: uint64(shardCoordinator.SelfId()),
common.MetricNumShardsWithoutMetachain: uint64(shardCoordinator.NoShards),
common.MetricNodeType: string(nodeType),
common.MetricRoundTime: uint64(6),
common.MetricAppVersion: version,
common.MetricRoundsPerEpoch: uint64(roundsPerEpoch),
common.MetricCrossCheckBlockHeight: "0",
common.MetricCrossCheckBlockHeight + "_0": uint64(0),
common.MetricCrossCheckBlockHeight + "_1": uint64(0),
common.MetricCrossCheckBlockHeight + "_2": uint64(0),
common.MetricCrossCheckBlockHeightMeta: uint64(0),
common.MetricIsSyncing: uint64(1),
common.MetricLeaderPercentage: fmt.Sprintf("%f", 2.0),
common.MetricDenomination: uint64(4),
common.MetricShardConsensusGroupSize: uint64(63),
common.MetricMetaConsensusGroupSize: uint64(400),
common.MetricNumNodesPerShard: uint64(402),
common.MetricNumMetachainNodes: uint64(401),
common.MetricStartTime: uint64(111111),
common.MetricRoundDuration: uint64(6000),
common.MetricMinTransactionVersion: uint64(1),
common.MetricNumValidators: uint64(2),
common.MetricConsensusGroupSize: uint64(63),
}

assert.Equal(t, len(expectedValues), len(keys))
for k, v := range expectedValues {
assert.Equal(t, v, keys[k], fmt.Sprintf("for key %s", k))
}
})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addition 3: extra tests here

	t.Run("should work - metachain", func(t *testing.T) {
		t.Parallel()

		keys := make(map[string]interface{})
		localStatusHandler := &statusHandler.AppStatusHandlerStub{
			SetUInt64ValueHandler: func(key string, value uint64) {
				keys[key] = value
			},
			SetStringValueHandler: func(key string, value string) {
				keys[key] = value
			},
		}
		localShardCoordinator := &testscommon.ShardsCoordinatorMock{
			NoShards: 3,
			SelfIDCalled: func() uint32 {
				return common.MetachainShardId
			},
		}

		err := InitMetrics(localStatusHandler, pubkeyString, nodeType, localShardCoordinator, nodesSetup, version, economicsConfigs, roundsPerEpoch, minTransactionVersion)
		assert.Nil(t, err)

		expectedValues := map[string]interface{}{
			common.MetricPublicKeyBlockSign:           pubkeyString,
			common.MetricShardId:                      uint64(localShardCoordinator.SelfId()),
			common.MetricNumShardsWithoutMetachain:    uint64(localShardCoordinator.NoShards),
			common.MetricNodeType:                     string(nodeType),
			common.MetricRoundTime:                    uint64(6),
			common.MetricAppVersion:                   version,
			common.MetricRoundsPerEpoch:               uint64(roundsPerEpoch),
			common.MetricCrossCheckBlockHeight:        "0",
			common.MetricCrossCheckBlockHeight + "_0": uint64(0),
			common.MetricCrossCheckBlockHeight + "_1": uint64(0),
			common.MetricCrossCheckBlockHeight + "_2": uint64(0),
			common.MetricCrossCheckBlockHeightMeta:    uint64(0),
			common.MetricIsSyncing:                    uint64(1),
			common.MetricLeaderPercentage:             fmt.Sprintf("%f", 2.0),
			common.MetricDenomination:                 uint64(4),
			common.MetricShardConsensusGroupSize:      uint64(63),
			common.MetricMetaConsensusGroupSize:       uint64(400),
			common.MetricNumNodesPerShard:             uint64(402),
			common.MetricNumMetachainNodes:            uint64(401),
			common.MetricStartTime:                    uint64(111111),
			common.MetricRoundDuration:                uint64(6000),
			common.MetricMinTransactionVersion:        uint64(1),
			common.MetricNumValidators:                uint64(0),
			common.MetricConsensusGroupSize:           uint64(400),
		}

		assert.Equal(t, len(expectedValues), len(keys))
		for k, v := range expectedValues {
			assert.Equal(t, v, keys[k], fmt.Sprintf("for key %s", k))
		}
	})
	t.Run("should work - invalid shard id", func(t *testing.T) {
		t.Parallel()

		keys := make(map[string]interface{})
		localStatusHandler := &statusHandler.AppStatusHandlerStub{
			SetUInt64ValueHandler: func(key string, value uint64) {
				keys[key] = value
			},
			SetStringValueHandler: func(key string, value string) {
				keys[key] = value
			},
		}
		localShardCoordinator := &testscommon.ShardsCoordinatorMock{
			NoShards: 3,
			SelfIDCalled: func() uint32 {
				return 10
			},
		}

		err := InitMetrics(localStatusHandler, pubkeyString, nodeType, localShardCoordinator, nodesSetup, version, economicsConfigs, roundsPerEpoch, minTransactionVersion)
		assert.Nil(t, err)

		assert.Equal(t, uint64(0), keys[common.MetricConsensusGroupSize])
	})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added, thanks

}

func TestSaveStringMetric(t *testing.T) {
t.Parallel()

t.Run("should not panic if appStatusHandler is nil", func(t *testing.T) {
assert.NotPanics(t, func() {
SaveStringMetric(nil, "key", "value")
})
})
t.Run("should work", func(t *testing.T) {
wasCalled := false
appStatusHandler := &statusHandler.AppStatusHandlerStub{
SetStringValueHandler: func(key string, value string) {
wasCalled = true
assert.Equal(t, "key", key)
assert.Equal(t, "value", value)
},
}
SaveStringMetric(appStatusHandler, "key", "value")
assert.True(t, wasCalled)
})
}

func TestSaveUint64Metric(t *testing.T) {
t.Parallel()

t.Run("should not panic if appStatusHandler is nil", func(t *testing.T) {
assert.NotPanics(t, func() {
SaveUint64Metric(nil, "key", 1)
})
})
t.Run("should work", func(t *testing.T) {
wasCalled := false
appStatusHandler := &statusHandler.AppStatusHandlerStub{
SetUInt64ValueHandler: func(key string, value uint64) {
wasCalled = true
assert.Equal(t, "key", key)
assert.Equal(t, uint64(1), value)
},
}
SaveUint64Metric(appStatusHandler, "key", 1)
assert.True(t, wasCalled)
})
}
35 changes: 35 additions & 0 deletions node/mock/applicationRunningTrigger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package mock

import (
"strings"

logger "github.com/multiversx/mx-chain-logger-go"
)

var log = logger.GetOrCreate("node/mock")

type applicationRunningTrigger struct {
chanClose chan struct{}
}

// NewApplicationRunningTrigger -
func NewApplicationRunningTrigger() *applicationRunningTrigger {
return &applicationRunningTrigger{
chanClose: make(chan struct{}),
}
}

// Write -
func (trigger *applicationRunningTrigger) Write(p []byte) (n int, err error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added, thanks

if strings.Contains(string(p), "application is now running") {
log.Info("got signal, trying to gracefully close the node")
close(trigger.chanClose)
}

return 0, nil
}

// ChanClose -
func (trigger *applicationRunningTrigger) ChanClose() chan struct{} {
return trigger.chanClose
}
Loading