File: tests.h

package info (click to toggle)
tomlplusplus 3.3.0%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 3,184 kB
  • sloc: cpp: 35,145; ansic: 2,220; python: 983; makefile: 25; sh: 17
file content (410 lines) | stat: -rw-r--r-- 13,849 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
// This file is a part of toml++ and is subject to the the terms of the MIT license.
// Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
// See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
// SPDX-License-Identifier: MIT

#pragma once
#include "settings.h"
#include <toml++/toml.h>

#if TOML_COMPILER_HAS_EXCEPTIONS ^ SHOULD_HAVE_EXCEPTIONS
#error TOML_COMPILER_HAS_EXCEPTIONS was not deduced correctly
#endif
#if TOML_COMPILER_HAS_EXCEPTIONS ^ TOML_EXCEPTIONS
#error TOML_EXCEPTIONS does not match TOML_COMPILER_HAS_EXCEPTIONS (default behaviour should be to match)
#endif
#if defined(_WIN32) ^ TOML_ENABLE_WINDOWS_COMPAT
#error TOML_ENABLE_WINDOWS_COMPAT does not match _WIN32 (default behaviour should be to match)
#endif
#if TOML_LIB_SINGLE_HEADER ^ USE_SINGLE_HEADER
#error TOML_LIB_SINGLE_HEADER was not set correctly
#endif

#if TOML_ICC
#define UNICODE_LITERALS_OK 0
#else
#define UNICODE_LITERALS_OK 1
#endif

TOML_DISABLE_SPAM_WARNINGS;
TOML_DISABLE_ARITHMETIC_WARNINGS;
#if TOML_CLANG == 13
#pragma clang diagnostic ignored "-Wreserved-identifier" // false-positive
#endif

TOML_DISABLE_WARNINGS;
#include "lib_catch2.h"
#include <sstream>
namespace toml
{
}
using namespace Catch::literals;
using namespace toml;
TOML_ENABLE_WARNINGS;

TOML_NODISCARD
TOML_ATTR(const)
TOML_ALWAYS_INLINE
constexpr size_t operator"" _sz(unsigned long long n) noexcept
{
	return static_cast<size_t>(n);
}

#define FILE_LINE_ARGS trim_file_path(std::string_view{ __FILE__ }), __LINE__
#define BOM_PREFIX	   "\xEF\xBB\xBF"

#if TOML_EXCEPTIONS
#define FORCE_FAIL(...) FAIL(__VA_ARGS__)
#else
#define FORCE_FAIL(...)                                                                                                \
	do                                                                                                                 \
	{                                                                                                                  \
		FAIL(__VA_ARGS__);                                                                                             \
		std::exit(-1);                                                                                                 \
		TOML_UNREACHABLE;                                                                                              \
	}                                                                                                                  \
	while (false)
#endif

#define CHECK_SYMMETRIC_RELOP(lhs, op, rhs, result)                                                                    \
	CHECK(((lhs)op(rhs)) == (result));                                                                                 \
	CHECK(((rhs)op(lhs)) == (result))

#define CHECK_SYMMETRIC_EQUAL(lhs, rhs)                                                                                \
	CHECK_SYMMETRIC_RELOP(lhs, ==, rhs, true);                                                                         \
	CHECK_SYMMETRIC_RELOP(lhs, !=, rhs, false)

#define CHECK_SYMMETRIC_INEQUAL(lhs, rhs)                                                                              \
	CHECK_SYMMETRIC_RELOP(lhs, ==, rhs, false);                                                                        \
	CHECK_SYMMETRIC_RELOP(lhs, !=, rhs, true)

template <typename T>
struct type_tag
{
	using type = T;
};

// function_view - adapted from here: https://vittorioromeo.info/index/blog/passing_functions_to_functions.html
template <typename Func>
class function_view;
template <typename R, typename... P>
class function_view<R(P...)> final
{
  private:
	using func_type		   = R(P...);
	using eraser_func_type = R(void*, P&&...);

