diff --git a/catch2/Dockerfile b/catch2/Dockerfile new file mode 100644 index 0000000..1d385be --- /dev/null +++ b/catch2/Dockerfile @@ -0,0 +1,27 @@ +FROM alpine:latest + +RUN apk update \ + && apk upgrade \ + && apk add --no-cache \ + clang \ + clang-dev \ + alpine-sdk \ + dpkg \ + cmake \ + ccache \ + ninja + +RUN ln -sf /usr/bin/clang /usr/bin/cc \ + && ln -sf /usr/bin/clang++ /usr/bin/c++ \ + && update-alternatives --install /usr/bin/cc cc /usr/bin/clang 10\ + && update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 10\ + && update-alternatives --auto cc \ + && update-alternatives --auto c++ \ + && update-alternatives --display cc \ + && update-alternatives --display c++ \ + && ls -l /usr/bin/cc /usr/bin/c++ \ + && cc --version \ + && c++ --version + +WORKDIR /code +COPY docker_image . diff --git a/catch2/README.md b/catch2/README.md index 7a8d93c..e84bf04 100644 --- a/catch2/README.md +++ b/catch2/README.md @@ -1,4 +1,12 @@ -# Catch2 +# GoogleTest + +## Scripts for local Docker + +Locally I use Windows, that's why my scripts are batch files. +- `build-image.bat` builds an image to run C++ kata, +- `run-image.bat` runs code of a kata in a container. Use `run-image KATA-CODE` to run, whenre `KATA-CODE` is a name of one of subdirectories in `kata_code` directory. + +## Docker image > `CodewarsReporter` is unfinished. @@ -9,10 +17,21 @@ cmake --build build && ./build/tests/tests --reporter=codewars Use `./configure.sh` and `./run-tests.sh`. - We'll configure and build an example project while building the container image so each submission will only build the submitted code. ---- - I haven't figured out the timings of the test events used in the custom reporter yet. The tests can be written more naturally, but it might be difficult to get the output we want. + +The `snippets` directory contains snippets of example kata as they are submitted, before preprocessing. + +The `kata_code` directory contains source file of a kata as produced by the CW preprocessor. The files are cp-ed into a container. + +## Example kata + +- `ExampleExceptions` - a set of test cases to evaluate output of the reporter in case of expected and unexpected exceptions. +- `ExampleFailureMessages` - showcase of assertions and matchers most useful for CW authors. +- `ExampleOrganization` - a set of test fixtures instantiated in various ways evaluate output of the reporter (especially grouping of tests). +- `ExampleParametrized` - a set of parametrized/generated tests to evaluate output of the reporter. +- `OriginalExample` - the original example of Google Test tests prepared by kazk. It uses no CW preprocessor markers, and only `preloaded.h` and `solution.cpp` are relevant. `preloaded.cpp` and `solution.h` are also saved, but are irrelevant, and cause no problems when buiding and testing the kata. +- `UniqueInOrder` - a kata which requires separate cpp file and header file for solution, because it requires users to implement two functions: one is a template, and one is a "normal" function. It uses CW preprocessor markers to split submitted solution snippet into two files. +- `Cafeteria` - a kata with no preprocessing markers, with a complex preloaded, where both solution and preloaded are treated as headers, and the cpp part of both gets (apparently) ignored. It also defines custom `operator <<` for the preloaded types, which is used by GTest to present instances in failure messages. To be honest, I am not sure why this example works because I expected it to fail with linker errors caused by standalone definitions of `operator<<`. I need to take a better look. diff --git a/catch2/build_image.bat b/catch2/build_image.bat new file mode 100644 index 0000000..f46827d --- /dev/null +++ b/catch2/build_image.bat @@ -0,0 +1 @@ +docker build --tag cw-catch2-cpp . \ No newline at end of file diff --git a/catch2/docker_image/.gitignore b/catch2/docker_image/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/catch2/docker_image/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/catch2/CMakeLists.txt b/catch2/docker_image/CMakeLists.txt similarity index 100% rename from catch2/CMakeLists.txt rename to catch2/docker_image/CMakeLists.txt diff --git a/catch2/configure.sh b/catch2/docker_image/configure.sh old mode 100755 new mode 100644 similarity index 70% rename from catch2/configure.sh rename to catch2/docker_image/configure.sh index f9bd0b8..6646ddb --- a/catch2/configure.sh +++ b/catch2/docker_image/configure.sh @@ -1,2 +1,2 @@ -#!/bin/bash +#!/bin/sh cmake -S . -B build -G Ninja diff --git a/catch2/docker_image/run-kata.sh b/catch2/docker_image/run-kata.sh new file mode 100644 index 0000000..4aa9cda --- /dev/null +++ b/catch2/docker_image/run-kata.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +./configure.sh && ./run-tests.sh \ No newline at end of file diff --git a/catch2/run-tests.sh b/catch2/docker_image/run-tests.sh old mode 100755 new mode 100644 similarity index 84% rename from catch2/run-tests.sh rename to catch2/docker_image/run-tests.sh index ffe2095..830b5aa --- a/catch2/run-tests.sh +++ b/catch2/docker_image/run-tests.sh @@ -1,3 +1,3 @@ -#!/bin/bash +#!/bin/sh cmake --build build && ./build/tests/tests --reporter=codewars diff --git a/gtest/src/CMakeLists.txt b/catch2/docker_image/src/CMakeLists.txt similarity index 68% rename from gtest/src/CMakeLists.txt rename to catch2/docker_image/src/CMakeLists.txt index bd71121..73fdc2f 100644 --- a/gtest/src/CMakeLists.txt +++ b/catch2/docker_image/src/CMakeLists.txt @@ -1,6 +1,7 @@ set(CMAKE_MESSAGE_LOG_LEVEL WARNING) set(CMAKE_RULE_MESSAGES OFF) set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) FetchContent_Declare( fmt @@ -9,10 +10,10 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(fmt) -find_package(Threads REQUIRED) -add_library(challenge solution.cpp) +add_library(challenge preloaded.cpp solution.cpp) target_include_directories(challenge PUBLIC ../include) target_compile_features(challenge PUBLIC cxx_std_20) target_compile_options(challenge PUBLIC -Wall -Wextra -O2) -target_link_libraries(challenge PUBLIC m crypto dl sqlite3 tbb fmt Threads::Threads) +# target_link_libraries(challenge PUBLIC m crypto dl sqlite3 tbb fmt Threads::Threads) +target_link_libraries(challenge PUBLIC m dl fmt Threads::Threads) diff --git a/catch2/tests/CMakeLists.txt b/catch2/docker_image/tests/CMakeLists.txt similarity index 94% rename from catch2/tests/CMakeLists.txt rename to catch2/docker_image/tests/CMakeLists.txt index 70c8a94..d681c4f 100644 --- a/catch2/tests/CMakeLists.txt +++ b/catch2/docker_image/tests/CMakeLists.txt @@ -22,7 +22,7 @@ add_executable(tests codewars_reporter.cpp test_solution.cpp) target_compile_features(tests PRIVATE cxx_std_20) target_compile_options(tests PRIVATE -Wall -Wextra -O2) # target_link_libraries(tests PRIVATE challenge Catch2::Catch2 m crypto dl sqlite3 tbb fmt Threads::Threads) -target_link_libraries(tests PRIVATE challenge Catch2::Catch2WithMain m crypto dl sqlite3 tbb fmt Threads::Threads) +target_link_libraries(tests PRIVATE challenge Catch2::Catch2WithMain m dl fmt Threads::Threads) # Not really necessary for CR, but allows running tests through CMake add_test(NAME tests COMMAND tests) diff --git a/catch2/docker_image/tests/codewars_reporter.cpp b/catch2/docker_image/tests/codewars_reporter.cpp new file mode 100644 index 0000000..8797724 --- /dev/null +++ b/catch2/docker_image/tests/codewars_reporter.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include + +#include +#include + +// TODO Complete custom reporter +// - https://github.com/catchorg/Catch2/blob/devel/docs/reporters.md +// - https://github.com/catchorg/Catch2/blob/devel/docs/reporter-events.md +// - https://github.com/catchorg/Catch2/blob/devel/docs/test-cases-and-sections.md +class CodewarsReporter : public Catch::StreamingReporterBase { + +private: + unsigned nestedSections = 0; + +public: + using StreamingReporterBase::StreamingReporterBase; + + CodewarsReporter( Catch::ReporterConfig&& config ): + StreamingReporterBase( CATCH_MOVE(config) ) { + // This is necessary to emit to syntetic, per-assertion IT's + m_preferences.shouldReportAllAssertions = true; + } + + static std::string getDescription() { + return "Reporter for Codewars"; + } + + // Emitted before the first test case is executed. + void testRunStarting(Catch::TestRunInfo const& testRunInfo) override { + std::cout << "\ntestRunStarting " << testRunInfo.name << '\n'; + } + + // Emitted after all the test cases have been executed. + void testRunEnded(__attribute__((unused)) Catch::TestRunStats const& testRunStats) override { + std::cout << "\ntestRunEnded\n"; + } + + void sectionStarting(Catch::SectionInfo const& sectionInfo) override { + // Do not emit groups for implicit sections, put results in + // the related test case (or test case partial) + if(nestedSections++ <= 0) + return; + std::cout << "\n[S]" << sectionInfo.name << '\n'; + } + + void sectionEnded(__attribute__((unused)) Catch::SectionStats const& sectionStats) override { + if(--nestedSections <= 0) + return; + std::cout << "\n\n"; + } + + void testCaseStarting(Catch::TestCaseInfo const& testInfo) override { + std::cout << "\n[TC]" << testInfo.name << '\n'; + } + + void testCaseEnded(__attribute__((unused)) Catch::TestCaseStats const& testCaseStats) override { + std::cout << "\n\n"; + } + + void testCasePartialStarting(Catch::TestCaseInfo const& testInfo, uint64_t partNumber) override { + std::cout << "\n[TCP]" << testInfo.name << '#' << partNumber << '\n'; + } + + void testCasePartialEnded(__attribute__((unused)) Catch::TestCaseStats const& testCaseStats, __attribute__((unused)) uint64_t partNumber) override { + std::cout << "\n\n"; + } + + void assertionStarting(__attribute__((unused)) Catch::AssertionInfo const& assertionInfo ) override { + std::cout << "\nAssertion: " << assertionInfo.macroName << "(" << assertionInfo.capturedExpression << ")\n"; + } + void assertionEnded(Catch::AssertionStats const& assertionStats ) override { + const Catch::AssertionResult& result = assertionStats.assertionResult; + std::string resultTag = result.succeeded() ? "PASSED" : "FAILED"; + std::string message = result.hasMessage() ? static_cast(result.getMessage()) : (result.succeeded() ? "Test passed" : "Test failed"); + std::cout << "\n<" << resultTag << "::>" << message << "\n"; + std::cout << "\n\n"; + } + +}; + + +CATCH_REGISTER_REPORTER("codewars", CodewarsReporter) \ No newline at end of file diff --git a/catch2/kata_code/ExampleOrganization/include/preloaded.h b/catch2/kata_code/ExampleOrganization/include/preloaded.h new file mode 100644 index 0000000..e69de29 diff --git a/catch2/kata_code/ExampleOrganization/include/solution.h b/catch2/kata_code/ExampleOrganization/include/solution.h new file mode 100644 index 0000000..e69de29 diff --git a/catch2/kata_code/ExampleOrganization/src/preloaded.cpp b/catch2/kata_code/ExampleOrganization/src/preloaded.cpp new file mode 100644 index 0000000..e69de29 diff --git a/catch2/kata_code/ExampleOrganization/src/solution.cpp b/catch2/kata_code/ExampleOrganization/src/solution.cpp new file mode 100644 index 0000000..e69de29 diff --git a/catch2/kata_code/ExampleOrganization/tests/test_solution.cpp b/catch2/kata_code/ExampleOrganization/tests/test_solution.cpp new file mode 100644 index 0000000..415485e --- /dev/null +++ b/catch2/kata_code/ExampleOrganization/tests/test_solution.cpp @@ -0,0 +1,143 @@ +#include + +#include +#include + + +TEST_CASE( "TestCase1" ) { + + REQUIRE( 5 == 5 ); + REQUIRE( 12 >= 5 ); + + SECTION( "Outermost section 1" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + + SECTION( "MidLevel section 11" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + + SECTION( "Innermost section 111" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + } + SECTION( "Innermost section 112" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + } + } + SECTION( "MidLevel section 21" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + + SECTION( "Innermost section 121" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + } + SECTION( "Innermost section 221" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + } + } + } + SECTION( "Outermost section 2" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + + SECTION( "MidLevel section 12" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + + SECTION( "Innermost section 112" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + } + SECTION( "Innermost section 212" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + } + } + SECTION( "MidLevel section 22" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + + SECTION( "Innermost section 122" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + } + SECTION( "Innermost section 122" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + } + } + } +} + +TEST_CASE( "TestCase2" ) { + + REQUIRE( 5 == 5 ); + REQUIRE( 12 >= 5 ); + + SECTION( "Outermost section 1" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + + SECTION( "MidLevel section 11" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + + SECTION( "Innermost section 111" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + } + SECTION( "Innermost section 112" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + } + } + SECTION( "MidLevel section 21" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + + SECTION( "Innermost section 121" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + } + SECTION( "Innermost section 221" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + } + } + } + SECTION( "Outermost section 2" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + + SECTION( "MidLevel section 12" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + + SECTION( "Innermost section 112" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + } + SECTION( "Innermost section 212" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + } + } + SECTION( "MidLevel section 22" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + + SECTION( "Innermost section 122" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + } + SECTION( "Innermost section 122" ) { + REQUIRE( 10 == 10 ); + REQUIRE( 15 >= 10 ); + } + } + } +} \ No newline at end of file diff --git a/gtest/include/challenge.h b/catch2/kata_code/OriginalExample/include/preloaded.h similarity index 99% rename from gtest/include/challenge.h rename to catch2/kata_code/OriginalExample/include/preloaded.h index 79722ce..5463ce5 100644 --- a/gtest/include/challenge.h +++ b/catch2/kata_code/OriginalExample/include/preloaded.h @@ -12,4 +12,4 @@ class Counter { int Increment(); // Returns the current counter value, and decrements it. int Decrement(); -}; +}; \ No newline at end of file diff --git a/catch2/kata_code/OriginalExample/include/solution.h b/catch2/kata_code/OriginalExample/include/solution.h new file mode 100644 index 0000000..e69de29 diff --git a/catch2/kata_code/OriginalExample/src/preloaded.cpp b/catch2/kata_code/OriginalExample/src/preloaded.cpp new file mode 100644 index 0000000..e69de29 diff --git a/catch2/src/solution.cpp b/catch2/kata_code/OriginalExample/src/solution.cpp similarity index 93% rename from catch2/src/solution.cpp rename to catch2/kata_code/OriginalExample/src/solution.cpp index 50991b8..8bcfe79 100644 --- a/catch2/src/solution.cpp +++ b/catch2/kata_code/OriginalExample/src/solution.cpp @@ -1,4 +1,4 @@ -#include +#include // Returns the current counter value, and increments it. int Counter::Increment() { diff --git a/catch2/tests/test_solution.cpp b/catch2/kata_code/OriginalExample/tests/test_solution.cpp similarity index 97% rename from catch2/tests/test_solution.cpp rename to catch2/kata_code/OriginalExample/tests/test_solution.cpp index 8027f42..f4356c9 100644 --- a/catch2/tests/test_solution.cpp +++ b/catch2/kata_code/OriginalExample/tests/test_solution.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include @@ -47,4 +47,4 @@ TEST_CASE("Counter") { REQUIRE(c.Increment() == 2); REQUIRE(c.Decrement() == 3); } -} +} \ No newline at end of file diff --git a/catch2/run_image.bat b/catch2/run_image.bat new file mode 100644 index 0000000..a9aef33 --- /dev/null +++ b/catch2/run_image.bat @@ -0,0 +1,8 @@ +set KATA=%1 +set IMAGE=cw-catch2-cpp +set WORKDIR=/code/ +set CREATE_CMD=docker container create --rm -w %WORKDIR% %IMAGE% ./run-kata.sh + +FOR /F %%i IN ('%CREATE_CMD%') DO set CONTAINER=%%i +docker cp ./kata_code/%KATA%/. %CONTAINER%:%WORKDIR% +docker start --attach -i %CONTAINER% diff --git a/catch2/snippets/OriginalExample/preloaded.snippet b/catch2/snippets/OriginalExample/preloaded.snippet new file mode 100644 index 0000000..b66b3ff --- /dev/null +++ b/catch2/snippets/OriginalExample/preloaded.snippet @@ -0,0 +1,20 @@ +#ifdef PRELOADED_INCLUDED +#define PRELOADED_INCLUDED + +#pragma once + +// A simple monotonic counter. +class Counter { + private: + int counter_; + + public: + // Creates a counter that starts at 0. + Counter() : counter_(0) {} + // Returns the current counter value, and increments it. + int Increment(); + // Returns the current counter value, and decrements it. + int Decrement(); +}; + +#endif PRELOADED_INCLUDED diff --git a/catch2/snippets/OriginalExample/solution.snippet b/catch2/snippets/OriginalExample/solution.snippet new file mode 100644 index 0000000..8bcfe79 --- /dev/null +++ b/catch2/snippets/OriginalExample/solution.snippet @@ -0,0 +1,16 @@ +#include + +// Returns the current counter value, and increments it. +int Counter::Increment() { + return counter_++; +} + +// Returns the current counter value, and decrements it. +// counter can not be less than 0, return 0 in this case +int Counter::Decrement() { + if (counter_ == 0) { + return counter_; + } else { + return counter_--; + } +} diff --git a/catch2/snippets/OriginalExample/test_solution.snippet b/catch2/snippets/OriginalExample/test_solution.snippet new file mode 100644 index 0000000..9d3ae01 --- /dev/null +++ b/catch2/snippets/OriginalExample/test_solution.snippet @@ -0,0 +1,64 @@ +#include +#include +#include + +namespace { +TEST(Vector_size_resize, resizing_bigger_changes_size_and_capacity) { + std::vector v( 5 ); + EXPECT_EQ( v.size(), 5 ); + EXPECT_GE( v.capacity(), 5 ); + + v.resize( 10 ); + EXPECT_EQ( v.size(), 10 ); + EXPECT_GE( v.capacity(), 10 ); +} + +TEST(Vector_size_resize, resizing_smaller_changes_size_but_not_capacity) { + std::vector v( 5 ); + EXPECT_EQ( v.size(), 5 ); + EXPECT_GE( v.capacity(), 5 ); + v.resize( 0 ); + + EXPECT_EQ( v.size(), 0 ); + EXPECT_GE( v.capacity(), 5 ); +} + +TEST(Vector_size_resize, reserving_bigger_changes_capacity_but_not_size) { + std::vector v( 5 ); + EXPECT_EQ( v.size(), 5 ); + EXPECT_GE( v.capacity(), 5 ); + v.reserve( 10 ); + + EXPECT_EQ( v.size(), 5 ); + EXPECT_GE( v.capacity(), 10 ); +} + +TEST(Vector_size_resize, reserving_smaller_does_not_change_size_or_capacity) { + std::vector v( 5 ); + EXPECT_EQ( v.size(), 5 ); + EXPECT_GE( v.capacity(), 5 ); + v.reserve( 0 ); + + EXPECT_EQ( v.size(), 5 ); + EXPECT_GE( v.capacity(), 5 ); +} + +TEST(Counter, Increment) { // Tests the Increment() method. + Counter c; + + // Test that counter 0 returns 0 + EXPECT_EQ(0, c.Decrement()); + + // EXPECT_EQ() evaluates its arguments exactly once, so they + // can have side effects. + + EXPECT_EQ(0, c.Increment()); + EXPECT_EQ(1, c.Increment()); + EXPECT_EQ(2, c.Increment()); + + EXPECT_EQ(3, c.Decrement()); +} + + + +} \ No newline at end of file diff --git a/catch2/tests/codewars_reporter.cpp b/catch2/tests/codewars_reporter.cpp deleted file mode 100644 index d0e0d6a..0000000 --- a/catch2/tests/codewars_reporter.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include -#include -#include -#include -#include - -#include - -// TODO Complete custom reporter -// - https://github.com/catchorg/Catch2/blob/devel/docs/reporters.md -// - https://github.com/catchorg/Catch2/blob/devel/docs/reporter-events.md -// - https://github.com/catchorg/Catch2/blob/devel/docs/test-cases-and-sections.md -class CodewarsReporter : public Catch::StreamingReporterBase { -public: - using StreamingReporterBase::StreamingReporterBase; - - static std::string getDescription() { - return "Reporter for Codewars"; - } - - // Emitted before the first test case is executed. - void testRunStarting(Catch::TestRunInfo const& testRunInfo) override { - std::cout << "testRunStarting " << testRunInfo.name << '\n'; - } - - // Emitted after all the test cases have been executed. - void testRunEnded(__attribute__((unused)) Catch::TestRunStats const& testRunStats) override { - std::cout << "testRunEnded\n"; - } - - void sectionStarting(__attribute__((unused)) Catch::SectionInfo const& sectionInfo) override { - // Cannot be used for group? Each testcase has implicit section. - std::cout << "\n" << sectionInfo.name << '\n'; - } - - void sectionEnded(__attribute__((unused)) Catch::SectionStats const& sectionStats) override { - std::cout << "\n\n"; - } - - void testCaseStarting(__attribute__((unused)) Catch::TestCaseInfo const& testInfo) override { - std::cout << "\n" << testInfo.name << '\n'; - } - - void testCaseEnded(__attribute__((unused)) Catch::TestCaseStats const& testCaseStats) override { - std::cout << "\n\n"; - } - - void testCasePartialStarting(Catch::TestCaseInfo const& testInfo, uint64_t partNumber) override { - std::cout << "TestCaseStartingPartial: " << testInfo.name << '#' << partNumber << '\n'; - } - - void testCasePartialEnded(Catch::TestCaseStats const& testCaseStats, uint64_t partNumber) override { - std::cout << "TestCasePartialEnded: " << testCaseStats.testInfo->name << '#' << partNumber << '\n'; - } -}; - - -CATCH_REGISTER_REPORTER("codewars", CodewarsReporter) diff --git a/gtest/Dockerfile b/gtest/Dockerfile new file mode 100644 index 0000000..1b464a6 --- /dev/null +++ b/gtest/Dockerfile @@ -0,0 +1,33 @@ +FROM alpine:latest + +RUN apk update \ + && apk upgrade \ + && apk add --no-cache \ + clang \ + clang-dev \ + alpine-sdk \ + dpkg \ + cmake \ + ccache \ + gtest \ + gtest-dev \ + ninja + +RUN ln -sf /usr/bin/clang /usr/bin/cc \ + && ln -sf /usr/bin/clang++ /usr/bin/c++ \ + && update-alternatives --install /usr/bin/cc cc /usr/bin/clang 10\ + && update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 10\ + && update-alternatives --auto cc \ + && update-alternatives --auto c++ \ + && update-alternatives --display cc \ + && update-alternatives --display c++ \ + && ls -l /usr/bin/cc /usr/bin/c++ \ + && cc --version \ + && c++ --version + +WORKDIR /code +COPY docker_image . + +# ENV APP_NAME='' + +# ENTRYPOINT ["/code/entrypoint.sh"] \ No newline at end of file diff --git a/gtest/README.md b/gtest/README.md index 0ce5b82..e9ebb99 100644 --- a/gtest/README.md +++ b/gtest/README.md @@ -1,5 +1,15 @@ # GoogleTest +## Scripts for local Docker + +Locally I use Windows, that's why my scripts are batch files. +- `build-image.bat` builds an image to run C++ kata, +- `run-image.bat` runs code of a kata in a container. Use `run-image KATA-CODE` to run, whenre `KATA-CODE` is a name of one of subdirectories in `kata_code` directory. + +## Docker image + +Kata code is built and run with: + ```bash cmake -S . -B build -G Ninja cmake --build build && ./build/tests/tests @@ -8,3 +18,17 @@ cmake --build build && ./build/tests/tests Use `./configure.sh` and `./run-tests.sh`. We'll configure and build an example project while building the container image so each submission will only build the submitted code. + +The `snippets` directory contains snippets of example kata as they are submitted, before preprocessing. + +The `kata_code` directory contains source file of a kata as produced by the CW preprocessor. The files are cp-ed into a container. + +## Example kata + +- `ExampleExceptions` - a set of test cases to evaluate output of the reporter in case of expected and unexpected exceptions. +- `ExampleFailureMessages` - showcase of assertions and matchers most useful for CW authors. +- `ExampleOrganization` - a set of test fixtures instantiated in various ways evaluate output of the reporter (especially grouping of tests). +- `ExampleParametrized` - a set of parametrized/generated tests to evaluate output of the reporter. +- `OriginalExample` - the original example of Google Test tests prepared by kazk. It uses no CW preprocessor markers, and only `preloaded.h` and `solution.cpp` are relevant. `preloaded.cpp` and `solution.h` are also saved, but are irrelevant, and cause no problems when buiding and testing the kata. +- `UniqueInOrder` - a kata which requires separate cpp file and header file for solution, because it requires users to implement two functions: one is a template, and one is a "normal" function. It uses CW preprocessor markers to split submitted solution snippet into two files. +- `Cafeteria` - a kata with no preprocessing markers, with a complex preloaded, where both solution and preloaded are treated as headers, and the cpp part of both gets (apparently) ignored. It also defines custom `operator <<` for the preloaded types, which is used by GTest to present instances in failure messages. To be honest, I am not sure why this example works because I expected it to fail with linker errors caused by standalone definitions of `operator<<`. I need to take a better look. diff --git a/gtest/build_image.bat b/gtest/build_image.bat new file mode 100644 index 0000000..9c249d1 --- /dev/null +++ b/gtest/build_image.bat @@ -0,0 +1 @@ +docker build --tag cw-gtest-cpp . \ No newline at end of file diff --git a/gtest/CMakeLists.txt b/gtest/docker_image/CMakeLists.txt similarity index 100% rename from gtest/CMakeLists.txt rename to gtest/docker_image/CMakeLists.txt diff --git a/gtest/configure.sh b/gtest/docker_image/configure.sh old mode 100755 new mode 100644 similarity index 70% rename from gtest/configure.sh rename to gtest/docker_image/configure.sh index f9bd0b8..6646ddb --- a/gtest/configure.sh +++ b/gtest/docker_image/configure.sh @@ -1,2 +1,2 @@ -#!/bin/bash +#!/bin/sh cmake -S . -B build -G Ninja diff --git a/gtest/docker_image/run-kata.sh b/gtest/docker_image/run-kata.sh new file mode 100644 index 0000000..4aa9cda --- /dev/null +++ b/gtest/docker_image/run-kata.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +./configure.sh && ./run-tests.sh \ No newline at end of file diff --git a/gtest/run-tests.sh b/gtest/docker_image/run-tests.sh old mode 100755 new mode 100644 similarity index 78% rename from gtest/run-tests.sh rename to gtest/docker_image/run-tests.sh index eead123..e680ada --- a/gtest/run-tests.sh +++ b/gtest/docker_image/run-tests.sh @@ -1,3 +1,3 @@ -#!/bin/bash +#!/bin/sh cmake --build build && ./build/tests/tests diff --git a/catch2/src/CMakeLists.txt b/gtest/docker_image/src/CMakeLists.txt similarity index 68% rename from catch2/src/CMakeLists.txt rename to gtest/docker_image/src/CMakeLists.txt index e1e18b4..82e1f39 100644 --- a/catch2/src/CMakeLists.txt +++ b/gtest/docker_image/src/CMakeLists.txt @@ -1,7 +1,6 @@ set(CMAKE_MESSAGE_LOG_LEVEL WARNING) set(CMAKE_RULE_MESSAGES OFF) set(THREADS_PREFER_PTHREAD_FLAG ON) -find_package(Threads REQUIRED) FetchContent_Declare( fmt @@ -10,9 +9,11 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(fmt) +find_package(Threads REQUIRED) -add_library(challenge solution.cpp) +add_library(challenge solution.cpp preloaded.cpp) target_include_directories(challenge PUBLIC ../include) target_compile_features(challenge PUBLIC cxx_std_20) target_compile_options(challenge PUBLIC -Wall -Wextra -O2) -target_link_libraries(challenge PUBLIC m crypto dl sqlite3 tbb fmt Threads::Threads) +# target_link_libraries(challenge PUBLIC m crypto dl sqlite3 tbb fmt Threads::Threads) +target_link_libraries(challenge PUBLIC m dl fmt Threads::Threads) diff --git a/gtest/tests/CMakeLists.txt b/gtest/docker_image/tests/CMakeLists.txt similarity index 79% rename from gtest/tests/CMakeLists.txt rename to gtest/docker_image/tests/CMakeLists.txt index 138c297..1b39279 100644 --- a/gtest/tests/CMakeLists.txt +++ b/gtest/docker_image/tests/CMakeLists.txt @@ -20,7 +20,8 @@ FetchContent_MakeAvailable(fmt) add_executable(tests main.cpp test_solution.cpp) target_compile_features(tests PRIVATE cxx_std_20) target_compile_options(tests PRIVATE -Wall -Wextra -O2) -target_link_libraries(tests PRIVATE challenge gtest gmock m crypto dl sqlite3 tbb fmt Threads::Threads) +# target_link_libraries(tests PRIVATE challenge gtest gmock m crypto dl sqlite3 tbb fmt Threads::Threads) +target_link_libraries(tests PRIVATE challenge gtest gmock m dl fmt Threads::Threads) # Not really necessary for CR, but allows running tests through CMake add_test(NAME tests COMMAND tests) diff --git a/gtest/tests/main.cpp b/gtest/docker_image/tests/main.cpp similarity index 78% rename from gtest/tests/main.cpp rename to gtest/docker_image/tests/main.cpp index 59b3d1f..ce40d3d 100644 --- a/gtest/tests/main.cpp +++ b/gtest/docker_image/tests/main.cpp @@ -1,10 +1,17 @@ -#include +#include +#include +#include + #include +using ::std::fprintf; +using ::std::fflush; + using ::testing::EmptyTestEventListener; using ::testing::InitGoogleTest; using ::testing::Test; using ::testing::TestCase; +using ::testing::TestSuite; using ::testing::TestEventListeners; using ::testing::TestInfo; using ::testing::TestResult; @@ -21,10 +28,16 @@ class QualifiedReporter : public EmptyTestEventListener { fflush(stdout); } - // TODO Group by `test_case_name`? It's not identifiers so uniqueness is not guaranteed. - // Called before a test starts. + void OnTestSuiteStart(const TestSuite& test_suite) override { + fprintf(stdout, "\n%s\n", test_suite.name()); + } + void OnTestSuiteEnd(const TestSuite& test_suite) override { + fprintf(stdout, "\n%" PRId64 "\n", test_suite.elapsed_time()); + fflush(stdout); + } + void OnTestStart(const TestInfo& test_info) override { - fprintf(stdout, "\n%s %s\n", test_info.test_case_name(), test_info.name()); + fprintf(stdout, "\n%s\n", test_info.name()); fflush(stdout); } @@ -44,7 +57,7 @@ class QualifiedReporter : public EmptyTestEventListener { if (result.Passed()) { fprintf(stdout, "\nTest Passed\n"); } - fprintf(stdout, "\n%ld\n", result.elapsed_time()); + fprintf(stdout, "\n%" PRId64 "\n", result.elapsed_time()); fflush(stdout); } diff --git a/gtest/kata_code/Cafeteria/include/preloaded.h b/gtest/kata_code/Cafeteria/include/preloaded.h new file mode 100644 index 0000000..2c13930 --- /dev/null +++ b/gtest/kata_code/Cafeteria/include/preloaded.h @@ -0,0 +1,67 @@ +#ifndef CW_PRELOADED_INCLUDED +#define CW_PRELOADED_INCLUDED + +#include +#include +#include + +struct Milk { + float fat; + + bool operator ==(const Milk &other) const { + return fat == other.fat; + } +}; + +std::ostream &operator<<(std::ostream &stream, const Milk &milk) { + stream << "Milk(" << milk.fat << ")"; + return stream; +} + + + +struct Sugar { + std::string sort; + + bool operator ==(const Sugar &other) const { + return sort == other.sort; + } +}; + +std::ostream &operator<<(std::ostream &stream, const Sugar &sugar) { + return (stream << "Sugar('" << sugar.sort << "')"); +} + + + +struct Coffee { + std::string sort; + std::vector milk; + std::vector sugar; + + bool operator==(const Coffee &other) const { + return sort == other.sort && milk == other.milk && sugar == other.sugar; + } +}; + +std::ostream &operator<<(std::ostream &stream, const Coffee &coffee) { + stream << "Coffee('" << coffee.sort << "', ["; + + for (unsigned i = 0; i < coffee.milk.size(); i++) { + if (i) stream << ", "; + stream << coffee.milk[i]; + } + + stream << "], ["; + + for (unsigned i = 0; i < coffee.sugar.size(); i++) { + if (i) stream << ", "; + stream << coffee.sugar[i]; + } + + stream << "])"; + + return stream; +} + +#endif // CW_PRELOADED_INCLUDED \ No newline at end of file diff --git a/gtest/kata_code/Cafeteria/include/solution.h b/gtest/kata_code/Cafeteria/include/solution.h new file mode 100644 index 0000000..7e64ec8 --- /dev/null +++ b/gtest/kata_code/Cafeteria/include/solution.h @@ -0,0 +1,42 @@ +#include +#include + +#include + +class CoffeeBuilder { + public: + std::string sort; + std::vector milk; + std::vector sugar; + + CoffeeBuilder set_black_coffee() { + sort = "Black"; + return *this; + } + + CoffeeBuilder set_cubano_coffee() { + sort = "Cubano"; + sugar.push_back({"Brown"}); + return *this; + } + + CoffeeBuilder set_antoccino_coffee() { + sort = "Americano"; + milk.push_back({0.5}); + return *this; + } + + CoffeeBuilder with_milk(float n) { + milk.push_back({n}); + return *this; + } + + CoffeeBuilder with_sugar(const std::string &s) { + sugar.push_back({s}); + return *this; + } + + Coffee build() { + return {sort, milk, sugar}; + } +}; \ No newline at end of file diff --git a/gtest/kata_code/Cafeteria/src/preloaded.cpp b/gtest/kata_code/Cafeteria/src/preloaded.cpp new file mode 100644 index 0000000..e69de29 diff --git a/gtest/kata_code/Cafeteria/src/solution.cpp b/gtest/kata_code/Cafeteria/src/solution.cpp new file mode 100644 index 0000000..e69de29 diff --git a/gtest/kata_code/Cafeteria/tests/test_solution.cpp b/gtest/kata_code/Cafeteria/tests/test_solution.cpp new file mode 100644 index 0000000..35083c8 --- /dev/null +++ b/gtest/kata_code/Cafeteria/tests/test_solution.cpp @@ -0,0 +1,80 @@ +#include +#include +#include +#include + +#include +#include +#include + +namespace { + TEST(Fixed_Tests, Tests) { + { + Coffee actual = CoffeeBuilder().set_black_coffee().with_sugar("Regular").with_milk(3.2).build(); + Coffee expected = {"Black", {{3.3}}, {{"Regular"}}}; + EXPECT_EQ(actual, expected) << expected; + } + + { + Coffee actual = CoffeeBuilder().set_antoccino_coffee().build(); + Coffee expected = {"Americano", {{0.5}}, {}}; + EXPECT_EQ(actual, expected); + } + + { + Coffee actual = CoffeeBuilder().set_cubano_coffee().build(); + Coffee expected = {"Cubano", {}, {{"Brown"}}}; + EXPECT_EQ(actual, expected); + } + } + + + auto randint(unsigned min, unsigned max) { + static std::random_device rd; + static std::mt19937 rng(rd()); + std::uniform_int_distribution uni(min, max); + return uni(rng); + } + + std::string randstr(unsigned n) { + static std::string base = "abcdefghijklmnopqrstuvwxyz"; + std::stringstream ss; + while (n--) ss << base[randint(0, base.size() - 1)]; + return ss.str(); + } + + TEST(Random_Tests, Tests) { + for (int i = 0; i < 100; i++) { + Coffee c; + CoffeeBuilder cb {}; + + int type = randint(0, 2); + if (type == 0) { + c.sort = "Black"; + cb = cb.set_black_coffee(); + } else if (type == 1) { + c.sort = "Americano"; + c.milk.push_back({0.5}); + cb = cb.set_antoccino_coffee(); + } else { + c.sort = "Cubano"; + c.sugar.push_back({"Brown"}); + cb = cb.set_cubano_coffee(); + } + + for (int i = randint(0, 2); i; i--) { + float n = randint(1, 30) / 10.0; + c.milk.push_back({n}); + cb = cb.with_milk(n); + } + + for (int i = randint(0, 2); i; i--) { + std::string s = randstr(randint(3, 5)); + c.sugar.push_back({s}); + cb = cb.with_sugar(s); + } + + EXPECT_EQ(cb.build(), c) << c; + } + } +} \ No newline at end of file diff --git a/gtest/kata_code/ExampleCrash/include/preloaded.h b/gtest/kata_code/ExampleCrash/include/preloaded.h new file mode 100644 index 0000000..e69de29 diff --git a/gtest/kata_code/ExampleCrash/include/solution.h b/gtest/kata_code/ExampleCrash/include/solution.h new file mode 100644 index 0000000..6f8e0c9 --- /dev/null +++ b/gtest/kata_code/ExampleCrash/include/solution.h @@ -0,0 +1 @@ +int crashOrNot(int n); \ No newline at end of file diff --git a/gtest/kata_code/ExampleCrash/src/preloaded.cpp b/gtest/kata_code/ExampleCrash/src/preloaded.cpp new file mode 100644 index 0000000..e69de29 diff --git a/gtest/kata_code/ExampleCrash/src/solution.cpp b/gtest/kata_code/ExampleCrash/src/solution.cpp new file mode 100644 index 0000000..2159b84 --- /dev/null +++ b/gtest/kata_code/ExampleCrash/src/solution.cpp @@ -0,0 +1,4 @@ +#include +int crashOrNot(int n) { + return n > 0 ? 42 : ((int*)NULL)[1]; +} \ No newline at end of file diff --git a/gtest/kata_code/ExampleCrash/tests/test_solution.cpp b/gtest/kata_code/ExampleCrash/tests/test_solution.cpp new file mode 100644 index 0000000..99a5baf --- /dev/null +++ b/gtest/kata_code/ExampleCrash/tests/test_solution.cpp @@ -0,0 +1,13 @@ +#include +#include + +namespace { + TEST(ExceptionTests, CrashInAssertion) { + EXPECT_EQ(1, crashOrNot(0)) << "incorrect answer for crashOrNot(0)"; + } + + TEST(CrashTests, CrashOutsideOfAssertion) { + int actual = crashOrNot(-13); + EXPECT_EQ(actual, -12) << "Answer -12 is incorrect, but should not get here"; + } +} \ No newline at end of file diff --git a/gtest/kata_code/ExampleExceptions/include/preloaded.h b/gtest/kata_code/ExampleExceptions/include/preloaded.h new file mode 100644 index 0000000..e69de29 diff --git a/gtest/kata_code/ExampleExceptions/include/solution.h b/gtest/kata_code/ExampleExceptions/include/solution.h new file mode 100644 index 0000000..6014860 --- /dev/null +++ b/gtest/kata_code/ExampleExceptions/include/solution.h @@ -0,0 +1 @@ +int throwOrNot(int n); \ No newline at end of file diff --git a/gtest/kata_code/ExampleExceptions/src/preloaded.cpp b/gtest/kata_code/ExampleExceptions/src/preloaded.cpp new file mode 100644 index 0000000..e69de29 diff --git a/gtest/kata_code/ExampleExceptions/src/solution.cpp b/gtest/kata_code/ExampleExceptions/src/solution.cpp new file mode 100644 index 0000000..5e1f6e9 --- /dev/null +++ b/gtest/kata_code/ExampleExceptions/src/solution.cpp @@ -0,0 +1,5 @@ +#include +int throwOrNot(int n) { + std::vector v = { 1, 2, 3 }; + return v.at(n); +} \ No newline at end of file diff --git a/gtest/kata_code/ExampleExceptions/tests/test_solution.cpp b/gtest/kata_code/ExampleExceptions/tests/test_solution.cpp new file mode 100644 index 0000000..ccfaa88 --- /dev/null +++ b/gtest/kata_code/ExampleExceptions/tests/test_solution.cpp @@ -0,0 +1,26 @@ +#include +#include + +namespace { + + TEST(ExceptionTests, ExpectedThrownOutsideOfAssertion) { + int actual = throwOrNot(7); + EXPECT_EQ(actual, 8) << "Answer 8 is incorrect, but should not get here"; + } + TEST(ExceptionTests, ExpectedNone_ActualNone) { + int actval = 10; + EXPECT_NO_THROW( actval = throwOrNot(2) ) << "throwOrNot(2) should not throw"; + EXPECT_EQ(actval, 3) << "incorrect answer for throwOrNot(2)"; + } + TEST(ExceptionTests, ExpectedNone_ActualThrown) { + int actval = 10; + EXPECT_NO_THROW( actval = throwOrNot(7) ) << "throwOrNot(7) should not throw"; + EXPECT_EQ(actval, 8) << "incorrect answer for throwOrNot(7)"; + } + TEST(ExceptionTests, ExpectedThrown_ActualNone) { + EXPECT_ANY_THROW( throwOrNot(0) ) << "should throw for throwOrNot(7)"; + } + TEST(ExceptionTests, ExpectedThrown_ActualThrown) { + EXPECT_ANY_THROW( throwOrNot(7) ) << "should throw for throwOrNot(7)"; + } +} \ No newline at end of file diff --git a/gtest/kata_code/ExampleFailureMessages/include/preloaded.h b/gtest/kata_code/ExampleFailureMessages/include/preloaded.h new file mode 100644 index 0000000..e69de29 diff --git a/gtest/kata_code/ExampleFailureMessages/include/solution.h b/gtest/kata_code/ExampleFailureMessages/include/solution.h new file mode 100644 index 0000000..af31bc8 --- /dev/null +++ b/gtest/kata_code/ExampleFailureMessages/include/solution.h @@ -0,0 +1,2 @@ +int alwaysFails(int n); +bool isEven(int n); \ No newline at end of file diff --git a/gtest/kata_code/ExampleFailureMessages/src/preloaded.cpp b/gtest/kata_code/ExampleFailureMessages/src/preloaded.cpp new file mode 100644 index 0000000..e69de29 diff --git a/gtest/kata_code/ExampleFailureMessages/src/solution.cpp b/gtest/kata_code/ExampleFailureMessages/src/solution.cpp new file mode 100644 index 0000000..7967a87 --- /dev/null +++ b/gtest/kata_code/ExampleFailureMessages/src/solution.cpp @@ -0,0 +1,7 @@ +int alwaysFails(int n) { + return n; +} + +bool isEven(int n) { + return alwaysFails(n) % 2 == 0; +} \ No newline at end of file diff --git a/gtest/kata_code/ExampleFailureMessages/tests/test_solution.cpp b/gtest/kata_code/ExampleFailureMessages/tests/test_solution.cpp new file mode 100644 index 0000000..d11b08a --- /dev/null +++ b/gtest/kata_code/ExampleFailureMessages/tests/test_solution.cpp @@ -0,0 +1,68 @@ +#include + +#include +#include + +#include + +using namespace ::testing; + +// Each assertion and matcher is presented with and +// without an optional failure message. + +namespace { + TEST(FailureMessageTests, Assertions) { + EXPECT_TRUE( false ); + EXPECT_TRUE( false ) << "Tested assertion: EXPECT_TRUE"; + + EXPECT_FALSE( true ); + EXPECT_FALSE( true ) << "Tested assertion: EXPECT_FALSE"; + + EXPECT_EQ( alwaysFails(13), 42 ); + EXPECT_EQ( alwaysFails(13), 42 ) << "Tested assertion: EXPECT_EQ"; + + EXPECT_NE( alwaysFails(42), 42 ); + EXPECT_NE( alwaysFails(42), 42 ) << "Tested assertion: EXPECT_NE"; + + EXPECT_LT( alwaysFails(256), 42 ); + EXPECT_LT( alwaysFails(256), 42 ) << "Tested assertion: EXPECT_LT"; + + EXPECT_STREQ( "13", "42" ); + EXPECT_STREQ( "13", "42" ) << "Tested assertion: EXPECT_STREQ"; + + EXPECT_NEAR( (double)alwaysFails(13), 42.0, 1e-3 ); + EXPECT_NEAR( (double)alwaysFails(13), 42.0, 1e-3 ) << "Tested assertion: EXPECT_NEAR"; + + EXPECT_PRED1( isEven, 13 ); + EXPECT_PRED1( isEven, 13 ) << "Tested assertion: EXPECT_PRED1"; + } + + TEST(FailureMessageTests, Matchers) { + EXPECT_THAT( false, ::testing::IsTrue() ); + EXPECT_THAT( false, ::testing::IsTrue() ) << "Tested matcher: IsTrue"; + + EXPECT_THAT( true, IsFalse() ); + EXPECT_THAT( true, IsFalse() ) << "Tested matcher: IsFalse"; + + EXPECT_THAT( alwaysFails(13), Eq(42) ); + EXPECT_THAT( alwaysFails(13), Eq(42) ) << "Tested Matcher: Eq"; + + EXPECT_THAT( alwaysFails(42), Ne(42) ); + EXPECT_THAT( alwaysFails(42), Ne(42) ) << "Tested matcher: Ne"; + + EXPECT_THAT( alwaysFails(256), Lt(42) ); + EXPECT_THAT( alwaysFails(256), Lt(42) ) << "Tested matcher: Lt"; + + EXPECT_THAT( "13", StrEq("42") ); + EXPECT_THAT( "13", StrEq("42") ) << "Tested matcher: StrEq"; + + EXPECT_THAT( (double)alwaysFails(13), DoubleNear(42.0, 1e-3) ); + EXPECT_THAT( (double)alwaysFails(13), DoubleNear(42.0, 1e-3) ) << "Tested matcher: DoubleNear"; + + EXPECT_THAT( std::vector(2), ContainerEq(std::vector(3)) ); + EXPECT_THAT( std::vector(2), ContainerEq(std::vector(3)) ) << "Tested matcher: ContainerEq"; + + EXPECT_THAT( std::vector{2}, WhenSorted(ContainerEq(std::vector{3}))); + EXPECT_THAT( std::vector{2}, WhenSorted(ContainerEq(std::vector{3}))) << "Tested matcher: WhenSorted ContainerEq"; + } +} \ No newline at end of file diff --git a/gtest/kata_code/ExampleOrganization/include/preloaded.h b/gtest/kata_code/ExampleOrganization/include/preloaded.h new file mode 100644 index 0000000..e69de29 diff --git a/gtest/kata_code/ExampleOrganization/include/solution.h b/gtest/kata_code/ExampleOrganization/include/solution.h new file mode 100644 index 0000000..e69de29 diff --git a/gtest/kata_code/ExampleOrganization/src/preloaded.cpp b/gtest/kata_code/ExampleOrganization/src/preloaded.cpp new file mode 100644 index 0000000..e69de29 diff --git a/gtest/kata_code/ExampleOrganization/src/solution.cpp b/gtest/kata_code/ExampleOrganization/src/solution.cpp new file mode 100644 index 0000000..e69de29 diff --git a/gtest/kata_code/ExampleOrganization/tests/test_solution.cpp b/gtest/kata_code/ExampleOrganization/tests/test_solution.cpp new file mode 100644 index 0000000..607a535 --- /dev/null +++ b/gtest/kata_code/ExampleOrganization/tests/test_solution.cpp @@ -0,0 +1,99 @@ +#include +#include + +namespace namespace1 { + + TEST(RegularTestSuite1, Test1) { + ASSERT_EQ(1, 2); + } + + TEST(RegularTestSuite1, Test2) { + ASSERT_EQ(1, 2); + } + + TEST(RegularTestSuite2, Test1) { + ASSERT_EQ(1, 2); + } + + TEST(RegularTestSuite2, Test2) { + ASSERT_EQ(1, 2); + } +} + +namespace namespace2 { + + TEST(RegularTestSuite1, Test1) { + ASSERT_EQ(1, 2); + } + + TEST(RegularTestSuite1, Test2) { + ASSERT_EQ(1, 2); + } + + TEST(RegularTestSuite2, Test1) { + ASSERT_EQ(1, 2); + } + + TEST(RegularTestSuite2, Test2) { + ASSERT_EQ(1, 2); + } +} + +namespace namepace3 { + + class TestFixtureClass1: public ::testing::Test {}; + class TestFixtureClass2: public ::testing::Test {}; + + TEST_F(TestFixtureClass1, Test1) { + ASSERT_EQ(1, 2); + } + TEST_F(TestFixtureClass1, Test2) { + ASSERT_EQ(1, 2); + } + TEST_F(TestFixtureClass2, Test1) { + ASSERT_EQ(1, 2); + } + TEST_F(TestFixtureClass2, Test2) { + ASSERT_EQ(1, 2); + } +} + +namespace namespace4 { + + typedef std::pair MyTestCase; + class ParametrizedTestsFixture1: public ::testing::TestWithParam { }; + class ParametrizedTestsFixture2: public ::testing::TestWithParam { }; + + static std::vector generateTests() { + std::vector cases; + for(int i=0; i<5; ++i) { + cases.push_back({ i * 2 + 540, true }); + cases.push_back({ i * 2 + 947, false}); + } + return cases; + } + + TEST_P(ParametrizedTestsFixture1, TestsSet1) { + ASSERT_EQ(1, 2); + } + TEST_P(ParametrizedTestsFixture1, TestsSet2) { + ASSERT_EQ(1, 2); + } + TEST_P(ParametrizedTestsFixture2, TestsSet1) { + ASSERT_EQ(1, 2); + } + TEST_P(ParametrizedTestsFixture2, TestsSet2) { + ASSERT_EQ(1, 2); + } + + INSTANTIATE_TEST_SUITE_P(BasicForm,ParametrizedTestsFixture1,::testing::ValuesIn(generateTests())); + INSTANTIATE_TEST_SUITE_P(WithNameGen,ParametrizedTestsFixture1,::testing::ValuesIn(generateTests()), + [](const testing::TestParamInfo& info) { + return "parameters_for_fixture1_are_" + std::to_string(info.param.first); + }); + INSTANTIATE_TEST_SUITE_P(BasicForm,ParametrizedTestsFixture2,::testing::ValuesIn(generateTests())); + INSTANTIATE_TEST_SUITE_P(WithNameGen,ParametrizedTestsFixture2,::testing::ValuesIn(generateTests()), + [](const testing::TestParamInfo& info) { + return "parameters_for_fixture2_are_" + std::to_string(info.param.first); + }); +} \ No newline at end of file diff --git a/gtest/kata_code/ExampleParametrized/include/preloaded.h b/gtest/kata_code/ExampleParametrized/include/preloaded.h new file mode 100644 index 0000000..e69de29 diff --git a/gtest/kata_code/ExampleParametrized/include/solution.h b/gtest/kata_code/ExampleParametrized/include/solution.h new file mode 100644 index 0000000..f299375 --- /dev/null +++ b/gtest/kata_code/ExampleParametrized/include/solution.h @@ -0,0 +1 @@ +bool evenOrOdd(int n); \ No newline at end of file diff --git a/gtest/kata_code/ExampleParametrized/src/preloaded.cpp b/gtest/kata_code/ExampleParametrized/src/preloaded.cpp new file mode 100644 index 0000000..e69de29 diff --git a/gtest/kata_code/ExampleParametrized/src/solution.cpp b/gtest/kata_code/ExampleParametrized/src/solution.cpp new file mode 100644 index 0000000..f9e5c92 --- /dev/null +++ b/gtest/kata_code/ExampleParametrized/src/solution.cpp @@ -0,0 +1,3 @@ +bool evenOrOdd(int n) { + return n % 2 == 0; +} \ No newline at end of file diff --git a/gtest/kata_code/ExampleParametrized/tests/test_solution.cpp b/gtest/kata_code/ExampleParametrized/tests/test_solution.cpp new file mode 100644 index 0000000..7206665 --- /dev/null +++ b/gtest/kata_code/ExampleParametrized/tests/test_solution.cpp @@ -0,0 +1,49 @@ +#include +#include +#include + +namespace { + TEST(FixedTests, test_0) { + EXPECT_EQ(evenOrOdd(0), true) << "evenOrOdd(0)"; + } + TEST(FixedTests, test_1) { + EXPECT_EQ(evenOrOdd(1), false) << "evenOrOdd(1)"; + } + TEST(FixedTests, test_minus_1) { + EXPECT_EQ(evenOrOdd(-1), false) << "evenOrOdd(-1)"; + } + + typedef std::pair MyTestCase; + class RandomTests: public ::testing::TestWithParam { + public: + int n; + bool exp; + + void SetUp() override { + auto param = GetParam(); + n = param.first; + exp = param.second; + } + }; + + static std::vector generateTests() { + std::vector cases; + for(int i=0; i<5; ++i) { + cases.push_back({ i * 2 + 540, true }); + cases.push_back({ i * 2 + 947, false}); + } + return cases; + } + + TEST_P(RandomTests, PassingCases) { + EXPECT_EQ(evenOrOdd(n), exp) << "evenOrOdd(" << n << ")"; + } + TEST_P(RandomTests, FailingCases) { + EXPECT_EQ(evenOrOdd(n), true) << "evenOrOdd(" << n << ")"; + } + INSTANTIATE_TEST_SUITE_P(BasicForm,RandomTests,::testing::ValuesIn(generateTests())); + INSTANTIATE_TEST_SUITE_P(WithNameGen,RandomTests,::testing::ValuesIn(generateTests()), + [](const testing::TestParamInfo& info) { + return "evenOrOdd_" + std::to_string(info.param.first); + }); +} \ No newline at end of file diff --git a/catch2/include/challenge.h b/gtest/kata_code/OriginalExample/include/preloaded.h similarity index 90% rename from catch2/include/challenge.h rename to gtest/kata_code/OriginalExample/include/preloaded.h index b361a40..5463ce5 100644 --- a/catch2/include/challenge.h +++ b/gtest/kata_code/OriginalExample/include/preloaded.h @@ -7,9 +7,9 @@ class Counter { public: // Creates a counter that starts at 0. - Counter() : counter_(1) {} + Counter() : counter_(0) {} // Returns the current counter value, and increments it. int Increment(); // Returns the current counter value, and decrements it. int Decrement(); -}; +}; \ No newline at end of file diff --git a/gtest/kata_code/OriginalExample/include/solution.h b/gtest/kata_code/OriginalExample/include/solution.h new file mode 100644 index 0000000..e69de29 diff --git a/gtest/kata_code/OriginalExample/src/preloaded.cpp b/gtest/kata_code/OriginalExample/src/preloaded.cpp new file mode 100644 index 0000000..e69de29 diff --git a/gtest/src/solution.cpp b/gtest/kata_code/OriginalExample/src/solution.cpp similarity index 93% rename from gtest/src/solution.cpp rename to gtest/kata_code/OriginalExample/src/solution.cpp index 50991b8..8bcfe79 100644 --- a/gtest/src/solution.cpp +++ b/gtest/kata_code/OriginalExample/src/solution.cpp @@ -1,4 +1,4 @@ -#include +#include // Returns the current counter value, and increments it. int Counter::Increment() { diff --git a/gtest/tests/test_solution.cpp b/gtest/kata_code/OriginalExample/tests/test_solution.cpp similarity index 98% rename from gtest/tests/test_solution.cpp rename to gtest/kata_code/OriginalExample/tests/test_solution.cpp index 91b4615..9d3ae01 100644 --- a/gtest/tests/test_solution.cpp +++ b/gtest/kata_code/OriginalExample/tests/test_solution.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include namespace { TEST(Vector_size_resize, resizing_bigger_changes_size_and_capacity) { @@ -61,4 +61,4 @@ TEST(Counter, Increment) { // Tests the Increment() method. -} +} \ No newline at end of file diff --git a/gtest/kata_code/UniqueInOrder/include/preloaded.h b/gtest/kata_code/UniqueInOrder/include/preloaded.h new file mode 100644 index 0000000..e69de29 diff --git a/gtest/kata_code/UniqueInOrder/include/solution.h b/gtest/kata_code/UniqueInOrder/include/solution.h new file mode 100644 index 0000000..a4b3bb8 --- /dev/null +++ b/gtest/kata_code/UniqueInOrder/include/solution.h @@ -0,0 +1,10 @@ +#include +#include +#include + +template std::vector uniqueInOrder(const std::vector& iterable){ + std::vector res = iterable; + res.erase(std::unique(begin(res), end(res)), end(res)); + return res; +} +std::vector uniqueInOrder(const std::string& iterable); diff --git a/gtest/kata_code/UniqueInOrder/src/preloaded.cpp b/gtest/kata_code/UniqueInOrder/src/preloaded.cpp new file mode 100644 index 0000000..e69de29 diff --git a/gtest/kata_code/UniqueInOrder/src/solution.cpp b/gtest/kata_code/UniqueInOrder/src/solution.cpp new file mode 100644 index 0000000..8a4d969 --- /dev/null +++ b/gtest/kata_code/UniqueInOrder/src/solution.cpp @@ -0,0 +1,9 @@ +#include +#include +#include + +std::vector uniqueInOrder(const std::string& iterable){ + std::string res = iterable; + res.erase(std::unique(begin(res), end(res)), end(res)); + return std::vector{begin(res), end(res)}; +} \ No newline at end of file diff --git a/gtest/kata_code/UniqueInOrder/tests/test_solution.cpp b/gtest/kata_code/UniqueInOrder/tests/test_solution.cpp new file mode 100644 index 0000000..ef66980 --- /dev/null +++ b/gtest/kata_code/UniqueInOrder/tests/test_solution.cpp @@ -0,0 +1,77 @@ +#include +#include + +#include "solution.h" + +#include +#include +#include + +auto randint(int min, int max) { + static std::random_device rd; + static std::mt19937 rng(rd()); + std::uniform_int_distribution uni(min, max); + return uni(rng); +} + +std::string printVec(const std::vector& vec) { + std::string res = "'" + std::string(1, vec[0]) + "'"; + for (unsigned int i = 1; i < vec.size(); i++) res += ", '" + std::string(1, vec[i]) + "'"; + return res; +} + +std::string printVec(const std::vector& vec) { + std::string res = std::to_string(vec[0]); + for (unsigned int i = 1; i < vec.size(); i++) res += ", " + std::to_string(vec[i]); + return res; +} +using VI = std::vector; +using VC = std::vector; + +TEST(UniqueInOrder, BasicTests) { + ASSERT_EQ(uniqueInOrder(""), VC{}); + ASSERT_EQ(uniqueInOrder("AA"), VC{ 'A' }); + ASSERT_EQ(uniqueInOrder("A"), VC{ 'A' }); + ASSERT_EQ(uniqueInOrder("AAAABBBCCDAABBB"), (VC{ 'A', 'B', 'C', 'D', 'A', 'B' })); + ASSERT_EQ(uniqueInOrder("AADD"), (VC{ 'A', 'D' })); + ASSERT_EQ(uniqueInOrder("AAD"), (VC{ 'A', 'D' })); + ASSERT_EQ(uniqueInOrder("ADD"), (VC{ 'A', 'D' })); + ASSERT_EQ(uniqueInOrder("ABBCcAD"), (VC{ 'A', 'B', 'C', 'c', 'A', 'D' })); + ASSERT_EQ(uniqueInOrder(VI{ 1,2,3,3 }), (VI{ 1,2,3 })); + ASSERT_EQ(uniqueInOrder(VC{ 'a','b','b' }), (VC{ 'a','b' })); +}; + template std::vector sol(const std::vector&iterable) { + std::vector res = iterable; + res.erase(std::unique(begin(res), end(res)), end(res)); + return res; + } + + std::vector sol(const std::string & iterable) { + std::string res = iterable; + res.erase(std::unique(begin(res), end(res)), end(res)); + return std::vector{begin(res), end(res)}; + } + TEST(UniqueInOrder, RandomTests) { + std::string base = "abcXYZ"; + for (unsigned int i = 0; i < 40; i++) { + unsigned int testCase = randint(0, 2), inputLen = randint(3, 40); + if (testCase == 0) { + std::string iterable; + for (; inputLen--;) iterable += base[randint(0, base.size() - 1)]; + std::cout << "Testing for '" << iterable << "'\n\n"; + ASSERT_EQ(uniqueInOrder(iterable), sol(iterable)); + } + else if (testCase == 1) { + std::vector iterable; + for (; inputLen--;) iterable.push_back(base[randint(0, base.size() - 1)]); + std::cout << "Testing for [" << printVec(iterable) << "]\n\n"; + ASSERT_EQ(uniqueInOrder(iterable), sol(iterable)); + } + else { + std::vector iterable; + for (; inputLen--;) iterable.push_back(randint(-5, 5)); + std::cout << "Testing for [" << printVec(iterable) << "]\n\n"; + ASSERT_EQ(uniqueInOrder(iterable), sol(iterable)); + } + } + }; \ No newline at end of file diff --git a/gtest/run_image.bat b/gtest/run_image.bat new file mode 100644 index 0000000..afc026a --- /dev/null +++ b/gtest/run_image.bat @@ -0,0 +1,8 @@ +set KATA=%1 +set IMAGE=cw-gtest-cpp +set WORKDIR=/code/ +set CREATE_CMD=docker container create --rm -w %WORKDIR% %IMAGE% ./run-kata.sh + +FOR /F %%i IN ('%CREATE_CMD%') DO set CONTAINER=%%i +docker cp ./kata_code/%KATA%/. %CONTAINER%:%WORKDIR% +docker start --attach -i %CONTAINER% diff --git a/gtest/snippets/Cafeteria/preloaded.snippet b/gtest/snippets/Cafeteria/preloaded.snippet new file mode 100644 index 0000000..85b6a42 --- /dev/null +++ b/gtest/snippets/Cafeteria/preloaded.snippet @@ -0,0 +1,67 @@ +#ifndef PRELOADED_INCLUDED +#define PRELOADED_INCLUDED + +#include +#include +#include + +struct Milk { + float fat; + + bool operator ==(const Milk &other) const { + return fat == other.fat; + } +}; + +std::ostream &operator<<(std::ostream &stream, const Milk &milk) { + stream << "Milk(" << milk.fat << ")"; + return stream; +} + + + +struct Sugar { + std::string sort; + + bool operator ==(const Sugar &other) const { + return sort == other.sort; + } +}; + +std::ostream &operator<<(std::ostream &stream, const Sugar &sugar) { + return (stream << "Sugar('" << sugar.sort << "')"); +} + + + +struct Coffee { + std::string sort; + std::vector milk; + std::vector sugar; + + bool operator==(const Coffee &other) const { + return sort == other.sort && milk == other.milk && sugar == other.sugar; + } +}; + +std::ostream &operator<<(std::ostream &stream, const Coffee &coffee) { + stream << "Coffee('" << coffee.sort << "', ["; + + for (unsigned i = 0; i < coffee.milk.size(); i++) { + if (i) stream << ", "; + stream << coffee.milk[i]; + } + + stream << "], ["; + + for (unsigned i = 0; i < coffee.sugar.size(); i++) { + if (i) stream << ", "; + stream << coffee.sugar[i]; + } + + stream << "])"; + + return stream; +} + +#endif // PRELOADED_INCLUDED \ No newline at end of file diff --git a/gtest/snippets/Cafeteria/solution.snippet b/gtest/snippets/Cafeteria/solution.snippet new file mode 100644 index 0000000..9499a78 --- /dev/null +++ b/gtest/snippets/Cafeteria/solution.snippet @@ -0,0 +1,47 @@ +#ifndef SOLUTION_INCLUDED +#define SOLUTION_INCLUDED + +#include +#include + +#include + +class CoffeeBuilder { + public: + std::string sort; + std::vector milk; + std::vector sugar; + + CoffeeBuilder set_black_coffee() { + sort = "Black"; + return *this; + } + + CoffeeBuilder set_cubano_coffee() { + sort = "Cubano"; + sugar.push_back({"Brown"}); + return *this; + } + + CoffeeBuilder set_antoccino_coffee() { + sort = "Americano"; + milk.push_back({0.5}); + return *this; + } + + CoffeeBuilder with_milk(float n) { + milk.push_back({n}); + return *this; + } + + CoffeeBuilder with_sugar(const std::string &s) { + sugar.push_back({s}); + return *this; + } + + Coffee build() { + return {sort, milk, sugar}; + } +}; + +#endif // SOLUTION_INCLUDED \ No newline at end of file diff --git a/gtest/snippets/Cafeteria/test_solution.snippet b/gtest/snippets/Cafeteria/test_solution.snippet new file mode 100644 index 0000000..35083c8 --- /dev/null +++ b/gtest/snippets/Cafeteria/test_solution.snippet @@ -0,0 +1,80 @@ +#include +#include +#include +#include + +#include +#include +#include + +namespace { + TEST(Fixed_Tests, Tests) { + { + Coffee actual = CoffeeBuilder().set_black_coffee().with_sugar("Regular").with_milk(3.2).build(); + Coffee expected = {"Black", {{3.3}}, {{"Regular"}}}; + EXPECT_EQ(actual, expected) << expected; + } + + { + Coffee actual = CoffeeBuilder().set_antoccino_coffee().build(); + Coffee expected = {"Americano", {{0.5}}, {}}; + EXPECT_EQ(actual, expected); + } + + { + Coffee actual = CoffeeBuilder().set_cubano_coffee().build(); + Coffee expected = {"Cubano", {}, {{"Brown"}}}; + EXPECT_EQ(actual, expected); + } + } + + + auto randint(unsigned min, unsigned max) { + static std::random_device rd; + static std::mt19937 rng(rd()); + std::uniform_int_distribution uni(min, max); + return uni(rng); + } + + std::string randstr(unsigned n) { + static std::string base = "abcdefghijklmnopqrstuvwxyz"; + std::stringstream ss; + while (n--) ss << base[randint(0, base.size() - 1)]; + return ss.str(); + } + + TEST(Random_Tests, Tests) { + for (int i = 0; i < 100; i++) { + Coffee c; + CoffeeBuilder cb {}; + + int type = randint(0, 2); + if (type == 0) { + c.sort = "Black"; + cb = cb.set_black_coffee(); + } else if (type == 1) { + c.sort = "Americano"; + c.milk.push_back({0.5}); + cb = cb.set_antoccino_coffee(); + } else { + c.sort = "Cubano"; + c.sugar.push_back({"Brown"}); + cb = cb.set_cubano_coffee(); + } + + for (int i = randint(0, 2); i; i--) { + float n = randint(1, 30) / 10.0; + c.milk.push_back({n}); + cb = cb.with_milk(n); + } + + for (int i = randint(0, 2); i; i--) { + std::string s = randstr(randint(3, 5)); + c.sugar.push_back({s}); + cb = cb.with_sugar(s); + } + + EXPECT_EQ(cb.build(), c) << c; + } + } +} \ No newline at end of file diff --git a/gtest/snippets/OriginalExample/preloaded.snippet b/gtest/snippets/OriginalExample/preloaded.snippet new file mode 100644 index 0000000..35ffd18 --- /dev/null +++ b/gtest/snippets/OriginalExample/preloaded.snippet @@ -0,0 +1,20 @@ +#ifndef PRELOADED_INCLUDED +#define PRELOADED_INCLUDED + +#pragma once + +// A simple monotonic counter. +class Counter { + private: + int counter_; + + public: + // Creates a counter that starts at 0. + Counter() : counter_(0) {} + // Returns the current counter value, and increments it. + int Increment(); + // Returns the current counter value, and decrements it. + int Decrement(); +}; + +#endif // PRELOADED_INCLUDED diff --git a/gtest/snippets/OriginalExample/solution.snippet b/gtest/snippets/OriginalExample/solution.snippet new file mode 100644 index 0000000..8bcfe79 --- /dev/null +++ b/gtest/snippets/OriginalExample/solution.snippet @@ -0,0 +1,16 @@ +#include + +// Returns the current counter value, and increments it. +int Counter::Increment() { + return counter_++; +} + +// Returns the current counter value, and decrements it. +// counter can not be less than 0, return 0 in this case +int Counter::Decrement() { + if (counter_ == 0) { + return counter_; + } else { + return counter_--; + } +} diff --git a/gtest/snippets/OriginalExample/test_solution.snippet b/gtest/snippets/OriginalExample/test_solution.snippet new file mode 100644 index 0000000..9d3ae01 --- /dev/null +++ b/gtest/snippets/OriginalExample/test_solution.snippet @@ -0,0 +1,64 @@ +#include +#include +#include + +namespace { +TEST(Vector_size_resize, resizing_bigger_changes_size_and_capacity) { + std::vector v( 5 ); + EXPECT_EQ( v.size(), 5 ); + EXPECT_GE( v.capacity(), 5 ); + + v.resize( 10 ); + EXPECT_EQ( v.size(), 10 ); + EXPECT_GE( v.capacity(), 10 ); +} + +TEST(Vector_size_resize, resizing_smaller_changes_size_but_not_capacity) { + std::vector v( 5 ); + EXPECT_EQ( v.size(), 5 ); + EXPECT_GE( v.capacity(), 5 ); + v.resize( 0 ); + + EXPECT_EQ( v.size(), 0 ); + EXPECT_GE( v.capacity(), 5 ); +} + +TEST(Vector_size_resize, reserving_bigger_changes_capacity_but_not_size) { + std::vector v( 5 ); + EXPECT_EQ( v.size(), 5 ); + EXPECT_GE( v.capacity(), 5 ); + v.reserve( 10 ); + + EXPECT_EQ( v.size(), 5 ); + EXPECT_GE( v.capacity(), 10 ); +} + +TEST(Vector_size_resize, reserving_smaller_does_not_change_size_or_capacity) { + std::vector v( 5 ); + EXPECT_EQ( v.size(), 5 ); + EXPECT_GE( v.capacity(), 5 ); + v.reserve( 0 ); + + EXPECT_EQ( v.size(), 5 ); + EXPECT_GE( v.capacity(), 5 ); +} + +TEST(Counter, Increment) { // Tests the Increment() method. + Counter c; + + // Test that counter 0 returns 0 + EXPECT_EQ(0, c.Decrement()); + + // EXPECT_EQ() evaluates its arguments exactly once, so they + // can have side effects. + + EXPECT_EQ(0, c.Increment()); + EXPECT_EQ(1, c.Increment()); + EXPECT_EQ(2, c.Increment()); + + EXPECT_EQ(3, c.Decrement()); +} + + + +} \ No newline at end of file diff --git a/gtest/snippets/UniqueInOrder/preloaded.snippet b/gtest/snippets/UniqueInOrder/preloaded.snippet new file mode 100644 index 0000000..e69de29 diff --git a/gtest/snippets/UniqueInOrder/solution.snippet b/gtest/snippets/UniqueInOrder/solution.snippet new file mode 100644 index 0000000..980621f --- /dev/null +++ b/gtest/snippets/UniqueInOrder/solution.snippet @@ -0,0 +1,25 @@ +#ifndef SOLUTION_INCLUDED +#define SOLUTION_INCLUDED + +#include +#include +#include + +template std::vector uniqueInOrder(const std::vector& iterable){ + std::vector res = iterable; + res.erase(std::unique(begin(res), end(res)), end(res)); + return res; +} +std::vector uniqueInOrder(const std::string& iterable); + +#endif // SOLUTION_INCLUDED + +#include +#include +#include + +std::vector uniqueInOrder(const std::string& iterable){ + std::string res = iterable; + res.erase(std::unique(begin(res), end(res)), end(res)); + return std::vector{begin(res), end(res)}; +} \ No newline at end of file diff --git a/gtest/snippets/UniqueInOrder/test_solution.snippet b/gtest/snippets/UniqueInOrder/test_solution.snippet new file mode 100644 index 0000000..ef66980 --- /dev/null +++ b/gtest/snippets/UniqueInOrder/test_solution.snippet @@ -0,0 +1,77 @@ +#include +#include + +#include "solution.h" + +#include +#include +#include + +auto randint(int min, int max) { + static std::random_device rd; + static std::mt19937 rng(rd()); + std::uniform_int_distribution uni(min, max); + return uni(rng); +} + +std::string printVec(const std::vector& vec) { + std::string res = "'" + std::string(1, vec[0]) + "'"; + for (unsigned int i = 1; i < vec.size(); i++) res += ", '" + std::string(1, vec[i]) + "'"; + return res; +} + +std::string printVec(const std::vector& vec) { + std::string res = std::to_string(vec[0]); + for (unsigned int i = 1; i < vec.size(); i++) res += ", " + std::to_string(vec[i]); + return res; +} +using VI = std::vector; +using VC = std::vector; + +TEST(UniqueInOrder, BasicTests) { + ASSERT_EQ(uniqueInOrder(""), VC{}); + ASSERT_EQ(uniqueInOrder("AA"), VC{ 'A' }); + ASSERT_EQ(uniqueInOrder("A"), VC{ 'A' }); + ASSERT_EQ(uniqueInOrder("AAAABBBCCDAABBB"), (VC{ 'A', 'B', 'C', 'D', 'A', 'B' })); + ASSERT_EQ(uniqueInOrder("AADD"), (VC{ 'A', 'D' })); + ASSERT_EQ(uniqueInOrder("AAD"), (VC{ 'A', 'D' })); + ASSERT_EQ(uniqueInOrder("ADD"), (VC{ 'A', 'D' })); + ASSERT_EQ(uniqueInOrder("ABBCcAD"), (VC{ 'A', 'B', 'C', 'c', 'A', 'D' })); + ASSERT_EQ(uniqueInOrder(VI{ 1,2,3,3 }), (VI{ 1,2,3 })); + ASSERT_EQ(uniqueInOrder(VC{ 'a','b','b' }), (VC{ 'a','b' })); +}; + template std::vector sol(const std::vector&iterable) { + std::vector res = iterable; + res.erase(std::unique(begin(res), end(res)), end(res)); + return res; + } + + std::vector sol(const std::string & iterable) { + std::string res = iterable; + res.erase(std::unique(begin(res), end(res)), end(res)); + return std::vector{begin(res), end(res)}; + } + TEST(UniqueInOrder, RandomTests) { + std::string base = "abcXYZ"; + for (unsigned int i = 0; i < 40; i++) { + unsigned int testCase = randint(0, 2), inputLen = randint(3, 40); + if (testCase == 0) { + std::string iterable; + for (; inputLen--;) iterable += base[randint(0, base.size() - 1)]; + std::cout << "Testing for '" << iterable << "'\n\n"; + ASSERT_EQ(uniqueInOrder(iterable), sol(iterable)); + } + else if (testCase == 1) { + std::vector iterable; + for (; inputLen--;) iterable.push_back(base[randint(0, base.size() - 1)]); + std::cout << "Testing for [" << printVec(iterable) << "]\n\n"; + ASSERT_EQ(uniqueInOrder(iterable), sol(iterable)); + } + else { + std::vector iterable; + for (; inputLen--;) iterable.push_back(randint(-5, 5)); + std::cout << "Testing for [" << printVec(iterable) << "]\n\n"; + ASSERT_EQ(uniqueInOrder(iterable), sol(iterable)); + } + } + }; \ No newline at end of file diff --git a/preprocessor/cwpp.ts b/preprocessor/cwpp.ts new file mode 100644 index 0000000..f03a7dc --- /dev/null +++ b/preprocessor/cwpp.ts @@ -0,0 +1,23 @@ +const splitSnippet = (code: string, name: string): [header: string, impl: string] => { + const lines = code.split(/(?<=\n)/); // Keep LF in each line + const marker = new RegExp( + `^\\s*#\\s*ifndef\\s+${name.toUpperCase()}_INCLUDED` + ); + const start = lines.findIndex((line) => marker.test(line)); + // No header file unless there's a start marker. + if (start === -1) return ["", code]; + + let counter = 1; + const end = lines.findIndex((line, i) => { + if (i > start) { + if (/^\s*#\s*endif\s*/.test(line)) return --counter === 0; + if (/^\s*#\s*if(?:n?def)?\s+/.test(line)) ++counter; + } + return false; + }); + // Put everything in header unless there's a matching `#endif`. + // The error should make more sense that way. + return end === -1 + ? [code, ""] + : [lines.slice(0, end + 1).join(""), lines.slice(end + 1).join("")]; +};