Commit b158a18a authored by Michael Crouse's avatar Michael Crouse Committed by Commit Bot

Place models and host model features from a fetch in the store.

This change persists the model and host model features received from a
fetch to the remote Optimization Guide Service by places them within
the store owned by the PredictionManager.

A future change will add browser tests and metrics to confirm expected
behavior of prediction manager, fetcher, and models.

Bug: 1001194
Change-Id: I705785d32560160b14f69f846f0148d3ef6abc43
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1934827Reviewed-by: default avatarTarun Bansal <tbansal@chromium.org>
Reviewed-by: default avatarSophie Chang <sophiechang@chromium.org>
Reviewed-by: default avatarDoug Arnett <dougarnett@chromium.org>
Commit-Queue: Michael Crouse <mcrouse@chromium.org>
Cr-Commit-Position: refs/heads/master@{#719265}
parent 0d06c2cd
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include "components/optimization_guide/optimization_guide_prefs.h" #include "components/optimization_guide/optimization_guide_prefs.h"
#include "components/optimization_guide/optimization_guide_store.h" #include "components/optimization_guide/optimization_guide_store.h"
#include "components/optimization_guide/optimization_guide_switches.h" #include "components/optimization_guide/optimization_guide_switches.h"
#include "components/optimization_guide/store_update_data.h"
#include "components/optimization_guide/top_host_provider.h" #include "components/optimization_guide/top_host_provider.h"
#include "components/prefs/pref_service.h" #include "components/prefs/pref_service.h"
#include "content/public/browser/navigation_handle.h" #include "content/public/browser/navigation_handle.h"
...@@ -62,9 +63,6 @@ constexpr base::TimeDelta kFetchRetryDelay = base::TimeDelta::FromMinutes(16); ...@@ -62,9 +63,6 @@ constexpr base::TimeDelta kFetchRetryDelay = base::TimeDelta::FromMinutes(16);
// The amount of time to wait after a successful fetch of models and host model // The amount of time to wait after a successful fetch of models and host model
// features before requesting an update from the remote Optimization Guide // features before requesting an update from the remote Optimization Guide
// Service. // Service.
//
// TODO(mcrouse): Remove this when the models and features are placed into the
// store and rely on the store to provide the update time.
constexpr base::TimeDelta kUpdateModelsAndFeaturesDelay = constexpr base::TimeDelta kUpdateModelsAndFeaturesDelay =
base::TimeDelta::FromHours(24); base::TimeDelta::FromHours(24);
...@@ -421,31 +419,15 @@ void PredictionManager::OnModelsAndHostFeaturesFetched( ...@@ -421,31 +419,15 @@ void PredictionManager::OnModelsAndHostFeaturesFetched(
if (!get_models_response_data) if (!get_models_response_data)
return; return;
// TODO(crbug/1001194): Asynchronously store the models and host model // Update host model features, even if empty so the store metadata
// features within the persistent store. // that contains the update time for new models and features to be fetched
// from the remote Optimization Guide Service is updated.
// The set of host model features that the models in
// |get_models_response_data| required in order to be evaluated. Every host
// model feature returned should contain all the features for the models
// currently supported.
base::flat_set<std::string> host_model_features;
if ((*get_models_response_data)->host_model_features_size() > 0) {
UpdateHostModelFeatures((*get_models_response_data)->host_model_features()); UpdateHostModelFeatures((*get_models_response_data)->host_model_features());
host_model_features.reserve((*get_models_response_data) // If no models were returned, no updates to the store are required.
->host_model_features(0) if ((*get_models_response_data)->models_size() > 0)
.model_features_size()); UpdatePredictionModels((*get_models_response_data)->models());
for (const auto& model_feature :
(*get_models_response_data)->host_model_features(0).model_features()) {
if (model_feature.has_feature_name())
host_model_features.insert(model_feature.feature_name());
}
}
if ((*get_models_response_data)->models_size() > 0) {
UpdatePredictionModels((*get_models_response_data)->mutable_models(),
host_model_features);
}
// TODO(crbug/1001194): After the models and host model features are stored // TODO(crbug/1001194): After the models and host model features are stored
// asynchronously, the timer will be set based on the update time provided // asynchronously, the timer will be set based on the update time provided
// by the store. // by the store.
...@@ -459,8 +441,21 @@ void PredictionManager::UpdateHostModelFeatures( ...@@ -459,8 +441,21 @@ void PredictionManager::UpdateHostModelFeatures(
const google::protobuf::RepeatedPtrField<proto::HostModelFeatures>& const google::protobuf::RepeatedPtrField<proto::HostModelFeatures>&
host_model_features) { host_model_features) {
SEQUENCE_CHECKER(sequence_checker_); SEQUENCE_CHECKER(sequence_checker_);
for (const auto& host_model_features : host_model_features) std::unique_ptr<StoreUpdateData> host_model_features_update_data =
ProcessAndStoreHostModelFeatures(host_model_features); StoreUpdateData::CreateHostModelFeaturesStoreUpdateData(
/*update_time=*/clock_->Now() + kUpdateModelsAndFeaturesDelay,
/*expiry_time=*/clock_->Now() +
features::StoredHostModelFeaturesFreshnessDuration());
for (const auto& host_model_features : host_model_features) {
if (ProcessAndStoreHostModelFeatures(host_model_features)) {
host_model_features_update_data->CopyHostModelFeaturesIntoUpdateData(
host_model_features);
}
}
model_and_features_store_->UpdateHostModelFeatures(
std::move(host_model_features_update_data),
base::BindOnce(&PredictionManager::OnHostModelFeaturesStored,
ui_weak_ptr_factory_.GetWeakPtr()));
} }
std::unique_ptr<PredictionModel> PredictionManager::CreatePredictionModel( std::unique_ptr<PredictionModel> PredictionManager::CreatePredictionModel(
...@@ -471,13 +466,41 @@ std::unique_ptr<PredictionModel> PredictionManager::CreatePredictionModel( ...@@ -471,13 +466,41 @@ std::unique_ptr<PredictionModel> PredictionManager::CreatePredictionModel(
} }
void PredictionManager::UpdatePredictionModels( void PredictionManager::UpdatePredictionModels(
google::protobuf::RepeatedPtrField<proto::PredictionModel>* const google::protobuf::RepeatedPtrField<proto::PredictionModel>&
prediction_models, prediction_models) {
const base::flat_set<std::string>& host_model_features) {
SEQUENCE_CHECKER(sequence_checker_); SEQUENCE_CHECKER(sequence_checker_);
std::unique_ptr<PredictionModel> prediction_model; std::unique_ptr<StoreUpdateData> prediction_model_update_data =
for (auto& model : *prediction_models) StoreUpdateData::CreatePredictionModelStoreUpdateData();
ProcessAndStorePredictionModel(model); bool models_to_store = false;
for (const auto& model : prediction_models) {
if (ProcessAndStorePredictionModel(model)) {
prediction_model_update_data->CopyPredictionModelIntoUpdateData(model);
models_to_store = true;
}
}
if (models_to_store) {
model_and_features_store_->UpdatePredictionModels(
std::move(prediction_model_update_data),
base::BindOnce(&PredictionManager::OnPredictionModelsStored,
ui_weak_ptr_factory_.GetWeakPtr()));
}
}
void PredictionManager::OnPredictionModelsStored() {
SEQUENCE_CHECKER(sequence_checker_);
LOCAL_HISTOGRAM_BOOLEAN(
"OptimizationGuide.PredictionManager.PredictionModelsStored", true);
}
void PredictionManager::OnHostModelFeaturesStored() {
SEQUENCE_CHECKER(sequence_checker_);
LOCAL_HISTOGRAM_BOOLEAN(
"OptimizationGuide.PredictionManager.HostModelFeaturesStored", true);
// TODO(crbug/1027596): Stopping the timer can be removed once the fetch
// callback refactor is done. Otherwise, at the start of a fetch, a timer is
// running to handle the cases that a fetch fails but the callback is not run.
fetch_timer_.Stop();
ScheduleModelsAndHostModelFeaturesFetch();
} }
void PredictionManager::OnStoreInitialized() { void PredictionManager::OnStoreInitialized() {
...@@ -557,42 +580,42 @@ void PredictionManager::OnLoadPredictionModel( ...@@ -557,42 +580,42 @@ void PredictionManager::OnLoadPredictionModel(
ProcessAndStorePredictionModel(*model); ProcessAndStorePredictionModel(*model);
} }
void PredictionManager::ProcessAndStorePredictionModel( bool PredictionManager::ProcessAndStorePredictionModel(
const proto::PredictionModel& model) { const proto::PredictionModel& model) {
SEQUENCE_CHECKER(sequence_checker_); SEQUENCE_CHECKER(sequence_checker_);
if (!model.model_info().has_optimization_target()) if (!model.model_info().has_optimization_target())
return; return false;
if (!registered_optimization_targets_.contains( if (!registered_optimization_targets_.contains(
model.model_info().optimization_target())) { model.model_info().optimization_target())) {
return; return false;
} }
std::unique_ptr<PredictionModel> prediction_model = std::unique_ptr<PredictionModel> prediction_model =
CreatePredictionModel(model); CreatePredictionModel(model);
if (!prediction_model) if (!prediction_model)
return; return false;
auto it = optimization_target_prediction_model_map_.find( auto it = optimization_target_prediction_model_map_.find(
model.model_info().optimization_target()); model.model_info().optimization_target());
if (it == optimization_target_prediction_model_map_.end()) { if (it == optimization_target_prediction_model_map_.end()) {
optimization_target_prediction_model_map_.emplace( optimization_target_prediction_model_map_.emplace(
model.model_info().optimization_target(), std::move(prediction_model)); model.model_info().optimization_target(), std::move(prediction_model));
return; return true;
} }
if (it->second->GetVersion() != prediction_model->GetVersion()) { if (it->second->GetVersion() != prediction_model->GetVersion()) {
it->second = std::move(prediction_model); it->second = std::move(prediction_model);
return; return true;
} }
return; return false;
} }
void PredictionManager::ProcessAndStoreHostModelFeatures( bool PredictionManager::ProcessAndStoreHostModelFeatures(
const proto::HostModelFeatures& host_model_features) { const proto::HostModelFeatures& host_model_features) {
SEQUENCE_CHECKER(sequence_checker_); SEQUENCE_CHECKER(sequence_checker_);
if (!host_model_features.has_host()) if (!host_model_features.has_host())
return; return false;
if (host_model_features.model_features_size() == 0) if (host_model_features.model_features_size() == 0)
return; return false;
base::flat_map<std::string, float> model_features_for_host; base::flat_map<std::string, float> model_features_for_host;
model_features_for_host.reserve(host_model_features.model_features_size()); model_features_for_host.reserve(host_model_features.model_features_size());
...@@ -618,11 +641,11 @@ void PredictionManager::ProcessAndStoreHostModelFeatures( ...@@ -618,11 +641,11 @@ void PredictionManager::ProcessAndStoreHostModelFeatures(
} }
} }
if (model_features_for_host.size() == 0) if (model_features_for_host.size() == 0)
return; return false;
host_model_features_map_[host_model_features.host()] = host_model_features_map_[host_model_features.host()] =
model_features_for_host; model_features_for_host;
return; return true;
} }
void PredictionManager::MaybeScheduleModelAndHostModelFeaturesFetch() { void PredictionManager::MaybeScheduleModelAndHostModelFeaturesFetch() {
......
...@@ -140,17 +140,19 @@ class PredictionManager ...@@ -140,17 +140,19 @@ class PredictionManager
virtual std::unique_ptr<PredictionModel> CreatePredictionModel( virtual std::unique_ptr<PredictionModel> CreatePredictionModel(
const proto::PredictionModel& model) const; const proto::PredictionModel& model) const;
// Process |host_model_features| to be stored in |host_model_features_map|. // Process |host_model_features| to be stored in memory in the host model
// features map for immediate use and asynchronously write them to the model
// and features store to be persisted.
void UpdateHostModelFeatures( void UpdateHostModelFeatures(
const google::protobuf::RepeatedPtrField<proto::HostModelFeatures>& const google::protobuf::RepeatedPtrField<proto::HostModelFeatures>&
host_model_features); host_model_features);
// Process |prediction_models| to be stored in // Process |prediction_models| to be stored in the in memory optimization
// |optimization_target_prediction_model_map_|. // target prediction model map for immediate use and asynchronously write the
// models to the model and features store to be persisted.
void UpdatePredictionModels( void UpdatePredictionModels(
google::protobuf::RepeatedPtrField<proto::PredictionModel>* const google::protobuf::RepeatedPtrField<proto::PredictionModel>&
prediction_models, prediction_models);
const base::flat_set<std::string>& host_model_features);
private: private:
// Called on construction to register optimization targets, initialize the // Called on construction to register optimization targets, initialize the
...@@ -181,7 +183,9 @@ class PredictionManager ...@@ -181,7 +183,9 @@ class PredictionManager
// Callback when the models and host model features have been fetched from the // Callback when the models and host model features have been fetched from the
// remote Optimization Guide Service and are ready for parsing. Processes the // remote Optimization Guide Service and are ready for parsing. Processes the
// prediction models and the host model features in the response and stores // prediction models and the host model features in the response and stores
// them for use. // them for use. The metadata entry containing the time that updates should be
// fetched from the remote Optimization Guide Service is updated, even when
// the response is empty.
void OnModelsAndHostFeaturesFetched( void OnModelsAndHostFeaturesFetched(
base::Optional<std::unique_ptr<proto::GetModelsResponse>> base::Optional<std::unique_ptr<proto::GetModelsResponse>>
get_models_response_data); get_models_response_data);
...@@ -192,6 +196,16 @@ class PredictionManager ...@@ -192,6 +196,16 @@ class PredictionManager
// true. // true.
void OnStoreInitialized(); void OnStoreInitialized();
// Callback run after prediction models are stored in
// |model_and_features_store_|.
void OnPredictionModelsStored();
// Callback run after host model features are stored in
// |model_and_features_store_|. |fetch_timer_| is stopped and the timer is
// rescheduled based on when models and host model features should be fetched
// again.
void OnHostModelFeaturesStored();
// Request the store to load all the host model features it contains. This // Request the store to load all the host model features it contains. This
// must be completed before any prediction models can be loaded from the // must be completed before any prediction models can be loaded from the
// store. // store.
...@@ -218,13 +232,15 @@ class PredictionManager ...@@ -218,13 +232,15 @@ class PredictionManager
std::unique_ptr<proto::PredictionModel> prediction_model); std::unique_ptr<proto::PredictionModel> prediction_model);
// Process |model| into a PredictionModel object and store it in the // Process |model| into a PredictionModel object and store it in the
// |optimization_target_prediction_model_map_|. // |optimization_target_prediction_model_map_|. Return true if a prediction
void ProcessAndStorePredictionModel(const proto::PredictionModel& model); // model object was created and successfully stored, otherwise false.
bool ProcessAndStorePredictionModel(const proto::PredictionModel& model);
// Process |host_model_features| from the into host model features // Process |host_model_features| from the into host model features
// usable by the PredictionManager. The processed host model features are // usable by the PredictionManager. The processed host model features are
// stored in |host_model_features_map_|. // stored in |host_model_features_map_|. Return true if host model features
void ProcessAndStoreHostModelFeatures( // can be constructed and successfully stored, otherwise, return false.
bool ProcessAndStoreHostModelFeatures(
const proto::HostModelFeatures& host_model_features); const proto::HostModelFeatures& host_model_features);
// Return the time when a prediction model and host model features fetch was // Return the time when a prediction model and host model features fetch was
......
...@@ -178,8 +178,9 @@ class OptimizationGuideStore { ...@@ -178,8 +178,9 @@ class OptimizationGuideStore {
std::unique_ptr<StoreUpdateData> CreateUpdateDataForPredictionModels() const; std::unique_ptr<StoreUpdateData> CreateUpdateDataForPredictionModels() const;
// Updates the prediction models contained in the store. The callback is run // Updates the prediction models contained in the store. The callback is run
// asynchronously after the database stores the prediction models. // asynchronously after the database stores the prediction models. Virtualized
void UpdatePredictionModels( // for testing.
virtual void UpdatePredictionModels(
std::unique_ptr<StoreUpdateData> prediction_models_update_data, std::unique_ptr<StoreUpdateData> prediction_models_update_data,
base::OnceClosure callback); base::OnceClosure callback);
...@@ -212,7 +213,8 @@ class OptimizationGuideStore { ...@@ -212,7 +213,8 @@ class OptimizationGuideStore {
// Updates the host model features contained in the store. The callback is run // Updates the host model features contained in the store. The callback is run
// asynchronously after the database stores the host model features. // asynchronously after the database stores the host model features.
void UpdateHostModelFeatures( // Virtualized for testing.
virtual void UpdateHostModelFeatures(
std::unique_ptr<StoreUpdateData> host_model_features_update_data, std::unique_ptr<StoreUpdateData> host_model_features_update_data,
base::OnceClosure callback); base::OnceClosure callback);
...@@ -248,6 +250,7 @@ class OptimizationGuideStore { ...@@ -248,6 +250,7 @@ class OptimizationGuideStore {
private: private:
friend class OptimizationGuideStoreTest; friend class OptimizationGuideStoreTest;
friend class StoreUpdateData; friend class StoreUpdateData;
friend class TestOptimizationGuideStore;
using EntryKeyPrefix = std::string; using EntryKeyPrefix = std::string;
using EntryKeySet = base::flat_set<EntryKey>; using EntryKeySet = base::flat_set<EntryKey>;
......
...@@ -171,8 +171,7 @@ class OptimizationGuideStoreTest : public testing::Test { ...@@ -171,8 +171,7 @@ class OptimizationGuideStoreTest : public testing::Test {
prediction_model = CreatePredictionModel(); prediction_model = CreatePredictionModel();
prediction_model->mutable_model_info()->set_optimization_target( prediction_model->mutable_model_info()->set_optimization_target(
optimization_target); optimization_target);
update_data->MovePredictionModelIntoUpdateData( update_data->CopyPredictionModelIntoUpdateData(*prediction_model);
std::move(*prediction_model));
} }
// Moves |host_model_features_count| into |update_data|. // Moves |host_model_features_count| into |update_data|.
...@@ -186,8 +185,7 @@ class OptimizationGuideStoreTest : public testing::Test { ...@@ -186,8 +185,7 @@ class OptimizationGuideStoreTest : public testing::Test {
model_feature->set_feature_name("host_feat1"); model_feature->set_feature_name("host_feat1");
model_feature->set_double_value(2.0); model_feature->set_double_value(2.0);
host_model_features.set_host(host_suffix); host_model_features.set_host(host_suffix);
update_data->MoveHostModelFeaturesIntoUpdateData( update_data->CopyHostModelFeaturesIntoUpdateData(host_model_features);
std::move(host_model_features));
} }
} }
......
...@@ -157,8 +157,8 @@ void StoreUpdateData::MoveHintIntoUpdateData(proto::Hint&& hint) { ...@@ -157,8 +157,8 @@ void StoreUpdateData::MoveHintIntoUpdateData(proto::Hint&& hint) {
std::move(entry_proto)); std::move(entry_proto));
} }
void StoreUpdateData::MoveHostModelFeaturesIntoUpdateData( void StoreUpdateData::CopyHostModelFeaturesIntoUpdateData(
proto::HostModelFeatures&& host_model_features) { const proto::HostModelFeatures& host_model_features) {
// All future modifications must be made by the same thread. Note, |this| may // All future modifications must be made by the same thread. Note, |this| may
// have been constructed on another thread. // have been constructed on another thread.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
...@@ -174,14 +174,13 @@ void StoreUpdateData::MoveHostModelFeaturesIntoUpdateData( ...@@ -174,14 +174,13 @@ void StoreUpdateData::MoveHostModelFeaturesIntoUpdateData(
OptimizationGuideStore::StoreEntryType::kHostModelFeatures)); OptimizationGuideStore::StoreEntryType::kHostModelFeatures));
entry_proto.set_expiry_time_secs( entry_proto.set_expiry_time_secs(
expiry_time_->ToDeltaSinceWindowsEpoch().InSeconds()); expiry_time_->ToDeltaSinceWindowsEpoch().InSeconds());
entry_proto.set_allocated_host_model_features( entry_proto.mutable_host_model_features()->CopyFrom(host_model_features);
new proto::HostModelFeatures(std::move(host_model_features)));
entries_to_save_->emplace_back(std::move(host_model_features_entry_key), entries_to_save_->emplace_back(std::move(host_model_features_entry_key),
std::move(entry_proto)); std::move(entry_proto));
} }
void StoreUpdateData::MovePredictionModelIntoUpdateData( void StoreUpdateData::CopyPredictionModelIntoUpdateData(
proto::PredictionModel&& prediction_model) { const proto::PredictionModel& prediction_model) {
// All future modifications must be made by the same thread. Note, |this| may // All future modifications must be made by the same thread. Note, |this| may
// have been constructed on another thread. // have been constructed on another thread.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
...@@ -196,8 +195,7 @@ void StoreUpdateData::MovePredictionModelIntoUpdateData( ...@@ -196,8 +195,7 @@ void StoreUpdateData::MovePredictionModelIntoUpdateData(
proto::StoreEntry entry_proto; proto::StoreEntry entry_proto;
entry_proto.set_entry_type(static_cast<proto::StoreEntryType>( entry_proto.set_entry_type(static_cast<proto::StoreEntryType>(
OptimizationGuideStore::StoreEntryType::kPredictionModel)); OptimizationGuideStore::StoreEntryType::kPredictionModel));
entry_proto.set_allocated_prediction_model( entry_proto.mutable_prediction_model()->CopyFrom(prediction_model);
new proto::PredictionModel(std::move(prediction_model)));
entries_to_save_->emplace_back(std::move(prediction_model_entry_key), entries_to_save_->emplace_back(std::move(prediction_model_entry_key),
std::move(entry_proto)); std::move(entry_proto));
} }
......
...@@ -65,15 +65,13 @@ class StoreUpdateData { ...@@ -65,15 +65,13 @@ class StoreUpdateData {
// called, |hint| is no longer valid. // called, |hint| is no longer valid.
void MoveHintIntoUpdateData(proto::Hint&& hint); void MoveHintIntoUpdateData(proto::Hint&& hint);
// Moves |host_model_features| in this update data. Afterwards, // Copies |host_model_features| into this update data.
// |host_model_features| is no longer valid. void CopyHostModelFeaturesIntoUpdateData(
void MoveHostModelFeaturesIntoUpdateData( const proto::HostModelFeatures& host_model_features);
proto::HostModelFeatures&& host_model_features);
// Copies |prediction_model| into this update data.
// Moves |prediction_model| in this update data. Afterwards, void CopyPredictionModelIntoUpdateData(
// |prediction_model| is no longer valid. const proto::PredictionModel& prediction_model);
void MovePredictionModelIntoUpdateData(
proto::PredictionModel&& prediction_model);
// Returns the store entry updates along with ownership to them. // Returns the store entry updates along with ownership to them.
std::unique_ptr<EntryVector> TakeUpdateEntries(); std::unique_ptr<EntryVector> TakeUpdateEntries();
......
...@@ -82,8 +82,7 @@ TEST(StoreUpdateDataTest, BuildPredictionModelUpdateData) { ...@@ -82,8 +82,7 @@ TEST(StoreUpdateDataTest, BuildPredictionModelUpdateData) {
std::unique_ptr<StoreUpdateData> prediction_model_update = std::unique_ptr<StoreUpdateData> prediction_model_update =
StoreUpdateData::CreatePredictionModelStoreUpdateData(); StoreUpdateData::CreatePredictionModelStoreUpdateData();
prediction_model_update->MovePredictionModelIntoUpdateData( prediction_model_update->CopyPredictionModelIntoUpdateData(prediction_model);
std::move(prediction_model));
EXPECT_FALSE(prediction_model_update->component_version().has_value()); EXPECT_FALSE(prediction_model_update->component_version().has_value());
EXPECT_FALSE(prediction_model_update->update_time().has_value()); EXPECT_FALSE(prediction_model_update->update_time().has_value());
// Verify there is 1 store entry. // Verify there is 1 store entry.
...@@ -106,7 +105,7 @@ TEST(StoreUpdateDataTest, BuildHostModelFeaturesUpdateData) { ...@@ -106,7 +105,7 @@ TEST(StoreUpdateDataTest, BuildHostModelFeaturesUpdateData) {
host_model_features_update_time + host_model_features_update_time +
optimization_guide::features:: optimization_guide::features::
StoredHostModelFeaturesFreshnessDuration()); StoredHostModelFeaturesFreshnessDuration());
host_model_features_update->MoveHostModelFeaturesIntoUpdateData( host_model_features_update->CopyHostModelFeaturesIntoUpdateData(
std::move(host_model_features)); std::move(host_model_features));
EXPECT_FALSE(host_model_features_update->component_version().has_value()); EXPECT_FALSE(host_model_features_update->component_version().has_value());
EXPECT_TRUE(host_model_features_update->update_time().has_value()); EXPECT_TRUE(host_model_features_update->update_time().has_value());
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment