blob: f82ef4856887fbd89e61e7c99ed0d27d7145e917 [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2020 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Tests for rust_uprev.py"""
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest
from unittest import mock
from llvm_tools import git
import rust_uprev
from rust_uprev import RustVersion
def _fail_command(cmd, *_args, **_kwargs):
err = subprocess.CalledProcessError(returncode=1, cmd=cmd)
err.stderr = b"mock failure"
raise err
class FetchDistfileTest(unittest.TestCase):
"""Tests rust_uprev.fetch_distfile_from_mirror()"""
@mock.patch.object(
rust_uprev, "get_distdir", return_value="/fake/distfiles"
)
@mock.patch.object(subprocess, "call", side_effect=_fail_command)
def test_fetch_difstfile_fail(self, *_args) -> None:
with self.assertRaises(subprocess.CalledProcessError):
rust_uprev.fetch_distfile_from_mirror("test_distfile.tar.gz")
@mock.patch.object(
rust_uprev,
"get_command_output_unchecked",
return_value="AccessDeniedException: Access denied.",
)
@mock.patch.object(
rust_uprev, "get_distdir", return_value="/fake/distfiles"
)
@mock.patch.object(subprocess, "call", return_value=0)
def test_fetch_distfile_acl_access_denied(self, *_args) -> None:
rust_uprev.fetch_distfile_from_mirror("test_distfile.tar.gz")
@mock.patch.object(
rust_uprev,
"get_command_output_unchecked",
return_value='[ { "entity": "allUsers", "role": "READER" } ]',
)
@mock.patch.object(
rust_uprev, "get_distdir", return_value="/fake/distfiles"
)
@mock.patch.object(subprocess, "call", return_value=0)
def test_fetch_distfile_acl_ok(self, *_args) -> None:
rust_uprev.fetch_distfile_from_mirror("test_distfile.tar.gz")
@mock.patch.object(
rust_uprev,
"get_command_output_unchecked",
return_value='[ { "entity": "___fake@google.com", "role": "OWNER" } ]',
)
@mock.patch.object(
rust_uprev, "get_distdir", return_value="/fake/distfiles"
)
@mock.patch.object(subprocess, "call", return_value=0)
def test_fetch_distfile_acl_wrong(self, *_args) -> None:
with self.assertRaisesRegex(Exception, "allUsers.*READER"):
with self.assertLogs(level="ERROR") as log:
rust_uprev.fetch_distfile_from_mirror("test_distfile.tar.gz")
self.assertIn(
'[ { "entity": "___fake@google.com", "role": "OWNER" } ]',
"\n".join(log.output),
)
class FindEbuildPathTest(unittest.TestCase):
"""Tests for rust_uprev.find_ebuild_path()"""
def test_exact_version(self):
with tempfile.TemporaryDirectory() as tmpdir:
ebuild = Path(tmpdir, "test-1.3.4.ebuild")
ebuild.touch()
Path(tmpdir, "test-1.2.3.ebuild").touch()
result = rust_uprev.find_ebuild_path(
tmpdir, "test", rust_uprev.RustVersion(1, 3, 4)
)
self.assertEqual(result, ebuild)
def test_no_version(self):
with tempfile.TemporaryDirectory() as tmpdir:
ebuild = Path(tmpdir, "test-1.2.3.ebuild")
ebuild.touch()
result = rust_uprev.find_ebuild_path(tmpdir, "test")
self.assertEqual(result, ebuild)
def test_patch_version(self):
with tempfile.TemporaryDirectory() as tmpdir:
ebuild = Path(tmpdir, "test-1.3.4-r3.ebuild")
ebuild.touch()
Path(tmpdir, "test-1.2.3.ebuild").touch()
result = rust_uprev.find_ebuild_path(
tmpdir, "test", rust_uprev.RustVersion(1, 3, 4)
)
self.assertEqual(result, ebuild)
class RustVersionTest(unittest.TestCase):
"""Tests for RustVersion class"""
def test_str(self):
obj = rust_uprev.RustVersion(major=1, minor=2, patch=3)
self.assertEqual(str(obj), "1.2.3")
def test_parse_version_only(self):
expected = rust_uprev.RustVersion(major=1, minor=2, patch=3)
actual = rust_uprev.RustVersion.parse("1.2.3")
self.assertEqual(expected, actual)
def test_parse_ebuild_name(self):
expected = rust_uprev.RustVersion(major=1, minor=2, patch=3)
actual = rust_uprev.RustVersion.parse_from_ebuild("rust-1.2.3.ebuild")
self.assertEqual(expected, actual)
actual = rust_uprev.RustVersion.parse_from_ebuild(
"rust-1.2.3-r1.ebuild"
)
self.assertEqual(expected, actual)
def test_parse_fail(self):
with self.assertRaises(AssertionError) as context:
rust_uprev.RustVersion.parse("invalid-rust-1.2.3")
self.assertEqual(
"failed to parse 'invalid-rust-1.2.3'", str(context.exception)
)
class PrepareUprevTest(unittest.TestCase):
"""Tests for prepare_uprev step in rust_uprev"""
def setUp(self):
self.bootstrap_version = rust_uprev.RustVersion(1, 1, 0)
self.version_old = rust_uprev.RustVersion(1, 2, 3)
self.version_new = rust_uprev.RustVersion(1, 3, 5)
@mock.patch.object(
rust_uprev,
"find_ebuild_for_rust_version",
return_value="/path/to/ebuild",
)
@mock.patch.object(rust_uprev, "find_ebuild_path")
@mock.patch.object(rust_uprev, "get_command_output")
def test_success_with_template(
self, mock_command, mock_find_ebuild, _ebuild_for_version
):
bootstrap_ebuild_path = Path(
"/path/to/rust-bootstrap/",
f"rust-bootstrap-{self.bootstrap_version}.ebuild",
)
mock_find_ebuild.return_value = bootstrap_ebuild_path
expected = (self.version_old, "/path/to/ebuild", self.bootstrap_version)
actual = rust_uprev.prepare_uprev(
rust_version=self.version_new, template=self.version_old
)
self.assertEqual(expected, actual)
mock_command.assert_not_called()
@mock.patch.object(
rust_uprev,
"find_ebuild_for_rust_version",
return_value="/path/to/ebuild",
)
@mock.patch.object(
rust_uprev,
"get_rust_bootstrap_version",
return_value=RustVersion(0, 41, 12),
)
@mock.patch.object(rust_uprev, "get_command_output")
def test_return_none_with_template_larger_than_input(
self, mock_command, *_args
):
ret = rust_uprev.prepare_uprev(
rust_version=self.version_old, template=self.version_new
)
self.assertIsNone(ret)
mock_command.assert_not_called()
@mock.patch.object(rust_uprev, "find_ebuild_path")
@mock.patch.object(os.path, "exists")
@mock.patch.object(rust_uprev, "get_command_output")
def test_success_without_template(
self, mock_command, mock_exists, mock_find_ebuild
):
rust_ebuild_path = f"/path/to/rust/rust-{self.version_old}-r3.ebuild"
mock_command.return_value = rust_ebuild_path
bootstrap_ebuild_path = Path(
"/path/to/rust-bootstrap",
f"rust-bootstrap-{self.bootstrap_version}.ebuild",
)
mock_find_ebuild.return_value = bootstrap_ebuild_path
expected = (self.version_old, rust_ebuild_path, self.bootstrap_version)
actual = rust_uprev.prepare_uprev(
rust_version=self.version_new, template=None
)
self.assertEqual(expected, actual)
mock_command.assert_called_once_with(["equery", "w", "rust"])
mock_exists.assert_not_called()
@mock.patch.object(
rust_uprev,
"get_rust_bootstrap_version",
return_value=RustVersion(0, 41, 12),
)
@mock.patch.object(os.path, "exists")
@mock.patch.object(rust_uprev, "get_command_output")
def test_return_none_with_ebuild_larger_than_input(
self, mock_command, mock_exists, *_args
):
mock_command.return_value = (
f"/path/to/rust/rust-{self.version_new}.ebuild"
)
ret = rust_uprev.prepare_uprev(
rust_version=self.version_old, template=None
)
self.assertIsNone(ret)
mock_exists.assert_not_called()
def test_prepare_uprev_from_json(self):
ebuild_path = "/path/to/the/ebuild"
json_result = (
list(self.version_new),
ebuild_path,
list(self.bootstrap_version),
)
expected = (self.version_new, ebuild_path, self.bootstrap_version)
actual = rust_uprev.prepare_uprev_from_json(json_result)
self.assertEqual(expected, actual)
class UpdateEbuildTest(unittest.TestCase):
"""Tests for update_ebuild step in rust_uprev"""
ebuild_file_before = """
BOOTSTRAP_VERSION="1.2.0"
"""
ebuild_file_after = """
BOOTSTRAP_VERSION="1.3.6"
"""
def test_success(self):
mock_open = mock.mock_open(read_data=self.ebuild_file_before)
# ebuild_file and new bootstrap version are deliberately different
ebuild_file = "/path/to/rust/rust-1.3.5.ebuild"
with mock.patch("builtins.open", mock_open):
rust_uprev.update_ebuild(
ebuild_file, rust_uprev.RustVersion.parse("1.3.6")
)
mock_open.return_value.__enter__().write.assert_called_once_with(
self.ebuild_file_after
)
def test_fail_when_ebuild_misses_a_variable(self):
mock_open = mock.mock_open(read_data="")
ebuild_file = "/path/to/rust/rust-1.3.5.ebuild"
with mock.patch("builtins.open", mock_open):
with self.assertRaises(RuntimeError) as context:
rust_uprev.update_ebuild(
ebuild_file, rust_uprev.RustVersion.parse("1.2.0")
)
self.assertEqual(
"BOOTSTRAP_VERSION not found in rust ebuild", str(context.exception)
)
class UpdateManifestTest(unittest.TestCase):
"""Tests for update_manifest step in rust_uprev"""
@mock.patch.object(rust_uprev, "ebuild_actions")
def test_update_manifest(self, mock_run):
ebuild_file = Path("/path/to/rust/rust-1.1.1.ebuild")
rust_uprev.update_manifest(ebuild_file)
mock_run.assert_called_once_with("rust", ["manifest"])
class UpdateBootstrapEbuildTest(unittest.TestCase):
"""Tests for rust_uprev.update_bootstrap_ebuild()"""
def test_update_bootstrap_ebuild(self):
# The update should do two things:
# 1. Create a copy of rust-bootstrap's ebuild with the new version number.
# 2. Add the old PV to RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE.
with tempfile.TemporaryDirectory() as tmpdir_str, mock.patch.object(
rust_uprev, "find_ebuild_path"
) as mock_find_ebuild:
tmpdir = Path(tmpdir_str)
bootstrapdir = Path.joinpath(tmpdir, "rust-bootstrap")
bootstrapdir.mkdir()
old_ebuild = bootstrapdir.joinpath("rust-bootstrap-1.45.2.ebuild")
old_ebuild.write_text(
encoding="utf-8",
data="""
some text
RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=(
\t1.43.1
\t1.44.1
)
some more text
""",
)
mock_find_ebuild.return_value = old_ebuild
rust_uprev.update_bootstrap_ebuild(rust_uprev.RustVersion(1, 46, 0))
new_ebuild = bootstrapdir.joinpath("rust-bootstrap-1.46.0.ebuild")
self.assertTrue(new_ebuild.exists())
text = new_ebuild.read_text()
self.assertEqual(
text,
"""
some text
RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=(
\t1.43.1
\t1.44.1
\t1.45.2
)
some more text
""",
)
class UpdateRustPackagesTests(unittest.TestCase):
"""Tests for update_rust_packages step."""
def setUp(self):
self.old_version = rust_uprev.RustVersion(1, 1, 0)
self.current_version = rust_uprev.RustVersion(1, 2, 3)
self.new_version = rust_uprev.RustVersion(1, 3, 5)
self.ebuild_file = os.path.join(
rust_uprev.RUST_PATH, "rust-{self.new_version}.ebuild"
)
def test_add_new_rust_packages(self):
package_before = (
f"dev-lang/rust-{self.old_version}\n"
f"dev-lang/rust-{self.current_version}"
)
package_after = (
f"dev-lang/rust-{self.old_version}\n"
f"dev-lang/rust-{self.current_version}\n"
f"dev-lang/rust-{self.new_version}"
)
mock_open = mock.mock_open(read_data=package_before)
with mock.patch("builtins.open", mock_open):
rust_uprev.update_rust_packages(self.new_version, add=True)
mock_open.return_value.__enter__().write.assert_called_once_with(
package_after
)
def test_remove_old_rust_packages(self):
package_before = (
f"dev-lang/rust-{self.old_version}\n"
f"dev-lang/rust-{self.current_version}\n"
f"dev-lang/rust-{self.new_version}"
)
package_after = (
f"dev-lang/rust-{self.current_version}\n"
f"dev-lang/rust-{self.new_version}"
)
mock_open = mock.mock_open(read_data=package_before)
with mock.patch("builtins.open", mock_open):
rust_uprev.update_rust_packages(self.old_version, add=False)
mock_open.return_value.__enter__().write.assert_called_once_with(
package_after
)
class RustUprevOtherStagesTests(unittest.TestCase):
"""Tests for other steps in rust_uprev"""
def setUp(self):
self.old_version = rust_uprev.RustVersion(1, 1, 0)
self.current_version = rust_uprev.RustVersion(1, 2, 3)
self.new_version = rust_uprev.RustVersion(1, 3, 5)
self.ebuild_file = os.path.join(
rust_uprev.RUST_PATH, "rust-{self.new_version}.ebuild"
)
@mock.patch.object(shutil, "copyfile")
@mock.patch.object(os, "listdir")
@mock.patch.object(subprocess, "check_call")
def test_copy_patches(self, mock_call, mock_ls, mock_copy):
mock_ls.return_value = [
f"rust-{self.old_version}-patch-1.patch",
f"rust-{self.old_version}-patch-2-old.patch",
f"rust-{self.current_version}-patch-1.patch",
f"rust-{self.current_version}-patch-2-new.patch",
]
rust_uprev.copy_patches(
rust_uprev.RUST_PATH, self.current_version, self.new_version
)
mock_copy.assert_has_calls(
[
mock.call(
os.path.join(
rust_uprev.RUST_PATH,
"files",
f"rust-{self.current_version}-patch-1.patch",
),
os.path.join(
rust_uprev.RUST_PATH,
"files",
f"rust-{self.new_version}-patch-1.patch",
),
),
mock.call(
os.path.join(
rust_uprev.RUST_PATH,
"files",
f"rust-{self.current_version}-patch-2-new.patch",
),
os.path.join(
rust_uprev.RUST_PATH,
"files",
f"rust-{self.new_version}-patch-2-new.patch",
),
),
]
)
mock_call.assert_called_once_with(
["git", "add", f"rust-{self.new_version}-*.patch"],
cwd=rust_uprev.RUST_PATH.joinpath("files"),
)
@mock.patch.object(shutil, "copyfile")
@mock.patch.object(subprocess, "check_call")
def test_create_ebuild(self, mock_call, mock_copy):
template_ebuild = f"/path/to/rust-{self.current_version}-r2.ebuild"
rust_uprev.create_ebuild(template_ebuild, self.new_version)
mock_copy.assert_called_once_with(
template_ebuild,
rust_uprev.RUST_PATH.joinpath(f"rust-{self.new_version}.ebuild"),
)
mock_call.assert_called_once_with(
["git", "add", f"rust-{self.new_version}.ebuild"],
cwd=rust_uprev.RUST_PATH,
)
@mock.patch.object(rust_uprev, "find_ebuild_for_package")
@mock.patch.object(subprocess, "check_call")
def test_remove_rust_bootstrap_version(self, mock_call, *_args):
bootstrap_path = os.path.join(
rust_uprev.RUST_PATH, "..", "rust-bootstrap"
)
rust_uprev.remove_rust_bootstrap_version(
self.old_version, lambda *x: ()
)
mock_call.has_calls(
[
[
"git",
"rm",
os.path.join(
bootstrap_path,
"files",
f"rust-bootstrap-{self.old_version}-*.patch",
),
],
[
"git",
"rm",
os.path.join(
bootstrap_path,
f"rust-bootstrap-{self.old_version}.ebuild",
),
],
]
)
@mock.patch.object(rust_uprev, "find_ebuild_path")
@mock.patch.object(subprocess, "check_call")
def test_remove_virtual_rust(self, mock_call, mock_find_ebuild):
ebuild_path = Path(
f"/some/dir/virtual/rust/rust-{self.old_version}.ebuild"
)
mock_find_ebuild.return_value = Path(ebuild_path)
rust_uprev.remove_virtual_rust(self.old_version)
mock_call.assert_called_once_with(
["git", "rm", str(ebuild_path.name)], cwd=ebuild_path.parent
)
@mock.patch.object(rust_uprev, "find_ebuild_path")
@mock.patch.object(shutil, "copyfile")
@mock.patch.object(subprocess, "check_call")
def test_update_virtual_rust(self, mock_call, mock_copy, mock_find_ebuild):
ebuild_path = Path(
f"/some/dir/virtual/rust/rust-{self.current_version}.ebuild"
)
mock_find_ebuild.return_value = Path(ebuild_path)
rust_uprev.update_virtual_rust(self.current_version, self.new_version)
mock_call.assert_called_once_with(
["git", "add", f"rust-{self.new_version}.ebuild"],
cwd=ebuild_path.parent,
)
mock_copy.assert_called_once_with(
ebuild_path.parent.joinpath(f"rust-{self.current_version}.ebuild"),
ebuild_path.parent.joinpath(f"rust-{self.new_version}.ebuild"),
)
@mock.patch.object(os, "listdir")
def test_find_oldest_rust_version_in_chroot_pass(self, mock_ls):
oldest_version_name = f"rust-{self.old_version}.ebuild"
mock_ls.return_value = [
oldest_version_name,
f"rust-{self.current_version}.ebuild",
f"rust-{self.new_version}.ebuild",
]
actual = rust_uprev.find_oldest_rust_version_in_chroot()
expected = (
self.old_version,
os.path.join(rust_uprev.RUST_PATH, oldest_version_name),
)
self.assertEqual(expected, actual)
@mock.patch.object(os, "listdir")
def test_find_oldest_rust_version_in_chroot_fail_with_only_one_ebuild(
self, mock_ls
):
mock_ls.return_value = [f"rust-{self.new_version}.ebuild"]
with self.assertRaises(RuntimeError) as context:
rust_uprev.find_oldest_rust_version_in_chroot()
self.assertEqual(
"Expect to find more than one Rust versions", str(context.exception)
)
@mock.patch.object(rust_uprev, "get_command_output")
@mock.patch.object(git, "CreateBranch")
def test_create_new_repo(self, mock_branch, mock_output):
mock_output.return_value = ""
rust_uprev.create_new_repo(self.new_version)
mock_branch.assert_called_once_with(
rust_uprev.RUST_PATH, f"rust-to-{self.new_version}"
)
@mock.patch.object(rust_uprev, "get_command_output")
@mock.patch.object(subprocess, "check_call")
def test_build_cross_compiler(self, mock_call, mock_output):
mock_output.return_value = f"rust-{self.new_version}.ebuild"
cros_targets = [
"x86_64-cros-linux-gnu",
"armv7a-cros-linux-gnueabihf",
"aarch64-cros-linux-gnu",
]
all_triples = ["x86_64-pc-linux-gnu"] + cros_targets
rust_ebuild = "RUSTC_TARGET_TRIPLES=(" + "\n\t".join(all_triples) + ")"
mock_open = mock.mock_open(read_data=rust_ebuild)
with mock.patch("builtins.open", mock_open):
rust_uprev.build_cross_compiler()
mock_call.assert_called_once_with(
["sudo", "emerge", "-j", "-G"]
+ [f"cross-{x}/gcc" for x in cros_targets + ["arm-none-eabi"]]
)
if __name__ == "__main__":
unittest.main()