6 Commits

Author SHA1 Message Date
2b68ba982d added more tests
All checks were successful
.NET Test / test (push) Successful in 1m6s
.NET Publish / publish (push) Successful in 38s
2024-08-15 22:00:49 +04:00
43135a5ffb fixed condition for base32 encode test
All checks were successful
.NET Test / test (push) Successful in 58s
2024-08-15 21:01:46 +04:00
2afd66aa57 remove IEqualityOperators interface from ImmutableSequence
All checks were successful
.NET Test / test (push) Successful in 1m3s
2024-08-15 20:51:18 +04:00
71ec5b2f35 * copyright update
All checks were successful
.NET Test / test (push) Successful in 1m2s
2024-08-15 20:22:45 +04:00
30af957fc6 + added ImmutableSequence
All checks were successful
.NET Test / test (push) Successful in 1m46s
.NET Publish / publish (push) Successful in 52s
2024-08-15 20:14:38 +04:00
d60b94dbaa * fixed guid generation 2024-08-15 19:55:04 +04:00
14 changed files with 404 additions and 106 deletions

View File

@@ -56,7 +56,7 @@ public class Decode
var actualBytesArray = Base32.Decode(testString); var actualBytesArray = Base32.Decode(testString);
actualBytesArray.Should().Equal(expected); actualBytesArray.Should().Equal(expected);
} }
[Theory] [Theory]
[InlineData(" ")] [InlineData(" ")]
[InlineData("hg2515i3215")] [InlineData("hg2515i3215")]

View File

@@ -28,7 +28,7 @@ public class Encode
var resultBytes = Base32.Decode(testString); var resultBytes = Base32.Decode(testString);
var resultString = Base32.Encode(resultBytes); var resultString = Base32.Encode(resultBytes);
resultString.Should().BeEquivalentTo(testString); resultString.Should().Be(testString);
} }
[Theory] [Theory]
@@ -43,14 +43,27 @@ public class Encode
{ {
var str = Base32.Encode(testArray); var str = Base32.Encode(testArray);
str.Should().Be(expected); str.Should().Be(expected);
} }
[Theory] [Theory]
[InlineData(new byte[] { })] [InlineData(new byte[] { })]
[InlineData(null)] [InlineData(null)]
public void WhenCalledWithEmptyByteArray_ShouldReturnEmptyString(byte[] testArray) public void WhenCalledWithEmptyByteArray_ShouldReturnEmptyString(byte[] testArray)
{ {
var actualBase32 = Base32.Encode(testArray); var actualBase32 = Base32.Encode(testArray);
actualBase32?.Should().Be(string.Empty); actualBase32.Should().Be(string.Empty);
}
[Theory]
[InlineData(new byte[] { })]
[InlineData(null)]
public void WhenCalledWithEmptyByteArray_ShouldReturnZeroAndNotChangeOutput(byte[] testArray)
{
char[] output = ['1', '2', '3', '4'];
var charsWritten = Base32.Encode(testArray, output);
charsWritten.Should().Be(0);
output.Should().Equal(['1', '2', '3', '4']);
} }
} }

View File

@@ -50,4 +50,36 @@ public class Decode
var result = Base64Url.Decode(testString); var result = Base64Url.Decode(testString);
result.Should().BeEquivalentTo(expected); result.Should().BeEquivalentTo(expected);
} }
[Theory]
[InlineData(" ")]
[InlineData("hg2&515i3215")]
[InlineData("hg712)21")]
[InlineData("hg712f 21")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1806:Do not ignore method results", Justification = "Test case")]
public void WhenCalledWithInvalidString_ShouldThrowFormatException(string testString)
{
Action action = () => Base64Url.Decode(testString);
action.Should().Throw<FormatException>();
}
[Theory]
[InlineData("5QrdUxDUV CAEGw8pvLsEw")]
[InlineData("6nE2uKQ4$0ar9kpmybgkdw")]
[InlineData("PyD6zwDqXkG*S1HPsp41wQ")]
[InlineData("!dOlPOh3wEe9PlyQgTMt2g")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1806:Do not ignore method results", Justification = "Test case")]
public void WhenCalledWithInvalidGuidString_ShouldThrowFormatException(string testString)
{
Action action = () => Base64Url.DecodeGuid(testString);
action.Should().Throw<FormatException>();
}
[Theory]
[InlineData(null)]
[InlineData("")]
public void WhenCalledWithNullString_ShouldReturnEmptyArray(string testString)
{
Base64Url.Decode(testString).Should().BeEmpty();
}
} }