	mutable void* ptr_				 = {};
	mutable eraser_func_type* eraser = {};

  public:
	function_view() noexcept = default;

	template <typename T>
	function_view(T&& x) noexcept : ptr_{ reinterpret_cast<void*>(std::addressof(x)) }
	{
		eraser = [](void* ptr, P&&... xs) -> R
		{ return (*reinterpret_cast<std::add_pointer_t<std::remove_reference_t<T>>>(ptr))(std::forward<P>(xs)...); };
	}

	decltype(auto) operator()(P&&... xs) const
	{
		return eraser(ptr_, std::forward<P>(xs)...);
	}

	TOML_NODISCARD
	operator bool() const noexcept
	{
		return !!ptr_;
	}
};

using pss_func = function_view<void(table&&)>;

bool parsing_should_succeed(std::string_view test_file,
							uint32_t test_line,
							std::string_view toml_str,
							pss_func&& func				 = {},
							std::string_view source_path = {});

bool parsing_should_fail(std::string_view test_file,
						 uint32_t test_line,
						 std::string_view toml_str,
						 source_index expected_failure_line	  = static_cast<source_index>(-1),
						 source_index expected_failure_column = static_cast<source_index>(-1));

TOML_PURE_GETTER
constexpr std::string_view trim_file_path(std::string_view sv) noexcept
{
	const auto src = std::min(sv.rfind("\\"sv), sv.rfind("/"sv));
	if (src != std::string_view::npos)
		sv = sv.substr(src + 1_sz);
	return sv;
}

