[experiments] A unit test for the experiments framework (#33334)
Adds a test for the experiments codegen. It updates the codegen to parse test_experiments.yaml and test_experiments_rollouts.yaml files and generate test_experiments.h and test_experiments.cc files along with an experiments_test.cc file. The experiments test verifies the returned value of IsExperimentEnabled with the expected value.
This commit is contained in:
parent
278978d6f0
commit
bf3ffcf600
|
|
@ -968,6 +968,7 @@ if(gRPC_BUILD_TESTS)
|
|||
endif()
|
||||
add_dependencies(buildtests_cxx exception_test)
|
||||
add_dependencies(buildtests_cxx exec_ctx_wakeup_scheduler_test)
|
||||
add_dependencies(buildtests_cxx experiments_test)
|
||||
add_dependencies(buildtests_cxx factory_test)
|
||||
add_dependencies(buildtests_cxx fake_binder_test)
|
||||
add_dependencies(buildtests_cxx fake_resolver_test)
|
||||
|
|
@ -11403,6 +11404,44 @@ target_link_libraries(exec_ctx_wakeup_scheduler_test
|
|||
)
|
||||
|
||||
|
||||
endif()
|
||||
if(gRPC_BUILD_TESTS)
|
||||
|
||||
add_executable(experiments_test
|
||||
test/core/experiments/experiments_test.cc
|
||||
test/core/experiments/fixtures/experiments.cc
|
||||
third_party/googletest/googletest/src/gtest-all.cc
|
||||
third_party/googletest/googlemock/src/gmock-all.cc
|
||||
)
|
||||
target_compile_features(experiments_test PUBLIC cxx_std_14)
|
||||
target_include_directories(experiments_test
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
|
||||
${_gRPC_RE2_INCLUDE_DIR}
|
||||
${_gRPC_SSL_INCLUDE_DIR}
|
||||
${_gRPC_UPB_GENERATED_DIR}
|
||||
${_gRPC_UPB_GRPC_GENERATED_DIR}
|
||||
${_gRPC_UPB_INCLUDE_DIR}
|
||||
${_gRPC_XXHASH_INCLUDE_DIR}
|
||||
${_gRPC_ZLIB_INCLUDE_DIR}
|
||||
third_party/googletest/googletest/include
|
||||
third_party/googletest/googletest
|
||||
third_party/googletest/googlemock/include
|
||||
third_party/googletest/googlemock
|
||||
${_gRPC_PROTO_GENS_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(experiments_test
|
||||
${_gRPC_BASELIB_LIBRARIES}
|
||||
${_gRPC_PROTOBUF_LIBRARIES}
|
||||
${_gRPC_ZLIB_LIBRARIES}
|
||||
${_gRPC_ALLTARGETS_LIBRARIES}
|
||||
grpc_test_util
|
||||
)
|
||||
|
||||
|
||||
endif()
|
||||
if(gRPC_BUILD_TESTS)
|
||||
|
||||
|
|
|
|||
|
|
@ -7409,6 +7409,18 @@ targets:
|
|||
- gpr
|
||||
- upb
|
||||
uses_polling: false
|
||||
- name: experiments_test
|
||||
gtest: true
|
||||
build: test
|
||||
language: c++
|
||||
headers:
|
||||
- test/core/experiments/fixtures/experiments.h
|
||||
src:
|
||||
- test/core/experiments/experiments_test.cc
|
||||
- test/core/experiments/fixtures/experiments.cc
|
||||
deps:
|
||||
- grpc_test_util
|
||||
uses_polling: false
|
||||
- name: factory_test
|
||||
gtest: true
|
||||
build: test
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ grpc_cc_library(
|
|||
"absl/strings",
|
||||
],
|
||||
language = "c++",
|
||||
tags = ["nofixdeps"],
|
||||
deps = [
|
||||
"no_destruct",
|
||||
"//:config_vars",
|
||||
|
|
|
|||
|
|
@ -54,6 +54,31 @@ std::atomic<bool> g_loaded(false);
|
|||
absl::AnyInvocable<bool(struct ExperimentMetadata)>* g_check_constraints_cb =
|
||||
nullptr;
|
||||
|
||||
class TestExperiments {
|
||||
public:
|
||||
TestExperiments(const ExperimentMetadata* experiment_metadata,
|
||||
size_t num_experiments) {
|
||||
enabled_ = new bool[num_experiments];
|
||||
for (size_t i = 0; i < num_experiments; i++) {
|
||||
if (g_check_constraints_cb != nullptr) {
|
||||
enabled_[i] = (*g_check_constraints_cb)(experiment_metadata[i]);
|
||||
} else {
|
||||
enabled_[i] = experiment_metadata[i].default_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Overloading [] operator to access elements in array style
|
||||
bool operator[](int index) { return enabled_[index]; }
|
||||
|
||||
~TestExperiments() { delete enabled_; }
|
||||
|
||||
private:
|
||||
bool* enabled_;
|
||||
};
|
||||
|
||||
TestExperiments* g_test_experiments = nullptr;
|
||||
|
||||
GPR_ATTRIBUTE_NOINLINE Experiments LoadExperimentsFromConfigVariable() {
|
||||
g_loaded.store(true, std::memory_order_relaxed);
|
||||
// Set defaults from metadata.
|
||||
|
|
@ -111,11 +136,20 @@ void TestOnlyReloadExperimentsFromConfigVariables() {
|
|||
PrintExperimentsList();
|
||||
}
|
||||
|
||||
void LoadTestOnlyExperimentsFromMetadata(
|
||||
const ExperimentMetadata* experiment_metadata, size_t num_experiments) {
|
||||
g_test_experiments =
|
||||
new TestExperiments(experiment_metadata, num_experiments);
|
||||
}
|
||||
|
||||
bool IsExperimentEnabled(size_t experiment_id) {
|
||||
// Normal path: just return the value;
|
||||
return ExperimentsSingleton().enabled[experiment_id];
|
||||
}
|
||||
|
||||
bool IsTestExperimentEnabled(size_t experiment_id) {
|
||||
return (*g_test_experiments)[experiment_id];
|
||||
}
|
||||
|
||||
void PrintExperimentsList() {
|
||||
size_t max_experiment_length = 0;
|
||||
for (size_t i = 0; i < kNumExperiments; i++) {
|
||||
|
|
|
|||
|
|
@ -26,17 +26,37 @@
|
|||
|
||||
namespace grpc_core {
|
||||
|
||||
struct ExperimentMetadata {
|
||||
const char* name;
|
||||
const char* description;
|
||||
const char* additional_constaints;
|
||||
bool default_value;
|
||||
bool allow_in_fuzzing_config;
|
||||
};
|
||||
|
||||
#ifndef GRPC_EXPERIMENTS_ARE_FINAL
|
||||
// Return true if experiment \a experiment_id is enabled.
|
||||
// Experiments are numbered by their order in the g_experiment_metadata array
|
||||
// declared in experiments.h.
|
||||
bool IsExperimentEnabled(size_t experiment_id);
|
||||
|
||||
// Given a test experiment id, returns true if the test experiment is enabled.
|
||||
// Test experiments can be loaded using the LoadTestOnlyExperimentsFromMetadata
|
||||
// method.
|
||||
bool IsTestExperimentEnabled(size_t experiment_id);
|
||||
|
||||
// Reload experiment state from config variables.
|
||||
// Does not change ForceEnableExperiment state.
|
||||
// Expects the caller to handle global thread safety - so really only
|
||||
// appropriate for carefully written tests.
|
||||
void TestOnlyReloadExperimentsFromConfigVariables();
|
||||
|
||||
// Reload experiment state from passed metadata.
|
||||
// Does not change ForceEnableExperiment state.
|
||||
// Expects the caller to handle global thread safety - so really only
|
||||
// appropriate for carefully written tests.
|
||||
void LoadTestOnlyExperimentsFromMetadata(
|
||||
const ExperimentMetadata* experiment_metadata, size_t num_experiments);
|
||||
#endif
|
||||
|
||||
// Print out a list of all experiments that are built into this binary.
|
||||
|
|
@ -49,14 +69,6 @@ void PrintExperimentsList();
|
|||
// If this is called twice for the same experiment, both calls must agree.
|
||||
void ForceEnableExperiment(absl::string_view experiment_name, bool enable);
|
||||
|
||||
struct ExperimentMetadata {
|
||||
const char* name;
|
||||
const char* description;
|
||||
const char* additional_constaints;
|
||||
bool default_value;
|
||||
bool allow_in_fuzzing_config;
|
||||
};
|
||||
|
||||
// Register a function to be called to validate the value an experiment can
|
||||
// take subject to additional constraints.
|
||||
// The function will take the ExperimentMetadata as its argument. It will return
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
# Copyright 2023 gRPC authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
load("//bazel:grpc_build_system.bzl", "grpc_cc_library", "grpc_cc_test")
|
||||
|
||||
grpc_cc_library(
|
||||
name = "experiments_lib",
|
||||
srcs = [
|
||||
"fixtures/experiments.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"fixtures/experiments.h",
|
||||
],
|
||||
language = "c++",
|
||||
deps = [
|
||||
"//:config_vars",
|
||||
"//:gpr",
|
||||
"//src/core:experiments",
|
||||
"//src/core:no_destruct",
|
||||
],
|
||||
)
|
||||
|
||||
grpc_cc_test(
|
||||
name = "experiments_test",
|
||||
srcs = ["experiments_test.cc"],
|
||||
external_deps = ["gtest"],
|
||||
language = "C++",
|
||||
uses_event_engine = False,
|
||||
uses_polling = False,
|
||||
deps = [
|
||||
":experiments_lib",
|
||||
"//:gpr",
|
||||
"//src/core:experiments",
|
||||
"//test/core/util:grpc_test_util",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright 2023 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Auto generated by tools/codegen/core/gen_experiments.py
|
||||
|
||||
#include <grpc/support/port_platform.h>
|
||||
|
||||
#include "test/core/experiments/fixtures/experiments.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "src/core/lib/experiments/config.h"
|
||||
|
||||
#ifndef GRPC_EXPERIMENTS_ARE_FINAL
|
||||
|
||||
bool GetExperimentTestExperiment1ExpectedValue() { return false; }
|
||||
|
||||
bool GetExperimentTestExperiment2ExpectedValue() { return false; }
|
||||
|
||||
bool GetExperimentTestExperiment3ExpectedValue() {
|
||||
#ifdef NDEBUG
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool GetExperimentTestExperiment4ExpectedValue() { return true; }
|
||||
|
||||
TEST(ExperimentsTest, CheckExperimentValuesTest) {
|
||||
ASSERT_EQ(grpc_core::IsTestExperiment1Enabled(),
|
||||
GetExperimentTestExperiment1ExpectedValue());
|
||||
|
||||
ASSERT_EQ(grpc_core::IsTestExperiment2Enabled(),
|
||||
GetExperimentTestExperiment2ExpectedValue());
|
||||
|
||||
ASSERT_EQ(grpc_core::IsTestExperiment3Enabled(),
|
||||
GetExperimentTestExperiment3ExpectedValue());
|
||||
|
||||
ASSERT_EQ(grpc_core::IsTestExperiment4Enabled(),
|
||||
GetExperimentTestExperiment4ExpectedValue());
|
||||
}
|
||||
|
||||
#endif // GRPC_EXPERIMENTS_ARE_FINAL
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
grpc_core::LoadTestOnlyExperimentsFromMetadata(
|
||||
grpc_core::g_test_experiment_metadata, grpc_core::kNumTestExperiments);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright 2023 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Auto generated by tools/codegen/core/gen_experiments.py
|
||||
|
||||
#include <grpc/support/port_platform.h>
|
||||
|
||||
#include "test/core/experiments/fixtures/experiments.h"
|
||||
|
||||
#ifndef GRPC_EXPERIMENTS_ARE_FINAL
|
||||
namespace {
|
||||
const char* const description_test_experiment_1 = "Test Experiment 1";
|
||||
const char* const additional_constraints_test_experiment_1 = "{}";
|
||||
const char* const description_test_experiment_2 = "Test Experiment 2";
|
||||
const char* const additional_constraints_test_experiment_2 = "{}";
|
||||
const char* const description_test_experiment_3 = "Test Experiment 3";
|
||||
const char* const additional_constraints_test_experiment_3 = "{}";
|
||||
const char* const description_test_experiment_4 = "Test Experiment 4";
|
||||
const char* const additional_constraints_test_experiment_4 = "{}";
|
||||
#ifdef NDEBUG
|
||||
const bool kDefaultForDebugOnly = false;
|
||||
#else
|
||||
const bool kDefaultForDebugOnly = true;
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
namespace grpc_core {
|
||||
|
||||
const ExperimentMetadata g_test_experiment_metadata[] = {
|
||||
{"test_experiment_1", description_test_experiment_1,
|
||||
additional_constraints_test_experiment_1, false, true},
|
||||
{"test_experiment_2", description_test_experiment_2,
|
||||
additional_constraints_test_experiment_2, false, true},
|
||||
{"test_experiment_3", description_test_experiment_3,
|
||||
additional_constraints_test_experiment_3, kDefaultForDebugOnly, true},
|
||||
{"test_experiment_4", description_test_experiment_4,
|
||||
additional_constraints_test_experiment_4, true, true},
|
||||
};
|
||||
|
||||
} // namespace grpc_core
|
||||
#endif
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright 2023 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Auto generated by tools/codegen/core/gen_experiments.py
|
||||
//
|
||||
// This file contains the autogenerated parts of the experiments API.
|
||||
//
|
||||
// It generates two symbols for each experiment.
|
||||
//
|
||||
// For the experiment named new_car_project, it generates:
|
||||
//
|
||||
// - a function IsNewCarProjectEnabled() that returns true if the experiment
|
||||
// should be enabled at runtime.
|
||||
//
|
||||
// - a macro GRPC_EXPERIMENT_IS_INCLUDED_NEW_CAR_PROJECT that is defined if the
|
||||
// experiment *could* be enabled at runtime.
|
||||
//
|
||||
// The function is used to determine whether to run the experiment or
|
||||
// non-experiment code path.
|
||||
//
|
||||
// If the experiment brings significant bloat, the macro can be used to avoid
|
||||
// including the experiment code path in the binary for binaries that are size
|
||||
// sensitive.
|
||||
//
|
||||
// By default that includes our iOS and Android builds.
|
||||
//
|
||||
// Finally, a small array is included that contains the metadata for each
|
||||
// experiment.
|
||||
//
|
||||
// A macro, GRPC_EXPERIMENTS_ARE_FINAL, controls whether we fix experiment
|
||||
// configuration at build time (if it's defined) or allow it to be tuned at
|
||||
// runtime (if it's disabled).
|
||||
//
|
||||
// If you are using the Bazel build system, that macro can be configured with
|
||||
// --define=grpc_experiments_are_final=true
|
||||
|
||||
#ifndef GRPC_TEST_CORE_EXPERIMENTS_FIXTURES_EXPERIMENTS_H
|
||||
#define GRPC_TEST_CORE_EXPERIMENTS_FIXTURES_EXPERIMENTS_H
|
||||
|
||||
#include <grpc/support/port_platform.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "src/core/lib/experiments/config.h"
|
||||
|
||||
namespace grpc_core {
|
||||
|
||||
#ifdef GRPC_EXPERIMENTS_ARE_FINAL
|
||||
inline bool IsTestExperiment1Enabled() { return false; }
|
||||
inline bool IsTestExperiment2Enabled() { return false; }
|
||||
#ifndef NDEBUG
|
||||
#define GRPC_EXPERIMENT_IS_INCLUDED_TEST_EXPERIMENT_3
|
||||
#endif
|
||||
inline bool IsTestExperiment3Enabled() {
|
||||
#ifdef NDEBUG
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
#define GRPC_EXPERIMENT_IS_INCLUDED_TEST_EXPERIMENT_4
|
||||
inline bool IsTestExperiment4Enabled() { return true; }
|
||||
#else
|
||||
#define GRPC_EXPERIMENT_IS_INCLUDED_TEST_EXPERIMENT_1
|
||||
inline bool IsTestExperiment1Enabled() { return IsTestExperimentEnabled(0); }
|
||||
#define GRPC_EXPERIMENT_IS_INCLUDED_TEST_EXPERIMENT_2
|
||||
inline bool IsTestExperiment2Enabled() { return IsTestExperimentEnabled(1); }
|
||||
#define GRPC_EXPERIMENT_IS_INCLUDED_TEST_EXPERIMENT_3
|
||||
inline bool IsTestExperiment3Enabled() { return IsTestExperimentEnabled(2); }
|
||||
#define GRPC_EXPERIMENT_IS_INCLUDED_TEST_EXPERIMENT_4
|
||||
inline bool IsTestExperiment4Enabled() { return IsTestExperimentEnabled(3); }
|
||||
|
||||
constexpr const size_t kNumTestExperiments = 4;
|
||||
extern const ExperimentMetadata g_test_experiment_metadata[kNumTestExperiments];
|
||||
|
||||
#endif
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_TEST_CORE_EXPERIMENTS_FIXTURES_EXPERIMENTS_H
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# Copyright 2023 gRPC authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
- name: test_experiment_1
|
||||
description: Test Experiment 1
|
||||
expiry: 2023/08/01
|
||||
owner: blah@abc.com
|
||||
- name: test_experiment_2
|
||||
description: Test Experiment 2
|
||||
expiry: 2023/01/01
|
||||
owner: blah1@abc.com
|
||||
- name: test_experiment_3
|
||||
description: Test Experiment 3
|
||||
expiry: 2024/01/01
|
||||
owner: blah2@abc.com
|
||||
- name: test_experiment_4
|
||||
description: Test Experiment 4
|
||||
expiry: 2022/01/01
|
||||
owner: blah3@abc.com
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
# Copyright 2023 gRPC authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
- name: test_experiment_1
|
||||
default: broken
|
||||
- name: test_experiment_2
|
||||
default: false
|
||||
- name: test_experiment_3
|
||||
default: debug
|
||||
- name: test_experiment_4
|
||||
default: true
|
||||
|
|
@ -64,7 +64,48 @@ If you are using the Bazel build system, that macro can be configured with
|
|||
"""
|
||||
|
||||
|
||||
def ToCStr(s, encoding="ascii"):
|
||||
def _EXPERIMENTS_TEST_SKELETON(defs, test_body):
|
||||
return f"""
|
||||
#include <grpc/support/port_platform.h>
|
||||
|
||||
#include "test/core/experiments/fixtures/experiments.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "src/core/lib/experiments/config.h"
|
||||
|
||||
#ifndef GRPC_EXPERIMENTS_ARE_FINAL
|
||||
{defs}
|
||||
TEST(ExperimentsTest, CheckExperimentValuesTest) {{
|
||||
{test_body}
|
||||
}}
|
||||
|
||||
#endif // GRPC_EXPERIMENTS_ARE_FINAL
|
||||
|
||||
int main(int argc, char** argv) {{
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
grpc_core::LoadTestOnlyExperimentsFromMetadata(
|
||||
grpc_core::g_test_experiment_metadata, grpc_core::kNumTestExperiments);
|
||||
return RUN_ALL_TESTS();
|
||||
}}
|
||||
"""
|
||||
|
||||
|
||||
def _EXPERIMENTS_EXPECTED_VALUE(name, expected_value):
|
||||
return f"""
|
||||
bool GetExperiment{name}ExpectedValue() {{
|
||||
{expected_value}
|
||||
}}
|
||||
"""
|
||||
|
||||
|
||||
def _EXPERIMENT_CHECK_TEXT(name):
|
||||
return f"""
|
||||
ASSERT_EQ(grpc_core::Is{name}Enabled(),
|
||||
GetExperiment{name}ExpectedValue());
|
||||
"""
|
||||
|
||||
|
||||
def ToCStr(s, encoding='ascii'):
|
||||
if isinstance(s, str):
|
||||
s = s.encode(encoding)
|
||||
result = ""
|
||||
|
|
@ -268,7 +309,7 @@ class ExperimentsCompiler(object):
|
|||
rollout_attributes["name"]
|
||||
].AddRolloutSpecification(self._defaults, rollout_attributes)
|
||||
|
||||
def GenerateExperimentsHdr(self, output_file):
|
||||
def GenerateExperimentsHdr(self, output_file, mode):
|
||||
with open(output_file, "w") as H:
|
||||
PutCopyright(H, "//")
|
||||
PutBanner(
|
||||
|
|
@ -278,8 +319,18 @@ class ExperimentsCompiler(object):
|
|||
"//",
|
||||
)
|
||||
|
||||
print("#ifndef GRPC_SRC_CORE_LIB_EXPERIMENTS_EXPERIMENTS_H", file=H)
|
||||
print("#define GRPC_SRC_CORE_LIB_EXPERIMENTS_EXPERIMENTS_H", file=H)
|
||||
if mode != "test":
|
||||
include_guard = "GRPC_SRC_CORE_LIB_EXPERIMENTS_EXPERIMENTS_H"
|
||||
else:
|
||||
file_path_list = output_file.split("/")[0:-1]
|
||||
file_name = output_file.split("/")[-1].split(".")[0]
|
||||
|
||||
include_guard = (
|
||||
f"GRPC_{'_'.join(path.upper() for path in file_path_list)}_{file_name.upper()}_H"
|
||||
)
|
||||
|
||||
print(f"#ifndef {include_guard}", file=H)
|
||||
print(f"#define {include_guard}", file=H)
|
||||
print(file=H)
|
||||
print("#include <grpc/support/port_platform.h>", file=H)
|
||||
print(file=H)
|
||||
|
|
@ -313,20 +364,27 @@ class ExperimentsCompiler(object):
|
|||
)
|
||||
print(
|
||||
"inline bool Is%sEnabled() { return"
|
||||
" IsExperimentEnabled(%d); }"
|
||||
% (SnakeToPascal(exp.name), i),
|
||||
" Is%sExperimentEnabled(%d); }"
|
||||
% (SnakeToPascal(exp.name), "Test" if mode == "test" else "", i),
|
||||
file=H,
|
||||
)
|
||||
print(file=H)
|
||||
|
||||
if mode == "test":
|
||||
num_experiments_var_name = "kNumTestExperiments"
|
||||
experiments_metadata_var_name = "g_test_experiment_metadata"
|
||||
else:
|
||||
num_experiments_var_name = "kNumExperiments"
|
||||
experiments_metadata_var_name = "g_experiment_metadata"
|
||||
print(
|
||||
"constexpr const size_t kNumExperiments = %d;"
|
||||
% len(self._experiment_definitions.keys()),
|
||||
f"constexpr const size_t {num_experiments_var_name} = "
|
||||
f"{len(self._experiment_definitions.keys())};",
|
||||
file=H,
|
||||
)
|
||||
print(
|
||||
(
|
||||
"extern const ExperimentMetadata"
|
||||
" g_experiment_metadata[kNumExperiments];"
|
||||
f" {experiments_metadata_var_name}[{num_experiments_var_name}];"
|
||||
),
|
||||
file=H,
|
||||
)
|
||||
|
|
@ -334,11 +392,9 @@ class ExperimentsCompiler(object):
|
|||
print("#endif", file=H)
|
||||
print("} // namespace grpc_core", file=H)
|
||||
print(file=H)
|
||||
print(
|
||||
"#endif // GRPC_SRC_CORE_LIB_EXPERIMENTS_EXPERIMENTS_H", file=H
|
||||
)
|
||||
print(f"#endif // {include_guard}", file=H)
|
||||
|
||||
def GenerateExperimentsSrc(self, output_file):
|
||||
def GenerateExperimentsSrc(self, output_file, header_file_path, mode):
|
||||
with open(output_file, "w") as C:
|
||||
PutCopyright(C, "//")
|
||||
PutBanner(
|
||||
|
|
@ -348,7 +404,7 @@ class ExperimentsCompiler(object):
|
|||
)
|
||||
|
||||
print("#include <grpc/support/port_platform.h>", file=C)
|
||||
print('#include "src/core/lib/experiments/experiments.h"', file=C)
|
||||
print(f"#include \"{header_file_path}\"", file=C)
|
||||
print(file=C)
|
||||
print("#ifndef GRPC_EXPERIMENTS_ARE_FINAL", file=C)
|
||||
print("namespace {", file=C)
|
||||
|
|
@ -380,8 +436,12 @@ class ExperimentsCompiler(object):
|
|||
print(file=C)
|
||||
print("namespace grpc_core {", file=C)
|
||||
print(file=C)
|
||||
if mode == "test":
|
||||
experiments_metadata_var_name = "g_test_experiment_metadata"
|
||||
else:
|
||||
experiments_metadata_var_name = "g_experiment_metadata"
|
||||
print(
|
||||
"const ExperimentMetadata g_experiment_metadata[] = {", file=C
|
||||
f"const ExperimentMetadata {experiments_metadata_var_name}[] = {{", file=C
|
||||
)
|
||||
for _, exp in self._experiment_definitions.items():
|
||||
print(
|
||||
|
|
@ -400,6 +460,21 @@ class ExperimentsCompiler(object):
|
|||
print("} // namespace grpc_core", file=C)
|
||||
print("#endif", file=C)
|
||||
|
||||
def GenTest(self, output_file):
|
||||
with open(output_file, 'w') as C:
|
||||
PutCopyright(C, "//")
|
||||
PutBanner(
|
||||
[C],
|
||||
["Auto generated by tools/codegen/core/gen_experiments.py"],
|
||||
"//")
|
||||
defs = ""
|
||||
test_body = ""
|
||||
for _, exp in self._experiment_definitions.items():
|
||||
defs += _EXPERIMENTS_EXPECTED_VALUE(
|
||||
SnakeToPascal(exp.name), self._final_return[exp.default])
|
||||
test_body += _EXPERIMENT_CHECK_TEXT(SnakeToPascal(exp.name))
|
||||
print(_EXPERIMENTS_TEST_SKELETON(defs, test_body), file=C)
|
||||
|
||||
def GenExperimentsBzl(self, output_file):
|
||||
if self._bzl_list_for_defaults is None:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -74,63 +74,69 @@ def ParseCommandLineArguments(args):
|
|||
action="store_false",
|
||||
help="If specified, disables checking experiment expiry dates",
|
||||
)
|
||||
flag_parser.add_argument(
|
||||
"--disable_gen_hdrs",
|
||||
action="store_true",
|
||||
help="If specified, disables generation of experiments hdr files",
|
||||
)
|
||||
flag_parser.add_argument(
|
||||
"--disable_gen_srcs",
|
||||
action="store_true",
|
||||
help="If specified, disables generation of experiments source files",
|
||||
)
|
||||
flag_parser.add_argument(
|
||||
"--disable_gen_bzl",
|
||||
action="store_true",
|
||||
help="If specified, disables generation of experiments.bzl file",
|
||||
)
|
||||
return flag_parser.parse_args(args)
|
||||
|
||||
|
||||
args = ParseCommandLineArguments(sys.argv[1:])
|
||||
|
||||
with open("src/core/lib/experiments/experiments.yaml") as f:
|
||||
attrs = yaml.safe_load(f.read())
|
||||
|
||||
with open("src/core/lib/experiments/rollouts.yaml") as f:
|
||||
rollouts = yaml.safe_load(f.read())
|
||||
def _GenerateExperimentFiles(args, mode):
|
||||
if mode == "test":
|
||||
_EXPERIMENTS_DEFS = "test/core/experiments/fixtures/test_experiments.yaml"
|
||||
_EXPERIMENTS_ROLLOUTS = (
|
||||
"test/core/experiments/fixtures/test_experiments_rollout.yaml"
|
||||
)
|
||||
_EXPERIMENTS_HDR_FILE = "test/core/experiments/fixtures/experiments.h"
|
||||
_EXPERIMENTS_SRC_FILE = "test/core/experiments/fixtures/experiments.cc"
|
||||
else:
|
||||
_EXPERIMENTS_DEFS = "src/core/lib/experiments/experiments.yaml"
|
||||
_EXPERIMENTS_ROLLOUTS = "src/core/lib/experiments/rollouts.yaml"
|
||||
_EXPERIMENTS_HDR_FILE = "src/core/lib/experiments/experiments.h"
|
||||
_EXPERIMENTS_SRC_FILE = "src/core/lib/experiments/experiments.cc"
|
||||
|
||||
compiler = exp.ExperimentsCompiler(
|
||||
DEFAULTS, FINAL_RETURN, FINAL_DEFINE, BZL_LIST_FOR_DEFAULTS
|
||||
)
|
||||
with open(_EXPERIMENTS_DEFS) as f:
|
||||
attrs = yaml.safe_load(f.read())
|
||||
|
||||
experiment_annotation = "gRPC Experiments: "
|
||||
for attr in attrs:
|
||||
exp_definition = exp.ExperimentDefinition(attr)
|
||||
if not exp_definition.IsValid(args.check):
|
||||
sys.exit(1)
|
||||
experiment_annotation += exp_definition.name + ":0,"
|
||||
if not compiler.AddExperimentDefinition(exp_definition):
|
||||
print("Experiment = %s ERROR adding" % exp_definition.name)
|
||||
with open(_EXPERIMENTS_ROLLOUTS) as f:
|
||||
rollouts = yaml.safe_load(f.read())
|
||||
|
||||
compiler = exp.ExperimentsCompiler(
|
||||
DEFAULTS, FINAL_RETURN, FINAL_DEFINE, BZL_LIST_FOR_DEFAULTS
|
||||
)
|
||||
|
||||
experiment_annotation = "gRPC Experiments: "
|
||||
for attr in attrs:
|
||||
exp_definition = exp.ExperimentDefinition(attr)
|
||||
if not exp_definition.IsValid(args.check):
|
||||
sys.exit(1)
|
||||
experiment_annotation += exp_definition.name + ":0,"
|
||||
if not compiler.AddExperimentDefinition(exp_definition):
|
||||
print("Experiment = %s ERROR adding" % exp_definition.name)
|
||||
sys.exit(1)
|
||||
|
||||
if len(experiment_annotation) > 2000:
|
||||
print("comma-delimited string of experiments is too long")
|
||||
sys.exit(1)
|
||||
|
||||
if len(experiment_annotation) > 2000:
|
||||
print("comma-delimited string of experiments is too long")
|
||||
sys.exit(1)
|
||||
for rollout_attr in rollouts:
|
||||
if not compiler.AddRolloutSpecification(rollout_attr):
|
||||
print("ERROR adding rollout spec")
|
||||
sys.exit(1)
|
||||
|
||||
for rollout_attr in rollouts:
|
||||
if not compiler.AddRolloutSpecification(rollout_attr):
|
||||
print("ERROR adding rollout spec")
|
||||
sys.exit(1)
|
||||
print(f"Mode = {mode} Generating experiments headers")
|
||||
compiler.GenerateExperimentsHdr(_EXPERIMENTS_HDR_FILE, mode)
|
||||
|
||||
print(f"Mode = {mode} Generating experiments srcs")
|
||||
compiler.GenerateExperimentsSrc(
|
||||
_EXPERIMENTS_SRC_FILE, _EXPERIMENTS_HDR_FILE, mode)
|
||||
|
||||
if not args.disable_gen_hdrs:
|
||||
print("Generating experiments headers")
|
||||
compiler.GenerateExperimentsHdr("src/core/lib/experiments/experiments.h")
|
||||
if mode == "test":
|
||||
print("Generating experiments tests")
|
||||
compiler.GenTest('test/core/experiments/experiments_test.cc')
|
||||
else:
|
||||
print("Generating experiments.bzl")
|
||||
compiler.GenExperimentsBzl("bazel/experiments.bzl")
|
||||
|
||||
if not args.disable_gen_srcs:
|
||||
print("Generating experiments srcs")
|
||||
compiler.GenerateExperimentsSrc("src/core/lib/experiments/experiments.cc")
|
||||
|
||||
if not args.disable_gen_bzl:
|
||||
print("Generating experiments.bzl")
|
||||
compiler.GenExperimentsBzl("bazel/experiments.bzl")
|
||||
_GenerateExperimentFiles(args, "production")
|
||||
_GenerateExperimentFiles(args, "test")
|
||||
|
|
|
|||
|
|
@ -160,6 +160,7 @@ INTERNAL_DEPS = {
|
|||
"//test/core/event_engine/fuzzing_event_engine"
|
||||
),
|
||||
"test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.pb.h": "//test/core/event_engine/fuzzing_event_engine:fuzzing_event_engine_proto",
|
||||
"test/core/experiments/test_experiments.h": "//test/core/experiments:test_experiments_lib",
|
||||
"google/api/expr/v1alpha1/syntax.upb.h": "google_type_expr_upb",
|
||||
"google/rpc/status.upb.h": "google_rpc_status_upb",
|
||||
"google/protobuf/any.upb.h": "protobuf_any_upb",
|
||||
|
|
|
|||
|
|
@ -3179,6 +3179,30 @@
|
|||
],
|
||||
"uses_polling": false
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"benchmark": false,
|
||||
"ci_platforms": [
|
||||
"linux",
|
||||
"mac",
|
||||
"posix",
|
||||
"windows"
|
||||
],
|
||||
"cpu_cost": 1.0,
|
||||
"exclude_configs": [],
|
||||
"exclude_iomgrs": [],
|
||||
"flaky": false,
|
||||
"gtest": true,
|
||||
"language": "c++",
|
||||
"name": "experiments_test",
|
||||
"platforms": [
|
||||
"linux",
|
||||
"mac",
|
||||
"posix",
|
||||
"windows"
|
||||
],
|
||||
"uses_polling": false
|
||||
},
|
||||
{
|
||||
"args": [],
|
||||
"benchmark": false,
|
||||
|
|
|
|||
Loading…
Reference in New Issue