robocop

Checks KYC attributes against sanction lists
Log | Files | Refs | Submodules | README | LICENSE

test_version.py (8367B)


      1 #!/usr/bin/env python3
      2 
      3 # This file is part of Robocop
      4 #
      5 # Robocop is free software; you can redistribute it and/or modify
      6 # it under the terms of the GNU General Public License as published by
      7 # the Free Software Foundation, either version 3 of the License, or
      8 # (at your option) any later version.
      9 #
     10 # Robocop is distributed in the hope that it will be useful,
     11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     13 # GNU General Public License for more details.
     14 #
     15 # You should have received a copy of the GNU General Public License
     16 # along with this program. If not, see <https://www.gnu.org/licenses/>.
     17 #
     18 # Copyright (C) 2026 Taler Systems SA
     19 
     20 """Exercise the real build script with dependency-free Cargo/Git fixtures."""
     21 
     22 import os
     23 import shutil
     24 import subprocess
     25 import tempfile
     26 import unittest
     27 from pathlib import Path
     28 
     29 ROOT = Path(__file__).resolve().parents[1]
     30 CRATE = Path(".")
     31 
     32 
     33 class VersionTests(unittest.TestCase):
     34     def setUp(self):
     35         self.temporary = tempfile.TemporaryDirectory(prefix="program-version-")
     36         self.addCleanup(self.temporary.cleanup)
     37         self.base = Path(self.temporary.name)
     38         self.repo = self.base / "source"
     39         self.env = os.environ.copy()
     40         self.env.update(
     41             {
     42                 "GIT_CONFIG_GLOBAL": os.devnull,
     43                 "GIT_CONFIG_NOSYSTEM": "1",
     44                 "GIT_AUTHOR_NAME": "Version Test",
     45                 "GIT_AUTHOR_EMAIL": "version@example.invalid",
     46                 "GIT_COMMITTER_NAME": "Version Test",
     47                 "GIT_COMMITTER_EMAIL": "version@example.invalid",
     48                 "CARGO_TARGET_DIR": str(self.base / "target"),
     49             }
     50         )
     51         for name in ("GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR"):
     52             self.env.pop(name, None)
     53         crate = self.repo / CRATE
     54         (crate / "src").mkdir(parents=True)
     55         shutil.copy(ROOT / CRATE / "build.rs", crate / "build.rs")
     56         (crate / "Cargo.toml").write_text(
     57             '[package]\nname = "version-probe"\nversion = "0.0.0"\nedition = "2024"\n'
     58         )
     59         (crate / "src/main.rs").write_text(
     60             'fn main() { println!("{}", env!("BUILD_VERSION")); }\n'
     61         )
     62         (self.repo / ".gitignore").write_text("target\nCargo.lock\n.version\n")
     63 
     64     def command(self, *args, cwd=None, check=True):
     65         result = subprocess.run(
     66             args,
     67             cwd=cwd or self.repo,
     68             env=self.env,
     69             text=True,
     70             capture_output=True,
     71             check=False,
     72         )
     73         if check and result.returncode:
     74             self.fail(f"{args}:\n{result.stdout}\n{result.stderr}")
     75         return result
     76 
     77     def git(self, *args, repo=None):
     78         return self.command("git", *args, cwd=repo).stdout.strip()
     79 
     80     def initialize(self):
     81         self.git("init", "-b", "main")
     82         self.git("add", ".")
     83         self.git("commit", "-m", "Initial source")
     84 
     85     def build(self, repo=None, check=True):
     86         return self.command(
     87             "cargo",
     88             "build",
     89             "--offline",
     90             "--verbose",
     91             "--manifest-path",
     92             str((repo or self.repo) / CRATE / "Cargo.toml"),
     93             cwd=repo,
     94             check=check,
     95         )
     96 
     97     def assert_version(self, expected, repo=None):
     98         result = self.build(repo)
     99         actual = self.command(
    100             str(self.base / "target/debug/version-probe")
    101         ).stdout.strip()
    102         self.assertEqual(expected, actual)
    103         return result
    104 
    105     def description(self, repo=None):
    106         return self.git("describe", "--tags", "--always", "--abbrev=8", repo=repo)
    107 
    108     def test_release_development_tags_and_untagged_commits(self):
    109         self.initialize()
    110         self.assert_version(self.git("rev-parse", "--short=8", "HEAD"))
    111         self.git("tag", "-a", "v1.2.3", "-m", "Release")
    112         self.assert_version("v1.2.3")
    113         self.git("commit", "--allow-empty", "-m", "Next commit")
    114         self.assert_version(self.description())
    115         self.git("tag", "v1.2.4-dev.1")
    116         self.assert_version("v1.2.4-dev.1")
    117         # Dirty files do not change the commit version.
    118         (self.repo / ".gitignore").write_text("target\nCargo.lock\n.version\nextra\n")
    119         self.assert_version("v1.2.4-dev.1")
    120 
    121     def test_git_takes_precedence_over_stamp(self):
    122         self.initialize()
    123         (self.repo / ".version").write_text("v0.0.1\n")
    124         self.assert_version(self.description())
    125 
    126     def test_incremental_branch_and_packed_tag_changes(self):
    127         self.initialize()
    128         self.git("tag", "v1.0.0")
    129         self.assert_version("v1.0.0")
    130         unchanged = self.assert_version("v1.0.0")
    131         self.assertIn("Fresh version-probe", unchanged.stderr)
    132         self.git("switch", "-c", "next")
    133         self.git("commit", "--allow-empty", "-m", "Next commit")
    134         self.assert_version(self.description())
    135         self.git("tag", "v1.1.0")
    136         self.assert_version("v1.1.0")
    137         self.git("pack-refs", "--all", "--prune")
    138         self.assert_version("v1.1.0")
    139         self.git("tag", "-d", "v1.1.0")
    140         self.assert_version(self.description())
    141         self.git("switch", "main")
    142         self.assert_version("v1.0.0")
    143         self.git("switch", "--detach", "next")
    144         self.assert_version(self.description())
    145 
    146     def test_linked_worktree_uses_shared_refs(self):
    147         self.initialize()
    148         self.git("tag", "v1.0.0")
    149         worktree = self.base / "worktree"
    150         self.git("worktree", "add", "-b", "linked", str(worktree))
    151         self.assert_version("v1.0.0", worktree)
    152         self.git("commit", "--allow-empty", "-m", "Worktree commit", repo=worktree)
    153         self.assert_version(self.description(worktree), worktree)
    154         self.git("tag", "v1.1.0", repo=worktree)
    155         self.assert_version("v1.1.0", worktree)
    156         self.git("pack-refs", "--all", "--prune")
    157         self.assert_version("v1.1.0", worktree)
    158         self.git("tag", "-d", "v1.1.0")
    159         self.assert_version(self.description(worktree), worktree)
    160 
    161     def test_shallow_tagged_and_untagged_clones(self):
    162         self.initialize()
    163         self.git("tag", "v1.0.0")
    164         clone = self.base / "clone"
    165         self.git(
    166             "clone", "--depth=1", "--branch=v1.0.0", self.repo.as_uri(), str(clone)
    167         )
    168         self.assert_version("v1.0.0", clone)
    169         self.git("commit", "--allow-empty", "-m", "Unreachable tag")
    170         shallow = self.base / "shallow"
    171         self.git("clone", "--depth=1", "--no-tags", self.repo.as_uri(), str(shallow))
    172         self.assert_version(
    173             self.git("rev-parse", "--short=8", "HEAD", repo=shallow), shallow
    174         )
    175 
    176     def test_archive_fallback_and_incremental_stamp_changes(self):
    177         (self.repo / ".version").write_text("  v2.0.0\n")
    178         self.assert_version("v2.0.0")
    179         (self.repo / ".version").write_text("v2.0.1\n")
    180         self.assert_version("v2.0.1")
    181         self.initialize()
    182         self.assert_version(self.description())
    183         (self.repo / ".git").rename(self.base / "saved-git")
    184         self.assert_version("v2.0.1")
    185 
    186     def test_archive_does_not_use_enclosing_repository(self):
    187         self.git("init", "-b", "main", repo=self.base)
    188         self.git("commit", "--allow-empty", "-m", "Unrelated project", repo=self.base)
    189         self.git("tag", "v9.9.9", repo=self.base)
    190         (self.repo / ".version").write_text("v2.0.0\n")
    191         self.assert_version("v2.0.0")
    192         (self.repo / ".version").unlink()
    193         result = self.build(check=False)
    194         self.assertNotEqual(0, result.returncode)
    195         self.assertIn("cannot determine program version", result.stderr)
    196 
    197     def test_missing_or_invalid_archive_stamp_fails(self):
    198         result = self.build(check=False)
    199         self.assertNotEqual(0, result.returncode)
    200         self.assertIn("Source archives must contain a .version file", result.stderr)
    201         for value in ("\n ", "v1.0.0\nv2.0.0", "v1.0.0\rjunk", "v1.0.0\0junk"):
    202             with self.subTest(value=value):
    203                 (self.repo / ".version").write_text(value)
    204                 result = self.build(check=False)
    205                 self.assertNotEqual(0, result.returncode)
    206                 self.assertIn(
    207                     "program version must be a nonempty single line", result.stderr
    208                 )
    209 
    210 
    211 if __name__ == "__main__":
    212     unittest.main()