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
25 changes: 25 additions & 0 deletions Dummies.UnitTests/AnyCollectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,31 @@ public void DictionaryContainingAnyKeyForcesADrawnKey() {
Check.ThatCode(() => Any.DictionaryOf(Any.Int32(), Any.Int32()).ContainingAnyKey(null!)).Throws<ArgumentNullException>();
}

[Fact(DisplayName = "ContainingEntry: pins the value for a required key, extending cardinality out of domain (issue #288).")]
public void DictionaryContainingEntryPinsTheValue() {
for (int i = 0; i < SampleCount; i++) {
// Key 3 is outside the key domain {1, 2} (supplied directly, extends cardinality); value 99 is outside
// the value domain {1..9}, proving it is the pinned value rather than a generated one.
Dictionary<int, int> dictionary =
Any.DictionaryOf(Any.Int32().OneOf(1, 2), Any.Int32().Between(1, 9))
.ContainingEntry(3, 99)
.WithCount(3)
.Generate();
Check.That(dictionary.Keys).Contains(1, 2, 3);
Check.That(dictionary[3]).IsEqualTo(99);
Check.That(dictionary.Count).IsEqualTo(3);
}
}

[Fact(DisplayName = "ContainingEntry: pinning the same key twice — or an entry and a ContainingKey — conflicts.")]
public void DictionaryContainingEntryDuplicateKeyConflicts() {
Check.ThatCode(() => Any.DictionaryOf(Any.Int32(), Any.Int32()).ContainingEntry(1, 10).ContainingEntry(1, 20))
.Throws<ConflictingAnyConstraintException>();

Check.ThatCode(() => Any.DictionaryOf(Any.Int32(), Any.Int32()).ContainingKey(1).ContainingEntry(1, 20))
.Throws<ConflictingAnyConstraintException>();
}

[Fact(DisplayName = "PairOf and TripleOf assemble value tuples from constrained parts.")]
public void PairAndTriple() {
for (int i = 0; i < SampleCount; i++) {
Expand Down
52 changes: 43 additions & 9 deletions Dummies/AnyDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,29 @@ namespace Dummies;
public sealed class AnyDictionary<TKey, TValue> : IAny<Dictionary<TKey, TValue>>, IHasRandomSource
where TKey : notnull {

#region Statics members declarations

private static readonly IReadOnlyDictionary<TKey, TValue> NoPinnedValues = new Dictionary<TKey, TValue>();

#endregion

#region Fields declarations

private readonly CollectionState<TKey> _keys;
private readonly RandomSource? _source;
private readonly IAny<TValue> _values;
private readonly CollectionState<TKey> _keys;
private readonly IReadOnlyDictionary<TKey, TValue> _pinnedValues;
private readonly RandomSource? _source;
private readonly IAny<TValue> _values;

#endregion

internal AnyDictionary(RandomSource? source, CollectionState<TKey> keys, IAny<TValue> values) {
_source = source;
_keys = keys;
_values = values;
internal AnyDictionary(RandomSource? source, CollectionState<TKey> keys, IAny<TValue> values)
: this(source, keys, values, NoPinnedValues) { }

private AnyDictionary(RandomSource? source, CollectionState<TKey> keys, IAny<TValue> values, IReadOnlyDictionary<TKey, TValue> pinnedValues) {
_source = source;
_keys = keys;
_values = values;
_pinnedValues = pinnedValues;
}

RandomSource? IHasRandomSource.Source => _source;
Expand Down Expand Up @@ -112,17 +123,40 @@ public AnyDictionary<TKey, TValue> ContainingAnyKey(IAny<TKey> generator) {
return With(_keys.WithContaining(generator, "ContainingAnyKey(<generator>)"));
}

/// <summary>
/// Requires the dictionary to contain the entry <paramref name="key" /> → <paramref name="value" />: the key
/// is forced in exactly as <see cref="ContainingKey" /> does (inheriting the out-of-domain cardinality
/// credit), and its value is pinned to <paramref name="value" /> instead of being drawn from the value
/// generator; the other entries stay arbitrary. Declaring two entries for the same key — or an entry and a
/// <see cref="ContainingKey" /> for it — conflicts, since the keys must be distinct.
/// </summary>
/// <param name="key">The key the generated dictionary must contain.</param>
/// <param name="value">The value pinned to <paramref name="key" />.</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> ContainingEntry(TKey key, TValue value) {
CollectionState<TKey> keys = _keys.WithContaining(key, $"ContainingEntry({AnyDerivation.Display(key)}, {AnyDerivation.Display(value)})");

Dictionary<TKey, TValue> pinned = new(_pinnedValues.Count + 1, _keys.Comparer);
foreach (KeyValuePair<TKey, TValue> entry in _pinnedValues) { pinned[entry.Key] = entry.Value; }
pinned[key] = value;

return new AnyDictionary<TKey, TValue>(_source, keys, _values, pinned);
}

/// <inheritdoc />
public Dictionary<TKey, TValue> Generate() {
List<TKey> keys = _keys.Materialize(_source ?? AmbientRandomSource.Instance);
Dictionary<TKey, TValue> dictionary = new(keys.Count, _keys.Comparer);
foreach (TKey key in keys) { dictionary[key] = _values.Generate(); }
foreach (TKey key in keys) {
dictionary[key] = _pinnedValues.ContainsKey(key) ? _pinnedValues[key] : _values.Generate();
}

return dictionary;
}

private AnyDictionary<TKey, TValue> With(CollectionState<TKey> keys) {
return new AnyDictionary<TKey, TValue>(_source, keys, _values);
return new AnyDictionary<TKey, TValue>(_source, keys, _values, _pinnedValues);
}

}
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 @@ -117,6 +117,7 @@ Dummies.AnyDecimal.Positive() -> Dummies.AnyDecimal!
Dummies.AnyDecimal.Zero() -> Dummies.AnyDecimal!
Dummies.AnyDictionary<TKey, TValue>
Dummies.AnyDictionary<TKey, TValue>.ContainingAnyKey(Dummies.IAny<TKey>! generator) -> Dummies.AnyDictionary<TKey, TValue>!
Dummies.AnyDictionary<TKey, TValue>.ContainingEntry(TKey key, TValue value) -> 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>!
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 @@ -102,6 +102,7 @@ Dummies.AnyDecimal.Positive() -> Dummies.AnyDecimal!
Dummies.AnyDecimal.Zero() -> Dummies.AnyDecimal!
Dummies.AnyDictionary<TKey, TValue>
Dummies.AnyDictionary<TKey, TValue>.ContainingAnyKey(Dummies.IAny<TKey>! generator) -> Dummies.AnyDictionary<TKey, TValue>!
Dummies.AnyDictionary<TKey, TValue>.ContainingEntry(TKey key, TValue value) -> 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>!
Expand Down