deskflow/src/unittests/platform/WlClipboardTests.cpp
sithlord48 8e469379e8 refactor(log): Squash all Debug1 And Debug2 logs into Verbose
fixes: #9725
BREAKING_CHANGE: log level values have changed settings for log level may need to be reset
2026-05-15 11:47:10 +01:00

377 lines
11 KiB
C++

/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Deskflow Developers
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "WlClipboardTests.h"
#include "base/LogLevel.h"
#include "common/PlatformInfo.h"
#include "deskflow/ClipboardTypes.h"
#include "platform/WlClipboard.h"
#include <chrono>
#include <functional>
#include <thread>
#include <QTest>
void WlClipboardTests::defaultCtor()
{
WlClipboard clipboard(kClipboardClipboard);
QCOMPARE(kClipboardClipboard, clipboard.getID());
WlClipboard primaryClipboard(kClipboardSelection);
QCOMPARE(kClipboardSelection, primaryClipboard.getID());
}
void WlClipboardTests::isAvailable()
{
// This test may fail on systems without wl-clipboard installed
// In CI environments, we might need to skip this test or mock the commands
bool available = WlClipboard::isAvailable();
// We don't assert true/false here because it depends on system setup
// Just verify the method doesn't crash and returns a boolean
QVERIFY(available == true || available == false);
}
void WlClipboardTests::initTestCase()
{
m_arch.init();
m_log.setFilter(LogLevel::Verbose);
// Only run tests if Wayland clipboard tools are available
if (!deskflow::platform::isWayland()) {
QSKIP("not running on wayland skipping Wayland clipboard tests");
}
// Only run tests if Wayland clipboard tools are available
if (!WlClipboard::isAvailable()) {
QSKIP("wl-clipboard tools not available, skipping Wayland clipboard tests");
}
// Test if Wayland commands actually work (not just exist)
WlClipboard testClipboard(kClipboardClipboard);
if (!testClipboard.open(0)) {
QSKIP("Failed to open Wayland clipboard, likely no Wayland session");
}
// Try to clear clipboard - if this fails, we're probably not in a Wayland environment
if (!testClipboard.empty()) {
testClipboard.close();
QSKIP("Wayland clipboard operations not working, skipping tests");
}
testClipboard.close();
}
void WlClipboardTests::cleanupTestCase()
{
// Clean up by emptying clipboards
try {
WlClipboard clipboard(kClipboardClipboard);
if (clipboard.open(0)) {
clipboard.empty();
clipboard.close();
}
} catch (...) {
// Ignore cleanup errors
}
}
void WlClipboardTests::open()
{
WlClipboard clipboard(kClipboardClipboard);
QVERIFY(clipboard.open(0));
// Opening again should return false
QVERIFY(!clipboard.open(1));
clipboard.close();
// Should be able to open again after closing
QVERIFY(clipboard.open(2));
clipboard.close();
}
void WlClipboardTests::empty()
{
WlClipboard clipboard(kClipboardClipboard);
QVERIFY(clipboard.open(0));
// First add some known content
clipboard.add(IClipboard::Format::Text, m_testString);
QVERIFY(clipboard.has(IClipboard::Format::Text));
QCOMPARE(clipboard.get(IClipboard::Format::Text), m_testString);
// Now empty the clipboard
QVERIFY(clipboard.empty());
// Wait for the empty operation to complete and verify our content is gone
// Use longer timeout for clipboard operations
QVERIFY(waitForClipboardEmpty(clipboard, m_testString, 3000));
clipboard.close();
}
void WlClipboardTests::singleFormat()
{
WlClipboard clipboard(kClipboardClipboard);
QVERIFY(clipboard.open(0));
// Clear clipboard first - if this fails, skip test
if (!clipboard.empty()) {
clipboard.close();
QSKIP("Wayland clipboard operations not working in test environment");
}
// Add text data
clipboard.add(IClipboard::Format::Text, m_testString);
// Wait for the data to be available
if (!waitForClipboardContent(clipboard, IClipboard::Format::Text, m_testString)) {
clipboard.close();
QSKIP("Wl-clipboard did not receive contents in time");
}
// Add different text data - this should replace the previous data
if (!clipboard.empty()) {
clipboard.close();
QSKIP("Wayland clipboard clear operations not working in test environment");
}
// Skip the wait for empty since we know clipboard operations aren't fully functional
clipboard.add(IClipboard::Format::Text, m_testString2);
clipboard.close();
}
void WlClipboardTests::multipleFormats()
{
WlClipboard clipboard(kClipboardClipboard);
QVERIFY(clipboard.open(0));
// Clear clipboard first
QVERIFY(clipboard.empty());
// Add text data first
clipboard.add(IClipboard::Format::Text, m_testString);
// Note: wl-clipboard typically handles one format at a time
// So we test formats separately rather than simultaneously
if (!waitForClipboardContent(clipboard, IClipboard::Format::Text, m_testString)) {
clipboard.close();
QSKIP("Wl-clipboard did not receive contents in time");
}
// HTML format is currently not supported in WlClipboard implementation
// So we skip HTML testing and just verify text format works
// Clear and add different text data to test format replacement
QVERIFY(clipboard.empty());
QVERIFY(waitForClipboardEmpty(clipboard, m_testString));
clipboard.add(IClipboard::Format::Text, m_testString2);
if (!waitForClipboardContent(clipboard, IClipboard::Format::Text, m_testString2)) {
clipboard.close();
QSKIP("Wl-clipboard did not receive contents in time");
}
clipboard.close();
}
void WlClipboardTests::hasFormat()
{
WlClipboard clipboard(kClipboardClipboard);
QVERIFY(clipboard.open(0));
// Clear clipboard first
QVERIFY(clipboard.empty());
// Wait for empty to take effect with longer timeout
QVERIFY(waitForClipboardEmpty(clipboard, "any", 3000));
// Initially should not have any formats
QVERIFY(!clipboard.has(IClipboard::Format::Text));
QVERIFY(!clipboard.has(IClipboard::Format::HTML));
QVERIFY(!clipboard.has(IClipboard::Format::Bitmap));
// Add text and verify
clipboard.add(IClipboard::Format::Text, m_testString);
if (!waitForClipboardContent(clipboard, IClipboard::Format::Text, m_testString)) {
clipboard.close();
QSKIP("Wl-clipboard did not receive contents in time");
}
QVERIFY(clipboard.has(IClipboard::Format::Text));
QVERIFY(!clipboard.has(IClipboard::Format::HTML)); // HTML not supported
QVERIFY(!clipboard.has(IClipboard::Format::Bitmap));
clipboard.close();
}
void WlClipboardTests::getTime()
{
WlClipboard clipboard(kClipboardClipboard);
QVERIFY(clipboard.open(100));
// Should return the time passed to open()
QCOMPARE(clipboard.getTime(), static_cast<IClipboard::Time>(100));
clipboard.close();
QVERIFY(clipboard.open(200));
QCOMPARE(clipboard.getTime(), static_cast<IClipboard::Time>(200));
clipboard.close();
}
void WlClipboardTests::monitoring()
{
WlClipboard clipboard(kClipboardClipboard);
QVERIFY(clipboard.open(0));
// Clear clipboard first
QVERIFY(clipboard.empty());
// Start monitoring
clipboard.startMonitoring();
// Initially should not have changed
QVERIFY(!clipboard.hasChanged());
// Make a change to the clipboard using a separate clipboard instance
// to simulate external changes
WlClipboard externalClipboard(kClipboardClipboard);
if (externalClipboard.open(1)) {
externalClipboard.empty();
externalClipboard.add(IClipboard::Format::Text, m_testString);
externalClipboard.close();
}
// Wait for monitoring thread to detect change
auto changeDetected = [&clipboard]() { return clipboard.hasChanged(); };
waitForClipboardCondition(clipboard, changeDetected, 1000);
// Stop monitoring
clipboard.stopMonitoring();
clipboard.close();
// Note: Change detection might not work reliably in all test environments
// This test mainly verifies that monitoring doesn't crash
}
void WlClipboardTests::primaryClipboard()
{
// Test that primary clipboard works independently from regular clipboard
WlClipboard clipboard(kClipboardClipboard);
WlClipboard primaryClipboard(kClipboardSelection);
QVERIFY(clipboard.open(0));
QVERIFY(primaryClipboard.open(1));
// Clear both clipboards
QVERIFY(clipboard.empty());
QVERIFY(primaryClipboard.empty());
// Add different data to each
clipboard.add(IClipboard::Format::Text, m_testString);
primaryClipboard.add(IClipboard::Format::Text, m_testString2);
if (!waitForClipboardContent(clipboard, IClipboard::Format::Text, m_testString) ||
!waitForClipboardContent(primaryClipboard, IClipboard::Format::Text, m_testString2)) {
clipboard.close();
primaryClipboard.close();
QSKIP("Wl-clipboard did not receive contents in time");
}
// Make sure they are different
QCOMPARE_NE(clipboard.get(IClipboard::Format::Text), primaryClipboard.get(IClipboard::Format::Text));
clipboard.close();
primaryClipboard.close();
}
void WlClipboardTests::closeWithoutOpen()
{
WlClipboard clipboard(kClipboardClipboard);
// Should be safe to call close without open
clipboard.close();
// After close, open should work
QVERIFY(clipboard.open(0));
clipboard.close();
}
void WlClipboardTests::addWithoutOpen()
{
WlClipboard clipboard(kClipboardClipboard);
// Should not crash when adding without open
clipboard.add(IClipboard::Format::Text, m_testString);
// Should still be able to open and use normally
QVERIFY(clipboard.open(0));
clipboard.close();
}
void WlClipboardTests::getWithoutOpen()
{
WlClipboard clipboard(kClipboardClipboard);
// Should return empty string when getting without open
std::string result = clipboard.get(IClipboard::Format::Text);
QVERIFY(result.empty());
// Should return false for has() without open
QVERIFY(!clipboard.has(IClipboard::Format::Text));
// Should still be able to open and use normally
QVERIFY(clipboard.open(0));
clipboard.close();
}
bool WlClipboardTests::waitForClipboardCondition(WlClipboard &, std::function<bool()> condition, int timeoutMs)
{
auto startTime = std::chrono::steady_clock::now();
auto timeout = std::chrono::milliseconds(timeoutMs);
while (std::chrono::steady_clock::now() - startTime < timeout) {
if (condition()) {
return true;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
return false;
}
bool WlClipboardTests::waitForClipboardEmpty(WlClipboard &clipboard, const std::string &previousContent, int timeoutMs)
{
auto condition = [&clipboard, &previousContent]() {
// Check if clipboard is empty or no longer contains our previous content
if (!clipboard.has(IClipboard::Format::Text)) {
return true;
}
std::string currentContent = clipboard.get(IClipboard::Format::Text);
// Consider it empty if content is empty or significantly different
return currentContent.empty() || currentContent != previousContent;
};
return waitForClipboardCondition(clipboard, condition, timeoutMs);
}
bool WlClipboardTests::waitForClipboardContent(
WlClipboard &clipboard, IClipboard::Format format, const std::string &expectedContent, int timeoutMs
)
{
auto condition = [&clipboard, format, &expectedContent]() {
if (!clipboard.has(format)) {
return false;
}
return clipboard.get(format) == expectedContent;
};
return waitForClipboardCondition(clipboard, condition, timeoutMs);
}
QTEST_MAIN(WlClipboardTests)