Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 53 additions & 9 deletions test/ip_address_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <cppcoro/net/ip_address.hpp>

#include "doctest/cppcoro_doctest.h"
#include <string_view>

TEST_SUITE_BEGIN("ip_address");

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

TEST_CASE("from_string")
{
CHECK(ip_address::from_string("") == std::nullopt);
CHECK(ip_address::from_string("foo") == std::nullopt);
CHECK(ip_address::from_string(" 192.168.0.1") == std::nullopt);
CHECK(ip_address::from_string("192.168.0.1asdf") == std::nullopt);

CHECK(ip_address::from_string("192.168.0.1") == ipv4_address(192, 168, 0, 1));
CHECK(ip_address::from_string("::192.168.0.1") == ipv6_address(0, 0, 0, 0, 0, 0, 0xc0a8, 0x1));
CHECK(ip_address::from_string("aabb:ccdd:11:2233:102:304:506:708") ==
ipv6_address{ 0xAABBCCDD00112233, 0x0102030405060708 });
CHECK(ip_address::from_string("") == std::nullopt);
CHECK(ip_address::from_string("foo") == std::nullopt);
CHECK(ip_address::from_string(" 192.168.0.1") == std::nullopt);
CHECK(ip_address::from_string("192.168.0.1asdf") == std::nullopt);

CHECK(ip_address::from_string("192.168.0.1") == ipv4_address(192, 168, 0, 1));
CHECK(ip_address::from_string("::192.168.0.1") == ipv6_address(0, 0, 0, 0, 0, 0, 0xc0a8, 0x1));
CHECK(ip_address::from_string("aabb:ccdd:11:2233:102:304:506:708") ==
ipv6_address{ 0xAABBCCDD00112233, 0x0102030405060708 });
}

TEST_CASE("round-trip and ordering")
{
// Round-trip: to_string(from_string(s)) yields canonical string, and parsing back preserves value
auto check_round_trip = [](std::string_view s) {
auto p = ip_address::from_string(s);
REQUIRE(p.has_value());
auto canon = p->to_string();
auto p2 = ip_address::from_string(canon);
REQUIRE(p2.has_value());
CHECK(*p == *p2);
// Canonical string should be stable
CHECK(p2->to_string() == canon);
};

// IPv4
check_round_trip("0.0.0.0");
check_round_trip("255.255.255.255");

// IPv6 (mixed case input should parse and normalise to lower-case hex without leading zeros)
check_round_trip("::");
check_round_trip("::1");
check_round_trip("FFFF:0000:0000:0000:0000:0000:0000:0001");
check_round_trip("::192.168.0.1");

// Ordering: IPv4 sorts less than IPv6
ip_address v4 = ipv4_address{127, 0, 0, 1};
ip_address v6 = ipv6_address{0, 0, 0, 0, 0, 0, 0, 1}; // ::1
CHECK(v4 < v6);

// Ordering within same family
CHECK(ipv4_address{1, 1, 1, 1} < ipv4_address{1, 1, 1, 2});
CHECK(ipv6_address{0, 0, 0, 0, 0, 0, 0, 1} < ipv6_address{0, 0, 0, 0, 0, 0, 0, 2});

// Reject trailing/leading whitespace
CHECK(ip_address::from_string("192.168.0.1 ") == std::nullopt);
CHECK(ip_address::from_string("\t::1") == std::nullopt);

// Reject malformed IPv6
CHECK(ip_address::from_string("::::") == std::nullopt);
CHECK(ip_address::from_string("gggg::1") == std::nullopt);
CHECK(ip_address::from_string("12345::1") == std::nullopt);
}

TEST_SUITE_END();
30 changes: 30 additions & 0 deletions test/ip_endpoint_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,35 @@ TEST_CASE("from_string" * doctest::skip{ isMsvc15_5X86Optimised })
443 });
}

TEST_CASE("round-trip and ordering" * doctest::skip{ isMsvc15_5X86Optimised })
{
// Round-trip
auto check_round_trip = [](const char* s) {
auto p = ip_endpoint::from_string(s);
REQUIRE(p.has_value());
auto canon = p->to_string();
auto p2 = ip_endpoint::from_string(canon);
REQUIRE(p2.has_value());
CHECK(*p == *p2);
CHECK(p2->to_string() == canon);
};

check_round_trip("192.168.2.254:80");
check_round_trip("[2001:db8:85a3::8a2e:370:7334]:22");

// Invalid ports/whitespace
CHECK(ip_endpoint::from_string("192.168.1.1:65536") == std::nullopt);
CHECK(ip_endpoint::from_string("[::1]:65536") == std::nullopt);
CHECK(ip_endpoint::from_string(" 192.168.1.1:80") == std::nullopt);
CHECK(ip_endpoint::from_string("[::1]:80 ") == std::nullopt);

// Ordering: IPv4 < IPv6, and within family compare address/port
ip_endpoint v4a = ipv4_endpoint{ ipv4_address{127,0,0,1}, 1 };
ip_endpoint v4b = ipv4_endpoint{ ipv4_address{127,0,0,1}, 2 };
ip_endpoint v6 = ipv6_endpoint{ ipv6_address{0,0,0,0,0,0,0,1}, 1 };
CHECK(v4a < v6);
CHECK(v4a < v4b);
}

