| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <fbl/algorithm.h> |
| #include <fuchsia/sysinfo/c/fidl.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/zx/channel.h> |
| #include <zircon/process.h> |
| #include <zircon/processargs.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/port.h> |
| #include <zircon/threads.h> |
| #include <test-utils/test-utils.h> |
| #include <unittest/unittest.h> |
| |
| static int thread_func(void* arg); |
| |
| // argv[0] |
| static char* program_path; |
| |
| // We have to poll a thread's state as there is no way to wait for it to |
| // transition states. Wait this amount of time. Generally the thread won't |
| // take very long so this is a compromise between polling too frequently and |
| // waiting too long. |
| constexpr zx_duration_t THREAD_BLOCKED_WAIT_DURATION = ZX_MSEC(1); |
| |
| static const char test_child_name[] = "test-child"; |
| |
| // The maximum number of handles we send with send_msg_with_handles. |
| constexpr uint32_t MAX_NUM_MSG_HANDLES = 2; |
| |
| // The number of handles used in the wait-many test. |
| constexpr uint32_t NUM_WAIT_MANY_HANDLES = MAX_NUM_MSG_HANDLES; |
| |
| const zx_port_packet_t port_test_packet = { |
| .key = 42u, |
| .type = ZX_PKT_TYPE_USER, |
| .status = -42, |
| { .user = { .u64 = { 1, 2, 3, 4 } } } |
| }; |
| |
| const zx_time_t interrupt_signaled_timestamp = 12345; |
| |
| enum MessageType : uint32_t { |
| MSG_DONE, |
| MSG_PASS, |
| MSG_FAIL, |
| MSG_PROCEED, |
| MSG_THREAD_HANDLE_REQUEST, |
| MSG_THREAD_HANDLE_RESPONSE, |
| MSG_SLEEP_TEST, |
| MSG_FUTEX_TEST, |
| MSG_PORT_TEST, |
| MSG_CHANNEL_TEST, |
| MSG_WAIT_ONE_TEST, |
| MSG_WAIT_MANY_TEST, |
| MSG_INTERRUPT_TEST, |
| }; |
| |
| struct Message { |
| MessageType type; |
| uint32_t num_handles; |
| zx_handle_t handles[MAX_NUM_MSG_HANDLES]; |
| }; |
| |
| static zx_status_t get_root_resource(zx_handle_t* root_resource) { |
| int fd = open("/dev/misc/sysinfo", O_RDWR); |
| if (fd < 0) { |
| unittest_printf("ERROR: Cannot open sysinfo: %d/%s\n", |
| errno, strerror(errno)); |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| zx::channel channel; |
| zx_status_t status = fdio_get_service_handle(fd, channel.reset_and_get_address()); |
| if (status != ZX_OK) { |
| fprintf(stderr, "ERROR: Cannot obtain sysinfo channel: %s (%d)\n", |
| zx_status_get_string(status), status); |
| return status; |
| } |
| |
| zx_status_t fidl_status = fuchsia_sysinfo_DeviceGetRootResource(channel.get(), &status, |
| root_resource); |
| if (fidl_status != ZX_OK) { |
| fprintf(stderr, "ERROR: Cannot obtain root resource: %s (%d)\n", |
| zx_status_get_string(fidl_status), fidl_status); |
| return fidl_status; |
| } else if (status != ZX_OK) { |
| fprintf(stderr, "ERROR: Cannot obtain root resource: %s (%d)\n", |
| zx_status_get_string(status), status); |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| static void send_msg_with_handles(zx_handle_t channel, MessageType type, |
| zx_handle_t* optional_handles, uint32_t num_handles) { |
| uint32_t data = type; |
| unittest_printf("sending message %d on handle %u, with %u handles\n", |
| type, channel, num_handles); |
| tu_channel_write(channel, 0, &data, sizeof(data), optional_handles, num_handles); |
| } |
| |
| static void send_msg(zx_handle_t channel, MessageType type) { |
| send_msg_with_handles(channel, type, nullptr, 0); |
| } |
| |
| static bool recv_msg(zx_handle_t channel, Message* msg) { |
| unittest_printf("waiting for message on handle %u\n", channel); |
| |
| if (!tu_channel_wait_readable(channel)) { |
| unittest_printf("peer closed while trying to read message\n"); |
| return false; |
| } |
| |
| uint32_t data; |
| uint32_t num_bytes = sizeof(data); |
| tu_channel_read(channel, 0, &data, &num_bytes, &msg->handles[0], &msg->num_handles); |
| if (num_bytes != sizeof(data)) { |
| unittest_printf("ERROR: unexpected message size, %u != %zu\n", |
| num_bytes, sizeof(data)); |
| return false; |
| } |
| |
| msg->type = static_cast<MessageType>(data); |
| unittest_printf("received message %u\n", msg->type); |
| return true; |
| } |
| |
| static bool recv_specific_msg(zx_handle_t channel, MessageType expected_type) { |
| Message msg; |
| ASSERT_TRUE(recv_msg(channel, &msg)); |
| ASSERT_EQ(msg.type, expected_type); |
| return true; |
| } |
| |
| static void do_msg_thread_handle_request(zx_handle_t channel, const Message* msg) { |
| if (msg->num_handles != 0) { |
| unittest_printf("ERROR: wrong number handles\n"); |
| send_msg(channel, MSG_FAIL); |
| return; |
| } |
| auto self = zx_thread_self(); |
| send_msg_with_handles(channel, MSG_THREAD_HANDLE_RESPONSE, &self, 1); |
| } |
| |
| static void do_msg_sleep_test(zx_handle_t channel, const Message* msg) { |
| if (msg->num_handles != 0) { |
| unittest_printf("ERROR: wrong number handles\n"); |
| // There's no point in sending MSG_FAIL here as the test can never |
| // receive MSG_PASS. |
| return; |
| } |
| zx_nanosleep(ZX_TIME_INFINITE); |
| /* NOTREACHED */ |
| } |
| |
| static void do_msg_futex_test(zx_handle_t channel, const Message* msg) { |
| if (msg->num_handles != 0) { |
| unittest_printf("ERROR: wrong number handles\n"); |
| // There's no point in sending MSG_FAIL here as the test can never |
| // receive MSG_PASS. |
| return; |
| } |
| |
| int futex_value = 0; |
| zx_status_t __UNUSED status = zx_futex_wait(&futex_value, 0, |
| ZX_HANDLE_INVALID, |
| ZX_TIME_INFINITE); |
| /* NOTREACHED*/ |
| } |
| |
| static void do_msg_port_test(zx_handle_t channel, const Message* msg) { |
| if (msg->num_handles != 1) { |
| unittest_printf("ERROR: wrong number handles\n"); |
| send_msg(channel, MSG_FAIL); |
| return; |
| } |
| |
| auto port = msg->handles[0]; |
| zx_port_packet_t packet; |
| auto status = zx_port_wait(port, ZX_TIME_INFINITE, &packet); |
| tu_handle_close(port); |
| if (status != ZX_OK) { |
| unittest_printf("ERROR: port_wait failed: %d/%s\n", status, |
| zx_status_get_string(status)); |
| send_msg(channel, MSG_FAIL); |
| return; |
| } |
| |
| if (packet.key != port_test_packet.key || |
| packet.type != port_test_packet.type || |
| packet.status != port_test_packet.status || |
| memcmp(&packet.user, &port_test_packet.user, sizeof(zx_port_packet_t::user)) != 0) { |
| unittest_printf("ERROR: bad data in packet\n"); |
| send_msg(channel, MSG_FAIL); |
| return; |
| } |
| |
| send_msg(channel, MSG_PASS); |
| } |
| |
| static void do_msg_channel_test(zx_handle_t channel, const Message* msg) { |
| if (msg->num_handles != 1) { |
| unittest_printf_critical("ERROR: wrong number handles\n"); |
| send_msg(channel, MSG_FAIL); |
| return; |
| } |
| |
| auto test_channel = msg->handles[0]; |
| uint32_t write_data = 0xdeadbeef; |
| uint32_t read_data; |
| zx_channel_call_args_t args = { |
| .wr_bytes = &write_data, |
| .wr_handles = nullptr, |
| .rd_bytes = &read_data, |
| .rd_handles = nullptr, |
| .wr_num_bytes = sizeof(write_data), |
| .wr_num_handles = 0, |
| .rd_num_bytes = sizeof(read_data), |
| .rd_num_handles = 0, |
| }; |
| |
| uint32_t actual_num_bytes = 0; |
| uint32_t actual_num_handles = 0; |
| auto status = zx_channel_call(test_channel, 0, ZX_TIME_INFINITE, &args, |
| &actual_num_bytes, &actual_num_handles); |
| tu_handle_close(test_channel); |
| if (status == ZX_ERR_PEER_CLOSED) { |
| // ok |
| } else { |
| unittest_printf_critical("ERROR: channel_call didn't get PEER_CLOSED: %d/%s\n", |
| status, zx_status_get_string(status)); |
| send_msg(channel, MSG_FAIL); |
| return; |
| } |
| |
| send_msg(channel, MSG_PASS); |
| } |
| |
| static void do_msg_wait_one_test(zx_handle_t channel, const Message* msg) { |
| if (msg->num_handles != 1) { |
| unittest_printf("ERROR: wrong number handles\n"); |
| send_msg(channel, MSG_FAIL); |
| return; |
| } |
| |
| // The test waits for this to make sure it doesn't see us blocked waiting |
| // for a Message. This is sent for wait_one and wait_many so that we don't |
| // have to know which one is used to wait for messages. |
| send_msg(channel, MSG_PROCEED); |
| |
| zx_signals_t observed = 0u; |
| auto status = zx_object_wait_one(msg->handles[0], ZX_EVENTPAIR_PEER_CLOSED, |
| ZX_TIME_INFINITE, &observed); |
| tu_handle_close(msg->handles[0]); |
| if (status != ZX_OK) { |
| unittest_printf("ERROR: wait_one failed: %d/%s\n", status, |
| zx_status_get_string(status)); |
| send_msg(channel, MSG_FAIL); |
| return; |
| } |
| |
| if (!(observed & ZX_EVENTPAIR_PEER_CLOSED)) { |
| unittest_printf("ERROR: ZX_EVENTPAIR_PEER_CLOSED not observed\n"); |
| send_msg(channel, MSG_FAIL); |
| return; |
| } |
| |
| send_msg(channel, MSG_PASS); |
| } |
| |
| static void do_msg_wait_many_test(zx_handle_t channel, const Message* msg) { |
| if (msg->num_handles > NUM_WAIT_MANY_HANDLES) { |
| unittest_printf("ERROR: too many handles\n"); |
| send_msg(channel, MSG_FAIL); |
| return; |
| } |
| |
| // The test waits for this to make sure it doesn't see us blocked waiting |
| // for a Message. This is sent for wait_one and wait_many so that we don't |
| // have to know which one is used to wait for messages. |
| send_msg(channel, MSG_PROCEED); |
| |
| uint32_t num_handles = msg->num_handles; |
| zx_wait_item_t items[num_handles]; |
| for (uint32_t i = 0; i < num_handles; ++i) { |
| items[i].handle = msg->handles[i]; |
| items[i].waitfor = ZX_EVENTPAIR_PEER_CLOSED; |
| } |
| auto status = zx_object_wait_many(&items[0], num_handles, ZX_TIME_INFINITE); |
| for (uint32_t i = 0; i < num_handles; ++i) { |
| tu_handle_close(msg->handles[i]); |
| } |
| if (status != ZX_OK) { |
| unittest_printf("ERROR: wait_many failed: %d/%s\n", status, |
| zx_status_get_string(status)); |
| send_msg(channel, MSG_FAIL); |
| return; |
| } |
| |
| // At least one of the handles should have gotten PEER_CLOSED. |
| bool got_peer_closed = false; |
| for (uint32_t i = 0; i < num_handles; ++i) { |
| if (items[i].pending & ZX_EVENTPAIR_PEER_CLOSED) { |
| got_peer_closed = true; |
| break; |
| } |
| } |
| if (!got_peer_closed) { |
| unittest_printf("ERROR: ZX_EVENTPAIR_PEER_CLOSED not observed\n"); |
| send_msg(channel, MSG_FAIL); |
| return; |
| } |
| |
| send_msg(channel, MSG_PASS); |
| } |
| |
| static void do_msg_interrupt_test(zx_handle_t channel, const Message* msg) { |
| if (msg->num_handles != 1) { |
| unittest_printf("ERROR: wrong number handles\n"); |
| send_msg(channel, MSG_FAIL); |
| return; |
| } |
| |
| auto interrupt = msg->handles[0]; |
| zx_time_t timestamp; |
| auto status = zx_interrupt_wait(interrupt, ×tamp); |
| tu_handle_close(interrupt); |
| if (status != ZX_OK) { |
| unittest_printf("ERROR: interrupt_wait failed: %d/%s\n", status, |
| zx_status_get_string(status)); |
| send_msg(channel, MSG_FAIL); |
| return; |
| } |
| |
| if (timestamp != interrupt_signaled_timestamp) { |
| unittest_printf("ERROR: interrupt timestamp mismatch\n"); |
| send_msg(channel, MSG_FAIL); |
| return; |
| } |
| |
| send_msg(channel, MSG_PASS); |
| } |
| |
| static void msg_loop(zx_handle_t channel) { |
| bool my_done_tests = false; |
| |
| while (!my_done_tests) { |
| Message msg; |
| msg.num_handles = static_cast<uint32_t>(fbl::count_of(msg.handles)); |
| if (!recv_msg(channel, &msg)) { |
| unittest_printf("ERROR: while receiving msg\n"); |
| return; |
| } |
| |
| switch (msg.type) { |
| case MSG_DONE: |
| my_done_tests = true; |
| break; |
| case MSG_THREAD_HANDLE_REQUEST: |
| do_msg_thread_handle_request(channel, &msg); |
| break; |
| case MSG_SLEEP_TEST: |
| do_msg_sleep_test(channel, &msg); |
| break; |
| case MSG_FUTEX_TEST: |
| do_msg_futex_test(channel, &msg); |
| break; |
| case MSG_PORT_TEST: |
| do_msg_port_test(channel, &msg); |
| break; |
| case MSG_CHANNEL_TEST: |
| do_msg_channel_test(channel, &msg); |
| break; |
| case MSG_WAIT_ONE_TEST: |
| do_msg_wait_one_test(channel, &msg); |
| break; |
| case MSG_WAIT_MANY_TEST: |
| do_msg_wait_many_test(channel, &msg); |
| break; |
| case MSG_INTERRUPT_TEST: |
| do_msg_interrupt_test(channel, &msg); |
| break; |
| default: |
| unittest_printf("ERROR: unknown message received: %u\n", msg.type); |
| break; |
| } |
| } |
| } |
| |
| static int thread_func(void* arg) { |
| unittest_printf("test thread starting\n"); |
| zx_handle_t msg_channel = (zx_handle_t) (uintptr_t) arg; |
| msg_loop(msg_channel); |
| unittest_printf("test thread exiting\n"); |
| tu_handle_close(msg_channel); |
| return 0; |
| } |
| |
| static void __NO_RETURN test_child(void) { |
| unittest_printf("Test child starting.\n"); |
| zx_handle_t channel = zx_take_startup_handle(PA_USER0); |
| if (channel == ZX_HANDLE_INVALID) |
| tu_fatal("zx_take_startup_handle", ZX_ERR_BAD_HANDLE - 1000); |
| msg_loop(channel); |
| unittest_printf("Test child exiting.\n"); |
| exit(0); |
| } |
| |
| static launchpad_t* setup_test_child(zx_handle_t job, const char* arg, |
| zx_handle_t* out_channel) { |
| unittest_printf("Starting test child %s.\n", arg); |
| zx_handle_t our_channel, their_channel; |
| tu_channel_create(&our_channel, &their_channel); |
| const char* test_child_path = program_path; |
| const char verbosity_string[] = |
| { 'v', '=', static_cast<char>(utest_verbosity_level + '0'), '\0' }; |
| const char* const argv[] = { |
| test_child_path, |
| arg, |
| verbosity_string, |
| }; |
| int argc = fbl::count_of(argv); |
| zx_handle_t handles[1] = { their_channel }; |
| uint32_t handle_ids[1] = { PA_USER0 }; |
| *out_channel = our_channel; |
| launchpad_t* lp = tu_launch_fdio_init(job, test_child_name, argc, argv, |
| NULL, 1, handles, handle_ids); |
| unittest_printf("Test child setup.\n"); |
| return lp; |
| } |
| |
| static void start_test_child(zx_handle_t job, const char* arg, |
| zx_handle_t* out_child, zx_handle_t* out_channel) { |
| launchpad_t* lp = setup_test_child(job, arg, out_channel); |
| *out_child = tu_launch_fdio_fini(lp); |
| unittest_printf("Test child started.\n"); |
| } |
| |
| static bool get_child_thread(zx_handle_t channel, zx_handle_t* thread) { |
| send_msg(channel, MSG_THREAD_HANDLE_REQUEST); |
| Message msg; |
| ASSERT_TRUE(recv_msg(channel, &msg)); |
| EXPECT_EQ(msg.type, MSG_THREAD_HANDLE_RESPONSE); |
| EXPECT_EQ(msg.num_handles, 1u); |
| *thread = msg.handles[0]; |
| return true; |
| } |
| |
| // Wait for |thread| to enter blocked state |reason|. |
| // We wait forever and let Unittest's watchdog handle errors. |
| |
| static void wait_thread_blocked(zx_handle_t thread, uint32_t reason) { |
| while (true) { |
| auto state = tu_thread_get_state(thread); |
| if (state == reason) |
| break; |
| zx_nanosleep(zx_deadline_after(THREAD_BLOCKED_WAIT_DURATION)); |
| } |
| } |
| |
| // Terminate |process| by killing it and wait for it to exit. |
| |
| static void terminate_process(zx_handle_t process) { |
| tu_task_kill(process); |
| tu_process_wait_signaled(process); |
| } |
| |
| static bool sleeping_test() { |
| BEGIN_TEST; |
| |
| // Start a new process. |
| zx_handle_t child, channel; |
| start_test_child(zx_job_default(), test_child_name, &child, &channel); |
| zx_handle_t thread; |
| ASSERT_TRUE(get_child_thread(channel, &thread)); |
| |
| send_msg(channel, MSG_SLEEP_TEST); |
| |
| // There is no good way to do this test without having the child |
| // sleep forever and then kill it: There's no way to interrupt the sleep, |
| // and there's no good value for the amount of time to sleep. |
| wait_thread_blocked(thread, ZX_THREAD_STATE_BLOCKED_SLEEPING); |
| |
| terminate_process(child); |
| |
| END_TEST; |
| } |
| |
| static bool futex_test() { |
| BEGIN_TEST; |
| |
| // Start a new process. |
| zx_handle_t child, channel; |
| start_test_child(zx_job_default(), test_child_name, &child, &channel); |
| zx_handle_t thread; |
| ASSERT_TRUE(get_child_thread(channel, &thread)); |
| |
| send_msg_with_handles(channel, MSG_FUTEX_TEST, nullptr, 0); |
| |
| wait_thread_blocked(thread, ZX_THREAD_STATE_BLOCKED_FUTEX); |
| |
| terminate_process(child); |
| |
| END_TEST; |
| } |
| |
| static bool port_test() { |
| BEGIN_TEST; |
| |
| // Start a new process. |
| zx_handle_t child, channel; |
| start_test_child(zx_job_default(), test_child_name, &child, &channel); |
| zx_handle_t thread; |
| ASSERT_TRUE(get_child_thread(channel, &thread)); |
| |
| zx_handle_t port; |
| ASSERT_EQ(zx_port_create(0, &port), ZX_OK); |
| zx_handle_t port_dupe = tu_handle_duplicate(port); |
| |
| send_msg_with_handles(channel, MSG_PORT_TEST, &port_dupe, 1); |
| |
| wait_thread_blocked(thread, ZX_THREAD_STATE_BLOCKED_PORT); |
| |
| // Wake the child up. |
| EXPECT_EQ(zx_port_queue(port, &port_test_packet), ZX_OK); |
| |
| // The child sends a pass/fail message back as extra verification that |
| // things went correctly on that side. |
| ASSERT_TRUE(recv_specific_msg(channel, MSG_PASS)); |
| |
| tu_handle_close(port); |
| terminate_process(child); |
| |
| END_TEST; |
| } |
| |
| static bool channel_test() { |
| BEGIN_TEST; |
| |
| // Start a new process. |
| zx_handle_t child, channel; |
| start_test_child(zx_job_default(), test_child_name, &child, &channel); |
| zx_handle_t thread; |
| ASSERT_TRUE(get_child_thread(channel, &thread)); |
| |
| zx_handle_t our_channel, their_channel; |
| ASSERT_EQ(zx_channel_create(0, &our_channel, &their_channel), ZX_OK); |
| |
| send_msg_with_handles(channel, MSG_CHANNEL_TEST, &their_channel, 1); |
| |
| wait_thread_blocked(thread, ZX_THREAD_STATE_BLOCKED_CHANNEL); |
| |
| // Wake the child up. |
| tu_handle_close(our_channel); |
| |
| // The child sends a pass/fail message back as extra verification that |
| // things went correctly on that side. |
| ASSERT_TRUE(recv_specific_msg(channel, MSG_PASS)); |
| |
| terminate_process(child); |
| |
| END_TEST; |
| } |
| |
| static bool wait_one_test() { |
| BEGIN_TEST; |
| |
| // Start a new process. |
| zx_handle_t child, channel; |
| start_test_child(zx_job_default(), test_child_name, &child, &channel); |
| zx_handle_t thread; |
| ASSERT_TRUE(get_child_thread(channel, &thread)); |
| |
| zx_handle_t h[2]; |
| ASSERT_EQ(zx_eventpair_create(0, &h[0], &h[1]), ZX_OK); |
| |
| send_msg_with_handles(channel, MSG_WAIT_ONE_TEST, &h[1], 1); |
| |
| // Don't continue until we see MSG_PROCEED, that tells us the child has |
| // received the message and isn't in a wait_one/wait_many syscall. |
| ASSERT_TRUE(recv_specific_msg(channel, MSG_PROCEED)); |
| |
| wait_thread_blocked(thread, ZX_THREAD_STATE_BLOCKED_WAIT_ONE); |
| |
| // Wake the child up. |
| tu_handle_close(h[0]); |
| |
| // The child sends a pass/fail message back as extra verification that |
| // things went correctly on that side. |
| ASSERT_TRUE(recv_specific_msg(channel, MSG_PASS)); |
| |
| terminate_process(child); |
| |
| END_TEST; |
| } |
| |
| static bool wait_many_test() { |
| BEGIN_TEST; |
| |
| // Start a new process. |
| zx_handle_t child, channel; |
| start_test_child(zx_job_default(), test_child_name, &child, &channel); |
| zx_handle_t thread; |
| ASSERT_TRUE(get_child_thread(channel, &thread)); |
| |
| uint32_t num_handles = NUM_WAIT_MANY_HANDLES; |
| zx_handle_t h[2][num_handles]; |
| for (uint32_t i = 0; i < num_handles; ++i) { |
| ASSERT_EQ(zx_eventpair_create(0, &h[0][i], &h[1][i]), ZX_OK); |
| } |
| |
| send_msg_with_handles(channel, MSG_WAIT_MANY_TEST, &h[1][0], num_handles); |
| |
| // Don't continue until we see MSG_PROCEED, that tells us the child has |
| // received the message and isn't in a wait_one/wait_many syscall. |
| ASSERT_TRUE(recv_specific_msg(channel, MSG_PROCEED)); |
| |
| wait_thread_blocked(thread, ZX_THREAD_STATE_BLOCKED_WAIT_MANY); |
| |
| // Wake the child up. |
| for (uint32_t i = 0; i < num_handles; ++i) { |
| tu_handle_close(h[0][i]); |
| } |
| |
| // The child sends a pass/fail message back as extra verification that |
| // things went correctly on that side. |
| ASSERT_TRUE(recv_specific_msg(channel, MSG_PASS)); |
| |
| terminate_process(child); |
| |
| END_TEST; |
| } |
| |
| // Just like wait_many_test except the child doesn't wait on any objects, just |
| // the (infinite) timeout. |
| static bool wait_many_no_objects_test() { |
| BEGIN_TEST; |
| |
| // Start a new process. |
| zx_handle_t child, channel; |
| start_test_child(zx_job_default(), test_child_name, &child, &channel); |
| zx_handle_t thread; |
| ASSERT_TRUE(get_child_thread(channel, &thread)); |
| |
| send_msg(channel, MSG_WAIT_MANY_TEST); |
| |
| // Don't continue until we see MSG_PROCEED, that tells us the child has |
| // received the message and isn't in a wait_one/wait_many syscall. |
| ASSERT_TRUE(recv_specific_msg(channel, MSG_PROCEED)); |
| |
| wait_thread_blocked(thread, ZX_THREAD_STATE_BLOCKED_WAIT_MANY); |
| |
| // The child won't be sending a pass/fail message back because it's stuck in |
| // object_wait_many so just kill it. |
| terminate_process(child); |
| |
| END_TEST; |
| } |
| |
| static bool interrupt_test() { |
| BEGIN_TEST; |
| |
| // Start a new process. |
| zx_handle_t child, channel; |
| start_test_child(zx_job_default(), test_child_name, &child, &channel); |
| zx_handle_t thread; |
| ASSERT_TRUE(get_child_thread(channel, &thread)); |
| |
| zx_handle_t resource; |
| ASSERT_EQ(get_root_resource(&resource), ZX_OK); |
| |
| zx_handle_t interrupt; |
| ASSERT_EQ(zx_interrupt_create(resource, 0, ZX_INTERRUPT_VIRTUAL, &interrupt), |
| ZX_OK); |
| zx_handle_t interrupt_dupe = tu_handle_duplicate(interrupt); |
| |
| send_msg_with_handles(channel, MSG_INTERRUPT_TEST, &interrupt_dupe, 1); |
| |
| wait_thread_blocked(thread, ZX_THREAD_STATE_BLOCKED_INTERRUPT); |
| |
| // Wake the child up. |
| EXPECT_EQ(zx_interrupt_trigger(interrupt, 0, interrupt_signaled_timestamp), ZX_OK); |
| |
| // The child sends a pass/fail message back as extra verification that |
| // things went correctly on that side. |
| ASSERT_TRUE(recv_specific_msg(channel, MSG_PASS)); |
| |
| tu_handle_close(interrupt); |
| terminate_process(child); |
| |
| END_TEST; |
| } |
| |
| BEGIN_TEST_CASE(thread_state_tests) |
| // ZX_THREAD_STATE_BLOCKED_EXCEPTION is tested in utest/exception. |
| // There's a lot of support logic and there's no reason to duplicate it here. |
| RUN_TEST(sleeping_test); |
| RUN_TEST(futex_test); |
| RUN_TEST(port_test); |
| RUN_TEST(channel_test); |
| RUN_TEST(wait_one_test); |
| RUN_TEST(wait_many_test); |
| RUN_TEST(wait_many_no_objects_test); |
| RUN_TEST(interrupt_test); |
| END_TEST_CASE(thread_state_tests) |
| |
| static void scan_argv(int argc, char** argv) { |
| for (int i = 1; i < argc; ++i) { |
| if (strncmp(argv[i], "v=", 2) == 0) { |
| int verbosity = atoi(argv[i] + 2); |
| unittest_set_verbosity_level(verbosity); |
| } |
| } |
| } |
| |
| int main(int argc, char **argv) { |
| program_path = argv[0]; |
| scan_argv(argc, argv); |
| |
| if (argc >= 2) { |
| if (strcmp(argv[1], test_child_name) == 0) { |
| test_child(); |
| return 0; |
| } |
| } |
| |
| bool success = unittest_run_all_tests(argc, argv); |
| |
| return success ? 0 : -1; |
| } |