Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect subclass overrides of cleanup methods #560

Merged
merged 4 commits into from
Jul 13, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions jupyter_client/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,10 +622,11 @@ async def shutdown_kernel(self, now=False, restart=False):
# most 1s, checking every 0.1s.
await self.finish_shutdown()

from . import __version__
from distutils.version import LooseVersion
# See comment in KernelManager.shutdown_kernel().
overrides_cleanup = type(self).cleanup is not AsyncKernelManager.cleanup
overrides_cleanup_resources = type(self).cleanup_resources is not AsyncKernelManager.cleanup_resources

if LooseVersion(__version__) < LooseVersion('6.2'):
if overrides_cleanup and not overrides_cleanup_resources:
self.cleanup(connection_file=not restart)
else:
self.cleanup_resources(restart=restart)
Expand Down
101 changes: 100 additions & 1 deletion jupyter_client/tests/test_kernelmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import multiprocessing as mp
import pytest
from unittest import TestCase
from tornado.testing import AsyncTestCase, gen_test, gen
from tornado.testing import AsyncTestCase, gen_test

from traitlets.config.loader import Config
from jupyter_core import paths
Expand Down Expand Up @@ -461,3 +461,102 @@ async def test_start_new_async_kernel(self):
await km.shutdown_kernel(now=True)
kc.stop_channels()
self.assertTrue(km.context.closed)


class AsyncKernelManagerSubclass(AsyncKernelManager):
"""Used to test deprecation "routes" that are determined by superclass' detection of methods.

This class represents a current subclass that overrides both cleanup() and cleanup_resources()
in order to be compatible with older jupyter_clients. We should find that cleanup_resources()
is called on these instances vix TestAsyncKernelManagerSubclass.
"""

def cleanup(self, connection_file=True):
super(AsyncKernelManagerSubclass, self).cleanup(connection_file=connection_file)
self.which_cleanup = 'cleanup'

def cleanup_resources(self, restart=False):
super(AsyncKernelManagerSubclass, self).cleanup_resources(restart=restart)
self.which_cleanup = 'cleanup_resources'


class AsyncKernelManagerWithCleanup(AsyncKernelManager):
"""Used to test deprecation "routes" that are determined by superclass' detection of methods.

This class represents the older subclass that overrides cleanup(). We should find that
cleanup() is called on these instances via TestAsyncKernelManagerWithCleanup.
"""

def cleanup(self, connection_file=True):
super(AsyncKernelManagerWithCleanup, self).cleanup(connection_file=connection_file)
self.which_cleanup = 'cleanup'


class TestAsyncKernelManagerSubclass(AsyncTestCase):
def setUp(self):
super(TestAsyncKernelManagerSubclass, self).setUp()
self.env_patch = test_env()
self.env_patch.start()

def tearDown(self):
super(TestAsyncKernelManagerSubclass, self).tearDown()
self.env_patch.stop()

def _install_test_kernel(self):
kernel_dir = pjoin(paths.jupyter_data_dir(), 'kernels', 'signaltest')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, we may not want to write into their real jupyter_data_dir during a test. I would have maybe opted to just mock function call and check that the correct mock is called when a subclass with the old vs new pattern exists. If we do the full integration test pattern like this we likely want to use a temporary directory to cleanup the files automatically and not pollute the system. This might be a bit of a pain to do though :/

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This install method is used by several existing tests as well. It doesn't create the kernelspec in the real jupyter_data_dir because the environment setup method points the "important" envs to a temporary directory, knowing that those envs take precedence.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright that works, thanks for clarifying

os.makedirs(kernel_dir)
with open(pjoin(kernel_dir, 'kernel.json'), 'w') as f:
f.write(json.dumps({
'argv': [sys.executable,
'-m', 'jupyter_client.tests.signalkernel',
'-f', '{connection_file}'],
'display_name': "Signal Test Kernel",
}))

def _get_tcp_km(self):
c = Config()
km = AsyncKernelManagerSubclass(config=c)
return km

def _get_ipc_km(self):
c = Config()
c.KernelManager.transport = 'ipc'
c.KernelManager.ip = 'test'
km = AsyncKernelManagerSubclass(config=c)
return km

async def _run_lifecycle(self, km):
await km.start_kernel(stdout=PIPE, stderr=PIPE)
self.assertTrue(await km.is_alive())
await km.restart_kernel(now=True)
self.assertTrue(await km.is_alive())
await km.interrupt_kernel()
self.assertTrue(isinstance(km, AsyncKernelManager))
await km.shutdown_kernel(now=True)
self.assertFalse(await km.is_alive())
self.assertTrue(km.context.closed)

@gen_test
async def test_which_cleanup_method(self):
# This test confirms that the normal test operations run using cleanup_resources.
km = self._get_tcp_km()
await self._run_lifecycle(km)

if isinstance(km, AsyncKernelManagerSubclass):
assert km.which_cleanup == "cleanup_resources"
else:
assert km.which_cleanup == "cleanup"


class TestAsyncKernelManagerWithCleanup(TestAsyncKernelManagerSubclass):
def _get_tcp_km(self):
c = Config()
km = AsyncKernelManagerWithCleanup(config=c)
return km

def _get_ipc_km(self):
c = Config()
c.KernelManager.transport = 'ipc'
c.KernelManager.ip = 'test'
km = AsyncKernelManagerWithCleanup(config=c)
return km