test_document.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. # -*- coding: utf-8 -*-
  2. # tests/test_document.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 manual page document behaviour. """
  9. import sys
  10. import os
  11. import os.path
  12. import collections
  13. import datetime
  14. import textwrap
  15. import unittest
  16. import unittest.mock
  17. __package__ = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
  18. __import__(__package__)
  19. sys.path.insert(1, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
  20. import manpage.document
  21. class NamedTuple_TestCaseMixIn:
  22. """ Mix-in class to add test cases for a `namedtuple` class. """
  23. def test_instantiate_with_positional_args(self):
  24. """ An instance should be created when positional args supplied. """
  25. args = self.test_args.values()
  26. instance = self.test_class(*args)
  27. self.assertIsInstance(instance, self.test_class)
  28. def test_instantiate_with_keyword_args(self):
  29. """ An instance should be created when keyword args supplied. """
  30. kwargs = self.test_args
  31. instance = self.test_class(**kwargs)
  32. self.assertIsInstance(instance, self.test_class)
  33. def test_instance_has_expected_items(self):
  34. """ The instance should have the expected items. """
  35. instance = self.test_class(**self.test_args)
  36. expected_items = list(self.test_args.values())
  37. instance_items = list(instance)
  38. self.assertEqual(instance_items, expected_items)
  39. def test_instance_has_expected_attributes(self):
  40. """ The instance should have the expected attributes. """
  41. instance = self.test_class(**self.test_args)
  42. expected_attributes = dict(self.test_args)
  43. for (name, expected_value) in expected_attributes.items():
  44. with self.subTest(attribute_name=name):
  45. attribute_value = getattr(instance, name)
  46. self.assertEqual(attribute_value, expected_value)
  47. class MetaData_TestCase(unittest.TestCase, NamedTuple_TestCaseMixIn):
  48. """ Test cases for class `MetaData`. """
  49. def setUp(self):
  50. """ Set up test fixtures. """
  51. super().setUp()
  52. self.test_class = manpage.document.MetaData
  53. self.test_args = collections.OrderedDict([
  54. ('name', "lorem"),
  55. ('whatis', "ipsum"),
  56. ('manual', "dolor"),
  57. ('section', "sit"),
  58. ('source', "amet"),
  59. ])
  60. def setup_metadata_instance(testcase):
  61. """ Set up a test `MetaData` instance. """
  62. testcase.test_metadata_instance = manpage.document.MetaData(
  63. "lorem", "ipsum", "dolor", "sit", "amet")
  64. def setup_document_instance(testcase, test_class=None, test_args=None):
  65. """ Set up a test `Document` instance. """
  66. if test_class is None:
  67. test_class = testcase.document_class
  68. if test_args is None:
  69. test_args = {}
  70. if 'metadata' not in test_args:
  71. setup_metadata_instance(testcase)
  72. test_args.update({
  73. 'metadata': testcase.test_metadata_instance,
  74. })
  75. testcase.test_document_instance = test_class(**test_args)
  76. class Document_content_sections_TestCaseMixIn:
  77. """ Mix-in class to add test cases for `Document.content_sections`. """
  78. def test_has_expected_content_sections(self):
  79. """ Should have expected content sections. """
  80. standard_section_titles = self.document_class.standard_section_titles
  81. for (scenario_name, scenario) in self.scenarios:
  82. with self.subTest(scenario=scenario_name):
  83. setup_document_instance(self)
  84. content_sections = self.test_document_instance.content_sections
  85. for section_title in standard_section_titles:
  86. with self.subTest(section_title):
  87. self.assertIn(section_title, content_sections)
  88. def test_content_sections_in_expected_sequence(self):
  89. """ Should have expected content sections. """
  90. standard_section_titles = self.document_class.standard_section_titles
  91. for (scenario_name, scenario) in self.scenarios:
  92. with self.subTest(scenario=scenario_name):
  93. setup_document_instance(self)
  94. content_sections = self.test_document_instance.content_sections
  95. self.assertEqual(
  96. list(content_sections.keys()),
  97. list(standard_section_titles))
  98. class Document_TestCase(
  99. unittest.TestCase, Document_content_sections_TestCaseMixIn):
  100. """ Test cases for class `Document`. """
  101. document_class = manpage.document.Document
  102. scenarios = [
  103. ('simple', {
  104. 'test_today_date': datetime.date(1234, 5, 6),
  105. 'expected_created_date': datetime.date(1234, 5, 6),
  106. }),
  107. ]
  108. def patch_date_today(self, scenario):
  109. """ Patch the `datetime.date.today` function for the scenario. """
  110. date_patcher = unittest.mock.patch.object(
  111. datetime, 'date', autospec=True)
  112. mock_date = date_patcher.start()
  113. self.addCleanup(date_patcher.stop)
  114. mock_date.today.return_value = scenario['test_today_date']
  115. def test_instantiate(self):
  116. """ An instance should be created. """
  117. for (scenario_name, scenario) in self.scenarios:
  118. with self.subTest(scenario=scenario_name):
  119. setup_document_instance(self)
  120. self.assertIsInstance(
  121. self.test_document_instance, self.document_class)
  122. def test_has_expected_metadata(self):
  123. """ Should have expected metadata. """
  124. for (scenario_name, scenario) in self.scenarios:
  125. with self.subTest(scenario=scenario_name):
  126. setup_document_instance(self)
  127. expected_metadata = self.test_metadata_instance
  128. self.assertEqual(
  129. self.test_document_instance.metadata,
  130. expected_metadata)
  131. def test_has_expected_date(self):
  132. """ Should have expected creation date. """
  133. for (scenario_name, scenario) in self.scenarios:
  134. with self.subTest(scenario=scenario_name):
  135. self.patch_date_today(scenario)
  136. setup_document_instance(self)
  137. self.assertEqual(
  138. self.test_document_instance.date,
  139. scenario['expected_created_date'])
  140. class Document_as_markup_TestCase(unittest.TestCase):
  141. """ Test cases for method `Document.as_markup`. """
  142. document_class = manpage.document.Document
  143. def setUp(self):
  144. """ Set up test fixtures. """
  145. super().setUp()
  146. setup_document_instance(self)
  147. self.patch_header()
  148. self.patch_editor_hints()
  149. def patch_header(self):
  150. """ Patch attributes of the header for this test case. """
  151. header_patcher = unittest.mock.patch.object(
  152. self.test_document_instance, 'header')
  153. mock_header = header_patcher.start()
  154. self.addCleanup(header_patcher.stop)
  155. mock_header.as_markup.return_value = "lorem ipsum dolor sit amet\n"
  156. def patch_editor_hints(self):
  157. """ Patch the `editor_hints` method for this test case. """
  158. editor_hints_patcher = unittest.mock.patch.object(
  159. manpage.document.GroffMarkup, 'editor_hints')
  160. mock_editor_hints = editor_hints_patcher.start()
  161. self.addCleanup(editor_hints_patcher.stop)
  162. mock_editor_hints.return_value = "vitae blandit eros\n"
  163. def make_expected_result(self):
  164. """ Make an expected result for this test case. """
  165. header = self.test_document_instance.header
  166. header_markup = header.as_markup.return_value
  167. content_sections = [
  168. section for section in
  169. self.test_document_instance.content_sections.values()
  170. if section is not None]
  171. content_markup = ".\n".join(
  172. section.as_markup() for section in content_sections)
  173. footer_markup = "\n".join([
  174. ".",
  175. manpage.document.GroffMarkup.editor_hints.return_value])
  176. result = "".join([header_markup, content_markup, footer_markup])
  177. return result
  178. def test_result_is_text(self):
  179. """ Result should be a text object. """
  180. result = self.test_document_instance.as_markup()
  181. self.assertIsInstance(result, type(u""))
  182. def test_returns_expected_markup(self):
  183. """ Result should be the expected markup text. """
  184. result = self.test_document_instance.as_markup()
  185. expected_result = self.make_expected_result()
  186. self.assertEqual(result, expected_result)
  187. class Document_insert_section_TestCase(unittest.TestCase):
  188. """ Test cases for method `Document.insert_section`. """
  189. document_class = manpage.document.Document
  190. scenarios = [
  191. ('index-first', {
  192. 'existing_section_titles': ["DOLOR", "SIT", "AMET"],
  193. 'test_args': {
  194. 'index': 0,
  195. 'section': manpage.document.DocumentSection("LOREM"),
  196. },
  197. 'expected_section_titles': ["LOREM", "DOLOR", "SIT", "AMET"],
  198. }),
  199. ('index-middle', {
  200. 'existing_section_titles': ["DOLOR", "SIT", "AMET"],
  201. 'test_args': {
  202. 'index': 2,
  203. 'section': manpage.document.DocumentSection("LOREM"),
  204. },
  205. 'expected_section_titles': ["DOLOR", "SIT", "LOREM", "AMET"],
  206. }),
  207. ('index-last', {
  208. 'existing_section_titles': ["DOLOR", "SIT", "AMET"],
  209. 'test_args': {
  210. 'index': 3,
  211. 'section': manpage.document.DocumentSection("LOREM"),
  212. },
  213. 'expected_section_titles': ["DOLOR", "SIT", "AMET", "LOREM"],
  214. }),
  215. ('index-end', {
  216. 'existing_section_titles': ["DOLOR", "SIT", "AMET"],
  217. 'test_args': {
  218. 'index': -1,
  219. 'section': manpage.document.DocumentSection("LOREM"),
  220. },
  221. 'expected_section_titles': ["DOLOR", "SIT", "LOREM", "AMET"],
  222. }),
  223. ]
  224. def setUp(self):
  225. """ Set up test fixtures. """
  226. super().setUp()
  227. setup_document_instance(self)
  228. def test_resulting_sections_in_expected_sequence(self):
  229. """ Resulting sections should be in expected sequence. """
  230. for (scenario_name, scenario) in self.scenarios:
  231. with self.subTest(scenario=scenario_name):
  232. test_sections = collections.OrderedDict(
  233. (title, manpage.document.DocumentSection(title))
  234. for title in scenario['existing_section_titles'])
  235. self.test_document_instance.content_sections = test_sections
  236. self.test_document_instance.insert_section(
  237. **scenario['test_args'])
  238. test_sections = self.test_document_instance.content_sections
  239. self.assertEqual(
  240. list(test_sections.keys()),
  241. scenario['expected_section_titles'])
  242. class DocumentHeader_TestCase(unittest.TestCase):
  243. """ Test cases for class `DocumentHeader`. """
  244. document_class = manpage.document.Document
  245. def setUp(self):
  246. """ Set up test fixtures. """
  247. super().setUp()
  248. setup_document_instance(self)
  249. def test_has_specified_document(self):
  250. """ Should have `document` value specified. """
  251. instance = manpage.document.DocumentHeader(
  252. self.test_document_instance)
  253. self.assertEqual(instance.document, self.test_document_instance)
  254. def test_has_metadata_from_document(self):
  255. """ Should have `metadata` value from document. """
  256. instance = manpage.document.DocumentHeader(
  257. self.test_document_instance)
  258. self.assertEqual(
  259. instance.metadata, self.test_document_instance.metadata)
  260. class DocumentHeader_title_markup_TestCase(unittest.TestCase):
  261. """ Test cases for method `DocumentHeader.title_markup`. """
  262. document_class = manpage.document.Document
  263. scenarios = [
  264. ('all fields', {
  265. 'metadata': manpage.document.MetaData(
  266. name="lorem", whatis=None,
  267. manual="dolor", section="sit", source="amet"),
  268. 'test_date': datetime.date(1234, 5, 6),
  269. 'expected_fields': manpage.document.TitleFields(
  270. "LOREM", "sit", "1234\\-05\\-06", "amet", "dolor"),
  271. }),
  272. ('missing manual', {
  273. 'metadata': manpage.document.MetaData(
  274. name="lorem", whatis=None,
  275. manual=None, section="sit", source="amet"),
  276. 'test_date': datetime.date(1234, 5, 6),
  277. 'expected_fields': manpage.document.TitleFields(
  278. "LOREM", "sit", "1234\\-05\\-06", "amet", None),
  279. }),
  280. ('missing source+manual', {
  281. 'metadata': manpage.document.MetaData(
  282. name="lorem", whatis=None,
  283. manual=None, section="sit", source=None),
  284. 'test_date': datetime.date(1234, 5, 6),
  285. 'expected_fields': manpage.document.TitleFields(
  286. "LOREM", "sit", "1234\\-05\\-06", None, None),
  287. }),
  288. ]
  289. def setUp(self):
  290. """ Set up test fixtures. """
  291. super().setUp()
  292. setup_document_instance(self)
  293. self.mock_title_command = unittest.mock.create_autospec(
  294. manpage.document.GroffMarkup.title_command,
  295. return_value=".TH example")
  296. title_command_patcher = unittest.mock.patch.object(
  297. manpage.document.GroffMarkup, 'title_command',
  298. new=self.mock_title_command)
  299. self.mock_title_command = title_command_patcher.start()
  300. self.addCleanup(title_command_patcher.stop)
  301. def test_calls_title_command_with_expected_fields(self):
  302. """ Should call `title_command` with expected fields. """
  303. for (scenario_name, scenario) in self.scenarios:
  304. with self.subTest(scenario=scenario_name):
  305. self.test_document_instance._created_date = scenario['test_date']
  306. self.test_document_instance.metadata = scenario['metadata']
  307. instance = manpage.document.DocumentHeader(
  308. self.test_document_instance)
  309. __ = instance.title_markup()
  310. self.mock_title_command.assert_called_with(
  311. scenario['expected_fields'])
  312. class DocumentHeader_as_markup_TestCase(unittest.TestCase):
  313. """ Test cases for method `DocumentHeader.as_markup`. """
  314. document_class = manpage.document.Document
  315. scenarios = [
  316. ('simple', {
  317. 'test_args': {
  318. 'encoding': "litora",
  319. },
  320. 'test_title_markup': "lorem ipsum dolor sit amet",
  321. 'expected_result': "lorem ipsum dolor sit amet",
  322. }),
  323. ]
  324. def setUp(self):
  325. """ Set up test fixtures. """
  326. super().setUp()
  327. setup_document_instance(self)
  328. title_markup_patcher = unittest.mock.patch.object(
  329. manpage.document.DocumentHeader, 'title_markup')
  330. title_markup_patcher.start()
  331. self.addCleanup(title_markup_patcher.stop)
  332. def test_returns_expected_result(self):
  333. """ Should return expected markup for inputs. """
  334. instance = manpage.document.DocumentHeader(self.test_document_instance)
  335. for (scenario_name, scenario) in self.scenarios:
  336. with self.subTest(scenario=scenario_name):
  337. instance.title_markup.return_value = scenario[
  338. 'test_title_markup']
  339. result = instance.as_markup(**scenario['test_args'])
  340. self.assertEqual(scenario['expected_result'], result)
  341. class DocumentSection_TestCase(unittest.TestCase):
  342. """ Test cases for class `DocumentSection`. """
  343. def setUp(self):
  344. """ Set up test fixtures. """
  345. super().setUp()
  346. self.test_args = {
  347. 'title': "LOREM IPSUM",
  348. 'body': textwrap.dedent("""\
  349. Dolor sit amet.
  350. """),
  351. }
  352. def test_has_specified_title(self):
  353. """ Should have `title` value specified. """
  354. instance = manpage.document.DocumentSection(**self.test_args)
  355. self.assertEqual(instance.title, self.test_args['title'])
  356. def test_has_specified_body(self):
  357. """ Should have `body` value specified. """
  358. instance = manpage.document.DocumentSection(**self.test_args)
  359. self.assertEqual(instance.body, self.test_args['body'])
  360. class DocumentSection_as_markup_TestCase(unittest.TestCase):
  361. """ Test cases for method `DocumentSection.as_markup`. """
  362. scenarios = [
  363. ('simple', {
  364. 'test_args': {
  365. 'title': "LOREM IPSUM",
  366. 'body': textwrap.dedent("""\
  367. Dolor sit amet.
  368. """),
  369. },
  370. 'expected_result': textwrap.dedent("""\
  371. .SH LOREM IPSUM
  372. Dolor sit amet.
  373. """),
  374. }),
  375. ('body-no-trailing-newline', {
  376. 'test_args': {
  377. 'title': "LOREM IPSUM",
  378. 'body': textwrap.dedent("""\
  379. Dolor sit amet."""),
  380. },
  381. 'expected_result': textwrap.dedent("""\
  382. .SH LOREM IPSUM
  383. Dolor sit amet.
  384. """),
  385. }),
  386. ('body-none', {
  387. 'test_args': {
  388. 'title': "LOREM IPSUM",
  389. 'body': None,
  390. },
  391. 'expected_result': textwrap.dedent("""\
  392. .SH LOREM IPSUM
  393. """),
  394. }),
  395. ]
  396. def setUp(self):
  397. """ Set up test fixtures. """
  398. super().setUp()
  399. title_markup_patcher = unittest.mock.patch.object(
  400. manpage.document.DocumentHeader, 'title_markup')
  401. title_markup_patcher.start()
  402. self.addCleanup(title_markup_patcher.stop)
  403. def test_returns_expected_result(self):
  404. """ Should return expected markup for inputs. """
  405. for (scenario_name, scenario) in self.scenarios:
  406. with self.subTest(scenario=scenario_name):
  407. test_args = scenario['test_args']
  408. instance = manpage.document.DocumentSection(**test_args)
  409. result = instance.as_markup()
  410. self.assertEqual(scenario['expected_result'], result)
  411. class CommandDocument_TestCase(
  412. unittest.TestCase, Document_content_sections_TestCaseMixIn):
  413. """ Test cases for class `CommandDocument`. """
  414. document_class = manpage.document.CommandDocument
  415. scenarios = [
  416. ('section-specified', {
  417. 'test_args': {
  418. 'metadata': manpage.document.MetaData(
  419. name="lorem", whatis="ipsum",
  420. manual="dolor", section="sit",
  421. source="amet"),
  422. },
  423. 'expected_metadata': manpage.document.MetaData(
  424. name="lorem", whatis="ipsum",
  425. manual="dolor", section="sit",
  426. source="amet"),
  427. }),
  428. ('section-default', {
  429. 'test_args': {
  430. 'metadata': manpage.document.MetaData(
  431. name="lorem", whatis="ipsum",
  432. manual="dolor", section=None,
  433. source="amet"),
  434. },
  435. 'expected_metadata': manpage.document.MetaData(
  436. name="lorem", whatis="ipsum",
  437. manual="dolor", section="1",
  438. source="amet"),
  439. }),
  440. ]
  441. def setUp(self):
  442. """ Set up test fixtures. """
  443. super().setUp()
  444. def test_instantiate(self):
  445. """ An instance should be created. """
  446. for (scenario_name, scenario) in self.scenarios:
  447. with self.subTest(scenario=scenario_name):
  448. test_args = scenario['test_args']
  449. setup_document_instance(self, test_args=scenario['test_args'])
  450. self.assertIsInstance(
  451. self.test_document_instance, self.document_class)
  452. def test_has_expected_metadata_value(self):
  453. """ The document `metadata` should have expected value. """
  454. for (scenario_name, scenario) in self.scenarios:
  455. with self.subTest(scenario=scenario_name):
  456. test_args = scenario['test_args']
  457. setup_document_instance(self, test_args=scenario['test_args'])
  458. self.assertEqual(
  459. self.test_document_instance.metadata,
  460. scenario['expected_metadata'])
  461. # This is free software: you may copy, modify, and/or distribute this work
  462. # under the terms of the GNU General Public License as published by the
  463. # Free Software Foundation; version 3 of that license or any later version.
  464. #
  465. # No warranty expressed or implied. See the file ‘LICENSE.GPL-3’ for details,
  466. # or view it online at <URL:https://www.gnu.org/licenses/gpl-3.0.html>.
  467. # Local variables:
  468. # coding: utf-8
  469. # mode: python
  470. # End:
  471. # vim: fileencoding=utf-8 filetype=python :