mirror of
https://github.com/mkdocs/mkdocs.git
synced 2026-03-27 18:08:31 +07:00
603 lines
21 KiB
Python
603 lines
21 KiB
Python
#!/usr/bin/env python
|
|
|
|
import dataclasses
|
|
import datetime
|
|
import logging
|
|
import os
|
|
import posixpath
|
|
import stat
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
from mkdocs import exceptions, utils
|
|
from mkdocs.tests.base import dedent, tempdir
|
|
from mkdocs.utils import meta
|
|
|
|
BASEYML = """
|
|
INHERIT: parent.yml
|
|
foo: bar
|
|
baz:
|
|
sub1: replaced
|
|
sub3: new
|
|
deep1:
|
|
deep2-1:
|
|
deep3-1: replaced
|
|
"""
|
|
PARENTYML = """
|
|
foo: foo
|
|
baz:
|
|
sub1: 1
|
|
sub2: 2
|
|
deep1:
|
|
deep2-1:
|
|
deep3-1: foo
|
|
deep3-2: bar
|
|
deep2-2: baz
|
|
"""
|
|
|
|
|
|
class UtilsTests(unittest.TestCase):
|
|
def test_is_markdown_file(self):
|
|
expected_results = {
|
|
'index.md': True,
|
|
'index.markdown': True,
|
|
'index.MARKDOWN': False,
|
|
'index.txt': False,
|
|
'indexmd': False,
|
|
}
|
|
for path, expected_result in expected_results.items():
|
|
with self.subTest(path):
|
|
is_markdown = utils.is_markdown_file(path)
|
|
self.assertEqual(is_markdown, expected_result)
|
|
|
|
def test_get_relative_url(self):
|
|
for case in [
|
|
dict(url='foo/bar', other='foo', expected='bar'),
|
|
dict(url='foo/bar.txt', other='foo', expected='bar.txt'),
|
|
dict(url='foo', other='foo/bar', expected='..'),
|
|
dict(url='foo', other='foo/bar.txt', expected='.'),
|
|
dict(url='foo/../../bar', other='.', expected='bar'),
|
|
dict(url='foo/../../bar', other='foo', expected='../bar'),
|
|
dict(url='foo//./bar/baz', other='foo/bar/baz', expected='.'),
|
|
dict(url='a/b/.././../c', other='.', expected='c'),
|
|
dict(url='a/b/c/d/ee', other='a/b/c/d/e', expected='../ee'),
|
|
dict(url='a/b/c/d/ee', other='a/b/z/d/e', expected='../../../c/d/ee'),
|
|
dict(url='foo', other='bar.', expected='foo'),
|
|
dict(url='foo', other='bar./', expected='../foo'),
|
|
dict(url='foo', other='foo/bar./', expected='..'),
|
|
dict(url='foo', other='foo/bar./.', expected='..'),
|
|
dict(url='foo', other='foo/bar././', expected='..'),
|
|
dict(url='foo/', other='foo/bar././', expected='../'),
|
|
dict(url='foo', other='foo', expected='.'),
|
|
dict(url='.foo', other='.foo', expected='.foo'),
|
|
dict(url='.foo/', other='.foo', expected='.foo/'),
|
|
dict(url='.foo', other='.foo/', expected='.'),
|
|
dict(url='.foo/', other='.foo/', expected='./'),
|
|
dict(url='///', other='', expected='./'),
|
|
dict(url='a///', other='', expected='a/'),
|
|
dict(url='a///', other='a', expected='./'),
|
|
dict(url='.', other='here', expected='..'),
|
|
dict(url='..', other='here', expected='..'),
|
|
dict(url='../..', other='here', expected='..'),
|
|
dict(url='../../a', other='here', expected='../a'),
|
|
dict(url='..', other='here.txt', expected='.'),
|
|
dict(url='a', other='', expected='a'),
|
|
dict(url='a', other='..', expected='a'),
|
|
dict(url='a', other='b', expected='../a'),
|
|
# The dots are considered a file. Documenting a long-standing bug:
|
|
dict(url='a', other='b/..', expected='../a'),
|
|
dict(url='a', other='b/../..', expected='a'),
|
|
dict(url='a/..../b', other='a/../b', expected='../a/..../b'),
|
|
dict(url='a/я/b', other='a/я/c', expected='../b'),
|
|
dict(url='a/я/b', other='a/яя/c', expected='../../я/b'),
|
|
]:
|
|
url, other, expected = case['url'], case['other'], case['expected']
|
|
with self.subTest(url=url, other=other):
|
|
# Leading slash intentionally ignored
|
|
self.assertEqual(utils.get_relative_url(url, other), expected)
|
|
self.assertEqual(utils.get_relative_url('/' + url, other), expected)
|
|
self.assertEqual(utils.get_relative_url(url, '/' + other), expected)
|
|
self.assertEqual(utils.get_relative_url('/' + url, '/' + other), expected)
|
|
|
|
def test_get_relative_url_empty(self):
|
|
for url in ['', '.', '/.']:
|
|
for other in ['', '.', '/', '/.']:
|
|
with self.subTest(url=url, other=other):
|
|
self.assertEqual(utils.get_relative_url(url, other), '.')
|
|
|
|
self.assertEqual(utils.get_relative_url('/', ''), './')
|
|
self.assertEqual(utils.get_relative_url('/', '/'), './')
|
|
self.assertEqual(utils.get_relative_url('/', '.'), './')
|
|
self.assertEqual(utils.get_relative_url('/', '/.'), './')
|
|
|
|
def test_normalize_url(self):
|
|
def test(path, base, expected):
|
|
self.assertEqual(utils.normalize_url(path, _Page(base)), expected)
|
|
|
|
# Absolute paths and anchors are unaffected.
|
|
for base in 'about.html', 'foo/bar.html', 'index.html', '', 'about/', 'foo/bar/':
|
|
test('https://media.cdn.org/jq.js', base, 'https://media.cdn.org/jq.js')
|
|
test('http://media.cdn.org/jquery.js', base, 'http://media.cdn.org/jquery.js')
|
|
test('//media.cdn.org/jquery.js', base, '//media.cdn.org/jquery.js')
|
|
test('#some_id', base, '#some_id')
|
|
|
|
path = 'media.cdn.org/jquery.js'
|
|
test(path, '', 'media.cdn.org/jquery.js')
|
|
test(path, 'index.html', 'media.cdn.org/jquery.js')
|
|
test(path, 'about.html', 'media.cdn.org/jquery.js')
|
|
test(path, 'about/', '../media.cdn.org/jquery.js')
|
|
test(path, 'foo/bar.html', '../media.cdn.org/jquery.js')
|
|
test(path, 'foo/bar/', '../../media.cdn.org/jquery.js')
|
|
|
|
path = 'local/file/jquery.js'
|
|
test(path, '', 'local/file/jquery.js')
|
|
test(path, 'index.html', 'local/file/jquery.js')
|
|
test(path, 'about.html', 'local/file/jquery.js')
|
|
test(path, 'about/', '../local/file/jquery.js')
|
|
test(path, 'foo/bar.html', '../local/file/jquery.js')
|
|
test(path, 'foo/bar/', '../../local/file/jquery.js')
|
|
|
|
path = '../../../../above/jquery.js'
|
|
test(path, '', '../../../../above/jquery.js')
|
|
test(path, 'index.html', '../../../../above/jquery.js')
|
|
test(path, 'about.html', '../../../../above/jquery.js')
|
|
test(path, 'about/', '../../../../../above/jquery.js')
|
|
test(path, 'foo/bar.html', '../../../../../above/jquery.js')
|
|
test(path, 'foo/bar/', '../../../../../../above/jquery.js')
|
|
|
|
path = '../some/dir/'
|
|
test(path, '', '../some/dir/')
|
|
test(path, 'index.html', '../some/dir/')
|
|
test(path, 'about.html', '../some/dir/')
|
|
test(path, 'about/', '../../some/dir/')
|
|
test(path, 'foo/bar.html', '../../some/dir/')
|
|
test(path, 'foo/bar/', '../../../some/dir/')
|
|
|
|
path = 'image.png'
|
|
test(path, '', 'image.png')
|
|
test(path, 'index.html', 'image.png')
|
|
test(path, 'about.html', 'image.png')
|
|
test(path, 'about/', '../image.png')
|
|
test(path, 'foo/bar.html', '../image.png')
|
|
test(path, 'foo/bar/', '../../image.png')
|
|
|
|
path = 'style.css?v=20180308c'
|
|
test(path, '', 'style.css?v=20180308c')
|
|
test(path, 'index.html', 'style.css?v=20180308c')
|
|
test(path, 'about.html', 'style.css?v=20180308c')
|
|
test(path, 'about/', '../style.css?v=20180308c')
|
|
test(path, 'foo/bar.html', '../style.css?v=20180308c')
|
|
test(path, 'foo/bar/', '../../style.css?v=20180308c')
|
|
|
|
# TODO: This shouldn't pass on Linux
|
|
# @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
|
|
def test_normalize_url_windows(self):
|
|
def test(path, base, expected):
|
|
self.assertEqual(utils.normalize_url(path, _Page(base)), expected)
|
|
|
|
with self.assertLogs('mkdocs', level='WARNING'):
|
|
path = 'local\\windows\\file\\jquery.js'
|
|
test(path, '', 'local/windows/file/jquery.js')
|
|
test(path, 'about/', '../local/windows/file/jquery.js')
|
|
test(path, 'foo/bar/', '../../local/windows/file/jquery.js')
|
|
|
|
def test_reduce_list(self):
|
|
self.assertEqual(
|
|
utils.reduce_list([1, 2, 3, 4, 5, 5, 2, 4, 6, 7, 8]),
|
|
[1, 2, 3, 4, 5, 6, 7, 8],
|
|
)
|
|
|
|
def test_insort(self):
|
|
a = [1, 2, 3]
|
|
utils.insort(a, 5)
|
|
self.assertEqual(a, [1, 2, 3, 5])
|
|
utils.insort(a, -1)
|
|
self.assertEqual(a, [-1, 1, 2, 3, 5])
|
|
utils.insort(a, 2)
|
|
self.assertEqual(a, [-1, 1, 2, 2, 3, 5])
|
|
utils.insort(a, 4)
|
|
self.assertEqual(a, [-1, 1, 2, 2, 3, 4, 5])
|
|
|
|
def test_insort_key(self):
|
|
a = [(1, 'a'), (1, 'b'), (2, 'c')]
|
|
utils.insort(a, (1, 'a'), key=lambda v: v[0])
|
|
self.assertEqual(a, [(1, 'a'), (1, 'b'), (1, 'a'), (2, 'c')])
|
|
|
|
def test_nest_paths(self, j=posixpath.join):
|
|
result = utils.nest_paths(
|
|
[
|
|
'index.md',
|
|
j('user-guide', 'configuration.md'),
|
|
j('user-guide', 'styling-your-docs.md'),
|
|
j('user-guide', 'writing-your-docs.md'),
|
|
j('about', 'contributing.md'),
|
|
j('about', 'license.md'),
|
|
j('about', 'release-notes.md'),
|
|
]
|
|
)
|
|
|
|
self.assertEqual(
|
|
result,
|
|
[
|
|
'index.md',
|
|
{
|
|
'User guide': [
|
|
j('user-guide', 'configuration.md'),
|
|
j('user-guide', 'styling-your-docs.md'),
|
|
j('user-guide', 'writing-your-docs.md'),
|
|
]
|
|
},
|
|
{
|
|
'About': [
|
|
j('about', 'contributing.md'),
|
|
j('about', 'license.md'),
|
|
j('about', 'release-notes.md'),
|
|
]
|
|
},
|
|
],
|
|
)
|
|
|
|
def test_nest_paths_native(self):
|
|
self.test_nest_paths(os.path.join)
|
|
|
|
def test_unicode_yaml(self):
|
|
yaml_src = dedent(
|
|
'''
|
|
key: value
|
|
key2:
|
|
- value
|
|
'''
|
|
)
|
|
|
|
config = utils.yaml_load(yaml_src)
|
|
self.assertTrue(isinstance(config['key'], str))
|
|
self.assertTrue(isinstance(config['key2'][0], str))
|
|
|
|
@mock.patch.dict(os.environ, {'VARNAME': 'Hello, World!', 'BOOLVAR': 'false'})
|
|
def test_env_var_in_yaml(self):
|
|
yaml_src = dedent(
|
|
'''
|
|
key1: !ENV VARNAME
|
|
key2: !ENV UNDEFINED
|
|
key3: !ENV [UNDEFINED, default]
|
|
key4: !ENV [UNDEFINED, VARNAME, default]
|
|
key5: !ENV BOOLVAR
|
|
'''
|
|
)
|
|
config = utils.yaml_load(yaml_src)
|
|
self.assertEqual(config['key1'], 'Hello, World!')
|
|
self.assertIsNone(config['key2'])
|
|
self.assertEqual(config['key3'], 'default')
|
|
self.assertEqual(config['key4'], 'Hello, World!')
|
|
self.assertIs(config['key5'], False)
|
|
|
|
@tempdir(files={'base.yml': BASEYML, 'parent.yml': PARENTYML})
|
|
def test_yaml_inheritance(self, tdir):
|
|
expected = {
|
|
'foo': 'bar',
|
|
'baz': {
|
|
'sub1': 'replaced',
|
|
'sub2': 2,
|
|
'sub3': 'new',
|
|
},
|
|
'deep1': {
|
|
'deep2-1': {
|
|
'deep3-1': 'replaced',
|
|
'deep3-2': 'bar',
|
|
},
|
|
'deep2-2': 'baz',
|
|
},
|
|
}
|
|
with open(os.path.join(tdir, 'base.yml')) as fd:
|
|
result = utils.yaml_load(fd)
|
|
self.assertEqual(result, expected)
|
|
|
|
@tempdir(files={'base.yml': BASEYML})
|
|
def test_yaml_inheritance_missing_parent(self, tdir):
|
|
with open(os.path.join(tdir, 'base.yml')) as fd:
|
|
with self.assertRaises(exceptions.ConfigurationError):
|
|
utils.yaml_load(fd)
|
|
|
|
@tempdir()
|
|
@tempdir()
|
|
def test_copy_files(self, src_dir, dst_dir):
|
|
cases = [
|
|
dict(
|
|
src_path='foo.txt',
|
|
dst_path='foo.txt',
|
|
expected='foo.txt',
|
|
),
|
|
dict(
|
|
src_path='bar.txt',
|
|
dst_path='foo/', # ensure src filename is appended
|
|
expected='foo/bar.txt',
|
|
),
|
|
dict(
|
|
src_path='baz.txt',
|
|
dst_path='foo/bar/baz.txt', # ensure missing dirs are created
|
|
expected='foo/bar/baz.txt',
|
|
),
|
|
]
|
|
|
|
for case in cases:
|
|
src, dst, expected = case['src_path'], case['dst_path'], case['expected']
|
|
with self.subTest(src):
|
|
src = os.path.join(src_dir, src)
|
|
with open(src, 'w') as f:
|
|
f.write('content')
|
|
dst = os.path.join(dst_dir, dst)
|
|
utils.copy_file(src, dst)
|
|
self.assertTrue(os.path.isfile(os.path.join(dst_dir, expected)))
|
|
|
|
@tempdir()
|
|
@tempdir()
|
|
def test_copy_files_without_permissions(self, src_dir, dst_dir):
|
|
cases = [
|
|
dict(src_path='foo.txt', expected='foo.txt'),
|
|
dict(src_path='bar.txt', expected='bar.txt'),
|
|
dict(src_path='baz.txt', expected='baz.txt'),
|
|
]
|
|
|
|
try:
|
|
for case in cases:
|
|
src, expected = case['src_path'], case['expected']
|
|
with self.subTest(src):
|
|
src = os.path.join(src_dir, src)
|
|
with open(src, 'w') as f:
|
|
f.write('content')
|
|
# Set src file to read-only
|
|
os.chmod(src, stat.S_IRUSR)
|
|
utils.copy_file(src, dst_dir)
|
|
self.assertTrue(os.path.isfile(os.path.join(dst_dir, expected)))
|
|
self.assertNotEqual(
|
|
os.stat(src).st_mode, os.stat(os.path.join(dst_dir, expected)).st_mode
|
|
)
|
|
# While src was read-only, dst must remain writable
|
|
self.assertTrue(os.access(os.path.join(dst_dir, expected), os.W_OK))
|
|
finally:
|
|
for case in cases:
|
|
# Undo read-only so we can delete temp files
|
|
src = os.path.join(src_dir, case['src_path'])
|
|
if os.path.exists(src):
|
|
os.chmod(src, stat.S_IRUSR | stat.S_IWUSR)
|
|
|
|
def test_mm_meta_data(self):
|
|
doc = dedent(
|
|
"""
|
|
Title: Foo Bar
|
|
Date: 2018-07-10
|
|
Summary: Line one
|
|
Line two
|
|
Tags: foo
|
|
Tags: bar
|
|
|
|
Doc body
|
|
"""
|
|
)
|
|
self.assertEqual(
|
|
meta.get_data(doc),
|
|
(
|
|
"Doc body",
|
|
{
|
|
'title': 'Foo Bar',
|
|
'date': '2018-07-10',
|
|
'summary': 'Line one Line two',
|
|
'tags': 'foo bar',
|
|
},
|
|
),
|
|
)
|
|
|
|
def test_mm_meta_data_blank_first_line(self):
|
|
doc = '\nfoo: bar\nDoc body'
|
|
self.assertEqual(meta.get_data(doc), (doc.lstrip(), {}))
|
|
|
|
def test_yaml_meta_data(self):
|
|
doc = dedent(
|
|
"""
|
|
---
|
|
Title: Foo Bar
|
|
Date: 2018-07-10
|
|
Summary: Line one
|
|
Line two
|
|
Tags:
|
|
- foo
|
|
- bar
|
|
---
|
|
Doc body
|
|
"""
|
|
)
|
|
self.assertEqual(
|
|
meta.get_data(doc),
|
|
(
|
|
"Doc body",
|
|
{
|
|
'Title': 'Foo Bar',
|
|
'Date': datetime.date(2018, 7, 10),
|
|
'Summary': 'Line one Line two',
|
|
'Tags': ['foo', 'bar'],
|
|
},
|
|
),
|
|
)
|
|
|
|
def test_yaml_meta_data_not_dict(self):
|
|
doc = dedent(
|
|
"""
|
|
---
|
|
- List item
|
|
---
|
|
Doc body
|
|
"""
|
|
)
|
|
self.assertEqual(meta.get_data(doc), (doc, {}))
|
|
|
|
def test_yaml_meta_data_invalid(self):
|
|
doc = dedent(
|
|
"""
|
|
---
|
|
foo: bar: baz
|
|
---
|
|
Doc body
|
|
"""
|
|
)
|
|
self.assertEqual(meta.get_data(doc), (doc, {}))
|
|
|
|
def test_no_meta_data(self):
|
|
doc = dedent(
|
|
"""
|
|
Doc body
|
|
"""
|
|
)
|
|
self.assertEqual(meta.get_data(doc), (doc, {}))
|
|
|
|
|
|
class ThemeUtilsTests(unittest.TestCase):
|
|
def setUp(self):
|
|
utils.get_themes.cache_clear()
|
|
|
|
def test_get_themes(self):
|
|
themes = utils.get_theme_names()
|
|
self.assertIn('mkdocs', themes)
|
|
self.assertIn('readthedocs', themes)
|
|
|
|
@mock.patch('mkdocs.utils.entry_points', autospec=True)
|
|
def test_get_theme_dir(self, mock_iter):
|
|
path = 'some/path'
|
|
|
|
theme = mock.Mock()
|
|
theme.name = 'mkdocs2'
|
|
theme.dist.name = 'mkdocs2'
|
|
theme.load().__file__ = os.path.join(path, '__init__.py')
|
|
|
|
mock_iter.return_value = [theme]
|
|
|
|
self.assertEqual(utils.get_theme_dir(theme.name), os.path.abspath(path))
|
|
|
|
def test_get_theme_dir_error(self):
|
|
with self.assertRaises(KeyError):
|
|
utils.get_theme_dir('nonexistanttheme')
|
|
|
|
@mock.patch('mkdocs.utils.entry_points', autospec=True)
|
|
def test_get_theme_dir_importerror(self, mock_iter):
|
|
theme = mock.Mock()
|
|
theme.name = 'mkdocs2'
|
|
theme.dist.name = 'mkdocs2'
|
|
theme.load.side_effect = ImportError()
|
|
|
|
mock_iter.return_value = [theme]
|
|
|
|
with self.assertRaises(ImportError):
|
|
utils.get_theme_dir(theme.name)
|
|
|
|
@mock.patch('mkdocs.utils.entry_points', autospec=True)
|
|
def test_get_themes_warning(self, mock_iter):
|
|
theme1 = mock.Mock()
|
|
theme1.name = 'mkdocs2'
|
|
theme1.dist.name = 'mkdocs2'
|
|
theme1.load().__file__ = "some/path1"
|
|
|
|
theme2 = mock.Mock()
|
|
theme2.name = 'mkdocs2'
|
|
theme2.dist.name = 'mkdocs3'
|
|
theme2.load().__file__ = "some/path2"
|
|
|
|
mock_iter.return_value = [theme1, theme2]
|
|
|
|
with self.assertLogs('mkdocs') as cm:
|
|
theme_names = utils.get_theme_names()
|
|
self.assertEqual(
|
|
'\n'.join(cm.output),
|
|
"WARNING:mkdocs.utils:A theme named 'mkdocs2' is provided by the Python "
|
|
"packages 'mkdocs3' and 'mkdocs2'. The one in 'mkdocs3' will be used.",
|
|
)
|
|
self.assertCountEqual(theme_names, ['mkdocs2'])
|
|
|
|
@mock.patch('mkdocs.utils.entry_points', autospec=True)
|
|
def test_get_themes_error(self, mock_iter):
|
|
theme1 = mock.Mock()
|
|
theme1.name = 'mkdocs'
|
|
theme1.dist.name = 'mkdocs'
|
|
theme1.load().__file__ = "some/path1"
|
|
|
|
theme2 = mock.Mock()
|
|
theme2.name = 'mkdocs'
|
|
theme2.dist.name = 'mkdocs2'
|
|
theme2.load().__file__ = "some/path2"
|
|
|
|
mock_iter.return_value = [theme1, theme2]
|
|
|
|
with self.assertRaisesRegex(
|
|
exceptions.ConfigurationError,
|
|
"The theme 'mkdocs' is a builtin theme but the package 'mkdocs2' "
|
|
"attempts to provide a theme with the same name.",
|
|
):
|
|
utils.get_theme_names()
|
|
|
|
|
|
class LogCounterTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.log = logging.getLogger('dummy')
|
|
self.log.propagate = False
|
|
self.log.setLevel(1)
|
|
self.counter = utils.CountHandler()
|
|
self.log.addHandler(self.counter)
|
|
|
|
def tearDown(self):
|
|
self.log.removeHandler(self.counter)
|
|
|
|
def test_default_values(self):
|
|
self.assertEqual(self.counter.get_counts(), [])
|
|
|
|
def test_count_critical(self):
|
|
self.assertEqual(self.counter.get_counts(), [])
|
|
self.log.critical('msg')
|
|
self.assertEqual(self.counter.get_counts(), [('CRITICAL', 1)])
|
|
|
|
def test_count_error(self):
|
|
self.assertEqual(self.counter.get_counts(), [])
|
|
self.log.error('msg')
|
|
self.assertEqual(self.counter.get_counts(), [('ERROR', 1)])
|
|
|
|
def test_count_warning(self):
|
|
self.assertEqual(self.counter.get_counts(), [])
|
|
self.log.warning('msg')
|
|
self.assertEqual(self.counter.get_counts(), [('WARNING', 1)])
|
|
|
|
def test_count_info(self):
|
|
self.assertEqual(self.counter.get_counts(), [])
|
|
self.log.info('msg')
|
|
self.assertEqual(self.counter.get_counts(), [('INFO', 1)])
|
|
|
|
def test_count_debug(self):
|
|
self.assertEqual(self.counter.get_counts(), [])
|
|
self.log.debug('msg')
|
|
self.assertEqual(self.counter.get_counts(), [('DEBUG', 1)])
|
|
|
|
def test_count_multiple(self):
|
|
self.assertEqual(self.counter.get_counts(), [])
|
|
self.log.warning('msg 1')
|
|
self.assertEqual(self.counter.get_counts(), [('WARNING', 1)])
|
|
self.log.warning('msg 2')
|
|
self.assertEqual(self.counter.get_counts(), [('WARNING', 2)])
|
|
self.log.debug('msg 3')
|
|
self.assertEqual(self.counter.get_counts(), [('WARNING', 2), ('DEBUG', 1)])
|
|
self.log.error('mdg 4')
|
|
self.assertEqual(self.counter.get_counts(), [('ERROR', 1), ('WARNING', 2), ('DEBUG', 1)])
|
|
|
|
def test_log_level(self):
|
|
self.assertEqual(self.counter.get_counts(), [])
|
|
self.counter.setLevel(logging.ERROR)
|
|
self.log.error('counted')
|
|
self.log.warning('not counted')
|
|
self.log.info('not counted')
|
|
self.assertEqual(self.counter.get_counts(), [('ERROR', 1)])
|
|
self.counter.setLevel(logging.WARNING)
|
|
self.log.error('counted')
|
|
self.log.warning('counted')
|
|
self.log.info('not counted')
|
|
self.assertEqual(self.counter.get_counts(), [('ERROR', 2), ('WARNING', 1)])
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class _Page:
|
|
url: str
|