Skip to content

Commit 2e674b6

Browse files
jgarzikclaude
andcommitted
Add assertIsNone; import test_closures, test_dict_extra, test_tuple_extra, test_set_extra, test_list_extra
Infrastructure: - assertIsNone/assertIsNotNone added to unittest New tests: - test_closures.py: 11 tests — closures, nonlocal, nested scopes, lambda closures - test_dict_extra.py: 20 tests — constructor, get/set/del, keys/values/items, get/pop/update/setdefault/clear/copy, comprehension, fromkeys, nested - test_tuple_extra.py: 18 tests — indexing, slicing, concat, count, index, unpacking, star-unpacking, constructor, nested, mixed types - test_set_extra.py: 20 tests, 1 skip — add/discard/remove/pop/clear, union/intersection/difference/symmetric_difference, copy, comprehension, frozenset, isdisjoint - test_list_extra.py: 25 tests — append/extend/insert/remove/pop/index/count, reverse/sort/copy/clear, slicing, multiplication, comparison, self-ref repr Total CPython test suites: 42 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7d41b52 commit 2e674b6

7 files changed

Lines changed: 635 additions & 0 deletions

File tree

Makefile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,16 @@ gen-cpython-tests:
130130
@$(PYTHON) -m py_compile tests/cpython/test_builtin.py
131131
@echo "Compiling tests/cpython/test_types.py..."
132132
@$(PYTHON) -m py_compile tests/cpython/test_types.py
133+
@echo "Compiling tests/cpython/test_closures.py..."
134+
@$(PYTHON) -m py_compile tests/cpython/test_closures.py
135+
@echo "Compiling tests/cpython/test_dict_extra.py..."
136+
@$(PYTHON) -m py_compile tests/cpython/test_dict_extra.py
137+
@echo "Compiling tests/cpython/test_tuple_extra.py..."
138+
@$(PYTHON) -m py_compile tests/cpython/test_tuple_extra.py
139+
@echo "Compiling tests/cpython/test_set_extra.py..."
140+
@$(PYTHON) -m py_compile tests/cpython/test_set_extra.py
141+
@echo "Compiling tests/cpython/test_list_extra.py..."
142+
@$(PYTHON) -m py_compile tests/cpython/test_list_extra.py
133143
@echo "Done."
134144

135145
check-cpython: $(TARGET) gen-cpython-tests
@@ -207,3 +217,13 @@ check-cpython: $(TARGET) gen-cpython-tests
207217
@./apython tests/cpython/__pycache__/test_builtin.cpython-312.pyc
208218
@echo "Running CPython test_types.py..."
209219
@./apython tests/cpython/__pycache__/test_types.cpython-312.pyc
220+
@echo "Running CPython test_closures.py..."
221+
@./apython tests/cpython/__pycache__/test_closures.cpython-312.pyc
222+
@echo "Running CPython test_dict_extra.py..."
223+
@./apython tests/cpython/__pycache__/test_dict_extra.cpython-312.pyc
224+
@echo "Running CPython test_tuple_extra.py..."
225+
@./apython tests/cpython/__pycache__/test_tuple_extra.cpython-312.pyc
226+
@echo "Running CPython test_set_extra.py..."
227+
@./apython tests/cpython/__pycache__/test_set_extra.cpython-312.pyc
228+
@echo "Running CPython test_list_extra.py..."
229+
@./apython tests/cpython/__pycache__/test_list_extra.cpython-312.pyc

lib/unittest/case.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,16 @@ def assertIs(self, first, second, msg=None):
177177
raise AssertionError(msg)
178178
raise AssertionError("%r is not %r" % (first, second))
179179

180+
def assertIsNone(self, obj, msg=None):
181+
if obj is not None:
182+
if msg:
183+
raise AssertionError(msg)
184+
raise AssertionError("%r is not None" % (obj,))
185+
186+
def assertIsNotNone(self, obj, msg=None):
187+
if obj is None:
188+
raise AssertionError(msg or "unexpectedly None")
189+
180190
def assertIsNot(self, first, second, msg=None):
181191
if first is second:
182192
if msg:

