Expose methods to validate and merge FieldTrial strings.
Bug: webrtc:11177
Change-Id: I0514d82bc904b1548c64fdef8b0a2a99a8dbd735
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/161309
Reviewed-by: Niels Moller <nisse@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Commit-Queue: Konrad Hofbauer <hofbauer@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#30027}
diff --git a/system_wrappers/BUILD.gn b/system_wrappers/BUILD.gn
index 2f30a16..7fc29c9 100644
--- a/system_wrappers/BUILD.gn
+++ b/system_wrappers/BUILD.gn
@@ -96,6 +96,7 @@
deps = [
"../rtc_base:checks",
"../rtc_base:logging",
+ "../rtc_base:stringutils",
"//third_party/abseil-cpp/absl/strings",
]
}
diff --git a/system_wrappers/include/field_trial.h b/system_wrappers/include/field_trial.h
index 1d0cef4..52db33b 100644
--- a/system_wrappers/include/field_trial.h
+++ b/system_wrappers/include/field_trial.h
@@ -16,7 +16,7 @@
// Field trials allow webrtc clients (such as Chrome) to turn on feature code
// in binaries out in the field and gather information with that.
//
-// By default WebRTC provides an implementaion of field trials that can be
+// By default WebRTC provides an implementation of field trials that can be
// found in system_wrappers/source/field_trial.cc. If clients want to provide
// a custom version, they will have to:
//
@@ -45,10 +45,10 @@
//
// Notes:
// - NOT every feature is a candidate to be controlled by this mechanism as
-// it may require negotation between involved parties (e.g. SDP).
+// it may require negotiation between involved parties (e.g. SDP).
//
// TODO(andresp): since chrome --force-fieldtrials does not marks the trial
-// as active it does not gets propaged to renderer process. For now one
+// as active it does not get propagated to the renderer process. For now one
// needs to push a config with start_active:true or run a local finch
// server.
//
@@ -84,6 +84,18 @@
const char* GetFieldTrialString();
+#ifndef WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT
+// Validates the given field trial string.
+bool FieldTrialsStringIsValid(const char* trials_string);
+
+// Merges two field trial strings.
+//
+// If a key (trial) exists twice with conflicting values (groups), the value
+// in 'second' takes precedence.
+// Shall only be called with valid FieldTrial strings.
+std::string MergeFieldTrialsStrings(const char* first, const char* second);
+#endif // WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT
+
} // namespace field_trial
} // namespace webrtc
diff --git a/system_wrappers/source/field_trial.cc b/system_wrappers/source/field_trial.cc
index 5b8a756..f1dccc9 100644
--- a/system_wrappers/source/field_trial.cc
+++ b/system_wrappers/source/field_trial.cc
@@ -17,6 +17,7 @@
#include "absl/strings/string_view.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
+#include "rtc_base/string_encode.h"
// Simple field trial implementation, which allows client to
// specify desired flags in InitFieldTrialsFromString.
@@ -36,7 +37,7 @@
//
// E.g. invalid config:
// "WebRTC-experiment1/Enabled" (note missing / separator at the end).
-bool FieldTrialsStringIsValid(const absl::string_view trials) {
+bool FieldTrialsStringIsValidInternal(const absl::string_view trials) {
if (trials.empty())
return true;
@@ -69,6 +70,38 @@
}
} // namespace
+bool FieldTrialsStringIsValid(const char* trials_string) {
+ return FieldTrialsStringIsValidInternal(trials_string);
+}
+
+void InsertOrReplaceFieldTrialStringsInMap(
+ std::map<std::string, std::string>* fieldtrial_map,
+ const absl::string_view trials_string) {
+ if (FieldTrialsStringIsValidInternal(trials_string)) {
+ std::vector<std::string> tokens;
+ rtc::split(std::string(trials_string), '/', &tokens);
+ // Skip last token which is empty due to trailing '/'.
+ for (size_t idx = 0; idx < tokens.size() - 1; idx += 2) {
+ (*fieldtrial_map)[tokens[idx]] = tokens[idx + 1];
+ }
+ } else {
+ RTC_DCHECK(false) << "Invalid field trials string:" << trials_string;
+ }
+}
+
+std::string MergeFieldTrialsStrings(const char* first, const char* second) {
+ std::map<std::string, std::string> fieldtrial_map;
+ InsertOrReplaceFieldTrialStringsInMap(&fieldtrial_map, first);
+ InsertOrReplaceFieldTrialStringsInMap(&fieldtrial_map, second);
+
+ // Merge into fieldtrial string.
+ std::string merged = "";
+ for (auto const& fieldtrial : fieldtrial_map) {
+ merged += fieldtrial.first + '/' + fieldtrial.second + '/';
+ }
+ return merged;
+}
+
std::string FindFullName(const std::string& name) {
if (trials_init_string == NULL)
return std::string();
@@ -107,7 +140,7 @@
RTC_LOG(LS_INFO) << "Setting field trial string:" << trials_string;
#ifndef WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT
if (trials_string) {
- RTC_DCHECK(FieldTrialsStringIsValid(trials_string))
+ RTC_DCHECK(FieldTrialsStringIsValidInternal(trials_string))
<< "Invalid field trials string:" << trials_string;
};
#endif // WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT
diff --git a/system_wrappers/source/field_trial_unittest.cc b/system_wrappers/source/field_trial_unittest.cc
index 67b841d..fdabe1b 100644
--- a/system_wrappers/source/field_trial_unittest.cc
+++ b/system_wrappers/source/field_trial_unittest.cc
@@ -21,10 +21,15 @@
InitFieldTrialsFromString("");
InitFieldTrialsFromString("Audio/Enabled/");
InitFieldTrialsFromString("Audio/Enabled/Video/Disabled/");
+ EXPECT_TRUE(FieldTrialsStringIsValid(""));
+ EXPECT_TRUE(FieldTrialsStringIsValid("Audio/Enabled/"));
+ EXPECT_TRUE(FieldTrialsStringIsValid("Audio/Enabled/Video/Disabled/"));
// Duplicate trials with the same value is fine
InitFieldTrialsFromString("Audio/Enabled/Audio/Enabled/");
InitFieldTrialsFromString("Audio/Enabled/B/C/Audio/Enabled/");
+ EXPECT_TRUE(FieldTrialsStringIsValid("Audio/Enabled/Audio/Enabled/"));
+ EXPECT_TRUE(FieldTrialsStringIsValid("Audio/Enabled/B/C/Audio/Enabled/"));
}
TEST(FieldTrialValidationTest, RejectsBadInputs) {
@@ -40,6 +45,26 @@
RTC_EXPECT_DEATH(
InitFieldTrialsFromString("Audio/Enabled/Video/Disabled/garbage"),
"Invalid field trials string:");
+ EXPECT_FALSE(FieldTrialsStringIsValid("Audio/EnabledVideo/Disabled/"));
+ EXPECT_FALSE(FieldTrialsStringIsValid("Audio/Enabled//Video/Disabled/"));
+ EXPECT_FALSE(FieldTrialsStringIsValid("/Audio/Enabled/Video/Disabled/"));
+ EXPECT_FALSE(FieldTrialsStringIsValid("Audio/Enabled/Video/Disabled"));
+ EXPECT_FALSE(
+ FieldTrialsStringIsValid("Audio/Enabled/Video/Disabled/garbage"));
+
+ // Empty trial or group
+ RTC_EXPECT_DEATH(InitFieldTrialsFromString("Audio//"),
+ "Invalid field trials string:");
+ RTC_EXPECT_DEATH(InitFieldTrialsFromString("/Enabled/"),
+ "Invalid field trials string:");
+ RTC_EXPECT_DEATH(InitFieldTrialsFromString("//"),
+ "Invalid field trials string:");
+ RTC_EXPECT_DEATH(InitFieldTrialsFromString("//Enabled"),
+ "Invalid field trials string:");
+ EXPECT_FALSE(FieldTrialsStringIsValid("Audio//"));
+ EXPECT_FALSE(FieldTrialsStringIsValid("/Enabled/"));
+ EXPECT_FALSE(FieldTrialsStringIsValid("//"));
+ EXPECT_FALSE(FieldTrialsStringIsValid("//Enabled"));
// Duplicate trials with different values is not fine
RTC_EXPECT_DEATH(InitFieldTrialsFromString("Audio/Enabled/Audio/Disabled/"),
@@ -47,6 +72,33 @@
RTC_EXPECT_DEATH(
InitFieldTrialsFromString("Audio/Enabled/B/C/Audio/Disabled/"),
"Invalid field trials string:");
+ EXPECT_FALSE(FieldTrialsStringIsValid("Audio/Enabled/Audio/Disabled/"));
+ EXPECT_FALSE(FieldTrialsStringIsValid("Audio/Enabled/B/C/Audio/Disabled/"));
+}
+
+TEST(FieldTrialMergingTest, MergesValidInput) {
+ EXPECT_EQ(MergeFieldTrialsStrings("Video/Enabled/", "Audio/Enabled/"),
+ "Audio/Enabled/Video/Enabled/");
+ EXPECT_EQ(MergeFieldTrialsStrings("Audio/Disabled/Video/Enabled/",
+ "Audio/Enabled/"),
+ "Audio/Enabled/Video/Enabled/");
+ EXPECT_EQ(
+ MergeFieldTrialsStrings("Audio/Enabled/Video/Enabled/", "Audio/Enabled/"),
+ "Audio/Enabled/Video/Enabled/");
+ EXPECT_EQ(
+ MergeFieldTrialsStrings("Audio/Enabled/Audio/Enabled/", "Video/Enabled/"),
+ "Audio/Enabled/Video/Enabled/");
+}
+
+TEST(FieldTrialMergingTest, DchecksBadInput) {
+ RTC_EXPECT_DEATH(MergeFieldTrialsStrings("Audio/Enabled/", "garbage"),
+ "Invalid field trials string:");
+}
+
+TEST(FieldTrialMergingTest, HandlesEmptyInput) {
+ EXPECT_EQ(MergeFieldTrialsStrings("", "Audio/Enabled/"), "Audio/Enabled/");
+ EXPECT_EQ(MergeFieldTrialsStrings("Audio/Enabled/", ""), "Audio/Enabled/");
+ EXPECT_EQ(MergeFieldTrialsStrings("", ""), "");
}
#endif // GTEST_HAS_DEATH_TEST && RTC_DCHECK_IS_ON && !defined(WEBRTC_ANDROID)
// && !defined(WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT)