179 lines
4.1 KiB
Go
179 lines
4.1 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
package i3ipc
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func listener(t *testing.T) net.Listener {
|
|
t.Helper()
|
|
l, err := net.Listen("unix", filepath.Join(t.TempDir(), "ipc"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { l.Close() })
|
|
return l
|
|
}
|
|
func handshake(c net.Conn, initial string) error {
|
|
for _, response := range []string{`{"success":true}`, `[{"id":2,"name":"A","focused":true}]`, `{"id":1,"type":"root","nodes":[]}`} {
|
|
m, err := readMessage(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if initial != "" {
|
|
if err := writeMessage(c, modeEvent, []byte(initial)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := writeMessage(c, m.kind, []byte(response)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
func TestSessionBaselineCancellationAndReconnect(t *testing.T) {
|
|
l := listener(t)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
events := make(chan Event, 8)
|
|
done := make(chan struct{})
|
|
server := make(chan error, 1)
|
|
go func() {
|
|
defer close(done)
|
|
Run(ctx, func() string { return l.Addr().String() }, func(e Event) { events <- e })
|
|
}()
|
|
go func() {
|
|
for _, name := range []string{"personal", "default"} {
|
|
c, err := l.Accept()
|
|
if err != nil {
|
|
server <- err
|
|
return
|
|
}
|
|
if err = handshake(c, `{"change":"snapshot"}`); err == nil {
|
|
err = writeMessage(c, modeEvent, []byte(`{"change":"`+name+`"}`))
|
|
}
|
|
if name == "default" && err == nil {
|
|
var b [1]byte
|
|
_, _ = c.Read(b[:])
|
|
}
|
|
c.Close()
|
|
if err != nil {
|
|
server <- err
|
|
return
|
|
}
|
|
}
|
|
server <- nil
|
|
}()
|
|
for _, name := range []string{"personal", "default"} {
|
|
select {
|
|
case e := <-events:
|
|
if e.Name != name {
|
|
t.Fatal(e)
|
|
}
|
|
case <-time.After(4 * time.Second):
|
|
t.Fatal("missing event/reconnect")
|
|
}
|
|
}
|
|
cancel()
|
|
select {
|
|
case <-done:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("idle read did not cancel")
|
|
}
|
|
if err := <-server; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
select {
|
|
case e := <-events:
|
|
t.Fatalf("snapshot announced: %+v", e)
|
|
default:
|
|
}
|
|
}
|
|
func TestSubscriptionFailure(t *testing.T) {
|
|
l := listener(t)
|
|
go func() {
|
|
c, err := l.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer c.Close()
|
|
m, err := readMessage(c)
|
|
if err == nil {
|
|
_ = writeMessage(c, m.kind, []byte(`{"success":false}`))
|
|
}
|
|
}()
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
if err := session(ctx, l.Addr().String(), func(Event) { t.Error("unexpected event") }); err == nil {
|
|
t.Fatal("accepted rejection")
|
|
}
|
|
}
|
|
func TestUnavailableAndCanceledQuery(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
if err := session(ctx, "/nonexistent/i3", func(Event) {}); err == nil {
|
|
t.Fatal("unavailable socket accepted")
|
|
}
|
|
l := listener(t)
|
|
accepted := make(chan struct{})
|
|
go func() {
|
|
c, err := l.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer c.Close()
|
|
close(accepted)
|
|
var b [4096]byte
|
|
for {
|
|
if _, err := c.Read(b[:]); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
ctx, cancel = context.WithCancel(context.Background())
|
|
defer cancel()
|
|
done := make(chan error, 1)
|
|
go func() { done <- session(ctx, l.Addr().String(), func(Event) {}) }()
|
|
<-accepted
|
|
cancel()
|
|
select {
|
|
case <-done:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("query did not cancel")
|
|
}
|
|
}
|
|
func TestInterleavedEventsAndBacklog(t *testing.T) {
|
|
a, b := net.Pipe()
|
|
defer a.Close()
|
|
defer b.Close()
|
|
c := connection{Conn: a}
|
|
go func() {
|
|
m, err := readMessage(b)
|
|
if err != nil {
|
|
return
|
|
}
|
|
_ = writeMessage(b, modeEvent, []byte(`{"change":"panel"}`))
|
|
_ = writeMessage(b, m.kind, []byte(`{"success":true}`))
|
|
}()
|
|
var result struct{ Success bool }
|
|
if err := c.request(subscribe, nil, &result); err != nil || !result.Success || len(c.pending) != 1 {
|
|
t.Fatalf("%+v %v", c.pending, err)
|
|
}
|
|
c.pending = make([]message, maxPending)
|
|
go func() {
|
|
_, err := readMessage(b)
|
|
if err == nil {
|
|
_ = writeMessage(b, modeEvent, []byte(`{}`))
|
|
}
|
|
}()
|
|
if err := c.request(tree, nil, &result); err == nil {
|
|
t.Fatal("unbounded pending events")
|
|
}
|
|
}
|