Skip to content

Commit

Permalink
feat: support return result in tester
Browse files Browse the repository at this point in the history
  • Loading branch information
sunxilin committed Jan 2, 2025
1 parent 8997112 commit aceb3c7
Show file tree
Hide file tree
Showing 24 changed files with 836 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@
"request": "launch",
"program": "${workspaceFolder}/out/linux/x64/tests/standalone/ten_runtime_smoke_test",
"args": [
"--gtest_filter=BasicTest.HelloWorld1"
"--gtest_filter=StandaloneTest.MockReturn"
],
"cwd": "${workspaceFolder}/out/linux/x64/tests/standalone/",
"env": {
Expand Down
43 changes: 39 additions & 4 deletions core/include/ten_runtime/binding/cpp/detail/test/env_tester.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,39 @@ class ten_env_tester_t {
return rc;
}

bool return_result(std::unique_ptr<cmd_result_t> &&cmd_result,
std::unique_ptr<cmd_t> &&target_cmd,
error_t *err = nullptr) {
TEN_ASSERT(c_ten_env_tester, "Should not happen.");

bool rc = false;

if (!cmd_result || !target_cmd) {
TEN_ASSERT(0, "Invalid argument.");
return rc;
}

rc = ten_env_tester_return_result(
c_ten_env_tester, cmd_result->get_underlying_msg(),
target_cmd->get_underlying_msg(), nullptr, nullptr,
err != nullptr ? err->get_c_error() : nullptr);

if (rc) {
// Only when is_final is true does the ownership of target_cmd
// transfer. Otherwise, target_cmd remains with the extension,
// allowing the extension to return more results.
if (cmd_result->is_final()) {
auto *cpp_target_cmd_ptr = target_cmd.release();
delete cpp_target_cmd_ptr;
}

auto *cpp_cmd_result_ptr = cmd_result.release();
delete cpp_cmd_result_ptr;
}

return rc;
}

bool stop_test(error_t *err = nullptr) {
TEN_ASSERT(c_ten_env_tester, "Should not happen.");
return ten_env_tester_stop_test(
Expand Down Expand Up @@ -195,7 +228,8 @@ class ten_env_tester_t {

if (c_cmd_result != nullptr) {
cmd_result = cmd_result_t::create(
// Clone a C shared_ptr to be owned by the C++ instance.
// Clone a C shared_ptr to be owned by the C++
// instance.
ten_shared_ptr_clone(c_cmd_result));
}

Expand All @@ -207,9 +241,10 @@ class ten_env_tester_t {
}

if (ten_cmd_result_is_final(c_cmd_result, nullptr)) {
// Only when is_final is true should the result handler be cleared.
// Otherwise, since more result handlers are expected, the result
// handler should not be cleared.
// Only when is_final is true should the result handler
// be cleared. Otherwise, since more result handlers
// are expected, the result handler should not be
// cleared.
delete result_handler;
}
}
Expand Down
6 changes: 6 additions & 0 deletions core/include/ten_runtime/test/env_tester.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,11 @@ TEN_RUNTIME_API bool ten_env_tester_send_video_frame(
ten_env_tester_error_handler_func_t handler, void *user_data,
ten_error_t *error);

TEN_RUNTIME_API bool ten_env_tester_return_result(
ten_env_tester_t *self, ten_shared_ptr_t *result,
ten_shared_ptr_t *target_cmd,
ten_env_tester_error_handler_func_t error_handler, void *user_data,
ten_error_t *error);

TEN_RUNTIME_API bool ten_env_tester_stop_test(ten_env_tester_t *self,
ten_error_t *error);
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,8 @@ TEN_RUNTIME_PRIVATE_API PyObject *ten_py_ten_env_tester_send_audio_frame(
TEN_RUNTIME_PRIVATE_API PyObject *ten_py_ten_env_tester_send_video_frame(
PyObject *self, PyObject *args);

TEN_RUNTIME_PRIVATE_API PyObject *ten_py_ten_env_tester_return_result(
PyObject *self, PyObject *args);

TEN_RUNTIME_PRIVATE_API bool ten_py_ten_env_tester_check_integrity(
ten_py_ten_env_tester_t *self);
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,12 @@ class _TenEnvTester:
def send_video_frame(
self, video_frame: _VideoFrame, error_handler: TestErrorHandler
) -> None: ...
def return_result(
self,
result: _CmdResult,
target_cmd: _Cmd,
error_handler: TestErrorHandler,
) -> None: ...
def stop_test(self) -> None: ...

class _ExtensionTester:
Expand Down
10 changes: 10 additions & 0 deletions core/src/ten_runtime/binding/python/interface/ten/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ def send_video_frame(
) -> None:
return self._internal.send_video_frame(video_frame, error_handler)

def return_result(
self,
cmd_result: CmdResult,
target_cmd: Cmd,
error_handler: ErrorHandler = None,
) -> None:
return self._internal.return_result(
cmd_result, target_cmd, error_handler
)

def stop_test(self) -> None:
return self._internal.stop_test()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ PyTypeObject *ten_py_ten_env_tester_type(void) {
NULL},
{"send_video_frame", ten_py_ten_env_tester_send_video_frame, METH_VARARGS,
NULL},
{"return_result", ten_py_ten_env_tester_return_result, METH_VARARGS,
NULL},
{NULL, NULL, 0, NULL},
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//
// Copyright © 2025 Agora
// This file is part of TEN Framework, an open source project.
// Licensed under the Apache License, Version 2.0, with certain conditions.
// Refer to the "LICENSE" file in the root directory for more information.
//
#include "include_internal/ten_runtime/binding/python/common/common.h"
#include "include_internal/ten_runtime/binding/python/common/error.h"
#include "include_internal/ten_runtime/binding/python/msg/cmd.h"
#include "include_internal/ten_runtime/binding/python/msg/cmd_result.h"
#include "include_internal/ten_runtime/binding/python/msg/msg.h"
#include "include_internal/ten_runtime/binding/python/test/env_tester.h"
#include "ten_runtime/msg/cmd_result/cmd_result.h"
#include "ten_runtime/test/env_tester.h"
#include "ten_utils/macro/check.h"

static void proxy_return_result_callback(ten_env_tester_t *self,
void *user_data, ten_error_t *error) {
TEN_ASSERT(self && ten_env_tester_check_integrity(self),
"Should not happen.");
TEN_ASSERT(user_data, "Should not happen.");

// About to call the Python function, so it's necessary to ensure that the GIL
// has been acquired.
//
// Allows C codes to work safely with Python objects.
PyGILState_STATE prev_state = ten_py_gil_state_ensure_internal();

ten_py_ten_env_tester_t *py_ten_env_tester = ten_py_ten_env_tester_wrap(self);
PyObject *cb_func = user_data;

PyObject *arglist = NULL;
ten_py_error_t *py_error = NULL;

if (!error) {
arglist = Py_BuildValue("(OO)", py_ten_env_tester->actual_py_ten_env_tester,
Py_None);
} else {
py_error = ten_py_error_wrap(error);
arglist = Py_BuildValue("(OO)", py_ten_env_tester->actual_py_ten_env_tester,
py_error);
}

PyObject *result = PyObject_CallObject(cb_func, arglist);
Py_XDECREF(result); // Ensure cleanup if an error occurred.

bool err_occurred = ten_py_check_and_clear_py_error();
TEN_ASSERT(!err_occurred, "Should not happen.");

Py_XDECREF(arglist);

if (py_error) {
ten_py_error_invalidate(py_error);
}

ten_py_gil_state_release_internal(prev_state);
}

PyObject *ten_py_ten_env_tester_return_result(PyObject *self, PyObject *args) {
ten_py_ten_env_tester_t *py_ten_env_tester = (ten_py_ten_env_tester_t *)self;
TEN_ASSERT(py_ten_env_tester &&
ten_py_ten_env_tester_check_integrity(py_ten_env_tester),
"Invalid argument.");

if (PyTuple_GET_SIZE(args) != 3) {
return ten_py_raise_py_value_error_exception(
"Invalid argument count when ten_env_tester.return_result.");
}

bool success = true;

ten_error_t err;
ten_error_init(&err);

ten_py_cmd_result_t *py_cmd_result = NULL;
ten_py_cmd_t *py_target_cmd = NULL;
PyObject *cb_func = NULL;

if (!PyArg_ParseTuple(args, "O!O!O", ten_py_cmd_result_py_type(),
&py_cmd_result, ten_py_cmd_py_type(), &py_target_cmd,
&cb_func)) {
success = false;
ten_py_raise_py_type_error_exception(
"Invalid argument type when return result.");
goto done;
}

// Check if cb_func is callable.
if (!PyCallable_Check(cb_func)) {
cb_func = NULL;
}

if (cb_func) {
// Increase the reference count of the callback function to ensure that it
// will not be destroyed before the callback is called.
Py_INCREF(cb_func);

success = ten_env_tester_return_result(
py_ten_env_tester->c_ten_env_tester, py_cmd_result->msg.c_msg,
py_target_cmd->msg.c_msg, proxy_return_result_callback, cb_func, &err);
} else {
success = ten_env_tester_return_result(
py_ten_env_tester->c_ten_env_tester, py_cmd_result->msg.c_msg,
py_target_cmd->msg.c_msg, NULL, NULL, &err);
}

if (!success) {
if (cb_func) {
Py_XDECREF(cb_func);
}

ten_py_raise_py_runtime_error_exception("Failed to return result.");
goto done;
} else {
if (ten_cmd_result_is_final(py_cmd_result->msg.c_msg, &err)) {
// Remove the C message from the python target message if it is the final
// cmd result.
ten_py_msg_destroy_c_msg(&py_target_cmd->msg);
}

ten_py_msg_destroy_c_msg(&py_cmd_result->msg);
}

done:
ten_error_deinit(&err);

if (success) {
Py_RETURN_NONE;
} else {
return NULL;
}
}
Loading

0 comments on commit aceb3c7

Please sign in to comment.