WIP but basic test e2e test running

This commit is contained in:
Jacob Dufault 2017-05-02 23:45:10 -07:00
parent d049cc23c6
commit 1b4f377682
7 changed files with 150 additions and 34 deletions

1
.gitignore vendored
View File

@ -9,6 +9,7 @@ waf-*
.waf* .waf*
*.swp *.swp
*.sln *.sln
.vscode/.ropeproject
*.vcxproj *.vcxproj
*.vcxproj.user *.vcxproj.user
*.vcxproj.filters *.vcxproj.filters

View File

@ -791,6 +791,7 @@ void RegisterMessageTypes() {
MessageRegistry::instance()->Register<Ipc_CancelRequest>(); MessageRegistry::instance()->Register<Ipc_CancelRequest>();
MessageRegistry::instance()->Register<Ipc_InitializeRequest>(); MessageRegistry::instance()->Register<Ipc_InitializeRequest>();
MessageRegistry::instance()->Register<Ipc_InitializedNotification>(); MessageRegistry::instance()->Register<Ipc_InitializedNotification>();
MessageRegistry::instance()->Register<Ipc_Exit>();
MessageRegistry::instance()->Register<Ipc_TextDocumentDidOpen>(); MessageRegistry::instance()->Register<Ipc_TextDocumentDidOpen>();
MessageRegistry::instance()->Register<Ipc_TextDocumentDidChange>(); MessageRegistry::instance()->Register<Ipc_TextDocumentDidChange>();
MessageRegistry::instance()->Register<Ipc_TextDocumentDidClose>(); MessageRegistry::instance()->Register<Ipc_TextDocumentDidClose>();
@ -1330,6 +1331,11 @@ bool QueryDbMainLoop(
break; break;
} }
case IpcId::Exit: {
exit(0);
break;
}
case IpcId::CqueryFreshenIndex: { case IpcId::CqueryFreshenIndex: {
std::cerr << "Freshening " << project->entries.size() << " files" << std::endl; std::cerr << "Freshening " << project->entries.size() << " files" << std::endl;
project->ForAllFilteredFiles(config, [&](int i, const Project::Entry& entry) { project->ForAllFilteredFiles(config, [&](int i, const Project::Entry& entry) {
@ -1974,6 +1980,7 @@ void LanguageServerStdinLoop(IndexerConfig* config, std::unordered_map<IpcId, Ti
} }
case IpcId::Initialize: case IpcId::Initialize:
case IpcId::Exit:
case IpcId::TextDocumentDidOpen: case IpcId::TextDocumentDidOpen:
case IpcId::TextDocumentDidChange: case IpcId::TextDocumentDidChange:
case IpcId::TextDocumentDidClose: case IpcId::TextDocumentDidClose:

View File

@ -10,6 +10,8 @@ const char* IpcIdToString(IpcId id) {
return "initialize"; return "initialize";
case IpcId::Initialized: case IpcId::Initialized:
return "initialized"; return "initialized";
case IpcId::Exit:
return "exit";
case IpcId::TextDocumentDidOpen: case IpcId::TextDocumentDidOpen:
return "textDocument/didOpen"; return "textDocument/didOpen";
case IpcId::TextDocumentDidChange: case IpcId::TextDocumentDidChange:

View File

@ -10,6 +10,7 @@ enum class IpcId : int {
CancelRequest = 0, CancelRequest = 0,
Initialize, Initialize,
Initialized, Initialized,
Exit,
TextDocumentDidOpen, TextDocumentDidOpen,
TextDocumentDidChange, TextDocumentDidChange,
TextDocumentDidClose, TextDocumentDidClose,

View File

@ -33,6 +33,11 @@ std::unique_ptr<BaseIpcMessage> MessageRegistry::ReadMessageFromStdin() {
std::string line; std::string line;
std::getline(std::cin, line); std::getline(std::cin, line);
// No content; end of stdin.
if (line.empty())
return nullptr;
// std::cin >> line; // std::cin >> line;
// std::cerr << "Read line " << line; // std::cerr << "Read line " << line;
@ -70,9 +75,10 @@ std::unique_ptr<BaseIpcMessage> MessageRegistry::ReadMessageFromStdin() {
} }
std::unique_ptr<BaseIpcMessage> MessageRegistry::Parse(Reader& visitor) { std::unique_ptr<BaseIpcMessage> MessageRegistry::Parse(Reader& visitor) {
std::string jsonrpc = visitor["jsonrpc"].GetString(); if (!visitor.HasMember("jsonrpc") || std::string(visitor["jsonrpc"].GetString()) != "2.0") {
if (jsonrpc != "2.0") std::cerr << "Bad or missing jsonrpc version" << std::endl;
exit(1); exit(1);
}
std::string method; std::string method;
ReflectMember(visitor, "method", method); ReflectMember(visitor, "method", method);

View File

@ -1052,7 +1052,10 @@ MAKE_REFLECT_STRUCT(Ipc_InitializedNotification, id);
struct Ipc_Exit : public IpcMessage<Ipc_Exit> {
static const IpcId kIpcId = IpcId::Exit;
};
MAKE_REFLECT_EMPTY_STRUCT(Ipc_Exit);

View File

@ -1,25 +1,7 @@
import json
import shlex import shlex
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
# We write test files in python. The test runner collects all python files in
# the directory and executes them. The test function just creates a test object
# which specifies expected stdin/stdout.
#
# Test functions are automatically discovered; they just need to be in the
# global environment and start with `Test_`.
class TestBuilder:
def WithFile(self, filename, contents):
"""
Writes the file contents to disk so that the language server can access it.
"""
pass
def Send(self, stdin):
"""
Send the given message to the language server.
"""
# Content-Length: ...\r\n # Content-Length: ...\r\n
# \r\n # \r\n
# { # {
@ -31,21 +13,138 @@ class TestBuilder:
# } # }
# } # }
# We write test files in python. The test runner collects all python files in
# the directory and executes them. The test function just creates a test object
# which specifies expected stdin/stdout.
#
# Test functions are automatically discovered; they just need to be in the
# global environment and start with `Test_`.
class TestBuilder:
def __init__(self):
self.files = []
self.sent = []
self.received = []
def WithFile(self, filename, contents):
"""
Writes the file contents to disk so that the language server can access it.
"""
self.files.append((filename, contents))
return self
def Send(self, stdin):
"""
Send the given message to the language server.
"""
stdin['jsonrpc'] = '2.0'
self.sent.append(stdin)
return self
def Expect(self, stdout): def Expect(self, stdout):
""" """
Expect a message from the language server. Expect a message from the language server.
""" """
pass self.received.append(stdout)
return self
def SetupCommonInit(): def SetupCommonInit(self):
""" """
Add initialize/initialized messages. Add initialize/initialized messages.
""" """
pass self.Send({
'id': 0,
'method': 'initialize',
'params': {
'processId': 123,
'rootPath': 'cquery',
'capabilities': {},
'trace': 'off'
}
})
self.Expect({
'id': 0,
'method': 'initialized',
'result': {}
})
return self
def _ExecuteTest(name, func):
"""
Executes a specific test.
|func| must return a TestBuilder object.
"""
test_builder = func()
if not isinstance(test_builder, TestBuilder):
raise Exception('%s does not return a TestBuilder instance' % name)
test_builder.Send({ 'method': 'exit' })
# Possible test runner implementation
cmd = "x64/Debug/indexer.exe --language-server"
process = Popen(shlex.split(cmd), stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True)
stdin = ''
for message in test_builder.sent:
payload = json.dumps(message)
wrapped = 'Content-Length: %s\r\n\r\n%s' % (len(payload), payload)
stdin += wrapped
print('## %s ##' % name)
print('== STDIN ==')
print(stdin)
(stdout, stderr) = process.communicate(stdin)
if stdout:
print('== STDOUT ==')
print(stdout)
if stderr:
print('== STDERR ==')
print(stderr)
# TODO: Actually verify stdout.
exit_code = process.wait()
def _DiscoverTests():
"""
Discover and return all tests.
"""
for name, value in globals().items():
if not callable(value):
continue
if not name.startswith('Test_'):
continue
yield (name, value)
def _RunTests():
"""
Executes all tests.
"""
for name, func in _DiscoverTests():
print('Running test function %s' % name)
_ExecuteTest(name, func)
def Test_Outline():
return TestBuilder()
class lsSymbolKind:
Function = 1
def lsSymbolInfo(name, position, kind):
return {
'name': name,
'position': position,
'kind': kind
}
def Test_Init():
return (TestBuilder()
.SetupCommonInit()
)
def _Test_Outline():
return (TestBuilder()
.SetupCommonInit() .SetupCommonInit()
.WithFile("foo.cc", .WithFile("foo.cc",
""" """
@ -58,15 +157,12 @@ def Test_Outline():
'params': {} 'params': {}
}) })
.Expect({ .Expect({
'id': 1 'id': 1,
'result': [ 'result': [
lsSymbolInfo('void main()', (1, 1), lsSymbolKind.Function) lsSymbolInfo('void main()', (1, 1), lsSymbolKind.Function)
] ]
}) }))
# Possible test runner implementation if __name__ == '__main__':
# cmd = "x64/Release/indexer.exe --language-server" _RunTests()
# process = Popen(shlex.split(cmd), stdin=PIPE, stdout=PIPE)
# process.communicate('{}')
# exit_code = process.wait()