p2p_manager_unittest.cc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. //
  2. // Copyright (C) 2012 The Android Open Source Project
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. #include "update_engine/p2p_manager.h"
  17. #include <dirent.h>
  18. #include <fcntl.h>
  19. #include <sys/stat.h>
  20. #include <sys/types.h>
  21. #include <sys/xattr.h>
  22. #include <unistd.h>
  23. #include <memory>
  24. #include <string>
  25. #include <vector>
  26. #include <base/bind.h>
  27. #include <base/callback.h>
  28. #include <base/files/file_util.h>
  29. #include <base/message_loop/message_loop.h>
  30. #include <base/strings/stringprintf.h>
  31. #include <brillo/asynchronous_signal_handler.h>
  32. #include <brillo/message_loops/base_message_loop.h>
  33. #include <brillo/message_loops/message_loop.h>
  34. #include <brillo/message_loops/message_loop_utils.h>
  35. #include <gmock/gmock.h>
  36. #include <gtest/gtest.h>
  37. #include <policy/libpolicy.h>
  38. #include <policy/mock_device_policy.h>
  39. #include "update_engine/common/fake_clock.h"
  40. #include "update_engine/common/prefs.h"
  41. #include "update_engine/common/subprocess.h"
  42. #include "update_engine/common/test_utils.h"
  43. #include "update_engine/common/utils.h"
  44. #include "update_engine/fake_p2p_manager_configuration.h"
  45. #include "update_engine/update_manager/fake_update_manager.h"
  46. #include "update_engine/update_manager/mock_policy.h"
  47. using base::TimeDelta;
  48. using brillo::MessageLoop;
  49. using std::string;
  50. using std::unique_ptr;
  51. using std::vector;
  52. using testing::_;
  53. using testing::DoAll;
  54. using testing::Return;
  55. using testing::SetArgPointee;
  56. namespace chromeos_update_engine {
  57. // Test fixture that sets up a testing configuration (with e.g. a
  58. // temporary p2p dir) for P2PManager and cleans up when the test is
  59. // done.
  60. class P2PManagerTest : public testing::Test {
  61. protected:
  62. P2PManagerTest() : fake_um_(&fake_clock_) {}
  63. ~P2PManagerTest() override {}
  64. // Derived from testing::Test.
  65. void SetUp() override {
  66. loop_.SetAsCurrent();
  67. async_signal_handler_.Init();
  68. subprocess_.Init(&async_signal_handler_);
  69. test_conf_ = new FakeP2PManagerConfiguration();
  70. // Allocate and install a mock policy implementation in the fake Update
  71. // Manager. Note that the FakeUpdateManager takes ownership of the policy
  72. // object.
  73. mock_policy_ = new chromeos_update_manager::MockPolicy(&fake_clock_);
  74. fake_um_.set_policy(mock_policy_);
  75. // Construct the P2P manager under test.
  76. manager_.reset(P2PManager::Construct(test_conf_,
  77. &fake_clock_,
  78. &fake_um_,
  79. "cros_au",
  80. 3,
  81. TimeDelta::FromDays(5)));
  82. }
  83. base::MessageLoopForIO base_loop_;
  84. brillo::BaseMessageLoop loop_{&base_loop_};
  85. brillo::AsynchronousSignalHandler async_signal_handler_;
  86. Subprocess subprocess_;
  87. // The P2PManager::Configuration instance used for testing.
  88. FakeP2PManagerConfiguration* test_conf_;
  89. FakeClock fake_clock_;
  90. chromeos_update_manager::MockPolicy* mock_policy_ = nullptr;
  91. chromeos_update_manager::FakeUpdateManager fake_um_;
  92. unique_ptr<P2PManager> manager_;
  93. };
  94. // Check that IsP2PEnabled() polls the policy correctly, with the value not
  95. // changing between calls.
  96. TEST_F(P2PManagerTest, P2PEnabledInitAndNotChanged) {
  97. EXPECT_CALL(*mock_policy_, P2PEnabled(_, _, _, _));
  98. EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, false));
  99. EXPECT_FALSE(manager_->IsP2PEnabled());
  100. brillo::MessageLoopRunMaxIterations(MessageLoop::current(), 100);
  101. EXPECT_FALSE(manager_->IsP2PEnabled());
  102. }
  103. // Check that IsP2PEnabled() polls the policy correctly, with the value changing
  104. // between calls.
  105. TEST_F(P2PManagerTest, P2PEnabledInitAndChanged) {
  106. EXPECT_CALL(*mock_policy_, P2PEnabled(_, _, _, _))
  107. .WillOnce(DoAll(SetArgPointee<3>(true),
  108. Return(chromeos_update_manager::EvalStatus::kSucceeded)));
  109. EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, true));
  110. EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, false));
  111. EXPECT_TRUE(manager_->IsP2PEnabled());
  112. brillo::MessageLoopRunMaxIterations(MessageLoop::current(), 100);
  113. EXPECT_FALSE(manager_->IsP2PEnabled());
  114. }
  115. // Check that we keep the $N newest files with the .$EXT.p2p extension.
  116. TEST_F(P2PManagerTest, HousekeepingCountLimit) {
  117. // Specifically pass 0 for |max_file_age| to allow files of any age. Note that
  118. // we need to reallocate the test_conf_ member, whose currently aliased object
  119. // will be freed.
  120. test_conf_ = new FakeP2PManagerConfiguration();
  121. manager_.reset(P2PManager::Construct(test_conf_,
  122. &fake_clock_,
  123. &fake_um_,
  124. "cros_au",
  125. 3,
  126. TimeDelta() /* max_file_age */));
  127. EXPECT_EQ(manager_->CountSharedFiles(), 0);
  128. base::Time start_time = base::Time::FromDoubleT(1246996800.);
  129. // Generate files with different timestamps matching our pattern and generate
  130. // other files not matching the pattern.
  131. for (int n = 0; n < 5; n++) {
  132. base::FilePath path = test_conf_->GetP2PDir().Append(
  133. base::StringPrintf("file_%d.cros_au.p2p", n));
  134. base::Time file_time = start_time + TimeDelta::FromMinutes(n);
  135. EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
  136. EXPECT_TRUE(base::TouchFile(path, file_time, file_time));
  137. path = test_conf_->GetP2PDir().Append(
  138. base::StringPrintf("file_%d.OTHER.p2p", n));
  139. EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
  140. EXPECT_TRUE(base::TouchFile(path, file_time, file_time));
  141. }
  142. // CountSharedFiles() only counts 'cros_au' files.
  143. EXPECT_EQ(manager_->CountSharedFiles(), 5);
  144. EXPECT_TRUE(manager_->PerformHousekeeping());
  145. // At this point - after HouseKeeping - we should only have
  146. // eight files left.
  147. for (int n = 0; n < 5; n++) {
  148. string file_name;
  149. bool expect;
  150. expect = (n >= 2);
  151. file_name = base::StringPrintf(
  152. "%s/file_%d.cros_au.p2p", test_conf_->GetP2PDir().value().c_str(), n);
  153. EXPECT_EQ(expect, utils::FileExists(file_name.c_str()));
  154. file_name = base::StringPrintf(
  155. "%s/file_%d.OTHER.p2p", test_conf_->GetP2PDir().value().c_str(), n);
  156. EXPECT_TRUE(utils::FileExists(file_name.c_str()));
  157. }
  158. // CountSharedFiles() only counts 'cros_au' files.
  159. EXPECT_EQ(manager_->CountSharedFiles(), 3);
  160. }
  161. // Check that we keep files with the .$EXT.p2p extension not older
  162. // than some specific age (5 days, in this test).
  163. TEST_F(P2PManagerTest, HousekeepingAgeLimit) {
  164. // We set the cutoff time to be 1 billion seconds (01:46:40 UTC on 9
  165. // September 2001 - arbitrary number, but constant to avoid test
  166. // flakiness) since the epoch and then we put two files before that
  167. // date and three files after.
  168. base::Time cutoff_time = base::Time::FromTimeT(1000000000);
  169. TimeDelta age_limit = TimeDelta::FromDays(5);
  170. // Set the clock just so files with a timestamp before |cutoff_time|
  171. // will be deleted at housekeeping.
  172. fake_clock_.SetWallclockTime(cutoff_time + age_limit);
  173. // Specifically pass 0 for |num_files_to_keep| to allow any number of files.
  174. // Note that we need to reallocate the test_conf_ member, whose currently
  175. // aliased object will be freed.
  176. test_conf_ = new FakeP2PManagerConfiguration();
  177. manager_.reset(P2PManager::Construct(test_conf_,
  178. &fake_clock_,
  179. &fake_um_,
  180. "cros_au",
  181. 0 /* num_files_to_keep */,
  182. age_limit));
  183. EXPECT_EQ(manager_->CountSharedFiles(), 0);
  184. // Generate files with different timestamps matching our pattern and generate
  185. // other files not matching the pattern.
  186. for (int n = 0; n < 5; n++) {
  187. base::FilePath path = test_conf_->GetP2PDir().Append(
  188. base::StringPrintf("file_%d.cros_au.p2p", n));
  189. // With five files and aiming for two of them to be before
  190. // |cutoff_time|, we distribute it like this:
  191. //
  192. // -------- 0 -------- 1 -------- 2 -------- 3 -------- 4 --------
  193. // |
  194. // cutoff_time
  195. //
  196. base::Time file_date = cutoff_time + (n - 2) * TimeDelta::FromDays(1) +
  197. TimeDelta::FromHours(12);
  198. EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
  199. EXPECT_TRUE(base::TouchFile(path, file_date, file_date));
  200. path = test_conf_->GetP2PDir().Append(
  201. base::StringPrintf("file_%d.OTHER.p2p", n));
  202. EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
  203. EXPECT_TRUE(base::TouchFile(path, file_date, file_date));
  204. }
  205. // CountSharedFiles() only counts 'cros_au' files.
  206. EXPECT_EQ(manager_->CountSharedFiles(), 5);
  207. EXPECT_TRUE(manager_->PerformHousekeeping());
  208. // At this point - after HouseKeeping - we should only have
  209. // eight files left.
  210. for (int n = 0; n < 5; n++) {
  211. string file_name;
  212. bool expect;
  213. expect = (n >= 2);
  214. file_name = base::StringPrintf(
  215. "%s/file_%d.cros_au.p2p", test_conf_->GetP2PDir().value().c_str(), n);
  216. EXPECT_EQ(expect, utils::FileExists(file_name.c_str()));
  217. file_name = base::StringPrintf(
  218. "%s/file_%d.OTHER.p2p", test_conf_->GetP2PDir().value().c_str(), n);
  219. EXPECT_TRUE(utils::FileExists(file_name.c_str()));
  220. }
  221. // CountSharedFiles() only counts 'cros_au' files.
  222. EXPECT_EQ(manager_->CountSharedFiles(), 3);
  223. }
  224. static bool CheckP2PFile(const string& p2p_dir,
  225. const string& file_name,
  226. ssize_t expected_size,
  227. ssize_t expected_size_xattr) {
  228. string path = p2p_dir + "/" + file_name;
  229. char ea_value[64] = {0};
  230. ssize_t ea_size;
  231. off_t p2p_size = utils::FileSize(path);
  232. if (p2p_size < 0) {
  233. LOG(ERROR) << "File " << path << " does not exist";
  234. return false;
  235. }
  236. if (expected_size != 0) {
  237. if (p2p_size != expected_size) {
  238. LOG(ERROR) << "Expected size " << expected_size << " but size was "
  239. << p2p_size;
  240. return false;
  241. }
  242. }
  243. if (expected_size_xattr == 0) {
  244. ea_size = getxattr(
  245. path.c_str(), "user.cros-p2p-filesize", &ea_value, sizeof ea_value - 1);
  246. if (ea_size == -1 && errno == ENODATA) {
  247. // This is valid behavior as we support files without the xattr set.
  248. } else {
  249. PLOG(ERROR) << "getxattr() didn't fail with ENODATA as expected, "
  250. << "ea_size=" << ea_size << ", errno=" << errno;
  251. return false;
  252. }
  253. } else {
  254. ea_size = getxattr(
  255. path.c_str(), "user.cros-p2p-filesize", &ea_value, sizeof ea_value - 1);
  256. if (ea_size < 0) {
  257. LOG(ERROR) << "Error getting xattr attribute";
  258. return false;
  259. }
  260. char* endp = nullptr;
  261. long long int val = strtoll(ea_value, &endp, 0); // NOLINT(runtime/int)
  262. if (endp == nullptr || *endp != '\0') {
  263. LOG(ERROR) << "Error parsing xattr '" << ea_value << "' as an integer";
  264. return false;
  265. }
  266. if (val != expected_size_xattr) {
  267. LOG(ERROR) << "Expected xattr size " << expected_size_xattr
  268. << " but size was " << val;
  269. return false;
  270. }
  271. }
  272. return true;
  273. }
  274. static bool CreateP2PFile(string p2p_dir,
  275. string file_name,
  276. size_t size,
  277. size_t size_xattr) {
  278. string path = p2p_dir + "/" + file_name;
  279. int fd = open(path.c_str(), O_CREAT | O_RDWR, 0644);
  280. if (fd == -1) {
  281. PLOG(ERROR) << "Error creating file with path " << path;
  282. return false;
  283. }
  284. if (ftruncate(fd, size) != 0) {
  285. PLOG(ERROR) << "Error truncating " << path << " to size " << size;
  286. close(fd);
  287. return false;
  288. }
  289. if (size_xattr != 0) {
  290. string decimal_size = std::to_string(size_xattr);
  291. if (fsetxattr(fd,
  292. "user.cros-p2p-filesize",
  293. decimal_size.c_str(),
  294. decimal_size.size(),
  295. 0) != 0) {
  296. PLOG(ERROR) << "Error setting xattr on " << path;
  297. close(fd);
  298. return false;
  299. }
  300. }
  301. close(fd);
  302. return true;
  303. }
  304. // Check that sharing a *new* file works.
  305. TEST_F(P2PManagerTest, ShareFile) {
  306. const int kP2PTestFileSize = 1000 * 1000; // 1 MB
  307. EXPECT_TRUE(manager_->FileShare("foo", kP2PTestFileSize));
  308. EXPECT_EQ(manager_->FileGetPath("foo"),
  309. test_conf_->GetP2PDir().Append("foo.cros_au.p2p.tmp"));
  310. EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
  311. "foo.cros_au.p2p.tmp",
  312. 0,
  313. kP2PTestFileSize));
  314. // Sharing it again - with the same expected size - should return true
  315. EXPECT_TRUE(manager_->FileShare("foo", kP2PTestFileSize));
  316. // ... but if we use the wrong size, it should fail
  317. EXPECT_FALSE(manager_->FileShare("foo", kP2PTestFileSize + 1));
  318. }
  319. // Check that making a shared file visible, does what is expected.
  320. TEST_F(P2PManagerTest, MakeFileVisible) {
  321. const int kP2PTestFileSize = 1000 * 1000; // 1 MB
  322. // First, check that it's not visible.
  323. manager_->FileShare("foo", kP2PTestFileSize);
  324. EXPECT_EQ(manager_->FileGetPath("foo"),
  325. test_conf_->GetP2PDir().Append("foo.cros_au.p2p.tmp"));
  326. EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
  327. "foo.cros_au.p2p.tmp",
  328. 0,
  329. kP2PTestFileSize));
  330. // Make the file visible and check that it changed its name. Do it
  331. // twice to check that FileMakeVisible() is idempotent.
  332. for (int n = 0; n < 2; n++) {
  333. manager_->FileMakeVisible("foo");
  334. EXPECT_EQ(manager_->FileGetPath("foo"),
  335. test_conf_->GetP2PDir().Append("foo.cros_au.p2p"));
  336. EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
  337. "foo.cros_au.p2p",
  338. 0,
  339. kP2PTestFileSize));
  340. }
  341. }
  342. // Check that we return the right values for existing files in P2P_DIR.
  343. TEST_F(P2PManagerTest, ExistingFiles) {
  344. bool visible;
  345. // Check that errors are returned if the file does not exist
  346. EXPECT_EQ(manager_->FileGetPath("foo"), base::FilePath());
  347. EXPECT_EQ(manager_->FileGetSize("foo"), -1);
  348. EXPECT_EQ(manager_->FileGetExpectedSize("foo"), -1);
  349. EXPECT_FALSE(manager_->FileGetVisible("foo", nullptr));
  350. // ... then create the file ...
  351. EXPECT_TRUE(CreateP2PFile(
  352. test_conf_->GetP2PDir().value(), "foo.cros_au.p2p", 42, 43));
  353. // ... and then check that the expected values are returned
  354. EXPECT_EQ(manager_->FileGetPath("foo"),
  355. test_conf_->GetP2PDir().Append("foo.cros_au.p2p"));
  356. EXPECT_EQ(manager_->FileGetSize("foo"), 42);
  357. EXPECT_EQ(manager_->FileGetExpectedSize("foo"), 43);
  358. EXPECT_TRUE(manager_->FileGetVisible("foo", &visible));
  359. EXPECT_TRUE(visible);
  360. // One more time, this time with a .tmp variant. First ensure it errors out..
  361. EXPECT_EQ(manager_->FileGetPath("bar"), base::FilePath());
  362. EXPECT_EQ(manager_->FileGetSize("bar"), -1);
  363. EXPECT_EQ(manager_->FileGetExpectedSize("bar"), -1);
  364. EXPECT_FALSE(manager_->FileGetVisible("bar", nullptr));
  365. // ... then create the file ...
  366. EXPECT_TRUE(CreateP2PFile(
  367. test_conf_->GetP2PDir().value(), "bar.cros_au.p2p.tmp", 44, 45));
  368. // ... and then check that the expected values are returned
  369. EXPECT_EQ(manager_->FileGetPath("bar"),
  370. test_conf_->GetP2PDir().Append("bar.cros_au.p2p.tmp"));
  371. EXPECT_EQ(manager_->FileGetSize("bar"), 44);
  372. EXPECT_EQ(manager_->FileGetExpectedSize("bar"), 45);
  373. EXPECT_TRUE(manager_->FileGetVisible("bar", &visible));
  374. EXPECT_FALSE(visible);
  375. }
  376. // This is a little bit ugly but short of mocking a 'p2p' service this
  377. // will have to do. E.g. we essentially simulate the various
  378. // behaviours of initctl(8) that we rely on.
  379. TEST_F(P2PManagerTest, StartP2P) {
  380. // Check that we can start the service
  381. test_conf_->SetInitctlStartCommand({"true"});
  382. EXPECT_TRUE(manager_->EnsureP2PRunning());
  383. test_conf_->SetInitctlStartCommand({"false"});
  384. EXPECT_FALSE(manager_->EnsureP2PRunning());
  385. test_conf_->SetInitctlStartCommand(
  386. {"sh", "-c", "echo \"initctl: Job is already running: p2p\" >&2; false"});
  387. EXPECT_TRUE(manager_->EnsureP2PRunning());
  388. test_conf_->SetInitctlStartCommand(
  389. {"sh", "-c", "echo something else >&2; false"});
  390. EXPECT_FALSE(manager_->EnsureP2PRunning());
  391. }
  392. // Same comment as for StartP2P
  393. TEST_F(P2PManagerTest, StopP2P) {
  394. // Check that we can start the service
  395. test_conf_->SetInitctlStopCommand({"true"});
  396. EXPECT_TRUE(manager_->EnsureP2PNotRunning());
  397. test_conf_->SetInitctlStopCommand({"false"});
  398. EXPECT_FALSE(manager_->EnsureP2PNotRunning());
  399. test_conf_->SetInitctlStopCommand(
  400. {"sh", "-c", "echo \"initctl: Unknown instance \" >&2; false"});
  401. EXPECT_TRUE(manager_->EnsureP2PNotRunning());
  402. test_conf_->SetInitctlStopCommand(
  403. {"sh", "-c", "echo something else >&2; false"});
  404. EXPECT_FALSE(manager_->EnsureP2PNotRunning());
  405. }
  406. static void ExpectUrl(const string& expected_url, const string& url) {
  407. EXPECT_EQ(url, expected_url);
  408. MessageLoop::current()->BreakLoop();
  409. }
  410. // Like StartP2P, we're mocking the different results that p2p-client
  411. // can return. It's not pretty but it works.
  412. TEST_F(P2PManagerTest, LookupURL) {
  413. // Emulate p2p-client returning valid URL with "fooX", 42 and "cros_au"
  414. // being propagated in the right places.
  415. test_conf_->SetP2PClientCommand(
  416. {"echo", "http://1.2.3.4/{file_id}_{minsize}"});
  417. manager_->LookupUrlForFile(
  418. "fooX",
  419. 42,
  420. TimeDelta(),
  421. base::Bind(ExpectUrl, "http://1.2.3.4/fooX.cros_au_42"));
  422. loop_.Run();
  423. // Emulate p2p-client returning invalid URL.
  424. test_conf_->SetP2PClientCommand({"echo", "not_a_valid_url"});
  425. manager_->LookupUrlForFile(
  426. "foobar", 42, TimeDelta(), base::Bind(ExpectUrl, ""));
  427. loop_.Run();
  428. // Emulate p2p-client conveying failure.
  429. test_conf_->SetP2PClientCommand({"false"});
  430. manager_->LookupUrlForFile(
  431. "foobar", 42, TimeDelta(), base::Bind(ExpectUrl, ""));
  432. loop_.Run();
  433. // Emulate p2p-client not existing.
  434. test_conf_->SetP2PClientCommand({"/path/to/non/existent/helper/program"});
  435. manager_->LookupUrlForFile(
  436. "foobar", 42, TimeDelta(), base::Bind(ExpectUrl, ""));
  437. loop_.Run();
  438. // Emulate p2p-client crashing.
  439. test_conf_->SetP2PClientCommand({"sh", "-c", "kill -SEGV $$"});
  440. manager_->LookupUrlForFile(
  441. "foobar", 42, TimeDelta(), base::Bind(ExpectUrl, ""));
  442. loop_.Run();
  443. // Emulate p2p-client exceeding its timeout.
  444. test_conf_->SetP2PClientCommand(
  445. {"sh",
  446. "-c",
  447. // The 'sleep' launched below could be left behind as an orphaned
  448. // process when the 'sh' process is terminated by SIGTERM. As a
  449. // remedy, trap SIGTERM and kill the 'sleep' process, which requires
  450. // launching 'sleep' in background and then waiting for it.
  451. "cleanup() { kill \"${sleep_pid}\"; exit 0; }; "
  452. "trap cleanup TERM; "
  453. "sleep 5 & "
  454. "sleep_pid=$!; "
  455. "echo http://1.2.3.4/; "
  456. "wait"});
  457. manager_->LookupUrlForFile("foobar",
  458. 42,
  459. TimeDelta::FromMilliseconds(500),
  460. base::Bind(ExpectUrl, ""));
  461. loop_.Run();
  462. }
  463. } // namespace chromeos_update_engine