Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-opentelemetry-test-utils for 
openSUSE:Factory checked in at 2026-06-27 18:04:24
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-opentelemetry-test-utils (Old)
 and      /work/SRC/openSUSE:Factory/.python-opentelemetry-test-utils.new.11887 
(New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-opentelemetry-test-utils"

Sat Jun 27 18:04:24 2026 rev:14 rq:1361878 version:0.63b1

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-opentelemetry-test-utils/python-opentelemetry-test-utils.changes
  2026-04-28 11:53:13.037658516 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-opentelemetry-test-utils.new.11887/python-opentelemetry-test-utils.changes
       2026-06-27 18:05:28.690723378 +0200
@@ -1,0 +2,15 @@
+Sat Jun 20 16:05:52 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 0.63b1:
+  * This is a patch release on the previous 1.42.0/0.63b0
+    release, fixing the issue(s) below.
+
+- update to 0.63b0:
+  * `opentelemetry-test-utils`: fix weaver live check hanging
+    when weaver log output fills the pipe buffer
+  * Expand `AGENTS.md` with instrumentation/GenAI guidance and
+    add PR review instructions.
+  * Switch to SPDX license headers and add CI enforcement
+  * Drop Python 3.9 support
+
+-------------------------------------------------------------------

Old:
----
  opentelemetry_test_utils-0.62b1.tar.gz

New:
----
  opentelemetry_test_utils-0.63b1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-opentelemetry-test-utils.spec ++++++
--- /var/tmp/diff_new_pack.fTGlmI/_old  2026-06-27 18:05:29.378746443 +0200
+++ /var/tmp/diff_new_pack.fTGlmI/_new  2026-06-27 18:05:29.382746577 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-opentelemetry-test-utils
-Version:        0.62b1
+Version:        0.63b1
 Release:        0
 Summary:        Test utilities for OpenTelemetry unit tests
 License:        Apache-2.0
@@ -26,14 +26,14 @@
 Source:         
https://files.pythonhosted.org/packages/source/o/opentelemetry-test-utils/opentelemetry_test_utils-%{version}.tar.gz
 BuildRequires:  %{python_module asgiref >= 3.0}
 BuildRequires:  %{python_module hatchling}
-BuildRequires:  %{python_module opentelemetry-api = 1.41.1}
-BuildRequires:  %{python_module opentelemetry-sdk = 1.41.1}
+BuildRequires:  %{python_module opentelemetry-api = 1.42.1}
+BuildRequires:  %{python_module opentelemetry-sdk = 1.42.1}
 BuildRequires:  %{python_module pip}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
 Requires:       python-asgiref >= 3.0
-Requires:       python-opentelemetry-api = 1.41.1
-Requires:       python-opentelemetry-sdk = 1.41.1
+Requires:       python-opentelemetry-api = 1.42.1
+Requires:       python-opentelemetry-sdk = 1.42.1
 BuildArch:      noarch
 %python_subpackages
 

++++++ opentelemetry_test_utils-0.62b1.tar.gz -> 
opentelemetry_test_utils-0.63b1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/opentelemetry_test_utils-0.62b1/PKG-INFO 
new/opentelemetry_test_utils-0.63b1/PKG-INFO
--- old/opentelemetry_test_utils-0.62b1/PKG-INFO        2020-02-02 
01:00:00.000000000 +0100
+++ new/opentelemetry_test_utils-0.63b1/PKG-INFO        2020-02-02 
01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: opentelemetry-test-utils
-Version: 0.62b1
+Version: 0.63b1
 Summary: Test utilities for OpenTelemetry unit tests
 Project-URL: Homepage, 
https://github.com/open-telemetry/opentelemetry-python/tests/opentelemetry-test-utils
 Project-URL: Repository, https://github.com/open-telemetry/opentelemetry-python
@@ -11,16 +11,16 @@
 Classifier: Intended Audience :: Developers
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.9
 Classifier: Programming Language :: Python :: 3.10
 Classifier: Programming Language :: Python :: 3.11
 Classifier: Programming Language :: Python :: 3.12
 Classifier: Programming Language :: Python :: 3.13
 Classifier: Programming Language :: Python :: 3.14
-Requires-Python: >=3.9
+Requires-Python: >=3.10
 Requires-Dist: asgiref~=3.0
-Requires-Dist: opentelemetry-api==1.41.1
-Requires-Dist: opentelemetry-sdk==1.41.1
+Requires-Dist: opentelemetry-api==1.42.1
+Requires-Dist: opentelemetry-sdk==1.42.1
+Requires-Dist: requests~=2.28
 Description-Content-Type: text/x-rst
 
 OpenTelemetry Test Utilities
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/opentelemetry_test_utils-0.62b1/pyproject.toml 
new/opentelemetry_test_utils-0.63b1/pyproject.toml
--- old/opentelemetry_test_utils-0.62b1/pyproject.toml  2020-02-02 
01:00:00.000000000 +0100
+++ new/opentelemetry_test_utils-0.63b1/pyproject.toml  2020-02-02 
01:00:00.000000000 +0100
@@ -8,7 +8,7 @@
 description = "Test utilities for OpenTelemetry unit tests"
 readme = "README.rst"
 license = "Apache-2.0"
-requires-python = ">=3.9"
+requires-python = ">=3.10"
 authors = [
   { name = "OpenTelemetry Authors", email = 
"[email protected]" },
 ]
@@ -18,7 +18,6 @@
   "Intended Audience :: Developers",
   "Programming Language :: Python",
   "Programming Language :: Python :: 3",
-  "Programming Language :: Python :: 3.9",
   "Programming Language :: Python :: 3.10",
   "Programming Language :: Python :: 3.11",
   "Programming Language :: Python :: 3.12",
@@ -27,8 +26,9 @@
 ]
 dependencies = [
   "asgiref ~= 3.0",
-  "opentelemetry-api == 1.41.1",
-  "opentelemetry-sdk == 1.41.1",
+  "opentelemetry-api == 1.42.1",
+  "opentelemetry-sdk == 1.42.1",
+  "requests ~= 2.28",
 ]
 
 [project.urls]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/__init__.py 
