1#!/usr/bin/env python3 2# Copyright 2023 The ChromiumOS Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Tests for auto_update_rust_bootstrap.""" 7 8import os 9from pathlib import Path 10import shutil 11import tempfile 12import textwrap 13import unittest 14from unittest import mock 15 16import auto_update_rust_bootstrap 17 18 19_GIT_PUSH_OUTPUT = r""" 20remote: Waiting for private key checker: 2/2 objects left 21remote: 22remote: Processing changes: new: 1 (\) 23remote: Processing changes: new: 1 (|) 24remote: Processing changes: new: 1 (/) 25remote: Processing changes: refs: 1, new: 1 (/) 26remote: Processing changes: refs: 1, new: 1 (/) 27remote: Processing changes: refs: 1, new: 1 (/) 28remote: Processing changes: refs: 1, new: 1, done 29remote: 30remote: SUCCESS 31remote: 32remote: https://chromium-review.googlesource.com/c/chromiumos/overlays/chromiumos-overlay/+/5018826 rust-bootstrap: use prebuilts [WIP] [NEW] 33remote: 34To https://chromium.googlesource.com/chromiumos/overlays/chromiumos-overlay 35 * [new reference] HEAD -> refs/for/main 36""" 37 38_GIT_PUSH_MULTI_CL_OUTPUT = r""" 39remote: Waiting for private key checker: 2/2 objects left 40remote: 41remote: Processing changes: new: 1 (\) 42remote: Processing changes: new: 1 (|) 43remote: Processing changes: new: 1 (/) 44remote: Processing changes: refs: 1, new: 1 (/) 45remote: Processing changes: refs: 1, new: 1 (/) 46remote: Processing changes: refs: 1, new: 1 (/) 47remote: Processing changes: refs: 1, new: 1, done 48remote: 49remote: SUCCESS 50remote: 51remote: https://chromium-review.googlesource.com/c/chromiumos/overlays/chromiumos-overlay/+/5339923 rust-bootstrap: add version 1.75.0 [NEW] 52remote: https://chromium-review.googlesource.com/c/chromiumos/overlays/chromiumos-overlay/+/5339924 rust-bootstrap: remove unused ebuilds [NEW] 53remote: 54To https://chromium.googlesource.com/chromiumos/overlays/chromiumos-overlay 55 * [new reference] HEAD -> refs/for/main 56""" 57 58 59class Test(unittest.TestCase): 60 """Tests for auto_update_rust_bootstrap.""" 61 62 def make_tempdir(self) -> Path: 63 tempdir = Path( 64 tempfile.mkdtemp(prefix="auto_update_rust_bootstrap_test_") 65 ) 66 self.addCleanup(shutil.rmtree, tempdir) 67 return tempdir 68 69 def test_git_cl_id_scraping(self): 70 self.assertEqual( 71 auto_update_rust_bootstrap.scrape_git_push_cl_id_strs( 72 _GIT_PUSH_OUTPUT 73 ), 74 ["5018826"], 75 ) 76 77 self.assertEqual( 78 auto_update_rust_bootstrap.scrape_git_push_cl_id_strs( 79 _GIT_PUSH_MULTI_CL_OUTPUT 80 ), 81 ["5339923", "5339924"], 82 ) 83 84 def test_ebuild_linking_logic_handles_direct_relative_symlinks(self): 85 tempdir = self.make_tempdir() 86 target = tempdir / "target.ebuild" 87 target.touch() 88 (tempdir / "symlink.ebuild").symlink_to(target.name) 89 self.assertTrue( 90 auto_update_rust_bootstrap.is_ebuild_linked_to_in_dir(target) 91 ) 92 93 def test_ebuild_linking_logic_handles_direct_absolute_symlinks(self): 94 tempdir = self.make_tempdir() 95 target = tempdir / "target.ebuild" 96 target.touch() 97 (tempdir / "symlink.ebuild").symlink_to(target) 98 self.assertTrue( 99 auto_update_rust_bootstrap.is_ebuild_linked_to_in_dir(target) 100 ) 101 102 def test_ebuild_linking_logic_handles_indirect_relative_symlinks(self): 103 tempdir = self.make_tempdir() 104 target = tempdir / "target.ebuild" 105 target.touch() 106 (tempdir / "symlink.ebuild").symlink_to( 107 Path("..") / tempdir.name / target.name 108 ) 109 self.assertTrue( 110 auto_update_rust_bootstrap.is_ebuild_linked_to_in_dir(target) 111 ) 112 113 def test_ebuild_linking_logic_handles_broken_symlinks(self): 114 tempdir = self.make_tempdir() 115 target = tempdir / "target.ebuild" 116 target.touch() 117 (tempdir / "symlink.ebuild").symlink_to("doesnt_exist.ebuild") 118 self.assertFalse( 119 auto_update_rust_bootstrap.is_ebuild_linked_to_in_dir(target) 120 ) 121 122 def test_ebuild_linking_logic_only_steps_through_one_symlink(self): 123 tempdir = self.make_tempdir() 124 target = tempdir / "target.ebuild" 125 target.symlink_to("doesnt_exist.ebuild") 126 (tempdir / "symlink.ebuild").symlink_to(target.name) 127 self.assertTrue( 128 auto_update_rust_bootstrap.is_ebuild_linked_to_in_dir(target) 129 ) 130 131 def test_raw_bootstrap_seq_finding_functions(self): 132 ebuild_contents = textwrap.dedent( 133 """\ 134 # Some copyright 135 FOO=bar 136 # Comment about RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=( 137 RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=( # another comment 138 1.2.3 # (with a comment with parens) 139 4.5.6 140 ) 141 """ 142 ) 143 144 ebuild_lines = ebuild_contents.splitlines() 145 ( 146 start, 147 end, 148 ) = auto_update_rust_bootstrap.find_raw_bootstrap_sequence_lines( 149 ebuild_lines 150 ) 151 self.assertEqual(start, len(ebuild_lines) - 4) 152 self.assertEqual(end, len(ebuild_lines) - 1) 153 154 def test_collect_ebuilds_by_version_ignores_older_versions(self): 155 tempdir = self.make_tempdir() 156 ebuild_170 = tempdir / "rust-bootstrap-1.70.0.ebuild" 157 ebuild_170.touch() 158 ebuild_170_r1 = tempdir / "rust-bootstrap-1.70.0-r1.ebuild" 159 ebuild_170_r1.touch() 160 ebuild_171_r2 = tempdir / "rust-bootstrap-1.71.1-r2.ebuild" 161 ebuild_171_r2.touch() 162 163 self.assertEqual( 164 auto_update_rust_bootstrap.collect_ebuilds_by_version(tempdir), 165 [ 166 ( 167 auto_update_rust_bootstrap.EbuildVersion( 168 major=1, minor=70, patch=0, rev=1 169 ), 170 ebuild_170_r1, 171 ), 172 ( 173 auto_update_rust_bootstrap.EbuildVersion( 174 major=1, minor=71, patch=1, rev=2 175 ), 176 ebuild_171_r2, 177 ), 178 ], 179 ) 180 181 def test_has_prebuilt_works(self): 182 tempdir = self.make_tempdir() 183 ebuild = tempdir / "rust-bootstrap-1.70.0.ebuild" 184 ebuild.write_text( 185 textwrap.dedent( 186 """\ 187 # Some copyright 188 FOO=bar 189 # Comment about RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=( 190 RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=( # another comment 191 1.67.0 192 1.68.1 193 1.69.0 194 ) 195 """ 196 ), 197 encoding="utf-8", 198 ) 199 200 self.assertTrue( 201 auto_update_rust_bootstrap.version_listed_in_bootstrap_sequence( 202 ebuild, 203 auto_update_rust_bootstrap.EbuildVersion( 204 major=1, 205 minor=69, 206 patch=0, 207 rev=0, 208 ), 209 ) 210 ) 211 212 self.assertFalse( 213 auto_update_rust_bootstrap.version_listed_in_bootstrap_sequence( 214 ebuild, 215 auto_update_rust_bootstrap.EbuildVersion( 216 major=1, 217 minor=70, 218 patch=0, 219 rev=0, 220 ), 221 ) 222 ) 223 224 def test_ebuild_updating_works(self): 225 tempdir = self.make_tempdir() 226 ebuild = tempdir / "rust-bootstrap-1.70.0.ebuild" 227 ebuild.write_text( 228 textwrap.dedent( 229 """\ 230 # Some copyright 231 FOO=bar 232 RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=( 233 \t1.67.0 234 \t1.68.1 235 \t1.69.0 236 ) 237 """ 238 ), 239 encoding="utf-8", 240 ) 241 242 auto_update_rust_bootstrap.add_version_to_bootstrap_sequence( 243 ebuild, 244 auto_update_rust_bootstrap.EbuildVersion( 245 major=1, 246 minor=70, 247 patch=1, 248 rev=2, 249 ), 250 dry_run=False, 251 ) 252 253 self.assertEqual( 254 ebuild.read_text(encoding="utf-8"), 255 textwrap.dedent( 256 """\ 257 # Some copyright 258 FOO=bar 259 RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=( 260 \t1.67.0 261 \t1.68.1 262 \t1.69.0 263 \t1.70.1-r2 264 ) 265 """ 266 ), 267 ) 268 269 def test_ebuild_version_parsing_works(self): 270 self.assertEqual( 271 auto_update_rust_bootstrap.parse_ebuild_version( 272 "rust-bootstrap-1.70.0-r2.ebuild" 273 ), 274 auto_update_rust_bootstrap.EbuildVersion( 275 major=1, minor=70, patch=0, rev=2 276 ), 277 ) 278 279 self.assertEqual( 280 auto_update_rust_bootstrap.parse_ebuild_version( 281 "rust-bootstrap-2.80.3.ebuild" 282 ), 283 auto_update_rust_bootstrap.EbuildVersion( 284 major=2, minor=80, patch=3, rev=0 285 ), 286 ) 287 288 with self.assertRaises(ValueError): 289 auto_update_rust_bootstrap.parse_ebuild_version( 290 "rust-bootstrap-2.80.3_pre1234.ebuild" 291 ) 292 293 def test_raw_ebuild_version_parsing_works(self): 294 self.assertEqual( 295 auto_update_rust_bootstrap.parse_raw_ebuild_version("1.70.0-r2"), 296 auto_update_rust_bootstrap.EbuildVersion( 297 major=1, minor=70, patch=0, rev=2 298 ), 299 ) 300 301 with self.assertRaises(ValueError): 302 auto_update_rust_bootstrap.parse_ebuild_version("2.80.3_pre1234") 303 304 def test_ensure_newest_version_does_nothing_if_no_new_rust_version(self): 305 tempdir = self.make_tempdir() 306 rust = tempdir / "rust" 307 rust.mkdir() 308 (rust / "rust-1.70.0-r1.ebuild").touch() 309 rust_bootstrap = tempdir / "rust-bootstrap" 310 rust_bootstrap.mkdir() 311 (rust_bootstrap / "rust-bootstrap-1.70.0.ebuild").touch() 312 313 self.assertFalse( 314 auto_update_rust_bootstrap.maybe_add_new_rust_bootstrap_version( 315 tempdir, rust_bootstrap, dry_run=True 316 ) 317 ) 318 319 @mock.patch.object(auto_update_rust_bootstrap, "update_ebuild_manifest") 320 def test_ensure_newest_version_upgrades_rust_bootstrap_properly( 321 self, update_ebuild_manifest 322 ): 323 tempdir = self.make_tempdir() 324 rust = tempdir / "rust" 325 rust.mkdir() 326 (rust / "rust-1.71.0-r1.ebuild").touch() 327 rust_bootstrap = tempdir / "rust-bootstrap" 328 rust_bootstrap.mkdir() 329 rust_bootstrap_1_70 = rust_bootstrap / "rust-bootstrap-1.70.0-r2.ebuild" 330 331 rust_bootstrap_contents = textwrap.dedent( 332 """\ 333 # Some copyright 334 FOO=bar 335 RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=( 336 \t1.67.0 337 \t1.68.1 338 \t1.69.0 339 \t1.70.0-r1 340 ) 341 """ 342 ) 343 rust_bootstrap_1_70.write_text( 344 rust_bootstrap_contents, encoding="utf-8" 345 ) 346 347 self.assertTrue( 348 auto_update_rust_bootstrap.maybe_add_new_rust_bootstrap_version( 349 tempdir, rust_bootstrap, dry_run=False, commit=False 350 ) 351 ) 352 update_ebuild_manifest.assert_called_once() 353 rust_bootstrap_1_71 = rust_bootstrap / "rust-bootstrap-1.71.0.ebuild" 354 355 self.assertTrue(rust_bootstrap_1_70.is_symlink()) 356 self.assertEqual( 357 os.readlink(rust_bootstrap_1_70), 358 rust_bootstrap_1_71.name, 359 ) 360 self.assertFalse(rust_bootstrap_1_71.is_symlink()) 361 self.assertEqual( 362 rust_bootstrap_1_71.read_text(encoding="utf-8"), 363 rust_bootstrap_contents, 364 ) 365 366 def test_ensure_newest_version_breaks_if_prebuilt_is_not_available(self): 367 tempdir = self.make_tempdir() 368 rust = tempdir / "rust" 369 rust.mkdir() 370 (rust / "rust-1.71.0-r1.ebuild").touch() 371 rust_bootstrap = tempdir / "rust-bootstrap" 372 rust_bootstrap.mkdir() 373 rust_bootstrap_1_70 = rust_bootstrap / "rust-bootstrap-1.70.0-r2.ebuild" 374 375 rust_bootstrap_contents = textwrap.dedent( 376 """\ 377 # Some copyright 378 FOO=bar 379 RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=( 380 \t1.67.0 381 \t1.68.1 382 \t1.69.0 383 # Note: Missing 1.70.0 for rust-bootstrap-1.71.1 384 ) 385 """ 386 ) 387 rust_bootstrap_1_70.write_text( 388 rust_bootstrap_contents, encoding="utf-8" 389 ) 390 391 with self.assertRaises( 392 auto_update_rust_bootstrap.MissingRustBootstrapPrebuiltError 393 ): 394 auto_update_rust_bootstrap.maybe_add_new_rust_bootstrap_version( 395 tempdir, rust_bootstrap, dry_run=True 396 ) 397 398 def test_version_deletion_does_nothing_if_all_versions_are_needed(self): 399 tempdir = self.make_tempdir() 400 rust = tempdir / "rust" 401 rust.mkdir() 402 (rust / "rust-1.71.0-r1.ebuild").touch() 403 rust_bootstrap = tempdir / "rust-bootstrap" 404 rust_bootstrap.mkdir() 405 (rust_bootstrap / "rust-bootstrap-1.70.0-r2.ebuild").touch() 406 407 self.assertFalse( 408 auto_update_rust_bootstrap.maybe_delete_old_rust_bootstrap_ebuilds( 409 tempdir, rust_bootstrap, dry_run=True 410 ) 411 ) 412 413 def test_version_deletion_ignores_newer_than_needed_versions(self): 414 tempdir = self.make_tempdir() 415 rust = tempdir / "rust" 416 rust.mkdir() 417 (rust / "rust-1.71.0-r1.ebuild").touch() 418 rust_bootstrap = tempdir / "rust-bootstrap" 419 rust_bootstrap.mkdir() 420 (rust_bootstrap / "rust-bootstrap-1.70.0-r2.ebuild").touch() 421 (rust_bootstrap / "rust-bootstrap-1.71.0-r1.ebuild").touch() 422 (rust_bootstrap / "rust-bootstrap-1.72.0.ebuild").touch() 423 424 self.assertFalse( 425 auto_update_rust_bootstrap.maybe_delete_old_rust_bootstrap_ebuilds( 426 tempdir, rust_bootstrap, dry_run=True 427 ) 428 ) 429 430 @mock.patch.object(auto_update_rust_bootstrap, "update_ebuild_manifest") 431 def test_version_deletion_deletes_old_files(self, update_ebuild_manifest): 432 tempdir = self.make_tempdir() 433 rust = tempdir / "rust" 434 rust.mkdir() 435 (rust / "rust-1.71.0-r1.ebuild").touch() 436 rust_bootstrap = tempdir / "rust-bootstrap" 437 rust_bootstrap.mkdir() 438 needed_rust_bootstrap = ( 439 rust_bootstrap / "rust-bootstrap-1.70.0-r2.ebuild" 440 ) 441 needed_rust_bootstrap.touch() 442 443 # There are quite a few of these, so corner-cases are tested. 444 445 # Symlink to outside of the group of files to delete. 446 bootstrap_1_68_symlink = rust_bootstrap / "rust-bootstrap-1.68.0.ebuild" 447 bootstrap_1_68_symlink.symlink_to(needed_rust_bootstrap.name) 448 # Ensure that absolute symlinks are caught. 449 bootstrap_1_68_symlink_abs = ( 450 rust_bootstrap / "rust-bootstrap-1.68.0-r1.ebuild" 451 ) 452 bootstrap_1_68_symlink_abs.symlink_to(needed_rust_bootstrap) 453 # Regular files should be no issue. 454 bootstrap_1_69_regular = rust_bootstrap / "rust-bootstrap-1.69.0.ebuild" 455 bootstrap_1_69_regular.touch() 456 # Symlinks linking back into the set of files to delete should also be 457 # no issue. 458 bootstrap_1_69_symlink = ( 459 rust_bootstrap / "rust-bootstrap-1.69.0-r2.ebuild" 460 ) 461 bootstrap_1_69_symlink.symlink_to(bootstrap_1_69_regular.name) 462 463 self.assertTrue( 464 auto_update_rust_bootstrap.maybe_delete_old_rust_bootstrap_ebuilds( 465 tempdir, 466 rust_bootstrap, 467 dry_run=False, 468 commit=False, 469 ) 470 ) 471 update_ebuild_manifest.assert_called_once() 472 473 self.assertFalse(bootstrap_1_68_symlink.exists()) 474 self.assertFalse(bootstrap_1_68_symlink_abs.exists()) 475 self.assertFalse(bootstrap_1_69_regular.exists()) 476 self.assertFalse(bootstrap_1_69_symlink.exists()) 477 self.assertTrue(needed_rust_bootstrap.exists()) 478 479 def test_version_deletion_raises_when_old_file_has_dep(self): 480 tempdir = self.make_tempdir() 481 rust = tempdir / "rust" 482 rust.mkdir() 483 (rust / "rust-1.71.0-r1.ebuild").touch() 484 rust_bootstrap = tempdir / "rust-bootstrap" 485 rust_bootstrap.mkdir() 486 old_rust_bootstrap = rust_bootstrap / "rust-bootstrap-1.69.0-r1.ebuild" 487 old_rust_bootstrap.touch() 488 (rust_bootstrap / "rust-bootstrap-1.70.0-r2.ebuild").symlink_to( 489 old_rust_bootstrap.name 490 ) 491 492 with self.assertRaises( 493 auto_update_rust_bootstrap.OldEbuildIsLinkedToError 494 ): 495 auto_update_rust_bootstrap.maybe_delete_old_rust_bootstrap_ebuilds( 496 tempdir, rust_bootstrap, dry_run=True 497 ) 498 499 500if __name__ == "__main__": 501 unittest.main() 502