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
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 ScopedTempDir temp_dir_;
65 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 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.
TEST_F(ProcessSingletonMacTest,DestructorReleases)79 TEST_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.
TEST_F(ProcessSingletonMacTest,Interlock)90 TEST_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()|.
TEST_F(ProcessSingletonMacTest,NotifyOtherProcessOrCreate)117 TEST_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