1+ #include < string>
2+ #include < fmt/format.h>
3+ #include < fmt/ranges.h>
4+
15#include " common/hobject.h"
26#include " gtest/gtest.h"
37
8+ using namespace std ::string_literals;
9+ using std::string;
10+
411TEST (HObject, cmp)
512{
613 hobject_t c{object_t {" fooc" }, " food" , CEPH_NOSNAP, 42 , 0 , " nspace" };
@@ -9,3 +16,303 @@ TEST(HObject, cmp)
916 ASSERT_EQ (-1 , cmp (c, d));
1017 ASSERT_EQ (-1 , cmp (d, e));
1118}
19+
20+ // ---- test methods that 'stringify' the object while escaping special characters ----
21+
22+
23+ /*
24+ * Two methods are used here: first - using a preset list of objects & hobjects,
25+ * comparing the output to the expected string; and a second method: comparing the
26+ * output for a "random"(*) object to the results when using the code from 'Squid'.
27+ *
28+ * (*) the object is not random, but it's not part of the preset list.
29+ */
30+
31+
32+ struct obj_n_expected_t {
33+ hobject_t obj;
34+ std::string expected_to_str;
35+ std::string expected_fmt;
36+ };
37+
38+ static std::vector<obj_n_expected_t > known_examples = {
39+
40+ // the first entry will be modified (by setting the max flag)
41+ {hobject_t {}, " MAX" , " MAX" },
42+
43+ {hobject_t {object_t (" o%:/name2" ), " aaaa" s, CEPH_NOSNAP, 67 , 0 , " n1" s},
44+ " 0000000000000000.34000000.head.o%p:/name2.aaaa.n1" ,
45+ " 0:c2000000:n1:aaaa:o%25%3a%2fname2:head" },
46+
47+ {hobject_t {object_t (" okey" ), " okey" s, CEPH_NOSNAP, 1 , 0 , " n12" s},
48+ " 0000000000000000.10000000.head.okey..n12" , " 0:80000000:n12::okey:head" },
49+
50+ {hobject_t {}, " 8000000000000000.00000000.0..." , " MIN" },
51+
52+ // / \todo not sure whether the '-1' or the 'FFF..' is correct:
53+ #if 0
54+ {hobject_t{object_t("oname"), std::string{}, 1, 234, -1, ""s},
55+ "FFFFFFFFFFFFFFFF.AE000000.1.oxxname..",
56+ "18446744073709551615:57000000:::oname:1"},
57+ #endif
58+ {hobject_t {object_t {" oname3" }, " oname3" s, CEPH_SNAPDIR, 910 , 1 , " n2" s},
59+ " 0000000000000001.E8300000.snapdir.oname3..n2" ,
60+ " 1:71c00000:n2::oname3:snapdir" },
61+
62+ {hobject_t {
63+ object_t (" nonprint\030 %_%.%" ), " c" s, 0x12345678 , 0xe0e0f0f0 , 0x2727 ,
64+ " n5" s},
65+ " 0000000000002727.0F0F0E0E.12345678.nonprint\x18 %p%u%p%e%p.c.n5" ,
66+ " 10023:0f0f0707:n5:c:nonprint%18%25_%25.%25:12345678" },
67+
68+ {hobject_t {object_t (" o//////" ), string (" ZZ" ), 0xaaaa , 65 , 1 , " zzzzz" },
69+ " 0000000000000001.14000000.aaaa.o//////.ZZ.zzzzz" ,
70+ " 1:82000000:zzzzz:ZZ:o%2f%2f%2f%2f%2f%2f:aaaa" }};
71+
72+ // original Ceph code as it was in version Squid
73+
74+ struct test_hobject_fmt_t : public hobject_t {
75+
76+ template <typename ... ARGS>
77+ test_hobject_fmt_t (ARGS&&... args) : hobject_t {std::forward<ARGS>(args)...}
78+ {}
79+
80+ test_hobject_fmt_t (const test_hobject_fmt_t & rhs) = default ;
81+ test_hobject_fmt_t (test_hobject_fmt_t && rhs) = default ;
82+ test_hobject_fmt_t & operator =(const test_hobject_fmt_t & rhs) = default ;
83+ test_hobject_fmt_t & operator =(test_hobject_fmt_t && rhs) = default ;
84+ test_hobject_fmt_t (hobject_t_max&& singleton) : test_hobject_fmt_t ()
85+ {
86+ max = true ;
87+ }
88+ test_hobject_fmt_t & operator =(hobject_t_max&& singleton)
89+ {
90+ *this = hobject_t ();
91+ max = true ;
92+ return *this ;
93+ }
94+ bool is_max () const { return max; }
95+ bool is_min () const
96+ {
97+ // this needs to match how it's constructed
98+ return snap == 0 && hash == 0 && !max && pool == INT64_MIN;
99+ }
100+
101+ constexpr auto operator <=>(const test_hobject_fmt_t & rhs) const noexcept
102+ {
103+ auto cmp = is_max () <=> rhs.is_max ();
104+ if (cmp != 0 )
105+ return cmp;
106+ cmp = pool <=> rhs.pool ;
107+ if (cmp != 0 )
108+ return cmp;
109+ cmp = get_bitwise_key () <=> rhs.get_bitwise_key ();
110+ if (cmp != 0 )
111+ return cmp;
112+ cmp = nspace <=> rhs.nspace ;
113+ if (cmp != 0 )
114+ return cmp;
115+ if (!(get_key ().empty () && rhs.get_key ().empty ())) {
116+ cmp = get_effective_key () <=> rhs.get_effective_key ();
117+ if (cmp != 0 )
118+ return cmp;
119+ }
120+ cmp = oid <=> rhs.oid ;
121+ if (cmp != 0 )
122+ return cmp;
123+ return snap <=> rhs.snap ;
124+ }
125+ constexpr bool operator ==(const hobject_t & rhs) const noexcept
126+ {
127+ return operator <=>(rhs) == 0 ;
128+ }
129+ };
130+
131+ static inline void append_out_escaped (const std::string& in, std::string* out)
132+ {
133+ for (auto i = in.cbegin (); i != in.cend (); ++i) {
134+ if (*i == ' %' || *i == ' :' || *i == ' /' || *i < 32 || *i >= 127 ) {
135+ char buf[4 ];
136+ snprintf (buf, sizeof (buf), " %%%02x" , (int )(unsigned char )*i);
137+ out->append (buf);
138+ } else {
139+ out->push_back (*i);
140+ }
141+ }
142+ }
143+
144+ // why don't we escape non-printable characters?
145+ static void append_escaped (const string& in, string* out)
146+ {
147+ for (string::const_iterator i = in.begin (); i != in.end (); ++i) {
148+ if (*i == ' %' ) {
149+ out->push_back (' %' );
150+ out->push_back (' p' );
151+ } else if (*i == ' .' ) {
152+ out->push_back (' %' );
153+ out->push_back (' e' );
154+ } else if (*i == ' _' ) {
155+ out->push_back (' %' );
156+ out->push_back (' u' );
157+ } else {
158+ out->push_back (*i);
159+ }
160+ }
161+ }
162+
163+ // original Ceph code as it was in version Squid
164+ string hobject_t::to_str () const
165+ {
166+ string out;
167+
168+ char snap_with_hash[1000 ];
169+ char * t = snap_with_hash;
170+ const char * end = t + sizeof (snap_with_hash);
171+
172+ uint64_t poolid (pool);
173+ t += snprintf (t, end - t, " %.*llX" , 16 , (long long unsigned )poolid);
174+
175+ uint32_t revhash (get_nibblewise_key_u32 ());
176+ t += snprintf (t, end - t, " .%.*X" , 8 , revhash);
177+
178+ if (snap == CEPH_NOSNAP)
179+ t += snprintf (t, end - t, " .head" );
180+ else if (snap == CEPH_SNAPDIR)
181+ t += snprintf (t, end - t, " .snapdir" );
182+ else
183+ t += snprintf (t, end - t, " .%llx" , (long long unsigned )snap);
184+
185+ out.append (snap_with_hash, t);
186+
187+ out.push_back (' .' );
188+ append_escaped (oid.name , &out);
189+ out.push_back (' .' );
190+ append_escaped (get_key (), &out);
191+ out.push_back (' .' );
192+ append_escaped (nspace, &out);
193+
194+ return out;
195+ }
196+
197+
198+ namespace fmt {
199+ // original Ceph code as it was in version Squid
200+ // (modified to use test_hobject_fmt_t)
201+ template <>
202+ struct formatter <test_hobject_fmt_t > {
203+
204+ constexpr auto parse (format_parse_context& ctx) { return ctx.begin (); }
205+
206+ template <typename FormatContext>
207+ auto format (const test_hobject_fmt_t & ho, FormatContext& ctx)
208+ {
209+ if (ho == hobject_t {}) {
210+ return fmt::format_to (ctx.out (), " MIN" );
211+ }
212+
213+ if (ho.is_max ()) {
214+ return fmt::format_to (ctx.out (), " MAX" );
215+ }
216+
217+ std::string v;
218+ append_out_escaped (ho.nspace , &v);
219+ v.push_back (' :' );
220+ append_out_escaped (ho.get_key (), &v);
221+ v.push_back (' :' );
222+ append_out_escaped (ho.oid .name , &v);
223+
224+ return fmt::format_to (
225+ ctx.out (), " {}:{:08x}:{}:{}" , static_cast <uint64_t >(ho.pool ),
226+ ho.get_bitwise_key_u32 (), v, ho.snap );
227+ }
228+ };
229+ } // namespace fmt
230+
231+
232+ TEST (HObject, to_str)
233+ {
234+ const auto dbg = false ; // turns on debug output
235+ known_examples[0 ].obj = hobject_t::get_max ();
236+
237+ for (const auto & [obj, expected_to_str, expected_fmt] : known_examples) {
238+ if (obj.is_max ()) {
239+ // no 'max' for to_str()
240+ continue ;
241+ }
242+ test_hobject_fmt_t legacy_obj{obj};
243+ if (dbg) {
244+ std::cout << " to_str(): legacy: " << legacy_obj.to_str ()
245+ << " . Now: " << obj.to_str () << std::endl;
246+ }
247+ EXPECT_EQ (legacy_obj.to_str (), obj.to_str ());
248+ EXPECT_EQ (expected_to_str, obj.to_str ());
249+ }
250+ }
251+
252+ // test the fmt::formatter for hobject_t vs legacy & the stream operator
253+ TEST (HObject, fmt)
254+ {
255+ const auto dbg = false ; // turns on debug output
256+ known_examples[0 ].obj = hobject_t::get_max ();
257+
258+ for (const auto & [obj, expected_to_str, expected_fmt] : known_examples) {
259+
260+ test_hobject_fmt_t legacy_obj{obj};
261+ if (dbg) {
262+ std::cout << fmt::format (" fmt: legacy: {} now: {}" , legacy_obj, obj)
263+ << std::endl;
264+ }
265+ EXPECT_EQ (fmt::format (" {}" , legacy_obj), fmt::format (" {}" , obj));
266+ EXPECT_EQ (expected_fmt, fmt::format (" {}" , obj));
267+
268+ if (dbg) {
269+ std::cout << " ostream: legacy: " << legacy_obj << " . Now: " << obj
270+ << std::endl;
271+ }
272+ std::ostringstream oss;
273+ oss << obj;
274+ std::ostringstream oss_legacy;
275+ oss_legacy << legacy_obj;
276+ EXPECT_EQ (oss_legacy.str (), oss.str ());
277+ EXPECT_EQ (oss.str (), fmt::format (" {}" , obj));
278+ }
279+ }
280+
281+ TEST (HObject, fmt_random)
282+ {
283+ const auto dbg = false ; // turns on debug output
284+ for (uint32_t i = 0 ; i < 10 ; i++) {
285+
286+ auto name_length = (i * 17 ) % 51 ;
287+ std::string name;
288+ for (int j = 0 ; j < name_length; j++) {
289+ name.push_back ((i * name_length + j) % 256 );
290+ }
291+
292+ std::string key =
293+ (i % 3 ) ? fmt::format (" key_{}::" , static_cast <unsigned char >(i)) : name;
294+
295+ snapid_t snap = (i % 7 ) ? i : ((i % 2 ) ? CEPH_SNAPDIR : CEPH_NOSNAP);
296+
297+ hobject_t obj{object_t {name}, key, snap, i, i % 10 , " n:_%.space" s};
298+
299+ test_hobject_fmt_t legacy_obj{obj};
300+
301+ if (dbg) {
302+ std::cout << fmt::format (" fmt: legacy: {} now: {}" , legacy_obj, obj)
303+ << std::endl;
304+ }
305+ EXPECT_EQ (fmt::format (" {}" , legacy_obj), fmt::format (" {}" , obj));
306+
307+ if (dbg) {
308+ std::cout << " ostream: legacy: " << legacy_obj << " . Now: " << obj
309+ << std::endl;
310+ }
311+ std::ostringstream oss;
312+ oss << obj;
313+ std::ostringstream oss_legacy;
314+ oss_legacy << legacy_obj;
315+ EXPECT_EQ (oss_legacy.str (), oss.str ());
316+ EXPECT_EQ (oss.str (), fmt::format (" {}" , obj));
317+ }
318+ }
0 commit comments