Files
llvm-project/lldb/unittests/Expression/ValueMatcher.cpp
David Peixotto 89c08ad2e2 [lldb] Add a gtest matcher for lldb_private::Value (#167427)
This commit adds a new `ValueMatcher` class that can be used in gtest
matching contexts to match against `lldb_private::Value` objects. We
always match against the values `value_type` and `context_type`. For
HostAddress values we will also match against the expected host buffer
contents. For Scalar, FileAddress, and LoadAddress values we match
against an expected Scalar value.

The matcher is used to improve the quality of the tests in the
`DwarfExpressionTest.cpp` file. Previously, the local `Evaluate`
function would return an `Expected<Scalar>` value which makes it hard to
verify that we actually get a Value of the expected type without adding
custom evaluation code. Now we return an `Expected<Value>` so that we
can match against the full value contents.

The resulting change improves the quality of the existing checks and in
some cases eliminates the need for special code to explicitly check
value types.

I followed the gtest
[guide](https://google.github.io/googletest/gmock_cook_book.html#writing-new-monomorphic-matchers)
for writing a new value matcher.
2025-11-13 08:42:34 -08:00

206 lines
7.3 KiB
C++

//===----------------------------------------------------------------------===//
//
// 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 "ValueMatcher.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/InterleavedRange.h"
#include "llvm/Support/raw_os_ostream.h"
#include "llvm/Support/raw_ostream.h"
using namespace lldb_private;
static void FormatValueDetails(llvm::raw_ostream &os,
Value::ValueType value_type,
Value::ContextType context_type,
const Scalar &scalar,
llvm::ArrayRef<uint8_t> buffer_data) {
os << "Value(";
os << "value_type=" << Value::GetValueTypeAsCString(value_type);
os << ", context_type=" << Value::GetContextTypeAsCString(context_type);
if (value_type == Value::ValueType::HostAddress) {
auto bytes_to_print = buffer_data.take_front(16);
os << ", buffer=[";
llvm::interleave(
bytes_to_print,
[&](uint8_t byte) {
os << llvm::format("%02x", static_cast<unsigned>(byte));
},
[&]() { os << " "; });
if (buffer_data.size() > 16)
os << " ...";
os << "] (" << buffer_data.size() << " bytes)";
} else {
os << ", value=" << scalar;
}
os << ")";
}
void lldb_private::PrintTo(const Value &val, std::ostream *os) {
if (!os)
return;
llvm::raw_os_ostream raw_os(*os);
FormatValueDetails(raw_os, val.GetValueType(), val.GetContextType(),
val.GetScalar(), val.GetBuffer().GetData());
}
bool ValueMatcher::MatchAndExplain(const Value &val,
std::ostream *stream) const {
if (stream) {
llvm::raw_os_ostream os(*stream);
return MatchAndExplainImpl(val, os);
}
llvm::raw_null_ostream os;
return MatchAndExplainImpl(val, os);
}
// Match the provided value and explain any mismatches using
// the raw_ostream. We use the llvm::raw_ostream here to simplify the formatting
// of Scalar values which already know how to print themselves to that stream.
bool ValueMatcher::MatchAndExplainImpl(const Value &val,
llvm::raw_ostream &os) const {
if (val.GetValueType() != m_value_type) {
os << "value_type mismatch: expected "
<< Value::GetValueTypeAsCString(m_value_type) << ", got "
<< Value::GetValueTypeAsCString(val.GetValueType()) << " ";
return false;
}
if (val.GetContextType() != m_context_type) {
os << "context_type mismatch: expected "
<< Value::GetContextTypeAsCString(m_context_type) << ", got "
<< Value::GetContextTypeAsCString(val.GetContextType()) << " ";
return false;
}
if (m_value_type == Value::ValueType::HostAddress) {
const DataBufferHeap &buffer = val.GetBuffer();
const size_t buffer_size = buffer.GetByteSize();
if (buffer_size != m_expected_bytes.size()) {
os << "buffer size mismatch: expected " << m_expected_bytes.size()
<< ", got " << buffer_size << " ";
return false;
}
const uint8_t *data = buffer.GetBytes();
for (size_t i = 0; i < buffer_size; ++i) {
if (data[i] != m_expected_bytes[i]) {
os << "byte mismatch at index " << i << ": expected "
<< llvm::format("0x%02x", static_cast<unsigned>(m_expected_bytes[i]))
<< ", got " << llvm::format("0x%02x", static_cast<unsigned>(data[i]))
<< " ";
return false;
}
}
} else {
// For Scalar, FileAddress, and LoadAddress compare m_value.
const Scalar &actual_scalar = val.GetScalar();
if (actual_scalar != m_expected_scalar) {
os << "scalar value mismatch: expected " << m_expected_scalar << ", got "
<< actual_scalar;
return false;
}
}
return true;
}
void ValueMatcher::DescribeTo(std::ostream *os) const {
if (!os)
return;
llvm::raw_os_ostream raw_os(*os);
FormatValueDetails(raw_os, m_value_type, m_context_type, m_expected_scalar,
m_expected_bytes);
}
void ValueMatcher::DescribeNegationTo(std::ostream *os) const {
if (!os)
return;
*os << "value does not match";
}
testing::Matcher<Value>
lldb_private::MatchScalarValue(Value::ValueType value_type,
const Scalar &expected_scalar,
Value::ContextType context_type) {
return ValueMatcher(value_type, expected_scalar, context_type);
}
testing::Matcher<Value>
lldb_private::MatchHostValue(Value::ValueType value_type,
const std::vector<uint8_t> &expected_bytes,
Value::ContextType context_type) {
return ValueMatcher(value_type, expected_bytes, context_type);
}
testing::Matcher<Value>
lldb_private::IsScalar(const Scalar &expected_scalar,
Value::ContextType context_type) {
return MatchScalarValue(Value::ValueType::Scalar, expected_scalar,
context_type);
}
testing::Matcher<Value>
lldb_private::IsLoadAddress(const Scalar &expected_address,
Value::ContextType context_type) {
return MatchScalarValue(Value::ValueType::LoadAddress, expected_address,
context_type);
}
testing::Matcher<Value>
lldb_private::IsFileAddress(const Scalar &expected_address,
Value::ContextType context_type) {
return MatchScalarValue(Value::ValueType::FileAddress, expected_address,
context_type);
}
testing::Matcher<Value>
lldb_private::IsHostValue(const std::vector<uint8_t> &expected_bytes,
Value::ContextType context_type) {
return MatchHostValue(Value::ValueType::HostAddress, expected_bytes,
context_type);
}
Scalar lldb_private::GetScalar(unsigned bits, uint64_t value, bool sign) {
Scalar scalar(value);
scalar.TruncOrExtendTo(bits, sign);
return scalar;
}
llvm::detail::ValueMatchesPoly<testing::Matcher<Value>>
lldb_private::ExpectScalar(const Scalar &expected_scalar,
Value::ContextType context_type) {
return llvm::HasValue(IsScalar(expected_scalar, context_type));
}
llvm::detail::ValueMatchesPoly<testing::Matcher<Value>>
lldb_private::ExpectScalar(unsigned bits, uint64_t value, bool sign,
Value::ContextType context_type) {
return ExpectScalar(GetScalar(bits, value, sign), context_type);
}
llvm::detail::ValueMatchesPoly<testing::Matcher<Value>>
lldb_private::ExpectLoadAddress(const Scalar &expected_address,
Value::ContextType context_type) {
return llvm::HasValue(IsLoadAddress(expected_address, context_type));
}
llvm::detail::ValueMatchesPoly<testing::Matcher<Value>>
lldb_private::ExpectFileAddress(const Scalar &expected_address,
Value::ContextType context_type) {
return llvm::HasValue(IsFileAddress(expected_address, context_type));
}
llvm::detail::ValueMatchesPoly<testing::Matcher<Value>>
lldb_private::ExpectHostAddress(const std::vector<uint8_t> &expected_bytes,
Value::ContextType context_type) {
return llvm::HasValue(IsHostValue(expected_bytes, context_type));
}