Files
hakurei/internal/pipewire/pipewire_test.go
Ophestra ce06b7b663
All checks were successful
Test / Create distribution (push) Successful in 30s
Test / Sandbox (push) Successful in 2m31s
Test / Hakurei (push) Successful in 3m29s
Test / Hpkg (push) Successful in 4m24s
Test / Sandbox (race detector) (push) Successful in 4m30s
Test / Hakurei (race detector) (push) Successful in 5m27s
Test / Flake checks (push) Successful in 1m40s
internal/pipewire: inform conn of blocking intent
The interface does not expose underlying kernel notification mechanisms. This change removes the need to poll in situations were the next call might block.

This is made cumbersome by the SyscallConn interface left over from a previous implementation, it will be replaced in a later commit as the current implementation does not make use of any net.Conn methods other than Close.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-12-19 00:00:33 +09:00

900 lines
33 KiB
Go

package pipewire_test
import (
"fmt"
"reflect"
"strconv"
. "syscall"
"testing"
"time"
"hakurei.app/container/stub"
"hakurei.app/internal/pipewire"
)
func TestContext(t *testing.T) {
t.Parallel()
var (
// Underlying connection stub holding test data.
conn = stubUnixConn{samples: []stubUnixConnSample{
{SYS_SENDMSG, samplePWContainer00, MSG_DONTWAIT | MSG_NOSIGNAL, nil, 0},
{SYS_RECVMSG, samplePWContainer01, MSG_DONTWAIT | MSG_CMSG_CLOEXEC, nil, 0},
{SYS_RECVMSG, "", MSG_DONTWAIT | MSG_CMSG_CLOEXEC, nil, EAGAIN},
{SYS_SENDMSG, samplePWContainer03, MSG_DONTWAIT | MSG_NOSIGNAL, nil, 0},
{SYS_RECVMSG, samplePWContainer04, MSG_DONTWAIT | MSG_CMSG_CLOEXEC, nil, 0},
{SYS_RECVMSG, "", MSG_DONTWAIT | MSG_CMSG_CLOEXEC, nil, EAGAIN},
{SYS_SENDMSG, samplePWContainer06, MSG_DONTWAIT | MSG_NOSIGNAL, []int{20, 21}, 0},
{SYS_RECVMSG, samplePWContainer07, MSG_DONTWAIT | MSG_CMSG_CLOEXEC, nil, 0},
{SYS_RECVMSG, "", MSG_DONTWAIT | MSG_CMSG_CLOEXEC, nil, EAGAIN},
}}
// Context instance under testing.
ctx = pipewire.MustNew(&conn, pipewire.SPADict{
{Key: pipewire.PW_KEY_REMOTE_INTENTION, Value: "manager"},
{Key: pipewire.PW_KEY_APP_NAME, Value: "pw-container"},
{Key: pipewire.PW_KEY_APP_PROCESS_BINARY, Value: "pw-container"},
{Key: pipewire.PW_KEY_APP_LANGUAGE, Value: "en_US.UTF-8"},
{Key: pipewire.PW_KEY_APP_PROCESS_ID, Value: "1443"},
{Key: pipewire.PW_KEY_APP_PROCESS_USER, Value: "alice"},
{Key: pipewire.PW_KEY_APP_PROCESS_HOST, Value: "nixos"},
{Key: pipewire.PW_KEY_APP_PROCESS_SESSION_ID, Value: "1"},
{Key: pipewire.PW_KEY_WINDOW_X11_DISPLAY, Value: ":0"},
{Key: "cpu.vm.name", Value: "qemu"},
{Key: "log.level", Value: "0"},
{Key: pipewire.PW_KEY_CPU_MAX_ALIGN, Value: "32"},
{Key: "default.clock.rate", Value: "48000"},
{Key: "default.clock.quantum", Value: "1024"},
{Key: "default.clock.min-quantum", Value: "32"},
{Key: "default.clock.max-quantum", Value: "2048"},
{Key: "default.clock.quantum-limit", Value: "8192"},
{Key: "default.clock.quantum-floor", Value: "4"},
{Key: "default.video.width", Value: "640"},
{Key: "default.video.height", Value: "480"},
{Key: "default.video.rate.num", Value: "25"},
{Key: "default.video.rate.denom", Value: "1"},
{Key: "clock.power-of-two-quantum", Value: "true"},
{Key: "link.max-buffers", Value: "64"},
{Key: "mem.warn-mlock", Value: "false"},
{Key: "mem.allow-mlock", Value: "true"},
{Key: "settings.check-quantum", Value: "false"},
{Key: "settings.check-rate", Value: "false"},
{Key: pipewire.PW_KEY_CORE_VERSION, Value: "1.4.7"},
{Key: pipewire.PW_KEY_CORE_NAME, Value: "pipewire-alice-1443"},
})
)
var registry *pipewire.Registry
const wantRegistryId = 2
if r, err := ctx.GetRegistry(); err != nil {
t.Fatalf("GetRegistry: error = %v", err)
} else {
if r.ID != wantRegistryId {
t.Fatalf("GetRegistry: ID = %d, want %d", r.ID, wantRegistryId)
}
registry = r
}
if err := ctx.GetCore().Sync(); err != nil {
t.Fatalf("Sync: error = %v", err)
}
wantCoreInfo0 := pipewire.CoreInfo{
ID: pipewire.PW_ID_CORE,
Cookie: -2069267610,
UserName: "alice",
HostName: "nixos",
Version: "1.4.7",
Name: "pipewire-0",
ChangeMask: pipewire.PW_CORE_CHANGE_MASK_PROPS,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_CONFIG_NAME, Value: "pipewire.conf"},
{Key: pipewire.PW_KEY_APP_NAME, Value: "pipewire"},
{Key: pipewire.PW_KEY_APP_PROCESS_BINARY, Value: "pipewire"},
{Key: pipewire.PW_KEY_APP_LANGUAGE, Value: "en_US.UTF-8"},
{Key: pipewire.PW_KEY_APP_PROCESS_ID, Value: "1446"},
{Key: pipewire.PW_KEY_APP_PROCESS_USER, Value: "alice"},
{Key: pipewire.PW_KEY_APP_PROCESS_HOST, Value: "nixos"},
{Key: pipewire.PW_KEY_WINDOW_X11_DISPLAY, Value: ":0"},
{Key: "cpu.vm.name", Value: "qemu"},
{Key: "link.max-buffers", Value: "16"},
{Key: pipewire.PW_KEY_CORE_DAEMON, Value: "true"},
{Key: pipewire.PW_KEY_CORE_NAME, Value: "pipewire-0"},
{Key: "default.clock.min-quantum", Value: "1024"},
{Key: pipewire.PW_KEY_CPU_MAX_ALIGN, Value: "32"},
{Key: "default.clock.rate", Value: "48000"},
{Key: "default.clock.quantum", Value: "1024"},
{Key: "default.clock.max-quantum", Value: "2048"},
{Key: "default.clock.quantum-limit", Value: "8192"},
{Key: "default.clock.quantum-floor", Value: "4"},
{Key: "default.video.width", Value: "640"},
{Key: "default.video.height", Value: "480"},
{Key: "default.video.rate.num", Value: "25"},
{Key: "default.video.rate.denom", Value: "1"},
{Key: "log.level", Value: "2"},
{Key: "clock.power-of-two-quantum", Value: "true"},
{Key: "mem.warn-mlock", Value: "false"},
{Key: "mem.allow-mlock", Value: "true"},
{Key: "settings.check-quantum", Value: "false"},
{Key: "settings.check-rate", Value: "false"},
{Key: pipewire.PW_KEY_OBJECT_ID, Value: "0"},
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "0"},
},
}
wantClient0 := pipewire.Client{
Info: &pipewire.ClientInfo{
ID: 34,
ChangeMask: pipewire.PW_CLIENT_CHANGE_MASK_PROPS,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_PROTOCOL, Value: "protocol-native"},
{Key: pipewire.PW_KEY_CORE_NAME, Value: "pipewire-alice-1443"},
{Key: pipewire.PW_KEY_SEC_SOCKET, Value: "pipewire-0-manager"},
{Key: pipewire.PW_KEY_SEC_PID, Value: "1443"},
{Key: pipewire.PW_KEY_SEC_UID, Value: "1000"},
{Key: pipewire.PW_KEY_SEC_GID, Value: "100"},
{Key: pipewire.PW_KEY_MODULE_ID, Value: "2"},
{Key: pipewire.PW_KEY_OBJECT_ID, Value: "34"},
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "34"},
{Key: pipewire.PW_KEY_REMOTE_INTENTION, Value: "manager"},
{Key: pipewire.PW_KEY_APP_NAME, Value: "pw-container"},
{Key: pipewire.PW_KEY_APP_PROCESS_BINARY, Value: "pw-container"},
{Key: pipewire.PW_KEY_APP_LANGUAGE, Value: "en_US.UTF-8"},
{Key: pipewire.PW_KEY_APP_PROCESS_ID, Value: "1443"},
{Key: pipewire.PW_KEY_APP_PROCESS_USER, Value: "alice"},
{Key: pipewire.PW_KEY_APP_PROCESS_HOST, Value: "nixos"},
{Key: pipewire.PW_KEY_APP_PROCESS_SESSION_ID, Value: "1"},
{Key: pipewire.PW_KEY_WINDOW_X11_DISPLAY, Value: ":0"},
{Key: "cpu.vm.name", Value: "qemu"},
{Key: "log.level", Value: "0"},
{Key: pipewire.PW_KEY_CPU_MAX_ALIGN, Value: "32"},
{Key: "default.clock.rate", Value: "48000"},
{Key: "default.clock.quantum", Value: "1024"},
{Key: "default.clock.min-quantum", Value: "32"},
{Key: "default.clock.max-quantum", Value: "2048"},
{Key: "default.clock.quantum-limit", Value: "8192"},
{Key: "default.clock.quantum-floor", Value: "4"},
{Key: "default.video.width", Value: "640"},
{Key: "default.video.height", Value: "480"},
{Key: "default.video.rate.num", Value: "25"},
{Key: "default.video.rate.denom", Value: "1"},
{Key: "clock.power-of-two-quantum", Value: "true"},
{Key: "link.max-buffers", Value: "64"},
{Key: "mem.warn-mlock", Value: "false"},
{Key: "mem.allow-mlock", Value: "true"},
{Key: "settings.check-quantum", Value: "false"},
{Key: "settings.check-rate", Value: "false"},
{Key: pipewire.PW_KEY_CORE_VERSION, Value: "1.4.7"},
{Key: pipewire.PW_KEY_ACCESS, Value: "unrestricted"},
},
},
Properties: pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "34"},
{Key: pipewire.PW_KEY_MODULE_ID, Value: "2"},
{Key: pipewire.PW_KEY_PROTOCOL, Value: "protocol-native"},
{Key: pipewire.PW_KEY_SEC_PID, Value: "1443"},
{Key: pipewire.PW_KEY_SEC_UID, Value: "1000"},
{Key: pipewire.PW_KEY_SEC_GID, Value: "100"},
{Key: pipewire.PW_KEY_SEC_SOCKET, Value: "pipewire-0-manager"},
},
}
wantRegistry0 := pipewire.Registry{
ID: wantRegistryId,
Objects: map[pipewire.Int]pipewire.RegistryGlobal{
pipewire.PW_ID_CORE: {
ID: pipewire.PW_ID_CORE,
Permissions: pipewire.PW_CORE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Core,
Version: pipewire.PW_VERSION_CORE,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "0"},
{Key: pipewire.PW_KEY_CORE_NAME, Value: "pipewire-0"},
},
},
1: {
ID: 1,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "1"},
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-rt"},
},
},
3: {
ID: 3,
Permissions: pipewire.PW_SECURITY_CONTEXT_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_SecurityContext,
Version: pipewire.PW_VERSION_SECURITY_CONTEXT,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "3"},
},
},
2: {
ID: 2,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "2"},
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-protocol-native"},
},
},
5: {
ID: 5,
Permissions: pipewire.PW_PROFILER_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Profiler,
Version: pipewire.PW_VERSION_PROFILER,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "5"},
},
},
4: {
ID: 4,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "4"},
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-profiler"},
},
},
6: {
ID: 6,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "6"},
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-metadata"},
},
},
7: {
ID: 7,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "7"},
{Key: pipewire.PW_KEY_MODULE_ID, Value: "6"},
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "metadata"},
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: pipewire.PW_TYPE_INTERFACE_Metadata},
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "3"},
},
},
8: {
ID: 8,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "8"},
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-spa-device-factory"},
},
},
9: {
ID: 9,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "9"},
{Key: pipewire.PW_KEY_MODULE_ID, Value: "8"},
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "spa-device-factory"},
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: pipewire.PW_TYPE_INTERFACE_Device},
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "3"},
},
},
10: {
ID: 10,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "10"},
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-spa-node-factory"},
},
},
11: {
ID: 11,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "11"},
{Key: pipewire.PW_KEY_MODULE_ID, Value: "10"},
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "spa-node-factory"},
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: pipewire.PW_TYPE_INTERFACE_Node},
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "3"},
},
},
12: {
ID: 12,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "12"},
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-client-node"},
},
},
13: {
ID: 13,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "13"},
{Key: pipewire.PW_KEY_MODULE_ID, Value: "12"},
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "client-node"},
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: pipewire.PW_TYPE_INTERFACE_ClientNode},
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "6"},
},
},
14: {
ID: 14,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "14"},
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-client-device"},
},
},
15: {
ID: 15,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "15"},
{Key: pipewire.PW_KEY_MODULE_ID, Value: "14"},
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "client-device"},
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: "Spa:Pointer:Interface:Device"},
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "0"},
},
},
16: {
ID: 16,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "16"},
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-portal"},
},
},
17: {
ID: 17,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "17"},
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-access"},
},
},
18: {
ID: 18,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "18"},
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-adapter"},
},
},
19: {
ID: 19,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "19"},
{Key: pipewire.PW_KEY_MODULE_ID, Value: "18"},
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "adapter"},
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: pipewire.PW_TYPE_INTERFACE_Node},
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "3"},
},
},
20: {
ID: 20,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "20"},
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-link-factory"},
},
},
21: {
ID: 21,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "21"},
{Key: pipewire.PW_KEY_MODULE_ID, Value: "20"},
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "link-factory"},
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: pipewire.PW_TYPE_INTERFACE_Link},
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "3"},
},
},
22: {
ID: 22,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "22"},
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-session-manager"},
},
},
23: {
ID: 23,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "23"},
{Key: pipewire.PW_KEY_MODULE_ID, Value: "22"},
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "client-endpoint"},
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: "PipeWire:Interface:ClientEndpoint"},
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "0"},
},
},
24: {
ID: 24,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "24"},
{Key: pipewire.PW_KEY_MODULE_ID, Value: "22"},
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "client-session"},
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: "PipeWire:Interface:ClientSession"},
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "0"},
},
},
25: {
ID: 25,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "25"},
{Key: pipewire.PW_KEY_MODULE_ID, Value: "22"},
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "session"},
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: "PipeWire:Interface:Session"},
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "0"},
},
},
26: {
ID: 26,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "26"},
{Key: pipewire.PW_KEY_MODULE_ID, Value: "22"},
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "endpoint"},
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: "PipeWire:Interface:Endpoint"},
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "0"},
},
},
27: {
ID: 27,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "27"},
{Key: pipewire.PW_KEY_MODULE_ID, Value: "22"},
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "endpoint-stream"},
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: "PipeWire:Interface:EndpointStream"},
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "0"},
},
},
28: {
ID: 28,
Permissions: pipewire.PW_FACTORY_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Factory,
Version: pipewire.PW_VERSION_FACTORY,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "28"},
{Key: pipewire.PW_KEY_MODULE_ID, Value: "22"},
{Key: pipewire.PW_KEY_FACTORY_NAME, Value: "endpoint-link"},
{Key: pipewire.PW_KEY_FACTORY_TYPE_NAME, Value: "PipeWire:Interface:EndpointLink"},
{Key: pipewire.PW_KEY_FACTORY_TYPE_VERSION, Value: "0"},
},
},
29: {
ID: 29,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "29"},
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-x11-bell"},
},
},
30: {
ID: 30,
Permissions: pipewire.PW_MODULE_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Module,
Version: pipewire.PW_VERSION_MODULE,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "30"},
{Key: pipewire.PW_KEY_MODULE_NAME, Value: pipewire.PIPEWIRE_MODULE_PREFIX + "module-jackdbus-detect"},
},
},
31: {
ID: 31,
Permissions: pipewire.PW_PERM_RWXM, // why is this not PW_NODE_PERM_MASK?
Type: pipewire.PW_TYPE_INTERFACE_Node,
Version: pipewire.PW_VERSION_NODE,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "31"},
{Key: pipewire.PW_KEY_FACTORY_ID, Value: "11"},
{Key: pipewire.PW_KEY_PRIORITY_DRIVER, Value: "200000"},
{Key: pipewire.PW_KEY_NODE_NAME, Value: "Dummy-Driver"},
},
},
32: {
ID: 32,
Permissions: pipewire.PW_PERM_RWXM, // why is this not PW_NODE_PERM_MASK?
Type: pipewire.PW_TYPE_INTERFACE_Node,
Version: pipewire.PW_VERSION_NODE,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "32"},
{Key: pipewire.PW_KEY_FACTORY_ID, Value: "11"},
{Key: pipewire.PW_KEY_PRIORITY_DRIVER, Value: "190000"},
{Key: pipewire.PW_KEY_NODE_NAME, Value: "Freewheel-Driver"},
},
},
33: {
ID: 33,
Permissions: pipewire.PW_METADATA_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Metadata,
Version: pipewire.PW_VERSION_METADATA,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "33"},
{Key: "metadata.name", Value: "settings"},
},
},
34: {
ID: 34,
Permissions: pipewire.PW_CLIENT_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Client,
Version: pipewire.PW_VERSION_CLIENT,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "34"},
{Key: pipewire.PW_KEY_MODULE_ID, Value: "2"},
{Key: pipewire.PW_KEY_PROTOCOL, Value: "protocol-native"},
{Key: pipewire.PW_KEY_SEC_PID, Value: "1443"},
{Key: pipewire.PW_KEY_SEC_UID, Value: "1000"},
{Key: pipewire.PW_KEY_SEC_GID, Value: "100"},
{Key: pipewire.PW_KEY_SEC_SOCKET, Value: "pipewire-0-manager"},
{Key: pipewire.PW_KEY_ACCESS, Value: "unrestricted"},
{Key: pipewire.PW_KEY_APP_NAME, Value: "pw-container"},
},
},
35: {
ID: 35,
Permissions: pipewire.PW_CLIENT_PERM_MASK,
Type: pipewire.PW_TYPE_INTERFACE_Client,
Version: pipewire.PW_VERSION_CLIENT,
Properties: &pipewire.SPADict{
{Key: pipewire.PW_KEY_OBJECT_SERIAL, Value: "35"},
{Key: pipewire.PW_KEY_MODULE_ID, Value: "2"},
{Key: pipewire.PW_KEY_PROTOCOL, Value: "protocol-native"},
{Key: pipewire.PW_KEY_SEC_PID, Value: "1447"},
{Key: pipewire.PW_KEY_SEC_UID, Value: "1000"},
{Key: pipewire.PW_KEY_SEC_GID, Value: "100"},
{Key: pipewire.PW_KEY_SEC_SOCKET, Value: "pipewire-0-manager"},
{Key: pipewire.PW_KEY_ACCESS, Value: "unrestricted"},
{Key: pipewire.PW_KEY_APP_NAME, Value: "WirePlumber"},
},
},
},
}
if coreInfo := ctx.GetCore().Info; !reflect.DeepEqual(coreInfo, &wantCoreInfo0) {
t.Fatalf("New: CoreInfo = %s, want %s", mustMarshalJSON(coreInfo), mustMarshalJSON(&wantCoreInfo0))
}
if client := ctx.GetClient(); !reflect.DeepEqual(client, &wantClient0) {
t.Fatalf("New: Client = %s, want %s", mustMarshalJSON(client), mustMarshalJSON(&wantClient0))
}
if registry.ID != wantRegistry0.ID {
t.Fatalf("GetRegistry: ID = %d, want %d", registry.ID, wantRegistry0.ID)
}
if !reflect.DeepEqual(registry.Objects, wantRegistry0.Objects) {
t.Fatalf("GetRegistry: Objects = %s, want %s", mustMarshalJSON(registry.Objects), mustMarshalJSON(wantRegistry0.Objects))
}
var securityContext *pipewire.SecurityContext
const wantSecurityContextId = 3
if c, err := registry.GetSecurityContext(); err != nil {
t.Fatalf("GetSecurityContext: error = %v", err)
} else {
if c.ID != wantSecurityContextId {
t.Fatalf("GetSecurityContext: ID = %d, want %d", c.ID, wantSecurityContextId)
}
securityContext = c
}
if err := ctx.Roundtrip(); err != nil {
t.Fatalf("Roundtrip: error = %v", err)
}
// none of these should change
if coreInfo := ctx.GetCore().Info; !reflect.DeepEqual(coreInfo, &wantCoreInfo0) {
t.Fatalf("Roundtrip: CoreInfo = %s, want %s", mustMarshalJSON(coreInfo), mustMarshalJSON(&wantCoreInfo0))
}
if client := ctx.GetClient(); !reflect.DeepEqual(client, &wantClient0) {
t.Fatalf("Roundtrip: Client = %s, want %s", mustMarshalJSON(client), mustMarshalJSON(&wantClient0))
}
if registry.ID != wantRegistry0.ID {
t.Fatalf("Roundtrip: ID = %d, want %d", registry.ID, wantRegistry0.ID)
}
if !reflect.DeepEqual(registry.Objects, wantRegistry0.Objects) {
t.Fatalf("Roundtrip: Objects = %s, want %s", mustMarshalJSON(registry.Objects), mustMarshalJSON(wantRegistry0.Objects))
}
if err := securityContext.Create(21, 20, pipewire.SPADict{
{Key: pipewire.PW_KEY_SEC_ENGINE, Value: "org.flatpak"},
{Key: pipewire.PW_KEY_ACCESS, Value: "restricted"},
}); err != nil {
t.Fatalf("SecurityContext.Create: error = %v", err)
}
// none of these should change
if coreInfo := ctx.GetCore().Info; !reflect.DeepEqual(coreInfo, &wantCoreInfo0) {
t.Fatalf("Roundtrip: CoreInfo = %s, want %s", mustMarshalJSON(coreInfo), mustMarshalJSON(&wantCoreInfo0))
}
if client := ctx.GetClient(); !reflect.DeepEqual(client, &wantClient0) {
t.Fatalf("Roundtrip: Client = %s, want %s", mustMarshalJSON(client), mustMarshalJSON(&wantClient0))
}
if registry.ID != wantRegistry0.ID {
t.Fatalf("Roundtrip: ID = %d, want %d", registry.ID, wantRegistry0.ID)
}
if !reflect.DeepEqual(registry.Objects, wantRegistry0.Objects) {
t.Fatalf("Roundtrip: Objects = %s, want %s", mustMarshalJSON(registry.Objects), mustMarshalJSON(wantRegistry0.Objects))
}
if err := ctx.Close(); err != nil {
t.Fatalf("Close: error = %v", err)
}
}
// stubUnixConnSample is sample data held by stubUnixConn.
type stubUnixConnSample struct {
nr uintptr
iovec string
flags int
files []int
errno Errno
}
// stubUnixConn implements [pipewire.Conn] and checks the behaviour of [pipewire.Context].
type stubUnixConn struct {
samples []stubUnixConnSample
current int
}
func (conn *stubUnixConn) MightBlock(timeout time.Duration) {
if timeout != 5*time.Second {
panic("unexpected timeout " + timeout.String())
}
if conn.current == 0 ||
(conn.samples[conn.current-1].nr == SYS_RECVMSG && conn.samples[conn.current-1].errno == EAGAIN && conn.samples[conn.current].nr == SYS_SENDMSG) ||
(conn.samples[conn.current-1].nr == SYS_SENDMSG && conn.samples[conn.current].nr == SYS_RECVMSG) {
return
}
panic("unexpected blocking hint before sample " + strconv.Itoa(conn.current))
}
// nextSample returns the current sample and increments the counter.
func (conn *stubUnixConn) nextSample(nr uintptr) (sample *stubUnixConnSample, wantOOB []byte, err error) {
sample = &conn.samples[conn.current]
conn.current++
if sample.nr != nr {
err = fmt.Errorf("unexpected syscall %d", SYS_SENDMSG)
return
}
if len(sample.files) > 0 {
wantOOB = UnixRights(sample.files...)
}
return
}
func (conn *stubUnixConn) Recvmsg(p, oob []byte, flags int) (n, oobn, recvflags int, err error) {
var (
sample *stubUnixConnSample
wantOOB []byte
)
sample, wantOOB, err = conn.nextSample(SYS_RECVMSG)
if err != nil {
return
}
if n = copy(p, sample.iovec); n != len(sample.iovec) {
err = fmt.Errorf("insufficient iovec size %d, want at least %d", len(p), len(sample.iovec))
return
}
if oobn = copy(oob, wantOOB); oobn != len(wantOOB) {
err = fmt.Errorf("insufficient oob size %d, want at least %d", len(oob), len(wantOOB))
return
}
if flags != sample.flags {
err = fmt.Errorf("flags = %#x, want %#x", flags, sample.flags)
return
}
recvflags = MSG_CMSG_CLOEXEC
if sample.errno != 0 {
err = sample.errno
if n != 0 {
panic("invalid recvmsg: n = " + strconv.Itoa(n))
}
n = -1
}
return
}
func (conn *stubUnixConn) Sendmsg(p, oob []byte, flags int) (n int, err error) {
var (
sample *stubUnixConnSample
wantOOB []byte
)
sample, wantOOB, err = conn.nextSample(SYS_SENDMSG)
if err != nil {
return
}
if string(p) != sample.iovec {
err = fmt.Errorf("iovec: %#v, want %#v", p, []byte(sample.iovec))
return
}
if string(oob[:len(wantOOB)]) != string(wantOOB) {
err = fmt.Errorf("oob: %#v, want %#v", oob[:len(wantOOB)], wantOOB)
return
}
if flags != sample.flags {
err = fmt.Errorf("flags = %#x, want %#x", flags, sample.flags)
return
}
n = len(sample.iovec)
if sample.errno != 0 {
err = sample.errno
}
return
}
func (conn *stubUnixConn) Close() error {
if conn.current != len(conn.samples) {
return fmt.Errorf("consumed %d samples, want %d", conn.current, len(conn.samples))
}
return nil
}
func TestContextErrors(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
err error
want string
}{
{"ProxyConsumeError invalid", pipewire.ProxyConsumeError{}, "invalid proxy consume error"},
{"ProxyConsumeError single", pipewire.ProxyConsumeError{
stub.UniqueError(0),
}, "unique error 0 injected by the test suite"},
{"ProxyConsumeError multiple", pipewire.ProxyConsumeError{
stub.UniqueError(1),
stub.UniqueError(2),
stub.UniqueError(3),
stub.UniqueError(4),
stub.UniqueError(5),
stub.UniqueError(6),
stub.UniqueError(7),
}, "unique error 1 injected by the test suite; 7 additional proxy errors occurred after this point"},
{"ProxyFatalError", &pipewire.ProxyFatalError{
Err: stub.UniqueError(8),
}, "unique error 8 injected by the test suite"},
{"ProxyFatalError proxy errors", &pipewire.ProxyFatalError{
Err: stub.UniqueError(9),
ProxyErrs: make([]error, 1<<4),
}, "unique error 9 injected by the test suite; 16 additional proxy errors occurred before this point"},
{"UnexpectedFileCountError", &pipewire.UnexpectedFileCountError{0, -1}, "received -1 files instead of the expected 0"},
{"UnacknowledgedProxyError", make(pipewire.UnacknowledgedProxyError, 1<<4), "server did not acknowledge 16 proxies"},
{"UnacknowledgedProxyDestructionError", make(pipewire.UnacknowledgedProxyDestructionError, 1<<4), "server did not acknowledge 16 proxy destructions"},
{"DanglingFilesError", make(pipewire.DanglingFilesError, 1<<4), "received 16 dangling files"},
{"UnexpectedFilesError", pipewire.UnexpectedFilesError(1 << 4), "server message headers claim to have sent more files than actually received"},
{"UnexpectedSequenceError", pipewire.UnexpectedSequenceError(1 << 4), "unexpected seq 16"},
{"UnsupportedFooterOpcodeError", pipewire.UnsupportedFooterOpcodeError(1 << 4), "unsupported footer opcode 16"},
{"RoundtripUnexpectedEOFError ErrRoundtripEOFHeader", pipewire.ErrRoundtripEOFHeader, "unexpected EOF decoding message header"},
{"RoundtripUnexpectedEOFError ErrRoundtripEOFBody", pipewire.ErrRoundtripEOFBody, "unexpected EOF establishing message body bounds"},
{"RoundtripUnexpectedEOFError ErrRoundtripEOFFooter", pipewire.ErrRoundtripEOFFooter, "unexpected EOF establishing message footer bounds"},
{"RoundtripUnexpectedEOFError ErrRoundtripEOFFooterOpcode", pipewire.ErrRoundtripEOFFooterOpcode, "unexpected EOF decoding message footer opcode"},
{"RoundtripUnexpectedEOFError invalid", pipewire.RoundtripUnexpectedEOFError(0xbad), "unexpected EOF"},
{"InconsistentFilesError", &pipewire.InconsistentFilesError{0, 2}, "queued 0 files instead of the expected 2"},
{"UnsupportedOpcodeError", &pipewire.UnsupportedOpcodeError{
Opcode: 0xff,
Interface: pipewire.PW_TYPE_INFO_INTERFACE_BASE + "Invalid",
}, "unsupported PipeWire:Interface:Invalid opcode 255"},
{"UnknownIdError", &pipewire.UnknownIdError{
Id: -1,
Data: "\x00",
}, "unknown proxy id -1"},
{"InvalidPingError", &pipewire.InvalidPingError{
ID: 0xbad,
Sequence: 0xcafe,
}, "received Core::Ping seq 51966 targeting unknown proxy id 2989"},
{"GlobalIDCollisionError", &pipewire.GlobalIDCollisionError{
ID: 0xbad,
Previous: &pipewire.RegistryGlobal{Type: "PipeWire:Interface:Invalid"},
Current: &pipewire.RegistryGlobal{Type: "PipeWire:Interface:NewInvalid"},
}, "new Registry::Global event for PipeWire:Interface:NewInvalid stepping on previous id 2989 for PipeWire:Interface:Invalid"},
{"UnknownGlobalIDRemoveError", pipewire.UnknownGlobalIDRemoveError(0xbad), "Registry::GlobalRemove event targets unknown id 2989"},
{"PermissionError", &pipewire.PermissionError{
ID: 2,
Message: "no permission to destroy 0",
}, "no permission to destroy 0"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if got := tc.err.Error(); got != tc.want {
t.Errorf("Error: %q, want %q", got, tc.want)
}
})
}
}