Skip to content

Run installer tests in pipeline #47313

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Mar 21, 2025
20 changes: 20 additions & 0 deletions eng/pipelines/templates/stages/vmr-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,26 @@ stages:
- template: ../steps/vmr-validate-asset-baseline.yml
parameters:
continueOnError: true
- job: ValidateInstallers_Linux_x64
displayName: Validate Installers - Linux x64
pool: ${{ parameters.pool_Linux }}
timeoutInMinutes: 30
steps:
- template: ../steps/vmr-validate-installers.yml
parameters:
targetArchitecture: x64
continueOnError: true
OS: Linux
- job: ValidateInstallers_Linux_arm64
displayName: Validate Installers - Linux arm64
pool: ${{ parameters.pool_LinuxArm64 }}
timeoutInMinutes: 30
steps:
- template: ../steps/vmr-validate-installers.yml
parameters:
targetArchitecture: arm64
continueOnError: true
OS: Linux
- ${{ if eq(variables.signEnabled, 'true') }}:
- job: ValidateSigning_Windows
displayName: Validate Signing - Windows
Expand Down
67 changes: 67 additions & 0 deletions eng/pipelines/templates/steps/vmr-validate-installers.yml
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was expecting to see tests listed under the tests tab of the build you linked to - https://dev.azure.com/dnceng/internal/_build/results?buildId=2655424&view=ms.vss-test-web.build-test-results-tab

Also do you happen to have build where there is a failure? I am curious what the UX is in this case on seeing what scenario test failed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, interesting - will check what's going on.

I do not have the pipeline run, with failing scenario tests. I could share the output of the local run.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed with defc5d2

This will still, likely, get refactored further to utilize shared build.sh invocation from jobs/vmr-build.yml.

Copy link
Member Author

@NikolaMilosavljevic NikolaMilosavljevic Mar 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was an issue with search folder so tests weren't being published. This is now fixed with 08f3e6b

Verification build with tests showing up correctly: https://dev.azure.com/dnceng/internal/_build/results?buildId=2659027&view=ms.vss-test-web.build-test-results-tab

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't look like the capture the inner scenario test log file. I'm curious how difficult it will be to diagnose failures when there is a scenario test failure. I am fine with adjusting as needed if this proves to be a problem.

Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
parameters:
- name: continueOnError
type: boolean
default: false

- name: targetArchitecture
type: string
default: ''

- name: OS
type: string
values:
- Windows_NT
- Linux
- Darwin

steps:
- task: DownloadBuildArtifacts@1
inputs:
artifactName: BlobArtifacts
downloadPath: $(Build.ArtifactStagingDirectory)
checkDownloadedFiles: true
displayName: Download Blob Artifacts

- task: DownloadBuildArtifacts@1
inputs:
artifactName: PackageArtifacts
downloadPath: $(Build.ArtifactStagingDirectory)
checkDownloadedFiles: true
displayName: Download Package Artifacts

# This is necessary whenever we want to publish/restore to an AzDO private feed
# Since sdk-task.ps1 tries to restore packages we need to do this authentication here
# otherwise it'll complain about accessing a private feed.
- task: NuGetAuthenticate@1
displayName: 'Authenticate to AzDO Feeds'

- ${{ if eq(parameters.OS, 'Linux') }}:
- script: |
extraBuildProperties="/p:TestRpmPackages=true"
if [[ '${{ parameters.targetArchitecture }}' == 'x64' ]]; then
# At the moment Deb packages are only available for x64
extraBuildProperties="$extraBuildProperties /p:TestDebPackages=true"
fi

./build.sh \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This additional entry point invocation worries me. The VMR is supposed to receive properties like TargetOS, TargetArchitecture, ContinuousIntegrationBuild, etc that help us distinguish different OSs, architectures, CI vs dev, etc.

Could this yml import the steps/vmr-build.yml which handles all that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be ideal. But, we don't currently use that for running tests - the only such instance also has its own custom invocation:

