installation_validator_unittest.cc revision 34680572440d7894ef8dafce81d8039ed80726a2
1// Copyright (c) 2012 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 <map>
6
7#include "base/command_line.h"
8#include "base/files/file_path.h"
9#include "base/logging.h"
10#include "base/memory/ref_counted.h"
11#include "base/version.h"
12#include "chrome/common/chrome_constants.h"
13#include "chrome/common/chrome_switches.h"
14#include "chrome/installer/util/channel_info.h"
15#include "chrome/installer/util/helper.h"
16#include "chrome/installer/util/installation_state.h"
17#include "chrome/installer/util/installation_validator.h"
18#include "testing/gmock/include/gmock/gmock.h"
19#include "testing/gtest/include/gtest/gtest.h"
20
21using installer::ChannelInfo;
22using installer::InstallationValidator;
23using installer::InstallationState;
24using installer::AppCommand;
25using installer::ProductState;
26using testing::_;
27using testing::StrictMock;
28using testing::Values;
29
30namespace {
31
32enum Channel {
33  STABLE_CHANNEL,
34  BETA_CHANNEL,
35  DEV_CHANNEL
36};
37
38enum PackageType {
39  SINGLE_INSTALL,
40  MULTI_INSTALL
41};
42
43enum Level {
44  USER_LEVEL,
45  SYSTEM_LEVEL
46};
47
48enum Vehicle {
49  GOOGLE_UPDATE,
50  MSI
51};
52
53enum ChannelModifier {
54  CM_MULTI        = 0x01,
55  CM_CHROME       = 0x02,
56  CM_CHROME_FRAME = 0x04,
57  CM_FULL         = 0x08
58};
59
60const wchar_t* const kChromeChannels[] = {
61  L"",
62  L"1.1-beta",
63  L"2.0-dev"
64};
65
66const wchar_t* const kChromeFrameChannels[] = {
67  L"",
68  L"beta",
69  L"dev"
70};
71
72class FakeProductState : public ProductState {
73 public:
74  void SetChannel(const wchar_t* base, int channel_modifiers);
75  void SetVersion(const char* version);
76  void SetUninstallCommand(BrowserDistribution::Type dist_type,
77                           Level install_level,
78                           const char* version,
79                           int channel_modifiers,
80                           Vehicle vehicle);
81  void AddOsUpgradeCommand(BrowserDistribution::Type dist_type,
82                           Level install_level,
83                           const char* version,
84                           int channel_modifiers);
85  void AddQueryEULAAcceptanceCommand(BrowserDistribution::Type dist_type,
86                                     Level install_level,
87                                     const char* version,
88                                     int channel_modifiers);
89  void AddQuickEnableApplicationHostCommand(BrowserDistribution::Type dist_type,
90                                            Level install_level,
91                                            const char* version,
92                                            int channel_modifiers);
93  void set_multi_install(bool is_multi_install) {
94    multi_install_ = is_multi_install;
95  }
96  installer::AppCommands& commands() { return commands_; }
97
98 protected:
99  struct ChannelMethodForModifier {
100    ChannelModifier modifier;
101    bool (ChannelInfo::*method)(bool value);
102  };
103
104  static base::FilePath GetSetupPath(
105      BrowserDistribution::Type dist_type,
106      Level install_level,
107      int channel_modifiers);
108
109  static base::FilePath GetSetupExePath(
110      BrowserDistribution::Type dist_type,
111      Level install_level,
112      const char* version,
113      int channel_modifiers);
114
115  static const ChannelMethodForModifier kChannelMethods[];
116};
117
118class FakeInstallationState : public InstallationState {
119 public:
120  void SetProductState(BrowserDistribution::Type type,
121                       Level install_level,
122                       const ProductState& product) {
123    GetProducts(install_level)[IndexFromDistType(type)].CopyFrom(product);
124  }
125
126 protected:
127  ProductState* GetProducts(Level install_level) {
128    return install_level == USER_LEVEL ? user_products_ : system_products_;
129  }
130};
131
132// static
133const FakeProductState::ChannelMethodForModifier
134    FakeProductState::kChannelMethods[] = {
135  { CM_MULTI,        &ChannelInfo::SetMultiInstall },
136  { CM_CHROME,       &ChannelInfo::SetChrome },
137  { CM_CHROME_FRAME, &ChannelInfo::SetChromeFrame },
138  { CM_FULL,         &ChannelInfo::SetFullSuffix }
139};
140
141// static
142base::FilePath FakeProductState::GetSetupPath(
143    BrowserDistribution::Type dist_type,
144    Level install_level,
145    int channel_modifiers) {
146  const bool is_multi_install = (channel_modifiers & CM_MULTI) != 0;
147  return installer::GetChromeInstallPath(
148      install_level == SYSTEM_LEVEL,
149      BrowserDistribution::GetSpecificDistribution(is_multi_install ?
150              BrowserDistribution::CHROME_BINARIES : dist_type));
151}
152
153// static
154base::FilePath FakeProductState::GetSetupExePath(
155    BrowserDistribution::Type dist_type,
156    Level install_level,
157    const char* version,
158    int channel_modifiers) {
159  base::FilePath setup_path = GetSetupPath(dist_type, install_level,
160                                           channel_modifiers);
161  return setup_path
162      .AppendASCII(version)
163      .Append(installer::kInstallerDir)
164      .Append(installer::kSetupExe);
165}
166
167// Sets the channel_ member of this instance according to a base channel value
168// and a set of modifiers.
169void FakeProductState::SetChannel(const wchar_t* base, int channel_modifiers) {
170  channel_.set_value(base);
171  for (size_t i = 0; i < arraysize(kChannelMethods); ++i) {
172    if ((channel_modifiers & kChannelMethods[i].modifier) != 0)
173      (channel_.*kChannelMethods[i].method)(true);
174  }
175}
176
177void FakeProductState::SetVersion(const char* version) {
178  version_.reset(version == NULL ? NULL : new Version(version));
179}
180
181// Sets the uninstall command for this object.
182void FakeProductState::SetUninstallCommand(BrowserDistribution::Type dist_type,
183                                           Level install_level,
184                                           const char* version,
185                                           int channel_modifiers,
186                                           Vehicle vehicle) {
187  DCHECK(version);
188
189  const bool is_multi_install = (channel_modifiers & CM_MULTI) != 0;
190  uninstall_command_ = CommandLine(GetSetupExePath(dist_type, install_level,
191                                                   version, channel_modifiers));
192  uninstall_command_.AppendSwitch(installer::switches::kUninstall);
193  if (install_level == SYSTEM_LEVEL)
194    uninstall_command_.AppendSwitch(installer::switches::kSystemLevel);
195  if (is_multi_install) {
196    uninstall_command_.AppendSwitch(installer::switches::kMultiInstall);
197    if (dist_type == BrowserDistribution::CHROME_BROWSER)
198      uninstall_command_.AppendSwitch(installer::switches::kChrome);
199    else if (dist_type == BrowserDistribution::CHROME_FRAME)
200      uninstall_command_.AppendSwitch(installer::switches::kChromeFrame);
201  } else if (dist_type == BrowserDistribution::CHROME_FRAME) {
202    uninstall_command_.AppendSwitch(installer::switches::kChromeFrame);
203  }
204  if (vehicle == MSI)
205    uninstall_command_.AppendSwitch(installer::switches::kMsi);
206}
207
208// Adds the "on-os-upgrade" Google Update product command.
209void FakeProductState::AddOsUpgradeCommand(BrowserDistribution::Type dist_type,
210                                           Level install_level,
211                                           const char* version,
212                                           int channel_modifiers) {
213  // Right now only Chrome browser uses this.
214  DCHECK_EQ(dist_type, BrowserDistribution::CHROME_BROWSER);
215
216  CommandLine cmd_line(GetSetupExePath(dist_type, install_level, version,
217                                       channel_modifiers));
218  cmd_line.AppendSwitch(installer::switches::kOnOsUpgrade);
219  // Imitating ChromeBrowserOperations::AppendProductFlags().
220  if ((channel_modifiers & CM_MULTI) != 0) {
221    cmd_line.AppendSwitch(installer::switches::kMultiInstall);
222    cmd_line.AppendSwitch(installer::switches::kChrome);
223  }
224  if (install_level == SYSTEM_LEVEL)
225    cmd_line.AppendSwitch(installer::switches::kSystemLevel);
226  cmd_line.AppendSwitch(installer::switches::kVerboseLogging);
227  AppCommand app_cmd(cmd_line.GetCommandLineString());
228  app_cmd.set_is_auto_run_on_os_upgrade(true);
229  commands_.Set(installer::kCmdOnOsUpgrade, app_cmd);
230}
231
232// Adds the "query-eula-acceptance" Google Update product command.
233void FakeProductState::AddQueryEULAAcceptanceCommand(
234    BrowserDistribution::Type dist_type,
235    Level install_level,
236    const char* version,
237    int channel_modifiers) {
238  DCHECK_EQ(dist_type, BrowserDistribution::CHROME_BINARIES);
239
240  CommandLine cmd_line(GetSetupExePath(dist_type, install_level, version,
241                                       channel_modifiers));
242  cmd_line.AppendSwitch(installer::switches::kQueryEULAAcceptance);
243  if (install_level == SYSTEM_LEVEL)
244    cmd_line.AppendSwitch(installer::switches::kSystemLevel);
245  cmd_line.AppendSwitch(installer::switches::kVerboseLogging);
246  AppCommand app_cmd(cmd_line.GetCommandLineString());
247  app_cmd.set_is_web_accessible(true);
248  app_cmd.set_is_run_as_user(true);
249  commands_.Set(installer::kCmdQueryEULAAcceptance, app_cmd);
250}
251
252// Adds the "quick-enable-application-host" Google Update product command.
253void FakeProductState::AddQuickEnableApplicationHostCommand(
254    BrowserDistribution::Type dist_type,
255    Level install_level,
256    const char* version,
257    int channel_modifiers) {
258  DCHECK_EQ(dist_type, BrowserDistribution::CHROME_BINARIES);
259  DCHECK_NE(channel_modifiers & CM_MULTI, 0);
260
261  CommandLine cmd_line(GetSetupExePath(dist_type, install_level, version,
262                                       channel_modifiers));
263  cmd_line.AppendSwitch(installer::switches::kMultiInstall);
264  cmd_line.AppendSwitch(installer::switches::kChromeAppLauncher);
265  cmd_line.AppendSwitch(installer::switches::kEnsureGoogleUpdatePresent);
266  AppCommand app_cmd(cmd_line.GetCommandLineString());
267  app_cmd.set_sends_pings(true);
268  app_cmd.set_is_web_accessible(true);
269  app_cmd.set_is_run_as_user(true);
270  commands_.Set(installer::kCmdQuickEnableApplicationHost, app_cmd);
271}
272
273}  // namespace
274
275// Fixture for testing the InstallationValidator.  Errors logged by the
276// validator are sent to an optional mock recipient (see
277// set_validation_error_recipient) upon which expectations can be placed.
278class InstallationValidatorTest
279    : public testing::TestWithParam<InstallationValidator::InstallationType> {
280 public:
281
282  // These shouldn't need to be public, but there seems to be some interaction
283  // with parameterized tests that requires it.
284  static void SetUpTestCase();
285  static void TearDownTestCase();
286
287  // Returns the multi channel modifiers for a given installation type.
288  static int GetChannelModifiers(InstallationValidator::InstallationType type);
289
290 protected:
291  typedef std::map<InstallationValidator::InstallationType, int>
292      InstallationTypeToModifiers;
293
294  class ValidationErrorRecipient {
295   public:
296    virtual ~ValidationErrorRecipient() { }
297    virtual void ReceiveValidationError(const char* file,
298                                        int line,
299                                        const char* message) = 0;
300  };
301  class MockValidationErrorRecipient : public ValidationErrorRecipient {
302   public:
303    MOCK_METHOD3(ReceiveValidationError, void(const char* file,
304                                              int line,
305                                              const char* message));
306  };
307
308 protected:
309  static bool HandleLogMessage(int severity,
310                               const char* file,
311                               int line,
312                               size_t message_start,
313                               const std::string& str);
314  static void set_validation_error_recipient(
315      ValidationErrorRecipient* recipient);
316  static void MakeProductState(
317      BrowserDistribution::Type prod_type,
318      InstallationValidator::InstallationType inst_type,
319      Level install_level,
320      Channel channel,
321      Vehicle vehicle,
322      FakeProductState* state);
323  static void MakeMachineState(
324      InstallationValidator::InstallationType inst_type,
325      Level install_level,
326      Channel channel,
327      Vehicle vehicle,
328      FakeInstallationState* state);
329  virtual void TearDown();
330
331  static logging::LogMessageHandlerFunction old_log_message_handler_;
332  static ValidationErrorRecipient* validation_error_recipient_;
333  static InstallationTypeToModifiers* type_to_modifiers_;
334};
335
336// static
337logging::LogMessageHandlerFunction
338    InstallationValidatorTest::old_log_message_handler_ = NULL;
339
340// static
341InstallationValidatorTest::ValidationErrorRecipient*
342    InstallationValidatorTest::validation_error_recipient_ = NULL;
343
344// static
345InstallationValidatorTest::InstallationTypeToModifiers*
346    InstallationValidatorTest::type_to_modifiers_ = NULL;
347
348// static
349int InstallationValidatorTest::GetChannelModifiers(
350    InstallationValidator::InstallationType type) {
351  DCHECK(type_to_modifiers_);
352  DCHECK(type_to_modifiers_->find(type) != type_to_modifiers_->end());
353
354  return (*type_to_modifiers_)[type];
355}
356
357// static
358void InstallationValidatorTest::SetUpTestCase() {
359  DCHECK(type_to_modifiers_ == NULL);
360  old_log_message_handler_ = logging::GetLogMessageHandler();
361  logging::SetLogMessageHandler(&HandleLogMessage);
362
363  type_to_modifiers_ = new InstallationTypeToModifiers();
364  InstallationTypeToModifiers& ttm = *type_to_modifiers_;
365  ttm[InstallationValidator::NO_PRODUCTS] = 0;
366  ttm[InstallationValidator::CHROME_SINGLE] = 0;
367  ttm[InstallationValidator::CHROME_MULTI] = CM_MULTI | CM_CHROME;
368  ttm[InstallationValidator::CHROME_FRAME_SINGLE] = 0;
369  ttm[InstallationValidator::CHROME_FRAME_SINGLE_CHROME_SINGLE] = 0;
370  ttm[InstallationValidator::CHROME_FRAME_SINGLE_CHROME_MULTI] =
371      CM_MULTI | CM_CHROME;
372  ttm[InstallationValidator::CHROME_FRAME_MULTI] = CM_MULTI | CM_CHROME_FRAME;
373  ttm[InstallationValidator::CHROME_FRAME_MULTI_CHROME_MULTI] =
374      CM_MULTI | CM_CHROME_FRAME | CM_CHROME;
375}
376
377// static
378void InstallationValidatorTest::TearDownTestCase() {
379  logging::SetLogMessageHandler(old_log_message_handler_);
380  old_log_message_handler_ = NULL;
381
382  delete type_to_modifiers_;
383  type_to_modifiers_ = NULL;
384}
385
386// static
387bool InstallationValidatorTest::HandleLogMessage(int severity,
388                                                 const char* file,
389                                                 int line,
390                                                 size_t message_start,
391                                                 const std::string& str) {
392  // All validation failures result in LOG(ERROR)
393  if (severity == logging::LOG_ERROR && !str.empty()) {
394    // Remove the trailing newline, if present.
395    size_t message_length = str.size() - message_start;
396    if (*str.rbegin() == '\n')
397      --message_length;
398    if (validation_error_recipient_ != NULL) {
399      validation_error_recipient_->ReceiveValidationError(
400          file, line, str.substr(message_start, message_length).c_str());
401    } else {
402      // Fail the test if an error wasn't handled.
403      ADD_FAILURE_AT(file, line)
404          << base::StringPiece(str.c_str() + message_start, message_length);
405    }
406    return true;
407  }
408
409  if (old_log_message_handler_ != NULL)
410    return (old_log_message_handler_)(severity, file, line, message_start, str);
411
412  return false;
413}
414
415// static
416void InstallationValidatorTest::set_validation_error_recipient(
417    ValidationErrorRecipient* recipient) {
418  validation_error_recipient_ = recipient;
419}
420
421// static
422// Populates |state| with the state of a valid installation of product
423// |prod_type|.  |inst_type| dictates properties of the installation
424// (multi-install, etc).
425void InstallationValidatorTest::MakeProductState(
426    BrowserDistribution::Type prod_type,
427    InstallationValidator::InstallationType inst_type,
428    Level install_level,
429    Channel channel,
430    Vehicle vehicle,
431    FakeProductState* state) {
432  DCHECK(state);
433
434  const bool is_multi_install =
435      prod_type == BrowserDistribution::CHROME_BINARIES ||
436      (prod_type == BrowserDistribution::CHROME_BROWSER &&
437       (inst_type & InstallationValidator::ProductBits::CHROME_MULTI) != 0) ||
438      (prod_type == BrowserDistribution::CHROME_FRAME &&
439       (inst_type &
440           InstallationValidator::ProductBits::CHROME_FRAME_MULTI) != 0);
441
442  const wchar_t* const* channels = &kChromeChannels[0];
443  if (prod_type == BrowserDistribution::CHROME_FRAME && !is_multi_install)
444    channels = &kChromeFrameChannels[0];  // SxS GCF has its own channel names.
445  const int channel_modifiers =
446      is_multi_install ? GetChannelModifiers(inst_type) : 0;
447
448  state->Clear();
449  state->SetChannel(channels[channel], channel_modifiers);
450  state->SetVersion(chrome::kChromeVersion);
451  state->SetUninstallCommand(prod_type, install_level, chrome::kChromeVersion,
452                             channel_modifiers, vehicle);
453  state->set_multi_install(is_multi_install);
454  if (prod_type == BrowserDistribution::CHROME_BINARIES) {
455    state->AddQueryEULAAcceptanceCommand(prod_type,
456                                         install_level,
457                                         chrome::kChromeVersion,
458                                         channel_modifiers);
459  }
460  if (prod_type == BrowserDistribution::CHROME_BINARIES) {
461    state->AddQuickEnableApplicationHostCommand(prod_type,
462                                                install_level,
463                                                chrome::kChromeVersion,
464                                                channel_modifiers);
465  }
466  if (prod_type == BrowserDistribution::CHROME_BROWSER) {
467    state->AddOsUpgradeCommand(prod_type,
468                               install_level,
469                               chrome::kChromeVersion,
470                               channel_modifiers);
471  }
472}
473
474// static
475// Populates |state| with the state of a valid installation of |inst_type|.
476void InstallationValidatorTest::MakeMachineState(
477    InstallationValidator::InstallationType inst_type,
478    Level install_level,
479    Channel channel,
480    Vehicle vehicle,
481    FakeInstallationState* state) {
482  DCHECK(state);
483
484  static const int kChromeMask =
485      (InstallationValidator::ProductBits::CHROME_SINGLE |
486       InstallationValidator::ProductBits::CHROME_MULTI);
487  static const int kChromeFrameMask =
488      (InstallationValidator::ProductBits::CHROME_FRAME_SINGLE |
489       InstallationValidator::ProductBits::CHROME_FRAME_MULTI);
490  static const int kBinariesMask =
491      (InstallationValidator::ProductBits::CHROME_MULTI |
492       InstallationValidator::ProductBits::CHROME_FRAME_MULTI);
493
494  FakeProductState prod_state;
495
496  if ((inst_type & kChromeMask) != 0) {
497    MakeProductState(BrowserDistribution::CHROME_BROWSER, inst_type,
498                     install_level, channel, vehicle, &prod_state);
499    state->SetProductState(BrowserDistribution::CHROME_BROWSER, install_level,
500                           prod_state);
501  }
502
503  if ((inst_type & kChromeFrameMask) != 0) {
504    MakeProductState(BrowserDistribution::CHROME_FRAME, inst_type,
505                     install_level, channel, vehicle, &prod_state);
506    state->SetProductState(BrowserDistribution::CHROME_FRAME, install_level,
507                           prod_state);
508  }
509
510  if ((inst_type & kBinariesMask) != 0) {
511    MakeProductState(BrowserDistribution::CHROME_BINARIES, inst_type,
512                     install_level, channel, vehicle, &prod_state);
513    state->SetProductState(BrowserDistribution::CHROME_BINARIES, install_level,
514                           prod_state);
515  }
516}
517
518void InstallationValidatorTest::TearDown() {
519  validation_error_recipient_ = NULL;
520}
521
522// Builds a proper machine state for a given InstallationType, then validates
523// it.
524TEST_P(InstallationValidatorTest, TestValidInstallation) {
525  const InstallationValidator::InstallationType inst_type = GetParam();
526  FakeInstallationState machine_state;
527  InstallationValidator::InstallationType type;
528  StrictMock<MockValidationErrorRecipient> recipient;
529  set_validation_error_recipient(&recipient);
530
531  MakeMachineState(inst_type, SYSTEM_LEVEL, STABLE_CHANNEL, GOOGLE_UPDATE,
532                   &machine_state);
533  EXPECT_TRUE(InstallationValidator::ValidateInstallationTypeForState(
534                  machine_state, true, &type));
535  EXPECT_EQ(inst_type, type);
536}
537
538// Run the test for all installation types.
539INSTANTIATE_TEST_CASE_P(
540    AllValidInstallations,
541    InstallationValidatorTest,
542    Values(InstallationValidator::NO_PRODUCTS,
543           InstallationValidator::CHROME_SINGLE,
544           InstallationValidator::CHROME_MULTI,
545           InstallationValidator::CHROME_FRAME_SINGLE,
546           InstallationValidator::CHROME_FRAME_SINGLE_CHROME_SINGLE,
547           InstallationValidator::CHROME_FRAME_SINGLE_CHROME_MULTI,
548           InstallationValidator::CHROME_FRAME_MULTI,
549           InstallationValidator::CHROME_FRAME_MULTI_CHROME_MULTI));
550