1 /** @file
2 
3     Unit tests for BufferFormat and bwprint.
4 
5     @section license License
6 
7     Licensed to the Apache Software Foundation (ASF) under one
8     or more contributor license agreements.  See the NOTICE file
9     distributed with this work for additional information
10     regarding copyright ownership.  The ASF licenses this file
11     to you under the Apache License, Version 2.0 (the
12     "License"); you may not use this file except in compliance
13     with the License.  You may obtain a copy of the License at
14 
15     http://www.apache.org/licenses/LICENSE-2.0
16 
17     Unless required by applicable law or agreed to in writing, software
18     distributed under the License is distributed on an "AS IS" BASIS,
19     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20     See the License for the specific language governing permissions and
21     limitations under the License.
22  */
23 
24 #include "catch.hpp"
25 #include "../../../tests/include/catch.hpp"
26 #include <chrono>
27 #include <iostream>
28 #include <netinet/in.h>
29 #include "tscore/BufferWriter.h"
30 #include "tscore/bwf_std_format.h"
31 #include "tscpp/util/MemSpan.h"
32 #include "tscore/ink_config.h"
33 #if TS_ENABLE_FIPS == 0
34 #include "tscore/INK_MD5.h"
35 #endif
36 #include "tscore/CryptoHash.h"
37 
38 using namespace std::literals;
39 
40 TEST_CASE("Buffer Writer << operator", "[bufferwriter][stream]")
41 {
42   ts::LocalBufferWriter<50> bw;
43 
44   bw << "The" << ' ' << "quick" << ' ' << "brown fox";
45 
46   REQUIRE(bw.view() == "The quick brown fox");
47 
48   bw.reduce(0);
49   bw << "x=" << bw.capacity();
50   REQUIRE(bw.view() == "x=50");
51 }
52 
53 TEST_CASE("bwprint basics", "[bwprint]")
54 {
55   ts::LocalBufferWriter<256> bw;
56   std::string_view fmt1{"Some text"sv};
57 
58   bw.print(fmt1);
59   REQUIRE(bw.view() == fmt1);
60   bw.reduce(0);
61   bw.print("Arg {}", 1);
62   REQUIRE(bw.view() == "Arg 1");
63   bw.reduce(0);
64   bw.print("arg 1 {1} and 2 {2} and 0 {0}", "zero", "one", "two");
65   REQUIRE(bw.view() == "arg 1 one and 2 two and 0 zero");
66   bw.reduce(0);
67   bw.print("args {2}{0}{1}", "zero", "one", "two");
68   REQUIRE(bw.view() == "args twozeroone");
69   bw.reduce(0);
70   bw.print("left |{:<10}|", "text");
71   REQUIRE(bw.view() == "left |text      |");
72   bw.reduce(0);
73   bw.print("right |{:>10}|", "text");
74   REQUIRE(bw.view() == "right |      text|");
75   bw.reduce(0);
76   bw.print("right |{:.>10}|", "text");
77   REQUIRE(bw.view() == "right |......text|");
78   bw.reduce(0);
79   bw.print("center |{:.^10}|", "text");
80   REQUIRE(bw.view() == "center |...text...|");
81   bw.reduce(0);
82   bw.print("center |{:.^11}|", "text");
83   REQUIRE(bw.view() == "center |...text....|");
84   bw.reduce(0);
85   bw.print("center |{:^^10}|", "text");
86   REQUIRE(bw.view() == "center |^^^text^^^|");
87   bw.reduce(0);
88   bw.print("center |{:%3A^10}|", "text");
89   REQUIRE(bw.view() == "center |:::text:::|");
90   bw.reduce(0);
91   bw.print("left >{0:<9}< right >{0:>9}< center >{0:^9}<", 956);
92   REQUIRE(bw.view() == "left >956      < right >      956< center >   956   <");
93 
94   bw.reduce(0);
95   bw.print("Format |{:>#010x}|", -956);
96   REQUIRE(bw.view() == "Format |0000-0x3bc|");
97   bw.reduce(0);
98   bw.print("Format |{:<#010x}|", -956);
99   REQUIRE(bw.view() == "Format |-0x3bc0000|");
100   bw.reduce(0);
101   bw.print("Format |{:#010x}|", -956);
102   REQUIRE(bw.view() == "Format |-0x00003bc|");
103 
104   bw.reduce(0);
105   bw.print("{{BAD_ARG_INDEX:{} of {}}}", 17, 23);
106   REQUIRE(bw.view() == "{BAD_ARG_INDEX:17 of 23}");
107 
108   bw.reduce(0);
109   bw.print("Arg {0} Arg {3}", 1, 2);
110   REQUIRE(bw.view() == "Arg 1 Arg {BAD_ARG_INDEX:3 of 2}");
111 
112   bw.reduce(0);
113   bw.print("{{stuff}} Arg {0} Arg {}", 1, 2);
114   REQUIRE(bw.view() == "{stuff} Arg 1 Arg 2");
115   bw.reduce(0);
116   bw.print("Arg {0} Arg {} and {{stuff}}", 3, 4);
117   REQUIRE(bw.view() == "Arg 3 Arg 4 and {stuff}");
118   bw.reduce(0);
119   bw.print("Arg {{{0}}} Arg {} and {{stuff}}", 5, 6);
120   REQUIRE(bw.view() == "Arg {5} Arg 6 and {stuff}");
121   bw.reduce(0);
122   bw.print("Arg {0} Arg {{}}{{}} {} and {{stuff}}", 7, 8);
123   REQUIRE(bw.view() == "Arg 7 Arg {}{} 8 and {stuff}");
124   bw.reduce(0);
125   bw.print("Arg {0} Arg {{{{}}}} {}", 9, 10);
126   REQUIRE(bw.view() == "Arg 9 Arg {{}} 10");
127 
128   bw.reduce(0);
129   bw.print("Arg {0} Arg {{{{}}}} {}", 9, 10);
130   REQUIRE(bw.view() == "Arg 9 Arg {{}} 10");
131   bw.reduce(0);
132 
133   bw.reset().print("{leif}");
134   REQUIRE(bw.view() == "{~leif~}"); // expected to be missing.
135 
136   bw.reset().print("Thread: {thread-name} [{thread-id:#x}] - Tick: {tick} - Epoch: {now} - timestamp: {timestamp} {0}\n", 31267);
137   // std::cout << bw;
138   /*
139   std::cout << ts::LocalBufferWriter<256>().print(
140     "Thread: {thread-name} [{thread-id:#x}] - Tick: {tick} - Epoch: {now} - timestamp: {timestamp} {0}{}", 31267, '\n');
141   */
142 }
143 
144 TEST_CASE("BWFormat numerics", "[bwprint][bwformat]")
145 {
146   ts::LocalBufferWriter<256> bw;
147   ts::BWFormat fmt("left >{0:<9}< right >{0:>9}< center >{0:^9}<");
148   std::string_view text{"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"};
149 
150   bw.reduce(0);
151   static const ts::BWFormat bad_arg_fmt{"{{BAD_ARG_INDEX:{} of {}}}"};
152   bw.print(bad_arg_fmt, 17, 23);
153   REQUIRE(bw.view() == "{BAD_ARG_INDEX:17 of 23}");
154 
155   bw.reduce(0);
156   bw.print(fmt, 956);
157   REQUIRE(bw.view() == "left >956      < right >      956< center >   956   <");
158 
159   bw.reduce(0);
160   bw.print("Text: _{0:.10,20}_", text);
161   REQUIRE(bw.view() == "Text: _abcdefghijklmnopqrst_");
162   bw.reduce(0);
163   bw.print("Text: _{0:-<20.52,20}_", text);
164   REQUIRE(bw.view() == "Text: _QRSTUVWXYZ----------_");
165 
166   void *ptr = reinterpret_cast<void *>(0XBADD0956);
167   bw.reduce(0);
168   bw.print("{}", ptr);
169   REQUIRE(bw.view() == "0xbadd0956");
170   bw.reduce(0);
171   bw.print("{:X}", ptr);
172   REQUIRE(bw.view() == "0XBADD0956");
173   int *int_ptr = static_cast<int *>(ptr);
174   bw.reduce(0);
175   bw.print("{}", int_ptr);
176   REQUIRE(bw.view() == "0xbadd0956");
177   auto char_ptr = "good";
178   bw.reduce(0);
179   bw.print("{:x}", static_cast<char *>(ptr));
180   REQUIRE(bw.view() == "0xbadd0956");
181   bw.reduce(0);
182   bw.print("{}", char_ptr);
183   REQUIRE(bw.view() == "good");
184 
185   ts::MemSpan span{ptr, 0x200};
186   bw.reduce(0);
187   bw.print("{}", span);
188   REQUIRE(bw.view() == "0x200@0xbadd0956");
189 
190   bw.reduce(0);
191   bw.print("{:x}", ts::MemSpan(const_cast<char *>(char_ptr), 4));
192   REQUIRE(bw.view() == "676f6f64");
193   bw.reduce(0);
194   bw.print("{:#x}", ts::MemSpan(const_cast<char *>(char_ptr), 4));
195   REQUIRE(bw.view() == "0x676f6f64");
196   bw.reduce(0);
197   bw.print("{:x}", ts::MemSpan<void>(const_cast<char *>(char_ptr), 4));
198   REQUIRE(bw.view() == "676f6f64");
199   bw.reduce(0);
200   bw.print("{:#x}", ts::MemSpan<void>(const_cast<char *>(char_ptr), 4));
201   REQUIRE(bw.view() == "0x676f6f64");
202 
203   std::string_view sv{"abc123"};
204   bw.reduce(0);
205   bw.print("{}", sv);
206   REQUIRE(bw.view() == sv);
207   bw.reduce(0);
208   bw.print("{:x}", sv);
209   REQUIRE(bw.view() == "616263313233");
210   bw.reduce(0);
211   bw.print("{:#x}", sv);
212   REQUIRE(bw.view() == "0x616263313233");
213   bw.reduce(0);
214   bw.print("|{:16x}|", sv);
215   REQUIRE(bw.view() == "|616263313233    |");
216   bw.reduce(0);
217   bw.print("|{:>16x}|", sv);
218   REQUIRE(bw.view() == "|    616263313233|");
219   bw.reduce(0);
220   bw.print("|{:^16x}|", sv);
221   REQUIRE(bw.view() == "|  616263313233  |");
222   bw.reduce(0);
223   bw.print("|{:>16.2x}|", sv);
224   REQUIRE(bw.view() == "|        63313233|");
225   bw.reduce(0);
226   bw.print("|{:<0.2,5x}|", sv);
227   REQUIRE(bw.view() == "|63313|");
228   bw.reset().print("|{:<.2,5x}|", sv);
229   REQUIRE(bw.view() == "|63313|");
230 
231   bw.reduce(0);
232   bw.print("|{}|", true);
233   REQUIRE(bw.view() == "|1|");
234   bw.reduce(0);
235   bw.print("|{}|", false);
236   REQUIRE(bw.view() == "|0|");
237   bw.reduce(0);
238   bw.print("|{:s}|", true);
239   REQUIRE(bw.view() == "|true|");
240   bw.reduce(0);
241   bw.print("|{:S}|", false);
242   REQUIRE(bw.view() == "|FALSE|");
243   bw.reduce(0);
244   bw.print("|{:>9s}|", false);
245   REQUIRE(bw.view() == "|    false|");
246   bw.reduce(0);
247   bw.print("|{:^10s}|", true);
248   REQUIRE(bw.view() == "|   true   |");
249 
250   // Test clipping a bit.
251   ts::LocalBufferWriter<20> bw20;
252   bw20.print("0123456789abc|{:^10s}|", true);
253   REQUIRE(bw20.view() == "0123456789abc|   tru");
254   bw20.reduce(0);
255   bw20.print("012345|{:^10s}|6789abc", true);
256   REQUIRE(bw20.view() == "012345|   true   |67");
257 
258 #if TS_ENABLE_FIPS == 0
259   INK_MD5 md5;
260   bw.reduce(0);
261   bw.print("{}", md5);
262   REQUIRE(bw.view() == "00000000000000000000000000000000");
263   CryptoContext().hash_immediate(md5, sv.data(), sv.size());
264   bw.reduce(0);
265   bw.print("{}", md5);
266   REQUIRE(bw.view() == "e99a18c428cb38d5f260853678922e03");
267 #endif
268 
269   bw.reset().print("Char '{}'", 'a');
270   REQUIRE(bw.view() == "Char 'a'");
271   bw.reset().print("Byte '{}'", uint8_t{'a'});
272   REQUIRE(bw.view() == "Byte '97'");
273 }
274 
275 TEST_CASE("bwstring", "[bwprint][bwstring]")
276 {
277   std::string s;
278   ts::TextView fmt("{} -- {}");
279   std::string_view text{"e99a18c428cb38d5f260853678922e03"};
280 
281   ts::bwprint(s, fmt, "string", 956);
282   REQUIRE(s.size() == 13);
283   REQUIRE(s == "string -- 956");
284 
285   ts::bwprint(s, fmt, 99999, text);
286   REQUIRE(s == "99999 -- e99a18c428cb38d5f260853678922e03");
287 
288   ts::bwprint(s, "{} .. |{:,20}|", 32767, text);
289   REQUIRE(s == "32767 .. |e99a18c428cb38d5f260|");
290 
291   ts::LocalBufferWriter<128> bw;
292   char buff[128];
293   snprintf(buff, sizeof(buff), "|%s|", bw.print("Deep Silent Complete by {}\0", "Nightwish"sv).data());
294   REQUIRE(std::string_view(buff) == "|Deep Silent Complete by Nightwish|");
295   snprintf(buff, sizeof(buff), "|%s|", bw.reset().print("Deep Silent Complete by {}\0elided junk", "Nightwish"sv).data());
296   REQUIRE(std::string_view(buff) == "|Deep Silent Complete by Nightwish|");
297 
298   // Special tests for clang analyzer failures - special asserts are needed to make it happy but
299   // those can break functionality.
300   fmt = "Did you know? {}{} is {}"sv;
301   s.resize(0);
302   ts::bwprint(s, fmt, "Lady "sv, "Persia"sv, "not mean");
303   REQUIRE(s == "Did you know? Lady Persia is not mean");
304   s.resize(0);
305   ts::bwprint(s, fmt, ""sv, "Phil", "correct");
306   REQUIRE(s == "Did you know? Phil is correct");
307   s.resize(0);
308   ts::bwprint(s, fmt, std::string_view(), "Leif", "confused");
309   REQUIRE(s == "Did you know? Leif is confused");
310 
311   {
312     std::string out;
313     ts::bwprint(out, fmt, ""sv, "Phil", "correct");
314     REQUIRE(out == "Did you know? Phil is correct");
315   }
316   {
317     std::string out;
318     ts::bwprint(out, fmt, std::string_view(), "Leif", "confused");
319     REQUIRE(out == "Did you know? Leif is confused");
320   }
321 
322   char const *null_string{nullptr};
323   ts::bwprint(s, "Null {0:x}.{0}", null_string);
324   REQUIRE(s == "Null 0x0.");
325   ts::bwprint(s, "Null {0:X}.{0}", nullptr);
326   REQUIRE(s == "Null 0X0.");
327   ts::bwprint(s, "Null {0:p}.{0:P}.{0:s}.{0:S}", null_string);
328   REQUIRE(s == "Null 0x0.0X0.null.NULL");
329 }
330 
331 TEST_CASE("BWFormat integral", "[bwprint][bwformat]")
332 {
333   ts::LocalBufferWriter<256> bw;
334   ts::BWFSpec spec;
335   uint32_t num = 30;
336   int num_neg  = -30;
337 
338   // basic
339   bwformat(bw, spec, num);
340   REQUIRE(bw.view() == "30");
341   bw.reduce(0);
342   bwformat(bw, spec, num_neg);
343   REQUIRE(bw.view() == "-30");
344   bw.reduce(0);
345 
346   // radix
347   ts::BWFSpec spec_hex;
348   spec_hex._radix_lead_p = true;
349   spec_hex._type         = 'x';
350   bwformat(bw, spec_hex, num);
351   REQUIRE(bw.view() == "0x1e");
352   bw.reduce(0);
353 
354   ts::BWFSpec spec_dec;
355   spec_dec._type = '0';
356   bwformat(bw, spec_dec, num);
357   REQUIRE(bw.view() == "30");
358   bw.reduce(0);
359 
360   ts::BWFSpec spec_bin;
361   spec_bin._radix_lead_p = true;
362   spec_bin._type         = 'b';
363   bwformat(bw, spec_bin, num);
364   REQUIRE(bw.view() == "0b11110");
365   bw.reduce(0);
366 
367   int one     = 1;
368   int two     = 2;
369   int three_n = -3;
370   // alignment
371   ts::BWFSpec left;
372   left._align = ts::BWFSpec::Align::LEFT;
373   left._min   = 5;
374   ts::BWFSpec right;
375   right._align = ts::BWFSpec::Align::RIGHT;
376   right._min   = 5;
377   ts::BWFSpec center;
378   center._align = ts::BWFSpec::Align::CENTER;
379   center._min   = 5;
380 
381   bwformat(bw, left, one);
382   bwformat(bw, right, two);
383   REQUIRE(bw.view() == "1        2");
384   bwformat(bw, right, two);
385   REQUIRE(bw.view() == "1        2    2");
386   bwformat(bw, center, three_n);
387   REQUIRE(bw.view() == "1        2    2 -3  ");
388 
389   std::atomic<int> ax{0};
390   bw.reset().print("ax == {}", ax);
391   REQUIRE(bw.view() == "ax == 0");
392   ++ax;
393   bw.reset().print("ax == {}", ax);
394   REQUIRE(bw.view() == "ax == 1");
395 }
396 
397 TEST_CASE("BWFormat floating", "[bwprint][bwformat]")
398 {
399   ts::LocalBufferWriter<256> bw;
400   ts::BWFSpec spec;
401 
402   bw.reduce(0);
403   bw.print("{}", 3.14);
404   REQUIRE(bw.view() == "3.14");
405   bw.reduce(0);
406   bw.print("{} {:.2} {:.0} ", 32.7, 32.7, 32.7);
407   REQUIRE(bw.view() == "32.70 32.70 32 ");
408   bw.reduce(0);
409   bw.print("{} neg {:.3}", -123.2, -123.2);
410   REQUIRE(bw.view() == "-123.20 neg -123.200");
411   bw.reduce(0);
412   bw.print("zero {} quarter {} half {} 3/4 {}", 0, 0.25, 0.50, 0.75);
413   REQUIRE(bw.view() == "zero 0 quarter 0.25 half 0.50 3/4 0.75");
414   bw.reduce(0);
415   bw.print("long {:.11}", 64.9);
416   REQUIRE(bw.view() == "long 64.90000000000");
417   bw.reduce(0);
418 
419   double n   = 180.278;
420   double neg = -238.47;
421   bwformat(bw, spec, n);
422   REQUIRE(bw.view() == "180.28");
423   bw.reduce(0);
424   bwformat(bw, spec, neg);
425   REQUIRE(bw.view() == "-238.47");
426   bw.reduce(0);
427 
428   spec._prec = 5;
429   bwformat(bw, spec, n);
430   REQUIRE(bw.view() == "180.27800");
431   bw.reduce(0);
432   bwformat(bw, spec, neg);
433   REQUIRE(bw.view() == "-238.47000");
434   bw.reduce(0);
435 
436   float f    = 1234;
437   float fneg = -1;
438   bwformat(bw, spec, f);
439   REQUIRE(bw.view() == "1234");
440   bw.reduce(0);
441   bwformat(bw, spec, fneg);
442   REQUIRE(bw.view() == "-1");
443   bw.reduce(0);
444   f          = 1234.5667;
445   spec._prec = 4;
446   bwformat(bw, spec, f);
447   REQUIRE(bw.view() == "1234.5667");
448   bw.reduce(0);
449 
450   bw << 1234 << .567;
451   REQUIRE(bw.view() == "12340.57");
452   bw.reduce(0);
453   bw << f;
454   REQUIRE(bw.view() == "1234.57");
455   bw.reduce(0);
456   bw << n;
457   REQUIRE(bw.view() == "180.28");
458   bw.reduce(0);
459   bw << f << n;
460   REQUIRE(bw.view() == "1234.57180.28");
461   bw.reduce(0);
462 
463   double edge = 0.345;
464   spec._prec  = 3;
465   bwformat(bw, spec, edge);
466   REQUIRE(bw.view() == "0.345");
467   bw.reduce(0);
468   edge = .1234;
469   bwformat(bw, spec, edge);
470   REQUIRE(bw.view() == "0.123");
471   bw.reduce(0);
472   edge = 1.0;
473   bwformat(bw, spec, edge);
474   REQUIRE(bw.view() == "1");
475   bw.reduce(0);
476 
477   // alignment
478   double first  = 1.23;
479   double second = 2.35;
480   double third  = -3.5;
481   ts::BWFSpec left;
482   left._align = ts::BWFSpec::Align::LEFT;
483   left._min   = 5;
484   ts::BWFSpec right;
485   right._align = ts::BWFSpec::Align::RIGHT;
486   right._min   = 5;
487   ts::BWFSpec center;
488   center._align = ts::BWFSpec::Align::CENTER;
489   center._min   = 5;
490 
491   bwformat(bw, left, first);
492   bwformat(bw, right, second);
493   REQUIRE(bw.view() == "1.23  2.35");
494   bwformat(bw, right, second);
495   REQUIRE(bw.view() == "1.23  2.35 2.35");
496   bwformat(bw, center, third);
497   REQUIRE(bw.view() == "1.23  2.35 2.35-3.50");
498   bw.reduce(0);
499 
500   double over = 1.4444444;
501   ts::BWFSpec over_min;
502   over_min._prec = 7;
503   over_min._min  = 5;
504   bwformat(bw, over_min, over);
505   REQUIRE(bw.view() == "1.4444444");
506   bw.reduce(0);
507 
508   // Edge
509   bw.print("{}", (1.0 / 0.0));
510   REQUIRE(bw.view() == "Inf");
511   bw.reduce(0);
512 
513   double inf = std::numeric_limits<double>::infinity();
514   bw.print("  {} ", inf);
515   REQUIRE(bw.view() == "  Inf ");
516   bw.reduce(0);
517 
518   double nan_1 = std::nan("1");
519   bw.print("{} {}", nan_1, nan_1);
520   REQUIRE(bw.view() == "NaN NaN");
521   bw.reduce(0);
522 
523   double z = 0.0;
524   bw.print("{}  ", z);
525   REQUIRE(bw.view() == "0  ");
526   bw.reduce(0);
527 }
528 
529 TEST_CASE("bwstring std formats", "[libts][bwprint]")
530 {
531   ts::LocalBufferWriter<120> w;
532 
533   w.print("{}", ts::bwf::Errno(13));
534   REQUIRE(w.view() == "EACCES: Permission denied [13]"sv);
535   w.reset().print("{}", ts::bwf::Errno(134));
536   REQUIRE(w.view().substr(0, 9) == "Unknown: "sv);
537 
538   time_t t = 1528484137;
539   // default is GMT
540   w.reset().print("{} is {}", t, ts::bwf::Date(t));
541   REQUIRE(w.view() == "1528484137 is 2018 Jun 08 18:55:37");
542   w.reset().print("{} is {}", t, ts::bwf::Date(t, "%a, %d %b %Y at %H.%M.%S"));
543   REQUIRE(w.view() == "1528484137 is Fri, 08 Jun 2018 at 18.55.37");
544   // OK to be explicit
545   w.reset().print("{} is {::gmt}", t, ts::bwf::Date(t));
546   REQUIRE(w.view() == "1528484137 is 2018 Jun 08 18:55:37");
547   w.reset().print("{} is {::gmt}", t, ts::bwf::Date(t, "%a, %d %b %Y at %H.%M.%S"));
548   REQUIRE(w.view() == "1528484137 is Fri, 08 Jun 2018 at 18.55.37");
549   // Local time - set it to something specific or the test will be geographically sensitive.
550   setenv("TZ", "CST6", 1);
551   tzset();
552   w.reset().print("{} is {::local}", t, ts::bwf::Date(t));
553   REQUIRE(w.view() == "1528484137 is 2018 Jun 08 12:55:37");
554   w.reset().print("{} is {::local}", t, ts::bwf::Date(t, "%a, %d %b %Y at %H.%M.%S"));
555   REQUIRE(w.view() == "1528484137 is Fri, 08 Jun 2018 at 12.55.37");
556 
557   // Verify these compile and run, not really much hope to check output.
558   w.reset().print("|{}|   |{}|", ts::bwf::Date(), ts::bwf::Date("%a, %d %b %Y"));
559 
560   w.reset().print("name = {}", ts::bwf::FirstOf("Persia"));
561   REQUIRE(w.view() == "name = Persia");
562   w.reset().print("name = {}", ts::bwf::FirstOf("Persia", "Evil Dave"));
563   REQUIRE(w.view() == "name = Persia");
564   w.reset().print("name = {}", ts::bwf::FirstOf("", "Evil Dave"));
565   REQUIRE(w.view() == "name = Evil Dave");
566   w.reset().print("name = {}", ts::bwf::FirstOf(nullptr, "Evil Dave"));
567   REQUIRE(w.view() == "name = Evil Dave");
568   w.reset().print("name = {}", ts::bwf::FirstOf("Persia", "Evil Dave", "Leif"));
569   REQUIRE(w.view() == "name = Persia");
570   w.reset().print("name = {}", ts::bwf::FirstOf("Persia", nullptr, "Leif"));
571   REQUIRE(w.view() == "name = Persia");
572   w.reset().print("name = {}", ts::bwf::FirstOf("", nullptr, "Leif"));
573   REQUIRE(w.view() == "name = Leif");
574 
575   const char *empty{nullptr};
576   std::string s1{"Persia"};
577   std::string_view s2{"Evil Dave"};
578   ts::TextView s3{"Leif"};
579   w.reset().print("name = {}", ts::bwf::FirstOf(empty, s3));
580   REQUIRE(w.view() == "name = Leif");
581   w.reset().print("name = {}", ts::bwf::FirstOf(s2, s3));
582   REQUIRE(w.view() == "name = Evil Dave");
583   w.reset().print("name = {}", ts::bwf::FirstOf(s1, empty, s2));
584   REQUIRE(w.view() == "name = Persia");
585   w.reset().print("name = {}", ts::bwf::FirstOf(empty, s2, s1, s3));
586   REQUIRE(w.view() == "name = Evil Dave");
587   w.reset().print("name = {}", ts::bwf::FirstOf(empty, empty, s3, empty, s2, s1));
588   REQUIRE(w.view() == "name = Leif");
589 
590   unsigned v = ntohl(0xdeadbeef);
591   w.reset().print("{}", ts::bwf::Hex_Dump(v));
592   REQUIRE(w.view() == "deadbeef");
593   w.reset().print("{:x}", ts::bwf::Hex_Dump(v));
594   REQUIRE(w.view() == "deadbeef");
595   w.reset().print("{:X}", ts::bwf::Hex_Dump(v));
596   REQUIRE(w.view() == "DEADBEEF");
597   w.reset().print("{:#X}", ts::bwf::Hex_Dump(v));
598   REQUIRE(w.view() == "0XDEADBEEF");
599   w.reset().print("{} bytes {} digits {}", sizeof(double), std::numeric_limits<double>::digits10, ts::bwf::Hex_Dump(2.718281828));
600   REQUIRE(w.view() == "8 bytes 15 digits 9b91048b0abf0540");
601 
602 #if TS_ENABLE_FIPS == 0
603   INK_MD5 md5;
604   w.reset().print("{}", ts::bwf::Hex_Dump(md5));
605   REQUIRE(w.view() == "00000000000000000000000000000000");
606   CryptoContext().hash_immediate(md5, s2.data(), s2.size());
607   w.reset().print("{}", ts::bwf::Hex_Dump(md5));
608   REQUIRE(w.view() == "f240ccd7a95c7ec66d6c111e2925b23e");
609 #endif
610 }
611 
612 // Normally there's no point in running the performance tests, but it's worth keeping the code
613 // for when additional testing needs to be done.
614 #if 0
615 TEST_CASE("bwperf", "[bwprint][performance]")
616 {
617   // Force these so I can easily change the set of tests.
618   auto start            = std::chrono::high_resolution_clock::now();
619   auto delta = std::chrono::high_resolution_clock::now() - start;
620   constexpr int N_LOOPS = 1000000;
621 
622   static constexpr const char * FMT = "Format |{:#010x}| '{}'";
623   static constexpr ts::TextView fmt{FMT, strlen(FMT)};
624   static constexpr std::string_view text{"e99a18c428cb38d5f260853678922e03"sv};
625   ts::LocalBufferWriter<256> bw;
626 
627   ts::BWFSpec spec;
628 
629   bw.reduce(0);
630   bw.print(fmt, -956, text);
631   REQUIRE(bw.view() == "Format |-0x00003bc| 'e99a18c428cb38d5f260853678922e03'");
632 
633   start = std::chrono::high_resolution_clock::now();
634   for (int i = 0; i < N_LOOPS; ++i) {
635     bw.reduce(0);
636     bw.print(fmt, -956, text);
637   }
638   delta = std::chrono::high_resolution_clock::now() - start;
639   std::cout << "bw.print() " << delta.count() << "ns or " << std::chrono::duration_cast<std::chrono::milliseconds>(delta).count()
640             << "ms" << std::endl;
641 
642   ts::BWFormat pre_fmt(fmt);
643   start = std::chrono::high_resolution_clock::now();
644   for (int i = 0; i < N_LOOPS; ++i) {
645     bw.reduce(0);
646     bw.print(pre_fmt, -956, text);
647   }
648   delta = std::chrono::high_resolution_clock::now() - start;
649   std::cout << "Preformatted: " << delta.count() << "ns or "
650             << std::chrono::duration_cast<std::chrono::milliseconds>(delta).count() << "ms" << std::endl;
651 
652   char buff[256];
653   start = std::chrono::high_resolution_clock::now();
654   for (int i = 0; i < N_LOOPS; ++i) {
655     snprintf(buff, sizeof(buff), "Format |%#0x10| '%.*s'", -956, static_cast<int>(text.size()), text.data());
656   }
657   delta = std::chrono::high_resolution_clock::now() - start;
658   std::cout << "snprint Timing is " << delta.count() << "ns or "
659             << std::chrono::duration_cast<std::chrono::milliseconds>(delta).count() << "ms" << std::endl;
660 }
661 #endif
662