Skip to content

Commit

Permalink
Add unit tests and rework API
Browse files Browse the repository at this point in the history
  • Loading branch information
Giuseppe Lumia committed Nov 15, 2021
1 parent e19ddcf commit d1e7729
Show file tree
Hide file tree
Showing 21 changed files with 489 additions and 29 deletions.
3 changes: 2 additions & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ max-line-length = 88
extend-exclude =
*node_modules,
*.serverless,
extend-ignore = E731
extend-ignore = E731, W503
# E731 do not assign a lambda expression, use a def
# W503 line break before binary operator
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
- Fix the mess you did in iteration 1 <--- OK!

## Wave 3 - Time for the boring work
- Add a test suite (100% coverage)
- Add a test suite (100% coverage) <--- OK!
- Add coverage report generation <--- OK!
- Packaging (poetry!) <--- OK!
- Add linters (flake8, black, isort, ~~bandit~~, ~~safety~~) <--- OK!
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
44 changes: 43 additions & 1 deletion poetry.lock

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

41 changes: 20 additions & 21 deletions pylaprof/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,16 @@ class FS(Storer):

def __init__(self, path=None):
"""
path (str)
Where to store the report. Defaults to `pylaprof-{date}.txt` if None.
path (func() -> str)
Function to use to get report's destination path. Defaults to a generator with
format `pylaprof-{date}.txt` if None.
"""
self.path = path
if path is None:
self.path = lambda: f"pylaprof-{datetime.now(timezone.utc).isoformat()}.txt"

def store(self, file):
path = self.path
if path is None:
now = datetime.now(timezone.utc).isoformat()
path = f"pylaprof-{now}.txt"
path = self.path()
with open(path, "w") as fp:
shutil.copyfileobj(file, fp)

Expand All @@ -57,38 +57,38 @@ def __init__(
self,
s3_opts=None,
bucket="pylaprof",
prefix="",
key=None,
put_object_opts=None,
):
"""
s3_opts (dict)
Additional options to provide to boto3's `resource` method.
bucket (string)
Bucket where to store the report file(s).
prefix (string)
String to prepend to report files.
key (func() -> str)
Function to use to get report's object key. Defaults to a generator with
format `{randstr}-{date}.txt` if None.
put_object_opts (dict)
Additional options to provide to bucket's `put_object` method.
Report files will be stored as `{prefix}{randstr}-{date}.txt`, with `randstr`
being a random string of 8 characters.
"""
if s3_opts is None:
s3_opts = {}
s3 = boto3.resource("s3", **s3_opts)

self.bucket = s3.Bucket(bucket)

self.prefix = prefix
self.key = key
if key is None:
self.key = (
lambda: f"{str(uuid.uuid4())[:8]}-{datetime.now(timezone.utc).isoformat()}.txt" # noqa
)

self.put_object_opts = {}
if put_object_opts is not None:
self.put_object_opts = put_object_opts

def store(self, file):
randstr = str(uuid.uuid4())[:8]
now = datetime.now(timezone.utc).isoformat()
key = f"{self.prefix}{randstr}-{now}.txt"
key = self.key()
self.bucket.put_object(Body=file.read(), Key=key, **self.put_object_opts)


