| // Copyright 2019 The Chromium OS 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 <gio/gio.h> |
| #include <glib.h> |
| |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include <base/logging.h> |
| #include <base/message_loop/message_loop.h> |
| #include <base/run_loop.h> |
| #include <gtest/gtest.h> |
| |
| #include "glib-bridge/glib_bridge.h" |
| |
| namespace glib_bridge { |
| |
| namespace { |
| constexpr base::TimeDelta kTestTimeout = base::TimeDelta::FromSeconds(1); |
| } // namespace |
| |
| class GlibBridgeTest : public ::testing::Test { |
| public: |
| GlibBridgeTest() : glib_bridge_(new GlibBridge(&message_loop_)) {} |
| ~GlibBridgeTest() override {} |
| |
| void Finish() { run_loop_.Quit(); } |
| |
| protected: |
| void Start() { |
| // Set up timeout |
| message_loop_.task_runner()->PostDelayedTask( |
| FROM_HERE, run_loop_.QuitClosure(), kTestTimeout); |
| run_loop_.Run(); |
| } |
| |
| private: |
| base::MessageLoopForIO message_loop_; |
| base::RunLoop run_loop_; |
| std::unique_ptr<GlibBridge> glib_bridge_; |
| |
| DISALLOW_COPY_AND_ASSIGN(GlibBridgeTest); |
| }; |
| |
| TEST_F(GlibBridgeTest, ReadFileCallback) { |
| struct UserData { |
| GlibBridgeTest* test; |
| ssize_t bytes_read; |
| }; |
| UserData user_data{this, 0}; |
| |
| GFile* dev_file = g_file_new_for_path("/dev/zero"); |
| ASSERT_NE(dev_file, nullptr); |
| GFileInputStream* istream = g_file_read(dev_file, nullptr, nullptr); |
| ASSERT_NE(istream, nullptr); |
| |
| constexpr int kBufSize = 64; |
| char buf[kBufSize]; |
| memset(buf, 1, kBufSize); |
| auto read_results_ready = [](GObject* source, GAsyncResult* res, |
| gpointer user_data) { |
| UserData* ud = reinterpret_cast<UserData*>(user_data); |
| ud->bytes_read = g_input_stream_read_finish( |
| reinterpret_cast<GInputStream*>(source), res, nullptr); |
| ud->test->Finish(); |
| }; |
| g_input_stream_read_async( |
| reinterpret_cast<GInputStream*>(istream), buf, kBufSize, |
| G_PRIORITY_DEFAULT, nullptr, |
| static_cast<GAsyncReadyCallback>(read_results_ready), &user_data); |
| Start(); |
| |
| ASSERT_EQ(user_data.bytes_read, kBufSize); |
| char expected_buf[kBufSize]; |
| memset(expected_buf, 0, kBufSize); |
| ASSERT_EQ(memcmp(buf, expected_buf, kBufSize), 0); |
| |
| g_object_unref(istream); |
| g_object_unref(dev_file); |
| } |
| |
| TEST_F(GlibBridgeTest, WriteFileCallback) { |
| struct UserData { |
| GlibBridgeTest* test; |
| ssize_t bytes_written; |
| }; |
| UserData user_data{this, 0}; |
| |
| GFile* dev_file = g_file_new_for_path("/dev/null"); |
| ASSERT_NE(dev_file, nullptr); |
| GFileOutputStream* ostream = |
| g_file_append_to(dev_file, G_FILE_CREATE_NONE, nullptr, nullptr); |
| ASSERT_NE(ostream, nullptr); |
| |
| const std::string buf("foobar"); |
| auto write_done = [](GObject* source, GAsyncResult* res, gpointer user_data) { |
| UserData* ud = reinterpret_cast<UserData*>(user_data); |
| ud->bytes_written = g_output_stream_write_finish( |
| reinterpret_cast<GOutputStream*>(source), res, nullptr); |
| ud->test->Finish(); |
| }; |
| g_output_stream_write_async( |
| reinterpret_cast<GOutputStream*>(ostream), buf.data(), buf.size(), |
| G_PRIORITY_DEFAULT, nullptr, static_cast<GAsyncReadyCallback>(write_done), |
| &user_data); |
| Start(); |
| |
| ASSERT_EQ(user_data.bytes_written, buf.size()); |
| |
| g_object_unref(ostream); |
| g_object_unref(dev_file); |
| } |
| |
| TEST_F(GlibBridgeTest, IdleCallback) { |
| struct UserData { |
| GlibBridgeTest* test; |
| bool called; |
| }; |
| UserData user_data{this, false}; |
| |
| auto idle_callback = [](gpointer user_data) { |
| UserData* ud = reinterpret_cast<UserData*>(user_data); |
| ud->called = true; |
| ud->test->Finish(); |
| return G_SOURCE_REMOVE; |
| }; |
| |
| g_idle_add(static_cast<GSourceFunc>(idle_callback), &user_data); |
| Start(); |
| |
| ASSERT_TRUE(user_data.called); |
| } |
| |
| TEST_F(GlibBridgeTest, TimeoutOnceCallback) { |
| struct UserData { |
| GlibBridgeTest* test; |
| bool called; |
| }; |
| UserData user_data{this, false}; |
| |
| auto timer_callback = [](gpointer user_data) { |
| UserData* ud = reinterpret_cast<UserData*>(user_data); |
| ud->called = true; |
| ud->test->Finish(); |
| return G_SOURCE_REMOVE; |
| }; |
| |
| constexpr uint kTimeoutIntervalMs = 200; |
| g_timeout_add(kTimeoutIntervalMs, static_cast<GSourceFunc>(timer_callback), |
| &user_data); |
| Start(); |
| |
| ASSERT_TRUE(user_data.called); |
| } |
| |
| TEST_F(GlibBridgeTest, TimeoutMultiCallback) { |
| constexpr int kTarget = 5; |
| struct UserData { |
| GlibBridgeTest* test; |
| int counter; |
| }; |
| UserData user_data{this, 0}; |
| |
| auto timer_callback = [](gpointer user_data) -> gboolean { |
| UserData* ud = reinterpret_cast<UserData*>(user_data); |
| ud->counter++; |
| if (ud->counter == kTarget) { |
| ud->test->Finish(); |
| return G_SOURCE_REMOVE; |
| } |
| return G_SOURCE_CONTINUE; |
| }; |
| |
| constexpr uint kTimeoutIntervalMs = 100; |
| g_timeout_add(kTimeoutIntervalMs, static_cast<GSourceFunc>(timer_callback), |
| &user_data); |
| Start(); |
| |
| ASSERT_EQ(user_data.counter, kTarget); |
| } |
| |
| TEST_F(GlibBridgeTest, MultipleTimeouts) { |
| constexpr uint kNumFlags = 5; |
| struct UserData { |
| GlibBridgeTest* test; |
| int counter; |
| bool called[kNumFlags]; |
| }; |
| UserData user_data{this, 0, {false}}; |
| |
| auto timer_callback = [](gpointer user_data) { |
| UserData* ud = reinterpret_cast<UserData*>(user_data); |
| ud->called[ud->counter] = true; |
| ud->counter++; |
| if (ud->counter == kNumFlags) |
| ud->test->Finish(); |
| return G_SOURCE_REMOVE; |
| }; |
| |
| constexpr uint kTimeoutIntervalMs = 100; |
| for (int i = 0; i < kNumFlags; i++) { |
| g_timeout_add(kTimeoutIntervalMs * (i + 1), |
| static_cast<GSourceFunc>(timer_callback), &user_data); |
| } |
| Start(); |
| |
| for (int i = 0; i < kNumFlags; i++) |
| ASSERT_TRUE(user_data.called[i]); |
| } |
| |
| namespace multi_io_test { |
| |
| constexpr int kBufSize = 64; |
| |
| struct UserData; |
| struct IoJob { |
| IoJob(GFile* file, GFileInputStream* istream, UserData* user_data) |
| : file(file), istream(istream), buf(kBufSize, 1), user_data(user_data) {} |
| |
| GFile* file; |
| GFileInputStream* istream; |
| std::vector<char> buf; |
| bool complete = false; |
| UserData* user_data; |
| }; |
| |
| struct UserData { |
| GlibBridgeTest* test; |
| std::vector<IoJob> io_jobs; |
| }; |
| |
| gboolean AllCompleteCheck(gpointer user_data) { |
| UserData* ud = reinterpret_cast<UserData*>(user_data); |
| bool all_complete = true; |
| for (const IoJob& io_job : ud->io_jobs) |
| all_complete &= io_job.complete; |
| if (all_complete) |
| ud->test->Finish(); |
| return G_SOURCE_REMOVE; |
| } |
| |
| void ReadResultsReady(GObject* source, GAsyncResult* res, gpointer user_data) { |
| IoJob* job = reinterpret_cast<IoJob*>(user_data); |
| job->complete = |
| g_input_stream_read_finish(reinterpret_cast<GInputStream*>(source), res, |
| nullptr) >= 0; |
| g_idle_add(static_cast<GSourceFunc>(&AllCompleteCheck), job->user_data); |
| } |
| |
| TEST_F(GlibBridgeTest, MultipleReadAndIdleCallbacks) { |
| UserData user_data{this}; |
| constexpr int kNumFiles = 5; |
| for (int i = 0; i < kNumFiles; i++) { |
| GFile* dev_file = g_file_new_for_path("/dev/zero"); |
| ASSERT_NE(dev_file, nullptr); |
| GFileInputStream* istream = g_file_read(dev_file, nullptr, nullptr); |
| ASSERT_NE(istream, nullptr); |
| user_data.io_jobs.emplace_back(dev_file, istream, &user_data); |
| } |
| |
| for (IoJob& io_job : user_data.io_jobs) { |
| g_input_stream_read_async( |
| reinterpret_cast<GInputStream*>(io_job.istream), &io_job.buf[0], |
| io_job.buf.size(), G_PRIORITY_DEFAULT, nullptr, |
| static_cast<GAsyncReadyCallback>(&ReadResultsReady), &io_job); |
| } |
| Start(); |
| |
| bool all_complete = true; |
| for (const IoJob& io_job : user_data.io_jobs) { |
| all_complete &= io_job.complete; |
| std::vector<char> expected_buf(io_job.buf.size(), 0); |
| ASSERT_EQ(io_job.buf, expected_buf); |
| |
| g_object_unref(io_job.istream); |
| g_object_unref(io_job.file); |
| } |
| ASSERT_TRUE(all_complete); |
| } |
| |
| } // namespace multi_io_test |
| |
| } // namespace glib_bridge |