Skip to content
Open
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
20 changes: 20 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,26 @@ jobs:
with:
extra_args: --from-ref ${{ github.event.pull_request.base.sha }} --to-ref ${{ github.event.pull_request.head.sha }}

ut-cpp:
needs: pre-commit
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y cmake ninja-build

- name: Configure C++ tests
run: cmake -B tests/ut/cpp/build -S tests/ut/cpp -G Ninja

- name: Build C++ tests
run: cmake --build tests/ut/cpp/build

- name: Run C++ tests
run: ctest --test-dir tests/ut/cpp/build --output-on-failure -L no_hardware

ut-py:
needs: pre-commit
runs-on: ubuntu-latest
Expand Down
120 changes: 120 additions & 0 deletions tests/ut/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
cmake_minimum_required(VERSION 3.15)
project(pto_runtime_tests CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Disable profiling to avoid device_time.h dependency in hot paths
add_compile_definitions(
PTO2_PROFILING=0
PTO2_ORCH_PROFILING=0
PTO2_SCHED_PROFILING=0
PTO2_TENSORMAP_PROFILING=0
PTO2_SPIN_VERBOSE_LOGGING=0
_GLIBCXX_USE_CXX11_ABI=0
)

# GoogleTest: prefer system installation, fallback to FetchContent
find_package(GTest QUIET)
if(NOT GTest_FOUND)
include(FetchContent)
FetchContent_Declare(googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
endif()

enable_testing()

# Source directories (use a2a3 as the reference arch for UT)
set(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../..)
set(TMR_RUNTIME_DIR ${PROJECT_ROOT}/src/a2a3/runtime/tensormap_and_ringbuffer/runtime)
set(TMR_ORCH_DIR ${PROJECT_ROOT}/src/a2a3/runtime/tensormap_and_ringbuffer/orchestration)
set(TMR_COMMON_DIR ${PROJECT_ROOT}/src/a2a3/runtime/tensormap_and_ringbuffer/common)
set(PLATFORM_INCLUDE_DIR ${PROJECT_ROOT}/src/a2a3/platform/include)
set(COMMON_TASK_DIR ${PROJECT_ROOT}/src/common/task_interface)

set(COMMON_INCLUDE_DIRS
${TMR_RUNTIME_DIR}
${TMR_ORCH_DIR}
${TMR_COMMON_DIR}
${PLATFORM_INCLUDE_DIR}
${COMMON_TASK_DIR}
)

# Determine the GTest link target name
if(TARGET GTest::gtest_main)
set(GTEST_TARGET GTest::gtest_main)
else()
set(GTEST_TARGET gtest_main)
endif()

# Stub sources
set(STUB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/stubs/test_stubs.cpp)

# Helper: add a test target (only if source file exists)
function(add_gtest_target name)
cmake_parse_arguments(ARG "" "" "SOURCES;EXTRA_SOURCES" ${ARGN})
# Check all source files exist
foreach(src ${ARG_SOURCES})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The foreach loop for checking source file existence only iterates over ARG_SOURCES. It should also check files in ARG_EXTRA_SOURCES to prevent build failures when an extra source file is missing or misspelled. This will make the helper function more robust.

    foreach(src ${ARG_SOURCES} ${ARG_EXTRA_SOURCES})

if(NOT IS_ABSOLUTE "${src}")
set(src "${CMAKE_CURRENT_SOURCE_DIR}/${src}")
endif()
if(NOT EXISTS "${src}")
message(STATUS "Skipping ${name}: ${src} not found")
return()
endif()
endforeach()
add_executable(${name} ${ARG_SOURCES} ${STUB_SOURCES} ${ARG_EXTRA_SOURCES})
target_include_directories(${name} PRIVATE ${COMMON_INCLUDE_DIRS})
target_link_libraries(${name} ${GTEST_TARGET})
add_test(NAME ${name} COMMAND ${name})
set_tests_properties(${name} PROPERTIES LABELS "no_hardware")
endfunction()

# --- Header-only tests (no runtime .cpp sources needed) ---

add_gtest_target(test_submit_types SOURCES test_submit_types.cpp)
add_gtest_target(test_core_types SOURCES test_core_types.cpp)
add_gtest_target(test_tensor SOURCES test_tensor.cpp)

# --- Tests requiring runtime .cpp sources ---

add_gtest_target(test_shared_memory
SOURCES test_shared_memory.cpp
EXTRA_SOURCES ${TMR_RUNTIME_DIR}/pto_shared_memory.cpp
)

add_gtest_target(test_ring_buffer
SOURCES test_ring_buffer.cpp
EXTRA_SOURCES
${TMR_RUNTIME_DIR}/pto_shared_memory.cpp
${TMR_RUNTIME_DIR}/pto_scheduler.cpp
${TMR_RUNTIME_DIR}/pto_ring_buffer.cpp
)

add_gtest_target(test_tensormap
SOURCES test_tensormap.cpp
EXTRA_SOURCES
${TMR_RUNTIME_DIR}/pto_tensormap.cpp
)

add_gtest_target(test_ready_queue
SOURCES test_ready_queue.cpp
EXTRA_SOURCES
${TMR_RUNTIME_DIR}/pto_scheduler.cpp
${TMR_RUNTIME_DIR}/pto_shared_memory.cpp
)

add_gtest_target(test_scheduler_state
SOURCES test_scheduler_state.cpp
EXTRA_SOURCES
${TMR_RUNTIME_DIR}/pto_scheduler.cpp
${TMR_RUNTIME_DIR}/pto_shared_memory.cpp
)

add_gtest_target(test_pto_types SOURCES test_pto_types.cpp)

add_gtest_target(test_dispatch_payload SOURCES test_dispatch_payload.cpp)
99 changes: 99 additions & 0 deletions tests/ut/cpp/stubs/test_stubs.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/**
* Link-time stubs for platform APIs used by runtime headers.
*
* Provides x86-compatible implementations of functions declared in
* platform headers (unified_log.h, device_time.h, common.h) so that
* runtime data structures can be unit-tested on CI runners without
* Ascend hardware or SDK.
*/

#include <chrono>
#include <cstdarg>
#include <cstdint>
#include <cstdio>
#include <stdexcept>
#include <string>

// =============================================================================
// unified_log.h stubs (5 log-level functions)
// =============================================================================

extern "C" {

void unified_log_error(const char* func, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
fprintf(stderr, "[ERROR] %s: ", func);
vfprintf(stderr, fmt, args);
fprintf(stderr, "\n");
va_end(args);
}

void unified_log_warn(const char* func, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
fprintf(stderr, "[WARN] %s: ", func);
vfprintf(stderr, fmt, args);
fprintf(stderr, "\n");
va_end(args);
}

void unified_log_info(const char* /* func */, const char* /* fmt */, ...) {
// Suppress info in tests
}

void unified_log_debug(const char* /* func */, const char* /* fmt */, ...) {
// Suppress debug in tests
}

void unified_log_always(const char* func, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
fprintf(stderr, "[ALWAYS] %s: ", func);
vfprintf(stderr, fmt, args);
fprintf(stderr, "\n");
va_end(args);
}

} // extern "C"

// =============================================================================
// device_time.h stub
// =============================================================================

uint64_t get_sys_cnt_aicpu() {
auto now = std::chrono::steady_clock::now();
return static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count());
}

// =============================================================================
// common.h stubs (assert_impl, get_stacktrace, AssertionError)
// =============================================================================

std::string get_stacktrace(int /* skip_frames */) {
return "<stacktrace not available in test stubs>";
}

class AssertionError : public std::runtime_error {
public:
AssertionError(const char* condition, const char* file, int line)
: std::runtime_error(std::string("Assertion failed: ") + condition + " at " + file + ":" +
std::to_string(line)),
condition_(condition),
file_(file),
line_(line) {}

const char* condition() const { return condition_; }
const char* file() const { return file_; }
int line() const { return line_; }

private:
const char* condition_;
const char* file_;
int line_;
};

[[noreturn]] void assert_impl(const char* condition, const char* file, int line) {
throw AssertionError(condition, file, line);
}
141 changes: 141 additions & 0 deletions tests/ut/cpp/test_core_types.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/**
* Unit tests for core types in pto_runtime2_types.h
*
* Tests PTO2TaskId encoding, alignment assertions, and utility macros.
*/

#include <gtest/gtest.h>

#include "pto_runtime2_types.h"

// =============================================================================
// PTO2TaskId encoding/extraction
// =============================================================================

TEST(TaskId, DefaultIsZero) {
PTO2TaskId id;
EXPECT_EQ(id.raw, 0u);
EXPECT_EQ(id.ring(), 0);
EXPECT_EQ(id.local(), 0u);
}

TEST(TaskId, MakeAndExtract) {
auto id = pto2_make_task_id(2, 42);
EXPECT_EQ(id.ring(), 2);
EXPECT_EQ(id.local(), 42u);
}

TEST(TaskId, RingInUpperBits) {
auto id = pto2_make_task_id(3, 0);
EXPECT_EQ(id.raw, static_cast<uint64_t>(3) << 32);
EXPECT_EQ(id.ring(), 3);
EXPECT_EQ(id.local(), 0u);
}

TEST(TaskId, MaxRingMaxLocal) {
auto id = pto2_make_task_id(255, 0xFFFFFFFF);
EXPECT_EQ(id.ring(), 255);
EXPECT_EQ(id.local(), 0xFFFFFFFF);
}

TEST(TaskId, Roundtrip) {
for (uint8_t ring = 0; ring < PTO2_MAX_RING_DEPTH; ring++) {
for (uint32_t local : {0u, 1u, 100u, 0xFFFFu, 0xFFFFFFFFu}) {
auto id = pto2_make_task_id(ring, local);
EXPECT_EQ(id.ring(), ring);
EXPECT_EQ(id.local(), local);
}
}
}

TEST(TaskId, Equality) {
auto a = pto2_make_task_id(1, 42);
auto b = pto2_make_task_id(1, 42);
auto c = pto2_make_task_id(1, 43);
auto d = pto2_make_task_id(2, 42);

EXPECT_TRUE(a == b);
EXPECT_FALSE(a != b);
EXPECT_TRUE(a != c);
EXPECT_TRUE(a != d);
}

TEST(TaskId, SizeIs8Bytes) {
EXPECT_EQ(sizeof(PTO2TaskId), 8u);
}

// =============================================================================
// PTO2TaskSlotState size (cache-line aligned)
// =============================================================================

TEST(TaskSlotState, SizeIs64Bytes) {
EXPECT_EQ(sizeof(PTO2TaskSlotState), 64u);
}

// =============================================================================
// PTO2_ALIGN_UP macro
// =============================================================================

TEST(AlignUp, Zero) {
EXPECT_EQ(PTO2_ALIGN_UP(0, 64), 0u);
}

TEST(AlignUp, AlreadyAligned) {
EXPECT_EQ(PTO2_ALIGN_UP(64, 64), 64u);
EXPECT_EQ(PTO2_ALIGN_UP(128, 64), 128u);
}

TEST(AlignUp, NotAligned) {
EXPECT_EQ(PTO2_ALIGN_UP(1, 64), 64u);
EXPECT_EQ(PTO2_ALIGN_UP(63, 64), 64u);
EXPECT_EQ(PTO2_ALIGN_UP(65, 64), 128u);
}

TEST(AlignUp, SmallAlignment) {
EXPECT_EQ(PTO2_ALIGN_UP(5, 4), 8u);
EXPECT_EQ(PTO2_ALIGN_UP(4, 4), 4u);
EXPECT_EQ(PTO2_ALIGN_UP(3, 4), 4u);
}

// =============================================================================
// Task state enum values
// =============================================================================

TEST(TaskState, EnumValues) {
EXPECT_EQ(PTO2_TASK_PENDING, 0);
EXPECT_EQ(PTO2_TASK_READY, 1);
EXPECT_EQ(PTO2_TASK_RUNNING, 2);
EXPECT_EQ(PTO2_TASK_COMPLETED, 3);
EXPECT_EQ(PTO2_TASK_CONSUMED, 4);
}

// =============================================================================
// Error code constants
// =============================================================================

TEST(ErrorCodes, Values) {
EXPECT_EQ(PTO2_ERROR_NONE, 0);
EXPECT_EQ(PTO2_ERROR_SCOPE_DEADLOCK, 1);
EXPECT_EQ(PTO2_ERROR_HEAP_RING_DEADLOCK, 2);
EXPECT_EQ(PTO2_ERROR_FLOW_CONTROL_DEADLOCK, 3);
EXPECT_EQ(PTO2_ERROR_DEP_POOL_OVERFLOW, 4);
EXPECT_EQ(PTO2_ERROR_INVALID_ARGS, 5);
EXPECT_EQ(PTO2_ERROR_SCHEDULER_TIMEOUT, 100);
}

// =============================================================================
// Configuration constants
// =============================================================================

TEST(Config, TaskWindowSizeIsPowerOf2) {
EXPECT_GT(PTO2_TASK_WINDOW_SIZE, 0);
EXPECT_EQ(PTO2_TASK_WINDOW_SIZE & (PTO2_TASK_WINDOW_SIZE - 1), 0);
}

TEST(Config, MaxRingDepth) {
EXPECT_EQ(PTO2_MAX_RING_DEPTH, 4);
}

TEST(Config, AlignSize) {
EXPECT_EQ(PTO2_ALIGN_SIZE, 64);
}
Loading
Loading