-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #21 from timbernat/unit-test-setup
Unit test setup
- Loading branch information
Showing
48 changed files
with
321 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
'''Additional data shipped along with polymerist source code''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
'''For checking whether object are valid Python modules and packages, and if so for gathering info from within them''' | ||
|
||
from typing import Union | ||
from pathlib import Path | ||
|
||
from importlib.resources import ( | ||
Package, | ||
files as get_package_path | ||
) | ||
from importlib.resources._common import get_package, from_package, resolve | ||
|
||
|
||
def is_module(module : Package) -> bool: | ||
'''Determine whether a given Package-like (i.e. str or ModuleType) is a valid Python module | ||
This will return True for packages, bottom-level modules (i.e. *.py) and Python scripts''' | ||
try: | ||
resolve(module) | ||
return True | ||
except ModuleNotFoundError: | ||
return False | ||
|
||
def is_package(package : Package) -> bool: | ||
'''Determine whether a given Package-like (i.e. str or ModuleType) is a valid Python package''' | ||
try: | ||
get_package(package) | ||
return True | ||
except (ModuleNotFoundError, TypeError): | ||
return False | ||
|
||
|
||
def get_resource_path_within_package(relative_path : Union[str, Path], package : Package) -> Path: | ||
'''Get the Path to a resource (i.e. either a directory or a file) which lives within a Python package''' | ||
package_path : Path = get_package_path(package) # will also implicitly check that the provided package exists as a module | ||
resource_path = package_path / relative_path # concat to Path here means string inputs for relative_path are valid without explicit conversion | ||
|
||
if not resource_path.exists(): # if this block is reached, it means "package" is a real module and resource path is DEFINED relative to package's path, so the below message is valid | ||
raise ValueError(f'{resolve(package).__name__} contains no resource "{relative_path}"') | ||
|
||
return resource_path | ||
|
||
def get_dir_path_within_package(relative_path : Union[str, Path], package : Package) -> Path: | ||
'''Get the Path to a directory which lives within a Python package''' | ||
dir_path : Path = get_resource_path_within_package(package=package, relative_path=relative_path) # performs all check associated with getting the resource | ||
|
||
if not dir_path.is_dir(): | ||
raise NotADirectoryError(f'{resolve(package).__name__} contains "{dir_path}", but it is not a directory') | ||
|
||
return dir_path | ||
|
||
def get_file_path_within_package(relative_path : Union[str, Path], package : Package) -> Path: | ||
'''Get the Path to a (non-directory) file which lives within a Python package''' | ||
file_path : Path = get_resource_path_within_package(package=package, relative_path=relative_path) # performs all check associated with getting the resource | ||
|
||
if not file_path.is_file(): | ||
raise FileNotFoundError(f'{resolve(package).__name__} contains no file "{file_path}"') | ||
|
||
return file_path |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
'''Unit tests for `analysis` package''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
'''Reference data used to load or verify unit tests''' |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
I contain pointless sample text for debugging purposes! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
'''Unit tests for `genutils` package''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
'''Unit tests for `decorators` package''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
'''Unit tests for `fileutils` package''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
'''Unit tests for `jsonio` package''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
'''Unit tests for `logutils` package''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
'''Unit tests for `sequences` package''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
'''Unit tests for `discernment` package''' |
97 changes: 97 additions & 0 deletions
97
polymerist/tests/genutils/sequences/discernment/test_discernment.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
'''Unit tests for DISCERNMENT-related functionality''' | ||
|
||
import pytest | ||
from polymerist.genutils.pkginspect import get_file_path_within_package | ||
from polymerist.tests import data as testdata | ||
|
||
import json | ||
from pathlib import Path | ||
|
||
from polymerist.genutils.sequences.discernment.inventory import SymbolInventory | ||
from polymerist.genutils.sequences.discernment.strategies import DISCERNMENTStrategy | ||
|
||
|
||
# DEFINE/LOAD HARD-CODED INPUTS AND EXPECTED OUTPUTS TO A PARTICULAR DISCERNMENT PROBLEM | ||
@pytest.fixture(scope='module') | ||
def word() -> str: | ||
return 'accg' | ||
|
||
@pytest.fixture(scope='module') | ||
def choice_bins() -> str: | ||
return ('bbc','aced','bad','daea','fccce','g','abcd','fggegc') | ||
|
||
@pytest.fixture(scope='module') | ||
def symbol_inventory(choice_bins) -> SymbolInventory: | ||
return SymbolInventory.from_bins(choice_bins) | ||
|
||
@pytest.fixture(scope='module') | ||
def solution_path() -> Path: | ||
return get_file_path_within_package('correct_discernment_solution.json', testdata) | ||
|
||
@pytest.fixture(scope='module') | ||
def correct_solution(solution_path) -> set[tuple[int, ...]]: | ||
with solution_path.open('r') as file: | ||
solution = set( | ||
tuple(indices) | ||
for indices in json.load(file) | ||
) | ||
return solution | ||
|
||
|
||
@pytest.mark.parametrize("ignore_multiplicities,unique_bins,DSClass", [(False, False, DSClass) for DSClass in DISCERNMENTStrategy.__subclasses__()]) # TODO: produce solutions with unique bins AND ignored multiplicities to fully test | ||
class TestDISCERNMENTStrategies: | ||
all_results : dict[str, set[int]] = {} # cache solutions to avoid tedoius recalculations | ||
def test_preserves_symbol_inventory( | ||
self, | ||
word : str, | ||
symbol_inventory : SymbolInventory, | ||
correct_solution : set[tuple[int, ...]], | ||
ignore_multiplicities : bool, | ||
unique_bins : bool, | ||
DSClass : type[DISCERNMENTStrategy], | ||
) -> None: | ||
'''Check to ensure that all implementations of DISCERNMENT solution strageties yield the same outputs and don't modify a provided symbol inventory | ||
Raises failure-specific Exception if inconsistency is detected, terminates silently (no Exception, returns None) otherwise''' | ||
mod_sym_inv = symbol_inventory.deepcopy() # create a copy of the symbol inventory to ensure any errant modifications do not affect other tests | ||
|
||
method_name = DSClass.__name__ | ||
ds_strat = DSClass() | ||
proposed_solution = set( | ||
idxs | ||
for idxs in ds_strat.enumerate_choice_labels( | ||
word, | ||
mod_sym_inv, | ||
ignore_multiplicities=ignore_multiplicities, | ||
unique_bins=unique_bins | ||
) | ||
) | ||
self.all_results[method_name] = proposed_solution # cache for comparison in later tests | ||
assert (mod_sym_inv == symbol_inventory), f'Algorithm {method_name} does not produce to correct enumeration of bin labels' | ||
|
||
def test_solution_is_correct( | ||
self, | ||
word : str, | ||
symbol_inventory : SymbolInventory, | ||
correct_solution : set[tuple[int, ...]], | ||
ignore_multiplicities : bool, | ||
unique_bins : bool, | ||
DSClass : type[DISCERNMENTStrategy], | ||
) -> None: | ||
method_name = DSClass.__name__ | ||
assert (self.all_results[method_name] == correct_solution), f'Algorithm {method_name} does not return symbol inventory to original state after completion' | ||
|
||
def test_solution_strategies_are_consistent( | ||
self, | ||
word : str, | ||
symbol_inventory : SymbolInventory, | ||
correct_solution : set[tuple[int, ...]], | ||
ignore_multiplicities : bool, | ||
unique_bins : bool, | ||
DSClass : type[DISCERNMENTStrategy], | ||
) -> None: | ||
method_name = DSClass.__name__ | ||
proposed_solution = self.all_results[method_name] | ||
|
||
for other_method_name, other_solution in self.all_results.items(): # check against all other methods PRIOR TO INSERTION (minimal number of checks guaranteed to verify all pairwise checks) | ||
# check both symmetric differences to make sure no solution sequences are unique to either method | ||
assert (proposed_solution - other_solution == set()) and (other_solution - proposed_solution == set()), f'Algorithms {method_name} and {other_method_name} produce inconsistent solutions' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
'''Unit tests for `similarity` package''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
'''Unit tests for package inspection utilities''' | ||
|
||
from types import ModuleType | ||
|
||
import pytest | ||
from pathlib import Path | ||
import math, json # use these as test cases, since they are pretty stable in stdlib | ||
|
||
from polymerist import polymerist # this is a dummy toplevel module, and NOt the entire polymerist package | ||
from polymerist import genutils | ||
from polymerist.genutils import pkginspect | ||
from polymerist import tests | ||
|
||
|
||
|
||
# TABULATED EXPECTED TESTS OUTPUTS | ||
non_module_types = [ # types that are obviously not modules OR packages, and which should fail | ||
bool, int, float, complex, tuple, list, dict, set, # str, Path # str and Path need to be tested separately | ||
] | ||
|
||
are_modules = [ | ||
('--not_a_module--', False), # deliberately weird to ensure this never accidentally clashes with a legit module name | ||
(math, True), | ||
('math', True), # test that the string -> module resolver also works as intended | ||
(json, True), | ||
('json', True), | ||
(json.decoder, True), | ||
('json.decoder', True), | ||
(polymerist, True), | ||
('polymerist.polymerist', True), | ||
(genutils, True), | ||
('polymerist.genutils', True), | ||
] | ||
|
||
are_packages = [ | ||
('--not_a_package--', False), # deliberately weird to ensure this never accidentally clashes with a legit module name | ||
(math, False), | ||
('math', False), # test that the string -> module resolver also works as intended | ||
(json, True), | ||
('json', True), | ||
(json.decoder, False), | ||
('json.decoder', False), | ||
(polymerist, False), | ||
('polymerist.polymerist', False), | ||
(genutils, True), | ||
('polymerist.genutils', True), | ||
] | ||
|
||
|
||
# MODULE AND PACKAGE PERCEPTION | ||
@pytest.mark.parametrize('module, expected_output', are_modules) | ||
def test_is_module(module : ModuleType, expected_output : bool) -> None: | ||
'''See if Python module perception behaves as expected''' | ||
assert pkginspect.is_module(module) == expected_output | ||
|
||
@pytest.mark.parametrize('non_module_type', non_module_types) | ||
def test_is_module_fail_on_invalid_types(non_module_type : type) -> None: | ||
'''check that module perception fails on invalid inputs''' | ||
with pytest.raises(AttributeError) as err_info: | ||
instance = non_module_type() # create a default instance | ||
_ = pkginspect.is_module(instance) | ||
|
||
@pytest.mark.parametrize('module, expected_output', are_packages) | ||
def test_is_package(module : ModuleType, expected_output : bool) -> None: | ||
'''See if Python package perception behaves as expected''' | ||
assert pkginspect.is_package(module) == expected_output | ||
|
||
@pytest.mark.parametrize('non_module_type', non_module_types) # NOTE: these args are in fact deliberately NOT renamed to ".*package" from ".*module" | ||
def test_is_module_fail_on_invalid_types(non_module_type : type) -> None: | ||
'''check that package perception fails on invalid inputs''' | ||
with pytest.raises(AttributeError) as err_info: | ||
instance = non_module_type() # create a default instance | ||
_ = pkginspect.is_package(instance) | ||
|
||
# FETCHING DATA FROM PACKAGES | ||
@pytest.mark.parametrize( | ||
'rel_path, module', | ||
[ | ||
('data', tests), | ||
('data/sample.dat', tests), | ||
pytest.param('daata/simple.dat', tests, marks=pytest.mark.xfail(raises=ValueError, reason="This isn't a real file", strict=True)), | ||
('pkginspect.py', genutils), | ||
pytest.param('fake/whatever.txt', pkginspect, marks=pytest.mark.xfail(raises=TypeError, reason="Module is not a package and therefore cannot contain resources", strict=True)), | ||
] | ||
) | ||
def test_get_resource_path(rel_path : str, module : ModuleType) -> None: | ||
'''Test fetching a resource (i.e. file OR dir) from a package''' | ||
resource_path = pkginspect.get_resource_path_within_package(rel_path, module) | ||
assert isinstance(resource_path, Path) | ||
|
||
@pytest.mark.parametrize( | ||
'rel_path, module', | ||
[ | ||
pytest.param('data', tests, marks=pytest.mark.xfail(raises=FileNotFoundError, reason="This is a directory, NOT a file", strict=True)), | ||
('data/sample.dat', tests), | ||
pytest.param('daata/simple.dat', tests, marks=pytest.mark.xfail(raises=ValueError, reason="This isn't a real file", strict=True)), | ||
('pkginspect.py', genutils), | ||
pytest.param('fake/whatever.txt', pkginspect, marks=pytest.mark.xfail(raises=TypeError, reason="Module is not a package and therefore cannot contain resources", strict=True)), | ||
] | ||
) | ||
def test_get_file_path(rel_path : str, module : ModuleType) -> None: | ||
'''Test fetching a file (i.e. NOT a dir) from a package''' | ||
resource_path = pkginspect.get_file_path_within_package(rel_path, module) | ||
assert isinstance(resource_path, Path) | ||
|
||
@pytest.mark.parametrize( | ||
'rel_path, module', | ||
[ | ||
('data', tests), | ||
pytest.param('data/sample.dat', tests, marks=pytest.mark.xfail(raises=NotADirectoryError, reason='This IS a real file, but not a directory', strict=True)), | ||
pytest.param('daata/simple.dat', tests, marks=pytest.mark.xfail(raises=ValueError, reason="This isn't a real file", strict=True)), | ||
pytest.param('pkginspect.py', genutils, marks=pytest.mark.xfail(raises=NotADirectoryError, reason='This IS a real file, but not a directory', strict=True)), | ||
pytest.param('fake/whatever.txt', pkginspect, marks=pytest.mark.xfail(raises=TypeError, reason="Module is not a package and therefore cannot contain resources", strict=True)), | ||
] | ||
) | ||
def test_get_dir_path(rel_path : str, module : ModuleType) -> None: | ||
'''Test fetching a dir (i.e. NOT a file) from a package''' | ||
resource_path = pkginspect.get_dir_path_within_package(rel_path, module) | ||
assert isinstance(resource_path, Path) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
'''Unit tests for `textual` package''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
'''Unit tests for `treetools` package''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
'''Unit tests for `typetools` package''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
'''Unit tests for `graphics` package''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
'''Unit tests for `maths` package''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
'''Unit tests for `combinatorics` package''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
'''Unit tests for `fractions` package''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
'''Unit tests for `greek` package''' |
Oops, something went wrong.