summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorAndreas Stöckel <astoecke@techfak.uni-bielefeld.de>2015-04-01 22:05:58 +0200
committerAndreas Stöckel <astoecke@techfak.uni-bielefeld.de>2016-04-25 22:19:29 +0200
commit0e3cf1963a4c5fa28f12a70b7d2f25c33580c182 (patch)
tree2ae365ad3a955c29491fb7f8f1c723e0899a2305 /test
parentac550e242be6e747f9420b705a33407652010b6c (diff)
Implement integration test framework
Diffstat (limited to 'test')
-rw-r--r--test/integration/Main.cpp353
-rw-r--r--test/integration/TestLogger.cpp70
-rw-r--r--test/integration/TestLogger.hpp63
-rw-r--r--test/integration/TestXmlParser.cpp249
-rw-r--r--test/integration/TestXmlParser.hpp89
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, &currentNode);
+
+ // 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_ */