| // 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 <utility> |
| #include <vector> |
| |
| #include <base/logging.h> |
| #include <base/test/task_environment.h> |
| #include <base/run_loop.h> |
| #include <gtest/gtest.h> |
| |
| #include "glib-bridge/glib_bridge.h" |
| #include "glib-bridge/glib_scopers.h" |
| |
| namespace glib_bridge { |
| |
| namespace { |
| |
| constexpr base::TimeDelta kTestTimeout = base::TimeDelta::FromSeconds(1); |
| |
| // Use instead of g_idle_add, which implicitly uses the global default context. |
| void ScheduleIdleCallback(GSourceFunc func, gpointer data) { |
| GSource* idle_source = g_idle_source_new(); |
| g_source_set_callback(idle_source, func, data, nullptr); |
| g_source_set_priority(idle_source, G_PRIORITY_DEFAULT); |
| g_source_attach(idle_source, g_main_context_get_thread_default()); |
| g_source_unref(idle_source); |
| } |
| |
| // Use instead of g_timeout_add, which implicitly uses the global default |
| // context. |
| void ScheduleTimeoutCallback(int timeout_ms, GSourceFunc func, gpointer data) { |
| GSource* timeout_source = g_timeout_source_new(timeout_ms); |
| g_source_set_callback(timeout_source, func, data, nullptr); |
| g_source_set_priority(timeout_source, G_PRIORITY_DEFAULT); |
| g_source_attach(timeout_source, g_main_context_get_thread_default()); |
| g_source_unref(timeout_source); |
| } |
| |
| } // namespace |
| |
| class GlibBridgeTest : public ::testing::Test { |
| public: |
| GlibBridgeTest() : glib_bridge_(new GlibBridge()) {} |
| GlibBridgeTest(const GlibBridgeTest&) = delete; |
| GlibBridgeTest& operator=(const GlibBridgeTest&) = delete; |
| |
| ~GlibBridgeTest() override {} |
| |
| void Finish() { run_loop_.Quit(); } |
| |
| protected: |
| void Start() { |
| // Set up timeout |
| task_environment_.GetMainThreadTaskRunner()->PostDelayedTask( |
| FROM_HERE, run_loop_.QuitClosure(), kTestTimeout); |
| run_loop_.Run(); |
| } |
| |
| private: |
| base::test::TaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::ThreadingMode::MAIN_THREAD_ONLY, |
| base::test::TaskEnvironment::MainThreadType::IO}; |
| base::RunLoop run_loop_; |
| std::unique_ptr<GlibBridge> glib_bridge_; |
| }; |
| |
| TEST_F(GlibBridgeTest, ReadFileCallback) { |
| struct UserData { |
| GlibBridgeTest* test; |
| ssize_t bytes_read; |
| }; |
| UserData user_data{this, 0}; |
| |
| ScopedGObject<GFile> dev_file(g_file_new_for_path("/dev/zero")); |
| ASSERT_TRUE(dev_file); |
| ScopedGObject<GFileInputStream> istream( |
| g_file_read(dev_file.get(), nullptr, nullptr)); |
| ASSERT_TRUE(istream); |
| |
| 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(G_INPUT_STREAM(source), res, nullptr); |
| ud->test->Finish(); |
| }; |
| g_input_stream_read_async( |
| G_INPUT_STREAM(istream.get()), 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); |
| } |
| |
| TEST_F(GlibBridgeTest, WriteFileCallback) { |
| struct UserData { |
| GlibBridgeTest* test; |
| ssize_t bytes_written; |
| }; |
| UserData user_data{this, 0}; |
| |
| ScopedGObject<GFile> dev_file(g_file_new_for_path("/dev/null")); |
| ASSERT_TRUE(dev_file); |
| ScopedGObject<GFileOutputStream> ostream( |
| g_file_append_to(dev_file.get(), G_FILE_CREATE_NONE, nullptr, nullptr)); |
| ASSERT_TRUE(ostream); |
| |
| 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(G_OUTPUT_STREAM(ostream.get()), buf.data(), |
| buf.size(), G_PRIORITY_DEFAULT, nullptr, |
| static_cast<GAsyncReadyCallback>(write_done), |
| &user_data); |
| Start(); |
| |
| ASSERT_EQ(user_data.bytes_written, buf.size()); |
| } |
| |
| 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; |
| }; |
| |
| ScheduleIdleCallback(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; |
| ScheduleTimeoutCallback(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; |
| ScheduleTimeoutCallback(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++) { |
| ScheduleTimeoutCallback(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(ScopedGObject<GFile> file, |
| ScopedGObject<GFileInputStream> istream, |
| UserData* user_data) |
| : file(std::move(file)), |
| istream(std::move(istream)), |
| buf(kBufSize, 1), |
| user_data(user_data) {} |
| |
| ScopedGObject<GFile> file; |
| ScopedGObject<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(G_INPUT_STREAM(source), res, nullptr) >= 0; |
| ScheduleIdleCallback(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++) { |
| ScopedGObject<GFile> dev_file(g_file_new_for_path("/dev/zero")); |
| ASSERT_TRUE(dev_file); |
| ScopedGObject<GFileInputStream> istream( |
| g_file_read(dev_file.get(), nullptr, nullptr)); |
| ASSERT_TRUE(istream); |
| user_data.io_jobs.emplace_back(std::move(dev_file), std::move(istream), |
| &user_data); |
| } |
| |
| for (IoJob& io_job : user_data.io_jobs) { |
| g_input_stream_read_async( |
| G_INPUT_STREAM(io_job.istream.get()), &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); |
| } |
| ASSERT_TRUE(all_complete); |
| } |
| |
| } // namespace multi_io_test |
| |
| } // namespace glib_bridge |