"""Tests for i2p_util core module — logging, hex, system, cache, collections, random, siphash.""" import struct import threading import pytest # === Log / LogManager === class TestLog: def test_level_constants(self): from i2p_util.log import Log assert Log.DEBUG < Log.INFO < Log.WARN < Log.ERROR < Log.CRIT def test_get_level(self): from i2p_util.log import Log assert Log.get_level("DEBUG") == Log.DEBUG assert Log.get_level("info") == Log.INFO assert Log.get_level("WARN") == Log.WARN assert Log.get_level("ERROR") == Log.ERROR assert Log.get_level("CRIT") == Log.CRIT assert Log.get_level("CRITICAL") == Log.CRIT # Unknown defaults to DEBUG assert Log.get_level("bogus") == Log.DEBUG def test_to_level_string(self): from i2p_util.log import Log assert Log.to_level_string(Log.DEBUG) == "DEBUG" assert Log.to_level_string(Log.INFO) == "INFO" assert Log.to_level_string(Log.WARN) == "WARN" assert Log.to_level_string(Log.ERROR) == "ERROR" assert Log.to_level_string(Log.CRIT) == "CRIT" assert Log.to_level_string(999) == "DEBUG" def test_should_log_respects_priority(self): from i2p_util.log import Log, LogManager mgr = LogManager() log = mgr.get_log("test.priority") log.set_minimum_priority(Log.WARN) assert not log.should_log(Log.DEBUG) assert not log.should_log(Log.INFO) assert log.should_log(Log.WARN) assert log.should_log(Log.ERROR) assert log.should_log(Log.CRIT) def test_should_debug_info_warn_error(self): from i2p_util.log import Log, LogManager mgr = LogManager() log = mgr.get_log("test.shorthand") log.set_minimum_priority(Log.ERROR) assert not log.should_debug() assert not log.should_info() assert not log.should_warn() assert log.should_error() def test_log_does_not_raise(self): from i2p_util.log import Log, LogManager mgr = LogManager() log = mgr.get_log("test.noexc") log.set_minimum_priority(Log.DEBUG) # Should not raise even with exception log.debug("msg") log.info("msg") log.warn("msg") log.error("msg") log.log(Log.CRIT, "critical msg") log.log(Log.ERROR, "with exc", ValueError("boom")) log.log_always(Log.DEBUG, "forced") def test_log_name(self): from i2p_util.log import LogManager mgr = LogManager() log = mgr.get_log("my.logger") assert log.name == "my.logger" def test_log_from_class(self): from i2p_util.log import LogManager mgr = LogManager() log = mgr.get_log(str) assert "str" in log.name class TestLogManager: def test_get_log_returns_same_instance(self): from i2p_util.log import LogManager mgr = LogManager() a = mgr.get_log("same") b = mgr.get_log("same") assert a is b def test_get_logs(self): from i2p_util.log import LogManager mgr = LogManager() mgr.get_log("a") mgr.get_log("b") assert len(mgr.get_logs()) >= 2 def test_set_default_limit(self): from i2p_util.log import Log, LogManager mgr = LogManager() log = mgr.get_log("default.test") mgr.set_default_limit(Log.ERROR) assert log.get_minimum_priority() == Log.ERROR assert mgr.get_default_limit() == Log.ERROR def test_shutdown_clears(self): from i2p_util.log import LogManager mgr = LogManager() mgr.get_log("x") mgr.shutdown() assert len(mgr.get_logs()) == 0 # === HexDump === class TestHexDump: def test_to_hex(self): from i2p_util.hex import HexDump assert HexDump.to_hex(b"\xde\xad\xbe\xef") == "deadbeef" def test_from_hex(self): from i2p_util.hex import HexDump assert HexDump.from_hex("deadbeef") == b"\xde\xad\xbe\xef" def test_roundtrip(self): from i2p_util.hex import HexDump data = b"Hello, I2P!" assert HexDump.from_hex(HexDump.to_hex(data)) == data def test_dump_format(self): from i2p_util.hex import HexDump data = b"ABCD" out = HexDump.dump(data) assert "00000000" in out assert "41 42 43 44" in out assert "|ABCD|" in out def test_dump_non_printable(self): from i2p_util.hex import HexDump data = bytes(range(32)) out = HexDump.dump(data) # Non-printable chars shown as dots assert "." in out def test_dump_offset_length(self): from i2p_util.hex import HexDump data = b"0123456789" out = HexDump.dump(data, offset=2, length=4) # Should show "2345" assert "32 33 34 35" in out def test_empty(self): from i2p_util.hex import HexDump assert HexDump.to_hex(b"") == "" assert HexDump.from_hex("") == b"" assert HexDump.dump(b"") == "" # === SystemVersion === class TestSystemVersion: def test_get_os_returns_string(self): from i2p_util.system import SystemVersion os_name = SystemVersion.get_os() assert isinstance(os_name, str) assert len(os_name) > 0 def test_get_arch_returns_string(self): from i2p_util.system import SystemVersion arch = SystemVersion.get_arch() assert arch in ("amd64", "arm64", "arm", "386", "unknown") def test_is_64bit(self): from i2p_util.system import SystemVersion import struct bits = struct.calcsize("P") * 8 assert SystemVersion.is_64bit() == (bits == 64) def test_get_cores_positive(self): from i2p_util.system import SystemVersion assert SystemVersion.get_cores() >= 1 def test_get_max_memory_positive(self): from i2p_util.system import SystemVersion assert SystemVersion.get_max_memory() > 0 def test_is_android_false(self): from i2p_util.system import SystemVersion assert SystemVersion.is_android() is False def test_python_version(self): from i2p_util.system import SystemVersion ver = SystemVersion.python_version() assert "." in ver def test_platform_booleans_consistent(self): from i2p_util.system import SystemVersion os_name = SystemVersion.get_os() if os_name == "linux": assert SystemVersion.is_linux() elif os_name == "mac": assert SystemVersion.is_mac() elif os_name == "windows": assert SystemVersion.is_windows() def test_is_arm(self): from i2p_util.system import SystemVersion result = SystemVersion.is_arm() assert isinstance(result, bool) def test_is_x86(self): from i2p_util.system import SystemVersion result = SystemVersion.is_x86() assert isinstance(result, bool) def test_is_slow(self): from i2p_util.system import SystemVersion result = SystemVersion.is_slow() assert isinstance(result, bool) def test_get_os_returns_known(self): from i2p_util.system import SystemVersion from unittest.mock import patch with patch("platform.system", return_value="Darwin"): assert SystemVersion.get_os() == "mac" with patch("platform.system", return_value="Windows"): assert SystemVersion.get_os() == "windows" with patch("platform.system", return_value="Linux"): assert SystemVersion.get_os() == "linux" with patch("platform.system", return_value="FreeBSD"): assert SystemVersion.get_os() == "freebsd" def test_get_arch_variants(self): from i2p_util.system import SystemVersion from unittest.mock import patch with patch("platform.machine", return_value="x86_64"): assert SystemVersion.get_arch() == "amd64" with patch("platform.machine", return_value="aarch64"): assert SystemVersion.get_arch() == "arm64" with patch("platform.machine", return_value="armv7l"): assert SystemVersion.get_arch() == "arm" with patch("platform.machine", return_value="i686"): assert SystemVersion.get_arch() == "386" with patch("platform.machine", return_value="mips"): assert SystemVersion.get_arch() == "unknown" def test_is_windows(self): from i2p_util.system import SystemVersion from unittest.mock import patch with patch("platform.system", return_value="Windows"): assert SystemVersion.is_windows() is True with patch("platform.system", return_value="Linux"): assert SystemVersion.is_windows() is False def test_is_mac(self): from i2p_util.system import SystemVersion from unittest.mock import patch with patch("platform.system", return_value="Darwin"): assert SystemVersion.is_mac() is True with patch("platform.system", return_value="Linux"): assert SystemVersion.is_mac() is False def test_get_max_memory_fallback(self): from i2p_util.system import SystemVersion from unittest.mock import patch with patch("resource.getrlimit", side_effect=ValueError("nope")): result = SystemVersion.get_max_memory() assert result == 2 * 1024 * 1024 * 1024 # === LHMCache === class TestLHMCache: def test_basic_set_get(self): from i2p_util.cache import LHMCache c = LHMCache(3) c["a"] = 1 assert c["a"] == 1 def test_eviction(self): from i2p_util.cache import LHMCache c = LHMCache(2) c["a"] = 1 c["b"] = 2 c["c"] = 3 # Should evict "a" assert "a" not in c assert c["b"] == 2 assert c["c"] == 3 def test_access_refreshes(self): from i2p_util.cache import LHMCache c = LHMCache(2) c["a"] = 1 c["b"] = 2 _ = c["a"] # Access "a" — moves it to end c["c"] = 3 # Should evict "b", not "a" assert "a" in c assert "b" not in c def test_update_refreshes(self): from i2p_util.cache import LHMCache c = LHMCache(2) c["a"] = 1 c["b"] = 2 c["a"] = 10 # Update "a" — moves it to end c["c"] = 3 # Should evict "b" assert c["a"] == 10 assert "b" not in c def test_get_default(self): from i2p_util.cache import LHMCache c = LHMCache(3) assert c.get("missing") is None assert c.get("missing", 42) == 42 # === SimpleByteCache === class TestSimpleByteCache: def test_acquire_returns_bytearray(self): from i2p_util.cache import SimpleByteCache sbc = SimpleByteCache(4, 16) buf = sbc.acquire() assert isinstance(buf, bytearray) assert len(buf) == 16 def test_release_and_reacquire(self): from i2p_util.cache import SimpleByteCache sbc = SimpleByteCache(4, 8) buf = sbc.acquire() buf[:] = b"\xff" * 8 sbc.release(buf) # Re-acquired buffer should be zeroed buf2 = sbc.acquire() assert len(buf2) == 8 def test_pool_limit(self): from i2p_util.cache import SimpleByteCache sbc = SimpleByteCache(2, 4) bufs = [sbc.acquire() for _ in range(5)] for b in bufs: sbc.release(b) # Pool should only hold 2 assert len(sbc._pool) <= 2 def test_get_instance_singleton(self): from i2p_util.cache import SimpleByteCache # Clear class state first a = SimpleByteCache.get_instance(64) b = SimpleByteCache.get_instance(64) assert a is b # === ByteCache === class TestByteCache: def test_acquire_release(self): from i2p_util.cache import ByteCache bc = ByteCache.get_instance(4, 32) buf = bc.acquire() assert len(buf) == 32 bc.release(buf) def test_clear_all(self): from i2p_util.cache import ByteCache bc = ByteCache.get_instance(4, 32) buf = bc.acquire() bc.release(buf) ByteCache.clear_all() # === OrderedProperties === class TestOrderedProperties: def test_sorted_keys(self): from i2p_util.collections import OrderedProperties p = OrderedProperties() p["c"] = "3" p["a"] = "1" p["b"] = "2" assert p.keys() == ["a", "b", "c"] def test_sorted_items(self): from i2p_util.collections import OrderedProperties p = OrderedProperties() p["z"] = "last" p["a"] = "first" items = p.items() assert items[0] == ("a", "first") assert items[1] == ("z", "last") def test_get_set_property(self): from i2p_util.collections import OrderedProperties p = OrderedProperties() p.set_property("key", "value") assert p.get_property("key") == "value" assert p.get_property("missing", "default") == "default" def test_values_sorted_by_key(self): from i2p_util.collections import OrderedProperties p = OrderedProperties() p["b"] = "B" p["a"] = "A" assert p.values() == ["A", "B"] # === RandomSource === class TestRandomSource: def test_singleton(self): from i2p_util.random import RandomSource a = RandomSource.get_instance() b = RandomSource.get_instance() assert a is b def test_next_int_range(self): from i2p_util.random import RandomSource rs = RandomSource.get_instance() for _ in range(100): v = rs.next_int(10) assert 0 <= v < 10 def test_next_int_bound_error(self): from i2p_util.random import RandomSource rs = RandomSource.get_instance() with pytest.raises(ValueError): rs.next_int(0) with pytest.raises(ValueError): rs.next_int(-1) def test_signed_next_int_range(self): from i2p_util.random import RandomSource rs = RandomSource.get_instance() v = rs.signed_next_int() assert -(2**31) <= v < 2**31 def test_next_long(self): from i2p_util.random import RandomSource rs = RandomSource.get_instance() for _ in range(50): v = rs.next_long(1000) assert 0 <= v < 1000 def test_next_bytes_length(self): from i2p_util.random import RandomSource rs = RandomSource.get_instance() b = rs.next_bytes(32) assert len(b) == 32 def test_next_bytes_into(self): from i2p_util.random import RandomSource rs = RandomSource.get_instance() buf = bytearray(16) rs.next_bytes_into(buf, 4, 8) # First 4 and last 4 should still be zero assert buf[:4] == b"\x00" * 4 assert buf[12:] == b"\x00" * 4 # Middle 8 bytes should be filled (overwhelmingly unlikely to be all zeros) def test_next_boolean(self): from i2p_util.random import RandomSource rs = RandomSource.get_instance() results = {rs.next_boolean() for _ in range(100)} assert True in results assert False in results # === SipHash === class TestSipHash: def test_digest_deterministic(self): from i2p_util.siphash import SipHash data = b"hello" a = SipHash.digest(data) b = SipHash.digest(data) assert a == b def test_digest_different_data(self): from i2p_util.siphash import SipHash a = SipHash.digest(b"hello") b = SipHash.digest(b"world") assert a != b def test_digest_is_64bit(self): from i2p_util.siphash import SipHash h = SipHash.digest(b"test data") assert 0 <= h < (1 << 64) def test_hash_code_is_32bit(self): from i2p_util.siphash import SipHash h = SipHash.hash_code(b"test data") assert 0 <= h < (1 << 32) def test_hash_code_none(self): from i2p_util.siphash import SipHash assert SipHash.hash_code(None) == 0 def test_digest_with_offset_length(self): from i2p_util.siphash import SipHash data = b"XXXhelloXXX" a = SipHash.digest(data, 3, 5) b = SipHash.digest(b"hello") assert a == b def test_empty_data(self): from i2p_util.siphash import SipHash h = SipHash.digest(b"") assert isinstance(h, int) def test_various_lengths(self): """Test all remainder cases (0-7 trailing bytes).""" from i2p_util.siphash import SipHash for length in range(17): data = bytes(range(length)) h = SipHash.digest(data) assert 0 <= h < (1 << 64) def test_known_siphash24_vectors(self): """Test against known SipHash-2-4 test vectors with k0=k1=0.""" from i2p_util.siphash import SipHash # With known keys, we can verify the algorithm is correct # Use internal method with k0=0, k1=0 result = SipHash._siphash24( 0x0706050403020100, 0x0f0e0d0c0b0a0908, bytes(range(15)), 0, 15, ) # Known test vector for SipHash-2-4 with these keys and this input assert result == 0xa129ca6149be45e5