From a5933aa2a72b40a131668bd77c8a260107b7159b Mon Sep 17 00:00:00 2001 From: Alan Hamlett Date: Wed, 6 Jul 2016 23:07:55 +0200 Subject: [PATCH] handle unknown exceptions from requests library by deleting cached session object --- tests/test_offlinequeue.py | 38 ++++++++++++++++++++++++++++++++++++++ wakatime/main.py | 14 +++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/tests/test_offlinequeue.py b/tests/test_offlinequeue.py index c4d21d2..dfbfcab 100644 --- a/tests/test_offlinequeue.py +++ b/tests/test_offlinequeue.py @@ -5,14 +5,20 @@ from wakatime.main import execute from wakatime.offlinequeue import Queue from wakatime.packages import requests +import logging import os import sqlite3 import sys import tempfile import time +from testfixtures import log_capture from wakatime.compat import u from wakatime.packages.requests.models import Response from . import utils +try: + from mock import call +except ImportError: + from unittest.mock import call class OfflineQueueTestCase(utils.TestCase): @@ -170,3 +176,35 @@ class OfflineQueueTestCase(utils.TestCase): db_file = queue.get_db_file() expected = os.path.join(os.path.expanduser('~'), '.wakatime.db') self.assertEquals(db_file, expected) + + @log_capture() + def test_heartbeat_saved_when_requests_raises_exception(self, logs): + logging.disable(logging.NOTSET) + + with tempfile.NamedTemporaryFile() as fh: + with utils.mock.patch('wakatime.offlinequeue.Queue.get_db_file') as mock_db_file: + mock_db_file.return_value = fh.name + + self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].side_effect = AttributeError("'Retry' object has no attribute 'history'") + + now = u(int(time.time())) + entity = 'tests/samples/codefiles/twolinefile.txt' + config = 'tests/samples/configs/good_config.cfg' + + args = ['--file', entity, '--config', config, '--time', now] + execute(args) + + queue = Queue() + saved_heartbeat = queue.pop() + self.assertEquals(os.path.realpath(entity), saved_heartbeat['entity']) + + self.assertEquals(sys.stdout.getvalue(), '') + self.assertEquals(sys.stderr.getvalue(), '') + + output = [u(' ').join(x) for x in logs.actual()] + expected = u("AttributeError: \\'Retry\\' object has no attribute \\'history\\'") + self.assertIn(expected, output[0]) + + self.patched['wakatime.session_cache.SessionCache.get'].assert_called_once_with() + self.patched['wakatime.session_cache.SessionCache.delete'].assert_has_calls([call(), call()]) + self.patched['wakatime.session_cache.SessionCache.save'].assert_not_called() diff --git a/wakatime/main.py b/wakatime/main.py index 2494fd3..c0969b3 100644 --- a/wakatime/main.py +++ b/wakatime/main.py @@ -187,7 +187,7 @@ def parseArguments(): # update args from configs if not args.hostname: if configs.has_option('settings', 'hostname'): - args.hostname = configs.get('settings', 'hostname') + args.hostname = configs.get('settings', 'hostname') if not args.key: default_key = None if configs.has_option('settings', 'api_key'): @@ -388,6 +388,18 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None, log.warn(exception_data) else: log.error(exception_data) + + except: # delete cached session when requests raises unknown exception + exception_data = { + sys.exc_info()[0].__name__: u(sys.exc_info()[1]), + 'traceback': traceback.format_exc(), + } + if offline: + queue = Queue() + queue.push(data, json.dumps(stats), plugin) + log.warn(exception_data) + session_cache.delete() + else: code = response.status_code if response is not None else None content = response.text if response is not None else None