Skip to content

Commit 9757ef7

Browse files
committed
test doubles to record & replay server interactions in example tests
1 parent ae07cd9 commit 9757ef7

26 files changed

+151
-18
lines changed

+llms/+internal/callAzureChatAPI.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464

6565
parameters = buildParametersCall(messages, functions, nvp);
6666

67-
[response, streamedText] = llms.internal.sendRequest(parameters,nvp.APIKey, URL, nvp.TimeOut, nvp.StreamFun);
67+
[response, streamedText] = llms.internal.sendRequestWrapper(parameters,nvp.APIKey, URL, nvp.TimeOut, nvp.StreamFun);
6868

6969
% If call errors, "choices" will not be part of response.Body.Data, instead
7070
% we get response.Body.Data.error

+llms/+internal/callOllamaChatAPI.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353

5454
parameters = buildParametersCall(model, messages, nvp);
5555

56-
[response, streamedText] = llms.internal.sendRequest(parameters,[],URL,nvp.TimeOut,nvp.StreamFun);
56+
[response, streamedText] = llms.internal.sendRequestWrapper(parameters,[],URL,nvp.TimeOut,nvp.StreamFun);
5757

5858
% If call errors, "choices" will not be part of response.Body.Data, instead
5959
% we get response.Body.Data.error

+llms/+internal/callOpenAIChatAPI.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262

6363
parameters = buildParametersCall(messages, functions, nvp);
6464

65-
[response, streamedText] = llms.internal.sendRequest(parameters,nvp.APIKey, END_POINT, nvp.TimeOut, nvp.StreamFun);
65+
[response, streamedText] = llms.internal.sendRequestWrapper(parameters,nvp.APIKey, END_POINT, nvp.TimeOut, nvp.StreamFun);
6666

6767
% If call errors, "choices" will not be part of response.Body.Data, instead
6868
% we get response.Body.Data.error