- ${{ if eq(parameters.runTests, 'True') }}:
- script: build.cmd
$(baseArguments)
$(targetArguments)
$(buildPassArguments)
${{ parameters.extraProperties }}
-test
-excludeCIBinarylog
/bl:artifacts/log/Release/Test.binlog
displayName: Run Tests
workingDirectory: ${{ variables.sourcesPath }}
timeoutInMinutes: ${{ variables.runTestsTimeout }}

I'll think some more about this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this to work I would use jobs/vmr-build.yml template, but first I'd need to modify it to enable test-only mode (skipBuild or testOnly), which would run tests but skip the build. Today, we allow tests, as an addition to regular build step.

This would also, likely, fix the issue with missing test status, as test logs are currently not uploaded from this job. The alternative is to add test-log publishing to the new job.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the only such instance also has its own custom invocation

Right, we need the additional invocation as we can't build and run tests from a single invocation today. It also better separates the actions so I don't see that as a problem. That additional invocation in vmr-build.yml is kept in sync with the build invocation so that isn't a problem. It receives the properties that the build invocation receives. If it doesn't then that's a bug.

My feedback here was around not introducing another YML entry point for the VMR. vmr-build.yml should be able to handle the different scenarios (building and testing today). The vmr-final-join has its own entry point and I think that should also get consolidated eventually.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this makes sense. I will add the test-only feature to jobs/vmr-build.yml and use that yml as a template for the new installer-testing job - thus removing the separate build.sh invocation.

Copy link
Member Author

@NikolaMilosavljevic NikolaMilosavljevic Mar 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Three ways to refactor this.

Option 1 is for steps template to run just one build steps - either regular build or tests. It seems clunky, as we'd need to supply some property that specifies the build type, regular or test. Based on such property (or properties) we'd only do regular or test build. This way we can share a single entry point across all VMR pipelines.

Option 2 would run build and test (if requested). This implies that we'd also need to run any steps that are executed between main and test builds, i.e.

- ${{ if eq(variables['_EnableDacSigning'], 'true') }}:
# TODO: Once we turn off the dotnet/runtime official build, move these templates into the VMR's eng folder.
- template: ${{ variables['Build.SourcesDirectory'] }}/src/runtime/eng/pipelines/coreclr/templates/remove-diagnostic-certs.yml
parameters:
isOfficialBuild: ${{ variables.isOfficialBuild }}
scriptRoot: '$(Build.SourcesDirectory)/src/runtime'
and
- ${{ if ne(parameters.runOnline, 'True' )}}:
- script: |
set -ex
# Update the owner of the staging directory to the current user
sudo chown -R $(whoami) $(artifactsStagingDir)
displayName: Update owner of artifacts staging directory

Option 3 would move all of these steps to the new build steps yml:

