• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import pickle
2import unittest
3from test import support
4from test.support import import_helper
5from test.support import os_helper
6
7
8turtle = import_helper.import_module('turtle')
9Vec2D = 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):
56        self.addCleanup(os_helper.unlink, os_helper.TESTFN)
57        with open(os_helper.TESTFN, 'w') as f:
58            f.write(cfg_str)
59        return os_helper.TESTFN
60
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
91    def test_partial_config_dict_with_comments(self):
92
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
133class Multiplier:
134
135    def __mul__(self, other):
136        return f'M*{other}'
137
138    def __rmul__(self, other):
139        return f'{other}*M'
140
141
142class TestVec2D(VectorComparisonMixin, unittest.TestCase):
143
144    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
179    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)
222        expected = Vec2D(5, 30)
223        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}')
231
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):
238        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)
241
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()
446