Skip to content

Commit 74d3472

Browse files
motiz88meta-codesync[bot]
authored andcommitted
Better source locations for EXPECT_CALL failures (facebook#54129)
Summary: Pull Request resolved: facebook#54129 Changelog: [Internal] The team maintaining gmock is [uninterested, as a matter of principle,](google/googletest#2646 (comment)) in improving the ergonomics of writing helper functions around `EXPECT_CALL`. However, such helpers have proven very useful for the `jsinspector-modern` C++ test suite. The out-of-the-box ergonomics *are* pretty bad, though, so this diff tries to improve the situation. The biggest offender in our test suite is the `expectMessageFromPage` helper function - if a test fails because an expectation isn't met, the error message points to the `EXPECT_CALL` line inside `expectMessageFromPage`, which is utterly useless compared to the line of test code that *called* `expectMessageFromPage`. Here, we reach into gmock's internals slightly to create a variant of the `EXPECT_CALL` macro that allows passing in a [`std::source_location`](https://en.cppreference.com/w/cpp/utility/source_location.html) (thanks, C++20!). We then teach `expectMessageFromPage` and other such helpers to capture a source location at the call site (using a parameter with a default value to `source_location::current()`) and use the modified macro to pass it into gmock. Reviewed By: huntie Differential Revision: D84368993 fbshipit-source-id: 62f64b6c5f626a54bc3da7143d84ee2d75d16ac6
1 parent f7cc2e5 commit 74d3472

File tree

3 files changed

+71
-22
lines changed

3 files changed

+71
-22
lines changed

packages/react-native/ReactCommon/jsinspector-modern/tests/ConsoleApiTest.cpp

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,13 @@ class ConsoleApiTest : public JsiIntegrationPortableTestBase<
9090
* Expect a console API call to be reported with parameters matching \param
9191
* paramsMatcher.
9292
*/
93-
void expectConsoleApiCall(Matcher<folly::dynamic> paramsMatcher) {
93+
void expectConsoleApiCall(
94+
Matcher<folly::dynamic> paramsMatcher,
95+
std::source_location location = std::source_location::current()) {
9496
if (runtimeEnabled_) {
95-
expectConsoleApiCallImpl(std::move(paramsMatcher));
97+
expectConsoleApiCallImpl(std::move(paramsMatcher), location);
9698
} else {
97-
expectedConsoleApiCalls_.emplace_back(paramsMatcher);
99+
expectedConsoleApiCalls_.emplace_back(paramsMatcher, location);
98100
}
99101
}
100102

@@ -103,9 +105,11 @@ class ConsoleApiTest : public JsiIntegrationPortableTestBase<
103105
* paramsMatcher, only if the Runtime domain is currently enabled ( = the call
104106
* is reported in real time).
105107
*/
106-
void expectConsoleApiCallImmediate(Matcher<folly::dynamic> paramsMatcher) {
108+
void expectConsoleApiCallImmediate(
109+
Matcher<folly::dynamic> paramsMatcher,
110+
std::source_location location = std::source_location::current()) {
107111
if (runtimeEnabled_) {
108-
expectConsoleApiCallImpl(std::move(paramsMatcher));
112+
expectConsoleApiCallImpl(std::move(paramsMatcher), location);
109113
}
110114
}
111115

@@ -115,9 +119,10 @@ class ConsoleApiTest : public JsiIntegrationPortableTestBase<
115119
* call will be buffered and reported later upon enabling the domain).
116120
*/
117121
void expectConsoleApiCallBuffered(
118-
const Matcher<folly::dynamic>& paramsMatcher) {
122+
const Matcher<folly::dynamic>& paramsMatcher,
123+
std::source_location location = std::source_location::current()) {
119124
if (!runtimeEnabled_) {
120-
expectedConsoleApiCalls_.emplace_back(paramsMatcher);
125+
expectedConsoleApiCalls_.emplace_back(paramsMatcher, location);
121126
}
122127
}
123128

@@ -145,19 +150,23 @@ class ConsoleApiTest : public JsiIntegrationPortableTestBase<
145150
return it->second;
146151
}
147152

148-
void expectConsoleApiCallImpl(Matcher<folly::dynamic> paramsMatcher) {
149-
this->expectMessageFromPage(JsonParsed(AllOf(
150-
AtJsonPtr("/method", "Runtime.consoleAPICalled"),
151-
AtJsonPtr("/params", std::move(paramsMatcher)))));
153+
void expectConsoleApiCallImpl(
154+
Matcher<folly::dynamic> paramsMatcher,
155+
std::source_location location) {
156+
this->expectMessageFromPage(
157+
JsonParsed(AllOf(
158+
AtJsonPtr("/method", "Runtime.consoleAPICalled"),
159+
AtJsonPtr("/params", std::move(paramsMatcher)))),
160+
location);
152161
}
153162

154163
void enableRuntimeDomain() {
155164
InSequence s;
156165
auto executionContextInfo = this->expectMessageFromPage(JsonParsed(
157166
AllOf(AtJsonPtr("/method", "Runtime.executionContextCreated"))));
158167
if (!runtimeEnabled_) {
159-
for (auto& call : expectedConsoleApiCalls_) {
160-
expectConsoleApiCallImpl(call);
168+
for (auto& [call, location] : expectedConsoleApiCalls_) {
169+
expectConsoleApiCallImpl(call, location);
161170
}
162171
expectedConsoleApiCalls_.clear();
163172
}
@@ -200,7 +209,8 @@ class ConsoleApiTest : public JsiIntegrationPortableTestBase<
200209
}
201210
}
202211

203-
std::vector<Matcher<folly::dynamic>> expectedConsoleApiCalls_;
212+
std::vector<std::pair<Matcher<folly::dynamic>, std::source_location>>
213+
expectedConsoleApiCalls_;
204214
bool runtimeEnabled_{false};
205215
std::unordered_map<std::string, std::string> scriptUrlsById_;
206216
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include <gmock/gmock.h>
9+
10+
#pragma once
11+
12+
/**
13+
* A variant of GMOCK_ON_CALL_IMPL that allows specifying the source location as
14+
* a std::source_location parameter.
15+
*/
16+
#define GMOCK_ON_CALL_WITH_SOURCE_LOCATION_IMPL_( \
17+
location, mock_expr, Setter, call) \
18+
((mock_expr).gmock_##call)( \
19+
::testing::internal::GetWithoutMatchers(), nullptr) \
20+
.Setter((location).file_name(), (location).line(), #mock_expr, #call)
21+
22+
/**
23+
* A variant of EXPECT_CALL that allows specifying the source location as a
24+
* std::source_location parameter;
25+
*/
26+
#define EXPECT_CALL_WITH_SOURCE_LOCATION(location, obj, call) \
27+
GMOCK_ON_CALL_WITH_SOURCE_LOCATION_IMPL_( \
28+
location, obj, InternalExpectedAt, call)

packages/react-native/ReactCommon/jsinspector-modern/tests/JsiIntegrationTest.h

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
#include <jsinspector-modern/InspectorInterfaces.h>
1717

1818
#include <memory>
19+
#include <source_location>
1920

2021
#include "FollyDynamicMatchers.h"
22+
#include "GmockHelpers.h"
2123
#include "InspectorMocks.h"
2224
#include "UniquePtrFactory.h"
2325
#include "utils/InspectorFlagOverridesGuard.h"
@@ -88,13 +90,15 @@ class JsiIntegrationPortableTestBase : public ::testing::Test,
8890
*/
8991
virtual void setupRuntimeBeforeRegistration(jsi::Runtime& /*runtime*/) {}
9092

91-
void connect() {
93+
void connect(
94+
std::source_location location = std::source_location::current()) {
9295
ASSERT_FALSE(toPage_) << "Can only connect once in a JSI integration test.";
9396
toPage_ = page_->connect(remoteConnections_.make_unique());
9497

9598
using namespace ::testing;
9699
// Default to ignoring console messages originating inside the backend.
97-
EXPECT_CALL(
100+
EXPECT_CALL_WITH_SOURCE_LOCATION(
101+
location,
98102
fromPage(),
99103
onMessage(JsonParsed(AllOf(
100104
AtJsonPtr("/method", "Runtime.consoleAPICalled"),
@@ -103,7 +107,8 @@ class JsiIntegrationPortableTestBase : public ::testing::Test,
103107

104108
// We'll always get an onDisconnect call when we tear
105109
// down the test. Expect it in order to satisfy the strict mock.
106-
EXPECT_CALL(*remoteConnections_[0], onDisconnect());
110+
EXPECT_CALL_WITH_SOURCE_LOCATION(
111+
location, *remoteConnections_[0], onDisconnect());
107112
}
108113

109114
void reload() {
@@ -146,10 +151,13 @@ class JsiIntegrationPortableTestBase : public ::testing::Test,
146151
*/
147152
template <typename Matcher>
148153
std::shared_ptr<const std::optional<folly::dynamic>> expectMessageFromPage(
149-
Matcher&& matcher) {
154+
Matcher&& matcher,
155+
std::source_location location = std::source_location::current()) {
156+
using namespace ::testing;
157+
ScopedTrace scope(location.file_name(), location.line(), "");
150158
std::shared_ptr result =
151159
std::make_shared<std::optional<folly::dynamic>>(std::nullopt);
152-
EXPECT_CALL(fromPage(), onMessage(matcher))
160+
EXPECT_CALL_WITH_SOURCE_LOCATION(location, fromPage(), onMessage(matcher))
153161
.WillOnce(
154162
([result](auto message) { *result = folly::parseJson(message); }))
155163
.RetiresOnSaturation();
@@ -188,15 +196,17 @@ class JsiIntegrationPortableTestBase : public ::testing::Test,
188196
JsiIntegrationPortableTestBase<EngineAdapter, Executor>& test_;
189197
};
190198

191-
SecondaryConnection connectSecondary() {
199+
SecondaryConnection connectSecondary(
200+
std::source_location location = std::source_location::current()) {
192201
auto toPage = page_->connect(remoteConnections_.make_unique());
193202

194203
SecondaryConnection secondary{
195204
std::move(toPage), *this, remoteConnections_.objectsVended() - 1};
196205

197206
using namespace ::testing;
198207
// Default to ignoring console messages originating inside the backend.
199-
EXPECT_CALL(
208+
EXPECT_CALL_WITH_SOURCE_LOCATION(
209+
location,
200210
secondary.fromPage(),
201211
onMessage(JsonParsed(AllOf(
202212
AtJsonPtr("/method", "Runtime.consoleAPICalled"),
@@ -205,7 +215,8 @@ class JsiIntegrationPortableTestBase : public ::testing::Test,
205215

206216
// We'll always get an onDisconnect call when we tear
207217
// down the test. Expect it in order to satisfy the strict mock.
208-
EXPECT_CALL(secondary.fromPage(), onDisconnect());
218+
EXPECT_CALL_WITH_SOURCE_LOCATION(
219+
location, secondary.fromPage(), onDisconnect());
209220

210221
return secondary;
211222
}

0 commit comments

Comments
 (0)