1 //
2 // Copyright (c) 2010-2024 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 using System;
8 using NUnit.Framework;
9 using Antmicro.Renode.Time;
10 using System.Collections.Generic;
11 using System.Threading.Tasks;
12 using System.Diagnostics;
13 using System.Threading;
14 using System.Collections.Concurrent;
15 using System.Linq;
16 using Antmicro.Renode.Core;
17 using Antmicro.Renode.Peripherals.Bus;
18 using Antmicro.Renode.EventRecording;
19 using Antmicro.Renode.Utilities;
20 using Antmicro.Renode.Debugging;
21 using Antmicro.Renode.Logging;
22 
23 namespace UnitTests
24 {
25     [TestFixture]
26     public class TimeSourceTests
27     {
28         [Test]
ShouldHandleMultipleSinks()29         public void ShouldHandleMultipleSinks()
30         {
31             const int slavesCount = 5;
32             const int roundsCount = 3;
33 
34             using(var masterTimeSource = new MasterTimeSource())
35             {
36                 var timeSinks = new SimpleTimeSink[slavesCount];
37                 for(int i = 0; i < slavesCount; i++)
38                 {
39                     timeSinks[i] = new SimpleTimeSink(double.MaxValue);
40                     masterTimeSource.RegisterSink(timeSinks[i]);
41                 }
42 
43                 // the first round does not increment the time - it just triggers a sync point
44                 masterTimeSource.Run(roundsCount + 1);
45                 Assert.AreEqual(roundsCount + 1, masterTimeSource.NumberOfSyncPoints);
46 
47                 for(int i = 0; i < slavesCount; i++)
48                 {
49                     Assert.AreEqual(roundsCount * masterTimeSource.Quantum.Ticks, timeSinks[i].ElapsedVirtualTime.Ticks);
50                     Assert.AreEqual(roundsCount, timeSinks[i].NumberOfRounds);
51                 }
52             }
53         }
54 
55         [Test]
ShouldNotSleepOnAdvanceImmediately()56         public void ShouldNotSleepOnAdvanceImmediately()
57         {
58             const int slavesCount = 5;
59             const int roundsCount = 3;
60 
61             using(var timeSource = new MasterTimeSource { Quantum = TimeInterval.FromMilliseconds(1000), AdvanceImmediately = true })
62             {
63                 var timeSinks = new SimpleTimeSink[slavesCount];
64                 for(int i = 0; i < slavesCount; i++)
65                 {
66                     timeSinks[i] = new SimpleTimeSink(double.MaxValue);
67                     timeSource.RegisterSink(timeSinks[i]);
68                 }
69                 var sw = Stopwatch.StartNew();
70 
71                 // the first round does not increment the time - it just triggers a sync point
72                 timeSource.Run(roundsCount + 1);
73 
74                 var after = sw.Elapsed;
75                 Assert.IsTrue(after.TotalSeconds < roundsCount);
76             }
77         }
78 
79         // TODO: think about those tests
80         [Test, Ignore("Ignored")]
ShouldCalculateCumulativeLoadForIndefinitePerformance()81         public void ShouldCalculateCumulativeLoadForIndefinitePerformance()
82         {
83             const int slavesCount = 5;
84             const int roundsCount = 3;
85 
86             using(var timeSource = new MasterTimeSource() { Quantum = TimeInterval.FromTicks(1000000), AdvanceImmediately = true })
87             {
88                 var timeSinks = new SimpleTimeSink[slavesCount];
89                 for(int i = 0; i < slavesCount; i++)
90                 {
91                     timeSinks[i] = new SimpleTimeSink(double.MaxValue);
92                     timeSource.RegisterSink(timeSinks[i]);
93                 }
94                 timeSource.Run(roundsCount);
95                 Assert.IsTrue(timeSource.CumulativeLoad < 0.1);
96             }
97         }
98 
99         [Test, Ignore("Ignored")]
ShouldCalculateCumulativeLoadForHighPerformance()100         public void ShouldCalculateCumulativeLoadForHighPerformance()
101         {
102             const int slavesCount = 5;
103             const int roundsCount = 3;
104 
105             using(var timeSource = new MasterTimeSource() { Quantum = TimeInterval.FromTicks(1000000), AdvanceImmediately = false })
106             {
107                 var timeSinks = new SimpleTimeSink[slavesCount];
108                 for(int i = 0; i < slavesCount; i++)
109                 {
110                     timeSinks[i] = new SimpleTimeSink(3.0);
111                     timeSource.RegisterSink(timeSinks[i]);
112                 }
113                 timeSource.Run(roundsCount);
114                 Assert.AreEqual(0.3, timeSource.CumulativeLoad, 0.05);
115             }
116         }
117 
118         [Test, Ignore("Ignored")]
ShouldCalculateCumulativeLoadForLowPerformance()119         public void ShouldCalculateCumulativeLoadForLowPerformance()
120         {
121             const int slavesCount = 5;
122             const int roundsCount = 3;
123 
124             using(var timeSource = new MasterTimeSource() { Quantum = TimeInterval.FromTicks(1000000), AdvanceImmediately = false })
125             {
126                 var timeSinks = new SimpleTimeSink[slavesCount];
127                 for(int i = 0; i < slavesCount; i++)
128                 {
129                     timeSinks[i] = new SimpleTimeSink(0.1);
130                     timeSource.RegisterSink(timeSinks[i]);
131                 }
132                 timeSource.Run(roundsCount);
133                 Assert.AreEqual(10.0, timeSource.CumulativeLoad, 0.05);
134             }
135         }
136 
137         [Test]
ShouldHandleSlaveTimeSourceWithSameQuantum()138         public void ShouldHandleSlaveTimeSourceWithSameQuantum()
139         {
140             const int roundsCount = 3;
141 
142             using(var timeSource = new MasterTimeSource { Quantum = TimeInterval.FromTicks(100), AdvanceImmediately = true })
143             using(var timeSlave = new SlaveTimeSource() { Quantum = TimeInterval.FromTicks(100), AdvanceImmediately = true })
144             {
145                 var timeSink = new SimpleTimeSink(1.0);
146 
147                 timeSource.RegisterSink(timeSlave);
148                 timeSlave.RegisterSink(timeSink);
149 
150                 // the first round does not increment the time - it just triggers a sync point
151                 timeSource.Run(roundsCount + 1);
152 
153                 Assert.AreEqual(roundsCount + 1, timeSource.NumberOfSyncPoints);
154                 Assert.AreEqual(roundsCount + 1, timeSlave.NumberOfSyncPoints);
155                 Assert.AreEqual(roundsCount, timeSink.NumberOfRounds);
156 
157                 Assert.AreEqual(roundsCount * timeSource.Quantum.Ticks, timeSource.ElapsedVirtualTime.Ticks);
158                 Assert.AreEqual(roundsCount * timeSource.Quantum.Ticks, timeSlave.ElapsedVirtualTime.Ticks);
159                 Assert.AreEqual(roundsCount * timeSource.Quantum.Ticks, timeSink.ElapsedVirtualTime.Ticks);
160             }
161         }
162 
163         [Test]
ShouldHandleSlaveTimeSourceWithDifferentQuantum()164         public void ShouldHandleSlaveTimeSourceWithDifferentQuantum()
165         {
166             const int roundsCount = 3;
167 
168             using(var timeSource = new MasterTimeSource { Quantum = TimeInterval.FromTicks(100), AdvanceImmediately = true })
169             using(var timeSlave = new SlaveTimeSource() { Quantum = TimeInterval.FromTicks(10), AdvanceImmediately = true })
170             {
171                 var timeSink = new SimpleTimeSink(1.0);
172 
173                 timeSource.RegisterSink(timeSlave);
174                 timeSlave.RegisterSink(timeSink);
175 
176                 // the first round does not increment the time - it just triggers a sync point
177                 timeSource.Run(roundsCount + 1);
178 
179                 Assert.AreEqual(roundsCount + 1, timeSource.NumberOfSyncPoints);
180                 Assert.AreEqual(10 * roundsCount + 1, timeSlave.NumberOfSyncPoints);
181                 Assert.AreEqual(10 * roundsCount, timeSink.NumberOfRounds);
182 
183                 Assert.AreEqual(roundsCount * timeSource.Quantum.Ticks, timeSource.ElapsedVirtualTime.Ticks);
184                 Assert.AreEqual(roundsCount * timeSource.Quantum.Ticks, timeSlave.ElapsedVirtualTime.Ticks);
185                 Assert.AreEqual(roundsCount * timeSource.Quantum.Ticks, timeSink.ElapsedVirtualTime.Ticks);
186             }
187         }
188 
189         [Test]
ShouldHandleSlaveTimeSourceWithNotAlignedQuantum()190         public void ShouldHandleSlaveTimeSourceWithNotAlignedQuantum()
191         {
192             using(var timeSource = new MasterTimeSource { Quantum = TimeInterval.FromTicks(10), AdvanceImmediately = true })
193             using(var timeSlave = new SlaveTimeSource() { Quantum = TimeInterval.FromTicks(3), AdvanceImmediately = true })
194             {
195                 var timeSink = new SimpleTimeSink(1.0);
196 
197                 timeSource.RegisterSink(timeSlave);
198                 timeSlave.RegisterSink(timeSink);
199 
200                 // the first round does not increment the time - it just triggers a sync point
201                 timeSource.Run(1);
202 
203                 Assert.AreEqual(1, timeSource.NumberOfSyncPoints);
204                 Assert.AreEqual(0, timeSlave.NumberOfSyncPoints);
205                 Assert.AreEqual(0, timeSink.NumberOfRounds);
206 
207                 Assert.AreEqual(0, timeSource.ElapsedVirtualTime.Ticks);
208                 Assert.AreEqual(0, timeSlave.ElapsedVirtualTime.Ticks);
209                 Assert.AreEqual(0, timeSink.ElapsedVirtualTime.Ticks);
210 
211                 timeSource.Run(1);
212 
213                 Assert.AreEqual(2, timeSource.NumberOfSyncPoints);
214                 Assert.AreEqual(4, timeSlave.NumberOfSyncPoints);
215                 Assert.AreEqual(3, timeSink.NumberOfRounds);
216 
217                 Assert.AreEqual(10, timeSource.ElapsedVirtualTime.Ticks);
218                 Assert.AreEqual(9, timeSlave.ElapsedVirtualTime.Ticks);
219                 Assert.AreEqual(9, timeSink.ElapsedVirtualTime.Ticks);
220 
221                 timeSource.Run(1);
222 
223                 Assert.AreEqual(3, timeSource.NumberOfSyncPoints);
224                 Assert.AreEqual(7, timeSlave.NumberOfSyncPoints);
225                 Assert.AreEqual(6, timeSink.NumberOfRounds);
226 
227                 Assert.AreEqual(20, timeSource.ElapsedVirtualTime.Ticks);
228                 Assert.AreEqual(18, timeSlave.ElapsedVirtualTime.Ticks);
229                 Assert.AreEqual(18, timeSink.ElapsedVirtualTime.Ticks);
230 
231                 timeSource.Run(1);
232 
233                 Assert.AreEqual(4, timeSource.NumberOfSyncPoints);
234                 Assert.AreEqual(11, timeSlave.NumberOfSyncPoints);
235                 Assert.AreEqual(10, timeSink.NumberOfRounds);
236 
237                 Assert.AreEqual(30, timeSource.ElapsedVirtualTime.Ticks);
238                 Assert.AreEqual(30, timeSlave.ElapsedVirtualTime.Ticks);
239                 Assert.AreEqual(30, timeSink.ElapsedVirtualTime.Ticks);
240 
241                 timeSource.Run(1);
242 
243                 Assert.AreEqual(5, timeSource.NumberOfSyncPoints);
244                 Assert.AreEqual(14, timeSlave.NumberOfSyncPoints);
245                 Assert.AreEqual(13, timeSink.NumberOfRounds);
246 
247                 Assert.AreEqual(40, timeSource.ElapsedVirtualTime.Ticks);
248                 Assert.AreEqual(39, timeSlave.ElapsedVirtualTime.Ticks);
249                 Assert.AreEqual(39, timeSink.ElapsedVirtualTime.Ticks);
250 
251                 timeSource.Run(1);
252 
253                 Assert.AreEqual(6, timeSource.NumberOfSyncPoints);
254                 Assert.AreEqual(17, timeSlave.NumberOfSyncPoints);
255                 Assert.AreEqual(16, timeSink.NumberOfRounds);
256 
257                 Assert.AreEqual(50, timeSource.ElapsedVirtualTime.Ticks);
258                 Assert.AreEqual(48, timeSlave.ElapsedVirtualTime.Ticks);
259                 Assert.AreEqual(48, timeSink.ElapsedVirtualTime.Ticks);
260 
261                 timeSource.Run(1);
262 
263                 Assert.AreEqual(7, timeSource.NumberOfSyncPoints);
264                 Assert.AreEqual(21, timeSlave.NumberOfSyncPoints);
265                 Assert.AreEqual(20, timeSink.NumberOfRounds);
266 
267                 Assert.AreEqual(60, timeSource.ElapsedVirtualTime.Ticks);
268                 Assert.AreEqual(60, timeSlave.ElapsedVirtualTime.Ticks);
269                 Assert.AreEqual(60, timeSink.ElapsedVirtualTime.Ticks);
270             }
271         }
272 
273         [Test]
ShouldNotTickDisconnectedSlaveTimeSource()274         public void ShouldNotTickDisconnectedSlaveTimeSource()
275         {
276             using(var master = new MasterTimeSource { Quantum = TimeInterval.FromTicks(10), AdvanceImmediately = true })
277             using(var timeSlave = new SlaveTimeSource() { Quantum = TimeInterval.FromTicks(10), AdvanceImmediately = true })
278             {
279                 var timeSink = new SimpleTimeSink(1.0);
280 
281                 master.RegisterSink(timeSlave);
282                 timeSlave.RegisterSink(timeSink);
283 
284                 // the first round does not increment the time - it just triggers a sync point
285                 master.Run(1 + 1);
286 
287                 Assert.AreEqual(2, master.NumberOfSyncPoints);
288                 Assert.AreEqual(2, timeSlave.NumberOfSyncPoints);
289                 Assert.AreEqual(1, timeSink.NumberOfRounds);
290 
291                 Assert.AreEqual(10, master.ElapsedVirtualTime.Ticks);
292                 Assert.AreEqual(10, timeSlave.ElapsedVirtualTime.Ticks);
293                 Assert.AreEqual(10, timeSink.ElapsedVirtualTime.Ticks);
294 
295                 timeSlave.TimeHandle.Dispose();
296 
297                 master.Run(1);
298 
299                 Assert.AreEqual(3, master.NumberOfSyncPoints);
300                 Assert.AreEqual(2, timeSlave.NumberOfSyncPoints);
301                 Assert.AreEqual(1, timeSink.NumberOfRounds);
302 
303                 Assert.AreEqual(20, master.ElapsedVirtualTime.Ticks);
304                 Assert.AreEqual(10, timeSlave.ElapsedVirtualTime.Ticks);
305                 Assert.AreEqual(10, timeSink.ElapsedVirtualTime.Ticks);
306             }
307         }
308 
309         [Test]
ShouldExecuteDelayedActionInNearestSyncedStateExactlyOnce()310         public void ShouldExecuteDelayedActionInNearestSyncedStateExactlyOnce()
311         {
312             var time = TimeInterval.Empty;
313 
314             var alreadyDone = false;
315             using(var master = new MasterTimeSource { Quantum = TimeInterval.FromTicks(10), AdvanceImmediately = true })
316             using(var timeSlave = new SlaveTimeSource() { Quantum = TimeInterval.FromTicks(10), AdvanceImmediately = true })
317             {
318                 var timeSink = new SimpleTimeSink(1.0, (sts, th, ti) =>
319                 {
320                     if(alreadyDone)
321                     {
322                         return true;
323                     }
324                     timeSlave.ExecuteInNearestSyncedState(ts =>
325                     {
326                         time = ts.TimeElapsed;
327                     });
328                     alreadyDone = true;
329                     return true;
330                 });
331 
332                 master.RegisterSink(timeSlave);
333                 timeSlave.RegisterSink(timeSink);
334 
335                 master.Run(1);
336                 Assert.AreEqual(0, time.Ticks);
337                 Assert.AreEqual(false, alreadyDone);
338                 // here we must run 2 rounds as delayed actions are executed at the beginning of each round
339                 master.Run(1);
340                 Assert.AreEqual(true, alreadyDone);
341                 Assert.AreEqual(10, time.Ticks);
342                 master.Run(1);
343                 Assert.AreEqual(10, time.Ticks);
344             }
345         }
346 
347         [Test]
ShouldExecuteDelayedActionAddedLaterInNearestSyncedState()348         public void ShouldExecuteDelayedActionAddedLaterInNearestSyncedState()
349         {
350             var time = TimeInterval.Empty;
351 
352             using(var master = new MasterTimeSource { Quantum = TimeInterval.FromTicks(10), AdvanceImmediately = true })
353             using(var timeSlave = new SlaveTimeSource() { Quantum = TimeInterval.FromTicks(10), AdvanceImmediately = true })
354             {
355                 var timeSink = new SimpleTimeSink(1.0, (sts, th, ti) =>
356                 {
357                     if(sts.NumberOfRounds == 10)
358                     {
359                         timeSlave.ExecuteInNearestSyncedState(ts =>
360                         {
361                             time = ts.TimeElapsed;
362                         });
363                     }
364                     return true;
365                 });
366 
367                 master.RegisterSink(timeSlave);
368                 timeSlave.RegisterSink(timeSink);
369 
370                 // the first round does not increment the time - it just triggers a sync point
371                 master.Run(10 + 1);
372 
373                 Assert.AreEqual(100, time.Ticks);
374             }
375         }
376 
377         [Test]
ShouldExecuteDelayedActionInSyncedStateExactlyOnce()378         public void ShouldExecuteDelayedActionInSyncedStateExactlyOnce()
379         {
380             var time = TimeInterval.Empty;
381 
382             using(var master = new MasterTimeSource { Quantum = TimeInterval.FromTicks(10), AdvanceImmediately = true })
383             using(var timeSlave = new SlaveTimeSource() { Quantum = TimeInterval.FromTicks(10), AdvanceImmediately = true })
384             {
385                 var timeSink = new SimpleTimeSink(1.0, (sts, th, ti) =>
386                 {
387                     if(sts.NumberOfRounds != 1)
388                     {
389                         return true;
390                     }
391                     timeSlave.ExecuteInSyncedState(ts =>
392                     {
393                         time = timeSlave.ElapsedVirtualTime;
394                     }, new TimeStamp(TimeInterval.FromTicks(30), master.Domain));
395                     return true;
396                 });
397 
398                 master.RegisterSink(timeSlave);
399                 timeSlave.RegisterSink(timeSink);
400 
401                 master.Run(3);
402                 Assert.AreEqual(0, time.Ticks);
403                 master.Run(1);
404                 Assert.AreEqual(30, time.Ticks);
405                 master.Run(1);
406                 Assert.AreEqual(30, time.Ticks);
407             }
408         }
409 
410         [Test]
ShouldExecuteNonAlignedDelayedActionInSyncedStateExactlyOnce()411         public void ShouldExecuteNonAlignedDelayedActionInSyncedStateExactlyOnce()
412         {
413             var time = TimeInterval.Empty;
414 
415             using(var master = new MasterTimeSource { Quantum = TimeInterval.FromTicks(10), AdvanceImmediately = true })
416             using(var timeSlave = new SlaveTimeSource() { Quantum = TimeInterval.FromTicks(10), AdvanceImmediately = true })
417             {
418                 var timeSink = new SimpleTimeSink(1.0, (sts, th, ti) =>
419                 {
420                     if(sts.NumberOfRounds != 1)
421                     {
422                         return true;
423                     }
424                     timeSlave.ExecuteInSyncedState(ts =>
425                     {
426                         time = timeSlave.ElapsedVirtualTime;
427                     }, new TimeStamp(TimeInterval.FromTicks(31), master.Domain));
428                     return true;
429                 });
430 
431                 master.RegisterSink(timeSlave);
432                 timeSlave.RegisterSink(timeSink);
433 
434                 master.Run(4);
435                 Assert.AreEqual(0, time.Ticks);
436                 master.Run(1);
437                 Assert.AreEqual(40, time.Ticks);
438                 master.Run(1);
439                 Assert.AreEqual(40, time.Ticks);
440             }
441         }
442 
443         [Test]
ShouldUpdateExecutedTimeAfterBlocking()444         public void ShouldUpdateExecutedTimeAfterBlocking()
445         {
446             var indicator = false;
447             var firstTime = true;
448             var threadToSinkSync = new ManualResetEvent(false);
449             var sinkToThreadSync = new ManualResetEvent(false);
450 
451             using(var master = new MasterTimeSource { Quantum = TimeInterval.FromTicks(10), AdvanceImmediately = true })
452             using(var timeSlave = new SlaveTimeSource() { Quantum = TimeInterval.FromTicks(10), AdvanceImmediately = true })
453             {
454                 var timeSink = new SimpleTimeSink(1.0, (sts, th, ti) =>
455                 {
456                     if(firstTime)
457                     {
458                         Assert.AreEqual(10, ti.Ticks);
459                         firstTime = false;
460                         var timeUsed = TimeInterval.FromTicks(ti.Ticks / 2);
461                         var timeLeft = ti - timeUsed;
462 
463                         sts.ElapsedVirtualTime += timeUsed;
464 
465                         th.ReportBackAndBreak(timeLeft);
466                         sinkToThreadSync.Set();
467                         threadToSinkSync.WaitOne();
468                     }
469                     else
470                     {
471                         Assert.AreEqual(5, ti.Ticks);
472                         sts.ElapsedVirtualTime += ti;
473                         th.ReportBackAndContinue(TimeInterval.Empty);
474                     }
475 
476                     return false;
477                 });
478 
479                 var testerThread = new TestThread(() =>
480                 {
481                     // wait for the pause
482                     sinkToThreadSync.WaitOne();
483 
484                     // here we sleep to make sure that master won't go further
485                     Thread.Sleep(5000);
486 
487                     Assert.AreEqual(5, master.ElapsedVirtualTime.Ticks);
488                     Assert.AreEqual(5, timeSlave.ElapsedVirtualTime.Ticks);
489                     indicator = true;
490 
491                     threadToSinkSync.Set();
492                 });
493                 testerThread.Start();
494 
495                 master.RegisterSink(timeSlave);
496                 timeSlave.RegisterSink(timeSink);
497 
498                 // just to pass the first syncpoint
499                 master.Run(1);
500                 testerThread.CheckExceptions();
501 
502                 master.Run(1);
503                 Assert.IsTrue(indicator);
504                 testerThread.CheckExceptions();
505 
506                 Assert.AreEqual(10, master.ElapsedVirtualTime.Ticks);
507                 Assert.AreEqual(10, timeSlave.ElapsedVirtualTime.Ticks);
508                 Assert.AreEqual(10, timeSink.ElapsedVirtualTime.Ticks);
509             }
510         }
511 
512         [Test]
ShouldHandleTwoBlockingSinks()513         public void ShouldHandleTwoBlockingSinks()
514         {
515             var indicator = false;
516 
517             using(var master = new MasterTimeSource { Quantum = TimeInterval.FromTicks(10), AdvanceImmediately = true })
518             using(var timeSlave = new SlaveTimeSource() { Quantum = TimeInterval.FromTicks(10), AdvanceImmediately = true })
519             using(var timeSinkA2 = new MoreComplicatedTimeSink("A"))
520             using(var timeSinkB2 = new MoreComplicatedTimeSink("B"))
521             {
522                 var ttt = new TestThread(() =>
523                 {
524                     Parallel(
525                         () =>
526                         {
527                             timeSinkA2.ExecuteOnDispatcherThread((sts, ti) =>
528                             {
529                                 this.Trace();
530                                 Assert.AreEqual(10, ti.Ticks);
531                                 var timeUsed = TimeInterval.FromTicks(4);
532                                 var timeLeft = ti - timeUsed;
533                                 sts.TimeHandle.ReportBackAndBreak(timeLeft);
534                             });
535                         },
536 
537                         () =>
538                         {
539                             timeSinkB2.ExecuteOnDispatcherThread((sts, ti) =>
540                             {
541                                 this.Trace();
542                                 Assert.AreEqual(10, ti.Ticks);
543                                 var timeUsed = TimeInterval.FromTicks(6);
544                                 var timeLeft = ti - timeUsed;
545                                 sts.TimeHandle.ReportBackAndBreak(timeLeft);
546                             });
547                         }
548                     );
549 
550                     // here we sleep to make sure that master won't go further
551                     this.Trace();
552                     Thread.Sleep(5000);
553 
554                     this.Trace();
555                     Assert.AreEqual(4, master.ElapsedVirtualTime.Ticks);
556                     Assert.AreEqual(4, timeSlave.ElapsedVirtualTime.Ticks);
557 
558                     Parallel(
559                         () =>
560                         {
561                             timeSinkA2.ExecuteOnDispatcherThread((sts, ti) =>
562                             {
563                                 this.Trace();
564                                 Assert.AreEqual(6, ti.Ticks);
565                                 var timeUsed = TimeInterval.FromTicks(4);
566                                 var timeLeft = ti - timeUsed;
567                                 sts.TimeHandle.ReportBackAndBreak(timeLeft);
568                             });
569                         },
570 
571                         () =>
572                         {
573                             timeSinkB2.ExecuteOnDispatcherThread((sts, ti) =>
574                             {
575                                 this.Trace();
576                                 Assert.AreEqual(4, ti.Ticks);
577                                 sts.TimeHandle.ReportBackAndBreak(ti);
578                             });
579                         }
580                     );
581 
582                     // here we sleep to make sure that master won't go further
583                     this.Trace();
584                     Thread.Sleep(5000);
585 
586                     this.Trace();
587                     Assert.AreEqual(6, master.ElapsedVirtualTime.Ticks);
588                     Assert.AreEqual(6, timeSlave.ElapsedVirtualTime.Ticks);
589 
590                     Parallel(
591                         () =>
592                         {
593                             timeSinkA2.ExecuteOnDispatcherThread((sts, ti) =>
594                             {
595                                 this.Trace();
596                                 Assert.AreEqual(2, ti.Ticks);
597                                 sts.TimeHandle.ReportBackAndBreak(ti);
598                             });
599                         },
600 
601                         () =>
602                         {
603                             timeSinkB2.ExecuteOnDispatcherThread((sts, ti) =>
604                             {
605                                 this.Trace();
606                                 Assert.AreEqual(4, ti.Ticks);
607                                 sts.TimeHandle.ReportBackAndContinue(TimeInterval.Empty);
608                             });
609                         }
610                     );
611 
612                     // here we sleep to make sure that master won't go further
613                     this.Trace();
614                     Thread.Sleep(5000);
615 
616                     this.Trace();
617                     Assert.AreEqual(8, master.ElapsedVirtualTime.Ticks);
618                     Assert.AreEqual(8, timeSlave.ElapsedVirtualTime.Ticks);
619 
620                     Parallel(
621                         () =>
622                         {
623                             timeSinkA2.ExecuteOnDispatcherThread((sts, ti) =>
624                             {
625                                 this.Trace();
626                                 Assert.AreEqual(2, ti.Ticks);
627                                 indicator = true;
628                                 sts.TimeHandle.ReportBackAndContinue(TimeInterval.Empty);
629                             });
630                         },
631 
632                         () =>
633                         {
634                             timeSinkB2.ExecuteOnDispatcherThread((sts, ti) =>
635                             {
636                                 this.Trace();
637                                 Assert.Fail();
638                             }, false); // do not wait for finish
639                             Thread.Sleep(10000); // wait for 10s and check if Fail() is called
640                         }
641                     );
642                 })
643                 { Name = "tester thread" };
644                 ttt.Start();
645 
646                 master.RegisterSink(timeSlave);
647                 timeSlave.RegisterSink(timeSinkA2);
648                 timeSlave.RegisterSink(timeSinkB2);
649 
650                 // just to pass the first syncpoint
651                 master.Run(1);
652 
653                 master.Run(1);
654                 Assert.IsTrue(indicator);
655 
656                 this.Trace();
657                 Assert.AreEqual(10, master.ElapsedVirtualTime.Ticks);
658                 Assert.AreEqual(10, timeSlave.ElapsedVirtualTime.Ticks);
659 
660                 ttt.Join();
661             }
662         }
663 
664         [Test]
ShouldHandleBlockingAtTheEndOfGrantedInterval()665         public void ShouldHandleBlockingAtTheEndOfGrantedInterval()
666         {
667             var indicator = false;
668 
669             using(var master = new MasterTimeSource { Quantum = TimeInterval.FromTicks(10), AdvanceImmediately = true })
670             using(var timeSlave = new SlaveTimeSource() { Quantum = TimeInterval.FromTicks(10), AdvanceImmediately = true })
671             using(var timeSink = new MoreComplicatedTimeSink("A"))
672             {
673                 var testerThread = new TestThread(() =>
674                 {
675                     this.Trace();
676                     timeSink.ExecuteOnDispatcherThread((ts, ti) =>
677                     {
678                         this.Trace();
679                         Assert.AreEqual(10, ti.Ticks);
680 
681                         ts.TimeHandle.ReportBackAndBreak(TimeInterval.Empty);
682                     });
683 
684                     this.Trace();
685                     // here we sleep to make sure that master won't go further
686                     Thread.Sleep(5000);
687 
688                     this.Trace();
689                     Assert.AreEqual(10, master.ElapsedVirtualTime.Ticks);
690                     Assert.AreEqual(10, timeSlave.ElapsedVirtualTime.Ticks);
691                     indicator = true;
692 
693                     this.Trace();
694                     timeSink.ExecuteOnDispatcherThread((ts, ti) =>
695                     {
696                         this.Trace();
697                         Assert.AreEqual(0, ti.Ticks);
698                         ts.TimeHandle.ReportBackAndContinue(TimeInterval.Empty);
699                     });
700 
701                     timeSink.ExecuteOnDispatcherThread((ts, ti) =>
702                     {
703                         this.Trace();
704                         Assert.AreEqual(10, ti.Ticks);
705                         ts.TimeHandle.ReportBackAndContinue(TimeInterval.Empty);
706                     });
707                 }){ Name = "tester thread" };
708                 testerThread.Start();
709 
710                 master.RegisterSink(timeSlave);
711                 timeSlave.RegisterSink(timeSink);
712 
713                 // just to pass the first syncpoint
714                 master.Run(1);
715                 testerThread.CheckExceptions();
716 
717                 this.Trace();
718                 master.Run(1);
719                 testerThread.CheckExceptions();
720                 this.Trace();
721                 Assert.IsTrue(indicator);
722                 Assert.AreEqual(10, master.ElapsedVirtualTime.Ticks);
723                 Assert.AreEqual(10, timeSlave.ElapsedVirtualTime.Ticks);
724 
725                 master.Run(1);
726                 Assert.AreEqual(20, master.ElapsedVirtualTime.Ticks);
727                 Assert.AreEqual(20, timeSlave.ElapsedVirtualTime.Ticks);
728                 testerThread.CheckExceptions();
729             }
730         }
731 
732         [Test, Repeat(5)]
ShouldRecordAndPlayEvents()733         public void ShouldRecordAndPlayEvents()
734         {
735             var temporaryFile = TemporaryFilesManager.Instance.GetTemporaryFile();
736             var machineFactory = new Func<Machine>(() =>
737             {
738                 var result = new Machine();
739                 var peripheral = new PeripheralMock(result);
740                 result.SystemBus.Register(peripheral, 0.By(1));
741                 result.SetLocalName(peripheral, "mock");
742                 return result;
743             });
744 
745             ulong evt;
746             IEnumerable<Tuple<ulong, int>> recordedEvents;
747             using(var machine = machineFactory())
748             {
749                 var peripheralMock = (PeripheralMock)machine["sysbus.mock"];
750 
751                 EmulationManager.Instance.Clear();
752                 EmulationManager.Instance.CurrentEmulation.AddMachine(machine);
753                 EmulationManager.Instance.CurrentEmulation.StartAll();
754 
755                 machine.RecordTo(temporaryFile, RecordingBehaviour.DomainExternal);
756 
757                 for(var i = 0; i < 100; i++)
758                 {
759                     peripheralMock.Method(i);
760                     peripheralMock.MethodTwoArgs(i, 0);
761                     Thread.Sleep(EmulationManager.Instance.CurrentEmulation.RandomGenerator.Next(30));
762                 }
763 
764                 EmulationManager.Instance.CurrentEmulation.PauseAll();
765                 evt = EmulationManager.Instance.CurrentEmulation.MasterTimeSource.ElapsedVirtualTime.Ticks;
766                 recordedEvents = peripheralMock.Events;
767                 EmulationManager.Instance.CurrentEmulation.RemoveMachine(machine);
768             }
769 
770             IEnumerable<Tuple<ulong, int>> playedEvents;
771             using(var machine = machineFactory())
772             {
773                 var peripheralMock = (PeripheralMock)machine["sysbus.mock"];
774                 EmulationManager.Instance.Clear();
775                 EmulationManager.Instance.CurrentEmulation.AddMachine(machine);
776 
777                 machine.PlayFrom(temporaryFile);
778 
779                 EmulationManager.Instance.CurrentEmulation.StartAll();
780 
781                 while(EmulationManager.Instance.CurrentEmulation.MasterTimeSource.ElapsedVirtualTime.Ticks < evt)
782                 {
783                     Thread.Yield();
784                 }
785 
786                 EmulationManager.Instance.CurrentEmulation.PauseAll();
787                 playedEvents = peripheralMock.Events;
788             }
789             CollectionAssert.AreEqual(recordedEvents, playedEvents);
790             TemporaryFilesManager.Instance.Cleanup();
791         }
792 
Parallel(Action a, Action b)793         private static void Parallel(Action a, Action b)
794         {
795             var t1 = Task.Factory.StartNew(a);
796             var t2 = Task.Factory.StartNew(b);
797 
798             Task.WaitAll(t1, t2);
799         }
800 
801         private class MoreComplicatedTimeSink : IdentifiableObject, ITimeSink, IDisposable
802         {
MoreComplicatedTimeSink(string name)803             public MoreComplicatedTimeSink(string name)
804             {
805                 barrier = new AutoResetEvent(false);
806                 barrierBack = new AutoResetEvent(false);
807                 this.name = name;
808             }
809 
Dispose()810             public void Dispose()
811             {
812                 isDisposed = true;
813                 ExecuteOnDispatcherThread(null, false);
814             }
815 
816             public TimeHandle TimeHandle
817             {
818                 get => handle;
819                 set
820                 {
821                     this.handle = value;
822                     dispatcherThread = new TestThread(Dispatcher) { Name = $"MoreComplicatedTimeSink: {name}" };
823                     dispatcherThread.Start();
824                 }
825             }
826 
ExecuteOnDispatcherThread(Action<ITimeSink, TimeInterval> action, bool wait = true)827             public void ExecuteOnDispatcherThread(Action<ITimeSink, TimeInterval> action, bool wait = true)
828             {
829                 this.Trace("About to execute on dispatcher thread");
830                 this.action = action;
831                 barrier.Set();
832                 if(wait)
833                 {
834                     this.Trace("Waiting until execution is finished");
835                     barrierBack.WaitOne();
836                     this.Trace("It's finished");
837                     dispatcherThread.CheckExceptions();
838                 }
839             }
840 
Dispatcher()841             private void Dispatcher()
842             {
843                 this.Trace("Starting dispatcher thread");
844                 try
845                 {
846                     handle.SinkSideActive = true;
847                     while(true)
848                     {
849                         this.Trace("Waiting on barrier...");
850                         barrier.WaitOne();
851                         this.Trace("After barrier");
852                         var a = action;
853                         action = null;
854                         if(a == null)
855                         {
856                             this.Trace("Action is null, finishing");
857                             break;
858                         }
859 
860                         TimeInterval timeUnits;
861                         while(!handle.RequestTimeInterval(out timeUnits))
862                         {
863                             this.Trace("Request not granted - finishing");
864                             if(isDisposed)
865                             {
866                                 return;
867                             }
868                             continue;
869                         }
870 
871                         this.Trace("Before action");
872                         a(this, timeUnits);
873                         this.Trace("Action finished");
874                         barrierBack.Set();
875                     }
876                 }
877                 catch(Exception e)
878                 {
879                     this.Trace(LogLevel.Error, $"Got an exception: {e.Message} @ {e.StackTrace}");
880                     throw;
881                 }
882                 finally
883                 {
884                     handle.SinkSideActive = false;
885                     this.Trace("Dispatcher thread finished");
886                 }
887             }
888 
889             private Action<ITimeSink, TimeInterval> action;
890             private TimeHandle handle;
891             private bool isDisposed;
892             private TestThread dispatcherThread;
893             private readonly AutoResetEvent barrier;
894             private readonly AutoResetEvent barrierBack;
895             private readonly string name;
896         }
897 
898         private class SimpleTimeSink : ITimeSink
899         {
SimpleTimeSink(double performance, Func<SimpleTimeSink, TimeHandle, TimeInterval, bool> a = null)900             public SimpleTimeSink(double performance, Func<SimpleTimeSink, TimeHandle, TimeInterval, bool> a = null)
901             {
902                 this.performance = performance;
903                 this.action = a;
904             }
905 
906             public TimeHandle TimeHandle
907             {
908                 get { return handle; }
909                 set
910                 {
911                     this.handle = value;
912                     dispatcherThread = new TestThread(Dispatcher) { Name = "SimpleTimeSink Dispatcher Thread" };
913                     dispatcherThread.Start();
914                 }
915             }
916 
917             public TimeInterval ElapsedVirtualTime { get; set; }
918 
919             public long NumberOfRounds { get; private set; }
920 
Dispatcher()921             private void Dispatcher()
922             {
923                 TimeInterval timeUnits;
924                 handle.SinkSideActive = true;
925                 while(true)
926                 {
927                     if(!handle.RequestTimeInterval(out timeUnits))
928                     {
929                         if(handle.DetachRequested)
930                         {
931                             break;
932                         }
933                         else
934                         {
935                             continue;
936                         }
937                     }
938 
939                     NumberOfRounds++;
940 
941                     if(action != null && !action(this, handle, timeUnits))
942                     {
943                         continue;
944                     }
945 
946                     if(performance < double.MaxValue)
947                     {
948                         var scaledTicks = (ulong)(timeUnits.Ticks / performance);
949                         System.Threading.Thread.Sleep(TimeInterval.FromTicks(scaledTicks).ToTimeSpan());
950                     }
951 
952                     ElapsedVirtualTime += timeUnits;
953                     handle.ReportBackAndContinue(TimeInterval.Empty);
954                 }
955                 handle.SinkSideActive = false;
956             }
957 
958             private TestThread dispatcherThread;
959             private TimeHandle handle;
960             private readonly double performance;
961             private readonly Func<SimpleTimeSink, TimeHandle, TimeInterval, bool> action;
962         }
963 
964         private class PeripheralMock : IBytePeripheral
965         {
PeripheralMock(IMachine machine)966             public PeripheralMock(IMachine machine)
967             {
968                 this.machine = machine;
969                 events = new Queue<Tuple<ulong, int>>();
970             }
971 
Reset()972             public void Reset()
973             {
974             }
975 
ReadByte(long offset)976             public byte ReadByte(long offset)
977             {
978                 return 0;
979             }
980 
WriteByte(long offset, byte value)981             public void WriteByte(long offset, byte value)
982             {
983             }
984 
Method(int counter)985             public void Method(int counter)
986             {
987                 machine.HandleTimeDomainEvent(MethodInner, counter, true);
988             }
989 
MethodTwoArgs(int counter, int dummyArg)990             public void MethodTwoArgs(int counter, int dummyArg)
991             {
992                 machine.HandleTimeDomainEvent(MethodTwoArgsInner, counter, dummyArg, true);
993             }
994 
995             public IEnumerable<Tuple<ulong, int>> Events
996             {
997                 get
998                 {
999                     return events.ToArray();
1000                 }
1001             }
1002 
MethodInner(int counter)1003             private void MethodInner(int counter)
1004             {
1005                 events.Enqueue(Tuple.Create(machine.LocalTimeSource.ElapsedVirtualTime.Ticks, counter));
1006             }
1007 
MethodTwoArgsInner(int counter, int dummyArg)1008             private void MethodTwoArgsInner(int counter, int dummyArg)
1009             {
1010                 events.Enqueue(Tuple.Create(machine.LocalTimeSource.ElapsedVirtualTime.Ticks, counter));
1011             }
1012 
1013             private readonly IMachine machine;
1014             private readonly Queue<Tuple<ulong, int>> events;
1015         }
1016 
1017         private class TestThread
1018         {
TestThread(Action a)1019             public TestThread(Action a)
1020             {
1021                 underlyingAction = a;
1022             }
1023 
Start()1024             public void Start()
1025             {
1026                 underlyingThread = new Thread(InnerBody) { Name = this.Name };
1027                 underlyingThread.Start();
1028             }
1029 
Join()1030             public void Join()
1031             {
1032                 underlyingThread.Join();
1033                 CheckExceptions();
1034             }
1035 
CheckExceptions()1036             public void CheckExceptions()
1037             {
1038                 if(caughtException != null)
1039                 {
1040                     throw caughtException;
1041                 }
1042             }
1043 
1044             public string Name { get; set; }
1045 
InnerBody()1046             private void InnerBody()
1047             {
1048                 try
1049                 {
1050                     underlyingAction();
1051                 }
1052                 catch(Exception e)
1053                 {
1054                     caughtException = e;
1055                 }
1056             }
1057 
1058             private readonly Action underlyingAction;
1059             private Exception caughtException;
1060             private Thread underlyingThread;
1061         }
1062     }
1063 }
1064