|
17 | 17 | import types |
18 | 18 | import unittest |
19 | 19 | import warnings |
| 20 | +from contextlib import ExitStack |
20 | 21 | from operator import neg |
21 | | -from test.support import TESTFN, unlink, check_warnings |
| 22 | +from test.support import ( |
| 23 | + EnvironmentVarGuard, TESTFN, check_warnings, swap_attr, unlink) |
22 | 24 | from test.support.script_helper import assert_python_ok |
| 25 | +from unittest.mock import MagicMock, patch |
23 | 26 | try: |
24 | 27 | import pty, signal |
25 | 28 | except ImportError: |
@@ -1514,6 +1517,111 @@ def test_construct_singletons(self): |
1514 | 1517 | self.assertRaises(TypeError, tp, 1, 2) |
1515 | 1518 | self.assertRaises(TypeError, tp, a=1, b=2) |
1516 | 1519 |
|
| 1520 | + |
| 1521 | +class TestBreakpoint(unittest.TestCase): |
| 1522 | + def setUp(self): |
| 1523 | + # These tests require a clean slate environment. For example, if the |
| 1524 | + # test suite is run with $PYTHONBREAKPOINT set to something else, it |
| 1525 | + # will mess up these tests. Similarly for sys.breakpointhook. |
| 1526 | + # Cleaning the slate here means you can't use breakpoint() to debug |
| 1527 | + # these tests, but I think that's okay. Just use pdb.set_trace() if |
| 1528 | + # you must. |
| 1529 | + self.resources = ExitStack() |
| 1530 | + self.addCleanup(self.resources.close) |
| 1531 | + self.env = self.resources.enter_context(EnvironmentVarGuard()) |
| 1532 | + del self.env['PYTHONBREAKPOINT'] |
| 1533 | + self.resources.enter_context( |
| 1534 | + swap_attr(sys, 'breakpointhook', sys.__breakpointhook__)) |
| 1535 | + |
| 1536 | + def test_breakpoint(self): |
| 1537 | + with patch('pdb.set_trace') as mock: |
| 1538 | + breakpoint() |
| 1539 | + mock.assert_called_once() |
| 1540 | + |
| 1541 | + def test_breakpoint_with_breakpointhook_set(self): |
| 1542 | + my_breakpointhook = MagicMock() |
| 1543 | + sys.breakpointhook = my_breakpointhook |
| 1544 | + breakpoint() |
| 1545 | + my_breakpointhook.assert_called_once_with() |
| 1546 | + |
| 1547 | + def test_breakpoint_with_breakpointhook_reset(self): |
| 1548 | + my_breakpointhook = MagicMock() |
| 1549 | + sys.breakpointhook = my_breakpointhook |
| 1550 | + breakpoint() |
| 1551 | + my_breakpointhook.assert_called_once_with() |
| 1552 | + # Reset the hook and it will not be called again. |
| 1553 | + sys.breakpointhook = sys.__breakpointhook__ |
| 1554 | + with patch('pdb.set_trace') as mock: |
| 1555 | + breakpoint() |
| 1556 | + mock.assert_called_once_with() |
| 1557 | + my_breakpointhook.assert_called_once_with() |
| 1558 | + |
| 1559 | + def test_breakpoint_with_args_and_keywords(self): |
| 1560 | + my_breakpointhook = MagicMock() |
| 1561 | + sys.breakpointhook = my_breakpointhook |
| 1562 | + breakpoint(1, 2, 3, four=4, five=5) |
| 1563 | + my_breakpointhook.assert_called_once_with(1, 2, 3, four=4, five=5) |
| 1564 | + |
| 1565 | + def test_breakpoint_with_passthru_error(self): |
| 1566 | + def my_breakpointhook(): |
| 1567 | + pass |
| 1568 | + sys.breakpointhook = my_breakpointhook |
| 1569 | + self.assertRaises(TypeError, breakpoint, 1, 2, 3, four=4, five=5) |
| 1570 | + |
| 1571 | + @unittest.skipIf(sys.flags.ignore_environment, '-E was given') |
| 1572 | + def test_envar_good_path_builtin(self): |
| 1573 | + self.env['PYTHONBREAKPOINT'] = 'int' |
| 1574 | + with patch('builtins.int') as mock: |
| 1575 | + breakpoint('7') |
| 1576 | + mock.assert_called_once_with('7') |
| 1577 | + |
| 1578 | + @unittest.skipIf(sys.flags.ignore_environment, '-E was given') |
| 1579 | + def test_envar_good_path_other(self): |
| 1580 | + self.env['PYTHONBREAKPOINT'] = 'sys.exit' |
| 1581 | + with patch('sys.exit') as mock: |
| 1582 | + breakpoint() |
| 1583 | + mock.assert_called_once_with() |
| 1584 | + |
| 1585 | + @unittest.skipIf(sys.flags.ignore_environment, '-E was given') |
| 1586 | + def test_envar_good_path_noop_0(self): |
| 1587 | + self.env['PYTHONBREAKPOINT'] = '0' |
| 1588 | + with patch('pdb.set_trace') as mock: |
| 1589 | + breakpoint() |
| 1590 | + mock.assert_not_called() |
| 1591 | + |
| 1592 | + def test_envar_good_path_empty_string(self): |
| 1593 | + # PYTHONBREAKPOINT='' is the same as it not being set. |
| 1594 | + self.env['PYTHONBREAKPOINT'] = '' |
| 1595 | + with patch('pdb.set_trace') as mock: |
| 1596 | + breakpoint() |
| 1597 | + mock.assert_called_once_with() |
| 1598 | + |
| 1599 | + @unittest.skipIf(sys.flags.ignore_environment, '-E was given') |
| 1600 | + def test_envar_unimportable(self): |
| 1601 | + for envar in ( |
| 1602 | + '.', '..', '.foo', 'foo.', '.int', 'int.' |
| 1603 | + 'nosuchbuiltin', |
| 1604 | + 'nosuchmodule.nosuchcallable', |
| 1605 | + ): |
| 1606 | + with self.subTest(envar=envar): |
| 1607 | + self.env['PYTHONBREAKPOINT'] = envar |
| 1608 | + mock = self.resources.enter_context(patch('pdb.set_trace')) |
| 1609 | + w = self.resources.enter_context(check_warnings(quiet=True)) |
| 1610 | + breakpoint() |
| 1611 | + self.assertEqual( |
| 1612 | + str(w.message), |
| 1613 | + f'Ignoring unimportable $PYTHONBREAKPOINT: "{envar}"') |
| 1614 | + self.assertEqual(w.category, RuntimeWarning) |
| 1615 | + mock.assert_not_called() |
| 1616 | + |
| 1617 | + def test_envar_ignored_when_hook_is_set(self): |
| 1618 | + self.env['PYTHONBREAKPOINT'] = 'sys.exit' |
| 1619 | + with patch('sys.exit') as mock: |
| 1620 | + sys.breakpointhook = int |
| 1621 | + breakpoint() |
| 1622 | + mock.assert_not_called() |
| 1623 | + |
| 1624 | + |
1517 | 1625 | @unittest.skipUnless(pty, "the pty and signal modules must be available") |
1518 | 1626 | class PtyTests(unittest.TestCase): |
1519 | 1627 | """Tests that use a pseudo terminal to guarantee stdin and stdout are |
|
0 commit comments