View File

@@ -29,4 +29,26 @@ public class Encode
var result = Base64Url.Encode(testBytes); var result = Base64Url.Encode(testBytes);
result.Should().Be(expected); result.Should().Be(expected);
} }
[Theory]
[InlineData(new byte[] { })]
[InlineData(null)]
public void WhenCalledWithEmptyByteArray_ShouldReturnEmptyString(byte[] testArray)
{
var actualBase32 = Base64Url.Encode(testArray);
actualBase32.Should().Be(string.Empty);
}
[Theory]
[InlineData(new byte[] { })]
[InlineData(null)]
public void WhenCalledWithEmptyByteArray_ShouldReturnZeroAndNotChangeOutput(byte[] testArray)
{
char[] output = ['1', '2', '3', '4'];
var charsWritten = Base64Url.Encode(testArray, output);
charsWritten.Should().Be(0);
output.Should().Equal(['1', '2', '3', '4']);
}
} }

View File

@@ -4,13 +4,87 @@ public class NewGuid
{ {
[Theory] [Theory]
[InlineData(GuidV8Entropy.Weak, [InlineData(GuidV8Entropy.Weak,
-25000000 -10000000, -5000000, -2000000, -1000000, -500000, -250000, -100000, -25000, -10000, -5000, -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, -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)] 2500, 5000, 10000, 25000, 100000, 250000, 500000, 1000000, 2000000, 5000000, 10000000, 100000000, 250000000)]
[InlineData(GuidV8Entropy.Strong, [InlineData(GuidV8Entropy.Strong,
-25000000 -10000000, -5000000, -2000000, -1000000, -500000, -250000, -100000, -25000, -10000, -5000, -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, -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)] 2500, 5000, 10000, 25000, 100000, 250000, 500000, 1000000, 2000000, 5000000, 10000000, 100000000, 250000000)]
[InlineData(GuidV8Entropy.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,
-4314, -3920, -3251, -3233, -3116, -2792, -2685, -2574, -2338, -2173,
-1934, -1914, -1528, -1483, -1410, -870, -757, -730, -263, -220,
151, 426, 588, 970, 1213, 1269, 1376, 1397, 1450, 1770,
2063, 2577, 2750, 2860, 3139, 3161, 3488, 3630, 3774, 4004,
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,
-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,
-4314, -3920, -3251, -3233, -3116, -2792, -2685, -2574, -2338, -2173,
-1934, -1914, -1528, -1483, -1410, -870, -757, -730, -263, -220,
151, 426, 588, 970, 1213, 1269, 1376, 1397, 1450, 1770,
2063, 2577, 2750, 2860, 3139, 3161, 3488, 3630, 3774, 4004,
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,
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,
107768232, 110571078, 110680108, 117974892, 119800380, 126381415, 135895862, 140034471, 149039187, 150906974,
156853001, 160514433, 166446323, 170148965, 171759448, 176494242, 184537553, 188558155, 197194403, 197615804,
201195323, 202294490, 203040975, 203331457, 205016944, 213460258, 217072025, 217185345, 231344025, 232390198,
235053215, 240175073, 245030721, 252275255, 252310334, 277070940, 277359970, 280624756, 288601124, 292427106,
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,
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,
107768232, 110571078, 110680108, 117974892, 119800380, 126381415, 135895862, 140034471, 149039187, 150906974,
156853001, 160514433, 166446323, 170148965, 171759448, 176494242, 184537553, 188558155, 197194403, 197615804,
201195323, 202294490, 203040975, 203331457, 205016944, 213460258, 217072025, 217185345, 231344025, 232390198,
235053215, 240175073, 245030721, 252275255, 252310334, 277070940, 277359970, 280624756, 288601124, 292427106,
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)
{
var rng = new Random(25);
var referenceTime = new DateTime(2024, 05, 17, 15, 36, 13, 771, DateTimeKind.Utc);
SortedList<DateTime, Guid> expected = new(seconds.Length);
foreach (var s in seconds)
{
var timestamp = referenceTime.AddMinutes(s);
expected.Add(timestamp, GuidV8.NewGuid(timestamp, entropy));
}
var sut = expected.Values.ToArray();
rng.Shuffle(sut);
sut.Order().Should().Equal(expected.Select(x => x.Value));
sut.OrderBy(x => x.ToString()).Should().Equal(expected.Select(x => x.Value));
sut.OrderDescending().Should().Equal(expected.Reverse().Select(x => x.Value));
sut.OrderByDescending(x => x.ToString()).Should().Equal(expected.Reverse().Select(x => x.Value));
}
[Theory]
[InlineData(GuidV8Entropy.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,
-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(GuidV8Entropy.Weak,
-9863, -9740, -9214, -8878, -8674, -8652, -8640, -8565, -8518, -8449, -9863, -9740, -9214, -8878, -8674, -8652, -8640, -8565, -8518, -8449,
-8390, -8193, -8108, -7808, -7501, -7203, -7133, -7020, -6983, -6855, -8390, -8193, -8108, -7808, -7501, -7203, -7133, -7020, -6983, -6855,
@@ -43,7 +117,7 @@ public class NewGuid
636537435, 637241486, 676077268, 691630262, 696158602, 714007596, 715537265, 718873134, 724266320, 741049828, 636537435, 637241486, 676077268, 691630262, 696158602, 714007596, 715537265, 718873134, 724266320, 741049828,
749379956, 757561933, 758668417, 761735205, 770349479, 797570403, 805896481, 809050934, 821655964, 821980469, 749379956, 757561933, 758668417, 761735205, 770349479, 797570403, 805896481, 809050934, 821655964, 821980469,
830824227, 840429528, 851772315, 859717719, 859763860, 867675943, 912124563, 914880620, 923914294, 930298008, 830824227, 840429528, 851772315, 859717719, 859763860, 867675943, 912124563, 914880620, 923914294, 930298008,
932610035, 937468680, 945565998, 949277691, 949397209, 951283050, 953249971, 953953188, 976210158, 982233484)] 932610035, 937468680, 945565998, 949277691, 949397209, 951283050, 953249971, 953953188, 976210158, 982233484, 982233485)]
[InlineData(GuidV8Entropy.Strong, [InlineData(GuidV8Entropy.Strong,
6629058, 24114993, 40561510, 46245969, 46876997, 48747281, 80489854, 110237218, 117445694, 118974860, 6629058, 24114993, 40561510, 46245969, 46876997, 48747281, 80489854, 110237218, 117445694, 118974860,
135132579, 141760591, 149114066, 158322437, 159065333, 164925904, 173848639, 175086337, 175704556, 176335514, 135132579, 141760591, 149114066, 158322437, 159065333, 164925904, 173848639, 175086337, 175704556, 176335514,
@@ -54,7 +128,7 @@ public class NewGuid
636537435, 637241486, 676077268, 691630262, 696158602, 714007596, 715537265, 718873134, 724266320, 741049828, 636537435, 637241486, 676077268, 691630262, 696158602, 714007596, 715537265, 718873134, 724266320, 741049828,
749379956, 757561933, 758668417, 761735205, 770349479, 797570403, 805896481, 809050934, 821655964, 821980469, 749379956, 757561933, 758668417, 761735205, 770349479, 797570403, 805896481, 809050934, 821655964, 821980469,
830824227, 840429528, 851772315, 859717719, 859763860, 867675943, 912124563, 914880620, 923914294, 930298008, 830824227, 840429528, 851772315, 859717719, 859763860, 867675943, 912124563, 914880620, 923914294, 930298008,
932610035, 937468680, 945565998, 949277691, 949397209, 951283050, 953249971, 953953188, 976210158, 982233484)] 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(GuidV8Entropy entropy, params int[] seconds)
{ {
var rng = new Random(25); var rng = new Random(25);
@@ -69,19 +143,22 @@ public class NewGuid
var sut = expected.Values.ToArray(); var sut = expected.Values.ToArray();
rng.Shuffle(sut); rng.Shuffle(sut);
sut.Order().Should().BeEquivalentTo(expected.Select(x => x.Value)); sut.Order().Should().Equal(expected.Select(x => x.Value));
sut.OrderDescending().Should().BeEquivalentTo(expected.Reverse().Select(x => x.Value)); sut.OrderBy(x => x.ToString()).Should().Equal(expected.Select(x => x.Value));
sut.OrderDescending().Should().Equal(expected.Reverse().Select(x => x.Value));
sut.OrderByDescending(x => x.ToString()).Should().Equal(expected.Reverse().Select(x => x.Value));
} }
[Theory] [Theory]
[InlineData(GuidV8Entropy.Weak, [InlineData(GuidV8Entropy.Weak,
-25000000 -10000000, -5000000, -2000000, -1000000, -500000, -250000, -100000, -25000, -10000, -5000, -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, -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)] 2500, 5000, 10000, 25000, 100000, 250000, 500000, 1000000, 2000000, 5000000, 10000000, 100000000, 2100000000)]
[InlineData(GuidV8Entropy.Strong, [InlineData(GuidV8Entropy.Strong,
-25000000 -10000000, -5000000, -2000000, -1000000, -500000, -250000, -100000, -25000, -10000, -5000, -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, -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)] 2500, 5000, 10000, 25000, 100000, 250000, 500000, 1000000, 2000000, 5000000, 10000000, 100000000, 2100000000)]
[InlineData(GuidV8Entropy.Weak, [InlineData(GuidV8Entropy.Weak,
-9863, -9740, -9214, -8878, -8674, -8652, -8640, -8565, -8518, -8449, -9863, -9740, -9214, -8878, -8674, -8652, -8640, -8565, -8518, -8449,
-8390, -8193, -8108, -7808, -7501, -7203, -7133, -7020, -6983, -6855, -8390, -8193, -8108, -7808, -7501, -7203, -7133, -7020, -6983, -6855,
@@ -114,7 +191,7 @@ public class NewGuid
636537435, 637241486, 676077268, 691630262, 696158602, 714007596, 715537265, 718873134, 724266320, 741049828, 636537435, 637241486, 676077268, 691630262, 696158602, 714007596, 715537265, 718873134, 724266320, 741049828,
749379956, 757561933, 758668417, 761735205, 770349479, 797570403, 805896481, 809050934, 821655964, 821980469, 749379956, 757561933, 758668417, 761735205, 770349479, 797570403, 805896481, 809050934, 821655964, 821980469,
830824227, 840429528, 851772315, 859717719, 859763860, 867675943, 912124563, 914880620, 923914294, 930298008, 830824227, 840429528, 851772315, 859717719, 859763860, 867675943, 912124563, 914880620, 923914294, 930298008,
932610035, 937468680, 945565998, 949277691, 949397209, 951283050, 953249971, 953953188, 976210158, 982233484)] 932610035, 937468680, 945565998, 949277691, 949397209, 951283050, 953249971, 953953188, 976210158, 982233484, 982233485)]
[InlineData(GuidV8Entropy.Strong, [InlineData(GuidV8Entropy.Strong,
6629058, 24114993, 40561510, 46245969, 46876997, 48747281, 80489854, 110237218, 117445694, 118974860, 6629058, 24114993, 40561510, 46245969, 46876997, 48747281, 80489854, 110237218, 117445694, 118974860,
135132579, 141760591, 149114066, 158322437, 159065333, 164925904, 173848639, 175086337, 175704556, 176335514, 135132579, 141760591, 149114066, 158322437, 159065333, 164925904, 173848639, 175086337, 175704556, 176335514,
@@ -125,10 +202,10 @@ public class NewGuid
636537435, 637241486, 676077268, 691630262, 696158602, 714007596, 715537265, 718873134, 724266320, 741049828, 636537435, 637241486, 676077268, 691630262, 696158602, 714007596, 715537265, 718873134, 724266320, 741049828,
749379956, 757561933, 758668417, 761735205, 770349479, 797570403, 805896481, 809050934, 821655964, 821980469, 749379956, 757561933, 758668417, 761735205, 770349479, 797570403, 805896481, 809050934, 821655964, 821980469,
830824227, 840429528, 851772315, 859717719, 859763860, 867675943, 912124563, 914880620, 923914294, 930298008, 830824227, 840429528, 851772315, 859717719, 859763860, 867675943, 912124563, 914880620, 923914294, 930298008,
932610035, 937468680, 945565998, 949277691, 949397209, 951283050, 953249971, 953953188, 976210158, 982233484)] 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(GuidV8Entropy entropy, params int[] seconds)
{ {
var rng = new Random(25); var rng = new Random(26);
var referenceTime = new DateTime(2024, 05, 17, 15, 36, 13, 771, DateTimeKind.Utc); var referenceTime = new DateTime(2024, 05, 17, 15, 36, 13, 771, DateTimeKind.Utc);
SortedList<DateTime, Guid> expected = new(seconds.Length); SortedList<DateTime, Guid> expected = new(seconds.Length);
foreach (var s in seconds) foreach (var s in seconds)
@@ -140,78 +217,10 @@ public class NewGuid
var sut = expected.Values.ToArray(); var sut = expected.Values.ToArray();
rng.Shuffle(sut); rng.Shuffle(sut);
sut.Order().Should().BeEquivalentTo(expected.Select(x => x.Value)); sut.Order().Should().Equal(expected.Select(x => x.Value));
sut.OrderDescending().Should().BeEquivalentTo(expected.Reverse().Select(x => x.Value)); sut.OrderBy(x => x.ToString()).Should().Equal(expected.Select(x => x.Value));
}
[Theory] sut.OrderDescending().Should().Equal(expected.Reverse().Select(x => x.Value));
[InlineData(GuidV8Entropy.Weak, sut.OrderByDescending(x => x.ToString()).Should().Equal(expected.Reverse().Select(x => x.Value));
-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)]
[InlineData(GuidV8Entropy.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)]
[InlineData(GuidV8Entropy.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,
-4314, -3920, -3251, -3233, -3116, -2792, -2685, -2574, -2338, -2173,
-1934, -1914, -1528, -1483, -1410, -870, -757, -730, -263, -220,
151, 426, 588, 970, 1213, 1269, 1376, 1397, 1450, 1770,
2063, 2577, 2750, 2860, 3139, 3161, 3488, 3630, 3774, 4004,
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,
-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,
-4314, -3920, -3251, -3233, -3116, -2792, -2685, -2574, -2338, -2173,
-1934, -1914, -1528, -1483, -1410, -870, -757, -730, -263, -220,
151, 426, 588, 970, 1213, 1269, 1376, 1397, 1450, 1770,
2063, 2577, 2750, 2860, 3139, 3161, 3488, 3630, 3774, 4004,
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,
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,
320988273, 322638028, 324110126, 326855208, 332533719, 336668313, 350798512, 366520367, 380595181, 405033666,
410929500, 414099488, 417697882, 421269768, 431907031, 435262715, 442168482, 449797406, 458508845, 489039529,
503344305, 514934509, 537515867, 555210743, 563951463, 578783864, 580560518, 580902916, 605801607, 635231377,
636537435, 637241486, 676077268, 691630262, 696158602, 714007596, 715537265, 718873134, 724266320, 741049828,
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)]
[InlineData(GuidV8Entropy.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,
320988273, 322638028, 324110126, 326855208, 332533719, 336668313, 350798512, 366520367, 380595181, 405033666,
410929500, 414099488, 417697882, 421269768, 431907031, 435262715, 442168482, 449797406, 458508845, 489039529,
503344305, 514934509, 537515867, 555210743, 563951463, 578783864, 580560518, 580902916, 605801607, 635231377,
636537435, 637241486, 676077268, 691630262, 696158602, 714007596, 715537265, 718873134, 724266320, 741049828,
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)]
public void Guids_Differing_By_Microseconds_Should_Be_Sortable(GuidV8Entropy entropy, params int[] seconds)
{
var rng = new Random(25);
var referenceTime = new DateTime(2024, 05, 17, 15, 36, 13, 771, DateTimeKind.Utc);
SortedList<DateTime, Guid> expected = new(seconds.Length);
foreach (var s in seconds)
{
var timestamp = referenceTime.AddMicroseconds(s);
expected.Add(timestamp, GuidV8.NewGuid(timestamp, entropy));
}
var sut = expected.Values.ToArray();
rng.Shuffle(sut);
sut.Order().Should().BeEquivalentTo(expected.Select(x => x.Value));
sut.OrderDescending().Should().BeEquivalentTo(expected.Reverse().Select(x => x.Value));
} }
} }

View File

@@ -0,0 +1,58 @@
using Just.Core.Extensions;
namespace Just.Core.Tests.SystemIOStreamExtensionsTests;
public class Populate
{
[Theory]
[InlineData(0, 0)]
[InlineData(0, 1)]
[InlineData(0, 3)]
[InlineData(0, 5)]
[InlineData(3, 0)]
[InlineData(3, 1)]
[InlineData(3, 5)]
[InlineData(5, 0)]
[InlineData(5, 1)]
[InlineData(5, 5)]
public void WhenCalled_ShouldPopulateSpecifiedRange(int offset, int length)
{
byte[] streamContent = [0x01, 0x02, 0x03, 0x04, 0x05,];
using var stream = new MemoryStream(streamContent);
var buffer = new byte[10];
stream.Populate(buffer, offset, length);
buffer.Skip(offset).Take(length).Should().Equal(streamContent.Take(length));
}
[Theory]
[InlineData(new byte[]{ 0x01, 0x02, 0x03, 0x04, }, 4)]
[InlineData(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05, }, 4)]
[InlineData(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05, }, 5)]
[InlineData(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, }, 5)]
public void WhenStreamContainsSameOrGreaterAmmountOfItems_ShouldPopulateBuffer(byte[] streamContent, int bufferSize)
{
using var stream = new MemoryStream(streamContent);
var buffer = new byte[bufferSize];
stream.Populate(buffer);
buffer.Should().Equal(streamContent.Take(bufferSize));
}
[Theory]
[InlineData(new byte[]{ 0x01, 0x02, 0x03, 0x04, }, 5)]
[InlineData(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05, }, 6)]
[InlineData(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05, }, 10)]
[InlineData(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, }, 9)]
public void WhenStreamContainsLessItems_ShouldThrowEndOfStreamException(byte[] streamContent, int bufferSize)
{
using var stream = new MemoryStream(streamContent);
var buffer = new byte[bufferSize];
Action action = () => stream.Populate(buffer);
action.Should().Throw<EndOfStreamException>();
}
}

