diff --git a/README.md b/README.md index 31ea33b9..c9f1ef4d 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,10 @@ jobs: contents: write steps: - uses: actions/checkout@v4 + with: + # New lines are discovered by running a three-dot notation diff on base_ref...head. They must share a common + # ancestor. Setting fetch-depth to 1000 here should ensure that. + fetch-depth: 1000 - name: Install everything, run the tests, produce the .coverage file run: make test # This is the part where you put your own test command @@ -205,6 +209,8 @@ jobs: contents: write steps: - uses: actions/checkout@v4 + with: + fetch-depth: 1000 - name: Install everything, run the tests, produce the .coverage file run: make test # This is the part where you put your own test command @@ -247,6 +253,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + fetch-depth: 1000 - name: Set up Python id: setup-python @@ -284,6 +292,8 @@ jobs: contents: write steps: - uses: actions/checkout@v4 + with: + fetch-depth: 1000 - uses: actions/download-artifact@v4 id: download diff --git a/coverage_comment/coverage.py b/coverage_comment/coverage.py index 591e9de9..2cb4699a 100644 --- a/coverage_comment/coverage.py +++ b/coverage_comment/coverage.py @@ -293,7 +293,7 @@ def get_added_lines( # don't merge chunks. This means the headers that describe line number # are always enough to derive what line numbers were added. git.fetch("origin", base_ref, "--depth=1000") - diff = git.diff("--unified=0", "FETCH_HEAD", "--", ".") + diff = git.diff("--unified=0", "FETCH_HEAD...HEAD") return parse_diff_output(diff) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py new file mode 100644 index 00000000..d6aace95 --- /dev/null +++ b/tests/integration/conftest.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +import os +import pathlib +import subprocess +import uuid + +import pytest + + +@pytest.fixture +def in_integration_env(integration_env, integration_dir): + curdir = os.getcwd() + os.chdir(integration_dir) + yield integration_dir + os.chdir(curdir) + + +@pytest.fixture +def integration_dir(tmp_path: pathlib.Path): + test_dir = tmp_path / "integration_test" + test_dir.mkdir() + return test_dir + + +@pytest.fixture +def file_path(integration_dir): + return integration_dir / "foo.py" + + +@pytest.fixture +def write_file(file_path): + def _(*variables): + content = "import os" + for i, var in enumerate(variables): + content += f"""\nif os.environ.get("{var}"):\n {i}\n""" + file_path.write_text(content, encoding="utf8") + + return _ + + +@pytest.fixture +def run_coverage(file_path, integration_dir): + def _(*variables): + subprocess.check_call( + ["coverage", "run", "--parallel", file_path.name], + cwd=integration_dir, + env=os.environ | dict.fromkeys(variables, "1"), + ) + + return _ + + +@pytest.fixture +def commit(integration_dir): + def _(): + subprocess.check_call( + ["git", "add", "."], + cwd=integration_dir, + ) + subprocess.check_call( + ["git", "commit", "-m", str(uuid.uuid4())], + cwd=integration_dir, + env={ + "GIT_AUTHOR_NAME": "foo", + "GIT_AUTHOR_EMAIL": "foo", + "GIT_COMMITTER_NAME": "foo", + "GIT_COMMITTER_EMAIL": "foo", + "GIT_CONFIG_GLOBAL": "/dev/null", + "GIT_CONFIG_SYSTEM": "/dev/null", + }, + ) + + return _ + + +@pytest.fixture +def integration_env(integration_dir, write_file, run_coverage, commit, request): + subprocess.check_call(["git", "init", "-b", "main"], cwd=integration_dir) + # diff coverage reads the "origin/{...}" branch so we simulate an origin remote + subprocess.check_call(["git", "remote", "add", "origin", "."], cwd=integration_dir) + write_file("A", "B") + commit() + + add_branch_mark = request.node.get_closest_marker("add_branches") + for additional_branch in add_branch_mark.args if add_branch_mark else []: + subprocess.check_call( + ["git", "switch", "-c", additional_branch], + cwd=integration_dir, + ) + + subprocess.check_call( + ["git", "switch", "-c", "branch"], + cwd=integration_dir, + ) + + write_file("A", "B", "C", "D") + commit() + + run_coverage("A", "C") + subprocess.check_call(["git", "fetch", "origin"], cwd=integration_dir) diff --git a/tests/integration/test_coverage.py b/tests/integration/test_coverage.py new file mode 100644 index 00000000..5243f7b7 --- /dev/null +++ b/tests/integration/test_coverage.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +import subprocess + +from coverage_comment import coverage +from coverage_comment import subprocess as subprocess_module + + +def test_get_added_lines( + commit, file_path, integration_dir, in_integration_env, write_file +): + """ + Lines added in the base_ref should not appear as added in HEAD + """ + git = subprocess_module.Git() + relative_file_path = file_path.relative_to(integration_dir) + + assert coverage.get_added_lines(git, "main") == { + relative_file_path: list(range(7, 13)) # Line numbers start at 1 + } + + subprocess.check_call(["git", "switch", "main"], cwd=integration_dir) + write_file("E", "F") + commit() + subprocess.check_call(["git", "push", "origin", "main"], cwd=integration_dir) + subprocess.check_call(["git", "switch", "branch"], cwd=integration_dir) + + assert coverage.get_added_lines(git, "main") == { + relative_file_path: list(range(7, 13)) # Line numbers start at 1 + } diff --git a/tests/integration/test_main.py b/tests/integration/test_main.py index f925e436..5729233b 100644 --- a/tests/integration/test_main.py +++ b/tests/integration/test_main.py @@ -1,59 +1,12 @@ from __future__ import annotations import json -import os import pathlib -import subprocess -import uuid import pytest from coverage_comment import main - -@pytest.fixture -def in_integration_env(integration_env, integration_dir): - curdir = os.getcwd() - os.chdir(integration_dir) - yield integration_dir - os.chdir(curdir) - - -@pytest.fixture -def integration_dir(tmp_path: pathlib.Path): - test_dir = tmp_path / "integration_test" - test_dir.mkdir() - return test_dir - - -@pytest.fixture -def file_path(integration_dir): - return integration_dir / "foo.py" - - -@pytest.fixture -def write_file(file_path): - def _(*variables): - content = "import os" - for i, var in enumerate(variables): - content += f"""\nif os.environ.get("{var}"):\n {i}\n""" - file_path.write_text(content, encoding="utf8") - - return _ - - -@pytest.fixture -def run_coverage(file_path, integration_dir): - def _(*variables): - subprocess.check_call( - ["coverage", "run", "--parallel", file_path.name], - cwd=integration_dir, - env=os.environ | dict.fromkeys(variables, "1"), - ) - - return _ - - DIFF_STDOUT = """diff --git a/foo.py b/foo.py index 6c08c94..b65c612 100644 --- a/foo.py @@ -68,56 +21,6 @@ def _(*variables): """ -@pytest.fixture -def commit(integration_dir): - def _(): - subprocess.check_call( - ["git", "add", "."], - cwd=integration_dir, - ) - subprocess.check_call( - ["git", "commit", "-m", str(uuid.uuid4())], - cwd=integration_dir, - env={ - "GIT_AUTHOR_NAME": "foo", - "GIT_AUTHOR_EMAIL": "foo", - "GIT_COMMITTER_NAME": "foo", - "GIT_COMMITTER_EMAIL": "foo", - "GIT_CONFIG_GLOBAL": "/dev/null", - "GIT_CONFIG_SYSTEM": "/dev/null", - }, - ) - - return _ - - -@pytest.fixture -def integration_env(integration_dir, write_file, run_coverage, commit, request): - subprocess.check_call(["git", "init", "-b", "main"], cwd=integration_dir) - # diff coverage reads the "origin/{...}" branch so we simulate an origin remote - subprocess.check_call(["git", "remote", "add", "origin", "."], cwd=integration_dir) - write_file("A", "B") - commit() - - add_branch_mark = request.node.get_closest_marker("add_branches") - for additional_branch in add_branch_mark.args if add_branch_mark else []: - subprocess.check_call( - ["git", "switch", "-c", additional_branch], - cwd=integration_dir, - ) - - subprocess.check_call( - ["git", "switch", "-c", "branch"], - cwd=integration_dir, - ) - - write_file("A", "B", "C", "D") - commit() - - run_coverage("A", "C") - subprocess.check_call(["git", "fetch", "origin"], cwd=integration_dir) - - def test_action__invalid_event_name(session, push_config, in_integration_env, get_logs): session.register("GET", "/repos/py-cov-action/foobar")( json={"default_branch": "main", "visibility": "public"} @@ -172,7 +75,7 @@ def checker(payload): )(status_code=403) git.register("git fetch origin main --depth=1000")() - git.register("git diff --unified=0 FETCH_HEAD -- .")(stdout=DIFF_STDOUT) + git.register("git diff --unified=0 FETCH_HEAD...HEAD")(stdout=DIFF_STDOUT) result = main.action( config=pull_request_config( @@ -253,7 +156,7 @@ def checker(payload): )(status_code=403) git.register("git fetch origin foo --depth=1000")(stdout=DIFF_STDOUT) - git.register("git diff --unified=0 FETCH_HEAD -- .")(stdout=DIFF_STDOUT) + git.register("git diff --unified=0 FETCH_HEAD...HEAD")(stdout=DIFF_STDOUT) result = main.action( config=pull_request_config( @@ -300,7 +203,7 @@ def test_action__pull_request__post_comment( session.register("GET", "/repos/py-cov-action/foobar/issues/2/comments")(json=[]) git.register("git fetch origin main --depth=1000")() - git.register("git diff --unified=0 FETCH_HEAD -- .")(stdout=DIFF_STDOUT) + git.register("git diff --unified=0 FETCH_HEAD...HEAD")(stdout=DIFF_STDOUT) comment = None @@ -347,7 +250,7 @@ def test_action__push__non_default_branch( json={"default_branch": "main", "visibility": "public"} ) git.register("git fetch origin main --depth=1000")(stdout=DIFF_STDOUT) - git.register("git diff --unified=0 FETCH_HEAD -- .")(stdout=DIFF_STDOUT) + git.register("git diff --unified=0 FETCH_HEAD...HEAD")(stdout=DIFF_STDOUT) payload = json.dumps({"coverage": 30.00}) # There is an existing badge in this test, allowing to test the coverage evolution @@ -436,7 +339,7 @@ def test_action__push__non_default_branch__no_pr( json={"default_branch": "main", "visibility": "public"} ) git.register("git fetch origin main --depth=1000")(stdout=DIFF_STDOUT) - git.register("git diff --unified=0 FETCH_HEAD -- .")(stdout=DIFF_STDOUT) + git.register("git diff --unified=0 FETCH_HEAD...HEAD")(stdout=DIFF_STDOUT) payload = json.dumps({"coverage": 30.00}) # There is an existing badge in this test, allowing to test the coverage evolution @@ -500,7 +403,7 @@ def test_action__pull_request__force_store_comment( )(text=payload, headers={"content-type": "application/vnd.github.raw+json"}) git.register("git fetch origin main --depth=1000")() - git.register("git diff --unified=0 FETCH_HEAD -- .")(stdout=DIFF_STDOUT) + git.register("git diff --unified=0 FETCH_HEAD...HEAD")(stdout=DIFF_STDOUT) result = main.action( config=pull_request_config(FORCE_WORKFLOW_RUN=True, GITHUB_OUTPUT=output_file), @@ -531,7 +434,7 @@ def test_action__pull_request__post_comment__no_marker( )(status_code=404) git.register("git fetch origin main --depth=1000")() - git.register("git diff --unified=0 FETCH_HEAD -- .")(stdout=DIFF_STDOUT) + git.register("git diff --unified=0 FETCH_HEAD...HEAD")(stdout=DIFF_STDOUT) result = main.action( config=pull_request_config(COMMENT_TEMPLATE="""foo"""), @@ -556,7 +459,7 @@ def test_action__pull_request__annotations( )(status_code=404) git.register("git fetch origin main --depth=1000")() - git.register("git diff --unified=0 FETCH_HEAD -- .")(stdout=DIFF_STDOUT) + git.register("git diff --unified=0 FETCH_HEAD...HEAD")(stdout=DIFF_STDOUT) # Who am I session.register("GET", "/user")(json={"login": "foo"}) @@ -598,7 +501,7 @@ def test_action__pull_request__post_comment__template_error( )(status_code=404) git.register("git fetch origin main --depth=1000")() - git.register("git diff --unified=0 FETCH_HEAD -- .")(stdout=DIFF_STDOUT) + git.register("git diff --unified=0 FETCH_HEAD...HEAD")(stdout=DIFF_STDOUT) result = main.action( config=pull_request_config(COMMENT_TEMPLATE="""{%"""), diff --git a/tests/unit/test_coverage.py b/tests/unit/test_coverage.py index 621219da..bed0fe31 100644 --- a/tests/unit/test_coverage.py +++ b/tests/unit/test_coverage.py @@ -309,7 +309,7 @@ def test_get_added_lines(git): """+++ b/README.md\n@@ -1,2 +1,3 @@\n-# coverage-comment\n+coverage-comment\n""" ) git.register("git fetch origin main --depth=1000")() - git.register("git diff --unified=0 FETCH_HEAD -- .")(stdout=diff) + git.register("git diff --unified=0 FETCH_HEAD...HEAD")(stdout=diff) assert coverage.get_added_lines(git=git, base_ref="main") == { pathlib.Path("README.md"): [1, 2, 3] } @@ -364,7 +364,7 @@ def test_parse_diff_output(git): rename to coverage_comment/annotations2.py """ git.register("git fetch origin main --depth=1000")() - git.register("git diff --unified=0 FETCH_HEAD -- .")(stdout=diff) + git.register("git diff --unified=0 FETCH_HEAD...HEAD")(stdout=diff) assert coverage.parse_diff_output(diff=diff) == { pathlib.Path("README.md"): [1, 3, 4, 5, 6], pathlib.Path("foo.txt"): [1], @@ -379,6 +379,6 @@ def test_parse_diff_output__error(git): index 1f1d9a4..e69de29 100644 """ git.register("git fetch origin main --depth=1000")() - git.register("git diff --unified=0 FETCH_HEAD -- .")(stdout=diff) + git.register("git diff --unified=0 FETCH_HEAD...HEAD")(stdout=diff) with pytest.raises(ValueError): coverage.parse_diff_output(diff=diff)