Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Hashids.net/Hashids.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ public partial class Hashids : IHashids

private const double SEP_DIV = 3.5;
private const double GUARD_DIV = 12.0;
private const int MaxNumberHashLength = 19; // Length of long.MaxValue;

private readonly char[] _alphabet;
private readonly char[] _seps;
Expand Down Expand Up @@ -303,7 +302,8 @@ private string GenerateHashFrom(ReadOnlySpan<long> numbers)
var startIndex = 1 + _salt.Length;
var length = _alphabet.Length - startIndex;

Span<char> hashBuffer = stackalloc char[MaxNumberHashLength];
// use buffer size of 19 which is the length of the biggest 64-bit integer (long.MaxValue = 9223372036854775807)
Span<char> hashBuffer = stackalloc char[19];

for (var i = 0; i < numbers.Length; i++)
{
Expand Down
161 changes: 79 additions & 82 deletions test/Hashids.net.test/GeneralTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using Moq;
Expand All @@ -9,33 +8,22 @@ namespace HashidsNet.test
{
public class GeneralTests
{
private const string salt = "this is my salt";
private readonly Hashids _hashids = new Hashids(salt);
private readonly Hashids _hashids = new Hashids(salt: "this is my salt");

[Fact]
public async Task EncodingIsThreadSafe()
public void EncodingIsThreadSafe()
{
var hashids = new Hashids();
const int threadCount = 6;
const int numberCount = 1000001;
const int numberCount = 10_000;

var tasks = Enumerable.Range(1, threadCount).Select(t => Task.Run(() =>
Parallel.For(0, 100, i =>
{
for (var n = 1; n < numberCount; n++)
for (var n = 0; n < numberCount; n++)
{
var s = hashids.Encode(n);
hashids.Decode(s).Should().Equal(n);
var e = hashids.Encode(n);
hashids.Decode(e).Should().Equal(n);
}
})).ToArray();

await Task.WhenAll(tasks);
}

[Fact]
public void DefaultSaltIsBlank()
{
// default salt of empty string "" should result in this encoded value
new Hashids().Encode(1, 2, 3).Should().Be("o2fXhV");
});
}

[Fact]
Expand All @@ -51,7 +39,17 @@ public void SingleInt_Encodes()
}

[Fact]
public void SingleReturn_Decodes()
public void SingleInt_Decodes()
{
_hashids.Decode("NkK9").Should().Equal(new[] { 12345 });
_hashids.Decode("5O8yp5P").Should().Equal(new[] { 666555444 });
_hashids.Decode("Wzo").Should().Equal(new[] { 1337 });
_hashids.Decode("DbE").Should().Equal(new[] { 808 });
_hashids.Decode("yj8").Should().Equal(new[] { 303 });
}

[Fact]
public void SingleInt_DecodesSingleNumber()
{
_hashids.DecodeSingle("NkK9").Should().Be(12345);
_hashids.DecodeSingle("5O8yp5P").Should().Be(666555444);
Expand All @@ -64,7 +62,7 @@ public void SingleReturn_Decodes()
}

[Fact]
public void SingleReturnOut_Decodes()
public void SingleInt_DecodesSingleNumberWithTry()
{
int value;

Expand All @@ -89,16 +87,6 @@ public void SingleReturnOut_Decodes()
value.Should().Be(303);
}

[Fact]
public void SingleInt_Decodes()
{
_hashids.Decode("NkK9").Should().Equal(new[] { 12345 });
_hashids.Decode("5O8yp5P").Should().Equal(new[] { 666555444 });
_hashids.Decode("Wzo").Should().Equal(new[] { 1337 });
_hashids.Decode("DbE").Should().Equal(new[] { 808 });
_hashids.Decode("yj8").Should().Equal(new[] { 303 });
}

[Fact]
public void SingleLong_Encodes()
{
Expand All @@ -107,62 +95,62 @@ public void SingleLong_Encodes()
_hashids.EncodeLong(4294967296L).Should().Be("D54yen6");
_hashids.EncodeLong(666555444333222L).Should().Be("KVO9yy1oO5j");
_hashids.EncodeLong(12345678901112L).Should().Be("4bNP1L26r");
_hashids.EncodeLong(Int64.MaxValue).Should().Be("jvNx4BjM5KYjv");
_hashids.EncodeLong(long.MaxValue).Should().Be("jvNx4BjM5KYjv");
}

[Fact]
public void SingleLong_Decode()
public void SingleLong_Decodes()
{
_hashids.DecodeLong("NV").Should().Equal(new[] { 1L });
_hashids.DecodeLong("21OjjRK").Should().Equal(new[] { 2147483648L });
_hashids.DecodeLong("D54yen6").Should().Equal(new[] { 4294967296L });
_hashids.DecodeLong("KVO9yy1oO5j").Should().Equal(new[] { 666555444333222L });
_hashids.DecodeLong("4bNP1L26r").Should().Equal(new[] { 12345678901112L });
_hashids.DecodeLong("jvNx4BjM5KYjv").Should().Equal(new[] { Int64.MaxValue });
_hashids.DecodeLong("jvNx4BjM5KYjv").Should().Equal(new[] { long.MaxValue });
}

[Fact]
public void SingleReturnLong_Decode()
public void SingleLong_DecodesSingleNumber()
{
_hashids.DecodeSingleLong("NV").Should().Be(1L);
_hashids.DecodeSingleLong("21OjjRK").Should().Be(2147483648L);
_hashids.DecodeSingleLong("D54yen6").Should().Be(4294967296L);
_hashids.DecodeSingleLong("KVO9yy1oO5j").Should().Be(666555444333222L);
_hashids.DecodeSingleLong("4bNP1L26r").Should().Be(12345678901112L);
_hashids.DecodeSingleLong("jvNx4BjM5KYjv").Should().Be(Int64.MaxValue);
_hashids.DecodeSingleLong("jvNx4BjM5KYjv").Should().Be(long.MaxValue);

Assert.Throws<NoResultException>(() => _hashids.DecodeSingleLong(string.Empty));
Assert.Throws<MultipleResultsException>(() => _hashids.DecodeSingleLong("6gH3kPY7MJ9zjM3"));
}

[Fact]
public void SingleReturnOutLong_Decodes()
public void SingleLong_DecodesSingleNumberWithTry()
{
long value;
long decoded;

_hashids.TryDecodeSingleLong("NV,NV", out value).Should().Be(false);
_hashids.TryDecodeSingleLong("NV", out value).Should().Be(true);
value.Should().Be(1L);
_hashids.TryDecodeSingleLong("NV,NV", out decoded).Should().Be(false);
_hashids.TryDecodeSingleLong("NV", out decoded).Should().Be(true);
decoded.Should().Be(1L);

_hashids.TryDecodeSingleLong("21OjjRK,21OjjRK", out value).Should().Be(false);
_hashids.TryDecodeSingleLong("21OjjRK", out value).Should().Be(true);
value.Should().Be(2147483648L);
_hashids.TryDecodeSingleLong("21OjjRK,21OjjRK", out decoded).Should().Be(false);
_hashids.TryDecodeSingleLong("21OjjRK", out decoded).Should().Be(true);
decoded.Should().Be(2147483648L);

_hashids.TryDecodeSingleLong("D54yen6,D54yen6", out value).Should().Be(false);
_hashids.TryDecodeSingleLong("D54yen6", out value).Should().Be(true);
value.Should().Be(4294967296L);
_hashids.TryDecodeSingleLong("D54yen6,D54yen6", out decoded).Should().Be(false);
_hashids.TryDecodeSingleLong("D54yen6", out decoded).Should().Be(true);
decoded.Should().Be(4294967296L);

_hashids.TryDecodeSingleLong("KVO9yy1oO5j,KVO9yy1oO5j", out value).Should().Be(false);
_hashids.TryDecodeSingleLong("KVO9yy1oO5j", out value).Should().Be(true);
value.Should().Be(666555444333222L);
_hashids.TryDecodeSingleLong("KVO9yy1oO5j,KVO9yy1oO5j", out decoded).Should().Be(false);
_hashids.TryDecodeSingleLong("KVO9yy1oO5j", out decoded).Should().Be(true);
decoded.Should().Be(666555444333222L);

_hashids.TryDecodeSingleLong("4bNP1L26r,4bNP1L26r", out value).Should().Be(false);
_hashids.TryDecodeSingleLong("4bNP1L26r", out value).Should().Be(true);
value.Should().Be(12345678901112L);
_hashids.TryDecodeSingleLong("4bNP1L26r,4bNP1L26r", out decoded).Should().Be(false);
_hashids.TryDecodeSingleLong("4bNP1L26r", out decoded).Should().Be(true);
decoded.Should().Be(12345678901112L);

_hashids.TryDecodeSingleLong("jvNx4BjM5KYjv,jvNx4BjM5KYjv", out value).Should().Be(false);
_hashids.TryDecodeSingleLong("jvNx4BjM5KYjv", out value).Should().Be(true);
value.Should().Be(Int64.MaxValue);
_hashids.TryDecodeSingleLong("jvNx4BjM5KYjv,jvNx4BjM5KYjv", out decoded).Should().Be(false);
_hashids.TryDecodeSingleLong("jvNx4BjM5KYjv", out decoded).Should().Be(true);
decoded.Should().Be(long.MaxValue);
}

[Fact]
Expand Down Expand Up @@ -192,8 +180,8 @@ public void ListOfInt_Decodes()
public void ListOfInt_Roundtrip()
{
var input = new[] { 12345, 67890, int.MaxValue };
var decodedValue = _hashids.Decode(_hashids.Encode(input));
decodedValue.Should().BeEquivalentTo(input);
var decoded = _hashids.Decode(_hashids.Encode(input));
decoded.Should().BeEquivalentTo(input);
}

[Fact]
Expand All @@ -212,8 +200,8 @@ public void ListOfLong_Decodes()
public void ListOfLong_Roundtrip()
{
var input = new[] { 1L, 1234567890123456789, long.MaxValue };
var decodedValue = _hashids.DecodeLong(_hashids.EncodeLong(input));
decodedValue.Should().BeEquivalentTo(input);
var decoded = _hashids.DecodeLong(_hashids.EncodeLong(input));
decoded.Should().BeEquivalentTo(input);
}

[Fact]
Expand Down Expand Up @@ -257,16 +245,16 @@ public void HexString_Roundtrip()
public void NumbersIncludingZero_AtStart_Roundtrip()
{
var input = new[] { 0, 1, 2 };
var decodedValue = _hashids.Decode(_hashids.Encode(input));
decodedValue.Should().Equal(input);
var decoded = _hashids.Decode(_hashids.Encode(input));
decoded.Should().Equal(input);
}

[Fact]
public void NumbersIncludingZero_AtEnd_Roundtrip()
{
var input = new[] { 1, 2, 0 };
var decodedValue = _hashids.Decode(_hashids.Encode(input));
decodedValue.Should().Equal(input);
var decoded = _hashids.Decode(_hashids.Encode(input));
decoded.Should().Equal(input);
}

[Fact]
Expand Down Expand Up @@ -311,26 +299,19 @@ public void ListWithNegativeNumbers_ReturnsEmptyString()
_hashids.EncodeLong(1, long.MaxValue, -4).Should().Be(string.Empty);
}

