diff --git a/.github/workflows/numpy.yml b/.github/workflows/numpy.yml index 74eeccf4..273ee970 100644 --- a/.github/workflows/numpy.yml +++ b/.github/workflows/numpy.yml @@ -75,4 +75,6 @@ jobs: EOF - pytest -v -rxXfE --ci + pytest -v -rxXfE --ci --json-report + - name: Verify JSON report against schema + run: ./verify_report.py diff --git a/report.schema.json b/report.schema.json new file mode 100644 index 00000000..1e7d04a9 --- /dev/null +++ b/report.schema.json @@ -0,0 +1,358 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "duration": { + "type": "number" + }, + "summary": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "collected": { + "type": "integer" + }, + "passed": { + "type": "integer" + }, + "failed": { + "type": "integer" + }, + "skipped": { + "type": "integer" + }, + "error": { + "type": "integer" + }, + "deselected": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "total" + ] + }, + "environment": { + "type": "object", + "properties": { + "Platform": { + "type": "string" + }, + "Packages": { + "type": "object", + "properties": { + "pytest": { + "type": "string" + }, + "pluggy": { + "type": "string" + }, + "py": { + "type": "string" + } + }, + "additionalProperties": { + "type": "string" + }, + "required": [ + "pytest", + "pluggy", + "py" + ] + }, + "array_api_tests_version": { + "type": "string" + }, + "array_api_tests_module": { + "type": "string" + }, + "Python": { + "type": "string" + }, + "Plugins": { + "type": "object", + "properties": { + "metadata": { + "type": "string" + }, + "json-report": { + "type": "string" + }, + "hypothesis": { + "type": "string" + } + }, + "additionalProperties": { + "type": "string" + }, + "required": [ + "metadata", + "json-report", + "hypothesis" + ] + } + }, + "required": [ + "Platform", + "Packages", + "array_api_tests_version", + "array_api_tests_module", + "Python", + "Plugins" + ] + }, + "collectors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "result": { + "type": "array", + "items": { + "type": "object", + "properties": { + "lineno": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "nodeid": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "type", + "nodeid" + ] + } + }, + "nodeid": { + "type": "string" + }, + "outcome": { + "type": "string" + }, + "longrepr": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "result", + "nodeid", + "outcome" + ] + } + }, + "tests": { + "type": "array", + "items": { + "type": "object", + "$defs": { + "teststage": { + "type": "object", + "properties": { + "duration": { + "type": "number" + }, + "longrepr": { + "type": "string" + }, + "outcome": { + "type": "string" + }, + "traceback": { + "type": "array", + "items": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "lineno": { + "type": "integer" + }, + "message": { + "type": "string" + } + }, + "required": [ + "path", + "lineno", + "message" + ] + } + }, + "crash": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "lineno": { + "type": "integer" + }, + "message": { + "type": "string" + } + }, + "required": [ + "path", + "lineno", + "message" + ] + }, + "stdout": { + "type": "string" + }, + "stderr": { + "type": "string" + }, + "stdout": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "duration", + "outcome" + ] + } + }, + "properties": { + "call": { + "$ref": "#/properties/tests/items/$defs/teststage" + }, + "crash": { + "$ref": "#/properties/tests/items/$defs/teststage" + }, + "teardown": { + "$ref": "#/properties/tests/items/$defs/teststage" + }, + "setup": { + "$ref": "#/properties/tests/items/$defs/teststage" + }, + "metadata": { + "type": "object", + "properties": { + "test_module": { + "type": "string" + }, + "array_api_function_name": { + "type": [ + "string", + "null" + ] + }, + "hypothesis_statistics": { + "type": "string" + }, + "hypothesis_report_information": { + "type": "array", + "items": { + "type": "string" + } + }, + "test_function": { + "type": "string" + }, + "params": { + "type": "object" + } + }, + "additionalProperties": false, + "required": [ + "test_module", + "array_api_function_name", + "test_function" + ] + }, + "lineno": { + "type": "integer" + }, + "keywords": { + "type": "array", + "items": { + "type": "string" + } + }, + "nodeid": { + "type": "string" + }, + "outcome": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "lineno", + "keywords", + "setup", + "nodeid", + "teardown", + "outcome" + ] + } + }, + "created": { + "type": "number" + }, + "root": { + "type": "string" + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "lineno": { + "type": "integer" + }, + "filename": { + "type": "string" + }, + "count": { + "type": "integer" + }, + "category": { + "type": "string" + }, + "message": { + "type": "string" + }, + "when": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "lineno", + "filename", + "count", + "category", + "message", + "when" + ] + } + }, + "exitcode": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "duration", + "summary", + "environment", + "tests", + "created", + "root", + "warnings", + "exitcode" + ] +} diff --git a/reporting.py b/reporting.py index f7c7d6b9..bfc1e693 100644 --- a/reporting.py +++ b/reporting.py @@ -7,6 +7,7 @@ import dataclasses import json import warnings +import math from hypothesis.strategies import SearchStrategy @@ -33,6 +34,13 @@ def to_json_serializable(o): return tuple(to_json_serializable(i) for i in o) if isinstance(o, list): return [to_json_serializable(i) for i in o] + if isinstance(o, float): + if math.isnan(o): + return "NaN" + if o == float('inf'): + return 'Infinity' + if o == -float('inf'): + return '-Infinity' # Ensure everything is JSON serializable. If this warning is issued, it # means the given type needs to be added above if possible. diff --git a/verify_report.py b/verify_report.py new file mode 100755 index 00000000..01e24ee9 --- /dev/null +++ b/verify_report.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +import sys +import json + +import jsonschema + +def main(): + if len(sys.argv) == 1: + report_file = '.report.json' + elif len(sys.argv) == 2: + report_file = sys.argv[1] + else: + sys.exit("Usage: verify_report.py [json_report_file]") + + schema = json.load(open('report.schema.json')) + + jsonschema.Validator.check_schema(schema) + + with open(report_file) as f: + report = json.load(f) + + # Make sure there are no nans in the report + json.dumps(report, allow_nan=False) + + jsonschema.validate(report, schema) + +if __name__ == '__main__': + main()