Skip to content

Commit 8273477

Browse files
authored
Switch to src layout and move tests to their own directory
- src/ layout seems to be the modern standard, and avoids e.g. false-positive success issues (where tests get run agains the source instead of the packaged library, and thus hide packaging issues): https://hynek.me/articles/testing-packaging/ - remove use of unittest entirely (switch everything to pytest, not just runner), also move doctesting to pytest - moving tests out avoids packaging them, and mucking up the source dir with more test files in the future - replace old test command by an invocation of tox The only thing that's lost is `setup.py check`, but turns out: - it only checks that `long_description` is valid and we don't use that - it wasn't being run on CI - invoking `setup.py` directly is getting deprecated
1 parent a8d45b6 commit 8273477

File tree

7 files changed

+85
-120
lines changed

7 files changed

+85
-120
lines changed

.github/workflows/ci.yml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ jobs:
7070
- name: Check results
7171
run: |
7272
# check that _regexes exists, and .eggs does not (== setuptools used our dependency)
73-
test -e ua_parser/_regexes.py -a ! -e .eggs
73+
test -e src/ua_parser/_regexes.py -a ! -e .eggs
7474
7575
test:
7676
strategy:
@@ -95,6 +95,8 @@ jobs:
9595
steps:
9696
- name: Checkout working copy
9797
uses: actions/checkout@v3
98+
with:
99+
submodules: true
98100
- name: Set up Python ${{ matrix.python-version }}
99101
uses: actions/setup-python@v3
100102
with:
@@ -111,11 +113,12 @@ jobs:
111113
fi
112114
python -mpip install -r requirements_dev.txt
113115
- name: install package in environment
114-
run: python setup.py develop
116+
run: pip install .
115117
- name: run tests
116-
#
117-
run: pytest -v -Werror -Wignore::ImportWarning
118-
- name: run doctests
119-
# pprint formatting was changed a lot in 3.5
120118
if: ${{ matrix.python-version != '2.7' }}
121-
run: python -mdoctest README.rst
119+
run: pytest -v -Werror -Wignore::ImportWarning --doctest-glob="*.rst"
120+
- name: run tests
121+
# pprint formatting was widely altered in 3.5, so can't run
122+
# doctests before then
123+
if: ${{ matrix.python-version == '2.7' }}
124+
run: pytest -v -Werror -Wignore::ImportWarning

Makefile

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
11
all: test
22

3-
test: clean
4-
@mkdir -p tmp
5-
@PYTHONPATH=tmp python setup.py develop -d tmp
6-
@# run all tests
7-
@PYTHONPATH=tmp python ua_parser/user_agent_parser_test.py
8-
@# run a single test
9-
@#PYTHONPATH=tmp python ua_parser/user_agent_parser_test.py ParseTest.testStringsDeviceBrandModel
3+
test:
4+
tox
105

116
clean:
127
@find . -name '*.pyc' -delete
138
@rm -rf tmp \
14-
ua_parser.egg-info \
9+
src/ua_parser.egg-info \
1510
dist \
1611
build \
17-
ua_parser/_regexes.py
12+
src/ua_parser/_regexes.py
1813
format:
1914
@black .
2015

setup.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from distutils import log
55
from distutils.core import Command
66
from distutils.command.build import build as _build
7-
from setuptools import setup
7+
from setuptools import setup, find_packages
88
from setuptools.command.develop import develop as _develop
99
from setuptools.command.sdist import sdist as _sdist
1010
from setuptools.command.install import install as _install
@@ -56,7 +56,7 @@ def finalize_options(self):
5656
)
5757

5858
if self.inplace:
59-
self.build_lib = "."
59+
self.build_lib = "./src"
6060
else:
6161
self.set_undefined_options("build", ("build_lib", "build_lib"))
6262
if self.work_path is None:
@@ -164,7 +164,7 @@ def update_manifest(self):
164164
if not sdist.finalized:
165165
return
166166

