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