Repository navigation
Native lookup_by_key raises without an index, as the JVM build does #213
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Test Python Examples | |
| on: | |
| push: | |
| branches: [main] | |
| paths-ignore: | |
| # Changes that cannot alter what these jobs build or run. README.md and | |
| # tests/README.md are deliberately NOT here: the wheel build copies README.md | |
| # (scripts/Dockerfile.build, pyproject `readme`) and test_no_monorepo_wording.py | |
| # reads both. docs/** is safe to skip while tests/conftest.py collect-ignores | |
| # test_docs_examples.py (it needs docs/index.md); if that Markdown site comes | |
| # back, take docs/** out of this list. Schedule, dispatch and workflow_call | |
| # ignore these filters and always run everything. | |
| - "docs/**" | |
| - "**/*.adoc" | |
| - "CLAUDE.md" | |
| - "examples/README.md" | |
| - "LICENSE*" | |
| # the native build has its own workflows; nothing under native/ is in the JVM wheel | |
| - "native/**" | |
| - ".github/workflows/test-native.yml" | |
| - ".github/workflows/release-native.yml" | |
| pull_request: | |
| # ready_for_review: draft PRs skip the platform matrices (see the job conditions), | |
| # so marking one ready is what starts them. | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| paths-ignore: | |
| # Same list as under push. | |
| - "docs/**" | |
| - "**/*.adoc" | |
| - "CLAUDE.md" | |
| - "examples/README.md" | |
| - "LICENSE*" | |
| # the native build has its own workflows; nothing under native/ is in the JVM wheel | |
| - "native/**" | |
| - ".github/workflows/test-native.yml" | |
| - ".github/workflows/release-native.yml" | |
| # A new upstream snapshot is published as arcadedata/arcadedb:latest without | |
| # any change here, so build and test against it every night as well. The tag | |
| # moves (ARCADEDB_IMAGE_TAG defaults to latest), so a night with no commit | |
| # here still tests a new engine; that is why there is no skip-if-main-unchanged | |
| # guard. One nightly per workflow, staggered an hour apart. | |
| schedule: | |
| - cron: "0 4 * * *" | |
| # Allow being called by other workflows (release-jvm.yml) | |
| workflow_call: | |
| inputs: | |
| arcadedb-tag: | |
| description: "arcadedata/arcadedb image tag to take the jars from (e.g. 26.10.2); default latest" | |
| required: false | |
| type: string | |
| default: "latest" | |
| examples: | |
| description: "Glob pattern(s) for examples to run (space-separated, relative to examples)." | |
| required: false | |
| type: string | |
| default: "0[1-9]_*.py 1[0-9]_*.py 2[0-9]_*.py" | |
| build-version: | |
| description: "Override package version (PEP 440) for build.sh" | |
| required: false | |
| type: string | |
| # Allow manual trigger | |
| workflow_dispatch: | |
| inputs: | |
| arcadedb-tag: | |
| description: "arcadedata/arcadedb image tag to take the jars from (e.g. 26.10.2); default latest" | |
| required: false | |
| default: "latest" | |
| examples: | |
| description: "Glob pattern(s) for examples to run (space-separated, relative to examples)." | |
| required: false | |
| default: "0[1-9]_*.py 1[0-9]_*.py 2[0-9]_*.py" | |
| # SUPERSEDE, do not race (same rule in test-jvm.yml). | |
| # | |
| # A force-push to a PR branch left the OLD run going alongside the new one: on | |
| # #6889 both 118b347ce and 75293c7a2 ran at once, and the stale one reported a | |
| # failure that the newer commit had already fixed. Anyone reading the PR sees a | |
| # red check against work that is done. The stale run also holds macOS/Windows | |
| # runners the organisation shares with ArcadeData/arcadedb. | |
| # | |
| # cancel-in-progress is scoped to pull_request ONLY. On a push to a long-lived | |
| # branch a superseded run is often the one you still want -- it is the record | |
| # for that commit -- so those are left to finish, and they get a group of their | |
| # own (run_id) so a queued push or nightly run is not replaced by a newer one | |
| # either. The group names the workflow literally: github.workflow is the | |
| # CALLER's name in a workflow_call, which would put this workflow and its | |
| # sibling in one group. | |
| concurrency: | |
| group: test-python-examples-${{ github.event.pull_request.number || github.run_id }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| # The image download-jars takes the jars from. Unset (push, pull_request, schedule) | |
| # means latest; a release build passes its tag. | |
| ARCADEDB_IMAGE_TAG: ${{ inputs.arcadedb-tag || 'latest' }} | |
| EXAMPLES: ${{ inputs.examples || '0[1-9]_*.py 1[0-9]_*.py 2[0-9]_*.py' }} | |
| # Note: Example 21 runs in CI at reduced scale; its arguments and 15-minute | |
| # limit are set in the per-example case in the "Run all examples" step below. | |
| permissions: | |
| contents: read | |
| jobs: | |
| # First job: Download ArcadeDB JARs (platform-agnostic) | |
| download-jars: | |
| name: Download ArcadeDB JARs | |
| runs-on: ubuntu-24.04 | |
| # Draft PRs skip the whole workflow; marking the PR ready starts it. | |
| if: github.event_name != 'pull_request' || github.event.pull_request.draft == false | |
| outputs: | |
| arcadedb-tag: ${{ steps.jars.outputs.arcadedb-tag }} | |
| matrix: ${{ steps.matrix.outputs.matrix }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| # The platform x Python legs of this run (scripts/select_matrix.py): a small matrix on | |
| # pull requests, the full one on every other event. The organisation shares one runner | |
| # cap with ArcadeData/arcadedb, so a pull request does not run every combination. | |
| # Done first, so a failure here costs no Docker pull. | |
| - name: Select the test matrix | |
| id: matrix | |
| shell: bash | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| run: | | |
| matrix="$(python3 scripts/select_matrix.py examples --event "$EVENT_NAME")" | |
| echo "$matrix" | |
| echo "matrix=$matrix" >> "$GITHUB_OUTPUT" | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 | |
| - name: Download JARs from ArcadeDB Docker image | |
| id: jars | |
| shell: bash | |
| run: | | |
| # Download JARs from the official Docker image: ARCADEDB_IMAGE_TAG, | |
| # `latest` (upstream's current snapshot) unless the caller pinned a tag. Every build job, Linux included, builds | |
| # from these jars (the artifact below), not from its own pull: upstream | |
| # re-pushes the snapshot tag on every deploy, so a pull per job could put | |
| # two different engine builds under one version in a single run. | |
| echo "๐ฆ Downloading JARs from arcadedata/arcadedb:${ARCADEDB_IMAGE_TAG}..." | |
| # Create output directory | |
| mkdir -p src/arcadedb/jars | |
| # Create a temporary container and copy JARs from it | |
| CONTAINER_ID=$(docker create "arcadedata/arcadedb:${ARCADEDB_IMAGE_TAG}") | |
| docker cp $CONTAINER_ID:/home/arcadedb/lib/. src/arcadedb/jars/ | |
| docker rm $CONTAINER_ID | |
| echo "โ Downloaded $(ls -1 src/arcadedb/jars/*.jar | wc -l) JARs" | |
| ARCADEDB_TAG=$(python3 scripts/extract_version.py src/arcadedb/jars --format=docker) | |
| echo "๐ ArcadeDB version: $ARCADEDB_TAG" | |
| # The build jobs pull arcadedata/arcadedb:<jar version> as their base image, | |
| # so a pinned tag must name that version exactly (26.10.2, not an alias). | |
| if [ "$ARCADEDB_IMAGE_TAG" != "latest" ] && [ "$ARCADEDB_IMAGE_TAG" != "$ARCADEDB_TAG" ]; then | |
| echo "โ arcadedb-tag '$ARCADEDB_IMAGE_TAG' holds engine $ARCADEDB_TAG; pass the exact version tag" | |
| exit 1 | |
| fi | |
| echo "arcadedb-tag=$ARCADEDB_TAG" >> "$GITHUB_OUTPUT" | |
| # Unfiltered on purpose: build-native.sh applies scripts/jar_exclusions.txt | |
| # to whatever lands in the jars directory, so the list lives in one place. | |
| - name: Upload JARs as artifact | |
| uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 | |
| with: | |
| name: arcadedb-jars-examples | |
| path: src/arcadedb/jars/*.jar | |
| retention-days: 1 | |
| # Second job: Test examples on each platform | |
| test-examples: | |
| name: Test Python Examples (${{ matrix.platform }}, Python ${{ matrix.python-version }}) | |
| runs-on: ${{ matrix.runs-on }} | |
| needs: download-jars | |
| if: github.event_name != 'pull_request' || github.event.pull_request.draft == false | |
| env: | |
| BUILD_VERSION: ${{ inputs.build-version }} | |
| # Pin the Hugging Face cache inside the workspace so it is cacheable with | |
| # one path on every platform (the default differs per OS). The examples | |
| # embed with sentence-transformers, and the full matrix is 16 jobs, so | |
| # uncached that is 16 independent model downloads per release and 16 | |
| # chances for a transient hub outage to fail a release. It has happened. | |
| HF_HOME: ${{ github.workspace }}/.hf-cache | |
| strategy: | |
| fail-fast: false | |
| # Chosen by the "Select the test matrix" step of download-jars from the event that | |
| # started the run (scripts/select_matrix.py holds the platform and Python lists): | |
| # small on pull requests, the full 4 platforms x 4 Pythons on every other event. | |
| # Every leg carries platform, runs-on and python-version, so the job names stay | |
| # "Test Python Examples (<platform>, Python <version>)". | |
| matrix: ${{ fromJSON(needs.download-jars.outputs.matrix) }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Download JARs artifact | |
| uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 | |
| with: | |
| name: arcadedb-jars-examples | |
| path: src/arcadedb/jars | |
| - name: Set up Java (for native builds on macOS/Windows) | |
| if: matrix.platform != 'linux/amd64' && matrix.platform != 'linux/arm64' | |
| uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0 | |
| with: | |
| distribution: 'corretto' | |
| java-version: '25' | |
| - name: Set up Docker Buildx (Linux only) | |
| if: matrix.platform == 'linux/amd64' || matrix.platform == 'linux/arm64' | |
| uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 | |
| - name: Set up Python | |
| uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Setup UV package manager | |
| shell: bash | |
| run: | | |
| # Retry transient network failures (a connection reset failed a whole examples job on #8897); the | |
| # script is downloaded first so a retried transfer is never piped half-written into sh. | |
| installer="$(mktemp)" | |
| curl -LsSf --retry 5 --retry-all-errors --retry-delay 5 -o "$installer" https://astral.sh/uv/install.sh | |
| sh "$installer" | |
| echo "$HOME/.local/bin" >> "$GITHUB_PATH" | |
| export PATH="$HOME/.local/bin:$PATH" | |
| uv --version | |
| - name: Install Python build dependencies | |
| shell: bash | |
| run: | | |
| uv pip install --system build wheel setuptools | |
| # Windows currently enabled, no symlink needed | |
| # - name: Create python3 symlink (Windows only) | |
| # if: matrix.platform == 'windows/amd64' || matrix.platform == 'windows/arm64' | |
| # shell: bash | |
| # run: | | |
| # PYTHON_DIR=$(dirname "$(which python)") | |
| # ln -s "$PYTHON_DIR/python.exe" "$PYTHON_DIR/python3.exe" || true | |
| # python3 --version | |
| - name: Build arcadedb (${{ matrix.platform }}) | |
| shell: bash | |
| env: | |
| # The engine version download-jars read from the ARCADEDB_IMAGE_TAG jars; | |
| # the Docker build takes its base image by this tag, the jars from the artifact | |
| ARCADEDB_TAG: ${{ needs.download-jars.outputs.arcadedb-tag }} | |
| run: | | |
| echo "๐จ Building arcadedb for ${{ matrix.platform }} with Python ${{ matrix.python-version }}..." | |
| # The jars download-jars fetched, on every platform, so all wheels of this | |
| # run carry the same engine build (and build.sh checks the integration jar) | |
| ./scripts/build.sh ${{ matrix.platform }} ${{ matrix.python-version }} src/arcadedb/jars | |
| # Note: Java is NOT required - arcadedb has bundled JRE! | |
| - name: Install ArcadeDB Python bindings | |
| shell: bash | |
| run: | | |
| uv pip install --system dist/arcadedb_jvm-*.whl | |
| - name: Install example dependencies | |
| shell: bash | |
| run: | | |
| # Install dependencies needed by examples | |
| # PyTorch is required by sentence-transformers, even on macOS | |
| # For Python 3.13+, wheels might only be on PyTorch official index | |
| if [[ "$OSTYPE" == "darwin"* ]]; then | |
| # macOS: Install CPU-only PyTorch (supports MPS acceleration) | |
| uv pip install --system torch --index-url https://download.pytorch.org/whl/cpu | |
| # numpy is not capped: the old macOS `numpy<2.0` was for Intel Macs, where PyTorch stops at 2.2 (built against | |
| # NumPy 1.x). That leg (darwin/amd64) is disabled; re-enabling it needs the cap back for that platform only. | |
| # On Apple Silicon the cap breaks pyarrow, which requires NumPy 2 (example 27). | |
| uv pip install --system numpy requests sentence-transformers pandas polars pyarrow | |
| else | |
| # Linux & Windows: Install CPU-only PyTorch to save space (avoid CUDA) | |
| uv pip install --system torch --index-url https://download.pytorch.org/whl/cpu | |
| uv pip install --system numpy requests sentence-transformers pandas polars pyarrow | |
| fi | |
| # Restored before the dataset step, which is what triggers the model | |
| # download. Keyed on the model name so bumping the model invalidates it. | |
| - name: Cache Hugging Face model | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ${{ github.workspace }}/.hf-cache | |
| key: hf-all-MiniLM-L6-v2-${{ runner.os }}-${{ runner.arch }} | |
| restore-keys: | | |
| hf-all-MiniLM-L6-v2-${{ runner.os }}- | |
| # Pre-warm with bounded retries, separately from the dataset step, so a | |
| # transient hub failure is retried here rather than surfacing mid-example | |
| # as an OSError that reads like a bindings bug. Retrying is appropriate | |
| # for this step specifically: it exercises third-party I/O, not our code. | |
| # Deliberately NOT fail-fast: if the hub is truly down the dataset step | |
| # below still runs and fails with its own message, so this cannot turn a | |
| # real breakage into a silent skip. | |
| - name: Pre-warm embedding model | |
| shell: bash | |
| continue-on-error: true | |
| run: | | |
| uv pip install --system sentence-transformers >/dev/null 2>&1 || true | |
| for attempt in 1 2 3; do | |
| if python3 -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('all-MiniLM-L6-v2'); print('model ready')"; then | |
| echo "โ model cached on attempt $attempt" | |
| exit 0 | |
| fi | |
| echo "โ ๏ธ model fetch failed (attempt $attempt/3)" | |
| [ "$attempt" -lt 3 ] && sleep $((attempt * 20)) | |
| done | |
| echo "::warning::could not pre-warm the embedding model after 3 attempts; the dataset step will retry" | |
| - name: Download datasets | |
| shell: bash | |
| run: | | |
| uv pip install --system tqdm py7zr lxml | |
| cd examples | |
| # Determine which examples are selected | |
| examples=$(ls $EXAMPLES 2>/dev/null || true) | |
| # EXIT 75 MEANS "THE DATASET HOST IS DOWN", NOT "THE BINDINGS BROKE". | |
| # | |
| # On 2026-08-29 the TLS certificate for files.grouplens.org expired and | |
| # every job here went red on BOTH this fork and upstream's main -- 11 | |
| # and 12 jobs, every OS, Python 3.10 through 3.14 -- at this step, | |
| # before a single example ran. Re-running does not help: a re-run | |
| # happens now, and the certificate is still expired. | |
| # | |
| # The cost is not the lost coverage, it is that upstream's main showed | |
| # a failing run titled with a Python bindings PR. A third party letting | |
| # a certificate lapse must not be indistinguishable from a regression | |
| # we introduced. | |
| # | |
| # So: skip the examples that need the dataset, keep running the rest, | |
| # and say plainly why. Any other non-zero exit is still a hard failure. | |
| SKIPPED_DATASETS="" | |
| fetch() { # fetch <label> <download_data.py args...> | |
| local label="$1"; shift | |
| # rc MUST be captured in the else branch. After `if cmd; then ...; fi` | |
| # with no else, the IF STATEMENT returns 0, so a `$?` read after `fi` | |
| # is the if's status and not the command's -- which silently turned | |
| # every failure into "exited 0" and would have made this skip | |
| # everything, including real ones. | |
| local rc=0 | |
| if python3 download_data.py "$@"; then | |
| return 0 | |
| else | |
| rc=$? | |
| fi | |
| if [ "$rc" -eq 75 ]; then | |
| echo "::warning title=Dataset unavailable::$label host unreachable; skipping examples that need it" | |
| SKIPPED_DATASETS="$SKIPPED_DATASETS $label" | |
| return 0 | |
| fi | |
| echo "::error title=Dataset download failed::$label exited $rc" | |
| return "$rc" | |
| } | |
| if echo "$examples" | grep -qE '(^|[[:space:]])(04_csv_import_documents|05_csv_import_graph|06_vector_search_recommendations)\.py([[:space:]]|$)'; then | |
| echo "๐ฅ Downloading MovieLens Small dataset..." | |
| fetch movielens-small movielens-small | |
| fi | |
| if echo "$examples" | grep -qE '(^|[[:space:]])(07_stackoverflow_tables_oltp|08_stackoverflow_tables_olap|09_stackoverflow_graph_oltp|10_stackoverflow_graph_olap|13_stackoverflow_hybrid_queries)\.py([[:space:]]|$)'; then | |
| echo "๐ฅ Downloading Stack Overflow Tiny dataset (no vectors)..." | |
| fetch stackoverflow-tiny-novec stackoverflow-tiny --no-vectors | |
| fi | |
| if echo "$examples" | grep -qE '(^|[[:space:]])(11_vector_index_build|12_vector_search)\.py([[:space:]]|$)'; then | |
| echo "๐ฅ Downloading Stack Overflow Tiny dataset (with vectors)..." | |
| fetch stackoverflow-tiny stackoverflow-tiny | |
| fi | |
| # HAND THE SKIP LIST TO THE NEXT STEP. | |
| # | |
| # Each step is its own shell, so SKIPPED_DATASETS dies here. Without | |
| # this the download skips politely and the example loop then runs | |
| # 04_csv_import_documents.py --dataset movielens-small against data | |
| # that was never fetched -- the job still fails, just later and with a | |
| # worse error. Caught in review on #6889. | |
| echo "SKIPPED_DATASETS=$(echo $SKIPPED_DATASETS | xargs)" >> "$GITHUB_ENV" | |
| if [ -n "$(echo $SKIPPED_DATASETS | xargs)" ]; then | |
| echo "::notice title=Datasets unavailable::skipping examples that need:$SKIPPED_DATASETS" | |
| fi | |
| - name: Install timeout command (macOS only) | |
| if: matrix.platform == 'darwin/amd64' || matrix.platform == 'darwin/arm64' | |
| shell: bash | |
| run: | | |
| # macOS doesn't have timeout command by default, use coreutils | |
| brew install coreutils | |
| - name: Set UTF-8 encoding (Windows only) | |
| if: matrix.platform == 'windows/amd64' || matrix.platform == 'windows/arm64' | |
| shell: bash | |
| run: | | |
| echo "PYTHONIOENCODING=utf-8" >> $GITHUB_ENV | |
| echo "PYTHONUTF8=1" >> $GITHUB_ENV | |
| - name: Run all examples | |
| id: run_examples | |
| shell: bash | |
| run: | | |
| cd examples | |
| echo "๐ Running Python Examples..." | |
| echo "" | |
| # Initialize counters | |
| total=0 | |
| passed=0 | |
| failed=0 | |
| skipped=0 | |
| # Create results file | |
| results_file="example-results.txt" | |
| > $results_file | |
| # Select example files by pattern(s) in EXAMPLES (space-separated globs) | |
| examples=$(ls $EXAMPLES 2>/dev/null | sort) | |
| if [ -z "$examples" ]; then | |
| echo "โ No example files found!" | |
| exit 1 | |
| fi | |
| # Detect timeout command (macOS uses gtimeout, Linux uses timeout) | |
| if command -v gtimeout &> /dev/null; then | |
| TIMEOUT_CMD="gtimeout" | |
| else | |
| TIMEOUT_CMD="timeout" | |
| fi | |
| # WHICH EXAMPLES NEED WHICH DATASET. Same grouping the download step | |
| # uses to decide what to fetch, kept in the same file so the two | |
| # cannot drift into disagreeing about what a dataset is for. | |
| needs_dataset() { # <example.py> -> prints the label, or nothing | |
| case "$1" in | |
| 04_csv_import_documents.py|05_csv_import_graph.py|06_vector_search_recommendations.py) | |
| echo "movielens-small" ;; | |
| 07_stackoverflow_tables_oltp.py|08_stackoverflow_tables_olap.py|09_stackoverflow_graph_oltp.py|10_stackoverflow_graph_olap.py|13_stackoverflow_hybrid_queries.py) | |
| echo "stackoverflow-tiny-novec" ;; | |
| 11_vector_index_build.py|12_vector_search.py) | |
| echo "stackoverflow-tiny" ;; | |
| esac | |
| } | |
| # Run each example | |
| for example in $examples; do | |
| total=$((total + 1)) | |
| # Skip, do not fail, when the dataset host was unreachable. Counted | |
| # as skipped and named, so a reader can tell "not run" from "passed". | |
| need=$(needs_dataset "$(basename "$example")") | |
| if [ -n "$need" ] && echo " ${SKIPPED_DATASETS:-} " | grep -q " $need "; then | |
| echo "SKIP $example - dataset '$need' unavailable (third-party host)" | |
| skipped=$((skipped + 1)) | |
| continue | |
| fi | |
| # Set example-specific parameters and timeout | |
| case "$example" in | |
| "21_graph_analytical_view_sql.py") | |
| example_args="--base-cities 1200 --stale-growth-cities 300 --sync-growth-cities 200 --regions 12 --batch-size 300 --sample-limit 5" | |
| example_name="$example (reduced scale for CI)" | |
| timeout_duration=900 # 15 minutes | |
| example_jvm_args="" | |
| ;; | |
| "22_numpy_bulk_io.py") | |
| example_args="--rows 50000 --points 100000 --vectors 5000" | |
| example_name="$example (reduced scale for CI)" | |
| timeout_duration=600 # 10 minutes | |
| example_jvm_args="" | |
| ;; | |
| "04_csv_import_documents.py") | |
| example_args="--dataset movielens-small --export" | |
| example_name="$example (movielens-small dataset with export)" | |
| timeout_duration=900 # 15 minutes | |
| example_jvm_args="-Xmx8g -Xms8g" | |
| ;; | |
| "05_csv_import_graph.py") | |
| example_args="--dataset movielens-small --method sql --import-jsonl ./exports/movielens_small_db.jsonl.tgz --export" | |
| example_name="$example (movielens-small dataset, embedded sql method, import/export)" | |
| timeout_duration=900 # 15 minutes | |
| example_jvm_args="" | |
| ;; | |
| "06_vector_search_recommendations.py") | |
| example_args="--import-jsonl ./exports/movielens_graph_small_db.jsonl.tgz" | |
| example_name="$example (vector search, import from JSONL)" | |
| timeout_duration=900 # 15 minutes | |
| example_jvm_args="" | |
| ;; | |
| "07_stackoverflow_tables_oltp.py") | |
| example_args="--dataset stackoverflow-tiny --db arcadedb_sql --threads 1 --transactions 1000 --batch-size 500 --mem-limit 2g --run-label ci07" | |
| example_name="$example (stackoverflow-tiny, arcadedb_sql, minimal oltp)" | |
| timeout_duration=900 | |
| example_jvm_args="" | |
| ;; | |
| "08_stackoverflow_tables_olap.py") | |
| example_args="--dataset stackoverflow-tiny --db arcadedb_sql --threads 1 --batch-size 500 --query-runs 1 --query-order fixed --mem-limit 2g --run-label ci08" | |
| example_name="$example (stackoverflow-tiny, arcadedb_sql, minimal olap)" | |
| timeout_duration=900 | |
| example_jvm_args="" | |
| ;; | |
| "09_stackoverflow_graph_oltp.py") | |
| example_args="--dataset stackoverflow-tiny --db arcadedb_cypher --threads 1 --transactions 1000 --batch-size 500 --mem-limit 2g --run-label ci09" | |
| example_name="$example (stackoverflow-tiny graph oltp, arcadedb cypher, minimal)" | |
| timeout_duration=900 | |
| example_jvm_args="" | |
| ;; | |
| "10_stackoverflow_graph_olap.py") | |
| example_args="--dataset stackoverflow-tiny --db arcadedb_cypher --threads 1 --batch-size 500 --query-runs 1 --query-order fixed --mem-limit 2g --run-label ci10" | |
| example_name="$example (stackoverflow-tiny graph olap, arcadedb cypher, minimal)" | |
| timeout_duration=900 | |
| example_jvm_args="" | |
| ;; | |
| "11_vector_index_build.py") | |
| example_args="--backend arcadedb_sql --dataset stackoverflow-tiny --threads 1 --mem-limit 2g --batch-size 500 --beam-width 100 --quantization NONE --run-label ci11_arcadedb_sql" | |
| example_name="$example (vector build, arcadedb_sql backend, minimal)" | |
| timeout_duration=1200 | |
| example_jvm_args="" | |
| ;; | |
| "12_vector_search.py") | |
| db_path=$(find ./my_test_databases -maxdepth 1 -type d -name 'backend=arcadedb_sql_dataset=stackoverflow-tiny_*run=ci11_arcadedb_sql' | head -n 1) | |
| if [ -z "$db_path" ]; then | |
| echo "โ Missing build output for 12_vector_search.py. Ensure 11_vector_index_build.py ran first." | tee -a $results_file | |
| failed=$((failed + 1)) | |
| echo "" | |
| continue | |
| fi | |
| example_args="--backend arcadedb_sql --dataset stackoverflow-tiny --db-path $db_path --ef-search-values 100 --k 10 --query-limit 100 --query-runs 1 --query-order fixed --threads 1 --mem-limit 2g --run-label ci12_arcadedb_sql" | |
| example_name="$example (vector search, arcadedb_sql backend, minimal)" | |
| timeout_duration=1200 | |
| example_jvm_args="" | |
| ;; | |
| "13_stackoverflow_hybrid_queries.py") | |
| example_args="--dataset stackoverflow-tiny --batch-size 500 --encode-batch-size 64 --top-k 5 --candidate-limit 100 --min-reputation 100 --infer-sample-limit 5000 --run-label ci13" | |
| example_name="$example (hybrid pipeline, tiny + minimal limits)" | |
| timeout_duration=1800 | |
| example_jvm_args="" | |
| ;; | |
| "14_lifecycle_timing.py") | |
| example_args="--runs 1 --table-records 2000 --graph-vertices 500 --vector-records 500 --vector-dimensions 32 --query-runs 10 --jvm-heap 4g" | |
| example_name="$example (lifecycle benchmark, minimal)" | |
| timeout_duration=900 | |
| example_jvm_args="" | |
| ;; | |
| "15_import_database_vs_transactional_table_ingest.py") | |
| example_args="--rows-per-table 2000 --tables 2 --columns 6 --string-size 32 --batch-size 500 --async-parallel 1 --parallel 1 --heap-size 4g --work-dir ./my_test_databases/import_vs_txn_dummy_ci" | |
| example_name="$example (table ingest benchmark, minimal)" | |
| timeout_duration=1200 | |
| example_jvm_args="" | |
| ;; | |
| "16_import_database_vs_transactional_graph_ingest.py") | |
| example_args="--vertices 2000 --edges 4000 --vertex-int-props 3 --vertex-str-props 2 --edge-int-props 1 --edge-str-props 1 --string-size 32 --batch-size 500 --async-parallel 1 --parallel 1 --heap-size 4g --work-dir ./my_test_databases/import_vs_txn_graph_ci" | |
| example_name="$example (graph ingest benchmark, minimal)" | |
| timeout_duration=1200 | |
| example_jvm_args="" | |
| ;; | |
| "17_timeseries_end_to_end.py") | |
| example_args="--hours 2 --interval-minutes 10" | |
| example_name="$example (time-series end-to-end, minimal)" | |
| timeout_duration=900 | |
| example_jvm_args="" | |
| ;; | |
| "18_geo_predicates_wkt.py") | |
| example_args="" | |
| example_name="$example (geo predicates with WKT points and polygons)" | |
| timeout_duration=900 | |
| example_jvm_args="" | |
| ;; | |
| "19_hash_index_exact_match.py") | |
| example_args="" | |
| example_name="$example (hash index exact-match workflow)" | |
| timeout_duration=900 | |
| example_jvm_args="" | |
| ;; | |
| "20_graph_algorithms_route_planning.py") | |
| example_args="" | |
| example_name="$example (graph algorithms route planning workflow)" | |
| timeout_duration=900 | |
| example_jvm_args="" | |
| ;; | |
| *) | |
| example_args="" | |
| example_name="$example" | |
| timeout_duration=900 # 15 minutes default | |
| example_jvm_args="" | |
| ;; | |
| esac | |
| log_file="${example%.py}.log" | |
| echo "----------------------------------------" | |
| echo "๐ Running: $example_name" | |
| echo "----------------------------------------" | |
| # Run the example with appropriate parameters | |
| if [ -n "$example_jvm_args" ]; then | |
| if ARCADEDB_JVM_ARGS="$example_jvm_args" JAVA_TOOL_OPTIONS="" _JAVA_OPTIONS="" $TIMEOUT_CMD $timeout_duration python "$example" $example_args > "$log_file" 2>&1; then | |
| exit_code=0 | |
| else | |
| exit_code=$? | |
| fi | |
| else | |
| if ARCADEDB_JVM_ARGS="" JAVA_TOOL_OPTIONS="" _JAVA_OPTIONS="" $TIMEOUT_CMD $timeout_duration python "$example" $example_args > "$log_file" 2>&1; then | |
| exit_code=0 | |
| else | |
| exit_code=$? | |
| fi | |
| fi | |
| # ANSWERS, NOT ONLY A CLEAN EXIT (humemai/arcadedb-embedded-python#12). Example 10 once returned 0 rows for | |
| # 5 of its 10 queries on every platform and passed. After a clean run, the | |
| # same arguments on the pure-Python reference backend must give the same | |
| # row count and hash for every query. | |
| if [ $exit_code -eq 0 ] && [ "$example" = "10_stackoverflow_graph_olap.py" ]; then | |
| ref_args="${example_args/--db arcadedb_cypher/--db python_memory}" | |
| ref_args="${ref_args/--run-label ci10/--run-label ci10ref}" | |
| ref_log="${example%.py}-reference.log" | |
| if ! $TIMEOUT_CMD $timeout_duration python "$example" $ref_args > "$ref_log" 2>&1; then | |
| echo "โ reference run (python_memory) of $example failed; last 20 lines:" | |
| tail -n 20 "$ref_log" | |
| exit_code=1 | |
| elif ! python scripts/compare_query_hashes.py "$log_file" "$ref_log"; then | |
| echo "โ $example: answers differ from the python_memory reference (above)" | |
| exit_code=1 | |
| fi | |
| fi | |
| if [ $exit_code -eq 0 ]; then | |
| echo "โ PASSED: $example_name" | tee -a $results_file | |
| passed=$((passed + 1)) | |
| else | |
| if [ $exit_code -eq 124 ]; then | |
| echo "โฑ๏ธ TIMEOUT: $example_name (exceeded $((timeout_duration/60)) minutes)" | tee -a $results_file | |
| failed=$((failed + 1)) | |
| else | |
| echo "โ FAILED: $example_name (exit code: $exit_code)" | tee -a $results_file | |
| failed=$((failed + 1)) | |
| fi | |
| # Show last 20 lines of error log | |
| echo "Last 20 lines of output:" | |
| tail -n 20 "$log_file" | |
| fi | |
| echo "" | |
| done | |
| # Print summary | |
| echo "========================================" | |
| echo "๐ EXAMPLE TEST SUMMARY" | |
| echo "========================================" | |
| echo "Total: $total" | |
| echo "Passed: $passed โ " | |
| echo "Failed: $failed โ" | |
| echo "Skipped: $skipped โญ๏ธ" | |
| echo "========================================" | |
| echo "" | |
| echo "Examples pattern(s): $EXAMPLES" | |
| # Output to GitHub Actions | |
| echo "total=$total" >> $GITHUB_OUTPUT | |
| echo "passed=$passed" >> $GITHUB_OUTPUT | |
| echo "failed=$failed" >> $GITHUB_OUTPUT | |
| echo "skipped=$skipped" >> $GITHUB_OUTPUT | |
| # Show detailed results | |
| echo "Detailed Results:" | |
| cat $results_file | |
| # Save list of examples for summary (before the failure exit, so | |
| # the summary is populated even when some examples fail) | |
| echo "$examples" > examples-ran.txt | |
| # Exit with error if any failed | |
| if [ $failed -gt 0 ]; then | |
| echo "โ Some examples failed!" | |
| exit 1 | |
| else | |
| echo "โ All examples passed!" | |
| fi | |
| - name: Generate test summary | |
| if: always() | |
| shell: bash | |
| run: | | |
| cd examples | |
| echo "## ๐ฎ Python Examples Test Results (${{ matrix.platform }})" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| total="${{ steps.run_examples.outputs.total || '0' }}" | |
| passed="${{ steps.run_examples.outputs.passed || '0' }}" | |
| failed="${{ steps.run_examples.outputs.failed || '0' }}" | |
| if [ "${{ steps.run_examples.outcome }}" = "success" ]; then | |
| echo "โ **Status**: ALL EXAMPLES PASSED ($passed/$total)" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "โ **Status**: SOME EXAMPLES FAILED ($passed/$total passed)" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Metric | Count |" >> $GITHUB_STEP_SUMMARY | |
| echo "|--------|------:|" >> $GITHUB_STEP_SUMMARY | |
| echo "| ๐ Total | $total |" >> $GITHUB_STEP_SUMMARY | |
| echo "| โ Passed | $passed |" >> $GITHUB_STEP_SUMMARY | |
| echo "| โ Failed | $failed |" >> $GITHUB_STEP_SUMMARY | |
| echo "| โญ๏ธ Skipped | ${{ steps.run_examples.outputs.skipped || '0' }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| # Add detailed results if available | |
| if [ -f example-results.txt ]; then | |
| echo "### Detailed Results" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "\`\`\`" >> $GITHUB_STEP_SUMMARY | |
| cat example-results.txt >> $GITHUB_STEP_SUMMARY | |
| echo "\`\`\`" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Examples Tested" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Pattern(s)**: $EXAMPLES" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ -f examples-ran.txt ]; then | |
| while IFS= read -r example; do | |
| [ -n "$example" ] && echo "- **$example**" >> $GITHUB_STEP_SUMMARY | |
| done < examples-ran.txt | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| - name: Upload example logs | |
| if: always() | |
| uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 | |
| with: | |
| name: example-logs-${{ matrix.platform == 'linux/amd64' && 'linux-amd64' || matrix.platform == 'linux/arm64' && 'linux-arm64' || matrix.platform == 'darwin/amd64' && 'darwin-amd64' || matrix.platform == 'darwin/arm64' && 'darwin-arm64' || matrix.platform == 'windows/amd64' && 'windows-amd64' || 'windows-arm64' }}-py${{ matrix.python-version }} | |
| path: | | |
| examples/*.log | |
| examples/example-results.txt | |
| retention-days: 7 | |
| - name: Upload example databases | |
| if: failure() | |
| uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 | |
| with: | |
| name: example-databases-${{ matrix.platform == 'linux/amd64' && 'linux-amd64' || matrix.platform == 'linux/arm64' && 'linux-arm64' || matrix.platform == 'darwin/amd64' && 'darwin-amd64' || matrix.platform == 'darwin/arm64' && 'darwin-arm64' || matrix.platform == 'windows/amd64' && 'windows-amd64' || 'windows-arm64' }}-py${{ matrix.python-version }} | |
| path: examples/my_test_databases/ | |
| retention-days: 3 | |
| # Summary job that checks all platforms | |
| test-examples-summary: | |
| name: Examples Test Summary | |
| needs: test-examples | |
| runs-on: ubuntu-latest | |
| # Skipped (not red) when the matrix was skipped for a draft PR. | |
| if: always() && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) | |
| steps: | |
| - name: Check test results | |
| shell: bash | |
| run: | | |
| echo "## ๐ฏ Overall Examples Test Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ "${{ needs.test-examples.result }}" = "success" ]; then | |
| echo "โ **All platforms passed example testing!**" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "All examples ran successfully across all enabled platforms." >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Platforms tested**: linux/amd64, linux/arm64, darwin/arm64, windows/amd64" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "โ **Some platforms failed example testing**" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Please check the individual platform jobs for details." >> $GITHUB_STEP_SUMMARY | |
| exit 1 | |
| fi |