@@ -255,28 +255,44 @@ def test_constructor_w_directed_read_options(self):
255255 expected_scopes , creds , directed_read_options = self .DIRECTED_READ_OPTIONS
256256 )
257257
258+ @mock .patch ("google.cloud.spanner_v1.client.metrics" )
259+ @mock .patch ("google.cloud.spanner_v1.client.CloudMonitoringMetricsExporter" )
260+ @mock .patch ("google.cloud.spanner_v1.client.PeriodicExportingMetricReader" )
261+ @mock .patch ("google.cloud.spanner_v1.client.MeterProvider" )
258262 @mock .patch ("google.cloud.spanner_v1.client.SpannerMetricsTracerFactory" )
259263 @mock .patch .dict (os .environ , {"SPANNER_DISABLE_BUILTIN_METRICS" : "false" })
260264 def test_constructor_w_metrics_initialization_error (
261- self , mock_spanner_metrics_factory
265+ self ,
266+ mock_spanner_metrics_factory ,
267+ mock_meter_provider ,
268+ mock_periodic_reader ,
269+ mock_exporter ,
270+ mock_metrics ,
262271 ):
263272 """
264273 Test that Client constructor handles exceptions during metrics
265274 initialization and logs a warning.
266275 """
267276 from google .cloud .spanner_v1 .client import Client
277+ from google .cloud .spanner_v1 import client as MUT
268278
279+ MUT ._metrics_monitor_initialized = False
269280 mock_spanner_metrics_factory .side_effect = Exception ("Metrics init failed" )
270281 creds = build_scoped_credentials ()
271-
272- with self .assertLogs ("google.cloud.spanner_v1.client" , level = "WARNING" ) as log :
273- client = Client (project = self .PROJECT , credentials = creds )
274- self .assertIsNotNone (client )
275- self .assertIn (
276- "Failed to initialize Spanner built-in metrics. Error: Metrics init failed" ,
277- log .output [0 ],
278- )
279- mock_spanner_metrics_factory .assert_called_once ()
282+ try :
283+ with self .assertLogs (
284+ "google.cloud.spanner_v1.client" , level = "WARNING"
285+ ) as log :
286+ client = Client (project = self .PROJECT , credentials = creds )
287+ self .assertIsNotNone (client )
288+ self .assertIn (
289+ "Failed to initialize Spanner built-in metrics. Error: Metrics init failed" ,
290+ log .output [0 ],
291+ )
292+ mock_spanner_metrics_factory .assert_called_once ()
293+ mock_metrics .set_meter_provider .assert_called_once ()
294+ finally :
295+ MUT ._metrics_monitor_initialized = False
280296
281297 @mock .patch ("google.cloud.spanner_v1.client.SpannerMetricsTracerFactory" )
282298 @mock .patch .dict (os .environ , {"SPANNER_DISABLE_BUILTIN_METRICS" : "true" })
@@ -293,6 +309,61 @@ def test_constructor_w_disable_builtin_metrics_using_env(
293309 self .assertIsNotNone (client )
294310 mock_spanner_metrics_factory .assert_called_once_with (enabled = False )
295311
312+ self .assertIsNotNone (client )
313+ mock_spanner_metrics_factory .assert_called_once_with (enabled = False )
314+
315+ @mock .patch ("google.cloud.spanner_v1.client.metrics" )
316+ @mock .patch ("google.cloud.spanner_v1.client.CloudMonitoringMetricsExporter" )
317+ @mock .patch ("google.cloud.spanner_v1.client.PeriodicExportingMetricReader" )
318+ @mock .patch ("google.cloud.spanner_v1.client.MeterProvider" )
319+ @mock .patch ("google.cloud.spanner_v1.client.SpannerMetricsTracerFactory" )
320+ @mock .patch .dict (os .environ , {"SPANNER_DISABLE_BUILTIN_METRICS" : "false" })
321+ def test_constructor_metrics_singleton_behavior (
322+ self ,
323+ mock_spanner_metrics_factory ,
324+ mock_meter_provider ,
325+ mock_periodic_reader ,
326+ mock_exporter ,
327+ mock_metrics ,
328+ ):
329+ """
330+ Test that metrics are only initialized once.
331+ """
332+ from google .cloud .spanner_v1 import client as MUT
333+
334+ # Reset global state for this test
335+ MUT ._metrics_monitor_initialized = False
336+ try :
337+ creds = build_scoped_credentials ()
338+
339+ # First client initialization
340+ client1 = MUT .Client (project = self .PROJECT , credentials = creds )
341+ self .assertIsNotNone (client1 )
342+ mock_metrics .set_meter_provider .assert_called_once ()
343+ mock_spanner_metrics_factory .assert_called_once ()
344+
345+ # Verify MeterProvider chain was created
346+ mock_meter_provider .assert_called_once ()
347+ mock_periodic_reader .assert_called_once ()
348+ mock_exporter .assert_called_once ()
349+
350+ self .assertTrue (MUT ._metrics_monitor_initialized )
351+
352+ # Reset mocks to verify they are NOT called again
353+ mock_metrics .set_meter_provider .reset_mock ()
354+ mock_spanner_metrics_factory .reset_mock ()
355+ mock_meter_provider .reset_mock ()
356+
357+ # Second client initialization
358+ client2 = MUT .Client (project = self .PROJECT , credentials = creds )
359+ self .assertIsNotNone (client2 )
360+ mock_metrics .set_meter_provider .assert_not_called ()
361+ mock_spanner_metrics_factory .assert_not_called ()
362+ mock_meter_provider .assert_not_called ()
363+ self .assertTrue (MUT ._metrics_monitor_initialized )
364+ finally :
365+ MUT ._metrics_monitor_initialized = False
366+
296367 @mock .patch ("google.cloud.spanner_v1.client.SpannerMetricsTracerFactory" )
297368 def test_constructor_w_disable_builtin_metrics_using_option (
298369 self , mock_spanner_metrics_factory
0 commit comments