From d705ca170862b84d5215cb4fe7fe368d75b1ff56 Mon Sep 17 00:00:00 2001 From: Sekomer Date: Wed, 8 Mar 2023 19:34:20 +0300 Subject: [PATCH] commitullah --- .gitignore | 10 +++ CMakeLists.txt | 22 +++++ LICENCE | 21 +++++ README.rst | 52 ++++++++++++ build.sh | 43 ++++++++++ include/ringbuffer.h | 18 +++++ include/stack.h | 17 ++++ setup.py | 29 +++++++ src/CMakeLists.txt | 26 ++++++ src/ringbuffer.c | 173 ++++++++++++++++++++++++++++++++++++++++ src/stack.c | 156 ++++++++++++++++++++++++++++++++++++ src/structura.c | 68 ++++++++++++++++ test/benchmark.py | 73 +++++++++++++++++ test/test-ringbuffer.py | 61 ++++++++++++++ test/test-stack.py | 67 ++++++++++++++++ 15 files changed, 836 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENCE create mode 100644 README.rst create mode 100755 build.sh create mode 100644 include/ringbuffer.h create mode 100644 include/stack.h create mode 100644 setup.py create mode 100644 src/CMakeLists.txt create mode 100644 src/ringbuffer.c create mode 100644 src/stack.c create mode 100644 src/structura.c create mode 100644 test/benchmark.py create mode 100644 test/test-ringbuffer.py create mode 100644 test/test-stack.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d036b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.git +.vscode + +build + +dist +wheelhouse +structura.egg-info + +MANIFEST diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..78b9f73 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,22 @@ +project(Structura) +set(CMAKE_C_STANDARD 99) +cmake_minimum_required(VERSION 3.0) + +add_subdirectory(src) +include_directories(include) + + +find_package(Python REQUIRED COMPONENTS Interpreter Development) +include_directories(${Python_INCLUDE_DIRS}) + +include_directories(/usr/include/python3.8) + +add_library(_structura SHARED src/ringbuffer.c src/structura.c src/stack.c) + +set_target_properties( + structura + PROPERTIES + PREFIX "" + OUTPUT_NAME "structura" + LINKER_LANGUAGE C +) \ No newline at end of file diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..c237a7a --- /dev/null +++ b/LICENCE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Alperen Serkan Aksöz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..e9e62f8 --- /dev/null +++ b/README.rst @@ -0,0 +1,52 @@ +Structura +===================================== + +.. image:: https://github.com/sekomer/structura/workflows/Tests/badge.svg + :alt: Structura build status on GitHub Actions + :target: https://github.com/sekomer/structura/actions + + +| + +.. contents:: + + +.. highlight:: bash + +Installation +------------ +From The Python Package Index + +``pip install structura`` + + +General Information +------------------- +- Source code: https://github.com/sekomer/structura +- Documentation: [ TODO ] + +Contributing +------------ +All contributions, suggestions, and optimization ideas are welcome! + +Using Structura +------------ +[ TODO ] + +Build Instructions +------------------ +To build `structura` from source, you should clone the repo and run the following command + +``sudo python3 setup.py install`` + +What's New +---------- +Ring buffers have been added. + +What's Coming +------------- +Linked Lists, Queues, Stacks, Hash Tables, Trees. + +Proposals for enhancement +------------------------- +You can create an issue or mail me at a.serkanaksoz@gmail.com diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..94bd608 --- /dev/null +++ b/build.sh @@ -0,0 +1,43 @@ +function check_error { + if [ $1 -ne 0 ]; then + echo "Error: $2" + sudo kill -9 $$ + fi +} + +# clean up +sudo rm -rf dist wheelhouse +check_error $? "Cleanup failed" + +# build +sudo python setup.py install sdist bdist_wheel +check_error $? "Build failed" + +# manylinux +auditwheel repair dist/*.whl +check_error $? "auditwheel failed" + +# remove wheel other than manylinux +sudo rm dist/*.whl +check_error $? "Failed to remove wheel" + +# copy manylinux to dist +sudo cp wheelhouse/*.whl dist/ +check_error $? "Failed to copy manylinux to dist" + +# remove wheelhouse +sudo rm -rf wheelhouse +check_error $? "Failed to remove wheelhouse" + +# upload if --upload is specified +if [ "$1" == "--upload" ]; then + sudo twine upload dist/* + check_error $? "Failed to upload package" +fi + + +# run tests +for file in test/*.py; do + sudo python $file + check_error $? "Failed to run $file" +done \ No newline at end of file diff --git a/include/ringbuffer.h b/include/ringbuffer.h new file mode 100644 index 0000000..491cb73 --- /dev/null +++ b/include/ringbuffer.h @@ -0,0 +1,18 @@ +#ifndef RINGBUFFER_H +#define RINGBUFFER_H + +#include + +extern PyTypeObject RingBufferType; + +typedef struct +{ + PyObject_HEAD; + long capacity; + long size; + long head; + long tail; + PyObject **items; +} RingBuffer; + +#endif /* RINGBUFFER_H */ diff --git a/include/stack.h b/include/stack.h new file mode 100644 index 0000000..96c9df2 --- /dev/null +++ b/include/stack.h @@ -0,0 +1,17 @@ +#ifndef STACK_H +#define STACK_H + +#include + +extern PyTypeObject StackType; + +typedef struct +{ + PyObject_HEAD; + long top; + long size; + long capacity; + PyObject **items; +} Stack; + +#endif /* STACK_H */ diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..d036d0d --- /dev/null +++ b/setup.py @@ -0,0 +1,29 @@ +import os +import setuptools +from distutils.core import setup, Extension + + +module = Extension( + "structura", + # list comprehension to get all .c files in the src directory + sources=[ + f"./src/{file}" for file in os.listdir("./src") if file.endswith(".c")], + include_dirs=['./include'], # list of directories containing header files +) + + +def main(): + setup(name="structura", + version="0.1.2", + description="C extension module for common data structures", + author="alperen serkan aksoz", + author_email="a.serkanaksoz@gmail.com", + url="https://github.com/sekomer/structura", + license="MIT", + ext_modules=[module], + platforms=['manylinux1_x86_64'] + ) + + +if __name__ == "__main__": + main() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..940a552 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.0) + +project(Structura) + +set(CMAKE_C_STANDARD 99) + +include_directories(${PROJECT_SOURCE_DIR}/include) + +# Add all the source files +set(SOURCES + structura.c + ringbuffer.c + stack.c +) + +# add headers from include directory +file(GLOB HEADERS ${PROJECT_SOURCE_DIR}/include/*.h) + + +# Create a library with all the source files +add_library(structura ${SOURCES} ${HEADERS}) + +# Link the Python library +find_package(Python REQUIRED COMPONENTS Interpreter Development) +target_include_directories(structura PUBLIC ${Python_INCLUDE_DIRS}) +target_link_libraries(structura ${Python_LIBRARIES}) \ No newline at end of file diff --git a/src/ringbuffer.c b/src/ringbuffer.c new file mode 100644 index 0000000..5d18fa5 --- /dev/null +++ b/src/ringbuffer.c @@ -0,0 +1,173 @@ +#define PY_SSIZE_T_CLEAN +#include +#include + +#include "../include/ringbuffer.h" + +static PyObject * +RingBuffer_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + RingBuffer *self; + + self = (RingBuffer *)type->tp_alloc(type, 0); + if (self != NULL) + { + // head is used to extract + // tail is used to insert + self->capacity = 0; + self->size = 0; + self->head = 0; + self->tail = 0; + self->items = NULL; + } + + return (PyObject *)self; +} + +static int +RingBuffer_init(RingBuffer *self, PyObject *args) +{ + PyObject *capacity = NULL; + + // get capacity from args + if (!PyArg_ParseTuple(args, "O", &capacity)) + return -1; + + if (!PyLong_Check(capacity)) + { + PyErr_SetString(PyExc_TypeError, "[TypeError] Capacity must be an integer"); + return -1; + } + + self->capacity = PyLong_AsLong(capacity); + self->items = (PyObject **)PyMem_Malloc(self->capacity * sizeof(PyObject *)); + + if (self->items == NULL) + { + PyErr_SetString(PyExc_MemoryError, "Out of memory"); + return -1; + } + + return 0; +} + +static void +RingBuffer_dealloc(RingBuffer *self) +{ + Py_XDECREF(self->items); + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static PyObject * +RingBuffer_is_empty(RingBuffer const *self) +{ + if (self->size == 0) + Py_RETURN_TRUE; + + Py_RETURN_FALSE; +} + +static PyObject * +RingBuffer_is_full(RingBuffer const *self) +{ + if (self->size == self->capacity) + Py_RETURN_TRUE; + + Py_RETURN_FALSE; +} + +static PyObject * +RingBuffer_enqueue(RingBuffer *self, PyObject *args) +{ + // its only works with integers, solve that and make it work with any type + PyObject *item = NULL; + + if (!PyArg_ParseTuple(args, "O", &item)) + return NULL; + Py_INCREF(item); + + // if its full, overwrite the oldest item + if (self->size == self->capacity) + { + PyObject *old_item = self->items[self->head]; + self->items[self->head] = item; + + assert(self->head == self->tail); + self->head = (self->head + 1) % self->capacity; + self->tail = (self->tail + 1) % self->capacity; + + Py_DECREF(old_item); + } + else + { + self->items[self->tail] = item; + self->tail = (self->tail + 1) % self->capacity; + self->size++; + } + + Py_RETURN_NONE; +} + +static PyObject * +RingBuffer_dequeue(RingBuffer *self) +{ + if (self->size == 0) + Py_RETURN_NONE; + + PyObject *item = self->items[self->head]; + self->items[self->head] = NULL; + self->head = (self->head + 1) % self->capacity; + self->size--; + + return item; +} + +static PyObject * +RingBuffer_peek(RingBuffer const *self) +{ + if (self->size == 0) + Py_RETURN_NONE; + + return self->items[self->head]; +} + +static PyObject * +RingBuffer_size(RingBuffer const *self) +{ + return PyLong_FromLong(self->size); +} + +static PyObject * +RingBuffer_capacity(RingBuffer const *self) +{ + return PyLong_FromLong(self->capacity); +} + +static PyMemberDef RingBuffer_members[] = { + {NULL} /* Sentinel */ +}; + +static PyMethodDef RingBuffer_methods[] = { + {"is_empty", (PyCFunction)RingBuffer_is_empty, METH_NOARGS, "Check if the RingBuffer is empty"}, + {"is_full", (PyCFunction)RingBuffer_is_full, METH_NOARGS, "Check if the RingBuffer is full"}, + {"enqueue", (PyCFunction)RingBuffer_enqueue, METH_VARARGS, "Add an item to the RingBuffer"}, + {"dequeue", (PyCFunction)RingBuffer_dequeue, METH_NOARGS, "Remove and return the oldest item from the RingBuffer"}, + {"peek", (PyCFunction)RingBuffer_peek, METH_NOARGS, "Return the oldest item from the RingBuffer"}, + {"size", (PyCFunction)RingBuffer_size, METH_NOARGS, "Return the size of the RingBuffer"}, + {"capacity", (PyCFunction)RingBuffer_capacity, METH_NOARGS, "Return the capacity of the RingBuffer"}, + {NULL} /* Sentinel */ +}; + +PyTypeObject RingBufferType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "RingBuffer", + .tp_basicsize = sizeof(RingBuffer), + .tp_itemsize = 0, + .tp_new = RingBuffer_new, + .tp_init = (initproc)RingBuffer_init, + .tp_dealloc = (destructor)RingBuffer_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = "RingBuffer data structure", + .tp_methods = RingBuffer_methods, + .tp_members = RingBuffer_members, +}; \ No newline at end of file diff --git a/src/stack.c b/src/stack.c new file mode 100644 index 0000000..082704d --- /dev/null +++ b/src/stack.c @@ -0,0 +1,156 @@ +#define PY_SSIZE_T_CLEAN +#include +#include + +#include "../include/stack.h" + +static PyObject * +Stack_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + Stack *self; + + self = (Stack *)type->tp_alloc(type, 0); + if (self != NULL) + { + self->capacity = 0; + self->size = 0; + self->top = -1; + self->items = NULL; + } + + return (PyObject *)self; +} + +static int Stack_init(Stack *self, PyObject *args) +{ + PyObject *capacity = NULL; + + // get capacity from args + if (!PyArg_ParseTuple(args, "O", &capacity)) + return -1; + + if (!PyLong_Check(capacity)) + { + PyErr_SetString(PyExc_TypeError, "[TypeError] Capacity must be an integer"); + return -1; + } + + self->capacity = PyLong_AsLong(capacity); + self->items = (PyObject **)PyMem_Malloc(self->capacity * sizeof(PyObject *)); + + if (self->items == NULL) + { + PyErr_SetString(PyExc_MemoryError, "Out of memory"); + return -1; + } + + return 0; +} + +static void Stack_dealloc(Stack *self) +{ + Py_XDECREF(self->items); + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static PyObject *Stack_push(Stack *self, PyObject *args) +{ + PyObject *item = NULL; + + if (!PyArg_ParseTuple(args, "O", &item)) + return NULL; + Py_IncRef(item); + + if (self->top == self->capacity - 1) + { + PyErr_SetString(PyExc_IndexError, "Stack is full"); + return NULL; + } + + self->top++; + self->items[self->top] = item; + self->size++; + + Py_RETURN_NONE; +} + +static PyObject *Stack_pop(Stack *self) +{ + if (self->top == -1) + { + PyErr_SetString(PyExc_IndexError, "Stack is empty"); + return NULL; + } + + PyObject *item = self->items[self->top]; + self->top--; + self->size--; + + return item; +} + +static PyObject *Stack_peek(Stack const *self) +{ + if (self->top == -1) + { + PyErr_SetString(PyExc_IndexError, "Stack is empty"); + return NULL; + } + + return self->items[self->top]; +} + +static PyObject *Stack_is_empty(Stack const *self) +{ + if (self->top == -1) + Py_RETURN_TRUE; + + Py_RETURN_FALSE; +} + +static PyObject *Stack_is_full(Stack const *self) +{ + if (self->top == self->capacity - 1) + Py_RETURN_TRUE; + + Py_RETURN_FALSE; +} + +static PyObject *Stack_size(Stack const *self) +{ + return PyLong_FromLong(self->size); +} + +static PyObject *Stack_capacity(Stack const *self) +{ + return PyLong_FromLong(self->capacity); +} + +static PyMemberDef Stack_members[] = { + {NULL}, +}; + +static PyMethodDef Stack_methods[] = { + {"push", (PyCFunction)Stack_push, METH_VARARGS, "Push an item to the stack"}, + {"pop", (PyCFunction)Stack_pop, METH_NOARGS, "Pop an item from the stack"}, + {"peek", (PyCFunction)Stack_peek, METH_NOARGS, "Peek the top item from the stack"}, + {"is_empty", (PyCFunction)Stack_is_empty, METH_NOARGS, "Check if the stack is empty"}, + {"is_full", (PyCFunction)Stack_is_full, METH_NOARGS, "Check if the stack is full"}, + {"size", (PyCFunction)Stack_size, METH_NOARGS, "Get the size of the stack"}, + {"capacity", (PyCFunction)Stack_capacity, METH_NOARGS, "Get the capacity of the stack"}, + {NULL}, +}; + +PyTypeObject StackType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "Stack", + .tp_basicsize = sizeof(Stack), + .tp_itemsize = 0, + .tp_new = Stack_new, + .tp_init = (initproc)Stack_init, + .tp_dealloc = (destructor)Stack_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = "Stack data structure", + .tp_methods = Stack_methods, + .tp_members = Stack_members, +}; \ No newline at end of file diff --git a/src/structura.c b/src/structura.c new file mode 100644 index 0000000..cd6156b --- /dev/null +++ b/src/structura.c @@ -0,0 +1,68 @@ +#define PY_SSIZE_T_CLEAN +#include + +extern PyTypeObject RingBufferType; +extern PyTypeObject StackType; + +static PyObject * +method_fputs(PyObject *self, PyObject *args) +{ + char *str = NULL; + char *filename = NULL; + + long bytes_copied = -1; + + /* Parse arguments */ + if (!PyArg_ParseTuple(args, "ss", &str, &filename)) + return NULL; + + FILE *fp = fopen(filename, "w"); + + if (fp == NULL) + { + PyErr_SetString(PyExc_IOError, "[Structura] Could not open file"); + return NULL; + } + + bytes_copied = fputs(str, fp); + fclose(fp); + + return PyLong_FromLong(bytes_copied); +} + +static PyMethodDef FputsMethods[] = { + {"fputs", method_fputs, METH_VARARGS, "Python interface for fputs C library function"}, + {NULL, NULL, 0, NULL}, +}; + +static struct PyModuleDef structuramodule = { + PyModuleDef_HEAD_INIT, + "structura", + "Python interface for the fputs C library function", + -1, + FputsMethods, +}; + +PyMODINIT_FUNC PyInit_structura(void) +{ + PyObject *module = PyModule_Create(&structuramodule); + + /* Add int constant by name */ + PyModule_AddIntConstant(module, "STRUCTURA_FLAG", 1024); + + /* Add RingBuffer type */ + if (PyType_Ready(&RingBufferType) < 0) + return NULL; + + Py_INCREF(&RingBufferType); + PyModule_AddObject(module, "RingBuffer", (PyObject *)&RingBufferType); + + /* Add Stack type */ + if (PyType_Ready(&StackType) < 0) + return NULL; + + Py_INCREF(&StackType); + PyModule_AddObject(module, "Stack", (PyObject *)&StackType); + + return module; +} \ No newline at end of file diff --git a/test/benchmark.py b/test/benchmark.py new file mode 100644 index 0000000..db00685 --- /dev/null +++ b/test/benchmark.py @@ -0,0 +1,73 @@ +from collections import deque +from structura import RingBuffer + + +# python ringbuffer implementation +class RingBuffer: + def __init__(self, size): + self.size = size + self.data = [None] * size + self.head = 0 + self.tail = 0 + + def enqueue(self, x): + self.data[self.head] = x + self.head = (self.head + 1) % self.size + if self.head == self.tail: + self.tail = (self.tail + 1) % self.size + + def dequeue(self): + x = self.data[self.tail] + self.tail = (self.tail + 1) % self.size + return x + + def size(self): + return self.size + + def peek(self): + return self.data[self.tail] + + def __getitem__(self, i): + return self.data[(self.tail + i) % self.size] + + def __setitem__(self, i, x): + self.data[(self.tail + i) % self.size] = x + + def __iter__(self): + for i in range(len(self)): + yield self[i] + + +def native(): + # generate a speed test for pyhon ringbuffer + rb = RingBuffer(10000000) + for i in range(10000000): + rb.enqueue(f'qwerqwer[{i}]]') + for i in range(10000000): + rb.dequeue() + + +def external(): + # generate a speed test for structura ringbuffer + rb = RingBuffer(10000000) + for i in range(10000000): + rb.enqueue(f'qwerqwer[{i}]]') + for i in range(10000000): + rb.dequeue() + + +def builtin(): + # generate a speed test for python deque + rb = deque(maxlen=10000000) + for i in range(10000000): + rb.append(f'qwerqwer[{i}]]') + for i in range(10000000): + rb.popleft() + + +if __name__ == '__main__': + # run the speed test + import timeit + print(timeit.timeit(native, number=1)) + print(timeit.timeit(external, number=1)) + print(timeit.timeit(builtin, number=1)) diff --git a/test/test-ringbuffer.py b/test/test-ringbuffer.py new file mode 100644 index 0000000..8bae95e --- /dev/null +++ b/test/test-ringbuffer.py @@ -0,0 +1,61 @@ +import unittest +from structura import RingBuffer + + +class TestRingBuffer(unittest.TestCase): + def test_ringbuffer_enqueue(self): + rb = RingBuffer(100) + for i in range(1000): + rb.enqueue(f'qwerqwer[{i}]]') + self.assertEqual(rb.size(), 100) + + def test_ringbuffer_peek(self): + rb = RingBuffer(32) + for i in range(32): + rb.enqueue(f'qwerqwer[{i}]]') + self.assertEqual(rb.peek(), 'qwerqwer[0]]') + + def test_ringbuffer_dequeue(self): + rb = RingBuffer(32) + for i in range(32): + rb.enqueue(f'qwerqwer[{i}]]') + self.assertEqual(rb.dequeue(), 'qwerqwer[0]]') + self.assertEqual(rb.size(), 31) + + def test_ringbuffer_is_empty(self): + rb = RingBuffer(32) + self.assertTrue(rb.is_empty()) + rb.enqueue('qwerqwer') + self.assertFalse(rb.is_empty()) + rb.dequeue() + self.assertTrue(rb.is_empty()) + + def test_ringbuffer_is_full(self): + rb = RingBuffer(32) + self.assertFalse(rb.is_full()) + for i in range(32): + rb.enqueue(f'qwerqwer[{i}]]') + self.assertTrue(rb.is_full()) + rb.dequeue() + self.assertFalse(rb.is_full()) + + def test_ringbuffer_size(self): + rb = RingBuffer(32) + self.assertEqual(rb.size(), 0) + for i in range(32): + rb.enqueue(f'qwerqwer[{i}]]') + self.assertEqual(rb.size(), 32) + rb.dequeue() + self.assertEqual(rb.size(), 31) + + def test_ringbuffer_dequeue_when_empty(self): + rb = RingBuffer(32) + self.assertEqual(rb.dequeue(), None) + + def test_ringbuffer_peek_when_empty(self): + rb = RingBuffer(32) + self.assertEqual(rb.peek(), None) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test-stack.py b/test/test-stack.py new file mode 100644 index 0000000..0e25bc5 --- /dev/null +++ b/test/test-stack.py @@ -0,0 +1,67 @@ +import unittest +from structura import Stack + + +class TestStack(unittest.TestCase): + def test_stack_push(self): + s = Stack(1000) + for i in range(1000): + s.push(f'qwerqwer[{i}]]') + self.assertEqual(s.size(), 1000) + + def test_stack_peek(self): + s = Stack(32) + for i in range(32): + s.push(f'qwerqwer[{i}]]') + self.assertEqual(s.peek(), 'qwerqwer[31]]') + + def test_stack_pop(self): + s = Stack(32) + for i in range(32): + s.push(f'qwerqwer[{i}]]') + self.assertEqual(s.pop(), 'qwerqwer[31]]') + self.assertEqual(s.size(), 31) + + def test_stack_is_empty(self): + s = Stack(32) + self.assertTrue(s.is_empty()) + s.push('qwerqwer') + self.assertFalse(s.is_empty()) + s.pop() + self.assertTrue(s.is_empty()) + + def test_stack_is_full(self): + s = Stack(32) + self.assertFalse(s.is_full()) + for i in range(32): + s.push(f'qwerqwer[{i}]]') + self.assertTrue(s.is_full()) + s.pop() + self.assertFalse(s.is_full()) + + def test_stack_size(self): + s = Stack(32) + self.assertEqual(s.size(), 0) + for i in range(32): + s.push(f'qwerqwer[{i}]]') + self.assertEqual(s.size(), 32) + s.pop() + self.assertEqual(s.size(), 31) + + def test_stack_pop_when_empty(self): + s = Stack(32) + self.assertRaises(IndexError, s.pop) + + def test_stack_peek_when_empty(self): + s = Stack(32) + self.assertRaises(IndexError, s.peek) + + def test_stack_push_when_full(self): + s = Stack(32) + for i in range(32): + s.push(f'qwerqwer[{i}]]') + self.assertRaises(IndexError, s.push, 'qwerqwer') + + +if __name__ == '__main__': + unittest.main()