Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 33 additions & 3 deletions Dummies.UnitTests/AnyCollectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,8 @@ public void ContainingOutsideDomainExtendsCardinality() {
}

// The same reasoning holds for a distinct list and for several out-of-domain values at once. Dictionary keys
// run through the very same CollectionState path, so the correction reaches them too — but AnyDictionary has
// no Containing surface, so its keys are gated purely by count (see DictionaryOfBehaves) and cannot exercise
// the out-of-domain case directly.
// run through the very same CollectionState path, so the correction reaches them too, now exercised directly
// through AnyDictionary.ContainingKey (see DictionaryContainingKeyOutsideDomainExtendsCardinality).
for (int i = 0; i < SampleCount; i++) {
HashSet<int> list = new(Any.ListOf(Any.Int32().OneOf(1, 2)).Containing(3).WithCount(3).Distinct().Generate());
Check.That(list).Contains(1, 2, 3);
Expand Down Expand Up @@ -321,6 +320,37 @@ public void DictionaryOfBehaves() {
Check.ThatCode(() => Any.DictionaryOf<int, int>(null!, Any.Int32())).Throws<ArgumentNullException>();
}

[Fact(DisplayName = "ContainingKey: a key outside the key domain extends the effective cardinality (issue #225).")]
public void DictionaryContainingKeyOutsideDomainExtendsCardinality() {
// Mirrors ContainingOutsideDomainExtendsCardinality on the dictionary surface: {1, 2} is all the key
// generator can produce, and 3 is supplied directly from outside that domain, so a three-entry dictionary is
// satisfiable — the out-of-domain cardinality-credit path AnyDictionary could not exercise before.
for (int i = 0; i < SampleCount; i++) {
Dictionary<int, string> dictionary =
Any.DictionaryOf(Any.Int32().OneOf(1, 2), Any.String().NonEmpty()).ContainingKey(3).WithCount(3).Generate();
Check.That(dictionary.Keys).Contains(1, 2, 3);
Check.That(dictionary.Count).IsEqualTo(3);
Check.That(dictionary.Values).ContainsOnlyElementsThatMatch(value => value.Length > 0);
}
}

[Fact(DisplayName = "ContainingKey: a within-domain key is present, an out-of-capacity one still conflicts eagerly.")]
public void DictionaryContainingKeyInsideDomainDoesNotInflate() {
// A within-domain fixed key is present and adds no capacity of its own.
for (int i = 0; i < SampleCount; i++) {
Dictionary<int, string> dictionary =
Any.DictionaryOf(Any.Int32().Between(1, 9), Any.String().NonEmpty()).WithCount(5).ContainingKey(7).Generate();
Check.That(dictionary.ContainsKey(7)).IsTrue();
Check.That(dictionary.Count).IsEqualTo(5);
}

// 1 is already producible, so three distinct keys over {1, 2} remain impossible and still fail eagerly,
// naming the shortfall — exactly as ContainingInsideDomainDoesNotInflate asserts for a set.
ConflictingAnyConstraintException conflict = Assert.Throws<ConflictingAnyConstraintException>(
() => Any.DictionaryOf(Any.Int32().OneOf(1, 2), Any.String().NonEmpty()).ContainingKey(1).WithCount(3));
Check.That(conflict.Message).Contains("2 distinct value");
}

[Fact(DisplayName = "PairOf and TripleOf assemble value tuples from constrained parts.")]
public void PairAndTriple() {
for (int i = 0; i < SampleCount; i++) {
Expand Down
15 changes: 15 additions & 0 deletions Dummies/AnyDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,21 @@ public AnyDictionary<TKey, TValue> WithCountBetween(int minimum, int maximum) {
return With(CountConstraints.WithCountBetween(_keys, minimum, maximum));
}

/// <summary>
/// Requires the dictionary to contain an entry for <paramref name="key" />. May be declared several times;
/// each required key takes one entry's room and the required keys must be distinct. A key outside the key
/// generator's domain extends the effective cardinality exactly as <see cref="AnySet{T}" />'s containment
/// does, so an otherwise impossible entry count becomes reachable; the entry's value is generated like any
/// other. Named <c>ContainingKey</c> rather than a bare <c>Containing</c> so the surface reads unambiguously
/// on a dictionary, whose elements are key/value pairs.
/// </summary>
/// <param name="key">The key the generated dictionary must contain.</param>
/// <returns>A new generator carrying the added constraint.</returns>
/// <exception cref="ConflictingAnyConstraintException">Thrown when the constraint contradicts a constraint already declared.</exception>
public AnyDictionary<TKey, TValue> ContainingKey(TKey key) {
return With(_keys.WithContaining(key, $"ContainingKey({AnyDerivation.Display(key)})"));
}

/// <inheritdoc />
public Dictionary<TKey, TValue> Generate() {
List<TKey> keys = _keys.Materialize(_source ?? AmbientRandomSource.Instance);
Expand Down
1 change: 1 addition & 0 deletions Dummies/PublicAPI/net8.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ Dummies.AnyDecimal.OneOf(params decimal[]! values) -> Dummies.AnyDecimal!
Dummies.AnyDecimal.Positive() -> Dummies.AnyDecimal!
Dummies.AnyDecimal.Zero() -> Dummies.AnyDecimal!
Dummies.AnyDictionary<TKey, TValue>
Dummies.AnyDictionary<TKey, TValue>.ContainingKey(TKey key) -> Dummies.AnyDictionary<TKey, TValue>!
Dummies.AnyDictionary<TKey, TValue>.Empty() -> Dummies.AnyDictionary<TKey, TValue>!
Dummies.AnyDictionary<TKey, TValue>.Generate() -> System.Collections.Generic.Dictionary<TKey, TValue>!
Dummies.AnyDictionary<TKey, TValue>.NonEmpty() -> Dummies.AnyDictionary<TKey, TValue>!
Expand Down
1 change: 1 addition & 0 deletions Dummies/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ Dummies.AnyDecimal.OneOf(params decimal[]! values) -> Dummies.AnyDecimal!
Dummies.AnyDecimal.Positive() -> Dummies.AnyDecimal!
Dummies.AnyDecimal.Zero() -> Dummies.AnyDecimal!
Dummies.AnyDictionary<TKey, TValue>
Dummies.AnyDictionary<TKey, TValue>.ContainingKey(TKey key) -> Dummies.AnyDictionary<TKey, TValue>!
Dummies.AnyDictionary<TKey, TValue>.Empty() -> Dummies.AnyDictionary<TKey, TValue>!
Dummies.AnyDictionary<TKey, TValue>.Generate() -> System.Collections.Generic.Dictionary<TKey, TValue>!
Dummies.AnyDictionary<TKey, TValue>.NonEmpty() -> Dummies.AnyDictionary<TKey, TValue>!
Expand Down