Skip to content

Commit

Permalink
TST: Add test for less harder to reach places
Browse files Browse the repository at this point in the history
  • Loading branch information
bashtage committed Oct 1, 2024
1 parent 578219d commit e0896a7
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 5 deletions.
6 changes: 4 additions & 2 deletions randomgen/common.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ cdef class BitGenerator(_BitGenerator):
mode = mode.lower() if isinstance(mode, str) else mode
self.mode = "numpy" if (numpy_seed or mode == "numpy") else "sequence"
super().__init__(seed)
if type(self) is BitGenerator:
raise NotImplementedError('BitGenerator is a base class and cannot be instantized')

def _supported_modes(self):
return ("sequence",)
Expand Down Expand Up @@ -150,7 +152,7 @@ cdef class BitGenerator(_BitGenerator):
* next_uint64 - function pointer to produce 64 bit integers
* next_uint32 - function pointer to produce 32 bit integers
* next_double - function pointer to produce doubles
* bitgen - pointer to the bit generator struct
* bit_generator - pointer to the bit generator struct
"""
if self._ctypes is None:
self._ctypes = prepare_ctypes(&self._bitgen)
Expand All @@ -172,7 +174,7 @@ cdef class BitGenerator(_BitGenerator):
* next_uint64 - function pointer to produce 64 bit integers
* next_uint32 - function pointer to produce 32 bit integers
* next_double - function pointer to produce doubles
* bitgen - pointer to the bit generator struct
* bit_generator - pointer to the bit generator struct
"""
if self._cffi is not None:
return self._cffi
Expand Down
45 changes: 43 additions & 2 deletions randomgen/entropy.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,41 @@ cdef Py_ssize_t compute_numel(size):
n = size
return n

cdef class TestSentinel(object):
cdef bint _testing_auto, _testing_system, _testing_fallback
def __init__(self, object auto=False, system=False, fallback=False):
self._testing_auto = auto
self._testing_system = system
self._testing_fallback = fallback


@property
def testing_auto(self):
return self._testing_auto

@testing_auto.setter
def testing_auto(self, object value):
self._testing_auto = value

@property
def testing_system(self):
return self._testing_system

@testing_system.setter
def testing_system(self, object value):
self._testing_system = value

@property
def testing_fallback(self):
return self._testing_fallback

@testing_fallback.setter
def testing_fallback(self, object value):
self._testing_fallback = value


_test_sentinel = TestSentinel()


