1// Copyright (c) 2010 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 <errno.h>
6#include <fcntl.h>
7#include <sys/file.h>
8
9#include "chrome/browser/process_singleton.h"
10
11#include "base/eintr_wrapper.h"
12#include "base/file_util.h"
13#include "base/path_service.h"
14#include "chrome/common/chrome_constants.h"
15#include "chrome/common/chrome_paths.h"
16#include "chrome/test/testing_profile.h"
17#include "testing/platform_test.h"
18
19namespace {
20
21class ProcessSingletonMacTest : public PlatformTest {
22 public:
23  virtual void SetUp() {
24    PlatformTest::SetUp();
25
26    // Put the lock in a temporary directory.  Doesn't need to be a
27    // full profile to test this code.
28    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
29    lock_path_ = temp_dir_.path().Append(chrome::kSingletonLockFilename);
30  }
31
32  virtual void TearDown() {
33    PlatformTest::TearDown();
34
35    // Verify that the lock was released.
36    EXPECT_FALSE(IsLocked());
37  }
38
39  // Return |true| if the file exists and is locked.  Forces a failure
40  // in the containing test in case of error condition.
41  bool IsLocked() {
42    int fd = HANDLE_EINTR(open(lock_path_.value().c_str(), O_RDONLY));
43    if (fd == -1) {
44      EXPECT_EQ(ENOENT, errno) << "Unexpected error opening lockfile.";
45      return false;
46    }
47
48    file_util::ScopedFD auto_close(&fd);
49
50    int rc = HANDLE_EINTR(flock(fd, LOCK_EX|LOCK_NB));
51
52    // Got the lock, so it wasn't already locked.  Close releases.
53    if (rc != -1)
54      return false;
55
56    // Someone else has the lock.
57    if (errno == EWOULDBLOCK)
58      return true;
59
60    EXPECT_EQ(EWOULDBLOCK, errno) << "Unexpected error acquiring lock.";
61    return false;
62  }
63
64  ScopedTempDir temp_dir_;
65  FilePath lock_path_;
66};
67
68// Test that the base case doesn't blow up.
69TEST_F(ProcessSingletonMacTest, Basic) {
70  ProcessSingleton ps(temp_dir_.path());
71  EXPECT_FALSE(IsLocked());
72  EXPECT_TRUE(ps.Create());
73  EXPECT_TRUE(IsLocked());
74  ps.Cleanup();
75  EXPECT_FALSE(IsLocked());
76}
77
78// The destructor should release the lock.
79TEST_F(ProcessSingletonMacTest, DestructorReleases) {
80  EXPECT_FALSE(IsLocked());
81  {
82    ProcessSingleton ps(temp_dir_.path());
83    EXPECT_TRUE(ps.Create());
84    EXPECT_TRUE(IsLocked());
85  }
86  EXPECT_FALSE(IsLocked());
87}
88
89// Multiple singletons should interlock appropriately.
90TEST_F(ProcessSingletonMacTest, Interlock) {
91  ProcessSingleton ps1(temp_dir_.path());
92  ProcessSingleton ps2(temp_dir_.path());
93
94  // Windows and Linux use a command-line flag to suppress this, but
95  // it is on a sub-process so the scope is contained.  Rather than
96  // add additional API to process_singleton.h in an #ifdef, just tell
97  // the reader what to expect and move on.
98  LOG(ERROR) << "Expect two failures to obtain the lock.";
99
100  // When |ps1| has the lock, |ps2| cannot get it.
101  EXPECT_FALSE(IsLocked());
102  EXPECT_TRUE(ps1.Create());
103  EXPECT_TRUE(IsLocked());
104  EXPECT_FALSE(ps2.Create());
105  ps1.Cleanup();
106
107  // And when |ps2| has the lock, |ps1| cannot get it.
108  EXPECT_FALSE(IsLocked());
109  EXPECT_TRUE(ps2.Create());
110  EXPECT_TRUE(IsLocked());
111  EXPECT_FALSE(ps1.Create());
112  ps2.Cleanup();
113  EXPECT_FALSE(IsLocked());
114}
115
116// Like |Interlock| test, but via |NotifyOtherProcessOrCreate()|.
117TEST_F(ProcessSingletonMacTest, NotifyOtherProcessOrCreate) {
118  ProcessSingleton ps1(temp_dir_.path());
119  ProcessSingleton ps2(temp_dir_.path());
120
121  // Windows and Linux use a command-line flag to suppress this, but
122  // it is on a sub-process so the scope is contained.  Rather than
123  // add additional API to process_singleton.h in an #ifdef, just tell
124  // the reader what to expect and move on.
125  LOG(ERROR) << "Expect two failures to obtain the lock.";
126
127  // When |ps1| has the lock, |ps2| cannot get it.
128  EXPECT_FALSE(IsLocked());
129  EXPECT_EQ(ProcessSingleton::PROCESS_NONE, ps1.NotifyOtherProcessOrCreate());
130  EXPECT_TRUE(IsLocked());
131  EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE, ps2.NotifyOtherProcessOrCreate());
132  ps1.Cleanup();
133
134  // And when |ps2| has the lock, |ps1| cannot get it.
135  EXPECT_FALSE(IsLocked());
136  EXPECT_EQ(ProcessSingleton::PROCESS_NONE, ps2.NotifyOtherProcessOrCreate());
137  EXPECT_TRUE(IsLocked());
138  EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE, ps1.NotifyOtherProcessOrCreate());
139  ps2.Cleanup();
140  EXPECT_FALSE(IsLocked());
141}
142
143// TODO(shess): Test that the lock is released when the process dies.
144// DEATH_TEST?  I don't know.  If the code to communicate between
145// browser processes is ever written, this all would need to be tested
146// more like the other platforms, in which case it would be easy.
147
148}  // namespace
149