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