Files
cthulhu/internal/wine/review_registry_test.go

175 lines
5.8 KiB
Go

// SPDX-License-Identifier: GPL-3.0-or-later
package wine
import (
"context"
"errors"
"fmt"
"git.stormux.org/storm/cthulhu/internal/atspi"
"github.com/godbus/dbus/v5"
"testing"
"time"
)
func TestReviewRegistryRetiresInactiveWindowTrees(t *testing.T) {
r := readyReviewRegistry(t)
n := registerNotice(t, r, ":1.2")
r.captureCall = func(_ context.Context, _ string, _ dbus.ObjectPath, notice ReviewFocusNotice) (map[string]dbus.Variant, error) {
f := reviewWire()
f["window"] = dbus.MakeVariant(notice.Window)
f["revision"] = dbus.MakeVariant(notice.Revision)
nodes := f["nodes"].Value().([]map[string]dbus.Variant)
nodes[0]["id"] = dbus.MakeVariant(notice.Window)
for _, node := range nodes[1:] {
if node["parent"].Value() == "window" {
node["parent"] = dbus.MakeVariant(notice.Window)
}
}
return f, nil
}
for i := 1; i <= 32; i++ {
n.Window = fmt.Sprintf("window%d", i)
n.Revision = uint64(i)
if !r.PresentFocus(n.Sender, n) {
t.Fatal("focus rejected")
}
one, err := r.Capture(context.Background(), n.ID())
if err != nil {
t.Fatal(err)
}
two, err := r.Capture(context.Background(), n.ID())
if err != nil || one != two {
t.Fatal("active window tree is not stable")
}
if len(r.providers[n.Sender].trees) != 1 {
t.Fatalf("retained %d inactive trees", len(r.providers[n.Sender].trees)-1)
}
}
}
func registryNotice(sender string) ReviewFocusNotice {
return ReviewFocusNotice{Sender: sender, Path: ReviewObjectPath, Instance: "helper-instance", Window: "window", Focus: "input", Revision: 1, Title: "same title", Role: "window", Bounds: atspi.Rectangle{X: 10, Y: 20, Width: 400, Height: 300}}
}
func readyReviewRegistry(t *testing.T) *ReviewRegistry {
t.Helper()
registry := NewReviewRegistry(nil)
// Unit tests substitute transport only; registration still requires lifecycle.
registry.ctx, registry.cancel = context.WithCancel(context.Background())
t.Cleanup(func() { registry.Stop(context.Background()) })
return registry
}
func registerNotice(t *testing.T, r *ReviewRegistry, sender string) ReviewFocusNotice {
t.Helper()
notice := registryNotice(sender)
if !r.RegisterProvider(sender, ReviewObjectPath, 1) || !r.PresentFocus(sender, notice) {
t.Fatal("registration rejected")
}
return notice
}
func TestReviewRegistryTwoProviders(t *testing.T) {
r := readyReviewRegistry(t)
one := registerNotice(t, r, ":1.2")
two := registerNotice(t, r, ":1.3")
if one.ID() == two.ID() || len(r.Focuses()) != 2 || !r.Current(one.ID()) || !r.Current(two.ID()) {
t.Fatal("providers collided")
}
if len(r.changes) != 1 {
t.Fatal("notifications are not coalesced")
}
if !r.RegisterProvider(one.Sender, ReviewObjectPath, 1) || r.Current(one.ID()) || len(r.Focuses()) != 1 {
t.Fatal("registration retained old lifetime")
}
}
func TestReviewRegistrySenderAssociation(t *testing.T) {
r := readyReviewRegistry(t)
n := registryNotice(":1.2")
if r.PresentFocus(n.Sender, n) || r.RegisterProvider("well.known", ReviewObjectPath, 1) || r.RegisterProvider(n.Sender, ReviewObjectPath, 2) || r.RegisterProvider(n.Sender, "/wrong", 1) {
t.Fatal("invalid provider accepted")
}
registerNotice(t, r, n.Sender)
n.Sender = ":1.99"
n.Path = "/forged"
if !r.PresentFocus(":1.2", n) {
t.Fatal("actual sender rejected")
}
got := r.Focuses()[0]
if got.Sender != ":1.2" || got.Path != ReviewObjectPath {
t.Fatal("payload forged destination")
}
n.Revision = 0
if r.PresentFocus(":1.2", n) {
t.Fatal("invalid focus accepted")
}
}
func TestReviewRegistryProviderDisappears(t *testing.T) {
r := readyReviewRegistry(t)
n := registerNotice(t, r, ":1.2")
r.disappeared(n.Sender)
if r.Current(n.ID()) || len(r.Focuses()) != 0 {
t.Fatal("dead provider retained")
}
if _, err := r.Capture(context.Background(), n.ID()); !errors.Is(err, ErrReviewUnavailable) {
t.Fatal(err)
}
}
func TestReviewCaptureDeadlineAndStaleReply(t *testing.T) {
r := readyReviewRegistry(t)
n := registerNotice(t, r, ":1.2")
calls := 0
r.captureCall = func(ctx context.Context, sender string, path dbus.ObjectPath, notice ReviewFocusNotice) (map[string]dbus.Variant, error) {
calls++
fields := reviewWire()
if calls == 1 {
fields["status"] = dbus.MakeVariant("stale")
fields["nodes"] = dbus.MakeVariant([]map[string]dbus.Variant{})
}
return fields, nil
}
tree, err := r.Capture(context.Background(), n.ID())
if err != nil || tree == nil || calls != 2 {
t.Fatalf("stale retry: %v calls=%d", err, calls)
}
again, err := r.Capture(context.Background(), n.ID())
if err != nil || again != tree {
t.Fatal("capture replaced live tree")
}
r.captureCall = func(ctx context.Context, _ string, _ dbus.ObjectPath, _ ReviewFocusNotice) (map[string]dbus.Variant, error) {
<-ctx.Done()
return nil, ctx.Err()
}
started := time.Now()
if _, err = r.Capture(context.Background(), n.ID()); err == nil || time.Since(started) > 750*time.Millisecond {
t.Fatalf("deadline: %v", err)
}
r.captureCall = func(context.Context, string, dbus.ObjectPath, ReviewFocusNotice) (map[string]dbus.Variant, error) {
f := reviewWire()
f["revision"] = dbus.MakeVariant(uint64(2))
return f, nil
}
if _, err = r.Capture(context.Background(), n.ID()); !errors.Is(err, ErrReviewStale) {
t.Fatalf("wrong source: %v", err)
}
}
func TestReviewCaptureStopCancels(t *testing.T) {
r := readyReviewRegistry(t)
n := registerNotice(t, r, ":1.2")
entered := make(chan struct{})
done := make(chan error, 1)
r.captureCall = func(ctx context.Context, _ string, _ dbus.ObjectPath, _ ReviewFocusNotice) (map[string]dbus.Variant, error) {
close(entered)
<-ctx.Done()
return nil, ctx.Err()
}
go func() { _, err := r.Capture(context.Background(), n.ID()); done <- err }()
<-entered
r.Stop(context.Background())
select {
case err := <-done:
if err == nil {
t.Fatal("cancelled capture accepted")
}
case <-time.After(time.Second):
t.Fatal("stop blocked")
}
}