Merge pull request #5826 from pgajdos/denose

do not require nose for testing
This commit is contained in:
Kevin Bates 2020-10-29 07:17:14 -07:00 committed by GitHub
commit 72217183c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 124 additions and 134 deletions

View File

@ -19,7 +19,7 @@ env:
before_install:
- pip install --upgrade pip
- pip install --upgrade setuptools wheel nose coverage codecov
- pip install --upgrade setuptools wheel pytest pytest-cov coverage codecov
- nvm install 6.9.2
- nvm use 6.9.2
- node --version
@ -65,7 +65,7 @@ script:
true
fi
- 'if [[ $GROUP == js* ]]; then travis_retry python -m notebook.jstest ${GROUP:3}; fi'
- 'if [[ $GROUP == python ]]; then nosetests -v --exclude-dir notebook/tests/selenium --with-coverage --cover-package=notebook notebook; fi'
- 'if [[ $GROUP == python ]]; then py.test -v --ignore notebook/tests/selenium --cov=notebook notebook; fi'
- 'if [[ $GROUP == selenium ]]; then py.test -sv notebook/tests/selenium; fi'
- |
if [[ $GROUP == docs ]]; then

View File

@ -21,7 +21,7 @@ install:
- cmd: conda config --set show_channel_urls true
- cmd: conda config --add channels conda-forge
#- cmd: conda update --yes --quiet conda
- cmd: conda install -y python=%CONDA_PY_SPEC% pyzmq tornado jupyter_client nbformat ipykernel pip nodejs nose
- cmd: conda install -y python=%CONDA_PY_SPEC% pyzmq tornado jupyter_client nbformat ipykernel pip nodejs pytest nose
# not using `conda install -y` on nbconvent package because there is
# currently a bug with the version that the anaconda installs, so we will just install it with pip
- cmd: pip install nbconvert
@ -29,4 +29,4 @@ install:
- cmd: pip install .[test]
test_script:
- nosetests -v notebook --exclude-dir notebook\tests\selenium
- py.test -v notebook --ignore notebook\tests\selenium

View File

@ -1,21 +1,20 @@
from ..security import passwd, passwd_check
import nose.tools as nt
def test_passwd_structure():
p = passwd('passphrase')
algorithm, hashed = p.split(':')
nt.assert_equal(algorithm, 'argon2')
nt.assert_true(hashed.startswith('$argon2id$'))
assert algorithm == 'argon2'
assert hashed.startswith('$argon2id$')
def test_roundtrip():
p = passwd('passphrase')
nt.assert_equal(passwd_check(p, 'passphrase'), True)
assert passwd_check(p, 'passphrase') == True
def test_bad():
p = passwd('passphrase')
nt.assert_equal(passwd_check(p, p), False)
nt.assert_equal(passwd_check(p, 'a:b:c:d'), False)
nt.assert_equal(passwd_check(p, 'a:b'), False)
assert passwd_check(p, p) == False
assert passwd_check(p, 'a:b:c:d') == False
assert passwd_check(p, 'a:b') == False
def test_passwd_check_unicode():
# GH issue #4524

View File

