-
Notifications
You must be signed in to change notification settings - Fork 42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JSON reporting #131
JSON reporting #131
Changes from 20 commits
17b3eab
fdb7ffd
cfbef56
7623741
3f25af0
ed4d58e
7f25cec
1f5284e
d094951
0c94174
befbd80
05c7802
9497ecd
b5234ad
f8e562c
3dca2ad
d62073a
1c42d3a
98db893
775d25b
f356ffd
a1ccd19
0841ef3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -127,3 +127,6 @@ dmypy.json | |
|
||
# Pyre type checker | ||
.pyre/ | ||
|
||
# pytest-json-report | ||
.report.json |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
""" | ||
This is a very basic test to see what names are defined in a library. It | ||
does not even require functioning hypothesis array_api support. | ||
""" | ||
|
||
import pytest | ||
|
||
from ._array_module import mod as xp, mod_name | ||
from .stubs import (array_attributes, array_methods, category_to_funcs, | ||
extension_to_funcs, EXTENSIONS) | ||
|
||
has_name_params = [] | ||
for ext, stubs in extension_to_funcs.items(): | ||
for stub in stubs: | ||
has_name_params.append(pytest.param(ext, stub.__name__)) | ||
for cat, stubs in category_to_funcs.items(): | ||
for stub in stubs: | ||
has_name_params.append(pytest.param(cat, stub.__name__)) | ||
for meth in array_methods: | ||
has_name_params.append(pytest.param('array_method', meth.__name__)) | ||
for attr in array_attributes: | ||
has_name_params.append(pytest.param('array_attribute', attr)) | ||
|
||
@pytest.mark.parametrize("category, name", has_name_params) | ||
def test_has_names(category, name): | ||
if category in EXTENSIONS: | ||
ext_mod = getattr(xp, category) | ||
assert hasattr(ext_mod, name), f"{mod_name} is missing the {category} extension function {name}()" | ||
elif category.startswith('array_'): | ||
# TODO: This would fail if ones() is missing. | ||
arr = xp.ones((1, 1)) | ||
if category == 'array_attribute': | ||
assert hasattr(arr, name), f"The {mod_name} array object is missing the attribute {name}" | ||
else: | ||
assert hasattr(arr, name), f"The {mod_name} array object is missing the method {name}()" | ||
else: | ||
assert hasattr(xp, name), f"{mod_name} is missing the {category} function {name}()" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,8 +7,9 @@ | |
from array_api_tests import _array_module as xp | ||
from array_api_tests._array_module import _UndefinedStub | ||
|
||
settings.register_profile("xp_default", deadline=800) | ||
from reporting import pytest_metadata, add_extra_json_metadata # noqa | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, the names have to be defined here so that pytest will see them as fixtures. I decided to keep the reporting in a separate file to keep things clean, since it's already a good bit of code and could potentially be more if we decide to add more stuff to the report in the future. |
||
|
||
settings.register_profile("xp_default", deadline=800) | ||
|
||
def pytest_addoption(parser): | ||
# Hypothesis max examples | ||
|
@@ -120,7 +121,7 @@ def pytest_collection_modifyitems(config, items): | |
mark.skip(reason="disabled via --disable-data-dependent-shapes") | ||
) | ||
break | ||
# skip if test not appropiate for CI | ||
# skip if test not appropriate for CI | ||
if ci: | ||
ci_mark = next((m for m in markers if m.name == "ci"), None) | ||
if ci_mark is None: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
from array_api_tests.dtype_helpers import dtype_to_name | ||
from array_api_tests import _array_module as xp | ||
from array_api_tests import __version__ | ||
|
||
from types import BuiltinFunctionType, FunctionType | ||
import dataclasses | ||
import json | ||
import warnings | ||
|
||
from hypothesis.strategies import SearchStrategy | ||
|
||
from pytest import mark, fixture | ||
try: | ||
import pytest_jsonreport # noqa | ||
except ImportError: | ||
raise ImportError("pytest-json-report is required to run the array API tests") | ||
|
||
def to_json_serializable(o): | ||
if o in dtype_to_name: | ||
return dtype_to_name[o] | ||
if isinstance(o, (BuiltinFunctionType, FunctionType, type)): | ||
return o.__name__ | ||
if dataclasses.is_dataclass(o): | ||
return to_json_serializable(dataclasses.asdict(o)) | ||
if isinstance(o, SearchStrategy): | ||
return repr(o) | ||
if isinstance(o, dict): | ||
return {to_json_serializable(k): to_json_serializable(v) for k, v in o.items()} | ||
if isinstance(o, tuple): | ||
if hasattr(o, '_asdict'): # namedtuple | ||
return to_json_serializable(o._asdict()) | ||
return tuple(to_json_serializable(i) for i in o) | ||
if isinstance(o, list): | ||
return [to_json_serializable(i) for i in o] | ||
|
||
# Ensure everything is JSON serializable. If this warning is issued, it | ||
# means the given type needs to be added above if possible. | ||
try: | ||
json.dumps(o) | ||
except TypeError: | ||
warnings.warn(f"{o!r} (of type {type(o)}) is not JSON-serializable. Using the repr instead.") | ||
return repr(o) | ||
|
||
return o | ||
|
||
@mark.optionalhook | ||
def pytest_metadata(metadata): | ||
""" | ||
Additional global metadata for --json-report. | ||
""" | ||
metadata['array_api_tests_module'] = xp.mod_name | ||
metadata['array_api_tests_version'] = __version__ | ||
|
||
@fixture(autouse=True) | ||
def add_extra_json_metadata(request, json_metadata): | ||
""" | ||
Additional per-test metadata for --json-report | ||
""" | ||
def add_metadata(name, obj): | ||
obj = to_json_serializable(obj) | ||
json_metadata[name] = obj | ||
|
||
test_module = request.module.__name__ | ||
if test_module.startswith('array_api_tests.meta'): | ||
return | ||
|
||
test_function = request.function.__name__ | ||
assert test_function.startswith('test_'), 'unexpected test function name' | ||
|
||
if test_module == 'array_api_tests.test_has_names': | ||
array_api_function_name = None | ||
else: | ||
array_api_function_name = test_function[len('test_'):] | ||
|
||
add_metadata('test_module', test_module) | ||
add_metadata('test_function', test_function) | ||
add_metadata('array_api_function_name', array_api_function_name) | ||
|
||
if hasattr(request.node, 'callspec'): | ||
params = request.node.callspec.params | ||
add_metadata('params', params) | ||
|
||
def finalizer(): | ||
# TODO: This metadata is all in the form of error strings. It might be | ||
# nice to extract the hypothesis failing inputs directly somehow. | ||
if hasattr(request.node, 'hypothesis_report_information'): | ||
add_metadata('hypothesis_report_information', request.node.hypothesis_report_information) | ||
if hasattr(request.node, 'hypothesis_statistics'): | ||
add_metadata('hypothesis_statistics', request.node.hypothesis_statistics) | ||
|
||
request.addfinalizer(finalizer) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
pytest | ||
pytest-json-report | ||
hypothesis>=6.45.0 | ||
ndindex>=1.6 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about
array_properties
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And in any case, if you could add this to
__all__
. Maybe this is just a weird side product of how I learnt Python, but I like using it to denote the internally-public API of a module.