XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEExtension.NetCore.AutoConfig

【DLL】自动实现配置文件的存储

公开
关注 0 Fork 0 Star 0
UTF-8
using Xunit;

namespace XFEExtension.NetCore.AutoConfig.Tests;

public class ConcurrentSaveTests
{
    [Fact]
    public void RuntimeSaveStateIsNotIncludedInJsonOrXmlProfiles()
    {
        var profile = new SerializationProfile { Name = "Test" };

        var json = XFEProfile.JsonSaveProfileOperation(profile, [], []);
        var xml = XFEProfile.XmlSaveProfileOperation(profile, [], []);

        Assert.Contains("Name", json);
        Assert.Contains("Name", xml);
        Assert.DoesNotContain(nameof(XFEProfile.AutoSaveDelay), json);
        Assert.DoesNotContain(nameof(XFEProfile.LastSaveException), json);
        Assert.DoesNotContain(nameof(XFEProfile.AutoSaveDelay), xml);
        Assert.DoesNotContain(nameof(XFEProfile.LastSaveException), xml);
    }

    [Fact]
    public async Task ConcurrentChangesAreCoalescedAndPersistTheLatestSnapshot()
    {
        var profileDirectory = Path.Combine(Path.GetTempPath(), "XFEAutoConfigTests", Guid.NewGuid().ToString("N"));
        Directory.CreateDirectory(profileDirectory);
        XFEProfile.ProfilesDefaultPath = profileDirectory;
        var profilePath = Path.Combine(profileDirectory, $"{nameof(ConcurrentProfile)}.xpf");
        ConcurrentProfile.ProfilePath = Path.Combine(profileDirectory, nameof(ConcurrentProfile));

        try
        {
            ConcurrentProfile.Current.AutoSaveDelay = TimeSpan.FromMilliseconds(250);
            ConcurrentProfile.ResetSaveCount();
            var numbers = ConcurrentProfile.Numbers;
            var lookup = ConcurrentProfile.Lookup;

            Parallel.Invoke(
                () => Parallel.For(0, 2_000, index => ConcurrentProfile.Value = index),
                () => Parallel.For(0, 1_000, numbers.Add),
                () => Parallel.For(0, 1_000, index => lookup.TryAdd(index, index)));

            const int finalValue = 123_456;
            ConcurrentProfile.Value = finalValue;
            var expectedContent = $"{finalValue}|1000|1000";
            await WaitForFileContentAsync(profilePath, expectedContent, TimeSpan.FromSeconds(10));
            await Task.Delay(500);

            Assert.Equal(1_000, numbers.Count);
            Assert.Equal(1_000, lookup.Count);
            Assert.Null(ConcurrentProfile.Current.LastSaveException);
            Assert.InRange(ConcurrentProfile.SaveCount, 1, 99);

            var saveCountBeforeManualSave = ConcurrentProfile.SaveCount;
            ConcurrentProfile.SaveProfile();
            Assert.Equal(saveCountBeforeManualSave + 1, ConcurrentProfile.SaveCount);
            Assert.Equal(expectedContent, await File.ReadAllTextAsync(profilePath));
            Assert.Empty(Directory.EnumerateFiles(profileDirectory, "*.tmp"));
        }
        finally
        {
            ConcurrentProfile.DeleteProfile();
            if (Directory.Exists(profileDirectory))
                Directory.Delete(profileDirectory, true);
        }
    }

    [Fact]
    public async Task SaveLoadAndDeleteAreSerializedForTheSamePath()
    {
        var profileDirectory = Path.Combine(Path.GetTempPath(), "XFEAutoConfigTests", Guid.NewGuid().ToString("N"));
        Directory.CreateDirectory(profileDirectory);
        ConcurrentProfile.ProfilePath = Path.Combine(profileDirectory, nameof(ConcurrentProfile));
        ConcurrentProfile.Current.AutoSaveDelay = TimeSpan.FromMilliseconds(20);

        try
        {
            var saveTask = Task.Run(async () =>
            {
                for (var index = 0; index < 30; index++)
                {
                    ConcurrentProfile.Value = index;
                    await ConcurrentProfile.SaveProfileAsync();
                }
            });
            var loadTask = Task.Run(() =>
            {
                for (var index = 0; index < 30; index++)
                    ConcurrentProfile.LoadProfile();
            });
            var deleteTask = Task.Run(() =>
            {
                for (var index = 0; index < 30; index++)
                    ConcurrentProfile.DeleteProfile();
            });

            await Task.WhenAll(saveTask, loadTask, deleteTask);
            ConcurrentProfile.Value = 42;
            await ConcurrentProfile.SaveProfileAsync();

            Assert.Null(ConcurrentProfile.Current.LastSaveException);
            Assert.True(File.Exists(ConcurrentProfile.ProfilePath + ".xpf"));
            Assert.Empty(Directory.EnumerateFiles(profileDirectory, "*.tmp"));
        }
        finally
        {
            ConcurrentProfile.DeleteProfile();
            if (Directory.Exists(profileDirectory))
                Directory.Delete(profileDirectory, true);
        }
    }

    private static async Task WaitForFileContentAsync(string profilePath, string expectedContent, TimeSpan timeout)
    {
        var timeoutAt = DateTime.UtcNow + timeout;
        while (DateTime.UtcNow < timeoutAt)
        {
            if (File.Exists(profilePath))
            {
                try
                {
                    if (await File.ReadAllTextAsync(profilePath) == expectedContent)
                        return;
                }
                catch (IOException)
                {
                }
            }
            await Task.Delay(25);
        }
        throw new TimeoutException($"配置文件未在规定时间内写入预期内容:{profilePath}");
    }
}