Files
llvm-project/lldb/unittests/Core/FormatEntityTest.cpp
Michael Buch 8f1cd07e3f [lldb][Format] Introduce a FormatEntity::Formatter class and move the Format API into it (#174618)
This patch creates a new `FormatEntity::Formatter` class and moves
`FormatEntity::Format` (and related APIs) into it. Most of the
parameters to `Format` are immutable across all recursive calls, so I
made them `const` member variables of `Formatter`. The main changes are
just mechanical renaming of:
```
FormatEntity::Format(...)
```
to
```
FormatEntity::Formatter(...).Format(stream, entry, valobj)
```
and making use of the member variables from inside `Format`.

We can probably make most of the parameters to the `Formatter`
constructor defaulted, but I chose not to in this patch to keep the diff
smaller.

The motivation for this is that I'm planning on adding logic to detect
recursive format entities (which would crash LLDB). That requires some
state, which in my opinion is best kept inside the `Formatter` class
instead of another parameter to `Format`.

The patch should be entirely NFC.
2026-01-07 15:40:55 +00:00

207 lines
6.4 KiB
C++

//===-- FormatEntityTest.cpp ---------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "lldb/Core/FormatEntity.h"
#include "lldb/Utility/Status.h"
#include "lldb/Utility/StreamString.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"
using namespace lldb_private;
using namespace llvm;
using Definition = FormatEntity::Entry::Definition;
using Entry = FormatEntity::Entry;
static Expected<std::string> Format(StringRef format_str) {
StreamString stream;
FormatEntity::Entry format;
Status status = FormatEntity::Parse(format_str, format);
if (status.Fail())
return status.ToError();
FormatEntity::Formatter(nullptr, nullptr, nullptr, false, false)
.Format(format, stream);
return stream.GetString().str();
}
TEST(FormatEntityTest, DefinitionConstructionNameAndType) {
Definition d("foo", FormatEntity::Entry::Type::Invalid);
EXPECT_STREQ(d.name, "foo");
EXPECT_EQ(d.string, nullptr);
EXPECT_EQ(d.type, FormatEntity::Entry::Type::Invalid);
EXPECT_EQ(d.data, 0UL);
EXPECT_EQ(d.num_children, 0UL);
EXPECT_EQ(d.children, nullptr);
EXPECT_FALSE(d.keep_separator);
}
TEST(FormatEntityTest, DefinitionConstructionNameAndString) {
Definition d("foo", "string");
EXPECT_STREQ(d.name, "foo");
EXPECT_STREQ(d.string, "string");
EXPECT_EQ(d.type, FormatEntity::Entry::Type::EscapeCode);
EXPECT_EQ(d.data, 0UL);
EXPECT_EQ(d.num_children, 0UL);
EXPECT_EQ(d.children, nullptr);
EXPECT_FALSE(d.keep_separator);
}
TEST(FormatEntityTest, DefinitionConstructionNameTypeData) {
Definition d("foo", FormatEntity::Entry::Type::Invalid, 33);
EXPECT_STREQ(d.name, "foo");
EXPECT_EQ(d.string, nullptr);
EXPECT_EQ(d.type, FormatEntity::Entry::Type::Invalid);
EXPECT_EQ(d.data, 33UL);
EXPECT_EQ(d.num_children, 0UL);
EXPECT_EQ(d.children, nullptr);
EXPECT_FALSE(d.keep_separator);
}
TEST(FormatEntityTest, DefinitionConstructionNameTypeChildren) {
Definition d("foo", FormatEntity::Entry::Type::Invalid, 33);
Definition parent("parent", FormatEntity::Entry::Type::Invalid, 1, &d);
EXPECT_STREQ(parent.name, "parent");
EXPECT_STREQ(parent.string, nullptr);
EXPECT_EQ(parent.type, FormatEntity::Entry::Type::Invalid);
EXPECT_EQ(parent.num_children, 1UL);
EXPECT_EQ(parent.children, &d);
EXPECT_FALSE(parent.keep_separator);
EXPECT_STREQ(parent.children[0].name, "foo");
EXPECT_EQ(parent.children[0].string, nullptr);
EXPECT_EQ(parent.children[0].type, FormatEntity::Entry::Type::Invalid);
EXPECT_EQ(parent.children[0].data, 33UL);
EXPECT_EQ(parent.children[0].num_children, 0UL);
EXPECT_EQ(parent.children[0].children, nullptr);
EXPECT_FALSE(d.keep_separator);
}
constexpr llvm::StringRef lookupStrings[] = {
"${addr.load}",
"${addr.file}",
"${ansi.fg.black}",
"${ansi.fg.red}",
"${ansi.fg.green}",
"${ansi.fg.yellow}",
"${ansi.fg.blue}",
"${ansi.fg.purple}",
"${ansi.fg.cyan}",
"${ansi.fg.white}",
"${ansi.bg.black}",
"${ansi.bg.red}",
"${ansi.bg.green}",
"${ansi.bg.yellow}",
"${ansi.bg.blue}",
"${ansi.bg.purple}",
"${ansi.bg.cyan}",
"${ansi.bg.white}",
"${file.basename}",
"${file.dirname}",
"${file.fullpath}",
"${frame.index}",
"${frame.pc}",
"${frame.fp}",
"${frame.sp}",
"${frame.flags}",
"${frame.no-debug}",
"${frame.reg.*}",
"${frame.is-artificial}",
"${frame.kind}",
"${function.id}",
"${function.name}",
"${function.name-without-args}",
"${function.name-with-args}",
"${function.mangled-name}",
"${function.addr-offset}",
"${function.concrete-only-addr-offset-no-padding}",
"${function.line-offset}",
"${function.pc-offset}",
"${function.initial-function}",
"${function.changed}",
"${function.is-optimized}",
"${function.is-inlined}",
"${line.file.basename}",
"${line.file.dirname}",
"${line.file.fullpath}",
"${line.number}",
"${line.column}",
"${line.start-addr}",
"${line.end-addr}",
"${module.file.basename}",
"${module.file.dirname}",
"${module.file.fullpath}",
"${process.id}",
"${process.name}",
"${process.file.basename}",
"${process.file.dirname}",
"${process.file.fullpath}",
"${script.frame}",
"${script.process}",
"${script.target}",
"${script.thread}",
"${script.var}",
"${script.svar}",
"${script.thread}",
"${svar.dummy-svar-to-test-wildcard}",
"${thread.id}",
"${thread.protocol_id}",
"${thread.index}",
"${thread.info.*}",
"${thread.queue}",
"${thread.name}",
"${thread.stop-reason}",
"${thread.stop-reason-raw}",
"${thread.return-value}",
"${thread.completed-expression}",
"${target.arch}",
"${target.file.basename}",
"${target.file.dirname}",
"${target.file.fullpath}",
"${var.dummy-var-to-test-wildcard}"};
TEST(FormatEntityTest, LookupAllEntriesInTree) {
for (const llvm::StringRef testString : lookupStrings) {
Entry e;
EXPECT_TRUE(FormatEntity::Parse(testString, e).Success())
<< "Formatting " << testString << " did not succeed";
}
}
TEST(FormatEntityTest, Scope) {
// Scope with one alternative.
EXPECT_THAT_EXPECTED(Format("{${frame.pc}|foo}"), HasValue("foo"));
// Scope with multiple alternatives.
EXPECT_THAT_EXPECTED(Format("{${frame.pc}|${function.name}|foo}"),
HasValue("foo"));
// Escaped pipe inside a scope.
EXPECT_THAT_EXPECTED(Format("{foo\\|bar}"), HasValue("foo|bar"));
// Unescaped pipe outside a scope.
EXPECT_THAT_EXPECTED(Format("foo|bar"), HasValue("foo|bar"));
// Nested scopes. Note that scopes always resolve.
EXPECT_THAT_EXPECTED(Format("{{${frame.pc}|foo}|{bar}}"), HasValue("foo"));
EXPECT_THAT_EXPECTED(Format("{{${frame.pc}}|{bar}}"), HasValue(""));
// Pipe between scopes.
EXPECT_THAT_EXPECTED(Format("{foo}|{bar}"), HasValue("foo|bar"));
EXPECT_THAT_EXPECTED(Format("{foo}||{bar}"), HasValue("foo||bar"));
// Empty space between pipes.
EXPECT_THAT_EXPECTED(Format("{{foo}||{bar}}"), HasValue("foo"));
EXPECT_THAT_EXPECTED(Format("{${frame.pc}||{bar}}"), HasValue(""));
}