Skip to content

Commit b17b1a0

Browse files
authored
Extend unit tests
1 parent a5a5cfd commit b17b1a0

File tree

7 files changed

+308
-9
lines changed

7 files changed

+308
-9
lines changed

test/ip_address_tests.cpp

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <cppcoro/net/ip_address.hpp>
77

88
#include "doctest/cppcoro_doctest.h"
9+
#include <string_view>
910

1011
TEST_SUITE_BEGIN("ip_address");
1112

@@ -31,15 +32,58 @@ TEST_CASE("to_string")
3132

3233
TEST_CASE("from_string")
3334
{
34-
CHECK(ip_address::from_string("") == std::nullopt);
35-
CHECK(ip_address::from_string("foo") == std::nullopt);
36-
CHECK(ip_address::from_string(" 192.168.0.1") == std::nullopt);
37-
CHECK(ip_address::from_string("192.168.0.1asdf") == std::nullopt);
38-
39-
CHECK(ip_address::from_string("192.168.0.1") == ipv4_address(192, 168, 0, 1));
40-
CHECK(ip_address::from_string("::192.168.0.1") == ipv6_address(0, 0, 0, 0, 0, 0, 0xc0a8, 0x1));
41-
CHECK(ip_address::from_string("aabb:ccdd:11:2233:102:304:506:708") ==
42-
ipv6_address{ 0xAABBCCDD00112233, 0x0102030405060708 });
35+
CHECK(ip_address::from_string("") == std::nullopt);
36+
CHECK(ip_address::from_string("foo") == std::nullopt);
37+
CHECK(ip_address::from_string(" 192.168.0.1") == std::nullopt);
38+
CHECK(ip_address::from_string("192.168.0.1asdf") == std::nullopt);
39+
40+
CHECK(ip_address::from_string("192.168.0.1") == ipv4_address(192, 168, 0, 1));
41+
CHECK(ip_address::from_string("::192.168.0.1") == ipv6_address(0, 0, 0, 0, 0, 0, 0xc0a8, 0x1));
42+
CHECK(ip_address::from_string("aabb:ccdd:11:2233:102:304:506:708") ==
43+
ipv6_address{ 0xAABBCCDD00112233, 0x0102030405060708 });
44+
}
45+
46+
TEST_CASE("round-trip and ordering")
47+
{
48+
// Round-trip: to_string(from_string(s)) yields canonical string, and parsing back preserves value
49+
auto check_round_trip = [](std::string_view s) {
50+
auto p = ip_address::from_string(s);
51+
REQUIRE(p.has_value());
52+
auto canon = p->to_string();
53+
auto p2 = ip_address::from_string(canon);
54+
REQUIRE(p2.has_value());
55+
CHECK(*p == *p2);
56+
// Canonical string should be stable
57+
CHECK(p2->to_string() == canon);
58+
};
59+
60+
// IPv4
61+
check_round_trip("0.0.0.0");
62+
check_round_trip("255.255.255.255");
63+
64+
// IPv6 (mixed case input should parse and normalise to lower-case hex without leading zeros)
65+
check_round_trip("::");
66+
check_round_trip("::1");
67+
check_round_trip("FFFF:0000:0000:0000:0000:0000:0000:0001");
68+
check_round_trip("::192.168.0.1");
69+
70+
// Ordering: IPv4 sorts less than IPv6
71+
ip_address v4 = ipv4_address{127, 0, 0, 1};
72+
ip_address v6 = ipv6_address{0, 0, 0, 0, 0, 0, 0, 1}; // ::1
73+
CHECK(v4 < v6);
74+
75+
// Ordering within same family
76+
CHECK(ipv4_address{1, 1, 1, 1} < ipv4_address{1, 1, 1, 2});
77+
CHECK(ipv6_address{0, 0, 0, 0, 0, 0, 0, 1} < ipv6_address{0, 0, 0, 0, 0, 0, 0, 2});
78+
79+
// Reject trailing/leading whitespace
80+
CHECK(ip_address::from_string("192.168.0.1 ") == std::nullopt);
81+
CHECK(ip_address::from_string("\t::1") == std::nullopt);
82+
83+
// Reject malformed IPv6
84+
CHECK(ip_address::from_string("::::") == std::nullopt);
85+
CHECK(ip_address::from_string("gggg::1") == std::nullopt);
86+
CHECK(ip_address::from_string("12345::1") == std::nullopt);
4387
}
4488

