blob: 9cc89aea8253d32bce108eea99eba69f58e27e41 [file] [log] [blame]
/*
* Copyright 2015 Nest Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Test bed for folly/Cancellation.h
#include <folly/Cancellation.h>
#include <gtest/gtest.h>
#include <future>
using ::folly::Cancellation;
/* Tests the basic operation of the construct */
TEST(Cancellation, BasicFunction)
{
Cancellation c;
EXPECT_FALSE(c.is_cancelled());
c.cancel();
EXPECT_TRUE(c.is_cancelled());
}
/* Independently constructed objects shall not share state */
TEST(Cancellation, Independence)
{
Cancellation c0;
Cancellation c1;
EXPECT_FALSE(c0.is_cancelled());
EXPECT_FALSE(c1.is_cancelled());
c0.cancel();
EXPECT_TRUE(c0.is_cancelled());
EXPECT_FALSE(c1.is_cancelled());
}
/* Objects using copy constructor shall have a common state */
TEST(Cancellation, CopyConstructor)
{
Cancellation c0;
Cancellation c1(c0);
EXPECT_FALSE(c0.is_cancelled());
EXPECT_FALSE(c1.is_cancelled());
c0.cancel();
EXPECT_TRUE(c0.is_cancelled());
EXPECT_TRUE(c1.is_cancelled());
}
/* Objects using assignment operator shall have a common state */
TEST(Cancellation, AssignmentOperator0)
{
Cancellation c0;
Cancellation c1;
c0 = c1;
EXPECT_FALSE(c0.is_cancelled());
EXPECT_FALSE(c1.is_cancelled());
c0.cancel();
EXPECT_TRUE(c0.is_cancelled());
EXPECT_TRUE(c1.is_cancelled());
}
/* Objects using assignment operator shall have a common state, even after calling cancel() */
TEST(Cancellation, AssignmentOperator1)
{
Cancellation c0;
Cancellation c1;
EXPECT_FALSE(c0.is_cancelled());
EXPECT_FALSE(c1.is_cancelled());
c1.cancel();
c0 = c1;
EXPECT_TRUE(c0.is_cancelled());
EXPECT_TRUE(c1.is_cancelled());
}
/* Objects using move operator shall transfer state */
TEST(Cancellation, MoveConstructor0)
{
Cancellation c0;
EXPECT_FALSE(c0.is_cancelled());
Cancellation c1(std::move(c0));
EXPECT_FALSE(c1.is_cancelled());
}
/* Objects using move operator shall transfer state */
TEST(Cancellation, MoveConstructor1)
{
Cancellation c0;
EXPECT_FALSE(c0.is_cancelled());
c0.cancel();
EXPECT_TRUE(c0.is_cancelled());
Cancellation c1(std::move(c0));
EXPECT_TRUE(c1.is_cancelled());
}
/* Objects using move assignment shall transfer state */
TEST(Cancellation, MoveAssignment0)
{
Cancellation c0;
EXPECT_FALSE(c0.is_cancelled());
Cancellation c1;
c1.cancel();
EXPECT_TRUE(c1.is_cancelled());
c1 = std::move(c0);
EXPECT_FALSE(c1.is_cancelled());
}
/* Objects using move assignment shall transfer state */
TEST(Cancellation, MoveAssignment1)
{
Cancellation c0;
Cancellation c1;
EXPECT_FALSE(c0.is_cancelled());
EXPECT_FALSE(c1.is_cancelled());
c0.cancel();
EXPECT_TRUE(c0.is_cancelled());
EXPECT_FALSE(c1.is_cancelled());
c0 = std::move(c1);
EXPECT_FALSE(c0.is_cancelled());
}
/* CancellationSharedState shall be a basic lockable type */
TEST(CancellationSharedState, LockableInterface)
{
folly::detail::CancellationSharedState cp;
cp.lock();
cp.unlock();
EXPECT_TRUE(true);
}
/* CancellationStateLock shall acquire lock if not cancelled */
TEST(CancellationSharedState, StateLockAcquire)
{
Cancellation c;
EXPECT_FALSE(c.is_cancelled());
auto h = c.hold_state();
EXPECT_TRUE(h.owns_lock());
EXPECT_TRUE(static_cast<bool>(h));
}
/* CancellationStateLock shall not acquire lock if cancelled */
TEST(CancellationSharedState, StateLockDenied)
{
Cancellation c;
c.cancel();
EXPECT_TRUE(c.is_cancelled());
auto h = c.hold_state();
EXPECT_FALSE(h.owns_lock());
EXPECT_FALSE(static_cast<bool>(h));
}
/* Multiple CancellationStateLock's may be held without blocking */
TEST(CancellationStateLock, MultiReader)
{
Cancellation c;
auto h0 = c.hold_state();
EXPECT_TRUE((bool)h0);
auto h1 = c.hold_state();
EXPECT_TRUE((bool)h1);
EXPECT_FALSE(c.is_cancelled());
h0.unlock();
EXPECT_FALSE(h0);
h1.unlock();
EXPECT_FALSE(h1);
c.cancel();
}
/* CancellationStateLock shall be RAII */
TEST(CancellationStateLock, RAII)
{
Cancellation c;
{
auto h = c.hold_state();
EXPECT_TRUE((bool)h);
}
c.cancel();
{
auto h = c.hold_state();
EXPECT_FALSE(h);
}
}
/* cancel operation shall block a cancel until all holds release */
TEST(CancellationStateLock, BlocksCancel)
{
Cancellation c;
auto h0 = c.hold_state();
auto h1 = c.hold_state();
auto h2 = c.hold_state();
auto h3 = c.hold_state();
auto h4 = c.hold_state();
EXPECT_TRUE((bool)h0);
EXPECT_TRUE((bool)h1);
EXPECT_TRUE((bool)h2);
EXPECT_TRUE((bool)h3);
EXPECT_TRUE((bool)h4);
EXPECT_FALSE(c.is_cancelled());
std::mutex mx;
std::condition_variable cond;
bool ready = false;
auto fut = std::async(std::launch::async,
[&] {
std::unique_lock<std::mutex> lock(mx);
ready = true;
cond.notify_one();
lock.unlock();
c.cancel();
});
std::unique_lock<std::mutex> lock(mx);
while (!ready) {
cond.wait(lock);
}
lock.unlock();
/* while adding a sleep right here would ensure that the remote thread
* is blocked... defer that to the BlocksMultiCancel test
*/
EXPECT_FALSE(c.is_cancelled());
h0.unlock();
EXPECT_FALSE(c.is_cancelled());
h1.unlock();
EXPECT_FALSE(c.is_cancelled());
h2.unlock();
EXPECT_FALSE(c.is_cancelled());
h3.unlock();
EXPECT_FALSE(c.is_cancelled());
h4.unlock();
/* state is indeterminate until thread completes */
fut.wait();
EXPECT_TRUE(c.is_cancelled());
}
/* cancel operation shall block multiple cancellations until all holds release */
TEST(CancellationStateLock, BlocksMultiCancel)
{
Cancellation c;
auto h0 = c.hold_state();
auto h1 = c.hold_state();
auto h2 = c.hold_state();
auto h3 = c.hold_state();
auto h4 = c.hold_state();
EXPECT_TRUE((bool)h0);
EXPECT_TRUE((bool)h1);
EXPECT_TRUE((bool)h2);
EXPECT_TRUE((bool)h3);
EXPECT_TRUE((bool)h4);
EXPECT_FALSE(c.is_cancelled());
std::mutex mx;
std::condition_variable cond;
unsigned ready {0};
auto lam = [&] {
std::unique_lock<std::mutex> lock(mx);
++ready;
cond.notify_one();
lock.unlock();
c.cancel();
};
auto fut0 = std::async(std::launch::async, lam);
auto fut1 = std::async(std::launch::async, lam);
auto fut2 = std::async(std::launch::async, lam);
auto fut3 = std::async(std::launch::async, lam);
auto fut4 = std::async(std::launch::async, lam);
std::unique_lock<std::mutex> lock(mx);
while (ready < 5) {
cond.wait(lock);
}
lock.unlock();
/* try to ensure remote threads are /really/ blocked */
usleep(40000);
EXPECT_FALSE(c.is_cancelled());
h0.unlock();
EXPECT_FALSE(c.is_cancelled());
h1.unlock();
EXPECT_FALSE(c.is_cancelled());
h2.unlock();
EXPECT_FALSE(c.is_cancelled());
h3.unlock();
EXPECT_FALSE(c.is_cancelled());
h4.unlock();
fut0.wait();
EXPECT_TRUE(c.is_cancelled());
fut1.wait();
fut2.wait();
fut3.wait();
fut4.wait();
EXPECT_TRUE(c.is_cancelled());
}