def seed_by_array(object seed, Py_ssize_t n):
"""
Expand Down Expand Up @@ -150,10 +185,16 @@ def random_entropy(size=None, source="system"):
else:
n = compute_numel(size)
randoms = np.zeros(n, dtype=np.uint32)
if source == "system":
if source in ("system", "auto"):
success = entropy_getbytes(<void *>(&randoms[0]), 4 * n)
else:
success = entropy_fallback_getbytes(<void *>(&randoms[0]), 4 * n)
if _test_sentinel.testing_auto and source == "auto":
success = False
if _test_sentinel.testing_system and source in ("auto", "system"):
success = False
if _test_sentinel.testing_fallback and source == "fallback":
success = False
if not success:
if source == "auto":
import warnings
Expand All @@ -162,7 +203,7 @@ def random_entropy(size=None, source="system"):
return random_entropy(size=size, source="fallback")
else:
raise RuntimeError("Unable to read from system cryptographic "
"provider")
"provider or use fallback")

if n == 0:
return random
Expand Down
8 changes: 8 additions & 0 deletions randomgen/tests/_shims.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from collections.abc import Sequence
from typing import Literal

import numpy as np
Expand All @@ -7,3 +8,10 @@ def int_to_array_shim(
value: int, name: str, bits: int, uint_size: Literal[32, 64]
) -> np.ndarray: ...
def byteswap_little_endian_shim(arr: np.ndarray) -> np.ndarray: ...
def object_to_int_shim(
val: int | np.ndarray | Sequence[int],
bits: int,
name: str,
default_bits: Literal[32, 64] = ...,
allowed_sizes: tuple[int] | tuple[int, int] = ...,
) -> int: ...
10 changes: 9 additions & 1 deletion randomgen/tests/_shims.pyx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
from randomgen.common cimport byteswap_little_endian, int_to_array, view_little_endian
from randomgen.common cimport (
byteswap_little_endian,
int_to_array,
object_to_int,
view_little_endian,
)

import numpy as np

Expand All @@ -13,3 +18,6 @@ def int_to_array_shim(value, name, bits, uint_size):

def byteswap_little_endian_shim(arr):
return byteswap_little_endian(arr)

def object_to_int_shim(val, bits, name, default_bits=64, allowed_sizes=(64,)):
return object_to_int(val, bits, name, default_bits, allowed_sizes)
42 changes: 42 additions & 0 deletions randomgen/tests/test_common.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import numpy as np
import pytest

from randomgen.common import BitGenerator, interface
from randomgen.entropy import seed_by_array
from randomgen.romu import Romu
from randomgen.tests._shims import (
byteswap_little_endian_shim,
int_to_array_shim,
object_to_int_shim,
view_little_endian_shim,
)

MISSING_CFFI = False
try:
import cffi # noqa: F401

Check notice

Code scanning / CodeQL

Unused import Note test

Import of 'cffi' is not used.
except ImportError:
MISSING_CFFI = True


def test_view_little_endian():
a = np.uint64([2**63])
Expand Down Expand Up @@ -79,3 +88,36 @@ def test_byteswap_little_endian():
result = byteswap_little_endian_shim(a).view(np.uint8)
expected = np.array([1, 2, 4, 8, 16, 32, 64, 128], dtype=np.uint8)
np.testing.assert_equal(result, expected)


def test_bitgenerator_error():
with pytest.raises(NotImplementedError, match="BitGenerator is a base class"):
BitGenerator(1)


@pytest.mark.skipif(MISSING_CFFI, reason="Requires CFFI")
def test_cffi():

tpl = Romu().cffi
assert isinstance(tpl, interface)
assert hasattr(tpl, "state_address")
assert hasattr(tpl, "state")
assert hasattr(tpl, "next_uint64")
assert hasattr(tpl, "next_uint32")
assert hasattr(tpl, "next_double")
assert hasattr(tpl, "bit_generator")


def test_object_to_int():
res = object_to_int_shim(1, 32, "test", allowed_sizes=(32, 64))
assert isinstance(res, int)
res = object_to_int_shim(1, 32, "test", default_bits=32, allowed_sizes=(32, 64))
assert isinstance(res, int)
res = object_to_int_shim(
np.array(1, dtype=np.uint64),
32,
"test",
default_bits=32,
allowed_sizes=(32, 64),
)
assert isinstance(res, int)
20 changes: 20 additions & 0 deletions randomgen/tests/test_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,26 @@ def test_fallback(self):
e2 = entropy.random_entropy(source="fallback")
assert_(e1 != e2)

def test_not_success(self):
from randomgen.entropy import _test_sentinel

_test_sentinel.testing_auto = True
with pytest.warns(RuntimeWarning):
e1 = entropy.random_entropy(source="auto")
assert isinstance(e1, int)
_test_sentinel.testing_auto = False
_test_sentinel.testing_fallback = True
time.sleep(0.1)
with pytest.raises(RuntimeError, match="Unable"):
entropy.random_entropy(1, source="fallback")
_test_sentinel.testing_fallback = False
e3 = entropy.random_entropy((2, 2), source="auto")
assert isinstance(e3, np.ndarray)

def test_invalid_source(self):
with pytest.raises(ValueError):
entropy.random_entropy(source="invalid")


class TestPCG32(TestPCG64):
@classmethod
Expand Down

0 comments on commit e0896a7

Please sign in to comment.