tests/cpython/test_closures.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
"""Tests for closures and nonlocal — adapted from CPython tests"""
2+
3+
import unittest
4+
5+
6+
class ClosureTest(unittest.TestCase):
7+
8+
def test_basic_closure(self):
9+
def outer(x):
10+
def inner():
11+
return x
12+
return inner
13+
self.assertEqual(outer(42)(), 42)
14+
15+
def test_closure_mutation(self):
16+
def counter():
17+
n = 0
18+
def inc():
19+
nonlocal n
20+
n += 1
21+
return n
22+
return inc
23+
c = counter()
24+
self.assertEqual(c(), 1)
25+
self.assertEqual(c(), 2)
26+
self.assertEqual(c(), 3)
27+
28+
def test_multiple_closures(self):
29+
def make_pair(x):
30+
def get():
31+
return x
32+
def set_(val):
33+
nonlocal x
34+
x = val
35+
return get, set_
36+
g, s = make_pair(10)
37+
self.assertEqual(g(), 10)
38+
s(20)
39+
self.assertEqual(g(), 20)
40+
41+
def test_nested_closures(self):
42+
def outer(x):
43+
def middle(y):
44+
def inner():
45+
return x + y
46+
return inner
47+
return middle
48+
self.assertEqual(outer(10)(20)(), 30)
49+
50+
def test_closure_in_loop(self):
51+
funcs = []
52+
for i in range(5):
53+
def f(n=i):
54+
return n
55+
funcs.append(f)
56+
self.assertEqual([f() for f in funcs], [0, 1, 2, 3, 4])
57+
58+
def test_shared_closure(self):
59+
def make_adders():
60+
fns = []
61+
for i in range(3):
62+
def adder(x, i=i):
63+
return x + i
64+
fns.append(adder)
65+
return fns
66+
adders = make_adders()
67+
self.assertEqual(adders[0](10), 10)
68+
self.assertEqual(adders[1](10), 11)
69+
self.assertEqual(adders[2](10), 12)
70+
71+
def test_closure_over_param(self):
72+
def factory(greeting):
73+
def greet(name):
74+
return greeting + " " + name
75+
return greet
76+
hello = factory("hello")
77+
self.assertEqual(hello("world"), "hello world")
78+
79+
def test_nonlocal_in_nested(self):
80+
result = []
81+
def outer():
82+
x = 0
83+
def inner():
84+
nonlocal x
85+
x += 1
86+
result.append(x)
87+
inner()
88+
inner()
89+
inner()
90+
outer()
91+
self.assertEqual(result, [1, 2, 3])
92+
93+
def test_closure_with_defaults(self):
94+
def make_pow(exp):
95+
def power(base):
96+
return base ** exp
97+
return power
98+
square = make_pow(2)
99+
cube = make_pow(3)
100+
self.assertEqual(square(5), 25)
101+
self.assertEqual(cube(3), 27)
102+
103+
def test_lambda_closure(self):
104+
def make_adder(n):
105+
return lambda x: x + n
106+
add10 = make_adder(10)
107+
self.assertEqual(add10(5), 15)
108+
109+
def test_closure_survives_outer(self):
110+
def make():
111+
data = [1, 2, 3]
112+
def get():
113+
return data
114+
return get
115+
g = make()
116+
self.assertEqual(g(), [1, 2, 3])
117+
self.assertEqual(g(), [1, 2, 3])
118+
119+
120+
if __name__ == "__main__":
121+
unittest.main()

