mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2026-10-29 01:26:08 +03:00
Add New Basic Tests
This commit is contained in:
19 files changed
+1710
-147
No files matched your search
@@ -1,4 +1,7 @@
|
||||
# flake8: noqa
|
||||
# mypy: ignore-errors
|
||||
"""Tests for journal_lock.py code."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import multiprocessing as mp
|
||||
@@ -25,22 +28,22 @@ def other_process_lock(continue_q: mp.Queue, exit_q: mp.Queue, lockfile: pathlib
|
||||
:param exit_q: When there's an item in this, exit.
|
||||
:param lockfile: Path where the lockfile should be.
|
||||
"""
|
||||
with open(lockfile / 'edmc-journal-lock.txt', mode='w+') as lf:
|
||||
print(f'sub-process: Opened {lockfile} for read...')
|
||||
with open(lockfile / "edmc-journal-lock.txt", mode="w+") as lf:
|
||||
print(f"sub-process: Opened {lockfile} for read...")
|
||||
# This needs to be kept in sync with journal_lock.py:_obtain_lock()
|
||||
if not _obtain_lock('sub-process', lf):
|
||||
print('sub-process: Failed to get lock, so returning')
|
||||
if not _obtain_lock("sub-process", lf):
|
||||
print("sub-process: Failed to get lock, so returning")
|
||||
return
|
||||
|
||||
print('sub-process: Got lock, telling main process to go...')
|
||||
continue_q.put('go', timeout=5)
|
||||
print("sub-process: Got lock, telling main process to go...")
|
||||
continue_q.put("go", timeout=5)
|
||||
# Wait for signal to exit
|
||||
print('sub-process: Waiting for exit signal...')
|
||||
print("sub-process: Waiting for exit signal...")
|
||||
exit_q.get(block=True, timeout=None)
|
||||
|
||||
# And clean up
|
||||
_release_lock('sub-process', lf)
|
||||
os.unlink(lockfile / 'edmc-journal-lock.txt')
|
||||
_release_lock("sub-process", lf)
|
||||
os.unlink(lockfile / "edmc-journal-lock.txt")
|
||||
|
||||
|
||||
def _obtain_lock(prefix: str, filehandle) -> bool:
|
||||
@@ -51,26 +54,27 @@ def _obtain_lock(prefix: str, filehandle) -> bool:
|
||||
:param filehandle: File handle already open on the lockfile.
|
||||
:return: bool - True if we obtained the lock.
|
||||
"""
|
||||
if sys.platform == 'win32':
|
||||
print(f'{prefix}: On win32')
|
||||
if sys.platform == "win32":
|
||||
print(f"{prefix}: On win32")
|
||||
import msvcrt
|
||||
|
||||
try:
|
||||
print(f'{prefix}: Trying msvcrt.locking() ...')
|
||||
print(f"{prefix}: Trying msvcrt.locking() ...")
|
||||
msvcrt.locking(filehandle.fileno(), msvcrt.LK_NBLCK, 4096)
|
||||
|
||||
except Exception as e:
|
||||
print(f'{prefix}: Unable to lock file: {e!r}')
|
||||
print(f"{prefix}: Unable to lock file: {e!r}")
|
||||
return False
|
||||
|
||||
else:
|
||||
import fcntl
|
||||
|
||||
print(f'{prefix}: Not win32, using fcntl')
|
||||
print(f"{prefix}: Not win32, using fcntl")
|
||||
try:
|
||||
fcntl.flock(filehandle, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
|
||||
except Exception as e:
|
||||
print(f'{prefix}: Unable to lock file: {e!r}')
|
||||
print(f"{prefix}: Unable to lock file: {e!r}")
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -84,30 +88,33 @@ def _release_lock(prefix: str, filehandle) -> bool:
|
||||
:param filehandle: File handle already open on the lockfile.
|
||||
:return: bool - True if we released the lock.
|
||||
"""
|
||||
if sys.platform == 'win32':
|
||||
print(f'{prefix}: On win32')
|
||||
if sys.platform == "win32":
|
||||
print(f"{prefix}: On win32")
|
||||
import msvcrt
|
||||
|
||||
try:
|
||||
print(f'{prefix}: Trying msvcrt.locking() ...')
|
||||
print(f"{prefix}: Trying msvcrt.locking() ...")
|
||||
filehandle.seek(0)
|
||||
msvcrt.locking(filehandle.fileno(), msvcrt.LK_UNLCK, 4096)
|
||||
|
||||
except Exception as e:
|
||||
print(f'{prefix}: Unable to unlock file: {e!r}')
|
||||
print(f"{prefix}: Unable to unlock file: {e!r}")
|
||||
return False
|
||||
|
||||
else:
|
||||
import fcntl
|
||||
|
||||
print(f'{prefix}: Not win32, using fcntl')
|
||||
print(f"{prefix}: Not win32, using fcntl")
|
||||
try:
|
||||
fcntl.flock(filehandle, fcntl.LOCK_UN)
|
||||
|
||||
except Exception as e:
|
||||
print(f'{prefix}: Unable to unlock file: {e!r}')
|
||||
print(f"{prefix}: Unable to unlock file: {e!r}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
###########################################################################
|
||||
|
||||
|
||||
@@ -116,16 +123,16 @@ class TestJournalLock:
|
||||
|
||||
@pytest.fixture
|
||||
def mock_journaldir(
|
||||
self, monkeypatch: MonkeyPatch,
|
||||
tmp_path_factory: TempdirFactory
|
||||
self, monkeypatch: MonkeyPatch, tmp_path_factory: TempdirFactory
|
||||
) -> Generator:
|
||||
"""Fixture for mocking config.get_str('journaldir')."""
|
||||
|
||||
def get_str(key: str, *, default: str | None = None) -> str:
|
||||
"""Mock config.*Config get_str to provide fake journaldir."""
|
||||
if key == 'journaldir':
|
||||
if key == "journaldir":
|
||||
return str(tmp_path_factory.getbasetemp())
|
||||
|
||||
print('Other key, calling up ...')
|
||||
print("Other key, calling up ...")
|
||||
return config.get_str(key) # Call the non-mocked
|
||||
|
||||
with monkeypatch.context() as m:
|
||||
@@ -134,17 +141,16 @@ class TestJournalLock:
|
||||
|
||||
@pytest.fixture
|
||||
def mock_journaldir_changing(
|
||||
self,
|
||||
monkeypatch: MonkeyPatch,
|
||||
tmp_path_factory: TempdirFactory
|
||||
self, monkeypatch: MonkeyPatch, tmp_path_factory: TempdirFactory
|
||||
) -> Generator:
|
||||
"""Fixture for mocking config.get_str('journaldir')."""
|
||||
|
||||
def get_str(key: str, *, default: str | None = None) -> str:
|
||||
"""Mock config.*Config get_str to provide fake journaldir."""
|
||||
if key == 'journaldir':
|
||||
if key == "journaldir":
|
||||
return tmp_path_factory.mktemp("changing") # type: ignore
|
||||
|
||||
print('Other key, calling up ...')
|
||||
print("Other key, calling up ...")
|
||||
return config.get_str(key) # Call the non-mocked
|
||||
|
||||
with monkeypatch.context() as m:
|
||||
@@ -155,7 +161,7 @@ class TestJournalLock:
|
||||
# Tests against JournalLock.__init__()
|
||||
def test_journal_lock_init(self, mock_journaldir: TempPathFactory):
|
||||
"""Test JournalLock instantiation."""
|
||||
print(f'{type(mock_journaldir)=}')
|
||||
print(f"{type(mock_journaldir)=}")
|
||||
tmpdir = str(mock_journaldir.getbasetemp())
|
||||
|
||||
jlock = JournalLock()
|
||||
@@ -215,32 +221,43 @@ class TestJournalLock:
|
||||
def test_obtain_lock_with_tmpdir_ro(self, mock_journaldir: TempPathFactory):
|
||||
"""Test JournalLock.obtain_lock() with read-only tmpdir."""
|
||||
tmpdir = str(mock_journaldir.getbasetemp())
|
||||
print(f'{tmpdir=}')
|
||||
print(f"{tmpdir=}")
|
||||
|
||||
# Make tmpdir read-only ?
|
||||
if sys.platform == 'win32':
|
||||
if sys.platform == "win32":
|
||||
# Ref: <https://stackoverflow.com/a/12168268>
|
||||
import ntsecuritycon as con
|
||||
import win32security
|
||||
|
||||
# Fetch user details
|
||||
winuser, domain, type = win32security.LookupAccountName("", os.environ.get('USERNAME'))
|
||||
winuser, domain, type = win32security.LookupAccountName(
|
||||
"", os.environ.get("USERNAME")
|
||||
)
|
||||
# Fetch the current security of tmpdir for that user.
|
||||
sd = win32security.GetFileSecurity(tmpdir, win32security.DACL_SECURITY_INFORMATION)
|
||||
dacl = sd.GetSecurityDescriptorDacl() # instead of dacl = win32security.ACL()
|
||||
sd = win32security.GetFileSecurity(
|
||||
tmpdir, win32security.DACL_SECURITY_INFORMATION
|
||||
)
|
||||
dacl = (
|
||||
sd.GetSecurityDescriptorDacl()
|
||||
) # instead of dacl = win32security.ACL()
|
||||
|
||||
# Add Write to Denied list
|
||||
# con.FILE_WRITE_DATA results in a 'Special permissions' being
|
||||
# listed on Properties > Security for the user in the 'Deny' column.
|
||||
# Clicking through to 'Advanced' shows a 'Deny' for
|
||||
# 'Create files / write data'.
|
||||
dacl.AddAccessDeniedAce(win32security.ACL_REVISION, con.FILE_WRITE_DATA, winuser)
|
||||
dacl.AddAccessDeniedAce(
|
||||
win32security.ACL_REVISION, con.FILE_WRITE_DATA, winuser
|
||||
)
|
||||
# Apply that change.
|
||||
sd.SetSecurityDescriptorDacl(1, dacl, 0) # may not be necessary
|
||||
win32security.SetFileSecurity(tmpdir, win32security.DACL_SECURITY_INFORMATION, sd)
|
||||
win32security.SetFileSecurity(
|
||||
tmpdir, win32security.DACL_SECURITY_INFORMATION, sd
|
||||
)
|
||||
|
||||
else:
|
||||
import stat
|
||||
|
||||
os.chmod(tmpdir, stat.S_IRUSR | stat.S_IXUSR)
|
||||
|
||||
jlock = JournalLock()
|
||||
@@ -249,7 +266,7 @@ class TestJournalLock:
|
||||
locked = jlock.obtain_lock()
|
||||
|
||||
# Revert permissions for test cleanup
|
||||
if sys.platform == 'win32':
|
||||
if sys.platform == "win32":
|
||||
# We can reuse winuser etc from before
|
||||
import pywintypes
|
||||
|
||||
@@ -258,12 +275,17 @@ class TestJournalLock:
|
||||
i = 0
|
||||
ace = dacl.GetAce(i)
|
||||
while ace:
|
||||
if ace[0] == (con.ACCESS_DENIED_ACE_TYPE, 0) and ace[1] == con.FILE_WRITE_DATA:
|
||||
if (
|
||||
ace[0] == (con.ACCESS_DENIED_ACE_TYPE, 0)
|
||||
and ace[1] == con.FILE_WRITE_DATA
|
||||
):
|
||||
# Delete the Ace that we added
|
||||
dacl.DeleteAce(i)
|
||||
# Apply that change.
|
||||
sd.SetSecurityDescriptorDacl(1, dacl, 0) # may not be necessary
|
||||
win32security.SetFileSecurity(tmpdir, win32security.DACL_SECURITY_INFORMATION, sd)
|
||||
win32security.SetFileSecurity(
|
||||
tmpdir, win32security.DACL_SECURITY_INFORMATION, sd
|
||||
)
|
||||
break
|
||||
|
||||
i += 1
|
||||
@@ -283,16 +305,17 @@ class TestJournalLock:
|
||||
"""Test JournalLock.obtain_lock() with tmpdir."""
|
||||
continue_q: mp.Queue = mp.Queue()
|
||||
exit_q: mp.Queue = mp.Queue()
|
||||
locker = mp.Process(target=other_process_lock,
|
||||
args=(continue_q, exit_q, mock_journaldir.getbasetemp())
|
||||
)
|
||||
print('Starting sub-process other_process_lock()...')
|
||||
locker = mp.Process(
|
||||
target=other_process_lock,
|
||||
args=(continue_q, exit_q, mock_journaldir.getbasetemp()),
|
||||
)
|
||||
print("Starting sub-process other_process_lock()...")
|
||||
locker.start()
|
||||
# Wait for the sub-process to have locked
|
||||
print('Waiting for "go" signal from sub-process...')
|
||||
continue_q.get(block=True, timeout=5)
|
||||
|
||||
print('Attempt actual lock test...')
|
||||
print("Attempt actual lock test...")
|
||||
# Now attempt to lock with to-test code
|
||||
jlock = JournalLock()
|
||||
second_attempt = jlock.obtain_lock()
|
||||
@@ -303,11 +326,11 @@ class TestJournalLock:
|
||||
# on later tests.
|
||||
jlock.journal_dir_lockfile.close() # type: ignore
|
||||
|
||||
print('Telling sub-process to quit...')
|
||||
exit_q.put('quit')
|
||||
print('Waiting for sub-process...')
|
||||
print("Telling sub-process to quit...")
|
||||
exit_q.put("quit")
|
||||
print("Waiting for sub-process...")
|
||||
locker.join()
|
||||
print('Done.')
|
||||
print("Done.")
|
||||
|
||||
###########################################################################
|
||||
# Tests against JournalLock.release_lock()
|
||||
@@ -322,9 +345,11 @@ class TestJournalLock:
|
||||
assert jlock.release_lock()
|
||||
|
||||
# And finally check it actually IS unlocked.
|
||||
with open(mock_journaldir.getbasetemp() / 'edmc-journal-lock.txt', mode='w+') as lf:
|
||||
assert _obtain_lock('release-lock', lf)
|
||||
assert _release_lock('release-lock', lf)
|
||||
with open(
|
||||
mock_journaldir.getbasetemp() / "edmc-journal-lock.txt", mode="w+"
|
||||
) as lf:
|
||||
assert _obtain_lock("release-lock", lf)
|
||||
assert _release_lock("release-lock", lf)
|
||||
|
||||
# Cleanup, to avoid side-effect on other tests
|
||||
os.unlink(str(jlock.journal_dir_lockfile_name))
|
||||
@@ -342,9 +367,7 @@ class TestJournalLock:
|
||||
|
||||
###########################################################################
|
||||
# Tests against JournalLock.update_lock()
|
||||
def test_update_lock(
|
||||
self,
|
||||
mock_journaldir_changing: TempPathFactory):
|
||||
def test_update_lock(self, mock_journaldir_changing: TempPathFactory):
|
||||
"""
|
||||
Test JournalLock.update_lock().
|
||||
|
||||
|
||||
Reference in new issue
Block a user