Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
autoreload_test.py269 linesDownload Raw Back to test
1import os
2import shutil
3import subprocess
4from subprocess import Popen
5import sys
6from tempfile import mkdtemp
7import textwrap
8import time
9import unittest
10
11
12class AutoreloadTest(unittest.TestCase):
13    def setUp(self):
14        # When these tests fail the output sometimes exceeds the default maxDiff.
15        self.maxDiff = 1024
16
17        self.path = mkdtemp()
18
19        # Most test apps run themselves twice via autoreload. The first time it manually triggers
20        # a reload (could also do this by touching a file but this is faster since filesystem
21        # timestamps are not necessarily high resolution). The second time it exits directly
22        # so that the autoreload wrapper (if it is used) doesn't catch it.
23        #
24        # The last line of each such test's "main" program should be
25        #     exec(open("run_twice_magic.py").read())
26        self.write_files(
27            {
28                "run_twice_magic.py": """
29                    import os
30                    import sys
31
32                    import tornado.autoreload
33
34                    sys.stdout.flush()
35
36                    if "TESTAPP_STARTED" not in os.environ:
37                        os.environ["TESTAPP_STARTED"] = "1"
38                        tornado.autoreload._reload()
39                    else:
40                        os._exit(0)
41                """
42            }
43        )
44
45    def tearDown(self):
46        try:
47            shutil.rmtree(self.path)
48        except OSError:
49            # Windows disallows deleting files that are in use by
50            # another process, and even though we've waited for our
51            # child process below, it appears that its lock on these
52            # files is not guaranteed to be released by this point.
53            # Sleep and try again (once).
54            time.sleep(1)
55            shutil.rmtree(self.path)
56
57    def write_files(self, tree, base_path=None):
58        """Write a directory tree to self.path.
59
60        tree is a dictionary mapping file names to contents, or
61        sub-dictionaries representing subdirectories.
62        """
63        if base_path is None:
64            base_path = self.path
65        for name, contents in tree.items():
66            if isinstance(contents, dict):
67                os.mkdir(os.path.join(base_path, name))
68                self.write_files(contents, os.path.join(base_path, name))
69            else:
70                with open(os.path.join(base_path, name), "w", encoding="utf-8") as f:
71                    f.write(textwrap.dedent(contents))
72
73    def run_subprocess(self, args):
74        # Make sure the tornado module under test is available to the test
75        # application
76        parts = [os.getcwd()]
77        if "PYTHONPATH" in os.environ:
78            parts += [
79                os.path.join(os.getcwd(), part)
80                for part in os.environ["PYTHONPATH"].split(os.pathsep)
81            ]
82        pythonpath = os.pathsep.join(parts)
83
84        p = Popen(
85            args,
86            stdout=subprocess.PIPE,
87            env=dict(os.environ, PYTHONPATH=pythonpath),
88            cwd=self.path,
89            universal_newlines=True,
90            encoding="utf-8",
91        )
92
93        # This timeout needs to be fairly generous for pypy due to jit
94        # warmup costs.
95        for i in range(40):
96            if p.poll() is not None:
97                break
98            time.sleep(0.1)
99        else:
100            p.kill()
101            raise Exception("subprocess failed to terminate")
102
103        out = p.communicate()[0]
104        self.assertEqual(p.returncode, 0)
105        return out
106
107    def test_reload(self):
108        main = """\
109import sys
110
111# In module mode, the path is set to the parent directory and we can import testapp.
112try:
113    import testapp
114except ImportError:
115    print("import testapp failed")
116else:
117    print("import testapp succeeded")
118
119spec = getattr(sys.modules[__name__], '__spec__', None)
120print(f"Starting {__name__=}, __spec__.name={getattr(spec, 'name', None)}")
121exec(open("run_twice_magic.py", encoding="utf-8").read())
122"""
123
124        # Create temporary test application
125        self.write_files(
126            {
127                "testapp": {
128                    "__init__.py": "",
129                    "__main__.py": main,
130                },
131            }
132        )
133
134        # The autoreload wrapper should support all the same modes as the python interpreter.
135        # The wrapper itself should have no effect on this test so we try all modes with and
136        # without it.
137        for wrapper in [False, True]:
138            with self.subTest(wrapper=wrapper):
139                with self.subTest(mode="module"):
140                    if wrapper:
141                        base_args = [sys.executable, "-m", "tornado.autoreload"]
142                    else:
143                        base_args = [sys.executable]
144                    # In module mode, the path is set to the parent directory and we can import
145                    # testapp. Also, the __spec__.name is set to the fully qualified module name.
146                    out = self.run_subprocess(base_args + ["-m", "testapp"])
147                    self.assertEqual(
148                        out,
149                        (
150                            "import testapp succeeded\n"
151                            + "Starting __name__='__main__', __spec__.name=testapp.__main__\n"
152                        )
153                        * 2,
154                    )
155
156                with self.subTest(mode="file"):
157                    out = self.run_subprocess(base_args + ["testapp/__main__.py"])
158                    # In file mode, we do not expect the path to be set so we can import testapp,
159                    # but when the wrapper is used the -m argument to the python interpreter
160                    # does this for us.
161                    expect_import = (
162                        "import testapp succeeded"
163                        if wrapper
164                        else "import testapp failed"
165                    )
166                    # In file mode there is no qualified module spec.
167                    self.assertEqual(
168                        out,
169                        f"{expect_import}\nStarting __name__='__main__', __spec__.name=None\n"
170                        * 2,
171                    )
172
173                with self.subTest(mode="directory"):
174                    # Running as a directory finds __main__.py like a module. It does not manipulate
175                    # sys.path but it does set a spec with a name of exactly __main__.
176                    out = self.run_subprocess(base_args + ["testapp"])
177                    expect_import = (
178                        "import testapp succeeded"
179                        if wrapper
180                        else "import testapp failed"
181                    )
182                    self.assertEqual(
183                        out,
184                        f"{expect_import}\nStarting __name__='__main__', __spec__.name=__main__\n"
185                        * 2,
186                    )
187
188    def test_reload_wrapper_preservation(self):
189        # This test verifies that when `python -m tornado.autoreload`
190        # is used on an application that also has an internal
191        # autoreload, the reload wrapper is preserved on restart.
192        main = """\
193import sys
194
195# This import will fail if path is not set up correctly
196import testapp
197
198if 'tornado.autoreload' not in sys.modules:
199    raise Exception('started without autoreload wrapper')
200
201print('Starting')
202exec(open("run_twice_magic.py", encoding="utf-8").read())
203"""
204
205        self.write_files(
206            {
207                "testapp": {
208                    "__init__.py": "",
209                    "__main__.py": main,
210                },
211            }
212        )
213
214        out = self.run_subprocess(
215            [sys.executable, "-m", "tornado.autoreload", "-m", "testapp"]
216        )
217        self.assertEqual(out, "Starting\n" * 2)
218
219    def test_reload_wrapper_args(self):
220        main = """\
221import os
222import sys
223
224print(os.path.basename(sys.argv[0]))
225print(f'argv={sys.argv[1:]}')
226exec(open("run_twice_magic.py", encoding="utf-8").read())
227"""
228        # Create temporary test application
229        self.write_files({"main.py": main})
230
231        # Make sure the tornado module under test is available to the test
232        # application
233        out = self.run_subprocess(
234            [
235                sys.executable,
236                "-m",
237                "tornado.autoreload",
238                "main.py",
239                "arg1",
240                "--arg2",
241                "-m",
242                "arg3",
243            ],
244        )
245
246        self.assertEqual(out, "main.py\nargv=['arg1', '--arg2', '-m', 'arg3']\n" * 2)
247
248    def test_reload_wrapper_until_success(self):
249        main = """\
250import os
251import sys
252
253if "TESTAPP_STARTED" in os.environ:
254    print("exiting cleanly")
255    sys.exit(0)
256else:
257    print("reloading")
258    exec(open("run_twice_magic.py", encoding="utf-8").read())
259"""
260
261        # Create temporary test application
262        self.write_files({"main.py": main})
263
264        out = self.run_subprocess(
265            [sys.executable, "-m", "tornado.autoreload", "--until-success", "main.py"]
266        )
267
268        self.assertEqual(out, "reloading\nexiting cleanly\n")
269 
codekingpro/portable-devtools · Team Ai