diff --git a/test/conftest.py b/test/conftest.py index d824ed0..fc62340 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,4 +1,5 @@ import os +import re import subprocess from pathlib import Path @@ -73,6 +74,10 @@ def private_test_repo(): # Fixture containing everything needed to access private github repo. # GIT2CPP_TEST_PRIVATE_TOKEN is the fine-grained Personal Access Token for private test repo. # If this is not available as an environment variable, tests that use this fixture are skipped. + + if GIT2CPP_TEST_WASM: + pytest.skip("Use of credentials in wasm not yet implemented") + token = os.getenv("GIT2CPP_TEST_PRIVATE_TOKEN") if token is None or len(token) == 0: pytest.skip("No token for private test repo GIT2CPP_TEST_PRIVATE_TOKEN") @@ -84,3 +89,11 @@ def private_test_repo(): "https_url": f"https://github.com/{org_name}/{repo_name}", "token": token, } + + +# Functions not fixtures below here. + + +def strip_ansi_colours(text): + # Strip ansi colour code sequences from a string. + return re.sub(r"\x1b\[[^m]*m", "", text) diff --git a/test/conftest_wasm.py b/test/conftest_wasm.py index ae183da..a5d8ee9 100644 --- a/test/conftest_wasm.py +++ b/test/conftest_wasm.py @@ -19,12 +19,15 @@ def pytest_ignore_collect(collection_path: pathlib.Path) -> bool: "test_clone.py", "test_commit.py", "test_config.py", + "test_diff.py", + "test_fetch.py", "test_fixtures.py", "test_git.py", "test_init.py", "test_log.py", "test_merge.py", "test_mv.py", + "test_push.py", "test_rebase.py", "test_remote.py", "test_reset.py", @@ -33,6 +36,7 @@ def pytest_ignore_collect(collection_path: pathlib.Path) -> bool: "test_rm.py", "test_stash.py", "test_status.py", + "test_tag.py", ] @@ -54,18 +58,6 @@ def load_page(page: Page): page.locator("#loaded").wait_for() -def os_chdir(dir: str): - subprocess.run(["cd", str(dir)], capture_output=True, check=True, text=True) - - -def os_getcwd(): - return subprocess.run(["pwd"], capture_output=True, check=True, text=True).stdout.strip() - - -def os_remove(file: str): - return subprocess.run(["rm", str(file)], capture_output=True, check=True, text=True) - - class MockPath(pathlib.Path): def __init__(self, path: str = ""): super().__init__(path) @@ -95,6 +87,9 @@ def mkdir(self, *, parents=False): args.append("-p") subprocess.run(["mkdir"] + args, capture_output=True, text=True, check=True) + def read_bytes(self) -> bytes: + raise RuntimeError("Not implemented") + def read_text(self) -> str: p = subprocess.run(["cat", str(self)], capture_output=True, text=True, check=True) text = p.stdout @@ -102,12 +97,23 @@ def read_text(self) -> str: text = text[:-1] return text + def write_bytes(self, data: bytes): + # Convert binary data to a string where each element is backslash-escaped so that we can + # write to file in cockle using `echo -e `. + encoded_string = "".join(map(lambda d: f"\\x{d:02x}", data)) + cmd = ["echo", "-e", encoded_string, ">", str(self)] + subprocess.run(cmd, capture_output=True, text=True, check=True) + return len(data) + def write_text(self, data: str): # Note that in general it is not valid to direct output of a subprocess.run call to a file, # but we get away with it here as the command arguments are passed straight through to # cockle without being checked. - p = subprocess.run(["echo", data, ">", str(self)], capture_output=True, text=True) - assert p.returncode == 0 + if data.endswith("\n"): + data = data[:-1] + cmd = ["echo", data, ">", str(self)] + subprocess.run(cmd, capture_output=True, text=True, check=True) + return len(data) def __truediv__(self, other): if isinstance(other, str): @@ -115,6 +121,28 @@ def __truediv__(self, other): raise RuntimeError("MockPath.__truediv__ only supports strings") +def os_chdir(dir: str): + subprocess.run(["cd", str(dir)], capture_output=True, check=True, text=True) + + +def os_getcwd(): + return subprocess.run(["pwd"], capture_output=True, check=True, text=True).stdout.strip() + + +def os_remove(file: str): + return subprocess.run(["rm", str(file)], capture_output=True, check=True, text=True) + + +def os_rename(src: str | MockPath, dst: str | MockPath): + return subprocess.run(["mv", str(src), str(dst)], capture_output=True, check=True, text=True) + + +def os_symlink(src: str | MockPath, dst: str | MockPath): + return subprocess.run( + ["ln", "-s", str(src), str(dst)], capture_output=True, check=True, text=True + ) + + def subprocess_run( page: Page, cmd: list[str], @@ -192,3 +220,5 @@ def mock_subprocess_run(page: Page, monkeypatch): monkeypatch.setattr(os, "chdir", os_chdir) monkeypatch.setattr(os, "getcwd", os_getcwd) monkeypatch.setattr(os, "remove", os_remove) + monkeypatch.setattr(os, "rename", os_rename) + monkeypatch.setattr(os, "symlink", os_symlink) diff --git a/test/test_checkout.py b/test/test_checkout.py index 8f7c740..d789e91 100644 --- a/test/test_checkout.py +++ b/test/test_checkout.py @@ -2,6 +2,8 @@ import pytest +from .conftest import strip_ansi_colours + def test_checkout(repo_init_with_commit, git2cpp_path, tmp_path): assert (tmp_path / "initial.txt").exists() @@ -103,6 +105,7 @@ def test_checkout_with_unstaged_changes(repo_init_with_commit, git2cpp_path, tmp # Should succeed and show status assert p_checkout.returncode == 0 + p_checkout.stdout = strip_ansi_colours(p_checkout.stdout) assert " M initial.txt" in p_checkout.stdout assert "Switched to branch 'newbranch'" in p_checkout.stdout diff --git a/test/test_diff.py b/test/test_diff.py index e17686d..3573aa4 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -3,6 +3,8 @@ import pytest +from .conftest import strip_ansi_colours + def test_diff_nogit(git2cpp_path, tmp_path): cmd = [git2cpp_path, "diff"] @@ -84,6 +86,8 @@ def test_diff_stat(repo_init_with_commit, git2cpp_path, tmp_path): cmd = [git2cpp_path, "diff", "--stat"] p = subprocess.run(cmd, capture_output=True, cwd=tmp_path, text=True) assert p.returncode == 0 + + p.stdout = strip_ansi_colours(p.stdout) assert "initial.txt" in p.stdout assert "1 file changed, 1 insertion(+)" in p.stdout assert "Modified content" not in p.stdout @@ -132,6 +136,7 @@ def test_diff_summary(repo_init_with_commit, git2cpp_path, tmp_path): cmd = [git2cpp_path, "diff", "--cached", "--summary"] p = subprocess.run(cmd, capture_output=True, cwd=tmp_path, text=True) assert p.returncode == 0 + p.stdout = strip_ansi_colours(p.stdout) assert "newfile.txt" in p.stdout assert "+" not in p.stdout @@ -146,6 +151,7 @@ def test_diff_name_only(repo_init_with_commit, git2cpp_path, tmp_path): p = subprocess.run(cmd, capture_output=True, cwd=tmp_path, text=True) assert p.returncode == 0 + p.stdout = strip_ansi_colours(p.stdout) assert p.stdout == "initial.txt\n" assert "+" not in p.stdout @@ -159,6 +165,7 @@ def test_diff_name_status(repo_init_with_commit, git2cpp_path, tmp_path): cmd = [git2cpp_path, "diff", "--name-status"] p = subprocess.run(cmd, capture_output=True, cwd=tmp_path, text=True) assert p.returncode == 0 + p.stdout = strip_ansi_colours(p.stdout) assert p.stdout == "M\tinitial.txt\n" @@ -172,6 +179,7 @@ def test_diff_raw(repo_init_with_commit, git2cpp_path, tmp_path): cmd = [git2cpp_path, "diff", "--raw"] p = subprocess.run(cmd, capture_output=True, cwd=tmp_path, text=True) assert p.returncode == 0 + p.stdout = strip_ansi_colours(p.stdout) assert "M\tinitial.txt" in p.stdout assert bool(re.search(":[0-9]*", p.stdout)) @@ -635,7 +643,6 @@ def test_diff_find_copies_harder( [git2cpp_path, "commit", "-m", "add original file"], cwd=tmp_path, check=True, - env=commit_env_config, ) # Create identical copy @@ -669,7 +676,6 @@ def test_diff_find_copies_with_threshold( [git2cpp_path, "commit", "-m", "add original file"], cwd=tmp_path, check=True, - env=commit_env_config, ) # Create a partial copy (60% similar) diff --git a/test/test_log.py b/test/test_log.py index 7a33609..87f81e7 100644 --- a/test/test_log.py +++ b/test/test_log.py @@ -3,6 +3,8 @@ import pytest +from .conftest import strip_ansi_colours + @pytest.mark.parametrize("format_flag", ["", "--format=full", "--format=fuller"]) def test_log(commit_env_config, git2cpp_path, tmp_path, format_flag): @@ -101,7 +103,7 @@ def test_log_with_head_reference(repo_init_with_commit, commit_env_config, git2c assert p_log.returncode == 0 # Check that HEAD reference is shown - assert "HEAD ->" in p_log.stdout + assert "HEAD ->" in strip_ansi_colours(p_log.stdout) assert "master" in p_log.stdout or "main" in p_log.stdout @@ -253,7 +255,7 @@ def test_log_commit_without_references(commit_env_config, git2cpp_path, tmp_path assert p_log.returncode == 0 # First commit line should have references - lines = p_log.stdout.split("\n") + lines = strip_ansi_colours(p_log.stdout).split("\n") first_commit_line = [line for line in lines if line.startswith("commit")][0] assert "(" in first_commit_line # Has references @@ -286,7 +288,7 @@ def test_log_abbrev_commit_flags( p = subprocess.run(cmd, capture_output=True, cwd=tmp_path, text=True) assert p.returncode == 0 - m = re.search(r"^commit\s+([0-9a-fA-F]+)", p.stdout, flags=re.MULTILINE) + m = re.search(r"^commit\s+([0-9a-fA-F]+)", strip_ansi_colours(p.stdout), flags=re.MULTILINE) if abbrev_commit_flag in ["", "--no-abbrev-commit"]: assert len(m.group(1)) == 40 else: diff --git a/test/test_status.py b/test/test_status.py index 8030a90..cb4b530 100644 --- a/test/test_status.py +++ b/test/test_status.py @@ -4,6 +4,8 @@ import pytest +from .conftest import strip_ansi_colours + @pytest.mark.parametrize("short_flag", ["", "-s", "--short"]) @pytest.mark.parametrize("long_flag", ["", "--long"]) @@ -185,6 +187,8 @@ def test_status_mixed_changes(repo_init_with_commit, git2cpp_path, tmp_path, sho p = subprocess.run(cmd_status, capture_output=True, cwd=tmp_path, text=True) assert p.returncode == 0 + + p.stdout = strip_ansi_colours(p.stdout) if short_flag == "-s": assert "A staged.txt" in p.stdout assert "D to_delete.txt" in p.stdout diff --git a/test/test_tag.py b/test/test_tag.py index d02caf5..f0d65f3 100644 --- a/test/test_tag.py +++ b/test/test_tag.py @@ -2,6 +2,8 @@ import pytest +from .conftest import GIT2CPP_TEST_WASM + def test_tag_list_empty(repo_init_with_commit, git2cpp_path, tmp_path): """Test listing tags when there are no tags.""" @@ -235,6 +237,9 @@ def test_tag_annotated_no_message(repo_init_with_commit, commit_env_config, git2 commit_cmd = [git2cpp_path, "commit", "-m", "my specific commit message"] subprocess.run(commit_cmd, cwd=tmp_path, check=True) + if GIT2CPP_TEST_WASM: + pytest.skip("Not possible to pass empty argument to wasm") + # Create tag with empty message (should create lightweight tag) create_cmd = [git2cpp_path, "tag", "-m", "", "v1.0.0"] subprocess.run(create_cmd, capture_output=True, cwd=tmp_path, check=True)