View File

@@ -0,0 +1,72 @@
using Just.Core.Extensions;
namespace Just.Core.Tests.SystemIOStreamExtensionsTests;
public class PopulateAsync
{
[Fact]
public async Task WhenCancellationRequested_ShouldThrowOperationCanceledException()
{
using var cts = new CancellationTokenSource();
byte[] streamContent = [0x01, 0x02, 0x03, 0x04, 0x05,];
using var stream = new MemoryStream(streamContent);
var buffer = new byte[5];
Func<Task> action = async () => await stream.PopulateAsync(buffer, cts.Token);
cts.Cancel();
await action.Should().ThrowAsync<OperationCanceledException>();
}
[Theory]
[InlineData(0, 0)]
[InlineData(0, 1)]
[InlineData(0, 3)]
[InlineData(0, 5)]
[InlineData(3, 0)]
[InlineData(3, 1)]
[InlineData(3, 5)]
[InlineData(5, 0)]
[InlineData(5, 1)]
[InlineData(5, 5)]
public async Task WhenCalled_ShouldPopulateSpecifiedRange(int offset, int length)
{
byte[] streamContent = [0x01, 0x02, 0x03, 0x04, 0x05,];
using var stream = new MemoryStream(streamContent);
var buffer = new byte[10];
await stream.PopulateAsync(buffer, offset, length);
buffer.Skip(offset).Take(length).Should().Equal(streamContent.Take(length));
}
[Theory]
[InlineData(new byte[]{ 0x01, 0x02, 0x03, 0x04, }, 4)]
[InlineData(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05, }, 4)]
[InlineData(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05, }, 5)]
[InlineData(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, }, 5)]
public async Task WhenStreamContainsSameOrGreaterAmmountOfItems_ShouldPopulateBuffer(byte[] streamContent, int bufferSize)
{
using var stream = new MemoryStream(streamContent);
var buffer = new byte[bufferSize];
await stream.PopulateAsync(buffer);
buffer.Should().Equal(streamContent.Take(bufferSize));
}
[Theory]
[InlineData(new byte[]{ 0x01, 0x02, 0x03, 0x04, }, 5)]
[InlineData(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05, }, 6)]
[InlineData(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05, }, 10)]
[InlineData(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, }, 9)]
public async Task WhenStreamContainsLessItems_ShouldThrowEndOfStreamException(byte[] streamContent, int bufferSize)
{
using var stream = new MemoryStream(streamContent);
var buffer = new byte[bufferSize];
Func<Task> action = async () => await stream.PopulateAsync(buffer);
await action.Should().ThrowAsync<EndOfStreamException>();
}
}

