mirror of https://github.com/Squidex/squidex.git
7 changed files with 329 additions and 0 deletions
@ -0,0 +1,75 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.ClientLibrary; |
||||
|
using Squidex.ClientLibrary.Management; |
||||
|
|
||||
|
namespace LoadTest |
||||
|
{ |
||||
|
public sealed class ClientQueryFixture : IDisposable |
||||
|
{ |
||||
|
public SquidexClient<TestEntity, TestEntityData> Client { get; } = TestClient.Build(); |
||||
|
|
||||
|
public ClientQueryFixture() |
||||
|
{ |
||||
|
Task.Run(async () => |
||||
|
{ |
||||
|
var apps = TestClient.ClientManager.CreateAppsClient(); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
await apps.PostAppAsync(new CreateAppDto |
||||
|
{ |
||||
|
Name = TestClient.TestAppName |
||||
|
}); |
||||
|
|
||||
|
var schemas = TestClient.ClientManager.CreateSchemasClient(); |
||||
|
|
||||
|
await schemas.PostSchemaAsync(TestClient.TestAppName, new CreateSchemaDto |
||||
|
{ |
||||
|
Name = TestClient.TestSchemaName, |
||||
|
Fields = new List<UpsertSchemaFieldDto> |
||||
|
{ |
||||
|
new UpsertSchemaFieldDto |
||||
|
{ |
||||
|
Name = TestClient.TestSchemaName, |
||||
|
Properties = new NumberFieldPropertiesDto() |
||||
|
} |
||||
|
}, |
||||
|
IsPublished = true |
||||
|
}); |
||||
|
} |
||||
|
catch (SquidexManagementException ex) |
||||
|
{ |
||||
|
if (ex.StatusCode != 400) |
||||
|
{ |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var contents = await Client.GetAllAsync(); |
||||
|
|
||||
|
foreach (var content in contents.Items) |
||||
|
{ |
||||
|
await Client.DeleteAsync(content); |
||||
|
} |
||||
|
|
||||
|
for (var i = 10; i > 0; i--) |
||||
|
{ |
||||
|
await Client.CreateAsync(new TestEntityData { Value = i }, true); |
||||
|
} |
||||
|
}).Wait(); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
<PropertyGroup> |
||||
|
<OutputType>Exe</OutputType> |
||||
|
<TargetFramework>netcoreapp2.2</TargetFramework> |
||||
|
</PropertyGroup> |
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" /> |
||||
|
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> |
||||
|
<PackageReference Include="Squidex.ClientLibrary" Version="3.9.0" /> |
||||
|
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> |
||||
|
<PackageReference Include="xunit" Version="2.4.1" /> |
||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1"> |
||||
|
<PrivateAssets>all</PrivateAssets> |
||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> |
||||
|
</PackageReference> |
||||
|
</ItemGroup> |
||||
|
<PropertyGroup> |
||||
|
<CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet> |
||||
|
</PropertyGroup> |
||||
|
<ItemGroup> |
||||
|
<AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -0,0 +1,25 @@ |
|||||
|
|
||||
|
Microsoft Visual Studio Solution File, Format Version 12.00 |
||||
|
# Visual Studio Version 16 |
||||
|
VisualStudioVersion = 16.0.29123.88 |
||||
|
MinimumVisualStudioVersion = 10.0.40219.1 |
||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoadTest", "LoadTest.csproj", "{EC732B4F-576E-4853-880D-AA676966D017}" |
||||
|
EndProject |
||||
|
Global |
||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution |
||||
|
Debug|Any CPU = Debug|Any CPU |
||||
|
Release|Any CPU = Release|Any CPU |
||||
|
EndGlobalSection |
||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution |
||||
|
{EC732B4F-576E-4853-880D-AA676966D017}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
||||
|
{EC732B4F-576E-4853-880D-AA676966D017}.Debug|Any CPU.Build.0 = Debug|Any CPU |
||||
|
{EC732B4F-576E-4853-880D-AA676966D017}.Release|Any CPU.ActiveCfg = Release|Any CPU |
||||
|
{EC732B4F-576E-4853-880D-AA676966D017}.Release|Any CPU.Build.0 = Release|Any CPU |
||||
|
EndGlobalSection |
||||
|
GlobalSection(SolutionProperties) = preSolution |
||||
|
HideSolutionNode = FALSE |
||||
|
EndGlobalSection |
||||
|
GlobalSection(ExtensibilityGlobals) = postSolution |
||||
|
SolutionGuid = {8ABC073D-D6C6-47F0-AFB6-D42F177D468A} |
||||
|
EndGlobalSection |
||||
|
EndGlobal |
||||
@ -0,0 +1,146 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Concurrent; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics; |
||||
|
using System.Linq; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.ClientLibrary; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace LoadTest |
||||
|
{ |
||||
|
public class QueryBenchmarks : IClassFixture<ClientQueryFixture> |
||||
|
{ |
||||
|
public ClientQueryFixture Fixture { get; } |
||||
|
|
||||
|
public QueryBenchmarks(ClientQueryFixture fixture) |
||||
|
{ |
||||
|
Fixture = fixture; |
||||
|
} |
||||
|
|
||||
|
public static IEnumerable<object[]> Loads() |
||||
|
{ |
||||
|
int[] users = { 1, 5, 10, 20, 50, 100 }; |
||||
|
int[] loads = { 5, 10, 20, 50, 100 }; |
||||
|
|
||||
|
foreach (var user in users) |
||||
|
{ |
||||
|
foreach (var load in loads) |
||||
|
{ |
||||
|
yield return new object[] { user, load }; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(Loads))] |
||||
|
public async Task Should_return_all(int numUsers, int numIterationsPerUser) |
||||
|
{ |
||||
|
await Run(numUsers, numIterationsPerUser, async () => |
||||
|
{ |
||||
|
await Fixture.Client.GetAsync(new ODataQuery { OrderBy = "data/value/iv asc" }); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(Loads))] |
||||
|
public async Task Should_return_items_with_skip(int numUsers, int numIterationsPerUser) |
||||
|
{ |
||||
|
await Run(numUsers, numIterationsPerUser, async () => |
||||
|
{ |
||||
|
await Fixture.Client.GetAsync(new ODataQuery { Skip = 5, OrderBy = "data/value/iv asc" }); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(Loads))] |
||||
|
public async Task Should_return_items_with_skip_and_top(int numUsers, int numIterationsPerUser) |
||||
|
{ |
||||
|
await Run(numUsers, numIterationsPerUser, async () => |
||||
|
{ |
||||
|
await Fixture.Client.GetAsync(new ODataQuery { Skip = 2, Top = 5, OrderBy = "data/value/iv asc" }); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(Loads))] |
||||
|
public async Task Should_return_items_with_ordering(int numUsers, int numIterationsPerUser) |
||||
|
{ |
||||
|
await Run(numUsers, numIterationsPerUser, async () => |
||||
|
{ |
||||
|
await Fixture.Client.GetAsync(new ODataQuery { Skip = 2, Top = 5, OrderBy = "data/value/iv desc" }); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(Loads))] |
||||
|
public async Task Should_return_items_with_filter(int numUsers, int numIterationsPerUser) |
||||
|
{ |
||||
|
await Run(numUsers, numIterationsPerUser, async () => |
||||
|
{ |
||||
|
await Fixture.Client.GetAsync(new ODataQuery { Filter = "data/value/iv gt 3 and data/value/iv lt 7", OrderBy = "data/value/iv asc" }); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private static async Task Run(int numUsers, int numIterationsPerUser, Func<Task> action, int expectedAvg = 100) |
||||
|
{ |
||||
|
var elapsedMs = new ConcurrentBag<long>(); |
||||
|
|
||||
|
var errors = 0; |
||||
|
|
||||
|
async Task RunAsync() |
||||
|
{ |
||||
|
for (var i = 0; i < numIterationsPerUser; i++) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var watch = Stopwatch.StartNew(); |
||||
|
|
||||
|
await action(); |
||||
|
|
||||
|
watch.Stop(); |
||||
|
|
||||
|
elapsedMs.Add(watch.ElapsedMilliseconds); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
Interlocked.Increment(ref errors); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var tasks = new List<Task>(); |
||||
|
|
||||
|
for (var i = 0; i < numUsers; i++) |
||||
|
{ |
||||
|
tasks.Add(Task.Run(RunAsync)); |
||||
|
} |
||||
|
|
||||
|
await Task.WhenAll(tasks); |
||||
|
|
||||
|
var count = elapsedMs.Count; |
||||
|
|
||||
|
var max = elapsedMs.Max(); |
||||
|
var min = elapsedMs.Min(); |
||||
|
|
||||
|
var avg = elapsedMs.Average(); |
||||
|
|
||||
|
Assert.InRange(max, 0, expectedAvg * 10); |
||||
|
Assert.InRange(min, 0, expectedAvg); |
||||
|
|
||||
|
Assert.InRange(avg, 0, expectedAvg); |
||||
|
|
||||
|
Assert.Equal(0, errors); |
||||
|
|
||||
|
Assert.Equal(count, numUsers * numIterationsPerUser); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.ClientLibrary; |
||||
|
|
||||
|
namespace LoadTest |
||||
|
{ |
||||
|
public static class TestClient |
||||
|
{ |
||||
|
public const string ServerUrl = "http://localhost:5000"; |
||||
|
|
||||
|
public const string ClientId = "root"; |
||||
|
public const string ClientSecret = "xeLd6jFxqbXJrfmNLlO2j1apagGGGSyZJhFnIuHp4I0="; |
||||
|
|
||||
|
public const string TestAppName = "integration-tests"; |
||||
|
public const string TestSchemaName = "numbers"; |
||||
|
public const string TestSchemaField = "value"; |
||||
|
|
||||
|
public static readonly SquidexClientManager ClientManager = |
||||
|
new SquidexClientManager("http://localhost:5000", TestAppName, ClientId, ClientSecret) |
||||
|
{ |
||||
|
ReadResponseAsString = true |
||||
|
}; |
||||
|
|
||||
|
public static SquidexClient<TestEntity, TestEntityData> Build() |
||||
|
{ |
||||
|
return ClientManager.GetClient<TestEntity, TestEntityData>(TestSchemaName); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Newtonsoft.Json; |
||||
|
using Squidex.ClientLibrary; |
||||
|
|
||||
|
namespace LoadTest |
||||
|
{ |
||||
|
public sealed class TestEntity : SquidexEntityBase<TestEntityData> |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public sealed class TestEntityData |
||||
|
{ |
||||
|
[JsonConverter(typeof(InvariantConverter))] |
||||
|
public int Value { get; set; } |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue