-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathExecuteExamplesInFunction.m
More file actions
230 lines (206 loc) · 7.06 KB
/
ExecuteExamplesInFunction.m
File metadata and controls
230 lines (206 loc) · 7.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
function status = ExecuteExamplesInFunction(theFunction,varargin)
% Open file, read it, parse execute any examples
%
% Syntax:
% status = ExecuteExamplesInFunction(theFunction)
%
% Description:
% Examples are enclosed in block quotes, following a comment line that
% starts exactly with "% Examples:". By enforcing the exact form, we
% maxmimize the odds that we find only real examples.
%
% Once there is a line that starts exactly with "% Examples:", any
% subsequent text in block quotes is treated as example code, until an
% ending block quote "%}" is followed by a blank line. This means that
% the examples should follow contiguously after the examples line, and
% prevents us from running actual block comments as examples.
%
% There is a check that prevents this function from running its own
% examples, to prevent an infinite recurse. Thus, if run on itself, this
% function reports a status of 0, even though there are examples in the
% source that may be run manually.
%
% Some examples are not good for autoexecute (for example, some require
% user input and that could be annoying). If text of the form
% "% ETTPSkip" appears in an example, it is not run.
%
% Inputs:
% theFunction - String. Name of the function file (with the .m at the
% end).
%
% Outputs:
% status - What happened?
% -1: Found examples but at least one crashed, or other
% error such as unmatched block comment open and
% close.
% 0: No examples found
% N: With N > 0. Number of examples run successfully,
% with none failing.
%
% Optional key/value pairs:
% 'verbose' - Boolean. Be verbose? Default false
% 'findfunction' Boolean. Rather than take the full path to the
% desired function, look for it on the path. Default
% false.
% 'printexampletext' Boolean. Print out string to be evaluated for each
% example. Can be useful for debugging which example
% is failing and why. Default false.
% 'closefigs' Close figures after running each example. Default
% true.
%
% Examples are provided in the code.
%
% See also:
% ExecuteTextInScript, RunExamples
%
% History
% 01/16/18 dhb Wasting time on a train.
% 01/20/18 dhb Add ability to look for funcitons
% on the path, via key/value pair.
% 02/29/20 dhb Add closefigs paramter.
% Examples:
%{
% Should execute both examples successfully
ExecuteExamplesInFunction('ExecuteTextInScript.m')
%}
%{
% Should report that there are no examples in itself, to avoid
% recursion
ExecuteExamplesInFunction('ExecuteExamplesInFunction.m')
%}
%{
% Try running examples in a function that is found on the path.
curDir = pwd;
cd(userpath);
ExecuteExamplesInFunction('TestFunctionWithExamples.m','findfunction',true);
cd(curDir);
%}
%% Parse input
%
% This converts keys and (I think) string values to lower case without spaces,
% if you have ieParamFormat on your path. Matches ISETBio/ISETCam
% conventions, but not available if you are not living in the ISET universe.
if (exist('ieParamFormat','file'))
varargin = ieParamFormat(varargin);
end
p = inputParser;
p.CaseSensitive = false;
p.addParameter('verbose',false,@islogical);
p.addParameter('findfunction',false,@islogical);
p.addParameter('printexampletext',false,@islogical);
p.addParameter('closefigs',true,@islogical);
p.parse(varargin{:});
%% Try to find function on path, if that is specified.
if (p.Results.findfunction)
theFunction = which(theFunction);
if (isempty(theFunction))
error('Could not find desired function on path.')
end
end
% Open file
theFileH = fopen(theFunction,'r');
theText = {char(fread(theFileH,'uint8=>char')')};
fclose(theFileH);
% Say hello
if (p.Results.verbose)
fprintf('Looking for and running examples in %s\n',theFunction);
end
if (strcmp(theFunction,[mfilename '.m']))
if (p.Results.verbose)
fprintf('Not running on self to prevent recursion');
end
status = 0;
return;
end
% Look for examples in file.
ind1 = strfind(theText{1},'% Examples:');
ind2 = strfind(theText{1},'% Example:');
ind = [ind1 ind2];
if (isempty(ind))
if (p.Results.verbose)
fprintf('\tNo comment line starting with "%% Examples:" or "%% Example:" in file\n');
end
status = 0;
return;
end
candidateText = theText{1}(ind(1)+9:end);
startIndices = strfind(candidateText,'%{');
endIndices = strfind(candidateText,'%}');
if (isempty(startIndices))
if (p.Results.verbose)
fprintf('\tNo block comment starts in file\n');
end
status = 0;
return;
end
if (length(startIndices) ~= length(endIndices))
if (p.Results.verbose)
fprintf('\tNumber of block comment ends does not match number of starts.\n');
end
status = -1;
return;
end
nExamplesOK = 0;
status = 0;
for bb = 1:length(startIndices)
% Get this example and run. If it throws an error, return with
% status -1. Otherwise, increment number of successful examples
% counter, and status.
exampleText = candidateText(startIndices(bb)+3:endIndices(bb)-1);
% Check for skip text in example. Don't execute if it is there
skipTest = strfind(exampleText,'% ETTBSkip');
if (~isempty(skipTest)) %#ok<*STREMP>
if (p.Results.verbose)
fprintf('\tExample %d contains ''%% ETTBSkip'' - skipping.\n',bb);
end
% Have a live example. Run it.
else
% Dump example text if asked
if (p.Results.printexampletext)
fprintf('Example text:\n');
disp(exampleText) %#ok<NOPRT>
end
% Do the eval inside a function so workspace is clean and nothing here
% gets clobbered.
tempStatus = EvalClean(exampleText,p.Results.closefigs);
if (tempStatus == 0)
if (p.Results.verbose)
fprintf('\tExample %d success\n',bb);
end
nExamplesOK = nExamplesOK+1;
status = nExamplesOK;
else
status = -1;
if (p.Results.verbose)
fprintf('\tExample %d failed\n',bb);
end
return;
end
end
% If this is not the last block comment, check whether the next one is
% contiguous. If not, we're done with examples and break out and go
% home.
if (bb < length(startIndices))
if (endIndices(bb)+3 <= length(candidateText))
if (candidateText(endIndices(bb)+3) ~= '%')
break;
end
end
end
end
end
% This short function forces examples to run in a clean workspace,
% and protects the calling workspace. Also closes any figures that
% are open if CLOSEFIGS is true, unless the called example clobbers
% the workspace in which case they are also left open.
function status = EvalClean(str,CLOSEFIGS)
try
eval(str)
status = 0;
catch
status = -1;
end
if (exist('CLOSEFIGS','var') & CLOSEFIGS)
close all;
end
end