Skip to content

Commit db8f092

Browse files
stsewdagjohnson
authored andcommitted
Move config module from rtd-build repo (#4242)
* Add pytest-describe dependencie * Import tests from validation.py * Downgrade pytest to 3.2.5 pytest-describe is incompatible with the current pytest (3.6.1) had to downgrade to 3.2.5 * Move validation.py * Fix tests Change the pathed module * Linter * Change import
1 parent 017f581 commit db8f092

File tree

4 files changed

+265
-2
lines changed

4 files changed

+265
-2
lines changed
+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import division, print_function, unicode_literals
3+
4+
import os
5+
6+
from mock import patch
7+
from pytest import raises
8+
from six import text_type
9+
10+
from readthedocs.config.validation import (
11+
INVALID_BOOL, INVALID_CHOICE, INVALID_DIRECTORY, INVALID_FILE, INVALID_LIST,
12+
INVALID_PATH, INVALID_STRING, ValidationError, validate_bool,
13+
validate_choice, validate_directory, validate_file, validate_list,
14+
validate_path, validate_string)
15+
16+
17+
def describe_validate_bool():
18+
def it_accepts_true():
19+
assert validate_bool(True) is True
20+
21+
def it_accepts_false():
22+
assert validate_bool(False) is False
23+
24+
def it_accepts_0():
25+
assert validate_bool(0) is False
26+
27+
def it_accepts_1():
28+
assert validate_bool(1) is True
29+
30+
def it_fails_on_string():
31+
with raises(ValidationError) as excinfo:
32+
validate_bool('random string')
33+
assert excinfo.value.code == INVALID_BOOL
34+
35+
36+
def describe_validate_choice():
37+
38+
def it_accepts_valid_choice():
39+
result = validate_choice('choice', ('choice', 'another_choice'))
40+
assert result is 'choice'
41+
42+
with raises(ValidationError) as excinfo:
43+
validate_choice('c', 'abc')
44+
assert excinfo.value.code == INVALID_LIST
45+
46+
def it_rejects_invalid_choice():
47+
with raises(ValidationError) as excinfo:
48+
validate_choice('not-a-choice', ('choice', 'another_choice'))
49+
assert excinfo.value.code == INVALID_CHOICE
50+
51+
52+
def describe_validate_list():
53+
54+
def it_accepts_list_types():
55+
result = validate_list(['choice', 'another_choice'])
56+
assert result == ['choice', 'another_choice']
57+
58+
result = validate_list(('choice', 'another_choice'))
59+
assert result == ['choice', 'another_choice']
60+
61+
def iterator():
62+
yield 'choice'
63+
64+
result = validate_list(iterator())
65+
assert result == ['choice']
66+
67+
with raises(ValidationError) as excinfo:
68+
validate_choice('c', 'abc')
69+
assert excinfo.value.code == INVALID_LIST
70+
71+
def it_rejects_string_types():
72+
with raises(ValidationError) as excinfo:
73+
result = validate_list('choice')
74+
assert excinfo.value.code == INVALID_LIST
75+
76+
77+
def describe_validate_directory():
78+
79+
def it_uses_validate_path(tmpdir):
80+
patcher = patch('readthedocs.config.validation.validate_path')
81+
with patcher as validate_path:
82+
path = text_type(tmpdir.mkdir('a directory'))
83+
validate_path.return_value = path
84+
validate_directory(path, str(tmpdir))
85+
validate_path.assert_called_with(path, str(tmpdir))
86+
87+
def it_rejects_files(tmpdir):
88+
tmpdir.join('file').write('content')
89+
with raises(ValidationError) as excinfo:
90+
validate_directory('file', str(tmpdir))
91+
assert excinfo.value.code == INVALID_DIRECTORY
92+
93+
94+
def describe_validate_file():
95+
96+
def it_uses_validate_path(tmpdir):
97+
patcher = patch('readthedocs.config.validation.validate_path')
98+
with patcher as validate_path:
99+
path = tmpdir.join('a file')
100+
path.write('content')
101+
path = str(path)
102+
validate_path.return_value = path
103+
validate_file(path, str(tmpdir))
104+
validate_path.assert_called_with(path, str(tmpdir))
105+
106+
def it_rejects_directories(tmpdir):
107+
tmpdir.mkdir('directory')
108+
with raises(ValidationError) as excinfo:
109+
validate_file('directory', str(tmpdir))
110+
assert excinfo.value.code == INVALID_FILE
111+
112+
113+
def describe_validate_path():
114+
115+
def it_accepts_relative_path(tmpdir):
116+
tmpdir.mkdir('a directory')
117+
validate_path('a directory', str(tmpdir))
118+
119+
def it_accepts_files(tmpdir):
120+
tmpdir.join('file').write('content')
121+
validate_path('file', str(tmpdir))
122+
123+
def it_accepts_absolute_path(tmpdir):
124+
path = str(tmpdir.mkdir('a directory'))
125+
validate_path(path, 'does not matter')
126+
127+
def it_returns_absolute_path(tmpdir):
128+
tmpdir.mkdir('a directory')
129+
path = validate_path('a directory', str(tmpdir))
130+
assert path == os.path.abspath(path)
131+
132+
def it_only_accepts_strings():
133+
with raises(ValidationError) as excinfo:
134+
validate_path(None, '')
135+
assert excinfo.value.code == INVALID_STRING
136+
137+
def it_rejects_non_existent_path(tmpdir):
138+
with raises(ValidationError) as excinfo:
139+
validate_path('does not exist', str(tmpdir))
140+
assert excinfo.value.code == INVALID_PATH
141+
142+
143+
def describe_validate_string():
144+
145+
def it_accepts_unicode():
146+
result = validate_string(u'Unicöde')
147+
assert isinstance(result, text_type)
148+
149+
def it_accepts_nonunicode():
150+
result = validate_string('Unicode')
151+
assert isinstance(result, text_type)
152+
153+
def it_rejects_float():
154+
with raises(ValidationError) as excinfo:
155+
validate_string(123.456)
156+
assert excinfo.value.code == INVALID_STRING
157+
158+
def it_rejects_none():
159+
with raises(ValidationError) as excinfo:
160+
validate_string(None)
161+
assert excinfo.value.code == INVALID_STRING

readthedocs/config/validation.py

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"""Validations for the RTD configuration file."""
2+
from __future__ import division, print_function, unicode_literals
3+
4+
import os
5+
6+
from six import string_types, text_type
7+
8+
INVALID_BOOL = 'invalid-bool'
9+
INVALID_CHOICE = 'invalid-choice'
10+
INVALID_LIST = 'invalid-list'
11+
INVALID_DIRECTORY = 'invalid-directory'
12+
INVALID_FILE = 'invalid-file'
13+
INVALID_PATH = 'invalid-path'
14+
INVALID_STRING = 'invalid-string'
15+
16+
17+
class ValidationError(Exception):
18+
19+
"""Base error for validations."""
20+
21+
messages = {
22+
INVALID_BOOL: 'expected one of (0, 1, true, false), got {value}',
23+
INVALID_CHOICE: 'expected one of ({choices}), got {value}',
24+
INVALID_DIRECTORY: '{value} is not a directory',
25+
INVALID_FILE: '{value} is not a file',
26+
INVALID_PATH: 'path {value} does not exist',
27+
INVALID_STRING: 'expected string',
28+
INVALID_LIST: 'expected list',
29+
}
30+
31+
def __init__(self, value, code, format_kwargs=None):
32+
self.value = value
33+
self.code = code
34+
defaults = {
35+
'value': value,
36+
}
37+
if format_kwargs is not None:
38+
defaults.update(format_kwargs)
39+
message = self.messages[code].format(**defaults)
40+
super(ValidationError, self).__init__(message)
41+
42+
43+
def validate_list(value):
44+
"""Check if ``value`` is an iterable."""
45+
if isinstance(value, str):
46+
raise ValidationError(value, INVALID_LIST)
47+
if not hasattr(value, '__iter__'):
48+
raise ValidationError(value, INVALID_LIST)
49+
return list(value)
50+
51+
52+
def validate_choice(value, choices):
53+
"""Check that ``value`` is in ``choices``."""
54+
choices = validate_list(choices)
55+
if value not in choices:
56+
raise ValidationError(value, INVALID_CHOICE, {
57+
'choices': ', '.join(map(str, choices))
58+
})
59+
return value
60+
61+
62+
def validate_bool(value):
63+
"""Check that ``value`` is an boolean value."""
64+
if value not in (0, 1, False, True):
65+
raise ValidationError(value, INVALID_BOOL)
66+
return bool(value)
67+
68+
69+
def validate_directory(value, base_path):
70+
"""Check that ``value`` is a directory."""
71+
path = validate_path(value, base_path)
72+
if not os.path.isdir(path):
73+
raise ValidationError(value, INVALID_DIRECTORY)
74+
return path
75+
76+
77+
def validate_file(value, base_path):
78+
"""Check that ``value`` is a file."""
79+
path = validate_path(value, base_path)
80+
if not os.path.isfile(path):
81+
raise ValidationError(value, INVALID_FILE)
82+
return path
83+
84+
85+
def validate_path(value, base_path):
86+
"""Check that ``value`` is an existent file in ``base_path``."""
87+
string_value = validate_string(value)
88+
pathed_value = os.path.join(base_path, string_value)
89+
final_value = os.path.abspath(pathed_value)
90+
if not os.path.exists(final_value):
91+
raise ValidationError(value, INVALID_PATH)
92+
return final_value
93+
94+
95+
def validate_string(value):
96+
"""Check that ``value`` is a string type."""
97+
if not isinstance(value, string_types):
98+
raise ValidationError(value, INVALID_STRING)
99+
return text_type(value)

readthedocs/rtd_tests/tests/test_build_config.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import pytest
66
import six
77
import yamale
8-
from readthedocs_build.testing import utils
8+
from readthedocs.config.tests import utils
99
from yamale.validators import DefaultValidators, Validator
1010

1111
V2_SCHEMA = path.join(

requirements/testing.txt

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
django-dynamic-fixture==2.0.0
44

5-
pytest<4,>=3.3.2
5+
# 3.6.1 and >3.2.5 is incompatible
6+
# with pytest-describe 0.11.0
7+
pytest==3.2.5
68
pytest-django==3.1.2
9+
pytest-describe==0.11.0
710
pytest-xdist==1.22.0
811
apipkg==1.4
912
execnet==1.5.0

0 commit comments

Comments
 (0)