device_status_collector_browsertest.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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 "chrome/browser/chromeos/policy/device_status_collector.h"
6
7#include "base/environment.h"
8#include "base/logging.h"
9#include "base/memory/scoped_ptr.h"
10#include "base/message_loop.h"
11#include "base/prefs/pref_service.h"
12#include "base/prefs/testing_pref_service.h"
13#include "base/threading/sequenced_worker_pool.h"
14#include "chrome/browser/chromeos/settings/cros_settings.h"
15#include "chrome/browser/chromeos/settings/cros_settings_names.h"
16#include "chrome/browser/chromeos/settings/cros_settings_provider.h"
17#include "chrome/browser/chromeos/settings/stub_cros_settings_provider.h"
18#include "chrome/browser/chromeos/system/mock_statistics_provider.h"
19#include "chrome/browser/chromeos/system/statistics_provider.h"
20#include "chrome/browser/policy/cloud/proto/device_management_backend.pb.h"
21#include "chrome/common/pref_names.h"
22#include "content/public/browser/browser_thread.h"
23#include "content/public/test/test_browser_thread.h"
24#include "testing/gmock/include/gmock/gmock.h"
25#include "testing/gtest/include/gtest/gtest.h"
26
27using ::testing::DoAll;
28using ::testing::NotNull;
29using ::testing::Return;
30using ::testing::SetArgPointee;
31using ::testing::_;
32using base::Time;
33using base::TimeDelta;
34
35namespace em = enterprise_management;
36
37namespace {
38
39const int64 kMillisecondsPerDay = Time::kMicrosecondsPerDay / 1000;
40
41scoped_ptr<content::Geoposition> mock_position_to_return_next;
42
43void SetMockPositionToReturnNext(const content::Geoposition &position) {
44  mock_position_to_return_next.reset(new content::Geoposition(position));
45}
46
47void MockPositionUpdateRequester(
48    const content::GeolocationUpdateCallback& callback) {
49  if (!mock_position_to_return_next.get())
50    return;
51
52  // If the fix is invalid, the DeviceStatusCollector will immediately request
53  // another update when it receives the callback. This is desirable and safe in
54  // real life where geolocation updates arrive asynchronously. In this testing
55  // harness, the callback is invoked synchronously upon request, leading to a
56  // request-callback loop. The loop is broken by returning the mock position
57  // only once.
58  scoped_ptr<content::Geoposition> position(
59      mock_position_to_return_next.release());
60  callback.Run(*position);
61}
62
63class TestingDeviceStatusCollector : public policy::DeviceStatusCollector {
64 public:
65  TestingDeviceStatusCollector(
66      PrefService* local_state,
67      chromeos::system::StatisticsProvider* provider)
68      : policy::DeviceStatusCollector(local_state,
69                                      provider,
70                                      &MockPositionUpdateRequester) {
71    // Set the baseline time to a fixed value (1 AM) to prevent test flakiness
72    // due to a single activity period spanning two days.
73    SetBaselineTime(Time::Now().LocalMidnight() + TimeDelta::FromHours(1));
74  }
75
76  void Simulate(IdleState* states, int len) {
77    for (int i = 0; i < len; i++)
78      IdleStateCallback(states[i]);
79  }
80
81  void set_max_stored_past_activity_days(unsigned int value) {
82    max_stored_past_activity_days_ = value;
83  }
84
85  void set_max_stored_future_activity_days(unsigned int value) {
86    max_stored_future_activity_days_ = value;
87  }
88
89  // Reset the baseline time.
90  void SetBaselineTime(Time time) {
91    baseline_time_ = time;
92    baseline_offset_periods_ = 0;
93  }
94
95 protected:
96  virtual void CheckIdleState() OVERRIDE {
97    // This should never be called in testing, as it results in a dbus call.
98    ADD_FAILURE();
99  }
100
101  // Each time this is called, returns a time that is a fixed increment
102  // later than the previous time.
103  virtual Time GetCurrentTime() OVERRIDE {
104    int poll_interval = policy::DeviceStatusCollector::kIdlePollIntervalSeconds;
105    return baseline_time_ +
106        TimeDelta::FromSeconds(poll_interval * baseline_offset_periods_++);
107  }
108
109 private:
110  // Baseline time for the fake times returned from GetCurrentTime().
111  Time baseline_time_;
112
113  // The number of simulated periods since the baseline time.
114  int baseline_offset_periods_;
115};
116
117// Return the total number of active milliseconds contained in a device
118// status report.
119int64 GetActiveMilliseconds(em::DeviceStatusReportRequest& status) {
120  int64 active_milliseconds = 0;
121  for (int i = 0; i < status.active_period_size(); i++) {
122    active_milliseconds += status.active_period(i).active_duration();
123  }
124  return active_milliseconds;
125}
126
127}  // namespace
128
129namespace policy {
130
131// Though it is a unit test, this test is linked with browser_tests so that it
132// runs in a separate process. The intention is to avoid overriding the timezone
133// environment variable for other tests.
134class DeviceStatusCollectorTest : public testing::Test {
135 public:
136  DeviceStatusCollectorTest()
137    : message_loop_(MessageLoop::TYPE_UI),
138      ui_thread_(content::BrowserThread::UI, &message_loop_),
139      file_thread_(content::BrowserThread::FILE, &message_loop_),
140      io_thread_(content::BrowserThread::IO, &message_loop_) {
141    // Run this test with a well-known timezone so that Time::LocalMidnight()
142    // returns the same values on all machines.
143    scoped_ptr<base::Environment> env(base::Environment::Create());
144    env->SetVar("TZ", "UTC");
145
146    TestingDeviceStatusCollector::RegisterPrefs(prefs_.registry());
147
148    EXPECT_CALL(statistics_provider_, GetMachineStatistic(_, NotNull()))
149        .WillRepeatedly(Return(false));
150
151    // Remove the real DeviceSettingsProvider and replace it with a stub.
152    cros_settings_ = chromeos::CrosSettings::Get();
153    device_settings_provider_ =
154        cros_settings_->GetProvider(chromeos::kReportDeviceVersionInfo);
155    EXPECT_TRUE(device_settings_provider_ != NULL);
156    EXPECT_TRUE(
157        cros_settings_->RemoveSettingsProvider(device_settings_provider_));
158    cros_settings_->AddSettingsProvider(&stub_settings_provider_);
159
160    RestartStatusCollector();
161  }
162
163  virtual ~DeviceStatusCollectorTest() {
164    // Finish pending tasks.
165    content::BrowserThread::GetBlockingPool()->FlushForTesting();
166    message_loop_.RunUntilIdle();
167
168    // Restore the real DeviceSettingsProvider.
169    EXPECT_TRUE(
170      cros_settings_->RemoveSettingsProvider(&stub_settings_provider_));
171    cros_settings_->AddSettingsProvider(device_settings_provider_);
172  }
173
174  void RestartStatusCollector() {
175    status_collector_.reset(
176        new TestingDeviceStatusCollector(&prefs_, &statistics_provider_));
177  }
178
179  void GetStatus() {
180    status_.Clear();
181    status_collector_->GetDeviceStatus(&status_);
182  }
183
184  void CheckThatNoLocationIsReported() {
185    GetStatus();
186    EXPECT_FALSE(status_.has_device_location());
187  }
188
189  void CheckThatAValidLocationIsReported() {
190    // Checks that a location is being reported which matches the valid fix
191    // set using SetMockPositionToReturnNext().
192    GetStatus();
193    EXPECT_TRUE(status_.has_device_location());
194    em::DeviceLocation location = status_.device_location();
195    if (location.has_error_code())
196      EXPECT_EQ(em::DeviceLocation::ERROR_CODE_NONE, location.error_code());
197    EXPECT_TRUE(location.has_latitude());
198    EXPECT_TRUE(location.has_longitude());
199    EXPECT_TRUE(location.has_accuracy());
200    EXPECT_TRUE(location.has_timestamp());
201    EXPECT_FALSE(location.has_altitude());
202    EXPECT_FALSE(location.has_altitude_accuracy());
203    EXPECT_FALSE(location.has_heading());
204    EXPECT_FALSE(location.has_speed());
205    EXPECT_FALSE(location.has_error_message());
206    EXPECT_DOUBLE_EQ(4.3, location.latitude());
207    EXPECT_DOUBLE_EQ(-7.8, location.longitude());
208    EXPECT_DOUBLE_EQ(3., location.accuracy());
209    // Check that the timestamp is not older than ten minutes.
210    EXPECT_TRUE(Time::Now() - Time::FromDoubleT(location.timestamp() / 1000.) <
211                TimeDelta::FromMinutes(10));
212  }
213
214  void CheckThatALocationErrorIsReported() {
215    GetStatus();
216    EXPECT_TRUE(status_.has_device_location());
217    em::DeviceLocation location = status_.device_location();
218    EXPECT_TRUE(location.has_error_code());
219    EXPECT_EQ(em::DeviceLocation::ERROR_CODE_POSITION_UNAVAILABLE,
220              location.error_code());
221  }
222
223 protected:
224  // Convenience method.
225  int64 ActivePeriodMilliseconds() {
226    return policy::DeviceStatusCollector::kIdlePollIntervalSeconds * 1000;
227  }
228
229  MessageLoop message_loop_;
230  content::TestBrowserThread ui_thread_;
231  content::TestBrowserThread file_thread_;
232  content::TestBrowserThread io_thread_;
233
234  TestingPrefServiceSimple prefs_;
235  chromeos::system::MockStatisticsProvider statistics_provider_;
236  scoped_ptr<TestingDeviceStatusCollector> status_collector_;
237  em::DeviceStatusReportRequest status_;
238  chromeos::CrosSettings* cros_settings_;
239  chromeos::CrosSettingsProvider* device_settings_provider_;
240  chromeos::StubCrosSettingsProvider stub_settings_provider_;
241};
242
243TEST_F(DeviceStatusCollectorTest, AllIdle) {
244  IdleState test_states[] = {
245    IDLE_STATE_IDLE,
246    IDLE_STATE_IDLE,
247    IDLE_STATE_IDLE
248  };
249  cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true);
250
251  // Test reporting with no data.
252  GetStatus();
253  EXPECT_EQ(0, status_.active_period_size());
254  EXPECT_EQ(0, GetActiveMilliseconds(status_));
255
256  // Test reporting with a single idle sample.
257  status_collector_->Simulate(test_states, 1);
258  GetStatus();
259  EXPECT_EQ(0, status_.active_period_size());
260  EXPECT_EQ(0, GetActiveMilliseconds(status_));
261
262  // Test reporting with multiple consecutive idle samples.
263  status_collector_->Simulate(test_states,
264                              sizeof(test_states) / sizeof(IdleState));
265  GetStatus();
266  EXPECT_EQ(0, status_.active_period_size());
267  EXPECT_EQ(0, GetActiveMilliseconds(status_));
268}
269
270TEST_F(DeviceStatusCollectorTest, AllActive) {
271  IdleState test_states[] = {
272    IDLE_STATE_ACTIVE,
273    IDLE_STATE_ACTIVE,
274    IDLE_STATE_ACTIVE
275  };
276  cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true);
277
278  // Test a single active sample.
279  status_collector_->Simulate(test_states, 1);
280  GetStatus();
281  EXPECT_EQ(1, status_.active_period_size());
282  EXPECT_EQ(1 * ActivePeriodMilliseconds(), GetActiveMilliseconds(status_));
283  status_.clear_active_period(); // Clear the result protobuf.
284
285  // Test multiple consecutive active samples.
286  status_collector_->Simulate(test_states,
287                              sizeof(test_states) / sizeof(IdleState));
288  GetStatus();
289  EXPECT_EQ(1, status_.active_period_size());
290  EXPECT_EQ(4 * ActivePeriodMilliseconds(), GetActiveMilliseconds(status_));
291}
292
293TEST_F(DeviceStatusCollectorTest, MixedStates) {
294  IdleState test_states[] = {
295    IDLE_STATE_ACTIVE,
296    IDLE_STATE_IDLE,
297    IDLE_STATE_ACTIVE,
298    IDLE_STATE_ACTIVE,
299    IDLE_STATE_IDLE,
300    IDLE_STATE_IDLE,
301    IDLE_STATE_ACTIVE
302  };
303  cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true);
304  status_collector_->Simulate(test_states,
305                              sizeof(test_states) / sizeof(IdleState));
306  GetStatus();
307  EXPECT_EQ(4 * ActivePeriodMilliseconds(), GetActiveMilliseconds(status_));
308}
309
310TEST_F(DeviceStatusCollectorTest, StateKeptInPref) {
311  IdleState test_states[] = {
312    IDLE_STATE_ACTIVE,
313    IDLE_STATE_IDLE,
314    IDLE_STATE_ACTIVE,
315    IDLE_STATE_ACTIVE,
316    IDLE_STATE_IDLE,
317    IDLE_STATE_IDLE
318  };
319  cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true);
320  status_collector_->Simulate(test_states,
321                              sizeof(test_states) / sizeof(IdleState));
322
323  // Process the list a second time after restarting the collector. It should be
324  // able to count the active periods found by the original collector, because
325  // the results are stored in a pref.
326  RestartStatusCollector();
327  status_collector_->Simulate(test_states,
328                              sizeof(test_states) / sizeof(IdleState));
329
330  GetStatus();
331  EXPECT_EQ(6 * ActivePeriodMilliseconds(), GetActiveMilliseconds(status_));
332}
333
334TEST_F(DeviceStatusCollectorTest, Times) {
335  IdleState test_states[] = {
336    IDLE_STATE_ACTIVE,
337    IDLE_STATE_IDLE,
338    IDLE_STATE_ACTIVE,
339    IDLE_STATE_ACTIVE,
340    IDLE_STATE_IDLE,
341    IDLE_STATE_IDLE
342  };
343  cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true);
344  status_collector_->Simulate(test_states,
345                              sizeof(test_states) / sizeof(IdleState));
346  GetStatus();
347  EXPECT_EQ(3 * ActivePeriodMilliseconds(), GetActiveMilliseconds(status_));
348}
349
350TEST_F(DeviceStatusCollectorTest, MaxStoredPeriods) {
351  IdleState test_states[] = {
352    IDLE_STATE_ACTIVE,
353    IDLE_STATE_IDLE
354  };
355  const int kMaxDays = 10;
356
357  cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true);
358  status_collector_->set_max_stored_past_activity_days(kMaxDays - 1);
359  status_collector_->set_max_stored_future_activity_days(1);
360  Time baseline = Time::Now().LocalMidnight();
361
362  // Simulate 12 active periods.
363  for (int i = 0; i < kMaxDays + 2; i++) {
364    status_collector_->Simulate(test_states,
365                                sizeof(test_states) / sizeof(IdleState));
366    // Advance the simulated clock by a day.
367    baseline += TimeDelta::FromDays(1);
368    status_collector_->SetBaselineTime(baseline);
369  }
370
371  // Check that we don't exceed the max number of periods.
372  GetStatus();
373  EXPECT_EQ(kMaxDays - 1, status_.active_period_size());
374
375  // Simulate some future times.
376  for (int i = 0; i < kMaxDays + 2; i++) {
377    status_collector_->Simulate(test_states,
378                                sizeof(test_states) / sizeof(IdleState));
379    // Advance the simulated clock by a day.
380    baseline += TimeDelta::FromDays(1);
381    status_collector_->SetBaselineTime(baseline);
382  }
383  // Set the clock back so the previous simulated times are in the future.
384  baseline -= TimeDelta::FromDays(20);
385  status_collector_->SetBaselineTime(baseline);
386
387  // Collect one more data point to trigger pruning.
388  status_collector_->Simulate(test_states, 1);
389
390  // Check that we don't exceed the max number of periods.
391  status_.clear_active_period();
392  GetStatus();
393  EXPECT_LT(status_.active_period_size(), kMaxDays);
394}
395
396TEST_F(DeviceStatusCollectorTest, ActivityTimesDisabledByDefault) {
397  // If the pref for collecting device activity times isn't explicitly turned
398  // on, no data on activity times should be reported.
399
400  IdleState test_states[] = {
401    IDLE_STATE_ACTIVE,
402    IDLE_STATE_ACTIVE,
403    IDLE_STATE_ACTIVE
404  };
405  status_collector_->Simulate(test_states,
406                              sizeof(test_states) / sizeof(IdleState));
407  GetStatus();
408  EXPECT_EQ(0, status_.active_period_size());
409  EXPECT_EQ(0, GetActiveMilliseconds(status_));
410}
411
412TEST_F(DeviceStatusCollectorTest, ActivityCrossingMidnight) {
413  IdleState test_states[] = {
414    IDLE_STATE_ACTIVE
415  };
416  cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true);
417
418  // Set the baseline time to 10 seconds after midnight.
419  status_collector_->SetBaselineTime(
420      Time::Now().LocalMidnight() + TimeDelta::FromSeconds(10));
421
422  status_collector_->Simulate(test_states, 1);
423  GetStatus();
424  ASSERT_EQ(2, status_.active_period_size());
425
426  em::ActiveTimePeriod period0 = status_.active_period(0);
427  em::ActiveTimePeriod period1 = status_.active_period(1);
428  EXPECT_EQ(ActivePeriodMilliseconds() - 10000, period0.active_duration());
429  EXPECT_EQ(10000, period1.active_duration());
430
431  em::TimePeriod time_period0 = period0.time_period();
432  em::TimePeriod time_period1 = period1.time_period();
433
434  EXPECT_EQ(time_period0.end_timestamp(), time_period1.start_timestamp());
435
436  // Ensure that the start and end times for the period are a day apart.
437  EXPECT_EQ(time_period0.end_timestamp() - time_period0.start_timestamp(),
438            kMillisecondsPerDay);
439  EXPECT_EQ(time_period1.end_timestamp() - time_period1.start_timestamp(),
440            kMillisecondsPerDay);
441}
442
443TEST_F(DeviceStatusCollectorTest, ActivityTimesKeptUntilSubmittedSuccessfully) {
444  IdleState test_states[] = {
445    IDLE_STATE_ACTIVE,
446    IDLE_STATE_ACTIVE,
447  };
448  cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true);
449
450  status_collector_->Simulate(test_states, 2);
451  GetStatus();
452  EXPECT_EQ(2 * ActivePeriodMilliseconds(), GetActiveMilliseconds(status_));
453  em::DeviceStatusReportRequest first_status(status_);
454
455  // The collector returns the same status again.
456  GetStatus();
457  EXPECT_EQ(first_status.SerializeAsString(), status_.SerializeAsString());
458
459  // After indicating a successful submit, the submitted status gets cleared,
460  // but what got collected meanwhile sticks around.
461  status_collector_->Simulate(test_states, 1);
462  status_collector_->OnSubmittedSuccessfully();
463  GetStatus();
464  EXPECT_EQ(ActivePeriodMilliseconds(), GetActiveMilliseconds(status_));
465}
466
467TEST_F(DeviceStatusCollectorTest, DevSwitchBootMode) {
468  // Test that boot mode data is not reported if the pref is not turned on.
469  EXPECT_CALL(statistics_provider_,
470              GetMachineStatistic("devsw_boot", NotNull()))
471      .WillRepeatedly(DoAll(SetArgPointee<1>("0"), Return(true)));
472  GetStatus();
473  EXPECT_FALSE(status_.has_boot_mode());
474
475  // Turn the pref on, and check that the status is reported iff the
476  // statistics provider returns valid data.
477  cros_settings_->SetBoolean(chromeos::kReportDeviceBootMode, true);
478
479  EXPECT_CALL(statistics_provider_,
480              GetMachineStatistic("devsw_boot", NotNull()))
481      .WillOnce(DoAll(SetArgPointee<1>("(error)"), Return(true)));
482  GetStatus();
483  EXPECT_FALSE(status_.has_boot_mode());
484
485  EXPECT_CALL(statistics_provider_,
486              GetMachineStatistic("devsw_boot", NotNull()))
487      .WillOnce(DoAll(SetArgPointee<1>(" "), Return(true)));
488  GetStatus();
489  EXPECT_FALSE(status_.has_boot_mode());
490
491  EXPECT_CALL(statistics_provider_,
492              GetMachineStatistic("devsw_boot", NotNull()))
493      .WillOnce(DoAll(SetArgPointee<1>("0"), Return(true)));
494  GetStatus();
495  EXPECT_EQ("Verified", status_.boot_mode());
496
497  EXPECT_CALL(statistics_provider_,
498              GetMachineStatistic("devsw_boot", NotNull()))
499      .WillOnce(DoAll(SetArgPointee<1>("1"), Return(true)));
500  GetStatus();
501  EXPECT_EQ("Dev", status_.boot_mode());
502}
503
504TEST_F(DeviceStatusCollectorTest, VersionInfo) {
505  // When the pref to collect this data is not enabled, expect that none of
506  // the fields are present in the protobuf.
507  GetStatus();
508  EXPECT_FALSE(status_.has_browser_version());
509  EXPECT_FALSE(status_.has_os_version());
510  EXPECT_FALSE(status_.has_firmware_version());
511
512  cros_settings_->SetBoolean(chromeos::kReportDeviceVersionInfo, true);
513  GetStatus();
514  EXPECT_TRUE(status_.has_browser_version());
515  EXPECT_TRUE(status_.has_os_version());
516  EXPECT_TRUE(status_.has_firmware_version());
517
518  // Check that the browser version is not empty. OS version & firmware
519  // don't have any reasonable values inside the unit test, so those
520  // aren't checked.
521  EXPECT_NE("", status_.browser_version());
522}
523
524TEST_F(DeviceStatusCollectorTest, Location) {
525  content::Geoposition valid_fix;
526  valid_fix.latitude = 4.3;
527  valid_fix.longitude = -7.8;
528  valid_fix.accuracy = 3.;
529  valid_fix.timestamp = Time::Now();
530
531  content::Geoposition invalid_fix;
532  invalid_fix.error_code =
533      content::Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
534  invalid_fix.timestamp = Time::Now();
535
536  // Check that when device location reporting is disabled, no location is
537  // reported.
538  SetMockPositionToReturnNext(valid_fix);
539  CheckThatNoLocationIsReported();
540
541  // Check that when device location reporting is enabled and a valid fix is
542  // available, the location is reported and is stored in local state.
543  SetMockPositionToReturnNext(valid_fix);
544  cros_settings_->SetBoolean(chromeos::kReportDeviceLocation, true);
545  EXPECT_FALSE(prefs_.GetDictionary(prefs::kDeviceLocation)->empty());
546  CheckThatAValidLocationIsReported();
547
548  // Restart the status collector. Check that the last known location has been
549  // retrieved from local state without requesting a geolocation update.
550  SetMockPositionToReturnNext(valid_fix);
551  RestartStatusCollector();
552  CheckThatAValidLocationIsReported();
553  EXPECT_TRUE(mock_position_to_return_next.get());
554
555  // Check that after disabling location reporting again, the last known
556  // location has been cleared from local state and is no longer reported.
557  SetMockPositionToReturnNext(valid_fix);
558  cros_settings_->SetBoolean(chromeos::kReportDeviceLocation, false);
559  // Allow the new pref to propagate to the status collector.
560  message_loop_.RunUntilIdle();
561  EXPECT_TRUE(prefs_.GetDictionary(prefs::kDeviceLocation)->empty());
562  CheckThatNoLocationIsReported();
563
564  // Check that after enabling location reporting again, an error is reported
565  // if no valid fix is available.
566  SetMockPositionToReturnNext(invalid_fix);
567  cros_settings_->SetBoolean(chromeos::kReportDeviceLocation, true);
568  // Allow the new pref to propagate to the status collector.
569  message_loop_.RunUntilIdle();
570  CheckThatALocationErrorIsReported();
571}
572
573}  // namespace policy
574