Skip to content

Commit

Permalink
Move parser tests from rtd-build repo (#4225)
Browse files Browse the repository at this point in the history
* Import parser tests

* Move code

* Linter

* Move utils module

* Isort

* Move tests for find.py

* Move implementation from find.py

* Add fixture

* Fix path for test

* Fix test about encoding

Probably the file was corrupted,
the test pass with a proper unicode filename.
See #3732 (comment)

* Linter

* Isort

* Remove wrong assert

We are using unicode literals
  • Loading branch information
stsewd authored and agjohnson committed Jun 14, 2018
1 parent 79d7808 commit aafd455
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 0 deletions.
Empty file added readthedocs/config/__init__.py
Empty file.
22 changes: 22 additions & 0 deletions readthedocs/config/find.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Helper functions to search files."""

from __future__ import division, print_function, unicode_literals

import os


def find_all(path, filenames):
"""Find all files in ``path`` that match in ``filenames``."""
path = os.path.abspath(path)
for root, dirs, files in os.walk(path, topdown=True):
dirs.sort()
for filename in filenames:
if filename in files:
yield os.path.abspath(os.path.join(root, filename))


def find_one(path, filenames):
"""Find the first file in ``path`` that match in ``filenames``."""
for _path in find_all(path, filenames):
return _path
return ''
34 changes: 34 additions & 0 deletions readthedocs/config/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
"""YAML parser for the RTD configuration file."""

from __future__ import division, print_function, unicode_literals

import yaml

__all__ = ('parse', 'ParseError')


class ParseError(Exception):

"""Parser related errors."""

pass


def parse(stream):
"""
Take file-like object and return a list of project configurations.
The files need be valid YAML and only contain mappings as documents.
Everything else raises a ``ParseError``.
"""
try:
configs = list(yaml.safe_load_all(stream))
except yaml.YAMLError as error:
raise ParseError('YAML: {message}'.format(message=error))
if not configs:
raise ParseError('Empty config')
for config in configs:
if not isinstance(config, dict):
raise ParseError('Expected mapping')
return configs
Empty file.
Empty file.
96 changes: 96 additions & 0 deletions readthedocs/config/tests/test_find.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from __future__ import division, print_function, unicode_literals

import os

import pytest
import six

from readthedocs.config.find import find_all, find_one

from .utils import apply_fs


def test_find_no_files(tmpdir):
with tmpdir.as_cwd():
paths = list(find_all(os.getcwd(), ('readthedocs.yml',)))
assert len(paths) == 0


def test_find_at_root(tmpdir):
apply_fs(tmpdir, {'readthedocs.yml': '', 'otherfile.txt': ''})

base = str(tmpdir)
paths = list(find_all(base, ('readthedocs.yml',)))
assert paths == [
os.path.abspath(os.path.join(base, 'readthedocs.yml'))
]


def test_find_nested(tmpdir):
apply_fs(tmpdir, {
'first': {
'readthedocs.yml': '',
},
'second': {
'confuser.txt': 'content',
},
'third': {
'readthedocs.yml': 'content',
'Makefile': '',
},
})
apply_fs(tmpdir, {'first/readthedocs.yml': ''})

base = str(tmpdir)
paths = list(find_all(base, ('readthedocs.yml',)))
assert set(paths) == set([
str(tmpdir.join('first', 'readthedocs.yml')),
str(tmpdir.join('third', 'readthedocs.yml')),
])


def test_find_multiple_files(tmpdir):
apply_fs(tmpdir, {
'first': {
'readthedocs.yml': '',
'.readthedocs.yml': 'content',
},
'second': {
'confuser.txt': 'content',
},
'third': {
'readthedocs.yml': 'content',
'Makefile': '',
},
})
apply_fs(tmpdir, {'first/readthedocs.yml': ''})

base = str(tmpdir)
paths = list(find_all(base, ('readthedocs.yml',
'.readthedocs.yml')))
assert paths == [
str(tmpdir.join('first', 'readthedocs.yml')),
str(tmpdir.join('first', '.readthedocs.yml')),
str(tmpdir.join('third', 'readthedocs.yml')),
]

paths = list(find_all(base, ('.readthedocs.yml',
'readthedocs.yml')))
assert paths == [
str(tmpdir.join('first', '.readthedocs.yml')),
str(tmpdir.join('first', 'readthedocs.yml')),
str(tmpdir.join('third', 'readthedocs.yml')),
]


@pytest.mark.skipif(not six.PY2, reason='Only for python2')
def test_find_unicode_path(tmpdir):
base_path = os.path.abspath(
os.path.join(os.path.dirname(__file__), 'fixtures/bad_encode_project')
)
path = find_one(base_path, ('readthedocs.yml',))
assert path == ''
unicode_base_path = base_path.decode('utf-8')
assert isinstance(unicode_base_path, unicode)
path = find_one(unicode_base_path, ('readthedocs.yml',))
assert path == ''
56 changes: 56 additions & 0 deletions readthedocs/config/tests/test_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from __future__ import division, print_function, unicode_literals

from io import StringIO

from pytest import raises

from readthedocs.config.parser import ParseError, parse


def test_parse_empty_config_file():
buf = StringIO(u'')
with raises(ParseError):
parse(buf)


def test_parse_invalid_yaml():
buf = StringIO(u'- - !asdf')
with raises(ParseError):
parse(buf)


def test_parse_bad_type():
buf = StringIO(u'Hello')
with raises(ParseError):
parse(buf)


def test_parse_single_config():
buf = StringIO(u'base: path')
config = parse(buf)
assert isinstance(config, list)
assert len(config) == 1
assert config[0]['base'] == 'path'


def test_parse_empty_list():
buf = StringIO(u'base: []')
config = parse(buf)
assert config[0]['base'] == []


def test_parse_multiple_configs_in_one_file():
buf = StringIO(
u'''
base: path
---
base: other_path
name: second
nested:
works: true
''')
configs = parse(buf)
assert isinstance(configs, list)
assert len(configs) == 2
assert configs[0]['base'] == 'path'
assert configs[1]['nested'] == {'works': True}
28 changes: 28 additions & 0 deletions readthedocs/config/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from __future__ import division, print_function, unicode_literals

from .utils import apply_fs


def test_apply_fs_with_empty_contents(tmpdir):
# Doesn't do anything if second paramter is empty.
apply_fs(tmpdir, {})
assert tmpdir.listdir() == []


def test_apply_fs_create_empty_file(tmpdir):
# Create empty file.
apply_fs(tmpdir, {'file': ''})
assert len(tmpdir.listdir()) == 1
assert tmpdir.join('file').read() == ''


def test_apply_fs_create_file_with_content(tmpdir):
# Create file with content.
apply_fs(tmpdir, {'file': 'content'})
assert tmpdir.join('file').read() == 'content'


def test_apply_fs_create_subdirectory(tmpdir):
# Create file with content.
apply_fs(tmpdir, {'subdir': {'file': 'content'}})
assert tmpdir.join('subdir', 'file').read() == 'content'
16 changes: 16 additions & 0 deletions readthedocs/config/tests/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from __future__ import division, print_function, unicode_literals


def apply_fs(tmpdir, contents):
"""
Create the directory structure specified in ``contents``. It's a dict of
filenames as keys and the file contents as values. If the value is another
dict, it's a subdirectory.
"""
for filename, content in contents.items():
if hasattr(content, 'items'):
apply_fs(tmpdir.mkdir(filename), content)
else:
file = tmpdir.join(filename)
file.write(content)
return tmpdir

0 comments on commit aafd455

Please sign in to comment.