| #!/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 |
| """ |
| |
| _GIT_PUSH_MULTI_CL_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/+/5339923 rust-bootstrap: add version 1.75.0 [NEW] |
| remote: https://chromium-review.googlesource.com/c/chromiumos/overlays/chromiumos-overlay/+/5339924 rust-bootstrap: remove unused ebuilds [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_strs( |
| _GIT_PUSH_OUTPUT |
| ), |
| ["5018826"], |
| ) |
| |
| self.assertEqual( |
| auto_update_rust_bootstrap.scrape_git_push_cl_id_strs( |
| _GIT_PUSH_MULTI_CL_OUTPUT |
| ), |
| ["5339923", "5339924"], |
| ) |
| |
| 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() |