Coverage for glotter/test_doc_generator.py: 100%
94 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-03-01 21:54 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-03-01 21:54 +0000
1import os
2import shlex
4from glotter.settings import get_settings
5from glotter.utils import quote
8def generate_test_docs(doc_dir, repo_name, repo_url):
9 """
10 Generate test documentation for all projects
12 :param doc_dir: Documentation directory
13 :param repo_name: Repository name
14 :param repo_url: Repository URL
15 """
17 settings = get_settings()
18 for project in settings.projects.values():
19 test_doc_generator = TestDocGenerator(project)
20 doc = test_doc_generator.generate_test_doc(repo_name, repo_url)
21 if doc:
22 project_dir = os.path.join(doc_dir, "-".join(project.words))
23 os.makedirs(project_dir)
24 project_doc_path = os.path.join(project_dir, "testing.md")
25 with open(os.path.join(project_doc_path), "w", encoding="utf-8") as f:
26 f.write(doc)
29class TestDocGenerator:
30 __test__ = False # Indicate this is not a test
32 def __init__(self, project):
33 self.project = project
34 self.project_title = " ".join(self.project.words).title()
36 def generate_test_doc(self, repo_name, repo_url):
37 if not self.project.tests:
38 return ""
40 doc = self._get_test_intro(repo_name, repo_url)
41 if self.project.requires_parameters:
42 for test_obj in self.project.tests.values():
43 test_doc_section_generator = TestDocSectionGenerator(test_obj)
44 doc += test_doc_section_generator.get_test_section()
46 return "\n".join(doc).rstrip() + "\n"
48 def _get_test_intro(self, repo_name, repo_url):
49 if not self.project.requires_parameters:
50 return [
51 "Verify that the actual output matches the expected output",
52 "(see [Requirements](#requirements)).",
53 ]
55 doc = [
56 f"Every project in the [{repo_name} repo]({repo_url}) should be tested.",
57 f"In this section, we specify the set of tests specific to {self.project_title}.",
58 ]
59 if len(self.project.tests) > 1:
60 doc += [
61 "In order to keep things simple, we split up the testing as follows:",
62 "",
63 ]
64 doc += [
65 "- " + _get_test_section_title(test_obj) for test_obj in self.project.tests.values()
66 ]
68 return doc + [""]
71def _get_test_section_title(test_obj):
72 return test_obj.name.replace("_", " ").title() + " Tests"
75class TestDocSectionGenerator:
76 __test__ = False # Indicate this is not a test
78 def __init__(self, test_obj):
79 self.test_obj = test_obj
80 self.test_obj_name = _get_test_section_title(test_obj)
82 def get_test_section(self):
83 return (
84 self._get_test_section_header() + self._get_test_table_header() + self._get_test_table()
85 )
87 def _get_test_section_header(self):
88 return [f"### {self.test_obj_name}", ""]
90 def _get_test_table_header(self):
91 cells = ["Description"] + self.test_obj.inputs
92 if self._any_test_output_is_different():
93 cells.append("Output")
95 return [
96 _cells_to_table_line(cells),
97 _cells_to_table_line("-" * len(cell) for cell in cells),
98 ]
100 def _any_test_output_is_different(self):
101 if len(self.test_obj.params) < 2:
102 return True
104 first_expected = self._get_expected_value(0)
105 return any(
106 self._get_expected_value(index) != first_expected
107 for index in range(1, len(self.test_obj.params))
108 )
110 def _get_expected_value(self, index):
111 expected_value = self.test_obj.params[index].expected
112 if isinstance(expected_value, dict) and "string" in expected_value:
113 expected_value = self.test_obj.strings[expected_value["string"]]
115 return expected_value
117 def _get_test_table(self):
118 doc = []
119 has_output_column = self._any_test_output_is_different()
120 num_input_params = len(self.test_obj.inputs)
121 first_output = None
122 for index, test_param in enumerate(self.test_obj.params):
123 output = self._get_expected_value(index)
124 if first_output is None:
125 first_output = output
127 cells = [test_param.name.title()]
128 if test_param.input is None:
129 inputs = []
130 else:
131 inputs = shlex.split(test_param.input)
132 extra_inputs = inputs[num_input_params:]
133 inputs = inputs[:num_input_params]
134 cells += [_quote_and_escape_pipe(value) for value in inputs]
135 if extra_inputs:
136 cells[-1] += " " + " ".join(
137 _quote_and_escape_pipe(value) for value in extra_inputs
138 )
140 cells += [""] * (num_input_params - len(inputs))
142 if has_output_column:
143 if isinstance(output, str):
144 cells.append(_quote_and_escape_pipe(output))
145 else:
146 cells.append("<br>".join(_quote_and_escape_pipe(item) for item in output))
148 doc.append(_cells_to_table_line(cells))
150 if not has_output_column:
151 doc += [
152 "",
153 "All of these tests should output the following:",
154 "",
155 "```",
156 first_output,
157 "```",
158 ]
160 return doc + [""]
163def _cells_to_table_line(cells):
164 return "| " + " | ".join(cells) + " |"
167def _quote_and_escape_pipe(value):
168 return quote(value.replace("|", "\\|"))