diff --git a/pkgs/sdk/server/test/AssertHelpers.cs b/pkgs/sdk/server/test/AssertHelpers.cs
index c4e2c3ac..a14c2f04 100644
--- a/pkgs/sdk/server/test/AssertHelpers.cs
+++ b/pkgs/sdk/server/test/AssertHelpers.cs
@@ -1,7 +1,8 @@
-using LaunchDarkly.Logging;
+using System;
+using LaunchDarkly.Logging;
+using LaunchDarkly.TestHelpers;
using Xunit;
using Xunit.Sdk;
-
using static LaunchDarkly.Sdk.Server.Subsystems.DataStoreTypes;
using static LaunchDarkly.TestHelpers.JsonAssertions;
@@ -34,7 +35,8 @@ public static void LogMessageText(LogCapture logCapture, bool shouldHave, LogLev
}
}
- private static void ThrowLogMatchException(LogCapture logCapture, bool shouldHave, LogLevel level, string text, bool isRegex) =>
+ private static void ThrowLogMatchException(LogCapture logCapture, bool shouldHave, LogLevel level, string text,
+ bool isRegex) =>
throw new AssertActualExpectedException(shouldHave, !shouldHave,
string.Format("Expected log {0} the {1} \"{2}\" at level {3}\n\nActual log output follows:\n{4}",
shouldHave ? "to have" : "not to have",
@@ -42,5 +44,39 @@ private static void ThrowLogMatchException(LogCapture logCapture, bool shouldHav
text,
level,
logCapture.ToString()));
+
+ ///
+ /// Expect that the given sink will receive an event that passes the provided predicate within the specified
+ /// timeout.
+ ///
+ /// The total time for the execution of this method may be greater than the timeout, because its implementation
+ /// depends on a function which itself has a timeout.
+ ///
+ /// the sink to check events from
+ /// the predicate to run against events
+ /// the message to show if the test fails
+ /// the overall timeout
+ /// the type of the sink and predicate
+ public static void ExpectPredicate(EventSink sink, Predicate predicate, string message,
+ TimeSpan timeout)
+ {
+ while (true)
+ {
+ var startTime = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+
+ var value = sink.ExpectValue(timeout);
+
+ if (predicate(value))
+ {
+ break;
+ }
+
+ if (!(DateTimeOffset.Now.ToUnixTimeMilliseconds() - startTime > timeout.TotalMilliseconds)) continue;
+
+ // XUnit 2.5+ adds Assert.Fail.
+ Assert.True(false, message);
+ return;
+ }
+ }
}
}
diff --git a/pkgs/sdk/server/test/Internal/DataSources/FileDataSourceTest.cs b/pkgs/sdk/server/test/Internal/DataSources/FileDataSourceTest.cs
index ef2bf1c6..c4f91e24 100644
--- a/pkgs/sdk/server/test/Internal/DataSources/FileDataSourceTest.cs
+++ b/pkgs/sdk/server/test/Internal/DataSources/FileDataSourceTest.cs
@@ -1,5 +1,7 @@
using System;
+using System.Linq;
using System.Threading;
+using Castle.Core.Internal;
using LaunchDarkly.Sdk.Server.Integrations;
using LaunchDarkly.Sdk.Server.Interfaces;
using LaunchDarkly.Sdk.Server.Internal.Model;
@@ -8,7 +10,6 @@
using YamlDotNet.Serialization;
using Xunit;
using Xunit.Abstractions;
-
using static LaunchDarkly.Sdk.Server.Subsystems.DataStoreTypes;
using static LaunchDarkly.Sdk.Server.TestUtils;
using static LaunchDarkly.TestHelpers.JsonAssertions;
@@ -24,7 +25,9 @@ public class FileDataSourceTest : BaseTest
private readonly FileDataSourceBuilder factory = FileData.DataSource();
private readonly Context user = Context.New("key");
- public FileDataSourceTest(ITestOutputHelper testOutput) : base(testOutput) { }
+ public FileDataSourceTest(ITestOutputHelper testOutput) : base(testOutput)
+ {
+ }
private IDataSource MakeDataSource() =>
factory.Build(BasicContext.WithDataSourceUpdates(_updateSink));
@@ -148,9 +151,52 @@ public void ModifiedFileIsReloadedIfAutoUpdateIsOn()
file.SetContentFromPath(TestUtils.TestFilePath("segment-only.json"));
- var newData = _updateSink.Inits.ExpectValue(TimeSpan.FromSeconds(5));
-
- AssertJsonEqual(DataSetAsJson(ExpectedDataSetForSegmentOnlyFile(2)), DataSetAsJson(newData));
+ AssertHelpers.ExpectPredicate(_updateSink.Inits, actual =>
+ {
+ var segments = actual.Data.First(item => item.Key == DataModel.Segments);
+ var features = actual.Data.First(item => item.Key == DataModel.Features);
+ if (!features.Value.Items.IsNullOrEmpty())
+ {
+ return false;
+ }
+
+ var segmentItems = segments.Value.Items.ToList();
+
+ if (segmentItems.Count != 1)
+ {
+ return false;
+ }
+
+ var segmentDescriptor = segmentItems[0];
+ if (segmentDescriptor.Key != "seg1")
+ {
+ return false;
+ }
+
+ if (segmentDescriptor.Value.Version == 1)
+ {
+ return false;
+ }
+
+ if (!(segmentDescriptor.Value.Item is Segment segment))
+ {
+ return false;
+ }
+
+ if (segment.Deleted)
+ {
+ return false;
+ }
+
+ if (segment.Included.Count != 1)
+ {
+ return false;
+ }
+
+ return segment.Included[0] == "user1";
+ },
+ "Did not receive expected update from the file data source.",
+ TimeSpan.FromSeconds(30));
}
}
}