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