4589
TEST_SUITE_END();

test/ip_endpoint_tests.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,35 @@ TEST_CASE("from_string" * doctest::skip{ isMsvc15_5X86Optimised })
5252
443 });
5353
}
5454

55+
TEST_CASE("round-trip and ordering" * doctest::skip{ isMsvc15_5X86Optimised })
56+
{
57+
// Round-trip
58+
auto check_round_trip = [](const char* s) {
59+
auto p = ip_endpoint::from_string(s);
60+
REQUIRE(p.has_value());
61+
auto canon = p->to_string();
62+
auto p2 = ip_endpoint::from_string(canon);
63+
REQUIRE(p2.has_value());
64+
CHECK(*p == *p2);
65+
CHECK(p2->to_string() == canon);
66+
};
67+
68+
check_round_trip("192.168.2.254:80");
69+
check_round_trip("[2001:db8:85a3::8a2e:370:7334]:22");
70+
71+
// Invalid ports/whitespace
72+
CHECK(ip_endpoint::from_string("192.168.1.1:65536") == std::nullopt);
73+
CHECK(ip_endpoint::from_string("[::1]:65536") == std::nullopt);
74+
CHECK(ip_endpoint::from_string(" 192.168.1.1:80") == std::nullopt);
75+
CHECK(ip_endpoint::from_string("[::1]:80 ") == std::nullopt);
76+
77+
// Ordering: IPv4 < IPv6, and within family compare address/port
78+
ip_endpoint v4a = ipv4_endpoint{ ipv4_address{127,0,0,1}, 1 };
79+
ip_endpoint v4b = ipv4_endpoint{ ipv4_address{127,0,0,1}, 2 };
80+
ip_endpoint v6 = ipv6_endpoint{ ipv6_address{0,0,0,0,0,0,0,1}, 1 };
81+
CHECK(v4a < v6);
82+
CHECK(v4a < v4b);
83+
}
84+
5585
TEST_SUITE_END();
5686

test/ipv4_address_tests.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,31 @@ TEST_CASE("from_string")
8787
CHECK(ipv4_address::from_string("0.0.0.0") == ipv4_address(0, 0, 0, 0));
8888
CHECK(ipv4_address::from_string("1.2.3.4") == ipv4_address(1, 2, 3, 4));
8989
}
90+
91+
TEST_CASE("round-trip and ordering")
92+
{
93+
// Round-trip canonicalisation via to_string()/from_string()
94+
auto check_round_trip = [](const char* s) {
95+
auto p = ipv4_address::from_string(s);
96+
REQUIRE(p.has_value());
97+
auto canon = p->to_string();
98+
auto p2 = ipv4_address::from_string(canon);
99+
REQUIRE(p2.has_value());
100+
CHECK(*p == *p2);
101+
CHECK(p2->to_string() == canon); // stable
102+
};
103+
104+
check_round_trip("0.0.0.0");
105+
check_round_trip("255.255.255.255");
106+
check_round_trip("192.168.0.1");
107+
108+
// Single-integer form canonicals to dotted-decimal
109+
auto p = ipv4_address::from_string("1");
110+
REQUIRE(p.has_value());
111+
CHECK(p->to_string() == std::string("0.0.0.1"));
112+
113+
// Ordering within IPv4 space
114+
CHECK(ipv4_address{1,2,3,4} < ipv4_address{1,2,3,5});
115+
CHECK(ipv4_address{10,0,0,1} < ipv4_address{10,0,0,2});
116+
}
90117
TEST_SUITE_END();

test/ipv4_endpoint_tests.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,32 @@ TEST_CASE("from_string")
3030
ipv4_endpoint{ ipv4_address{ 192, 168, 2, 254 }, 80 });
3131
}
3232

