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
18using namespace BPrivate;
19
20class ErrorCapturingListener : public BJsonEventListener {
21public:
22								ErrorCapturingListener();
23		virtual					~ErrorCapturingListener();
24
25			bool				Handle(const BJsonEvent& event);
26			void				HandleError(status_t status, int32 line,
27									const char* message);
28			void				Complete() {};
29
30			status_t			ErrorStatus();
31			int32				GetErrorLine();
32			BString				GetErrorMessage();
33			bool				HasEventsAfterError();
34			json_event_type		FirstEventTypeAfterError();
35private:
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
50class FailingDataIO : public BDataIO {
51public:
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
65FailingDataIO::FailingDataIO()
66{
67}
68
69
70FailingDataIO::~FailingDataIO()
71{
72}
73
74
75ssize_t
76FailingDataIO::Read(void* buffer, size_t size)
77{
78	return B_IO_ERROR;
79}
80
81
82ssize_t
83FailingDataIO::Write(const void* buffer, size_t size)
84{
85	fprintf(stdout, "attempt to write");
86	return B_IO_ERROR;
87}
88
89
90status_t
91FailingDataIO::Flush()
92{
93	return B_IO_ERROR;
94}
95
96
97// #pragma mark - ErrorCapturingListener
98
99
100ErrorCapturingListener::ErrorCapturingListener()
101{
102	fErrorStatus = B_OK;
103	fFirstEventTypeAfterError = B_JSON_NULL; // least likely
104	fEventCountAfterError = 0;
105}
106
107
108ErrorCapturingListener::~ErrorCapturingListener()
109{
110}
111
112
113bool
114ErrorCapturingListener::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
126void
127ErrorCapturingListener::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
140status_t
141ErrorCapturingListener::ErrorStatus()
142{
143	return fErrorStatus;
144}
145
146
147int32
148ErrorCapturingListener::GetErrorLine()
149{
150	return fErrorLine;
151}
152
153
154BString
155ErrorCapturingListener::GetErrorMessage()
156{
157	return fErrorMessage;
158}
159
160
161json_event_type
162ErrorCapturingListener::FirstEventTypeAfterError()
163{
164	return fFirstEventTypeAfterError;
165}
166
167
168bool
169ErrorCapturingListener::HasEventsAfterError()
170{
171	return fEventCountAfterError > 0;
172}
173
174
175JsonErrorHandlingTest::JsonErrorHandlingTest()
176{
177}
178
179
180JsonErrorHandlingTest::~JsonErrorHandlingTest()
181{
182}
183
184
185void
186JsonErrorHandlingTest::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
198void
199JsonErrorHandlingTest::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
210void
211JsonErrorHandlingTest::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
224void
225JsonErrorHandlingTest::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
235void
236JsonErrorHandlingTest::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
269void
270JsonErrorHandlingTest::TestCompletelyUnknown()
271{
272	TestParseWithUnexpectedCharacter(
273		JSON_SAMPLE_BROKEN_COMPLETELY_UNKNOWN, 1, B_BAD_DATA, 'z');
274}
275
276
277void
278JsonErrorHandlingTest::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
285void
286JsonErrorHandlingTest::TestStringUnterminated()
287{
288	TestParseWithErrorMessage(JSON_SAMPLE_BROKEN_UNTERMINATED_STRING, 1,
289		B_BAD_DATA, "unexpected end of input");
290}
291
292
293void
294JsonErrorHandlingTest::TestBadStringEscape()
295{
296	TestParseWithBadStringEscape(
297		JSON_SAMPLE_BROKEN_BAD_STRING_ESCAPE, 1, B_BAD_DATA, 'v');
298}
299
300
301void
302JsonErrorHandlingTest::TestBadNumber()
303{
304	TestParseWithErrorMessage(JSON_SAMPLE_BROKEN_NUMBER, 1, B_BAD_DATA,
305		"malformed number");
306}
307
308
309void
310JsonErrorHandlingTest::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
320JsonErrorHandlingTest::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}