File: interactive_test_definitions.h

package info (click to toggle)
chromium 138.0.7204.183-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,908 kB
  • sloc: cpp: 34,937,088; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (296 lines) | stat: -rw-r--r-- 11,226 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef UI_BASE_INTERACTION_INTERACTIVE_TEST_DEFINITIONS_H_
#define UI_BASE_INTERACTION_INTERACTIVE_TEST_DEFINITIONS_H_

#include <type_traits>
#include <utility>

#include "base/functional/callback_helpers.h"
#include "base/test/bind.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/interaction/interaction_sequence.h"

namespace ui::test::internal {

// Specifies an element either by ID or by name.
using ElementSpecifier = std::variant<ElementIdentifier, std::string_view>;

// Specifies a sequence of steps.
using MultiStep = std::vector<InteractionSequence::StepBuilder>;

// Similar to `std::invocable<T, Args...>`, but does not put constraints on the
// parameters passed to the invocation method.
template <typename T>
concept IsCallable = requires { &std::decay_t<T>::operator(); };

// Applies if `T` has bound state (such as a lambda expression with captures).
template <typename T>
concept HasState = !std::is_empty_v<std::remove_reference_t<T>>;

// Helper for matching a function pointer.
template <typename T>
inline constexpr bool IsFunctionPointerValue = false;

template <typename R, typename... Args>
inline constexpr bool IsFunctionPointerValue<R (*)(Args...)> = true;

// Applies if `T` is a function pointer (but not a pointer to an instance
// member function).
template <typename T>
concept IsFunctionPointer = IsFunctionPointerValue<T>;

// Optionally converts `function` to something that is compatible with a
// base::OnceCallback.
template <typename F>
auto MaybeBind(F&& function) {
  if constexpr (base::IsBaseCallback<F>) {
    // Callbacks are already callbacks, so can be returned as-is.
    return std::forward<F>(function);
  } else if constexpr (IsCallable<F> && HasState<F>) {
    // Callable objects with state can only be bound with
    // `base::BindLambdaForTesting`.
    return base::BindLambdaForTesting(std::forward<F>(function));
  } else if constexpr ((IsCallable<F> && !HasState<F>) ||
                       IsFunctionPointer<F>) {
    // Function pointers and empty callable objects can be bound using
    // `base::BindOnce`.
    return base::BindOnce(std::forward<F>(function));
  } else if constexpr (std::same_as<F, decltype(base::DoNothing())>) {
    // base::DoNothing() is compatible with callbacks, so return it as-is.
    return function;
  } else {
    static_assert(base::AlwaysFalse<F>, "Can only bind callable objects.");
  }
}

// Helper struct that captures information about what signature a function-like
// object would have if it were bound.
template <typename F>
struct MaybeBindTypeHelper {
  using CallbackType = std::invoke_result_t<decltype(&MaybeBind<F>), F>;
  using ReturnType = typename CallbackType::ResultType;
  using Signature = typename CallbackType::RunType;
};

// DoNothing always has a void return type but no defined signature.
template <>
struct MaybeBindTypeHelper<decltype(base::DoNothing())> {
  using ReturnType = void;
};

// Optionally converts `function` to something that is compatible with a
// base::RepeatingCallback, or returns it as-is if it's already a callback.
template <typename F>
base::RepeatingCallback<typename MaybeBindTypeHelper<F>::Signature>
MaybeBindRepeating(F&& function) {
  if constexpr (IsCallable<F> && !HasState<F> &&
                std::copy_constructible<std::decay_t<F>>) {
    return base::BindRepeating(std::forward<F>(function));
  } else {
    return MaybeBind(std::forward<F>(function));
  }
}

template <typename T>
struct ArgsExtractor;

template <typename R, typename... Args>
struct ArgsExtractor<R(Args...)> {
  using Holder = std::tuple<Args...>;
};

template <typename F>
using ReturnTypeOf = MaybeBindTypeHelper<F>::ReturnType;

template <size_t N, typename F>
using NthArgumentOf = std::tuple_element_t<
    N,
    typename ArgsExtractor<typename MaybeBindTypeHelper<F>::Signature>::Holder>;

// Requires that `F` resolves to some kind of callable object with call
// signature `S`.
template <typename F, typename S>
concept HasSignature =
    std::same_as<typename MaybeBindTypeHelper<F>::Signature, S> ||
    std::same_as<F, decltype(base::DoNothing())>;

// Helper for `HasCompatibleSignature`; see recursive implementation below.
template <typename F, typename S>
inline constexpr bool HasCompatibleSignatureValue = false;

// Requires that `F` resolves to some kind of callable object whose signature
// can be rectified to `S`; see `base::RectifyCallback` for more information.
// (Basically, `F` can omit arguments from the left of `S`; these arguments
// will be ignored.)
template <typename F, typename S>
concept HasCompatibleSignature = HasCompatibleSignatureValue<F, S>;

// This is the leaf state for the recursive compatibility computation; see
// below.
template <typename F, typename R>
  requires HasSignature<F, R()>
inline constexpr bool HasCompatibleSignatureValue<F, R()> = true;

// Implementation for `HasCompatibleSignature`.
//
// This removes arguments one by one from the left of the target signature `S`
// to see if `F` has that signature. The recursion stops when one matches, or
// when the arg list is empty (in which case the leaf state is hit, above).
template <typename F, typename R, typename A, typename... Args>
  requires HasSignature<F, R(A, Args...)> ||
               HasCompatibleSignature<F, R(Args...)>
inline constexpr bool HasCompatibleSignatureValue<F, R(A, Args...)> = true;

// Checks that `T` is a reference wrapper around any type.
template <typename T>
concept IsReferenceWrapper = base::is_instantiation<std::reference_wrapper, T>;

// Helper to determine the type used to match a value. The default is to just
// use the decayed value type.
template <typename T>
struct MatcherTypeHelper {
  using ActualType = T;
};

// Specialization for string types used in Chrome. For any representation of a
// string using character type, the type used for matching is the corresponding
// `std::basic_string`.
//
// Add to this template if different character formats become supported (e.g.
// char8_t, char32_t, wchar_t, etc.)
template <typename C>
  requires(std::same_as<std::remove_const_t<C>, char> ||
           std::same_as<std::remove_const_t<C>, char16_t>)
struct MatcherTypeHelper<C*> {
  using ActualType = std::basic_string<std::remove_const_t<C>>;
};

// Map string_view-like types to strings to avoid storing dangling references in
// matchers.
template <typename CharT, typename Traits>
struct MatcherTypeHelper<std::basic_string_view<CharT, Traits>> {
  using ActualType = std::basic_string<CharT, Traits>;
};

// Gets the appropriate matchable type for `T`. This affects string-like types
// (e.g. `const char*`) as the corresponding `Matcher` should match a
// `std::string` or `std::u16string`.
template <typename T>
using MatcherTypeFor = MatcherTypeHelper<std::decay_t<T>>::ActualType;

// Determines if `T` is a valid type to be used in a matcher. This precludes
// string-like types (const char*, constexpr char16_t[], etc.) in favor of
// `std::string` and `std::u16string`.
template <typename T>
concept IsValidMatcherType = std::same_as<T, MatcherTypeFor<T>>;

template <typename T>
concept IsGtestMatcher = requires { typename T::is_gtest_matcher; };

template <typename T>
concept HasMatchAndExplain = requires { &T::MatchAndExplain; };

template <typename T>
concept IsMatcher = IsGtestMatcher<T> || HasMatchAndExplain<T> ||
                    base::is_instantiation<testing::PolymorphicMatcher, T>;

// Accepts any function-like object that is compatible with
// `InteractionSequence::StepCallback`.
template <typename F>
concept IsStepCallback = internal::
    HasCompatibleSignature<F, void(InteractionSequence*, TrackedElement*)>;

// Accepts any function-like object that can be used with `Check()` and
// `CheckResult()`.
template <typename F, typename R>
concept IsCheckCallback =
    internal::HasCompatibleSignature<F,
                                     R(const InteractionSequence*,
                                       const TrackedElement*)>;

// Converts an ElementSpecifier to an element ID or name and sets it onto
// `builder`.
void SpecifyElement(ui::InteractionSequence::StepBuilder& builder,
                    ElementSpecifier element);

std::string DescribeElement(ElementSpecifier spec);

InteractionSequence::Builder BuildSubsequence(MultiStep steps);

// Takes an argument expected to be a literal value and retrieves the literal
// value by either calling the object (if it's callable), unwrapping it (if it's
// a `std::reference_wrapper`) or just returning it otherwise.
//
// This allows e.g. passing deferred or computed values to the `Log()` verb.
template <typename Arg>
auto UnwrapArgument(Arg arg) {
  if constexpr (base::IsBaseCallback<Arg>) {
    return std::move(arg).Run();
  } else if constexpr (internal::IsFunctionPointer<Arg>) {
    return (*arg)();
  } else if constexpr (internal::IsCallable<Arg>) {
    return arg();
  } else {
    return arg;
  }
}

// Converts value of type U to testing::Matcher<T>.
template <typename T, typename U>
testing::Matcher<T> CreateMatcherFromValue(U value) {
  if constexpr (internal::IsReferenceWrapper<U>) {
    return testing::Matcher<T>(T(value.get()));
  } else if constexpr (std::derived_from<U, testing::Matcher<T>> ||
                       std::convertible_to<U, testing::Matcher<T>>) {
    // Note that a Matcher<T> is actually a wrapper around a "matcher"
    // object, not a matcher itself.
    return value;
  } else if constexpr (internal::IsMatcher<U>) {
    // Need to wrap the "matcher" in a Matcher<T> for it to be used.
    return testing::Matcher<T>(value);
  } else {
    return testing::Matcher<T>(
        T(internal::UnwrapArgument<U>(std::move(value))));
  }
}

// Serves as a strongly-typed wrapper around a MultiStep that carries additional
// semantic or syntactic information; for example the "Then" and "Else"
// sequences of an `If()`.
//
// This object is move-constructible and move-assignable, but only to blocks of
// the same type `T`. This prevents a user from writing `Else()` where a
// `ThenBlock` is expected.
//
// Do not use directly. To create a block type for use in InteractiveTest
// primitives, see `DeclareStepBlockFactory()`, which also creates an
// appropriately-named factory method.
template <typename T>
class StepBlock {
 public:
  StepBlock();
  explicit StepBlock(MultiStep steps) : steps_(std::move(steps)) {}
  StepBlock(StepBlock<T>&& other) noexcept = default;
  StepBlock& operator=(StepBlock<T>&& other) noexcept = default;
  ~StepBlock() = default;

  MultiStep& steps() { return steps_; }
  const MultiStep& steps() const { return steps_; }

 private:
  MultiStep steps_;
};

// Explicitly exclude lvalue references when calling certain methods. These
// methods would not compile anyway, but this concept is provided as a courtesy
// to generate better compile errors.
template <typename T>
concept IsValueOrRvalue = !std::is_lvalue_reference_v<T>;

}  // namespace ui::test::internal

#endif  // UI_BASE_INTERACTION_INTERACTIVE_TEST_DEFINITIONS_H_