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