|
1 | 1 | package install |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "os" |
| 5 | + "path/filepath" |
4 | 6 | "strings" |
5 | 7 | "testing" |
6 | 8 | ) |
@@ -500,3 +502,247 @@ func TestParityInstallRequest(t *testing.T) { |
500 | 502 | } |
501 | 503 | }) |
502 | 504 | } |
| 505 | + |
| 506 | +// --------------------------------------------------------------------------- |
| 507 | +// TestParityCachePin -- mirrors cache_pin.py |
| 508 | +// --------------------------------------------------------------------------- |
| 509 | + |
| 510 | +// TestParityCachePinConstants verifies the marker filename and schema version |
| 511 | +// match the Python constants. |
| 512 | +func TestParityCachePinConstants(t *testing.T) { |
| 513 | + if MarkerFilename != ".apm-pin" { |
| 514 | + t.Fatalf("MarkerFilename: want .apm-pin, got %q", MarkerFilename) |
| 515 | + } |
| 516 | + if CachePinSchemaVersion != 1 { |
| 517 | + t.Fatalf("CachePinSchemaVersion: want 1, got %d", CachePinSchemaVersion) |
| 518 | + } |
| 519 | +} |
| 520 | + |
| 521 | +// TestParityCachePinWriteAndVerify exercises the round-trip write -> verify. |
| 522 | +func TestParityCachePinWriteAndVerify(t *testing.T) { |
| 523 | + dir := t.TempDir() |
| 524 | + WriteMarker(dir, "abc123") |
| 525 | + |
| 526 | + marker := filepath.Join(dir, MarkerFilename) |
| 527 | + if _, err := os.Stat(marker); err != nil { |
| 528 | + t.Fatalf("marker file not created: %v", err) |
| 529 | + } |
| 530 | + |
| 531 | + if err := VerifyMarker(dir, "abc123"); err != nil { |
| 532 | + t.Fatalf("unexpected error: %v", err) |
| 533 | + } |
| 534 | +} |
| 535 | + |
| 536 | +// TestParityCachePinWriteNoopOnMissingDir verifies WriteMarker is silent when |
| 537 | +// the path does not exist -- mirroring the Python try/except OSError. |
| 538 | +func TestParityCachePinWriteNoopOnMissingDir(t *testing.T) { |
| 539 | + WriteMarker("/nonexistent/path/that/does/not/exist", "abc123") |
| 540 | + // no panic, no error |
| 541 | +} |
| 542 | + |
| 543 | +// TestParityCachePinWriteNoopOnFile verifies WriteMarker is a no-op when the |
| 544 | +// path is a file, not a directory. |
| 545 | +func TestParityCachePinWriteNoopOnFile(t *testing.T) { |
| 546 | + f, err := os.CreateTemp(t.TempDir(), "not-a-dir") |
| 547 | + if err != nil { |
| 548 | + t.Fatal(err) |
| 549 | + } |
| 550 | + f.Close() |
| 551 | + WriteMarker(f.Name(), "abc123") |
| 552 | + // no panic |
| 553 | +} |
| 554 | + |
| 555 | +// TestParityCachePinMissingMarker verifies VerifyMarker returns CachePinError |
| 556 | +// when the marker file is absent. |
| 557 | +func TestParityCachePinMissingMarker(t *testing.T) { |
| 558 | + dir := t.TempDir() |
| 559 | + err := VerifyMarker(dir, "abc123") |
| 560 | + if err == nil { |
| 561 | + t.Fatal("expected error for missing marker") |
| 562 | + } |
| 563 | + ce, ok := err.(*CachePinError) |
| 564 | + if !ok { |
| 565 | + t.Fatalf("expected *CachePinError, got %T", err) |
| 566 | + } |
| 567 | + if !strings.Contains(ce.Error(), "cache pin marker missing") { |
| 568 | + t.Fatalf("unexpected message: %q", ce.Error()) |
| 569 | + } |
| 570 | + if !strings.Contains(ce.Error(), "supply-chain hardening") { |
| 571 | + t.Fatalf("missing supply-chain phrase: %q", ce.Error()) |
| 572 | + } |
| 573 | +} |
| 574 | + |
| 575 | +// TestParityCachePinMalformedJSON verifies VerifyMarker returns CachePinError |
| 576 | +// for invalid JSON. |
| 577 | +func TestParityCachePinMalformedJSON(t *testing.T) { |
| 578 | + dir := t.TempDir() |
| 579 | + marker := filepath.Join(dir, MarkerFilename) |
| 580 | + if err := os.WriteFile(marker, []byte("not json at all"), 0o644); err != nil { |
| 581 | + t.Fatal(err) |
| 582 | + } |
| 583 | + err := VerifyMarker(dir, "abc123") |
| 584 | + if err == nil { |
| 585 | + t.Fatal("expected error for malformed JSON") |
| 586 | + } |
| 587 | + ce, ok := err.(*CachePinError) |
| 588 | + if !ok { |
| 589 | + t.Fatalf("expected *CachePinError, got %T", err) |
| 590 | + } |
| 591 | + if !strings.Contains(ce.Error(), "not valid JSON") { |
| 592 | + t.Fatalf("unexpected message: %q", ce.Error()) |
| 593 | + } |
| 594 | +} |
| 595 | + |
| 596 | +// TestParityCachePinWrongSchema verifies VerifyMarker returns CachePinError |
| 597 | +// for an unsupported schema_version -- mirrors the Python check. |
| 598 | +func TestParityCachePinWrongSchema(t *testing.T) { |
| 599 | + dir := t.TempDir() |
| 600 | + marker := filepath.Join(dir, MarkerFilename) |
| 601 | + payload := `{"schema_version": 99, "resolved_commit": "abc"}` |
| 602 | + if err := os.WriteFile(marker, []byte(payload), 0o644); err != nil { |
| 603 | + t.Fatal(err) |
| 604 | + } |
| 605 | + err := VerifyMarker(dir, "abc") |
| 606 | + if err == nil { |
| 607 | + t.Fatal("expected error for wrong schema") |
| 608 | + } |
| 609 | + ce, ok := err.(*CachePinError) |
| 610 | + if !ok { |
| 611 | + t.Fatalf("expected *CachePinError, got %T", err) |
| 612 | + } |
| 613 | + if !strings.Contains(ce.Error(), "unsupported schema_version") { |
| 614 | + t.Fatalf("unexpected message: %q", ce.Error()) |
| 615 | + } |
| 616 | + if !strings.Contains(ce.Error(), "99") { |
| 617 | + t.Fatalf("schema version 99 not mentioned: %q", ce.Error()) |
| 618 | + } |
| 619 | +} |
| 620 | + |
| 621 | +// TestParityCachePinMissingCommitField verifies VerifyMarker returns |
| 622 | +// CachePinError when the marker has no resolved_commit field. |
| 623 | +func TestParityCachePinMissingCommitField(t *testing.T) { |
| 624 | + dir := t.TempDir() |
| 625 | + marker := filepath.Join(dir, MarkerFilename) |
| 626 | + payload := `{"schema_version": 1}` |
| 627 | + if err := os.WriteFile(marker, []byte(payload), 0o644); err != nil { |
| 628 | + t.Fatal(err) |
| 629 | + } |
| 630 | + err := VerifyMarker(dir, "abc123") |
| 631 | + if err == nil { |
| 632 | + t.Fatal("expected error for missing resolved_commit") |
| 633 | + } |
| 634 | + ce, ok := err.(*CachePinError) |
| 635 | + if !ok { |
| 636 | + t.Fatalf("expected *CachePinError, got %T", err) |
| 637 | + } |
| 638 | + if !strings.Contains(ce.Error(), "missing resolved_commit") { |
| 639 | + t.Fatalf("unexpected message: %q", ce.Error()) |
| 640 | + } |
| 641 | +} |
| 642 | + |
| 643 | +// TestParityCachePinMismatch verifies VerifyMarker returns CachePinError when |
| 644 | +// the marker commit differs from the expected commit. |
| 645 | +func TestParityCachePinMismatch(t *testing.T) { |
| 646 | + dir := t.TempDir() |
| 647 | + WriteMarker(dir, "aaa111") |
| 648 | + err := VerifyMarker(dir, "bbb222") |
| 649 | + if err == nil { |
| 650 | + t.Fatal("expected error for commit mismatch") |
| 651 | + } |
| 652 | + ce, ok := err.(*CachePinError) |
| 653 | + if !ok { |
| 654 | + t.Fatalf("expected *CachePinError, got %T", err) |
| 655 | + } |
| 656 | + if !strings.Contains(ce.Error(), "cache pin mismatch") { |
| 657 | + t.Fatalf("unexpected message: %q", ce.Error()) |
| 658 | + } |
| 659 | + if !strings.Contains(ce.Error(), "aaa111") { |
| 660 | + t.Fatalf("marker commit not in error: %q", ce.Error()) |
| 661 | + } |
| 662 | + if !strings.Contains(ce.Error(), "bbb222") { |
| 663 | + t.Fatalf("expected commit not in error: %q", ce.Error()) |
| 664 | + } |
| 665 | +} |
| 666 | + |
| 667 | +// TestParityCachePinIdempotent verifies WriteMarker overwrites prior markers. |
| 668 | +func TestParityCachePinIdempotent(t *testing.T) { |
| 669 | + dir := t.TempDir() |
| 670 | + WriteMarker(dir, "first") |
| 671 | + WriteMarker(dir, "second") |
| 672 | + |
| 673 | + if err := VerifyMarker(dir, "second"); err != nil { |
| 674 | + t.Fatalf("expected second marker: %v", err) |
| 675 | + } |
| 676 | + if err := VerifyMarker(dir, "first"); err == nil { |
| 677 | + t.Fatal("expected mismatch after overwrite") |
| 678 | + } |
| 679 | +} |
| 680 | + |
| 681 | +// --------------------------------------------------------------------------- |
| 682 | +// TestParitySources -- mirrors sources.py |
| 683 | +// --------------------------------------------------------------------------- |
| 684 | + |
| 685 | +// TestParityMaterializationDefaults verifies NewMaterialization sets default |
| 686 | +// Deltas with installed:1. |
| 687 | +func TestParityMaterializationDefaults(t *testing.T) { |
| 688 | + m := NewMaterialization(nil, "/tmp/some-pkg", "owner/repo") |
| 689 | + if m.InstallPath != "/tmp/some-pkg" { |
| 690 | + t.Fatalf("InstallPath: want /tmp/some-pkg, got %q", m.InstallPath) |
| 691 | + } |
| 692 | + if m.DepKey != "owner/repo" { |
| 693 | + t.Fatalf("DepKey: want owner/repo, got %q", m.DepKey) |
| 694 | + } |
| 695 | + if m.Deltas == nil { |
| 696 | + t.Fatal("Deltas should not be nil") |
| 697 | + } |
| 698 | + if m.Deltas["installed"] != 1 { |
| 699 | + t.Fatalf("Deltas[installed]: want 1, got %d", m.Deltas["installed"]) |
| 700 | + } |
| 701 | +} |
| 702 | + |
| 703 | +// TestParityMaterializationNilPackageInfo verifies Materialization can hold a |
| 704 | +// nil PackageInfo (skip-integration signal). |
| 705 | +func TestParityMaterializationNilPackageInfo(t *testing.T) { |
| 706 | + m := NewMaterialization(nil, "/tmp/x", "k") |
| 707 | + if m.PackageInfo != nil { |
| 708 | + t.Fatal("PackageInfo should be nil") |
| 709 | + } |
| 710 | +} |
| 711 | + |
| 712 | +// TestParityMaterializationUnpinnedDelta verifies that callers can add an |
| 713 | +// "unpinned" delta alongside "installed" -- matching the Python pattern. |
| 714 | +func TestParityMaterializationUnpinnedDelta(t *testing.T) { |
| 715 | + m := NewMaterialization(nil, "/tmp/x", "k") |
| 716 | + m.Deltas["unpinned"] = 1 |
| 717 | + if m.Deltas["unpinned"] != 1 { |
| 718 | + t.Fatalf("Deltas[unpinned]: want 1, got %d", m.Deltas["unpinned"]) |
| 719 | + } |
| 720 | +} |
| 721 | + |
| 722 | +// TestParitySourceConstants verifies the integrate-error-prefix constants |
| 723 | +// match the Python class attributes. |
| 724 | +func TestParitySourceConstants(t *testing.T) { |
| 725 | + if IntegrateErrorPrefix != "Failed to integrate primitives" { |
| 726 | + t.Fatalf("IntegrateErrorPrefix: got %q", IntegrateErrorPrefix) |
| 727 | + } |
| 728 | + if IntegrateErrorPrefixLocal != "Failed to integrate primitives from local package" { |
| 729 | + t.Fatalf("IntegrateErrorPrefixLocal: got %q", IntegrateErrorPrefixLocal) |
| 730 | + } |
| 731 | + if IntegrateErrorPrefixCached != "Failed to integrate primitives from cached package" { |
| 732 | + t.Fatalf("IntegrateErrorPrefixCached: got %q", IntegrateErrorPrefixCached) |
| 733 | + } |
| 734 | +} |
| 735 | + |
| 736 | +// TestParitySourceKindValues verifies the SourceKind constants are distinct |
| 737 | +// and match the expected ordering. |
| 738 | +func TestParitySourceKindValues(t *testing.T) { |
| 739 | + if SourceKindLocal == SourceKindCached { |
| 740 | + t.Fatal("Local and Cached should differ") |
| 741 | + } |
| 742 | + if SourceKindLocal == SourceKindFresh { |
| 743 | + t.Fatal("Local and Fresh should differ") |
| 744 | + } |
| 745 | + if SourceKindCached == SourceKindFresh { |
| 746 | + t.Fatal("Cached and Fresh should differ") |
| 747 | + } |
| 748 | +} |
0 commit comments