diff --git a/.editorconfig b/.editorconfig
index 5d928536..a7a7ca38 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -305,6 +305,11 @@ indent_size = 2
charset = utf-8
indent_size = 2
+# Json files
+[*.json]
+charset = utf-8
+indent_size = 2
+
# Shell scripts
[*.sh]
end_of_line = lf
diff --git a/.gitignore b/.gitignore
index 5369e4c1..b79da535 100644
--- a/.gitignore
+++ b/.gitignore
@@ -288,6 +288,7 @@ __pycache__/
*.xsd.cs
# Custom
+BenchmarkDotNet.Artifacts*/
coverage.xml
.DS_Store
.vscode
diff --git a/benchmarks/ConfigCat.Client.Benchmarks.sln b/benchmarks/ConfigCat.Client.Benchmarks.sln
new file mode 100644
index 00000000..a1d17bf8
--- /dev/null
+++ b/benchmarks/ConfigCat.Client.Benchmarks.sln
@@ -0,0 +1,60 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.6.33815.320
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigCat.Client.Benchmarks", "ConfigCat.Client.Benchmarks\ConfigCat.Client.Benchmarks.csproj", "{B7381881-0709-4F72-AE6C-3778979CD8C1}"
+ ProjectSection(ProjectDependencies) = postProject
+ {B51439A6-F230-46E5-9BC3-7E4E9FA841FC} = {B51439A6-F230-46E5-9BC3-7E4E9FA841FC}
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigCatClient", "..\src\ConfigCatClient\ConfigCatClient.csproj", "{B51439A6-F230-46E5-9BC3-7E4E9FA841FC}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OldVersionLib", "OldVersionLib\OldVersionLib.csproj", "{53015044-8ED1-4F77-BB02-357313F7952A}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NewVersionLib", "NewVersionLib\NewVersionLib.csproj", "{30B22E19-6701-4C36-B1F4-72AE24E93CEA}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ProjectReferences", "ProjectReferences", "{3B9B9CF8-8D20-423D-A327-60D2D2C77976}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Benchmark|Any CPU = Benchmark|Any CPU
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B7381881-0709-4F72-AE6C-3778979CD8C1}.Benchmark|Any CPU.ActiveCfg = Release|Any CPU
+ {B7381881-0709-4F72-AE6C-3778979CD8C1}.Benchmark|Any CPU.Build.0 = Release|Any CPU
+ {B7381881-0709-4F72-AE6C-3778979CD8C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B7381881-0709-4F72-AE6C-3778979CD8C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B7381881-0709-4F72-AE6C-3778979CD8C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B7381881-0709-4F72-AE6C-3778979CD8C1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B51439A6-F230-46E5-9BC3-7E4E9FA841FC}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU
+ {B51439A6-F230-46E5-9BC3-7E4E9FA841FC}.Benchmark|Any CPU.Build.0 = Benchmark|Any CPU
+ {B51439A6-F230-46E5-9BC3-7E4E9FA841FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B51439A6-F230-46E5-9BC3-7E4E9FA841FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B51439A6-F230-46E5-9BC3-7E4E9FA841FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B51439A6-F230-46E5-9BC3-7E4E9FA841FC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {53015044-8ED1-4F77-BB02-357313F7952A}.Benchmark|Any CPU.ActiveCfg = Release|Any CPU
+ {53015044-8ED1-4F77-BB02-357313F7952A}.Benchmark|Any CPU.Build.0 = Release|Any CPU
+ {53015044-8ED1-4F77-BB02-357313F7952A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {53015044-8ED1-4F77-BB02-357313F7952A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {53015044-8ED1-4F77-BB02-357313F7952A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {53015044-8ED1-4F77-BB02-357313F7952A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {30B22E19-6701-4C36-B1F4-72AE24E93CEA}.Benchmark|Any CPU.ActiveCfg = Release|Any CPU
+ {30B22E19-6701-4C36-B1F4-72AE24E93CEA}.Benchmark|Any CPU.Build.0 = Release|Any CPU
+ {30B22E19-6701-4C36-B1F4-72AE24E93CEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {30B22E19-6701-4C36-B1F4-72AE24E93CEA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {30B22E19-6701-4C36-B1F4-72AE24E93CEA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {30B22E19-6701-4C36-B1F4-72AE24E93CEA}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {B51439A6-F230-46E5-9BC3-7E4E9FA841FC} = {3B9B9CF8-8D20-423D-A327-60D2D2C77976}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {71FC06CD-80AD-4090-863E-1965313C9027}
+ EndGlobalSection
+EndGlobal
diff --git a/benchmarks/ConfigCat.Client.Benchmarks/ConfigCat.Client.Benchmarks.csproj b/benchmarks/ConfigCat.Client.Benchmarks/ConfigCat.Client.Benchmarks.csproj
new file mode 100644
index 00000000..d6560026
--- /dev/null
+++ b/benchmarks/ConfigCat.Client.Benchmarks/ConfigCat.Client.Benchmarks.csproj
@@ -0,0 +1,35 @@
+
+
+
+ Exe
+ net48;net6.0
+ 10.0
+ enable
+ nullable
+ true
+ ..\..\src\ConfigCatClient.snk
+
+
+
+
+
+
+
+
+
+ Configuration=Benchmark
+ from_project
+
+
+
+
+
+
+
+
+ from_nuget
+
+
+
+
+
diff --git a/benchmarks/ConfigCat.Client.Benchmarks/FlagEvaluationBenchmark.cs b/benchmarks/ConfigCat.Client.Benchmarks/FlagEvaluationBenchmark.cs
new file mode 100644
index 00000000..91fd1802
--- /dev/null
+++ b/benchmarks/ConfigCat.Client.Benchmarks/FlagEvaluationBenchmark.cs
@@ -0,0 +1,68 @@
+extern alias from_nuget;
+extern alias from_project;
+
+using System;
+using BenchmarkDotNet.Attributes;
+
+namespace ConfigCat.Client.Benchmarks;
+
+[MemoryDiagnoser]
+public class FlagEvaluationBenchmark
+{
+ private object evaluationServicesOld = null!;
+ private from_nuget::ConfigCat.Client.User userOld = null!;
+
+ private object evaluationServicesNew = null!;
+ private from_project::ConfigCat.Client.User userNew = null!;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ Environment.CurrentDirectory = AppContext.BaseDirectory;
+
+ this.evaluationServicesOld = Old.BenchmarkHelper.CreateEvaluationServices(LogInfo);
+ this.userOld = new("Cat") { Email = "cat@configcat.com", Custom = { ["Version"] = "1.1.1", ["Number"] = "1" } };
+
+ this.evaluationServicesNew = New.BenchmarkHelper.CreateEvaluationServices(LogInfo);
+ this.userNew = new("Cat") { Email = "cat@configcat.com", Custom = { ["Version"] = "1.1.1", ["Number"] = "1" } };
+ }
+
+ [Params(false, true)]
+ public bool LogInfo { get; set; }
+
+ [Benchmark]
+ public object Basic_ConfigV5()
+ {
+ return Old.BenchmarkHelper.Evaluate(this.evaluationServicesOld, "basicFlag", false);
+ }
+
+ [Benchmark]
+ public object Basic_ConfigV6()
+ {
+ return New.BenchmarkHelper.Evaluate(this.evaluationServicesNew, "basicFlag", false);
+ }
+
+ [Benchmark]
+ public object Complex_ConfigV5()
+ {
+ return Old.BenchmarkHelper.Evaluate(this.evaluationServicesOld, "complexFlag", "", this.userOld);
+ }
+
+ [Benchmark]
+ public object Complex_ConfigV6()
+ {
+ return New.BenchmarkHelper.Evaluate(this.evaluationServicesNew, "complexFlag", "", this.userNew);
+ }
+
+ [Benchmark]
+ public object All_ConfigV5()
+ {
+ return Old.BenchmarkHelper.EvaluateAll(this.evaluationServicesOld, this.userOld);
+ }
+
+ [Benchmark]
+ public object All_ConfigV6()
+ {
+ return New.BenchmarkHelper.EvaluateAll(this.evaluationServicesNew, this.userNew);
+ }
+}
diff --git a/benchmarks/JsonDeserializationBenchmark.cs b/benchmarks/ConfigCat.Client.Benchmarks/JsonDeserializationBenchmark.cs
similarity index 57%
rename from benchmarks/JsonDeserializationBenchmark.cs
rename to benchmarks/ConfigCat.Client.Benchmarks/JsonDeserializationBenchmark.cs
index 0010593a..36900bbf 100644
--- a/benchmarks/JsonDeserializationBenchmark.cs
+++ b/benchmarks/ConfigCat.Client.Benchmarks/JsonDeserializationBenchmark.cs
@@ -1,24 +1,24 @@
-extern alias from_nuget;
+extern alias from_nuget;
extern alias from_project;
using BenchmarkDotNet.Attributes;
using System;
-namespace ConfigCatClient.Benchmarks;
+namespace ConfigCat.Client.Benchmarks;
[MemoryDiagnoser]
public class JsonDeserializationBenchmark
{
- private readonly from_project::ConfigCat.Client.IConfigCatClient newClient = from_project::ConfigCat.Client.ConfigCatClientBuilder
- .Initialize("rv3YCMKenkaM7xkOCVQfeg/-I_w49WSQUWdZypPPM4Yyg")
- .WithManualPoll()
- .WithBaseUrl(new Uri("https://test-cdn-global.configcat.com"))
- .Create();
+ private readonly from_project::ConfigCat.Client.IConfigCatClient newClient = from_project::ConfigCat.Client.ConfigCatClient.Get("rv3YCMKenkaM7xkOCVQfeg/-I_w49WSQUWdZypPPM4Yyg", o =>
+ {
+ o.PollingMode = from_project::ConfigCat.Client.PollingModes.ManualPoll;
+ o.BaseUrl = new Uri("https://test-cdn-global.configcat.com");
+ });
- private readonly from_nuget::ConfigCat.Client.IConfigCatClient oldClient = from_nuget::ConfigCat.Client.ConfigCatClientBuilder
- .Initialize("rv3YCMKenkaM7xkOCVQfeg/-I_w49WSQUWdZypPPM4Yyg")
- .WithManualPoll()
- .WithBaseUrl(new Uri("https://test-cdn-global.configcat.com"))
- .Create();
+ private readonly from_nuget::ConfigCat.Client.IConfigCatClient oldClient = from_nuget::ConfigCat.Client.ConfigCatClient.Get("rv3YCMKenkaM7xkOCVQfeg/-I_w49WSQUWdZypPPM4Yyg", o =>
+ {
+ o.PollingMode = from_nuget::ConfigCat.Client.PollingModes.ManualPoll;
+ o.BaseUrl = new Uri("https://test-cdn-global.configcat.com");
+ });
private readonly from_project::ConfigCat.Client.User newUser = new("test@test.com");
private readonly from_nuget::ConfigCat.Client.User oldUser = new("test@test.com");
diff --git a/benchmarks/ConfigCat.Client.Benchmarks/MatrixTestBenchmark.cs b/benchmarks/ConfigCat.Client.Benchmarks/MatrixTestBenchmark.cs
new file mode 100644
index 00000000..6d8fa245
--- /dev/null
+++ b/benchmarks/ConfigCat.Client.Benchmarks/MatrixTestBenchmark.cs
@@ -0,0 +1,45 @@
+using System;
+using BenchmarkDotNet.Attributes;
+
+namespace ConfigCat.Client.Benchmarks;
+
+[MemoryDiagnoser]
+public class MatrixTestBenchmark
+{
+ private Old.MatrixTestRunnerBase testRunnerOld = null!;
+ private object evaluationServicesOld = null!;
+
+ private New.MatrixTestRunnerBase testRunnerNew = null!;
+ private object evaluationServicesNew = null!;
+
+ private object?[][] tests = null!;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ Environment.CurrentDirectory = AppContext.BaseDirectory;
+
+ this.testRunnerOld = new();
+ this.evaluationServicesOld = Old.BenchmarkHelper.CreateEvaluationServices(LogInfo);
+
+ this.testRunnerNew = new();
+ this.evaluationServicesNew = New.BenchmarkHelper.CreateEvaluationServices(LogInfo);
+
+ this.tests = New.BenchmarkHelper.GetMatrixTests();
+ }
+
+ [Params(false, true)]
+ public bool LogInfo { get; set; }
+
+ [Benchmark]
+ public int MatrixTests_ConfigV5()
+ {
+ return Old.BenchmarkHelper.RunAllMatrixTests(this.testRunnerOld, this.evaluationServicesOld, this.tests);
+ }
+
+ [Benchmark]
+ public int MatrixTests_ConfigV6()
+ {
+ return New.BenchmarkHelper.RunAllMatrixTests(this.testRunnerNew, this.evaluationServicesNew, this.tests);
+ }
+}
diff --git a/benchmarks/ConfigCat.Client.Benchmarks/Program.cs b/benchmarks/ConfigCat.Client.Benchmarks/Program.cs
new file mode 100644
index 00000000..a84eebc9
--- /dev/null
+++ b/benchmarks/ConfigCat.Client.Benchmarks/Program.cs
@@ -0,0 +1,11 @@
+using BenchmarkDotNet.Running;
+
+namespace ConfigCatClient.Benchmarks;
+
+internal class Program
+{
+ private static void Main(string[] args)
+ {
+ BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
+ }
+}
diff --git a/benchmarks/ConfigCatClient.Benchmarks.csproj b/benchmarks/ConfigCatClient.Benchmarks.csproj
deleted file mode 100644
index 60965e3e..00000000
--- a/benchmarks/ConfigCatClient.Benchmarks.csproj
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
- Exe
- net6.0
- Debug;Release;Benchmark
-
-
-
- true
-
-
-
-
-
-
-
-
-
-
- ..\src\ConfigCatClient\bin\Benchmark\netstandard2.1\ConfigCat.Client.Benchmark.dll
- from_project
- true
-
-
-
-
-
-
- from_nuget
-
-
-
-
-
diff --git a/benchmarks/ConfigCatClient.Benchmarks.sln b/benchmarks/ConfigCatClient.Benchmarks.sln
deleted file mode 100644
index 07a8d758..00000000
--- a/benchmarks/ConfigCatClient.Benchmarks.sln
+++ /dev/null
@@ -1,39 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.30907.101
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigCatClient.Benchmarks", "ConfigCatClient.Benchmarks.csproj", "{B7381881-0709-4F72-AE6C-3778979CD8C1}"
- ProjectSection(ProjectDependencies) = postProject
- {B51439A6-F230-46E5-9BC3-7E4E9FA841FC} = {B51439A6-F230-46E5-9BC3-7E4E9FA841FC}
- EndProjectSection
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigCatClient", "..\src\ConfigCatClient\ConfigCatClient.csproj", "{B51439A6-F230-46E5-9BC3-7E4E9FA841FC}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Benchmark|Any CPU = Benchmark|Any CPU
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {B7381881-0709-4F72-AE6C-3778979CD8C1}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU
- {B7381881-0709-4F72-AE6C-3778979CD8C1}.Benchmark|Any CPU.Build.0 = Benchmark|Any CPU
- {B7381881-0709-4F72-AE6C-3778979CD8C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B7381881-0709-4F72-AE6C-3778979CD8C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B7381881-0709-4F72-AE6C-3778979CD8C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B7381881-0709-4F72-AE6C-3778979CD8C1}.Release|Any CPU.Build.0 = Release|Any CPU
- {B51439A6-F230-46E5-9BC3-7E4E9FA841FC}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU
- {B51439A6-F230-46E5-9BC3-7E4E9FA841FC}.Benchmark|Any CPU.Build.0 = Benchmark|Any CPU
- {B51439A6-F230-46E5-9BC3-7E4E9FA841FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B51439A6-F230-46E5-9BC3-7E4E9FA841FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B51439A6-F230-46E5-9BC3-7E4E9FA841FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B51439A6-F230-46E5-9BC3-7E4E9FA841FC}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {71FC06CD-80AD-4090-863E-1965313C9027}
- EndGlobalSection
-EndGlobal
diff --git a/benchmarks/NewVersionLib/BenchmarkHelper.Shared.cs b/benchmarks/NewVersionLib/BenchmarkHelper.Shared.cs
new file mode 100644
index 00000000..c9dad514
--- /dev/null
+++ b/benchmarks/NewVersionLib/BenchmarkHelper.Shared.cs
@@ -0,0 +1,57 @@
+using System.Linq;
+using ConfigCat.Client.Evaluation;
+
+#if BENCHMARK_OLD
+namespace ConfigCat.Client.Benchmarks.Old;
+#else
+namespace ConfigCat.Client.Benchmarks.New;
+#endif
+
+internal class EvaluationServices
+{
+ public EvaluationServices(bool logInfo)
+ {
+ Logger = new LoggerWrapper(new NullLogger { LogLevel = logInfo ? LogLevel.Info : LogLevel.Warning });
+ Evaluator = new RolloutEvaluator(Logger);
+ }
+
+ public LoggerWrapper Logger { get; }
+ public RolloutEvaluator Evaluator { get; }
+}
+
+public static partial class BenchmarkHelper
+{
+ public static object CreateEvaluationServices(bool logInfo) => new EvaluationServices(logInfo);
+
+ public static object?[][] GetMatrixTests()
+ where TDescriptor : IMatrixTestDescriptor, new()
+ {
+ return MatrixTestRunnerBase.GetTests().ToArray();
+ }
+
+ public static bool RunMatrixTest(this MatrixTestRunnerBase runner, object evaluationServices, string settingKey, string expectedReturnValue, User? user = null)
+ where TDescriptor : IMatrixTestDescriptor, new()
+ {
+ var services = (EvaluationServices)evaluationServices;
+ return runner.RunTest(services.Evaluator, services.Logger, settingKey, expectedReturnValue, user);
+ }
+
+ public static int RunAllMatrixTests(this MatrixTestRunnerBase runner, object evaluationServices, object?[][] tests)
+ where TDescriptor : IMatrixTestDescriptor, new()
+ {
+ var services = (EvaluationServices)evaluationServices;
+ return runner.RunAllTests(services.Evaluator, services.Logger, tests);
+ }
+
+ public static EvaluationDetails Evaluate(object evaluationServices, string key, T defaultValue, User? user = null)
+ {
+ var services = (EvaluationServices)evaluationServices;
+ return services.Evaluator.Evaluate(Config.Settings, key, defaultValue, user, remoteConfig: null, services.Logger);
+ }
+
+ public static EvaluationDetails[] EvaluateAll(object evaluationServices, User? user = null)
+ {
+ var services = (EvaluationServices)evaluationServices;
+ return services.Evaluator.EvaluateAll(Config.Settings, user, remoteConfig: null, services.Logger, "empty array", out _);
+ }
+}
diff --git a/benchmarks/NewVersionLib/BenchmarkHelper.cs b/benchmarks/NewVersionLib/BenchmarkHelper.cs
new file mode 100644
index 00000000..be1d3127
--- /dev/null
+++ b/benchmarks/NewVersionLib/BenchmarkHelper.cs
@@ -0,0 +1,148 @@
+using System;
+using ConfigCat.Client.Tests.Helpers;
+
+namespace ConfigCat.Client.Benchmarks.New;
+
+public static partial class BenchmarkHelper
+{
+ public class BasicMatrixTestsDescriptor : IMatrixTestDescriptor
+ {
+ public ConfigLocation ConfigLocation => new ConfigLocation.LocalFile("data", "sample_v5.json");
+ public string MatrixResultFileName => "testmatrix.csv";
+ }
+
+ private static readonly Config Config = new Func(() =>
+ {
+ var config = new Config
+ {
+ Preferences = new Preferences
+ {
+ Salt = "LKQu1a62agfNnWuGwA8cZglf4x0yZSbY2En7WQn5dWw"
+ },
+ Settings =
+ {
+ ["basicFlag"] = new Setting
+ {
+ SettingType = SettingType.Boolean,
+ Value = new SettingValue { BoolValue = true },
+ },
+ ["complexFlag"] = new Setting
+ {
+ SettingType = SettingType.String,
+ TargetingRules = new[]
+ {
+ new TargetingRule
+ {
+ Conditions = new[]
+ {
+ new ConditionContainer
+ {
+ UserCondition = new UserCondition()
+ {
+ ComparisonAttribute = nameof(User.Identifier),
+ Comparator = UserComparator.SensitiveIsOneOf,
+ StringListValue = new[]
+ {
+ "61418c941ecda8031d08ab86ec821e676fde7b6a59cd16b1e7191503c2f8297d",
+ "2ebea0310612c4c40d183b0c123d9bd425cf54f1e101f42858e701b5077cba01"
+ }
+ }
+ },
+ },
+ SimpleValue = new SimpleSettingValue { Value = new SettingValue { StringValue = "a" } },
+ },
+ new TargetingRule
+ {
+ Conditions = new[]
+ {
+ new ConditionContainer
+ {
+ UserCondition = new UserCondition()
+ {
+ ComparisonAttribute = nameof(User.Email),
+ Comparator = UserComparator.ContainsAnyOf,
+ StringListValue = new[] { "@example.com" }
+ }
+ },
+ },
+ SimpleValue = new SimpleSettingValue { Value = new SettingValue { StringValue = "b" } },
+ },
+ new TargetingRule
+ {
+ Conditions = new[]
+ {
+ new ConditionContainer
+ {
+ UserCondition = new UserCondition()
+ {
+ ComparisonAttribute = "Version",
+ Comparator = UserComparator.SemVerIsOneOf,
+ StringListValue = new[] { "1.0.0", "2.0.0" }
+ }
+ },
+ },
+ SimpleValue = new SimpleSettingValue { Value = new SettingValue { StringValue = "c" } },
+ },
+ new TargetingRule
+ {
+ Conditions = new[]
+ {
+ new ConditionContainer
+ {
+ UserCondition = new UserCondition()
+ {
+ ComparisonAttribute = "Version",
+ Comparator = UserComparator.SemVerGreater,
+ StringValue = "3.0.0"
+ }
+ },
+ },
+ SimpleValue = new SimpleSettingValue { Value = new SettingValue { StringValue = "d" } },
+ },
+ new TargetingRule
+ {
+ Conditions = new[]
+ {
+ new ConditionContainer
+ {
+ UserCondition = new UserCondition()
+ {
+ ComparisonAttribute = "Number",
+ Comparator = UserComparator.NumberGreater,
+ DoubleValue = 3.14
+ }
+ },
+ },
+ SimpleValue = new SimpleSettingValue { Value = new SettingValue { StringValue = "e" } },
+ },
+ new TargetingRule
+ {
+ PercentageOptions = new[]
+ {
+ new PercentageOption
+ {
+ Percentage = 20,
+ Value = new SettingValue { StringValue = "p20" }
+ },
+ new PercentageOption
+ {
+ Percentage = 30,
+ Value = new SettingValue { StringValue = "p30" }
+ },
+ new PercentageOption
+ {
+ Percentage = 50,
+ Value = new SettingValue { StringValue = "p50" }
+ },
+ }
+ },
+ },
+ Value = new SettingValue { StringValue = "fallback" }
+ }
+ }
+ };
+
+ config.OnDeserialized();
+ return config;
+ })();
+}
diff --git a/benchmarks/NewVersionLib/ConfigHelper.cs b/benchmarks/NewVersionLib/ConfigHelper.cs
new file mode 100644
index 00000000..32fa5dd2
--- /dev/null
+++ b/benchmarks/NewVersionLib/ConfigHelper.cs
@@ -0,0 +1,10 @@
+#if BENCHMARK_OLD
+using Config = ConfigCat.Client.SettingsWithPreferences;
+#endif
+
+namespace ConfigCat.Client.Tests.Helpers;
+
+internal static class ConfigHelper
+{
+ public static Config FetchConfigCached(this ConfigLocation location) => location.FetchConfig();
+}
diff --git a/benchmarks/NewVersionLib/NewVersionLib.csproj b/benchmarks/NewVersionLib/NewVersionLib.csproj
new file mode 100644
index 00000000..d5434022
--- /dev/null
+++ b/benchmarks/NewVersionLib/NewVersionLib.csproj
@@ -0,0 +1,33 @@
+
+
+
+ ConfigCatClientBenchmarks
+ ConfigCat.Client.Benchmarks.New
+ net48;net6.0
+ 10.0
+ enable
+ nullable
+ true
+ ..\..\src\ConfigCatClient.snk
+ BENCHMARK_NEW;$(DefineConstants)
+
+
+
+
+ Configuration=Benchmark
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
diff --git a/benchmarks/NewVersionLib/NullLogger.cs b/benchmarks/NewVersionLib/NullLogger.cs
new file mode 100644
index 00000000..f75a4162
--- /dev/null
+++ b/benchmarks/NewVersionLib/NullLogger.cs
@@ -0,0 +1,14 @@
+using System;
+
+#if BENCHMARK_OLD
+namespace ConfigCat.Client.Benchmarks.Old;
+#else
+namespace ConfigCat.Client.Benchmarks.New;
+#endif
+
+public class NullLogger : IConfigCatLogger
+{
+ public LogLevel LogLevel { get; set; }
+
+ public void Log(LogLevel level, LogEventId eventId, ref FormattableLogMessage message, Exception? exception = null) { }
+}
diff --git a/benchmarks/OldVersionLib/BenchmarkHelper.cs b/benchmarks/OldVersionLib/BenchmarkHelper.cs
new file mode 100644
index 00000000..bfff8c45
--- /dev/null
+++ b/benchmarks/OldVersionLib/BenchmarkHelper.cs
@@ -0,0 +1,104 @@
+using System.Reflection;
+using System.Text.Json;
+using ConfigCat.Client.Tests.Helpers;
+
+namespace ConfigCat.Client.Benchmarks.Old;
+
+public static partial class BenchmarkHelper
+{
+ public class BasicMatrixTestsDescriptor : IMatrixTestDescriptor
+ {
+ public ConfigLocation ConfigLocation => new ConfigLocation.LocalFile("data", "sample_v5_old.json");
+ public string MatrixResultFileName => "testmatrix.csv";
+ }
+
+ private static readonly SettingsWithPreferences Config = new SettingsWithPreferences
+ {
+ Settings =
+ {
+ ["basicFlag"] = CreateSetting(SettingType.Boolean, JsonDocument.Parse("true").RootElement),
+ ["complexFlag"] = CreateSetting(SettingType.String, JsonDocument.Parse("\"fallback\"").RootElement,
+ new[]
+ {
+ new RolloutRule
+ {
+ Order = 0,
+ ComparisonAttribute = nameof(User.Identifier),
+ Comparator = Comparator.SensitiveOneOf,
+ ComparisonValue = "68d93aa74a0aa1664f65ad6c0515f24769b15c84,8409e4e5d27a1465165012b03b2606f0e5b08250",
+ Value = JsonDocument.Parse("\"a\"").RootElement,
+ },
+ new RolloutRule
+ {
+ Order = 1,
+ ComparisonAttribute = nameof(User.Email),
+ Comparator = Comparator.Contains,
+ ComparisonValue = "@example.com",
+ Value = JsonDocument.Parse("\"b\"").RootElement,
+ },
+ new RolloutRule
+ {
+ Order = 2,
+ ComparisonAttribute = "Version",
+ Comparator = Comparator.SemVerIn,
+ ComparisonValue = "1.0.0, 2.0.0",
+ Value = JsonDocument.Parse("\"c\"").RootElement,
+ },
+ new RolloutRule
+ {
+ Order = 3,
+ ComparisonAttribute = "Version",
+ Comparator = Comparator.SemVerGreaterThan,
+ ComparisonValue = "3.0.0",
+ Value = JsonDocument.Parse("\"d\"").RootElement,
+ },
+ new RolloutRule
+ {
+ Order = 4,
+ ComparisonAttribute = "Number",
+ Comparator = Comparator.NumberGreaterThan,
+ ComparisonValue = "3.14",
+ Value = JsonDocument.Parse("\"e\"").RootElement,
+ },
+ },
+ new[]
+ {
+ new RolloutPercentageItem
+ {
+ Percentage = 20,
+ Value = JsonDocument.Parse("\"p20\"").RootElement
+ },
+ new RolloutPercentageItem
+ {
+ Percentage = 30,
+ Value = JsonDocument.Parse("\"p30\"").RootElement
+ },
+ new RolloutPercentageItem
+ {
+ Percentage = 50,
+ Value = JsonDocument.Parse("\"p50\"").RootElement
+ },
+ })
+ }
+ };
+
+ private static Setting CreateSetting(SettingType settingType, JsonElement value, RolloutRule[]? targetingRules = null, RolloutPercentageItem[]? percentageOptions = null)
+ {
+ var setting = new Setting
+ {
+ SettingType = settingType,
+ Value = value,
+ };
+
+ SetPrivatePropertyValue(setting, nameof(setting.RolloutRules), targetingRules);
+ SetPrivatePropertyValue(setting, nameof(setting.RolloutPercentageItems), percentageOptions);
+
+ return setting;
+ }
+
+ private static void SetPrivatePropertyValue(object obj, string propertyName, object? value)
+ {
+ var type = obj.GetType();
+ type.InvokeMember(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty, null, obj, new[] { value });
+ }
+}
diff --git a/benchmarks/OldVersionLib/OldVersionLib.csproj b/benchmarks/OldVersionLib/OldVersionLib.csproj
new file mode 100644
index 00000000..001c5e43
--- /dev/null
+++ b/benchmarks/OldVersionLib/OldVersionLib.csproj
@@ -0,0 +1,36 @@
+
+
+
+
+ ConfigCatClientTests
+ ConfigCat.Client.Benchmarks.New
+ net48;net6.0
+ 10.0
+ enable
+ nullable
+ true
+ ..\..\src\ConfigCatClient.snk
+ BENCHMARK_OLD;$(DefineConstants)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
diff --git a/benchmarks/OldVersionLib/data/sample_v5_old.json b/benchmarks/OldVersionLib/data/sample_v5_old.json
new file mode 100644
index 00000000..253e5320
--- /dev/null
+++ b/benchmarks/OldVersionLib/data/sample_v5_old.json
@@ -0,0 +1,334 @@
+{
+ "f": {
+ "stringDefaultCat": {
+ "v": "Cat",
+ "t": 1,
+ "p": [],
+ "r": []
+ },
+ "stringIsInDogDefaultCat": {
+ "v": "Cat",
+ "t": 1,
+ "p": [],
+ "r": [
+ {
+ "o": 0,
+ "a": "Email",
+ "t": 0,
+ "c": "a@configcat.com, b@configcat.com",
+ "v": "Dog"
+ },
+ {
+ "o": 1,
+ "a": "Custom1",
+ "t": 0,
+ "c": "admin",
+ "v": "Dog"
+ }
+ ]
+ },
+ "stringIsNotInDogDefaultCat": {
+ "v": "Cat",
+ "t": 1,
+ "p": [],
+ "r": [
+ {
+ "o": 0,
+ "a": "Email",
+ "t": 1,
+ "c": "a@configcat.com,b@configcat.com",
+ "v": "Dog"
+ }
+ ]
+ },
+ "stringContainsDogDefaultCat": {
+ "v": "Cat",
+ "t": 1,
+ "p": [],
+ "r": [
+ {
+ "o": 0,
+ "a": "Email",
+ "t": 2,
+ "c": "@configcat.com",
+ "v": "Dog"
+ }
+ ]
+ },
+ "stringNotContainsDogDefaultCat": {
+ "v": "Cat",
+ "t": 1,
+ "p": [],
+ "r": [
+ {
+ "o": 0,
+ "a": "Email",
+ "t": 3,
+ "c": "@configcat.com",
+ "v": "Dog"
+ }
+ ]
+ },
+ "string25Cat25Dog25Falcon25Horse": {
+ "v": "Chicken",
+ "t": 1,
+ "p": [
+ {
+ "o": 0,
+ "v": "Cat",
+ "p": 25
+ },
+ {
+ "o": 1,
+ "v": "Dog",
+ "p": 25
+ },
+ {
+ "o": 2,
+ "v": "Falcon",
+ "p": 25
+ },
+ {
+ "o": 3,
+ "v": "Horse",
+ "p": 25
+ }
+ ],
+ "r": []
+ },
+ "string75Cat0Dog25Falcon0Horse": {
+ "v": "Chicken",
+ "t": 1,
+ "p": [
+ {
+ "o": 0,
+ "v": "Cat",
+ "p": 75
+ },
+ {
+ "o": 1,
+ "v": "Dog",
+ "p": 0
+ },
+ {
+ "o": 2,
+ "v": "Falcon",
+ "p": 25
+ },
+ {
+ "o": 3,
+ "v": "Horse",
+ "p": 0
+ }
+ ],
+ "r": []
+ },
+ "string25Cat25Dog25Falcon25HorseAdvancedRules": {
+ "v": "Chicken",
+ "t": 1,
+ "p": [
+ {
+ "o": 0,
+ "v": "Cat",
+ "p": 25
+ },
+ {
+ "o": 1,
+ "v": "Dog",
+ "p": 25
+ },
+ {
+ "o": 2,
+ "v": "Falcon",
+ "p": 25
+ },
+ {
+ "o": 3,
+ "v": "Horse",
+ "p": 25
+ }
+ ],
+ "r": [
+ {
+ "o": 0,
+ "a": "Country",
+ "t": 0,
+ "c": "Hungary, United Kingdom",
+ "v": "Dolphin"
+ },
+ {
+ "o": 1,
+ "a": "Custom1",
+ "t": 2,
+ "c": "admi",
+ "v": "Lion"
+ },
+ {
+ "o": 2,
+ "a": "Email",
+ "t": 2,
+ "c": "@configcat.com",
+ "v": "Kitten"
+ }
+ ]
+ },
+ "boolDefaultTrue": {
+ "v": true,
+ "t": 0,
+ "p": [],
+ "r": []
+ },
+ "boolDefaultFalse": {
+ "v": false,
+ "t": 0,
+ "p": [],
+ "r": []
+ },
+ "bool30TrueAdvancedRules": {
+ "v": true,
+ "t": 0,
+ "p": [
+ {
+ "o": 0,
+ "v": true,
+ "p": 30
+ },
+ {
+ "o": 1,
+ "v": false,
+ "p": 70
+ }
+ ],
+ "r": [
+ {
+ "o": 0,
+ "a": "Email",
+ "t": 0,
+ "c": "a@configcat.com, b@configcat.com",
+ "v": false
+ },
+ {
+ "o": 1,
+ "a": "Country",
+ "t": 2,
+ "c": "United",
+ "v": false
+ }
+ ]
+ },
+ "integer25One25Two25Three25FourAdvancedRules": {
+ "v": -1,
+ "t": 2,
+ "p": [
+ {
+ "o": 0,
+ "v": 1,
+ "p": 25
+ },
+ {
+ "o": 1,
+ "v": 2,
+ "p": 25
+ },
+ {
+ "o": 2,
+ "v": 3,
+ "p": 25
+ },
+ {
+ "o": 3,
+ "v": 4,
+ "p": 25
+ }
+ ],
+ "r": [
+ {
+ "o": 0,
+ "a": "Email",
+ "t": 2,
+ "c": "@configcat.com",
+ "v": 5
+ }
+ ]
+ },
+ "integerDefaultOne": {
+ "v": 1,
+ "t": 2,
+ "p": [],
+ "r": []
+ },
+ "doubleDefaultPi": {
+ "v": 3.1415,
+ "t": 3,
+ "p": [],
+ "r": []
+ },
+ "double25Pi25E25Gr25Zero": {
+ "v": -1.0,
+ "t": 3,
+ "p": [
+ {
+ "o": 0,
+ "v": 3.1415,
+ "p": 25
+ },
+ {
+ "o": 1,
+ "v": 2.7182,
+ "p": 25
+ },
+ {
+ "o": 2,
+ "v": 1.61803,
+ "p": 25
+ },
+ {
+ "o": 3,
+ "v": 0.0,
+ "p": 25
+ }
+ ],
+ "r": [
+ {
+ "o": 0,
+ "a": "Email",
+ "t": 2,
+ "c": "@configcat.com",
+ "v": 5.561
+ }
+ ]
+ },
+ "keySampleText": {
+ "v": "Cat",
+ "t": 1,
+ "p": [
+ {
+ "o": 0,
+ "v": "Falcon",
+ "p": 50
+ },
+ {
+ "o": 1,
+ "v": "Horse",
+ "p": 50
+ }
+ ],
+ "r": [
+ {
+ "o": 0,
+ "a": "Country",
+ "t": 0,
+ "c": "Hungary,Bahamas",
+ "v": "Dog"
+ },
+ {
+ "o": 1,
+ "a": "SubscriptionType",
+ "t": 0,
+ "c": "unlimited",
+ "v": "Lion"
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/benchmarks/Program.cs b/benchmarks/Program.cs
deleted file mode 100644
index 527bf545..00000000
--- a/benchmarks/Program.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using BenchmarkDotNet.Running;
-using System;
-
-namespace ConfigCatClient.Benchmarks;
-
-internal class Program
-{
- private static void Main(string[] args)
- {
- BenchmarkRunner.Run();
-
- Console.ReadKey();
- }
-}
diff --git a/src/ConfigCat.Client.Tests/BasicConfigCatClientIntegrationTests.cs b/src/ConfigCat.Client.Tests/BasicConfigCatClientIntegrationTests.cs
index b856926b..43e0bd44 100644
--- a/src/ConfigCat.Client.Tests/BasicConfigCatClientIntegrationTests.cs
+++ b/src/ConfigCat.Client.Tests/BasicConfigCatClientIntegrationTests.cs
@@ -340,7 +340,7 @@ static void Configure(ConfigCatClientOptions options)
public async Task Http_Timeout_Test_Async()
{
var response = $"{{ \"f\": {{ \"fakeKey\": {{ \"v\": \"fakeValue\", \"p\": [] ,\"r\": [] }} }} }}";
- using IConfigCatClient manualPollClient = ConfigCatClient.Get("fake", options =>
+ using IConfigCatClient manualPollClient = ConfigCatClient.Get("fake-67890123456789012/1234567890123456789012", options =>
{
options.PollingMode = PollingModes.ManualPoll;
options.Logger = ConsoleLogger;
@@ -357,7 +357,7 @@ public async Task Http_Timeout_Test_Async()
public void Http_Timeout_Test_Sync()
{
var response = $"{{ \"f\": {{ \"fakeKey\": {{ \"v\": \"fakeValue\", \"p\": [] ,\"r\": [] }} }} }}";
- using IConfigCatClient manualPollClient = ConfigCatClient.Get("fake", options =>
+ using IConfigCatClient manualPollClient = ConfigCatClient.Get("fake-67890123456789012/1234567890123456789012", options =>
{
options.PollingMode = PollingModes.ManualPoll;
options.Logger = ConsoleLogger;
@@ -374,7 +374,7 @@ public async Task Ensure_MaxInitWait_Overrides_Timeout()
{
var now = DateTimeOffset.UtcNow;
var response = $"{{ \"f\": {{ \"fakeKey\": {{ \"v\": \"fakeValue\", \"p\": [] ,\"r\": [] }} }} }}";
- using IConfigCatClient manualPollClient = ConfigCatClient.Get("fake", options =>
+ using IConfigCatClient manualPollClient = ConfigCatClient.Get("fake-67890123456789012/1234567890123456789012", options =>
{
options.PollingMode = PollingModes.AutoPoll(maxInitWaitTime: TimeSpan.FromSeconds(1));
options.Logger = ConsoleLogger;
@@ -390,7 +390,7 @@ public void Ensure_MaxInitWait_Overrides_Timeout_Sync()
{
var now = DateTimeOffset.UtcNow;
var response = $"{{ \"f\": {{ \"fakeKey\": {{ \"v\": \"fakeValue\", \"p\": [] ,\"r\": [] }} }} }}";
- using IConfigCatClient manualPollClient = ConfigCatClient.Get("fake", options =>
+ using IConfigCatClient manualPollClient = ConfigCatClient.Get("fake-67890123456789012/1234567890123456789012", options =>
{
options.PollingMode = PollingModes.AutoPoll(maxInitWaitTime: TimeSpan.FromSeconds(1));
options.Logger = ConsoleLogger;
@@ -407,7 +407,7 @@ public void Ensure_Client_Dispose_Kill_Hanging_Http_Call()
var defer = new ManualResetEvent(false);
var now = DateTimeOffset.UtcNow;
var response = $"{{ \"f\": {{ \"fakeKey\": {{ \"v\": \"fakeValue\", \"p\": [] ,\"r\": [] }} }} }}";
- using IConfigCatClient manualPollClient = ConfigCatClient.Get("fake", options =>
+ using IConfigCatClient manualPollClient = ConfigCatClient.Get("fake-67890123456789012/1234567890123456789012", options =>
{
options.Logger = ConsoleLogger;
options.HttpClientHandler = new FakeHttpClientHandler(System.Net.HttpStatusCode.OK, response, TimeSpan.FromSeconds(5));
@@ -426,7 +426,7 @@ public void Ensure_Client_Dispose_Kill_Hanging_Http_Call_Sync()
var defer = new ManualResetEvent(false);
var now = DateTimeOffset.UtcNow;
var response = $"{{ \"f\": {{ \"fakeKey\": {{ \"v\": \"fakeValue\", \"p\": [] ,\"r\": [] }} }} }}";
- using IConfigCatClient manualPollClient = ConfigCatClient.Get("fake", options =>
+ using IConfigCatClient manualPollClient = ConfigCatClient.Get("fake-67890123456789012/1234567890123456789012", options =>
{
options.Logger = ConsoleLogger;
options.HttpClientHandler = new FakeHttpClientHandler(System.Net.HttpStatusCode.OK, response, TimeSpan.FromSeconds(5));
@@ -447,7 +447,7 @@ public void Ensure_Client_Dispose_Kill_Hanging_Http_Call_Sync()
public void Ensure_Multiple_Requests_Doesnt_Interfere_In_ValueTasks()
{
var response = $"{{ \"f\": {{ \"fakeKey\": {{ \"v\": \"fakeValue\", \"p\": [] ,\"r\": [] }} }} }}";
- using IConfigCatClient manualPollClient = ConfigCatClient.Get("fake", options =>
+ using IConfigCatClient manualPollClient = ConfigCatClient.Get("fake-67890123456789012/1234567890123456789012", options =>
{
options.Logger = ConsoleLogger;
options.PollingMode = PollingModes.ManualPoll;
diff --git a/src/ConfigCat.Client.Tests/ConfigCacheTests.cs b/src/ConfigCat.Client.Tests/ConfigCacheTests.cs
index 8c46a2fc..1ce2065e 100644
--- a/src/ConfigCat.Client.Tests/ConfigCacheTests.cs
+++ b/src/ConfigCat.Client.Tests/ConfigCacheTests.cs
@@ -228,21 +228,21 @@ public async Task ConfigCache_ShouldHandleWhenExternalCacheFails(bool isAsync)
loggerMock.Verify(l => l.Log(LogLevel.Error, 2200, ref It.Ref.IsAny, It.Is(ex => ex is ApplicationException)), Times.Once);
}
- [DataRow("test1", "147c5b4c2b2d7c77e1605b1a4309f0ea6684a0c6")]
- [DataRow("test2", "c09513b1756de9e4bc48815ec7a142b2441ed4d5")]
+ [DataRow("test1", "7f845c43ecc95e202b91e271435935e6d1391e5d")]
+ [DataRow("test2", "a78b7e323ef543a272c74540387566a22415148a")]
[DataTestMethod]
public void CacheKeyGeneration_ShouldBePlatformIndependent(string sdkKey, string expectedCacheKey)
{
Assert.AreEqual(expectedCacheKey, ConfigCatClient.GetCacheKey(sdkKey));
}
- private const string PayloadTestConfigJson = "{\"p\":{\"u\":\"https://cdn-global.configcat.com\",\"r\":0},\"f\":{\"testKey\":{\"v\":\"testValue\",\"t\":1,\"p\":[],\"r\":[]}}}";
+ private const string PayloadTestConfigJson = "{\"p\":{\"u\":\"https://cdn-global.configcat.com\",\"r\":0,\"s\":\"FUkC6RADjzF0vXrDSfJn7BcEBag9afw1Y6jkqjMP9BA=\"},\"f\":{\"testKey\":{\"t\":1,\"v\":{\"s\":\"testValue\"}}}}";
[DataRow(PayloadTestConfigJson, "2023-06-14T15:27:15.8440000Z", "test-etag", "1686756435844\ntest-etag\n" + PayloadTestConfigJson)]
[DataTestMethod]
public void CachePayloadSerialization_ShouldBePlatformIndependent(string configJson, string timeStamp, string httpETag, string expectedPayload)
{
var timeStampDateTime = DateTimeOffset.ParseExact(timeStamp, "o", CultureInfo.InvariantCulture).UtcDateTime;
- var pc = new ProjectConfig(configJson, configJson.Deserialize(), timeStampDateTime, httpETag);
+ var pc = new ProjectConfig(configJson, configJson.Deserialize(), timeStampDateTime, httpETag);
Assert.AreEqual(expectedPayload, ProjectConfig.Serialize(pc));
}
diff --git a/src/ConfigCat.Client.Tests/ConfigCat.Client.Tests.csproj b/src/ConfigCat.Client.Tests/ConfigCat.Client.Tests.csproj
index 12c07e46..784def52 100644
--- a/src/ConfigCat.Client.Tests/ConfigCat.Client.Tests.csproj
+++ b/src/ConfigCat.Client.Tests/ConfigCat.Client.Tests.csproj
@@ -8,12 +8,41 @@
10.0
enable
nullable
- strongname.snk
+ ..\ConfigCatClient.snk
-
- USE_NEWTONSOFT_JSON
-
+
+
+
+ USE_NEWTONSOFT_JSON
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -22,8 +51,6 @@
-
-
@@ -35,47 +62,8 @@
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
+
+ PreserveNewest
diff --git a/src/ConfigCat.Client.Tests/ConfigCatClientTests.cs b/src/ConfigCat.Client.Tests/ConfigCatClientTests.cs
index b7ccb4b9..ce6c1fa6 100644
--- a/src/ConfigCat.Client.Tests/ConfigCatClientTests.cs
+++ b/src/ConfigCat.Client.Tests/ConfigCatClientTests.cs
@@ -57,6 +57,44 @@ public void CreateAnInstance_WhenSdkKeyIsNull_ShouldThrowArgumentNullException()
using var _ = ConfigCatClient.Get(sdkKey!);
}
+ [DataRow("sdk-key-90123456789012", false, false)]
+ [DataRow("sdk-key-9012345678901/1234567890123456789012", false, false)]
+ [DataRow("sdk-key-90123456789012/123456789012345678901", false, false)]
+ [DataRow("sdk-key-90123456789012/12345678901234567890123", false, false)]
+ [DataRow("sdk-key-901234567890123/1234567890123456789012", false, false)]
+ [DataRow("sdk-key-90123456789012/1234567890123456789012", false, true)]
+ [DataRow("configcat-sdk-1/sdk-key-90123456789012", false, false)]
+ [DataRow("configcat-sdk-1/sdk-key-9012345678901/1234567890123456789012", false, false)]
+ [DataRow("configcat-sdk-1/sdk-key-90123456789012/123456789012345678901", false, false)]
+ [DataRow("configcat-sdk-1/sdk-key-90123456789012/12345678901234567890123", false, false)]
+ [DataRow("configcat-sdk-1/sdk-key-901234567890123/1234567890123456789012", false, false)]
+ [DataRow("configcat-sdk-1/sdk-key-90123456789012/1234567890123456789012", false, true)]
+ [DataRow("configcat-sdk-2/sdk-key-90123456789012/1234567890123456789012", false, false)]
+ [DataRow("configcat-proxy/", false, false)]
+ [DataRow("configcat-proxy/", true, false)]
+ [DataRow("configcat-proxy/sdk-key-90123456789012", false, false)]
+ [DataRow("configcat-proxy/sdk-key-90123456789012", true, true)]
+ [DataTestMethod]
+ [DoNotParallelize]
+ public void SdkKeyFormat_ShouldBeValidated(string sdkKey, bool customBaseUrl, bool isValid)
+ {
+ Action? configureOptions = customBaseUrl
+ ? o => o.BaseUrl = new Uri("https://my-configcat-proxy")
+ : null;
+
+ if (isValid)
+ {
+ using var _ = ConfigCatClient.Get(sdkKey, configureOptions);
+ }
+ else
+ {
+ Assert.ThrowsException(() =>
+ {
+ using var _ = ConfigCatClient.Get(sdkKey, configureOptions);
+ });
+ }
+ }
+
[ExpectedException(typeof(ArgumentOutOfRangeException))]
[TestMethod]
[DoNotParallelize]
@@ -83,7 +121,7 @@ public void CreateAnInstance_WhenLazyLoadConfigurationTimeToLiveSecondsIsZero_Sh
[DoNotParallelize]
public void CreateAnInstance_WhenLoggerIsNull_ShouldCreateAnInstance()
{
- using var client = ConfigCatClient.Get("hsdrTr4sxbHdSgdhHRZds346hdgsS2vfsgf/GsdrTr4sxbHdSgdhHRZds346hdOPsSgvfsgf", options =>
+ using var client = ConfigCatClient.Get("hsdrTr4sxbHdSgdhHRZds3/GsdrTr4sxbHdSgdhHRZds3", options =>
{
options.Logger = null;
});
@@ -95,7 +133,7 @@ public void CreateAnInstance_WhenLoggerIsNull_ShouldCreateAnInstance()
[DoNotParallelize]
public void CreateAnInstance_WithSdkKey_ShouldCreateAnInstance()
{
- using var _ = ConfigCatClient.Get("hsdrTr4sxbHdSgdhHRZds346hdgsS2vfsgf/GsdrTr4sxbHdSgdhHRZds346hdOPsSgvfsgf");
+ using var _ = ConfigCatClient.Get("hsdrTr4sxbHdSgdhHRZds3/GsdrTr4sxbHdSgdhHRZds3");
}
[TestMethod]
@@ -149,8 +187,12 @@ public void GetValue_EvaluateServiceThrowException_ShouldReturnDefaultValue()
const string defaultValue = "Victory for the Firstborn!";
+ this.configServiceMock
+ .Setup(m => m.GetConfig())
+ .Throws();
+
this.evaluatorMock
- .Setup(m => m.Evaluate(It.IsAny(), It.IsAny(), defaultValue, null))
+ .Setup(m => m.Evaluate(It.IsAny(), ref It.Ref.IsAny, out It.Ref.IsAny))
.Throws();
var client = new ConfigCatClient(this.configServiceMock.Object, this.loggerMock.Object, this.evaluatorMock.Object, new Hooks());
@@ -178,8 +220,12 @@ public async Task GetValueAsync_EvaluateServiceThrowException_ShouldReturnDefaul
const string defaultValue = "Victory for the Firstborn!";
+ this.configServiceMock
+ .Setup(m => m.GetConfigAsync(It.IsAny()))
+ .Throws();
+
this.evaluatorMock
- .Setup(m => m.Evaluate(It.IsAny(), It.IsAny(), defaultValue, null))
+ .Setup(m => m.Evaluate(It.IsAny(), ref It.Ref.IsAny, out It.Ref.IsAny))
.Throws();
var client = new ConfigCatClient(this.configServiceMock.Object, this.loggerMock.Object, this.evaluatorMock.Object, new Hooks());
@@ -240,8 +286,8 @@ public async Task GetValueDetails_ShouldReturnCorrectEvaluationDetails_SettingIs
Assert.AreSame(user, actual.User);
Assert.IsNotNull(actual.ErrorMessage);
Assert.IsNull(actual.ErrorException);
- Assert.IsNull(actual.MatchedEvaluationRule);
- Assert.IsNull(actual.MatchedEvaluationPercentageRule);
+ Assert.IsNull(actual.MatchedTargetingRule);
+ Assert.IsNull(actual.MatchedPercentageOption);
}
[DataRow(false)]
@@ -292,8 +338,8 @@ public async Task GetValueDetails_ShouldReturnCorrectEvaluationDetails_SettingIs
Assert.IsNull(actual.User);
Assert.IsNull(actual.ErrorMessage);
Assert.IsNull(actual.ErrorException);
- Assert.IsNull(actual.MatchedEvaluationRule);
- Assert.IsNull(actual.MatchedEvaluationPercentageRule);
+ Assert.IsNull(actual.MatchedTargetingRule);
+ Assert.IsNull(actual.MatchedPercentageOption);
}
[DataRow(false)]
@@ -346,8 +392,8 @@ public async Task GetValueDetails_ShouldReturnCorrectEvaluationDetails_SettingIs
Assert.AreSame(user, actual.User);
Assert.IsNull(actual.ErrorMessage);
Assert.IsNull(actual.ErrorException);
- Assert.IsNotNull(actual.MatchedEvaluationRule);
- Assert.IsNull(actual.MatchedEvaluationPercentageRule);
+ Assert.IsNotNull(actual.MatchedTargetingRule);
+ Assert.IsNull(actual.MatchedPercentageOption);
}
[DataRow(false)]
@@ -400,8 +446,8 @@ public async Task GetValueDetails_ShouldReturnCorrectEvaluationDetails_SettingIs
Assert.AreSame(user, actual.User);
Assert.IsNull(actual.ErrorMessage);
Assert.IsNull(actual.ErrorException);
- Assert.IsNull(actual.MatchedEvaluationRule);
- Assert.IsNotNull(actual.MatchedEvaluationPercentageRule);
+ Assert.IsNull(actual.MatchedTargetingRule);
+ Assert.IsNotNull(actual.MatchedPercentageOption);
}
[DataRow(false)]
@@ -443,8 +489,8 @@ public async Task GetValueDetails_ConfigServiceThrowException_ShouldReturnDefaul
Assert.IsNull(actual.User);
Assert.AreEqual(errorMessage, actual.ErrorMessage);
Assert.IsInstanceOfType(actual.ErrorException, typeof(ApplicationException));
- Assert.IsNull(actual.MatchedEvaluationRule);
- Assert.IsNull(actual.MatchedEvaluationPercentageRule);
+ Assert.IsNull(actual.MatchedTargetingRule);
+ Assert.IsNull(actual.MatchedPercentageOption);
}
[DataRow(false)]
@@ -463,7 +509,7 @@ public async Task GetValueDetails_EvaluateServiceThrowException_ShouldReturnDefa
var timeStamp = ProjectConfig.GenerateTimeStamp();
this.evaluatorMock
- .Setup(m => m.Evaluate(It.IsAny(), It.IsAny(), defaultValue, It.IsAny()))
+ .Setup(m => m.Evaluate(It.IsAny(), ref It.Ref.IsAny, out It.Ref.IsAny))
.Throws(new ApplicationException(errorMessage));
var client = CreateClientWithMockedFetcher(cacheKey, this.loggerMock, this.fetcherMock,
@@ -506,8 +552,8 @@ public async Task GetValueDetails_EvaluateServiceThrowException_ShouldReturnDefa
Assert.AreSame(user, actual.User);
Assert.AreEqual(errorMessage, actual.ErrorMessage);
Assert.IsInstanceOfType(actual.ErrorException, typeof(ApplicationException));
- Assert.IsNull(actual.MatchedEvaluationRule);
- Assert.IsNull(actual.MatchedEvaluationPercentageRule);
+ Assert.IsNull(actual.MatchedTargetingRule);
+ Assert.IsNull(actual.MatchedPercentageOption);
Assert.AreEqual(1, flagEvaluatedEvents.Count);
Assert.AreSame(actual, flagEvaluatedEvents[0].EvaluationDetails);
@@ -575,8 +621,8 @@ public async Task GetAllValueDetails_ShouldReturnCorrectEvaluationDetails(bool i
Assert.AreSame(user, actualDetails.User);
Assert.IsNull(actualDetails.ErrorMessage);
Assert.IsNull(actualDetails.ErrorException);
- Assert.IsNotNull(actualDetails.MatchedEvaluationRule);
- Assert.IsNull(actualDetails.MatchedEvaluationPercentageRule);
+ Assert.IsNotNull(actualDetails.MatchedTargetingRule);
+ Assert.IsNull(actualDetails.MatchedPercentageOption);
var flagEvaluatedDetails = flagEvaluatedEvents.Select(e => e.EvaluationDetails).FirstOrDefault(details => details.Key == expectedItem.Key);
@@ -594,7 +640,7 @@ public async Task GetAllValueDetails_DeserializeFailed_ShouldReturnWithEmptyArra
this.configServiceMock.Setup(m => m.GetConfig()).Returns(ProjectConfig.Empty);
this.configServiceMock.Setup(m => m.GetConfigAsync(It.IsAny())).ReturnsAsync(ProjectConfig.Empty);
- var o = new SettingsWithPreferences();
+ var o = new Config();
using IConfigCatClient client = new ConfigCatClient(this.configServiceMock.Object, this.loggerMock.Object, this.evaluatorMock.Object, new Hooks());
@@ -622,6 +668,10 @@ public async Task GetAllValueDetails_ConfigServiceThrowException_ShouldReturnEmp
{
// Arrange
+ this.configServiceMock
+ .Setup(m => m.GetConfig())
+ .Throws();
+
this.configServiceMock
.Setup(m => m.GetConfigAsync(It.IsAny()))
.Throws();
@@ -658,7 +708,7 @@ public async Task GetAllValueDetails_EvaluateServiceThrowException_ShouldReturnD
var timeStamp = ProjectConfig.GenerateTimeStamp();
this.evaluatorMock
- .Setup(m => m.Evaluate(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
+ .Setup(m => m.Evaluate(It.IsAny(), ref It.Ref.IsAny, out It.Ref.IsAny))
.Throws(new ApplicationException(errorMessage));
var client = CreateClientWithMockedFetcher(cacheKey, this.loggerMock, this.fetcherMock,
@@ -707,8 +757,8 @@ public async Task GetAllValueDetails_EvaluateServiceThrowException_ShouldReturnD
Assert.AreSame(user, actualDetails.User);
Assert.AreEqual(errorMessage, actualDetails.ErrorMessage);
Assert.IsInstanceOfType(actualDetails.ErrorException, typeof(ApplicationException));
- Assert.IsNull(actualDetails.MatchedEvaluationRule);
- Assert.IsNull(actualDetails.MatchedEvaluationPercentageRule);
+ Assert.IsNull(actualDetails.MatchedTargetingRule);
+ Assert.IsNull(actualDetails.MatchedPercentageOption);
var flagEvaluatedDetails = flagEvaluatedEvents.Select(e => e.EvaluationDetails).FirstOrDefault(details => details.Key == key);
@@ -779,8 +829,8 @@ public void GetAllKeys_DeserializerThrowException_ShouldReturnsWithEmptyArray()
{
// Arrange
- this.configServiceMock.Setup(m => m.GetConfigAsync(It.IsAny())).ReturnsAsync(ProjectConfig.Empty);
- var o = new SettingsWithPreferences();
+ this.configServiceMock.Setup(m => m.GetConfig()).Returns(ProjectConfig.Empty);
+ var o = new Config();
IConfigCatClient instance = new ConfigCatClient(
this.configServiceMock.Object,
@@ -1160,11 +1210,11 @@ void Configure(ConfigCatClientOptions options)
// Act
- using var client1 = ConfigCatClient.Get("test", Configure);
+ using var client1 = ConfigCatClient.Get("test-67890123456789012/1234567890123456789012", Configure);
var warnings1 = warnings.ToArray();
warnings.Clear();
- using var client2 = ConfigCatClient.Get("test", passConfigureToSecondGet ? Configure : null);
+ using var client2 = ConfigCatClient.Get("test-67890123456789012/1234567890123456789012", passConfigureToSecondGet ? Configure : null);
var warnings2 = warnings.ToArray();
// Assert
@@ -1189,7 +1239,7 @@ public void Dispose_CachedInstanceRemoved()
{
// Arrange
- var client1 = ConfigCatClient.Get("test", options => options.PollingMode = PollingModes.ManualPoll);
+ var client1 = ConfigCatClient.Get("test-67890123456789012/1234567890123456789012", options => options.PollingMode = PollingModes.ManualPoll);
// Act
@@ -1211,7 +1261,7 @@ public void Dispose_CanRemoveCurrentCachedInstanceOnly()
{
// Arrange
- var client1 = ConfigCatClient.Get("test", options => options.PollingMode = PollingModes.ManualPoll);
+ var client1 = ConfigCatClient.Get("test-67890123456789012/1234567890123456789012", options => options.PollingMode = PollingModes.ManualPoll);
// Act
@@ -1221,7 +1271,7 @@ public void Dispose_CanRemoveCurrentCachedInstanceOnly()
var instanceCount2 = ConfigCatClient.Instances.GetAliveCount();
- var client2 = ConfigCatClient.Get("test", options => options.PollingMode = PollingModes.ManualPoll);
+ var client2 = ConfigCatClient.Get("test-67890123456789012/1234567890123456789012", options => options.PollingMode = PollingModes.ManualPoll);
var instanceCount3 = ConfigCatClient.Instances.GetAliveCount();
@@ -1248,8 +1298,8 @@ public void DisposeAll_CachedInstancesRemoved()
{
// Arrange
- var client1 = ConfigCatClient.Get("test1", options => options.PollingMode = PollingModes.AutoPoll());
- var client2 = ConfigCatClient.Get("test2", options => options.PollingMode = PollingModes.ManualPoll);
+ var client1 = ConfigCatClient.Get("test1-7890123456789012/1234567890123456789012", options => options.PollingMode = PollingModes.AutoPoll());
+ var client2 = ConfigCatClient.Get("test2-7890123456789012/1234567890123456789012", options => options.PollingMode = PollingModes.ManualPoll);
// Act
@@ -1283,8 +1333,8 @@ static void CreateClients(out int instanceCount)
// because that could interfere with this test: when raising the event, the service acquires a strong reference to the client,
// which would temporarily prevent the client from being GCd. This could break the test in the case of unlucky timing.
// Setting maxInitWaitTime to zero prevents this because then the event is raised immediately at creation.
- var client1 = ConfigCatClient.Get("test1", options => options.PollingMode = PollingModes.AutoPoll(maxInitWaitTime: TimeSpan.Zero));
- var client2 = ConfigCatClient.Get("test2", options => options.PollingMode = PollingModes.ManualPoll);
+ var client1 = ConfigCatClient.Get("test1-7890123456789012/1234567890123456789012", options => options.PollingMode = PollingModes.AutoPoll(maxInitWaitTime: TimeSpan.Zero));
+ var client2 = ConfigCatClient.Get("test2-7890123456789012/1234567890123456789012", options => options.PollingMode = PollingModes.ManualPoll);
instanceCount = ConfigCatClient.Instances.GetAliveCount();
@@ -1316,7 +1366,7 @@ public void CachedInstancesCanBeGCdWhenHookHandlerClosesOverClientInstance()
[MethodImpl(MethodImplOptions.NoInlining)]
static void CreateClients(out int instanceCount)
{
- var client = ConfigCatClient.Get("test1", options => options.PollingMode = PollingModes.AutoPoll(maxInitWaitTime: TimeSpan.Zero));
+ var client = ConfigCatClient.Get("test1-7890123456789012/1234567890123456789012", options => options.PollingMode = PollingModes.AutoPoll(maxInitWaitTime: TimeSpan.Zero));
client.ConfigChanged += (_, e) =>
{
diff --git a/src/ConfigCat.Client.Tests/ConfigEvaluatorTestsBase.cs b/src/ConfigCat.Client.Tests/ConfigEvaluatorTestsBase.cs
deleted file mode 100644
index 728188b5..00000000
--- a/src/ConfigCat.Client.Tests/ConfigEvaluatorTestsBase.cs
+++ /dev/null
@@ -1,114 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
-using ConfigCat.Client.Evaluation;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace ConfigCat.Client.Tests;
-
-public abstract class ConfigEvaluatorTestsBase
-{
-#pragma warning disable IDE1006 // Naming Styles
- private protected readonly LoggerWrapper Logger = new ConsoleLogger(LogLevel.Debug).AsWrapper();
-#pragma warning restore IDE1006 // Naming Styles
-
- private protected readonly IReadOnlyDictionary config;
-
- internal readonly IRolloutEvaluator configEvaluator;
-
- protected abstract string SampleJsonFileName { get; }
-
- protected abstract string MatrixResultFileName { get; }
-
- public ConfigEvaluatorTestsBase()
- {
- this.configEvaluator = new RolloutEvaluator(this.Logger);
-
- this.config = GetSampleJson().Deserialize()!.Settings;
- }
-
- protected virtual void AssertValue(string keyName, string expected, User? user)
- {
- var k = keyName.ToLowerInvariant();
-
- if (k.StartsWith("bool"))
- {
- var actual = this.configEvaluator.Evaluate(this.config, keyName, false, user, null, this.Logger).Value;
-
- Assert.AreEqual(bool.Parse(expected), actual, $"keyName: {keyName} | userId: {user?.Identifier}");
- }
- else if (k.StartsWith("double"))
- {
- var actual = this.configEvaluator.Evaluate(this.config, keyName, double.NaN, user, null, this.Logger).Value;
-
- Assert.AreEqual(double.Parse(expected, CultureInfo.InvariantCulture), actual, $"keyName: {keyName} | userId: {user?.Identifier}");
- }
- else if (k.StartsWith("integer"))
- {
- var actual = this.configEvaluator.Evaluate(this.config, keyName, int.MinValue, user, null, this.Logger).Value;
-
- Assert.AreEqual(int.Parse(expected), actual, $"keyName: {keyName} | userId: {user?.Identifier}");
- }
- else
- {
- var actual = this.configEvaluator.Evaluate(this.config, keyName, string.Empty, user, null, this.Logger).Value;
-
- Assert.AreEqual(expected, actual, $"keyName: {keyName} | userId: {user?.Identifier}");
- }
- }
-
- protected string GetSampleJson()
- {
- using Stream stream = File.OpenRead(Path.Combine("data", SampleJsonFileName));
- using StreamReader reader = new(stream);
- return reader.ReadToEnd();
- }
-
- public async Task MatrixTest(Action assertation)
- {
- using Stream stream = File.OpenRead(Path.Combine("data", MatrixResultFileName));
- using StreamReader reader = new(stream);
- var header = (await reader.ReadLineAsync())!;
-
- var columns = header.Split(new[] { ';' }).ToList();
-
- while (!reader.EndOfStream)
- {
- var rawline = await reader.ReadLineAsync();
-
- if (string.IsNullOrEmpty(rawline))
- {
- continue;
- }
-
- var row = rawline.Split(new[] { ';' });
-
- User? u = null;
-
- if (row[0] != "##null##")
- {
- u = new User(row[0])
- {
- Email = row[1] == "##null##" ? null : row[1],
- Country = row[2] == "##null##" ? null : row[2],
- Custom = row[3] == "##null##" ? null! : new Dictionary { { columns[3], row[3] } }
- };
- }
-
- for (var i = 4; i < columns.Count; i++)
- {
- assertation(columns[i], row[i], u);
- }
- }
- }
-
- [TestCategory("MatrixTests")]
- [TestMethod]
- public async Task Run_MatrixTests()
- {
- await MatrixTest(AssertValue);
- }
-}
diff --git a/src/ConfigCat.Client.Tests/ConfigV1EvaluationTests.cs b/src/ConfigCat.Client.Tests/ConfigV1EvaluationTests.cs
new file mode 100644
index 00000000..6135fa9f
--- /dev/null
+++ b/src/ConfigCat.Client.Tests/ConfigV1EvaluationTests.cs
@@ -0,0 +1,130 @@
+using System.Collections.Generic;
+using ConfigCat.Client.Tests.Helpers;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace ConfigCat.Client.Tests;
+
+[TestClass]
+public class ConfigV1EvaluationTests : EvaluationTestsBase
+{
+ public class BasicTestsDescriptor : IMatrixTestDescriptor
+ {
+ // https://app.configcat.com/08d5a03c-feb7-af1e-a1fa-40b3329f8bed/08d62463-86ec-8fde-f5b5-1c5c426fc830/244cf8b0-f604-11e8-b543-f23c917f9d8d
+ public ConfigLocation ConfigLocation => new ConfigLocation.Cdn("PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A");
+ public string MatrixResultFileName => "testmatrix.csv";
+ public static IEnumerable