Skip to content
This repository has been archived by the owner on Oct 8, 2024. It is now read-only.

Commit

Permalink
solution and test for tracer multi-import issue (#161)
Browse files Browse the repository at this point in the history
* solution and test for tracer multi-import issue

* vbump and fmt

* fixed test failure

* addressed pr comments

* new linter
  • Loading branch information
PietroPasotti authored Aug 16, 2024
1 parent e128a57 commit abd2ff6
Show file tree
Hide file tree
Showing 15 changed files with 98 additions and 13 deletions.
17 changes: 14 additions & 3 deletions lib/charms/tempo_k8s/v1/charm_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,6 @@ def _remove_stale_otel_sdk_packages():

_remove_stale_otel_sdk_packages()


import functools
import inspect
import logging
Expand Down Expand Up @@ -271,7 +270,7 @@ def _remove_stale_otel_sdk_packages():
# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version

LIBPATCH = 14
LIBPATCH = 15

PYDEPS = ["opentelemetry-exporter-otlp-proto-http==1.21.0"]

Expand All @@ -281,7 +280,6 @@ def _remove_stale_otel_sdk_packages():
# set this to 0 if you are debugging/developing this library source
dev_logger.setLevel(logging.CRITICAL)


_CharmType = Type[CharmBase] # the type CharmBase and any subclass thereof
_C = TypeVar("_C", bound=_CharmType)
_T = TypeVar("_T", bound=type)
Expand Down Expand Up @@ -333,9 +331,22 @@ def _get_tracer() -> Optional[Tracer]:
try:
return tracer.get()
except LookupError:
# fallback: this course-corrects for a user error where charm_tracing symbols are imported
# from different paths (typically charms.tempo_k8s... and lib.charms.tempo_k8s...)
try:
ctx: Context = copy_context()
if context_tracer := _get_tracer_from_context(ctx):
logger.warning(
"Tracer not found in `tracer` context var. "
"Verify that you're importing all `charm_tracing` symbols from the same module path. \n"
"For example, DO"
": `from charms.lib...charm_tracing import foo, bar`. \n"
"DONT: \n"
" \t - `from charms.lib...charm_tracing import foo` \n"
" \t - `from lib...charm_tracing import bar` \n"
"For more info: https://python-notes.curiousefficiency.org/en/latest/python"
"_concepts/import_traps.html#the-double-import-trap"
)
return context_tracer.get()
else:
return None
Expand Down
1 change: 1 addition & 0 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from ops.main import main
from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus, WaitingStatus
from ops.pebble import APIError

from tempo import Tempo

logger = logging.getLogger(__name__)
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/test_ingressed_tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
import requests
import yaml
from pytest_operator.plugin import OpsTest
from tempo import Tempo
from tenacity import retry, stop_after_attempt, wait_exponential

from tempo import Tempo
from tests.integration.helpers import get_relation_data

METADATA = yaml.safe_load(Path("./charmcraft.yaml").read_text())
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/test_tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
import requests
import yaml
from pytest_operator.plugin import OpsTest
from tempo import Tempo
from tenacity import retry, stop_after_attempt, wait_exponential

from tempo import Tempo
from tests.integration.helpers import get_relation_data

METADATA = yaml.safe_load(Path("./charmcraft.yaml").read_text())
Expand Down
3 changes: 2 additions & 1 deletion tests/interface/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
from unittest.mock import patch

import pytest
from charm import TempoCharm
from charms.tempo_k8s.v1.charm_tracing import charm_tracing_disabled
from interface_tester import InterfaceTester
from ops.pebble import Layer
from scenario.state import Container, State

from charm import TempoCharm


# Interface tests are centrally hosted at https://github.com/canonical/charm-relation-interfaces.
# this fixture is used by the test runner of charm-relation-interfaces to test tempo's compliance
Expand Down
3 changes: 2 additions & 1 deletion tests/scenario/conftest.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from unittest.mock import patch

import pytest
from charm import TempoCharm
from scenario import Context

from charm import TempoCharm


@pytest.fixture
def tempo_charm():
Expand Down
1 change: 1 addition & 0 deletions tests/scenario/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import scenario
import yaml

from tempo import Tempo


Expand Down
67 changes: 67 additions & 0 deletions tests/scenario/test_a_charm_tracer_multi_import_warning.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# WARNING ensure that this test module runs before any other scenario test file, else the
# imports from previous tests will pollute the sys.modules and cause this test to fail.
# I know this is horrible but yea, couldn't find a better way to fix the issue. Tried:
# - delete from sys.modules all modules containing 'charms.tempo_k8s'
# - delete from sys.modules all modules containing 'otlp_http'


from unittest.mock import patch

# this test file is intentionally quite broken, don't modify the imports
# import autoinstrument from charms.[...]
from charms.tempo_k8s.v1.charm_tracing import _autoinstrument as autoinstrument
from ops import CharmBase
from scenario import Context, State

# import trace from lib.charms.[...]
from lib.charms.tempo_k8s.v1.charm_tracing import trace


@trace
def _my_fn(foo):
return foo + 1


class MyCharmSimpleEvent(CharmBase):
META = {"name": "margherita"}

def __init__(self, fw):
super().__init__(fw)
fw.observe(self.on.start, self._on_start)

def _on_start(self, _):
_my_fn(2)

@property
def tempo(self):
return "foo.bar:80"


autoinstrument(MyCharmSimpleEvent, "tempo")


def test_charm_tracer_multi_import_warning(caplog, monkeypatch):
"""Check that we warn the user in case the test is importing tracing symbols from multiple paths.
See https://python-notes.curiousefficiency.org/en/latest/python_concepts/import_traps.html#the-double-import-trap
for a great explanation of what's going on.
"""
import opentelemetry

with patch(
"opentelemetry.exporter.otlp.proto.http.trace_exporter.OTLPSpanExporter.export"
) as f:
f.return_value = opentelemetry.sdk.trace.export.SpanExportResult.SUCCESS
ctx = Context(MyCharmSimpleEvent, meta=MyCharmSimpleEvent.META)
with caplog.at_level("WARNING"):
ctx.run("start", State())

spans = f.call_args_list[0].args[0]
assert [span.name for span in spans] == [
# we're only able to capture the _my_fn span because of the fallback behaviour
"function call: _my_fn",
"method call: MyCharmSimpleEvent._on_start",
"event: start",
"margherita/0: start event",
]
assert "Tracer not found in `tracer` context var." in caplog.records[0].message
2 changes: 1 addition & 1 deletion tests/scenario/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
from scenario import Container, Mount, Relation, State
from scenario.sequences import check_builtin_sequences
from scenario.state import Notice, _BoundNotice
from tempo import Tempo

from tempo import Tempo
from tests.scenario.helpers import get_tempo_config

TEMPO_CHARM_ROOT = Path(__file__).parent.parent.parent
Expand Down
4 changes: 1 addition & 3 deletions tests/scenario/test_charm_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import pytest
import scenario
from charms.tempo_k8s.v1.charm_tracing import CHARM_TRACING_ENABLED
from charms.tempo_k8s.v1.charm_tracing import CHARM_TRACING_ENABLED, get_current_span, trace
from charms.tempo_k8s.v1.charm_tracing import _autoinstrument as autoinstrument
from charms.tempo_k8s.v2.tracing import (
ProtocolType,
Expand All @@ -19,8 +19,6 @@
from scenario import Context, State
from scenario.runtime import UncaughtCharmError

from lib.charms.tempo_k8s.v1.charm_tracing import get_current_span, trace

os.environ[CHARM_TRACING_ENABLED] = "1"

logger = logging.getLogger(__name__)
Expand Down
1 change: 1 addition & 0 deletions tests/scenario/test_ingressed_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import yaml
from charms.tempo_k8s.v1.charm_tracing import charm_tracing_disabled
from scenario import Container, Relation, State

from tempo import Tempo


Expand Down
3 changes: 2 additions & 1 deletion tests/scenario/test_tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
from unittest.mock import patch

import pytest
from charm import Tempo
from charms.tempo_k8s.v1.charm_tracing import charm_tracing_disabled
from charms.tempo_k8s.v2.tracing import TracingProviderAppData, TracingRequirerAppData
from scenario import Container, Relation, State

from charm import Tempo


@pytest.fixture
def base_state():
Expand Down
1 change: 1 addition & 0 deletions tests/scenario/test_tracing_requirer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
)
from ops import CharmBase, Framework, RelationBrokenEvent, RelationChangedEvent
from scenario import Context, Relation, State

from tempo import Tempo


Expand Down
3 changes: 2 additions & 1 deletion tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
import unittest
from unittest.mock import patch

from charm import TempoCharm
from ops.model import ActiveStatus
from ops.testing import Harness

from charm import TempoCharm

CONTAINER_NAME = "tempo"


Expand Down
1 change: 1 addition & 0 deletions tests/unit/test_tempo.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from unittest.mock import patch

import pytest

from tempo import Tempo


Expand Down

0 comments on commit abd2ff6

Please sign in to comment.