GCC Code Coverage Report


ernie/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
141 / 149
94.6%
Branches:
186 / 292
63.7%
Functions:
25 / 25
100.0%

scene/schema.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 /// @file schema.cpp
3 /// @brief Schema lookups and value validation.
4 #include "ernie/scene/schema.h"
5
6 #include "ernie/scene/convert.h"
7 #include "ernie/util/text.h"
8
9 #include <algorithm>
10 #include <cmath>
11 #include <stdexcept>
12 #include <string>
13
14 namespace ernie {
15 namespace {
16
17 /// What the bounds take: "above 0 and below 170", "from 0 to 1", "finite and 1 or more".
18 8 std::string RangeText(const AttributeMeta& meta) {
19 8 const std::optional<Bound>& lower = meta.lower;
20 8 const std::optional<Bound>& upper = meta.upper;
21
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 std::string text;
22
5/8
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
8 if (lower && upper && !lower->exclusive && !upper->exclusive) {
23
4/8
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 3 times.
✗ Branch 11 not taken.
6 text = "from " + ToText(lower->value) + " to " + ToText(upper->value);
24 } else {
25 5 std::vector<std::string> limits;
26
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (lower) {
27
2/8
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 9 taken 5 times.
✗ Branch 10 not taken.
20 limits.push_back(lower->exclusive ? "above " + ToText(lower->value)
28
3/10
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 5 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
15 : ToText(lower->value) + " or more");
29 }
30
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (upper) {
31
0/8
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ limits.push_back(upper->exclusive ? "below " + ToText(upper->value)
32
0/10
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ : ToText(upper->value) + " or less");
33 }
34
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 text = Join(limits, " and ");
35 5 }
36
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 3 times.
8 if (meta.finite) {
37
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 6 taken 5 times.
✗ Branch 7 not taken.
5 text = text.empty() ? "finite" : "finite and " + text;
38 }
39 8 return text;
40 ✗ }
41
42 1363 bool Fits(const AttributeMeta& meta, double number) {
43 // Written to fail for NaN.
44
3/4
✓ Branch 0 taken 1237 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1311 times.
✓ Branch 3 taken 52 times.
1363 return (!meta.finite || std::isfinite(number)) &&
45
3/4
✓ Branch 0 taken 1311 times.
✓ Branch 1 taken 52 times.
✓ Branch 2 taken 40 times.
✗ Branch 3 not taken.
1403 (!meta.lower ||
46
11/12
✓ Branch 0 taken 1237 times.
✓ Branch 1 taken 126 times.
✓ Branch 2 taken 40 times.
✓ Branch 3 taken 1271 times.
✓ Branch 4 taken 40 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1266 times.
✓ Branch 7 taken 5 times.
✓ Branch 8 taken 29 times.
✓ Branch 9 taken 1329 times.
✓ Branch 10 taken 3 times.
✓ Branch 11 taken 26 times.
2726 (meta.lower->exclusive ? number > meta.lower->value : number >= meta.lower->value)) &&
47
3/4
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 1329 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
1361 (!meta.upper ||
48
5/6
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 23 times.
✓ Branch 5 taken 3 times.
29 (meta.upper->exclusive ? number < meta.upper->value : number <= meta.upper->value));
49 }
50
51 /// @return Why `value` doesn't fit the attribute, or empty when it does.
52 3107 std::string Problem(const AttributeDef& def, const Value& value) {
53 3107 const std::vector<double> numbers = ToNumbers(value);
54
2/2
✓ Branch 0 taken 1363 times.
✓ Branch 1 taken 3099 times.
4462 for (const double number : numbers) {
55
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1355 times.
1363 if (!Fits(def.meta, number)) {
56
3/6
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 8 times.
✗ Branch 8 not taken.
24 return "must be " + RangeText(def.meta) +
57
3/6
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
32 (numbers.size() > 1 ? " in each component" : "") + ", not " + ToText(number);
58 }
59 }
60 3099 const std::vector<std::string_view>& options = def.meta.options;
61
2/2
✓ Branch 0 taken 126 times.
✓ Branch 1 taken 2973 times.
3099 if (const auto* token = value.Get<std::string>();
62
4/4
✓ Branch 0 taken 105 times.
✓ Branch 1 taken 21 times.
✓ Branch 3 taken 99 times.
✓ Branch 4 taken 6 times.
126 token && !options.empty() && std::ranges::find(options, *token) == options.end()) {
63
3/6
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 6 times.
✗ Branch 8 not taken.
18 return "must be one of " + Join(options, ", ") + ", not " + *token;
64 }
65 3093 return {};
66 3107 }
67
68 /// @return Why `array` doesn't fit the tensor attribute, or empty when it does.
69 116 std::string ArrayProblem(const AttributeDef& def, const Tensor& array) {
70
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 115 times.
116 if (def.type != ColumnType::Tensor) {
71 1 return "takes one value, not an array; set it";
72 }
73
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 114 times.
115 if (def.meta.bytes) {
74 1 return {};
75 }
76
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 66 times.
114 const DType type = array.Type();
77 114 const bool integer = type == DType::Int32 || type == DType::Int64 || type == DType::UInt32;
78 114 const bool floating = type == DType::Float32 || type == DType::Float64;
79
4/4
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 66 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 113 times.
114 if (def.meta.integer ? !integer : !floating) {
80
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 return def.meta.integer ? "must hold integers" : "must hold float32 or float64 numbers";
81 }
82
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 110 times.
113 if (const std::vector<std::int64_t>& ranks = def.meta.ranks; !ranks.empty()) {
83
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 if (std::ranges::find(ranks, static_cast<std::int64_t>(array.Rank())) != ranks.end()) {
84 3 return {};
85 }
86
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ return "must have " +
87
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Join(ranks, " or ", [](std::int64_t rank) { return std::to_string(rank); }) +
88 ✗ " dimensions";
89 }
90 110 const std::int64_t components = def.meta.components;
91
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 61 times.
110 const bool rows =
92
5/6
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 61 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 49 times.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 44 times.
110 array.Rank() == 2 && array.Shape()[1] == std::max<std::int64_t>(components, 1);
93
2/2
✓ Branch 0 taken 61 times.
✓ Branch 1 taken 49 times.
110 const bool flat =
94
4/6
✓ Branch 0 taken 61 times.
✓ Branch 1 taken 49 times.
✓ Branch 3 taken 61 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 61 times.
171 array.Rank() == 1 && array.Elements() % std::max<std::int64_t>(components, 1) == 0;
95
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 105 times.
110 if (!rows && !flat) {
96
4/10
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 5 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 5 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
15 return components > 1 ? "must be (n, " + std::to_string(components) + ") or flat"
97
2/8
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 5 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
5 : "must be flat";
98 }
99 105 return {};
100 }
101
102 3223 void ThrowIf(const std::string& problem, std::string_view attribute, const Path& path) {
103
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 3202 times.
3223 if (!problem.empty()) {
104
4/8
✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 21 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 21 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 21 times.
✗ Branch 11 not taken.
84 throw std::invalid_argument("'" + path.String() + "': '" + std::string(attribute) + "' " +
105
2/4
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 21 times.
✗ Branch 6 not taken.
63 problem);
106 }
107 3202 }
108
109 } // namespace
110
111 6042 bool SchemaDef::IsA(const SchemaDef& other) const {
112
2/2
✓ Branch 0 taken 12207 times.
✓ Branch 1 taken 5312 times.
23561 return FirstInLineage([&](const SchemaDef& type) { return &type == &other; }) != nullptr;
113 }
114
115 1803 bool SchemaDef::IsOpen(std::string_view attribute) const {
116 1803 return FirstInLineage([&](const SchemaDef& type) {
117 5273 return std::ranges::any_of(type.openNamespaces, [&](std::string_view space) {
118
4/4
✓ Branch 0 taken 2324 times.
✓ Branch 1 taken 619 times.
✓ Branch 3 taken 1173 times.
✓ Branch 4 taken 1151 times.
2943 return space == "*" || attribute.starts_with(space);
119 5273 });
120 1803 }) != nullptr;
121 }
122
123 7 const AttributeDef* SchemaDef::Find(std::string_view attribute) const {
124 7 const AttributeDef* found = nullptr;
125 7 FirstInLineage([&](const SchemaDef& type) {
126 11 const auto def = std::ranges::find(type.attributes, attribute, &AttributeDef::name);
127
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 4 times.
11 found = def != type.attributes.end() ? &*def : nullptr;
128 11 return found != nullptr;
129 });
130 7 return found;
131 }
132
133 2403 std::vector<const AttributeDef*> SchemaDef::AllAttributes() const {
134 2403 std::vector<const AttributeDef*> all =
135
2/2
✓ Branch 0 taken 1594 times.
✓ Branch 1 taken 809 times.
2403 base != nullptr ? base->AllAttributes() : std::vector<const AttributeDef*>{};
136
2/2
✓ Branch 0 taken 7514 times.
✓ Branch 1 taken 2403 times.
9917 for (const AttributeDef& def : attributes) {
137
1/2
✓ Branch 1 taken 7514 times.
✗ Branch 2 not taken.
7514 all.push_back(&def);
138 }
139 2403 return all;
140 ✗ }
141
142 3065 std::vector<std::string_view> SchemaDef::AllBuiltinApis() const {
143 3065 std::vector<std::string_view> all =
144
2/2
✓ Branch 0 taken 1962 times.
✓ Branch 1 taken 1103 times.
3065 base != nullptr ? base->AllBuiltinApis() : std::vector<std::string_view>{};
145
1/2
✓ Branch 1 taken 3065 times.
✗ Branch 2 not taken.
3065 all.insert(all.end(), builtinApis.begin(), builtinApis.end());
146 3065 return all;
147 ✗ }
148
149 15480 const Value* SchemaDef::FallbackOverride(std::string_view attribute) const {
150 15480 const Value* found = nullptr;
151 15480 FirstInLineage([&](const SchemaDef& type) {
152 41714 const auto override = std::ranges::find(type.fallbackOverrides, attribute,
153 &std::pair<std::string_view, Value>::first);
154
2/2
✓ Branch 0 taken 141 times.
✓ Branch 1 taken 41573 times.
41714 found = override != type.fallbackOverrides.end() ? &override->second : nullptr;
155 41714 return found != nullptr;
156 });
157 15480 return found;
158 }
159
160 14162 ExtentRule SchemaDef::Extent() const {
161 14162 const SchemaDef* ruled = FirstInLineage(
162
2/2
✓ Branch 0 taken 93 times.
✓ Branch 1 taken 14162 times.
14255 [](const SchemaDef& type) { return type.extent.kind != ExtentRule::Kind::Inherit; });
163
1/2
✓ Branch 0 taken 14162 times.
✗ Branch 1 not taken.
14162 return ruled != nullptr ? ruled->extent : kInheritExtent;
164 }
165
166 3323 const SchemaDef* FindSchema(std::string_view name) {
167 3323 const std::span<const SchemaDef* const> schemas = Schemas();
168 3323 const auto found = std::ranges::find_if(
169
2/2
✓ Branch 0 taken 55367 times.
✓ Branch 1 taken 3316 times.
58683 schemas, [&](const SchemaDef* schema) { return schema->name == name; });
170
2/2
✓ Branch 0 taken 3316 times.
✓ Branch 1 taken 7 times.
3323 return found != schemas.end() ? *found : nullptr;
171 }
172
173 517 std::span<const AttributeDef* const> InheritedAttributes() {
174 130 static const std::vector<const AttributeDef*> kInherited = [] {
175 130 std::vector<const AttributeDef*> inherited;
176
3/4
✓ Branch 1 taken 130 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 8450 times.
✓ Branch 4 taken 130 times.
8580 for (const SchemaDef* schema : Schemas()) {
177
2/2
✓ Branch 0 taken 39650 times.
✓ Branch 1 taken 8450 times.
48100 for (const AttributeDef& def : schema->attributes) {
178
2/2
✓ Branch 0 taken 520 times.
✓ Branch 1 taken 39130 times.
39650 if (def.meta.inherited) {
179
1/2
✓ Branch 1 taken 520 times.
✗ Branch 2 not taken.
520 inherited.push_back(&def);
180 }
181 }
182 }
183 130 return inherited;
184
4/6
✓ Branch 0 taken 130 times.
✓ Branch 1 taken 387 times.
✓ Branch 3 taken 130 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 130 times.
✗ Branch 7 not taken.
517 }();
185 517 return kInherited;
186 }
187
188 21 const AttributeDef* FindInherited(std::string_view name) {
189 21 const std::span<const AttributeDef* const> inherited = InheritedAttributes();
190 21 const auto found =
191
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 21 times.
75 std::ranges::find_if(inherited, [&](const AttributeDef* def) { return def->name == name; });
192
1/2
✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
21 return found != inherited.end() ? *found : nullptr;
193 }
194
195 515 ApiName ApiName::Parse(std::string_view api) {
196 515 const std::size_t colon = api.find(':');
197 515 const std::string_view name = api.substr(0, colon);
198 515 const ApiName parsed{FindSchema(name), colon == std::string_view::npos ? std::string_view()
199
2/2
✓ Branch 0 taken 291 times.
✓ Branch 1 taken 224 times.
515 : api.substr(colon + 1)};
200
4/4
✓ Branch 0 taken 513 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 225 times.
✓ Branch 3 taken 288 times.
515 const bool single = parsed.schema != nullptr && parsed.schema->kind == SchemaKind::SingleApply;
201 1030 const bool multiple =
202
4/4
✓ Branch 0 taken 513 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 289 times.
✓ Branch 3 taken 224 times.
515 parsed.schema != nullptr && parsed.schema->kind == SchemaKind::MultipleApply;
203
8/8
✓ Branch 0 taken 288 times.
✓ Branch 1 taken 227 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 287 times.
✓ Branch 4 taken 224 times.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 222 times.
515 if (!(single && parsed.instance.empty()) && !(multiple && IsIdentifier(parsed.instance))) {
204 6 throw std::invalid_argument(
205
5/8
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 6 times.
✗ Branch 10 not taken.
22 "'" + std::string(api) + "' is no API schema" +
206 (multiple ? ": a multiple-apply one takes an instance name, as CollectionAPI:lights"
207
1/2
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
18 : ""));
208 }
209 509 return parsed;
210 }
211
212 7129 std::string ApiName::Attribute(std::string_view declared) const {
213 7129 return Replace(declared, kInstanceName, instance);
214 }
215
216 220 std::string ApiName::ToString() const {
217
9/16
✓ Branch 0 taken 156 times.
✓ Branch 1 taken 64 times.
✓ Branch 3 taken 156 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 64 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 64 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 220 times.
✗ Branch 13 not taken.
✓ Branch 15 taken 220 times.
✗ Branch 16 not taken.
✓ Branch 18 taken 64 times.
✓ Branch 19 taken 156 times.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
504 return std::string(schema->name) + (instance.empty() ? "" : ":" + std::string(instance));
218 }
219
220 3107 void Validate(const AttributeDef& def, std::string_view attribute, const Value& value,
221 const Path& path) {
222
2/2
✓ Branch 2 taken 3093 times.
✓ Branch 3 taken 14 times.
3107 ThrowIf(Problem(def, value), attribute, path);
223 3093 }
224
225 116 void ValidateArray(const AttributeDef& def, std::string_view attribute, const Tensor& array,
226 const Path& path) {
227
2/2
✓ Branch 2 taken 109 times.
✓ Branch 3 taken 7 times.
116 ThrowIf(ArrayProblem(def, array), attribute, path);
228 109 }
229
230 } // namespace ernie
231