blob: 8d6d3a7bd806ca02d3a975b4908d31d34268bf93 [file] [log] [blame]
Serhiy Storchaka3c5fa562016-06-05 10:32:57 +03001import pickle
Serhiy Storchaka41e9ad12016-06-04 23:27:26 +03002import unittest
3from test import support
Hai Shi96a6a6d2020-07-09 21:25:10 +08004from test.support import import_helper
Hai Shi79bb2c92020-08-06 19:51:29 +08005from test.support import os_helper
Serhiy Storchaka41e9ad12016-06-04 23:27:26 +03006
Hai Shi96a6a6d2020-07-09 21:25:10 +08007
8turtle = import_helper.import_module('turtle')
Serhiy Storchaka41e9ad12016-06-04 23:27:26 +03009Vec2D = turtle.Vec2D
10
11test_config = """\
12width = 0.75
13height = 0.8
14canvwidth = 500
15canvheight = 200
16leftright = 100
17topbottom = 100
18mode = world
19colormode = 255
20delay = 100
21undobuffersize = 10000
22shape = circle
23pencolor = red
24fillcolor = blue
25resizemode = auto
26visible = None
27language = english
28exampleturtle = turtle
29examplescreen = screen
30title = Python Turtle Graphics
31using_IDLE = ''
32"""
33
34test_config_two = """\
35# Comments!
36# Testing comments!
37pencolor = red
38fillcolor = blue
39visible = False
40language = english
41# Some more
42# comments
43using_IDLE = False
44"""
45
46invalid_test_config = """
47pencolor = red
48fillcolor: blue
49visible = False
50"""
51
52
53class TurtleConfigTest(unittest.TestCase):
54
55 def get_cfg_file(self, cfg_str):
Hai Shi79bb2c92020-08-06 19:51:29 +080056 self.addCleanup(os_helper.unlink, os_helper.TESTFN)
57 with open(os_helper.TESTFN, 'w') as f:
Serhiy Storchaka41e9ad12016-06-04 23:27:26 +030058 f.write(cfg_str)
Hai Shi79bb2c92020-08-06 19:51:29 +080059 return os_helper.TESTFN
Serhiy Storchaka41e9ad12016-06-04 23:27:26 +030060
61 def test_config_dict(self):
62
63 cfg_name = self.get_cfg_file(test_config)
64 parsed_cfg = turtle.config_dict(cfg_name)
65
66 expected = {
67 'width' : 0.75,
68 'height' : 0.8,
69 'canvwidth' : 500,
70 'canvheight': 200,
71 'leftright': 100,
72 'topbottom': 100,
73 'mode': 'world',
74 'colormode': 255,
75 'delay': 100,
76 'undobuffersize': 10000,
77 'shape': 'circle',
78 'pencolor' : 'red',
79 'fillcolor' : 'blue',
80 'resizemode' : 'auto',
81 'visible' : None,
82 'language': 'english',
83 'exampleturtle': 'turtle',
84 'examplescreen': 'screen',
85 'title': 'Python Turtle Graphics',
86 'using_IDLE': '',
87 }
88
89 self.assertEqual(parsed_cfg, expected)
90
Min ho Kimc4cacc82019-07-31 08:16:13 +100091 def test_partial_config_dict_with_comments(self):
Serhiy Storchaka41e9ad12016-06-04 23:27:26 +030092
93 cfg_name = self.get_cfg_file(test_config_two)
94 parsed_cfg = turtle.config_dict(cfg_name)
95
96 expected = {
97 'pencolor': 'red',
98 'fillcolor': 'blue',
99 'visible': False,
100 'language': 'english',
101 'using_IDLE': False,
102 }
103
104 self.assertEqual(parsed_cfg, expected)
105
106 def test_config_dict_invalid(self):
107
108 cfg_name = self.get_cfg_file(invalid_test_config)
109
110 with support.captured_stdout() as stdout:
111 parsed_cfg = turtle.config_dict(cfg_name)
112
113 err_msg = stdout.getvalue()
114
115 self.assertIn('Bad line in config-file ', err_msg)
116 self.assertIn('fillcolor: blue', err_msg)
117
118 self.assertEqual(parsed_cfg, {
119 'pencolor': 'red',
120 'visible': False,
121 })
122
123
124class VectorComparisonMixin:
125
126 def assertVectorsAlmostEqual(self, vec1, vec2):
127 if len(vec1) != len(vec2):
128 self.fail("Tuples are not of equal size")
129 for idx, (i, j) in enumerate(zip(vec1, vec2)):
130 self.assertAlmostEqual(
131 i, j, msg='values at index {} do not match'.format(idx))
132
Serhiy Storchakafd4cafd2020-09-07 18:55:22 +0300133class Multiplier:
134
135 def __mul__(self, other):
136 return f'M*{other}'
137
138 def __rmul__(self, other):
139 return f'{other}*M'
140
Serhiy Storchaka41e9ad12016-06-04 23:27:26 +0300141
142class TestVec2D(VectorComparisonMixin, unittest.TestCase):
143
Serhiy Storchaka3c5fa562016-06-05 10:32:57 +0300144 def test_constructor(self):
145 vec = Vec2D(0.5, 2)
146 self.assertEqual(vec[0], 0.5)
147 self.assertEqual(vec[1], 2)
148 self.assertIsInstance(vec, Vec2D)
149
150 self.assertRaises(TypeError, Vec2D)
151 self.assertRaises(TypeError, Vec2D, 0)
152 self.assertRaises(TypeError, Vec2D, (0, 1))
153 self.assertRaises(TypeError, Vec2D, vec)
154 self.assertRaises(TypeError, Vec2D, 0, 1, 2)
155
156 def test_repr(self):
157 vec = Vec2D(0.567, 1.234)
158 self.assertEqual(repr(vec), '(0.57,1.23)')
159
160 def test_equality(self):
161 vec1 = Vec2D(0, 1)
162 vec2 = Vec2D(0.0, 1)
163 vec3 = Vec2D(42, 1)
164 self.assertEqual(vec1, vec2)
165 self.assertEqual(vec1, tuple(vec1))
166 self.assertEqual(tuple(vec1), vec1)
167 self.assertNotEqual(vec1, vec3)
168 self.assertNotEqual(vec2, vec3)
169
170 def test_pickling(self):
171 vec = Vec2D(0.5, 2)
172 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
173 with self.subTest(proto=proto):
174 pickled = pickle.dumps(vec, protocol=proto)
175 unpickled = pickle.loads(pickled)
176 self.assertEqual(unpickled, vec)
177 self.assertIsInstance(unpickled, Vec2D)
178
Serhiy Storchaka41e9ad12016-06-04 23:27:26 +0300179 def _assert_arithmetic_cases(self, test_cases, lambda_operator):
180 for test_case in test_cases:
181 with self.subTest(case=test_case):
182
183 ((first, second), expected) = test_case
184
185 op1 = Vec2D(*first)
186 op2 = Vec2D(*second)
187
188 result = lambda_operator(op1, op2)
189
190 expected = Vec2D(*expected)
191
192 self.assertVectorsAlmostEqual(result, expected)
193
194 def test_vector_addition(self):
195
196 test_cases = [
197 (((0, 0), (1, 1)), (1.0, 1.0)),
198 (((-1, 0), (2, 2)), (1, 2)),
199 (((1.5, 0), (1, 1)), (2.5, 1)),
200 ]
201
202 self._assert_arithmetic_cases(test_cases, lambda x, y: x + y)
203
204 def test_vector_subtraction(self):
205
206 test_cases = [
207 (((0, 0), (1, 1)), (-1, -1)),
208 (((10.625, 0.125), (10, 0)), (0.625, 0.125)),
209 ]
210
211 self._assert_arithmetic_cases(test_cases, lambda x, y: x - y)
212
213 def test_vector_multiply(self):
214
215 vec1 = Vec2D(10, 10)
216 vec2 = Vec2D(0.5, 3)
217 answer = vec1 * vec2
218 expected = 35
219 self.assertAlmostEqual(answer, expected)
220
221 vec = Vec2D(0.5, 3)
Serhiy Storchaka41e9ad12016-06-04 23:27:26 +0300222 expected = Vec2D(5, 30)
Serhiy Storchakafd4cafd2020-09-07 18:55:22 +0300223 self.assertVectorsAlmostEqual(vec * 10, expected)
224 self.assertVectorsAlmostEqual(10 * vec, expected)
225 self.assertVectorsAlmostEqual(vec * 10.0, expected)
226 self.assertVectorsAlmostEqual(10.0 * vec, expected)
227
228 M = Multiplier()
229 self.assertEqual(vec * M, Vec2D(f"{vec[0]}*M", f"{vec[1]}*M"))
230 self.assertEqual(M * vec, f'M*{vec}')
Serhiy Storchaka41e9ad12016-06-04 23:27:26 +0300231
232 def test_vector_negative(self):
233 vec = Vec2D(10, -10)
234 expected = (-10, 10)
235 self.assertVectorsAlmostEqual(-vec, expected)
236
237 def test_distance(self):
Miss Islington (bot)16a174f2021-07-26 08:55:50 -0700238 self.assertEqual(abs(Vec2D(6, 8)), 10)
239 self.assertEqual(abs(Vec2D(0, 0)), 0)
240 self.assertAlmostEqual(abs(Vec2D(2.5, 6)), 6.5)
Serhiy Storchaka41e9ad12016-06-04 23:27:26 +0300241
242 def test_rotate(self):
243
244 cases = [
245 (((0, 0), 0), (0, 0)),
246 (((0, 1), 90), (-1, 0)),
247 (((0, 1), -90), (1, 0)),
248 (((1, 0), 180), (-1, 0)),
249 (((1, 0), 360), (1, 0)),
250 ]
251
252 for case in cases:
253 with self.subTest(case=case):
254 (vec, rot), expected = case
255 vec = Vec2D(*vec)
256 got = vec.rotate(rot)
257 self.assertVectorsAlmostEqual(got, expected)
258
259
260class TestTNavigator(VectorComparisonMixin, unittest.TestCase):
261
262 def setUp(self):
263 self.nav = turtle.TNavigator()
264
265 def test_goto(self):
266 self.nav.goto(100, -100)
267 self.assertAlmostEqual(self.nav.xcor(), 100)
268 self.assertAlmostEqual(self.nav.ycor(), -100)
269
270 def test_pos(self):
271 self.assertEqual(self.nav.pos(), self.nav._position)
272 self.nav.goto(100, -100)
273 self.assertEqual(self.nav.pos(), self.nav._position)
274
275 def test_left(self):
276 self.assertEqual(self.nav._orient, (1.0, 0))
277 self.nav.left(90)
278 self.assertVectorsAlmostEqual(self.nav._orient, (0.0, 1.0))
279
280 def test_right(self):
281 self.assertEqual(self.nav._orient, (1.0, 0))
282 self.nav.right(90)
283 self.assertVectorsAlmostEqual(self.nav._orient, (0, -1.0))
284
285 def test_reset(self):
286 self.nav.goto(100, -100)
287 self.assertAlmostEqual(self.nav.xcor(), 100)
288 self.assertAlmostEqual(self.nav.ycor(), -100)
289 self.nav.reset()
290 self.assertAlmostEqual(self.nav.xcor(), 0)
291 self.assertAlmostEqual(self.nav.ycor(), 0)
292
293 def test_forward(self):
294 self.nav.forward(150)
295 expected = Vec2D(150, 0)
296 self.assertVectorsAlmostEqual(self.nav.position(), expected)
297
298 self.nav.reset()
299 self.nav.left(90)
300 self.nav.forward(150)
301 expected = Vec2D(0, 150)
302 self.assertVectorsAlmostEqual(self.nav.position(), expected)
303
304 self.assertRaises(TypeError, self.nav.forward, 'skldjfldsk')
305
306 def test_backwards(self):
307 self.nav.back(200)
308 expected = Vec2D(-200, 0)
309 self.assertVectorsAlmostEqual(self.nav.position(), expected)
310
311 self.nav.reset()
312 self.nav.right(90)
313 self.nav.back(200)
314 expected = Vec2D(0, 200)
315 self.assertVectorsAlmostEqual(self.nav.position(), expected)
316
317 def test_distance(self):
318 self.nav.forward(100)
319 expected = 100
320 self.assertAlmostEqual(self.nav.distance(Vec2D(0,0)), expected)
321
322 def test_radians_and_degrees(self):
323 self.nav.left(90)
324 self.assertAlmostEqual(self.nav.heading(), 90)
325 self.nav.radians()
326 self.assertAlmostEqual(self.nav.heading(), 1.57079633)
327 self.nav.degrees()
328 self.assertAlmostEqual(self.nav.heading(), 90)
329
330 def test_towards(self):
331
332 coordinates = [
333 # coordinates, expected
334 ((100, 0), 0.0),
335 ((100, 100), 45.0),
336 ((0, 100), 90.0),
337 ((-100, 100), 135.0),
338 ((-100, 0), 180.0),
339 ((-100, -100), 225.0),
340 ((0, -100), 270.0),
341 ((100, -100), 315.0),
342 ]
343
344 for (x, y), expected in coordinates:
345 self.assertEqual(self.nav.towards(x, y), expected)
346 self.assertEqual(self.nav.towards((x, y)), expected)
347 self.assertEqual(self.nav.towards(Vec2D(x, y)), expected)
348
349 def test_heading(self):
350
351 self.nav.left(90)
352 self.assertAlmostEqual(self.nav.heading(), 90)
353 self.nav.left(45)
354 self.assertAlmostEqual(self.nav.heading(), 135)
355 self.nav.right(1.6)
356 self.assertAlmostEqual(self.nav.heading(), 133.4)
357 self.assertRaises(TypeError, self.nav.right, 'sdkfjdsf')
358 self.nav.reset()
359
360 rotations = [10, 20, 170, 300]
361 result = sum(rotations) % 360
362 for num in rotations:
363 self.nav.left(num)
364 self.assertEqual(self.nav.heading(), result)
365 self.nav.reset()
366
367 result = (360-sum(rotations)) % 360
368 for num in rotations:
369 self.nav.right(num)
370 self.assertEqual(self.nav.heading(), result)
371 self.nav.reset()
372
373 rotations = [10, 20, -170, 300, -210, 34.3, -50.2, -10, -29.98, 500]
374 sum_so_far = 0
375 for num in rotations:
376 if num < 0:
377 self.nav.right(abs(num))
378 else:
379 self.nav.left(num)
380 sum_so_far += num
381 self.assertAlmostEqual(self.nav.heading(), sum_so_far % 360)
382
383 def test_setheading(self):
384 self.nav.setheading(102.32)
385 self.assertAlmostEqual(self.nav.heading(), 102.32)
386 self.nav.setheading(-123.23)
387 self.assertAlmostEqual(self.nav.heading(), (-123.23) % 360)
388 self.nav.setheading(-1000.34)
389 self.assertAlmostEqual(self.nav.heading(), (-1000.34) % 360)
390 self.nav.setheading(300000)
391 self.assertAlmostEqual(self.nav.heading(), 300000%360)
392
393 def test_positions(self):
394 self.nav.forward(100)
395 self.nav.left(90)
396 self.nav.forward(-200)
397 self.assertVectorsAlmostEqual(self.nav.pos(), (100.0, -200.0))
398
399 def test_setx_and_sety(self):
400 self.nav.setx(-1023.2334)
401 self.nav.sety(193323.234)
402 self.assertVectorsAlmostEqual(self.nav.pos(), (-1023.2334, 193323.234))
403
404 def test_home(self):
405 self.nav.left(30)
406 self.nav.forward(-100000)
407 self.nav.home()
408 self.assertVectorsAlmostEqual(self.nav.pos(), (0,0))
409 self.assertAlmostEqual(self.nav.heading(), 0)
410
411 def test_distance_method(self):
412 self.assertAlmostEqual(self.nav.distance(30, 40), 50)
413 vec = Vec2D(0.22, .001)
414 self.assertAlmostEqual(self.nav.distance(vec), 0.22000227271553355)
415 another_turtle = turtle.TNavigator()
416 another_turtle.left(90)
417 another_turtle.forward(10000)
418 self.assertAlmostEqual(self.nav.distance(another_turtle), 10000)
419
420
421class TestTPen(unittest.TestCase):
422
423 def test_pendown_and_penup(self):
424
425 tpen = turtle.TPen()
426
427 self.assertTrue(tpen.isdown())
428 tpen.penup()
429 self.assertFalse(tpen.isdown())
430 tpen.pendown()
431 self.assertTrue(tpen.isdown())
432
433 def test_showturtle_hideturtle_and_isvisible(self):
434
435 tpen = turtle.TPen()
436
437 self.assertTrue(tpen.isvisible())
438 tpen.hideturtle()
439 self.assertFalse(tpen.isvisible())
440 tpen.showturtle()
441 self.assertTrue(tpen.isvisible())
442
443
444if __name__ == '__main__':
445 unittest.main()