testing: migrate fetchMercurial.sh

Change-Id: Iab81ea161e3be69bf7753b3ed9cd8f59cd8fe0ed
This commit is contained in:
eldritch horrors
2026-02-27 17:42:17 +00:00
parent f13566a3f6
commit 4caa622d78
3 changed files with 251 additions and 123 deletions
-122
View File
@@ -1,122 +0,0 @@
source common.sh
[[ $(type -p hg) ]] || skipTest "Mercurial not installed"
clearStore
# Intentionally not in a canonical form
# See https://github.com/NixOS/nix/issues/6195
repo=$TEST_ROOT/./hg
rm -rf $repo ${repo}-tmp $TEST_HOME/.cache/nix
hg init $repo
echo '[ui]' >> $repo/.hg/hgrc
echo 'username = Foobar <foobar@example.org>' >> $repo/.hg/hgrc
# Set ui.tweakdefaults to ensure HGPLAIN is being set.
echo 'tweakdefaults = True' >> $repo/.hg/hgrc
echo utrecht > $repo/hello
touch $repo/.hgignore
hg add --cwd $repo hello .hgignore
hg commit --cwd $repo -m 'Bla1'
rev1=$(hg log --cwd $repo -r tip --template '{node}')
echo world > $repo/hello
hg commit --cwd $repo -m 'Bla2'
rev2=$(hg log --cwd $repo -r tip --template '{node}')
hg --cwd $repo bookmark aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
# Fetch an unclean branch.
echo unclean > $repo/hello
path=$(nix eval --impure --raw --expr "(builtins.fetchMercurial \"file://$repo\").outPath")
[[ $(cat $path/hello) = unclean ]]
hg revert --cwd $repo --all
# Fetch the currently checked out revision.
path=$(nix eval --impure --raw --expr "(builtins.fetchMercurial \"file://$repo\").outPath")
[[ $(cat $path/hello) = world ]]
# In pure eval mode, fetchMercurial without a revision should fail.
[[ $(nix eval --impure --raw --expr "(builtins.readFile (fetchMercurial \"file://$repo\" + \"/hello\"))") = world ]]
(! nix eval --raw --expr "builtins.readFile (fetchMercurial \"file://$repo\" + \"/hello\")")
# Fetch using an explicit revision hash.
path2=$(nix eval --impure --raw --expr "(builtins.fetchMercurial { url = \"file://$repo\"; rev = \"$rev2\"; }).outPath")
[[ $path = $path2 ]]
out=$(nix eval --impure --raw --expr "builtins.fetchMercurial { url = \"file://$repo\"; rev = \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"; }" 2>&1) || status=$?
[[ $status == 1 ]]
[[ $out =~ 'hg failed with exit code' ]]
# In pure eval mode, fetchMercurial with a revision should succeed.
[[ $(nix eval --raw --expr "builtins.readFile (fetchMercurial { url = \"file://$repo\"; rev = \"$rev2\"; } + \"/hello\")") = world ]]
# Fetch again. This should be cached.
mv $repo ${repo}-tmp
path2=$(nix eval --impure --raw --expr "(builtins.fetchMercurial \"file://$repo\").outPath")
[[ $path = $path2 ]]
[[ $(nix eval --impure --raw --expr "(builtins.fetchMercurial \"file://$repo\").branch") = default ]]
[[ $(nix eval --impure --expr "(builtins.fetchMercurial \"file://$repo\").revCount") = 2 ]]
[[ $(nix eval --impure --raw --expr "(builtins.fetchMercurial \"file://$repo\").rev") = $rev2 ]]
# But with TTL 0, it should fail.
(! nix eval --impure --refresh --expr "builtins.fetchMercurial \"file://$repo\"")
# Fetching with a explicit hash should succeed.
path2=$(nix eval --refresh --raw --expr "(builtins.fetchMercurial { url = \"file://$repo\"; rev = \"$rev2\"; }).outPath")
[[ $path = $path2 ]]
path2=$(nix eval --refresh --raw --expr "(builtins.fetchMercurial { url = \"file://$repo\"; rev = \"$rev1\"; }).outPath")
[[ $(cat $path2/hello) = utrecht ]]
mv ${repo}-tmp $repo
# Using a clean working tree should produce the same result.
path2=$(nix eval --impure --raw --expr "(builtins.fetchMercurial \"$repo\").outPath")
[[ $path = $path2 ]]
# Using an unclean tree should yield the tracked but uncommitted changes.
mkdir $repo/dir1 $repo/dir2
echo foo > $repo/dir1/foo
echo bar > $repo/bar
echo bar > $repo/dir2/bar
hg add --cwd $repo dir1/foo
hg rm --cwd $repo hello
path2=$(nix eval --impure --raw --expr "(builtins.fetchMercurial \"$repo\").outPath")
[ ! -e $path2/hello ]
[ ! -e $path2/bar ]
[ ! -e $path2/dir2/bar ]
[ ! -e $path2/.hg ]
[[ $(cat $path2/dir1/foo) = foo ]]
[[ $(nix eval --impure --raw --expr "(builtins.fetchMercurial \"$repo\").rev") = 0000000000000000000000000000000000000000 ]]
# ... unless we're using an explicit ref.
path3=$(nix eval --impure --raw --expr "(builtins.fetchMercurial { url = \"$repo\"; rev = \"default\"; }).outPath")
[[ $path = $path3 ]]
# Committing should not affect the store path.
hg commit --cwd $repo -m 'Bla3'
path4=$(nix eval --impure --refresh --raw --expr "(builtins.fetchMercurial \"file://$repo\").outPath")
[[ $path2 = $path4 ]]
echo paris > $repo/hello
# Passing a `name` argument should be reflected in the output path
path5=$(nix eval -vvvvv --impure --refresh --raw --expr "(builtins.fetchMercurial { url = \"file://$repo\"; name = \"foo\"; } ).outPath")
[[ $path5 =~ -foo$ ]]
# Fetch the currently checked out revision on a non-default branch.
hg branch --cwd $repo meow
hg commit --cwd $repo -m 'Bla4'
[[ $(nix eval --impure --raw --expr "(builtins.fetchMercurial \"file://$repo\").branch") = meow ]]
[[ $(nix eval --impure --expr "(builtins.fetchMercurial \"file://$repo\").revCount") = 4 ]]
# Fetch an older revision currently checked out.
hg update --cwd $repo 2
[[ $(nix eval --impure --raw --expr "(builtins.fetchMercurial \"file://$repo\").branch") = default ]]
[[ $(nix eval --impure --expr "(builtins.fetchMercurial \"file://$repo\").revCount") = 3 ]]
-1
View File
@@ -38,7 +38,6 @@ functional_tests_scripts = [
'remote-store.sh',
'legacy-ssh-store.sh',
'experimental-features.sh',
'fetchMercurial.sh',
'gc-auto.sh',
'user-envs.sh',
'binary-cache.sh',
@@ -0,0 +1,251 @@
import dataclasses
import shutil
import subprocess
from dataclasses import dataclass
from pathlib import Path
from textwrap import dedent
from collections.abc import Callable
import pytest
from testlib.fixtures.env import ManagedEnv
from testlib.fixtures.nix import Nix
from testlib.fixtures.command import Command
@pytest.fixture(autouse=True)
def common_init(nix: Nix):
nix.settings.add_xp_feature("nix-command")
nix.env.path.add_program("hg")
@dataclass
class HgRepo:
path: Path
rev1: str
rev2: str
@property
def uri(self) -> str:
# Intentionally not in a canonical form
# See https://github.com/NixOS/nix/issues/6195
return f'"file://{self.path.parent}/./{self.path.name}"'
@pytest.fixture(scope="session")
def static_hg_repo(tmp_path_factory: pytest.TempPathFactory) -> HgRepo:
"""
create a static mercurial repo with our default settings because hg is *so slow*
"""
repo = tmp_path_factory.mktemp("hg")
subprocess.check_call(["hg", "init", repo])
(repo / ".hg/hgrc").write_text(
dedent("""
[ui]
username = Foobar <foobar@example.org>
# Set ui.tweakdefaults to ensure HGPLAIN is being set.
tweakdefaults = True
""")
)
(repo / "hello").write_text("utrecht")
(repo / ".hgignore").touch()
subprocess.check_call(["hg", "add", "--cwd", repo, "hello", ".hgignore"])
subprocess.check_call(["hg", "commit", "--cwd", repo, "-m", "Bla1"])
rev1 = (
subprocess.run(
["hg", "log", "--cwd", repo, "-r", "tip", "--template", "{node}"],
stdout=subprocess.PIPE,
check=True,
)
.stdout.strip()
.decode("utf8", errors="replace")
)
(repo / "hello").write_text("world")
subprocess.check_call(["hg", "commit", "--cwd", repo, "-m", "Bla2"])
rev2 = (
subprocess.run(
["hg", "log", "--cwd", repo, "-r", "tip", "--template", "{node}"],
stdout=subprocess.PIPE,
check=True,
)
.stdout.strip()
.decode("utf8", errors="replace")
)
subprocess.check_call(
["hg", "--cwd", repo, "bookmark", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
)
return HgRepo(repo, rev1, rev2)
@pytest.fixture
def hg(static_hg_repo: HgRepo, env: ManagedEnv) -> HgRepo:
repo = env.dirs.home / "hg"
shutil.copytree(static_hg_repo.path, repo)
return dataclasses.replace(static_hg_repo, path=repo)
def test_fetch_unclean(hg: HgRepo, nix: Nix):
(hg.path / "hello").write_text("unclean")
result = (
nix.nix(
["eval", "--impure", "--raw", "--expr", f"(builtins.fetchMercurial {hg.uri}).outPath"]
)
.run()
.ok()
)
assert "is unclean" in result.stderr_plain
assert (Path(result.stdout_plain) / "hello").read_text() == "unclean"
def test_fetch_current_rev(hg: HgRepo, nix: Nix):
expr = f"(builtins.fetchMercurial {hg.uri}).outPath"
result = nix.nix(["eval", "--impure", "--raw", "--expr", expr]).run().ok()
assert (Path(result.stdout_plain) / "hello").read_text() == "world"
def test_pure_fetch_without_rev(hg: HgRepo, nix: Nix):
expr = f'builtins.readFile (builtins.fetchMercurial {hg.uri} + "/hello")'
result = nix.nix(["eval", "--raw", "--expr", expr]).run().expect(1)
assert (
"error: in pure evaluation mode, 'fetchMercurial' requires a Mercurial revision"
in result.stderr_s
)
def test_pure_fetch_with_good_rev(hg: HgRepo, nix: Nix):
expr = f'(builtins.fetchMercurial {{ url = {hg.uri}; rev = "{hg.rev2}"; }}).outPath'
result = nix.nix(["eval", "--raw", "--expr", expr]).run().ok()
assert (Path(result.stdout_plain) / "hello").read_text() == "world"
def test_pure_fetch_with_bad_rev(hg: HgRepo, nix: Nix):
expr = f'(builtins.fetchMercurial {{ url = {hg.uri}; rev = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; }}).outPath'
result = nix.nix(["eval", "--raw", "--expr", expr]).run().expect(1)
assert "hg failed with exit code" in result.stderr_s
def test_fetch_attrs(hg: HgRepo, nix: Nix):
expr = f'builtins.removeAttrs (builtins.fetchMercurial {{ url = {hg.uri}; rev = "{hg.rev2}"; }}) [ "outPath" ]'
result = nix.nix(["eval", "--impure", "--json", "--expr", expr]).run().json()
assert result["branch"] == "default"
assert result["revCount"] == 2
assert result["rev"] == hg.rev2
def test_caching(hg: HgRepo, nix: Nix):
expr = f"(builtins.fetchMercurial {hg.uri}).outPath"
path1 = nix.nix(["eval", "--impure", "--raw", "--expr", expr]).run().ok().stdout_plain
shutil.rmtree(hg.path)
path2 = nix.nix(["eval", "--impure", "--raw", "--expr", expr]).run().ok().stdout_plain
assert path1 == path2
assert (Path(path2) / "hello").read_text() == "world"
# with TTL 0 refetch should fail
result = nix.nix(["eval", "--impure", "--refresh", "--expr", expr]).run().expect(1)
assert "error: 'hg pull' failed" in result.stderr_s
class TestExplicitHashes:
def test_rev2(self, hg: HgRepo, nix: Nix):
expr = f'(builtins.fetchMercurial {{ url = {hg.uri}; rev = "{hg.rev2}"; }}).outPath'
path1 = nix.nix(["eval", "--raw", "--expr", expr]).run().ok().stdout_plain
shutil.rmtree(hg.path)
path2 = nix.nix(["eval", "--refresh", "--raw", "--expr", expr]).run().ok().stdout_plain
assert path1 == path2
def test_rev1(self, hg: HgRepo, nix: Nix):
expr = f'(builtins.fetchMercurial {{ url = {hg.uri}; rev = "{hg.rev1}"; }}).outPath'
path2 = nix.nix(["eval", "--refresh", "--raw", "--expr", expr]).run().ok().stdout_plain
assert (Path(path2) / "hello").read_text() == "utrecht"
class TestFetchDirt:
@pytest.fixture(scope="class")
def static_changed_repo(
self, static_hg_repo: HgRepo, tmp_path_factory: pytest.TempPathFactory
) -> HgRepo:
repo = tmp_path_factory.mktemp("hg-changed")
shutil.copytree(static_hg_repo.path, repo, dirs_exist_ok=True)
for name in ["dir1", "dir2"]:
(repo / name).mkdir()
(repo / "dir1/foo").write_text("foo")
(repo / "bar").write_text("bar")
(repo / "dir2/bar").write_text("bar")
subprocess.check_call(["hg", "add", "--cwd", repo, "dir1/foo"])
subprocess.check_call(["hg", "rm", "--cwd", repo, "hello"])
return dataclasses.replace(static_hg_repo, path=repo)
# this intentionally overrides the module-level fixture name
@pytest.fixture
def hg(self, static_changed_repo: HgRepo, env: ManagedEnv) -> HgRepo:
repo = env.dirs.home / "hg"
shutil.copytree(static_changed_repo.path, repo)
return dataclasses.replace(static_changed_repo, path=repo)
def test_fetch_simple(self, hg: HgRepo, nix: Nix, command: Callable[..., Command]):
expr = f"""
let f = builtins.fetchMercurial {hg.uri};
in {{ path = f.outPath; rev = f.rev; }}
"""
result = nix.nix(["eval", "--impure", "--json", "--expr", expr]).run().json()
path = Path(result["path"])
assert not (path / "hello").exists()
assert not (path / "bar").exists()
assert not (path / "dir2/bar").exists()
assert (path / "dir1/foo").read_text() == "foo"
assert result["rev"] == "0000000000000000000000000000000000000000"
command(["hg", "commit", "--cwd", hg.path, "-m", "Bla3"]).run().ok()
result2 = nix.nix(["eval", "--impure", "--json", "--expr", expr]).run().json()
assert result["path"] == result2["path"]
def test_fetch_rev(self, hg: HgRepo, nix: Nix):
expr = f"""
let f = builtins.fetchMercurial {{ url = {hg.uri}; rev = "default"; }};
in {{ path = f.outPath; rev = f.rev; }}
"""
result = nix.nix(["eval", "--impure", "--json", "--expr", expr]).run().json()
path = Path(result["path"])
assert (path / "hello").exists()
assert not (path / "bar").exists()
assert not (path / "dir2/bar").exists()
assert not (path / "dir1/foo").exists()
assert result["rev"] == hg.rev2
def test_fetch_with_name(hg: HgRepo, nix: Nix):
"""Passing a `name` argument should be reflected in the output path"""
(hg.path / "hello").write_text("paris")
expr = f'(builtins.fetchMercurial {{ url = {hg.uri}; name = "foo"; }}).outPath'
path = (
nix.nix(["eval", "--impure", "--refresh", "--raw", "--expr", expr]).run().ok().stdout_plain
)
assert path.endswith("-foo")
class TestNondefaultBranch:
@pytest.fixture(autouse=True)
def setup(self, hg: HgRepo, command: Callable[..., Command]):
(hg.path / "hello").write_text("paris")
command(["hg", "branch", "--cwd", hg.path, "meow"]).run().ok()
command(["hg", "commit", "--cwd", hg.path, "-m", "Bla3"]).run().ok()
def test_simple(self, hg: HgRepo, nix: Nix):
expr = f'builtins.removeAttrs (builtins.fetchMercurial {hg.uri}) [ "outPath" ]'
result = nix.nix(["eval", "--impure", "--json", "--expr", expr]).run().json()
assert result["branch"] == "meow"
assert result["revCount"] == 3
def test_older_rev(self, hg: HgRepo, nix: Nix, command: Callable[..., Command]):
command(["hg", "update", "--cwd", hg.path, "1"]).run().ok()
expr = f'builtins.removeAttrs (builtins.fetchMercurial {hg.uri}) [ "outPath" ]'
result = nix.nix(["eval", "--impure", "--json", "--expr", expr]).run().json()
assert result["branch"] == "default"
assert result["revCount"] == 2