@ -5,11 +5,11 @@
import io as stdlib_io
import os.path
import unittest
import pytest
import stat
import sys
import nose.tools as nt
from ipython_genutils.testing.decorators import skip_win32
from ..fileio import atomic_writing
from ipython_genutils.tempdir import TemporaryDirectory
@ -38,24 +38,24 @@ def test_atomic_writing():
# OSError: The user lacks the privilege (Windows)
have_symlink = False
with nt.assert_raises(CustomExc):
with pytest.raises(CustomExc):
with atomic_writing(f1) as f:
f.write(u'Failing write')
raise CustomExc
# Because of the exception, the file should not have been modified
with stdlib_io.open(f1, 'r') as f:
nt.assert_equal(f.read(), u'Before')
assert f.read() == u'Before'
with atomic_writing(f1) as f:
f.write(u'Overwritten')
with stdlib_io.open(f1, 'r') as f:
nt.assert_equal(f.read(), u'Overwritten')
assert f.read() == u'Overwritten'
if os.name != 'nt':
mode = stat.S_IMODE(os.stat(f1).st_mode)
nt.assert_equal(mode, orig_mode)
assert mode == orig_mode
if have_symlink:
# Check that writing over a file preserves a symlink
@ -63,33 +63,35 @@ def test_atomic_writing():
f.write(u'written from symlink')
with stdlib_io.open(f1, 'r') as f:
nt.assert_equal(f.read(), u'written from symlink')
assert f.read() == u'written from symlink'
def _save_umask():
global umask
umask = os.umask(0)
os.umask(umask)
class TestWithSetUmask(unittest.TestCase):
def setUp(self):
# save umask
global umask
umask = os.umask(0)
os.umask(umask)
def tearDown(self):
# restore umask
os.umask(umask)
def _restore_umask():
os.umask(umask)
@skip_win32
@nt.with_setup(_save_umask, _restore_umask)
def test_atomic_writing_umask():
with TemporaryDirectory() as td:
os.umask(0o022)
f1 = os.path.join(td, '1')
with atomic_writing(f1) as f:
f.write(u'1')
mode = stat.S_IMODE(os.stat(f1).st_mode)
nt.assert_equal(mode, 0o644, '{:o} != 644'.format(mode))
os.umask(0o057)
f2 = os.path.join(td, '2')
with atomic_writing(f2) as f:
f.write(u'2')
mode = stat.S_IMODE(os.stat(f2).st_mode)
nt.assert_equal(mode, 0o620, '{:o} != 620'.format(mode))
@pytest.mark.skipif(sys.platform == "win32", reason="do not run on windows")
def test_atomic_writing_umask(self):
with TemporaryDirectory() as td:
os.umask(0o022)
f1 = os.path.join(td, '1')
with atomic_writing(f1) as f:
f.write(u'1')
mode = stat.S_IMODE(os.stat(f1).st_mode)
assert mode == 0o644
os.umask(0o057)
f2 = os.path.join(td, '2')
with atomic_writing(f2) as f:
f.write(u'2')
mode = stat.S_IMODE(os.stat(f2).st_mode)
assert mode == 0o620
def test_atomic_writing_newlines():
@ -105,21 +107,21 @@ def test_atomic_writing_newlines():
f.write(lf)
with stdlib_io.open(path, 'r', newline='') as f:
read = f.read()
nt.assert_equal(read, plat)
assert read == plat
# test newline=LF
with stdlib_io.open(path, 'w', newline='\n') as f:
f.write(lf)
with stdlib_io.open(path, 'r', newline='') as f:
read = f.read()
nt.assert_equal(read, lf)
assert read == lf
# test newline=CRLF
with atomic_writing(path, newline='\r\n') as f:
f.write(lf)
with stdlib_io.open(path, 'r', newline='') as f:
read = f.read()
nt.assert_equal(read, crlf)
assert read == crlf
# test newline=no convert
text = u'crlf\r\ncr\rlf\n'
@ -127,4 +129,4 @@ def test_atomic_writing_newlines():
f.write(text)
with stdlib_io.open(path, 'r', newline='') as f:
read = f.read()
nt.assert_equal(read, text)
assert read == text

View File

@ -6,16 +6,14 @@ import time
from contextlib import contextmanager
from itertools import combinations
from nose import SkipTest
from tornado.web import HTTPError
from unittest import TestCase
from unittest import TestCase, skipIf
from tempfile import NamedTemporaryFile
from nbformat import v4 as nbformat
from ipython_genutils.tempdir import TemporaryDirectory
from traitlets import TraitError
from ipython_genutils.testing import decorators as dec
from ..filemanager import FileContentsManager
@ -126,7 +124,7 @@ class TestFileContentsManager(TestCase):
# broken symlinks should still be shown in the contents manager
self.assertTrue('bad symlink' in contents)
@dec.skipif(sys.platform == 'win32')
@skipIf(sys.platform == 'win32', "will not run on windows")
def test_recursive_symlink(self):
with TemporaryDirectory() as td:
cm = FileContentsManager(root_dir=td)
@ -165,13 +163,10 @@ class TestFileContentsManager(TestCase):
[symlink_model, file_model],
)
def test_403(self):
if hasattr(os, 'getuid'):
if os.getuid() == 0:
raise SkipTest("Can't test permissions as root")
if sys.platform.startswith('win'):
raise SkipTest("Can't test permissions on Windows")
@skipIf(hasattr(os, 'getuid') and os.getuid() == 0, "Can't test permissions as root")
@skipIf(sys.platform.startswith('win'), "Can't test permissions on Windows")
def test_403(self):
with TemporaryDirectory() as td:
cm = FileContentsManager(root_dir=td)
model = cm.new_untitled(type='file')

