Compiling the cachekey test and converted it from a command line program to a unit test (#12545)
diff --git a/plugins/cachekey/CMakeLists.txt b/plugins/cachekey/CMakeLists.txt
index 82f9f67..f88a89d 100644
--- a/plugins/cachekey/CMakeLists.txt
+++ b/plugins/cachekey/CMakeLists.txt
@@ -20,3 +20,12 @@
target_link_libraries(cachekey PRIVATE PCRE::PCRE)
verify_global_plugin(cachekey)
verify_remap_plugin(cachekey)
+
+# Register test with CTest when testing is enabled.
+if(BUILD_TESTING)
+ add_executable(pattern_test unit_tests/pattern_test.cc pattern.cc common.cc)
+ target_include_directories(pattern_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
+ target_compile_definitions(pattern_test PRIVATE CACHEKEY_UNIT_TEST)
+ target_link_libraries(pattern_test PRIVATE Catch2::Catch2WithMain PCRE::PCRE libswoc::libswoc ts::tsutil)
+ add_catch2_test(NAME pattern_test COMMAND pattern_test)
+endif()
diff --git a/plugins/cachekey/tests/pattern_test.cc b/plugins/cachekey/tests/pattern_test.cc
deleted file mode 100644
index efefef8..0000000
--- a/plugins/cachekey/tests/pattern_test.cc
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-*/
-
-#include "pattern.h"
-#include <iostream>
-#include <vector>
-
-/**
- * @file pattern_test.cc
- * @brief simple test driver to test and experiment with PCRE patterns.
- *
- * @todo: create unit tests for the classes in pattern.cc.
- * Compile with -DCACHEKEY_UNIT_TEST to use non-ATS logging.
- */
-int
-main(int argc, char *argv[])
-{
- if (argc < 3) {
- std::cerr << "Usage: " << String(argv[0]) << " subject pattern" << std::endl;
- return -1;
- }
-
- String subject(argv[1]);
- String pattern(argv[2]);
-
- std::cout << "subject: '" << subject << "'" << std::endl;
- std::cout << "pattern: '" << pattern << "'" << std::endl;
-
- Pattern p;
- if (p.init(pattern)) {
- std::cout << "--- matching ---" << std::endl;
- bool result = p.match(subject);
- std::cout << "subject:'" << subject << "' " << (char *)(result ? "matched" : "did not match") << " pattern:'" << pattern << "'"
- << std::endl;
-
- std::cout << "--- capture ---" << std::endl;
- StringVector v;
- result = p.capture(subject, v);
- for (StringVector::iterator it = v.begin(); it != v.end(); ++it) {
- std::cout << "capture: " << *it << std::endl;
- }
-
- std::cout << "--- replace ---" << std::endl;
- String r;
- result = p.replace(subject, r);
- std::cout << "replacement result:'" << result << "'" << std::endl;
-
- } else {
- std::cout << "pattern: '" << pattern << "' failed to compile" << std::endl;
- }
-}
diff --git a/plugins/cachekey/unit_tests/pattern_test.cc b/plugins/cachekey/unit_tests/pattern_test.cc
new file mode 100644
index 0000000..01b4bb0
--- /dev/null
+++ b/plugins/cachekey/unit_tests/pattern_test.cc
@@ -0,0 +1,121 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+// This file adds multiple test cases for Pattern: compile, match, capture, replace.
+#include <catch2/catch_test_macros.hpp>
+#include "pattern.h"
+
+TEST_CASE("Pattern compile and match behavior", "[cachekey][pattern]")
+{
+ SECTION("Simple literal match")
+ {
+ Pattern p;
+ REQUIRE(p.init("hello"));
+ CHECK(p.match("hello") == true);
+ CHECK(p.match("hell") == false);
+ }
+
+ SECTION("Simple capture groups")
+ {
+ Pattern p;
+ REQUIRE(p.init("^(\\w+)-(\\d+)$"));
+ StringVector caps;
+ CHECK(p.capture("item-123", caps));
+ // capture returns all groups including group 0, so expect 3 entries (full + 2 groups)
+ CHECK(caps.size() == 3);
+ CHECK(caps[1] == "item");
+ CHECK(caps[2] == "123");
+ }
+
+ SECTION("Replacement using tokens")
+ {
+ Pattern p;
+ REQUIRE(p.init("^(\\w+)-(\\d+)$", "$2:$1", /*replace*/ true));
+ String res;
+ CHECK(p.replace("item-123", res));
+ CHECK(res == "123:item");
+ }
+
+ SECTION("Invalid pattern fails to compile")
+ {
+ Pattern p;
+ // malformed pattern (unclosed parentheses)
+ CHECK(p.init("(unclosed") == false);
+ }
+
+ SECTION("Greedy vs non-greedy capture")
+ {
+ Pattern pg;
+ Pattern png;
+ REQUIRE(pg.init("a(.*)b")); // greedy
+ REQUIRE(png.init("a(.*?)b")); // non-greedy
+
+ StringVector caps_g;
+ StringVector caps_ng;
+ REQUIRE(pg.capture("a123b456b", caps_g));
+ REQUIRE(png.capture("a123b456b", caps_ng));
+
+ // greedy should capture up to the last 'b'
+ CHECK(caps_g.size() >= 2);
+ CHECK(caps_g[1] == "123b456");
+
+ // non-greedy should capture up to the first 'b'
+ CHECK(caps_ng.size() >= 2);
+ CHECK(caps_ng[1] == "123");
+ }
+
+ SECTION("Empty-string anchors")
+ {
+ Pattern p;
+ REQUIRE(p.init("^$"));
+ // Pattern::match uses PCRE_NOTEMPTY which prevents empty-string matches.
+ // Therefore '^$' will NOT match an empty subject with the current implementation.
+ CHECK(p.match("") == false);
+ CHECK(p.match("not-empty") == false);
+ }
+
+ SECTION("Case-insensitive inline flag")
+ {
+ Pattern p;
+ // PCRE inline flag for case-insensitive
+ REQUIRE(p.init("(?i)AbC"));
+ CHECK(p.match("aBc") == true);
+ CHECK(p.match("ABC") == true);
+ }
+
+ SECTION("Repeated captures and empty captures")
+ {
+ Pattern p;
+ REQUIRE(p.init("(\\w*)-(\\w*)"));
+ StringVector caps;
+ REQUIRE(p.capture("-foo", caps));
+ CHECK(caps.size() == 3);
+ // first group before '-' is empty
+ CHECK(caps[1] == "");
+ CHECK(caps[2] == "foo");
+ }
+
+ SECTION("Long subject match")
+ {
+ Pattern p;
+ REQUIRE(p.init("^a+$"));
+ // create a long string of 'a' characters
+ std::string long_s(10000, 'a');
+ CHECK(p.match(long_s.c_str()) == true);
+ }
+}