187 lines
4.0 KiB
Go
187 lines
4.0 KiB
Go
|
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 can’t 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 can’t 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())
|
|||
|
}
|
|||
|
}
|