Clock Faking Test Resource
keith_hodges at yahoo.co.uk
Sat Jun 16 08:19:00 UTC 2007
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).
^ 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.
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
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
'http://mc.gjallar.se' in the ProcessSpecific package.
More information about the Squeak-dev