test_reference.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. # -*- coding: utf-8 -*-
  2. # tests/test_reference.py
  3. # Part of ‘manpage’, a Python library for making Unix manual documents.
  4. #
  5. # Copyright © 2015–2016 Ben Finney <ben+python@benfinney.id.au>
  6. #
  7. # This is free software: see the grant of license at end of this file.
  8. """ Unit tests for references within manual page documents. """
  9. import sys
  10. import os
  11. import os.path
  12. import operator
  13. import textwrap
  14. import unittest
  15. import unittest.mock
  16. __package__ = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
  17. __import__(__package__)
  18. sys.path.insert(1, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
  19. import manpage.document
  20. def setup_reference_instance(testcase, test_class=None, test_args=None):
  21. """ Set up a `Reference` instance. """
  22. if test_class is None:
  23. test_class = testcase.reference_class
  24. if test_args is None:
  25. test_args = testcase.test_args
  26. testcase.test_reference_instance = test_class(**test_args)
  27. class Reference_TestCase(unittest.TestCase):
  28. """ Test cases for class `Reference`. """
  29. reference_class = manpage.document.Reference
  30. scenarios = [
  31. ('simple', {
  32. 'test_args': {},
  33. 'expected_class': reference_class,
  34. }),
  35. ]
  36. def setUp(self):
  37. """ Set up test fixtures. """
  38. super().setUp()
  39. self.scenarios_by_name = dict(self.scenarios)
  40. def test_instantiate(self):
  41. """ An instance should be created. """
  42. for (scenario_name, scenario) in self.scenarios:
  43. with self.subTest(scenario=scenario_name):
  44. setup_reference_instance(self, test_args=scenario['test_args'])
  45. self.assertIsInstance(
  46. self.test_reference_instance,
  47. self.reference_class)
  48. def test_as_markup_not_implemented(self):
  49. """ Method `as_markup` should raise NotImplementedError. """
  50. scenario = self.scenarios_by_name['simple']
  51. instance = self.reference_class(**scenario['test_args'])
  52. with self.assertRaises(NotImplementedError):
  53. instance.as_markup()
  54. def test_comparison_tuple_not_implemented(self):
  55. """ Property `_comparison_tuple` should raise NotImplementedError. """
  56. scenario = self.scenarios_by_name['simple']
  57. instance = self.reference_class(**scenario['test_args'])
  58. with self.assertRaises(NotImplementedError):
  59. instance._comparison_tuple
  60. class Reference_comparison_TestCaseMixIn:
  61. """ Mix-in to add comparison test cases for a `Reference` class. """
  62. def test_comparisons_as_expected(self):
  63. """ Comparisons should be as expected. """
  64. for (scenario_name, scenario) in self.scenarios:
  65. with self.subTest(scenario=scenario_name):
  66. for (operator_name, operator_func) in (
  67. (name, getattr(operator, name))
  68. for name in scenario['expected_true']):
  69. with self.subTest(operator=operator_name):
  70. result = operator_func(
  71. scenario['instance_this'],
  72. scenario['instance_other'])
  73. self.assertTrue(result)
  74. for (operator_name, operator_func) in (
  75. (name, getattr(operator, name))
  76. for name in scenario['expected_false']):
  77. with self.subTest(operator=operator_name):
  78. result = operator_func(
  79. scenario['instance_this'],
  80. scenario['instance_other'])
  81. self.assertFalse(result)
  82. class DocumentReference_TestCase(unittest.TestCase):
  83. """ Test cases for class `DocumentReference`. """
  84. reference_class = manpage.document.DocumentReference
  85. def setUp(self):
  86. """ Set up test fixtures. """
  87. super().setUp()
  88. self.test_args = {
  89. 'name': "lorem",
  90. 'section': "99",
  91. }
  92. setup_reference_instance(self)
  93. def test_instantiate(self):
  94. """ An instance should be created. """
  95. self.assertIsInstance(
  96. self.test_reference_instance, self.reference_class)
  97. def test_has_specified_name(self):
  98. """ Should have `name` value specified. """
  99. self.assertEqual(
  100. self.test_reference_instance.name, self.test_args['name'])
  101. def test_has_specified_section(self):
  102. """ Should have `section` value specified. """
  103. self.assertEqual(
  104. self.test_reference_instance.section,
  105. self.test_args['section'])
  106. def test_text_representation_as_expected(self):
  107. """ Should have expected text representation. """
  108. expected_text = "{0.name} ({0.section})".format(
  109. self.test_reference_instance)
  110. self.assertEqual(
  111. str(self.test_reference_instance), expected_text)
  112. def test_programmer_representation_as_expected(self):
  113. """ Should have expected programmer representation. """
  114. class_name = self.reference_class.__name__
  115. expected_text = "{class_name}({name!r}, {section!r})".format(
  116. class_name=class_name,
  117. name=self.test_reference_instance.name,
  118. section=self.test_reference_instance.section)
  119. self.assertEqual(
  120. repr(self.test_reference_instance), expected_text)
  121. class DocumentReference_as_markup_TestCase(unittest.TestCase):
  122. """ Test cases for method `DocumentReference.as_markup`. """
  123. reference_class = manpage.document.DocumentReference
  124. scenarios = [
  125. ('simple', {
  126. 'test_args': {
  127. 'name': "lorem",
  128. 'section': "99",
  129. },
  130. 'expected_result': textwrap.dedent("""\
  131. .BR lorem (99)
  132. """),
  133. }),
  134. ('name-hyphenated', {
  135. 'test_args': {
  136. 'name': "lorem-ipsum",
  137. 'section': "99",
  138. },
  139. 'expected_result': textwrap.dedent("""\
  140. .BR lorem\\-ipsum (99)
  141. """),
  142. }),
  143. ]
  144. def test_returns_expected_result(self):
  145. """ Should return expected markup for inputs. """
  146. for (scenario_name, scenario) in self.scenarios:
  147. with self.subTest(scenario=scenario_name):
  148. setup_reference_instance(self, test_args=scenario['test_args'])
  149. expected_markup = textwrap.dedent("""\
  150. .BR {0[name]} ({0[section]})
  151. """).format(scenario['test_args'])
  152. result = self.test_reference_instance.as_markup()
  153. self.assertEqual(result, expected_markup)
  154. class DocumentReference_from_text_TestCase(unittest.TestCase):
  155. """ Test cases for method `DocumentReference.from_text`. """
  156. error_class = manpage.document.DocumentReference.ReferenceFormatError
  157. scenarios = [
  158. ('simple', {
  159. 'test_text': "lorem(99)",
  160. 'expected_instance': manpage.document.DocumentReference(
  161. "lorem", "99"),
  162. }),
  163. ('name-hyphenated', {
  164. 'test_text': "lorem-ipsum(99)",
  165. 'expected_instance': manpage.document.DocumentReference(
  166. "lorem-ipsum", "99"),
  167. }),
  168. ('section-contains-nondigit', {
  169. 'test_text': "lorem-ipsum(99xyz)",
  170. 'expected_instance': manpage.document.DocumentReference(
  171. "lorem-ipsum", "99xyz"),
  172. }),
  173. ('empty', {
  174. 'test_text': "",
  175. 'expected_error': error_class,
  176. }),
  177. ('name-only', {
  178. 'test_text': "lorem-ipsum",
  179. 'expected_error': error_class,
  180. }),
  181. ('section-empty', {
  182. 'test_text': "lorem-ipsum()",
  183. 'expected_error': error_class,
  184. }),
  185. ('section-starts-nondigit', {
  186. 'test_text': "lorem-ipsum(xyz99)",
  187. 'expected_error': error_class,
  188. }),
  189. ]
  190. def setUp(self):
  191. """ Set up test fixtures. """
  192. super().setUp()
  193. self.reference_class = manpage.document.DocumentReference
  194. def test_returns_expected_result(self):
  195. """ Should return expected instance for inputs. """
  196. for (scenario_name, scenario) in (
  197. (scenario_name, scenario)
  198. for (scenario_name, scenario) in self.scenarios
  199. if 'expected_instance' in scenario):
  200. with self.subTest(scenario=scenario_name):
  201. instance = self.reference_class.from_text(
  202. scenario['test_text'])
  203. self.assertEqual(instance, scenario['expected_instance'])
  204. def test_raises_expected_error(self):
  205. """ Should raise expected error for inputs. """
  206. for (scenario_name, scenario) in (
  207. (scenario_name, scenario)
  208. for (scenario_name, scenario) in self.scenarios
  209. if 'expected_error' in scenario):
  210. with self.subTest(scenario=scenario_name):
  211. with self.assertRaises(scenario['expected_error']):
  212. __ = self.reference_class.from_text(
  213. scenario['test_text'])
  214. class DocumentReference_comparison_TestCase(
  215. unittest.TestCase, Reference_comparison_TestCaseMixIn):
  216. """ Test cases for `DocumentReference` comparison methods. """
  217. reference_class = manpage.document.DocumentReference
  218. scenarios = [
  219. ('type-same,equal', {
  220. 'instance_this': reference_class("lorem", "5"),
  221. 'instance_other': reference_class("lorem", "5"),
  222. 'expected_true': ['eq', 'le', 'ge'],
  223. 'expected_false': ['ne', 'lt', 'gt'],
  224. }),
  225. ('type-same,name-less', {
  226. 'instance_this': reference_class("lorem", "5"),
  227. 'instance_other': reference_class("nullam", "5"),
  228. 'expected_true': ['ne', 'le', 'lt'],
  229. 'expected_false': ['eq', 'ge', 'gt'],
  230. }),
  231. ('type-same,name-greater', {
  232. 'instance_this': reference_class("lorem", "5"),
  233. 'instance_other': reference_class("dolor", "5"),
  234. 'expected_true': ['ne', 'ge', 'gt'],
  235. 'expected_false': ['eq', 'le', 'lt'],
  236. }),
  237. ('type-same,section-less', {
  238. 'instance_this': reference_class("lorem", "5"),
  239. 'instance_other': reference_class("lorem", "8"),
  240. 'expected_true': ['ne', 'le', 'lt'],
  241. 'expected_false': ['eq', 'ge', 'gt'],
  242. }),
  243. ('type-same,section-greater', {
  244. 'instance_this': reference_class("lorem", "5"),
  245. 'instance_other': reference_class("lorem", "1"),
  246. 'expected_true': ['ne', 'ge', 'gt'],
  247. 'expected_false': ['eq', 'le', 'lt'],
  248. }),
  249. ('type-different,other-external', {
  250. 'instance_this': reference_class("lorem", "5"),
  251. 'instance_other': manpage.document.ExternalReference(
  252. "Alpha Beta"),
  253. 'expected_true': ['ne', 'le', 'lt'],
  254. 'expected_false': ['eq', 'ge', 'gt'],
  255. }),
  256. ('type-different,other-not-reference', {
  257. 'instance_this': reference_class("lorem", "5"),
  258. 'instance_other': (5, 5),
  259. 'expected_true': ['ne', 'ge', 'gt'],
  260. 'expected_false': ['eq', 'le', 'lt'],
  261. }),
  262. ]
  263. class ExternalReference_TestCase(unittest.TestCase):
  264. """ Test cases for class `ExternalReference`. """
  265. reference_class = manpage.document.ExternalReference
  266. scenarios = [
  267. ('simple', {
  268. 'test_args': {
  269. 'title': "Lorem Ipsum",
  270. 'url': "spam://example.org/beans/",
  271. },
  272. 'expected_text_representation': (
  273. "Lorem Ipsum <URL:spam://example.org/beans/>"),
  274. 'expected_programmer_representation': (
  275. "ExternalReference"
  276. "('Lorem Ipsum', 'spam://example.org/beans/')"),
  277. }),
  278. ('url-missing', {
  279. 'test_args': {
  280. 'title': "Lorem Ipsum",
  281. 'url': None,
  282. },
  283. 'expected_text_representation': "Lorem Ipsum",
  284. 'expected_programmer_representation': (
  285. "ExternalReference('Lorem Ipsum', None)"),
  286. }),
  287. ]
  288. def setUp(self):
  289. """ Set up test fixtures. """
  290. super().setUp()
  291. simple_scenario = dict(self.scenarios)['simple']
  292. self.test_args = simple_scenario['test_args']
  293. setup_reference_instance(self)
  294. def test_instantiate(self):
  295. """ An instance should be created. """
  296. self.assertIsInstance(
  297. self.test_reference_instance, self.reference_class)
  298. def test_has_specified_title(self):
  299. """ Should have `title` value specified. """
  300. self.assertEqual(
  301. self.test_reference_instance.title, self.test_args['title'])
  302. def test_has_specified_url(self):
  303. """ Should have `url` value specified. """
  304. self.assertEqual(
  305. self.test_reference_instance.url, self.test_args['url'])
  306. def test_text_representation_as_expected(self):
  307. """ Should have expected text representation. """
  308. for (scenario_name, scenario) in self.scenarios:
  309. with self.subTest(scenario=scenario_name):
  310. instance = self.reference_class(
  311. **scenario['test_args'])
  312. self.assertEqual(
  313. str(instance),
  314. scenario['expected_text_representation'])
  315. def test_programmer_representation_as_expected(self):
  316. """ Should have expected programmer representation. """
  317. for (scenario_name, scenario) in self.scenarios:
  318. with self.subTest(scenario=scenario_name):
  319. instance = self.reference_class(
  320. **scenario['test_args'])
  321. self.assertEqual(
  322. repr(instance),
  323. scenario['expected_programmer_representation'])
  324. class ExternalReference_as_markup_TestCase(unittest.TestCase):
  325. """ Test cases for method `DocumentReference.as_markup`. """
  326. reference_class = manpage.document.ExternalReference
  327. scenarios = [
  328. ('simple', {
  329. 'test_args': {
  330. 'title': "Lorem Ipsum",
  331. 'url': "spam://example.org/beans/",
  332. },
  333. 'expected_result': textwrap.dedent("""\
  334. .UR spam://example.org/beans/
  335. Lorem Ipsum
  336. .UE
  337. """),
  338. }),
  339. ('url-missing', {
  340. 'test_args': {
  341. 'title': "Lorem Ipsum",
  342. 'url': None,
  343. },
  344. 'expected_result': textwrap.dedent("""\
  345. Lorem Ipsum
  346. """),
  347. }),
  348. ]
  349. def test_returns_expected_result(self):
  350. """ Should return expected markup for inputs. """
  351. for (scenario_name, scenario) in self.scenarios:
  352. with self.subTest(scenario=scenario_name):
  353. setup_reference_instance(self, test_args=scenario['test_args'])
  354. result = self.test_reference_instance.as_markup()
  355. self.assertEqual(result, scenario['expected_result'])
  356. class ExternalReference_comparison_TestCase(
  357. unittest.TestCase, Reference_comparison_TestCaseMixIn):
  358. """ Test cases for `ExternalReference` comparison methods. """
  359. reference_class = manpage.document.ExternalReference
  360. scenarios = [
  361. ('type-same,equal', {
  362. 'instance_this': reference_class("Lorem", "spam"),
  363. 'instance_other': reference_class("Lorem", "spam"),
  364. 'expected_true': ['eq', 'le', 'ge'],
  365. 'expected_false': ['ne', 'lt', 'gt'],
  366. }),
  367. ('type-same,title-less', {
  368. 'instance_this': reference_class("Lorem", "spam"),
  369. 'instance_other': reference_class("Nullam", "spam"),
  370. 'expected_true': ['ne', 'le', 'lt'],
  371. 'expected_false': ['eq', 'ge', 'gt'],
  372. }),
  373. ('type-same,title-greater', {
  374. 'instance_this': reference_class("Lorem", "spam"),
  375. 'instance_other': reference_class("Dolor", "spam"),
  376. 'expected_true': ['ne', 'ge', 'gt'],
  377. 'expected_false': ['eq', 'le', 'lt'],
  378. }),
  379. ('type-same,url-less', {
  380. 'instance_this': reference_class("Lorem", "spam"),
  381. 'instance_other': reference_class("Lorem", "xyzzy"),
  382. 'expected_true': ['ne', 'le', 'lt'],
  383. 'expected_false': ['eq', 'ge', 'gt'],
  384. }),
  385. ('type-same,section-greater', {
  386. 'instance_this': reference_class("Lorem", "spam"),
  387. 'instance_other': reference_class("Lorem", "beans"),
  388. 'expected_true': ['ne', 'ge', 'gt'],
  389. 'expected_false': ['eq', 'le', 'lt'],
  390. }),
  391. ('type-different,other-document', {
  392. 'instance_this': reference_class("Lorem", "spam"),
  393. 'instance_other': manpage.document.DocumentReference.from_text(
  394. "nullam(8)"),
  395. 'expected_true': ['ne', 'ge', 'gt'],
  396. 'expected_false': ['eq', 'le', 'lt'],
  397. }),
  398. ('type-different,other-not-reference', {
  399. 'instance_this': reference_class("Lorem", "spam"),
  400. 'instance_other': (5, 5),
  401. 'expected_true': ['ne', 'ge', 'gt'],
  402. 'expected_false': ['eq', 'le', 'lt'],
  403. }),
  404. ]
  405. # This is free software: you may copy, modify, and/or distribute this work
  406. # under the terms of the GNU General Public License as published by the
  407. # Free Software Foundation; version 3 of that license or any later version.
  408. #
  409. # No warranty expressed or implied. See the file ‘LICENSE.GPL-3’ for details,
  410. # or view it online at <URL:https://www.gnu.org/licenses/gpl-3.0.html>.
  411. # Local variables:
  412. # coding: utf-8
  413. # mode: python
  414. # End:
  415. # vim: fileencoding=utf-8 filetype=python :