Bump actions/upload-artifact from 4 to 7 #745
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build and Test | |
| on: | |
| push: | |
| branches: | |
| - main | |
| - pre-main-integration | |
| pull_request: | |
| branches: | |
| - main | |
| - pre-main-integration | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| global-json-file: global.json | |
| - name: Cache NuGet packages | |
| uses: actions/cache@v4 | |
| 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: Install DocFX | |
| run: dotnet tool update --global docfx --version '2.*' | |
| - 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 | |
| DOTNET_ROLL_FORWARD=Major 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 | |
| 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 higher thresholds for core diff logic. | |
| # コア差分ロジックに対するクラス単位の高い閾値。 | |
| core_class_line_threshold = 90.0 | |
| core_class_branch_threshold = 85.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 (skipped)") | |
| 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 warnings (non-blocking):", file=sys.stderr) | |
| for w in core_warnings: | |
| print(f" - {w}", file=sys.stderr) | |
| 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@v7 | |
| with: | |
| name: TestAndCoverage | |
| if-no-files-found: warn | |
| path: | | |
| TestResults/**/*.trx | |
| TestResults/**/coverage.cobertura.xml | |
| CoverageReport/** | |
| - name: Upload documentation artifact | |
| uses: actions/upload-artifact@v7 | |
| 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@v7 | |
| with: | |
| name: FolderDiffIL4DotNet | |
| path: publish/** | |
| mutation-testing: | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| global-json-file: global.json | |
| - name: Cache NuGet packages | |
| uses: actions/cache@v4 | |
| 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 | |
| DOTNET_ROLL_FORWARD=Major 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: Post mutation score summary | |
| if: always() | |
| run: | | |
| REPORT=$(find StrykerOutput -name '*.json' -path '*/reports/*' | head -1) | |
| if [ -n "$REPORT" ] && [ -f "$REPORT" ]; then | |
| SCORE=$(python3 -c " | |
| import json, sys | |
| with open('$REPORT') as f: | |
| data = json.load(f) | |
| score = data.get('mutationScore', data.get('files', {}).get('mutationScore', 'N/A')) | |
| print(score) | |
| " 2>/dev/null || echo "N/A") | |
| echo "## Mutation Testing Results" >> "$GITHUB_STEP_SUMMARY" | |
| echo "" >> "$GITHUB_STEP_SUMMARY" | |
| echo "**Mutation Score:** ${SCORE}%" >> "$GITHUB_STEP_SUMMARY" | |
| else | |
| echo "## Mutation Testing Results" >> "$GITHUB_STEP_SUMMARY" | |
| echo "" >> "$GITHUB_STEP_SUMMARY" | |
| echo "⚠ Could not find Stryker JSON report to extract mutation score." >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| - name: Upload mutation testing report | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: StrykerReport | |
| if-no-files-found: warn | |
| path: StrykerOutput/** | |
| benchmark: | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'workflow_dispatch' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| global-json-file: global.json | |
| - name: Cache NuGet packages | |
| uses: actions/cache@v4 | |
| 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@v7 | |
| with: | |
| name: BenchmarkResults | |
| if-no-files-found: warn | |
| path: BenchmarkDotNet.Artifacts/** | |
| test-windows: | |
| runs-on: windows-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| global-json-file: global.json | |
| - name: Cache NuGet packages | |
| uses: actions/cache@v4 | |
| 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 | |
| run: dotnet tool install --global dotnet-ildasm | |
| 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 |