Add support for lists to the FieldTrialParser.

List elements are separated by a |. If the key is given without a : we
treat that as a empty list.

We also support parsing multiple lists as a list-of-structs, see the
unit test for usage examples.

Bug: webrtc:9346
Change-Id: I32d3ce612fef476b1c481c00a893d7fa2f339e92
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/130464
Reviewed-by: Sebastian Jansson <srte@webrtc.org>
Commit-Queue: Jonas Olsson <jonasolsson@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#27560}
This commit is contained in:
Jonas Olsson 2019-04-11 11:53:26 +02:00 committed by Commit Bot
parent 7ddef1af88
commit 97d84ef78e
7 changed files with 450 additions and 9 deletions

View File

@ -40,6 +40,8 @@ rtc_static_library("audio_allocation_settings") {
rtc_static_library("field_trial_parser") {
sources = [
"field_trial_list.cc",
"field_trial_list.h",
"field_trial_parser.cc",
"field_trial_parser.h",
"field_trial_units.cc",
@ -51,6 +53,7 @@ rtc_static_library("field_trial_parser") {
"../../api/units:time_delta",
"../../rtc_base:checks",
"../../rtc_base:logging",
"../../rtc_base:stringutils",
"//third_party/abseil-cpp/absl/types:optional",
]
}
@ -151,6 +154,7 @@ if (rtc_include_tests) {
sources = [
"cpu_speed_experiment_unittest.cc",
"field_trial_list_unittest.cc",
"field_trial_parser_unittest.cc",
"field_trial_units_unittest.cc",
"keyframe_interval_settings_unittest.cc",

View File

@ -0,0 +1,57 @@
/*
* Copyright 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "rtc_base/experiments/field_trial_list.h"
namespace webrtc {
FieldTrialListBase::FieldTrialListBase(std::string key)
: FieldTrialParameterInterface(key),
failed_(false),
parse_got_called_(false) {}
bool FieldTrialListBase::Failed() const {
return failed_;
}
bool FieldTrialListBase::Used() const {
return parse_got_called_;
}
int FieldTrialListWrapper::Length() {
return GetList()->Size();
}
bool FieldTrialListWrapper::Failed() {
return GetList()->Failed();
}
bool FieldTrialListWrapper::Used() {
return GetList()->Used();
}
bool FieldTrialStructListBase::Parse(absl::optional<std::string> str_value) {
RTC_NOTREACHED();
return true;
}
int FieldTrialStructListBase::ValidateAndGetLength() {
int length = -1;
for (std::unique_ptr<FieldTrialListWrapper>& list : sub_lists_) {
if (list->Failed())
return -1;
else if (!list->Used())
continue;
else if (length == -1)
length = list->Length();
else if (length != list->Length())
return -1;
}
return length;
}
} // namespace webrtc

View File

@ -0,0 +1,224 @@
/*
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef RTC_BASE_EXPERIMENTS_FIELD_TRIAL_LIST_H_
#define RTC_BASE_EXPERIMENTS_FIELD_TRIAL_LIST_H_
#include <initializer_list>
#include <memory>
#include <string>
#include <vector>
#include "rtc_base/experiments/field_trial_parser.h"
#include "rtc_base/string_encode.h"
// List support for field trial strings. FieldTrialList and FieldTrialStructList
// are used similarly to the other FieldTrialParameters, but take a variable
// number of parameters. A FieldTrialList<T> parses a |-delimeted string into a
// list of T, using ParseTypedParameter to parse the individual tokens.
// Example string: "my_list:1|2|3,empty_list,other_list:aardvark".
// A FieldTrialStructList combines multiple lists into a list-of-structs. It
// ensures that all its sublists parse correctly and have the same length, then
// uses user-supplied accessor functions to write those elements into structs of
// a user-supplied type.
// See the unit test for usage and behavior.
namespace webrtc {
class FieldTrialListBase : public FieldTrialParameterInterface {
protected:
friend class FieldTrialListWrapper;
explicit FieldTrialListBase(std::string key);
bool Failed() const;
bool Used() const;
virtual int Size() = 0;
bool failed_;
bool parse_got_called_;
};
// This class represents a vector of type T. The elements are separated by a |
// and parsed using ParseTypedParameter.
template <typename T>
class FieldTrialList : public FieldTrialListBase {
public:
explicit FieldTrialList(std::string key) : FieldTrialList(key, {}) {}
FieldTrialList(std::string key, std::initializer_list<T> default_values)
: FieldTrialListBase(key), values_(default_values) {}
std::vector<T> Get() const { return values_; }
operator std::vector<T>() const { return Get(); }
const T& operator[](size_t index) const { return values_[index]; }
const std::vector<T>* operator->() const { return &values_; }
protected:
bool Parse(absl::optional<std::string> str_value) override {
parse_got_called_ = true;
if (!str_value) {
values_.clear();
return true;
}
std::vector<std::string> tokens;
std::vector<T> new_values_;
rtc::split(str_value.value(), '|', &tokens);
for (std::string token : tokens) {
absl::optional<T> value = ParseTypedParameter<T>(token);
if (value) {
new_values_.push_back(*value);
} else {
failed_ = true;
return false;
}
}
values_.swap(new_values_);
return true;
}
int Size() override { return values_.size(); }
private:
std::vector<T> values_;
};
class FieldTrialListWrapper {
public:
virtual ~FieldTrialListWrapper() = default;
// Takes the element at the given index in the wrapped list and writes it to
// the given struct.
virtual void WriteElement(void* struct_to_write, int index) = 0;
virtual FieldTrialListBase* GetList() = 0;
int Length();
// Returns true iff the wrapped list has failed to parse at least one token.
bool Failed();
bool Used();
protected:
FieldTrialListWrapper() = default;
};
namespace field_trial_list_impl {
// The LambdaTypeTraits struct provides type information about lambdas in the
// template expressions below.
template <typename T>
struct LambdaTypeTraits : public LambdaTypeTraits<decltype(&T::operator())> {};
template <typename ClassType, typename RetType, typename SourceType>
struct LambdaTypeTraits<RetType* (ClassType::*)(SourceType*)const> {
using ret = RetType;
using src = SourceType;
};
template <typename T>
struct TypedFieldTrialListWrapper : FieldTrialListWrapper {
public:
TypedFieldTrialListWrapper(std::string key,
std::function<void(void*, T)> sink)
: list_(key), sink_(sink) {}
void WriteElement(void* struct_to_write, int index) override {
sink_(struct_to_write, list_[index]);
}
FieldTrialListBase* GetList() { return &list_; }
private:
FieldTrialList<T> list_;
std::function<void(void*, T)> sink_;
};
} // namespace field_trial_list_impl
template <typename F,
typename Traits = typename field_trial_list_impl::LambdaTypeTraits<F>>
FieldTrialListWrapper* FieldTrialStructMember(std::string key, F accessor) {
return new field_trial_list_impl::TypedFieldTrialListWrapper<
typename Traits::ret>(key, [accessor](void* s, typename Traits::ret t) {
*accessor(static_cast<typename Traits::src*>(s)) = t;
});
}
// This base class is here to reduce the amount of code we have to generate for
// each type of FieldTrialStructList.
class FieldTrialStructListBase : public FieldTrialParameterInterface {
protected:
FieldTrialStructListBase(
std::initializer_list<FieldTrialListWrapper*> sub_lists)
: FieldTrialParameterInterface(""), sub_lists_() {
// Take ownership of the list wrappers generated by FieldTrialStructMember
// on the call site.
for (FieldTrialListWrapper* const* it = sub_lists.begin();
it != sub_lists.end(); it++) {
sub_parameters_.push_back((*it)->GetList());
sub_lists_.push_back(std::unique_ptr<FieldTrialListWrapper>(*it));
}
}
// Check that all of our sublists that were in the field trial string had the
// same number of elements. If they do, we return that length. If they had
// different lengths, any sublist had parse failures or no sublists had
// user-supplied values, we return -1.
int ValidateAndGetLength();
bool Parse(absl::optional<std::string> str_value) override;
std::vector<std::unique_ptr<FieldTrialListWrapper>> sub_lists_;
};
template <typename S>
class FieldTrialStructList : public FieldTrialStructListBase {
public:
FieldTrialStructList(std::initializer_list<FieldTrialListWrapper*> l,
std::initializer_list<S> default_list)
: FieldTrialStructListBase(l), values_(default_list) {}
std::vector<S> Get() const { return values_; }
operator std::vector<S>() const { return Get(); }
const S& operator[](size_t index) const { return values_[index]; }
const std::vector<S>* operator->() const { return &values_; }
protected:
void ParseDone() override {
int length = ValidateAndGetLength();
if (length == -1)
return;
std::vector<S> new_values(length, S());
for (std::unique_ptr<FieldTrialListWrapper>& li : sub_lists_) {
if (li->Used()) {
for (int i = 0; i < length; i++) {
li->WriteElement(&new_values[i], i);
}
}
}
values_.swap(new_values);
}
private:
std::vector<S> values_;
};
} // namespace webrtc
#endif // RTC_BASE_EXPERIMENTS_FIELD_TRIAL_LIST_H_

View File

@ -0,0 +1,133 @@
/*
* Copyright 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "rtc_base/experiments/field_trial_list.h"
#include "rtc_base/gunit.h"
#include "test/gmock.h"
using testing::ElementsAre;
using testing::IsEmpty;
namespace webrtc {
struct Garment {
int price = 0;
std::string color = "";
// Only needed for testing.
Garment() = default;
Garment(int p, std::string c) : price(p), color(c) {}
bool operator==(const Garment& other) const {
return price == other.price && color == other.color;
}
};
TEST(FieldTrialListTest, ParsesListParameter) {
FieldTrialList<int> my_list("l", {5});
EXPECT_THAT(my_list.Get(), ElementsAre(5));
// If one element is invalid the list is unchanged.
ParseFieldTrial({&my_list}, "l:1|2|hat");
EXPECT_THAT(my_list.Get(), ElementsAre(5));
ParseFieldTrial({&my_list}, "l");
EXPECT_THAT(my_list.Get(), IsEmpty());
ParseFieldTrial({&my_list}, "l:1|2|3");
EXPECT_THAT(my_list.Get(), ElementsAre(1, 2, 3));
ParseFieldTrial({&my_list}, "l:-1");
EXPECT_THAT(my_list.Get(), ElementsAre(-1));
FieldTrialList<std::string> another_list("l", {"hat"});
EXPECT_THAT(another_list.Get(), ElementsAre("hat"));
ParseFieldTrial({&another_list}, "l");
EXPECT_THAT(another_list.Get(), IsEmpty());
ParseFieldTrial({&another_list}, "l:");
EXPECT_THAT(another_list.Get(), ElementsAre(""));
ParseFieldTrial({&another_list}, "l:scarf|hat|mittens");
EXPECT_THAT(another_list.Get(), ElementsAre("scarf", "hat", "mittens"));
ParseFieldTrial({&another_list}, "l:scarf");
EXPECT_THAT(another_list.Get(), ElementsAre("scarf"));
}
// Normal usage.
TEST(FieldTrialListTest, ParsesStructList) {
FieldTrialStructList<Garment> my_list(
{FieldTrialStructMember("color", [](Garment* g) { return &g->color; }),
FieldTrialStructMember("price", [](Garment* g) { return &g->price; })},
{{1, "blue"}, {2, "red"}});
ParseFieldTrial({&my_list},
"color:mauve|red|gold,"
"price:10|20|30,"
"other_param:asdf");
ASSERT_THAT(my_list.Get(),
ElementsAre(Garment{10, "mauve"}, Garment{20, "red"},
Garment{30, "gold"}));
}
// One FieldTrialList has the wrong length, so we use the user-provided default
// list.
TEST(FieldTrialListTest, StructListKeepsDefaultWithMismatchingLength) {
FieldTrialStructList<Garment> my_list(
{FieldTrialStructMember("wrong_length",
[](Garment* g) { return &g->color; }),
FieldTrialStructMember("price", [](Garment* g) { return &g->price; })},
{{1, "blue"}, {2, "red"}});
ParseFieldTrial({&my_list},
"wrong_length:mauve|magenta|chartreuse|indigo,"
"garment:hat|hat|crown,"
"price:10|20|30");
ASSERT_THAT(my_list.Get(),
ElementsAre(Garment{1, "blue"}, Garment{2, "red"}));
}
// One list is missing. We set the values we're given, and the others remain
// as whatever the Garment default constructor set them to.
TEST(FieldTrialListTest, StructListUsesDefaultForMissingList) {
FieldTrialStructList<Garment> my_list(
{FieldTrialStructMember("color", [](Garment* g) { return &g->color; }),
FieldTrialStructMember("price", [](Garment* g) { return &g->price; })},
{{1, "blue"}, {2, "red"}});
ParseFieldTrial({&my_list}, "price:10|20|30");
ASSERT_THAT(my_list.Get(),
ElementsAre(Garment{10, ""}, Garment{20, ""}, Garment{30, ""}));
}
// The user haven't provided values for any lists, so we use the default list.
TEST(FieldTrialListTest, StructListUsesDefaultListWithoutValues) {
FieldTrialStructList<Garment> my_list(
{FieldTrialStructMember("color", [](Garment* g) { return &g->color; }),
FieldTrialStructMember("price", [](Garment* g) { return &g->price; })},
{{1, "blue"}, {2, "red"}});
ParseFieldTrial({&my_list}, "");
ASSERT_THAT(my_list.Get(),
ElementsAre(Garment{1, "blue"}, Garment{2, "red"}));
}
// Some lists are provided and all are empty, so we return a empty list.
TEST(FieldTrialListTest, StructListHandlesEmptyLists) {
FieldTrialStructList<Garment> my_list(
{FieldTrialStructMember("color", [](Garment* g) { return &g->color; }),
FieldTrialStructMember("price", [](Garment* g) { return &g->price; })},
{{1, "blue"}, {2, "red"}});
ParseFieldTrial({&my_list}, "color,price");
ASSERT_EQ(my_list.Get().size(), 0u);
}
} // namespace webrtc

View File

@ -1,5 +1,5 @@
/*
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
* Copyright 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
@ -33,9 +33,6 @@ FieldTrialParameterInterface::~FieldTrialParameterInterface() {
RTC_DCHECK(used_) << "Field trial parameter with key: '" << key_
<< "' never used.";
}
std::string FieldTrialParameterInterface::Key() const {
return key_;
}
void ParseFieldTrial(
std::initializer_list<FieldTrialParameterInterface*> fields,
@ -44,13 +41,23 @@ void ParseFieldTrial(
FieldTrialParameterInterface* keyless_field = nullptr;
for (FieldTrialParameterInterface* field : fields) {
field->MarkAsUsed();
if (field->Key().empty()) {
if (!field->sub_parameters_.empty()) {
for (FieldTrialParameterInterface* sub_field : field->sub_parameters_) {
RTC_DCHECK(!sub_field->key_.empty());
sub_field->MarkAsUsed();
field_map[sub_field->key_] = sub_field;
}
continue;
}
if (field->key_.empty()) {
RTC_DCHECK(!keyless_field);
keyless_field = field;
} else {
field_map[field->Key()] = field;
field_map[field->key_] = field;
}
}
size_t i = 0;
while (i < trial_string.length()) {
int val_end = FindOrEnd(trial_string, i, ',');
@ -78,6 +85,10 @@ void ParseFieldTrial(
<< "' (found in trial: \"" << trial_string << "\")";
}
}
for (FieldTrialParameterInterface* field : fields) {
field->ParseDone();
}
}
template <>

View File

@ -11,10 +11,13 @@
#define RTC_BASE_EXPERIMENTS_FIELD_TRIAL_PARSER_H_
#include <stdint.h>
#include <initializer_list>
#include <map>
#include <set>
#include <string>
#include <vector>
#include "absl/types/optional.h"
// Field trial parser functionality. Provides funcitonality to parse field trial
@ -24,7 +27,7 @@
// ignored. Parameters are declared with a given type for which an
// implementation of ParseTypedParameter should be provided. The
// ParseTypedParameter implementation is given whatever is between the : and the
// ,. FieldTrialOptional will use nullopt if the key is provided without :.
// ,. If the key is provided without : a FieldTrialOptional will use nullopt.
// Example string: "my_optional,my_int:3,my_string:hello"
@ -47,10 +50,14 @@ class FieldTrialParameterInterface {
std::string raw_string);
void MarkAsUsed() { used_ = true; }
virtual bool Parse(absl::optional<std::string> str_value) = 0;
std::string Key() const;
virtual void ParseDone() {}
std::vector<FieldTrialParameterInterface*> sub_parameters_;
std::string key_;
private:
std::string key_;
bool used_ = false;
};

View File

@ -8,9 +8,12 @@
* be found in the AUTHORS file in the root of the source tree.
*/
#include "rtc_base/experiments/field_trial_parser.h"
#include "rtc_base/experiments/field_trial_list.h"
#include "rtc_base/gunit.h"
#include "system_wrappers/include/field_trial.h"
#include "test/field_trial.h"
#include "test/gmock.h"
namespace webrtc {
namespace {
@ -38,6 +41,7 @@ enum class CustomEnum {
kRed = 1,
kBlue = 2,
};
} // namespace
TEST(FieldTrialParserTest, ParsesValidParameters) {
@ -152,4 +156,5 @@ TEST(FieldTrialParserTest, ParsesCustomEnumParameter) {
ParseFieldTrial({&my_enum}, "e:5");
EXPECT_EQ(my_enum.Get(), CustomEnum::kBlue);
}
} // namespace webrtc