diff --git a/tests/samples/output/main_test_good_config_file b/tests/samples/output/main_test_good_config_file index 70fb7af..1a0a7d0 100644 --- a/tests/samples/output/main_test_good_config_file +++ b/tests/samples/output/main_test_good_config_file @@ -1,5 +1,5 @@ Traceback (most recent call last): - File "{file}", line 230, in parseArguments + File "{file}", line {lineno}, in parseArguments args.timeout = int(configs.get('settings', 'timeout')) ValueError: invalid literal for int() with base 10: 'abc' diff --git a/tests/test_main.py b/tests/test_main.py index 5202e33..37bf0d9 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -6,8 +6,15 @@ from wakatime.packages import requests import os import time +import re import sys from wakatime.compat import u +from wakatime.constants import ( + API_ERROR, + AUTH_ERROR, + CONFIG_FILE_PARSE_ERROR, + SUCCESS, +) from wakatime.packages.requests.models import Response from . import utils @@ -54,7 +61,7 @@ class BaseTestCase(utils.TestCase): args = ['--file', entity, '--key', '123', '--config', config] retval = execute(args) - self.assertEquals(retval, 0) + self.assertEquals(retval, SUCCESS) self.assertEquals(sys.stdout.getvalue(), '') self.assertEquals(sys.stderr.getvalue(), '') @@ -107,10 +114,11 @@ class BaseTestCase(utils.TestCase): config = 'tests/samples/configs/has_everything.cfg' args = ['--file', entity, '--config', config] retval = execute(args) - self.assertEquals(retval, 0) + self.assertEquals(retval, SUCCESS) expected_stdout = open('tests/samples/output/main_test_good_config_file').read() traceback_file = os.path.realpath('wakatime/main.py') - self.assertEquals(sys.stdout.getvalue(), expected_stdout.format(file=traceback_file)) + lineno = int(re.search(r' line (\d+),', sys.stdout.getvalue()).group(1)) + self.assertEquals(sys.stdout.getvalue(), expected_stdout.format(file=traceback_file, lineno=lineno)) self.assertEquals(sys.stderr.getvalue(), '') self.patched['wakatime.session_cache.SessionCache.get'].assert_called_once_with() @@ -129,7 +137,7 @@ class BaseTestCase(utils.TestCase): config = 'tests/samples/configs/sample_alternate_apikey.cfg' args = ['--file', entity, '--config', config] retval = execute(args) - self.assertEquals(retval, 0) + self.assertEquals(retval, SUCCESS) self.assertEquals(sys.stdout.getvalue(), '') self.assertEquals(sys.stderr.getvalue(), '') @@ -145,7 +153,7 @@ class BaseTestCase(utils.TestCase): config = 'tests/samples/configs/bad_config.cfg' args = ['--file', entity, '--config', config] retval = execute(args) - self.assertEquals(retval, 103) + self.assertEquals(retval, CONFIG_FILE_PARSE_ERROR) self.assertIn('ParsingError', sys.stdout.getvalue()) self.assertEquals(sys.stderr.getvalue(), '') self.patched['wakatime.offlinequeue.Queue.push'].assert_not_called() @@ -165,7 +173,7 @@ class BaseTestCase(utils.TestCase): args = ['--file', entity, '--key', '123', '--config', config, '--time', now] retval = execute(args) - self.assertEquals(retval, 102) + self.assertEquals(retval, API_ERROR) self.assertEquals(sys.stdout.getvalue(), '') self.assertEquals(sys.stderr.getvalue(), '') @@ -206,7 +214,7 @@ class BaseTestCase(utils.TestCase): args = ['--file', entity, '--key', '123', '--config', config, '--time', now] retval = execute(args) - self.assertEquals(retval, 102) + self.assertEquals(retval, API_ERROR) self.assertEquals(sys.stdout.getvalue(), '') self.assertEquals(sys.stderr.getvalue(), '') @@ -266,7 +274,7 @@ class BaseTestCase(utils.TestCase): retval = execute(args) - self.assertEquals(retval, 102) + self.assertEquals(retval, API_ERROR) self.assertEquals(sys.stdout.getvalue(), '') self.assertEquals(sys.stderr.getvalue(), '') @@ -307,7 +315,7 @@ class BaseTestCase(utils.TestCase): retval = execute(args) - self.assertEquals(retval, 102) + self.assertEquals(retval, API_ERROR) self.assertEquals(sys.stdout.getvalue(), '') self.assertEquals(sys.stderr.getvalue(), '') @@ -318,6 +326,47 @@ class BaseTestCase(utils.TestCase): self.patched['wakatime.offlinequeue.Queue.push'].assert_not_called() self.patched['wakatime.offlinequeue.Queue.pop'].assert_not_called() + def test_401_response(self): + response = Response() + response.status_code = 401 + self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = response + + now = u(int(time.time())) + + args = ['--file', 'tests/samples/codefiles/twolinefile.txt', '--key', '123', + '--config', 'tests/samples/configs/paranoid.cfg', '--time', now] + + + retval = execute(args) + self.assertEquals(retval, AUTH_ERROR) + self.assertEquals(sys.stdout.getvalue(), '') + self.assertEquals(sys.stderr.getvalue(), '') + + self.patched['wakatime.session_cache.SessionCache.delete'].assert_called_once_with() + self.patched['wakatime.session_cache.SessionCache.get'].assert_called_once_with() + self.patched['wakatime.session_cache.SessionCache.save'].assert_not_called() + + heartbeat = { + 'language': 'Text only', + 'lines': 2, + 'entity': 'HIDDEN.txt', + 'project': os.path.basename(os.path.abspath('.')), + 'branch': os.environ.get('TRAVIS_COMMIT', ANY), + 'time': float(now), + 'type': 'file', + } + stats = { + u('cursorpos'): None, + u('dependencies'): [], + u('language'): u('Text only'), + u('lineno'): None, + u('lines'): 2, + } + + self.patched['wakatime.offlinequeue.Queue.push'].assert_called_once_with(heartbeat, ANY, None) + self.assertEquals(stats, json.loads(self.patched['wakatime.offlinequeue.Queue.push'].call_args[0][1])) + self.patched['wakatime.offlinequeue.Queue.pop'].assert_not_called() + def test_alternate_project(self): response = Response() response.status_code = 0 @@ -330,7 +379,7 @@ class BaseTestCase(utils.TestCase): args = ['--file', entity, '--alternate-project', 'xyz', '--config', config, '--time', now] retval = execute(args) - self.assertEquals(retval, 102) + self.assertEquals(retval, API_ERROR) self.assertEquals(sys.stdout.getvalue(), '') self.assertEquals(sys.stderr.getvalue(), '') @@ -371,7 +420,7 @@ class BaseTestCase(utils.TestCase): args = ['--file', entity, '--project', 'xyz', '--config', config, '--time', now] retval = execute(args) - self.assertEquals(retval, 102) + self.assertEquals(retval, API_ERROR) self.assertEquals(sys.stdout.getvalue(), '') self.assertEquals(sys.stderr.getvalue(), '') @@ -409,7 +458,7 @@ class BaseTestCase(utils.TestCase): config = 'tests/samples/configs/good_config.cfg' args = ['--file', entity, '--config', config] retval = execute(args) - self.assertEquals(retval, 0) + self.assertEquals(retval, SUCCESS) self.assertEquals(sys.stdout.getvalue(), '') self.assertEquals(sys.stderr.getvalue(), '') @@ -429,7 +478,7 @@ class BaseTestCase(utils.TestCase): config = 'tests/samples/configs/good_config.cfg' args = ['--file', entity, '--config', config, '--proxy', 'localhost:1234'] retval = execute(args) - self.assertEquals(retval, 0) + self.assertEquals(retval, SUCCESS) self.assertEquals(sys.stdout.getvalue(), '') self.assertEquals(sys.stderr.getvalue(), '') @@ -454,7 +503,7 @@ class BaseTestCase(utils.TestCase): args = ['--entity', entity, '--entitytype', 'domain', '--config', config, '--time', now] retval = execute(args) - self.assertEquals(retval, 102) + self.assertEquals(retval, API_ERROR) self.assertEquals(sys.stdout.getvalue(), '') self.assertEquals(sys.stderr.getvalue(), '') @@ -491,7 +540,7 @@ class BaseTestCase(utils.TestCase): args = ['--entity', entity, '--entitytype', 'app', '--config', config, '--time', now] retval = execute(args) - self.assertEquals(retval, 102) + self.assertEquals(retval, API_ERROR) self.assertEquals(sys.stdout.getvalue(), '') self.assertEquals(sys.stderr.getvalue(), '') diff --git a/wakatime/constants.py b/wakatime/constants.py index c88f835..135039e 100644 --- a/wakatime/constants.py +++ b/wakatime/constants.py @@ -13,3 +13,4 @@ SUCCESS = 0 API_ERROR = 102 CONFIG_FILE_PARSE_ERROR = 103 +AUTH_ERROR = 104 diff --git a/wakatime/main.py b/wakatime/main.py index ff94e3e..f09a9d9 100644 --- a/wakatime/main.py +++ b/wakatime/main.py @@ -30,7 +30,12 @@ sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pac from .__about__ import __version__ from .compat import u, open, is_py3 -from .constants import SUCCESS, API_ERROR, CONFIG_FILE_PARSE_ERROR +from .constants import ( + API_ERROR, + AUTH_ERROR, + CONFIG_FILE_PARSE_ERROR, + SUCCESS, +) from .logger import setup_logging from .offlinequeue import Queue from .packages import argparse @@ -289,6 +294,9 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None, timestamp=None, isWrite=None, plugin=None, offline=None, entity_type='file', hidefilenames=None, proxy=None, api_url=None, timeout=None, **kwargs): """Sends heartbeat as POST request to WakaTime api server. + + Returns `SUCCESS` when heartbeat was sent, otherwise returns an + error code constant. """ if not api_url: @@ -375,7 +383,7 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None, 'response_code': response_code, }) session_cache.save(session) - return True + return SUCCESS if offline: if response_code != 400: queue = Queue() @@ -385,6 +393,8 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None, 'response_code': response_code, 'response_content': response_content, }) + session_cache.delete() + return AUTH_ERROR elif log.isEnabledFor(logging.DEBUG): log.warn({ 'response_code': response_code, @@ -401,7 +411,39 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None, 'response_content': response_content, }) session_cache.delete() - return False + return API_ERROR + + +def sync_offline_heartbeats(args, hostname): + """Sends all heartbeats which were cached in the offline Queue.""" + + queue = Queue() + while True: + heartbeat = queue.pop() + if heartbeat is None: + break + status = send_heartbeat( + project=heartbeat['project'], + entity=heartbeat['entity'], + timestamp=heartbeat['time'], + branch=heartbeat['branch'], + hostname=hostname, + stats=json.loads(heartbeat['stats']), + key=args.key, + isWrite=heartbeat['is_write'], + plugin=heartbeat['plugin'], + offline=args.offline, + hidefilenames=args.hidefilenames, + entity_type=heartbeat['type'], + proxy=args.proxy, + api_url=args.api_url, + timeout=args.timeout, + ) + if status != SUCCESS: + if status == AUTH_ERROR: + return AUTH_ERROR + break + return SUCCESS def execute(argv=None): @@ -438,37 +480,15 @@ def execute(argv=None): kwargs['project'] = project kwargs['branch'] = branch kwargs['stats'] = stats - kwargs['hostname'] = args.hostname or socket.gethostname() + hostname = args.hostname or socket.gethostname() + kwargs['hostname'] = hostname kwargs['timeout'] = args.timeout - if send_heartbeat(**kwargs): - queue = Queue() - while True: - heartbeat = queue.pop() - if heartbeat is None: - break - sent = send_heartbeat( - project=heartbeat['project'], - entity=heartbeat['entity'], - timestamp=heartbeat['time'], - branch=heartbeat['branch'], - hostname=kwargs['hostname'], - stats=json.loads(heartbeat['stats']), - key=args.key, - isWrite=heartbeat['is_write'], - plugin=heartbeat['plugin'], - offline=args.offline, - hidefilenames=args.hidefilenames, - entity_type=heartbeat['type'], - proxy=args.proxy, - api_url=args.api_url, - timeout=args.timeout, - ) - if not sent: - break - return SUCCESS - - return API_ERROR + status = send_heartbeat(**kwargs) + if status == SUCCESS: + return sync_offline_heartbeats(args, hostname) + else: + return status else: log.debug('File does not exist; ignoring this heartbeat.')