Avoiding "FAILURE IN SERVER" when writing UnitTests

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.

Hi,

Maybe you could run the test without booting the server? Since the test isn’t checking for any results from the server I guess that would work.

Thanks – it would work with Pbind.play (or Pdef.play), but not with the Ndef.source set to a Pdef, as I have in my live-coding framework. The Ndef is the one that “plays” the Pdef, and it requires the Server to be running…

For example, the following code (run by hand) won’t produce any events when the Server isn’t booted:

Ndef(\test).play;
Pdef(\test, Pbind(\degree, 1).trace);
Ndef(\test).source = Pdef(\test);