tests/cpython/test_dict_extra.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"""Extra dict tests — adapted from CPython test_dict.py"""
2+
3+
import unittest
4+
5+
6+
class DictTest(unittest.TestCase):
7+
8+
def test_constructor(self):
9+
self.assertEqual(dict(), {})
10+
self.assertEqual(dict({}), {})
11+
12+
def test_literal(self):
13+
d = {'a': 1, 'b': 2, 'c': 3}
14+
self.assertEqual(len(d), 3)
15+
self.assertEqual(d['a'], 1)
16+
17+
def test_setitem_getitem(self):
18+
d = {}
19+
d['key'] = 'value'
20+
self.assertEqual(d['key'], 'value')
21+
d['key'] = 'new'
22+
self.assertEqual(d['key'], 'new')
23+
24+
def test_delitem(self):
25+
d = {'a': 1, 'b': 2}
26+
del d['a']
27+
self.assertEqual(d, {'b': 2})
28+
with self.assertRaises(KeyError):
29+
del d['a']
30+
31+
def test_contains(self):
32+
d = {'a': 1, 'b': 2}
33+
self.assertIn('a', d)
34+
self.assertNotIn('c', d)
35+
36+
def test_len(self):
37+
self.assertEqual(len({}), 0)
38+
self.assertEqual(len({'a': 1}), 1)
39+
self.assertEqual(len({'a': 1, 'b': 2}), 2)
40+
41+
def test_keys_values_items(self):
42+
d = {'a': 1, 'b': 2}
43+
self.assertEqual(sorted(d.keys()), ['a', 'b'])
44+
self.assertEqual(sorted(d.values()), [1, 2])
45+
self.assertEqual(sorted(d.items()), [('a', 1), ('b', 2)])
46+
47+
def test_get(self):
48+
d = {'a': 1}
49+
self.assertEqual(d.get('a'), 1)
50+
self.assertIsNone(d.get('b'))
51+
self.assertEqual(d.get('b', 42), 42)
52+
53+
def test_pop(self):
54+
d = {'a': 1, 'b': 2}
55+
self.assertEqual(d.pop('a'), 1)
56+
self.assertEqual(d, {'b': 2})
57+
self.assertEqual(d.pop('c', 99), 99)
58+
self.assertRaises(KeyError, d.pop, 'c')
59+
60+
def test_update(self):
61+
d = {'a': 1}
62+
d.update({'b': 2, 'c': 3})
63+
self.assertEqual(d, {'a': 1, 'b': 2, 'c': 3})
64+
d.update({'a': 10})
65+
self.assertEqual(d['a'], 10)
66+
67+
def test_setdefault(self):
68+
d = {'a': 1}
69+
self.assertEqual(d.setdefault('a', 99), 1)
70+
self.assertEqual(d.setdefault('b', 99), 99)
71+
self.assertEqual(d['b'], 99)
72+
73+
def test_clear(self):
74+
d = {'a': 1, 'b': 2}
75+
d.clear()
76+
self.assertEqual(d, {})
77+
self.assertEqual(len(d), 0)
78+
79+
def test_copy(self):
80+
d = {'a': 1, 'b': [2, 3]}
81+
d2 = d.copy()
82+
self.assertEqual(d, d2)
83+
d2['a'] = 99
84+
self.assertEqual(d['a'], 1)
85+
86+
def test_iteration(self):
87+
d = {'a': 1, 'b': 2, 'c': 3}
88+
keys = []
89+
for k in d:
90+
keys.append(k)
91+
self.assertEqual(sorted(keys), ['a', 'b', 'c'])
92+
93+
def test_comprehension(self):
94+
d = {x: x**2 for x in range(5)}
95+
self.assertEqual(len(d), 5)
96+
self.assertEqual(d[3], 9)
97+
self.assertEqual(d[4], 16)
98+
99+
def test_equality(self):
100+
self.assertEqual({'a': 1}, {'a': 1})
101+
self.assertNotEqual({'a': 1}, {'a': 2})
102+
self.assertNotEqual({'a': 1}, {'b': 1})
103+
104+
def test_bool(self):
105+
self.assertFalse(bool({}))
106+
self.assertTrue(bool({'a': 1}))
107+
108+
def test_mixed_key_types(self):
109+
d = {1: 'int', 'a': 'str', (1, 2): 'tuple'}
110+
self.assertEqual(d[1], 'int')
111+
self.assertEqual(d['a'], 'str')
112+
self.assertEqual(d[(1, 2)], 'tuple')
113+
114+
def test_fromkeys(self):
115+
d = dict.fromkeys(['a', 'b', 'c'], 0)
116+
self.assertEqual(len(d), 3)
117+
self.assertEqual(d['a'], 0)
118+
self.assertEqual(d['c'], 0)
119+
120+
def test_nested(self):
121+
d = {'a': {'x': 1}, 'b': {'y': 2}}
122+
self.assertEqual(d['a']['x'], 1)
123+
self.assertEqual(d['b']['y'], 2)
124+
125+
126+
if __name__ == "__main__":
127+
unittest.main()

0 commit comments

Comments
 (0)