From 6f22c5ee649871635243d35ad697d5cfeda215c3 Mon Sep 17 00:00:00 2001 From: Ce Gao Date: Sat, 11 Feb 2017 10:24:23 +0800 Subject: [PATCH 01/13] client: Add a dev denpendency and a test case --- vscode-client/.gitignore | 4 +++- vscode-client/package.json | 8 +++++--- vscode-client/test/extension.test.js | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 vscode-client/test/extension.test.js diff --git a/vscode-client/.gitignore b/vscode-client/.gitignore index 3dc9acd..faeedd6 100644 --- a/vscode-client/.gitignore +++ b/vscode-client/.gitignore @@ -1,4 +1,6 @@ out server node_modules -.vscode-dev \ No newline at end of file +.vscode-dev +.vscode-test + diff --git a/vscode-client/package.json b/vscode-client/package.json index 8fbd51c..b8bfbc6 100644 --- a/vscode-client/package.json +++ b/vscode-client/package.json @@ -20,13 +20,15 @@ "vscode:prepublish": "cp -r ../coala-langserver.sh ../coala-langserver.py ../coala_langserver ./out && node ./node_modules/vscode/bin/compile", "compile": "python3 ../setup.py install && node ./node_modules/vscode/bin/compile -watch -p ./", "postinstall": "node ./node_modules/vscode/bin/install", - "vscode": "npm run vscode:prepublish && VSCODE=$(which code-insiders || which code || echo echo ERROR: neither the code nor code-insiders vscode executable is installed); USER=dummy-dont-share-vscode-instance $VSCODE --user-data-dir=$PWD/.vscode-dev/user-data --extensionHomePath=$PWD/.vscode-dev/extensions --extensionDevelopmentPath=$PWD $*" + "vscode": "npm run vscode:prepublish && VSCODE=$(which code-insiders || which code || echo echo ERROR: neither the code nor code-insiders vscode executable is installed); USER=dummy-dont-share-vscode-instance $VSCODE --user-data-dir=$PWD/.vscode-dev/user-data --extensionHomePath=$PWD/.vscode-dev/extensions --extensionDevelopmentPath=$PWD $*", + "test": "node ./node_modules/vscode/bin/test" }, "devDependencies": { "typescript": "^1.8.9", - "vscode": "^0.11.0" + "vscode": "^0.11.0", + "vsce": "^1.18.0" }, "dependencies": { "vscode-languageclient": "^2.4.2-next.12" } -} \ No newline at end of file +} diff --git a/vscode-client/test/extension.test.js b/vscode-client/test/extension.test.js new file mode 100644 index 0000000..a98fd02 --- /dev/null +++ b/vscode-client/test/extension.test.js @@ -0,0 +1,17 @@ +// The module 'assert' provides assertion methods from node +import * as assert from 'assert'; + +// You can import and use all API from the 'vscode' module +// as well as import your extension to test it +// import * as vscode from 'vscode'; +// import * as myExtension from '../extension'; + +// Defines a Mocha test suite to group tests of similar kind together +suite('Extension Tests', () => { + + // Defines a Mocha unit test + test('Something 1', () => { + assert.equal(-1, [1, 2, 3].indexOf(5)); + assert.equal(-1, [1, 2, 3].indexOf(0)); + }); +}); From 4594fca335d99984e25bcbb7a03cbb168895a0be Mon Sep 17 00:00:00 2001 From: Ce Gao Date: Sat, 11 Feb 2017 10:25:10 +0800 Subject: [PATCH 02/13] server: Add server BDD test cases --- coala_langserver/uri.py | 1 + tests/server.features/steps/uri_steps.py | 45 ++++++++++++++++++++++++ tests/server.features/uri.feature | 17 +++++++++ 3 files changed, 63 insertions(+) create mode 100644 tests/server.features/steps/uri_steps.py create mode 100644 tests/server.features/uri.feature diff --git a/coala_langserver/uri.py b/coala_langserver/uri.py index 12c2f51..4a3e14c 100644 --- a/coala_langserver/uri.py +++ b/coala_langserver/uri.py @@ -10,4 +10,5 @@ def path_from_uri(uri): def dir_from_uri(uri): + """Get the directory name from the path.""" return os.path.dirname(path_from_uri(uri)) diff --git a/tests/server.features/steps/uri_steps.py b/tests/server.features/steps/uri_steps.py new file mode 100644 index 0000000..fdb2d74 --- /dev/null +++ b/tests/server.features/steps/uri_steps.py @@ -0,0 +1,45 @@ +# -*- coding: UTF-8 -*- + +# @mark.steps +# ---------------------------------------------------------------------------- +# STEPS: +# ---------------------------------------------------------------------------- +from behave import given, when, then +from coala_langserver.uri import path_from_uri, dir_from_uri + + +@given('There is a string with "file://"') +def step_impl(context): + context.str = 'file://Users' + + +@when('I pass the string with the prefix to path_from_uri') +def step_impl(context): + context.path = path_from_uri(context.str) + + +@given('There is a string without "file://"') +def step_impl(context): + context.str = '/Users' + + +@when('I pass the string without the prefix to path_from_uri') +def step_impl(context): + context.path = path_from_uri(context.str) + + +@then('it should return a string without "file://"') +def step_impl(context): + assert context.failed is False + assert 'file://' not in context.path + + +@when('I pass the string to dir_from_uri') +def step_impl(context): + context.path = dir_from_uri(context.str) + + +@then('it should return the directory of the path') +def step_impl(context): + assert context.failed is False + assert context.path is '/' diff --git a/tests/server.features/uri.feature b/tests/server.features/uri.feature new file mode 100644 index 0000000..fc2d2c2 --- /dev/null +++ b/tests/server.features/uri.feature @@ -0,0 +1,17 @@ +Feature: uri module + uri is a module of language-server. + + Scenario: Test path_from_uri + Given There is a string with "file://" + When I pass the string with the prefix to path_from_uri + Then it should return a string without "file://" + + Scenario: Test path_from_uri + Given There is a string without "file://" + When I pass the string without the prefix to path_from_uri + Then it should return a string without "file://" + + Scenario: Test dir_from_uri + Given There is a string without "file://" + When I pass the string to dir_from_uri + Then it should return the directory of the path From 6c592d3c63b8aa1a407f15fb6e2da8ab9f49305c Mon Sep 17 00:00:00 2001 From: Ce Gao Date: Sat, 11 Feb 2017 10:25:30 +0800 Subject: [PATCH 03/13] CI: Add travis and codecov support --- .coveragerc | 13 +++++++++++++ .travis.yml | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 .coveragerc create mode 100644 .travis.yml diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..4ae72a5 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,13 @@ +[run] +branch = True +source = . + +[report] +exclude_lines = + if self.debug: + pragma: no cover + raise NotImplementedError + if __name__ == .__main__.: +ignore_errors = True +omit = + tests/* diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6de2c3e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,52 @@ +sudo: false +language: python +python: + - 3.4 + - 3.5 + - 3.6 + +cache: + pip: true + directories: + - node_modules + +os: +# # The macOS build in travis is really slow. +# - osx + - linux + +# Note that in order to run the tests on Linux, +# you need to have a before_install configuration +# to enable Linux to start VS Code from the build. +# ref https://code.visualstudio.com/Docs/extensions/testing-extensions +before_install: + - if [ $TRAVIS_OS_NAME == "linux" ]; then + export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; + sh -e /etc/init.d/xvfb start; + sleep 3; + fi + +install: + # beheve is a dev dependeny, so not in requirements.txt. + - pip install behave + - pip install -r ./requirements.txt + # codecov is a code coverage tool. + - pip install codecov + - cd ./vscode-client + - npm install + - mkdir ./out + - npm run vscode:prepublish + - cd - > /dev/null + +script: + # Server side tests. + - coverage run $(which behave) ./tests/server.features +# # Integration tests. +# - cd ./vscode-client +# - npm test +# - cd - > /dev/null + +after_success: + - codecov +# # If the build was triggered by a tag, publish the new version +# - 'if [[ $TRAVIS_TAG == v* ]]; then vsce publish -p $VSCE_TOKEN; fi' From 47a966213c9d8608729370ac38e57b86dbd9d137 Mon Sep 17 00:00:00 2001 From: Ce Gao Date: Sat, 11 Feb 2017 10:41:44 +0800 Subject: [PATCH 04/13] tests: Add test cases for log --- tests/server.features/log.feature | 7 +++++++ tests/server.features/steps/log_steps.py | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 tests/server.features/log.feature create mode 100644 tests/server.features/steps/log_steps.py diff --git a/tests/server.features/log.feature b/tests/server.features/log.feature new file mode 100644 index 0000000..1d7021a --- /dev/null +++ b/tests/server.features/log.feature @@ -0,0 +1,7 @@ +Feature: log module + log is a module of language-server. + + Scenario: Test log + Given There is a string + When I pass the string to log + Then it should return normally diff --git a/tests/server.features/steps/log_steps.py b/tests/server.features/steps/log_steps.py new file mode 100644 index 0000000..af9287e --- /dev/null +++ b/tests/server.features/steps/log_steps.py @@ -0,0 +1,23 @@ +# -*- coding: UTF-8 -*- + +# @mark.steps +# ---------------------------------------------------------------------------- +# STEPS: +# ---------------------------------------------------------------------------- +from behave import given, when, then +from coala_langserver.log import log + + +@given('There is a string') +def step_impl(context): + context.str = 'file://Users' + + +@when('I pass the string to log') +def step_impl(context): + log(context.str) + + +@then('it should return normally') +def step_impl(context): + assert context.failed is False From 4c1aea2d38830ed7e52bca1cff4d6f7a156f5744 Mon Sep 17 00:00:00 2001 From: Ce Gao Date: Sat, 11 Feb 2017 19:36:23 +0800 Subject: [PATCH 05/13] coala: Add .coafile and reformat code --- .coafile | 41 ++++++++++++++++++ coala-langserver.py | 2 +- coala_langserver/coalashim.py | 18 ++++---- coala_langserver/diagnostic.py | 50 ++++++++-------------- coala_langserver/fs.py | 6 +-- coala_langserver/jsonrpc.py | 70 +++++++++++++++---------------- coala_langserver/langserver.py | 76 +++++++++++++++++----------------- coala_langserver/uri.py | 8 ++-- requirements.txt | 1 + 9 files changed, 147 insertions(+), 125 deletions(-) create mode 100644 .coafile diff --git a/.coafile b/.coafile new file mode 100644 index 0000000..51ea27b --- /dev/null +++ b/.coafile @@ -0,0 +1,41 @@ +[Default] +files = *.py, coala_langserver/*.py, tests/**/*.py + +max_line_length = 79 +use_spaces = True + +[python] +# Patches may conflict with autopep8 so putting them in own section so they +# will be executed sequentially; also we need the LineLengthBear to double +# check the line length because PEP8Bear sometimes isn't able to correct the +# linelength. +bears = SpaceConsistencyBear, QuotesBear +language = python +preferred_quotation = ' + +[autopep8] +bears = PEP8Bear, PycodestyleBear + +[linelength] # Sometimes autopep8 makes too long lines, need to check after! +bears = LineLengthBear +ignore_length_regex = ^.*https?:// + +[commit] +bears = GitCommitBear +shortlog_trailing_period = False +shortlog_regex = ([^:]*|[^:]+[^ ]: [A-Z0-9*].*) + +[LineCounting] +enabled = False +bears = LineCountBear +max_lines_per_file = 1000 + +[TODOS] +enabled = False +bears = KeywordBear +language = python3 + +# Note that the ci_keywords and cs_keywords are only used here because coala +# 0.8.x (current stable release) needs them. +ci_keywords, keywords = \#TODO, \# TODO, \#FIXME, \# FIXME +cs_keywords = diff --git a/coala-langserver.py b/coala-langserver.py index ac60e17..7e77ec6 100644 --- a/coala-langserver.py +++ b/coala-langserver.py @@ -8,4 +8,4 @@ langserver.main() except Exception as e: tb = traceback.format_exc() - print("FATAL ERROR: {} {}".format(e, tb)) + print('FATAL ERROR: {} {}'.format(e, tb)) diff --git a/coala_langserver/coalashim.py b/coala_langserver/coalashim.py index adb8f66..fbdda53 100644 --- a/coala_langserver/coalashim.py +++ b/coala_langserver/coalashim.py @@ -5,25 +5,25 @@ def run_coala_with_specific_file(working_dir, file): - """Run coala in a specified directory.""" - command = ["coala", "--json", "--find-config", "--files", file] + '""Run coala in a specified directory.""' + command = ['coala', '--json', '--find-config', '--files', file] stdout_file = tempfile.TemporaryFile() - kwargs = {"stdout": stdout_file, - "cwd": working_dir} + kwargs = {'stdout': stdout_file, + 'cwd': working_dir} process = subprocess.Popen(command, **kwargs) retval = process.wait() output_str = None if retval == 1: stdout_file.seek(0) - output_str = stdout_file.read().decode("utf-8", "ignore") + output_str = stdout_file.read().decode('utf-8', 'ignore') if output_str: - log("Output =", output_str) + log('Output =', output_str) else: - log("No results for the file") + log('No results for the file') elif retval == 0: - log("No issues found") + log('No issues found') else: - log("Exited with:", retval) + log('Exited with:', retval) stdout_file.close() return output_str diff --git a/coala_langserver/diagnostic.py b/coala_langserver/diagnostic.py index 3c253c9..804fa0b 100644 --- a/coala_langserver/diagnostic.py +++ b/coala_langserver/diagnostic.py @@ -1,50 +1,34 @@ import json -class Diagnostic(object): - """Diagnostic class for VS Code.""" - - def __init__(self, severity, range, message, section, origin): - self.severity = severity - self.range = range - self.message = message - self.source = "coala" - self.section = section - self.origin = origin - self.real_message = self.make_real_message() - - def make_real_message(self): - return "[{}] {}: {}".format(self.section, self.origin, self.message) - - def output_to_diagnostics(output): - """Turn output to diagnstics.""" + '""Turn output to diagnstics.""' if output is None: return None - output_json = json.loads(output)["results"] + output_json = json.loads(output)['results'] res = [] for key, problems in output_json.items(): section = key for problem in problems: - severity = problem["severity"] - message = problem["message"] - origin = problem["origin"] - real_message = "[{}] {}: {}".format(section, origin, message) - for code in problem["affected_code"]: + severity = problem['severity'] + message = problem['message'] + origin = problem['origin'] + real_message = '[{}] {}: {}'.format(section, origin, message) + for code in problem['affected_code']: res.append({ - "severity": severity, - "range": { - "start": { + 'severity': severity, + 'range': { + 'start': { # Trick: VS Code starts from 0? - "line": code["start"]["line"] - 1, - "character": code["start"]["column"] + 'line': code['start']['line'] - 1, + 'character': code['start']['column'] }, - "end": { - "line": code["end"]["line"] - 1, - "character": code["end"]["column"] + 'end': { + 'line': code['end']['line'] - 1, + 'character': code['end']['column'] } }, - "source": "coala", - "message": real_message + 'source': 'coala', + 'message': real_message }) return res diff --git a/coala_langserver/fs.py b/coala_langserver/fs.py index 27205e1..8a4d791 100644 --- a/coala_langserver/fs.py +++ b/coala_langserver/fs.py @@ -1,18 +1,14 @@ -import base64 import os from abc import ABC, abstractmethod -from functools import lru_cache from typing import List -from .jsonrpc import JSONRPC2Connection - class FileException(Exception): pass class Entry: - """Generic representation of a directory entry.""" + '""Generic representation of a directory entry.""' def __init__(self, name, is_dir, size): self.name = name diff --git a/coala_langserver/jsonrpc.py b/coala_langserver/jsonrpc.py index 430f64c..bc11cba 100644 --- a/coala_langserver/jsonrpc.py +++ b/coala_langserver/jsonrpc.py @@ -31,10 +31,10 @@ class TCPReadWriter(ReadWriter): def readline(self, *args): data = self.reader.readline(*args) - return data.decode("utf-8") + return data.decode('utf-8') def read(self, *args): - return self.reader.read(*args).decode("utf-8") + return self.reader.read(*args).decode('utf-8') def write(self, out): self.writer.write(out.encode()) @@ -52,30 +52,30 @@ def handle(self, id, request): @staticmethod def _read_header_content_length(line): - if len(line) < 2 or line[-2:] != "\r\n": - raise JSONRPC2Error("Line endings must be \\r\\n") - if line.startswith("Content-Length: "): - _, value = line.split("Content-Length: ") + if len(line) < 2 or line[-2:] != '\r\n': + raise JSONRPC2Error('Line endings must be \\r\\n') + if line.startswith('Content-Length: '): + _, value = line.split('Content-Length: ') value = value.strip() try: return int(value) except ValueError: raise JSONRPC2Error( - "Invalid Content-Length header: {}".format(value)) + 'Invalid Content-Length header: {}'.format(value)) def _receive(self): line = self.conn.readline() - if line == "": + if line == '': raise EOFError() length = self._read_header_content_length(line) # Keep reading headers until we find the sentinel line for the JSON # request. - while line != "\r\n": + while line != '\r\n': line = self.conn.readline() body = self.conn.read(length) obj = json.loads(body) # If the next message doesn't have an id, just give it a random key. - self._msg_buffer[obj.get("id") or uuid.uuid4()] = obj + self._msg_buffer[obj.get('id') or uuid.uuid4()] = obj def read_message(self, _id=None): """Read a JSON RPC message sent over the current connection. If @@ -92,50 +92,50 @@ def read_message(self, _id=None): def write_response(self, _id, result): body = { - "jsonrpc": "2.0", - "id": _id, - "result": result, + 'jsonrpc': '2.0', + 'id': _id, + 'result': result, } - body = json.dumps(body, separators=(",", ":")) + body = json.dumps(body, separators=(',', ':')) content_length = len(body) response = ( - "Content-Length: {}\r\n" - "Content-Type: application/vscode-jsonrpc; charset=utf8\r\n\r\n" - "{}".format(content_length, body)) + 'Content-Length: {}\r\n' + 'Content-Type: application/vscode-jsonrpc; charset=utf8\r\n\r\n' + '{}'.format(content_length, body)) self.conn.write(response) - log("RESPONSE: ", response) + log('RESPONSE: ', response) def send_request(self, method: str, params): _id = random.randint(0, 2 ** 16) # TODO(renfred) guarantee uniqueness. body = { - "jsonrpc": "2.0", - "id": _id, - "method": method, - "params": params, + 'jsonrpc': '2.0', + 'id': _id, + 'method': method, + 'params': params, } - body = json.dumps(body, separators=(",", ":")) + body = json.dumps(body, separators=(',', ':')) content_length = len(body) request = ( - "Content-Length: {}\r\n" - "Content-Type: application/vscode-jsonrpc; charset=utf8\r\n\r\n" - "{}".format(content_length, body)) - log("SENDING REQUEST: ", request) + 'Content-Length: {}\r\n' + 'Content-Type: application/vscode-jsonrpc; charset=utf8\r\n\r\n' + '{}'.format(content_length, body)) + log('SENDING REQUEST: ', request) self.conn.write(request) return self.read_message(_id) def send_notification(self, method: str, params): body = { - "jsonrpc": "2.0", - "method": method, - "params": params, + 'jsonrpc': '2.0', + 'method': method, + 'params': params, } - body = json.dumps(body, separators=(",", ":")) + body = json.dumps(body, separators=(',', ':')) content_length = len(body) notification = ( - "Content-Length: {}\r\n" - "Content-Type: application/vscode-jsonrpc; charset=utf8\r\n\r\n" - "{}".format(content_length, body)) - log("SENDING notification: ", notification) + 'Content-Length: {}\r\n' + 'Content-Type: application/vscode-jsonrpc; charset=utf8\r\n\r\n' + '{}'.format(content_length, body)) + log('SENDING notification: ', notification) self.conn.write(notification) return None diff --git a/coala_langserver/langserver.py b/coala_langserver/langserver.py index 71dd6b2..582a084 100644 --- a/coala_langserver/langserver.py +++ b/coala_langserver/langserver.py @@ -23,11 +23,11 @@ def handle(self): s.listen() except Exception as e: tb = traceback.format_exc() - log("ERROR: {} {}".format(e, tb)) + log('ERROR: {} {}'.format(e, tb)) class LangServer(JSONRPC2Connection): - """Language server for coala base on JSON RPC.""" + '""Language server for coala base on JSON RPC.""' def __init__(self, conn=None): super().__init__(conn=conn) @@ -36,41 +36,41 @@ def __init__(self, conn=None): self.fs = LocalFileSystem() def handle(self, _id, request): - """Handle the request from language client.""" - log("REQUEST: ", request) + '""Handle the request from language client.""' + log('REQUEST: ', request) resp = None - if request["method"] == "initialize": + if request['method'] == 'initialize': resp = self.serve_initialize(request) # TODO: Support didChange. # elif request["method"] == "textDocument/didChange": # resp = self.serve_change(request) # elif request["method"] == "workspace/didChangeWatchedFiles": # resp = self.serve_did_change_watched_files(request) - elif request["method"] == "textDocument/didSave": + elif request['method'] == 'textDocument/didSave': resp = self.serve_did_save(request) if resp is not None: - self.write_response(request["id"], resp) + self.write_response(request['id'], resp) def serve_initialize(self, request): - """Serve for the initialization request.""" - params = request["params"] + '""Serve for the initialization request.""' + params = request['params'] # Notice that the root_path could be None. - if "rootUri" in params: - self.root_path = path_from_uri(params["rootUri"]) - elif "rootPath" in params: - self.root_path = path_from_uri(params["rootPath"]) + if 'rootUri' in params: + self.root_path = path_from_uri(params['rootUri']) + elif 'rootPath' in params: + self.root_path = path_from_uri(params['rootPath']) return { - "capabilities": { - "textDocumentSync": 1 + 'capabilities': { + 'textDocumentSync': 1 } } def serve_did_save(self, request): - """Serve for did_change request.""" - params = request["params"] - uri = params["textDocument"]["uri"] + '""Serve for did_change request.""' + params = request['params'] + uri = params['textDocument']['uri'] path = path_from_uri(uri) diagnostics = output_to_diagnostics( run_coala_with_specific_file(self.root_path, path)) @@ -78,9 +78,9 @@ def serve_did_save(self, request): return None def serve_change(self, request): - """Serve for the request of documentation changed.""" - params = request["params"] - uri = params["textDocument"]["uri"] + '""Serve for the request of documentation changed.""' + params = request['params'] + uri = params['textDocument']['uri'] path = path_from_uri(uri) diagnostics = output_to_diagnostics( run_coala_with_specific_file(self.root_path, path)) @@ -88,10 +88,10 @@ def serve_change(self, request): return None def serve_did_change_watched_files(self, request): - """Serve for thr workspace/didChangeWatchedFiles request.""" - changes = request["changes"] + '""Serve for thr workspace/didChangeWatchedFiles request.""' + changes = request['changes'] for fileEvent in changes: - uri = fileEvent["uri"] + uri = fileEvent['uri'] path = path_from_uri(uri) diagnostics = output_to_diagnostics( run_coala_with_specific_file(self.root_path, path)) @@ -102,28 +102,28 @@ def send_diagnostics(self, path, diagnostics): if diagnostics is not None: _diagnostics = diagnostics params = { - "uri": "file://{0}".format(path), - "diagnostics": _diagnostics, + 'uri': 'file://{0}'.format(path), + 'diagnostics': _diagnostics, } - self.send_notification("textDocument/publishDiagnostics", params) + self.send_notification('textDocument/publishDiagnostics', params) def main(): - parser = argparse.ArgumentParser(description="") - parser.add_argument("--mode", default="stdio", - help="communication (stdio|tcp)") - parser.add_argument("--addr", default=2087, - help="server listen (tcp)", type=int) + parser = argparse.ArgumentParser(description='') + parser.add_argument('--mode', default='stdio', + help='communication (stdio|tcp)') + parser.add_argument('--addr', default=2087, + help='server listen (tcp)', type=int) args = parser.parse_args() - if args.mode == "stdio": - log("Reading on stdin, writing on stdout") + if args.mode == 'stdio': + log('Reading on stdin, writing on stdout') s = LangServer(conn=ReadWriter(sys.stdin, sys.stdout)) s.listen() - elif args.mode == "tcp": - host, addr = "0.0.0.0", args.addr - log("Accepting TCP connections on {}:{}".format(host, addr)) + elif args.mode == 'tcp': + host, addr = '0.0.0.0', args.addr + log('Accepting TCP connections on {}:{}'.format(host, addr)) ThreadingTCPServer.allow_reuse_address = True s = ThreadingTCPServer((host, addr), LangserverTCPTransport) try: @@ -132,5 +132,5 @@ def main(): s.shutdown() -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/coala_langserver/uri.py b/coala_langserver/uri.py index 4a3e14c..7ca75ba 100644 --- a/coala_langserver/uri.py +++ b/coala_langserver/uri.py @@ -2,13 +2,13 @@ def path_from_uri(uri): - """Get the path from JSON RPC initialization request.""" - if not uri.startswith("file://"): + '""Get the path from JSON RPC initialization request.""' + if not uri.startswith('file://'): return uri - _, path = uri.split("file://", 1) + _, path = uri.split('file://', 1) return path def dir_from_uri(uri): - """Get the directory name from the path.""" + '""Get the directory name from the path.""' return os.path.dirname(path_from_uri(uri)) diff --git a/requirements.txt b/requirements.txt index 269697b..a46e077 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ coala>=0.10.0.dev20170122051037 typing>=3.5.3.0 +coala-bears>=0.10.0.dev20170209163733 \ No newline at end of file From a99350f0bcbcc32f6e40ab5cf10f33e3495732f0 Mon Sep 17 00:00:00 2001 From: Ce Gao Date: Sat, 11 Feb 2017 19:36:46 +0800 Subject: [PATCH 06/13] tests: Add test cases for coalashim --- tests/resources/qulified.py | 1 + tests/resources/unqualified.py | 2 + tests/server.features/coalashim.feature | 14 +++++ .../server.features/steps/coalashim_steps.py | 61 +++++++++++++++++++ 4 files changed, 78 insertions(+) create mode 100644 tests/resources/qulified.py create mode 100644 tests/resources/unqualified.py create mode 100644 tests/server.features/coalashim.feature create mode 100644 tests/server.features/steps/coalashim_steps.py diff --git a/tests/resources/qulified.py b/tests/resources/qulified.py new file mode 100644 index 0000000..f5efd16 --- /dev/null +++ b/tests/resources/qulified.py @@ -0,0 +1 @@ +print('Hello, World.') diff --git a/tests/resources/unqualified.py b/tests/resources/unqualified.py new file mode 100644 index 0000000..2ce46a0 --- /dev/null +++ b/tests/resources/unqualified.py @@ -0,0 +1,2 @@ +def test(): + a = 1 \ No newline at end of file diff --git a/tests/server.features/coalashim.feature b/tests/server.features/coalashim.feature new file mode 100644 index 0000000..1cb3c9e --- /dev/null +++ b/tests/server.features/coalashim.feature @@ -0,0 +1,14 @@ +Feature: coalashim module + coalashim is a module of language-server, it interacts with coala core. + + Scenario: Test run_coala_with_specific_file + Given the current directory and path of qualified.py + When I pass the qualified.py to run_coala_with_specific_file + Then it should return output in json format + And with no error in the output + + Scenario: Test run_coala_with_specific_file + Given the current directory and path of unqualified.py + When I pass the unqualified.py to run_coala_with_specific_file + Then it should return output in json format + And with autopep8 errors in the output diff --git a/tests/server.features/steps/coalashim_steps.py b/tests/server.features/steps/coalashim_steps.py new file mode 100644 index 0000000..0062513 --- /dev/null +++ b/tests/server.features/steps/coalashim_steps.py @@ -0,0 +1,61 @@ +# -*- coding: UTF-8 -*- + +# @mark.steps +# ---------------------------------------------------------------------------- +# STEPS: +# ---------------------------------------------------------------------------- +import os +import json +from behave import given, when, then +from coala_langserver.coalashim import run_coala_with_specific_file + + +@given('the current directory and path of qualified.py') +def step_impl(context): + context.dir = os.path.abspath( + os.path.join( + os.path.dirname(os.path.abspath(__file__)), + os.pardir, + os.pardir, + os.pardir + ) + ) + context.path = os.path.join(context.dir, 'tests', 'resources', 'qualified.py') + + +@when('I pass the qualified.py to run_coala_with_specific_file') +def step_impl(context): + context.output = run_coala_with_specific_file(context.dir, context.path) + + +@then('it should return output in json format') +def step_impl(context): + assert context.failed is False + + +@then('with no error in the output') +def step_impl(context): + assert context.output is None + + +@given('the current directory and path of unqualified.py') +def step_impl(context): + context.dir = os.path.abspath( + os.path.join( + os.path.dirname(os.path.abspath(__file__)), + os.pardir, + os.pardir, + os.pardir + ) + ) + context.path = os.path.join(context.dir, 'tests', 'resources', 'unqualified.py') + + +@when('I pass the unqualified.py to run_coala_with_specific_file') +def step_impl(context): + context.output = run_coala_with_specific_file(context.dir, context.path) + + +@then('with autopep8 errors in the output') +def step_impl(context): + assert json.loads(context.output)['results']['autopep8'] is not None From b82cca6318a9fa9c02b597edcfd89f3da4d883a3 Mon Sep 17 00:00:00 2001 From: Ce Gao Date: Sat, 11 Feb 2017 19:40:47 +0800 Subject: [PATCH 07/13] tests: Add test cases for diagnostic --- .travis.yml | 7 ++++ tests/server.features/diagnostic.feature | 7 ++++ .../server.features/steps/diagnostic_steps.py | 35 +++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 tests/server.features/diagnostic.feature create mode 100644 tests/server.features/steps/diagnostic_steps.py diff --git a/.travis.yml b/.travis.yml index 6de2c3e..1c3b86c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,13 @@ before_install: sleep 3; fi +before_script: + # nltk 3.2.2 dropped support for Python 3.3 + - > + if [[ "$TRAVIS_PYTHON_VERSION" != "3.3" ]]; then + python -m nltk.downloader punkt maxent_treebank_pos_tagger averaged_perceptron_tagger + fi + install: # beheve is a dev dependeny, so not in requirements.txt. - pip install behave diff --git a/tests/server.features/diagnostic.feature b/tests/server.features/diagnostic.feature new file mode 100644 index 0000000..8677e5c --- /dev/null +++ b/tests/server.features/diagnostic.feature @@ -0,0 +1,7 @@ +Feature: diagnostic module + diagnostic is a module of language-server. + + Scenario: Test output_to_diagnostics + Given the output with errors by coala + When I pass the parameters to output_to_diagnostics + Then it should return output in vscode format diff --git a/tests/server.features/steps/diagnostic_steps.py b/tests/server.features/steps/diagnostic_steps.py new file mode 100644 index 0000000..4baf371 --- /dev/null +++ b/tests/server.features/steps/diagnostic_steps.py @@ -0,0 +1,35 @@ +# -*- coding: UTF-8 -*- + +# @mark.steps +# ---------------------------------------------------------------------------- +# STEPS: +# ---------------------------------------------------------------------------- +import os +from behave import given, when, then +from coala_langserver.diagnostic import output_to_diagnostics +from coala_langserver.coalashim import run_coala_with_specific_file + + +@given('the output with errors by coala') +def step_impl(context): + context.dir = os.path.abspath( + os.path.join( + os.path.dirname(os.path.abspath(__file__)), + os.pardir, + os.pardir, + 'resources' + ) + ) + context.path = os.path.join(context.dir, 'unqualified.py') + + context.output = run_coala_with_specific_file(context.dir, context.path) + + +@when('I pass the parameters to output_to_diagnostics') +def step_impl(context): + context.message = output_to_diagnostics(context.output) + + +@then('it should return output in vscode format') +def step_impl(context): + assert len(context.message) is not 0 From 5e9b8e1f29fbed13cf3b43627c5223c30ed68b4a Mon Sep 17 00:00:00 2001 From: Ce Gao Date: Sun, 12 Feb 2017 13:42:48 +0800 Subject: [PATCH 08/13] tests: Add test cases for jsonrpc --- .coveragerc | 1 + coala_langserver/fs.py | 53 --------- coala_langserver/langserver.py | 3 - tests/resources/{qulified.py => qualified.py} | 0 tests/server.features/jsonrpc.feature | 38 +++++++ tests/server.features/steps/jsonrpc_steps.py | 106 ++++++++++++++++++ 6 files changed, 145 insertions(+), 56 deletions(-) delete mode 100644 coala_langserver/fs.py rename tests/resources/{qulified.py => qualified.py} (100%) create mode 100644 tests/server.features/jsonrpc.feature create mode 100644 tests/server.features/steps/jsonrpc_steps.py diff --git a/.coveragerc b/.coveragerc index 4ae72a5..0534080 100644 --- a/.coveragerc +++ b/.coveragerc @@ -5,6 +5,7 @@ source = . [report] exclude_lines = if self.debug: + pass pragma: no cover raise NotImplementedError if __name__ == .__main__.: diff --git a/coala_langserver/fs.py b/coala_langserver/fs.py deleted file mode 100644 index 8a4d791..0000000 --- a/coala_langserver/fs.py +++ /dev/null @@ -1,53 +0,0 @@ -import os -from abc import ABC, abstractmethod -from typing import List - - -class FileException(Exception): - pass - - -class Entry: - '""Generic representation of a directory entry.""' - - def __init__(self, name, is_dir, size): - self.name = name - self.is_dir = is_dir - self.size = size - - -class FileSystem(ABC): - - @abstractmethod - def open(self, path: str) -> str: - pass - - @abstractmethod - def listdir(self, path: str) -> List[Entry]: - pass - - def walk(self, top: str): - dir = self.listdir(top) - files, dirs = [], [] - for e in dir: - if e.is_dir: - dirs.append(os.path.join(top, e.name)) - else: - files.append(os.path.join(top, e.name)) - yield top, dirs, files - for d in dirs: - yield from self.walk(d) - - -class LocalFileSystem(FileSystem): - - def open(self, path): - return open(path).read() - - def listdir(self, path): - entries = [] - names = os.listdir(path) - for n in names: - p = os.path.join(path, n) - entries.append(Entry(n, os.path.isdir(p), os.path.getsize(p))) - return entries diff --git a/coala_langserver/langserver.py b/coala_langserver/langserver.py index 582a084..b9448af 100644 --- a/coala_langserver/langserver.py +++ b/coala_langserver/langserver.py @@ -3,7 +3,6 @@ import socketserver import traceback -from .fs import LocalFileSystem from .jsonrpc import JSONRPC2Connection, ReadWriter, TCPReadWriter from .log import log from .coalashim import run_coala_with_specific_file @@ -32,8 +31,6 @@ class LangServer(JSONRPC2Connection): def __init__(self, conn=None): super().__init__(conn=conn) self.root_path = None - self.symbol_cache = None - self.fs = LocalFileSystem() def handle(self, _id, request): '""Handle the request from language client.""' diff --git a/tests/resources/qulified.py b/tests/resources/qualified.py similarity index 100% rename from tests/resources/qulified.py rename to tests/resources/qualified.py diff --git a/tests/server.features/jsonrpc.feature b/tests/server.features/jsonrpc.feature new file mode 100644 index 0000000..047f604 --- /dev/null +++ b/tests/server.features/jsonrpc.feature @@ -0,0 +1,38 @@ +Feature: jsonrpc module + jsonrpc is a module of language-server. + + Scenario: Test ReadWriter + Given the string + When I write it to ReadWriter + Then it should read from ReadWriter + + Scenario: Test ReadWriter + Given the string + When I write it to ReadWriter + Then it should readline from ReadWriter + + Scenario: Test TCPReadWriter + Given the string + When I write it to TCPReadWriter + Then it should read from TCPReadWriter + + Scenario: Test TCPReadWriter + Given the string + When I write it to TCPReadWriter + Then it should readline from TCPReadWriter + + Scenario: Test send_notification and read_message + Given the JSONRPC2Connection instance + When I write a notification to the JSONRPC2Connection + Then it should return the notification from JSONRPC2Connection + + Scenario: Test write_response + Given the JSONRPC2Connection instance + When I write a response to the JSONRPC2Connection + Then it should return the response from JSONRPC2Connection + + # TODO: block until we have generantee the unique request. +# Scenario: Test send_request +# Given the JSONRPC2Connection instance +# When I write a request to the JSONRPC2Connection with id +# Then it should return the request from JSONRPC2Connection with id diff --git a/tests/server.features/steps/jsonrpc_steps.py b/tests/server.features/steps/jsonrpc_steps.py new file mode 100644 index 0000000..0b7b443 --- /dev/null +++ b/tests/server.features/steps/jsonrpc_steps.py @@ -0,0 +1,106 @@ +# -*- coding: UTF-8 -*- + +# @mark.steps +# ---------------------------------------------------------------------------- +# STEPS: +# ---------------------------------------------------------------------------- +import tempfile +import json +from behave import given, when, then +from coala_langserver.jsonrpc import ReadWriter, TCPReadWriter, JSONRPC2Connection + + +@given('the string') +def step_impl(context): + context.str = 'test-cases' + + +@when('I write it to ReadWriter') +def step_impl(context): + context.f = tempfile.TemporaryFile(mode='w+') + context.readWriter = ReadWriter(context.f, context.f) + context.readWriter.write(context.str) + + +@then('it should read from ReadWriter') +def step_impl(context): + context.f.seek(0) + assert context.readWriter.read(len(context.str)) is not '' + context.f.close() + + +@then('it should readline from ReadWriter') +def step_impl(context): + context.f.seek(0) + assert context.readWriter.readline() is not '' + context.f.close() + + +@when('I write it to TCPReadWriter') +def step_impl(context): + context.f = tempfile.TemporaryFile() + context.readWriter = TCPReadWriter(context.f, context.f) + context.readWriter.write(context.str) + + +@then('it should read from TCPReadWriter') +def step_impl(context): + context.f.seek(0) + assert context.readWriter.read(len(context.str)) is not '' + context.f.close() + + +@then('it should readline from TCPReadWriter') +def step_impl(context): + context.f.seek(0) + assert context.readWriter.readline() is not '' + context.f.close() + + +@given('the JSONRPC2Connection instance') +def step_impl(context): + context.f = tempfile.TemporaryFile() + context.jsonConn = JSONRPC2Connection(conn=TCPReadWriter(context.f, context.f)) + + +@when('I write a request to the JSONRPC2Connection with id') +def step_impl(context): + context.jsonConn.send_request('mockMethod', { + 'mock': 'mock' + }) + + +@then('it should return the request from JSONRPC2Connection with id') +def step_impl(context): + context.f.seek(0) + assert context.jsonConn.read_message() is not None + context.f.close() + + +@when('I write a notification to the JSONRPC2Connection') +def step_impl(context): + context.jsonConn.send_notification('mockMethod', { + 'mock': 'mock' + }) + + +@then('it should return the notification from JSONRPC2Connection') +def step_impl(context): + context.f.seek(0) + assert context.jsonConn.read_message() is not None + context.f.close() + + +@when('I write a response to the JSONRPC2Connection') +def step_impl(context): + context.ID = 1 + context.jsonConn.write_response(context.ID, { + 'mock': 'mock' + }) + + +@then('it should return the response from JSONRPC2Connection') +def step_impl(context): + context.f.seek(0) + assert context.jsonConn.read_message(context.ID) is not None + context.f.close() From 87c584131aeaa1dce77aa1da8cd0946d3c8b91f4 Mon Sep 17 00:00:00 2001 From: Ce Gao Date: Mon, 13 Feb 2017 22:45:52 +0800 Subject: [PATCH 09/13] tests: Add test cases for langserver --- coala_langserver/langserver.py | 44 ++++++------- tests/server.features/langserver.feature | 13 ++++ tests/server.features/steps/jsonrpc_steps.py | 1 + .../server.features/steps/langserver_steps.py | 63 +++++++++++++++++++ 4 files changed, 99 insertions(+), 22 deletions(-) create mode 100644 tests/server.features/langserver.feature create mode 100644 tests/server.features/steps/langserver_steps.py diff --git a/coala_langserver/langserver.py b/coala_langserver/langserver.py index b9448af..2f796e9 100644 --- a/coala_langserver/langserver.py +++ b/coala_langserver/langserver.py @@ -39,13 +39,13 @@ def handle(self, _id, request): if request['method'] == 'initialize': resp = self.serve_initialize(request) - # TODO: Support didChange. + # TODO: Support did_change and did_change_watched_files. # elif request["method"] == "textDocument/didChange": # resp = self.serve_change(request) # elif request["method"] == "workspace/didChangeWatchedFiles": # resp = self.serve_did_change_watched_files(request) elif request['method'] == 'textDocument/didSave': - resp = self.serve_did_save(request) + self.serve_did_save(request) if resp is not None: self.write_response(request['id'], resp) @@ -72,27 +72,27 @@ def serve_did_save(self, request): diagnostics = output_to_diagnostics( run_coala_with_specific_file(self.root_path, path)) self.send_diagnostics(path, diagnostics) - return None - def serve_change(self, request): - '""Serve for the request of documentation changed.""' - params = request['params'] - uri = params['textDocument']['uri'] - path = path_from_uri(uri) - diagnostics = output_to_diagnostics( - run_coala_with_specific_file(self.root_path, path)) - self.send_diagnostics(path, diagnostics) - return None - - def serve_did_change_watched_files(self, request): - '""Serve for thr workspace/didChangeWatchedFiles request.""' - changes = request['changes'] - for fileEvent in changes: - uri = fileEvent['uri'] - path = path_from_uri(uri) - diagnostics = output_to_diagnostics( - run_coala_with_specific_file(self.root_path, path)) - self.send_diagnostics(path, diagnostics) + # TODO: Support did_change and did_change_watched_files. + # def serve_change(self, request): + # '""Serve for the request of documentation changed.""' + # params = request['params'] + # uri = params['textDocument']['uri'] + # path = path_from_uri(uri) + # diagnostics = output_to_diagnostics( + # run_coala_with_specific_file(self.root_path, path)) + # self.send_diagnostics(path, diagnostics) + # return None + # + # def serve_did_change_watched_files(self, request): + # '""Serve for thr workspace/didChangeWatchedFiles request.""' + # changes = request['changes'] + # for fileEvent in changes: + # uri = fileEvent['uri'] + # path = path_from_uri(uri) + # diagnostics = output_to_diagnostics( + # run_coala_with_specific_file(self.root_path, path)) + # self.send_diagnostics(path, diagnostics) def send_diagnostics(self, path, diagnostics): _diagnostics = [] diff --git a/tests/server.features/langserver.feature b/tests/server.features/langserver.feature new file mode 100644 index 0000000..d8b2c5e --- /dev/null +++ b/tests/server.features/langserver.feature @@ -0,0 +1,13 @@ +Feature: langserver module + langserver is the main program of language-server. + + Scenario: Test serve_initialize + Given the LangServer instance + When I send a initialize request to the server + Then it should return the response with textDocumentSync + + # TODO: Add positive test case. + Scenario: Test serve_did_save + Given the LangServer instance + When I send a did_save request about a non-existed file to the server + Then it should send a publishDiagnostics request diff --git a/tests/server.features/steps/jsonrpc_steps.py b/tests/server.features/steps/jsonrpc_steps.py index 0b7b443..43db5a5 100644 --- a/tests/server.features/steps/jsonrpc_steps.py +++ b/tests/server.features/steps/jsonrpc_steps.py @@ -93,6 +93,7 @@ def step_impl(context): @when('I write a response to the JSONRPC2Connection') def step_impl(context): + # BUG: when id = 0 context.ID = 1 context.jsonConn.write_response(context.ID, { 'mock': 'mock' diff --git a/tests/server.features/steps/langserver_steps.py b/tests/server.features/steps/langserver_steps.py new file mode 100644 index 0000000..565b913 --- /dev/null +++ b/tests/server.features/steps/langserver_steps.py @@ -0,0 +1,63 @@ +# -*- coding: UTF-8 -*- + +# @mark.steps +# ---------------------------------------------------------------------------- +# STEPS: +# ---------------------------------------------------------------------------- +import tempfile +from behave import given, when, then +from coala_langserver.jsonrpc import TCPReadWriter +from coala_langserver.langserver import LangServer + + +@given('the LangServer instance') +def step_impl(context): + context.f = tempfile.TemporaryFile() + context.langServer = LangServer(conn=TCPReadWriter(context.f, context.f)) + + +@when('I send a initialize request to the server') +def step_impl(context): + request = { + 'method': 'initialize', + 'params': { + 'rootPath': '/Users/mock-user/mock-dir', + 'capabilities': {}, + }, + 'id': 1, + 'jsonrpc': '2.0' + } + context.langServer.handle(1, request) + + +@then('it should return the response with textDocumentSync') +def step_impl(context): + context.f.seek(0) + response = context.langServer.read_message(1) + assert response is not None + assert response['result']['capabilities']['textDocumentSync'] is 1 + context.f.close() + + +@when('I send a did_save request about a non-existed file to the server') +def step_impl(context): + request = { + 'method': 'textDocument/didSave', + 'params': { + 'textDocument': { + 'uri': 'file:///Users/mock-user/non-exist.py' + } + }, + 'jsonrpc': '2.0' + } + context.langServer.handle(None, request) + + +@then('it should send a publishDiagnostics request') +def step_impl(context): + context.f.seek(0) + response = context.langServer.read_message() + assert response is not None + assert response['method'] == 'textDocument/publishDiagnostics' + assert len(response['params']['diagnostics']) is 0 + context.f.close() From 1307e50ab711fb996687098455479c6e50649687 Mon Sep 17 00:00:00 2001 From: Ce Gao Date: Tue, 14 Feb 2017 11:28:28 +0800 Subject: [PATCH 10/13] tests: Remove main func coverage --- .coveragerc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.coveragerc b/.coveragerc index 0534080..9e73f79 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,6 +1,9 @@ [run] branch = True source = . +omit = + coala-langserver.py + tests/* [report] exclude_lines = @@ -8,7 +11,6 @@ exclude_lines = pass pragma: no cover raise NotImplementedError + def main if __name__ == .__main__.: ignore_errors = True -omit = - tests/* From 751e88405d28476834c3ac1243b09f8a6b550a53 Mon Sep 17 00:00:00 2001 From: Ce Gao Date: Wed, 15 Feb 2017 15:20:08 +0800 Subject: [PATCH 11/13] requirements.txt: Update dependency --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index a46e077..676d7fd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -coala>=0.10.0.dev20170122051037 +coala>=0.10.0.dev20170213201648 typing>=3.5.3.0 -coala-bears>=0.10.0.dev20170209163733 \ No newline at end of file +coala-bears>=0.10.0.dev20170215041744 From 870302228ed702bd50cfcb6fc5aa75f971af48b9 Mon Sep 17 00:00:00 2001 From: Ce Gao Date: Wed, 15 Feb 2017 16:12:28 +0800 Subject: [PATCH 12/13] CI: Fix a bug about GitHub PR commit Signed-off-by: Ce Gao --- .travis.yml | 3 +++ README.md | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index 1c3b86c..5d28525 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,6 +46,9 @@ install: - cd - > /dev/null script: + - > + # https://github.com/coala/coala-bears/issues/1037 + - sed -i.bak '/bears = GitCommitBear/d' .coafile # Server side tests. - coverage run $(which behave) ./tests/server.features # # Integration tests. diff --git a/README.md b/README.md index 59a8617..c7afc71 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # coala-vs-code +[![Build Status](https://travis-ci.org/coala/coala-vs-code.svg?branch=master)](https://travis-ci.org/coala/coala-vs-code) +[![codecov](https://codecov.io/gh/coala/coala-vs-code/branch/master/graph/badge.svg)](https://codecov.io/gh/coala/coala-vs-code) + A visual studio code plugin working via [Language Server Protocol (LSP)](https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md).Python versions 3.x is supported. ## Feature preview From 7fba07a58df53f299c29c3cf23ef87999ef0ddad Mon Sep 17 00:00:00 2001 From: Ce Gao Date: Wed, 15 Feb 2017 16:26:07 +0800 Subject: [PATCH 13/13] coverage: Add codecov configuration Signed-off-by: Ce Gao --- .codecov.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..ba76755 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,9 @@ +comment: false +coverage: + status: + changes: false + patch: false + project: + default: + enabled: true + target: 80%