using AutoFixture; using BenchmarkDotNet.Attributes; using FluentAssertions; using MoreLinq; namespace Huffman.Tree.Tests { [TestClass] [MemoryDiagnoser] public class HuffmanTreeTests { public readonly record struct TestCase { public (string token, long frequency)[] Tokens { get; init; } public (string token, int[] bits)[] Expected { get; init; } } private static IEnumerable TestCases => EnumerateTestCases().SelectMany(x => x.OfType()); private IEnumerable>? _codes; private (string token, long frequency)[] _tokens; public HuffmanTreeTests() { _tokens = TestCases.First().Tokens; } [TestMethod] [DynamicData(nameof(EnumerateTestCases), DynamicDataSourceType.Method)] public void BuildTreeShouldSuccess(TestCase testCase) { // This is to overcome the requirement for BuildTree() to be argument- & deferred-result-free. It's required by BenchmarkDotNet. _tokens = testCase.Tokens; BuildTree(); PrintCodesToOutput(); ValidateCodes(testCase.Expected); } private void ValidateCodes((string token, int[] bits)[] expected) { var expectedBits = expected.Select(x => (x.token, x.bits.Select(bit => Convert.ToBoolean(bit)))); _ = _codes.Should().NotBeNull(); _ = _codes!.Select(x => (x.Token, x.Code)) .Should() .NotBeNull().And .BeEquivalentTo(expectedBits); } private void PrintCodesToOutput() { foreach (var node in _codes!) { string bits = node.Code!.AsString(); Console.WriteLine($"{node.Token} => {bits}"); } } public static IEnumerable EnumerateTestCases() { yield return new object[] { new TestCase { // test case Tokens = new[] { ("a", 10L), ("b", 5L), ("c", 2L), ("d", 14L), ("e", 15L), (";", long.MaxValue) }, // expected result Expected = new[] { (";", new[] { 1 }), ("a", new[] { 0, 0, 1 }), ("b", new[] { 0, 0, 0, 1 }), ("c", new[] { 0, 0, 0, 0 }), ("d", new[] { 0, 1, 0 }), ("e", new[] { 0, 1, 1 }) } } }; } [Benchmark] public void BuildTree() { _codes = _tokens?.AsHuffmanTree().Nodes.OfType>(); // We keep this assertions here because we want to build all the codes. For this we need to access each of them. _ = _codes.Should().NotBeNull().And.AllSatisfy(node => node.Code!.AsString().Should().NotBeNull().And.NotBe(string.Empty)); } [Benchmark] [TestMethod] public void BuildTree100() { var fixture = new Fixture(); var collection = fixture.CreateMany<(string token, long frequency)>(100); _tokens = collection.ToArray(); _codes = _tokens?.AsHuffmanTree().Nodes.OfType>(); _codes.ForEach(x => Console.WriteLine($"{x.Token} => {x.Code.AsString()}")); // We keep this assertions here because we want to build all the codes. For this we need to access each of them. _ = _codes.Should().NotBeNull().And.AllSatisfy(node => node.Code!.AsString().Should().NotBeNull().And.NotBe(string.Empty)); } } }