• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TEN-framework / ten-framework / 20476676207

24 Dec 2025 02:39AM UTC coverage: 57.615%. First build
20476676207

Pull #1912

github

web-flow
Merge a67dd7469 into 834d71495
Pull Request #1912: fix: python test case & improve the user experience of TenError in Python

9 of 11 new or added lines in 2 files covered. (81.82%)

54068 of 93843 relevant lines covered (57.62%)

710914.38 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

38.78
/core/src/ten_runtime/binding/python/native/common/error.c
1
//
2
// Copyright © 2025 Agora
3
// This file is part of TEN Framework, an open source project.
4
// Licensed under the Apache License, Version 2.0, with certain conditions.
5
// Refer to the "LICENSE" file in the root directory for more information.
6
//
7
#include "include_internal/ten_runtime/binding/python/common/error.h"
8

9
#include <stdbool.h>
10

11
#include "include_internal/ten_runtime/binding/python/common/python_stuff.h"
12
#include "ten_utils/lib/error.h"
13
#include "ten_utils/lib/string.h"
14
#include "ten_utils/log/log.h"
15
#include "ten_utils/macro/check.h"
16
#include "ten_utils/macro/mark.h"
17

18
static PyTypeObject *ten_py_error_type = NULL;
19

20
static ten_py_error_t *ten_py_error_create_internal(PyTypeObject *py_type) {
3✔
21
  if (!py_type) {
3✔
22
    py_type = ten_py_error_type;
×
23
  }
×
24

25
  ten_py_error_t *py_error = (ten_py_error_t *)py_type->tp_alloc(py_type, 0);
3✔
26
  TEN_ASSERT(py_error, "Failed to allocate memory.");
3✔
27

28
  TEN_ERROR_INIT(py_error->c_error);
3✔
29

30
  return py_error;
3✔
31
}
3✔
32

33
PyObject *ten_py_error_create(PyTypeObject *type, PyObject *args,
34
                              TEN_UNUSED PyObject *kwds) {
3✔
35
  if (PyTuple_GET_SIZE(args) != 2) {
6✔
36
    return ten_py_raise_py_value_error_exception(
×
37
        "Invalid argument count when TenError.create.");
×
38
  }
×
39

40
  ten_error_code_t error_code = TEN_ERROR_CODE_OK;
3✔
41
  const char *error_message = NULL;
3✔
42

43
  if (!PyArg_ParseTuple(args, "iz", &error_code, &error_message)) {
3✔
44
    return ten_py_raise_py_value_error_exception("Failed to parse arguments.");
×
45
  }
×
46

47
  ten_py_error_t *py_error = ten_py_error_create_internal(type);
3✔
48
  if (!py_error) {
3✔
49
    return ten_py_raise_py_memory_error_exception("Failed to allocate memory.");
×
50
  }
×
51

52
  ten_error_set_error_code(&py_error->c_error, error_code);
3✔
53
  if (error_message) {
3✔
54
    ten_error_set_error_message(&py_error->c_error, error_message);
2✔
55
  }
2✔
56

57
  return (PyObject *)py_error;
3✔
58
}
3✔
59

60
ten_py_error_t *ten_py_error_wrap(ten_error_t *error) {
26✔
61
  if (!error) {
26✔
62
    return NULL;
×
63
  }
×
64

65
  ten_py_error_t *py_error = (ten_py_error_t *)ten_py_error_py_type()->tp_alloc(
26✔
66
      ten_py_error_py_type(), 0);
26✔
67
  if (!py_error) {
26✔
68
    TEN_ASSERT(0, "Failed to allocate memory.");
×
69
    return NULL;
×
70
  }
×
71

72
  ten_error_init(&py_error->c_error);
26✔
73
  ten_error_copy(&py_error->c_error, error);
26✔
74

75
  return py_error;
26✔
76
}
26✔
77

78
void ten_py_error_invalidate(ten_py_error_t *py_error) {
19✔
79
  TEN_ASSERT(py_error, "py_error should not be NULL.");
19✔
80
  Py_DECREF(py_error);
19✔
81
}
19✔
82