[Fact]
public void DifferentSalt_ReturnsEmptyList()
{
_hashids.Decode("NkK9").Should().Equal(new[] { 12345 });
new Hashids("different salt").Decode("NkK9").Should().Equal(new int[0]);
}

[Fact]
public void HashMinLength_EncodesHashWithAtLeastThatLength()
{
var hashLength = 18;
var hashids = new Hashids(salt, hashLength);
var hashids = new Hashids(salt: "this is my salt", minHashLength: hashLength);
hashids.Encode(1).Length.Should().BeGreaterOrEqualTo(hashLength);
hashids.Encode(4140, 21147, 115975, 678570, 4213597, 27644437).Length.Should().BeGreaterOrEqualTo(hashLength);
}

[Fact]
public void HashMinLength_Decodes()
{
var hashids = new Hashids(salt, 8);
var hashids = new Hashids(salt: "this is my salt", minHashLength: 8);
hashids.Decode("gB0NV05e").Should().Equal(new[] { 1 });
hashids.Decode("mxi8XH87").Should().Equal(new[] { 25, 100, 950 });
hashids.Decode("KQcmkIW8hX").Should().Equal(new[] { 5, 200, 195, 1 });
Expand All @@ -356,17 +337,20 @@ public void AlphabetWithDashes_Roundtrip()
public void AlphabetLessThanMinLength_ThrowsArgumentException()
{
var tooShortAlphabet = Hashids.DEFAULT_ALPHABET.Substring(0, Hashids.MIN_ALPHABET_LENGTH - 1);
Action invocation = () => new Hashids(alphabet: tooShortAlphabet);
var invocation = () => new Hashids(alphabet: tooShortAlphabet);
invocation.Should().Throw<ArgumentException>();
}

[Fact]
public void AlphabetAtLeastMinLength_ShouldNotThrowException()
{
var minLengthAlphabet = Hashids.DEFAULT_ALPHABET.Substring(0, Hashids.MIN_ALPHABET_LENGTH);
var minLengthCtor = () => new Hashids(alphabet: minLengthAlphabet);
minLengthCtor.Should().NotThrow();

var largerLengthAlphabet = Hashids.DEFAULT_ALPHABET.Substring(0, Hashids.MIN_ALPHABET_LENGTH + 1);
var results1 = new Hashids(alphabet: minLengthAlphabet);
var results2 = new Hashids(alphabet: largerLengthAlphabet);
var largerLengthCtor = () => new Hashids(alphabet: largerLengthAlphabet);
largerLengthCtor.Should().NotThrow();
}

[Fact]
Expand All @@ -376,10 +360,24 @@ public void NullAlphabet_ThrowsNullArgumentException()
invocation.Should().Throw<ArgumentNullException>();
}

