xref: /haiku/src/tests/kits/shared/JsonErrorHandlingTest.cpp (revision 0c28e8e5a05e7d67c3ceb391ae7bb4298e0bb624)
1 /*
2  * Copyright 2017, Andrew Lindesay <apl@lindesay.co.nz>
3  * Distributed under the terms of the MIT License.
4  */
5 #include "JsonErrorHandlingTest.h"
6 
7 #include <AutoDeleter.h>
8 
9 #include <Json.h>
10 #include <JsonEventListener.h>
11 
12 #include <cppunit/TestCaller.h>
13 #include <cppunit/TestSuite.h>
14 
15 #include "JsonSamples.h"
16 
17 
18 using namespace BPrivate;
19 
20 class ErrorCapturingListener : public BJsonEventListener {
21 public:
22 								ErrorCapturingListener();
23 		virtual					~ErrorCapturingListener();
24 
25 			bool				Handle(const BJsonEvent& event);
26 			void				HandleError(status_t status, int32 line,
27 									const char* message);
Complete()28 			void				Complete() {};
29 
30 			status_t			ErrorStatus();
31 			int32				GetErrorLine();
32 			BString				GetErrorMessage();
33 			bool				HasEventsAfterError();
34 			json_event_type		FirstEventTypeAfterError();
35 private:
36 			status_t			fErrorStatus;
37 			int32				fErrorLine;
38 			BString				fErrorMessage;
39 			json_event_type		fFirstEventTypeAfterError;
40 			int32				fEventCountAfterError;
41 
42 };
43 
44 
45 /*! This DataIO concrete implementation is designed to open and then to fail
46     in order to simulate what might happen if there were an IO problem when
47     parsing some JSON.
48 */
49 
50 class FailingDataIO : public BDataIO {
51 public:
52 									FailingDataIO();
53 		virtual						~FailingDataIO();
54 
55 		ssize_t						Read(void* buffer, size_t size);
56 		ssize_t						Write(const void* buffer, size_t size);
57 
58 		status_t					Flush();
59 };
60 
61 
62 // #pragma mark - FailingDataIO
63 
64 
FailingDataIO()65 FailingDataIO::FailingDataIO()
66 {
67 }
68 
69 
~FailingDataIO()70 FailingDataIO::~FailingDataIO()
71 {
72 }
73 
74 
75 ssize_t
Read(void * buffer,size_t size)76 FailingDataIO::Read(void* buffer, size_t size)
77 {
78 	return B_IO_ERROR;
79 }
80 
81 
82 ssize_t
Write(const void * buffer,size_t size)83 FailingDataIO::Write(const void* buffer, size_t size)
84 {
85 	fprintf(stdout, "attempt to write");
86 	return B_IO_ERROR;
87 }
88 
89 
90 status_t
Flush()91 FailingDataIO::Flush()
92 {
93 	return B_IO_ERROR;
94 }
95 
96 
97 // #pragma mark - ErrorCapturingListener
98 
99 
ErrorCapturingListener()100 ErrorCapturingListener::ErrorCapturingListener()
101 {
102 	fErrorStatus = B_OK;
103 	fFirstEventTypeAfterError = B_JSON_NULL; // least likely
104 	fEventCountAfterError = 0;
105 }
106 
107 
~ErrorCapturingListener()108 ErrorCapturingListener::~ErrorCapturingListener()
109 {
110 }
111 
112 
113 bool
Handle(const BJsonEvent & event)114 ErrorCapturingListener::Handle(const BJsonEvent& event)
115 {
116 	if (fErrorStatus != B_OK) {
117 		if (fEventCountAfterError == 0)
118 			fFirstEventTypeAfterError = event.EventType();
119 
120 		fEventCountAfterError++;
121 	}
122 	return true; // keep going.
123 }
124 
125 
126 void
HandleError(status_t status,int32 line,const char * message)127 ErrorCapturingListener::HandleError(status_t status, int32 line,
128 	const char *message)
129 {
130 	fErrorStatus = status;
131 	fErrorLine = line;
132 
133 	if (message != NULL)
134 		fErrorMessage = BString(message);
135 	else
136 		fErrorMessage = BString();
137 }
138 
139 
140 status_t
ErrorStatus()141 ErrorCapturingListener::ErrorStatus()
142 {
143 	return fErrorStatus;
144 }
145 
146 
147 int32
GetErrorLine()148 ErrorCapturingListener::GetErrorLine()
149 {
150 	return fErrorLine;
151 }
152 
153 
154 BString
GetErrorMessage()155 ErrorCapturingListener::GetErrorMessage()
156 {
157 	return fErrorMessage;
158 }
159 
160 
161 json_event_type
FirstEventTypeAfterError()162 ErrorCapturingListener::FirstEventTypeAfterError()
163 {
164 	return fFirstEventTypeAfterError;
165 }
166 
167 
168 bool
HasEventsAfterError()169 ErrorCapturingListener::HasEventsAfterError()
170 {
171 	return fEventCountAfterError > 0;
172 }
173 
174 
JsonErrorHandlingTest()175 JsonErrorHandlingTest::JsonErrorHandlingTest()
176 {
177 }
178 
179 
~JsonErrorHandlingTest()180 JsonErrorHandlingTest::~JsonErrorHandlingTest()
181 {
182 }
183 
184 
185 void
TestParseWithBadStringEscape(const char * input,int32 line,status_t expectedStatus,char expectedBadEscapeChar)186 JsonErrorHandlingTest::TestParseWithBadStringEscape(const char* input,
187 	int32 line, status_t expectedStatus, char expectedBadEscapeChar)
188 {
189 	BString expectedMessage;
190 	expectedMessage.SetToFormat("unexpected escaped character [%c] "
191 		"in string parsing", expectedBadEscapeChar);
192 
193 	TestParseWithErrorMessage(input, line, expectedStatus,
194 		expectedMessage.String());
195 }
196 
197 
198 void
TestParseWithUnterminatedElement(const char * input,int32 line,status_t expectedStatus)199 JsonErrorHandlingTest::TestParseWithUnterminatedElement(const char* input,
200 	int32 line, status_t expectedStatus)
201 {
202 	BString expectedMessage;
203 	expectedMessage.SetToFormat("unterminated element");
204 
205 	TestParseWithErrorMessage(input, line, expectedStatus,
206 		expectedMessage.String());
207 }
208 
209 
210 void
TestParseWithUnexpectedCharacter(const char * input,int32 line,status_t expectedStatus,char expectedChar)211 JsonErrorHandlingTest::TestParseWithUnexpectedCharacter(const char* input,
212 	int32 line, status_t expectedStatus, char expectedChar)
213 {
214 	BString expectedMessage;
215 	expectedMessage.SetToFormat("unexpected character [%" B_PRIu8
216 		"] (%c) when parsing element", static_cast<uint8>(expectedChar),
217 		expectedChar);
218 
219 	TestParseWithErrorMessage(input, line, expectedStatus,
220 		expectedMessage.String());
221 }
222 
223 
224 void
TestParseWithErrorMessage(const char * input,int32 line,status_t expectedStatus,const char * expectedMessage)225 JsonErrorHandlingTest::TestParseWithErrorMessage(const char* input, int32 line,
226 	status_t expectedStatus, const char* expectedMessage)
227 {
228 	fprintf(stderr, "in >%s<\n", input);
229 	BDataIO *inputData = new BMemoryIO(input, strlen(input));
230 	ObjectDeleter<BDataIO> inputDataDeleter(inputData);
231 	TestParseWithErrorMessage(inputData, line, expectedStatus, expectedMessage);
232 }
233 
234 
235 void
TestParseWithErrorMessage(BDataIO * inputData,int32 line,status_t expectedStatus,const char * expectedMessage)236 JsonErrorHandlingTest::TestParseWithErrorMessage(BDataIO* inputData, int32 line,
237 	status_t expectedStatus, const char* expectedMessage)
238 {
239 	ErrorCapturingListener* listener = new ErrorCapturingListener();
240 	ObjectDeleter<ErrorCapturingListener> listenerDeleter(listener);
241 
242 // ----------------------
243 	BPrivate::BJson::Parse(inputData, listener);
244 // ----------------------
245 
246 	fprintf(stderr, "expected error at line %" B_PRIi32 " - %s : %s\n",
247 		line,
248 		strerror(expectedStatus),
249 		expectedMessage);
250 
251 	fprintf(stderr, "actual error at line %" B_PRIi32 " - %s : %s\n",
252 		listener->GetErrorLine(),
253 		strerror(listener->ErrorStatus()),
254 		listener->GetErrorMessage().String());
255 
256 	if (listener->HasEventsAfterError()) {
257 		fprintf(stderr, "first event after error [%d]\n",
258 			listener->FirstEventTypeAfterError());
259 	}
260 
261 	CPPUNIT_ASSERT(!listener->HasEventsAfterError());
262 	CPPUNIT_ASSERT_EQUAL(expectedStatus, listener->ErrorStatus());
263 	CPPUNIT_ASSERT_EQUAL(line, listener->GetErrorLine());
264 	CPPUNIT_ASSERT(0 == strcmp(expectedMessage,
265 		listener->GetErrorMessage().String()));
266 }
267 
268 
269 void
TestCompletelyUnknown()270 JsonErrorHandlingTest::TestCompletelyUnknown()
271 {
272 	TestParseWithUnexpectedCharacter(
273 		JSON_SAMPLE_BROKEN_COMPLETELY_UNKNOWN, 1, B_BAD_DATA, 'z');
274 }
275 
276 
277 void
TestObjectWithPrematureSeparator()278 JsonErrorHandlingTest::TestObjectWithPrematureSeparator()
279 {
280 	TestParseWithErrorMessage(JSON_SAMPLE_BROKEN_OBJECT_PREMATURE_SEPARATOR, 1,
281 		B_BAD_DATA, "unexpected item separator when parsing start of object");
282 }
283 
284 
285 void
TestStringUnterminated()286 JsonErrorHandlingTest::TestStringUnterminated()
287 {
288 	TestParseWithErrorMessage(JSON_SAMPLE_BROKEN_UNTERMINATED_STRING, 1,
289 		B_BAD_DATA, "unexpected end of input");
290 }
291 
292 
293 void
TestBadStringEscape()294 JsonErrorHandlingTest::TestBadStringEscape()
295 {
296 	TestParseWithBadStringEscape(
297 		JSON_SAMPLE_BROKEN_BAD_STRING_ESCAPE, 1, B_BAD_DATA, 'v');
298 }
299 
300 
301 void
TestBadNumber()302 JsonErrorHandlingTest::TestBadNumber()
303 {
304 	TestParseWithErrorMessage(JSON_SAMPLE_BROKEN_NUMBER, 1, B_BAD_DATA,
305 		"malformed number");
306 }
307 
308 
309 void
TestIOIssue()310 JsonErrorHandlingTest::TestIOIssue()
311 {
312 	BDataIO *inputData = new FailingDataIO();
313 	ObjectDeleter<BDataIO> inputDataDeleter(inputData);
314 	TestParseWithErrorMessage(inputData, -1, B_IO_ERROR,
315 		"io related read error");
316 }
317 
318 
319 /*static*/ void
AddTests(BTestSuite & parent)320 JsonErrorHandlingTest::AddTests(BTestSuite& parent)
321 {
322 	CppUnit::TestSuite& suite = *new CppUnit::TestSuite(
323 		"JsonErrorHandlingTest");
324 
325 	suite.addTest(new CppUnit::TestCaller<JsonErrorHandlingTest>(
326 		"JsonErrorHandlingTest::TestCompletelyUnknown",
327 		&JsonErrorHandlingTest::TestCompletelyUnknown));
328 
329 	suite.addTest(new CppUnit::TestCaller<JsonErrorHandlingTest>(
330 		"JsonErrorHandlingTest::TestObjectWithPrematureSeparator",
331 		&JsonErrorHandlingTest::TestObjectWithPrematureSeparator));
332 
333 	suite.addTest(new CppUnit::TestCaller<JsonErrorHandlingTest>(
334 		"JsonErrorHandlingTest::TestStringUnterminated",
335 		&JsonErrorHandlingTest::TestStringUnterminated));
336 
337 	suite.addTest(new CppUnit::TestCaller<JsonErrorHandlingTest>(
338 		"JsonErrorHandlingTest::TestBadStringEscape",
339 		&JsonErrorHandlingTest::TestBadStringEscape));
340 
341 	suite.addTest(new CppUnit::TestCaller<JsonErrorHandlingTest>(
342 		"JsonErrorHandlingTest::TestBadNumber",
343 		&JsonErrorHandlingTest::TestBadNumber));
344 
345 	suite.addTest(new CppUnit::TestCaller<JsonErrorHandlingTest>(
346 		"JsonErrorHandlingTest::TestIOIssue",
347 		&JsonErrorHandlingTest::TestIOIssue));
348 
349 	parent.addTest("JsonErrorHandlingTest", &suite);
350 }