[gjs/wip/3v1n0/toggle-queue-tests: 1/4] tests: Move internal API tests into a different test binary




commit fa24e37d0a813581a4d76038829c663407298395
Author: Marco Trevisan (TreviƱo) <mail 3v1n0 net>
Date:   Sun May 16 16:50:18 2021 +0200

    tests: Move internal API tests into a different test binary

 installed-tests/js/libgjstesttools/meson.build |   3 +
 meson.build                                    |  36 +-
 test/gjs-test-toggle-queue.cpp                 | 688 +++++++++++++++++++++++++
 test/gjs-tests-internal.cpp                    |  25 +
 test/gjs-tests.cpp                             |   3 -
 test/meson.build                               |  36 +-
 6 files changed, 766 insertions(+), 25 deletions(-)
---
diff --git a/installed-tests/js/libgjstesttools/meson.build b/installed-tests/js/libgjstesttools/meson.build
index 036d7076..2e57483a 100644
--- a/installed-tests/js/libgjstesttools/meson.build
+++ b/installed-tests/js/libgjstesttools/meson.build
@@ -17,3 +17,6 @@ gjstest_tools_gir = gnome.generate_gir(libgjstesttools,
     install: get_option('installed_tests'), install_dir_gir: false,
     install_dir_typelib: installed_tests_execdir)
 gjstest_tools_typelib = gjstest_tools_gir[1]
+libgjstesttools_dep = declare_dependency(
+    link_with: libgjstesttools,
+    include_directories: include_directories('.'))
diff --git a/meson.build b/meson.build
index 8d11ec84..2f1a03c7 100644
--- a/meson.build
+++ b/meson.build
@@ -496,12 +496,20 @@ if host_machine.system() == 'windows'
     libgjs_cpp_args += ['-DWIN32', '-DXP_WIN']
 endif
 
+base_build_dep = declare_dependency(
+    compile_args: libgjs_cpp_args,
+    dependencies: libgjs_dependencies)
+
 libgjs_jsapi = static_library(meson.project_name() + '-jsapi',
     libgjs_jsapi_sources, probes_header, probes_objfile,
-    cpp_args: libgjs_cpp_args,
-    dependencies: libgjs_dependencies,
+    dependencies: base_build_dep,
     install: false)
 
+libgjs_internal = static_library(meson.project_name() + '-internal',
+    libgjs_sources, probes_header, probes_objfile,
+    dependencies: base_build_dep,
+    link_with: libgjs_jsapi)
+
 link_args = []
 symbol_map = files('libgjs.map')
 symbol_list = files('libgjs.symbols')
@@ -513,12 +521,10 @@ link_args += cxx.get_supported_link_arguments([
 ])
 
 libgjs = shared_library(meson.project_name(),
-    libgjs_sources, libgjs_private_sources, module_resource_srcs,
-    probes_header, probes_objfile,
-    cpp_args: libgjs_cpp_args,
+    sources: [ libgjs_private_sources, module_resource_srcs ],
     link_args: link_args, link_depends: [symbol_map, symbol_list],
-    link_with: libgjs_jsapi,
-    dependencies: libgjs_dependencies,
+    link_whole: libgjs_internal,
+    dependencies: base_build_dep,
     version: '0.0.0', soversion: '0',
     gnu_symbol_visibility: 'hidden',
     install: true)
@@ -527,7 +533,7 @@ install_headers(gjs_public_headers, subdir: api_name / 'gjs')
 
 # Allow using libgjs as a subproject
 libgjs_dep = declare_dependency(link_with: [libgjs, libgjs_jsapi],
-    dependencies: libgjs_dependencies, include_directories: top_include)
+    dependencies: base_build_dep, include_directories: top_include)
 
 ### Build GjsPrivate introspection library #####################################
 
@@ -543,7 +549,6 @@ gjs_private_typelib = gjs_private_gir[1]
 gjs_console_srcs = ['gjs/console.cpp']
 
 gjs_console = executable('gjs-console', gjs_console_srcs,
-    cpp_args: libgjs_cpp_args,
     dependencies: libgjs_dep, install: true)
 
 meson.add_install_script('build/symlink-gjs.py', get_option('bindir'))
@@ -585,6 +590,7 @@ libgjs_test_tools_builddir = js_tests_builddir / 'libgjstesttools'
 tests_environment.set('TOP_BUILDDIR', meson.build_root())
 tests_environment.set('GJS_USE_UNINSTALLED_FILES', '1')
 tests_environment.set('GJS_PATH', '')