- ${{ if eq(parameters.targetOS, 'windows') }}:
# Node 20.x is a toolset dependency to build aspnetcore
# Keep in sync with aspnetcore: https://github.com/dotnet/aspnetcore/blob/7d5309210d8f7bae8fa074da495e9d009d67f1b4/.azure/pipelines/ci.yml#L719-L722
- task: NodeTool@0
displayName: Install Node 20.x
inputs:
versionSpec: 20.x
- ${{ if eq(variables['_EnableDacSigning'], 'true') }}:
# TODO: Once we turn off the dotnet/runtime official build, move these templates into the VMR's eng folder.
- template: ${{ variables['Build.SourcesDirectory'] }}/src/runtime/eng/pipelines/coreclr/templates/install-diagnostic-certs.yml
parameters:
isOfficialBuild: ${{ variables.isOfficialBuild }}
certNames:
- 'dotnetesrp-diagnostics-aad-ssl-cert'
- 'dotnet-diagnostics-esrp-pki-onecert'
vaultName: 'clrdiag-esrp-id'
azureSubscription: 'diagnostics-esrp-kvcertuser'
scriptRoot: '$(Build.SourcesDirectory)/src/runtime'
- script: build.cmd
$(baseArguments)
$(targetArguments)
$(signArguments)
$(buildPassArguments)
$(ibcArguments)
$(_SignDiagnosticFilesArgs)
${{ parameters.extraProperties }}
displayName: Build
workingDirectory: ${{ variables.sourcesPath }}
- ${{ if eq(variables['_EnableDacSigning'], 'true') }}:
# TODO: Once we turn off the dotnet/runtime official build, move these templates into the VMR's eng folder.
- template: ${{ variables['Build.SourcesDirectory'] }}/src/runtime/eng/pipelines/coreclr/templates/remove-diagnostic-certs.yml
parameters:
isOfficialBuild: ${{ variables.isOfficialBuild }}
scriptRoot: '$(Build.SourcesDirectory)/src/runtime'
- ${{ if eq(parameters.runTests, 'True') }}:
- script: build.cmd
$(baseArguments)
$(targetArguments)
$(buildPassArguments)
${{ parameters.extraProperties }}
-test
-excludeCIBinarylog
/bl:artifacts/log/Release/Test.binlog
displayName: Run Tests
workingDirectory: ${{ variables.sourcesPath }}
timeoutInMinutes: ${{ variables.runTestsTimeout }}
- ${{ else }}:
- ${{ if eq(parameters.targetOS, 'osx') }}:
- script: |
$(sourcesPath)/eng/common/native/install-dependencies.sh osx
displayName: Install dependencies
- ${{ if eq(parameters.buildSourceOnly, 'true') }}:
- script: |
set -ex
customPrepArgs=""
prepSdk=true
if [[ -n '${{ parameters.artifactsRid }}' ]]; then
customPrepArgs="${customPrepArgs} --artifacts-rid ${{ parameters.artifactsRid }}"
fi
if [[ '${{ parameters.withPreviousSDK }}' == 'True' ]]; then
# Source-built artifacts are from CentOS 9 Stream. We want to download them without
# downloading portable versions from the internet.
customPrepArgs="${customPrepArgs} --no-sdk --no-bootstrap"
prepSdk=false
elif [[ '${{ length(parameters.reuseBuildArtifactsFrom) }}' -gt '0' ]]; then
customPrepArgs="${customPrepArgs} --no-sdk --no-artifacts"
prepSdk=false
fi
if [[ "$prepSdk" == "false" ]]; then
mkdir $(sourcesPath)/.dotnet
previousSdkPath="$(sourcesPath)/prereqs/packages/archive/dotnet-sdk-*.tar.gz"
eval tar -ozxf "$previousSdkPath" -C "$(sourcesPath)/.dotnet"
eval rm -f "$previousSdkPath"
echo "##vso[task.setvariable variable=additionalBuildArgs]--with-sdk $(sourcesPath)/.dotnet"
fi
./prep-source-build.sh $customPrepArgs
displayName: Prep the Build
workingDirectory: $(sourcesPath)
- script: |
set -ex
df -h
customEnvVars=""
customPreBuildArgs=""
customBuildArgs="--ci --clean-while-building --prepareMachine -c ${{ parameters.configuration }} $(officialBuildParameter)"
if [[ '${{ parameters.runOnline }}' == 'True' ]]; then
customBuildArgs="$customBuildArgs --online"
else
customPreBuildArgs="$customPreBuildArgs sudo unshare -n"
fi
if [[ '${{ parameters.enablePoison }}' == 'True' ]]; then
customBuildArgs="$customBuildArgs --poison"
fi
if [[ '${{ parameters.buildFromArchive }}' == 'True' ]]; then
customBuildArgs="$customBuildArgs --source-repository https://github.com/dotnet/dotnet"
customBuildArgs="$customBuildArgs --source-version $(git -C "$(vmrPath)" rev-parse HEAD)"
fi
if [[ '${{ parameters.buildSourceOnly }}' == 'True' ]]; then
customBuildArgs="$customBuildArgs --source-only"
extraBuildProperties="$extraBuildProperties /p:ReportSbrpUsage=true"
fi
if [[ '${{ parameters.useMonoRuntime }}' == 'True' ]]; then
customBuildArgs="$customBuildArgs --use-mono-runtime"
fi
if [[ '${{ parameters.sign }}' == 'True' ]] && [[ '${{ parameters.buildSourceOnly }}' != 'True' ]]; then
customBuildArgs="$customBuildArgs --sign"
if [[ '$(_SignType)' == 'real' ]] || [[ '$(_SignType)' == 'test' ]]; then
# Force dry run signing until https://github.com/dotnet/source-build/issues/4793 is resolved - https://github.com/dotnet/source-build/issues/4678
extraBuildProperties="$extraBuildProperties /p:DotNetSignType=$(_SignType) /p:TeamName=$(_TeamName) /p:ForceDryRunSigning=true"
else
extraBuildProperties="$extraBuildProperties /p:ForceDryRunSigning=true"
fi
fi
if [[ -n "${{ parameters.targetRid }}" ]]; then
customBuildArgs="$customBuildArgs --target-rid ${{ parameters.targetRid }}"
fi
if [[ -n "${{ parameters.crossRootFs }}" ]]; then
customEnvVars="$customEnvVars ROOTFS_DIR=${{ parameters.crossRootFs}}"
if [[ '${{ parameters.targetArchitecture }}' != 'wasm' ]]; then
extraBuildProperties="$extraBuildProperties /p:CrossBuild=true"
fi
fi
if [[ ! -z '${{ parameters.targetOS }}' ]]; then
extraBuildProperties="$extraBuildProperties /p:TargetOS=${{ parameters.targetOS }}"
fi
if [[ ! -z '${{ parameters.targetArchitecture }}' ]]; then
extraBuildProperties="$extraBuildProperties /p:TargetArchitecture=${{ parameters.targetArchitecture }}"
fi
if [[ -n "${{ parameters.buildPass }}" ]]; then
extraBuildProperties="$extraBuildProperties /p:DotNetBuildPass=${{ parameters.buildPass }}"
fi
if [[ -n "${{ parameters.extraProperties }}" ]]; then
extraBuildProperties="$extraBuildProperties ${{ parameters.extraProperties }}"
fi
extraBuildProperties="$extraBuildProperties /p:VerticalName=$(Agent.JobName)"
extraBuildProperties="$extraBuildProperties /p:ArtifactsStagingDir=$(artifactsStagingDir)"
buildArgs="$(additionalBuildArgs) $customBuildArgs $extraBuildProperties"
for envVar in $customEnvVars; do
customEnvVarsWithBashSyntax="$customEnvVarsWithBashSyntax export $envVar;"
done
eval $customEnvVarsWithBashSyntax
$customPreBuildArgs ./build.sh $buildArgs
displayName: Build
workingDirectory: $(sourcesPath)
- ${{ if ne(parameters.runOnline, 'True' )}}:
- script: |
set -ex
# Update the owner of the staging directory to the current user
sudo chown -R $(whoami) $(artifactsStagingDir)
displayName: Update owner of artifacts staging directory
# Only run tests if enabled
- ${{ if eq(parameters.runTests, 'True') }}:
# Setup the NuGet sources used by the tests to use private feeds. This is necessary when testing internal-only product
# builds where the packages are only available in the private feeds. This allows the tests to restore from those feeds.
- ${{ if eq(variables['System.TeamProject'], 'internal') }}:
- task: Bash@3
displayName: Setup Private Feeds Credentials
inputs:
filePath: $(sourcesPath)/src/sdk/eng/common/SetupNugetSources.sh
arguments: $(sourcesPath)/src/sdk/NuGet.config $Token
env:
Token: $(dn-bot-dnceng-artifact-feeds-rw)
- script: cp "$(sourcesPath)/src/sdk/NuGet.config" "$(sourcesPath)/test/Microsoft.DotNet.SourceBuild.Tests/assets/online.NuGet.Config"
displayName: Copy Test NuGet Config for Smoke Tests
- script: |
set -ex
customPreBuildArgs=''
customBuildArgs=''
extraBuildProperties=''
customBuildArgs="--ci --prepareMachine -c ${{ parameters.configuration }} $(officialBuildParameter)"
if [[ '${{ parameters.runOnline }}' == 'False' ]]; then
customPreBuildArgs="$customPreBuildArgs sudo"
fi
if [[ ! -z '${{ parameters.targetOS }}' ]]; then
extraBuildProperties="$extraBuildProperties /p:TargetOS=${{ parameters.targetOS }}"
fi
if [[ ! -z '${{ parameters.targetArchitecture }}' ]]; then
extraBuildProperties="$extraBuildProperties /p:TargetArchitecture=${{ parameters.targetArchitecture }}"
fi
if [[ '${{ parameters.buildSourceOnly }}' == 'True' ]]; then
if [[ '${{ parameters.enablePoison }}' == 'True' ]]; then
customBuildArgs="$customBuildArgs --poison"
fi
customBuildArgs="$customBuildArgs --source-only /p:SourceBuildTestsExcludeOmniSharpTests=${{ parameters.excludeOmniSharpTests }}"
fi
if [[ -n "${{ parameters.targetRid }}" ]]; then
customBuildArgs="$customBuildArgs --target-rid ${{ parameters.targetRid }}"
fi
extraBuildProperties="$extraBuildProperties /p:VerticalName=$(Agent.JobName)"
extraBuildProperties="$extraBuildProperties /p:ArtifactsStagingDir=$(artifactsStagingDir)"
if [[ -n "${{ parameters.extraProperties }}" ]]; then
extraBuildProperties="$extraBuildProperties ${{ parameters.extraProperties }}"
fi
cd $(sourcesPath)
$customPreBuildArgs ./build.sh --test --excludeCIBinarylog /bl:artifacts/log/Release/Test.binlog $customBuildArgs $extraBuildProperties $(additionalBuildArgs)
displayName: Run Tests
timeoutInMinutes: ${{ variables.runTestsTimeout }}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Option 3 sounds best but I'm not sure what others think. cc @mmitche

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I am missing the difference between Option 1 and 3 here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Option 1 would create a very small yml with a single step to invoke build.cmd or build.sh with various arguments. All conditioning, prep, and all other steps, like installing Node would stay in jobs/vmr-build.yml. This option would require a new parameter that would specify the build type (i.e. regularBuild vs test), so we could run one or the other type of build.

