go-i3/subscribe_test.go

187 lines
4.0 KiB
Go
Raw Permalink Normal View History

2023-10-22 16:19:10 +02:00
package i3
import (
"context"
"fmt"
"os"
"os/exec"
"sync"
"testing"
"time"
"golang.org/x/sync/errgroup"
)
// TestSubscribeSubprocess runs in a process which has been started with
// DISPLAY= pointing to an Xvfb instance with i3 -c testdata/i3.config running.
func TestSubscribeSubprocess(t *testing.T) {
if os.Getenv("GO_WANT_XVFB") != "1" {
t.Skip("parent process")
}
// TODO(https://github.com/i3/i3/issues/2988): as soon as we are targeting
// i3 4.15, use SendTick to eliminate race conditions in this test.
t.Run("subscribe", func(t *testing.T) {
var eg errgroup.Group
ws := Subscribe(WorkspaceEventType)
received := make(chan Event)
eg.Go(func() error {
defer close(received)
if ws.Next() {
received <- ws.Event()
}
return ws.Close()
})
// As we cant know when EventReceiver.Next() actually subscribes, we
// just continuously switch workspaces.
ctx, canc := context.WithCancel(context.Background())
defer canc()
go func() {
cnt := 2
for ctx.Err() == nil {
RunCommand(fmt.Sprintf("workspace %d", cnt))
cnt++
time.Sleep(10 * time.Millisecond)
}
}()
select {
case <-received:
case <-time.After(5 * time.Second):
t.Fatalf("timeout waiting for an event from i3")
}
if err := eg.Wait(); err != nil {
t.Fatal(err)
}
})
t.Run("subscribeParallel", func(t *testing.T) {
var mu sync.Mutex
received := make(map[string]int)
recv1 := Subscribe(WorkspaceEventType)
go func() {
for recv1.Next() {
ev := recv1.Event().(*WorkspaceEvent)
if ev.Change == "init" {
mu.Lock()
received[ev.Current.Name]++
mu.Unlock()
}
}
}()
recv2 := Subscribe(WorkspaceEventType)
go func() {
for recv2.Next() {
ev := recv2.Event().(*WorkspaceEvent)
if ev.Change == "init" {
mu.Lock()
received[ev.Current.Name]++
mu.Unlock()
}
}
}()
cnt := 2
start := time.Now()
for time.Since(start) < 5*time.Second {
mu.Lock()
done := received[fmt.Sprintf("%d", cnt-1)] == 2
mu.Unlock()
if done {
return // success
}
if _, err := RunCommand(fmt.Sprintf("workspace %d", cnt)); err != nil {
t.Fatal(err)
}
cnt++
time.Sleep(10 * time.Millisecond)
}
})
t.Run("subscribeMultiple", func(t *testing.T) {
var eg errgroup.Group
ws := Subscribe(WorkspaceEventType, ModeEventType)
received := make(chan struct{})
eg.Go(func() error {
defer close(received)
defer ws.Close()
seen := map[EventType]bool{
WorkspaceEventType: false,
ModeEventType: false,
}
Outer:
for ws.Next() {
switch ws.Event().(type) {
case *WorkspaceEvent:
seen[WorkspaceEventType] = true
case *ModeEvent:
seen[ModeEventType] = true
}
for _, seen := range seen {
if !seen {
continue Outer
}
}
return nil
}
return ws.Close()
})
// As we cant know when EventReceiver.Next() actually subscribes, we
// just continuously switch workspaces and modes.
ctx, canc := context.WithCancel(context.Background())
defer canc()
go func() {
modes := []string{"default", "conf"}
cnt := 2
for ctx.Err() == nil {
RunCommand(fmt.Sprintf("workspace %d", cnt))
cnt++
RunCommand(fmt.Sprintf("mode %s", modes[cnt%len(modes)]))
time.Sleep(10 * time.Millisecond)
}
}()
select {
case <-received:
case <-time.After(5 * time.Second):
t.Fatalf("timeout waiting for an event from i3")
}
if err := eg.Wait(); err != nil {
t.Fatal(err)
}
})
}
func TestSubscribe(t *testing.T) {
t.Parallel()
ctx, canc := context.WithCancel(context.Background())
defer canc()
_, DISPLAY, err := launchXvfb(ctx)
if err != nil {
t.Fatal(err)
}
cleanup, err := launchI3(ctx, DISPLAY, "")
if err != nil {
t.Fatal(err)
}
defer cleanup()
cmd := exec.Command(os.Args[0], "-test.run=TestSubscribeSubprocess", "-test.v")
cmd.Env = []string{
"GO_WANT_XVFB=1",
"DISPLAY=" + DISPLAY,
"PATH=" + os.Getenv("PATH"),
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
t.Fatal(err.Error())
}
}