/* * Copyright (c) 2023 Proton AG * * This file is part of ProtonVPN. * * ProtonVPN is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ProtonVPN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ProtonVPN. If not, see . */ using System; using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; using ProtonVPN.Common.Core.Extensions; namespace ProtonVPN.Common.Core.Tests.Extensions; [TestClass] public class TimeSpanExtensionsTest { private const int NUM_OF_TRIES = 1000; private readonly IReadOnlyList _deviations = [0.01, 0.1, 0.2, 0.25, 0.333, 0.5]; private readonly IReadOnlyList _testValues = [ TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(60), TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(30), TimeSpan.FromHours(1), TimeSpan.FromHours(3), TimeSpan.FromHours(6), TimeSpan.FromHours(8), TimeSpan.FromHours(12), TimeSpan.FromHours(24)]; private readonly IReadOnlyList> _cornerCases = [ new Tuple(TimeSpan.FromSeconds(200), -0.1, TimeSpan.FromSeconds(180)), new Tuple(TimeSpan.FromSeconds(1), -100000, TimeSpan.FromSeconds(-99999)), new Tuple(TimeSpan.FromSeconds(-30), 0.5, TimeSpan.FromSeconds(-45)), new Tuple(TimeSpan.FromSeconds(-2), 100000, TimeSpan.FromSeconds(-200002)), new Tuple(TimeSpan.FromSeconds(-60), -0.1, TimeSpan.FromSeconds(-54)), new Tuple(TimeSpan.FromSeconds(-1), -100000, TimeSpan.FromSeconds(99999))]; [TestMethod] public void TestAddJitterNormalCases() { foreach (double deviation in _deviations) { foreach (TimeSpan testValue in _testValues) { TimeSpan minValue = testValue; TimeSpan maxValue = TimeSpan.FromTicks(testValue.Ticks + (long)(testValue.Ticks * deviation)); TestAddJitterCombination(testValue, deviation, minValue, maxValue); } } } private void TestAddJitterCombination(TimeSpan value, double deviation, TimeSpan expectedMinValue, TimeSpan expectedMaxValue) { for (int i = 0; i < NUM_OF_TRIES; i++) { TimeSpan result = TimeSpanExtensions.AddJitter(value, deviation); Assert.IsTrue(result >= expectedMinValue, $"The jitter result {result} is under the " + $"minimum value {expectedMinValue}. Deviation {deviation} and value {value}."); Assert.IsTrue(result <= expectedMaxValue, $"The jitter result {result} is above the " + $"maximum value {expectedMaxValue}. Deviation {deviation} and value {value}."); } } [TestMethod] public void TestAddJitterCornerCases_UsingRandom() { TestAddJitterCombination(TimeSpan.FromSeconds(200), -0.1, TimeSpan.FromSeconds(180), TimeSpan.FromSeconds(200)); TestAddJitterCombination(TimeSpan.FromSeconds(1), -100000, TimeSpan.FromSeconds(-99999), TimeSpan.FromSeconds(1)); TestAddJitterCombination(TimeSpan.FromSeconds(-30), 0.5, TimeSpan.FromSeconds(-45), TimeSpan.FromSeconds(-30)); TestAddJitterCombination(TimeSpan.FromSeconds(-2), 100000, TimeSpan.FromSeconds(-200002), TimeSpan.FromSeconds(-2)); TestAddJitterCombination(TimeSpan.FromSeconds(-60), -0.1, TimeSpan.FromSeconds(-60), TimeSpan.FromSeconds(-54)); TestAddJitterCombination(TimeSpan.FromSeconds(-1), -100000, TimeSpan.FromSeconds(-1), TimeSpan.FromSeconds(99999)); } [TestMethod] public void TestAddJitterCornerCases_ForcingRandomAsZero() { foreach (Tuple cornerCase in _cornerCases) { TimeSpan result = TimeSpanExtensions.AddJitter(cornerCase.Item1, cornerCase.Item2, randomValue: 0d); Assert.AreEqual(cornerCase.Item1, result); } } // Note: Random.NextDouble() actually returns a value up to <1.0, it shouldn't return 1.0 [TestMethod] public void TestAddJitterCornerCases_ForcingRandomAsOne() { foreach (Tuple cornerCase in _cornerCases) { TimeSpan result = TimeSpanExtensions.AddJitter(cornerCase.Item1, cornerCase.Item2, randomValue: 1d); Assert.AreEqual(cornerCase.Item3, result); } } [TestMethod] [DataRow(5, 8, 5)] [DataRow(5, 5, 5)] [DataRow(0, -5, -5)] [DataRow(2, -5, -5)] [DataRow(-2, 5, -2)] [DataRow(-2, -5, -5)] public void TestMinTimeSpan(double intervalAInSeconds, double intervalBInSeconds, double expectedIntervalInSeconds) { TimeSpan intervalA = TimeSpan.FromSeconds(intervalAInSeconds); TimeSpan intervalB = TimeSpan.FromSeconds(intervalBInSeconds); TimeSpan result = TimeSpanExtensions.Min(intervalA, intervalB); Assert.AreEqual(expectedIntervalInSeconds, result.TotalSeconds); } [TestMethod] [DataRow(5, 3, 8, 5)] [DataRow(5, 6, 8, 6)] [DataRow(5, 3, 4, 4)] [DataRow(-5, -8, -3, -5)] [DataRow(-5, -8, -6, -6)] [DataRow(-5, -4, -3, -4)] public void TestClampTimeSpan(double intervalInSeconds, double minIntervalInSeconds, double maxIntervalInSeconds, double expectedIntervalInSeconds) { TimeSpan interval = TimeSpan.FromSeconds(intervalInSeconds); TimeSpan minInterval = TimeSpan.FromSeconds(minIntervalInSeconds); TimeSpan maxInterval = TimeSpan.FromSeconds(maxIntervalInSeconds); TimeSpan result = interval.Clamp(minInterval, maxInterval); Assert.AreEqual(expectedIntervalInSeconds, result.TotalSeconds); } }