This is an automated email from the ASF dual-hosted git repository.

alexstocks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git


The following commit(s) were added to refs/heads/develop by this push:
     new 47abbbca6 test: add unit test for metrics (#3122)
47abbbca6 is described below

commit 47abbbca60fb8ff3c475f23f0d64bd715a257d08
Author: zbchi <[email protected]>
AuthorDate: Mon Dec 22 14:12:49 2025 +0800

    test: add unit test for metrics (#3122)
    
    * test: add unit test for metrics
    
    * fix mistake
    
    * format code
    
    * use const
    
    * fix: improve metrics tests based on Copilot review
---
 metrics/api_test.go                                | 323 +++++++++++++++++++++
 metrics/bus_test.go                                |  73 ++++-
 metrics/common_test.go                             | 104 +++++++
 metrics/config_center/collector_test.go            |  93 ++++++
 .../{bus_test.go => metadata/collector_test.go}    |  48 +--
 metrics/options_test.go                            | 151 ++++++++++
 metrics/registry/event_test.go                     | 111 +++++++
 metrics/{bus_test.go => reporter_test.go}          |  37 +--
 metrics/rpc/event_test.go                          |  76 +++++
 metrics/rpc/util_test.go                           | 190 ++++++++++++
 10 files changed, 1156 insertions(+), 50 deletions(-)

diff --git a/metrics/api_test.go b/metrics/api_test.go
new file mode 100644
index 000000000..edcac2a73
--- /dev/null
+++ b/metrics/api_test.go
@@ -0,0 +1,323 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package metrics
+
+import (
+       "testing"
+)
+
+import (
+       "github.com/stretchr/testify/assert"
+)
+
+// mockMetricRegistry is a simple mock implementation of MetricRegistry for 
testing
+type mockMetricRegistry struct {
+       counters   map[string]*mockCounterMetric
+       gauges     map[string]*mockGaugeMetric
+       histograms map[string]*mockObservableMetric
+       summaries  map[string]*mockObservableMetric
+       rts        map[string]*mockObservableMetric
+}
+
+func newMockMetricRegistry() *mockMetricRegistry {
+       return &mockMetricRegistry{
+               counters:   make(map[string]*mockCounterMetric),
+               gauges:     make(map[string]*mockGaugeMetric),
+               histograms: make(map[string]*mockObservableMetric),
+               summaries:  make(map[string]*mockObservableMetric),
+               rts:        make(map[string]*mockObservableMetric),
+       }
+}
+
+func (m *mockMetricRegistry) Counter(id *MetricId) CounterMetric {
+       if c, ok := m.counters[id.Name]; ok {
+               return c
+       }
+       c := &mockCounterMetric{}
+       m.counters[id.Name] = c
+       return c
+}
+
+func (m *mockMetricRegistry) Gauge(id *MetricId) GaugeMetric {
+       if g, ok := m.gauges[id.Name]; ok {
+               return g
+       }
+       g := &mockGaugeMetric{}
+       m.gauges[id.Name] = g
+       return g
+}
+
+func (m *mockMetricRegistry) Histogram(id *MetricId) ObservableMetric {
+       if h, ok := m.histograms[id.Name]; ok {
+               return h
+       }
+       h := &mockObservableMetric{}
+       m.histograms[id.Name] = h
+       return h
+}
+
+func (m *mockMetricRegistry) Summary(id *MetricId) ObservableMetric {
+       if s, ok := m.summaries[id.Name]; ok {
+               return s
+       }
+       s := &mockObservableMetric{}
+       m.summaries[id.Name] = s
+       return s
+}
+
+func (m *mockMetricRegistry) Rt(id *MetricId, opts *RtOpts) ObservableMetric {
+       if rt, ok := m.rts[id.Name]; ok {
+               return rt
+       }
+       rt := &mockObservableMetric{}
+       m.rts[id.Name] = rt
+       return rt
+}
+
+func (m *mockMetricRegistry) Export() {}
+
+type mockCounterMetric struct {
+       value float64
+}
+
+func (m *mockCounterMetric) Inc()          { m.value++ }
+func (m *mockCounterMetric) Add(v float64) { m.value += v }
+
+type mockGaugeMetric struct {
+       value float64
+}
+
+func (m *mockGaugeMetric) Set(v float64) { m.value = v }
+func (m *mockGaugeMetric) Inc()          { m.value++ }
+func (m *mockGaugeMetric) Dec()          { m.value-- }
+func (m *mockGaugeMetric) Add(v float64) { m.value += v }
+func (m *mockGaugeMetric) Sub(v float64) { m.value -= v }
+
+type mockObservableMetric struct {
+       value float64
+}
+
+func (m *mockObservableMetric) Observe(v float64) { m.value = v }
+
+func TestMetricIdTagKeys(t *testing.T) {
+       tests := []struct {
+               name string
+               tags map[string]string
+               want int
+       }{
+               {
+                       name: "empty tags",
+                       tags: map[string]string{},
+                       want: 0,
+               },
+               {
+                       name: "single tag",
+                       tags: map[string]string{"app": "dubbo"},
+                       want: 1,
+               },
+               {
+                       name: "multiple tags",
+                       tags: map[string]string{
+                               "app":     "dubbo",
+                               "version": "1.0.0",
+                               "ip":      "127.0.0.1",
+                       },
+                       want: 3,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       m := &MetricId{Name: "test_metric", Tags: tt.tags}
+                       got := m.TagKeys()
+                       assert.Equal(t, tt.want, len(got))
+                       for k := range tt.tags {
+                               assert.Contains(t, got, k)
+                       }
+               })
+       }
+}
+
+func TestBaseCollectorStateCount(t *testing.T) {
+       registry := newMockMetricRegistry()
+       collector := &BaseCollector{R: registry}
+       level := GetApplicationLevel()
+
+       totalKey := NewMetricKey("total", "Total requests")
+       succKey := NewMetricKey("succ", "Success requests")
+       failKey := NewMetricKey("fail", "Failed requests")
+
+       t.Run("success", func(t *testing.T) {
+               collector.StateCount(totalKey, succKey, failKey, level, true)
+
+               total := registry.counters["total"]
+               succ := registry.counters["succ"]
+               fail := registry.counters["fail"]
+
+               assert.NotNil(t, total)
+               assert.NotNil(t, succ)
+               assert.Equal(t, float64(1), total.value)
+               assert.Equal(t, float64(1), succ.value)
+               if fail != nil {
+                       assert.Equal(t, float64(0), fail.value)
+               }
+       })
+
+       t.Run("failure", func(t *testing.T) {
+               collector.StateCount(totalKey, succKey, failKey, level, false)
+
+               total := registry.counters["total"]
+               fail := registry.counters["fail"]
+
+               assert.NotNil(t, total)
+               assert.NotNil(t, fail)
+               assert.Equal(t, float64(2), total.value)
+               assert.Equal(t, float64(1), fail.value)
+       })
+}
+
+func TestDefaultCounterVec(t *testing.T) {
+       key := NewMetricKey("test_counter", "Test counter")
+       labels := map[string]string{"app": "dubbo", "version": "1.0.0"}
+
+       t.Run("Inc", func(t *testing.T) {
+               registry := newMockMetricRegistry()
+               counterVec := NewCounterVec(registry, key)
+               counterVec.Inc(labels)
+               metricId := NewMetricIdByLabels(key, labels)
+               counter := registry.Counter(metricId).(*mockCounterMetric)
+               assert.Equal(t, float64(1), counter.value)
+       })
+
+       t.Run("Add", func(t *testing.T) {
+               registry := newMockMetricRegistry()
+               counterVec := NewCounterVec(registry, key)
+               counterVec.Add(labels, 5.0)
+               metricId := NewMetricIdByLabels(key, labels)
+               counter := registry.Counter(metricId).(*mockCounterMetric)
+               assert.Equal(t, float64(5), counter.value)
+       })
+}
+
+func TestDefaultGaugeVec(t *testing.T) {
+       key := NewMetricKey("test_gauge", "Test gauge")
+       labels := map[string]string{"app": "dubbo", "version": "1.0.0"}
+
+       t.Run("Set", func(t *testing.T) {
+               registry := newMockMetricRegistry()
+               gaugeVec := NewGaugeVec(registry, key)
+               gaugeVec.Set(labels, 100.0)
+               gauge := registry.Gauge(NewMetricIdByLabels(key, 
labels)).(*mockGaugeMetric)
+               assert.Equal(t, float64(100), gauge.value)
+       })
+
+       t.Run("Inc", func(t *testing.T) {
+               registry := newMockMetricRegistry()
+               gaugeVec := NewGaugeVec(registry, key)
+               gaugeVec.Set(labels, 100.0)
+               gaugeVec.Inc(labels)
+               gauge := registry.Gauge(NewMetricIdByLabels(key, 
labels)).(*mockGaugeMetric)
+               assert.Equal(t, float64(101), gauge.value)
+       })
+
+       t.Run("Dec", func(t *testing.T) {
+               registry := newMockMetricRegistry()
+               gaugeVec := NewGaugeVec(registry, key)
+               gaugeVec.Set(labels, 100.0)
+               gaugeVec.Dec(labels)
+               gauge := registry.Gauge(NewMetricIdByLabels(key, 
labels)).(*mockGaugeMetric)
+               assert.Equal(t, float64(99), gauge.value)
+       })
+
+       t.Run("Add", func(t *testing.T) {
+               registry := newMockMetricRegistry()
+               gaugeVec := NewGaugeVec(registry, key)
+               gaugeVec.Set(labels, 100.0)
+               gaugeVec.Add(labels, 50.0)
+               gauge := registry.Gauge(NewMetricIdByLabels(key, 
labels)).(*mockGaugeMetric)
+               assert.Equal(t, float64(150), gauge.value)
+       })
+
+       t.Run("Sub", func(t *testing.T) {
+               registry := newMockMetricRegistry()
+               gaugeVec := NewGaugeVec(registry, key)
+               gaugeVec.Set(labels, 100.0)
+               gaugeVec.Sub(labels, 30.0)
+               gauge := registry.Gauge(NewMetricIdByLabels(key, 
labels)).(*mockGaugeMetric)
+               assert.Equal(t, float64(70), gauge.value)
+       })
+}
+
+func TestDefaultRtVec(t *testing.T) {
+       registry := newMockMetricRegistry()
+       key := NewMetricKey("test_rt", "Test response time")
+       rtOpts := &RtOpts{Aggregate: false}
+       rtVec := NewRtVec(registry, key, rtOpts)
+       labels := map[string]string{"app": "dubbo", "version": "1.0.0"}
+
+       rtVec.Record(labels, 100.0)
+       rt := registry.Rt(NewMetricIdByLabels(key, labels), rtOpts)
+       assert.NotNil(t, rt)
+}
+
+func TestDefaultQpsMetricVec(t *testing.T) {
+       registry := newMockMetricRegistry()
+       key := NewMetricKey("test_qps", "Test QPS")
+       qpsVec := NewQpsMetricVec(registry, key)
+       labels := map[string]string{"app": "dubbo", "version": "1.0.0"}
+
+       for i := 0; i < 5; i++ {
+               qpsVec.Record(labels)
+       }
+
+       gauge := registry.Gauge(NewMetricIdByLabels(key, labels))
+       assert.NotNil(t, gauge)
+}
+
+func TestDefaultAggregateCounterVec(t *testing.T) {
+       registry := newMockMetricRegistry()
+       key := NewMetricKey("test_agg_counter", "Test aggregate counter")
+       aggCounterVec := NewAggregateCounterVec(registry, key)
+       labels := map[string]string{"app": "dubbo", "version": "1.0.0"}
+
+       for i := 0; i < 3; i++ {
+               aggCounterVec.Inc(labels)
+       }
+
+       gauge := registry.Gauge(NewMetricIdByLabels(key, labels))
+       assert.NotNil(t, gauge)
+}
+
+func TestDefaultQuantileMetricVec(t *testing.T) {
+       registry := newMockMetricRegistry()
+       keys := []*MetricKey{
+               NewMetricKey("test_quantile_p50", "P50 quantile"),
+               NewMetricKey("test_quantile_p90", "P90 quantile"),
+       }
+       quantileVec := NewQuantileMetricVec(registry, keys, []float64{0.5, 0.9})
+       labels := map[string]string{"app": "dubbo", "version": "1.0.0"}
+
+       for i := 0; i < 10; i++ {
+               quantileVec.Record(labels, float64(i*10))
+       }
+
+       for _, key := range keys {
+               gauge := registry.Gauge(NewMetricIdByLabels(key, labels))
+               assert.NotNil(t, gauge)
+       }
+}
diff --git a/metrics/bus_test.go b/metrics/bus_test.go
index 4e4a68eec..2d72f92c5 100644
--- a/metrics/bus_test.go
+++ b/metrics/bus_test.go
@@ -28,9 +28,13 @@ import (
 var mockChan = make(chan MetricsEvent, 16)
 
 type MockEvent struct {
+       eventType string
 }
 
 func (m MockEvent) Type() string {
+       if m.eventType != "" {
+               return m.eventType
+       }
        return "dubbo.metrics.mock"
 }
 
@@ -38,17 +42,76 @@ func NewEmptyMockEvent() *MockEvent {
        return &MockEvent{}
 }
 
+func NewMockEventWithType(eventType string) *MockEvent {
+       return &MockEvent{eventType: eventType}
+}
+
 func init() {
        Subscribe("dubbo.metrics.mock", mockChan)
        Publish(NewEmptyMockEvent())
 }
 
 func TestBusPublish(t *testing.T) {
-       t.Run("testBusPublish", func(t *testing.T) {
-               event := <-mockChan
+       event := <-mockChan
+
+       if mockEvent, ok := event.(*MockEvent); ok {
+               assert.Equal(t, NewEmptyMockEvent(), mockEvent)
+       }
+}
+
+func TestBusUnsubscribe(t *testing.T) {
+       testChan := make(chan MetricsEvent, 1)
+       eventType := "dubbo.metrics.test.unsubscribe"
+
+       Subscribe(eventType, testChan)
+
+       testEvent1 := NewMockEventWithType(eventType)
+       Publish(testEvent1)
+
+       select {
+       case event := <-testChan:
+               assert.Equal(t, testEvent1, event)
+       default:
+               t.Fatal("Expected to receive event before unsubscribe")
+       }
+
+       Unsubscribe(eventType)
 
-               if event, ok := event.(MockEvent); ok {
-                       assert.Equal(t, event, NewEmptyMockEvent())
+       testEvent2 := NewMockEventWithType(eventType)
+       Publish(testEvent2)
+
+       select {
+       case _, ok := <-testChan:
+               if ok {
+                       t.Fatal("Channel should be closed after Unsubscribe")
                }
-       })
+       default:
+       }
+}
+
+func TestBusPublishChannelFull(t *testing.T) {
+       testChan := make(chan MetricsEvent, 1)
+       eventType := "dubbo.metrics.test.full"
+
+       Subscribe(eventType, testChan)
+
+       testEvent1 := NewMockEventWithType(eventType)
+       testChan <- testEvent1
+
+       testEvent2 := NewMockEventWithType(eventType)
+       Publish(testEvent2)
+
+       select {
+       case event := <-testChan:
+               assert.Equal(t, testEvent1, event)
+               select {
+               case <-testChan:
+                       t.Fatal("Expected second event to be dropped when 
channel is full")
+               default:
+               }
+       default:
+               t.Fatal("Expected to receive first event from channel")
+       }
+
+       Unsubscribe(eventType)
 }
diff --git a/metrics/common_test.go b/metrics/common_test.go
new file mode 100644
index 000000000..b3d943ea0
--- /dev/null
+++ b/metrics/common_test.go
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package metrics
+
+import (
+       "os"
+       "testing"
+)
+
+import (
+       "github.com/stretchr/testify/assert"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/common/constant"
+)
+
+func TestMain(m *testing.M) {
+       InitAppInfo("test-app", "1.0.0")
+       os.Exit(m.Run())
+}
+
+func TestGetApplicationLevel(t *testing.T) {
+       level := GetApplicationLevel()
+
+       assert.NotNil(t, level)
+       assert.Equal(t, "test-app", level.ApplicationName)
+       assert.Equal(t, "1.0.0", level.Version)
+       assert.NotEmpty(t, level.Ip)
+       assert.NotEmpty(t, level.HostName)
+}
+
+func TestApplicationMetricLevelTags(t *testing.T) {
+       level := GetApplicationLevel()
+       tags := level.Tags()
+
+       assert.NotNil(t, tags)
+       assert.Equal(t, "test-app", tags[constant.TagApplicationName])
+       assert.Equal(t, "1.0.0", tags[constant.TagApplicationVersion])
+       assert.NotEmpty(t, tags[constant.TagIp])
+       assert.NotEmpty(t, tags[constant.TagHostname])
+       assert.Equal(t, "", tags[constant.TagGitCommitId])
+}
+
+func TestServiceMetricLevelTags(t *testing.T) {
+       serviceMetric := NewServiceMetric("com.example.Service")
+       tags := serviceMetric.Tags()
+
+       assert.NotNil(t, tags)
+       assert.Equal(t, "test-app", tags[constant.TagApplicationName])
+       assert.Equal(t, "1.0.0", tags[constant.TagApplicationVersion])
+       assert.Equal(t, "com.example.Service", tags[constant.TagInterface])
+       assert.NotEmpty(t, tags[constant.TagIp])
+       assert.NotEmpty(t, tags[constant.TagHostname])
+}
+
+func TestMethodMetricLevelTags(t *testing.T) {
+       serviceMetric := NewServiceMetric("com.example.Service")
+       methodLevel := MethodMetricLevel{
+               ServiceMetricLevel: serviceMetric,
+               Method:             "TestMethod",
+               Group:              "test-group",
+               Version:            "1.0.0",
+       }
+
+       tags := methodLevel.Tags()
+
+       assert.NotNil(t, tags)
+       assert.Equal(t, "test-app", tags[constant.TagApplicationName])
+       assert.Equal(t, "1.0.0", tags[constant.TagApplicationVersion])
+       assert.Equal(t, "com.example.Service", tags[constant.TagInterface])
+       assert.Equal(t, "TestMethod", tags[constant.TagMethod])
+       assert.Equal(t, "test-group", tags[constant.TagGroup])
+       assert.Equal(t, "1.0.0", tags[constant.TagVersion])
+}
+
+func TestConfigCenterLevelTags(t *testing.T) {
+       level := NewConfigCenterLevel("test-key", "test-group", "nacos", 
"added")
+       tags := level.Tags()
+
+       assert.NotNil(t, tags)
+       assert.Equal(t, "test-app", tags[constant.TagApplicationName])
+       assert.Equal(t, "test-key", tags[constant.TagKey])
+       assert.Equal(t, "test-group", tags[constant.TagGroup])
+       assert.Equal(t, "nacos", tags[constant.TagConfigCenter])
+       assert.Equal(t, "added", tags[constant.TagChangeType])
+       assert.NotEmpty(t, tags[constant.TagIp])
+       assert.NotEmpty(t, tags[constant.TagHostname])
+}
diff --git a/metrics/config_center/collector_test.go 
b/metrics/config_center/collector_test.go
new file mode 100644
index 000000000..5cb965d22
--- /dev/null
+++ b/metrics/config_center/collector_test.go
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package metrics
+
+import (
+       "testing"
+)
+
+import (
+       "github.com/stretchr/testify/assert"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/common/constant"
+       "dubbo.apache.org/dubbo-go/v3/remoting"
+)
+
+func TestConfigCenterMetricEventType(t *testing.T) {
+       event := &ConfigCenterMetricEvent{
+               key:          "test-key",
+               group:        "test-group",
+               configCenter: Nacos,
+               changeType:   remoting.EventTypeAdd,
+               size:         1.0,
+       }
+
+       assert.Equal(t, constant.MetricsConfigCenter, event.Type())
+}
+
+func TestConfigCenterMetricEventGetChangeType(t *testing.T) {
+       tests := []struct {
+               name       string
+               changeType remoting.EventType
+               want       string
+       }{
+               {
+                       name:       "EventTypeAdd",
+                       changeType: remoting.EventTypeAdd,
+                       want:       "added",
+               },
+               {
+                       name:       "EventTypeDel",
+                       changeType: remoting.EventTypeDel,
+                       want:       "deleted",
+               },
+               {
+                       name:       "EventTypeUpdate",
+                       changeType: remoting.EventTypeUpdate,
+                       want:       "modified",
+               },
+               {
+                       name:       "unknown event type",
+                       changeType: remoting.EventType(999),
+                       want:       "",
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       event := &ConfigCenterMetricEvent{
+                               changeType: tt.changeType,
+                       }
+                       got := event.getChangeType()
+                       assert.Equal(t, tt.want, got)
+               })
+       }
+}
+
+func TestNewIncMetricEvent(t *testing.T) {
+       event := NewIncMetricEvent("test-key", "test-group", 
remoting.EventTypeAdd, Nacos)
+
+       assert.NotNil(t, event)
+       assert.Equal(t, "test-key", event.key)
+       assert.Equal(t, "test-group", event.group)
+       assert.Equal(t, remoting.EventTypeAdd, event.changeType)
+       assert.Equal(t, Nacos, event.configCenter)
+       assert.Equal(t, 1.0, event.size)
+}
diff --git a/metrics/bus_test.go b/metrics/metadata/collector_test.go
similarity index 52%
copy from metrics/bus_test.go
copy to metrics/metadata/collector_test.go
index 4e4a68eec..d0dda338e 100644
--- a/metrics/bus_test.go
+++ b/metrics/metadata/collector_test.go
@@ -15,40 +15,50 @@
  * limitations under the License.
  */
 
-package metrics
+package metadata
 
 import (
        "testing"
+       "time"
 )
 
 import (
        "github.com/stretchr/testify/assert"
 )
 
-var mockChan = make(chan MetricsEvent, 16)
+import (
+       "dubbo.apache.org/dubbo-go/v3/common/constant"
+)
 
-type MockEvent struct {
-}
+func TestMetadataMetricEventType(t *testing.T) {
+       event := &MetadataMetricEvent{
+               Name: MetadataPush,
+               Succ: true,
+       }
 
-func (m MockEvent) Type() string {
-       return "dubbo.metrics.mock"
+       assert.Equal(t, constant.MetricsMetadata, event.Type())
 }
 
-func NewEmptyMockEvent() *MockEvent {
-       return &MockEvent{}
-}
+func TestMetadataMetricEventCostMs(t *testing.T) {
+       start := time.Now()
+       end := start.Add(10 * time.Millisecond)
+
+       event := &MetadataMetricEvent{
+               Name:  MetadataPush,
+               Start: start,
+               End:   end,
+       }
 
-func init() {
-       Subscribe("dubbo.metrics.mock", mockChan)
-       Publish(NewEmptyMockEvent())
+       cost := event.CostMs()
+       assert.Equal(t, 10.0, cost)
 }
 
-func TestBusPublish(t *testing.T) {
-       t.Run("testBusPublish", func(t *testing.T) {
-               event := <-mockChan
+func TestNewMetadataMetricTimeEvent(t *testing.T) {
+       event := NewMetadataMetricTimeEvent(MetadataPush)
 
-               if event, ok := event.(MockEvent); ok {
-                       assert.Equal(t, event, NewEmptyMockEvent())
-               }
-       })
+       assert.NotNil(t, event)
+       assert.Equal(t, MetadataPush, event.Name)
+       assert.NotNil(t, event.Start)
+       assert.NotNil(t, event.Attachment)
+       assert.Empty(t, event.Attachment)
 }
diff --git a/metrics/options_test.go b/metrics/options_test.go
new file mode 100644
index 000000000..7f7d2851a
--- /dev/null
+++ b/metrics/options_test.go
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package metrics
+
+import (
+       "testing"
+       "time"
+)
+
+import (
+       "github.com/stretchr/testify/assert"
+)
+
+func TestDefaultOptions(t *testing.T) {
+       opts := defaultOptions()
+       assert.NotNil(t, opts)
+       assert.NotNil(t, opts.Metrics)
+}
+
+func TestNewOptions(t *testing.T) {
+       t.Run("no options", func(t *testing.T) {
+               opts := NewOptions()
+               assert.NotNil(t, opts)
+               assert.NotNil(t, opts.Metrics)
+       })
+
+       t.Run("with single option", func(t *testing.T) {
+               opts := NewOptions(WithPrometheus())
+               assert.NotNil(t, opts)
+               assert.Equal(t, "prometheus", opts.Metrics.Protocol)
+       })
+
+       t.Run("with multiple options", func(t *testing.T) {
+               opts := NewOptions(
+                       WithPrometheus(),
+                       WithPort(9090),
+                       WithPath("/metrics"),
+               )
+               assert.NotNil(t, opts)
+               assert.Equal(t, "prometheus", opts.Metrics.Protocol)
+               assert.Equal(t, "9090", opts.Metrics.Port)
+               assert.Equal(t, "/metrics", opts.Metrics.Path)
+       })
+}
+
+func TestWithAggregationEnabled(t *testing.T) {
+       opts := NewOptions(WithAggregationEnabled())
+       assert.NotNil(t, opts.Metrics.Aggregation.Enabled)
+       assert.True(t, *opts.Metrics.Aggregation.Enabled)
+}
+
+func TestWithAggregationBucketNum(t *testing.T) {
+       opts := NewOptions(WithAggregationBucketNum(20))
+       assert.Equal(t, 20, opts.Metrics.Aggregation.BucketNum)
+}
+
+func TestWithAggregationTimeWindowSeconds(t *testing.T) {
+       opts := NewOptions(WithAggregationTimeWindowSeconds(60))
+       assert.Equal(t, 60, opts.Metrics.Aggregation.TimeWindowSeconds)
+}
+
+func TestWithPrometheus(t *testing.T) {
+       opts := NewOptions(WithPrometheus())
+       assert.Equal(t, "prometheus", opts.Metrics.Protocol)
+}
+
+func TestWithPrometheusExporterEnabled(t *testing.T) {
+       opts := NewOptions(WithPrometheusExporterEnabled())
+       assert.NotNil(t, opts.Metrics.Prometheus.Exporter.Enabled)
+       assert.True(t, *opts.Metrics.Prometheus.Exporter.Enabled)
+}
+
+func TestWithPrometheusPushgatewayEnabled(t *testing.T) {
+       opts := NewOptions(WithPrometheusPushgatewayEnabled())
+       assert.NotNil(t, opts.Metrics.Prometheus.Pushgateway.Enabled)
+       assert.True(t, *opts.Metrics.Prometheus.Pushgateway.Enabled)
+}
+
+func TestWithPrometheusGatewayUrl(t *testing.T) {
+       opts := NewOptions(WithPrometheusGatewayUrl("http://localhost:9091";))
+       assert.Equal(t, "http://localhost:9091";, 
opts.Metrics.Prometheus.Pushgateway.BaseUrl)
+}
+
+func TestWithPrometheusGatewayJob(t *testing.T) {
+       opts := NewOptions(WithPrometheusGatewayJob("test-job"))
+       assert.Equal(t, "test-job", opts.Metrics.Prometheus.Pushgateway.Job)
+}
+
+func TestWithPrometheusGatewayUsername(t *testing.T) {
+       opts := NewOptions(WithPrometheusGatewayUsername("admin"))
+       assert.Equal(t, "admin", opts.Metrics.Prometheus.Pushgateway.Username)
+}
+
+func TestWithPrometheusGatewayPassword(t *testing.T) {
+       opts := NewOptions(WithPrometheusGatewayPassword("secret"))
+       assert.Equal(t, "secret", opts.Metrics.Prometheus.Pushgateway.Password)
+}
+
+func TestWithPrometheusGatewayInterval(t *testing.T) {
+       opts := NewOptions(WithPrometheusGatewayInterval(60 * time.Second))
+       assert.Equal(t, 60, opts.Metrics.Prometheus.Pushgateway.PushInterval)
+}
+
+func TestWithConfigCenterEnabled(t *testing.T) {
+       opts := NewOptions(WithConfigCenterEnabled())
+       assert.NotNil(t, opts.Metrics.EnableConfigCenter)
+       assert.True(t, *opts.Metrics.EnableConfigCenter)
+}
+
+func TestWithMetadataEnabled(t *testing.T) {
+       opts := NewOptions(WithMetadataEnabled())
+       assert.NotNil(t, opts.Metrics.EnableMetadata)
+       assert.True(t, *opts.Metrics.EnableMetadata)
+}
+
+func TestWithRegistryEnabled(t *testing.T) {
+       opts := NewOptions(WithRegistryEnabled())
+       assert.NotNil(t, opts.Metrics.EnableRegistry)
+       assert.True(t, *opts.Metrics.EnableRegistry)
+}
+
+func TestWithEnabled(t *testing.T) {
+       opts := NewOptions(WithEnabled())
+       assert.NotNil(t, opts.Metrics.Enable)
+       assert.True(t, *opts.Metrics.Enable)
+}
+
+func TestWithPort(t *testing.T) {
+       opts := NewOptions(WithPort(8080))
+       assert.Equal(t, "8080", opts.Metrics.Port)
+}
+
+func TestWithPath(t *testing.T) {
+       opts := NewOptions(WithPath("/custom/metrics"))
+       assert.Equal(t, "/custom/metrics", opts.Metrics.Path)
+}
diff --git a/metrics/registry/event_test.go b/metrics/registry/event_test.go
new file mode 100644
index 000000000..b7d2236de
--- /dev/null
+++ b/metrics/registry/event_test.go
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package registry
+
+import (
+       "testing"
+       "time"
+)
+
+import (
+       "github.com/stretchr/testify/assert"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/common/constant"
+)
+
+func TestRegistryMetricsEventType(t *testing.T) {
+       event := &RegistryMetricsEvent{
+               Name: Reg,
+               Succ: true,
+       }
+
+       assert.Equal(t, constant.MetricsRegistry, event.Type())
+}
+
+func TestRegistryMetricsEventCostMs(t *testing.T) {
+       start := time.Now()
+       end := start.Add(10 * time.Millisecond)
+
+       event := &RegistryMetricsEvent{
+               Name:  Reg,
+               Start: start,
+               End:   end,
+       }
+
+       cost := event.CostMs()
+       assert.Equal(t, 10.0, cost)
+}
+
+func TestNewRegisterEvent(t *testing.T) {
+       start := time.Now()
+       event := NewRegisterEvent(true, start).(*RegistryMetricsEvent)
+
+       assert.NotNil(t, event)
+       assert.Equal(t, Reg, event.Name)
+       assert.True(t, event.Succ)
+       assert.Equal(t, start, event.Start)
+       assert.True(t, event.End.After(start))
+}
+
+func TestNewSubscribeEvent(t *testing.T) {
+       event := NewSubscribeEvent(true).(*RegistryMetricsEvent)
+
+       assert.NotNil(t, event)
+       assert.Equal(t, Sub, event.Name)
+       assert.True(t, event.Succ)
+}
+
+func TestNewNotifyEvent(t *testing.T) {
+       start := time.Now()
+       event := NewNotifyEvent(start).(*RegistryMetricsEvent)
+
+       assert.NotNil(t, event)
+       assert.Equal(t, Notify, event.Name)
+       assert.Equal(t, start, event.Start)
+       assert.True(t, event.End.After(start))
+}
+
+func TestNewDirectoryEvent(t *testing.T) {
+       event := NewDirectoryEvent("test-dir").(*RegistryMetricsEvent)
+
+       assert.NotNil(t, event)
+       assert.Equal(t, Directory, event.Name)
+       assert.NotNil(t, event.Attachment)
+       assert.Equal(t, "test-dir", event.Attachment["DirTyp"])
+}
+
+func TestNewServerRegisterEvent(t *testing.T) {
+       start := time.Now()
+       event := NewServerRegisterEvent(true, start).(*RegistryMetricsEvent)
+
+       assert.NotNil(t, event)
+       assert.Equal(t, ServerReg, event.Name)
+       assert.True(t, event.Succ)
+       assert.Equal(t, start, event.Start)
+       assert.True(t, event.End.After(start))
+}
+
+func TestNewServerSubscribeEvent(t *testing.T) {
+       event := NewServerSubscribeEvent(true).(*RegistryMetricsEvent)
+
+       assert.NotNil(t, event)
+       assert.Equal(t, ServerSub, event.Name)
+       assert.True(t, event.Succ)
+}
diff --git a/metrics/bus_test.go b/metrics/reporter_test.go
similarity index 64%
copy from metrics/bus_test.go
copy to metrics/reporter_test.go
index 4e4a68eec..45fa7141d 100644
--- a/metrics/bus_test.go
+++ b/metrics/reporter_test.go
@@ -25,30 +25,15 @@ import (
        "github.com/stretchr/testify/assert"
 )
 
-var mockChan = make(chan MetricsEvent, 16)
-
-type MockEvent struct {
-}
-
-func (m MockEvent) Type() string {
-       return "dubbo.metrics.mock"
-}
-
-func NewEmptyMockEvent() *MockEvent {
-       return &MockEvent{}
-}
-
-func init() {
-       Subscribe("dubbo.metrics.mock", mockChan)
-       Publish(NewEmptyMockEvent())
-}
-
-func TestBusPublish(t *testing.T) {
-       t.Run("testBusPublish", func(t *testing.T) {
-               event := <-mockChan
-
-               if event, ok := event.(MockEvent); ok {
-                       assert.Equal(t, event, NewEmptyMockEvent())
-               }
-       })
+func TestNewReporterConfig(t *testing.T) {
+       config := NewReporterConfig()
+
+       assert.NotNil(t, config)
+       assert.True(t, config.Enable)
+       assert.Equal(t, "dubbo", config.Namespace)
+       assert.Equal(t, "9090", config.Port)
+       assert.Equal(t, "/metrics", config.Path)
+       assert.Equal(t, string(ReportModePull), string(config.Mode))
+       assert.Empty(t, config.PushGatewayAddress)
+       assert.Equal(t, int64(DefMaxAge), config.SummaryMaxAge)
 }
diff --git a/metrics/rpc/event_test.go b/metrics/rpc/event_test.go
new file mode 100644
index 000000000..a20e20350
--- /dev/null
+++ b/metrics/rpc/event_test.go
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package rpc
+
+import (
+       "testing"
+       "time"
+)
+
+import (
+       "github.com/stretchr/testify/assert"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/common"
+       "dubbo.apache.org/dubbo-go/v3/common/constant"
+       "dubbo.apache.org/dubbo-go/v3/protocol/base"
+       "dubbo.apache.org/dubbo-go/v3/protocol/invocation"
+       "dubbo.apache.org/dubbo-go/v3/protocol/result"
+)
+
+func TestMetricsEventType(t *testing.T) {
+       url := base.NewBaseInvoker(&common.URL{})
+       invoc := invocation.NewRPCInvocation("TestMethod", []any{}, nil)
+
+       event := &metricsEvent{
+               name:       BeforeInvoke,
+               invoker:    url,
+               invocation: invoc,
+       }
+
+       assert.Equal(t, constant.MetricsRpc, event.Type())
+}
+
+func TestNewBeforeInvokeEvent(t *testing.T) {
+       url := base.NewBaseInvoker(&common.URL{})
+       invoc := invocation.NewRPCInvocation("TestMethod", []any{}, nil)
+
+       event := NewBeforeInvokeEvent(url, invoc).(*metricsEvent)
+
+       assert.NotNil(t, event)
+       assert.Equal(t, BeforeInvoke, event.name)
+       assert.Equal(t, url, event.invoker)
+       assert.Equal(t, invoc, event.invocation)
+}
+
+func TestNewAfterInvokeEvent(t *testing.T) {
+       url := base.NewBaseInvoker(&common.URL{})
+       invoc := invocation.NewRPCInvocation("TestMethod", []any{}, nil)
+       costTime := 100 * time.Millisecond
+       res := &result.RPCResult{}
+
+       event := NewAfterInvokeEvent(url, invoc, costTime, res).(*metricsEvent)
+
+       assert.NotNil(t, event)
+       assert.Equal(t, AfterInvoke, event.name)
+       assert.Equal(t, url, event.invoker)
+       assert.Equal(t, invoc, event.invocation)
+       assert.Equal(t, costTime, event.costTime)
+       assert.Equal(t, res, event.result)
+}
diff --git a/metrics/rpc/util_test.go b/metrics/rpc/util_test.go
new file mode 100644
index 000000000..fdc861d85
--- /dev/null
+++ b/metrics/rpc/util_test.go
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package rpc
+
+import (
+       "strconv"
+       "testing"
+)
+
+import (
+       "github.com/stretchr/testify/assert"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/common"
+       "dubbo.apache.org/dubbo-go/v3/common/constant"
+       "dubbo.apache.org/dubbo-go/v3/protocol/invocation"
+)
+
+func TestBuildLabels(t *testing.T) {
+       url := common.NewURLWithOptions(
+               common.WithProtocol("dubbo"),
+               common.WithParamsValue(constant.ApplicationKey, "test-app"),
+               common.WithParamsValue(constant.AppVersionKey, "1.0.0"),
+               common.WithParamsValue(constant.VersionKey, "2.0.0"),
+               common.WithParamsValue(constant.GroupKey, "test-group"),
+               common.WithParamsValue(constant.InterfaceKey, 
"com.example.Service"),
+       )
+
+       invoc := invocation.NewRPCInvocation("TestMethod", []any{}, nil)
+
+       labels := buildLabels(url, invoc)
+
+       assert.NotNil(t, labels)
+       assert.Equal(t, "test-app", labels[constant.TagApplicationName])
+       assert.Equal(t, "1.0.0", labels[constant.TagApplicationVersion])
+       assert.Equal(t, "com.example.Service", labels[constant.TagInterface])
+       assert.Equal(t, "TestMethod", labels[constant.TagMethod])
+       assert.Equal(t, "test-group", labels[constant.TagGroup])
+       assert.Equal(t, "2.0.0", labels[constant.TagVersion])
+       assert.NotEmpty(t, labels[constant.TagHostname])
+       assert.NotEmpty(t, labels[constant.TagIp])
+}
+
+func TestGetRole(t *testing.T) {
+       tests := []struct {
+               name     string
+               url      *common.URL
+               expected string
+       }{
+               {
+                       name: "provider role",
+                       url: common.NewURLWithOptions(
+                               
common.WithParamsValue(constant.RegistryRoleKey, strconv.Itoa(common.PROVIDER)),
+                       ),
+                       expected: constant.SideProvider,
+               },
+               {
+                       name: "consumer role",
+                       url: common.NewURLWithOptions(
+                               
common.WithParamsValue(constant.RegistryRoleKey, strconv.Itoa(common.CONSUMER)),
+                       ),
+                       expected: constant.SideConsumer,
+               },
+               {
+                       name: "no role",
+                       url: common.NewURLWithOptions(
+                               common.WithProtocol("dubbo"),
+                       ),
+                       expected: "",
+               },
+               {
+                       name: "invalid role",
+                       url: common.NewURLWithOptions(
+                               
common.WithParamsValue(constant.RegistryRoleKey, "invalid"),
+                       ),
+                       expected: "",
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       role := getRole(tt.url)
+                       assert.Equal(t, tt.expected, role)
+               })
+       }
+}
+
+func TestIsProvider(t *testing.T) {
+       tests := []struct {
+               name     string
+               url      *common.URL
+               expected bool
+       }{
+               {
+                       name: "provider",
+                       url: common.NewURLWithOptions(
+                               
common.WithParamsValue(constant.RegistryRoleKey, strconv.Itoa(common.PROVIDER)),
+                       ),
+                       expected: true,
+               },
+               {
+                       name: "provider string",
+                       url: common.NewURLWithOptions(
+                               
common.WithParamsValue(constant.RegistryRoleKey, "3"),
+                       ),
+                       expected: true,
+               },
+               {
+                       name: "not provider",
+                       url: common.NewURLWithOptions(
+                               
common.WithParamsValue(constant.RegistryRoleKey, strconv.Itoa(common.CONSUMER)),
+                       ),
+                       expected: false,
+               },
+               {
+                       name: "no role",
+                       url: common.NewURLWithOptions(
+                               common.WithProtocol("dubbo"),
+                       ),
+                       expected: false,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       result := isProvider(tt.url)
+                       assert.Equal(t, tt.expected, result)
+               })
+       }
+}
+
+func TestIsConsumer(t *testing.T) {
+       tests := []struct {
+               name     string
+               url      *common.URL
+               expected bool
+       }{
+               {
+                       name: "consumer",
+                       url: common.NewURLWithOptions(
+                               
common.WithParamsValue(constant.RegistryRoleKey, strconv.Itoa(common.CONSUMER)),
+                       ),
+                       expected: true,
+               },
+               {
+                       name: "consumer string",
+                       url: common.NewURLWithOptions(
+                               
common.WithParamsValue(constant.RegistryRoleKey, "0"),
+                       ),
+                       expected: true,
+               },
+               {
+                       name: "not consumer",
+                       url: common.NewURLWithOptions(
+                               
common.WithParamsValue(constant.RegistryRoleKey, strconv.Itoa(common.PROVIDER)),
+                       ),
+                       expected: false,
+               },
+               {
+                       name: "no role",
+                       url: common.NewURLWithOptions(
+                               common.WithProtocol("dubbo"),
+                       ),
+                       expected: false,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       result := isConsumer(tt.url)
+                       assert.Equal(t, tt.expected, result)
+               })
+       }
+}


Reply via email to