Skip to content

build(deps): bump the gradle-minor-patch group with 2 updates #153

build(deps): bump the gradle-minor-patch group with 2 updates

build(deps): bump the gradle-minor-patch group with 2 updates #153

Workflow file for this run

name: Tests
# Triggers only on pull request lifecycle events:
# - opened (PR creation)
# - synchronize (push to the PR branch while the PR is open)
# - reopened
# These are the default `pull_request` activity types — listed explicitly
# here for clarity. Pre-PR pushes don't run CI by design (saves minutes
# during early WIP commits).
on:
pull_request:
types: [opened, synchronize, reopened]
branches: ['**']
permissions:
contents: read
# Cancel an in-progress run when a new commit lands on the same PR.
concurrency:
group: pr-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
changes:
# A documentation-only pull request does not need the suite below. But a
# required status check can never be conditional -- the list is static -- and
# a matrix job that GitHub SKIPS does not expand its name, so it reports no
# per-combination context at all and the pull request waits forever for
# checks that never arrive. Measured on MarketDataApp/sdk-php#95.
#
# So `Tests passed` at the bottom is the single context branch protection
# requires, and this job decides whether the suite does any work.
name: Decide whether the suite applies
runs-on: ubuntu-latest
timeout-minutes: 2
outputs:
code: ${{ steps.filter.outputs.code }}
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Detect whether anything but documentation changed
id: filter
env:
BASE: ${{ github.event.pull_request.base.sha }}
HEAD: ${{ github.event.pull_request.head.sha }}
run: |
# Only a pull request can be documentation-only here. A push, a release
# and a workflow_call all run the suite.
if [ "${{ github.event_name }}" != "pull_request" ]; then
echo "code=true" >> "$GITHUB_OUTPUT"
echo "Not a pull request, so running the suite."
exit 0
fi
# Every path out of here that is not a confident "documentation only"
# says true. A gate that cannot see both ends of the diff must never
# conclude that no code changed.
for sha in "$BASE" "$HEAD"; do
if ! git cat-file -e "$sha^{commit}" 2>/dev/null; then
echo "code=true" >> "$GITHUB_OUTPUT"
echo "Cannot reach $sha, so running the suite rather than guessing."
exit 0
fi
done
# Three dots: the diff from the merge base, not from wherever the base
# branch has moved to since.
files=$(git diff --name-only "$BASE...$HEAD")
echo "changed files:"
printf '%s\n' "$files" | sed 's/^/ /'
# Inverted deliberately. This lists what DOCUMENTATION looks like and
# runs the suite for everything else. Enumerating code paths instead
# would skip the suite the first time somebody adds a source directory
# nobody remembered to list.
if [ -n "$files" ] && ! printf '%s\n' "$files" | grep -qvE '(^docs/|\.md$)'; then
echo "code=false" >> "$GITHUB_OUTPUT"
echo "Documentation only: the suite skips, and Tests passed says so."
else
echo "code=true" >> "$GITHUB_OUTPUT"
echo "Something other than documentation changed: running the suite."
fi
verify:
needs: changes
# `!= 'false'`, not `== 'true'`. A failed gate leaves this empty,
# and the safe reading of empty is RUN THE SUITE.
if: needs.changes.outputs.code != 'false'
name: Verify (JDK 17)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
# PRs only run on JDK 17 (the minimum target). Forward-compat
# regressions on JDK 21/25 are caught post-merge by main.yml.
- name: Set up JDK 17
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '17'
# Validates the wrapper jar hash and caches Gradle home + wrapper
# dists between runs.
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build, test, lint, coverage
run: ./gradlew build --stacktrace
- name: Upload test reports on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-reports
path: |
build/reports/tests/
build/test-results/
retention-days: 14
# Coverage ratchet lives in Codecov: codecov.yml at the repo root
# configures `threshold: 5%` so a PR fails the Codecov status check
# if line coverage drops more than 5 pp vs the base branch.
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v7
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: build/reports/jacoco/test/jacocoTestReport.xml
fail_ci_if_error: true
integration:
name: Integration tests (live API, JDK 17)
# SDK requirements §13: the live suite runs on every pull request. JDK 17
# only, like `/integrationtest`; the full {17, 21, 25} matrix stays in
# main.yml and behind `/integrationtestfull`.
#
# `needs: changes` and NOT `needs: verify`. The code review starts when
# this workflow completes and reads the result of this job, so running the
# two side by side is what keeps the wait at the longer of them rather
# than their sum. The live suite takes about two minutes and a build that
# does not compile fails here before a single request is sent, so the
# quota a red `verify` would have saved is small. sdk-py, sdk-csharp,
# sdk-go and sdk-php gate their live job on `changes` alone as well.
needs: changes
# Three conditions, and `Tests passed` below accepts a skip for exactly
# these three and no other:
# 1. the gate did not say "documentation only", so a markdown change
# spends no live API quota. `!= 'false'` so a failed gate still runs;
# 2. the pull request was not opened by dependabot, and
# 3. it does not come from a fork: neither receives repository secrets
# on `pull_request`. They skip this JOB, which shows as "skipped" in
# the checks, not as a green run that tested nothing.
# Clause 2 reads the AUTHOR of the pull request, not `github.actor`, which
# is whoever triggered this run. A maintainer who reopens a dependabot
# pull request becomes the actor, and the run then does receive the
# token, so a test on the actor would send a freshly bumped dependency's
# code to the live API without anybody having decided to. A dependabot
# pull request never runs the suite on its own, whoever triggers the run;
# a maintainer runs it for one, or for a fork, with `/integrationtest`.
# A head repository that has since been deleted reads as empty, which is
# not this repository, so it skips too.
if: >-
needs.changes.outputs.code != 'false' &&
github.event.pull_request.user.login != 'dependabot[bot]' &&
github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
# Every test talks to the live API; without a cap one hung request would
# hold the runner until the 6-hour default.
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Set up JDK 17
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '17'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
# SDK requirements §17.3: a missing token fails the job, never skips it.
- name: Run integration tests against live API
env:
MARKETDATA_TOKEN: ${{ secrets.MARKETDATA_TOKEN }}
MARKETDATA_RUN_INTEGRATION_TESTS: 'true'
run: |
if [ -z "$MARKETDATA_TOKEN" ]; then
echo "::error::MARKETDATA_TOKEN secret is missing; the live integration suite fails rather than skips (SDK requirements 17.3)."
exit 1
fi
./gradlew integrationTest -PtestJdk=17 --stacktrace
- name: Upload integration-test reports on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: integration-test-reports-jdk17
path: |
build/reports/tests/integrationTest/
build/test-results/integrationTest/
retention-days: 14
tests-passed:
# The single context branch protection requires. It always reports, so it is
# always a decision rather than a silence.
name: Tests passed
needs: [changes, verify, integration]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- name: Require the suite to have passed, or to have been skipped for cause
env:
CODE: ${{ needs.changes.outputs.code }}
R0: ${{ needs.verify.result }}
R1: ${{ needs.integration.result }}
# The negation of the last two clauses of the `integration` job's
# `if:`, evaluated by the same expression engine so the two cannot
# disagree: `true` for a pull request opened by dependabot, whoever
# triggered this run, and for a fork.
NO_SECRETS: ${{ github.event.pull_request.user.login == 'dependabot[bot]' || github.event.pull_request.head.repo.full_name != github.repository }}
run: |
echo "gate said code=${CODE:-<nothing: the gate itself failed>}"
echo "verify: ${R0}"
echo "integration: ${R1} (pull request without secrets: ${NO_SECRETS})"
# Two classes of job, because they skip for different reasons.
#
# STRICT jobs apply to every change, so the only acceptable skip is the
# one this workflow's gate asked for. If the gate failed, CODE is empty,
# the job skipped for some other reason, and a green here would cover
# untested code.
for result in "${R0}"; do
case "$result" in
success) ;;
skipped)
if [ "$CODE" != "false" ]; then
echo "A required job was skipped and the gate did not ask for it."
exit 1
fi
;;
*) echo "A required job reported: $result"; exit 1 ;;
esac
done
# LIVE jobs need the API token, so they have one more acceptable skip:
# a pull request opened by dependabot or coming from a fork, which
# never runs the suite on its own. Nothing else is accepted, and a
# FAILURE always fails: that includes the job refusing to run without
# its token on a pull request that should have had one.
for result in "${R1}"; do
case "$result" in
success) ;;
skipped)
if [ "$CODE" != "false" ] && [ "$NO_SECRETS" != "true" ]; then
echo "The live suite was skipped, and neither the gate nor a pull request without secrets explains it."
exit 1
fi
;;
*) echo "The live suite reported: $result"; exit 1 ;;
esac
done
echo "Every job passed, or skipped for a reason this gate accepts."