Coverage for tests/test_check.py: 14%

101 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-10-22 01:55 -0700

1# This file is part of felis. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22import contextlib 

23import copy 

24import os 

25import unittest 

26from collections.abc import Iterator, MutableMapping 

27from typing import Any 

28 

29import yaml 

30 

31from felis import DEFAULT_FRAME, CheckingVisitor 

32 

33TESTDIR = os.path.abspath(os.path.dirname(__file__)) 

34TEST_YAML = os.path.join(TESTDIR, "data", "test.yml") 

35 

36 

37@contextlib.contextmanager 

38def remove_key(mapping: MutableMapping[str, Any], key: str) -> Iterator[MutableMapping[str, Any]]: 

39 """Remove the key from the dictionary.""" 

40 value = mapping.pop(key) 

41 yield mapping 

42 mapping[key] = value 

43 

44 

45@contextlib.contextmanager 

46def replace_key( 

47 mapping: MutableMapping[str, Any], key: str, value: Any 

48) -> Iterator[MutableMapping[str, Any]]: 

49 """Replace key value in the dictionary.""" 

50 if key in mapping: 

51 value, mapping[key] = mapping[key], value 

52 yield mapping 

53 value, mapping[key] = mapping[key], value 

54 else: 

55 mapping[key] = value 

56 yield mapping 

57 del mapping[key] 

58 

59 

60class VisitorTestCase(unittest.TestCase): 

61 """Tests for CheckingVisitor class.""" 

62 

63 schema_obj: MutableMapping[str, Any] = {} 

64 

65 def setUp(self) -> None: 

66 """Load data from test file.""" 

67 with open(TEST_YAML) as test_yaml: 

68 self.schema_obj = yaml.load(test_yaml, Loader=yaml.SafeLoader) 

69 self.schema_obj.update(DEFAULT_FRAME) 

70 

71 def test_check(self) -> None: 

72 """Check YAML consistency using CheckingVisitor visitor.""" 

73 visitor = CheckingVisitor() 

74 visitor.visit_schema(self.schema_obj) 

75 

76 def test_error_schema(self) -> None: 

77 """Check for errors at schema level.""" 

78 

79 schema = copy.deepcopy(self.schema_obj) 

80 

81 # Missing @id 

82 with remove_key(schema, "@id"): 

83 with self.assertRaisesRegex(ValueError, "No @id defined for object"): 

84 CheckingVisitor().visit_schema(schema) 

85 

86 # Delete tables. 

87 with remove_key(schema, "tables"): 

88 with self.assertRaisesRegex(KeyError, "'tables'"): 

89 CheckingVisitor().visit_schema(schema) 

90 

91 def test_error_table(self) -> None: 

92 """Check for errors at table level.""" 

93 

94 schema = copy.deepcopy(self.schema_obj) 

95 table = schema["tables"][0] 

96 

97 # Missing @id 

98 with remove_key(table, "@id"): 

99 with self.assertRaisesRegex(ValueError, "No @id defined for object"): 

100 CheckingVisitor().visit_schema(schema) 

101 

102 # Missing name. 

103 with remove_key(table, "name"): 

104 with self.assertRaisesRegex(ValueError, "No name for table object"): 

105 CheckingVisitor().visit_schema(schema) 

106 

107 # Missing columns. 

108 with remove_key(table, "columns"): 

109 with self.assertRaisesRegex(KeyError, "'columns'"): 

110 CheckingVisitor().visit_schema(schema) 

111 

112 # Duplicate table @id causes warning. 

113 table2 = schema["tables"][1] 

114 with replace_key(table, "@id", "#duplicateID"), replace_key(table2, "@id", "#duplicateID"): 

115 with self.assertLogs(logger="felis", level="WARNING") as cm: 

116 CheckingVisitor().visit_schema(schema) 

117 self.assertEqual(cm.output, ["WARNING:felis:Duplication of @id #duplicateID"]) 

118 

119 def test_error_column(self) -> None: 

120 """Check for errors at column level.""" 

121 

122 schema = copy.deepcopy(self.schema_obj) 

123 column = schema["tables"][0]["columns"][0] 

124 

125 # Missing @id 

126 with remove_key(column, "@id"): 

127 with self.assertRaisesRegex(ValueError, "No @id defined for object"): 

128 CheckingVisitor().visit_schema(schema) 

129 

130 # Missing name. 

131 with remove_key(column, "name"): 

132 with self.assertRaisesRegex(ValueError, "No name for table object"): 

133 CheckingVisitor().visit_schema(schema) 

134 

135 # Missing datatype. 

136 with remove_key(column, "datatype"): 

137 with self.assertRaisesRegex(ValueError, "No datatype defined"): 

138 CheckingVisitor().visit_schema(schema) 

139 

140 # Incorrect datatype. 

141 with replace_key(column, "datatype", "nibble"): 

142 with self.assertRaisesRegex(ValueError, "Incorrect Type Name"): 

143 CheckingVisitor().visit_schema(schema) 

144 

145 # Duplicate @id causes warning. 

146 table2 = schema["tables"][1] 

147 with replace_key(column, "@id", "#duplicateID"), replace_key(table2, "@id", "#duplicateID"): 

148 with self.assertLogs(logger="felis", level="WARNING") as cm: 

149 CheckingVisitor().visit_schema(schema) 

150 self.assertEqual(cm.output, ["WARNING:felis:Duplication of @id #duplicateID"]) 

151 

152 def test_error_index(self) -> None: 

153 """Check for errors at index level.""" 

154 

155 schema = copy.deepcopy(self.schema_obj) 

156 table = schema["tables"][0] 

157 

158 # Missing @id 

159 index = {"name": "IDX_index", "columns": [table["columns"][0]["@id"]]} 

160 with replace_key(table, "indexes", [index]): 

161 with self.assertRaisesRegex(ValueError, "No @id defined for object"): 

162 CheckingVisitor().visit_schema(schema) 

163 

164 # Missing name. 

165 index = { 

166 "@id": "#IDX_index", 

167 "columns": [table["columns"][0]["@id"]], 

168 } 

169 with replace_key(table, "indexes", [index]): 

170 with self.assertRaisesRegex(ValueError, "No name for table object"): 

171 CheckingVisitor().visit_schema(schema) 

172 

173 # Both columns and expressions specified. 

174 index = { 

175 "@id": "#IDX_index", 

176 "name": "IDX_index", 

177 "columns": [table["columns"][0]["@id"]], 

178 "expressions": ["1+2"], 

179 } 

180 with replace_key(table, "indexes", [index]): 

181 with self.assertRaisesRegex(ValueError, "Defining columns and expressions is not valid"): 

182 CheckingVisitor().visit_schema(schema) 

183 

184 # Duplicate @id causes warning. 

185 index = { 

186 "@id": "#duplicateID", 

187 "name": "IDX_index", 

188 "columns": [table["columns"][0]["@id"]], 

189 } 

190 table2 = schema["tables"][1] 

191 with replace_key(table, "indexes", [index]), replace_key(table2, "@id", "#duplicateID"): 

192 with self.assertLogs(logger="felis", level="WARNING") as cm: 

193 CheckingVisitor().visit_schema(schema) 

194 self.assertEqual(cm.output, ["WARNING:felis:Duplication of @id #duplicateID"]) 

195 

196 

197if __name__ == "__main__": 197 ↛ 198line 197 didn't jump to line 198, because the condition on line 197 was never true

198 unittest.main()