Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -127,18 +127,22 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/include)
# Modules
add_subdirectory("modules")

if(MSVC)
add_compile_options(/utf-8)
endif()
add_subdirectory(src)

option(DOTEST "option for test" OFF)

# 单元测试 (参考 deepin-system-monitor 模式)
if(DOTEST)
# 激活 ENABLE_AUTO_UNIT_TEST: 让 app 跳过单例互斥 + singleapplication 在退出路径 flush gcov,
# 配合 tools/coverage/gcov_flush_shim.c 实现运行真实程序采集覆盖率。
# 必须在 add_subdirectory(src) 之前, 让源码中的测试隔离宏也生效。
add_compile_definitions(ENABLE_AUTO_UNIT_TEST)
endif()

if(MSVC)
add_compile_options(/utf-8)
endif()
add_subdirectory(src)

if(DOTEST)
add_subdirectory(tests)
endif()

11 changes: 11 additions & 0 deletions src/lib/common/manager/sessionworker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,18 @@ bool SessionWorker::onStateChanged(int state, std::string &msg)
case RPC_ERROR: {
// code = 110: timeout, unabled ping
WLOG << "error remote code: " << msg;
#ifdef ENABLE_AUTO_UNIT_TEST
int code = 0;
try {
code = std::stoi(msg);
} catch (...) {
ELOG << "invalid error code: " << msg;
emit onConnectChanged(state, addr);
return false;
}
#else
int code = std::stoi(msg);
#endif
if (asio::error::host_unreachable == code
|| asio::error::timed_out == code) {
ELOG << "ping failed or timeout: " << msg;
Expand Down
12 changes: 11 additions & 1 deletion src/lib/common/manager/transferworker.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
// SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

Expand Down Expand Up @@ -202,7 +202,17 @@ bool TransferWorker::tryStartReceive(QStringList names, QString &ip, int port, Q
std::string savePath = dirname.toStdString();
_file_client->setConfig(accessToken, savePath);

#ifdef ENABLE_AUTO_UNIT_TEST
std::vector<std::string> webs;
try {
webs = _file_client->parseWeb(accessToken);
} catch (...) {
ELOG << "invalid access token, JWT parse failed";
return false;
}
#else
std::vector<std::string> webs = _file_client->parseWeb(accessToken);
#endif
#ifdef QT_DEBUG
for (const auto& web : webs) {
DLOG << "Web: " << web;
Expand Down
2 changes: 2 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ add_subdirectory(slotipc)
add_subdirectory(logging)
add_subdirectory(dfmplugin)
add_subdirectory(daemon)
add_subdirectory(sslconf)
add_subdirectory(manager)
290 changes: 290 additions & 0 deletions tests/common/stub.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#ifndef __STUB_H__
#define __STUB_H__


#ifdef _WIN32
//windows
#include <windows.h>
#include <processthreadsapi.h>
#else
//linux
#include <unistd.h>
#include <sys/mman.h>
#endif
//c
#include <cstddef>
#include <cstring>
//c++
#include <map>
#include <memory>
#include <stdexcept>


#define ADDR(CLASS_NAME,MEMBER_NAME) (&CLASS_NAME::MEMBER_NAME)

/**********************************************************
replace function
**********************************************************/
#ifdef _WIN32
#define CACHEFLUSH(addr, size) FlushInstructionCache(GetCurrentProcess(), addr, size)
#else
#define CACHEFLUSH(addr, size) __builtin___clear_cache(addr, addr + size)
#endif

#if defined(__aarch64__) || defined(_M_ARM64)
#define CODESIZE 16U
#define CODESIZE_MIN 16U
#define CODESIZE_MAX CODESIZE
// ldr x9, +8
// br x9
// addr
#define REPLACE_FAR(t, fn, fn_stub)\
((uint32_t*)fn)[0] = 0x58000040 | 9;\
((uint32_t*)fn)[1] = 0xd61f0120 | (9 << 5);\
*(long long *)(fn + 8) = (long long )fn_stub;\
CACHEFLUSH((char *)fn, CODESIZE);
#define REPLACE_NEAR(t, fn, fn_stub) REPLACE_FAR(t, fn, fn_stub)
#elif defined(__arm__) || defined(_M_ARM)
#define CODESIZE 8U
#define CODESIZE_MIN 8U
#define CODESIZE_MAX CODESIZE
// ldr pc, [pc, #-4]
#define REPLACE_FAR(t, fn, fn_stub)\
((uint32_t*)fn)[0] = 0xe51ff004;\
((uint32_t*)fn)[1] = (uint32_t)fn_stub;\
CACHEFLUSH((char *)fn, CODESIZE);
#define REPLACE_NEAR(t, fn, fn_stub) REPLACE_FAR(t, fn, fn_stub)
#elif defined(__thumb__) || defined(_M_THUMB)
#error "Thumb is not supported"
#else //__i386__ _x86_64__
#define CODESIZE 13U
#define CODESIZE_MIN 5U
#define CODESIZE_MAX CODESIZE
//13 byte(jmp m16:64)
//movabs $0x102030405060708,%r11
//jmpq *%r11
#define REPLACE_FAR(t, fn, fn_stub)\
*fn = 0x49;\
*(fn + 1) = 0xbb;\
*(long long *)(fn + 2) = (long long)fn_stub;\
*(fn + 10) = 0x41;\
*(fn + 11) = 0xff;\
*(fn + 12) = 0xe3;\
//CACHEFLUSH((char *)fn, CODESIZE);

//5 byte(jmp rel32)
#define REPLACE_NEAR(t, fn, fn_stub)\
*fn = 0xE9;\
*(int *)(fn + 1) = (int)(fn_stub - fn - CODESIZE_MIN);\
//CACHEFLUSH((char *)fn, CODESIZE);
#endif

struct func_stub
{
char *fn;
unsigned char code_buf[CODESIZE];
bool far_jmp;
};

class Stub
{
public:
Stub()
{
#ifdef _WIN32
SYSTEM_INFO sys_info;
GetSystemInfo(&sys_info);
m_pagesize = sys_info.dwPageSize;
#else
m_pagesize = sysconf(_SC_PAGE_SIZE);
#endif

if (m_pagesize < 0)
{
m_pagesize = 4096;
}
}
~Stub()
{
struct func_stub *pstub;
for(auto iter=m_result.begin(); iter != m_result.end(); iter++)
{
pstub = iter->second;
#ifdef _WIN32
DWORD lpflOldProtect;
if(0 != VirtualProtect(pageof(pstub->fn), m_pagesize * 2, PAGE_EXECUTE_READWRITE, &lpflOldProtect))
#else
if (0 == mprotect(pageof(pstub->fn), m_pagesize * 2, PROT_READ | PROT_WRITE))
#endif
{

if(pstub->far_jmp)
{
std::memcpy(pstub->fn, pstub->code_buf, CODESIZE_MAX);
}
else
{
std::memcpy(pstub->fn, pstub->code_buf, CODESIZE_MIN);
}

#ifdef _WIN32
VirtualProtect(pageof(pstub->fn), m_pagesize * 2, PAGE_EXECUTE_READ, &lpflOldProtect);
#else
mprotect(pageof(pstub->fn), m_pagesize * 2, PROT_READ | PROT_EXEC);
#endif
}

iter->second = nullptr;
delete pstub;
}

return;
}
template<typename T,typename S>
void set(T addr, S addr_stub)
{
char * fn;
char * fn_stub;
fn = addrof(addr);
fn_stub = addrof(addr_stub);
auto pstub = std::make_unique<func_stub>();
//start
pstub->fn = fn;

if(distanceof(fn, fn_stub))
{
pstub->far_jmp = true;
std::memcpy(pstub->code_buf, fn, CODESIZE_MAX);
}
else
{
pstub->far_jmp = false;
std::memcpy(pstub->code_buf, fn, CODESIZE_MIN);
}

#ifdef _WIN32
DWORD lpflOldProtect;
if(0 == VirtualProtect(pageof(pstub->fn), m_pagesize * 2, PAGE_EXECUTE_READWRITE, &lpflOldProtect))
#else
if (-1 == mprotect(pageof(pstub->fn), m_pagesize * 2, PROT_READ | PROT_WRITE))
#endif
{
throw std::runtime_error("stub set memory protect to w+r+x failed");
}

if(pstub->far_jmp)
{
REPLACE_FAR(this, fn, fn_stub);
}
else
{
REPLACE_NEAR(this, fn, fn_stub);
}


#ifdef _WIN32
if(0 == VirtualProtect(pageof(pstub->fn), m_pagesize * 2, PAGE_EXECUTE_READ, &lpflOldProtect))
#else
if (-1 == mprotect(pageof(pstub->fn), m_pagesize * 2, PROT_READ | PROT_EXEC))
#endif
{
throw std::runtime_error("stub set memory protect to r+x failed");
}
m_result.insert(std::pair<char*,func_stub*>(fn, pstub.release()));
return;
}

template<typename T>
void reset(T addr)
{
char * fn;
fn = addrof(addr);

std::map<char*,func_stub*>::iterator iter = m_result.find(fn);

if (iter == m_result.end())
{
return;
}
struct func_stub *pstub;
pstub = iter->second;

#ifdef _WIN32
DWORD lpflOldProtect;
if(0 == VirtualProtect(pageof(pstub->fn), m_pagesize * 2, PAGE_EXECUTE_READWRITE, &lpflOldProtect))
#else
if (-1 == mprotect(pageof(pstub->fn), m_pagesize * 2, PROT_READ | PROT_WRITE))
#endif
{
throw std::runtime_error("stub reset memory protect to w+r+x failed");
}

if(pstub->far_jmp)
{
std::memcpy(pstub->fn, pstub->code_buf, CODESIZE_MAX);
}
else
{
std::memcpy(pstub->fn, pstub->code_buf, CODESIZE_MIN);
}


#ifdef _WIN32
if(0 == VirtualProtect(pageof(pstub->fn), m_pagesize * 2, PAGE_EXECUTE_READ, &lpflOldProtect))
#else
if (-1 == mprotect(pageof(pstub->fn), m_pagesize * 2, PROT_READ | PROT_EXEC))
#endif
{
throw std::runtime_error("stub reset memory protect to r+x failed");
}
m_result.erase(iter);
delete pstub;

return;
}
private:
char *pageof(char* addr)
{
#ifdef _WIN32
return (char *)((unsigned long long)addr & ~(m_pagesize - 1));
#else
return (char *)((unsigned long)addr & ~(m_pagesize - 1));
#endif
}

template<typename T>
char* addrof(T addr)
{
char* ptr;
std::memcpy(&ptr, &addr, sizeof(ptr));
return ptr;
}

bool distanceof(char* addr, char* addr_stub)
{
std::ptrdiff_t diff = addr_stub >= addr ? addr_stub - addr : addr - addr_stub;
if((sizeof(addr) > 4) && (diff > 0x7FFFFFFF || diff < -0x7FFFFFFF - 1))
{
return true;
}
return false;
}

private:
#ifdef _WIN32
//LLP64
long long m_pagesize;
#else
//LP64
long m_pagesize;
#endif
std::map<char*, func_stub*> m_result;

};


#endif
25 changes: 25 additions & 0 deletions tests/manager/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# lib/common/manager 单元测试
# 链接 sessionmanager 静态库 (内含 SessionManager/SessionWorker/TransferWorker/FileSizeCounter)
find_package(GTest REQUIRED)
find_package(Qt6 REQUIRED COMPONENTS Core Network Widgets Test)

set(CMAKE_AUTOMOC ON)

file(GLOB TEST_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*_test.cpp")
if(TEST_SOURCES)
add_executable(manager_tests ${TEST_SOURCES} main.cpp)
target_compile_options(manager_tests PRIVATE -fno-access-control -Wno-shadow)
target_link_libraries(manager_tests PRIVATE
GTest::GTest
Qt6::Core Qt6::Network Qt6::Widgets Qt6::Test
sessionmanager session httpweb sslconf logging)
target_include_directories(manager_tests PRIVATE
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/src/lib/common
${CMAKE_SOURCE_DIR}/src/lib/common/manager
${CMAKE_SOURCE_DIR}/src/lib/common/session
${CMAKE_SOURCE_DIR}/src/lib/common/httpweb
${CMAKE_SOURCE_DIR}/tests/common
${CMAKE_SOURCE_DIR}/3rdparty/include)
add_test(NAME manager_tests COMMAND manager_tests)
endif()
Loading
Loading