167-
sdist.filelist.files.append("ua_parser/_regexes.py")
167+
sdist.filelist.files.append("src/ua_parser/_regexes.py")
168168

169169

170170
class develop(_develop):
@@ -204,8 +204,8 @@ class sdist(_sdist):
204204
description="Python port of Browserscope's user agent parser",
205205
author="PBS",
206206
author_email="[email protected]",
207-
packages=["ua_parser"],
208-
package_dir={"": "."},
207+
packages=find_packages(where="src"),
208+
package_dir={"": "src"},
209209
license="Apache 2.0",
210210
zip_safe=False,
211211
url="https://github.com/ua-parser/uap-python",
File renamed without changes.
File renamed without changes.

ua_parser/user_agent_parser_test.py renamed to tests/test_legacy.py

Lines changed: 63 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@
1414

1515

1616
"""User Agent Parser Unit Tests.
17-
Run:
18-
# python -m user_agent_parser_test (runs all the tests, takes awhile)
19-
or like:
20-
# python -m user_agent_parser_test ParseTest.testBrowserscopeStrings
2117
"""
2218

2319

@@ -30,8 +26,9 @@
3026
import platform
3127
import re
3228
import sys
33-
import unittest
3429
import warnings
30+
31+
import pytest
3532
import yaml
3633

3734
if platform.python_implementation() == "PyPy":
@@ -54,7 +51,7 @@
5451
)
5552

5653

57-
class ParseTest(unittest.TestCase):
54+
class ParseTest:
5855
def testBrowserscopeStrings(self):
5956
self.runUserAgentTestsFromYAML(
6057
os.path.join(TEST_RESOURCES_DIR, "tests/test_ua.yaml")
@@ -112,12 +109,8 @@ def testParseAll(self):
112109
}
113110

