Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
labels:
- "ci"
64 changes: 50 additions & 14 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
name: Build and Test Package
on: [push, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build-sdist:
name: Build Source Dist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
- name: Set Up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Install Tools
Expand All @@ -18,11 +21,11 @@ jobs:
- name: Source Packaging
run: |
python -m build --sdist
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: sdist
path: 'dist/spotfire-*.tar.gz'
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: test-files
path: |
Expand Down Expand Up @@ -50,12 +53,12 @@ jobs:
operating-system: ['ubuntu-latest', 'windows-latest']
fail-fast: false
steps:
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
name: sdist
path: dist
- name: Set Up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install Build Requirements
Expand All @@ -81,7 +84,7 @@ jobs:
python -m build --wheel
# Move wheel out of build dir into top-level dist dir
mv dist\*.whl ..\dist
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: wheel-${{ matrix.python-version }}-${{ matrix.operating-system }}
path: 'dist/spotfire-*.whl'
Expand All @@ -96,16 +99,16 @@ jobs:
test-environment: ${{ fromJson(needs.build-sdist.outputs.test-environments) }}
fail-fast: false
steps:
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
name: wheel-${{ matrix.python-version }}-${{ matrix.operating-system }}
path: dist
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
name: test-files
path: test-files
- name: Set Up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install Dependencies (Linux)
Expand All @@ -122,7 +125,7 @@ jobs:
env:
TEST_FILES_DIR: ${{ github.workspace }}/test-files/spotfire/test/files
TEST_ENVIRONMENT: ${{ matrix.test-environment }}
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
if: ${{ always() }}
with:
name: test-results-${{ matrix.python-version }}-${{ matrix.operating-system }}-${{ matrix.test-environment }}
Expand All @@ -138,14 +141,14 @@ jobs:
echo -n "python-version=" >> $GITHUB_OUTPUT
echo '${{ needs.build-sdist.outputs.python-versions }}' | sed -e 's/[^"]*"//' -e 's/".*//' >> $GITHUB_OUTPUT
- name: Set Up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ steps.version.outputs.python-version }}
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
name: sdist
path: dist
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
name: wheel-${{ steps.version.outputs.python-version }}-ubuntu-latest
path: dist
Expand All @@ -163,3 +166,36 @@ jobs:
mypy spotfire
cython-lint spotfire vendor
find spotfire -name '*_helpers.[ch]' | xargs cpplint --repository=.
sanitizers:
name: AddressSanitizer + UBSan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
submodules: recursive
- uses: actions/setup-python@v6
with:
python-version: '3.13'
- name: Install dependencies
run: |
pip install setuptools Cython "numpy>=2.0.0rc1"
pip install ".[polars]"
pip install html-testRunner polars pillow
- name: Rebuild extension with AddressSanitizer + UBSan
env:
CFLAGS: "-fsanitize=address,undefined -fno-omit-frame-pointer -g -D_FORTIFY_SOURCE=2 -fstack-protector-strong"
LDFLAGS: "-fsanitize=address,undefined"
run: python setup.py build_ext --inplace
- name: Run tests under AddressSanitizer + UBSan
run: |
LIBASAN=$(gcc -print-file-name=libasan.so)
LD_PRELOAD="$LIBASAN" PYTHONMALLOC=malloc python -m spotfire.test
env:
ASAN_OPTIONS: "detect_leaks=0:allocator_may_return_null=1:intercept_cxx_exceptions=0"
UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1"
TEST_ENVIRONMENT: asan
- uses: actions/upload-artifact@v7
if: always()
with:
name: test-results-sanitizers
path: build/test-results/*.html
4 changes: 2 additions & 2 deletions .github/workflows/pylint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ jobs:
name: Check Linters
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
- name: Set Up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Install Tools
Expand Down
22 changes: 11 additions & 11 deletions .github/workflows/sbom.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
outputs:
python-versions: ${{ steps.dynamic.outputs.pythons }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Read python-versions
id: dynamic
run: |
Expand All @@ -48,14 +48,14 @@ jobs:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive # needed for vendor/sbdf-c when building/installing sdist

# workflow_run: reuse artifact from build.yaml — no rebuild
- name: Download sdist (from workflow_run)
if: github.event_name == 'workflow_run'
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: sdist
path: dist
Expand All @@ -64,7 +64,7 @@ jobs:

# push / release / workflow_dispatch: build fresh
- name: Set Up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Build sdist
Expand Down Expand Up @@ -118,7 +118,7 @@ jobs:
--tool "trivy-${{ env.TRIVY_VERSION }}"

- name: Upload SBOM artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: sbom-sdist
path: spotfire-sdist.sbom.spdx.json
Expand All @@ -133,14 +133,14 @@ jobs:
python-version: ${{ fromJson(needs.setup.outputs.python-versions) }}
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive # needed for vendor/sbdf-c when building wheel fresh

# workflow_run: reuse the ubuntu wheel artifact from build.yaml — no rebuild
- name: Download wheel (from workflow_run)
if: github.event_name == 'workflow_run'
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: wheel-${{ matrix.python-version }}-ubuntu-latest
path: dist
Expand All @@ -150,7 +150,7 @@ jobs:
# Also download the sdist so scan-env can install from it (wheel is platform-specific)
- name: Download sdist (from workflow_run)
if: github.event_name == 'workflow_run'
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: sdist
path: dist
Expand All @@ -160,7 +160,7 @@ jobs:
# push / release / workflow_dispatch: build fresh on Linux
- name: Set Up Python
if: github.event_name != 'workflow_run'
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Build wheel
Expand Down Expand Up @@ -221,7 +221,7 @@ jobs:
--tool "trivy-${{ env.TRIVY_VERSION }}"

- name: Upload SBOM artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: sbom-wheel-${{ matrix.python-version }}
path: spotfire-wheel-${{ matrix.python-version }}.sbom.spdx.json
Expand All @@ -234,7 +234,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Download all SBOM artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
pattern: sbom-*
path: all-sboms
Expand Down
19 changes: 16 additions & 3 deletions spotfire/test/test_sbdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,19 @@
import pandas as pd
import pandas.testing as pdtest
import numpy as np
import geopandas as gpd
import matplotlib.pyplot
import seaborn
try:
import geopandas as gpd # type: ignore[import-not-found]
except ImportError:
gpd = None # type: ignore[assignment]
try:
import matplotlib # type: ignore[import-not-found]
import matplotlib.pyplot
except ImportError:
matplotlib = None # type: ignore[assignment]
try:
import seaborn # type: ignore[import-not-found]
except ImportError:
seaborn = None # type: ignore[assignment]
import PIL.Image
from packaging import version

Expand Down Expand Up @@ -137,6 +147,7 @@ def test_read_10001(self):
self.assertEqual(dataframe.at[10000, "String"], "kiwis")
self.assertEqual(dataframe.at[10000, "Binary"], b"\x7c\x7d\x7e\x7f")

@unittest.skipIf(gpd is None, "geopandas not installed")
def test_read_write_geodata(self):
"""Test that geo-encoded data is properly converted to/from ``GeoDataFrame``."""
gdf = sbdf.import_data(utils.get_test_data_file("sbdf/NACountries.sbdf"))
Expand Down Expand Up @@ -468,6 +479,7 @@ def test_numpy_timedelta_resolution(self):
val = df2.at[1, 'x']
self.assertEqual(val, target)

@unittest.skipIf(matplotlib is None, "matplotlib not installed")
def test_image_matplot(self):
"""Verify Matplotlib figures export properly."""
matplotlib.pyplot.clf()
Expand All @@ -480,6 +492,7 @@ def test_image_matplot(self):
else:
self.fail(f"Expected PNG bytes, got {type(image)}: {image!r}")

@unittest.skipIf(seaborn is None, "seaborn not installed")
def test_image_seaborn(self):
"""Verify Seaborn grids export properly."""
matplotlib.pyplot.clf()
Expand Down