You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
233 lines
8.0 KiB
233 lines
8.0 KiB
// Copyright 2015 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 "base/command_line.h"
|
|
#include "base/files/file.h"
|
|
#include "base/files/file_util.h"
|
|
#include "base/files/scoped_temp_dir.h"
|
|
#include "base/macros.h"
|
|
#include "base/test/multiprocess_test.h"
|
|
#include "base/test/test_timeouts.h"
|
|
#include "base/threading/platform_thread.h"
|
|
#include "base/time/time.h"
|
|
#include "build/build_config.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
#include "testing/multiprocess_func_list.h"
|
|
|
|
using base::File;
|
|
using base::FilePath;
|
|
|
|
namespace {
|
|
|
|
// Flag for the parent to share a temp dir to the child.
|
|
const char kTempDirFlag[] = "temp-dir";
|
|
|
|
// Flags to control how the subprocess unlocks the file.
|
|
const char kFileUnlock[] = "file-unlock";
|
|
const char kCloseUnlock[] = "close-unlock";
|
|
const char kExitUnlock[] = "exit-unlock";
|
|
|
|
// File to lock in temp dir.
|
|
const char kLockFile[] = "lockfile";
|
|
|
|
// Constants for various requests and responses, used as |signal_file| parameter
|
|
// to signal/wait helpers.
|
|
const char kSignalLockFileLocked[] = "locked.signal";
|
|
const char kSignalLockFileClose[] = "close.signal";
|
|
const char kSignalLockFileClosed[] = "closed.signal";
|
|
const char kSignalLockFileUnlock[] = "unlock.signal";
|
|
const char kSignalLockFileUnlocked[] = "unlocked.signal";
|
|
const char kSignalExit[] = "exit.signal";
|
|
|
|
// Signal an event by creating a file which didn't previously exist.
|
|
bool SignalEvent(const FilePath& signal_dir, const char* signal_file) {
|
|
File file(signal_dir.AppendASCII(signal_file),
|
|
File::FLAG_CREATE | File::FLAG_WRITE);
|
|
return file.IsValid();
|
|
}
|
|
|
|
// Check whether an event was signaled.
|
|
bool CheckEvent(const FilePath& signal_dir, const char* signal_file) {
|
|
File file(signal_dir.AppendASCII(signal_file),
|
|
File::FLAG_OPEN | File::FLAG_READ);
|
|
return file.IsValid();
|
|
}
|
|
|
|
// Busy-wait for an event to be signaled, returning false for timeout.
|
|
bool WaitForEventWithTimeout(const FilePath& signal_dir,
|
|
const char* signal_file,
|
|
const base::TimeDelta& timeout) {
|
|
const base::Time finish_by = base::Time::Now() + timeout;
|
|
while (!CheckEvent(signal_dir, signal_file)) {
|
|
if (base::Time::Now() > finish_by)
|
|
return false;
|
|
base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Wait forever for the event to be signaled (should never return false).
|
|
bool WaitForEvent(const FilePath& signal_dir, const char* signal_file) {
|
|
return WaitForEventWithTimeout(signal_dir, signal_file,
|
|
base::TimeDelta::Max());
|
|
}
|
|
|
|
// Keep these in sync so StartChild*() can refer to correct test main.
|
|
#define ChildMain ChildLockUnlock
|
|
#define ChildMainString "ChildLockUnlock"
|
|
|
|
// Subprocess to test getting a file lock then releasing it. |kTempDirFlag|
|
|
// must pass in an existing temporary directory for the lockfile and signal
|
|
// files. One of the following flags must be passed to determine how to unlock
|
|
// the lock file:
|
|
// - |kFileUnlock| calls Unlock() to unlock.
|
|
// - |kCloseUnlock| calls Close() while the lock is held.
|
|
// - |kExitUnlock| exits while the lock is held.
|
|
MULTIPROCESS_TEST_MAIN(ChildMain) {
|
|
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
|
|
const FilePath temp_path = command_line->GetSwitchValuePath(kTempDirFlag);
|
|
CHECK(base::DirectoryExists(temp_path));
|
|
|
|
// Immediately lock the file.
|
|
File file(temp_path.AppendASCII(kLockFile),
|
|
File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WRITE);
|
|
CHECK(file.IsValid());
|
|
CHECK_EQ(File::FILE_OK, file.Lock());
|
|
CHECK(SignalEvent(temp_path, kSignalLockFileLocked));
|
|
|
|
if (command_line->HasSwitch(kFileUnlock)) {
|
|
// Wait for signal to unlock, then unlock the file.
|
|
CHECK(WaitForEvent(temp_path, kSignalLockFileUnlock));
|
|
CHECK_EQ(File::FILE_OK, file.Unlock());
|
|
CHECK(SignalEvent(temp_path, kSignalLockFileUnlocked));
|
|
} else if (command_line->HasSwitch(kCloseUnlock)) {
|
|
// Wait for the signal to close, then close the file.
|
|
CHECK(WaitForEvent(temp_path, kSignalLockFileClose));
|
|
file.Close();
|
|
CHECK(!file.IsValid());
|
|
CHECK(SignalEvent(temp_path, kSignalLockFileClosed));
|
|
} else {
|
|
CHECK(command_line->HasSwitch(kExitUnlock));
|
|
}
|
|
|
|
// Wait for signal to exit, so that unlock or close can be distinguished from
|
|
// exit.
|
|
CHECK(WaitForEvent(temp_path, kSignalExit));
|
|
return 0;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class FileLockingTest : public testing::Test {
|
|
public:
|
|
FileLockingTest() = default;
|
|
|
|
protected:
|
|
void SetUp() override {
|
|
testing::Test::SetUp();
|
|
|
|
// Setup the temp dir and the lock file.
|
|
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
|
|
lock_file_.Initialize(
|
|
temp_dir_.GetPath().AppendASCII(kLockFile),
|
|
File::FLAG_CREATE | File::FLAG_READ | File::FLAG_WRITE);
|
|
ASSERT_TRUE(lock_file_.IsValid());
|
|
}
|
|
|
|
bool SignalEvent(const char* signal_file) {
|
|
return ::SignalEvent(temp_dir_.GetPath(), signal_file);
|
|
}
|
|
|
|
bool WaitForEventOrTimeout(const char* signal_file) {
|
|
return ::WaitForEventWithTimeout(temp_dir_.GetPath(), signal_file,
|
|
TestTimeouts::action_timeout());
|
|
}
|
|
|
|
// Start a child process set to use the specified unlock action, and wait for
|
|
// it to lock the file.
|
|
void StartChildAndSignalLock(const char* unlock_action) {
|
|
// Create a temporary dir and spin up a ChildLockExit subprocess against it.
|
|
const FilePath temp_path = temp_dir_.GetPath();
|
|
base::CommandLine child_command_line(
|
|
base::GetMultiProcessTestChildBaseCommandLine());
|
|
child_command_line.AppendSwitchPath(kTempDirFlag, temp_path);
|
|
child_command_line.AppendSwitch(unlock_action);
|
|
lock_child_ = base::SpawnMultiProcessTestChild(
|
|
ChildMainString, child_command_line, base::LaunchOptions());
|
|
ASSERT_TRUE(lock_child_.IsValid());
|
|
|
|
// Wait for the child to lock the file.
|
|
ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileLocked));
|
|
}
|
|
|
|
// Signal the child to exit cleanly.
|
|
void ExitChildCleanly() {
|
|
ASSERT_TRUE(SignalEvent(kSignalExit));
|
|
int rv = -1;
|
|
ASSERT_TRUE(WaitForMultiprocessTestChildExit(
|
|
lock_child_, TestTimeouts::action_timeout(), &rv));
|
|
ASSERT_EQ(0, rv);
|
|
}
|
|
|
|
base::ScopedTempDir temp_dir_;
|
|
base::File lock_file_;
|
|
base::Process lock_child_;
|
|
|
|
private:
|
|
DISALLOW_COPY_AND_ASSIGN(FileLockingTest);
|
|
};
|
|
|
|
// Test that locks are released by Unlock().
|
|
TEST_F(FileLockingTest, LockAndUnlock) {
|
|
StartChildAndSignalLock(kFileUnlock);
|
|
|
|
ASSERT_NE(File::FILE_OK, lock_file_.Lock());
|
|
ASSERT_TRUE(SignalEvent(kSignalLockFileUnlock));
|
|
ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileUnlocked));
|
|
ASSERT_EQ(File::FILE_OK, lock_file_.Lock());
|
|
ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
|
|
|
|
ExitChildCleanly();
|
|
}
|
|
|
|
// Test that locks are released on Close().
|
|
TEST_F(FileLockingTest, UnlockOnClose) {
|
|
StartChildAndSignalLock(kCloseUnlock);
|
|
|
|
ASSERT_NE(File::FILE_OK, lock_file_.Lock());
|
|
ASSERT_TRUE(SignalEvent(kSignalLockFileClose));
|
|
ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileClosed));
|
|
ASSERT_EQ(File::FILE_OK, lock_file_.Lock());
|
|
ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
|
|
|
|
ExitChildCleanly();
|
|
}
|
|
|
|
// Test that locks are released on exit.
|
|
TEST_F(FileLockingTest, UnlockOnExit) {
|
|
StartChildAndSignalLock(kExitUnlock);
|
|
|
|
ASSERT_NE(File::FILE_OK, lock_file_.Lock());
|
|
ExitChildCleanly();
|
|
ASSERT_EQ(File::FILE_OK, lock_file_.Lock());
|
|
ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
|
|
}
|
|
|
|
// Test that killing the process releases the lock. This should cover crashing.
|
|
// Flaky on Android (http://crbug.com/747518)
|
|
#if defined(OS_ANDROID)
|
|
#define MAYBE_UnlockOnTerminate DISABLED_UnlockOnTerminate
|
|
#else
|
|
#define MAYBE_UnlockOnTerminate UnlockOnTerminate
|
|
#endif
|
|
TEST_F(FileLockingTest, MAYBE_UnlockOnTerminate) {
|
|
// The child will wait for an exit which never arrives.
|
|
StartChildAndSignalLock(kExitUnlock);
|
|
|
|
ASSERT_NE(File::FILE_OK, lock_file_.Lock());
|
|
ASSERT_TRUE(TerminateMultiProcessTestChild(lock_child_, 0, true));
|
|
ASSERT_EQ(File::FILE_OK, lock_file_.Lock());
|
|
ASSERT_EQ(File::FILE_OK, lock_file_.Unlock());
|
|
}
|