mirror of
https://github.com/actions/runner.git
synced 2026-02-15 18:18:00 +08:00
Compare commits
35 Commits
v2.330.0
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
100be5b97e | ||
|
|
6680090084 | ||
|
|
15cb558d8f | ||
|
|
d5a8a936c1 | ||
|
|
cdb77c6804 | ||
|
|
a4a19b152e | ||
|
|
1b5486aa8f | ||
|
|
4214709d1b | ||
|
|
3ffedabea3 | ||
|
|
3a80a78cae | ||
|
|
6822f4aba2 | ||
|
|
ad43c639cf | ||
|
|
5d4fb30d5b | ||
|
|
1df72a54ca | ||
|
|
02013cf967 | ||
|
|
7d5c17a190 | ||
|
|
3f43560cb9 | ||
|
|
73f7dbb681 | ||
|
|
f554a6446d | ||
|
|
bdceac4ab3 | ||
|
|
3f1dd45172 | ||
|
|
cf8f50b4d8 | ||
|
|
2cf22c4858 | ||
|
|
04d77df0c7 | ||
|
|
651077689d | ||
|
|
c96dcd4729 | ||
|
|
4b0058f15c | ||
|
|
87d1dfb798 | ||
|
|
c992a2b406 | ||
|
|
b2204f1fab | ||
|
|
f99c3e6ee8 | ||
|
|
463496e4fb | ||
|
|
3f9f6f3994 | ||
|
|
221f65874f | ||
|
|
9a21440691 |
@@ -4,7 +4,7 @@
|
|||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
||||||
"ghcr.io/devcontainers/features/dotnet": {
|
"ghcr.io/devcontainers/features/dotnet": {
|
||||||
"version": "8.0.416"
|
"version": "8.0.417"
|
||||||
},
|
},
|
||||||
"ghcr.io/devcontainers/features/node:1": {
|
"ghcr.io/devcontainers/features/node:1": {
|
||||||
"version": "20"
|
"version": "20"
|
||||||
|
|||||||
52
.github/workflows/build.yml
vendored
52
.github/workflows/build.yml
vendored
@@ -14,6 +14,9 @@ on:
|
|||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.md'
|
- '**.md'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
@@ -50,7 +53,7 @@ jobs:
|
|||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
# Build runner layout
|
# Build runner layout
|
||||||
- name: Build & Layout Release
|
- name: Build & Layout Release
|
||||||
@@ -75,8 +78,53 @@ jobs:
|
|||||||
# Upload runner package tar.gz/zip as artifact
|
# Upload runner package tar.gz/zip as artifact
|
||||||
- name: Publish Artifact
|
- name: Publish Artifact
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: runner-package-${{ matrix.runtime }}
|
name: runner-package-${{ matrix.runtime }}
|
||||||
path: |
|
path: |
|
||||||
_package
|
_package
|
||||||
|
|
||||||
|
docker:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ ubuntu-latest, ubuntu-24.04-arm ]
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
docker_platform: linux/amd64
|
||||||
|
- os: ubuntu-24.04-arm
|
||||||
|
docker_platform: linux/arm64
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Get latest runner version
|
||||||
|
id: latest_runner
|
||||||
|
uses: actions/github-script@v8
|
||||||
|
with:
|
||||||
|
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
script: |
|
||||||
|
const release = await github.rest.repos.getLatestRelease({
|
||||||
|
owner: 'actions',
|
||||||
|
repo: 'runner',
|
||||||
|
});
|
||||||
|
const version = release.data.tag_name.replace(/^v/, '');
|
||||||
|
core.setOutput('version', version);
|
||||||
|
|
||||||
|
- name: Setup Docker buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build Docker image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: ./images
|
||||||
|
load: true
|
||||||
|
platforms: ${{ matrix.docker_platform }}
|
||||||
|
tags: |
|
||||||
|
${{ github.sha }}:latest
|
||||||
|
build-args: |
|
||||||
|
RUNNER_VERSION=${{ steps.latest_runner.outputs.version }}
|
||||||
|
|
||||||
|
- name: Test Docker image
|
||||||
|
run: |
|
||||||
|
docker run --rm ${{ github.sha }}:latest ./run.sh --version
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
|
|||||||
2
.github/workflows/dependency-check.yml
vendored
2
.github/workflows/dependency-check.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
|||||||
npm-vulnerabilities: ${{ steps.check-versions.outputs.npm-vulnerabilities }}
|
npm-vulnerabilities: ${{ steps.check-versions.outputs.npm-vulnerabilities }}
|
||||||
open-dependency-prs: ${{ steps.check-prs.outputs.open-dependency-prs }}
|
open-dependency-prs: ${{ steps.check-prs.outputs.open-dependency-prs }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
|
|||||||
4
.github/workflows/docker-buildx-upgrade.yml
vendored
4
.github/workflows/docker-buildx-upgrade.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
BUILDX_CURRENT_VERSION: ${{ steps.check_buildx_version.outputs.CURRENT_VERSION }}
|
BUILDX_CURRENT_VERSION: ${{ steps.check_buildx_version.outputs.CURRENT_VERSION }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Check Docker version
|
- name: Check Docker version
|
||||||
id: check_docker_version
|
id: check_docker_version
|
||||||
@@ -89,7 +89,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Update Docker version
|
- name: Update Docker version
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
75
.github/workflows/docker-publish.yml
vendored
Normal file
75
.github/workflows/docker-publish.yml
vendored
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
name: Publish DockerImage from Release Branch
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
releaseBranch:
|
||||||
|
description: 'Release Branch (releases/mXXX)'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish-image:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
id-token: write
|
||||||
|
attestations: write
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: ${{ github.repository_owner }}/actions-runner
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.inputs.releaseBranch }}
|
||||||
|
|
||||||
|
- name: Compute image version
|
||||||
|
id: image
|
||||||
|
uses: actions/github-script@v8
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const fs = require('fs');
|
||||||
|
const runnerVersion = fs.readFileSync('${{ github.workspace }}/releaseVersion', 'utf8').replace(/\n$/g, '');
|
||||||
|
console.log(`Using runner version ${runnerVersion}`);
|
||||||
|
if (!/^\d+\.\d+\.\d+$/.test(runnerVersion)) {
|
||||||
|
throw new Error(`Invalid runner version: ${runnerVersion}`);
|
||||||
|
}
|
||||||
|
core.setOutput('version', runnerVersion);
|
||||||
|
|
||||||
|
- name: Setup Docker buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log into registry ${{ env.REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
id: build-and-push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: ./images
|
||||||
|
platforms: |
|
||||||
|
linux/amd64
|
||||||
|
linux/arm64
|
||||||
|
tags: |
|
||||||
|
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.image.outputs.version }}
|
||||||
|
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
build-args: |
|
||||||
|
RUNNER_VERSION=${{ steps.image.outputs.version }}
|
||||||
|
push: true
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.source=${{github.server_url}}/${{github.repository}}
|
||||||
|
org.opencontainers.image.licenses=MIT
|
||||||
|
annotations: |
|
||||||
|
org.opencontainers.image.description=https://github.com/actions/runner/releases/tag/v${{ steps.image.outputs.version }}
|
||||||
|
|
||||||
|
- name: Generate attestation
|
||||||
|
uses: actions/attest-build-provenance@v3
|
||||||
|
with:
|
||||||
|
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
subject-digest: ${{ steps.build-and-push.outputs.digest }}
|
||||||
|
push-to-registry: true
|
||||||
4
.github/workflows/dotnet-upgrade.yml
vendored
4
.github/workflows/dotnet-upgrade.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
DOTNET_CURRENT_MAJOR_MINOR_VERSION: ${{ steps.fetch_current_version.outputs.DOTNET_CURRENT_MAJOR_MINOR_VERSION }}
|
DOTNET_CURRENT_MAJOR_MINOR_VERSION: ${{ steps.fetch_current_version.outputs.DOTNET_CURRENT_MAJOR_MINOR_VERSION }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
- name: Get current major minor version
|
- name: Get current major minor version
|
||||||
id: fetch_current_version
|
id: fetch_current_version
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -89,7 +89,7 @@ jobs:
|
|||||||
if: ${{ needs.dotnet-update.outputs.SHOULD_UPDATE == 1 && needs.dotnet-update.outputs.BRANCH_EXISTS == 0 }}
|
if: ${{ needs.dotnet-update.outputs.SHOULD_UPDATE == 1 && needs.dotnet-update.outputs.BRANCH_EXISTS == 0 }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: feature/dotnetsdk-upgrade/${{ needs.dotnet-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
|
ref: feature/dotnetsdk-upgrade/${{ needs.dotnet-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
|
|||||||
2
.github/workflows/node-upgrade.yml
vendored
2
.github/workflows/node-upgrade.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
update-node:
|
update-node:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- name: Get latest Node versions
|
- name: Get latest Node versions
|
||||||
id: node-versions
|
id: node-versions
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
2
.github/workflows/npm-audit-typescript.yml
vendored
2
.github/workflows/npm-audit-typescript.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
|||||||
npm-audit-with-ts-fix:
|
npm-audit-with-ts-fix:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/npm-audit.yml
vendored
2
.github/workflows/npm-audit.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
npm-audit:
|
npm-audit:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
|
|||||||
33
.github/workflows/release.yml
vendored
33
.github/workflows/release.yml
vendored
@@ -11,12 +11,12 @@ jobs:
|
|||||||
if: startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/main'
|
if: startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/main'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
# Make sure ./releaseVersion match ./src/runnerversion
|
# Make sure ./releaseVersion match ./src/runnerversion
|
||||||
# Query GitHub release ensure version is not used
|
# Query GitHub release ensure version is not used
|
||||||
- name: Check version
|
- name: Check version
|
||||||
uses: actions/github-script@v8.0.0
|
uses: actions/github-script@v8
|
||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||||
script: |
|
script: |
|
||||||
@@ -86,7 +86,7 @@ jobs:
|
|||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
# Build runner layout
|
# Build runner layout
|
||||||
- name: Build & Layout Release
|
- name: Build & Layout Release
|
||||||
@@ -118,7 +118,7 @@ jobs:
|
|||||||
# Upload runner package tar.gz/zip as artifact.
|
# Upload runner package tar.gz/zip as artifact.
|
||||||
- name: Publish Artifact
|
- name: Publish Artifact
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: runner-packages-${{ matrix.runtime }}
|
name: runner-packages-${{ matrix.runtime }}
|
||||||
path: |
|
path: |
|
||||||
@@ -129,41 +129,41 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
# Download runner package tar.gz/zip produced by 'build' job
|
# Download runner package tar.gz/zip produced by 'build' job
|
||||||
- name: Download Artifact (win-x64)
|
- name: Download Artifact (win-x64)
|
||||||
uses: actions/download-artifact@v6
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: runner-packages-win-x64
|
name: runner-packages-win-x64
|
||||||
path: ./
|
path: ./
|
||||||
- name: Download Artifact (win-arm64)
|
- name: Download Artifact (win-arm64)
|
||||||
uses: actions/download-artifact@v6
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: runner-packages-win-arm64
|
name: runner-packages-win-arm64
|
||||||
path: ./
|
path: ./
|
||||||
- name: Download Artifact (osx-x64)
|
- name: Download Artifact (osx-x64)
|
||||||
uses: actions/download-artifact@v6
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: runner-packages-osx-x64
|
name: runner-packages-osx-x64
|
||||||
path: ./
|
path: ./
|
||||||
- name: Download Artifact (osx-arm64)
|
- name: Download Artifact (osx-arm64)
|
||||||
uses: actions/download-artifact@v6
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: runner-packages-osx-arm64
|
name: runner-packages-osx-arm64
|
||||||
path: ./
|
path: ./
|
||||||
- name: Download Artifact (linux-x64)
|
- name: Download Artifact (linux-x64)
|
||||||
uses: actions/download-artifact@v6
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: runner-packages-linux-x64
|
name: runner-packages-linux-x64
|
||||||
path: ./
|
path: ./
|
||||||
- name: Download Artifact (linux-arm)
|
- name: Download Artifact (linux-arm)
|
||||||
uses: actions/download-artifact@v6
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: runner-packages-linux-arm
|
name: runner-packages-linux-arm
|
||||||
path: ./
|
path: ./
|
||||||
- name: Download Artifact (linux-arm64)
|
- name: Download Artifact (linux-arm64)
|
||||||
uses: actions/download-artifact@v6
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: runner-packages-linux-arm64
|
name: runner-packages-linux-arm64
|
||||||
path: ./
|
path: ./
|
||||||
@@ -171,7 +171,7 @@ jobs:
|
|||||||
# Create ReleaseNote file
|
# Create ReleaseNote file
|
||||||
- name: Create ReleaseNote
|
- name: Create ReleaseNote
|
||||||
id: releaseNote
|
id: releaseNote
|
||||||
uses: actions/github-script@v8.0.0
|
uses: actions/github-script@v8
|
||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||||
script: |
|
script: |
|
||||||
@@ -296,11 +296,11 @@ jobs:
|
|||||||
IMAGE_NAME: ${{ github.repository_owner }}/actions-runner
|
IMAGE_NAME: ${{ github.repository_owner }}/actions-runner
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Compute image version
|
- name: Compute image version
|
||||||
id: image
|
id: image
|
||||||
uses: actions/github-script@v8.0.0
|
uses: actions/github-script@v8
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@@ -334,8 +334,9 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
labels: |
|
labels: |
|
||||||
org.opencontainers.image.source=${{github.server_url}}/${{github.repository}}
|
org.opencontainers.image.source=${{github.server_url}}/${{github.repository}}
|
||||||
org.opencontainers.image.description=https://github.com/actions/runner/releases/tag/v${{ steps.image.outputs.version }}
|
|
||||||
org.opencontainers.image.licenses=MIT
|
org.opencontainers.image.licenses=MIT
|
||||||
|
annotations: |
|
||||||
|
org.opencontainers.image.description=https://github.com/actions/runner/releases/tag/v${{ steps.image.outputs.version }}
|
||||||
|
|
||||||
- name: Generate attestation
|
- name: Generate attestation
|
||||||
uses: actions/attest-build-provenance@v3
|
uses: actions/attest-build-provenance@v3
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ Debian based OS (Debian, Ubuntu, Linux Mint)
|
|||||||
- liblttng-ust1 or liblttng-ust0
|
- liblttng-ust1 or liblttng-ust0
|
||||||
- libkrb5-3
|
- libkrb5-3
|
||||||
- zlib1g
|
- zlib1g
|
||||||
- libssl1.1, libssl1.0.2 or libssl1.0.0
|
- libssl3t64, libssl3, libssl1.1, libssl1.0.2 or libssl1.0.0
|
||||||
- libicu63, libicu60, libicu57 or libicu55
|
- libicu76, libicu75, ..., libicu66, libicu65, libicu63, libicu60, libicu57, libicu55, or libicu52
|
||||||
|
|
||||||
Fedora based OS (Fedora, Red Hat Enterprise Linux, CentOS, Oracle Linux 7)
|
Fedora based OS (Fedora, Red Hat Enterprise Linux, CentOS, Oracle Linux 7)
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# Source: https://github.com/dotnet/dotnet-docker
|
# Source: https://github.com/dotnet/dotnet-docker
|
||||||
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy AS build
|
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-noble AS build
|
||||||
|
|
||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG RUNNER_VERSION
|
ARG RUNNER_VERSION
|
||||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.7.0
|
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.7.0
|
||||||
ARG DOCKER_VERSION=29.0.1
|
ARG DOCKER_VERSION=29.2.0
|
||||||
ARG BUILDX_VERSION=0.30.0
|
ARG BUILDX_VERSION=0.31.1
|
||||||
|
|
||||||
RUN apt update -y && apt install curl unzip -y
|
RUN apt update -y && apt install curl unzip -y
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ RUN curl -f -L -o runner-container-hooks.zip https://github.com/actions/runner-c
|
|||||||
&& unzip ./runner-container-hooks.zip -d ./k8s \
|
&& unzip ./runner-container-hooks.zip -d ./k8s \
|
||||||
&& rm runner-container-hooks.zip
|
&& rm runner-container-hooks.zip
|
||||||
|
|
||||||
RUN curl -f -L -o runner-container-hooks.zip https://github.com/actions/runner-container-hooks/releases/download/v0.8.0/actions-runner-hooks-k8s-0.8.0.zip \
|
RUN curl -f -L -o runner-container-hooks.zip https://github.com/actions/runner-container-hooks/releases/download/v0.8.1/actions-runner-hooks-k8s-0.8.1.zip \
|
||||||
&& unzip ./runner-container-hooks.zip -d ./k8s-novolume \
|
&& unzip ./runner-container-hooks.zip -d ./k8s-novolume \
|
||||||
&& rm runner-container-hooks.zip
|
&& rm runner-container-hooks.zip
|
||||||
|
|
||||||
@@ -36,12 +36,12 @@ RUN export RUNNER_ARCH=${TARGETARCH} \
|
|||||||
"https://github.com/docker/buildx/releases/download/v${BUILDX_VERSION}/buildx-v${BUILDX_VERSION}.linux-${TARGETARCH}" \
|
"https://github.com/docker/buildx/releases/download/v${BUILDX_VERSION}/buildx-v${BUILDX_VERSION}.linux-${TARGETARCH}" \
|
||||||
&& chmod +x /usr/local/lib/docker/cli-plugins/docker-buildx
|
&& chmod +x /usr/local/lib/docker/cli-plugins/docker-buildx
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy
|
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-noble
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
ENV RUNNER_MANUALLY_TRAP_SIG=1
|
ENV RUNNER_MANUALLY_TRAP_SIG=1
|
||||||
ENV ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT=1
|
ENV ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT=1
|
||||||
ENV ImageOS=ubuntu22
|
ENV ImageOS=ubuntu24
|
||||||
|
|
||||||
# 'gpg-agent' and 'software-properties-common' are needed for the 'add-apt-repository' command that follows
|
# 'gpg-agent' and 'software-properties-common' are needed for the 'add-apt-repository' command that follows
|
||||||
RUN apt update -y \
|
RUN apt update -y \
|
||||||
@@ -54,8 +54,6 @@ RUN add-apt-repository ppa:git-core/ppa \
|
|||||||
&& apt install -y git \
|
&& apt install -y git \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /home/runner
|
|
||||||
|
|
||||||
RUN adduser --disabled-password --gecos "" --uid 1001 runner \
|
RUN adduser --disabled-password --gecos "" --uid 1001 runner \
|
||||||
&& groupadd docker --gid 123 \
|
&& groupadd docker --gid 123 \
|
||||||
&& usermod -aG sudo runner \
|
&& usermod -aG sudo runner \
|
||||||
@@ -64,6 +62,8 @@ RUN adduser --disabled-password --gecos "" --uid 1001 runner \
|
|||||||
&& echo "Defaults env_keep += \"DEBIAN_FRONTEND\"" >> /etc/sudoers \
|
&& echo "Defaults env_keep += \"DEBIAN_FRONTEND\"" >> /etc/sudoers \
|
||||||
&& chmod 777 /home/runner
|
&& chmod 777 /home/runner
|
||||||
|
|
||||||
|
WORKDIR /home/runner
|
||||||
|
|
||||||
COPY --chown=runner:docker --from=build /actions-runner .
|
COPY --chown=runner:docker --from=build /actions-runner .
|
||||||
COPY --from=build /usr/local/lib/docker/cli-plugins/docker-buildx /usr/local/lib/docker/cli-plugins/docker-buildx
|
COPY --from=build /usr/local/lib/docker/cli-plugins/docker-buildx /usr/local/lib/docker/cli-plugins/docker-buildx
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,27 @@
|
|||||||
## What's Changed
|
## What's Changed
|
||||||
* Custom Image: Preflight checks by @lawrencegripper in https://github.com/actions/runner/pull/4081
|
* Fix owner of /home/runner directory by @nikola-jokic in https://github.com/actions/runner/pull/4132
|
||||||
* Update dotnet sdk to latest version @8.0.415 by @github-actions[bot] in https://github.com/actions/runner/pull/4080
|
* Update Docker to v29.0.2 and Buildx to v0.30.1 by @github-actions[bot] in https://github.com/actions/runner/pull/4135
|
||||||
* Link to an extant discussion category by @jsoref in https://github.com/actions/runner/pull/4084
|
* Update workflow around runner docker image. by @TingluoHuang in https://github.com/actions/runner/pull/4133
|
||||||
* Improve logic around decide IsHostedServer. by @TingluoHuang in https://github.com/actions/runner/pull/4086
|
* Fix regex for validating runner version format by @TingluoHuang in https://github.com/actions/runner/pull/4136
|
||||||
* chore: update Node versions by @github-actions[bot] in https://github.com/actions/runner/pull/4093
|
* chore: update Node versions by @github-actions[bot] in https://github.com/actions/runner/pull/4144
|
||||||
* Compare updated template evaluator by @ericsciple in https://github.com/actions/runner/pull/4092
|
* Ensure safe_sleep tries alternative approaches by @TingluoHuang in https://github.com/actions/runner/pull/4146
|
||||||
* fix(dockerfile): set more lenient permissions on /home/runner by @caxu-rh in https://github.com/actions/runner/pull/4083
|
* Bump actions/github-script from 7 to 8 by @dependabot[bot] in https://github.com/actions/runner/pull/4137
|
||||||
* Add support for libicu73-76 for newer Debian/Ubuntu versions by @lets-build-an-ocean in https://github.com/actions/runner/pull/4098
|
* Bump actions/checkout from 5 to 6 by @dependabot[bot] in https://github.com/actions/runner/pull/4130
|
||||||
* Bump actions/download-artifact from 5 to 6 by @dependabot[bot] in https://github.com/actions/runner/pull/4089
|
* chore: update Node versions by @github-actions[bot] in https://github.com/actions/runner/pull/4149
|
||||||
* Bump actions/upload-artifact from 4 to 5 by @dependabot[bot] in https://github.com/actions/runner/pull/4088
|
* Bump docker image to use ubuntu 24.04 by @TingluoHuang in https://github.com/actions/runner/pull/4018
|
||||||
* Bump Azure.Storage.Blobs from 12.25.1 to 12.26.0 by @dependabot[bot] in https://github.com/actions/runner/pull/4077
|
* Add support for case function by @AllanGuigou in https://github.com/actions/runner/pull/4147
|
||||||
* Only start runner after network is online by @dupondje in https://github.com/actions/runner/pull/4094
|
* Cleanup feature flag actions_container_action_runner_temp by @ericsciple in https://github.com/actions/runner/pull/4163
|
||||||
* Retry http error related to DNS resolution failure. by @TingluoHuang in https://github.com/actions/runner/pull/4110
|
* Bump actions/download-artifact from 6 to 7 by @dependabot[bot] in https://github.com/actions/runner/pull/4155
|
||||||
* Update Docker to v29.0.1 and Buildx to v0.30.0 by @github-actions[bot] in https://github.com/actions/runner/pull/4114
|
* Bump actions/upload-artifact from 5 to 6 by @dependabot[bot] in https://github.com/actions/runner/pull/4157
|
||||||
* chore: update Node versions by @github-actions[bot] in https://github.com/actions/runner/pull/4115
|
* Set ACTIONS_ORCHESTRATION_ID as env to actions. by @TingluoHuang in https://github.com/actions/runner/pull/4178
|
||||||
* Update dotnet sdk to latest version @8.0.416 by @github-actions[bot] in https://github.com/actions/runner/pull/4116
|
* Allow hosted VM report job telemetry via .setup_info file. by @TingluoHuang in https://github.com/actions/runner/pull/4186
|
||||||
* Compare updated workflow parser for ActionManifestManager by @ericsciple in https://github.com/actions/runner/pull/4111
|
* Bump typescript from 5.9.2 to 5.9.3 in /src/Misc/expressionFunc/hashFiles by @dependabot[bot] in https://github.com/actions/runner/pull/4184
|
||||||
* Bump npm pkg version for hashFiles. by @TingluoHuang in https://github.com/actions/runner/pull/4122
|
* Bump Azure.Storage.Blobs from 12.26.0 to 12.27.0 by @dependabot[bot] in https://github.com/actions/runner/pull/4189
|
||||||
|
|
||||||
## New Contributors
|
## New Contributors
|
||||||
* @lawrencegripper made their first contribution in https://github.com/actions/runner/pull/4081
|
* @AllanGuigou made their first contribution in https://github.com/actions/runner/pull/4147
|
||||||
* @caxu-rh made their first contribution in https://github.com/actions/runner/pull/4083
|
|
||||||
* @lets-build-an-ocean made their first contribution in https://github.com/actions/runner/pull/4098
|
|
||||||
* @dupondje made their first contribution in https://github.com/actions/runner/pull/4094
|
|
||||||
|
|
||||||
**Full Changelog**: https://github.com/actions/runner/compare/v2.329.0...v2.330.0
|
**Full Changelog**: https://github.com/actions/runner/compare/v2.330.0...v2.331.0
|
||||||
|
|
||||||
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
|
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
|
||||||
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
|
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.330.0
|
<Update to ./src/runnerversion when creating release>
|
||||||
|
|||||||
14
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
14
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
@@ -23,7 +23,7 @@
|
|||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"lint-staged": "^15.5.0",
|
"lint-staged": "^15.5.0",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.0.3",
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@aashutoshrathi/word-wrap": {
|
"node_modules/@aashutoshrathi/word-wrap": {
|
||||||
@@ -4439,9 +4439,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.9.2",
|
"version": "5.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
@@ -7643,9 +7643,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"version": "5.9.2",
|
"version": "5.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"unbox-primitive": {
|
"unbox-primitive": {
|
||||||
|
|||||||
@@ -46,6 +46,6 @@
|
|||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"lint-staged": "^15.5.0",
|
"lint-staged": "^15.5.0",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.0.3",
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ NODE_URL=https://nodejs.org/dist
|
|||||||
NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download
|
NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download
|
||||||
# When you update Node versions you must also create a new release of alpine_nodejs at that updated version.
|
# When you update Node versions you must also create a new release of alpine_nodejs at that updated version.
|
||||||
# Follow the instructions here: https://github.com/actions/alpine_nodejs?tab=readme-ov-file#getting-started
|
# Follow the instructions here: https://github.com/actions/alpine_nodejs?tab=readme-ov-file#getting-started
|
||||||
NODE20_VERSION="20.19.5"
|
NODE20_VERSION="20.20.0"
|
||||||
NODE24_VERSION="24.11.1"
|
NODE24_VERSION="24.13.0"
|
||||||
|
|
||||||
get_abs_path() {
|
get_abs_path() {
|
||||||
# exploits the fact that pwd will print abs path when no args
|
# exploits the fact that pwd will print abs path when no args
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
apt_get_with_fallbacks libssl1.1$ libssl1.0.2$ libssl1.0.0$
|
apt_get_with_fallbacks libssl3t64$ libssl3$ libssl1.1$ libssl1.0.2$ libssl1.0.0$
|
||||||
if [ $? -ne 0 ]
|
if [ $? -ne 0 ]
|
||||||
then
|
then
|
||||||
echo "'$apt_get' failed with exit code '$?'"
|
echo "'$apt_get' failed with exit code '$?'"
|
||||||
|
|||||||
@@ -1,5 +1,36 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# try to use sleep if available
|
||||||
|
if [ -x "$(command -v sleep)" ]; then
|
||||||
|
sleep "$1"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# try to use ping if available
|
||||||
|
if [ -x "$(command -v ping)" ]; then
|
||||||
|
ping -c $(( $1 + 1 )) 127.0.0.1 > /dev/null
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# try to use read -t from stdin/stdout/stderr if we are in bash
|
||||||
|
if [ -n "$BASH_VERSION" ]; then
|
||||||
|
if command -v read >/dev/null 2>&1; then
|
||||||
|
if [ -t 0 ]; then
|
||||||
|
read -t "$1" -u 0 || :;
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if [ -t 1 ]; then
|
||||||
|
read -t "$1" -u 1 || :;
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if [ -t 2 ]; then
|
||||||
|
read -t "$1" -u 2 || :;
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# fallback to a busy wait
|
||||||
SECONDS=0
|
SECONDS=0
|
||||||
while [[ $SECONDS -lt $1 ]]; do
|
while [[ $SECONDS -lt $1 ]]; do
|
||||||
:
|
:
|
||||||
|
|||||||
@@ -169,10 +169,11 @@ namespace GitHub.Runner.Common
|
|||||||
public static readonly string AllowRunnerContainerHooks = "DistributedTask.AllowRunnerContainerHooks";
|
public static readonly string AllowRunnerContainerHooks = "DistributedTask.AllowRunnerContainerHooks";
|
||||||
public static readonly string AddCheckRunIdToJobContext = "actions_add_check_run_id_to_job_context";
|
public static readonly string AddCheckRunIdToJobContext = "actions_add_check_run_id_to_job_context";
|
||||||
public static readonly string DisplayHelpfulActionsDownloadErrors = "actions_display_helpful_actions_download_errors";
|
public static readonly string DisplayHelpfulActionsDownloadErrors = "actions_display_helpful_actions_download_errors";
|
||||||
public static readonly string ContainerActionRunnerTemp = "actions_container_action_runner_temp";
|
|
||||||
public static readonly string SnapshotPreflightHostedRunnerCheck = "actions_snapshot_preflight_hosted_runner_check";
|
public static readonly string SnapshotPreflightHostedRunnerCheck = "actions_snapshot_preflight_hosted_runner_check";
|
||||||
public static readonly string SnapshotPreflightImageGenPoolCheck = "actions_snapshot_preflight_image_gen_pool_check";
|
public static readonly string SnapshotPreflightImageGenPoolCheck = "actions_snapshot_preflight_image_gen_pool_check";
|
||||||
public static readonly string CompareWorkflowParser = "actions_runner_compare_workflow_parser";
|
public static readonly string CompareWorkflowParser = "actions_runner_compare_workflow_parser";
|
||||||
|
public static readonly string SetOrchestrationIdEnvForActions = "actions_set_orchestration_id_env_for_actions";
|
||||||
|
public static readonly string SendJobLevelAnnotations = "actions_send_job_level_annotations";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node version migration related constants
|
// Node version migration related constants
|
||||||
|
|||||||
@@ -178,8 +178,12 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate can connect.
|
// Validate can connect using the obtained vss credentials.
|
||||||
|
// In Runner Admin flow there's nothing new to test connection to at this point as registerToken is already validated via GetTenantCredential.
|
||||||
|
if (!runnerSettings.UseRunnerAdminFlow)
|
||||||
|
{
|
||||||
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), creds);
|
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), creds);
|
||||||
|
}
|
||||||
|
|
||||||
_term.WriteLine();
|
_term.WriteLine();
|
||||||
_term.WriteSuccessMessage("Connected to GitHub");
|
_term.WriteSuccessMessage("Connected to GitHub");
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace GitHub.Runner.Listener
|
|||||||
public interface IJobDispatcher : IRunnerService
|
public interface IJobDispatcher : IRunnerService
|
||||||
{
|
{
|
||||||
bool Busy { get; }
|
bool Busy { get; }
|
||||||
TaskCompletionSource<bool> RunOnceJobCompleted { get; }
|
TaskCompletionSource<TaskResult> RunOnceJobCompleted { get; }
|
||||||
void Run(Pipelines.AgentJobRequestMessage message, bool runOnce = false);
|
void Run(Pipelines.AgentJobRequestMessage message, bool runOnce = false);
|
||||||
bool Cancel(JobCancelMessage message);
|
bool Cancel(JobCancelMessage message);
|
||||||
Task WaitAsync(CancellationToken token);
|
Task WaitAsync(CancellationToken token);
|
||||||
@@ -56,7 +56,7 @@ namespace GitHub.Runner.Listener
|
|||||||
// timeout limit can be overwritten by environment GITHUB_ACTIONS_RUNNER_CHANNEL_TIMEOUT
|
// timeout limit can be overwritten by environment GITHUB_ACTIONS_RUNNER_CHANNEL_TIMEOUT
|
||||||
private TimeSpan _channelTimeout;
|
private TimeSpan _channelTimeout;
|
||||||
|
|
||||||
private TaskCompletionSource<bool> _runOnceJobCompleted = new();
|
private TaskCompletionSource<TaskResult> _runOnceJobCompleted = new();
|
||||||
|
|
||||||
public event EventHandler<JobStatusEventArgs> JobStatus;
|
public event EventHandler<JobStatusEventArgs> JobStatus;
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ namespace GitHub.Runner.Listener
|
|||||||
Trace.Info($"Set runner/worker IPC timeout to {_channelTimeout.TotalSeconds} seconds.");
|
Trace.Info($"Set runner/worker IPC timeout to {_channelTimeout.TotalSeconds} seconds.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public TaskCompletionSource<bool> RunOnceJobCompleted => _runOnceJobCompleted;
|
public TaskCompletionSource<TaskResult> RunOnceJobCompleted => _runOnceJobCompleted;
|
||||||
|
|
||||||
public bool Busy { get; private set; }
|
public bool Busy { get; private set; }
|
||||||
|
|
||||||
@@ -340,18 +340,19 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
private async Task RunOnceAsync(Pipelines.AgentJobRequestMessage message, string orchestrationId, WorkerDispatcher previousJobDispatch, CancellationToken jobRequestCancellationToken, CancellationToken workerCancelTimeoutKillToken)
|
private async Task RunOnceAsync(Pipelines.AgentJobRequestMessage message, string orchestrationId, WorkerDispatcher previousJobDispatch, CancellationToken jobRequestCancellationToken, CancellationToken workerCancelTimeoutKillToken)
|
||||||
{
|
{
|
||||||
|
var jobResult = TaskResult.Succeeded;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await RunAsync(message, orchestrationId, previousJobDispatch, jobRequestCancellationToken, workerCancelTimeoutKillToken);
|
jobResult = await RunAsync(message, orchestrationId, previousJobDispatch, jobRequestCancellationToken, workerCancelTimeoutKillToken);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Trace.Info("Fire signal for one time used runner.");
|
Trace.Info("Fire signal for one time used runner.");
|
||||||
_runOnceJobCompleted.TrySetResult(true);
|
_runOnceJobCompleted.TrySetResult(jobResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RunAsync(Pipelines.AgentJobRequestMessage message, string orchestrationId, WorkerDispatcher previousJobDispatch, CancellationToken jobRequestCancellationToken, CancellationToken workerCancelTimeoutKillToken)
|
private async Task<TaskResult> RunAsync(Pipelines.AgentJobRequestMessage message, string orchestrationId, WorkerDispatcher previousJobDispatch, CancellationToken jobRequestCancellationToken, CancellationToken workerCancelTimeoutKillToken)
|
||||||
{
|
{
|
||||||
Busy = true;
|
Busy = true;
|
||||||
try
|
try
|
||||||
@@ -399,7 +400,7 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
// renew job request task complete means we run out of retry for the first job request renew.
|
// renew job request task complete means we run out of retry for the first job request renew.
|
||||||
Trace.Info($"Unable to renew job request for job {message.JobId} for the first time, stop dispatching job to worker.");
|
Trace.Info($"Unable to renew job request for job {message.JobId} for the first time, stop dispatching job to worker.");
|
||||||
return;
|
return TaskResult.Abandoned;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jobRequestCancellationToken.IsCancellationRequested)
|
if (jobRequestCancellationToken.IsCancellationRequested)
|
||||||
@@ -412,7 +413,7 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
// complete job request with result Cancelled
|
// complete job request with result Cancelled
|
||||||
await CompleteJobRequestAsync(_poolId, message, systemConnection, lockToken, TaskResult.Canceled);
|
await CompleteJobRequestAsync(_poolId, message, systemConnection, lockToken, TaskResult.Canceled);
|
||||||
return;
|
return TaskResult.Canceled;
|
||||||
}
|
}
|
||||||
|
|
||||||
HostContext.WritePerfCounter($"JobRequestRenewed_{requestId.ToString()}");
|
HostContext.WritePerfCounter($"JobRequestRenewed_{requestId.ToString()}");
|
||||||
@@ -523,7 +524,7 @@ namespace GitHub.Runner.Listener
|
|||||||
await renewJobRequest;
|
await renewJobRequest;
|
||||||
|
|
||||||
// not finish the job request since the job haven't run on worker at all, we will not going to set a result to server.
|
// not finish the job request since the job haven't run on worker at all, we will not going to set a result to server.
|
||||||
return;
|
return TaskResult.Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we get first jobrequest renew succeed and start the worker process with the job message.
|
// we get first jobrequest renew succeed and start the worker process with the job message.
|
||||||
@@ -604,7 +605,7 @@ namespace GitHub.Runner.Listener
|
|||||||
Trace.Error(detailInfo);
|
Trace.Error(detailInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return TaskResultUtil.TranslateFromReturnCode(returnCode);
|
||||||
}
|
}
|
||||||
else if (completedTask == renewJobRequest)
|
else if (completedTask == renewJobRequest)
|
||||||
{
|
{
|
||||||
@@ -706,6 +707,8 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
// complete job request
|
// complete job request
|
||||||
await CompleteJobRequestAsync(_poolId, message, systemConnection, lockToken, resultOnAbandonOrCancel);
|
await CompleteJobRequestAsync(_poolId, message, systemConnection, lockToken, resultOnAbandonOrCancel);
|
||||||
|
|
||||||
|
return resultOnAbandonOrCancel;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -324,8 +324,11 @@ namespace GitHub.Runner.Listener
|
|||||||
HostContext.EnableAuthMigration("EnableAuthMigrationByDefault");
|
HostContext.EnableAuthMigration("EnableAuthMigrationByDefault");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hosted runner only run one job and would like to know the result of the job for telemetry and alerting on failure spike.
|
||||||
|
var returnJobResultForHosted = StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("ACTIONS_RUNNER_RETURN_JOB_RESULT_FOR_HOSTED"));
|
||||||
|
|
||||||
// Run the runner interactively or as service
|
// Run the runner interactively or as service
|
||||||
return await ExecuteRunnerAsync(settings, command.RunOnce || settings.Ephemeral);
|
return await ExecuteRunnerAsync(settings, command.RunOnce || settings.Ephemeral || returnJobResultForHosted, returnJobResultForHosted);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -401,12 +404,27 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
|
|
||||||
//create worker manager, create message listener and start listening to the queue
|
//create worker manager, create message listener and start listening to the queue
|
||||||
private async Task<int> RunAsync(RunnerSettings settings, bool runOnce = false)
|
private async Task<int> RunAsync(RunnerSettings settings, bool runOnce = false, bool returnRunOnceJobResult = false)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Trace.Info(nameof(RunAsync));
|
Trace.Info(nameof(RunAsync));
|
||||||
|
|
||||||
|
// Validate directory permissions.
|
||||||
|
string workDirectory = HostContext.GetDirectory(WellKnownDirectory.Work);
|
||||||
|
Trace.Info($"Validating directory permissions for: '{workDirectory}'");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(workDirectory);
|
||||||
|
IOUtil.ValidateExecutePermission(workDirectory);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Trace.Error(ex);
|
||||||
|
_term.WriteError($"Fail to create and validate runner's work directory '{workDirectory}'.");
|
||||||
|
return Constants.Runner.ReturnCode.TerminatedError;
|
||||||
|
}
|
||||||
|
|
||||||
// First try using migrated settings if available
|
// First try using migrated settings if available
|
||||||
var configManager = HostContext.GetService<IConfigurationManager>();
|
var configManager = HostContext.GetService<IConfigurationManager>();
|
||||||
RunnerSettings migratedSettings = null;
|
RunnerSettings migratedSettings = null;
|
||||||
@@ -565,6 +583,21 @@ namespace GitHub.Runner.Listener
|
|||||||
Trace.Info($"Ignore any exception after cancel message loop. {ex}");
|
Trace.Info($"Ignore any exception after cancel message loop. {ex}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (returnRunOnceJobResult)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jobResult = await jobDispatcher.RunOnceJobCompleted.Task;
|
||||||
|
return TaskResultUtil.TranslateToReturnCode(jobResult);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Trace.Error("run once job finished with error.");
|
||||||
|
Trace.Error(ex);
|
||||||
|
return Constants.Runner.ReturnCode.TerminatedError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Constants.Runner.ReturnCode.Success;
|
return Constants.Runner.ReturnCode.Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -851,14 +884,14 @@ namespace GitHub.Runner.Listener
|
|||||||
return Constants.Runner.ReturnCode.Success;
|
return Constants.Runner.ReturnCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int> ExecuteRunnerAsync(RunnerSettings settings, bool runOnce)
|
private async Task<int> ExecuteRunnerAsync(RunnerSettings settings, bool runOnce, bool returnRunOnceJobResult)
|
||||||
{
|
{
|
||||||
int returnCode = Constants.Runner.ReturnCode.Success;
|
int returnCode = Constants.Runner.ReturnCode.Success;
|
||||||
bool restart = false;
|
bool restart = false;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
restart = false;
|
restart = false;
|
||||||
returnCode = await RunAsync(settings, runOnce);
|
returnCode = await RunAsync(settings, runOnce, returnRunOnceJobResult);
|
||||||
|
|
||||||
if (returnCode == Constants.Runner.ReturnCode.RunnerConfigurationRefreshed)
|
if (returnCode == Constants.Runner.ReturnCode.RunnerConfigurationRefreshed)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.3" />
|
||||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||||
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
|
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -318,6 +318,17 @@ namespace GitHub.Runner.Worker
|
|||||||
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!context.Global.HasDeprecatedSetOutput)
|
||||||
|
{
|
||||||
|
context.Global.HasDeprecatedSetOutput = true;
|
||||||
|
var telemetry = new JobTelemetry
|
||||||
|
{
|
||||||
|
Type = JobTelemetryType.ActionCommand,
|
||||||
|
Message = "DeprecatedCommand: set-output"
|
||||||
|
};
|
||||||
|
context.Global.JobTelemetry.Add(telemetry);
|
||||||
|
}
|
||||||
|
|
||||||
if (!command.Properties.TryGetValue(SetOutputCommandProperties.Name, out string outputName) || string.IsNullOrEmpty(outputName))
|
if (!command.Properties.TryGetValue(SetOutputCommandProperties.Name, out string outputName) || string.IsNullOrEmpty(outputName))
|
||||||
{
|
{
|
||||||
throw new Exception("Required field 'name' is missing in ##[set-output] command.");
|
throw new Exception("Required field 'name' is missing in ##[set-output] command.");
|
||||||
@@ -353,6 +364,17 @@ namespace GitHub.Runner.Worker
|
|||||||
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!context.Global.HasDeprecatedSaveState)
|
||||||
|
{
|
||||||
|
context.Global.HasDeprecatedSaveState = true;
|
||||||
|
var telemetry = new JobTelemetry
|
||||||
|
{
|
||||||
|
Type = JobTelemetryType.ActionCommand,
|
||||||
|
Message = "DeprecatedCommand: save-state"
|
||||||
|
};
|
||||||
|
context.Global.JobTelemetry.Add(telemetry);
|
||||||
|
}
|
||||||
|
|
||||||
if (!command.Properties.TryGetValue(SaveStateCommandProperties.Name, out string stateName) || string.IsNullOrEmpty(stateName))
|
if (!command.Properties.TryGetValue(SaveStateCommandProperties.Name, out string stateName) || string.IsNullOrEmpty(stateName))
|
||||||
{
|
{
|
||||||
throw new Exception("Required field 'name' is missing in ##[save-state] command.");
|
throw new Exception("Required field 'name' is missing in ##[save-state] command.");
|
||||||
|
|||||||
@@ -316,6 +316,7 @@ namespace GitHub.Runner.Worker
|
|||||||
Schema = _actionManifestSchema,
|
Schema = _actionManifestSchema,
|
||||||
// TODO: Switch to real tracewriter for cutover
|
// TODO: Switch to real tracewriter for cutover
|
||||||
TraceWriter = new GitHub.Actions.WorkflowParser.ObjectTemplating.EmptyTraceWriter(),
|
TraceWriter = new GitHub.Actions.WorkflowParser.ObjectTemplating.EmptyTraceWriter(),
|
||||||
|
AllowCaseFunction = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Expression values from execution context
|
// Expression values from execution context
|
||||||
|
|||||||
@@ -315,6 +315,7 @@ namespace GitHub.Runner.Worker
|
|||||||
maxBytes: 10 * 1024 * 1024),
|
maxBytes: 10 * 1024 * 1024),
|
||||||
Schema = _actionManifestSchema,
|
Schema = _actionManifestSchema,
|
||||||
TraceWriter = executionContext.ToTemplateTraceWriter(),
|
TraceWriter = executionContext.ToTemplateTraceWriter(),
|
||||||
|
AllowCaseFunction = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Expression values from execution context
|
// Expression values from execution context
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ namespace GitHub.Runner.Worker
|
|||||||
"EvaluateContainerEnvironment",
|
"EvaluateContainerEnvironment",
|
||||||
() => _legacyManager.EvaluateContainerEnvironment(executionContext, token, extraExpressionValues),
|
() => _legacyManager.EvaluateContainerEnvironment(executionContext, token, extraExpressionValues),
|
||||||
() => _newManager.EvaluateContainerEnvironment(executionContext, ConvertToNewToken(token) as GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens.MappingToken, ConvertToNewExpressionValues(extraExpressionValues)),
|
() => _newManager.EvaluateContainerEnvironment(executionContext, ConvertToNewToken(token) as GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens.MappingToken, ConvertToNewExpressionValues(extraExpressionValues)),
|
||||||
(legacyResult, newResult) => {
|
(legacyResult, newResult) =>
|
||||||
|
{
|
||||||
var trace = HostContext.GetTrace(nameof(ActionManifestManagerWrapper));
|
var trace = HostContext.GetTrace(nameof(ActionManifestManagerWrapper));
|
||||||
return CompareDictionaries(trace, legacyResult, newResult, "ContainerEnvironment");
|
return CompareDictionaries(trace, legacyResult, newResult, "ContainerEnvironment");
|
||||||
});
|
});
|
||||||
@@ -165,9 +166,150 @@ namespace GitHub.Runner.Worker
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize new steps and deserialize to old steps
|
var result = new List<GitHub.DistributedTask.Pipelines.ActionStep>();
|
||||||
var json = StringUtil.ConvertToJson(newSteps, Newtonsoft.Json.Formatting.None);
|
foreach (var step in newSteps)
|
||||||
return StringUtil.ConvertFromJson<List<GitHub.DistributedTask.Pipelines.ActionStep>>(json);
|
{
|
||||||
|
var actionStep = new GitHub.DistributedTask.Pipelines.ActionStep
|
||||||
|
{
|
||||||
|
ContextName = step.Id,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (step is GitHub.Actions.WorkflowParser.RunStep runStep)
|
||||||
|
{
|
||||||
|
actionStep.Condition = ExtractConditionString(runStep.If);
|
||||||
|
actionStep.DisplayNameToken = ConvertToLegacyToken<TemplateToken>(runStep.Name);
|
||||||
|
actionStep.ContinueOnError = ConvertToLegacyToken<TemplateToken>(runStep.ContinueOnError);
|
||||||
|
actionStep.TimeoutInMinutes = ConvertToLegacyToken<TemplateToken>(runStep.TimeoutMinutes);
|
||||||
|
actionStep.Environment = ConvertToLegacyToken<TemplateToken>(runStep.Env);
|
||||||
|
actionStep.Reference = new GitHub.DistributedTask.Pipelines.ScriptReference();
|
||||||
|
actionStep.Inputs = BuildRunStepInputs(runStep);
|
||||||
|
}
|
||||||
|
else if (step is GitHub.Actions.WorkflowParser.ActionStep usesStep)
|
||||||
|
{
|
||||||
|
actionStep.Condition = ExtractConditionString(usesStep.If);
|
||||||
|
actionStep.DisplayNameToken = ConvertToLegacyToken<TemplateToken>(usesStep.Name);
|
||||||
|
actionStep.ContinueOnError = ConvertToLegacyToken<TemplateToken>(usesStep.ContinueOnError);
|
||||||
|
actionStep.TimeoutInMinutes = ConvertToLegacyToken<TemplateToken>(usesStep.TimeoutMinutes);
|
||||||
|
actionStep.Environment = ConvertToLegacyToken<TemplateToken>(usesStep.Env);
|
||||||
|
actionStep.Reference = ParseActionReference(usesStep.Uses?.Value);
|
||||||
|
actionStep.Inputs = ConvertToLegacyToken<MappingToken>(usesStep.With);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add(actionStep);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ExtractConditionString(GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens.BasicExpressionToken ifToken)
|
||||||
|
{
|
||||||
|
if (ifToken == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Expression property is internal, so we use ToString() which formats as "${{ expr }}"
|
||||||
|
// Then strip the delimiters to get just the expression
|
||||||
|
var str = ifToken.ToString();
|
||||||
|
if (str.StartsWith("${{") && str.EndsWith("}}"))
|
||||||
|
{
|
||||||
|
return str.Substring(3, str.Length - 5).Trim();
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MappingToken BuildRunStepInputs(GitHub.Actions.WorkflowParser.RunStep runStep)
|
||||||
|
{
|
||||||
|
var inputs = new MappingToken(null, null, null);
|
||||||
|
|
||||||
|
// script (from run)
|
||||||
|
if (runStep.Run != null)
|
||||||
|
{
|
||||||
|
inputs.Add(
|
||||||
|
new StringToken(null, null, null, "script"),
|
||||||
|
ConvertToLegacyToken<TemplateToken>(runStep.Run));
|
||||||
|
}
|
||||||
|
|
||||||
|
// shell
|
||||||
|
if (runStep.Shell != null)
|
||||||
|
{
|
||||||
|
inputs.Add(
|
||||||
|
new StringToken(null, null, null, "shell"),
|
||||||
|
ConvertToLegacyToken<TemplateToken>(runStep.Shell));
|
||||||
|
}
|
||||||
|
|
||||||
|
// working-directory
|
||||||
|
if (runStep.WorkingDirectory != null)
|
||||||
|
{
|
||||||
|
inputs.Add(
|
||||||
|
new StringToken(null, null, null, "workingDirectory"),
|
||||||
|
ConvertToLegacyToken<TemplateToken>(runStep.WorkingDirectory));
|
||||||
|
}
|
||||||
|
|
||||||
|
return inputs.Count > 0 ? inputs : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GitHub.DistributedTask.Pipelines.ActionStepDefinitionReference ParseActionReference(string uses)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(uses))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Docker reference: docker://image:tag
|
||||||
|
if (uses.StartsWith("docker://", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return new GitHub.DistributedTask.Pipelines.ContainerRegistryReference
|
||||||
|
{
|
||||||
|
Image = uses.Substring("docker://".Length)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local path reference: ./path/to/action
|
||||||
|
if (uses.StartsWith("./") || uses.StartsWith(".\\"))
|
||||||
|
{
|
||||||
|
return new GitHub.DistributedTask.Pipelines.RepositoryPathReference
|
||||||
|
{
|
||||||
|
RepositoryType = "self",
|
||||||
|
Path = uses
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repository reference: owner/repo@ref or owner/repo/path@ref
|
||||||
|
var atIndex = uses.LastIndexOf('@');
|
||||||
|
string refPart = null;
|
||||||
|
string repoPart = uses;
|
||||||
|
|
||||||
|
if (atIndex > 0)
|
||||||
|
{
|
||||||
|
refPart = uses.Substring(atIndex + 1);
|
||||||
|
repoPart = uses.Substring(0, atIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split by / to get owner/repo and optional path
|
||||||
|
var parts = repoPart.Split('/');
|
||||||
|
string name;
|
||||||
|
string path = null;
|
||||||
|
|
||||||
|
if (parts.Length >= 2)
|
||||||
|
{
|
||||||
|
name = $"{parts[0]}/{parts[1]}";
|
||||||
|
if (parts.Length > 2)
|
||||||
|
{
|
||||||
|
path = string.Join("/", parts, 2, parts.Length - 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
name = repoPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GitHub.DistributedTask.Pipelines.RepositoryPathReference
|
||||||
|
{
|
||||||
|
RepositoryType = "GitHub",
|
||||||
|
Name = name,
|
||||||
|
Ref = refPart,
|
||||||
|
Path = path
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private T ConvertToLegacyToken<T>(GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens.TemplateToken newToken) where T : TemplateToken
|
private T ConvertToLegacyToken<T>(GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens.TemplateToken newToken) where T : TemplateToken
|
||||||
@@ -633,6 +775,14 @@ namespace GitHub.Runner.Worker
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for known equivalent error patterns (e.g., JSON parse errors)
|
||||||
|
// where both parsers correctly reject invalid input but with different wording
|
||||||
|
if (PipelineTemplateEvaluatorWrapper.HasJsonExceptionType(legacyException) && PipelineTemplateEvaluatorWrapper.HasJsonExceptionType(newException))
|
||||||
|
{
|
||||||
|
trace.Info("CompareExceptions - both exceptions are JSON parse errors, treating as matched");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Compare exception messages recursively (including inner exceptions)
|
// Compare exception messages recursively (including inner exceptions)
|
||||||
var legacyMessages = GetExceptionMessages(legacyException);
|
var legacyMessages = GetExceptionMessages(legacyException);
|
||||||
var newMessages = GetExceptionMessages(newException);
|
var newMessages = GetExceptionMessages(newException);
|
||||||
@@ -697,5 +847,6 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -379,7 +379,14 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
prefix = PipelineTemplateConstants.RunDisplayPrefix;
|
prefix = PipelineTemplateConstants.RunDisplayPrefix;
|
||||||
var repositoryReference = action.Reference as RepositoryPathReference;
|
var repositoryReference = action.Reference as RepositoryPathReference;
|
||||||
var pathString = string.IsNullOrEmpty(repositoryReference.Path) ? string.Empty : $"/{repositoryReference.Path}";
|
var pathString = string.Empty;
|
||||||
|
if (!string.IsNullOrEmpty(repositoryReference.Path))
|
||||||
|
{
|
||||||
|
// For local actions (Name is empty), don't prepend "/" to avoid "/./"
|
||||||
|
pathString = string.IsNullOrEmpty(repositoryReference.Name)
|
||||||
|
? repositoryReference.Path
|
||||||
|
: $"/{repositoryReference.Path}";
|
||||||
|
}
|
||||||
var repoString = string.IsNullOrEmpty(repositoryReference.Ref) ? $"{repositoryReference.Name}{pathString}" :
|
var repoString = string.IsNullOrEmpty(repositoryReference.Ref) ? $"{repositoryReference.Name}{pathString}" :
|
||||||
$"{repositoryReference.Name}{pathString}@{repositoryReference.Ref}";
|
$"{repositoryReference.Name}{pathString}@{repositoryReference.Ref}";
|
||||||
tokenToParse = new StringToken(null, null, null, repoString);
|
tokenToParse = new StringToken(null, null, null, repoString);
|
||||||
|
|||||||
@@ -499,7 +499,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
PublishStepTelemetry();
|
PublishStepTelemetry();
|
||||||
|
|
||||||
if (_record.RecordType == "Task")
|
if (_record.RecordType == ExecutionContextType.Task)
|
||||||
{
|
{
|
||||||
var stepResult = new StepResult
|
var stepResult = new StepResult
|
||||||
{
|
{
|
||||||
@@ -532,6 +532,25 @@ namespace GitHub.Runner.Worker
|
|||||||
Global.StepsResult.Add(stepResult);
|
Global.StepsResult.Add(stepResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Global.Variables.GetBoolean(Constants.Runner.Features.SendJobLevelAnnotations) ?? false)
|
||||||
|
{
|
||||||
|
if (_record.RecordType == ExecutionContextType.Job)
|
||||||
|
{
|
||||||
|
_record.Issues?.ForEach(issue =>
|
||||||
|
{
|
||||||
|
var annotation = issue.ToAnnotation();
|
||||||
|
if (annotation != null)
|
||||||
|
{
|
||||||
|
Global.JobAnnotations.Add(annotation.Value);
|
||||||
|
if (annotation.Value.IsInfrastructureIssue && string.IsNullOrEmpty(Global.InfrastructureFailureCategory))
|
||||||
|
{
|
||||||
|
Global.InfrastructureFailureCategory = issue.Category;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Root != this)
|
if (Root != this)
|
||||||
{
|
{
|
||||||
// only dispose TokenSource for step level ExecutionContext
|
// only dispose TokenSource for step level ExecutionContext
|
||||||
|
|||||||
@@ -11,10 +11,5 @@ namespace GitHub.Runner.Worker
|
|||||||
var isContainerHooksPathSet = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(Constants.Hooks.ContainerHooksPath));
|
var isContainerHooksPathSet = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(Constants.Hooks.ContainerHooksPath));
|
||||||
return isContainerHookFeatureFlagSet && isContainerHooksPathSet;
|
return isContainerHookFeatureFlagSet && isContainerHooksPathSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsContainerActionRunnerTempEnabled(Variables variables)
|
|
||||||
{
|
|
||||||
return variables?.GetBoolean(Constants.Runner.Features.ContainerActionRunnerTemp) ?? false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,5 +31,7 @@ namespace GitHub.Runner.Worker
|
|||||||
public JObject ContainerHookState { get; set; }
|
public JObject ContainerHookState { get; set; }
|
||||||
public bool HasTemplateEvaluatorMismatch { get; set; }
|
public bool HasTemplateEvaluatorMismatch { get; set; }
|
||||||
public bool HasActionManifestMismatch { get; set; }
|
public bool HasActionManifestMismatch { get; set; }
|
||||||
|
public bool HasDeprecatedSetOutput { get; set; }
|
||||||
|
public bool HasDeprecatedSaveState { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,19 +191,13 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
ArgUtil.Directory(tempWorkflowDirectory, nameof(tempWorkflowDirectory));
|
ArgUtil.Directory(tempWorkflowDirectory, nameof(tempWorkflowDirectory));
|
||||||
|
|
||||||
container.MountVolumes.Add(new MountVolume("/var/run/docker.sock", "/var/run/docker.sock"));
|
container.MountVolumes.Add(new MountVolume("/var/run/docker.sock", "/var/run/docker.sock"));
|
||||||
if (FeatureManager.IsContainerActionRunnerTempEnabled(ExecutionContext.Global.Variables))
|
|
||||||
{
|
|
||||||
container.MountVolumes.Add(new MountVolume(tempDirectory, "/github/runner_temp"));
|
container.MountVolumes.Add(new MountVolume(tempDirectory, "/github/runner_temp"));
|
||||||
}
|
|
||||||
container.MountVolumes.Add(new MountVolume(tempHomeDirectory, "/github/home"));
|
container.MountVolumes.Add(new MountVolume(tempHomeDirectory, "/github/home"));
|
||||||
container.MountVolumes.Add(new MountVolume(tempWorkflowDirectory, "/github/workflow"));
|
container.MountVolumes.Add(new MountVolume(tempWorkflowDirectory, "/github/workflow"));
|
||||||
container.MountVolumes.Add(new MountVolume(tempFileCommandDirectory, "/github/file_commands"));
|
container.MountVolumes.Add(new MountVolume(tempFileCommandDirectory, "/github/file_commands"));
|
||||||
container.MountVolumes.Add(new MountVolume(defaultWorkingDirectory, "/github/workspace"));
|
container.MountVolumes.Add(new MountVolume(defaultWorkingDirectory, "/github/workspace"));
|
||||||
|
|
||||||
if (FeatureManager.IsContainerActionRunnerTempEnabled(ExecutionContext.Global.Variables))
|
|
||||||
{
|
|
||||||
container.AddPathTranslateMapping(tempDirectory, "/github/runner_temp");
|
container.AddPathTranslateMapping(tempDirectory, "/github/runner_temp");
|
||||||
}
|
|
||||||
container.AddPathTranslateMapping(tempHomeDirectory, "/github/home");
|
container.AddPathTranslateMapping(tempHomeDirectory, "/github/home");
|
||||||
container.AddPathTranslateMapping(tempWorkflowDirectory, "/github/workflow");
|
container.AddPathTranslateMapping(tempWorkflowDirectory, "/github/workflow");
|
||||||
container.AddPathTranslateMapping(tempFileCommandDirectory, "/github/file_commands");
|
container.AddPathTranslateMapping(tempFileCommandDirectory, "/github/file_commands");
|
||||||
@@ -245,6 +239,14 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
Environment["ACTIONS_RESULTS_URL"] = resultsUrl;
|
Environment["ACTIONS_RESULTS_URL"] = resultsUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ExecutionContext.Global.Variables.GetBoolean(Constants.Runner.Features.SetOrchestrationIdEnvForActions) ?? false)
|
||||||
|
{
|
||||||
|
if (ExecutionContext.Global.Variables.TryGetValue(Constants.Variables.System.OrchestrationId, out var orchestrationId) && !string.IsNullOrEmpty(orchestrationId))
|
||||||
|
{
|
||||||
|
Environment["ACTIONS_ORCHESTRATION_ID"] = orchestrationId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var variable in this.Environment)
|
foreach (var variable in this.Environment)
|
||||||
{
|
{
|
||||||
container.ContainerEnvironmentVariables[variable.Key] = container.TranslateToContainerPath(variable.Value);
|
container.ContainerEnvironmentVariables[variable.Key] = container.TranslateToContainerPath(variable.Value);
|
||||||
|
|||||||
@@ -77,6 +77,14 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
Environment["ACTIONS_CACHE_SERVICE_V2"] = bool.TrueString;
|
Environment["ACTIONS_CACHE_SERVICE_V2"] = bool.TrueString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ExecutionContext.Global.Variables.GetBoolean(Constants.Runner.Features.SetOrchestrationIdEnvForActions) ?? false)
|
||||||
|
{
|
||||||
|
if (ExecutionContext.Global.Variables.TryGetValue(Constants.Variables.System.OrchestrationId, out var orchestrationId) && !string.IsNullOrEmpty(orchestrationId))
|
||||||
|
{
|
||||||
|
Environment["ACTIONS_ORCHESTRATION_ID"] = orchestrationId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Resolve the target script.
|
// Resolve the target script.
|
||||||
string target = null;
|
string target = null;
|
||||||
if (stage == ActionRunStage.Main)
|
if (stage == ActionRunStage.Main)
|
||||||
|
|||||||
@@ -318,6 +318,14 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
Environment["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = systemConnection.Authorization.Parameters[EndpointAuthorizationParameters.AccessToken];
|
Environment["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = systemConnection.Authorization.Parameters[EndpointAuthorizationParameters.AccessToken];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ExecutionContext.Global.Variables.GetBoolean(Constants.Runner.Features.SetOrchestrationIdEnvForActions) ?? false)
|
||||||
|
{
|
||||||
|
if (ExecutionContext.Global.Variables.TryGetValue(Constants.Variables.System.OrchestrationId, out var orchestrationId) && !string.IsNullOrEmpty(orchestrationId))
|
||||||
|
{
|
||||||
|
Environment["ACTIONS_ORCHESTRATION_ID"] = orchestrationId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ExecutionContext.Debug($"{fileName} {arguments}");
|
ExecutionContext.Debug($"{fileName} {arguments}");
|
||||||
|
|
||||||
Inputs.TryGetValue("standardInInput", out var standardInInput);
|
Inputs.TryGetValue("standardInInput", out var standardInInput);
|
||||||
|
|||||||
3
src/Runner.Worker/InternalsVisibleTo.cs
Normal file
3
src/Runner.Worker/InternalsVisibleTo.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Test")]
|
||||||
@@ -112,6 +112,13 @@ namespace GitHub.Runner.Worker
|
|||||||
groupName = "Machine Setup Info";
|
groupName = "Machine Setup Info";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// not output internal groups
|
||||||
|
if (groupName.StartsWith("_internal_", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
jobContext.Global.JobTelemetry.Add(new JobTelemetry() { Type = JobTelemetryType.General, Message = info.Detail });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
context.Output($"##[group]{groupName}");
|
context.Output($"##[group]{groupName}");
|
||||||
var multiLines = info.Detail.Replace("\r\n", "\n").TrimEnd('\n').Split('\n');
|
var multiLines = info.Detail.Replace("\r\n", "\n").TrimEnd('\n').Split('\n');
|
||||||
foreach (var line in multiLines)
|
foreach (var line in multiLines)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using GitHub.Actions.WorkflowParser;
|
using GitHub.Actions.WorkflowParser;
|
||||||
using GitHub.DistributedTask.Expressions2;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
@@ -216,12 +216,15 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TLegacy EvaluateAndCompare<TLegacy, TNew>(
|
internal TLegacy EvaluateAndCompare<TLegacy, TNew>(
|
||||||
string methodName,
|
string methodName,
|
||||||
Func<TLegacy> legacyEvaluator,
|
Func<TLegacy> legacyEvaluator,
|
||||||
Func<TNew> newEvaluator,
|
Func<TNew> newEvaluator,
|
||||||
Func<TLegacy, TNew, bool> resultComparer)
|
Func<TLegacy, TNew, bool> resultComparer)
|
||||||
{
|
{
|
||||||
|
// Capture cancellation state before evaluation
|
||||||
|
var cancellationRequestedBefore = _context.CancellationToken.IsCancellationRequested;
|
||||||
|
|
||||||
// Legacy evaluator
|
// Legacy evaluator
|
||||||
var legacyException = default(Exception);
|
var legacyException = default(Exception);
|
||||||
var legacyResult = default(TLegacy);
|
var legacyResult = default(TLegacy);
|
||||||
@@ -253,14 +256,18 @@ namespace GitHub.Runner.Worker
|
|||||||
newException = ex;
|
newException = ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Capture cancellation state after evaluation
|
||||||
|
var cancellationRequestedAfter = _context.CancellationToken.IsCancellationRequested;
|
||||||
|
|
||||||
// Compare results or exceptions
|
// Compare results or exceptions
|
||||||
|
bool hasMismatch = false;
|
||||||
if (legacyException != null || newException != null)
|
if (legacyException != null || newException != null)
|
||||||
{
|
{
|
||||||
// Either one or both threw exceptions - compare them
|
// Either one or both threw exceptions - compare them
|
||||||
if (!CompareExceptions(legacyException, newException))
|
if (!CompareExceptions(legacyException, newException))
|
||||||
{
|
{
|
||||||
_trace.Info($"{methodName} exception mismatch");
|
_trace.Info($"{methodName} exception mismatch");
|
||||||
RecordMismatch($"{methodName}");
|
hasMismatch = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -269,6 +276,20 @@ namespace GitHub.Runner.Worker
|
|||||||
if (!resultComparer(legacyResult, newResult))
|
if (!resultComparer(legacyResult, newResult))
|
||||||
{
|
{
|
||||||
_trace.Info($"{methodName} mismatch");
|
_trace.Info($"{methodName} mismatch");
|
||||||
|
hasMismatch = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only record mismatch if it wasn't caused by a cancellation race condition
|
||||||
|
if (hasMismatch)
|
||||||
|
{
|
||||||
|
if (!cancellationRequestedBefore && cancellationRequestedAfter)
|
||||||
|
{
|
||||||
|
// Cancellation state changed during evaluation window - skip recording
|
||||||
|
_trace.Info($"{methodName} mismatch skipped due to cancellation race condition");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
RecordMismatch($"{methodName}");
|
RecordMismatch($"{methodName}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,6 +633,13 @@ namespace GitHub.Runner.Worker
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for known equivalent error patterns (e.g., JSON parse errors)
|
||||||
|
// where both parsers correctly reject invalid input but with different wording
|
||||||
|
if (IsKnownEquivalentErrorPattern(legacyException, newException))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Compare exception messages recursively (including inner exceptions)
|
// Compare exception messages recursively (including inner exceptions)
|
||||||
var legacyMessages = GetExceptionMessages(legacyException);
|
var legacyMessages = GetExceptionMessages(legacyException);
|
||||||
var newMessages = GetExceptionMessages(newException);
|
var newMessages = GetExceptionMessages(newException);
|
||||||
@@ -634,6 +662,67 @@ namespace GitHub.Runner.Worker
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if two exceptions match a known pattern where both parsers correctly reject
|
||||||
|
/// invalid input but with different error messages (e.g., JSON parse errors from fromJSON).
|
||||||
|
/// </summary>
|
||||||
|
private bool IsKnownEquivalentErrorPattern(Exception legacyException, Exception newException)
|
||||||
|
{
|
||||||
|
// fromJSON('') - both parsers fail when parsing empty string as JSON
|
||||||
|
// The error messages differ but both indicate JSON parsing failure.
|
||||||
|
// Legacy throws raw JsonReaderException: "Error reading JToken from JsonReader..."
|
||||||
|
// New wraps it: "Error parsing fromJson" with inner JsonReaderException
|
||||||
|
// Both may be wrapped in TemplateValidationException: "The template is not valid..."
|
||||||
|
if (HasJsonExceptionType(legacyException) && HasJsonExceptionType(newException))
|
||||||
|
{
|
||||||
|
_trace.Info("CompareExceptions - both exceptions are JSON parse errors, treating as matched");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the exception chain contains a JSON-related exception type.
|
||||||
|
/// </summary>
|
||||||
|
internal static bool HasJsonExceptionType(Exception ex)
|
||||||
|
{
|
||||||
|
var toProcess = new Queue<Exception>();
|
||||||
|
toProcess.Enqueue(ex);
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
while (toProcess.Count > 0 && count < 50)
|
||||||
|
{
|
||||||
|
var current = toProcess.Dequeue();
|
||||||
|
if (current == null) continue;
|
||||||
|
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (current is Newtonsoft.Json.JsonReaderException ||
|
||||||
|
current is System.Text.Json.JsonException)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current is AggregateException aggregateEx)
|
||||||
|
{
|
||||||
|
foreach (var innerEx in aggregateEx.InnerExceptions)
|
||||||
|
{
|
||||||
|
if (innerEx != null && count < 50)
|
||||||
|
{
|
||||||
|
toProcess.Enqueue(innerEx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (current.InnerException != null)
|
||||||
|
{
|
||||||
|
toProcess.Enqueue(current.InnerException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private IList<string> GetExceptionMessages(Exception ex)
|
private IList<string> GetExceptionMessages(Exception ex)
|
||||||
{
|
{
|
||||||
var messages = new List<string>();
|
var messages = new List<string>();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ namespace GitHub.DistributedTask.Expressions2
|
|||||||
{
|
{
|
||||||
static ExpressionConstants()
|
static ExpressionConstants()
|
||||||
{
|
{
|
||||||
|
AddFunction<Case>("case", 3, Byte.MaxValue);
|
||||||
AddFunction<Contains>("contains", 2, 2);
|
AddFunction<Contains>("contains", 2, 2);
|
||||||
AddFunction<EndsWith>("endsWith", 2, 2);
|
AddFunction<EndsWith>("endsWith", 2, 2);
|
||||||
AddFunction<Format>("format", 1, Byte.MaxValue);
|
AddFunction<Format>("format", 1, Byte.MaxValue);
|
||||||
|
|||||||
@@ -17,9 +17,10 @@ namespace GitHub.DistributedTask.Expressions2
|
|||||||
String expression,
|
String expression,
|
||||||
ITraceWriter trace,
|
ITraceWriter trace,
|
||||||
IEnumerable<INamedValueInfo> namedValues,
|
IEnumerable<INamedValueInfo> namedValues,
|
||||||
IEnumerable<IFunctionInfo> functions)
|
IEnumerable<IFunctionInfo> functions,
|
||||||
|
Boolean allowCaseFunction = true)
|
||||||
{
|
{
|
||||||
var context = new ParseContext(expression, trace, namedValues, functions);
|
var context = new ParseContext(expression, trace, namedValues, functions, allowCaseFunction);
|
||||||
context.Trace.Info($"Parsing expression: <{expression}>");
|
context.Trace.Info($"Parsing expression: <{expression}>");
|
||||||
return CreateTree(context);
|
return CreateTree(context);
|
||||||
}
|
}
|
||||||
@@ -349,6 +350,10 @@ namespace GitHub.DistributedTask.Expressions2
|
|||||||
{
|
{
|
||||||
throw new ParseException(ParseExceptionKind.TooManyParameters, token: @operator, expression: context.Expression);
|
throw new ParseException(ParseExceptionKind.TooManyParameters, token: @operator, expression: context.Expression);
|
||||||
}
|
}
|
||||||
|
else if (functionInfo.Name.Equals("case", StringComparison.OrdinalIgnoreCase) && function.Parameters.Count % 2 == 0)
|
||||||
|
{
|
||||||
|
throw new ParseException(ParseExceptionKind.EvenParameters, token: @operator, expression: context.Expression);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -411,6 +416,12 @@ namespace GitHub.DistributedTask.Expressions2
|
|||||||
String name,
|
String name,
|
||||||
out IFunctionInfo functionInfo)
|
out IFunctionInfo functionInfo)
|
||||||
{
|
{
|
||||||
|
if (String.Equals(name, "case", StringComparison.OrdinalIgnoreCase) && !context.AllowCaseFunction)
|
||||||
|
{
|
||||||
|
functionInfo = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return ExpressionConstants.WellKnownFunctions.TryGetValue(name, out functionInfo) ||
|
return ExpressionConstants.WellKnownFunctions.TryGetValue(name, out functionInfo) ||
|
||||||
context.ExtensionFunctions.TryGetValue(name, out functionInfo);
|
context.ExtensionFunctions.TryGetValue(name, out functionInfo);
|
||||||
}
|
}
|
||||||
@@ -418,6 +429,7 @@ namespace GitHub.DistributedTask.Expressions2
|
|||||||
private sealed class ParseContext
|
private sealed class ParseContext
|
||||||
{
|
{
|
||||||
public Boolean AllowUnknownKeywords;
|
public Boolean AllowUnknownKeywords;
|
||||||
|
public Boolean AllowCaseFunction;
|
||||||
public readonly String Expression;
|
public readonly String Expression;
|
||||||
public readonly Dictionary<String, IFunctionInfo> ExtensionFunctions = new Dictionary<String, IFunctionInfo>(StringComparer.OrdinalIgnoreCase);
|
public readonly Dictionary<String, IFunctionInfo> ExtensionFunctions = new Dictionary<String, IFunctionInfo>(StringComparer.OrdinalIgnoreCase);
|
||||||
public readonly Dictionary<String, INamedValueInfo> ExtensionNamedValues = new Dictionary<String, INamedValueInfo>(StringComparer.OrdinalIgnoreCase);
|
public readonly Dictionary<String, INamedValueInfo> ExtensionNamedValues = new Dictionary<String, INamedValueInfo>(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -433,7 +445,8 @@ namespace GitHub.DistributedTask.Expressions2
|
|||||||
ITraceWriter trace,
|
ITraceWriter trace,
|
||||||
IEnumerable<INamedValueInfo> namedValues,
|
IEnumerable<INamedValueInfo> namedValues,
|
||||||
IEnumerable<IFunctionInfo> functions,
|
IEnumerable<IFunctionInfo> functions,
|
||||||
Boolean allowUnknownKeywords = false)
|
Boolean allowUnknownKeywords = false,
|
||||||
|
Boolean allowCaseFunction = true)
|
||||||
{
|
{
|
||||||
Expression = expression ?? String.Empty;
|
Expression = expression ?? String.Empty;
|
||||||
if (Expression.Length > ExpressionConstants.MaxLength)
|
if (Expression.Length > ExpressionConstants.MaxLength)
|
||||||
@@ -454,6 +467,7 @@ namespace GitHub.DistributedTask.Expressions2
|
|||||||
|
|
||||||
LexicalAnalyzer = new LexicalAnalyzer(Expression);
|
LexicalAnalyzer = new LexicalAnalyzer(Expression);
|
||||||
AllowUnknownKeywords = allowUnknownKeywords;
|
AllowUnknownKeywords = allowUnknownKeywords;
|
||||||
|
AllowCaseFunction = allowCaseFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class NoOperationTraceWriter : ITraceWriter
|
private class NoOperationTraceWriter : ITraceWriter
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ namespace GitHub.DistributedTask.Expressions2
|
|||||||
case ParseExceptionKind.TooManyParameters:
|
case ParseExceptionKind.TooManyParameters:
|
||||||
description = "Too many parameters supplied";
|
description = "Too many parameters supplied";
|
||||||
break;
|
break;
|
||||||
|
case ParseExceptionKind.EvenParameters:
|
||||||
|
description = "Even number of parameters supplied, requires an odd number of parameters";
|
||||||
|
break;
|
||||||
case ParseExceptionKind.UnexpectedEndOfExpression:
|
case ParseExceptionKind.UnexpectedEndOfExpression:
|
||||||
description = "Unexpected end of expression";
|
description = "Unexpected end of expression";
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
ExceededMaxLength,
|
ExceededMaxLength,
|
||||||
TooFewParameters,
|
TooFewParameters,
|
||||||
TooManyParameters,
|
TooManyParameters,
|
||||||
|
EvenParameters,
|
||||||
UnexpectedEndOfExpression,
|
UnexpectedEndOfExpression,
|
||||||
UnexpectedSymbol,
|
UnexpectedSymbol,
|
||||||
UnrecognizedFunction,
|
UnrecognizedFunction,
|
||||||
|
|||||||
45
src/Sdk/DTExpressions2/Expressions2/Sdk/Functions/Case.cs
Normal file
45
src/Sdk/DTExpressions2/Expressions2/Sdk/Functions/Case.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#nullable disable // Consider removing in the future to minimize likelihood of NullReferenceException; refer https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using GitHub.Actions.Expressions.Data;
|
||||||
|
|
||||||
|
namespace GitHub.DistributedTask.Expressions2.Sdk.Functions
|
||||||
|
{
|
||||||
|
internal sealed class Case : Function
|
||||||
|
{
|
||||||
|
protected sealed override Object EvaluateCore(
|
||||||
|
EvaluationContext context,
|
||||||
|
out ResultMemory resultMemory)
|
||||||
|
{
|
||||||
|
resultMemory = null;
|
||||||
|
// Validate argument count - must be odd (pairs of predicate-result plus default)
|
||||||
|
if (Parameters.Count % 2 == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("case requires an odd number of arguments");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate predicate-result pairs
|
||||||
|
for (var i = 0; i < Parameters.Count - 1; i += 2)
|
||||||
|
{
|
||||||
|
var predicate = Parameters[i].Evaluate(context);
|
||||||
|
|
||||||
|
// Predicate must be a boolean
|
||||||
|
if (predicate.Kind != ValueKind.Boolean)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("case predicate must evaluate to a boolean value");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If predicate is true, return the corresponding result
|
||||||
|
if ((Boolean)predicate.Value)
|
||||||
|
{
|
||||||
|
var result = Parameters[i + 1].Evaluate(context);
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No predicate matched, return default (last argument)
|
||||||
|
var defaultResult = Parameters[Parameters.Count - 1].Evaluate(context);
|
||||||
|
return defaultResult.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -86,6 +86,12 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
|
|
||||||
internal ITraceWriter TraceWriter { get; set; }
|
internal ITraceWriter TraceWriter { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the case expression function is allowed.
|
||||||
|
/// Defaults to true. Set to false to disable the case function.
|
||||||
|
/// </summary>
|
||||||
|
internal Boolean AllowCaseFunction { get; set; } = true;
|
||||||
|
|
||||||
private IDictionary<String, Int32> FileIds
|
private IDictionary<String, Int32> FileIds
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
var originalBytes = context.Memory.CurrentBytes;
|
var originalBytes = context.Memory.CurrentBytes;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions, allowCaseFunction: context.AllowCaseFunction);
|
||||||
var options = new EvaluationOptions
|
var options = new EvaluationOptions
|
||||||
{
|
{
|
||||||
MaxMemory = context.Memory.MaxBytes,
|
MaxMemory = context.Memory.MaxBytes,
|
||||||
@@ -94,7 +94,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
var originalBytes = context.Memory.CurrentBytes;
|
var originalBytes = context.Memory.CurrentBytes;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions, allowCaseFunction: context.AllowCaseFunction);
|
||||||
var options = new EvaluationOptions
|
var options = new EvaluationOptions
|
||||||
{
|
{
|
||||||
MaxMemory = context.Memory.MaxBytes,
|
MaxMemory = context.Memory.MaxBytes,
|
||||||
@@ -123,7 +123,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
var originalBytes = context.Memory.CurrentBytes;
|
var originalBytes = context.Memory.CurrentBytes;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions, allowCaseFunction: context.AllowCaseFunction);
|
||||||
var options = new EvaluationOptions
|
var options = new EvaluationOptions
|
||||||
{
|
{
|
||||||
MaxMemory = context.Memory.MaxBytes,
|
MaxMemory = context.Memory.MaxBytes,
|
||||||
@@ -152,7 +152,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
var originalBytes = context.Memory.CurrentBytes;
|
var originalBytes = context.Memory.CurrentBytes;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions, allowCaseFunction: context.AllowCaseFunction);
|
||||||
var options = new EvaluationOptions
|
var options = new EvaluationOptions
|
||||||
{
|
{
|
||||||
MaxMemory = context.Memory.MaxBytes,
|
MaxMemory = context.Memory.MaxBytes,
|
||||||
|
|||||||
@@ -663,7 +663,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
var node = default(ExpressionNode);
|
var node = default(ExpressionNode);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
node = expressionParser.CreateTree(condition, null, namedValues, functions) as ExpressionNode;
|
node = expressionParser.CreateTree(condition, null, namedValues, functions, allowCaseFunction: context.AllowCaseFunction) as ExpressionNode;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -421,7 +421,7 @@
|
|||||||
"mapping": {
|
"mapping": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"image": "string",
|
"image": "string",
|
||||||
"options": "non-empty-string",
|
"options": "string",
|
||||||
"env": "container-env",
|
"env": "container-env",
|
||||||
"ports": "sequence-of-non-empty-string",
|
"ports": "sequence-of-non-empty-string",
|
||||||
"volumes": "sequence-of-non-empty-string",
|
"volumes": "sequence-of-non-empty-string",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ namespace GitHub.Actions.Expressions
|
|||||||
{
|
{
|
||||||
static ExpressionConstants()
|
static ExpressionConstants()
|
||||||
{
|
{
|
||||||
|
AddFunction<Case>("case", 3, Byte.MaxValue);
|
||||||
AddFunction<Contains>("contains", 2, 2);
|
AddFunction<Contains>("contains", 2, 2);
|
||||||
AddFunction<EndsWith>("endsWith", 2, 2);
|
AddFunction<EndsWith>("endsWith", 2, 2);
|
||||||
AddFunction<Format>("format", 1, Byte.MaxValue);
|
AddFunction<Format>("format", 1, Byte.MaxValue);
|
||||||
|
|||||||
@@ -17,9 +17,10 @@ namespace GitHub.Actions.Expressions
|
|||||||
String expression,
|
String expression,
|
||||||
ITraceWriter trace,
|
ITraceWriter trace,
|
||||||
IEnumerable<INamedValueInfo> namedValues,
|
IEnumerable<INamedValueInfo> namedValues,
|
||||||
IEnumerable<IFunctionInfo> functions)
|
IEnumerable<IFunctionInfo> functions,
|
||||||
|
Boolean allowCaseFunction = true)
|
||||||
{
|
{
|
||||||
var context = new ParseContext(expression, trace, namedValues, functions);
|
var context = new ParseContext(expression, trace, namedValues, functions, allowCaseFunction: allowCaseFunction);
|
||||||
context.Trace.Info($"Parsing expression: <{expression}>");
|
context.Trace.Info($"Parsing expression: <{expression}>");
|
||||||
return CreateTree(context);
|
return CreateTree(context);
|
||||||
}
|
}
|
||||||
@@ -349,6 +350,10 @@ namespace GitHub.Actions.Expressions
|
|||||||
{
|
{
|
||||||
throw new ParseException(ParseExceptionKind.TooManyParameters, token: @operator, expression: context.Expression);
|
throw new ParseException(ParseExceptionKind.TooManyParameters, token: @operator, expression: context.Expression);
|
||||||
}
|
}
|
||||||
|
else if (functionInfo.Name.Equals("case", StringComparison.OrdinalIgnoreCase) && function.Parameters.Count % 2 == 0)
|
||||||
|
{
|
||||||
|
throw new ParseException(ParseExceptionKind.EvenParameters, token: @operator, expression: context.Expression);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -411,6 +416,12 @@ namespace GitHub.Actions.Expressions
|
|||||||
String name,
|
String name,
|
||||||
out IFunctionInfo functionInfo)
|
out IFunctionInfo functionInfo)
|
||||||
{
|
{
|
||||||
|
if (String.Equals(name, "case", StringComparison.OrdinalIgnoreCase) && !context.AllowCaseFunction)
|
||||||
|
{
|
||||||
|
functionInfo = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return ExpressionConstants.WellKnownFunctions.TryGetValue(name, out functionInfo) ||
|
return ExpressionConstants.WellKnownFunctions.TryGetValue(name, out functionInfo) ||
|
||||||
context.ExtensionFunctions.TryGetValue(name, out functionInfo);
|
context.ExtensionFunctions.TryGetValue(name, out functionInfo);
|
||||||
}
|
}
|
||||||
@@ -418,6 +429,7 @@ namespace GitHub.Actions.Expressions
|
|||||||
private sealed class ParseContext
|
private sealed class ParseContext
|
||||||
{
|
{
|
||||||
public Boolean AllowUnknownKeywords;
|
public Boolean AllowUnknownKeywords;
|
||||||
|
public Boolean AllowCaseFunction;
|
||||||
public readonly String Expression;
|
public readonly String Expression;
|
||||||
public readonly Dictionary<String, IFunctionInfo> ExtensionFunctions = new Dictionary<String, IFunctionInfo>(StringComparer.OrdinalIgnoreCase);
|
public readonly Dictionary<String, IFunctionInfo> ExtensionFunctions = new Dictionary<String, IFunctionInfo>(StringComparer.OrdinalIgnoreCase);
|
||||||
public readonly Dictionary<String, INamedValueInfo> ExtensionNamedValues = new Dictionary<String, INamedValueInfo>(StringComparer.OrdinalIgnoreCase);
|
public readonly Dictionary<String, INamedValueInfo> ExtensionNamedValues = new Dictionary<String, INamedValueInfo>(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -433,7 +445,8 @@ namespace GitHub.Actions.Expressions
|
|||||||
ITraceWriter trace,
|
ITraceWriter trace,
|
||||||
IEnumerable<INamedValueInfo> namedValues,
|
IEnumerable<INamedValueInfo> namedValues,
|
||||||
IEnumerable<IFunctionInfo> functions,
|
IEnumerable<IFunctionInfo> functions,
|
||||||
Boolean allowUnknownKeywords = false)
|
Boolean allowUnknownKeywords = false,
|
||||||
|
Boolean allowCaseFunction = true)
|
||||||
{
|
{
|
||||||
Expression = expression ?? String.Empty;
|
Expression = expression ?? String.Empty;
|
||||||
if (Expression.Length > ExpressionConstants.MaxLength)
|
if (Expression.Length > ExpressionConstants.MaxLength)
|
||||||
@@ -454,6 +467,7 @@ namespace GitHub.Actions.Expressions
|
|||||||
|
|
||||||
LexicalAnalyzer = new LexicalAnalyzer(Expression);
|
LexicalAnalyzer = new LexicalAnalyzer(Expression);
|
||||||
AllowUnknownKeywords = allowUnknownKeywords;
|
AllowUnknownKeywords = allowUnknownKeywords;
|
||||||
|
AllowCaseFunction = allowCaseFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class NoOperationTraceWriter : ITraceWriter
|
private class NoOperationTraceWriter : ITraceWriter
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ namespace GitHub.Actions.Expressions
|
|||||||
case ParseExceptionKind.TooManyParameters:
|
case ParseExceptionKind.TooManyParameters:
|
||||||
description = "Too many parameters supplied";
|
description = "Too many parameters supplied";
|
||||||
break;
|
break;
|
||||||
|
case ParseExceptionKind.EvenParameters:
|
||||||
|
description = "Even number of parameters supplied, requires an odd number of parameters";
|
||||||
|
break;
|
||||||
case ParseExceptionKind.UnexpectedEndOfExpression:
|
case ParseExceptionKind.UnexpectedEndOfExpression:
|
||||||
description = "Unexpected end of expression";
|
description = "Unexpected end of expression";
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace GitHub.Actions.Expressions
|
|||||||
ExceededMaxLength,
|
ExceededMaxLength,
|
||||||
TooFewParameters,
|
TooFewParameters,
|
||||||
TooManyParameters,
|
TooManyParameters,
|
||||||
|
EvenParameters,
|
||||||
UnexpectedEndOfExpression,
|
UnexpectedEndOfExpression,
|
||||||
UnexpectedSymbol,
|
UnexpectedSymbol,
|
||||||
UnrecognizedFunction,
|
UnrecognizedFunction,
|
||||||
|
|||||||
45
src/Sdk/Expressions/Sdk/Functions/Case.cs
Normal file
45
src/Sdk/Expressions/Sdk/Functions/Case.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#nullable disable // Consider removing in the future to minimize likelihood of NullReferenceException; refer https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using GitHub.Actions.Expressions.Data;
|
||||||
|
|
||||||
|
namespace GitHub.Actions.Expressions.Sdk.Functions
|
||||||
|
{
|
||||||
|
internal sealed class Case : Function
|
||||||
|
{
|
||||||
|
protected sealed override Object EvaluateCore(
|
||||||
|
EvaluationContext context,
|
||||||
|
out ResultMemory resultMemory)
|
||||||
|
{
|
||||||
|
resultMemory = null;
|
||||||
|
// Validate argument count - must be odd (pairs of predicate-result plus default)
|
||||||
|
if (Parameters.Count % 2 == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("case requires an odd number of arguments");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate predicate-result pairs
|
||||||
|
for (var i = 0; i < Parameters.Count - 1; i += 2)
|
||||||
|
{
|
||||||
|
var predicate = Parameters[i].Evaluate(context);
|
||||||
|
|
||||||
|
// Predicate must be a boolean
|
||||||
|
if (predicate.Kind != ValueKind.Boolean)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("case predicate must evaluate to a boolean value");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If predicate is true, return the corresponding result
|
||||||
|
if ((Boolean)predicate.Value)
|
||||||
|
{
|
||||||
|
var result = Parameters[i + 1].Evaluate(context);
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No predicate matched, return default (last argument)
|
||||||
|
var defaultResult = Parameters[Parameters.Count - 1].Evaluate(context);
|
||||||
|
return defaultResult.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,19 +18,19 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.26.0" />
|
<PackageReference Include="Azure.Storage.Blobs" Version="12.27.0" />
|
||||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
||||||
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
||||||
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="8.0.0" />
|
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="10.0.2" />
|
||||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||||
<PackageReference Include="Minimatch" Version="2.0.0" />
|
<PackageReference Include="Minimatch" Version="2.0.0" />
|
||||||
<PackageReference Include="YamlDotNet.Signed" Version="5.3.0" />
|
<PackageReference Include="YamlDotNet.Signed" Version="5.3.0" />
|
||||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||||
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
|
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
|
||||||
<PackageReference Include="System.Formats.Asn1" Version="8.0.1" />
|
<PackageReference Include="System.Formats.Asn1" Version="10.0.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable // Consider removing in the future to minimize likelihood of NullReferenceException; refer https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references
|
#nullable disable // Consider removing in the future to minimize likelihood of NullReferenceException; refer https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -1153,8 +1153,13 @@ namespace GitHub.Actions.WorkflowParser.Conversion
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (String.IsNullOrEmpty(result.Image))
|
if (String.IsNullOrEmpty(result.Image))
|
||||||
|
{
|
||||||
|
// Only error during early validation (parse time)
|
||||||
|
// At runtime (expression evaluation), empty image = no container
|
||||||
|
if (isEarlyValidation)
|
||||||
{
|
{
|
||||||
context.Error(value, "Container image cannot be empty");
|
context.Error(value, "Container image cannot be empty");
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1775,7 +1780,7 @@ namespace GitHub.Actions.WorkflowParser.Conversion
|
|||||||
var node = default(ExpressionNode);
|
var node = default(ExpressionNode);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
node = expressionParser.CreateTree(condition, null, namedValues, functions) as ExpressionNode;
|
node = expressionParser.CreateTree(condition, null, namedValues, functions, allowCaseFunction: context.AllowCaseFunction) as ExpressionNode;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -113,6 +113,12 @@ namespace GitHub.Actions.WorkflowParser.ObjectTemplating
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal Boolean StrictJsonParsing { get; set; }
|
internal Boolean StrictJsonParsing { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the case expression function is allowed.
|
||||||
|
/// Defaults to true. Set to false to disable the case function.
|
||||||
|
/// </summary>
|
||||||
|
internal Boolean AllowCaseFunction { get; set; } = true;
|
||||||
|
|
||||||
internal ITraceWriter TraceWriter { get; set; }
|
internal ITraceWriter TraceWriter { get; set; }
|
||||||
|
|
||||||
private IDictionary<String, Int32> FileIds
|
private IDictionary<String, Int32> FileIds
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ namespace GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens
|
|||||||
var originalBytes = context.Memory.CurrentBytes;
|
var originalBytes = context.Memory.CurrentBytes;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions, allowCaseFunction: context.AllowCaseFunction);
|
||||||
var options = new EvaluationOptions
|
var options = new EvaluationOptions
|
||||||
{
|
{
|
||||||
MaxMemory = context.Memory.MaxBytes,
|
MaxMemory = context.Memory.MaxBytes,
|
||||||
@@ -93,7 +93,7 @@ namespace GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens
|
|||||||
var originalBytes = context.Memory.CurrentBytes;
|
var originalBytes = context.Memory.CurrentBytes;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions, allowCaseFunction: context.AllowCaseFunction);
|
||||||
var options = new EvaluationOptions
|
var options = new EvaluationOptions
|
||||||
{
|
{
|
||||||
MaxMemory = context.Memory.MaxBytes,
|
MaxMemory = context.Memory.MaxBytes,
|
||||||
@@ -123,7 +123,7 @@ namespace GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens
|
|||||||
var originalBytes = context.Memory.CurrentBytes;
|
var originalBytes = context.Memory.CurrentBytes;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions, allowCaseFunction: context.AllowCaseFunction);
|
||||||
var options = new EvaluationOptions
|
var options = new EvaluationOptions
|
||||||
{
|
{
|
||||||
MaxMemory = context.Memory.MaxBytes,
|
MaxMemory = context.Memory.MaxBytes,
|
||||||
@@ -153,7 +153,7 @@ namespace GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens
|
|||||||
var originalBytes = context.Memory.CurrentBytes;
|
var originalBytes = context.Memory.CurrentBytes;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
|
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions, allowCaseFunction: context.AllowCaseFunction);
|
||||||
var options = new EvaluationOptions
|
var options = new EvaluationOptions
|
||||||
{
|
{
|
||||||
MaxMemory = context.Memory.MaxBytes,
|
MaxMemory = context.Memory.MaxBytes,
|
||||||
|
|||||||
@@ -2593,7 +2593,7 @@
|
|||||||
"description": "Use `jobs.<job_id>.container.image` to define the Docker image to use as the container to run the action. The value can be the Docker Hub image or a registry name."
|
"description": "Use `jobs.<job_id>.container.image` to define the Docker image to use as the container to run the action. The value can be the Docker Hub image or a registry name."
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"type": "non-empty-string",
|
"type": "string",
|
||||||
"description": "Use `jobs.<job_id>.container.options` to configure additional Docker container resource options."
|
"description": "Use `jobs.<job_id>.container.options` to configure additional Docker container resource options."
|
||||||
},
|
},
|
||||||
"env": "container-env",
|
"env": "container-env",
|
||||||
|
|||||||
@@ -739,7 +739,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
Assert.True(jobDispatcher.RunOnceJobCompleted.Task.IsCompleted, "JobDispatcher should set task complete token for one time agent.");
|
Assert.True(jobDispatcher.RunOnceJobCompleted.Task.IsCompleted, "JobDispatcher should set task complete token for one time agent.");
|
||||||
if (jobDispatcher.RunOnceJobCompleted.Task.IsCompleted)
|
if (jobDispatcher.RunOnceJobCompleted.Task.IsCompleted)
|
||||||
{
|
{
|
||||||
Assert.True(await jobDispatcher.RunOnceJobCompleted.Task, "JobDispatcher should set task complete token to 'TRUE' for one time agent.");
|
var result = await jobDispatcher.RunOnceJobCompleted.Task;
|
||||||
|
Assert.Equal(TaskResult.Succeeded, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -295,13 +295,13 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_messageListener.Setup(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()))
|
_messageListener.Setup(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()))
|
||||||
.Returns(Task.CompletedTask);
|
.Returns(Task.CompletedTask);
|
||||||
|
|
||||||
var runOnceJobCompleted = new TaskCompletionSource<bool>();
|
var runOnceJobCompleted = new TaskCompletionSource<TaskResult>();
|
||||||
_jobDispatcher.Setup(x => x.RunOnceJobCompleted)
|
_jobDispatcher.Setup(x => x.RunOnceJobCompleted)
|
||||||
.Returns(runOnceJobCompleted);
|
.Returns(runOnceJobCompleted);
|
||||||
_jobDispatcher.Setup(x => x.Run(It.IsAny<Pipelines.AgentJobRequestMessage>(), It.IsAny<bool>()))
|
_jobDispatcher.Setup(x => x.Run(It.IsAny<Pipelines.AgentJobRequestMessage>(), It.IsAny<bool>()))
|
||||||
.Callback(() =>
|
.Callback(() =>
|
||||||
{
|
{
|
||||||
runOnceJobCompleted.TrySetResult(true);
|
runOnceJobCompleted.TrySetResult(TaskResult.Succeeded);
|
||||||
});
|
});
|
||||||
_jobNotification.Setup(x => x.StartClient(It.IsAny<String>()))
|
_jobNotification.Setup(x => x.StartClient(It.IsAny<String>()))
|
||||||
.Callback(() =>
|
.Callback(() =>
|
||||||
@@ -399,13 +399,13 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_messageListener.Setup(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()))
|
_messageListener.Setup(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()))
|
||||||
.Returns(Task.CompletedTask);
|
.Returns(Task.CompletedTask);
|
||||||
|
|
||||||
var runOnceJobCompleted = new TaskCompletionSource<bool>();
|
var runOnceJobCompleted = new TaskCompletionSource<TaskResult>();
|
||||||
_jobDispatcher.Setup(x => x.RunOnceJobCompleted)
|
_jobDispatcher.Setup(x => x.RunOnceJobCompleted)
|
||||||
.Returns(runOnceJobCompleted);
|
.Returns(runOnceJobCompleted);
|
||||||
_jobDispatcher.Setup(x => x.Run(It.IsAny<Pipelines.AgentJobRequestMessage>(), It.IsAny<bool>()))
|
_jobDispatcher.Setup(x => x.Run(It.IsAny<Pipelines.AgentJobRequestMessage>(), It.IsAny<bool>()))
|
||||||
.Callback(() =>
|
.Callback(() =>
|
||||||
{
|
{
|
||||||
runOnceJobCompleted.TrySetResult(true);
|
runOnceJobCompleted.TrySetResult(TaskResult.Succeeded);
|
||||||
});
|
});
|
||||||
_jobNotification.Setup(x => x.StartClient(It.IsAny<String>()))
|
_jobNotification.Setup(x => x.StartClient(It.IsAny<String>()))
|
||||||
.Callback(() =>
|
.Callback(() =>
|
||||||
@@ -733,8 +733,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
|
|
||||||
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
|
|
||||||
var completedTask = new TaskCompletionSource<bool>();
|
var completedTask = new TaskCompletionSource<TaskResult>();
|
||||||
completedTask.SetResult(true);
|
completedTask.SetResult(TaskResult.Succeeded);
|
||||||
_jobDispatcher.Setup(x => x.RunOnceJobCompleted).Returns(completedTask);
|
_jobDispatcher.Setup(x => x.RunOnceJobCompleted).Returns(completedTask);
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
@@ -834,8 +834,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
|
|
||||||
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
|
|
||||||
var completedTask = new TaskCompletionSource<bool>();
|
var completedTask = new TaskCompletionSource<TaskResult>();
|
||||||
completedTask.SetResult(true);
|
completedTask.SetResult(TaskResult.Succeeded);
|
||||||
_jobDispatcher.Setup(x => x.RunOnceJobCompleted).Returns(completedTask);
|
_jobDispatcher.Setup(x => x.RunOnceJobCompleted).Returns(completedTask);
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
@@ -954,8 +954,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
|
|
||||||
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
_configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
|
||||||
|
|
||||||
var completedTask = new TaskCompletionSource<bool>();
|
var completedTask = new TaskCompletionSource<TaskResult>();
|
||||||
completedTask.SetResult(true);
|
completedTask.SetResult(TaskResult.Succeeded);
|
||||||
_jobDispatcher.Setup(x => x.RunOnceJobCompleted).Returns(completedTask);
|
_jobDispatcher.Setup(x => x.RunOnceJobCompleted).Returns(completedTask);
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
|
|||||||
@@ -457,6 +457,8 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
new SetEnvCommandExtension(),
|
new SetEnvCommandExtension(),
|
||||||
new WarningCommandExtension(),
|
new WarningCommandExtension(),
|
||||||
new AddMaskCommandExtension(),
|
new AddMaskCommandExtension(),
|
||||||
|
new SetOutputCommandExtension(),
|
||||||
|
new SaveStateCommandExtension(),
|
||||||
};
|
};
|
||||||
foreach (var command in commands)
|
foreach (var command in commands)
|
||||||
{
|
{
|
||||||
@@ -499,5 +501,53 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void SetOutputCommand_EmitsTelemetryOnce()
|
||||||
|
{
|
||||||
|
using (TestHostContext hc = CreateTestContext())
|
||||||
|
{
|
||||||
|
_ec.Object.Global.JobTelemetry = new List<JobTelemetry>();
|
||||||
|
var reference = string.Empty;
|
||||||
|
_ec.Setup(x => x.SetOutput(It.IsAny<string>(), It.IsAny<string>(), out reference));
|
||||||
|
|
||||||
|
// First set-output should add telemetry
|
||||||
|
Assert.True(_commandManager.TryProcessCommand(_ec.Object, "::set-output name=foo::bar", null));
|
||||||
|
Assert.Single(_ec.Object.Global.JobTelemetry);
|
||||||
|
Assert.Equal(JobTelemetryType.ActionCommand, _ec.Object.Global.JobTelemetry[0].Type);
|
||||||
|
Assert.Equal("DeprecatedCommand: set-output", _ec.Object.Global.JobTelemetry[0].Message);
|
||||||
|
Assert.True(_ec.Object.Global.HasDeprecatedSetOutput);
|
||||||
|
|
||||||
|
// Second set-output should not add another telemetry entry
|
||||||
|
Assert.True(_commandManager.TryProcessCommand(_ec.Object, "::set-output name=foo2::bar2", null));
|
||||||
|
Assert.Single(_ec.Object.Global.JobTelemetry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void SaveStateCommand_EmitsTelemetryOnce()
|
||||||
|
{
|
||||||
|
using (TestHostContext hc = CreateTestContext())
|
||||||
|
{
|
||||||
|
_ec.Object.Global.JobTelemetry = new List<JobTelemetry>();
|
||||||
|
_ec.Setup(x => x.IsEmbedded).Returns(false);
|
||||||
|
_ec.Setup(x => x.IntraActionState).Returns(new Dictionary<string, string>());
|
||||||
|
|
||||||
|
// First save-state should add telemetry
|
||||||
|
Assert.True(_commandManager.TryProcessCommand(_ec.Object, "::save-state name=foo::bar", null));
|
||||||
|
Assert.Single(_ec.Object.Global.JobTelemetry);
|
||||||
|
Assert.Equal(JobTelemetryType.ActionCommand, _ec.Object.Global.JobTelemetry[0].Type);
|
||||||
|
Assert.Equal("DeprecatedCommand: save-state", _ec.Object.Global.JobTelemetry[0].Message);
|
||||||
|
Assert.True(_ec.Object.Global.HasDeprecatedSaveState);
|
||||||
|
|
||||||
|
// Second save-state should not add another telemetry entry
|
||||||
|
Assert.True(_commandManager.TryProcessCommand(_ec.Object, "::save-state name=foo2::bar2", null));
|
||||||
|
Assert.Single(_ec.Object.Global.JobTelemetry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
424
src/Test/L0/Worker/ActionManifestParserComparisonL0.cs
Normal file
424
src/Test/L0/Worker/ActionManifestParserComparisonL0.cs
Normal file
@@ -0,0 +1,424 @@
|
|||||||
|
using GitHub.Actions.WorkflowParser;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
|
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Runner.Worker;
|
||||||
|
using LegacyContextData = GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
|
using LegacyExpressions = GitHub.DistributedTask.Expressions2;
|
||||||
|
using Moq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Common.Tests.Worker
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tests for parser comparison wrapper classes.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ActionManifestParserComparisonL0
|
||||||
|
{
|
||||||
|
private CancellationTokenSource _ecTokenSource;
|
||||||
|
private Mock<IExecutionContext> _ec;
|
||||||
|
private TestHostContext _hc;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void ConvertToLegacySteps_ProducesCorrectSteps_WithExplicitPropertyMapping()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange - Test that ActionManifestManagerWrapper properly converts new steps to legacy format
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
// Enable comparison feature
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
// Register required services
|
||||||
|
var legacyManager = new ActionManifestManagerLegacy();
|
||||||
|
legacyManager.Initialize(_hc);
|
||||||
|
_hc.SetSingleton<IActionManifestManagerLegacy>(legacyManager);
|
||||||
|
|
||||||
|
var newManager = new ActionManifestManager();
|
||||||
|
newManager.Initialize(_hc);
|
||||||
|
_hc.SetSingleton<IActionManifestManager>(newManager);
|
||||||
|
|
||||||
|
var wrapper = new ActionManifestManagerWrapper();
|
||||||
|
wrapper.Initialize(_hc);
|
||||||
|
|
||||||
|
var manifestPath = Path.Combine(TestUtil.GetTestDataPath(), "conditional_composite_action.yml");
|
||||||
|
|
||||||
|
// Act - Load through the wrapper (which internally converts)
|
||||||
|
var result = wrapper.Load(_ec.Object, manifestPath);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal(ActionExecutionType.Composite, result.Execution.ExecutionType);
|
||||||
|
|
||||||
|
var compositeExecution = result.Execution as CompositeActionExecutionData;
|
||||||
|
Assert.NotNull(compositeExecution);
|
||||||
|
Assert.NotNull(compositeExecution.Steps);
|
||||||
|
Assert.Equal(6, compositeExecution.Steps.Count);
|
||||||
|
|
||||||
|
// Verify steps are NOT null (this was the bug - JSON round-trip produced nulls)
|
||||||
|
foreach (var step in compositeExecution.Steps)
|
||||||
|
{
|
||||||
|
Assert.NotNull(step);
|
||||||
|
Assert.NotNull(step.Reference);
|
||||||
|
Assert.IsType<GitHub.DistributedTask.Pipelines.ScriptReference>(step.Reference);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify step with condition
|
||||||
|
var successStep = compositeExecution.Steps[2];
|
||||||
|
Assert.Equal("success-conditional", successStep.ContextName);
|
||||||
|
Assert.Equal("success()", successStep.Condition);
|
||||||
|
|
||||||
|
// Verify step with complex condition
|
||||||
|
var lastStep = compositeExecution.Steps[5];
|
||||||
|
Assert.Contains("inputs.exit-code == 1", lastStep.Condition);
|
||||||
|
Assert.Contains("failure()", lastStep.Condition);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateJobContainer_EmptyImage_BothParsersReturnNull()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange - Test that both parsers return null for empty container image at runtime
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
var fileTable = new List<string>();
|
||||||
|
|
||||||
|
// Create legacy evaluator
|
||||||
|
var legacyTraceWriter = new GitHub.DistributedTask.ObjectTemplating.EmptyTraceWriter();
|
||||||
|
var schema = PipelineTemplateSchemaFactory.GetSchema();
|
||||||
|
var legacyEvaluator = new PipelineTemplateEvaluator(legacyTraceWriter, schema, fileTable);
|
||||||
|
|
||||||
|
// Create new evaluator
|
||||||
|
var newTraceWriter = new GitHub.Actions.WorkflowParser.ObjectTemplating.EmptyTraceWriter();
|
||||||
|
var newEvaluator = new WorkflowTemplateEvaluator(newTraceWriter, fileTable, features: null);
|
||||||
|
|
||||||
|
// Create a token representing an empty container image (simulates expression evaluated to empty string)
|
||||||
|
var emptyImageToken = new StringToken(null, null, null, "");
|
||||||
|
|
||||||
|
var contextData = new DictionaryContextData();
|
||||||
|
var expressionFunctions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
// Act - Call both evaluators
|
||||||
|
var legacyResult = legacyEvaluator.EvaluateJobContainer(emptyImageToken, contextData, expressionFunctions);
|
||||||
|
|
||||||
|
// Convert token for new evaluator
|
||||||
|
var newToken = new GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens.StringToken(null, null, null, "");
|
||||||
|
var newContextData = new GitHub.Actions.Expressions.Data.DictionaryExpressionData();
|
||||||
|
var newExpressionFunctions = new List<GitHub.Actions.Expressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
var newResult = newEvaluator.EvaluateJobContainer(newToken, newContextData, newExpressionFunctions);
|
||||||
|
|
||||||
|
// Assert - Both should return null for empty image (no container)
|
||||||
|
Assert.Null(legacyResult);
|
||||||
|
Assert.Null(newResult);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void FromJsonEmptyString_BothParsersFail_WithDifferentMessages()
|
||||||
|
{
|
||||||
|
// This test verifies that both parsers fail with different error messages when parsing fromJSON('')
|
||||||
|
// The comparison layer should treat these as semantically equivalent (both are JSON parse errors)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
var fileTable = new List<string>();
|
||||||
|
|
||||||
|
// Create legacy evaluator
|
||||||
|
var legacyTraceWriter = new GitHub.DistributedTask.ObjectTemplating.EmptyTraceWriter();
|
||||||
|
var schema = PipelineTemplateSchemaFactory.GetSchema();
|
||||||
|
var legacyEvaluator = new PipelineTemplateEvaluator(legacyTraceWriter, schema, fileTable);
|
||||||
|
|
||||||
|
// Create new evaluator
|
||||||
|
var newTraceWriter = new GitHub.Actions.WorkflowParser.ObjectTemplating.EmptyTraceWriter();
|
||||||
|
var newEvaluator = new WorkflowTemplateEvaluator(newTraceWriter, fileTable, features: null);
|
||||||
|
|
||||||
|
// Create expression token for fromJSON('')
|
||||||
|
var legacyToken = new BasicExpressionToken(null, null, null, "fromJson('')");
|
||||||
|
var newToken = new GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens.BasicExpressionToken(null, null, null, "fromJson('')");
|
||||||
|
|
||||||
|
var contextData = new DictionaryContextData();
|
||||||
|
var newContextData = new GitHub.Actions.Expressions.Data.DictionaryExpressionData();
|
||||||
|
var expressionFunctions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
var newExpressionFunctions = new List<GitHub.Actions.Expressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
// Act - Both should throw
|
||||||
|
Exception legacyException = null;
|
||||||
|
Exception newException = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
legacyEvaluator.EvaluateStepDisplayName(legacyToken, contextData, expressionFunctions);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
legacyException = ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
newEvaluator.EvaluateStepName(newToken, newContextData, newExpressionFunctions);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
newException = ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert - Both threw exceptions
|
||||||
|
Assert.NotNull(legacyException);
|
||||||
|
Assert.NotNull(newException);
|
||||||
|
|
||||||
|
// Verify the error messages are different (which is why we need semantic comparison)
|
||||||
|
Assert.NotEqual(legacyException.Message, newException.Message);
|
||||||
|
|
||||||
|
// Verify both are JSON parse errors (contain JSON-related error indicators)
|
||||||
|
var legacyFullMsg = GetFullExceptionMessage(legacyException);
|
||||||
|
var newFullMsg = GetFullExceptionMessage(newException);
|
||||||
|
|
||||||
|
// At least one should contain indicators of JSON parsing failure
|
||||||
|
var legacyIsJsonError = legacyFullMsg.Contains("JToken") ||
|
||||||
|
legacyFullMsg.Contains("JsonReader") ||
|
||||||
|
legacyFullMsg.Contains("fromJson");
|
||||||
|
var newIsJsonError = newFullMsg.Contains("JToken") ||
|
||||||
|
newFullMsg.Contains("JsonReader") ||
|
||||||
|
newFullMsg.Contains("fromJson");
|
||||||
|
|
||||||
|
Assert.True(legacyIsJsonError, $"Legacy exception should be JSON error: {legacyFullMsg}");
|
||||||
|
Assert.True(newIsJsonError, $"New exception should be JSON error: {newFullMsg}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateDefaultInput_BothParsersAgree()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var legacyManager = new ActionManifestManagerLegacy();
|
||||||
|
legacyManager.Initialize(_hc);
|
||||||
|
_hc.SetSingleton<IActionManifestManagerLegacy>(legacyManager);
|
||||||
|
|
||||||
|
var newManager = new ActionManifestManager();
|
||||||
|
newManager.Initialize(_hc);
|
||||||
|
_hc.SetSingleton<IActionManifestManager>(newManager);
|
||||||
|
|
||||||
|
var wrapper = new ActionManifestManagerWrapper();
|
||||||
|
wrapper.Initialize(_hc);
|
||||||
|
|
||||||
|
_ec.Object.ExpressionValues["github"] = new LegacyContextData.DictionaryContextData();
|
||||||
|
_ec.Object.ExpressionValues["strategy"] = new LegacyContextData.DictionaryContextData();
|
||||||
|
_ec.Object.ExpressionValues["matrix"] = new LegacyContextData.DictionaryContextData();
|
||||||
|
_ec.Object.ExpressionValues["steps"] = new LegacyContextData.DictionaryContextData();
|
||||||
|
_ec.Object.ExpressionValues["job"] = new LegacyContextData.DictionaryContextData();
|
||||||
|
_ec.Object.ExpressionValues["runner"] = new LegacyContextData.DictionaryContextData();
|
||||||
|
_ec.Object.ExpressionValues["env"] = new LegacyContextData.DictionaryContextData();
|
||||||
|
_ec.Object.ExpressionFunctions.Add(new LegacyExpressions.FunctionInfo<GitHub.Runner.Worker.Expressions.HashFilesFunction>("hashFiles", 1, 255));
|
||||||
|
|
||||||
|
var result = wrapper.EvaluateDefaultInput(_ec.Object, "testInput", new StringToken(null, null, null, "defaultValue"));
|
||||||
|
|
||||||
|
Assert.Equal("defaultValue", result);
|
||||||
|
Assert.False(_ec.Object.Global.HasActionManifestMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateContainerArguments_BothParsersAgree()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var legacyManager = new ActionManifestManagerLegacy();
|
||||||
|
legacyManager.Initialize(_hc);
|
||||||
|
_hc.SetSingleton<IActionManifestManagerLegacy>(legacyManager);
|
||||||
|
|
||||||
|
var newManager = new ActionManifestManager();
|
||||||
|
newManager.Initialize(_hc);
|
||||||
|
_hc.SetSingleton<IActionManifestManager>(newManager);
|
||||||
|
|
||||||
|
var wrapper = new ActionManifestManagerWrapper();
|
||||||
|
wrapper.Initialize(_hc);
|
||||||
|
|
||||||
|
var arguments = new SequenceToken(null, null, null);
|
||||||
|
arguments.Add(new StringToken(null, null, null, "arg1"));
|
||||||
|
arguments.Add(new StringToken(null, null, null, "arg2"));
|
||||||
|
|
||||||
|
var evaluateContext = new Dictionary<string, LegacyContextData.PipelineContextData>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var result = wrapper.EvaluateContainerArguments(_ec.Object, arguments, evaluateContext);
|
||||||
|
|
||||||
|
Assert.Equal(2, result.Count);
|
||||||
|
Assert.Equal("arg1", result[0]);
|
||||||
|
Assert.Equal("arg2", result[1]);
|
||||||
|
Assert.False(_ec.Object.Global.HasActionManifestMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateContainerEnvironment_BothParsersAgree()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var legacyManager = new ActionManifestManagerLegacy();
|
||||||
|
legacyManager.Initialize(_hc);
|
||||||
|
_hc.SetSingleton<IActionManifestManagerLegacy>(legacyManager);
|
||||||
|
|
||||||
|
var newManager = new ActionManifestManager();
|
||||||
|
newManager.Initialize(_hc);
|
||||||
|
_hc.SetSingleton<IActionManifestManager>(newManager);
|
||||||
|
|
||||||
|
var wrapper = new ActionManifestManagerWrapper();
|
||||||
|
wrapper.Initialize(_hc);
|
||||||
|
|
||||||
|
var environment = new MappingToken(null, null, null);
|
||||||
|
environment.Add(new StringToken(null, null, null, "hello"), new StringToken(null, null, null, "world"));
|
||||||
|
|
||||||
|
var evaluateContext = new Dictionary<string, LegacyContextData.PipelineContextData>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var result = wrapper.EvaluateContainerEnvironment(_ec.Object, environment, evaluateContext);
|
||||||
|
|
||||||
|
Assert.Equal(1, result.Count);
|
||||||
|
Assert.Equal("world", result["hello"]);
|
||||||
|
Assert.False(_ec.Object.Global.HasActionManifestMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateCompositeOutputs_BothParsersAgree()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var legacyManager = new ActionManifestManagerLegacy();
|
||||||
|
legacyManager.Initialize(_hc);
|
||||||
|
_hc.SetSingleton<IActionManifestManagerLegacy>(legacyManager);
|
||||||
|
|
||||||
|
var newManager = new ActionManifestManager();
|
||||||
|
newManager.Initialize(_hc);
|
||||||
|
_hc.SetSingleton<IActionManifestManager>(newManager);
|
||||||
|
|
||||||
|
var wrapper = new ActionManifestManagerWrapper();
|
||||||
|
wrapper.Initialize(_hc);
|
||||||
|
|
||||||
|
var outputDef = new MappingToken(null, null, null);
|
||||||
|
outputDef.Add(new StringToken(null, null, null, "description"), new StringToken(null, null, null, "test output"));
|
||||||
|
outputDef.Add(new StringToken(null, null, null, "value"), new StringToken(null, null, null, "value1"));
|
||||||
|
|
||||||
|
var token = new MappingToken(null, null, null);
|
||||||
|
token.Add(new StringToken(null, null, null, "output1"), outputDef);
|
||||||
|
|
||||||
|
var evaluateContext = new Dictionary<string, LegacyContextData.PipelineContextData>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var result = wrapper.EvaluateCompositeOutputs(_ec.Object, token, evaluateContext);
|
||||||
|
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.False(_ec.Object.Global.HasActionManifestMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetFullExceptionMessage(Exception ex)
|
||||||
|
{
|
||||||
|
var messages = new List<string>();
|
||||||
|
var current = ex;
|
||||||
|
while (current != null)
|
||||||
|
{
|
||||||
|
messages.Add(current.Message);
|
||||||
|
current = current.InnerException;
|
||||||
|
}
|
||||||
|
return string.Join(" -> ", messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Setup([CallerMemberName] string name = "")
|
||||||
|
{
|
||||||
|
_ecTokenSource?.Dispose();
|
||||||
|
_ecTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
_hc = new TestHostContext(this, name);
|
||||||
|
|
||||||
|
var expressionValues = new LegacyContextData.DictionaryContextData();
|
||||||
|
var expressionFunctions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
_ec = new Mock<IExecutionContext>();
|
||||||
|
_ec.Setup(x => x.Global)
|
||||||
|
.Returns(new GlobalContext
|
||||||
|
{
|
||||||
|
FileTable = new List<String>(),
|
||||||
|
Variables = new Variables(_hc, new Dictionary<string, VariableValue>()),
|
||||||
|
WriteDebug = true,
|
||||||
|
});
|
||||||
|
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
|
||||||
|
_ec.Setup(x => x.ExpressionValues).Returns(expressionValues);
|
||||||
|
_ec.Setup(x => x.ExpressionFunctions).Returns(expressionFunctions);
|
||||||
|
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"{tag}{message}"); });
|
||||||
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>())).Callback((Issue issue, ExecutionContextLogOptions logOptions) => { _hc.GetTrace().Info($"[{issue.Type}]{logOptions.LogMessageOverride ?? issue.Message}"); });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Teardown()
|
||||||
|
{
|
||||||
|
_hc?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -316,6 +316,94 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
Assert.Equal("${{ matrix.node }}", _actionRunner.DisplayName);
|
Assert.Equal("${{ matrix.node }}", _actionRunner.DisplayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateDisplayNameForLocalAction()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Setup();
|
||||||
|
var actionId = Guid.NewGuid();
|
||||||
|
var action = new Pipelines.ActionStep()
|
||||||
|
{
|
||||||
|
Name = "action",
|
||||||
|
Id = actionId,
|
||||||
|
Reference = new Pipelines.RepositoryPathReference()
|
||||||
|
{
|
||||||
|
RepositoryType = Pipelines.PipelineConstants.SelfAlias,
|
||||||
|
Path = "./"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_actionRunner.Action = action;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var validDisplayName = _actionRunner.EvaluateDisplayName(_context, _actionRunner.ExecutionContext, out bool updated);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(validDisplayName);
|
||||||
|
Assert.True(updated);
|
||||||
|
Assert.Equal("Run ./", _actionRunner.DisplayName); // NOT "Run /./"
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateDisplayNameForLocalActionWithPath()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Setup();
|
||||||
|
var actionId = Guid.NewGuid();
|
||||||
|
var action = new Pipelines.ActionStep()
|
||||||
|
{
|
||||||
|
Name = "action",
|
||||||
|
Id = actionId,
|
||||||
|
Reference = new Pipelines.RepositoryPathReference()
|
||||||
|
{
|
||||||
|
RepositoryType = Pipelines.PipelineConstants.SelfAlias,
|
||||||
|
Path = "./.github/actions/my-action"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_actionRunner.Action = action;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var validDisplayName = _actionRunner.EvaluateDisplayName(_context, _actionRunner.ExecutionContext, out bool updated);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(validDisplayName);
|
||||||
|
Assert.True(updated);
|
||||||
|
Assert.Equal("Run ./.github/actions/my-action", _actionRunner.DisplayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateDisplayNameForRemoteActionWithPath()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Setup();
|
||||||
|
var actionId = Guid.NewGuid();
|
||||||
|
var action = new Pipelines.ActionStep()
|
||||||
|
{
|
||||||
|
Name = "action",
|
||||||
|
Id = actionId,
|
||||||
|
Reference = new Pipelines.RepositoryPathReference()
|
||||||
|
{
|
||||||
|
Name = "owner/repo",
|
||||||
|
Path = "subdir",
|
||||||
|
Ref = "v1"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_actionRunner.Action = action;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var validDisplayName = _actionRunner.EvaluateDisplayName(_context, _actionRunner.ExecutionContext, out bool updated);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(validDisplayName);
|
||||||
|
Assert.True(updated);
|
||||||
|
Assert.Equal("Run owner/repo/subdir@v1", _actionRunner.DisplayName);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
|
|||||||
550
src/Test/L0/Worker/PipelineTemplateEvaluatorWrapperL0.cs
Normal file
550
src/Test/L0/Worker/PipelineTemplateEvaluatorWrapperL0.cs
Normal file
@@ -0,0 +1,550 @@
|
|||||||
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
|
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Runner.Worker;
|
||||||
|
using LegacyContextData = GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
|
using LegacyExpressions = GitHub.DistributedTask.Expressions2;
|
||||||
|
using Moq;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Common.Tests.Worker
|
||||||
|
{
|
||||||
|
public sealed class PipelineTemplateEvaluatorWrapperL0
|
||||||
|
{
|
||||||
|
private CancellationTokenSource _ecTokenSource;
|
||||||
|
private Mock<IExecutionContext> _ec;
|
||||||
|
private TestHostContext _hc;
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
// EvaluateAndCompare core behavior
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateAndCompare_DoesNotRecordMismatch_WhenResultsMatch()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
|
||||||
|
var token = new StringToken(null, null, null, "test-value");
|
||||||
|
var contextData = new DictionaryContextData();
|
||||||
|
var expressionFunctions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
var result = wrapper.EvaluateStepDisplayName(token, contextData, expressionFunctions);
|
||||||
|
|
||||||
|
Assert.Equal("test-value", result);
|
||||||
|
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateAndCompare_SkipsMismatchRecording_WhenCancellationOccursDuringEvaluation()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
|
||||||
|
// Call EvaluateAndCompare directly: the new evaluator cancels the token
|
||||||
|
// and returns a different value, forcing hasMismatch = true.
|
||||||
|
// Because cancellation flipped during the evaluation window, the
|
||||||
|
// mismatch should be skipped.
|
||||||
|
var result = wrapper.EvaluateAndCompare<string, string>(
|
||||||
|
"TestCancellationSkip",
|
||||||
|
() => "legacy-value",
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
_ecTokenSource.Cancel();
|
||||||
|
return "different-value";
|
||||||
|
},
|
||||||
|
(legacy, @new) => string.Equals(legacy, @new, StringComparison.Ordinal));
|
||||||
|
|
||||||
|
Assert.Equal("legacy-value", result);
|
||||||
|
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateAndCompare_RecordsMismatch_WhenResultsDifferWithoutCancellation()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
|
||||||
|
// Different results without cancellation — mismatch SHOULD be recorded.
|
||||||
|
var result = wrapper.EvaluateAndCompare<string, string>(
|
||||||
|
"TestMismatchRecorded",
|
||||||
|
() => "legacy-value",
|
||||||
|
() => "different-value",
|
||||||
|
(legacy, @new) => string.Equals(legacy, @new, StringComparison.Ordinal));
|
||||||
|
|
||||||
|
Assert.Equal("legacy-value", result);
|
||||||
|
Assert.True(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
// Smoke tests — both parsers agree, no mismatch recorded
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateStepContinueOnError_BothParsersAgree()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
var token = new BooleanToken(null, null, null, true);
|
||||||
|
var contextData = new DictionaryContextData();
|
||||||
|
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
var result = wrapper.EvaluateStepContinueOnError(token, contextData, functions);
|
||||||
|
|
||||||
|
Assert.True(result);
|
||||||
|
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateStepEnvironment_BothParsersAgree()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
var token = new MappingToken(null, null, null);
|
||||||
|
token.Add(new StringToken(null, null, null, "FOO"), new StringToken(null, null, null, "bar"));
|
||||||
|
var contextData = new DictionaryContextData();
|
||||||
|
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
var result = wrapper.EvaluateStepEnvironment(token, contextData, functions, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("bar", result["FOO"]);
|
||||||
|
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateStepIf_BothParsersAgree()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
var token = new BasicExpressionToken(null, null, null, "true");
|
||||||
|
var contextData = new DictionaryContextData();
|
||||||
|
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
var expressionState = new List<KeyValuePair<string, object>>();
|
||||||
|
|
||||||
|
var result = wrapper.EvaluateStepIf(token, contextData, functions, expressionState);
|
||||||
|
|
||||||
|
Assert.True(result);
|
||||||
|
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateStepInputs_BothParsersAgree()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
var token = new MappingToken(null, null, null);
|
||||||
|
token.Add(new StringToken(null, null, null, "input1"), new StringToken(null, null, null, "val1"));
|
||||||
|
var contextData = new DictionaryContextData();
|
||||||
|
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
var result = wrapper.EvaluateStepInputs(token, contextData, functions);
|
||||||
|
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("val1", result["input1"]);
|
||||||
|
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateStepTimeout_BothParsersAgree()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
var token = new NumberToken(null, null, null, 10);
|
||||||
|
var contextData = new DictionaryContextData();
|
||||||
|
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
var result = wrapper.EvaluateStepTimeout(token, contextData, functions);
|
||||||
|
|
||||||
|
Assert.Equal(10, result);
|
||||||
|
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateJobContainer_EmptyImage_BothParsersAgree()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
var token = new StringToken(null, null, null, "");
|
||||||
|
var contextData = new DictionaryContextData();
|
||||||
|
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
var result = wrapper.EvaluateJobContainer(token, contextData, functions);
|
||||||
|
|
||||||
|
Assert.Null(result);
|
||||||
|
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateJobOutput_BothParsersAgree()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
var token = new MappingToken(null, null, null);
|
||||||
|
token.Add(new StringToken(null, null, null, "out1"), new StringToken(null, null, null, "val1"));
|
||||||
|
var contextData = new DictionaryContextData();
|
||||||
|
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
var result = wrapper.EvaluateJobOutput(token, contextData, functions);
|
||||||
|
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("val1", result["out1"]);
|
||||||
|
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateEnvironmentUrl_BothParsersAgree()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
var token = new StringToken(null, null, null, "https://example.com");
|
||||||
|
var contextData = new DictionaryContextData();
|
||||||
|
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
var result = wrapper.EvaluateEnvironmentUrl(token, contextData, functions);
|
||||||
|
|
||||||
|
Assert.NotNull(result);
|
||||||
|
var stringResult = result as StringToken;
|
||||||
|
Assert.NotNull(stringResult);
|
||||||
|
Assert.Equal("https://example.com", stringResult.Value);
|
||||||
|
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateJobDefaultsRun_BothParsersAgree()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
var token = new MappingToken(null, null, null);
|
||||||
|
token.Add(new StringToken(null, null, null, "shell"), new StringToken(null, null, null, "bash"));
|
||||||
|
var contextData = new DictionaryContextData();
|
||||||
|
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
var result = wrapper.EvaluateJobDefaultsRun(token, contextData, functions);
|
||||||
|
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("bash", result["shell"]);
|
||||||
|
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateJobServiceContainers_Null_BothParsersAgree()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
var contextData = new DictionaryContextData();
|
||||||
|
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
var result = wrapper.EvaluateJobServiceContainers(null, contextData, functions);
|
||||||
|
|
||||||
|
Assert.Null(result);
|
||||||
|
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateJobSnapshotRequest_Null_BothParsersAgree()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
var contextData = new DictionaryContextData();
|
||||||
|
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
var result = wrapper.EvaluateJobSnapshotRequest(null, contextData, functions);
|
||||||
|
|
||||||
|
Assert.Null(result);
|
||||||
|
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
// JSON parse error equivalence via EvaluateAndCompare
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateAndCompare_JsonReaderExceptions_TreatedAsEquivalent()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
|
||||||
|
// Both throw JsonReaderException with different messages — should be treated as equivalent
|
||||||
|
var legacyEx = new Newtonsoft.Json.JsonReaderException("Error reading JToken from JsonReader. Path '', line 0, position 0.");
|
||||||
|
var newEx = new Newtonsoft.Json.JsonReaderException("Error parsing fromJson", new Newtonsoft.Json.JsonReaderException("Unexpected end"));
|
||||||
|
|
||||||
|
Assert.Throws<Newtonsoft.Json.JsonReaderException>(() =>
|
||||||
|
wrapper.EvaluateAndCompare<string, string>(
|
||||||
|
"TestJsonEquivalence",
|
||||||
|
() => throw legacyEx,
|
||||||
|
() => throw newEx,
|
||||||
|
(a, b) => string.Equals(a, b, StringComparison.Ordinal)));
|
||||||
|
|
||||||
|
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateAndCompare_MixedJsonExceptionTypes_TreatedAsEquivalent()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
|
||||||
|
// Legacy throws Newtonsoft JsonReaderException, new throws System.Text.Json.JsonException
|
||||||
|
var legacyEx = new Newtonsoft.Json.JsonReaderException("Error reading JToken");
|
||||||
|
var newEx = new System.Text.Json.JsonException("Error parsing fromJson");
|
||||||
|
|
||||||
|
Assert.Throws<Newtonsoft.Json.JsonReaderException>(() =>
|
||||||
|
wrapper.EvaluateAndCompare<string, string>(
|
||||||
|
"TestMixedJsonTypes",
|
||||||
|
() => throw legacyEx,
|
||||||
|
() => throw newEx,
|
||||||
|
(a, b) => string.Equals(a, b, StringComparison.Ordinal)));
|
||||||
|
|
||||||
|
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateAndCompare_NonJsonExceptions_RecordsMismatch()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
|
||||||
|
// Both throw non-JSON exceptions with different messages — should record mismatch
|
||||||
|
var legacyEx = new InvalidOperationException("some error");
|
||||||
|
var newEx = new InvalidOperationException("different error");
|
||||||
|
|
||||||
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
wrapper.EvaluateAndCompare<string, string>(
|
||||||
|
"TestNonJsonMismatch",
|
||||||
|
() => throw legacyEx,
|
||||||
|
() => throw newEx,
|
||||||
|
(a, b) => string.Equals(a, b, StringComparison.Ordinal)));
|
||||||
|
|
||||||
|
Assert.True(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
// Helpers
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
private void Setup([CallerMemberName] string name = "")
|
||||||
|
{
|
||||||
|
_ecTokenSource?.Dispose();
|
||||||
|
_ecTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
_hc = new TestHostContext(this, name);
|
||||||
|
|
||||||
|
var expressionValues = new LegacyContextData.DictionaryContextData();
|
||||||
|
var expressionFunctions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
_ec = new Mock<IExecutionContext>();
|
||||||
|
_ec.Setup(x => x.Global)
|
||||||
|
.Returns(new GlobalContext
|
||||||
|
{
|
||||||
|
FileTable = new List<String>(),
|
||||||
|
Variables = new Variables(_hc, new Dictionary<string, VariableValue>()),
|
||||||
|
WriteDebug = true,
|
||||||
|
});
|
||||||
|
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
|
||||||
|
_ec.Setup(x => x.ExpressionValues).Returns(expressionValues);
|
||||||
|
_ec.Setup(x => x.ExpressionFunctions).Returns(expressionFunctions);
|
||||||
|
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"{tag}{message}"); });
|
||||||
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>())).Callback((Issue issue, ExecutionContextLogOptions logOptions) => { _hc.GetTrace().Info($"[{issue.Type}]{logOptions.LogMessageOverride ?? issue.Message}"); });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Teardown()
|
||||||
|
{
|
||||||
|
_hc?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ LAYOUT_DIR="$SCRIPT_DIR/../_layout"
|
|||||||
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
||||||
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
||||||
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
||||||
DOTNETSDK_VERSION="8.0.416"
|
DOTNETSDK_VERSION="8.0.417"
|
||||||
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
||||||
RUNNER_VERSION=$(cat runnerversion)
|
RUNNER_VERSION=$(cat runnerversion)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "8.0.416"
|
"version": "8.0.417"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.330.0
|
2.331.0
|
||||||
|
|||||||
Reference in New Issue
Block a user