|
| 1 | +""" |
| 2 | +Testing module for the topic_updater script. This modules tests whether the |
| 3 | +abstractions, SectionHeading, Skeleton, which are used in the script correctly |
| 4 | +work. |
| 5 | +""" |
| 6 | +from itertools import dropwhile |
| 7 | +from pathlib import Path |
| 8 | +import copy |
| 9 | +import os |
| 10 | +import unittest |
| 11 | + |
| 12 | +import mock |
| 13 | + |
| 14 | +import topic_updater as tu |
| 15 | + |
| 16 | +TEST_INPUTS = Path("tests/TEST_INPUTS/") |
| 17 | + |
| 18 | + |
| 19 | +class TestSectionHeading(unittest.TestCase): |
| 20 | + """ |
| 21 | + Tests the section heading class, which represents a specific secion heading |
| 22 | + in a topic markdown. |
| 23 | + """ |
| 24 | + @classmethod |
| 25 | + def setUpClass(cls): |
| 26 | + """ Setup example section header. """ |
| 27 | + cls.section_title = "## Section Title" |
| 28 | + cls.test_meta_text_line0 = "_ Some meta text _" + os.linesep |
| 29 | + cls.test_meta_text_line1 = "_ Another line meta text _" + os.linesep |
| 30 | + |
| 31 | + cls.new_heading = tu.SectionHeading(cls.section_title) |
| 32 | + cls.new_heading.append_meta_text(cls.test_meta_text_line0) |
| 33 | + |
| 34 | + def test_basic_construction(self): |
| 35 | + """ Checks if we setup the basic section header correclty. """ |
| 36 | + new_heading = copy.deepcopy(self.new_heading) |
| 37 | + |
| 38 | + self.assertEqual(new_heading.header_text, self.section_title) |
| 39 | + self.assertEqual(new_heading.meta_text, [self.test_meta_text_line0]) |
| 40 | + |
| 41 | + new_heading.append_meta_text(self.test_meta_text_line1) |
| 42 | + |
| 43 | + self.assertEqual( |
| 44 | + new_heading.meta_text, |
| 45 | + [self.test_meta_text_line0, self.test_meta_text_line1]) |
| 46 | + |
| 47 | + def test_meta_text_to_line_conversion(self): |
| 48 | + """ Checks if we correctly generate meta text. """ |
| 49 | + new_heading = copy.deepcopy(self.new_heading) |
| 50 | + new_heading.append_meta_text(self.test_meta_text_line1) |
| 51 | + |
| 52 | + meta_text_lines = new_heading.convert_meta_text_to_lines() |
| 53 | + |
| 54 | + self.assertEqual(meta_text_lines[0], os.linesep, |
| 55 | + "First line should be only a linesep.") |
| 56 | + self.assertEqual(meta_text_lines[1], self.test_meta_text_line0) |
| 57 | + self.assertEqual( |
| 58 | + meta_text_lines[2], self.test_meta_text_line1 + os.linesep, |
| 59 | + "The last meta line should have an additional line separator") |
| 60 | + |
| 61 | + |
| 62 | +class TestSkeleton(unittest.TestCase): |
| 63 | + """ |
| 64 | + Tests the skeleton class which loads the topic skeleton from a file and |
| 65 | + parses it. |
| 66 | + """ |
| 67 | + @classmethod |
| 68 | + def setUpClass(cls): |
| 69 | + """ Setup small test skeleton which contains all edge cases. """ |
| 70 | + cls.test_skeleton = tu.Skeleton(TEST_INPUTS / "test_skeleton.md") |
| 71 | + |
| 72 | + def test_actual_skeleton_parse(self): |
| 73 | + """ |
| 74 | + Checks that we can fully parse the actual skeleton. |
| 75 | + """ |
| 76 | + offset_to_main_folder = "../../../" |
| 77 | + actual_skeleton = tu.Skeleton(TEST_INPUTS / offset_to_main_folder / |
| 78 | + "skeleton.md") |
| 79 | + |
| 80 | + self.assertEqual(actual_skeleton.get_title_heading().header_text, |
| 81 | + "# Module name: topic name") |
| 82 | + headings = list( |
| 83 | + map(lambda heading: heading.header_text, actual_skeleton.headings)) |
| 84 | + self.assertEqual(headings[0], "# Module name: topic name") |
| 85 | + self.assertEqual(headings[1], "## Overview") |
| 86 | + |
| 87 | + self.assertEqual(headings[-2], "### Points to cover") |
| 88 | + self.assertEqual(headings[-1], "## Further studies") |
| 89 | + |
| 90 | + def test_heading_lookup(self): |
| 91 | + """ |
| 92 | + Checks if we can lookup different section headings in the skeleton. |
| 93 | + """ |
| 94 | + section_with_user_content = self.test_skeleton.lookup_heading( |
| 95 | + "## Section with user content:") |
| 96 | + missing_mid = self.test_skeleton.lookup_heading( |
| 97 | + "## Missing mid section") |
| 98 | + |
| 99 | + self.assertEqual(section_with_user_content.header_text, |
| 100 | + "## Section with user content: provided by the user") |
| 101 | + self.assertEqual( |
| 102 | + section_with_user_content.meta_text[0].rstrip(), |
| 103 | + "_ Example section where user text is in the header _") |
| 104 | + |
| 105 | + self.assertEqual(missing_mid.header_text, "## Missing mid section") |
| 106 | + self.assertEqual( |
| 107 | + missing_mid.meta_text[0].rstrip(), |
| 108 | + "_ This section could be missing in the middle of the topics _") |
| 109 | + |
| 110 | + def test_title_heading_lookup(self): |
| 111 | + """ |
| 112 | + Checks if we get the correct title heading from the test skeleton. |
| 113 | + """ |
| 114 | + self.assertEqual(self.test_skeleton.get_title_heading().header_text, |
| 115 | + "# Main Title") |
| 116 | + self.assertEqual( |
| 117 | + self.test_skeleton.get_title_heading().meta_text[0], |
| 118 | + "_Skeleton instructions are typeset in italic text._" + os.linesep) |
| 119 | + |
| 120 | + def test_that_meta_text_with_linebreaks_inbetween_meta_markers_parse(self): |
| 121 | + """ |
| 122 | + Checks that we correctly parse meta text that stretches over multiple |
| 123 | + lines. |
| 124 | + """ |
| 125 | + multi_line_meta_heading = self.test_skeleton.lookup_heading( |
| 126 | + "## Meta text with line breaks") |
| 127 | + |
| 128 | + self.assertEqual(multi_line_meta_heading.meta_text[0], |
| 129 | + "_ Some text here" + os.linesep) |
| 130 | + self.assertEqual(multi_line_meta_heading.meta_text[1], |
| 131 | + "some after the line break _" + os.linesep) |
| 132 | + |
| 133 | + |
| 134 | +class TestSkeletonTopicUpdates(unittest.TestCase): |
| 135 | + """ |
| 136 | + Test skeleton topic update function. This test tries to cover different |
| 137 | + scenarios that could occure during topic updates and verifies that we |
| 138 | + handle them correctly. |
| 139 | + """ |
| 140 | + @classmethod |
| 141 | + def setUpClass(cls): |
| 142 | + """ Setup small test skeleton which contains all edge cases. """ |
| 143 | + cls.test_skeleton = tu.Skeleton(TEST_INPUTS / "test_skeleton.md") |
| 144 | + |
| 145 | + def test_not_to_override_user_provided_heading_text(self): |
| 146 | + """ |
| 147 | + Checks that the update method does not override user provided text, |
| 148 | + which is part of some section headings. |
| 149 | + """ |
| 150 | + topic_file_path = TEST_INPUTS / "user_content_heading_topic.md" |
| 151 | + with open(topic_file_path, "r") as topic_file: |
| 152 | + updated_topic_lines = self.test_skeleton.update_topic_meta_text( |
| 153 | + topic_file) |
| 154 | + |
| 155 | + start_test_scope_lines = list( |
| 156 | + dropwhile( |
| 157 | + lambda line: not line.startswith( |
| 158 | + "## Section with user content:"), updated_topic_lines)) |
| 159 | + |
| 160 | + self.assertTrue( |
| 161 | + start_test_scope_lines[0].endswith( |
| 162 | + " This is provided by the user" + os.linesep), |
| 163 | + "User provided content was modified") |
| 164 | + |
| 165 | + def test_that_we_dont_override_user_text_in_sections(self): |
| 166 | + """ |
| 167 | + Checks that when we don't change user written text in section when |
| 168 | + updating the topic. |
| 169 | + """ |
| 170 | + topic_file_path = TEST_INPUTS / "user_content_heading_topic.md" |
| 171 | + with open(topic_file_path, "r") as topic_file: |
| 172 | + updated_topic_lines = self.test_skeleton.update_topic_meta_text( |
| 173 | + topic_file) |
| 174 | + |
| 175 | + start_test_scope_lines = list( |
| 176 | + dropwhile( |
| 177 | + lambda line: not line.startswith( |
| 178 | + "Users can add different content here"), |
| 179 | + updated_topic_lines)) |
| 180 | + |
| 181 | + self.assertTrue( |
| 182 | + start_test_scope_lines[0].startswith( |
| 183 | + "Users can add different content here."), |
| 184 | + "User provided content was modified") |
| 185 | + self.assertTrue(start_test_scope_lines[1].startswith("<table>"), |
| 186 | + "User provided content was modified") |
| 187 | + self.assertTrue(start_test_scope_lines[2].startswith("</table>"), |
| 188 | + "User provided content was modified") |
| 189 | + |
| 190 | + @mock.patch('topic_updater._cli_yn_choice') |
| 191 | + def test_if_missing_section_gets_added_to_the_end(self, mock_cli_yn): |
| 192 | + """ |
| 193 | + Checks if a section that is missing at the end of the topic file gets |
| 194 | + automatically added. |
| 195 | + """ |
| 196 | + mock_cli_yn.return_value = True |
| 197 | + topic_file_path = TEST_INPUTS / "missing_sections.md" |
| 198 | + with open(topic_file_path, "r") as topic_file: |
| 199 | + updated_topic_lines = self.test_skeleton.update_topic_meta_text( |
| 200 | + topic_file) |
| 201 | + |
| 202 | + self.assertEqual( |
| 203 | + updated_topic_lines[-3].rstrip(), "## Missing end section", |
| 204 | + "Missing section was not added to the end of the file.") |
| 205 | + self.assertEqual( |
| 206 | + updated_topic_lines[-1].rstrip(), |
| 207 | + "_ This section could be missing at the end of the document _") |
| 208 | + |
| 209 | + @mock.patch('topic_updater._cli_yn_choice') |
| 210 | + def test_if_missing_section_gets_added_in_the_middle(self, mock_cli_yn): |
| 211 | + """ |
| 212 | + Checks if a section that is missing in the middle of the topic file |
| 213 | + gets added if the user wants it. The test assumes the user supplies |
| 214 | + yes as an answer. |
| 215 | + """ |
| 216 | + mock_cli_yn.return_value = True |
| 217 | + topic_file_path = TEST_INPUTS / "missing_sections.md" |
| 218 | + with open(topic_file_path, "r") as topic_file: |
| 219 | + updated_topic_lines = self.test_skeleton.update_topic_meta_text( |
| 220 | + topic_file) |
| 221 | + |
| 222 | + # Reduces the topic lines to only contain sections and removes all |
| 223 | + # lines before the test scope, i.e., the section before the missing |
| 224 | + # section. |
| 225 | + start_test_scope_lines = list( |
| 226 | + filter( |
| 227 | + lambda line: line.startswith("##"), |
| 228 | + dropwhile( |
| 229 | + lambda line: not line.startswith( |
| 230 | + "## Section with user content"), |
| 231 | + iter(updated_topic_lines)))) |
| 232 | + |
| 233 | + # Verify that the previous section is correct |
| 234 | + self.assertTrue( |
| 235 | + start_test_scope_lines[0].startswith( |
| 236 | + "## Section with user content"), |
| 237 | + "The section before the added missing section is wrong.") |
| 238 | + |
| 239 | + # Verify the section was inserted |
| 240 | + self.assertEqual( |
| 241 | + start_test_scope_lines[1].rstrip(), "## Missing mid section", |
| 242 | + "The missing section was not inserted correctly.") |
| 243 | + |
| 244 | + # Verify the next section is correct |
| 245 | + self.assertTrue( |
| 246 | + start_test_scope_lines[2].startswith( |
| 247 | + "## Italics text should be updated"), |
| 248 | + "The section after the added missing section is wrong.") |
| 249 | + |
| 250 | + def test_that_meta_text_gets_updated(self): |
| 251 | + """ |
| 252 | + Checks that meta text in the topic gets updated according to the |
| 253 | + skeleton. |
| 254 | + """ |
| 255 | + topic_file_path = TEST_INPUTS / "fix_wrong_sections.md" |
| 256 | + with open(topic_file_path, "r") as topic_file: |
| 257 | + updated_topic_lines = self.test_skeleton.update_topic_meta_text( |
| 258 | + topic_file) |
| 259 | + |
| 260 | + topic_lines_starting_from_italics_section = list( |
| 261 | + dropwhile( |
| 262 | + lambda line: not line.startswith( |
| 263 | + "## Italics text should be updated"), |
| 264 | + iter(updated_topic_lines))) |
| 265 | + self.assertEqual( |
| 266 | + topic_lines_starting_from_italics_section[0].rstrip(), |
| 267 | + "## Italics text should be updated", |
| 268 | + "Could not find italics section.") |
| 269 | + self.assertEqual( |
| 270 | + topic_lines_starting_from_italics_section[2].rstrip(), |
| 271 | + "_ Updated italics text. _") |
| 272 | + |
| 273 | + def test_topic_does_not_end_in_empty_line(self): |
| 274 | + """ |
| 275 | + Checks that the update topic lines do not end with and empty line. |
| 276 | + """ |
| 277 | + topic_file_path = TEST_INPUTS / "user_content_heading_topic.md" |
| 278 | + with open(topic_file_path, "r") as topic_file: |
| 279 | + updated_topic_lines = self.test_skeleton.update_topic_meta_text( |
| 280 | + topic_file) |
| 281 | + |
| 282 | + self.assertNotEqual(updated_topic_lines[-1], os.linesep) |
| 283 | + |
| 284 | + def test_newline_between_heading_and_meta_text(self): |
| 285 | + """ |
| 286 | + Checks that we emit a newline between the heading and the meta text. |
| 287 | + """ |
| 288 | + topic_file_path = TEST_INPUTS / "user_content_heading_topic.md" |
| 289 | + with open(topic_file_path, "r") as topic_file: |
| 290 | + updated_topic_lines = self.test_skeleton.update_topic_meta_text( |
| 291 | + topic_file) |
| 292 | + |
| 293 | + start_test_scope_lines = list( |
| 294 | + dropwhile( |
| 295 | + lambda line: not line.startswith("## Section with user"), |
| 296 | + updated_topic_lines)) |
| 297 | + self.assertEqual(start_test_scope_lines[1], os.linesep, |
| 298 | + "No newline between heading and meta text.") |
| 299 | + self.assertTrue( |
| 300 | + start_test_scope_lines[2].startswith("_ Example section")) |
0 commit comments