+llms/+internal/sendRequestWrapper.m

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
function [response, streamedText] = sendRequestWrapper(varargin)
2+
% This function is undocumented and will change in a future release
3+
4+
% A wrapper around sendRequest to have a test seam
5+
[response, streamedText] = llms.internal.sendRequest(varargin{:});

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
*.env
22
*.asv
33
*.mat
4+
!tests/recordings/*.mat
45
startup.m
56
papers_to_read.csv
67
data/*

extractOpenAIEmbeddings.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
end
4848

4949

50-
response = llms.internal.sendRequest(parameters,key, END_POINT, nvp.TimeOut);
50+
response = llms.internal.sendRequestWrapper(parameters,key, END_POINT, nvp.TimeOut);
5151

5252
if isfield(response.Body.Data, "data")
5353
emb = [response.Body.Data.data.embedding];
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
function [response, streamedText] = sendRequestWrapper(parameters, token, varargin)
2+
% This function is undocumented and will change in a future release
3+
4+
% A wrapper around sendRequest to have a test seam
5+
persistent seenCalls
6+
if isempty(seenCalls)
7+
seenCalls = cell(0,2);
8+
end
9+
10+
persistent filename
11+
12+
if nargin == 1 && isequal(parameters,"close")
13+
save(filename+".mat","seenCalls");
14+
seenCalls = cell(0,2);
15+
return
16+
end
17+
18+
if nargin==2 && isequal(parameters,"open")
19+
filename = token;
20+
return
21+
end
22+
23+
streamFunCalls = {};
24+
hasCallback = nargin >= 5 && isa(varargin{3},'function_handle');
25+
if hasCallback
26+
streamFun = varargin{3};
27+
end
28+
function wrappedStreamFun(varargin)
29+
streamFunCalls(end+1) = varargin;
30+
streamFun(varargin{:});
31+
end
32+
if hasCallback
33+
varargin{3} = @wrappedStreamFun;
34+
end
35+
36+
37+
[response, streamedText] = llms.internal.sendRequest(parameters, token, varargin{:});
38+
39+
seenCalls(end+1,:) = {{parameters},{response,streamFunCalls,streamedText}};
40+
end

tests/recording-doubles/addpath.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
function addpath(~)
2+
% ignore addpath calls in examples
Binary file not shown.
Binary file not shown.
Binary file not shown.
17.3 KB
Binary file not shown.
16.9 KB
Binary file not shown.
4.66 KB
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

tests/recordings/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Test Double Recordings
2+
3+
Testing the examples typically takes a long time and tends to have false negatives relatively often, mostly due to timeout errors.
4+
5+
The point of testing the examples is not to test that we can connect to the servers. We have other test points for that. Hence, we insert a “test double” while testing the examples that keeps recordings of previous interactions with the servers and just replays the responses.
6+
7+
This directory contains those recordings.
8+
9+
## Generating Recordings
10+
11+
To generate or re-generate recordings (e.g., after changing an example, or making relevant software changes), open [`texampleTests.m`](../texampleTests.m) and in `setUpAndTearDowns`, change `capture = false;` to `capture = true;`. Then, run the test points relevant to the example(s) in question, and change `capture` back to `false`.
12+
Binary file not shown.
Binary file not shown.
Binary file not shown.
179 Bytes
Binary file not shown.
179 Bytes
Binary file not shown.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
function [response, streamedText] = sendRequestWrapper(parameters, token, varargin)
2+
% This function is undocumented and will change in a future release
3+
4+
% A wrapper around sendRequest to have a test seam
5+
persistent seenCalls
6+
if isempty(seenCalls)
7+
seenCalls = cell(0,2);
8+
end
9+
10+
if nargin == 1 && isequal(parameters,"close")
11+
seenCalls = cell(0,2);
12+
return
13+
end
14+
15+
if nargin==2 && isequal(parameters,"open")
16+
load(token+".mat","seenCalls");
17+
return
18+
end
19+
20+
result = seenCalls{1,2};
21+
response = result{1};
22+
streamFunCalls = result{2};
23+
streamedText = result{3};
24+
25+
if nargin >= 5 && isa(varargin{3},'function_handle')
26+
streamFun = varargin{3};
27+
cellfun(streamFun, streamFunCalls);
28+
end
29+
30+
seenCalls(1,:) = [];

tests/replaying-doubles/addpath.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
function addpath(~)
2+
% ignore addpath calls in examples

tests/texampleTests.m

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,25 @@
88
ChatBotExample = {"CreateSimpleChatBot", "CreateSimpleOllamaChatBot"};
99
end
1010

11+
properties
12+
TestDir;
13+
end
1114

1215
methods (TestClassSetup)
1316
function setUpAndTearDowns(testCase)
17+
% Capture and replay server interactions
18+
testCase.TestDir = fileparts(mfilename("fullpath"));
19+
import matlab.unittest.fixtures.PathFixture
20+
capture = false; % run in capture or replay mode, cf. recordings/README.md
21+
22+
if capture
23+
testCase.applyFixture(PathFixture( ...
24+
fullfile(testCase.TestDir,"recording-doubles")));
25+
else
26+
testCase.applyFixture(PathFixture( ...
27+
fullfile(testCase.TestDir,"replaying-doubles")));
28+
end
29+
1430
import matlab.unittest.fixtures.CurrentFolderFixture
1531
testCase.applyFixture(CurrentFolderFixture("../examples/mlx-scripts"));
1632

@@ -29,22 +45,39 @@ function setUpAndTearDowns(testCase)
2945
testCase.addTeardown(@() iCloseAll());
3046
end
3147
end
32-
48+
49+
methods
50+
function startCapture(testCase,testName)
51+
llms.internal.sendRequestWrapper("open", ...
52+
fullfile(testCase.TestDir,"recordings",testName));
53+
end
54+
end
55+
56+
methods(TestMethodTeardown)
57+
function closeCapture(~)
58+
llms.internal.sendRequestWrapper("close");
59+
end
60+
end
61+
3362
methods(Test)
34-
function testAnalyzeScientificPapersUsingFunctionCalls(~)
63+
function testAnalyzeScientificPapersUsingFunctionCalls(testCase)
64+
testCase.startCapture("AnalyzeScientificPapersUsingFunctionCalls");
3565
AnalyzeScientificPapersUsingFunctionCalls;
3666
end
3767

3868
function testAnalyzeSentimentinTextUsingChatGPTinJSONMode(testCase)
69+
testCase.startCapture("AnalyzeSentimentinTextUsingChatGPTinJSONMode");
3970
testCase.verifyWarning(@AnalyzeSentimentinTextUsingChatGPTinJSONMode,...
4071
"llms:warningJsonInstruction");
4172
end
4273

43-
function testAnalyzeTextDataUsingParallelFunctionCallwithChatGPT(~)
74+
function testAnalyzeTextDataUsingParallelFunctionCallwithChatGPT(testCase)
75+
testCase.startCapture("AnalyzeTextDataUsingParallelFunctionCallwithChatGPT");
4476
AnalyzeTextDataUsingParallelFunctionCallwithChatGPT;
4577
end
4678

4779
function testCreateSimpleChatBot(testCase,ChatBotExample)
80+
testCase.startCapture(ChatBotExample);
4881
% set up a fake input command, returning canned user prompts
4982
count = 0;
5083
prompts = [
@@ -85,43 +118,51 @@ function testCreateSimpleChatBot(testCase,ChatBotExample)
85118
testCase.verifySize(messages.Messages,[1 2*(count-1)]);
86119
end
87120

88-
function testDescribeImagesUsingChatGPT(~)
121+
function testDescribeImagesUsingChatGPT(testCase)
122+
testCase.startCapture("DescribeImagesUsingChatGPT");
89123
DescribeImagesUsingChatGPT;
90124
end
91125

92-
function testInformationRetrievalUsingOpenAIDocumentEmbedding(~)
126+
function testInformationRetrievalUsingOpenAIDocumentEmbedding(testCase)
127+
testCase.startCapture("InformationRetrievalUsingOpenAIDocumentEmbedding");
93128
InformationRetrievalUsingOpenAIDocumentEmbedding;
94129
end
95130

96-
function testProcessGeneratedTextinRealTimebyUsingChatGPTinStreamingMode(~)
131+
function testProcessGeneratedTextinRealTimebyUsingChatGPTinStreamingMode(testCase)
132+
testCase.startCapture("ProcessGeneratedTextinRealTimebyUsingChatGPTinStreamingMode");
97133
ProcessGeneratedTextinRealTimebyUsingChatGPTinStreamingMode;
98134
end
99135

100-
function testProcessGeneratedTextInRealTimeByUsingOllamaInStreamingMode(~)
136+
function testProcessGeneratedTextInRealTimeByUsingOllamaInStreamingMode(testCase)
137+
testCase.startCapture("ProcessGeneratedTextInRealTimeByUsingOllamaInStreamingMode");
101138
ProcessGeneratedTextInRealTimeByUsingOllamaInStreamingMode;
102139
end
103140

104-
function testRetrievalAugmentedGenerationUsingChatGPTandMATLAB(~)
141+
function testRetrievalAugmentedGenerationUsingChatGPTandMATLAB(testCase)
142+
testCase.startCapture("RetrievalAugmentedGenerationUsingChatGPTandMATLAB");
105143
RetrievalAugmentedGenerationUsingChatGPTandMATLAB;
106144
end
107145

108-
function testRetrievalAugmentedGenerationUsingOllamaAndMATLAB(~)
146+
function testRetrievalAugmentedGenerationUsingOllamaAndMATLAB(testCase)
147+
testCase.startCapture("RetrievalAugmentedGenerationUsingOllamaAndMATLAB");
109148
RetrievalAugmentedGenerationUsingOllamaAndMATLAB;
110149
end
111150

112-
function testSummarizeLargeDocumentsUsingChatGPTandMATLAB(~)
151+
function testSummarizeLargeDocumentsUsingChatGPTandMATLAB(testCase)
152+
testCase.startCapture("SummarizeLargeDocumentsUsingChatGPTandMATLAB");
113153
SummarizeLargeDocumentsUsingChatGPTandMATLAB;
114154
end
115155

116-
function testUsingDALLEToEditImages(~)
156+
function testUsingDALLEToEditImages(testCase)
157+
testCase.startCapture("UsingDALLEToEditImages");
117158
UsingDALLEToEditImages;
118159
end
119160

120-
function testUsingDALLEToGenerateImages(~)
161+
function testUsingDALLEToGenerateImages(testCase)
162+
testCase.startCapture("UsingDALLEToGenerateImages");
121163
UsingDALLEToGenerateImages;
122164
end
123-
end
124-
165+
end
125166
end
126167

127168
function iCloseAll()

0 commit comments

Comments
 (0)