And, if we've built our rotors correctly, we get clean passes.
/** * Make sure each rotor's mapping is coherent * @return null * @dataProvider fiveRotorsDataProvider */ function testCoherentRotorIdentities(Enigma_Rotor_Abstract $rotor) { $rotor=new Enigma_Rotor_I; $mapping=$rotor->getCoreMapping(); foreach($mapping as $i=>$o) { $this->assertSame( 1, 'Input "'.$i.'" must be upper-case letter' ); $this->assertSame( 1, 'Output must be upper-case letter' ); $inputsSeen[$i]=true; $outputsSeen[$o]=true; } } function fiveRotorsDataProvider() { )); }
hasPegAtPosition($position) with a Boolean answer. Since all rotors have this behaviour, we put it in the Enigma_Rotor_Abstract class, and, as with the core mapping, store the position as a private property. (For our test rotors, Enigma_Rotor_Configurable, we'll assume a default position of "A" for now, until we need to do something smarter).We can similarly give the slots the ability to check for turnover, with a knockOnDue() function in Enigma_Rotor_Slot. To test this, we'll need to load a slot with a known rotor in a known position.
/** * Do we go "tick" here? * @return bool */ public function hasPegAtPosition($position) { } and Enigma_Rotor_I gains and so on for the other rotors. We can test these wheels in RotorTest with:
and allow ourselves to pass them by adding:
/** * Check that slots know when to trigger a "Knock-On" * * @dataProvider fiveRotorsTurnoverDataProvider * @param $rotor Enigma_Rotor_Abstract * @param $offsetTurnoverDue * @param $offsetNoTurnover * @return null */ function testSlotKnockOnDue(Enigma_Rotor_Abstract $rotor, $offsetTurnoverDue,$offsetNoTurnover) { $slot=new Enigma_RotorSlot(); $slot->loadRotor($rotor); $slot->setRotorOffset($offsetTurnoverDue); $this->assertTrue($slot->turnoverDue()); $slot->setRotorOffset ($offsetNoTurnover); $this->assertFalse($slot->turnoverDue()); } function fiveRotorsTurnoverDataProvider() { )); }
to the Enigma_RotorSlot class.
public function turnoverDue() { return($this->_rotor->hasPegAtPosition($this->getRotorOffsetAsCharacter())); } public function getRotorOffsetAsCharacter () { return $this->_alphabetPositionToCharacter($this->getRotorOffset()); }
How do we test this though? If it works correctly, it'll cause a turnover in the next rotor up, but we'll have to set and then read this rotor's position – we're testing an indirect effect, which is slightly risky.
public function incrementRotorOffset() { $this->_rotorOffset++; if($this->_rotorOffset>26) { $this->_rotorOffset=1; } //If we're in turnover position, notify the next slot if($this->turnoverDue()) { $this->notifyTurnoverObserver(); } } public function registerTurnoverObserver(Enigma_RotorSlot $observer) { $this->_turnoverObserver=$observer; } public function notifyTurnoverObserver() { if($this->_turnoverObserver) { $this->_turnoverObserver->incrementRotorOffset(); } }
That looks promising (and works!), but how could we ask "has slot[1]'s rotor had incrementRotorOffset called once?"
function testNextRotorKnockOnSimple() { //Three slots, as per a basic enigma device //each slot is observed by next one up $rotorSlots[1]->registerTurnoverObserver($rotorSlots[2]); $rotorSlots[0]->registerTurnoverObserver($rotorSlots[1]); //load the slots $rotorSlots[0]->loadRotor(new Enigma_Rotor_I); $rotorSlots[1]->loadRotor(new Enigma_Rotor_II); $rotorSlots[2]->loadRotor(new Enigma_Rotor_III); //put slot 0's wheel slightly before its turnover position $rotorSlots[0]->setRotorOffset('P'); //put other wheels in known position $rotorSlots[1]->setRotorOffset('A'); $rotorSlots[2]->setRotorOffset('A'); //Check 0 and 1 are in expected positions: $this->assertSame('P',$rotorSlots[0]->getRotorOffsetAsCharacter()); $this->assertSame('A',$rotorSlots[1]->getRotorOffsetAsCharacter()); $this->assertSame('A',$rotorSlots[2]->getRotorOffsetAsCharacter()); //Turn over slot 0 $rotorSlots[0]->incrementRotorOffset(); //0 moves, 1 stays still $this->assertSame('Q',$rotorSlots[0]->getRotorOffsetAsCharacter()); $this->assertSame('A',$rotorSlots[1]->getRotorOffsetAsCharacter()); $this->assertSame('A',$rotorSlots[2]->getRotorOffsetAsCharacter()); //Turn over slot 0 $rotorSlots[0]->incrementRotorOffset(); //Bottom 2 move this time $this->assertSame('R',$rotorSlots[0]->getRotorOffsetAsCharacter()); $this->assertSame('B',$rotorSlots[1]->getRotorOffsetAsCharacter()); $this->assertSame('A',$rotorSlots[2]->getRotorOffsetAsCharacter()); }
Our first line says "Create a fake Enigma_RotorSlot with incrementRotorOffsets() and setRotorOffset() functions". The second says "This class' incrementRotorOffset() function must be called once in the current test function".
$mockRotor->expects($this->once())->method('incrementRotorOffset');
This is a simple application of this capability (and one we could have been able to work around), but shows how it can be used. However, it works, and with that we've tested all the mechanical functionality of the Enigma system.
function testNextRotorKnockOnMock() { $mockRotor=$this->getMock( 'Enigma_RotorSlot', ); $mockRotor->expects($this->once())->method('incrementRotorOffset'); //each slot is observed by next one up $rotorSlots[1]->registerTurnoverObserver($rotorSlots[2]); $rotorSlots[0]->registerTurnoverObserver($rotorSlots[1]); //load the slots $rotorSlots[0]->loadRotor(new Enigma_Rotor_I); $rotorSlots[1]->loadRotor(new Enigma_Rotor_II); $rotorSlots[2]->loadRotor(new Enigma_Rotor_III); //put slot 0's wheel slightly before its turnover position $rotorSlots[0]->setRotorOffset('P'); //put other wheels in known position $rotorSlots[1]->setRotorOffset('A'); $rotorSlots[2]->setRotorOffset('A'); //Turn over slot 0 $rotorSlots[0]->incrementRotorOffset(); $rotorSlots[0]->incrementRotorOffset(); }
All content copyright Richard George (richard@phase.org), 2009-2010
Sponsored links to recommended books: