Skip to content

Commit

Permalink
Add shutdown tests, stardog-specific grammar test for sparql, adjust …
Browse files Browse the repository at this point in the history
…sparql initialization logic to align with protocol
  • Loading branch information
Ty Soehngen committed Dec 21, 2018
1 parent a81f3b3 commit c60fa00
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 37 deletions.
10 changes: 6 additions & 4 deletions packages/sms-language-server/__tests__/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { join, resolve } from 'path';
import {
testInitHandshakeForAllTransports,
getStdioConnection,
testShutdown,
} from '../../../utils/testUtils';
import {
InitializeRequest,
Expand All @@ -15,11 +16,12 @@ import {
} from 'vscode-languageserver-protocol';
import { ChildProcess } from 'child_process';

const server = resolve(join(__dirname, '..', 'src', 'cli.ts'));
const pathToServer = resolve(join(__dirname, '..', 'src', 'cli.ts'));

testInitHandshakeForAllTransports(server);
testInitHandshakeForAllTransports(pathToServer);
testShutdown(pathToServer);

describe('sms capabilities', () => {
describe('sms language server', () => {
let cp: ChildProcess;
let connection: ProtocolConnection;
const textDocument = TextDocumentItem.create(
Expand All @@ -29,7 +31,7 @@ describe('sms capabilities', () => {
'mapping <urn:mapping> from sql { select timeztamp'
);
beforeAll(async () => {
const processAndConn = getStdioConnection(server);
const processAndConn = getStdioConnection(pathToServer);
connection = processAndConn.connection;
cp = processAndConn.child_process;

Expand Down
100 changes: 81 additions & 19 deletions packages/sparql-language-server/__tests__/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { join, resolve } from 'path';
import {
testInitHandshakeForAllTransports,
getStdioConnection,
testShutdown,
} from '../../../utils/testUtils';
import {
InitializeRequest,
Expand All @@ -13,37 +14,47 @@ import {
HoverRequest,
Position,
CompletionRequest,
CompletionItem,
} from 'vscode-languageserver-protocol';
import { ChildProcess } from 'child_process';

const server = resolve(join(__dirname, '..', 'src', 'cli.ts'));
const pathToServer = resolve(join(__dirname, '..', 'src', 'cli.ts'));
const initParams = {
capabilities: {},
processId: process.pid,
rootUri: '/',
workspaceFolders: null,
};

testInitHandshakeForAllTransports(server);
const selectTextDoc = TextDocumentItem.create(
'/foo.rq',
'sparql',
1,
'select * { ?a ?b ?c '
);
const pathsTextDoc = TextDocumentItem.create(
'/paths.rq',
'sparql',
1,
'paths st'
);

describe('sparql capabilities', () => {
testInitHandshakeForAllTransports(pathToServer);
testShutdown(pathToServer);

describe('sparql language server', () => {
let cp: ChildProcess;
let connection: ProtocolConnection;
const textDocument = TextDocumentItem.create(
'/foo.rq',
'sparql',
1,
'select * { ?a ?b ?c '
);
beforeAll(async () => {
const processAndConn = getStdioConnection(server);
const processAndConn = getStdioConnection(pathToServer);
connection = processAndConn.connection;
cp = processAndConn.child_process;

connection.listen();
await connection.sendRequest(InitializeRequest.type, {
capabilities: {},
processId: process.pid,
rootUri: '/',
workspaceFolders: null,
});
await connection.sendRequest(InitializeRequest.type, initParams);
await connection.sendNotification(InitializedNotification.type);
await connection.sendNotification(DidOpenTextDocumentNotification.type, {
textDocument,
textDocument: selectTextDoc,
});
});
afterAll(() => {
Expand All @@ -68,20 +79,21 @@ describe('sparql capabilities', () => {
},
},
]);
connection.onNotification(PublishDiagnosticsNotification.type, () => {});
done();
});
});
it('publishes hover messages', async (done) => {
const res = await connection.sendRequest(HoverRequest.type, {
textDocument,
textDocument: selectTextDoc,
position: Position.create(0, 3),
});
expect(res.contents).toBe('```\nSelectClause\n```');
done();
});
it('publishes autocompletion items', async (done) => {
const res = await connection.sendRequest(CompletionRequest.type, {
textDocument,
textDocument: selectTextDoc,
position: Position.create(0, 3),
});
expect(res).toHaveLength(25);
Expand All @@ -105,4 +117,54 @@ describe('sparql capabilities', () => {
});
done();
});
it('handles stardog-specific grammar', async (done) => {
await connection.sendNotification(DidOpenTextDocumentNotification.type, {
textDocument: pathsTextDoc,
});
const res = await connection.sendRequest(CompletionRequest.type, {
textDocument: pathsTextDoc,
position: Position.create(0, 5),
});
expect(
(res as CompletionItem[]).some((item) => item.label === 'paths shortest')
).toBe(true);
done();
});
});

describe('w3 sparql language server', () => {
let cp: ChildProcess;
let connection: ProtocolConnection;
beforeAll(async () => {
const processAndConn = getStdioConnection(pathToServer);
cp = processAndConn.child_process;

connection = processAndConn.connection;
connection.listen();

await connection.sendRequest(InitializeRequest.type, {
...initParams,
initializationOptions: {
grammar: 'w3',
},
});
await connection.sendNotification(InitializedNotification.type);
await connection.sendNotification(DidOpenTextDocumentNotification.type, {
textDocument: pathsTextDoc,
});
});
afterAll(() => cp.kill());
it('initializes a W3SparqlParser', async (done) => {
await connection.sendNotification(DidOpenTextDocumentNotification.type, {
textDocument: pathsTextDoc,
});
const res = await connection.sendRequest(CompletionRequest.type, {
textDocument: pathsTextDoc,
position: Position.create(0, 5),
});
expect(
(res as CompletionItem[]).some((item) => item.label === 'paths shortest')
).toBe(false);
done();
});
});
49 changes: 41 additions & 8 deletions packages/sparql-language-server/src/SparqlLanguageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ import {
CompletionItem,
CompletionItemKind,
TextEdit,
InitializeParams,
ResponseError,
ErrorCodes,
StarRequestHandler,
} from 'vscode-languageserver';
import {
StardogSparqlParser,
W3SpecSparqlParser,
traverse,
isCstNode,
sparqlKeywords,
Expand Down Expand Up @@ -41,9 +46,7 @@ const ARBITRARILY_LARGE_NUMBER = 100000000000000;
@autoBindMethods
export class SparqlLanguageServer {
protected readonly documents = new TextDocuments();
private parser = new StardogSparqlParser({
config: { errorMessageProvider },
});
private parser: StardogSparqlParser | W3SpecSparqlParser;
private latestTokens: IToken[];
private latestCst: CstNode;

Expand All @@ -54,20 +57,50 @@ export class SparqlLanguageServer {
constructor(protected readonly connection: IConnection) {
this.documents.listen(this.connection);
this.documents.onDidChangeContent(this.handleContentChange);
this.connection.onRequest(this.handleUninitializedRequest);
this.connection.onInitialize(this.handleInitialization);
}

start() {
this.connection.listen();
}

handleUninitializedRequest: StarRequestHandler = () =>
new ResponseError(
ErrorCodes.ServerNotInitialized,
'Expecting "initialize" request from client.'
);
handleUnhandledRequest: StarRequestHandler = (method) =>
new ResponseError(
ErrorCodes.MethodNotFound,
`Request: "${method}" is not handled by the server.`
);

handleInitialization(params: InitializeParams): InitializeResult {
// Setting this StarHandler is intended to overwrite the handler set
// in the constructor, which always responded with a "Server not initialized"
// error. Here, we're initialized, so we replace with an "Unhandled method"
this.connection.onRequest(this.handleUnhandledRequest);
this.connection.onCompletion(this.handleCompletion);
this.connection.onHover(this.handleHover);
this.connection.onNotification(
LSPExtensionMethod.DID_UPDATE_COMPLETION_DATA,
this.handleUpdateCompletionData
);
}

start() {
this.connection.listen();
}
if (
params.initializationOptions &&
params.initializationOptions.grammar === 'w3'
) {
this.parser = new W3SpecSparqlParser({
config: { errorMessageProvider },
});
} else {
this.parser = new StardogSparqlParser({
config: { errorMessageProvider },
});
}

handleInitialization(_params): InitializeResult {
return {
capabilities: {
// Tell the client that the server works in NONE text document sync mode
Expand Down
10 changes: 6 additions & 4 deletions packages/turtle-language-server/__tests__/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { join, resolve } from 'path';
import {
testInitHandshakeForAllTransports,
getStdioConnection,
testShutdown,
} from '../../../utils/testUtils';
import {
InitializeRequest,
Expand All @@ -15,11 +16,12 @@ import {
} from 'vscode-languageserver-protocol';
import { ChildProcess } from 'child_process';

const server = resolve(join(__dirname, '..', 'src', 'cli.ts'));
const pathToServer = resolve(join(__dirname, '..', 'src', 'cli.ts'));

testInitHandshakeForAllTransports(server);
testInitHandshakeForAllTransports(pathToServer);
testShutdown(pathToServer);

describe('turtle capabilities', () => {
describe('turtle language server', () => {
let cp: ChildProcess;
let connection: ProtocolConnection;
const textDocument = TextDocumentItem.create(
Expand All @@ -29,7 +31,7 @@ describe('turtle capabilities', () => {
'<someIri> <anotherIri> <partOfAnIri'
);
beforeAll(async () => {
const processAndConn = getStdioConnection(server);
const processAndConn = getStdioConnection(pathToServer);
connection = processAndConn.connection;
cp = processAndConn.child_process;

Expand Down
39 changes: 37 additions & 2 deletions utils/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
InitializeRequest,
createProtocolConnection,
ProtocolConnection,
ShutdownRequest,
ExitNotification,
} from 'vscode-languageserver-protocol';
import * as portscanner from 'portscanner';

Expand All @@ -25,7 +27,12 @@ export const getStdioConnection = (pathToServer: string) => {
const connection = createProtocolConnection(
new StreamMessageReader(cp.stdout),
new StreamMessageWriter(cp.stdin),
null
{
log: console.log,
info: console.info,
error: console.error,
warn: console.warn,
}
);
return { child_process: cp, connection };
};
Expand All @@ -49,7 +56,7 @@ export const testInitHandshake = (
};

export const testInitHandshakeForAllTransports = (pathToServer: string) =>
describe(pathToServer, () => {
describe('initialization handshake', () => {
let cp: ChildProcess;
afterEach(() => cp.kill());
it('performs LSP initialization via stdio', (done) => {
Expand Down Expand Up @@ -120,3 +127,31 @@ export const testInitHandshakeForAllTransports = (pathToServer: string) =>
);
});
});

export const testShutdown = (pathToServer) => {
describe('shutdown', () => {
let cp: ChildProcess;
afterEach(() => cp && cp.kill());
it('exits with code 1 if an exit notification is received before a shutdown request', async (done) => {
const { child_process, connection } = getStdioConnection(pathToServer);
connection.listen();
cp = child_process;
cp.on('exit', (code, _signal) => {
expect(code).toBe(1);
done();
});
connection.sendNotification(ExitNotification.type);
});
it('shuts down on shutdown request', async (done) => {
const { child_process, connection } = getStdioConnection(pathToServer);
connection.listen();
cp = child_process;
cp.on('close', (code, _signal) => {
expect(code).toBe(0);
done();
});
await connection.sendRequest(ShutdownRequest.type);
connection.sendNotification(ExitNotification.type);
});
});
};

0 comments on commit c60fa00

Please sign in to comment.