Chromium Code Reviews| Index: util/mach/scoped_task_suspend_test.cc |
| diff --git a/util/mach/scoped_task_suspend_test.cc b/util/mach/scoped_task_suspend_test.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..4c92960d33944a34c05b29fca698e82906f72cc5 |
| --- /dev/null |
| +++ b/util/mach/scoped_task_suspend_test.cc |
| @@ -0,0 +1,170 @@ |
| +// Copyright 2014 The Crashpad 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 "util/mach/scoped_task_suspend.h" |
| + |
| +#include <mach/mach.h> |
| +#include <stdint.h> |
| + |
| +#include <algorithm> |
| + |
| +#include "gtest/gtest.h" |
| +#include "util/file/fd_io.h" |
| +#include "util/mach/task_memory.h" |
| +#include "util/misc/clock.h" |
| +#include "util/test/mac/mach_multiprocess.h" |
| + |
| +namespace crashpad { |
| +namespace test { |
| +namespace { |
| + |
| +class ScopedTaskSuspendTest final : public MachMultiprocess { |
| + public: |
| + ScopedTaskSuspendTest() : MachMultiprocess() {} |
| + ~ScopedTaskSuspendTest() {} |
| + |
| + private: |
| + // Reads the int at |address| from the child process’ memory at |task_memory|. |
| + static int CounterValue(TaskMemory* task_memory, mach_vm_address_t address) { |
|
Robert Sesek
2014/10/13 15:01:37
Does this test really need to use TaskMemory? Coul
|
| + int counter = 0; |
| + EXPECT_TRUE(task_memory->Read(address, sizeof(counter), &counter)); |
| + return counter; |
| + } |
| + |
| + // Determines whether the child process is updating its counter. The counter |
| + // is expected to be updated while the child process is running, but should |
| + // appear frozen while the child process is suspended. |
| + static bool CounterIsRunning( |
| + TaskMemory* task_memory, mach_vm_address_t address) { |
| + int initial_counter = CounterValue(task_memory, address); |
| + |
| + // Look for any change in the counter. If there is any change, the counter |
| + // is running. If there is no change, it doesn’t necessarily mean that the |
| + // child process is suspended, because it may not have been scheduled for |
| + // another reason. Retry a few times in a loop with a delay. |
| + for (size_t tries = 10; tries > 0; --tries) { |
| + SleepNanoseconds(1E5); // 100 microseconds |
| + |
| + int counter = CounterValue(task_memory, address); |
| + if (counter != initial_counter) { |
| + return true; |
| + } |
| + } |
| + |
| + return false; |
| + } |
| + |
| + // MachMultiprocess: |
| + |
| + virtual void MachMultiprocessParent() override { |
| + int read_fd = ReadPipeFD(); |
| + |
| + // Get the address of the counter in the child process. |
| + mach_vm_address_t address; |
| + CheckedReadFD(read_fd, &address, sizeof(address)); |
| + |
| + task_t child_task = ChildTask(); |
| + TaskMemory task_memory(child_task); |
| + |
| + // In the initial state, the child process should be scheduled, and it |
| + // should be updating its counter. |
| + EXPECT_TRUE(CounterIsRunning(&task_memory, address)); |
| + |
| + { |
| + // Suspend the child process. |
| + ScopedTaskSuspend suspend(child_task); |
| + |
| + // CounterIsRunning() just makes sure that the child process is not |
| + // running within a short duration. Checking the counter value before and |
| + // after the sleep ensures that the child process has not run during a |
| + // larger period that it was expected to be suspended. |
| + int initial_value = CounterValue(&task_memory, address); |
| + |
| + EXPECT_FALSE(CounterIsRunning(&task_memory, address)); |
| + |
| + // Sleep for a little while, giving the child process a chance to measure |
| + // that one of its loop iterations took a long time. |
| + SleepNanoseconds(1E6); // 1 millisecond |
| + |
| + EXPECT_FALSE(CounterIsRunning(&task_memory, address)); |
| + |
| + // Make sure that the child process hasn’t had a chance to run at all. |
| + int final_value = CounterValue(&task_memory, address); |
| + EXPECT_EQ(initial_value, final_value); |
| + } |
| + |
| + // The suspension should no longer be placed, and the child process should |
| + // be running again. This makes sure that when the counter was seen as |
| + // stopped before, it’s because the child process was really suspended, and |
| + // not because it had exited its loop. |
| + EXPECT_TRUE(CounterIsRunning(&task_memory, address)); |
| + |
| + // Get the child’s idea of its final counter value, and compare that against |
| + // this process’ idea of the same value. This tests that CounterValue() was |
| + // in fact reading the right value from the child process. |
| + int expect_counter; |
| + CheckedReadFD(read_fd, &expect_counter, sizeof(expect_counter)); |
| + |
| + int counter = CounterValue(&task_memory, address); |
| + EXPECT_EQ(expect_counter, counter); |
| + } |
| + |
| + virtual void MachMultiprocessChild() override { |
| + int write_fd = WritePipeFD(); |
| + |
| + // Tell the parent process where to find the counter in this process’ |
| + // address space. |
| + volatile int counter = 0; |
| + mach_vm_address_t address = reinterpret_cast<mach_vm_address_t>(&counter); |
| + CheckedWriteFD(write_fd, &address, sizeof(address)); |
| + |
| + // Loop for a while, computing the longest duration for an iteration of the |
| + // loop. Inside each loop, update the counter, which the parent process can |
| + // examine to see whether the loop is running from its perspective. |
| + uint64_t start = ClockMonotonicNanoseconds(); |
| + uint64_t deadline = start + 1E8; // 100 milliseconds |
| + uint64_t now = start; |
| + uint64_t last = now; |
| + uint64_t max_delay = 0; |
| + do { |
| + ++counter; |
| + uint64_t delay = now - last; |
| + max_delay = std::max(max_delay, delay); |
| + last = now; |
| + } while ((now = ClockMonotonicNanoseconds()) < deadline); |
| + |
| + // The longest iteration of the loop should be at least as long as the time |
| + // that the parent process had this process suspended, but because of the |
| + // tricky nature of scheduling, just make sure that the longest iteration |
| + // took a perceptible amount of time. |
| + EXPECT_GE(max_delay, 1E5); // 100 microseconds |
| + |
| + // Tell the parent process where the counter ended up. |
| + CheckedWriteFD(write_fd, const_cast<int*>(&counter), sizeof(counter)); |
| + |
| + // Wait for the parent process to finish reading this process’ memory. |
| + CheckedReadFDAtEOF(ReadPipeFD()); |
| + } |
| + |
| + DISALLOW_COPY_AND_ASSIGN(ScopedTaskSuspendTest); |
| +}; |
| + |
| +TEST(ScopedTaskSuspend, ScopedTaskSuspend) { |
| + ScopedTaskSuspendTest scoped_task_suspend_test; |
| + scoped_task_suspend_test.Run(); |
| +} |
| + |
| +} // namespace |
| +} // namespace test |
| +} // namespace crashpad |