[Fact]
public void DefaultSaltIsBlank()
{
// default salt of empty string "" should result in this encoded value
new Hashids().Encode(1, 2, 3).Should().Be("o2fXhV");
}

[Fact]
public void DifferentSalt_ReturnsEmptyList()
{
_hashids.Decode("NkK9").Should().Equal(new[] { 12345 });
new Hashids("different salt").Decode("NkK9").Should().Equal(new int[0]);
}

[Fact]
public void CustomAlphabet_Roundtrip()
{
var hashids = new Hashids(salt, 0, "ABCDEFGhijklmn34567890-:");
var hashids = new Hashids(salt: "this is my salt", minHashLength: 0, alphabet: "ABCDEFGhijklmn34567890-:");
var input = new[] { 1, 2, 3, 4, 5 };
hashids.Encode(input).Should().Be("6nhmFDikA0");
hashids.Decode(hashids.Encode(input)).Should().BeEquivalentTo(input);
Expand All @@ -388,7 +386,7 @@ public void CustomAlphabet_Roundtrip()
[Fact]
public void CustomAlphabet2_Roundtrip()
{
var hashids = new Hashids(salt, 0, "ABCDEFGHIJKMNOPQRSTUVWXYZ23456789");
var hashids = new Hashids(salt: "this is my salt", minHashLength: 0, alphabet: "ABCDEFGHIJKMNOPQRSTUVWXYZ23456789");
var input = new[] { 1, 2, 3, 4, 5 };
hashids.Encode(input).Should().Be("44HYIRU3TO");
hashids.Decode(hashids.Encode(input)).Should().BeEquivalentTo(input);
Expand All @@ -397,8 +395,7 @@ public void CustomAlphabet2_Roundtrip()
[Fact]
public void SaltIsLongerThanAlphabet_Roundtrip()
{
var longSalt = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
var hashids = new Hashids(salt: longSalt);
var hashids = new Hashids(salt: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890");
var input = new[] { 1, 2, 0 };
var decodedValue = hashids.Decode(hashids.Encode(input));
decodedValue.Should().Equal(input);
Expand All @@ -408,7 +405,7 @@ public void SaltIsLongerThanAlphabet_Roundtrip()
public void GuardCharacterOnly_DecodesToEmptyArray()
{
// no salt creates guard characters: "abde"
var hashids = new Hashids("");
var hashids = new Hashids(salt: "");
var decodedValue = hashids.Decode("a");
decodedValue.Should().Equal(Array.Empty<int>());
}
Expand Down
16 changes: 8 additions & 8 deletions test/Hashids.net.test/IssueSpecificTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace HashidsNet.test
public class IssueSpecificTests
{
[Fact]
void issue_8_should_not_throw_out_of_range_exception()
void Issue_8_should_not_throw_out_of_range_exception()
{
var hashids = new Hashids("janottaa", 6);
var numbers = hashids.Decode("NgAzADEANAA=");
Expand All @@ -18,7 +18,7 @@ void issue_8_should_not_throw_out_of_range_exception()
// seems to happen when you are encoding A LOT of longs at the same time.
// see if it is possible to make this a faster test (or remove it since it is unlikely that it will reapper).
[Fact]
void issue_12_should_not_throw_out_of_range_exception()
void Issue_12_should_not_throw_out_of_range_exception()
{
var hash = new Hashids("zXZVFf2N38uV");
var longs = new List<long>();
Expand All @@ -38,23 +38,23 @@ void issue_12_should_not_throw_out_of_range_exception()
}

[Fact]
void issue_15_it_should_return_emtpy_array_when_decoding_characters_missing_in_alphabet()
void Issue_15_it_should_return_empty_array_when_decoding_characters_missing_in_alphabet()
{
var hashids = new Hashids(salt: "Salty stuff", alphabet: "qwerty1234!¤%&/()=", seps: "1234");
var numbers = hashids.Decode("abcd");
numbers.Length.Should().Be(0);

var hashids2 = new Hashids();
hashids.Decode("13-37").Length.Should().Be(0);
hashids.DecodeLong("32323kldffd!").Length.Should().Be(0);
hashids2.Decode("13-37").Length.Should().Be(0);
hashids2.DecodeLong("32323kldffd!").Length.Should().Be(0);

var hashids3 = new Hashids(alphabet: "1234567890;:_!#¤%&/()=", seps: "!#¤%&/()=");
hashids.Decode("asdfb").Length.Should().Be(0);
hashids.DecodeLong("asdfgfdgdfgkj").Length.Should().Be(0);
hashids3.Decode("asdfb").Length.Should().Be(0);
hashids3.DecodeLong("asdfgfdgdfgkj").Length.Should().Be(0);
}

[Fact]
void issue_64_it_should_be_possible_to_encode_and_decode_long_max_value()
void Issue_64_long_max_value_with_min_alphabet_length()
{
var hashids = new Hashids("salt", alphabet: "0123456789ABCDEF");
var hash = hashids.EncodeLong(long.MaxValue);
Expand Down