blob: 71dfd96e1d2a9ca84cc901438ef08d67ea7591d6 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// A compilation of helpers utilities to support running and validating the compatibility tests.
#ifndef SRC_TESTS_FIDL_COMPATIBILITY_HELPERS_H_
#define SRC_TESTS_FIDL_COMPATIBILITY_HELPERS_H_
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/default.h>
#include <lib/sys/component/cpp/testing/realm_builder.h>
#include <lib/syslog/cpp/macros.h>
#include <map>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include <re2/re2.h>
#include <src/lib/fxl/strings/utf_codecs.h>
#include <src/tests/fidl/compatibility/hlcpp_client_app.h>
using fidl::test::compatibility::this_is_a_struct;
using fidl::test::compatibility::this_is_a_table;
using fidl::test::compatibility::this_is_a_union;
using fidl::test::compatibility::this_is_a_xunion;
namespace fidl_test_compatibility_helpers {
// Want a size small enough that it doesn't get too big to transmit but
// large enough to exercise interesting code paths.
constexpr uint8_t kArbitraryVectorSize = 3;
// This is used as a literal constant in compatibility_test_service.fidl.
constexpr uint8_t kArbitraryConstant = 2;
// A predicate function that returns true if the specified proxy + server pair should be tested for
// a given test run.
using AllowImplPair =
std::function<bool(const std::string& proxy_url, const std::string& server_url)>;
// A simple list of implementations to be tested.
using Impls = std::vector<std::string>;
// A summary of findings to be printed as human-readable output.
using Summary = std::map<std::string, bool>;
// A test setup and executing function.
using TestBody = std::function<void(async::Loop& loop, fidl::test::compatibility::EchoPtr& proxy,
const std::string& server_url, const std::string& proxy_url)>;
// Returns an |AllowImplPair| predicate that returns false if ANY of the provided list of
// substrings is found in the implementation list.
AllowImplPair Exclude(std::initializer_list<const char*> substrings);
// Get the short name of the language binding being tested, like "rust" or "cpp".
std::string ExtractShortName(const std::string& pkg_url);
// Run a test for all possible proxy + server combinations.
void ForAllImpls(const Impls& impls, const TestBody& body);
// Only test some proxy + server combinations, using an |AllowImplPair| predicate function to
// determine whether or not the particular proxy + server combination should be executed.
void ForSomeImpls(const Impls& impls, const AllowImplPair& allow, const TestBody& body);
// Parse the input args to build a list of binding implementations being tested. Returns false if no
// viable implementation names are found in the passed in command line arguments.
bool GetImplsUnderTest(Impls* out_impls);
// Mint a simple handle for test-case building purposes.
zx::handle Handle();
// Compare two handles for equality.
::testing::AssertionResult HandlesEq(const zx::object_base& a, const zx::object_base& b);
// Prints a summary of the tests performed, and their results, to the terminal.
void PrintSummary(const Summary& summary);
// Random UTF8 string generator, with a byte (not character!) length of |count|.
std::string RandomUTF8(size_t count, std::default_random_engine& rand_engine);
// A generic class for generating random data for a FIDL type.
class DataGenerator {
public:
explicit DataGenerator(int seed) : rand_engine_(seed) {}
template <typename T>
T choose(T a, T b) {
return next<bool>() ? a : b;
}
// Note: uniform_int_distribution is undefined behavior for integral types
// smaller than short.
template <typename T>
struct UniformDistType {
using Type = T;
};
template <>
struct UniformDistType<bool> {
using Type = uint16_t;
};
template <>
struct UniformDistType<uint8_t> {
using Type = uint16_t;
};
template <>
struct UniformDistType<int8_t> {
using Type = int16_t;
};
template <typename T>
std::enable_if_t<std::is_integral_v<T>, T> next() {
return static_cast<T>(std::uniform_int_distribution<typename UniformDistType<T>::Type>(
0, std::numeric_limits<T>::max())(rand_engine_));
}
template <typename T>
std::enable_if_t<std::is_floating_point_v<T>, T> next() {
return std::uniform_real_distribution<T>{}(rand_engine_);
}
template <typename T>
std::enable_if_t<std::is_same_v<T, std::string>, T> next(size_t count = kArbitraryConstant) {
std::string random_string;
random_string.reserve(count);
do {
// Generate a random 32 bit unsigned int to use a the code point.
uint32_t code_point = next<uint32_t>();
// Mask the random number so that it can be encoded into the number of
// bytes remaining.
size_t remaining = count - random_string.size();
if (remaining == 1) {
code_point &= 0x7F;
} else if (remaining == 2) {
code_point &= 0x7FF;
} else if (remaining == 3) {
code_point &= 0xFFFF;
} else {
// Mask to fall within the general range of code points.
code_point &= 0x1FFFFF;
}
// Check that it's really a valid code point, otherwise try again.
if (!fxl::IsValidCodepoint(code_point)) {
continue;
}
// Add the character to the random string.
fxl::WriteUnicodeCharacter(code_point, &random_string);
FX_CHECK(random_string.size() <= count);
} while (random_string.size() < count);
return random_string;
}
template <typename T>
std::enable_if_t<std::is_same_v<T, fidl::StringPtr>, T> next(size_t count = kArbitraryConstant) {
return nullable<fidl::StringPtr>(fidl::StringPtr(), [this, count]() -> fidl::StringPtr {
return fidl::StringPtr(next<std::string>(count));
});
}
template <typename T>
std::enable_if_t<std::is_same_v<T, zx::handle>, T> next(bool nullable = false) {
if (!nullable || next<bool>()) {
zx_handle_t raw_event;
const zx_status_t status = zx_event_create(0u, &raw_event);
// Can't use gtest ASSERT_EQ because we're in a non-void function.
ZX_ASSERT_MSG(status == ZX_OK, "status = %d", status);
return zx::handle(raw_event);
}
return {};
}
template <typename T>
std::enable_if_t<std::is_same_v<T, this_is_a_struct>, T> next() {
this_is_a_struct value{};
value.s = next<std::string>();
return value;
}
template <typename T>
std::enable_if_t<std::is_same_v<T, std::unique_ptr<this_is_a_struct>>, T> next() {
return nullable<std::unique_ptr<this_is_a_struct>>(
nullptr, [this]() { return std::make_unique<this_is_a_struct>(next<this_is_a_struct>()); });
}
template <typename T>
std::enable_if_t<std::is_same_v<T, this_is_a_table>, T> next() {
this_is_a_table value{};
value.set_s(next<std::string>());
return value;
}
template <typename T>
std::enable_if_t<std::is_same_v<T, this_is_a_union>, T> next() {
this_is_a_union value{};
if (next<bool>()) {
value.set_b(next<bool>());
} else {
value.set_s(next<std::string>());
}
return value;
}
template <typename T>
std::enable_if_t<std::is_same_v<T, std::unique_ptr<this_is_a_union>>, T> next() {
return nullable<std::unique_ptr<this_is_a_union>>(
nullptr, [this]() { return std::make_unique<this_is_a_union>(next<this_is_a_union>()); });
}
template <typename T>
std::enable_if_t<std::is_same_v<T, this_is_a_xunion>, T> next() {
this_is_a_xunion value{};
if (next<bool>()) {
value.set_b(next<bool>());
} else {
value.set_s(next<std::string>());
}
return value;
}
private:
std::default_random_engine rand_engine_;
template <typename T>
T nullable(T null_value, std::function<T(void)> generate_value) {
if (next<bool>()) {
return generate_value();
}
return null_value;
}
};
} // namespace fidl_test_compatibility_helpers
#endif // SRC_TESTS_FIDL_COMPATIBILITY_HELPERS_H_