128 lines
3.2 KiB
Go
128 lines
3.2 KiB
Go
package lsp
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"testing"
|
|
|
|
"github.com/microsoft/typescript-go/internal/bundled"
|
|
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
|
|
"github.com/microsoft/typescript-go/internal/project"
|
|
"github.com/microsoft/typescript-go/internal/vfs/vfstest"
|
|
)
|
|
|
|
type shutdownTestReader struct{}
|
|
|
|
func (shutdownTestReader) Read() (*lsproto.Message, error) { return nil, io.EOF }
|
|
|
|
type shutdownTestWriter struct{}
|
|
|
|
func (shutdownTestWriter) Write(*lsproto.Message) error { return nil }
|
|
|
|
// TestServerShutdownNoDeadlock verifies that operations after shutdown
|
|
// don't block.
|
|
func TestServerShutdownNoDeadlock(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if !bundled.Embedded {
|
|
t.Skip("bundled files are not embedded")
|
|
}
|
|
|
|
fs := bundled.WrapFS(vfstest.FromMap(map[string]string{
|
|
"/test/tsconfig.json": "{}",
|
|
"/test/index.ts": "const x = 1;",
|
|
}, false))
|
|
|
|
server := NewServer(&ServerOptions{
|
|
In: shutdownTestReader{},
|
|
Out: shutdownTestWriter{},
|
|
Err: io.Discard,
|
|
Cwd: "/test",
|
|
FS: fs,
|
|
DefaultLibraryPath: bundled.LibPath(),
|
|
})
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
server.backgroundCtx = ctx
|
|
|
|
// Start write loop to drain queue
|
|
writeLoopDone := make(chan struct{})
|
|
go func() {
|
|
_ = server.writeLoop(ctx)
|
|
close(writeLoopDone)
|
|
}()
|
|
|
|
// Create session with the server's lifecycle context
|
|
server.initStarted.Store(true)
|
|
server.session = project.NewSession(&project.SessionInit{
|
|
BackgroundCtx: ctx,
|
|
Options: &project.SessionOptions{
|
|
CurrentDirectory: "/test",
|
|
DefaultLibraryPath: bundled.LibPath(),
|
|
PositionEncoding: lsproto.PositionEncodingKindUTF8,
|
|
WatchEnabled: false,
|
|
LoggingEnabled: true,
|
|
},
|
|
FS: fs,
|
|
Logger: server.logger,
|
|
})
|
|
|
|
// Open a file to establish a project
|
|
server.session.DidOpenFile(ctx, "file:///test/index.ts", 1, "const x = 1;", lsproto.LanguageKindTypeScript)
|
|
server.session.WaitForBackgroundTasks()
|
|
|
|
// Shutdown (cancel context and wait for write loop to exit)
|
|
cancel()
|
|
<-writeLoopDone
|
|
|
|
// Trigger operations that would log (these should not block)
|
|
server.session.DidChangeFile(ctx, "file:///test/index.ts", 2, []lsproto.TextDocumentContentChangePartialOrWholeDocument{
|
|
{
|
|
WholeDocument: &lsproto.TextDocumentContentChangeWholeDocument{
|
|
Text: "const x = 2;",
|
|
},
|
|
},
|
|
})
|
|
_, _ = server.session.GetLanguageService(ctx, "file:///test/index.ts")
|
|
server.session.WaitForBackgroundTasks()
|
|
|
|
server.session.Close()
|
|
}
|
|
|
|
func TestServerOutgoingQueueDoesNotBlockWithoutWriter(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
server := NewServer(&ServerOptions{
|
|
In: shutdownTestReader{},
|
|
Out: shutdownTestWriter{},
|
|
Err: io.Discard,
|
|
Cwd: "/test",
|
|
})
|
|
server.backgroundCtx = t.Context()
|
|
|
|
msg := lsproto.WindowLogMessageInfo.NewNotificationMessage(&lsproto.LogMessageParams{
|
|
Type: lsproto.MessageTypeInfo,
|
|
Message: "queued",
|
|
}).Message()
|
|
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
for range 1000 {
|
|
if err := server.send(msg); err != nil {
|
|
done <- err
|
|
return
|
|
}
|
|
}
|
|
done <- nil
|
|
}()
|
|
|
|
select {
|
|
case err := <-done:
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
case <-t.Context().Done():
|
|
t.Fatal("sending outgoing messages blocked without a writer")
|
|
}
|
|
}
|