test_turtle.py 13 KB

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