diff options
| author | Andreas Stöckel <astoecke@techfak.uni-bielefeld.de> | 2015-04-01 22:05:58 +0200 | 
|---|---|---|
| committer | Andreas Stöckel <astoecke@techfak.uni-bielefeld.de> | 2016-04-25 22:19:29 +0200 | 
| commit | 0e3cf1963a4c5fa28f12a70b7d2f25c33580c182 (patch) | |
| tree | 2ae365ad3a955c29491fb7f8f1c723e0899a2305 /test | |
| parent | ac550e242be6e747f9420b705a33407652010b6c (diff) | |
Implement integration test framework
Diffstat (limited to 'test')
| -rw-r--r-- | test/integration/Main.cpp | 353 | ||||
| -rw-r--r-- | test/integration/TestLogger.cpp | 70 | ||||
| -rw-r--r-- | test/integration/TestLogger.hpp | 63 | ||||
| -rw-r--r-- | test/integration/TestXmlParser.cpp | 249 | ||||
| -rw-r--r-- | test/integration/TestXmlParser.hpp | 89 | 
5 files changed, 824 insertions, 0 deletions
| diff --git a/test/integration/Main.cpp b/test/integration/Main.cpp new file mode 100644 index 0000000..06e1c4a --- /dev/null +++ b/test/integration/Main.cpp @@ -0,0 +1,353 @@ +/* +    Ousía +    Copyright (C) 2015  Benjamin Paaßen, Andreas Stöckel + +    This program is free software: you can redistribute it and/or modify +    it under the terms of the GNU General Public License as published by +    the Free Software Foundation, either version 3 of the License, or +    (at your option) any later version. + +    This program is distributed in the hope that it will be useful, +    but WITHOUT ANY WARRANTY; without even the implied warranty of +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +    GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License +    along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file Main.cpp + * + * This file provides the integration test framework used in Ousía. The + * integration test framework recursively iterates over the files in the + * "testdata/integration" folder and searches for pairs of X.in.os[x]ml and + * X.out.osxml files. The "in" files are then processed, converted to XML and + * compared to the "out" XML files. Comparison is performed by parsing both + * files using eXpat, sorting the arguments and ignoring certain tags that may + * well differ between two files. + * + * @author Andreas Stöckel (astoecke@techfak.uni-bielefeld.de) + */ + +#include <unistd.h>  // Non-portable, needed for isatty + +#include <fstream> +#include <iostream> +#include <memory> +#include <ostream> +#include <sstream> +#include <queue> +#include <vector> + +#include <boost/filesystem.hpp> + +#include <core/common/Utils.hpp> +#include <core/frontend/TerminalLogger.hpp> +#include <core/managed/Manager.hpp> +#include <core/model/Document.hpp> +#include <core/model/Ontology.hpp> +#include <core/model/Project.hpp> +#include <core/model/Typesystem.hpp> +#include <core/parser/ParserContext.hpp> +#include <core/parser/ParserScope.hpp> +#include <core/resource/ResourceManager.hpp> +#include <core/Registry.hpp> +#include <formats/osxml/OsxmlParser.hpp> +#include <formats/osml/OsmlParser.hpp> +#include <plugins/filesystem/SpecialPaths.hpp> +#include <plugins/filesystem/FileLocator.hpp> +#include <plugins/xml/XmlOutput.hpp> + +#include "TestXmlParser.hpp" +#include "TestLogger.hpp" + +using namespace ousia; + +namespace fs = boost::filesystem; + +namespace { + +const size_t SUCCESS = 0; +const size_t ERROR = 1; + +/** + * Structure representing a single test case. + */ +struct Test { +	/** +	 * Input file. +	 */ +	std::string infile; + +	/** +	 * Output file. +	 */ +	std::string outfile; + +	/** +	 * Set to true if the test is expected to fail. +	 */ +	bool shouldFail : 1; + +	/** +	 * Set to true once the test was successful, otherwise initializes to false. +	 */ +	bool success : 1; + +	/** +	 * Default constructor. +	 */ +	Test() : shouldFail(false), success(false) {} + +	/** +	 * Constructor for a standard test. +	 * +	 * @param infile is the input file. +	 * @param outfile is the output file containing the expected input. +	 */ +	Test(const std::string &infile, const std::string &outfile) +	    : infile(infile), outfile(outfile), shouldFail(false), success(false) +	{ +	} + +	/** +	 * Constructor for a test with expected failure. +	 * +	 * @param infile is the input file. +	 */ +	Test(const std::string &infile) +	    : infile(infile), shouldFail(true), success(false) +	{ +	} +}; + +static bool parseFile(const std::string &infile, std::ostream &os) +{ +	// TODO: Share this code with the main CLI + +	// Initialize global instances. +	bool useColor = isatty(STDERR_FILENO); +	TerminalLogger logger{std::cerr, useColor}; +	Manager manager; +	Registry registry; +	ResourceManager resourceManager; +	ParserScope scope; +	Rooted<Project> project{new Project(manager)}; +	FileLocator fileLocator; +	ParserContext context{registry, resourceManager, scope, project, logger}; + +	// Connect the Source Context Callback of the logger to provide the user +	// with context information (line, column, filename, text) for log messages +	logger.setSourceContextCallback(resourceManager.getSourceContextCallback()); + +	// Fill registry +	registry.registerDefaultExtensions(); +	OsmlParser osmlParser; +	OsxmlParser osxmlParser; +	registry.registerParser( +	    {"text/vnd.ousia.osml"}, +	    {&RttiTypes::Document, &RttiTypes::Ontology, &RttiTypes::Typesystem}, +	    &osmlParser); +	registry.registerParser( +	    {"text/vnd.ousia.osml+xml"}, +	    {&RttiTypes::Document, &RttiTypes::Ontology, &RttiTypes::Typesystem}, +	    &osxmlParser); +	registry.registerResourceLocator(&fileLocator); + +	// Register search paths +	fileLocator.addDefaultSearchPaths(); + +	// Now all preparation is done and we can parse the input document. +	Rooted<Node> docNode = +	    context.import(infile, "", "", {&RttiTypes::Document}); + +	if (logger.hasError() || docNode == nullptr) { +		return false; +	} +	Rooted<Document> doc = docNode.cast<Document>(); + +	xml::XmlTransformer transform; +	transform.writeXml(doc, os, logger, resourceManager, true); +	return true; +} + +static bool runTest(test::Logger &logger, const Test &test) +{ +	// Parse the infile and dump it as OSXML to a string stream +	std::stringstream actual_output; +	bool res = parseFile(test.infile, actual_output); + +	// If this is a test with expected failure, check whether this failure +	// occured. +	if (test.shouldFail) { +		if (!res) { +			logger.success("Parsing failed as expected"); +			return true; +		} +		logger.fail("Expected error while parsing, but parsing succeeded!"); +		logger.note("Got following output from " + test.infile); +		logger.result(actual_output); +		return false; +	} else if (!res) { +		logger.fail("Unexpected error while parsing input file"); +		return false; +	} + +	// Write the actual_output to disk +	 + +	// Parse both the actual output and the expected output stream +	std::ifstream expected_output(test.outfile); +	logger.note("Parsing serialized XML"); +	std::set<int> errExpected, errActual; +	auto actual = test::parseXml(logger, actual_output, errActual); +	logger.note("Parsing expected XML from " + test.outfile); +	auto expected = test::parseXml(logger, expected_output, errExpected); + +	bool ok = false; +	if (actual.first && expected.first && +	    expected.second->compareTo(logger, actual.second, errExpected, +	                               errActual)) { +		logger.success("OK!"); +		ok = true; +	} + +	if (!ok) { +		logger.note("XML returned by serializer:"); +		logger.result(actual_output, errActual); +		logger.note("XML stored in file:"); +		logger.result(expected_output, errExpected); +		return false; +	} + +	return ok; +} + +/** + * Method used to gather the integration tests. + * + * @param root is the root directory from which the test cases should be + * gathered. + * @return a list of "Test" structures describing the test cases. + */ +static std::vector<Test> gatherTests(fs::path root) +{ +	// Result list +	std::vector<Test> res; + +	// End of a directory iterator +	const fs::directory_iterator end; + +	// Search all subdirectories of the given "root" directory, do so by using +	// a stack +	std::queue<fs::path> dirs{{root}}; +	while (!dirs.empty()) { +		// Fetch the current directory from the queue and remove it +		fs::path dir = dirs.front(); +		dirs.pop(); + +		// Iterate over the contents of this directory +		for (fs::directory_iterator it(dir); it != end; it++) { +			fs::path p = it->path(); +			// If the path p is itself a directory, store it on the stack, +			if (fs::is_directory(p)) { +				dirs.emplace(p); +			} else if (fs::is_regular_file(p)) { +				// Fetch the filename +				const std::string &fn = p.native(); + +				// Check whether the test ends with ".in.osml" or ".in.osxml" +				std::string testname; +				bool shouldFail = false; +				if (Utils::endsWith(fn, ".in.osml")) { +					testname = fn.substr(0, fn.size() - 8); +				} else if (Utils::endsWith(fn, ".in.osxml")) { +					testname = fn.substr(0, fn.size() - 9); +				} else if (Utils::endsWith(fn, ".fail.osml")) { +					testname = fn.substr(0, fn.size() - 10); +					shouldFail = true; +				} else if (Utils::endsWith(fn, ".fail.osxml")) { +					testname = fn.substr(0, fn.size() - 11); +					shouldFail = true; +				} + +				// If yes, check whether the same file exists ending with +				// .out.osxml -- if this is the case, add the resulting test +				// case to the result +				if (!testname.empty()) { +					if (shouldFail) { +						res.emplace_back(fn); +					} else { +						const std::string outFn = testname + ".out.osxml"; +						if (fs::is_regular_file(outFn)) { +							res.emplace_back(fn, outFn); +						} +					} +				} +			} +		} +	} + +	// Return the unit test list +	return res; +} +} + +int main(int argc, char **argv) +{ +	// Initialize terminal logger. Only use color if writing to a terminal (tty) +	bool useColor = isatty(STDERR_FILENO); +	test::Logger logger(std::cerr, useColor); +	logger.headline("OUSÍA INTEGRATION TEST FRAMEWORK"); +	logger.note("(c) Benjamin Paaßen, Andreas Stöckel 2015"); +	logger.note("This program is free software licensed under the GPLv3"); + +	// Use boost filesystem to recursively iterate over all files +	fs::path root = +	    fs::path(SpecialPaths::getDebugTestdataDir()) / "integration"; +	if (!fs::is_directory(root)) { +		logger.fail("Could not find integration test data directory: " + +		            root.native()); +		return ERROR; +	} + +	// Fetch all test cases +	logger.headline("GATHER TESTS"); +	std::vector<Test> tests = gatherTests(root); +	std::string testsWord = tests.size() == 1 ? " test" : " tests"; +	logger.note(std::to_string(tests.size()) + testsWord + " found"); + +	logger.headline("RUN TESTS"); +	size_t successCount = 0; +	size_t failCount = 0; +	for (auto &test : tests) { +		logger.note("Running test " + test.infile); +		if (runTest(logger, test)) { +			test.success = true; +			successCount++; +		} else { +			failCount++; +		} +	} + +	// Write the final error messages +	logger.headline("TEST SUMMARY"); +	logger.note(std::string("Ran ") + std::to_string(failCount + successCount) + +	            testsWord + ", " + std::to_string(failCount) + " failed, " + +	            std::to_string(successCount) + " succeeded"); +	if (failCount > 0) { +		logger.note("The following tests failed:"); +		for (const auto &test : tests) { +			if (!test.success) { +				logger.fail(test.infile); +			} +		} +	} else { +		logger.success("All tests completed successfully!"); +	} + +	// Inform the shell about failing integration tests +	return failCount > 0 ? ERROR : SUCCESS; +} + diff --git a/test/integration/TestLogger.cpp b/test/integration/TestLogger.cpp new file mode 100644 index 0000000..d745c0f --- /dev/null +++ b/test/integration/TestLogger.cpp @@ -0,0 +1,70 @@ +/* +    Ousía +    Copyright (C) 2015  Benjamin Paaßen, Andreas Stöckel + +    This program is free software: you can redistribute it and/or modify +    it under the terms of the GNU General Public License as published by +    the Free Software Foundation, either version 3 of the License, or +    (at your option) any later version. + +    This program is distributed in the hope that it will be useful, +    but WITHOUT ANY WARRANTY; without even the implied warranty of +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +    GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License +    along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <iomanip> + +#include "TestLogger.hpp" + +namespace ousia { +namespace test { + +Logger::Logger(std::ostream &os, bool useColor) : os(os), terminal(useColor) {} + +void Logger::fail(const std::string &msg) +{ +	os << terminal.color(Terminal::RED, true) << "[Fail]" << terminal.reset() +	   << " " << msg << std::endl; +} + +void Logger::success(const std::string &msg) +{ +	os << terminal.color(Terminal::GREEN, true) << "[Success]" +	   << terminal.reset() << " " << msg << std::endl; +} + +void Logger::note(const std::string &msg) +{ +	os << terminal.color(Terminal::BLUE, true) << "[Note]" << terminal.reset() +	   << " " << msg << std::endl; +} + +void Logger::result(std::istream &is, const std::set<int> &errLines) +{ +	std::string line; +	is.clear(); +	is.seekg(0); +	size_t lineNumber = 0; +	while (std::getline(is, line)) { +		lineNumber++; +		const bool hasErr = errLines.count(lineNumber); +		if (hasErr) { +			os << terminal.background(Terminal::RED); +		} +		os << terminal.color(Terminal::BLACK, !hasErr) << (hasErr ? "!" : " ") +		   << std::setw(3) << lineNumber << ":" << terminal.reset() << " " +		   << line << std::endl; +	} +} + +void Logger::headline(const std::string &msg) +{ +	os << std::endl << "== " << terminal.bright() << msg << terminal.reset() +	   << " ==" << std::endl; +} +} +} diff --git a/test/integration/TestLogger.hpp b/test/integration/TestLogger.hpp new file mode 100644 index 0000000..1c18191 --- /dev/null +++ b/test/integration/TestLogger.hpp @@ -0,0 +1,63 @@ +/* +    Ousía +    Copyright (C) 2015  Benjamin Paaßen, Andreas Stöckel + +    This program is free software: you can redistribute it and/or modify +    it under the terms of the GNU General Public License as published by +    the Free Software Foundation, either version 3 of the License, or +    (at your option) any later version. + +    This program is distributed in the hope that it will be useful, +    but WITHOUT ANY WARRANTY; without even the implied warranty of +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +    GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License +    along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file TestLogger.hpp + * + * Class used to print messages and streams in the integration test utility. + * + * @author Andreas Stöckel (astoecke@techfak.uni-bielefeld.de) + */ + +#ifndef _OUSIA_TEST_LOGGER_HPP_ +#define _OUSIA_TEST_LOGGER_HPP_ + +#include <ostream> +#include <istream> +#include <set> +#include <string> + +#include <core/frontend/Terminal.hpp> + +namespace ousia { +namespace test { + +/** + * Class used for printing messages to the screen. + */ +class Logger { +private: +	std::ostream &os; +	Terminal terminal; + +public: +	Logger(std::ostream &os, bool useColor); + +	void fail(const std::string &msg); +	void success(const std::string &msg); +	void note(const std::string &msg); +	void result(std::istream &is, +	            const std::set<int> &errLines = std::set<int>{}); +	void headline(const std::string &msg); +}; + +} +} + +#endif /* _OUSIA_TEST_LOGGER_HPP_ */ + diff --git a/test/integration/TestXmlParser.cpp b/test/integration/TestXmlParser.cpp new file mode 100644 index 0000000..b9eae0e --- /dev/null +++ b/test/integration/TestXmlParser.cpp @@ -0,0 +1,249 @@ +/* +    Ousía +    Copyright (C) 2015  Benjamin Paaßen, Andreas Stöckel + +    This program is free software: you can redistribute it and/or modify +    it under the terms of the GNU General Public License as published by +    the Free Software Foundation, either version 3 of the License, or +    (at your option) any later version. + +    This program is distributed in the hope that it will be useful, +    but WITHOUT ANY WARRANTY; without even the implied warranty of +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +    GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License +    along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <expat.h> + +#include <core/common/Utils.hpp> + +#include "TestXmlParser.hpp" + +namespace ousia { +namespace test { + +/* Class XmlNode */ + +std::string XmlNode::path() +{ +	std::string p; +	if (!parent.expired()) { +		std::shared_ptr<XmlNode> parentPtr = parent.lock(); +		if (parentPtr.get() != nullptr) { +			p = parentPtr->path() + "/"; +		} +	} +	return p + name; +} + +bool XmlNode::compareTo(Logger &logger, std::shared_ptr<XmlNode> other, +               std::set<int> &errExpected, std::set<int> &errActual) +{ +	bool ok = true; + +	// Compare name and text +	if (name != other->name) { +		logger.fail(path() + ": names differ, expected \"" + name + +		            "\", but got \"" + other->name + "\""); +		ok = false; +	} +	if (text != other->text) { +		logger.fail(path() + ": texts differ, expected \"" + text + +		            "\", but got \"" + other->text + "\""); +		ok = false; +	} + +	// Compare the attributes +	if (attributes.size() != other->attributes.size()) { +		logger.fail( +		    path() + ": attribute count differs, expected " + +		    std::to_string(attributes.size()) + " attributes, but got " + +		    std::to_string(other->attributes.size()) + " attributes"); +		ok = false; +	} +	for (const auto &attribute : attributes) { +		auto it = other->attributes.find(attribute.first); +		if (it == other->attributes.end()) { +			logger.fail(path() + ": attribute \"" + attribute.first + +			            "\" is missing in actual output"); +			ok = false; +		} else if (it->second != attribute.second) { +			logger.fail(path() + ": expected \"" + attribute.second + +			            "\" for attribute \"" + attribute.first + +			            "\" but got \"" + it->second + "\""); +			ok = false; +		} +	} + +	// Compare the children +	if (children.size() != other->children.size()) { +		logger.fail(path() + ": children count differs, expected " + +		            std::to_string(children.size()) + +		            " children, but got " + +		            std::to_string(other->children.size()) + " children"); +		ok = false; +	} + +	// Store the actual position +	if (!ok) { +		logger.fail("Location in expected output is " + +		            std::to_string(line) + ":" + std::to_string(column) + +		            ", location in actual output is " + +		            std::to_string(other->line) + ":" + +		            std::to_string(other->column)); +		errExpected.insert(line); +		errActual.insert(other->line); +	} + +	// Compare the children +	const size_t count = std::min(children.size(), other->children.size()); +	for (size_t i = 0; i < count; i++) { +		ok = children[i]->compareTo(logger, other->children[i], errExpected, +		                            errActual) & +		     ok; +	} + +	return ok; +} + +static const std::vector<std::string> IGNORE_TAGS{"import"}; +static const std::vector<std::string> IGNORE_ATTRS{"xmlns"}; + +static bool checkIgnore(const std::vector<std::string> &ignoreList, +                        const std::string &name) +{ +	for (const auto &s : ignoreList) { +		if (Utils::startsWith(s, name)) { +			return true; +		} +	} +	return false; +} + +/** + * Callback called by eXpat whenever a start handler is reached. + */ +static void xmlStartElementHandler(void *ref, const XML_Char *name, +                                   const XML_Char **attrs) +{ +	XML_Parser parser = static_cast<XML_Parser>(ref); +	std::shared_ptr<XmlNode> &node = +	    *(static_cast<std::shared_ptr<XmlNode> *>(XML_GetUserData(parser))); + +	// Store the child node in the parent node, check for ignoring nodes once +	// an element ends +	std::shared_ptr<XmlNode> childNode = +	    std::make_shared<XmlNode>(node, name); +	childNode->line = XML_GetCurrentLineNumber(parser); +	childNode->column = XML_GetCurrentColumnNumber(parser); +	node->children.push_back(childNode); +	node = childNode; + +	// Assemble the node attributes +	const XML_Char **attr = attrs; +	while (*attr) { +		// Convert the C string to a std::string +		const std::string key{*(attr++)}; +		const std::string value{*(attr++)}; + +		// Ignore certain attributes +		if (!checkIgnore(IGNORE_ATTRS, key)) { +			childNode->attributes.emplace(key, value); +		} +	} +} + +static void xmlEndElementHandler(void *ref, const XML_Char *name) +{ +	XML_Parser parser = static_cast<XML_Parser>(ref); +	std::shared_ptr<XmlNode> &node = +	    *(static_cast<std::shared_ptr<XmlNode> *>(XML_GetUserData(parser))); + +	// Set the current node to the parent node +	node = node->parent.lock(); + +	// If the child node should have been ignored, remove it now +	if (checkIgnore(IGNORE_TAGS, name)) { +		node->children.pop_back(); +	} +} + +static void xmlCharacterDataHandler(void *ref, const XML_Char *s, int len) +{ +	// Fetch a reference at the currently active node +	XML_Parser parser = static_cast<XML_Parser>(ref); +	std::shared_ptr<XmlNode> &node = +	    *(static_cast<std::shared_ptr<XmlNode> *>(XML_GetUserData(parser))); + +	// Store a new text node in the current node +	std::string text = Utils::trim(std::string(s, len)); +	if (!text.empty()) { +		std::shared_ptr<XmlNode> textNode = +		    std::make_shared<XmlNode>(node, "$text"); +		textNode->text = text; +		textNode->line = XML_GetCurrentLineNumber(parser); +		textNode->column = XML_GetCurrentColumnNumber(parser); +		node->children.push_back(textNode); +	} +} + +std::pair<bool, std::shared_ptr<XmlNode>> parseXml( +    Logger &logger, std::istream &is, std::set<int> &errLines) +{ +	std::shared_ptr<XmlNode> root = std::make_shared<XmlNode>(); +	std::shared_ptr<XmlNode> currentNode = root; + +	XML_Parser parser = XML_ParserCreate("UTF-8"); + +	// Pass the reference to this parser instance to the XML handler +	XML_UseParserAsHandlerArg(parser); +	XML_SetUserData(parser, ¤tNode); + +	// Set the callback functions +	XML_SetStartElementHandler(parser, xmlStartElementHandler); +	XML_SetEndElementHandler(parser, xmlEndElementHandler); +	XML_SetCharacterDataHandler(parser, xmlCharacterDataHandler); + +	// Feed data into expat while there is data to process +	constexpr size_t BUFFER_SIZE = 64 * 1024; +	bool ok = true; +	while (true) { +		// Fetch a buffer from expat for the input data +		char *buf = static_cast<char *>(XML_GetBuffer(parser, BUFFER_SIZE)); +		if (!buf) { +			logger.fail("Cannot parse XML, out of memory"); +			ok = false; +			break; +		} + +		// Read into the buffer +		size_t bytesRead = is.read(buf, BUFFER_SIZE).gcount(); + +		// Parse the data and handle any XML error as exception +		if (!XML_ParseBuffer(parser, bytesRead, bytesRead == 0)) { +			int line = XML_GetCurrentLineNumber(parser); +			int column = XML_GetCurrentColumnNumber(parser); +			logger.fail("Cannot parse XML, " + +			            std::string(XML_ErrorString(XML_GetErrorCode(parser))) + +			            ", at line " + std::to_string(line) + ", column " + +			            std::to_string(column)); +			errLines.insert(line); +			ok = false; +			break; +		} + +		// Abort once there are no more bytes in the stream +		if (bytesRead == 0) { +			break; +		} +	} + +	return std::pair<bool, std::shared_ptr<XmlNode>>(ok, root); +} + +} +} + diff --git a/test/integration/TestXmlParser.hpp b/test/integration/TestXmlParser.hpp new file mode 100644 index 0000000..d6a2c45 --- /dev/null +++ b/test/integration/TestXmlParser.hpp @@ -0,0 +1,89 @@ +/* +    Ousía +    Copyright (C) 2015  Benjamin Paaßen, Andreas Stöckel + +    This program is free software: you can redistribute it and/or modify +    it under the terms of the GNU General Public License as published by +    the Free Software Foundation, either version 3 of the License, or +    (at your option) any later version. + +    This program is distributed in the hope that it will be useful, +    but WITHOUT ANY WARRANTY; without even the implied warranty of +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +    GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License +    along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file TestXmlParser.hpp + * + * Contains a method to parse a XML file into a simple tree representation. + * + * @author Andreas Stöckel (astoecke@techfak.uni-bielefeld.de) + */ + +#ifndef _OUSIA_TEST_XML_PARSER_HPP_ +#define _OUSIA_TEST_XML_PARSER_HPP_ + +#include <memory> +#include <map> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "TestLogger.hpp" + +namespace ousia { +namespace test { + +/** + * Class used to represent a XML file. + */ +struct XmlNode { +	std::weak_ptr<XmlNode> parent; +	std::vector<std::shared_ptr<XmlNode>> children; +	std::map<std::string, std::string> attributes; +	std::string name; +	std::string text; +	int line; +	int column; + +	/** +	 * Default constructor, creates a root node. +	 */ +	XmlNode() : line(0), column(0) {} + +	/** +	 * Creates a node for the given parent with the given name. +	 * +	 * @param parent is the parent XML node. +	 */ +	XmlNode(std::weak_ptr<XmlNode> parent, const std::string &name) +	    : parent(parent), name(name), line(0), column(0) +	{ +	} + +	/** +	 * Returns the path to this node. +	 */ +	std::string path(); + +	/** +	 * Compares two XmlNode instances, logs differences to the given +	 * test::logger instance. +	 */ +	bool compareTo(Logger &logger, std::shared_ptr<XmlNode> other, +	               std::set<int> &errExpected, std::set<int> &errActual); +}; + +std::pair<bool, std::shared_ptr<XmlNode>> parseXml(Logger &logger, +                                                       std::istream &is, +                                                       std::set<int> &errLines); + +} +} + +#endif /* _OUSIA_TEST_XML_PARSER_HPP_ */ | 
