delete_after_reboot_helper_unittest.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <windows.h>
6#include <shlobj.h>
7
8#include "base/file_util.h"
9#include "base/memory/scoped_ptr.h"
10#include "base/strings/string_util.h"
11#include "base/win/registry.h"
12#include "chrome/installer/util/delete_after_reboot_helper.h"
13#include "testing/gtest/include/gtest/gtest.h"
14
15namespace {
16
17// These tests exercise the Delete-After-Reboot code which requires
18// modifications to HKLM. This will fail on Vista and above if the user
19// is not an admin or if UAC is on.
20// I tried using RegOverridePredefKey to test, but MoveFileEx ignore this
21// even on 32 bit machines :-( As such, running this test may pollute
22// your PendingFileRenameOperations value.
23class DeleteAfterRebootHelperTest : public testing::Test {
24 protected:
25  virtual void SetUp() {
26    // Create a temporary directory for testing and fill it with some files.
27    std::wstring no_prefix;
28    file_util::CreateNewTempDirectory(no_prefix, &temp_dir_);
29    file_util::CreateTemporaryFileInDir(temp_dir_, &temp_file_);
30
31    temp_subdir_ = temp_dir_.Append(L"subdir");
32    file_util::CreateDirectory(temp_subdir_);
33    file_util::CreateTemporaryFileInDir(temp_subdir_, &temp_subdir_file_);
34
35    // Copy the current pending moves and then clear it if we can:
36    if (IsUserAnAdmin()) {
37      GetPendingMovesValue(&original_pending_moves_);
38    }
39  }
40  virtual void TearDown() {
41    // Delete the temporary directory if it's still there.
42    base::Delete(temp_dir_, true);
43
44    // Try and restore the pending moves value, if we have one.
45    if (IsUserAnAdmin() && original_pending_moves_.size() > 1) {
46      base::win::RegKey session_manager_key(
47          HKEY_LOCAL_MACHINE, kSessionManagerKey,
48          KEY_CREATE_SUB_KEY | KEY_SET_VALUE);
49      if (!session_manager_key.Handle()) {
50        // Couldn't open / create the key.
51        DLOG(ERROR) << "Failed to open session manager key for writing.";
52      }
53
54      std::vector<char> buffer;
55      StringArrayToMultiSZBytes(original_pending_moves_, &buffer);
56      session_manager_key.WriteValue(kPendingFileRenameOps, &buffer[0],
57                                     buffer.size(), REG_MULTI_SZ);
58    }
59  }
60
61  // Compares two buffers of size len. Returns true if they are equal,
62  // false otherwise. Standard warnings about making sure the buffers
63  // are at least len chars long apply.
64  template<class Type>
65  bool CompareBuffers(Type* buf1, Type* buf2, int len) {
66    Type* comp1 = buf1;
67    Type* comp2 = buf2;
68    for (int i = 0; i < len; i++) {
69      if (*comp1 != *comp2)
70        return false;
71      comp1++;
72      comp2++;
73    }
74    return true;
75  }
76
77  // Returns the size of the given list of wstrings in bytes, including
78  // null chars, plus an additional terminating null char.
79  // e.g. the length of all the strings * sizeof(wchar_t).
80  virtual size_t WStringPairListSize(
81      const std::vector<PendingMove>& string_list) {
82    size_t length = 0;
83    std::vector<PendingMove>::const_iterator iter(string_list.begin());
84    for (; iter != string_list.end(); ++iter) {
85      length += iter->first.size() + 1;  // +1 for the null char.
86      length += iter->second.size() + 1;  // +1 for the null char.
87    }
88    length++;  // for the additional null char.
89    return length * sizeof(wchar_t);
90  }
91
92  std::vector<PendingMove> original_pending_moves_;
93
94  base::FilePath temp_dir_;
95  base::FilePath temp_file_;
96  base::FilePath temp_subdir_;
97  base::FilePath temp_subdir_file_;
98};
99}
100
101TEST_F(DeleteAfterRebootHelperTest, TestStringListToMultiSZConversions) {
102  struct StringTest {
103    wchar_t* test_name;
104    wchar_t* str;
105    DWORD length;
106    size_t count;
107  } tests[] = {
108    { L"basic", L"foo\0bar\0fee\0bee\0boo\0bong\0\0", 26 * sizeof(wchar_t), 3 },
109    { L"empty", L"\0\0", 2 * sizeof(wchar_t), 1 },
110    { L"deletes", L"foo\0\0bar\0\0bizz\0\0", 16 * sizeof(wchar_t), 3 },
111  };
112
113  for (int i = 0; i < arraysize(tests); i++) {
114    std::vector<PendingMove> string_list;
115    EXPECT_TRUE(SUCCEEDED(
116        MultiSZBytesToStringArray(reinterpret_cast<char*>(tests[i].str),
117                                  tests[i].length, &string_list)))
118        << tests[i].test_name;
119    EXPECT_EQ(tests[i].count, string_list.size()) << tests[i].test_name;
120    std::vector<char> buffer;
121    buffer.resize(WStringPairListSize(string_list));
122    StringArrayToMultiSZBytes(string_list, &buffer);
123    EXPECT_TRUE(CompareBuffers(&buffer[0],
124                reinterpret_cast<char*>(tests[i].str),
125                tests[i].length)) << tests[i].test_name;
126  }
127
128  StringTest failures[] =
129    { L"malformed", reinterpret_cast<wchar_t*>("oddnumb\0\0"), 9, 1 };
130
131  for (int i = 0; i < arraysize(failures); i++) {
132    std::vector<PendingMove> string_list;
133    EXPECT_FALSE(SUCCEEDED(
134        MultiSZBytesToStringArray(reinterpret_cast<char*>(failures[i].str),
135                                  failures[i].length, &string_list)))
136        << failures[i].test_name;
137  }
138}
139
140
141TEST_F(DeleteAfterRebootHelperTest, TestFileDeleteScheduleAndUnschedule) {
142  if (!IsUserAnAdmin()) {
143    return;
144  }
145
146  EXPECT_TRUE(ScheduleDirectoryForDeletion(temp_dir_.value().c_str()));
147
148  std::vector<PendingMove> pending_moves;
149  EXPECT_TRUE(SUCCEEDED(GetPendingMovesValue(&pending_moves)));
150
151  // We should see, somewhere in this key, deletion writs for
152  // temp_file_, temp_subdir_file_, temp_subdir_ and temp_dir_ in that order.
153  EXPECT_TRUE(pending_moves.size() > 3);
154
155  // Get the short form of temp_file_ and use that to match.
156  std::wstring short_temp_file(GetShortPathName(temp_file_.value().c_str()));
157
158  // Scan for the first expected delete.
159  std::vector<PendingMove>::const_iterator iter(pending_moves.begin());
160  for (; iter != pending_moves.end(); iter++) {
161    if (MatchPendingDeletePath(short_temp_file, iter->first))
162      break;
163  }
164
165  // Check that each of the deletes we expect are there in order.
166  base::FilePath expected_paths[] =
167      { temp_file_, temp_subdir_file_, temp_subdir_, temp_dir_ };
168  for (int i = 0; i < arraysize(expected_paths); ++i) {
169    EXPECT_FALSE(iter == pending_moves.end());
170    if (iter != pending_moves.end()) {
171      std::wstring short_path_name(
172          GetShortPathName(expected_paths[i].value().c_str()));
173      EXPECT_TRUE(MatchPendingDeletePath(short_path_name, iter->first));
174      ++iter;
175    }
176  }
177
178  // Test that we can remove the pending deletes.
179  EXPECT_TRUE(RemoveFromMovesPendingReboot(temp_dir_.value().c_str()));
180  HRESULT hr = GetPendingMovesValue(&pending_moves);
181  EXPECT_TRUE(hr == S_OK || hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND));
182
183  std::vector<PendingMove>::const_iterator check_iter(pending_moves.begin());
184  for (; check_iter != pending_moves.end(); ++check_iter) {
185    EXPECT_FALSE(MatchPendingDeletePath(short_temp_file, check_iter->first));
186  }
187}
188
189TEST_F(DeleteAfterRebootHelperTest, TestFileDeleteSchedulingWithActualDeletes) {
190  if (!IsUserAnAdmin()) {
191    return;
192  }
193
194  std::vector<PendingMove> initial_pending_moves;
195  GetPendingMovesValue(&initial_pending_moves);
196  size_t initial_pending_moves_size = initial_pending_moves.size();
197
198  EXPECT_TRUE(ScheduleDirectoryForDeletion(temp_dir_.value().c_str()));
199
200  std::vector<PendingMove> pending_moves;
201  EXPECT_TRUE(SUCCEEDED(GetPendingMovesValue(&pending_moves)));
202
203  // We should see, somewhere in this key, deletion writs for
204  // temp_file_, temp_subdir_file_, temp_subdir_ and temp_dir_ in that order.
205  EXPECT_TRUE(pending_moves.size() > 3);
206
207  // Get the short form of temp_file_ and use that to match.
208  std::wstring short_temp_file(GetShortPathName(temp_file_.value().c_str()));
209
210  // Scan for the first expected delete.
211  std::vector<PendingMove>::const_iterator iter(pending_moves.begin());
212  for (; iter != pending_moves.end(); iter++) {
213    if (MatchPendingDeletePath(short_temp_file, iter->first))
214      break;
215  }
216
217  // Check that each of the deletes we expect are there in order.
218  base::FilePath expected_paths[] =
219      { temp_file_, temp_subdir_file_, temp_subdir_, temp_dir_ };
220  for (int i = 0; i < arraysize(expected_paths); ++i) {
221    EXPECT_FALSE(iter == pending_moves.end());
222    if (iter != pending_moves.end()) {
223      std::wstring short_path_name(
224          GetShortPathName(expected_paths[i].value().c_str()));
225      EXPECT_TRUE(MatchPendingDeletePath(short_path_name, iter->first));
226      ++iter;
227    }
228  }
229
230  // Delete the temporary directory.
231  base::Delete(temp_dir_, true);
232
233  // Test that we can remove the pending deletes.
234  EXPECT_TRUE(RemoveFromMovesPendingReboot(temp_dir_.value().c_str()));
235  HRESULT hr = GetPendingMovesValue(&pending_moves);
236  EXPECT_TRUE(hr == S_OK || hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND));
237
238  EXPECT_EQ(initial_pending_moves_size, pending_moves.size());
239
240  std::vector<PendingMove>::const_iterator check_iter(pending_moves.begin());
241  for (; check_iter != pending_moves.end(); ++check_iter) {
242    EXPECT_FALSE(MatchPendingDeletePath(short_temp_file, check_iter->first));
243  }
244}
245
246