Expand Down Expand Up @@ -151,7 +151,7 @@ def __init__(self, period=0.01, single=True, min_time=0, sampler=None, storer=No
single (bool)
Profile only the thread which created this instance.
min_time (int)
Store profiling data only if execution lasts more than this amount of time.
Store profiling data only if execution lasts more than this amount of seconds.
sampler (Sampler)
Sampler to use to process stack frames.
Defaults to an instance of `StackCollapse` if None.
Expand Down Expand Up @@ -183,7 +183,7 @@ def __init__(self, period=0.01, single=True, min_time=0, sampler=None, storer=No
sampler = StackCollapse()
self.sampler = sampler

self._can_run = False
self._can_run = False # Variable to control profiler's main loop.
self._stop_event = threading.Event()
self.daemon = True
self.clean_exit = False
Expand All @@ -198,9 +198,8 @@ def stop(self):
Request thread to stop.
Does not wait for actual termination (use join() method).
"""
if self.is_alive():
self._can_run = False
self._stop_event.set()
self._can_run = False
self._stop_event.set()

def __enter__(self):
self.start()
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pylaprof"
version = "0.3.0"
version = "0.3.1"
description = "A Python sampling profiler for AWS Lambda functions (and not only)."
authors = ["Giuseppe Lumia <[email protected]>"]
license = "MIT"
Expand All @@ -18,6 +18,7 @@ black = "^21.10b0"
isort = "^5.10.1"
pytest = "^6.2.5"
coverage = {version = "^6.1.2", extras = ["toml"]}
freezegun = "^1.1.0"

[tool.poetry.scripts]
pylaprof-merge = "scripts.merge:main"
Expand Down
23 changes: 23 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import os
import tempfile
from unittest.mock import Mock

import pytest


@pytest.fixture
def tmpcwd():
"""Run test in a temporary directory."""
cwd = os.getcwd()
with tempfile.TemporaryDirectory(prefix="pylaprof-test") as tmpdir:
os.chdir(tmpdir)
yield
os.chdir(cwd)


@pytest.fixture
def boto3_mock(monkeypatch):
"""Monkeypatch pylaprof's boto3 module."""
mock = Mock()
monkeypatch.setattr("pylaprof.boto3", mock)
return mock
5 changes: 5 additions & 0 deletions tests/dummy-report.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<module> (/home/gius/pylaprof/tests/launcher.py:33);__enter__ (/home/gius/pylaprof/tests/pylaprof/__init__.py:210);start (/home/gius/pylaprof/tests/pylaprof/__init__.py:198);start (/usr/lib/python3.9/threading.py:897);wait (/usr/lib/python3.9/threading.py:574);wait (/usr/lib/python3.9/threading.py:312) 1
<module> (/home/gius/pylaprof/tests/launcher.py:34);main (/home/gius/pylaprof/tests/launcher.py:15);handler (/home/gius/pylaprof/tests/handler.py:8);sleepy_task (/home/gius/pylaprof/tests/handler.py:16) 290
<module> (/home/gius/pylaprof/tests/launcher.py:34);main (/home/gius/pylaprof/tests/launcher.py:15);handler (/home/gius/pylaprof/tests/handler.py:9);cpu_intens_task (/home/gius/pylaprof/tests/handler.py:22) 129
<module> (/home/gius/pylaprof/tests/launcher.py:34);main (/home/gius/pylaprof/tests/launcher.py:15);handler (/home/gius/pylaprof/tests/handler.py:9);cpu_intens_task (/home/gius/pylaprof/tests/handler.py:21) 33
<module> (/home/gius/pylaprof/tests/launcher.py:34);main (/home/gius/pylaprof/tests/launcher.py:15);handler (/home/gius/pylaprof/tests/handler.py:10);io_task (/home/gius/pylaprof/tests/handler.py:26);get (/home/gius/.local/lib/python3.9/site-packages/requests/api.py:76);request (/home/gius/.local/lib/python3.9/site-packages/requests/api.py:61);request (/home/gius/.local/lib/python3.9/site-packages/requests/sessions.py:542);send (/home/gius/.local/lib/python3.9/site-packages/requests/sessions.py:655);send (/home/gius/.local/lib/python3.9/site-packages/requests/adapters.py:439);urlopen (/usr/lib/python3.9/site-packages/urllib3/connectionpool.py:699);_make_request (/usr/lib/python3.9/site-packages/urllib3/connectionpool.py:440);getresponse (/usr/lib/python3.9/http/client.py:1371);begin (/usr/lib/python3.9/http/client.py:319);_read_status (/usr/lib/python3.9/http/client.py:280);readinto (/usr/lib/python3.9/socket.py:704) 290
Loading

0 comments on commit d1e7729

Please sign in to comment.