From 5bfcb4d1b84ee3e340270570ab9ccd188daaaad4 Mon Sep 17 00:00:00 2001 From: Luis Marsano Date: Fri, 26 Mar 2021 00:43:11 -0400 Subject: [PATCH 1/6] add tests for reloading module prepare test fixtures consisting of a package and module with relative & absolute import test scripting server runs with these variations - from package or from module - with or without reloader the server code is based off servertest.py --- test/servertestpackage/__init__.py | 0 test/servertestpackage/__main__.py | 38 ++++++++++++++++++++++++++++++ test/servertestpackage/absolute.py | 0 test/servertestpackage/module.py | 1 + test/servertestpackage/relative.py | 2 ++ test/test_server.py | 30 +++++++++++++++++------ 6 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 test/servertestpackage/__init__.py create mode 100644 test/servertestpackage/__main__.py create mode 100644 test/servertestpackage/absolute.py create mode 120000 test/servertestpackage/module.py create mode 100644 test/servertestpackage/relative.py diff --git a/test/servertestpackage/__init__.py b/test/servertestpackage/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/servertestpackage/__main__.py b/test/servertestpackage/__main__.py new file mode 100644 index 000000000..da10bfada --- /dev/null +++ b/test/servertestpackage/__main__.py @@ -0,0 +1,38 @@ +import sys, os, socket + +import servertestpackage.absolute +from . import relative + +test_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +os.chdir(test_root) +sys.path.insert(0, os.path.dirname(test_root)) +sys.path.insert(0, test_root) + +try: + server = sys.argv[1] + port = int(sys.argv[2]) + reloads= '--reload' in sys.argv + + if server == 'gevent': + from gevent import monkey + monkey.patch_all() + elif server == 'eventlet': + import eventlet + eventlet.monkey_patch() + + try: + import coverage + coverage.process_startup() + except ImportError: + pass + + from bottle import route, run + route('/test', callback=relative.test_handler) + run(reloader=reloads, port=port, server=server, quiet=True) + +except socket.error: + sys.exit(3) +except ImportError: + sys.exit(128) +except KeyboardInterrupt: + pass diff --git a/test/servertestpackage/absolute.py b/test/servertestpackage/absolute.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/servertestpackage/module.py b/test/servertestpackage/module.py new file mode 120000 index 000000000..5a427d194 --- /dev/null +++ b/test/servertestpackage/module.py @@ -0,0 +1 @@ +__main__.py \ No newline at end of file diff --git a/test/servertestpackage/relative.py b/test/servertestpackage/relative.py new file mode 100644 index 000000000..7ea5e9649 --- /dev/null +++ b/test/servertestpackage/relative.py @@ -0,0 +1,2 @@ +def test_handler(): + return 'OK' diff --git a/test/test_server.py b/test/test_server.py index 3363872d4..ae259550d 100644 --- a/test/test_server.py +++ b/test/test_server.py @@ -14,8 +14,7 @@ except: from urllib2 import urlopen -serverscript = os.path.join(os.path.dirname(__file__), 'servertest.py') - +test_root = os.path.dirname(os.path.abspath(__file__)) def ping(server, port): ''' Check if a server accepts connections on a specific TCP port ''' @@ -28,10 +27,14 @@ def ping(server, port): finally: s.close() - -class TestServer(unittest.TestCase): +class TestServerBase(unittest.TestCase): server = 'wsgiref' skip = False + script = [] + extra_args = [] + + def base_cmd(self, port): + return [sys.executable] + self.script + [self.server, str(port)] + self.extra_args def setUp(self): self.skip = self.skip or 'fast' in sys.argv @@ -40,9 +43,8 @@ def setUp(self): for port in range(8800, 8900): self.port = port # Start servertest.py in a subprocess - cmd = [sys.executable, serverscript, self.server, str(port)] - cmd += sys.argv[1:] # pass cmdline arguments to subprocesses - self.p = Popen(cmd, stdout=PIPE, stderr=PIPE) + cmd = self.base_cmd(port) + sys.argv[1:] # pass cmdline arguments to subprocesses + self.p = Popen(cmd, stdout=PIPE, stderr=PIPE, cwd=test_root) # Wait for the socket to accept connections for i in range(100): time.sleep(0.1) @@ -92,11 +94,25 @@ def fetch(self, url): except Exception as E: return repr(E) +class TestServer(TestServerBase): + script = [os.path.join(os.path.dirname(__file__), 'servertest.py')] + def test_simple(self): ''' Test a simple static page with this server adapter. ''' if self.skip: return self.assertEqual(tob('OK'), self.fetch('test')) +class TestServerPackage(TestServer): + script = ['-m', 'servertestpackage'] + +class TestServerPackageReloader(TestServerPackage): + extra_args = ['--reload'] + +class TestServerModule(TestServer): + script = ['-m', 'servertestpackage.module'] + +class TestServerModuleReloader(TestServerModule): + extra_args = ['--reload'] blacklist = ['cgi', 'flup', 'gae', 'wsgiref'] blacklist += ['fapws3', 'cherrypy', 'diesel'] # deprecated adapters From 47a55ac83eb5323791ac2e42be8d99d547c834b4 Mon Sep 17 00:00:00 2001 From: Luis Marsano Date: Fri, 26 Mar 2021 00:51:56 -0400 Subject: [PATCH 2/6] pass reloader tests python doesn't have an obvious way to reveal the original command and module name, so we check the __main__ module for __package__ and infer it's a module running as a script unless the value is None, which implies a file path was provided modules running as scripts are invoked with -m flags packages running with scripts have base filename __main__.py from this we recreate an invocation --- bottle.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/bottle.py b/bottle.py index 23fff762c..9f4c4bccf 100755 --- a/bottle.py +++ b/bottle.py @@ -3633,6 +3633,14 @@ def load_app(target): _debug = debug +def _main_module_name(): + package = vars(sys.modules['__main__'])['__package__'] + if package is None: + return None + else: + file_path = sys.argv[0] + base_name, _ = os.path.splitext(os.path.basename(file_path)) + return package if base_name == '__main__' else '.'.join((package, base_name)) def run(app=None, server='wsgiref', @@ -3667,11 +3675,13 @@ def run(app=None, try: fd, lockfile = tempfile.mkstemp(prefix='bottle.', suffix='.lock') os.close(fd) # We only need this file to exist. We never write to it + module_name = _main_module_name() + options = [sys.argv[0]] if module_name is None else ['-m', module_name] + args = [sys.executable] + options + sys.argv[1:] + environ = os.environ.copy() + environ['BOTTLE_CHILD'] = 'true' + environ['BOTTLE_LOCKFILE'] = lockfile while os.path.exists(lockfile): - args = [sys.executable] + sys.argv - environ = os.environ.copy() - environ['BOTTLE_CHILD'] = 'true' - environ['BOTTLE_LOCKFILE'] = lockfile p = subprocess.Popen(args, env=environ) while p.poll() is None: # Busy wait... os.utime(lockfile, None) # I am alive! From 80025ac23939881f4bc371cb7eba46a498240985 Mon Sep 17 00:00:00 2001 From: Luis Marsano Date: Sun, 28 Mar 2021 00:31:30 -0400 Subject: [PATCH 3/6] test: prepare top-level & submodule for clarity these will support top-level module script test cases --- test/servertestpackage/module.py | 1 - test/servertestpackage/{__main__.py => submodule.py} | 0 2 files changed, 1 deletion(-) delete mode 120000 test/servertestpackage/module.py rename test/servertestpackage/{__main__.py => submodule.py} (100%) diff --git a/test/servertestpackage/module.py b/test/servertestpackage/module.py deleted file mode 120000 index 5a427d194..000000000 --- a/test/servertestpackage/module.py +++ /dev/null @@ -1 +0,0 @@ -__main__.py \ No newline at end of file diff --git a/test/servertestpackage/__main__.py b/test/servertestpackage/submodule.py similarity index 100% rename from test/servertestpackage/__main__.py rename to test/servertestpackage/submodule.py From 2fa900c8c06ed96610fe1e12a5032df044a5bffd Mon Sep 17 00:00:00 2001 From: Luis Marsano Date: Sun, 28 Mar 2021 00:35:40 -0400 Subject: [PATCH 4/6] test: add package main script and cases for top-level script arrange package script to import submodule new test cases check the top-level module name is prepared correctly module tests cases are renamed submodule for clarity --- test/servertestpackage/__main__.py | 1 + test/servertesttop.py | 1 + test/test_server.py | 19 +++++++++++-------- 3 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 test/servertestpackage/__main__.py create mode 100644 test/servertesttop.py diff --git a/test/servertestpackage/__main__.py b/test/servertestpackage/__main__.py new file mode 100644 index 000000000..fb35a52c6 --- /dev/null +++ b/test/servertestpackage/__main__.py @@ -0,0 +1 @@ +from . import submodule diff --git a/test/servertesttop.py b/test/servertesttop.py new file mode 100644 index 000000000..9e317bc5e --- /dev/null +++ b/test/servertesttop.py @@ -0,0 +1 @@ +import servertestpackage.submodule diff --git a/test/test_server.py b/test/test_server.py index ae259550d..a8340d651 100644 --- a/test/test_server.py +++ b/test/test_server.py @@ -27,10 +27,10 @@ def ping(server, port): finally: s.close() -class TestServerBase(unittest.TestCase): +class TestServer(unittest.TestCase): server = 'wsgiref' skip = False - script = [] + script = [os.path.join(os.path.dirname(__file__), 'servertest.py')] extra_args = [] def base_cmd(self, port): @@ -94,24 +94,27 @@ def fetch(self, url): except Exception as E: return repr(E) -class TestServer(TestServerBase): - script = [os.path.join(os.path.dirname(__file__), 'servertest.py')] - def test_simple(self): ''' Test a simple static page with this server adapter. ''' if self.skip: return self.assertEqual(tob('OK'), self.fetch('test')) +class TestServerTopModule(TestServer): + script = ['-m', 'servertesttop'] + +class TestServerTopModuleReloader(TestServerTopModule): + extra_args = ['--reload'] + class TestServerPackage(TestServer): script = ['-m', 'servertestpackage'] class TestServerPackageReloader(TestServerPackage): extra_args = ['--reload'] -class TestServerModule(TestServer): - script = ['-m', 'servertestpackage.module'] +class TestServerSubmodule(TestServer): + script = ['-m', 'servertestpackage.submodule'] -class TestServerModuleReloader(TestServerModule): +class TestServerSubmoduleReloader(TestServerSubmodule): extra_args = ['--reload'] blacklist = ['cgi', 'flup', 'gae', 'wsgiref'] From 8013433f31ebd4fa065bd7cc210cb172022b9186 Mon Sep 17 00:00:00 2001 From: Luis Marsano Date: Sun, 28 Mar 2021 00:37:09 -0400 Subject: [PATCH 5/6] pass new cases & refactor refactor function to return main script arguments --- bottle.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/bottle.py b/bottle.py index 9f4c4bccf..ed75ff6a9 100755 --- a/bottle.py +++ b/bottle.py @@ -3633,14 +3633,21 @@ def load_app(target): _debug = debug -def _main_module_name(): - package = vars(sys.modules['__main__'])['__package__'] +def _main_module_args(): + package = sys.modules['__main__'].__package__ + file_path = sys.argv[0] if package is None: - return None + return [file_path] else: - file_path = sys.argv[0] base_name, _ = os.path.splitext(os.path.basename(file_path)) - return package if base_name == '__main__' else '.'.join((package, base_name)) + return [ + '-m', + ( + package if base_name == '__main__' + else '.'.join((package, base_name)) if package + else base_name + ) + ] def run(app=None, server='wsgiref', @@ -3675,9 +3682,7 @@ def run(app=None, try: fd, lockfile = tempfile.mkstemp(prefix='bottle.', suffix='.lock') os.close(fd) # We only need this file to exist. We never write to it - module_name = _main_module_name() - options = [sys.argv[0]] if module_name is None else ['-m', module_name] - args = [sys.executable] + options + sys.argv[1:] + args = [sys.executable] + _main_module_args() + sys.argv[1:] environ = os.environ.copy() environ['BOTTLE_CHILD'] = 'true' environ['BOTTLE_LOCKFILE'] = lockfile From abf3431958c401322d97271a9ae594e7d877d84e Mon Sep 17 00:00:00 2001 From: Luis Marsano Date: Fri, 2 Apr 2021 01:33:31 -0400 Subject: [PATCH 6/6] replace sys.modules access with import statement __main__ can be imported --- bottle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bottle.py b/bottle.py index ed75ff6a9..776b2bb4c 100755 --- a/bottle.py +++ b/bottle.py @@ -3634,7 +3634,7 @@ def load_app(target): _debug = debug def _main_module_args(): - package = sys.modules['__main__'].__package__ + from __main__ import __package__ as package file_path = sys.argv[0] if package is None: return [file_path]