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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ x86/
bld/
[Bb]in/
[Oo]bj/
[Bb]enchmark[Dd]ot[Nn]et.Artifacts/
[Ll]og/

# Visual Studio 2015 cache/options directory
Expand Down
30 changes: 30 additions & 0 deletions Base62.Benchmark/Base62.Benchmark.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
</ItemGroup>

<ItemGroup Condition="'$(NugetPackageVersion)' == ''">
<ProjectReference Include="..\Base62\Base62.csproj" />
</ItemGroup>

<ItemGroup Condition="'$(NugetPackageVersion)' != ''">
<PackageReference Include="Base62" Version="$(NugetPackageVersion)" />
</ItemGroup>

<ItemGroup>
<None Include="..\Base62.Tests\validation_data.txt">
<Link>validation_data.txt</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible>
</None>
</ItemGroup>

</Project>
98 changes: 98 additions & 0 deletions Base62.Benchmark/Base62Benchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Order;

namespace Base62.Benchmark;

[Orderer(SummaryOrderPolicy.Method)]
public class Base62Benchmark
{
private readonly Base62Converter _converter = new();
private readonly Base62Converter _invertedConverter = new(Base62Converter.CharacterSet.INVERTED);
private string[] _inputs = [];
private string[] _encodedValues = [];
private byte[] _bytesWithLeadingZeros = [];

[GlobalSetup]
public void GlobalSetup()
{
var validationPath = Path.Combine(AppContext.BaseDirectory, "validation_data.txt");
var inputs = new List<string>();
var encoded = new List<string>();

foreach (var line in File.ReadLines(validationPath))
{
var parts = line.Split('\t');
if (parts.Length < 2)
continue;

inputs.Add(parts[0]);
encoded.Add(parts[1]);
}

_inputs = inputs.ToArray();
_encodedValues = encoded.ToArray();
_bytesWithLeadingZeros = [0, 0, 1, 2, 0, 0];
}

[Benchmark]
public void EncodeString_AllValidationSamples()
{
foreach (var input in _inputs)
{
_ = _converter.Encode(input);
}
}

[Benchmark]
public void DecodeString_AllValidationSamples()
{
foreach (var value in _encodedValues)
{
_ = _converter.Decode(value);
}
}

[Benchmark]
public void EncodeDecode_RoundTrip_AllValidationSamples()
{
foreach (var input in _inputs)
{
var encoded = _converter.Encode(input);
_ = _converter.Decode(encoded);
}
}

[Benchmark]
public void EncodeBytes_WithLeadingZeros()
{
_ = _converter.Encode(_bytesWithLeadingZeros);
}

[Benchmark]
public void DecodeBytes_WithLeadingZeros()
{
var encoded = _converter.Encode(_bytesWithLeadingZeros);
_ = _converter.Decode(encoded);
}

[Benchmark]
public void EncodeString_InvertedCharacterSet()
{
foreach (var input in _inputs)
{
_ = _invertedConverter.Encode(input);
}
}

[Benchmark]
public void EncodeString_ShortAscii()
{
_ = _converter.Encode("120");
}

[Benchmark]
public void EncodeString_Unicode()
{
_ = _converter.Encode("love爱");
}
}
46 changes: 46 additions & 0 deletions Base62.Benchmark/BenchmarkConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.Collections.Generic;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Exporters.Csv;
using BenchmarkDotNet.Exporters.Json;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;

namespace Base62.Benchmark;

public class BenchmarkConfig : ManualConfig
{
public BenchmarkConfig(IEnumerable<string> latestPackageVersionNumbers)
{
var framework = CoreRuntime.Core10_0;

AddExporter(MarkdownExporter.GitHub);
AddExporter(CsvExporter.Default);
AddExporter(JsonExporter.Default);

AddLogger(ConsoleLogger.Default);

AddColumnProvider(DefaultColumnProviders.Instance);

AddDiagnoser(MemoryDiagnoser.Default);

foreach (var versionNumber in latestPackageVersionNumbers)
{
var nugetPackageJob = Job.ShortRun
.WithRuntime(framework)
.WithMsBuildArguments($"/p:NugetPackageVersion={versionNumber}")
.WithId($"v{versionNumber}");

AddJob(nugetPackageJob);
}

var currentProjectJob = Job.ShortRun
.WithRuntime(framework)
.WithId("current");

AddJob(currentProjectJob);
}
}
12 changes: 12 additions & 0 deletions Base62.Benchmark/Nuget/NugetPackage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Text.Json.Serialization;

namespace Base62.Benchmark.Nuget;

public class NugetPackage
{
[JsonPropertyName("id")]
public required string Id { get; set; }

[JsonPropertyName("version")]
public required string Version { get; set; }
}
15 changes: 15 additions & 0 deletions Base62.Benchmark/Nuget/NugetSearchResultRoot.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Text.Json.Serialization;

namespace Base62.Benchmark.Nuget;

public class NugetSearchResultRoot
{
[JsonPropertyName("version")]
public int Version { get; set; }

[JsonPropertyName("problems")]
public List<string> Problems { get; set; } = [];

[JsonPropertyName("searchResult")]
public List<NugetSourceResult> SearchResult { get; set; } = [];
}
12 changes: 12 additions & 0 deletions Base62.Benchmark/Nuget/NugetSourceResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Text.Json.Serialization;