template <typename T>
inline bool parse_expected_value(std::string_view test_file,
								 uint32_t test_line,
								 std::string_view value_str,
								 const T& expected)
{
	INFO("["sv << test_file << ", line "sv << test_line << "] "sv
			   << "parse_expected_value(\""sv << value_str << "\")"sv)

	std::string val;
	static constexpr auto key = "val = "sv;
	val.reserve(key.length() + value_str.length());
	val.append(key);
	val.append(value_str);

	static constexpr auto is_val = [](char32_t codepoint)
	{
		if constexpr (impl::node_type_of<T> == node_type::string)
			return codepoint == U'"' || codepoint == U'\'';
		else
			return !impl::is_whitespace(codepoint);
	};

	source_position pos{ 1, static_cast<source_index>(key.length()) };
	source_position begin{}, end{};
	{
		impl::utf8_decoder decoder;
		for (auto c : value_str)
		{
			decoder(static_cast<uint8_t>(c));
			if (!decoder.has_code_point())
				continue;

			if (impl::is_ascii_vertical_whitespace(decoder.codepoint))
			{
				if (decoder.codepoint == U'\n')
				{
					pos.line++;
					pos.column = source_index{ 1 };
				}
				continue;
			}

			pos.column++;
			if (is_val(decoder.codepoint))
			{
				if (!begin)
					begin = pos;
				else
					end = pos;
			}
		}
		if (!end)
			end = begin;
		end.column++;
	}

	using value_type = impl::native_type_of<impl::remove_cvref<T>>;
	value<value_type> val_parsed;
	{
		INFO("["sv << test_file << ", line "sv << test_line << "] "sv
				   << "parse_expected_value: Checking initial parse"sv)

		bool stolen_value = false; // parsing_should_succeed invokes the functor more than once
		const auto result = parsing_should_succeed(
			test_file,
			test_line,
			std::string_view{ val },
			[&](table&& tbl)
			{
				REQUIRE(tbl.size() == 1);
				auto nv = tbl["val"sv];
				REQUIRE(nv);
				REQUIRE(nv.is<value_type>());
				REQUIRE(nv.as<value_type>());
				REQUIRE(nv.type() == impl::node_type_of<T>);
				REQUIRE(nv.node());
				REQUIRE(nv.node()->is<value_type>());
				REQUIRE(nv.node()->as<value_type>());
				REQUIRE(nv.node()->type() == impl::node_type_of<T>);

				// check homogeneity
				REQUIRE(nv.is_homogeneous());
				REQUIRE(nv.is_homogeneous(node_type::none));
				REQUIRE(nv.is_homogeneous(impl::node_type_of<T>));
				REQUIRE(nv.is_homogeneous<value_type>());
				REQUIRE(nv.node()->is_homogeneous());
				REQUIRE(nv.node()->is_homogeneous(node_type::none));
				REQUIRE(nv.node()->is_homogeneous(impl::node_type_of<T>));
				REQUIRE(nv.node()->is_homogeneous<value_type>());
				for (auto nt = impl::unwrap_enum(node_type::table); nt <= impl::unwrap_enum(node_type::date_time); nt++)
				{
					if (node_type{ nt } == impl::node_type_of<T>)
						continue;
					node* first_nonmatch{};
					REQUIRE(!nv.is_homogeneous(node_type{ nt }));
					REQUIRE(!nv.is_homogeneous(node_type{ nt }, first_nonmatch));
					REQUIRE(first_nonmatch == nv.node());
					REQUIRE(!nv.node()->is_homogeneous(node_type{ nt }));
					REQUIRE(!nv.node()->is_homogeneous(node_type{ nt }, first_nonmatch));
					REQUIRE(first_nonmatch == nv.node());
				}

				// check the raw value
				REQUIRE(nv.node()->value<value_type>() == expected);
				REQUIRE(nv.node()->value_or(T{}) == expected);
				REQUIRE(nv.as<value_type>()->get() == expected);
				REQUIRE(nv.value<value_type>() == expected);
				REQUIRE(nv.value_or(T{}) == expected);
				REQUIRE(nv.ref<value_type>() == expected);
				REQUIRE(nv.node()->ref<value_type>() == expected);

				// check the table relops
				REQUIRE(tbl == table{ { { "val"sv, expected } } });
				REQUIRE(!(tbl != table{ { { "val"sv, expected } } }));

				// check value/node relops
				CHECK_SYMMETRIC_EQUAL(*nv.as<value_type>(), *nv.as<value_type>());
				CHECK_SYMMETRIC_EQUAL(*nv.as<value_type>(), expected);
				CHECK_SYMMETRIC_EQUAL(nv, expected);

				// make sure source info is correct
				CHECK_SYMMETRIC_EQUAL(nv.node()->source().begin, begin);
				CHECK_SYMMETRIC_EQUAL(nv.node()->source().end, end);

				// check float identities etc
				if constexpr (std::is_same_v<value_type, double>)
				{
					auto& float_node = *nv.as<value_type>();
					const auto fpcls = impl::fpclassify(*float_node);
					if (fpcls == impl::fp_class::nan)
					{
						CHECK_SYMMETRIC_EQUAL(float_node, std::numeric_limits<double>::quiet_NaN());
						CHECK_SYMMETRIC_INEQUAL(float_node, std::numeric_limits<double>::infinity());
						CHECK_SYMMETRIC_INEQUAL(float_node, -std::numeric_limits<double>::infinity());
						CHECK_SYMMETRIC_INEQUAL(float_node, 1.0);
						CHECK_SYMMETRIC_INEQUAL(float_node, 0.0);
						CHECK_SYMMETRIC_INEQUAL(float_node, -1.0);
					}
					else if (fpcls == impl::fp_class::neg_inf || fpcls == impl::fp_class::pos_inf)
					{
						CHECK_SYMMETRIC_INEQUAL(float_node, std::numeric_limits<double>::quiet_NaN());
						if (fpcls == impl::fp_class::neg_inf)
						{
							CHECK_SYMMETRIC_EQUAL(float_node, -std::numeric_limits<double>::infinity());
							CHECK_SYMMETRIC_INEQUAL(float_node, std::numeric_limits<double>::infinity());
						}
						else
						{
							CHECK_SYMMETRIC_EQUAL(float_node, std::numeric_limits<double>::infinity());
							CHECK_SYMMETRIC_INEQUAL(float_node, -std::numeric_limits<double>::infinity());
						}
						CHECK_SYMMETRIC_INEQUAL(float_node, 1.0);
						CHECK_SYMMETRIC_INEQUAL(float_node, 0.0);
						CHECK_SYMMETRIC_INEQUAL(float_node, -1.0);
					}
					else
					{
						CHECK_SYMMETRIC_INEQUAL(float_node, std::numeric_limits<double>::quiet_NaN());
						CHECK_SYMMETRIC_INEQUAL(float_node, std::numeric_limits<double>::infinity());
						CHECK_SYMMETRIC_INEQUAL(float_node, -std::numeric_limits<double>::infinity());
						CHECK_SYMMETRIC_EQUAL(float_node, *float_node);
						if (std::abs(*float_node) <= 1e10)
						{
							CHECK_SYMMETRIC_INEQUAL(float_node, *float_node + 100.0);
							CHECK_SYMMETRIC_INEQUAL(float_node, *float_node - 100.0);
						}
						CHECK(float_node < std::numeric_limits<double>::infinity());
						CHECK(float_node > -std::numeric_limits<double>::infinity());
					}
				}

				// steal the val for round-trip tests
				if (!stolen_value)
				{
					val_parsed	 = std::move(*nv.as<value_type>());
					stolen_value = true;
				}
			});

		if (!result)
			return false;
	}

	// check round-tripping
	{
		INFO("["sv << test_file << ", line "sv << test_line << "] "sv
				   << "parse_expected_value: Checking round-trip"sv)
		{
			std::string str;
			{
				auto tbl = table{ { { "val"sv, *val_parsed } } };
				std::ostringstream ss;
				ss << tbl;
				str = std::move(ss).str();
			}

			bool value_ok = true;
			const auto parse_ok =
				parsing_should_succeed(test_file,
									   test_line,
									   std::string_view{ str },
									   [&](table&& tbl)
									   {
										   REQUIRE(tbl.size() == 1);
										   auto nv = tbl["val"sv];
										   REQUIRE(nv);
										   REQUIRE(nv.as<value_type>());
										   REQUIRE(nv.node()->type() == impl::node_type_of<T>);

										   if (value_ok && nv.ref<value_type>() != expected)
										   {
											   value_ok = false;
											   FORCE_FAIL("Value was not the same after round-tripping"sv);
										   }
									   });

			if (!parse_ok || value_ok)
				return false;
		}
	}

	return true;
}

