|
18 | 18 | importtextwrap |
19 | 19 | importtypes |
20 | 20 | importunittest |
| 21 | +importunittest.mockasmock |
21 | 22 | importwarnings |
22 | 23 | importweakref |
23 | 24 |
|
| 25 | +fromcontextlibimportnullcontext |
24 | 26 | fromfunctoolsimportpartial |
25 | 27 | fromitertoolsimportproduct,islice |
26 | 28 | fromtestimportsupport |
|
121 | 123 | </foo> |
122 | 124 | """ |
123 | 125 |
|
| 126 | +defis_python_implementation(): |
| 127 | +assertETisnotNone,"ET must be initialized" |
| 128 | +assertpyETisnotNone,"pyET must be initialized" |
| 129 | +returnETispyET |
| 130 | + |
| 131 | + |
| 132 | +defequal_wrapper(cls): |
| 133 | +"""Mock cls.__eq__ to check whether it has been called or not. |
| 134 | +
|
| 135 | + The behaviour of cls.__eq__ (side-effects included) is left as is. |
| 136 | + """ |
| 137 | +eq=cls.__eq__ |
| 138 | +returnmock.patch.object(cls,"__eq__",autospec=True,wraps=eq) |
| 139 | + |
| 140 | + |
124 | 141 | defcheckwarnings(*filters,quiet=False): |
125 | 142 | defdecorator(test): |
126 | 143 | defnewtest(*args,**kwargs): |
@@ -2562,6 +2579,7 @@ def test_pickle_issue18997(self): |
2562 | 2579 |
|
2563 | 2580 |
|
2564 | 2581 | classBadElementTest(ElementTestCase,unittest.TestCase): |
| 2582 | + |
2565 | 2583 | deftest_extend_mutable_list(self): |
2566 | 2584 | classX: |
2567 | 2585 | @property |
@@ -2600,18 +2618,168 @@ class Y(X, ET.Element): |
2600 | 2618 | e=ET.Element('foo') |
2601 | 2619 | e.extend(L) |
2602 | 2620 |
|
2603 | | -deftest_remove_with_mutating(self): |
2604 | | -classX(ET.Element): |
| 2621 | +deftest_remove_with_clear_assume_missing(self): |
| 2622 | +# gh-126033: Check that a concurrent clear() for an assumed-to-be |
| 2623 | +# missing element does not make the interpreter crash. |
| 2624 | +self.do_test_remove_with_clear(raises=True) |
| 2625 | + |
| 2626 | +deftest_remove_with_clear_assume_existing(self): |
| 2627 | +# gh-126033: Check that a concurrent clear() for an assumed-to-be |
| 2628 | +# existing element does not make the interpreter crash. |
| 2629 | +self.do_test_remove_with_clear(raises=False) |
| 2630 | + |
| 2631 | +defdo_test_remove_with_clear(self,*,raises): |
| 2632 | + |
| 2633 | +# Until the discrepency between "del root[:]" and "root.clear()" is |
| 2634 | +# resolved, we need to keep two tests. Previously, using "del root[:]" |
| 2635 | +# did not crash with the reproducer of gh-126033 while "root.clear()" |
| 2636 | +# did. |
| 2637 | + |
| 2638 | +classE(ET.Element): |
| 2639 | +"""Local class to be able to mock E.__eq__ for introspection.""" |
| 2640 | + |
| 2641 | +classX(E): |
2605 | 2642 | def__eq__(self,o): |
2606 | | -dele[:] |
2607 | | -returnFalse |
2608 | | -e=ET.Element('foo') |
2609 | | -e.extend([X('bar')]) |
2610 | | -self.assertRaises(ValueError,e.remove,ET.Element('baz')) |
| 2643 | +delroot[:] |
| 2644 | +returnnotraises |
2611 | 2645 |
|
2612 | | -e=ET.Element('foo') |
2613 | | -e.extend([ET.Element('bar')]) |
2614 | | -self.assertRaises(ValueError,e.remove,X('baz')) |
| 2646 | +classY(E): |
| 2647 | +def__eq__(self,o): |
| 2648 | +root.clear() |
| 2649 | +returnnotraises |
| 2650 | + |
| 2651 | +ifraises: |
| 2652 | +get_checker_context=lambda:self.assertRaises(ValueError) |
| 2653 | +else: |
| 2654 | +get_checker_context=nullcontext |
| 2655 | + |
| 2656 | +self.assertIs(E.__eq__,object.__eq__) |
| 2657 | + |
| 2658 | +forZ,side_effectin [(X,'del root[:]'), (Y,'root.clear()')]: |
| 2659 | +self.enterContext(self.subTest(side_effect=side_effect)) |
| 2660 | + |
| 2661 | +# test removing R() from [U()] |
| 2662 | +forR,U,descriptionin [ |
| 2663 | + (E,Z,"remove missing E() from [Z()]"), |
| 2664 | + (Z,E,"remove missing Z() from [E()]"), |
| 2665 | + (Z,Z,"remove missing Z() from [Z()]"), |
| 2666 | + ]: |
| 2667 | +withself.subTest(description): |
| 2668 | +root=E('top') |
| 2669 | +root.extend([U('one')]) |
| 2670 | +withget_checker_context(): |
| 2671 | +root.remove(R('missing')) |
| 2672 | + |
| 2673 | +# test removing R() from [U(), V()] |
| 2674 | +cases=self.cases_for_remove_missing_with_mutations(E,Z) |
| 2675 | +forR,U,V,descriptionincases: |
| 2676 | +withself.subTest(description): |
| 2677 | +root=E('top') |
| 2678 | +root.extend([U('one'),V('two')]) |
| 2679 | +withget_checker_context(): |
| 2680 | +root.remove(R('missing')) |
| 2681 | + |
| 2682 | +# Test removing root[0] from [Z()]. |
| 2683 | +# |
| 2684 | +# Since we call root.remove() with root[0], Z.__eq__() |
| 2685 | +# will not be called (we branch on the fast Py_EQ path). |
| 2686 | +withself.subTest("remove root[0] from [Z()]"): |
| 2687 | +root=E('top') |
| 2688 | +root.append(Z('rem')) |
| 2689 | +withequal_wrapper(E)asf,equal_wrapper(Z)asg: |
| 2690 | +root.remove(root[0]) |
| 2691 | +f.assert_not_called() |
| 2692 | +g.assert_not_called() |
| 2693 | + |
| 2694 | +# Test removing root[1] (of type R) from [U(), R()]. |
| 2695 | +is_special=is_python_implementation()andraisesandZisY |
| 2696 | +ifis_python_implementation()andraisesandZisY: |
| 2697 | +# In pure Python, using root.clear() sets the children |
| 2698 | +# list to [] without calling list.clear(). |
| 2699 | +# |
| 2700 | +# For this reason, the call to root.remove() first |
| 2701 | +# checks root[0] and sets the children list to [] |
| 2702 | +# since either root[0] or root[1] is an evil element. |
| 2703 | +# |
| 2704 | +# Since checking root[1] still uses the old reference |
| 2705 | +# to the children list, PyObject_RichCompareBool() branches |
| 2706 | +# to the fast Py_EQ path and Y.__eq__() is called exactly |
| 2707 | +# once (when checking root[0]). |
| 2708 | +continue |
| 2709 | +else: |
| 2710 | +cases=self.cases_for_remove_existing_with_mutations(E,Z) |
| 2711 | +forR,U,descriptionincases: |
| 2712 | +withself.subTest(description): |
| 2713 | +root=E('top') |
| 2714 | +root.extend([U('one'),R('rem')]) |
| 2715 | +withget_checker_context(): |
| 2716 | +root.remove(root[1]) |
| 2717 | + |
| 2718 | +deftest_remove_with_mutate_root_assume_missing(self): |
| 2719 | +# gh-126033: Check that a concurrent mutation for an assumed-to-be |
| 2720 | +# missing element does not make the interpreter crash. |
| 2721 | +self.do_test_remove_with_mutate_root(raises=True) |
| 2722 | + |
| 2723 | +deftest_remove_with_mutate_root_assume_existing(self): |
| 2724 | +# gh-126033: Check that a concurrent mutation for an assumed-to-be |
| 2725 | +# existing element does not make the interpreter crash. |
| 2726 | +self.do_test_remove_with_mutate_root(raises=False) |
| 2727 | + |
| 2728 | +defdo_test_remove_with_mutate_root(self,*,raises): |
| 2729 | +E=ET.Element |
| 2730 | + |
| 2731 | +classZ(E): |
| 2732 | +def__eq__(self,o): |
| 2733 | +delroot[0] |
| 2734 | +returnnotraises |
| 2735 | + |
| 2736 | +ifraises: |
| 2737 | +get_checker_context=lambda:self.assertRaises(ValueError) |
| 2738 | +else: |
| 2739 | +get_checker_context=nullcontext |
| 2740 | + |
| 2741 | +# test removing R() from [U(), V()] |
| 2742 | +cases=self.cases_for_remove_missing_with_mutations(E,Z) |
| 2743 | +forR,U,V,descriptionincases: |
| 2744 | +withself.subTest(description): |
| 2745 | +root=E('top') |
| 2746 | +root.extend([U('one'),V('two')]) |
| 2747 | +withget_checker_context(): |
| 2748 | +root.remove(R('missing')) |
| 2749 | + |
| 2750 | +# test removing root[1] (of type R) from [U(), R()] |
| 2751 | +cases=self.cases_for_remove_existing_with_mutations(E,Z) |
| 2752 | +forR,U,descriptionincases: |
| 2753 | +withself.subTest(description): |
| 2754 | +root=E('top') |
| 2755 | +root.extend([U('one'),R('rem')]) |
| 2756 | +withget_checker_context(): |
| 2757 | +root.remove(root[1]) |
| 2758 | + |
| 2759 | +defcases_for_remove_missing_with_mutations(self,E,Z): |
| 2760 | +# Cases for removing R() from [U(), V()]. |
| 2761 | +# The case U = V = R = E is not interesting as there is no mutation. |
| 2762 | +forU,Vin [(E,Z), (Z,E), (Z,Z)]: |
| 2763 | +description= (f"remove missing{E.__name__}() from " |
| 2764 | +f"[{U.__name__}(),{V.__name__}()]") |
| 2765 | +yieldE,U,V,description |
| 2766 | + |
| 2767 | +forU,Vin [(E,E), (E,Z), (Z,E), (Z,Z)]: |
| 2768 | +description= (f"remove missing{Z.__name__}() from " |
| 2769 | +f"[{U.__name__}(),{V.__name__}()]") |
| 2770 | +yieldZ,U,V,description |
| 2771 | + |
| 2772 | +defcases_for_remove_existing_with_mutations(self,E,Z): |
| 2773 | +# Cases for removing root[1] (of type R) from [U(), R()]. |
| 2774 | +# The case U = R = E is not interesting as there is no mutation. |
| 2775 | +forU,R,descriptionin [ |
| 2776 | + (E,Z,"remove root[1] from [E(), Z()]"), |
| 2777 | + (Z,E,"remove root[1] from [Z(), E()]"), |
| 2778 | + (Z,Z,"remove root[1] from [Z(), Z()]"), |
| 2779 | + ]: |
| 2780 | +description= (f"remove root[1] (of type{R.__name__}) " |
| 2781 | +f"from [{U.__name__}(),{R.__name__}()]") |
| 2782 | +yieldR,U,description |
2615 | 2783 |
|
2616 | 2784 | @support.infinite_recursion(25) |
2617 | 2785 | deftest_recursive_repr(self): |
|