#include "./filtered_string_view.h" #include // Specification: 2.3 - Static Data Members // Purpose: Ensure that the default predicate function returns true for any // character passed in. // Expected: The function should always return true for all possible arguments. // Note: Taken directly from the specification, modified for catch2. TEST_CASE("default predicate function always returns true") { const auto min = std::numeric_limits::min(); const auto max = std::numeric_limits::max(); for (char c = min; c != max; c++) { REQUIRE(fsv::filtered_string_view::default_predicate(c)); } } // Specification: 2.4.1 - Default Constructor // Purpose: Determine that the default constructor results in the equivalent // of an empty string. // Expected: The view should compare equal to an empty string with a zero // length. Data should be set to nullptr. // Note: We cannot test where the underlying predicate points to directly as // this would make the test brittle by introducing implementation details. // We may only test this implicitly via string comparisons. TEST_CASE("default constructed is equivalent to an empty string") { const auto view = fsv::filtered_string_view{}; REQUIRE(std::string{""} == view); REQUIRE(std::size(view) == 0); REQUIRE(std::data(view) == nullptr); } // Specification: 2.4.2 - Implicit String Constructor // Purpose: Ensure the implicit string constructor does not modify the string it // originally refers to or filter the data. It has not been provided // with an alternative predicate, so it should perform no filtering. // Expected: The view should be equivalent to the parent string. Data should // point directly to the parent string. The length should be equal to the parent // string. TEST_CASE("implicit string constructor is equivalent to parent string") { const auto parent = std::string{"parent string"}; const auto view = fsv::filtered_string_view{parent}; REQUIRE(parent == view); REQUIRE(std::data(parent) == std::data(view)); REQUIRE(std::size(parent) == std::size(view)); }; // Specification: 2.4.3 - String Constructor with Predicate // Purpose: The string constructor with a supplied predicate should filter the // original string by the rules provided by the predicate. // Expected: The view should NOT be equivalent to the parent string. The lengths // should not be identical. This is provided that the predicate // filters part of the original string. TEST_CASE("string constructor with predicate filters parent string") { const auto parent = std::string{"parent string"}; const auto view = fsv::filtered_string_view{parent, [](const char& c) { return c != 's'; }}; REQUIRE(view == std::string{"parent tring"}); REQUIRE(std::data(parent) == std::data(view)); REQUIRE(std::size(parent) != std::size(view)); }; // Specification: 2.4.4 - Implicit Null-Terminated String Constructor // Purpose: The implicit null terminated string constructor should perform // identically to the standard string constructor. // Expected: The view should be equivalent to the parent string in data, length // and equality. TEST_CASE("implicit null terminated string does not filter parent string") { const auto parent = std::string{"parent string"}; const auto view = fsv::filtered_string_view{parent.c_str()}; REQUIRE(view == parent); REQUIRE(std::data(parent) == std::data(view)); REQUIRE(std::size(parent) == std::size(view)); }; // Specification: 2.4.5 - Null-Terminated String with Predicate Constructor // Purpose: The null terminated string with predicate constructor should operate // identically to the standard string constructor with a predicate. // Given a predicate, it should filter the string via that rule. // Expected: The view should not be equivalent to the parent string. Data should // remain the same, while size should be different. TEST_CASE("null-terminated string with predicate constructor filters the parent string") { const auto parent = std::string{"parent string"}; const auto view = fsv::filtered_string_view{parent.c_str(), [](const char& c) { return c != 's'; }}; REQUIRE(view == std::string{"parent tring"}); REQUIRE(std::data(parent) == std::data(view)); REQUIRE(std::size(parent) != std::size(view)); } // Specification: 2.4.6 - Copy and Move Constructors // Purpose: Copy constructors function as expected. In addition, the copy // constructor has an equality constraint. // Expected: The newly constructed object must compare equal to the copied // object. // Note: This test was taken from the specification, modified for catch2. We // split the test into two for readability (the move constructor is // separate), and include additional length and non std::data equality // tests. TEST_CASE("copy constructors compare equal") { const auto parent = fsv::filtered_string_view{"parent string"}; const auto copy = parent; REQUIRE(std::data(copy) == std::data(parent)); REQUIRE(copy == parent); REQUIRE(std::size(copy) == std::size(parent)); } // Specification: 2.4.6 - Copy and Move Constructors // Purpose: Copy and move constructors function as expected. In addition, the // copy and move constructors have a number of constraints supplied by // the specification. // Expected: The move constructor should default construct the moved-from object, // while the new object should be identical to the original object. TEST_CASE("move constructor is identical to moved from object before move") { const auto parent = fsv::filtered_string_view{"parent string"}; auto copy = parent; const auto move = std::move(copy); REQUIRE(std::data(copy) == nullptr); REQUIRE(copy == fsv::filtered_string_view{}); } // Specification: 2.5.2 - Copy Assignment // Purpose: Copy assignment should copy at least the length, data and predicate // of other such that the they compare equal via operator==. // Expected: The view targeted by the copy assignment should compare equal to // the parent view. // Notes: Copied from the specification, modified for catch2. TEST_CASE("copy assignment passes equality comparison to parent object") { const auto parent = fsv::filtered_string_view{"42 bro", [](const char& c) { return c == '4' || c == '2'; }}; auto copy = fsv::filtered_string_view{}; copy = parent; REQUIRE(copy == parent); } // Specification: 2.5.2 - Copy Assignment // Purpose: Copy assignment should not modify itself when self copied. // Expected: Copy remains identical despite a self-copy. TEST_CASE("copy assignment should not change object if self copied") { auto parent = fsv::filtered_string_view{"42 bro", [](const char& c) { return c == '4' || c == '2'; }}; const auto copy = parent; // We copy here so we keep the previous information. parent = parent; REQUIRE(copy == parent); } // Specification 2.5.3 - Move Assignment // Purpose: A moved from object should be left in a valid state equal to a // default constructed view. In addition, the move should move the // contents of the old view to the new view. // Expected: The moved from object is default constructed, while the new object // should be identical to the parent view. TEST_CASE("move assignment should transfer contents and default construct moved") { auto parent = fsv::filtered_string_view{"'89 baby", [](const char& c) { return c == '8' || c == '9'; }}; // Copy parent before the move so we can check the move consists of this. const auto copy = parent; auto move = fsv::filtered_string_view{}; move = std::move(parent); REQUIRE(std::data(move) == std::data(copy)); REQUIRE(move == copy); REQUIRE(std::size(move) == std::size(copy)); // Ensure default construction. REQUIRE(std::size(parent) == 0); REQUIRE(std::data(parent) == nullptr); } // Specification 2.5.3 - Move Assignment // Purpose: Move assignment should not modify itself when self moved. // Expected: Move remains identical despite a self-move. TEST_CASE("move assignment should not change object if self copied") { auto parent = fsv::filtered_string_view{"42 bro", [](const char& c) { return c == '4' || c == '2'; }}; const auto copy = parent; // We copy here so we keep the previous information. parent = std::move(parent); REQUIRE(copy == parent); } // Specification 2.5.4 - Subscript // Purpose: Test if we can read the string given a specific subscript. We supply // a predicate which filters some data so we can read into a view. // Expected: The subscript is equal to the expected characters, filtering out // those defined by the predicate. TEST_CASE("subscript should be affected by view predicate") { const auto parent = fsv::filtered_string_view("123", [](const char& c) { return c != '2'; }); REQUIRE(parent[0] == '1'); REQUIRE(parent[1] == '3'); } // Specification 2.5.5 - String Type Conversion // Purpose: We should be able to convert a view back into a string. Filtered out // data should be removed, as if we were looking at the view. // Expected: The view converted into a string should return a string affected by // the predicate. TEST_CASE("string conversion should be affected by view predicate") { const auto parent = fsv::filtered_string_view{"123", [](const char& c) { return c != '2'; }}; REQUIRE(static_cast(parent) == "13"); } // Specification 2.5.5 - String Type Conversion // Purpose: A string conversion should be a copy of the underlying data. // Expected: The data pointer to the view should be different to the new string. TEST_CASE("string conversion should return a new string") { const auto parent = fsv::filtered_string_view{"123", [](const char& c) { return c != '2'; }}; const auto s = static_cast(parent); REQUIRE(std::data(s) != std::data(parent)); } // Specification: 2.6.1 - At // Purpose: At performs bounds checking of a filtered string. We ensure at performs // identically to subscript operators, unless we read out of bounds, in // which case it throws. // Expected: The view should return expected at results, similar to subscript. TEST_CASE("at should be affected by view predicate") { const auto parent = fsv::filtered_string_view{"123", [](const char& c) { return c != '2'; }}; REQUIRE(parent.at(0) == '1'); REQUIRE(parent.at(1) == '3'); } // Specification: 2.6.1 - At // Purpose: At should throw on an out of bounds read, negative or positive, with // a specific message and std::domain_error. // Expected: The view should throw if we read outside of size(). TEST_CASE("at should throw on out of bounds reads") { const auto parent = fsv::filtered_string_view{"123", [](const char& c) { return c != '2'; }}; REQUIRE_THROWS_AS(parent.at(-1), std::domain_error); REQUIRE_THROWS_WITH(parent.at(-1), "filtered_string_view::at(-1): invalid index"); REQUIRE_THROWS_WITH(parent.at(2), "filtered_string_view::at(2): invalid index"); REQUIRE_THROWS_AS(parent.at(2), std::domain_error); } // Specification: 2.6.2 - Size // Purpose: Size should be equal to the size of the parent string with no pred. // Expected: The string size should be equal to the view size with no predicate. TEST_CASE("size should be equal to the parent string with no predicate") { const auto parent = fsv::filtered_string_view{"Maltese"}; REQUIRE(std::size(parent) == 7); } // Specification: 2.6.2 - Size // Purpose: Size should be equal to the size of the filtered string. // Expected: The string size should be equal to the amount of times the predicate // returned true for that parent string. TEST_CASE("size should be affected by view predicate") { const auto parent = fsv::filtered_string_view{"Maltese", [](const char& c) { return c == 'a' or c == 'e'; }}; REQUIRE(std::size(parent) == 3); } // Specification: 2.6.3 - Empty // Purpose: Empty should return true when the size of the view is zero. // Expected: An empty view should return true. A non-empty view should return // false. TEST_CASE("empty should return true when size is zero, and false when nonzero") { REQUIRE(fsv::filtered_string_view{}.empty() == true); REQUIRE(fsv::filtered_string_view{"1"}.empty() == false); } // Specification: 2.6.3 - Empty // Purpose: Empty should return true when the view with predicate results in an // empty string. // Expected: The 'viewed' string with a false predicate should always return true. TEST_CASE("empty should return true when predicate filters all characters") { const auto parent = fsv::filtered_string_view{"non_empty", [](const char&) { return false; }}; REQUIRE(parent.size() == 0); REQUIRE(parent.empty() == true); } // Specification: 2.6.3 - Data // Purpose: Data should return the owned string, without an affected filter. // Expected: The data of a string view should not be affected by the predicate, // so should be equal to the parent string despite the predicate. TEST_CASE("data should not be affected by the predicate") { const auto parent = std::string{"chars"}; const auto view = fsv::filtered_string_view{parent, [](const char&) { return false; }}; REQUIRE(std::string{std::data(view)} == parent); } // Specification: 2.6.3 - Predicate // Purpose: The predicate method should return the predicate passed in any // constructor. // Expected: The effects of a function should passed to the view should be made // when calling the function provided by the predicate method. TEST_CASE("predicate returns the same function during construction") { int count = 0; const auto view = fsv::filtered_string_view{"", [&](const char&) { ++count; return true; }}; // We don't know how many times predicate calls the original function, so we // store the result of count after the constructor. const auto before_call = count; view.predicate()(char{}); REQUIRE(before_call + 1 == count); } // Specification: 2.7.1 - Equality Comparison // Purpose: Ensure that the predicate affects the result of the equality // comparison. // Expected: Equality should be true when the predicate results in two // functionally identical views of strings regardless of its data. TEST_CASE("equality comparison is true when affected by predicate") { const auto view1 = fsv::filtered_string_view{"bca", [](const char& c) { return c == 'a'; }}; const auto view2 = fsv::filtered_string_view{"abc", [](const char& c) { return c == 'a'; }}; REQUIRE(view1 == view2); } // Specification: 2.7.1 - Equality Comparison // Purpose: Ensure that the predicate affects the result of the equality // comparison. // Expected: Equality should be false when the predicate differs on the same // string and produces different results through the view. TEST_CASE("equality comparison is false when affected by predicate") { const auto parent = std::string{"abc"}; const auto view1 = fsv::filtered_string_view{parent, [](const char& c) { return c == 'a'; }}; const auto view2 = fsv::filtered_string_view{parent, [](const char& c) { return c == 'b'; }}; REQUIRE(not(view1 == view2)); } // Specification: 2.7.1 - Equality Comparison // Purpose: Ensure that inequality functions as expected. // Expected: Equality should be the inverse of inequality. TEST_CASE("inequality is the inverse of equality") { const auto view1 = fsv::filtered_string_view{"bca", [](const char& c) { return c == 'a'; }}; const auto view2 = fsv::filtered_string_view{"abc", [](const char& c) { return c == 'a'; }}; REQUIRE(view1 == view2); REQUIRE(not(view1 != view2)); } // Specification: 2.7.2 - Relational Comparison // Purpose: Ensure that relational comparisons functions as expected. // Expected: Relational comparisons should be logically consistent. // Note: Test copied from the specification, modified for catch2. TEST_CASE("relational comparison is logically consistent") { const auto lo = fsv::filtered_string_view{"aaa"}; const auto hi = fsv::filtered_string_view{"zzz"}; REQUIRE(lo < hi); REQUIRE(lo <= hi); REQUIRE(not(lo > hi)); REQUIRE(not(lo >= hi)); REQUIRE(lo <=> hi == std::strong_ordering::less); } // Specification: 2.7.2 - Relational Comparison // Purpose: Ensure that relational comparisons functions are affected by the // predicate. // Expected: Relational comparisons should use the result of the view, not the // source string. TEST_CASE("relational comparison is affected by predicate") { const auto lo = fsv::filtered_string_view{"za", [](const auto& c) { return c != 'z'; }}; const auto hi = fsv::filtered_string_view{"az", [](const auto& c) { return c != 'a'; }}; REQUIRE(lo < hi); REQUIRE(lo <= hi); REQUIRE(not(lo > hi)); REQUIRE(not(lo >= hi)); REQUIRE(lo <=> hi == std::strong_ordering::less); } // Specification: 2.7.3 - Output Stream // Purpose: Ensure the filtered string outputs to a stream and is filtered by // the predicate. // Expected: The output stream filters the underlying data. TEST_CASE("output stream is affected by predicate") { const auto view = fsv::filtered_string_view{"output!", [](const auto& c) { return c != 't'; }}; auto stream = std::stringstream{}; stream << view; REQUIRE(std::string{stream.str()} == "oupu!"); } // Specification: 2.8.1 - Compose // Purpose: Ensure that compose results in the logical AND expression equivalent // defined in the specification. // Expected: The result of multiple predicates are applied and only those which // return true for all provided predicates are viewed in the // underlying filter. // Notes: Taken from the specification, modified for catch2. TEST_CASE("compose functions as logical AND expression for predicates") { const auto best_languages = fsv::filtered_string_view{"c / c++"}; const auto vf = std::vector{[](const char& c) { return c == 'c' || c == '+' || c == '/'; }, [](const char& c) { return c > ' '; }, [](const char&) { return true; }}; const auto sv = fsv::compose(best_languages, vf); REQUIRE(std::string{sv} == "c/c++"); } // Specification 2.8.2 - Split // Purpose: Ensure that split conforms to the specification (similar to // python3's string split). // Expected: The delimiter should not appear in any of the split views. // Note: Test from specification, modified for catch2. TEST_CASE("split does not include delimiters") { const auto view = fsv::filtered_string_view{"0xDEADBEEF / 0xdeadbeef"}; const auto token = fsv::filtered_string_view{" / "}; const auto result = fsv::split(view, token); REQUIRE(result == std::vector{"0xDEADBEEF", "0xdeadbeef"}); } // Specification 2.8.2 - Split // Purpose: Ensure that split conforms to the specification (similar to // python3's string split). // Expected: The delimiter should produce empty strings when no characters appear // before or after it. // Note: Test from specification, modified for catch2. TEST_CASE("split produces empty strings when around lone delimiters") { const auto view = fsv::filtered_string_view{"xax"}; const auto token = fsv::filtered_string_view{"x"}; const auto result = fsv::split(view, token); REQUIRE(result == std::vector{"", "a", ""}); } // Specification 2.8.2 - Split // Purpose: Ensure that split conforms to the specification (similar to // python3's string split). // Expected: The delimiter should produce empty strings when only delimiters // are present. // Note: Test from specification, modified for catch2. TEST_CASE("split produces multiple empty strings around only delimiters") { const auto view = fsv::filtered_string_view{"xx"}; const auto token = fsv::filtered_string_view{"x"}; const auto result = fsv::split(view, token); REQUIRE(result == std::vector{"", "", ""}); } // Specification 2.8.2 - Split // Purpose: Ensure that split conforms to the specification (IDENTICAL to // python3's string split). We test multiple edge cases here, with // multiple delimiters on the outside and inside to ensure we conform. // Expected: The output should match the python3 equivalent split function. TEST_CASE("split functions as expected multiple delimiters") { const auto view = fsv::filtered_string_view{"a a Complicated Split Many Tokens among this stringa haha"}; const auto token = fsv::filtered_string_view{"a"}; const auto result = fsv::split(view, token); const auto expected = std::vector{"", " ", " Complic", "ted Split M", "ny Tokens ", "mong this string", " h", "h", ""}; REQUIRE(result == expected); } // Specification 2.8.3 - Substr // Purpose: Ensure that substr conforms to the specification. // Expected: The substring should split the view as if an offeset was provided // to the original string. // Note: Test from specification, modified for catch2. TEST_CASE("substr splits at correct offset") { const auto view = fsv::filtered_string_view{"Siberian Husky"}; REQUIRE(fsv::substr(view, 9) == "Husky"); } // Specification 2.8.3 - Substr // Purpose: Ensure that substr conforms to the specification. // Expected: The substring should split the view as if an offeset was provided // to the original string. // Note: Test from specification, modified for catch2. TEST_CASE("substr respects view predicate") { const auto view = fsv::filtered_string_view{"Sled Dog", [](const auto& c) { return std::isupper(static_cast(c)); }}; REQUIRE(fsv::substr(view, 0, 2) == "SD"); } // Specification 2.8.3 - Substr // Purpose: Ensure that substr conforms to the specification. // Expected: The substring should produce an empty string when provided with // zero as a length argument. TEST_CASE("substr produces an empty string with zero length") { const auto view = fsv::filtered_string_view{"length zero", [](const auto&) { return false; }}; REQUIRE(fsv::substr(view, 0) == ""); } // Specification: 2.9 - Iterator // Purpose: Ensure the iterator functions correctly on an empty view. // Expected: A std::copy of the empty view should result in an empty string. TEST_CASE("forward iterators function on empty views") { const auto view = fsv::filtered_string_view{}; auto str = std::string{}; std::copy(std::begin(view), std::end(view), std::back_inserter(str)); REQUIRE(str == ""); } // Specification: 2.9 - Iterator // Purpose: Ensure we have access to a valid iterator which conforms to the // standard. Cend and Cbegin should also be available and identical to // the standard non-const iterator. // Expected: A std::copy of the view should remain equal to the underlying // string, given the predicate does not modify its output. TEST_CASE("forward iterators function as expected") { const auto view = fsv::filtered_string_view{"iterator"}; auto str = std::string{}; std::copy(std::begin(view), std::end(view), std::back_inserter(str)); REQUIRE(str == "iterator"); str.clear(); std::copy(std::cbegin(view), std::cend(view), std::back_inserter(str)); REQUIRE(str == "iterator"); } // Specification: 2.9 - Iterator // Purpose: Ensure we have access to a valid iterator which conforms to the // standard. Cend and cbegin should also be available. // Expected: A std::copy should copy the underlying string filter. TEST_CASE("forward iterator is affected by predicate") { const auto view = fsv::filtered_string_view{"iterator", [](const auto& c) { return c != 'r'; }}; auto str = std::string{}; std::copy(std::begin(view), std::end(view), std::back_inserter(str)); REQUIRE(str == "iteato"); str.clear(); std::copy(std::cbegin(view), std::cend(view), std::back_inserter(str)); REQUIRE(str == "iteato"); } // Specification: 2.10 - Range // Purpose: Ensure our iterator functions as a bidirectional range. // Expected: A std::copy of a backwards iterator produces the view in reverse // of the begin iterators. TEST_CASE("bidirectional iterators function as expected") { const auto view = fsv::filtered_string_view{"iterator"}; auto str = std::string{}; std::copy(std::rbegin(view), std::rend(view), std::back_inserter(str)); REQUIRE(str == "rotareti"); str.clear(); std::copy(std::crbegin(view), std::crend(view), std::back_inserter(str)); REQUIRE(str == "rotareti"); } // Specification: 2.10 - Range // Purpose: Ensure our iterator functions as a bidirectional range with respect // to the provided predicate. // Expected: A std::copy of a backwards iterator produces the view in reverse // of the begin iterators, including the affects of the predicate. TEST_CASE("bidirectional iterators are affected by predicate.") { const auto view = fsv::filtered_string_view{"iterator", [](const auto& c) { return c != 'r'; }}; auto str = std::string{}; std::copy(std::rbegin(view), std::rend(view), std::back_inserter(str)); REQUIRE(str == "otaeti"); str.clear(); std::copy(std::crbegin(view), std::crend(view), std::back_inserter(str)); REQUIRE(str == "otaeti"); }