Compare commits

...

3 Commits

Author SHA1 Message Date
blueloveTH
9ecfc0196f ...
...

...

...

...

...

...

...

...
2025-03-11 01:13:16 +08:00
blueloveTH
cef0a4a254 fix #351 2025-03-11 00:16:00 +08:00
blueloveTH
c99f0ee919 partially fix #351 2025-03-10 23:56:02 +08:00
11 changed files with 334 additions and 338 deletions

View File

@ -92,10 +92,18 @@ jobs:
uses: jirutka/setup-alpine@v1
with:
arch: x86
packages: gcc g++ make cmake libc-dev linux-headers python3
packages: gcc g++ make cmake libc-dev linux-headers python3 gdb
- name: Build and Test
run: |
uname -m
python cmake_build.py Debug
# gdb_commands.txt
echo "run" > gdb_commands.txt
echo "backtrace" >> gdb_commands.txt
echo "quit" >> gdb_commands.txt
gdb -batch -x gdb_commands.txt --args ./main tests/77_builtin_func_1.py
python cmake_build.py
python scripts/run_tests.py
python scripts/run_tests.py benchmark

View File

@ -20,3 +20,11 @@ May be one of:
### `sys.argv`
The command line arguments. Set by `py_sys_setargv`.
### `sys.setrecursionlimit(limit: int)`
Set the maximum depth of the Python interpreter stack to `limit`. This limit prevents infinite recursion from causing an overflow of the C stack and crashing the interpreter.
### `sys.getrecursionlimit() -> int`
Return the current value of the recursion limit.

View File

