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