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

1import os 

2import shlex 

3 

4from glotter.settings import get_settings 

5from glotter.utils import quote 

6 

7 

8def generate_test_docs(doc_dir, repo_name, repo_url): 

9 """ 

10 Generate test documentation for all projects 

11 

12 :param doc_dir: Documentation directory 

13 :param repo_name: Repository name 

14 :param repo_url: Repository URL 

15 """ 

16 

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) 

27 

28 

29class TestDocGenerator: 

30 __test__ = False # Indicate this is not a test 

31 

32 def __init__(self, project): 

33 self.project = project 

34 self.project_title = " ".join(self.project.words).title() 

35 

36 def generate_test_doc(self, repo_name, repo_url): 

37 if not self.project.tests: 

38 return "" 

39 

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() 

45 

46 return "\n".join(doc).rstrip() + "\n" 

47 

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 ] 

54 

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 ] 

67 

68 return doc + [""] 

69 

70 

71def _get_test_section_title(test_obj): 

72 return test_obj.name.replace("_", " ").title() + " Tests" 

73 

74 

75class TestDocSectionGenerator: 

76 __test__ = False # Indicate this is not a test 

77 

78 def __init__(self, test_obj): 

79 self.test_obj = test_obj 

80 self.test_obj_name = _get_test_section_title(test_obj) 

81 

82 def get_test_section(self): 

83 return ( 

84 self._get_test_section_header() + self._get_test_table_header() + self._get_test_table() 

85 ) 

86 

87 def _get_test_section_header(self): 

88 return [f"### {self.test_obj_name}", ""] 

89 

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") 

94 

95 return [ 

96 _cells_to_table_line(cells), 

97 _cells_to_table_line("-" * len(cell) for cell in cells), 

98 ] 

99 

100 def _any_test_output_is_different(self): 

101 if len(self.test_obj.params) < 2: 

102 return True 

103 

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 ) 

109 

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"]] 

114 

115 return expected_value 

116 

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 

126 

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 ) 

139 

140 cells += [""] * (num_input_params - len(inputs)) 

141 

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)) 

147 

148 doc.append(_cells_to_table_line(cells)) 

149 

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 ] 

159 

160 return doc + [""] 

161 

162 

163def _cells_to_table_line(cells): 

164 return "| " + " | ".join(cells) + " |" 

165 

166 

167def _quote_and_escape_pipe(value): 

168 return quote(value.replace("|", "\\|"))