blob: 3369466c72b7dcf13f0f602cb52ad1aecb2a43b8 [file] [edit]
/*
* Copyright (C) 2026 The Android Open Source Project
*
* Licensed 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 "perfetto/ext/base/regex.h"
#include <optional>
#include <string_view>
#include <vector>
#include "src/base/regex/regex_std.h"
#if PERFETTO_BUILDFLAG(PERFETTO_RE2)
#include "src/base/regex/regex_re2.h"
#endif
#if PERFETTO_BUILDFLAG(PERFETTO_PCRE2)
#include "src/base/regex/regex_pcre2.h"
#endif
#include "test/gtest_and_gmock.h"
namespace perfetto {
namespace base {
namespace {
template <typename T>
struct BackendTraits;
template <>
struct BackendTraits<RegexStd> {
static const char* name() { return "RegexStd"; }
};
#if PERFETTO_BUILDFLAG(PERFETTO_RE2)
template <>
struct BackendTraits<RegexRe2> {
static const char* name() { return "RegexRe2"; }
};
#endif
#if PERFETTO_BUILDFLAG(PERFETTO_PCRE2)
template <>
struct BackendTraits<RegexPcre2> {
static const char* name() { return "RegexPcre2"; }
};
#endif
template <typename Backend>
class RegexBackendTest : public ::testing::Test {
protected:
StatusOr<Backend> Create(std::string_view pattern,
bool case_insensitive = false) {
return Backend::Create(pattern, case_insensitive);
}
};
using BackendTypes = ::testing::Types<RegexStd
#if PERFETTO_BUILDFLAG(PERFETTO_RE2)
,
RegexRe2
#endif
#if PERFETTO_BUILDFLAG(PERFETTO_PCRE2)
,
RegexPcre2
#endif
>;
TYPED_TEST_SUITE(RegexBackendTest, BackendTypes, /* trailing ',' for GCC*/);
TYPED_TEST(RegexBackendTest, PartialMatch) {
auto re_or = this->Create("abc");
ASSERT_TRUE(re_or.ok());
auto& re = re_or.value();
EXPECT_TRUE(re.PartialMatch("abc"));
EXPECT_TRUE(re.PartialMatch("xabcy"));
EXPECT_FALSE(re.PartialMatch("abd"));
}
TYPED_TEST(RegexBackendTest, FullMatch) {
auto re_or = this->Create("abc");
ASSERT_TRUE(re_or.ok());
auto& re = re_or.value();
EXPECT_TRUE(re.FullMatch("abc"));
EXPECT_FALSE(re.FullMatch("xabcy"));
EXPECT_FALSE(re.FullMatch("abcd"));
}
TYPED_TEST(RegexBackendTest, PartialMatchWithGroups) {
auto re_or = this->Create("([a-z]+)-([0-9]+)");
ASSERT_TRUE(re_or.ok());
auto& re = re_or.value();
std::vector<std::string_view> matches;
EXPECT_TRUE(re.PartialMatchWithGroups("foo-123", matches));
ASSERT_EQ(matches.size(), 3u);
EXPECT_EQ(matches[0], "foo-123");
EXPECT_EQ(matches[1], "foo");
EXPECT_EQ(matches[2], "123");
}
TYPED_TEST(RegexBackendTest, PartialMatchWithGroupsNoMatch) {
auto re_or = this->Create("([a-z]+)-([0-9]+)");
ASSERT_TRUE(re_or.ok());
std::vector<std::string_view> matches;
EXPECT_FALSE(re_or->PartialMatchWithGroups("foo-bar", matches));
EXPECT_TRUE(matches.empty());
}
TYPED_TEST(RegexBackendTest, PartialMatchWithGroupsOptionalGroup) {
auto re_or = this->Create("a(b)?c");
ASSERT_TRUE(re_or.ok());
std::vector<std::string_view> matches;
EXPECT_TRUE(re_or->PartialMatchWithGroups("ac", matches));
ASSERT_EQ(matches.size(), 2u);
EXPECT_EQ(matches[0], "ac");
EXPECT_TRUE(matches[1].empty());
}
TYPED_TEST(RegexBackendTest, FullMatchWithGroups) {
auto re_or = this->Create("([a-z]+)-([0-9]+)");
ASSERT_TRUE(re_or.ok());
std::vector<std::string_view> matches;
EXPECT_TRUE(re_or->FullMatchWithGroups("foo-123", matches));
ASSERT_EQ(matches.size(), 3u);
EXPECT_EQ(matches[0], "foo-123");
EXPECT_EQ(matches[1], "foo");
EXPECT_EQ(matches[2], "123");
EXPECT_FALSE(re_or->FullMatchWithGroups("xfoo-123x", matches));
EXPECT_TRUE(matches.empty());
}
TYPED_TEST(RegexBackendTest, GlobalReplace) {
auto re_or = this->Create("a");
ASSERT_TRUE(re_or.ok());
EXPECT_EQ(re_or->GlobalReplace("aba", "x"), "xbx");
EXPECT_EQ(re_or->GlobalReplace("bbb", "x"), "bbb");
EXPECT_EQ(re_or->GlobalReplace("aba", ""), "b");
}
TYPED_TEST(RegexBackendTest, PerlShorthands) {
{
auto re_or = this->Create(R"(\d+)");
ASSERT_TRUE(re_or.ok());
EXPECT_TRUE(re_or->PartialMatch("abc123"));
EXPECT_FALSE(re_or->PartialMatch("abcxyz"));
EXPECT_TRUE(re_or->FullMatch("42"));
}
{
auto re_or = this->Create(R"(\w+)");
ASSERT_TRUE(re_or.ok());
EXPECT_TRUE(re_or->FullMatch("hello_world123"));
EXPECT_FALSE(re_or->FullMatch("hello world"));
}
{
auto re_or = this->Create(R"(\s+)");
ASSERT_TRUE(re_or.ok());
EXPECT_TRUE(re_or->PartialMatch("hello world"));
EXPECT_FALSE(re_or->PartialMatch("helloworld"));
}
}
TYPED_TEST(RegexBackendTest, CaseInsensitive) {
auto re_or = this->Create("abc", /*case_insensitive=*/true);
ASSERT_TRUE(re_or.ok());
EXPECT_TRUE(re_or->PartialMatch("ABC"));
EXPECT_TRUE(re_or->PartialMatch("aBc"));
EXPECT_TRUE(re_or->FullMatch("ABC"));
EXPECT_FALSE(re_or->PartialMatch("abd"));
}
TYPED_TEST(RegexBackendTest, CaretMatchesOnlyStringStart) {
auto re_or = this->Create("^abc");
ASSERT_TRUE(re_or.ok());
EXPECT_TRUE(re_or->PartialMatch("abc"));
EXPECT_TRUE(re_or->PartialMatch("abcdef"));
// ^ should NOT match after a newline (no MULTILINE).
EXPECT_FALSE(re_or->PartialMatch("xyz\nabc"));
}
TYPED_TEST(RegexBackendTest, SearchWithOffset) {
auto re_or = this->Create("([0-9]+)");
ASSERT_TRUE(re_or.ok());
std::vector<std::string_view> matches;
std::string_view input = "abc123def456";
// From offset 0 — should find "123".
EXPECT_TRUE(re_or->SearchWithOffset(input, 0, matches));
ASSERT_GE(matches.size(), 1u);
EXPECT_EQ(matches[0], "123");
// From offset 6 (past "123") — should find "456".
EXPECT_TRUE(re_or->SearchWithOffset(input, 6, matches));
ASSERT_GE(matches.size(), 1u);
EXPECT_EQ(matches[0], "456");
// From offset 12 (end) — no match.
EXPECT_FALSE(re_or->SearchWithOffset(input, 12, matches));
}
// ============================================================================
// Tests for the Regex wrapper class (uses the active backend via RegexImpl).
// ============================================================================
TEST(RegexTest, PartialMatch) {
auto re = Regex::CreateOrCheck("abc");
EXPECT_TRUE(re.PartialMatch("abc"));
EXPECT_TRUE(re.PartialMatch("xabcy"));
EXPECT_FALSE(re.PartialMatch("abd"));
}
TEST(RegexTest, FullMatch) {
auto re = Regex::CreateOrCheck("abc");
EXPECT_TRUE(re.FullMatch("abc"));
EXPECT_FALSE(re.FullMatch("xabcy"));
}
TEST(RegexTest, Move) {
Regex re1 = Regex::CreateOrCheck("abc");
EXPECT_TRUE(re1.PartialMatch("abc"));
Regex re2 = std::move(re1);
EXPECT_TRUE(re2.PartialMatch("abc"));
}
TEST(RegexTest, MoveAssignment) {
Regex re1 = Regex::CreateOrCheck("abc");
Regex re2 = Regex::CreateOrCheck("xyz");
re2 = std::move(re1);
EXPECT_TRUE(re2.PartialMatch("abc"));
EXPECT_FALSE(re2.PartialMatch("xyz"));
}
TEST(RegexTest, EmptyString) {
auto re = Regex::CreateOrCheck("abc");
EXPECT_FALSE(re.PartialMatch(""));
EXPECT_FALSE(re.FullMatch(""));
std::vector<std::string_view> matches;
EXPECT_FALSE(re.PartialMatchWithGroups("", matches));
}
TEST(RegexTest, Alternation) {
auto re = Regex::CreateOrCheck("cat|dog");
EXPECT_TRUE(re.PartialMatch("I have a cat"));
EXPECT_TRUE(re.PartialMatch("I have a dog"));
EXPECT_FALSE(re.PartialMatch("I have a bird"));
}
TEST(RegexTest, PartialMatchAll) {
auto re = Regex::CreateOrCheck("([0-9]+)");
auto it = re.PartialMatchAll("foo123bar456baz789");
std::vector<std::string_view> groups;
auto m1 = it.NextWithGroups(groups);
ASSERT_TRUE(m1.has_value());
EXPECT_EQ(*m1, "123");
ASSERT_EQ(groups.size(), 2u);
EXPECT_EQ(groups[1], "123");
auto m2 = it.Next();
ASSERT_TRUE(m2.has_value());
EXPECT_EQ(*m2, "456");
auto m3 = it.Next();
ASSERT_TRUE(m3.has_value());
EXPECT_EQ(*m3, "789");
EXPECT_FALSE(it.Next().has_value());
}
TEST(RegexTest, PartialMatchAllNoMatch) {
auto re = Regex::CreateOrCheck("[0-9]+");
auto it = re.PartialMatchAll("hello");
EXPECT_FALSE(it.Next().has_value());
}
// --- CopyableRegex tests ---
TEST(CopyableRegexTest, CopyConstruct) {
CopyableRegex re1(Regex::CreateOrCheck("abc"));
CopyableRegex re2(re1);
EXPECT_TRUE(re2.PartialMatch("abc"));
EXPECT_FALSE(re2.PartialMatch("xyz"));
EXPECT_TRUE(re1.PartialMatch("abc"));
}
TEST(CopyableRegexTest, CopyAssignment) {
CopyableRegex re1(Regex::CreateOrCheck("abc"));
CopyableRegex re2(Regex::CreateOrCheck("xyz"));
re2 = re1;
EXPECT_TRUE(re2.PartialMatch("abc"));
EXPECT_FALSE(re2.PartialMatch("xyz"));
EXPECT_TRUE(re1.PartialMatch("abc"));
}
TEST(CopyableRegexTest, CaseInsensitiveCopy) {
CopyableRegex re1(
Regex::CreateOrCheck("abc", Regex::CaseSensitivity::kInsensitive));
CopyableRegex re2(re1);
EXPECT_TRUE(re2.PartialMatch("ABC"));
EXPECT_FALSE(re2.PartialMatch("abd"));
}
TEST(CopyableRegexTest, MoveConstruct) {
CopyableRegex re1(Regex::CreateOrCheck("abc"));
CopyableRegex re2(std::move(re1));
EXPECT_TRUE(re2.PartialMatch("abc"));
}
TEST(CopyableRegexTest, FullMatchWithGroups) {
CopyableRegex re(Regex::CreateOrCheck("([a-z]+)-([0-9]+)"));
std::vector<std::string_view> matches;
EXPECT_TRUE(re.FullMatchWithGroups("foo-123", matches));
ASSERT_EQ(matches.size(), 3u);
EXPECT_EQ(matches[1], "foo");
EXPECT_EQ(matches[2], "123");
}
} // namespace
} // namespace base
} // namespace perfetto