Reset master to testing content
This commit is contained in:
1 parent
0f2587b719
commit
42afa2a56c
1483 files changed
+145308
-938069
No files matched your search
@@ -0,0 +1,216 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package atspi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNativeSequentialAdapterLifecycle(t *testing.T) {
|
||||
if os.Getenv("CTHULHU_NATIVE_LIFECYCLE") != "1" {
|
||||
t.Skip("requires private Xvfb/AT-SPI session")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
fixture := exec.CommandContext(ctx, "/usr/bin/python3", "-c", `
|
||||
import gi
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk
|
||||
window = Gtk.Window(title="Adapter lifecycle probe")
|
||||
box = Gtk.Box()
|
||||
window.add(box)
|
||||
for name in ("First", "Second"):
|
||||
box.pack_start(Gtk.Button(label=name), True, True, 0)
|
||||
window.show_all()
|
||||
Gtk.main()
|
||||
`)
|
||||
fixture.Stderr = os.Stderr
|
||||
if err := fixture.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
_ = fixture.Process.Kill()
|
||||
_ = fixture.Wait()
|
||||
}()
|
||||
var stopped []*Adapter
|
||||
first := New()
|
||||
// Start a fresh adapter, then restart the original instance. Events must
|
||||
// reach only the current listener even though the provider stays alive.
|
||||
for cycle, adapter := range []*Adapter{first, New(), first, New()} {
|
||||
if err := adapter.Start(ctx); err != nil {
|
||||
t.Fatalf("cycle %d start: %v", cycle, err)
|
||||
}
|
||||
if cycle == 0 {
|
||||
other := New()
|
||||
if err := other.Start(ctx); err == nil {
|
||||
t.Fatal("concurrent adapter acquired the global AT-SPI loop")
|
||||
}
|
||||
}
|
||||
func() {
|
||||
defer func() {
|
||||
stopCtx, stop := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer stop()
|
||||
if err := adapter.Stop(stopCtx); err != nil {
|
||||
t.Errorf("cycle %d stop: %v", cycle, err)
|
||||
}
|
||||
}()
|
||||
var window ObjectReference
|
||||
for !window.Valid() && ctx.Err() == nil {
|
||||
tree, err := adapter.CaptureDesktop(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
window = findNamedReference(t, tree, "Adapter lifecycle probe")
|
||||
}
|
||||
if !window.Valid() {
|
||||
t.Fatal("fixture window missing")
|
||||
}
|
||||
for _, args := range [][]string{{"search", "--sync", "--onlyvisible", "--name", "^Adapter lifecycle probe$", "windowfocus"}, {"key", "Tab"}} {
|
||||
if output, err := exec.CommandContext(ctx, "xdotool", args...).CombinedOutput(); err != nil {
|
||||
t.Fatalf("xdotool: %v: %s", err, output)
|
||||
}
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case event := <-adapter.Events():
|
||||
if event.Type == FocusedStateEvent && event.Detail1 == 1 &&
|
||||
(event.Source.Name == "First" || event.Source.Name == "Second") {
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Fatal("focus event missing after adapter restart")
|
||||
}
|
||||
}
|
||||
}()
|
||||
if adapter.callbackData != nil || adapter.callbackHandle != 0 || adapter.eventListener != nil {
|
||||
t.Fatal("stopped adapter retained native callbacks")
|
||||
}
|
||||
for _, old := range stopped {
|
||||
if old == adapter {
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case event := <-old.Events():
|
||||
t.Fatalf("stopped adapter received %s", event.Type)
|
||||
default:
|
||||
}
|
||||
}
|
||||
for len(adapter.events) != 0 {
|
||||
<-adapter.events
|
||||
}
|
||||
stopped = append(stopped, adapter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNativeAdapterPendingCaptureShutdown(t *testing.T) {
|
||||
if os.Getenv("CTHULHU_NATIVE_LIFECYCLE") != "1" {
|
||||
t.Skip("requires private Xvfb/AT-SPI session")
|
||||
}
|
||||
// Initialize on the GLib owner without starting dispatch so a canceled
|
||||
// request deterministically leaves its idle callback pending at shutdown.
|
||||
func() {
|
||||
nativeAdapterOwner.Lock()
|
||||
defer nativeAdapterOwner.Unlock()
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
adapter := New()
|
||||
if err := adapter.initialize(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
adapter.ready.Store(true)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Millisecond)
|
||||
defer cancel()
|
||||
_, err := adapter.CaptureDesktop(ctx)
|
||||
if err != context.DeadlineExceeded || len(adapter.treeRequests) != 1 {
|
||||
t.Fatalf("pending capture = %d, %v", len(adapter.treeRequests), err)
|
||||
}
|
||||
adapter.ready.Store(false)
|
||||
if err := adapter.releaseNativeCallbacks(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(adapter.treeRequests) != 0 || adapter.callbackHandle != 0 {
|
||||
t.Fatal("shutdown retained pending capture")
|
||||
}
|
||||
}()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
adapter := New()
|
||||
if err := adapter.Start(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := adapter.Stop(ctx); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
if _, err := adapter.CaptureDesktop(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNativeCanceledAdapterStart(t *testing.T) {
|
||||
if os.Getenv("CTHULHU_NATIVE_LIFECYCLE") != "1" {
|
||||
t.Skip("requires private Xvfb/AT-SPI session")
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
adapter := New()
|
||||
if err := adapter.Start(ctx); err != context.Canceled {
|
||||
t.Fatalf("canceled start = %v", err)
|
||||
}
|
||||
if adapter.running.Load() || adapter.ready.Load() || adapter.callbackData != nil || adapter.eventListener != nil {
|
||||
t.Fatal("canceled startup retained native resources")
|
||||
}
|
||||
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if err := adapter.Start(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := adapter.Stop(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNativeAdapterStopRetry(t *testing.T) {
|
||||
if os.Getenv("CTHULHU_NATIVE_LIFECYCLE") != "1" {
|
||||
t.Skip("requires private Xvfb/AT-SPI session")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
old := New()
|
||||
if err := old.Start(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expired, expire := context.WithCancel(ctx)
|
||||
expire()
|
||||
// The quit request can finish after this caller times out. Retrying Stop
|
||||
// must not attach another global quit callback that kills a later adapter.
|
||||
_ = old.Stop(expired)
|
||||
select {
|
||||
case <-old.loopDone:
|
||||
case <-ctx.Done():
|
||||
t.Fatal("timed-out shutdown did not finish")
|
||||
}
|
||||
adapter := New()
|
||||
if err := adapter.Start(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := adapter.Stop(ctx); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
if err := old.Stop(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := adapter.CaptureDesktop(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user