a697941150
Overriding checks/reviewers as CODEOWNER changes are pending * Runners Update Update aqlprofile-continuous_integration.yml Update aqlprofile-continuous_integration.yml Update aqlprofile-continuous_integration.yml Update aqlprofile-continuous_integration.yml Update aqlprofile-continuous_integration.yml Update aqlprofile-continuous_integration.yml Update aqlprofile-continuous_integration.yml Update aqlprofile-continuous_integration.yml Update aqlprofile-continuous_integration.yml Update aqlprofile-continuous_integration.yml Update aqlprofile-continuous_integration.yml Update aqlprofile-continuous_integration.yml Testing ROCProfiler-SDK Testing ROCProfiler-SDK Changing CDash Fixing ROCProfiler-SDK Moving AQLProfile Navi3 and Navi4 to DIND Moving AQLProfile Navi3 and Navi4 to DIND Moving AQLProfile Navi3 and Navi4 to DIND Moving AQLProfile Navi3 and Navi4 to DIND Moving AQLProfile Navi3 and Navi4 to DIND Updating RHEL and SLES for AQLProfile Updating RHEL and SLES for AQLProfile Updating RHEL and SLES for AQLProfile Updating RHEL and SLES for AQLProfile Updating RHEL and SLES for AQLProfile Updating RHEL and SLES for AQLProfile Updating RHEL and SLES for AQLProfile Updating RHEL and SLES for AQLProfile Updating RHEL and SLES for AQLProfile Updating RHEL and SLES for AQLProfile Updating images Updating images Updating images Updating images Updating RHEL and SLES for AQLProfile Fixing RPM OSes AQLprofile Updating RHEL and SLES for AQLProfile Updating RHEL and SLES for AQLProfile Updating RHEL and SLES for AQLProfile Updating RHEL and SLES for AQLProfile Updating RHEL and SLES for AQLProfile Updating RHEL and SLES for AQLProfile Updating RHEL and SLES for ROCProfiler-SDK Updating RHEL and SLES for ROCProfiler-SDK Updating RHEL and SLES for ROCProfiler-SDK Updating RHEL and SLES for ROCProfiler-SDK Updating RHEL and SLES for ROCProfiler-SDK Updating RHEL and SLES for ROCProfiler-SDK Updating RHEL and SLES for ROCProfiler-SDK Updating RHEL and SLES for ROCProfiler-SDK Updating RHEL and SLES for ROCProfiler-SDK Updating RHEL and SLES for ROCProfiler-SDK Updating RHEL and SLES for ROCProfiler-SDK Updating RHEL and SLES for ROCProfiler-SDK * Fixing ENV for ROCProfiler-SDK Fixing ENV for ROCProfiler-SDK Temp workaround for OpenMP targets Fixing ROCProfiler-SDK for Ubuntu * Fixing Ubuntu Workflow Fixing Ubuntu Workflow Fixing Ubuntu Workflow Fixing Ubuntu Workflow Fixing Ubuntu Workflow Fixing Ubuntu Workflow Fixing Ubuntu Workflow Fixing Ubuntu Workflow Update rocprofiler-sdk-continuous_integration.yml Fixing Ubuntu Workflow Fixing Ubuntu Workflow Fixing Ubuntu Workflow Fixing Ubuntu Workflow Fixing Ubuntu Workflow Adding RPM Package Adding RPM Package Fixing OPenMP Compiler Issues Fixing OPenMP Compiler Issues Fixing OPenMP Compiler Issues Fixing OPenMP Compiler Issues Fixing OPenMP Compiler Issues Fixing OPenMP Compiler Issues Fixing OPenMP Compiler Issues Fixing OPenMP Compiler Issues Update rocprofiler-sdk-continuous_integration.yml Update rocprofiler-sdk-continuous_integration.yml Update aqlprofile-continuous_integration.yml Update rocprofiler-sdk-continuous_integration.yml Fixing AQLProfile * [rocprofiler-sdk][CI] add latest aqlprofile to rocprofiler-sdk workflow (#352) * add aqlprofile * misc. * format * add sudo to install * Update rocprofiler-sdk-continuous_integration.yml * Update rocprofiler-sdk-continuous_integration.yml * Update rocprofiler-sdk-continuous_integration.yml --------- Co-authored-by: Ammar ELWazir <ammar.elwazir@amd.com> Update aqlprofile-continuous_integration.yml Removing extra packages Removing extra packages Fixing ROCM Path Issues Fixing ROCM Path Issues Fixing ROCM Path Issues Fixing RHEL Fixing RHEL Fixing RHEL Fixing RHEL Fixing RHEL Fixing Sanitizers * General Fixes * Fixing ROCProfiler-SDK CI * Fixing ROCProfiler-SDK CI * Update projects/aqlprofile/dashboard.cmake Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * General Fixes * Update Readme.txt * Fix ROCProfiler SDK CI * Fix ROCProfiler SDK CI * Fix ROCProfiler SDK CI * Fix ROCProfiler SDK CI * Update rocprofiler-sdk-continuous_integration.yml * Fix ROCProfiler SDK CI * Fix ROCProfiler SDK CI * Fix for RHEL and Sanitizers for ROCProfiler-SDK * Fix for RHEL and Sanitizers for ROCProfiler-SDK * Fix for RHEL and Sanitizers for ROCProfiler-SDK * Fix for RHEL and Sanitizers for ROCProfiler-SDK * Upgrade ROCm Release & Fix for RHEL & SLES - ROCProfiler SDK CI * Fix for RHEL & SLES - ROCProfiler SDK CI * Fix for RHEL & SLES & Sanitizers - ROCProfiler SDK CI * Fix for RHEL & SLES & Sanitizers - ROCProfiler SDK CI * Update rocprofiler-sdk-continuous_integration.yml * Update rocprofiler-sdk-continuous_integration.yml * Update rocprofiler-sdk-continuous_integration.yml * Update rocprofiler-sdk-continuous_integration.yml * Update rocprofiler-sdk-continuous_integration.yml * Update rocprofiler-sdk-continuous_integration.yml * Update rocprofiler-sdk-continuous_integration.yml * Adding ROCR Installation * Adding ROCR Installation * Adding ROCR Installation * Adding ROCR Installation * Adding ROCR Installation * Adding ROCR Installation * Update run-ci.py * Fix for Sanitizers & Fix for RHEL 8.8 * Updating Code Coverage Workflow * Updating Code Coverage Workflow * Formatting Fix * Formatting Fix * Fix for Code Coverage & Sanitizers * Fix for Code Coverage & Sanitizers * Fix for Code Coverage & Sanitizers * Caching Docker * Caching Docker * Caching Docker * Changing Runner for CI Builder * Adding CCache * Fixing Core * Fixing Core * Fixing Core * Fixing Core * Fixing Core * Update rocprofiler-sdk-continuous_integration.yml * Update ROCm and amdgpu repository configurations * Refactor repository configuration commands in CI * Fix installation commands in CI workflow * Remove unnecessary packages from installation commands * Update ROCm and amdgpu repository paths in CI config * Update pip installation commands to handle errors * Install AWS CLI in CI workflow * Update rocprofiler-sdk-continuous_integration.yml * Remove awscli installation from CI workflow * Modify PATH and pipx install commands in CI config * Refactor ROCm SDK CI workflow to eliminate redundancy * Add safe.directory configuration for git * Update rocprofiler-sdk-continuous_integration.yml * Fix CMake install prefix in CI workflow * Add variant option to ccache configuration * Change compiler launcher from ccache to sccache * Set up Python virtual environment in CI workflow * Remove ccache launcher from CMake build * Add environment setup for building projects * Add Curl installation step for RHEL 8.8 * Update rocprofiler-sdk-continuous_integration.yml * Update rocprofiler-sdk-continuous_integration.yml * Fixing RPM * Fixing RPM & Code Coverage * Fixing RPM * Fixing CI * Lowering the size of the docker image * Update aqlprofile-continuous_integration.yml * Updating paths in AQLProfile * Splitting the Build CI Docker Images from Main CI * Create Dockerfile.ci, update ci docker workflow to reference it * Splitting the Build CI Docker Images from Main CI * Add new line to Dockerfile.ci * Remove on schedule logic from ci docker workflow, change cdash project name in run-ci.py * Update file path in build_ci_docker_images.yml * Remove context from docker step * Update file path in build_ci_docker_images * more path changes * remove context again * Update rocprofiler-sdk-build_ci_docker_images.yml * Update rocprofiler-sdk-code_coverage.yml * Update rocprofiler-sdk-continuous_integration.yml * Remove env variables from rocprofiler-sdk-build_ci_docker_images.yml * Rename docker images file * Rename KEY to FILE_NAME for Docker tarball * [rocprofiler-sdk][CI] lint fixes (#830) * lint fixes. * Updating Code Coverage Workflow * Update rocprofiler-sdk-code_coverage.yml * Update format.hpp * Update format.hpp --------- Co-authored-by: Venkateshwar Reddy Kandula <venkateshwar.kandula1306@gmail.com> Co-authored-by: Elwazir, Ammar <Ammar.Elwazir@amd.com> * TEMP: Removing ROCR build from develop * [rocprofiler-sdk][SDK] Add new HIP API changes for ROCm 7.1 (#856) * Add new HIP 7.1 changes. * bug fix. * bug fix. * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix typo in hipDriverEntryPoint case statement --------- Co-authored-by: Venkateshwar Reddy Kandula <venkateshwar.kandula1306@gmail.com> Co-authored-by: Ammar ELWazir <ammar.elwazir@amd.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Venkateshwar Reddy Kandula <Venkateshwarreddy.Kandula@amd.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: jbonnell-amd <jason.bonnell@amd.com> Co-authored-by: Venkateshwar Reddy Kandula <venkateshwar.kandula1306@gmail.com>
672 lines
21 KiB
C++
672 lines
21 KiB
C++
#include <gtest/gtest.h>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <optional>
|
|
#include <regex>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include "lib/common/regex.hpp" // rocprofiler::common::regex::...
|
|
|
|
namespace R = rocprofiler::common::regex;
|
|
|
|
// ----------------------------- Helpers -----------------------------
|
|
|
|
struct StdRes
|
|
{
|
|
bool ok = false;
|
|
size_t b = 0;
|
|
size_t e = 0;
|
|
};
|
|
|
|
namespace
|
|
{
|
|
std::optional<bool>
|
|
TryStdMatch(std::string_view text, std::string_view pat)
|
|
{
|
|
try
|
|
{
|
|
std::regex re(std::string(pat), std::regex::ECMAScript);
|
|
return std::regex_match(std::string(text), re);
|
|
} catch(const std::regex_error&)
|
|
{
|
|
return std::nullopt; // invalid pattern for std::regex
|
|
}
|
|
}
|
|
|
|
std::optional<StdRes>
|
|
TryStdSearch(std::string_view text, std::string_view pat)
|
|
{
|
|
try
|
|
{
|
|
std::regex re(std::string(pat), std::regex::ECMAScript);
|
|
std::cmatch m;
|
|
std::string s(text);
|
|
if(std::regex_search(s.c_str(), m, re))
|
|
{
|
|
return StdRes{true,
|
|
static_cast<size_t>(m.position()),
|
|
static_cast<size_t>(m.position() + m.length())};
|
|
}
|
|
return StdRes{false, 0, 0};
|
|
} catch(const std::regex_error&)
|
|
{
|
|
return std::nullopt; // invalid pattern
|
|
}
|
|
}
|
|
|
|
std::optional<std::string>
|
|
TryStdReplace(std::string_view text, std::string_view pat, std::string_view repl)
|
|
{
|
|
try
|
|
{
|
|
std::regex re(std::string(pat), std::regex::ECMAScript);
|
|
return std::regex_replace(std::string(text), re, std::string(repl));
|
|
} catch(const std::regex_error&)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
// ----------------------------- Tests -------------------------------
|
|
|
|
TEST(regex_parity, literals_and_escapes)
|
|
{
|
|
// Avoid invalid ECMAScript escapes that std::regex rejects (e.g., "\c").
|
|
struct C
|
|
{
|
|
const char* s;
|
|
const char* p;
|
|
};
|
|
std::vector<C> cases = {
|
|
{"abc", "abc"},
|
|
{"a\nb", "a\\nb"},
|
|
{"a\tb", "a\\tb"},
|
|
{"\\", "\\\\"},
|
|
{"a", "a\\n"}, // literal 'n' after backslash for our engine & ECMAScript
|
|
};
|
|
for(auto& c : cases)
|
|
{
|
|
auto sm = TryStdMatch(c.s, c.p);
|
|
if(!sm.has_value()) continue; // skip invalid for std::regex
|
|
EXPECT_EQ(R::regex_match(c.s, c.p), sm.value());
|
|
|
|
auto ss = TryStdSearch(c.s, c.p);
|
|
if(!ss.has_value()) continue;
|
|
EXPECT_EQ(R::regex_search(c.s, c.p), ss->ok);
|
|
if(ss->ok)
|
|
{
|
|
size_t b = 0;
|
|
size_t e = 0;
|
|
ASSERT_TRUE(R::regex_search(c.s, c.p, b, e));
|
|
EXPECT_EQ(b, ss->b);
|
|
EXPECT_EQ(e, ss->e);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(regex_parity, dot_and_anchors)
|
|
{
|
|
auto cmp = [&](const std::string& s, const std::string& p) {
|
|
auto sm = TryStdMatch(s, p);
|
|
if(!sm) return;
|
|
EXPECT_EQ(R::regex_match(s, p), *sm);
|
|
|
|
auto ss = TryStdSearch(s, p);
|
|
if(!ss) return;
|
|
EXPECT_EQ(R::regex_search(s, p), ss->ok);
|
|
if(ss->ok)
|
|
{
|
|
size_t b = 0;
|
|
size_t e = 0;
|
|
ASSERT_TRUE(R::regex_search(s, p, b, e));
|
|
EXPECT_EQ(b, ss->b);
|
|
EXPECT_EQ(e, ss->e);
|
|
}
|
|
};
|
|
cmp("abc", "a.c");
|
|
cmp("abc", "^abc$");
|
|
cmp("zzzHello", "^Hello");
|
|
cmp("Hello world", "^Hello");
|
|
cmp("world!", "world!$");
|
|
}
|
|
|
|
TEST(regex_parity, char_classes_and_shortcuts)
|
|
{
|
|
std::vector<std::pair<std::string, std::string>> pats = {{"abc", "[a-c][a-c][a-c]"},
|
|
{"abc", "[^0-9]+"},
|
|
{"A_", "\\w\\w"},
|
|
{"9 ", "\\d\\s"},
|
|
{"__", "\\W\\W"},
|
|
{"Z5z", "[A-Z]\\d[a-z]"}};
|
|
for(auto& [s, p] : pats)
|
|
{
|
|
auto sm = TryStdMatch(s, p);
|
|
if(!sm) continue;
|
|
EXPECT_EQ(R::regex_match(s, p), *sm);
|
|
|
|
auto ss = TryStdSearch(s, p);
|
|
if(!ss) continue;
|
|
EXPECT_EQ(R::regex_search(s, p), ss->ok);
|
|
if(ss->ok)
|
|
{
|
|
size_t b = 0;
|
|
size_t e = 0;
|
|
ASSERT_TRUE(R::regex_search(s, p, b, e));
|
|
EXPECT_EQ(b, ss->b);
|
|
EXPECT_EQ(e, ss->e);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(regex_parity, alternation_and_grouping)
|
|
{
|
|
std::string s = "abc123xyz";
|
|
std::string p = "(abc|def)\\d{3}xyz";
|
|
auto sm = TryStdMatch(s, p);
|
|
ASSERT_TRUE(sm.has_value());
|
|
EXPECT_EQ(R::regex_match(s, p), *sm);
|
|
|
|
auto ss = TryStdSearch(s, p);
|
|
ASSERT_TRUE(ss.has_value());
|
|
EXPECT_EQ(R::regex_search(s, p), ss->ok);
|
|
if(ss->ok)
|
|
{
|
|
size_t b = 0;
|
|
size_t e = 0;
|
|
ASSERT_TRUE(R::regex_search(s, p, b, e));
|
|
EXPECT_EQ(b, ss->b);
|
|
EXPECT_EQ(e, ss->e);
|
|
}
|
|
|
|
EXPECT_TRUE(R::regex_search("foo bar", "(foo|bar)"));
|
|
EXPECT_FALSE(R::regex_search("zzz", "(foo|bar)"));
|
|
}
|
|
|
|
TEST(regex_parity, quantifiers_greedy)
|
|
{
|
|
struct C
|
|
{
|
|
const char* s;
|
|
const char* p;
|
|
};
|
|
std::vector<C> cases = {
|
|
{"", "a*"},
|
|
{"aaa", "a+"},
|
|
{"aaab", "a+b"},
|
|
{"abbb", "ab{3}"},
|
|
{"abbbbb", "ab{3,}"},
|
|
{"acccb", "ac{2,3}b"},
|
|
};
|
|
for(auto& c : cases)
|
|
{
|
|
auto sm = TryStdMatch(c.s, c.p);
|
|
ASSERT_TRUE(sm.has_value());
|
|
EXPECT_EQ(R::regex_match(c.s, c.p), *sm);
|
|
}
|
|
}
|
|
|
|
TEST(regex_parity, backtracking_across_tail)
|
|
{
|
|
const std::string s = "/prefix/%env{ARBITRARY_ENV_VARIABLE}%/suffix.txt";
|
|
const std::string p = "(.*)%(env|ENV)\\{([A-Z0-9_]+)\\}%(.*)";
|
|
auto ss = TryStdSearch(s, p);
|
|
ASSERT_TRUE(ss.has_value());
|
|
size_t b = 0;
|
|
size_t e = 0;
|
|
ASSERT_TRUE(R::regex_search(s, p, b, e));
|
|
ASSERT_TRUE(ss->ok);
|
|
EXPECT_EQ(b, ss->b);
|
|
EXPECT_EQ(e, ss->e);
|
|
}
|
|
|
|
TEST(regex_parity, search_span)
|
|
{
|
|
const std::string s = "xx abcd123 yy";
|
|
const std::string p = "[a-z]+\\d+";
|
|
auto ss = TryStdSearch(s, p);
|
|
ASSERT_TRUE(ss.has_value());
|
|
size_t b = 999;
|
|
size_t e = 999;
|
|
ASSERT_TRUE(R::regex_search(s, p, b, e));
|
|
ASSERT_TRUE(ss->ok);
|
|
EXPECT_EQ(b, ss->b);
|
|
EXPECT_EQ(e, ss->e);
|
|
}
|
|
|
|
TEST(regex_parity, replace_whole_and_groups)
|
|
{
|
|
const std::string s = "abc123def";
|
|
const std::string p = "(\\w+?)(\\d+)(\\w+)";
|
|
auto r1 = TryStdReplace(s, p, "[$&]");
|
|
ASSERT_TRUE(r1.has_value());
|
|
auto r2 = TryStdReplace(s, p, "($1)-{$2}-<$3>");
|
|
ASSERT_TRUE(r2.has_value());
|
|
auto r3 = TryStdReplace(s, "(\\d+)", "pre($`) mid($&) post($')");
|
|
ASSERT_TRUE(r3.has_value());
|
|
|
|
EXPECT_EQ(R::regex_replace(s, p, "[$&]"), *r1);
|
|
EXPECT_EQ(R::regex_replace(s, p, "($1)-{$2}-<$3>"), *r2);
|
|
EXPECT_EQ(R::regex_replace(s, "(\\d+)", "pre($`) mid($&) post($')"), *r3);
|
|
}
|
|
|
|
TEST(regex_parity, replace_global_multiple_hits)
|
|
{
|
|
const std::string s = "a1 b22 c333";
|
|
const std::string p = "(\\d+)";
|
|
auto sr = TryStdReplace(s, p, "[$1]");
|
|
ASSERT_TRUE(sr.has_value());
|
|
EXPECT_EQ(R::regex_replace(s, p, "[$1]"), *sr);
|
|
}
|
|
|
|
TEST(regex_parity, replace_two_digit_capture_index)
|
|
{
|
|
// 11 groups: 1=outer, 2=a, ..., 10=i, 11=j
|
|
const std::string s = "abcdefghij";
|
|
const std::string p = "((a)(b)(c)(d)(e)(f)(g)(h)(i)(j))";
|
|
auto sr = TryStdReplace(s, p, "$10");
|
|
ASSERT_TRUE(sr.has_value());
|
|
EXPECT_EQ(R::regex_replace(s, p, "$10"), *sr); // should be "i"
|
|
}
|
|
|
|
TEST(regex_parity, env_patterns_from_issue)
|
|
{
|
|
const std::string fpath = "/home/user/summary/%env{ARBITRARY_ENV_VARIABLE}%/out_summary.txt";
|
|
|
|
const std::vector<std::string> pats = {
|
|
"(.*)%(env|ENV)\\{([A-Z0-9_]+)\\}%(.*)", // should match
|
|
"(.*)\\$(env|ENV)\\{([A-Z0-9_]+)\\}(.*)", // should NOT match
|
|
"(.*)%q\\{([A-Z0-9_]+)\\}(.*)" // should NOT match here
|
|
};
|
|
|
|
for(const auto& p : pats)
|
|
{
|
|
auto ss = TryStdSearch(fpath, p);
|
|
ASSERT_TRUE(ss.has_value());
|
|
size_t b = 0;
|
|
size_t e = 0;
|
|
bool got = R::regex_search(fpath, p, b, e);
|
|
EXPECT_EQ(got, ss->ok) << "pattern: " << p;
|
|
if(ss->ok)
|
|
{
|
|
EXPECT_EQ(b, ss->b);
|
|
EXPECT_EQ(e, ss->e);
|
|
// Check common replacements
|
|
auto r1 = TryStdReplace(fpath, p, "$1");
|
|
ASSERT_TRUE(r1.has_value());
|
|
auto r3 = TryStdReplace(fpath, p, "$3");
|
|
ASSERT_TRUE(r3.has_value());
|
|
auto r4 = TryStdReplace(fpath, p, "$4");
|
|
ASSERT_TRUE(r4.has_value());
|
|
EXPECT_EQ(R::regex_replace(fpath, p, "$1"), *r1);
|
|
EXPECT_EQ(R::regex_replace(fpath, p, "$3"), *r3);
|
|
EXPECT_EQ(R::regex_replace(fpath, p, "$4"), *r4);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(regex_parity, zero_length_and_empty)
|
|
{
|
|
auto sm = TryStdMatch("", "a*");
|
|
ASSERT_TRUE(sm.has_value());
|
|
EXPECT_EQ(R::regex_match("", "a*"), *sm);
|
|
|
|
auto ss = TryStdSearch("", "");
|
|
ASSERT_TRUE(ss.has_value());
|
|
EXPECT_EQ(R::regex_search("", ""), ss->ok);
|
|
if(ss->ok)
|
|
{
|
|
size_t b = 0;
|
|
size_t e = 0;
|
|
ASSERT_TRUE(R::regex_search("", "", b, e));
|
|
EXPECT_EQ(b, ss->b);
|
|
EXPECT_EQ(e, ss->e);
|
|
}
|
|
}
|
|
|
|
TEST(regex_parity, bad_patterns_throw)
|
|
{
|
|
// Both should throw on bad syntax we recognize (unterminated brackets/parens)
|
|
EXPECT_THROW({ R::regex_search("abc", "[a-z"); }, std::runtime_error);
|
|
EXPECT_THROW({ R::regex_match("abc", "(abc"); }, std::runtime_error);
|
|
EXPECT_THROW({ R::regex_replace("abc", "[", "x"); }, std::runtime_error);
|
|
|
|
EXPECT_THROW(
|
|
{
|
|
std::regex re("[a-z", std::regex::ECMAScript);
|
|
(void) re;
|
|
},
|
|
std::regex_error);
|
|
EXPECT_THROW(
|
|
{
|
|
std::regex re("(abc", std::regex::ECMAScript);
|
|
(void) re;
|
|
},
|
|
std::regex_error);
|
|
EXPECT_THROW(
|
|
{
|
|
std::regex re("[", std::regex::ECMAScript);
|
|
(void) re;
|
|
},
|
|
std::regex_error);
|
|
}
|
|
|
|
// --- LAZY QUANTIFIERS -------------------------------------------------
|
|
TEST(regex_parity, lazy_quantifiers)
|
|
{
|
|
const std::string s = "a---b---c";
|
|
const std::string p = "a.*?b";
|
|
size_t b1 = 0;
|
|
size_t e1 = 0;
|
|
size_t b2 = 0;
|
|
size_t e2 = 0;
|
|
// search span parity
|
|
ASSERT_TRUE(R::regex_search(s, p, b1, e1));
|
|
std::regex re(p, std::regex::ECMAScript);
|
|
std::cmatch m;
|
|
ASSERT_TRUE(std::regex_search(s.c_str(), m, re));
|
|
b2 = static_cast<size_t>(m.position());
|
|
e2 = b2 + static_cast<size_t>(m.length());
|
|
EXPECT_EQ(b1, b2);
|
|
EXPECT_EQ(e1, e2);
|
|
// replace should touch minimal range
|
|
std::string r1 = R::regex_replace(s, p, "X");
|
|
std::string r2 = std::regex_replace(s, re, "X");
|
|
EXPECT_EQ(r1, r2);
|
|
}
|
|
|
|
// --- CAPTURE VALUE IS LAST ITERATION OF A QUANTIFIED GROUP -----------
|
|
TEST(regex_parity, capture_is_last_iteration)
|
|
{
|
|
const std::string s = "ababab";
|
|
const std::string p = "(ab)*";
|
|
// Replacing the match with $1 should be "ab" (last repetition)
|
|
EXPECT_EQ(R::regex_replace(s, p, "$1"), std::regex_replace(s, std::regex(p), "$1"));
|
|
}
|
|
|
|
// --- ALTERNATION CHOICE (LEFT-TO-RIGHT) -------------------------------
|
|
TEST(regex_parity, alternation_preference)
|
|
{
|
|
// (a|ab)b on "abb" prefers 'a' alternative (leftmost that leads to a match)
|
|
const std::string s = "abb";
|
|
const std::string p = "(a|ab)b";
|
|
std::string r1 = R::regex_replace(s, p, "($1)");
|
|
std::string r2 = std::regex_replace(s, std::regex(p), "($1)");
|
|
EXPECT_EQ(r1, r2); // should be "(a)"
|
|
}
|
|
|
|
// --- CHARACTER CLASS CORNER CASES -------------------------------------
|
|
TEST(regex_parity, class_hyphen_literal_edges)
|
|
{
|
|
// '-' first/last is literal
|
|
EXPECT_TRUE(R::regex_match("-", "[-a]"));
|
|
EXPECT_TRUE(std::regex_match(std::string("-"), std::regex("[-a]")));
|
|
EXPECT_TRUE(R::regex_match("a", "[-a]"));
|
|
EXPECT_TRUE(std::regex_match(std::string("a"), std::regex("[-a]")));
|
|
}
|
|
|
|
TEST(regex_parity, class_escaped_bracket_and_negated_shorthand)
|
|
{
|
|
// Escaped ']' inside class
|
|
EXPECT_TRUE(R::regex_match("]", "[\\]]"));
|
|
EXPECT_TRUE(std::regex_match(std::string("]"), std::regex("[\\]]")));
|
|
// Negated digit class allows letters
|
|
EXPECT_TRUE(R::regex_match("g", "[^\\d]"));
|
|
EXPECT_TRUE(std::regex_match(std::string("g"), std::regex("[^\\d]")));
|
|
}
|
|
|
|
// --- ANCHORS WITH EMPTY STRING ----------------------------------------
|
|
TEST(regex_parity, anchors_empty_string)
|
|
{
|
|
EXPECT_EQ(R::regex_match("", "^$"), std::regex_match(std::string(""), std::regex("^$")));
|
|
}
|
|
|
|
// --- QUANTIFIER {0} ZERO REPS -----------------------------------------
|
|
TEST(regex_parity, quantifier_zero_reps_matches_empty)
|
|
{
|
|
// {0} should match empty; compare match result
|
|
const std::string p = "a{0}";
|
|
EXPECT_EQ(R::regex_search("xyz", p),
|
|
(bool) std::regex_search(std::string("xyz"), std::regex(p)));
|
|
EXPECT_EQ(R::regex_match("", p), std::regex_match(std::string(""), std::regex(p)));
|
|
}
|
|
|
|
// --- REPLACEMENT TOKEN CORNER CASES -----------------------------------
|
|
TEST(regex_parity, replacement_one_digit_then_literal)
|
|
{
|
|
// When only one group exists, "$10" == "$1" + "0"
|
|
const std::string s = "a";
|
|
const std::string p = "(a)";
|
|
EXPECT_EQ(R::regex_replace(s, p, "$10"), std::regex_replace(s, std::regex(p), "$10"));
|
|
}
|
|
|
|
TEST(regex_parity, replacement_unknown_two_digit_group_falls_back)
|
|
{
|
|
// With only 1 capture, "$99" -> ($9 empty) + "9" => "9"
|
|
const std::string s = "a";
|
|
const std::string p = "(a)";
|
|
EXPECT_EQ(R::regex_replace(s, p, "$99"), std::regex_replace(s, std::regex(p), "$99"));
|
|
}
|
|
|
|
TEST(regex_parity, replacement_dollar_at_end_is_literal)
|
|
{
|
|
const std::string s = "abc123";
|
|
const std::string p = "\\d+";
|
|
EXPECT_EQ(R::regex_replace(s, p, "x$"), std::regex_replace(s, std::regex(p), "x$"));
|
|
}
|
|
|
|
TEST(regex_parity, replacement_whole_match_aliases)
|
|
{
|
|
const std::string s = "abc123def";
|
|
const std::string p = "(\\w+?)(\\d+)(\\w+)";
|
|
EXPECT_EQ(R::regex_replace(s, p, "<$0>"), std::regex_replace(s, std::regex(p), "<$0>"));
|
|
EXPECT_EQ(R::regex_replace(s, p, "<$&>"), std::regex_replace(s, std::regex(p), "<$&>"));
|
|
}
|
|
|
|
// --- CAPTURE INDEXING STABILITY WITH NESTED GROUPS ---------------------
|
|
TEST(regex_parity, nested_capture_indices_left_to_right)
|
|
{
|
|
// Ensure left-to-right numbering at '(' is honored
|
|
const std::string s = "xyz";
|
|
const std::string p = "((x)(y))(z)";
|
|
// Expect $1="xy", $2="x", $3="y", $4="z"
|
|
EXPECT_EQ(R::regex_replace(s, p, "$1|$2|$3|$4"),
|
|
std::regex_replace(s, std::regex(p), "$1|$2|$3|$4"));
|
|
}
|
|
|
|
// --- COMPATIBILITY AND INTERFACE TESTS -------------------------------------------
|
|
TEST(regex_compatibility, deterministic_behavior)
|
|
{
|
|
// This test verifies that our implementation doesn't depend on std::regex
|
|
// across different usage patterns and string types
|
|
|
|
std::vector<std::pair<std::string, std::string>> test_cases = {
|
|
{"hello world", "hello.*world"},
|
|
{"file_v1.2.3.txt", "v(\\d+)\\.(\\d+)\\.(\\d+)"},
|
|
{"path/to/file", "path/to/.*"},
|
|
{"123-456-7890", "\\d{3}-\\d{3}-\\d{4}"},
|
|
{"", "a*"}, // Empty string edge case
|
|
{"abcdef", "[a-f]+"}};
|
|
|
|
for(const auto& [text, pattern] : test_cases)
|
|
{
|
|
// Operations should be deterministic and repeatable
|
|
bool match_result = R::regex_match(text, pattern);
|
|
bool search_result = R::regex_search(text, pattern);
|
|
|
|
// Results should be consistent across multiple calls
|
|
EXPECT_EQ(match_result, R::regex_match(text, pattern))
|
|
<< "Match result inconsistent for: " << text << " with pattern: " << pattern;
|
|
EXPECT_EQ(search_result, R::regex_search(text, pattern))
|
|
<< "Search result inconsistent for: " << text << " with pattern: " << pattern;
|
|
}
|
|
}
|
|
|
|
TEST(regex_compatibility, string_interface_types)
|
|
{
|
|
// Test that our implementation works correctly with different string interface types
|
|
// (const char*, std::string, std::string_view)
|
|
|
|
const char* c_str = "test string with numbers 123";
|
|
std::string std_str = "test string with numbers 123";
|
|
std::string_view sv = std_str;
|
|
|
|
const std::string pattern = "\\d+";
|
|
|
|
// All these should produce the same results
|
|
bool c_str_result = R::regex_search(c_str, pattern);
|
|
bool std_str_result = R::regex_search(std_str, pattern);
|
|
bool sv_result = R::regex_search(sv, pattern);
|
|
|
|
EXPECT_EQ(c_str_result, std_str_result);
|
|
EXPECT_EQ(std_str_result, sv_result);
|
|
EXPECT_TRUE(c_str_result); // Should find "123"
|
|
|
|
// Test position results are consistent
|
|
size_t c_begin, c_end, s_begin, s_end, sv_begin, sv_end;
|
|
EXPECT_TRUE(R::regex_search(c_str, pattern, c_begin, c_end));
|
|
EXPECT_TRUE(R::regex_search(std_str, pattern, s_begin, s_end));
|
|
EXPECT_TRUE(R::regex_search(sv, pattern, sv_begin, sv_end));
|
|
|
|
EXPECT_EQ(c_begin, s_begin);
|
|
EXPECT_EQ(s_begin, sv_begin);
|
|
EXPECT_EQ(c_end, s_end);
|
|
EXPECT_EQ(s_end, sv_end);
|
|
}
|
|
|
|
TEST(regex_compatibility, build_system_patterns)
|
|
{
|
|
// Test patterns commonly used in build systems and deployment scenarios
|
|
|
|
struct TestCase
|
|
{
|
|
std::string text;
|
|
std::string pattern;
|
|
std::string replacement;
|
|
std::string expected;
|
|
};
|
|
|
|
std::vector<TestCase> cases = {
|
|
// Environment variable patterns (common in build systems)
|
|
{"/path/%env{HOME}%/file", "%(env|ENV)\\{([A-Z_]+)\\}%", "${$2}", "/path/${HOME}/file"},
|
|
|
|
// Version patterns (common in package management)
|
|
{"package-1.2.3", "(\\w+)-(\\d+)\\.(\\d+)\\.(\\d+)", "$1_v$2_$3_$4", "package_v1_2_3"},
|
|
|
|
// Path patterns (common in file systems)
|
|
{"/usr/lib64/libfoo.so.1",
|
|
"/usr/lib(\\d*)/([^/]+)\\.so\\.(\\d+)",
|
|
"lib$1/$2.so.$3",
|
|
"lib64/libfoo.so.1"},
|
|
|
|
// Architecture patterns (common in cross-compilation)
|
|
{"x86_64-linux-gnu-gcc",
|
|
"(\\w+)-(\\w+)-(\\w+)-(\\w+)",
|
|
"$1/$2/$3/$4",
|
|
"x86_64/linux/gnu/gcc"}};
|
|
|
|
for(const auto& test_case : cases)
|
|
{
|
|
std::string result =
|
|
R::regex_replace(test_case.text, test_case.pattern, test_case.replacement);
|
|
EXPECT_EQ(result, test_case.expected)
|
|
<< "Failed for text: " << test_case.text << " pattern: " << test_case.pattern
|
|
<< " replacement: " << test_case.replacement;
|
|
}
|
|
}
|
|
|
|
TEST(regex_compatibility, memory_safety)
|
|
{
|
|
// Test that our implementation doesn't have memory issues that could be
|
|
|
|
std::vector<std::string> large_texts;
|
|
const std::string base_text = "This is a test string with numbers 123 and more text ";
|
|
|
|
// Create larger strings to test memory handling
|
|
for(int i = 0; i < 10; ++i)
|
|
{
|
|
std::string large_text;
|
|
for(int j = 0; j < 100; ++j)
|
|
{
|
|
large_text += base_text + std::to_string(j) + " ";
|
|
}
|
|
large_texts.push_back(large_text);
|
|
}
|
|
|
|
const std::string pattern = "\\d+";
|
|
|
|
for(const auto& text : large_texts)
|
|
{
|
|
// These operations should not cause memory corruption or leaks
|
|
size_t matches = 0;
|
|
size_t pos = 0;
|
|
|
|
while(pos < text.length())
|
|
{
|
|
size_t begin, end;
|
|
if(R::regex_search(text.substr(pos), pattern, begin, end))
|
|
{
|
|
matches++;
|
|
pos += begin + (end - begin);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
EXPECT_GT(matches, 0) << "Should find numbers in the large text";
|
|
EXPECT_LT(matches, 2000) << "Shouldn't find unreasonable number of matches";
|
|
}
|
|
}
|
|
|
|
TEST(regex_compatibility, thread_safety)
|
|
{
|
|
// Test that our implementation is thread-safe and doesn't have
|
|
|
|
const std::string text = "concurrent test 123 with multiple threads 456";
|
|
const std::string pattern = "\\d+";
|
|
std::atomic<int> success_count{0};
|
|
std::atomic<int> failure_count{0};
|
|
|
|
auto worker = [&]() {
|
|
for(int i = 0; i < 100; ++i)
|
|
{
|
|
try
|
|
{
|
|
size_t begin, end;
|
|
if(R::regex_search(text, pattern, begin, end))
|
|
{
|
|
success_count++;
|
|
}
|
|
else
|
|
{
|
|
failure_count++;
|
|
}
|
|
} catch(...)
|
|
{
|
|
failure_count++;
|
|
}
|
|
}
|
|
};
|
|
|
|
auto threads = std::vector<std::thread>{};
|
|
threads.reserve(4);
|
|
|
|
for(int i = 0; i < 4; ++i)
|
|
{
|
|
threads.emplace_back(worker);
|
|
}
|
|
|
|
for(auto& thread : threads)
|
|
{
|
|
thread.join();
|
|
}
|
|
|
|
EXPECT_EQ(success_count.load(), 400) << "All regex operations should succeed";
|
|
EXPECT_EQ(failure_count.load(), 0) << "No regex operations should fail";
|
|
}
|