dotnet 9 and sequential id
All checks were successful
.NET Test / test (push) Successful in 49s
.NET Publish / publish (push) Successful in 41s

This commit is contained in:
2025-08-01 22:21:42 +04:00
parent 2b68ba982d
commit 3665abaab8
14 changed files with 428 additions and 68 deletions

View File

@@ -36,7 +36,7 @@ public class Decode
var actualBytesArray = Base32.Decode(str);
actualBytesArray.Should().Equal(expected);
}
[Theory]
[InlineData("ZFXJMF5N====", new byte[] { 0b11001001, 0b01101110, 0b10010110, 0b00010111, 0b10101101, })]
[InlineData("CPIKTMY=====", new byte[] { 0b00010011, 0b11010000, 0b10101001, 0b10110011, })]
@@ -72,7 +72,7 @@ public class Decode
[Theory]
[InlineData(null)]
[InlineData("")]
public void WhenCalledWithNullString_ShouldReturnEmptyArray(string testString)
public void WhenCalledWithNullString_ShouldReturnEmptyArray(string? testString)
{
Base32.Decode(testString).Should().BeEmpty();
}

View File

@@ -23,6 +23,9 @@ public class Encode
[InlineData("GSHXGB5ORKNDSFLSU2YWALI=")]
[InlineData("TMFSC64ZZNPQSNGCFIODS7TR")]
[InlineData("ZTDUBU4QZFFMDJKBII334EIB")]
[InlineData("2IO2HTALCXZWCBD2AAAAAAAAAAAA====")]
[InlineData("WXYEOQUZULMCY6ZQTDOLTRUZZMKQ====")]
[InlineData("FG4M3ZQM3TVWDMBUP5L7N7V3JS7KBM2E")]
public void WhenDecodedFromString_ShouldBeEncodedToTheSameString(string testString)
{
var resultBytes = Base32.Decode(testString);
@@ -34,6 +37,8 @@ public class Encode
[Theory]
[InlineData("FG4M3ZQM3TVWDMBUP5L7N7V3JS7KBM2E", new byte[] { 0x29, 0xb8, 0xcd, 0xe6, 0x0c, 0xdc, 0xeb, 0x61, 0xb0, 0x34, 0x7f, 0x57, 0xf6, 0xfe, 0xbb, 0x4c, 0xbe, 0xa0, 0xb3, 0x44, })]
[InlineData("WXYEOQUZULMCY6ZQTDOLTRUZZMKQ====", new byte[] { 0xb5, 0xf0, 0x47, 0x42, 0x99, 0xa2, 0xd8, 0x2c, 0x7b, 0x30, 0x98, 0xdc, 0xb9, 0xc6, 0x99, 0xcb, 0x15, })]
[InlineData("2IO2HTALCXZWCBD2AAAAAAAAAAAA====", new byte[] { 0xd2, 0x1d, 0xa3, 0xcc, 0x0b, 0x15, 0xf3, 0x61, 0x04, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, })]
[InlineData("2IO2HTALCXZWCBD2AAAAAAAA", new byte[] { 0xd2, 0x1d, 0xa3, 0xcc, 0x0b, 0x15, 0xf3, 0x61, 0x04, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, })]
[InlineData("2IO2HTALCXZWCBD2", new byte[] { 0xd2, 0x1d, 0xa3, 0xcc, 0x0b, 0x15, 0xf3, 0x61, 0x04, 0x7a, })]
[InlineData("ZFXJMF5N", new byte[] { 0b11001001, 0b01101110, 0b10010110, 0b00010111, 0b10101101, })]
[InlineData("CPIKTMY=", new byte[] { 0b00010011, 0b11010000, 0b10101001, 0b10110011, })]
@@ -48,7 +53,7 @@ public class Encode
[Theory]
[InlineData(new byte[] { })]
[InlineData(null)]
public void WhenCalledWithEmptyByteArray_ShouldReturnEmptyString(byte[] testArray)
public void WhenCalledWithEmptyByteArray_ShouldReturnEmptyString(byte[]? testArray)
{
var actualBase32 = Base32.Encode(testArray);
actualBase32.Should().Be(string.Empty);
@@ -57,7 +62,7 @@ public class Encode
[Theory]
[InlineData(new byte[] { })]
[InlineData(null)]
public void WhenCalledWithEmptyByteArray_ShouldReturnZeroAndNotChangeOutput(byte[] testArray)
public void WhenCalledWithEmptyByteArray_ShouldReturnZeroAndNotChangeOutput(byte[]? testArray)
{
char[] output = ['1', '2', '3', '4'];

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
@@ -15,13 +15,13 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View File

@@ -3,15 +3,15 @@ namespace Just.Core.Tests.GuidV8Tests;
public class NewGuid
{
[Theory]
[InlineData(GuidV8Entropy.Weak,
[InlineData(RngEntropy.Weak,
-25000000, -10000000, -5000000, -2000000, -1000000, -500000, -250000, -100000, -25000, -10000, -5000,
-2500, -1000, -500, -499, -497, -450, -300, -200, -50, -1, 0, 1, 2, 5, 10, 20, 50, 100, 200, 350, 500, 1000,
2500, 5000, 10000, 25000, 100000, 250000, 500000, 1000000, 2000000, 5000000, 10000000, 100000000, 250000000)]
[InlineData(GuidV8Entropy.Strong,
[InlineData(RngEntropy.Strong,
-25000000, -10000000, -5000000, -2000000, -1000000, -500000, -250000, -100000, -25000, -10000, -5000,
-2500, -1000, -500, -499, -497, -450, -300, -200, -50, -1, 0, 1, 2, 5, 10, 20, 50, 100, 200, 350, 500, 1000,
2500, 5000, 10000, 25000, 100000, 250000, 500000, 1000000, 2000000, 5000000, 10000000, 100000000, 250000000)]
[InlineData(GuidV8Entropy.Weak,
[InlineData(RngEntropy.Weak,
-9863, -9740, -9214, -8878, -8674, -8652, -8640, -8565, -8518, -8449,
-8390, -8193, -8108, -7808, -7501, -7203, -7133, -7020, -6983, -6855,
-6576, -6162, -5630, -5505, -5472, -5382, -4706, -4680, -4509, -4454,
@@ -22,7 +22,7 @@ public class NewGuid
4163, 4198, 4366, 4662, 4746, 4879, 5467, 5601, 5912, 5979,
6128, 6277, 6323, 6437, 6699, 6853, 7556, 7776, 7795, 8099,
8336, 8592, 8682, 8683, 8818, 8904, 9375, 9466, 9551, 9708)]
[InlineData(GuidV8Entropy.Strong,
[InlineData(RngEntropy.Strong,
-9863, -9740, -9214, -8878, -8674, -8652, -8640, -8565, -8518, -8449,
-8390, -8193, -8108, -7808, -7501, -7203, -7133, -7020, -6983, -6855,
-6576, -6162, -5630, -5505, -5472, -5382, -4706, -4680, -4509, -4454,
@@ -33,7 +33,7 @@ public class NewGuid
4163, 4198, 4366, 4662, 4746, 4879, 5467, 5601, 5912, 5979,
6128, 6277, 6323, 6437, 6699, 6853, 7556, 7776, 7795, 8099,
8336, 8592, 8682, 8683, 8818, 8904, 9375, 9466, 9551, 9708)]
[InlineData(GuidV8Entropy.Weak,
[InlineData(RngEntropy.Weak,
5635912, 6673780, 17277183, 17512959, 19098799, 21672621, 30581958, 30824885, 31874213, 35192781,
36337094, 37752116, 38387215, 39154682, 40525427, 52288093, 55218356, 59065156, 65231785, 75430932,
76289058, 79078058, 85770685, 85925884, 94726743, 94864163, 95781967, 96150006, 96482085, 102570414,
@@ -44,7 +44,7 @@ public class NewGuid
292563035, 299285016, 303834917, 310357836, 315078337, 316367236, 318311758, 318873972, 319675272, 321784171,
324204294, 327667283, 330287252, 338438172, 349863360, 360777768, 366398711, 368637150, 368776734, 371900343,
379094084, 379818879, 381448333, 381814627, 382393101, 382483709, 385600870, 389455134, 396115960, 399364095)]
[InlineData(GuidV8Entropy.Strong,
[InlineData(RngEntropy.Strong,
5635912, 6673780, 17277183, 17512959, 19098799, 21672621, 30581958, 30824885, 31874213, 35192781,
36337094, 37752116, 38387215, 39154682, 40525427, 52288093, 55218356, 59065156, 65231785, 75430932,
76289058, 79078058, 85770685, 85925884, 94726743, 94864163, 95781967, 96150006, 96482085, 102570414,
@@ -55,7 +55,7 @@ public class NewGuid
292563035, 299285016, 303834917, 310357836, 315078337, 316367236, 318311758, 318873972, 319675272, 321784171,
324204294, 327667283, 330287252, 338438172, 349863360, 360777768, 366398711, 368637150, 368776734, 371900343,
379094084, 379818879, 381448333, 381814627, 382393101, 382483709, 385600870, 389455134, 396115960, 399364095)]
public void Guids_Differing_By_Minutes_Should_Be_Sortable(GuidV8Entropy entropy, params int[] seconds)
public void Guids_Differing_By_Minutes_Should_Be_Sortable(RngEntropy entropy, params int[] seconds)
{
var rng = new Random(25);
var referenceTime = new DateTime(2024, 05, 17, 15, 36, 13, 771, DateTimeKind.Utc);
@@ -77,15 +77,15 @@ public class NewGuid
}
[Theory]
[InlineData(GuidV8Entropy.Weak,
[InlineData(RngEntropy.Weak,
-250000000, -25000000, -10000000, -5000000, -2000000, -1000000, -500000, -250000, -100000, -25000, -10000, -5000,
-2500, -1000, -500, -499, -497, -450, -300, -200, -50, -1, 0, 1, 2, 5, 10, 20, 50, 100, 200, 350, 500, 1000,
2500, 5000, 10000, 25000, 100000, 250000, 500000, 1000000, 2000000, 5000000, 10000000, 100000000, 2100000000)]
[InlineData(GuidV8Entropy.Strong,
[InlineData(RngEntropy.Strong,
-250000000, -25000000, -10000000, -5000000, -2000000, -1000000, -500000, -250000, -100000, -25000, -10000, -5000,
-2500, -1000, -500, -499, -497, -450, -300, -200, -50, -1, 0, 1, 2, 5, 10, 20, 50, 100, 200, 350, 500, 1000,
2500, 5000, 10000, 25000, 100000, 250000, 500000, 1000000, 2000000, 5000000, 10000000, 100000000, 2100000000)]
[InlineData(GuidV8Entropy.Weak,
[InlineData(RngEntropy.Weak,
-9863, -9740, -9214, -8878, -8674, -8652, -8640, -8565, -8518, -8449,
-8390, -8193, -8108, -7808, -7501, -7203, -7133, -7020, -6983, -6855,
-6576, -6162, -5630, -5505, -5472, -5382, -4706, -4680, -4509, -4454,
@@ -96,7 +96,7 @@ public class NewGuid
4163, 4198, 4366, 4662, 4746, 4879, 5467, 5601, 5912, 5979,
6128, 6277, 6323, 6437, 6699, 6853, 7556, 7776, 7795, 8099,
8336, 8592, 8682, 8683, 8818, 8904, 9375, 9466, 9551, 9708)]
[InlineData(GuidV8Entropy.Strong,
[InlineData(RngEntropy.Strong,
-9863, -9740, -9214, -8878, -8674, -8652, -8640, -8565, -8518, -8449,
-8390, -8193, -8108, -7808, -7501, -7203, -7133, -7020, -6983, -6855,
-6576, -6162, -5630, -5505, -5472, -5382, -4706, -4680, -4509, -4454,
@@ -107,7 +107,7 @@ public class NewGuid
4163, 4198, 4366, 4662, 4746, 4879, 5467, 5601, 5912, 5979,
6128, 6277, 6323, 6437, 6699, 6853, 7556, 7776, 7795, 8099,
8336, 8592, 8682, 8683, 8818, 8904, 9375, 9466, 9551, 9708)]
[InlineData(GuidV8Entropy.Weak,
[InlineData(RngEntropy.Weak,
6629058, 24114993, 40561510, 46245969, 46876997, 48747281, 80489854, 110237218, 117445694, 118974860,
135132579, 141760591, 149114066, 158322437, 159065333, 164925904, 173848639, 175086337, 175704556, 176335514,
200302773, 207133553, 230088723, 234521706, 239587338, 263755571, 264571928, 290118284, 292346548, 319322378,
@@ -118,7 +118,7 @@ public class NewGuid
749379956, 757561933, 758668417, 761735205, 770349479, 797570403, 805896481, 809050934, 821655964, 821980469,
830824227, 840429528, 851772315, 859717719, 859763860, 867675943, 912124563, 914880620, 923914294, 930298008,
932610035, 937468680, 945565998, 949277691, 949397209, 951283050, 953249971, 953953188, 976210158, 982233484, 982233485)]
[InlineData(GuidV8Entropy.Strong,
[InlineData(RngEntropy.Strong,
6629058, 24114993, 40561510, 46245969, 46876997, 48747281, 80489854, 110237218, 117445694, 118974860,
135132579, 141760591, 149114066, 158322437, 159065333, 164925904, 173848639, 175086337, 175704556, 176335514,
200302773, 207133553, 230088723, 234521706, 239587338, 263755571, 264571928, 290118284, 292346548, 319322378,
@@ -129,7 +129,7 @@ public class NewGuid
749379956, 757561933, 758668417, 761735205, 770349479, 797570403, 805896481, 809050934, 821655964, 821980469,
830824227, 840429528, 851772315, 859717719, 859763860, 867675943, 912124563, 914880620, 923914294, 930298008,
932610035, 937468680, 945565998, 949277691, 949397209, 951283050, 953249971, 953953188, 976210158, 982233484, 982233485)]
public void Guids_Differing_By_Seconds_Should_Be_Sortable(GuidV8Entropy entropy, params int[] seconds)
public void Guids_Differing_By_Seconds_Should_Be_Sortable(RngEntropy entropy, params int[] seconds)
{
var rng = new Random(25);
var referenceTime = new DateTime(2024, 05, 17, 15, 36, 13, 771, DateTimeKind.Utc);
@@ -151,15 +151,15 @@ public class NewGuid
}
[Theory]
[InlineData(GuidV8Entropy.Weak,
[InlineData(RngEntropy.Weak,
-250000000, -25000000, -10000000, -5000000, -2000000, -1000000, -500000, -250000, -100000, -25000, -10000, -5000,
-2500, -1000, -500, -499, -497, -450, -300, -200, -50, -1, 0, 1, 2, 5, 10, 20, 50, 100, 200, 350, 500, 1000,
2500, 5000, 10000, 25000, 100000, 250000, 500000, 1000000, 2000000, 5000000, 10000000, 100000000, 2100000000)]
[InlineData(GuidV8Entropy.Strong,
[InlineData(RngEntropy.Strong,
-250000000, -25000000, -10000000, -5000000, -2000000, -1000000, -500000, -250000, -100000, -25000, -10000, -5000,
-2500, -1000, -500, -499, -497, -450, -300, -200, -50, -1, 0, 1, 2, 5, 10, 20, 50, 100, 200, 350, 500, 1000,
2500, 5000, 10000, 25000, 100000, 250000, 500000, 1000000, 2000000, 5000000, 10000000, 100000000, 2100000000)]
[InlineData(GuidV8Entropy.Weak,
[InlineData(RngEntropy.Weak,
-9863, -9740, -9214, -8878, -8674, -8652, -8640, -8565, -8518, -8449,
-8390, -8193, -8108, -7808, -7501, -7203, -7133, -7020, -6983, -6855,
-6576, -6162, -5630, -5505, -5472, -5382, -4706, -4680, -4509, -4454,
@@ -170,7 +170,7 @@ public class NewGuid
4163, 4198, 4366, 4662, 4746, 4879, 5467, 5601, 5912, 5979,
6128, 6277, 6323, 6437, 6699, 6853, 7556, 7776, 7795, 8099,
8336, 8592, 8682, 8683, 8818, 8904, 9375, 9466, 9551, 9708)]
[InlineData(GuidV8Entropy.Strong,
[InlineData(RngEntropy.Strong,
-9863, -9740, -9214, -8878, -8674, -8652, -8640, -8565, -8518, -8449,
-8390, -8193, -8108, -7808, -7501, -7203, -7133, -7020, -6983, -6855,
-6576, -6162, -5630, -5505, -5472, -5382, -4706, -4680, -4509, -4454,
@@ -181,7 +181,7 @@ public class NewGuid
4163, 4198, 4366, 4662, 4746, 4879, 5467, 5601, 5912, 5979,
6128, 6277, 6323, 6437, 6699, 6853, 7556, 7776, 7795, 8099,
8336, 8592, 8682, 8683, 8818, 8904, 9375, 9466, 9551, 9708)]
[InlineData(GuidV8Entropy.Weak,
[InlineData(RngEntropy.Weak,
6629058, 24114993, 40561510, 46245969, 46876997, 48747281, 80489854, 110237218, 117445694, 118974860,
135132579, 141760591, 149114066, 158322437, 159065333, 164925904, 173848639, 175086337, 175704556, 176335514,
200302773, 207133553, 230088723, 234521706, 239587338, 263755571, 264571928, 290118284, 292346548, 319322378,
@@ -192,7 +192,7 @@ public class NewGuid
749379956, 757561933, 758668417, 761735205, 770349479, 797570403, 805896481, 809050934, 821655964, 821980469,
830824227, 840429528, 851772315, 859717719, 859763860, 867675943, 912124563, 914880620, 923914294, 930298008,
932610035, 937468680, 945565998, 949277691, 949397209, 951283050, 953249971, 953953188, 976210158, 982233484, 982233485)]
[InlineData(GuidV8Entropy.Strong,
[InlineData(RngEntropy.Strong,
6629058, 24114993, 40561510, 46245969, 46876997, 48747281, 80489854, 110237218, 117445694, 118974860,
135132579, 141760591, 149114066, 158322437, 159065333, 164925904, 173848639, 175086337, 175704556, 176335514,
200302773, 207133553, 230088723, 234521706, 239587338, 263755571, 264571928, 290118284, 292346548, 319322378,
@@ -203,7 +203,7 @@ public class NewGuid
749379956, 757561933, 758668417, 761735205, 770349479, 797570403, 805896481, 809050934, 821655964, 821980469,
830824227, 840429528, 851772315, 859717719, 859763860, 867675943, 912124563, 914880620, 923914294, 930298008,
932610035, 937468680, 945565998, 949277691, 949397209, 951283050, 953249971, 953953188, 976210158, 982233484, 982233485)]
public void Guids_Differing_By_Milliseconds_Should_Be_Sortable(GuidV8Entropy entropy, params int[] seconds)
public void Guids_Differing_By_Milliseconds_Should_Be_Sortable(RngEntropy entropy, params int[] seconds)
{
var rng = new Random(26);
var referenceTime = new DateTime(2024, 05, 17, 15, 36, 13, 771, DateTimeKind.Utc);

View File

@@ -0,0 +1,140 @@
namespace Just.Core.Tests.SeqIdTests;
public class NextId
{
private static readonly DateTime TestEpoch = new(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private readonly SeqId _seqId = new(TestEpoch);
private const int TimestampShift = 22; // 63 - 41 = 22
private const long TimestampMask = 0x1FFFFFFFFFF; // 41 bits mask
private const int SeqShift = 14; // TimestampShift - 8 = 14
private const long SeqMask = 0xFF; // 8 bits mask
private const long RandMask = 0x3FFF; // 14 bits mask (since 2^14 = 16384)
[Fact]
public void NextId_ShouldHaveCorrectBitStructure()
{
// Arrange
var time = TestEpoch.AddMilliseconds(500);
// Act
long id = _seqId.Next(time);
// Assert
long timestampPart = (id >> TimestampShift) & TimestampMask;
long sequencePart = (id >> SeqShift) & SeqMask;
long randomPart = id & RandMask;
timestampPart.Should().Be(500);
sequencePart.Should().Be(0);
randomPart.Should().BeInRange(0, RandMask);
}
[Fact]
public void NextId_ShouldIncrementSequenceForSameTimestamp()
{
// Arrange
var time = TestEpoch.AddMilliseconds(100);
// Act
long id1 = _seqId.Next(time);
long id2 = _seqId.Next(time);
// Assert
long sequence1 = (id1 >> SeqShift) & SeqMask;
long sequence2 = (id2 >> SeqShift) & SeqMask;
sequence2.Should().Be(sequence1 + 1);
}
[Fact]
public void NextId_ShouldResetSequenceForNewTimestamp()
{
// Arrange
var time1 = TestEpoch.AddMilliseconds(100);
var time2 = time1.AddMilliseconds(1);
// Act
_ = _seqId.Next(time1); // Sequence increments to 0
_ = _seqId.Next(time1); // Sequence increments to 1
long id = _seqId.Next(time2); // Should reset to 0
// Assert
long sequence = (id >> SeqShift) & SeqMask;
sequence.Should().Be(0);
}
[Fact]
public void NextId_ShouldThrowWhenTimestampDecreases()
{
// Arrange
var time1 = TestEpoch.AddMilliseconds(200);
var time2 = TestEpoch.AddMilliseconds(100);
// Act & Assert
_seqId.Next(time1); // First call sets last timestamp
Action act = () => _seqId.Next(time2);
act.Should().Throw<InvalidOperationException>()
.WithMessage("Refused to create new SeqId. Last timestamp is in the future.");
}
[Fact]
public void NextId_ShouldThrowWhenSequenceExhausted()
{
// Arrange
var time = TestEpoch.AddMilliseconds(200);
// Act & Assert
for (int i = 0; i < 255; i++)
{
_seqId.Next(time); // Exhauste sequence
}
Action act = () => _seqId.Next(time);
act.Should().Throw<IndexOutOfRangeException>()
.WithMessage("Refused to create new SeqId. Sequence exhausted.");
}
[Fact]
public void NextId_WithStrongEntropy_ShouldSetLower14Bits()
{
// Arrange
var time = TestEpoch.AddMilliseconds(300);
// Act
long id = _seqId.Next(time, RngEntropy.Strong);
// Assert
long randomPart = id & RandMask;
randomPart.Should().BeInRange(0, RandMask);
}
[Fact]
public void NextId_WithWeakEntropy_ShouldSetLower14Bits()
{
// Arrange
var time = TestEpoch.AddMilliseconds(400);
// Act
long id = _seqId.Next(time, RngEntropy.Weak);
// Assert
long randomPart = id & RandMask;
randomPart.Should().BeInRange(0, RandMask);
}
[Fact]
public void DefaultInstance_NextId_ShouldUseDefaultEpoch()
{
// Arrange
var now = DateTime.UtcNow;
var defaultEpoch = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc);
long expectedTimestamp = (long)(now - defaultEpoch).TotalMilliseconds;
// Act
long id = SeqId.NextId();
// Assert
long timestampPart = (id >> TimestampShift) & TimestampMask;
timestampPart.Should().BeCloseTo(expectedTimestamp & TimestampMask, 1); // Mask handles overflow
}
}