Option 3 would move the whole ~250 line block I linked above into the new steps yml. It keeps most of build and related steps together, and leaves the extra stuff like publishing in the current yml.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will follow up with refactoring in a separate PR - still working out the issues.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are missing the --ci flag here which is one of the more important args as it controls whether the machine wide package cache gets used or not (for non-source-only builds).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yeah, I don't use it locally, but other official legs/steps do use it - will fix, thanks.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed with 5dfa459

--ci \
-t \
--projects test/Microsoft.DotNet.Installer.Tests/Microsoft.DotNet.Installer.Tests.csproj \
/p:BlobArtifactsDir=$(Build.ArtifactStagingDirectory)/BlobArtifacts \
/p:PackageArtifactsDir=$(Build.ArtifactStagingDirectory)/PackageArtifacts \
$extraBuildProperties
displayName: Validate installer packages
workingDirectory: $(Build.SourcesDirectory)
continueOnError: ${{ parameters.continueOnError }}

- task: PublishTestResults@2
displayName: Publish Test Results
condition: succeededOrFailed()
continueOnError: true
inputs:
testRunner: VSTest
testResultsFiles: 'artifacts/TestResults/Release/*.trx'
searchFolder: $(Build.SourcesDirectory)
mergeTestResults: true
publishRunAttachments: true
testRunTitle: Tests_$(Agent.JobName)
11 changes: 11 additions & 0 deletions src/SourceBuild/content/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ usage()
echo " --build-repo-tests Build repository tests"

