| /* |
| * 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()); |
| } |