Skip to content

Commit

Permalink
Add Test Script Infrastructure
Browse files Browse the repository at this point in the history
- Add TechnicMotorTestScript

#161 non-breaking
  • Loading branch information
tthiery committed Apr 3, 2021
1 parent bb2a4e5 commit 8d76a23
Show file tree
Hide file tree
Showing 6 changed files with 344 additions and 1 deletion.
17 changes: 16 additions & 1 deletion powered-up.sln
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31112.23
MinimumVisualStudioVersion = 15.0.26124.0
Expand All @@ -22,6 +22,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpBrick.PoweredUp.Mobile
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpBrick.PoweredUp.BlueGigaBLE", "src\SharpBrick.PoweredUp.BlueGigaBLE\SharpBrick.PoweredUp.BlueGigaBLE.csproj", "{E0DC0096-D7F1-4995-833D-9A6C0C3F8F98}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpBrick.PoweredUp.TestScript", "test\SharpBrick.PoweredUp.TestScript\SharpBrick.PoweredUp.TestScript.csproj", "{EED1D80A-4083-46B3-843F-1B584FDB6862}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -104,6 +106,18 @@ Global
{F66A2B09-84B6-477D-9B15-926E771C7D80}.Release|x64.Build.0 = Release|Any CPU
{F66A2B09-84B6-477D-9B15-926E771C7D80}.Release|x86.ActiveCfg = Release|Any CPU
{F66A2B09-84B6-477D-9B15-926E771C7D80}.Release|x86.Build.0 = Release|Any CPU
{EED1D80A-4083-46B3-843F-1B584FDB6862}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EED1D80A-4083-46B3-843F-1B584FDB6862}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EED1D80A-4083-46B3-843F-1B584FDB6862}.Debug|x64.ActiveCfg = Debug|Any CPU
{EED1D80A-4083-46B3-843F-1B584FDB6862}.Debug|x64.Build.0 = Debug|Any CPU
{EED1D80A-4083-46B3-843F-1B584FDB6862}.Debug|x86.ActiveCfg = Debug|Any CPU
{EED1D80A-4083-46B3-843F-1B584FDB6862}.Debug|x86.Build.0 = Debug|Any CPU
{EED1D80A-4083-46B3-843F-1B584FDB6862}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EED1D80A-4083-46B3-843F-1B584FDB6862}.Release|Any CPU.Build.0 = Release|Any CPU
{EED1D80A-4083-46B3-843F-1B584FDB6862}.Release|x64.ActiveCfg = Release|Any CPU
{EED1D80A-4083-46B3-843F-1B584FDB6862}.Release|x64.Build.0 = Release|Any CPU
{EED1D80A-4083-46B3-843F-1B584FDB6862}.Release|x86.ActiveCfg = Release|Any CPU
{EED1D80A-4083-46B3-843F-1B584FDB6862}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -115,6 +129,7 @@ Global
{D82C23F2-86B4-4847-B2BD-7F194279FD43} = {F4D84196-5E85-41FE-9C39-38BD8F78E84D}
{E2D7D98D-9F20-4761-B507-D379A530E77D} = {62C31C3D-8ACF-4ED3-A3D8-225536F3AC6D}
{F66A2B09-84B6-477D-9B15-926E771C7D80} = {62C31C3D-8ACF-4ED3-A3D8-225536F3AC6D}
{EED1D80A-4083-46B3-843F-1B584FDB6862} = {39B30145-497F-4AEB-A014-BBF27DA0651A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {34AA1641-ACED-43ED-A0DD-BA88E43A67A8}
Expand Down
11 changes: 11 additions & 0 deletions test/SharpBrick.PoweredUp.TestScript/ITestScript.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Threading.Tasks;
using SharpBrick.PoweredUp.Deployment;

namespace SharpBrick.PoweredUp.TestScript
{
public interface ITestScript
{
void DefineDeploymentModel(DeploymentModelBuilder builder);
Task ExecuteScriptAsync(Hub hub, TestScriptExecutionContext context);
}
}
91 changes: 91 additions & 0 deletions test/SharpBrick.PoweredUp.TestScript/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using SharpBrick.PoweredUp.Deployment;
using SharpBrick.PoweredUp.Hubs;

namespace SharpBrick.PoweredUp.TestScript
{
class Program
{
static async Task Main(string[] args)
{
var serviceProvider = new ServiceCollection()
.AddLogging(builder =>
{
builder.AddConsole();
})
.AddPoweredUp()
.AddWinRTBluetooth() // using WinRT Bluetooth on Windows (separate NuGet SharpBrick.PoweredUp.WinRT)
.BuildServiceProvider();

var host = serviceProvider.GetService<PoweredUpHost>();

IEnumerable<ITestScript> scripts = new ITestScript[] {
new TechnicMotorTestScript<TechnicLargeLinearMotor>(),
};

var context = new TestScriptExecutionContext(serviceProvider.GetService<ILogger<TestScriptExecutionContext>>());

foreach (var script in scripts)
{
// Test Script
context.Log.LogInformation($"Execute Script {script.GetType().Name}");

// build deployment model
var model = BbuildDeploymentModel(script);
PrintModel(context, model);

// Accept to execute Test Script
var executeTest = await context.ConfirmAsync("> Confirm to execute Test Script");

if (executeTest)
{
context.Log.LogInformation("> Discovering & Connecting Hub");
var hubType = HubFactory.GetTypeFromSystemType(model.Hubs[0].HubType ?? throw new InvalidOperationException("Specify the hub type in the test script."));
using var hub = await host.DiscoverAsync(hubType);
await hub.ConnectAsync();

context.Log.LogInformation("> Verifying Deployment Model (fix it if necessary)");
await hub.VerifyDeploymentModelAsync(model);

context.Log.LogInformation("> Start Test Script");
await script.ExecuteScriptAsync(hub, context);

context.Log.LogInformation("> Switch Off Hub");
await hub.SwitchOffAsync();
}
else
{
context.Log.LogWarning($"> User decided not to execute Test Script");
}
}

}

private static void PrintModel(TestScriptExecutionContext context, DeploymentModel model)
{
context.Log.LogInformation($"> Deployment Model of Test Script");

foreach (var hub in model.Hubs)
{
context.Log.LogInformation($" > Hub: {hub.HubType}");

foreach (var device in hub.Devices)
{
context.Log.LogInformation($" > Device: {device.DeviceType} @ {device.PortId}");
}
}
}

private static DeploymentModel BbuildDeploymentModel(ITestScript script)
{
var builder = new DeploymentModelBuilder();
script.DefineDeploymentModel(builder);
var model = builder.Build();
return model;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0-windows10.0.19041.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\SharpBrick.PoweredUp\SharpBrick.PoweredUp.csproj" />
<ProjectReference Include="..\..\src\SharpBrick.PoweredUp.WinRT\SharpBrick.PoweredUp.WinRT.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="5.0.0" />
</ItemGroup>

</Project>
161 changes: 161 additions & 0 deletions test/SharpBrick.PoweredUp.TestScript/TechnicMotorTestScript.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using SharpBrick.PoweredUp.Deployment;

namespace SharpBrick.PoweredUp.TestScript
{
public class TechnicMotorTestScript<TTechnicMotor> : ITestScript where TTechnicMotor : AbsoluteMotor, IPoweredUpDevice
{
public void DefineDeploymentModel(DeploymentModelBuilder builder)
=> builder.AddHub<TechnicMediumHub>(hubBuilder =>
{
hubBuilder.AddDevice<TTechnicMotor>(0);
});

public async Task ExecuteScriptAsync(Hub hub, TestScriptExecutionContext context)
{
var motor = hub.Port(0).GetDevice<TTechnicMotor>();

await motor.GotoRealZeroAsync();
await Task.Delay(2000);
await motor.SetZeroAsync();

// TestCase: AbsoluteMotorReportsAbsolutePosition
await motor.TryLockDeviceForCombinedModeNotificationSetupAsync(motor.ModeIndexAbsolutePosition, motor.ModeIndexPosition);
await motor.SetupNotificationAsync(motor.ModeIndexPosition, true, 2);
await motor.SetupNotificationAsync(motor.ModeIndexAbsolutePosition, true, 2);
await motor.UnlockFromCombinedModeNotificationSetupAsync(true);

await context.ConfirmAsync("AbsoluteMotor.GotoRealZeroAsync: Is in zero position? Adjust Beam to 0°?");

await TestCase1_TachoMotorPositionByDegreesAsync(context, motor);

await TestCase2_TachoMotorExplicitPositionAsync(context, motor);

await TestCase3_TachoMotorHighSpeedAndFloatingAsync(context, motor);

await TestCase4_TachoMotorPositiveNegativeSpeedForTimeAsync(context, motor);

await TestCase5_TachoMotorAccelerationAsync(context, motor);

await TestCase6_BasicMotorAsync(context, motor);
}

private static async Task TestCase1_TachoMotorPositionByDegreesAsync(TestScriptExecutionContext context, TTechnicMotor motor)
{
context.Log.LogInformation("TachoMotor: Testing positioning by degress");

await motor.StartSpeedForDegreesAsync(45, 10, 100);
await Task.Delay(2000);
context.Assert(motor.AbsolutePosition, 42, 48);
context.Assert(motor.Position, 42, 48);

await motor.StartSpeedForDegreesAsync(45, 10, 100);
await Task.Delay(2000);
context.Assert(motor.AbsolutePosition, 87, 93);
context.Assert(motor.Position, 87, 93);

await motor.StartSpeedForDegreesAsync(45, 10, 100);
await Task.Delay(2000);
context.Assert(motor.AbsolutePosition, 132, 138);
context.Assert(motor.Position, 132, 138);

await motor.StartSpeedForDegreesAsync(45, 10, 100);
await Task.Delay(2000);
context.Assert(Math.Abs(motor.AbsolutePosition), 177, 180);
context.Assert(motor.Position, 177, 183);

await context.ConfirmAsync("TachoMotor.StartSpeedForDegreesAsync: Has moved 40 times CW each by 45°?");
}

private static async Task TestCase2_TachoMotorExplicitPositionAsync(TestScriptExecutionContext context, TTechnicMotor motor)
{
context.Log.LogInformation("TachoMotor: Testing explicit position from TachoMotor.SetZero");

await motor.GotoPositionAsync(360, 10, 100);
await Task.Delay(2000);
context.Assert(motor.AbsolutePosition, -3, 3);
context.Assert(motor.Position, 357, 363);

await context.ConfirmAsync("TachoMotor.GotoPositionAsync: has moved CW to zero position?");

await motor.GotoPositionAsync(810, 10, 100);
await Task.Delay(4000);
context.Assert(motor.AbsolutePosition, 87, 93);
context.Assert(motor.Position, 807, 813);

await context.ConfirmAsync("TachoMotor.GotoPositionAsync: Motor has moved CW by 360° + 90° and is exaclty 90° off zero?");
}

private static async Task TestCase3_TachoMotorHighSpeedAndFloatingAsync(TestScriptExecutionContext context, TTechnicMotor motor)
{
context.Log.LogInformation("TachoMotor: High different Speed and Floating");

await motor.StartSpeedForDegreesAsync(810, -100, 100, SpecialSpeed.Float);
await Task.Delay(2000);

await context.ConfirmAsync("TachoMotor.StartSpeedForDegreesAsync: High speed CCW turn with floating end state?");

await ResetToZeroAsync(context, motor, 2000);
}

private static async Task TestCase4_TachoMotorPositiveNegativeSpeedForTimeAsync(TestScriptExecutionContext context, TTechnicMotor motor)
{
context.Log.LogInformation("TachoMotor: Positive and Negative Speeds and for Time");

await motor.StartSpeedForTimeAsync(1000, 10, 100);
await Task.Delay(2000);

await context.ConfirmAsync("TachoMotor.StartSpeedForTimeAsync: CW for 1s?");

await motor.StartSpeedForTimeAsync(1000, -10, 100);
await Task.Delay(2000);

await context.ConfirmAsync("TachoMotor.StartSpeedForTimeAsync: CCW for 1s?");

await ResetToZeroAsync(context, motor, 3000);
}

private static async Task TestCase5_TachoMotorAccelerationAsync(TestScriptExecutionContext context, TTechnicMotor motor)
{
context.Log.LogInformation("TachoMotor: Acceleration Profiles");

await motor.SetAccelerationTimeAsync(500);
await motor.SetDecelerationTimeAsync(500);

await motor.StartSpeedForTimeAsync(2000, 50, 100, SpecialSpeed.Brake, SpeedProfiles.AccelerationProfile | SpeedProfiles.DecelerationProfile);
await Task.Delay(4000);

await context.ConfirmAsync("TachoMotor.SetAccelerationTimeAsync: CW 0.5s each slow start and end?");

await ResetToZeroAsync(context, motor, 20_000);
}

private static async Task TestCase6_BasicMotorAsync(TestScriptExecutionContext context, TTechnicMotor motor)
{
context.Log.LogInformation("TachoMotor: Start Speed & Power");

await motor.StartPowerAsync(100);
await Task.Delay(1000);
await motor.StopByBrakeAsync();
await motor.StartPowerAsync(-100);
await Task.Delay(1000);
await motor.StopByFloatAsync();

await context.ConfirmAsync("TachoMotor.StartPowerAsync: CW for 1s, brake, CCW for 1s, floating?");

await ResetToZeroAsync(context, motor, 3000);
}

private static async Task ResetToZeroAsync(TestScriptExecutionContext context, TTechnicMotor motor, int expectedTime)
{
await motor.GotoPositionAsync(0, 10, 100);
await Task.Delay(expectedTime);
context.Assert(motor.AbsolutePosition, -3, 3);
context.Assert(motor.Position, -3, 3);

await context.ConfirmAsync("TachoMotor.GotoPositionAsync: has moved to zero position?");
}
}
}
47 changes: 47 additions & 0 deletions test/SharpBrick.PoweredUp.TestScript/TestScriptExecutionContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

namespace SharpBrick.PoweredUp.TestScript
{
public class TestScriptExecutionContext
{
public TestScriptExecutionContext(ILogger log)
{
Log = log ?? throw new ArgumentNullException(nameof(log));
}

public ILogger Log { get; }
public Hub CurrentHub { get; set; }

public async Task<bool> ConfirmAsync(string question)
{
Console.Write(question + " (Y/n)");

var confirm = Console.ReadLine().Trim().ToUpperInvariant() != "N";

if (confirm)
{
Log.LogInformation($"[OK] by User: {question}");
}
else
{
Log.LogError($"[FAIL] by User {question}");
}

return confirm;
}

public void Assert(int value, int min, int max)
{
if (value >= min && value <= max)
{
Log.LogInformation($"[OK] by Assert within expectations: {min} <= {value} <= {max}");
}
else
{
Log.LogError($"[FAIL] by Assert outside expectations: {min} <= {value} <= {max}");
}
}
}
}

0 comments on commit 8d76a23

Please sign in to comment.