Up to Main Index                               Up to Journal for May, 2019

                      JOURNAL FOR SATURDAY 4TH MAY, 2019
______________________________________________________________________________

SUBJECT: With time comes wisdom…
   DATE: Sat  4 May 15:30:22 BST 2019

I’m currently working on tests for the reimplemented GET and DROP commands. As
a result a few interesting things have occurred. For some people reading this
mocking time will be nothing new, for others it might be insightful. Either
way I know people prefer it when I talk about actual coding and the decisions,
good and bad, that I make.

Since adding the tests for quota a while ago the time taken to run all of the
tests for WolfMUD has become painfully slow — from under 2 seconds to over 30
seconds. The reason for this is that the tests for quota use time.Sleep to
test for expired quota after a period of time. I knew when I wrote the tests
that using time.Sleep was a bad idea that was going to come back and bite me
in the arse. It’s made me want to avoid running the tests, or at least the
full suite of tests, whenever I can get away with it. This is not a good
situation :(

I could update the quota tests to only run if short mode is not requested.
However, that would be just as bad — I’d always be tempted to run the tests
with the ‘-short’ option. What I really need to do is fix the quota tests so
that they can run at full speed without actually sleeping.

First step to a solution was to create a TimeSource in the quota package. I
went with a simple type as it was all that was needed[1] in this instance:


  type TimeSource func() time.Time


I then modified NewQuota to expect a TimeSource as a parameter, which is then
stored in a quota struct as Now:


  func NewQuota(ts TimeSource) *quota { … }

  type quota struct {
    Now      TimeSource      // Time source for current time
                             // More fields follow, but not shown here…
  }


But what do we actually pass as a TimeSource? Well the type definition of
TimeSource says it is a function that takes no parameters and returns a
time.Time value. How about:


  q := NewQuota(time.Now)


That works, and we just have to make a few tweaks in quota.go to change from
using the time package directly to using the TimeSource. That is, instead of
using time.Now().UnixNano() we use q.Now().UnixNano() to get the current time.

How does that help us with the testing problem? Now that NewQuota is expecting
a TimeSource we can mock an alternative implementation for use when testing.

These two lines are the implementation I came up with:


  var fakeTime = time.Date(2019, time.May, 1, 13, 50, 55, 1, time.UTC)

  func now() time.Time { return fakeTime }


When initialised fakeTime is set to a fixed, known date and time. This allows
for the tests to be reproduceable.

When testing the now function is then passed to NewQuota as the TimeSource:


  q := NewQuota(now)


This is only a small change, but we now have a TimeSource that we can manually
adjust with nano-second precision when testing. To adjust the fake time the
normal time methods, such as Add, can be used. As an example we can simulate
an IP address that is trying to connect at 1 nano-second intervals by manually
advancing time:


  for x := 1; x <= maxQuota; x++ {
    fakeTime = fakeTime.Add(time.Nanosecond)
    q.Quota(localHost)
  }


Another example is advancing the time to trigger a sweep of the quota cache for
idle/inactive entries:


  fakeTime = fakeTime.Add(config.Quota.Window + time.Nanosecond)
  q.CacheSweep()


Fast forwarding, rewinding and suspending time are now all possible during
testing. By manipulating the fake time, instead of calling time.Sleep, the
quota tests in the comms package can now run at full speed:


  BEFORE: ok  code.wolfmud.org/WolfMUD.git/comms  36.028s
   AFTER: ok  code.wolfmud.org/WolfMUD.git/comms   0.018s


With these simple changes the whole test suite for WolfMUD is running in under
2 seconds once again :) No more excuses for not running them!

Working on tests for GET and DROP I wanted to try out an idea for ‘scripting’
commands for testing using the .wrj file format. This is an example using the
WHICH command:


  %%
        Ref: L1
       Name: Test room A
  Inventory: O1 O2

  This is a room for testing. A large letter 'A' is painted on the wall.
  %%
        Ref: O1
       Name: a small green ball
    Aliases: +SMALL +GREEN BALL

  This is a small, green rubber ball.
  %%
        Ref: O2
       Name: a large green ball
    Aliases: +LARGE +GREEN BALL

  This is a large, green rubber ball.
  %%
  a>which
  a<[INFO]You look around for nothing in particular.
  %%
  a>which ball
  a<[GOOD]You see a small green ball here.
  o<[INFO]The actor looks around taking note of various things.
  %%
  a>which all ball
  a<[GOOD]You see a small green ball here.
  a<[GOOD]You see a large green ball here.
  o<[INFO]The actor looks around taking note of various things.
  %%
  a>which 0 ball
  a<[BAD]You don't have that many 'BALL' here.
  o<[INFO]The actor looks around for something.
  %%


The idea is that this would setup a test zone. Here a single location with two
items. Then following that are the test cases, as free text sections. Input is
represented by ‘>’ lines and expected output ‘<’ — think I/O redirection. The
letter before the ‘<’ and ‘>’ represent who the text is for/from. The ‘a’ is
for the actor, ‘p’ for participant and ‘o’ for observers. Output can also
include colour commands such as ‘[RED]’ or ‘[BAD]’. A regular Go _test.go file
would then load the file, setup the test zone and run through the test cases.
This is still a work in progress and I’m not sure if I will run with it or not
yet. The alternative is more test files like the current cmd/which_test.go
file.

One thing I have not worked out yet is how to specify observers in other
locations. For example, when testing the SNEEZE command. Maybe something like:


  %%
  a>SNEEZE
  a<[GOOD]You sneeze. Aaahhhccchhhooo!
  o<[INFO]You see Diddymus sneeze.
  @L2<[INFO]You hear a loud sneeze.
  @L3<[INFO]You hear a sneeze.


Here we have the actor (a), any observers at the actor’s location (o), any
observers at location L2 (@L2) and at location L3 (@L3). Maybe the a, o, @L1
and @L2 should be references of specific test players placed into the test
zone? Maybe even a comma separated list of references where the test players
all receive the same message? As I said it’s still a work in progress.

For some reason the Vim syntax file, that I hacked together ages ago and I’ve
always used to highlight .wrj files, started throwing a hissy fit while
working on this. It’s always been temperamental and in need of fixing, which
is why I haven’t made it publicly available. A quiet afternoon later and I’d
rewritten it from scratch. The syntax file has some long lines that are too
wide for this page, but you can see and/or save a copy here: wrj.vim

Obligatory screenshot[2] editing data/zones/zinara.wrj: vim-wrj.png

I’m not sure if it’s useful making my Vim syntax file available or not. Not
everybody uses Vim, but I guess it might help when writing something similar
for other editors? Contributions for other editors are most welcome!

Changes for the faster quota tests are now available on the public dev branch.

--
Diddymus

  [1] If I was going to mock something more complex in testing I would have
      used an interface.

  [2] If you download and use the syntax file the exact colours you see will
      depend on your colour scheme.


  Up to Main Index                               Up to Journal for May, 2019