blob: 1e61db75379d52b17057ba9bcd8bbce7cdc77db2 [file] [log] [blame]
//
// Copyright (C) 2014 The Android Open Source Project
//
// 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.
//
#include "update_engine/update_manager/evaluation_context.h"
#include <memory>
#include <string>
#include <base/bind.h>
#include <brillo/message_loops/fake_message_loop.h>
#include <brillo/message_loops/message_loop_utils.h>
#include <gtest/gtest.h>
#include "update_engine/common/fake_clock.h"
#include "update_engine/update_manager/fake_variable.h"
#include "update_engine/update_manager/generic_variables.h"
#include "update_engine/update_manager/mock_variable.h"
#include "update_engine/update_manager/umtest_utils.h"
using base::Bind;
using base::Closure;
using base::Time;
using base::TimeDelta;
using brillo::MessageLoop;
using brillo::MessageLoopRunMaxIterations;
using brillo::MessageLoopRunUntil;
using chromeos_update_engine::FakeClock;
using std::string;
using std::unique_ptr;
using testing::Return;
using testing::StrictMock;
using testing::_;
namespace chromeos_update_manager {
namespace {
void DoNothing() {}
// Sets the value of the passed pointer to true.
void SetTrue(bool* value) {
*value = true;
}
bool GetBoolean(bool* value) {
return *value;
}
template<typename T>
void ReadVar(scoped_refptr<EvaluationContext> ec, Variable<T>* var) {
ec->GetValue(var);
}
// Runs |evaluation|; if the value pointed by |count_p| is greater than zero,
// decrement it and schedule a reevaluation; otherwise, writes true to |done_p|.
void EvaluateRepeatedly(Closure evaluation, scoped_refptr<EvaluationContext> ec,
int* count_p, bool* done_p) {
evaluation.Run();
// Schedule reevaluation if needed.
if (*count_p > 0) {
Closure closure = Bind(EvaluateRepeatedly, evaluation, ec, count_p, done_p);
ASSERT_TRUE(ec->RunOnValueChangeOrTimeout(closure))
<< "Failed to schedule reevaluation, count_p=" << *count_p;
(*count_p)--;
} else {
*done_p = true;
}
}
} // namespace
class UmEvaluationContextTest : public ::testing::Test {
protected:
void SetUp() override {
loop_.SetAsCurrent();
// Apr 22, 2009 19:25:00 UTC (this is a random reference point).
fake_clock_.SetMonotonicTime(Time::FromTimeT(1240428300));
// Mar 2, 2006 1:23:45 UTC.
fake_clock_.SetWallclockTime(Time::FromTimeT(1141262625));
eval_ctx_ = new EvaluationContext(
&fake_clock_, default_timeout_, default_timeout_,
unique_ptr<base::Callback<void(EvaluationContext*)>>(nullptr));
}
void TearDown() override {
// Ensure that the evaluation context did not leak and is actually being
// destroyed.
if (eval_ctx_) {
base::WeakPtr<EvaluationContext> eval_ctx_weak_alias =
eval_ctx_->weak_ptr_factory_.GetWeakPtr();
ASSERT_NE(nullptr, eval_ctx_weak_alias.get());
eval_ctx_ = nullptr;
EXPECT_EQ(nullptr, eval_ctx_weak_alias.get())
<< "The evaluation context was not destroyed! This is likely a bug "
"in how the test was written, look for leaking handles to the EC, "
"possibly through closure objects.";
}
// Check that the evaluation context removed all the observers.
EXPECT_TRUE(fake_int_var_.observer_list_.empty());
EXPECT_TRUE(fake_async_var_.observer_list_.empty());
EXPECT_TRUE(fake_const_var_.observer_list_.empty());
EXPECT_TRUE(fake_poll_var_.observer_list_.empty());
EXPECT_FALSE(loop_.PendingTasks());
}
TimeDelta default_timeout_ = TimeDelta::FromSeconds(5);
brillo::FakeMessageLoop loop_{nullptr};
FakeClock fake_clock_;
scoped_refptr<EvaluationContext> eval_ctx_;
// FakeVariables used for testing the EvaluationContext. These are required
// here to prevent them from going away *before* the EvaluationContext under
// test does, which keeps a reference to them.
FakeVariable<bool> fail_var_ = {"fail_var", kVariableModePoll};
FakeVariable<int> fake_int_var_ = {"fake_int", kVariableModePoll};
FakeVariable<string> fake_async_var_ = {"fake_async", kVariableModeAsync};
FakeVariable<string> fake_const_var_ = {"fake_const", kVariableModeConst};
FakeVariable<string> fake_poll_var_ = {"fake_poll",
TimeDelta::FromSeconds(1)};
StrictMock<MockVariable<string>> mock_var_async_ {
"mock_var_async", kVariableModeAsync};
StrictMock<MockVariable<string>> mock_var_poll_ {
"mock_var_poll", kVariableModePoll};
};
TEST_F(UmEvaluationContextTest, GetValueFails) {
// FakeVariable is initialized as returning null.
EXPECT_EQ(nullptr, eval_ctx_->GetValue(&fake_int_var_));
}
TEST_F(UmEvaluationContextTest, GetValueFailsWithInvalidVar) {
EXPECT_EQ(nullptr, eval_ctx_->GetValue(static_cast<Variable<int>*>(nullptr)));
}
TEST_F(UmEvaluationContextTest, GetValueReturns) {
const int* p_fake_int;
fake_int_var_.reset(new int(42));
p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
ASSERT_NE(nullptr, p_fake_int);
EXPECT_EQ(42, *p_fake_int);
}
TEST_F(UmEvaluationContextTest, GetValueCached) {
const int* p_fake_int;
fake_int_var_.reset(new int(42));
p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
// Check that if the variable changes, the EvaluationContext keeps returning
// the cached value.
fake_int_var_.reset(new int(5));
p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
ASSERT_NE(nullptr, p_fake_int);
EXPECT_EQ(42, *p_fake_int);
}
TEST_F(UmEvaluationContextTest, GetValueCachesNull) {
const int* p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
EXPECT_EQ(nullptr, p_fake_int);
fake_int_var_.reset(new int(42));
// A second attempt to read the variable should not work because this
// EvaluationContext already got a null value.
p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
EXPECT_EQ(nullptr, p_fake_int);
}
TEST_F(UmEvaluationContextTest, GetValueMixedTypes) {
const int* p_fake_int;
const string* p_fake_string;
fake_int_var_.reset(new int(42));
fake_poll_var_.reset(new string("Hello world!"));
// Check that the EvaluationContext can handle multiple Variable types. This
// is mostly a compile-time check due to the template nature of this method.
p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
p_fake_string = eval_ctx_->GetValue(&fake_poll_var_);
ASSERT_NE(nullptr, p_fake_int);
EXPECT_EQ(42, *p_fake_int);
ASSERT_NE(nullptr, p_fake_string);
EXPECT_EQ("Hello world!", *p_fake_string);
}
// Test that we don't schedule an event if there's no variable to wait for.
TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutWithoutVariables) {
fake_const_var_.reset(new string("Hello world!"));
EXPECT_EQ(*eval_ctx_->GetValue(&fake_const_var_), "Hello world!");
EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
}
// Test that reevaluation occurs when an async variable it depends on changes.
TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutWithVariables) {
fake_async_var_.reset(new string("Async value"));
eval_ctx_->GetValue(&fake_async_var_);
bool value = false;
EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
// Check that the scheduled callback isn't run until we signal a ValueChaged.
MessageLoopRunMaxIterations(MessageLoop::current(), 100);
EXPECT_FALSE(value);
fake_async_var_.NotifyValueChanged();
EXPECT_FALSE(value);
// Ensure that the scheduled callback isn't run until we are back on the main
// loop.
MessageLoopRunMaxIterations(MessageLoop::current(), 100);
EXPECT_TRUE(value);
}
// Test that we don't re-schedule the events if we are attending one.
TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutCalledTwice) {
fake_async_var_.reset(new string("Async value"));
eval_ctx_->GetValue(&fake_async_var_);
bool value = false;
EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
// The scheduled event should still work.
fake_async_var_.NotifyValueChanged();
MessageLoopRunMaxIterations(MessageLoop::current(), 100);
EXPECT_TRUE(value);
}
// Test that reevaluation occurs when a polling timeout fires.
TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutRunsFromTimeout) {
fake_poll_var_.reset(new string("Polled value"));
eval_ctx_->GetValue(&fake_poll_var_);
bool value = false;
EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
// Check that the scheduled callback isn't run until the timeout occurs.
MessageLoopRunMaxIterations(MessageLoop::current(), 10);
EXPECT_FALSE(value);
MessageLoopRunUntil(MessageLoop::current(),
TimeDelta::FromSeconds(10),
Bind(&GetBoolean, &value));
EXPECT_TRUE(value);
}
// Test that callback is called when evaluation context expires, and that it
// cannot be used again unless the expiration deadline is reset.
TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutExpires) {
fake_async_var_.reset(new string("Async value"));
eval_ctx_->GetValue(&fake_async_var_);
bool value = false;
EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
// Check that the scheduled callback isn't run until the timeout occurs.
MessageLoopRunMaxIterations(MessageLoop::current(), 10);
EXPECT_FALSE(value);
MessageLoopRunUntil(MessageLoop::current(),
TimeDelta::FromSeconds(10),
Bind(&GetBoolean, &value));
EXPECT_TRUE(value);
// Ensure that we cannot reschedule an evaluation.
EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
// Ensure that we can reschedule an evaluation after resetting expiration.
eval_ctx_->ResetExpiration();
EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
}
// Test that we clear the events when destroying the EvaluationContext.
TEST_F(UmEvaluationContextTest, RemoveObserversAndTimeoutTest) {
fake_async_var_.reset(new string("Async value"));
eval_ctx_->GetValue(&fake_async_var_);
bool value = false;
EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
eval_ctx_ = nullptr;
// This should not trigger the callback since the EvaluationContext waiting
// for it is gone, and it should have remove all its observers.
fake_async_var_.NotifyValueChanged();
MessageLoopRunMaxIterations(MessageLoop::current(), 100);
EXPECT_FALSE(value);
}
// Scheduling two reevaluations from the callback should succeed.
TEST_F(UmEvaluationContextTest,
RunOnValueChangeOrTimeoutReevaluatesRepeatedly) {
fake_poll_var_.reset(new string("Polled value"));
Closure evaluation = Bind(ReadVar<string>, eval_ctx_, &fake_poll_var_);
int num_reevaluations = 2;
bool done = false;
// Run the evaluation once.
evaluation.Run();
// Schedule repeated reevaluations.
Closure closure = Bind(EvaluateRepeatedly, evaluation, eval_ctx_,
&num_reevaluations, &done);
ASSERT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(closure));
MessageLoopRunUntil(MessageLoop::current(),
TimeDelta::FromSeconds(10),
Bind(&GetBoolean, &done));
EXPECT_EQ(0, num_reevaluations);
}
// Test that we can delete the EvaluationContext while having pending events.
TEST_F(UmEvaluationContextTest, ObjectDeletedWithPendingEventsTest) {
fake_async_var_.reset(new string("Async value"));
fake_poll_var_.reset(new string("Polled value"));
eval_ctx_->GetValue(&fake_async_var_);
eval_ctx_->GetValue(&fake_poll_var_);
EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
// TearDown() checks for leaked observers on this async_variable, which means
// that our object is still alive after removing its reference.
}
// Test that timed events fired after removal of the EvaluationContext don't
// crash.
TEST_F(UmEvaluationContextTest, TimeoutEventAfterDeleteTest) {
FakeVariable<string> fake_short_poll_var = {"fake_short_poll",
TimeDelta::FromSeconds(1)};
fake_short_poll_var.reset(new string("Polled value"));
eval_ctx_->GetValue(&fake_short_poll_var);
bool value = false;
EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
// Remove the last reference to the EvaluationContext and run the loop for
// 10 seconds to give time to the main loop to trigger the timeout Event (of 1
// second). Our callback should not be called because the EvaluationContext
// was removed before the timeout event is attended.
eval_ctx_ = nullptr;
MessageLoopRunUntil(MessageLoop::current(),
TimeDelta::FromSeconds(10),
Bind(&GetBoolean, &value));
EXPECT_FALSE(value);
}
TEST_F(UmEvaluationContextTest, DefaultTimeout) {
// Test that the evaluation timeout calculation uses the default timeout on
// setup.
EXPECT_CALL(mock_var_async_, GetValue(default_timeout_, _))
.WillOnce(Return(nullptr));
EXPECT_EQ(nullptr, eval_ctx_->GetValue(&mock_var_async_));
}
TEST_F(UmEvaluationContextTest, TimeoutUpdatesWithMonotonicTime) {
fake_clock_.SetMonotonicTime(
fake_clock_.GetMonotonicTime() + TimeDelta::FromSeconds(1));
TimeDelta timeout = default_timeout_ - TimeDelta::FromSeconds(1);
EXPECT_CALL(mock_var_async_, GetValue(timeout, _))
.WillOnce(Return(nullptr));
EXPECT_EQ(nullptr, eval_ctx_->GetValue(&mock_var_async_));
}
TEST_F(UmEvaluationContextTest, ResetEvaluationResetsTimesWallclock) {
Time cur_time = fake_clock_.GetWallclockTime();
// Advance the time on the clock but don't call ResetEvaluation yet.
fake_clock_.SetWallclockTime(cur_time + TimeDelta::FromSeconds(4));
EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
cur_time - TimeDelta::FromSeconds(1)));
EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(cur_time));
EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(
cur_time + TimeDelta::FromSeconds(1)));
// Call ResetEvaluation now, which should use the new evaluation time.
eval_ctx_->ResetEvaluation();
cur_time = fake_clock_.GetWallclockTime();
EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
cur_time - TimeDelta::FromSeconds(1)));
EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(cur_time));
EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(
cur_time + TimeDelta::FromSeconds(1)));
}
TEST_F(UmEvaluationContextTest, ResetEvaluationResetsTimesMonotonic) {
Time cur_time = fake_clock_.GetMonotonicTime();
// Advance the time on the clock but don't call ResetEvaluation yet.
fake_clock_.SetMonotonicTime(cur_time + TimeDelta::FromSeconds(4));
EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
cur_time - TimeDelta::FromSeconds(1)));
EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(cur_time));
EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(
cur_time + TimeDelta::FromSeconds(1)));
// Call ResetEvaluation now, which should use the new evaluation time.
eval_ctx_->ResetEvaluation();
cur_time = fake_clock_.GetMonotonicTime();
EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
cur_time - TimeDelta::FromSeconds(1)));
EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(cur_time));
EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(
cur_time + TimeDelta::FromSeconds(1)));
}
TEST_F(UmEvaluationContextTest,
IsWallclockTimeGreaterThanSignalsTriggerReevaluation) {
EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(
fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(1)));
// The "false" from IsWallclockTimeGreaterThan means that's not that timestamp
// yet, so this should schedule a callback for when that happens.
EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
}
TEST_F(UmEvaluationContextTest,
IsMonotonicTimeGreaterThanSignalsTriggerReevaluation) {
EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(
fake_clock_.GetMonotonicTime() + TimeDelta::FromSeconds(1)));
// The "false" from IsMonotonicTimeGreaterThan means that's not that timestamp
// yet, so this should schedule a callback for when that happens.
EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
}
TEST_F(UmEvaluationContextTest,
IsWallclockTimeGreaterThanDoesntRecordPastTimestamps) {
// IsWallclockTimeGreaterThan() should ignore timestamps on the past for
// reevaluation.
EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(20)));
EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(1)));
// Callback should not be scheduled.
EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
}
TEST_F(UmEvaluationContextTest,
IsMonotonicTimeGreaterThanDoesntRecordPastTimestamps) {
// IsMonotonicTimeGreaterThan() should ignore timestamps on the past for
// reevaluation.
EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
fake_clock_.GetMonotonicTime() - TimeDelta::FromSeconds(20)));
EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
fake_clock_.GetMonotonicTime() - TimeDelta::FromSeconds(1)));
// Callback should not be scheduled.
EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
}
TEST_F(UmEvaluationContextTest, DumpContext) {
// |fail_var_| yield "(no value)" since it is unset.
eval_ctx_->GetValue(&fail_var_);
// Check that this is included.
fake_int_var_.reset(new int(42));
eval_ctx_->GetValue(&fake_int_var_);
// Check that double-quotes are escaped properly.
fake_poll_var_.reset(new string("Hello \"world\"!"));
eval_ctx_->GetValue(&fake_poll_var_);
// Note that the variables are printed in alphabetical order. Also
// see UmEvaluationContextText::SetUp() where the values used for
// |evaluation_start_{monotonic,wallclock| are set.
EXPECT_EQ("{\n"
" \"evaluation_start_monotonic\": \"4/22/2009 19:25:00 GMT\",\n"
" \"evaluation_start_wallclock\": \"3/2/2006 1:23:45 GMT\",\n"
" \"variables\": {\n"
" \"fail_var\": \"(no value)\",\n"
" \"fake_int\": \"42\",\n"
" \"fake_poll\": \"Hello \\\"world\\\"!\"\n"
" }\n"
"}",
eval_ctx_->DumpContext());
}
} // namespace chromeos_update_manager