Skip to content

Commit

Permalink
Write json fixture test groundwork
Browse files Browse the repository at this point in the history
  • Loading branch information
njgheorghita committed Jan 4, 2018
1 parent e5f1bac commit bc945d2
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 1 deletion.
21 changes: 21 additions & 0 deletions ethpm/assets/lockfiles/owned/1.0.0.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"lockfile_version": "1",
"version": "1.0.0",
"package_name": "owned",
"meta": {
"license": "MIT",
"authors": [
"Piper Merriam <[email protected]>"
],
"description": "Reusable contracts which implement a privileged'owner' model for authorization",
"keywords": [
"authorization"
],
"links": {
"documentation": "ipfs://QmQiqrwqdav5bV8mtv4PqGksGcDWo43f7PAZYwhJqNEv2j"
}
},
"sources": {
"./contracts/owned.sol": "ipfs://QmUjYUcX9kLv2FQH8nwc3RLLXtU3Yv5XFpvEjFcAKXB6xD"
}
}
11 changes: 11 additions & 0 deletions ethpm/assets/lockfiles/owned/contracts/owned.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
pragma solidity ^0.4.0;

contract owned {
address owner;

function owned() {
owner = msg.sender;
}

modifier onlyowner { if (msg.sender != owner) throw; _; }
}
150 changes: 150 additions & 0 deletions ethpm/utils/fixture_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import fnmatch
import functools
import hashlib
import json
import os
from cytoolz import (
curry,
identity,
)
from eth_utils import (
to_tuple
)


#
# Filesystem fixture loading.
#
@to_tuple
def _recursive_find_files(base_dir, pattern):
for dirpath, _, filenames in os.walk(base_dir):
for filename in filenames:
if fnmatch.fnmatch(filename, pattern):
yield os.path.join(dirpath, filename)


def find_fixture_files(fixtures_base_dir):
all_fixture_paths = _recursive_find_files(fixtures_base_dir, "*.json")
return all_fixture_paths


@to_tuple
def find_fixtures(fixtures_base_dir):
"""
Finds all of the (fixture_path, fixture_key) pairs for a given path under
the JSON test fixtures directory.
"""
all_fixture_paths = find_fixture_files(fixtures_base_dir)

for fixture_path in sorted(all_fixture_paths):
with open(fixture_path) as fixture_file:
fixtures = json.load(fixture_file)

for fixture_key in sorted(fixtures.keys()):
yield (fixture_path, fixture_key)


# we use an LRU cache on this function so that we can sort the tests such that
# all fixtures from the same file are executed sequentially allowing us to keep
# a small rolling cache of the loaded fixture files.
@functools.lru_cache(maxsize=4)
def load_json_fixture(fixture_path):
"""
Loads a fixture file, caching the most recent files it loaded.
"""
with open(fixture_path) as fixture_file:
file_fixtures = json.load(fixture_file)
return file_fixtures


def load_fixture(fixture_path, fixture_key, normalize_fn=identity):
"""
Loads a specific fixture from a fixture file, optionally passing it through
a normalization function.
"""
file_fixtures = load_json_fixture(fixture_path)
fixture = normalize_fn(file_fixtures[fixture_key])
return fixture


#
# Pytest fixture generation
#
def idfn(fixture_params):
"""
Function for pytest to produce uniform names for fixtures.
"""
return ":".join((str(item) for item in fixture_params))


def get_fixtures_file_hash(all_fixture_paths):
"""
Returns the MD5 hash of the fixture files. Used for cache busting.
"""
hasher = hashlib.md5()
for fixture_path in sorted(all_fixture_paths):
with open(fixture_path, 'rb') as fixture_file:
hasher.update(fixture_file.read())
return hasher.hexdigest()


@curry
def generate_fixture_tests(metafunc,
base_fixture_path,
filter_fn=identity,
preprocess_fn=identity):
"""
Helper function for use with `pytest_generate_tests` which will use the
pytest caching facilities to reduce the load time for fixture tests.
- `metafunc` is the parameter from `pytest_generate_tests`
- `base_fixture_path` is the base path that fixture files will be collected from.
- `filter_fn` handles ignoring or marking the various fixtures. See `filter_fixtures`.
- `preprocess_fn` handles any preprocessing that should be done on the raw
fixtures (such as expanding the statetest fixtures to be multiple tests for
each fork.
"""
fixture_namespace = os.path.basename(base_fixture_path)

