-
-
Notifications
You must be signed in to change notification settings - Fork 19
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 #219 from praw-dev/upgrade_tests
Fully migrate from nose to pytest and general code clean up
- Loading branch information
Showing
63 changed files
with
5,870 additions
and
7,460 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
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 |
---|---|---|
|
@@ -6,5 +6,5 @@ python: | |
extra_requirements: | ||
- readthedocs | ||
path: . | ||
version: 3.8 | ||
version: '3.8' | ||
version: 2 |
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 |
---|---|---|
@@ -1,7 +1,3 @@ | ||
include CHANGES.rst LICENSE.txt README.rst praw_license.txt | ||
include asyncpraw/praw.ini | ||
include "asyncpraw/images/PRAW logo.png" | ||
include docs/Makefile | ||
recursive-include docs *.png *.py *.rst | ||
recursive-include tests *.json *.py | ||
recursive-include tests/files * |
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
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
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
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 |
---|---|---|
@@ -1,190 +1,58 @@ | ||
"""Prepare py.test.""" | ||
"""Prepare pytest.""" | ||
import asyncio | ||
import json | ||
import os | ||
from base64 import b64encode | ||
from datetime import datetime | ||
from functools import wraps | ||
|
||
import pytest | ||
from _pytest.tmpdir import _mk_tmp | ||
from vcr import VCR | ||
from vcr.cassette import Cassette | ||
from vcr.persisters.filesystem import FilesystemPersister | ||
from vcr.serialize import deserialize, serialize | ||
|
||
|
||
# Prevent calls to sleep | ||
async def _sleep(*args): | ||
raise Exception("Call to sleep") | ||
class Placeholders: | ||
def __init__(self, _dict): | ||
self.__dict__ = _dict | ||
|
||
|
||
asyncio.sleep = _sleep | ||
@pytest.fixture | ||
def image_path(): | ||
"""Return path to image.""" | ||
|
||
def _get_path(name): | ||
"""Return path to image.""" | ||
return os.path.join(os.path.dirname(__file__), "integration", "files", name) | ||
|
||
return _get_path | ||
|
||
def b64_string(input_string): | ||
"""Return a base64 encoded string (not bytes) from input_string.""" | ||
return b64encode(input_string.encode("utf-8")).decode("utf-8") | ||
|
||
def pytest_configure(config): | ||
pytest.placeholders = Placeholders(placeholders) | ||
config.addinivalue_line( | ||
"markers", "cassette_name: Name of cassette to use for test." | ||
) | ||
config.addinivalue_line( | ||
"markers", "recorder_kwargs: Arguments to pass to the recorder." | ||
) | ||
|
||
def env_default(key): | ||
"""Return environment variable or placeholder string.""" | ||
return os.environ.get(f"prawtest_{key}", f"placeholder_{key}") | ||
|
||
@pytest.fixture(autouse=True) | ||
def patch_sleep(monkeypatch): | ||
"""Auto patch sleep to speed up tests.""" | ||
|
||
def filter_access_token(response): | ||
"""Add VCR callback to filter access token.""" | ||
request_uri = response["url"] | ||
if "api/v1/access_token" not in request_uri or response["status"]["code"] != 200: | ||
return response | ||
body = response["body"]["string"].decode() | ||
try: | ||
token = json.loads(body)["access_token"] | ||
response["body"]["string"] = response["body"]["string"].replace( | ||
token.encode("utf-8"), b"<ACCESS_TOKEN>" | ||
) | ||
placeholders["access_token"] = token | ||
except (KeyError, TypeError, ValueError): | ||
async def _sleep(*_, **__): | ||
"""Dud sleep function.""" | ||
pass | ||
return response | ||
|
||
|
||
def serialize_dict(data: dict): | ||
"""This is to filter out buffered readers.""" | ||
new_dict = {} | ||
for key, value in data.items(): | ||
if key == "file": | ||
new_dict[key] = serialize_file(value.name) | ||
elif isinstance(value, dict): | ||
new_dict[key] = serialize_dict(value) | ||
elif isinstance(value, list): | ||
new_dict[key] = serialize_list(value) | ||
else: | ||
new_dict[key] = value | ||
return new_dict | ||
|
||
|
||
def serialize_file(file_name): | ||
with open(file_name, "rb") as f: | ||
return f.read().decode("utf-8", "replace") | ||
|
||
|
||
def serialize_list(data: list): | ||
new_list = [] | ||
for item in data: | ||
if isinstance(item, dict): | ||
new_list.append(serialize_dict(item)) | ||
elif isinstance(item, list): | ||
new_list.append(serialize_list(item)) | ||
elif isinstance(item, tuple): | ||
if item[0] == "file": | ||
item = (item[0], serialize_file(item[1].name)) | ||
new_list.append(item) | ||
else: | ||
new_list.append(item) | ||
return new_list | ||
|
||
monkeypatch.setattr(asyncio, "sleep", value=_sleep) | ||
|
||
|
||
os.environ["praw_check_for_updates"] = "False" | ||
|
||
placeholders = { | ||
x: env_default(x) | ||
x: os.environ.get(f"prawtest_{x}", f"placeholder_{x}") | ||
for x in ( | ||
"auth_code client_id client_secret password redirect_uri test_subreddit" | ||
" user_agent username refresh_token" | ||
"auth_code client_id client_secret password redirect_uri refresh_token" | ||
" test_subreddit user_agent username" | ||
).split() | ||
} | ||
|
||
placeholders["basic_auth"] = b64_string( | ||
f"{placeholders['client_id']}:{placeholders['client_secret']}" | ||
) | ||
|
||
|
||
class CustomPersister(FilesystemPersister): | ||
@classmethod | ||
def load_cassette(cls, cassette_path, serializer): | ||
try: | ||
with open(cassette_path) as f: | ||
cassette_content = f.read() | ||
except OSError: | ||
raise ValueError("Cassette not found.") | ||
for replacement, value in [ | ||
(v, f"<{k.upper()}>") for k, v in placeholders.items() | ||
]: | ||
cassette_content = cassette_content.replace(value, replacement) | ||
cassette = deserialize(cassette_content, serializer) | ||
return cassette | ||
|
||
@staticmethod | ||
def save_cassette(cassette_path, cassette_dict, serializer): | ||
data = serialize(cassette_dict, serializer) | ||
for replacement, value in [ | ||
(f"<{k.upper()}>", v) for k, v in placeholders.items() | ||
]: | ||
data = data.replace(value, replacement) | ||
dirname, filename = os.path.split(cassette_path) | ||
if dirname and not os.path.exists(dirname): | ||
os.makedirs(dirname) | ||
with open(cassette_path, "w") as f: | ||
f.write(data) | ||
|
||
|
||
class CustomSerializer(object): | ||
@staticmethod | ||
def serialize(cassette_dict): | ||
cassette_dict["recorded_at"] = datetime.now().isoformat()[:-7] | ||
return ( | ||
f"{json.dumps(serialize_dict(cassette_dict), sort_keys=True, indent=2)}\n" | ||
) | ||
|
||
@staticmethod | ||
def deserialize(cassette_string): | ||
return json.loads(cassette_string) | ||
|
||
|
||
vcr = VCR( | ||
before_record_response=filter_access_token, | ||
cassette_library_dir="tests/integration/cassettes", | ||
match_on=["uri", "method"], | ||
path_transformer=VCR.ensure_suffix(".json"), | ||
serializer="custom_serializer", | ||
) | ||
vcr.register_serializer("custom_serializer", CustomSerializer) | ||
vcr.register_persister(CustomPersister) | ||
|
||
|
||
def after_init(func, *args): | ||
func(*args) | ||
|
||
|
||
def add_init_hook(original_init): | ||
"""Wrap an __init__ method to also call some hooks.""" | ||
|
||
@wraps(original_init) | ||
def wrapper(self, *args, **kwargs): | ||
original_init(self, *args, **kwargs) | ||
after_init(init_hook, self) | ||
|
||
return wrapper | ||
|
||
|
||
Cassette.__init__ = add_init_hook(Cassette.__init__) | ||
|
||
|
||
def init_hook(cassette): | ||
if not cassette.requests: | ||
pytest.set_up_record() # dynamically defined in __init__.py | ||
|
||
|
||
class Placeholders: | ||
def __init__(self, _dict): | ||
self.__dict__ = _dict | ||
|
||
|
||
def pytest_configure(): | ||
pytest.placeholders = Placeholders(placeholders) | ||
|
||
|
||
@pytest.fixture | ||
def tmp_path(request, tmp_path_factory): | ||
# Manually create tmp_path fixture since asynctest does not play nicely with | ||
# fixtures as args | ||
request.cls.tmp_path = _mk_tmp(request, tmp_path_factory) | ||
placeholders["basic_auth"] = b64encode( | ||
f"{placeholders['client_id']}:{placeholders['client_secret']}".encode("utf-8") | ||
).decode("utf-8") |
Oops, something went wrong.