Skip to content

Commit

Permalink
Merge pull request #2393 from bdarnell/autoreload-wrapper
Browse files Browse the repository at this point in the history
autoreload: Preserve wrapper with internal reload
  • Loading branch information
bdarnell committed May 20, 2018
2 parents eb487ca + feee98f commit 0210320
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 9 deletions.
25 changes: 21 additions & 4 deletions tornado/autoreload.py
Expand Up @@ -107,6 +107,9 @@
_reload_hooks = []
_reload_attempted = False
_io_loops = weakref.WeakKeyDictionary() # type: ignore
_autoreload_is_main = False
_original_argv = None
_original_spec = None


def start(check_time=500):
Expand Down Expand Up @@ -214,19 +217,23 @@ def _reload():
# __spec__ is not available (Python < 3.4), check instead if
# sys.path[0] is an empty string and add the current directory to
# $PYTHONPATH.
spec = getattr(sys.modules['__main__'], '__spec__', None)
if spec:
argv = ['-m', spec.name] + sys.argv[1:]
if _autoreload_is_main:
spec = _original_spec
argv = _original_argv
else:
spec = getattr(sys.modules['__main__'], '__spec__', None)
argv = sys.argv
if spec:
argv = ['-m', spec.name] + argv[1:]
else:
path_prefix = '.' + os.pathsep
if (sys.path[0] == '' and
not os.environ.get("PYTHONPATH", "").startswith(path_prefix)):
os.environ["PYTHONPATH"] = (path_prefix +
os.environ.get("PYTHONPATH", ""))
if not _has_execv:
subprocess.Popen([sys.executable] + argv)
sys.exit(0)
os._exit(0)
else:
try:
os.execv(sys.executable, [sys.executable] + argv)
Expand Down Expand Up @@ -269,7 +276,17 @@ def main():
can catch import-time problems like syntax errors that would otherwise
prevent the script from reaching its call to `wait`.
"""
# Remember that we were launched with autoreload as main.
# The main module can be tricky; set the variables both in our globals
# (which may be __main__) and the real importable version.
import tornado.autoreload
global _autoreload_is_main
global _original_argv, _original_spec
tornado.autoreload._autoreload_is_main = _autoreload_is_main = True
original_argv = sys.argv
tornado.autoreload._original_argv = _original_argv = original_argv
original_spec = getattr(sys.modules['__main__'], '__spec__', None)
tornado.autoreload._original_spec = _original_spec = original_spec
sys.argv = sys.argv[:]
if len(sys.argv) >= 3 and sys.argv[1] == "-m":
mode = "module"
Expand Down
74 changes: 69 additions & 5 deletions tornado/test/autoreload_test.py
@@ -1,14 +1,19 @@
from __future__ import absolute_import, division, print_function
import os
import shutil
import subprocess
from subprocess import Popen
import sys
from tempfile import mkdtemp
import time

from tornado.test.util import unittest


MAIN = """\
class AutoreloadTest(unittest.TestCase):

def test_reload_module(self):
main = """\
import os
import sys
Expand All @@ -24,15 +29,13 @@
autoreload._reload()
"""


class AutoreloadTest(unittest.TestCase):
def test_reload_module(self):
# Create temporary test application
path = mkdtemp()
self.addCleanup(shutil.rmtree, path)
os.mkdir(os.path.join(path, 'testapp'))
open(os.path.join(path, 'testapp/__init__.py'), 'w').close()
with open(os.path.join(path, 'testapp/__main__.py'), 'w') as f:
f.write(MAIN)
f.write(main)

# Make sure the tornado module under test is available to the test
# application
Expand All @@ -46,3 +49,64 @@ def test_reload_module(self):
universal_newlines=True)
out = p.communicate()[0]
self.assertEqual(out, 'Starting\nStarting\n')

def test_reload_wrapper_preservation(self):
# This test verifies that when `python -m tornado.autoreload`
# is used on an application that also has an internal
# autoreload, the reload wrapper is preserved on restart.
main = """\
import os
import sys
# This import will fail if path is not set up correctly
import testapp
if 'tornado.autoreload' not in sys.modules:
raise Exception('started without autoreload wrapper')
import tornado.autoreload
print('Starting')
sys.stdout.flush()
if 'TESTAPP_STARTED' not in os.environ:
os.environ['TESTAPP_STARTED'] = '1'
# Simulate an internal autoreload (one not caused
# by the wrapper).
tornado.autoreload._reload()
else:
# Exit directly so autoreload doesn't catch it.
os._exit(0)
"""

# Create temporary test application
path = mkdtemp()
os.mkdir(os.path.join(path, 'testapp'))
self.addCleanup(shutil.rmtree, path)
init_file = os.path.join(path, 'testapp', '__init__.py')
open(init_file, 'w').close()
main_file = os.path.join(path, 'testapp', '__main__.py')
with open(main_file, 'w') as f:
f.write(main)

# Make sure the tornado module under test is available to the test
# application
pythonpath = os.getcwd()
if 'PYTHONPATH' in os.environ:
pythonpath += os.pathsep + os.environ['PYTHONPATH']

autoreload_proc = Popen(
[sys.executable, '-m', 'tornado.autoreload', '-m', 'testapp'],
stdout=subprocess.PIPE, cwd=path,
env=dict(os.environ, PYTHONPATH=pythonpath),
universal_newlines=True)

for i in range(20):
if autoreload_proc.poll() is not None:
break
time.sleep(0.1)
else:
autoreload_proc.kill()
raise Exception("subprocess failed to terminate")

out = autoreload_proc.communicate()[0]
self.assertEqual(out, 'Starting\n' * 2)

0 comments on commit 0210320

Please sign in to comment.