update_engine: Create cros vs. aosp boundary clear
Its time to make the boundary between Chrome OS and Android code more
clear. This CL moves all CrOS only code to "chromeos" directory and the
same for Android (in "android" directory). This way we would easily know
which code is uses in which project and can keep the code cleaner and
more maintainable.
One big remaining problem is download_action* files. It seems like
DownloadAction class does a lot of things that chrome OS needs and it
depends on a lot of Chrome OS stuff, but Android is also using thie
Action in a way that circumvent the Chrome OS stuff. For example Android
checks for SystemState to be nullptr to not do things. This is really
fragile and needs to change. Probably Android Team has to implement
their own DownloadAction of some sort and not re use the Chrome OS one
in a very fragile way.
Removed a few android files that have not been used anywhere.
Changed some clang-format and lint issues in order to pass preupload.
BUG=b:171829801
TEST=cros_workon_make --board reef --test update_engine
Change-Id: I3fff1d4a100a065a5c1484a845241b5521614d9f
Reviewed-on: https://chromium-review.googlesource.com/c/aosp/platform/system/update_engine/+/2508965
Tested-by: Amin Hassani <ahassani@chromium.org>
Auto-Submit: Amin Hassani <ahassani@chromium.org>
Reviewed-by: Jae Hoon Kim <kimjae@chromium.org>
Reviewed-by: Tianjie Xu <xunchang@google.com>
Reviewed-by: Kelvin Zhang <zhangkelvin@google.com>
Commit-Queue: Amin Hassani <ahassani@chromium.org>
diff --git a/cros/p2p_manager_unittest.cc b/cros/p2p_manager_unittest.cc
new file mode 100644
index 0000000..8b6d741
--- /dev/null
+++ b/cros/p2p_manager_unittest.cc
@@ -0,0 +1,538 @@
+//
+// Copyright (C) 2012 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/cros/p2p_manager.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/xattr.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/files/file_util.h>
+#if BASE_VER < 780000 // Android
+#include <base/message_loop/message_loop.h>
+#endif // BASE_VER < 780000
+#include <base/strings/stringprintf.h>
+#if BASE_VER >= 780000 // CrOS
+#include <base/task/single_thread_task_executor.h>
+#endif // BASE_VER >= 780000
+#include <brillo/asynchronous_signal_handler.h>
+#include <brillo/message_loops/base_message_loop.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <policy/libpolicy.h>
+#include <policy/mock_device_policy.h>
+
+#include "update_engine/common/fake_clock.h"
+#include "update_engine/common/prefs.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/cros/fake_p2p_manager_configuration.h"
+#include "update_engine/update_manager/fake_update_manager.h"
+#include "update_engine/update_manager/mock_policy.h"
+
+using base::TimeDelta;
+using brillo::MessageLoop;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+using testing::_;
+using testing::DoAll;
+using testing::Return;
+using testing::SetArgPointee;
+
+namespace chromeos_update_engine {
+
+// Test fixture that sets up a testing configuration (with e.g. a
+// temporary p2p dir) for P2PManager and cleans up when the test is
+// done.
+class P2PManagerTest : public testing::Test {
+ protected:
+ P2PManagerTest() : fake_um_(&fake_clock_) {}
+ ~P2PManagerTest() override {}
+
+ // Derived from testing::Test.
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ async_signal_handler_.Init();
+ subprocess_.Init(&async_signal_handler_);
+ test_conf_ = new FakeP2PManagerConfiguration();
+
+ // Allocate and install a mock policy implementation in the fake Update
+ // Manager. Note that the FakeUpdateManager takes ownership of the policy
+ // object.
+ mock_policy_ = new chromeos_update_manager::MockPolicy(&fake_clock_);
+ fake_um_.set_policy(mock_policy_);
+
+ // Construct the P2P manager under test.
+ manager_.reset(P2PManager::Construct(test_conf_,
+ &fake_clock_,
+ &fake_um_,
+ "cros_au",
+ 3,
+ TimeDelta::FromDays(5)));
+ }
+
+#if BASE_VER < 780000 // Android
+ base::MessageLoopForIO base_loop_;
+ brillo::BaseMessageLoop loop_{&base_loop_};
+#else // CrOS
+ base::SingleThreadTaskExecutor base_loop_{base::MessagePumpType::IO};
+ brillo::BaseMessageLoop loop_{base_loop_.task_runner()};
+#endif // BASE_VER < 780000
+ brillo::AsynchronousSignalHandler async_signal_handler_;
+ Subprocess subprocess_;
+
+ // The P2PManager::Configuration instance used for testing.
+ FakeP2PManagerConfiguration* test_conf_;
+
+ FakeClock fake_clock_;
+ chromeos_update_manager::MockPolicy* mock_policy_ = nullptr;
+ chromeos_update_manager::FakeUpdateManager fake_um_;
+
+ unique_ptr<P2PManager> manager_;
+};
+
+// Check that IsP2PEnabled() polls the policy correctly, with the value not
+// changing between calls.
+TEST_F(P2PManagerTest, P2PEnabledInitAndNotChanged) {
+ EXPECT_CALL(*mock_policy_, P2PEnabled(_, _, _, _));
+ EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, false));
+
+ EXPECT_FALSE(manager_->IsP2PEnabled());
+ brillo::MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_FALSE(manager_->IsP2PEnabled());
+}
+
+// Check that IsP2PEnabled() polls the policy correctly, with the value changing
+// between calls.
+TEST_F(P2PManagerTest, P2PEnabledInitAndChanged) {
+ EXPECT_CALL(*mock_policy_, P2PEnabled(_, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<3>(true),
+ Return(chromeos_update_manager::EvalStatus::kSucceeded)));
+ EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, true));
+ EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, false));
+
+ EXPECT_TRUE(manager_->IsP2PEnabled());
+ brillo::MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_FALSE(manager_->IsP2PEnabled());
+}
+
+// Check that we keep the $N newest files with the .$EXT.p2p extension.
+TEST_F(P2PManagerTest, HousekeepingCountLimit) {
+ // Specifically pass 0 for |max_file_age| to allow files of any age. Note that
+ // we need to reallocate the test_conf_ member, whose currently aliased object
+ // will be freed.
+ test_conf_ = new FakeP2PManagerConfiguration();
+ manager_.reset(P2PManager::Construct(test_conf_,
+ &fake_clock_,
+ &fake_um_,
+ "cros_au",
+ 3,
+ TimeDelta() /* max_file_age */));
+ EXPECT_EQ(manager_->CountSharedFiles(), 0);
+
+ base::Time start_time = base::Time::FromDoubleT(1246996800.);
+ // Generate files with different timestamps matching our pattern and generate
+ // other files not matching the pattern.
+ for (int n = 0; n < 5; n++) {
+ base::FilePath path = test_conf_->GetP2PDir().Append(
+ base::StringPrintf("file_%d.cros_au.p2p", n));
+ base::Time file_time = start_time + TimeDelta::FromMinutes(n);
+ EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
+ EXPECT_TRUE(base::TouchFile(path, file_time, file_time));
+
+ path = test_conf_->GetP2PDir().Append(
+ base::StringPrintf("file_%d.OTHER.p2p", n));
+ EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
+ EXPECT_TRUE(base::TouchFile(path, file_time, file_time));
+ }
+ // CountSharedFiles() only counts 'cros_au' files.
+ EXPECT_EQ(manager_->CountSharedFiles(), 5);
+
+ EXPECT_TRUE(manager_->PerformHousekeeping());
+
+ // At this point - after HouseKeeping - we should only have
+ // eight files left.
+ for (int n = 0; n < 5; n++) {
+ string file_name;
+ bool expect;
+
+ expect = (n >= 2);
+ file_name = base::StringPrintf(
+ "%s/file_%d.cros_au.p2p", test_conf_->GetP2PDir().value().c_str(), n);
+ EXPECT_EQ(expect, utils::FileExists(file_name.c_str()));
+
+ file_name = base::StringPrintf(
+ "%s/file_%d.OTHER.p2p", test_conf_->GetP2PDir().value().c_str(), n);
+ EXPECT_TRUE(utils::FileExists(file_name.c_str()));
+ }
+ // CountSharedFiles() only counts 'cros_au' files.
+ EXPECT_EQ(manager_->CountSharedFiles(), 3);
+}
+
+// Check that we keep files with the .$EXT.p2p extension not older
+// than some specific age (5 days, in this test).
+TEST_F(P2PManagerTest, HousekeepingAgeLimit) {
+ // We set the cutoff time to be 1 billion seconds (01:46:40 UTC on 9
+ // September 2001 - arbitrary number, but constant to avoid test
+ // flakiness) since the epoch and then we put two files before that
+ // date and three files after.
+ base::Time cutoff_time = base::Time::FromTimeT(1000000000);
+ TimeDelta age_limit = TimeDelta::FromDays(5);
+
+ // Set the clock just so files with a timestamp before |cutoff_time|
+ // will be deleted at housekeeping.
+ fake_clock_.SetWallclockTime(cutoff_time + age_limit);
+
+ // Specifically pass 0 for |num_files_to_keep| to allow any number of files.
+ // Note that we need to reallocate the test_conf_ member, whose currently
+ // aliased object will be freed.
+ test_conf_ = new FakeP2PManagerConfiguration();
+ manager_.reset(P2PManager::Construct(test_conf_,
+ &fake_clock_,
+ &fake_um_,
+ "cros_au",
+ 0 /* num_files_to_keep */,
+ age_limit));
+ EXPECT_EQ(manager_->CountSharedFiles(), 0);
+
+ // Generate files with different timestamps matching our pattern and generate
+ // other files not matching the pattern.
+ for (int n = 0; n < 5; n++) {
+ base::FilePath path = test_conf_->GetP2PDir().Append(
+ base::StringPrintf("file_%d.cros_au.p2p", n));
+
+ // With five files and aiming for two of them to be before
+ // |cutoff_time|, we distribute it like this:
+ //
+ // -------- 0 -------- 1 -------- 2 -------- 3 -------- 4 --------
+ // |
+ // cutoff_time
+ //
+ base::Time file_date = cutoff_time + (n - 2) * TimeDelta::FromDays(1) +
+ TimeDelta::FromHours(12);
+
+ EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
+ EXPECT_TRUE(base::TouchFile(path, file_date, file_date));
+
+ path = test_conf_->GetP2PDir().Append(
+ base::StringPrintf("file_%d.OTHER.p2p", n));
+ EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
+ EXPECT_TRUE(base::TouchFile(path, file_date, file_date));
+ }
+ // CountSharedFiles() only counts 'cros_au' files.
+ EXPECT_EQ(manager_->CountSharedFiles(), 5);
+
+ EXPECT_TRUE(manager_->PerformHousekeeping());
+
+ // At this point - after HouseKeeping - we should only have
+ // eight files left.
+ for (int n = 0; n < 5; n++) {
+ string file_name;
+ bool expect;
+
+ expect = (n >= 2);
+ file_name = base::StringPrintf(
+ "%s/file_%d.cros_au.p2p", test_conf_->GetP2PDir().value().c_str(), n);
+ EXPECT_EQ(expect, utils::FileExists(file_name.c_str()));
+
+ file_name = base::StringPrintf(
+ "%s/file_%d.OTHER.p2p", test_conf_->GetP2PDir().value().c_str(), n);
+ EXPECT_TRUE(utils::FileExists(file_name.c_str()));
+ }
+ // CountSharedFiles() only counts 'cros_au' files.
+ EXPECT_EQ(manager_->CountSharedFiles(), 3);
+}
+
+static bool CheckP2PFile(const string& p2p_dir,
+ const string& file_name,
+ ssize_t expected_size,
+ ssize_t expected_size_xattr) {
+ string path = p2p_dir + "/" + file_name;
+ char ea_value[64] = {0};
+ ssize_t ea_size;
+
+ off_t p2p_size = utils::FileSize(path);
+ if (p2p_size < 0) {
+ LOG(ERROR) << "File " << path << " does not exist";
+ return false;
+ }
+
+ if (expected_size != 0) {
+ if (p2p_size != expected_size) {
+ LOG(ERROR) << "Expected size " << expected_size << " but size was "
+ << p2p_size;
+ return false;
+ }
+ }
+
+ if (expected_size_xattr == 0) {
+ ea_size = getxattr(
+ path.c_str(), "user.cros-p2p-filesize", &ea_value, sizeof ea_value - 1);
+ if (ea_size == -1 && errno == ENODATA) {
+ // This is valid behavior as we support files without the xattr set.
+ } else {
+ PLOG(ERROR) << "getxattr() didn't fail with ENODATA as expected, "
+ << "ea_size=" << ea_size << ", errno=" << errno;
+ return false;
+ }
+ } else {
+ ea_size = getxattr(
+ path.c_str(), "user.cros-p2p-filesize", &ea_value, sizeof ea_value - 1);
+ if (ea_size < 0) {
+ LOG(ERROR) << "Error getting xattr attribute";
+ return false;
+ }
+ char* endp = nullptr;
+ long long int val = strtoll(ea_value, &endp, 0); // NOLINT(runtime/int)
+ if (endp == nullptr || *endp != '\0') {
+ LOG(ERROR) << "Error parsing xattr '" << ea_value << "' as an integer";
+ return false;
+ }
+ if (val != expected_size_xattr) {
+ LOG(ERROR) << "Expected xattr size " << expected_size_xattr
+ << " but size was " << val;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool CreateP2PFile(string p2p_dir,
+ string file_name,
+ size_t size,
+ size_t size_xattr) {
+ string path = p2p_dir + "/" + file_name;
+
+ int fd = open(path.c_str(), O_CREAT | O_RDWR, 0644);
+ if (fd == -1) {
+ PLOG(ERROR) << "Error creating file with path " << path;
+ return false;
+ }
+ if (ftruncate(fd, size) != 0) {
+ PLOG(ERROR) << "Error truncating " << path << " to size " << size;
+ close(fd);
+ return false;
+ }
+
+ if (size_xattr != 0) {
+ string decimal_size = std::to_string(size_xattr);
+ if (fsetxattr(fd,
+ "user.cros-p2p-filesize",
+ decimal_size.c_str(),
+ decimal_size.size(),
+ 0) != 0) {
+ PLOG(ERROR) << "Error setting xattr on " << path;
+ close(fd);
+ return false;
+ }
+ }
+
+ close(fd);
+ return true;
+}
+
+// Check that sharing a *new* file works.
+TEST_F(P2PManagerTest, ShareFile) {
+ const int kP2PTestFileSize = 1000 * 1000; // 1 MB
+
+ EXPECT_TRUE(manager_->FileShare("foo", kP2PTestFileSize));
+ EXPECT_EQ(manager_->FileGetPath("foo"),
+ test_conf_->GetP2PDir().Append("foo.cros_au.p2p.tmp"));
+ EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
+ "foo.cros_au.p2p.tmp",
+ 0,
+ kP2PTestFileSize));
+
+ // Sharing it again - with the same expected size - should return true
+ EXPECT_TRUE(manager_->FileShare("foo", kP2PTestFileSize));
+
+ // ... but if we use the wrong size, it should fail
+ EXPECT_FALSE(manager_->FileShare("foo", kP2PTestFileSize + 1));
+}
+
+// Check that making a shared file visible, does what is expected.
+TEST_F(P2PManagerTest, MakeFileVisible) {
+ const int kP2PTestFileSize = 1000 * 1000; // 1 MB
+
+ // First, check that it's not visible.
+ manager_->FileShare("foo", kP2PTestFileSize);
+ EXPECT_EQ(manager_->FileGetPath("foo"),
+ test_conf_->GetP2PDir().Append("foo.cros_au.p2p.tmp"));
+ EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
+ "foo.cros_au.p2p.tmp",
+ 0,
+ kP2PTestFileSize));
+ // Make the file visible and check that it changed its name. Do it
+ // twice to check that FileMakeVisible() is idempotent.
+ for (int n = 0; n < 2; n++) {
+ manager_->FileMakeVisible("foo");
+ EXPECT_EQ(manager_->FileGetPath("foo"),
+ test_conf_->GetP2PDir().Append("foo.cros_au.p2p"));
+ EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
+ "foo.cros_au.p2p",
+ 0,
+ kP2PTestFileSize));
+ }
+}
+
+// Check that we return the right values for existing files in P2P_DIR.
+TEST_F(P2PManagerTest, ExistingFiles) {
+ bool visible;
+
+ // Check that errors are returned if the file does not exist
+ EXPECT_EQ(manager_->FileGetPath("foo"), base::FilePath());
+ EXPECT_EQ(manager_->FileGetSize("foo"), -1);
+ EXPECT_EQ(manager_->FileGetExpectedSize("foo"), -1);
+ EXPECT_FALSE(manager_->FileGetVisible("foo", nullptr));
+ // ... then create the file ...
+ EXPECT_TRUE(CreateP2PFile(
+ test_conf_->GetP2PDir().value(), "foo.cros_au.p2p", 42, 43));
+ // ... and then check that the expected values are returned
+ EXPECT_EQ(manager_->FileGetPath("foo"),
+ test_conf_->GetP2PDir().Append("foo.cros_au.p2p"));
+ EXPECT_EQ(manager_->FileGetSize("foo"), 42);
+ EXPECT_EQ(manager_->FileGetExpectedSize("foo"), 43);
+ EXPECT_TRUE(manager_->FileGetVisible("foo", &visible));
+ EXPECT_TRUE(visible);
+
+ // One more time, this time with a .tmp variant. First ensure it errors out..
+ EXPECT_EQ(manager_->FileGetPath("bar"), base::FilePath());
+ EXPECT_EQ(manager_->FileGetSize("bar"), -1);
+ EXPECT_EQ(manager_->FileGetExpectedSize("bar"), -1);
+ EXPECT_FALSE(manager_->FileGetVisible("bar", nullptr));
+ // ... then create the file ...
+ EXPECT_TRUE(CreateP2PFile(
+ test_conf_->GetP2PDir().value(), "bar.cros_au.p2p.tmp", 44, 45));
+ // ... and then check that the expected values are returned
+ EXPECT_EQ(manager_->FileGetPath("bar"),
+ test_conf_->GetP2PDir().Append("bar.cros_au.p2p.tmp"));
+ EXPECT_EQ(manager_->FileGetSize("bar"), 44);
+ EXPECT_EQ(manager_->FileGetExpectedSize("bar"), 45);
+ EXPECT_TRUE(manager_->FileGetVisible("bar", &visible));
+ EXPECT_FALSE(visible);
+}
+
+// This is a little bit ugly but short of mocking a 'p2p' service this
+// will have to do. E.g. we essentially simulate the various
+// behaviours of initctl(8) that we rely on.
+TEST_F(P2PManagerTest, StartP2P) {
+ // Check that we can start the service
+ test_conf_->SetInitctlStartCommand({"true"});
+ EXPECT_TRUE(manager_->EnsureP2PRunning());
+ test_conf_->SetInitctlStartCommand({"false"});
+ EXPECT_FALSE(manager_->EnsureP2PRunning());
+ test_conf_->SetInitctlStartCommand(
+ {"sh", "-c", "echo \"initctl: Job is already running: p2p\" >&2; false"});
+ EXPECT_TRUE(manager_->EnsureP2PRunning());
+ test_conf_->SetInitctlStartCommand(
+ {"sh", "-c", "echo something else >&2; false"});
+ EXPECT_FALSE(manager_->EnsureP2PRunning());
+}
+
+// Same comment as for StartP2P
+TEST_F(P2PManagerTest, StopP2P) {
+ // Check that we can start the service
+ test_conf_->SetInitctlStopCommand({"true"});
+ EXPECT_TRUE(manager_->EnsureP2PNotRunning());
+ test_conf_->SetInitctlStopCommand({"false"});
+ EXPECT_FALSE(manager_->EnsureP2PNotRunning());
+ test_conf_->SetInitctlStopCommand(
+ {"sh", "-c", "echo \"initctl: Unknown instance \" >&2; false"});
+ EXPECT_TRUE(manager_->EnsureP2PNotRunning());
+ test_conf_->SetInitctlStopCommand(
+ {"sh", "-c", "echo something else >&2; false"});
+ EXPECT_FALSE(manager_->EnsureP2PNotRunning());
+}
+
+static void ExpectUrl(const string& expected_url, const string& url) {
+ EXPECT_EQ(url, expected_url);
+ MessageLoop::current()->BreakLoop();
+}
+
+// Like StartP2P, we're mocking the different results that p2p-client
+// can return. It's not pretty but it works.
+TEST_F(P2PManagerTest, LookupURL) {
+ // Emulate p2p-client returning valid URL with "fooX", 42 and "cros_au"
+ // being propagated in the right places.
+ test_conf_->SetP2PClientCommand(
+ {"echo", "http://1.2.3.4/{file_id}_{minsize}"});
+ manager_->LookupUrlForFile(
+ "fooX",
+ 42,
+ TimeDelta(),
+ base::Bind(ExpectUrl, "http://1.2.3.4/fooX.cros_au_42"));
+ loop_.Run();
+
+ // Emulate p2p-client returning invalid URL.
+ test_conf_->SetP2PClientCommand({"echo", "not_a_valid_url"});
+ manager_->LookupUrlForFile(
+ "foobar", 42, TimeDelta(), base::Bind(ExpectUrl, ""));
+ loop_.Run();
+
+ // Emulate p2p-client conveying failure.
+ test_conf_->SetP2PClientCommand({"false"});
+ manager_->LookupUrlForFile(
+ "foobar", 42, TimeDelta(), base::Bind(ExpectUrl, ""));
+ loop_.Run();
+
+ // Emulate p2p-client not existing.
+ test_conf_->SetP2PClientCommand({"/path/to/non/existent/helper/program"});
+ manager_->LookupUrlForFile(
+ "foobar", 42, TimeDelta(), base::Bind(ExpectUrl, ""));
+ loop_.Run();
+
+ // Emulate p2p-client crashing.
+ test_conf_->SetP2PClientCommand({"sh", "-c", "kill -SEGV $$"});
+ manager_->LookupUrlForFile(
+ "foobar", 42, TimeDelta(), base::Bind(ExpectUrl, ""));
+ loop_.Run();
+
+ // Emulate p2p-client exceeding its timeout.
+ test_conf_->SetP2PClientCommand(
+ {"sh",
+ "-c",
+ // The 'sleep' launched below could be left behind as an orphaned
+ // process when the 'sh' process is terminated by SIGTERM. As a
+ // remedy, trap SIGTERM and kill the 'sleep' process, which requires
+ // launching 'sleep' in background and then waiting for it.
+ "cleanup() { kill \"${sleep_pid}\"; exit 0; }; "
+ "trap cleanup TERM; "
+ "sleep 5 & "
+ "sleep_pid=$!; "
+ "echo http://1.2.3.4/; "
+ "wait"});
+ manager_->LookupUrlForFile("foobar",
+ 42,
+ TimeDelta::FromMilliseconds(500),
+ base::Bind(ExpectUrl, ""));
+ loop_.Run();
+}
+
+} // namespace chromeos_update_engine