Add structured destination comparison details
This commit is contained in:
@@ -194,7 +194,15 @@ func compareDestination(req Request, status state.DestinationStatus) state.Compa
|
|||||||
return state.Comparison{Outcome: state.OutcomeDestinationOlder, Reason: "fixed destination source is older than selected source"}
|
return state.Comparison{Outcome: state.OutcomeDestinationOlder, Reason: "fixed destination source is older than selected source"}
|
||||||
}
|
}
|
||||||
if destinationManifest.Created.After(req.SourceBundle.Manifest.Created) {
|
if destinationManifest.Created.After(req.SourceBundle.Manifest.Created) {
|
||||||
return state.Comparison{Outcome: state.OutcomeDestinationNewer, Reason: "fixed destination source is newer than selected source"}
|
return state.Comparison{
|
||||||
|
Outcome: state.OutcomeDestinationNewer,
|
||||||
|
Reason: "fixed destination source is newer than selected source",
|
||||||
|
Detail: state.ComparisonDetail{
|
||||||
|
Kind: state.ComparisonDetailDestinationNewer,
|
||||||
|
CurrentSourceID: req.SourceBundle.Manifest.ID,
|
||||||
|
DestinationSourceID: destinationManifest.ID,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return comparison
|
return comparison
|
||||||
}
|
}
|
||||||
@@ -207,7 +215,15 @@ func compareDestination(req Request, status state.DestinationStatus) state.Compa
|
|||||||
return state.Comparison{Outcome: state.OutcomeDestinationOlder, Reason: "fixed destination source is older than selected source"}
|
return state.Comparison{Outcome: state.OutcomeDestinationOlder, Reason: "fixed destination source is older than selected source"}
|
||||||
}
|
}
|
||||||
if destinationManifest.Created.After(req.SourceBundle.Manifest.Created) {
|
if destinationManifest.Created.After(req.SourceBundle.Manifest.Created) {
|
||||||
return state.Comparison{Outcome: state.OutcomeDestinationNewer, Reason: "fixed destination source is newer than selected source"}
|
return state.Comparison{
|
||||||
|
Outcome: state.OutcomeDestinationNewer,
|
||||||
|
Reason: "fixed destination source is newer than selected source",
|
||||||
|
Detail: state.ComparisonDetail{
|
||||||
|
Kind: state.ComparisonDetailDestinationNewer,
|
||||||
|
CurrentSourceID: req.SourceBundle.Manifest.ID,
|
||||||
|
DestinationSourceID: destinationManifest.ID,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return comparison
|
return comparison
|
||||||
}
|
}
|
||||||
|
|||||||
41
internal/publish/plan_test.go
Normal file
41
internal/publish/plan_test.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package publish
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/distributor/internal/config"
|
||||||
|
"gitea.maximumdirect.net/eric/distributor/internal/state"
|
||||||
|
"gitea.maximumdirect.net/eric/distributor/internal/storage/fake"
|
||||||
|
"gitea.maximumdirect.net/eric/distributor/internal/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCompareDestinationFixedPathReportsDestinationNewerDetail(t *testing.T) {
|
||||||
|
sourceBackend := fake.New()
|
||||||
|
sourceBundle := testutil.WriteFakeSourceBundle(t, sourceBackend, "bundle", testutil.BundleOptions{})
|
||||||
|
destinationState := testutil.DestinationState(sourceBundle.Manifest, testutil.DestinationStateOptions{})
|
||||||
|
destinationState.Source.Manifest.ID = "latest.previous"
|
||||||
|
destinationState.Source.Manifest.Created = sourceBundle.Manifest.Created.Add(time.Hour)
|
||||||
|
|
||||||
|
comparison := compareDestination(Request{
|
||||||
|
PipelineID: "reports",
|
||||||
|
DestinationID: "archive",
|
||||||
|
SourceBundle: sourceBundle,
|
||||||
|
DestinationBundlePath: "",
|
||||||
|
PathMapping: config.PathMappingFixed,
|
||||||
|
State: config.StatePolicy{Mode: config.StateModeSingleOwner},
|
||||||
|
}, state.DestinationStatus{State: &destinationState, HasContents: true})
|
||||||
|
|
||||||
|
if comparison.Outcome != state.OutcomeDestinationNewer {
|
||||||
|
t.Fatalf("comparison outcome = %s, want %s", comparison.Outcome, state.OutcomeDestinationNewer)
|
||||||
|
}
|
||||||
|
if comparison.Reason != "fixed destination source is newer than selected source" {
|
||||||
|
t.Fatalf("comparison reason = %q, want fixed-path newer reason", comparison.Reason)
|
||||||
|
}
|
||||||
|
if comparison.Detail.Kind != state.ComparisonDetailDestinationNewer {
|
||||||
|
t.Fatalf("detail kind = %q, want %q", comparison.Detail.Kind, state.ComparisonDetailDestinationNewer)
|
||||||
|
}
|
||||||
|
if comparison.Detail.CurrentSourceID != sourceBundle.Manifest.ID || comparison.Detail.DestinationSourceID != "latest.previous" {
|
||||||
|
t.Fatalf("detail = %#v, want source ids", comparison.Detail)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,37 +30,75 @@ type DestinationStatus struct {
|
|||||||
type Comparison struct {
|
type Comparison struct {
|
||||||
Outcome Outcome
|
Outcome Outcome
|
||||||
Reason string
|
Reason string
|
||||||
|
Detail ComparisonDetail
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComparisonDetailKind string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ComparisonDetailNone ComparisonDetailKind = ""
|
||||||
|
ComparisonDetailInvalidState ComparisonDetailKind = "invalid_state"
|
||||||
|
ComparisonDetailUnmanagedContent ComparisonDetailKind = "unmanaged_content"
|
||||||
|
ComparisonDetailPipelineIDMismatch ComparisonDetailKind = "pipeline_id_mismatch"
|
||||||
|
ComparisonDetailDestinationIDMismatch ComparisonDetailKind = "destination_id_mismatch"
|
||||||
|
ComparisonDetailDifferentSourceID ComparisonDetailKind = "different_source_id"
|
||||||
|
ComparisonDetailSameCreatedDigestConflict ComparisonDetailKind = "same_created_digest_conflict"
|
||||||
|
ComparisonDetailDestinationNewer ComparisonDetailKind = "destination_newer"
|
||||||
|
ComparisonDetailSharedRootOwnerAbsent ComparisonDetailKind = "shared_root_owner_absent"
|
||||||
|
ComparisonDetailSharedRootOutputOwner ComparisonDetailKind = "shared_root_output_owner_conflict"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ComparisonDetail struct {
|
||||||
|
Kind ComparisonDetailKind
|
||||||
|
CurrentPipelineID string
|
||||||
|
CurrentDestinationID string
|
||||||
|
DestinationPipelineID string
|
||||||
|
DestinationDestinationID string
|
||||||
|
CurrentSourceID string
|
||||||
|
DestinationSourceID string
|
||||||
|
CurrentSourceDigest string
|
||||||
|
DestinationSourceDigest string
|
||||||
|
Path string
|
||||||
|
CurrentOwner OwnerScope
|
||||||
|
ConflictingOwner OwnerScope
|
||||||
}
|
}
|
||||||
|
|
||||||
func CompareSharedRootOwner(source bundle.Manifest, scope OwnerScope, status DestinationStatus) Comparison {
|
func CompareSharedRootOwner(source bundle.Manifest, scope OwnerScope, status DestinationStatus) Comparison {
|
||||||
if status.StateErr != nil {
|
if status.StateErr != nil {
|
||||||
return Comparison{Outcome: OutcomeInvalidState, Reason: status.StateErr.Error()}
|
return Comparison{Outcome: OutcomeInvalidState, Reason: status.StateErr.Error(), Detail: ComparisonDetail{Kind: ComparisonDetailInvalidState}}
|
||||||
}
|
}
|
||||||
if status.SharedRoot != nil {
|
if status.SharedRoot != nil {
|
||||||
if err := ValidateSharedRoot(*status.SharedRoot); err != nil {
|
if err := ValidateSharedRoot(*status.SharedRoot); err != nil {
|
||||||
return Comparison{Outcome: OutcomeInvalidState, Reason: err.Error()}
|
return Comparison{Outcome: OutcomeInvalidState, Reason: err.Error(), Detail: ComparisonDetail{Kind: ComparisonDetailInvalidState}}
|
||||||
}
|
}
|
||||||
owner, ok := status.SharedRoot.Owner(scope)
|
owner, ok := status.SharedRoot.Owner(scope)
|
||||||
if !ok {
|
if !ok {
|
||||||
return Comparison{Outcome: OutcomeDestinationAbsent, Reason: fmt.Sprintf("destination owner %s/%s is absent", scope.PipelineID, scope.DestinationID)}
|
return Comparison{
|
||||||
|
Outcome: OutcomeDestinationAbsent,
|
||||||
|
Reason: fmt.Sprintf("destination owner %s/%s is absent", scope.PipelineID, scope.DestinationID),
|
||||||
|
Detail: ComparisonDetail{
|
||||||
|
Kind: ComparisonDetailSharedRootOwnerAbsent,
|
||||||
|
CurrentOwner: scope,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return compareManifests(source, owner.Source.Manifest)
|
return compareManifests(source, owner.Source.Manifest)
|
||||||
}
|
}
|
||||||
if status.State != nil {
|
if status.State != nil {
|
||||||
destinationState := *status.State
|
destinationState := *status.State
|
||||||
if err := Validate(destinationState); err != nil {
|
if err := Validate(destinationState); err != nil {
|
||||||
return Comparison{Outcome: OutcomeInvalidState, Reason: err.Error()}
|
return Comparison{Outcome: OutcomeInvalidState, Reason: err.Error(), Detail: ComparisonDetail{Kind: ComparisonDetailInvalidState}}
|
||||||
}
|
}
|
||||||
if destinationState.PipelineID != scope.PipelineID {
|
if destinationState.PipelineID != scope.PipelineID {
|
||||||
return Comparison{Outcome: OutcomeIdentityMismatch, Reason: fmt.Sprintf("pipeline id %q does not match %q", destinationState.PipelineID, scope.PipelineID)}
|
return pipelineIDMismatchComparison(destinationState.PipelineID, scope.PipelineID)
|
||||||
}
|
}
|
||||||
if destinationState.DestinationID != scope.DestinationID {
|
if destinationState.DestinationID != scope.DestinationID {
|
||||||
return Comparison{Outcome: OutcomeIdentityMismatch, Reason: fmt.Sprintf("destination id %q does not match %q", destinationState.DestinationID, scope.DestinationID)}
|
return destinationIDMismatchComparison(destinationState.DestinationID, scope.DestinationID)
|
||||||
}
|
}
|
||||||
return compareManifests(source, destinationState.Source.Manifest)
|
return compareManifests(source, destinationState.Source.Manifest)
|
||||||
}
|
}
|
||||||
if status.HasContents {
|
if status.HasContents {
|
||||||
return Comparison{Outcome: OutcomeDestinationUnmanaged, Reason: "destination has content but no distributor state"}
|
return Comparison{Outcome: OutcomeDestinationUnmanaged, Reason: "destination has content but no distributor state", Detail: ComparisonDetail{Kind: ComparisonDetailUnmanagedContent}}
|
||||||
}
|
}
|
||||||
return Comparison{Outcome: OutcomeDestinationAbsent, Reason: "destination state is absent"}
|
return Comparison{Outcome: OutcomeDestinationAbsent, Reason: "destination state is absent"}
|
||||||
}
|
}
|
||||||
@@ -70,45 +108,95 @@ func compareManifests(source, destination bundle.Manifest) Comparison {
|
|||||||
return Comparison{Outcome: OutcomeSameSource, Reason: "destination source manifest matches source"}
|
return Comparison{Outcome: OutcomeSameSource, Reason: "destination source manifest matches source"}
|
||||||
}
|
}
|
||||||
if destination.ID != source.ID {
|
if destination.ID != source.ID {
|
||||||
return Comparison{Outcome: OutcomeDifferentSourceConflict, Reason: "destination source id differs from source"}
|
return Comparison{
|
||||||
|
Outcome: OutcomeDifferentSourceConflict,
|
||||||
|
Reason: "destination source id differs from source",
|
||||||
|
Detail: ComparisonDetail{
|
||||||
|
Kind: ComparisonDetailDifferentSourceID,
|
||||||
|
CurrentSourceID: source.ID,
|
||||||
|
DestinationSourceID: destination.ID,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if destination.Created.Before(source.Created) {
|
if destination.Created.Before(source.Created) {
|
||||||
return Comparison{Outcome: OutcomeDestinationOlder, Reason: "destination source is older than source"}
|
return Comparison{Outcome: OutcomeDestinationOlder, Reason: "destination source is older than source"}
|
||||||
}
|
}
|
||||||
if destination.Created.After(source.Created) {
|
if destination.Created.After(source.Created) {
|
||||||
return Comparison{Outcome: OutcomeDestinationNewer, Reason: "destination source is newer than source"}
|
return Comparison{
|
||||||
|
Outcome: OutcomeDestinationNewer,
|
||||||
|
Reason: "destination source is newer than source",
|
||||||
|
Detail: ComparisonDetail{
|
||||||
|
Kind: ComparisonDetailDestinationNewer,
|
||||||
|
CurrentSourceID: source.ID,
|
||||||
|
DestinationSourceID: destination.ID,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if destination.Digest != source.Digest {
|
if destination.Digest != source.Digest {
|
||||||
return Comparison{Outcome: OutcomeSameCreatedConflict, Reason: "destination source has same id and created time but different digest"}
|
return Comparison{
|
||||||
|
Outcome: OutcomeSameCreatedConflict,
|
||||||
|
Reason: "destination source has same id and created time but different digest",
|
||||||
|
Detail: ComparisonDetail{
|
||||||
|
Kind: ComparisonDetailSameCreatedDigestConflict,
|
||||||
|
CurrentSourceID: source.ID,
|
||||||
|
DestinationSourceID: destination.ID,
|
||||||
|
CurrentSourceDigest: source.Digest,
|
||||||
|
DestinationSourceDigest: destination.Digest,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Comparison{Outcome: OutcomeInvalidState, Reason: "destination source differs from source without a supported comparison outcome"}
|
return Comparison{Outcome: OutcomeInvalidState, Reason: "destination source differs from source without a supported comparison outcome", Detail: ComparisonDetail{Kind: ComparisonDetailInvalidState}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Compare(source bundle.Manifest, pipelineID, destinationID string, status DestinationStatus) Comparison {
|
func Compare(source bundle.Manifest, pipelineID, destinationID string, status DestinationStatus) Comparison {
|
||||||
if status.StateErr != nil {
|
if status.StateErr != nil {
|
||||||
return Comparison{Outcome: OutcomeInvalidState, Reason: status.StateErr.Error()}
|
return Comparison{Outcome: OutcomeInvalidState, Reason: status.StateErr.Error(), Detail: ComparisonDetail{Kind: ComparisonDetailInvalidState}}
|
||||||
}
|
}
|
||||||
if status.State == nil {
|
if status.State == nil {
|
||||||
if status.HasContents {
|
if status.HasContents {
|
||||||
return Comparison{Outcome: OutcomeDestinationUnmanaged, Reason: "destination has content but no distributor state"}
|
return Comparison{Outcome: OutcomeDestinationUnmanaged, Reason: "destination has content but no distributor state", Detail: ComparisonDetail{Kind: ComparisonDetailUnmanagedContent}}
|
||||||
}
|
}
|
||||||
return Comparison{Outcome: OutcomeDestinationAbsent, Reason: "destination state is absent"}
|
return Comparison{Outcome: OutcomeDestinationAbsent, Reason: "destination state is absent"}
|
||||||
}
|
}
|
||||||
|
|
||||||
destinationState := *status.State
|
destinationState := *status.State
|
||||||
if err := Validate(destinationState); err != nil {
|
if err := Validate(destinationState); err != nil {
|
||||||
return Comparison{Outcome: OutcomeInvalidState, Reason: err.Error()}
|
return Comparison{Outcome: OutcomeInvalidState, Reason: err.Error(), Detail: ComparisonDetail{Kind: ComparisonDetailInvalidState}}
|
||||||
}
|
}
|
||||||
if destinationState.PipelineID != pipelineID {
|
if destinationState.PipelineID != pipelineID {
|
||||||
return Comparison{Outcome: OutcomeIdentityMismatch, Reason: fmt.Sprintf("pipeline id %q does not match %q", destinationState.PipelineID, pipelineID)}
|
return pipelineIDMismatchComparison(destinationState.PipelineID, pipelineID)
|
||||||
}
|
}
|
||||||
if destinationState.DestinationID != destinationID {
|
if destinationState.DestinationID != destinationID {
|
||||||
return Comparison{Outcome: OutcomeIdentityMismatch, Reason: fmt.Sprintf("destination id %q does not match %q", destinationState.DestinationID, destinationID)}
|
return destinationIDMismatchComparison(destinationState.DestinationID, destinationID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return compareManifests(source, destinationState.Source.Manifest)
|
return compareManifests(source, destinationState.Source.Manifest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pipelineIDMismatchComparison(destinationPipelineID, currentPipelineID string) Comparison {
|
||||||
|
return Comparison{
|
||||||
|
Outcome: OutcomeIdentityMismatch,
|
||||||
|
Reason: fmt.Sprintf("pipeline id %q does not match %q", destinationPipelineID, currentPipelineID),
|
||||||
|
Detail: ComparisonDetail{
|
||||||
|
Kind: ComparisonDetailPipelineIDMismatch,
|
||||||
|
CurrentPipelineID: currentPipelineID,
|
||||||
|
DestinationPipelineID: destinationPipelineID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func destinationIDMismatchComparison(destinationDestinationID, currentDestinationID string) Comparison {
|
||||||
|
return Comparison{
|
||||||
|
Outcome: OutcomeIdentityMismatch,
|
||||||
|
Reason: fmt.Sprintf("destination id %q does not match %q", destinationDestinationID, currentDestinationID),
|
||||||
|
Detail: ComparisonDetail{
|
||||||
|
Kind: ComparisonDetailDestinationIDMismatch,
|
||||||
|
CurrentDestinationID: currentDestinationID,
|
||||||
|
DestinationDestinationID: destinationDestinationID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func manifestsEqual(a, b bundle.Manifest) bool {
|
func manifestsEqual(a, b bundle.Manifest) bool {
|
||||||
if a.SchemaVersion != b.SchemaVersion ||
|
if a.SchemaVersion != b.SchemaVersion ||
|
||||||
a.ID != b.ID ||
|
a.ID != b.ID ||
|
||||||
|
|||||||
@@ -97,6 +97,93 @@ func TestCompareOutcomes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCompareReportsStructuredDetails(t *testing.T) {
|
||||||
|
source := validManifest(t)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
status DestinationStatus
|
||||||
|
wantKind ComparisonDetailKind
|
||||||
|
assertions func(t *testing.T, detail ComparisonDetail)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "pipeline mismatch",
|
||||||
|
status: DestinationStatus{State: withState(t, source, func(s *DistributorState) { s.PipelineID = "other" })},
|
||||||
|
wantKind: ComparisonDetailPipelineIDMismatch,
|
||||||
|
assertions: func(t *testing.T, detail ComparisonDetail) {
|
||||||
|
t.Helper()
|
||||||
|
if detail.DestinationPipelineID != "other" || detail.CurrentPipelineID != "reports" {
|
||||||
|
t.Fatalf("detail = %#v, want pipeline ids", detail)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "destination mismatch",
|
||||||
|
status: DestinationStatus{State: withState(t, source, func(s *DistributorState) { s.DestinationID = "other" })},
|
||||||
|
wantKind: ComparisonDetailDestinationIDMismatch,
|
||||||
|
assertions: func(t *testing.T, detail ComparisonDetail) {
|
||||||
|
t.Helper()
|
||||||
|
if detail.DestinationDestinationID != "other" || detail.CurrentDestinationID != "archive" {
|
||||||
|
t.Fatalf("detail = %#v, want destination ids", detail)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "different source id",
|
||||||
|
status: DestinationStatus{State: withState(t, source, func(s *DistributorState) { s.Source.Manifest.ID = "other.source" })},
|
||||||
|
wantKind: ComparisonDetailDifferentSourceID,
|
||||||
|
assertions: func(t *testing.T, detail ComparisonDetail) {
|
||||||
|
t.Helper()
|
||||||
|
if detail.DestinationSourceID != "other.source" || detail.CurrentSourceID != source.ID {
|
||||||
|
t.Fatalf("detail = %#v, want source ids", detail)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same created digest conflict",
|
||||||
|
status: DestinationStatus{State: withState(t, source, func(s *DistributorState) {
|
||||||
|
s.Source.Manifest.Files[0].SHA256 = "sha256:3333333333333333333333333333333333333333333333333333333333333333"
|
||||||
|
s.Source.Manifest.Digest = bundle.BundleDigest(s.Source.Manifest.Files)
|
||||||
|
})},
|
||||||
|
wantKind: ComparisonDetailSameCreatedDigestConflict,
|
||||||
|
assertions: func(t *testing.T, detail ComparisonDetail) {
|
||||||
|
t.Helper()
|
||||||
|
if detail.CurrentSourceDigest == "" || detail.DestinationSourceDigest == "" || detail.CurrentSourceDigest == detail.DestinationSourceDigest {
|
||||||
|
t.Fatalf("detail = %#v, want different source digests", detail)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "destination newer",
|
||||||
|
status: DestinationStatus{State: withState(t, source, func(s *DistributorState) {
|
||||||
|
s.Source.Manifest.Created = source.Created.Add(time.Hour)
|
||||||
|
})},
|
||||||
|
wantKind: ComparisonDetailDestinationNewer,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid state",
|
||||||
|
status: DestinationStatus{StateErr: errors.New("invalid json")},
|
||||||
|
wantKind: ComparisonDetailInvalidState,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unmanaged content",
|
||||||
|
status: DestinationStatus{HasContents: true},
|
||||||
|
wantKind: ComparisonDetailUnmanagedContent,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := Compare(source, "reports", "archive", tt.status)
|
||||||
|
if got.Detail.Kind != tt.wantKind {
|
||||||
|
t.Fatalf("Compare() detail kind = %q, want %q; comparison=%#v", got.Detail.Kind, tt.wantKind, got)
|
||||||
|
}
|
||||||
|
if tt.assertions != nil {
|
||||||
|
tt.assertions(t, got.Detail)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func withState(t *testing.T, source bundle.Manifest, mutate func(*DistributorState)) *DistributorState {
|
func withState(t *testing.T, source bundle.Manifest, mutate func(*DistributorState)) *DistributorState {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
stateManifest := source
|
stateManifest := source
|
||||||
|
|||||||
@@ -191,7 +191,17 @@ func (s SharedRootState) PathOwnershipConflict(scope OwnerScope, paths []string)
|
|||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
owner, exists := s.OutputOwner(path)
|
owner, exists := s.OutputOwner(path)
|
||||||
if exists && owner != scope {
|
if exists && owner != scope {
|
||||||
return PathOwnershipConflict{Path: path, Owner: owner}, true
|
return PathOwnershipConflict{
|
||||||
|
Path: path,
|
||||||
|
Owner: owner,
|
||||||
|
CurrentOwner: scope,
|
||||||
|
Detail: ComparisonDetail{
|
||||||
|
Kind: ComparisonDetailSharedRootOutputOwner,
|
||||||
|
Path: path,
|
||||||
|
CurrentOwner: scope,
|
||||||
|
ConflictingOwner: owner,
|
||||||
|
},
|
||||||
|
}, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return PathOwnershipConflict{}, false
|
return PathOwnershipConflict{}, false
|
||||||
|
|||||||
@@ -57,8 +57,10 @@ type SharedRootOutputFile struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PathOwnershipConflict struct {
|
type PathOwnershipConflict struct {
|
||||||
Path string
|
Path string
|
||||||
Owner OwnerScope
|
Owner OwnerScope
|
||||||
|
CurrentOwner OwnerScope
|
||||||
|
Detail ComparisonDetail
|
||||||
}
|
}
|
||||||
|
|
||||||
type rawSharedRootState struct {
|
type rawSharedRootState struct {
|
||||||
|
|||||||
@@ -130,6 +130,12 @@ func TestSharedRootOutputHelpers(t *testing.T) {
|
|||||||
if !ok || conflict.Owner != html {
|
if !ok || conflict.Owner != html {
|
||||||
t.Fatalf("conflict = %#v ok=%t, want html owner conflict", conflict, ok)
|
t.Fatalf("conflict = %#v ok=%t, want html owner conflict", conflict, ok)
|
||||||
}
|
}
|
||||||
|
if conflict.Detail.Kind != ComparisonDetailSharedRootOutputOwner {
|
||||||
|
t.Fatalf("conflict detail kind = %q, want %q", conflict.Detail.Kind, ComparisonDetailSharedRootOutputOwner)
|
||||||
|
}
|
||||||
|
if conflict.Detail.Path != "report.html" || conflict.Detail.CurrentOwner != archive || conflict.Detail.ConflictingOwner != html {
|
||||||
|
t.Fatalf("conflict detail = %#v, want path and owners", conflict.Detail)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemoveMissingSharedRootOwnerOutputs(t *testing.T) {
|
func TestRemoveMissingSharedRootOwnerOutputs(t *testing.T) {
|
||||||
@@ -241,6 +247,12 @@ func TestCompareSharedRootOwnerScopesCurrentOwner(t *testing.T) {
|
|||||||
if missing.Outcome != OutcomeDestinationAbsent {
|
if missing.Outcome != OutcomeDestinationAbsent {
|
||||||
t.Fatalf("missing owner comparison = %#v, want destination absent", missing)
|
t.Fatalf("missing owner comparison = %#v, want destination absent", missing)
|
||||||
}
|
}
|
||||||
|
if missing.Detail.Kind != ComparisonDetailSharedRootOwnerAbsent {
|
||||||
|
t.Fatalf("missing owner detail kind = %q, want %q", missing.Detail.Kind, ComparisonDetailSharedRootOwnerAbsent)
|
||||||
|
}
|
||||||
|
if missing.Detail.CurrentOwner != CurrentOwnerScope("missing", "archive") {
|
||||||
|
t.Fatalf("missing owner detail = %#v, want missing/archive", missing.Detail)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCompareSharedRootOwnerAcceptsMatchingSingleOwnerState(t *testing.T) {
|
func TestCompareSharedRootOwnerAcceptsMatchingSingleOwnerState(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user