|
3 | 3 | import subprocess
|
4 | 4 | import textwrap
|
5 | 5 | import time
|
| 6 | +import signal |
6 | 7 |
|
7 | 8 | import e3.fs
|
8 | 9 | import e3.os.fs
|
@@ -115,6 +116,123 @@ def run_test():
|
115 | 116 | e.restore()
|
116 | 117 |
|
117 | 118 |
|
| 119 | +@pytest.mark.skipif(sys.platform == "win32", reason="A linux test") |
| 120 | +def test_rlimit_ctrl_c(): |
| 121 | + """Test rlimit CTRL-C. |
| 122 | +
|
| 123 | + When a parent process launched two or more child processes using rlimit, the CTRL-C |
| 124 | + command no longer worked. |
| 125 | +
|
| 126 | + This was because when an rlimit process was launched, it became the foreground |
| 127 | + process. |
| 128 | +
|
| 129 | + However, when the foreground process was killed, it left the parent process without |
| 130 | + a foreground, so CTRL-C was ignored. |
| 131 | +
|
| 132 | + Examples: |
| 133 | + Example 1: Spawn 1 rlimit child process: |
| 134 | + python (Parent process) |
| 135 | + | |
| 136 | + -> rlimit (Child process / foreground process) |
| 137 | +
|
| 138 | + A CTRL-C appear: |
| 139 | + The child process in the foreground was killed, leaving the parent |
| 140 | + process alone with no foreground process but no child. Due to our |
| 141 | + usage, this posed no known problems. However, leaving the parent |
| 142 | + process without a foreground can cause unexpected results. |
| 143 | +
|
| 144 | + Example 2: Spawn 2 rlimit child process: |
| 145 | + python (Parent process) |
| 146 | + | |
| 147 | + -> rlimit (Child process) ==> This process was the foreground process as |
| 148 | + | long as no other rlimit process was running. |
| 149 | + | In this example, we have 2 rlimit processes, |
| 150 | + | so this process is not in the foreground. |
| 151 | + | |
| 152 | + -> rlimit (Child process / foreground process) |
| 153 | +
|
| 154 | + A CTRL-C appear: |
| 155 | + The foreground process has been killed, leaving no foreground process. |
| 156 | + Signals were no longer propagated, so CTRL-C did nothing. |
| 157 | + """ |
| 158 | + try: |
| 159 | + from ptyprocess import PtyProcess |
| 160 | + except ImportError: |
| 161 | + raise ImportError("ptyprocess is needed to run this tests") from None |
| 162 | + |
| 163 | + # Only a linux test |
| 164 | + assert sys.platform.startswith("linux"), "This test make sens only on linux" |
| 165 | + |
| 166 | + script_to_run = """ |
| 167 | +from __future__ import annotations |
| 168 | +
|
| 169 | +import sys |
| 170 | +from e3.os.process import Run |
| 171 | +
|
| 172 | +p2 = Run(["sleep", "100"], timeout=30, bg=True) |
| 173 | +p1 = Run(["sleep", "10"], timeout=1) |
| 174 | +# CTRL-C is now blocking |
| 175 | +p2.wait() |
| 176 | +""" |
| 177 | + |
| 178 | + with open("tmp-test_rlimic_ctrl_c.py", "w") as f: |
| 179 | + f.write(script_to_run) |
| 180 | + |
| 181 | + start = time.perf_counter() |
| 182 | + p = PtyProcess.spawn([sys.executable, "tmp-test_rlimic_ctrl_c.py"]) |
| 183 | + time.sleep(5) |
| 184 | + p.sendintr() |
| 185 | + # !!! Warning: |
| 186 | + # if the script_to_run write something on stdout, this will wait forever. |
| 187 | + p.wait() |
| 188 | + end = time.perf_counter() |
| 189 | + assert int(end - start) < 30, f"CTRL-C failed: take {int(end - start)} seconds" |
| 190 | + |
| 191 | + |
| 192 | +@pytest.mark.skipif(sys.platform == "win32", reason="A linux test") |
| 193 | +def test_rlimit_foreground_option(): |
| 194 | + """Test rlimit --foreground. |
| 195 | +
|
| 196 | + Test if we can read/write from an interactive terminal using rlimit --foreground. |
| 197 | + """ |
| 198 | + try: |
| 199 | + from ptyprocess import PtyProcess |
| 200 | + except ImportError: |
| 201 | + raise ImportError("ptyprocess is needed to run this tests") from None |
| 202 | + |
| 203 | + # Only a linux test |
| 204 | + assert sys.platform.startswith("linux"), "This test make sens only on linux" |
| 205 | + |
| 206 | + # Test with --foreground |
| 207 | + os.environ["PS1"] = "$ " |
| 208 | + p = PtyProcess.spawn( |
| 209 | + [e3.os.process.get_rlimit(), "--foreground", "30", "bash", "--norc", "-i"], |
| 210 | + env=os.environ, |
| 211 | + echo=False, |
| 212 | + ) |
| 213 | + p.write(b"echo 'test rlimit --foreground'\n", flush=True) |
| 214 | + time.sleep(2) |
| 215 | + assert p.read() == b"$ test rlimit --foreground\r\n$ " |
| 216 | + # Ensure that the process is killed |
| 217 | + p.kill(signal.SIGKILL) |
| 218 | + |
| 219 | + # Test without foreground (Should failed) |
| 220 | + p = PtyProcess.spawn( |
| 221 | + [e3.os.process.get_rlimit(), "30", "bash", "--norc", "-i"], |
| 222 | + env=os.environ, |
| 223 | + echo=False, |
| 224 | + ) |
| 225 | + p.write(b"echo 'test rlimit (no --foreground)'\n", flush=True) |
| 226 | + time.sleep(2) |
| 227 | + with pytest.raises(EOFError): |
| 228 | + # The echo command should not be executed by bash. So p.read() will raise an |
| 229 | + # EOFError. And if not, we will raise an Exception because this is not |
| 230 | + # what we want. |
| 231 | + p.read() |
| 232 | + # Ensure that the process is killed |
| 233 | + p.kill(signal.SIGKILL) |
| 234 | + |
| 235 | + |
118 | 236 | def test_not_found():
|
119 | 237 | with pytest.raises(OSError) as err:
|
120 | 238 | e3.os.process.Run(["e3-bin-not-found"])
|
|
0 commit comments