From 0f5ad22fba7fd98dc777ae70943843c75dce818d Mon Sep 17 00:00:00 2001 From: Pradeep Tammali Date: Sat, 13 Apr 2024 13:36:39 +0200 Subject: [PATCH] fix: remove include from config since source is used in pytest-cov add branch coverage to `make test` more test cases to cover branch coverage --- Makefile | 2 +- codecov/main.py | 4 +- pyproject.toml | 3 - tests/test_coverage.py | 11 ++ tests/test_main.py | 222 +++++++++++++++++++++++++++++++---------- tests/test_template.py | 28 ++++++ 6 files changed, 210 insertions(+), 60 deletions(-) diff --git a/Makefile b/Makefile index 42c7d22..fce5dbb 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ lint: pipenv run pre-commit run --all-files test: - pipenv run pytest tests/* --cov=codecov --cov-report=term-missing + pipenv run pytest tests/* --cov-branch --cov=codecov --cov-report=term-missing report: pipenv run pytest tests --cov-branch --cov=codecov --cov-report=term-missing --cov-report=json:/tmp/report.json diff --git a/codecov/main.py b/codecov/main.py index 1b25a48..19228d3 100644 --- a/codecov/main.py +++ b/codecov/main.py @@ -153,7 +153,7 @@ def process_pr( # pylint: disable=too-many-locals log.info( 'Cannot post comment. This is probably because of body contents reached maximum allowed length in the comment' ) - else: - log.debug('Comment created on PR') + return 1 + log.debug('Comment created on PR') return 0 diff --git a/pyproject.toml b/pyproject.toml index 828dd15..1693969 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,9 +85,6 @@ ignore = ['E501', 'W503', 'W504'] [tool.pytest.ini_options] env = ['APP_ENVIRONMENT = unittest'] -[tool.coverage.run] -include = ['codecov/*'] - [tool.coverage.report] show_missing = true diff --git a/tests/test_coverage.py b/tests/test_coverage.py index 583d84d..6654165 100644 --- a/tests/test_coverage.py +++ b/tests/test_coverage.py @@ -256,6 +256,17 @@ def test_get_diff_coverage_info(make_coverage_obj, added_lines, update_obj, expe pathlib.Path('test.py'): [7], }, ), + ( + 'diff --git a/test.py b/test.py\n' + 'index 1111111..2222222 100644\n' + '--- a/test.py\n' + '+++ b/test.py\n' + '@@ -5,5 +5,7 @@ def calculate_sum(a, b):\n' + ' return a + b\n' + ' def test_calculate_sum():\n' + ' assert calculate_sum(-1, 1) == 0\n', + {}, + ), ], ) def test_parse_line_number_diff_line(line_number_diff_line, expected): diff --git a/tests/test_main.py b/tests/test_main.py index 3bde5c8..adf53b9 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import json import pathlib +import tempfile from unittest import mock import pytest @@ -8,74 +9,110 @@ from codecov import github, main -@mock.patch('codecov.main.settings.Config.from_environ') -@mock.patch('codecov.main.log.setup') -@mock.patch('codecov.main.sys.exit') -@mock.patch('codecov.main.httpx.Client') -@mock.patch('codecov.main.action') -def test_main_success(mock_action, mock_httpx_client, mock_sys_exit, mock_log_setup, mock_config_from_environ): - mock_config = mock_config_from_environ.return_value - mock_github_session = mock_httpx_client.return_value - mock_action.return_value = 0 +@mock.patch('pathlib.Path.open') +def test_process_pr_skip_coverage( + mock_open: mock.Mock, + base_config, + gh, + coverage_json, + session, + caplog, +): + config = base_config(SKIP_COVERAGE=True) + caplog.set_level('INFO') + mock_open.return_value.__enter__.return_value.read.return_value = json.dumps(coverage_json) + diff_data = 'diff --git a/file.py b/file.py\nindex 1234567..abcdefg 100644\n--- a/file.py\n+++ b/file.py\n@@ -1,2 +1,2 @@\n-foo\n+bar\n-baz\n+qux\n' + session.register('GET', f'/repos/{config.GITHUB_REPOSITORY}/pulls/{config.GITHUB_PR_NUMBER}')(text=diff_data) - main.main() + repo_info = github.RepositoryInfo(default_branch='main', visibility='public') + result = main.process_pr(config, gh, repo_info, config.GITHUB_PR_NUMBER) - mock_config_from_environ.assert_called_once_with(environ=mock.ANY) - mock_log_setup.assert_called_once_with(debug=mock_config.DEBUG) - mock_action.assert_called_once_with(config=mock_config, github_session=mock_github_session) - mock_sys_exit.assert_called_once_with(0) + assert result == 0 + assert caplog.records[-1].message == 'Skipping coverage report generation' -@mock.patch('codecov.main.settings.Config.from_environ') -def test_main_skip_coverage(mock_config_from_environ, base_config): - mock_config_from_environ.return_value = base_config(SKIP_COVERAGE=True) - with pytest.raises(SystemExit): - main.main() +@mock.patch('pathlib.Path.open') +def test_process_pr_skip_coverage_with_annotations( + mock_open: mock.Mock, + base_config, + gh, + coverage_json, + session, + caplog, +): + config = base_config( + SKIP_COVERAGE=True, + ANNOTATE_MISSING_LINES=True, + ) + caplog.set_level('INFO') + mock_open.return_value.__enter__.return_value.read.return_value = json.dumps(coverage_json) + diff_data = 'diff --git a/file.py b/file.py\nindex 1234567..abcdefg 100644\n--- a/file.py\n+++ b/file.py\n@@ -1,2 +1,2 @@\n-foo\n+bar\n-baz\n+qux\n' + session.register('GET', f'/repos/{config.GITHUB_REPOSITORY}/pulls/{config.GITHUB_PR_NUMBER}')(text=diff_data) + repo_info = github.RepositoryInfo(default_branch='main', visibility='public') + result = main.process_pr(config, gh, repo_info, config.GITHUB_PR_NUMBER) -@mock.patch('codecov.main.settings.Config.from_environ') -@mock.patch('codecov.main.sys.exit') -@mock.patch('codecov.main.httpx.Client') -@mock.patch('codecov.main.action') -def test_main_exception(mock_action, mock_httpx_client, mock_sys_exit, mock_config_from_environ): - mock_config = mock_config_from_environ.return_value - mock_github_session = mock_httpx_client.return_value - mock_action.side_effect = Exception() + assert result == 0 - main.main() - mock_config_from_environ.assert_called_once_with(environ=mock.ANY) - mock_action.assert_called_once_with(config=mock_config, github_session=mock_github_session) - mock_sys_exit.assert_called_once_with(1) +@mock.patch('pathlib.Path.open') +@mock.patch('codecov.template.read_template_file') +def test_process_pr_with_annotations_missing_marker_error( + mock_read_template_file: mock.Mock, + mock_open: mock.Mock, + base_config, + gh, + coverage_json, + session, +): + config = base_config(SUBPROJECT_ID='sub_project') + mock_read_template_file.return_value = """{% block foo %}foo{% endblock foo %}""" + mock_open.return_value.__enter__.return_value.read.return_value = json.dumps(coverage_json) + diff_data = 'diff --git a/file.py b/file.py\nindex 1234567..abcdefg 100644\n--- a/file.py\n+++ b/file.py\n@@ -1,2 +1,2 @@\n-foo\n+bar\n-baz\n+qux\n' + session.register('GET', f'/repos/{config.GITHUB_REPOSITORY}/pulls/{config.GITHUB_PR_NUMBER}')(text=diff_data) + session.register('GET', '/user')(json={'login': 'foo'}) + repo_info = github.RepositoryInfo(default_branch='main', visibility='public') + result = main.process_pr(config, gh, repo_info, config.GITHUB_PR_NUMBER) -def test_action_pull_request_success(session, base_config): - config = base_config() - main.process_pr = mock.Mock(return_value=0) - session.register('GET', f'/repos/{config.GITHUB_REPOSITORY}/pulls/{config.GITHUB_PR_NUMBER}')( - json={'number': config.GITHUB_PR_NUMBER, 'state': 'open'} - ) - session.register('GET', f'/repos/{config.GITHUB_REPOSITORY}')( - json={'default_branch': 'baz', 'visibility': 'public'} - ) + assert result == 1 - result = main.action(config=config, github_session=session) - assert result == 0 +@mock.patch('pathlib.Path.open') +@mock.patch('codecov.main.template.read_template_file') +def test_process_pr_with_annotations_template_error( + mock_read_template_file: mock.Mock, + mock_open: mock.Mock, + base_config, + gh, + coverage_json, + session, +): + config = base_config( + ANNOTATE_MISSING_LINES=True, + ANNOTATIONS_OUTPUT_PATH=pathlib.Path(tempfile.mkstemp(suffix='.json')[1]), + SUBPROJECT_ID='sub_project', + MINIMUM_GREEN=100, + MINIMUM_ORANGE=80, + COMPLETE_PROJECT_REPORT=True, + COVERAGE_REPORT_URL='https://example.com', + ) + mock_read_template_file.return_value = '{% for i in range(5) %}{{ i }{% endfor %}' + mock_open.return_value.__enter__.return_value.read.return_value = json.dumps(coverage_json) + diff_data = 'diff --git a/file.py b/file.py\nindex 1234567..abcdefg 100644\n--- a/file.py\n+++ b/file.py\n@@ -1,2 +1,2 @@\n-foo\n+bar\n-baz\n+qux\n' + session.register('GET', f'/repos/{config.GITHUB_REPOSITORY}/pulls/{config.GITHUB_PR_NUMBER}')(text=diff_data) + session.register('GET', '/user')(json={'login': 'foo'}) + repo_info = github.RepositoryInfo(default_branch='main', visibility='public') + result = main.process_pr(config, gh, repo_info, config.GITHUB_PR_NUMBER) -def test_action_pull_request_failed(session, base_config): - config = base_config() - session.register('GET', f'/repos/{config.GITHUB_REPOSITORY}/pulls/{config.GITHUB_PR_NUMBER}')(status_code=404) - result = main.action(config=config, github_session=session) assert result == 1 -# TODO: Fix the test cases below @mock.patch('pathlib.Path.open') @mock.patch('codecov.main.template.read_template_file') @mock.patch('codecov.main.github.post_comment') -def test_process_pr_with_annotations( +def test_process_pr_with_annotations_cannot_post( mock_post_comment: mock.Mock, mock_read_template_file: mock.Mock, mock_open: mock.Mock, @@ -86,14 +123,14 @@ def test_process_pr_with_annotations( ): config = base_config( ANNOTATE_MISSING_LINES=True, - ANNOTATIONS_OUTPUT_PATH=pathlib.Path('output.json'), + ANNOTATIONS_OUTPUT_PATH=pathlib.Path(tempfile.mkstemp(suffix='.json')[1]), SUBPROJECT_ID='sub_project', ) mock_read_template_file.return_value = """ {% block foo %}foo{% endblock foo %} {{ marker }} """ - mock_post_comment.return_value = None + mock_post_comment.side_effect = github.CannotPostComment mock_open.return_value.__enter__.return_value.read.return_value = json.dumps(coverage_json) diff_data = 'diff --git a/file.py b/file.py\nindex 1234567..abcdefg 100644\n--- a/file.py\n+++ b/file.py\n@@ -1,2 +1,2 @@\n-foo\n+bar\n-baz\n+qux\n' session.register('GET', f'/repos/{config.GITHUB_REPOSITORY}/pulls/{config.GITHUB_PR_NUMBER}')(text=diff_data) @@ -102,27 +139,104 @@ def test_process_pr_with_annotations( repo_info = github.RepositoryInfo(default_branch='main', visibility='public') result = main.process_pr(config, gh, repo_info, config.GITHUB_PR_NUMBER) - assert result == 0 + assert result == 1 + mock_post_comment.assert_called_once() @mock.patch('pathlib.Path.open') -def test_process_pr_with_annotations_skip_coverage( +@mock.patch('codecov.main.template.read_template_file') +@mock.patch('codecov.main.github.post_comment') +def test_process_pr_with_annotations( + mock_post_comment: mock.Mock, + mock_read_template_file: mock.Mock, mock_open: mock.Mock, base_config, gh, coverage_json, session, + caplog, ): config = base_config( ANNOTATE_MISSING_LINES=True, - ANNOTATIONS_OUTPUT_PATH=pathlib.Path('output.json'), - SKIP_COVERAGE=True, + ANNOTATIONS_OUTPUT_PATH=pathlib.Path(tempfile.mkstemp(suffix='.json')[1]), + SUBPROJECT_ID='sub_project', ) + caplog.set_level('DEBUG') + mock_read_template_file.return_value = """ + {% block foo %}foo{% endblock foo %} + {{ marker }} + """ + mock_post_comment.return_value = None mock_open.return_value.__enter__.return_value.read.return_value = json.dumps(coverage_json) diff_data = 'diff --git a/file.py b/file.py\nindex 1234567..abcdefg 100644\n--- a/file.py\n+++ b/file.py\n@@ -1,2 +1,2 @@\n-foo\n+bar\n-baz\n+qux\n' session.register('GET', f'/repos/{config.GITHUB_REPOSITORY}/pulls/{config.GITHUB_PR_NUMBER}')(text=diff_data) + session.register('GET', '/user')(json={'login': 'foo'}) repo_info = github.RepositoryInfo(default_branch='main', visibility='public') result = main.process_pr(config, gh, repo_info, config.GITHUB_PR_NUMBER) assert result == 0 + assert caplog.records[-1].message == 'Comment created on PR' + + +@mock.patch('codecov.main.settings.Config.from_environ') +@mock.patch('codecov.main.log.setup') +@mock.patch('codecov.main.sys.exit') +@mock.patch('codecov.main.httpx.Client') +@mock.patch('codecov.main.action') +def test_main_success(mock_action, mock_httpx_client, mock_sys_exit, mock_log_setup, mock_config_from_environ): + mock_config = mock_config_from_environ.return_value + mock_github_session = mock_httpx_client.return_value + mock_action.return_value = 0 + + main.main() + + mock_config_from_environ.assert_called_once_with(environ=mock.ANY) + mock_log_setup.assert_called_once_with(debug=mock_config.DEBUG) + mock_action.assert_called_once_with(config=mock_config, github_session=mock_github_session) + mock_sys_exit.assert_called_once_with(0) + + +@mock.patch('codecov.main.settings.Config.from_environ') +def test_main_skip_coverage(mock_config_from_environ, base_config): + mock_config_from_environ.return_value = base_config(SKIP_COVERAGE=True) + with pytest.raises(SystemExit): + main.main() + + +@mock.patch('codecov.main.settings.Config.from_environ') +@mock.patch('codecov.main.sys.exit') +@mock.patch('codecov.main.httpx.Client') +@mock.patch('codecov.main.action') +def test_main_exception(mock_action, mock_httpx_client, mock_sys_exit, mock_config_from_environ): + mock_config = mock_config_from_environ.return_value + mock_github_session = mock_httpx_client.return_value + mock_action.side_effect = Exception() + + main.main() + + mock_config_from_environ.assert_called_once_with(environ=mock.ANY) + mock_action.assert_called_once_with(config=mock_config, github_session=mock_github_session) + mock_sys_exit.assert_called_once_with(1) + + +def test_action_pull_request_success(session, base_config): + config = base_config() + main.process_pr = mock.Mock(return_value=0) + session.register('GET', f'/repos/{config.GITHUB_REPOSITORY}/pulls/{config.GITHUB_PR_NUMBER}')( + json={'number': config.GITHUB_PR_NUMBER, 'state': 'open'} + ) + session.register('GET', f'/repos/{config.GITHUB_REPOSITORY}')( + json={'default_branch': 'baz', 'visibility': 'public'} + ) + + result = main.action(config=config, github_session=session) + + assert result == 0 + + +def test_action_pull_request_failed(session, base_config): + config = base_config() + session.register('GET', f'/repos/{config.GITHUB_REPOSITORY}/pulls/{config.GITHUB_PR_NUMBER}')(status_code=404) + result = main.action(config=config, github_session=session) + assert result == 1 diff --git a/tests/test_template.py b/tests/test_template.py index 8712643..b9057d1 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -566,6 +566,34 @@ def test_get_file_url_base(): ) +def test_get_file_url_pr_base(): + filename = pathlib.Path('test_file.py') + lines = (1, 10) + repo_name = 'my_repo' + pr_number = 123 + base_ref = 'main' + + expected_url = f'https://github.com/{repo_name}/blob/{base_ref}/{str(filename)}#L1-L10' + assert ( + template.get_file_url(filename, lines, True, repo_name=repo_name, pr_number=pr_number, base_ref=base_ref) + == expected_url + ) + + +def test_get_file_url_pr_base_no_lines(): + filename = pathlib.Path('test_file.py') + lines = None + repo_name = 'my_repo' + pr_number = 123 + base_ref = 'main' + + expected_url = f'https://github.com/{repo_name}/blob/{base_ref}/{str(filename)}' + assert ( + template.get_file_url(filename, lines, True, repo_name=repo_name, pr_number=pr_number, base_ref=base_ref) + == expected_url + ) + + def test_get_file_url_pr(): filename = pathlib.Path('test_file.py') lines = (1, 10)