diff options
Diffstat (limited to 'rust_tools/auto_update_rust_bootstrap_test.py')
-rwxr-xr-x | rust_tools/auto_update_rust_bootstrap_test.py | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/rust_tools/auto_update_rust_bootstrap_test.py b/rust_tools/auto_update_rust_bootstrap_test.py new file mode 100755 index 00000000..ea58e723 --- /dev/null +++ b/rust_tools/auto_update_rust_bootstrap_test.py @@ -0,0 +1,473 @@ +#!/usr/bin/env python3 +# Copyright 2023 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 auto_update_rust_bootstrap.""" + + +import os +from pathlib import Path +import shutil +import tempfile +import textwrap +import unittest +from unittest import mock + +import auto_update_rust_bootstrap + + +_GIT_PUSH_OUTPUT = r""" +remote: Waiting for private key checker: 2/2 objects left +remote: +remote: Processing changes: new: 1 (\) +remote: Processing changes: new: 1 (|) +remote: Processing changes: new: 1 (/) +remote: Processing changes: refs: 1, new: 1 (/) +remote: Processing changes: refs: 1, new: 1 (/) +remote: Processing changes: refs: 1, new: 1 (/) +remote: Processing changes: refs: 1, new: 1, done +remote: +remote: SUCCESS +remote: +remote: https://chromium-review.googlesource.com/c/chromiumos/overlays/chromiumos-overlay/+/5018826 rust-bootstrap: use prebuilts [WIP] [NEW] +remote: +To https://chromium.googlesource.com/chromiumos/overlays/chromiumos-overlay + * [new reference] HEAD -> refs/for/main +""" + + +class Test(unittest.TestCase): + """Tests for auto_update_rust_bootstrap.""" + + def make_tempdir(self) -> Path: + tempdir = Path( + tempfile.mkdtemp(prefix="auto_update_rust_bootstrap_test_") + ) + self.addCleanup(shutil.rmtree, tempdir) + return tempdir + + def test_git_cl_id_scraping(self): + self.assertEqual( + auto_update_rust_bootstrap.scrape_git_push_cl_id(_GIT_PUSH_OUTPUT), + 5018826, + ) + + def test_ebuild_linking_logic_handles_direct_relative_symlinks(self): + tempdir = self.make_tempdir() + target = tempdir / "target.ebuild" + target.touch() + (tempdir / "symlink.ebuild").symlink_to(target.name) + self.assertTrue( + auto_update_rust_bootstrap.is_ebuild_linked_to_in_dir(target) + ) + + def test_ebuild_linking_logic_handles_direct_absolute_symlinks(self): + tempdir = self.make_tempdir() + target = tempdir / "target.ebuild" + target.touch() + (tempdir / "symlink.ebuild").symlink_to(target) + self.assertTrue( + auto_update_rust_bootstrap.is_ebuild_linked_to_in_dir(target) + ) + + def test_ebuild_linking_logic_handles_indirect_relative_symlinks(self): + tempdir = self.make_tempdir() + target = tempdir / "target.ebuild" + target.touch() + (tempdir / "symlink.ebuild").symlink_to( + Path("..") / tempdir.name / target.name + ) + self.assertTrue( + auto_update_rust_bootstrap.is_ebuild_linked_to_in_dir(target) + ) + + def test_ebuild_linking_logic_handles_broken_symlinks(self): + tempdir = self.make_tempdir() + target = tempdir / "target.ebuild" + target.touch() + (tempdir / "symlink.ebuild").symlink_to("doesnt_exist.ebuild") + self.assertFalse( + auto_update_rust_bootstrap.is_ebuild_linked_to_in_dir(target) + ) + + def test_ebuild_linking_logic_only_steps_through_one_symlink(self): + tempdir = self.make_tempdir() + target = tempdir / "target.ebuild" + target.symlink_to("doesnt_exist.ebuild") + (tempdir / "symlink.ebuild").symlink_to(target.name) + self.assertTrue( + auto_update_rust_bootstrap.is_ebuild_linked_to_in_dir(target) + ) + + def test_raw_bootstrap_seq_finding_functions(self): + ebuild_contents = textwrap.dedent( + """\ + # Some copyright + FOO=bar + # Comment about RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=( + RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=( # another comment + 1.2.3 # (with a comment with parens) + 4.5.6 + ) + """ + ) + + ebuild_lines = ebuild_contents.splitlines() + ( + start, + end, + ) = auto_update_rust_bootstrap.find_raw_bootstrap_sequence_lines( + ebuild_lines + ) + self.assertEqual(start, len(ebuild_lines) - 4) + self.assertEqual(end, len(ebuild_lines) - 1) + + def test_collect_ebuilds_by_version_ignores_older_versions(self): + tempdir = self.make_tempdir() + ebuild_170 = tempdir / "rust-bootstrap-1.70.0.ebuild" + ebuild_170.touch() + ebuild_170_r1 = tempdir / "rust-bootstrap-1.70.0-r1.ebuild" + ebuild_170_r1.touch() + ebuild_171_r2 = tempdir / "rust-bootstrap-1.71.1-r2.ebuild" + ebuild_171_r2.touch() + + self.assertEqual( + auto_update_rust_bootstrap.collect_ebuilds_by_version(tempdir), + [ + ( + auto_update_rust_bootstrap.EbuildVersion( + major=1, minor=70, patch=0, rev=1 + ), + ebuild_170_r1, + ), + ( + auto_update_rust_bootstrap.EbuildVersion( + major=1, minor=71, patch=1, rev=2 + ), + ebuild_171_r2, + ), + ], + ) + + def test_has_prebuilt_works(self): + tempdir = self.make_tempdir() + ebuild = tempdir / "rust-bootstrap-1.70.0.ebuild" + ebuild.write_text( + textwrap.dedent( + """\ + # Some copyright + FOO=bar + # Comment about RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=( + RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=( # another comment + 1.67.0 + 1.68.1 + 1.69.0 + ) + """ + ), + encoding="utf-8", + ) + + self.assertTrue( + auto_update_rust_bootstrap.version_listed_in_bootstrap_sequence( + ebuild, + auto_update_rust_bootstrap.EbuildVersion( + major=1, + minor=69, + patch=0, + rev=0, + ), + ) + ) + + self.assertFalse( + auto_update_rust_bootstrap.version_listed_in_bootstrap_sequence( + ebuild, + auto_update_rust_bootstrap.EbuildVersion( + major=1, + minor=70, + patch=0, + rev=0, + ), + ) + ) + + def test_ebuild_updating_works(self): + tempdir = self.make_tempdir() + ebuild = tempdir / "rust-bootstrap-1.70.0.ebuild" + ebuild.write_text( + textwrap.dedent( + """\ + # Some copyright + FOO=bar + RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=( + \t1.67.0 + \t1.68.1 + \t1.69.0 + ) + """ + ), + encoding="utf-8", + ) + + auto_update_rust_bootstrap.add_version_to_bootstrap_sequence( + ebuild, + auto_update_rust_bootstrap.EbuildVersion( + major=1, + minor=70, + patch=1, + rev=2, + ), + dry_run=False, + ) + + self.assertEqual( + ebuild.read_text(encoding="utf-8"), + textwrap.dedent( + """\ + # Some copyright + FOO=bar + RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=( + \t1.67.0 + \t1.68.1 + \t1.69.0 + \t1.70.1-r2 + ) + """ + ), + ) + + def test_ebuild_version_parsing_works(self): + self.assertEqual( + auto_update_rust_bootstrap.parse_ebuild_version( + "rust-bootstrap-1.70.0-r2.ebuild" + ), + auto_update_rust_bootstrap.EbuildVersion( + major=1, minor=70, patch=0, rev=2 + ), + ) + + self.assertEqual( + auto_update_rust_bootstrap.parse_ebuild_version( + "rust-bootstrap-2.80.3.ebuild" + ), + auto_update_rust_bootstrap.EbuildVersion( + major=2, minor=80, patch=3, rev=0 + ), + ) + + with self.assertRaises(ValueError): + auto_update_rust_bootstrap.parse_ebuild_version( + "rust-bootstrap-2.80.3_pre1234.ebuild" + ) + + def test_raw_ebuild_version_parsing_works(self): + self.assertEqual( + auto_update_rust_bootstrap.parse_raw_ebuild_version("1.70.0-r2"), + auto_update_rust_bootstrap.EbuildVersion( + major=1, minor=70, patch=0, rev=2 + ), + ) + + with self.assertRaises(ValueError): + auto_update_rust_bootstrap.parse_ebuild_version("2.80.3_pre1234") + + def test_ensure_newest_version_does_nothing_if_no_new_rust_version(self): + tempdir = self.make_tempdir() + rust = tempdir / "rust" + rust.mkdir() + (rust / "rust-1.70.0-r1.ebuild").touch() + rust_bootstrap = tempdir / "rust-bootstrap" + rust_bootstrap.mkdir() + (rust_bootstrap / "rust-bootstrap-1.70.0.ebuild").touch() + + self.assertFalse( + auto_update_rust_bootstrap.maybe_add_new_rust_bootstrap_version( + tempdir, rust_bootstrap, dry_run=True + ) + ) + + @mock.patch.object(auto_update_rust_bootstrap, "update_ebuild_manifest") + def test_ensure_newest_version_upgrades_rust_bootstrap_properly( + self, update_ebuild_manifest + ): + tempdir = self.make_tempdir() + rust = tempdir / "rust" + rust.mkdir() + (rust / "rust-1.71.0-r1.ebuild").touch() + rust_bootstrap = tempdir / "rust-bootstrap" + rust_bootstrap.mkdir() + rust_bootstrap_1_70 = rust_bootstrap / "rust-bootstrap-1.70.0-r2.ebuild" + + rust_bootstrap_contents = textwrap.dedent( + """\ + # Some copyright + FOO=bar + RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=( + \t1.67.0 + \t1.68.1 + \t1.69.0 + \t1.70.0-r1 + ) + """ + ) + rust_bootstrap_1_70.write_text( + rust_bootstrap_contents, encoding="utf-8" + ) + + self.assertTrue( + auto_update_rust_bootstrap.maybe_add_new_rust_bootstrap_version( + tempdir, rust_bootstrap, dry_run=False, commit=False + ) + ) + update_ebuild_manifest.assert_called_once() + rust_bootstrap_1_71 = rust_bootstrap / "rust-bootstrap-1.71.0.ebuild" + + self.assertTrue(rust_bootstrap_1_70.is_symlink()) + self.assertEqual( + os.readlink(rust_bootstrap_1_70), + rust_bootstrap_1_71.name, + ) + self.assertFalse(rust_bootstrap_1_71.is_symlink()) + self.assertEqual( + rust_bootstrap_1_71.read_text(encoding="utf-8"), + rust_bootstrap_contents, + ) + + def test_ensure_newest_version_breaks_if_prebuilt_is_not_available(self): + tempdir = self.make_tempdir() + rust = tempdir / "rust" + rust.mkdir() + (rust / "rust-1.71.0-r1.ebuild").touch() + rust_bootstrap = tempdir / "rust-bootstrap" + rust_bootstrap.mkdir() + rust_bootstrap_1_70 = rust_bootstrap / "rust-bootstrap-1.70.0-r2.ebuild" + + rust_bootstrap_contents = textwrap.dedent( + """\ + # Some copyright + FOO=bar + RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=( + \t1.67.0 + \t1.68.1 + \t1.69.0 + # Note: Missing 1.70.0 for rust-bootstrap-1.71.1 + ) + """ + ) + rust_bootstrap_1_70.write_text( + rust_bootstrap_contents, encoding="utf-8" + ) + + with self.assertRaises( + auto_update_rust_bootstrap.MissingRustBootstrapPrebuiltError + ): + auto_update_rust_bootstrap.maybe_add_new_rust_bootstrap_version( + tempdir, rust_bootstrap, dry_run=True + ) + + def test_version_deletion_does_nothing_if_all_versions_are_needed(self): + tempdir = self.make_tempdir() + rust = tempdir / "rust" + rust.mkdir() + (rust / "rust-1.71.0-r1.ebuild").touch() + rust_bootstrap = tempdir / "rust-bootstrap" + rust_bootstrap.mkdir() + (rust_bootstrap / "rust-bootstrap-1.70.0-r2.ebuild").touch() + + self.assertFalse( + auto_update_rust_bootstrap.maybe_delete_old_rust_bootstrap_ebuilds( + tempdir, rust_bootstrap, dry_run=True + ) + ) + + def test_version_deletion_ignores_newer_than_needed_versions(self): + tempdir = self.make_tempdir() + rust = tempdir / "rust" + rust.mkdir() + (rust / "rust-1.71.0-r1.ebuild").touch() + rust_bootstrap = tempdir / "rust-bootstrap" + rust_bootstrap.mkdir() + (rust_bootstrap / "rust-bootstrap-1.70.0-r2.ebuild").touch() + (rust_bootstrap / "rust-bootstrap-1.71.0-r1.ebuild").touch() + (rust_bootstrap / "rust-bootstrap-1.72.0.ebuild").touch() + + self.assertFalse( + auto_update_rust_bootstrap.maybe_delete_old_rust_bootstrap_ebuilds( + tempdir, rust_bootstrap, dry_run=True + ) + ) + + @mock.patch.object(auto_update_rust_bootstrap, "update_ebuild_manifest") + def test_version_deletion_deletes_old_files(self, update_ebuild_manifest): + tempdir = self.make_tempdir() + rust = tempdir / "rust" + rust.mkdir() + (rust / "rust-1.71.0-r1.ebuild").touch() + rust_bootstrap = tempdir / "rust-bootstrap" + rust_bootstrap.mkdir() + needed_rust_bootstrap = ( + rust_bootstrap / "rust-bootstrap-1.70.0-r2.ebuild" + ) + needed_rust_bootstrap.touch() + + # There are quite a few of these, so corner-cases are tested. + + # Symlink to outside of the group of files to delete. + bootstrap_1_68_symlink = rust_bootstrap / "rust-bootstrap-1.68.0.ebuild" + bootstrap_1_68_symlink.symlink_to(needed_rust_bootstrap.name) + # Ensure that absolute symlinks are caught. + bootstrap_1_68_symlink_abs = ( + rust_bootstrap / "rust-bootstrap-1.68.0-r1.ebuild" + ) + bootstrap_1_68_symlink_abs.symlink_to(needed_rust_bootstrap) + # Regular files should be no issue. + bootstrap_1_69_regular = rust_bootstrap / "rust-bootstrap-1.69.0.ebuild" + bootstrap_1_69_regular.touch() + # Symlinks linking back into the set of files to delete should also be + # no issue. + bootstrap_1_69_symlink = ( + rust_bootstrap / "rust-bootstrap-1.69.0-r2.ebuild" + ) + bootstrap_1_69_symlink.symlink_to(bootstrap_1_69_regular.name) + + self.assertTrue( + auto_update_rust_bootstrap.maybe_delete_old_rust_bootstrap_ebuilds( + tempdir, + rust_bootstrap, + dry_run=False, + commit=False, + ) + ) + update_ebuild_manifest.assert_called_once() + + self.assertFalse(bootstrap_1_68_symlink.exists()) + self.assertFalse(bootstrap_1_68_symlink_abs.exists()) + self.assertFalse(bootstrap_1_69_regular.exists()) + self.assertFalse(bootstrap_1_69_symlink.exists()) + self.assertTrue(needed_rust_bootstrap.exists()) + + def test_version_deletion_raises_when_old_file_has_dep(self): + tempdir = self.make_tempdir() + rust = tempdir / "rust" + rust.mkdir() + (rust / "rust-1.71.0-r1.ebuild").touch() + rust_bootstrap = tempdir / "rust-bootstrap" + rust_bootstrap.mkdir() + old_rust_bootstrap = rust_bootstrap / "rust-bootstrap-1.69.0-r1.ebuild" + old_rust_bootstrap.touch() + (rust_bootstrap / "rust-bootstrap-1.70.0-r2.ebuild").symlink_to( + old_rust_bootstrap.name + ) + + with self.assertRaises( + auto_update_rust_bootstrap.OldEbuildIsLinkedToError + ): + auto_update_rust_bootstrap.maybe_delete_old_rust_bootstrap_ebuilds( + tempdir, rust_bootstrap, dry_run=True + ) + + +if __name__ == "__main__": + unittest.main() |