Skip to content

Commit

Permalink
Bug/630 Enforce more deterministic timing on unit/integration tests u…
Browse files Browse the repository at this point in the history
…sing `WaitableLoRaRequest` (Azure#635)

* Use waitable request factory method from integration tests

* Move CreateWaitableRequest factory method to WaitableLoRaRequest class

* Make WaitableLoRaRequest constructor private

* Simplify WaitableLoRaRequest initialization

* Enforce deterministic timing for more tests

* Use factory method for WaitableLoRaRequest payload constructor

* Simplify WaitableLoRaRequest start time handling

* Pass default PacketForwarder in disabled test

* Document WaitableLoRaRequest factory methods

* Throw exception if region not found
  • Loading branch information
bastbu authored Oct 26, 2021
1 parent f4dc4f3 commit 85d483a
Show file tree
Hide file tree
Showing 12 changed files with 129 additions and 103 deletions.
31 changes: 10 additions & 21 deletions Tests/Common/MessageProcessorTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@ namespace LoRaWan.Tests.Common
using System.Threading.Tasks;
using LoRaTools.ADR;
using LoRaTools.LoRaPhysical;
using LoRaTools.Regions;
using LoRaWan.NetworkServer;
using LoRaWan.NetworkServer.ADR;
using Microsoft.Azure.Devices.Client;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

public class MessageProcessorTestBase : IDisposable
{
Expand Down Expand Up @@ -106,25 +104,16 @@ public LoRaDevice CreateLoRaDevice(SimulatedDevice simulatedDevice)
return device;
}

public WaitableLoRaRequest CreateWaitableRequest(Rxpk rxpk,
IPacketForwarder packetForwarder = null,
TimeSpan? startTimeOffset = null,
TimeSpan? constantElapsedTime = null,
bool useRealTimer = false)
{
var requestStartTime = startTimeOffset.HasValue ? DateTime.UtcNow.Subtract(startTimeOffset.Value) : DateTime.UtcNow;
var request = new WaitableLoRaRequest(rxpk, packetForwarder ?? PacketForwarder, requestStartTime);

if (!useRealTimer)
{
constantElapsedTime ??= TimeSpan.Zero;
Assert.True(RegionManager.TryResolveRegion(rxpk, out var region));
var timeWatcher = new TestLoRaOperationTimeWatcher(region, constantElapsedTime.Value);
request.UseTimeWatcher(timeWatcher);
}

return request;
}
protected WaitableLoRaRequest CreateWaitableRequest(Rxpk rxpk,
IPacketForwarder packetForwarder = null,
TimeSpan? startTimeOffset = null,
TimeSpan? constantElapsedTime = null,
bool useRealTimer = false) =>
WaitableLoRaRequest.Create(rxpk,
packetForwarder ?? PacketForwarder,
startTimeOffset,
constantElapsedTime,
useRealTimer);

protected virtual void Dispose(bool disposing)
{
Expand Down
53 changes: 41 additions & 12 deletions Tests/Common/WaitableLoRaRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@
namespace LoRaWan.Tests.Common
{
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using LoRaTools.LoRaMessage;
using LoRaTools.LoRaPhysical;
using LoRaTools.Regions;
using LoRaWan.NetworkServer;

public sealed class WaitableLoRaRequest : LoRaRequest, IDisposable
{
private readonly SemaphoreSlim complete;
private readonly SemaphoreSlim complete = new SemaphoreSlim(0);

public bool ProcessingFailed { get; private set; }

Expand All @@ -22,21 +24,48 @@ public sealed class WaitableLoRaRequest : LoRaRequest, IDisposable

public bool ProcessingSucceeded { get; private set; }

public WaitableLoRaRequest(LoRaPayloadData payload)
private WaitableLoRaRequest(LoRaPayloadData payload)
: base(payload)
{
this.complete = new SemaphoreSlim(0);
}

public WaitableLoRaRequest(Rxpk rxpk, IPacketForwarder packetForwarder)
: this(rxpk, packetForwarder, DateTime.UtcNow)
{
}
{ }

public WaitableLoRaRequest(Rxpk rxpk, IPacketForwarder packetForwarder, DateTime startTime)
private WaitableLoRaRequest(Rxpk rxpk, IPacketForwarder packetForwarder, DateTime startTime)
: base(rxpk, packetForwarder, startTime)
{ }

/// <summary>
/// Creates a WaitableLoRaRequest using a real time watcher.
/// </summary>
public static WaitableLoRaRequest Create(LoRaPayloadData payload) =>
new WaitableLoRaRequest(payload);

/// <summary>
/// Creates a WaitableLoRaRequest that uses a deterministic time handler.
/// </summary>
/// <param name="rxpk">Rxpk instance.</param>
/// <param name="packetForwarder">PacketForwarder instance.</param>
/// <param name="startTimeOffset">Is subtracted from the current time to determine the start time for the deterministic time watcher. Default is TimeSpan.Zero.</param>
/// <param name="constantElapsedTime">Controls how much time is elapsed when querying the time watcher. Default is TimeSpan.Zero.</param>
/// <param name="useRealTimer">Allows you to opt-in to use a real, non-deterministic time watcher.</param>
public static WaitableLoRaRequest Create(Rxpk rxpk,
IPacketForwarder packetForwarder = null,
TimeSpan? startTimeOffset = null,
TimeSpan? constantElapsedTime = null,
bool useRealTimer = false)
{
this.complete = new SemaphoreSlim(0);
var request = new WaitableLoRaRequest(rxpk,
packetForwarder ?? new TestPacketForwarder(),
DateTime.UtcNow.Subtract(startTimeOffset ?? TimeSpan.Zero));

if (!useRealTimer)
{
constantElapsedTime ??= TimeSpan.Zero;
if (!RegionManager.TryResolveRegion(rxpk, out var region))
throw new InvalidOperationException("Could not resolve region.");
var timeWatcher = new TestLoRaOperationTimeWatcher(region, constantElapsedTime.Value);
request.UseTimeWatcher(timeWatcher);
}

return request;
}

public override void NotifyFailed(string deviceId, LoRaDeviceRequestFailedReason reason, Exception exception = null)
Expand Down
20 changes: 10 additions & 10 deletions Tests/Integration/DecoderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public async Task When_No_Decoder_Is_Defined_Sends_Raw_Payload(string deviceGate
// sends unconfirmed message
var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage(msgPayload, fcnt: 1);
var rxpk = unconfirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0];
using var request = new WaitableLoRaRequest(rxpk, PacketForwarder);
using var request = CreateWaitableRequest(rxpk);
messageDispatcher.DispatchRequest(request);
Assert.True(await request.WaitCompleteAsync());
Assert.Null(request.ResponseDownlink);
Expand Down Expand Up @@ -178,7 +178,7 @@ public async Task When_Using_DecoderValueSensor_Should_Send_Decoded_Value(string
// sends unconfirmed message
var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage(msgPayload, fcnt: 1);
var rxpk = unconfirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0];
using var request = new WaitableLoRaRequest(rxpk, PacketForwarder);
using var request = CreateWaitableRequest(rxpk);
messageDispatcher.DispatchRequest(request);
Assert.True(await request.WaitCompleteAsync());

Expand Down Expand Up @@ -264,7 +264,7 @@ public async Task When_Rxpk_Has_Additional_Information_Should_Include_In_Telemet
var rxpk = unconfirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0];
rxpk.ExtraData["x_power"] = 22.3;
rxpk.ExtraData["x_wind"] = "NE";
using var request = new WaitableLoRaRequest(rxpk, PacketForwarder);
using var request = CreateWaitableRequest(rxpk);
messageDispatcher.DispatchRequest(request);
Assert.True(await request.WaitCompleteAsync());

Expand Down Expand Up @@ -355,7 +355,7 @@ public async Task When_Using_Custom_Decoder_Returns_String_Should_Send_Decoded_V
// sends unconfirmed message
var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage("1", fcnt: 1);
var rxpk = unconfirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0];
using var request = new WaitableLoRaRequest(rxpk, PacketForwarder);
using var request = CreateWaitableRequest(rxpk);
messageDispatcher.DispatchRequest(request);
Assert.True(await request.WaitCompleteAsync());

Expand Down Expand Up @@ -441,7 +441,7 @@ public async Task When_Using_Custom_Decoder_Returns_Empty_Should_Send_Decoded_Va
// sends unconfirmed message
var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage("1", fcnt: 1);
var rxpk = unconfirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0];
using var request = new WaitableLoRaRequest(rxpk, PacketForwarder);
using var request = CreateWaitableRequest(rxpk);
messageDispatcher.DispatchRequest(request);
Assert.True(await request.WaitCompleteAsync());

Expand Down Expand Up @@ -529,7 +529,7 @@ public async Task When_Using_Custom_Decoder_Returns_JsonString_Should_Send_Decod
// sends unconfirmed message
var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage("1", fcnt: 1);
var rxpk = unconfirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0];
using var request = new WaitableLoRaRequest(rxpk, PacketForwarder);
using var request = CreateWaitableRequest(rxpk);
messageDispatcher.DispatchRequest(request);
Assert.True(await request.WaitCompleteAsync());

