Clock Faking Test Resource

Keith Hodges keith_hodges at
Sat Jun 16 08:19:00 UTC 2007

Dear Ralph,

you asked about a clock faking test resource.

A while ago I developed a ClassClonerTestResource, this allows you to 
take a current class which is normally a singleton that is in use, e.g. 
DateAndTime, and to clone it especially for testing purposes so as to be 
able to test the clone separately. This would allow me to set my clock 
under test to some crucial rollover points and test its behaviour.

I developed this especially to test bug fix 474 on mantis. [ Edgar/Ralph 
- here is a fix with tests for you!]

However what this doesn't do is replace DateAndTime, so if you want to 
run code which calls "DateAndTime now" and have it do something clever 
like run last week, or run backwards it will not work.

However you raise an interesting problem which I think it may be worth 
solving for everyone, and I think that as a result of my recent 
discovery of the amazing ProcessLocalVariables I may have an answer.

At present ProcessLocalVariables only support value and #value:  As it 
happens I added a #doesNotUnderstand: handler as an experiment in 
allowing gjallar Sessions to spoof as MagmaSessions for Magma. At the 
time I thought that this would have some interesting possibilities. For 
us it means that a ProcessLocalVariable can spoof a class for us within 
a defined scope (process or block).

ProcessSpecificVariable-doesNotUnderstand: aMessage

    ^ aMessage sendTo: self value

So I made a DateAndTimeWarp a subclass of DynamicVariable (a variant of 
the ProcessLocalVariable which has block scope as well as process scope) 
and set the default to return DateAndTime.  We now have a psuedo 
DateAndTime "class" which is pluggable on a per process basis. 

If we swap DateAndTime in the system dictionary for DateAndTimeWarp, it 
works! (i.e. nothing breaks!!!)


(DateAndTime respondsTo: #installImposter) ifTrue: [ self error: 
'DateAndTime is already spoofed' ].

    DateAndTimeReal := Smalltalk at: #DateAndTime.
    Smalltalk at: #DateAndTime put: self.

Now we can substitute our clone with some modifications. The fixed 
DateAndTime implementation available on mantis via "Installer mantis 
fixBug: 474." (tested in 3.8 and Gjallar0,4) has already been refactored 
to refer to its ClassVar "ClockProvider" for all clock references. 
(default setting is class Time) So all we need to do is replace 
ClockProvider, we will replace it with an instance of DateAndTimeWarp,

DateAndTimeWarp-class-warpedClockBeginAt: t speed: s

    | newClock newSystemClockProvider |

    newSystemClockProvider := self new setStart: t speed: s.

    newClock := ClassClonerTestResource cloneOf: DateAndTimeReal.
    newClock classPool at: #ClockProvider put: newSystemClockProvider.

    clock initializeOffsets.

    ^ newClock

We instantiate our new clock provider with a start time, (nil for now), 
and a speed. The new clock provider saves some offsets, and 
re-implements #totalSeconds, #millisecondClockValue and 
#secondsWhenClockTicks (this method waits until the clock ticks and is 
used for syncing the seconds and milliseconds clocks together.

The new clock is substituted for the default clock with the following code:

DateAndTimeWarp-class-beginAt: start warpSpeed: s during: aBlock

    ^ self value: (self warpedClockBeginAt: start speed: s) during: aBlock

To Test:

 DateAndTime beginAt: DateAndTime yesterday warpSpeed: 3 during: [

    10 timesRepeat: [
        Transcript cr; show: DateAndTime now.
        (Delay forSeconds: 1) wait.


I thought that was pretty impressive stuff. I am well impressed with 
these process local variables!

Also speed wise:

au natural, DateAndTime nowBenchmark. '6.84 µs' (I did improve 
DateAndTime now by a factor of 50-80 in fix 474) The equivalent 
benchmark spoofed using process local variable DynamicVariable, 
benchmark gives: 29.09 µs, i.e. not slow.

This code is available in Gjallar monticello repository 
'' in the ProcessSpecific package.



More information about the Squeak-dev mailing list