]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
autoreload: Add --until-success flag
authorBen Darnell <ben@bendarnell.com>
Thu, 27 Jul 2023 00:15:12 +0000 (20:15 -0400)
committerBen Darnell <ben@bendarnell.com>
Thu, 27 Jul 2023 00:15:12 +0000 (20:15 -0400)
This flag terminates the autoreload loop after the first successful
run. This makes it possible to cleanly shut down a process that is using
"python -m tornado.autoreload" without printing a traceback.

Fixes #2398

tornado/autoreload.py
tornado/test/autoreload_test.py

index 376b346e3dc6ab0ad55841e9747d14b6b7de1efb..3277982d76acc9653ecb0dca4258705fc9812bb9 100644 (file)
@@ -313,6 +313,11 @@ def main() -> None:
     )
     parser.disable_interspersed_args()
     parser.add_option("-m", dest="module", metavar="module", help="module to run")
+    parser.add_option(
+        "--until-success",
+        action="store_true",
+        help="stop reloading after the program exist successfully (status code 0)",
+    )
     opts, rest = parser.parse_args()
     if opts.module is None:
         if not rest:
@@ -324,6 +329,7 @@ def main() -> None:
         path = None
         sys.argv = [sys.argv[0]] + rest
 
+    exit_status = 1
     try:
         import runpy
 
@@ -333,6 +339,7 @@ def main() -> None:
             assert path is not None
             runpy.run_path(path, run_name="__main__")
     except SystemExit as e:
+        exit_status = e.code
         gen_log.info("Script exited with status %s", e.code)
     except Exception as e:
         gen_log.warning("Script exited with uncaught exception", exc_info=True)
@@ -349,6 +356,7 @@ def main() -> None:
             if e.filename is not None:
                 watch(e.filename)
     else:
+        exit_status = 0
         gen_log.info("Script exited normally")
     # restore sys.argv so subsequent executions will include autoreload
     sys.argv = original_argv
@@ -361,6 +369,8 @@ def main() -> None:
         if loader is not None:
             watch(loader.get_filename())  # type: ignore
 
+    if opts.until_success and exit_status == 0:
+        return
     wait()
 
 
index 025b6ed5df811e2fa08cfe2e1e63628c585cca9e..5675faa2a58ec7b9c51b43fdb9a336c5a827f58f 100644 (file)
@@ -16,12 +16,12 @@ class AutoreloadTest(unittest.TestCase):
 
         self.path = mkdtemp()
 
-        # Each test app runs itself twice via autoreload. The first time it manually triggers
+        # Most test apps run themselves twice via autoreload. The first time it manually triggers
         # a reload (could also do this by touching a file but this is faster since filesystem
         # timestamps are not necessarily high resolution). The second time it exits directly
         # so that the autoreload wrapper (if it is used) doesn't catch it.
         #
-        # The last line of each test's "main" program should be
+        # The last line of each such test's "main" program should be
         #     exec(open("run_twice_magic.py").read())
         self.write_files(
             {
@@ -240,3 +240,25 @@ exec(open("run_twice_magic.py").read())
         )
 
         self.assertEqual(out, "main.py\nargv=['arg1', '--arg2', '-m', 'arg3']\n" * 2)
+
+    def test_reload_wrapper_until_success(self):
+        main = """\
+import os
+import sys
+
+if "TESTAPP_STARTED" in os.environ:
+    print("exiting cleanly")
+    sys.exit(0)
+else:
+    print("reloading")
+    exec(open("run_twice_magic.py").read())
+"""
+
+        # Create temporary test application
+        self.write_files({"main.py": main})
+
+        out = self.run_subprocess(
+            [sys.executable, "-m", "tornado.autoreload", "--until-success", "main.py"]
+        )
+
+        self.assertEqual(out, "reloading\nexiting cleanly\n")