Skip to content

Commit

Permalink
Benchmark test project POC (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmbillie authored Jan 5, 2022
1 parent 1a15a1e commit fe0fe29
Show file tree
Hide file tree
Showing 9 changed files with 246 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ updates:
day: "sunday"
time: "21:00"
timezone: "America/Detroit"
# only allow critical security update PRs
open-pull-requests-limit: 0
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ jobs:
run: |
dotnet test ./test/HotPotato.Integration.Test/HotPotato.Integration.Test.csproj -c Release -l:"JUnit;LogFilePath=$GITHUB_WORKSPACE/test/results/integrationResults.xml" --no-restore --no-build
- name: Run Performance Tests
run: |
dotnet test ./test/HotPotato.Benchmark.Test/HotPotato.Benchmark.Test.csproj -c Release --no-restore --no-build
- name: Run E2E Tests
env:
SpecToken: ${{ secrets.GITHUB_TOKEN }}
Expand Down
11 changes: 10 additions & 1 deletion HotPotato.sln
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HotPotato.TestServ.Test", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HotPotato.Extensions", "src\HotPotato.Extensions\HotPotato.Extensions.csproj", "{FFEDDFCF-DE15-48DD-A7D5-E7375B9B2DAA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotPotato.NUnitHelpers", "src\HotPotato.NUnitHelpers\HotPotato.NUnitHelpers.csproj", "{DB886999-5A93-4BE8-9AEE-A1FDAB136F6C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HotPotato.NUnitHelpers", "src\HotPotato.NUnitHelpers\HotPotato.NUnitHelpers.csproj", "{DB886999-5A93-4BE8-9AEE-A1FDAB136F6C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotPotato.Benchmark.Test", "test\HotPotato.Benchmark.Test\HotPotato.Benchmark.Test.csproj", "{3AEFBAB9-D575-4876-8FA7-F89D4A14B4BE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -123,6 +125,12 @@ Global
{DB886999-5A93-4BE8-9AEE-A1FDAB136F6C}.Docker|Any CPU.Build.0 = Debug|Any CPU
{DB886999-5A93-4BE8-9AEE-A1FDAB136F6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DB886999-5A93-4BE8-9AEE-A1FDAB136F6C}.Release|Any CPU.Build.0 = Release|Any CPU
{3AEFBAB9-D575-4876-8FA7-F89D4A14B4BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3AEFBAB9-D575-4876-8FA7-F89D4A14B4BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3AEFBAB9-D575-4876-8FA7-F89D4A14B4BE}.Docker|Any CPU.ActiveCfg = Debug|Any CPU
{3AEFBAB9-D575-4876-8FA7-F89D4A14B4BE}.Docker|Any CPU.Build.0 = Debug|Any CPU
{3AEFBAB9-D575-4876-8FA7-F89D4A14B4BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3AEFBAB9-D575-4876-8FA7-F89D4A14B4BE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -135,6 +143,7 @@ Global
{9F37DBB8-2971-4155-BCBD-D82DB5C8D1DC} = {18D73CFF-6ECA-4A0C-8A6A-97F84BC3E1C8}
{573BE9CC-36D6-40C9-A005-2DAB411861BA} = {18D73CFF-6ECA-4A0C-8A6A-97F84BC3E1C8}
{06E2B4CF-A84A-4E9A-91F8-F2794F5B7B8A} = {18D73CFF-6ECA-4A0C-8A6A-97F84BC3E1C8}
{3AEFBAB9-D575-4876-8FA7-F89D4A14B4BE} = {18D73CFF-6ECA-4A0C-8A6A-97F84BC3E1C8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2836D304-9C78-41F8-9A22-517D86241D2C}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Newtonsoft.Json.Linq;
using NJsonSchema;
using System.Collections.Generic;
using System.Linq;

namespace HotPotato.OpenApi.Validators
{
Expand All @@ -18,9 +19,11 @@ public static List<ValidationError> ValidateUndefinedProperties(this JsonSchema

if (schemaProperties != null)
{
//responses should always come back with lowercase properties, but I still wanted to add this and the childToken.Path.ToLower() just in case
List<string> schemaPropertyKeys = schemaProperties.Keys.Select(x => x.ToLower()).ToList();
foreach (JToken childToken in childTokens)
{
if (!schemaProperties.ContainsKey(childToken.Path))
if (!schemaPropertyKeys.Contains(childToken.Path.ToLower()))
{
validationErrors.Add(new ValidationError($"Property not found in spec: {childToken.Path}", ValidationErrorKind.PropertyNotInSpec, childToken.Path, 0, 0));
}
Expand Down
37 changes: 37 additions & 0 deletions test/HotPotato.Benchmark.Test/HotPotato.Benchmark.Test.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="NBench" Version="2.0.1" />
<PackageReference Include="NSwag.Core" Version="13.13.2" />
<PackageReference Include="NSwag.Core.Yaml" Version="13.13.2" />
<PackageReference Include="Pro.NBench.xUnit" Version="2.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\HotPotato.OpenApi.Test\HotPotato.OpenApi.Test.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="PerfSpec.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
57 changes: 57 additions & 0 deletions test/HotPotato.Benchmark.Test/MatcherBenchmarkTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using HotPotato.OpenApi.Matchers;
using NBench;
using Pro.NBench.xUnit.XunitExtensions;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Xunit;
using Xunit.Abstractions;

namespace HotPotato.Benchmark.Test
{
//xUnit thinks that the PerfSetup should be a theory, so the warning needs to be disabled
#pragma warning disable xUnit1013
public class MatcherBenchmarkTest
{
private Counter _counter;

//consider scalable lengths for path strings
private const string AValidPath = "/foo/bar";
private readonly List<string> ValidPaths = new List<string>
{
"/a/b/c",
"/a/b",
"/foo/bar",
"/d/c"
};

public MatcherBenchmarkTest(ITestOutputHelper output)
{
Trace.Listeners.Clear();
Trace.Listeners.Add(new XunitTraceListener(output));
}

[PerfSetup]
public void Setup(BenchmarkContext context)
{
_counter = context.GetCounter("Iterations");
}

/// <summary>
/// Ensure that we can match paths at least 500,000 times per second (5ms).
/// </summary>
[NBenchFact]
[PerfBenchmark(
Description = "Ensure matching doesn't take too long",
NumberOfIterations = 3,
RunTimeMilliseconds = 1000,
RunMode = RunMode.Throughput,
TestMode = TestMode.Test)]
[CounterThroughputAssertion("Iterations", MustBe.GreaterThan, 500000)]
public void PathMatcher_Match_Benchmark()
{
PathMatcher.Match(AValidPath, ValidPaths);
_counter.Increment();
}
}
}
59 changes: 59 additions & 0 deletions test/HotPotato.Benchmark.Test/PerfSpec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
openapi: '3.0.1'
info:
version: 1.0.0
title: Truncated Raw Potato Spec for Performance Testing
paths:
/order:
post:
summary: Add a new order
requestBody:
description: Creating a new order with all properties
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
responses:
'201':
description: Order created successfully
content:
application/json:
schema:
$ref: '#/components/schemas/Order'

components:
schemas:
Order:
required:
- id
- price
- items
properties:
id:
type: integer
price:
type: number
nullable: true
items:
type:
schema:
$ref: '#/components/schemas/ItemsList'
Item:
required:
- itemId
- name
- price
properties:
itemId:
type: integer
name:
type: string
price:
type: number
nullable: true
ItemsList:
type: array
items:
type:
schema:
$ref: '#/components/schemas/Item'
73 changes: 73 additions & 0 deletions test/HotPotato.Benchmark.Test/ValidatorBenchmarkTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using HotPotato.Core.Http;
using HotPotato.OpenApi.Results;
using HotPotato.OpenApi.Validators;
using HotPotato.OpenApi.SpecificationProvider;
using Moq;
using NBench;
using Newtonsoft.Json;
using NSwag;
using Pro.NBench.xUnit.XunitExtensions;
using System;
using System.IO;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Diagnostics;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;

namespace HotPotato.Benchmark.Test
{
//xUnit thinks that the PerfSetup should be a theory, so the warning needs to be disabled
#pragma warning disable xUnit1013
public class ValidatorBenchmarkTest
{
private Counter _counter;
private IValidationStrategy validationStrategy;

public ValidatorBenchmarkTest(ITestOutputHelper output)
{
ResultCollector resultCollector = new ResultCollector();

string specPath = Path.Combine(Environment.CurrentDirectory, "PerfSpec.yaml");
//stub this out on SpecificationProvider
OpenApiDocument spec = OpenApiYamlDocument.FromFileAsync(specPath).Result;
Mock<ISpecificationProvider> mockSpecPro = new Mock<ISpecificationProvider>();
mockSpecPro.Setup(x => x.GetSpecDocument()).Returns(spec);

validationStrategy = new ValidationBuilder(resultCollector, mockSpecPro.Object)
.WithPath("/order")
.WithMethod(HttpMethod.Post)
.WithStatusCode(HttpStatusCode.Created)
.WithBody("{\"id\":5,\"price\":10.0,\"items\":[]}", new HttpContentType("application/json"))
.Build();

Trace.Listeners.Clear();
Trace.Listeners.Add(new XunitTraceListener(output));
}

[PerfSetup]
public void Setup(BenchmarkContext context)
{
_counter = context.GetCounter("Iterations");
}

/// <summary>
/// Ensure that we can validate at least 10,000 times per second.
/// </summary>
[NBenchFact]
[PerfBenchmark(
Description = "Ensure validation doesn't take too long",
NumberOfIterations = 3,
RunTimeMilliseconds = 1000,
RunMode = RunMode.Throughput,
TestMode = TestMode.Test)]
[CounterThroughputAssertion("Iterations", MustBe.GreaterThan, 10000)]
public void ValidationStrategy_Validate_Benchmark()
{
validationStrategy.Validate();
_counter.Increment();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ public class PathValidatorTest
private const string AValidEndpoint = "https://api.hyland.com/workflow/life-cycles";
[Theory]
[InlineData("/workflow/life-cycles")]
[InlineData("/workflow/life-cycles/")]
public void PathValidator_GeneratesPathItem(string path)
{
OpenApiDocument swagDoc = new OpenApiDocument();
Expand Down

0 comments on commit fe0fe29

Please sign in to comment.