Skip to content

Commit 4e6a035

Browse files
committed
Respond to comment
1 parent 84b853b commit 4e6a035

File tree

1 file changed

+69
-0
lines changed

1 file changed

+69
-0
lines changed

_posts/2025-08-04-testing-races-with-a-synchronizing-decorator.html

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,4 +259,73 @@ <h2 id="comments-header">
259259
</div>
260260
<div class="comment-date">2025-09-10 7:26 UTC</div>
261261
</div>
262+
263+
<div class="comment" id="4a1c7151b80b429aa27386ff2332ca29">
264+
<div class="comment-author"><a href="/">Mark Seemann</a> <a href="#4a1c7151b80b429aa27386ff2332ca29">#</a></div>
265+
<div class="comment-content">
266+
<p>
267+
Thank you for the example code. Obviously, you've made some assumptions about how this particular code base works, but often the devil is in the details, so when I finally had a bit of time on my hands, I decided to try out the feature on the real code base. After a bit of back and forth, I wrote this test:
268+
</p>
269+
<p>
270+
<pre>[<span style="color:#2b91af;">Fact</span>]
271+
<span style="color:blue;">public</span>&nbsp;<span style="color:blue;">void</span>&nbsp;<span style="font-weight:bold;color:#74531f;">OverbookingAttemptsSerialize</span>()
272+
{
273+
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">now</span>&nbsp;=&nbsp;<span style="color:#2b91af;">DateTimeOffset</span>.UtcNow;
274+
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#2b91af;">Gen</span>.<span style="color:#74531f;">Const</span>(()&nbsp;=&gt;&nbsp;(<span style="color:blue;">new</span>&nbsp;<span style="color:#2b91af;">RestaurantService</span>(),&nbsp;<span style="color:blue;">new</span>&nbsp;<span style="color:#2b91af;">ConcurrentBag</span>&lt;<span style="color:blue;">bool</span>&gt;()))
275+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">SampleParallel</span>(
276+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#2b91af;">Gen</span>.Int[1,&nbsp;10]
277+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">Operation</span>&lt;(<span style="color:#2b91af;">RestaurantService</span>,&nbsp;<span style="color:#2b91af;">ConcurrentBag</span>&lt;<span style="color:blue;">bool</span>&gt;)&gt;(
278+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">async</span>&nbsp;(<span style="font-weight:bold;color:#1f377f;">t</span>,&nbsp;<span style="font-weight:bold;color:#1f377f;">q</span>)&nbsp;=&gt;
279+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
280+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">var</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">res</span>&nbsp;=&nbsp;<span style="font-weight:bold;color:#8f08c4;">await</span>&nbsp;<span style="font-weight:bold;color:#1f377f;">t</span>.Item1.<span style="font-weight:bold;color:#74531f;">PostReservation</span>(
281+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:blue;">new</span>&nbsp;<span style="color:#2b91af;">ReservationDtoBuilder</span>()
282+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">WithDate</span>(<span style="font-weight:bold;color:#1f377f;">now</span>.<span style="font-weight:bold;color:#74531f;">AddDays</span>(1).Date.<span style="font-weight:bold;color:#74531f;">AddHours</span>(18.5))
283+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">WithQuantity</span>(<span style="font-weight:bold;color:#1f377f;">q</span>)
284+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.<span style="font-weight:bold;color:#74531f;">Build</span>());
285+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">t</span>.Item2.<span style="font-weight:bold;color:#74531f;">Add</span>(<span style="font-weight:bold;color:#1f377f;">res</span>.IsSuccessStatusCode);
286+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}),
287+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-weight:bold;color:#1f377f;">equal</span>:&nbsp;(<span style="font-weight:bold;color:#1f377f;">t1</span>,&nbsp;<span style="font-weight:bold;color:#1f377f;">t2</span>)&nbsp;=&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">t1</span>.Item2.<span style="font-weight:bold;color:#74531f;">SequenceEqual</span>(<span style="font-weight:bold;color:#1f377f;">t2</span>.Item2));
288+
}</pre>
289+
</p>
290+
<p>
291+
An important part of the Restaurant REST API design is that clients receive correct responses when things go wrong. That's the motivation for including a <code>ConcurrentBag</code> as part of the state, recording whether the response indicates success or failure.
292+
</p>
293+
<p>
294+
To my disappointment, even when the race bug is absent (i.e. when transaction control is present in the service), this test fails. Here's a typical output from a failing test:
295+
</p>
296+
<p>
297+
<pre>Message: 
298+
&nbsp;&nbsp;&nbsp;&nbsp;CsCheck.CsCheckException&nbsp;:&nbsp;Set&nbsp;seed:&nbsp;&quot;0005Pq8wWlm1&quot;&nbsp;or&nbsp;-e&nbsp;CsCheck_Seed=0005Pq8wWlm1&nbsp;to&nbsp;reproduce&nbsp;(0&nbsp;shrinks,&nbsp;72&nbsp;skipped,&nbsp;100&nbsp;total).
299+
&nbsp;&nbsp;&nbsp;&nbsp;
300+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Initial&nbsp;state:&nbsp;(Ploeh.Samples.Restaurants.RestApi.SqlIntegrationTests.RestaurantService,&nbsp;{})
301+
&nbsp;&nbsp;&nbsp;&nbsp;Sequential&nbsp;Operations:&nbsp;[]
302+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Parallel&nbsp;Operations:&nbsp;[Op0&nbsp;4,&nbsp;Op0&nbsp;1,&nbsp;Op0&nbsp;4]
303+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;On&nbsp;Threads:&nbsp;[0,&nbsp;1,&nbsp;0]
304+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Final&nbsp;state:&nbsp;(Ploeh.Samples.Restaurants.RestApi.SqlIntegrationTests.RestaurantService,&nbsp;{False,&nbsp;True,&nbsp;True})
305+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Linearized:&nbsp;[Op0&nbsp;4,&nbsp;Op0&nbsp;1,&nbsp;Op0&nbsp;4]&nbsp;-&gt;&nbsp;(Ploeh.Samples.Restaurants.RestApi.SqlIntegrationTests.RestaurantService,&nbsp;{False,&nbsp;False,&nbsp;False})
306+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:&nbsp;[Op0&nbsp;1,&nbsp;Op0&nbsp;4,&nbsp;Op0&nbsp;4]&nbsp;-&gt;&nbsp;(Ploeh.Samples.Restaurants.RestApi.SqlIntegrationTests.RestaurantService,&nbsp;{False,&nbsp;False,&nbsp;False})
307+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:&nbsp;[Op0&nbsp;4,&nbsp;Op0&nbsp;4,&nbsp;Op0&nbsp;1]&nbsp;-&gt;&nbsp;(Ploeh.Samples.Restaurants.RestApi.SqlIntegrationTests.RestaurantService,&nbsp;{False,&nbsp;False,&nbsp;False})
308+
</pre>
309+
</p>
310+
<p>
311+
At first this is surprising, and upon further reflection, it may not be that surprising. Still, there are things that I don't understand.
312+
</p>
313+
<p>
314+
Here's what's initially surprising: Implicit in this test is that the restaurant seats a maximum of 10 people a day. Thus, with 4, 1, and 4 seats being requested, it surprised me that <em>any</em> of these reservations were being declined. Still, all the linearized models have <code>{False,&nbsp;False,&nbsp;False}</code>; in other words, <em>no</em> reservations were accepted.
315+
</p>
316+
<p>
317+
Then I remembered that this test is actually running on a real SQL Server database, and since I'm assuming that CsCheck has been running quite a few scenarios already, it actually makes sense that that particular day is already completely sold out. Does <em>72 skipped</em> indicate that there were 72 prior runs before this one? Or does it mean that CsCheck found a counterexample after 28 tries, and then decided to skip the remaining 72 runs?
318+
</p>
319+
<p>
320+
In any case, even if it only did 27 prior runs, it seems likely that the date is already completely sold out. It then puzzles me that the final state is <code>{False,&nbsp;True,&nbsp;True}</code>. I honestly can't think of a good explanation of that, but perhaps you can?
321+
</p>
322+
<p>
323+
In any case, I still think that I understand why this can't work as written above. The problem with filling up all reservations for a given date is the reason why I originally performed every attempt to provoke the race on a unique date. Is there a way to do that with CsCheck and <code>SampleParallel</code>?
324+
</p>
325+
<p>
326+
Alternatively, one would need a way to tear down the persistent fixture (i.e. the database) after each run. I can't identify an API that enables me to do that, but perhaps I'm missing something. Is that option somehow available?
327+
</p>
328+
</div>
329+
<div class="comment-date">2025-10-09 14:32 UTC</div>
330+
</div>
262331
</div>

0 commit comments

Comments
 (0)