blob: 486702054c5782d6cae49098d0b3dc766e8707e9 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/updater/win/task_scheduler.h"
#include <lmsname.h>
#include <mstask.h>
#include <security.h>
#include <shlobj.h>
#include <taskschd.h>
#include <memory>
#include <string>
#include <vector>
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/bind.h"
#include "base/test/test_timeouts.h"
#include "base/time/time.h"
#include "base/win/scoped_bstr.h"
#include "chrome/updater/test/integration_tests_impl.h"
#include "chrome/updater/test_scope.h"
#include "chrome/updater/updater_branding.h"
#include "chrome/updater/util/unittest_util.h"
#include "chrome/updater/util/util.h"
#include "chrome/updater/util/win_util.h"
#include "chrome/updater/win/test/test_executables.h"
#include "chrome/updater/win/test/test_strings.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace updater {
namespace {
// The name of the tasks as will be visible in the scheduler so we know we can
// safely delete them if they get stuck for whatever reason.
const wchar_t kTaskName1[] = L"Chrome Updater Test task 1 (delete me)";
const wchar_t kTaskName2[] = L"Chrome Updater Test task 2 (delete me)";
const wchar_t kPrefixTaskName1[] = L"Chrome Updater Test task 1";
const wchar_t kPrefixTaskName2[] = L"Chrome Updater Test task 2";
// Optional descriptions for the tasks above.
const wchar_t kTaskDescription1[] =
L"Task 1 used only for Chrome Updater unit testing.";
const wchar_t kTaskDescription2[] =
L"Task 2 used only for Chrome Updater unit testing.";
// A command-line switch used in testing.
const char kUnitTestSwitch[] = "a_switch";
class TaskSchedulerTests : public ::testing::Test {
public:
void SetUp() override {
task_scheduler_ = TaskScheduler::CreateInstance(GetTestScope());
ASSERT_TRUE(task_scheduler_);
EXPECT_TRUE(IsServiceRunning(SERVICE_SCHEDULE));
ASSERT_TRUE(test::KillProcesses(kTestProcessExecutableName, 0))
<< test::PrintProcesses(kTestProcessExecutableName);
ASSERT_FALSE(test::IsProcessRunning(kTestProcessExecutableName));
}
void TearDown() override {
EXPECT_TRUE(task_scheduler_->DeleteTask(kTaskName1));
EXPECT_TRUE(task_scheduler_->DeleteTask(kTaskName2));
EXPECT_FALSE(test::IsProcessRunning(kTestProcessExecutableName))
<< test::PrintProcesses(kTestProcessExecutableName);
EXPECT_TRUE(test::KillProcesses(kTestProcessExecutableName, 0));
}
// Converts a base::Time that is in UTC and returns the corresponding local
// time on the current system.
base::Time UTCTimeToLocalTime(const base::Time& time_utc) {
const FILETIME file_time_utc = time_utc.ToFileTime();
FILETIME file_time_local = {};
SYSTEMTIME system_time_utc = {};
SYSTEMTIME system_time_local = {};
// We do not use ::FileTimeToLocalFileTime, since it uses the current
// settings for the time zone and daylight saving time, instead of the
// settings at the time of `time_utc`.
if (!::FileTimeToSystemTime(&file_time_utc, &system_time_utc) ||
!::SystemTimeToTzSpecificLocalTime(nullptr, &system_time_utc,
&system_time_local) ||
!::SystemTimeToFileTime(&system_time_local, &file_time_local)) {
return base::Time();
}
return base::Time::FromFileTime(file_time_local);
}
void RunTaskTest(TaskScheduler::TriggerType trigger_type) {
base::CommandLine command_line =
GetTestProcessCommandLine(GetTestScope(), test::GetTestName());
// Create a unique name for a shared event to be waited for in this process
// and signaled in the test process to confirm it was scheduled and ran.
test::EventHolder event_holder(test::CreateWaitableEventForTest());
command_line.AppendSwitchNative(kTestEventToSignal, event_holder.name);
EXPECT_TRUE(task_scheduler_->RegisterTask(
kTaskName1, kTaskDescription1, command_line, trigger_type, false));
// Check that the created task matches the trigger it was created with.
TaskScheduler::TaskInfo info;
EXPECT_TRUE(task_scheduler_->GetTaskInfo(kTaskName1, &info));
EXPECT_EQ(info.trigger_types, trigger_type);
if (trigger_type != TaskScheduler::TRIGGER_TYPE_NOW) {
EXPECT_TRUE(task_scheduler_->StartTask(kTaskName1));
}
VLOG(0) << [this]() {
TaskScheduler::TaskInfo info;
EXPECT_TRUE(task_scheduler_->GetTaskInfo(kTaskName1, &info));
return info;
}();
EXPECT_TRUE(
event_holder.event.TimedWait(TestTimeouts::action_max_timeout()));
EXPECT_TRUE(test::WaitFor(base::BindLambdaForTesting(
[&]() { return !task_scheduler_->IsTaskRunning(kTaskName1); })));
if (trigger_type == TaskScheduler::TRIGGER_TYPE_NOW) {
base::Time next_run_time;
EXPECT_FALSE(
task_scheduler_->GetNextTaskRunTime(kTaskName1, &next_run_time));
}
test::PrintLog(GetTestScope());
}
void RunGetTaskInfoTriggerTypesTest(int expected_trigger_types) {
base::CommandLine command_line =
GetTestProcessCommandLine(GetTestScope(), test::GetTestName());
EXPECT_TRUE(task_scheduler_->RegisterTask(kTaskName1, kTaskDescription1,
command_line,
expected_trigger_types, false));
TaskScheduler::TaskInfo info;
EXPECT_TRUE(task_scheduler_->GetTaskInfo(kTaskName1, &info));
EXPECT_EQ(info.trigger_types, expected_trigger_types);
EXPECT_TRUE(task_scheduler_->DeleteTask(kTaskName1));
}
protected:
scoped_refptr<TaskScheduler> task_scheduler_;
};
} // namespace
TEST_F(TaskSchedulerTests, DeleteAndIsRegistered) {
EXPECT_FALSE(task_scheduler_->IsTaskRegistered(kTaskName1));
// Construct the full-path of the test executable.
base::CommandLine command_line =
GetTestProcessCommandLine(GetTestScope(), test::GetTestName());
// Validate that the task is properly seen as registered when it is.
EXPECT_TRUE(
task_scheduler_->RegisterTask(kTaskName1, kTaskDescription1, command_line,
TaskScheduler::TRIGGER_TYPE_HOURLY, false));
EXPECT_TRUE(task_scheduler_->IsTaskRegistered(kTaskName1));
// Validate that a task with a similar name is not seen as registered.
EXPECT_FALSE(task_scheduler_->IsTaskRegistered(kTaskName2));
// While the first one is still seen as registered, until it gets deleted.
EXPECT_TRUE(task_scheduler_->IsTaskRegistered(kTaskName1));
EXPECT_TRUE(task_scheduler_->DeleteTask(kTaskName1));
EXPECT_FALSE(task_scheduler_->IsTaskRegistered(kTaskName1));
// The other task should still not be registered.
EXPECT_FALSE(task_scheduler_->IsTaskRegistered(kTaskName2));
}
TEST_F(TaskSchedulerTests, RunAProgramNow) {
RunTaskTest(TaskScheduler::TRIGGER_TYPE_NOW);
}
TEST_F(TaskSchedulerTests, StartTask) {
RunTaskTest(TaskScheduler::TRIGGER_TYPE_HOURLY);
}
TEST_F(TaskSchedulerTests, Hourly) {
base::CommandLine command_line =
GetTestProcessCommandLine(GetTestScope(), test::GetTestName());
base::Time now(base::Time::NowFromSystemTime());
EXPECT_TRUE(
task_scheduler_->RegisterTask(kTaskName1, kTaskDescription1, command_line,
TaskScheduler::TRIGGER_TYPE_HOURLY, false));
EXPECT_TRUE(task_scheduler_->IsTaskRegistered(kTaskName1));
base::Time next_run_time;
EXPECT_TRUE(task_scheduler_->GetNextTaskRunTime(kTaskName1, &next_run_time));
// Check that the task starts approximately 5 minutes from the current time.
EXPECT_GT(next_run_time, UTCTimeToLocalTime(now + base::Minutes(3)));
EXPECT_LT(next_run_time, UTCTimeToLocalTime(now + base::Minutes(7)));
// Check that the task has a hourly trigger.
TaskScheduler::TaskInfo info;
EXPECT_TRUE(task_scheduler_->GetTaskInfo(kTaskName1, &info));
EXPECT_EQ(info.trigger_types, TaskScheduler::TRIGGER_TYPE_HOURLY);
EXPECT_TRUE(task_scheduler_->DeleteTask(kTaskName1));
EXPECT_FALSE(task_scheduler_->IsTaskRegistered(kTaskName1));
EXPECT_FALSE(task_scheduler_->GetNextTaskRunTime(kTaskName1, &next_run_time));
}
TEST_F(TaskSchedulerTests, EveryFiveHours) {
base::CommandLine command_line =
GetTestProcessCommandLine(GetTestScope(), test::GetTestName());
base::Time now(base::Time::NowFromSystemTime());
EXPECT_TRUE(task_scheduler_->RegisterTask(
kTaskName1, kTaskDescription1, command_line,
TaskScheduler::TRIGGER_TYPE_EVERY_FIVE_HOURS, false));
EXPECT_TRUE(task_scheduler_->IsTaskRegistered(kTaskName1));
base::Time next_run_time;
EXPECT_TRUE(task_scheduler_->GetNextTaskRunTime(kTaskName1, &next_run_time));
// Check that the task starts approximately 5 minutes from the current time.
EXPECT_GT(next_run_time, UTCTimeToLocalTime(now + base::Minutes(3)));
EXPECT_LT(next_run_time, UTCTimeToLocalTime(now + base::Minutes(7)));
// Check that the task has a five hour trigger.
TaskScheduler::TaskInfo info;
EXPECT_TRUE(task_scheduler_->GetTaskInfo(kTaskName1, &info));
EXPECT_EQ(info.trigger_types, TaskScheduler::TRIGGER_TYPE_EVERY_FIVE_HOURS);
EXPECT_TRUE(task_scheduler_->DeleteTask(kTaskName1));
EXPECT_FALSE(task_scheduler_->IsTaskRegistered(kTaskName1));
EXPECT_FALSE(task_scheduler_->GetNextTaskRunTime(kTaskName1, &next_run_time));
}
TEST_F(TaskSchedulerTests, SetTaskEnabled) {
base::CommandLine command_line =
GetTestProcessCommandLine(GetTestScope(), test::GetTestName());
EXPECT_TRUE(
task_scheduler_->RegisterTask(kTaskName1, kTaskDescription1, command_line,
TaskScheduler::TRIGGER_TYPE_HOURLY, false));
EXPECT_TRUE(task_scheduler_->IsTaskRegistered(kTaskName1));
EXPECT_TRUE(task_scheduler_->IsTaskEnabled(kTaskName1));
EXPECT_TRUE(task_scheduler_->SetTaskEnabled(kTaskName1, true));
EXPECT_TRUE(task_scheduler_->IsTaskEnabled(kTaskName1));
EXPECT_TRUE(task_scheduler_->SetTaskEnabled(kTaskName1, false));
EXPECT_FALSE(task_scheduler_->IsTaskEnabled(kTaskName1));
EXPECT_TRUE(task_scheduler_->SetTaskEnabled(kTaskName1, true));
EXPECT_TRUE(task_scheduler_->IsTaskEnabled(kTaskName1));
}
TEST_F(TaskSchedulerTests, IsTaskRunning) {
base::CommandLine command_line =
GetTestProcessCommandLine(GetTestScope(), test::GetTestName());
// Create a unique name for a shared event to be waited for in the task and
// signaled in this test.
test::EventHolder event_holder(test::CreateWaitableEventForTest());
command_line.AppendSwitchNative(kTestEventToWaitOn, event_holder.name);
EXPECT_TRUE(
task_scheduler_->RegisterTask(kTaskName1, kTaskDescription1, command_line,
TaskScheduler::TRIGGER_TYPE_NOW, false));
EXPECT_TRUE(test::WaitFor(base::BindLambdaForTesting(
[&]() { return task_scheduler_->IsTaskRunning(kTaskName1); })));
EXPECT_EQ(test::FindProcesses(kTestProcessExecutableName).size(), 1U);
event_holder.event.Signal();
EXPECT_TRUE(test::WaitFor(base::BindLambdaForTesting(
[&]() { return !task_scheduler_->IsTaskRunning(kTaskName1); })));
EXPECT_TRUE(test::FindProcesses(kTestProcessExecutableName).empty());
}
TEST_F(TaskSchedulerTests, GetTaskNameList) {
base::CommandLine command_line =
GetTestProcessCommandLine(GetTestScope(), test::GetTestName());
EXPECT_TRUE(
task_scheduler_->RegisterTask(kTaskName1, kTaskDescription1, command_line,
TaskScheduler::TRIGGER_TYPE_HOURLY, false));
EXPECT_TRUE(task_scheduler_->IsTaskRegistered(kTaskName1));
EXPECT_TRUE(
task_scheduler_->RegisterTask(kTaskName2, kTaskDescription2, command_line,
TaskScheduler::TRIGGER_TYPE_HOURLY, false));
EXPECT_TRUE(task_scheduler_->IsTaskRegistered(kTaskName2));
std::vector<std::wstring> task_names;
EXPECT_TRUE(task_scheduler_->GetTaskNameList(&task_names));
EXPECT_TRUE(base::Contains(task_names, kTaskName1));
EXPECT_TRUE(base::Contains(task_names, kTaskName2));
}
TEST_F(TaskSchedulerTests, FindFirstTaskName) {
base::CommandLine command_line =
GetTestProcessCommandLine(GetTestScope(), test::GetTestName());
EXPECT_TRUE(
task_scheduler_->RegisterTask(kTaskName1, kTaskDescription1, command_line,
TaskScheduler::TRIGGER_TYPE_HOURLY, false));
EXPECT_TRUE(task_scheduler_->IsTaskRegistered(kTaskName1));
EXPECT_TRUE(
task_scheduler_->RegisterTask(kTaskName2, kTaskDescription2, command_line,
TaskScheduler::TRIGGER_TYPE_HOURLY, false));
EXPECT_TRUE(task_scheduler_->IsTaskRegistered(kTaskName2));
EXPECT_STREQ(kTaskName1,
task_scheduler_->FindFirstTaskName(kPrefixTaskName1).c_str());
EXPECT_STREQ(kTaskName2,
task_scheduler_->FindFirstTaskName(kPrefixTaskName2).c_str());
}
TEST_F(TaskSchedulerTests, GetTasksIncludesHidden) {
base::CommandLine command_line =
GetTestProcessCommandLine(GetTestScope(), test::GetTestName());
EXPECT_TRUE(
task_scheduler_->RegisterTask(kTaskName1, kTaskDescription1, command_line,
TaskScheduler::TRIGGER_TYPE_HOURLY, true));
EXPECT_TRUE(task_scheduler_->IsTaskRegistered(kTaskName1));
std::vector<std::wstring> task_names;
EXPECT_TRUE(task_scheduler_->GetTaskNameList(&task_names));
EXPECT_TRUE(base::Contains(task_names, kTaskName1));
}
TEST_F(TaskSchedulerTests, GetTaskInfoExecActions) {
base::CommandLine command_line1({L"c:\\test\\process 1.exe"});
EXPECT_TRUE(task_scheduler_->RegisterTask(
kTaskName1, kTaskDescription1, command_line1,
TaskScheduler::TRIGGER_TYPE_HOURLY, false));
EXPECT_TRUE(task_scheduler_->IsTaskRegistered(kTaskName1));
TaskScheduler::TaskInfo info;
EXPECT_FALSE(task_scheduler_->GetTaskInfo(kTaskName2, &info));
EXPECT_EQ(0UL, info.exec_actions.size());
EXPECT_TRUE(task_scheduler_->GetTaskInfo(kTaskName1, &info));
ASSERT_EQ(1UL, info.exec_actions.size());
EXPECT_EQ(base::CommandLine::QuoteForCommandLineToArgvW(
command_line1.GetProgram().value()),
info.exec_actions[0].application_path.value());
EXPECT_EQ(command_line1.GetArgumentsString(), info.exec_actions[0].arguments);
base::CommandLine command_line2({L"c:\\test\\process2.exe"});
command_line2.AppendSwitch(kUnitTestSwitch);
EXPECT_TRUE(task_scheduler_->RegisterTask(
kTaskName2, kTaskDescription2, command_line2,
TaskScheduler::TRIGGER_TYPE_HOURLY, false));
EXPECT_TRUE(task_scheduler_->IsTaskRegistered(kTaskName2));
// The |info| struct is re-used to ensure that new task information overwrites
// the previous contents of the struct.
EXPECT_TRUE(task_scheduler_->GetTaskInfo(kTaskName2, &info));
ASSERT_EQ(1UL, info.exec_actions.size());
EXPECT_EQ(base::CommandLine::QuoteForCommandLineToArgvW(
command_line2.GetProgram().value()),
info.exec_actions[0].application_path.value());
EXPECT_EQ(command_line2.GetArgumentsString(), info.exec_actions[0].arguments);
}
TEST_F(TaskSchedulerTests, GetTaskInfoNameAndDescription) {
base::CommandLine command_line1 =
GetTestProcessCommandLine(GetTestScope(), test::GetTestName());
EXPECT_TRUE(task_scheduler_->RegisterTask(
kTaskName1, kTaskDescription1, command_line1,
TaskScheduler::TRIGGER_TYPE_HOURLY, false));
EXPECT_TRUE(task_scheduler_->IsTaskRegistered(kTaskName1));
TaskScheduler::TaskInfo info;
EXPECT_FALSE(task_scheduler_->GetTaskInfo(kTaskName2, &info));
EXPECT_EQ(L"", info.description);
EXPECT_EQ(L"", info.name);
EXPECT_TRUE(task_scheduler_->GetTaskInfo(kTaskName1, &info));
EXPECT_EQ(kTaskDescription1, info.description);
EXPECT_EQ(kTaskName1, info.name);
const std::wstring expected_task_folder =
base::StrCat({L"\\" COMPANY_SHORTNAME_STRING,
IsSystemInstall(GetTestScope()) ? L"System" : L"User",
L"\\" PRODUCT_FULLNAME_STRING});
EXPECT_EQ(task_scheduler_->GetTaskSubfolderName(), expected_task_folder);
EXPECT_TRUE(task_scheduler_->HasTaskFolder(expected_task_folder.c_str()));
}
TEST_F(TaskSchedulerTests, GetTaskInfoLogonType) {
const bool is_system = IsSystemInstall(GetTestScope());
base::CommandLine command_line1 =
GetTestProcessCommandLine(GetTestScope(), test::GetTestName());
EXPECT_TRUE(task_scheduler_->RegisterTask(
kTaskName1, kTaskDescription1, command_line1,
TaskScheduler::TRIGGER_TYPE_HOURLY, false));
EXPECT_TRUE(task_scheduler_->IsTaskRegistered(kTaskName1));
TaskScheduler::TaskInfo info;
EXPECT_FALSE(task_scheduler_->GetTaskInfo(kTaskName2, &info));
EXPECT_EQ(0U, info.logon_type);
EXPECT_TRUE(task_scheduler_->GetTaskInfo(kTaskName1, &info));
EXPECT_EQ(!is_system, !!(info.logon_type & TaskScheduler::LOGON_INTERACTIVE));
EXPECT_EQ(is_system, !!(info.logon_type & TaskScheduler::LOGON_SERVICE));
EXPECT_FALSE(info.logon_type & TaskScheduler::LOGON_S4U);
}
TEST_F(TaskSchedulerTests, GetTaskInfoUserId) {
const bool is_system = IsSystemInstall(GetTestScope());
base::CommandLine command_line1 =
GetTestProcessCommandLine(GetTestScope(), test::GetTestName());
EXPECT_TRUE(task_scheduler_->RegisterTask(
kTaskName1, kTaskDescription1, command_line1,
TaskScheduler::TRIGGER_TYPE_HOURLY, false));
EXPECT_TRUE(task_scheduler_->IsTaskRegistered(kTaskName1));
TaskScheduler::TaskInfo info;
EXPECT_FALSE(task_scheduler_->GetTaskInfo(kTaskName2, &info));
EXPECT_STREQ(L"", info.user_id.c_str());
EXPECT_TRUE(task_scheduler_->GetTaskInfo(kTaskName1, &info));
const std::wstring expected_user_id = [&is_system]() -> std::wstring {
if (is_system) {
return L"SYSTEM";
}
base::win::ScopedBstr user_name_bstr;
ULONG user_name_size = 256;
EXPECT_TRUE(::GetUserNameExW(
NameSamCompatible,
user_name_bstr.AllocateBytes(user_name_size * sizeof(OLECHAR)),
&user_name_size));
return user_name_bstr.Get();
}();
EXPECT_TRUE(base::EndsWith(info.user_id, expected_user_id,
base::CompareCase::INSENSITIVE_ASCII) ||
base::EndsWith(expected_user_id, info.user_id,
base::CompareCase::INSENSITIVE_ASCII));
}
TEST_F(TaskSchedulerTests, GetTaskInfoTriggerTypes) {
for (const TaskScheduler::TriggerType expected_trigger_type : {
TaskScheduler::TRIGGER_TYPE_POST_REBOOT,
TaskScheduler::TRIGGER_TYPE_HOURLY,
TaskScheduler::TRIGGER_TYPE_EVERY_FIVE_HOURS,
}) {
if (expected_trigger_type == TaskScheduler::TRIGGER_TYPE_POST_REBOOT &&
!::IsUserAnAdmin()) {
continue;
}
RunGetTaskInfoTriggerTypesTest(expected_trigger_type);
}
RunGetTaskInfoTriggerTypesTest(TaskScheduler::TRIGGER_TYPE_POST_REBOOT |
TaskScheduler::TRIGGER_TYPE_HOURLY |
TaskScheduler::TRIGGER_TYPE_EVERY_FIVE_HOURS);
}
TEST(TaskSchedulerTest, NoSubfolders) {
scoped_refptr<TaskScheduler> task_scheduler = TaskScheduler::CreateInstance(
GetTestScope(), /*use_task_subfolders=*/false);
ASSERT_TRUE(task_scheduler);
constexpr int kNumTasks = 6;
const std::wstring kTaskNamePrefix(base::ASCIIToWide(test::GetTestName()));
for (int count = 0; count < kNumTasks; ++count) {
std::wstring task_name(kTaskNamePrefix);
task_name.push_back(L'0' + count);
ASSERT_TRUE(task_scheduler->RegisterTask(
task_name.c_str(), task_name.c_str(),
base::CommandLine::FromString(L"C:\\temp\\temp.exe"),
TaskScheduler::TriggerType::TRIGGER_TYPE_HOURLY, false));
ASSERT_TRUE(task_scheduler->DeleteTask(task_name.c_str()));
}
}
TEST(TaskSchedulerTest, ForEachTaskWithPrefix) {
for (bool use_task_subfolders : {true, false}) {
scoped_refptr<TaskScheduler> task_scheduler =
TaskScheduler::CreateInstance(GetTestScope(), use_task_subfolders);
ASSERT_TRUE(task_scheduler);
constexpr int kNumTasks = 6;
const std::wstring kTaskNamePrefix(base::ASCIIToWide(test::GetTestName()));
for (int count = 0; count < kNumTasks; ++count) {
std::wstring task_name(kTaskNamePrefix);
task_name.push_back(L'0' + count);
EXPECT_TRUE(task_scheduler->RegisterTask(
task_name.c_str(), task_name.c_str(),
base::CommandLine::FromString(L"C:\\temp\\temp.exe"),
TaskScheduler::TriggerType::TRIGGER_TYPE_HOURLY, false));
}
scoped_refptr<TaskScheduler> task_scheduler_different_namespace =
TaskScheduler::CreateInstance(GetTestScope(), !use_task_subfolders);
ASSERT_TRUE(task_scheduler_different_namespace);
int count_entries = 0;
task_scheduler_different_namespace->ForEachTaskWithPrefix(
kTaskNamePrefix,
base::BindLambdaForTesting(
[&count_entries](const std::wstring& /*task_name*/) {
++count_entries;
}));
EXPECT_EQ(count_entries, 0);
count_entries = 0;
task_scheduler->ForEachTaskWithPrefix(
kTaskNamePrefix,
base::BindLambdaForTesting(
[&count_entries, &task_scheduler,
kTaskNamePrefix](const std::wstring& task_name) {
EXPECT_TRUE(base::StartsWith(task_name, kTaskNamePrefix));
++count_entries;
task_scheduler->DeleteTask(task_name.c_str());
}));
EXPECT_EQ(count_entries, kNumTasks);
}
}
} // namespace updater