From 1038af98f08f5c16828fabde3d6b559013c6c123 Mon Sep 17 00:00:00 2001 From: Ophestra Umiker Date: Sat, 28 Sep 2024 00:06:16 +0900 Subject: [PATCH] dbus: add tests Signed-off-by: Ophestra Umiker --- dbus/config_test.go | 152 ++++++++++++++++++++ dbus/dbus_test.go | 132 +++++++++++++++++ dbus/export_test.go | 13 ++ dbus/samples_test.go | 167 ++++++++++++++++++++++ dbus/testdata/dev.vencord.Vesktop.json | 18 +++ dbus/testdata/org.chromium.Chromium*.json | 8 ++ dbus/testdata/org.chromium.Chromium.json | 20 +++ flake.nix | 2 +- 8 files changed, 511 insertions(+), 1 deletion(-) create mode 100644 dbus/config_test.go create mode 100644 dbus/dbus_test.go create mode 100644 dbus/export_test.go create mode 100644 dbus/samples_test.go create mode 100644 dbus/testdata/dev.vencord.Vesktop.json create mode 100644 dbus/testdata/org.chromium.Chromium*.json create mode 100644 dbus/testdata/org.chromium.Chromium.json diff --git a/dbus/config_test.go b/dbus/config_test.go new file mode 100644 index 0000000..f92fe56 --- /dev/null +++ b/dbus/config_test.go @@ -0,0 +1,152 @@ +package dbus_test + +import ( + "path" + "reflect" + "slices" + "strings" + "testing" + + "git.ophivana.moe/cat/fortify/dbus" +) + +func TestConfig_Args(t *testing.T) { + for _, tc := range testCases() { + if tc.wantErr { + // args does not check for nulls + continue + } + + t.Run("build arguments for "+tc.id, func(t *testing.T) { + if got := tc.c.Args(tc.bus); !slices.Equal(got, tc.want) { + t.Errorf("Args(%q) = %v, want %v", + tc.bus, + got, tc.want) + } + }) + } +} + +func TestNewConfigFromFile(t *testing.T) { + for _, tc := range testCases() { + name := new(strings.Builder) + name.WriteString("parse configuration file for application ") + name.WriteString(tc.id) + if tc.wantErr { + name.WriteString(" with unexpected results") + } + + samplePath := path.Join("testdata", tc.id+".json") + + t.Run(name.String(), func(t *testing.T) { + got, err := dbus.NewConfigFromFile(samplePath) + if err != nil { + t.Errorf("NewConfigFromFile(%q) error = %v", + samplePath, + err) + return + } + if !tc.wantErr && !reflect.DeepEqual(got, tc.c) { + t.Errorf("NewConfigFromFile(%q) got = %v, want %v", + samplePath, + got, tc.c) + } + if tc.wantErr && reflect.DeepEqual(got, tc.c) { + t.Errorf("NewConfigFromFile(%q) got = %v, wantErr %v", + samplePath, + got, tc.wantErr) + } + }) + } +} + +func TestNewConfig(t *testing.T) { + ids := [...]string{"org.chromium.Chromium", "dev.vencord.Vesktop"} + + type newTestCase struct { + id string + args [2]bool + want *dbus.Config + } + + // populate tests from IDs in generic tests + tcs := make([]newTestCase, 0, (len(ids)+1)*4) + // tests for defaults without id + tcs = append(tcs, + newTestCase{"", [2]bool{false, false}, &dbus.Config{ + Call: make(map[string]string), + Broadcast: make(map[string]string), + Filter: true, + }}, + newTestCase{"", [2]bool{false, true}, &dbus.Config{ + Call: make(map[string]string), + Broadcast: make(map[string]string), + Filter: true, + }}, + newTestCase{"", [2]bool{true, false}, &dbus.Config{ + Talk: []string{"org.freedesktop.DBus", "org.freedesktop.Notifications"}, + Call: map[string]string{"org.freedesktop.portal.*": "*"}, + Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"}, + Filter: true, + }}, + newTestCase{"", [2]bool{true, true}, &dbus.Config{ + Talk: []string{"org.freedesktop.DBus", "org.freedesktop.Notifications"}, + Call: map[string]string{"org.freedesktop.portal.*": "*"}, + Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"}, + Filter: true, + }}, + ) + for _, id := range ids { + tcs = append(tcs, + newTestCase{id, [2]bool{false, false}, &dbus.Config{ + Call: make(map[string]string), + Broadcast: make(map[string]string), + Filter: true, + }}, + newTestCase{id, [2]bool{false, true}, &dbus.Config{ + Call: make(map[string]string), + Broadcast: make(map[string]string), + Filter: true, + }}, + newTestCase{id, [2]bool{true, false}, &dbus.Config{ + Talk: []string{"org.freedesktop.DBus", "org.freedesktop.Notifications"}, + Own: []string{id + ".*"}, + Call: map[string]string{"org.freedesktop.portal.*": "*"}, + Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"}, + Filter: true, + }}, + newTestCase{id, [2]bool{true, true}, &dbus.Config{ + Talk: []string{"org.freedesktop.DBus", "org.freedesktop.Notifications"}, + Own: []string{id + ".*", "org.mpris.MediaPlayer2." + id + ".*"}, + Call: map[string]string{"org.freedesktop.portal.*": "*"}, + Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"}, + Filter: true, + }}, + ) + } + + for _, tc := range tcs { + name := new(strings.Builder) + name.WriteString("create new configuration struct") + + if tc.args[0] { + name.WriteString(" with builtin defaults") + if tc.args[1] { + name.WriteString(" (mpris)") + } + } + + if tc.id != "" { + name.WriteString(" for application ID ") + name.WriteString(tc.id) + } + + t.Run(name.String(), func(t *testing.T) { + if gotC := dbus.NewConfig(tc.id, tc.args[0], tc.args[1]); !reflect.DeepEqual(gotC, tc.want) { + t.Errorf("NewConfig(%q, %t, %t) = %v, want %v", + tc.id, tc.args[0], tc.args[1], + gotC, tc.want) + } + }) + } +} diff --git a/dbus/dbus_test.go b/dbus/dbus_test.go new file mode 100644 index 0000000..e2e2795 --- /dev/null +++ b/dbus/dbus_test.go @@ -0,0 +1,132 @@ +package dbus_test + +import ( + "errors" + "strings" + "testing" + + "git.ophivana.moe/cat/fortify/dbus" +) + +const ( + binPath = "/usr/bin/bwrap" +) + +func TestNew(t *testing.T) { + for _, tc := range [][2][2]string{ + { + {"unix:path=/run/user/1971/bus", "/tmp/fortify.1971/1ca5d183ef4c99e74c3e544715f32702/bus"}, + {"unix:path=/run/dbus/system_bus_socket", "/tmp/fortify.1971/1ca5d183ef4c99e74c3e544715f32702/system_bus_socket"}, + }, + { + {"unix:path=/run/user/1971/bus", "/tmp/fortify.1971/881ac3796ff3f3bf0a773824383187a0/bus"}, + {"unix:path=/run/dbus/system_bus_socket", "/tmp/fortify.1971/881ac3796ff3f3bf0a773824383187a0/system_bus_socket"}, + }, + { + {"unix:path=/run/user/1971/bus", "/tmp/fortify.1971/3d1a5084520ef79c0c6a49a675bac701/bus"}, + {"unix:path=/run/dbus/system_bus_socket", "/tmp/fortify.1971/3d1a5084520ef79c0c6a49a675bac701/system_bus_socket"}, + }, + { + {"unix:path=/run/user/1971/bus", "/tmp/fortify.1971/2a1639bab712799788ea0ff7aa280c35/bus"}, + {"unix:path=/run/dbus/system_bus_socket", "/tmp/fortify.1971/2a1639bab712799788ea0ff7aa280c35/system_bus_socket"}, + }, + } { + t.Run("create instance for "+tc[0][0]+" and "+tc[1][0], func(t *testing.T) { + if got := dbus.New(binPath, tc[0], tc[1]); !got.CompareTestNew(binPath, tc[0], tc[1]) { + t.Errorf("New(%q, %q, %q) = %v", + binPath, tc[0], tc[1], + got) + } + }) + } +} + +func TestProxy_Seal(t *testing.T) { + ep := dbus.New(binPath, [2]string{}, [2]string{}) + if err := ep.Seal(nil, nil); !errors.Is(err, dbus.ErrConfig) { + t.Errorf("Seal(nil, nil) error = %v, want %v", + err, dbus.ErrConfig) + } + + for id, tc := range testCasePairs() { + t.Run("create seal for "+id, func(t *testing.T) { + p := dbus.New(binPath, tc[0].bus, tc[1].bus) + if err := p.Seal(tc[0].c, tc[1].c); (err != nil) != tc[0].wantErr { + t.Errorf("Seal(%p, %p) error = %v, wantErr %v", + tc[0].c, tc[1].c, + err, tc[0].wantErr) + return + } + + // rest of the tests happen for sealed instances + if tc[0].wantErr { + return + } + + // build null-terminated string from wanted args + want := new(strings.Builder) + args := append(tc[0].want, tc[1].want...) + for _, arg := range args { + want.WriteString(arg) + want.WriteByte('\x00') + } + + wt := p.AccessTestProxySeal() + got := new(strings.Builder) + if _, err := wt.WriteTo(got); err != nil { + t.Errorf("p.seal.WriteTo(): %v", err) + } + + if want.String() != got.String() { + t.Errorf("Seal(%p, %p) seal = %v, want %v", + tc[0].c, tc[1].c, + got.String(), want.String()) + } + }) + } +} + +func TestProxy_Seal_Panic(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("Seal: did not panic from repeated seal") + } + }() + + p := dbus.New(binPath, [2]string{}, [2]string{}) + _ = p.Seal(dbus.NewConfig("", true, false), nil) + _ = p.Seal(dbus.NewConfig("", true, false), nil) +} + +func TestProxy_String(t *testing.T) { + for id, tc := range testCasePairs() { + // this test does not test errors + if tc[0].wantErr { + continue + } + + t.Run("strings for "+id, func(t *testing.T) { + p := dbus.New(binPath, tc[0].bus, tc[1].bus) + + // test unsealed behaviour + want := "(unsealed dbus proxy)" + if got := p.String(); got != want { + t.Errorf("String() = %v, want %v", + got, want) + } + + if err := p.Seal(tc[0].c, tc[1].c); err != nil { + t.Errorf("Seal(%p, %p) error = %v, wantErr %v", + tc[0].c, tc[1].c, + err, tc[0].wantErr) + } + + // test sealed behaviour + want = strings.Join(append(tc[0].want, tc[1].want...), " ") + if got := p.String(); got != want { + t.Errorf("String() = %v, want %v", + got, want) + } + }) + } +} diff --git a/dbus/export_test.go b/dbus/export_test.go new file mode 100644 index 0000000..dff0241 --- /dev/null +++ b/dbus/export_test.go @@ -0,0 +1,13 @@ +package dbus + +import "io" + +// CompareTestNew provides TestNew with comparison access to unexported Proxy fields. +func (p *Proxy) CompareTestNew(path string, session, system [2]string) bool { + return path == p.path && session == p.session && system == p.system +} + +// AccessTestProxySeal provides TestProxy_Seal with access to unexported Proxy seal field. +func (p *Proxy) AccessTestProxySeal() io.WriterTo { + return p.seal +} diff --git a/dbus/samples_test.go b/dbus/samples_test.go new file mode 100644 index 0000000..8fde1d7 --- /dev/null +++ b/dbus/samples_test.go @@ -0,0 +1,167 @@ +package dbus_test + +import ( + "sync" + + "git.ophivana.moe/cat/fortify/dbus" +) + +var samples = []dbusTestCase{ + { + "org.chromium.Chromium", &dbus.Config{ + See: nil, + Talk: []string{"org.freedesktop.Notifications", "org.freedesktop.FileManager1", "org.freedesktop.ScreenSaver"}, + Own: []string{"org.chromium.Chromium.*", "org.mpris.MediaPlayer2.org.chromium.Chromium.*", "org.mpris.MediaPlayer2.chromium.*"}, + Call: map[string]string{"org.freedesktop.portal.*": "*"}, + Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"}, + Log: false, + Filter: true, + }, false, + [2]string{"unix:path=/run/user/1971/bus", "/tmp/fortify.1971/12622d846cc3fe7b4c10359d01f0eb47/bus"}, + []string{ + "unix:path=/run/user/1971/bus", + "/tmp/fortify.1971/12622d846cc3fe7b4c10359d01f0eb47/bus", + "--filter", + "--talk=org.freedesktop.Notifications", + "--talk=org.freedesktop.FileManager1", + "--talk=org.freedesktop.ScreenSaver", + "--own=org.chromium.Chromium.*", + "--own=org.mpris.MediaPlayer2.org.chromium.Chromium.*", + "--own=org.mpris.MediaPlayer2.chromium.*", + "--call=org.freedesktop.portal.*=*", + "--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*", + }, + }, + { + "org.chromium.Chromium*", &dbus.Config{ + See: nil, + Talk: []string{"org.freedesktop.Avahi", "org.freedesktop.UPower"}, + Own: nil, + Call: nil, + Broadcast: nil, + Log: false, + Filter: true, + }, false, + [2]string{"unix:path=/run/dbus/system_bus_socket", "/tmp/fortify.1971/12622d846cc3fe7b4c10359d01f0eb47/system_bus_socket"}, + []string{"unix:path=/run/dbus/system_bus_socket", + "/tmp/fortify.1971/12622d846cc3fe7b4c10359d01f0eb47/system_bus_socket", + "--filter", + "--talk=org.freedesktop.Avahi", + "--talk=org.freedesktop.UPower", + }, + }, + + { + "dev.vencord.Vesktop", &dbus.Config{ + See: nil, + Talk: []string{"org.freedesktop.Notifications", "org.kde.StatusNotifierWatcher"}, + Own: []string{"dev.vencord.Vesktop.*", "org.mpris.MediaPlayer2.dev.vencord.Vesktop.*"}, + Call: map[string]string{"org.freedesktop.portal.*": "*"}, + Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"}, + Log: false, + Filter: true, + }, false, + [2]string{"unix:path=/run/user/1971/bus", "/tmp/fortify.1971/34c24f16a0d791d28835ededaf446033/bus"}, + []string{ + "unix:path=/run/user/1971/bus", + "/tmp/fortify.1971/34c24f16a0d791d28835ededaf446033/bus", + "--filter", + "--talk=org.freedesktop.Notifications", + "--talk=org.kde.StatusNotifierWatcher", + "--own=dev.vencord.Vesktop.*", + "--own=org.mpris.MediaPlayer2.dev.vencord.Vesktop.*", + "--call=org.freedesktop.portal.*=*", + "--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*"}, + }, +} + +type dbusTestCase struct { + id string + c *dbus.Config + wantErr bool + bus [2]string + want []string +} + +var ( + testCasesV []dbusTestCase + testCasePairsV map[string][2]dbusTestCase + + testCaseOnce sync.Once +) + +func testCases() []dbusTestCase { + testCaseOnce.Do(testCaseGenerate) + return testCasesV +} + +func testCasePairs() map[string][2]dbusTestCase { + testCaseOnce.Do(testCaseGenerate) + return testCasePairsV +} + +func injectNulls(t *[]string) { + f := make([]string, len(*t)) + for i := range f { + f[i] = "\x00" + (*t)[i] + "\x00" + } + *t = f +} + +func testCaseGenerate() { + // create null-injected test cases + testCasesV = make([]dbusTestCase, len(samples)*2) + for i := range samples { + testCasesV[i] = samples[i] + testCasesV[len(samples)+i] = samples[i] + testCasesV[len(samples)+i].c = new(dbus.Config) + *testCasesV[len(samples)+i].c = *samples[i].c + + // inject nulls + fi := &testCasesV[len(samples)+i] + fi.wantErr = true + fi.c = &*fi.c + + injectNulls(&fi.c.See) + injectNulls(&fi.c.Talk) + injectNulls(&fi.c.Own) + } + + // enumerate test case pairs + var pc int + for _, tc := range samples { + if tc.id != "" { + pc++ + } + } + testCasePairsV = make(map[string][2]dbusTestCase, pc) + for i, tc := range testCasesV { + if tc.id == "" { + continue + } + + // skip already enumerated system bus test + if tc.id[len(tc.id)-1] == '*' { + continue + } + + ftp := [2]dbusTestCase{tc} + + // system proxy tests always place directly after its user counterpart with id ending in * + if i+1 < len(testCasesV) && testCasesV[i+1].id[len(testCasesV[i+1].id)-1] == '*' { + // attach system bus config + ftp[1] = testCasesV[i+1] + + // check for misplaced/mismatching tests + if ftp[0].wantErr != ftp[1].wantErr || ftp[0].id+"*" != ftp[1].id { + panic("mismatching session/system pairing") + } + } + + k := tc.id + if tc.wantErr { + k = "malformed_" + k + } + testCasePairsV[k] = ftp + } +} diff --git a/dbus/testdata/dev.vencord.Vesktop.json b/dbus/testdata/dev.vencord.Vesktop.json new file mode 100644 index 0000000..0525977 --- /dev/null +++ b/dbus/testdata/dev.vencord.Vesktop.json @@ -0,0 +1,18 @@ +{ + "talk":[ + "org.freedesktop.Notifications", + "org.kde.StatusNotifierWatcher" + ], + "own":[ + "dev.vencord.Vesktop.*", + "org.mpris.MediaPlayer2.dev.vencord.Vesktop.*" + ], + "call":{ + "org.freedesktop.portal.*":"*" + }, + "broadcast":{ + "org.freedesktop.portal.*":"@/org/freedesktop/portal/*" + }, + + "filter":true +} diff --git a/dbus/testdata/org.chromium.Chromium*.json b/dbus/testdata/org.chromium.Chromium*.json new file mode 100644 index 0000000..977c26f --- /dev/null +++ b/dbus/testdata/org.chromium.Chromium*.json @@ -0,0 +1,8 @@ +{ + "talk":[ + "org.freedesktop.Avahi", + "org.freedesktop.UPower" + ], + + "filter":true +} \ No newline at end of file diff --git a/dbus/testdata/org.chromium.Chromium.json b/dbus/testdata/org.chromium.Chromium.json new file mode 100644 index 0000000..8b93023 --- /dev/null +++ b/dbus/testdata/org.chromium.Chromium.json @@ -0,0 +1,20 @@ +{ + "talk":[ + "org.freedesktop.Notifications", + "org.freedesktop.FileManager1", + "org.freedesktop.ScreenSaver" + ], + "own":[ + "org.chromium.Chromium.*", + "org.mpris.MediaPlayer2.org.chromium.Chromium.*", + "org.mpris.MediaPlayer2.chromium.*" + ], + "call":{ + "org.freedesktop.portal.*":"*" + }, + "broadcast":{ + "org.freedesktop.portal.*":"@/org/freedesktop/portal/*" + }, + + "filter":true +} diff --git a/flake.nix b/flake.nix index 1d5357d..3a2f669 100644 --- a/flake.nix +++ b/flake.nix @@ -36,7 +36,7 @@ default = nixpkgsFor.${system}.mkShell { buildInputs = with nixpkgsFor.${system}; - [ self.packages.${system}.fortify ] ++ self.packages.${system}.fortify.buildInputs; + self.packages.${system}.fortify.buildInputs ++ [ self.packages.${system}.fortify ]; }; }); };