/* * Copyright (C) 2022 The Android Open Source Project * * 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 #include #include #include #include #include #include #define LOG_TAG "StreamWorker_Test" #include using android::hardware::audio::common::StreamLogic; using android::hardware::audio::common::StreamWorker; class TestWorkerLogic : public StreamLogic { public: struct Stream { void setErrorStatus() { status = Status::ABORT; } void setStopStatus() { status = Status::EXIT; } std::atomic status = Status::CONTINUE; }; // Use nullptr to test error reporting from the worker thread. explicit TestWorkerLogic(Stream* stream) : mStream(stream) {} size_t getWorkerCycles() const { return mWorkerCycles; } int getPriority() const { return mPriority; } bool hasWorkerCycleCalled() const { return mWorkerCycles != 0; } bool hasNoWorkerCycleCalled(useconds_t usec) { const size_t cyclesBefore = mWorkerCycles; usleep(usec); return mWorkerCycles == cyclesBefore; } protected: // StreamLogic implementation std::string init() override { return mStream != nullptr ? "" : "Expected error"; } Status cycle() override { mPriority = getpriority(PRIO_PROCESS, 0); do { mWorkerCycles++; } while (mWorkerCycles == 0); return mStream->status; } private: Stream* const mStream; std::atomic mWorkerCycles = 0; std::atomic mPriority = ANDROID_PRIORITY_DEFAULT; }; using TestWorker = StreamWorker; // The parameter specifies whether an extra call to 'stop' is made at the end. class StreamWorkerInvalidTest : public testing::TestWithParam { public: StreamWorkerInvalidTest() : StreamWorkerInvalidTest(nullptr) {} void TearDown() override { if (GetParam()) { worker.stop(); } } protected: StreamWorkerInvalidTest(TestWorker::Stream* stream) : testing::TestWithParam(), worker(stream) {} TestWorker worker; }; TEST_P(StreamWorkerInvalidTest, Uninitialized) { EXPECT_FALSE(worker.hasWorkerCycleCalled()); EXPECT_FALSE(worker.hasError()); } TEST_P(StreamWorkerInvalidTest, UninitializedPauseIgnored) { EXPECT_FALSE(worker.hasError()); worker.pause(); EXPECT_FALSE(worker.hasError()); } TEST_P(StreamWorkerInvalidTest, UninitializedResumeIgnored) { EXPECT_FALSE(worker.hasError()); worker.resume(); EXPECT_FALSE(worker.hasError()); } TEST_P(StreamWorkerInvalidTest, Start) { EXPECT_FALSE(worker.start()); EXPECT_FALSE(worker.hasWorkerCycleCalled()); EXPECT_TRUE(worker.hasError()); } TEST_P(StreamWorkerInvalidTest, PauseIgnored) { EXPECT_FALSE(worker.start()); EXPECT_TRUE(worker.hasError()); worker.pause(); EXPECT_TRUE(worker.hasError()); } TEST_P(StreamWorkerInvalidTest, ResumeIgnored) { EXPECT_FALSE(worker.start()); EXPECT_TRUE(worker.hasError()); worker.resume(); EXPECT_TRUE(worker.hasError()); } INSTANTIATE_TEST_SUITE_P(StreamWorkerInvalid, StreamWorkerInvalidTest, testing::Bool()); class StreamWorkerTest : public StreamWorkerInvalidTest { public: StreamWorkerTest() : StreamWorkerInvalidTest(&stream) {} protected: TestWorker::Stream stream; }; static constexpr unsigned kWorkerIdleCheckTime = 50 * 1000; TEST_P(StreamWorkerTest, Uninitialized) { EXPECT_FALSE(worker.hasWorkerCycleCalled()); EXPECT_FALSE(worker.hasError()); } TEST_P(StreamWorkerTest, Start) { ASSERT_TRUE(worker.start()); EXPECT_TRUE(worker.waitForAtLeastOneCycle()); EXPECT_FALSE(worker.hasError()); } TEST_P(StreamWorkerTest, StartStop) { ASSERT_TRUE(worker.start()); EXPECT_TRUE(worker.waitForAtLeastOneCycle()); EXPECT_FALSE(worker.hasError()); worker.stop(); EXPECT_FALSE(worker.hasError()); } TEST_P(StreamWorkerTest, WorkerExit) { ASSERT_TRUE(worker.start()); stream.setStopStatus(); worker.waitForAtLeastOneCycle(); EXPECT_FALSE(worker.hasError()); EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime)); } TEST_P(StreamWorkerTest, WorkerJoin) { ASSERT_TRUE(worker.start()); stream.setStopStatus(); worker.join(); EXPECT_FALSE(worker.hasError()); EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime)); } TEST_P(StreamWorkerTest, WorkerError) { ASSERT_TRUE(worker.start()); stream.setErrorStatus(); worker.waitForAtLeastOneCycle(); EXPECT_TRUE(worker.hasError()); EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime)); } TEST_P(StreamWorkerTest, StopAfterError) { ASSERT_TRUE(worker.start()); stream.setErrorStatus(); worker.waitForAtLeastOneCycle(); EXPECT_TRUE(worker.hasError()); EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime)); worker.stop(); EXPECT_TRUE(worker.hasError()); } TEST_P(StreamWorkerTest, PauseResume) { ASSERT_TRUE(worker.start()); EXPECT_TRUE(worker.waitForAtLeastOneCycle()); EXPECT_FALSE(worker.hasError()); worker.pause(); EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime)); EXPECT_FALSE(worker.hasError()); const size_t workerCyclesBefore = worker.getWorkerCycles(); worker.resume(); // 'resume' is synchronous and returns after the worker has looped at least once. EXPECT_GT(worker.getWorkerCycles(), workerCyclesBefore); EXPECT_FALSE(worker.hasError()); } TEST_P(StreamWorkerTest, StopPaused) { ASSERT_TRUE(worker.start()); EXPECT_TRUE(worker.waitForAtLeastOneCycle()); EXPECT_FALSE(worker.hasError()); worker.pause(); worker.stop(); EXPECT_FALSE(worker.hasError()); } TEST_P(StreamWorkerTest, PauseAfterErrorIgnored) { ASSERT_TRUE(worker.start()); stream.setErrorStatus(); worker.waitForAtLeastOneCycle(); EXPECT_TRUE(worker.hasError()); worker.pause(); EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime)); EXPECT_TRUE(worker.hasError()); } TEST_P(StreamWorkerTest, ResumeAfterErrorIgnored) { ASSERT_TRUE(worker.start()); stream.setErrorStatus(); worker.waitForAtLeastOneCycle(); EXPECT_TRUE(worker.hasError()); worker.resume(); EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime)); EXPECT_TRUE(worker.hasError()); } TEST_P(StreamWorkerTest, WorkerErrorOnResume) { ASSERT_TRUE(worker.start()); EXPECT_TRUE(worker.waitForAtLeastOneCycle()); EXPECT_FALSE(worker.hasError()); worker.pause(); EXPECT_FALSE(worker.hasError()); stream.setErrorStatus(); EXPECT_FALSE(worker.hasError()); worker.resume(); worker.waitForAtLeastOneCycle(); EXPECT_TRUE(worker.hasError()); EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime)); } TEST_P(StreamWorkerTest, WaitForAtLeastOneCycle) { ASSERT_TRUE(worker.start()); const size_t workerCyclesBefore = worker.getWorkerCycles(); EXPECT_TRUE(worker.waitForAtLeastOneCycle()); EXPECT_GT(worker.getWorkerCycles(), workerCyclesBefore); } TEST_P(StreamWorkerTest, WaitForAtLeastOneCycleError) { ASSERT_TRUE(worker.start()); stream.setErrorStatus(); EXPECT_FALSE(worker.waitForAtLeastOneCycle()); } TEST_P(StreamWorkerTest, MutexDoesNotBlockWorker) { ASSERT_TRUE(worker.start()); const size_t workerCyclesBefore = worker.getWorkerCycles(); worker.testLockUnlockMutex(true); while (worker.getWorkerCycles() == workerCyclesBefore) { usleep(kWorkerIdleCheckTime); } worker.testLockUnlockMutex(false); EXPECT_TRUE(worker.waitForAtLeastOneCycle()); EXPECT_FALSE(worker.hasError()); } TEST_P(StreamWorkerTest, ThreadName) { const std::string workerName = "TestWorker"; ASSERT_TRUE(worker.start(workerName)) << worker.getError(); char nameBuf[128]; ASSERT_EQ(0, pthread_getname_np(worker.testGetThreadNativeHandle(), nameBuf, sizeof(nameBuf))); EXPECT_EQ(workerName, nameBuf); } TEST_P(StreamWorkerTest, ThreadPriority) { const int priority = ANDROID_PRIORITY_LOWEST; ASSERT_TRUE(worker.start("", priority)) << worker.getError(); EXPECT_TRUE(worker.waitForAtLeastOneCycle()); EXPECT_EQ(priority, worker.getPriority()); } TEST_P(StreamWorkerTest, DeferredStartCheckNoError) { stream.setStopStatus(); EXPECT_TRUE(worker.start(android::hardware::audio::common::internal::kTestSingleThread)); EXPECT_FALSE(worker.hasError()); } TEST_P(StreamWorkerTest, DeferredStartCheckWithError) { stream.setErrorStatus(); EXPECT_FALSE(worker.start(android::hardware::audio::common::internal::kTestSingleThread)); EXPECT_TRUE(worker.hasError()); } INSTANTIATE_TEST_SUITE_P(StreamWorker, StreamWorkerTest, testing::Bool());