Expand Down Expand Up @@ -619,7 +619,7 @@ public async Task When_Using_Custom_Decoder_Returns_Complex_Object_Should_Send_D
// sends unconfirmed message
var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage("1", fcnt: 1);
var rxpk = unconfirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0];
using var request = new WaitableLoRaRequest(rxpk, PacketForwarder);
using var request = CreateWaitableRequest(rxpk);
messageDispatcher.DispatchRequest(request);
Assert.True(await request.WaitCompleteAsync());

Expand Down Expand Up @@ -705,7 +705,7 @@ public async Task When_Using_Custom_Fails_Returns_Sets_Error_Information_In_Valu
// sends unconfirmed message
var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage("1", fcnt: 1);
var rxpk = unconfirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0];
using var request = new WaitableLoRaRequest(rxpk, PacketForwarder);
using var request = CreateWaitableRequest(rxpk);
messageDispatcher.DispatchRequest(request);
Assert.True(await request.WaitCompleteAsync());

Expand Down Expand Up @@ -769,7 +769,7 @@ public async Task When_Resent_Message_Using_Custom_Decoder_Returns_Complex_Objec
// sends unconfirmed message
var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage("1", fcnt: 10);
var rxpk = unconfirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0];
using var request = new WaitableLoRaRequest(rxpk, PacketForwarder);
using var request = CreateWaitableRequest(rxpk);
messageDispatcher.DispatchRequest(request);
Assert.True(await request.WaitCompleteAsync());
Assert.NotNull(request.ResponseDownlink);
Expand All @@ -787,7 +787,7 @@ public async Task When_Resent_Message_Using_Custom_Decoder_Returns_Complex_Objec
// sends unconfirmed message
var confirmedMessagePayload = simulatedDevice.CreateConfirmedDataUpMessage("1", fcnt: 10);
var rxpk2 = confirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0];
using var request2 = new WaitableLoRaRequest(rxpk2, PacketForwarder);
using var request2 = CreateWaitableRequest(rxpk2);
messageDispatcher.DispatchRequest(request2);
Assert.True(await request2.WaitCompleteAsync());
Assert.NotNull(request2.ResponseDownlink);
Expand Down
13 changes: 9 additions & 4 deletions Tests/Integration/JoinTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ public async Task Join_And_Send_Unconfirmed_And_Confirmed_Messages(string device
FrameCounterUpdateStrategyProvider);

