blob: da96402cb8d3712ea5feec9aca9d1b55c453800d [file] [log] [blame]
Austin Schuhcb108412019-10-13 16:09:54 -07001#include "aos/configuration.h"
2
3#include "absl/strings/strip.h"
Philipp Schrader790cb542023-07-05 21:06:52 -07004#include "flatbuffers/reflection.h"
5#include "glog/logging.h"
6#include "gmock/gmock.h"
7#include "gtest/gtest.h"
8
Nathan Leong307c9692022-10-08 15:25:03 -07009#include "aos/events/ping_generated.h"
Austin Schuhcb108412019-10-13 16:09:54 -070010#include "aos/json_to_flatbuffer.h"
Austin Schuh1ef01ef2021-02-07 20:40:36 -080011#include "aos/testing/flatbuffer_eq.h"
Austin Schuh373f1762021-06-02 21:07:09 -070012#include "aos/testing/path.h"
Austin Schuhcb108412019-10-13 16:09:54 -070013#include "aos/testing/test_logging.h"
14#include "aos/util/file.h"
Austin Schuhcb108412019-10-13 16:09:54 -070015
Stephan Pleinesf63bde82024-01-13 15:59:33 -080016namespace aos::configuration::testing {
Austin Schuhcb108412019-10-13 16:09:54 -070017
Austin Schuh373f1762021-06-02 21:07:09 -070018using aos::testing::ArtifactPath;
Austin Schuhfb37c612022-08-11 15:24:51 -070019namespace chrono = std::chrono;
Austin Schuh66602132020-02-28 13:38:37 -080020
Austin Schuhcb108412019-10-13 16:09:54 -070021class ConfigurationTest : public ::testing::Test {
22 public:
23 ConfigurationTest() { ::aos::testing::EnableTestLogging(); }
24};
25
26typedef ConfigurationTest ConfigurationDeathTest;
27
28// *the* expected location for all working tests.
Austin Schuh1ef01ef2021-02-07 20:40:36 -080029aos::FlatbufferDetachedBuffer<Channel> ExpectedLocation() {
30 return JsonToFlatbuffer<Channel>(
31 "{ \"name\": \"/foo\", \"type\": \".aos.bar\", \"max_size\": 5 }");
32}
33
Austin Schuhbca6cf02019-12-22 17:28:34 -080034// And for multinode setups
Austin Schuh1ef01ef2021-02-07 20:40:36 -080035aos::FlatbufferDetachedBuffer<Channel> ExpectedMultinodeLocation() {
36 return JsonToFlatbuffer<Channel>(
37 "{ \"name\": \"/foo\", \"type\": \".aos.bar\", \"max_size\": 5, "
38 "\"source_node\": \"pi1\" }");
39}
Austin Schuhcb108412019-10-13 16:09:54 -070040
41// Tests that we can read and merge a configuration.
42TEST_F(ConfigurationTest, ConfigMerge) {
Austin Schuh40485ed2019-10-26 21:51:44 -070043 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -070044 ReadConfig(ArtifactPath("aos/testdata/config1.json"));
Ravago Jonescf453ab2020-05-06 21:14:53 -070045 LOG(INFO) << "Read: " << FlatbufferToJson(config, {.multi_line = true});
Austin Schuhcb108412019-10-13 16:09:54 -070046
Austin Schuh373f1762021-06-02 21:07:09 -070047 EXPECT_EQ(absl::StripSuffix(util::ReadFileToStringOrDie(
48 ArtifactPath("aos/testdata/expected.json")),
49 "\n"),
50 FlatbufferToJson(config, {.multi_line = true}));
Austin Schuhcb108412019-10-13 16:09:54 -070051}
52
Austin Schuhc9e10ec2020-01-26 16:08:28 -080053// Tests that we can get back a ChannelIndex.
54TEST_F(ConfigurationTest, ChannelIndex) {
55 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -070056 ReadConfig(ArtifactPath("aos/testdata/config1.json"));
Austin Schuhc9e10ec2020-01-26 16:08:28 -080057
58 EXPECT_EQ(
59 ChannelIndex(&config.message(), config.message().channels()->Get(1u)),
60 1u);
61}
62
Austin Schuh217a9782019-12-21 23:02:50 -080063// Tests that we can read and merge a multinode configuration.
64TEST_F(ConfigurationTest, ConfigMergeMultinode) {
65 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -070066 ReadConfig(ArtifactPath("aos/testdata/config1_multinode.json"));
Ravago Jonescf453ab2020-05-06 21:14:53 -070067 LOG(INFO) << "Read: " << FlatbufferToJson(config, {.multi_line = true});
Austin Schuh217a9782019-12-21 23:02:50 -080068
Ravago Jonescf453ab2020-05-06 21:14:53 -070069 EXPECT_EQ(std::string(absl::StripSuffix(
Austin Schuh373f1762021-06-02 21:07:09 -070070 util::ReadFileToStringOrDie(
71 ArtifactPath("aos/testdata/expected_multinode.json")),
Ravago Jonescf453ab2020-05-06 21:14:53 -070072 "\n")),
73 FlatbufferToJson(config, {.multi_line = true}));
Austin Schuh217a9782019-12-21 23:02:50 -080074}
75
Alex Perrycb7da4b2019-08-28 19:35:56 -070076// Tests that we sort the entries in a config so we can look entries up.
77TEST_F(ConfigurationTest, UnsortedConfig) {
78 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -070079 ReadConfig(ArtifactPath("aos/testdata/backwards.json"));
Alex Perrycb7da4b2019-08-28 19:35:56 -070080
Ravago Jonescf453ab2020-05-06 21:14:53 -070081 LOG(INFO) << "Read: " << FlatbufferToJson(config, {.multi_line = true});
Alex Perrycb7da4b2019-08-28 19:35:56 -070082
Austin Schuhf1fff282020-03-28 16:57:32 -070083 EXPECT_EQ(FlatbufferToJson(GetChannel(config, "/aos/robot_state",
Austin Schuhbca6cf02019-12-22 17:28:34 -080084 "aos.RobotState", "app1", nullptr)),
Austin Schuhf1fff282020-03-28 16:57:32 -070085 "{ \"name\": \"/aos/robot_state\", \"type\": \"aos.RobotState\", "
Alex Perrycb7da4b2019-08-28 19:35:56 -070086 "\"max_size\": 5 }");
87}
88
Austin Schuhcb108412019-10-13 16:09:54 -070089// Tests that we die when a file is imported twice.
90TEST_F(ConfigurationDeathTest, DuplicateFile) {
91 EXPECT_DEATH(
92 {
Austin Schuh40485ed2019-10-26 21:51:44 -070093 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -070094 ReadConfig(ArtifactPath("aos/testdata/config1_bad.json"));
Austin Schuhcb108412019-10-13 16:09:54 -070095 },
Austin Schuh373f1762021-06-02 21:07:09 -070096 "aos/testdata/config1_bad.json");
Austin Schuhcb108412019-10-13 16:09:54 -070097}
98
Milind Upadhyay17098ba2022-04-15 22:18:50 -070099// Tests that we die when we give an invalid path.
100TEST_F(ConfigurationDeathTest, NonexistentFile) {
101 EXPECT_DEATH(
102 {
103 FlatbufferDetachedBuffer<Configuration> config =
104 ReadConfig("nonexistent/config.json");
105 },
106 "above error");
107}
108
109// Tests that we return std::nullopt when we give an invalid path.
110TEST_F(ConfigurationTest, NonexistentFileOptional) {
111 std::optional<FlatbufferDetachedBuffer<Configuration>> config =
112 MaybeReadConfig("nonexistent/config.json");
113 EXPECT_FALSE(config.has_value());
114}
115
Austin Schuhf1fff282020-03-28 16:57:32 -0700116// Tests that we reject invalid channel names. This means any channels with //
117// in their name, a trailing /, or regex characters.
118TEST_F(ConfigurationDeathTest, InvalidChannelName) {
119 EXPECT_DEATH(
120 {
121 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700122 ReadConfig(ArtifactPath("aos/testdata/invalid_channel_name1.json"));
Austin Schuhf1fff282020-03-28 16:57:32 -0700123 },
124 "Channel names can't end with '/'");
125 EXPECT_DEATH(
126 {
127 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700128 ReadConfig(ArtifactPath("aos/testdata/invalid_channel_name2.json"));
Austin Schuhf1fff282020-03-28 16:57:32 -0700129 },
130 "Invalid channel name");
131 EXPECT_DEATH(
132 {
133 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700134 ReadConfig(ArtifactPath("aos/testdata/invalid_channel_name3.json"));
Austin Schuhf1fff282020-03-28 16:57:32 -0700135 LOG(FATAL) << "Foo";
136 },
137 "Invalid channel name");
Austin Schuh47e382e2023-05-28 11:20:56 -0700138 EXPECT_DEATH(
139 {
140 FlatbufferDetachedBuffer<Configuration> config =
141 ReadConfig(ArtifactPath("aos/testdata/invalid_channel_name4.json"));
142 LOG(FATAL) << "Foo";
143 },
144 "Channel names must start with '/'");
Austin Schuhf1fff282020-03-28 16:57:32 -0700145}
146
Austin Schuh8d6cea82020-02-28 12:17:16 -0800147// Tests that we can modify a config with a json snippet.
148TEST_F(ConfigurationTest, MergeWithConfig) {
149 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700150 ReadConfig(ArtifactPath("aos/testdata/config1.json"));
Ravago Jonescf453ab2020-05-06 21:14:53 -0700151 LOG(INFO) << "Read: " << FlatbufferToJson(config, {.multi_line = true});
Austin Schuh8d6cea82020-02-28 12:17:16 -0800152
153 FlatbufferDetachedBuffer<Configuration> updated_config =
154 MergeWithConfig(&config.message(),
155 R"channel({
156 "channels": [
157 {
158 "name": "/foo",
159 "type": ".aos.bar",
160 "max_size": 100
161 }
162 ]
163})channel");
164
Austin Schuh373f1762021-06-02 21:07:09 -0700165 EXPECT_EQ(absl::StripSuffix(util::ReadFileToStringOrDie(ArtifactPath(
166 "aos/testdata/expected_merge_with.json")),
Ravago Jonescf453ab2020-05-06 21:14:53 -0700167 "\n"),
168 FlatbufferToJson(updated_config, {.multi_line = true}));
Austin Schuh8d6cea82020-02-28 12:17:16 -0800169}
170
Austin Schuhcb108412019-10-13 16:09:54 -0700171// Tests that we can lookup a location, complete with maps, from a merged
172// config.
Austin Schuh40485ed2019-10-26 21:51:44 -0700173TEST_F(ConfigurationTest, GetChannel) {
174 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700175 ReadConfig(ArtifactPath("aos/testdata/config1.json"));
Austin Schuhcb108412019-10-13 16:09:54 -0700176
177 // Test a basic lookup first.
Austin Schuh1ef01ef2021-02-07 20:40:36 -0800178 EXPECT_THAT(GetChannel(config, "/foo", ".aos.bar", "app1", nullptr),
179 aos::testing::FlatbufferEq(ExpectedLocation()));
Austin Schuhcb108412019-10-13 16:09:54 -0700180
181 // Test that an invalid name results in nullptr back.
Austin Schuhbca6cf02019-12-22 17:28:34 -0800182 EXPECT_EQ(GetChannel(config, "/invalid_name", ".aos.bar", "app1", nullptr),
183 nullptr);
Austin Schuhcb108412019-10-13 16:09:54 -0700184
185 // Tests that a root map/rename works. And that they get processed from the
186 // bottom up.
Austin Schuh1ef01ef2021-02-07 20:40:36 -0800187 EXPECT_THAT(GetChannel(config, "/batman", ".aos.bar", "app1", nullptr),
188 aos::testing::FlatbufferEq(ExpectedLocation()));
Austin Schuhcb108412019-10-13 16:09:54 -0700189
190 // And then test that an application specific map/rename works.
Austin Schuh1ef01ef2021-02-07 20:40:36 -0800191 EXPECT_THAT(GetChannel(config, "/bar", ".aos.bar", "app1", nullptr),
192 aos::testing::FlatbufferEq(ExpectedLocation()));
193 EXPECT_THAT(GetChannel(config, "/baz", ".aos.bar", "app2", nullptr),
194 aos::testing::FlatbufferEq(ExpectedLocation()));
Austin Schuhcb108412019-10-13 16:09:54 -0700195
196 // And then test that an invalid application name gets properly ignored.
Austin Schuh1ef01ef2021-02-07 20:40:36 -0800197 EXPECT_THAT(GetChannel(config, "/foo", ".aos.bar", "app3", nullptr),
198 aos::testing::FlatbufferEq(ExpectedLocation()));
Austin Schuhbca6cf02019-12-22 17:28:34 -0800199}
200
James Kuszmaulc8503f32022-06-25 16:17:12 -0700201// Tests that we can do reverse-lookups of channel names.
202TEST_F(ConfigurationTest, GetChannelAliases) {
203 FlatbufferDetachedBuffer<Configuration> config =
204 ReadConfig(ArtifactPath("aos/testdata/config1.json"));
205
206 // Test a basic lookup first.
207 EXPECT_THAT(
208 GetChannelAliases(&config.message(), "/foo", ".aos.bar", "app1", nullptr),
209 ::testing::UnorderedElementsAre("/foo", "/batman", "/bar"));
210 EXPECT_THAT(
211 GetChannelAliases(&config.message(), "/bar", ".aos.bar", "app1", nullptr),
212 ::testing::UnorderedElementsAre("/batman", "/bar"));
213 EXPECT_THAT(GetChannelAliases(&config.message(), "/batman", ".aos.bar",
214 "app1", nullptr),
215 ::testing::UnorderedElementsAre("/batman"));
216 // /bar (deliberately) does not get included because of the ordering in the
217 // map.
218 EXPECT_THAT(
219 GetChannelAliases(&config.message(), "/foo", ".aos.bar", "", nullptr),
220 ::testing::UnorderedElementsAre("/foo", "/batman"));
221 EXPECT_THAT(
222 GetChannelAliases(&config.message(), "/foo", ".aos.bar", "app2", nullptr),
223 ::testing::UnorderedElementsAre("/foo", "/batman", "/baz"));
224}
225
Austin Schuhbca6cf02019-12-22 17:28:34 -0800226// Tests that we can lookup a location with node specific maps.
227TEST_F(ConfigurationTest, GetChannelMultinode) {
228 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700229 ReadConfig(ArtifactPath("aos/testdata/good_multinode.json"));
Austin Schuhbca6cf02019-12-22 17:28:34 -0800230 const Node *pi1 = GetNode(&config.message(), "pi1");
231 const Node *pi2 = GetNode(&config.message(), "pi2");
232
233 // Test a basic lookup first.
Austin Schuh1ef01ef2021-02-07 20:40:36 -0800234 EXPECT_THAT(GetChannel(config, "/foo", ".aos.bar", "app1", pi1),
235 aos::testing::FlatbufferEq(ExpectedMultinodeLocation()));
236 EXPECT_THAT(GetChannel(config, "/foo", ".aos.bar", "app1", pi2),
237 aos::testing::FlatbufferEq(ExpectedMultinodeLocation()));
Austin Schuhbca6cf02019-12-22 17:28:34 -0800238
239 // Tests that a root map/rename works with a node specific map.
Austin Schuh1ef01ef2021-02-07 20:40:36 -0800240 EXPECT_THAT(GetChannel(config, "/batman", ".aos.bar", "app1", pi1),
241 aos::testing::FlatbufferEq(ExpectedMultinodeLocation()));
Austin Schuhbca6cf02019-12-22 17:28:34 -0800242
Austin Schuha22ee352024-05-08 16:24:48 -0700243 // Tests that node specific maps get ignored
244 EXPECT_EQ(GetChannel(config, "/batman", ".aos.bar", "", nullptr), nullptr);
245
Austin Schuhbca6cf02019-12-22 17:28:34 -0800246 // Tests that a root map/rename fails with a node specific map for the wrong
247 // node.
248 EXPECT_EQ(GetChannel(config, "/batman", ".aos.bar", "app1", pi2), nullptr);
249
250 // And then test that an application specific map/rename works.
Austin Schuh1ef01ef2021-02-07 20:40:36 -0800251 EXPECT_THAT(GetChannel(config, "/batman2", ".aos.bar", "app1", pi1),
252 aos::testing::FlatbufferEq(ExpectedMultinodeLocation()));
253 EXPECT_THAT(GetChannel(config, "/batman3", ".aos.bar", "app1", pi1),
254 aos::testing::FlatbufferEq(ExpectedMultinodeLocation()));
Austin Schuhbca6cf02019-12-22 17:28:34 -0800255
256 // And then that it fails when the node changes.
257 EXPECT_EQ(GetChannel(config, "/batman3", ".aos.bar", "app1", pi2), nullptr);
258}
259
James Kuszmaulc8503f32022-06-25 16:17:12 -0700260// Tests that reverse channel lookup on a multi-node config (including with
261// wildcards) works.
262TEST_F(ConfigurationTest, GetChannelAliasesMultinode) {
263 FlatbufferDetachedBuffer<Configuration> config =
264 ReadConfig(ArtifactPath("aos/testdata/good_multinode.json"));
265
266 const Node *pi1 = GetNode(&config.message(), "pi1");
267 const Node *pi2 = GetNode(&config.message(), "pi2");
268
269 EXPECT_THAT(
270 GetChannelAliases(&config.message(), "/foo", ".aos.bar", "app1", pi1),
271 ::testing::UnorderedElementsAre("/foo", "/batman", "/batman2", "/batman3",
272 "/magic/string"));
273
274 EXPECT_THAT(
275 GetChannelAliases(&config.message(), "/foo", ".aos.baz", "app1", pi1),
276 ::testing::UnorderedElementsAre("/foo", "/batman3", "/magic/string"));
277
278 EXPECT_THAT(
279 GetChannelAliases(&config.message(), "/foo/testing", ".aos.bar", "", pi1),
280 ::testing::UnorderedElementsAre("/foo/testing", "/magic/string/testing"));
281
282 EXPECT_THAT(
283 GetChannelAliases(&config.message(), "/foo/testing", ".aos.bar", "app1",
284 pi2),
285 ::testing::UnorderedElementsAre("/foo/testing", "/magic/string/testing"));
286}
287
Austin Schuhbca6cf02019-12-22 17:28:34 -0800288// Tests that we can lookup a location with type specific maps.
289TEST_F(ConfigurationTest, GetChannelTypedMultinode) {
290 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700291 ReadConfig(ArtifactPath("aos/testdata/good_multinode.json"));
Austin Schuhbca6cf02019-12-22 17:28:34 -0800292 const Node *pi1 = GetNode(&config.message(), "pi1");
293
294 // Test a basic lookup first.
Austin Schuh1ef01ef2021-02-07 20:40:36 -0800295 EXPECT_THAT(GetChannel(config, "/batman", ".aos.bar", "app1", pi1),
296 aos::testing::FlatbufferEq(ExpectedMultinodeLocation()));
Austin Schuhbca6cf02019-12-22 17:28:34 -0800297
298 // Now confirm that a second message on the same name doesn't get remapped.
299 const char *kExpectedBazMultinodeLocation =
300 "{ \"name\": \"/batman\", \"type\": \".aos.baz\", \"max_size\": 5, "
301 "\"source_node\": \"pi1\" }";
302 EXPECT_EQ(
303 FlatbufferToJson(GetChannel(config, "/batman", ".aos.baz", "app1", pi1)),
304 kExpectedBazMultinodeLocation);
Austin Schuhcb108412019-10-13 16:09:54 -0700305}
306
Austin Schuhf1fff282020-03-28 16:57:32 -0700307// Tests that we can lookup a location with a glob
308TEST_F(ConfigurationTest, GetChannelGlob) {
309 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700310 ReadConfig(ArtifactPath("aos/testdata/good_multinode.json"));
Austin Schuhf1fff282020-03-28 16:57:32 -0700311 const Node *pi1 = GetNode(&config.message(), "pi1");
312
313 // Confirm that a glob with nothing after it matches.
Austin Schuh1ef01ef2021-02-07 20:40:36 -0800314 EXPECT_THAT(GetChannel(config, "/magic/string", ".aos.bar", "app7", pi1),
315 aos::testing::FlatbufferEq(ExpectedMultinodeLocation()));
Austin Schuhf1fff282020-03-28 16:57:32 -0700316
317 // Now confirm that glob with something following it matches and renames
318 // correctly.
319 const char *kExpectedSubfolderMultinodeLocation =
320 "{ \"name\": \"/foo/subfolder\", \"type\": \".aos.bar\", \"max_size\": "
321 "5, \"source_node\": \"pi1\" }";
322 EXPECT_EQ(FlatbufferToJson(GetChannel(config, "/magic/string/subfolder",
323 ".aos.bar", "app7", pi1)),
324 kExpectedSubfolderMultinodeLocation);
325}
326
Austin Schuh217a9782019-12-21 23:02:50 -0800327// Tests that we reject a configuration which has a nodes list, but has channels
328// withoout source_node filled out.
329TEST_F(ConfigurationDeathTest, InvalidSourceNode) {
330 EXPECT_DEATH(
331 {
332 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700333 ReadConfig(ArtifactPath("aos/testdata/invalid_nodes.json"));
Austin Schuh217a9782019-12-21 23:02:50 -0800334 },
335 "source_node");
336
337 EXPECT_DEATH(
338 {
339 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700340 ReadConfig(ArtifactPath("aos/testdata/invalid_source_node.json"));
Austin Schuh217a9782019-12-21 23:02:50 -0800341 },
342 "source_node");
343
344 EXPECT_DEATH(
345 {
Austin Schuh373f1762021-06-02 21:07:09 -0700346 FlatbufferDetachedBuffer<Configuration> config = ReadConfig(
347 ArtifactPath("aos/testdata/invalid_destination_node.json"));
Austin Schuh217a9782019-12-21 23:02:50 -0800348 },
349 "destination_nodes");
350
351 EXPECT_DEATH(
352 {
353 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700354 ReadConfig(ArtifactPath("aos/testdata/self_forward.json"));
Austin Schuh217a9782019-12-21 23:02:50 -0800355 },
356 "forwarding data to itself");
357}
358
359// Tests that our node writeable helpers work as intended.
360TEST_F(ConfigurationTest, ChannelIsSendableOnNode) {
361 FlatbufferDetachedBuffer<Channel> good_channel(JsonToFlatbuffer(
Austin Schuh719946b2019-12-28 14:51:01 -0800362 R"channel({
Austin Schuh217a9782019-12-21 23:02:50 -0800363 "name": "/test",
364 "type": "aos.examples.Ping",
365 "source_node": "foo"
366})channel",
367 Channel::MiniReflectTypeTable()));
368
369 FlatbufferDetachedBuffer<Channel> bad_channel(JsonToFlatbuffer(
Austin Schuh719946b2019-12-28 14:51:01 -0800370 R"channel({
Austin Schuh217a9782019-12-21 23:02:50 -0800371 "name": "/test",
372 "type": "aos.examples.Ping",
373 "source_node": "bar"
374})channel",
375 Channel::MiniReflectTypeTable()));
376
377 FlatbufferDetachedBuffer<Node> node(JsonToFlatbuffer(
Austin Schuh719946b2019-12-28 14:51:01 -0800378 R"node({
Austin Schuh217a9782019-12-21 23:02:50 -0800379 "name": "foo"
380})node",
381 Node::MiniReflectTypeTable()));
382
383 EXPECT_TRUE(
384 ChannelIsSendableOnNode(&good_channel.message(), &node.message()));
385 EXPECT_FALSE(
386 ChannelIsSendableOnNode(&bad_channel.message(), &node.message()));
387}
388
389// Tests that our node readable and writeable helpers work as intended.
390TEST_F(ConfigurationTest, ChannelIsReadableOnNode) {
391 FlatbufferDetachedBuffer<Channel> good_channel(JsonToFlatbuffer(
Austin Schuh719946b2019-12-28 14:51:01 -0800392 R"channel({
Austin Schuh217a9782019-12-21 23:02:50 -0800393 "name": "/test",
394 "type": "aos.examples.Ping",
395 "source_node": "bar",
396 "destination_nodes": [
Austin Schuh719946b2019-12-28 14:51:01 -0800397 {
398 "name": "baz"
399 },
400 {
401 "name": "foo"
402 }
Austin Schuh217a9782019-12-21 23:02:50 -0800403 ]
404})channel",
405 Channel::MiniReflectTypeTable()));
406
407 FlatbufferDetachedBuffer<Channel> bad_channel1(JsonToFlatbuffer(
Austin Schuh719946b2019-12-28 14:51:01 -0800408 R"channel({
Austin Schuh217a9782019-12-21 23:02:50 -0800409 "name": "/test",
410 "type": "aos.examples.Ping",
411 "source_node": "bar"
412})channel",
413 Channel::MiniReflectTypeTable()));
414
415 FlatbufferDetachedBuffer<Channel> bad_channel2(JsonToFlatbuffer(
Austin Schuh719946b2019-12-28 14:51:01 -0800416 R"channel({
Austin Schuh217a9782019-12-21 23:02:50 -0800417 "name": "/test",
418 "type": "aos.examples.Ping",
419 "source_node": "bar",
420 "destination_nodes": [
Austin Schuh719946b2019-12-28 14:51:01 -0800421 {
422 "name": "baz"
423 }
Austin Schuh217a9782019-12-21 23:02:50 -0800424 ]
425})channel",
426 Channel::MiniReflectTypeTable()));
427
428 FlatbufferDetachedBuffer<Node> node(JsonToFlatbuffer(
Austin Schuh719946b2019-12-28 14:51:01 -0800429 R"node({
Austin Schuh217a9782019-12-21 23:02:50 -0800430 "name": "foo"
431})node",
432 Node::MiniReflectTypeTable()));
433
434 EXPECT_TRUE(
435 ChannelIsReadableOnNode(&good_channel.message(), &node.message()));
436 EXPECT_FALSE(
437 ChannelIsReadableOnNode(&bad_channel1.message(), &node.message()));
438 EXPECT_FALSE(
439 ChannelIsReadableOnNode(&bad_channel2.message(), &node.message()));
440}
441
James Kuszmaul24db2d32023-05-26 11:40:12 -0700442// Tests that our channel is forwarded helpers work as intended.
443TEST_F(ConfigurationTest, ChannelIsForwardedFromNode) {
444 FlatbufferDetachedBuffer<Channel> forwarded_channel(JsonToFlatbuffer(
445 R"channel({
446 "name": "/test",
447 "type": "aos.examples.Ping",
448 "source_node": "bar",
449 "destination_nodes": [
450 {
451 "name": "baz"
452 },
453 {
454 "name": "foo"
455 }
456 ]
457})channel",
458 Channel::MiniReflectTypeTable()));
459
460 FlatbufferDetachedBuffer<Channel> single_node_channel(JsonToFlatbuffer(
461 R"channel({
462 "name": "/test",
463 "type": "aos.examples.Ping"
464})channel",
465 Channel::MiniReflectTypeTable()));
466
467 FlatbufferDetachedBuffer<Channel> zero_length_vector_channel(JsonToFlatbuffer(
468 R"channel({
469 "name": "/test",
470 "type": "aos.examples.Ping",
471 "source_node": "bar",
472 "destination_nodes": [
473 ]
474})channel",
475 Channel::MiniReflectTypeTable()));
476
477 FlatbufferDetachedBuffer<Node> node(JsonToFlatbuffer(
478 R"node({
479 "name": "bar"
480})node",
481 Node::MiniReflectTypeTable()));
482
483 FlatbufferDetachedBuffer<Node> readable_node(JsonToFlatbuffer(
484 R"node({
485 "name": "foo"
486})node",
487 Node::MiniReflectTypeTable()));
488
489 EXPECT_TRUE(ChannelIsForwardedFromNode(&forwarded_channel.message(),
490 &node.message()));
491 EXPECT_FALSE(ChannelIsForwardedFromNode(&forwarded_channel.message(),
492 &readable_node.message()));
493 EXPECT_FALSE(
494 ChannelIsForwardedFromNode(&single_node_channel.message(), nullptr));
495 EXPECT_FALSE(ChannelIsForwardedFromNode(&zero_length_vector_channel.message(),
496 &node.message()));
497}
498
Austin Schuh719946b2019-12-28 14:51:01 -0800499// Tests that our node message is logged helpers work as intended.
500TEST_F(ConfigurationTest, ChannelMessageIsLoggedOnNode) {
501 FlatbufferDetachedBuffer<Channel> logged_on_self_channel(JsonToFlatbuffer(
502 R"channel({
503 "name": "/test",
504 "type": "aos.examples.Ping",
505 "source_node": "bar",
506 "destination_nodes": [
507 {
508 "name": "baz"
509 }
510 ]
511})channel",
512 Channel::MiniReflectTypeTable()));
Austin Schuh217a9782019-12-21 23:02:50 -0800513
Austin Schuh719946b2019-12-28 14:51:01 -0800514 FlatbufferDetachedBuffer<Channel> not_logged_channel(JsonToFlatbuffer(
515 R"channel({
516 "name": "/test",
517 "type": "aos.examples.Ping",
518 "source_node": "bar",
519 "logger": "NOT_LOGGED",
520 "destination_nodes": [
521 {
522 "name": "baz",
523 "timestamp_logger": "LOCAL_LOGGER"
524 }
525 ]
526})channel",
527 Channel::MiniReflectTypeTable()));
528
529 FlatbufferDetachedBuffer<Channel> logged_on_remote_channel(JsonToFlatbuffer(
530 R"channel({
531 "name": "/test",
532 "type": "aos.examples.Ping",
533 "source_node": "bar",
534 "logger": "REMOTE_LOGGER",
Austin Schuhda40e472020-03-28 15:15:29 -0700535 "logger_nodes": ["baz"],
Austin Schuh719946b2019-12-28 14:51:01 -0800536 "destination_nodes": [
537 {
538 "name": "baz"
539 }
540 ]
541})channel",
542 Channel::MiniReflectTypeTable()));
543
544 FlatbufferDetachedBuffer<Channel> logged_on_separate_logger_node_channel(
545 JsonToFlatbuffer(
546 R"channel({
547 "name": "/test",
548 "type": "aos.examples.Ping",
549 "source_node": "bar",
550 "logger": "REMOTE_LOGGER",
Austin Schuhda40e472020-03-28 15:15:29 -0700551 "logger_nodes": ["foo"],
Austin Schuh719946b2019-12-28 14:51:01 -0800552 "destination_nodes": [
553 {
554 "name": "baz"
555 }
556 ]
557})channel",
558 Channel::MiniReflectTypeTable()));
559
Ravago Jonescf453ab2020-05-06 21:14:53 -0700560 FlatbufferDetachedBuffer<Channel> logged_on_both_channel(JsonToFlatbuffer(
561 R"channel({
Austin Schuh719946b2019-12-28 14:51:01 -0800562 "name": "/test",
563 "type": "aos.examples.Ping",
564 "source_node": "bar",
565 "logger": "LOCAL_AND_REMOTE_LOGGER",
Austin Schuhda40e472020-03-28 15:15:29 -0700566 "logger_nodes": ["baz"],
Austin Schuh719946b2019-12-28 14:51:01 -0800567 "destination_nodes": [
568 {
569 "name": "baz"
570 }
571 ]
572})channel",
Ravago Jonescf453ab2020-05-06 21:14:53 -0700573 Channel::MiniReflectTypeTable()));
Austin Schuh719946b2019-12-28 14:51:01 -0800574
575 FlatbufferDetachedBuffer<Node> foo_node(JsonToFlatbuffer(
576 R"node({
577 "name": "foo"
578})node",
579 Node::MiniReflectTypeTable()));
580
581 FlatbufferDetachedBuffer<Node> bar_node(JsonToFlatbuffer(
582 R"node({
583 "name": "bar"
584})node",
585 Node::MiniReflectTypeTable()));
586
587 FlatbufferDetachedBuffer<Node> baz_node(JsonToFlatbuffer(
588 R"node({
589 "name": "baz"
590})node",
591 Node::MiniReflectTypeTable()));
592
593 // Local logger.
594 EXPECT_FALSE(ChannelMessageIsLoggedOnNode(&logged_on_self_channel.message(),
595 &foo_node.message()));
596 EXPECT_TRUE(ChannelMessageIsLoggedOnNode(&logged_on_self_channel.message(),
597 &bar_node.message()));
598 EXPECT_FALSE(ChannelMessageIsLoggedOnNode(&logged_on_self_channel.message(),
599 &baz_node.message()));
Austin Schuh48e94502021-06-18 18:35:53 -0700600 EXPECT_TRUE(
601 ChannelMessageIsLoggedOnNode(&logged_on_self_channel.message(), nullptr));
Austin Schuh719946b2019-12-28 14:51:01 -0800602
603 // No logger.
604 EXPECT_FALSE(ChannelMessageIsLoggedOnNode(&not_logged_channel.message(),
605 &foo_node.message()));
606 EXPECT_FALSE(ChannelMessageIsLoggedOnNode(&not_logged_channel.message(),
Ravago Jonescf453ab2020-05-06 21:14:53 -0700607 &bar_node.message()));
Austin Schuh719946b2019-12-28 14:51:01 -0800608 EXPECT_FALSE(ChannelMessageIsLoggedOnNode(&not_logged_channel.message(),
609 &baz_node.message()));
Austin Schuh48e94502021-06-18 18:35:53 -0700610 EXPECT_FALSE(
611 ChannelMessageIsLoggedOnNode(&not_logged_channel.message(), nullptr));
Austin Schuh719946b2019-12-28 14:51:01 -0800612
613 // Remote logger.
614 EXPECT_FALSE(ChannelMessageIsLoggedOnNode(&logged_on_remote_channel.message(),
615 &foo_node.message()));
616 EXPECT_FALSE(ChannelMessageIsLoggedOnNode(&logged_on_remote_channel.message(),
617 &bar_node.message()));
618 EXPECT_TRUE(ChannelMessageIsLoggedOnNode(&logged_on_remote_channel.message(),
619 &baz_node.message()));
620
621 // Separate logger.
622 EXPECT_TRUE(ChannelMessageIsLoggedOnNode(
623 &logged_on_separate_logger_node_channel.message(), &foo_node.message()));
624 EXPECT_FALSE(ChannelMessageIsLoggedOnNode(
625 &logged_on_separate_logger_node_channel.message(), &bar_node.message()));
626 EXPECT_FALSE(ChannelMessageIsLoggedOnNode(
627 &logged_on_separate_logger_node_channel.message(), &baz_node.message()));
628
629 // Logged in multiple places.
630 EXPECT_FALSE(ChannelMessageIsLoggedOnNode(&logged_on_both_channel.message(),
631 &foo_node.message()));
632 EXPECT_TRUE(ChannelMessageIsLoggedOnNode(&logged_on_both_channel.message(),
633 &bar_node.message()));
634 EXPECT_TRUE(ChannelMessageIsLoggedOnNode(&logged_on_both_channel.message(),
635 &baz_node.message()));
636}
637
Austin Schuh48e94502021-06-18 18:35:53 -0700638// Tests that our node message is logged helpers work as intended.
639TEST_F(ConfigurationDeathTest, ChannelMessageIsLoggedOnNode) {
640 FlatbufferDetachedBuffer<Channel> logged_on_both_channel(JsonToFlatbuffer(
641 R"channel({
642 "name": "/test",
643 "type": "aos.examples.Ping",
644 "source_node": "bar",
645 "logger": "LOCAL_AND_REMOTE_LOGGER",
646 "logger_nodes": ["baz"],
647 "destination_nodes": [
648 {
649 "name": "baz"
650 }
651 ]
652})channel",
653 Channel::MiniReflectTypeTable()));
654
655 FlatbufferDetachedBuffer<Channel> logged_on_separate_logger_node_channel(
656 JsonToFlatbuffer(
657 R"channel({
658 "name": "/test",
659 "type": "aos.examples.Ping",
660 "source_node": "bar",
661 "logger": "REMOTE_LOGGER",
662 "logger_nodes": ["foo"],
663 "destination_nodes": [
664 {
665 "name": "baz"
666 }
667 ]
668})channel",
669 Channel::MiniReflectTypeTable()));
670
671 EXPECT_DEATH(
672 {
673 ChannelMessageIsLoggedOnNode(&logged_on_both_channel.message(),
674 nullptr);
675 },
676 "Unsupported logging configuration in a single node world");
677 EXPECT_DEATH(
678 {
679 ChannelMessageIsLoggedOnNode(
680 &logged_on_separate_logger_node_channel.message(), nullptr);
681 },
682 "Unsupported logging configuration in a single node world");
683}
684
Austin Schuh719946b2019-12-28 14:51:01 -0800685// Tests that our forwarding timestamps are logged helpers work as intended.
686TEST_F(ConfigurationTest, ConnectionDeliveryTimeIsLoggedOnNode) {
687 FlatbufferDetachedBuffer<Channel> logged_on_self_channel(JsonToFlatbuffer(
688 R"channel({
689 "name": "/test",
690 "type": "aos.examples.Ping",
691 "source_node": "bar",
692 "logger": "REMOTE_LOGGER",
Austin Schuhda40e472020-03-28 15:15:29 -0700693 "logger_nodes": ["baz"],
Austin Schuh719946b2019-12-28 14:51:01 -0800694 "destination_nodes": [
695 {
696 "name": "baz"
697 }
698 ]
699})channel",
700 Channel::MiniReflectTypeTable()));
701
702 FlatbufferDetachedBuffer<Channel> not_logged_channel(JsonToFlatbuffer(
703 R"channel({
704 "name": "/test",
705 "type": "aos.examples.Ping",
706 "source_node": "bar",
707 "logger": "NOT_LOGGED",
708 "destination_nodes": [
709 {
710 "name": "baz",
711 "timestamp_logger": "NOT_LOGGED"
712 }
713 ]
714})channel",
715 Channel::MiniReflectTypeTable()));
716
717 FlatbufferDetachedBuffer<Channel> logged_on_remote_channel(JsonToFlatbuffer(
718 R"channel({
719 "name": "/test",
720 "type": "aos.examples.Ping",
721 "source_node": "bar",
722 "destination_nodes": [
723 {
724 "name": "baz",
725 "timestamp_logger": "REMOTE_LOGGER",
Austin Schuhda40e472020-03-28 15:15:29 -0700726 "timestamp_logger_nodes": ["bar"]
Austin Schuh719946b2019-12-28 14:51:01 -0800727 }
728 ]
729})channel",
730 Channel::MiniReflectTypeTable()));
731
732 FlatbufferDetachedBuffer<Channel> logged_on_separate_logger_node_channel(
733 JsonToFlatbuffer(
734 R"channel({
735 "name": "/test",
736 "type": "aos.examples.Ping",
737 "source_node": "bar",
738 "logger": "REMOTE_LOGGER",
Austin Schuhda40e472020-03-28 15:15:29 -0700739 "logger_nodes": ["foo"],
Austin Schuh719946b2019-12-28 14:51:01 -0800740 "destination_nodes": [
741 {
742 "name": "baz",
743 "timestamp_logger": "REMOTE_LOGGER",
Austin Schuhda40e472020-03-28 15:15:29 -0700744 "timestamp_logger_nodes": ["foo"]
Austin Schuh719946b2019-12-28 14:51:01 -0800745 }
746 ]
747})channel",
748 Channel::MiniReflectTypeTable()));
749
Ravago Jonescf453ab2020-05-06 21:14:53 -0700750 FlatbufferDetachedBuffer<Channel> logged_on_both_channel(JsonToFlatbuffer(
751 R"channel({
Austin Schuh719946b2019-12-28 14:51:01 -0800752 "name": "/test",
753 "type": "aos.examples.Ping",
754 "source_node": "bar",
755 "destination_nodes": [
756 {
757 "name": "baz",
758 "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
Austin Schuhda40e472020-03-28 15:15:29 -0700759 "timestamp_logger_nodes": ["bar"]
Austin Schuh719946b2019-12-28 14:51:01 -0800760 }
761 ]
762})channel",
Ravago Jonescf453ab2020-05-06 21:14:53 -0700763 Channel::MiniReflectTypeTable()));
Austin Schuh719946b2019-12-28 14:51:01 -0800764
765 FlatbufferDetachedBuffer<Node> foo_node(JsonToFlatbuffer(
766 R"node({
767 "name": "foo"
768})node",
769 Node::MiniReflectTypeTable()));
770
771 FlatbufferDetachedBuffer<Node> bar_node(JsonToFlatbuffer(
772 R"node({
773 "name": "bar"
774})node",
775 Node::MiniReflectTypeTable()));
776
777 FlatbufferDetachedBuffer<Node> baz_node(JsonToFlatbuffer(
778 R"node({
779 "name": "baz"
780})node",
781 Node::MiniReflectTypeTable()));
782
783 // Local logger.
784 EXPECT_FALSE(ConnectionDeliveryTimeIsLoggedOnNode(
785 &logged_on_self_channel.message(), &baz_node.message(),
786 &foo_node.message()));
787 EXPECT_FALSE(ConnectionDeliveryTimeIsLoggedOnNode(
788 &logged_on_self_channel.message(), &baz_node.message(),
789 &bar_node.message()));
790 EXPECT_TRUE(ConnectionDeliveryTimeIsLoggedOnNode(
791 &logged_on_self_channel.message(), &baz_node.message(),
792 &baz_node.message()));
793
794 // No logger means.
795 EXPECT_FALSE(ConnectionDeliveryTimeIsLoggedOnNode(
796 &not_logged_channel.message(), &baz_node.message(), &foo_node.message()));
797 EXPECT_FALSE(ConnectionDeliveryTimeIsLoggedOnNode(
798 &not_logged_channel.message(), &baz_node.message(), &bar_node.message()));
799 EXPECT_FALSE(ConnectionDeliveryTimeIsLoggedOnNode(
800 &not_logged_channel.message(), &baz_node.message(), &baz_node.message()));
801
802 // Remote logger.
803 EXPECT_FALSE(ConnectionDeliveryTimeIsLoggedOnNode(
804 &logged_on_remote_channel.message(), &baz_node.message(),
805 &foo_node.message()));
806 EXPECT_TRUE(ConnectionDeliveryTimeIsLoggedOnNode(
807 &logged_on_remote_channel.message(), &baz_node.message(),
808 &bar_node.message()));
809 EXPECT_FALSE(ConnectionDeliveryTimeIsLoggedOnNode(
810 &logged_on_remote_channel.message(), &baz_node.message(),
811 &baz_node.message()));
812
813 // Separate logger.
814 EXPECT_TRUE(ConnectionDeliveryTimeIsLoggedOnNode(
815 &logged_on_separate_logger_node_channel.message(), &baz_node.message(),
816 &foo_node.message()));
817 EXPECT_FALSE(ConnectionDeliveryTimeIsLoggedOnNode(
818 &logged_on_separate_logger_node_channel.message(), &baz_node.message(),
819 &bar_node.message()));
820 EXPECT_FALSE(ConnectionDeliveryTimeIsLoggedOnNode(
821 &logged_on_separate_logger_node_channel.message(), &baz_node.message(),
822 &baz_node.message()));
823
824 // Logged on both the node and a remote node.
825 EXPECT_FALSE(ConnectionDeliveryTimeIsLoggedOnNode(
826 &logged_on_both_channel.message(), &baz_node.message(),
827 &foo_node.message()));
828 EXPECT_TRUE(ConnectionDeliveryTimeIsLoggedOnNode(
829 &logged_on_both_channel.message(), &baz_node.message(),
830 &bar_node.message()));
831 EXPECT_TRUE(ConnectionDeliveryTimeIsLoggedOnNode(
832 &logged_on_both_channel.message(), &baz_node.message(),
833 &baz_node.message()));
834}
Austin Schuhe84c3ed2019-12-14 15:29:48 -0800835
836// Tests that we can deduce source nodes from a multinode config.
837TEST_F(ConfigurationTest, SourceNodeNames) {
838 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700839 ReadConfig(ArtifactPath("aos/testdata/config1_multinode.json"));
Austin Schuhe84c3ed2019-12-14 15:29:48 -0800840
841 // This is a bit simplistic in that it doesn't test deduplication, but it does
842 // exercise a lot of the logic.
843 EXPECT_THAT(
844 SourceNodeNames(&config.message(), config.message().nodes()->Get(0)),
845 ::testing::ElementsAreArray({"pi2"}));
846 EXPECT_THAT(
847 SourceNodeNames(&config.message(), config.message().nodes()->Get(1)),
848 ::testing::ElementsAreArray({"pi1"}));
849}
850
851// Tests that we can deduce destination nodes from a multinode config.
852TEST_F(ConfigurationTest, DestinationNodeNames) {
853 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700854 ReadConfig(ArtifactPath("aos/testdata/config1_multinode.json"));
Austin Schuhe84c3ed2019-12-14 15:29:48 -0800855
856 // This is a bit simplistic in that it doesn't test deduplication, but it does
857 // exercise a lot of the logic.
858 EXPECT_THAT(
859 DestinationNodeNames(&config.message(), config.message().nodes()->Get(0)),
860 ::testing::ElementsAreArray({"pi2"}));
861 EXPECT_THAT(
862 DestinationNodeNames(&config.message(), config.message().nodes()->Get(1)),
863 ::testing::ElementsAreArray({"pi1"}));
864}
865
Austin Schuhc9e10ec2020-01-26 16:08:28 -0800866// Tests that we can pull out all the nodes.
867TEST_F(ConfigurationTest, GetNodes) {
868 {
869 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700870 ReadConfig(ArtifactPath("aos/testdata/good_multinode.json"));
Austin Schuhc9e10ec2020-01-26 16:08:28 -0800871 const Node *pi1 = GetNode(&config.message(), "pi1");
872 const Node *pi2 = GetNode(&config.message(), "pi2");
873
874 EXPECT_THAT(GetNodes(&config.message()), ::testing::ElementsAre(pi1, pi2));
875 }
876
877 {
878 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700879 ReadConfig(ArtifactPath("aos/testdata/config1.json"));
Austin Schuhc9e10ec2020-01-26 16:08:28 -0800880 EXPECT_THAT(GetNodes(&config.message()), ::testing::ElementsAre(nullptr));
881 }
882}
883
Austin Schuh65465332020-11-05 17:36:53 -0800884// Tests that we can pull out all the nodes with a tag.
885TEST_F(ConfigurationTest, GetNodesWithTag) {
886 {
887 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700888 ReadConfig(ArtifactPath("aos/testdata/good_multinode.json"));
Austin Schuh65465332020-11-05 17:36:53 -0800889 const Node *pi1 = GetNode(&config.message(), "pi1");
890 const Node *pi2 = GetNode(&config.message(), "pi2");
891
892 EXPECT_THAT(GetNodesWithTag(&config.message(), "a"),
893 ::testing::ElementsAre(pi1));
894 EXPECT_THAT(GetNodesWithTag(&config.message(), "b"),
895 ::testing::ElementsAre(pi2));
896 EXPECT_THAT(GetNodesWithTag(&config.message(), "c"),
897 ::testing::ElementsAre(pi1, pi2));
898 }
899
900 {
901 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700902 ReadConfig(ArtifactPath("aos/testdata/config1.json"));
Austin Schuh65465332020-11-05 17:36:53 -0800903 EXPECT_THAT(GetNodesWithTag(&config.message(), "arglfish"),
904 ::testing::ElementsAre(nullptr));
905 }
906}
907
Brian Silverman631b6262021-11-10 12:25:08 -0800908// Tests that we can check if a node has a tag.
909TEST_F(ConfigurationTest, NodeHasTag) {
910 {
911 FlatbufferDetachedBuffer<Configuration> config =
912 ReadConfig(ArtifactPath("aos/testdata/good_multinode.json"));
913 const Node *pi1 = GetNode(&config.message(), "pi1");
914 const Node *pi2 = GetNode(&config.message(), "pi2");
915
916 EXPECT_TRUE(NodeHasTag(pi1, "a"));
917 EXPECT_FALSE(NodeHasTag(pi2, "a"));
918 EXPECT_FALSE(NodeHasTag(pi1, "b"));
919 EXPECT_TRUE(NodeHasTag(pi2, "b"));
920 EXPECT_TRUE(NodeHasTag(pi1, "c"));
921 EXPECT_TRUE(NodeHasTag(pi2, "c"));
922 EXPECT_FALSE(NodeHasTag(pi1, "nope"));
923 EXPECT_FALSE(NodeHasTag(pi2, "nope"));
924 }
925
926 EXPECT_TRUE(NodeHasTag(nullptr, "arglfish"));
927}
928
Austin Schuhc9e10ec2020-01-26 16:08:28 -0800929// Tests that we can extract a node index from a config.
930TEST_F(ConfigurationTest, GetNodeIndex) {
931 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700932 ReadConfig(ArtifactPath("aos/testdata/good_multinode.json"));
Austin Schuh04408fc2020-02-16 21:48:54 -0800933 FlatbufferDetachedBuffer<Configuration> config2 =
Austin Schuh373f1762021-06-02 21:07:09 -0700934 ReadConfig(ArtifactPath("aos/testdata/good_multinode.json"));
Austin Schuhc9e10ec2020-01-26 16:08:28 -0800935 const Node *pi1 = GetNode(&config.message(), "pi1");
936 const Node *pi2 = GetNode(&config.message(), "pi2");
937
Austin Schuh04408fc2020-02-16 21:48:54 -0800938 // Try the normal case.
Austin Schuhc9e10ec2020-01-26 16:08:28 -0800939 EXPECT_EQ(GetNodeIndex(&config.message(), pi1), 0);
940 EXPECT_EQ(GetNodeIndex(&config.message(), pi2), 1);
Austin Schuh04408fc2020-02-16 21:48:54 -0800941
942 // Now try if we have node pointers from a different message.
943 EXPECT_EQ(GetNodeIndex(&config2.message(), pi1), 0);
944 EXPECT_EQ(GetNodeIndex(&config2.message(), pi2), 1);
945
946 // And now try string names.
947 EXPECT_EQ(GetNodeIndex(&config2.message(), pi1->name()->string_view()), 0);
948 EXPECT_EQ(GetNodeIndex(&config2.message(), pi2->name()->string_view()), 1);
Austin Schuhc9e10ec2020-01-26 16:08:28 -0800949}
950
951// Tests that GetNodeOrDie handles both single and multi-node worlds and returns
952// valid nodes.
953TEST_F(ConfigurationDeathTest, GetNodeOrDie) {
954 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700955 ReadConfig(ArtifactPath("aos/testdata/good_multinode.json"));
Austin Schuhc9e10ec2020-01-26 16:08:28 -0800956 FlatbufferDetachedBuffer<Configuration> config2 =
Austin Schuh373f1762021-06-02 21:07:09 -0700957 ReadConfig(ArtifactPath("aos/testdata/good_multinode.json"));
Austin Schuhc9e10ec2020-01-26 16:08:28 -0800958 {
959 // Simple case, nullptr -> nullptr
960 FlatbufferDetachedBuffer<Configuration> single_node_config =
Austin Schuh373f1762021-06-02 21:07:09 -0700961 ReadConfig(ArtifactPath("aos/testdata/config1.json"));
Austin Schuhc9e10ec2020-01-26 16:08:28 -0800962 EXPECT_EQ(nullptr, GetNodeOrDie(&single_node_config.message(), nullptr));
963
964 // Confirm that we die when a node is passed in.
965 EXPECT_DEATH(
966 {
967 GetNodeOrDie(&single_node_config.message(),
968 config.message().nodes()->Get(0));
969 },
970 "Provided a node in a single node world.");
971 }
972
973 const Node *pi1 = GetNode(&config.message(), "pi1");
974 // Now try a lookup using a node from a different instance of the config.
975 EXPECT_EQ(pi1,
976 GetNodeOrDie(&config.message(), config2.message().nodes()->Get(0)));
977}
978
Brian Silvermanaa2633f2020-02-17 21:04:14 -0800979TEST_F(ConfigurationTest, GetNodeFromHostname) {
980 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700981 ReadConfig(ArtifactPath("aos/testdata/good_multinode.json"));
Brian Silvermanaa2633f2020-02-17 21:04:14 -0800982 EXPECT_EQ("pi1",
983 CHECK_NOTNULL(GetNodeFromHostname(&config.message(), "raspberrypi"))
984 ->name()
985 ->string_view());
986 EXPECT_EQ("pi2", CHECK_NOTNULL(
987 GetNodeFromHostname(&config.message(), "raspberrypi2"))
988 ->name()
989 ->string_view());
990 EXPECT_EQ(nullptr, GetNodeFromHostname(&config.message(), "raspberrypi3"));
991 EXPECT_EQ(nullptr, GetNodeFromHostname(&config.message(), "localhost"));
992 EXPECT_EQ(nullptr, GetNodeFromHostname(&config.message(), "3"));
993}
994
995TEST_F(ConfigurationTest, GetNodeFromHostnames) {
996 FlatbufferDetachedBuffer<Configuration> config =
Austin Schuh373f1762021-06-02 21:07:09 -0700997 ReadConfig(ArtifactPath("aos/testdata/good_multinode_hostnames.json"));
Brian Silvermanaa2633f2020-02-17 21:04:14 -0800998 EXPECT_EQ("pi1",
999 CHECK_NOTNULL(GetNodeFromHostname(&config.message(), "raspberrypi"))
1000 ->name()
1001 ->string_view());
1002 EXPECT_EQ("pi2", CHECK_NOTNULL(
1003 GetNodeFromHostname(&config.message(), "raspberrypi2"))
1004 ->name()
1005 ->string_view());
1006 EXPECT_EQ("pi2", CHECK_NOTNULL(
1007 GetNodeFromHostname(&config.message(), "raspberrypi3"))
1008 ->name()
1009 ->string_view());
Ravago Jonescf453ab2020-05-06 21:14:53 -07001010 EXPECT_EQ("pi2",
1011 CHECK_NOTNULL(GetNodeFromHostname(&config.message(), "other"))
1012 ->name()
1013 ->string_view());
Brian Silvermanaa2633f2020-02-17 21:04:14 -08001014 EXPECT_EQ(nullptr, GetNodeFromHostname(&config.message(), "raspberrypi4"));
1015 EXPECT_EQ(nullptr, GetNodeFromHostname(&config.message(), "localhost"));
1016 EXPECT_EQ(nullptr, GetNodeFromHostname(&config.message(), "3"));
1017}
1018
Austin Schuhfc7b6a02021-07-12 21:19:07 -07001019// Tests that SourceNodeIndex reasonably handles a multi-node log file.
1020TEST_F(ConfigurationTest, SourceNodeIndex) {
1021 FlatbufferDetachedBuffer<Configuration> config =
1022 ReadConfig(ArtifactPath("aos/testdata/good_multinode.json"));
1023 std::vector<size_t> result = SourceNodeIndex(&config.message());
1024
1025 EXPECT_THAT(result, ::testing::ElementsAreArray({0, 1, 0, 0}));
1026}
1027
Austin Schuh1ccc3a12024-04-30 17:46:29 -07001028// Tests that SourceNode reasonably handles both single and multi-node configs.
1029TEST_F(ConfigurationTest, SourceNode) {
1030 {
1031 FlatbufferDetachedBuffer<Configuration> config_single_node =
1032 ReadConfig(ArtifactPath("aos/testdata/config1.json"));
1033 const Node *result =
1034 SourceNode(&config_single_node.message(),
1035 config_single_node.message().channels()->Get(0));
1036 EXPECT_EQ(result, nullptr);
1037 }
1038
1039 {
1040 FlatbufferDetachedBuffer<Configuration> config_multi_node =
1041 ReadConfig(ArtifactPath("aos/testdata/good_multinode.json"));
1042 size_t pi1_channels = 0;
1043 size_t pi2_channels = 0;
1044 for (const aos::Channel *channel :
1045 *config_multi_node.message().channels()) {
1046 const Node *result = SourceNode(&config_multi_node.message(), channel);
1047 if (channel->source_node()->string_view() == "pi1") {
1048 ++pi1_channels;
1049 EXPECT_EQ(result, config_multi_node.message().nodes()->Get(0));
1050 } else {
1051 ++pi2_channels;
1052 EXPECT_EQ(result, config_multi_node.message().nodes()->Get(1));
1053 }
1054 }
1055 EXPECT_GT(pi1_channels, 0u);
1056 EXPECT_GT(pi2_channels, 0u);
1057 }
1058}
1059
Austin Schuh5e95bd62021-10-11 18:40:22 -07001060// Tests that we reject invalid logging configurations.
1061TEST_F(ConfigurationDeathTest, InvalidLoggerConfig) {
1062 EXPECT_DEATH(
1063 {
Milind Upadhyay17098ba2022-04-15 22:18:50 -07001064 FlatbufferDetachedBuffer<Configuration> config = ReadConfig(
1065 ArtifactPath("aos/testdata/invalid_logging_configuration.json"));
Austin Schuh5e95bd62021-10-11 18:40:22 -07001066 },
1067 "Logging timestamps without data");
1068}
1069
Austin Schuha156fb22021-10-11 19:23:21 -07001070// Tests that we reject duplicate timestamp destination node configurations.
1071TEST_F(ConfigurationDeathTest, DuplicateTimestampDestinationNodes) {
1072 EXPECT_DEATH(
1073 {
1074 FlatbufferDetachedBuffer<Configuration> config = ReadConfig(
1075 ArtifactPath("aos/testdata/duplicate_destination_nodes.json"));
1076 },
1077 "Found duplicate timestamp_logger_nodes in");
1078}
1079
1080// Tests that we reject duplicate logger node configurations for a channel's
1081// data.
1082TEST_F(ConfigurationDeathTest, DuplicateLoggerNodes) {
1083 EXPECT_DEATH(
1084 {
1085 FlatbufferDetachedBuffer<Configuration> config = ReadConfig(
1086 ArtifactPath("aos/testdata/duplicate_logger_nodes.json"));
1087 },
1088 "Found duplicate logger_nodes in");
1089}
1090
Austin Schuhfb37c612022-08-11 15:24:51 -07001091// Tests that we properly compute the queue size for the provided duration.
1092TEST_F(ConfigurationTest, QueueSize) {
1093 EXPECT_EQ(QueueSize(100, chrono::seconds(2)), 200);
1094 EXPECT_EQ(QueueSize(200, chrono::seconds(2)), 400);
1095 EXPECT_EQ(QueueSize(100, chrono::seconds(6)), 600);
1096 EXPECT_EQ(QueueSize(100, chrono::milliseconds(10)), 1);
1097 EXPECT_EQ(QueueSize(100, chrono::milliseconds(10) - chrono::nanoseconds(1)),
1098 1);
1099 EXPECT_EQ(QueueSize(100, chrono::milliseconds(10) - chrono::nanoseconds(2)),
1100 1);
1101}
1102
1103// Tests that we compute scratch buffer size correctly too.
1104TEST_F(ConfigurationTest, QueueScratchBufferSize) {
1105 const aos::FlatbufferDetachedBuffer<Channel> channel =
1106 JsonToFlatbuffer<Channel>(
1107 "{ \"name\": \"/foo\", \"type\": \".aos.bar\", \"num_readers\": 5, "
1108 "\"num_senders\": 10 }");
Austin Schuhfb37c612022-08-11 15:24:51 -07001109 EXPECT_EQ(QueueScratchBufferSize(&channel.message()), 15);
1110}
1111
Nathan Leong307c9692022-10-08 15:25:03 -07001112// Tests that GetSchema returns schema of specified type
1113TEST_F(ConfigurationTest, GetSchema) {
1114 FlatbufferDetachedBuffer<Configuration> config =
1115 ReadConfig(ArtifactPath("aos/events/pingpong_config.json"));
1116 FlatbufferVector<reflection::Schema> expected_schema =
1117 FileToFlatbuffer<reflection::Schema>(
1118 ArtifactPath("aos/events/ping.bfbs"));
1119 EXPECT_EQ(FlatbufferToJson(GetSchema(&config.message(), "aos.examples.Ping")),
1120 FlatbufferToJson(expected_schema));
1121 EXPECT_EQ(GetSchema(&config.message(), "invalid_name"), nullptr);
1122}
1123
1124// Tests that GetSchema template returns schema of specified type
1125TEST_F(ConfigurationTest, GetSchemaTemplate) {
1126 FlatbufferDetachedBuffer<Configuration> config =
1127 ReadConfig(ArtifactPath("aos/events/pingpong_config.json"));
1128 FlatbufferVector<reflection::Schema> expected_schema =
1129 FileToFlatbuffer<reflection::Schema>(
1130 ArtifactPath("aos/events/ping.bfbs"));
1131 EXPECT_EQ(FlatbufferToJson(GetSchema<aos::examples::Ping>(&config.message())),
1132 FlatbufferToJson(expected_schema));
1133}
1134
1135// Tests that GetSchemaDetachedBuffer returns detached buffer of specified type
1136TEST_F(ConfigurationTest, GetSchemaDetachedBuffer) {
1137 FlatbufferDetachedBuffer<Configuration> config =
1138 ReadConfig(ArtifactPath("aos/events/pingpong_config.json"));
1139 FlatbufferVector<reflection::Schema> expected_schema =
1140 FileToFlatbuffer<reflection::Schema>(
1141 ArtifactPath("aos/events/ping.bfbs"));
1142 EXPECT_EQ(FlatbufferToJson(
1143 GetSchemaDetachedBuffer(&config.message(), "aos.examples.Ping")
1144 .value()),
1145 FlatbufferToJson(expected_schema));
1146 EXPECT_EQ(GetSchemaDetachedBuffer(&config.message(), "invalid_name"),
1147 std::nullopt);
1148}
1149
James Kuszmaul741a4d02023-01-05 14:59:21 -08001150// Tests that we can use a utility to add individual channels to a single-node
1151// config.
1152TEST_F(ConfigurationTest, AddChannelToConfigSingleNode) {
1153 FlatbufferDetachedBuffer<Configuration> base_config =
1154 ReadConfig(ArtifactPath("aos/testdata/config1.json"));
1155
1156 FlatbufferVector<reflection::Schema> schema =
1157 FileToFlatbuffer<reflection::Schema>(
1158 ArtifactPath("aos/events/ping.bfbs"));
1159
1160 FlatbufferDetachedBuffer<Configuration> new_config =
1161 AddChannelToConfiguration(&base_config.message(), "/new", schema);
1162
1163 ASSERT_EQ(new_config.message().channels()->size(),
1164 base_config.message().channels()->size() + 1);
1165
1166 const Channel *channel =
1167 GetChannel(new_config, "/new", "aos.examples.Ping", "", nullptr);
1168 ASSERT_TRUE(channel != nullptr);
1169 ASSERT_TRUE(channel->has_schema());
1170 // Check that we don't populate channel settings that we don't override the
1171 // defaults of.
1172 ASSERT_FALSE(channel->has_frequency());
1173}
1174
1175// Tests that we can use a utility to add individual channels to a multi-node
1176// config.
1177TEST_F(ConfigurationTest, AddChannelToConfigMultiNode) {
1178 FlatbufferDetachedBuffer<Configuration> base_config =
1179 ReadConfig(ArtifactPath("aos/testdata/good_multinode.json"));
1180
1181 FlatbufferVector<reflection::Schema> schema =
1182 FileToFlatbuffer<reflection::Schema>(
1183 ArtifactPath("aos/events/ping.bfbs"));
1184
1185 aos::ChannelT channel_overrides;
1186 channel_overrides.frequency = 971;
1187 FlatbufferDetachedBuffer<Configuration> new_config =
1188 AddChannelToConfiguration(&base_config.message(), "/new", schema,
1189 GetNode(&base_config.message(), "pi1"),
1190 channel_overrides);
1191
1192 ASSERT_EQ(new_config.message().channels()->size(),
1193 base_config.message().channels()->size() + 1);
1194
1195 const Channel *channel =
1196 GetChannel(new_config, "/new", "aos.examples.Ping", "", nullptr);
1197 ASSERT_TRUE(channel != nullptr);
1198 ASSERT_TRUE(channel->has_schema());
1199 ASSERT_TRUE(channel->has_source_node());
1200 ASSERT_EQ("pi1", channel->source_node()->string_view());
1201 ASSERT_EQ(971, channel->frequency());
1202}
1203
Maxwell Gumley8c1b87f2024-02-13 17:54:52 -07001204// Create a new configuration with the specified channel removed.
1205// Initially there must be exactly one channel in the base_config that matches
1206// the criteria. Check to make sure the new configuration has one less channel,
1207// and that channel is the specified channel.
1208void TestGetPartialConfiguration(const Configuration &base_config,
1209 std::string_view test_channel_name,
1210 std::string_view test_channel_type) {
1211 const Channel *channel_from_base_config = GetChannel(
1212 &base_config, test_channel_name, test_channel_type, "", nullptr);
1213 ASSERT_TRUE(channel_from_base_config != nullptr);
1214
1215 const FlatbufferDetachedBuffer<Configuration> new_config =
1216 configuration::GetPartialConfiguration(
1217 base_config,
1218 // should_include_channel function
1219 [test_channel_name, test_channel_type](const Channel &channel) {
1220 if (channel.name()->string_view() == test_channel_name &&
1221 channel.type()->string_view() == test_channel_type) {
1222 LOG(INFO) << "Omitting channel from save_log, channel: "
1223 << channel.name()->string_view() << ", "
1224 << channel.type()->string_view();
1225 return false;
1226 }
1227 return true;
1228 });
1229
1230 EXPECT_EQ(new_config.message().channels()->size(),
1231 base_config.channels()->size() - 1);
1232
1233 channel_from_base_config = GetChannel(&base_config, test_channel_name,
1234 test_channel_type, "", nullptr);
1235 EXPECT_TRUE(channel_from_base_config != nullptr);
1236
1237 const Channel *channel_from_new_config =
1238 GetChannel(new_config, test_channel_name, test_channel_type, "", nullptr);
1239 EXPECT_TRUE(channel_from_new_config == nullptr);
1240}
1241
1242// Tests that we can use a utility to remove individual channels from a
1243// single-node config.
1244TEST_F(ConfigurationTest, RemoveChannelsFromConfigSingleNode) {
1245 FlatbufferDetachedBuffer<Configuration> base_config =
1246 ReadConfig(ArtifactPath("aos/testdata/config1.json"));
1247
1248 constexpr std::string_view test_channel_name = "/foo2";
1249 constexpr std::string_view test_channel_type = ".aos.bar";
1250
1251 TestGetPartialConfiguration(base_config.message(), test_channel_name,
1252 test_channel_type);
1253}
1254
1255// Tests that we can use a utility to remove individual channels from a
1256// multi-node config.
1257TEST_F(ConfigurationTest, RemoveChannelsFromConfigMultiNode) {
1258 FlatbufferDetachedBuffer<Configuration> base_config =
1259 ReadConfig(ArtifactPath("aos/testdata/good_multinode.json"));
1260
1261 constexpr std::string_view test_channel_name = "/batman";
1262 constexpr std::string_view test_channel_type = ".aos.baz";
1263
1264 TestGetPartialConfiguration(base_config.message(), test_channel_name,
1265 test_channel_type);
1266}
1267
Maxwell Gumleyf84aca32024-05-10 13:51:59 -06001268// Test fixture for testing IsNodeFromConfiguration.
1269// Initializes multiple configurations which share the same node names.
1270// Use IsNodeFromConfiguration to check if a node is in a configuration.
1271class IsNodeFromConfigurationFixtureTest : public ConfigurationTest {
1272 protected:
1273 // Use unique_ptr for deferred initialization
1274 std::unique_ptr<FlatbufferDetachedBuffer<Configuration>> config1;
1275 std::unique_ptr<FlatbufferDetachedBuffer<Configuration>> config2;
1276 const Node *node1_config1;
1277 const Node *node2_config1;
1278 const Node *node1_config2;
1279 const Node *node2_config2;
1280
1281 IsNodeFromConfigurationFixtureTest() {
1282 // Initialize configurations here
1283 config1 = std::make_unique<FlatbufferDetachedBuffer<Configuration>>(
1284 JsonToFlatbuffer(R"config({
1285 "nodes": [
1286 {"name": "node1"},
1287 {"name": "node2"}
1288 ]
1289 })config",
1290 Configuration::MiniReflectTypeTable()));
1291
1292 config2 = std::make_unique<FlatbufferDetachedBuffer<Configuration>>(
1293 JsonToFlatbuffer(R"config({
1294 "nodes": [
1295 {"name": "node1"},
1296 {"name": "node2"}
1297 ]
1298 })config",
1299 Configuration::MiniReflectTypeTable()));
1300
1301 // Initialize nodes after configuration initialization
1302 node1_config1 = config1->message().nodes()->Get(0);
1303 node2_config1 = config1->message().nodes()->Get(1);
1304 node1_config2 = config2->message().nodes()->Get(0);
1305 node2_config2 = config2->message().nodes()->Get(1);
1306 }
1307
1308 void SetUp() override {
1309 ConfigurationTest::SetUp();
1310 // Any additional setup can go here.
1311 }
1312};
1313
1314// Test case when node exists in the configuration.
1315TEST_F(IsNodeFromConfigurationFixtureTest, NodeExistsInConfiguration) {
1316 EXPECT_TRUE(IsNodeFromConfiguration(&config1->message(), node1_config1));
1317 EXPECT_TRUE(IsNodeFromConfiguration(&config1->message(), node2_config1));
1318}
1319
1320// Test case when node does not exist in the configuration.
1321TEST_F(IsNodeFromConfigurationFixtureTest, NodeDoesNotExistsInConfiguration) {
1322 EXPECT_FALSE(IsNodeFromConfiguration(&config1->message(), node1_config2));
1323 EXPECT_FALSE(IsNodeFromConfiguration(&config1->message(), node2_config2));
1324}
1325
1326// Test case for nodes with same names but from different configurations.
1327TEST_F(IsNodeFromConfigurationFixtureTest, SameNameDifferentConfiguration) {
1328 EXPECT_FALSE(IsNodeFromConfiguration(&config1->message(), node1_config2));
1329 EXPECT_FALSE(IsNodeFromConfiguration(&config1->message(), node2_config2));
1330 EXPECT_FALSE(IsNodeFromConfiguration(&config2->message(), node1_config1));
1331 EXPECT_FALSE(IsNodeFromConfiguration(&config2->message(), node2_config1));
1332}
1333
1334// Test case for null pointers.
1335TEST_F(IsNodeFromConfigurationFixtureTest, NullPointers) {
1336 EXPECT_FALSE(IsNodeFromConfiguration(nullptr, nullptr));
1337 EXPECT_FALSE(IsNodeFromConfiguration(&config1->message(), nullptr));
1338 EXPECT_FALSE(IsNodeFromConfiguration(nullptr, node1_config1));
1339}
1340
1341// Tests that SourceNode reasonably handles both single and multi-node configs.
1342TEST(IsNodeFromConfigurationTest, SingleNode) {
1343 FlatbufferDetachedBuffer<Configuration> config_single_node =
1344 ReadConfig(ArtifactPath("aos/testdata/config1.json"));
1345 EXPECT_TRUE(IsNodeFromConfiguration(&config_single_node.message(), nullptr));
1346}
1347
Austin Schuh605d5ff2024-05-10 15:59:54 -07001348// Tests that we can use a utility to remove individual channels from a
1349// multi-node config.
1350TEST_F(ConfigurationTest, MultinodeMerge) {
1351 FlatbufferDetachedBuffer<Configuration> config =
1352 ReadConfig(ArtifactPath("aos/testdata/multinode_merge.json"));
1353
1354 EXPECT_EQ(
1355 absl::StripSuffix(util::ReadFileToStringOrDie(ArtifactPath(
1356 "aos/testdata/multinode_merge_expected.json")),
1357 "\n"),
1358 FlatbufferToJson(config, {.multi_line = true}));
1359}
1360
Stephan Pleinesf63bde82024-01-13 15:59:33 -08001361} // namespace aos::configuration::testing