From 1b4f377682ace4aeba3f2c47c049082d8cbfec15 Mon Sep 17 00:00:00 2001 From: Jacob Dufault Date: Tue, 2 May 2017 23:45:10 -0700 Subject: [PATCH] WIP but basic test e2e test running --- .gitignore | 1 + src/command_line.cc | 7 ++ src/ipc.cc | 2 + src/ipc.h | 1 + src/language_server_api.cc | 10 ++- src/language_server_api.h | 5 +- test_runner_e2e.py | 158 +++++++++++++++++++++++++++++-------- 7 files changed, 150 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index 849a2624..1b02f3f9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ waf-* .waf* *.swp *.sln +.vscode/.ropeproject *.vcxproj *.vcxproj.user *.vcxproj.filters diff --git a/src/command_line.cc b/src/command_line.cc index 71f340f0..81bf489b 100644 --- a/src/command_line.cc +++ b/src/command_line.cc @@ -791,6 +791,7 @@ void RegisterMessageTypes() { MessageRegistry::instance()->Register(); MessageRegistry::instance()->Register(); MessageRegistry::instance()->Register(); + MessageRegistry::instance()->Register(); MessageRegistry::instance()->Register(); MessageRegistry::instance()->Register(); MessageRegistry::instance()->Register(); @@ -1330,6 +1331,11 @@ bool QueryDbMainLoop( break; } + case IpcId::Exit: { + exit(0); + break; + } + case IpcId::CqueryFreshenIndex: { std::cerr << "Freshening " << project->entries.size() << " files" << std::endl; project->ForAllFilteredFiles(config, [&](int i, const Project::Entry& entry) { @@ -1974,6 +1980,7 @@ void LanguageServerStdinLoop(IndexerConfig* config, std::unordered_map MessageRegistry::ReadMessageFromStdin() { std::string line; std::getline(std::cin, line); + + // No content; end of stdin. + if (line.empty()) + return nullptr; + // std::cin >> line; // std::cerr << "Read line " << line; @@ -70,9 +75,10 @@ std::unique_ptr MessageRegistry::ReadMessageFromStdin() { } std::unique_ptr MessageRegistry::Parse(Reader& visitor) { - std::string jsonrpc = visitor["jsonrpc"].GetString(); - if (jsonrpc != "2.0") + if (!visitor.HasMember("jsonrpc") || std::string(visitor["jsonrpc"].GetString()) != "2.0") { + std::cerr << "Bad or missing jsonrpc version" << std::endl; exit(1); + } std::string method; ReflectMember(visitor, "method", method); diff --git a/src/language_server_api.h b/src/language_server_api.h index 367f6cbc..aa50b2c4 100644 --- a/src/language_server_api.h +++ b/src/language_server_api.h @@ -1052,7 +1052,10 @@ MAKE_REFLECT_STRUCT(Ipc_InitializedNotification, id); - +struct Ipc_Exit : public IpcMessage { + static const IpcId kIpcId = IpcId::Exit; +}; +MAKE_REFLECT_EMPTY_STRUCT(Ipc_Exit); diff --git a/test_runner_e2e.py b/test_runner_e2e.py index 5ea480ec..b1c350d0 100644 --- a/test_runner_e2e.py +++ b/test_runner_e2e.py @@ -1,25 +1,7 @@ +import json import shlex 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 # \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): """ Expect a message from the language server. """ - pass + self.received.append(stdout) + return self - def SetupCommonInit(): + def SetupCommonInit(self): """ 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() .WithFile("foo.cc", """ @@ -58,15 +157,12 @@ def Test_Outline(): 'params': {} }) .Expect({ - 'id': 1 + 'id': 1, 'result': [ lsSymbolInfo('void main()', (1, 1), lsSymbolKind.Function) ] - }) + })) -# Possible test runner implementation -# cmd = "x64/Release/indexer.exe --language-server" -# process = Popen(shlex.split(cmd), stdin=PIPE, stdout=PIPE) -# process.communicate('{}') -# exit_code = process.wait() \ No newline at end of file +if __name__ == '__main__': + _RunTests() \ No newline at end of file