diff --git a/CMakeLists.txt b/CMakeLists.txt index a7fdee33196..cd5b2c15645 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index be0992812db..8cc28a1ac60 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -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 diff --git a/src/core/BUILD b/src/core/BUILD index 65f00c4c3a0..dc83c9e6dbb 100644 --- a/src/core/BUILD +++ b/src/core/BUILD @@ -126,6 +126,7 @@ grpc_cc_library( "absl/strings", ], language = "c++", + tags = ["nofixdeps"], deps = [ "no_destruct", "//:config_vars", diff --git a/src/core/lib/experiments/config.cc b/src/core/lib/experiments/config.cc index 97f04bf082c..891229bc151 100644 --- a/src/core/lib/experiments/config.cc +++ b/src/core/lib/experiments/config.cc @@ -54,6 +54,31 @@ std::atomic g_loaded(false); absl::AnyInvocable* 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++) { diff --git a/src/core/lib/experiments/config.h b/src/core/lib/experiments/config.h index 29d9d2ae657..e93382634b7 100644 --- a/src/core/lib/experiments/config.h +++ b/src/core/lib/experiments/config.h @@ -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 diff --git a/test/core/experiments/BUILD b/test/core/experiments/BUILD new file mode 100644 index 00000000000..2e2a75907d3 --- /dev/null +++ b/test/core/experiments/BUILD @@ -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", + ], +) diff --git a/test/core/experiments/experiments_test.cc b/test/core/experiments/experiments_test.cc new file mode 100644 index 00000000000..ed90e7745da --- /dev/null +++ b/test/core/experiments/experiments_test.cc @@ -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 + +#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(); +} diff --git a/test/core/experiments/fixtures/experiments.cc b/test/core/experiments/fixtures/experiments.cc new file mode 100644 index 00000000000..ac2d3334985 --- /dev/null +++ b/test/core/experiments/fixtures/experiments.cc @@ -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 + +#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 diff --git a/test/core/experiments/fixtures/experiments.h b/test/core/experiments/fixtures/experiments.h new file mode 100644 index 00000000000..4ee688ed42a --- /dev/null +++ b/test/core/experiments/fixtures/experiments.h @@ -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 + +#include + +#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 diff --git a/test/core/experiments/fixtures/test_experiments.yaml b/test/core/experiments/fixtures/test_experiments.yaml new file mode 100644 index 00000000000..34d1fe3d28d --- /dev/null +++ b/test/core/experiments/fixtures/test_experiments.yaml @@ -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 diff --git a/test/core/experiments/fixtures/test_experiments_rollout.yaml b/test/core/experiments/fixtures/test_experiments_rollout.yaml new file mode 100644 index 00000000000..63ba9f838d4 --- /dev/null +++ b/test/core/experiments/fixtures/test_experiments_rollout.yaml @@ -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 diff --git a/tools/codegen/core/experiments_compiler.py b/tools/codegen/core/experiments_compiler.py index 3b1188b8e78..67a0285c04a 100644 --- a/tools/codegen/core/experiments_compiler.py +++ b/tools/codegen/core/experiments_compiler.py @@ -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 + +#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 ", 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 ", 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 diff --git a/tools/codegen/core/gen_experiments.py b/tools/codegen/core/gen_experiments.py index 7984ee13615..cb90d7aa067 100755 --- a/tools/codegen/core/gen_experiments.py +++ b/tools/codegen/core/gen_experiments.py @@ -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") diff --git a/tools/distrib/fix_build_deps.py b/tools/distrib/fix_build_deps.py index 5ec3d460335..e860b54fc5f 100755 --- a/tools/distrib/fix_build_deps.py +++ b/tools/distrib/fix_build_deps.py @@ -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", diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json index 1ca3b07110b..a313470869c 100644 --- a/tools/run_tests/generated/tests.json +++ b/tools/run_tests/generated/tests.json @@ -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,