View File

@ -6,7 +6,6 @@ from datetime import datetime
from io import StringIO
from unittest.mock import patch
import nose.tools as nt
from tornado import gen
from tornado.web import HTTPError
from tornado.httpclient import HTTPRequest, HTTPResponse
@ -72,7 +71,7 @@ def mock_gateway_request(url, **kwargs):
name = json_body.get('name')
env = json_body.get('env')
kspec_name = env.get('KERNEL_KSPEC_NAME')
nt.assert_equal(name, kspec_name) # Ensure that KERNEL_ env values get propagated
assert name == kspec_name # Ensure that KERNEL_ env values get propagated
model = generate_model(name)
running_kernels[model.get('id')] = model # Register model as a running kernel
response_buf = StringIO(json.dumps(model))
@ -164,19 +163,19 @@ class TestGateway(NotebookTestBase):
super().setUp()
def test_gateway_options(self):
nt.assert_equal(self.notebook.gateway_config.gateway_enabled, True)
nt.assert_equal(self.notebook.gateway_config.url, TestGateway.mock_gateway_url)
nt.assert_equal(self.notebook.gateway_config.http_user, TestGateway.mock_http_user)
nt.assert_equal(self.notebook.gateway_config.connect_timeout, self.notebook.gateway_config.connect_timeout)
nt.assert_equal(self.notebook.gateway_config.connect_timeout, 44.4)
nt.assert_equal(self.notebook.gateway_config.request_timeout, 96.0)
nt.assert_equal(os.environ['KERNEL_LAUNCH_TIMEOUT'], str(96)) # Ensure KLT gets set from request-timeout
assert self.notebook.gateway_config.gateway_enabled == True
assert self.notebook.gateway_config.url == TestGateway.mock_gateway_url
assert self.notebook.gateway_config.http_user == TestGateway.mock_http_user
assert self.notebook.gateway_config.connect_timeout == self.notebook.gateway_config.connect_timeout
assert self.notebook.gateway_config.connect_timeout == 44.4
assert self.notebook.gateway_config.request_timeout == 96.0
assert os.environ['KERNEL_LAUNCH_TIMEOUT'] == str(96) # Ensure KLT gets set from request-timeout
def test_gateway_class_mappings(self):
# Ensure appropriate class mappings are in place.
nt.assert_equal(self.notebook.kernel_manager_class.__name__, 'GatewayKernelManager')
nt.assert_equal(self.notebook.session_manager_class.__name__, 'GatewaySessionManager')
nt.assert_equal(self.notebook.kernel_spec_manager_class.__name__, 'GatewayKernelSpecManager')
assert self.notebook.kernel_manager_class.__name__ == 'GatewayKernelManager'
assert self.notebook.session_manager_class.__name__ == 'GatewaySessionManager'
assert self.notebook.kernel_spec_manager_class.__name__ == 'GatewayKernelSpecManager'
def test_gateway_get_kernelspecs(self):
# Validate that kernelspecs come from gateway.
@ -185,19 +184,19 @@ class TestGateway(NotebookTestBase):
self.assertEqual(response.status_code, 200)
content = json.loads(response.content.decode('utf-8'))
kspecs = content.get('kernelspecs')
self.assertEqual(len(kspecs), 2)
self.assertEqual(kspecs.get('kspec_bar').get('name'), 'kspec_bar')
assert len(kspecs) == 2
assert kspecs.get('kspec_bar').get('name') == 'kspec_bar'
def test_gateway_get_named_kernelspec(self):
# Validate that a specific kernelspec can be retrieved from gateway.
with mocked_gateway:
response = self.request('GET', '/api/kernelspecs/kspec_foo')
self.assertEqual(response.status_code, 200)
assert response.status_code == 200
kspec_foo = json.loads(response.content.decode('utf-8'))
self.assertEqual(kspec_foo.get('name'), 'kspec_foo')
assert kspec_foo.get('name') == 'kspec_foo'
response = self.request('GET', '/api/kernelspecs/no_such_spec')
self.assertEqual(response.status_code, 404)
assert response.status_code == 404
def test_gateway_session_lifecycle(self):
# Validate session lifecycle functions; create and delete.