83
void ten_py_error_destroy(PyObject *self) {
29✔
84
  ten_py_error_t *py_error = (ten_py_error_t *)self;
29✔
85
  if (!py_error) {
29✔
86
    return;
×
87
  }
×
88

89
  ten_error_deinit(&py_error->c_error);
29✔
90

91
  Py_TYPE(self)->tp_free(self);
29✔
92
}
29✔
93

94
PyObject *ten_py_error_get_error_code(PyObject *self,
95
                                      TEN_UNUSED PyObject *args) {
6✔
96
  ten_py_error_t *py_error = (ten_py_error_t *)self;
6✔
97
  if (!py_error) {
6✔
98
    return ten_py_raise_py_value_error_exception("Invalid argument.");
×
99
  }
×
100

101
  return PyLong_FromLong(ten_error_code(&py_error->c_error));
6✔
102
}
6✔
103

104
PyObject *ten_py_error_get_error_message(PyObject *self,
105
                                         TEN_UNUSED PyObject *args) {
3✔
106
  ten_py_error_t *py_error = (ten_py_error_t *)self;
3✔
107
  if (!py_error) {
3✔
108
    return ten_py_raise_py_value_error_exception("Invalid argument.");
×
109
  }
×
110

111
  return PyUnicode_FromString(ten_error_message(&py_error->c_error));
3✔
112
}
3✔
113

114
PyObject *ten_py_error_str(PyObject *self) {
25✔
115
  ten_py_error_t *py_error = (ten_py_error_t *)self;
25✔
116
  if (!py_error) {
25✔
NEW
117
    return PyUnicode_FromString("<TenError: invalid>");
×
NEW
118
  }
×
119

120
  ten_error_code_t error_code = ten_error_code(&py_error->c_error);
25✔
121
  const char *error_message = ten_error_message(&py_error->c_error);
25✔
122

123
  return PyUnicode_FromFormat("error_code: %d, error_message: %s", error_code,
25✔
124
                              error_message ? error_message : "");
25✔
125
}
25✔
126

127
static void ten_py_print_py_error(void) {
×
128
  PyObject *ptype = NULL;
×
129
  PyObject *pvalue = NULL;
×
130
  PyObject *ptraceback = NULL;
×
131

132
  PyErr_Fetch(&ptype, &pvalue, &ptraceback);
×
133
  if (!ptype && !pvalue && !ptraceback) {
×
134
    // No error to fetch.
135
    return;
×
136
  }
×
137

138
  PyErr_NormalizeException(&ptype, &pvalue, &ptraceback);
×
139
  if (pvalue != NULL) {
×
140
    PyObject *py_err_msg = PyObject_Str(pvalue);
×
141
    if (!py_err_msg) {
×
142
      TEN_LOGE("Failed to convert exception value to string");
×
143
    } else {
×
144
      const char *err_msg = PyUnicode_AsUTF8(py_err_msg);
×
145
      if (!err_msg) {
×
146
        TEN_LOGE("Failed to encode exception message as UTF-8");
×
147
      } else {
×
148
        const char *py_exception_type = PyExceptionClass_Name(ptype);
×
149
        if (!py_exception_type) {
×
150
          py_exception_type = "Unknown Exception";
×
151
        }
×
152
        TEN_LOGE("%s: %s", py_exception_type, err_msg);
×
153
      }
×
154
      Py_DECREF(py_err_msg);
×
155
    }
×
156
  } else {
×
157
    TEN_LOGE("Failed to get exception value");
×
158
  }
×
159

160
  if (ptraceback) {
×
161
    PyObject *stderr_file = PySys_GetObject("stderr");
×
162
    if (stderr_file) {
×
163
      // Dump the call stack of python codes to stderr.
164
      PyTraceBack_Print(ptraceback, stderr_file);
×
165
    } else {
×
166
      TEN_LOGW("Failed to get stderr to dump backtrace");
×
167
    }
×
168
  }
×
169

170
  Py_XDECREF(ptype);
×
171
  Py_XDECREF(pvalue);
×
172
  Py_XDECREF(ptraceback);
×
173
}
×
174

175
bool ten_py_check_and_clear_py_error(void) {
432✔
176
  bool err_occurred = PyErr_Occurred();
432✔
177
  if (err_occurred) {
432✔
178
    ten_py_print_py_error();
×
179

180
    PyErr_Clear();
×
181
  }
×
182
  return err_occurred;
432✔
183
}
432✔
184

