turbo-editors/turbo-corepublic Fork 0
v0.9.0
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

🛟 Updated. 28d5985 · on v0.9.0 · k33g · 17h ago
fakelsp_test.go · 157 lines · 3.9 KBGo Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package app

import (
	"bufio"
	"encoding/json"
	"net"
	"sync"
	"testing"
	"time"

	"codeberg.org/turbo-editors/turbo-core/lsp"
)

// fakeLSP is a language server living in this process, at the far end of an
// in-memory pipe.
//
// The `lsp` package has one of these too, for testing the client. This one
// exists to test what the *editor* says to a server, which is a different
// question and belongs here.
type fakeLSP struct {
	stream net.Conn
	reader *bufio.Reader

	mu      sync.Mutex
	seen    map[string]int
	answers map[string]json.RawMessage
}

// frame is one JSON-RPC message, reduced to what this fake needs to look at.
type frame struct {
	JSONRPC string          `json:"jsonrpc"`
	ID      json.RawMessage `json:"id,omitempty"`
	Method  string          `json:"method,omitempty"`
	Result  json.RawMessage `json:"result,omitempty"`
}

// newFakeLanguage returns an initialised client wired to a fake server. The
// client is not attached to the app yet — connectLanguage does that, so a test
// can check what happens on either side of the moment it becomes ready.
func newFakeLanguage(t *testing.T, _ *App) (*lsp.Client, *fakeLSP) {
	t.Helper()

	clientSide, serverSide := net.Pipe()
	server := &fakeLSP{
		stream:  serverSide,
		reader:  bufio.NewReader(serverSide),
		seen:    map[string]int{},
		answers: map[string]json.RawMessage{},
	}

	client := lsp.NewClient(clientSide, t.TempDir(), "Turbo Test")
	go client.Run() //nolint:errcheck // failures surface through the client
	go server.serve()

	t.Cleanup(func() {
		clientSide.Close()
		serverSide.Close()
	})

	if err := client.Initialize(t.Context()); err != nil {
		t.Fatalf("Initialize() error = %v", err)
	}
	return client, server
}

// connectLanguage makes the app talk to a client, as a successful start-up
// would.
func connectLanguage(a *App, client *lsp.Client) {
	a.language.attach(client)
}

// serve answers every request with null and records every method it sees.
func (s *fakeLSP) serve() {
	for {
		body, err := lsp.ReadMessage(s.reader)
		if err != nil {
			return
		}

		var msg frame
		if err := json.Unmarshal(body, &msg); err != nil {
			return
		}
		s.record(msg.Method)

		if len(msg.ID) == 0 || msg.Method == "" {
			continue // a notification, or an answer to something we asked
		}
		reply, err := json.Marshal(frame{JSONRPC: "2.0", ID: msg.ID, Result: s.answerTo(msg.Method)})
		if err != nil {
			return
		}
		if err := lsp.WriteMessage(s.stream, reply); err != nil {
			return
		}
	}
}

// answerTo returns what this server has been told to answer a method with,
// and null when it has been told nothing — which is what every request got
// before results could be set, and what most tests still want.
func (s *fakeLSP) answerTo(method string) json.RawMessage {
	s.mu.Lock()
	defer s.mu.Unlock()
	if answer, set := s.answers[method]; set {
		return answer
	}
	return json.RawMessage("null")
}

// setAnswer tells the server what to reply to one method.
func (s *fakeLSP) setAnswer(t *testing.T, method string, result any) {
	t.Helper()

	encoded, err := json.Marshal(result)
	if err != nil {
		t.Fatalf("cannot encode the answer for %s: %v", method, err)
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	s.answers[method] = encoded
}

// record counts one method.
func (s *fakeLSP) record(method string) {
	if method == "" {
		return
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	s.seen[method]++
}

// methodCount returns how many times the client has sent a method.
func (s *fakeLSP) methodCount(method string) int {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.seen[method]
}

// waitForMethod blocks until the server has seen a method, so a test never
// races the notification it is about to assert on.
func waitForMethod(t *testing.T, server *fakeLSP, method string) {
	t.Helper()

	deadline := time.After(2 * time.Second)
	for {
		if server.methodCount(method) > 0 {
			return
		}
		select {
		case <-deadline:
			t.Fatalf("the server never saw %q", method)
		case <-time.After(time.Millisecond):
		}
	}
}