diff --git a/SConstruct b/SConstruct new file mode 100644 index 0000000..8b24d9e --- /dev/null +++ b/SConstruct @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 + +import sys +import os +import os.path +import glob +import re + +if sys.version_info < (3,): + def isbasestring(s): + return isinstance(s,basestring) +else: + def isbasestring(s): + return isinstance(s, (str,bytes)) + +def add_kel_source_files(self, sources, filetype, lib_env=None, shared=False, target_post=""): + + if isbasestring(filetype): + dir_path = self.Dir('.').abspath + filetype = sorted(glob.glob(dir_path+"/"+filetype)) + + for path in filetype: + target_name = re.sub( r'(.*?)(\.cpp|\.c\+\+)', r'\1' + target_post, path ) + if shared: + target_name+='.os' + sources.append( self.SharedObject( target=target_name, source=path ) ) + else: + target_name+='.o' + sources.append( self.StaticObject( target=target_name, source=path ) ) + pass + +env=Environment(CPPPATH=['#source'], + CXX='clang++', + CPPDEFINES=['GIN_UNIX'], + CXXFLAGS=['-std=c++17','-g','-Wall','-Wextra'], + LIBS=[]) +env.__class__.add_source_files = add_kel_source_files + +env.sources = [] +env.headers = [] +env.objects = [] + +Export('env') +SConscript('source/SConscript') +SConscript('test/SConscript') + +# Clang format part +env.Append(BUILDERS={'ClangFormat' : Builder(action = 'clang-format --style=file -i $SOURCE')}) +env.format_actions = [] +def format_iter(env,files): + for f in files: + env.format_actions.append(env.AlwaysBuild(env.ClangFormat(target=f+"-clang-format",source=f))) + pass + +format_iter(env,env.sources + env.headers) + +env.Alias('format', env.format_actions) +env.Alias('test', env.test_program) diff --git a/source/SConscript b/source/SConscript new file mode 100644 index 0000000..3817910 --- /dev/null +++ b/source/SConscript @@ -0,0 +1,15 @@ +#!/bin/false + +import os +import os.path +import glob + + +Import('env') + +dir_path = Dir('.').abspath + +env.sources += sorted(glob.glob(dir_path + "/*.cpp")) +env.headers += sorted(glob.glob(dir_path + "/*.h")) + +env.add_source_files(env.objects, env.sources) diff --git a/test/SConscript b/test/SConscript new file mode 100644 index 0000000..43bc8fa --- /dev/null +++ b/test/SConscript @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 + +import os +import os.path +import glob + +Import('env') + +dir_path = Dir('.').abspath +env.test_sources = sorted(glob.glob(dir_path + "/*.cpp")) + +env_test = env.Clone() +env.test_objects = [] +env.test_sources.append(dir_path+'/suite/suite.cpp') + +env.test_program = env_test.Program('#bin/test', [env.test_sources, env.objects]) diff --git a/test/suite/suite.cpp b/test/suite/suite.cpp new file mode 100644 index 0000000..023b37e --- /dev/null +++ b/test/suite/suite.cpp @@ -0,0 +1,108 @@ +#include "suite.h" + +#include +#include +#include +#include + +namespace ent { +namespace test { + + TestCase* testCaseHead = nullptr; + TestCase** testCaseTail = &testCaseHead; + + TestCase::TestCase(const std::string& file_, uint line_, const std::string& description_): + file{file_}, + line{line_}, + description{description_}, + matched_filter{false}, + next{nullptr}, + prev{testCaseTail} + { + *prev = this; + testCaseTail = &next; + } + + TestCase::~TestCase(){ + *prev = next; + if( next == nullptr ){ + testCaseTail = prev; + }else{ + next->prev = prev; + } + } + + class TestRunner { + private: + enum Colour { + RED, + GREEN, + BLUE, + WHITE + }; + + void write(Colour colour, const std::string& front, const std::string& message){ + std::string start_col, end_col; + switch(colour){ + case RED: start_col = "\033[0;1;31m"; break; + case GREEN: start_col = "\033[0;1;32m"; break; + case BLUE: start_col = "\033[0;1;34m"; break; + case WHITE: start_col = "\033[0m"; break; + } + end_col = "\033[0m"; + std::cout<next){ + testCase->matched_filter = true; + } + } + + int run() { + size_t passed_count = 0; + size_t failed_count = 0; + + for(TestCase* testCase = testCaseHead; testCase != nullptr; testCase =testCase->next){ + if(testCase->matched_filter){ + std::string name = testCase->file + std::string(":") + std::to_string(testCase->line) + std::string(": ") + testCase->description; + write(BLUE, "[ TEST ] ", name); + bool failed = true; + std::string fail_message; + auto start_clock = std::chrono::steady_clock::now(); + try { + testCase->run(); + failed = false; + }catch(std::exception& e){ + fail_message = e.what(); + failed = true; + } + auto stop_clock = std::chrono::steady_clock::now(); + + auto runtime_duration_intern = stop_clock - start_clock; + auto runtime_duration = std::chrono::duration_cast(runtime_duration_intern); + std::string message = name + std::string(" (") + std::to_string(runtime_duration.count()) + std::string(" µs)"); + if(failed){ + write(RED, "[ FAIL ] ", message + " " + fail_message); + ++failed_count; + }else{ + write(GREEN, "[ PASS ] ", message); + ++passed_count; + } + } + } + + if(passed_count>0) write(GREEN, std::to_string(passed_count) + std::string(" test(s) passed"), ""); + if(failed_count>0) write(RED, std::to_string(failed_count) + std::string(" test(s) failed"), ""); + return -failed_count; + } + }; +} +} + +int main() { + ent::test::TestRunner runner; + runner.allowAll(); + int rv = runner.run(); + return rv<0?-1:0; +} diff --git a/test/suite/suite.h b/test/suite/suite.h new file mode 100644 index 0000000..c68bfd6 --- /dev/null +++ b/test/suite/suite.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include + +#define KEL_CONCAT_(x,y) x##y +#define KEL_CONCAT(x,y) KEL_CONCAT_(x,y) +#define KEL_UNIQUE_NAME(prefix) KEL_CONCAT(prefix, __LINE__) + +namespace ent { +namespace test { +class TestRunner; +class TestCase { +private: + std::string file; + uint line; + std::string description; + bool matched_filter; + TestCase* next; + TestCase** prev; + + friend class TestRunner; +public: + TestCase(const std::string& file_, uint line_, const std::string& description_); + ~TestCase(); + + virtual void run() = 0; +}; +} +} +#define KEL_TEST(description) \ + class KEL_UNIQUE_NAME(TestCase) : public ::ent::test::TestCase { \ + public: \ + KEL_UNIQUE_NAME(TestCase)(): ::ent::test::TestCase(__FILE__,__LINE__,description) {} \ + void run() override; \ + }KEL_UNIQUE_NAME(testCase); \ + void KEL_UNIQUE_NAME(TestCase)::run() + +#define KEL_EXPECT(expr, msg) \ + if( ! (expr) ){ \ + throw std::runtime_error{msg}; \ + }