using Apq.Cfg.Internal; namespace Apq.Cfg.Tests; /// /// 性能优化相关测试 /// public class PerformanceOptimizationTests : IDisposable { private readonly string _testDir; public PerformanceOptimizationTests() { _testDir = Path.Combine(Path.GetTempPath(), $"ApqCfgPerfTests_{Guid.NewGuid():N}"); Directory.CreateDirectory(_testDir); } public void Dispose() { if (Directory.Exists(_testDir)) { try { Directory.Delete(_testDir, true); } catch { } } } // ========== 批量操作 API 测试 ========== [Fact] public void GetMany_ReturnsAllRequestedKeys() { // Arrange var jsonPath = Path.Combine(_testDir, "config.json"); File.WriteAllText(jsonPath, """ { "Key1": "Value1", "Key2": "Value2", "Key3": "Value3" } """); using var cfg = new CfgBuilder() .AddJson(jsonPath, level: 0, writeable: true) .Build(); // Act var result = cfg.GetMany(new[] { "Key1", "Key2", "Key3" }); // Assert Assert.Equal(3, result.Count); Assert.Equal("Value1", result["Key1"]); Assert.Equal("Value2", result["Key2"]); Assert.Equal("Value3", result["Key3"]); } [Fact] public void GetMany_ReturnsNullForMissingKeys() { // Arrange var jsonPath = Path.Combine(_testDir, "config.json"); File.WriteAllText(jsonPath, """{"Key1": "Value1"}"""); using var cfg = new CfgBuilder() .AddJson(jsonPath, level: 0, writeable: true) .Build(); // Act var result = cfg.GetMany(new[] { "Key1", "NonExistent" }); // Assert Assert.Equal(2, result.Count); Assert.Equal("Value1", result["Key1"]); Assert.Null(result["NonExistent"]); } [Fact] public void GetMany_Generic_ConvertsTypes() { // Arrange var jsonPath = Path.Combine(_testDir, "config.json"); File.WriteAllText(jsonPath, """ { "Int1": "42", "Int2": "100", "Int3": "999" } """); using var cfg = new CfgBuilder() .AddJson(jsonPath, level: 0, writeable: true) .Build(); // Act var result = cfg.GetMany(new[] { "Int1", "Int2", "Int3" }); // Assert Assert.Equal(3, result.Count); Assert.Equal(42, result["Int1"]); Assert.Equal(100, result["Int2"]); Assert.Equal(999, result["Int3"]); } [Fact] public void GetMany_IncludesPendingChanges() { // Arrange var jsonPath = Path.Combine(_testDir, "config.json"); File.WriteAllText(jsonPath, """{"Key1": "Original"}"""); using var cfg = new CfgBuilder() .AddJson(jsonPath, level: 0, writeable: true) .Build(); cfg.Set("Key1", "Modified"); cfg.Set("Key2", "NewValue"); // Act var result = cfg.GetMany(new[] { "Key1", "Key2" }); // Assert Assert.Equal("Modified", result["Key1"]); Assert.Equal("NewValue", result["Key2"]); } [Fact] public void SetMany_SetsMultipleValues() { // Arrange var jsonPath = Path.Combine(_testDir, "config.json"); File.WriteAllText(jsonPath, """{}"""); using var cfg = new CfgBuilder() .AddJson(jsonPath, level: 0, writeable: true) .Build(); // Act cfg.SetMany(new Dictionary { ["Key1"] = "Value1", ["Key2"] = "Value2", ["Key3"] = "Value3" }); // Assert Assert.Equal("Value1", cfg.Get("Key1")); Assert.Equal("Value2", cfg.Get("Key2")); Assert.Equal("Value3", cfg.Get("Key3")); } [Fact] public async Task SetMany_PersistsAfterSave() { // Arrange var jsonPath = Path.Combine(_testDir, "config.json"); File.WriteAllText(jsonPath, "{}"); using var cfg = new CfgBuilder() .AddJson(jsonPath, level: 0, writeable: true) .Build(); // Act cfg.SetMany(new Dictionary { ["Key1"] = "Value1", ["Key2"] = "Value2" }); await cfg.SaveAsync(); // Assert - 重新读取文件验证 var content = File.ReadAllText(jsonPath); Assert.Contains("Key1", content); Assert.Contains("Value1", content); Assert.Contains("Key2", content); Assert.Contains("Value2", content); } // ========== GetMany 回调方式测试(高性能 API)========== [Fact] public void GetMany_Callback_InvokesForEachKey() { // Arrange var jsonPath = Path.Combine(_testDir, "config.json"); File.WriteAllText(jsonPath, """ { "Key1": "Value1", "Key2": "Value2", "Key3": "Value3" } """); using var cfg = new CfgBuilder() .AddJson(jsonPath, level: 0, writeable: true) .Build(); var results = new Dictionary(); // Act cfg.GetMany(new[] { "Key1", "Key2", "Key3" }, (key, value) => { results[key] = value; }); // Assert Assert.Equal(3, results.Count); Assert.Equal("Value1", results["Key1"]); Assert.Equal("Value2", results["Key2"]); Assert.Equal("Value3", results["Key3"]); } [Fact] public void GetMany_Callback_ReturnsNullForMissingKeys() { // Arrange var jsonPath = Path.Combine(_testDir, "config.json"); File.WriteAllText(jsonPath, """{"Key1": "Value1"}"""); using var cfg = new CfgBuilder() .AddJson(jsonPath, level: 0, writeable: true) .Build(); var results = new Dictionary(); // Act cfg.GetMany(new[] { "Key1", "NonExistent" }, (key, value) => { results[key] = value; }); // Assert Assert.Equal(2, results.Count); Assert.Equal("Value1", results["Key1"]); Assert.Null(results["NonExistent"]); } [Fact] public void GetMany_Callback_IncludesPendingChanges() { // Arrange var jsonPath = Path.Combine(_testDir, "config.json"); File.WriteAllText(jsonPath, """{"Key1": "Original"}"""); using var cfg = new CfgBuilder() .AddJson(jsonPath, level: 0, writeable: true) .Build(); cfg.Set("Key1", "Modified"); cfg.Set("Key2", "NewValue"); var results = new Dictionary(); // Act cfg.GetMany(new[] { "Key1", "Key2" }, (key, value) => { results[key] = value; }); // Assert Assert.Equal("Modified", results["Key1"]); Assert.Equal("NewValue", results["Key2"]); } [Fact] public void GetMany_Callback_PreservesKeyOrder() { // Arrange var jsonPath = Path.Combine(_testDir, "config.json"); File.WriteAllText(jsonPath, """ { "A": "1", "B": "2", "C": "3" } """); using var cfg = new CfgBuilder() .AddJson(jsonPath, level: 0, writeable: true) .Build(); var orderedKeys = new List(); // Act cfg.GetMany(new[] { "C", "A", "B" }, (key, value) => { orderedKeys.Add(key); }); // Assert - 回调顺序应与输入顺序一致 Assert.Equal(new[] { "C", "A", "B" }, orderedKeys); } [Fact] public void GetMany_Generic_Callback_ConvertsTypes() { // Arrange var jsonPath = Path.Combine(_testDir, "config.json"); File.WriteAllText(jsonPath, """ { "Int1": "42", "Int2": "100", "Int3": "999" } """); using var cfg = new CfgBuilder() .AddJson(jsonPath, level: 0, writeable: true) .Build(); var results = new Dictionary(); // Act cfg.GetMany(new[] { "Int1", "Int2", "Int3" }, (key, value) => { results[key] = value; }); // Assert Assert.Equal(3, results.Count); Assert.Equal(42, results["Int1"]); Assert.Equal(100, results["Int2"]); Assert.Equal(999, results["Int3"]); } [Fact] public void GetMany_Generic_Callback_ReturnsDefaultForMissingKeys() { // Arrange var jsonPath = Path.Combine(_testDir, "config.json"); File.WriteAllText(jsonPath, """{"Int1": "42"}"""); using var cfg = new CfgBuilder() .AddJson(jsonPath, level: 0, writeable: true) .Build(); var results = new Dictionary(); // Act cfg.GetMany(new[] { "Int1", "NonExistent" }, (key, value) => { results[key] = value; }); // Assert Assert.Equal(2, results.Count); Assert.Equal(42, results["Int1"]); Assert.Equal(default(int), results["NonExistent"]); } [Fact] public void GetMany_Generic_Callback_IncludesPendingChanges() { // Arrange var jsonPath = Path.Combine(_testDir, "config.json"); File.WriteAllText(jsonPath, """{"Int1": "10"}"""); using var cfg = new CfgBuilder() .AddJson(jsonPath, level: 0, writeable: true) .Build(); cfg.Set("Int1", "20"); cfg.Set("Int2", "30"); var results = new Dictionary(); // Act cfg.GetMany(new[] { "Int1", "Int2" }, (key, value) => { results[key] = value; }); // Assert Assert.Equal(20, results["Int1"]); Assert.Equal(30, results["Int2"]); } [Fact] public void GetMany_Callback_WithEmptyKeys_DoesNotInvokeCallback() { // Arrange var jsonPath = Path.Combine(_testDir, "config.json"); File.WriteAllText(jsonPath, """{"Key1": "Value1"}"""); using var cfg = new CfgBuilder() .AddJson(jsonPath, level: 0, writeable: true) .Build(); var callCount = 0; // Act cfg.GetMany(Array.Empty(), (key, value) => { callCount++; }); // Assert Assert.Equal(0, callCount); } // ========== KeyPathParser 测试 ========== [Fact] public void KeyPathParser_GetFirstSegment_ReturnsFirstPart() { // Act & Assert Assert.Equal("Database", KeyPathParser.GetFirstSegment("Database:ConnectionString").ToString()); Assert.Equal("SingleKey", KeyPathParser.GetFirstSegment("SingleKey").ToString()); Assert.Equal("A", KeyPathParser.GetFirstSegment("A:B:C").ToString()); } [Fact] public void KeyPathParser_GetRemainder_ReturnsRestAfterFirstSegment() { // Act & Assert Assert.Equal("ConnectionString", KeyPathParser.GetRemainder("Database:ConnectionString").ToString()); Assert.True(KeyPathParser.GetRemainder("SingleKey").IsEmpty); Assert.Equal("B:C", KeyPathParser.GetRemainder("A:B:C").ToString()); } [Fact] public void KeyPathParser_GetLastSegment_ReturnsLastPart() { // Act & Assert Assert.Equal("ConnectionString", KeyPathParser.GetLastSegment("Database:ConnectionString").ToString()); Assert.Equal("SingleKey", KeyPathParser.GetLastSegment("SingleKey").ToString()); Assert.Equal("C", KeyPathParser.GetLastSegment("A:B:C").ToString()); } [Fact] public void KeyPathParser_GetParentPath_ReturnsPathWithoutLastSegment() { // Act & Assert Assert.Equal("Database", KeyPathParser.GetParentPath("Database:ConnectionString").ToString()); Assert.True(KeyPathParser.GetParentPath("SingleKey").IsEmpty); Assert.Equal("A:B", KeyPathParser.GetParentPath("A:B:C").ToString()); } [Fact] public void KeyPathParser_GetDepth_ReturnsCorrectDepth() { // Act & Assert Assert.Equal(0, KeyPathParser.GetDepth("")); Assert.Equal(1, KeyPathParser.GetDepth("SingleKey")); Assert.Equal(2, KeyPathParser.GetDepth("Database:ConnectionString")); Assert.Equal(3, KeyPathParser.GetDepth("A:B:C")); } [Fact] public void KeyPathParser_StartsWithSegment_MatchesCorrectly() { // Act & Assert Assert.True(KeyPathParser.StartsWithSegment("Database:ConnectionString", "Database")); Assert.True(KeyPathParser.StartsWithSegment("Database", "Database")); Assert.False(KeyPathParser.StartsWithSegment("DatabaseBackup:Path", "Database")); Assert.False(KeyPathParser.StartsWithSegment("Other:Key", "Database")); } [Fact] public void KeyPathParser_Combine_JoinsPathsCorrectly() { // Act & Assert Assert.Equal("Database:ConnectionString", KeyPathParser.Combine("Database", "ConnectionString")); Assert.Equal("ConnectionString", KeyPathParser.Combine("", "ConnectionString")); Assert.Equal("Database", KeyPathParser.Combine("Database", "")); Assert.Equal("A:B:C", KeyPathParser.Combine("A:B", "C")); } [Fact] public void KeyPathParser_EnumerateSegments_IteratesAllSegments() { // Arrange var segments = new List(); // Act foreach (var segment in KeyPathParser.EnumerateSegments("A:B:C:D")) { segments.Add(segment.ToString()); } // Assert Assert.Equal(4, segments.Count); Assert.Equal("A", segments[0]); Assert.Equal("B", segments[1]); Assert.Equal("C", segments[2]); Assert.Equal("D", segments[3]); } // ========== ValueCache 测试 ========== [Fact] public void ValueCache_SetAndGet_WorksCorrectly() { // Arrange var cache = new ValueCache(); // Act cache.Set("Key1", 42); cache.Set("Key2", "StringValue"); // Assert Assert.True(cache.TryGet("Key1", out var intValue)); Assert.Equal(42, intValue); Assert.True(cache.TryGet("Key2", out var stringValue)); Assert.Equal("StringValue", stringValue); } [Fact] public void ValueCache_TryGet_ReturnsFalseForMissingKey() { // Arrange var cache = new ValueCache(); // Act & Assert Assert.False(cache.TryGet("NonExistent", out _)); } [Fact] public void ValueCache_DifferentTypes_CachedSeparately() { // Arrange var cache = new ValueCache(); // Act cache.Set("Key", 42); cache.Set("Key", "42"); // Assert Assert.True(cache.TryGet("Key", out var intValue)); Assert.Equal(42, intValue); Assert.True(cache.TryGet("Key", out var stringValue)); Assert.Equal("42", stringValue); } [Fact] public void ValueCache_Invalidate_RemovesKey() { // Arrange var cache = new ValueCache(); cache.Set("Key1", 42); cache.Set("Key2", 100); // Act cache.Invalidate("Key1"); // Assert Assert.False(cache.TryGet("Key1", out _)); Assert.True(cache.TryGet("Key2", out _)); } [Fact] public void ValueCache_Clear_RemovesAllKeys() { // Arrange var cache = new ValueCache(); cache.Set("Key1", 42); cache.Set("Key2", 100); // Act cache.Clear(); // Assert Assert.False(cache.TryGet("Key1", out _)); Assert.False(cache.TryGet("Key2", out _)); } [Fact] public void ValueCache_Version_IncrementsOnInvalidate() { // Arrange var cache = new ValueCache(); var initialVersion = cache.Version; // Act cache.Set("Key", 42); var afterSetVersion = cache.Version; cache.Invalidate("Key"); var afterInvalidateVersion = cache.Version; // Assert Assert.Equal(initialVersion, afterSetVersion); // Set 不改变版本 Assert.Equal(initialVersion + 1, afterInvalidateVersion); // Invalidate 增加版本 } // ========== FastCollections 测试 ========== [Fact] public void FastReadOnlyDictionary_WorksCorrectly() { // Arrange var source = new Dictionary { ["A"] = 1, ["B"] = 2, ["C"] = 3 }; // Act var fast = source.ToFastReadOnly(); // Assert Assert.Equal(3, fast.Count); Assert.Equal(1, fast["A"]); Assert.Equal(2, fast["B"]); Assert.Equal(3, fast["C"]); Assert.True(fast.ContainsKey("A")); Assert.False(fast.ContainsKey("D")); Assert.True(fast.TryGetValue("B", out var value)); Assert.Equal(2, value); } [Fact] public void FastReadOnlySet_WorksCorrectly() { // Arrange var source = new[] { "A", "B", "C" }; // Act var fast = source.ToFastReadOnlySet(); // Assert Assert.Equal(3, fast.Count); Assert.True(fast.Contains("A")); Assert.True(fast.Contains("B")); Assert.True(fast.Contains("C")); Assert.False(fast.Contains("D")); } }