⛏️ index : haiku.git

/*
 * Copyright 2017, Andrew Lindesay <apl@lindesay.co.nz>
 * Distributed under the terms of the MIT License.
 */
#include "JsonErrorHandlingTest.h"

#include <AutoDeleter.h>

#include <Json.h>
#include <JsonEventListener.h>

#include <cppunit/TestCaller.h>
#include <cppunit/TestSuite.h>

#include "JsonSamples.h"


using namespace BPrivate;

class ErrorCapturingListener : public BJsonEventListener {
public:
								ErrorCapturingListener();
		virtual					~ErrorCapturingListener();

			bool				Handle(const BJsonEvent& event);
			void				HandleError(status_t status, int32 line,
									const char* message);
			void				Complete() {};

			status_t			ErrorStatus();
			int32				GetErrorLine();
			BString				GetErrorMessage();
			bool				HasEventsAfterError();
			json_event_type		FirstEventTypeAfterError();
private:
			status_t			fErrorStatus;
			int32				fErrorLine;
			BString				fErrorMessage;
			json_event_type		fFirstEventTypeAfterError;
			int32				fEventCountAfterError;

};


/*! This DataIO concrete implementation is designed to open and then to fail
    in order to simulate what might happen if there were an IO problem when
    parsing some JSON.
*/

class FailingDataIO : public BDataIO {
public:
									FailingDataIO();
		virtual						~FailingDataIO();

		ssize_t						Read(void* buffer, size_t size);
		ssize_t						Write(const void* buffer, size_t size);

		status_t					Flush();
};


// #pragma mark - FailingDataIO


FailingDataIO::FailingDataIO()
{
}


FailingDataIO::~FailingDataIO()
{
}


ssize_t
FailingDataIO::Read(void* buffer, size_t size)
{
	return B_IO_ERROR;
}


ssize_t
FailingDataIO::Write(const void* buffer, size_t size)
{
	fprintf(stdout, "attempt to write");
	return B_IO_ERROR;
}


status_t
FailingDataIO::Flush()
{
	return B_IO_ERROR;
}


// #pragma mark - ErrorCapturingListener


ErrorCapturingListener::ErrorCapturingListener()
{
	fErrorStatus = B_OK;
	fFirstEventTypeAfterError = B_JSON_NULL; // least likely
	fEventCountAfterError = 0;
}


ErrorCapturingListener::~ErrorCapturingListener()
{
}


bool
ErrorCapturingListener::Handle(const BJsonEvent& event)
{
	if (fErrorStatus != B_OK) {
		if (fEventCountAfterError == 0)
			fFirstEventTypeAfterError = event.EventType();

		fEventCountAfterError++;
	}
	return true; // keep going.
}


void
ErrorCapturingListener::HandleError(status_t status, int32 line,
	const char *message)
{
	fErrorStatus = status;
	fErrorLine = line;

	if (message != NULL)
		fErrorMessage = BString(message);
	else
		fErrorMessage = BString();
}


status_t
ErrorCapturingListener::ErrorStatus()
{
	return fErrorStatus;
}


int32
ErrorCapturingListener::GetErrorLine()
{
	return fErrorLine;
}


BString
ErrorCapturingListener::GetErrorMessage()
{
	return fErrorMessage;
}


json_event_type
ErrorCapturingListener::FirstEventTypeAfterError()
{
	return fFirstEventTypeAfterError;
}


bool
ErrorCapturingListener::HasEventsAfterError()
{
	return fEventCountAfterError > 0;
}


JsonErrorHandlingTest::JsonErrorHandlingTest()
{
}


JsonErrorHandlingTest::~JsonErrorHandlingTest()
{
}


void
JsonErrorHandlingTest::TestParseWithBadStringEscape(const char* input,
	int32 line, status_t expectedStatus, char expectedBadEscapeChar)
{
	BString expectedMessage;
	expectedMessage.SetToFormat("unexpected escaped character [%c] "
		"in string parsing", expectedBadEscapeChar);

	TestParseWithErrorMessage(input, line, expectedStatus,
		expectedMessage.String());
}


void
JsonErrorHandlingTest::TestParseWithUnterminatedElement(const char* input,
	int32 line, status_t expectedStatus)
{
	BString expectedMessage;
	expectedMessage.SetToFormat("unterminated element");

	TestParseWithErrorMessage(input, line, expectedStatus,
		expectedMessage.String());
}


void
JsonErrorHandlingTest::TestParseWithUnexpectedCharacter(const char* input,
	int32 line, status_t expectedStatus, char expectedChar)
{
	BString expectedMessage;
	expectedMessage.SetToFormat("unexpected character [%" B_PRIu8
		"] (%c) when parsing element", static_cast<uint8>(expectedChar),
		expectedChar);

	TestParseWithErrorMessage(input, line, expectedStatus,
		expectedMessage.String());
}


