GCC Code Coverage Report


Directory: include/tts/
File: include/tts/tools/as_text.hpp
Date: 2026-10-14 16:28:14
Exec Total Coverage
Lines: 60 63 95.2%
Functions: 150 288 52.1%
Branches: 34 44 77.3%

Line Branch Exec Source
1 //======================================================================================================================
2 /*
3 TTS - Tiny Test System
4 Copyright : TTS Contributors & Maintainers
5 SPDX-License-Identifier: BSL-1.0
6 */
7 //======================================================================================================================
8 #pragma once
9
10 #include <tts/tools/text.hpp>
11 #include <tts/tools/options.hpp>
12 #include <tts/tools/source_location.hpp>
13 #include <tts/tools/types.hpp>
14
15 namespace tts
16 {
17 //====================================================================================================================
18 /**
19 @defgroup tools-text Text Utilities
20 @{
21 **/
22 //====================================================================================================================
23
24 //====================================================================================================================
25 /*!
26 @ingroup tools-text
27 @public
28 @brief Value-to-string conversion
29
30 When displaying the data required to understand a test's failures, TTS may need to print out
31 value of various types. @ref as_text provides a centralized way to perform such a task by handling
32 most common types including:
33 + pointers
34 + floating points
35 + integers
36 + sequences of such types
37
38 @ref as_text takes care of applying any command-line options related to formatting to the value
39 printed. For user defined types, an overload of a `to_text` function must be found via ADL.
40
41 @groupheader{Examples}
42 @snippet doc/as_text.cpp snippet
43
44 @param e Value to convert to @ref tts::text
45 @return An instance of @ref tts::text containing a representation of the value of e
46
47 @see tts::text
48 **/
49 //====================================================================================================================
50 // Declared before the base because the base recurses through it: the elements of a sequence
51 // are rendered by as_text, and for a scalar element ADL alone would find nothing.
52 template<typename T> text as_text(T const& e);
53
54 namespace _
55 {
56 //==================================================================================================================
57 // How TTS renders a value when nothing more specific is known. Pulled into a base so that a
58 // specialization of tts::display only has to say what it changes.
59 //==================================================================================================================
60 template<typename T> struct builtin_display
61 {
62 986 static text render(T const& e)
63 {
64 static_assert(
65 !requires { to_text(e); },
66 "[TTS] tts::to_text is no longer a customization point. "
67 "Specialize tts::display<T>::render instead.");
68
69 302 if constexpr(std::floating_point<T>) return floating(e);
70 498 else if constexpr(std::integral<T>) return integral(e);
71 else if constexpr(_::string<T>)
72
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
4 return text("'%.*s'", static_cast<int>(e.size()), e.data() ? e.data() : "");
73 4 else if constexpr(_::optional<T>) return optional(e);
74 6 else if constexpr(std::is_pointer_v<T>) return pointer(e);
75 166 else if constexpr(_::sequence<T>) return sequence(e);
76 6 else return bytes(e);
77 }
78
79 private:
80 297 static text floating(T const& e)
81 {
82 297 auto precision = ::tts::arguments().value(16, "--precision");
83 297 bool hexmode = ::tts::arguments()("-x", "--hex");
84 297 bool scimode = ::tts::arguments()("-s", "--scientific");
85
86
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 151 times.
297 if(scimode) return text("%.*E", precision, e);
87
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 151 times.
297 else if(hexmode) return text("%#.*A", precision, e);
88 297 else return text("%.*g", precision, e);
89 }
90
91 494 static text integral(T const& e)
92 {
93 if constexpr(sizeof(T) > 4)
94 {
95
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
4 auto fmt = ::tts::arguments()("-x", "--hex") ? "%lX" : "%ld";
96 4 return text(fmt, e);
97 }
98 else
99 {
100
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 249 times.
490 auto fmt = ::tts::arguments()("-x", "--hex") ? "%X" : "%d";
101 490 return text(fmt, e);
102 }
103 }
104
105 2 static text optional(T const& e)
106 {
107 // Safe access to type name
108
1/1
✓ Branch 1 taken 2 times.
2 auto type_desc = as_text(typename_<typename T::value_type>);
109
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 text base {"optional<%s>", type_desc.data() ? type_desc.data() : "unknown"};
110
111
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
2 if(e.has_value())
112 {
113
2/2
✓ Branch 1 taken 1 times.
✓ Branch 4 taken 1 times.
1 auto val_desc = as_text(e.value());
114
2/3
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 6 taken 1 times.
1 return base + text("{%s}", val_desc.data() ? val_desc.data() : "?");
115 1 }
116
1/1
✓ Branch 1 taken 1 times.
1 else return base + "{}";
117 2 }
118
119 6 static text pointer(T const& e)
120 {
121
1/1
✓ Branch 1 taken 3 times.
6 auto type_desc = as_text(typename_<T>);
122
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
12 return text("%p (%s)", (void*)(e), type_desc.data() ? type_desc.data() : "unknown");
123 6 }
124
125 166 static text sequence(T const& e)
126 {
127 166 text that("{ ");
128
4/4
✓ Branch 2 taken 327 times.
✓ Branch 3 taken 82 times.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 1 times.
828 for(auto const& v: e)
129
3/3
✓ Branch 1 taken 331 times.
✓ Branch 4 taken 331 times.
✓ Branch 7 taken 331 times.
662 that += as_text(v) + " ";
130
1/1
✓ Branch 1 taken 83 times.
166 that += "}";
131 166 return that;
132 ✗ }
133
134 6 static text bytes(T const& e)
135 {
136 // Display accessible bytes
137 6 auto const* raw = reinterpret_cast<std::byte const*>(&e);
138 6 text txt_bytes("[ ");
139
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 4 times.
86 for(std::size_t i = 0; i < sizeof(e); ++i)
140
2/2
✓ Branch 2 taken 56 times.
✓ Branch 5 taken 56 times.
160 txt_bytes += text("%2.2X", std::to_integer<unsigned>(raw[ i ])) + " ";
141
1/1
✓ Branch 1 taken 4 times.
6 txt_bytes += "]";
142
143
1/1
✓ Branch 1 taken 4 times.
6 auto type_desc = as_text(typename_<T>);
144 return text("%s: %s",
145 12 type_desc.data() ? type_desc.data() : "unknown",
146
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
24 txt_bytes.data() ? txt_bytes.data() : "[]");
147 6 }
148 };
149 }
150
151 //====================================================================================================================
152 /*!
153 @ingroup tools-text
154 @brief How a value of type T is rendered in a report
155
156 Specialize this rather than overloading to_text: a specialization that does not match is a
157 compilation error, where a misnamed overload fell back on the byte dump in silence. A type
158 without a specialization keeps the built-in rendering.
159
160 The `to_text` free function this replaces is gone. An overload left behind is reported where it
161 would have been used, rather than ignored in silence.
162 **/
163 //====================================================================================================================
164 template<typename T> struct display
165 {
166 };
167
168 //====================================================================================================================
169 // TTS renders its own types through its own trait. They used to carry a hidden friend to_text,
170 // which was the customization point being removed, so they answer here like anyone else would.
171 //====================================================================================================================
172 template<> struct display<text>
173 {
174 1 static text render(text const& t)
175 {
176 1 return t;
177 }
178 };
179
180 template<typename T> struct display<type<T>>
181 {
182 static text render(type<T> const&)
183 {
184 return as_text(typename_<T>);
185 }
186 };
187
188 template<typename T> struct display<_::typename_impl<T>>
189 {
190 730 static text render(_::typename_impl<T> const& t)
191 {
192 730 return text("%.*s", t.size(), t.data());
193 }
194 };
195
196 template<> struct display<_::source_location>
197 {
198 static text render(_::source_location const& s)
199 {
200 return text(s.data());
201 }
202 };
203 }
204
205 namespace tts::_
206 {
207 template<typename T>
208 concept described = requires(T const& e) { display<T>::render(e); };
209 }
210
211 namespace tts
212 {
213
214 1738 template<typename T> text as_text(T const& e)
215 {
216 748 if constexpr(_::described<T>) return display<T>::render(e);
217 else
218 {
219 static_assert(
220 !requires { display<T>::render; },
221 "[TTS] tts::display<T>::render does not accept a value of type T.");
222
223 990 return _::builtin_display<T>::render(e);
224 }
225 }
226
227 //====================================================================================================================
228 //! @}
229 //====================================================================================================================
230
231 ✗ template<std::size_t N> auto as_text(char const (&t)[ N ])
232 {
233 ✗ return text(t);
234 }
235 1 inline auto as_text(std::nullptr_t)
236 {
237 1 return text("nullptr");
238 }
239 2 inline auto as_text(bool b)
240 {
241
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 return text(b ? "true" : "false");
242 }
243 }
244