Skip to content

Commit

Permalink
Merge pull request #19 from intergral/coverage
Browse files Browse the repository at this point in the history
chore(tests): add more tests and enforce test coverage
  • Loading branch information
Umaaz committed Jan 17, 2024
2 parents 945c18e + 404d520 commit 3091554
Show file tree
Hide file tree
Showing 49 changed files with 1,736 additions and 53 deletions.
10 changes: 4 additions & 6 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,14 @@ per-file-ignores =
# ignore unused imports in __init__ files
*/__init__.py: F401
# supress some docstring requirements in tests
test/__init__.py: D104
test/test_deep/__init__.py: D104
test/test_deep/*/__init__.py: D104,D107
test/test_deep/*/test_*.py: D101,D102,D107,D100,D105
test/test_deep/test_*.py: D101,D102,D107,D100,D105
test/test_deep/test_target.py: D102,D107,D103
tests/unit_tests/*.py: D
tests/unit_tests/**/*.py: D
# these files are from OTEL so should use OTEL license.
*/deep/api/types.py: NCF102
*/deep/api/resource/__init__.py: NCF102
*/deep/api/attributes/__init__.py: NCF102
tests/unit_tests/api/attributes/*.py: NCF102,D
tests/unit_tests/api/resource/*.py: NCF102,D

detailed-output = True
copyright-regex =
Expand Down
22 changes: 20 additions & 2 deletions .github/workflows/on_push.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,26 @@ jobs:
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r dev-requirements.txt
- name: Flake8
run: flake8
run: make lint

coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Python # Set Python version
uses: actions/setup-python@v4
with:
python-version: 3.12
# Install pip and pytest
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r dev-requirements.txt
- run: |
make coverage
tests:

Expand All @@ -46,7 +64,7 @@ jobs:
pip install -r dev-requirements.txt
pip install .
- name: Test with pytest
run: pytest test --doctest-modules --junitxml=junit/test-results-${{ matrix.python-version }}.xml
run: pytest tests/unit_tests --doctest-modules --junitxml=junit/test-results-${{ matrix.python-version }}.xml
- name: Upload pytest test results
uses: actions/upload-artifact@v3
with:
Expand Down
2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ endif

.PHONY: test
test:
pytest test
pytest tests/unit_tests

.PHONY: coverage
coverage:
pytest tests/unit_tests --cov=deep --cov-report term --cov-fail-under=77 --cov-report html --cov-branch

.PHONY: lint
lint:
Expand Down
7 changes: 5 additions & 2 deletions deep-python-client.iml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
<sourceFolder url="file://$MODULE_DIR$/deep-extractor/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/examples/simple-app/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/venv/lib/python3.10/site-packages/deep" />
</content>
<orderEntry type="jdk" jdkName="Python 3.10 (deep-python-client)" jdkType="Python SDK" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PackageRequirementsSettings">
<option name="requirementsPath" value="requirements.txt, dev-requirments.txt" />
</component>
</module>
4 changes: 4 additions & 0 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ flake8-docstrings
certifi>=2023.7.22 # not directly required, pinned by Snyk to avoid a vulnerability
flake8-header-validator>=0.0.3
setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability
pytest-cov
mockito
opentelemetry-api
opentelemetry-sdk
12 changes: 12 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,15 @@ path = "src/deep/version.py"
# read dependencies from reuirements.txt
[tool.setuptools.dynamic]
dependencies = {file = ["requirements.txt"]}

[tool.pytest.ini_options]
pythonpath = [
"./src",
"./tests"
]

[tool.coverage.report]
exclude_lines = [
"if TYPE_CHECKING:",
"@abc.abstractmethod"
]
2 changes: 1 addition & 1 deletion src/deep/api/attributes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def _clean_attribute_value(


def _clean_attribute(
key: str, value: types.AttributeValue, max_len: Optional[int]
key: str, value: Union[types.AttributeValue, Sequence[types.AttributeValue]], max_len: Optional[int]
) -> Optional[types.AttributeValue]:
"""
Check if attribute value is valid and cleans it if required.
Expand Down
1 change: 1 addition & 0 deletions src/deep/api/deep.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def shutdown(self):
if not self.started:
return
self.task_handler.flush()
self.poll.shutdown()
self.started = False

def register_tracepoint(self, path: str, line: int, args: Dict[str, str] = None,
Expand Down
12 changes: 7 additions & 5 deletions src/deep/api/plugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,24 +38,26 @@ def __plugin_generator(configured):
try:
module, cls = plugin.rsplit(".", 1)
yield getattr(import_module(module), cls)
logging.debug('Did import default integration %s', plugin)
except (DidNotEnable, SyntaxError) as e:
logging.debug('Did import integration %s', plugin)
except (DidNotEnable, Exception) as e:
logging.debug(
"Did not import default integration %s: %s", plugin, e
"Did not import integration %s: %s", plugin, e
)


def load_plugins() -> 'Tuple[list[Plugin], BoundedAttributes]':
def load_plugins(custom=None) -> 'Tuple[list[Plugin], BoundedAttributes]':
"""
Load all the deep plugins.
Attempt to load each plugin, if successful merge a attributes list of each plugin.
:return: the loaded plugins and attributes.
"""
if custom is None:
custom = []
bounded_attributes = BoundedAttributes(immutable=False)
loaded = []
for plugin in __plugin_generator(DEEP_PLUGINS):
for plugin in __plugin_generator(DEEP_PLUGINS + custom):
try:
plugin_instance = plugin()
if not plugin_instance.is_active():
Expand Down
4 changes: 2 additions & 2 deletions src/deep/api/plugin/otel.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ def __span_name(span):
@staticmethod
def __span_id(span):
# type: (_Span)-> Optional[str]
return (OTelPlugin.__format_span_id(span.context.__span_id)) if span else None
return (OTelPlugin.__format_span_id(span.context.span_id)) if span else None

@staticmethod
def __trace_id(span):
# type: (_Span)-> Optional[str]
return (OTelPlugin.__format_trace_id(span.context.__trace_id)) if span else None
return (OTelPlugin.__format_trace_id(span.context.trace_id)) if span else None

@staticmethod
def __get_span():
Expand Down
41 changes: 40 additions & 1 deletion src/deep/api/resource/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,14 @@ def create(
DeepResourceDetector().detect()
).merge(Resource(attributes, schema_url))
if not resource.attributes.get(SERVICE_NAME, None):
default_service_name = "unknown_service:python"
default_service_name = "unknown_service"
process_executable_name = resource.attributes.get(
PROCESS_EXECUTABLE_NAME, None
)
if process_executable_name:
default_service_name += ":" + process_executable_name
else:
default_service_name += ":python"
resource = resource.merge(
Resource({SERVICE_NAME: default_service_name}, schema_url)
)
Expand Down Expand Up @@ -294,3 +296,40 @@ def detect(self) -> "Resource":
if service_name:
env_resource_map[SERVICE_NAME] = service_name
return Resource(env_resource_map)


def get_aggregated_resources(
detectors: typing.List["ResourceDetector"],
initial_resource: typing.Optional[Resource] = None,
timeout=5,
) -> "Resource":
"""Retrieve resources from detectors in the order that they were passed.
:param detectors: List of resources in order of priority
:param initial_resource: Static resource. This has the highest priority
:param timeout: Number of seconds to wait for each detector to return
:return:
"""
detectors_merged_resource = initial_resource or Resource.create()
import concurrent.futures

with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
futures = [executor.submit(detector.detect) for detector in detectors]
for detector_ind, future in enumerate(futures):
detector = detectors[detector_ind]
try:
detected_resource = future.result(timeout=timeout)
# pylint: disable=broad-except
except Exception as ex:
detected_resource = _EMPTY_RESOURCE
if detector.raise_on_error:
raise ex
logging.warning(
"Exception %s in detector %s, ignoring", ex, detector
)
finally:
detectors_merged_resource = detectors_merged_resource.merge(
detected_resource
)

return detectors_merged_resource
2 changes: 1 addition & 1 deletion src/deep/api/tracepoint/eventsnapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def __init__(self, tracepoint, ts, resource, frames, var_lookup: Dict[str, 'Vari
"""
self._id = random.getrandbits(128)
self._tracepoint = tracepoint
self._var_lookup: dict[str, 'Variable'] = var_lookup
self._var_lookup: Dict[str, 'Variable'] = var_lookup
self._ts_nanos = ts
self._frames = frames
self._watches = []
Expand Down
8 changes: 5 additions & 3 deletions src/deep/config/config_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"""Service for handling deep config."""

import os
from typing import Any, List, Dict
from typing import Any, List

from deep import logging
from deep.api.plugin import Plugin
Expand All @@ -28,16 +28,18 @@
class ConfigService:
"""This is the main service that handles config for DEEP."""

def __init__(self, custom: Dict[str, any]):
def __init__(self, custom=None, tracepoints=TracepointConfigService()):
"""
Create a new config object.
:param custom: any custom values that are passed to DEEP
"""
if custom is None:
custom = {}
self._plugins = []
self.__custom = custom
self._resource = None
self._tracepoint_config = TracepointConfigService()
self._tracepoint_config = tracepoints
self._tracepoint_logger: 'TracepointLogger' = DefaultLogger()

def __getattribute__(self, name: str) -> Any:
Expand Down
3 changes: 2 additions & 1 deletion src/deep/config/tracepoint_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def __init__(self) -> None:
self._current_hash = None
self._last_update = 0
self._task_handler = None
self._listeners: list[ConfigUpdateListener] = []
self._listeners: List[ConfigUpdateListener] = []

def update_no_change(self, ts):
"""
Expand Down Expand Up @@ -149,6 +149,7 @@ def remove_custom(self, config: TracePointConfig):
for idx, cfg in enumerate(self._custom):
if cfg.id == config.id:
del self._custom[idx]
self.__trigger_update(None, None)
return


Expand Down
2 changes: 1 addition & 1 deletion src/deep/logging/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def error(msg, *args, **kwargs):
:param args: the args for the log
:param kwargs: the kwargs
"""
logging.getLogger("deep").debug(msg, *args, **kwargs)
logging.getLogger("deep").error(msg, *args, **kwargs)


def exception(msg, *args, exc_info=True, **kwargs):
Expand Down
8 changes: 6 additions & 2 deletions src/deep/poll/poll.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ def __init__(self, config: ConfigService, grpc: GRPCService):
def start(self):
"""Start the long poll service."""
logging.info("Starting Long Poll system")
if self.timer is not None:
self.timer.stop()
self.timer = RepeatedTimer("Tracepoint Long Poll", self.config.POLL_TIMER, self.poll)
self.__initial_poll()
self.timer.start()
Expand All @@ -72,3 +70,9 @@ def poll(self):
else:
self.config.tracepoints.update_new_config(response.ts_nanos, response.current_hash,
convert_response(response.response))

def shutdown(self):
"""Shutdown the timer."""
if self.timer:
self.timer.stop()
self.timer = None
2 changes: 1 addition & 1 deletion src/deep/push/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,4 @@ def convert_snapshot(snapshot: EventSnapshot) -> Snapshot:
log_msg=snapshot.log_msg)
except Exception:
logging.exception("Error converting to protobuf")
return Snapshot()
return None
3 changes: 3 additions & 0 deletions src/deep/push/push_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ def push_snapshot(self, snapshot: EventSnapshot):
def _push_task(self, snapshot):
from deep.push import convert_snapshot
converted = convert_snapshot(snapshot)
if converted is None:
return

logging.debug("Uploading snapshot: %s", snapshot_id_as_hex_str(snapshot.id))

stub = SnapshotServiceStub(self.grpc.channel)
Expand Down
4 changes: 2 additions & 2 deletions src/deep/task/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ def submit_task(self, task, *args) -> Future:
self._pending[next_id] = future

# cannot use 'del' in lambda: https://stackoverflow.com/a/41953232/5151254
def callback(future: Future):
if future.exception() is not None:
def callback(_future: Future):
if _future.exception() is not None:
logging.exception("Submitted task failed %s", task)
if next_id in self._pending:
del self._pending[next_id]
Expand Down
20 changes: 1 addition & 19 deletions src/deep/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,32 +35,14 @@ def time_ns():
return time.time_ns()


def reduce_list(key, update_value, default_value, lst):
"""Reduce a list to a dict.
key :: list_item -> dict_key
update_value :: key * existing_value -> updated_value
default_value :: initial value passed to update_value
lst :: The list
default_value comes before l. This is different from functools.reduce,
because functools.reduce's order is wrong.
"""
d = {}
for k in lst:
j = key(k)
d[j] = update_value(k, d.get(j, default_value))
return d


def str2bool(string):
"""
Convert a string to a boolean.
:param string: the string to convert
:return: True, if string is yes, true, t or 1. (case insensitive)
"""
return string.lower() in ("yes", "true", "t", "1")
return string.lower() in ("yes", "true", "t", "1", "y")


class RepeatedTimer:
Expand Down
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 3091554

Please sign in to comment.