Coverage for tests/test_check.py: 14%
101 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-28 10:01 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-28 10:01 +0000
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/>.
22import contextlib
23import copy
24import os
25import unittest
26from collections.abc import Iterator, MutableMapping
27from typing import Any
29import yaml
31from felis import DEFAULT_FRAME, CheckingVisitor
33TESTDIR = os.path.abspath(os.path.dirname(__file__))
34TEST_YAML = os.path.join(TESTDIR, "data", "test.yml")
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
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]
60class VisitorTestCase(unittest.TestCase):
61 """Tests for CheckingVisitor class."""
63 schema_obj: MutableMapping[str, Any] = {}
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)
71 def test_check(self) -> None:
72 """Check YAML consistency using CheckingVisitor visitor."""
73 visitor = CheckingVisitor()
74 visitor.visit_schema(self.schema_obj)
76 def test_error_schema(self) -> None:
77 """Check for errors at schema level."""
79 schema = copy.deepcopy(self.schema_obj)
81 # Missing @id
82 with remove_key(schema, "@id"):
83 with self.assertRaisesRegex(ValueError, "No @id defined for object"):
84 CheckingVisitor().visit_schema(schema)
86 # Delete tables.
87 with remove_key(schema, "tables"):
88 with self.assertRaisesRegex(KeyError, "'tables'"):
89 CheckingVisitor().visit_schema(schema)
91 def test_error_table(self) -> None:
92 """Check for errors at table level."""
94 schema = copy.deepcopy(self.schema_obj)
95 table = schema["tables"][0]
97 # Missing @id
98 with remove_key(table, "@id"):
99 with self.assertRaisesRegex(ValueError, "No @id defined for object"):
100 CheckingVisitor().visit_schema(schema)
102 # Missing name.
103 with remove_key(table, "name"):
104 with self.assertRaisesRegex(ValueError, "No name for table object"):
105 CheckingVisitor().visit_schema(schema)
107 # Missing columns.
108 with remove_key(table, "columns"):
109 with self.assertRaisesRegex(KeyError, "'columns'"):
110 CheckingVisitor().visit_schema(schema)
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"])
119 def test_error_column(self) -> None:
120 """Check for errors at column level."""
122 schema = copy.deepcopy(self.schema_obj)
123 column = schema["tables"][0]["columns"][0]
125 # Missing @id
126 with remove_key(column, "@id"):
127 with self.assertRaisesRegex(ValueError, "No @id defined for object"):
128 CheckingVisitor().visit_schema(schema)
130 # Missing name.
131 with remove_key(column, "name"):
132 with self.assertRaisesRegex(ValueError, "No name for table object"):
133 CheckingVisitor().visit_schema(schema)
135 # Missing datatype.
136 with remove_key(column, "datatype"):
137 with self.assertRaisesRegex(ValueError, "No datatype defined"):
138 CheckingVisitor().visit_schema(schema)
140 # Incorrect datatype.
141 with replace_key(column, "datatype", "nibble"):
142 with self.assertRaisesRegex(ValueError, "Incorrect Type Name"):
143 CheckingVisitor().visit_schema(schema)
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"])
152 def test_error_index(self) -> None:
153 """Check for errors at index level."""
155 schema = copy.deepcopy(self.schema_obj)
156 table = schema["tables"][0]
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)
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)
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)
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"])
197if __name__ == "__main__": 197 ↛ 198line 197 didn't jump to line 198, because the condition on line 197 was never true
198 unittest.main()