Skip to content

Commit e659df6

Browse files
committed
Add a PredicateQueue class.
1 parent 3711245 commit e659df6

3 files changed

Lines changed: 637 additions & 0 deletions

File tree

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
// Copyright Subatomix Research Inc.
2+
// SPDX-License-Identifier: MIT
3+
4+
using System.Collections;
5+
6+
namespace DependencyQueue;
7+
8+
[TestFixture]
9+
public class PredicateQueueTests
10+
{
11+
[Test]
12+
public void Construct_Default()
13+
{
14+
var queue = new PredicateQueue<string>();
15+
16+
queue.Count.ShouldBe(0);
17+
queue .ShouldBeEmpty();
18+
}
19+
20+
[Test]
21+
public void Construct_Enumerable()
22+
{
23+
var queue = new PredicateQueue<string?>(["a", null, "b"]);
24+
25+
queue.Count.ShouldBe(3);
26+
queue .ShouldBe(["a", null, "b"]);
27+
}
28+
29+
[Test]
30+
public void Construct_Enumerable_Null()
31+
{
32+
Should.Throw<ArgumentNullException>(() =>
33+
{
34+
_ = new PredicateQueue<string>(null!);
35+
});
36+
}
37+
38+
[Test]
39+
public void Enqueue_Single()
40+
{
41+
var queue = new PredicateQueue<string>();
42+
43+
queue.Enqueue("a");
44+
45+
queue.Count.ShouldBe(1);
46+
queue .ShouldHaveSingleItem("a");
47+
}
48+
49+
[Test]
50+
public void Enqueue_Multiple()
51+
{
52+
var queue = new PredicateQueue<string?>();
53+
54+
queue.Enqueue("a");
55+
queue.Enqueue(null);
56+
queue.Enqueue("b");
57+
58+
queue.Count.ShouldBe(3);
59+
queue .ShouldBe(["a", null, "b"]);
60+
}
61+
62+
[Test]
63+
public void Peek_Empty()
64+
{
65+
var queue = new PredicateQueue<string>();
66+
67+
Should.Throw<InvalidOperationException>(queue.Peek);
68+
}
69+
70+
[Test]
71+
public void Peek_NotEmpty()
72+
{
73+
var queue = new PredicateQueue<string>(["a", "b"]);
74+
75+
queue.Peek().ShouldBe("a");
76+
77+
queue.Count.ShouldBe(2);
78+
queue .ShouldBe(["a", "b"]);
79+
}
80+
81+
[Test]
82+
public void TryPeek_Empty()
83+
{
84+
var queue = new PredicateQueue<string>();
85+
86+
var result = queue.TryPeek(out var item);
87+
88+
result.ShouldBeFalse();
89+
item .ShouldBeNull();
90+
}
91+
92+
[Test]
93+
public void TryPeek_NotEmpty()
94+
{
95+
var queue = new PredicateQueue<string>(["a", "b"]);
96+
97+
var result = queue.TryPeek(out var item);
98+
99+
result.ShouldBeTrue();
100+
item .ShouldBe("a");
101+
102+
queue.Count.ShouldBe(2);
103+
queue .ShouldBe(["a", "b"]);
104+
}
105+
106+
[Test]
107+
public void TryDequeue_Empty()
108+
{
109+
var queue = new PredicateQueue<string>();
110+
111+
var result = queue.TryDequeue(s => s, _ => true, out var item);
112+
113+
result.ShouldBeFalse();
114+
item .ShouldBeNull();
115+
}
116+
117+
[Test]
118+
public void TryDequeue_Match()
119+
{
120+
var queue = new PredicateQueue<string?>(["a", null, "b", "c"]);
121+
122+
var result = queue.TryDequeue(s => s?[0], c => c >= 'b', out var item);
123+
124+
result.ShouldBeTrue();
125+
item .ShouldBe("b");
126+
127+
queue.Count.ShouldBe(3);
128+
queue .ShouldBe(["a", null, "c"]);
129+
130+
result = queue.TryDequeue(s => s, s => s is null, out item);
131+
132+
result.ShouldBeTrue();
133+
item .ShouldBe(null);
134+
135+
queue.Count.ShouldBe(2);
136+
queue .ShouldBe(["a", "c"]);
137+
}
138+
139+
[Test]
140+
public void TryDequeue_NoMatch()
141+
{
142+
var queue = new PredicateQueue<string>(["a", "b", "c"]);
143+
144+
var result = queue.TryDequeue(s => s[0], char.IsDigit, out var item);
145+
146+
result.ShouldBeFalse();
147+
item .ShouldBeNull();
148+
149+
queue.Count.ShouldBe(3);
150+
queue .ShouldBe(["a", "b", "c"]);
151+
}
152+
153+
[Test]
154+
public void TryDequeue_ThenEnqueue()
155+
{
156+
var queue = new PredicateQueue<string>(["a", "b", "c"]);
157+
158+
// Remove some items to create free slots
159+
queue.TryDequeue(s => s, s => s == "c", out _);
160+
queue.TryDequeue(s => s, s => s == "a", out _);
161+
162+
// New items should reuse free slots
163+
queue.Enqueue("d");
164+
queue.Enqueue("e");
165+
166+
queue.Count.ShouldBe(3);
167+
queue .ShouldBe(["b", "d", "e"]);
168+
}
169+
170+
[Test]
171+
public void TryDequeue_NullConverter()
172+
{
173+
var queue = new PredicateQueue<string>(["a"]);
174+
175+
Should.Throw<ArgumentNullException>(() =>
176+
{
177+
queue.TryDequeue<string>(null!, _ => true, out _);
178+
});
179+
}
180+
181+
[Test]
182+
public void TryDequeue_NullPredicate()
183+
{
184+
var queue = new PredicateQueue<string>(["a"]);
185+
186+
Should.Throw<ArgumentNullException>(() =>
187+
{
188+
queue.TryDequeue(s => s, null!, out _);
189+
});
190+
}
191+
192+
[Test]
193+
public void GetEnumerator_Empty()
194+
{
195+
var queue = new PredicateQueue<string>();
196+
197+
using var enumerator = queue.GetEnumerator();
198+
199+
enumerator.MoveNext().ShouldBeFalse();
200+
}
201+
202+
[Test]
203+
public void GetEnumerator_NonEmpty()
204+
{
205+
var queue = new PredicateQueue<string?>(["a", null, "b"]);
206+
207+
using var enumerator = queue.GetEnumerator();
208+
209+
enumerator.MoveNext().ShouldBeTrue(); enumerator.Current.ShouldBe("a");
210+
enumerator.MoveNext().ShouldBeTrue(); enumerator.Current.ShouldBe(null);
211+
enumerator.MoveNext().ShouldBeTrue(); enumerator.Current.ShouldBe("b");
212+
enumerator.MoveNext().ShouldBeFalse();
213+
enumerator.MoveNext().ShouldBeFalse(); // still false
214+
}
215+
216+
[Test]
217+
public void GetEnumerator_ExplicitGeneric()
218+
{
219+
var queue = new PredicateQueue<string?>(["a", null, "b"]);
220+
221+
using var enumerator = ((IEnumerable<string?>) queue).GetEnumerator();
222+
223+
enumerator.MoveNext().ShouldBeTrue(); enumerator.Current.ShouldBe("a");
224+
enumerator.MoveNext().ShouldBeTrue(); enumerator.Current.ShouldBe(null);
225+
enumerator.MoveNext().ShouldBeTrue(); enumerator.Current.ShouldBe("b");
226+
enumerator.MoveNext().ShouldBeFalse();
227+
}
228+
229+
[Test]
230+
public void GetEnumerator_ExplicitNonGeneric()
231+
{
232+
var queue = new PredicateQueue<string?>(["a", null, "b"]);
233+
234+
var enumerator = ((IEnumerable) queue).GetEnumerator();
235+
236+
enumerator.MoveNext().ShouldBeTrue(); enumerator.Current.ShouldBe("a");
237+
enumerator.MoveNext().ShouldBeTrue(); enumerator.Current.ShouldBe(null);
238+
enumerator.MoveNext().ShouldBeTrue(); enumerator.Current.ShouldBe("b");
239+
enumerator.MoveNext().ShouldBeFalse();
240+
}
241+
}
242+
243+
[TestFixture]
244+
public class PredicateQueueEnumeratorTests
245+
{
246+
[Test]
247+
public void Current_BeforeFirst()
248+
{
249+
var queue = new PredicateQueue<string>(["a"]);
250+
251+
using var enumerator = queue.GetEnumerator();
252+
253+
Should.Throw<InvalidOperationException>(() => { _ = enumerator.Current; });
254+
}
255+
256+
[Test]
257+
public void Current_AfterLast()
258+
{
259+
var queue = new PredicateQueue<string>(["a"]);
260+
261+
using var enumerator = queue.GetEnumerator();
262+
263+
enumerator.MoveNext();
264+
enumerator.MoveNext(); // Move past end
265+
266+
Should.Throw<InvalidOperationException>(() => { _ = enumerator.Current; });
267+
}
268+
269+
[Test]
270+
public void Current_NonGeneric_Ok()
271+
{
272+
var queue = new PredicateQueue<string>(["a"]);
273+
274+
using var enumerator = queue.GetEnumerator();
275+
276+
enumerator.MoveNext();
277+
278+
((IEnumerator) enumerator).Current.ShouldBe("a");
279+
}
280+
281+
[Test]
282+
public void Current_NonGeneric_BeforeFirst()
283+
{
284+
var queue = new PredicateQueue<string>(["a"]);
285+
286+
using var enumerator = queue.GetEnumerator();
287+
288+
Should.Throw<InvalidOperationException>(() => { _ = ((IEnumerator) enumerator).Current; });
289+
}
290+
291+
[Test]
292+
public void Current_NonGeneric_AfterLast()
293+
{
294+
var queue = new PredicateQueue<string>(["a"]);
295+
296+
using var enumerator = queue.GetEnumerator();
297+
298+
enumerator.MoveNext();
299+
enumerator.MoveNext(); // Move past end
300+
301+
Should.Throw<InvalidOperationException>(() => { _ = ((IEnumerator) enumerator).Current; });
302+
}
303+
304+
[Test]
305+
public void Reset()
306+
{
307+
var queue = new PredicateQueue<string>(["a", "b"]);
308+
309+
using var enumerator = queue.GetEnumerator();
310+
311+
enumerator.MoveNext();
312+
enumerator.MoveNext();
313+
enumerator.Reset();
314+
enumerator.MoveNext().ShouldBeTrue(); enumerator.Current.ShouldBe("a");
315+
enumerator.MoveNext().ShouldBeTrue(); enumerator.Current.ShouldBe("b");
316+
enumerator.MoveNext().ShouldBeFalse();
317+
}
318+
319+
[Test]
320+
public void Dispose_Multiple()
321+
{
322+
var queue = new PredicateQueue<string>(["a"]);
323+
324+
using var enumerator = queue.GetEnumerator();
325+
326+
enumerator.MoveNext();
327+
enumerator.Dispose();
328+
// disposed again here
329+
}
330+
}

DependencyQueue/Errors.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ internal static Exception ArgumentOutOfRange(string name)
2323
internal static Exception ObjectDisposed(string? name)
2424
=> new ObjectDisposedException(name);
2525

26+
internal static Exception CollectionEmpty()
27+
=> new InvalidOperationException("The collection is empty.");
28+
29+
internal static Exception EnumeratorNoCurrentItem()
30+
=> new InvalidOperationException(
31+
"The enumerator is positioned before the first element " +
32+
"of the collection or after the last element."
33+
);
34+
2635
internal static Exception NoCurrentEntry()
2736
=> new InvalidOperationException(
2837
"The builder does not have a current entry. " +

0 commit comments

Comments
 (0)