diff --git a/.github/workflows/gitlab-manual-ci.yml b/.github/workflows/gitlab-manual-ci.yml index 3ac6b45..9522978 100644 --- a/.github/workflows/gitlab-manual-ci.yml +++ b/.github/workflows/gitlab-manual-ci.yml @@ -7,12 +7,40 @@ on: description: "Branch, tag, or SHA in this repository to test" required: true default: "develop" + gitlab_target: + description: "GitLab target project" + required: true + type: choice + options: + - swc + - gitlab_com + default: swc + result_server_target: + description: "Result server target resolved by GitLab CI/CD variable scope" + required: true + type: choice + options: + - dev2 + - dev + default: dev2 code: description: "Benchkit code filter, for example: qws,genesis" required: false system: description: "System filter, for example: Fugaku,MiyabiG" required: false + nodes: + description: "Optional list.csv nodes filter, for example: 1" + required: false + numproc_node: + description: "Optional list.csv numproc_node filter, for example: 4" + required: false + nthreads: + description: "Optional list.csv nthreads filter, for example: 32" + required: false + activity: + description: "Optional public activity/budget label recorded as BK_EXECUTION_ACTIVITY" + required: false allocation_project_id: description: "Optional allocation/project ID passed as BK_ALLOCATION_PROJECT_ID" required: false @@ -53,6 +81,17 @@ jobs: fetch-depth: 0 path: trusted + - name: Reject production main workflow branch + env: + WORKFLOW_REF_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + + if [ "${WORKFLOW_REF_NAME}" = "main" ]; then + echo "::error::GitLab Manual CI must be run from develop or a trusted non-production workflow branch." + exit 1 + fi + - name: Reject production main target env: TARGET_REF: ${{ inputs.target_ref }} @@ -66,12 +105,20 @@ jobs: ;; esac - - name: Prepare GitLab repository settings - id: gitlab-repo + - name: Prepare SWC GitLab repository settings + if: ${{ inputs.gitlab_target == 'swc' }} + id: gitlab-repo-swc uses: ./trusted/.github/actions/prepare-gitlab-repo with: gitlab-repo: ${{ secrets.GITLAB_REPO }} + - name: Prepare GitLab.com repository settings + if: ${{ inputs.gitlab_target == 'gitlab_com' }} + id: gitlab-repo-com + uses: ./trusted/.github/actions/prepare-gitlab-repo + with: + gitlab-repo: gitlab.com/yoshifuminakamura/benchkit.git + - name: Check out target ref uses: actions/checkout@v4 with: @@ -82,14 +129,22 @@ jobs: - name: Push target ref to GitLab test branch working-directory: target env: - GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }} - GITLAB_REPO_HOST_PATH: ${{ steps.gitlab-repo.outputs.host-path }} + GITLAB_TARGET: ${{ inputs.gitlab_target }} + GITLAB_TOKEN_SWC: ${{ secrets.GITLAB_TOKEN }} + GITLAB_TOKEN_GITLAB_COM: ${{ secrets.GITLAB_COM_TOKEN }} + GITLAB_REPO_HOST_PATH: ${{ steps.gitlab-repo-swc.outputs.host-path || steps.gitlab-repo-com.outputs.host-path }} TARGET_REF: ${{ inputs.target_ref }} run: | set -euo pipefail + case "${GITLAB_TARGET}" in + swc) GITLAB_TOKEN="${GITLAB_TOKEN_SWC}" ;; + gitlab_com) GITLAB_TOKEN="${GITLAB_TOKEN_GITLAB_COM}" ;; + *) echo "Unsupported GitLab target: ${GITLAB_TARGET}"; exit 1 ;; + esac + if [ -z "${GITLAB_TOKEN}" ] || [ -z "${GITLAB_REPO_HOST_PATH}" ]; then - echo "GITLAB_TOKEN and GITLAB_REPO secrets are required." + echo "Selected GitLab target token and repository settings are required." exit 1 fi @@ -106,11 +161,18 @@ jobs: - name: Trigger and wait for GitLab pipeline env: - GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }} - GITLAB_HOST: ${{ steps.gitlab-repo.outputs.host }} - GITLAB_PROJECT_PATH: ${{ steps.gitlab-repo.outputs.project-path }} + GITLAB_TARGET: ${{ inputs.gitlab_target }} + GITLAB_TOKEN_SWC: ${{ secrets.GITLAB_TOKEN }} + GITLAB_TOKEN_GITLAB_COM: ${{ secrets.GITLAB_COM_TOKEN }} + GITLAB_HOST: ${{ steps.gitlab-repo-swc.outputs.host || steps.gitlab-repo-com.outputs.host }} + GITLAB_PROJECT_PATH: ${{ steps.gitlab-repo-swc.outputs.project-path || steps.gitlab-repo-com.outputs.project-path }} CODE_FILTER: ${{ inputs.code }} SYSTEM_FILTER: ${{ inputs.system }} + NODES_FILTER: ${{ inputs.nodes }} + NUMPROC_NODE_FILTER: ${{ inputs.numproc_node }} + NTHREADS_FILTER: ${{ inputs.nthreads }} + RESULT_SERVER_TARGET: ${{ inputs.result_server_target }} + ACTIVITY: ${{ inputs.activity }} ALLOCATION_PROJECT_ID: ${{ inputs.allocation_project_id }} BENCHPARK_APP: ${{ inputs.app }} BENCHPARK: ${{ inputs.benchpark }} @@ -119,6 +181,16 @@ jobs: run: | set -euo pipefail + case "${GITLAB_TARGET}" in + swc) GITLAB_TOKEN="${GITLAB_TOKEN_SWC}" ;; + gitlab_com) GITLAB_TOKEN="${GITLAB_TOKEN_GITLAB_COM}" ;; + *) echo "Unsupported GitLab target: ${GITLAB_TARGET}"; exit 1 ;; + esac + if [ -z "${GITLAB_TOKEN}" ] || [ -z "${GITLAB_HOST}" ] || [ -z "${GITLAB_PROJECT_PATH}" ]; then + echo "Selected GitLab target token and repository settings are required." + exit 1 + fi + project_encoded="$(PROJECT_PATH="${GITLAB_PROJECT_PATH}" python3 -c 'import os, urllib.parse; print(urllib.parse.quote(os.environ["PROJECT_PATH"], safe=""))')" api="https://${GITLAB_HOST}/api/v4/projects/${project_encoded}/pipeline" @@ -140,6 +212,17 @@ jobs: add_variable("code", os.environ.get("CODE_FILTER", "")) add_variable("system", os.environ.get("SYSTEM_FILTER", "")) + add_variable("nodes", os.environ.get("NODES_FILTER", "")) + add_variable("numproc_node", os.environ.get("NUMPROC_NODE_FILTER", "")) + add_variable("nthreads", os.environ.get("NTHREADS_FILTER", "")) + result_server_target = os.environ.get("RESULT_SERVER_TARGET", "") + if result_server_target == "dev": + add_variable("BK_GITLAB_ENVIRONMENT", "develop") + elif result_server_target == "dev2": + print("GitLab result server target: dev2") + elif result_server_target: + raise SystemExit(f"Unsupported result_server_target: {result_server_target}") + add_variable("BK_EXECUTION_ACTIVITY", os.environ.get("ACTIVITY", "")) add_variable("BK_ALLOCATION_PROJECT_ID", os.environ.get("ALLOCATION_PROJECT_ID", "")) add_variable("app", os.environ.get("BENCHPARK_APP", "")) @@ -212,11 +295,18 @@ jobs: if: always() working-directory: trusted env: - GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }} - GITLAB_REPO_HOST_PATH: ${{ steps.gitlab-repo.outputs.host-path }} + GITLAB_TARGET: ${{ inputs.gitlab_target }} + GITLAB_TOKEN_SWC: ${{ secrets.GITLAB_TOKEN }} + GITLAB_TOKEN_GITLAB_COM: ${{ secrets.GITLAB_COM_TOKEN }} + GITLAB_REPO_HOST_PATH: ${{ steps.gitlab-repo-swc.outputs.host-path || steps.gitlab-repo-com.outputs.host-path }} run: | set -euo pipefail - if [ -n "${GITLAB_TEST_BRANCH:-}" ] && [ -n "${GITLAB_REPO_HOST_PATH:-}" ]; then + case "${GITLAB_TARGET}" in + swc) GITLAB_TOKEN="${GITLAB_TOKEN_SWC}" ;; + gitlab_com) GITLAB_TOKEN="${GITLAB_TOKEN_GITLAB_COM}" ;; + *) GITLAB_TOKEN="" ;; + esac + if [ -n "${GITLAB_TEST_BRANCH:-}" ] && [ -n "${GITLAB_TOKEN:-}" ] && [ -n "${GITLAB_REPO_HOST_PATH:-}" ]; then git remote add gitlab-cleanup "https://oauth2:${GITLAB_TOKEN}@${GITLAB_REPO_HOST_PATH}" || true git push gitlab-cleanup --delete "${GITLAB_TEST_BRANCH}" || true fi diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 84f9030..ce7def2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,6 +23,10 @@ stages: variables: code: "" system: "" + nodes: "" + numproc_node: "" + nthreads: "" + BK_GITLAB_ENVIRONMENT: "" app: "" benchpark: "false" park_only: "false" @@ -64,13 +68,26 @@ variables: echo "No code filter detected, running all programs" fi fi + - | + if [[ -n "$nodes" ]]; then + echo "Nodes filter from API variable: $nodes" + fi + if [[ -n "$numproc_node" ]]; then + echo "Processes-per-node filter from API variable: $numproc_node" + fi + if [[ -n "$nthreads" ]]; then + echo "Threads-per-process filter from API variable: $nthreads" + fi + if [[ -n "$BK_GITLAB_ENVIRONMENT" ]]; then + echo "GitLab environment from API variable: $BK_GITLAB_ENVIRONMENT" + fi generate_matrix: stage: generate before_script: *filters script: - - bash ./scripts/matrix_generate.sh code=$code system=$system + - bash ./scripts/matrix_generate.sh "code=$code" "system=$system" "nodes=$nodes" "numproc_node=$numproc_node" "nthreads=$nthreads" tags: - general artifacts: diff --git a/docs/ci.md b/docs/ci.md index e14e960..e21f180 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -27,9 +27,9 @@ Workflows that push to GitLab or trigger GitLab pipelines use these secrets: | Secret | Format / 形式 | Purpose / 目的 | |---|---|---| -| `GITLAB_TOKEN` | GitLab token with push and pipeline API access / pushとpipeline APIに使えるGitLab token | Authenticates Git operations and Pipeline API calls / Git操作とPipeline API呼び出しを認証する | -| `GITLAB_REPO` | Scheme-less `host/path` such as `gitlab.example.com/group/project.git` / `gitlab.example.com/group/project.git` のようなschemeなし`host/path` | Selects the GitLab project used by sync and manual CI / syncとmanual CIが使うGitLab projectを指定する | -| `GITLAB_COM_TOKEN` | GitLab.com token with push access / GitLab.comへのpush権限を持つtoken | Optionally mirrors protected branches and tags to a secondary GitLab.com mirror / 任意で保護ブランチとtagをGitLab.com上の副mirrorへmirrorする | +| `GITLAB_TOKEN` | GitLab token with push and pipeline API access / pushとpipeline APIに使えるGitLab token | Authenticates SWC GitLab operations and Pipeline API calls / SWC GitLabのGit操作とPipeline API呼び出しを認証する | +| `GITLAB_REPO` | Scheme-less `host/path` such as `gitlab.example.com/group/project.git` / `gitlab.example.com/group/project.git` のようなschemeなし`host/path` | Selects the SWC GitLab project used by sync and manual CI / syncとmanual CIが使うSWC GitLab projectを指定する | +| `GITLAB_COM_TOKEN` | GitLab.com token with push and pipeline API access / GitLab.comへのpushとpipeline APIに使えるtoken | Mirrors protected refs to GitLab.com and can run `GitLab Manual CI` when `gitlab_target=gitlab_com` / GitLab.com上の副mirrorへ保護refをmirrorし、`gitlab_target=gitlab_com`時の`GitLab Manual CI`にも使う | `GITLAB_REPO` に `https://` や `http://` は付けません。`GitLab Manual CI` と `Sync protected branches to GitLab` は同じ形式を検証して使います。 @@ -70,8 +70,14 @@ The workflow accepts these inputs: | Input / 入力 | Description / 説明 | Example / 例 | |---|---|---| | `target_ref` | Branch, tag, or SHA in the upstream repository to test / upstreamリポジトリ内でテストするbranch、tag、SHA | `feature/my-change`, `ci/pr-123`, `develop` | +| `gitlab_target` | GitLab project to receive the temporary test branch and pipeline trigger / 一時テストbranchのpush先およびpipeline trigger先のGitLab project | `swc`, `gitlab_com` | +| `result_server_target` | Result server target selected through GitLab CI/CD variable environment scope. `dev2` uses the default scope; `dev` sets `BK_GITLAB_ENVIRONMENT=develop`. / GitLab CI/CD変数のenvironment scopeで選ぶ結果返送先。`dev2`はdefault scopeを使い、`dev`は`BK_GITLAB_ENVIRONMENT=develop`を渡します | `dev2`, `dev` | | `code` | Benchkit program filter / Benchkitプログラムのフィルタ | `qws,genesis` | | `system` | Benchkit system filter. Legacy Benchpark bridge jobs in this repo do not honor this as a general system selector. / Benchkit systemフィルタ。このrepo内のlegacy Benchpark bridge jobは汎用system selectorとしては扱いません | `Fugaku,MiyabiG` | +| `nodes` | Optional `programs//list.csv` nodes filter. Use this with `code` and `system` to run only selected matrix rows. / 任意の `programs//list.csv` nodesフィルタ。`code`、`system`と併用して特定matrix行だけを実行します | `1`, `1,2` | +| `numproc_node` | Optional `list.csv` processes-per-node filter / 任意の `list.csv` processes-per-nodeフィルタ | `4` | +| `nthreads` | Optional `list.csv` threads-per-process filter / 任意の `list.csv` threads-per-processフィルタ | `32` | +| `activity` | Optional public activity or budget label recorded as `BK_EXECUTION_ACTIVITY` and copied into Result environment metadata / `BK_EXECUTION_ACTIVITY`として記録しResult環境metadataへコピーする任意の公開activityまたはbudget label | `SBDProfile` | | `allocation_project_id` | Optional semantic project/allocation ID passed to GitLab as `BK_ALLOCATION_PROJECT_ID`. Benchkit translates it to scheduler syntax only for systems that require it, for example Slurm `--account=` on RIKYU and PJM `-g ` on Fugaku. / GitLab へ `BK_ALLOCATION_PROJECT_ID` として渡す任意の意味的な project/allocation ID。Benchkit は必要な system に限って scheduler 書式へ変換します。例: RIKYU の Slurm `--account=`、Fugaku の PJM `-g ` | `rkp00010` | | `app` | Legacy Benchpark bridge app filter. Active Benchpark CI/CD/CB result handling is maintained in a separate project. / legacy Benchpark bridge appフィルタ。現行Benchpark CI/CD/CB結果受け取りは別プロジェクト側で管理します | `osu-micro-benchmarks` | | `benchpark` | Enable the legacy Benchpark bridge path together with Benchkit / legacy Benchpark bridge pathも有効化 | `true` | @@ -82,6 +88,10 @@ The workflow accepts these inputs: `GitLab Manual CI` は開発・release candidate検証用であり、本番 `main` branch結果を作る入口ではありません。このworkflowは本番 `main` branchを指す `target_ref` を拒否します。公開用の `main` 計測は、Portal管理のtriggerから、明示的な `code` / `system` scope と明示的な `RESULT_SERVER` pipeline variable を渡して作成します。 +GitHub Actions also shows a branch selector for the workflow file itself. If `main` appears there, it only selects which checked-in workflow definition to run. This workflow rejects the production `main` workflow branch and also rejects `target_ref=main` as a manual benchmark target. + +GitHub Actions UIにはworkflow file自体のbranch selectorも表示されます。そこに`main`が出る場合でも、それはどのworkflow定義を実行するかの選択に過ぎません。このworkflowは本番`main`のworkflow branchからの実行と、manual benchmark targetとしての`target_ref=main`の両方を拒否します。 + このworkflowは以下を行います。 The workflow: @@ -94,8 +104,8 @@ The workflow: - Uses `ci.skip` for that push so the push itself does not start a full GitLab pipeline. - GitLab Pipeline APIを使ってpipelineを明示的に起動します。 - Starts a GitLab pipeline through the GitLab Pipeline API. -- `code`, `system`, `allocation_project_id`, `app`, `park_only`, `park_send`などの指定変数を渡します。 -- Passes the selected scope variables such as `code`, `system`, `allocation_project_id`, `app`, `park_only`, and `park_send`. +- `result_server_target`から決まる`BK_GITLAB_ENVIRONMENT`、および`code`, `system`, `nodes`, `numproc_node`, `nthreads`, `activity`, `allocation_project_id`, `app`, `park_only`, `park_send`などの指定変数を渡します。 +- Passes `BK_GITLAB_ENVIRONMENT` derived from `result_server_target`, plus selected scope variables such as `code`, `system`, `nodes`, `numproc_node`, `nthreads`, `activity`, `allocation_project_id`, `app`, `park_only`, and `park_send`. - GitLab pipelineの完了を待ちます。 - Waits for the GitLab pipeline to finish. - 実行後、一時GitLabブランチを削除します。 @@ -160,6 +170,11 @@ The recommended mechanism is pipeline variables. `GitLab Manual CI` uses pipelin |---|---|---| | `system` | Benchkit system filter. Legacy Benchpark bridge jobs in this repo are not a general multi-system Benchpark runner. / Benchkit systemフィルタ。このrepo内のlegacy Benchpark bridge jobは汎用multi-system Benchpark runnerではありません | `MiyabiG,MiyabiC,RC_GENOA` | | `code` | Benchkit program filter / Benchkit programフィルタ | `qws,genesis` | +| `nodes` | Optional `list.csv` nodes filter for selecting specific Benchkit matrix rows / 特定のBenchkit matrix行を選ぶ任意の`list.csv` nodesフィルタ | `1` | +| `numproc_node` | Optional `list.csv` processes-per-node filter / 任意の`list.csv` processes-per-nodeフィルタ | `4` | +| `nthreads` | Optional `list.csv` threads-per-process filter / 任意の`list.csv` threads-per-processフィルタ | `32` | +| `BK_GITLAB_ENVIRONMENT` | Optional GitLab job environment name used for GitLab CI/CD variable environment scope resolution. When unset, jobs use `$CI_COMMIT_BRANCH`. / GitLab CI/CD変数のenvironment scope解決に使う任意のGitLab job environment名。未指定時は`$CI_COMMIT_BRANCH`を使います | `develop` | +| `BK_EXECUTION_ACTIVITY` | Optional public activity or budget label stored in environment snapshot metadata / environment snapshot metadataに保存する任意の公開activityまたはbudget label | `SBDProfile` | | `BK_ALLOCATION_PROJECT_ID` | Optional semantic project/allocation ID. Benchkit validates the value and derives scheduler arguments only for systems that support it: `--account=` on RIKYU and `-g ` on Fugaku/FugakuCN. / 任意の意味的な project/allocation ID。Benchkit は値を検証し、対応 system に限って scheduler 引数へ変換します。RIKYU では `--account=`、Fugaku/FugakuCN では `-g ` になります | `rkp00010` | | `app` | Legacy Benchpark bridge app filter. Active Benchpark CI/CD/CB result handling has moved to a separate project. / legacy Benchpark bridge appフィルタ。現行Benchpark CI/CD/CB結果受け取りは別プロジェクト側へ移行済み | `osu-micro-benchmarks` | | `benchpark` | Enable the legacy Benchpark bridge path / legacy Benchpark bridge pathを有効化 | `true` | diff --git a/result_server/routes/admin.py b/result_server/routes/admin.py index d216703..17704ad 100644 --- a/result_server/routes/admin.py +++ b/result_server/routes/admin.py @@ -709,6 +709,7 @@ def _build_execution_pipeline_plan(store): park_only=False, park_send=False, allocation_project_id=resolve_result.allocation_project_id, + activity=str(profile.get("activity", "")) if profile else "", scheduler_extra_args="", result_server_url=_portal_result_server_url(), target_id=gitlab_target.id if gitlab_target else gitlab_target_id, diff --git a/result_server/templates/_results_table.html b/result_server/templates/_results_table.html index bb0542c..942b259 100644 --- a/result_server/templates/_results_table.html +++ b/result_server/templates/_results_table.html @@ -88,6 +88,23 @@ display: block; font-weight: 600; } +.activity-cell { + min-width: 120px; + max-width: 190px; + white-space: normal; + line-height: 1.25; +} +.activity-headline { + display: block; + font-weight: 600; +} +.activity-subline { + display: block; + color: #607282; + font-size: 0.82em; + overflow: hidden; + text-overflow: ellipsis; +} .run-cause-subline { display: block; color: #607282; @@ -195,6 +212,13 @@ {% elif key == "system" %} {% include "_results_table_cell_system.html" %} + {% elif key == "activity_context" %} + + {{ row.activity_context.headline }} + {% if row.activity_context.subline %} + {{ row.activity_context.subline }} + {% endif %} + {% elif key in ["nodes", "numproc_node", "nthreads"] %} {{ row[key] }} {% elif key == "profile_summary" %} diff --git a/result_server/tests/test_execution_profiles.py b/result_server/tests/test_execution_profiles.py index d97b9f0..0117994 100644 --- a/result_server/tests/test_execution_profiles.py +++ b/result_server/tests/test_execution_profiles.py @@ -35,6 +35,7 @@ from routes.admin import _portal_result_server_url # noqa: E402 from trigger_runner import ( # noqa: E402 cron_matches, + _build_trigger_plan, _git_ls_remote, run_triggers, ) @@ -583,6 +584,39 @@ def test_trigger_runner_cron_matches_basic_fields(): assert cron_matches("0 2 *", now) == (False, ["cron expression must have 5 fields"]) +def test_trigger_runner_plan_includes_profile_activity(tmp_path, monkeypatch): + monkeypatch.setenv("RESULT_SERVER_GITLAB_REPO", "gitlab.example.org/group/benchkit.git") + db_path = tmp_path / "cx_portal.sqlite3" + store = ExecutionProfileStore(str(db_path)) + store.upsert_profile(_profile(activity="ActivityAlpha"), actor="admin@test.com") + trigger, errors = normalize_trigger_definition( + { + "id": "source-system-demoapp-1400", + "trigger_type": "scheduled", + "profile_id": "source-system-demoapp-nightly", + "target_ref": "develop", + "cron_expr": "0 14 * * *", + "timezone": "Asia/Tokyo", + } + ) + assert errors == [] + assert trigger is not None + + payload, plan_errors = _build_trigger_plan( + store, + trigger, + result_server_url="https://portal.example.test/dev", + trigger_reason="cron:0 14 * * *@2026-08-07T14:00+09:00", + now=datetime(2026, 8, 7, 5, 0, tzinfo=UTC), + ) + + variables = payload["payload"]["variables"] + assert plan_errors == [] + assert variables["BK_EXECUTION_ACTIVITY"] == "ActivityAlpha" + assert variables["BK_ALLOCATION_PROJECT_ID"] == "project00010" + assert variables["RESULT_SERVER"] == "https://portal.example.test/dev" + + def test_trigger_runner_blocks_scheduled_trigger_outside_profile_period( tmp_path, monkeypatch, @@ -2580,6 +2614,7 @@ def test_admin_execution_profiles_dry_run_submit_records_payload(tmp_path, monke payload_record = json.loads(row[4]) variables = payload_record["payload"]["variables"] assert variables["code"] == "demoapp" + assert variables["BK_EXECUTION_ACTIVITY"] == "FutureSystem" assert variables["BK_ALLOCATION_PROJECT_ID"] == "project00010" assert variables["RESULT_SERVER"] == "http://localhost" assert variables["BK_TRIGGER_ID"] == "source-system-demoapp-nightly" @@ -2631,6 +2666,7 @@ def test_admin_execution_profiles_dry_run_uses_profile_scope_values( variables = payload_record["payload"]["variables"] assert variables["code"] == "demoapp" assert variables["system"] == "SourceSystem" + assert variables["BK_EXECUTION_ACTIVITY"] == "FutureSystem" assert variables["BK_ALLOCATION_PROJECT_ID"] == "project00010" assert variables["RESULT_SERVER"] == "http://localhost" assert "exp" not in variables @@ -2766,6 +2802,7 @@ def test_admin_execution_profiles_dry_run_allows_profile_without_allocation( assert row[0] == "dry_run_ready" assert json.loads(row[1]) == [] variables = json.loads(row[2])["payload"]["variables"] + assert variables["BK_EXECUTION_ACTIVITY"] == "FutureSystem" assert "BK_ALLOCATION_PROJECT_ID" not in variables finally: _cleanup(temp_dirs) @@ -2981,6 +3018,7 @@ def fake_submit(plan, *, token): assert token == "secret-token" assert plan.payload["variables"]["code"] == "demoapp" assert plan.payload["variables"]["system"] == "DemoSystem" + assert plan.payload["variables"]["BK_EXECUTION_ACTIVITY"] == "FutureSystem" assert plan.payload["variables"]["BK_ALLOCATION_PROJECT_ID"] == "project00010" assert plan.payload["variables"]["RESULT_SERVER"] == "http://localhost" assert plan.payload["variables"]["BK_TRIGGER_ID"] == "source-system-demoapp-nightly" diff --git a/result_server/tests/test_results_loader.py b/result_server/tests/test_results_loader.py index 593cd71..77aee01 100644 --- a/result_server/tests/test_results_loader.py +++ b/result_server/tests/test_results_loader.py @@ -17,6 +17,7 @@ install_portal_test_stubs() from utils.result_records import load_result_json, load_result_json_batch, summarize_result_quality +from utils.result_table_rows import build_result_table_row from utils.results_loader import load_results_table from utils.table_filters import get_filter_options @@ -275,6 +276,7 @@ def test_existing_columns_unchanged(self, flask_app, tmp_dir): {"label": "FOM", "key": "fom", "tooltip": "Figure of Merit - Benchmark performance metric value with its unit when available"}, {"label": "FOM version", "key": "fom_version", "tooltip": "Version identifier for the FOM measurement section - helps identify which code region was measured when users modify the timing boundaries"}, {"label": "SYSTEM", "key": "system", "tooltip": "Computing system name"}, + {"label": "Activity", "key": "activity_context", "tooltip": "Public activity or budget allocation context recorded with the benchmark run"}, {"label": "Nodes", "key": "nodes"}, {"label": "P/N", "key": "numproc_node", "tooltip": "Number of processes per node"}, {"label": "T/P", "key": "nthreads", "tooltip": "Number of threads per process"}, @@ -297,6 +299,12 @@ def test_public_surface_columns_and_links_are_reduced(self, flask_app, tmp_dir): "FOM": 1.0, "pipeline_id": 17026, "ci_trigger": "schedule", + "environment_snapshot": { + "summary": { + "activity": "SBDProfile", + "allocation_project_id": "project00010", + } + }, "profile_data": { "tool": "ncu", "level": "single", @@ -320,12 +328,15 @@ def test_public_surface_columns_and_links_are_reduced(self, flask_app, tmp_dir): assert "json_link" not in column_keys assert "ci_summary" not in column_keys assert "execution_trigger_summary" not in column_keys + assert "activity_context" in column_keys assert "profile_summary" in column_keys assert rows[0]["json_link"] is None assert rows[0]["data_link"] == f"/results/padata_20250101_120000_{uid}.tgz" assert rows[0]["source_link"] is None assert rows[0]["profile_summary"] == "ncu / single" assert rows[0]["profile_summary_meta"]["ncu_options"] == ["--set", "basic"] + assert rows[0]["activity_context"]["headline"] == "SBDProfile" + assert rows[0]["activity_context"]["subline"] == "allocation project00010" def test_existing_row_fields_preserved(self, flask_app, tmp_dir): """Test case.""" @@ -349,9 +360,43 @@ def test_existing_row_fields_preserved(self, flask_app, tmp_dir): assert row["timestamp"] == "2025-01-01 12:00:00" assert row["numproc_node"] == "48" assert row["nthreads"] == "12" + assert row["activity_context"]["headline"] == "-" assert row["profile_summary"] == "-" assert row["execution_trigger_summary"]["headline"] == "-" + def test_activity_context_falls_back_to_trigger_run_variables(self, flask_app, tmp_dir): + uid = str(uuid.uuid4()) + filename = f"result_20250101_120000_{uid}.json" + _write_json(tmp_dir, filename, { + "code": "demoapp", + "system": "DemoSystem", + "FOM": 1.0, + "pipeline_id": "12345", + }) + trigger_runs_by_pipeline = { + "12345": { + "payload_json": { + "payload": { + "variables": { + "BK_EXECUTION_ACTIVITY": "ActivityAlpha", + "BK_ALLOCATION_PROJECT_ID": "project00020", + } + } + } + } + } + + with flask_app.test_request_context(): + row = build_result_table_row( + filename, + load_result_json(filename, tmp_dir), + [], + trigger_runs_by_pipeline, + ) + + assert row["activity_context"]["headline"] == "ActivityAlpha" + assert row["activity_context"]["subline"] == "allocation project00020" + def test_profile_summary_is_built_from_profile_data(self, flask_app, tmp_dir): uid = str(uuid.uuid4()) _write_json(tmp_dir, f"result_20250101_120000_{uid}.json", { diff --git a/result_server/tests/test_trigger_display.py b/result_server/tests/test_trigger_display.py index d068118..ed59854 100644 --- a/result_server/tests/test_trigger_display.py +++ b/result_server/tests/test_trigger_display.py @@ -172,6 +172,7 @@ def test_summarize_trigger_run_extracts_payload_context(): "variables": { "code": "demoapp", "system": "DemoSystem", + "BK_EXECUTION_ACTIVITY": "ActivityAlpha", "BK_ALLOCATION_PROJECT_ID": "project00010", "RESULT_SERVER": "https://portal.example.org/dev", }, @@ -187,6 +188,7 @@ def test_summarize_trigger_run_extracts_payload_context(): assert summary["target_ref"] == "develop" assert summary["code"] == "demoapp" assert summary["system"] == "DemoSystem" + assert summary["activity"] == "ActivityAlpha" assert summary["allocation_project_id"] == "project00010" assert summary["pipeline_id"] == "3186" assert summary["reason_label"] == "cron 0 14 * * * / 2026-08-07T14:00+09:00" diff --git a/result_server/trigger_runner.py b/result_server/trigger_runner.py index d7032b3..c5c37e2 100644 --- a/result_server/trigger_runner.py +++ b/result_server/trigger_runner.py @@ -208,6 +208,7 @@ def _build_trigger_plan( code=code, system=system, allocation_project_id=profile_result.allocation_project_id, + activity=str(profile.get("activity", "")) if profile else "", scheduler_extra_args="", result_server_url=result_server_url, target_id=gitlab_target.id if gitlab_target else trigger.get("gitlab_target", ""), diff --git a/result_server/utils/gitlab_pipeline.py b/result_server/utils/gitlab_pipeline.py index 84c7b25..fe9d7f2 100644 --- a/result_server/utils/gitlab_pipeline.py +++ b/result_server/utils/gitlab_pipeline.py @@ -180,6 +180,7 @@ def build_pipeline_plan( park_only: bool = False, park_send: bool = False, allocation_project_id: str = "", + activity: str = "", scheduler_extra_args: str = "", result_server_url: str = "", target_id: str = "", @@ -209,6 +210,7 @@ def build_pipeline_plan( _add_variable(variables, "park_only", "true") if park_send: _add_variable(variables, "park_send", "true") + _add_variable(variables, "BK_EXECUTION_ACTIVITY", activity) _add_variable(variables, "BK_ALLOCATION_PROJECT_ID", allocation_project_id) _add_variable(variables, "RESULT_SERVER", result_server_url) if scheduler_extra_args: diff --git a/result_server/utils/result_table_rows.py b/result_server/utils/result_table_rows.py index 6e3f0dc..2651a3c 100644 --- a/result_server/utils/result_table_rows.py +++ b/result_server/utils/result_table_rows.py @@ -50,6 +50,10 @@ def build_result_table_row( "fom_unit": result_data.get("FOM_unit") or "", "fom_version": result_data.get("FOM_version", "N/A"), "system": result_data.get("system", "N/A"), + "activity_context": _build_activity_context( + result_data, + trigger_runs_by_pipeline, + ), "nodes": result_data.get("node_count", "N/A"), "numproc_node": _normalize_optional_field(result_data.get("numproc_node")), "nthreads": _normalize_optional_field(result_data.get("nthreads")), @@ -102,6 +106,110 @@ def _normalize_pipeline_timing(pipeline_timing, key): def _has_vector_metrics(result_data): metrics = result_data.get("metrics", {}) return isinstance(metrics, dict) and "vector" in metrics + + +def _build_activity_context(result_data, trigger_runs_by_pipeline=None): + """Return public activity/allocation context for the result table.""" + trigger_runs_by_pipeline = trigger_runs_by_pipeline or {} + activity = _extract_result_activity(result_data) + allocation_project_id = _extract_result_allocation_project_id(result_data) + + if (not activity or not allocation_project_id) and trigger_runs_by_pipeline: + for pipeline_id in _result_pipeline_ids(result_data): + run = trigger_runs_by_pipeline.get(pipeline_id) + variables = _trigger_run_variables(run) + if not activity: + activity = variables.get("BK_EXECUTION_ACTIVITY", "") + if not allocation_project_id: + allocation_project_id = variables.get("BK_ALLOCATION_PROJECT_ID", "") + if activity and allocation_project_id: + break + + headline = activity or allocation_project_id or "-" + subline = "" + if activity and allocation_project_id: + subline = f"allocation {allocation_project_id}" + elif allocation_project_id: + subline = "allocation" + + title_parts = [] + if activity: + title_parts.append(f"activity={activity}") + if allocation_project_id: + title_parts.append(f"allocation_project_id={allocation_project_id}") + return { + "headline": headline, + "subline": subline, + "title": "; ".join(title_parts) if title_parts else "No activity or allocation metadata was recorded for this result.", + "activity": activity, + "allocation_project_id": allocation_project_id, + } + + +def _extract_result_activity(result_data): + snapshot = result_data.get("environment_snapshot") + if not isinstance(snapshot, dict): + return "" + summary = snapshot.get("summary") + summary = summary if isinstance(summary, dict) else {} + payload = snapshot.get("payload") + payload = payload if isinstance(payload, dict) else {} + execution = payload.get("execution") + execution = execution if isinstance(execution, dict) else {} + return _first_text( + summary.get("activity"), + execution.get("activity"), + ) + + +def _extract_result_allocation_project_id(result_data): + snapshot = result_data.get("environment_snapshot") + if not isinstance(snapshot, dict): + return "" + summary = snapshot.get("summary") + summary = summary if isinstance(summary, dict) else {} + payload = snapshot.get("payload") + payload = payload if isinstance(payload, dict) else {} + system = payload.get("system") + system = system if isinstance(system, dict) else {} + return _first_text( + summary.get("allocation_project_id"), + system.get("allocation_project_id"), + ) + + +def _trigger_run_variables(run): + if not isinstance(run, dict): + return {} + payload = run.get("payload_json") if isinstance(run.get("payload_json"), dict) else {} + plan_payload = payload.get("payload") if isinstance(payload.get("payload"), dict) else {} + variables = plan_payload.get("variables") if isinstance(plan_payload.get("variables"), dict) else {} + return { + str(key): str(value).strip() + for key, value in variables.items() + if value not in (None, "") + } + + +def _result_pipeline_ids(result_data): + values = [] + for value in (result_data.get("pipeline_id"), result_data.get("parent_pipeline_id")): + text = str(value).strip() if value not in (None, "") else "" + if text and text not in values: + values.append(text) + return values + + +def _first_text(*values): + for value in values: + if value is None or isinstance(value, (dict, list)): + continue + text = str(value).strip() + if text: + return text + return "" + + def _find_matching_padata_archive(json_filename, result_data, padata_filenames): result_uuid = extract_result_uuid(json_filename) or result_data.get("_server_uuid") if not result_uuid: diff --git a/result_server/utils/results_loader.py b/result_server/utils/results_loader.py index 05f3918..4623f46 100644 --- a/result_server/utils/results_loader.py +++ b/result_server/utils/results_loader.py @@ -24,6 +24,7 @@ {"label": "FOM", "key": "fom", "tooltip": "Figure of Merit - Benchmark performance metric value with its unit when available"}, {"label": "FOM version", "key": "fom_version", "tooltip": "Version identifier for the FOM measurement section - helps identify which code region was measured when users modify the timing boundaries"}, {"label": "SYSTEM", "key": "system", "tooltip": "Computing system name"}, + {"label": "Activity", "key": "activity_context", "tooltip": "Public activity or budget allocation context recorded with the benchmark run"}, {"label": "Nodes", "key": "nodes"}, {"label": "P/N", "key": "numproc_node", "tooltip": "Number of processes per node"}, {"label": "T/P", "key": "nthreads", "tooltip": "Number of threads per process"}, diff --git a/result_server/utils/trigger_display.py b/result_server/utils/trigger_display.py index b07bef7..ca6ef27 100644 --- a/result_server/utils/trigger_display.py +++ b/result_server/utils/trigger_display.py @@ -140,6 +140,7 @@ def summarize_trigger_run(run: dict[str, Any]) -> dict[str, Any]: "target_ref": plan_payload.get("ref") or "-", "code": variables.get("code") or "-", "system": variables.get("system") or "-", + "activity": variables.get("BK_EXECUTION_ACTIVITY") or "-", "allocation_project_id": variables.get("BK_ALLOCATION_PROJECT_ID") or "-", "result_server": variables.get("RESULT_SERVER") or "-", "pipeline_id": _trigger_run_pipeline_ids(run)[0] if _trigger_run_pipeline_ids(run) else "-", diff --git a/scripts/collect_environment_snapshot.sh b/scripts/collect_environment_snapshot.sh index 05b4856..843b542 100755 --- a/scripts/collect_environment_snapshot.sh +++ b/scripts/collect_environment_snapshot.sh @@ -210,6 +210,7 @@ jq -n \ --arg stage "$snapshot_stage" \ --arg collected_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ --arg system "${BK_SYSTEM:-${system:-}}" \ + --arg execution_activity "${BK_EXECUTION_ACTIVITY:-}" \ --arg allocation_project_id "${BK_ALLOCATION_PROJECT_ID:-}" \ --arg runner_description "${CI_RUNNER_DESCRIPTION:-}" \ --arg runner_id "${CI_RUNNER_ID:-}" \ @@ -254,6 +255,9 @@ jq -n \ cpu_model: $cpu_model } }, + execution: { + activity: $execution_activity + }, scheduler: { kind: $scheduler_kind, slurm_job_id: $slurm_job_id, diff --git a/scripts/job_functions.sh b/scripts/job_functions.sh index 4013212..2dc32f4 100644 --- a/scripts/job_functions.sh +++ b/scripts/job_functions.sh @@ -215,6 +215,7 @@ match_filter() { IFS=',' read -ra FILTER_LIST <<< "$filter_str" for filter_item in "${FILTER_LIST[@]}"; do + filter_item=$(echo "$filter_item" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') if [[ "$target" == "$filter_item" ]]; then return 0 fi @@ -222,6 +223,24 @@ match_filter() { return 1 } +gitlab_environment_name_yaml() { + local environment_name="${BK_GITLAB_ENVIRONMENT:-}" + local escaped + + if [[ -z "$environment_name" ]]; then + printf '$CI_COMMIT_BRANCH' + return 0 + fi + + if [[ ! "$environment_name" =~ ^[A-Za-z0-9][A-Za-z0-9_.:/-]{0,127}$ ]]; then + echo "ERROR: BK_GITLAB_ENVIRONMENT contains unsupported characters" >&2 + return 1 + fi + + escaped=$(printf '%s' "$environment_name" | sed 's/\\/\\\\/g; s/"/\\"/g') + printf '"%s"' "$escaped" +} + # send_results ジョブの YAML ブロックを生成 # $1: ジョブ名プレフィックス(例: qws_Fugaku_N1_P4_T12) # $2: 依存ジョブ名(needs に指定するジョブ名) @@ -240,6 +259,9 @@ emit_send_results_job() { local mode="$6" local build_job="$7" local run_job="$8" + local environment_name + + environment_name=$(gitlab_environment_name_yaml) echo " ${job_prefix}_send_results: @@ -247,7 +269,7 @@ ${job_prefix}_send_results: needs: [\"${depends_on}\"] tags: [fncx-curl-jq] environment: - name: \$CI_COMMIT_BRANCH + name: ${environment_name} script: - id - bash -lc 'ls -la results/ 2>&1 | head -20' @@ -325,8 +347,10 @@ emit_estimate_job() { local code="$4" local output="$5" local estimate_runner_tag="${BK_ESTIMATE_RUNNER_TAG:-fncx-estimate-python}" + local environment_name estimate_runner_tag=$(printf '%s' "$estimate_runner_tag" | sed 's/"/\\"/g') + environment_name=$(gitlab_environment_name_yaml) echo " ${job_prefix}_estimate: @@ -334,7 +358,7 @@ ${job_prefix}_estimate: needs: [\"${depends_on}\"] tags: [\"${estimate_runner_tag}\"] environment: - name: \$CI_COMMIT_BRANCH + name: ${environment_name} script: - echo \"Running estimation for ${code}\" - if [ -d send_results_workspace/results ]; then cp -R send_results_workspace/results results; fi @@ -355,6 +379,9 @@ emit_send_estimate_job() { local job_prefix="$1" local depends_on="$2" local output="$3" + local environment_name + + environment_name=$(gitlab_environment_name_yaml) echo " ${job_prefix}_send_estimate: @@ -362,7 +389,7 @@ ${job_prefix}_send_estimate: needs: [\"${depends_on}\"] tags: [fncx-curl-jq] environment: - name: \$CI_COMMIT_BRANCH + name: ${environment_name} script: - bash scripts/result_server/send_estimate.sh diff --git a/scripts/matrix_generate.sh b/scripts/matrix_generate.sh index 3459cdb..1ef763e 100644 --- a/scripts/matrix_generate.sh +++ b/scripts/matrix_generate.sh @@ -20,6 +20,9 @@ source ./scripts/job_functions.sh CODE_FILTER="" SYSTEM_FILTER="" +NODES_FILTER="" +NUMPROC_NODE_FILTER="" +NTHREADS_FILTER="" ESTIMATE_RUNNER_TAG="${BK_ESTIMATE_RUNNER_TAG:-fncx-estimate-python}" ESTIMATE_RUNNER_TAG=$(printf '%s' "$ESTIMATE_RUNNER_TAG" | sed 's/"/\\"/g') @@ -27,6 +30,9 @@ while [[ $# -gt 0 ]]; do case $1 in code=*) CODE_FILTER="${1#code=}" ;; system=*) SYSTEM_FILTER="${1#system=}" ;; + nodes=*) NODES_FILTER="${1#nodes=}" ;; + numproc_node=*) NUMPROC_NODE_FILTER="${1#numproc_node=}" ;; + nthreads=*) NTHREADS_FILTER="${1#nthreads=}" ;; *) echo "Unknown argument: $1"; exit 1 ;; esac shift @@ -64,6 +70,9 @@ for listfile in programs/*/list.csv; do parse_list_csv_line "$system" "$enable" "$nodes" "$numproc_node" "$nthreads" "$elapse" || continue match_filter "$SYSTEM_FILTER" "$csv_system" || continue + match_filter "$NODES_FILTER" "$csv_nodes" || continue + match_filter "$NUMPROC_NODE_FILTER" "$csv_numproc_node" || continue + match_filter "$NTHREADS_FILTER" "$csv_nthreads" || continue system="$csv_system" nodes="$csv_nodes" diff --git a/scripts/result.sh b/scripts/result.sh index 36d22ad..b481fcf 100644 --- a/scripts/result.sh +++ b/scripts/result.sh @@ -668,6 +668,7 @@ build_environment_snapshot_block() { schema_version: 1, collected_at: ($primary.collected_at // ""), system: ($primary.system // {}), + execution: ($primary.execution // {}), scheduler: ($primary.scheduler // {}), runner: ($primary.runner // {}), ci: ($primary.ci // {}), @@ -718,6 +719,7 @@ build_environment_snapshot_block() { hash: $hash, summary: { system: ($payload.system.name // ""), + activity: ($payload.execution.activity // ""), allocation_project_id: ($payload.system.allocation_project_id // ""), scheduler: ($payload.scheduler.kind // ""), runner: ($payload.runner.description // ""), diff --git a/scripts/tests/run_profile_data_shell_tests.sh b/scripts/tests/run_profile_data_shell_tests.sh index bdea4c7..67f4c1c 100644 --- a/scripts/tests/run_profile_data_shell_tests.sh +++ b/scripts/tests/run_profile_data_shell_tests.sh @@ -50,6 +50,7 @@ scripts/tests/test_ci_timing_context.sh scripts/tests/test_ncu_plan_generation.sh scripts/tests/test_sbd_ncu_profile.sh scripts/tests/test_scheduler_extra_args.sh +scripts/tests/test_matrix_generate_filters.sh EOF ;; result-sender) diff --git a/scripts/tests/test_build_environment_snapshot.sh b/scripts/tests/test_build_environment_snapshot.sh index e196245..66b76b9 100644 --- a/scripts/tests/test_build_environment_snapshot.sh +++ b/scripts/tests/test_build_environment_snapshot.sh @@ -87,6 +87,7 @@ chmod +x "${TMP_DIR}/bin/nvc" pushd "${TMP_DIR}/project/src" >/dev/null export BK_SYSTEM=TestSystem +export BK_EXECUTION_ACTIVITY=ActivityAlpha export BK_BENCHKIT_ROOT="${TMP_DIR}/project" export BK_SNAPSHOT_TOOL_COMMANDS="make bash nvcc nvc" export BK_SNAPSHOT_ENV_VARS="CC SECRET_TOKEN" @@ -107,6 +108,7 @@ jq -e \ ' .stage == "build_actual" and .system.name == "TestSystem" and + .execution.activity == "ActivityAlpha" and .toolchain.commands.make.path == $make_path and (.toolchain.commands.bash.path | length) > 0 and .toolchain.nvcc == "Cuda compilation tools, release 13.1, V13.1.115" and diff --git a/scripts/tests/test_matrix_generate_filters.sh b/scripts/tests/test_matrix_generate_filters.sh new file mode 100644 index 0000000..f569f8f --- /dev/null +++ b/scripts/tests/test_matrix_generate_filters.sh @@ -0,0 +1,47 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +REPO_DIR=$(cd "${SCRIPT_DIR}/../.." && pwd) + +TMP_DIR=$(mktemp -d) +trap 'rm -rf "${TMP_DIR}"' EXIT + +mkdir -p "${TMP_DIR}/project" +ln -s "${REPO_DIR}/config" "${TMP_DIR}/project/config" +ln -s "${REPO_DIR}/programs" "${TMP_DIR}/project/programs" +ln -s "${REPO_DIR}/scripts" "${TMP_DIR}/project/scripts" + +pushd "${TMP_DIR}/project" >/dev/null + +bash scripts/matrix_generate.sh code=sbd system=RIKYU nodes=1 +grep -q '^sbd_RIKYU_N1_P4_T32_run:' .gitlab-ci.generated.yml +grep -q 'name: $CI_COMMIT_BRANCH' .gitlab-ci.generated.yml +if grep -q '^sbd_RIKYU_N2_P4_T32_run:' .gitlab-ci.generated.yml; then + echo "nodes=1 filter must not emit the SBD RIKYU 2-node job" >&2 + exit 1 +fi +if grep -q '^sbd_RIKYU_N4_P4_T32_run:' .gitlab-ci.generated.yml; then + echo "nodes=1 filter must not emit the SBD RIKYU 4-node job" >&2 + exit 1 +fi + +bash scripts/matrix_generate.sh code=sbd system=RIKYU nodes='2, 4' +if grep -q '^sbd_RIKYU_N1_P4_T32_run:' .gitlab-ci.generated.yml; then + echo "nodes=2,4 filter must not emit the SBD RIKYU 1-node job" >&2 + exit 1 +fi +grep -q '^sbd_RIKYU_N2_P4_T32_run:' .gitlab-ci.generated.yml +grep -q '^sbd_RIKYU_N4_P4_T32_run:' .gitlab-ci.generated.yml + +BK_GITLAB_ENVIRONMENT=develop bash scripts/matrix_generate.sh code=sbd system=RIKYU nodes=1 +grep -q 'name: "develop"' .gitlab-ci.generated.yml + +if BK_GITLAB_ENVIRONMENT='bad scope' bash scripts/matrix_generate.sh code=sbd system=RIKYU nodes=1 >/dev/null 2>&1; then + echo "invalid BK_GITLAB_ENVIRONMENT must fail matrix generation" >&2 + exit 1 +fi + +popd >/dev/null + +echo "matrix generation filter test passed" diff --git a/scripts/tests/test_result_common_json_contract.sh b/scripts/tests/test_result_common_json_contract.sh index 597f44e..e2d27e3 100644 --- a/scripts/tests/test_result_common_json_contract.sh +++ b/scripts/tests/test_result_common_json_contract.sh @@ -116,6 +116,9 @@ cat > "${TMP_DIR}/results/environment_snapshot_build.json" <<'EOF' "system": { "name": "DemoSystem" }, + "execution": { + "activity": "ActivityAlpha" + }, "scheduler": { "kind": "batch" }, @@ -144,6 +147,9 @@ cat > "${TMP_DIR}/results/environment_snapshot_run.json" <<'EOF' "system": { "name": "DemoSystem" }, + "execution": { + "activity": "ActivityAlpha" + }, "scheduler": { "kind": "batch" }, @@ -324,6 +330,7 @@ jq -e ' .environment_snapshot.schema_version == 1 and (.environment_snapshot.hash | startswith("sha256:")) and .environment_snapshot.summary.system == "DemoSystem" and + .environment_snapshot.summary.activity == "ActivityAlpha" and .environment_snapshot.summary.scheduler == "batch" and .environment_snapshot.summary.runner == "demo-runner" and .environment_snapshot.summary.ci_pipeline_id == "4242" and