+tests_environment.set('GJS_DEBUG_OUTPUT', 'stderr')
 tests_environment.prepend('GI_TYPELIB_PATH', meson.current_build_dir(),
     js_tests_builddir, libgjs_test_tools_builddir)
 tests_environment.prepend('LD_LIBRARY_PATH', meson.current_build_dir(),
@@ -624,18 +630,18 @@ endif
 
 ### Tests and test setups ######################################################
 
-# Note: The test program in test/ needs to be ported
-#       to Windows before we can build it on Windows.
-if host_machine.system() != 'windows'
-    subdir('test')
-endif
-
 if not get_option('skip_gtk_tests')
     have_gtk4 = dependency('gtk4', required: false).found()
 endif
 
 subdir('installed-tests')
 
+# Note: The test program in test/ needs to be ported
+#       to Windows before we can build it on Windows.
+if host_machine.system() != 'windows'
+    subdir('test')
+endif
+
 valgrind_environment = environment()
 valgrind_environment.set('G_SLICE', 'always-malloc,debug-blocks')
 valgrind_environment.set('G_DEBUG',
diff --git a/test/gjs-test-toggle-queue.cpp b/test/gjs-test-toggle-queue.cpp
new file mode 100644
index 00000000..8b25b2c2
--- /dev/null
+++ b/test/gjs-test-toggle-queue.cpp
@@ -0,0 +1,688 @@
+/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 Canonical, Ltd.
+// SPDX-FileContributor: Marco Trevisan <marco trevisan canonical com>
+
+#include <config.h>
+
+#include <algorithm>  // for copy
+#include <atomic>
+#include <chrono>
+#include <deque>
+#include <memory>
+#include <thread>
+#include <tuple>    // for tie
+#include <utility>  // for pair
+
+#include <girepository.h>
+#include <glib-object.h>
+#include <glib.h>
+
+#include <js/GCAPI.h>  // for JS_GC
+#include <js/TypeDecls.h>
+
+#include "gi/object.h"
+#include "gi/toggle.h"
+#include "gjs/context.h"
+#include "gjs/jsapi-util.h"
+#include "installed-tests/js/libgjstesttools/gjs-test-tools.h"
+#include "test/gjs-test-utils.h"
+
+namespace Gjs {
+namespace Test {
+
+static GMutex s_gc_lock;
+static GCond s_gc_finished;
+static std::atomic_int s_gc_counter;
+static std::deque<std::pair<::ObjectInstance*, ::ToggleQueue::Direction>>
+    s_toggle_history;
+
+struct ObjectInstance : ::ObjectInstance {
+    using ::ObjectInstance::ensure_uses_toggle_ref;
+    using ::ObjectInstance::new_for_gobject;
+    using ::ObjectInstance::wrapper_is_rooted;
+};
+
+struct ToggleQueue {
+    static decltype(::ToggleQueue::get_default()) get_default() {
+        return ::ToggleQueue::get_default();
+    }
+    static void reset_queue() {
+        auto tq = get_default();
+        tq->m_shutdown = false;
+        g_clear_handle_id(&tq->m_idle_id, g_source_remove);
+        tq->q.clear();
+    }
+    static decltype(::ToggleQueue::q) queue() { return get_default()->q; }
+    static ::ToggleQueue::Handler handler() {
+        return get_default()->m_toggle_handler;
+    }
+};
+
+namespace TQ {
+
+static void on_gc(JSContext*, JSGCStatus status, JS::GCReason, void*) {
+    if (status != JSGC_END)
+        return;
+
+    g_mutex_lock(&s_gc_lock);
+    s_gc_counter.fetch_add(1);
+    g_cond_broadcast(&s_gc_finished);
+    g_mutex_unlock(&s_gc_lock);
+}
+
+static void setup(GjsUnitTestFixture* fx, const void*) {
+    g_irepository_prepend_search_path(g_getenv("TOP_BUILDDIR"));
+    gjs_test_tools_init();
+    gjs_unit_test_fixture_setup(fx, nullptr);
+    JS_SetGCCallback(fx->cx, on_gc, fx);
+
+    GjsAutoError error;
+    int code;
+
+    const char* gi_initializer = "imports.gi;";
+    g_assert_true(gjs_context_eval(fx->gjs_context, gi_initializer, -1,
+                                   "<gjs-test-toggle>", &code, error.out()));
+    g_assert_no_error(error);
+}
+
+static void wait_for_gc(GjsUnitTestFixture* fx) {
+    int count = s_gc_counter.load();
+
+    JS_GC(fx->cx);
+
+    g_mutex_lock(&s_gc_lock);
+    while (count == s_gc_counter.load()) {
+        g_cond_wait(&s_gc_finished, &s_gc_lock);
+    }
+    g_mutex_unlock(&s_gc_lock);
+}
+
+static void teardown(GjsUnitTestFixture* fx, const void*) {
+    for (auto pair : s_toggle_history)
+        ToggleQueue::get_default()->cancel(pair.first);
+
+    s_toggle_history.clear();
+    gjs_unit_test_fixture_teardown(fx, nullptr);
+
+    g_assert_true(ToggleQueue::queue().empty());
+    ToggleQueue::reset_queue();
+    gjs_test_tools_reset();
+}
+
+}  // namespace TQ
+
+static ::ObjectInstance* new_test_gobject(GjsUnitTestFixture* fx) {
+    GjsAutoUnref<GObject> gobject(
+        G_OBJECT(g_object_new(G_TYPE_OBJECT, nullptr)));
+    auto* object = ObjectInstance::new_for_gobject(fx->cx, gobject);
+    static_cast<ObjectInstance*>(object)->ensure_uses_toggle_ref(fx->cx);
+    return object;
+}
+
+static void wait_for(int interval) {
+    GjsAutoPointer<GMainLoop, GMainLoop, g_main_loop_unref> loop(
+        g_main_loop_new(nullptr, false));
+    g_timeout_add_full(
+        G_PRIORITY_LOW, interval,
+        [](void* data) {
+            g_main_loop_quit(static_cast<GMainLoop*>(data));
+            return G_SOURCE_REMOVE;
+        },
+        loop, nullptr);
+    g_main_loop_run(loop);
+}
+
+static void toggles_handler(::ObjectInstance* object,
+                            ::ToggleQueue::Direction direction) {
+    s_toggle_history.emplace_back(object, direction);
+}
+
+static void test_toggle_queue_unlock_empty(GjsUnitTestFixture*, const void*) {
+    assert_equal(ToggleQueue::get_default()->cancel(nullptr), false, false);
+}
+
+static void test_toggle_queue_unlock_same_thread(GjsUnitTestFixture*,
+                                                 const void*) {
+    auto tq = ToggleQueue::get_default();
+    assert_equal(tq->cancel(nullptr), false, false);
+    assert_equal(ToggleQueue::get_default()->cancel(nullptr), false, false);
+}
+
+static void test_toggle_blocks_other_thread(GjsUnitTestFixture*, const void*) {
+    struct LockedQueue {
+        decltype(ToggleQueue::get_default()) tq = ToggleQueue::get_default();
+    };
+
+    auto locked_queue = std::make_unique<LockedQueue>();
+    assert_equal(locked_queue->tq->cancel(nullptr), false, false);
+
+    std::atomic_bool other_thread_running(false);
+    std::atomic_bool accessed_from_other_thread(false);
+    auto th = std::thread([&accessed_from_other_thread, &other_thread_running] {
+        other_thread_running.store(true);
+        auto locked_queue = std::make_unique<LockedQueue>();
+        accessed_from_other_thread.store(true);
+        assert_equal(ToggleQueue::get_default()->cancel(nullptr), false, false);
+        other_thread_running = false;
+    });
+
+    while (!other_thread_running.load())
+        g_assert_false(accessed_from_other_thread.load());
+
+    std::this_thread::sleep_for(std::chrono::milliseconds(100));
+    g_assert_true(other_thread_running);
+    g_assert_false(accessed_from_other_thread);
+
+    auto other_queue = std::make_unique<LockedQueue>();
+    assert_equal(other_queue->tq->cancel(nullptr), false, false);
+
+    other_queue.reset();
+    std::this_thread::sleep_for(std::chrono::milliseconds(100));
+    g_assert_true(other_thread_running);
+    g_assert_false(accessed_from_other_thread);
+
+    // Ok, now other thread may get the lock...
+    locked_queue.reset();
+    while (!accessed_from_other_thread.load()) {
+    }
+    g_assert_true(accessed_from_other_thread);
+
+    // Can enter again from main thread!
+    th.join();
+    g_assert_false(other_thread_running);
+    assert_equal(ToggleQueue::get_default()->cancel(nullptr), false, false);
+}
+
+static void test_toggle_queue_empty(GjsUnitTestFixture*, const void*) {
+    auto tq = ToggleQueue::get_default();
+    tq->handle_all_toggles(toggles_handler);
+    g_assert_true(s_toggle_history.empty());
+}
+
+static void test_toggle_queue_empty_cancel(GjsUnitTestFixture*, const void*) {
+    auto tq = ToggleQueue::get_default();
+    bool toggle_down_queued, toggle_up_queued;
+    std::tie(toggle_down_queued, toggle_up_queued) = tq->cancel(nullptr);
+    g_assert_false(toggle_down_queued);
+    g_assert_false(toggle_up_queued);
+}
+
+static void test_toggle_queue_enqueue_one(GjsUnitTestFixture* fx, const void*) {
+    auto* instance = new_test_gobject(fx);
+    auto tq = ToggleQueue::get_default();
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+
+    tq->handle_all_toggles(toggles_handler);
+    assert_equal(s_toggle_history.size(), 1LU);
+    assert_equal(s_toggle_history.front(), instance,
+                 ::ToggleQueue::Direction::UP);
+}
+
+static void test_toggle_queue_enqueue_one_cancel(GjsUnitTestFixture* fx,
+                                                 const void*) {
+    auto* instance = new_test_gobject(fx);
+    auto tq = ToggleQueue::get_default();
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+
+    bool toggle_down_queued, toggle_up_queued;
+    std::tie(toggle_down_queued, toggle_up_queued) = tq->cancel(instance);
+    g_assert_false(toggle_down_queued);
+    g_assert_true(toggle_up_queued);
+
+    tq->handle_all_toggles(toggles_handler);
+    g_assert_true(s_toggle_history.empty());
+}
+
+static void test_toggle_queue_enqueue_many_equal(GjsUnitTestFixture* fx,
+                                                 const void*) {
+    auto* instance = new_test_gobject(fx);
+    auto tq = ToggleQueue::get_default();
+    tq->enqueue(instance, ::ToggleQueue::Direction::DOWN, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::DOWN, toggles_handler);
+
+    tq->handle_all_toggles(toggles_handler);
+    assert_equal(s_toggle_history.size(), 0LU);
+
+    bool toggle_down_queued, toggle_up_queued;
+    std::tie(toggle_down_queued, toggle_up_queued) = tq->cancel(instance);
+    g_assert_false(toggle_down_queued);
+    g_assert_false(toggle_up_queued);
+}
+
+static void test_toggle_queue_enqueue_many_equal_cancel(GjsUnitTestFixture* fx,
+                                                        const void*) {
+    auto* instance = new_test_gobject(fx);
+    auto tq = ToggleQueue::get_default();
+    tq->enqueue(instance, ::ToggleQueue::Direction::DOWN, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::DOWN, toggles_handler);
+
+    bool toggle_down_queued, toggle_up_queued;
+    std::tie(toggle_down_queued, toggle_up_queued) = tq->cancel(instance);
+    g_assert_false(toggle_down_queued);
+    g_assert_false(toggle_up_queued);
+}
+
+static void test_toggle_queue_enqueue_more_up(GjsUnitTestFixture* fx,
+                                              const void*) {
+    auto* instance = new_test_gobject(fx);
+    auto tq = ToggleQueue::get_default();
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::DOWN, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::DOWN, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+
+    tq->handle_all_toggles(toggles_handler);
+    assert_equal(s_toggle_history.size(), 2LU);
+    assert_equal(s_toggle_history.at(0), instance,
+                 ::ToggleQueue::Direction::UP);
+    assert_equal(s_toggle_history.at(1), instance,
+                 ::ToggleQueue::Direction::UP);
+
+    bool toggle_down_queued, toggle_up_queued;
+    std::tie(toggle_down_queued, toggle_up_queued) = tq->cancel(instance);
+    g_assert_false(toggle_down_queued);
+    g_assert_false(toggle_up_queued);
+}
+
+static void test_toggle_queue_enqueue_only_up(GjsUnitTestFixture* fx,
+                                              const void*) {
+    auto* instance = new_test_gobject(fx);
+    auto tq = ToggleQueue::get_default();
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+
+    tq->handle_all_toggles(toggles_handler);
+    assert_equal(s_toggle_history.size(), 4LU);
+    assert_equal(s_toggle_history.at(0), instance,
+                 ::ToggleQueue::Direction::UP);
+    assert_equal(s_toggle_history.at(1), instance,
+                 ::ToggleQueue::Direction::UP);
+    assert_equal(s_toggle_history.at(2), instance,
+                 ::ToggleQueue::Direction::UP);
+    assert_equal(s_toggle_history.at(3), instance,
+                 ::ToggleQueue::Direction::UP);
+
+    bool toggle_down_queued, toggle_up_queued;
+    std::tie(toggle_down_queued, toggle_up_queued) = tq->cancel(instance);
+    g_assert_false(toggle_down_queued);
+    g_assert_false(toggle_up_queued);
+}
+
+static void test_toggle_queue_handle_more_up(GjsUnitTestFixture* fx,
+                                             const void*) {
+    auto* instance = new_test_gobject(fx);
+    auto tq = ToggleQueue::get_default();
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::DOWN, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::DOWN, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+
+    wait_for(50);
+
+    assert_equal(s_toggle_history.size(), 2LU);
+    assert_equal(s_toggle_history.at(0), instance,
+                 ::ToggleQueue::Direction::UP);
+    assert_equal(s_toggle_history.at(1), instance,
+                 ::ToggleQueue::Direction::UP);
+
+    bool toggle_down_queued, toggle_up_queued;
+    std::tie(toggle_down_queued, toggle_up_queued) = tq->cancel(instance);
+    g_assert_false(toggle_down_queued);
+    g_assert_false(toggle_up_queued);
+}
+
+static void test_toggle_queue_handle_only_up(GjsUnitTestFixture* fx,
+                                             const void*) {
+    auto* instance = new_test_gobject(fx);
+    auto tq = ToggleQueue::get_default();
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+
+    wait_for(50);
+
+    assert_equal(s_toggle_history.size(), 4LU);
+    assert_equal(s_toggle_history.at(0), instance,
+                 ::ToggleQueue::Direction::UP);
+    assert_equal(s_toggle_history.at(1), instance,
+                 ::ToggleQueue::Direction::UP);
+    assert_equal(s_toggle_history.at(2), instance,
+                 ::ToggleQueue::Direction::UP);
+    assert_equal(s_toggle_history.at(3), instance,
+                 ::ToggleQueue::Direction::UP);
+
+    bool toggle_down_queued, toggle_up_queued;
+    std::tie(toggle_down_queued, toggle_up_queued) = tq->cancel(instance);
+    g_assert_false(toggle_down_queued);
+    g_assert_false(toggle_up_queued);
+}
+
+static void test_toggle_queue_enqueue_only_up_cancel(GjsUnitTestFixture* fx,
+                                                     const void*) {
+    auto* instance = new_test_gobject(fx);
+    auto tq = ToggleQueue::get_default();
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+    tq->enqueue(instance, ::ToggleQueue::Direction::UP, toggles_handler);
+
+    bool toggle_down_queued, toggle_up_queued;
+    std::tie(toggle_down_queued, toggle_up_queued) = tq->cancel(instance);
+    g_assert_false(toggle_down_queued);
+    g_assert_true(toggle_up_queued);
+
+    tq->handle_all_toggles(toggles_handler);
+    g_assert_true(s_toggle_history.empty());
+}
+
+static void test_toggle_queue_object_from_main_thread(GjsUnitTestFixture* fx,
+                                                      const void*) {
+    auto* instance = new_test_gobject(fx);
+    auto tq = ToggleQueue::get_default();
+
+    GjsAutoUnref<GObject> reffed(instance->ptr(), GjsAutoTakeOwnership());
+
+    bool toggle_down_queued, toggle_up_queued;
+    std::tie(toggle_down_queued, toggle_up_queued) = tq->cancel(instance);
+    g_assert_false(toggle_down_queued);
+    g_assert_false(toggle_up_queued);
+
+    tq->handle_all_toggles(toggles_handler);
+    g_assert_true(s_toggle_history.empty());
+}
+
+static void test_toggle_queue_object_from_main_thread_already_enqueued(
+    GjsUnitTestFixture* fx, const void*) {
+    auto* instance = new_test_gobject(fx);
+    GjsAutoUnref<GObject> reffed;
+    GjsAutoError error;
+
+    reffed = instance->ptr();
+    gjs_test_tools_ref_other_thread(reffed, error.out());
+    g_assert_no_error(error);
+
+    assert_equal(ToggleQueue::queue().size(), 1LU);
+    assert_equal(ToggleQueue::queue().at(0).object, instance);
+    assert_equal(ToggleQueue::queue().at(0).direction,
+                 ::ToggleQueue::Direction::UP);
+
+    auto tq = ToggleQueue::get_default();
+    bool toggle_down_queued, toggle_up_queued;
+    std::tie(toggle_down_queued, toggle_up_queued) = tq->cancel(instance);
+    g_assert_false(toggle_down_queued);
+    g_assert_true(toggle_up_queued);
+
+    tq->handle_all_toggles(toggles_handler);
+    g_assert_true(s_toggle_history.empty());
+}
+
+static void test_toggle_queue_object_from_main_thread_unref_already_enqueued(
+    GjsUnitTestFixture* fx, const void*) {
+    auto* instance = new_test_gobject(fx);
+    GjsAutoUnref<GObject> reffed;
+    GjsAutoError error;
+
+    reffed = instance->ptr();
+    gjs_test_tools_ref_other_thread(reffed, error.out());
+    g_assert_no_error(error);
+    assert_equal(ToggleQueue::queue().size(), 1LU);
+    assert_equal(ToggleQueue::queue().at(0).direction,
+                 ::ToggleQueue::Direction::UP);
+
+    reffed.reset();
+    g_assert_true(ToggleQueue::queue().empty());
+
+    auto tq = ToggleQueue::get_default();
+    bool toggle_down_queued, toggle_up_queued;
+    std::tie(toggle_down_queued, toggle_up_queued) = tq->cancel(instance);
+    g_assert_false(toggle_down_queued);
+    g_assert_false(toggle_up_queued);
+
+    tq->handle_all_toggles(toggles_handler);
+    g_assert_true(s_toggle_history.empty());
+}
+
+static void test_toggle_queue_object_from_other_thread_ref_unref(
+    GjsUnitTestFixture* fx, const void*) {
+    auto* instance = new_test_gobject(fx);
+
+    GjsAutoError error;
+    gjs_test_tools_ref_other_thread(instance->ptr(), error.out());
+    g_assert_no_error(error);
+    assert_equal(ToggleQueue::queue().size(), 1LU);
+    assert_equal(ToggleQueue::queue().at(0).direction,
+                 ::ToggleQueue::Direction::UP);
+
+    gjs_test_tools_unref_other_thread(instance->ptr(), error.out());
+    g_assert_no_error(error);
+    g_assert_true(ToggleQueue::queue().empty());
+
+    auto tq = ToggleQueue::get_default();
+    bool toggle_down_queued, toggle_up_queued;
+    std::tie(toggle_down_queued, toggle_up_queued) = tq->cancel(instance);
+    g_assert_false(toggle_down_queued);
+    g_assert_false(toggle_up_queued);
+
+    tq->handle_all_toggles(toggles_handler);
+    g_assert_true(s_toggle_history.empty());
+}
+
+static void test_toggle_queue_object_handle_up(GjsUnitTestFixture* fx,
+                                               const void*) {
+    auto* instance = new_test_gobject(fx);
+    auto* instance_test = reinterpret_cast<ObjectInstance*>(instance);
+
+    GjsAutoError error;
+    gjs_test_tools_ref_other_thread(instance->ptr(), error.out());
+    g_assert_no_error(error);
+    GjsAutoUnref<GObject> reffed(instance->ptr());
+    assert_equal(ToggleQueue::queue().size(), 1LU);
+    assert_equal(ToggleQueue::queue().at(0).direction,
+                 ::ToggleQueue::Direction::UP);
+
+    wait_for(50);
+    g_assert_true(instance_test->wrapper_is_rooted());
+    ToggleQueue::get_default()->handle_all_toggles(toggles_handler);
+    g_assert_true(s_toggle_history.empty());
+}
+
+static void test_toggle_queue_object_handle_up_down(GjsUnitTestFixture* fx,
+                                                    const void*) {
+    auto* instance = new_test_gobject(fx);
+    auto* instance_test = reinterpret_cast<ObjectInstance*>(instance);
+
+    GjsAutoError error;
+    gjs_test_tools_ref_other_thread(instance->ptr(), error.out());
+    g_assert_no_error(error);
+    assert_equal(ToggleQueue::queue().size(), 1LU);
+    assert_equal(ToggleQueue::queue().at(0).direction,
+                 ::ToggleQueue::Direction::UP);
+
+    gjs_test_tools_unref_other_thread(instance->ptr(), error.out());
+    g_assert_no_error(error);
+    g_assert_true(ToggleQueue::queue().empty());
+
+    wait_for(50);
+    g_assert_false(instance_test->wrapper_is_rooted());
+    ToggleQueue::get_default()->handle_all_toggles(toggles_handler);
+    g_assert_true(s_toggle_history.empty());
+}
+
+static void test_toggle_queue_object_handle_up_down_delayed(
+    GjsUnitTestFixture* fx, const void*) {
+    auto* instance = new_test_gobject(fx);
+    auto* instance_test = reinterpret_cast<ObjectInstance*>(instance);
+
+    GjsAutoError error;
+    gjs_test_tools_ref_other_thread(instance->ptr(), error.out());
+    g_assert_no_error(error);
+    assert_equal(ToggleQueue::queue().size(), 1LU);
+    assert_equal(ToggleQueue::queue().at(0).direction,
+                 ::ToggleQueue::Direction::UP);
+
+    wait_for(50);
+    g_assert_true(instance_test->wrapper_is_rooted());
+    ToggleQueue::get_default()->handle_all_toggles(toggles_handler);
+    g_assert_true(s_toggle_history.empty());
+
+    gjs_test_tools_unref_other_thread(instance->ptr(), error.out());
+    g_assert_no_error(error);
+    assert_equal(ToggleQueue::queue().size(), 1LU);
+    assert_equal(ToggleQueue::queue().at(0).direction,
+                 ::ToggleQueue::Direction::DOWN);
+
+    wait_for(50);
+    g_assert_false(instance_test->wrapper_is_rooted());
+    ToggleQueue::get_default()->handle_all_toggles(toggles_handler);
+    g_assert_true(s_toggle_history.empty());
+}
+
+static void test_toggle_queue_object_handle_up_down_on_gc(
+    GjsUnitTestFixture* fx, const void*) {
+    auto* instance = new_test_gobject(fx);
+
+    GjsAutoError error;
+    gjs_test_tools_ref_other_thread(instance->ptr(), error.out());
+    g_assert_no_error(error);
+    assert_equal(ToggleQueue::queue().size(), 1LU);
+    assert_equal(ToggleQueue::queue().at(0).direction,
+                 ::ToggleQueue::Direction::UP);
+
+    gjs_test_tools_unref_other_thread(instance->ptr(), error.out());
+    g_assert_no_error(error);
+    g_assert_true(ToggleQueue::queue().empty());
+
+    GWeakRef weak_ref;
+    g_weak_ref_init(&weak_ref, instance->ptr());
+
+    TQ::wait_for_gc(fx);
+    g_assert_null(g_weak_ref_get(&weak_ref));
+
+    ToggleQueue::get_default()->handle_all_toggles(toggles_handler);
+    g_assert_true(s_toggle_history.empty());
+}
+
+static void test_toggle_queue_object_handle_many_up(GjsUnitTestFixture* fx,
+                                                    const void*) {
+    auto* instance = new_test_gobject(fx);
+    auto* instance_test = reinterpret_cast<ObjectInstance*>(instance);
+
+    GjsAutoError error;
+    gjs_test_tools_ref_other_thread(instance->ptr(), error.out());
+    g_assert_no_error(error);
+    GjsAutoUnref<GObject> reffed(instance->ptr());
+    // Simulating the case where late threads are causing this...
+    ToggleQueue::get_default()->enqueue(instance, ::ToggleQueue::Direction::UP,
+                                        ToggleQueue().handler());
+
+    assert_equal(ToggleQueue::queue().size(), 2LU);
+    assert_equal(ToggleQueue::queue().at(0).direction,
+                 ::ToggleQueue::Direction::UP);
+    assert_equal(ToggleQueue::queue().at(1).direction,
+                 ::ToggleQueue::Direction::UP);
+
+    wait_for(50);
+    g_assert_true(instance_test->wrapper_is_rooted());
+    ToggleQueue::get_default()->handle_all_toggles(toggles_handler);
+    g_assert_true(s_toggle_history.empty());
+}
+
+static void test_toggle_queue_object_handle_many_up_and_down(
+    GjsUnitTestFixture* fx, const void*) {
+    auto* instance = new_test_gobject(fx);
+    auto* instance_test = reinterpret_cast<ObjectInstance*>(instance);
+
+    // This is something similar to what is happening on #297
+    GjsAutoError error;
+    gjs_test_tools_ref_other_thread(instance->ptr(), error.out());
+    g_assert_no_error(error);
+    ToggleQueue::get_default()->enqueue(instance, ::ToggleQueue::Direction::UP,
+                                        ToggleQueue().handler());
+    gjs_test_tools_unref_other_thread(instance->ptr(), error.out());
+    g_assert_no_error(error);
+    ToggleQueue::get_default()->enqueue(
+        instance, ::ToggleQueue::Direction::DOWN, ToggleQueue().handler());
+
+    g_assert_true(ToggleQueue::queue().empty());
+
+    wait_for(50);
+    g_assert_false(instance_test->wrapper_is_rooted());
+    g_assert_true(ToggleQueue::queue().empty());
+
+    GWeakRef weak_ref;
+    g_assert_true(G_IS_OBJECT(instance->ptr()));
+    g_weak_ref_init(&weak_ref, instance->ptr());
+
+    TQ::wait_for_gc(fx);
+    g_assert_null(g_weak_ref_get(&weak_ref));
+    g_assert_true(ToggleQueue::queue().empty());
+}
+
+void add_tests_for_toggle_queue() {
+#define ADD_TOGGLE_QUEUE_TEST(path, f)                                        \
+    g_test_add("/toggle-queue/" path, GjsUnitTestFixture, nullptr, TQ::setup, \
+               f, TQ::teardown);
+
+    ADD_TOGGLE_QUEUE_TEST("spin-lock/unlock-empty",
+                          test_toggle_queue_unlock_empty);
+    ADD_TOGGLE_QUEUE_TEST("spin-lock/unlock-same-thread",
+                          test_toggle_queue_unlock_same_thread);
+    ADD_TOGGLE_QUEUE_TEST("spin-lock/blocks-other-thread",
+                          test_toggle_blocks_other_thread);
+
+    ADD_TOGGLE_QUEUE_TEST("empty", test_toggle_queue_empty);
+    ADD_TOGGLE_QUEUE_TEST("empty_cancel", test_toggle_queue_empty_cancel);
+    ADD_TOGGLE_QUEUE_TEST("enqueue_one", test_toggle_queue_enqueue_one);
+    ADD_TOGGLE_QUEUE_TEST("enqueue_one_cancel",
+                          test_toggle_queue_enqueue_one_cancel);
+    ADD_TOGGLE_QUEUE_TEST("enqueue_many_equal",
+                          test_toggle_queue_enqueue_many_equal);
+    ADD_TOGGLE_QUEUE_TEST("enqueue_many_equal_cancel",
+                          test_toggle_queue_enqueue_many_equal_cancel);
+    ADD_TOGGLE_QUEUE_TEST("enqueue_more_up", test_toggle_queue_enqueue_more_up);
+    ADD_TOGGLE_QUEUE_TEST("enqueue_only_up", test_toggle_queue_enqueue_only_up);
+    ADD_TOGGLE_QUEUE_TEST("enqueue_only_up_cancel",
+                          test_toggle_queue_enqueue_only_up_cancel);
+    ADD_TOGGLE_QUEUE_TEST("handle_more_up", test_toggle_queue_handle_more_up);
+    ADD_TOGGLE_QUEUE_TEST("handle_only_up", test_toggle_queue_handle_only_up);
+
+    ADD_TOGGLE_QUEUE_TEST("object/not-enqueued_main_thread",
+                          test_toggle_queue_object_from_main_thread);
+    ADD_TOGGLE_QUEUE_TEST(
+        "object/already_enqueued_main_thread",
+        test_toggle_queue_object_from_main_thread_already_enqueued);
+    ADD_TOGGLE_QUEUE_TEST(
+        "object/already_enqueued_unref_main_thread",
+        test_toggle_queue_object_from_main_thread_unref_already_enqueued);
+    ADD_TOGGLE_QUEUE_TEST("object/ref_unref_other_thread",
+                          test_toggle_queue_object_from_other_thread_ref_unref);
+    ADD_TOGGLE_QUEUE_TEST("object/handle_up",
+                          test_toggle_queue_object_handle_up);
+    ADD_TOGGLE_QUEUE_TEST("object/handle_up_down",
+                          test_toggle_queue_object_handle_up_down);
+    ADD_TOGGLE_QUEUE_TEST("object/handle_up_down_delayed",
+                          test_toggle_queue_object_handle_up_down_delayed);
+    ADD_TOGGLE_QUEUE_TEST("object/handle_up_down_on_gc",
+                          test_toggle_queue_object_handle_up_down_on_gc);
+    ADD_TOGGLE_QUEUE_TEST("object/handle_many_up",
+                          test_toggle_queue_object_handle_many_up);
+    ADD_TOGGLE_QUEUE_TEST("object/handle_many_up_and_down",
+                          test_toggle_queue_object_handle_many_up_and_down);
+
+#undef ADD_TOGGLE_QUEUE_TEST
+}
+
+}  // namespace Test
+}  // namespace Gjs
diff --git a/test/gjs-tests-internal.cpp b/test/gjs-tests-internal.cpp
new file mode 100644
index 00000000..4f8f74ba
--- /dev/null
+++ b/test/gjs-tests-internal.cpp
@@ -0,0 +1,25 @@
+/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 Canonical, Ltd.
+// SPDX-FileContributor: Marco Trevisan <marco trevisan canonical com>
+
+#include <config.h>
+#include <glib.h>
+
+#include "test/gjs-test-utils.h"
+
+int main(int argc, char** argv) {
+    /* Avoid interference in the tests from stray environment variable */
+    g_unsetenv("GJS_ENABLE_PROFILER");
+    g_unsetenv("GJS_TRACE_FD");
+
+    g_test_init(&argc, &argv, nullptr);
+
+    gjs_test_add_tests_for_rooting();
+    gjs_test_add_tests_for_parse_call_args();
+    gjs_test_add_tests_for_jsapi_utils();
+
+    g_test_run();
+
+    return 0;
+}
diff --git a/test/gjs-tests.cpp b/test/gjs-tests.cpp
index 8eda4e27..9034d879 100644
--- a/test/gjs-tests.cpp
+++ b/test/gjs-tests.cpp
@@ -955,9 +955,6 @@ main(int    argc,
 #undef ADD_JSAPI_UTIL_TEST
 
     gjs_test_add_tests_for_coverage ();
-    gjs_test_add_tests_for_parse_call_args();
-    gjs_test_add_tests_for_rooting();
-    gjs_test_add_tests_for_jsapi_utils();
 
     g_test_run();
 
diff --git a/test/meson.build b/test/meson.build
index 83c63067..dd902877 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -7,21 +7,43 @@ mock_js_resources_files = gnome.compile_resources('mock-js-resources',
     'mock-js-resources.gresource.xml', c_name: 'mock_js_resources',
     source_dir: '..')
 
+libgjs_tests_common = static_library('libgjs-tests-common',
+    sources: [
+        'gjs-test-utils.cpp', 'gjs-test-utils.h',
+        'gjs-test-common.cpp', 'gjs-test-common.h',
+    ],
+    cpp_args: libgjs_cpp_args,
+    include_directories: top_include, dependencies: libgjs_dependencies,
+)
+
 gjs_tests_sources = [
     'gjs-tests.cpp',
-    'gjs-test-common.cpp', 'gjs-test-common.h',
-    'gjs-test-utils.cpp', 'gjs-test-utils.h',
-    'gjs-test-call-args.cpp',
     'gjs-test-coverage.cpp',
-    'gjs-test-rooting.cpp',
-    'gjs-test-jsapi-utils.cpp',
     'gjs-test-no-introspection-object.cpp', 'gjs-test-no-introspection-object.h',
 ]
 
 gjs_tests = executable('gjs-tests', gjs_tests_sources, mock_js_resources_files,
-    cpp_args: ['-DGJS_COMPILATION'] + directory_defines,
-    include_directories: top_include, dependencies: libgjs_dep)
+    include_directories: top_include, dependencies: libgjs_dep,
+    link_with: libgjs_tests_common)
 
 test('API tests', gjs_tests, args: ['--tap', '--keep-going', '--verbose'],
     depends: gjs_private_typelib, env: tests_environment, protocol: 'tap',
     suite: 'C', timeout: 60)
+
+gjs_tests_internal = executable('gjs-tests-internal',
+    sources: [
+        'gjs-tests-internal.cpp',
+        'gjs-test-call-args.cpp',
+        'gjs-test-rooting.cpp',
+        'gjs-test-jsapi-utils.cpp',
+        module_resource_srcs,
+    ],
+    include_directories: top_include,
+    cpp_args: libgjs_cpp_args,
+    dependencies: [libgjs_dependencies, libgjstesttools_dep],
+    link_with: [libgjs_tests_common, libgjs_internal])
+
+test('Internal API tests', gjs_tests_internal,
+    args: ['--tap', '--keep-going', '--verbose'],
+    env: tests_environment, protocol: 'tap',
+    suite: ['C', 'thread-safe'])


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]