diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b8b29689..ed79bb5b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -175,3 +175,29 @@ jobs: with: name: ${{ github.job }}.zip path: contrib/android/libs + + sanitizers: + + name: Sanitizers + + runs-on: ubuntu-latest + + env: + CC: clang + CFLAGS: "-fsanitize=address,undefined -fno-omit-frame-pointer -g -O1" + LDFLAGS: "-fsanitize=address,undefined" + ASAN_OPTIONS: detect_leaks=1:abort_on_error=1 + UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1 + + steps: + - uses: actions/checkout@v6 + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install libbluetooth-dev libusb-1.0-0-dev + - run: autoreconf --install --force + - run: ./configure + - run: make + - run: make distcheck + - name: Run parse fixtures + run: test/run-parse.sh diff --git a/Makefile.am b/Makefile.am index 37986117..bc07dc91 100644 --- a/Makefile.am +++ b/Makefile.am @@ -20,4 +20,9 @@ EXTRA_DIST = \ contrib/android/Android.mk \ contrib/msvc/libdivecomputer.vcxproj \ contrib/msvc/libdivecomputer.vcxproj.filters \ - contrib/udev/libdivecomputer.rules + contrib/udev/libdivecomputer.rules \ + test/run-parse.sh \ + test/fixtures/manifest.txt \ + test/fixtures/shearwater_petrel2-0001.bin \ + test/fixtures/hw_ostc5-0001.bin \ + test/fixtures/garmin_descent_mk1-0001.bin diff --git a/test/fixtures/garmin_descent_mk1-0001.bin b/test/fixtures/garmin_descent_mk1-0001.bin new file mode 100644 index 00000000..fb7f5b69 Binary files /dev/null and b/test/fixtures/garmin_descent_mk1-0001.bin differ diff --git a/test/fixtures/hw_ostc5-0001.bin b/test/fixtures/hw_ostc5-0001.bin new file mode 100644 index 00000000..6181ce58 Binary files /dev/null and b/test/fixtures/hw_ostc5-0001.bin differ diff --git a/test/fixtures/manifest.txt b/test/fixtures/manifest.txt new file mode 100644 index 00000000..ed0e83ee --- /dev/null +++ b/test/fixtures/manifest.txt @@ -0,0 +1,8 @@ +# Parse fixture manifest +# Format: filename|vendor|product +# Used by test/run-parse.sh to verify each blob with the correct descriptor. +# All dives are the owner's own recordings, cleared for redistribution under +# the library licence. No GPS or personal location data is present. +shearwater_petrel2-0001.bin|Shearwater|Petrel 2 +hw_ostc5-0001.bin|Heinrichs Weikamp|OSTC 5 +garmin_descent_mk1-0001.bin|Garmin|Descent Mk1 diff --git a/test/fixtures/shearwater_petrel2-0001.bin b/test/fixtures/shearwater_petrel2-0001.bin new file mode 100644 index 00000000..31ac7b53 Binary files /dev/null and b/test/fixtures/shearwater_petrel2-0001.bin differ diff --git a/test/run-parse.sh b/test/run-parse.sh new file mode 100755 index 00000000..6d8282bf --- /dev/null +++ b/test/run-parse.sh @@ -0,0 +1,82 @@ +#!/bin/sh +# AI-generated (Claude) +# +# run-parse.sh - Parse fixture harness for libdivecomputer +# +# Reads test/fixtures/manifest.txt, runs 'dctool parse' over each raw blob +# using the exact model descriptor recorded in the manifest, and exits +# non-zero if any invocation fails. +# +# When run under an ASAN/UBSAN build (as wired into the 'sanitizers' CI job) +# the sanitizer environment variables cause any detected error to abort the +# process, which surfaces here as a non-zero exit from dctool. +# +# Usage: test/run-parse.sh [path-to-dctool] +# If no path is given the script searches for 'dctool' under examples/. +# +# Copyright (C) 2024 The libdivecomputer contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. + +set -e + +# Locate the source root (the directory containing this script's parent). +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +FIXTURE_DIR="$SCRIPT_DIR/fixtures" +MANIFEST="$FIXTURE_DIR/manifest.txt" + +# Locate dctool. +if [ -n "$1" ]; then + DCTOOL="$1" +elif [ -x "$ROOT_DIR/examples/dctool" ]; then + DCTOOL="$ROOT_DIR/examples/dctool" +elif [ -x "$ROOT_DIR/examples/.libs/dctool" ]; then + DCTOOL="$ROOT_DIR/examples/.libs/dctool" +else + echo "ERROR: cannot find dctool under examples/. Build the project first or pass its path as an argument." >&2 + exit 1 +fi + +echo "Using dctool: $DCTOOL" + +if [ ! -f "$MANIFEST" ]; then + echo "ERROR: manifest not found: $MANIFEST" >&2 + exit 1 +fi + +FAILURES=0 + +while IFS='|' read -r filename vendor product; do + # Skip blank lines and comments. + case "$filename" in + ''|\#*) continue ;; + esac + + blob="$FIXTURE_DIR/$filename" + descriptor="$vendor $product" + + if [ ! -f "$blob" ]; then + echo "ERROR: fixture not found: $blob" >&2 + FAILURES=$((FAILURES + 1)) + continue + fi + + echo "Parsing $filename with descriptor '$descriptor' ..." + if "$DCTOOL" -d "$descriptor" parse -o /dev/null "$blob"; then + echo " OK" + else + echo " FAILED (exit $?)" >&2 + FAILURES=$((FAILURES + 1)) + fi +done < "$MANIFEST" + +if [ "$FAILURES" -ne 0 ]; then + echo "ERROR: $FAILURES fixture(s) failed to parse." >&2 + exit 1 +fi + +echo "All fixtures parsed successfully."