114111
result = user_agent_parser.Parse(user_agent_string)
115-
self.assertEqual(
116-
result,
117-
expected,
118-
"UA: {0}\n expected<{1}> != actual<{2}>".format(
119-
user_agent_string, expected, result
120-
),
112+
assert result == expected, "UA: {0}\n expected<{1}> != actual<{2}>".format(
113+
user_agent_string, expected, result
121114
)
122115

123116
# Run a set of test cases from a YAML file
@@ -140,26 +133,22 @@ def runUserAgentTestsFromYAML(self, file_name):
140133

141134
result = {}
142135
result = user_agent_parser.ParseUserAgent(user_agent_string)
143-
self.assertEqual(
144-
result,
145-
expected,
146-
"UA: {0}\n expected<{1}, {2}, {3}, {4}> != actual<{5}, {6}, {7}, {8}>".format(
147-
user_agent_string,
148-
expected["family"],
149-
expected["major"],
150-
expected["minor"],
151-
expected["patch"],
152-
result["family"],
153-
result["major"],
154-
result["minor"],
155-
result["patch"],
156-
),
157-
)
158-
self.assertLessEqual(
159-
len(user_agent_parser._PARSE_CACHE),
160-
user_agent_parser.MAX_CACHE_SIZE,
161-
"verify that the cache size never exceeds the configured setting",
136+
assert (
137+
result == expected
138+
), "UA: {0}\n expected<{1}, {2}, {3}, {4}> != actual<{5}, {6}, {7}, {8}>".format(
139+
user_agent_string,
140+
expected["family"],
141+
expected["major"],
142+
expected["minor"],
143+
expected["patch"],
144+
result["family"],
145+
result["major"],
146+
result["minor"],
147+
result["patch"],
162148
)
149+
assert (
150+
len(user_agent_parser._PARSE_CACHE) <= user_agent_parser.MAX_CACHE_SIZE
151+
), "verify that the cache size never exceeds the configured setting"
163152

164153
def runOSTestsFromYAML(self, file_name):
165154
yamlFile = open(os.path.join(TEST_RESOURCES_DIR, file_name))
@@ -180,22 +169,20 @@ def runOSTestsFromYAML(self, file_name):
180169
}
181170

182171
result = user_agent_parser.ParseOS(user_agent_string)
183-
self.assertEqual(
184-
result,
185-
expected,
186-
"UA: {0}\n expected<{1} {2} {3} {4} {5}> != actual<{6} {7} {8} {9} {10}>".format(
187-
user_agent_string,
188-
expected["family"],
189-
expected["major"],
190-
expected["minor"],
191-
expected["patch"],
192-
expected["patch_minor"],
193-
result["family"],
194-
result["major"],
195-
result["minor"],
196-
result["patch"],
197-
result["patch_minor"],
198-
),
172+
assert (
173+
result == expected
174+
), "UA: {0}\n expected<{1} {2} {3} {4} {5}> != actual<{6} {7} {8} {9} {10}>".format(
175+
user_agent_string,
176+
expected["family"],
177+
expected["major"],
178+
expected["minor"],
179+
expected["patch"],
180+
expected["patch_minor"],
181+
result["family"],
182+
result["major"],
183+
result["minor"],
184+
result["patch"],
185+
result["patch_minor"],
199186
)
200187

201188
def runDeviceTestsFromYAML(self, file_name):
@@ -215,35 +202,33 @@ def runDeviceTestsFromYAML(self, file_name):
215202
}
216203

217204
result = user_agent_parser.ParseDevice(user_agent_string)
218-
self.assertEqual(
219-
result,
220-
expected,
221-
"UA: {0}\n expected<{1} {2} {3}> != actual<{4} {5} {6}>".format(
222-
user_agent_string,
223-
expected["family"],
224-
expected["brand"],
225-
expected["model"],
226-
result["family"],
227-
result["brand"],
228-
result["model"],
229-
),
205+
assert (
206+
result == expected
207+
), "UA: {0}\n expected<{1} {2} {3}> != actual<{4} {5} {6}>".format(
208+
user_agent_string,
209+
expected["family"],
210+
expected["brand"],
211+
expected["model"],
212+
result["family"],
213+
result["brand"],
214+
result["model"],
230215
)
231216

232217

233-
class GetFiltersTest(unittest.TestCase):
218+
class GetFiltersTest:
234219
def testGetFiltersNoMatchesGiveEmptyDict(self):
235220
user_agent_string = "foo"
236221
filters = user_agent_parser.GetFilters(
237222
user_agent_string, js_user_agent_string=None
238223
)
239-
self.assertEqual({}, filters)
224+
assert {} == filters
240225

241226
def testGetFiltersJsUaPassedThrough(self):
242227
user_agent_string = "foo"
243228
filters = user_agent_parser.GetFilters(
244229
user_agent_string, js_user_agent_string="bar"
245230
)
246-
self.assertEqual({"js_user_agent_string": "bar"}, filters)
231+
assert {"js_user_agent_string": "bar"} == filters
247232

248233
def testGetFiltersJsUserAgentFamilyAndVersions(self):
249234
user_agent_string = (
@@ -254,38 +239,32 @@ def testGetFiltersJsUserAgentFamilyAndVersions(self):
254239
filters = user_agent_parser.GetFilters(
255240
user_agent_string, js_user_agent_string="bar", js_user_agent_family="foo"
256241
)
257-
self.assertEqual(
258-
{"js_user_agent_string": "bar", "js_user_agent_family": "foo"}, filters
259-
)
242+
assert {"js_user_agent_string": "bar", "js_user_agent_family": "foo"} == filters
260243

261244

262-
class TestDeprecationWarnings(unittest.TestCase):
245+
class TestDeprecationWarnings:
263246
def setUp(self):
264247
"""In Python 2.7, catch_warnings apparently does not do anything if
265248
the warning category is not active, whereas in 3(.6 and up) it
266249
seems to work out of the box.
267250
"""
268-
super(TestDeprecationWarnings, self).setUp()
269251
warnings.simplefilter("always", DeprecationWarning)
270252

271253
def tearDown(self):
272254
# not ideal as it discards all other warnings updates from the
273255
# process, should really copy the contents of
274256
# `warnings.filters`, then reset-it.
275257
warnings.resetwarnings()
276-
super(TestDeprecationWarnings, self).tearDown()
277258

278259
def test_parser_deprecation(self):
279-
with warnings.catch_warnings(record=True) as ws:
260+
with pytest.warns(DeprecationWarning) as ws:
280261
user_agent_parser.ParseWithJSOverrides("")
281-
self.assertEqual(len(ws), 1)
282-
self.assertEqual(ws[0].category, DeprecationWarning)
262+
assert len(ws) == 1
283263

284264
def test_printer_deprecation(self):
285-
with warnings.catch_warnings(record=True) as ws:
265+
with pytest.warns(DeprecationWarning) as ws:
286266
user_agent_parser.Pretty("")
287-
self.assertEqual(len(ws), 1)
288-
self.assertEqual(ws[0].category, DeprecationWarning)
267+
assert len(ws) == 1
289268

290269
def test_js_bits_deprecation(self):
291270
for parser, count in [
@@ -295,33 +274,28 @@ def test_js_bits_deprecation(self):
295274
(user_agent_parser.ParseDevice, 1),
296275
]:
297276
user_agent_parser._PARSE_CACHE.clear()
298-
with warnings.catch_warnings(record=True) as ws:
277+
with pytest.warns(DeprecationWarning) as ws:
299278
parser("some random thing", js_attribute=True)
300-
self.assertEqual(len(ws), count)
301-
for w in ws:
302-
self.assertEqual(w.category, DeprecationWarning)
279+
assert len(ws) == count
303280

304281

305-
class ErrTest(unittest.TestCase):
306-
@unittest.skipIf(
307-
sys.version_info < (3,), "bytes and str are not differentiated in P2"
282+
class ErrTest:
283+
@pytest.mark.skipif(
284+
sys.version_info < (3,),
285+
reason="bytes and str are not differentiated in P2",
308286
)
309287
def test_bytes(self):
310-
with self.assertRaises(TypeError):
288+
with pytest.raises(TypeError):
311289
user_agent_parser.Parse(b"")
312290

313291
def test_int(self):
314-
with self.assertRaises(TypeError):
292+
with pytest.raises(TypeError):
315293
user_agent_parser.Parse(0)
316294

317295
def test_list(self):
318-
with self.assertRaises(TypeError):
296+
with pytest.raises(TypeError):
319297
user_agent_parser.Parse([])
320298

321299
def test_tuple(self):
322-
with self.assertRaises(TypeError):
300+
with pytest.raises(TypeError):
323301
user_agent_parser.Parse(())
324-
325-
326-
if __name__ == "__main__":
327-
unittest.main()

tox.ini

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,11 @@ skipsdist = True
88
usedevelop = True
99
deps = -rrequirements_dev.txt
1010
commands =
11-
pytest -Werror {posargs}
12-
python -mdoctest README.rst
11+
pytest -Werror --doctest-glob="*.rst" {posargs}
1312

1413
[testenv:py27]
1514
# no doctesting in 2.7 because of formatting divergences
16-
commands = pytest {posargs}
17-
18-
[testenv:docs]
19-
skip_install = True
20-
deps = docutils
21-
Pygments
22-
commands = python setup.py check -s --restructuredtext --metadata
15+
commands = pytest -Werror {posargs}
2316

2417
[testenv:flake8]
2518
skip_install = True
@@ -33,4 +26,4 @@ commands = black --check --diff .
3326

3427
[flake8]
3528
max_line_length = 88
36-
filename = ua_parser/
29+
filename = src/ua_parser/

0 commit comments

Comments
 (0)