blob: dd070bf8cfbaf41a09c4fe9bfe1a31a3bc2d757d [file]
#!/usr/bin/env python3
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0.
import json
from pathlib import Path
import tempfile
import subprocess
import sys
import unittest
from unittest.mock import patch
import oink_module
class ModuleLockTest(unittest.TestCase):
def test_build_resolves_relative_go_before_changing_directory(self):
import argparse
import os
import versioning
actual_run = subprocess.run
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
executable = root / "go"
executable.write_text("#!/bin/sh\nprintf 'resolved-go'\n")
executable.chmod(0o755)
relative = os.path.relpath(executable, Path.cwd())
entry = {"id": "latest", "archived": False, "publishPath": ""}
args = argparse.Namespace(manifest=root / "manifest", version="latest", sha="a" * 40,
output=root / "out", site_origin="https://example.org/",
historical_origin="https://example.org/")
def download(assembly, go):
self.assertEqual(go, str(executable.resolve()))
self.assertEqual(subprocess.check_output([go], cwd=assembly, text=True), "resolved-go")
raise ValueError("verified relative Go from assembly")
with patch.dict(os.environ, GO_BIN=relative), \
patch.object(versioning, "load_manifest", return_value={"versions": [entry]}), \
patch.object(versioning, "prepare_output_directory", return_value=args.output), \
patch.object(versioning, "run"), \
patch.object(versioning.subprocess, "run", side_effect=lambda argv, **kw:
None if argv[0] == "git" else actual_run(argv, **kw)), \
patch.object(versioning, "overlay_shell", side_effect=lambda assembly, **kw: assembly.mkdir()), \
patch.object(versioning, "derived_version_config", return_value={}), \
patch.object(versioning, "download_locked", side_effect=download) as resolve:
with self.assertRaisesRegex(SystemExit, "OINK module verification failed: verified relative Go"):
versioning.build(args)
resolve.assert_called_once()
def config(self, version="v1.0.0", **changes):
return dict(Module={"Path": oink_module.SITE_MODULE},
Require=[{"Path": oink_module.MODULE, "Version": version}], **changes)
def download(self, version="v1.0.0", config=None, metadata=None, resolved=None, graph=None, sums=True, extra=""):
module = dict(Path=oink_module.MODULE, Version=version, Sum="h1:archive", GoModSum="h1:mod")
resolution = dict(Path=oink_module.MODULE, Version=version)
module.update(metadata or {})
resolution.update(resolved or {})
replies = [json.dumps(config or self.config(version)), json.dumps(module),
json.dumps(resolution), graph or f"{oink_module.SITE_MODULE}\n{oink_module.MODULE} {version}\n", "all modules verified"]
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
(root / "go.sum").write_text(
(f"\n{oink_module.MODULE} {version} h1:archive\n\n{oink_module.MODULE} {version}/go.mod h1:mod\n" if sums else "") + extra)
with patch.object(oink_module, "command", side_effect=replies) as command:
result = oink_module.download_locked(root, "/verified/bin/go")
self.assertEqual(command.call_args.args[0], ["mod", "verify"])
self.assertTrue(all(call.args[2] == "/verified/bin/go" for call in command.call_args_list))
return result
def test_rejects_stale_and_duplicate_checksums(self):
for extra in [f"{oink_module.MODULE} v0.9.0 h1:stale\n",
f"{oink_module.MODULE} v1.0.0 h1:duplicate\n"]:
with self.subTest(extra=extra), self.assertRaises(ValueError):
self.download(extra=extra)
def test_mismatch_diagnostic_has_expected_and_actual(self):
with self.assertRaisesRegex(ValueError, "h1:archive.*h1:wrong"):
self.download(metadata={"Sum": "h1:wrong"})
def test_failed_go_command_preserves_download_error(self):
failure = subprocess.CalledProcessError(1, ["go"], output='{"Error":"checksum mismatch"}')
with patch.object(oink_module.subprocess, "check_output", side_effect=failure):
with self.assertRaisesRegex(ValueError, "verified/bin/go.*checksum mismatch"):
oink_module.command(["mod", "download", "-json"], Path("."), "/verified/bin/go")
def test_cli_missing_go_has_no_traceback(self):
import os
result = subprocess.run([sys.executable, oink_module.__file__], text=True, capture_output=True,
env=dict(os.environ, GO_BIN="/missing/oink-test-go"))
self.assertNotEqual(result.returncode, 0)
self.assertIn("/missing/oink-test-go", result.stderr)
self.assertNotIn("Traceback", result.stderr)
def test_accepts_each_locked_release(self):
for version in ["v1.0.0", "v1.1.0"]:
with self.subTest(version=version):
self.assertEqual(self.download(version)["Version"], version)
def test_rejects_changed_dependency_boundary(self):
configurations = [self.config(Replace=[{"New": {"Path": "local"}}]),
self.config(Exclude=[{"Path": oink_module.MODULE, "Version": "v1.0.0"}])]
wrong_site = self.config()
wrong_site["Module"]["Path"] = "example.com/site"
configurations.append(wrong_site)
extra = self.config()
extra["Require"].append({"Path": "example.com/extra", "Version": "v1.0.0"})
configurations.append(extra)
for config in configurations:
with self.subTest(config=config), self.assertRaises(ValueError):
self.download(config=config)
def test_missing_checksums_stop_before_download(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
(root / "go.sum").write_text("")
with patch.object(oink_module, "command", return_value=json.dumps(self.config())) as command:
with self.assertRaises(ValueError):
oink_module.download_locked(root)
self.assertEqual(command.call_count, 1)
def test_rejects_download_identity_or_checksum_mismatch(self):
for metadata in [{"Sum": "h1:wrong"}, {"GoModSum": "h1:wrong"},
{"Path": "example.com/theme"}, {"Version": "v9.0.0"}]:
with self.subTest(metadata=metadata), self.assertRaises(ValueError):
self.download(metadata=metadata)
def test_rejects_resolved_override_and_extra_dependency(self):
for resolved in [{"Replace": {"Path": "local"}}, {"Version": "v9.0.0"},
{"Path": "example.com/theme"}]:
with self.subTest(resolved=resolved), self.assertRaises(ValueError):
self.download(resolved=resolved)
with self.assertRaises(ValueError):
self.download(graph=f"{oink_module.SITE_MODULE}\n{oink_module.MODULE} v1.0.0\nexample.com/extra v1.0.0\n")
if __name__ == "__main__":
unittest.main()