OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Crashpad Authors. All rights reserved. |
| 2 // |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 // you may not use this file except in compliance with the License. |
| 5 // You may obtain a copy of the License at |
| 6 // |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 // |
| 9 // Unless required by applicable law or agreed to in writing, software |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 // See the License for the specific language governing permissions and |
| 13 // limitations under the License. |
| 14 |
| 15 #include "util/mac/process_reader.h" |
| 16 |
| 17 #include <dispatch/dispatch.h> |
| 18 #include <mach/mach.h> |
| 19 #include <string.h> |
| 20 |
| 21 #include <map> |
| 22 #include <string> |
| 23 |
| 24 #include "base/logging.h" |
| 25 #include "base/mac/scoped_mach_port.h" |
| 26 #include "base/posix/eintr_wrapper.h" |
| 27 #include "build/build_config.h" |
| 28 #include "gtest/gtest.h" |
| 29 #include "util/file/fd_io.h" |
| 30 #include "util/stdlib/pointer_container.h" |
| 31 #include "util/test/mac/mach_errors.h" |
| 32 #include "util/test/mac/mach_multiprocess.h" |
| 33 #include "util/test/errors.h" |
| 34 |
| 35 namespace { |
| 36 |
| 37 using namespace crashpad; |
| 38 using namespace crashpad::test; |
| 39 |
| 40 TEST(ProcessReader, SelfBasic) { |
| 41 ProcessReader process_reader; |
| 42 ASSERT_TRUE(process_reader.Initialize(mach_task_self())); |
| 43 |
| 44 #if !defined(ARCH_CPU_64_BITS) |
| 45 EXPECT_FALSE(process_reader.Is64Bit()); |
| 46 #else |
| 47 EXPECT_TRUE(process_reader.Is64Bit()); |
| 48 #endif |
| 49 |
| 50 EXPECT_EQ(getpid(), process_reader.ProcessID()); |
| 51 EXPECT_EQ(getppid(), process_reader.ParentProcessID()); |
| 52 |
| 53 const char kTestMemory[] = "Some test memory"; |
| 54 char buffer[arraysize(kTestMemory)]; |
| 55 ASSERT_TRUE(process_reader.Memory()->Read( |
| 56 reinterpret_cast<mach_vm_address_t>(kTestMemory), |
| 57 sizeof(kTestMemory), |
| 58 &buffer)); |
| 59 EXPECT_STREQ(kTestMemory, buffer); |
| 60 } |
| 61 |
| 62 const char kTestMemory[] = "Read me from another process"; |
| 63 |
| 64 class ProcessReaderChild final : public MachMultiprocess { |
| 65 public: |
| 66 ProcessReaderChild() : MachMultiprocess() {} |
| 67 |
| 68 ~ProcessReaderChild() {} |
| 69 |
| 70 protected: |
| 71 void Parent() override { |
| 72 ProcessReader process_reader; |
| 73 ASSERT_TRUE(process_reader.Initialize(ChildTask())); |
| 74 |
| 75 #if !defined(ARCH_CPU_64_BITS) |
| 76 EXPECT_FALSE(process_reader.Is64Bit()); |
| 77 #else |
| 78 EXPECT_TRUE(process_reader.Is64Bit()); |
| 79 #endif |
| 80 |
| 81 EXPECT_EQ(getpid(), process_reader.ParentProcessID()); |
| 82 EXPECT_EQ(ChildPID(), process_reader.ProcessID()); |
| 83 |
| 84 int read_fd = ReadPipeFD(); |
| 85 |
| 86 mach_vm_address_t address; |
| 87 int rv = ReadFD(read_fd, &address, sizeof(address)); |
| 88 ASSERT_EQ(static_cast<ssize_t>(sizeof(address)), rv) |
| 89 << ErrnoMessage("read"); |
| 90 |
| 91 std::string read_string; |
| 92 ASSERT_TRUE(process_reader.Memory()->ReadCString(address, &read_string)); |
| 93 EXPECT_EQ(kTestMemory, read_string); |
| 94 |
| 95 // Tell the child that it’s OK to exit. The child needed to be kept alive |
| 96 // until the parent finished working with it. |
| 97 int write_fd = WritePipeFD(); |
| 98 char c = '\0'; |
| 99 rv = WriteFD(write_fd, &c, 1); |
| 100 ASSERT_EQ(1, rv) << ErrnoMessage("write"); |
| 101 } |
| 102 |
| 103 void Child() override { |
| 104 int write_fd = WritePipeFD(); |
| 105 |
| 106 mach_vm_address_t address = |
| 107 reinterpret_cast<mach_vm_address_t>(kTestMemory); |
| 108 int rv = WriteFD(write_fd, &address, sizeof(address)); |
| 109 ASSERT_EQ(static_cast<ssize_t>(sizeof(address)), rv) |
| 110 << ErrnoMessage("write"); |
| 111 |
| 112 // Wait for the parent to say that it’s OK to exit. |
| 113 int read_fd = ReadPipeFD(); |
| 114 char c; |
| 115 rv = ReadFD(read_fd, &c, 1); |
| 116 ASSERT_EQ(1, rv) << ErrnoMessage("read"); |
| 117 } |
| 118 |
| 119 private: |
| 120 DISALLOW_COPY_AND_ASSIGN(ProcessReaderChild); |
| 121 }; |
| 122 |
| 123 TEST(ProcessReader, ChildBasic) { |
| 124 ProcessReaderChild process_reader_child; |
| 125 process_reader_child.Run(); |
| 126 } |
| 127 |
| 128 // Returns a thread ID given a pthread_t. This wraps pthread_threadid_np() but |
| 129 // that function has a cumbersome interface because it returns a success value. |
| 130 // This function CHECKs success and returns the thread ID directly. |
| 131 uint64_t PthreadToThreadID(pthread_t pthread) { |
| 132 uint64_t thread_id; |
| 133 int rv = pthread_threadid_np(pthread, &thread_id); |
| 134 CHECK_EQ(rv, 0); |
| 135 return thread_id; |
| 136 } |
| 137 |
| 138 TEST(ProcessReader, SelfOneThread) { |
| 139 ProcessReader process_reader; |
| 140 ASSERT_TRUE(process_reader.Initialize(mach_task_self())); |
| 141 |
| 142 const std::vector<ProcessReaderThread>& threads = process_reader.Threads(); |
| 143 |
| 144 // If other tests ran in this process previously, threads may have been |
| 145 // created and may still be running. This check must look for at least one |
| 146 // thread, not exactly one thread. |
| 147 ASSERT_GE(threads.size(), 1u); |
| 148 |
| 149 EXPECT_EQ(PthreadToThreadID(pthread_self()), threads[0].id); |
| 150 |
| 151 base::mac::ScopedMachSendRight thread_self(mach_thread_self()); |
| 152 EXPECT_EQ(thread_self, threads[0].port); |
| 153 |
| 154 EXPECT_EQ(0, threads[0].suspend_count); |
| 155 } |
| 156 |
| 157 class TestThreadPool { |
| 158 public: |
| 159 struct ThreadExpectation { |
| 160 mach_vm_address_t stack_address; |
| 161 int suspend_count; |
| 162 }; |
| 163 |
| 164 TestThreadPool() : thread_infos_() { |
| 165 } |
| 166 |
| 167 // Resumes suspended threads, signals each thread’s exit semaphore asking it |
| 168 // to exit, and joins each thread, blocking until they have all exited. |
| 169 ~TestThreadPool() { |
| 170 for (ThreadInfo* thread_info : thread_infos_) { |
| 171 mach_port_t thread_port = pthread_mach_thread_np(thread_info->pthread); |
| 172 while (thread_info->suspend_count > 0) { |
| 173 kern_return_t kr = thread_resume(thread_port); |
| 174 EXPECT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "thread_resume"); |
| 175 --thread_info->suspend_count; |
| 176 } |
| 177 } |
| 178 |
| 179 for (const ThreadInfo* thread_info : thread_infos_) { |
| 180 dispatch_semaphore_signal(thread_info->exit_semaphore); |
| 181 } |
| 182 |
| 183 for (const ThreadInfo* thread_info : thread_infos_) { |
| 184 int rv = pthread_join(thread_info->pthread, NULL); |
| 185 CHECK_EQ(0, rv); |
| 186 } |
| 187 } |
| 188 |
| 189 // Starts |thread_count| threads and waits on each thread’s ready semaphore, |
| 190 // so that when this function returns, all threads have been started and have |
| 191 // all run to the point that they’ve signalled that they are ready. |
| 192 void StartThreads(size_t thread_count) { |
| 193 ASSERT_TRUE(thread_infos_.empty()); |
| 194 |
| 195 for (size_t thread_index = 0; thread_index < thread_count; ++thread_index) { |
| 196 ThreadInfo* thread_info = new ThreadInfo(); |
| 197 thread_infos_.push_back(thread_info); |
| 198 |
| 199 int rv = pthread_create(&thread_info->pthread, |
| 200 NULL, |
| 201 ThreadMain, |
| 202 thread_info); |
| 203 ASSERT_EQ(0, rv); |
| 204 } |
| 205 |
| 206 for (const ThreadInfo* thread_info : thread_infos_) { |
| 207 long rv = dispatch_semaphore_wait(thread_info->ready_semaphore, |
| 208 DISPATCH_TIME_FOREVER); |
| 209 ASSERT_EQ(0, rv); |
| 210 } |
| 211 |
| 212 // If present, suspend the thread at indices 1 through 3 the same number of |
| 213 // times as their index. This tests reporting of suspend counts. |
| 214 for (size_t thread_index = 1; |
| 215 thread_index < thread_infos_.size() && thread_index < 4; |
| 216 ++thread_index) { |
| 217 mach_port_t thread_port = |
| 218 pthread_mach_thread_np(thread_infos_[thread_index]->pthread); |
| 219 for (size_t suspend_count = 0; |
| 220 suspend_count < thread_index; |
| 221 ++suspend_count) { |
| 222 kern_return_t kr = thread_suspend(thread_port); |
| 223 EXPECT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "thread_suspend"); |
| 224 if (kr == KERN_SUCCESS) { |
| 225 ++thread_infos_[thread_index]->suspend_count; |
| 226 } |
| 227 } |
| 228 } |
| 229 } |
| 230 |
| 231 uint64_t GetThreadInfo(size_t thread_index, |
| 232 ThreadExpectation* expectation) { |
| 233 CHECK_LT(thread_index, thread_infos_.size()); |
| 234 |
| 235 const ThreadInfo* thread_info = thread_infos_[thread_index]; |
| 236 expectation->stack_address = thread_info->stack_address; |
| 237 expectation->suspend_count = thread_info->suspend_count; |
| 238 |
| 239 return PthreadToThreadID(thread_info->pthread); |
| 240 } |
| 241 |
| 242 private: |
| 243 struct ThreadInfo { |
| 244 ThreadInfo() |
| 245 : pthread(NULL), |
| 246 stack_address(0), |
| 247 ready_semaphore(dispatch_semaphore_create(0)), |
| 248 exit_semaphore(dispatch_semaphore_create(0)), |
| 249 suspend_count(0) { |
| 250 } |
| 251 |
| 252 ~ThreadInfo() { |
| 253 dispatch_release(exit_semaphore); |
| 254 dispatch_release(ready_semaphore); |
| 255 } |
| 256 |
| 257 // The thread’s ID, set at the time the thread is created. |
| 258 pthread_t pthread; |
| 259 |
| 260 // An address somewhere within the thread’s stack. The thread sets this in |
| 261 // its ThreadMain(). |
| 262 mach_vm_address_t stack_address; |
| 263 |
| 264 // The worker thread signals ready_semaphore to indicate that it’s done |
| 265 // setting up its ThreadInfo structure. The main thread waits on this |
| 266 // semaphore before using any data that the worker thread is responsible for |
| 267 // setting. |
| 268 dispatch_semaphore_t ready_semaphore; |
| 269 |
| 270 // The worker thread waits on exit_semaphore to determine when it’s safe to |
| 271 // exit. The main thread signals exit_semaphore when it no longer needs the |
| 272 // worker thread. |
| 273 dispatch_semaphore_t exit_semaphore; |
| 274 |
| 275 // The thread’s suspend count. |
| 276 int suspend_count; |
| 277 }; |
| 278 |
| 279 static void* ThreadMain(void* argument) { |
| 280 ThreadInfo* thread_info = static_cast<ThreadInfo*>(argument); |
| 281 |
| 282 thread_info->stack_address = |
| 283 reinterpret_cast<mach_vm_address_t>(&thread_info); |
| 284 |
| 285 dispatch_semaphore_signal(thread_info->ready_semaphore); |
| 286 dispatch_semaphore_wait(thread_info->exit_semaphore, DISPATCH_TIME_FOREVER); |
| 287 |
| 288 // Check this here after everything’s known to be synchronized, otherwise |
| 289 // there’s a race between the parent thread storing this thread’s pthread_t |
| 290 // in thread_info_pthread and this thread starting and attempting to access |
| 291 // it. |
| 292 CHECK_EQ(pthread_self(), thread_info->pthread); |
| 293 |
| 294 return NULL; |
| 295 } |
| 296 |
| 297 // This is a PointerVector because the address of a ThreadInfo object is |
| 298 // passed to each thread’s ThreadMain(), so they cannot move around in memory. |
| 299 PointerVector<ThreadInfo> thread_infos_; |
| 300 |
| 301 DISALLOW_COPY_AND_ASSIGN(TestThreadPool); |
| 302 }; |
| 303 |
| 304 typedef std::map<uint64_t, TestThreadPool::ThreadExpectation> ThreadMap; |
| 305 |
| 306 // Verifies that all of the threads in |threads|, obtained from ProcessReader, |
| 307 // agree with the expectation in |thread_map|. If |tolerate_extra_threads| is |
| 308 // true, |threads| is allowed to contain threads that are not listed in |
| 309 // |thread_map|. This is useful when testing situations where code outside of |
| 310 // the test’s control (such as system libraries) may start threads, or may have |
| 311 // started threads prior to a test’s execution. |
| 312 void ExpectSeveralThreads(ThreadMap* thread_map, |
| 313 const std::vector<ProcessReaderThread>& threads, |
| 314 const bool tolerate_extra_threads) { |
| 315 if (tolerate_extra_threads) { |
| 316 ASSERT_GE(threads.size(), thread_map->size()); |
| 317 } else { |
| 318 ASSERT_EQ(thread_map->size(), threads.size()); |
| 319 } |
| 320 |
| 321 for (size_t thread_index = 0; thread_index < threads.size(); ++thread_index) { |
| 322 const ProcessReaderThread& thread = threads[thread_index]; |
| 323 mach_vm_address_t thread_stack_region_end = |
| 324 thread.stack_region_address + thread.stack_region_size; |
| 325 |
| 326 const auto& iterator = thread_map->find(thread.id); |
| 327 if (!tolerate_extra_threads) { |
| 328 // Make sure that the thread is in the expectation map. |
| 329 ASSERT_NE(thread_map->end(), iterator); |
| 330 } |
| 331 |
| 332 if (iterator != thread_map->end()) { |
| 333 EXPECT_GE(iterator->second.stack_address, thread.stack_region_address); |
| 334 EXPECT_LT(iterator->second.stack_address, thread_stack_region_end); |
| 335 |
| 336 EXPECT_EQ(iterator->second.suspend_count, thread.suspend_count); |
| 337 |
| 338 // Remove the thread from the expectation map since it’s already been |
| 339 // found. This makes it easy to check for duplicate thread IDs, and makes |
| 340 // it easy to check that all expected threads were found. |
| 341 thread_map->erase(iterator); |
| 342 } |
| 343 |
| 344 // Make sure that this thread’s ID, stack region, and port don’t conflict |
| 345 // with any other thread’s. Each thread should have a unique value for its |
| 346 // ID and port, and each should have its own stack that doesn’t touch any |
| 347 // other thread’s stack. |
| 348 for (size_t other_thread_index = 0; |
| 349 other_thread_index < threads.size(); |
| 350 ++other_thread_index) { |
| 351 if (thread_index == other_thread_index) { |
| 352 continue; |
| 353 } |
| 354 |
| 355 const ProcessReaderThread& other_thread = threads[other_thread_index]; |
| 356 |
| 357 EXPECT_NE(thread.id, other_thread.id); |
| 358 EXPECT_NE(thread.port, other_thread.port); |
| 359 |
| 360 mach_vm_address_t other_thread_stack_region_end = |
| 361 other_thread.stack_region_address + other_thread.stack_region_size; |
| 362 EXPECT_FALSE( |
| 363 thread.stack_region_address >= other_thread.stack_region_address && |
| 364 thread.stack_region_address < other_thread_stack_region_end); |
| 365 EXPECT_FALSE( |
| 366 thread_stack_region_end > other_thread.stack_region_address & |
| 367 thread_stack_region_end <= other_thread_stack_region_end); |
| 368 } |
| 369 } |
| 370 |
| 371 // Make sure that each expected thread was found. |
| 372 EXPECT_TRUE(thread_map->empty()); |
| 373 } |
| 374 |
| 375 TEST(ProcessReader, SelfSeveralThreads) { |
| 376 // Set up the ProcessReader here, before any other threads are running. This |
| 377 // tests that the threads it returns are lazily initialized as a snapshot of |
| 378 // the threads at the time of the first call to Threads(), and not at the |
| 379 // time the ProcessReader was created or initialized. |
| 380 ProcessReader process_reader; |
| 381 ASSERT_TRUE(process_reader.Initialize(mach_task_self())); |
| 382 |
| 383 TestThreadPool thread_pool; |
| 384 const size_t kChildThreads = 16; |
| 385 thread_pool.StartThreads(kChildThreads); |
| 386 if (Test::HasFatalFailure()) { |
| 387 return; |
| 388 } |
| 389 |
| 390 // Build a map of all expected threads, keyed by each thread’s ID. The values |
| 391 // are addresses that should lie somewhere within each thread’s stack. |
| 392 ThreadMap thread_map; |
| 393 const uint64_t self_thread_id = PthreadToThreadID(pthread_self()); |
| 394 TestThreadPool::ThreadExpectation expectation; |
| 395 expectation.stack_address = reinterpret_cast<mach_vm_address_t>(&thread_map); |
| 396 expectation.suspend_count = 0; |
| 397 thread_map[self_thread_id] = expectation; |
| 398 for (size_t thread_index = 0; thread_index < kChildThreads; ++thread_index) { |
| 399 uint64_t thread_id = thread_pool.GetThreadInfo(thread_index, &expectation); |
| 400 |
| 401 // There can’t be any duplicate thread IDs. |
| 402 EXPECT_EQ(0u, thread_map.count(thread_id)); |
| 403 |
| 404 thread_map[thread_id] = expectation; |
| 405 } |
| 406 |
| 407 const std::vector<ProcessReaderThread>& threads = process_reader.Threads(); |
| 408 |
| 409 // Other tests that have run previously may have resulted in the creation of |
| 410 // threads that still exist, so pass true for |tolerate_extra_threads|. |
| 411 ExpectSeveralThreads(&thread_map, threads, true); |
| 412 |
| 413 // When testing in-process, verify that when this thread shows up in the |
| 414 // vector, it has the expected thread port, and that this thread port only |
| 415 // shows up once. |
| 416 base::mac::ScopedMachSendRight thread_self(mach_thread_self()); |
| 417 bool found_thread_self = false; |
| 418 for (const ProcessReaderThread& thread : threads) { |
| 419 if (thread.port == thread_self) { |
| 420 EXPECT_FALSE(found_thread_self); |
| 421 found_thread_self = true; |
| 422 EXPECT_EQ(self_thread_id, thread.id); |
| 423 } |
| 424 } |
| 425 EXPECT_TRUE(found_thread_self); |
| 426 } |
| 427 |
| 428 class ProcessReaderThreadedChild final : public MachMultiprocess { |
| 429 public: |
| 430 explicit ProcessReaderThreadedChild(size_t thread_count) |
| 431 : MachMultiprocess(), |
| 432 thread_count_(thread_count) { |
| 433 } |
| 434 |
| 435 ~ProcessReaderThreadedChild() {} |
| 436 |
| 437 protected: |
| 438 void Parent() override { |
| 439 ProcessReader process_reader; |
| 440 ASSERT_TRUE(process_reader.Initialize(ChildTask())); |
| 441 |
| 442 int read_fd = ReadPipeFD(); |
| 443 |
| 444 // Build a map of all expected threads, keyed by each thread’s ID, and with |
| 445 // addresses that should lie somewhere within each thread’s stack as values. |
| 446 // These IDs and addresses all come from the child process via the pipe. |
| 447 ThreadMap thread_map; |
| 448 for (size_t thread_index = 0; |
| 449 thread_index < thread_count_ + 1; |
| 450 ++thread_index) { |
| 451 uint64_t thread_id; |
| 452 int rv = ReadFD(read_fd, &thread_id, sizeof(thread_id)); |
| 453 ASSERT_EQ(static_cast<ssize_t>(sizeof(thread_id)), rv) |
| 454 << ErrnoMessage("read"); |
| 455 |
| 456 TestThreadPool::ThreadExpectation expectation; |
| 457 rv = ReadFD(read_fd, |
| 458 &expectation.stack_address, |
| 459 sizeof(expectation.stack_address)); |
| 460 ASSERT_EQ(static_cast<ssize_t>(sizeof(expectation.stack_address)), rv) |
| 461 << ErrnoMessage("read"); |
| 462 |
| 463 rv = ReadFD(read_fd, |
| 464 &expectation.suspend_count, |
| 465 sizeof(expectation.suspend_count)); |
| 466 ASSERT_EQ(static_cast<ssize_t>(sizeof(expectation.suspend_count)), rv) |
| 467 << ErrnoMessage("read"); |
| 468 |
| 469 // There can’t be any duplicate thread IDs. |
| 470 EXPECT_EQ(0u, thread_map.count(thread_id)); |
| 471 |
| 472 thread_map[thread_id] = expectation; |
| 473 } |
| 474 |
| 475 const std::vector<ProcessReaderThread>& threads = process_reader.Threads(); |
| 476 |
| 477 // The child shouldn’t have any threads other than its main thread and the |
| 478 // ones it created in its pool, so pass false for |tolerate_extra_threads|. |
| 479 ExpectSeveralThreads(&thread_map, threads, false); |
| 480 |
| 481 // Tell the child that it’s OK to exit. The child needed to be kept alive |
| 482 // until the parent finished working with it. |
| 483 int write_fd = WritePipeFD(); |
| 484 char c = '\0'; |
| 485 int rv = WriteFD(write_fd, &c, 1); |
| 486 ASSERT_EQ(1, rv) << ErrnoMessage("write"); |
| 487 } |
| 488 |
| 489 void Child() override { |
| 490 TestThreadPool thread_pool; |
| 491 thread_pool.StartThreads(thread_count_); |
| 492 if (testing::Test::HasFatalFailure()) { |
| 493 return; |
| 494 } |
| 495 |
| 496 int write_fd = WritePipeFD(); |
| 497 |
| 498 // This thread isn’t part of the thread pool, but the parent will be able |
| 499 // to inspect it. Write an entry for it. |
| 500 uint64_t thread_id = PthreadToThreadID(pthread_self()); |
| 501 |
| 502 int rv = WriteFD(write_fd, &thread_id, sizeof(thread_id)); |
| 503 ASSERT_EQ(static_cast<ssize_t>(sizeof(thread_id)), rv) |
| 504 << ErrnoMessage("write"); |
| 505 |
| 506 TestThreadPool::ThreadExpectation expectation; |
| 507 expectation.stack_address = reinterpret_cast<mach_vm_address_t>(&thread_id); |
| 508 expectation.suspend_count = 0; |
| 509 |
| 510 rv = WriteFD(write_fd, |
| 511 &expectation.stack_address, |
| 512 sizeof(expectation.stack_address)); |
| 513 ASSERT_EQ(static_cast<ssize_t>(sizeof(expectation.stack_address)), rv) |
| 514 << ErrnoMessage("write"); |
| 515 |
| 516 rv = WriteFD(write_fd, |
| 517 &expectation.suspend_count, |
| 518 sizeof(expectation.suspend_count)); |
| 519 ASSERT_EQ(static_cast<ssize_t>(sizeof(expectation.suspend_count)), rv) |
| 520 << ErrnoMessage("write"); |
| 521 |
| 522 // Write an entry for everything in the thread pool. |
| 523 for (size_t thread_index = 0; |
| 524 thread_index < thread_count_; |
| 525 ++thread_index) { |
| 526 uint64_t thread_id = |
| 527 thread_pool.GetThreadInfo(thread_index, &expectation); |
| 528 |
| 529 rv = WriteFD(write_fd, &thread_id, sizeof(thread_id)); |
| 530 ASSERT_EQ(static_cast<ssize_t>(sizeof(thread_id)), rv) |
| 531 << ErrnoMessage("write"); |
| 532 |
| 533 rv = WriteFD(write_fd, |
| 534 &expectation.stack_address, |
| 535 sizeof(expectation.stack_address)); |
| 536 ASSERT_EQ(static_cast<ssize_t>(sizeof(expectation.stack_address)), rv) |
| 537 << ErrnoMessage("write"); |
| 538 |
| 539 rv = WriteFD(write_fd, |
| 540 &expectation.suspend_count, |
| 541 sizeof(expectation.suspend_count)); |
| 542 ASSERT_EQ(static_cast<ssize_t>(sizeof(expectation.suspend_count)), rv) |
| 543 << ErrnoMessage("write"); |
| 544 } |
| 545 |
| 546 // Wait for the parent to say that it’s OK to exit. |
| 547 int read_fd = ReadPipeFD(); |
| 548 char c; |
| 549 rv = ReadFD(read_fd, &c, 1); |
| 550 ASSERT_EQ(1, rv) << ErrnoMessage("read"); |
| 551 } |
| 552 |
| 553 private: |
| 554 size_t thread_count_; |
| 555 |
| 556 DISALLOW_COPY_AND_ASSIGN(ProcessReaderThreadedChild); |
| 557 }; |
| 558 |
| 559 TEST(ProcessReader, ChildOneThread) { |
| 560 // The main thread plus zero child threads equals one thread. |
| 561 const size_t kChildThreads = 0; |
| 562 ProcessReaderThreadedChild process_reader_threaded_child(kChildThreads); |
| 563 process_reader_threaded_child.Run(); |
| 564 } |
| 565 |
| 566 TEST(ProcessReader, ChildSeveralThreads) { |
| 567 const size_t kChildThreads = 64; |
| 568 ProcessReaderThreadedChild process_reader_threaded_child(kChildThreads); |
| 569 process_reader_threaded_child.Run(); |
| 570 } |
| 571 |
| 572 } // namespace |
OLD | NEW |