echo "Advanced settings:"
echo " --projects <value> Project or solution file to build"
echo " --ci Set when running on CI server"
echo " --clean-while-building Cleans each repo after building (reduces disk space usage, short: -cwb)"
echo " --excludeCIBinarylog Don't output binary log (short: -nobl)"
Expand Down Expand Up @@ -82,6 +83,7 @@ packagesArchiveDir="${packagesDir}archive/"
packagesPreviouslySourceBuiltDir="${packagesDir}previously-source-built/"

# Advanced settings
projects=''
ci=false
exclude_ci_binary_log=false
prepare_machine=false
Expand Down Expand Up @@ -176,6 +178,10 @@ while [[ $# > 0 ]]; do
-build-repo-tests)
properties+=( "/p:DotNetBuildTests=true" )
;;
-projects)
projects=$2
shift
;;
-ci)
ci=true
;;
Expand Down Expand Up @@ -222,6 +228,11 @@ if [[ "$test" == true ]]; then
export MSBUILDDISABLENODEREUSE=1
fi

# Override project if specified on cmd-line
if [[ ! -z "$projects" ]]; then
project="$projects"
fi

function Build {
if [[ "$sourceOnly" != "true" ]]; then

Expand Down
7 changes: 7 additions & 0 deletions src/SourceBuild/content/eng/build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Param(

# Advanced settings
[switch]$buildRepoTests,
[string]$projects,
[switch]$ci,
[switch][Alias('cwb')]$cleanWhileBuilding,
[switch][Alias('nobl')]$excludeCIBinarylog,
Expand All @@ -36,6 +37,7 @@ function Get-Usage() {

Write-Host "Advanced settings:"
Write-Host " -buildRepoTests Build repository tests"
Write-Host " -projects <value> Project or solution file to build"
Write-Host " -ci Set when running on CI server"
Write-Host " -cleanWhileBuilding Cleans each repo after building (reduces disk space usage, short: -cwb)"
Write-Host " -excludeCIBinarylog Don't output binary log (short: -nobl)"
Expand Down Expand Up @@ -64,6 +66,11 @@ if ($test) {
$env:MSBUILDENSURESTDOUTFORTASKPROCESSES="1"
}

# Override project if specified on cmd-line
if ($projects) {
$project = $projects
}

if ($sign) {
$arguments += "/p:Sign=true"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@

namespace Microsoft.DotNet.Installer.Tests;

public enum Architecture
{
X64,
Arm64
}

public static class Config
{
public static string AssetsDirectory { get; } = GetRuntimeConfig(AssetsDirectorySwitch);
Expand All @@ -20,7 +26,7 @@ public static class Config
public static string ScenarioTestsNuGetConfigPath { get; } = GetRuntimeConfig(ScenarioTestsNuGetConfigSwitch);
const string ScenarioTestsNuGetConfigSwitch = RuntimeConfigSwitchPrefix + nameof(ScenarioTestsNuGetConfigPath);

public static string Architecture { get; } = GetRuntimeConfig(ArchitectureSwitch);
public static Architecture Architecture { get; } = GetArchitecture(GetRuntimeConfig(ArchitectureSwitch));
const string ArchitectureSwitch = RuntimeConfigSwitchPrefix + nameof(Architecture);

public static bool TestRpmPackages { get; } = TryGetRuntimeConfig(TestRpmPackagesSwitch, out bool value) ? value : false;
Expand All @@ -34,6 +40,13 @@ public static class Config

public const string RuntimeConfigSwitchPrefix = "Microsoft.DotNet.Installer.Tests.";

public static Architecture GetArchitecture(string architecture) => architecture switch
{
"x64" => Architecture.X64,
"arm64" => Architecture.Arm64,
_ => throw new ArgumentException($"Unknown architecture: {architecture}")
};

public static string GetRuntimeConfig(string key)
{
return TryGetRuntimeConfig(key, out string? value) ? value : throw new InvalidOperationException($"Runtime config setting '{key}' must be specified");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ public class LinuxInstallerTests : IDisposable
private readonly string _tmpDir;
private readonly string _contextDir;
private readonly ITestOutputHelper _outputHelper;
private readonly string _excludeLinuxArch;

private bool _rpmContextInitialized = false;
private bool _debContextInitialized = false;
private bool _sharedContextInitialized = false;

private const string NetStandard21RpmPackage = @"https://dotnetcli.blob.core.windows.net/dotnet/Runtime/3.1.0/netstandard-targeting-pack-2.1.0-x64.rpm";
private const string NetStandard21DebPackage = @"https://dotnetcli.blob.core.windows.net/dotnet/Runtime/3.1.0/netstandard-targeting-pack-2.1.0-x64.deb";
private const string RuntimeDepsRepo = "mcr.microsoft.com/dotnet/nightly/runtime-deps";
private const string RuntimeDepsRepo = "mcr.microsoft.com/dotnet/runtime-deps";
private const string RuntimeDepsVersion = "10.0-preview";

public static bool IncludeRpmTests => Config.TestRpmPackages;
Expand All @@ -53,13 +54,17 @@ public LinuxInstallerTests(ITestOutputHelper outputHelper)
Directory.CreateDirectory(_tmpDir);
_contextDir = Path.Combine(_tmpDir, Path.GetRandomFileName());
Directory.CreateDirectory(_contextDir);

_excludeLinuxArch = Config.Architecture == Architecture.X64 ?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this just be the following? Why is the condition necessary?

Suggested change
_excludeLinuxArch = Config.Architecture == Architecture.X64 ?
_excludeLinuxArch = Architecture.Arm64.ToString().ToLower()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong example... Config.Architecture.ToString().ToLower()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For x64 tests we exclude arm64 packages from copying to the image. For arm64 tests we exclude x64 packages.

Copy link
Member

@MichaelSimons MichaelSimons Mar 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see - taking a step back, my larger question is what will happen if/when additional architectures are added? Ideally this code would be written in a way that would be unaffected.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we add another supported architecture for testing, we'd need to modify this code and the Architecture enum, at the same time.

This member field as an optimization only. We could just iterate over all supported architectures that are not being tested, in the filtering method. That would incur performance cost.

Ideally, as we discussed before, I think filtering should be modified to consume the merged manifest and filter packages based on vertical name/Id. I can do a follow up PR with that change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think filtering should be modified to consume the merged manifest and filter packages based on vertical name/Id. I can do a follow up PR with that change.

Sounds good to me.

Architecture.Arm64.ToString().ToLower() :
Architecture.X64.ToString().ToLower();
}

public void Dispose()
{
try
{
//Directory.Delete(_tmpDir, recursive: true);
Directory.Delete(_tmpDir, recursive: true);
}
catch
{
Expand Down Expand Up @@ -92,31 +97,46 @@ public void DebTest(string repo, string tag)

private void InitializeContext(PackageType packageType)
{
string packageArchitecture =
Config.Architecture == Architecture.X64 ?
"x64" :
packageType == PackageType.Rpm ?
"aarch64" :
"arm64";

if (packageType == PackageType.Rpm && !_rpmContextInitialized)
{
// For rpm enumerate RPM packages, excluding those that contain ".cm." in the name
List<string> rpmPackages = Directory.GetFiles(Config.AssetsDirectory, "*.rpm", SearchOption.AllDirectories)
.Where(p => !Path.GetFileName(p).Contains("-cm.") && !Path.GetFileName(p).EndsWith("azl.rpm"))
// Copy all applicable RPM packages, excluding Mariner and Azure Linux copies
List<string> rpmPackages =
Directory.GetFiles(Config.AssetsDirectory, $"*-{packageArchitecture}*.rpm", SearchOption.AllDirectories)
.Where(p => !Path.GetFileName(p).Contains("-cm.") &&
!Path.GetFileName(p).Contains("-azl-") &&
!Path.GetFileName(p).EndsWith("azl.rpm"))
.ToList();

foreach (string rpmPackage in rpmPackages)
{
File.Copy(rpmPackage, Path.Combine(_contextDir, Path.GetFileName(rpmPackage)));
}

DownloadFileAsync(NetStandard21RpmPackage, Path.Combine(_contextDir, Path.GetFileName(NetStandard21RpmPackage))).Wait();
if (Config.Architecture == Architecture.X64)
{
DownloadFileAsync(NetStandard21RpmPackage, Path.Combine(_contextDir, Path.GetFileName(NetStandard21RpmPackage))).Wait();
}
_rpmContextInitialized = true;
}
else if (!_debContextInitialized)
{
// Copy all DEB packages as well
foreach (string debPackage in Directory.GetFiles(Config.AssetsDirectory, "*.deb", SearchOption.AllDirectories))
// Copy all applicable DEB packages
foreach (string debPackage in Directory.GetFiles(Config.AssetsDirectory, $"*-{packageArchitecture}*.deb", SearchOption.AllDirectories))
{
File.Copy(debPackage, Path.Combine(_contextDir, Path.GetFileName(debPackage)));
}

// Download NetStandard 2.1 packages
DownloadFileAsync(NetStandard21DebPackage, Path.Combine(_contextDir, Path.GetFileName(NetStandard21DebPackage))).Wait();
if (Config.Architecture == Architecture.X64)
{
DownloadFileAsync(NetStandard21DebPackage, Path.Combine(_contextDir, Path.GetFileName(NetStandard21DebPackage))).Wait();
}
_debContextInitialized = true;
}

Expand All @@ -127,7 +147,10 @@ private void InitializeContext(PackageType packageType)
Directory.CreateDirectory(nugetPackagesDir);
foreach (string package in Directory.GetFiles(Config.PackagesDirectory, "*.nupkg", SearchOption.AllDirectories))
{
File.Copy(package, Path.Combine(nugetPackagesDir, Path.GetFileName(package)));
if (ShouldCopyPackage(package.ToLower()))
{
File.Copy(package, Path.Combine(nugetPackagesDir, Path.GetFileName(package)));
}
}

// Copy and update NuGet.config from scenario-tests repo
Expand All @@ -147,6 +170,24 @@ private void InitializeContext(PackageType packageType)
}
}

private bool ShouldCopyPackage(string package)
{
if (package.Contains(".osx-") ||
package.Contains(".win-") ||
package.Contains(".linux-musl-") ||
package.Contains(".linux-bionic-") ||
package.Contains(".mono.") ||
package.Contains("symbols") ||
package.Contains("vs.redist") ||
package.Contains(".linux-arm.") ||
package.Contains($".linux-{_excludeLinuxArch}."))
{
return false;
}

return true;
}

private void InsertLocalPackagesPathToNuGetConfig(string nuGetConfig, string localPackagesPath)
{
XDocument doc = XDocument.Load(nuGetConfig);
Expand Down Expand Up @@ -247,7 +288,7 @@ private List<string> GetPackageList(string baseImage, PackageType packageType)
AddPackage(packageList, "aspnetcore-runtime-", packageType);
AddPackage(packageList, "aspnetcore-targeting-pack-", packageType);
AddPackage(packageList, "dotnet-apphost-pack-", packageType);
if (Config.Architecture == "x64")
if (Config.Architecture == Architecture.X64)
{
// netstandard package exists for x64 only
AddPackage(packageList, "netstandard-targeting-pack-", packageType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
<VSTestCLIRunSettings>$(VSTestCLIRunSettings);RunConfiguration.DotNetHostPath=$(DotnetTool)</VSTestCLIRunSettings>
</PropertyGroup>

<PropertyGroup>
<BlobArtifactsDir Condition="'$(BlobArtifactsDir)' == ''">$(ArtifactsAssetsDir)</BlobArtifactsDir>
<PackageArtifactsDir Condition="'$(PackageArtifactsDir)' == ''">$(ArtifactsPackagesDir)</PackageArtifactsDir>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\TestUtilities\TestUtilities.csproj" />
</ItemGroup>
Expand All @@ -15,10 +20,10 @@
<ItemGroup>
<!-- General configs -->
<RuntimeHostConfigurationOption Include="$(MSBuildProjectName).AssetsDirectory">
<Value>$(ArtifactsAssetsDir)</Value>
<Value>$(BlobArtifactsDir)</Value>
</RuntimeHostConfigurationOption>
<RuntimeHostConfigurationOption Include="$(MSBuildProjectName).PackagesDirectory">
<Value>$(ArtifactsPackagesDir)</Value>
<Value>$(PackageArtifactsDir)</Value>
</RuntimeHostConfigurationOption>
<RuntimeHostConfigurationOption Include="$(MSBuildProjectName).ScenarioTestsNuGetConfigPath">
<Value>$(RepoRoot)src\scenario-tests\NuGet.config</Value>
Expand Down
Loading