Skip to content

Commit 8e08aea

Browse files
committed
[DependencyResolver] Add helper for checking assignment completeness.
- Also, rename `VersionAssignment` -> `VersionAssignmentSet` to make it more obvious this is basically a glorified collection.
1 parent d273901 commit 8e08aea

File tree

2 files changed

+46
-3
lines changed

2 files changed

+46
-3
lines changed

Sources/PackageGraph/DependencyResolver.swift

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,15 +174,19 @@ enum BoundVersion {
174174
case version(Version)
175175
}
176176

177-
/// An assignment of versions for a set of packages.
177+
/// A container for version assignments for a set of packages.
178178
///
179179
/// This is intended to be an efficient data structure for accumulating a set of
180180
/// version assignments along with efficient access to the derived information
181181
/// about the assignment (for example, the unified set of constraints it
182182
/// induces).
183+
///
184+
/// The set itself is designed to only ever contain a consistent set of
185+
/// assignments, i.e. each assignment should satisfy the induced
186+
/// `constraints`, but this invariant is not explicitly enforced.
183187
//
184188
// FIXME: Actually make efficient.
185-
struct VersionAssignment<C> where C: PackageContainer {
189+
struct VersionAssignmentSet<C> where C: PackageContainer {
186190
typealias Container = C
187191
typealias Identifier = Container.Identifier
188192

@@ -217,6 +221,9 @@ struct VersionAssignment<C> where C: PackageContainer {
217221
///
218222
/// This consists of the merged constraints which need to be satisfied on
219223
/// each package as a result of the versions selected in the assignment.
224+
///
225+
/// The resulting constraint set is guaranteed to be non-empty for each
226+
/// mapping, assuming the invariants on the set are followed.
220227
//
221228
// FIXME: We need to cache this.
222229
var constraints: [Identifier: VersionSetSpecifier] {
@@ -267,6 +274,29 @@ struct VersionAssignment<C> where C: PackageContainer {
267274
}
268275
}
269276
}
277+
278+
/// Check if the assignment is valid and complete.
279+
func checkIfValidAndComplete() -> Bool {
280+
// Validity should hold trivially, because it is an invariant of the collection.
281+
for assignment in assignments.values {
282+
if !isValid(binding: assignment.binding, for: assignment.container) {
283+
return false
284+
}
285+
}
286+
287+
// Check completeness, by simply looking at all the entries in the induced constraints.
288+
for key in self.constraints.keys {
289+
// Verify we have a non-excluded entry for this key.
290+
switch assignments[key]?.binding {
291+
case .version?:
292+
continue
293+
case .excluded?, nil:
294+
return false
295+
}
296+
}
297+
298+
return true
299+
}
270300
}
271301

272302
/// A general purpose package dependency resolver.

Tests/PackageGraphTests/DependencyResolverTests.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,29 +139,42 @@ class DependencyResolverTests: XCTestCase {
139139
])
140140
let b = MockPackageContainer(name: "B", dependenciesByVersion: [
141141
v1: [(container: "C", versionRequirement: v1Range)]])
142+
let c = MockPackageContainer(name: "C", dependenciesByVersion: [
143+
v1: []])
142144

143-
var assignment = VersionAssignment<MockPackageContainer>()
145+
var assignment = VersionAssignmentSet<MockPackageContainer>()
144146
XCTAssertEqual(assignment.constraints, [:])
145147
XCTAssert(assignment.isValid(binding: .version(v2), for: b))
148+
// An empty assignment is valid.
149+
XCTAssert(assignment.checkIfValidAndComplete())
146150

147151
// Add an assignment and check the constraints.
148152
assignment[a] = .version(v1)
149153
XCTAssertEqual(assignment.constraints, ["B": v1Range])
150154
XCTAssert(assignment.isValid(binding: .version(v1), for: b))
151155
XCTAssert(!assignment.isValid(binding: .version(v2), for: b))
156+
// This is invalid (no 'B' assignment).
157+
XCTAssert(!assignment.checkIfValidAndComplete())
152158

153159
// Check another assignment.
154160
assignment[b] = .version(v1)
155161
XCTAssertEqual(assignment.constraints, ["B": v1Range, "C": v1Range])
162+
XCTAssert(!assignment.checkIfValidAndComplete())
156163

157164
// Check excluding 'A'.
158165
assignment[a] = .excluded
159166
XCTAssertEqual(assignment.constraints, ["C": v1Range])
167+
XCTAssert(!assignment.checkIfValidAndComplete())
168+
169+
// Check completing the assignment.
170+
assignment[c] = .version(v1)
171+
XCTAssert(assignment.checkIfValidAndComplete())
160172

161173
// Check bringing back 'A' at a different version, which has only a
162174
// more restrictive 'C' dependency.
163175
assignment[a] = .version(v2)
164176
XCTAssertEqual(assignment.constraints, ["C": v1_1Range])
177+
XCTAssert(assignment.checkIfValidAndComplete())
165178
}
166179
}
167180

0 commit comments

Comments
 (0)