View File

@ -1,10 +1,7 @@
import nose.tools as nt
from notebook import i18n
def test_parse_accept_lang_header():
palh = i18n.parse_accept_lang_header
nt.assert_equal(palh(''), [])
nt.assert_equal(palh('zh-CN,en-GB;q=0.7,en;q=0.3'),
['en', 'en_GB', 'zh', 'zh_CN'])
nt.assert_equal(palh('nl,fr;q=0'), ['nl'])
assert palh('') == []
assert palh('zh-CN,en-GB;q=0.7,en;q=0.3') == ['en', 'en_GB', 'zh', 'zh_CN']
assert palh('nl,fr;q=0') == ['nl']

View File

@ -5,6 +5,7 @@
import glob
import os
import pytest
import sys
import tarfile
import zipfile
@ -320,7 +321,7 @@ class TestInstallNBExtension(TestCase):
assert check_nbextension([f], user=True)
assert not check_nbextension([f, pjoin('dne', f)], user=True)
@dec.skip_win32
@pytest.mark.skipif(sys.platform == "win32", reason="do not run on windows")
def test_install_symlink(self):
with TemporaryDirectory() as d:
f = u'ƒ.js'
@ -332,7 +333,7 @@ class TestInstallNBExtension(TestCase):
link = os.readlink(dest)
self.assertEqual(link, src)
@dec.skip_win32
@pytest.mark.skipif(sys.platform == "win32", reason="do not run on windows")
def test_overwrite_broken_symlink(self):
with TemporaryDirectory() as d:
f = u'ƒ.js'
@ -348,7 +349,7 @@ class TestInstallNBExtension(TestCase):
link = os.readlink(dest)
self.assertEqual(link, src2)
@dec.skip_win32
@pytest.mark.skipif(sys.platform == "win32", reason="do not run on windows")
def test_install_symlink_destination(self):
with TemporaryDirectory() as d:
f = u'ƒ.js'
@ -361,7 +362,7 @@ class TestInstallNBExtension(TestCase):
link = os.readlink(dest)
self.assertEqual(link, src)
@dec.skip_win32
@pytest.mark.skipif(sys.platform == "win32", reason="do not run on windows")
def test_install_symlink_bad(self):
with self.assertRaises(ValueError):
install_nbextension("http://example.com/foo.js", symlink=True)

View File

