From 8800439d2c138ea4723eb350902da39511fc74d4 Mon Sep 17 00:00:00 2001 From: David Peixotto Date: Fri, 7 Nov 2025 16:43:21 -0800 Subject: [PATCH 01/13] [lldb] Add a gtest matcher for lldb_private::Value 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` value which makes it hard to verify that we actually get a Value of the expected type. Now we return an `Expected` 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. --- .../Expression/DWARFExpressionTest.cpp | 202 ++++--------- lldb/unittests/Expression/ValueMatcher.h | 283 ++++++++++++++++++ 2 files changed, 343 insertions(+), 142 deletions(-) create mode 100644 lldb/unittests/Expression/ValueMatcher.h diff --git a/lldb/unittests/Expression/DWARFExpressionTest.cpp b/lldb/unittests/Expression/DWARFExpressionTest.cpp index 9d11060becfae..8c5568d9e4e65 100644 --- a/lldb/unittests/Expression/DWARFExpressionTest.cpp +++ b/lldb/unittests/Expression/DWARFExpressionTest.cpp @@ -5,8 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// - #include "lldb/Expression/DWARFExpression.h" +#include "ValueMatcher.h" #ifdef ARCH_AARCH64 #include "Plugins/ABI/AArch64/ABISysV_arm64.h" #endif @@ -135,40 +135,18 @@ class MockRegisterContext : public RegisterContext { }; } // namespace -static llvm::Expected Evaluate(llvm::ArrayRef expr, - lldb::ModuleSP module_sp = {}, - DWARFUnit *unit = nullptr, - ExecutionContext *exe_ctx = nullptr, - RegisterContext *reg_ctx = nullptr) { +static llvm::Expected Evaluate(llvm::ArrayRef expr, + lldb::ModuleSP module_sp = {}, + DWARFUnit *unit = nullptr, + ExecutionContext *exe_ctx = nullptr, + RegisterContext *reg_ctx = nullptr) { DataExtractor extractor(expr.data(), expr.size(), lldb::eByteOrderLittle, /*addr_size*/ 4); - llvm::Expected result = DWARFExpression::Evaluate( - exe_ctx, reg_ctx, module_sp, extractor, unit, lldb::eRegisterKindLLDB, - /*initial_value_ptr=*/nullptr, - /*object_address_ptr=*/nullptr); - if (!result) - return result.takeError(); - - switch (result->GetValueType()) { - case Value::ValueType::Scalar: - return result->GetScalar(); - case Value::ValueType::LoadAddress: - return LLDB_INVALID_ADDRESS; - case Value::ValueType::HostAddress: { - // Convert small buffers to scalars to simplify the tests. - DataBufferHeap &buf = result->GetBuffer(); - if (buf.GetByteSize() <= 8) { - uint64_t val = 0; - memcpy(&val, buf.GetBytes(), buf.GetByteSize()); - return Scalar(llvm::APInt(buf.GetByteSize() * 8, val, false)); - } - } - [[fallthrough]]; - default: - break; - } - return llvm::createStringError("unsupported value type"); + return DWARFExpression::Evaluate(exe_ctx, reg_ctx, module_sp, extractor, unit, + lldb::eRegisterKindLLDB, + /*initial_value_ptr=*/nullptr, + /*object_address_ptr=*/nullptr); } class DWARFExpressionTester : public YAMLModuleTester { @@ -177,18 +155,11 @@ class DWARFExpressionTester : public YAMLModuleTester { : YAMLModuleTester(yaml_data, cu_index) {} using YAMLModuleTester::YAMLModuleTester; - llvm::Expected Eval(llvm::ArrayRef expr) { + llvm::Expected Eval(llvm::ArrayRef expr) { return ::Evaluate(expr, m_module_sp, m_dwarf_unit); } }; -/// Unfortunately Scalar's operator==() is really picky. -static Scalar GetScalar(unsigned bits, uint64_t value, bool sign) { - Scalar scalar(value); - scalar.TruncOrExtendTo(bits, sign); - return scalar; -} - /// This is needed for the tests that use a mock process. class DWARFExpressionMockProcessTest : public ::testing::Test { public: @@ -255,48 +226,48 @@ class MockTarget : public Target { TEST(DWARFExpression, DW_OP_pick) { EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1, DW_OP_lit0, DW_OP_pick, 0}), - llvm::HasValue(0)); + ExpectScalar(0)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1, DW_OP_lit0, DW_OP_pick, 1}), - llvm::HasValue(1)); + ExpectScalar(1)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1, DW_OP_lit0, DW_OP_pick, 2}), llvm::Failed()); } TEST(DWARFExpression, DW_OP_const) { // Extend to address size. - EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const1u, 0x88}), llvm::HasValue(0x88)); + EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const1u, 0x88}), ExpectScalar(0x88)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const1s, 0x88}), - llvm::HasValue(0xffffff88)); + ExpectScalar(0xffffff88)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const2u, 0x47, 0x88}), - llvm::HasValue(0x8847)); + ExpectScalar(0x8847)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const2s, 0x47, 0x88}), - llvm::HasValue(0xffff8847)); + ExpectScalar(0xffff8847)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const4u, 0x44, 0x42, 0x47, 0x88}), - llvm::HasValue(0x88474244)); + ExpectScalar(0x88474244)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const4s, 0x44, 0x42, 0x47, 0x88}), - llvm::HasValue(0x88474244)); + ExpectScalar(0x88474244)); // Truncate to address size. EXPECT_THAT_EXPECTED( Evaluate({DW_OP_const8u, 0x00, 0x11, 0x22, 0x33, 0x44, 0x42, 0x47, 0x88}), - llvm::HasValue(0x33221100)); + ExpectScalar(0x33221100)); EXPECT_THAT_EXPECTED( Evaluate({DW_OP_const8s, 0x00, 0x11, 0x22, 0x33, 0x44, 0x42, 0x47, 0x88}), - llvm::HasValue(0x33221100)); + ExpectScalar(0x33221100)); // Don't truncate to address size for compatibility with clang (pr48087). EXPECT_THAT_EXPECTED( Evaluate({DW_OP_constu, 0x81, 0x82, 0x84, 0x88, 0x90, 0xa0, 0x40}), - llvm::HasValue(0x01010101010101)); + ExpectScalar(0x01010101010101)); EXPECT_THAT_EXPECTED( Evaluate({DW_OP_consts, 0x81, 0x82, 0x84, 0x88, 0x90, 0xa0, 0x40}), - llvm::HasValue(0xffff010101010101)); + ExpectScalar(0xffff010101010101)); } TEST(DWARFExpression, DW_OP_skip) { EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const1u, 0x42, DW_OP_skip, 0x02, 0x00, DW_OP_const1u, 0xff}), - llvm::HasValue(0x42)); + ExpectScalar(0x42)); } TEST(DWARFExpression, DW_OP_bra) { @@ -309,7 +280,7 @@ TEST(DWARFExpression, DW_OP_bra) { DW_OP_const1u, 0xff, // push 0xff }), // clang-format on - llvm::HasValue(0x42)); + ExpectScalar(0x42)); EXPECT_THAT_ERROR(Evaluate({DW_OP_bra, 0x01, 0x00}).takeError(), llvm::Failed()); @@ -414,42 +385,42 @@ TEST(DWARFExpression, DW_OP_convert) { EXPECT_THAT_EXPECTED( t.Eval({DW_OP_const4u, 0x11, 0x22, 0x33, 0x44, // DW_OP_convert, offs_uint32_t, DW_OP_stack_value}), - llvm::HasValue(GetScalar(64, 0x44332211, not_signed))); + ExpectScalar(64, 0x44332211, not_signed)); // Zero-extend to 64 bits. EXPECT_THAT_EXPECTED( t.Eval({DW_OP_const4u, 0x11, 0x22, 0x33, 0x44, // DW_OP_convert, offs_uint64_t, DW_OP_stack_value}), - llvm::HasValue(GetScalar(64, 0x44332211, not_signed))); + ExpectScalar(64, 0x44332211, not_signed)); // Sign-extend to 64 bits. EXPECT_THAT_EXPECTED( t.Eval({DW_OP_const4s, 0xcc, 0xdd, 0xee, 0xff, // DW_OP_convert, offs_sint64_t, DW_OP_stack_value}), - llvm::HasValue(GetScalar(64, 0xffffffffffeeddcc, is_signed))); + ExpectScalar(64, 0xffffffffffeeddcc, is_signed)); // Sign-extend, then truncate. EXPECT_THAT_EXPECTED( t.Eval({DW_OP_const4s, 0xcc, 0xdd, 0xee, 0xff, // DW_OP_convert, offs_sint64_t, // DW_OP_convert, offs_uint32_t, DW_OP_stack_value}), - llvm::HasValue(GetScalar(32, 0xffeeddcc, not_signed))); + ExpectScalar(32, 0xffeeddcc, not_signed)); // Truncate to default unspecified (pointer-sized) type. EXPECT_THAT_EXPECTED(t.Eval({DW_OP_const4s, 0xcc, 0xdd, 0xee, 0xff, // DW_OP_convert, offs_sint64_t, // DW_OP_convert, 0x00, DW_OP_stack_value}), - llvm::HasValue(GetScalar(32, 0xffeeddcc, not_signed))); + ExpectScalar(32, 0xffeeddcc, not_signed)); // Truncate to 8 bits. EXPECT_THAT_EXPECTED(t.Eval({DW_OP_const4s, 'A', 'B', 'C', 'D', DW_OP_convert, offs_uchar, DW_OP_stack_value}), - llvm::HasValue(GetScalar(8, 'A', not_signed))); + ExpectScalar(8, 'A', not_signed)); // Also truncate to 8 bits. EXPECT_THAT_EXPECTED(t.Eval({DW_OP_const4s, 'A', 'B', 'C', 'D', DW_OP_convert, offs_schar, DW_OP_stack_value}), - llvm::HasValue(GetScalar(8, 'A', is_signed))); + ExpectScalar(8, 'A', is_signed)); // // Errors. @@ -479,33 +450,21 @@ TEST(DWARFExpression, DW_OP_stack_value) { TEST(DWARFExpression, DW_OP_piece) { EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const2u, 0x11, 0x22, DW_OP_piece, 2, DW_OP_const2u, 0x33, 0x44, DW_OP_piece, 2}), - llvm::HasValue(GetScalar(32, 0x44332211, true))); + ExpectHostAddress({0x11, 0x22, 0x33, 0x44})); EXPECT_THAT_EXPECTED( Evaluate({DW_OP_piece, 1, DW_OP_const1u, 0xff, DW_OP_piece, 1}), // Note that the "00" should really be "undef", but we can't // represent that yet. - llvm::HasValue(GetScalar(16, 0xff00, true))); -} - -TEST(DWARFExpression, DW_OP_piece_host_address) { - static const uint8_t expr_data[] = {DW_OP_lit2, DW_OP_stack_value, - DW_OP_piece, 40}; - llvm::ArrayRef expr(expr_data, sizeof(expr_data)); - DataExtractor extractor(expr.data(), expr.size(), lldb::eByteOrderLittle, 4); + ExpectHostAddress({0x00, 0xff})); // This tests if ap_int is extended to the right width. // expect 40*8 = 320 bits size. - llvm::Expected result = - DWARFExpression::Evaluate(nullptr, nullptr, nullptr, extractor, nullptr, - lldb::eRegisterKindDWARF, nullptr, nullptr); - ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); - ASSERT_EQ(result->GetValueType(), Value::ValueType::HostAddress); - ASSERT_EQ(result->GetBuffer().GetByteSize(), 40ul); - const uint8_t *data = result->GetBuffer().GetBytes(); - ASSERT_EQ(data[0], 2); - for (int i = 1; i < 40; i++) { - ASSERT_EQ(data[i], 0); - } + std::vector expected_host_buffer(40, 0); + expected_host_buffer[0] = 2; + + EXPECT_THAT_EXPECTED( + Evaluate({{DW_OP_lit2, DW_OP_stack_value, DW_OP_piece, 40}}), + ExpectHostAddress(expected_host_buffer)); } TEST(DWARFExpression, DW_OP_implicit_value) { @@ -513,7 +472,7 @@ TEST(DWARFExpression, DW_OP_implicit_value) { EXPECT_THAT_EXPECTED( Evaluate({DW_OP_implicit_value, bytes, 0x11, 0x22, 0x33, 0x44}), - llvm::HasValue(GetScalar(8 * bytes, 0x44332211, true))); + ExpectHostAddress({0x11, 0x22, 0x33, 0x44})); } TEST(DWARFExpression, DW_OP_unknown) { @@ -548,20 +507,13 @@ TEST_F(DWARFExpressionMockProcessTest, DW_OP_deref) { // Implicit location: *0x4. EXPECT_THAT_EXPECTED( Evaluate({DW_OP_lit4, DW_OP_deref, DW_OP_stack_value}, {}, {}, &exe_ctx), - llvm::HasValue(GetScalar(32, 0x07060504, false))); + ExpectScalar(32, 0x07060504, false)); // Memory location: *(*0x4). - // Evaluate returns LLDB_INVALID_ADDRESS for all load addresses. EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit4, DW_OP_deref}, {}, {}, &exe_ctx), - llvm::HasValue(Scalar(LLDB_INVALID_ADDRESS))); + ExpectLoadAddress(0x07060504)); // Memory location: *0x4. - // Evaluate returns LLDB_INVALID_ADDRESS for all load addresses. EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit4}, {}, {}, &exe_ctx), - llvm::HasValue(Scalar(4))); - // Implicit location: *0x4. - // Evaluate returns LLDB_INVALID_ADDRESS for all load addresses. - EXPECT_THAT_EXPECTED( - Evaluate({DW_OP_lit4, DW_OP_deref, DW_OP_stack_value}, {}, {}, &exe_ctx), - llvm::HasValue(GetScalar(32, 0x07060504, false))); + ExpectScalar(Scalar(4))); } TEST_F(DWARFExpressionMockProcessTest, WASM_DW_OP_addr) { @@ -581,18 +533,9 @@ TEST_F(DWARFExpressionMockProcessTest, WASM_DW_OP_addr) { ExecutionContext exe_ctx(target_sp, false); // DW_OP_addr takes a single operand of address size width: - uint8_t expr[] = {DW_OP_addr, 0x40, 0x0, 0x0, 0x0}; - DataExtractor extractor(expr, sizeof(expr), lldb::eByteOrderLittle, - /*addr_size*/ 4); - - llvm::Expected result = DWARFExpression::Evaluate( - &exe_ctx, /*reg_ctx*/ nullptr, /*module_sp*/ {}, extractor, - /*unit*/ nullptr, lldb::eRegisterKindLLDB, - /*initial_value_ptr*/ nullptr, - /*object_address_ptr*/ nullptr); - - ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); - ASSERT_EQ(result->GetValueType(), Value::ValueType::LoadAddress); + EXPECT_THAT_EXPECTED( + Evaluate({DW_OP_addr, 0x40, 0x0, 0x0, 0x0}, {}, {}, &exe_ctx), + ExpectLoadAddress(0x40)); } TEST_F(DWARFExpressionMockProcessTest, WASM_DW_OP_addr_index) { @@ -676,15 +619,11 @@ TEST_F(DWARFExpressionMockProcessTest, WASM_DW_OP_addr_index) { DWARFExpression expr(extractor); llvm::Expected result = evaluate(expr); - ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); - ASSERT_EQ(result->GetValueType(), Value::ValueType::LoadAddress); - ASSERT_EQ(result->GetScalar().UInt(), 0x5678u); + EXPECT_THAT_EXPECTED(result, ExpectLoadAddress(0x5678u)); ASSERT_TRUE(expr.Update_DW_OP_addr(dwarf_cu, 0xdeadbeef)); result = evaluate(expr); - ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); - ASSERT_EQ(result->GetValueType(), Value::ValueType::LoadAddress); - ASSERT_EQ(result->GetScalar().UInt(), 0xdeadbeefu); + EXPECT_THAT_EXPECTED(result, ExpectLoadAddress(0xdeadbeefu)); } class CustomSymbolFileDWARF : public SymbolFileDWARF { @@ -778,11 +717,12 @@ static auto testExpressionVendorExtensions(lldb::ModuleSP module_sp, RegisterContext *reg_ctx) { // Test that expression extensions can be evaluated, for example // DW_OP_WASM_location which is not currently handled by DWARFExpression: - EXPECT_THAT_EXPECTED(Evaluate({DW_OP_WASM_location, 0x03, // WASM_GLOBAL:0x03 - 0x04, 0x00, 0x00, // index:u32 - 0x00, DW_OP_stack_value}, - module_sp, &dwarf_unit, nullptr, reg_ctx), - llvm::HasValue(GetScalar(32, 42, false))); + EXPECT_THAT_EXPECTED( + Evaluate({DW_OP_WASM_location, 0x03, // WASM_GLOBAL:0x03 + 0x04, 0x00, 0x00, // index:u32 + 0x00, DW_OP_stack_value}, + module_sp, &dwarf_unit, nullptr, reg_ctx), + ExpectScalar(32, 42, false, Value::ContextType::RegisterInfo)); // Test that searches for opcodes work in the presence of extensions: uint8_t expr[] = {DW_OP_WASM_location, 0x03, 0x04, 0x00, 0x00, 0x00, @@ -1148,17 +1088,8 @@ TEST_F(DWARFExpressionMockProcessTest, DW_OP_piece_file_addr) { uint8_t expr[] = {DW_OP_addr, 0x40, 0x0, 0x0, 0x0, DW_OP_piece, 1, DW_OP_addr, 0x50, 0x0, 0x0, 0x0, DW_OP_piece, 1}; - DataExtractor extractor(expr, sizeof(expr), lldb::eByteOrderLittle, - /*addr_size=*/4); - llvm::Expected result = DWARFExpression::Evaluate( - &exe_ctx, /*reg_ctx=*/nullptr, /*module_sp=*/{}, extractor, - /*unit=*/nullptr, lldb::eRegisterKindLLDB, - /*initial_value_ptr=*/nullptr, - /*object_address_ptr=*/nullptr); - - ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); - ASSERT_EQ(result->GetValueType(), Value::ValueType::HostAddress); - ASSERT_THAT(result->GetBuffer().GetData(), ElementsAre(0x11, 0x22)); + EXPECT_THAT_EXPECTED(Evaluate(expr, {}, {}, &exe_ctx), + ExpectHostAddress({0x11, 0x22})); } /// A Process whose `ReadMemory` override queries a DenseMap. @@ -1228,28 +1159,15 @@ TEST_F(DWARFExpressionMockProcessTestWithAArch, DW_op_deref_no_ptr_fixing) { process_sp->GetThreadList().AddThread(thread); auto evaluate_expr = [&](auto &expr_data) { - DataExtractor extractor(expr_data, sizeof(expr_data), - lldb::eByteOrderLittle, - /*addr_size*/ 8); - DWARFExpression expr(extractor); - ExecutionContext exe_ctx(process_sp); - llvm::Expected result = DWARFExpression::Evaluate( - &exe_ctx, reg_ctx_sp.get(), /*module_sp*/ nullptr, extractor, - /*unit*/ nullptr, lldb::eRegisterKindLLDB, - /*initial_value_ptr=*/nullptr, - /*object_address_ptr=*/nullptr); - return result; + return Evaluate(expr_data, {}, {}, &exe_ctx, reg_ctx_sp.get()); }; uint8_t expr_reg[] = {DW_OP_breg22, 0}; llvm::Expected result_reg = evaluate_expr(expr_reg); - ASSERT_THAT_EXPECTED(result_reg, llvm::Succeeded()); - ASSERT_EQ(result_reg->GetValueType(), Value::ValueType::LoadAddress); - ASSERT_EQ(result_reg->GetScalar().ULongLong(), addr); + EXPECT_THAT_EXPECTED(result_reg, ExpectLoadAddress(addr)); uint8_t expr_deref[] = {DW_OP_breg22, 0, DW_OP_deref}; llvm::Expected result_deref = evaluate_expr(expr_deref); - ASSERT_THAT_EXPECTED(result_deref, llvm::Succeeded()); - ASSERT_EQ(result_deref->GetScalar().ULongLong(), expected_value); + EXPECT_THAT_EXPECTED(result_deref, ExpectLoadAddress(expected_value)); } diff --git a/lldb/unittests/Expression/ValueMatcher.h b/lldb/unittests/Expression/ValueMatcher.h new file mode 100644 index 0000000000000..38672bf054e0f --- /dev/null +++ b/lldb/unittests/Expression/ValueMatcher.h @@ -0,0 +1,283 @@ +//===-- ValueMatcher.h ----------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// \file +/// This file contains the definition of the ValueMatcher class which is a used +/// to match lldb_private::Value in gtest assert/expect macros. It also contains +/// several helper functions to create matchers for common Value types. +//===----------------------------------------------------------------------===// + +#ifndef LLDB_UNITTESTS_EXPRESSION_VALUEMATCHER_H +#define LLDB_UNITTESTS_EXPRESSION_VALUEMATCHER_H + +#include "lldb/Core/Value.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/Scalar.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Testing/Support/Error.h" +#include "gtest/gtest.h" +#include +#include +#include + +namespace lldb_private { + +/// Helper function to format Value details to an ostream. +/// +/// This is used for format the details coming from either a Value directly +/// or the parts stored in the ValueMatcher object. +inline void FormatValueDetails(std::ostream &os, Value::ValueType value_type, + Value::ContextType context_type, + const Scalar &scalar, + llvm::ArrayRef buffer_data) { + os << "Value("; + os << "value_type=" << Value::GetValueTypeAsCString(value_type); + os << ", context_type=" << Value::GetContextTypeAsCString(context_type); + + if (value_type == Value::ValueType::HostAddress) { + os << ", buffer=["; + for (size_t i = 0; i < std::min(buffer_data.size(), size_t(16)); ++i) { + if (i > 0) + os << " "; + os << std::hex << std::setw(2) << std::setfill('0') + << static_cast(buffer_data[i]); + } + if (buffer_data.size() > 16) { + os << " ..."; + } + os << std::dec << "] (" << buffer_data.size() << " bytes)"; + } else { + std::string scalar_str; + llvm::raw_string_ostream scalar_os(scalar_str); + scalar_os << scalar; + os << ", value=" << scalar_os.str(); + } + os << ")"; +} + +/// Custom printer for Value objects to make test failures more readable. +inline void PrintTo(const Value &val, std::ostream *os) { + if (!os) + return; + + FormatValueDetails(*os, val.GetValueType(), val.GetContextType(), + val.GetScalar(), val.GetBuffer().GetData()); +} + +/// Custom matcher for Value. +/// +/// It matches against an expected value_type, and context_type. +/// For HostAddress value types it will match the expected contents of +/// the host buffer. For other value types it matches against an expected +/// scalar value. +class ValueMatcher : public testing::MatcherInterface { +public: + ValueMatcher(Value::ValueType value_type, const Scalar &expected_scalar, + Value::ContextType context_type) + : m_value_type(value_type), m_context_type(context_type), + m_expected_scalar(expected_scalar) { + assert(value_type == Value::ValueType::Scalar || + value_type == Value::ValueType::FileAddress || + value_type == Value::ValueType::LoadAddress); + } + + ValueMatcher(Value::ValueType value_type, + const std::vector &expected_bytes, + Value::ContextType context_type) + : m_value_type(value_type), m_context_type(context_type), + m_expected_bytes(expected_bytes) { + assert(value_type == Value::ValueType::HostAddress); + } + + bool MatchAndExplain(const Value &val, + testing::MatchResultListener *listener) const override { + if (val.GetValueType() != m_value_type) { + *listener << "value_type mismatch: expected " + << Value::GetValueTypeAsCString(m_value_type) << ", got " + << Value::GetValueTypeAsCString(val.GetValueType()) << " "; + return false; + } + + if (val.GetContextType() != m_context_type) { + *listener << "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()) { + *listener << "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]) { + *listener << "byte mismatch at index " << i << ": expected " + << "0x" << std::hex << std::setw(2) << std::setfill('0') + << static_cast(m_expected_bytes[i]) << ", got " + << "0x" << std::hex << std::setw(2) << std::setfill('0') + << static_cast(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) { + std::string expected_str, actual_str; + llvm::raw_string_ostream expected_os(expected_str); + llvm::raw_string_ostream actual_os(actual_str); + expected_os << m_expected_scalar; + actual_os << actual_scalar; + *listener << "scalar value mismatch: expected " << expected_os.str() + << ", got " << actual_os.str() << " "; + return false; + } + } + + return true; + } + + void DescribeTo(std::ostream *os) const override { + if (!os) + return; + FormatValueDetails(*os, m_value_type, m_context_type, m_expected_scalar, + m_expected_bytes); + } + + void DescribeNegationTo(std::ostream *os) const override { + if (!os) + return; + *os << "value does not match"; + } + +private: + Value::ValueType m_value_type = Value::ValueType::Invalid; + Value::ContextType m_context_type = Value::ContextType::Invalid; + Scalar m_expected_scalar{}; + std::vector m_expected_bytes{}; +}; + +/// Matcher for Value with Scalar, FileAddress, or LoadAddress types. +/// Use with llvm::HasValue() to match Expected: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(MatchScalarValue(...))); +inline testing::Matcher +MatchScalarValue(Value::ValueType value_type, const Scalar &expected_scalar, + Value::ContextType context_type) { + return testing::Matcher( + new ValueMatcher(value_type, expected_scalar, context_type)); +} + +/// Matcher for Value with HostAddress type. +/// Use with llvm::HasValue() to match Expected: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(MatchHostValue(...))); +inline testing::Matcher +MatchHostValue(Value::ValueType value_type, + const std::vector &expected_bytes, + Value::ContextType context_type) { + return testing::Matcher( + new ValueMatcher(value_type, expected_bytes, context_type)); +} + +/// Helper to match a Scalar value and context type. +/// Use with llvm::HasValue() to match Expected: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(IsScalar(42))); +inline testing::Matcher IsScalar(const Scalar &expected_scalar, + Value::ContextType context_type) { + return MatchScalarValue(Value::ValueType::Scalar, expected_scalar, + context_type); +} + +/// Helper to match a LoadAddress value and context type. +/// Use with llvm::HasValue() to match Expected: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(IsLoadAddress(0x1000))); +inline testing::Matcher IsLoadAddress(const Scalar &expected_address, + Value::ContextType context_type) { + return MatchScalarValue(Value::ValueType::LoadAddress, expected_address, + context_type); +} + +/// Helper to match a FileAddress value and context type. +/// Use with llvm::HasValue() to match Expected: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(IsFileAddress(Scalar(0x1000)))); +inline testing::Matcher IsFileAddress(const Scalar &expected_address, + Value::ContextType context_type) { + return MatchScalarValue(Value::ValueType::FileAddress, expected_address, + context_type); +} + +/// Helper to match a HostAddress value and context type. +/// Use with llvm::HasValue() to match Expected: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(IsHostValue({0x11, 0x22}))); +inline testing::Matcher +IsHostValue(const std::vector &expected_bytes, + Value::ContextType context_type) { + return MatchHostValue(Value::ValueType::HostAddress, expected_bytes, + context_type); +} + +/// Helper to create a scalar because Scalar's operator==() is really picky. +inline Scalar GetScalar(unsigned bits, uint64_t value, bool sign) { + Scalar scalar(value); + scalar.TruncOrExtendTo(bits, sign); + return scalar; +} + +/// Helper that combines IsScalar with llvm::HasValue for Expected. +/// Use it on an Expected like this: +/// EXPECT_THAT_EXPECTED(result, ExpectScalar(42)); +inline auto +ExpectScalar(const Scalar &expected_scalar, + Value::ContextType context_type = Value::ContextType::Invalid) { + return llvm::HasValue(IsScalar(expected_scalar, context_type)); +} + +/// Helper that combines GetScalar with ExpectScalar to get a precise scalar. +/// Use it on an Expected like this: +/// EXPECT_THAT_EXPECTED(result, ExpectScalar(8, 42, true)); +inline auto +ExpectScalar(unsigned bits, uint64_t value, bool sign, + Value::ContextType context_type = Value::ContextType::Invalid) { + return ExpectScalar(GetScalar(bits, value, sign), context_type); +} + +/// Helper that combines IsLoadAddress with llvm::HasValue for Expected. +/// Use it on an Expected like this: +/// EXPECT_THAT_EXPECTED(result, ExpectLoadAddress(0x1000)); +inline auto ExpectLoadAddress( + const Scalar &expected_address, + Value::ContextType context_type = Value::ContextType::Invalid) { + return llvm::HasValue(IsLoadAddress(expected_address, context_type)); +} + +/// Helper that combines IsFileAddress with llvm::HasValue for Expected. +/// Use it on an Expected like this: +/// EXPECT_THAT_EXPECTED(result, ExpectFileAddress(Scalar(0x2000))); +inline auto ExpectFileAddress( + const Scalar &expected_address, + Value::ContextType context_type = Value::ContextType::Invalid) { + return llvm::HasValue(IsFileAddress(expected_address, context_type)); +} + +/// Helper that combines IsHostValue with llvm::HasValue for Expected. +/// Use it on an Expected like this: +/// EXPECT_THAT_EXPECTED(result, ExpectHostAddress({0x11, 0x22})); +inline auto ExpectHostAddress( + const std::vector &expected_bytes, + Value::ContextType context_type = Value::ContextType::Invalid) { + return llvm::HasValue(IsHostValue(expected_bytes, context_type)); +} + +} // namespace lldb_private + +#endif // LLDB_UNITTESTS_EXPRESSION_VALUEMATCHER_H From 9c4318539aca27bdeb4e8aeb0d04aa59ae436c15 Mon Sep 17 00:00:00 2001 From: David Peixotto Date: Tue, 11 Nov 2025 14:52:30 -0800 Subject: [PATCH 02/13] Use new style for monomorphic matchers --- lldb/unittests/Expression/ValueMatcher.h | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lldb/unittests/Expression/ValueMatcher.h b/lldb/unittests/Expression/ValueMatcher.h index 38672bf054e0f..1f801d155841c 100644 --- a/lldb/unittests/Expression/ValueMatcher.h +++ b/lldb/unittests/Expression/ValueMatcher.h @@ -75,7 +75,7 @@ inline void PrintTo(const Value &val, std::ostream *os) { /// For HostAddress value types it will match the expected contents of /// the host buffer. For other value types it matches against an expected /// scalar value. -class ValueMatcher : public testing::MatcherInterface { +class ValueMatcher { public: ValueMatcher(Value::ValueType value_type, const Scalar &expected_scalar, Value::ContextType context_type) @@ -94,8 +94,11 @@ class ValueMatcher : public testing::MatcherInterface { assert(value_type == Value::ValueType::HostAddress); } + // Typedef to hook into the gtest matcher machinery. + using is_gtest_matcher = void; + bool MatchAndExplain(const Value &val, - testing::MatchResultListener *listener) const override { + testing::MatchResultListener *listener) const { if (val.GetValueType() != m_value_type) { *listener << "value_type mismatch: expected " << Value::GetValueTypeAsCString(m_value_type) << ", got " @@ -148,14 +151,14 @@ class ValueMatcher : public testing::MatcherInterface { return true; } - void DescribeTo(std::ostream *os) const override { + void DescribeTo(std::ostream *os) const { if (!os) return; FormatValueDetails(*os, m_value_type, m_context_type, m_expected_scalar, m_expected_bytes); } - void DescribeNegationTo(std::ostream *os) const override { + void DescribeNegationTo(std::ostream *os) const { if (!os) return; *os << "value does not match"; @@ -174,8 +177,7 @@ class ValueMatcher : public testing::MatcherInterface { inline testing::Matcher MatchScalarValue(Value::ValueType value_type, const Scalar &expected_scalar, Value::ContextType context_type) { - return testing::Matcher( - new ValueMatcher(value_type, expected_scalar, context_type)); + return ValueMatcher(value_type, expected_scalar, context_type); } /// Matcher for Value with HostAddress type. @@ -185,8 +187,7 @@ inline testing::Matcher MatchHostValue(Value::ValueType value_type, const std::vector &expected_bytes, Value::ContextType context_type) { - return testing::Matcher( - new ValueMatcher(value_type, expected_bytes, context_type)); + return ValueMatcher(value_type, expected_bytes, context_type); } /// Helper to match a Scalar value and context type. From f9b8debd5b7f61db2fd0f63e33572433a97a35dc Mon Sep 17 00:00:00 2001 From: David Peixotto Date: Tue, 11 Nov 2025 16:22:10 -0800 Subject: [PATCH 03/13] Use llvm::raw_ostream where possible --- lldb/unittests/Expression/ValueMatcher.h | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/lldb/unittests/Expression/ValueMatcher.h b/lldb/unittests/Expression/ValueMatcher.h index 1f801d155841c..e907bccda59a7 100644 --- a/lldb/unittests/Expression/ValueMatcher.h +++ b/lldb/unittests/Expression/ValueMatcher.h @@ -18,11 +18,11 @@ #include "lldb/Utility/DataBufferHeap.h" #include "lldb/Utility/Scalar.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/Format.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Testing/Support/Error.h" #include "gtest/gtest.h" #include -#include #include namespace lldb_private { @@ -31,7 +31,8 @@ namespace lldb_private { /// /// This is used for format the details coming from either a Value directly /// or the parts stored in the ValueMatcher object. -inline void FormatValueDetails(std::ostream &os, Value::ValueType value_type, +inline void FormatValueDetails(llvm::raw_ostream &os, + Value::ValueType value_type, Value::ContextType context_type, const Scalar &scalar, llvm::ArrayRef buffer_data) { @@ -44,18 +45,14 @@ inline void FormatValueDetails(std::ostream &os, Value::ValueType value_type, for (size_t i = 0; i < std::min(buffer_data.size(), size_t(16)); ++i) { if (i > 0) os << " "; - os << std::hex << std::setw(2) << std::setfill('0') - << static_cast(buffer_data[i]); + os << llvm::format("%02x", static_cast(buffer_data[i])); } if (buffer_data.size() > 16) { os << " ..."; } - os << std::dec << "] (" << buffer_data.size() << " bytes)"; + os << "] (" << buffer_data.size() << " bytes)"; } else { - std::string scalar_str; - llvm::raw_string_ostream scalar_os(scalar_str); - scalar_os << scalar; - os << ", value=" << scalar_os.str(); + os << ", value=" << scalar; } os << ")"; } @@ -65,7 +62,8 @@ inline void PrintTo(const Value &val, std::ostream *os) { if (!os) return; - FormatValueDetails(*os, val.GetValueType(), val.GetContextType(), + llvm::raw_os_ostream raw_os(*os); + FormatValueDetails(raw_os, val.GetValueType(), val.GetContextType(), val.GetScalar(), val.GetBuffer().GetData()); } @@ -154,7 +152,8 @@ class ValueMatcher { void DescribeTo(std::ostream *os) const { if (!os) return; - FormatValueDetails(*os, m_value_type, m_context_type, m_expected_scalar, + llvm::raw_os_ostream raw_os(*os); + FormatValueDetails(raw_os, m_value_type, m_context_type, m_expected_scalar, m_expected_bytes); } From 96200e463d8100c7ebf414f9db19d8b326d1224e Mon Sep 17 00:00:00 2001 From: David Peixotto Date: Tue, 11 Nov 2025 16:56:56 -0800 Subject: [PATCH 04/13] Move implementation to cpp file --- lldb/unittests/Expression/CMakeLists.txt | 1 + lldb/unittests/Expression/ValueMatcher.cpp | 185 +++++++++++++++++++++ lldb/unittests/Expression/ValueMatcher.h | 185 ++++----------------- 3 files changed, 214 insertions(+), 157 deletions(-) create mode 100644 lldb/unittests/Expression/ValueMatcher.cpp diff --git a/lldb/unittests/Expression/CMakeLists.txt b/lldb/unittests/Expression/CMakeLists.txt index 2600557b6b376..0e0b002500eb4 100644 --- a/lldb/unittests/Expression/CMakeLists.txt +++ b/lldb/unittests/Expression/CMakeLists.txt @@ -10,6 +10,7 @@ add_lldb_unittest(ExpressionTests DWARFExpressionTest.cpp CppModuleConfigurationTest.cpp ExpressionTest.cpp + ValueMatcher.cpp LINK_COMPONENTS Support diff --git a/lldb/unittests/Expression/ValueMatcher.cpp b/lldb/unittests/Expression/ValueMatcher.cpp new file mode 100644 index 0000000000000..5de3ecf09ee57 --- /dev/null +++ b/lldb/unittests/Expression/ValueMatcher.cpp @@ -0,0 +1,185 @@ +//===-- ValueMatcher.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 "ValueMatcher.h" +#include "llvm/Support/Format.h" +#include + +using namespace lldb_private; + +static void FormatValueDetails(llvm::raw_ostream &os, Value::ValueType value_type, + Value::ContextType context_type, const Scalar &scalar, + llvm::ArrayRef buffer_data) { + os << "Value("; + os << "value_type=" << Value::GetValueTypeAsCString(value_type); + os << ", context_type=" << Value::GetContextTypeAsCString(context_type); + + if (value_type == Value::ValueType::HostAddress) { + os << ", buffer=["; + for (size_t i = 0; i < std::min(buffer_data.size(), size_t(16)); ++i) { + if (i > 0) + os << " "; + os << llvm::format("%02x", static_cast(buffer_data[i])); + } + 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, testing::MatchResultListener *listener) const { + if (val.GetValueType() != m_value_type) { + *listener << "value_type mismatch: expected " + << Value::GetValueTypeAsCString(m_value_type) << ", got " + << Value::GetValueTypeAsCString(val.GetValueType()) << " "; + return false; + } + + if (val.GetContextType() != m_context_type) { + *listener << "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()) { + *listener << "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]) { + *listener << "byte mismatch at index " << i << ": expected " << "0x" + << std::hex << std::setw(2) << std::setfill('0') + << static_cast(m_expected_bytes[i]) << ", got " << "0x" + << std::hex << std::setw(2) << std::setfill('0') + << static_cast(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) { + std::string expected_str, actual_str; + llvm::raw_string_ostream expected_os(expected_str); + llvm::raw_string_ostream actual_os(actual_str); + expected_os << m_expected_scalar; + actual_os << actual_scalar; + *listener << "scalar value mismatch: expected " << expected_os.str() + << ", got " << actual_os.str() << " "; + 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 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 lldb_private::MatchHostValue(Value::ValueType value_type, + const std::vector &expected_bytes, + Value::ContextType context_type) { + return ValueMatcher(value_type, expected_bytes, context_type); +} + +testing::Matcher lldb_private::IsScalar(const Scalar &expected_scalar, + Value::ContextType context_type) { + return MatchScalarValue(Value::ValueType::Scalar, expected_scalar, + context_type); +} + +testing::Matcher lldb_private::IsLoadAddress(const Scalar &expected_address, + Value::ContextType context_type) { + return MatchScalarValue(Value::ValueType::LoadAddress, expected_address, + context_type); +} + +testing::Matcher lldb_private::IsFileAddress(const Scalar &expected_address, + Value::ContextType context_type) { + return MatchScalarValue(Value::ValueType::FileAddress, expected_address, + context_type); +} + +testing::Matcher lldb_private::IsHostValue(const std::vector &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> +lldb_private::ExpectScalar(const Scalar &expected_scalar, Value::ContextType context_type) { + return llvm::HasValue(IsScalar(expected_scalar, context_type)); +} + +llvm::detail::ValueMatchesPoly> +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> +lldb_private::ExpectLoadAddress(const Scalar &expected_address, + Value::ContextType context_type) { + return llvm::HasValue(IsLoadAddress(expected_address, context_type)); +} + +llvm::detail::ValueMatchesPoly> +lldb_private::ExpectFileAddress(const Scalar &expected_address, + Value::ContextType context_type) { + return llvm::HasValue(IsFileAddress(expected_address, context_type)); +} + +llvm::detail::ValueMatchesPoly> +lldb_private::ExpectHostAddress(const std::vector &expected_bytes, + Value::ContextType context_type) { + return llvm::HasValue(IsHostValue(expected_bytes, context_type)); +} diff --git a/lldb/unittests/Expression/ValueMatcher.h b/lldb/unittests/Expression/ValueMatcher.h index e907bccda59a7..d0c0d5107e10d 100644 --- a/lldb/unittests/Expression/ValueMatcher.h +++ b/lldb/unittests/Expression/ValueMatcher.h @@ -27,45 +27,8 @@ namespace lldb_private { -/// Helper function to format Value details to an ostream. -/// -/// This is used for format the details coming from either a Value directly -/// or the parts stored in the ValueMatcher object. -inline void FormatValueDetails(llvm::raw_ostream &os, - Value::ValueType value_type, - Value::ContextType context_type, - const Scalar &scalar, - llvm::ArrayRef buffer_data) { - os << "Value("; - os << "value_type=" << Value::GetValueTypeAsCString(value_type); - os << ", context_type=" << Value::GetContextTypeAsCString(context_type); - - if (value_type == Value::ValueType::HostAddress) { - os << ", buffer=["; - for (size_t i = 0; i < std::min(buffer_data.size(), size_t(16)); ++i) { - if (i > 0) - os << " "; - os << llvm::format("%02x", static_cast(buffer_data[i])); - } - if (buffer_data.size() > 16) { - os << " ..."; - } - os << "] (" << buffer_data.size() << " bytes)"; - } else { - os << ", value=" << scalar; - } - os << ")"; -} - /// Custom printer for Value objects to make test failures more readable. -inline void 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()); -} +void PrintTo(const Value &val, std::ostream *os); /// Custom matcher for Value. /// @@ -96,72 +59,11 @@ class ValueMatcher { using is_gtest_matcher = void; bool MatchAndExplain(const Value &val, - testing::MatchResultListener *listener) const { - if (val.GetValueType() != m_value_type) { - *listener << "value_type mismatch: expected " - << Value::GetValueTypeAsCString(m_value_type) << ", got " - << Value::GetValueTypeAsCString(val.GetValueType()) << " "; - return false; - } - - if (val.GetContextType() != m_context_type) { - *listener << "context_type mismatch: expected " - << Value::GetContextTypeAsCString(m_context_type) << ", got " - << Value::GetContextTypeAsCString(val.GetContextType()) << " "; - return false; - } + testing::MatchResultListener *listener) const; - 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()) { - *listener << "buffer size mismatch: expected " - << m_expected_bytes.size() << ", got " << buffer_size << " "; - return false; - } + void DescribeTo(std::ostream *os) const; - const uint8_t *data = buffer.GetBytes(); - for (size_t i = 0; i < buffer_size; ++i) { - if (data[i] != m_expected_bytes[i]) { - *listener << "byte mismatch at index " << i << ": expected " - << "0x" << std::hex << std::setw(2) << std::setfill('0') - << static_cast(m_expected_bytes[i]) << ", got " - << "0x" << std::hex << std::setw(2) << std::setfill('0') - << static_cast(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) { - std::string expected_str, actual_str; - llvm::raw_string_ostream expected_os(expected_str); - llvm::raw_string_ostream actual_os(actual_str); - expected_os << m_expected_scalar; - actual_os << actual_scalar; - *listener << "scalar value mismatch: expected " << expected_os.str() - << ", got " << actual_os.str() << " "; - return false; - } - } - - return true; - } - - void 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 DescribeNegationTo(std::ostream *os) const { - if (!os) - return; - *os << "value does not match"; - } + void DescribeNegationTo(std::ostream *os) const; private: Value::ValueType m_value_type = Value::ValueType::Invalid; @@ -173,110 +75,79 @@ class ValueMatcher { /// Matcher for Value with Scalar, FileAddress, or LoadAddress types. /// Use with llvm::HasValue() to match Expected: /// EXPECT_THAT_EXPECTED(result, llvm::HasValue(MatchScalarValue(...))); -inline testing::Matcher -MatchScalarValue(Value::ValueType value_type, const Scalar &expected_scalar, - Value::ContextType context_type) { - return ValueMatcher(value_type, expected_scalar, context_type); -} +testing::Matcher MatchScalarValue(Value::ValueType value_type, + const Scalar &expected_scalar, + Value::ContextType context_type); /// Matcher for Value with HostAddress type. /// Use with llvm::HasValue() to match Expected: /// EXPECT_THAT_EXPECTED(result, llvm::HasValue(MatchHostValue(...))); -inline testing::Matcher +testing::Matcher MatchHostValue(Value::ValueType value_type, const std::vector &expected_bytes, - Value::ContextType context_type) { - return ValueMatcher(value_type, expected_bytes, context_type); -} + Value::ContextType context_type); /// Helper to match a Scalar value and context type. /// Use with llvm::HasValue() to match Expected: /// EXPECT_THAT_EXPECTED(result, llvm::HasValue(IsScalar(42))); -inline testing::Matcher IsScalar(const Scalar &expected_scalar, - Value::ContextType context_type) { - return MatchScalarValue(Value::ValueType::Scalar, expected_scalar, - context_type); -} +testing::Matcher IsScalar(const Scalar &expected_scalar, + Value::ContextType context_type); /// Helper to match a LoadAddress value and context type. /// Use with llvm::HasValue() to match Expected: /// EXPECT_THAT_EXPECTED(result, llvm::HasValue(IsLoadAddress(0x1000))); -inline testing::Matcher IsLoadAddress(const Scalar &expected_address, - Value::ContextType context_type) { - return MatchScalarValue(Value::ValueType::LoadAddress, expected_address, - context_type); -} +testing::Matcher IsLoadAddress(const Scalar &expected_address, + Value::ContextType context_type); /// Helper to match a FileAddress value and context type. /// Use with llvm::HasValue() to match Expected: /// EXPECT_THAT_EXPECTED(result, llvm::HasValue(IsFileAddress(Scalar(0x1000)))); -inline testing::Matcher IsFileAddress(const Scalar &expected_address, - Value::ContextType context_type) { - return MatchScalarValue(Value::ValueType::FileAddress, expected_address, - context_type); -} +testing::Matcher IsFileAddress(const Scalar &expected_address, + Value::ContextType context_type); /// Helper to match a HostAddress value and context type. /// Use with llvm::HasValue() to match Expected: /// EXPECT_THAT_EXPECTED(result, llvm::HasValue(IsHostValue({0x11, 0x22}))); -inline testing::Matcher -IsHostValue(const std::vector &expected_bytes, - Value::ContextType context_type) { - return MatchHostValue(Value::ValueType::HostAddress, expected_bytes, - context_type); -} +testing::Matcher IsHostValue(const std::vector &expected_bytes, + Value::ContextType context_type); /// Helper to create a scalar because Scalar's operator==() is really picky. -inline Scalar GetScalar(unsigned bits, uint64_t value, bool sign) { - Scalar scalar(value); - scalar.TruncOrExtendTo(bits, sign); - return scalar; -} +Scalar GetScalar(unsigned bits, uint64_t value, bool sign); /// Helper that combines IsScalar with llvm::HasValue for Expected. /// Use it on an Expected like this: /// EXPECT_THAT_EXPECTED(result, ExpectScalar(42)); -inline auto +llvm::detail::ValueMatchesPoly> ExpectScalar(const Scalar &expected_scalar, - Value::ContextType context_type = Value::ContextType::Invalid) { - return llvm::HasValue(IsScalar(expected_scalar, context_type)); -} + Value::ContextType context_type = Value::ContextType::Invalid); /// Helper that combines GetScalar with ExpectScalar to get a precise scalar. /// Use it on an Expected like this: /// EXPECT_THAT_EXPECTED(result, ExpectScalar(8, 42, true)); -inline auto +llvm::detail::ValueMatchesPoly> ExpectScalar(unsigned bits, uint64_t value, bool sign, - Value::ContextType context_type = Value::ContextType::Invalid) { - return ExpectScalar(GetScalar(bits, value, sign), context_type); -} + Value::ContextType context_type = Value::ContextType::Invalid); /// Helper that combines IsLoadAddress with llvm::HasValue for Expected. /// Use it on an Expected like this: /// EXPECT_THAT_EXPECTED(result, ExpectLoadAddress(0x1000)); -inline auto ExpectLoadAddress( +llvm::detail::ValueMatchesPoly> ExpectLoadAddress( const Scalar &expected_address, - Value::ContextType context_type = Value::ContextType::Invalid) { - return llvm::HasValue(IsLoadAddress(expected_address, context_type)); -} + Value::ContextType context_type = Value::ContextType::Invalid); /// Helper that combines IsFileAddress with llvm::HasValue for Expected. /// Use it on an Expected like this: /// EXPECT_THAT_EXPECTED(result, ExpectFileAddress(Scalar(0x2000))); -inline auto ExpectFileAddress( +llvm::detail::ValueMatchesPoly> ExpectFileAddress( const Scalar &expected_address, - Value::ContextType context_type = Value::ContextType::Invalid) { - return llvm::HasValue(IsFileAddress(expected_address, context_type)); -} + Value::ContextType context_type = Value::ContextType::Invalid); /// Helper that combines IsHostValue with llvm::HasValue for Expected. /// Use it on an Expected like this: /// EXPECT_THAT_EXPECTED(result, ExpectHostAddress({0x11, 0x22})); -inline auto ExpectHostAddress( +llvm::detail::ValueMatchesPoly> ExpectHostAddress( const std::vector &expected_bytes, - Value::ContextType context_type = Value::ContextType::Invalid) { - return llvm::HasValue(IsHostValue(expected_bytes, context_type)); -} + Value::ContextType context_type = Value::ContextType::Invalid); } // namespace lldb_private From 79d1cab26415aa4aab8c448e6ecb3166efe3373f Mon Sep 17 00:00:00 2001 From: David Peixotto Date: Tue, 11 Nov 2025 17:18:02 -0800 Subject: [PATCH 05/13] Use raw_ostream for MatchAndExplain --- lldb/unittests/Expression/ValueMatcher.cpp | 47 +++++++++++++--------- lldb/unittests/Expression/ValueMatcher.h | 2 + 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/lldb/unittests/Expression/ValueMatcher.cpp b/lldb/unittests/Expression/ValueMatcher.cpp index 5de3ecf09ee57..959cbba7a821d 100644 --- a/lldb/unittests/Expression/ValueMatcher.cpp +++ b/lldb/unittests/Expression/ValueMatcher.cpp @@ -8,6 +8,8 @@ #include "ValueMatcher.h" #include "llvm/Support/Format.h" +#include "llvm/Support/raw_os_ostream.h" +#include "llvm/Support/raw_ostream.h" #include using namespace lldb_private; @@ -47,17 +49,28 @@ void lldb_private::PrintTo(const Value &val, std::ostream *os) { bool ValueMatcher::MatchAndExplain( const Value &val, testing::MatchResultListener *listener) const { + if (listener && listener->stream()) { + llvm::raw_os_ostream os(*listener->stream()); + return MatchAndExplainImpl(val, os); + } + + llvm::raw_null_ostream os; + return MatchAndExplainImpl(val, os); +} + +bool ValueMatcher::MatchAndExplainImpl(const Value &val, + llvm::raw_ostream &os) const { if (val.GetValueType() != m_value_type) { - *listener << "value_type mismatch: expected " - << Value::GetValueTypeAsCString(m_value_type) << ", got " - << Value::GetValueTypeAsCString(val.GetValueType()) << " "; + os << "value_type mismatch: expected " + << Value::GetValueTypeAsCString(m_value_type) << ", got " + << Value::GetValueTypeAsCString(val.GetValueType()) << " "; return false; } if (val.GetContextType() != m_context_type) { - *listener << "context_type mismatch: expected " - << Value::GetContextTypeAsCString(m_context_type) << ", got " - << Value::GetContextTypeAsCString(val.GetContextType()) << " "; + os << "context_type mismatch: expected " + << Value::GetContextTypeAsCString(m_context_type) << ", got " + << Value::GetContextTypeAsCString(val.GetContextType()) << " "; return false; } @@ -65,19 +78,18 @@ bool ValueMatcher::MatchAndExplain( const DataBufferHeap &buffer = val.GetBuffer(); const size_t buffer_size = buffer.GetByteSize(); if (buffer_size != m_expected_bytes.size()) { - *listener << "buffer size mismatch: expected " << m_expected_bytes.size() - << ", got " << buffer_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]) { - *listener << "byte mismatch at index " << i << ": expected " << "0x" - << std::hex << std::setw(2) << std::setfill('0') - << static_cast(m_expected_bytes[i]) << ", got " << "0x" - << std::hex << std::setw(2) << std::setfill('0') - << static_cast(data[i]) << " "; + os << "byte mismatch at index " << i << ": expected " + << llvm::format("0x%02x", static_cast(m_expected_bytes[i])) + << ", got " << llvm::format("0x%02x", static_cast(data[i])) + << " "; return false; } } @@ -85,13 +97,8 @@ bool ValueMatcher::MatchAndExplain( // For Scalar, FileAddress, and LoadAddress - compare m_value const Scalar &actual_scalar = val.GetScalar(); if (actual_scalar != m_expected_scalar) { - std::string expected_str, actual_str; - llvm::raw_string_ostream expected_os(expected_str); - llvm::raw_string_ostream actual_os(actual_str); - expected_os << m_expected_scalar; - actual_os << actual_scalar; - *listener << "scalar value mismatch: expected " << expected_os.str() - << ", got " << actual_os.str() << " "; + os << "scalar value mismatch: expected " << m_expected_scalar << ", got " + << actual_scalar; return false; } } diff --git a/lldb/unittests/Expression/ValueMatcher.h b/lldb/unittests/Expression/ValueMatcher.h index d0c0d5107e10d..318e03b199261 100644 --- a/lldb/unittests/Expression/ValueMatcher.h +++ b/lldb/unittests/Expression/ValueMatcher.h @@ -70,6 +70,8 @@ class ValueMatcher { Value::ContextType m_context_type = Value::ContextType::Invalid; Scalar m_expected_scalar{}; std::vector m_expected_bytes{}; + + bool MatchAndExplainImpl(const Value &val, llvm::raw_ostream &os) const; }; /// Matcher for Value with Scalar, FileAddress, or LoadAddress types. From 7aa8269358a4ab0354d77f59272a9d3256576159 Mon Sep 17 00:00:00 2001 From: David Peixotto Date: Tue, 11 Nov 2025 17:19:29 -0800 Subject: [PATCH 06/13] cleanup headers --- lldb/unittests/Expression/ValueMatcher.cpp | 1 - lldb/unittests/Expression/ValueMatcher.h | 3 --- 2 files changed, 4 deletions(-) diff --git a/lldb/unittests/Expression/ValueMatcher.cpp b/lldb/unittests/Expression/ValueMatcher.cpp index 959cbba7a821d..bf2ba69120b80 100644 --- a/lldb/unittests/Expression/ValueMatcher.cpp +++ b/lldb/unittests/Expression/ValueMatcher.cpp @@ -10,7 +10,6 @@ #include "llvm/Support/Format.h" #include "llvm/Support/raw_os_ostream.h" #include "llvm/Support/raw_ostream.h" -#include using namespace lldb_private; diff --git a/lldb/unittests/Expression/ValueMatcher.h b/lldb/unittests/Expression/ValueMatcher.h index 318e03b199261..818e0bd79088d 100644 --- a/lldb/unittests/Expression/ValueMatcher.h +++ b/lldb/unittests/Expression/ValueMatcher.h @@ -15,10 +15,7 @@ #define LLDB_UNITTESTS_EXPRESSION_VALUEMATCHER_H #include "lldb/Core/Value.h" -#include "lldb/Utility/DataBufferHeap.h" #include "lldb/Utility/Scalar.h" -#include "llvm/ADT/ArrayRef.h" -#include "llvm/Support/Format.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Testing/Support/Error.h" #include "gtest/gtest.h" From 2c77e50684567331945a1ad8f8ccd30175bd53e2 Mon Sep 17 00:00:00 2001 From: David Peixotto Date: Tue, 11 Nov 2025 17:25:08 -0800 Subject: [PATCH 07/13] Cleanup initializer --- lldb/unittests/Expression/ValueMatcher.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lldb/unittests/Expression/ValueMatcher.h b/lldb/unittests/Expression/ValueMatcher.h index 818e0bd79088d..88c822c9d65e0 100644 --- a/lldb/unittests/Expression/ValueMatcher.h +++ b/lldb/unittests/Expression/ValueMatcher.h @@ -65,8 +65,8 @@ class ValueMatcher { private: Value::ValueType m_value_type = Value::ValueType::Invalid; Value::ContextType m_context_type = Value::ContextType::Invalid; - Scalar m_expected_scalar{}; - std::vector m_expected_bytes{}; + Scalar m_expected_scalar; + std::vector m_expected_bytes; bool MatchAndExplainImpl(const Value &val, llvm::raw_ostream &os) const; }; From fe89760e32b5c1010ef78e19fe6f0c930795cb1f Mon Sep 17 00:00:00 2001 From: David Peixotto Date: Tue, 11 Nov 2025 17:39:39 -0800 Subject: [PATCH 08/13] Use llvm::interleave --- lldb/unittests/Expression/ValueMatcher.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lldb/unittests/Expression/ValueMatcher.cpp b/lldb/unittests/Expression/ValueMatcher.cpp index bf2ba69120b80..53edd4326eb96 100644 --- a/lldb/unittests/Expression/ValueMatcher.cpp +++ b/lldb/unittests/Expression/ValueMatcher.cpp @@ -8,25 +8,30 @@ #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 buffer_data) { +static void FormatValueDetails(llvm::raw_ostream &os, + Value::ValueType value_type, + Value::ContextType context_type, + const Scalar &scalar, + llvm::ArrayRef 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=["; - for (size_t i = 0; i < std::min(buffer_data.size(), size_t(16)); ++i) { - if (i > 0) - os << " "; - os << llvm::format("%02x", static_cast(buffer_data[i])); - } + llvm::interleave( + bytes_to_print, + [&](uint8_t byte) { + os << llvm::format("%02x", static_cast(byte)); + }, + [&]() { os << " "; }); if (buffer_data.size() > 16) { os << " ..."; } From efe61d1dab6b0423b7e0eeea2216f26412642b9e Mon Sep 17 00:00:00 2001 From: David Peixotto Date: Tue, 11 Nov 2025 17:40:26 -0800 Subject: [PATCH 09/13] clang-format --- lldb/unittests/Expression/ValueMatcher.cpp | 45 +++++++++++++--------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/lldb/unittests/Expression/ValueMatcher.cpp b/lldb/unittests/Expression/ValueMatcher.cpp index 53edd4326eb96..8a288d8bf5ad4 100644 --- a/lldb/unittests/Expression/ValueMatcher.cpp +++ b/lldb/unittests/Expression/ValueMatcher.cpp @@ -124,38 +124,44 @@ void ValueMatcher::DescribeNegationTo(std::ostream *os) const { *os << "value does not match"; } -testing::Matcher lldb_private::MatchScalarValue(Value::ValueType value_type, - const Scalar &expected_scalar, - Value::ContextType context_type) { +testing::Matcher +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 lldb_private::MatchHostValue(Value::ValueType value_type, - const std::vector &expected_bytes, - Value::ContextType context_type) { +testing::Matcher +lldb_private::MatchHostValue(Value::ValueType value_type, + const std::vector &expected_bytes, + Value::ContextType context_type) { return ValueMatcher(value_type, expected_bytes, context_type); } -testing::Matcher lldb_private::IsScalar(const Scalar &expected_scalar, - Value::ContextType context_type) { +testing::Matcher +lldb_private::IsScalar(const Scalar &expected_scalar, + Value::ContextType context_type) { return MatchScalarValue(Value::ValueType::Scalar, expected_scalar, context_type); } -testing::Matcher lldb_private::IsLoadAddress(const Scalar &expected_address, - Value::ContextType context_type) { +testing::Matcher +lldb_private::IsLoadAddress(const Scalar &expected_address, + Value::ContextType context_type) { return MatchScalarValue(Value::ValueType::LoadAddress, expected_address, context_type); } -testing::Matcher lldb_private::IsFileAddress(const Scalar &expected_address, - Value::ContextType context_type) { +testing::Matcher +lldb_private::IsFileAddress(const Scalar &expected_address, + Value::ContextType context_type) { return MatchScalarValue(Value::ValueType::FileAddress, expected_address, context_type); } -testing::Matcher lldb_private::IsHostValue(const std::vector &expected_bytes, - Value::ContextType context_type) { +testing::Matcher +lldb_private::IsHostValue(const std::vector &expected_bytes, + Value::ContextType context_type) { return MatchHostValue(Value::ValueType::HostAddress, expected_bytes, context_type); } @@ -167,30 +173,31 @@ Scalar lldb_private::GetScalar(unsigned bits, uint64_t value, bool sign) { } llvm::detail::ValueMatchesPoly> -lldb_private::ExpectScalar(const Scalar &expected_scalar, Value::ContextType context_type) { +lldb_private::ExpectScalar(const Scalar &expected_scalar, + Value::ContextType context_type) { return llvm::HasValue(IsScalar(expected_scalar, context_type)); } llvm::detail::ValueMatchesPoly> lldb_private::ExpectScalar(unsigned bits, uint64_t value, bool sign, - Value::ContextType context_type) { + Value::ContextType context_type) { return ExpectScalar(GetScalar(bits, value, sign), context_type); } llvm::detail::ValueMatchesPoly> lldb_private::ExpectLoadAddress(const Scalar &expected_address, - Value::ContextType context_type) { + Value::ContextType context_type) { return llvm::HasValue(IsLoadAddress(expected_address, context_type)); } llvm::detail::ValueMatchesPoly> lldb_private::ExpectFileAddress(const Scalar &expected_address, - Value::ContextType context_type) { + Value::ContextType context_type) { return llvm::HasValue(IsFileAddress(expected_address, context_type)); } llvm::detail::ValueMatchesPoly> lldb_private::ExpectHostAddress(const std::vector &expected_bytes, - Value::ContextType context_type) { + Value::ContextType context_type) { return llvm::HasValue(IsHostValue(expected_bytes, context_type)); } From 006a07dbe37587d076e390c619b5a8f67a9718f0 Mon Sep 17 00:00:00 2001 From: David Peixotto Date: Wed, 12 Nov 2025 08:46:11 -0800 Subject: [PATCH 10/13] formatting --- lldb/unittests/Expression/ValueMatcher.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lldb/unittests/Expression/ValueMatcher.cpp b/lldb/unittests/Expression/ValueMatcher.cpp index 8a288d8bf5ad4..c8c6a1893b18c 100644 --- a/lldb/unittests/Expression/ValueMatcher.cpp +++ b/lldb/unittests/Expression/ValueMatcher.cpp @@ -32,9 +32,8 @@ static void FormatValueDetails(llvm::raw_ostream &os, os << llvm::format("%02x", static_cast(byte)); }, [&]() { os << " "; }); - if (buffer_data.size() > 16) { + if (buffer_data.size() > 16) os << " ..."; - } os << "] (" << buffer_data.size() << " bytes)"; } else { os << ", value=" << scalar; From 1d910e18de92412e47b2c751abc58996a1aafcd0 Mon Sep 17 00:00:00 2001 From: David Peixotto Date: Wed, 12 Nov 2025 13:35:06 -0800 Subject: [PATCH 11/13] Apply suggestions from code review Improve comments and update file header to match coding standards. Co-authored-by: Jonas Devlieghere --- lldb/unittests/Expression/ValueMatcher.cpp | 4 ++-- lldb/unittests/Expression/ValueMatcher.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lldb/unittests/Expression/ValueMatcher.cpp b/lldb/unittests/Expression/ValueMatcher.cpp index c8c6a1893b18c..bac2a3a6fe80b 100644 --- a/lldb/unittests/Expression/ValueMatcher.cpp +++ b/lldb/unittests/Expression/ValueMatcher.cpp @@ -1,4 +1,4 @@ -//===-- ValueMatcher.cpp --------------------------------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -97,7 +97,7 @@ bool ValueMatcher::MatchAndExplainImpl(const Value &val, } } } else { - // For Scalar, FileAddress, and LoadAddress - compare m_value + // 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 " diff --git a/lldb/unittests/Expression/ValueMatcher.h b/lldb/unittests/Expression/ValueMatcher.h index 88c822c9d65e0..824f9ea6c186e 100644 --- a/lldb/unittests/Expression/ValueMatcher.h +++ b/lldb/unittests/Expression/ValueMatcher.h @@ -1,4 +1,4 @@ -//===-- ValueMatcher.h ----------------------------------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. From 6c720553b9a7dfaeed1f13fc2e7586b15b972840 Mon Sep 17 00:00:00 2001 From: David Peixotto Date: Wed, 12 Nov 2025 14:19:32 -0800 Subject: [PATCH 12/13] Add link to matcher guide --- lldb/unittests/Expression/ValueMatcher.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lldb/unittests/Expression/ValueMatcher.h b/lldb/unittests/Expression/ValueMatcher.h index 824f9ea6c186e..7fadc7205a431 100644 --- a/lldb/unittests/Expression/ValueMatcher.h +++ b/lldb/unittests/Expression/ValueMatcher.h @@ -9,6 +9,9 @@ /// This file contains the definition of the ValueMatcher class which is a used /// to match lldb_private::Value in gtest assert/expect macros. It also contains /// several helper functions to create matchers for common Value types. +/// +/// The ValueMatcher class was created using the gtest guide found here: +// https://google.github.io/googletest/gmock_cook_book.html#writing-new-monomorphic-matchers //===----------------------------------------------------------------------===// #ifndef LLDB_UNITTESTS_EXPRESSION_VALUEMATCHER_H From 39c7b40211fba02d6dfbb70178067041f5fcc349 Mon Sep 17 00:00:00 2001 From: David Peixotto Date: Wed, 12 Nov 2025 14:21:22 -0800 Subject: [PATCH 13/13] Use std::ostream as gtest interface and add comment --- lldb/unittests/Expression/ValueMatcher.cpp | 11 +++++++---- lldb/unittests/Expression/ValueMatcher.h | 3 +-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lldb/unittests/Expression/ValueMatcher.cpp b/lldb/unittests/Expression/ValueMatcher.cpp index bac2a3a6fe80b..ee7ccaebabd64 100644 --- a/lldb/unittests/Expression/ValueMatcher.cpp +++ b/lldb/unittests/Expression/ValueMatcher.cpp @@ -50,10 +50,10 @@ void lldb_private::PrintTo(const Value &val, std::ostream *os) { val.GetScalar(), val.GetBuffer().GetData()); } -bool ValueMatcher::MatchAndExplain( - const Value &val, testing::MatchResultListener *listener) const { - if (listener && listener->stream()) { - llvm::raw_os_ostream os(*listener->stream()); +bool ValueMatcher::MatchAndExplain(const Value &val, + std::ostream *stream) const { + if (stream) { + llvm::raw_os_ostream os(*stream); return MatchAndExplainImpl(val, os); } @@ -61,6 +61,9 @@ bool ValueMatcher::MatchAndExplain( 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) { diff --git a/lldb/unittests/Expression/ValueMatcher.h b/lldb/unittests/Expression/ValueMatcher.h index 7fadc7205a431..3ca7b15e1d3c8 100644 --- a/lldb/unittests/Expression/ValueMatcher.h +++ b/lldb/unittests/Expression/ValueMatcher.h @@ -58,8 +58,7 @@ class ValueMatcher { // Typedef to hook into the gtest matcher machinery. using is_gtest_matcher = void; - bool MatchAndExplain(const Value &val, - testing::MatchResultListener *listener) const; + bool MatchAndExplain(const Value &val, std::ostream *os) const; void DescribeTo(std::ostream *os) const;