Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .azure/pipelines/ci-public.yml
Original file line number Diff line number Diff line change
Expand Up @@ -636,8 +636,8 @@ stages:
agentOs: Windows
isAzDOTestingJob: true
timeoutInMinutes: 240
# Temporarily disabled due to https://github.com/dotnet/aspnetcore/issues/63140
condition: 'false'
# Re-enabled after fixing template isolation conflicts
condition: 'true'
steps:
- script: git submodule update --init
displayName: Update submodules
Expand Down
110 changes: 110 additions & 0 deletions CI_TEMPLATE_TESTS_FIX_SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# CI Template Tests Fix Summary

## Original Issue
The CI job at `.azure/pipelines/ci-public.yml:632` was disabled due to conflicts with concurrent template tests, causing build failures when both the local development validation and Helix template tests ran simultaneously.

## Root Cause Analysis

### Conflict Sources
1. **Global Template Installation Conflicts**
- Helix template tests used isolated custom hive (`--debug:custom-hive`)
- Blazor web script installed templates globally in user profile
- Concurrent install/uninstall operations caused race conditions

2. **Shared Template Engine State**
- Both workflows accessed the same global template engine state
- Template packages were installed/uninstalled without isolation
- Led to "template not found" or "template already installed" errors

3. **Environment Variable Pollution**
- Blazor web script modified global `DOTNET_ROOT`, `DOTNET_ROOT_X86`, and `Path`
- Could interfere with concurrent test execution environments

## Solution Implemented

### Template Isolation Strategy
Adopted the same isolation pattern already proven in Helix template tests:

#### Before (Conflicting):
```powershell
# Global template installation
dotnet new install $PackagePath
# Global template usage
dotnet new $templateName --no-restore
```

#### After (Isolated):
```powershell
# Unique hive per test run
$CustomHivePath = "$PSScriptRoot/.templatehive-$templateName"
# Isolated installation
dotnet new install $PackagePath --debug:disable-sdk-templates --debug:custom-hive $CustomHivePath
# Isolated usage
dotnet new $templateName --no-restore --debug:custom-hive $CustomHivePath --debug:disable-sdk-templates
```

### Key Components
1. **Unique Custom Hive**: `$PSScriptRoot/.templatehive-$templateName` per test
2. **SDK Template Exclusion**: `--debug:disable-sdk-templates` prevents interference
3. **Automatic Cleanup**: Custom hive removed in `finally` block
4. **Process-Scoped Environment**: Variables scoped to avoid global pollution

## Files Modified
- `src/ProjectTemplates/scripts/Test-Template.psm1`: Added template isolation
- `.azure/pipelines/ci-public.yml`: Re-enabled disabled job (condition: 'false' → 'true')

## How to Test Similar Issues in the Future

### 1. Identify Conflicts
```bash
# Check for global template operations
grep -r "dotnet new install" --exclude-dir=.git
grep -r "\.templateengine" --exclude-dir=.git

# Check for environment variable modifications
grep -r "env:DOTNET_ROOT" --exclude-dir=.git
grep -r "env:Path" --exclude-dir=.git
```

### 2. Implement Isolation
- Use `--debug:custom-hive` for template operations
- Use unique directory names per test run
- Clean up custom hives in finally blocks
- Scope environment variables to process level

### 3. Validate Solution
- Run both workflows individually to ensure they still work
- Run both workflows concurrently to verify no conflicts
- Check CI logs for template-related errors

## Monitoring Points for Re-enabled Functionality

### Success Indicators
- Both template test jobs complete successfully
- No "template not found" errors in logs
- No "template already installed" errors in logs
- No timeout issues in template operations
- Proper artifact flow between jobs

### Warning Signs
- Template installation/uninstallation errors
- Test isolation failures
- Environment variable conflicts
- Resource access conflicts
- Concurrent access exceptions

## Technical Notes

### Template Engine Isolation
The `--debug:custom-hive` flag creates a completely isolated template engine instance:
- Templates installed in custom hive don't affect global template state
- Multiple custom hives can operate simultaneously without conflicts
- Each hive maintains its own template package registry

### Command Syntax
The solution uses the proven command pattern from existing template tests:
```powershell
dotnet new {command} --debug:disable-sdk-templates --debug:custom-hive "{path}"
```

This ensures compatibility with the existing template test infrastructure while providing complete isolation.
32 changes: 14 additions & 18 deletions src/ProjectTemplates/scripts/Test-Template.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,15 @@ function Test-Template {
Write-Verbose "Copying $PSScriptRoot/.runtime/shared/Microsoft.AspNetCore.App to $PSScriptRoot/.dotnet/shared";
Copy-Item -Path "$PSScriptRoot/.runtime/shared/Microsoft.AspNetCore.App" -Destination "$PSScriptRoot/.dotnet/shared" -Recurse -Force;

# Use process-scoped environment variables to avoid conflicts with concurrent tests
$env:DOTNET_ROOT = "$PSScriptRoot/.dotnet";
$env:DOTNET_ROOT_X86 = "$PSScriptRoot/.dotnet";
$env:Path = "$PSScriptRoot/.dotnet;$env:Path";

# Use a unique custom hive for template isolation to avoid conflicts with concurrent template tests
$CustomHivePath = "$PSScriptRoot/.templatehive-$templateName";
Remove-Item -Path $CustomHivePath -Recurse -ErrorAction Ignore;

$tmpDir = "$PSScriptRoot/$templateName";
Remove-Item -Path $tmpDir -Recurse -ErrorAction Ignore;
Push-Location ..;
Expand All @@ -50,30 +56,16 @@ function Test-Template {

$PackageName = (Get-Item $PackagePath).Name;

if (-not (Test-Path "$($env:USERPROFILE)/.templateengine/packages/$PackageName")) {
Write-Verbose "Installing package from $PackagePath";
dotnet new install $PackagePath;
}
else {
Write-Verbose "Uninstalling package from $PackagePath";
if (-not ($PackageName -match $PackagePattern)) {
Write-Error "$PackageName did not match $PackagePattern";
}
$PackageId = $Matches["PackageId"];
$PackageVersion = $Matches["Version"];
Write-Verbose "Uninstalling existing package $PackageId.$PackageVersion";
dotnet new uninstall "$PackageId.$PackageVersion";

Write-Verbose "Installing package from $PackagePath";
dotnet new install $PackagePath;
}
# Use custom hive for template installation to avoid conflicts with concurrent template tests
Write-Verbose "Installing package from $PackagePath using custom hive $CustomHivePath";
dotnet new install $PackagePath --debug:disable-sdk-templates --debug:custom-hive $CustomHivePath;


Write-Verbose "Creating directory $tmpDir"
New-Item -ErrorAction Ignore -Path $tmpDir -ItemType Directory | Out-Null;
Push-Location $tmpDir -StackName TemplateFolder;
try {
$TemplateArguments = , "new" + $TemplateArguments + , "--no-restore";
$TemplateArguments = , "new" + $TemplateArguments + , "--no-restore" + , "--debug:custom-hive" + , $CustomHivePath + , "--debug:disable-sdk-templates";
Write-Verbose "Running dotnet command with arguments: $TemplateArguments";
dotnet @TemplateArguments;

Expand Down Expand Up @@ -124,6 +116,10 @@ function Test-Template {
}
finally {
Pop-Location -StackName TemplateFolder;

# Clean up custom hive to avoid conflicts with future runs
Write-Verbose "Cleaning up custom hive $CustomHivePath";
Remove-Item -Path $CustomHivePath -Recurse -ErrorAction Ignore;
}
}

Expand Down
Loading