@ -11,7 +11,7 @@ from tempfile import NamedTemporaryFile
from unittest.mock import patch
import nose.tools as nt
import pytest
from traitlets.tests.utils import check_help_all_output
@ -37,11 +37,11 @@ def test_server_info_file():
nbapp.initialize(argv=[])
nbapp.write_server_info_file()
servers = get_servers()
nt.assert_equal(len(servers), 1)
nt.assert_equal(servers[0]['port'], nbapp.port)
nt.assert_equal(servers[0]['url'], nbapp.connection_url)
assert len(servers) == 1
assert servers[0]['port'] == nbapp.port
assert servers[0]['url'] == nbapp.connection_url
nbapp.remove_server_info_file()
nt.assert_equal(get_servers(), [])
assert get_servers() == []
# The ENOENT error should be silenced.
nbapp.remove_server_info_file()
@ -49,43 +49,43 @@ def test_server_info_file():
def test_nb_dir():
with TemporaryDirectory() as td:
app = NotebookApp(notebook_dir=td)
nt.assert_equal(app.notebook_dir, td)
assert app.notebook_dir == td
def test_no_create_nb_dir():
with TemporaryDirectory() as td:
nbdir = os.path.join(td, 'notebooks')
app = NotebookApp()
with nt.assert_raises(TraitError):
with pytest.raises(TraitError):
app.notebook_dir = nbdir
def test_missing_nb_dir():
with TemporaryDirectory() as td:
nbdir = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
app = NotebookApp()
with nt.assert_raises(TraitError):
with pytest.raises(TraitError):
app.notebook_dir = nbdir
def test_invalid_nb_dir():
with NamedTemporaryFile() as tf:
app = NotebookApp()
with nt.assert_raises(TraitError):
with pytest.raises(TraitError):
app.notebook_dir = tf
def test_nb_dir_with_slash():
with TemporaryDirectory(suffix="_slash" + os.sep) as td:
app = NotebookApp(notebook_dir=td)
nt.assert_false(app.notebook_dir.endswith(os.sep))
assert not app.notebook_dir.endswith(os.sep)
def test_nb_dir_root():
root = os.path.abspath(os.sep) # gets the right value on Windows, Posix
app = NotebookApp(notebook_dir=root)
nt.assert_equal(app.notebook_dir, root)
assert app.notebook_dir == root
def test_generate_config():
with TemporaryDirectory() as td:
app = NotebookApp(config_dir=td)
app.initialize(['--generate-config', '--allow-root'])
with nt.assert_raises(NoStart):
with pytest.raises(NoStart):
app.start()
assert os.path.exists(os.path.join(td, 'jupyter_notebook_config.py'))
@ -100,7 +100,7 @@ def test_pep440_version():
'1.2.3.dev1.post2',
]:
def loc():
with nt.assert_raises(ValueError):
with pytest.raises(ValueError):
raise_on_bad_version(version)
yield loc
@ -136,7 +136,7 @@ def test_notebook_password():
app.start()
nb = NotebookApp()
nb.load_config_file()
nt.assert_not_equal(nb.password, '')
assert nb.password != ''
passwd_check(nb.password, password)
class TestingStopApp(notebookapp.NbserverStopApp):
@ -171,17 +171,17 @@ def test_notebook_stop():
app = TestingStopApp()
app.initialize(['105'])
app.start()
nt.assert_equal(len(app.servers_shut_down), 1)
nt.assert_equal(app.servers_shut_down[0]['port'], 105)
assert len(app.servers_shut_down) == 1
assert app.servers_shut_down[0]['port'] == 105
# test no match
with mock_servers, patch('os.kill') as os_kill:
app = TestingStopApp()
app.initialize(['999'])
with nt.assert_raises(SystemExit) as exc:
with pytest.raises(SystemExit) as exc:
app.start()
nt.assert_equal(exc.exception.code, 1)
nt.assert_equal(len(app.servers_shut_down), 0)
assert exc.value.code == 1
assert len(app.servers_shut_down) == 0
class NotebookAppTests(NotebookTestBase):

View File

@ -1,6 +1,5 @@
import re
from nose.tools import assert_regex, assert_not_regex
from notebook.base.handlers import path_regex
@ -16,7 +15,7 @@ def test_path_regex():
'/x/foo/bar',
'/x/foo/bar.txt',
):
assert_regex(path, path_pat)
assert re.match(path_pat, path)
def test_path_regex_bad():
for path in (
@ -29,4 +28,4 @@ def test_path_regex_bad():
'/y',
'/y/x/foo',
):
assert_not_regex(path, path_pat)
assert not re.match(path_pat, path)

View File

