1 /** @file
2 
3     IpMap unit tests.
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 "tscore/IpMap.h"
25 #include <sstream>
26 #include <catch.hpp>
27 #include <tscore/BufferWriter.h>
28 
29 std::ostream &
operator <<(std::ostream & s,IpEndpoint const & addr)30 operator<<(std::ostream &s, IpEndpoint const &addr)
31 {
32   ip_text_buffer b;
33   ats_ip_ntop(addr, b, sizeof(b));
34   s << b;
35   return s;
36 }
37 
38 void
IpMapTestPrint(IpMap & map)39 IpMapTestPrint(IpMap &map)
40 {
41   printf("IpMap Dump\n");
42   for (auto &spot : map) {
43     ip_text_buffer ipb1, ipb2;
44 
45     printf("%s - %s : %p\n", ats_ip_ntop(spot.min(), ipb1, sizeof ipb1), ats_ip_ntop(spot.max(), ipb2, sizeof(ipb2)), spot.data());
46   }
47   printf("\n");
48 }
49 
50 // --- Test helper classes ---
51 class MapMarkedAt : public Catch::MatcherBase<IpMap>
52 {
53   IpEndpoint const &_addr;
54 
55 public:
MapMarkedAt(IpEndpoint const & addr)56   MapMarkedAt(IpEndpoint const &addr) : _addr(addr) {}
57 
58   bool
match(IpMap const & map) const59   match(IpMap const &map) const override
60   {
61     return map.contains(&_addr);
62   }
63 
64   std::string
describe() const65   describe() const override
66   {
67     std::ostringstream ss;
68     ss << _addr << " is marked";
69     return ss.str();
70   }
71 };
72 
73 // The builder function
74 inline MapMarkedAt
IsMarkedAt(IpEndpoint const & _addr)75 IsMarkedAt(IpEndpoint const &_addr)
76 {
77   return {_addr};
78 }
79 
80 class MapMarkedWith : public Catch::MatcherBase<IpMap>
81 {
82   IpEndpoint const &_addr;
83   void *_mark;
84   mutable bool _found_p = false;
85 
86 public:
MapMarkedWith(IpEndpoint const & addr,void * mark)87   MapMarkedWith(IpEndpoint const &addr, void *mark) : _addr(addr), _mark(mark) {}
88 
89   bool
match(IpMap const & map) const90   match(IpMap const &map) const override
91   {
92     void *mark = nullptr;
93     return (_found_p = map.contains(&_addr, &mark)) && mark == _mark;
94   }
95 
96   std::string
describe() const97   describe() const override
98   {
99     std::ostringstream ss;
100     if (_found_p) {
101       ss << "is marked at " << _addr << " with " << std::hex << reinterpret_cast<intptr_t>(_mark);
102     } else {
103       ss << "is not marked at " << _addr;
104     }
105     return ss.str();
106   }
107 };
108 
109 inline MapMarkedWith
IsMarkedWith(IpEndpoint const & addr,void * mark)110 IsMarkedWith(IpEndpoint const &addr, void *mark)
111 {
112   return {addr, mark};
113 }
114 
115 // -------------
116 // --- TESTS ---
117 // -------------
118 TEST_CASE("IpMap Basic", "[libts][ipmap]")
119 {
120   IpMap map;
121   void *const markA = reinterpret_cast<void *>(1);
122   void *const markB = reinterpret_cast<void *>(2);
123   void *const markC = reinterpret_cast<void *>(3);
124   void *mark; // for retrieval
125 
126   in_addr_t ip5 = htonl(5), ip9 = htonl(9);
127   in_addr_t ip10 = htonl(10), ip15 = htonl(15), ip20 = htonl(20);
128   in_addr_t ip50 = htonl(50), ip60 = htonl(60);
129   in_addr_t ip100 = htonl(100), ip120 = htonl(120), ip140 = htonl(140);
130   in_addr_t ip150 = htonl(150), ip160 = htonl(160);
131   in_addr_t ip200 = htonl(200);
132   in_addr_t ip0   = 0;
133   in_addr_t ipmax = ~static_cast<in_addr_t>(0);
134 
135   map.mark(ip10, ip20, markA);
136   map.mark(ip5, ip9, markA);
137   {
138     INFO("Coalesce failed");
139     CHECK(map.count() == 1);
140   }
141   {
142     INFO("Range max not found.");
143     CHECK(map.contains(ip9));
144   }
145   {
146     INFO("Span min not found");
147     CHECK(map.contains(ip10, &mark));
148   }
149   {
150     INFO("Mark not preserved.");
151     CHECK(mark == markA);
152   }
153 
154   map.fill(ip15, ip100, markB);
155   {
156     INFO("Fill failed.");
157     CHECK(map.count() == 2);
158   }
159   {
160     INFO("fill interior missing");
161     CHECK(map.contains(ip50, &mark));
162   }
163   {
164     INFO("Fill mark not preserved.");
165     CHECK(mark == markB);
166   }
167   {
168     INFO("Span min not found.");
169     CHECK(!map.contains(ip200));
170   }
171   {
172     INFO("Old span interior not found");
173     CHECK(map.contains(ip15, &mark));
174   }
175   {
176     INFO("Fill overwrote mark.");
177     CHECK(mark == markA);
178   }
179 
180   map.clear();
181   {
182     INFO("Clear failed.");
183     CHECK(map.count() == 0);
184   }
185 
186   map.mark(ip20, ip50, markA);
187   map.mark(ip100, ip150, markB);
188   map.fill(ip10, ip200, markC);
189   CHECK(map.count() == 5);
190   {
191     INFO("Left span missing");
192     CHECK(map.contains(ip15, &mark));
193   }
194   {
195     INFO("Middle span missing");
196     CHECK(map.contains(ip60, &mark));
197   }
198   {
199     INFO("fill mark wrong.");
200     CHECK(mark == markC);
201   }
202   {
203     INFO("right span missing.");
204     CHECK(map.contains(ip160));
205   }
206   {
207     INFO("right span missing");
208     CHECK(map.contains(ip120, &mark));
209   }
210   {
211     INFO("wrong data on right mark span.");
212     CHECK(mark == markB);
213   }
214 
215   map.unmark(ip140, ip160);
216   {
217     INFO("unmark failed");
218     CHECK(map.count() == 5);
219   }
220   {
221     INFO("unmark left edge still there.");
222     CHECK(!map.contains(ip140));
223   }
224   {
225     INFO("unmark middle still there.");
226     CHECK(!map.contains(ip150));
227   }
228   {
229     INFO("unmark right edge still there.");
230     CHECK(!map.contains(ip160));
231   }
232 
233   map.clear();
234   map.mark(ip20, ip20, markA);
235   {
236     INFO("Map failed on singleton insert");
237     CHECK(map.contains(ip20));
238   }
239   map.mark(ip10, ip200, markB);
240   mark = 0;
241   map.contains(ip20, &mark);
242   {
243     INFO("Map held singleton against range.");
244     CHECK(mark == markB);
245   }
246   map.mark(ip100, ip120, markA);
247   map.mark(ip150, ip160, markB);
248   map.mark(ip0, ipmax, markC);
249   {
250     INFO("IpMap: Full range fill left extra ranges.");
251     CHECK(map.count() == 1);
252   }
253 }
254 
255 TEST_CASE("IpMap Unmark", "[libts][ipmap]")
256 {
257   IpMap map;
258   //  ip_text_buffer ipb1, ipb2;
259   void *const markA = reinterpret_cast<void *>(1);
260 
261   IpEndpoint a_0, a_0_0_0_16, a_0_0_0_17, a_max;
262   IpEndpoint a_10_28_56_0, a_10_28_56_4, a_10_28_56_255;
263   IpEndpoint a_10_28_55_255, a_10_28_57_0;
264   IpEndpoint a_63_128_1_12;
265   IpEndpoint a_loopback, a_loopback2;
266   IpEndpoint a6_0, a6_max, a6_fe80_9d90, a6_fe80_9d9d, a6_fe80_9d95;
267 
268   ats_ip_pton("0.0.0.0", &a_0);
269   ats_ip_pton("0.0.0.16", &a_0_0_0_16);
270   ats_ip_pton("0.0.0.17", &a_0_0_0_17);
271   ats_ip_pton("255.255.255.255", &a_max);
272   ats_ip_pton("10.28.55.255", &a_10_28_55_255);
273   ats_ip_pton("10.28.56.0", &a_10_28_56_0);
274   ats_ip_pton("10.28.56.4", &a_10_28_56_4);
275   ats_ip_pton("10.28.56.255", &a_10_28_56_255);
276   ats_ip_pton("10.28.57.0", &a_10_28_57_0);
277   ats_ip_pton("63.128.1.12", &a_63_128_1_12);
278   ats_ip_pton("::", &a6_0);
279   ats_ip_pton("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", &a6_max);
280   ats_ip_pton("fe80::221:9bff:fe10:9d90", &a6_fe80_9d90);
281   ats_ip_pton("fe80::221:9bff:fe10:9d9d", &a6_fe80_9d9d);
282   ats_ip_pton("fe80::221:9bff:fe10:9d95", &a6_fe80_9d95);
283   ats_ip_pton("127.0.0.1", &a_loopback);
284   ats_ip_pton("127.0.0.255", &a_loopback2);
285 
286   map.mark(&a_0, &a_max, markA);
287   {
288     INFO("IpMap Unmark: Full range not single.");
289     CHECK(map.count() == 1);
290   }
291   map.unmark(&a_10_28_56_0, &a_10_28_56_255);
292   {
293     INFO("IpMap Unmark: Range unmark failed.");
294     CHECK(map.count() == 2);
295   }
296   // Generic range check.
297   {
298     INFO("IpMap Unmark: Range unmark min address not removed.");
299     CHECK(!map.contains(&a_10_28_56_0));
300   }
301   {
302     INFO("IpMap Unmark: Range unmark max address not removed.");
303     CHECK(!map.contains(&a_10_28_56_255));
304   }
305   {
306     INFO("IpMap Unmark: Range unmark min-1 address removed.");
307     CHECK(map.contains(&a_10_28_55_255));
308   }
309   {
310     INFO("IpMap Unmark: Range unmark max+1 address removed.");
311     CHECK(map.contains(&a_10_28_57_0));
312   }
313   // Test min bounded range.
314   map.unmark(&a_0, &a_0_0_0_16);
315   {
316     INFO("IpMap Unmark: Range unmark zero address not removed.");
317     CHECK(!map.contains(&a_0));
318   }
319   {
320     INFO("IpMap Unmark: Range unmark zero bounded range max not removed.");
321     CHECK(!map.contains(&a_0_0_0_16));
322   }
323   {
324     INFO("IpMap Unmark: Range unmark zero bounded range max+1 removed.");
325     CHECK(map.contains(&a_0_0_0_17));
326   }
327 }
328 
329 TEST_CASE("IpMap Fill", "[libts][ipmap]")
330 {
331   IpMap map;
332   void *const allow = reinterpret_cast<void *>(0);
333   void *const deny  = reinterpret_cast<void *>(~0);
334   void *const markA = reinterpret_cast<void *>(1);
335   void *const markB = reinterpret_cast<void *>(2);
336   void *const markC = reinterpret_cast<void *>(3);
337 
338   IpEndpoint a0, a_10_28_56_0, a_10_28_56_4, a_10_28_56_255, a3, a4;
339   IpEndpoint a_9_255_255_255, a_10_0_0_0, a_10_0_0_19, a_10_0_0_255, a_10_0_1_0;
340   IpEndpoint a_max, a_loopback, a_loopback2;
341   IpEndpoint a_10_28_55_255, a_10_28_57_0;
342   IpEndpoint a_63_128_1_12;
343   IpEndpoint a_0000_0000, a_0000_0001, a_ffff_ffff;
344   IpEndpoint a_fe80_9d8f, a_fe80_9d90, a_fe80_9d95, a_fe80_9d9d, a_fe80_9d9e;
345 
346   ats_ip_pton("0.0.0.0", &a0);
347   ats_ip_pton("255.255.255.255", &a_max);
348 
349   ats_ip_pton("9.255.255.255", &a_9_255_255_255);
350   ats_ip_pton("10.0.0.0", &a_10_0_0_0);
351   ats_ip_pton("10.0.0.19", &a_10_0_0_19);
352   ats_ip_pton("10.0.0.255", &a_10_0_0_255);
353   ats_ip_pton("10.0.1.0", &a_10_0_1_0);
354 
355   ats_ip_pton("10.28.55.255", &a_10_28_55_255);
356   ats_ip_pton("10.28.56.0", &a_10_28_56_0);
357   ats_ip_pton("10.28.56.4", &a_10_28_56_4);
358   ats_ip_pton("10.28.56.255", &a_10_28_56_255);
359   ats_ip_pton("10.28.57.0", &a_10_28_57_0);
360 
361   ats_ip_pton("192.168.1.0", &a3);
362   ats_ip_pton("192.168.1.255", &a4);
363 
364   ats_ip_pton("::", &a_0000_0000);
365   ats_ip_pton("::1", &a_0000_0001);
366   ats_ip_pton("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", &a_ffff_ffff);
367   ats_ip_pton("fe80::221:9bff:fe10:9d8f", &a_fe80_9d8f);
368   ats_ip_pton("fe80::221:9bff:fe10:9d90", &a_fe80_9d90);
369   ats_ip_pton("fe80::221:9bff:fe10:9d95", &a_fe80_9d95);
370   ats_ip_pton("fe80::221:9bff:fe10:9d9d", &a_fe80_9d9d);
371   ats_ip_pton("fe80::221:9bff:fe10:9d9e", &a_fe80_9d9e);
372 
373   ats_ip_pton("127.0.0.0", &a_loopback);
374   ats_ip_pton("127.0.0.255", &a_loopback2);
375   ats_ip_pton("63.128.1.12", &a_63_128_1_12);
376 
377   SECTION("subnet overfill")
378   {
379     map.fill(&a_10_28_56_0, &a_10_28_56_255, deny);
380     map.fill(&a0, &a_max, allow);
381     CHECK_THAT(map, IsMarkedWith(a_10_28_56_4, deny));
382   }
383 
384   SECTION("singleton overfill")
385   {
386     map.fill(&a_loopback, &a_loopback, allow);
387     {
388       INFO("singleton not marked.");
389       CHECK_THAT(map, IsMarkedAt(a_loopback));
390     }
391     map.fill(&a0, &a_max, deny);
392     THEN("singleton mark")
393     {
394       CHECK_THAT(map, IsMarkedWith(a_loopback, allow));
395       THEN("not empty")
396       {
397         REQUIRE(map.begin() != map.end());
398         IpMap::iterator spot = map.begin();
399         ++spot;
400         THEN("more than one range")
401         {
402           REQUIRE(spot != map.end());
403           THEN("ranges disjoint")
404           {
405             INFO(" " << map.begin()->max() << " < " << spot->min());
406             REQUIRE(-1 == ats_ip_addr_cmp(map.begin()->max(), spot->min()));
407           }
408         }
409       }
410     }
411   }
412 
413   SECTION("3")
414   {
415     map.fill(&a_loopback, &a_loopback2, markA);
416     map.fill(&a_10_28_56_0, &a_10_28_56_255, markB);
417     {
418       INFO("over extended range");
419       CHECK_THAT(map, !IsMarkedWith(a_63_128_1_12, markC));
420     }
421     map.fill(&a0, &a_max, markC);
422     {
423       INFO("IpMap[2]: Fill failed.");
424       CHECK(map.count() == 5);
425     }
426     {
427       INFO("invalid mark in range gap");
428       CHECK_THAT(map, IsMarkedWith(a_63_128_1_12, markC));
429     }
430   }
431 
432   SECTION("4")
433   {
434     map.fill(&a_10_0_0_0, &a_10_0_0_255, allow);
435     map.fill(&a_loopback, &a_loopback2, allow);
436     {
437       INFO("invalid mark between ranges");
438       CHECK_THAT(map, !IsMarkedAt(a_63_128_1_12));
439     }
440     {
441       INFO("invalid mark in lower range");
442       CHECK_THAT(map, IsMarkedWith(a_10_0_0_19, allow));
443     }
444     map.fill(&a0, &a_max, deny);
445     {
446       INFO("range count incorrect");
447       CHECK(map.count() == 5);
448     }
449     {
450       INFO("mark between ranges");
451       CHECK_THAT(map, IsMarkedWith(a_63_128_1_12, deny));
452     }
453 
454     map.fill(&a_fe80_9d90, &a_fe80_9d9d, markA);
455     map.fill(&a_0000_0001, &a_0000_0001, markA);
456     map.fill(&a_0000_0000, &a_ffff_ffff, markB);
457 
458     {
459       INFO("IpMap Fill[v6]: Zero address has bad mark.");
460       CHECK_THAT(map, IsMarkedWith(a_0000_0000, markB));
461     }
462     {
463       INFO("IpMap Fill[v6]: Max address has bad mark.");
464       CHECK_THAT(map, IsMarkedWith(a_ffff_ffff, markB));
465     }
466     {
467       INFO("IpMap Fill[v6]: 9d90 address has bad mark.");
468       CHECK_THAT(map, IsMarkedWith(a_fe80_9d90, markA));
469     }
470     {
471       INFO("IpMap Fill[v6]: 9d8f address has bad mark.");
472       CHECK_THAT(map, IsMarkedWith(a_fe80_9d8f, markB));
473     }
474     {
475       INFO("IpMap Fill[v6]: 9d9d address has bad mark.");
476       CHECK_THAT(map, IsMarkedWith(a_fe80_9d9d, markA));
477     }
478     {
479       INFO("IpMap Fill[v6]: 9d9b address has bad mark.");
480       CHECK_THAT(map, IsMarkedWith(a_fe80_9d9e, markB));
481     }
482     {
483       INFO("IpMap Fill[v6]: ::1 has bad mark.");
484       CHECK_THAT(map, IsMarkedWith(a_0000_0001, markA));
485     }
486 
487     {
488       INFO("IpMap Fill[pre-refill]: Bad range count.");
489       CHECK(map.count() == 10);
490     }
491     // These should be ignored by the map as it is completely covered for IPv6.
492     map.fill(&a_fe80_9d90, &a_fe80_9d9d, markA);
493     map.fill(&a_0000_0001, &a_0000_0001, markC);
494     map.fill(&a_0000_0000, &a_ffff_ffff, markB);
495     {
496       INFO("IpMap Fill[post-refill]: Bad range count.");
497       CHECK(map.count() == 10);
498     }
499   }
500 
501   SECTION("5")
502   {
503     map.fill(&a_fe80_9d90, &a_fe80_9d9d, markA);
504     map.fill(&a_0000_0001, &a_0000_0001, markC);
505     map.fill(&a_0000_0000, &a_ffff_ffff, markB);
506     {
507       INFO("IpMap Fill[v6-2]: Zero address has bad mark.");
508       CHECK_THAT(map, IsMarkedWith(a_0000_0000, markB));
509     }
510     {
511       INFO("IpMap Fill[v6-2]: Max address has bad mark.");
512       CHECK_THAT(map, IsMarkedWith(a_ffff_ffff, markB));
513     }
514     {
515       INFO("IpMap Fill[v6-2]: 9d90 address has bad mark.");
516       CHECK_THAT(map, IsMarkedWith(a_fe80_9d90, markA));
517     }
518     {
519       INFO("IpMap Fill[v6-2]: 9d8f address has bad mark.");
520       CHECK_THAT(map, IsMarkedWith(a_fe80_9d8f, markB));
521     }
522     {
523       INFO("IpMap Fill[v6-2]: 9d9d address has bad mark.");
524       CHECK_THAT(map, IsMarkedWith(a_fe80_9d9d, markA));
525     }
526     {
527       INFO("IpMap Fill[v6-2]: 9d9b address has bad mark.");
528       CHECK_THAT(map, IsMarkedWith(a_fe80_9d9e, markB));
529     }
530     {
531       INFO("IpMap Fill[v6-2]: ::1 has bad mark.");
532       CHECK_THAT(map, IsMarkedWith(a_0000_0001, markC));
533     }
534   }
535 }
536 
537 TEST_CASE("IpMap CloseIntersection", "[libts][ipmap]")
538 {
539   IpMap map;
540   void *const markA = reinterpret_cast<void *>(1);
541   void *const markB = reinterpret_cast<void *>(2);
542   void *const markC = reinterpret_cast<void *>(3);
543   void *const markD = reinterpret_cast<void *>(4);
544 
545   IpEndpoint a_1_l, a_1_u, a_2_l, a_2_u, a_3_l, a_3_u, a_4_l, a_4_u, a_5_l, a_5_u, a_6_l, a_6_u, a_7_l, a_7_u;
546   IpEndpoint b_1_l, b_1_u;
547   IpEndpoint c_1_l, c_1_u, c_2_l, c_2_u, c_3_l, c_3_u;
548   IpEndpoint c_3_m;
549   IpEndpoint d_1_l, d_1_u, d_2_l, d_2_u;
550 
551   IpEndpoint a_1_m;
552 
553   ats_ip_pton("123.88.172.0", &a_1_l);
554   ats_ip_pton("123.88.180.93", &a_1_m);
555   ats_ip_pton("123.88.191.255", &a_1_u);
556   ats_ip_pton("123.89.132.0", &a_2_l);
557   ats_ip_pton("123.89.135.255", &a_2_u);
558   ats_ip_pton("123.89.160.0", &a_3_l);
559   ats_ip_pton("123.89.167.255", &a_3_u);
560   ats_ip_pton("123.90.108.0", &a_4_l);
561   ats_ip_pton("123.90.111.255", &a_4_u);
562   ats_ip_pton("123.90.152.0", &a_5_l);
563   ats_ip_pton("123.90.159.255", &a_5_u);
564   ats_ip_pton("123.91.0.0", &a_6_l);
565   ats_ip_pton("123.91.35.255", &a_6_u);
566   ats_ip_pton("123.91.40.0", &a_7_l);
567   ats_ip_pton("123.91.47.255", &a_7_u);
568 
569   ats_ip_pton("123.78.100.0", &b_1_l);
570   ats_ip_pton("123.78.115.255", &b_1_u);
571 
572   ats_ip_pton("123.88.204.0", &c_1_l);
573   ats_ip_pton("123.88.219.255", &c_1_u);
574   ats_ip_pton("123.90.112.0", &c_2_l);
575   ats_ip_pton("123.90.119.255", &c_2_u);
576   ats_ip_pton("123.90.132.0", &c_3_l);
577   ats_ip_pton("123.90.134.157", &c_3_m);
578   ats_ip_pton("123.90.135.255", &c_3_u);
579 
580   ats_ip_pton("123.82.196.0", &d_1_l);
581   ats_ip_pton("123.82.199.255", &d_1_u);
582   ats_ip_pton("123.82.204.0", &d_2_l);
583   ats_ip_pton("123.82.219.255", &d_2_u);
584 
585   map.mark(a_1_l, a_1_u, markA);
586   map.mark(a_2_l, a_2_u, markA);
587   map.mark(a_3_l, a_3_u, markA);
588   map.mark(a_4_l, a_4_u, markA);
589   map.mark(a_5_l, a_5_u, markA);
590   map.mark(a_6_l, a_6_u, markA);
591   map.mark(a_7_l, a_7_u, markA);
592   CHECK_THAT(map, IsMarkedAt(a_1_m));
593 
594   map.mark(b_1_l, b_1_u, markB);
595   CHECK_THAT(map, IsMarkedWith(a_1_m, markA));
596 
597   map.mark(c_1_l, c_1_u, markC);
598   map.mark(c_2_l, c_2_u, markC);
599   map.mark(c_3_l, c_3_u, markC);
600   CHECK_THAT(map, IsMarkedWith(a_1_m, markA));
601 
602   map.mark(d_1_l, d_1_u, markD);
603   map.mark(d_2_l, d_2_u, markD);
604   CHECK_THAT(map, IsMarkedAt(a_1_m));
605   CHECK_THAT(map, IsMarkedWith(b_1_u, markB));
606   CHECK_THAT(map, IsMarkedWith(c_3_m, markC));
607   CHECK_THAT(map, IsMarkedWith(d_2_l, markD));
608 
609   CHECK(map.count() == 13);
610 
611   // Check move constructor.
612   IpMap m2{std::move(map)};
613   // Original map should be empty.
614   REQUIRE(map.count() == 0);
615   // Do all these again on the destination map.
616   CHECK_THAT(m2, IsMarkedWith(a_1_m, markA));
617   CHECK_THAT(m2, IsMarkedWith(a_1_m, markA));
618   CHECK_THAT(m2, IsMarkedWith(a_1_m, markA));
619   CHECK_THAT(m2, IsMarkedWith(a_1_m, markA));
620   CHECK_THAT(m2, IsMarkedWith(b_1_u, markB));
621   CHECK_THAT(m2, IsMarkedWith(c_3_m, markC));
622   CHECK_THAT(m2, IsMarkedWith(d_2_l, markD));
623   CHECK(m2.count() == 13);
624 
625 #if 0
626   ts::LocalBufferWriter<1024> w;
627   std::cout << "Basic map dump" << std::endl;
628   std::cout << w.print("{}", m2).view() << std::endl;
629   w.reset();
630   std::cout << "With tree detail" << std::endl;
631   std::cout << w.print("{::x}", m2).view() << std::endl;
632 #endif
633 };
634