From 12c87c3a7da94dc8b4b4e1ccfa3379f7e4f699bd Mon Sep 17 00:00:00 2001 From: Ali Bahrami Date: Sat, 26 Jul 2025 21:00:23 +0200 Subject: [PATCH 1/3] Update to .NET 9 and modernize project dependencies Upgraded target frameworks to include .NET 9 and updated NuGet package dependencies to the latest versions. Improved code formatting, applied modern C# features, and enhanced unit test assertions with FluentAssertions for better readability. Updated GitHub Actions workflow and documentation for compatibility with these changes. --- .github/workflows/dotnet.yml | 32 +- AutoNumber/AutoNumber.csproj | 80 ++--- AutoNumber/BlobOptimisticDataStore.cs | 32 +- AutoNumber/DebugOnlyFileDataStore.cs | 6 +- .../Options/AutoNumberOptionsBuilder.cs | 2 +- AutoNumber/ScopeState.cs | 2 +- AutoNumber/UniqueIdGenerator.cs | 4 +- IntegrationTests/Azure.cs | 81 +++-- IntegrationTests/DependencyInjectionTest.cs | 142 ++++---- IntegrationTests/File.cs | 63 ++-- IntegrationTests/ITestScope.cs | 11 +- IntegrationTests/IntegrationTests.csproj | 85 ++--- IntegrationTests/Scenarios.cs | 305 +++++++++--------- README.md | 22 +- UnitTests/DictionaryExtentionsTests.cs | 127 ++++---- UnitTests/UniqueIdGeneratorTest.cs | 219 +++++++------ UnitTests/UnitTests.csproj | 78 ++--- 17 files changed, 636 insertions(+), 655 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index c159cd1..749d523 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -12,19 +12,19 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Install dependencies - run: dotnet restore - - name: Build - run: dotnet build --configuration Release --no-restore - - name: Running Azurite - run: docker run -d -p 10000:10000 -p 10001:10001 mcr.microsoft.com/azure-storage/azurite - - name: Test - run: dotnet test --no-restore - - name: Create the package - run: dotnet pack --configuration Release - - name: Publish the package to NUGET - env: # Set the secret as an input - NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} - if: ${{ github.ref == 'refs/heads/main' }} - run: dotnet nuget push AutoNumber/bin/Release/*.nupkg --source https://api.nuget.org/v3/index.json --api-key $NUGET_API_KEY + - uses: actions/checkout@v2 + - name: Install dependencies + run: dotnet restore + - name: Build + run: dotnet build --configuration Release --no-restore + - name: Running Azurite + run: docker run -d -p 10000:10000 -p 10001:10001 mcr.microsoft.com/azure-storage/azurite + - name: Test + run: dotnet test --no-restore + - name: Create the package + run: dotnet pack --configuration Release + - name: Publish the package to NUGET + env: # Set the secret as an input + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + if: ${{ github.ref == 'refs/heads/main' }} + run: dotnet nuget push AutoNumber/bin/Release/*.nupkg --source https://api.nuget.org/v3/index.json --api-key $NUGET_API_KEY diff --git a/AutoNumber/AutoNumber.csproj b/AutoNumber/AutoNumber.csproj index c2943c8..570b2d1 100644 --- a/AutoNumber/AutoNumber.csproj +++ b/AutoNumber/AutoNumber.csproj @@ -1,41 +1,43 @@  - - net6.0;net8.0;netstandard2.0;netstandard2.1 - 8.0.30703 - ..\ - SnowMaker - AzureAutoNumber - High performance, distributed unique id generator for Azure environments. - 1.4.0 - 1.5.0.0 - 1.5.0.0 - bin\$(Configuration)\ - Ali Bahraminezhad - MS-PL - https://github.com/0x414c49/AzureAutoNumber - Azure - AutoNumber - AutoNumber - AutoNumber - true - * .NET 8 -* All azure and other nuget dependencies upgraded - AzureAutoNumber - 1.5.0 - False - - - full - - - pdbonly - - - - - - - - - + + net6.0;net8.0;net9.0;netstandard2.0;netstandard2.1 + 8.0.30703 + ..\ + SnowMaker + AzureAutoNumber + High performance, distributed unique id generator for Azure environments. + 1.5.2 + 1.5.2.0 + 9.0 + 1.5.2.0 + bin\$(Configuration)\ + Ali Bahraminezhad + MS-PL + https://github.com/0x414c49/AzureAutoNumber + Azure + AutoNumber + AutoNumber + AutoNumber + true + * .NET 8 + * All azure and other nuget dependencies upgraded + + AzureAutoNumber + 1.5.0 + False + + + full + + + pdbonly + + + + + + + + + diff --git a/AutoNumber/BlobOptimisticDataStore.cs b/AutoNumber/BlobOptimisticDataStore.cs index f0e5f1e..e378f69 100644 --- a/AutoNumber/BlobOptimisticDataStore.cs +++ b/AutoNumber/BlobOptimisticDataStore.cs @@ -19,7 +19,7 @@ public class BlobOptimisticDataStore : IOptimisticDataStore private const string SeedValue = "1"; private readonly BlobContainerClient blobContainer; private readonly ConcurrentDictionary blobReferences; - private readonly object blobReferencesLock = new object(); + private readonly object blobReferencesLock = new(); public BlobOptimisticDataStore(BlobServiceClient blobServiceClient, string containerName) { @@ -36,22 +36,18 @@ public string GetData(string blockName) { var blobReference = GetBlobReference(blockName); - using (var stream = new MemoryStream()) - { - blobReference.DownloadTo(stream); - return Encoding.UTF8.GetString(stream.ToArray()); - } + using var stream = new MemoryStream(); + blobReference.DownloadTo(stream); + return Encoding.UTF8.GetString(stream.ToArray()); } public async Task GetDataAsync(string blockName) { var blobReference = GetBlobReference(blockName); - using (var stream = new MemoryStream()) - { - await blobReference.DownloadToAsync(stream).ConfigureAwait(false); - return Encoding.UTF8.GetString(stream.ToArray()); - } + using var stream = new MemoryStream(); + await blobReference.DownloadToAsync(stream).ConfigureAwait(false); + return Encoding.UTF8.GetString(stream.ToArray()); } public async Task InitAsync() @@ -73,7 +69,7 @@ public bool TryOptimisticWrite(string blockName, string data) { var blobRequestCondition = new BlobRequestConditions { - IfMatch = (blobReference.GetProperties()).Value.ETag + IfMatch = blobReference.GetProperties().Value.ETag }; UploadText( blobReference, @@ -155,10 +151,8 @@ private async Task UploadTextAsync(BlockBlobClient blob, string text, BlobReques ContentType = "text/plain" }; - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(text))) - { - await blob.UploadAsync(stream, header, null, accessCondition, null, null).ConfigureAwait(false); - } + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(text)); + await blob.UploadAsync(stream, header, null, accessCondition).ConfigureAwait(false); } private void UploadText(BlockBlobClient blob, string text, BlobRequestConditions accessCondition) @@ -168,10 +162,8 @@ private void UploadText(BlockBlobClient blob, string text, BlobRequestConditions ContentType = "text/plain" }; - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(text))) - { - blob.Upload(stream, header, null, accessCondition, null, null); - } + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(text)); + blob.Upload(stream, header, null, accessCondition); } } } \ No newline at end of file diff --git a/AutoNumber/DebugOnlyFileDataStore.cs b/AutoNumber/DebugOnlyFileDataStore.cs index 0c93bfd..3064d55 100644 --- a/AutoNumber/DebugOnlyFileDataStore.cs +++ b/AutoNumber/DebugOnlyFileDataStore.cs @@ -27,10 +27,8 @@ public string GetData(string blockName) { var file = File.Create(blockPath); - using (var streamWriter = new StreamWriter(file)) - { - streamWriter.Write(SeedValue); - } + using var streamWriter = new StreamWriter(file); + streamWriter.Write(SeedValue); return SeedValue; } diff --git a/AutoNumber/Options/AutoNumberOptionsBuilder.cs b/AutoNumber/Options/AutoNumberOptionsBuilder.cs index 9dce0e7..7fe8960 100644 --- a/AutoNumber/Options/AutoNumberOptionsBuilder.cs +++ b/AutoNumber/Options/AutoNumberOptionsBuilder.cs @@ -16,7 +16,7 @@ public AutoNumberOptionsBuilder(IConfiguration configuration) configuration.GetSection(AutoNumber).Bind(Options); } - public AutoNumberOptions Options { get; } = new AutoNumberOptions(); + public AutoNumberOptions Options { get; } = new(); /// /// Uses the default StorageAccount already defined in dependency injection diff --git a/AutoNumber/ScopeState.cs b/AutoNumber/ScopeState.cs index 7f026b8..30a18b9 100644 --- a/AutoNumber/ScopeState.cs +++ b/AutoNumber/ScopeState.cs @@ -2,7 +2,7 @@ namespace AutoNumber { internal class ScopeState { - public readonly object IdGenerationLock = new object(); + public readonly object IdGenerationLock = new(); public long HighestIdAvailableInBatch; public long LastId; } diff --git a/AutoNumber/UniqueIdGenerator.cs b/AutoNumber/UniqueIdGenerator.cs index 765fa88..3a3af48 100644 --- a/AutoNumber/UniqueIdGenerator.cs +++ b/AutoNumber/UniqueIdGenerator.cs @@ -58,7 +58,7 @@ private void UpdateFromSyncStore(string scopeName, ScopeState state) var firstIdInNextBatch = state.HighestIdAvailableInBatch + 1; if (optimisticDataStore.TryOptimisticWrite(scopeName, - firstIdInNextBatch.ToString(CultureInfo.InvariantCulture))) + firstIdInNextBatch.ToString(CultureInfo.InvariantCulture))) return; writesAttempted++; @@ -72,7 +72,7 @@ private void UpdateFromSyncStore(string scopeName, ScopeState state) private readonly IOptimisticDataStore optimisticDataStore; private readonly IDictionary states = new Dictionary(); - private readonly object statesLock = new object(); + private readonly object statesLock = new(); private int maxWriteAttempts = 25; #endregion diff --git a/IntegrationTests/Azure.cs b/IntegrationTests/Azure.cs index 03d0b3d..4f3ff4b 100644 --- a/IntegrationTests/Azure.cs +++ b/IntegrationTests/Azure.cs @@ -7,58 +7,55 @@ using Azure.Storage.Blobs.Specialized; using NUnit.Framework; -namespace IntegrationTests.cs -{ - [TestFixture] - public class Azure : Scenarios - { - private readonly BlobServiceClient blobServiceClient = new BlobServiceClient("UseDevelopmentStorage=true"); +namespace IntegrationTests.cs; - protected override TestScope BuildTestScope() - { - return new TestScope(new BlobServiceClient("UseDevelopmentStorage=true")); - } +[TestFixture] +public class Azure : Scenarios +{ + private readonly BlobServiceClient blobServiceClient = new("UseDevelopmentStorage=true"); - protected override IOptimisticDataStore BuildStore(TestScope scope) - { - var blobOptimisticDataStore = new BlobOptimisticDataStore(blobServiceClient, scope.ContainerName); - blobOptimisticDataStore.Init(); - return blobOptimisticDataStore; - } + protected override TestScope BuildTestScope() + { + return new TestScope(new BlobServiceClient("UseDevelopmentStorage=true")); } - public sealed class TestScope : ITestScope + protected override IOptimisticDataStore BuildStore(TestScope scope) { - private readonly BlobServiceClient blobServiceClient; + var blobOptimisticDataStore = new BlobOptimisticDataStore(blobServiceClient, scope.ContainerName); + blobOptimisticDataStore.Init(); + return blobOptimisticDataStore; + } +} + +public sealed class TestScope : ITestScope +{ + private readonly BlobServiceClient blobServiceClient; - public TestScope(BlobServiceClient blobServiceClient) - { - var ticks = DateTime.UtcNow.Ticks; - IdScopeName = string.Format("autonumbertest{0}", ticks); - ContainerName = string.Format("autonumbertest{0}", ticks); + public TestScope(BlobServiceClient blobServiceClient) + { + var ticks = DateTime.UtcNow.Ticks; + IdScopeName = string.Format("autonumbertest{0}", ticks); + ContainerName = string.Format("autonumbertest{0}", ticks); - this.blobServiceClient = blobServiceClient; - } + this.blobServiceClient = blobServiceClient; + } - public string ContainerName { get; } + public string ContainerName { get; } - public string IdScopeName { get; } + public string IdScopeName { get; } - public string ReadCurrentPersistedValue() - { - var blobContainer = blobServiceClient.GetBlobContainerClient(ContainerName); - var blob = blobContainer.GetBlockBlobClient(IdScopeName); - using (var stream = new MemoryStream()) - { - blob.DownloadToAsync(stream).GetAwaiter().GetResult(); - return Encoding.UTF8.GetString(stream.ToArray()); - } - } + public string ReadCurrentPersistedValue() + { + var blobContainer = blobServiceClient.GetBlobContainerClient(ContainerName); + var blob = blobContainer.GetBlockBlobClient(IdScopeName); + using var stream = new MemoryStream(); + blob.DownloadToAsync(stream).GetAwaiter().GetResult(); + return Encoding.UTF8.GetString(stream.ToArray()); + } - public void Dispose() - { - var blobContainer = blobServiceClient.GetBlobContainerClient(ContainerName); - blobContainer.DeleteAsync().GetAwaiter().GetResult(); - } + public void Dispose() + { + var blobContainer = blobServiceClient.GetBlobContainerClient(ContainerName); + blobContainer.DeleteAsync().GetAwaiter().GetResult(); } } \ No newline at end of file diff --git a/IntegrationTests/DependencyInjectionTest.cs b/IntegrationTests/DependencyInjectionTest.cs index cccd401..191e2e3 100644 --- a/IntegrationTests/DependencyInjectionTest.cs +++ b/IntegrationTests/DependencyInjectionTest.cs @@ -2,99 +2,99 @@ using AutoNumber.Interfaces; using AutoNumber.Options; using Azure.Storage.Blobs; +using FluentAssertions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using NUnit.Framework; -namespace AutoNumber.IntegrationTests +namespace AutoNumber.IntegrationTests; + +[TestFixture] +public class DependencyInjectionTest { - [TestFixture] - public class DependencyInjectionTest + public IConfigurationRoot Configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true).Build(); + + private ServiceProvider GenerateServiceProvider() { - public IConfigurationRoot Configuration = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", true, true).Build(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(new BlobServiceClient("UseDevelopmentStorage=true")); + serviceCollection.AddSingleton(Configuration); + serviceCollection.AddAutoNumber(); + return serviceCollection.BuildServiceProvider(); + } - private ServiceProvider GenerateServiceProvider() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(new BlobServiceClient("UseDevelopmentStorage=true")); - serviceCollection.AddSingleton(Configuration); - serviceCollection.AddAutoNumber(); - return serviceCollection.BuildServiceProvider(); - } - - [Test] - public void OptionsBuilderShouldGenerateOptions() - { - var serviceProvider = GenerateServiceProvider(); - var optionsBuilder = new AutoNumberOptionsBuilder(serviceProvider.GetService()); + [Test] + public void OptionsBuilderShouldGenerateOptions() + { + var serviceProvider = GenerateServiceProvider(); + var optionsBuilder = new AutoNumberOptionsBuilder(serviceProvider.GetService()); - optionsBuilder.SetBatchSize(5); - Assert.AreEqual(5, optionsBuilder.Options.BatchSize); + optionsBuilder.SetBatchSize(5); + Assert.Equals(5, optionsBuilder.Options.BatchSize); - optionsBuilder.SetMaxWriteAttempts(10); - Assert.AreEqual(10, optionsBuilder.Options.MaxWriteAttempts); + optionsBuilder.SetMaxWriteAttempts(10); + Assert.Equals(10, optionsBuilder.Options.MaxWriteAttempts); - optionsBuilder.UseDefaultContainerName(); - Assert.AreEqual("unique-urls", optionsBuilder.Options.StorageContainerName); + optionsBuilder.UseDefaultContainerName(); + Assert.Equals("unique-urls", optionsBuilder.Options.StorageContainerName); - optionsBuilder.UseContainerName("test"); - Assert.AreEqual("test", optionsBuilder.Options.StorageContainerName); + optionsBuilder.UseContainerName("test"); + Assert.Equals("test", optionsBuilder.Options.StorageContainerName); - optionsBuilder.UseDefaultStorageAccount(); - Assert.AreEqual(null, optionsBuilder.Options.StorageAccountConnectionString); + optionsBuilder.UseDefaultStorageAccount(); + Assert.Equals(null, optionsBuilder.Options.StorageAccountConnectionString); - optionsBuilder.UseStorageAccount("test"); - Assert.AreEqual("test123", optionsBuilder.Options.StorageAccountConnectionString); + optionsBuilder.UseStorageAccount("test"); + Assert.Equals("test123", optionsBuilder.Options.StorageAccountConnectionString); - optionsBuilder.UseStorageAccount("test-22"); - Assert.AreEqual("test-22", optionsBuilder.Options.StorageAccountConnectionString); - } + optionsBuilder.UseStorageAccount("test-22"); + Assert.Equals("test-22", optionsBuilder.Options.StorageAccountConnectionString); + } - [Test] - public void ShouldCraeteUniqueIdGenerator() - { - var serviceProvider = GenerateServiceProvider(); + [Test] + public void ShouldCraeteUniqueIdGenerator() + { + var serviceProvider = GenerateServiceProvider(); - var uniqueId = serviceProvider.GetService(); + var uniqueId = serviceProvider.GetService(); - Assert.NotNull(uniqueId); - } + uniqueId.Should().NotBeNull(); + } - [Test] - public void ShouldOptionsContainsDefaultValues() - { - var serviceProvider = GenerateServiceProvider(); + [Test] + public void ShouldOptionsContainsDefaultValues() + { + var serviceProvider = GenerateServiceProvider(); + + var options = serviceProvider.GetService>(); - var options = serviceProvider.GetService>(); + options.Value.Should().NotBeNull(); + Assert.Equals(25, options.Value.MaxWriteAttempts); + Assert.Equals(50, options.Value.BatchSize); + Assert.Equals("unique-urls", options.Value.StorageContainerName); + } - Assert.NotNull(options.Value); - Assert.AreEqual(25, options.Value.MaxWriteAttempts); - Assert.AreEqual(50, options.Value.BatchSize); - Assert.AreEqual("unique-urls", options.Value.StorageContainerName); - } + [Test] + public void ShouldResolveUniqueIdGenerator() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(new BlobServiceClient("UseDevelopmentStorage=true")); - [Test] - public void ShouldResolveUniqueIdGenerator() + serviceCollection.AddAutoNumber(Configuration, x => { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(new BlobServiceClient("UseDevelopmentStorage=true")); - - serviceCollection.AddAutoNumber(Configuration, x => - { - return x.UseContainerName("ali") - .UseDefaultStorageAccount() - .SetBatchSize(10) - .SetMaxWriteAttempts() - .Options; - }); - - var service = serviceCollection.BuildServiceProvider() - .GetService(); - - Assert.NotNull(service); - } + return x.UseContainerName("ali") + .UseDefaultStorageAccount() + .SetBatchSize(10) + .SetMaxWriteAttempts() + .Options; + }); + + var service = serviceCollection.BuildServiceProvider() + .GetService(); + + service.Should().NotBeNull(); } } \ No newline at end of file diff --git a/IntegrationTests/File.cs b/IntegrationTests/File.cs index 27aceae..a7dfd27 100644 --- a/IntegrationTests/File.cs +++ b/IntegrationTests/File.cs @@ -4,47 +4,46 @@ using AutoNumber.Interfaces; using NUnit.Framework; -namespace IntegrationTests.cs +namespace IntegrationTests.cs; + +[TestFixture] +public class File : Scenarios { - [TestFixture] - public class File : Scenarios + protected override TestScope BuildTestScope() + { + return new TestScope(); + } + + protected override IOptimisticDataStore BuildStore(TestScope scope) + { + return new DebugOnlyFileDataStore(scope.DirectoryPath); + } + + public class TestScope : ITestScope { - protected override TestScope BuildTestScope() + public TestScope() { - return new TestScope(); + var ticks = DateTime.UtcNow.Ticks; + IdScopeName = string.Format("AutoNumbertest{0}", ticks); + + DirectoryPath = Path.Combine(Path.GetTempPath(), IdScopeName); + Directory.CreateDirectory(DirectoryPath); } - protected override IOptimisticDataStore BuildStore(TestScope scope) + public string DirectoryPath { get; } + + public string IdScopeName { get; } + + public string ReadCurrentPersistedValue() { - return new DebugOnlyFileDataStore(scope.DirectoryPath); + var filePath = Path.Combine(DirectoryPath, string.Format("{0}.txt", IdScopeName)); + return System.IO.File.ReadAllText(filePath); } - public class TestScope : ITestScope + public void Dispose() { - public TestScope() - { - var ticks = DateTime.UtcNow.Ticks; - IdScopeName = string.Format("AutoNumbertest{0}", ticks); - - DirectoryPath = Path.Combine(Path.GetTempPath(), IdScopeName); - Directory.CreateDirectory(DirectoryPath); - } - - public string DirectoryPath { get; } - - public string IdScopeName { get; } - - public string ReadCurrentPersistedValue() - { - var filePath = Path.Combine(DirectoryPath, string.Format("{0}.txt", IdScopeName)); - return System.IO.File.ReadAllText(filePath); - } - - public void Dispose() - { - if (Directory.Exists(DirectoryPath)) - Directory.Delete(DirectoryPath, true); - } + if (Directory.Exists(DirectoryPath)) + Directory.Delete(DirectoryPath, true); } } } \ No newline at end of file diff --git a/IntegrationTests/ITestScope.cs b/IntegrationTests/ITestScope.cs index 4b64b30..e678a38 100644 --- a/IntegrationTests/ITestScope.cs +++ b/IntegrationTests/ITestScope.cs @@ -1,11 +1,10 @@ using System; -namespace IntegrationTests.cs +namespace IntegrationTests.cs; + +public interface ITestScope : IDisposable { - public interface ITestScope : IDisposable - { - string IdScopeName { get; } + string IdScopeName { get; } - string ReadCurrentPersistedValue(); - } + string ReadCurrentPersistedValue(); } \ No newline at end of file diff --git a/IntegrationTests/IntegrationTests.csproj b/IntegrationTests/IntegrationTests.csproj index 4187655..966bbab 100644 --- a/IntegrationTests/IntegrationTests.csproj +++ b/IntegrationTests/IntegrationTests.csproj @@ -1,44 +1,45 @@  - - 8.0.30703 - IntegrationTests.cs - IntegrationTests.cs - ..\ - IntegrationTests.cs - Microsoft - IntegrationTests.cs - Copyright © Microsoft 2011 - bin\$(Configuration)\ - net6.0;net8.0 - - - full - - - pdbonly - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - Always - - + + 8.0.30703 + IntegrationTests.cs + IntegrationTests.cs + ..\ + IntegrationTests.cs + Microsoft + IntegrationTests.cs + Copyright © Microsoft 2011 + bin\$(Configuration)\ + net8.0;net9.0 + + + full + + + pdbonly + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + Always + + \ No newline at end of file diff --git a/IntegrationTests/Scenarios.cs b/IntegrationTests/Scenarios.cs index f63f8c0..6e5b559 100644 --- a/IntegrationTests/Scenarios.cs +++ b/IntegrationTests/Scenarios.cs @@ -4,179 +4,168 @@ using System.Threading.Tasks; using AutoNumber; using AutoNumber.Interfaces; +using FluentAssertions; using NUnit.Framework; +using NUnit.Framework.Legacy; -namespace IntegrationTests.cs +namespace IntegrationTests.cs; + +public abstract class Scenarios where TTestScope : ITestScope { - public abstract class Scenarios where TTestScope : ITestScope + protected abstract IOptimisticDataStore BuildStore(TTestScope scope); + protected abstract TTestScope BuildTestScope(); + + [Test] + public void ShouldReturnOneForFirstIdInNewScope() { - protected abstract IOptimisticDataStore BuildStore(TTestScope scope); - protected abstract TTestScope BuildTestScope(); + // Arrange + using var testScope = BuildTestScope(); + var store = BuildStore(testScope); + var generator = new UniqueIdGenerator(store) { BatchSize = 3 }; - [Test] - public void ShouldReturnOneForFirstIdInNewScope() - { - // Arrange - using (var testScope = BuildTestScope()) - { - var store = BuildStore(testScope); - var generator = new UniqueIdGenerator(store) {BatchSize = 3}; + // Act + var generatedId = generator.NextId(testScope.IdScopeName); - // Act - var generatedId = generator.NextId(testScope.IdScopeName); + // Assert + Assert.Equals(1, generatedId); + } - // Assert - Assert.AreEqual(1, generatedId); - } - } + [Test] + public void ShouldInitializeBlobForFirstIdInNewScope() + { + // Arrange + using var testScope = BuildTestScope(); + var store = BuildStore(testScope); + var generator = new UniqueIdGenerator(store) { BatchSize = 3 }; - [Test] - public void ShouldInitializeBlobForFirstIdInNewScope() - { - // Arrange - using (var testScope = BuildTestScope()) - { - var store = BuildStore(testScope); - var generator = new UniqueIdGenerator(store) {BatchSize = 3}; + // Act + generator.NextId(testScope.IdScopeName); //1 - // Act - generator.NextId(testScope.IdScopeName); //1 + // Assert + Assert.Equals("4", testScope.ReadCurrentPersistedValue()); + } + + [Test] + public void ShouldNotUpdateBlobAtEndOfBatch() + { + // Arrange + using var testScope = BuildTestScope(); + var store = BuildStore(testScope); + var generator = new UniqueIdGenerator(store) { BatchSize = 3 }; + + // Act + generator.NextId(testScope.IdScopeName); //1 + generator.NextId(testScope.IdScopeName); //2 + generator.NextId(testScope.IdScopeName); //3 + + // Assert + Assert.Equals("4", testScope.ReadCurrentPersistedValue()); + } + + [Test] + public void ShouldUpdateBlobWhenGeneratingNextIdAfterEndOfBatch() + { + // Arrange + using var testScope = BuildTestScope(); + var store = BuildStore(testScope); + var generator = new UniqueIdGenerator(store) { BatchSize = 3 }; + + // Act + generator.NextId(testScope.IdScopeName); //1 + generator.NextId(testScope.IdScopeName); //2 + generator.NextId(testScope.IdScopeName); //3 + generator.NextId(testScope.IdScopeName); //4 + + // Assert + Assert.Equals("7", testScope.ReadCurrentPersistedValue()); + } - // Assert - Assert.AreEqual("4", testScope.ReadCurrentPersistedValue()); - } - } + [Test] + public void ShouldReturnIdsFromThirdBatchIfSecondBatchTakenByAnotherGenerator() + { + // Arrange + using var testScope = BuildTestScope(); + var store1 = BuildStore(testScope); + var generator1 = new UniqueIdGenerator(store1) { BatchSize = 3 }; + var store2 = BuildStore(testScope); + var generator2 = new UniqueIdGenerator(store2) { BatchSize = 3 }; + + // Act + generator1.NextId(testScope.IdScopeName); //1 + generator1.NextId(testScope.IdScopeName); //2 + generator1.NextId(testScope.IdScopeName); //3 + generator2.NextId(testScope.IdScopeName); //4 + var lastId = generator1.NextId(testScope.IdScopeName); //7 + + // Assert + Assert.Equals(7, lastId); + } - [Test] - public void ShouldNotUpdateBlobAtEndOfBatch() + [Test] + public void ShouldReturnIdsAcrossMultipleGenerators() + { + // Arrange + using var testScope = BuildTestScope(); + var store1 = BuildStore(testScope); + var generator1 = new UniqueIdGenerator(store1) { BatchSize = 3 }; + var store2 = BuildStore(testScope); + var generator2 = new UniqueIdGenerator(store2) { BatchSize = 3 }; + + // Act + var generatedIds = new[] { - // Arrange - using (var testScope = BuildTestScope()) + generator1.NextId(testScope.IdScopeName), //1 + generator1.NextId(testScope.IdScopeName), //2 + generator1.NextId(testScope.IdScopeName), //3 + generator2.NextId(testScope.IdScopeName), //4 + generator1.NextId(testScope.IdScopeName), //7 + generator2.NextId(testScope.IdScopeName), //5 + generator2.NextId(testScope.IdScopeName), //6 + generator2.NextId(testScope.IdScopeName), //10 + generator1.NextId(testScope.IdScopeName), //8 + generator1.NextId(testScope.IdScopeName) //9 + }; + + // Assert + CollectionAssert.Equals( + new[] { 1, 2, 3, 4, 7, 5, 6, 10, 8, 9 }, + generatedIds); + } + + [Test] + public void ShouldSupportUsingOneGeneratorFromMultipleThreads() + { + // Arrange + using var testScope = BuildTestScope(); + var store = BuildStore(testScope); + var generator = new UniqueIdGenerator(store) { BatchSize = 1000 }; + const int testLength = 10000; + + // Act + var generatedIds = new ConcurrentQueue(); + var threadIds = new ConcurrentQueue(); + var scopeName = testScope.IdScopeName; + Parallel.For( + 0, + testLength, + new ParallelOptions { MaxDegreeOfParallelism = 10 }, + _ => { - var store = BuildStore(testScope); - var generator = new UniqueIdGenerator(store) {BatchSize = 3}; + generatedIds.Enqueue(generator.NextId(scopeName)); + threadIds.Enqueue(Thread.CurrentThread.ManagedThreadId); + }); - // Act - generator.NextId(testScope.IdScopeName); //1 - generator.NextId(testScope.IdScopeName); //2 - generator.NextId(testScope.IdScopeName); //3 + // Assert we generated the right count of ids + Assert.Equals(testLength, generatedIds.Count); - // Assert - Assert.AreEqual("4", testScope.ReadCurrentPersistedValue()); - } - } + // Assert there were no duplicates + var duplicates = generatedIds.GroupBy(n => n).Any(g => g.Count() != 1); + duplicates.Should().BeFalse(); - [Test] - public void ShouldUpdateBlobWhenGeneratingNextIdAfterEndOfBatch() - { - // Arrange - using (var testScope = BuildTestScope()) - { - var store = BuildStore(testScope); - var generator = new UniqueIdGenerator(store) {BatchSize = 3}; - - // Act - generator.NextId(testScope.IdScopeName); //1 - generator.NextId(testScope.IdScopeName); //2 - generator.NextId(testScope.IdScopeName); //3 - generator.NextId(testScope.IdScopeName); //4 - - // Assert - Assert.AreEqual("7", testScope.ReadCurrentPersistedValue()); - } - } - - [Test] - public void ShouldReturnIdsFromThirdBatchIfSecondBatchTakenByAnotherGenerator() - { - // Arrange - using (var testScope = BuildTestScope()) - { - var store1 = BuildStore(testScope); - var generator1 = new UniqueIdGenerator(store1) {BatchSize = 3}; - var store2 = BuildStore(testScope); - var generator2 = new UniqueIdGenerator(store2) {BatchSize = 3}; - - // Act - generator1.NextId(testScope.IdScopeName); //1 - generator1.NextId(testScope.IdScopeName); //2 - generator1.NextId(testScope.IdScopeName); //3 - generator2.NextId(testScope.IdScopeName); //4 - var lastId = generator1.NextId(testScope.IdScopeName); //7 - - // Assert - Assert.AreEqual(7, lastId); - } - } - - [Test] - public void ShouldReturnIdsAcrossMultipleGenerators() - { - // Arrange - using (var testScope = BuildTestScope()) - { - var store1 = BuildStore(testScope); - var generator1 = new UniqueIdGenerator(store1) {BatchSize = 3}; - var store2 = BuildStore(testScope); - var generator2 = new UniqueIdGenerator(store2) {BatchSize = 3}; - - // Act - var generatedIds = new[] - { - generator1.NextId(testScope.IdScopeName), //1 - generator1.NextId(testScope.IdScopeName), //2 - generator1.NextId(testScope.IdScopeName), //3 - generator2.NextId(testScope.IdScopeName), //4 - generator1.NextId(testScope.IdScopeName), //7 - generator2.NextId(testScope.IdScopeName), //5 - generator2.NextId(testScope.IdScopeName), //6 - generator2.NextId(testScope.IdScopeName), //10 - generator1.NextId(testScope.IdScopeName), //8 - generator1.NextId(testScope.IdScopeName) //9 - }; - - // Assert - CollectionAssert.AreEqual( - new[] {1, 2, 3, 4, 7, 5, 6, 10, 8, 9}, - generatedIds); - } - } - - [Test] - public void ShouldSupportUsingOneGeneratorFromMultipleThreads() - { - // Arrange - using (var testScope = BuildTestScope()) - { - var store = BuildStore(testScope); - var generator = new UniqueIdGenerator(store) {BatchSize = 1000}; - const int testLength = 10000; - - // Act - var generatedIds = new ConcurrentQueue(); - var threadIds = new ConcurrentQueue(); - var scopeName = testScope.IdScopeName; - Parallel.For( - 0, - testLength, - new ParallelOptions {MaxDegreeOfParallelism = 10}, - i => - { - generatedIds.Enqueue(generator.NextId(scopeName)); - threadIds.Enqueue(Thread.CurrentThread.ManagedThreadId); - }); - - // Assert we generated the right count of ids - Assert.AreEqual(testLength, generatedIds.Count); - - // Assert there were no duplicates - Assert.IsFalse(generatedIds.GroupBy(n => n).Any(g => g.Count() != 1)); - - // Assert we used multiple threads - var uniqueThreadsUsed = threadIds.Distinct().Count(); - if (uniqueThreadsUsed == 1) - Assert.Inconclusive("The test failed to actually utilize multiple threads"); - } - } + + // Assert we used multiple threads + var uniqueThreadsUsed = threadIds.Distinct().Count(); + if (uniqueThreadsUsed == 1) + Assert.Inconclusive("The test failed to actually utilize multiple threads"); } } \ No newline at end of file diff --git a/README.md b/README.md index b9997c5..482c875 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ High performance, distributed unique thread-safe id generator for Azure. ## How to use -The project is rely on Azure Blob Storage. `AutoNumber` package will generate ids by using a single text file on the Azure Blob Storage. - +The project is rely on Azure Blob Storage. `AutoNumber` package will generate ids by using a single text file on the +Azure Blob Storage. ``` var blobServiceClient = new BlobServiceClient(connectionString); @@ -32,8 +32,9 @@ var id2 = idGen.NextId("orders"); ``` ### With Microsoft DI -The project has an extension method to add it and its dependencies to Microsoft ASP.NET DI. ~~The only caveat is you need to registry type of `BlobServiceClient` in DI before registring `AutoNumber`.~~ +The project has an extension method to add it and its dependencies to Microsoft ASP.NET DI. ~~The only caveat is you +need to registry type of `BlobServiceClient` in DI before registring `AutoNumber`.~~ Use options builder to configure the service, take into account the default settings will read from `appsettings.json`. @@ -49,10 +50,8 @@ services.AddAutoNumber(Configuration, x => }); ``` - #### Deprecated way to register the service: - ``` // configure the services // you need to register an instane of CloudStorageAccount before using this @@ -72,7 +71,9 @@ public class Foo ``` ### Configuration -These are default configuration for `AutoNumber`. If you prefer registering AutoNumber with `AddAddNumber` method, these options can be set via `appsettings.json`. + +These are default configuration for `AutoNumber`. If you prefer registering AutoNumber with `AddAddNumber` method, these +options can be set via `appsettings.json`. ``` { @@ -83,9 +84,14 @@ These are default configuration for `AutoNumber`. If you prefer registering Auto } } ``` + ### Support -Support this proejct and me via [paypal](https://paypal.me/alibahraminezhad) +Support this proejct and me via [paypal](https://paypal.me/alibahraminezhad) ## Credits -Most of the credits of this library goes to [Tatham Oddie](https://tatham.blog/2011/07/14/released-snowmaker-a-unique-id-generator-for-azure-or-any-other-cloud-hosting-environment/) for making SnowMaker. I forked his work and made lots of change to make it available on .NET Standard (2.0 and 2.1). SnowMaker is out-dated and is using very old version of Azure Packages. + +Most of the credits of this library goes +to [Tatham Oddie](https://tatham.blog/2011/07/14/released-snowmaker-a-unique-id-generator-for-azure-or-any-other-cloud-hosting-environment/) +for making SnowMaker. I forked his work and made lots of change to make it available on .NET Standard (2.0 and 2.1). +SnowMaker is out-dated and is using very old version of Azure Packages. diff --git a/UnitTests/DictionaryExtentionsTests.cs b/UnitTests/DictionaryExtentionsTests.cs index bf9a4bc..5be25b7 100644 --- a/UnitTests/DictionaryExtentionsTests.cs +++ b/UnitTests/DictionaryExtentionsTests.cs @@ -3,85 +3,84 @@ using AutoNumber.Extensions; using NUnit.Framework; -namespace AutoNumber.UnitTests +namespace AutoNumber.UnitTests; + +[TestFixture] +public class DictionaryExtentionsTests { - [TestFixture] - public class DictionaryExtentionsTests + private static bool IsLockedOnCurrentThread(object lockObject) { - private static bool IsLockedOnCurrentThread(object lockObject) + var reset = new ManualResetEvent(false); + var couldLockBeAcquiredOnOtherThread = false; + new Thread(() => { - var reset = new ManualResetEvent(false); - var couldLockBeAcquiredOnOtherThread = false; - new Thread(() => - { - couldLockBeAcquiredOnOtherThread = Monitor.TryEnter(lockObject, 0); - reset.Set(); - }).Start(); - reset.WaitOne(); - return !couldLockBeAcquiredOnOtherThread; - } + couldLockBeAcquiredOnOtherThread = Monitor.TryEnter(lockObject, 0); + reset.Set(); + }).Start(); + reset.WaitOne(); + return !couldLockBeAcquiredOnOtherThread; + } - [Test] - public void GetValueShouldCallTheValueInitializerWithinTheLockIfTheKeyDoesntExist() + [Test] + public void GetValueShouldCallTheValueInitializerWithinTheLockIfTheKeyDoesntExist() + { + var dictionary = new Dictionary { - var dictionary = new Dictionary - { - {"foo", "bar"} - }; + { "foo", "bar" } + }; - var dictionaryLock = new object(); + var dictionaryLock = new object(); - // Act - dictionary.GetValue( - "bar", - dictionaryLock, - () => - { - // Assert - Assert.That(IsLockedOnCurrentThread(dictionaryLock), Is.True); - return "qak"; - }); - } + // Act + dictionary.GetValue( + "bar", + dictionaryLock, + () => + { + // Assert + Assert.That(IsLockedOnCurrentThread(dictionaryLock), Is.True); + return "qak"; + }); + } - [Test] - public void GetValueShouldReturnExistingValueWithoutUsingTheLock() + [Test] + public void GetValueShouldReturnExistingValueWithoutUsingTheLock() + { + var dictionary = new Dictionary { - var dictionary = new Dictionary - { - {"foo", "bar"} - }; + { "foo", "bar" } + }; - // Act - // null can't be used as a lock and will throw an exception if attempted - var value = dictionary.GetValue("foo", null, null); + // Act + // null can't be used as a lock and will throw an exception if attempted + var value = dictionary.GetValue("foo", null, null); - // Assert - Assert.That("bar", Is.EqualTo(value)); - } + // Assert + Assert.That("bar", Is.EqualTo(value)); + } - [Test] - public void GetValueShouldStoreNewValuesAfterCallingTheValueInitializerOnce() + [Test] + public void GetValueShouldStoreNewValuesAfterCallingTheValueInitializerOnce() + { + var dictionary = new Dictionary { - var dictionary = new Dictionary - { - {"foo", "bar"} - }; + { "foo", "bar" } + }; - var dictionaryLock = new object(); + var dictionaryLock = new object(); - // Arrange - dictionary.GetValue("bar", dictionaryLock, () => "qak"); + // Arrange + dictionary.GetValue("bar", dictionaryLock, () => "qak"); - // Act - dictionary.GetValue( - "bar", - dictionaryLock, - () => - { - // Assert - Assert.Fail("Value initializer should not have been called a second time."); - return null; - }); - } + // Act + dictionary.GetValue( + "bar", + dictionaryLock, + () => + { + // Assert + Assert.Fail("Value initializer should not have been called a second time."); + return null; + }); } } \ No newline at end of file diff --git a/UnitTests/UniqueIdGeneratorTest.cs b/UnitTests/UniqueIdGeneratorTest.cs index 011a4ff..9965c42 100644 --- a/UnitTests/UniqueIdGeneratorTest.cs +++ b/UnitTests/UniqueIdGeneratorTest.cs @@ -5,135 +5,134 @@ using NUnit.Framework; using NUnit.Framework.Legacy; -namespace AutoNumber.UnitTests +namespace AutoNumber.UnitTests; + +[TestFixture] +public class UniqueIdGeneratorTest { - [TestFixture] - public class UniqueIdGeneratorTest + [Test] + public void ConstructorShouldNotRetrieveDataFromStore() { - [Test] - public void ConstructorShouldNotRetrieveDataFromStore() - { - var store = Substitute.For(); - // ReSharper disable once ObjectCreationAsStatement - new UniqueIdGenerator(store); - store.DidNotReceiveWithAnyArgs().GetData(null); - } - - [Test] - public void MaxWriteAttemptsShouldThrowArgumentOutOfRangeExceptionWhenValueIsNegative() - { - var store = Substitute.For(); - Assert.That(() => - // ReSharper disable once ObjectCreationAsStatement - new UniqueIdGenerator(store) - { - MaxWriteAttempts = -1 - } - , Throws.TypeOf()); - } - - [Test] - public void MaxWriteAttemptsShouldThrowArgumentOutOfRangeExceptionWhenValueIsZero() - { - var store = Substitute.For(); - Assert.That(() => - // ReSharper disable once ObjectCreationAsStatement - new UniqueIdGenerator(store) - { - MaxWriteAttempts = 0 - } - , Throws.TypeOf()); - } - - [Test] - public void NextIdShouldReturnNumbersSequentially() - { - var store = Substitute.For(); - store.GetData("test").Returns("0", "250"); - store.TryOptimisticWrite("test", "3").Returns(true); - - var subject = new UniqueIdGenerator(store) - { - BatchSize = 3 - }; - - Assert.That(subject.NextId("test"), Is.EqualTo(0)); - Assert.That(subject.NextId("test"), Is.EqualTo(1)); - Assert.That(subject.NextId("test"), Is.EqualTo(2)); - } - - [Test] - public void NextIdShouldRollOverToNewBlockWhenCurrentBlockIsExhausted() - { - var store = Substitute.For(); - store.GetData("test").Returns("0", "250"); - store.TryOptimisticWrite("test", "3").Returns(true); - store.TryOptimisticWrite("test", "253").Returns(true); - - var subject = new UniqueIdGenerator(store) - { - BatchSize = 3 - }; - - Assert.That(subject.NextId("test"), Is.EqualTo(0)); - Assert.That(subject.NextId("test"), Is.EqualTo(1)); - Assert.That(subject.NextId("test"), Is.EqualTo(2) ); - Assert.That(subject.NextId("test"), Is.EqualTo(250)); - Assert.That(subject.NextId("test"), Is.EqualTo(251)); - Assert.That(subject.NextId("test"), Is.EqualTo(252)); - } + var store = Substitute.For(); + // ReSharper disable once ObjectCreationAsStatement + new UniqueIdGenerator(store); + store.DidNotReceiveWithAnyArgs().GetData(null); + } - [Test] - public void NextIdShouldThrowExceptionOnCorruptData() - { - var store = Substitute.For(); - store.GetData("test").Returns("abc"); + [Test] + public void MaxWriteAttemptsShouldThrowArgumentOutOfRangeExceptionWhenValueIsNegative() + { + var store = Substitute.For(); + Assert.That(() => + // ReSharper disable once ObjectCreationAsStatement + new UniqueIdGenerator(store) + { + MaxWriteAttempts = -1 + } + , Throws.TypeOf()); + } - Assert.That(() => + [Test] + public void MaxWriteAttemptsShouldThrowArgumentOutOfRangeExceptionWhenValueIsZero() + { + var store = Substitute.For(); + Assert.That(() => + // ReSharper disable once ObjectCreationAsStatement + new UniqueIdGenerator(store) { - var generator = new UniqueIdGenerator(store); - generator.NextId("test"); + MaxWriteAttempts = 0 } - , Throws.TypeOf()); - } + , Throws.TypeOf()); + } + + [Test] + public void NextIdShouldReturnNumbersSequentially() + { + var store = Substitute.For(); + store.GetData("test").Returns("0", "250"); + store.TryOptimisticWrite("test", "3").Returns(true); - [Test] - public void NextIdShouldThrowExceptionOnNullData() + var subject = new UniqueIdGenerator(store) { - var store = Substitute.For(); - store.GetData("test").Returns((string) null); + BatchSize = 3 + }; - Assert.That(() => - { - var generator = new UniqueIdGenerator(store); - generator.NextId("test"); - } - , Throws.TypeOf()); - } + Assert.That(subject.NextId("test"), Is.EqualTo(0)); + Assert.That(subject.NextId("test"), Is.EqualTo(1)); + Assert.That(subject.NextId("test"), Is.EqualTo(2)); + } + + [Test] + public void NextIdShouldRollOverToNewBlockWhenCurrentBlockIsExhausted() + { + var store = Substitute.For(); + store.GetData("test").Returns("0", "250"); + store.TryOptimisticWrite("test", "3").Returns(true); + store.TryOptimisticWrite("test", "253").Returns(true); - [Test] - public void NextIdShouldThrowExceptionWhenRetriesAreExhausted() + var subject = new UniqueIdGenerator(store) { - var store = Substitute.For(); - store.GetData("test").Returns("0"); - store.TryOptimisticWrite("test", "3").Returns(false, false, false, true); + BatchSize = 3 + }; + + Assert.That(subject.NextId("test"), Is.EqualTo(0)); + Assert.That(subject.NextId("test"), Is.EqualTo(1)); + Assert.That(subject.NextId("test"), Is.EqualTo(2)); + Assert.That(subject.NextId("test"), Is.EqualTo(250)); + Assert.That(subject.NextId("test"), Is.EqualTo(251)); + Assert.That(subject.NextId("test"), Is.EqualTo(252)); + } - var generator = new UniqueIdGenerator(store) - { - MaxWriteAttempts = 3 - }; + [Test] + public void NextIdShouldThrowExceptionOnCorruptData() + { + var store = Substitute.For(); + store.GetData("test").Returns("abc"); - try + Assert.That(() => { + var generator = new UniqueIdGenerator(store); generator.NextId("test"); } - catch (Exception ex) + , Throws.TypeOf()); + } + + [Test] + public void NextIdShouldThrowExceptionOnNullData() + { + var store = Substitute.For(); + store.GetData("test").Returns((string)null); + + Assert.That(() => { - StringAssert.StartsWith("Failed to update the data store after 3 attempts.", ex.Message); - return; + var generator = new UniqueIdGenerator(store); + generator.NextId("test"); } + , Throws.TypeOf()); + } + + [Test] + public void NextIdShouldThrowExceptionWhenRetriesAreExhausted() + { + var store = Substitute.For(); + store.GetData("test").Returns("0"); + store.TryOptimisticWrite("test", "3").Returns(false, false, false, true); - Assert.Fail("NextId should have thrown and been caught in the try block"); + var generator = new UniqueIdGenerator(store) + { + MaxWriteAttempts = 3 + }; + + try + { + generator.NextId("test"); } + catch (Exception ex) + { + StringAssert.StartsWith("Failed to update the data store after 3 attempts.", ex.Message); + return; + } + + Assert.Fail("NextId should have thrown and been caught in the try block"); } } \ No newline at end of file diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index 4032358..8063b09 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -1,41 +1,41 @@  - - 8.0.30703 - AutoNumber.UnitTests - AutoNumber.UnitTests - ..\ - UnitTests - Microsoft - UnitTests - Copyright © Microsoft 2011 - bin\$(Configuration)\ - net6.0;net8.0 - - - full - - - pdbonly - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + 8.0.30703 + AutoNumber.UnitTests + AutoNumber.UnitTests + ..\ + UnitTests + Microsoft + UnitTests + Copyright © Microsoft 2011 + bin\$(Configuration)\ + net8.0;net9.0 + + + full + + + pdbonly + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + \ No newline at end of file From 16a1bab676fe977c8abaa3a8dfd724261f1bcb15 Mon Sep 17 00:00:00 2001 From: Ali Bahrami Date: Sat, 26 Jul 2025 21:09:51 +0200 Subject: [PATCH 2/3] Update GitHub Actions workflow to use .NET 9 --- .github/workflows/dotnet.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 749d523..69c5f8c 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -13,6 +13,11 @@ jobs: steps: - uses: actions/checkout@v2 + - uses: actions/checkout@v2 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' - name: Install dependencies run: dotnet restore - name: Build From 54b7c3a381358cd48a3e5149fbb8bba4852dc177 Mon Sep 17 00:00:00 2001 From: Ali Bahrami Date: Sat, 26 Jul 2025 21:11:42 +0200 Subject: [PATCH 3/3] Pin Azurite Docker image to the latest tag in GitHub Actions workflow --- .github/workflows/dotnet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 69c5f8c..a308863 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -23,7 +23,7 @@ jobs: - name: Build run: dotnet build --configuration Release --no-restore - name: Running Azurite - run: docker run -d -p 10000:10000 -p 10001:10001 mcr.microsoft.com/azure-storage/azurite + run: docker run -d -p 10000:10000 -p 10001:10001 mcr.microsoft.com/azure-storage/azurite:latest - name: Test run: dotnet test --no-restore - name: Create the package