#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common/lib/Array2D.h" #include "common/lib/Array3D.h" #include "common/lib/Array4D.h" #include "common/lib/Graph.h" #include "common/lib/Helper.h" #include "common/lib/Input.h" #include "common/lib/Json.h" #include "common/lib/Mapper.h" #include "common/lib/Math.h" #include "common/lib/Md5.h" #include "common/lib/Timer.h" #define TERM_RED "\x1B[31m" #define TERM_GREEN "\x1B[32m" #define TERM_CLEAR "\033[0m" namespace aoc { template class Test { private: std::string name; std::string inputFile; OutputType expectedOutput; bool isExample = false; int part = 0; public: Test( const std::string& name, const std::string& inputFile, const OutputType& expectedOutput, bool isExample, int part) : name{name}, inputFile{inputFile}, expectedOutput{expectedOutput}, isExample{isExample}, part{part} { std::transform(this->inputFile.begin(), this->inputFile.end(), this->inputFile.begin(), [](char c) { return std::tolower(c); }); } const std::string& GetInputFile() const { return inputFile; } const std::string& GetName() const { return name; } const OutputType& GetExpectedOutput() const { return expectedOutput; } bool IsExample() const { return isExample; } int GetPart() const { return part; } }; class Day { public: virtual bool AocRunTestCases() = 0; virtual const std::string& GetName() const = 0; }; template class DayT : public Day { protected: std::vector*> tests; std::string name; bool isExample = true; public: DayT(const std::string& name) : name{name} { } void AddTest(Test* test) { tests.emplace_back(test); } bool AocRunTestCases() override { for (const auto& test : tests) { isExample = test->IsExample(); std::ifstream inputStream{test->GetInputFile()}; if (!inputStream.is_open()) { std::cout << TERM_RED << "Could not find input file: " << test->GetInputFile() << TERM_CLEAR << std::endl; return false; } Timer timer{}; std::string testName = name + ".P" + std::to_string(test->GetPart()) + "." + test->GetName(); std::cout << TERM_GREEN << "[ RUN ] " << TERM_CLEAR << testName << std::endl; InputType input = ReadInput(inputStream); OutputType output = test->GetPart() == 1 ? Output1(input) : Output2(input); timer.Stop(); if (!Check(test->GetExpectedOutput(), output, testName, timer.Elapsed())) { return false; }; } return true; } const std::string& GetName() const override { return name; } virtual InputType ReadInput(std::istream& input) = 0; virtual OutputType Output1(const InputType& input) = 0; virtual OutputType Output2(const InputType& input) = 0; private: bool Check(const OutputType& expected, const OutputType& actual, const std::string& name, double elapsedTime) const { if (expected == actual) { std::cout << TERM_GREEN << "[ ] " << TERM_CLEAR << " Took: " << elapsedTime << " seconds" << std::endl; std::cout << TERM_GREEN << "[ OK ] " << TERM_CLEAR << " Result: " << actual << std::endl; return true; } else { std::cout << TERM_RED << "[ ] " << TERM_CLEAR << "Unexpected value in " << name << std::endl; std::cout << TERM_RED << "[ ] " << TERM_CLEAR << " expect: " << expected << std::endl; std::cout << TERM_RED << "[ FAILED ] " << TERM_CLEAR << " actual: " << actual << std::endl; return false; } } }; struct ptr_compare { using is_transparent = void; bool operator()(const Day* lhs, const Day* rhs) const { return lhs->GetName() < rhs->GetName(); }; bool operator()(const Day* lhs, const std::string& rhs) const { return lhs->GetName() < rhs; }; bool operator()(const std::string& lhs, const Day* rhs) const { return lhs < rhs->GetName(); }; }; class Registry { private: using Days = std::set; Days days; Day* AddDay(Day* day) { days.emplace(day); return day; } template Test* AddTest(std::string year, std::string day, Test* test) { std::string name = year + "." + day; auto it = days.find(name); if (it == days.end()) { std::cout << TERM_RED << "Could not find Day: " << name << TERM_CLEAR << std::endl; return test; } DayT* dayT = dynamic_cast*>(*it); if (!dayT) { std::cout << TERM_RED << "Failed to cast day: " << name << TERM_CLEAR << std::endl; return test; } dayT->AddTest(test); return test; } public: template static DayImpl* RegisterDay(DayImpl* day) { return (DayImpl*)GetInstance()->AddDay(day); } template static Test* RegisterTest(std::string year, std::string day, Test* test) { return GetInstance()->AddTest(year, day, test); } static Registry* GetInstance(); static Days& GetDays() { return GetInstance()->days; } }; } #define DAY_STR(year, day) #year "." #day #define REGISTER_DAY(year, day, InputTypeType, OutputTypeType) \ using InputType = InputTypeType; \ using OutputType = OutputTypeType; \ class DayImpl : public aoc::DayT \ { \ public: \ DayImpl() \ : DayT(DAY_STR(year, day)) \ { \ } \ InputType ReadInput(std::istream& input) override; \ OutputType Output1(const InputType& input) override; \ OutputType Output2(const InputType& input) override; \ static DayImpl* test; \ }; \ DayImpl* DayImpl::test = aoc::Registry::RegisterDay(new DayImpl()) #define READ_INPUT(x) InputType DayImpl::ReadInput(std::istream& x) #define OUTPUT1(x) OutputType DayImpl::Output1(const InputType& x) #define OUTPUT2(x) OutputType DayImpl::Output2(const InputType& x) #define TEST_NAME_EXAMPLE(year, day, name, part) y##year##day##name##part #define TEST_NAME(year, day, name, part) y##year##day##name##part #define _INTERNAL_REGISTER_TEST_FULL(year, day, name, part, val, testName, inputFile, isExample) \ static aoc::Test* testName = aoc::Registry::RegisterTest( \ #year, #day, new aoc::Test(#name, inputFile, val, isExample, part)) #define REGISTER_TEST_EXAMPLE(year, day, name, part, val) \ _INTERNAL_REGISTER_TEST_FULL(year, \ day, \ name, \ part, \ val, \ TEST_NAME_EXAMPLE(year, day, name, part), \ "res/" #year "/" #day "/test_input.txt", \ true) #define REGISTER_TEST_EXAMPLE_FILE(year, day, name, part, val, inputFileName) \ _INTERNAL_REGISTER_TEST_FULL(year, \ day, \ name, \ part, \ val, \ TEST_NAME_EXAMPLE(year, day, name, part), \ "res/" #year "/" #day "/" + std::string(inputFileName), \ true) #define REGISTER_TEST(year, day, name, part, val) \ _INTERNAL_REGISTER_TEST_FULL( \ year, day, name, part, val, TEST_NAME(year, day, name, part), "res/" #year "/" #day "/input.txt", false) #define REGISTER_TEST_FILE(year, day, name, part, val, inputFileName) \ _INTERNAL_REGISTER_TEST_FULL(year, \ day, \ name, \ part, \ val, \ TEST_NAME(year, day, name, part), \ "res/" #year "/" #day "/" + std::string(inputFileName), \ false)