#include #include namespace { warppipe::ConnectionOptions DefaultOptions() { warppipe::ConnectionOptions options; options.threading = warppipe::ThreadingMode::kThreadLoop; options.autoconnect = true; options.application_name = "warppipe-tests"; return options; } bool ContainsNode(const std::vector& nodes, uint32_t id) { for (const auto& node : nodes) { if (node.id.value == id) { return true; } } return false; } bool ContainsLink(const std::vector& links, uint32_t id) { for (const auto& link : links) { if (link.id.value == id) { return true; } } return false; } } // namespace TEST_CASE("caller thread mode is not implemented") { warppipe::ConnectionOptions options = DefaultOptions(); options.threading = warppipe::ThreadingMode::kCallerThread; auto result = warppipe::Client::Create(options); REQUIRE_FALSE(result.ok()); REQUIRE(result.status.code == warppipe::StatusCode::kNotImplemented); } TEST_CASE("connects or reports unavailable") { warppipe::ConnectionOptions connection_options = DefaultOptions(); auto result = warppipe::Client::Create(connection_options); if (!result.ok()) { REQUIRE(result.status.code == warppipe::StatusCode::kUnavailable); return; } auto nodes = result.value->ListNodes(); REQUIRE(nodes.ok()); } TEST_CASE("invalid remote name fails") { warppipe::ConnectionOptions options = DefaultOptions(); options.remote_name = "warppipe-test-missing-remote"; auto result = warppipe::Client::Create(options); REQUIRE_FALSE(result.ok()); REQUIRE(result.status.code == warppipe::StatusCode::kUnavailable); } TEST_CASE("create and remove virtual sink/source when available") { warppipe::ConnectionOptions options = DefaultOptions(); auto result = warppipe::Client::Create(options); if (!result.ok()) { SUCCEED("PipeWire unavailable"); return; } warppipe::VirtualNodeOptions node_options; node_options.display_name = "warppipe-test-virtual"; node_options.group = "warppipe-test"; node_options.format.rate = 44100; node_options.format.channels = 2; auto sink = result.value->CreateVirtualSink("warppipe-test-sink", node_options); if (!sink.ok()) { if (sink.status.code == warppipe::StatusCode::kUnavailable) { SUCCEED("PipeWire unavailable"); return; } REQUIRE(sink.ok()); } auto source = result.value->CreateVirtualSource("warppipe-test-source", node_options); if (!source.ok()) { if (source.status.code == warppipe::StatusCode::kUnavailable) { SUCCEED("PipeWire unavailable"); return; } REQUIRE(source.ok()); } REQUIRE(result.value->RemoveNode(sink.value.node).ok()); REQUIRE(result.value->RemoveNode(source.value.node).ok()); } TEST_CASE("missing media class returns invalid argument") { auto result = warppipe::Client::Create(DefaultOptions()); if (!result.ok()) { SUCCEED("PipeWire unavailable"); return; } warppipe::VirtualNodeOptions options; options.media_class_override = ""; auto sink = result.value->CreateVirtualSink("warppipe-test-missing-class", options); REQUIRE_FALSE(sink.ok()); REQUIRE(sink.status.code == warppipe::StatusCode::kInvalidArgument); } TEST_CASE("duplicate node name returns invalid argument") { auto result = warppipe::Client::Create(DefaultOptions()); if (!result.ok()) { SUCCEED("PipeWire unavailable"); return; } auto first = result.value->CreateVirtualSink("warppipe-dup", warppipe::VirtualNodeOptions{}); if (!first.ok()) { if (first.status.code == warppipe::StatusCode::kUnavailable) { SUCCEED("PipeWire unavailable"); return; } REQUIRE(first.ok()); } auto second = result.value->CreateVirtualSink("warppipe-dup", warppipe::VirtualNodeOptions{}); REQUIRE_FALSE(second.ok()); REQUIRE(second.status.code == warppipe::StatusCode::kInvalidArgument); REQUIRE(result.value->RemoveNode(first.value.node).ok()); } TEST_CASE("loopback target missing returns not found") { auto result = warppipe::Client::Create(DefaultOptions()); if (!result.ok()) { SUCCEED("PipeWire unavailable"); return; } warppipe::VirtualNodeOptions options; options.behavior = warppipe::VirtualBehavior::kLoopback; options.target_node = "warppipe-missing-target"; auto sink = result.value->CreateVirtualSink("warppipe-loopback", options); REQUIRE_FALSE(sink.ok()); REQUIRE(sink.status.code == warppipe::StatusCode::kNotFound); } TEST_CASE("registry removal cleans up ports and links") { auto result = warppipe::Client::Create(DefaultOptions()); if (!result.ok()) { SUCCEED("PipeWire unavailable"); return; } const uint32_t node_id = 500001; const uint32_t out_port_id = 500002; const uint32_t in_port_id = 500003; const uint32_t link_id = 500004; warppipe::NodeInfo node; node.id = warppipe::NodeId{node_id}; node.name = "warppipe-test-node"; node.media_class = "Audio/Sink"; REQUIRE(result.value->Test_InsertNode(node).ok()); warppipe::PortInfo out_port; out_port.id = warppipe::PortId{out_port_id}; out_port.node = warppipe::NodeId{node_id}; out_port.name = "output"; out_port.is_input = false; REQUIRE(result.value->Test_InsertPort(out_port).ok()); warppipe::PortInfo in_port; in_port.id = warppipe::PortId{in_port_id}; in_port.node = warppipe::NodeId{node_id}; in_port.name = "input"; in_port.is_input = true; REQUIRE(result.value->Test_InsertPort(in_port).ok()); warppipe::Link link; link.id = warppipe::LinkId{link_id}; link.output_port = warppipe::PortId{out_port_id}; link.input_port = warppipe::PortId{in_port_id}; REQUIRE(result.value->Test_InsertLink(link).ok()); auto snapshot = result.value->ListNodes(); REQUIRE(snapshot.ok()); REQUIRE(ContainsNode(snapshot.value, node_id)); REQUIRE(result.value->Test_RemoveGlobal(node_id).ok()); auto ports = result.value->ListPorts(warppipe::NodeId{node_id}); REQUIRE(ports.ok()); REQUIRE(ports.value.empty()); auto links = result.value->ListLinks(); REQUIRE(links.ok()); REQUIRE_FALSE(ContainsLink(links.value, link_id)); } TEST_CASE("autoconnect reconnects after forced disconnect") { auto result = warppipe::Client::Create(DefaultOptions()); if (!result.ok()) { SUCCEED("PipeWire unavailable"); return; } auto nodes = result.value->ListNodes(); REQUIRE(nodes.ok()); REQUIRE(result.value->Test_ForceDisconnect().ok()); auto nodes_after = result.value->ListNodes(); REQUIRE(nodes_after.ok()); }