|
5 | 5 |
|
6 | 6 | import sys |
7 | 7 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'lib', 'atomic')) |
8 | | -from fstab import FstabEntry, update_fstab |
| 8 | +from fstab import FstabEntry, update_fstab, update_fstab_home |
9 | 9 |
|
10 | 10 |
|
11 | 11 | class TestFstabEntryParse: |
@@ -137,13 +137,14 @@ def test_basic_replacement(self, tmp_path): |
137 | 137 | assert "root-new" in text |
138 | 138 | assert "subvol=home" in text |
139 | 139 |
|
140 | | - def test_backup_created(self, tmp_path): |
| 140 | + def test_backup_cleaned_after_success(self, tmp_path): |
| 141 | + """Backup is created during write but removed on success.""" |
141 | 142 | path = tmp_path / "fstab" |
142 | 143 | path.write_text("UUID=x / btrfs rw,subvol=root-old 0 0\n") |
143 | 144 | update_fstab(str(path), "root-old", "root-new") |
144 | 145 | bak = tmp_path / "fstab.bak" |
145 | | - assert bak.exists() |
146 | | - assert "root-old" in bak.read_text() |
| 146 | + assert not bak.exists() |
| 147 | + assert "root-new" in path.read_text() |
147 | 148 |
|
148 | 149 | def test_no_root_entry(self, tmp_path): |
149 | 150 | path = tmp_path / "fstab" |
@@ -267,3 +268,157 @@ def test_subvol_mismatch_without_subvolid_generic_error(self, tmp_path, capsys): |
267 | 268 | assert "subvol=root-old not found" in err |
268 | 269 | # Must NOT mention subvolid since there is none |
269 | 270 | assert "subvolid=" not in err |
| 271 | + |
| 272 | + |
| 273 | +class TestSetSubvol: |
| 274 | + """Tests for FstabEntry.set_subvol() — unconditional subvol replacement.""" |
| 275 | + |
| 276 | + def test_basic_replacement(self): |
| 277 | + e = FstabEntry.parse("UUID=x /home btrfs rw,subvol=/home 0 0\n") |
| 278 | + assert e.set_subvol("home-kde") |
| 279 | + assert "subvol=/home-kde" in e.options |
| 280 | + |
| 281 | + def test_preserves_leading_slash(self): |
| 282 | + e = FstabEntry.parse("UUID=x /home btrfs rw,subvol=/home 0 0\n") |
| 283 | + e.set_subvol("home-kde") |
| 284 | + assert "subvol=/home-kde" in e.options |
| 285 | + |
| 286 | + def test_no_leading_slash_preserved(self): |
| 287 | + e = FstabEntry.parse("UUID=x /home btrfs rw,subvol=home 0 0\n") |
| 288 | + e.set_subvol("home-kde") |
| 289 | + assert "subvol=home-kde" in e.options |
| 290 | + assert "/home-kde" not in e.options |
| 291 | + |
| 292 | + def test_already_set_no_change(self): |
| 293 | + e = FstabEntry.parse("UUID=x /home btrfs rw,subvol=/home-kde 0 0\n") |
| 294 | + assert not e.set_subvol("home-kde") |
| 295 | + |
| 296 | + def test_preserves_other_options(self): |
| 297 | + e = FstabEntry.parse( |
| 298 | + "UUID=x /home btrfs rw,noatime,subvol=/home,compress=zstd 0 0\n" |
| 299 | + ) |
| 300 | + e.set_subvol("home-dev") |
| 301 | + opts = e.options.split(",") |
| 302 | + assert "rw" in opts |
| 303 | + assert "noatime" in opts |
| 304 | + assert "compress=zstd" in opts |
| 305 | + assert "subvol=/home-dev" in opts |
| 306 | + |
| 307 | + def test_comment_line(self): |
| 308 | + e = FstabEntry.parse("# subvol=home\n") |
| 309 | + assert not e.set_subvol("home-new") |
| 310 | + |
| 311 | + def test_input_slash_stripped(self): |
| 312 | + """Input with leading slash is normalized.""" |
| 313 | + e = FstabEntry.parse("UUID=x /home btrfs rw,subvol=/home 0 0\n") |
| 314 | + e.set_subvol("/home-kde") |
| 315 | + assert "subvol=/home-kde" in e.options |
| 316 | + |
| 317 | + def test_no_subvol_option(self): |
| 318 | + """Entry without subvol= → no change.""" |
| 319 | + e = FstabEntry.parse("UUID=x /home btrfs rw,noatime 0 0\n") |
| 320 | + assert not e.set_subvol("home-kde") |
| 321 | + |
| 322 | + |
| 323 | +class TestUpdateFstabHome: |
| 324 | + """Tests for update_fstab_home() function.""" |
| 325 | + |
| 326 | + def test_basic_replacement(self, tmp_path): |
| 327 | + path = tmp_path / "fstab" |
| 328 | + path.write_text( |
| 329 | + "UUID=x / btrfs rw,subvol=/root-old 0 0\n" |
| 330 | + "UUID=x /home btrfs rw,subvol=/home 0 0\n" |
| 331 | + ) |
| 332 | + assert update_fstab_home(str(path), "home-kde") |
| 333 | + text = path.read_text() |
| 334 | + assert "subvol=/home-kde" in text |
| 335 | + # Root entry untouched |
| 336 | + assert "subvol=/root-old" in text |
| 337 | + |
| 338 | + def test_no_home_entry(self, tmp_path): |
| 339 | + path = tmp_path / "fstab" |
| 340 | + path.write_text("UUID=x / btrfs rw,subvol=/root-old 0 0\n") |
| 341 | + assert not update_fstab_home(str(path), "home-kde") |
| 342 | + |
| 343 | + def test_no_subvol_in_home(self, tmp_path): |
| 344 | + path = tmp_path / "fstab" |
| 345 | + path.write_text( |
| 346 | + "UUID=x / btrfs rw,subvol=/root-old 0 0\n" |
| 347 | + "UUID=y /home ext4 rw,noatime 0 0\n" |
| 348 | + ) |
| 349 | + assert not update_fstab_home(str(path), "home-kde") |
| 350 | + |
| 351 | + def test_already_set(self, tmp_path): |
| 352 | + path = tmp_path / "fstab" |
| 353 | + path.write_text( |
| 354 | + "UUID=x /home btrfs rw,subvol=/home-kde 0 0\n" |
| 355 | + ) |
| 356 | + assert update_fstab_home(str(path), "home-kde") |
| 357 | + |
| 358 | + def test_nonexistent_file(self): |
| 359 | + assert not update_fstab_home("/nonexistent/fstab", "home-kde") |
| 360 | + |
| 361 | + def test_empty_subvol_name(self, tmp_path): |
| 362 | + path = tmp_path / "fstab" |
| 363 | + path.write_text("UUID=x /home btrfs rw,subvol=/home 0 0\n") |
| 364 | + assert not update_fstab_home(str(path), "") |
| 365 | + |
| 366 | + def test_invalid_subvol_name(self, tmp_path): |
| 367 | + path = tmp_path / "fstab" |
| 368 | + path.write_text("UUID=x /home btrfs rw,subvol=/home 0 0\n") |
| 369 | + assert not update_fstab_home(str(path), "../etc/passwd") |
| 370 | + |
| 371 | + def test_permissions_preserved(self, tmp_path): |
| 372 | + path = tmp_path / "fstab" |
| 373 | + path.write_text("UUID=x /home btrfs rw,subvol=/home 0 0\n") |
| 374 | + path.chmod(0o644) |
| 375 | + update_fstab_home(str(path), "home-kde") |
| 376 | + assert path.stat().st_mode & 0o7777 == 0o644 |
| 377 | + |
| 378 | + def test_backup_cleaned_up_on_success(self, tmp_path): |
| 379 | + path = tmp_path / "fstab" |
| 380 | + path.write_text("UUID=x /home btrfs rw,subvol=/home 0 0\n") |
| 381 | + update_fstab_home(str(path), "home-kde") |
| 382 | + assert not (tmp_path / "fstab.home-bak").exists() |
| 383 | + |
| 384 | + def test_root_untouched(self, tmp_path): |
| 385 | + path = tmp_path / "fstab" |
| 386 | + path.write_text( |
| 387 | + "UUID=x / btrfs rw,subvol=/root-20250601 0 0\n" |
| 388 | + "UUID=x /home btrfs rw,subvol=/home 0 0\n" |
| 389 | + "UUID=x /var/log btrfs rw,subvol=/log 0 0\n" |
| 390 | + ) |
| 391 | + update_fstab_home(str(path), "home-dev") |
| 392 | + text = path.read_text() |
| 393 | + assert "subvol=/root-20250601" in text |
| 394 | + assert "subvol=/home-dev" in text |
| 395 | + assert "subvol=/log" in text |
| 396 | + |
| 397 | + def test_preserves_comments(self, tmp_path): |
| 398 | + path = tmp_path / "fstab" |
| 399 | + path.write_text( |
| 400 | + "# home filesystem\n" |
| 401 | + "UUID=x /home btrfs rw,subvol=/home 0 0\n" |
| 402 | + ) |
| 403 | + update_fstab_home(str(path), "home-kde") |
| 404 | + text = path.read_text() |
| 405 | + assert "# home filesystem" in text |
| 406 | + assert "subvol=/home-kde" in text |
| 407 | + |
| 408 | + def test_real_fstab(self, tmp_path): |
| 409 | + path = tmp_path / "fstab" |
| 410 | + path.write_text( |
| 411 | + "# /etc/fstab\n" |
| 412 | + "\n" |
| 413 | + "UUID=ABCD-1234 /efi vfat rw,relatime 0 2\n" |
| 414 | + "UUID=aaaa-bbbb / btrfs rw,noatime,compress=zstd:3,subvol=/root-20250220-141710 0 0\n" |
| 415 | + "UUID=aaaa-bbbb /home btrfs rw,noatime,compress=zstd:3,subvol=/home 0 0\n" |
| 416 | + "UUID=aaaa-bbbb /var/log btrfs rw,noatime,compress=zstd:3,subvol=/log 0 0\n" |
| 417 | + "tmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0\n" |
| 418 | + ) |
| 419 | + assert update_fstab_home(str(path), "home-kde") |
| 420 | + text = path.read_text() |
| 421 | + assert "subvol=/home-kde" in text |
| 422 | + assert "subvol=/root-20250220-141710" in text |
| 423 | + assert "subvol=/log" in text |
| 424 | + assert "ABCD-1234" in text |
0 commit comments