| // Copyright 2017 The Cobalt Authors. All Rights Reserved. |
| // |
| // 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. |
| |
| #include "starboard/common/semaphore.h" |
| #include "starboard/common/time.h" |
| #include "starboard/nplb/thread_helpers.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace starboard { |
| namespace nplb { |
| |
| TEST(Semaphore, PutAndTake) { |
| Semaphore semaphore; |
| semaphore.Put(); |
| semaphore.Take(); |
| EXPECT_FALSE(semaphore.TakeTry()); |
| } |
| |
| TEST(Semaphore, PutAndTakeTry) { |
| Semaphore semaphore; |
| semaphore.Put(); |
| EXPECT_TRUE(semaphore.TakeTry()); |
| } |
| |
| // Tests the expectation that waiting no time will succeed |
| // If the semaphore is uncontended. |
| TEST(Semaphore, TakeWait_Zero) { |
| Semaphore semaphore; |
| semaphore.Put(); |
| EXPECT_TRUE(semaphore.TakeWait(0)); |
| } |
| |
| TEST(Semaphore, InitialValue_One) { |
| Semaphore semaphore(1); |
| semaphore.Take(); |
| EXPECT_FALSE(semaphore.TakeTry()); |
| } |
| |
| class ThreadTakesSemaphore : public AbstractTestThread { |
| public: |
| explicit ThreadTakesSemaphore(Semaphore* s) : semaphore_(s) {} |
| void Run() override { semaphore_->Take(); } |
| |
| private: |
| Semaphore* semaphore_; |
| }; |
| TEST(Semaphore, ThreadTakes) { |
| Semaphore semaphore; |
| ThreadTakesSemaphore thread(&semaphore); |
| thread.Start(); |
| semaphore.Put(); |
| thread.Join(); |
| } |
| |
| class ThreadTakesWaitSemaphore : public AbstractTestThread { |
| public: |
| explicit ThreadTakesWaitSemaphore(int64_t wait_us) |
| : thread_started_(false), |
| wait_us_(wait_us), |
| result_signaled_(false), |
| result_wait_time_(0) {} |
| void Run() override { |
| thread_started_ = true; |
| int64_t start_time = CurrentMonotonicTime(); |
| result_signaled_ = semaphore_.TakeWait(wait_us_); |
| result_wait_time_ = CurrentMonotonicTime() - start_time; |
| } |
| |
| // Use a volatile bool to signal when the thread has started executing |
| // instead of a semaphore since some platforms may take a relatively long |
| // time after signalling the semaphore to return from the Put. |
| volatile bool thread_started_; |
| |
| int64_t wait_us_; |
| Semaphore semaphore_; |
| bool result_signaled_; |
| int64_t result_wait_time_; |
| }; |
| |
| TEST(Semaphore, FLAKY_ThreadTakesWait_PutBeforeTimeExpires) { |
| int64_t timeout_time = 250'000; // 250ms |
| int64_t wait_time = 1000; // 1ms |
| ThreadTakesWaitSemaphore thread(timeout_time); |
| |
| // Create thread and wait for it to start executing. |
| thread.Start(); |
| while (!thread.thread_started_) { |
| SbThreadSleep(1000); |
| } |
| |
| SbThreadSleep(wait_time); |
| thread.semaphore_.Put(); |
| |
| thread.Join(); |
| |
| EXPECT_TRUE(thread.result_signaled_); |
| EXPECT_LT(thread.result_wait_time_, timeout_time); |
| } |
| |
| double IsDoubleNear(double first, double second, double diff_threshold) { |
| double diff = first - second; |
| if (diff < 0) { |
| diff = -diff; |
| } |
| return diff < diff_threshold; |
| } |
| |
| TEST(Semaphore, ThreadTakesWait_TimeExpires) { |
| const int attempts = 20; // Retest up to 20 times. |
| bool passed = false; |
| |
| const int64_t kTimeThreshold = 5'000; // 5ms |
| |
| for (int i = 0; i < attempts; ++i) { |
| int64_t wait_time = 20'000; // 20ms |
| ThreadTakesWaitSemaphore thread(wait_time); |
| |
| // Create thread and wait for it to start executing. |
| thread.Start(); |
| while (!thread.thread_started_) { |
| SbThreadSleep(1000); |
| } |
| |
| // It is possible for the thread to be preempted just before processing |
| // Semaphore::TakeWait, so sleep for an extra amount of time to avoid the |
| // semaphore being legitimately signalled during the wait time (because |
| // the thread started TakeWait late). |
| SbThreadSleep(wait_time * 5); |
| thread.semaphore_.Put(); |
| |
| thread.Join(); |
| EXPECT_FALSE(thread.result_signaled_); |
| |
| if (IsDoubleNear(1.0 * wait_time, 1.0 * thread.result_wait_time_, |
| kTimeThreshold * 1.0)) { |
| return; // Test passed. |
| } |
| } |
| |
| EXPECT_TRUE(false) << "Thread waited, but time exceeded expectations."; |
| } |
| |
| class ThreadPutsSemaphore : public AbstractTestThread { |
| public: |
| explicit ThreadPutsSemaphore(Semaphore* s) : semaphore_(s) {} |
| void Run() override { semaphore_->Put(); } |
| |
| private: |
| Semaphore* semaphore_; |
| }; |
| TEST(Semaphore, ThreadPuts) { |
| Semaphore semaphore; |
| ThreadPutsSemaphore thread(&semaphore); |
| thread.Start(); |
| thread.Join(); |
| semaphore.Put(); |
| } |
| |
| } // namespace nplb |
| } // namespace starboard |