blob: 5ee3b231f24dd100d5653e6c74267ec14b0ca625 [file]
// Copyright 2023 Google LLC
//
// 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
//
// https://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.
#include "mpact/sim/generic/config.h"
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "absl/container/btree_map.h"
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "googlemock/include/gmock/gmock.h"
#include "googletest/include/gtest/gtest.h"
#include "mpact/sim/proto/component_data.pb.h"
#include "src/google/protobuf/text_format.h"
#include "src/google/protobuf/util/message_differencer.h"
namespace {
using ::mpact::sim::generic::Config;
using ::mpact::sim::generic::ConfigBase;
using ::mpact::sim::generic::ConfigValue;
using ::mpact::sim::proto::ComponentData;
using ::mpact::sim::proto::ComponentValueEntry;
constexpr char kBoolConfigName[] = "BoolConfigName";
constexpr char kInt64ConfigName[] = "Int64ConfigName";
constexpr char kUint64ConfigName[] = "Uint64ConfigName";
constexpr char kDoubleConfigName[] = "DoubleConfigName";
constexpr char kStringConfigName[] = "StringConfigName";
constexpr char kProtoValue[] = R"pb(
configuration { name: "BoolConfigName" bool_value: true }
configuration { name: "Int64ConfigName" sint64_value: -123 }
configuration { name: "Uint64ConfigName" uint64_value: 123 }
configuration { name: "DoubleConfigName" double_value: 0.25 }
configuration { name: "StringConfigName" string_value: "string value" }
)pb";
constexpr char kProtoNoName[] = R"pb(
configuration { sint64_value: -123 }
configuration { uint64_value: 123 }
configuration { double_value: 0.25 }
configuration { string_value: "string value" }
configuration { bool_value: true }
)pb";
constexpr char kProtoWrongName[] = R"pb(
configuration { name: "ConfigNameWrong" }
)pb";
constexpr char kProtoWrongValues[] = R"pb(
configuration { name: "BoolConfigName" sint64_value: -123 }
configuration { name: "Int64ConfigName" uint64_value: 123 }
configuration { name: "Uint64ConfigName" double_value: 0.25 }
configuration { name: "DoubleConfigName" string_value: "string value" }
configuration { name: "StringConfigName" bool_value: true }
)pb";
constexpr bool kBoolValue = true;
constexpr int64_t kInt64Value = -123;
constexpr uint64_t kUint64Value = 123;
constexpr double kDoubleValue = 0.25;
constexpr char kStringValue[] = "string value";
// Simple test of construction and name property.
TEST(ConfigTest, BaseConstruction) {
Config<bool> bool_config(kBoolConfigName);
EXPECT_EQ(bool_config.name(), kBoolConfigName);
Config<int64_t> int64_config(kInt64ConfigName);
EXPECT_EQ(int64_config.name(), kInt64ConfigName);
Config<uint64_t> uint64_config(kUint64ConfigName);
EXPECT_EQ(uint64_config.name(), kUint64ConfigName);
Config<double> double_config(kDoubleConfigName);
EXPECT_EQ(double_config.name(), kDoubleConfigName);
Config<std::string> string_config(kStringConfigName);
EXPECT_EQ(string_config.name(), kStringConfigName);
}
// Testing that the value can be set and retrieved using the variant type.
TEST(ConfigTest, ConfigValue) {
ConfigValue input_value;
ConfigValue output_value;
Config<bool> bool_config(kBoolConfigName);
input_value = ConfigValue(kBoolValue);
EXPECT_TRUE(bool_config.SetConfigValue(input_value).ok());
output_value = bool_config.GetConfigValue();
EXPECT_EQ(std::get<bool>(output_value), kBoolValue);
Config<int64_t> int64_config(kInt64ConfigName);
input_value = ConfigValue(kInt64Value);
EXPECT_TRUE(int64_config.SetConfigValue(input_value).ok());
output_value = int64_config.GetConfigValue();
EXPECT_EQ(std::get<int64_t>(output_value), kInt64Value);
Config<uint64_t> uint64_config(kUint64ConfigName);
input_value = ConfigValue(kUint64Value);
EXPECT_TRUE(uint64_config.SetConfigValue(input_value).ok());
output_value = uint64_config.GetConfigValue();
EXPECT_EQ(std::get<uint64_t>(output_value), kUint64Value);
Config<double> double_config(kDoubleConfigName);
input_value = ConfigValue(kDoubleValue);
EXPECT_TRUE(double_config.SetConfigValue(input_value).ok());
output_value = double_config.GetConfigValue();
EXPECT_EQ(std::get<double>(output_value), kDoubleValue);
Config<std::string> string_config(kStringConfigName);
input_value = ConfigValue(std::string(kStringValue));
EXPECT_TRUE(string_config.SetConfigValue(input_value).ok());
output_value = string_config.GetConfigValue();
EXPECT_EQ(std::get<std::string>(output_value), kStringValue);
}
// Testing that error is returned if a ConfigValue with the wrong type
// in the variant is passed int.
TEST(ConfigTest, WrongConfigValueType) {
ConfigValue input_value;
Config<bool> bool_config(kBoolConfigName);
input_value = ConfigValue(kInt64Value);
EXPECT_TRUE(absl::IsInvalidArgument(bool_config.SetConfigValue(input_value)));
}
// Testing that the initial values can be retrieved correctly.
TEST(ConfigTest, InitialValue) {
Config<bool> bool_config(kBoolConfigName, kBoolValue);
EXPECT_EQ(bool_config.GetValue(), kBoolValue);
Config<int64_t> int64_config(kInt64ConfigName, kInt64Value);
EXPECT_EQ(int64_config.GetValue(), kInt64Value);
Config<uint64_t> uint64_config(kUint64ConfigName, kUint64Value);
EXPECT_EQ(uint64_config.GetValue(), kUint64Value);
Config<double> double_config(kDoubleConfigName, kDoubleValue);
EXPECT_EQ(double_config.GetValue(), kDoubleValue);
Config<std::string> string_config(kStringConfigName,
std::string(kStringValue));
EXPECT_EQ(string_config.GetValue(), kStringValue);
}
// Testing that the value can be set and retrieved using the typed call.
TEST(ConfigTest, TypedValue) {
Config<bool> bool_config(kBoolConfigName);
bool_config.SetValue(kBoolValue);
EXPECT_EQ(bool_config.GetValue(), kBoolValue);
Config<int64_t> int64_config(kInt64ConfigName);
int64_config.SetValue(kInt64Value);
EXPECT_EQ(int64_config.GetValue(), kInt64Value);
Config<uint64_t> uint64_config(kUint64ConfigName);
uint64_config.SetValue(kUint64Value);
EXPECT_EQ(uint64_config.GetValue(), kUint64Value);
Config<double> double_config(kDoubleConfigName);
double_config.SetValue(kDoubleValue);
EXPECT_EQ(double_config.GetValue(), kDoubleValue);
Config<std::string> string_config(kStringConfigName);
string_config.SetValue(std::string(kStringValue));
EXPECT_EQ(string_config.GetValue(), kStringValue);
}
// Tests that the config entries correctly exports to a proto.
TEST(ConfigTest, ProtoExport) {
Config<bool> bool_config(kBoolConfigName, kBoolValue);
Config<int64_t> int64_config(kInt64ConfigName, kInt64Value);
Config<uint64_t> uint64_config(kUint64ConfigName, kUint64Value);
Config<double> double_config(kDoubleConfigName, kDoubleValue);
Config<std::string> string_config(kStringConfigName,
std::string(kStringValue));
// Add config entries to vector of ConfigBase. Part of the motivation here
// is that this is an intended use case for reading out the configuration
// and exporting it to a proto. Saving the configuration after a simulation
// is useful in post analysis to be able to tie together the configuration
// information and the collected statistics.
std::vector<ConfigBase *> config_vector;
config_vector.push_back(&bool_config);
config_vector.push_back(&int64_config);
config_vector.push_back(&uint64_config);
config_vector.push_back(&double_config);
config_vector.push_back(&string_config);
auto exported_proto = std::make_unique<ComponentData>();
ComponentValueEntry *entry;
for (auto const &config : config_vector) {
entry = exported_proto->add_configuration();
EXPECT_TRUE(config->Export(entry).ok());
}
// Ensure that the proto is parsed correctly.
ComponentData fromText;
EXPECT_TRUE(
google::protobuf::TextFormat::ParseFromString(kProtoValue, &fromText));
// The proto parsed from the string should be equal to that exported from
// the configuration entries.
EXPECT_TRUE(google::protobuf::util::MessageDifferencer::Equals(
fromText, *exported_proto));
}
// Tests that the config entries correctly import values from a proto.
TEST(ConfigTest, ProtoImport) {
Config<bool> bool_config(kBoolConfigName);
Config<int64_t> int64_config(kInt64ConfigName);
Config<uint64_t> uint64_config(kUint64ConfigName);
Config<double> double_config(kDoubleConfigName);
Config<std::string> string_config(kStringConfigName);
// Add the configuration entries to a map from name to ConfigBase. This is to
// mimic a use case where a proto is read in for a software component and the
// values are imported into its configuration entries (stored in a registry).
absl::btree_map<std::string, ConfigBase *> config_map;
config_map.insert(std::make_pair(bool_config.name(), &bool_config));
config_map.insert(std::make_pair(int64_config.name(), &int64_config));
config_map.insert(std::make_pair(uint64_config.name(), &uint64_config));
config_map.insert(std::make_pair(double_config.name(), &double_config));
config_map.insert(std::make_pair(string_config.name(), &string_config));
ComponentData fromText;
// Parse the proto from text description.
EXPECT_TRUE(
google::protobuf::TextFormat::ParseFromString(kProtoValue, &fromText));
// For each configuration entry, look up a config entry with a matching name
// and import the proto value to the config.
for (int index = 0; index < fromText.configuration_size(); index++) {
const ComponentValueEntry &entry = fromText.configuration(index);
if (entry.has_name() && config_map.contains(entry.name())) {
ConfigBase *config = config_map.at(entry.name());
EXPECT_TRUE(config->Import(&entry).ok());
}
}
// Verify that the values are those specified in the proto.
EXPECT_EQ(bool_config.GetValue(), kBoolValue);
EXPECT_EQ(int64_config.GetValue(), kInt64Value);
EXPECT_EQ(uint64_config.GetValue(), kUint64Value);
EXPECT_EQ(double_config.GetValue(), kDoubleValue);
EXPECT_EQ(string_config.GetValue(), kStringValue);
}
// Negative proto import test - nullptr entry.
TEST(ConfigTest, ImportFailNullProto) {
Config<bool> bool_config(kBoolConfigName);
Config<int64_t> int64_config(kInt64ConfigName);
Config<uint64_t> uint64_config(kUint64ConfigName);
Config<double> double_config(kDoubleConfigName);
Config<std::string> string_config(kStringConfigName);
std::vector<ConfigBase *> config_vector;
config_vector.push_back(&bool_config);
config_vector.push_back(&int64_config);
config_vector.push_back(&uint64_config);
config_vector.push_back(&double_config);
config_vector.push_back(&string_config);
// Expect each import to fail with invalid argument.
for (auto *config : config_vector) {
EXPECT_TRUE(absl::IsInvalidArgument(config->Import(nullptr)));
}
}
// Negative proto import test - no name in configuration value.
TEST(ConfigTest, ImportFailNoNameInProto) {
Config<bool> bool_config(kBoolConfigName);
Config<int64_t> int64_config(kInt64ConfigName);
Config<uint64_t> uint64_config(kUint64ConfigName);
Config<double> double_config(kDoubleConfigName);
Config<std::string> string_config(kStringConfigName);
std::vector<ConfigBase *> config_vector;
config_vector.push_back(&bool_config);
config_vector.push_back(&int64_config);
config_vector.push_back(&uint64_config);
config_vector.push_back(&double_config);
config_vector.push_back(&string_config);
ComponentData fromText;
// Parse the proto from text description.
EXPECT_TRUE(
google::protobuf::TextFormat::ParseFromString(kProtoNoName, &fromText));
// Expect each import to fail with internal error.
const ComponentValueEntry &entry = fromText.configuration(0);
for (int index = 0; index < fromText.configuration_size(); index++) {
EXPECT_TRUE(absl::IsInternal(config_vector[index]->Import(&entry)));
}
}
// Negative proto import test - wrong names in configuration value.
TEST(ConfigTest, ImportFailWrongNameInProto) {
Config<bool> bool_config(kBoolConfigName);
Config<int64_t> int64_config(kInt64ConfigName);
Config<uint64_t> uint64_config(kUint64ConfigName);
Config<double> double_config(kDoubleConfigName);
Config<std::string> string_config(kStringConfigName);
std::vector<ConfigBase *> config_vector;
config_vector.push_back(&bool_config);
config_vector.push_back(&int64_config);
config_vector.push_back(&uint64_config);
config_vector.push_back(&double_config);
config_vector.push_back(&string_config);
ComponentData fromText;
// Parse the proto from text description.
EXPECT_TRUE(google::protobuf::TextFormat::ParseFromString(kProtoWrongName,
&fromText));
// Expect each import to fail with internal error, as the proto message
// passed in has a name that doesn't match the configuration entry.
for (int index = 0; index < fromText.configuration_size(); index++) {
const ComponentValueEntry &entry = fromText.configuration(index);
EXPECT_TRUE(absl::IsInternal(config_vector[index]->Import(&entry)));
}
}
// Negative proto import test - wrong value fields in the proto.
TEST(ConfigTest, ImportFailWrongValue) {
Config<bool> bool_config(kBoolConfigName);
Config<int64_t> int64_config(kInt64ConfigName);
Config<uint64_t> uint64_config(kUint64ConfigName);
Config<double> double_config(kDoubleConfigName);
Config<std::string> string_config(kStringConfigName);
// Add the configuration entries to a map from name to ConfigBase. This is to
// mimic a use case where a proto is read in for a software component and the
// values are imported into its configuration entries (stored in a registry).
absl::btree_map<std::string, ConfigBase *> config_map;
config_map.insert(std::make_pair(bool_config.name(), &bool_config));
config_map.insert(std::make_pair(int64_config.name(), &int64_config));
config_map.insert(std::make_pair(uint64_config.name(), &uint64_config));
config_map.insert(std::make_pair(double_config.name(), &double_config));
config_map.insert(std::make_pair(string_config.name(), &string_config));
ComponentData fromText;
// Parse the proto from text description.
EXPECT_TRUE(google::protobuf::TextFormat::ParseFromString(kProtoWrongValues,
&fromText));
// For each configuration entry, look up a config entry with a matching name
// and import the proto value to the config. Because the value fields are
// mismatches to the type of the config entry they should all fail with
// internal errors.
for (int index = 0; index < fromText.configuration_size(); index++) {
const ComponentValueEntry &entry = fromText.configuration(index);
if (entry.has_name() && config_map.contains(entry.name())) {
ConfigBase *config = config_map.at(entry.name());
EXPECT_TRUE(absl::IsInternal(config->Import(&entry)));
}
}
}
// Tests that the value written callback is made when the config entry is
// written to.
TEST(ConfigTest, Callback) {
Config<bool> bool_config(kBoolConfigName);
bool it_worked = false;
bool_config.AddValueWrittenCallback(
[&it_worked]() -> void { it_worked = true; });
EXPECT_FALSE(it_worked);
bool_config.SetValue(true);
EXPECT_TRUE(it_worked);
}
} // namespace