View File

@@ -24,6 +24,8 @@ public static class Base32
[Pure] [Pure]
public static int Encode(ReadOnlySpan<byte> input, Span<char> output) public static int Encode(ReadOnlySpan<byte> input, Span<char> output)
{ {
if (input.IsEmpty) return 0;
int i = 0; int i = 0;
ReadOnlySpan<char> alphabet = Alphabet; ReadOnlySpan<char> alphabet = Alphabet;
for (int offset = 0; offset < input.Length;) for (int offset = 0; offset < input.Length;)

View File

@@ -95,6 +95,8 @@ public static class Base64Url
[Pure] public static int Encode(ReadOnlySpan<byte> input, Span<char> output) [Pure] public static int Encode(ReadOnlySpan<byte> input, Span<char> output)
{ {
if (input.IsEmpty) return 0;
var charlen = 8 * ((input.Length + 5) / 6); var charlen = 8 * ((input.Length + 5) / 6);
Span<char> chars = stackalloc char[charlen]; Span<char> chars = stackalloc char[charlen];
Convert.TryToBase64Chars(input, chars, out int charsWritten); Convert.TryToBase64Chars(input, chars, out int charsWritten);

View File

@@ -0,0 +1,90 @@
using System.Collections;
using System.Collections.Immutable;
namespace Just.Core.Collections;
public class ImmutableSequence<T> :
IEnumerable<T>,
IReadOnlyList<T>,
IEquatable<ImmutableSequence<T>>
{
private static readonly int InitialHash = typeof(ImmutableSequence<T>).GetHashCode();
private static readonly Func<T?, T?, bool> CompareItem = EqualityComparer<T>.Default.Equals;
private readonly ImmutableList<T> _values;
public ImmutableSequence(ImmutableList<T> values) => _values = values;
public ImmutableSequence() : this(ImmutableArray<T>.Empty)
{
}
public ImmutableSequence(IEnumerable<T> values)
{
_values = [..values];
}
public ImmutableSequence(ReadOnlySpan<T> values) : this(ImmutableList.Create(values))
{
}
public bool IsEmpty => _values.IsEmpty;
public int Count => _values.Count;
public T this[int index] => _values[index];
public T this[Index index] => _values[index];
public ImmutableSequence<T> this[Range range]
{
get
{
var (offset, count) = range.GetOffsetAndLength(_values.Count);
return ConstructNew(_values.GetRange(offset, count));
}
}
protected virtual ImmutableSequence<T> ConstructNew(ImmutableList<T> values) => new(values);
public ImmutableSequence<T> Add(T value) => ConstructNew([.._values, value]);
public ImmutableSequence<T> AddFront(T value) => ConstructNew([value, .._values]);
public ImmutableList<T>.Enumerator GetEnumerator() => _values.GetEnumerator();
IEnumerator<T> IEnumerable<T>.GetEnumerator() => ((IEnumerable<T>)_values).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_values).GetEnumerator();
public override string ToString() => string.Join(Environment.NewLine, _values);
public virtual bool Equals([NotNullWhen(true)] ImmutableSequence<T>? other)
{
if (ReferenceEquals(this, other))
{
return true;
}
if (_values.Count != other?._values.Count)
{
return false;
}
for (int i = 0; i < _values.Count; i++)
{
if (!CompareItem(_values[i], other._values[i]))
{
return false;
}
}
return true;
}
public override bool Equals([NotNullWhen(true)] object? obj) => Equals(obj as ImmutableSequence<T>);
public override int GetHashCode()
{
HashCode hash = new();
hash.Add(InitialHash);
foreach (var value in _values)
{
hash.Add(value);
}
return hash.ToHashCode();
}
public static bool operator ==(ImmutableSequence<T>? left, ImmutableSequence<T>? right) => left is null ? right is null : left.Equals(right);
public static bool operator !=(ImmutableSequence<T>? left, ImmutableSequence<T>? right) => !(left == right);
}

View File

@@ -10,7 +10,7 @@
<Description>Small .Net library with useful helper classes, functions and extensions.</Description> <Description>Small .Net library with useful helper classes, functions and extensions.</Description>
<PackageTags>extensions;helpers;helper-functions</PackageTags> <PackageTags>extensions;helpers;helper-functions</PackageTags>
<Authors>JustFixMe</Authors> <Authors>JustFixMe</Authors>
<Copyright>Copyright (c) 2023 JustFixMe</Copyright> <Copyright>Copyright (c) 2023,2024 JustFixMe</Copyright>
<PackageLicenseFile>LICENSE</PackageLicenseFile> <PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/JustFixMe/Just.Core/</RepositoryUrl> <RepositoryUrl>https://github.com/JustFixMe/Just.Core/</RepositoryUrl>

View File

@@ -8,7 +8,7 @@ public static class SystemIOStreamExtensions
=> stream.Populate(buffer.AsSpan()); => stream.Populate(buffer.AsSpan());
public static void Populate(this Stream stream, Span<byte> buffer) public static void Populate(this Stream stream, Span<byte> buffer)
{ {
do while (buffer.Length > 0)
{ {
var readed = stream.Read(buffer); var readed = stream.Read(buffer);
@@ -19,7 +19,6 @@ public static class SystemIOStreamExtensions
buffer = buffer[readed..]; buffer = buffer[readed..];
} }
while (buffer.Length > 0);
} }
public static async ValueTask PopulateAsync(this Stream stream, byte[] buffer, CancellationToken cancellationToken = default) public static async ValueTask PopulateAsync(this Stream stream, byte[] buffer, CancellationToken cancellationToken = default)
@@ -28,7 +27,7 @@ public static class SystemIOStreamExtensions
=> await stream.PopulateAsync(buffer.AsMemory(offset, length), cancellationToken); => await stream.PopulateAsync(buffer.AsMemory(offset, length), cancellationToken);
public static async ValueTask PopulateAsync(this Stream stream, Memory<byte> buffer, CancellationToken cancellationToken = default) public static async ValueTask PopulateAsync(this Stream stream, Memory<byte> buffer, CancellationToken cancellationToken = default)
{ {
do while (buffer.Length > 0)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@@ -41,6 +40,5 @@ public static class SystemIOStreamExtensions
buffer = buffer[readed..]; buffer = buffer[readed..];
} }
while (buffer.Length > 0);
} }
} }

