Skip to content

Commit

Permalink
Fix entity unit test (CallFaultyEntity) (#2408)
Browse files Browse the repository at this point in the history
* reduce timing-dependence of entity unit test (CallFaultyEntity) to reduce spurious test failures

* fix whitespace that breaks build
  • Loading branch information
sebastianburckhardt authored Mar 2, 2023
1 parent 57bd9a3 commit ee2fba7
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 23 deletions.
7 changes: 7 additions & 0 deletions test/Common/TestEntityClasses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ public static Task FaultyEntityFunction([EntityTrigger] IDurableEntityContext co
context.DeleteState();
return Task.CompletedTask;
}
else if (context.OperationName == "delay")
{
return Task.Delay(TimeSpan.FromSeconds(context.GetInput<int>()));
}

return context.DispatchAsync<FaultyEntity>();
}
Expand All @@ -177,6 +181,9 @@ public static Task FaultyEntityFunctionWithoutDispatch([EntityTrigger] IDurableE
context.DeleteState();
break;

case "delay":
return Task.Delay(TimeSpan.FromSeconds(context.GetInput<int>()));

case "Get":
if (!context.HasState)
{
Expand Down
76 changes: 53 additions & 23 deletions test/Common/TestOrchestrations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -936,10 +936,31 @@ async Task ExpectException(Task t)
Assert.True(await ctx.CallEntityAsync<bool>(entityId, "exists"));
}

// send batch
ctx.SignalEntity(entityId, "Set", 42);
ctx.SignalEntity(entityId, "SetThenThrow", 333);
ctx.SignalEntity(entityId, "DeleteThenThrow");
// we use this utility function to try to enforce that a bunch of signals is delivered as a single batch.
// This is required for some of the tests here to work, since the batching affects the entity state management.
// The "enforcement" mechanism we use is not 100% failsafe (it still makes timing assumptions about the provider)
// but it should be more reliable than the original version of this test which failed quite frequently, as it was
// simply assuming that signals that are sent at the same time are always processed as a batch.
async Task ProcessAllSignalsInSingleBatch(Action sendSignals)
{
// first issue a signal that, when delivered, keeps the entity busy for a second
ctx.SignalEntity(entityId, "delay", 1);

// we now need to yield briefly so that the delay signal is sent before the others
await ctx.CreateTimer(ctx.CurrentUtcDateTime + TimeSpan.FromMilliseconds(1), CancellationToken.None);

// now send the signals in the batch. These should all arrive and get queued (inside the storage provider)
// while the entity is executing the delay operation. Therefore, after the delay operation finishes,
// all of the signals are processed in a single batch.
sendSignals();
}

await ProcessAllSignalsInSingleBatch(() =>
{
ctx.SignalEntity(entityId, "Set", 42);
ctx.SignalEntity(entityId, "SetThenThrow", 333);
ctx.SignalEntity(entityId, "DeleteThenThrow");
});

if (rollbackOnException)
{
Expand All @@ -951,12 +972,14 @@ async Task ExpectException(Task t)
ctx.SignalEntity(entityId, "Set", 42);
}

// send batch
ctx.SignalEntity(entityId, "Get");
ctx.SignalEntity(entityId, "Set", 42);
ctx.SignalEntity(entityId, "Delete");
ctx.SignalEntity(entityId, "Set", 43);
ctx.SignalEntity(entityId, "DeleteThenThrow");
await ProcessAllSignalsInSingleBatch(() =>
{
ctx.SignalEntity(entityId, "Get");
ctx.SignalEntity(entityId, "Set", 42);
ctx.SignalEntity(entityId, "Delete");
ctx.SignalEntity(entityId, "Set", 43);
ctx.SignalEntity(entityId, "DeleteThenThrow");
});

if (rollbackOnException)
{
Expand All @@ -967,9 +990,11 @@ async Task ExpectException(Task t)
Assert.False(await ctx.CallEntityAsync<bool>(entityId, "exists"));
}

// send batch
ctx.SignalEntity(entityId, "Set", 55);
ctx.SignalEntity(entityId, "SetToUnserializable");
await ProcessAllSignalsInSingleBatch(() =>
{
ctx.SignalEntity(entityId, "Set", 55);
ctx.SignalEntity(entityId, "SetToUnserializable");
});

if (rollbackOnException)
{
Expand All @@ -981,11 +1006,13 @@ async Task ExpectException(Task t)
await ctx.CallEntityAsync<bool>(entityId, "deletewithoutreading");
}

// send batch
ctx.SignalEntity(entityId, "Set", 56);
ctx.SignalEntity(entityId, "SetToUnDeserializable");
ctx.SignalEntity(entityId, "Set", 12);
ctx.SignalEntity(entityId, "SetThenThrow", 999);
await ProcessAllSignalsInSingleBatch(() =>
{
ctx.SignalEntity(entityId, "Set", 56);
ctx.SignalEntity(entityId, "SetToUnDeserializable");
ctx.SignalEntity(entityId, "Set", 12);
ctx.SignalEntity(entityId, "SetThenThrow", 999);
});

if (rollbackOnException)
{
Expand All @@ -999,11 +1026,14 @@ async Task ExpectException(Task t)

await ctx.CallEntityAsync<bool>(entityId, "deletewithoutreading");

ctx.SignalEntity(entityId, "Set", 1);
ctx.SignalEntity(entityId, "Delete");
ctx.SignalEntity(entityId, "Set", 2);
ctx.SignalEntity(entityId, "Delete");
ctx.SignalEntity(entityId, "SetThenThrow", 3);
await ProcessAllSignalsInSingleBatch(() =>
{
ctx.SignalEntity(entityId, "Set", 1);
ctx.SignalEntity(entityId, "Delete");
ctx.SignalEntity(entityId, "Set", 2);
ctx.SignalEntity(entityId, "Delete");
ctx.SignalEntity(entityId, "SetThenThrow", 3);
});

if (rollbackOnException)
{
Expand Down

0 comments on commit ee2fba7

Please sign in to comment.