--- name: Test on: push: branches: [main] pull_request: branches: [main] schedule: # * is a special character in YAML so you have to quote this string # Run at 1:00 every day - cron: 0 1 * * * workflow_dispatch: {} # We share Vuforia credentials and therefore Vuforia databases across # workflows. We therefore want to run only one workflow at a time. concurrency: vuforia_credentials permissions: {} jobs: # CI tests with matrix ci-tests: runs-on: ubuntu-latest environment: vuforia strategy: fail-fast: false matrix: python-version: ['3.14'] ci_pattern: - tests/mock_vws/test_query.py::TestContentType - tests/mock_vws/test_query.py::TestSuccess - tests/mock_vws/test_query.py::TestIncorrectFields - tests/mock_vws/test_query.py::TestMaxNumResults - tests/mock_vws/test_query.py::TestResultOrder - tests/mock_vws/test_query.py::TestIncludeTargetData - tests/mock_vws/test_query.py::TestAcceptHeader - tests/mock_vws/test_query.py::TestActiveFlag - tests/mock_vws/test_query.py::TestBadImage - tests/mock_vws/test_query.py::TestMaximumImageFileSize - tests/mock_vws/test_query.py::TestMaximumImageDimensions - tests/mock_vws/test_query.py::TestImageFormats - tests/mock_vws/test_query.py::TestProcessing - tests/mock_vws/test_query.py::TestUpdate - tests/mock_vws/test_query.py::TestDeleted - tests/mock_vws/test_query.py::TestTargetStatusFailed - tests/mock_vws/test_query.py::TestDateFormats - tests/mock_vws/test_query.py::TestInactiveProject - tests/mock_vws/test_add_target.py::TestContentTypes - tests/mock_vws/test_add_target.py::TestMissingData - tests/mock_vws/test_add_target.py::TestWidth - tests/mock_vws/test_add_target.py::TestTargetName - tests/mock_vws/test_add_target.py::TestImage - tests/mock_vws/test_add_target.py::TestActiveFlag - tests/mock_vws/test_add_target.py::TestUnexpectedData - tests/mock_vws/test_add_target.py::TestApplicationMetadata - tests/mock_vws/test_add_target.py::TestInactiveProject - tests/mock_vws/test_authorization_header.py::TestAuthorizationHeader - tests/mock_vws/test_authorization_header.py::TestMalformed::test_one_part_no_space - tests/mock_vws/test_authorization_header.py::TestMalformed::test_one_part_with_space - tests/mock_vws/test_authorization_header.py::TestMalformed::test_missing_signature - tests/mock_vws/test_authorization_header.py::TestBadKey - tests/mock_vws/test_cloud_query_failure_response.py - tests/mock_vws/test_content_length.py::TestIncorrect::test_not_integer - tests/mock_vws/test_content_length.py::TestIncorrect::test_too_large - tests/mock_vws/test_content_length.py::TestIncorrect::test_too_small - tests/mock_vws/test_database_summary.py::TestDatabaseSummary::test_success - tests/mock_vws/test_database_summary.py::TestDatabaseSummary::test_active_images - tests/mock_vws/test_database_summary.py::TestDatabaseSummary::test_failed_images - tests/mock_vws/test_database_summary.py::TestDatabaseSummary::test_inactive_images - tests/mock_vws/test_database_summary.py::TestDatabaseSummary::test_inactive_failed - tests/mock_vws/test_database_summary.py::TestDatabaseSummary::test_deleted - tests/mock_vws/test_database_summary.py::TestProcessingImages - tests/mock_vws/test_database_summary.py::TestQuotas - tests/mock_vws/test_database_summary.py::TestRecos - tests/mock_vws/test_database_summary.py::TestRequestUsage - tests/mock_vws/test_database_summary.py::TestInactiveProject - tests/mock_vws/test_date_header.py::TestFormat - tests/mock_vws/test_date_header.py::TestMissing - tests/mock_vws/test_date_header.py::TestSkewedTime::test_date_out_of_range_after - tests/mock_vws/test_date_header.py::TestSkewedTime::test_date_out_of_range_before - tests/mock_vws/test_date_header.py::TestSkewedTime::test_date_in_range_after - tests/mock_vws/test_date_header.py::TestSkewedTime::test_date_in_range_before - tests/mock_vws/test_delete_target.py - tests/mock_vws/test_get_duplicates.py - tests/mock_vws/test_get_target.py - tests/mock_vws/test_invalid_given_id.py - tests/mock_vws/test_invalid_json.py::TestInvalidJSON::test_invalid_json - tests/mock_vws/test_invalid_json.py::TestInvalidJSON::test_invalid_json_with_skewed_time - tests/mock_vws/test_target_list.py - tests/mock_vws/test_reco_counts_report.py - tests/mock_vws/test_target_raters.py - tests/mock_vws/test_target_summary.py - tests/mock_vws/test_unexpected_json.py - tests/mock_vws/test_update_target.py::TestActiveFlag - tests/mock_vws/test_update_target.py::TestApplicationMetadata::test_base64_encoded - tests/mock_vws/test_update_target.py::TestApplicationMetadata::test_invalid_type - tests/mock_vws/test_update_target.py::TestApplicationMetadata::test_not_base64_encoded_processable - tests/mock_vws/test_update_target.py::TestApplicationMetadata::test_not_base64_encoded_not_processable - tests/mock_vws/test_update_target.py::TestApplicationMetadata::test_metadata_too_large - tests/mock_vws/test_update_target.py::TestImage::test_image_valid - tests/mock_vws/test_update_target.py::TestImage::test_bad_image_format_or_color_space - tests/mock_vws/test_update_target.py::TestImage::test_corrupted - tests/mock_vws/test_update_target.py::TestImage::test_image_too_large - tests/mock_vws/test_update_target.py::TestImage::test_not_base64_encoded_processable - tests/mock_vws/test_update_target.py::TestImage::test_not_base64_encoded_not_processable - tests/mock_vws/test_update_target.py::TestImage::test_not_image - tests/mock_vws/test_update_target.py::TestImage::test_invalid_type - tests/mock_vws/test_update_target.py::TestImage::test_rating_can_change - tests/mock_vws/test_update_target.py::TestTargetName::test_name_valid - tests/mock_vws/test_update_target.py::TestTargetName::test_name_invalid - tests/mock_vws/test_update_target.py::TestTargetName::test_existing_target_name - tests/mock_vws/test_update_target.py::TestTargetName::test_same_name_given - tests/mock_vws/test_update_target.py::TestUnexpectedData - tests/mock_vws/test_update_target.py::TestUpdate - tests/mock_vws/test_update_target.py::TestWidth - tests/mock_vws/test_update_target.py::TestInactiveProject - tests/mock_vws/test_requests_mock_usage.py - tests/mock_vws/test_respx_mock_usage.py - tests/mock_vws/test_flask_app_usage.py - tests/mock_vws/test_model_target_generation_failure.py - tests/mock_vws/test_model_target_generation_warning.py - tests/mock_vws/test_model_target_web_api.py - tests/mock_vws/test_vumark_generation_api.py - tests/mock_vws/test_vumark_generation_failure.py - tests/mock_vws/test_target_validators.py - tests/mock_vws/test_healthcheck.py - tests/mock_vws/test_docker.py - README.rst - docs/ steps: - uses: actions/checkout@v7 with: persist-credentials: false - name: Install uv uses: astral-sh/setup-uv@v9.0.0 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' - name: Set secrets file env: CI_PATTERN: ${{ matrix.ci_pattern }} ENCRYPTED_FILE: secrets.tar.gpg LARGE_SECRET_PASSPHRASE: ${{ secrets.PASSPHRASE_FOR_VUFORIA_SECRETS }} JOB_INDEX: ${{ strategy.job-index }} run: | # See the "CI Setup" document for details of how this was set up. ci/decrypt_secret.sh tar xvf "${HOME}"/secrets/secrets.tar cp "./ci_secrets/vuforia_secrets_${JOB_INDEX}.env" ./vuforia_secrets.env # We have seen issues with running out of disk space on test_docker - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main if: matrix.ci_pattern == 'tests/mock_vws/test_docker.py' with: # All of these default to true (meaning they are removed). docker-images: false large-packages: false swap-storage: false tool-cache: false android: true dotnet: true haskell: true - name: Run tests run: | uv run --extra=dev \ coverage run -m pytest \ -s \ -vvv \ --showlocals \ --exitfirst \ ${{ matrix.ci_pattern }} env: UV_PYTHON: ${{ matrix.python-version }} - name: Sanitize pattern for artifact name id: sanitize run: | SANITIZED_PATTERN=$(echo "${{ matrix.ci_pattern }}" | sed 's/::/-/g' | sed 's/:/-/g' | sed 's|/|-|g') echo "name=coverage-data-ci-${{ matrix.python-version }}-${SANITIZED_PATTERN}" >> "$GITHUB_OUTPUT" - name: Upload coverage data uses: actions/upload-artifact@v7 with: name: ${{ steps.sanitize.outputs.name }} path: .coverage.* include-hidden-files: true if-no-files-found: error # Skip tests skip-tests: runs-on: ubuntu-latest strategy: matrix: python-version: ['3.14'] platform: [ubuntu-latest] steps: - uses: actions/checkout@v7 with: persist-credentials: false - name: Install uv uses: astral-sh/setup-uv@v9.0.0 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' - name: Set secrets file run: | cp ./vuforia_secrets.env.example ./vuforia_secrets.env - name: Run tests run: | uv run --extra=dev \ coverage run -m pytest \ --skip-docker_build_tests \ --skip-docker_in_memory \ --skip-mock \ --skip-real \ --capture=no \ -vvv \ --exitfirst \ . env: UV_PYTHON: ${{ matrix.python-version }} - name: Upload coverage data uses: actions/upload-artifact@v7 with: name: coverage-data-skip-tests-${{ matrix.python-version }} path: .coverage.* include-hidden-files: true if-no-files-found: error # Windows tests windows-tests: runs-on: windows-latest strategy: matrix: python-version: ['3.14'] steps: - uses: actions/checkout@v7 with: persist-credentials: false - name: Install uv uses: astral-sh/setup-uv@v9.0.0 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' - name: Set secrets file run: | cp ./vuforia_secrets.env.example ./vuforia_secrets.env - name: Start Docker shell: pwsh run: | $service = Get-Service docker -ErrorAction Stop if ($service.Status -ne "Running") { Start-Service docker } $deadline = (Get-Date).AddMinutes(2) do { docker info if ($LASTEXITCODE -eq 0) { exit 0 } Start-Sleep -Seconds 5 } while ((Get-Date) -lt $deadline) docker info - name: Run tests shell: bash run: | # We use pytest-xdist to make this run much faster. # The downside is that we cannot use -s / --capture=no. # uv run --extra=dev \ coverage run -m pytest \ --skip-real \ -vvv \ --exitfirst \ -n auto \ . env: UV_PYTHON: ${{ matrix.python-version }} - name: Upload coverage data uses: actions/upload-artifact@v7 with: name: coverage-data-windows-${{ matrix.python-version }} path: .coverage.* include-hidden-files: true if-no-files-found: error coverage: name: Combine & check coverage needs: [ci-tests, skip-tests, windows-tests] if: always() runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: persist-credentials: false - name: Install uv uses: astral-sh/setup-uv@v9.0.0 with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' python-version: '3.14' - uses: actions/download-artifact@v8 with: pattern: coverage-data-* merge-multiple: true - name: Require 100% Coverage id: coverage run: | uv run --extra=dev coverage combine uv run --extra=dev coverage html --skip-covered --skip-empty # Report and write to summary, without failing yet. uv run --extra=dev coverage report --format=markdown \ >> "$GITHUB_STEP_SUMMARY" || true # Report again and fail if under 100%. uv run --extra=dev coverage report - name: Upload HTML report if check failed uses: actions/upload-artifact@v7 with: name: html-report path: htmlcov if: ${{ failure() }} # Final completion check completion-ci: needs: [ci-tests, skip-tests, windows-tests, coverage] runs-on: ubuntu-latest if: always() steps: - name: Check all jobs status run: |- if ! ${{ needs.ci-tests.result == 'success' }}; then echo "CI tests failed" exit 1 fi if ! ${{ needs.skip-tests.result == 'success' }}; then echo "Skip tests failed" exit 1 fi if ! ${{ needs.windows-tests.result == 'success' }}; then echo "Windows tests failed" exit 1 fi if ! ${{ needs.coverage.result == 'success' }}; then echo "Coverage check failed" exit 1 fi