void
JsonErrorHandlingTest::TestParseWithErrorMessage(const char* input, int32 line,
	status_t expectedStatus, const char* expectedMessage)
{
	fprintf(stderr, "in >%s<\n", input);
	BDataIO *inputData = new BMemoryIO(input, strlen(input));
	ObjectDeleter<BDataIO> inputDataDeleter(inputData);
	TestParseWithErrorMessage(inputData, line, expectedStatus, expectedMessage);
}


void
JsonErrorHandlingTest::TestParseWithErrorMessage(BDataIO* inputData, int32 line,
	status_t expectedStatus, const char* expectedMessage)
{
	ErrorCapturingListener* listener = new ErrorCapturingListener();
	ObjectDeleter<ErrorCapturingListener> listenerDeleter(listener);

// ----------------------
	BPrivate::BJson::Parse(inputData, listener);
// ----------------------

	fprintf(stderr, "expected error at line %" B_PRIi32 " - %s : %s\n",
		line,
		strerror(expectedStatus),
		expectedMessage);

	fprintf(stderr, "actual error at line %" B_PRIi32 " - %s : %s\n",
		listener->GetErrorLine(),
		strerror(listener->ErrorStatus()),
		listener->GetErrorMessage().String());

	if (listener->HasEventsAfterError()) {
		fprintf(stderr, "first event after error [%d]\n",
			listener->FirstEventTypeAfterError());
	}

	CPPUNIT_ASSERT(!listener->HasEventsAfterError());
	CPPUNIT_ASSERT_EQUAL(expectedStatus, listener->ErrorStatus());
	CPPUNIT_ASSERT_EQUAL(line, listener->GetErrorLine());
	CPPUNIT_ASSERT(0 == strcmp(expectedMessage,
		listener->GetErrorMessage().String()));
}


void
JsonErrorHandlingTest::TestCompletelyUnknown()
{
	TestParseWithUnexpectedCharacter(
		JSON_SAMPLE_BROKEN_COMPLETELY_UNKNOWN, 1, B_BAD_DATA, 'z');
}


void
JsonErrorHandlingTest::TestObjectWithPrematureSeparator()
{
	TestParseWithErrorMessage(JSON_SAMPLE_BROKEN_OBJECT_PREMATURE_SEPARATOR, 1,
		B_BAD_DATA, "unexpected item separator when parsing start of object");
}


void
JsonErrorHandlingTest::TestStringUnterminated()
{
	TestParseWithErrorMessage(JSON_SAMPLE_BROKEN_UNTERMINATED_STRING, 1,
		B_BAD_DATA, "unexpected end of input");
}


void
JsonErrorHandlingTest::TestBadStringEscape()
{
	TestParseWithBadStringEscape(
		JSON_SAMPLE_BROKEN_BAD_STRING_ESCAPE, 1, B_BAD_DATA, 'v');
}


void
JsonErrorHandlingTest::TestBadNumber()
{
	TestParseWithErrorMessage(JSON_SAMPLE_BROKEN_NUMBER, 1, B_BAD_DATA,
		"malformed number");
}


void
JsonErrorHandlingTest::TestIOIssue()
{
	BDataIO *inputData = new FailingDataIO();
	ObjectDeleter<BDataIO> inputDataDeleter(inputData);
	TestParseWithErrorMessage(inputData, -1, B_IO_ERROR,
		"io related read error");
}


/*static*/ void
JsonErrorHandlingTest::AddTests(BTestSuite& parent)
{
	CppUnit::TestSuite& suite = *new CppUnit::TestSuite(
		"JsonErrorHandlingTest");

	suite.addTest(new CppUnit::TestCaller<JsonErrorHandlingTest>(
		"JsonErrorHandlingTest::TestCompletelyUnknown",
		&JsonErrorHandlingTest::TestCompletelyUnknown));

	suite.addTest(new CppUnit::TestCaller<JsonErrorHandlingTest>(
		"JsonErrorHandlingTest::TestObjectWithPrematureSeparator",
		&JsonErrorHandlingTest::TestObjectWithPrematureSeparator));

	suite.addTest(new CppUnit::TestCaller<JsonErrorHandlingTest>(
		"JsonErrorHandlingTest::TestStringUnterminated",
		&JsonErrorHandlingTest::TestStringUnterminated));

	suite.addTest(new CppUnit::TestCaller<JsonErrorHandlingTest>(
		"JsonErrorHandlingTest::TestBadStringEscape",
		&JsonErrorHandlingTest::TestBadStringEscape));

	suite.addTest(new CppUnit::TestCaller<JsonErrorHandlingTest>(
		"JsonErrorHandlingTest::TestBadNumber",
		&JsonErrorHandlingTest::TestBadNumber));

	suite.addTest(new CppUnit::TestCaller<JsonErrorHandlingTest>(
		"JsonErrorHandlingTest::TestIOIssue",
		&JsonErrorHandlingTest::TestIOIssue));

	parent.addTest("JsonErrorHandlingTest", &suite);
}