namespace Base62.Benchmark.Nuget;

public class NugetSourceResult
{
[JsonPropertyName("sourceName")]
public required string SourceName { get; set; }

[JsonPropertyName("packages")]
public List<NugetPackage> Packages { get; set; } = [];
}
47 changes: 47 additions & 0 deletions Base62.Benchmark/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Diagnostics;
using System.Text.Json;
using Base62.Benchmark.Nuget;
using BenchmarkDotNet.Running;

namespace Base62.Benchmark;

public static class Program
{
public static async Task Main(string[] args)
{
var latestVersions = await GetLatestPackageVersionNumbers("Base62", topN: 1);

_ = BenchmarkRunner.Run<Base62Benchmark>(new BenchmarkConfig(latestVersions));
}
Comment thread
ghost1face marked this conversation as resolved.

private static async Task<List<string>> GetLatestPackageVersionNumbers(
string packageName,
int topN = 1,
CancellationToken cancellationToken = default)
{
var startInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = $"package search {packageName} --format json --exact-match",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
};

using var process = Process.Start(startInfo)
?? throw new InvalidOperationException("Failed to start dotnet process");

await process.WaitForExitAsync(cancellationToken);

var output = await process.StandardOutput.ReadToEndAsync(cancellationToken);

var packages = JsonSerializer.Deserialize<NugetSearchResultRoot>(output);

var latestPackages = packages?.SearchResult
.SelectMany(sr => sr.Packages)
.OrderByDescending(p => Version.Parse(p.Version))
.Take(topN);
Comment thread
ghost1face marked this conversation as resolved.

return latestPackages?.Select(p => p.Version).ToList() ?? [];
}
}
100 changes: 100 additions & 0 deletions Base62.Tests/Base62Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,106 @@ public void FirstZeroBytesAreConvertedCorrectly()
Assert.Equal(sourceBytes, decoded);
}

[Fact]
public void EmptyString_EncodesAndDecodesToEmpty()
{
var converter = new Base62Converter();

var encoded = converter.Encode(string.Empty);
var decoded = converter.Decode(encoded);

Assert.Equal(string.Empty, encoded);
Assert.Equal(string.Empty, decoded);
}

[Fact]
public void EmptyByteArray_EncodesAndDecodesToEmpty()
{
var converter = new Base62Converter();
var empty = Array.Empty<byte>();

var encoded = converter.Encode(empty);
var decoded = converter.Decode(encoded);

Assert.Empty(encoded);
Assert.Empty(decoded);
}

[Fact]
public void AllZeroBytes_ArePreservedOnRoundTrip()
{
var sourceBytes = new byte[] { 0, 0, 0 };
var converter = new Base62Converter();
var encodedBytes = converter.Encode(sourceBytes);
var decoded = converter.Decode(encodedBytes);

Assert.Equal(sourceBytes, decoded);
Assert.Equal("000", converter.Encode("\0\0\0"));
}

[Fact]
public void StringWithLeadingNullBytes_RoundTrips()
{
var input = "\0\0\0";
var converter = new Base62Converter();
var encoded = converter.Encode(input);
var decoded = converter.Decode(encoded);

Assert.Equal(input, decoded);
Assert.StartsWith("0", encoded);
}

[Fact]
public void SingleZeroByte_RoundTrips()
{
var sourceBytes = new byte[] { 0 };
var converter = new Base62Converter();

Assert.Equal(sourceBytes, converter.Decode(converter.Encode(sourceBytes)));
}

[Fact]
public void Decode_WithCharacterOutsideCharset_DoesNotThrow()
{
var converter = new Base62Converter();
var validEncoded = converter.Encode("120");

var decoded = converter.Decode(validEncoded + "@");

Assert.NotEqual("120", decoded);
}

[Fact]
public void Decode_WithCharacterAboveAsciiRange_DoesNotThrow()
{
var converter = new Base62Converter();
var validEncoded = converter.Encode("120");

var decoded = converter.Decode(validEncoded + "\u0080");

Assert.NotEqual("120", decoded);
}

[Fact]
public void InvertedCharacterSet_AllZeroBytes_RoundTrip()
{
var sourceBytes = new byte[] { 0, 0 };
var converter = new Base62Converter(Base62Converter.CharacterSet.INVERTED);

Assert.Equal(sourceBytes, converter.Decode(converter.Encode(sourceBytes)));
}

[Fact]
public void DefaultConstructor_MatchesExplicitDefaultCharacterSet()
{
var input = "120";
var implicitConverter = new Base62Converter();
var explicitConverter = new Base62Converter(Base62Converter.CharacterSet.DEFAULT);

Assert.Equal(explicitConverter.Encode(input), implicitConverter.Encode(input));
Assert.Equal(explicitConverter.Decode("DWjo"), implicitConverter.Decode("DWjo"));
}

public static IEnumerable<object[]> GetData()
{
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "validation_data.txt");
Expand Down
Loading
Loading