private static void StopTimer(ControlledTimer timer) { if (timer != null) { timer.Stop(); } }
public async Task SetShotButtonAsync(bool value) { await Task.Delay(1); using (await this.Lock.AcquireAsync()) { this.ShotButton = value; if (this.ShotButton) { // should never turn on the make shots button when there is no water if (this.WaterLevel <= 0) { Specification.Assert(false, "Please do not turn on shot maker if there is no water"); } } if (value && this.ShotTimer == null) { // start monitoring the coffee level. this.ShotTimer = new ControlledTimer("ShotTimer", TimeSpan.FromSeconds(1), this.MonitorShot); } else if (!value && this.ShotTimer != null) { StopTimer(this.ShotTimer); this.ShotTimer = null; } } }
private async Task OnGrinderButtonChanged(bool value) { using (await this.Lock.AcquireAsync()) { this.GrinderButton = value; if (this.GrinderButton) { // should never turn on the grinder when there is no coffee to grind if (this.HopperLevel <= 0) { Specification.Assert(false, "Please do not turn on grinder if there are no beans in the hopper"); } } if (value && this.CoffeeLevelTimer == null) { // start monitoring the coffee level. this.CoffeeLevelTimer = new ControlledTimer("CoffeeLevelTimer", TimeSpan.FromSeconds(0.1), this.MonitorGrinder); } else if (!value && this.CoffeeLevelTimer != null) { StopTimer(this.CoffeeLevelTimer); this.CoffeeLevelTimer = null; } } }
private void MonitorShot() { Task.Run(async() => { // one second of running water completes the shot. using (await this.Lock.AcquireAsync()) { this.WaterLevel -= 1; // turn off the water. this.ShotButton = false; this.ShotTimer = null; } // event callbacks should not be inside the lock otherwise we could get deadlocks. if (this.WaterLevel > 0) { if (this.ShotComplete != null) { this.ShotComplete(this, true); } } else { if (this.WaterEmpty != null) { this.WaterEmpty(this, true); } } }); }
private void MonitorWaterTemperature() { double temp = this.WaterTemperature; if (this.WaterHeaterButton) { // Note: when running in production mode we run forever, and it is fun // to watch the water heat up and cool down. But in test mode this creates // too many async events to explore which makes the test slow. So in test // mode we short circuit this process and jump straight to the boundry conditions. if (!this.RunSlowly && temp < 99) { temp = 99; } // every time interval the temperature increases by 10 degrees up to 100 degrees if (temp < 100) { temp = (int)temp + 10; this.WaterTemperature = temp; if (this.WaterTemperatureChanged != null) { this.WaterTemperatureChanged(this, this.WaterTemperature); } } else { if (this.WaterHot != null) { this.WaterHot(this, true); } } } else { // then it is cooling down to room temperature, more slowly. if (temp > 70) { temp -= 0.1; this.WaterTemperature = temp; } } // start another callback. this.WaterHeaterTimer = new ControlledTimer("WaterHeaterTimer", TimeSpan.FromSeconds(0.1), this.MonitorWaterTemperature); }
public MockSensors(bool runSlowly) { this.Lock = AsyncLock.Create(); this.RunSlowly = runSlowly; this.RandomGenerator = Generator.Create(); // The use of randomness here makes this mock a more interesting test as it will // make sure the coffee machine handles these values correctly. this.WaterLevel = this.RandomGenerator.NextInteger(100); this.HopperLevel = this.RandomGenerator.NextInteger(100); this.WaterHeaterButton = false; this.WaterTemperature = this.RandomGenerator.NextInteger(50) + 30; this.GrinderButton = false; this.PortaFilterCoffeeLevel = 0; this.ShotButton = false; this.DoorOpen = this.RandomGenerator.NextBoolean(5); this.WaterHeaterTimer = new ControlledTimer("WaterHeaterTimer", TimeSpan.FromSeconds(0.1), this.MonitorWaterTemperature); }
internal void OnStopTest() { if (!this.IsInitialized) { // not ready! return; } if (this.HaltTimer != null) { this.HaltTimer.Stop(); this.HaltTimer = null; } // Halt the CoffeeMachine. HaltEvent is async and we must ensure the // CoffeeMachine is really halted before we create a new one because MockSensors // will get confused if two CoffeeMachines are running at the same time. // So we've implemented a terminate handshake here. We send event to the CoffeeMachine // to terminate, and it sends back a HaltedEvent when it really has been halted. this.Log.WriteLine("forcing termination of CoffeeMachine."); Task.Run(this.CoffeeMachine.TerminateAsync); }
public async Task SetPowerSwitchAsync(bool value) { await Task.Delay(1); // NOTE: you should not use C# locks that interact with Tasks (like Task.Run) because // it can result in deadlocks, instead use the Coyote AsyncLock as follows. using (await this.Lock.AcquireAsync()) { this.PowerOn = value; if (!this.PowerOn) { // master power override then also turns everything else off for safety! this.WaterHeaterButton = false; this.GrinderButton = false; this.ShotButton = false; StopTimer(this.CoffeeLevelTimer); this.CoffeeLevelTimer = null; StopTimer(this.ShotTimer); this.ShotTimer = null; } } }
public async Task RunTest() { bool halted = true; while (this.RunForever || this.Iterations <= 1) { this.Log.WriteLine("#################################################################"); // Create a new CoffeeMachine instance string error = null; if (halted) { this.Log.WriteLine("starting new CoffeeMachine iteration {0}.", this.Iterations); this.IsInitialized = false; this.CoffeeMachine = new CoffeeMachine(); halted = false; this.IsInitialized = await this.CoffeeMachine.InitializeAsync(this.Sensors); if (!this.IsInitialized) { error = "init failed"; } } if (error == null) { // Setup a timer to randomly kill the coffee machine. When the timer fires // we will restart the coffee machine and this is testing that the machine can // recover gracefully when that happens. this.HaltTimer = new ControlledTimer("HaltTimer", TimeSpan.FromSeconds(this.RandomGenerator.NextInteger(7) + 1), new Action(this.OnStopTest)); // Request a coffee! var shots = this.RandomGenerator.NextInteger(3) + 1; error = await this.CoffeeMachine.MakeCoffeeAsync(shots); } if (string.Compare(error, "<halted>", StringComparison.OrdinalIgnoreCase) == 0) { // then OnStopTest did it's thing, so it is time to create new coffee machine. this.Log.WriteWarning("CoffeeMachine is halted."); halted = true; } else if (!string.IsNullOrEmpty(error)) { this.Log.WriteWarning("CoffeeMachine reported an error."); this.RunForever = false; // no point trying to make more coffee. this.Iterations = 10; } else { // in this case we let the same CoffeeMachine continue on then. this.Log.WriteLine("CoffeeMachine completed the job."); } this.Iterations++; } // Shutdown the sensors because test is now complete. this.Log.WriteLine("Test is complete, press ENTER to continue..."); await this.Sensors.TerminateAsync(); }
private void MonitorGrinder() { // Every time interval the portafilter fills 10%. // When it's full the grinder turns off automatically, unless the hopper is empty in which case // grinding does nothing! Task.Run(async() => { bool changed = false; bool notifyEmpty = false; bool turnOffGrinder = false; using (await this.Lock.AcquireAsync()) { double hopperLevel = this.HopperLevel; if (hopperLevel > 0) { double level = this.PortaFilterCoffeeLevel; // Note: when running in production mode we run in real time, and it is fun // to watch the portafilter filling up. But in test mode this creates // too many async events to explore which makes the test slow. So in test // mode we short circuit this process and jump straight to the boundry conditions. if (!this.RunSlowly && level < 99) { hopperLevel -= 98 - (int)level; this.Log.WriteLine("### HopperLevel: RunSlowly = {0}, level = {1}", this.RunSlowly, hopperLevel); level = 99; } if (level < 100) { level += 10; this.PortaFilterCoffeeLevel = level; changed = true; if (level >= 100) { turnOffGrinder = true; } } // and the hopper level drops by 0.1 percent hopperLevel -= 1; this.HopperLevel = hopperLevel; } if (this.HopperLevel <= 0) { hopperLevel = 0; notifyEmpty = true; StopTimer(this.CoffeeLevelTimer); this.CoffeeLevelTimer = null; } } if (turnOffGrinder) { // turning off the grinder is automatic await this.OnGrinderButtonChanged(false); } // event callbacks should not be inside the lock otherwise we could get deadlocks. if (notifyEmpty && this.HopperEmpty != null) { this.HopperEmpty(this, true); } if (changed && this.PortaFilterCoffeeLevelChanged != null) { this.PortaFilterCoffeeLevelChanged(this, this.PortaFilterCoffeeLevel); } if (this.HopperLevel <= 0 && this.HopperEmpty != null) { this.HopperEmpty(this, true); } // start another callback. this.CoffeeLevelTimer = new ControlledTimer("WaterHeaterTimer", TimeSpan.FromSeconds(0.1), this.MonitorGrinder); }); }