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_comments(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 130class Multiplier: 131 132 def __mul__(self, other): 133 return f'M*{other}' 134 135 def __rmul__(self, other): 136 return f'{other}*M' 137 138 139class TestVec2D(VectorComparisonMixin, unittest.TestCase): 140 141 def test_constructor(self): 142 vec = Vec2D(0.5, 2) 143 self.assertEqual(vec[0], 0.5) 144 self.assertEqual(vec[1], 2) 145 self.assertIsInstance(vec, Vec2D) 146 147 self.assertRaises(TypeError, Vec2D) 148 self.assertRaises(TypeError, Vec2D, 0) 149 self.assertRaises(TypeError, Vec2D, (0, 1)) 150 self.assertRaises(TypeError, Vec2D, vec) 151 self.assertRaises(TypeError, Vec2D, 0, 1, 2) 152 153 def test_repr(self): 154 vec = Vec2D(0.567, 1.234) 155 self.assertEqual(repr(vec), '(0.57,1.23)') 156 157 def test_equality(self): 158 vec1 = Vec2D(0, 1) 159 vec2 = Vec2D(0.0, 1) 160 vec3 = Vec2D(42, 1) 161 self.assertEqual(vec1, vec2) 162 self.assertEqual(vec1, tuple(vec1)) 163 self.assertEqual(tuple(vec1), vec1) 164 self.assertNotEqual(vec1, vec3) 165 self.assertNotEqual(vec2, vec3) 166 167 def test_pickling(self): 168 vec = Vec2D(0.5, 2) 169 for proto in range(pickle.HIGHEST_PROTOCOL + 1): 170 with self.subTest(proto=proto): 171 pickled = pickle.dumps(vec, protocol=proto) 172 unpickled = pickle.loads(pickled) 173 self.assertEqual(unpickled, vec) 174 self.assertIsInstance(unpickled, Vec2D) 175 176 def _assert_arithmetic_cases(self, test_cases, lambda_operator): 177 for test_case in test_cases: 178 with self.subTest(case=test_case): 179 180 ((first, second), expected) = test_case 181 182 op1 = Vec2D(*first) 183 op2 = Vec2D(*second) 184 185 result = lambda_operator(op1, op2) 186 187 expected = Vec2D(*expected) 188 189 self.assertVectorsAlmostEqual(result, expected) 190 191 def test_vector_addition(self): 192 193 test_cases = [ 194 (((0, 0), (1, 1)), (1.0, 1.0)), 195 (((-1, 0), (2, 2)), (1, 2)), 196 (((1.5, 0), (1, 1)), (2.5, 1)), 197 ] 198 199 self._assert_arithmetic_cases(test_cases, lambda x, y: x + y) 200 201 def test_vector_subtraction(self): 202 203 test_cases = [ 204 (((0, 0), (1, 1)), (-1, -1)), 205 (((10.625, 0.125), (10, 0)), (0.625, 0.125)), 206 ] 207 208 self._assert_arithmetic_cases(test_cases, lambda x, y: x - y) 209 210 def test_vector_multiply(self): 211 212 vec1 = Vec2D(10, 10) 213 vec2 = Vec2D(0.5, 3) 214 answer = vec1 * vec2 215 expected = 35 216 self.assertAlmostEqual(answer, expected) 217 218 vec = Vec2D(0.5, 3) 219 expected = Vec2D(5, 30) 220 self.assertVectorsAlmostEqual(vec * 10, expected) 221 self.assertVectorsAlmostEqual(10 * vec, expected) 222 self.assertVectorsAlmostEqual(vec * 10.0, expected) 223 self.assertVectorsAlmostEqual(10.0 * vec, expected) 224 225 M = Multiplier() 226 self.assertEqual(vec * M, Vec2D(f"{vec[0]}*M", f"{vec[1]}*M")) 227 self.assertEqual(M * vec, f'M*{vec}') 228 229 def test_vector_negative(self): 230 vec = Vec2D(10, -10) 231 expected = (-10, 10) 232 self.assertVectorsAlmostEqual(-vec, expected) 233 234 def test_distance(self): 235 vec = Vec2D(6, 8) 236 expected = 10 237 self.assertEqual(abs(vec), expected) 238 239 vec = Vec2D(0, 0) 240 expected = 0 241 self.assertEqual(abs(vec), expected) 242 243 vec = Vec2D(2.5, 6) 244 expected = 6.5 245 self.assertEqual(abs(vec), expected) 246 247 def test_rotate(self): 248 249 cases = [ 250 (((0, 0), 0), (0, 0)), 251 (((0, 1), 90), (-1, 0)), 252 (((0, 1), -90), (1, 0)), 253 (((1, 0), 180), (-1, 0)), 254 (((1, 0), 360), (1, 0)), 255 ] 256 257 for case in cases: 258 with self.subTest(case=case): 259 (vec, rot), expected = case 260 vec = Vec2D(*vec) 261 got = vec.rotate(rot) 262 self.assertVectorsAlmostEqual(got, expected) 263 264 265class TestTNavigator(VectorComparisonMixin, unittest.TestCase): 266 267 def setUp(self): 268 self.nav = turtle.TNavigator() 269 270 def test_goto(self): 271 self.nav.goto(100, -100) 272 self.assertAlmostEqual(self.nav.xcor(), 100) 273 self.assertAlmostEqual(self.nav.ycor(), -100) 274 275 def test_pos(self): 276 self.assertEqual(self.nav.pos(), self.nav._position) 277 self.nav.goto(100, -100) 278 self.assertEqual(self.nav.pos(), self.nav._position) 279 280 def test_left(self): 281 self.assertEqual(self.nav._orient, (1.0, 0)) 282 self.nav.left(90) 283 self.assertVectorsAlmostEqual(self.nav._orient, (0.0, 1.0)) 284 285 def test_right(self): 286 self.assertEqual(self.nav._orient, (1.0, 0)) 287 self.nav.right(90) 288 self.assertVectorsAlmostEqual(self.nav._orient, (0, -1.0)) 289 290 def test_reset(self): 291 self.nav.goto(100, -100) 292 self.assertAlmostEqual(self.nav.xcor(), 100) 293 self.assertAlmostEqual(self.nav.ycor(), -100) 294 self.nav.reset() 295 self.assertAlmostEqual(self.nav.xcor(), 0) 296 self.assertAlmostEqual(self.nav.ycor(), 0) 297 298 def test_forward(self): 299 self.nav.forward(150) 300 expected = Vec2D(150, 0) 301 self.assertVectorsAlmostEqual(self.nav.position(), expected) 302 303 self.nav.reset() 304 self.nav.left(90) 305 self.nav.forward(150) 306 expected = Vec2D(0, 150) 307 self.assertVectorsAlmostEqual(self.nav.position(), expected) 308 309 self.assertRaises(TypeError, self.nav.forward, 'skldjfldsk') 310 311 def test_backwards(self): 312 self.nav.back(200) 313 expected = Vec2D(-200, 0) 314 self.assertVectorsAlmostEqual(self.nav.position(), expected) 315 316 self.nav.reset() 317 self.nav.right(90) 318 self.nav.back(200) 319 expected = Vec2D(0, 200) 320 self.assertVectorsAlmostEqual(self.nav.position(), expected) 321 322 def test_distance(self): 323 self.nav.forward(100) 324 expected = 100 325 self.assertAlmostEqual(self.nav.distance(Vec2D(0,0)), expected) 326 327 def test_radians_and_degrees(self): 328 self.nav.left(90) 329 self.assertAlmostEqual(self.nav.heading(), 90) 330 self.nav.radians() 331 self.assertAlmostEqual(self.nav.heading(), 1.57079633) 332 self.nav.degrees() 333 self.assertAlmostEqual(self.nav.heading(), 90) 334 335 def test_towards(self): 336 337 coordinates = [ 338 # coordinates, expected 339 ((100, 0), 0.0), 340 ((100, 100), 45.0), 341 ((0, 100), 90.0), 342 ((-100, 100), 135.0), 343 ((-100, 0), 180.0), 344 ((-100, -100), 225.0), 345 ((0, -100), 270.0), 346 ((100, -100), 315.0), 347 ] 348 349 for (x, y), expected in coordinates: 350 self.assertEqual(self.nav.towards(x, y), expected) 351 self.assertEqual(self.nav.towards((x, y)), expected) 352 self.assertEqual(self.nav.towards(Vec2D(x, y)), expected) 353 354 def test_heading(self): 355 356 self.nav.left(90) 357 self.assertAlmostEqual(self.nav.heading(), 90) 358 self.nav.left(45) 359 self.assertAlmostEqual(self.nav.heading(), 135) 360 self.nav.right(1.6) 361 self.assertAlmostEqual(self.nav.heading(), 133.4) 362 self.assertRaises(TypeError, self.nav.right, 'sdkfjdsf') 363 self.nav.reset() 364 365 rotations = [10, 20, 170, 300] 366 result = sum(rotations) % 360 367 for num in rotations: 368 self.nav.left(num) 369 self.assertEqual(self.nav.heading(), result) 370 self.nav.reset() 371 372 result = (360-sum(rotations)) % 360 373 for num in rotations: 374 self.nav.right(num) 375 self.assertEqual(self.nav.heading(), result) 376 self.nav.reset() 377 378 rotations = [10, 20, -170, 300, -210, 34.3, -50.2, -10, -29.98, 500] 379 sum_so_far = 0 380 for num in rotations: 381 if num < 0: 382 self.nav.right(abs(num)) 383 else: 384 self.nav.left(num) 385 sum_so_far += num 386 self.assertAlmostEqual(self.nav.heading(), sum_so_far % 360) 387 388 def test_setheading(self): 389 self.nav.setheading(102.32) 390 self.assertAlmostEqual(self.nav.heading(), 102.32) 391 self.nav.setheading(-123.23) 392 self.assertAlmostEqual(self.nav.heading(), (-123.23) % 360) 393 self.nav.setheading(-1000.34) 394 self.assertAlmostEqual(self.nav.heading(), (-1000.34) % 360) 395 self.nav.setheading(300000) 396 self.assertAlmostEqual(self.nav.heading(), 300000%360) 397 398 def test_positions(self): 399 self.nav.forward(100) 400 self.nav.left(90) 401 self.nav.forward(-200) 402 self.assertVectorsAlmostEqual(self.nav.pos(), (100.0, -200.0)) 403 404 def test_setx_and_sety(self): 405 self.nav.setx(-1023.2334) 406 self.nav.sety(193323.234) 407 self.assertVectorsAlmostEqual(self.nav.pos(), (-1023.2334, 193323.234)) 408 409 def test_home(self): 410 self.nav.left(30) 411 self.nav.forward(-100000) 412 self.nav.home() 413 self.assertVectorsAlmostEqual(self.nav.pos(), (0,0)) 414 self.assertAlmostEqual(self.nav.heading(), 0) 415 416 def test_distance_method(self): 417 self.assertAlmostEqual(self.nav.distance(30, 40), 50) 418 vec = Vec2D(0.22, .001) 419 self.assertAlmostEqual(self.nav.distance(vec), 0.22000227271553355) 420 another_turtle = turtle.TNavigator() 421 another_turtle.left(90) 422 another_turtle.forward(10000) 423 self.assertAlmostEqual(self.nav.distance(another_turtle), 10000) 424 425 426class TestTPen(unittest.TestCase): 427 428 def test_pendown_and_penup(self): 429 430 tpen = turtle.TPen() 431 432 self.assertTrue(tpen.isdown()) 433 tpen.penup() 434 self.assertFalse(tpen.isdown()) 435 tpen.pendown() 436 self.assertTrue(tpen.isdown()) 437 438 def test_showturtle_hideturtle_and_isvisible(self): 439 440 tpen = turtle.TPen() 441 442 self.assertTrue(tpen.isvisible()) 443 tpen.hideturtle() 444 self.assertFalse(tpen.isvisible()) 445 tpen.showturtle() 446 self.assertTrue(tpen.isvisible()) 447 448 449if __name__ == '__main__': 450 unittest.main() 451