• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <errno.h>
6 #include <fcntl.h>
7 #include <sys/file.h>
8 
9 #include "chrome/browser/process_singleton.h"
10 
11 #include "base/file_util.h"
12 #include "base/path_service.h"
13 #include "base/posix/eintr_wrapper.h"
14 #include "chrome/common/chrome_constants.h"
15 #include "chrome/common/chrome_paths.h"
16 #include "chrome/test/base/testing_profile.h"
17 #include "testing/platform_test.h"
18 
19 namespace {
20 
21 class ProcessSingletonMacTest : public PlatformTest {
22  public:
SetUp()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 
TearDown()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.
IsLocked()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   base::ScopedTempDir temp_dir_;
65   base::FilePath lock_path_;
66 };
67 
68 // Test that the base case doesn't blow up.
TEST_F(ProcessSingletonMacTest,Basic)69 TEST_F(ProcessSingletonMacTest, Basic) {
70   ProcessSingleton ps(temp_dir_.path(),
71                       ProcessSingleton::NotificationCallback());
72   EXPECT_FALSE(IsLocked());
73   EXPECT_TRUE(ps.Create());
74   EXPECT_TRUE(IsLocked());
75   ps.Cleanup();
76   EXPECT_FALSE(IsLocked());
77 }
78 
79 // The destructor should release the lock.
TEST_F(ProcessSingletonMacTest,DestructorReleases)80 TEST_F(ProcessSingletonMacTest, DestructorReleases) {
81   EXPECT_FALSE(IsLocked());
82   {
83     ProcessSingleton ps(temp_dir_.path(),
84                         ProcessSingleton::NotificationCallback());
85     EXPECT_TRUE(ps.Create());
86     EXPECT_TRUE(IsLocked());
87   }
88   EXPECT_FALSE(IsLocked());
89 }
90 
91 // Multiple singletons should interlock appropriately.
TEST_F(ProcessSingletonMacTest,Interlock)92 TEST_F(ProcessSingletonMacTest, Interlock) {
93   ProcessSingleton ps1(temp_dir_.path(),
94                        ProcessSingleton::NotificationCallback());
95   ProcessSingleton ps2(temp_dir_.path(),
96                        ProcessSingleton::NotificationCallback());
97 
98   // Windows and Linux use a command-line flag to suppress this, but
99   // it is on a sub-process so the scope is contained.  Rather than
100   // add additional API to process_singleton.h in an #ifdef, just tell
101   // the reader what to expect and move on.
102   LOG(ERROR) << "Expect two failures to obtain the lock.";
103 
104   // When |ps1| has the lock, |ps2| cannot get it.
105   EXPECT_FALSE(IsLocked());
106   EXPECT_TRUE(ps1.Create());
107   EXPECT_TRUE(IsLocked());
108   EXPECT_FALSE(ps2.Create());
109   ps1.Cleanup();
110 
111   // And when |ps2| has the lock, |ps1| cannot get it.
112   EXPECT_FALSE(IsLocked());
113   EXPECT_TRUE(ps2.Create());
114   EXPECT_TRUE(IsLocked());
115   EXPECT_FALSE(ps1.Create());
116   ps2.Cleanup();
117   EXPECT_FALSE(IsLocked());
118 }
119 
120 // Like |Interlock| test, but via |NotifyOtherProcessOrCreate()|.
TEST_F(ProcessSingletonMacTest,NotifyOtherProcessOrCreate)121 TEST_F(ProcessSingletonMacTest, NotifyOtherProcessOrCreate) {
122   ProcessSingleton ps1(temp_dir_.path(),
123                        ProcessSingleton::NotificationCallback());
124   ProcessSingleton ps2(temp_dir_.path(),
125                        ProcessSingleton::NotificationCallback());
126 
127   // Windows and Linux use a command-line flag to suppress this, but
128   // it is on a sub-process so the scope is contained.  Rather than
129   // add additional API to process_singleton.h in an #ifdef, just tell
130   // the reader what to expect and move on.
131   LOG(ERROR) << "Expect two failures to obtain the lock.";
132 
133   // When |ps1| has the lock, |ps2| cannot get it.
134   EXPECT_FALSE(IsLocked());
135   EXPECT_EQ(
136       ProcessSingleton::PROCESS_NONE,
137       ps1.NotifyOtherProcessOrCreate());
138   EXPECT_TRUE(IsLocked());
139   EXPECT_EQ(
140       ProcessSingleton::PROFILE_IN_USE,
141       ps2.NotifyOtherProcessOrCreate());
142   ps1.Cleanup();
143 
144   // And when |ps2| has the lock, |ps1| cannot get it.
145   EXPECT_FALSE(IsLocked());
146   EXPECT_EQ(
147       ProcessSingleton::PROCESS_NONE,
148       ps2.NotifyOtherProcessOrCreate());
149   EXPECT_TRUE(IsLocked());
150   EXPECT_EQ(
151       ProcessSingleton::PROFILE_IN_USE,
152       ps1.NotifyOtherProcessOrCreate());
153   ps2.Cleanup();
154   EXPECT_FALSE(IsLocked());
155 }
156 
157 // TODO(shess): Test that the lock is released when the process dies.
158 // DEATH_TEST?  I don't know.  If the code to communicate between
159 // browser processes is ever written, this all would need to be tested
160 // more like the other platforms, in which case it would be easy.
161 
162 }  // namespace
163