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