TEST_SUITE_END();

27 changes: 27 additions & 0 deletions test/ipv4_address_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,31 @@ TEST_CASE("from_string")
CHECK(ipv4_address::from_string("0.0.0.0") == ipv4_address(0, 0, 0, 0));
CHECK(ipv4_address::from_string("1.2.3.4") == ipv4_address(1, 2, 3, 4));
}

TEST_CASE("round-trip and ordering")
{
// Round-trip canonicalisation via to_string()/from_string()
auto check_round_trip = [](const char* s) {
auto p = ipv4_address::from_string(s);
REQUIRE(p.has_value());
auto canon = p->to_string();
auto p2 = ipv4_address::from_string(canon);
REQUIRE(p2.has_value());
CHECK(*p == *p2);
CHECK(p2->to_string() == canon); // stable
};

check_round_trip("0.0.0.0");
check_round_trip("255.255.255.255");
check_round_trip("192.168.0.1");

// Single-integer form canonicals to dotted-decimal
auto p = ipv4_address::from_string("1");
REQUIRE(p.has_value());
CHECK(p->to_string() == std::string("0.0.0.1"));

// Ordering within IPv4 space
CHECK(ipv4_address{1,2,3,4} < ipv4_address{1,2,3,5});
CHECK(ipv4_address{10,0,0,1} < ipv4_address{10,0,0,2});
}
TEST_SUITE_END();
28 changes: 28 additions & 0 deletions test/ipv4_endpoint_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,32 @@ TEST_CASE("from_string")
ipv4_endpoint{ ipv4_address{ 192, 168, 2, 254 }, 80 });
}

TEST_CASE("round-trip, boundary ports and invalid inputs")
{
// Round-trip canonicalisation
auto check_round_trip = [](const char* s) {
auto p = ipv4_endpoint::from_string(s);
REQUIRE(p.has_value());
auto canon = p->to_string();
auto p2 = ipv4_endpoint::from_string(canon);
REQUIRE(p2.has_value());
CHECK(*p == *p2);
CHECK(p2->to_string() == canon);
};

check_round_trip("192.168.2.254:80");
check_round_trip("0.0.0.0:0");
check_round_trip("255.255.255.255:65535");

// Boundary ports
CHECK(ipv4_endpoint{ ipv4_address{0,0,0,0}, 0 }.to_string() == std::string("0.0.0.0:0"));
CHECK(ipv4_endpoint{ ipv4_address{255,255,255,255}, 65535 }.to_string() == std::string("255.255.255.255:65535"));

// Invalid: whitespace and out-of-range ports
CHECK(ipv4_endpoint::from_string(" 192.168.0.1:80") == std::nullopt);
CHECK(ipv4_endpoint::from_string("192.168.0.1:80 ") == std::nullopt);
CHECK(ipv4_endpoint::from_string("192.168.0.1:65536") == std::nullopt);
CHECK(ipv4_endpoint::from_string("192.168.0.1:-1") == std::nullopt);
}

TEST_SUITE_END();
29 changes: 29 additions & 0 deletions test/ipv6_address_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,35 @@ TEST_CASE("from_string")
ipv6_address(0x20010db885a308d3, 0x13198a2e03707348));
}

TEST_CASE("round-trip and normalization")
{
auto check_round_trip = [](const char* s) {
auto p = ipv6_address::from_string(s);
REQUIRE(p.has_value());
auto canon = p->to_string();
auto p2 = ipv6_address::from_string(canon);
REQUIRE(p2.has_value());
CHECK(*p == *p2);
CHECK(p2->to_string() == canon);
};

// Basic
check_round_trip("::");
check_round_trip("::1");
check_round_trip("FFFF:0000:0000:0000:0000:0000:0000:0001");

// Mixed case input should normalise to lower-case
check_round_trip("AbCd:Ef01::");

// IPv4 interop
check_round_trip("::ffff:192.168.0.1");

// Ordering stability already covered elsewhere; here just ensure canonical contraction is stable
auto p = ipv6_address::from_string("0001:0010:0100:1000::");
REQUIRE(p.has_value());
CHECK(p->to_string() == std::string("1:10:100:1000::"));
}

