Unverified Commit e24b25f8 authored by Dmitry Tokarev's avatar Dmitry Tokarev Committed by GitHub
Browse files

feat: add Allure test dashboard on GitHub Pages (#6903)


Signed-off-by: default avatarDmitry Tokarev <dtokarev@nvidia.com>
Co-authored-by: default avatarClaude Opus 4.6 (1M context) <noreply@anthropic.com>
parent c021814f
......@@ -73,7 +73,7 @@ runs:
kubectl config get-contexts
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.12'
cache: 'pip'
......@@ -96,6 +96,10 @@ runs:
DYN_TEST_OUTPUT_PATH: ${{ github.workspace }}/test-output
TEST_NAME: ${{ inputs.test_name }}
EXTRA_PYTEST_ARGS: ${{ inputs.extra_pytest_args }}
DYNAMO_TEST_FRAMEWORK: ${{ inputs.framework }}
DYNAMO_TEST_PLATFORM: ${{ inputs.platform_arch }}
DYNAMO_TEST_TYPE: deploy_${{ inputs.profile }}
DYNAMO_TEST_WORKFLOW: ${{ github.workflow }}
run: |
mkdir -p test-results
......@@ -121,6 +125,7 @@ runs:
-v -s \
--durations=10 \
--junitxml=test-results/pytest_deploy_${TEST_NAME}_${{ inputs.platform_arch }}_${{ github.run_id }}_${{ job.check_run_id }}.xml \
--alluredir=test-results/allure-results \
--log-cli-level=INFO
- name: Cleanup Deployment
......@@ -184,3 +189,12 @@ runs:
path: ${{ github.workspace }}/test-output/
if-no-files-found: warn
retention-days: 7
- name: Upload Allure Results
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f #v6
if: always()
with:
name: allure-results-${{ steps.test-name.outputs.name }}-${{ inputs.platform_arch }}-${{ github.run_id }}-${{ job.check_run_id }}
path: test-results/allure-results/
retention-days: 7
if-no-files-found: ignore
......@@ -121,7 +121,7 @@ runs:
PYTEST_CMD="pytest -v --collect-only -m \"${{ inputs.pytest_marks }}\""
else
echo "🚀 Running pytest in normal mode"
PYTEST_CMD="pytest --continue-on-collection-errors -v --tb=short --basetemp=/tmp/pytest_temp -o cache_dir=/tmp/.pytest_cache --junitxml=/workspace/test-results/${{ env.PYTEST_XML_FILE }} --durations=20 -m \"${{ inputs.pytest_marks }}\""
PYTEST_CMD="pytest --continue-on-collection-errors -v --tb=short --basetemp=/tmp/pytest_temp -o cache_dir=/tmp/.pytest_cache --junitxml=/workspace/test-results/${{ env.PYTEST_XML_FILE }} --alluredir=/workspace/test-results/allure-results --durations=20 -m \"${{ inputs.pytest_marks }}\""
# Detect GPU availability and conditionally add GPU flags
GPU_FLAGS=""
......@@ -142,6 +142,10 @@ runs:
if [[ -n "${HF_TOKEN:-}" ]]; then
DOCKER_ENV_FLAGS+=(--env "HF_TOKEN=${HF_TOKEN}")
fi
DOCKER_ENV_FLAGS+=(--env "DYNAMO_TEST_FRAMEWORK=${{ inputs.framework }}")
DOCKER_ENV_FLAGS+=(--env "DYNAMO_TEST_PLATFORM=${{ inputs.platform_arch }}")
DOCKER_ENV_FLAGS+=(--env "DYNAMO_TEST_TYPE=${{ inputs.test_type }}")
DOCKER_ENV_FLAGS+=(--env "DYNAMO_TEST_WORKFLOW=${GITHUB_WORKFLOW}")
docker run ${GPU_FLAGS} --rm -w /workspace \
--cpus=${NUM_CPUS} \
......@@ -224,7 +228,7 @@ runs:
# Construct final command with xdist parallelization (-n) and other options
# --dist=loadscope groups tests by module/class to prevent race conditions in stateful tests
PYTEST_CMD="pytest ${PARALLEL_OPTS} --dist=loadscope --continue-on-collection-errors -v --tb=short --basetemp=/tmp/pytest_temp -o cache_dir=/tmp/.pytest_cache --junitxml=/workspace/test-results/${{ env.PYTEST_XML_FILE }} --durations=10 -m \"${{ inputs.pytest_marks }}\""
PYTEST_CMD="pytest ${PARALLEL_OPTS} --dist=loadscope --continue-on-collection-errors -v --tb=short --basetemp=/tmp/pytest_temp -o cache_dir=/tmp/.pytest_cache --junitxml=/workspace/test-results/${{ env.PYTEST_XML_FILE }} --alluredir=/workspace/test-results/allure-results --durations=10 -m \"${{ inputs.pytest_marks }}\""
fi
# Get absolute path for test-results directory and ensure it has proper permissions
......@@ -237,6 +241,10 @@ runs:
if [[ -n "${HF_TOKEN:-}" ]]; then
DOCKER_ENV_FLAGS+=(--env "HF_TOKEN=${HF_TOKEN}")
fi
DOCKER_ENV_FLAGS+=(--env "DYNAMO_TEST_FRAMEWORK=${{ inputs.framework }}")
DOCKER_ENV_FLAGS+=(--env "DYNAMO_TEST_PLATFORM=${{ inputs.platform_arch }}")
DOCKER_ENV_FLAGS+=(--env "DYNAMO_TEST_TYPE=${{ inputs.test_type }}")
DOCKER_ENV_FLAGS+=(--env "DYNAMO_TEST_WORKFLOW=${GITHUB_WORKFLOW}")
docker run ${GPU_FLAGS} ${DOCKER_OPTS} --rm -w /workspace \
--network host \
......@@ -303,9 +311,18 @@ runs:
docker rm -f dynamo-minio-test 2>/dev/null || true
- name: Upload Test Results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
if: always() # Always upload test results, even if tests failed
with:
name: test-results-${{ inputs.framework }}-${{ env.STR_TEST_TYPE }}-${{ env.PLATFORM_ARCH }}-${{ github.run_id }}-${{ job.check_run_id }}
path: test-results/pytest_test_report_${{ inputs.framework }}_${{ env.STR_TEST_TYPE }}_${{ inputs.platform_arch }}_${{ github.run_id }}_${{ job.check_run_id }}.xml
retention-days: 7
- name: Upload Allure Results
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
if: always()
with:
name: allure-results-${{ inputs.framework }}-${{ env.STR_TEST_TYPE }}-${{ env.PLATFORM_ARCH }}-${{ github.run_id }}-${{ job.check_run_id }}
path: test-results/allure-results/
retention-days: 7
if-no-files-found: ignore
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
import { defineConfig } from "allure";
// Labels use "dynamo_" prefix to avoid collision with allure-pytest's
// built-in "framework" label (always set to "pytest").
const byLabel = (name, value) => ({ labels }) =>
labels.find((l) => l.name === name && l.value === value);
// Also match on pytest marker tags (e.g. @pytest.mark.vllm adds tag "vllm")
// as a fallback for tests where conftest.py hook doesn't fire.
const byTagOrLabel = (tagValue, labelName, labelValue) => ({ labels }) =>
labels.find((l) => l.name === labelName && l.value === labelValue) ||
labels.find((l) => l.name === "tag" && l.value === tagValue);
// Environment matcher: matches on framework tag + platform label
const envMatcher = (framework, platform) => ({ labels }) => {
const hasFramework =
labels.find((l) => l.name === "dynamo_framework" && l.value === framework) ||
labels.find((l) => l.name === "tag" && l.value === framework);
const hasPlatform = platform
? labels.find((l) => l.name === "dynamo_platform" && l.value === platform)
: true;
return hasFramework && hasPlatform;
};
export default defineConfig({
name: "Dynamo Test Health",
output: "./allure-report",
historyPath: "./history.jsonl",
environments: {
"vllm-amd64": { name: "vLLM (amd64)", matcher: envMatcher("vllm", "amd64") },
"vllm-arm64": { name: "vLLM (arm64)", matcher: envMatcher("vllm", "arm64") },
"sglang-amd64": { name: "SGLang (amd64)", matcher: envMatcher("sglang", "amd64") },
"sglang-arm64": { name: "SGLang (arm64)", matcher: envMatcher("sglang", "arm64") },
"trtllm-amd64": { name: "TRT-LLM (amd64)", matcher: envMatcher("trtllm", "amd64") },
"trtllm-arm64": { name: "TRT-LLM (arm64)", matcher: envMatcher("trtllm", "arm64") },
},
plugins: {
pr: {
import: "@allurereport/plugin-awesome",
options: {
reportName: "PR",
singleFile: false,
filter: byLabel("dynamo_workflow", "PR"),
publish: true,
},
},
postMerge: {
import: "@allurereport/plugin-awesome",
options: {
reportName: "Post-Merge",
singleFile: false,
filter: byLabel("dynamo_workflow", "Post-Merge CI Pipeline"),
publish: true,
},
},
nightly: {
import: "@allurereport/plugin-awesome",
options: {
reportName: "Nightly",
singleFile: false,
filter: byLabel("dynamo_workflow", "Nightly CI Pipeline"),
publish: true,
},
},
release: {
import: "@allurereport/plugin-awesome",
options: {
reportName: "Release",
singleFile: false,
filter: byLabel("dynamo_workflow", "Release Pipeline"),
publish: true,
},
},
vllm: {
import: "@allurereport/plugin-awesome",
options: {
reportName: "vLLM",
singleFile: false,
filter: byTagOrLabel("vllm", "dynamo_framework", "vllm"),
publish: true,
},
},
sglang: {
import: "@allurereport/plugin-awesome",
options: {
reportName: "SGLang",
singleFile: false,
filter: byTagOrLabel("sglang", "dynamo_framework", "sglang"),
publish: true,
},
},
trtllm: {
import: "@allurereport/plugin-awesome",
options: {
reportName: "TRT-LLM",
singleFile: false,
filter: byTagOrLabel("trtllm", "dynamo_framework", "trtllm"),
publish: true,
},
},
},
});
......@@ -53,6 +53,7 @@ ignore:
- '.github/release.yml'
- '.github/copy-pr-bot.yaml'
- '.github/dco.yml'
- '.github/allurerc.mjs'
- 'container/Dockerfile.docs'
- 'container/run.sh'
- 'container/use-sccache.sh'
......
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
name: Allure Test Report
on:
workflow_run:
workflows:
- 'Post-Merge CI Pipeline'
- 'Nightly CI Pipeline'
- 'Release Pipeline'
types: [completed]
workflow_dispatch:
inputs:
run_id:
description: 'Workflow run ID to download allure-results artifacts from'
required: true
type: string
report_subdir:
description: 'Subdirectory on gh-pages for the report'
required: false
type: string
default: 'dev'
jobs:
resolve-params:
runs-on: ubuntu-latest
if: >-
github.event_name == 'workflow_dispatch' ||
github.event.workflow_run.conclusion != 'skipped'
outputs:
run_id: ${{ steps.params.outputs.run_id }}
subdir: ${{ steps.params.outputs.subdir }}
workflow_label: ${{ steps.params.outputs.workflow_label }}
steps:
- name: Determine report parameters
id: params
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "run_id=${{ inputs.run_id }}" >> $GITHUB_OUTPUT
echo "subdir=${{ inputs.report_subdir }}" >> $GITHUB_OUTPUT
echo "workflow_label=${{ inputs.report_subdir }}" >> $GITHUB_OUTPUT
else
echo "run_id=${{ github.event.workflow_run.id }}" >> $GITHUB_OUTPUT
WORKFLOW_NAME="${{ github.event.workflow_run.name }}"
echo "workflow_label=${WORKFLOW_NAME}" >> $GITHUB_OUTPUT
case "$WORKFLOW_NAME" in
*"Post-Merge"*) echo "subdir=post-merge" >> $GITHUB_OUTPUT ;;
*"Nightly"*) echo "subdir=nightly" >> $GITHUB_OUTPUT ;;
*"Release"*) echo "subdir=release" >> $GITHUB_OUTPUT ;;
*)
echo "::error::Unknown workflow name: '${WORKFLOW_NAME}'"
echo "::error::If this is a new workflow, update: (1) this case statement, (2) allurerc.mjs plugin filters, (3) the allure-all unified dashboard config."
echo "::error::If an existing workflow was renamed, also update the gh-pages subdir and dashboard-results path to preserve history trends."
exit 1
;;
esac
fi
generate-report:
needs: resolve-params
uses: ./.github/workflows/generate-allure-report.yml
with:
run_id: ${{ needs.resolve-params.outputs.run_id }}
subdir: ${{ needs.resolve-params.outputs.subdir }}
workflow_label: ${{ needs.resolve-params.outputs.workflow_label }}
permissions:
contents: write
actions: read
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
# Reusable workflow: generate Allure reports and deploy to GitHub Pages.
# Called by pr.yaml (pre-merge) and allure-report.yml (post-merge/nightly/release).
name: Generate Allure Report
on:
workflow_call:
inputs:
run_id:
description: 'Workflow run ID to download allure-results artifacts from'
required: true
type: string
subdir:
description: 'Subdirectory under allure/ on gh-pages (e.g. pr, post-merge, nightly, release)'
required: true
type: string
workflow_label:
description: 'Workflow label injected into allure results for dashboard filtering'
required: true
type: string
permissions:
contents: write
actions: read
jobs:
generate-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Download Allure Results
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
run-id: ${{ inputs.run_id }}
pattern: allure-results-*
path: allure-results
merge-multiple: true
github-token: ${{ github.token }}
- name: Check for Allure results
id: check-results
run: |
if [ -d allure-results ] && [ "$(ls -A allure-results 2>/dev/null)" ]; then
echo "has_results=true" >> $GITHUB_OUTPUT
echo "Found allure results:"
ls -la allure-results/
else
echo "has_results=false" >> $GITHUB_OUTPUT
echo "No allure results found. Skipping report generation."
fi
- name: Inject workflow label into allure results
if: steps.check-results.outputs.has_results == 'true'
env:
WORKFLOW_LABEL: ${{ inputs.workflow_label }}
run: |
# The conftest.py hook doesn't fire for all tests, so inject
# dynamo_workflow label into all result files to ensure workflow
# tiles in the unified dashboard capture every test.
python3 -c "
import json, glob, sys
wf = sys.argv[1]
for f in glob.glob('allure-results/*-result.json'):
with open(f, 'r') as fh:
data = json.load(fh)
labels = data.setdefault('labels', [])
if not any(l.get('name') == 'dynamo_workflow' for l in labels):
labels.append({'name': 'dynamo_workflow', 'value': wf})
with open(f, 'w') as fh:
json.dump(data, fh)
" "$WORKFLOW_LABEL"
- name: Install Allure 2 CLI
if: steps.check-results.outputs.has_results == 'true'
run: |
ALLURE_VERSION="2.38.0"
wget -q "https://github.com/allure-framework/allure2/releases/download/${ALLURE_VERSION}/allure-${ALLURE_VERSION}.tgz"
tar -xzf "allure-${ALLURE_VERSION}.tgz"
mv "allure-${ALLURE_VERSION}" allure2-cli
- name: Install Allure 3 CLI
if: steps.check-results.outputs.has_results == 'true'
run: |
npm install allure@3.3.1
- name: Get Allure history from gh-pages
if: steps.check-results.outputs.has_results == 'true'
env:
SUBDIR: ${{ inputs.subdir }}
run: |
git fetch origin gh-pages:gh-pages 2>/dev/null || echo "No gh-pages branch yet"
# Restore history for Allure 3 report (primary, at allure/${SUBDIR}/)
# Allure 3 uses history.jsonl (not history/*.json like Allure 2)
if git show "gh-pages:allure/${SUBDIR}/history.jsonl" 2>/dev/null; then
git show "gh-pages:allure/${SUBDIR}/history.jsonl" > allure-v3-history.jsonl
echo "Restored Allure 3 history.jsonl from gh-pages/allure/${SUBDIR}"
else
echo "No previous Allure 3 history found"
fi
# Restore history for Allure 2 report (at allure/v2/${SUBDIR}/)
if git show "gh-pages:allure/v2/${SUBDIR}/history" 2>/dev/null; then
mkdir -p allure-results/history
for f in history.json history-trend.json duration-trend.json categories-trend.json retry-trend.json; do
git show "gh-pages:allure/v2/${SUBDIR}/history/$f" > "allure-results/history/$f" 2>/dev/null || true
done
echo "Restored Allure 2 history from gh-pages/allure/v2/${SUBDIR}"
else
echo "No previous Allure 2 history found"
fi
- name: Generate Allure 2 Report
if: steps.check-results.outputs.has_results == 'true'
run: |
./allure2-cli/bin/allure generate allure-results -o allure-report --clean
- name: Generate Allure 3 Report
if: steps.check-results.outputs.has_results == 'true'
run: |
mkdir -p allure-v3-workspace/allure-results
# Copy results but exclude Allure 2 history dir to avoid format confusion
rsync -a --exclude='history' allure-results/ allure-v3-workspace/allure-results/
# Restore Allure 3 history.jsonl into the workspace
if [ -f allure-v3-history.jsonl ]; then
cp allure-v3-history.jsonl allure-v3-workspace/history.jsonl
fi
# historyPath requires a config file
echo 'export default { historyPath: "./history.jsonl" };' > allure-v3-workspace/allurerc.mjs
npx allure generate --config="${GITHUB_WORKSPACE}/allure-v3-workspace/allurerc.mjs" --cwd allure-v3-workspace -o allure-report-v3
- name: Generate unified dashboard
if: steps.check-results.outputs.has_results == 'true'
env:
SUBDIR: ${{ inputs.subdir }}
run: |
# Combine this workflow's results with stored results from other workflows
mkdir -p unified-workspace/allure-results
# Copy this workflow's results (exclude Allure 2 history dir)
rsync -a --exclude='history' allure-results/ unified-workspace/allure-results/
# Fetch stored results from other workflows on gh-pages
git fetch origin gh-pages:gh-pages 2>/dev/null || true
for dir in pr post-merge nightly release; do
if [ "$dir" = "$SUBDIR" ]; then continue; fi
if git show "gh-pages:dashboard-results/${dir}" 2>/dev/null; then
mkdir -p "unified-workspace/allure-results/${dir}-imported"
git archive gh-pages "dashboard-results/${dir}" | tar -x -C /tmp/
cp -r "/tmp/dashboard-results/${dir}/"* "unified-workspace/allure-results/${dir}-imported/" 2>/dev/null || true
fi
done
# Restore unified dashboard history for trend charts
if git show "gh-pages:allure/history.jsonl" 2>/dev/null; then
git show "gh-pages:allure/history.jsonl" > unified-workspace/history.jsonl
echo "Restored unified dashboard history from gh-pages"
else
echo "No previous unified dashboard history found"
fi
# Generate unified dashboard using allurerc.mjs
npx allure generate --config="${GITHUB_WORKSPACE}/.github/allurerc.mjs" --cwd unified-workspace -o allure-all-report
- name: Deploy to GitHub Pages
if: steps.check-results.outputs.has_results == 'true'
env:
SUBDIR: ${{ inputs.subdir }}
run: |
DEPLOY_DIR=$(mktemp -d)
cd "$DEPLOY_DIR"
git init
git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Reuse the checkout token for push access
git config http.https://github.com/.extraheader "AUTHORIZATION: basic $(echo -n "x-access-token:${{ github.token }}" | base64)"
# Pull existing gh-pages content or start fresh
if git ls-remote --exit-code origin gh-pages &>/dev/null; then
git fetch origin gh-pages
git checkout gh-pages
else
git checkout --orphan gh-pages
fi
# One-time cleanup: remove old path layout from before allure/ prefix migration
for old_dir in allure-all pre-merge pre-merge-v2 post-merge post-merge-v2 nightly nightly-v2 release release-v2; do
if [ -d "$old_dir" ]; then
echo "Removing legacy directory: $old_dir"
rm -rf "$old_dir"
fi
done
# Deploy Allure 3 report (primary)
mkdir -p "allure/${SUBDIR}"
rm -rf "allure/${SUBDIR}/"*
cp -r "${GITHUB_WORKSPACE}/allure-report-v3/"* "allure/${SUBDIR}/"
# Persist Allure 3 history.jsonl for trend charts
if [ -f "${GITHUB_WORKSPACE}/allure-v3-workspace/history.jsonl" ]; then
cp "${GITHUB_WORKSPACE}/allure-v3-workspace/history.jsonl" "allure/${SUBDIR}/history.jsonl"
fi
# Deploy Allure 2 report
mkdir -p "allure/v2/${SUBDIR}"
rm -rf "allure/v2/${SUBDIR}/"*
cp -r "${GITHUB_WORKSPACE}/allure-report/"* "allure/v2/${SUBDIR}/"
# Store raw results for unified dashboard aggregation (exclude Allure 2 history)
mkdir -p "dashboard-results/${SUBDIR}"
rm -rf "dashboard-results/${SUBDIR}/"*
rsync -a --exclude='history' "${GITHUB_WORKSPACE}/allure-results/" "dashboard-results/${SUBDIR}/"
# Deploy unified dashboard (overwrites report files, preserves subdirs)
mkdir -p allure
cp -r "${GITHUB_WORKSPACE}/allure-all-report/"* allure/
# Persist history.jsonl for trend charts
if [ -f "${GITHUB_WORKSPACE}/unified-workspace/history.jsonl" ]; then
cp "${GITHUB_WORKSPACE}/unified-workspace/history.jsonl" allure/history.jsonl
fi
# Commit and push
git add -A
if git diff --cached --quiet; then
echo "No changes to deploy"
else
git commit -m "Deploy Allure reports for ${SUBDIR} (run ${{ github.run_id }})"
# Retry push with rebase to handle concurrent gh-pages updates
for i in 1 2 3; do
if git push origin gh-pages; then
break
fi
echo "Push failed, retrying with rebase (attempt $i/3)..."
git fetch origin gh-pages
git rebase origin/gh-pages
done
fi
- name: Print report URLs
if: steps.check-results.outputs.has_results == 'true'
env:
SUBDIR: ${{ inputs.subdir }}
run: |
echo "## Allure Reports" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- [Allure Report](https://ai-dynamo.github.io/dynamo/allure/${SUBDIR}/)" >> $GITHUB_STEP_SUMMARY
echo "- [Allure 2 Report](https://ai-dynamo.github.io/dynamo/allure/v2/${SUBDIR}/)" >> $GITHUB_STEP_SUMMARY
echo "- [Unified Dashboard](https://ai-dynamo.github.io/dynamo/allure/)" >> $GITHUB_STEP_SUMMARY
......@@ -423,6 +423,23 @@ jobs:
vcluster_name: ${{ needs.deploy-operator.outputs.vcluster_name }}
vcluster_namespace: ${{ needs.deploy-operator.outputs.namespace }}
# ============================================================================
# ALLURE REPORT
# Generate Allure test report and deploy to GitHub Pages
# ============================================================================
allure-report:
needs: [vllm-pipeline, sglang-pipeline, trtllm-pipeline, deploy-test-vllm, deploy-test-sglang, deploy-test-trtllm]
if: ${{ !cancelled() }}
uses: ./.github/workflows/generate-allure-report.yml
with:
run_id: ${{ github.run_id }}
subdir: pr
workflow_label: PR
permissions:
contents: write
actions: read
# ============================================================================
# CLEANUP JOBS
# Clean up ephemeral Kubernetes namespace and resources
......
......@@ -5,6 +5,8 @@
# For deploy/utils async file IO used by deploy tests and profiler deployment helpers
aiofiles<=25.1.0
# For Allure test reporting in CI
allure-pytest==2.15.3
# For MinIO/S3 operations in LoRA tests (replaces AWS CLI dependency)
boto3==1.42.4
boto3-stubs[s3]==1.42.9 # Type stubs for boto3 S3 client
......
......@@ -77,6 +77,36 @@ def pytest_addoption(parser: pytest.Parser) -> None:
)
def pytest_runtest_setup(item):
"""Add Allure labels and parameters from CI environment."""
try:
import allure
except ImportError:
return
env_params = {
"framework": os.environ.get("DYNAMO_TEST_FRAMEWORK"),
"platform": os.environ.get("DYNAMO_TEST_PLATFORM"),
"test_type": os.environ.get("DYNAMO_TEST_TYPE"),
}
for name, value in env_params.items():
if value:
allure.dynamic.parameter(name, value)
# Labels used by allurerc.mjs plugin filters for the unified dashboard.
# Use "dynamo_" prefix to avoid collision with allure-pytest's built-in
# "framework" label (which is always set to "pytest").
env_labels = {
"dynamo_workflow": os.environ.get("DYNAMO_TEST_WORKFLOW"),
"dynamo_framework": os.environ.get("DYNAMO_TEST_FRAMEWORK"),
"dynamo_platform": os.environ.get("DYNAMO_TEST_PLATFORM"),
"dynamo_testType": os.environ.get("DYNAMO_TEST_TYPE"),
}
for name, value in env_labels.items():
if value:
allure.dynamic.label(name, value)
LOG_FORMAT = "[TEST] %(asctime)s %(levelname)s %(name)s: %(message)s"
DATE_FORMAT = "%Y-%m-%dT%H:%M:%S"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment