mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2026-08-24 10:05:29 -05:00
* Add (hopefully) non-controversial ruff rules * mypy-exclusions.txt * PIE * Fix pre-commit? * PYI * RSE * S + ignores * Whitespace fix * Re-add build-init-files.py * Add comment about security ignores * Update mypy-exclusions.txt Co-authored-by: Kyle Altendorf <sda@fstab.net> * Fix the executable status of build-init-files.py * Set executable bit for tools/run_block.py and readd shebang * Fix new errors --------- Co-authored-by: Kyle Altendorf <sda@fstab.net>
446 lines
14 KiB
Python
446 lines
14 KiB
Python
from __future__ import annotations
|
|
|
|
import contextlib
|
|
from collections.abc import AsyncIterator, Iterator
|
|
from typing import Optional, TypeVar
|
|
|
|
import aiohttp
|
|
import anyio
|
|
import pytest
|
|
from chia_rs.sized_ints import uint64
|
|
|
|
from chia._tests.util.misc import RecordingWebServer
|
|
from chia._tests.util.split_managers import SplitAsyncManager, SplitManager, split_async_manager, split_manager
|
|
from chia._tests.wallet.test_singleton_lifecycle_fast import satisfies_hint
|
|
from chia.cmds.cmds_util import format_bytes, format_minutes, validate_directory_writable
|
|
from chia.full_node.tx_processing_queue import ValuedEvent
|
|
from chia.types.blockchain_format.program import Program
|
|
from chia.util.batches import to_batches
|
|
from chia.util.errors import InvalidPathError
|
|
from chia.util.timing import adjusted_timeout, backoff_times
|
|
|
|
T = TypeVar("T")
|
|
|
|
|
|
class TestMisc:
|
|
@pytest.mark.anyio
|
|
async def test_format_bytes(self):
|
|
assert format_bytes(None) == "Invalid"
|
|
assert format_bytes(dict()) == "Invalid"
|
|
assert format_bytes("some bytes") == "Invalid"
|
|
assert format_bytes(-1024) == "Invalid"
|
|
assert format_bytes(0) == "0.000 MiB"
|
|
assert format_bytes(1024) == "0.001 MiB"
|
|
assert format_bytes(1024**2 - 1000) == "0.999 MiB"
|
|
assert format_bytes(1024**2) == "1.000 MiB"
|
|
assert format_bytes(1024**3) == "1.000 GiB"
|
|
assert format_bytes(1024**4) == "1.000 TiB"
|
|
assert format_bytes(1024**5) == "1.000 PiB"
|
|
assert format_bytes(1024**6) == "1.000 EiB"
|
|
assert format_bytes(1024**7) == "1.000 ZiB"
|
|
assert format_bytes(1024**8) == "1.000 YiB"
|
|
assert format_bytes(1024**9) == "1024.000 YiB"
|
|
assert format_bytes(1024**10) == "1048576.000 YiB"
|
|
assert format_bytes(1024**20).endswith("YiB")
|
|
|
|
@pytest.mark.anyio
|
|
async def test_format_minutes(self):
|
|
assert format_minutes(None) == "Invalid"
|
|
assert format_minutes(dict()) == "Invalid"
|
|
assert format_minutes("some minutes") == "Invalid"
|
|
assert format_minutes(-1) == "Unknown"
|
|
assert format_minutes(0) == "Now"
|
|
assert format_minutes(1) == "1 minute"
|
|
assert format_minutes(59) == "59 minutes"
|
|
assert format_minutes(60) == "1 hour"
|
|
assert format_minutes(61) == "1 hour and 1 minute"
|
|
assert format_minutes(119) == "1 hour and 59 minutes"
|
|
assert format_minutes(1380) == "23 hours"
|
|
assert format_minutes(1440) == "1 day"
|
|
assert format_minutes(2160) == "1 day and 12 hours"
|
|
assert format_minutes(8640) == "6 days"
|
|
assert format_minutes(10080) == "1 week"
|
|
assert format_minutes(20160) == "2 weeks"
|
|
assert format_minutes(40240) == "3 weeks and 6 days"
|
|
assert format_minutes(40340) == "4 weeks"
|
|
assert format_minutes(43800) == "1 month"
|
|
assert format_minutes(102000) == "2 months and 1 week"
|
|
assert format_minutes(481800) == "11 months"
|
|
assert format_minutes(525600) == "1 year"
|
|
assert format_minutes(1007400) == "1 year and 11 months"
|
|
assert format_minutes(5256000) == "10 years"
|
|
|
|
|
|
def test_validate_directory_writable(tmp_path) -> None:
|
|
write_test_path = tmp_path / ".write_test" # `.write_test` is used in validate_directory_writable
|
|
validate_directory_writable(tmp_path)
|
|
assert not write_test_path.exists()
|
|
|
|
subdir = tmp_path / "subdir"
|
|
with pytest.raises(InvalidPathError, match="Directory doesn't exist") as exc_info:
|
|
validate_directory_writable(subdir)
|
|
assert exc_info.value.path == subdir
|
|
assert not write_test_path.exists()
|
|
|
|
(tmp_path / ".write_test").mkdir()
|
|
with pytest.raises(InvalidPathError, match="Directory not writable") as exc_info:
|
|
validate_directory_writable(tmp_path)
|
|
assert exc_info.value.path == tmp_path
|
|
|
|
|
|
def test_empty_lists() -> None:
|
|
# An empty list should return an empty iterator and skip the loop's body.
|
|
empty: list[int] = []
|
|
with pytest.raises(StopIteration):
|
|
next(to_batches(empty, 1))
|
|
|
|
|
|
@pytest.mark.parametrize("collection_type", [list, set])
|
|
def test_valid(collection_type: type) -> None:
|
|
for k in range(1, 10):
|
|
test_collection = collection_type([x for x in range(k)])
|
|
for i in range(1, len(test_collection) + 1): # Test batch_size 1 to 11 (length + 1)
|
|
checked = 0
|
|
for batch in to_batches(test_collection, i):
|
|
assert batch.remaining == max(len(test_collection) - checked - i, 0)
|
|
assert len(batch.entries) <= i
|
|
entries = []
|
|
for j, entry in enumerate(test_collection):
|
|
if j < checked:
|
|
continue
|
|
if j >= min(checked + i, len(test_collection)):
|
|
break
|
|
entries.append(entry)
|
|
assert batch.entries == entries
|
|
checked += len(batch.entries)
|
|
assert checked == len(test_collection)
|
|
|
|
|
|
def test_invalid_batch_sizes() -> None:
|
|
with pytest.raises(ValueError):
|
|
next(to_batches([], 0))
|
|
|
|
with pytest.raises(ValueError):
|
|
next(to_batches([], -1))
|
|
|
|
|
|
def test_invalid_input_type() -> None:
|
|
with pytest.raises(ValueError, match="Unsupported type"):
|
|
next(to_batches(dict({1: 2}), 1))
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def sync_manager(y: list[str]) -> Iterator[None]:
|
|
y.append("entered")
|
|
yield
|
|
y.append("exited")
|
|
|
|
|
|
def test_split_manager_class_works() -> None:
|
|
x: list[str] = []
|
|
|
|
split = SplitManager(manager=sync_manager(y=x), object=None)
|
|
assert x == []
|
|
|
|
split.enter()
|
|
assert x == ["entered"]
|
|
|
|
split.exit()
|
|
assert x == ["entered", "exited"]
|
|
|
|
|
|
def test_split_manager_function_exits_if_needed() -> None:
|
|
x: list[str] = []
|
|
|
|
with split_manager(manager=sync_manager(y=x), object=None) as split:
|
|
assert x == []
|
|
|
|
split.enter()
|
|
assert x == ["entered"]
|
|
|
|
assert x == ["entered", "exited"]
|
|
|
|
|
|
def test_split_manager_function_skips_if_not_needed() -> None:
|
|
x: list[str] = []
|
|
|
|
with split_manager(manager=sync_manager(y=x), object=None) as split:
|
|
assert x == []
|
|
|
|
split.enter()
|
|
assert x == ["entered"]
|
|
|
|
split.exit()
|
|
assert x == ["entered", "exited"]
|
|
|
|
assert x == ["entered", "exited"]
|
|
|
|
|
|
def test_split_manager_raises_on_second_entry() -> None:
|
|
x: list[str] = []
|
|
|
|
split = SplitManager(manager=sync_manager(y=x), object=None)
|
|
split.enter()
|
|
|
|
with pytest.raises(Exception, match="^already entered$"):
|
|
split.enter()
|
|
|
|
|
|
def test_split_manager_raises_on_second_entry_after_exiting() -> None:
|
|
x: list[str] = []
|
|
|
|
split = SplitManager(manager=sync_manager(y=x), object=None)
|
|
split.enter()
|
|
split.exit()
|
|
|
|
with pytest.raises(Exception, match="^already entered, already exited$"):
|
|
split.enter()
|
|
|
|
|
|
def test_split_manager_raises_on_second_exit() -> None:
|
|
x: list[str] = []
|
|
|
|
split = SplitManager(manager=sync_manager(y=x), object=None)
|
|
split.enter()
|
|
split.exit()
|
|
|
|
with pytest.raises(Exception, match="^already exited$"):
|
|
split.exit()
|
|
|
|
|
|
def test_split_manager_raises_on_exit_without_entry() -> None:
|
|
x: list[str] = []
|
|
|
|
split = SplitManager(manager=sync_manager(y=x), object=None)
|
|
|
|
with pytest.raises(Exception, match="^not yet entered$"):
|
|
split.exit()
|
|
|
|
|
|
@contextlib.asynccontextmanager
|
|
async def async_manager(y: list[str]) -> AsyncIterator[None]:
|
|
y.append("entered")
|
|
yield
|
|
y.append("exited")
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_split_async_manager_class_works() -> None:
|
|
x: list[str] = []
|
|
|
|
split = SplitAsyncManager(manager=async_manager(y=x), object=None)
|
|
assert x == []
|
|
|
|
await split.enter()
|
|
assert x == ["entered"]
|
|
|
|
await split.exit()
|
|
assert x == ["entered", "exited"]
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_split_async_manager_function_exits_if_needed() -> None:
|
|
x: list[str] = []
|
|
|
|
async with split_async_manager(manager=async_manager(y=x), object=None) as split:
|
|
assert x == []
|
|
|
|
await split.enter()
|
|
assert x == ["entered"]
|
|
|
|
assert x == ["entered", "exited"]
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_split_async_manager_function_skips_if_not_needed() -> None:
|
|
x: list[str] = []
|
|
|
|
async with split_async_manager(manager=async_manager(y=x), object=None) as split:
|
|
assert x == []
|
|
|
|
await split.enter()
|
|
assert x == ["entered"]
|
|
|
|
await split.exit()
|
|
assert x == ["entered", "exited"]
|
|
|
|
assert x == ["entered", "exited"]
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_split_async_manager_raises_on_second_entry() -> None:
|
|
x: list[str] = []
|
|
|
|
split = SplitAsyncManager(manager=async_manager(y=x), object=None)
|
|
await split.enter()
|
|
|
|
with pytest.raises(Exception, match="^already entered$"):
|
|
await split.enter()
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_split_async_manager_raises_on_second_entry_after_exiting() -> None:
|
|
x: list[str] = []
|
|
|
|
split = SplitAsyncManager(manager=async_manager(y=x), object=None)
|
|
await split.enter()
|
|
await split.exit()
|
|
|
|
with pytest.raises(Exception, match="^already entered, already exited$"):
|
|
await split.enter()
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_split_async_manager_raises_on_second_exit() -> None:
|
|
x: list[str] = []
|
|
|
|
split = SplitAsyncManager(manager=async_manager(y=x), object=None)
|
|
await split.enter()
|
|
await split.exit()
|
|
|
|
with pytest.raises(Exception, match="^already exited$"):
|
|
await split.exit()
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_split_async_manager_raises_on_exit_without_entry() -> None:
|
|
x: list[str] = []
|
|
|
|
split = SplitAsyncManager(manager=async_manager(y=x), object=None)
|
|
|
|
with pytest.raises(Exception, match="^not yet entered$"):
|
|
await split.exit()
|
|
|
|
|
|
async def wait_for_valued_event_waiters(
|
|
event: ValuedEvent[T],
|
|
count: int,
|
|
timeout: float = 10,
|
|
) -> None:
|
|
with anyio.fail_after(delay=adjusted_timeout(timeout)):
|
|
for delay in backoff_times():
|
|
if len(event._event._waiters) >= count:
|
|
return
|
|
await anyio.sleep(delay)
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_valued_event_wait_already_set() -> None:
|
|
valued_event = ValuedEvent[int]()
|
|
value = 37
|
|
valued_event.set(value)
|
|
|
|
with anyio.fail_after(adjusted_timeout(10)):
|
|
result = await valued_event.wait()
|
|
|
|
assert result == value
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_valued_event_wait_not_yet_set() -> None:
|
|
valued_event = ValuedEvent[int]()
|
|
value = 37
|
|
result: Optional[int] = None
|
|
|
|
async def wait(valued_event: ValuedEvent[int]) -> None:
|
|
nonlocal result
|
|
result = await valued_event.wait()
|
|
|
|
with anyio.fail_after(adjusted_timeout(10)):
|
|
async with anyio.create_task_group() as task_group:
|
|
task_group.start_soon(wait, valued_event)
|
|
await wait_for_valued_event_waiters(event=valued_event, count=1)
|
|
valued_event.set(value)
|
|
|
|
assert result == value
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_valued_event_wait_blocks_when_not_set() -> None:
|
|
valued_event = ValuedEvent[int]()
|
|
with pytest.raises(TimeoutError):
|
|
# if we could just process until there are no pending events, that would be great
|
|
with anyio.fail_after(adjusted_timeout(1)):
|
|
await valued_event.wait()
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_valued_event_multiple_waits_all_get_values() -> None:
|
|
results: list[int] = []
|
|
valued_event = ValuedEvent[int]()
|
|
value = 37
|
|
task_count = 10
|
|
|
|
async def wait_and_append() -> None:
|
|
results.append(await valued_event.wait())
|
|
|
|
async with anyio.create_task_group() as task_group:
|
|
for i in range(task_count):
|
|
task_group.start_soon(wait_and_append, name=f"wait_and_append_{i}")
|
|
|
|
await wait_for_valued_event_waiters(event=valued_event, count=task_count)
|
|
valued_event.set(value)
|
|
|
|
assert results == [value] * task_count
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_valued_event_set_again_raises_and_does_not_change_value() -> None:
|
|
valued_event = ValuedEvent[int]()
|
|
value = 37
|
|
valued_event.set(value)
|
|
|
|
with pytest.raises(Exception, match="^Value already set$"):
|
|
valued_event.set(value + 1)
|
|
|
|
with anyio.fail_after(adjusted_timeout(10)):
|
|
result = await valued_event.wait()
|
|
|
|
assert result == value
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_valued_event_wait_raises_if_not_set() -> None:
|
|
valued_event = ValuedEvent[int]()
|
|
valued_event._event.set()
|
|
|
|
with pytest.raises(Exception, match="^Value not set despite event being set$"):
|
|
with anyio.fail_after(adjusted_timeout(10)):
|
|
await valued_event.wait()
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_recording_web_server_specified_response(
|
|
recording_web_server: RecordingWebServer,
|
|
) -> None:
|
|
expected_response = {"success": True, "magic": "asparagus"}
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.post(
|
|
url=recording_web_server.web_server.url(),
|
|
json={"response": expected_response},
|
|
) as response:
|
|
response.raise_for_status()
|
|
assert await response.json() == expected_response
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"obj, type_hint, expected_result",
|
|
[
|
|
(42, int, True),
|
|
(42, uint64, False),
|
|
(uint64(42), uint64, True),
|
|
("42", int, False),
|
|
([4, 2], list[int], True),
|
|
([4, "2"], list[int], False),
|
|
((4, 2), tuple[int, int], True),
|
|
((4, "2"), tuple[int, int], False),
|
|
((4, 2), tuple[int, ...], True),
|
|
((4, "2"), tuple[int, ...], False),
|
|
([(4, Program.to([2]))], list[tuple[int, Program]], True),
|
|
([(4, "2")], tuple[int, str], False),
|
|
],
|
|
)
|
|
def test_satisfies_hint(obj: T, type_hint: type[T], expected_result: bool) -> None:
|
|
assert satisfies_hint(obj, type_hint) == expected_result
|