33+
TEST_CASE("round-trip, boundary ports and invalid inputs")
34+
{
35+
// Round-trip canonicalisation
36+
auto check_round_trip = [](const char* s) {
37+
auto p = ipv4_endpoint::from_string(s);
38+
REQUIRE(p.has_value());
39+
auto canon = p->to_string();
40+
auto p2 = ipv4_endpoint::from_string(canon);
41+
REQUIRE(p2.has_value());
42+
CHECK(*p == *p2);
43+
CHECK(p2->to_string() == canon);
44+
};
45+
46+
check_round_trip("192.168.2.254:80");
47+
check_round_trip("0.0.0.0:0");
48+
check_round_trip("255.255.255.255:65535");
49+
50+
// Boundary ports
51+
CHECK(ipv4_endpoint{ ipv4_address{0,0,0,0}, 0 }.to_string() == std::string("0.0.0.0:0"));
52+
CHECK(ipv4_endpoint{ ipv4_address{255,255,255,255}, 65535 }.to_string() == std::string("255.255.255.255:65535"));
53+
54+
// Invalid: whitespace and out-of-range ports
55+
CHECK(ipv4_endpoint::from_string(" 192.168.0.1:80") == std::nullopt);
56+
CHECK(ipv4_endpoint::from_string("192.168.0.1:80 ") == std::nullopt);
57+
CHECK(ipv4_endpoint::from_string("192.168.0.1:65536") == std::nullopt);
58+
CHECK(ipv4_endpoint::from_string("192.168.0.1:-1") == std::nullopt);
59+
}
60+
3361
TEST_SUITE_END();

test/ipv6_address_tests.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,35 @@ TEST_CASE("from_string")
109109
ipv6_address(0x20010db885a308d3, 0x13198a2e03707348));
110110
}
111111

112+
TEST_CASE("round-trip and normalization")
113+
{
114+
auto check_round_trip = [](const char* s) {
115+
auto p = ipv6_address::from_string(s);
116+
REQUIRE(p.has_value());
117+
auto canon = p->to_string();
118+
auto p2 = ipv6_address::from_string(canon);
119+
REQUIRE(p2.has_value());
120+
CHECK(*p == *p2);
121+
CHECK(p2->to_string() == canon);
122+
};
123+
124+
// Basic
125+
check_round_trip("::");
126+
check_round_trip("::1");
127+
check_round_trip("FFFF:0000:0000:0000:0000:0000:0000:0001");
128+
129+
// Mixed case input should normalise to lower-case
130+
check_round_trip("AbCd:Ef01::");
131+
132+
// IPv4 interop
133+
check_round_trip("::ffff:192.168.0.1");
134+
135+
// Ordering stability already covered elsewhere; here just ensure canonical contraction is stable
136+
auto p = ipv6_address::from_string("0001:0010:0100:1000::");
137+
REQUIRE(p.has_value());
138+
CHECK(p->to_string() == std::string("1:10:100:1000::"));
139+
}
140+
112141
TEST_CASE("from_string IPv4 interop format")
113142
{
114143
CHECK(

test/ipv6_endpoint_tests.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,30 @@ TEST_CASE("from_string" * doctest::skip{ isMsvc15_5X86Optimised })
5252
ipv6_endpoint{ ipv6_address{ 0x20010db885a30000, 0x00008a2e03707334 }, 65535 });
5353
}
5454

55+
TEST_CASE("round-trip and invalid ports/brackets" * doctest::skip{ isMsvc15_5X86Optimised })
56+
{
57+
// Round-trip canonicalisation
58+
auto check_round_trip = [](const char* s) {
59+
auto p = ipv6_endpoint::from_string(s);
60+
REQUIRE(p.has_value());
61+
auto canon = p->to_string();
62+
auto p2 = ipv6_endpoint::from_string(canon);
63+
REQUIRE(p2.has_value());
64+
CHECK(*p == *p2);
65+
CHECK(p2->to_string() == canon);
66+
};
67+
68+
check_round_trip("[::1]:1");
69+
check_round_trip("[2001:db8:85a3::8a2e:370:7334]:22");
70+
71+
// Invalid port ranges and whitespace
72+
CHECK(ipv6_endpoint::from_string("[::1]:65536") == std::nullopt);
73+
CHECK(ipv6_endpoint::from_string(" [::1]:80") == std::nullopt);
74+
CHECK(ipv6_endpoint::from_string("[::1]:80 ") == std::nullopt);
75+
76+
// Malformed brackets
77+
CHECK(ipv6_endpoint::from_string("[[::1]]:80") == std::nullopt);
78+
CHECK(ipv6_endpoint::from_string("[::1:80") == std::nullopt);
79+
}
80+
5581
TEST_SUITE_END();