// Create a join request and join with the device.
using var joinRequest = CreateWaitableRequest(joinRxpk, constantElapsedTime: TimeSpan.FromMilliseconds(300));
using var joinRequest =
CreateWaitableRequest(joinRxpk, constantElapsedTime: TimeSpan.FromMilliseconds(300));
messageProcessor.DispatchRequest(joinRequest);
Assert.True(await joinRequest.WaitCompleteAsync());
Assert.True(joinRequest.ProcessingSucceeded);
Expand Down Expand Up @@ -157,7 +158,9 @@ public async Task Join_And_Send_Unconfirmed_And_Confirmed_Messages(string device

// sends unconfirmed message with a given starting frame counter
var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage("100", fcnt: startingPayloadFcnt);
using var unconfirmedRequest = CreateWaitableRequest(unconfirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0], constantElapsedTime: TimeSpan.FromMilliseconds(300));
using var unconfirmedRequest =
CreateWaitableRequest(unconfirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0],
constantElapsedTime: TimeSpan.FromMilliseconds(300));
messageProcessor.DispatchRequest(unconfirmedRequest);
Assert.True(await unconfirmedRequest.WaitCompleteAsync());
Assert.Null(unconfirmedRequest.ResponseDownlink);
Expand Down Expand Up @@ -529,13 +532,15 @@ public async Task When_Multiple_Joins_Are_Received_Should_Get_Twins_Once(string
FrameCounterUpdateStrategyProvider);

// 1st join request
using var joinRequest1 = CreateWaitableRequest(simulatedDevice.CreateJoinRequest().SerializeUplink(simulatedDevice.AppKey).Rxpk[0]);
using var joinRequest1 =
CreateWaitableRequest(simulatedDevice.CreateJoinRequest().SerializeUplink(simulatedDevice.AppKey).Rxpk[0]);
messageProcessor.DispatchRequest(joinRequest1);

await Task.Delay(100);

// 2nd join request
using var joinRequest2 = CreateWaitableRequest(simulatedDevice.CreateJoinRequest().SerializeUplink(simulatedDevice.AppKey).Rxpk[0]);
using var joinRequest2 =
CreateWaitableRequest(simulatedDevice.CreateJoinRequest().SerializeUplink(simulatedDevice.AppKey).Rxpk[0]);
messageProcessor.DispatchRequest(joinRequest2);