// manually instantiate some templates to reduce obj bloat and test compilation time

extern template bool parse_expected_value(std::string_view, uint32_t, std::string_view, const int&);
extern template bool parse_expected_value(std::string_view, uint32_t, std::string_view, const unsigned int&);
extern template bool parse_expected_value(std::string_view, uint32_t, std::string_view, const bool&);
extern template bool parse_expected_value(std::string_view, uint32_t, std::string_view, const float&);
extern template bool parse_expected_value(std::string_view, uint32_t, std::string_view, const double&);
extern template bool parse_expected_value(std::string_view, uint32_t, std::string_view, const std::string_view&);
namespace std
{
	extern template class unique_ptr<const Catch::IExceptionTranslator>;
}
namespace Catch
{
	extern template struct StringMaker<node_view<node>>;
	extern template struct StringMaker<node_view<const node>>;
	extern template ReusableStringStream& ReusableStringStream::operator<<(node_view<node> const&);
	extern template ReusableStringStream& ReusableStringStream::operator<<(node_view<const node> const&);
	namespace Detail
	{
		extern template std::string stringify(const node_view<node>&);
		extern template std::string stringify(const node_view<const node>&);
	}
}

#if TOML_CPP >= 20 && TOML_CLANG && TOML_CLANG <= 14 // https://github.com/llvm/llvm-project/issues/55560

TOML_PUSH_WARNINGS;
TOML_DISABLE_WARNINGS;

namespace
{
	[[maybe_unused]] static std::u8string clang_string_workaround(const char8_t* a, const char8_t* b)
	{
		return { a, b };
	}
}

TOML_POP_WARNINGS;

#endif