Hi there, I’ve always been writing unit tests for my live-coding pattern-generation tools. Until now, I’ve been testing the output (Patterns) directly, by doing pat.asStream.nextN
and comparing with an expected list of Events.
However, in some cases, there may be differences between actually playing them and just getting a list of events. (For example, some Patterns like Ptime
return different results when actually playing on a clock, so if you use those, you need to play
.)
So for some of my tests, now I’d like to actually play the patterns (these are Pdefs, running as the source for Ndefs) and check the results that way. So in a new test method, I boot up the Server and actually play patterns. I set the clock to run fast (e.g. tempo=100), so the tests don’t take “real time” to run, but I often get unpredictable “Group/Node not found” messages from the server. Of course, if I slow the tempo down, things work much more smoothly.
The tests seem to run okay and I always get the expected list of Events from my “finish” function, but I was wondering if there is a workflow that would reliably get rid of the Node/Group error messages. I’ve tried inserting wait
in various places, and it’s hit-or-miss (doesn’t reliably eliminate the messages). It seems to be related to booting the server, though UnitTest.bootServer
shouldn’t return until it’s fully booted with the ServerTree
initialized… And even if I put a long wait afterwards (several seconds), it doesn’t always avoid these errors. On the other hand, if the Server is already running before my test is run, I generally don’t see the errors. Any tips on the correct way to clean this up?
Here is a simplified UnitTest class that shows the effect (especially if you run TestPlayEvents.run
when the server is not running).
TestPlayEvents : UnitTest {
prPlayAndGetEvents { arg pattern, numDesiredEvents = 4, desiredKeys = #[\degree, \dur, \beat], maxTime = 2.0;
var events = [];
var finishFunc = Pfunc{ |ev|
// We set e.parent_(nil) so we lose the extra stuff fromm Event.default and
// only get a simple Event with the keys we want. (Alternatively could do ().putAll(e))
events = events.add(ev.select{ |val,key| desiredKeys.includes(key) }.parent_(nil));
if (events.size < numDesiredEvents) {
true
} {
// When we've got enough events, return nil so the pattern stops,
// because otherwise we'll get overwhelmed (we've set the clock
// to run very fast)
nil
}
};
Pdef.clear;
Ndef.clear;
Pdef(\test, Pbind(\beat, Ptime(), \finish, finishFunc) <> pattern);
Ndef(\test, Pdef(\test)).play;
this.wait({ events.size >= numDesiredEvents }, "waiting for all Events", maxTime);
// Not sure why the Pfunc adds extra events after the end, so
// just truncate the list to the desired size...
^events.keep(numDesiredEvents)
}
test_playing {
TempoClock.default.tempo = 100;
this.bootServer;
1.wait;
{
var expected = [
(degree: 0, dur: 0.25, beat: 0.0),
(degree: 1, dur: 0.25, beat: 0.25),
(degree: 2, dur: 0.25, beat: 0.5),
(degree: 3, dur: 0.25, beat: 0.75),
(degree: 5, dur: 0.25, beat: 1.0),
(degree: 1, dur: 0.25, beat: 1.25),
(degree: 2, dur: 0.25, beat: 1.5),
(degree: 3, dur: 0.25, beat: 1.75),
(degree: 0, dur: 0.25, beat: 2.0),
];
var events = this.prPlayAndGetEvents(
Pbind(\degree, Ppatlace([Pseq([0,5], inf), 1, 2, 3], inf), \dur, 0.25),
expected.size);
this.assertEquals(events, expected, "simple alternation");
}.value;
}
}
Once that class is compiled, run:
TestPlayEvents.run
I generally see something like this:
...
SC_AudioDriver: sample rate = 44100.000000, driver's block size = 64
SuperCollider 3 server ready.
Requested notification messages from server 'localhost'
localhost: server process's maxLogins (1) matches with my options.
localhost: keeping clientID (0) as confirmed by server process.
Safety('localhost') is running, using 'safeClip_2'.
Shared memory server interface initialized
FAILURE IN SERVER /g_new duplicate node ID
PASS: a TestPlayEvents: test_playing - simple alternation
Is:
[ ( 'degree': 0, 'dur': 0.25, 'beat': 0.0 ), ( 'degree': 1, 'dur': 0.25, 'beat': 0.25 ), ( 'degree': 2, 'dur': 0.25, 'beat': 0.5 ), ( 'degree': 3, 'dur': 0.25, 'beat': 0.75 ), ( 'degree': 5, 'dur': 0.25, 'beat': 1.0 ), ( 'degree': 1, 'dur': 0.25, 'beat': 1.25 ), ( 'degree': 2, 'dur': 0.25, 'beat': 1.5 ), ( 'degree': 3, 'dur': 0.25, 'beat': 1.75 ), ( 'degree': 0, 'dur': 0.25, 'beat': 2.0 ) ]
Should be:
[ ( 'degree': 0, 'beat': 0.0, 'dur': 0.25 ), ( 'degree': 1, 'beat': 0.25, 'dur': 0.25 ), ( 'degree': 2, 'beat': 0.5, 'dur': 0.25 ), ( 'degree': 3, 'beat': 0.75, 'dur': 0.25 ), ( 'degree': 5, 'beat': 1.0, 'dur': 0.25 ), ( 'degree': 1, 'beat': 1.25, 'dur': 0.25 ), ( 'degree': 2, 'beat': 1.5, 'dur': 0.25 ), ( 'degree': 3, 'beat': 1.75, 'dur': 0.25 ), ( 'degree': 0, 'beat': 2.0, 'dur': 0.25 ) ]
UNIT TESTS FOR 'TestPlayEvents' COMPLETED
There were no failures
FAILURE IN SERVER /s_new Group 1000 not found
FAILURE IN SERVER /n_set Node 1004 not found
FAILURE IN SERVER /s_new Group 1000 not found
FAILURE IN SERVER /n_set Node 1005 not found
FAILURE IN SERVER /s_new Group 1000 not found
FAILURE IN SERVER /n_set Node 1006 not found
FAILURE IN SERVER /s_new Group 1000 not found
FAILURE IN SERVER /n_set Node 1007 not found
FAILURE IN SERVER /s_new Group 1000 not found
FAILURE IN SERVER /n_set Node 1008 not found
FAILURE IN SERVER /s_new Group 1000 not found
FAILURE IN SERVER /n_set Node 1009 not found
FAILURE IN SERVER /s_new Group 1000 not found
FAILURE IN SERVER /n_set Node 1010 not found
FAILURE IN SERVER /s_new Group 1000 not found
FAILURE IN SERVER /n_set Node 1011 not found
Thanks,
Glen.
P.S. This is with SC 3.11.0 on Windows 10.