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"],
)