@ -2,8 +2,6 @@
import os
import nose.tools as nt
from jupyter_client.session import Session
from ..base.zmqhandlers import (
serialize_binary_message,
@ -15,7 +13,7 @@ def test_serialize_binary():
msg = s.msg('data_pub', content={'a': 'b'})
msg['buffers'] = [ memoryview(os.urandom(3)) for i in range(3) ]
bmsg = serialize_binary_message(msg)
nt.assert_is_instance(bmsg, bytes)
assert isinstance(bmsg, bytes)
def test_deserialize_binary():
s = Session()
@ -23,4 +21,4 @@ def test_deserialize_binary():
msg['buffers'] = [ memoryview(os.urandom(2)) for i in range(3) ]
bmsg = serialize_binary_message(msg)
msg2 = deserialize_binary_message(bmsg)
nt.assert_equal(msg2, msg)
assert msg2 == msg

View File

@ -5,8 +5,9 @@
import ctypes
import os
import sys
import nose.tools as nt
import pytest
from traitlets.tests.utils import check_help_all_output
from notebook.utils import url_escape, url_unescape, is_hidden, is_file_hidden
@ -26,62 +27,61 @@ def test_url_escape():
# changes path or notebook name with special characters to url encoding
# these tests specifically encode paths with spaces
path = url_escape('/this is a test/for spaces/')
nt.assert_equal(path, '/this%20is%20a%20test/for%20spaces/')
assert path == '/this%20is%20a%20test/for%20spaces/'
path = url_escape('notebook with space.ipynb')
nt.assert_equal(path, 'notebook%20with%20space.ipynb')
assert path == 'notebook%20with%20space.ipynb'
path = url_escape('/path with a/notebook and space.ipynb')
nt.assert_equal(path, '/path%20with%20a/notebook%20and%20space.ipynb')
assert path == '/path%20with%20a/notebook%20and%20space.ipynb'
path = url_escape('/ !@$#%^&* / test %^ notebook @#$ name.ipynb')
nt.assert_equal(path,
'/%20%21%40%24%23%25%5E%26%2A%20/%20test%20%25%5E%20notebook%20%40%23%24%20name.ipynb')
assert path == '/%20%21%40%24%23%25%5E%26%2A%20/%20test%20%25%5E%20notebook%20%40%23%24%20name.ipynb'
def test_url_unescape():
# decodes a url string to a plain string
# these tests decode paths with spaces
path = url_unescape('/this%20is%20a%20test/for%20spaces/')
nt.assert_equal(path, '/this is a test/for spaces/')
assert path == '/this is a test/for spaces/'
path = url_unescape('notebook%20with%20space.ipynb')
nt.assert_equal(path, 'notebook with space.ipynb')
assert path == 'notebook with space.ipynb'
path = url_unescape('/path%20with%20a/notebook%20and%20space.ipynb')
nt.assert_equal(path, '/path with a/notebook and space.ipynb')
assert path == '/path with a/notebook and space.ipynb'
path = url_unescape(
'/%20%21%40%24%23%25%5E%26%2A%20/%20test%20%25%5E%20notebook%20%40%23%24%20name.ipynb')
nt.assert_equal(path, '/ !@$#%^&* / test %^ notebook @#$ name.ipynb')
assert path == '/ !@$#%^&* / test %^ notebook @#$ name.ipynb'
def test_is_hidden():
with TemporaryDirectory() as root:
subdir1 = os.path.join(root, 'subdir')
os.makedirs(subdir1)
nt.assert_equal(is_hidden(subdir1, root), False)
nt.assert_equal(is_file_hidden(subdir1), False)
assert is_hidden(subdir1, root) == False
assert is_file_hidden(subdir1) == False
subdir2 = os.path.join(root, '.subdir2')
os.makedirs(subdir2)
nt.assert_equal(is_hidden(subdir2, root), True)
nt.assert_equal(is_file_hidden(subdir2), True)#
assert is_hidden(subdir2, root) == True
assert is_file_hidden(subdir2) == True
# root dir is always visible
nt.assert_equal(is_hidden(subdir2, subdir2), False)
assert is_hidden(subdir2, subdir2) == False
subdir34 = os.path.join(root, 'subdir3', '.subdir4')
os.makedirs(subdir34)
nt.assert_equal(is_hidden(subdir34, root), True)
nt.assert_equal(is_hidden(subdir34), True)
assert is_hidden(subdir34, root) == True
assert is_hidden(subdir34) == True
subdir56 = os.path.join(root, '.subdir5', 'subdir6')
os.makedirs(subdir56)
nt.assert_equal(is_hidden(subdir56, root), True)
nt.assert_equal(is_hidden(subdir56), True)
nt.assert_equal(is_file_hidden(subdir56), False)
nt.assert_equal(is_file_hidden(subdir56, os.stat(subdir56)), False)
assert is_hidden(subdir56, root) == True
assert is_hidden(subdir56) == True
assert is_file_hidden(subdir56) == False
assert is_file_hidden(subdir56, os.stat(subdir56)) == False
@skip_if_not_win32
@pytest.mark.skipif(sys.platform != "win32", reason="run on windows only")
def test_is_hidden_win32():
with TemporaryDirectory() as root:
root = cast_unicode(root)

View File

@ -115,8 +115,8 @@ for more information.
'prometheus_client'
],
extras_require = {
'test': ['nose', 'coverage', 'requests', 'nose_warnings_filters',
'nbval', 'nose-exclude', 'selenium', 'pytest', 'pytest-cov'],
'test': ['pytest', 'coverage', 'requests',
'nbval', 'selenium', 'pytest', 'pytest-cov'],
'docs': ['sphinx', 'nbsphinx', 'sphinxcontrib_github_alt', 'sphinx_rtd_theme'],
'test:sys_platform != "win32"': ['requests-unixsocket'],
'json-logging': ['json-logging']