From 43adcc6c1048b4814887054ac58c208983893a3a Mon Sep 17 00:00:00 2001
From: Yugar-1 <xiaoyu.zhang@intel.com>
Date: Mon, 9 Sep 2024 15:23:59 +0800
Subject: [PATCH] Add unit test for memory bandwidth exporter. (#411)

* Add unit test for memory bandwidth exporter.

Signed-off-by: Yugar-1 <xiaoyu.zhang@intel.com>
---
 .github/workflows/pr-go-unittests.yaml        |   3 -
 .../collector/class_collector_test.go         | 110 ++++++
 .../collector/collector.go                    |  20 +-
 .../collector/collector_test.go               | 185 +++++++++
 .../collector/common_test.go                  | 363 ++++++++++++++++++
 .../collector/container_collector_test.go     | 125 ++++++
 .../collector/node_collector_test.go          | 110 ++++++
 7 files changed, 905 insertions(+), 11 deletions(-)
 create mode 100644 kubernetes-addons/memory-bandwidth-exporter/collector/class_collector_test.go
 create mode 100644 kubernetes-addons/memory-bandwidth-exporter/collector/collector_test.go
 create mode 100644 kubernetes-addons/memory-bandwidth-exporter/collector/common_test.go
 create mode 100644 kubernetes-addons/memory-bandwidth-exporter/collector/container_collector_test.go
 create mode 100644 kubernetes-addons/memory-bandwidth-exporter/collector/node_collector_test.go

diff --git a/.github/workflows/pr-go-unittests.yaml b/.github/workflows/pr-go-unittests.yaml
index c669d4a7..fff86e25 100644
--- a/.github/workflows/pr-go-unittests.yaml
+++ b/.github/workflows/pr-go-unittests.yaml
@@ -91,9 +91,6 @@ jobs:
 
       - name: Run tests and generate coverage
         run: |
-          if [ "${{ matrix.gopath }}" == "${MBE_DIR}" ]; then
-            exit 0
-          fi
           cd ${{ matrix.gopath }}
           go test -coverprofile=coverage.out $(go list ./... | grep -v /e2e)
           ${{ github.workspace }}/.github/workflows/scripts/go-coverage.sh
diff --git a/kubernetes-addons/memory-bandwidth-exporter/collector/class_collector_test.go b/kubernetes-addons/memory-bandwidth-exporter/collector/class_collector_test.go
new file mode 100644
index 00000000..6e8fec88
--- /dev/null
+++ b/kubernetes-addons/memory-bandwidth-exporter/collector/class_collector_test.go
@@ -0,0 +1,110 @@
+package collector
+
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/go-kit/log"
+)
+
+func TestNewClassCollector(t *testing.T) {
+	nn := "node1"
+	metrics1 := noMetrics
+	metrics2 := allMetrics
+	metrics3 := "mb,llc,cpu"
+	type fields struct {
+		classCollectorMetrics *string
+		nodeName              *string
+	}
+	type args struct {
+		logger   log.Logger
+		interval time.Duration
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		want    Collector
+		wantErr bool
+	}{
+		{
+			name: "TestNewNodeCollector 1",
+			fields: fields{
+				classCollectorMetrics: &metrics1,
+				nodeName:              &nn,
+			},
+			args: args{
+				logger:   log.NewNopLogger(),
+				interval: 3 * time.Second,
+			},
+			want: &classCollector{
+				statsCache: make(map[string]*stats),
+				interval:   3 * time.Second,
+				logger:     log.NewNopLogger(),
+				nodeName:   "node1",
+				metrics:    make(map[string]struct{}),
+			},
+			wantErr: false,
+		},
+		{
+			name: "TestNewNodeCollector 2",
+			fields: fields{
+				classCollectorMetrics: &metrics2,
+				nodeName:              &nn,
+			},
+			args: args{
+				logger:   log.NewNopLogger(),
+				interval: 3 * time.Second,
+			},
+			want: &classCollector{
+				statsCache: make(map[string]*stats),
+				interval:   3 * time.Second,
+				logger:     log.NewNopLogger(),
+				nodeName:   "node1",
+				metrics: map[string]struct{}{
+					"mb":  {},
+					"llc": {},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "TestNewNodeCollector 3",
+			fields: fields{
+				classCollectorMetrics: &metrics3,
+				nodeName:              &nn,
+			},
+			args: args{
+				logger:   log.NewNopLogger(),
+				interval: 3 * time.Second,
+			},
+			want: &classCollector{
+				statsCache: make(map[string]*stats),
+				interval:   3 * time.Second,
+				logger:     log.NewNopLogger(),
+				nodeName:   "node1",
+				metrics: map[string]struct{}{
+					"mb":  {},
+					"llc": {},
+					"cpu": {},
+				},
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			classCollectorMetrics = tt.fields.classCollectorMetrics
+			nodeName = tt.fields.nodeName
+			got, err := NewClassCollector(tt.args.logger, tt.args.interval)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("NewClassCollector() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("NewClassCollector() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/kubernetes-addons/memory-bandwidth-exporter/collector/collector.go b/kubernetes-addons/memory-bandwidth-exporter/collector/collector.go
index 83b7d7f3..34523850 100644
--- a/kubernetes-addons/memory-bandwidth-exporter/collector/collector.go
+++ b/kubernetes-addons/memory-bandwidth-exporter/collector/collector.go
@@ -48,37 +48,41 @@ func ParseCollectorMetrics() bool {
 	for collector := range collectorState {
 		if collector == containerCollectorSubsystem {
 			var isDefaultEnabled bool
-			if *containerCollectorMetrics == noMetrics {
+			if containerCollectorMetrics == nil || *containerCollectorMetrics == noMetrics {
 				isDefaultEnabled = false
+				collectorMetrics[collector] = noMetrics
 			} else {
 				isDefaultEnabled = true
+				collectorMetrics[collector] = *containerCollectorMetrics
 			}
 			collectorState[collector] = &isDefaultEnabled
-			collectorMetrics[collector] = *containerCollectorMetrics
-			if *containerCollectorMetrics == allMetrics || strings.Contains(*containerCollectorMetrics, "mb") ||
-				strings.Contains(*containerCollectorMetrics, "llc") {
+			if containerCollectorMetrics != nil && (*containerCollectorMetrics == allMetrics ||
+				strings.Contains(*containerCollectorMetrics, "mb") ||
+				strings.Contains(*containerCollectorMetrics, "llc")) {
 				isNeedNRIPlugin = true
 			}
 		}
 		if collector == classCollectorSubsystem {
 			var isDefaultEnabled bool
-			if *classCollectorMetrics == "none" {
+			if classCollectorMetrics == nil || *classCollectorMetrics == noMetrics {
 				isDefaultEnabled = false
+				collectorMetrics[collector] = noMetrics
 			} else {
 				isDefaultEnabled = true
+				collectorMetrics[collector] = *classCollectorMetrics
 			}
 			collectorState[collector] = &isDefaultEnabled
-			collectorMetrics[collector] = *classCollectorMetrics
 		}
 		if collector == nodeCollectorSubsystem {
 			var isDefaultEnabled bool
-			if *nodeCollectorMetrics == "none" {
+			if nodeCollectorMetrics == nil || *nodeCollectorMetrics == noMetrics {
 				isDefaultEnabled = false
+				collectorMetrics[collector] = noMetrics
 			} else {
 				isDefaultEnabled = true
+				collectorMetrics[collector] = *nodeCollectorMetrics
 			}
 			collectorState[collector] = &isDefaultEnabled
-			collectorMetrics[collector] = *nodeCollectorMetrics
 		}
 	}
 	return isNeedNRIPlugin
diff --git a/kubernetes-addons/memory-bandwidth-exporter/collector/collector_test.go b/kubernetes-addons/memory-bandwidth-exporter/collector/collector_test.go
new file mode 100644
index 00000000..a633cf7d
--- /dev/null
+++ b/kubernetes-addons/memory-bandwidth-exporter/collector/collector_test.go
@@ -0,0 +1,185 @@
+package collector
+
+import (
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/go-kit/log"
+)
+
+func TestParseCollectorMetrics(t *testing.T) {
+	type fields struct {
+		containerCollectorMetrics *string
+		classCollectorMetrics     *string
+		nodeCollectorMetrics      *string
+	}
+	metrics1 := noMetrics
+	metrics2 := allMetrics
+	metrics3 := "mb"
+	tests := []struct {
+		name   string
+		fields fields
+		want   bool
+	}{
+		{
+			name: "TestParseCollectorMetrics 1",
+			fields: fields{
+				containerCollectorMetrics: nil,
+				classCollectorMetrics:     nil,
+				nodeCollectorMetrics:      nil,
+			},
+			want: false,
+		},
+		{
+			name: "TestParseCollectorMetrics 2",
+			fields: fields{
+				containerCollectorMetrics: &metrics1,
+				classCollectorMetrics:     nil,
+				nodeCollectorMetrics:      nil,
+			},
+			want: false,
+		},
+		{
+			name: "TestParseCollectorMetrics 3",
+			fields: fields{
+				containerCollectorMetrics: &metrics2,
+				classCollectorMetrics:     nil,
+				nodeCollectorMetrics:      nil,
+			},
+			want: true,
+		},
+		{
+			name: "TestParseCollectorMetrics 4",
+			fields: fields{
+				containerCollectorMetrics: &metrics3,
+				classCollectorMetrics:     nil,
+				nodeCollectorMetrics:      nil,
+			},
+			want: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			containerCollectorMetrics = tt.fields.containerCollectorMetrics
+			classCollectorMetrics = tt.fields.classCollectorMetrics
+			nodeCollectorMetrics = tt.fields.nodeCollectorMetrics
+			if got := ParseCollectorMetrics(); got != tt.want {
+				t.Errorf("ParseCollectorMetrics() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestNewCollector(t *testing.T) {
+	isDefaultEnabled := true
+	isDefaultDisabled := false
+	type fields struct {
+		collectorState map[string]*bool
+	}
+	type args struct {
+		logger   log.Logger
+		interval time.Duration
+		filters  []string
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "TestNewCollector 1",
+			fields: fields{
+				collectorState: map[string]*bool{
+					containerCollectorSubsystem: &isDefaultEnabled,
+					classCollectorSubsystem:     &isDefaultDisabled,
+					nodeCollectorSubsystem:      &isDefaultDisabled,
+				},
+			},
+			args: args{
+				logger:   log.NewNopLogger(),
+				interval: 3 * time.Second,
+				filters:  []string{},
+			},
+			wantErr: false,
+		},
+		{
+			name: "TestNewCollector 2",
+			fields: fields{
+				collectorState: map[string]*bool{
+					// containerCollectorSubsystem: &isDefaultDisabled,
+					classCollectorSubsystem: &isDefaultDisabled,
+					nodeCollectorSubsystem:  &isDefaultDisabled,
+				},
+			},
+			args: args{
+				logger:   log.NewNopLogger(),
+				interval: 3 * time.Second,
+				filters: []string{
+					containerCollectorSubsystem,
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "TestNewCollector32",
+			fields: fields{
+				collectorState: map[string]*bool{
+					containerCollectorSubsystem: &isDefaultDisabled,
+					classCollectorSubsystem:     &isDefaultDisabled,
+					nodeCollectorSubsystem:      &isDefaultDisabled,
+				},
+			},
+			args: args{
+				logger:   log.NewNopLogger(),
+				interval: 3 * time.Second,
+				filters: []string{
+					containerCollectorSubsystem,
+				},
+			},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(
+			tt.name,
+			func(t *testing.T) {
+				collectorState = tt.fields.collectorState
+				initiatedCollectorsMtx = sync.Mutex{}
+				initiatedCollectors = make(map[string]Collector)
+				_, err := NewCollector(tt.args.logger, tt.args.interval, tt.args.filters...)
+				if (err != nil) != tt.wantErr {
+					t.Errorf("NewCollector() error = %v, wantErr %v", err, tt.wantErr)
+					return
+				}
+			},
+		)
+	}
+}
+
+func TestIsNoDataError(t *testing.T) {
+	type args struct {
+		err error
+	}
+	tests := []struct {
+		name string
+		args args
+		want bool
+	}{
+		{
+			name: "TestIsNoDataError 1",
+			args: args{
+				err: nil,
+			},
+			want: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := IsNoDataError(tt.args.err); got != tt.want {
+				t.Errorf("IsNoDataError() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/kubernetes-addons/memory-bandwidth-exporter/collector/common_test.go b/kubernetes-addons/memory-bandwidth-exporter/collector/common_test.go
new file mode 100644
index 00000000..593bbba4
--- /dev/null
+++ b/kubernetes-addons/memory-bandwidth-exporter/collector/common_test.go
@@ -0,0 +1,363 @@
+package collector
+
+import (
+	"reflect"
+	"testing"
+)
+
+func Test_isNeedCollectMbLLc(t *testing.T) {
+	type args struct {
+		metrics map[string]struct{}
+	}
+	tests := []struct {
+		name string
+		args args
+		want bool
+	}{
+		{
+			name: "need collect mb and llc",
+			args: args{
+				metrics: map[string]struct{}{
+					"mb":  {},
+					"llc": {},
+				},
+			},
+			want: true,
+		},
+		{
+			name: "do not need collect mb and llc",
+			args: args{
+				metrics: map[string]struct{}{
+					"cpu": {},
+				},
+			},
+			want: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := isNeedCollectMbLLc(tt.args.metrics); got != tt.want {
+				t.Errorf("isNeedCollectMbLLc() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_isNeedCollectCpu(t *testing.T) {
+	type args struct {
+		metrics map[string]struct{}
+	}
+	tests := []struct {
+		name string
+		args args
+		want bool
+	}{
+		{
+			name: "need collect cpu",
+			args: args{
+				metrics: map[string]struct{}{
+					"mb":  {},
+					"llc": {},
+					"cpu": {},
+				},
+			},
+			want: true,
+		},
+		{
+			name: "do not need collect cpu",
+			args: args{
+				metrics: map[string]struct{}{
+					"mb":  {},
+					"llc": {},
+				},
+			},
+			want: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := isNeedCollectCpu(tt.args.metrics); got != tt.want {
+				t.Errorf("isNeedCollectCpu() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_isNeedCollectMemory(t *testing.T) {
+	type args struct {
+		metrics map[string]struct{}
+	}
+	tests := []struct {
+		name string
+		args args
+		want bool
+	}{
+		{
+			name: "need collect memory",
+			args: args{
+				metrics: map[string]struct{}{
+					"mb":     {},
+					"llc":    {},
+					"cpu":    {},
+					"memory": {},
+				},
+			},
+			want: true,
+		},
+		{
+			name: "do not need collect memory",
+			args: args{
+				metrics: map[string]struct{}{
+					"mb":  {},
+					"llc": {},
+				},
+			},
+			want: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := isNeedCollectMemory(tt.args.metrics); got != tt.want {
+				t.Errorf("isNeedCollectMemory() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_getMetricsKeys(t *testing.T) {
+	type args struct {
+		m map[string]struct{}
+	}
+	tests := []struct {
+		name string
+		args args
+		want string
+	}{
+		{
+			name: "get mertics keys 1",
+			args: args{
+				m: map[string]struct{}{
+					"mb":  {},
+					"llc": {},
+				},
+			},
+			want: "mb,llc",
+		},
+		{
+			name: "get mertics keys 2",
+			args: args{
+				m: map[string]struct{}{},
+			},
+			want: "",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := getMetricsKeys(tt.args.m); got != tt.want {
+				t.Errorf("getMetricsKeys() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_bytesToMiB(t *testing.T) {
+	type args struct {
+		bytes uint64
+	}
+	tests := []struct {
+		name string
+		args args
+		want float64
+	}{
+		{
+			name: "bytes to MiB",
+			args: args{
+				bytes: 1048576,
+			},
+			want: 1,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := bytesToMiB(tt.args.bytes); got != tt.want {
+				t.Errorf("bytesToMiB() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_bytesToMB(t *testing.T) {
+	type args struct {
+		bytes uint64
+	}
+	tests := []struct {
+		name string
+		args args
+		want float64
+	}{
+		{
+			name: "bytes to MB",
+			args: args{
+				bytes: 1000000,
+			},
+			want: 1,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := bytesToMB(tt.args.bytes); got != tt.want {
+				t.Errorf("bytesToMB() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_processStats(t *testing.T) {
+	type args struct {
+		oldStats RawStats
+		newStats RawStats
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    ProcessedStats
+		wantErr bool
+	}{
+		{
+			name: "process stats",
+			args: args{
+				oldStats: RawStats{
+					SocketNum: 2,
+					MemoryBandwidth: map[string]RawMemoryBandwidthStats{
+						"0": {
+							TotalBytes:          10000000,
+							LocalBytes:          5000000,
+							TotalBytesTimeStamp: "2021-01-01  15:04:05",
+							LocalBytesTimeStamp: "2021-01-01  15:04:05",
+						},
+						"1": {
+							TotalBytes:          10000000,
+							LocalBytes:          5000000,
+							TotalBytesTimeStamp: "2021-01-01  15:04:05",
+							LocalBytesTimeStamp: "2021-01-01  15:04:05",
+						},
+					},
+					Cache: map[string]RawCacheStats{
+						"0": {
+							LLCOccupancy: 1048576,
+						},
+						"1": {
+							LLCOccupancy: 524288,
+						},
+					},
+					CPUUtilization: &RawCPUStats{
+						CPU:       1000000,
+						TimeStamp: "2021-01-01  15:04:05",
+					},
+					Memory: 100,
+				},
+				newStats: RawStats{
+					SocketNum: 2,
+					MemoryBandwidth: map[string]RawMemoryBandwidthStats{
+						"0": {
+							TotalBytes:          20000000,
+							LocalBytes:          10000000,
+							TotalBytesTimeStamp: "2021-01-01  15:04:15",
+							LocalBytesTimeStamp: "2021-01-01  15:04:15",
+						},
+						"1": {
+							TotalBytes:          30000000,
+							LocalBytes:          10000000,
+							TotalBytesTimeStamp: "2021-01-01  15:04:15",
+							LocalBytesTimeStamp: "2021-01-01  15:04:15",
+						},
+					},
+					Cache: map[string]RawCacheStats{
+						"0": {
+							LLCOccupancy: 1048576,
+						},
+						"1": {
+							LLCOccupancy: 524288,
+						},
+					},
+					CPUUtilization: &RawCPUStats{
+						CPU:       2000000,
+						TimeStamp: "2021-01-01  15:04:15",
+					},
+					Memory: 1024 * 1024,
+				},
+			},
+			want: ProcessedStats{
+				socketNum: 2,
+				SumMemoryBandwidth: ProcessedMemoryBandwidthStats{
+					TotalMBps: 3,
+					LocalMBps: 1,
+				},
+				SumCache: ProcessedCacheStats{
+					LLCOccupancy: 1.5,
+				},
+				MemoryBandwidth: map[string]ProcessedMemoryBandwidthStats{
+					"0": {
+						TotalMBps: 1,
+						LocalMBps: 0.5,
+					},
+					"1": {
+						TotalMBps: 2,
+						LocalMBps: 0.5,
+					},
+				},
+				Cache: map[string]ProcessedCacheStats{
+					"0": {
+						LLCOccupancy: 1,
+					},
+					"1": {
+						LLCOccupancy: 0.5,
+					},
+				},
+				CPUUtilization: 0.1,
+				Memory:         1,
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := processStats(tt.args.oldStats, tt.args.newStats)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("processStats() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("processStats() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_stringInSlice(t *testing.T) {
+	type args struct {
+		str  string
+		list []string
+	}
+	tests := []struct {
+		name string
+		args args
+		want bool
+	}{
+		{
+			name: "string in slice",
+			args: args{
+				str:  "a",
+				list: []string{"a", "b", "c"},
+			},
+			want: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := stringInSlice(tt.args.str, tt.args.list); got != tt.want {
+				t.Errorf("stringInSlice() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/kubernetes-addons/memory-bandwidth-exporter/collector/container_collector_test.go b/kubernetes-addons/memory-bandwidth-exporter/collector/container_collector_test.go
new file mode 100644
index 00000000..6cc0e52e
--- /dev/null
+++ b/kubernetes-addons/memory-bandwidth-exporter/collector/container_collector_test.go
@@ -0,0 +1,125 @@
+package collector
+
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/go-kit/log"
+	"github.com/opea-project/GenAIInfra/kubernetes-addons/memory-bandwidth-exporter/info"
+)
+
+func TestNewContainerCollector(t *testing.T) {
+	nn := "node2"
+	metrics1 := noMetrics
+	metrics2 := allMetrics
+	metrics3 := "llc,cpu"
+	namespaceWhiteList1 := "system"
+	namespaceWhiteList2 := "system,kube-system"
+	type fields struct {
+		containerCollectorMetrics *string
+		nodeName                  *string
+		namespaceWhiteList        *string
+	}
+	type args struct {
+		logger   log.Logger
+		interval time.Duration
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		want    Collector
+		wantErr bool
+	}{
+		{
+			name: "TestNewNodeCollector 1",
+			fields: fields{
+				containerCollectorMetrics: &metrics1,
+				nodeName:                  &nn,
+				namespaceWhiteList:        &namespaceWhiteList1,
+			},
+			args: args{
+				logger:   log.NewNopLogger(),
+				interval: 3 * time.Second,
+			},
+			want: &containerCollector{
+				statsCache:         make(map[string]stats),
+				containerInfos:     make(map[string]info.ContainerInfo),
+				interval:           3 * time.Second,
+				logger:             log.NewNopLogger(),
+				namespaceWhiteList: []string{"system"},
+				monTimes:           0,
+				metrics:            make(map[string]struct{}),
+			},
+			wantErr: false,
+		},
+		{
+			name: "TestNewNodeCollector 2",
+			fields: fields{
+				containerCollectorMetrics: &metrics2,
+				nodeName:                  &nn,
+				namespaceWhiteList:        &namespaceWhiteList2,
+			},
+			args: args{
+				logger:   log.NewNopLogger(),
+				interval: 3 * time.Second,
+			},
+			want: &containerCollector{
+				statsCache:         make(map[string]stats),
+				containerInfos:     make(map[string]info.ContainerInfo),
+				interval:           3 * time.Second,
+				logger:             log.NewNopLogger(),
+				namespaceWhiteList: []string{"system", "kube-system"},
+				monTimes:           0,
+				metrics: map[string]struct{}{
+					"mb":     {},
+					"llc":    {},
+					"cpu":    {},
+					"memory": {},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "TestNewNodeCollector 3",
+			fields: fields{
+				containerCollectorMetrics: &metrics3,
+				nodeName:                  &nn,
+				namespaceWhiteList:        &namespaceWhiteList2,
+			},
+			args: args{
+				logger:   log.NewNopLogger(),
+				interval: 3 * time.Second,
+			},
+			want: &containerCollector{
+				statsCache:         make(map[string]stats),
+				containerInfos:     make(map[string]info.ContainerInfo),
+				interval:           3 * time.Second,
+				logger:             log.NewNopLogger(),
+				namespaceWhiteList: []string{"system", "kube-system"},
+				monTimes:           0,
+				metrics: map[string]struct{}{
+					"llc": {},
+					"cpu": {},
+				},
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			containerCollectorMetrics = tt.fields.containerCollectorMetrics
+			nodeName = tt.fields.nodeName
+			namespaceWhiteList = tt.fields.namespaceWhiteList
+			got, err := NewContainerCollector(tt.args.logger, tt.args.interval)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("NewContainerCollector() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("NewContainerCollector() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/kubernetes-addons/memory-bandwidth-exporter/collector/node_collector_test.go b/kubernetes-addons/memory-bandwidth-exporter/collector/node_collector_test.go
new file mode 100644
index 00000000..f133cb4f
--- /dev/null
+++ b/kubernetes-addons/memory-bandwidth-exporter/collector/node_collector_test.go
@@ -0,0 +1,110 @@
+package collector
+
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/go-kit/log"
+)
+
+func TestNewNodeCollector(t *testing.T) {
+	nn := "node1"
+	metrics1 := noMetrics
+	metrics2 := allMetrics
+	metrics3 := "mb,llc,cpu"
+	type fields struct {
+		nodeCollectorMetrics *string
+		nodeName             *string
+	}
+	type args struct {
+		logger   log.Logger
+		interval time.Duration
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		want    Collector
+		wantErr bool
+	}{
+		{
+			name: "TestNewNodeCollector 1",
+			fields: fields{
+				nodeCollectorMetrics: &metrics1,
+				nodeName:             &nn,
+			},
+			args: args{
+				logger:   log.NewNopLogger(),
+				interval: 3 * time.Second,
+			},
+			want: &nodeCollctor{
+				interval:     3 * time.Second,
+				logger:       log.NewNopLogger(),
+				monGroupPath: rootResctrlPath,
+				nodeName:     "node1",
+				metrics:      make(map[string]struct{}),
+			},
+			wantErr: false,
+		},
+		{
+			name: "TestNewNodeCollector 2",
+			fields: fields{
+				nodeCollectorMetrics: &metrics2,
+				nodeName:             &nn,
+			},
+			args: args{
+				logger:   log.NewNopLogger(),
+				interval: 3 * time.Second,
+			},
+			want: &nodeCollctor{
+				interval:     3 * time.Second,
+				logger:       log.NewNopLogger(),
+				monGroupPath: rootResctrlPath,
+				nodeName:     "node1",
+				metrics: map[string]struct{}{
+					"mb":  {},
+					"llc": {},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "TestNewNodeCollector 3",
+			fields: fields{
+				nodeCollectorMetrics: &metrics3,
+				nodeName:             &nn,
+			},
+			args: args{
+				logger:   log.NewNopLogger(),
+				interval: 3 * time.Second,
+			},
+			want: &nodeCollctor{
+				interval:     3 * time.Second,
+				logger:       log.NewNopLogger(),
+				monGroupPath: rootResctrlPath,
+				nodeName:     "node1",
+				metrics: map[string]struct{}{
+					"mb":  {},
+					"llc": {},
+					"cpu": {},
+				},
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			nodeCollectorMetrics = tt.fields.nodeCollectorMetrics
+			nodeName = tt.fields.nodeName
+			got, err := NewNodeCollector(tt.args.logger, tt.args.interval)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("NewNodeCollector() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("NewNodeCollector() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}