summaryrefslogtreecommitdiffstats
path: root/chrome/browser/process_singleton_mac_unittest.cc
blob: 978f6f971869984299b947dfcfb1cb10b620b480 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// Copyright (c) 2010 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 <errno.h>
#include <fcntl.h>
#include <sys/file.h>

#include "chrome/browser/process_singleton.h"

#include "base/eintr_wrapper.h"
#include "base/file_util.h"
#include "base/path_service.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/testing_profile.h"
#include "testing/platform_test.h"

namespace {

class ProcessSingletonMacTest : public PlatformTest {
 public:
  virtual void SetUp() {
    PlatformTest::SetUp();

    // Put the lock in a temporary directory.  Doesn't need to be a
    // full profile to test this code.
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    lock_path_ = temp_dir_.path().Append(chrome::kSingletonLockFilename);
  }

  virtual void TearDown() {
    PlatformTest::TearDown();

    // Verify that the lock was released.
    EXPECT_FALSE(IsLocked());
  }

  // Return |true| if the file exists and is locked.  Forces a failure
  // in the containing test in case of error condition.
  bool IsLocked() {
    int fd = HANDLE_EINTR(open(lock_path_.value().c_str(), O_RDONLY));
    if (fd == -1) {
      EXPECT_EQ(ENOENT, errno) << "Unexpected error opening lockfile.";
      return false;
    }

    file_util::ScopedFD auto_close(&fd);

    int rc = HANDLE_EINTR(flock(fd, LOCK_EX|LOCK_NB));

    // Got the lock, so it wasn't already locked.  Close releases.
    if (rc != -1)
      return false;

    // Someone else has the lock.
    if (errno == EWOULDBLOCK)
      return true;

    EXPECT_EQ(EWOULDBLOCK, errno) << "Unexpected error acquiring lock.";
    return false;
  }

  ScopedTempDir temp_dir_;
  FilePath lock_path_;
};

// Test that the base case doesn't blow up.
TEST_F(ProcessSingletonMacTest, Basic) {
  ProcessSingleton ps(temp_dir_.path());
  EXPECT_FALSE(IsLocked());
  EXPECT_TRUE(ps.Create());
  EXPECT_TRUE(IsLocked());
  ps.Cleanup();
  EXPECT_FALSE(IsLocked());
}

// The destructor should release the lock.
TEST_F(ProcessSingletonMacTest, DestructorReleases) {
  EXPECT_FALSE(IsLocked());
  {
    ProcessSingleton ps(temp_dir_.path());
    EXPECT_TRUE(ps.Create());
    EXPECT_TRUE(IsLocked());
  }
  EXPECT_FALSE(IsLocked());
}

// Multiple singletons should interlock appropriately.
TEST_F(ProcessSingletonMacTest, Interlock) {
  ProcessSingleton ps1(temp_dir_.path());
  ProcessSingleton ps2(temp_dir_.path());

  // Windows and Linux use a command-line flag to suppress this, but
  // it is on a sub-process so the scope is contained.  Rather than
  // add additional API to process_singleton.h in an #ifdef, just tell
  // the reader what to expect and move on.
  LOG(ERROR) << "Expect two failures to obtain the lock.";

  // When |ps1| has the lock, |ps2| cannot get it.
  EXPECT_FALSE(IsLocked());
  EXPECT_TRUE(ps1.Create());
  EXPECT_TRUE(IsLocked());
  EXPECT_FALSE(ps2.Create());
  ps1.Cleanup();

  // And when |ps2| has the lock, |ps1| cannot get it.
  EXPECT_FALSE(IsLocked());
  EXPECT_TRUE(ps2.Create());
  EXPECT_TRUE(IsLocked());
  EXPECT_FALSE(ps1.Create());
  ps2.Cleanup();
  EXPECT_FALSE(IsLocked());
}

// Like |Interlock| test, but via |NotifyOtherProcessOrCreate()|.
TEST_F(ProcessSingletonMacTest, NotifyOtherProcessOrCreate) {
  ProcessSingleton ps1(temp_dir_.path());
  ProcessSingleton ps2(temp_dir_.path());

  // Windows and Linux use a command-line flag to suppress this, but
  // it is on a sub-process so the scope is contained.  Rather than
  // add additional API to process_singleton.h in an #ifdef, just tell
  // the reader what to expect and move on.
  LOG(ERROR) << "Expect two failures to obtain the lock.";

  // When |ps1| has the lock, |ps2| cannot get it.
  EXPECT_FALSE(IsLocked());
  EXPECT_EQ(ProcessSingleton::PROCESS_NONE, ps1.NotifyOtherProcessOrCreate());
  EXPECT_TRUE(IsLocked());
  EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE, ps2.NotifyOtherProcessOrCreate());
  ps1.Cleanup();

  // And when |ps2| has the lock, |ps1| cannot get it.
  EXPECT_FALSE(IsLocked());
  EXPECT_EQ(ProcessSingleton::PROCESS_NONE, ps2.NotifyOtherProcessOrCreate());
  EXPECT_TRUE(IsLocked());
  EXPECT_EQ(ProcessSingleton::PROFILE_IN_USE, ps1.NotifyOtherProcessOrCreate());
  ps2.Cleanup();
  EXPECT_FALSE(IsLocked());
}

// TODO(shess): Test that the lock is released when the process dies.
// DEATH_TEST?  I don't know.  If the code to communicate between
// browser processes is ever written, this all would need to be tested
// more like the other platforms, in which case it would be easy.

}  // namespace