TEST_CASE("from_string IPv4 interop format")
{
CHECK(
Expand Down
26 changes: 26 additions & 0 deletions test/ipv6_endpoint_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,30 @@ TEST_CASE("from_string" * doctest::skip{ isMsvc15_5X86Optimised })
ipv6_endpoint{ ipv6_address{ 0x20010db885a30000, 0x00008a2e03707334 }, 65535 });
}

TEST_CASE("round-trip and invalid ports/brackets" * doctest::skip{ isMsvc15_5X86Optimised })
{
// Round-trip canonicalisation
auto check_round_trip = [](const char* s) {
auto p = ipv6_endpoint::from_string(s);
REQUIRE(p.has_value());
auto canon = p->to_string();
auto p2 = ipv6_endpoint::from_string(canon);
REQUIRE(p2.has_value());
CHECK(*p == *p2);
CHECK(p2->to_string() == canon);
};

check_round_trip("[::1]:1");
check_round_trip("[2001:db8:85a3::8a2e:370:7334]:22");

// Invalid port ranges and whitespace
CHECK(ipv6_endpoint::from_string("[::1]:65536") == std::nullopt);
CHECK(ipv6_endpoint::from_string(" [::1]:80") == std::nullopt);
CHECK(ipv6_endpoint::from_string("[::1]:80 ") == std::nullopt);

// Malformed brackets
CHECK(ipv6_endpoint::from_string("[[::1]]:80") == std::nullopt);
CHECK(ipv6_endpoint::from_string("[::1:80") == std::nullopt);
}

TEST_SUITE_END();
115 changes: 115 additions & 0 deletions test/task_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@
#include <cppcoro/sync_wait.hpp>
#include <cppcoro/when_all_ready.hpp>
#include <cppcoro/fmap.hpp>
#include <cppcoro/cancellation_token.hpp>
#include <cppcoro/cancellation_source.hpp>
#include <cppcoro/operation_cancelled.hpp>

#include "counted.hpp"

#include <ostream>
#include <string>
#include <type_traits>
#include <memory>
#include <stdexcept>

#include "doctest/cppcoro_doctest.h"

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

TEST_CASE("exception propagation from task")
{
using cppcoro::task;

auto throws_now = []() -> task<int>
{
throw std::runtime_error{"boom"};
co_return 0;
};

// sync_wait should propagate the exception
CHECK_THROWS_AS(cppcoro::sync_wait(throws_now()), const std::runtime_error&);

// co_await inside another task should also propagate
cppcoro::sync_wait([&]() -> task<>
{
CHECK_THROWS_AS(co_await throws_now(), const std::runtime_error&);
co_return;
}());
}

TEST_CASE("cancellation observed by task")
{
using cppcoro::task;
using cppcoro::cancellation_source;
using cppcoro::operation_cancelled;

// Not cancelled path does not throw
{
cancellation_source s;
auto t = [tok = s.token()]() -> task<>
{
// Should not throw if not cancelled
tok.throw_if_cancellation_requested();
co_return;
};
CHECK_NOTHROW(cppcoro::sync_wait(t()));
}

// Cancellation requested before start throws
{
cancellation_source s;
auto t = [tok = s.token()]() -> task<>
{
tok.throw_if_cancellation_requested();
co_return;
};
s.request_cancellation();
CHECK_THROWS_AS(cppcoro::sync_wait(t()), const operation_cancelled&);
}
}

TEST_CASE("move-only result type")
{
using cppcoro::task;

auto make_ptr = []() -> task<std::unique_ptr<int>>
{
co_return std::make_unique<int>(42);
};

// sync_wait returns a move-only result
auto p = cppcoro::sync_wait(make_ptr());
REQUIRE(p);
CHECK(*p == 42);

// co_await should also move out the value
cppcoro::sync_wait([&]() -> task<>
{
auto q = co_await make_ptr();
REQUIRE(q);
CHECK(*q == 42);
co_return;
}());
}

TEST_CASE("task can be moved and awaited")
{
using cppcoro::task;
using cppcoro::single_consumer_event;

single_consumer_event evt;

auto make = [&]() -> task<int>
{
co_await evt;
co_return 7;
};

task<int> t = make();

// Move-construct and await moved-to task
task<int> u = std::move(t);

int result = 0;
cppcoro::sync_wait(cppcoro::when_all_ready(
[&]() -> task<>
{
result = co_await u;
co_return;
}(),
[&]() -> task<>
{
evt.set();
co_return;
}()));

CHECK(result == 7);
}

TEST_SUITE_END();