View File

@@ -13,28 +13,28 @@ public static class GuidV8
[Pure] [Pure]
public static Guid NewGuid(DateTime dateTime, GuidV8Entropy entropy = GuidV8Entropy.Strong) public static Guid NewGuid(DateTime dateTime, GuidV8Entropy entropy = GuidV8Entropy.Strong)
{ {
var timestamp = dateTime.ToBinary() & 0x3FFF_FFFF_FFFF_FFFF; var epoch = dateTime.Subtract(DateTime.UnixEpoch);
var timestamp = epoch.Ticks / (TimeSpan.TicksPerMillisecond / 10);
Span<byte> ts = stackalloc byte[8]; Span<byte> ts = stackalloc byte[8];
MemoryMarshal.Write(ts, timestamp); MemoryMarshal.Write(ts, timestamp);
Span<byte> bytes = stackalloc byte[16]; Span<byte> bytes = stackalloc byte[16];
ts[4..].CopyTo(bytes[..4]); ts[0..2].CopyTo(bytes[4..6]);
ts[2..4].CopyTo(bytes[4..6]); ts[2..6].CopyTo(bytes[..4]);
ts[..2].CopyTo(bytes[6..8]);
bytes[7] = (byte)((bytes[7] & 0x0f) | 0x80);
if (entropy == GuidV8Entropy.Strong) if (entropy == GuidV8Entropy.Strong)
{ {
RandomNumberGenerator.Fill(bytes[8..]); RandomNumberGenerator.Fill(bytes[6..]);
} }
else else
{ {
Random.Shared.NextBytes(bytes[8..]); Random.Shared.NextBytes(bytes[6..]);
} }
bytes[7] = (byte)((bytes[7] & 0x0F) | 0x80);
return new Guid(bytes); return new Guid(bytes);
} }
} }

View File

@@ -1,4 +1,4 @@
Copyright (c) 2023 JustFixMe Copyright (c) 2023,2024 JustFixMe
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal