Clarify semantic reconciliation candidate numbers
This commit is contained in:
@@ -12,8 +12,8 @@ import (
|
||||
func TestAssessProducesAStableOriginalPositionPlan(t *testing.T) {
|
||||
preparation := proposalPreparation(t)
|
||||
response := ProposalResponse{DuplicateGroups: []DuplicateGroup{
|
||||
{CandidateIDs: []int{5, 4}, CanonicalCandidateID: 5},
|
||||
{CandidateIDs: []int{2, 1}, CanonicalCandidateID: 2},
|
||||
{CandidateNumbers: []int{5, 4}, CanonicalCandidateNumber: 5},
|
||||
{CandidateNumbers: []int{2, 1}, CanonicalCandidateNumber: 2},
|
||||
}}
|
||||
assessment := preparation.Assess(response)
|
||||
want := []planGroupSnapshot{
|
||||
@@ -28,8 +28,8 @@ func TestAssessProducesAStableOriginalPositionPlan(t *testing.T) {
|
||||
}
|
||||
|
||||
reordered := preparation.Assess(ProposalResponse{DuplicateGroups: []DuplicateGroup{
|
||||
{CandidateIDs: []int{1, 2}, CanonicalCandidateID: 2},
|
||||
{CandidateIDs: []int{4, 5}, CanonicalCandidateID: 5},
|
||||
{CandidateNumbers: []int{1, 2}, CanonicalCandidateNumber: 2},
|
||||
{CandidateNumbers: []int{4, 5}, CanonicalCandidateNumber: 5},
|
||||
}})
|
||||
if got := snapshotPlan(reordered.Plan()); !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("reordered plan = %#v, want %#v", got, want)
|
||||
@@ -53,6 +53,7 @@ func TestIssueDetailsPreservesIssueOrder(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCorrectionGuidanceCoversEveryIssueCategoryWithoutExposingInternalLabels(t *testing.T) {
|
||||
numberRange := CandidateNumberRange{First: 1, Last: 8}
|
||||
if len(issueCorrectionProse) != len(allIssueCategories) {
|
||||
t.Fatalf("correction prose entries = %d, categories = %d", len(issueCorrectionProse), len(allIssueCategories))
|
||||
}
|
||||
@@ -69,15 +70,15 @@ func TestCorrectionGuidanceCoversEveryIssueCategoryWithoutExposingInternalLabels
|
||||
issues[index] = Issue{GroupIndex: index, Category: category}
|
||||
}
|
||||
|
||||
details, err := CorrectionDetails(issues)
|
||||
details, err := CorrectionDetails(issues, numberRange)
|
||||
if err != nil {
|
||||
t.Fatalf("CorrectionDetails() error = %v", err)
|
||||
}
|
||||
guidance, err := CorrectionGuidance(issues)
|
||||
guidance, err := CorrectionGuidance(issues, numberRange)
|
||||
if err != nil {
|
||||
t.Fatalf("CorrectionGuidance() error = %v", err)
|
||||
}
|
||||
if len(details) != len(allIssueCategories) || !strings.Contains(guidance, "Duplicate group 1") || !strings.Contains(guidance, "complete corrected JSON response") || len(guidance) > contracts.MaxNormalizeRetryCorrectionGuidanceBytes {
|
||||
if len(details) != len(allIssueCategories)+1 || !strings.Contains(guidance, "Duplicate group 1") || !strings.Contains(guidance, "candidate-number range 1 through 8") || !strings.Contains(guidance, "start_unit_id") || !strings.Contains(guidance, "complete corrected JSON response") || len(guidance) > contracts.MaxNormalizeRetryCorrectionGuidanceBytes {
|
||||
t.Fatalf("correction details = %#v guidance = %q", details, guidance)
|
||||
}
|
||||
for _, category := range allIssueCategories {
|
||||
@@ -88,20 +89,21 @@ func TestCorrectionGuidanceCoversEveryIssueCategoryWithoutExposingInternalLabels
|
||||
}
|
||||
|
||||
func TestCorrectionDetailsDeduplicatesAndBoundsAffectedGroupLists(t *testing.T) {
|
||||
numberRange := CandidateNumberRange{First: 1, Last: 256}
|
||||
issues := make([]Issue, 0, 257)
|
||||
for group := 0; group < 256; group++ {
|
||||
issues = append(issues, Issue{GroupIndex: group, Category: IssueMemberUnknown})
|
||||
}
|
||||
issues = append(issues, Issue{GroupIndex: 0, Category: IssueMemberUnknown})
|
||||
|
||||
details, err := CorrectionDetails(issues)
|
||||
details, err := CorrectionDetails(issues, numberRange)
|
||||
if err != nil {
|
||||
t.Fatalf("CorrectionDetails() error = %v", err)
|
||||
}
|
||||
if len(details) != 1 || !strings.Contains(details[0], "additional affected group") || strings.Count(details[0], "Duplicate groups") != 1 {
|
||||
t.Fatalf("CorrectionDetails() = %#v, want one bounded grouped detail", details)
|
||||
if len(details) != 2 || !strings.Contains(details[0], "additional affected group") || strings.Count(details[0], "Duplicate groups") != 1 || !strings.Contains(details[1], "source positions") {
|
||||
t.Fatalf("CorrectionDetails() = %#v, want one bounded grouped detail and one namespace reminder", details)
|
||||
}
|
||||
guidance, err := CorrectionGuidance(issues)
|
||||
guidance, err := CorrectionGuidance(issues, numberRange)
|
||||
if err != nil || len(guidance) > contracts.MaxNormalizeRetryCorrectionGuidanceBytes {
|
||||
t.Fatalf("CorrectionGuidance() = %q, %v", guidance, err)
|
||||
}
|
||||
@@ -112,10 +114,15 @@ func TestCorrectionDetailsRejectsUnknownOrInvalidIssues(t *testing.T) {
|
||||
{{GroupIndex: 0, Category: "future_unmapped_category"}},
|
||||
{{GroupIndex: -1, Category: IssueMemberUnknown}},
|
||||
} {
|
||||
if details, err := CorrectionDetails(issues); err == nil || details != nil {
|
||||
if details, err := CorrectionDetails(issues, CandidateNumberRange{First: 1, Last: 2}); err == nil || details != nil {
|
||||
t.Fatalf("CorrectionDetails(%#v) = %#v, %v; want fail-closed error", issues, details, err)
|
||||
}
|
||||
}
|
||||
for _, numberRange := range []CandidateNumberRange{{}, {First: 0, Last: 2}, {First: 1, Last: 1}} {
|
||||
if details, err := CorrectionDetails([]Issue{{GroupIndex: 0, Category: IssueMemberUnknown}}, numberRange); err == nil || details != nil {
|
||||
t.Fatalf("CorrectionDetails(range %#v) = %#v, %v; want fail-closed error", numberRange, details, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssessRejectsEveryUnsafeLocalGroupShape(t *testing.T) {
|
||||
@@ -125,15 +132,15 @@ func TestAssessRejectsEveryUnsafeLocalGroupShape(t *testing.T) {
|
||||
group DuplicateGroup
|
||||
category IssueCategory
|
||||
}{
|
||||
{name: "zero member", group: DuplicateGroup{CandidateIDs: []int{0, 2}, CanonicalCandidateID: 2}, category: IssueMemberNonPositive},
|
||||
{name: "negative member", group: DuplicateGroup{CandidateIDs: []int{-1, 2}, CanonicalCandidateID: 2}, category: IssueMemberNonPositive},
|
||||
{name: "unknown member", group: DuplicateGroup{CandidateIDs: []int{99, 2}, CanonicalCandidateID: 2}, category: IssueMemberUnknown},
|
||||
{name: "repeated member", group: DuplicateGroup{CandidateIDs: []int{1, 1}, CanonicalCandidateID: 1}, category: IssueRepeatedMember},
|
||||
{name: "too small", group: DuplicateGroup{CandidateIDs: []int{1}, CanonicalCandidateID: 1}, category: IssueFewerThanTwoMembers},
|
||||
{name: "zero canonical", group: DuplicateGroup{CandidateIDs: []int{1, 2}, CanonicalCandidateID: 0}, category: IssueCanonicalNonPositive},
|
||||
{name: "negative canonical", group: DuplicateGroup{CandidateIDs: []int{1, 2}, CanonicalCandidateID: -1}, category: IssueCanonicalNonPositive},
|
||||
{name: "unknown canonical", group: DuplicateGroup{CandidateIDs: []int{1, 2}, CanonicalCandidateID: 99}, category: IssueCanonicalUnknown},
|
||||
{name: "canonical not member", group: DuplicateGroup{CandidateIDs: []int{1, 2}, CanonicalCandidateID: 3}, category: IssueCanonicalNotMember},
|
||||
{name: "zero member", group: DuplicateGroup{CandidateNumbers: []int{0, 2}, CanonicalCandidateNumber: 2}, category: IssueMemberNonPositive},
|
||||
{name: "negative member", group: DuplicateGroup{CandidateNumbers: []int{-1, 2}, CanonicalCandidateNumber: 2}, category: IssueMemberNonPositive},
|
||||
{name: "unknown member", group: DuplicateGroup{CandidateNumbers: []int{99, 2}, CanonicalCandidateNumber: 2}, category: IssueMemberUnknown},
|
||||
{name: "repeated member", group: DuplicateGroup{CandidateNumbers: []int{1, 1}, CanonicalCandidateNumber: 1}, category: IssueRepeatedMember},
|
||||
{name: "too small", group: DuplicateGroup{CandidateNumbers: []int{1}, CanonicalCandidateNumber: 1}, category: IssueFewerThanTwoMembers},
|
||||
{name: "zero canonical", group: DuplicateGroup{CandidateNumbers: []int{1, 2}, CanonicalCandidateNumber: 0}, category: IssueCanonicalNonPositive},
|
||||
{name: "negative canonical", group: DuplicateGroup{CandidateNumbers: []int{1, 2}, CanonicalCandidateNumber: -1}, category: IssueCanonicalNonPositive},
|
||||
{name: "unknown canonical", group: DuplicateGroup{CandidateNumbers: []int{1, 2}, CanonicalCandidateNumber: 99}, category: IssueCanonicalUnknown},
|
||||
{name: "canonical not member", group: DuplicateGroup{CandidateNumbers: []int{1, 2}, CanonicalCandidateNumber: 3}, category: IssueCanonicalNotMember},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
@@ -151,9 +158,9 @@ func TestAssessRejectsEveryUnsafeLocalGroupShape(t *testing.T) {
|
||||
func TestAssessDiscardsEveryOverlappingGroupAndRetainsIndependentGroups(t *testing.T) {
|
||||
preparation := proposalPreparation(t)
|
||||
assessment := preparation.Assess(ProposalResponse{DuplicateGroups: []DuplicateGroup{
|
||||
{CandidateIDs: []int{1, 2}, CanonicalCandidateID: 1},
|
||||
{CandidateIDs: []int{2, 3}, CanonicalCandidateID: 2},
|
||||
{CandidateIDs: []int{4, 5}, CanonicalCandidateID: 5},
|
||||
{CandidateNumbers: []int{1, 2}, CanonicalCandidateNumber: 1},
|
||||
{CandidateNumbers: []int{2, 3}, CanonicalCandidateNumber: 2},
|
||||
{CandidateNumbers: []int{4, 5}, CanonicalCandidateNumber: 5},
|
||||
}})
|
||||
wantPlan := []planGroupSnapshot{{members: []int{5, 6}, canonical: 6}}
|
||||
if got := snapshotPlan(assessment.Plan()); !reflect.DeepEqual(got, wantPlan) {
|
||||
@@ -168,8 +175,8 @@ func TestAssessDiscardsEveryOverlappingGroupAndRetainsIndependentGroups(t *testi
|
||||
}
|
||||
|
||||
invalidAndSafe := preparation.Assess(ProposalResponse{DuplicateGroups: []DuplicateGroup{
|
||||
{CandidateIDs: []int{1, 99}, CanonicalCandidateID: 1},
|
||||
{CandidateIDs: []int{1, 2}, CanonicalCandidateID: 2},
|
||||
{CandidateNumbers: []int{1, 99}, CanonicalCandidateNumber: 1},
|
||||
{CandidateNumbers: []int{1, 2}, CanonicalCandidateNumber: 2},
|
||||
}})
|
||||
wantPlan = []planGroupSnapshot{{members: []int{0, 2}, canonical: 2}}
|
||||
if got := snapshotPlan(invalidAndSafe.Plan()); !reflect.DeepEqual(got, wantPlan) {
|
||||
@@ -183,13 +190,13 @@ func TestAssessDiscardsEveryOverlappingGroupAndRetainsIndependentGroups(t *testi
|
||||
func TestAssessmentAccessorsAndInputsDoNotShareRetainedState(t *testing.T) {
|
||||
preparation := proposalPreparation(t)
|
||||
response := ProposalResponse{DuplicateGroups: []DuplicateGroup{
|
||||
{CandidateIDs: []int{1, 2}, CanonicalCandidateID: 2},
|
||||
{CandidateNumbers: []int{1, 2}, CanonicalCandidateNumber: 2},
|
||||
}}
|
||||
assessment := preparation.Assess(response)
|
||||
want := snapshotPlan(assessment.Plan())
|
||||
|
||||
response.DuplicateGroups[0].CandidateIDs[0] = 99
|
||||
response.DuplicateGroups[0].CanonicalCandidateID = 99
|
||||
response.DuplicateGroups[0].CandidateNumbers[0] = 99
|
||||
response.DuplicateGroups[0].CanonicalCandidateNumber = 99
|
||||
plan := assessment.Plan()
|
||||
groups := plan.Groups()
|
||||
members := groups[0].MemberPositions()
|
||||
@@ -200,7 +207,7 @@ func TestAssessmentAccessorsAndInputsDoNotShareRetainedState(t *testing.T) {
|
||||
}
|
||||
|
||||
invalid := preparation.Assess(ProposalResponse{DuplicateGroups: []DuplicateGroup{{
|
||||
CandidateIDs: []int{1, 99}, CanonicalCandidateID: 1,
|
||||
CandidateNumbers: []int{1, 99}, CanonicalCandidateNumber: 1,
|
||||
}}})
|
||||
issues := invalid.Issues()
|
||||
issues[0].GroupIndex = 99
|
||||
|
||||
Reference in New Issue
Block a user