1+ classdef (SharedTestFixtures = { % add 'fixtures' folder as test fixture
2+ matlab .unittest .fixtures .PathFixture(' fixtures' ),...
3+ matlab .unittest .fixtures .PathFixture([' fixtures' filesep ' util' ])}) ...
4+ bindMpepServer_test < matlab .unittest .TestCase & matlab .mock .TestCase
5+
6+ properties (SetAccess = protected )
7+ % Timeline mock object
8+ Timeline
9+ % Timeline behaviour object
10+ Behaviour
11+ % An experiment reference for the test
12+ Ref
13+ % Default ports used by bindMpepServer
14+ Ports = [9999 , 1001 ]
15+ end
16+
17+ methods (TestClassSetup )
18+ function setTestFlag(testCase )
19+ % SETTESTFLAG Set test flag
20+ % Sets global INTEST flag to true and adds teardown. Also creates a
21+ % dummy expRef for tests.
22+ %
23+ % TODO Make into shared fixture
24+
25+ % Set INTEST flag
26+ assert(endsWith(which(' dat.paths' ),...
27+ fullfile(' tests' , ' fixtures' , ' +dat' , ' paths.m' )));
28+ setTestFlag(true )
29+ testCase .addTeardown(@setTestFlag , false )
30+ % Set test expRef
31+ testCase.Ref = dat .constructExpRef(' test' , now , 1 );
32+ end
33+ end
34+
35+ methods (TestMethodSetup )
36+ function setMockRig(testCase )
37+ % SETMOCKRIG Inject mock rig with shadowed hw.devices
38+ % 1. Create mock timeline
39+ % 2. Set the mock rig object to be returned on calls to hw.devices
40+ % 3. Add teardowns
41+ %
42+ % See also mockRig, KbQueueCheck
43+
44+ % Create fresh Timeline mock
45+ [testCase .Timeline , testCase .Behaviour ] = createMock(testCase , ...
46+ ' AddedProperties' , properties(hw .Timeline )' , ...
47+ ' AddedMethods' , methods(hw .Timeline )' );
48+
49+ % Inject our mock via calls to hw.devices
50+ rig.timeline = testCase .Timeline ;
51+ hw .devices(' testRig' , false , rig );
52+
53+ % Clear mock histories just to be safe
54+ testCase .addTeardown(@testCase .clearMockHistory , testCase .Timeline );
55+ testCase .addTeardown(@clear , ' KbQueueCheck' , ' pnet' , ' devices' )
56+ end
57+ end
58+
59+ methods (Test )
60+ function test_bindMpepListener(testCase )
61+ % Test binding of sockets and returning of tls object
62+ % NB Actually calls bindMpepServer
63+ port = randi(10000 );
64+ [T , tls ] = evalc([' tl.bindMpepServer(' , num2str(port ), ' )' ]);
65+ % Check log
66+ testCase .verifyMatches(T , ' Bound UDP sockets' , ...
67+ ' failed to log socket bind' )
68+ % Check returned fields
69+ expected = {' close' ; ' process' ; ' listen' ; ' AlyxInstance' ; ' tlObj' };
70+ testCase .verifyEqual(fieldnames(tls ), expected , ...
71+ ' Unexpected structure returned' )
72+ % Check funciton handles
73+ actual = structfun(@(f )isa(f , ' function_handle' ), tls );
74+ testCase .verifyEqual(actual , [true(3 ,1 ); false(2 ,1 )])
75+ % Check Alyx instance and Timeline objects set
76+ testCase .verifyTrue(isa(tls .AlyxInstance , ' Alyx' ), ...
77+ ' Failed to create Alyx instance' )
78+ testCase .verifyTrue(isequal(tls .tlObj , testCase .Timeline ), ...
79+ ' Failed to set Timeline' )
80+ % Check socket opened on correct port
81+ history = pnet(' gethistory' );
82+ testCase .verifyEqual(history{1 }, {' udpsocket' , port }, ...
83+ ' Failed to open socket on specified port' )
84+ end
85+
86+ function test_close(testCase )
87+ % Test the close callback
88+ tls = tl .bindMpepServer ; % #ok<NASGU> % Return tls object
89+ ports = testCase .Ports ; % Default ports opened
90+ arrayfun(@(s ) pnet(' setoutput' , s , ' close' , []), ports ); % Set output
91+ T = evalc(' tls.close()' ); % Callback
92+ % Check log
93+ testCase .verifyMatches(T , ' Unbinding' , ' failed to log close' )
94+ % Check close called on each socket
95+ history = pnet(' gethistory' ); % Get pnet call history
96+ correct = cellfun(@(a ) strcmp(a{2 }, ' close' ), history(end - 1 : end ));
97+ testCase .verifyTrue(all(correct ), ' Failed to close sockets' )
98+ end
99+
100+ function test_process(testCase )
101+ % Test process callback
102+ import matlab .unittest .constraints .IsOfClass
103+ import matlab .mock .constraints .Occurred
104+ [subject , series , seq ] = dat .parseExpRef(testCase .Ref );
105+
106+ tls = tl .bindMpepServer ; % Return tls object
107+ ports = testCase .Ports ; % Default ports opened
108+ arrayfun(@(s ) pnet(' setoutput' , s , ' readpacket' , 1000 ), ports ); % Set output
109+ pnet(' setoutput' , ports(2 ), ' gethost' , {randi(99 ,1 ,4 ), 88 }); % Set output
110+
111+ % Set messages
112+ % Stringify Alyx instance
113+ ai = Alyx .parseAlyxInstance(testCase .Ref , Alyx(' user' ,' ' ));
114+ % Function for constructing message strings
115+ str = @(cmd ) sprintf(' %s %s %s %d %s ' , cmd , subject , ...
116+ datestr(series , ' yyyymmdd' ), seq , iff(strcmp(cmd ,' alyx' ),ai ,' ' ));
117+ % Commands
118+ cmd = {' alyx' , ' expstart' , ' expend' , ' expinterupt' };
119+ % Set output for 'read'
120+ pnet(' setoutput' , ports(2 ), ' read' , sequence(mapToCell(str , cmd )));
121+ % Trigger reads
122+ arrayfun(@(~) tls .process(), 1 : length(cmd ))
123+
124+ % Test Timeline interactions
125+ timeline = testCase .Behaviour ;
126+ testCase .verifyThat([...
127+ timeline .start(testCase .Ref , IsOfClass(? Alyx )),... % expstart
128+ withAnyInputs(timeline .record ), ... % "
129+ withAnyInputs(timeline .stop ), ... % expstop
130+ withAnyInputs(timeline .stop )], ... % expinterupt
131+ Occurred(' RespectingOrder' , true ))
132+
133+ % Retrieve mock history for Timeline
134+ history = testCase .getMockHistory(testCase .Timeline );
135+ % Find inputs to start method
136+ f = @(method ) @(a ) strcmp(a .Name , method );
137+ actual = fun .filter(f(' start' ), history ).Inputs{end };
138+ % Check AlyxInstance updated with the one we passed in above
139+ testCase .verifyEqual(actual .User , ' user' , ' Failed to update AlyxInstance' )
140+
141+ % Get pnet history
142+ history = pnet(' gethistory' );
143+ % Calls to write should equal the number of messages read
144+ writeCalls = cellfun(@(C ) strcmp(C{2 }, ' write' ), history );
145+ testCase .verifyEqual(sum(writeCalls ), length(cmd ), ' Failed echo messages' )
146+
147+ % Test process fails
148+ testCase .throwExceptionWhen(withAnyInputs(timeline .start ), ...
149+ MException(' Timeline:error' , ' Error during experiment.' ));
150+ % Clear pnet history
151+ pnet(' clearhistory' );
152+ % Set output for 'read'
153+ pnet(' setoutput' , ports(2 ), ' read' , str(' expstart' ));
154+ % Trigger pnet read; use evalc to supress output
155+ evalc(' tls.process()' );
156+
157+ % Check message not echoed after error
158+ history = pnet(' gethistory' );
159+ % Calls to write should equal the number of messages read
160+ writeCalls = cellfun(@(C ) strcmp(C{2 }, ' write' ), history );
161+ testCase .verifyFalse(any(writeCalls ), ' Unexpected message echo' )
162+ end
163+
164+ function test_listen(testCase )
165+ % TODO
166+ end
167+ end
168+
169+ end
0 commit comments