185
PyObject *ten_py_raise_py_value_error_exception(const char *msg, ...) {
×
186
  ten_string_t err_msg;
×
187
  TEN_STRING_INIT(err_msg);
×
188

189
  va_list args;
×
190
  va_start(args, msg);
×
191
  ten_string_append_from_va_list(&err_msg, msg, args);
×
192
  va_end(args);
×
193

194
  TEN_LOGD("Raise Python ValueError exception: %s",
×
195
           ten_string_get_raw_str(&err_msg));
×
196
  PyErr_SetString(PyExc_ValueError, ten_string_get_raw_str(&err_msg));
×
197

198
  ten_string_deinit(&err_msg);
×
199

200
  // Returning NULL indicates that an exception has occurred during the
201
  // function's execution, and signaling to the Python interpreter to handle the
202
  // exception.
203
  return NULL;
×
204
}
×
205

206
PyObject *ten_py_raise_py_type_error_exception(const char *msg) {
×
207
  TEN_LOGD("Raise Python TypeError exception: %s", msg);
×
208
  PyErr_SetString(PyExc_TypeError, msg);
×
209

210
  return NULL;
×
211
}
×
212

213
PyObject *ten_py_raise_py_memory_error_exception(const char *msg) {
×
214
  TEN_LOGD("Raise Python TypeError exception: %s", msg);
×
215
  PyErr_SetString(PyExc_MemoryError, msg);
×
216

217
  return NULL;
×
218
}
×
219

220
PyObject *ten_py_raise_py_system_error_exception(const char *msg) {
×
221
  TEN_LOGD("Raise Python SystemError exception: %s", msg);
×
222
  PyErr_SetString(PyExc_SystemError, msg);
×
223

224
  return NULL;
×
225
}
×
226

227
PyObject *ten_py_raise_py_import_error_exception(const char *msg) {
×
228
  TEN_LOGD("Raise Python ImportError exception: %s", msg);
×
229
  PyErr_SetString(PyExc_ImportError, msg);
×
230

231
  return NULL;
×
232
}
×
233

234
PyObject *ten_py_raise_py_runtime_error_exception(const char *msg) {
×
235
  TEN_LOGD("Raise Python RuntimeError exception: %s", msg);
×
236
  PyErr_SetString(PyExc_RuntimeError, msg);
×
237

238
  return NULL;
×
239
}
×
240

241
PyObject *ten_py_raise_py_not_implemented_error_exception(const char *msg) {
×
242
  TEN_LOGD("Raise Python NotImplementedError exception: %s", msg);
×
243
  PyErr_SetString(PyExc_NotImplementedError, msg);
×
244

245
  return NULL;
×
246
}
×
247

248
bool ten_py_error_init_for_module(PyObject *module) {
5✔
249
  PyTypeObject *py_type = ten_py_error_py_type();
5✔
250

251
  if (PyType_Ready(py_type) < 0) {
5✔
252
    ten_py_raise_py_system_error_exception("Python Error class is not ready.");
×
253

254
    TEN_ASSERT(0, "Should not happen.");
×
255
    return false;
×
256
  }
×
257

258
  if (PyModule_AddObjectRef(module, "_TenError", (PyObject *)py_type) < 0) {
5✔
259
    ten_py_raise_py_import_error_exception(
×
260
        "Failed to add Python type to module.");
×
261

262
    TEN_ASSERT(0, "Should not happen.");
×
263
    return false;
×
264
  }
×
265

266
  return true;
5✔
267
}
5✔
268

269
PyObject *ten_py_error_register_error_type(TEN_UNUSED PyObject *self,
270
                                           PyObject *args) {
5✔
271
  PyObject *cls = NULL;
5✔
272
  if (!PyArg_ParseTuple(args, "O!", &PyType_Type, &cls)) {
5✔
273
    return NULL;
×
274
  }
×
275

276
  Py_XINCREF(cls);
5✔
277
  Py_XDECREF(ten_py_error_type);
5✔
278

279
  ten_py_error_type = (PyTypeObject *)cls;
5✔
280

281
  Py_RETURN_NONE;
5✔
282
}
5✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc