Skip to content
This repository was archived by the owner on May 22, 2025. It is now read-only.

Commit a29464c

Browse files
committed
Squashed 'dotnet-server-sdk-shared-tests/' changes from 3eff003..ea2fb6f
ea2fb6f use 6.x GA release 23ee6a6 update dependency to match what the SDK is using 9d138db use latest prerelease SDK 7bace38 fix target framework 82197f0 update dependencies 478cad3 update to latest prerelease API 7d9d771 Merge branch 'sdk5.x' 32eb2bf update shared test code for SDK 6.0 API (#2) git-subtree-dir: dotnet-server-sdk-shared-tests git-subtree-split: ea2fb6f73c3e98315c6a7bbaf07ea8c2f19dbbc7
1 parent b392c72 commit a29464c

18 files changed

+1212
-612
lines changed

.circleci/config.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ jobs:
1111
steps:
1212
- checkout
1313
- run: dotnet restore
14-
- run: dotnet build LaunchDarkly.ServerSdk.SharedTests -f netstandard2.0
15-
- run: dotnet test LaunchDarkly.ServerSdk.SharedTests.Tests/LaunchDarkly.ServerSdk.SharedTests.Tests.csproj -f netcoreapp2.1
14+
- run: dotnet build LaunchDarkly.ServerSdk.SharedTests
15+
- run: dotnet test LaunchDarkly.ServerSdk.SharedTests.Tests/LaunchDarkly.ServerSdk.SharedTests.Tests.csproj
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System.Threading.Tasks;
2+
using LaunchDarkly.Sdk.Server.Interfaces;
3+
4+
using static LaunchDarkly.Sdk.Server.Interfaces.DataStoreTypes;
5+
6+
namespace LaunchDarkly.Sdk.Server.SharedTests.DataStore
7+
{
8+
// MockAsyncStore is defined as a simple wrapper around MockSyncStore because we're not trying to
9+
// test any real asynchronous functionality in the data store itself; we're just testing that the
10+
// SDK makes the appropriate calls to the IPersistentDataStoreAsync API.
11+
12+
public class MockAsyncStore : IPersistentDataStoreAsync
13+
{
14+
private readonly MockSyncStore _syncStore;
15+
16+
public MockAsyncStore(MockDatabase db, string prefix)
17+
{
18+
_syncStore = new MockSyncStore(db, prefix);
19+
}
20+
21+
public void Dispose() { }
22+
23+
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
24+
25+
public async Task<SerializedItemDescriptor?> GetAsync(DataKind kind, string key) =>
26+
_syncStore.Get(kind, key);
27+
28+
public async Task<KeyedItems<SerializedItemDescriptor>> GetAllAsync(DataKind kind) =>
29+
_syncStore.GetAll(kind);
30+
31+
public async Task InitAsync(FullDataSet<SerializedItemDescriptor> allData) =>
32+
_syncStore.Init(allData);
33+
34+
public async Task<bool> InitializedAsync() => _syncStore.Initialized();
35+
36+
public async Task<bool> IsStoreAvailableAsync() => true;
37+
38+
public async Task<bool> UpsertAsync(DataKind kind, string key, SerializedItemDescriptor item) =>
39+
_syncStore.Upsert(kind, key, item);
40+
41+
#pragma warning restore CS1998
42+
}
43+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System.Collections.Generic;
2+
3+
using static LaunchDarkly.Sdk.Server.Interfaces.DataStoreTypes;
4+
5+
namespace LaunchDarkly.Sdk.Server.SharedTests.DataStore
6+
{
7+
public class MockDatabase
8+
{
9+
public static readonly MockDatabase Instance = new MockDatabase();
10+
11+
public readonly IDictionary<string, Dictionary<DataKind, Dictionary<string, SerializedItemDescriptor>>> _data =
12+
new Dictionary<string, Dictionary<DataKind, Dictionary<string, SerializedItemDescriptor>>>();
13+
14+
public readonly ISet<string> _inited = new HashSet<string>();
15+
16+
private MockDatabase() { }
17+
18+
public Dictionary<DataKind, Dictionary<string, SerializedItemDescriptor>> DataForPrefix(string prefix)
19+
{
20+
if (_data.TryGetValue(prefix ?? "", out var ret))
21+
{
22+
return ret;
23+
}
24+
var d = new Dictionary<DataKind, Dictionary<string, SerializedItemDescriptor>>();
25+
_data[prefix ?? ""] = d;
26+
return d;
27+
}
28+
29+
public Dictionary<string, SerializedItemDescriptor> DataForPrefixAndKind(string prefix, DataKind kind)
30+
{
31+
var dfp = DataForPrefix(prefix);
32+
if (dfp.TryGetValue(kind, out var ret))
33+
{
34+
return ret;
35+
}
36+
var d = new Dictionary<string, SerializedItemDescriptor>();
37+
dfp[kind] = d;
38+
return d;
39+
}
40+
41+
public void Clear(string prefix)
42+
{
43+
_data.Remove(prefix ?? "");
44+
_inited.Remove(prefix ?? "");
45+
}
46+
47+
public bool Inited(string prefix) => _inited.Contains(prefix ?? "");
48+
49+
public void SetInited(string prefix) => _inited.Add(prefix ?? "");
50+
}
51+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System.Collections.Generic;
2+
using LaunchDarkly.Sdk.Server.Interfaces;
3+
4+
using static LaunchDarkly.Sdk.Server.Interfaces.DataStoreTypes;
5+
6+
namespace LaunchDarkly.Sdk.Server.SharedTests.DataStore
7+
{
8+
public class MockSyncStore : IPersistentDataStore
9+
{
10+
private readonly MockDatabase _db;
11+
private readonly string _prefix;
12+
13+
public MockSyncStore(MockDatabase db, string prefix)
14+
{
15+
_db = db;
16+
_prefix = prefix ?? "";
17+
}
18+
19+
public void Dispose() { }
20+
21+
public SerializedItemDescriptor? Get(DataKind kind, string key) =>
22+
_db.DataForPrefixAndKind(_prefix, kind).TryGetValue(key, out var ret) ?
23+
ret : (SerializedItemDescriptor?)null;
24+
25+
public KeyedItems<SerializedItemDescriptor> GetAll(DataKind kind) =>
26+
new KeyedItems<SerializedItemDescriptor>(_db.DataForPrefixAndKind(_prefix, kind));
27+
28+
public void Init(FullDataSet<SerializedItemDescriptor> allData)
29+
{
30+
_db.DataForPrefix(_prefix).Clear();
31+
foreach (var coll in allData.Data)
32+
{
33+
_db.DataForPrefix(_prefix)[coll.Key] = new Dictionary<string, SerializedItemDescriptor>(coll.Value.Items);
34+
}
35+
_db.SetInited(_prefix);
36+
}
37+
38+
public bool Initialized() => _db.Inited(_prefix);
39+
40+
public bool IsStoreAvailable() => true;
41+
42+
public bool Upsert(DataKind kind, string key, SerializedItemDescriptor item)
43+
{
44+
var dict = _db.DataForPrefixAndKind(_prefix, kind);
45+
if (dict.TryGetValue(key, out var oldItem) && oldItem.Version >= item.Version)
46+
{
47+
return false;
48+
}
49+
dict[key] = item;
50+
return true;
51+
}
52+
}
53+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System.Threading.Tasks;
2+
using LaunchDarkly.Sdk.Server.Interfaces;
3+
using Xunit;
4+
using Xunit.Abstractions;
5+
6+
namespace LaunchDarkly.Sdk.Server.SharedTests.DataStore
7+
{
8+
// This runs PersistentDataStoreBaseTests against a mock store implementation that is known to
9+
// behave as expected, to verify that the test suite logic has the correct expectations.
10+
11+
[Collection("Sequential")] // don't want this and the other test class to run simultaneously
12+
public class PersistentDataStoreBaseTestsAsyncTest : PersistentDataStoreBaseTests
13+
{
14+
protected override PersistentDataStoreTestConfig Configuration =>
15+
new PersistentDataStoreTestConfig
16+
{
17+
StoreAsyncFactoryFunc = CreateStoreFactory,
18+
ClearDataAction = ClearAllData,
19+
};
20+
21+
public PersistentDataStoreBaseTestsAsyncTest(ITestOutputHelper testOutput) : base(testOutput) { }
22+
23+
private IPersistentDataStoreAsyncFactory CreateStoreFactory(string prefix) =>
24+
new MockAsyncStoreFactory { Database = MockDatabase.Instance, Prefix = prefix };
25+
26+
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
27+
private async Task ClearAllData(string prefix) =>
28+
MockDatabase.Instance.Clear(prefix);
29+
#pragma warning restore CS1998
30+
31+
private class MockAsyncStoreFactory : IPersistentDataStoreAsyncFactory
32+
{
33+
internal MockDatabase Database { get; set; }
34+
internal string Prefix { get; set; }
35+
36+
public IPersistentDataStoreAsync CreatePersistentDataStore(LdClientContext context) =>
37+
new MockAsyncStore(Database, Prefix);
38+
}
39+
}
40+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System.Threading.Tasks;
2+
using LaunchDarkly.Sdk.Server.Interfaces;
3+
using Xunit;
4+
using Xunit.Abstractions;
5+
6+
namespace LaunchDarkly.Sdk.Server.SharedTests.DataStore
7+
{
8+
// This runs PersistentDataStoreBaseTests against a mock store implementation that is known to
9+
// behave as expected, to verify that the test suite logic has the correct expectations.
10+
11+
[Collection("Sequential")] // don't want this and the other test class to run simultaneously
12+
public class PersistentDataStoreBaseTestsSyncTest : PersistentDataStoreBaseTests
13+
{
14+
protected override PersistentDataStoreTestConfig Configuration =>
15+
new PersistentDataStoreTestConfig
16+
{
17+
StoreFactoryFunc = CreateStoreFactory,
18+
ClearDataAction = ClearAllData,
19+
};
20+
21+
public PersistentDataStoreBaseTestsSyncTest(ITestOutputHelper testOutput) : base(testOutput) { }
22+
23+
private IPersistentDataStoreFactory CreateStoreFactory(string prefix) =>
24+
new MockSyncStoreFactory { Database = MockDatabase.Instance, Prefix = prefix };
25+
26+
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
27+
private async Task ClearAllData(string prefix) =>
28+
MockDatabase.Instance.Clear(prefix);
29+
#pragma warning restore CS1998
30+
31+
private class MockSyncStoreFactory : IPersistentDataStoreFactory
32+
{
33+
internal MockDatabase Database { get; set; }
34+
internal string Prefix { get; set; }
35+
36+
public IPersistentDataStore CreatePersistentDataStore(LdClientContext context) =>
37+
new MockSyncStore(Database, Prefix);
38+
}
39+
}
40+
}

LaunchDarkly.ServerSdk.SharedTests.Tests/LaunchDarkly.ServerSdk.SharedTests.Tests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22
<PropertyGroup>
33
<TargetFrameworks>netcoreapp2.1</TargetFrameworks>
44
<IsPackable>false</IsPackable>
5+
<RootNamespace>LaunchDarkly.Sdk.Server.SharedTests</RootNamespace>
56
</PropertyGroup>
67

78
<ItemGroup>
89
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
910
<PackageReference Include="xunit" Version="2.4.1" />
1011
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
1112
</ItemGroup>
13+
<ItemGroup>
14+
<ProjectReference Include="..\LaunchDarkly.ServerSdk.SharedTests\LaunchDarkly.ServerSdk.SharedTests.csproj" />
15+
</ItemGroup>
1216
</Project>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
5+
namespace LaunchDarkly.Sdk.Server.SharedTests
6+
{
7+
internal static class AsyncUtils
8+
{
9+
private static readonly TaskFactory _taskFactory = new TaskFactory(CancellationToken.None,
10+
TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);
11+
12+
// This procedure for blocking on a Task without using Task.Wait is derived from the MIT-licensed ASP.NET
13+
// code here: https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
14+
// In general, mixing sync and async code is not recommended, and if done in other ways can result in
15+
// deadlocks. See: https://stackoverflow.com/questions/9343594/how-to-call-asynchronous-method-from-synchronous-method-in-c
16+
// Task.Wait would only be safe if we could guarantee that every intermediate Task within the async
17+
// code had been modified with ConfigureAwait(false), but that is very error-prone and we can't depend
18+
// on feature store implementors doing so.
19+
20+
internal static void WaitSafely(Func<Task> taskFn)
21+
{
22+
_taskFactory.StartNew(taskFn)
23+
.Unwrap()
24+
.GetAwaiter()
25+
.GetResult();
26+
// Note, GetResult does not throw AggregateException so we don't need to post-process exceptions
27+
}
28+
29+
internal static bool WaitSafely(Func<Task> taskFn, TimeSpan timeout)
30+
{
31+
try
32+
{
33+
return _taskFactory.StartNew(taskFn)
34+
.Unwrap()
35+
.Wait(timeout);
36+
}
37+
catch (AggregateException e)
38+
{
39+
throw UnwrapAggregateException(e);
40+
}
41+
}
42+
43+
internal static T WaitSafely<T>(Func<Task<T>> taskFn)
44+
{
45+
return _taskFactory.StartNew(taskFn)
46+
.Unwrap()
47+
.GetAwaiter()
48+
.GetResult();
49+
}
50+
51+
private static Exception UnwrapAggregateException(AggregateException e)
52+
{
53+
if (e.InnerExceptions.Count == 1)
54+
{
55+
return e.InnerExceptions[0];
56+
}
57+
return e;
58+
}
59+
}
60+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
using static LaunchDarkly.Sdk.Server.Interfaces.DataStoreTypes;
6+
7+
namespace LaunchDarkly.Sdk.Server.SharedTests.DataStore
8+
{
9+
/// <summary>
10+
/// Simplifies building the input parameter for a data store's Init method.
11+
/// </summary>
12+
public class DataBuilder
13+
{
14+
private readonly IDictionary<DataKind, IDictionary<String, TestEntity>> _data =
15+
new Dictionary<DataKind, IDictionary<String, TestEntity>>();
16+
17+
public DataBuilder Add(DataKind kind, params TestEntity[] items)
18+
{
19+
IDictionary<String, TestEntity> itemsDict;
20+
if (!_data.TryGetValue(kind, out itemsDict))
21+
{
22+
itemsDict = new Dictionary<String, TestEntity>();
23+
_data[kind] = itemsDict;
24+
}
25+
foreach (var item in items)
26+
{
27+
itemsDict[item.Key] = item;
28+
}
29+
return this;
30+
}
31+
32+
public FullDataSet<SerializedItemDescriptor> BuildSerialized()
33+
{
34+
return new FullDataSet<SerializedItemDescriptor>(
35+
_data.ToDictionary(kv => kv.Key,
36+
kv => new KeyedItems<SerializedItemDescriptor>(
37+
kv.Value.ToDictionary(kv1 => kv1.Key,
38+
kv1 => kv1.Value.SerializedItemDescriptor
39+
)
40+
)
41+
));
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)