if 'fixture_data' in metafunc.fixturenames:
all_fixture_paths = find_fixture_files(base_fixture_path)
current_file_hash = get_fixtures_file_hash(all_fixture_paths)

data_cache_key = 'pyevm/statetest/fixtures/{0}/data'.format(fixture_namespace)
file_hash_cache_key = 'pyevm/statetest/fixtures/{0}/data-hash'.format(fixture_namespace)

cached_file_hash = metafunc.config.cache.get(file_hash_cache_key, None)
cached_fixture_data = metafunc.config.cache.get(data_cache_key, None)

bust_cache = any((
cached_file_hash is None,
cached_fixture_data is None,
cached_file_hash != current_file_hash,
))

if bust_cache:
all_fixtures = find_fixtures(base_fixture_path)

metafunc.config.cache.set(data_cache_key, all_fixtures)
metafunc.config.cache.set(file_hash_cache_key, current_file_hash)
else:
all_fixtures = cached_fixture_data

if not len(all_fixtures):
raise AssertionError(
"Suspiciously found zero fixtures: {0}".format(base_fixture_path)
)

filtered_fixtures = filter_fn(preprocess_fn(all_fixtures))

metafunc.parametrize('fixture_data', filtered_fixtures, ids=idfn)


#
# Fixture Normalizers
#
def normalize_ethpmtest_fixture(fixture):
normalized_fixture = {
'in': fixture['in'],
'out': fixture['out']
}
return normalized_fixture
25 changes: 25 additions & 0 deletions fixtures/EthpmTests/ownedtest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"test": {
"in": "./ethpm/assets/lockfiles/owned/1.0.0.json",
"out": {
"package_identifier": "./lockfiles/owned/1.0.0.json",
"repr": "<bound method Package.__repr__ of <Package owned==1.0.0>>",
"lockfile_version": "1",
"version": "1.0.0",
"package_name": "owned",
"meta": {
"license": "MIT",
"authors": ["Piper Merriam <[email protected]>"],
"description": "Reusable contracts which implement a privileged'owner' model for authorization",
"keywords": ["authorization"],
"links": {
"documentation": "ipfs://QmQiqrwqdav5bV8mtv4PqGksGcDWo43f7PAZYwhJqNEv2j"
}
},
"sources": {
"./contracts/owned.sol": "ipfs://QmUjYUcX9kLv2FQH8nwc3RLLXtU3Yv5XFpvEjFcAKXB6xD"
},
"build_dependencies": null
}
}
}
1 change: 0 additions & 1 deletion tests/ethpm/test_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,3 @@ def test_package_object_has_version_property(valid_lockfile):
def test_package_has_custom_str_repr(valid_lockfile):
current_package = Package(valid_lockfile)
assert current_package.__repr__() == "<Package wallet==1.0.0>"

44 changes: 44 additions & 0 deletions tests/json-fixtures/test_ethpm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import pytest

import os

from ethpm.utils.fixture_tests import (
generate_fixture_tests,
load_fixture,
normalize_ethpmtest_fixture,
)

from ethpm.package import (
Package,
)

ROOT_PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))


BASE_FIXTURE_PATH = os.path.join(ROOT_PROJECT_DIR, 'fixtures', 'EthpmTests')


def pytest_generate_tests(metafunc):
generate_fixture_tests(
metafunc=metafunc,
base_fixture_path=BASE_FIXTURE_PATH,
)


@pytest.fixture
def fixture(fixture_data):
fixture_path, fixture_key = fixture_data
fixture = load_fixture(
fixture_path,
fixture_key,
normalize_ethpmtest_fixture,
)
return fixture


def test_ethpm_fixtures(fixture):
current_package = Package.from_file(fixture['in'])
expected = fixture['out']
assert str(current_package.__repr__) == expected['repr']
assert current_package.name == expected['package_name']
assert current_package.version == expected['version']

0 comments on commit bc945d2

Please sign in to comment.