Coverage for pycommons / dev / tests / compile_and_run.py: 100%
37 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 03:04 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 03:04 +0000
1"""Compile and run some Python code for testing purposes."""
3from os import chdir, getcwd
4from typing import Final
6from pycommons.io.console import logger
7from pycommons.io.path import directory_path
8from pycommons.io.temp import temp_dir
11def compile_and_run(code: str, source: str) -> None:
12 """
13 Compile and run some code for testing purposes.
15 This method first checks the types of its parameters.
16 It then performs some superficial sanity checks on `code`.
17 Then, it changes the working directory to a temporary folder which is
18 deleted after all work is done.
19 It then compiles and, if that was successful, executes the code fragment.
20 Then working directory is changed back to the original directory and the
21 temporary directory is deleted.
23 :param code: the code to be compiled and run
24 :param source: the source of the code
25 :raises TypeError: if `code` or `source` are not strings
26 :raises ValueError: if any parameter has an invalid value
27 or if the code execution fails
29 >>> wd = getcwd()
30 >>> try:
31 ... compile_and_run(None, "bla")
32 ... except TypeError as te:
33 ... print(te)
34 descriptor '__len__' requires a 'str' object but received a 'NoneType'
36 >>> wd == getcwd()
37 True
39 >>> try:
40 ... compile_and_run(1, "bla")
41 ... except TypeError as te:
42 ... print(te)
43 descriptor '__len__' requires a 'str' object but received a 'int'
45 >>> wd == getcwd()
46 True
48 >>> try:
49 ... compile_and_run("x=5", None)
50 ... except TypeError as te:
51 ... print(te)
52 descriptor '__len__' requires a 'str' object but received a 'NoneType'
54 >>> wd == getcwd()
55 True
57 >>> try:
58 ... compile_and_run("x=5", 1)
59 ... except TypeError as te:
60 ... print(te)
61 descriptor '__len__' requires a 'str' object but received a 'int'
63 >>> wd == getcwd()
64 True
66 >>> try:
67 ... compile_and_run(None, None)
68 ... except TypeError as te:
69 ... print(te)
70 descriptor '__len__' requires a 'str' object but received a 'NoneType'
72 >>> wd == getcwd()
73 True
75 >>> try:
76 ... compile_and_run("x=5", "")
77 ... except ValueError as ve:
78 ... print(ve)
79 Invalid source: ''.
81 >>> wd == getcwd()
82 True
84 >>> try:
85 ... compile_and_run("x=5", " ")
86 ... except ValueError as ve:
87 ... print(ve)
88 Source cannot be only white space, but ' ' is.
90 >>> wd == getcwd()
91 True
93 >>> try:
94 ... compile_and_run("", "x")
95 ... except ValueError as ve:
96 ... print(ve)
97 Code '' from 'x' is empty?
99 >>> wd == getcwd()
100 True
102 >>> try:
103 ... compile_and_run(" ", "x")
104 ... except ValueError as ve:
105 ... print(ve)
106 Code ' ' from 'x' consists of only white space!
108 >>> wd == getcwd()
109 True
111 >>> try:
112 ... compile_and_run("ä ", "x")
113 ... except ValueError as ve:
114 ... print(ve)
115 Code 'ä ' from 'x' contains non-ASCII characters.
117 >>> wd == getcwd()
118 True
120 >>> from contextlib import redirect_stdout
121 >>> try:
122 ... with redirect_stdout(None):
123 ... compile_and_run("<>-sdf/%'!234", "src")
124 ... except ValueError as ve:
125 ... print(ve)
126 Error when compiling 'src'.
128 >>> wd == getcwd()
129 True
131 >>> try:
132 ... with redirect_stdout(None):
133 ... compile_and_run("1/0", "src")
134 ... except ValueError as ve:
135 ... print(ve)
136 Error when executing 'src'.
138 >>> wd == getcwd()
139 True
141 >>> with redirect_stdout(None):
142 ... compile_and_run("print(1)", "src")
144 >>> wd == getcwd()
145 True
146 """
147 if str.__len__(source) <= 0:
148 raise ValueError(f"Invalid source: {source!r}.")
149 use_source: Final[str] = str.strip(source)
150 if str.__len__(use_source) <= 0:
151 raise ValueError(
152 f"Source cannot be only white space, but {source!r} is.")
154 if str.__len__(code) <= 0:
155 raise ValueError(f"Code {code!r} from {source!r} is empty?")
156 use_code: Final[str] = str.rstrip(code)
157 code_len: Final[int] = str.__len__(use_code)
158 if code_len <= 0:
159 raise ValueError(
160 f"Code {code!r} from {source!r} consists of only white space!")
162 if not str.isascii(use_code):
163 raise ValueError(
164 f"Code {code!r} from {source!r} contains non-ASCII characters.")
166 working_dir: Final[str] = directory_path(getcwd())
167 logger(f"Original working directory is {working_dir!r}.")
169 with temp_dir() as td:
170 logger(f"Changing working directory to temp dir {td!r} to "
171 f"process source {use_source!r}.")
172 try:
173 chdir(td)
174 try:
175 compiled = compile( # noqa # nosec
176 use_code, filename=use_source, # noqa # nosec
177 mode="exec", dont_inherit=True) # noqa # nosec
178 except BaseException as be: # noqa
179 raise ValueError(
180 f"Error when compiling {use_source!r}.") from be
181 logger(f"Successfully compiled, now executing {use_source!r}.")
182 try:
183 exec(compiled, {}) # pylint: disable = W0122 # noqa # nosec
184 except BaseException as be: # noqa
185 raise ValueError(
186 f"Error when executing {use_source!r}.") from be
187 finally:
188 chdir(working_dir)
189 logger(f"Changed working directory back to {working_dir!r}")
190 logger(f"Successfully finished executing code from {use_source!r}.")