Skip to content

Commit 244cd0b

Browse files
committed
ensure that PYTHON_HISTORY is not altered
1 parent ef22044 commit 244cd0b

File tree

4 files changed

+98
-41
lines changed

4 files changed

+98
-41
lines changed

Lib/test/support/script_helper.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33

44
import collections
55
import importlib
6+
import site
7+
import stat
68
import sys
79
import os
810
import os.path
911
import subprocess
1012
import py_compile
13+
import unittest
14+
import unittest.mock as mock
1115

1216
from importlib.util import source_from_cache
1317
from test import support
@@ -322,3 +326,49 @@ def title(text):
322326
raise AssertionError(f"{name} failed")
323327
else:
324328
assert_python_ok("-u", script, "-v")
329+
330+
331+
_site_gethistoryfile = site.gethistoryfile
332+
def _gethistoryfile():
333+
"""Patch site.gethistoryfile() to ignore -I for PYTHON_HISTORY.
334+
335+
The -I option is necessary for test_no_memory() but using it
336+
forbids using a custom PYTHON_HISTORY.
337+
"""
338+
history = os.environ.get("PYTHON_HISTORY")
339+
return history or os.path.join(os.path.expanduser('~'), '.python_history')
340+
341+
342+
def patch_gethistoryfile(sitemodule=site):
343+
return mock.patch.object(sitemodule, "gethistoryfile", _gethistoryfile)
344+
345+
346+
def _file_signature(file):
347+
st = os.stat(file)
348+
return (stat.S_IFMT(st.st_mode), st.st_size)
349+
350+
351+
class EnsureSafeUserHistory(unittest.TestCase):
352+
353+
@classmethod
354+
def setUpClass(cls):
355+
if history_file := _site_gethistoryfile():
356+
if os.path.exists(history_file):
357+
cls.__history_file = history_file
358+
cls.__history_stat = _file_signature(history_file)
359+
else:
360+
cls.__history_file = cls.__history_stat = None
361+
362+
def tearDown(self):
363+
super().tearDown()
364+
if self.__history_file is None:
365+
return
366+
self.assertTrue(
367+
os.path.exists(self.__history_file),
368+
f"PYTHON_HISTORY file ({self.__history_file!r}) was deleted"
369+
)
370+
self.assertEqual(
371+
self.__history_stat,
372+
_file_signature(self.__history_file),
373+
f"PYTHON_HISTORY file ({self.__history_file!r}) was altered"
374+
)

Lib/test/test_cmd_line_script.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
from test.support import import_helper, is_apple, os_helper
1818
from test.support.script_helper import (
1919
make_pkg, make_script, make_zip_pkg, make_zip_script,
20-
assert_python_ok, assert_python_failure, spawn_python, kill_python)
20+
assert_python_ok, assert_python_failure, spawn_python, kill_python,
21+
patch_gethistoryfile, EnsureSafeUserHistory
22+
)
2123

2224
verbose = support.verbose
2325

