diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj index a7c3ce4e..70260ba9 100644 --- a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj +++ b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj @@ -16,6 +16,7 @@ + diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs index a5295c63..e0b97c5c 100644 --- a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs +++ b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs @@ -1,103 +1,92 @@ -//// -//// Copyright © Pete Sramek. All rights reserved. -//// Licensed under the MIT License. See LICENSE file in the project root for full license information. -//// - -//namespace PolylineAlgorithm.Benchmarks; - -//using BenchmarkDotNet.Attributes; -//using BenchmarkDotNet.Engines; -//using PolylineAlgorithm.Utility; -//using System.Collections.Generic; - -///// -///// Benchmarks for . -///// -//public class PolylineEncoderBenchmark { -// private readonly Consumer _consumer = new(); - -// /// -// /// Number of coordinates for benchmarks. -// /// -// [Params(1, 100, 1_000)] -// public int CoordinatesCount { get; set; } - -//#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. -// /// -// /// Coordinates as list. -// /// -// public List<(double Latitude, double Longitude)> List { get; private set; } - -// /// -// /// Coordinates as array. -// /// -// public (double Latitude, double Longitude)[] Array { get; private set; } - -// /// -// /// Coordinates as read-only memory. -// /// -// public ReadOnlyMemory<(double Latitude, double Longitude)> Memory { get; private set; } - -//#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. - -// /// -// /// Polyline encoder instance. -// /// -// private readonly StringPolylineEncoder _stringEncoder = new(); - -// /// -// /// Polyline encoder instance. -// /// -// private readonly CharArrayPolylineEncoder _charArrayEncoder = new(); - -// /// -// /// Polyline encoder instance. -// /// -// private readonly MemoryPolylineEncoder _memoryEncoder = new(); - -// /// -// /// Sets up benchmark data. -// /// -// [GlobalSetup] -// public void SetupData() { -// List = [.. RandomValueProvider.GetCoordinates(CoordinatesCount).Select(c => (c.Latitude, c.Longitude))]; -// Array = [.. List]; -// Memory = Array.AsMemory(); -// } - -// /// -// /// Benchmark: encode coordinates from span. -// /// -// /// Encoded polyline. -// [Benchmark] -// public void PolylineEncoder_Encode_Span() { -// var polyline = _encoder -// .Encode(Memory.Span!); - -// _consumer.Consume(polyline); -// } - -// /// -// /// Benchmark: encode coordinates from array. -// /// -// /// Encoded polyline. -// [Benchmark] -// public void PolylineEncoder_Encode_Array() { -// var polyline = _encoder -// .Encode(Array!); - -// _consumer.Consume(polyline); -// } - -// /// -// /// Benchmark: encode coordinates from list. -// /// -// /// Encoded polyline. -// [Benchmark] -// public void PolylineEncoder_Encode_List() { -// var polyline = _encoder -// .Encode(List!); - -// _consumer.Consume(polyline); -// } -//} \ No newline at end of file +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm.Benchmarks; + +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; +using PolylineAlgorithm.Abstraction; +using PolylineAlgorithm.Extensions; +using PolylineAlgorithm.Utility; +using System.Collections.Generic; + +/// +/// Benchmarks for . +/// +public class PolylineEncoderBenchmark { + private readonly Consumer _consumer = new(); + + /// + /// Number of coordinates for benchmarks. + /// + [Params(1, 100, 1_000)] + public int CoordinatesCount { get; set; } + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + /// + /// Coordinates as list. + /// + public List<(double Latitude, double Longitude)> List { get; private set; } + + /// + /// Coordinates as array. + /// + public (double Latitude, double Longitude)[] Array { get; private set; } + + /// + /// Coordinates as read-only memory. + /// + public ReadOnlyMemory<(double Latitude, double Longitude)> Memory { get; private set; } + +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + + /// + /// Polyline encoder instance. + /// + private readonly StringPolylineEncoder _encoder = new(); + + /// + /// Sets up benchmark data. + /// + [GlobalSetup] + public void SetupData() { + List = [.. RandomValueProvider.GetCoordinates(CoordinatesCount)]; + Array = [.. List]; + Memory = Array.AsMemory(); + } + + /// + /// Benchmark: encode coordinates from span. + /// + [Benchmark] + public void PolylineEncoder_Encode_Span() { + var polyline = _encoder.Encode(Memory.Span); + _consumer.Consume(polyline); + } + + /// + /// Benchmark: encode coordinates from array. + /// + [Benchmark] + public void PolylineEncoder_Encode_Array() { + var polyline = _encoder.Encode(Array); + _consumer.Consume(polyline); + } + + /// + /// Benchmark: encode coordinates from list. + /// + [Benchmark] + public void PolylineEncoder_Encode_List() { + var polyline = _encoder.Encode(List); + _consumer.Consume(polyline); + } + + private sealed class StringPolylineEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> { + protected override string CreatePolyline(ReadOnlyMemory polyline) => polyline.ToString(); + protected override double GetLatitude((double Latitude, double Longitude) current) => current.Latitude; + protected override double GetLongitude((double Latitude, double Longitude) current) => current.Longitude; + } +} diff --git a/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs b/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs index c8cc1e54..7c0449b7 100644 --- a/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs +++ b/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs @@ -4,10 +4,8 @@ // using Microsoft.Extensions.Logging; -using PolylineAlgorithm.Diagnostics; using PolylineAlgorithm.Internal; using PolylineAlgorithm.Internal.Diagnostics; -using PolylineAlgorithm.Internal.Diagnostics; using System.Runtime.CompilerServices; namespace PolylineAlgorithm.Abstraction { @@ -54,27 +52,6 @@ protected AbstractPolylineDecoder(PolylineEncodingOptions options) { /// public PolylineEncodingOptions Options { get; } - /// - /// Decodes an encoded into a sequence of instances. - /// - /// - /// The instance containing the encoded polyline string to decode. - /// - /// - /// An of representing the decoded latitude and longitude pairs. - /// - /// - /// Thrown when is . - /// - /// - /// Thrown when is empty. - /// - /// - /// Thrown when the polyline format is invalid or malformed at a specific position. - /// - public IEnumerable Decode(TPolyline polyline) - => Decode(polyline, CancellationToken.None); - /// /// Decodes an encoded into a sequence of instances, /// with support for cancellation. @@ -100,7 +77,7 @@ public IEnumerable Decode(TPolyline polyline) /// /// Thrown when is canceled during decoding. /// - public IEnumerable Decode(TPolyline polyline, CancellationToken cancellationToken) { + public IEnumerable Decode(TPolyline polyline, CancellationToken cancellationToken = default) { const string OperationName = nameof(Decode); _logger?.LogOperationStartedDebug(OperationName); diff --git a/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs b/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs index 3f68736a..801a45b3 100644 --- a/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs +++ b/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs @@ -9,11 +9,11 @@ namespace PolylineAlgorithm.Abstraction; using PolylineAlgorithm; using PolylineAlgorithm.Internal; using PolylineAlgorithm.Internal.Diagnostics; -using PolylineAlgorithm.Internal.Diagnostics; using System; using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Threading; /// /// Provides a base implementation for encoding sequences of geographic coordinates into encoded polyline strings. @@ -61,6 +61,9 @@ protected AbstractPolylineEncoder(PolylineEncodingOptions options) { /// /// The collection of objects to encode. /// + /// + /// A that can be used to cancel the encoding operation. + /// /// /// An instance of representing the encoded coordinates. /// @@ -74,7 +77,7 @@ protected AbstractPolylineEncoder(PolylineEncodingOptions options) { /// Thrown when the internal encoding buffer cannot accommodate the encoded value. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "MA0051:Method is too long", Justification = "Method contains local methods. Actual method only 55 lines.")] - public TPolyline Encode(ReadOnlySpan coordinates) { + public TPolyline Encode(ReadOnlySpan coordinates, CancellationToken cancellationToken = default) { const string OperationName = nameof(Encode); _logger @@ -98,6 +101,7 @@ public TPolyline Encode(ReadOnlySpan coordinates) { try { for (var i = 0; i < coordinates.Length; i++) { + cancellationToken.ThrowIfCancellationRequested(); delta .Next( @@ -150,7 +154,7 @@ static void ValidateEmptyCoordinates(ref ReadOnlySpan coordinates, logger .LogEmptyArgumentWarning(nameof(coordinates)); - ExceptionGuard.ThrwoArgumentCannotBeEmptyEnumerationMessage(nameof(coordinates)); + ExceptionGuard.ThrowArgumentCannotBeEmptyEnumerationMessage(nameof(coordinates)); } } } diff --git a/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs b/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs deleted file mode 100644 index b29c0ecd..00000000 --- a/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs +++ /dev/null @@ -1,86 +0,0 @@ -// -// Copyright © Pete Sramek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace PolylineAlgorithm.Extensions; - -using PolylineAlgorithm; -using PolylineAlgorithm.Abstraction; -using PolylineAlgorithm.Internal.Diagnostics; -using System; -using System.Collections.Generic; - -/// -/// Provides extension methods for the interface to facilitate decoding encoded polylines. -/// -public static class PolylineDecoderExtensions { - /// - /// Decodes an encoded polyline string into a sequence of geographic coordinates. - /// - /// - /// The instance used to perform the decoding operation. - /// - /// - /// The encoded polyline string to decode. - /// - /// - /// An containing the decoded latitude and longitude pairs. - /// - /// - /// Thrown when or is . - /// - public static IEnumerable Decode(this IPolylineDecoder decoder, string polyline) { - if (decoder is null) { - ExceptionGuard.ThrowArgumentNull(nameof(decoder)); - } - - return decoder.Decode(Polyline.FromString(polyline)); - } - - /// - /// Decodes an encoded polyline represented as a character array into a sequence of geographic coordinates. - /// - /// - /// The instance used to perform the decoding operation. - /// - /// - /// The encoded polyline as a character array to decode. - /// - /// - /// An containing the decoded latitude and longitude pairs. - /// - /// - /// Thrown when or is . - /// - public static IEnumerable Decode(this IPolylineDecoder decoder, char[] polyline) { - if (decoder is null) { - ExceptionGuard.ThrowArgumentNull(nameof(decoder)); - } - - return decoder.Decode(Polyline.FromCharArray(polyline)); - } - - /// - /// Decodes an encoded polyline represented as a read-only memory of characters into a sequence of geographic coordinates. - /// - /// - /// The instance used to perform the decoding operation. - /// - /// - /// The encoded polyline as a read-only memory of characters to decode. - /// - /// - /// An containing the decoded latitude and longitude pairs. - /// - /// - /// Thrown when is . - /// - public static IEnumerable Decode(this IPolylineDecoder decoder, ReadOnlyMemory polyline) { - if (decoder is null) { - ExceptionGuard.ThrowArgumentNull(nameof(decoder)); - } - - return decoder.Decode(Polyline.FromMemory(polyline)); - } -} diff --git a/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineDecoderTests.cs b/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineDecoderTests.cs index 5c89ec5a..037b3a9e 100644 --- a/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineDecoderTests.cs +++ b/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineDecoderTests.cs @@ -1,39 +1,144 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm.Tests.Abstraction; + using PolylineAlgorithm.Abstraction; +using PolylineAlgorithm.Utility; +using System; +using System.Collections.Generic; -namespace PolylineAlgorithm.Tests.Abstraction { - [TestClass] - public sealed class AbstractPolylineDecoderTests { - private sealed class TestStringDecoder : AbstractPolylineDecoder { - protected override ReadOnlyMemory GetReadOnlyMemory(in string polyline) => polyline.AsMemory(); - protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) => (latitude, longitude); - } +/// +/// Tests for . +/// +[TestClass] +public sealed class AbstractPolylineDecoderTests { + private sealed class TestStringDecoder : AbstractPolylineDecoder { + protected override ReadOnlyMemory GetReadOnlyMemory(in string polyline) => polyline.AsMemory(); + protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) => (latitude, longitude); + } - [TestMethod] - public void Decode_Null_Polyline_Throws_ArgumentNullException() { - // Arrange - var decoder = new TestStringDecoder(); + private sealed class TestStringDecoderWithOptions : AbstractPolylineDecoder { + public TestStringDecoderWithOptions(PolylineEncodingOptions options) + : base(options) { } - // Act & Assert - var ex = Assert.ThrowsExactly(() => decoder.Decode((string?)null!).ToList()); - Assert.AreEqual("polyline", ex.ParamName); - } + protected override ReadOnlyMemory GetReadOnlyMemory(in string polyline) => polyline.AsMemory(); + protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) => (latitude, longitude); + } - [TestMethod] - public void Decode_Empty_Polyline_Throws_InvalidPolylineException() { - // Arrange - var decoder = new TestStringDecoder(); + /// + /// Tests that Decode with a null polyline throws . + /// + [TestMethod] + public void Decode_With_Null_Polyline_Throws_ArgumentNullException() { + // Arrange + TestStringDecoder decoder = new(); - // Act & Assert - empty string is too short (min block length = 1) - Assert.ThrowsExactly(() => decoder.Decode(string.Empty).ToList()); - } + // Act & Assert + ArgumentNullException ex = Assert.ThrowsExactly(() => decoder.Decode((string?)null!).ToList()); + Assert.AreEqual("polyline", ex.ParamName); + } + + /// + /// Tests that Decode with an empty polyline throws . + /// + [TestMethod] + public void Decode_With_Empty_Polyline_Throws_InvalidPolylineException() { + // Arrange + TestStringDecoder decoder = new(); + + // Act & Assert + Assert.ThrowsExactly(() => decoder.Decode(string.Empty).ToList()); + } + + /// + /// Tests that Decode with a polyline containing an invalid character throws . + /// + [TestMethod] + public void Decode_With_Invalid_Character_Polyline_Throws_InvalidPolylineException() { + // Arrange + TestStringDecoder decoder = new(); + + // '!' (33) is below allowed range ('?' == 63) + // Act & Assert + Assert.ThrowsExactly(() => decoder.Decode("!").ToList()); + } - [TestMethod] - public void Decode_Invalid_Character_Polyline_Throws_InvalidPolylineException() { - // Arrange - var decoder = new TestStringDecoder(); + /// + /// Tests that Decode with a valid polyline returns the expected coordinates. + /// + [TestMethod] + public void Decode_ValidPolyline_ReturnsExpectedCoordinates() { + // Arrange + TestStringDecoder decoder = new(); + string polyline = StaticValueProvider.Valid.GetPolyline(); + (double Latitude, double Longitude)[] expected = [.. StaticValueProvider.Valid.GetCoordinates()]; - // '!' (33) is below allowed range ('?' == 63) and should trigger invalid polyline character - Assert.ThrowsExactly(() => decoder.Decode("!").ToList()); + // Act + (double Latitude, double Longitude)[] result = [.. decoder.Decode(polyline)]; + + // Assert + Assert.AreEqual(expected.Length, result.Length); + for (int i = 0; i < expected.Length; i++) { + Assert.AreEqual(expected[i].Latitude, result[i].Latitude, 1e-5); + Assert.AreEqual(expected[i].Longitude, result[i].Longitude, 1e-5); } } -} \ No newline at end of file + + /// + /// Tests that the options constructor with null throws . + /// + [TestMethod] + public void Constructor_WithNullOptions_ThrowsArgumentNullException() { + // Act & Assert + ArgumentNullException ex = Assert.ThrowsExactly(() => new TestStringDecoderWithOptions(null!)); + Assert.AreEqual("options", ex.ParamName); + } + + /// + /// Tests that the Options property returns the configured options. + /// + [TestMethod] + public void Options_Default_ReturnsDefaultOptions() { + // Arrange + TestStringDecoder decoder = new(); + + // Assert + Assert.IsNotNull(decoder.Options); + Assert.AreEqual(5u, decoder.Options.Precision); + } + + /// + /// Tests that the options constructor stores the provided options. + /// + [TestMethod] + public void Constructor_WithOptions_StoresOptions() { + // Arrange + PolylineEncodingOptions options = PolylineEncodingOptionsBuilder.Create() + .WithPrecision(7) + .Build(); + + // Act + TestStringDecoderWithOptions decoder = new(options); + + // Assert + Assert.AreSame(options, decoder.Options); + } + + /// + /// Tests that Decode with a pre-cancelled token throws . + /// + [TestMethod] + public void Decode_PreCancelledToken_ThrowsOperationCanceledException() { + // Arrange + TestStringDecoder decoder = new(); + string polyline = StaticValueProvider.Valid.GetPolyline(); + using CancellationTokenSource cts = new(); + cts.Cancel(); + + // Act & Assert + Assert.ThrowsExactly(() => decoder.Decode(polyline, cts.Token).ToList()); + } +} diff --git a/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineEncoderTests.cs b/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineEncoderTests.cs new file mode 100644 index 00000000..9a79fa0e --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineEncoderTests.cs @@ -0,0 +1,152 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm.Tests.Abstraction; + +using PolylineAlgorithm.Abstraction; +using PolylineAlgorithm.Utility; +using System; + +/// +/// Tests for . +/// +[TestClass] +public sealed class AbstractPolylineEncoderTests { + private sealed class TestStringEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> { + public TestStringEncoder() + : base() { } + + public TestStringEncoder(PolylineEncodingOptions options) + : base(options) { } + + protected override string CreatePolyline(ReadOnlyMemory polyline) => polyline.ToString(); + protected override double GetLatitude((double Latitude, double Longitude) current) => current.Latitude; + protected override double GetLongitude((double Latitude, double Longitude) current) => current.Longitude; + } + + /// + /// Tests that the default constructor creates an instance with default options. + /// + [TestMethod] + public void Constructor_Default_CreatesInstanceWithDefaultOptions() { + // Act + TestStringEncoder encoder = new(); + + // Assert + Assert.IsNotNull(encoder); + Assert.IsNotNull(encoder.Options); + Assert.AreEqual(5u, encoder.Options.Precision); + Assert.AreEqual(512, encoder.Options.StackAllocLimit); + } + + /// + /// Tests that the options constructor with null throws . + /// + [TestMethod] + public void Constructor_WithNullOptions_ThrowsArgumentNullException() { + // Act & Assert + ArgumentNullException ex = Assert.ThrowsExactly(() => new TestStringEncoder(null!)); + Assert.AreEqual("options", ex.ParamName); + } + + /// + /// Tests that the options constructor stores the provided options. + /// + [TestMethod] + public void Constructor_WithOptions_StoresOptions() { + // Arrange + PolylineEncodingOptions options = PolylineEncodingOptionsBuilder.Create() + .WithPrecision(7) + .Build(); + + // Act + TestStringEncoder encoder = new(options); + + // Assert + Assert.AreSame(options, encoder.Options); + } + + /// + /// Tests that Encode with an empty span throws . + /// + [TestMethod] + public void Encode_EmptySpan_ThrowsArgumentException() { + // Arrange + TestStringEncoder encoder = new(); + + // Act & Assert + Assert.ThrowsExactly(() => encoder.Encode(ReadOnlySpan<(double, double)>.Empty)); + } + + /// + /// Tests that Encode with a single valid coordinate returns a non-empty string. + /// + [TestMethod] + public void Encode_SingleCoordinate_ReturnsNonEmptyString() { + // Arrange + TestStringEncoder encoder = new(); + (double, double)[] coordinates = [(0.0, 0.0)]; + + // Act + string result = encoder.Encode(coordinates.AsSpan()); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Length > 0); + } + + /// + /// Tests that Encode with known coordinates returns the expected polyline string. + /// + [TestMethod] + public void Encode_KnownCoordinates_ReturnsExpectedPolyline() { + // Arrange + TestStringEncoder encoder = new(); + (double Latitude, double Longitude)[] coordinates = [.. StaticValueProvider.Valid.GetCoordinates()]; + string expected = StaticValueProvider.Valid.GetPolyline(); + + // Act + string result = encoder.Encode(coordinates.AsSpan()); + + // Assert + Assert.AreEqual(expected, result); + } + + /// + /// Tests that Encode with a pre-cancelled token throws . + /// + [TestMethod] + public void Encode_PreCancelledToken_ThrowsOperationCanceledException() { + // Arrange + TestStringEncoder encoder = new(); + using CancellationTokenSource cts = new(); + cts.Cancel(); + (double, double)[] coordinates = [(0.0, 0.0), (1.0, 1.0)]; + + // Act & Assert + Assert.ThrowsExactly(() => encoder.Encode(coordinates.AsSpan(), cts.Token)); + } + + /// + /// Tests that Encode still produces the correct result when the buffer exceeds the stack allocation + /// limit, forcing heap allocation via . + /// + [TestMethod] + public void Encode_WithSmallStackAllocLimit_UsesHeapAllocationAndProducesCorrectResult() { + // Arrange — force heap path by making stackAllocLimit smaller than any real encoding needs + PolylineEncodingOptions options = PolylineEncodingOptionsBuilder.Create() + .WithStackAllocLimit(1) + .Build(); + TestStringEncoder encoder = new(options); + (double Latitude, double Longitude)[] coordinates = [.. StaticValueProvider.Valid.GetCoordinates()]; + string expected = StaticValueProvider.Valid.GetPolyline(); + + // Act + string result = encoder.Encode(coordinates.AsSpan()); + + // Assert + Assert.AreEqual(expected, result); + } +} diff --git a/tests/PolylineAlgorithm.Tests/Extensions/PolylineEncoderExtensionsTests.cs b/tests/PolylineAlgorithm.Tests/Extensions/PolylineEncoderExtensionsTests.cs new file mode 100644 index 00000000..f1439b75 --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/Extensions/PolylineEncoderExtensionsTests.cs @@ -0,0 +1,123 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm.Tests.Extensions; + +using PolylineAlgorithm.Abstraction; +using PolylineAlgorithm.Extensions; +using PolylineAlgorithm.Utility; +using System; +using System.Collections.Generic; + +/// +/// Tests for . +/// +[TestClass] +public sealed class PolylineEncoderExtensionsTests { + private sealed class TestStringEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> { + protected override string CreatePolyline(ReadOnlyMemory polyline) => polyline.ToString(); + protected override double GetLatitude((double Latitude, double Longitude) current) => current.Latitude; + protected override double GetLongitude((double Latitude, double Longitude) current) => current.Longitude; + } + + // ----- Encode(List) ----- + + /// + /// Tests that Encode with a null encoder throws . + /// + [TestMethod] + public void Encode_List_NullEncoder_ThrowsArgumentNullException() { + // Arrange — use interface type so the extension method is resolved + IPolylineEncoder<(double, double), string>? encoder = null; + List<(double, double)> coordinates = [(0.0, 0.0)]; + + // Act & Assert + ArgumentNullException ex = Assert.ThrowsExactly( + () => encoder!.Encode(coordinates)); + Assert.AreEqual("encoder", ex.ParamName); + } + + /// + /// Tests that Encode with a null list throws . + /// + [TestMethod] + public void Encode_List_NullCoordinates_ThrowsArgumentNullException() { + // Arrange + TestStringEncoder encoder = new(); + List<(double, double)>? coordinates = null; + + // Act & Assert + ArgumentNullException ex = Assert.ThrowsExactly( + () => encoder.Encode(coordinates!)); + Assert.AreEqual("coordinates", ex.ParamName); + } + + /// + /// Tests that Encode with a valid list returns the expected polyline. + /// + [TestMethod] + public void Encode_List_ValidCoordinates_ReturnsExpectedPolyline() { + // Arrange + TestStringEncoder encoder = new(); + List<(double Latitude, double Longitude)> coordinates = [.. StaticValueProvider.Valid.GetCoordinates()]; + string expected = StaticValueProvider.Valid.GetPolyline(); + + // Act + string result = encoder.Encode(coordinates); + + // Assert + Assert.AreEqual(expected, result); + } + + // ----- Encode(T[]) ----- + + /// + /// Tests that Encode with a null encoder throws . + /// + [TestMethod] + public void Encode_Array_NullEncoder_ThrowsArgumentNullException() { + // Arrange — call the extension method explicitly because IPolylineEncoder.Encode(ReadOnlySpan) + // would be preferred over the extension when calling through method syntax with an array argument. + IPolylineEncoder<(double, double), string>? encoder = null; + (double, double)[] coordinates = [(0.0, 0.0)]; + + // Act & Assert + ArgumentNullException ex = Assert.ThrowsExactly( + () => PolylineEncoderExtensions.Encode<(double, double), string>(encoder!, coordinates)); + Assert.AreEqual("encoder", ex.ParamName); + } + + /// + /// Tests that Encode with a null array throws . + /// + [TestMethod] + public void Encode_Array_NullCoordinates_ThrowsArgumentNullException() { + // Arrange — call the extension method explicitly (same reasoning as above). + IPolylineEncoder<(double, double), string> encoder = new TestStringEncoder(); + (double, double)[]? coordinates = null; + + // Act & Assert + ArgumentNullException ex = Assert.ThrowsExactly( + () => PolylineEncoderExtensions.Encode<(double, double), string>(encoder, coordinates!)); + Assert.AreEqual("coordinates", ex.ParamName); + } + + /// + /// Tests that Encode with a valid array returns the expected polyline. + /// + [TestMethod] + public void Encode_Array_ValidCoordinates_ReturnsExpectedPolyline() { + // Arrange + TestStringEncoder encoder = new(); + (double Latitude, double Longitude)[] coordinates = [.. StaticValueProvider.Valid.GetCoordinates()]; + string expected = StaticValueProvider.Valid.GetPolyline(); + + // Act + string result = encoder.Encode(coordinates); + + // Assert + Assert.AreEqual(expected, result); + } +} diff --git a/tests/PolylineAlgorithm.Tests/Internal/CoordinateDeltaTests.cs b/tests/PolylineAlgorithm.Tests/Internal/CoordinateDeltaTests.cs index c4fa5100..1e915fe2 100644 --- a/tests/PolylineAlgorithm.Tests/Internal/CoordinateDeltaTests.cs +++ b/tests/PolylineAlgorithm.Tests/Internal/CoordinateDeltaTests.cs @@ -6,6 +6,7 @@ namespace PolylineAlgorithm.Tests.Internal; using PolylineAlgorithm.Internal; +using System.Globalization; /// /// Tests for . @@ -13,10 +14,9 @@ namespace PolylineAlgorithm.Tests.Internal; [TestClass] public sealed class CoordinateDeltaTests { /// - /// Tests that default constructor initializes delta values to zero. + /// Tests that the default constructor initializes delta values to zero. /// [TestMethod] - public void Constructor_Default_Initializes_Latitude_And_Longitude_To_Zero() { // Act CoordinateDelta delta = new(); @@ -27,167 +27,54 @@ public void Constructor_Default_Initializes_Latitude_And_Longitude_To_Zero() { } /// - /// Tests that Next with positive values calculates correct delta from zero. - /// - [TestMethod] - - public void Next_With_Positive_Values_Calculates_Delta_From_Zero() { - // Arrange - CoordinateDelta delta = new(); - - // Act - delta.Next(10, 20); - - // Assert - Assert.AreEqual(10, delta.Latitude); - Assert.AreEqual(20, delta.Longitude); - } - - /// - /// Tests that Next with negative values calculates correct delta from zero. + /// Tests that a single call to Next computes the correct delta from the initial zero state. /// [TestMethod] - - public void Next_With_Negative_Values_Calculates_Delta_From_Zero() { + [DataRow(10, 20, 10, 20)] + [DataRow(-50, -100, -50, -100)] + [DataRow(0, 0, 0, 0)] + [DataRow(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue)] + [DataRow(int.MinValue, int.MinValue, int.MinValue, int.MinValue)] + public void Next_Single_Call_From_Zero_Computes_Expected_Delta(int latitude, int longitude, int expectedLatitude, int expectedLongitude) { // Arrange CoordinateDelta delta = new(); // Act - delta.Next(-50, -100); + delta.Next(latitude, longitude); // Assert - Assert.AreEqual(-50, delta.Latitude); - Assert.AreEqual(-100, delta.Longitude); + Assert.AreEqual(expectedLatitude, delta.Latitude); + Assert.AreEqual(expectedLongitude, delta.Longitude); } /// - /// Tests that Next with zero values keeps delta at zero. + /// Tests that two consecutive calls to Next compute the delta relative to the previous value. /// [TestMethod] - - public void Next_With_Zero_Values_Keeps_Delta_At_Zero() { - // Arrange - CoordinateDelta delta = new(); - - // Act - delta.Next(0, 0); - - // Assert - Assert.AreEqual(0, delta.Latitude); - Assert.AreEqual(0, delta.Longitude); - } - - /// - /// Tests that Next called multiple times calculates delta from previous value. - /// - [TestMethod] - - public void Next_Called_Multiple_Times_Calculates_Delta_From_Previous_Value() { - // Arrange - CoordinateDelta delta = new(); - - // Act - delta.Next(10, 20); - delta.Next(15, 30); - - // Assert - Assert.AreEqual(5, delta.Latitude); - Assert.AreEqual(10, delta.Longitude); - } - - /// - /// Tests that Next with decreasing values calculates negative delta. - /// - [TestMethod] - - public void Next_With_Decreasing_Values_Calculates_Negative_Delta() { - // Arrange - CoordinateDelta delta = new(); - - // Act - delta.Next(100, 200); - delta.Next(50, 150); - - // Assert - Assert.AreEqual(-50, delta.Latitude); - Assert.AreEqual(-50, delta.Longitude); - } - - /// - /// Tests that Next with same values as previous calculates zero delta. - /// - [TestMethod] - - public void Next_With_Same_Values_As_Previous_Calculates_Zero_Delta() { - // Arrange - CoordinateDelta delta = new(); - - // Act - delta.Next(42, 84); - delta.Next(42, 84); - - // Assert - Assert.AreEqual(0, delta.Latitude); - Assert.AreEqual(0, delta.Longitude); - } - - /// - /// Tests that Next with maximum integer values calculates correct delta. - /// - [TestMethod] - - public void Next_With_Maximum_Integer_Values_Calculates_Correct_Delta() { + [DataRow(10, 20, 15, 30, 5, 10)] + [DataRow(100, 200, 50, 150, -50, -50)] + [DataRow(42, 84, 42, 84, 0, 0)] + [DataRow(-50, 100, 25, -75, 75, -175)] + public void Next_Sequential_Calls_Compute_Delta_From_Previous_Value( + int firstLatitude, int firstLongitude, + int secondLatitude, int secondLongitude, + int expectedLatitude, int expectedLongitude) { // Arrange CoordinateDelta delta = new(); + delta.Next(firstLatitude, firstLongitude); // Act - delta.Next(int.MaxValue, int.MaxValue); + delta.Next(secondLatitude, secondLongitude); // Assert - Assert.AreEqual(int.MaxValue, delta.Latitude); - Assert.AreEqual(int.MaxValue, delta.Longitude); + Assert.AreEqual(expectedLatitude, delta.Latitude); + Assert.AreEqual(expectedLongitude, delta.Longitude); } /// - /// Tests that Next with minimum integer values calculates correct delta. + /// Tests that ToString on a default instance returns a string containing expected structural keywords and a zero value. /// [TestMethod] - - public void Next_With_Minimum_Integer_Values_Calculates_Correct_Delta() { - // Arrange - CoordinateDelta delta = new(); - - // Act - delta.Next(int.MinValue, int.MinValue); - - // Assert - Assert.AreEqual(int.MinValue, delta.Latitude); - Assert.AreEqual(int.MinValue, delta.Longitude); - } - - /// - /// Tests that Next with mixed positive and negative values calculates correct delta. - /// - [TestMethod] - - public void Next_With_Mixed_Positive_And_Negative_Values_Calculates_Correct_Delta() { - // Arrange - CoordinateDelta delta = new(); - - // Act - delta.Next(-50, 100); - delta.Next(25, -75); - - // Assert - Assert.AreEqual(75, delta.Latitude); - Assert.AreEqual(-175, delta.Longitude); - } - - /// - /// Tests that ToString with default constructor returns formatted string with zeros. - /// - [TestMethod] - public void ToString_With_Default_Constructor_Returns_Formatted_String_With_Zeros() { // Arrange CoordinateDelta delta = new(); @@ -205,29 +92,31 @@ public void ToString_With_Default_Constructor_Returns_Formatted_String_With_Zero } /// - /// Tests that ToString after Next returns formatted string with correct values. + /// Tests that ToString reflects the delta values computed by Next. /// [TestMethod] - - public void ToString_After_Next_Returns_Formatted_String_With_Correct_Values() { + [DataRow(42, 84)] + [DataRow(-100, -200)] + [DataRow(int.MaxValue, int.MaxValue)] + [DataRow(int.MinValue, int.MinValue)] + public void ToString_After_Next_Contains_Expected_Values(int latitude, int longitude) { // Arrange CoordinateDelta delta = new(); - delta.Next(42, 84); + delta.Next(latitude, longitude); // Act string result = delta.ToString(); // Assert Assert.IsNotNull(result); - Assert.IsTrue(result.Contains("42", StringComparison.Ordinal)); - Assert.IsTrue(result.Contains("84", StringComparison.Ordinal)); + Assert.IsTrue(result.Contains(latitude.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal)); + Assert.IsTrue(result.Contains(longitude.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal)); } /// - /// Tests that ToString after multiple Next calls returns formatted string with latest values. + /// Tests that ToString after multiple Next calls reflects the most recent delta values. /// [TestMethod] - public void ToString_After_Multiple_Next_Calls_Returns_Formatted_String_With_Latest_Values() { // Arrange CoordinateDelta delta = new(); @@ -244,58 +133,4 @@ public void ToString_After_Multiple_Next_Calls_Returns_Formatted_String_With_Lat Assert.IsTrue(result.Contains("20", StringComparison.Ordinal)); } - /// - /// Tests that ToString with negative values returns formatted string with negative signs. - /// - [TestMethod] - - public void ToString_With_Negative_Values_Returns_Formatted_String_With_Negative_Signs() { - // Arrange - CoordinateDelta delta = new(); - delta.Next(-100, -200); - - // Act - string result = delta.ToString(); - - // Assert - Assert.IsNotNull(result); - Assert.IsTrue(result.Contains("-100", StringComparison.Ordinal)); - Assert.IsTrue(result.Contains("-200", StringComparison.Ordinal)); - } - - /// - /// Tests that ToString with maximum integer values returns formatted string. - /// - [TestMethod] - - public void ToString_With_Maximum_Integer_Values_Returns_Formatted_String() { - // Arrange - CoordinateDelta delta = new(); - delta.Next(int.MaxValue, int.MaxValue); - - // Act - string result = delta.ToString(); - - // Assert - Assert.IsNotNull(result); - Assert.IsTrue(result.Contains(int.MaxValue.ToString(System.Globalization.CultureInfo.InvariantCulture), StringComparison.Ordinal)); - } - - /// - /// Tests that ToString with minimum integer values returns formatted string. - /// - [TestMethod] - - public void ToString_With_Minimum_Integer_Values_Returns_Formatted_String() { - // Arrange - CoordinateDelta delta = new(); - delta.Next(int.MinValue, int.MinValue); - - // Act - string result = delta.ToString(); - - // Assert - Assert.IsNotNull(result); - Assert.IsTrue(result.Contains(int.MinValue.ToString(System.Globalization.CultureInfo.InvariantCulture), StringComparison.Ordinal)); - } -} \ No newline at end of file +} diff --git a/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/ExceptionGuardTests.cs b/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/ExceptionGuardTests.cs index 9e955cca..ae62566e 100644 --- a/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/ExceptionGuardTests.cs +++ b/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/ExceptionGuardTests.cs @@ -17,7 +17,6 @@ public sealed class ExceptionGuardTests { /// Tests that ThrowNotFiniteNumber throws ArgumentOutOfRangeException with correct parameter name. /// [TestMethod] - public void ThrowNotFiniteNumber_WithParamName_ThrowsArgumentOutOfRangeException() { // Arrange const string paramName = "value"; @@ -32,7 +31,6 @@ public void ThrowNotFiniteNumber_WithParamName_ThrowsArgumentOutOfRangeException /// Tests that ThrowArgumentNull throws ArgumentNullException with correct parameter name. /// [TestMethod] - public void ThrowArgumentNull_WithParamName_ThrowsArgumentNullException() { // Arrange const string paramName = "input"; @@ -46,7 +44,6 @@ public void ThrowArgumentNull_WithParamName_ThrowsArgumentNullException() { /// Tests that ThrowBufferOverflow throws OverflowException with correct message. /// [TestMethod] - public void ThrowBufferOverflow_WithMessage_ThrowsOverflowException() { // Arrange const string message = "Buffer overflow occurred."; @@ -60,7 +57,6 @@ public void ThrowBufferOverflow_WithMessage_ThrowsOverflowException() { /// Tests that ThrowCoordinateValueOutOfRange throws ArgumentOutOfRangeException with correct parameter name. /// [TestMethod] - public void ThrowCoordinateValueOutOfRange_WithParameters_ThrowsArgumentOutOfRangeException() { // Arrange const double value = 100.0; @@ -78,7 +74,6 @@ public void ThrowCoordinateValueOutOfRange_WithParameters_ThrowsArgumentOutOfRan /// Tests that StackAllocLimitMustBeEqualOrGreaterThan throws ArgumentOutOfRangeException with correct parameter name. /// [TestMethod] - public void StackAllocLimitMustBeEqualOrGreaterThan_WithParameters_ThrowsArgumentOutOfRangeException() { // Arrange const int minValue = 10; @@ -94,7 +89,6 @@ public void StackAllocLimitMustBeEqualOrGreaterThan_WithParameters_ThrowsArgumen /// Tests that ThrwoArgumentCannotBeEmptyEnumerationMessage throws ArgumentException with correct parameter name. /// [TestMethod] - public void ThrwoArgumentCannotBeEmptyEnumerationMessage_WithParamName_ThrowsArgumentException() { // Arrange const string paramName = "collection"; @@ -109,7 +103,6 @@ public void ThrwoArgumentCannotBeEmptyEnumerationMessage_WithParamName_ThrowsArg /// Tests that ThrowCouldNotWriteEncodedValueToBuffer throws InvalidOperationException with correct message. /// [TestMethod] - public void ThrowCouldNotWriteEncodedValueToBuffer_ThrowsInvalidOperationException() { // Act & Assert var ex = Assert.ThrowsExactly(() => ExceptionGuard.ThrowCouldNotWriteEncodedValueToBuffer()); @@ -120,7 +113,6 @@ public void ThrowCouldNotWriteEncodedValueToBuffer_ThrowsInvalidOperationExcepti /// Tests that ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength throws ArgumentException with correct parameter name. /// [TestMethod] - public void ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength_WithParameters_ThrowsArgumentException() { // Arrange const int destinationLength = 5; @@ -137,7 +129,6 @@ public void ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength_Wi /// Tests that ThrowInvalidPolylineLength throws InvalidPolylineException with correct message. /// [TestMethod] - public void ThrowInvalidPolylineLength_WithParameters_ThrowsInvalidPolylineException() { // Arrange const int length = 5; @@ -152,7 +143,6 @@ public void ThrowInvalidPolylineLength_WithParameters_ThrowsInvalidPolylineExcep /// Tests that ThrowInvalidPolylineCharacter throws InvalidPolylineException with correct message. /// [TestMethod] - public void ThrowInvalidPolylineCharacter_WithParameters_ThrowsInvalidPolylineException() { // Arrange const char character = '!'; @@ -167,7 +157,6 @@ public void ThrowInvalidPolylineCharacter_WithParameters_ThrowsInvalidPolylineEx /// Tests that ThrowPolylineBlockTooLong throws InvalidPolylineException with correct message. /// [TestMethod] - public void ThrowPolylineBlockTooLong_WithPosition_ThrowsInvalidPolylineException() { // Arrange const int position = 42; @@ -181,7 +170,6 @@ public void ThrowPolylineBlockTooLong_WithPosition_ThrowsInvalidPolylineExceptio /// Tests that ThrowInvalidPolylineFormat throws InvalidPolylineException with correct message. /// [TestMethod] - public void ThrowInvalidPolylineFormat_WithPosition_ThrowsInvalidPolylineException() { // Arrange const long position = 100L; @@ -195,7 +183,6 @@ public void ThrowInvalidPolylineFormat_WithPosition_ThrowsInvalidPolylineExcepti /// Tests that ThrowInvalidPolylineBlockTerminator throws InvalidPolylineException with correct message. /// [TestMethod] - public void ThrowInvalidPolylineBlockTerminator_ThrowsInvalidPolylineException() { // Act & Assert var ex = Assert.ThrowsExactly(() => ExceptionGuard.ThrowInvalidPolylineBlockTerminator()); @@ -206,7 +193,6 @@ public void ThrowInvalidPolylineBlockTerminator_ThrowsInvalidPolylineException() /// Tests that FormatStackAllocLimitMustBeEqualOrGreaterThan returns formatted message with specified value. /// [TestMethod] - public void FormatStackAllocLimitMustBeEqualOrGreaterThan_WithMinValue_ReturnsFormattedMessage() { // Arrange const int minValue = 10; @@ -223,7 +209,6 @@ public void FormatStackAllocLimitMustBeEqualOrGreaterThan_WithMinValue_ReturnsFo /// Tests that FormatPolylineCannotBeShorterThan returns formatted message with specified values. /// [TestMethod] - public void FormatPolylineCannotBeShorterThan_WithLengthAndMinLength_ReturnsFormattedMessage() { // Arrange const int length = 5; @@ -242,7 +227,6 @@ public void FormatPolylineCannotBeShorterThan_WithLengthAndMinLength_ReturnsForm /// Tests that FormatMalformedPolyline returns formatted message with position. /// [TestMethod] - public void FormatMalformedPolyline_WithPosition_ReturnsFormattedMessage() { // Arrange const long position = 42L; @@ -259,7 +243,6 @@ public void FormatMalformedPolyline_WithPosition_ReturnsFormattedMessage() { /// Tests that FormatMalformedPolyline with zero position returns formatted message. /// [TestMethod] - public void FormatMalformedPolyline_WithZeroPosition_ReturnsFormattedMessage() { // Arrange const long position = 0L; @@ -276,7 +259,6 @@ public void FormatMalformedPolyline_WithZeroPosition_ReturnsFormattedMessage() { /// Tests that FormatMalformedPolyline with negative position returns formatted message. /// [TestMethod] - public void FormatMalformedPolyline_WithNegativePosition_ReturnsFormattedMessage() { // Arrange const long position = -10L; @@ -293,7 +275,6 @@ public void FormatMalformedPolyline_WithNegativePosition_ReturnsFormattedMessage /// Tests that FormatMalformedPolyline with large position returns formatted message. /// [TestMethod] - public void FormatMalformedPolyline_WithLargePosition_ReturnsFormattedMessage() { // Arrange const long position = long.MaxValue; @@ -310,7 +291,6 @@ public void FormatMalformedPolyline_WithLargePosition_ReturnsFormattedMessage() /// Tests that FormatCoordinateValueMustBeBetween returns formatted message with all parameters. /// [TestMethod] - public void FormatCoordinateValueMustBeBetween_WithParameters_ReturnsFormattedMessage() { // Arrange const string name = "latitude"; @@ -331,7 +311,6 @@ public void FormatCoordinateValueMustBeBetween_WithParameters_ReturnsFormattedMe /// Tests that FormatCoordinateValueMustBeBetween with positive values returns formatted message. /// [TestMethod] - public void FormatCoordinateValueMustBeBetween_WithPositiveValues_ReturnsFormattedMessage() { // Arrange const string name = "longitude"; @@ -352,7 +331,6 @@ public void FormatCoordinateValueMustBeBetween_WithPositiveValues_ReturnsFormatt /// Tests that FormatCoordinateValueMustBeBetween with fractional values returns formatted message. /// [TestMethod] - public void FormatCoordinateValueMustBeBetween_WithFractionalValues_ReturnsFormattedMessage() { // Arrange const string name = "value"; @@ -371,7 +349,6 @@ public void FormatCoordinateValueMustBeBetween_WithFractionalValues_ReturnsForma /// Tests that FormatPolylineBlockTooLong returns formatted message with position. /// [TestMethod] - public void FormatPolylineBlockTooLong_WithPosition_ReturnsFormattedMessage() { // Arrange const int position = 15; @@ -388,7 +365,6 @@ public void FormatPolylineBlockTooLong_WithPosition_ReturnsFormattedMessage() { /// Tests that FormatPolylineBlockTooLong with zero position returns formatted message. /// [TestMethod] - public void FormatPolylineBlockTooLong_WithZeroPosition_ReturnsFormattedMessage() { // Arrange const int position = 0; @@ -405,7 +381,6 @@ public void FormatPolylineBlockTooLong_WithZeroPosition_ReturnsFormattedMessage( /// Tests that FormatPolylineBlockTooLong with large position returns formatted message. /// [TestMethod] - public void FormatPolylineBlockTooLong_WithLargePosition_ReturnsFormattedMessage() { // Arrange const int position = int.MaxValue; @@ -422,7 +397,6 @@ public void FormatPolylineBlockTooLong_WithLargePosition_ReturnsFormattedMessage /// Tests that FormatInvalidPolylineCharacter returns formatted message with character and position. /// [TestMethod] - public void FormatInvalidPolylineCharacter_WithCharacterAndPosition_ReturnsFormattedMessage() { // Arrange const char character = '!'; @@ -441,7 +415,6 @@ public void FormatInvalidPolylineCharacter_WithCharacterAndPosition_ReturnsForma /// Tests that FormatInvalidPolylineCharacter with letter character returns formatted message. /// [TestMethod] - public void FormatInvalidPolylineCharacter_WithLetterCharacter_ReturnsFormattedMessage() { // Arrange const char character = 'Z'; @@ -460,7 +433,6 @@ public void FormatInvalidPolylineCharacter_WithLetterCharacter_ReturnsFormattedM /// Tests that FormatInvalidPolylineCharacter with special character returns formatted message. /// [TestMethod] - public void FormatInvalidPolylineCharacter_WithSpecialCharacter_ReturnsFormattedMessage() { // Arrange const char character = '@'; @@ -479,7 +451,6 @@ public void FormatInvalidPolylineCharacter_WithSpecialCharacter_ReturnsFormatted /// Tests that FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength returns formatted message. /// [TestMethod] - public void FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength_WithLengths_ReturnsFormattedMessage() { // Arrange const int destinationLength = 5; @@ -498,7 +469,6 @@ public void FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength_W /// Tests that FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength with zero destination length returns formatted message. /// [TestMethod] - public void FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength_WithZeroDestinationLength_ReturnsFormattedMessage() { // Arrange const int destinationLength = 0; @@ -517,7 +487,6 @@ public void FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength_W /// Tests that FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength with large values returns formatted message. /// [TestMethod] - public void FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength_WithLargeValues_ReturnsFormattedMessage() { // Arrange const int destinationLength = 1000; @@ -536,7 +505,6 @@ public void FormatDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength_W /// Tests that FormatInvalidPolylineLength returns formatted message with length and min values. /// [TestMethod] - public void FormatInvalidPolylineLength_WithLengthAndMin_ReturnsFormattedMessage() { // Arrange const int length = 5; @@ -555,7 +523,6 @@ public void FormatInvalidPolylineLength_WithLengthAndMin_ReturnsFormattedMessage /// Tests that FormatInvalidPolylineLength with zero length returns formatted message. /// [TestMethod] - public void FormatInvalidPolylineLength_WithZeroLength_ReturnsFormattedMessage() { // Arrange const int length = 0; @@ -574,7 +541,6 @@ public void FormatInvalidPolylineLength_WithZeroLength_ReturnsFormattedMessage() /// Tests that FormatInvalidPolylineLength with negative values returns formatted message. /// [TestMethod] - public void FormatInvalidPolylineLength_WithNegativeValues_ReturnsFormattedMessage() { // Arrange const int length = -5; @@ -593,7 +559,6 @@ public void FormatInvalidPolylineLength_WithNegativeValues_ReturnsFormattedMessa /// Tests that GetArgumentValueMustBeFiniteNumber returns non-null message. /// [TestMethod] - public void GetArgumentValueMustBeFiniteNumber_ReturnsNonNullMessage() { // Act string result = ExceptionGuard.ExceptionMessage.GetArgumentValueMustBeFiniteNumber(); @@ -607,7 +572,6 @@ public void GetArgumentValueMustBeFiniteNumber_ReturnsNonNullMessage() { /// Tests that GetCouldNotWriteEncodedValueToTheBuffer returns non-null message. /// [TestMethod] - public void GetCouldNotWriteEncodedValueToTheBuffer_ReturnsNonNullMessage() { // Act string result = ExceptionGuard.ExceptionMessage.GetCouldNotWriteEncodedValueToTheBuffer(); @@ -621,7 +585,6 @@ public void GetCouldNotWriteEncodedValueToTheBuffer_ReturnsNonNullMessage() { /// Tests that GetArgumentCannotBeEmpty returns non-null message. /// [TestMethod] - public void GetArgumentCannotBeEmpty_ReturnsNonNullMessage() { // Act string result = ExceptionGuard.ExceptionMessage.GetArgumentCannotBeEmpty(); @@ -635,7 +598,6 @@ public void GetArgumentCannotBeEmpty_ReturnsNonNullMessage() { /// Tests that GetInvalidPolylineBlockTerminator returns non-null message. /// [TestMethod] - public void GetInvalidPolylineBlockTerminator_ReturnsNonNullMessage() { // Act string result = ExceptionGuard.ExceptionMessage.GetInvalidPolylineBlockTerminator(); diff --git a/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogDebugExtensionsTests.cs b/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogDebugExtensionsTests.cs index 9fdbbcd3..14cc2bd4 100644 --- a/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogDebugExtensionsTests.cs +++ b/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogDebugExtensionsTests.cs @@ -34,8 +34,10 @@ public void Dispose() { } } } + /// + /// Tests that LogOperationStartedDebug WithOperationName LogsStartedMessage. + /// [TestMethod] - public void LogOperationStartedDebug_WithOperationName_LogsStartedMessage() { var logger = new TestLogger(); const string operationName = "TestOperation"; @@ -47,8 +49,10 @@ public void LogOperationStartedDebug_WithOperationName_LogsStartedMessage() { Assert.Contains($"Operation {operationName} has started.", logger.Logs[0].Message, StringComparison.Ordinal); } + /// + /// Tests that LogOperationFailedDebug WithOperationName LogsFailedMessage. + /// [TestMethod] - public void LogOperationFailedDebug_WithOperationName_LogsFailedMessage() { var logger = new TestLogger(); const string operationName = "TestOperation"; @@ -60,8 +64,10 @@ public void LogOperationFailedDebug_WithOperationName_LogsFailedMessage() { Assert.Contains($"Operation {operationName} has failed.", logger.Logs[0].Message, StringComparison.Ordinal); } + /// + /// Tests that LogOperationFinishedDebug WithOperationName LogsFinishedMessage. + /// [TestMethod] - public void LogOperationFinishedDebug_WithOperationName_LogsFinishedMessage() { var logger = new TestLogger(); const string operationName = "TestOperation"; @@ -73,8 +79,10 @@ public void LogOperationFinishedDebug_WithOperationName_LogsFinishedMessage() { Assert.Contains($"Operation {operationName} has finished.", logger.Logs[0].Message, StringComparison.Ordinal); } + /// + /// Tests that LogDecodedCoordinateDebug WithCoordinatesAndPosition LogsDecodedCoordinateMessage. + /// [TestMethod] - public void LogDecodedCoordinateDebug_WithCoordinatesAndPosition_LogsDecodedCoordinateMessage() { var logger = new TestLogger(); const double latitude = 38.5; @@ -88,8 +96,10 @@ public void LogDecodedCoordinateDebug_WithCoordinatesAndPosition_LogsDecodedCoor Assert.Contains(string.Create(CultureInfo.InvariantCulture, $"Decoded coordinate: (Latitude: {latitude}, Longitude: {longitude}) at position {position}."), logger.Logs[0].Message, StringComparison.Ordinal); } + /// + /// Tests that LogOperationStartedDebug WithNullOperationName LogsMessage. + /// [TestMethod] - public void LogOperationStartedDebug_WithNullOperationName_LogsMessage() { var logger = new TestLogger(); const string? operationName = null; @@ -101,8 +111,10 @@ public void LogOperationStartedDebug_WithNullOperationName_LogsMessage() { Assert.Contains("Operation", logger.Logs[0].Message, StringComparison.Ordinal); } + /// + /// Tests that LogOperationFailedDebug WithNullOperationName LogsMessage. + /// [TestMethod] - public void LogOperationFailedDebug_WithNullOperationName_LogsMessage() { var logger = new TestLogger(); const string? operationName = null; @@ -114,8 +126,10 @@ public void LogOperationFailedDebug_WithNullOperationName_LogsMessage() { Assert.Contains("Operation", logger.Logs[0].Message, StringComparison.Ordinal); } + /// + /// Tests that LogOperationFinishedDebug WithNullOperationName LogsMessage. + /// [TestMethod] - public void LogOperationFinishedDebug_WithNullOperationName_LogsMessage() { var logger = new TestLogger(); const string? operationName = null; @@ -127,8 +141,10 @@ public void LogOperationFinishedDebug_WithNullOperationName_LogsMessage() { Assert.Contains("Operation", logger.Logs[0].Message, StringComparison.Ordinal); } + /// + /// Tests that LogDecodedCoordinateDebug WithZeroCoordinates LogsMessage. + /// [TestMethod] - public void LogDecodedCoordinateDebug_WithZeroCoordinates_LogsMessage() { var logger = new TestLogger(); const double latitude = 0.0; @@ -142,8 +158,10 @@ public void LogDecodedCoordinateDebug_WithZeroCoordinates_LogsMessage() { Assert.Contains("Decoded coordinate", logger.Logs[0].Message, StringComparison.Ordinal); } + /// + /// Tests that LogDecodedCoordinateDebug WithNegativeCoordinates LogsMessage. + /// [TestMethod] - public void LogDecodedCoordinateDebug_WithNegativeCoordinates_LogsMessage() { var logger = new TestLogger(); const double latitude = -90.0; @@ -157,8 +175,10 @@ public void LogDecodedCoordinateDebug_WithNegativeCoordinates_LogsMessage() { Assert.Contains(string.Create(CultureInfo.InvariantCulture, $"Latitude: {latitude}, Longitude: {longitude}"), logger.Logs[0].Message, StringComparison.Ordinal); } + /// + /// Tests that LogOperationStartedDebug WithEmptyOperationName LogsMessage. + /// [TestMethod] - public void LogOperationStartedDebug_WithEmptyOperationName_LogsMessage() { var logger = new TestLogger(); string operationName = string.Empty; @@ -170,8 +190,10 @@ public void LogOperationStartedDebug_WithEmptyOperationName_LogsMessage() { Assert.Contains("Operation", logger.Logs[0].Message, StringComparison.Ordinal); } + /// + /// Tests that LogOperationFailedDebug WithEmptyOperationName LogsMessage. + /// [TestMethod] - public void LogOperationFailedDebug_WithEmptyOperationName_LogsMessage() { var logger = new TestLogger(); string operationName = string.Empty; @@ -183,8 +205,10 @@ public void LogOperationFailedDebug_WithEmptyOperationName_LogsMessage() { Assert.Contains("Operation", logger.Logs[0].Message, StringComparison.Ordinal); } + /// + /// Tests that LogOperationFinishedDebug WithEmptyOperationName LogsMessage. + /// [TestMethod] - public void LogOperationFinishedDebug_WithEmptyOperationName_LogsMessage() { var logger = new TestLogger(); string operationName = string.Empty; diff --git a/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogWarningExtensionsTests.cs b/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogWarningExtensionsTests.cs index a2d31ff8..a4dc4912 100644 --- a/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogWarningExtensionsTests.cs +++ b/tests/PolylineAlgorithm.Tests/Internal/Diagnostics/LogWarningExtensionsTests.cs @@ -32,6 +32,9 @@ public void Dispose() { } } } + /// + /// Tests that LogNullArgumentWarning LogsExpectedMessage. + /// [TestMethod] public void LogNullArgumentWarning_LogsExpectedMessage() { var logger = new TestLogger(); @@ -39,6 +42,9 @@ public void LogNullArgumentWarning_LogsExpectedMessage() { Assert.IsTrue(logger.Logs.Exists(l => l.Message.Contains("Argument foo is null.", StringComparison.Ordinal))); } + /// + /// Tests that LogEmptyArgumentWarning LogsExpectedMessage. + /// [TestMethod] public void LogEmptyArgumentWarning_LogsExpectedMessage() { var logger = new TestLogger(); @@ -46,6 +52,9 @@ public void LogEmptyArgumentWarning_LogsExpectedMessage() { Assert.IsTrue(logger.Logs.Exists(l => l.Message.Contains("Argument bar is empty.", StringComparison.Ordinal))); } + /// + /// Tests that LogInternalBufferOverflowWarning LogsExpectedMessage. + /// [TestMethod] public void LogInternalBufferOverflowWarning_LogsExpectedMessage() { var logger = new TestLogger(); @@ -53,6 +62,9 @@ public void LogInternalBufferOverflowWarning_LogsExpectedMessage() { Assert.IsTrue(logger.Logs.Exists(l => l.Message.Contains("Internal buffer has size of 2. At position 1 is required additional 3 space.", StringComparison.Ordinal))); } + /// + /// Tests that LogCannotWriteValueToBufferWarning LogsExpectedMessage. + /// [TestMethod] public void LogCannotWriteValueToBufferWarning_LogsExpectedMessage() { var logger = new TestLogger(); @@ -60,6 +72,9 @@ public void LogCannotWriteValueToBufferWarning_LogsExpectedMessage() { Assert.IsTrue(logger.Logs.Exists(l => l.Message.Contains("Cannot write to internal buffer at position 4. Current coordinate is at index 5.", StringComparison.Ordinal))); } + /// + /// Tests that LogPolylineCannotBeShorterThanWarning LogsExpectedMessage. + /// [TestMethod] public void LogPolylineCannotBeShorterThanWarning_LogsExpectedMessage() { var logger = new TestLogger(); @@ -67,6 +82,9 @@ public void LogPolylineCannotBeShorterThanWarning_LogsExpectedMessage() { Assert.IsTrue(logger.Logs.Exists(l => l.Message.Contains("Polyline is too short. Minimal length is 7. Actual length is 6.", StringComparison.Ordinal))); } + /// + /// Tests that LogRequestedBufferSizeExceedsMaxBufferLengthWarning LogsExpectedMessage. + /// [TestMethod] public void LogRequestedBufferSizeExceedsMaxBufferLengthWarning_LogsExpectedMessage() { var logger = new TestLogger(); @@ -74,6 +92,9 @@ public void LogRequestedBufferSizeExceedsMaxBufferLengthWarning_LogsExpectedMess Assert.IsTrue(logger.Logs.Exists(l => l.Message.Contains("Requested buffer size of 8 exceeds maximum allowed buffer length of 9.", StringComparison.Ordinal))); } + /// + /// Tests that LogInvalidPolylineWarning LogsExpectedMessage. + /// [TestMethod] public void LogInvalidPolylineWarning_LogsExpectedMessage() { var logger = new TestLogger(); @@ -81,6 +102,9 @@ public void LogInvalidPolylineWarning_LogsExpectedMessage() { Assert.IsTrue(logger.Logs.Exists(l => l.Message.Contains("Polyline is invalid or malformed at position 10.", StringComparison.Ordinal))); } + /// + /// Tests that LogInvalidPolylineFormatWarning LogsExpectedMessage. + /// [TestMethod] [SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "No need to be strict in tests.")] public void LogInvalidPolylineFormatWarning_LogsExpectedMessage() { diff --git a/tests/PolylineAlgorithm.Tests/Internal/Pow10Tests.cs b/tests/PolylineAlgorithm.Tests/Internal/Pow10Tests.cs index 58d4344e..ba3fc860 100644 --- a/tests/PolylineAlgorithm.Tests/Internal/Pow10Tests.cs +++ b/tests/PolylineAlgorithm.Tests/Internal/Pow10Tests.cs @@ -13,162 +13,37 @@ namespace PolylineAlgorithm.Tests.Internal; [TestClass] public sealed class Pow10Tests { /// - /// Tests that GetFactor with precision 0 returns 1. + /// Tests that GetFactor returns the correct power-of-ten factor for the given precision. /// [TestMethod] - - public void GetFactor_With_Precision_Zero_Returns_One() { + [DataRow(0u, 1u)] + [DataRow(1u, 10u)] + [DataRow(2u, 100u)] + [DataRow(3u, 1000u)] + [DataRow(4u, 10000u)] + [DataRow(5u, 100000u)] + [DataRow(6u, 1000000u)] + [DataRow(7u, 10000000u)] + [DataRow(8u, 100000000u)] + [DataRow(9u, 1000000000u)] + public void GetFactor_With_Valid_Precision_Returns_Expected_Value(uint precision, uint expected) { // Act - uint result = Pow10.GetFactor(0); + uint result = Pow10.GetFactor(precision); // Assert - Assert.AreEqual(1u, result); + Assert.AreEqual(expected, result); } /// - /// Tests that GetFactor with precision 1 returns 10. + /// Tests that GetFactor throws when precision causes overflow. /// [TestMethod] - - public void GetFactor_With_Precision_One_Returns_Ten() { - // Act - uint result = Pow10.GetFactor(1); - - // Assert - Assert.AreEqual(10u, result); - } - - /// - /// Tests that GetFactor with precision 2 returns 100. - /// - [TestMethod] - - public void GetFactor_With_Precision_Two_Returns_One_Hundred() { - // Act - uint result = Pow10.GetFactor(2); - - // Assert - Assert.AreEqual(100u, result); - } - - /// - /// Tests that GetFactor with precision 3 returns 1000. - /// - [TestMethod] - - public void GetFactor_With_Precision_Three_Returns_One_Thousand() { - // Act - uint result = Pow10.GetFactor(3); - - // Assert - Assert.AreEqual(1000u, result); - } - - /// - /// Tests that GetFactor with precision 4 returns 10000. - /// - [TestMethod] - - public void GetFactor_With_Precision_Four_Returns_Ten_Thousand() { - // Act - uint result = Pow10.GetFactor(4); - - // Assert - Assert.AreEqual(10000u, result); - } - - /// - /// Tests that GetFactor with precision 5 returns 100000. - /// - [TestMethod] - - public void GetFactor_With_Precision_Five_Returns_One_Hundred_Thousand() { - // Act - uint result = Pow10.GetFactor(5); - - // Assert - Assert.AreEqual(100000u, result); - } - - /// - /// Tests that GetFactor with precision 6 returns 1000000. - /// - [TestMethod] - - public void GetFactor_With_Precision_Six_Returns_One_Million() { - // Act - uint result = Pow10.GetFactor(6); - - // Assert - Assert.AreEqual(1000000u, result); - } - - /// - /// Tests that GetFactor with precision 7 returns 10000000. - /// - [TestMethod] - - public void GetFactor_With_Precision_Seven_Returns_Ten_Million() { - // Act - uint result = Pow10.GetFactor(7); - - // Assert - Assert.AreEqual(10000000u, result); - } - - /// - /// Tests that GetFactor with precision 8 returns 100000000. - /// - [TestMethod] - - public void GetFactor_With_Precision_Eight_Returns_One_Hundred_Million() { - // Act - uint result = Pow10.GetFactor(8); - - // Assert - Assert.AreEqual(100000000u, result); - } - - /// - /// Tests that GetFactor with precision 9 returns 1000000000. - /// - [TestMethod] - - public void GetFactor_With_Precision_Nine_Returns_One_Billion() { - // Act - uint result = Pow10.GetFactor(9); - - // Assert - Assert.AreEqual(1000000000u, result); - } - - /// - /// Tests that GetFactor with precision causing overflow throws OverflowException. - /// - [TestMethod] - - public void GetFactor_With_Precision_Causing_Overflow_Throws_OverflowException() { - // Act & Assert - Assert.ThrowsExactly(() => Pow10.GetFactor(15)); - } - - /// - /// Tests that GetFactor with large precision causing overflow throws OverflowException. - /// - [TestMethod] - - public void GetFactor_With_Large_Precision_Causing_Overflow_Throws_OverflowException() { - // Act & Assert - Assert.ThrowsExactly(() => Pow10.GetFactor(20)); - } - - /// - /// Tests that GetFactor with maximum uint precision throws OverflowException. - /// - [TestMethod] - - public void GetFactor_With_Maximum_Uint_Precision_Throws_OverflowException() { + [DataRow(10u)] + [DataRow(15u)] + [DataRow(20u)] + [DataRow(uint.MaxValue)] + public void GetFactor_With_Overflowing_Precision_Throws_OverflowException(uint precision) { // Act & Assert - Assert.ThrowsExactly(() => Pow10.GetFactor(uint.MaxValue)); + Assert.ThrowsExactly(() => Pow10.GetFactor(precision)); } -} \ No newline at end of file +} diff --git a/tests/PolylineAlgorithm.Tests/InvalidPolylineExceptionTests.cs b/tests/PolylineAlgorithm.Tests/InvalidPolylineExceptionTests.cs new file mode 100644 index 00000000..903d85ee --- /dev/null +++ b/tests/PolylineAlgorithm.Tests/InvalidPolylineExceptionTests.cs @@ -0,0 +1,44 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm.Tests; + +using System; + +/// +/// Tests for . +/// +[TestClass] +public sealed class InvalidPolylineExceptionTests { + /// + /// Tests that the default constructor creates an instance with a null message. + /// + [TestMethod] + public void Constructor_Default_CreatesInstance() { + // Act + InvalidPolylineException ex = new(); + + // Assert + Assert.IsNotNull(ex); + Assert.IsNull(ex.InnerException); + } + + /// + /// Tests that the message and inner exception constructor stores both values. + /// + [TestMethod] + public void Constructor_WithMessageAndInnerException_StoresBoth() { + // Arrange + const string message = "polyline is malformed"; + Exception inner = new InvalidOperationException("inner"); + + // Act + InvalidPolylineException ex = new(message, inner); + + // Assert + Assert.AreEqual(message, ex.Message); + Assert.AreSame(inner, ex.InnerException); + } +} diff --git a/tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj b/tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj index 72fdded9..ff88dcb7 100644 --- a/tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj +++ b/tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj @@ -18,7 +18,6 @@ - diff --git a/tests/PolylineAlgorithm.Tests/PolylineEncodingTests.cs b/tests/PolylineAlgorithm.Tests/PolylineEncodingTests.cs index 192fb456..08b8bd0f 100644 --- a/tests/PolylineAlgorithm.Tests/PolylineEncodingTests.cs +++ b/tests/PolylineAlgorithm.Tests/PolylineEncodingTests.cs @@ -18,998 +18,526 @@ public sealed class PolylineEncodingTests { /// Tests that Normalize returns zero when value is zero. /// [TestMethod] - public void Normalize_ZeroValue_ReturnsZero() { - // Arrange - const double value = 0.0; - const uint precision = 5; - // Act - int result = PolylineEncoding.Normalize(value, precision); + int result = PolylineEncoding.Normalize(0.0); // Assert Assert.AreEqual(0, result); } /// - /// Tests that Normalize throws when value is NaN. + /// Tests that Normalize throws when value is not finite. /// [TestMethod] - - public void Normalize_NaNValue_ThrowsArgumentOutOfRangeException() { - // Arrange - const double value = double.NaN; - const uint precision = 5; - + [DataRow(double.NaN)] + [DataRow(double.PositiveInfinity)] + [DataRow(double.NegativeInfinity)] + public void Normalize_With_NonFinite_Value_Throws_ArgumentOutOfRangeException(double value) { // Act & Assert - Assert.ThrowsExactly(() => PolylineEncoding.Normalize(value, precision)); + Assert.ThrowsExactly(() => PolylineEncoding.Normalize(value, 5)); } /// - /// Tests that Normalize throws when value is positive infinity. + /// Tests that Normalize returns the expected normalized integer for the given value and precision. /// [TestMethod] + [DataRow(37.78903, 0u, 37)] + [DataRow(-122.4123, 0u, -122)] + [DataRow(37.78903, 5u, 3778903)] + [DataRow(-122.4123, 5u, -12241230)] + [DataRow(37.78903, 1u, 377)] + [DataRow(37.789034, 6u, 37789034)] + [DataRow(37.789999, 5u, 3778999)] + [DataRow(0.00001, 5u, 1)] + [DataRow(-0.00001, 5u, -1)] + public void Normalize_With_Value_And_Precision_Returns_Expected_Normalized_Value(double value, uint precision, int expected) { + // Act + int result = PolylineEncoding.Normalize(value, precision); - public void Normalize_PositiveInfinity_ThrowsArgumentOutOfRangeException() { - // Arrange - const double value = double.PositiveInfinity; - const uint precision = 5; - - // Act & Assert - Assert.ThrowsExactly(() => PolylineEncoding.Normalize(value, precision)); + // Assert + Assert.AreEqual(expected, result); } - /// - /// Tests that Normalize throws when value is negative infinity. - /// - [TestMethod] - - public void Normalize_NegativeInfinity_ThrowsArgumentOutOfRangeException() { - // Arrange - const double value = double.NegativeInfinity; - const uint precision = 5; + #endregion - // Act & Assert - Assert.ThrowsExactly(() => PolylineEncoding.Normalize(value, precision)); - } + #region Denormalize Tests /// - /// Tests that Normalize with zero precision returns truncated value. + /// Tests that Denormalize returns zero when value is zero. /// [TestMethod] - - public void Normalize_ZeroPrecision_ReturnsTruncatedValue() { - // Arrange - const double value = 37.78903; - const uint precision = 0; - + public void Denormalize_ZeroValue_ReturnsZero() { // Act - int result = PolylineEncoding.Normalize(value, precision); + double result = PolylineEncoding.Denormalize(0); // Assert - Assert.AreEqual(37, result); + Assert.AreEqual(0.0, result); } /// - /// Tests that Normalize with zero precision truncates negative values correctly. + /// Tests that Denormalize returns the expected floating-point value for the given integer and precision. /// [TestMethod] - - public void Normalize_ZeroPrecisionNegative_ReturnsTruncatedValue() { - // Arrange - const double value = -122.4123; - const uint precision = 0; - + [DataRow(37, 0u, 37.0)] + [DataRow(-122, 0u, -122.0)] + [DataRow(3778903, 5u, 37.78903)] + [DataRow(-12241230, 5u, -122.4123)] + [DataRow(377, 1u, 37.7)] + [DataRow(37789034, 6u, 37.789034)] + [DataRow(1, 5u, 0.00001)] + [DataRow(-1, 5u, -0.00001)] + public void Denormalize_With_Value_And_Precision_Returns_Expected_Denormalized_Value(int value, uint precision, double expected) { // Act - int result = PolylineEncoding.Normalize(value, precision); + double result = PolylineEncoding.Denormalize(value, precision); // Assert - Assert.AreEqual(-122, result); + Assert.AreEqual(expected, result, 1e-7); } + #endregion + + #region TryReadValue Tests + /// - /// Tests that Normalize with default precision normalizes positive value correctly. + /// Tests that TryReadValue returns false when position is at buffer length. /// [TestMethod] - - public void Normalize_DefaultPrecisionPositive_ReturnsNormalizedValue() { + public void TryReadValue_PositionAtBufferLength_ReturnsFalse() { // Arrange - const double value = 37.78903; - const uint precision = 5; + ReadOnlyMemory buffer = "_p~iF~ps|U".AsMemory(); + int delta = 0; + int position = buffer.Length; // Act - int result = PolylineEncoding.Normalize(value, precision); + bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); // Assert - Assert.AreEqual(3778903, result); + Assert.IsFalse(result); + Assert.AreEqual(0, delta); } /// - /// Tests that Normalize with default precision normalizes negative value correctly. + /// Tests that TryReadValue returns false when position exceeds buffer length. /// [TestMethod] - - public void Normalize_DefaultPrecisionNegative_ReturnsNormalizedValue() { + public void TryReadValue_PositionExceedsBufferLength_ReturnsFalse() { // Arrange - const double value = -122.4123; - const uint precision = 5; + ReadOnlyMemory buffer = "_p~iF~ps|U".AsMemory(); + int delta = 0; + int position = buffer.Length + 1; // Act - int result = PolylineEncoding.Normalize(value, precision); + bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); // Assert - Assert.AreEqual(-12241230, result); + Assert.IsFalse(result); + Assert.AreEqual(0, delta); } /// - /// Tests that Normalize with precision 1 works correctly. + /// Tests that TryReadValue reads a positive single-character encoded value. /// [TestMethod] - - public void Normalize_Precision1_ReturnsNormalizedValue() { + public void TryReadValue_PositiveSingleChar_ReadsValueAndReturnsTrue() { // Arrange - const double value = 37.78903; - const uint precision = 1; + // Encode value 5: zigzag = 10 = 0x0A; char = 10 + 63 = 73 = 'I' + ReadOnlyMemory buffer = "I".AsMemory(); // Single char encoding of value 5 + int delta = 0; + int position = 0; // Act - int result = PolylineEncoding.Normalize(value, precision); + bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); // Assert - Assert.AreEqual(377, result); + Assert.IsTrue(result); + Assert.AreEqual(5, delta); + Assert.AreEqual(1, position); } /// - /// Tests that Normalize with precision 6 works correctly. + /// Tests that TryReadValue reads a positive multi-character encoded value. /// [TestMethod] - - public void Normalize_Precision6_ReturnsNormalizedValue() { + public void TryReadValue_PositiveMultiChar_ReadsValueAndReturnsTrue() { // Arrange - const double value = 37.789034; - const uint precision = 6; + // _p~iF encodes latitude 38.5 (normalized = 3850000, zigzag = 7700000) + ReadOnlyMemory buffer = "_p~iF".AsMemory(); + int delta = 0; + int position = 0; // Act - int result = PolylineEncoding.Normalize(value, precision); + bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); // Assert - Assert.AreEqual(37789034, result); + Assert.IsTrue(result); + Assert.AreEqual(3850000, delta); + Assert.AreEqual(5, position); } /// - /// Tests that Normalize truncates fractional parts. + /// Tests that TryReadValue reads a negative encoded value. /// [TestMethod] - - public void Normalize_ValueWithFractionalPart_TruncatesFractionalPart() { + public void TryReadValue_NegativeValue_ReadsValueAndReturnsTrue() { // Arrange - const double value = 37.789999; - const uint precision = 5; + // ~ps|U encodes longitude -120.2 (normalized = -12020000, zigzag encodes negative) + ReadOnlyMemory buffer = "~ps|U".AsMemory(); + int delta = 0; + int position = 0; // Act - int result = PolylineEncoding.Normalize(value, precision); + bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); // Assert - Assert.AreEqual(3778999, result); + Assert.IsTrue(result); + Assert.AreEqual(-12020000, delta); + Assert.AreEqual(5, position); } /// - /// Tests that Normalize handles very small values. + /// Tests that TryReadValue accumulates into existing delta. /// [TestMethod] - - public void Normalize_VerySmallValue_ReturnsNormalizedValue() { + public void TryReadValue_WithExistingDelta_AccumulatesDelta() { // Arrange - const double value = 0.00001; - const uint precision = 5; + ReadOnlyMemory buffer = "I".AsMemory(); // encodes 5 + int delta = 10; // existing delta + int position = 0; // Act - int result = PolylineEncoding.Normalize(value, precision); + bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); // Assert - Assert.AreEqual(1, result); + Assert.IsTrue(result); + Assert.AreEqual(15, delta); // 10 + 5 = 15 } /// - /// Tests that Normalize handles negative very small values. + /// Tests that TryReadValue reads multiple values sequentially from the buffer. /// [TestMethod] + public void TryReadValue_MultipleValues_ReadsSequentially() { + // Arrange - "_p~iF~ps|U" encodes lat 38.5 then delta lon -120.2 + ReadOnlyMemory buffer = "_p~iF~ps|U".AsMemory(); + int delta = 0; + int position = 0; - public void Normalize_NegativeVerySmallValue_ReturnsNormalizedValue() { - // Arrange - const double value = -0.00001; - const uint precision = 5; + // Act - read first value + bool first = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); + int firstDelta = delta; - // Act - int result = PolylineEncoding.Normalize(value, precision); + // read second value + bool second = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); // Assert - Assert.AreEqual(-1, result); + Assert.IsTrue(first); + Assert.IsTrue(second); + Assert.AreEqual(3850000, firstDelta); + Assert.AreEqual(10, position); // consumed all 10 chars } - #endregion - - #region Denormalize Tests - /// - /// Tests that Denormalize returns zero when value is zero. + /// Tests that TryReadValue returns false when buffer ends mid-value. /// [TestMethod] - - public void Denormalize_ZeroValue_ReturnsZero() { - // Arrange - const int value = 0; - const uint precision = 5; + public void TryReadValue_BufferEndsMidValue_ReturnsFalse() { + // Arrange - truncate a multi-char encoding + ReadOnlyMemory buffer = "_p~".AsMemory(); // incomplete multi-char encoding + int delta = 0; + int position = 0; // Act - double result = PolylineEncoding.Denormalize(value, precision); + bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); // Assert - Assert.AreEqual(0.0, result); + Assert.IsFalse(result); } /// - /// Tests that Denormalize with zero precision returns same value as double. + /// Tests that TryReadValue correctly reads from a non-zero starting position. /// [TestMethod] - - public void Denormalize_ZeroPrecision_ReturnsSameValue() { - // Arrange - const int value = 37; - const uint precision = 0; + public void TryReadValue_StartingFromMiddle_ReadsCorrectly() { + // Arrange - "_p~iF~ps|U": start at position 5 to read the longitude value + ReadOnlyMemory buffer = "_p~iF~ps|U".AsMemory(); + int delta = 0; + int position = 5; // Act - double result = PolylineEncoding.Denormalize(value, precision); + bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); // Assert - Assert.AreEqual(37.0, result); + Assert.IsTrue(result); + Assert.AreEqual(-12020000, delta); + Assert.AreEqual(10, position); } + #endregion + + #region TryWriteValue Tests + /// - /// Tests that Denormalize with zero precision handles negative values. + /// Tests that TryWriteValue returns false when the buffer is too small. /// [TestMethod] - - public void Denormalize_ZeroPrecisionNegative_ReturnsSameValue() { + public void TryWriteValue_BufferTooSmall_ReturnsFalse() { // Arrange - const int value = -122; - const uint precision = 0; + Span buffer = []; + int position = 0; // Act - double result = PolylineEncoding.Denormalize(value, precision); + bool result = PolylineEncoding.TryWriteValue(3850000, buffer, ref position); // Assert - Assert.AreEqual(-122.0, result); + Assert.IsFalse(result); + Assert.AreEqual(0, position); } /// - /// Tests that Denormalize with default precision denormalizes positive value correctly. + /// Tests that TryWriteValue returns false when the remaining buffer is too small. /// [TestMethod] - - public void Denormalize_DefaultPrecisionPositive_ReturnsDenormalizedValue() { - // Arrange - const int value = 3778903; - const uint precision = 5; + public void TryWriteValue_RemainingBufferTooSmall_ReturnsFalse() { + // Arrange - need 5 chars for 3850000, but only 3 remain + Span buffer = new char[3]; + int position = 0; // Act - double result = PolylineEncoding.Denormalize(value, precision); + bool result = PolylineEncoding.TryWriteValue(3850000, buffer, ref position); // Assert - Assert.AreEqual(37.78903, result, 0.0000001); + Assert.IsFalse(result); + Assert.AreEqual(0, position); } /// - /// Tests that Denormalize with default precision denormalizes negative value correctly. + /// Tests that TryWriteValue correctly encodes zero. /// [TestMethod] - - public void Denormalize_DefaultPrecisionNegative_ReturnsDenormalizedValue() { + public void TryWriteValue_ZeroValue_WritesCorrectly() { // Arrange - const int value = -12241230; - const uint precision = 5; + Span buffer = new char[10]; + int position = 0; // Act - double result = PolylineEncoding.Denormalize(value, precision); + bool result = PolylineEncoding.TryWriteValue(0, buffer, ref position); // Assert - Assert.AreEqual(-122.4123, result, 0.0000001); + Assert.IsTrue(result); + Assert.AreEqual(1, position); + Assert.AreEqual('?', buffer[0]); // 0 + 63 = '?' } /// - /// Tests that Denormalize with precision 1 works correctly. + /// Tests that TryWriteValue correctly encodes a positive value. /// [TestMethod] - - public void Denormalize_Precision1_ReturnsDenormalizedValue() { + public void TryWriteValue_PositiveValue_WritesCorrectly() { // Arrange - const int value = 377; - const uint precision = 1; + Span buffer = new char[10]; + int position = 0; // Act - double result = PolylineEncoding.Denormalize(value, precision); + bool result = PolylineEncoding.TryWriteValue(3850000, buffer, ref position); // Assert - Assert.AreEqual(37.7, result, 0.0000001); + Assert.IsTrue(result); + Assert.AreEqual(5, position); + Assert.AreEqual("_p~iF", new string(buffer[..5])); } /// - /// Tests that Denormalize with precision 6 works correctly. + /// Tests that TryWriteValue correctly encodes a negative value. /// [TestMethod] - - public void Denormalize_Precision6_ReturnsDenormalizedValue() { + public void TryWriteValue_NegativeValue_WritesCorrectly() { // Arrange - const int value = 37789034; - const uint precision = 6; + Span buffer = new char[10]; + int position = 0; // Act - double result = PolylineEncoding.Denormalize(value, precision); + bool result = PolylineEncoding.TryWriteValue(-12020000, buffer, ref position); // Assert - Assert.AreEqual(37.789034, result, 0.0000001); + Assert.IsTrue(result); + Assert.AreEqual(5, position); + Assert.AreEqual("~ps|U", new string(buffer[..5])); } /// - /// Tests that Denormalize handles very small values. + /// Tests that TryWriteValue correctly encodes multiple values sequentially. /// [TestMethod] - - public void Denormalize_VerySmallValue_ReturnsDenormalizedValue() { + public void TryWriteValue_MultipleValues_WritesSequentially() { // Arrange - const int value = 1; - const uint precision = 5; + Span buffer = new char[20]; + int position = 0; // Act - double result = PolylineEncoding.Denormalize(value, precision); + bool first = PolylineEncoding.TryWriteValue(3850000, buffer, ref position); + bool second = PolylineEncoding.TryWriteValue(-12020000, buffer, ref position); // Assert - Assert.AreEqual(0.00001, result, 0.0000001); + Assert.IsTrue(first); + Assert.IsTrue(second); + Assert.AreEqual("_p~iF~ps|U", new string(buffer[..10])); } /// - /// Tests that Denormalize handles negative very small values. + /// Tests that TryWriteValue correctly encodes a small positive value. /// [TestMethod] - - public void Denormalize_NegativeVerySmallValue_ReturnsDenormalizedValue() { + public void TryWriteValue_SmallPositiveValue_WritesCorrectly() { // Arrange - const int value = -1; - const uint precision = 5; + Span buffer = new char[10]; + int position = 0; // Act - double result = PolylineEncoding.Denormalize(value, precision); + bool result = PolylineEncoding.TryWriteValue(5, buffer, ref position); // Assert - Assert.AreEqual(-0.00001, result, 0.0000001); + Assert.IsTrue(result); + Assert.AreEqual(1, position); + Assert.AreEqual('I', buffer[0]); // zigzag(5) = 10; 10 + 63 = 73 = 'I' } - #endregion - - #region TryReadValue Tests - /// - /// Tests that TryReadValue returns false when position is at buffer length. + /// Tests that TryWriteValue correctly encodes a small negative value. /// [TestMethod] - - public void TryReadValue_PositionAtBufferLength_ReturnsFalse() { + public void TryWriteValue_SmallNegativeValue_WritesCorrectly() { // Arrange - ReadOnlyMemory buffer = "_p~iF~ps|U".AsMemory(); - int delta = 0; - int position = buffer.Length; + Span buffer = new char[10]; + int position = 0; // Act - bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); + bool result = PolylineEncoding.TryWriteValue(-5, buffer, ref position); // Assert - Assert.IsFalse(result); - Assert.AreEqual(0, delta); + Assert.IsTrue(result); + Assert.AreEqual(1, position); + Assert.AreEqual('H', buffer[0]); // zigzag(-5) = 9; 9 + 63 = 72 = 'H' } /// - /// Tests that TryReadValue returns false when position exceeds buffer length. + /// Tests that TryWriteValue writes at the correct non-zero starting position. /// [TestMethod] - - public void TryReadValue_PositionExceedsBufferLength_ReturnsFalse() { + public void TryWriteValue_NonZeroStartPosition_WritesAtCorrectPosition() { // Arrange - ReadOnlyMemory buffer = "_p~iF~ps|U".AsMemory(); - int delta = 0; - int position = buffer.Length + 1; + Span buffer = new char[20]; + int position = 5; // Act - bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); + bool result = PolylineEncoding.TryWriteValue(5, buffer, ref position); // Assert - Assert.IsFalse(result); - Assert.AreEqual(0, delta); + Assert.IsTrue(result); + Assert.AreEqual(6, position); + Assert.AreEqual('I', buffer[5]); } /// - /// Tests that TryReadValue reads positive single-character value correctly. + /// Tests that TryWriteValue correctly encodes a large positive value. /// [TestMethod] - - public void TryReadValue_PositiveSingleChar_ReadsValueAndReturnsTrue() { + public void TryWriteValue_LargePositiveValue_WritesCorrectly() { // Arrange - ReadOnlyMemory buffer = "?".AsMemory(); - int delta = 0; + Span buffer = new char[10]; int position = 0; + const int delta = 3778903; + int expectedSize = PolylineEncoding.GetRequiredBufferSize(delta); // Act - bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); + bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); // Assert Assert.IsTrue(result); - Assert.AreEqual(0, delta); - Assert.AreEqual(1, position); + Assert.AreEqual(expectedSize, position); } /// - /// Tests that TryReadValue reads multi-character positive value correctly. + /// Tests that TryWriteValue correctly encodes a large negative value. /// [TestMethod] - - public void TryReadValue_PositiveMultiChar_ReadsValueAndReturnsTrue() { + public void TryWriteValue_LargeNegativeValue_WritesCorrectly() { // Arrange - Span buffer = stackalloc char[10]; - int writePosition = 0; - const int expectedDelta = 3778903; - - // First write the value to get the correct encoding - PolylineEncoding.TryWriteValue(expectedDelta, buffer, ref writePosition); - ReadOnlyMemory readBuffer = new string(buffer[..writePosition]).AsMemory(); - - int delta = 0; + Span buffer = new char[10]; int position = 0; + const int delta = -12241230; + int expectedSize = PolylineEncoding.GetRequiredBufferSize(delta); // Act - bool result = PolylineEncoding.TryReadValue(ref delta, readBuffer, ref position); + bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); // Assert Assert.IsTrue(result); - Assert.AreEqual(expectedDelta, delta); - Assert.AreEqual(writePosition, position); + Assert.AreEqual(expectedSize, position); } + #endregion + + #region GetRequiredBufferSize Tests + /// - /// Tests that TryReadValue reads negative value correctly. + /// Tests that GetRequiredBufferSize returns the expected character count for the given delta. /// [TestMethod] - - public void TryReadValue_NegativeValue_ReadsValueAndReturnsTrue() { - // Arrange - Span buffer = stackalloc char[10]; - int writePosition = 0; - const int expectedDelta = -12241230; - - // First write the value to get the correct encoding - PolylineEncoding.TryWriteValue(expectedDelta, buffer, ref writePosition); - ReadOnlyMemory readBuffer = new string(buffer[..writePosition]).AsMemory(); - - int delta = 0; - int position = 0; - + [DataRow(0, 1)] + [DataRow(1, 1)] + [DataRow(-1, 1)] + [DataRow(15, 1)] + [DataRow(16, 2)] + [DataRow(3778903, 5)] + [DataRow(-12241230, 5)] + public void GetRequiredBufferSize_Returns_Expected_Size(int delta, int expectedSize) { // Act - bool result = PolylineEncoding.TryReadValue(ref delta, readBuffer, ref position); + int size = PolylineEncoding.GetRequiredBufferSize(delta); // Assert - Assert.IsTrue(result); - Assert.AreEqual(expectedDelta, delta); - Assert.AreEqual(writePosition, position); + Assert.AreEqual(expectedSize, size); } /// - /// Tests that TryReadValue accumulates delta correctly. + /// Tests that GetRequiredBufferSize returns a valid size for the maximum positive integer. /// [TestMethod] - - public void TryReadValue_WithExistingDelta_AccumulatesDelta() { - // Arrange - Span buffer = stackalloc char[10]; - int writePosition = 0; - const int valueDelta = 3778903; - - // First write the value to get the correct encoding - PolylineEncoding.TryWriteValue(valueDelta, buffer, ref writePosition); - ReadOnlyMemory readBuffer = new string(buffer[..writePosition]).AsMemory(); - - int delta = 100; - int position = 0; - + public void GetRequiredBufferSize_MaxInt_ReturnsCorrectSize() { // Act - bool result = PolylineEncoding.TryReadValue(ref delta, readBuffer, ref position); + int size = PolylineEncoding.GetRequiredBufferSize(int.MaxValue); // Assert - Assert.IsTrue(result); - Assert.AreEqual(3779003, delta); + Assert.IsGreaterThan(0, size); + Assert.IsLessThanOrEqualTo(7, size); // Maximum size for int32 } /// - /// Tests that TryReadValue reads multiple values from buffer. + /// Tests that GetRequiredBufferSize returns a valid size for the minimum negative integer. /// [TestMethod] - - public void TryReadValue_MultipleValues_ReadsSequentially() { - // Arrange - Span buffer = stackalloc char[20]; - int writePosition = 0; - const int expectedDelta1 = 3778903; - const int expectedDelta2 = -12241230; - - // Write both values - PolylineEncoding.TryWriteValue(expectedDelta1, buffer, ref writePosition); - PolylineEncoding.TryWriteValue(expectedDelta2, buffer, ref writePosition); - ReadOnlyMemory readBuffer = new string(buffer[..writePosition]).AsMemory(); - - int delta1 = 0; - int position = 0; - + public void GetRequiredBufferSize_MinInt_ReturnsCorrectSize() { // Act - bool result1 = PolylineEncoding.TryReadValue(ref delta1, readBuffer, ref position); - int delta2 = 0; - bool result2 = PolylineEncoding.TryReadValue(ref delta2, readBuffer, ref position); + int size = PolylineEncoding.GetRequiredBufferSize(int.MinValue); // Assert - Assert.IsTrue(result1); - Assert.AreEqual(expectedDelta1, delta1); - Assert.IsTrue(result2); - Assert.AreEqual(expectedDelta2, delta2); - Assert.AreEqual(writePosition, position); + Assert.IsGreaterThan(0, size); + Assert.IsLessThanOrEqualTo(7, size); // Maximum size for int32 } /// - /// Tests that TryReadValue returns false when buffer ends mid-value. + /// Tests that GetRequiredBufferSize is consistent with the actual bytes written by TryWriteValue. /// [TestMethod] - - public void TryReadValue_BufferEndsMidValue_ReturnsFalse() { + public void GetRequiredBufferSize_ConsistentWithTryWriteValue_MatchesActualSize() { // Arrange - Span fullBuffer = stackalloc char[10]; - int writePosition = 0; - PolylineEncoding.TryWriteValue(3778903, fullBuffer, ref writePosition); - - // Create incomplete buffer (truncate last character) - ReadOnlyMemory buffer = new string(fullBuffer[..(writePosition - 1)]).AsMemory(); - int delta = 0; + const int delta = 3778903; + int expectedSize = PolylineEncoding.GetRequiredBufferSize(delta); + Span buffer = stackalloc char[expectedSize]; int position = 0; // Act - bool result = PolylineEncoding.TryReadValue(ref delta, buffer, ref position); - - // Assert - Assert.IsFalse(result); - Assert.AreEqual(buffer.Length, position); - } - - /// - /// Tests that TryReadValue reads value from middle of buffer. - /// - [TestMethod] - - public void TryReadValue_StartingFromMiddle_ReadsCorrectly() { - // Arrange - Span buffer = stackalloc char[20]; - int writePosition = 0; - const int expectedDelta1 = 3778903; - const int expectedDelta2 = -12241230; - - // Write both values - PolylineEncoding.TryWriteValue(expectedDelta1, buffer, ref writePosition); - int secondValuePosition = writePosition; - PolylineEncoding.TryWriteValue(expectedDelta2, buffer, ref writePosition); - ReadOnlyMemory readBuffer = new string(buffer[..writePosition]).AsMemory(); - - int delta = 0; - int position = secondValuePosition; // Start from second value - - // Act - bool result = PolylineEncoding.TryReadValue(ref delta, readBuffer, ref position); - - // Assert - Assert.IsTrue(result); - Assert.AreEqual(expectedDelta2, delta); - Assert.AreEqual(writePosition, position); - } - - #endregion - - #region TryWriteValue Tests - - /// - /// Tests that TryWriteValue returns false when buffer is too small. - /// - [TestMethod] - - public void TryWriteValue_BufferTooSmall_ReturnsFalse() { - // Arrange - Span buffer = stackalloc char[2]; - const int delta = 3778903; - int position = 0; - - // Act - bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); - - // Assert - Assert.IsFalse(result); - Assert.AreEqual(0, position); - } - - /// - /// Tests that TryWriteValue returns false when remaining buffer is too small. - /// - [TestMethod] - - public void TryWriteValue_RemainingBufferTooSmall_ReturnsFalse() { - // Arrange - Span buffer = stackalloc char[10]; - const int delta = 3778903; - int position = 8; // Only 2 chars remaining, need 5 - - // Act - bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); - - // Assert - Assert.IsFalse(result); - Assert.AreEqual(8, position); - } - - /// - /// Tests that TryWriteValue writes zero correctly. - /// - [TestMethod] - - public void TryWriteValue_ZeroValue_WritesCorrectly() { - // Arrange - Span buffer = stackalloc char[10]; - const int delta = 0; - int position = 0; - - // Act - bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); - - // Assert - Assert.IsTrue(result); - Assert.AreEqual(1, position); - Assert.AreEqual('?', buffer[0]); - } - - /// - /// Tests that TryWriteValue writes positive value correctly. - /// - [TestMethod] - - public void TryWriteValue_PositiveValue_WritesCorrectly() { - // Arrange - Span buffer = stackalloc char[10]; - const int delta = 3778903; - int position = 0; - - // Act - bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); - - // Assert - Assert.IsTrue(result); - Assert.IsGreaterThan(0, position); - - // Verify by reading back - ReadOnlyMemory readBuffer = new string(buffer[..position]).AsMemory(); - int readDelta = 0; - int readPosition = 0; - bool readResult = PolylineEncoding.TryReadValue(ref readDelta, readBuffer, ref readPosition); - - Assert.IsTrue(readResult); - Assert.AreEqual(delta, readDelta); - } - - /// - /// Tests that TryWriteValue writes negative value correctly. - /// - [TestMethod] - - public void TryWriteValue_NegativeValue_WritesCorrectly() { - // Arrange - Span buffer = stackalloc char[10]; - const int delta = -12241230; - int position = 0; - - // Act - bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); - - // Assert - Assert.IsTrue(result); - Assert.IsGreaterThan(0, position); - - // Verify by reading back - ReadOnlyMemory readBuffer = new string(buffer[..position]).AsMemory(); - int readDelta = 0; - int readPosition = 0; - bool readResult = PolylineEncoding.TryReadValue(ref readDelta, readBuffer, ref readPosition); - - Assert.IsTrue(readResult); - Assert.AreEqual(delta, readDelta); - } - - /// - /// Tests that TryWriteValue writes multiple values sequentially. - /// - [TestMethod] - - public void TryWriteValue_MultipleValues_WritesSequentially() { - // Arrange - Span buffer = stackalloc char[20]; - int position = 0; - const int delta1 = 3778903; - const int delta2 = -12241230; - - // Act - bool result1 = PolylineEncoding.TryWriteValue(delta1, buffer, ref position); - int midPosition = position; - bool result2 = PolylineEncoding.TryWriteValue(delta2, buffer, ref position); - - // Assert - Assert.IsTrue(result1); - Assert.IsTrue(result2); - Assert.IsGreaterThan(midPosition, position); - - // Verify by reading back both values - ReadOnlyMemory readBuffer = new string(buffer[..position]).AsMemory(); - int readDelta1 = 0; - int readPosition = 0; - PolylineEncoding.TryReadValue(ref readDelta1, readBuffer, ref readPosition); - int readDelta2 = 0; - PolylineEncoding.TryReadValue(ref readDelta2, readBuffer, ref readPosition); - - Assert.AreEqual(delta1, readDelta1); - Assert.AreEqual(delta2, readDelta2); - } - - /// - /// Tests that TryWriteValue writes small positive value correctly. - /// - [TestMethod] - - public void TryWriteValue_SmallPositiveValue_WritesCorrectly() { - // Arrange - Span buffer = stackalloc char[10]; - const int delta = 1; - int position = 0; - - // Act - bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); - - // Assert - Assert.IsTrue(result); - Assert.AreEqual(1, position); - - // Verify by reading back - ReadOnlyMemory readBuffer = new string(buffer[..position]).AsMemory(); - int readDelta = 0; - int readPosition = 0; - PolylineEncoding.TryReadValue(ref readDelta, readBuffer, ref readPosition); - Assert.AreEqual(delta, readDelta); - } - - /// - /// Tests that TryWriteValue writes small negative value correctly. - /// - [TestMethod] - - public void TryWriteValue_SmallNegativeValue_WritesCorrectly() { - // Arrange - Span buffer = stackalloc char[10]; - const int delta = -1; - int position = 0; - - // Act - bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); - - // Assert - Assert.IsTrue(result); - Assert.AreEqual(1, position); - - // Verify by reading back - ReadOnlyMemory readBuffer = new string(buffer[..position]).AsMemory(); - int readDelta = 0; - int readPosition = 0; - PolylineEncoding.TryReadValue(ref readDelta, readBuffer, ref readPosition); - Assert.AreEqual(delta, readDelta); - } - - /// - /// Tests that TryWriteValue starts writing at specified position. - /// - [TestMethod] - - public void TryWriteValue_NonZeroStartPosition_WritesAtCorrectPosition() { - // Arrange - Span buffer = stackalloc char[10]; - buffer[0] = 'X'; - buffer[1] = 'Y'; - const int delta = 0; - int position = 2; - - // Act - bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); - - // Assert - Assert.IsTrue(result); - Assert.AreEqual(3, position); - Assert.AreEqual('X', buffer[0]); - Assert.AreEqual('Y', buffer[1]); - Assert.AreEqual('?', buffer[2]); - } - - /// - /// Tests that TryWriteValue handles large positive values. - /// - [TestMethod] - - public void TryWriteValue_LargePositiveValue_WritesCorrectly() { - // Arrange - Span buffer = stackalloc char[10]; - const int delta = int.MaxValue / 2; - int position = 0; - - // Act - bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); - - // Assert - Assert.IsTrue(result); - Assert.IsGreaterThan(0, position); - } - - /// - /// Tests that TryWriteValue handles large negative values. - /// - [TestMethod] - - public void TryWriteValue_LargeNegativeValue_WritesCorrectly() { - // Arrange - Span buffer = stackalloc char[10]; - const int delta = int.MinValue / 2; - int position = 0; - - // Act - bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); - - // Assert - Assert.IsTrue(result); - Assert.IsGreaterThan(0, position); - } - - #endregion - - #region GetRequiredBufferSize Tests - - /// - /// Tests that GetRequiredBufferSize returns 1 for zero value. - /// - [TestMethod] - - public void GetRequiredBufferSize_ZeroValue_ReturnsOne() { - // Arrange - const int delta = 0; - - // Act - int size = PolylineEncoding.GetRequiredBufferSize(delta); - - // Assert - Assert.AreEqual(1, size); - } - - /// - /// Tests that GetRequiredBufferSize returns correct size for small positive value. - /// - [TestMethod] - - public void GetRequiredBufferSize_SmallPositiveValue_ReturnsOne() { - // Arrange - const int delta = 1; - - // Act - int size = PolylineEncoding.GetRequiredBufferSize(delta); - - // Assert - Assert.AreEqual(1, size); - } - - /// - /// Tests that GetRequiredBufferSize returns correct size for small negative value. - /// - [TestMethod] - - public void GetRequiredBufferSize_SmallNegativeValue_ReturnsOne() { - // Arrange - const int delta = -1; - - // Act - int size = PolylineEncoding.GetRequiredBufferSize(delta); - - // Assert - Assert.AreEqual(1, size); - } - - /// - /// Tests that GetRequiredBufferSize returns correct size for large positive value. - /// - [TestMethod] - - public void GetRequiredBufferSize_LargePositiveValue_ReturnsCorrectSize() { - // Arrange - const int delta = 3778903; - - // Act - int size = PolylineEncoding.GetRequiredBufferSize(delta); - - // Assert - Assert.AreEqual(5, size); - } - - /// - /// Tests that GetRequiredBufferSize returns correct size for large negative value. - /// - [TestMethod] - - public void GetRequiredBufferSize_LargeNegativeValue_ReturnsCorrectSize() { - // Arrange - const int delta = -12241230; - - // Act - int size = PolylineEncoding.GetRequiredBufferSize(delta); - - // Assert - Assert.AreEqual(5, size); - } - - /// - /// Tests that GetRequiredBufferSize handles maximum positive integer. - /// - [TestMethod] - - public void GetRequiredBufferSize_MaxInt_ReturnsCorrectSize() { - // Arrange - const int delta = int.MaxValue; - - // Act - int size = PolylineEncoding.GetRequiredBufferSize(delta); - - // Assert - Assert.IsGreaterThan(0, size); - Assert.IsLessThanOrEqualTo(7, size); // Maximum size for int32 - } - - /// - /// Tests that GetRequiredBufferSize handles minimum negative integer. - /// - [TestMethod] - - public void GetRequiredBufferSize_MinInt_ReturnsCorrectSize() { - // Arrange - const int delta = int.MinValue; - - // Act - int size = PolylineEncoding.GetRequiredBufferSize(delta); - - // Assert - Assert.IsGreaterThan(0, size); - Assert.IsLessThanOrEqualTo(7, size); // Maximum size for int32 - } - - /// - /// Tests that GetRequiredBufferSize returns consistent size with TryWriteValue. - /// - [TestMethod] - - public void GetRequiredBufferSize_ConsistentWithTryWriteValue_MatchesActualSize() { - // Arrange - const int delta = 3778903; - int expectedSize = PolylineEncoding.GetRequiredBufferSize(delta); - Span buffer = stackalloc char[expectedSize]; - int position = 0; - - // Act - bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); + bool result = PolylineEncoding.TryWriteValue(delta, buffer, ref position); // Assert Assert.IsTrue(result); @@ -1017,10 +545,9 @@ public void GetRequiredBufferSize_ConsistentWithTryWriteValue_MatchesActualSize( } /// - /// Tests that GetRequiredBufferSize with undersized buffer causes TryWriteValue to fail. + /// Tests that an undersized buffer causes TryWriteValue to fail. /// [TestMethod] - public void GetRequiredBufferSize_UndersizedBuffer_CausesTryWriteValueToFail() { // Arrange const int delta = 3778903; @@ -1036,38 +563,6 @@ public void GetRequiredBufferSize_UndersizedBuffer_CausesTryWriteValueToFail() { Assert.AreEqual(0, position); } - /// - /// Tests that GetRequiredBufferSize returns correct size for boundary value 15. - /// - [TestMethod] - - public void GetRequiredBufferSize_BoundaryValue15_ReturnsOne() { - // Arrange - const int delta = 15; // 15 << 1 = 30, which is less than 32 (Space) - - // Act - int size = PolylineEncoding.GetRequiredBufferSize(delta); - - // Assert - Assert.AreEqual(1, size); - } - - /// - /// Tests that GetRequiredBufferSize returns correct size for boundary value 16. - /// - [TestMethod] - - public void GetRequiredBufferSize_BoundaryValue16_ReturnsTwo() { - // Arrange - const int delta = 16; // 16 << 1 = 32, which equals Space - - // Act - int size = PolylineEncoding.GetRequiredBufferSize(delta); - - // Assert - Assert.AreEqual(2, size); - } - #endregion #region ValidateFormat Tests @@ -1076,65 +571,45 @@ public void GetRequiredBufferSize_BoundaryValue16_ReturnsTwo() { /// Tests that ValidateFormat succeeds with a valid polyline. /// [TestMethod] - public void ValidateFormat_ValidPolyline_DoesNotThrow() { - // Arrange - const string polyline = "_p~iF~ps|U_ulLnnqC_mqNvxq`@"; - // Act & Assert - PolylineEncoding.ValidateFormat(polyline); + PolylineEncoding.ValidateFormat("_p~iF~ps|U_ulLnnqC_mqNvxq`@"); } /// - /// Tests that ValidateFormat throws when polyline contains invalid character. + /// Tests that ValidateFormat throws when polyline contains an invalid character. /// [TestMethod] - public void ValidateFormat_InvalidCharacter_ThrowsInvalidPolylineException() { - // Arrange - const string polyline = "_p~iF!ps|U"; - // Act & Assert - Assert.ThrowsExactly(() => PolylineEncoding.ValidateFormat(polyline)); + Assert.ThrowsExactly(() => PolylineEncoding.ValidateFormat("_p~iF!ps|U")); } /// /// Tests that ValidateFormat throws when polyline has invalid block structure. /// [TestMethod] - public void ValidateFormat_InvalidBlockStructure_ThrowsInvalidPolylineException() { - // Arrange - const string polyline = "________"; // All continuation characters, no block terminator - // Act & Assert - Assert.ThrowsExactly(() => PolylineEncoding.ValidateFormat(polyline)); + Assert.ThrowsExactly(() => PolylineEncoding.ValidateFormat("________")); // all continuation chars, no terminator } /// - /// Tests that ValidateFormat succeeds with empty polyline ending with terminator. + /// Tests that ValidateFormat succeeds with a single terminator character. /// [TestMethod] - - public void ValidateFormat_EmptyPolylineWithTerminator_DoesNotThrow() { - // Arrange - const string polyline = "?"; // Single terminator character - + public void ValidateFormat_SingleTerminator_DoesNotThrow() { // Act & Assert - PolylineEncoding.ValidateFormat(polyline); + PolylineEncoding.ValidateFormat("?"); } /// - /// Tests that ValidateFormat throws when block is too long. + /// Tests that ValidateFormat throws when a block exceeds maximum length. /// [TestMethod] - public void ValidateFormat_BlockTooLong_ThrowsInvalidPolylineException() { - // Arrange - const string polyline = "________?"; // 8 characters in block (max is 7) - // Act & Assert - Assert.ThrowsExactly(() => PolylineEncoding.ValidateFormat(polyline)); + Assert.ThrowsExactly(() => PolylineEncoding.ValidateFormat("________?")); // 8-char block (max is 7) } #endregion @@ -1142,147 +617,30 @@ public void ValidateFormat_BlockTooLong_ThrowsInvalidPolylineException() { #region ValidateCharRange Tests /// - /// Tests that ValidateCharRange succeeds with all valid characters. - /// - [TestMethod] - - public void ValidateCharRange_AllValidCharacters_DoesNotThrow() { - // Arrange - const string polyline = "_p~iF~ps|U_ulLnnqC_mqNvxq`@"; - - // Act & Assert - PolylineEncoding.ValidateCharRange(polyline); - } - - /// - /// Tests that ValidateCharRange succeeds with minimum valid character. - /// - [TestMethod] - - public void ValidateCharRange_MinimumValidCharacter_DoesNotThrow() { - // Arrange - const string polyline = "?"; // ASCII 63 (Min) - - // Act & Assert - PolylineEncoding.ValidateCharRange(polyline); - } - - /// - /// Tests that ValidateCharRange succeeds with maximum valid character. - /// - [TestMethod] - - public void ValidateCharRange_MaximumValidCharacter_DoesNotThrow() { - // Arrange - const string polyline = "~"; // ASCII 126 (Max) - - // Act & Assert - PolylineEncoding.ValidateCharRange(polyline); - } - - /// - /// Tests that ValidateCharRange throws when character is below minimum. - /// - [TestMethod] - - public void ValidateCharRange_CharacterBelowMinimum_ThrowsInvalidPolylineException() { - // Arrange - const string polyline = ">"; // ASCII 62 (below Min of 63) - - // Act & Assert - Assert.ThrowsExactly(() => PolylineEncoding.ValidateCharRange(polyline)); - } - - /// - /// Tests that ValidateCharRange throws when character is above maximum. - /// - [TestMethod] - - public void ValidateCharRange_CharacterAboveMaximum_ThrowsInvalidPolylineException() { - // Arrange - const string polyline = "\u007F"; // ASCII 127 (above Max of 126) - - // Act & Assert - Assert.ThrowsExactly(() => PolylineEncoding.ValidateCharRange(polyline)); - } - - /// - /// Tests that ValidateCharRange throws when invalid character is in middle of valid polyline. - /// - [TestMethod] - - public void ValidateCharRange_InvalidCharacterInMiddle_ThrowsInvalidPolylineException() { - // Arrange - const string polyline = "_p~iF!ps|U"; // '!' is ASCII 33 (below Min) - - // Act & Assert - Assert.ThrowsExactly(() => PolylineEncoding.ValidateCharRange(polyline)); - } - - /// - /// Tests that ValidateCharRange succeeds with empty polyline. - /// - [TestMethod] - - public void ValidateCharRange_EmptyPolyline_DoesNotThrow() { - // Arrange - const string polyline = ""; - - // Act & Assert - PolylineEncoding.ValidateCharRange(polyline); - } - - /// - /// Tests that ValidateCharRange throws when invalid character is at end of polyline. + /// Tests that ValidateCharRange succeeds for valid polyline strings. /// [TestMethod] - - public void ValidateCharRange_InvalidCharacterAtEnd_ThrowsInvalidPolylineException() { - // Arrange - const string polyline = "_p~iF~ps|U!"; // '!' at end - - // Act & Assert - Assert.ThrowsExactly(() => PolylineEncoding.ValidateCharRange(polyline)); - } - - /// - /// Tests that ValidateCharRange succeeds with long polyline to trigger SIMD path. - /// - [TestMethod] - - public void ValidateCharRange_LongPolyline_DoesNotThrow() { - // Arrange - // Create a string long enough to trigger SIMD vectorization (typically 8-16 chars depending on platform) - const string polyline = "????????????????????????????????"; - + [DataRow("_p~iF~ps|U_ulLnnqC_mqNvxq`@")] + [DataRow("?")] // min valid char (ASCII 63) + [DataRow("~")] // max valid char (ASCII 126) + [DataRow("")] // empty is valid + [DataRow("????????????????????????????????")] // long string to trigger SIMD path + public void ValidateCharRange_With_Valid_Polyline_Does_Not_Throw(string polyline) { // Act & Assert PolylineEncoding.ValidateCharRange(polyline); } /// - /// Tests that ValidateCharRange throws when invalid character is in SIMD-processed section. - /// - [TestMethod] - - public void ValidateCharRange_InvalidCharacterInSimdSection_ThrowsInvalidPolylineException() { - // Arrange - // Create a long string with invalid character to trigger SIMD path detection - const string polyline = "????????!???????????????????????"; - - // Act & Assert - Assert.ThrowsExactly(() => PolylineEncoding.ValidateCharRange(polyline)); - } - - /// - /// Tests that ValidateCharRange throws when invalid character is in scalar remainder section. + /// Tests that ValidateCharRange throws for polylines containing invalid characters. /// [TestMethod] - - public void ValidateCharRange_InvalidCharacterInScalarRemainder_ThrowsInvalidPolylineException() { - // Arrange - // Create a string that leaves remainder after SIMD processing - const string polyline = "????????????????\u007F"; // Valid chars + one invalid at end - + [DataRow(">")] // ASCII 62 (below min of 63) + [DataRow("\u007F")] // ASCII 127 (above max of 126) + [DataRow("_p~iF!ps|U")] // '!' in middle (ASCII 33) + [DataRow("_p~iF~ps|U!")] // '!' at end + [DataRow("????????!???????????????????????")] // invalid in SIMD section + [DataRow("????????????????\u007F")] // invalid in scalar remainder + public void ValidateCharRange_With_Invalid_Character_Throws_InvalidPolylineException(string polyline) { // Act & Assert Assert.ThrowsExactly(() => PolylineEncoding.ValidateCharRange(polyline)); } @@ -1292,134 +650,32 @@ public void ValidateCharRange_InvalidCharacterInScalarRemainder_ThrowsInvalidPol #region ValidateBlockLength Tests /// - /// Tests that ValidateBlockLength succeeds with single block. - /// - [TestMethod] - - public void ValidateBlockLength_SingleBlock_DoesNotThrow() { - // Arrange - const string polyline = "?"; // Single terminator - - // Act & Assert - PolylineEncoding.ValidateBlockLength(polyline); - } - - /// - /// Tests that ValidateBlockLength succeeds with multiple blocks. - /// - [TestMethod] - - public void ValidateBlockLength_MultipleBlocks_DoesNotThrow() { - // Arrange - const string polyline = "_p~iF~ps|U"; // Multiple blocks - - // Act & Assert - PolylineEncoding.ValidateBlockLength(polyline); - } - - /// - /// Tests that ValidateBlockLength succeeds with maximum length block. - /// - [TestMethod] - - public void ValidateBlockLength_MaximumLengthBlock_DoesNotThrow() { - // Arrange - const string polyline = "______?"; // 6 continuation chars + terminator (max length is 7 total) - - // Act & Assert - PolylineEncoding.ValidateBlockLength(polyline); - } - - /// - /// Tests that ValidateBlockLength throws when block exceeds maximum length. - /// - [TestMethod] - - public void ValidateBlockLength_BlockExceedsMaximumLength_ThrowsInvalidPolylineException() { - // Arrange - const string polyline = "________?"; // 8 chars in block (exceeds max of 7) - - // Act & Assert - Assert.ThrowsExactly(() => PolylineEncoding.ValidateBlockLength(polyline)); - } - - /// - /// Tests that ValidateBlockLength throws when polyline does not end with block terminator. - /// - [TestMethod] - - public void ValidateBlockLength_NoBlockTerminator_ThrowsInvalidPolylineException() { - // Arrange - const string polyline = "________"; // All continuation characters, no terminator - - // Act & Assert - Assert.ThrowsExactly(() => PolylineEncoding.ValidateBlockLength(polyline)); - } - - /// - /// Tests that ValidateBlockLength throws when empty polyline has no block terminator. - /// - [TestMethod] - - public void ValidateBlockLength_EmptyPolyline_ThrowsInvalidPolylineException() { - // Arrange - const string polyline = ""; - - // Act & Assert - Assert.ThrowsExactly(() => PolylineEncoding.ValidateBlockLength(polyline)); - } - - /// - /// Tests that ValidateBlockLength throws when block is too long in middle of polyline. - /// - [TestMethod] - - public void ValidateBlockLength_TooLongBlockInMiddle_ThrowsInvalidPolylineException() { - // Arrange - const string polyline = "?________?"; // Valid block, then too-long block - - // Act & Assert - Assert.ThrowsExactly(() => PolylineEncoding.ValidateBlockLength(polyline)); - } - - /// - /// Tests that ValidateBlockLength succeeds with consecutive terminators. + /// Tests that ValidateBlockLength succeeds for valid block structures. /// [TestMethod] - - public void ValidateBlockLength_ConsecutiveTerminators_DoesNotThrow() { - // Arrange - const string polyline = "??"; // Two consecutive terminators (two 1-char blocks) - - // Act & Assert - PolylineEncoding.ValidateBlockLength(polyline); - } - - /// - /// Tests that ValidateBlockLength succeeds with mixed block lengths. - /// - [TestMethod] - - public void ValidateBlockLength_MixedBlockLengths_DoesNotThrow() { - // Arrange - const string polyline = "?__?_____?"; // Blocks of length 1, 2, and 5 - + [DataRow("?")] // single terminator + [DataRow("_p~iF~ps|U")] // multiple blocks + [DataRow("______?")] // 6 continuation chars + terminator (maximum block length) + [DataRow("??")] // consecutive terminators + [DataRow("?__?_____?")] // mixed block lengths + public void ValidateBlockLength_With_Valid_Polyline_Does_Not_Throw(string polyline) { // Act & Assert PolylineEncoding.ValidateBlockLength(polyline); } /// - /// Tests that ValidateBlockLength throws when second-to-last block is too long. + /// Tests that ValidateBlockLength throws for invalid block structures. /// [TestMethod] - - public void ValidateBlockLength_SecondToLastBlockTooLong_ThrowsInvalidPolylineException() { - // Arrange - const string polyline = "________?_?"; // First block is 8 chars (too long) - + [DataRow("________?")] // 8-char block (exceeds max of 7) + [DataRow("________")] // all continuation chars, no terminator + [DataRow("")] // empty polyline has no terminator + [DataRow("?________?")] // valid block then too-long block + [DataRow("________?_?")] // first block too long + public void ValidateBlockLength_With_Invalid_Polyline_Throws_InvalidPolylineException(string polyline) { // Act & Assert Assert.ThrowsExactly(() => PolylineEncoding.ValidateBlockLength(polyline)); } #endregion -} \ No newline at end of file +} diff --git a/utilities/PolylineAlgorithm.Utility/RandomValueProvider.cs b/utilities/PolylineAlgorithm.Utility/RandomValueProvider.cs index 85d14b2b..d3d83b9e 100644 --- a/utilities/PolylineAlgorithm.Utility/RandomValueProvider.cs +++ b/utilities/PolylineAlgorithm.Utility/RandomValueProvider.cs @@ -23,11 +23,11 @@ internal static class RandomValueProvider { private static readonly PolylineEncoder _encoder = new(); /// - /// Gets a collection of random instances of the specified count. + /// Gets a collection of random latitude/longitude tuples of the specified count. /// The same collection is cached and reused for the same count value. /// /// The number of coordinates to generate. - /// An enumerable collection of random objects. + /// An enumerable collection of random latitude/longitude tuples. public static IEnumerable<(double Latitude, double Longitude)> GetCoordinates(int count) { var entry = GetCaheEntry(count); @@ -39,11 +39,11 @@ internal static class RandomValueProvider { } /// - /// Gets a representing the encoded polyline for a random collection of coordinates of the specified count. + /// Gets the encoded polyline string for a random collection of coordinates of the specified count. /// The same polyline is cached and reused for the same count value. /// /// The number of coordinates to generate and encode. - /// A representing the encoded polyline. + /// A representing the encoded polyline. public static string GetPolyline(int count) { var entry = GetCaheEntry(count); diff --git a/utilities/PolylineAlgorithm.Utility/StaticValueProvider.cs b/utilities/PolylineAlgorithm.Utility/StaticValueProvider.cs index 4a07f55a..d6d2813c 100644 --- a/utilities/PolylineAlgorithm.Utility/StaticValueProvider.cs +++ b/utilities/PolylineAlgorithm.Utility/StaticValueProvider.cs @@ -18,7 +18,7 @@ internal static class Valid { private const string Polyline = "???_gsia@_cidP??~fsia@?~fsia@~bidP?~bidP??_gsia@"; /// - /// A predefined collection of instances representing a closed path around the globe. + /// A predefined collection of latitude/longitude tuples representing a closed path around the globe. /// private static readonly IEnumerable<(double Latitude, double Longitude)> _coordinates = [ new (0, 0), @@ -32,17 +32,17 @@ internal static class Valid { ]; /// - /// Gets the predefined collection of instances. + /// Gets the predefined collection of latitude/longitude tuples. /// - /// An containing the static coordinates. + /// An of latitude/longitude tuples containing the static coordinates. public static IEnumerable<(double Latitude, double Longitude)> GetCoordinates() { return _coordinates; } /// - /// Gets the predefined instance. + /// Gets the predefined encoded polyline string. /// - /// The static value. + /// The static encoded polyline string. public static string GetPolyline() { return Polyline; }