Skip to content

Bump github/codeql-action/upload-sarif from 3.37.3 to 4.37.6 #1175

Bump github/codeql-action/upload-sarif from 3.37.3 to 4.37.6

Bump github/codeql-action/upload-sarif from 3.37.3 to 4.37.6 #1175

Workflow file for this run

name: Build and Test
on:
push:
branches:
- main
- pre-main-integration
pull_request:
branches:
- main
- pre-main-integration
workflow_dispatch:
permissions:
contents: read
env:
DOCFX_VERSION: 2.78.5
DOTNET_ILDASM_VERSION: 0.12.2
jobs:
javascript-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version-file: .node-version
cache: npm
cache-dependency-path: package-lock.json
- name: Restore npm dependencies
run: npm ci
- name: Run JavaScript tests
run: npm run test:js
- name: Audit npm dependencies
run: npm run audit:high
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Set up .NET
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
- name: Cache NuGet packages
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Restore dependencies
run: dotnet restore FolderDiffIL4DotNet.sln
- name: Verify formatting
run: dotnet format FolderDiffIL4DotNet.sln --verify-no-changes --no-restore --verbosity minimal
- name: Test NuGet audit gate
run: python3 -m unittest discover -s scripts/tests -p 'test_*.py'
- name: Audit NuGet dependencies
run: python3 scripts/nuget_audit_gate.py --solution FolderDiffIL4DotNet.sln
- name: Build
run: dotnet build FolderDiffIL4DotNet.sln --configuration Release --no-restore
- name: Install DocFX
run: dotnet tool update --global docfx --version "$DOCFX_VERSION"
- name: Generate documentation site
run: |
export PATH="$PATH:$HOME/.dotnet/tools"
docfx metadata docfx.json
docfx build docfx.json
- name: Install real disassembler for E2E tests
if: ${{ hashFiles('FolderDiffIL4DotNet.Tests/FolderDiffIL4DotNet.Tests.csproj') != '' }}
run: |
dotnet tool install --global dotnet-ildasm --version "$DOTNET_ILDASM_VERSION"
echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH"
DOTNET_ROLL_FORWARD=Major "$HOME/.dotnet/tools/dotnet-ildasm" --version
- name: Restore local tools
run: dotnet tool restore
- name: Test with coverage
if: ${{ hashFiles('FolderDiffIL4DotNet.Tests/FolderDiffIL4DotNet.Tests.csproj') != '' }}
env:
DOTNET_ROLL_FORWARD: Major
FOLDERDIFF_RUN_E2E: true
run: dotnet test FolderDiffIL4DotNet.Tests/FolderDiffIL4DotNet.Tests.csproj --configuration Release --no-build --nologo --settings coverlet.runsettings --logger "trx;LogFileName=test_results.trx" --collect:"XPlat Code Coverage" --results-directory ./TestResults
- name: Generate coverage report
if: ${{ hashFiles('FolderDiffIL4DotNet.Tests/FolderDiffIL4DotNet.Tests.csproj') != '' }}
run: |
dotnet tool run reportgenerator \
-reports:"TestResults/**/coverage.cobertura.xml" \
-targetdir:"CoverageReport" \
-reporttypes:"MarkdownSummaryGithub;Cobertura;HtmlInline_AzurePipelines"
if [ -f "CoverageReport/SummaryGithub.md" ]; then
cat CoverageReport/SummaryGithub.md >> "$GITHUB_STEP_SUMMARY"
fi
- name: Enforce coverage thresholds
if: ${{ hashFiles('FolderDiffIL4DotNet.Tests/FolderDiffIL4DotNet.Tests.csproj') != '' }}
run: |
python3 - <<'PY'
import glob
import sys
import xml.etree.ElementTree as ET
line_threshold = 80.0
branch_threshold = 75.0
# Per-class baseline floors for core diff logic.
# コア差分ロジックに対するクラス単位のベースライン下限。
core_class_line_threshold = 85.0
core_class_branch_threshold = 65.0
core_classes = [
"FolderDiffIL4DotNet.Services.FileDiffService",
"FolderDiffIL4DotNet.Services.FolderDiffService",
"FolderDiffIL4DotNet.Services.FileComparisonService",
]
matches = glob.glob("TestResults/**/coverage.cobertura.xml", recursive=True)
if not matches:
print("coverage.cobertura.xml was not found.", file=sys.stderr)
sys.exit(1)
root = ET.parse(matches[0]).getroot()
line_rate = float(root.attrib["line-rate"]) * 100.0
branch_rate = float(root.attrib["branch-rate"]) * 100.0
print(f"Total line coverage : {line_rate:.2f}% (threshold {line_threshold:.2f}%)")
print(f"Total branch coverage: {branch_rate:.2f}% (threshold {branch_threshold:.2f}%)")
failures = []
if line_rate < line_threshold:
failures.append(f"line coverage {line_rate:.2f}% < {line_threshold:.2f}%")
if branch_rate < branch_threshold:
failures.append(f"branch coverage {branch_rate:.2f}% < {branch_threshold:.2f}%")
# Check per-class thresholds for core diff classes.
# Partial classes produce multiple <class> entries with the same name
# but different filenames — aggregate line/branch hits across all entries.
# コア差分クラスのクラス単位閾値をチェック。
# Partial class は同名で異なるファイルの複数 <class> エントリを生成するため、
# 全エントリの行数・ブランチ数を集約してから判定する。
print()
print("--- Core class coverage ---")
from collections import defaultdict
agg = defaultdict(lambda: {"lines_covered": 0, "lines_total": 0,
"branches_covered": 0, "branches_total": 0})
for pkg in root.findall(".//package"):
for cls in pkg.findall(".//class"):
cls_name = cls.attrib.get("name", "")
if cls_name not in core_classes:
continue
for line in cls.findall(".//line"):
agg[cls_name]["lines_total"] += 1
if int(line.attrib.get("hits", "0")) > 0:
agg[cls_name]["lines_covered"] += 1
if line.attrib.get("branch", "false").lower() == "true":
cc = line.attrib.get("condition-coverage", "")
# Format: "X% (covered/total)"
# 形式: "X% (covered/total)"
if "(" in cc and "/" in cc:
inner = cc.split("(")[1].rstrip(")")
cov, tot = inner.split("/")
agg[cls_name]["branches_covered"] += int(cov)
agg[cls_name]["branches_total"] += int(tot)
core_warnings = []
for cls_name in core_classes:
d = agg[cls_name]
if d["lines_total"] == 0:
print(f" {cls_name}: no coverage data found [MISSING]")
core_warnings.append(f"{cls_name} coverage data was not found")
continue
cls_line = d["lines_covered"] / d["lines_total"] * 100.0
cls_branch = (d["branches_covered"] / d["branches_total"] * 100.0
if d["branches_total"] > 0 else 100.0)
status = "OK"
if cls_line < core_class_line_threshold or cls_branch < core_class_branch_threshold:
status = "BELOW TARGET"
print(f" {cls_name}: line {cls_line:.2f}%, branch {cls_branch:.2f}% [{status}]")
if cls_line < core_class_line_threshold:
core_warnings.append(
f"{cls_name} line coverage {cls_line:.2f}% < {core_class_line_threshold:.2f}%"
)
if cls_branch < core_class_branch_threshold:
core_warnings.append(
f"{cls_name} branch coverage {cls_branch:.2f}% < {core_class_branch_threshold:.2f}%"
)
if core_warnings:
print("\nCore class coverage threshold check failed:", file=sys.stderr)
for w in core_warnings:
print(f" - {w}", file=sys.stderr)
failures.extend(core_warnings)
if failures:
print("\nCoverage threshold check failed:", file=sys.stderr)
for failure in failures:
print(f" - {failure}", file=sys.stderr)
sys.exit(1)
PY
- name: Validate test scope map
if: ${{ hashFiles('FolderDiffIL4DotNet.Tests/FolderDiffIL4DotNet.Tests.csproj') != '' }}
continue-on-error: true
run: python3 scripts/validate-test-scope-map.py
- name: Upload test and coverage artifacts
if: ${{ always() && hashFiles('FolderDiffIL4DotNet.Tests/FolderDiffIL4DotNet.Tests.csproj') != '' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: TestAndCoverage
if-no-files-found: warn
path: |
TestResults/**/*.trx
TestResults/**/coverage.cobertura.xml
CoverageReport/**
- name: Upload documentation artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: DocumentationSite
path: _site/**
- name: Publish
run: dotnet publish FolderDiffIL4DotNet.csproj --configuration Release --no-build --output publish
- name: Remove debugging symbols
run: find publish -name '*.pdb' -delete
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: FolderDiffIL4DotNet
path: publish/**
mutation-testing:
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request'
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Set up .NET
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
- name: Cache NuGet packages
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Restore dependencies
run: dotnet restore FolderDiffIL4DotNet.sln
- name: Build
run: dotnet build FolderDiffIL4DotNet.sln --configuration Release --no-restore
- name: Restore local tools
run: dotnet tool restore
- name: Install real disassembler for E2E tests
run: |
dotnet tool install --global dotnet-ildasm --version "$DOTNET_ILDASM_VERSION"
echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH"
DOTNET_ROLL_FORWARD=Major "$HOME/.dotnet/tools/dotnet-ildasm" --version
- name: Run Stryker mutation testing
env:
DOTNET_ROLL_FORWARD: Major
run: dotnet tool run dotnet-stryker --config-file stryker-config.json --output StrykerOutput
- name: Generate mutation visibility summary
if: always()
run: >
python3 scripts/generate-mutation-summary.py
--output-root StrykerOutput
--run-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
--summary-artifact-name "StrykerSummary-${{ github.run_number }}-${{ github.run_attempt }}"
--report-artifact-name "StrykerReport-${{ github.run_number }}-${{ github.run_attempt }}"
- name: Post mutation summary to job summary
if: always()
run: cat StrykerOutput/mutation-summary.md >> "$GITHUB_STEP_SUMMARY"
- name: Upload mutation visibility summary
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: StrykerSummary-${{ github.run_number }}-${{ github.run_attempt }}
if-no-files-found: error
path: |
StrykerOutput/mutation-summary.md
StrykerOutput/mutation-summary.json
- name: Upload mutation testing report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: StrykerReport-${{ github.run_number }}-${{ github.run_attempt }}
if-no-files-found: warn
path: StrykerOutput/**
mutation-comment:
runs-on: ubuntu-latest
needs: mutation-testing
if: always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
permissions:
contents: read
issues: write
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download mutation visibility summary
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: StrykerSummary-${{ github.run_number }}-${{ github.run_attempt }}
path: StrykerOutput
- name: Post mutation summary to pull request
continue-on-error: true
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
with:
script: |
const { upsertMutationSummaryComment } = require('./scripts/update-mutation-pr-comment.js');
await upsertMutationSummaryComment({
github,
context,
summaryPath: 'StrykerOutput/mutation-summary.md',
});
benchmark:
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch'
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Set up .NET
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
- name: Cache NuGet packages
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Restore dependencies
run: dotnet restore FolderDiffIL4DotNet.Benchmarks/FolderDiffIL4DotNet.Benchmarks.csproj
- name: Run benchmarks
run: dotnet run --project FolderDiffIL4DotNet.Benchmarks/FolderDiffIL4DotNet.Benchmarks.csproj --configuration Release -- --exporters json github
- name: Upload benchmark results
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: BenchmarkResults
if-no-files-found: warn
path: BenchmarkDotNet.Artifacts/**
test-windows:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Set up .NET
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
- name: Cache NuGet packages
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~\AppData\Local\NuGet\packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Restore dependencies
run: dotnet restore FolderDiffIL4DotNet.sln
- name: Build
run: dotnet build FolderDiffIL4DotNet.sln --configuration Release --no-restore
- name: Install real disassembler for E2E tests
shell: pwsh
run: |
dotnet tool install --global dotnet-ildasm --version $env:DOTNET_ILDASM_VERSION
$toolPath = Join-Path $env:USERPROFILE ".dotnet\tools"
$toolPath | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
& (Join-Path $toolPath "dotnet-ildasm.exe") --version
env:
DOTNET_ROLL_FORWARD: Major
- name: Test
run: dotnet test FolderDiffIL4DotNet.Tests/FolderDiffIL4DotNet.Tests.csproj --configuration Release --no-build --nologo
env:
DOTNET_ROLL_FORWARD: Major
FOLDERDIFF_RUN_E2E: true
test-macos:
name: macOS stable tests and CLI smoke
runs-on: macos-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Set up .NET
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
- name: Cache NuGet packages
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Restore dependencies
run: dotnet restore FolderDiffIL4DotNet.sln
- name: Build
run: dotnet build FolderDiffIL4DotNet.sln --configuration Release --no-restore
- name: Run stable tests
run: >-
dotnet test FolderDiffIL4DotNet.Tests/FolderDiffIL4DotNet.Tests.csproj
--configuration Release
--no-build
--nologo
--filter "Category!=E2E&Category!=Performance"
--logger "trx;LogFileName=macos_test_results.trx"
--results-directory ./MacOsTestResults
- name: Run non-interactive CLI smoke
shell: bash
run: |
set -euo pipefail
smoke_root="$RUNNER_TEMP/nildiff-macos-smoke"
old_dir="$smoke_root/old folder"
new_dir="$smoke_root/new folder"
report_root="$smoke_root/reports"
cli_path="$GITHUB_WORKSPACE/bin/Release/net8.0/nildiff"
mkdir -p "$old_dir" "$new_dir" "$report_root"
printf 'old only\n' > "$old_dir/only-old.txt"
printf 'before\n' > "$old_dir/CaseProbe.txt"
printf 'after\n' > "$new_dir/caseprobe.txt"
test -x "$cli_path"
"$cli_path" --doctor --skip-il --no-banner --no-pause
set +e
"$cli_path" \
"$old_dir" \
"$new_dir" \
macos-smoke \
--output "$report_root" \
--skip-il \
--no-banner \
--no-pause \
--fail-on-diff
smoke_exit=$?
set -e
if [ "$smoke_exit" -ne 5 ]; then
echo "::error::Expected reportable-difference exit code 5, got $smoke_exit."
exit 1
fi
for artifact in diff_report.md diff_report.html audit_log.json; do
test -s "$report_root/macos-smoke/$artifact"
done
grep -Fq "$old_dir" "$report_root/macos-smoke/diff_report.md"
grep -Fq "$new_dir" "$report_root/macos-smoke/diff_report.md"
grep -Fq '| `[ * ]` | CaseProbe.txt |' "$report_root/macos-smoke/diff_report.md"
- name: Upload macOS test results
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: MacOsTestResults
path: MacOsTestResults/*.trx