new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/__init__.py
--- old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/__init__.py      
2020-02-02 01:00:00.000000000 +0100
+++ new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/__init__.py      
2020-02-02 01:00:00.000000000 +0100
@@ -1,16 +1,5 @@
 # Copyright The OpenTelemetry Authors
-#
-# Licensed 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.
+# SPDX-License-Identifier: Apache-2.0
 
 # type: ignore
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/asgitestutil.py 
new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/asgitestutil.py
--- old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/asgitestutil.py  
2020-02-02 01:00:00.000000000 +0100
+++ new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/asgitestutil.py  
2020-02-02 01:00:00.000000000 +0100
@@ -1,16 +1,5 @@
 # Copyright The OpenTelemetry Authors
-#
-# Licensed 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.
+# SPDX-License-Identifier: Apache-2.0
 
 import asyncio
 from unittest import IsolatedAsyncioTestCase
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/concurrency_test.py 
new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/concurrency_test.py
--- 
old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/concurrency_test.py  
    2020-02-02 01:00:00.000000000 +0100
+++ 
new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/concurrency_test.py  
    2020-02-02 01:00:00.000000000 +0100
@@ -1,22 +1,12 @@
 # Copyright The OpenTelemetry Authors
-#
-# Licensed 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.
+# SPDX-License-Identifier: Apache-2.0
 
 import sys
 import threading
 import unittest
+from collections.abc import Callable
 from functools import partial
-from typing import Callable, List, Optional, TypeVar
+from typing import TypeVar
 from unittest.mock import Mock
 
 ReturnT = TypeVar("ReturnT")
@@ -66,11 +56,11 @@
     def run_with_many_threads(
         func_to_test: Callable[[], ReturnT],
         num_threads: int = 100,
-    ) -> List[ReturnT]:
+    ) -> list[ReturnT]:
         """Util to run ``func_to_test`` in ``num_threads`` concurrently"""
 
         barrier = threading.Barrier(num_threads)
-        results: List[Optional[ReturnT]] = [None] * num_threads
+        results: list[ReturnT | None] = [None] * num_threads
 
         def thread_start(idx: int) -> None:
             nonlocal results
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/globals_test.py 
new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/globals_test.py
--- old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/globals_test.py  
2020-02-02 01:00:00.000000000 +0100
+++ new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/globals_test.py  
2020-02-02 01:00:00.000000000 +0100
@@ -1,16 +1,5 @@
 # Copyright The OpenTelemetry Authors
