using System; using System.Globalization; namespace SystemTests; [TestClass] public class NumberExtensionsTest { /// /// Runs an assertion block under the invariant culture, so tests of /// culture-sensitive number formatting are deterministic regardless of the /// machine's locale. /// private static void WithInvariantCulture( Action action ) { var previous = CultureInfo.CurrentCulture; CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; try { action(); } finally { CultureInfo.CurrentCulture = previous; } } /// /// FormatBytes should pick the right suffix per magnitude, with the short /// format dropping the decimals. /// [TestMethod] public void FormatBytes() { WithInvariantCulture( () => { Assert.AreEqual( "500b", 500.FormatBytes() ); Assert.AreEqual( "2.00kb", 2048.FormatBytes() ); Assert.AreEqual( "2kb", 2048.FormatBytes( shortFormat: true ) ); Assert.AreEqual( "5.00mb", (5L * 1024 * 1024).FormatBytes() ); Assert.AreEqual( "3.00gb", (3L * 1024 * 1024 * 1024).FormatBytes() ); } ); } /// /// Clamp should constrain values to the inclusive range. /// [TestMethod] public void Clamp() { Assert.AreEqual( 5, 3.Clamp( 5, 10 ) ); Assert.AreEqual( 10, 30.Clamp( 5, 10 ) ); Assert.AreEqual( 7, 7.Clamp( 5, 10 ) ); Assert.AreEqual( 0.5f, 0.9f.Clamp( 0f, 0.5f ) ); } /// /// FormatSeconds should use the compact "1w2d3h4m5s" style, only showing /// the largest relevant units. /// [TestMethod] public void FormatSeconds() { Assert.AreEqual( "45s", 45L.FormatSeconds() ); Assert.AreEqual( "1m30s", 90L.FormatSeconds() ); Assert.AreEqual( "1h1m40s", 3700L.FormatSeconds() ); } /// /// FormatSecondsLong should spell out the units. /// [TestMethod] public void FormatSecondsLong() { Assert.AreEqual( "45 seconds", 45L.FormatSecondsLong() ); Assert.AreEqual( "1 minutes, 30 seconds", 90L.FormatSecondsLong() ); } /// /// FormatNumberShort should thousands-separate small numbers and switch /// to a K suffix from five digits up. /// [TestMethod] public void FormatNumberShort() { WithInvariantCulture( () => { Assert.AreEqual( "999", 999L.FormatNumberShort() ); Assert.AreEqual( "1,500", 1500L.FormatNumberShort() ); Assert.AreEqual( "15K", 15000L.FormatNumberShort() ); } ); } /// /// UnsignedMod should wrap negative values into the positive range, /// unlike the % operator. /// [TestMethod] public void UnsignedMod() { Assert.AreEqual( 2, 5.UnsignedMod( 3 ) ); Assert.AreEqual( 2, (-1).UnsignedMod( 3 ) ); Assert.AreEqual( 0, (-3).UnsignedMod( 3 ) ); } /// /// BitsSet should count set bits (population count). /// [TestMethod] public void BitsSet() { Assert.AreEqual( 0, 0.BitsSet() ); Assert.AreEqual( 1, 8.BitsSet() ); Assert.AreEqual( 8, 255.BitsSet() ); } /// /// Plural should pick the singular form only for exactly one. /// [TestMethod] public void Plural() { Assert.AreEqual( "1 apple", $"1 {1.Plural( "apple", "apples" )}" ); Assert.AreEqual( "2 apples", $"2 {2.Plural( "apple", "apples" )}" ); Assert.AreEqual( "0 apples", $"0 {0.Plural( "apple", "apples" )}" ); } /// /// IsPowerOfTwo should accept exact powers of two and reject everything else. /// Note the bit-trick implementation reports 0 as a power of two - pinned /// here so a change in that quirk is a deliberate one. /// [TestMethod] public void IsPowerOfTwo() { Assert.IsTrue( 1.IsPowerOfTwo() ); Assert.IsTrue( 64.IsPowerOfTwo() ); Assert.IsTrue( 1024.IsPowerOfTwo() ); Assert.IsTrue( 0.IsPowerOfTwo() ); Assert.IsFalse( 3.IsPowerOfTwo() ); Assert.IsFalse( 1000.IsPowerOfTwo() ); } /// /// Enum flag helpers should test, set and clear flags without boxing /// surprises. /// [TestMethod] public void EnumFlags() { var value = AttributeTargets.Class | AttributeTargets.Method; Assert.IsTrue( value.Contains( AttributeTargets.Class ) ); Assert.IsFalse( value.Contains( AttributeTargets.Field ) ); var added = value.WithFlag( AttributeTargets.Field, true ); Assert.IsTrue( added.Contains( AttributeTargets.Field ) ); var removed = added.WithFlag( AttributeTargets.Field, false ); Assert.IsFalse( removed.Contains( AttributeTargets.Field ) ); Assert.AreEqual( (int)AttributeTargets.Class, AttributeTargets.Class.AsInt() ); } }