From 99ded566559941b8a51cbf8a9b3ee1e35b9510e6 Mon Sep 17 00:00:00 2001 From: Daniel-I-Am Date: Thu, 4 Jun 2020 17:51:21 +0200 Subject: [PATCH 1/2] Add Timer service --- ChaosBot/Services/Timer.cs | 117 +++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 ChaosBot/Services/Timer.cs diff --git a/ChaosBot/Services/Timer.cs b/ChaosBot/Services/Timer.cs new file mode 100644 index 0000000..2300a17 --- /dev/null +++ b/ChaosBot/Services/Timer.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace ChaosBot.Services +{ + public static class Timer + { + private static Dictionary _timers = new Dictionary(); + + private static int _nextTimerIndex = 0; + private static int NextTimerIndex + { + get + { + _nextTimerIndex++; + return _nextTimerIndex; + } + } + + public static int RunAt(Action toRun, DateTime deadlineTime) + { + DateTime current = DateTime.Now; + TimeSpan timeToGo = deadlineTime - current; + if (timeToGo < TimeSpan.Zero) + { + return -1; //time already passed + } + + int timerIndex = NextTimerIndex; + _timers.Add(timerIndex, new System.Threading.Timer(x => + { + toRun(); + _timers.Remove(timerIndex); + }, null, timeToGo, Timeout.InfiniteTimeSpan)); + + return timerIndex; + } + + public static int RunIn(Action toRun, TimeSpan dueTime) + { + DateTime current = DateTime.Now; + if (dueTime < TimeSpan.Zero) + { + return -1; //time already passed + } + + int timerIndex = NextTimerIndex; + _timers.Add(timerIndex, new System.Threading.Timer(x => + { + toRun(); + _timers.Remove(timerIndex); + }, null, dueTime, Timeout.InfiniteTimeSpan)); + + return timerIndex; + } + + public static int RunTimer(Action toRun, TimeSpan interval) + { + return RunTimer(toRun, interval, TimeSpan.Zero); + } + + public static int RunTimer(Action toRun, TimeSpan interval, TimeSpan offset) + { + DateTime current = DateTime.Now; + if (offset < TimeSpan.Zero) + { + return -1; //time already passed + } + + int timerIndex = NextTimerIndex; + + void Callback() + { + toRun(); + + if (_timers.ContainsKey(timerIndex)) + _timers.Remove(timerIndex); + _timers.Add(timerIndex, new System.Threading.Timer(x => { Callback(); }, null, interval, Timeout.InfiniteTimeSpan)); + } + + if (offset > TimeSpan.Zero) + { + _timers.Add(timerIndex, new System.Threading.Timer(x => { Callback(); }, null, offset, Timeout.InfiniteTimeSpan)); + } + else + { + Callback(); + } + + return timerIndex; + } + + public static void CancelTimer(int id) + { + if (id < 0) return; + + if (!_timers.ContainsKey(id)) return; + + _timers.GetValueOrDefault(id)?.Dispose(); + + if (_timers.ContainsKey(id)) + _timers.Remove(id); + } + + public static void Join(int id) + { + if (id < 0) return; + + while (_timers.ContainsKey(id)) + { + Thread.Sleep(500); + } + } + } +} \ No newline at end of file From 3cb1f137f295f52b0548355383cbb36f4b1218c4 Mon Sep 17 00:00:00 2001 From: Daniel-I-Am Date: Thu, 4 Jun 2020 19:14:45 +0200 Subject: [PATCH 2/2] Add unit testing and test timer class --- .gitignore | 1 + ChaosBot.UnitTests/ChaosBot.UnitTests.csproj | 19 +++++ ChaosBot.UnitTests/TimerTests.cs | 90 ++++++++++++++++++++ ChaosBot.sln | 6 ++ 4 files changed, 116 insertions(+) create mode 100644 ChaosBot.UnitTests/ChaosBot.UnitTests.csproj create mode 100644 ChaosBot.UnitTests/TimerTests.cs diff --git a/.gitignore b/.gitignore index a800e90..c7f5f33 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ obj/ .idea/ /ChaosBot/appsettings.json /ChaosBot/ChaosBotSQL.db +*.sln.DotSettings.user diff --git a/ChaosBot.UnitTests/ChaosBot.UnitTests.csproj b/ChaosBot.UnitTests/ChaosBot.UnitTests.csproj new file mode 100644 index 0000000..cc43bc8 --- /dev/null +++ b/ChaosBot.UnitTests/ChaosBot.UnitTests.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp3.0 + + false + + + + + + + + + + + + + diff --git a/ChaosBot.UnitTests/TimerTests.cs b/ChaosBot.UnitTests/TimerTests.cs new file mode 100644 index 0000000..fc859f1 --- /dev/null +++ b/ChaosBot.UnitTests/TimerTests.cs @@ -0,0 +1,90 @@ +using System; +using Antlr4.Runtime; +using ChaosBot.Services; +using NUnit.Framework; + +namespace ChaosBot.UnitTests +{ + public class TimerTests + { + [SetUp] + public void Setup() + { + } + + [Test] + public void RunAt_RunningTimerAtTimestamp_True() + { + DateTime current = DateTime.Now; + DateTime runAt = current + new TimeSpan(TimeSpan.TicksPerSecond * 10); + DateTime testAt = current + new TimeSpan(TimeSpan.TicksPerSecond * 2); + + int totalRuns = 0; + + void ToRun() + { + totalRuns++; + } + + int timerId = Timer.RunAt(ToRun, runAt); + + Assert.AreEqual(totalRuns, 0); + // Wait for the timer to finish + Timer.Join(timerId); + Assert.AreEqual(totalRuns, 1); + Assert.GreaterOrEqual(DateTime.Now, testAt); + } + + [Test] + public void RunIn_RunningTimerInTimeSpan_True() + { + DateTime current = DateTime.Now; + TimeSpan runIn = new TimeSpan(TimeSpan.TicksPerSecond * 10); + DateTime testAt = current + new TimeSpan(TimeSpan.TicksPerSecond * 2); + + int totalRuns = 0; + + void ToRun() + { + totalRuns++; + } + + int timerId = Timer.RunIn(ToRun, runIn); + + Assert.AreEqual(totalRuns, 0); + // Wait for the timer to finish + Timer.Join(timerId); + Assert.AreEqual(totalRuns, 1); + Assert.GreaterOrEqual(DateTime.Now, testAt); + } + + [Test] + public void RunTimer_RunningTimerCountingExecutions_True() + { + DateTime current = DateTime.Now; + TimeSpan interval = new TimeSpan(TimeSpan.TicksPerSecond * 2); + DateTime testAt = current + new TimeSpan(TimeSpan.TicksPerSecond * 11); + + int totalRuns = 0; + + void ToRun() + { + totalRuns++; + } + + int timerId = Timer.RunTimer(ToRun, interval, offset: interval); + + void CancelCallback() + { + Timer.CancelTimer(timerId); + } + + int cancelTimerId = Timer.RunAt(CancelCallback, testAt); + + Assert.AreEqual(totalRuns, 0); + // Wait for the cancel timer to finish + Timer.Join(cancelTimerId); + Assert.AreEqual(totalRuns, 5); + } + } +} \ No newline at end of file diff --git a/ChaosBot.sln b/ChaosBot.sln index f35eeb4..712d969 100644 --- a/ChaosBot.sln +++ b/ChaosBot.sln @@ -2,6 +2,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChaosBot", "ChaosBot\ChaosBot.csproj", "{0222079F-84D7-4EEF-A2A6-D5AD67546D61}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChaosBot.UnitTests", "ChaosBot.UnitTests\ChaosBot.UnitTests.csproj", "{A4678BAA-93AF-4A10-8548-1921236BC57E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -12,5 +14,9 @@ Global {0222079F-84D7-4EEF-A2A6-D5AD67546D61}.Debug|Any CPU.Build.0 = Debug|Any CPU {0222079F-84D7-4EEF-A2A6-D5AD67546D61}.Release|Any CPU.ActiveCfg = Release|Any CPU {0222079F-84D7-4EEF-A2A6-D5AD67546D61}.Release|Any CPU.Build.0 = Release|Any CPU + {A4678BAA-93AF-4A10-8548-1921236BC57E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4678BAA-93AF-4A10-8548-1921236BC57E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4678BAA-93AF-4A10-8548-1921236BC57E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4678BAA-93AF-4A10-8548-1921236BC57E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal