Commit fff419e2 authored by aggsol's avatar aggsol 🙉

added stub for shuffle testing

parent 6e247800
......@@ -31,4 +31,5 @@ SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}")
message(STATUS "Version = ${PROJECT_VERSION}")
add_subdirectory(src)
\ No newline at end of file
add_subdirectory(src)
add_subdirectory(test)
\ No newline at end of file
add_executable("gamebook-checker"
gamebook-checker.cpp
add_library(
gamebook
STATIC
Parser.cpp
)
target_include_directories(
gamebook
PUBLIC
$<INSTALL_INTERFACE:include>
${CMAKE_CURRENT_SOURCE_DIR}/src
PRIVATE
)
add_executable("gamebook-checker"
gamebook-checker.cpp
)
target_link_libraries("gamebook-checker" gamebook)
target_compile_definitions("gamebook-checker" PRIVATE _FORTIFY_SOURCE=2)
target_compile_options("gamebook-checker" PRIVATE
-Wall -Wextra -Wshadow -Wnon-virtual-dtor -Wold-style-cast -Woverloaded-virtual -Wzero-as-null-pointer-constant
......
......@@ -19,12 +19,42 @@
#include <cassert>
#include <istream>
#include <fstream>
#include <sstream>
#include <iostream>
namespace bodhi
{
Parser::Parser(const std::string& path)
{
m_file.open(path);
if(not m_file.is_open())
{
throw std::runtime_error("Error : Cannot load " + path);
}
}
bool Parser::parse(std::map<int, Section>& sections)
{
std::string line;
m_lineCounter = 1;
while(m_file)
{
std::getline(m_file, line);
try
{
parseLine(line, sections);
}
catch(const std::exception& ex)
{
std::cerr << "Error : Line:" << m_lineCounter << " :" << ex.what() << "\n";
return true;
}
m_lineCounter++;
}
return false;
}
void Parser::parseLine(const std::string& line, std::map<int, Section>& sections)
{
if(line.empty())
......
......@@ -19,6 +19,7 @@
#define BODHI_PARSER_HPP
#include <map>
#include <fstream>
#include "Section.hpp"
......@@ -27,10 +28,19 @@ namespace bodhi
class Parser
{
public:
void parseLine(const std::string& line, std::map<int, Section>& sections);
explicit Parser(const std::string& path);
/*
* Returns true on error
*/
bool parse(std::map<int, Section>& sections);
int lineCount() const { return m_lineCounter; }
private:
void parseLine(const std::string& line, std::map<int, Section>& sections);
int m_currSection = -1;
enum class LineType
......@@ -53,6 +63,9 @@ private:
ReferenceSection = 6,
Anchor = 8
};
std::ifstream m_file;
int m_lineCounter = 0;
};
}
......
......@@ -20,7 +20,6 @@
#include <cassert>
#include <istream>
#include <fstream>
#include <sstream>
#include <deque>
......@@ -41,30 +40,13 @@ int main(int argc, char* argv[])
auto dot = args.getOpt("d", "dot-file");
auto verbose = args.getOpt("v", "verbose");
std::ifstream file(input);
if(not file.is_open())
{
throw std::runtime_error("Error : Cannot load " + input);
}
std::map<int, bodhi::Section> sections;
bodhi::Parser parser;
std::string line;
int lineCounter = 1;
while(file)
bodhi::Parser parser(input);
if(parser.parse(sections))
{
std::getline(file, line);
try
{
parser.parseLine(line, sections);
}
catch(const std::exception& ex)
{
std::cerr << "Error : Line:" << lineCounter << " :" << ex.what() << "\n";
return 101;
}
lineCounter++;
return 101;
}
// Set parents
......@@ -112,7 +94,7 @@ int main(int argc, char* argv[])
if(verbose)
{
std::cout << "Line count: " << lineCounter << "\n"
std::cout << "Line count: " << parser.lineCount() << "\n"
<< "Number of sections: " << sections.size() << "\n";
for(auto& s: sections)
......
IF(CMAKE_BUILD_TYPE MATCHES Debug)
# add the unittest here
add_executable(
unittest
ShuffleTest.cpp
tiny-unit.cpp
)
target_compile_features(unittest PUBLIC cxx_std_14)
target_link_libraries(unittest gamebook)
ENDIF()
\ No newline at end of file
#include "tiny-unit.hpp"
#include "../src/Parser.hpp"
#include <string>
#include <stdexcept>
class ShuffleTest : public tiny::Unit
{
public:
ShuffleTest()
: tiny::Unit(__FILE__)
{
tiny::Unit::registerTest(&ShuffleTest::basic, "basic");
}
static void basic()
{
bodhi::Parser parser("../test/linear.adoc");
}
};
ShuffleTest shuffleTest;
\ No newline at end of file
[[section-1]]
== 1
AAA
Turn to <<section-2>>
[[section-2]]
== 2
BBB
Turn to <<section-3>>
[[section-3]]
== 3
CCC
Turn to <<section-4>>
[[section-4]]
== 4
DDD
Turn to <<section-5>>
[[section-5]]
== 5
EEE
Turn to <<section-6>>
[[section-6]]
== 6
FFF
Turn to <<section-7>>
[[section-7]]
== 7
GGG
Turn o <<section-8>>
[[section-8]]
== 8
[.fixed]
HHH
Turn to <<section-9>>
[[section-9]]
== 9
III
Turn to <<section-10>>
[[section-10]]
== 10
JJJ
This is the end
/**
* Tiny Unit Test Framework
* Author: Kim <foss@aggsol.de>
*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <foss@aggsol.de> wrote this file. As long as you retain this
* notice you can do whatever you want with this stuff. If we meet some day,
* and you think this stuff is worth it, you can buy me a beer in return KIM
* ----------------------------------------------------------------------------
*/
#include "tiny-unit.hpp"
#include <iostream>
namespace
{
/**
* Force the unit vector to be initialzed before registering any units.
*/
struct TinyUnit
{
std::vector< tiny::Unit* > unitTests = {};
};
TinyUnit& TINY()
{
static TinyUnit tinyUnit;
return tinyUnit;
}
void registerUnit(tiny::Unit* pUnit)
{
TINY().unitTests.push_back(pUnit);
}
}
namespace tiny
{
/**
* On construction register this unit to run.
*/
Unit::Unit(const std::string& name)
: m_name(name)
{
if(name.empty() == true)
{
throw std::runtime_error("Unit test name must not be empty");
}
registerUnit( this );
}
void Unit::registerTest(TestFunc foo, const std::string& n)
{
if(n.empty() == true)
{
throw std::runtime_error("Test name must not be empty");
}
if(foo == nullptr)
{
throw std::runtime_error("Test function must not be null");
}
TestCase testCase;
testCase.foo = foo;
testCase.name = n;
m_testCases.push_back( testCase );
}
/**
* If parameter 'name' is set then only test with matching name run.
* Returns 0 if the the unit passed, 1 otherwise.
*/
unsigned Unit::runTests(const std::string& name)
{
unsigned fail = 0;
if(name.empty())
{
std::cout << "Unit Test: " << m_name << "\n";
}
for(auto & m_testCase : m_testCases)
{
try
{
if(name.size() > 0 && name != m_testCase.name)
{
fail = 1;
continue;
}
m_testCase.foo();
std::cout << "[ Ok ] " << m_testCase.name << "\n";
}
catch(const TestFailed& ex)
{
std::cout << "[FAIL] " << m_testCase.name << " "
<< ex.what() << "\n";
fail = 1;
}
catch(const std::exception& ex)
{
std::cout << "[FAIL] " << m_testCase.name
<< " Unexpected exception: " << ex.what() << "\n";
fail = 1;
}
catch(...)
{
std::cout << "[FAIL] " << m_testCase.name
<< " Unexpected type thrown!\n";
fail = 1;
}
}
return fail;
}
/**
* Test if the expression is false, then report the expression, filename and line.
*/
void assertOk(bool expr, const char* rep, const char* filename, unsigned line)
{
if(expr == false)
{
std::ostringstream msg;
msg << filename << ":" << line << ": <" << rep << "> is false.";
throw TestFailed( msg.str() );
}
}
void handleMissedException(const std::string& type, const char* filename, unsigned line)
{
std::ostringstream msg;
msg << filename << ":" << line
<< ": Expected exception " << type << ". Nothing caught.";
throw tiny::TestFailed( msg.str() );
}
/**
* Loop through all registered units and run their test cases.
*/
int runUnits(const std::string& name)
{
unsigned failedUnits = 0;
const unsigned numUnits = TINY().unitTests.size();
for(unsigned i=0; i<numUnits; ++i)
{
failedUnits += TINY().unitTests[i]->runTests(name);
}
if(name.empty())
{
std::cout << (numUnits-failedUnits) << "/" << numUnits << " unit tests passed.\n";
}
if(failedUnits == 0) return 0;
return failedUnits;
}
}
/**
* Optional first parameter selects matching tests for execution
*/
int main(int argc, char* argv[])
{
if(argc > 2)
{
std::cerr << "Invalid parameters\n";
return 255;
}
std::string name;
if(argc == 2)
{
name.assign(argv[1]);
}
#ifdef CODE_COVERAGE
std::cout << "Force exit code 0\n";
tiny::runUnits(name);
return 0;
#else
return tiny::runUnits(name);
#endif
}
/**
* Tiny Unit Test Framework
* Author: Kim <foss@aggsol.de>
*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <foss@aggsol.de> wrote this file. As long as you retain this
* notice you can do whatever you want with this stuff. If we meet some day,
* and you think this stuff is worth it, you can buy me a beer in return KIM
* ----------------------------------------------------------------------------
*/
#ifndef TINY_UNIT_HPP_
#define TINY_UNIT_HPP_
#include <stdexcept>
#include <string>
#include <sstream>
#include <vector>
/**
* Enable "not" keyword if /permissve- is nor available
*/
#if _MSC_VER
#include <ciso646>
#endif
/**
* Assertion macros for boolean expressions or equality tests.
*/
#define TINY_ASSERT_OK(expr) do { tiny::assertOk(expr, #expr, __FILE__, __LINE__); } while(false)
#define TINY_ASSERT_EQUAL(actual, expected) do { tiny::assertEqual(actual, expected, __FILE__, __LINE__); } while(false)
/**
* Assertion macros for expected exceptions
*/
#define TINY_ASSERT_TRY() do { bool tiny_exception = false; try {
#define TINY_ASSERT_CATCH(type) } catch (const type &) { tiny_exception = true; } if( tiny_exception == false ) { tiny::handleMissedException(#type, __FILE__, __LINE__); } } while(false)
namespace tiny
{
/**
* Function pointer type for test cases
*/
typedef void (*TestFunc) (void);
/**
* A Unit contains test cases. Every test case has to be registered.
*/
class Unit
{
public:
explicit Unit(const std::string& name);
void registerTest(TestFunc foo, const std::string& testName);
unsigned runTests(const std::string& name);
protected:
virtual ~Unit() {}
private:
struct TestCase
{
TestFunc foo { nullptr };
std::string name {} ;
};
Unit() = delete;
Unit(const Unit& other) = delete;
Unit(Unit&& other) = delete;
std::vector< TestCase > m_testCases = {};
std::string m_name;
};
/**
* Exception if a test failed, other exceptions must be catched or
* the test will fail.
*/
class TestFailed : public std::exception
{
public:
explicit TestFailed(const std::string& msg)
: m_message(msg)
{}
~TestFailed() throw() override {}
const char* what() const throw() override
{
return m_message.c_str();
}
private:
TestFailed();
std::string m_message;
};
/**
* Basic assertion for testing
*/
void assertOk(bool expr, const char* rep, const char* filename, unsigned line);
/**
* Assertion for equality
*/
template< typename U, typename V >
void assertEqual(const U& actual, const V& expected, const char* filename, unsigned line)
{
if( !(actual == static_cast<U>(expected)) )
{
std::ostringstream msg;
msg << filename << ":" << line
<< ": Not equal. Expected=<" << expected << "> Actual=<" << actual << ">";
throw TestFailed( msg.str() );
}
}
void handleMissedException(const std::string& type, const char* filename, unsigned line);
}
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment