blob: e3eb86ab95035400aa0f156bf47008f34c14d744 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/common/mac/staging_watcher.h"
#include <dispatch/dispatch.h>
#include "base/mac/bundle_locations.h"
#include "base/mac/mac_util.h"
#include "base/mac/scoped_nsobject.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
enum class KVOOrNot { kUseKVO, kDontUseKVO };
class StagingKeyWatcherTest : public testing::TestWithParam<KVOOrNot> {
public:
StagingKeyWatcherTest() = default;
~StagingKeyWatcherTest() = default;
protected:
void SetUp() override {
testingBundleID_.reset([[NSString alloc]
initWithFormat:@"org.chromium.StagingKeyWatcherTest.%d", getpid()]);
defaults_.reset(
[[NSUserDefaults alloc] initWithSuiteName:testingBundleID_]);
[defaults_ removeObjectForKey:[CrStagingKeyWatcher stagingKeyForTesting]];
}
void TearDown() override {
[defaults_ removeObjectForKey:[CrStagingKeyWatcher stagingKeyForTesting]];
}
base::scoped_nsobject<CrStagingKeyWatcher> CreateKeyWatcher() {
base::scoped_nsobject<CrStagingKeyWatcher> keyWatcher(
[[CrStagingKeyWatcher alloc]
initWithUserDefaults:defaults_
pollingTime:0.5
disableKVOForTesting:(GetParam() == KVOOrNot::kDontUseKVO)]);
return keyWatcher;
}
void SetDefaultsValue(id value) {
[defaults_ setObject:value
forKey:[CrStagingKeyWatcher stagingKeyForTesting]];
}
void ClearDefaultsValueInSeparateProcess() {
[NSTask launchedTaskWithLaunchPath:@"/usr/bin/defaults"
arguments:@[
@"delete", testingBundleID_.get(),
[CrStagingKeyWatcher stagingKeyForTesting]
]];
}
void SetDefaultsValueInSeparateProcess() {
NSString* appPath = [base::mac::OuterBundle() bundlePath];
[NSTask launchedTaskWithLaunchPath:@"/usr/bin/defaults"
arguments:@[
@"write", testingBundleID_.get(),
[CrStagingKeyWatcher stagingKeyForTesting],
@"-dict", appPath, appPath
]];
}
private:
base::scoped_nsobject<NSString> testingBundleID_;
base::scoped_nsobject<NSUserDefaults> defaults_;
};
INSTANTIATE_TEST_SUITE_P(KVOandNot,
StagingKeyWatcherTest,
testing::Values(KVOOrNot::kUseKVO,
KVOOrNot::kDontUseKVO));
} // namespace
TEST_P(StagingKeyWatcherTest, NoBlockingWhenNoKey) {
base::scoped_nsobject<CrStagingKeyWatcher> watcher = CreateKeyWatcher();
[watcher waitForStagingKeyToClear];
ASSERT_FALSE([watcher lastWaitWasBlockedForTesting]);
}
TEST_P(StagingKeyWatcherTest, NoBlockingWhenWrongKeyType) {
SetDefaultsValue(@"this is not a dictionary");
base::scoped_nsobject<CrStagingKeyWatcher> watcher = CreateKeyWatcher();
[watcher waitForStagingKeyToClear];
ASSERT_FALSE([watcher lastWaitWasBlockedForTesting]);
}
TEST_P(StagingKeyWatcherTest, NoBlockingWhenArrayType) {
SetDefaultsValue(@[ @3, @1, @4, @1, @5 ]);
base::scoped_nsobject<CrStagingKeyWatcher> watcher = CreateKeyWatcher();
[watcher waitForStagingKeyToClear];
ASSERT_FALSE([watcher lastWaitWasBlockedForTesting]);
}
TEST_P(StagingKeyWatcherTest, NoBlockingWhenEmptyArray) {
SetDefaultsValue(@[]);
base::scoped_nsobject<CrStagingKeyWatcher> watcher = CreateKeyWatcher();
[watcher waitForStagingKeyToClear];
ASSERT_FALSE([watcher lastWaitWasBlockedForTesting]);
}
TEST_P(StagingKeyWatcherTest, NoBlockingWhenEmptyDictionary) {
SetDefaultsValue(@{});
base::scoped_nsobject<CrStagingKeyWatcher> watcher = CreateKeyWatcher();
[watcher waitForStagingKeyToClear];
ASSERT_FALSE([watcher lastWaitWasBlockedForTesting]);
}
TEST_P(StagingKeyWatcherTest, BlockFunctionality) {
NSString* appPath = [base::mac::OuterBundle() bundlePath];
SetDefaultsValue(@{appPath : appPath});
NSRunLoop* runloop = [NSRunLoop currentRunLoop];
ASSERT_EQ(nil, [runloop currentMode]);
dispatch_async(dispatch_get_main_queue(), ^{
ASSERT_NE(nil, [runloop currentMode]);
ClearDefaultsValueInSeparateProcess();
});
base::scoped_nsobject<CrStagingKeyWatcher> watcher = CreateKeyWatcher();
[watcher waitForStagingKeyToClear];
ASSERT_TRUE([watcher lastWaitWasBlockedForTesting]);
}
TEST_P(StagingKeyWatcherTest, CallbackOnKeySet) {
// The staging key begins not set.
base::scoped_nsobject<CrStagingKeyWatcher> watcher = CreateKeyWatcher();
NSRunLoop* runloop = [NSRunLoop currentRunLoop];
__block bool observerCalled = false;
[watcher setStagingKeyChangedObserver:^(BOOL stagingKeySet) {
observerCalled = true;
CFRunLoopStop([runloop getCFRunLoop]);
}];
SetDefaultsValueInSeparateProcess();
ASSERT_FALSE([watcher isStagingKeySet]);
while (!observerCalled && [runloop runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]]) {
/* run! */
}
EXPECT_TRUE([watcher isStagingKeySet]);
}
TEST_P(StagingKeyWatcherTest, CallbackOnKeyUnset) {
NSString* appPath = [base::mac::OuterBundle() bundlePath];
SetDefaultsValue(@{appPath : appPath});
base::scoped_nsobject<CrStagingKeyWatcher> watcher = CreateKeyWatcher();
NSRunLoop* runloop = [NSRunLoop currentRunLoop];
__block bool observerCalled = false;
[watcher setStagingKeyChangedObserver:^(BOOL stagingKeySet) {
observerCalled = true;
CFRunLoopStop([runloop getCFRunLoop]);
}];
ClearDefaultsValueInSeparateProcess();
ASSERT_TRUE([watcher isStagingKeySet]);
while (!observerCalled && [runloop runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]]) {
/* run! */
}
EXPECT_FALSE([watcher isStagingKeySet]);
}