test/task_tests.cpp

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@
88
#include <cppcoro/sync_wait.hpp>
99
#include <cppcoro/when_all_ready.hpp>
1010
#include <cppcoro/fmap.hpp>
11+
#include <cppcoro/cancellation_token.hpp>
12+
#include <cppcoro/cancellation_source.hpp>
13+
#include <cppcoro/operation_cancelled.hpp>
1114

1215
#include "counted.hpp"
1316

1417
#include <ostream>
1518
#include <string>
1619
#include <type_traits>
20+
#include <memory>
21+
#include <stdexcept>
1722

1823
#include "doctest/cppcoro_doctest.h"
1924

@@ -346,4 +351,114 @@ TEST_CASE("lots of synchronous completions doesn't result in stack-overflow")
346351
cppcoro::sync_wait(run());
347352
}
348353

354+
TEST_CASE("exception propagation from task")
355+
{
356+
using cppcoro::task;
357+
358+
auto throws_now = []() -> task<int>
359+
{
360+
throw std::runtime_error{"boom"};
361+
co_return 0;
362+
};
363+
364+
// sync_wait should propagate the exception
365+
CHECK_THROWS_AS(cppcoro::sync_wait(throws_now()), const std::runtime_error&);
366+
367+
// co_await inside another task should also propagate
368+
cppcoro::sync_wait([&]() -> task<>
369+
{
370+
CHECK_THROWS_AS(co_await throws_now(), const std::runtime_error&);
371+
co_return;
372+
}());
373+
}
374+
375+
TEST_CASE("cancellation observed by task")
376+
{
377+
using cppcoro::task;
378+
using cppcoro::cancellation_source;
379+
using cppcoro::operation_cancelled;
380+
381+
// Not cancelled path does not throw
382+
{
383+
cancellation_source s;
384+
auto t = [tok = s.token()]() -> task<>
385+
{
386+
// Should not throw if not cancelled
387+
tok.throw_if_cancellation_requested();
388+
co_return;
389+
};
390+
CHECK_NOTHROW(cppcoro::sync_wait(t()));
391+
}
392+
393+
// Cancellation requested before start throws
394+
{
395+
cancellation_source s;
396+
auto t = [tok = s.token()]() -> task<>
397+
{
398+
tok.throw_if_cancellation_requested();
399+
co_return;
400+
};
401+
s.request_cancellation();
402+
CHECK_THROWS_AS(cppcoro::sync_wait(t()), const operation_cancelled&);
403+
}
404+
}
405+
406+
TEST_CASE("move-only result type")
407+
{
408+
using cppcoro::task;
409+
410+
auto make_ptr = []() -> task<std::unique_ptr<int>>
411+
{
412+
co_return std::make_unique<int>(42);
413+
};
414+
415+
// sync_wait returns a move-only result
416+
auto p = cppcoro::sync_wait(make_ptr());
417+
REQUIRE(p);
418+
CHECK(*p == 42);
419+
420+
// co_await should also move out the value
421+
cppcoro::sync_wait([&]() -> task<>
422+
{
423+
auto q = co_await make_ptr();
424+
REQUIRE(q);
425+
CHECK(*q == 42);
426+
co_return;
427+
}());
428+
}
429+
430+
TEST_CASE("task can be moved and awaited")
431+
{
432+
using cppcoro::task;
433+
using cppcoro::single_consumer_event;
434+
435+
single_consumer_event evt;
436+
437+
auto make = [&]() -> task<int>
438+
{
439+
co_await evt;
440+
co_return 7;
441+
};
442+
443+
task<int> t = make();
444+
445+
// Move-construct and await moved-to task
446+
task<int> u = std::move(t);
447+
448+
int result = 0;
449+
cppcoro::sync_wait(cppcoro::when_all_ready(
450+
[&]() -> task<>
451+
{
452+
result = co_await u;
453+
co_return;
454+
}(),
455+
[&]() -> task<>
456+
{
457+
evt.set();
458+
co_return;
459+
}()));
460+
461+
CHECK(result == 7);
462+
}
463+
349464
TEST_SUITE_END();

0 commit comments

Comments
 (0)