blob: 4e8f3d710817c5e58d77dc08d8f16e2def5eac0c [file] [log] [blame]
// 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