@@ -90,7 +92,8 @@ def _make_test_zip_pkg(zip_dir, zip_basename, pkg_name, script_basename,
9092

9193

9294
@support.force_not_colorized_test_class
93-
class CmdLineTest(unittest.TestCase):
95+
@patch_gethistoryfile()
96+
class CmdLineTest(EnsureSafeUserHistory, unittest.TestCase):
9497
def _check_output(self, script_name, exit_code, data,
9598
expected_file, expected_argv0,
9699
expected_path0, expected_package,

Lib/test/test_pyrepl/test_pyrepl.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from test.support import has_subprocess_support, SHORT_TIMEOUT, STDLIB_DIR
1717
from test.support.import_helper import import_module
1818
from test.support.os_helper import EnvironmentVarGuard, unlink
19+
from test.support.script_helper import EnsureSafeUserHistory
1920

2021
from .support import (
2122
FakeConsole,
@@ -45,7 +46,7 @@
4546
pty = None
4647

4748

48-
class ReplTestCase(TestCase):
49+
class ReplTestCase(EnsureSafeUserHistory, TestCase):
4950
def setUp(self):
5051
if not has_subprocess_support:
5152
raise SkipTest("test module requires subprocess")
@@ -97,10 +98,11 @@ def _run_repl(
9798
env["PYTHON_HISTORY"] = os.path.join(cwd, ".regrtest_history")
9899
if cmdline_args is not None:
99100
if "PYTHON_HISTORY" in env:
100-
self.assertNotIn(
101-
"-I", cmdline_args,
102-
"PYTHON_HISTORY will be ignored by -I"
103-
)
101+
for bad_option in ('-I', '-E'):
102+
self.assertNotIn(
103+
bad_option, cmdline_args,
104+
f"PYTHON_HISTORY will be ignored by {bad_option}"
105+
)
104106
cmd.extend(cmdline_args)
105107

106108
try:

Lib/test/test_repl.py

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
SuppressCrashReport,
1515
SHORT_TIMEOUT,
1616
)
17-
from test.support.script_helper import kill_python
17+
from test.support.script_helper import kill_python, EnsureSafeUserHistory
1818
from test.support.import_helper import import_module
1919

2020
try:
@@ -71,7 +71,7 @@ def run_on_interactive_mode(source):
7171

7272

7373
@support.force_not_colorized_test_class
74-
class TestInteractiveInterpreter(unittest.TestCase):
74+
class TestInteractiveInterpreter(EnsureSafeUserHistory, unittest.TestCase):
7575

7676
@cpython_only
7777
# Python built with Py_TRACE_REFS fail with a fatal error in
@@ -303,42 +303,44 @@ def test_asyncio_repl_reaches_python_startup_script(self):
303303

304304
@unittest.skipUnless(pty, "requires pty")
305305
def test_asyncio_repl_is_ok(self):
306-
m, s = pty.openpty()
307-
cmd = [sys.executable, "-I", "-m", "asyncio"]
308-
env = os.environ.copy()
309-
proc = subprocess.Popen(
310-
cmd,
311-
stdin=s,
312-
stdout=s,
313-
stderr=s,
314-
text=True,
315-
close_fds=True,
316-
env=env,
317-
)
318-
os.close(s)
319-
os.write(m, b"await asyncio.sleep(0)\n")
320-
os.write(m, b"exit()\n")
321-
output = []
322-
while select.select([m], [], [], SHORT_TIMEOUT)[0]:
323-
try:
324-
data = os.read(m, 1024).decode("utf-8")
325-
if not data:
306+
with os_helper.temp_dir() as tmpdir:
307+
m, s = pty.openpty()
308+
cmd = [sys.executable, "-m", "asyncio"]
309+
env = os.environ.copy()
310+
env["PYTHON_HISTORY"] = os.path.join(tmpdir, ".asyncio_history")
311+
proc = subprocess.Popen(
312+
cmd,
313+
stdin=s,
314+
stdout=s,
315+
stderr=s,
316+
text=True,
317+
close_fds=True,
318+
env=env,
319+
)
320+
os.close(s)
321+
os.write(m, b"await asyncio.sleep(0)\n")
322+
os.write(m, b"exit()\n")
323+
output = []
324+
while select.select([m], [], [], SHORT_TIMEOUT)[0]:
325+
try:
326+
data = os.read(m, 1024).decode("utf-8")
327+
if not data:
328+
break
329+
except OSError:
326330
break
327-
except OSError:
328-
break
329-
output.append(data)
330-
os.close(m)
331-
try:
332-
exit_code = proc.wait(timeout=SHORT_TIMEOUT)
333-
except subprocess.TimeoutExpired:
334-
proc.kill()
335-
exit_code = proc.wait()
331+
output.append(data)
332+
os.close(m)
333+
try:
334+
exit_code = proc.wait(timeout=SHORT_TIMEOUT)
335+
except subprocess.TimeoutExpired:
336+
proc.kill()
337+
exit_code = proc.wait()
336338

337-
self.assertEqual(exit_code, 0, "".join(output))
339+
self.assertEqual(exit_code, 0, "".join(output))
338340

339341

340342
@support.force_not_colorized_test_class
341-
class TestInteractiveModeSyntaxErrors(unittest.TestCase):
343+
class TestInteractiveModeSyntaxErrors(EnsureSafeUserHistory, unittest.TestCase):
342344

343345
def test_interactive_syntax_error_correct_line(self):
344346
output = run_on_interactive_mode(dedent("""\
@@ -356,7 +358,7 @@ def f():
356358
self.assertEqual(traceback_lines, expected_lines)
357359

358360

359-
class TestAsyncioREPL(unittest.TestCase):
361+
class TestAsyncioREPL(EnsureSafeUserHistory, unittest.TestCase):
360362
def test_multiple_statements_fail_early(self):
361363
user_input = "1 / 0; print(f'afterwards: {1+1}')"
362364
p = spawn_repl("-m", "asyncio")

0 commit comments

Comments
 (0)