| #! /usr/bin/env python |
| from __future__ import print_function |
| import sys |
| import textwrap |
| |
| try: |
| import argparse |
| except ImportError: |
| print(textwrap.dedent(""" |
| The argparse library could not be imported. jsonschema_suite requires |
| either Python 2.7 or for you to install argparse. You can do so by |
| running `pip install argparse`, `easy_install argparse` or by |
| downloading argparse and running `python2.6 setup.py install`. |
| |
| See https://pypi.python.org/pypi/argparse for details. |
| """.strip("\n"))) |
| sys.exit(1) |
| |
| import errno |
| import fnmatch |
| import json |
| import os |
| import random |
| import shutil |
| import unittest |
| import warnings |
| |
| if getattr(unittest, "skipIf", None) is None: |
| unittest.skipIf = lambda cond, msg : lambda fn : fn |
| |
| try: |
| import jsonschema |
| except ImportError: |
| jsonschema = None |
| else: |
| validators = getattr( |
| jsonschema.validators, "validators", jsonschema.validators |
| ) |
| |
| |
| ROOT_DIR = os.path.join( |
| os.path.dirname(__file__), os.pardir).rstrip("__pycache__") |
| SUITE_ROOT_DIR = os.path.join(ROOT_DIR, "tests") |
| |
| REMOTES = { |
| "integer.json": {"type": "integer"}, |
| "subSchemas.json": { |
| "integer": {"type": "integer"}, |
| "refToInteger": {"$ref": "#/integer"}, |
| }, |
| "folder/folderInteger.json": {"type": "integer"} |
| } |
| REMOTES_DIR = os.path.join(ROOT_DIR, "remotes") |
| |
| TESTSUITE_SCHEMA = { |
| "$schema": "http://json-schema.org/draft-03/schema#", |
| "type": "array", |
| "items": { |
| "type": "object", |
| "properties": { |
| "description": {"type": "string", "required": True}, |
| "schema": {"required": True}, |
| "tests": { |
| "type": "array", |
| "items": { |
| "type": "object", |
| "properties": { |
| "description": {"type": "string", "required": True}, |
| "data": {"required": True}, |
| "valid": {"type": "boolean", "required": True} |
| }, |
| "additionalProperties": False |
| }, |
| "minItems": 1 |
| } |
| }, |
| "additionalProperties": False, |
| "minItems": 1 |
| } |
| } |
| |
| |
| def files(paths): |
| for path in paths: |
| with open(path) as test_file: |
| yield json.load(test_file) |
| |
| |
| def groups(paths): |
| for test_file in files(paths): |
| for group in test_file: |
| yield group |
| |
| |
| def cases(paths): |
| for test_group in groups(paths): |
| for test in test_group["tests"]: |
| test["schema"] = test_group["schema"] |
| yield test |
| |
| |
| def collect(root_dir): |
| for root, dirs, files in os.walk(root_dir): |
| for filename in fnmatch.filter(files, "*.json"): |
| yield os.path.join(root, filename) |
| |
| |
| class SanityTests(unittest.TestCase): |
| @classmethod |
| def setUpClass(cls): |
| print("Looking for tests in %s" % SUITE_ROOT_DIR) |
| cls.test_files = list(collect(SUITE_ROOT_DIR)) |
| print("Found %s test files" % len(cls.test_files)) |
| assert cls.test_files, "Didn't find the test files!" |
| |
| def test_all_files_are_valid_json(self): |
| for path in self.test_files: |
| with open(path) as test_file: |
| try: |
| json.load(test_file) |
| except ValueError as error: |
| self.fail("%s contains invalid JSON (%s)" % (path, error)) |
| |
| def test_all_descriptions_have_reasonable_length(self): |
| for case in cases(self.test_files): |
| descript = case["description"] |
| self.assertLess( |
| len(descript), |
| 60, |
| "%r is too long! (keep it to less than 60 chars)" % (descript,) |
| ) |
| |
| def test_all_descriptions_are_unique(self): |
| for group in groups(self.test_files): |
| descriptions = set(test["description"] for test in group["tests"]) |
| self.assertEqual( |
| len(descriptions), |
| len(group["tests"]), |
| "%r contains a duplicate description" % (group,) |
| ) |
| |
| @unittest.skipIf(jsonschema is None, "Validation library not present!") |
| def test_all_schemas_are_valid(self): |
| for schema in os.listdir(SUITE_ROOT_DIR): |
| schema_validator = validators.get(schema) |
| if schema_validator is not None: |
| test_files = collect(os.path.join(SUITE_ROOT_DIR, schema)) |
| for case in cases(test_files): |
| try: |
| schema_validator.check_schema(case["schema"]) |
| except jsonschema.SchemaError as error: |
| self.fail("%s contains an invalid schema (%s)" % |
| (case, error)) |
| else: |
| warnings.warn("No schema validator for %s" % schema) |
| |
| @unittest.skipIf(jsonschema is None, "Validation library not present!") |
| def test_suites_are_valid(self): |
| validator = jsonschema.Draft3Validator(TESTSUITE_SCHEMA) |
| for tests in files(self.test_files): |
| try: |
| validator.validate(tests) |
| except jsonschema.ValidationError as error: |
| self.fail(str(error)) |
| |
| def test_remote_schemas_are_updated(self): |
| for url, schema in REMOTES.items(): |
| filepath = os.path.join(REMOTES_DIR, url) |
| with open(filepath) as schema_file: |
| self.assertEqual(json.load(schema_file), schema) |
| |
| |
| def main(arguments): |
| if arguments.command == "check": |
| suite = unittest.TestLoader().loadTestsFromTestCase(SanityTests) |
| result = unittest.TextTestRunner(verbosity=2).run(suite) |
| sys.exit(not result.wasSuccessful()) |
| elif arguments.command == "flatten": |
| selected_cases = [case for case in cases(collect(arguments.version))] |
| |
| if arguments.randomize: |
| random.shuffle(selected_cases) |
| |
| json.dump(selected_cases, sys.stdout, indent=4, sort_keys=True) |
| elif arguments.command == "remotes": |
| json.dump(REMOTES, sys.stdout, indent=4, sort_keys=True) |
| elif arguments.command == "dump_remotes": |
| if arguments.update: |
| shutil.rmtree(arguments.out_dir, ignore_errors=True) |
| |
| try: |
| os.makedirs(arguments.out_dir) |
| except OSError as e: |
| if e.errno == errno.EEXIST: |
| print("%s already exists. Aborting." % arguments.out_dir) |
| sys.exit(1) |
| raise |
| |
| for url, schema in REMOTES.items(): |
| filepath = os.path.join(arguments.out_dir, url) |
| |
| try: |
| os.makedirs(os.path.dirname(filepath)) |
| except OSError as e: |
| if e.errno != errno.EEXIST: |
| raise |
| |
| with open(filepath, "wb") as out_file: |
| json.dump(schema, out_file, indent=4, sort_keys=True) |
| elif arguments.command == "serve": |
| try: |
| from flask import Flask, jsonify |
| except ImportError: |
| print(textwrap.dedent(""" |
| The Flask library is required to serve the remote schemas. |
| |
| You can install it by running `pip install Flask`. |
| |
| Alternatively, see the `jsonschema_suite remotes` or |
| `jsonschema_suite dump_remotes` commands to create static files |
| that can be served with your own web server. |
| """.strip("\n"))) |
| sys.exit(1) |
| |
| app = Flask(__name__) |
| |
| @app.route("/<path:path>") |
| def serve_path(path): |
| if path in REMOTES: |
| return jsonify(REMOTES[path]) |
| return "Document does not exist.", 404 |
| |
| app.run(port=1234) |
| |
| |
| parser = argparse.ArgumentParser( |
| description="JSON Schema Test Suite utilities", |
| ) |
| subparsers = parser.add_subparsers(help="utility commands", dest="command") |
| |
| check = subparsers.add_parser("check", help="Sanity check the test suite.") |
| |
| flatten = subparsers.add_parser( |
| "flatten", |
| help="Output a flattened file containing a selected version's test cases." |
| ) |
| flatten.add_argument( |
| "--randomize", |
| action="store_true", |
| help="Randomize the order of the outputted cases.", |
| ) |
| flatten.add_argument( |
| "version", help="The directory containing the version to output", |
| ) |
| |
| remotes = subparsers.add_parser( |
| "remotes", |
| help="Output the expected URLs and their associated schemas for remote " |
| "ref tests as a JSON object." |
| ) |
| |
| dump_remotes = subparsers.add_parser( |
| "dump_remotes", help="Dump the remote ref schemas into a file tree", |
| ) |
| dump_remotes.add_argument( |
| "--update", |
| action="store_true", |
| help="Update the remotes in an existing directory.", |
| ) |
| dump_remotes.add_argument( |
| "--out-dir", |
| default=REMOTES_DIR, |
| type=os.path.abspath, |
| help="The output directory to create as the root of the file tree", |
| ) |
| |
| serve = subparsers.add_parser( |
| "serve", |
| help="Start a webserver to serve schemas used by remote ref tests." |
| ) |
| |
| if __name__ == "__main__": |
| main(parser.parse_args()) |