@ -38,6 +38,10 @@ typedef struct VM {
py_TValue last_retval;
py_TValue curr_exception;
int recursion_depth;
int max_recursion_depth;
bool is_curr_exc_handled; // handled by try-except block but not cleared yet
py_TValue reg[8]; // users' registers

View File

@ -495,10 +495,12 @@ PK_API py_StackRef py_pushtmp();
/// If return false: `[self] -> [self]` (no change).
PK_API bool py_pushmethod(py_Name name);
/// Call a callable object via pocketpy's calling convention.
/// You need to prepare the stack using this form: `callable, self/nil, arg1, arg2, ..., k1, v1, k2,
/// v2, ...` `argc` is the number of positional arguments excluding `self`. `kwargc` is the number
/// of keyword arguments, i.e. the number of key-value pairs. The result will be set to
/// `py_retval()`. The stack size will be reduced by `2 + argc + kwargc * 2`.
/// You need to prepare the stack using the following format:
/// `callable, self/nil, arg1, arg2, ..., k1, v1, k2, v2, ...`.
/// `argc` is the number of positional arguments excluding `self`.
/// `kwargc` is the number of keyword arguments.
/// The result will be set to `py_retval()`.
/// The stack size will be reduced by `2 + argc + kwargc * 2`.
PK_API bool py_vectorcall(uint16_t argc, uint16_t kwargc) PY_RAISE PY_RETURN;
/// Evaluate an expression and push the result to the stack.
/// This function is used for testing.
@ -739,7 +741,7 @@ enum py_PredefinedType {
tp_KeyboardInterrupt,
tp_StopIteration,
tp_SyntaxError,
tp_StackOverflowError,
tp_RecursionError,
tp_OSError,
tp_NotImplementedError,
tp_TypeError,

View File

@ -8,9 +8,12 @@ def test_file(filepath, cpython=False):
if cpython:
return os.system("python " + filepath) == 0
if sys.platform == 'win32':
return os.system("main.exe " + filepath) == 0
code = os.system("main.exe " + filepath)
else:
return os.system("./main " + filepath) == 0
code = os.system("./main " + filepath)
if code != 0:
print('Return code:', code)
return code == 0
def test_dir(path):
print("Testing directory:", path)

View File

@ -13,14 +13,6 @@ static bool stack_format_object(VM* self, c11_sv spec);
#define CHECK_RETURN_FROM_EXCEPT_OR_FINALLY() \
if(self->is_curr_exc_handled) py_clearexc(NULL)
#define CHECK_STACK_OVERFLOW() \
do { \
if(self->stack.sp > self->stack.end) { \
py_exception(tp_StackOverflowError, ""); \
goto __ERROR; \
} \
} while(0)
#define DISPATCH() \
do { \
frame->ip++; \
@ -61,6 +53,7 @@ static bool stack_format_object(VM* self, c11_sv spec);
*SECOND() = *THIRD(); \
} while(0)
// Must use a DISPATCH() after vectorcall_opcall() immediately!
#define vectorcall_opcall(argc, kwargc) \
do { \
FrameResult res = VM__vectorcall(self, (argc), (kwargc), true); \
@ -93,6 +86,15 @@ FrameResult VM__run_top_frame(VM* self) {
while(true) {
Bytecode byte;
__NEXT_FRAME:
if(self->recursion_depth >= self->max_recursion_depth) {
py_exception(tp_RecursionError, "maximum recursion depth exceeded");
goto __ERROR;
}
if(self->stack.sp >= self->stack.end) {
c11__abort(
"Stack overflow! Please increase PK_VM_STACK_SIZE or reduce the max recursion limit.");
}
codes = frame->co->codes.data;
frame->ip++;
@ -149,39 +151,32 @@ FrameResult VM__run_top_frame(VM* self) {
DISPATCH();
/*****************************************/
case OP_LOAD_CONST: {
CHECK_STACK_OVERFLOW();
PUSH(c11__at(py_TValue, &frame->co->consts, byte.arg));
DISPATCH();
}
case OP_LOAD_NONE: {
CHECK_STACK_OVERFLOW();
py_newnone(SP()++);
DISPATCH();
}
case OP_LOAD_TRUE: {
CHECK_STACK_OVERFLOW();
py_newbool(SP()++, true);
DISPATCH();
}
case OP_LOAD_FALSE: {
CHECK_STACK_OVERFLOW();
py_newbool(SP()++, false);
DISPATCH();
}
/*****************************************/
case OP_LOAD_SMALL_INT: {
CHECK_STACK_OVERFLOW();
py_newint(SP()++, (int16_t)byte.arg);
DISPATCH();
}
/*****************************************/
case OP_LOAD_ELLIPSIS: {
CHECK_STACK_OVERFLOW();
py_newellipsis(SP()++);
DISPATCH();
}
case OP_LOAD_FUNCTION: {
CHECK_STACK_OVERFLOW();
FuncDecl_ decl = c11__getitem(FuncDecl_, &frame->co->func_decls, byte.arg);
Function* ud = py_newobject(SP(), tp_function, 0, sizeof(Function));
Function__ctor(ud, decl, frame->module, frame->globals);
@ -352,8 +347,7 @@ FrameResult VM__run_top_frame(VM* self) {
} else {
INSERT_THIRD(); // [?, a, b]
*THIRD() = *magic; // [__getitem__, a, b]
if(!py_vectorcall(1, 0)) goto __ERROR;
PUSH(py_retval());
vectorcall_opcall(1, 0);
}
DISPATCH();
}
@ -413,7 +407,7 @@ FrameResult VM__run_top_frame(VM* self) {
if(!py_callcfunc(magic->_cfunc, 3, THIRD())) goto __ERROR;
STACK_SHRINK(4);
} else {
*FOURTH() = *magic; // [__selitem__, a, b, val]
*FOURTH() = *magic; // [__setitem__, a, b, val]
if(!py_vectorcall(2, 0)) goto __ERROR;
}
DISPATCH();
@ -503,8 +497,7 @@ FrameResult VM__run_top_frame(VM* self) {
py_newnil(SP()++); // [complex, NULL]
py_newint(SP()++, 0); // [complex, NULL, 0]
*SP()++ = tmp; // [complex, NULL, 0, x]
if(!py_vectorcall(2, 0)) goto __ERROR;
PUSH(py_retval());
vectorcall_opcall(2, 0);
DISPATCH();
}
case OP_BUILD_BYTES: {
@ -1079,8 +1072,7 @@ FrameResult VM__run_top_frame(VM* self) {
TOP()->type);
goto __ERROR;
}
if(!py_vectorcall(0, 0)) goto __ERROR;
PUSH(py_retval());
vectorcall_opcall(0, 0);
DISPATCH();
}
case OP_WITH_EXIT: {

View File

@ -64,6 +64,7 @@ static bool generator__next__(int argc, py_Ref argv) {
}
vm->stack.sp = ud->frame->p0;
vm->top_frame = vm->top_frame->f_back;
vm->recursion_depth--;
ud->state = 1;
return true;
} else {

View File

@ -71,6 +71,10 @@ void VM__ctor(VM* self) {
self->last_retval = *py_NIL();
self->curr_exception = *py_NIL();
self->recursion_depth = 0;
self->max_recursion_depth = 1000;
self->is_curr_exc_handled = false;
self->ctx = NULL;
@ -162,7 +166,7 @@ void VM__ctor(VM* self) {
py_setdict(&self->builtins, py_name("StopIteration"), py_tpobject(tp_StopIteration));
INJECT_BUILTIN_EXC(SyntaxError, tp_Exception);
INJECT_BUILTIN_EXC(StackOverflowError, tp_Exception);
INJECT_BUILTIN_EXC(RecursionError, tp_Exception);
INJECT_BUILTIN_EXC(OSError, tp_Exception);
INJECT_BUILTIN_EXC(NotImplementedError, tp_Exception);
INJECT_BUILTIN_EXC(TypeError, tp_Exception);
@ -265,6 +269,7 @@ void VM__dtor(VM* self) {
void VM__push_frame(VM* self, py_Frame* frame) {
frame->f_back = self->top_frame;
self->top_frame = frame;
self->recursion_depth++;
if(self->trace_info.func) self->trace_info.func(frame, TRACE_EVENT_PUSH);
}
@ -277,6 +282,7 @@ void VM__pop_frame(VM* self) {
// pop frame and delete
self->top_frame = frame->f_back;
Frame__delete(frame);
self->recursion_depth--;
}
static void _clip_int(int* value, int min, int max) {
@ -469,12 +475,6 @@ FrameResult VM__vectorcall(VM* self, uint16_t argc, uint16_t kwargc, bool opcall
py_Ref argv = p0 + 1 + (int)py_isnil(p0 + 1);
if(p0->type == tp_function) {
// check stack overflow
if(self->stack.sp > self->stack.end) {
py_exception(tp_StackOverflowError, "");
return RES_ERROR;
}
Function* fn = py_touserdata(p0);
const CodeObject* co = &fn->decl->code;

View File

@ -240,9 +240,28 @@ void pk__add_module_io() {}
#endif
static bool sys_setrecursionlimit(int argc, py_Ref argv) {
PY_CHECK_ARGC(1);
PY_CHECK_ARG_TYPE(0, tp_int);
int limit = py_toint(py_arg(0));
if(limit <= pk_current_vm->recursion_depth) return ValueError("the limit is too low");
pk_current_vm->max_recursion_depth = limit;
py_newnone(py_retval());
return true;
}
static bool sys_getrecursionlimit(int argc, py_Ref argv) {
PY_CHECK_ARGC(0);
py_newint(py_retval(), pk_current_vm->max_recursion_depth);
return true;
}
void pk__add_module_sys() {
py_Ref mod = py_newmodule("sys");
py_newstr(py_emplacedict(mod, py_name("platform")), PY_SYS_PLATFORM_STRING);
py_newstr(py_emplacedict(mod, py_name("version")), PK_VERSION);
py_newlist(py_emplacedict(mod, py_name("argv")));
py_bindfunc(mod, "setrecursionlimit", sys_setrecursionlimit);
py_bindfunc(mod, "getrecursionlimit", sys_getrecursionlimit);
}

View File

@ -57,14 +57,14 @@ class TestSuperNoBaseMethod(TestSuperBase):
try:
t = TestSuperNoParent()
print('未能拦截错误')
exit(1)
exit(2)
except:
pass
try:
t = TestSuperNoBaseMethod()
print('未能拦截错误')
exit(1)
exit(3)
except:
pass
@ -83,7 +83,7 @@ try:
c = C()
c.method()
print('未能拦截错误')
exit(1)
exit(4)
except:
pass
@ -91,7 +91,7 @@ try:
d = D()
d.method()
print('未能拦截错误')
exit(1)
exit(5)
except:
pass
@ -134,14 +134,14 @@ assert type(hash(a)) is int
try:
hash({1:1})
print('未能拦截错误')
exit(1)
exit(6)
except:
pass
try:
hash([1])
print('未能拦截错误')
exit(1)
exit(7)
except:
pass
@ -166,7 +166,7 @@ repr(A())
try:
range(1,2,3,4)
print('未能拦截错误, 在测试 range')
exit(1)
exit(8)
except:
pass
@ -174,14 +174,14 @@ except:
try:
int('asad')
print('未能拦截错误, 在测试 int')
exit(1)
exit(9)
except:
pass
try:
int(123, 16)
print('未能拦截错误, 在测试 int')
exit(1)
exit(10)
except:
pass
@ -192,14 +192,14 @@ assert type(11%2) is int
try:
float('asad')
print('未能拦截错误, 在测试 float')
exit(1)
exit(11)
except:
pass
try:
float([])
print('未能拦截错误, 在测试 float')
exit(1)
exit(12)
except:
pass
@ -213,7 +213,7 @@ assert type('25363546'.index('63')) is int
try:
'25363546'.index('err')
print('未能拦截错误, 在测试 str.index')
exit(1)
exit(13)
except:
pass
@ -228,7 +228,7 @@ assert '25363546'.find('err') == -1
try:
list(1,2)
print('未能拦截错误, 在测试 list')
exit(1)
exit(14)
except:
pass
@ -238,7 +238,7 @@ assert type([1,2,3,4,5].index(4)) is int
try:
[1,2,3,4,5].index(6)
print('未能拦截错误, 在测试 list.index')
exit(1)
exit(15)
except:
pass
@ -249,7 +249,7 @@ except:
try:
[1,2,3,4,5].remove(6)
print('未能拦截错误, 在测试 list.remove')
exit(1)
exit(16)
except:
pass
@ -259,7 +259,7 @@ except:
try:
[1,2,3,4,5].pop(1,2,3,4)
print('未能拦截错误, 在测试 list.pop')
exit(1)
exit(17)
except:
pass
@ -275,7 +275,7 @@ assert type(12 * [12]) is list
try:
tuple(1,2)
print('未能拦截错误, 在测试 tuple')
exit(1)
exit(18)
except:
pass
@ -305,287 +305,3 @@ assert type(repr(bytes([0x41, 0x42, 0x43]))) is str
# /************ slice ************/
assert type(slice(0.1, 0.2, 0.3)) is slice
# 未完全测试准确性-----------------------------------------------
# 116: 1529: bind_property(_t(tp_slice), "start", [](VM* vm, ArgsView args){
# #####: 1530: return CAST(Slice&, args[0]).start;
# -: 1531: });
# 116: 1532: bind_property(_t(tp_slice), "stop", [](VM* vm, ArgsView args){
# #####: 1533: return CAST(Slice&, args[0]).stop;
# -: 1534: });
# 116: 1535: bind_property(_t(tp_slice), "step", [](VM* vm, ArgsView args){
# #####: 1536: return CAST(Slice&, args[0]).step;
# -: 1537: });
s = slice(1, 2, 3)
assert type(s) is slice
assert s.start == 1
assert s.stop == 2
assert s.step == 3
# 未完全测试准确性-----------------------------------------------
# test slice.__repr__
assert type(repr(slice(1,1,1))) is str
# /************ namedict ************/
# # test namedict.keys:
# class A():
# def __init__(self):
# self.a = 10
# def method(self):
# pass
# my_namedict = A().__dict__
# assert type(my_namedict.keys()) is list
# # test namedict.values:
# class A():
# def __init__(self):
# self.a = 10
# def method(self):
# pass
# my_namedict = A().__dict__
# assert type(my_namedict.values()) is list
# class A():
# def __init__(self):
# self.a = 10
# def method(self):
# pass
# my_namedict = A().__dict__
# assert type(len(my_namedict)) is int
class A():
def __init__(self):
self.a = 10
def method(self):
pass
my_namedict = A().__dict__
try:
hash(my_namedict)
print('未能拦截错误, 在测试 namedict.__hash__')
exit(1)
except TypeError:
pass
a = hash(object()) # object is hashable
a = hash(A()) # A is hashable
class B:
def __eq__(self, o): return True
def __ne__(self, o): return False
try:
hash(B())
print('未能拦截错误, 在测试 B.__hash__')
exit(1)
except TypeError:
pass
# 未完全测试准确性-----------------------------------------------
# test namedict.__repr__:
class A():
def __init__(self):
self.a = 10
def method(self):
pass
my_namedict = A().__dict__
assert type(repr(my_namedict)) is str
# /************ dict ************/
# 未完全测试准确性-----------------------------------------------
# test dict:
assert type(dict([(1,2)])) is dict
try:
dict([(1, 2, 3)])
print('未能拦截错误, 在测试 dict')
exit(1)
except:
pass
try:
dict([(1, 2)], 1)
print('未能拦截错误, 在测试 dict')
exit(1)
except:
pass
try:
hash(dict([(1,2)]))
print('未能拦截错误, 在测试 dict.__hash__')
exit(1)
except:
pass
# test dict.__iter__
for k in {1:2, 2:3, 3:4}.keys():
assert k in [1,2,3]
# 未完全测试准确性-----------------------------------------------
# test dict.get
assert {1:2, 3:4}.get(1) == 2
assert {1:2, 3:4}.get(2) is None
assert {1:2, 3:4}.get(20, 100) == 100
try:
{1:2, 3:4}.get(1,1, 1)
print('未能拦截错误, 在测试 dict.get')
exit(1)
except:
pass
# 未完全测试准确性-----------------------------------------------
# test dict.__repr__
assert type(repr({1:2, 3:4})) is str
# /************ property ************/
class A():
def __init__(self):
self._name = '123'
@property
def value(self):
return 2
def get_name(self):
'''
doc string 1
'''
return self._name
def set_name(self, val):
'''
doc string 2
'''
self._name = val
assert A().value == 2
A.name = property(A.get_name, A.set_name)
class Vector2:
def __init__(self) -> None:
self._x = 0
@property
def x(self):
return self._x
@x.setter
def x(self, val):
self._x = val
v = Vector2()
assert v.x == 0
v.x = 10
assert v.x == 10
# function.__doc__
def aaa():
'12345'
pass
assert aaa.__doc__ == '12345'
# test callable
assert callable(lambda: 1) is True # function
assert callable(1) is False # int
assert callable(object) is True # type
assert callable(object()) is False
assert callable([].append) is True # bound method
assert callable([].__getitem__) is True # bound method
class A:
def __init__(self):
pass
def __call__(self):
pass
assert callable(A) is True # type
assert callable(A()) is True # instance with __call__
assert callable(A.__call__) is True # bound method
assert callable(A.__init__) is True # bound method
assert callable(print) is True # builtin function
assert callable(isinstance) is True # builtin function
assert id(0) is None
assert id(2**62) is None
# test issubclass
assert issubclass(int, int) is True
assert issubclass(int, object) is True
assert issubclass(object, int) is False
assert issubclass(object, object) is True
assert issubclass(int, type) is False
assert issubclass(type, type) is True
assert issubclass(float, int) is False
def f(a, b):
c = a
del a
return sum([b, c])
assert f(1, 2) == 3
# /************ module time ************/
import time
# test time.time
assert type(time.time()) is float
local_t = time.localtime()
assert type(local_t.tm_year) is int
assert type(local_t.tm_mon) is int
assert type(local_t.tm_mday) is int
assert type(local_t.tm_hour) is int
assert type(local_t.tm_min) is int
assert type(local_t.tm_sec) is int
assert type(local_t.tm_wday) is int
assert type(local_t.tm_yday) is int
assert type(local_t.tm_isdst) is int
# test time.sleep
time.sleep(0.1)
# test time.localtime
assert type(time.localtime()) is time.struct_time
# test min/max
assert min(1, 2) == 1
assert min(1, 2, 3) == 1
assert min([1, 2]) == 1
assert min([1, 2], key=lambda x: -x) == 2
assert max(1, 2) == 2
assert max(1, 2, 3) == 3
assert max([1, 2]) == 2
assert max([1, 2, 3], key=lambda x: -x) == 1
assert min([
(3, 1),
(1, 2),
(1, 3),
(1, 4),
]) == (1, 2)
assert min(1, 2) == 1
assert max(1, 2) == 2
exit()
dir_int = dir(int)
assert dir_int[:4] == ['__add__', '__and__', '__base__', '__eq__']

243
tests/77_builtin_func_2.py Normal file
View File

@ -0,0 +1,243 @@
# 未完全测试准确性-----------------------------------------------
# 116: 1529: bind_property(_t(tp_slice), "start", [](VM* vm, ArgsView args){
# #####: 1530: return CAST(Slice&, args[0]).start;
# -: 1531: });
# 116: 1532: bind_property(_t(tp_slice), "stop", [](VM* vm, ArgsView args){
# #####: 1533: return CAST(Slice&, args[0]).stop;
# -: 1534: });
# 116: 1535: bind_property(_t(tp_slice), "step", [](VM* vm, ArgsView args){
# #####: 1536: return CAST(Slice&, args[0]).step;
# -: 1537: });
s = slice(1, 2, 3)
assert type(s) is slice
assert s.start == 1
assert s.stop == 2
assert s.step == 3
# 未完全测试准确性-----------------------------------------------
# test slice.__repr__
assert type(repr(slice(1,1,1))) is str
class A():
def __init__(self):
self.a = 10
def method(self):
pass
my_namedict = A().__dict__
try:
hash(my_namedict)
print('未能拦截错误, 在测试 namedict.__hash__')
exit(1)
except TypeError:
pass
a = hash(object()) # object is hashable
a = hash(A()) # A is hashable
class B:
def __eq__(self, o): return True
def __ne__(self, o): return False
try:
hash(B())
print('未能拦截错误, 在测试 B.__hash__')
exit(1)
except TypeError:
pass
# 未完全测试准确性-----------------------------------------------
# test namedict.__repr__:
class A():
def __init__(self):
self.a = 10
def method(self):
pass
my_namedict = A().__dict__
assert type(repr(my_namedict)) is str
# /************ dict ************/
# 未完全测试准确性-----------------------------------------------
# test dict:
assert type(dict([(1,2)])) is dict
try:
dict([(1, 2, 3)])
print('未能拦截错误, 在测试 dict')
exit(1)
except:
pass
try:
dict([(1, 2)], 1)
print('未能拦截错误, 在测试 dict')
exit(1)
except:
pass
try:
hash(dict([(1,2)]))
print('未能拦截错误, 在测试 dict.__hash__')
exit(1)
except:
pass
# test dict.__iter__
for k in {1:2, 2:3, 3:4}.keys():
assert k in [1,2,3]
# 未完全测试准确性-----------------------------------------------
# test dict.get
assert {1:2, 3:4}.get(1) == 2
assert {1:2, 3:4}.get(2) is None
assert {1:2, 3:4}.get(20, 100) == 100
try:
{1:2, 3:4}.get(1,1, 1)
print('未能拦截错误, 在测试 dict.get')
exit(1)
except:
pass
# 未完全测试准确性-----------------------------------------------
# test dict.__repr__
assert type(repr({1:2, 3:4})) is str
# /************ property ************/
class A():
def __init__(self):
self._name = '123'
@property
def value(self):
return 2
def get_name(self):
'''
doc string 1
'''
return self._name
def set_name(self, val):
'''
doc string 2
'''
self._name = val
assert A().value == 2
A.name = property(A.get_name, A.set_name)
class Vector2:
def __init__(self) -> None:
self._x = 0
@property
def x(self):
return self._x
@x.setter
def x(self, val):
self._x = val
v = Vector2()
assert v.x == 0
v.x = 10
assert v.x == 10
# function.__doc__
def aaa():
'12345'
pass
assert aaa.__doc__ == '12345'
# test callable
assert callable(lambda: 1) is True # function
assert callable(1) is False # int
assert callable(object) is True # type
assert callable(object()) is False
assert callable([].append) is True # bound method
assert callable([].__getitem__) is True # bound method
class A:
def __init__(self):
pass
def __call__(self):
pass
assert callable(A) is True # type
assert callable(A()) is True # instance with __call__
assert callable(A.__call__) is True # bound method
assert callable(A.__init__) is True # bound method
assert callable(print) is True # builtin function
assert callable(isinstance) is True # builtin function
assert id(0) is None
assert id(2**62) is None
# test issubclass
assert issubclass(int, int) is True
assert issubclass(int, object) is True
assert issubclass(object, int) is False
assert issubclass(object, object) is True
assert issubclass(int, type) is False
assert issubclass(type, type) is True
assert issubclass(float, int) is False
def f(a, b):
c = a
del a
return sum([b, c])
assert f(1, 2) == 3
# /************ module time ************/
import time
# test time.time
assert type(time.time()) is float
local_t = time.localtime()
assert type(local_t.tm_year) is int
assert type(local_t.tm_mon) is int
assert type(local_t.tm_mday) is int
assert type(local_t.tm_hour) is int
assert type(local_t.tm_min) is int
assert type(local_t.tm_sec) is int
assert type(local_t.tm_wday) is int
assert type(local_t.tm_yday) is int
assert type(local_t.tm_isdst) is int
# test time.sleep
time.sleep(0.1)
# test time.localtime
assert type(time.localtime()) is time.struct_time
# test min/max
assert min(1, 2) == 1
assert min(1, 2, 3) == 1
assert min([1, 2]) == 1
assert min([1, 2], key=lambda x: -x) == 2
assert max(1, 2) == 2
assert max(1, 2, 3) == 3
assert max([1, 2]) == 2
assert max([1, 2, 3], key=lambda x: -x) == 1
assert min([
(3, 1),
(1, 2),
(1, 3),
(1, 4),
]) == (1, 2)
assert min(1, 2) == 1
assert max(1, 2) == 2