diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ca37570 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# ---> Vim +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +subprojects +builddir +ttassets +backup +*.exe +*.dll +*.world +coverage +coverage/* +.venv diff --git a/.vimrc_proj b/.vimrc_proj new file mode 100644 index 0000000..2b745b4 --- /dev/null +++ b/.vimrc_proj @@ -0,0 +1 @@ +set makeprg=meson\ compile\ -C\ . diff --git a/LICENSE b/LICENSE index 2071b23..913f2fa 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) +Copyright (c) Zed A. Shaw Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..37752d3 --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +all: build test + +reset: +ifeq '$(OS)' 'Windows_NT' + powershell -executionpolicy bypass .\scripts\reset_build.ps1 +else + sh -x ./scripts/reset_build.sh +endif + +build: + meson compile -j 10 -C builddir + +release_build: + meson --wipe builddir -Db_ndebug=true --buildtype release + meson compile -j 10 -C builddir + +debug_build: + meson setup --wipe builddir -Db_ndebug=true --buildtype debugoptimized + meson compile -j 10 -C builddir + +test: build + ./builddir/fuc2it + +run: build test +ifeq '$(OS)' 'Windows_NT' + powershell "cp ./builddir/fuc2it.exe ." + ./fuc2it +else + ./builddir/fuc2it +endif + +debug: build + gdb --nx -x .gdbinit --ex run --args builddir/fuc2it + +debug_run: build + gdb --nx -x .gdbinit --batch --ex run --ex bt --ex q --args builddir/fuc2it + +clean: + meson compile --clean -C builddir + +debug_test: build + gdb --nx -x .gdbinit --ex run --args builddir/fuc2it -e diff --git a/include/fuc2.hpp b/include/fuc2.hpp new file mode 100644 index 0000000..a24789d --- /dev/null +++ b/include/fuc2.hpp @@ -0,0 +1,57 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace fuc2 { + using Func = std::function; + using Case = std::pair; + + struct Options { + bool fail_fast; + }; + + struct Set { + std::string name; + Options options; + std::vector tests; + std::source_location location = std::source_location::current(); + }; + + std::string craft_error( + const std::string& type, + const std::string& test, + const std::string &message, + const std::source_location location, + const std::source_location test_sig = + std::source_location::current()); + + void CHECK(bool test, const std::string &message="", + const std::source_location location = std::source_location::current()); + + void EQUAL(auto v1, auto v2, const std::string &message="", + const std::source_location location = std::source_location::current()) + { + if(v1 != v2) { + auto test_value = fmt::format("{} != {}", v1, v2); + throw std::runtime_error(craft_error("EQUAL", test_value, message, location)); + } + } + + void NOT_EQUAL(auto v1, auto v2, const std::string &message="", + const std::source_location location = std::source_location::current()) + { + if(v1 == v2) { + auto test_value = fmt::format("{} == {}", v1, v2); + throw std::runtime_error(craft_error("NOT_EQUAL", test_value, message, location)); + } + } + + void BLOWS_UP(std::function cb, const std::string &message, + const std::source_location location = std::source_location::current()); + + int run(const Set& test_set); +} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..d19262a --- /dev/null +++ b/meson.build @@ -0,0 +1,50 @@ +project('fuc2', 'cpp', + version: '0.1.0', + default_options: [ + 'cpp_std=c++23', + 'cpp_args=-D_GLIBCXX_DEBUG=1 -D_GLIBCXX_DEBUG_PEDANTIC=1', + ]) + +# use this for common options only for our executables +cpp_args=[ + '-Wno-unused-parameter', + '-Wno-unused-function', + '-Wno-unused-variable', + '-Wno-unused-but-set-variable', + '-Wno-deprecated-declarations', + ] +link_args=[] +# these are passed as override_defaults +exe_defaults = [ 'warning_level=2', 'werror=false'] + +fmt = subproject('fmt').get_variable('fmt_dep') + +dependencies = [fmt] + +sources = [ + 'src/fuc2.cpp', +] + +fuc2_includes = include_directories('include') + +fuc2_lib = static_library('fuc2', + sources, + pic: true, + cpp_args: cpp_args, + include_directories: fuc2_includes, + override_options: exe_defaults, + dependencies: dependencies) + +fuc2_dep = declare_dependency( + link_with: fuc2_lib, + include_directories: fuc2_includes) + +executable('fuc2it', [ + 'tests/sample1.cpp', + ], + cpp_args: cpp_args, + link_args: link_args, + override_options: exe_defaults, + include_directories: fuc2_includes, + link_with: [fuc2_lib], + dependencies: dependencies) diff --git a/scripts/reset_build.ps1 b/scripts/reset_build.ps1 new file mode 100644 index 0000000..975852d --- /dev/null +++ b/scripts/reset_build.ps1 @@ -0,0 +1,7 @@ +mv .\subprojects\packagecache . +rm -recurse -force .\subprojects\,.\builddir\ +mkdir subprojects +mv .\packagecache .\subprojects\ +mkdir builddir +cp wraps\*.wrap subprojects\ +meson setup --default-library=static --prefer-static builddir diff --git a/scripts/reset_build.sh b/scripts/reset_build.sh new file mode 100644 index 0000000..89931e7 --- /dev/null +++ b/scripts/reset_build.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +mv -f ./subprojects/packagecache . +rm -rf subprojects builddir +mkdir subprojects +mv -f packagecache ./subprojects/ && true +mkdir builddir +cp wraps/*.wrap subprojects/ +# on OSX you can't do this with static +meson setup --default-library=static --prefer-static builddir diff --git a/src/fuc2.cpp b/src/fuc2.cpp new file mode 100644 index 0000000..5e42b09 --- /dev/null +++ b/src/fuc2.cpp @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include +#include "fuc2.hpp" + +namespace fuc2 { + std::string craft_error( + const std::string& type, + const std::string& test, + const std::string &message, + const std::source_location location, + const std::source_location test_sig) + { + return fmt::format( + "{} {}\n" // type message + " LOCATION: {}:{}:{}\n" // file:line:func + " TEST: {}\n" // test + " CALL: {}\n", // call + type, + message, + location.file_name(), + location.line(), + location.function_name(), + test, + test_sig.function_name()); + } + + void CHECK(bool test, const std::string &message, const std::source_location location) + { + if(!test) { + auto test_value = fmt::format("{} != true", test); + throw std::runtime_error( + craft_error("CHECK", test_value, message, location)); + } + } + + void BLOWS_UP(std::function cb, const std::string &message, const std::source_location location) { + bool it_failed = false; + + try { + cb(); + } catch(const std::exception& err) { + // do it this way because I think an optimizer would remove this if empty + it_failed = true; + } + + if(!it_failed) { + throw std::runtime_error(craft_error("BLOWSUP", "", message, location)); + } + } + + int run(const Set& test_set) { + int fail_count = 0; + std::vector errors; + + fmt::println("################# {} \"{}\" ###############", + test_set.location.file_name(), test_set.name); + + for(const auto& [name, test] : test_set.tests) { + bool did_fail = false; + fmt::println("----------- START {}", name); + + try { + test(); + } catch(const std::exception& e) { + fail_count++; + did_fail = true; + fmt::println("🚨 FAIL! {}", e.what()); + errors.push_back(e.what()); + + if(test_set.options.fail_fast) { + break; + } + } + + std::string pass_fail = did_fail ? "🔴" : "🟢"; + fmt::println("{} {}\n==========", pass_fail, name); + } + + if(fail_count > 0) { + fmt::println("🚨 FAIL COUNT: {}", fail_count); + + for(auto& msg : errors) { + fmt::println("---------\n{}", msg); + } + } else { + fmt::println("👍 ALL PASS"); + } + + return fail_count; + } +} diff --git a/tests/sample1.cpp b/tests/sample1.cpp new file mode 100644 index 0000000..86aae54 --- /dev/null +++ b/tests/sample1.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include + +using namespace fuc2; + +void test_push_pop_back() { + std::deque ages; + + for(int i = 0; i < 5; i++) { + ages.push_back(i * 34); + } + + CHECK(ages.size() == 5, "wrong count"); + + for(int i = 0; i < 5; i++) { + ages.pop_back(); + fmt::println("count: {}", ages.size()); + } + + EQUAL(ages.size(), size_t(0), "wrong count"); + NOT_EQUAL(ages.size(), size_t(5), "wrong count"); +} + +void test_push_pop_front() { + std::deque ages; + + for(int i = 0; i < 5; i++) { + ages.push_front(i * 34); + } + + CHECK(ages.size() == 5, "wrong count"); + + for(int i = 0; i < 5; i++) { + ages.pop_front(); + fmt::println("count: {}", ages.size()); + } + + NOT_EQUAL(ages.size(), size_t(0), "wrong count"); +} + +void test_push_blows_up() { + std::deque ages; + + auto runner = [&]() { + // uncomment this to see how the C++ stdlib sabotages you + // ages.pop_front(); + + ages.at(10); + }; + + BLOWS_UP(runner, "pop_front empty should crash"); +} + +int main(int argc, char* argv[]) { + return run({ + .name="std::deque basic operations", + .options={ .fail_fast=false }, + .tests={ + {"push_pop_back", test_push_pop_back}, + {"push_pop_front", test_push_pop_front}, + {"push_blows_up", test_push_blows_up}, + } + }); +} diff --git a/tests/sample_failing.cpp b/tests/sample_failing.cpp new file mode 100644 index 0000000..86aae54 --- /dev/null +++ b/tests/sample_failing.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include + +using namespace fuc2; + +void test_push_pop_back() { + std::deque ages; + + for(int i = 0; i < 5; i++) { + ages.push_back(i * 34); + } + + CHECK(ages.size() == 5, "wrong count"); + + for(int i = 0; i < 5; i++) { + ages.pop_back(); + fmt::println("count: {}", ages.size()); + } + + EQUAL(ages.size(), size_t(0), "wrong count"); + NOT_EQUAL(ages.size(), size_t(5), "wrong count"); +} + +void test_push_pop_front() { + std::deque ages; + + for(int i = 0; i < 5; i++) { + ages.push_front(i * 34); + } + + CHECK(ages.size() == 5, "wrong count"); + + for(int i = 0; i < 5; i++) { + ages.pop_front(); + fmt::println("count: {}", ages.size()); + } + + NOT_EQUAL(ages.size(), size_t(0), "wrong count"); +} + +void test_push_blows_up() { + std::deque ages; + + auto runner = [&]() { + // uncomment this to see how the C++ stdlib sabotages you + // ages.pop_front(); + + ages.at(10); + }; + + BLOWS_UP(runner, "pop_front empty should crash"); +} + +int main(int argc, char* argv[]) { + return run({ + .name="std::deque basic operations", + .options={ .fail_fast=false }, + .tests={ + {"push_pop_back", test_push_pop_back}, + {"push_pop_front", test_push_pop_front}, + {"push_blows_up", test_push_blows_up}, + } + }); +} diff --git a/wraps/fmt.wrap b/wraps/fmt.wrap new file mode 100644 index 0000000..7e9c5c4 --- /dev/null +++ b/wraps/fmt.wrap @@ -0,0 +1,13 @@ +[wrap-file] +directory = fmt-12.0.0 +source_url = https://github.com/fmtlib/fmt/archive/12.0.0.tar.gz +source_filename = fmt-12.0.0.tar.gz +source_hash = aa3e8fbb6a0066c03454434add1f1fc23299e85758ceec0d7d2d974431481e40 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_12.0.0-1/fmt-12.0.0.tar.gz +patch_filename = fmt_12.0.0-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/fmt_12.0.0-1/get_patch +patch_hash = 307f288ebf3850abf2f0c50ac1fb07de97df9538d39146d802f3c0d6cada8998 +wrapdb_version = 12.0.0-1 + +[provide] +dependency_names = fmt