-#
-# Licensed 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.
+# SPDX-License-Identifier: Apache-2.0
 
 import unittest
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/httptest.py 
new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/httptest.py
--- old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/httptest.py      
2020-02-02 01:00:00.000000000 +0100
+++ new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/httptest.py      
2020-02-02 01:00:00.000000000 +0100
@@ -1,16 +1,5 @@
 # Copyright The OpenTelemetry Authors
-#
-# Licensed 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.
+# SPDX-License-Identifier: Apache-2.0
 
 import re
 import unittest
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/metrictestutil.py 
new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/metrictestutil.py
--- 
old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/metrictestutil.py    
    2020-02-02 01:00:00.000000000 +0100
+++ 
new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/metrictestutil.py    
    2020-02-02 01:00:00.000000000 +0100
@@ -1,20 +1,7 @@
 # Copyright The OpenTelemetry Authors
-#
-# Licensed 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.
+# SPDX-License-Identifier: Apache-2.0
 
 
-from typing import Optional
-
 from opentelemetry.attributes import BoundedAttributes
 from opentelemetry.sdk.metrics.export import (
     AggregationTemporality,
@@ -108,8 +95,8 @@
 def _generate_histogram(
     name: str,
     attributes: Attributes = None,
-    description: Optional[str] = None,
-    unit: Optional[str] = None,
+    description: str | None = None,
+    unit: str | None = None,
 ) -> Metric:
     if attributes is None:
         attributes = BoundedAttributes(attributes={"a": 1, "b": True})
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/mock_test_classes.py 
new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/mock_test_classes.py
--- 
old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/mock_test_classes.py 
    2020-02-02 01:00:00.000000000 +0100
+++ 
new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/mock_test_classes.py 
    2020-02-02 01:00:00.000000000 +0100
@@ -1,16 +1,5 @@
 # Copyright The OpenTelemetry Authors
-#
-# Licensed 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.
+# SPDX-License-Identifier: Apache-2.0
 
 
 class IterEntryPoint:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/mock_textmap.py 
new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/mock_textmap.py
--- old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/mock_textmap.py  
2020-02-02 01:00:00.000000000 +0100
+++ new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/mock_textmap.py  
2020-02-02 01:00:00.000000000 +0100
@@ -1,18 +1,6 @@
 # Copyright The OpenTelemetry Authors
-#
-# Licensed 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.
+# SPDX-License-Identifier: Apache-2.0
 
-import typing
 
 from opentelemetry import trace
 from opentelemetry.context import Context
@@ -36,7 +24,7 @@
     def extract(
         self,
         carrier: CarrierT,
-        context: typing.Optional[Context] = None,
+        context: Context | None = None,
         getter: Getter = default_getter,
     ) -> Context:
         return Context()
@@ -44,7 +32,7 @@
     def inject(
         self,
         carrier: CarrierT,
-        context: typing.Optional[Context] = None,
+        context: Context | None = None,
         setter: Setter = default_setter,
     ) -> None:
         return None
@@ -63,7 +51,7 @@
     def extract(
         self,
         carrier: CarrierT,
-        context: typing.Optional[Context] = None,
+        context: Context | None = None,
         getter: Getter = default_getter,
     ) -> Context:
         if context is None:
@@ -88,7 +76,7 @@
     def inject(
         self,
         carrier: CarrierT,
-        context: typing.Optional[Context] = None,
+        context: Context | None = None,
         setter: Setter = default_setter,
     ) -> None:
         span = trace.get_current_span(context)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/spantestutil.py 
new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/spantestutil.py
--- old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/spantestutil.py  
2020-02-02 01:00:00.000000000 +0100
+++ new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/spantestutil.py  
2020-02-02 01:00:00.000000000 +0100
@@ -1,16 +1,5 @@
 # Copyright The OpenTelemetry Authors
-#
-# Licensed 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.
+# SPDX-License-Identifier: Apache-2.0
 
 from functools import partial
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/test_base.py 
new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/test_base.py
--- old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/test_base.py     
2020-02-02 01:00:00.000000000 +0100
+++ new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/test_base.py     
2020-02-02 01:00:00.000000000 +0100
@@ -1,21 +1,10 @@
 # Copyright The OpenTelemetry Authors
-#
-# Licensed 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.
+# SPDX-License-Identifier: Apache-2.0
 
 import logging
 import unittest
+from collections.abc import Sequence
 from contextlib import contextmanager
-from typing import Optional, Sequence, Tuple
 
 from opentelemetry import metrics as metrics_api
 from opentelemetry import trace as trace_api
@@ -118,7 +107,7 @@
         return tracer_provider, memory_exporter
 
     @staticmethod
-    def create_meter_provider(**kwargs) -> Tuple[MeterProvider, MetricReader]:
+    def create_meter_provider(**kwargs) -> tuple[MeterProvider, MetricReader]:
         """Helper to create a configured meter provider
         Creates a `MeterProvider` and an `InMemoryMetricReader`.
         Returns:
@@ -142,7 +131,7 @@
         finally:
             logging.disable(logging.NOTSET)
 
-    def get_sorted_metrics(self, scope: Optional[str] = None):
+    def get_sorted_metrics(self, scope: str | None = None):
         """Returns recorded metrics sorted by name.
 
         Args:
@@ -177,7 +166,7 @@
         self,
         metric: Metric,
         expected_data_points: Sequence[DataPointT],
-        est_value_delta: Optional[float] = 0,
+        est_value_delta: float | None = 0,
     ):
         self.assertEqual(
             len(expected_data_points), len(metric.data.data_points)
@@ -192,7 +181,7 @@
     def is_data_points_equal(
         expected_data_point: DataPointT,
         data_point: DataPointT,
-        est_value_delta: Optional[float] = 0,
+        est_value_delta: float | None = 0,
     ):
         if type(expected_data_point) != type(  # noqa: E721
             data_point
@@ -230,7 +219,7 @@
         self,
         expected_data_point: DataPointT,
         data_points: Sequence[DataPointT],
-        est_value_delta: Optional[float] = 0,
+        est_value_delta: float | None = 0,
     ):
         is_data_point_exist = False
         for data_point in data_points:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/version/__init__.py 
new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/version/__init__.py
--- 
old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/version/__init__.py  
    2020-02-02 01:00:00.000000000 +0100
+++ 
new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/version/__init__.py  
    2020-02-02 01:00:00.000000000 +0100
@@ -1 +1,4 @@
-__version__ = "0.62b1"
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+
+__version__ = "0.63b1"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/weaver_live_check.py 
new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/weaver_live_check.py
--- 
old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/weaver_live_check.py 
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/weaver_live_check.py 
    2020-02-02 01:00:00.000000000 +0100
@@ -0,0 +1,496 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+
+import functools
+import json
+import logging
+import os
+import shutil
+import socket
+import subprocess
+import tempfile
+from collections import defaultdict
+from collections.abc import Sequence
+from itertools import chain
+from typing import Any
+
+from requests import Session, post
+from requests.adapters import HTTPAdapter
+from urllib3.util.retry import Retry
+
+from opentelemetry.semconv.schemas import Schemas
+
+logger = logging.getLogger(__name__)
+
+
+def _find_free_port() -> int:
+    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
+        sock.bind(("", 0))
+        return sock.getsockname()[1]
+
+
+def _extract_violations(report: dict) -> list:
+    """Extract and deduplicate violations from the full report.
+
+    Get all violations using python version of this jq filter:
+      [ .. | objects | select(has("live_check_result"))
+        | .live_check_result.all_advice[]?
+        | select(.level == "violation") ]
+      | group_by(.id, .message, .context, .signal_name, .signal_type)
+      | map({ ..., count: length })
+      | sort_by(-.count, .message)
+    """
+    raw: list[dict] = []
+
+    def _collect(obj: Any) -> list[dict]:
+        if isinstance(obj, dict):
+            result: list[dict] = []
+            lcr = obj.get("live_check_result")
+            if isinstance(lcr, dict):
+                advices = lcr.get("all_advice")
+                if isinstance(advices, list):
+                    result.extend(
+                        a for a in advices if a.get("level") == "violation"
+                    )
+            for value in obj.values():
+                result.extend(_collect(value))
+            return result
+        if isinstance(obj, list):
+            return list(chain.from_iterable(_collect(item) for item in obj))
+        return []
+
+    raw = _collect(report)
+
+    groups: dict[tuple, list] = defaultdict(list)
+    for violation in raw:
+        ctx = violation.get("context")
+        key = (
+            violation.get("id"),
+            violation.get("message"),
+            json.dumps(ctx, sort_keys=True)
+            if isinstance(ctx, (dict, list))
+            else ctx,
+            violation.get("signal_name"),
+            violation.get("signal_type"),
+        )
+        groups[key].append(violation)
+
+    violations = [
+        {
+            "id": k[0],
+            "message": k[1],
+            "context": vs[0].get(
+                "context"
+            ),  # preserve original dict, not JSON string
+            "signal_name": k[3],
+            "signal_type": k[4],
+            "count": len(vs),
+        }
+        for k, vs in groups.items()
+    ]
+    violations.sort(key=lambda v: (-v["count"], v.get("message") or ""))
+    return violations
+
+
+def _format_violations(violations: list) -> str:
+    """Format violations list as human-readable text."""
+    lines = []
+    for violation in violations:
+        signal = ""
+        signal_type = violation.get("signal_type")
+        signal_name = violation.get("signal_name")
+        if signal_type and signal_name:
+            signal = f" on {signal_type} '{signal_name}'"
+        elif signal_type:
+            signal = f" on {signal_type}"
+        elif signal_name:
+            signal = f" on '{signal_name}'"
+        lines.append(
+            f"- [{violation.get('id')}] {violation.get('message')} 
({violation['count']} occurrence(s){signal})"
+        )
+    return "\n".join(lines)
+
+
+class LiveCheckError(AssertionError):
+    """Raised by :meth:`WeaverLiveCheck.end_and_check` when semconv violations 
are found.
+
+    The full :class:`LiveCheckReport` is attached as :attr:`report` for
+    structured inspection beyond the human-readable message::
+
+        with pytest.raises(LiveCheckError) as exc_info:
+            weaver.end_and_check()
+
+        err = exc_info.value
+        assert any(
+            v["id"] == "my_policy_check"
+            and v["context"]["attribute_name"] == "my.attribute"
+            for v in err.report.violations
+        )
+    """
+
+    def __init__(self, message: str, report: "LiveCheckReport") -> None:
+        super().__init__(message)
+        self.report = report
+
+
+class LiveCheckReport:
+    """The result of a weaver live-check run.
+
+    Provides structured access to violations and the full raw JSON report.
+
+    See 
https://github.com/open-telemetry/weaver/tree/main/crates/weaver_live_check#output
+    for the full report structure.
+
+    Example — asserting on metrics statistics::
+
+        report = weaver.end()
+        seen = report["statistics"]["seen_registry_metrics"]
+        assert seen.get("http.server.request.duration") == 1
+
+    Example — asserting on violations::
+
+        report = weaver.end()
+        assert any(
+            v["id"] == "my_policy_check"
+            and v["context"]["attribute_name"] == "my.attribute"
+            for v in report.violations
+        )
+    """
+
+    def __init__(self, report: dict[str, Any]) -> None:
+        self._report = report
+
+    @functools.cached_property
+    def violations(self) -> list[dict[str, Any]]:
+        """Deduplicated list of semconv violations found in the report.
+
+        Each item is a dict with keys: ``id``, ``message``, ``context``
+        (the raw context dict from weaver, e.g. ``{"attribute_name": "foo"}``),
+        ``signal_name``, ``signal_type``, ``count``.
+        """
+        return _extract_violations(self._report)
+
+    def __getitem__(self, key: str) -> Any:
+        return self._report[key]
+
+    def get(self, key: str, default: Any = None) -> Any:
+        return self._report.get(key, default)
+
+    def __contains__(self, key: object) -> bool:
+        return key in self._report
+
+    def __repr__(self) -> str:
+        num_violations = len(self.violations)
+        return f"LiveCheckReport({num_violations} violation{'s' if 
num_violations != 1 else ''})"
+
+
+# NOTE: WeaverLiveCheck is experimental and its API is subject to change.
+class WeaverLiveCheck:
+    """Runs ``weaver registry live-check`` as a subprocess and validates
+    OTLP telemetry against OpenTelemetry semantic conventions.
+
+    .. note::
+        This class is experimental and its API is subject to change without 
notice.
+
+
+    Requires the ``weaver`` binary on PATH:
+    https://github.com/open-telemetry/weaver/releases
+
+    Typical use as a context manager::
+
+        def test_my_telemetry(self):
+            with WeaverLiveCheck() as weaver:
+                exporter = OTLPSpanExporter(
+                    endpoint=weaver.otlp_endpoint, insecure=True
+                )
+                # ... configure provider, emit telemetry ...
+                provider.force_flush()
+
+                # Signals weaver to stop, raises LiveCheckError listing 
violations
+                # if any, or returns a LiveCheckReport on success.
+                report = weaver.end_and_check()
+            # __exit__ calls close(), which is idempotent if end_and_check() 
was already called
+
+    Use :meth:`end` when you need the full :class:`LiveCheckReport`
+    regardless of whether violations were found — for example, to assert that
+    specific metrics were observed or to inspect violation fields directly::
+
+        with WeaverLiveCheck() as weaver:
+            # ... configure provider, emit telemetry ...
+            provider.force_flush()
+            report = weaver.end()
+
+        seen_metrics = report["statistics"]["seen_registry_metrics"]
+        assert seen_metrics.get("http.server.request.duration") == 1
+
+    Lifecycle:
+        - :meth:`start` — launches weaver and waits for it to become ready.
+        - :attr:`otlp_endpoint` — gRPC OTLP endpoint to point exporters at.
+        - :meth:`end` — signals weaver to stop and always returns a
+          :class:`LiveCheckReport`.  Never raises for semconv violations; use
+          this when you want to write your own assertions.
+        - :meth:`end_and_check` — signals weaver to stop and raises
+          :class:`LiveCheckError` with a human-readable violation list and the
+          full report attached if weaver exits non-zero.  Returns a
+          :class:`LiveCheckReport` on success.
+        - :meth:`close` — stops weaver if not already stopped and terminates 
the
+          process.  Never raises for semconv violations.  Idempotent; safe to
+          call even if :meth:`end_and_check` or :meth:`end` was already called.
+    """
+
+    def __init__(
+        self,
+        registry: str | None = None,
+        schema_version: str | None = None,
+        policies_dir: str | None = None,
+        inactivity_timeout: int = 30,
+        otlp_port: int = 0,
+        admin_port: int = 0,
+        extra_args: Sequence[str] | None = None,
+    ):
+        """Build the ``weaver registry live-check`` command.
+
+        ``extra_args`` is appended verbatim to the weaver command after the
+        managed flags (``--registry``, ``--otlp-grpc-port``, etc.) and lets
+        callers pass additional weaver options — for example ``["--quiet"]``
+        or ``["--skip-policies"]`` — without subclassing.
+        """
+        weaver_bin = shutil.which("weaver")
+        if not weaver_bin:
+            raise RuntimeError(
+                "weaver binary not found on PATH. "
+                "Install it from 
https://github.com/open-telemetry/weaver/releases";
+            )
+
+        self._otlp_port = otlp_port or _find_free_port()
+        self._admin_port = admin_port or _find_free_port()
+        self._ready = False
+        self._stopped = False
+        self._process: subprocess.Popen[bytes] | None = None
+        self._stdout_path: str | None = None
+        self._stderr_path: str | None = None
+
+        command = [
+            weaver_bin,
+            "registry",
+            "live-check",
+            f"--inactivity-timeout={inactivity_timeout}",
+            f"--otlp-grpc-port={self._otlp_port}",
+            f"--admin-port={self._admin_port}",
+            "--output=http",
+            "--format=json",
+        ]
+
+        if policies_dir:
+            command += ["--advice-policies", os.path.abspath(policies_dir)]
+
+        if registry is None:
+            if schema_version is None:
+                schema_version = list(Schemas)[-1].value.rsplit("/", 1)[-1]
+            registry = 
f"https://github.com/open-telemetry/semantic-conventions/archive/refs/tags/v{schema_version}.tar.gz[model]";
+        elif os.path.isdir(registry):
+            registry = os.path.abspath(registry)
+
+        command += ["--registry", registry]
+
+        if extra_args:
+            command.extend(extra_args)
+
+        self._command = command
+        logger.debug("Weaver command: %s", command)
+
+    def __enter__(self) -> "WeaverLiveCheck":
+        return self.start()
+
+    def __exit__(self, exc_type: Any, *_: Any) -> None:
+        if exc_type is not None:
+            self._stopped = True
+        self.close()
+
+    def start(self) -> "WeaverLiveCheck":
+        logger.debug("Starting WeaverLiveCheck process...")
+        # Redirect weaver's stdout/stderr to tempfiles
+        stdout_fd, self._stdout_path = tempfile.mkstemp(
+            prefix="weaver-stdout-", suffix=".log"
+        )
+        stderr_fd, self._stderr_path = tempfile.mkstemp(
+            prefix="weaver-stderr-", suffix=".log"
+        )
+        try:
+            self._process = subprocess.Popen(  # pylint: 
disable=consider-using-with
+                self._command,
+                stdout=stdout_fd,
+                stderr=stderr_fd,
+            )
+        finally:
+            os.close(stdout_fd)
+            os.close(stderr_fd)
+        try:
+            self._wait_for_ready()
+            self._ready = True
+        except Exception as exc:  # pylint: disable=broad-except
+            logs = self._read_weaver_logs()
+            logger.error(
+                "WeaverLiveCheck did not start: %s, logs: %s", exc, logs
+            )
+            raise
+        return self
+
+    def _wait_for_ready(self) -> None:
+        retry = Retry(
+            total=10,
+            backoff_factor=1,
+            backoff_max=1,
+            # Any non-2xx response from /health means weaver isn't ready yet.
+            status_forcelist=list(range(300, 600)),
+            raise_on_status=True,
+            allowed_methods=["GET"],
+        )
+        session = Session()
+        session.mount("http://";, HTTPAdapter(max_retries=retry))
+        try:
+            session.get(
+                f"http://localhost:{self._admin_port}/health";, timeout=5
+            )
+        except Exception as exc:  # pylint: disable=broad-except
+            if self._process is not None and self._process.poll() is not None:
+                raise RuntimeError(
+                    f"WeaverLiveCheck process exited unexpectedly (code 
{self._process.returncode})"
+                ) from exc
+            raise TimeoutError(
+                "WeaverLiveCheck did not become ready in time"
+            ) from exc
+
+    @property
+    def otlp_endpoint(self) -> str:
+        return f"http://localhost:{self._otlp_port}";
+
+    def _do_stop(self, timeout: int) -> tuple["LiveCheckReport", int]:
+        """POST /stop, wait for the process to exit, return (report, 
exit_code).
+
+        Raises for infrastructure errors (HTTP failure, process communication).
+        Never raises for semconv violations.
+        """
+        if not self._ready:
+            raise RuntimeError(
+                "WeaverLiveCheck process did not start successfully"
+            )
+        try:
+            response = post(
+                f"http://localhost:{self._admin_port}/stop";, timeout=5
+            )
+            response.raise_for_status()
+            report = LiveCheckReport(response.json())
+            assert self._process is not None
+            exit_code = self._process.wait(timeout=timeout)
+        except Exception as exc:  # pylint: disable=broad-except
+            logs = self._read_weaver_logs()
+            logger.error(
+                "Error communicating with weaver: %s, logs: %s", exc, logs
+            )
+            raise
+        return report, exit_code
+
+    def end(self, timeout: int = 30) -> "LiveCheckReport":
+        """Signal weaver to stop and return the full :class:`LiveCheckReport`.
+
+        Never raises for semconv violations — use this when you want to write
+        your own assertions against :attr:`LiveCheckReport.violations` or the
+        raw report data.
+
+        Raises :exc:`RuntimeError` for infrastructure problems (weaver failed
+        to start, HTTP communication error, etc.).
+
+        See 
https://github.com/open-telemetry/weaver/tree/main/crates/weaver_live_check#output
+        for the report structure.
+        """
+        if self._stopped:
+            logger.warning(
+                "end() called after weaver already stopped; returning empty 
report"
+            )
+            return LiveCheckReport({})
+        self._stopped = True
+        report, _ = self._do_stop(timeout)
+        return report
+
+    def end_and_check(self, timeout: int = 30) -> "LiveCheckReport":
+        """Signal weaver to stop and assert no semconv violations were found.
+
+        Returns the :class:`LiveCheckReport` when weaver exits successfully
+        (exit code 0).
+
+        Does **not** return if weaver exits with a non-zero status — raises
+        :exc:`LiveCheckError` (a subclass of :exc:`AssertionError`) with a
+        human-readable list of violations and the full :class:`LiveCheckReport`
+        attached as :attr:`LiveCheckError.report`.
+        Use :meth:`end` if you need the report regardless of violations.
+
+        Raises :exc:`RuntimeError` for infrastructure problems (weaver failed
+        to start, HTTP communication error, etc.).
+        """
+        if self._stopped:
+            logger.warning(
+                "end_and_check() called after weaver already stopped; 
returning empty report"
+            )
+            return LiveCheckReport({})
+        self._stopped = True
+        report, exit_code = self._do_stop(timeout)
+        if exit_code == 0:
+            # Success — no violations found, no errors communicating with 
weaver
+            return report
+        raise LiveCheckError(
+            f"Semconv violations 
found:\n{_format_violations(report.violations)}",
+            report,
+        )
+
+    def _read_weaver_logs(self) -> str | None:
+        if self._process is None:
+            return None
+        try:
+            if self._process.poll() is None:
+                self._process.kill()
+            try:
+                self._process.wait(timeout=5)
+            except subprocess.TimeoutExpired:
+                pass
+
+            def _read(path: str | None) -> str:
+                if path is None or not os.path.exists(path):
+                    return ""
+                with open(path, "rb") as fp:
+                    return fp.read().decode(errors="replace")
+
+            return f"{_read(self._stdout_path)}\n{_read(self._stderr_path)}"
+        except Exception as exc:  # pylint: disable=broad-except
+            logger.error("Could not get weaver logs: %s", exc)
+            return None
+
+    def close(self) -> None:
+        """Stop weaver and clean up the process.
+
+        If weaver has not been stopped yet, sends the ``/stop`` signal and
+        waits for the process to exit.  Never raises for semconv violations.
+        Idempotent — safe to call multiple times or after :meth:`end` /
+        :meth:`end_and_check` has already been called.
+        """
+        if not self._stopped:
+            self._stopped = True
+            if self._ready:
+                try:
+                    self._do_stop(timeout=30)
+                except Exception as exc:  # pylint: disable=broad-except
+                    logger.debug("Error stopping weaver during close: %s", exc)
+        if self._process and self._process.poll() is None:
+            self._process.terminate()
+            try:
+                self._process.wait(timeout=5)
+            except subprocess.TimeoutExpired:
+                self._process.kill()
+        for path in (self._stdout_path, self._stderr_path):
+            if path is not None:
+                try:
+                    os.unlink(path)
+                except OSError:
+                    pass
+        self._stdout_path = None
+        self._stderr_path = None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/wsgitestutil.py 
new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/wsgitestutil.py
--- old/opentelemetry_test_utils-0.62b1/src/opentelemetry/test/wsgitestutil.py  
2020-02-02 01:00:00.000000000 +0100
+++ new/opentelemetry_test_utils-0.63b1/src/opentelemetry/test/wsgitestutil.py  
2020-02-02 01:00:00.000000000 +0100
@@ -1,16 +1,5 @@
 # Copyright The OpenTelemetry Authors
-#
-# Licensed 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.
+# SPDX-License-Identifier: Apache-2.0
 
 import io
 import wsgiref.util as wsgiref_util
@@ -48,7 +37,8 @@
 
         trace_id = trace.format_trace_id(span.get_span_context().trace_id)
         span_id = trace.format_span_id(span.get_span_context().span_id)
+        trace_flags = span.get_span_context().trace_flags
         self.assertEqual(
-            f"00-{trace_id}-{span_id}-01",
+            f"00-{trace_id}-{span_id}-{trace_flags:02x}",
             headers["traceresponse"],
         )

Reply via email to