diff --git a/README.md b/README.md index 0769462..1668121 100644 --- a/README.md +++ b/README.md @@ -3,21 +3,21 @@ [](https://pepy.tech/project/locklib) [](https://pepy.tech/project/locklib) -[](https://coveralls.io/github/pomponchik/locklib?branch=main) -[](https://github.com/boyter/scc/) -[](https://hitsofcode.com/github/pomponchik/locklib/view?branch=main) -[](https://github.com/pomponchik/locklib/actions/workflows/tests_and_coverage.yml) +[](https://coveralls.io/github/mutating/locklib?branch=main) +[](https://github.com/boyter/scc/) +[](https://hitsofcode.com/github/mutating/locklib/view?branch=main) +[](https://github.com/mutating/locklib/actions/workflows/tests_and_coverage.yml) [](https://pypi.python.org/pypi/locklib) [](https://badge.fury.io/py/locklib) [](http://mypy-lang.org/) [](https://github.com/astral-sh/ruff) -[](https://deepwiki.com/pomponchik/locklib) +[](https://deepwiki.com/mutating/locklib)
- +
@@ -43,7 +43,7 @@ pip install locklib ... or directly from git: ```bash -pip install git+https://github.com/pomponchik/locklib.git +pip install git+https://github.com/mutating/locklib.git ``` You can also quickly try out this and other packages without having to install using [instld](https://github.com/pomponchik/instld). @@ -219,4 +219,6 @@ If the `notify` method was called with the same parameter only when the lock act How does it work? A modified [algorithm for determining the correct parenthesis sequence](https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B0%D0%B2%D0%B8%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D1%81%D0%BA%D0%BE%D0%B1%D0%BE%D1%87%D0%BD%D0%B0%D1%8F_%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C) is used here. For each thread for which any events were registered (taking the mutex, releasing the mutex, and also calling the `notify` method), the check takes place separately, that is, we determine that it was the same thread that held the mutex when `notify` was called, and not some other one. -> ⚠️ The thread id is used to identify the streams. This id may be reused if the current thread ends, which in some cases may lead to incorrect identification of lock coverage for operations that were not actually covered by the lock. Make sure that this cannot happen during your test. +> ⚠️ The thread id is used to identify the threads. This id may be reused if the current thread ends, which in some cases may lead to incorrect identification of lock coverage for operations that were not actually covered by the lock. Make sure that this cannot happen during your test. + +If no event with the specified identifier was recorded in any of the threads, the `ThereWasNoSuchEventError` exception will be raised by default. If you want to disable this so that the method simply returns `False` in such situations, pass the additional argument `raise_exception=False` to `was_event_locked`. diff --git a/locklib/__init__.py b/locklib/__init__.py index 767fef9..47b3b05 100644 --- a/locklib/__init__.py +++ b/locklib/__init__.py @@ -2,6 +2,9 @@ from locklib.errors import ( StrangeEventOrderError as StrangeEventOrderError, ) +from locklib.errors import ( + ThereWasNoSuchEventError as ThereWasNoSuchEventError, +) from locklib.locks.smart_lock.lock import SmartLock as SmartLock from locklib.locks.tracer.tracer import ( LockTraceWrapper as LockTraceWrapper, diff --git a/locklib/errors.py b/locklib/errors.py index 17e4b2f..90ffabb 100644 --- a/locklib/errors.py +++ b/locklib/errors.py @@ -1,5 +1,8 @@ class DeadLockError(Exception): - pass + ... class StrangeEventOrderError(Exception): - pass + ... + +class ThereWasNoSuchEventError(Exception): + ... diff --git a/locklib/locks/tracer/tracer.py b/locklib/locks/tracer/tracer.py index 27e120f..aec7fc9 100644 --- a/locklib/locks/tracer/tracer.py +++ b/locklib/locks/tracer/tracer.py @@ -3,7 +3,7 @@ from types import TracebackType from typing import Dict, List, Optional, Type -from locklib.errors import StrangeEventOrderError +from locklib.errors import StrangeEventOrderError, ThereWasNoSuchEventError from locklib.locks.tracer.events import TracerEvent, TracerEventType from locklib.protocols.lock import LockProtocol @@ -46,9 +46,11 @@ def notify(self, identifier: str) -> None: ), ) - def was_event_locked(self, identifier: str) -> bool: + def was_event_locked(self, identifier: str, raise_exception: bool = True) -> bool: stacks: Dict[int, List[TracerEvent]] = defaultdict(list) + there_was_action_with_this_identifier = False + for event in self.trace: stack = stacks[event.thread_id] @@ -57,11 +59,18 @@ def was_event_locked(self, identifier: str) -> bool: elif event.type == TracerEventType.RELEASE: if not stack: - raise StrangeEventOrderError('Release event without corresponding acquire event.') + if raise_exception: + raise StrangeEventOrderError('Release event without corresponding acquire event.') + return False stack.pop() elif event.type == TracerEventType.ACTION: - if event.identifier == identifier and not stack: - return False + if event.identifier == identifier: + there_was_action_with_this_identifier = True + if not stack: + return False + + if (not there_was_action_with_this_identifier) and raise_exception: + raise ThereWasNoSuchEventError(f'No events with identifier "{identifier}" occurred in any of the threads, so the question "was it thread-safe" is meaningless.') - return True + return there_was_action_with_this_identifier diff --git a/pyproject.toml b/pyproject.toml index a7944e9..df92a68 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = 'setuptools.build_meta' [project] name = 'locklib' -version = '0.0.19' +version = '0.0.20' authors = [ { name='Evgeniy Blinov', email='zheni-b@yandex.ru' }, ] diff --git a/tests/units/locks/tracer/test_tracer.py b/tests/units/locks/tracer/test_tracer.py index fcca42c..5a69c94 100644 --- a/tests/units/locks/tracer/test_tracer.py +++ b/tests/units/locks/tracer/test_tracer.py @@ -3,8 +3,9 @@ from typing import List, Union import pytest +from full_match import match -from locklib import LockTraceWrapper, StrangeEventOrderError +from locklib import LockTraceWrapper, StrangeEventOrderError, ThereWasNoSuchEventError from locklib.locks.tracer.events import TracerEvent, TracerEventType @@ -72,11 +73,22 @@ def release(self): with pytest.raises(StrangeEventOrderError): wrapper.was_event_locked('kek') + with pytest.raises(StrangeEventOrderError): + wrapper.was_event_locked('kek', raise_exception=True) + + assert not wrapper.was_event_locked('kek', raise_exception=False) + def test_event_is_locked_if_there_was_no_events(): wrapper = LockTraceWrapper(Lock()) - assert wrapper.was_event_locked('kek') + assert not wrapper.was_event_locked('kek', raise_exception=False) + + with pytest.raises(ThereWasNoSuchEventError, match=match('No events with identifier "kek" occurred in any of the threads, so the question "was it thread-safe" is meaningless.')): + wrapper.was_event_locked('kek') + + with pytest.raises(ThereWasNoSuchEventError, match=match('No events with identifier "kek" occurred in any of the threads, so the question "was it thread-safe" is meaningless.')): + wrapper.was_event_locked('kek', raise_exception=True) def test_event_is_locked_if_there_are_only_opening_and_slosing_events(): @@ -85,7 +97,13 @@ def test_event_is_locked_if_there_are_only_opening_and_slosing_events(): with wrapper: pass - assert wrapper.was_event_locked('kek') + assert not wrapper.was_event_locked('kek', raise_exception=False) + + with pytest.raises(ThereWasNoSuchEventError, match=match('No events with identifier "kek" occurred in any of the threads, so the question "was it thread-safe" is meaningless.')): + wrapper.was_event_locked('kek') + + with pytest.raises(ThereWasNoSuchEventError, match=match('No events with identifier "kek" occurred in any of the threads, so the question "was it thread-safe" is meaningless.')): + wrapper.was_event_locked('kek', raise_exception=True) def test_simple_case_of_locked_event(): @@ -208,4 +226,10 @@ def test_unknown_event_type(): wrapper.trace.append(TracerEvent('unknown', 1)) - assert wrapper.was_event_locked('lol') + assert not wrapper.was_event_locked('lol', raise_exception=False) + + with pytest.raises(ThereWasNoSuchEventError, match=match('No events with identifier "lol" occurred in any of the threads, so the question "was it thread-safe" is meaningless.')): + wrapper.was_event_locked('lol') + + with pytest.raises(ThereWasNoSuchEventError, match=match('No events with identifier "lol" occurred in any of the threads, so the question "was it thread-safe" is meaningless.')): + wrapper.was_event_locked('lol', raise_exception=True)