diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml new file mode 100644 index 0000000..c01b25a --- /dev/null +++ b/.github/workflows/cpp.yml @@ -0,0 +1,84 @@ +name: C++ CI + +on: + push: + branches: [main] + paths: + - "cpp/**" + pull_request: + branches: [main] + paths: + - "cpp/**" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + compiler: [gcc, clang] + include: + - compiler: gcc + cc: gcc + cxx: g++ + - compiler: clang + cc: clang + cxx: clang++ + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install build dependencies + run: | + sudo apt-get update + sudo apt-get install -y cmake + pip install pybind11 + + - name: Build C++ extension + env: + CC: ${{ matrix.cc }} + CXX: ${{ matrix.cxx }} + run: | + cd cpp + mkdir -p build && cd build + cmake .. -Dpybind11_DIR="$(python3 -c 'import pybind11; print(pybind11.get_cmake_dir())')" + cmake --build . --config Release -j "$(nproc)" + + - name: Verify module built + run: ls -la cpp/build/fast_resize*.so + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install clang-tidy + run: | + sudo apt-get update + sudo apt-get install -y clang-tidy + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install pybind11 + run: pip install pybind11 + + - name: Generate compile_commands.json + run: | + cd cpp + mkdir -p build && cd build + cmake .. \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ + -Dpybind11_DIR="$(python3 -c 'import pybind11; print(pybind11.get_cmake_dir())')" + + - name: Run clang-tidy + run: | + cd cpp + clang-tidy -p build fast_resize.cpp \ + --checks='-*,readability-*,bugprone-*,performance-*,modernize-*,-modernize-use-trailing-return-type' \ + --warnings-as-errors='bugprone-*' diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 0000000..d11b373 --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,60 @@ +name: Python CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + lint: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.11", "3.12"] + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt -r requirements-dev.txt + + - name: Ruff lint + run: ruff check src/ tests/ + + - name: Ruff format check + run: ruff format --check src/ tests/ + + - name: Mypy type check + run: mypy src/ + + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.11", "3.12"] + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt -r requirements-dev.txt + pip install -e . + + - name: Run tests + run: pytest -v --tb=short + + - name: Run tests with coverage + run: pytest --cov=src --cov-report=term-missing --cov-fail-under=75 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..efd933d --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,109 @@ +stages: + - lint + - test + - build + +# --------------------------------------------------------------------------- +# Python jobs +# --------------------------------------------------------------------------- + +.python_setup: &python_setup + before_script: + - python -m pip install --upgrade pip + - pip install -r requirements.txt -r requirements-dev.txt + +python-lint:3.11: + stage: lint + image: python:3.11 + <<: *python_setup + script: + - ruff check src/ tests/ + - ruff format --check src/ tests/ + - mypy src/ + +python-lint:3.12: + stage: lint + image: python:3.12 + <<: *python_setup + script: + - ruff check src/ tests/ + - ruff format --check src/ tests/ + - mypy src/ + +python-test:3.11: + stage: test + image: python:3.11 + <<: *python_setup + script: + - pip install -e . + - pytest -v --tb=short + - pytest --cov=src --cov-report=term-missing --cov-fail-under=75 + +python-test:3.12: + stage: test + image: python:3.12 + <<: *python_setup + script: + - pip install -e . + - pytest -v --tb=short + - pytest --cov=src --cov-report=term-missing --cov-fail-under=75 + +# --------------------------------------------------------------------------- +# C++ jobs +# --------------------------------------------------------------------------- + +.cpp_rules: + rules: + - changes: + - cpp/**/* + +.cpp_setup: &cpp_setup + before_script: + - apt-get update && apt-get install -y cmake g++ clang clang-tidy + - pip install pybind11 + +cpp-build-gcc: + stage: build + image: python:3.12 + extends: .cpp_rules + <<: *cpp_setup + variables: + CC: gcc + CXX: g++ + script: + - cd cpp + - mkdir -p build && cd build + - cmake .. -Dpybind11_DIR="$(python3 -c 'import pybind11; print(pybind11.get_cmake_dir())')" + - cmake --build . --config Release -j "$(nproc)" + - ls -la fast_resize*.so + +cpp-build-clang: + stage: build + image: python:3.12 + extends: .cpp_rules + <<: *cpp_setup + variables: + CC: clang + CXX: clang++ + script: + - cd cpp + - mkdir -p build && cd build + - cmake .. -Dpybind11_DIR="$(python3 -c 'import pybind11; print(pybind11.get_cmake_dir())')" + - cmake --build . --config Release -j "$(nproc)" + - ls -la fast_resize*.so + +cpp-lint: + stage: lint + image: python:3.12 + extends: .cpp_rules + <<: *cpp_setup + script: + - cd cpp + - mkdir -p build && cd build + - cmake .. + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + -Dpybind11_DIR="$(python3 -c 'import pybind11; print(pybind11.get_cmake_dir())')" + - cd .. + - clang-tidy -p build fast_resize.cpp + --checks='-*,readability-*,bugprone-*,performance-*,modernize-*,-modernize-use-trailing-return-type' + --warnings-as-errors='bugprone-*' diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d7f49b..95bf52a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.1.0] - 2026-03-28 + +### Added + +- GitLab CI pipeline (`.gitlab-ci.yml`) mirroring the GitHub Actions setup: Python + lint/test on 3.11 and 3.12, C++ build with GCC/Clang, and clang-tidy lint. +- GitHub Actions CI workflow for Python: ruff lint/format, mypy type checking, and pytest + with coverage on Python 3.11 and 3.12. +- GitHub Actions CI workflow for C++: build with GCC and Clang, clang-tidy lint + (triggered on `cpp/` path changes). +- `pytest-cov` added to dev dependencies for coverage reporting. + +### Fixed + +- Integer overflow in `fast_resize.cpp` `bilinear_resize` buffer size check: cast to + `size_t` before multiplication instead of after. + ## [1.0.2] - 2026-03-28 ### Fixed diff --git a/PROJECT_DESCRIPTION.md b/PROJECT_DESCRIPTION.md index b96f596..fd2d7f7 100644 --- a/PROJECT_DESCRIPTION.md +++ b/PROJECT_DESCRIPTION.md @@ -13,7 +13,7 @@ The system is built according to strict **Clean Architecture** principles with f ``` ┌─────────────────────────────────────────────────────────────────┐ │ Presentation Layer (FastAPI routes, Pydantic schemas, │ -│ middleware, dependency injection) │ +│ middleware, dependency injection) │ ├─────────────────────────────────────────────────────────────────┤ │ Application Layer (use cases, DTOs — orchestration logic) │ ├─────────────────────────────────────────────────────────────────┤ diff --git a/cpp/fast_resize.cpp b/cpp/fast_resize.cpp index f361c59..b311256 100644 --- a/cpp/fast_resize.cpp +++ b/cpp/fast_resize.cpp @@ -46,7 +46,7 @@ std::vector bilinear_resize( if (src_w <= 0 || src_h <= 0 || dst_w <= 0 || dst_h <= 0) { throw std::invalid_argument("dimensions must be positive"); } - if (static_cast(src_w * src_h * channels) != src.size()) { + if (static_cast(src_w) * src_h * channels != src.size()) { throw std::invalid_argument("src buffer size mismatch"); } diff --git a/pyproject.toml b/pyproject.toml index 954e196..befbc2e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "image-processing-service" -version = "1.0.2" +version = "1.1.0" description = "High-performance image processing microservice with Clean Architecture" requires-python = ">=3.11" dependencies = [ @@ -19,6 +19,7 @@ dependencies = [ dev = [ "pytest>=8.0,<9", "pytest-asyncio>=0.24,<1", + "pytest-cov>=6.0,<8", "httpx>=0.28,<1", "aiosqlite>=0.20,<1", "ruff>=0.8,<1", diff --git a/requirements-dev.txt b/requirements-dev.txt index 6ff3ac8..e5314cf 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,6 @@ pytest>=8.0,<9 pytest-asyncio>=0.24,<1 +pytest-cov>=6.0,<8 httpx>=0.28,<1 aiosqlite>=0.20,<1 ruff>=0.8,<1