From ec8369a618f40e96892a12cc75221e38b3a72f12 Mon Sep 17 00:00:00 2001 From: David Alfonso Date: Tue, 17 Sep 2019 19:42:49 +0200 Subject: [PATCH 1/3] Add basic cli tests for date/time option --- tests/conftest.py | 6 ++ tests/test_cli.py | 174 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 tests/test_cli.py diff --git a/tests/conftest.py b/tests/conftest.py index 68221829..8d5eaa5d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ """Provide fixtures for pytest-based unit tests.""" +from click.testing import CliRunner import pytest from watson import Watson @@ -13,3 +14,8 @@ def config_dir(tmpdir): @pytest.fixture def watson(config_dir): return Watson(config_dir=config_dir) + + +@pytest.fixture +def runner(): + return CliRunner() diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 00000000..b7aa1d5c --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,174 @@ +import re +import arrow +from itertools import combinations +from datetime import datetime, timedelta +from dateutil.tz import tzlocal + +import pytest + +from watson import cli + + +# Not all ISO-8601 compliant strings are recognized by arrow.get(str) +VALID_DATES_DATA = [ + ('2018', '2018-01-01 00:00:00'), # years + ('2018-04', '2018-04-01 00:00:00'), # calendar dates + ('2018-04-10', '2018-04-10 00:00:00'), + ('2018/04/10', '2018-04-10 00:00:00'), + ('2018.04.10', '2018-04-10 00:00:00'), + ('2018-4-10', '2018-04-10 00:00:00'), + ('2018/4/10', '2018-04-10 00:00:00'), + ('2018.4.10', '2018-04-10 00:00:00'), + ('20180410', '2018-04-10 00:00:00'), + ('2018-123', '2018-05-03 00:00:00'), # ordinal dates + ('2018-04-10 12:30:43', '2018-04-10 12:30:43'), + ('2018-04-10T12:30:43', '2018-04-10 12:30:43'), + ('2018-04-10 12:30:43Z', '2018-04-10 12:30:43'), + ('2018-04-10 12:30:43.1233', '2018-04-10 12:30:43'), + ('2018-04-10 12:30:43+03:00', '2018-04-10 12:30:43'), + ('2018-04-10 12:30:43-07:00', '2018-04-10 12:30:43'), + ('2018-04-10T12:30:43-07:00', '2018-04-10 12:30:43'), + ('2018-04-10 12:30', '2018-04-10 12:30:00'), + ('2018-04-10T12:30', '2018-04-10 12:30:00'), + ('2018-04-10 12', '2018-04-10 12:00:00'), + ('2018-04-10T12', '2018-04-10 12:00:00'), +] + +INVALID_DATES_DATA = [ + (' 2018'), + ('2018 '), + ('201804'), + ('18-04-10'), + ('180410'), # truncated representation not allowed + ('2018-W08'), # despite week dates being part of ISO-8601 + ('2018W08'), + ('2018-W08-2'), + ('2018W082'), + ('hello 2018'), + ('yesterday'), + ('tomorrow'), + ('14:05:12.000'), # Times alone are not allowed + ('140512.000'), + ('14:05:12'), + ('140512'), + ('14:05'), + ('14.05'), + ('2018-04-10T'), + ('2018-04-10T12:30:43.'), +] + +VALID_TIMES_DATA = [ + ('14:12'), + ('14:12:43'), + ('2019-04-10T14:12'), + ('2019-04-10T14:12:43'), +] + + +class OutputParser: + FRAME_ID_PATTERN = re.compile(r'id: (?P[0-9a-f]+)') + + @staticmethod + def get_frame_id(output): + return OutputParser.FRAME_ID_PATTERN.search(output).group('frame_id') + + @staticmethod + def get_start_date(watson, output): + frame_id = OutputParser.get_frame_id(output) + return watson.frames[frame_id].start.format('YYYY-MM-DD HH:mm:ss') + + +# watson add + +@pytest.mark.parametrize('test_dt,expected', VALID_DATES_DATA) +def test_add_valid_date(runner, watson, test_dt, expected): + result = runner.invoke( + cli.add, + ['-f', test_dt, '-t', test_dt, 'project-name'], + obj=watson) + assert result.exit_code == 0 + assert OutputParser.get_start_date(watson, result.output) == expected + + +@pytest.mark.parametrize('test_dt', INVALID_DATES_DATA) +def test_add_invalid_date(runner, watson, test_dt): + result = runner.invoke(cli.add, + ['-f', test_dt, '-t', test_dt, 'project-name'], + obj=watson) + assert result.exit_code != 0 + + +# watson aggregate + +@pytest.mark.parametrize('test_dt,expected', VALID_DATES_DATA) +def test_aggregate_valid_date(runner, watson, test_dt, expected): + # This is super fast, because no internal 'report' invocations are made + result = runner.invoke(cli.aggregate, + ['-f', test_dt, '-t', test_dt], + obj=watson) + assert result.exit_code == 0 + + +@pytest.mark.parametrize('test_dt', INVALID_DATES_DATA) +def test_aggregate_invalid_date(runner, watson, test_dt): + # This is super fast, because no internal 'report' invocations are made + result = runner.invoke(cli.aggregate, + ['-f', test_dt, '-t', test_dt], + obj=watson) + assert result.exit_code != 0 + + +# watson log + +@pytest.mark.parametrize('cmd', [cli.aggregate, cli.log, cli.report]) +def test_incompatible_options(runner, watson, cmd): + name_interval_options = ['--' + s for s in cli._SHORTCUT_OPTIONS] + for opt1, opt2 in combinations(name_interval_options, 2): + result = runner.invoke(cmd, [opt1, opt2], obj=watson) + assert result.exit_code != 0 + + +@pytest.mark.parametrize('test_dt,expected', VALID_DATES_DATA) +def test_log_valid_date(runner, watson, test_dt, expected): + result = runner.invoke(cli.log, ['-f', test_dt, '-t', test_dt], obj=watson) + assert result.exit_code == 0 + + +@pytest.mark.parametrize('test_dt', INVALID_DATES_DATA) +def test_log_invalid_date(runner, watson, test_dt): + result = runner.invoke(cli.log, ['-f', test_dt, '-t', test_dt], obj=watson) + assert result.exit_code != 0 + + +# watson report + +@pytest.mark.parametrize('test_dt,expected', VALID_DATES_DATA) +def test_report_valid_date(runner, watson, test_dt, expected): + result = runner.invoke(cli.report, + ['-f', test_dt, '-t', test_dt], + obj=watson) + assert result.exit_code == 0 + + +@pytest.mark.parametrize('test_dt', INVALID_DATES_DATA) +def test_report_invalid_date(runner, watson, test_dt): + result = runner.invoke(cli.report, + ['-f', test_dt, '-t', test_dt], + obj=watson) + assert result.exit_code != 0 + + +# watson stop + +@pytest.mark.parametrize('at_dt', VALID_TIMES_DATA) +def test_stop_valid_time(runner, watson, mocker, at_dt): + mocker.patch('arrow.arrow.datetime', wraps=datetime) + start_dt = datetime(2019, 4, 10, 14, 0, 0, tzinfo=tzlocal()) + arrow.arrow.datetime.now.return_value = start_dt + result = runner.invoke(cli.start, ['a-project'], obj=watson) + assert result.exit_code == 0 + # Simulate one hour has elapsed, so that 'at_dt' is older than now() + # but newer than the start date. + arrow.arrow.datetime.now.return_value = (start_dt + timedelta(hours=1)) + result = runner.invoke(cli.stop, ['--at', at_dt], obj=watson) + assert result.exit_code == 0 From 3c4e1719cc9e66eb1d02fafc2313a1b789e52ed3 Mon Sep 17 00:00:00 2001 From: David Alfonso Date: Fri, 20 Sep 2019 13:34:05 +0200 Subject: [PATCH 2/3] Fix mock deprecation warning using mocker in tests --- tests/test_config.py | 20 ++--- tests/test_utils.py | 4 +- tests/test_watson.py | 204 ++++++++++++++++++++++--------------------- 3 files changed, 115 insertions(+), 113 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 59abedab..fda514e2 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -7,13 +7,13 @@ from . import mock_read -def test_config_get(mock, watson): +def test_config_get(mocker, watson): content = u""" [backend] url = foo token = """ - mock.patch.object(ConfigParser, 'read', mock_read(content)) + mocker.patch.object(ConfigParser, 'read', mock_read(content)) config = watson.config assert config.get('backend', 'url') == 'foo' assert config.get('backend', 'token') == '' @@ -23,7 +23,7 @@ def test_config_get(mock, watson): assert config.get('option', 'spamm', 'eggs') == 'eggs' -def test_config_getboolean(mock, watson): +def test_config_getboolean(mocker, watson): content = u""" [options] flag1 = 1 @@ -33,7 +33,7 @@ def test_config_getboolean(mock, watson): flag5 = false flag6 = """ - mock.patch.object(ConfigParser, 'read', mock_read(content)) + mocker.patch.object(ConfigParser, 'read', mock_read(content)) config = watson.config assert config.getboolean('options', 'flag1') is True assert config.getboolean('options', 'flag1', False) is True @@ -47,14 +47,14 @@ def test_config_getboolean(mock, watson): assert config.getboolean('options', 'missing', True) is True -def test_config_getint(mock, watson): +def test_config_getint(mocker, watson): content = u""" [options] value1 = 42 value2 = spamm value3 = """ - mock.patch.object(ConfigParser, 'read', mock_read(content)) + mocker.patch.object(ConfigParser, 'read', mock_read(content)) config = watson.config assert config.getint('options', 'value1') == 42 assert config.getint('options', 'value1', 666) == 42 @@ -71,7 +71,7 @@ def test_config_getint(mock, watson): config.getint('options', 'value3') -def test_config_getfloat(mock, watson): +def test_config_getfloat(mocker, watson): content = u""" [options] value1 = 3.14 @@ -80,7 +80,7 @@ def test_config_getfloat(mock, watson): value4 = """ - mock.patch.object(ConfigParser, 'read', mock_read(content)) + mocker.patch.object(ConfigParser, 'read', mock_read(content)) config = watson.config assert config.getfloat('options', 'value1') == 3.14 assert config.getfloat('options', 'value1', 6.66) == 3.14 @@ -98,7 +98,7 @@ def test_config_getfloat(mock, watson): config.getfloat('options', 'value4') -def test_config_getlist(mock, watson): +def test_config_getlist(mocker, watson): content = u""" # empty lines in option values (including the first one) are discarded [options] @@ -121,7 +121,7 @@ def test_config_getlist(mock, watson): two #three four # five """ - mock.patch.object(ConfigParser, 'read', mock_read(content)) + mocker.patch.object(ConfigParser, 'read', mock_read(content)) gl = watson.config.getlist assert gl('options', 'value1') == ['one', 'two three', 'four', 'five six'] diff --git a/tests/test_utils.py b/tests/test_utils.py index 4b84b974..d31793ba 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -134,7 +134,7 @@ def test_safe_save(config_dir): assert os.path.getmtime(save_file) >= os.path.getmtime(backup_file) -def test_safe_save_tmpfile_on_other_filesystem(config_dir, mock): +def test_safe_save_tmpfile_on_other_filesystem(config_dir, mocker): save_file = os.path.join(config_dir, 'test') backup_file = os.path.join(config_dir, 'test' + '.bak') @@ -148,7 +148,7 @@ def test_safe_save_tmpfile_on_other_filesystem(config_dir, mock): # simulate tmpfile being on another file-system # OSError is caught and handled by shutil.move() used by save_safe() - mock.patch('os.rename', side_effect=OSError) + mocker.patch('os.rename', side_effect=OSError) safe_save(save_file, "Again") assert os.path.exists(backup_file) diff --git a/tests/test_watson.py b/tests/test_watson.py index 632560fb..6486e957 100644 --- a/tests/test_watson.py +++ b/tests/test_watson.py @@ -28,8 +28,8 @@ @pytest.fixture -def json_mock(mock): - return mock.patch.object( +def json_mock(mocker): + return mocker.patch.object( json, 'dumps', side_effect=json.dumps, autospec=True ) @@ -39,105 +39,105 @@ def json_mock(mock): # current -def test_current(mock, watson): +def test_current(mocker, watson): content = json.dumps({'project': 'foo', 'start': 4000, 'tags': ['A', 'B']}) - mock.patch('%s.open' % builtins, mock.mock_open(read_data=content)) + mocker.patch('%s.open' % builtins, mocker.mock_open(read_data=content)) assert watson.current['project'] == 'foo' assert watson.current['start'] == arrow.get(4000) assert watson.current['tags'] == ['A', 'B'] -def test_current_with_empty_file(mock, watson): - mock.patch('%s.open' % builtins, mock.mock_open(read_data="")) - mock.patch('os.path.getsize', return_value=0) +def test_current_with_empty_file(mocker, watson): + mocker.patch('%s.open' % builtins, mocker.mock_open(read_data="")) + mocker.patch('os.path.getsize', return_value=0) assert watson.current == {} -def test_current_with_nonexistent_file(mock, watson): - mock.patch('%s.open' % builtins, side_effect=IOError) +def test_current_with_nonexistent_file(mocker, watson): + mocker.patch('%s.open' % builtins, side_effect=IOError) assert watson.current == {} -def test_current_watson_non_valid_json(mock, watson): +def test_current_watson_non_valid_json(mocker, watson): content = "{'foo': bar}" - mock.patch('%s.open' % builtins, mock.mock_open(read_data=content)) - mock.patch('os.path.getsize', return_value=len(content)) + mocker.patch('%s.open' % builtins, mocker.mock_open(read_data=content)) + mocker.patch('os.path.getsize', return_value=len(content)) with pytest.raises(WatsonError): watson.current -def test_current_with_given_state(config_dir, mock): +def test_current_with_given_state(config_dir, mocker): content = json.dumps({'project': 'foo', 'start': 4000}) watson = Watson(current={'project': 'bar', 'start': 4000}, config_dir=config_dir) - mock.patch('%s.open' % builtins, mock.mock_open(read_data=content)) + mocker.patch('%s.open' % builtins, mocker.mock_open(read_data=content)) assert watson.current['project'] == 'bar' -def test_current_with_empty_given_state(config_dir, mock): +def test_current_with_empty_given_state(config_dir, mocker): content = json.dumps({'project': 'foo', 'start': 4000}) watson = Watson(current=[], config_dir=config_dir) - mock.patch('%s.open' % builtins, mock.mock_open(read_data=content)) + mocker.patch('%s.open' % builtins, mocker.mock_open(read_data=content)) assert watson.current == {} # last_sync -def test_last_sync(mock, watson): +def test_last_sync(mocker, watson): now = arrow.get(4123) content = json.dumps(now.timestamp) - mock.patch('%s.open' % builtins, mock.mock_open(read_data=content)) + mocker.patch('%s.open' % builtins, mocker.mock_open(read_data=content)) assert watson.last_sync == now -def test_last_sync_with_empty_file(mock, watson): - mock.patch('%s.open' % builtins, mock.mock_open(read_data="")) - mock.patch('os.path.getsize', return_value=0) +def test_last_sync_with_empty_file(mocker, watson): + mocker.patch('%s.open' % builtins, mocker.mock_open(read_data="")) + mocker.patch('os.path.getsize', return_value=0) assert watson.last_sync == arrow.get(0) -def test_last_sync_with_nonexistent_file(mock, watson): - mock.patch('%s.open' % builtins, side_effect=IOError) +def test_last_sync_with_nonexistent_file(mocker, watson): + mocker.patch('%s.open' % builtins, side_effect=IOError) assert watson.last_sync == arrow.get(0) -def test_last_sync_watson_non_valid_json(mock, watson): +def test_last_sync_watson_non_valid_json(mocker, watson): content = "{'foo': bar}" - mock.patch('%s.open' % builtins, mock.mock_open(read_data=content)) - mock.patch('os.path.getsize', return_value=len(content)) + mocker.patch('%s.open' % builtins, mocker.mock_open(read_data=content)) + mocker.patch('os.path.getsize', return_value=len(content)) with pytest.raises(WatsonError): watson.last_sync -def test_last_sync_with_given_state(config_dir, mock): +def test_last_sync_with_given_state(config_dir, mocker): content = json.dumps(123) now = arrow.now() watson = Watson(last_sync=now, config_dir=config_dir) - mock.patch('%s.open' % builtins, mock.mock_open(read_data=content)) + mocker.patch('%s.open' % builtins, mocker.mock_open(read_data=content)) assert watson.last_sync == now -def test_last_sync_with_empty_given_state(config_dir, mock): +def test_last_sync_with_empty_given_state(config_dir, mocker): content = json.dumps(123) watson = Watson(last_sync=None, config_dir=config_dir) - mock.patch('%s.open' % builtins, mock.mock_open(read_data=content)) + mocker.patch('%s.open' % builtins, mocker.mock_open(read_data=content)) assert watson.last_sync == arrow.get(0) # frames -def test_frames(mock, watson): +def test_frames(mocker, watson): content = json.dumps([[4000, 4010, 'foo', None, ['A', 'B', 'C']]]) - mock.patch('%s.open' % builtins, mock.mock_open(read_data=content)) + mocker.patch('%s.open' % builtins, mocker.mock_open(read_data=content)) assert len(watson.frames) == 1 assert watson.frames[0].project == 'foo' assert watson.frames[0].start == arrow.get(4000) @@ -145,10 +145,10 @@ def test_frames(mock, watson): assert watson.frames[0].tags == ['A', 'B', 'C'] -def test_frames_without_tags(mock, watson): +def test_frames_without_tags(mocker, watson): content = json.dumps([[4000, 4010, 'foo', None]]) - mock.patch('%s.open' % builtins, mock.mock_open(read_data=content)) + mocker.patch('%s.open' % builtins, mocker.mock_open(read_data=content)) assert len(watson.frames) == 1 assert watson.frames[0].project == 'foo' assert watson.frames[0].start == arrow.get(4000) @@ -156,42 +156,42 @@ def test_frames_without_tags(mock, watson): assert watson.frames[0].tags == [] -def test_frames_with_empty_file(mock, watson): - mock.patch('%s.open' % builtins, mock.mock_open(read_data="")) - mock.patch('os.path.getsize', return_value=0) +def test_frames_with_empty_file(mocker, watson): + mocker.patch('%s.open' % builtins, mocker.mock_open(read_data="")) + mocker.patch('os.path.getsize', return_value=0) assert len(watson.frames) == 0 -def test_frames_with_nonexistent_file(mock, watson): - mock.patch('%s.open' % builtins, side_effect=IOError) +def test_frames_with_nonexistent_file(mocker, watson): + mocker.patch('%s.open' % builtins, side_effect=IOError) assert len(watson.frames) == 0 -def test_frames_watson_non_valid_json(mock, watson): +def test_frames_watson_non_valid_json(mocker, watson): content = "{'foo': bar}" - mock.patch('%s.open' % builtins, mock.mock_open(read_data=content)) - mock.patch('os.path.getsize', return_value=len(content)) + mocker.patch('%s.open' % builtins, mocker.mock_open(read_data=content)) + mocker.patch('os.path.getsize', return_value=len(content)) with pytest.raises(WatsonError): watson.frames -def test_given_frames(config_dir, mock): +def test_given_frames(config_dir, mocker): content = json.dumps([[4000, 4010, 'foo', None, ['A']]]) watson = Watson(frames=[[4000, 4010, 'bar', None, ['A', 'B']]], config_dir=config_dir) - mock.patch('%s.open' % builtins, mock.mock_open(read_data=content)) + mocker.patch('%s.open' % builtins, mocker.mock_open(read_data=content)) assert len(watson.frames) == 1 assert watson.frames[0].project == 'bar' assert watson.frames[0].tags == ['A', 'B'] -def test_frames_with_empty_given_state(config_dir, mock): +def test_frames_with_empty_given_state(config_dir, mocker): content = json.dumps([[0, 10, 'foo', None, ['A']]]) watson = Watson(frames=[], config_dir=config_dir) - mock.patch('%s.open' % builtins, mock.mock_open(read_data=content)) + mocker.patch('%s.open' % builtins, mocker.mock_open(read_data=content)) assert len(watson.frames) == 0 @@ -202,17 +202,17 @@ def test_empty_config_dir(): assert watson._dir == get_app_dir('watson') -def test_wrong_config(mock, watson): +def test_wrong_config(mocker, watson): content = u""" toto """ - mock.patch.object(ConfigParser, 'read', mock_read(content)) + mocker.patch.object(ConfigParser, 'read', mock_read(content)) with pytest.raises(ConfigurationError): watson.config -def test_empty_config(mock, watson): - mock.patch.object(ConfigParser, 'read', mock_read(u'')) +def test_empty_config(mocker, watson): + mocker.patch.object(ConfigParser, 'read', mock_read(u'')) assert len(watson.config.sections()) == 0 @@ -249,29 +249,29 @@ def test_start_two_projects(watson): assert watson.is_started is True -def test_start_default_tags(mock, watson): +def test_start_default_tags(mocker, watson): content = u""" [default_tags] my project = A B """ - mock.patch.object(ConfigParser, 'read', mock_read(content)) + mocker.patch.object(ConfigParser, 'read', mock_read(content)) watson.start('my project') assert watson.current['tags'] == ['A', 'B'] -def test_start_default_tags_with_supplementary_input_tags(mock, watson): +def test_start_default_tags_with_supplementary_input_tags(mocker, watson): content = u""" [default_tags] my project = A B """ - mock.patch.object(ConfigParser, 'read', mock_read(content)) + mocker.patch.object(ConfigParser, 'read', mock_read(content)) watson.start('my project', tags=['C', 'D']) assert watson.current['tags'] == ['C', 'D', 'A', 'B'] -def test_start_nogap(mock, watson): +def test_start_nogap(watson): watson.start('foo') watson.stop() @@ -348,17 +348,17 @@ def test_cancel_no_project(watson): # save -def test_save_without_changes(mock, watson, json_mock): - mock.patch('%s.open' % builtins, mock.mock_open()) +def test_save_without_changes(mocker, watson, json_mock): + mocker.patch('%s.open' % builtins, mocker.mock_open()) watson.save() assert not json_mock.called -def test_save_current(mock, watson, json_mock): +def test_save_current(mocker, watson, json_mock): watson.start('foo', ['A', 'B']) - mock.patch('%s.open' % builtins, mock.mock_open()) + mocker.patch('%s.open' % builtins, mocker.mock_open()) watson.save() assert json_mock.call_count == 1 @@ -368,10 +368,10 @@ def test_save_current(mock, watson, json_mock): assert result['tags'] == ['A', 'B'] -def test_save_current_without_tags(mock, watson, json_mock): +def test_save_current_without_tags(mocker, watson, json_mock): watson.start('foo') - mock.patch('%s.open' % builtins, mock.mock_open()) + mocker.patch('%s.open' % builtins, mocker.mock_open()) watson.save() assert json_mock.call_count == 1 @@ -384,10 +384,10 @@ def test_save_current_without_tags(mock, watson, json_mock): assert dump_args['ensure_ascii'] is False -def test_save_empty_current(config_dir, mock, json_mock): +def test_save_empty_current(config_dir, mocker, json_mock): watson = Watson(current={}, config_dir=config_dir) - mock.patch('%s.open' % builtins, mock.mock_open()) + mocker.patch('%s.open' % builtins, mocker.mock_open()) watson.current = {'project': 'foo', 'start': 4000} watson.save() @@ -404,21 +404,21 @@ def test_save_empty_current(config_dir, mock, json_mock): assert result == {} -def test_save_frames_no_change(config_dir, mock, json_mock): +def test_save_frames_no_change(config_dir, mocker, json_mock): watson = Watson(frames=[[4000, 4010, 'foo', None]], config_dir=config_dir) - mock.patch('%s.open' % builtins, mock.mock_open()) + mocker.patch('%s.open' % builtins, mocker.mock_open()) watson.save() assert not json_mock.called -def test_save_added_frame(config_dir, mock, json_mock): +def test_save_added_frame(config_dir, mocker, json_mock): watson = Watson(frames=[[4000, 4010, 'foo', None]], config_dir=config_dir) watson.frames.add('bar', 4010, 4020, ['A']) - mock.patch('%s.open' % builtins, mock.mock_open()) + mocker.patch('%s.open' % builtins, mocker.mock_open()) watson.save() assert json_mock.call_count == 1 @@ -430,12 +430,12 @@ def test_save_added_frame(config_dir, mock, json_mock): assert result[1][4] == ['A'] -def test_save_changed_frame(config_dir, mock, json_mock): +def test_save_changed_frame(config_dir, mocker, json_mock): watson = Watson(frames=[[4000, 4010, 'foo', None, ['A']]], config_dir=config_dir) watson.frames[0] = ('bar', 4000, 4010, ['A', 'B']) - mock.patch('%s.open' % builtins, mock.mock_open()) + mocker.patch('%s.open' % builtins, mocker.mock_open()) watson.save() assert json_mock.call_count == 1 @@ -448,51 +448,51 @@ def test_save_changed_frame(config_dir, mock, json_mock): assert dump_args['ensure_ascii'] is False -def test_save_config_no_changes(mock, watson): - mock.patch('%s.open' % builtins, mock.mock_open()) - write_mock = mock.patch.object(ConfigParser, 'write') +def test_save_config_no_changes(mocker, watson): + mocker.patch('%s.open' % builtins, mocker.mock_open()) + write_mock = mocker.patch.object(ConfigParser, 'write') watson.save() assert not write_mock.called -def test_save_config(mock, watson): - mock.patch('%s.open' % builtins, mock.mock_open()) - write_mock = mock.patch.object(ConfigParser, 'write') +def test_save_config(mocker, watson): + mocker.patch('%s.open' % builtins, mocker.mock_open()) + write_mock = mocker.patch.object(ConfigParser, 'write') watson.config = ConfigParser() watson.save() assert write_mock.call_count == 1 -def test_save_last_sync(mock, watson, json_mock): +def test_save_last_sync(mocker, watson, json_mock): now = arrow.now() watson.last_sync = now - mock.patch('%s.open' % builtins, mock.mock_open()) + mocker.patch('%s.open' % builtins, mocker.mock_open()) watson.save() assert json_mock.call_count == 1 assert json_mock.call_args[0][0] == now.timestamp -def test_save_empty_last_sync(config_dir, mock, json_mock): +def test_save_empty_last_sync(config_dir, mocker, json_mock): watson = Watson(last_sync=arrow.now(), config_dir=config_dir) watson.last_sync = None - mock.patch('%s.open' % builtins, mock.mock_open()) + mocker.patch('%s.open' % builtins, mocker.mock_open()) watson.save() assert json_mock.call_count == 1 assert json_mock.call_args[0][0] == 0 -def test_watson_save_calls_safe_save(mock, config_dir, watson): +def test_watson_save_calls_safe_save(mocker, config_dir, watson): frames_file = os.path.join(config_dir, 'frames') watson.start('foo', tags=['A', 'B']) watson.stop() - save_mock = mock.patch('watson.watson.safe_save') + save_mock = mocker.patch('watson.watson.safe_save') watson.save() assert watson._frames.changed @@ -531,7 +531,7 @@ def test_push_with_no_token(watson): watson.push(arrow.now()) -def test_push(mock, watson): +def test_push(mocker, watson): config = ConfigParser() config.add_section('backend') config.set('backend', 'url', 'http://foo.com') @@ -550,7 +550,7 @@ def test_push(mock, watson): watson.frames.add('foo', 4001, 4002) watson.frames.add('bar', 4003, 4004) - mock.patch.object(watson, '_get_remote_projects', return_value=[ + mocker.patch.object(watson, '_get_remote_projects', return_value=[ {'name': 'foo', 'id': '08288b71-4500-40dd-96b1-a995937a15fd'}, {'name': 'bar', 'id': 'f0534272-65fa-4832-a49e-0eedf68b3a84'}, {'name': 'lol', 'id': '7fdaf65e-66bd-4c01-b09e-74bdc8cbe552'}, @@ -560,14 +560,15 @@ class Response: def __init__(self): self.status_code = 201 - mock_put = mock.patch('requests.post', return_value=Response()) - mock.patch.object(Watson, 'config', new_callable=mock.PropertyMock, - return_value=config) + mock_put = mocker.patch('requests.post', return_value=Response()) + mocker.patch.object( + Watson, 'config', new_callable=mocker.PropertyMock, return_value=config + ) watson.push(last_pull) requests.post.assert_called_once_with( - mock.ANY, - mock.ANY, + mocker.ANY, + mocker.ANY, headers={ 'content-type': 'application/json', 'Authorization': "Token " + config.get('backend', 'token') @@ -614,7 +615,7 @@ def test_pull_with_no_token(watson): watson.pull() -def test_pull(mock, watson): +def test_pull(mocker, watson): config = ConfigParser() config.add_section('backend') config.set('backend', 'url', 'http://foo.com') @@ -626,7 +627,7 @@ def test_pull(mock, watson): 'foo', 4001, 4002, ['A', 'B'], id='1c006c6e6cc14c80ab22b51c857c0b06' ) - mock.patch.object(watson, '_get_remote_projects', return_value=[ + mocker.patch.object(watson, '_get_remote_projects', return_value=[ {'name': 'foo', 'id': '08288b71-4500-40dd-96b1-a995937a15fd'}, {'name': 'bar', 'id': 'f0534272-65fa-4832-a49e-0eedf68b3a84'}, ]) @@ -653,13 +654,14 @@ def json(self): } ] - mock.patch('requests.get', return_value=Response()) - mock.patch.object(Watson, 'config', new_callable=mock.PropertyMock, - return_value=config) + mocker.patch('requests.get', return_value=Response()) + mocker.patch.object( + Watson, 'config', new_callable=mocker.PropertyMock, return_value=config + ) watson.pull() requests.get.assert_called_once_with( - mock.ANY, + mocker.ANY, params={'last_sync': watson.last_sync}, headers={ 'content-type': 'application/json', @@ -795,8 +797,8 @@ def test_report(watson): watson.report(arrow.now(), arrow.now(), tags=["A"], ignore_tags=["A"]) -def test_report_current(mock, config_dir): - mock.patch('arrow.utcnow', return_value=arrow.get(5000)) +def test_report_current(mocker, config_dir): + mocker.patch('arrow.utcnow', return_value=arrow.get(5000)) watson = Watson( current={'project': 'foo', 'start': 4000}, @@ -823,7 +825,7 @@ def test_report_current(mock, config_dir): # renaming project updates frame last_updated time -def test_rename_project_with_time(mock, watson): +def test_rename_project_with_time(watson): """ Renaming a project should update the "last_updated" time on any frame that contains that project. @@ -857,7 +859,7 @@ def test_rename_project_with_time(mock, watson): assert watson.frames[1].updated_at.timestamp == 4035 -def test_rename_tag_with_time(mock, watson): +def test_rename_tag_with_time(watson): """ Renaming a tag should update the "last_updated" time on any frame that contains that tag. @@ -893,7 +895,7 @@ def test_rename_tag_with_time(mock, watson): # add -def test_add_success(mock, watson): +def test_add_success(watson): """ Adding a new frame outside of live tracking successfully """ @@ -905,7 +907,7 @@ def test_add_success(mock, watson): assert 'fuu' in watson.frames[0].tags -def test_add_failure(mock, watson): +def test_add_failure(watson): """ Adding a new frame outside of live tracking fails when to date is before from date @@ -915,7 +917,7 @@ def test_add_failure(mock, watson): from_date=7000, to_date=6000) -def test_validate_report_options(mock, watson): +def test_validate_report_options(watson): assert watson._validate_report_options(["project_foo"], None) assert watson._validate_report_options(None, ["project_foo"]) assert not watson._validate_report_options(["project_foo"], From 851abe266b28e7a75bd631602586faa9862f1794 Mon Sep 17 00:00:00 2001 From: David Alfonso Date: Sun, 22 Sep 2019 12:03:27 +0200 Subject: [PATCH 3/3] Fix PythonUnknownMarkWarning then running tests Pytest does not recognize the 'datafiles' mark provided by the corresponding plugin. The solution is to register this mark as a custom one in pytest.ini, as suggested by the warning message itself. --- pytest.ini | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 pytest.ini diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..04a84cb1 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +markers = + datafiles: pytest-datafiles plugin marker. This avoids warning message.