await Task.WhenAll(joinRequest1.WaitCompleteAsync(), joinRequest2.WaitCompleteAsync());
Expand Down
10 changes: 5 additions & 5 deletions Tests/Integration/KeepAliveConnectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public async Task After_ClassA_Sends_Data_Should_Disconnect()
// sends unconfirmed message
var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage("hello");
var rxpk = unconfirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0];
using var request = new WaitableLoRaRequest(rxpk, PacketForwarder);
using var request = CreateWaitableRequest(rxpk);
messageDispatcher.DispatchRequest(request);
Assert.True(await request.WaitCompleteAsync());
Assert.True(request.ProcessingSucceeded);
Expand Down Expand Up @@ -160,7 +160,7 @@ public async Task After_ClassA_Sends_Multiple_Data_Should_Disconnect()
{
var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage(msg.ToString(CultureInfo.InvariantCulture));
var rxpk = unconfirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0];
using var request = new WaitableLoRaRequest(rxpk, PacketForwarder);
using var request = CreateWaitableRequest(rxpk);
messageDispatcher.DispatchRequest(request);
Assert.True(await request.WaitCompleteAsync());
Assert.True(request.ProcessingSucceeded);
Expand Down Expand Up @@ -229,15 +229,15 @@ public async Task After_Disconnecting_Should_Reconnect()
FrameCounterUpdateStrategyProvider);

// sends unconfirmed message #1
using var request1 = new WaitableLoRaRequest(simulatedDevice.CreateUnconfirmedMessageUplink("1").Rxpk[0], PacketForwarder);
using var request1 = CreateWaitableRequest(simulatedDevice.CreateUnconfirmedMessageUplink("1").Rxpk[0]);
messageDispatcher.DispatchRequest(request1);
Assert.True(await request1.WaitCompleteAsync());
Assert.True(request1.ProcessingSucceeded);

await EnsureDisconnectedAsync(disconnectedEvent);

// sends unconfirmed message #2
using var request2 = new WaitableLoRaRequest(simulatedDevice.CreateUnconfirmedMessageUplink("2").Rxpk[0], PacketForwarder);
using var request2 = CreateWaitableRequest(simulatedDevice.CreateUnconfirmedMessageUplink("2").Rxpk[0]);
messageDispatcher.DispatchRequest(request2);
Assert.True(await request2.WaitCompleteAsync());
Assert.True(request2.ProcessingSucceeded);
Expand Down Expand Up @@ -300,7 +300,7 @@ public async Task When_Device_Is_Loaded_Should_Disconnect_After_Sending_Data()
// sends unconfirmed message
var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage("hello");
var rxpk = unconfirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey).Rxpk[0];
using var request = new WaitableLoRaRequest(rxpk, PacketForwarder);
using var request = CreateWaitableRequest(rxpk);
messageDispatcher.DispatchRequest(request);
Assert.True(await request.WaitCompleteAsync());
Assert.True(request.ProcessingSucceeded);
Expand Down
6 changes: 3 additions & 3 deletions Tests/Integration/ParallelProcessingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,15 +208,15 @@ public async Task ABP_Load_And_Receiving_Multiple_Unconfirmed_Should_Send_All_To
var unconfirmedMessage2 = simulatedDevice.CreateUnconfirmedMessageUplink("2", fcnt: 2).Rxpk[0];
var unconfirmedMessage3 = simulatedDevice.CreateUnconfirmedMessageUplink("3", fcnt: 3).Rxpk[0];

var req1 = new WaitableLoRaRequest(unconfirmedMessage1, this.packetForwarder);
var req1 = CreateWaitableRequest(unconfirmedMessage1, this.packetForwarder);
messageDispatcher.DispatchRequest(req1);
await Task.Delay(parallelTestConfiguration.BetweenMessageDuration.Next());

var req2 = new WaitableLoRaRequest(unconfirmedMessage2, this.packetForwarder);
var req2 = CreateWaitableRequest(unconfirmedMessage2, this.packetForwarder);
messageDispatcher.DispatchRequest(req2);
await Task.Delay(parallelTestConfiguration.BetweenMessageDuration.Next());

var req3 = new WaitableLoRaRequest(unconfirmedMessage3, this.packetForwarder);
var req3 = CreateWaitableRequest(unconfirmedMessage3, this.packetForwarder);
messageDispatcher.DispatchRequest(req3);
await Task.Delay(parallelTestConfiguration.BetweenMessageDuration.Next());

Expand Down
Loading

0 comments on commit 85d483a

Please sign in to comment.