DCCL v5
Loading...
Searching...
No Matches
test.cpp
1// Copyright 2026:
2// GobySoft, LLC (2013-)
3// Community contributors (see AUTHORS file)
4// File authors:
5// Toby Schneider <toby@gobysoft.org>
6//
7//
8// This file is part of the Dynamic Compact Control Language Library
9// ("DCCL").
10//
11// DCCL is free software: you can redistribute it and/or modify
12// it under the terms of the GNU Lesser General Public License as published by
13// the Free Software Foundation, either version 2.1 of the License, or
14// (at your option) any later version.
15//
16// DCCL is distributed in the hope that it will be useful,
17// but WITHOUT ANY WARRANTY; without even the implied warranty of
18// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19// GNU Lesser General Public License for more details.
20//
21// You should have received a copy of the GNU Lesser General Public License
22// along with DCCL. If not, see <http://www.gnu.org/licenses/>.
23
24// Tests the Codec public API around the encode/decode happy path that the
25// other suites already cover: the raw char-buffer overload, load/unload
26// bookkeeping, size queries, the info() reports, and the load- and
27// encode-time error paths.
28
29#include <cassert>
30#include <iostream>
31#include <sstream>
32#include <string>
33
34#include "dccl/codec.h"
35
36#include "test.pb.h"
37
38namespace
39{
40int checks = 0;
41
42void check(bool condition, const std::string& what)
43{
44 ++checks;
45 if (!condition)
46 {
47 std::cerr << "FAILED: " << what << std::endl;
48 exit(1);
49 }
50}
51
52// Runs op and reports whether it threw the expected exception type.
53template <typename Exception, typename Callable>
54void check_throws(Callable op, const std::string& what)
55{
56 ++checks;
57 try
58 {
59 op();
60 }
61 catch (const Exception& e)
62 {
63 std::cout << "ok (threw as expected): " << what << ": " << e.what() << std::endl;
64 return;
65 }
66 catch (const std::exception& e)
67 {
68 std::cerr << "FAILED: " << what << " threw the wrong type: " << e.what() << std::endl;
69 exit(1);
70 }
71 std::cerr << "FAILED: " << what << " did not throw" << std::endl;
72 exit(1);
73}
74
75void test_raw_buffer_encode()
76{
77 dccl::Codec codec;
78 codec.load<dccl::test::SimpleMsg>();
79
80 dccl::test::SimpleMsg msg;
81 msg.set_a(42);
82 msg.set_b(7);
83
84 std::string as_string;
85 codec.encode(&as_string, msg);
86
87 char buffer[256];
88 size_t written = codec.encode(buffer, sizeof(buffer), msg);
89
90 check(written == as_string.size(), "char* and std::string overloads agree on size");
91 check(std::string(buffer, written) == as_string,
92 "char* and std::string overloads agree on bytes");
93 check(written == codec.size(msg), "size() matches the encoded length");
94
95 dccl::test::SimpleMsg decoded;
96 codec.decode(std::string(buffer, written), &decoded);
97 check(decoded.a() == 42 && decoded.b() == 7, "raw buffer round trips");
98
99 // header_only stops after the head, so it must be shorter
100 char head_buffer[256];
101 size_t head_written = codec.encode(head_buffer, sizeof(head_buffer), msg, true);
102 check(head_written > 0, "header-only encode writes something");
103 check(head_written < written, "header-only encode is shorter than a full encode");
104
105 // a buffer too small for even the head, and one too small for head+body
106 check_throws<std::length_error>([&]() { codec.encode(buffer, 0, msg); },
107 "encode into a zero-length buffer");
108 check_throws<std::length_error>([&]() { codec.encode(buffer, head_written, msg); },
109 "encode into a buffer that only fits the head");
110}
111
112void test_load_and_unload()
113{
114 dccl::Codec codec;
115
116 std::size_t hash = codec.load<dccl::test::SimpleMsg>();
117 check(hash != 0, "load returns a hash");
118 check(codec.load<dccl::test::SimpleMsg>() == hash, "loading twice yields the same hash");
119
120 codec.load<dccl::test::OtherMsg>();
121 check(codec.id<dccl::test::SimpleMsg>() == 220, "SimpleMsg keeps its declared id");
122 check(codec.id<dccl::test::OtherMsg>() == 221, "OtherMsg keeps its declared id");
123
124 // unload by descriptor, then by id
125 codec.unload<dccl::test::SimpleMsg>();
126 dccl::test::SimpleMsg msg;
127 msg.set_a(1);
128 check_throws<dccl::Exception>(
129 [&]()
130 {
131 std::string s;
132 codec.encode(&s, msg);
133 },
134 "encoding an unloaded message");
135
136 codec.unload(221);
137 dccl::test::OtherMsg other;
138 other.set_c(1);
139 check_throws<dccl::Exception>(
140 [&]()
141 {
142 std::string s;
143 codec.encode(&s, other);
144 },
145 "encoding a message unloaded by id");
146
147 // unloading something that was never loaded is a no-op, not an error
148 codec.unload(dccl::test::SimpleMsg::descriptor());
149 codec.unload(9999);
150 check(true, "unloading an unloaded message is a no-op");
151
152 codec.load<dccl::test::SimpleMsg>();
153 codec.load<dccl::test::OtherMsg>();
154 codec.unload_all();
155 check_throws<dccl::Exception>(
156 [&]()
157 {
158 std::string s;
159 codec.encode(&s, msg);
160 },
161 "encoding after unload_all");
162}
163
164void test_load_errors()
165{
166 dccl::Codec codec;
167
168 check_throws<dccl::Exception>([&]() { codec.load<dccl::test::NoMaxBytesMsg>(); },
169 "loading a message without max_bytes");
170 check_throws<dccl::Exception>([&]() { codec.load<dccl::test::NoCodecVersionMsg>(); },
171 "loading a message without codec_version");
172 check_throws<dccl::Exception>([&]() { codec.load<dccl::test::TooBigMsg>(); },
173 "loading a message that exceeds its own max_bytes");
174
175 // a second message claiming an id already in use must be rejected
176 codec.load<dccl::test::SimpleMsg>();
177 check_throws<dccl::Exception>([&]() { codec.load<dccl::test::DuplicateIdMsg>(); },
178 "loading a message with a duplicate DCCL id");
179
180 // codec versions 2 and 3 predate 'oneof' support
181 check_throws<dccl::Exception>([&]() { codec.load<dccl::test::OneofV2Msg>(); },
182 "loading a v2 message containing a oneof");
183 check_throws<dccl::Exception>([&]() { codec.load<dccl::test::OneofV3Msg>(); },
184 "loading a v3 message containing a oneof");
185}
186
187void test_encode_errors()
188{
189 dccl::Codec codec;
190 codec.load<dccl::test::SimpleMsg>();
191
192 // 'a' is required and unset
193 dccl::test::SimpleMsg incomplete;
194 check_throws<dccl::Exception>(
195 [&]()
196 {
197 std::string s;
198 codec.encode(&s, incomplete);
199 },
200 "encoding a message with an unset required field");
201
202 // the report of missing fields walks into sub-messages and repeated
203 // sub-messages
204 codec.load<dccl::test::NestedMsg>();
205 dccl::test::NestedMsg nested;
206 nested.mutable_single(); // present but its own required field is unset
207 nested.add_several();
208 nested.add_several();
209
210 bool named_the_field = false;
211 try
212 {
213 std::string s;
214 codec.encode(&s, nested);
215 }
216 catch (const dccl::Exception& e)
217 {
218 named_the_field = std::string(e.what()).find("inner_required") != std::string::npos;
219 }
220 check(named_the_field, "the error names the missing field inside a sub-message");
221
222 // out-of-bounds values are clamped by default, and throw under strict
223 dccl::test::SimpleMsg out_of_range;
224 out_of_range.set_a(999999);
225 std::string clamped;
226 codec.encode(&clamped, out_of_range);
227 check(!clamped.empty(), "an out-of-range field encodes when not strict");
228
229 codec.set_strict(true);
230 check_throws<dccl::OutOfRangeException>(
231 [&]()
232 {
233 std::string s;
234 codec.encode(&s, out_of_range);
235 },
236 "an out-of-range field under strict mode");
237 codec.set_strict(false);
238}
239
240void test_size_queries()
241{
242 dccl::Codec codec;
243 codec.load<dccl::test::SimpleMsg>();
244
245 unsigned max = codec.max_size<dccl::test::SimpleMsg>();
246 unsigned min = codec.min_size<dccl::test::SimpleMsg>();
247
248 check(min <= max, "min_size does not exceed max_size");
249 check(max <= 32, "max_size respects the declared max_bytes");
250
251 dccl::test::SimpleMsg msg;
252 msg.set_a(1);
253 msg.set_b(2);
254 unsigned actual = codec.size(msg);
255 check(actual >= min && actual <= max, "an actual encoding falls between min and max size");
256
257 // the descriptor overloads agree with the template ones
258 check(codec.max_size(dccl::test::SimpleMsg::descriptor()) == max,
259 "max_size descriptor overload agrees");
260 check(codec.min_size(dccl::test::SimpleMsg::descriptor()) == min,
261 "min_size descriptor overload agrees");
262}
263
264void test_info_output()
265{
266 dccl::Codec codec;
267 codec.load<dccl::test::SimpleMsg>();
268 codec.load<dccl::test::OtherMsg>();
269
270 std::stringstream single;
271 codec.info<dccl::test::SimpleMsg>(&single);
272 check(single.str().find("SimpleMsg") != std::string::npos, "info() names the message");
273 check(single.str().find("a") != std::string::npos, "info() lists fields");
274
275 std::stringstream all;
276 codec.info_all(&all);
277 check(all.str().find("SimpleMsg") != std::string::npos, "info_all() covers SimpleMsg");
278 check(all.str().find("OtherMsg") != std::string::npos, "info_all() covers OtherMsg");
279 check(all.str().find("2 messages loaded") != std::string::npos,
280 "info_all() reports how many messages are loaded");
281
282 // a console narrower than the title leaves no room for the guard bars, and
283 // must produce output rather than throwing
284 codec.set_console_width(1);
285 std::stringstream narrow;
286 codec.info_all(&narrow);
287 check(!narrow.str().empty(), "info_all() copes with a console narrower than its title");
288
289 codec.set_console_width(200);
290 std::stringstream wide;
291 codec.info_all(&wide);
292 check(!wide.str().empty(), "info_all() copes with a wide console");
293}
294
295void test_library_loading()
296{
297 dccl::Codec codec;
298
299 check_throws<dccl::Exception>([&]() { codec.load_library(static_cast<void*>(nullptr)); },
300 "load_library with a null handle");
301 check_throws<dccl::Exception>([&]() { codec.unload_library(nullptr); },
302 "unload_library with a null handle");
303 check_throws<dccl::Exception>([&]() { codec.load_library("/nonexistent/libnope.so"); },
304 "load_library with a path that does not resolve");
305}
306
307void test_id_codec()
308{
309 dccl::Codec codec;
310 check(codec.get_id_codec() == dccl::Codec::default_id_codec_name(),
311 "a default-constructed Codec uses the default id codec");
312
313 check_throws<dccl::Exception>([&]() { dccl::Codec bad("no.such.id.codec"); },
314 "constructing with an unknown id codec");
315
316 // setting a codec unloads everything, since ids may be reassigned
317 codec.load<dccl::test::SimpleMsg>();
318 check_throws<dccl::Exception>([&]() { codec.set_id_codec("no.such.id.codec"); },
319 "setting an unknown id codec");
320}
321
322void test_id_from_bytes()
323{
324 dccl::Codec codec;
325 codec.load<dccl::test::SimpleMsg>();
326
327 dccl::test::SimpleMsg msg;
328 msg.set_a(5);
329 std::string bytes;
330 codec.encode(&bytes, msg);
331
332 check(codec.id(bytes) == 220, "the id can be read back off the wire");
333 check(codec.id(bytes.begin(), bytes.end()) == 220, "the iterator id() overload agrees");
334 check(codec.id(dccl::test::SimpleMsg::descriptor()) == 220,
335 "the descriptor id() overload agrees");
336}
337} // namespace
338
339int main(int argc, char* argv[])
340{
341 bool verbose = false;
342 for (int i = 1; i < argc; ++i)
343 {
344 if (argv[i] && argv[i][0] == '-' && argv[i][1] == 'v' && argv[i][2] == '\0')
345 verbose = true;
346 }
347
348 dccl::dlog.connect(verbose ? dccl::logger::ALL : dccl::logger::WARN_PLUS, &std::cerr);
349
350 test_raw_buffer_encode();
351 test_load_and_unload();
352 test_load_errors();
353 test_encode_errors();
354 test_size_queries();
355 test_info_output();
356 test_library_loading();
357 test_id_codec();
358 test_id_from_bytes();
359
360 std::cout << checks << " checks passed" << std::endl;
361 return 0;
362}
The Dynamic CCL enCODer/DECoder. This is the main class you will use to load, encode and decode DCCL ...
Definition codec.h:61
Exception class for DCCL.
Definition exception.h:47
void connect(int verbosity_mask, Slot slot)
Connect the output of one or more given verbosities to a slot (function pointer or similar)
Definition logger.h:213
Dynamic Compact Control Language namespace.
Definition any.h:28