DCCL v5
Loading...
Searching...
No Matches
test.cpp
1// Copyright 2011-2026:
2// GobySoft, LLC (2013-)
3// Massachusetts Institute of Technology (2007-2014)
4// Community contributors (see AUTHORS file)
5// File authors:
6// Cadmus To <cadmus.to+dev@gmail.com>
7//
8//
9// This file is part of the Dynamic Compact Control Language Library
10// ("DCCL").
11//
12// DCCL is free software: you can redistribute it and/or modify
13// it under the terms of the GNU Lesser General Public License as published by
14// the Free Software Foundation, either version 2.1 of the License, or
15// (at your option) any later version.
16//
17// DCCL is distributed in the hope that it will be useful,
18// but WITHOUT ANY WARRANTY; without even the implied warranty of
19// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20// GNU Lesser General Public License for more details.
21//
22// You should have received a copy of the GNU Lesser General Public License
23// along with DCCL. If not, see <http://www.gnu.org/licenses/>.
24
25// Tests encoding and decoding of different float precisions on DefaultNumericFieldCodec
26
27#include "../../codec.h"
28#include "test.pb.h"
29
30#include <array>
31#include <cassert>
32#include <cmath>
33
34using namespace dccl::test;
35
36// Issue #149: min/max limits of floating point fields are not always inclusive
37// https://github.com/GobySoft/dccl/issues/149
38//
39// v4 codec fails (OutOfRangeException in strict mode) encoding a float equal to max boundary.
40// v5 codec (PR #152 fix) succeeds for the same value.
41static void test_issue149()
42{
43 dccl::Codec codec;
44
45 // ---- v4 codec: encoding value == max should fail (demonstrates the bug) ----
46 codec.load<Issue149V4Msg>();
47 codec.set_strict(true);
48
49 bool v4_threw = false;
50 {
51 Issue149V4Msg msg_in;
52 msg_in.set_a(0.05f);
53 try
54 {
55 std::string encoded;
56 codec.encode(&encoded, msg_in);
57 }
59 {
60 v4_threw = true;
61 }
62 }
63 assert(v4_threw && "issue #149: v4 codec must throw OutOfRangeException for value == max");
64 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << "issue #149: v4 encoding of max boundary value threw OutOfRangeException as expected" << std::endl;
65
66 codec.set_strict(false);
67
68 // ---- v5 codec: encoding value == max must succeed (verifies the fix) ----
69 codec.load<Issue149V5Msg>();
70 codec.set_strict(true);
71
72 {
73 const float test_val = 0.05f;
74 const float resolution = 0.01f; // precision=2 -> resolution=10^-2
75
76 Issue149V5Msg msg_in;
77 msg_in.set_a(test_val);
78
79 std::string encoded;
80 codec.encode(&encoded, msg_in);
81
82 Issue149V5Msg msg_out;
83 codec.decode(encoded, &msg_out);
84
85 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << "issue #149: v5 encoding of max boundary value succeeded, decoded = "
86 << msg_out.a() << std::endl;
87 assert(std::abs(msg_out.a() - test_val) <= resolution / 2 &&
88 "issue #149: v5 decoded value too far from input for max boundary");
89 }
90}
91
92int main(int argc, char* argv[])
93{
94 bool verbose = false;
95 for (int i = 1; i < argc; ++i)
96 {
97 if (argv[i] && argv[i][0] == '-' && argv[i][1] == 'v' && argv[i][2] == '\0')
98 verbose = true;
99 }
100
101 dccl::dlog.connect(verbose ? dccl::logger::ALL : dccl::logger::WARN_PLUS, &std::cerr);
102
103 dccl::Codec codec;
104
105 // Simple testing
106 codec.load<FloatMsg>();
107 codec.info<FloatMsg>(&dccl::dlog);
108
109 {
110 const auto *field_ptr = FloatMsg::GetDescriptor()->FindFieldByName("f");
111 assert(field_ptr);
112 const auto &field = *field_ptr;
113
114 const auto &opts = field.options();
115 const auto &dccl_ext = opts.GetExtension(dccl::field);
116
117 auto res = std::numeric_limits<double>::quiet_NaN();
118 if (dccl_ext.has_precision()) {
119 res = std::pow(10.0, -dccl_ext.precision());
120 } else {
121 res = dccl_ext.resolution();
122 }
123
124 const auto tol = res/2;
125 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << "Tolerance for DCCL float = " << res << " / 2 = " << tol << std::endl;
126
127 const auto test_cases = std::array<float, 3>{{0.000001f, 0.010001f, 1.000001f}};
128 for (const auto test_case : test_cases) {
129 auto msg_in = FloatMsg{};
130 msg_in.set_f(test_case);
131
132 auto encoded = std::string{};
133 codec.encode(&encoded, msg_in);
134
135 auto msg_out = FloatMsg{};
136 codec.decode(encoded, &msg_out);
137
138 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << "Checking if encoded value " << msg_out.f() << " is close enough to " << test_case << "..." << std::endl;
139 assert(std::abs(test_case - msg_out.f()) < tol);
140 }
141 }
142
143 // Testing with negative precision
144 codec.load<NegativePrecisionFloatMsg>();
145 codec.info<NegativePrecisionFloatMsg>(&dccl::dlog);
146
147 {
148 const auto *field_ptr = NegativePrecisionFloatMsg::GetDescriptor()->FindFieldByName("f");
149 assert(field_ptr);
150 const auto &field = *field_ptr;
151
152 const auto &opts = field.options();
153 const auto &dccl_ext = opts.GetExtension(dccl::field);
154
155 auto res = std::numeric_limits<double>::quiet_NaN();
156 if (dccl_ext.has_precision()) {
157 res = std::pow(10.0, -dccl_ext.precision());
158 } else {
159 res = dccl_ext.resolution();
160 }
161
162 const auto tol = res/2;
163 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << "Tolerance for DCCL float = " << res << " / 2 = " << tol << std::endl;
164
165 const auto test_cases = std::array<float, 3>{{10.f, -10.f, 1.f}};
166 for (const auto test_case : test_cases) {
167 auto msg_in = NegativePrecisionFloatMsg{};
168 msg_in.set_f(test_case);
169
170 auto encoded = std::string{};
171 codec.encode(&encoded, msg_in);
172
173 auto msg_out = NegativePrecisionFloatMsg{};
174 codec.decode(encoded, &msg_out);
175
176 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << "Checking if encoded value " << msg_out.f() << " is close enough to " << test_case << "..." << std::endl;
177 assert(std::abs(test_case - msg_out.f()) < tol);
178 }
179 }
180
181 // Test conversion of values at different precisions
182 codec.load<PrecisionRangeFloatMsg>();
183 codec.info<PrecisionRangeFloatMsg>(&dccl::dlog);
184
185 {
186 auto resolutions = std::vector<double>{};
187 resolutions.resize(7);
188 for (auto field_idx = 0ul; field_idx < 7; ++field_idx) {
189 const auto field_name = "prec" + std::to_string(field_idx);
190 const auto *field_ptr = PrecisionRangeFloatMsg::GetDescriptor()->FindFieldByName(field_name);
191 assert(field_ptr);
192 const auto &field = *field_ptr;
193
194 const auto &opts = field.options();
195 const auto &dccl_ext = opts.GetExtension(dccl::field);
196
197 if (dccl_ext.has_precision()) {
198 resolutions[field_idx] = std::pow(10.0, -dccl_ext.precision());
199 } else {
200 resolutions[field_idx] = dccl_ext.resolution();
201 }
202
203 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << "Tolerance for DCCL float number " << field_idx << " is " << resolutions[field_idx] << " / 2 = " << resolutions[field_idx]/2 << std::endl;
204 }
205
206 for (auto i = -100; i < 101; ++i) {
207 // Sweeping between the 6 and 7 decimal places of support for float.
208 // Float may not be able to express some of these values, but that's not
209 // in our scope. We're just making sure whatever float can express is
210 // preserved on the other end.
211
212 const auto test_val = 1000000+i;
213 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << "Testing encoding of " << test_val << " on a range of precisions..." << std::endl;
214 auto msg_in = PrecisionRangeFloatMsg{};
215 msg_in.set_prec0(test_val * resolutions[0]);
216 msg_in.set_prec1(test_val * resolutions[1]);
217 msg_in.set_prec2(test_val * resolutions[2]);
218 msg_in.set_prec3(test_val * resolutions[3]);
219 msg_in.set_prec4(test_val * resolutions[4]);
220 msg_in.set_prec5(test_val * resolutions[5]);
221 msg_in.set_prec6(test_val * resolutions[6]);
222
223 auto encoded = std::string{};
224 codec.encode(&encoded, msg_in);
225
226 auto msg_out = PrecisionRangeFloatMsg{};
227 codec.decode(encoded, &msg_out);
228
229 const auto diff0 = std::abs(msg_out.prec0() - msg_in.prec0());
230 const auto diff1 = std::abs(msg_out.prec1() - msg_in.prec1());
231 const auto diff2 = std::abs(msg_out.prec2() - msg_in.prec2());
232 const auto diff3 = std::abs(msg_out.prec3() - msg_in.prec3());
233 const auto diff4 = std::abs(msg_out.prec4() - msg_in.prec4());
234 const auto diff5 = std::abs(msg_out.prec5() - msg_in.prec5());
235 const auto diff6 = std::abs(msg_out.prec6() - msg_in.prec6());
236 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << "Checking if " << msg_out.prec0() << " is close enough to " << msg_in.prec0() << "...";
237 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << " i.e. Difference " << diff0 << "<=" << resolutions[0]/2 << std::endl;
238 assert(diff0 <= resolutions[0]/2);
239 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << "Checking if " << msg_out.prec1() << " is close enough to " << msg_in.prec1() << "...";
240 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << " i.e. Difference " << diff1 << "<=" << resolutions[1]/2 << std::endl;
241 assert(diff1 <= resolutions[1]/2);
242 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << "Checking if " << msg_out.prec2() << " is close enough to " << msg_in.prec2() << "...";
243 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << " i.e. Difference " << diff2 << "<=" << resolutions[2]/2 << std::endl;
244 assert(diff2 <= resolutions[2]/2);
245 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << "Checking if " << msg_out.prec3() << " is close enough to " << msg_in.prec3() << "...";
246 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << " i.e. Difference " << diff3 << "<=" << resolutions[3]/2 << std::endl;
247 assert(diff3 <= resolutions[3]/2);
248 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << "Checking if " << msg_out.prec4() << " is close enough to " << msg_in.prec4() << "...";
249 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << " i.e. Difference " << diff4 << "<=" << resolutions[4]/2 << std::endl;
250 assert(diff4 <= resolutions[4]/2);
251 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << "Checking if " << msg_out.prec5() << " is close enough to " << msg_in.prec5() << "...";
252 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << " i.e. Difference " << diff5 << "<=" << resolutions[5]/2 << std::endl;
253 assert(diff5 <= resolutions[5]/2);
254 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << "Checking if " << msg_out.prec6() << " is close enough to " << msg_in.prec6() << "...";
255 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << " i.e. Difference " << diff6 << "<=" << resolutions[6]/2 << std::endl;
256 assert(diff6 <= resolutions[6]/2);
257 }
258 }
259
260 test_issue149();
261
262 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << "all tests passed" << std::endl;
263}
The Dynamic CCL enCODer/DECoder. This is the main class you will use to load, encode and decode DCCL ...
Definition codec.h:61
bool is(logger::Verbosity verbosity, logger::Group group=logger::GENERAL)
Indicates the verbosity of the Logger until the next std::flush or std::endl. The boolean return is u...
Definition logger.h:191
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
Unit test namespace.
Definition test.cpp:45
Dynamic Compact Control Language namespace.
Definition any.h:28