diff --git a/TestAll.ps1 b/TestAll.ps1 index e6b9e061e6..016afd2b4c 100644 --- a/TestAll.ps1 +++ b/TestAll.ps1 @@ -72,7 +72,10 @@ param( [Switch]$ShowSystemInfo=$true, [Parameter(Mandatory=$true)] - [string]$wprProfilePath + [string]$wprProfilePath, + + [Parameter(Mandatory=$false)] + [string]$callingStage = '' ) $StartTime = Get-Date @@ -120,6 +123,15 @@ function Get-Tests } } + if ($callingStage -eq 'TestSampleApps') + { + $tests = $tests | Where-Object { $_.Filename -like "WindowsAppSDK.Test.SampleTests.dll" } + } + else + { + $tests = $tests | Where-Object { $_.Filename -notlike "WindowsAppSDK.Test.SampleTests.dll" } + } + $tests } @@ -140,7 +152,7 @@ function Run-TaefTest $teLogFile = (Join-Path $env:Build_SourcesDirectory "BuildOutput\$Configuration\$Platform\Te.wtl") $teLogPathTo = (Join-Path $env:Build_SourcesDirectory "TestOutput\$Configuration\$Platform") - & $tePath $dllFile $test.Parameters /enableWttLogging /appendWttLogging /screenCaptureOnError /logFile:$teLogFile $/testMode:EtwLogger /EtwLogger:WprProfile=WDGDEPAdex /EtwLogger:SavePoint=TestFailure /EtwLogger:RecordingScope=Execution /EtwLogger:WprProfileFile=$wprProfilePath + & $tePath $dllFile $test.Parameters /enableWttLogging /appendWttLogging /screenCaptureOnError /logFile:$teLogFile /testMode:EtwLogger /EtwLogger:WprProfile=WDGDEPAdex /EtwLogger:SavePoint=TestFailure /EtwLogger:RecordingScope=Execution /EtwLogger:WprProfileFile=$wprProfilePath } function Run-PowershellTest @@ -218,6 +230,9 @@ function Get-SystemInfo Write-Host "Powershell : $($PSVersionTable.PSEdition) $($PSVersionTable.PSVersion)" } +$env:Build_Platform = $Platform.ToLower() +$env:Build_Configuration = $Configuration.ToLower() + if ($ShowSystemInfo -eq $true) { Get-SystemInfo diff --git a/WindowsAppRuntime.sln b/WindowsAppRuntime.sln index 9da57f3c05..714bda40d2 100644 --- a/WindowsAppRuntime.sln +++ b/WindowsAppRuntime.sln @@ -742,6 +742,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mrmmin", "dev\MRTCore\mrt\m EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MRM", "dev\MRTCore\mrt\Core\src\MRM.vcxproj", "{CF03CC8D-FFF1-4CDC-B773-D219AD4E6F76}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsAppSDK.Test.NetCore", "test\WindowsAppSDK.Test.NetCore\WindowsAppSDK.Test.NetCore.csproj", "{BF580B26-B869-3AF1-43EC-D0FD55A49E4D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsAppSDK.Test.SampleTests", "test\WindowsAppSDK.Test.SampleTests\WindowsAppSDK.Test.SampleTests.csproj", "{346E099B-45E4-FF40-E63D-8B34915223C1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -2588,6 +2592,38 @@ Global {CF03CC8D-FFF1-4CDC-B773-D219AD4E6F76}.Release|x64.Build.0 = Release|x64 {CF03CC8D-FFF1-4CDC-B773-D219AD4E6F76}.Release|x86.ActiveCfg = Release|Win32 {CF03CC8D-FFF1-4CDC-B773-D219AD4E6F76}.Release|x86.Build.0 = Release|Win32 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Debug|Any CPU.ActiveCfg = Debug|x64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Debug|Any CPU.Build.0 = Debug|x64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Debug|ARM64.ActiveCfg = Debug|arm64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Debug|ARM64.Build.0 = Debug|arm64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Debug|x64.ActiveCfg = Debug|x64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Debug|x64.Build.0 = Debug|x64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Debug|x86.ActiveCfg = Debug|x86 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Debug|x86.Build.0 = Debug|x86 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Release|Any CPU.ActiveCfg = Release|x64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Release|Any CPU.Build.0 = Release|x64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Release|ARM64.ActiveCfg = Release|arm64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Release|ARM64.Build.0 = Release|arm64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Release|x64.ActiveCfg = Release|x64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Release|x64.Build.0 = Release|x64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Release|x86.ActiveCfg = Release|x86 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Release|x86.Build.0 = Release|x86 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Debug|Any CPU.ActiveCfg = Debug|x64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Debug|Any CPU.Build.0 = Debug|x64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Debug|ARM64.ActiveCfg = Debug|arm64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Debug|ARM64.Build.0 = Debug|arm64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Debug|x64.ActiveCfg = Debug|x64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Debug|x64.Build.0 = Debug|x64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Debug|x86.ActiveCfg = Debug|x86 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Debug|x86.Build.0 = Debug|x86 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Release|Any CPU.ActiveCfg = Release|x64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Release|Any CPU.Build.0 = Release|x64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Release|ARM64.ActiveCfg = Release|arm64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Release|ARM64.Build.0 = Release|arm64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Release|x64.ActiveCfg = Release|x64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Release|x64.Build.0 = Release|x64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Release|x86.ActiveCfg = Release|x86 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2811,6 +2847,8 @@ Global {E9C055BB-6AE4-497A-A354-D07841E68976} = {022E355A-AB24-48EE-9CC0-965BEFDF5E8C} {DC453DE3-18FD-43E7-8103-20763C8B97C8} = {5012149E-F09F-4F18-A03C-FFE597203821} {C40AE1D8-FD5F-472E-86B5-DDA5ABA6FF99} = {8630F7AA-2969-4DC9-8700-9B468C1DC21D} + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D} = {8630F7AA-2969-4DC9-8700-9B468C1DC21D} + {346E099B-45E4-FF40-E63D-8B34915223C1} = {8630F7AA-2969-4DC9-8700-9B468C1DC21D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4B3D7591-CFEC-4762-9A07-ABE99938FB77} diff --git a/build/AzurePipelinesTemplates/WindowsAppSDK-BuildAndTestSampleApps-Stage.yml b/build/AzurePipelinesTemplates/WindowsAppSDK-BuildAndTestSampleApps-Stage.yml new file mode 100644 index 0000000000..4d723844a8 --- /dev/null +++ b/build/AzurePipelinesTemplates/WindowsAppSDK-BuildAndTestSampleApps-Stage.yml @@ -0,0 +1,148 @@ +# Note: In Foundation, the approach to test launching sample apps differs slightly from Aggregator: +# 1. In Foundation, we only exercises a subset of the sample apps that are tested in Aggregator. +# 2. All sample apps in Foundation are built and tested in SelfContained mode. +# The reason for this difference is: sample apps only use component package in Foundation. There is no associated framework package, Runtime package is not generated until Aggregator. +# 3. Foundation pipeline only supports running sample app testing in one specific stage. + +parameters: + - name: "IsOneBranch" + type: boolean + default: true + - name: TestSampleApps + displayName: "Test launch of Sample apps separately in the TestSampleApps Stage" + type: boolean + default: true + - name: TestOnArm64 + type: boolean + default: true + - name: SamplesBuildOutputArtifactName + displayName: "Base name for Samples build output artifacts" + type: string + default: 'SamplesBuildOutput' + - name: runStaticAnalysis + type: boolean + default: true + - name: maxParallel_x64 + type: number + default: 10 + - name: maxParallel_arm64 + type: number + default: 10 + # sampleFeatureAreasList param have the same naming rule and pattern as Aggregator repo: + # https://microsoft.visualstudio.com/ProjectReunion/_git/WindowsAppSDKAggregator?path=/build/AzurePipelinesTemplates/WindowsAppSDK-BuildWindowsAppSDK-Stages.yml + - name: "sampleFeatureAreasList" + type: object + default: + SamplesCompatTest: + - 'Installer' + - 'Unpackaged' + - 'Notifications-Push' + - 'Insights' + - 'AppLifecycle-Activation-cpp-cpp--console--unpackaged' + - 'AppLifecycle-Activation-cpp-cpp--win32--unpackaged' + - 'AppLifecycle-Activation-cs' + - 'AppLifecycle-Instancing-cpp-cpp--console--unpackaged' + - 'AppLifecycle-Instancing-cpp-cpp--win32--packaged' + - 'AppLifecycle-Instancing-cpp-cpp--win32--unpackaged' + - 'AppLifecycle-Instancing-cs' + - 'AppLifecycle-Instancing-cs1' + - 'AppLifecycle-StateNotifications-cpp-cpp--console--unpackaged' + - 'AppLifecycle-StateNotifications-cpp-cpp--win32--packaged' + - 'AppLifecycle-StateNotifications-cpp-cpp--win32--unpackaged' + - 'AppLifecycle-StateNotifications-cs' + - 'AppLifecycle-StateNotifications-cs1' + - 'AppLifecycle-EnvironmentVariables' + - 'AppLifecycle-Restart-cpp--console--unpackaged' + - 'ResourceManagement-cpp-cpp--console--unpackaged' + - 'ResourceManagement-cs-cs--winforms--unpackaged' + - 'ResourceManagement-cs1' + - 'Mica-cpp--win32' + - 'Windowing-cpp-cpp--win32' + - 'Windowing-cs-cs--winforms--unpackaged' + - 'SelfContainedDeployment-cpp-cpp--console--unpackaged' + - 'SelfContainedDeployment-cs' + +stages: +- stage: BuildSampleApps_x64 + dependsOn: Pack + variables: + SamplesRepoName: 'WindowsAppSDK-Samples' + SelfRepoName: 'WindowsAppSDK' + FoundationVersion: $[ stageDependencies.Pack.NugetPackage.outputs['DetermineComponentNugetPackageVersion.componentPackageVersion'] ] + jobs: + - template: WindowsAppSDK-BuildSamplesCompat-Job.yml + parameters: + IsOneBranch: ${{ parameters.IsOneBranch }} + JobName: "SamplesCompatTest" + FeatureAreas: ${{ parameters.sampleFeatureAreasList.SamplesCompatTest }} + BuildConfig: + - 'Release' + - 'Debug' + BuildPlatform: + - 'x64' + ${{ if eq(parameters.TestSampleApps, 'true') }}: + SamplesArtifactName: ${{ parameters.SamplesBuildOutputArtifactName }}_x64 + ${{ if ne(parameters.TestSampleApps, 'true') }}: + SamplesArtifactName: '' + runStaticAnalysis : ${{ parameters.runStaticAnalysis }} + maxParallel: ${{ parameters.maxParallel_x64 }} + +- stage: BuildSampleApps_arm64 + dependsOn: Pack + variables: + SamplesRepoName: 'WindowsAppSDK-Samples' + SelfRepoName: 'WindowsAppSDK' + FoundationVersion: $[ stageDependencies.Pack.NugetPackage.outputs['DetermineComponentNugetPackageVersion.componentPackageVersion'] ] + jobs: + - template: WindowsAppSDK-BuildSamplesCompat-Job.yml + parameters: + IsOneBranch: ${{ parameters.IsOneBranch }} + JobName: "SamplesCompatTest" + FeatureAreas: ${{ parameters.sampleFeatureAreasList.SamplesCompatTest }} + BuildConfig: + - 'Release' + - 'Debug' + BuildPlatform: + - 'arm64' + ${{ if eq(parameters.TestSampleApps, 'true') }}: + SamplesArtifactName: ${{ parameters.SamplesBuildOutputArtifactName }}_arm64 + ${{ if ne(parameters.TestSampleApps, 'true') }}: + SamplesArtifactName: '' + runStaticAnalysis : ${{ parameters.runStaticAnalysis }} + maxParallel: ${{ parameters.maxParallel_arm64 }} + +- ${{ if eq( parameters.TestSampleApps, true ) }}: + - stage: TestSampleApps_x64 + dependsOn: + - BuildSampleApps_x64 + - Build_x64 + variables: + SamplesRepoName: 'WindowsAppSDK-Samples' + SelfRepoName: 'WindowsAppSDK' + jobs: + - template: WindowsAppSDK-RunTestsInPipeline-Job.yml + parameters: + jobName: TestSamplesX64 + samplesArtifactName: ${{ parameters.SamplesBuildOutputArtifactName }}_x64 + callingStage: 'TestSampleApps' + sampleFeatureAreasList: ${{ parameters.sampleFeatureAreasList }} + IsOneBranch: ${{ parameters.IsOneBranch }} + TestMatrix: $[ variables.SampleAppsTests ] + + - ${{ if eq(parameters.TestOnArm64, true)}}: + - stage: TestSampleApps_arm64 + dependsOn: + - BuildSampleApps_arm64 + - Build_arm64 + variables: + SamplesRepoName: 'WindowsAppSDK-Samples' + SelfRepoName: 'WindowsAppSDK' + jobs: + - template: WindowsAppSDK-RunTestsInPipeline-Job.yml + parameters: + jobName: TestSamplesArm64 + samplesArtifactName: ${{ parameters.SamplesBuildOutputArtifactName }}_arm64 + callingStage: 'TestSampleApps' + sampleFeatureAreasList: ${{ parameters.sampleFeatureAreasList }} + IsOneBranch: ${{ parameters.IsOneBranch }} + TestMatrix: $[ variables.SampleAppsTestsArm64 ] \ No newline at end of file diff --git a/build/AzurePipelinesTemplates/WindowsAppSDK-BuildSamplesCompat-Job.yml b/build/AzurePipelinesTemplates/WindowsAppSDK-BuildSamplesCompat-Job.yml new file mode 100644 index 0000000000..2446fecc41 --- /dev/null +++ b/build/AzurePipelinesTemplates/WindowsAppSDK-BuildSamplesCompat-Job.yml @@ -0,0 +1,398 @@ +# This yml file is used to build sample apps, having the same structure and pattern as Aggregator repo: +# https://microsoft.visualstudio.com/ProjectReunion/_git/WindowsAppSDKAggregator?path=/build/AzurePipelinesTemplates/WindowsAppSDK-BuildSamplesCompat-Job.yml +parameters: +- name: "IsOneBranch" + type: boolean + default: true +- name: JobName + type: string + default: 'SamplesCompatTest' +- name: FeatureAreas + type: object + default: + - '' +- name: "BuildConfig" + type: object + default: + - 'Release' + - 'Debug' +- name: "BuildPlatform" + type: object + default: + - 'x64' +- name: "SamplesArtifactName" + displayName: "Supply a valid base name for Sample Apps BuildOutput to trigger publishing of these artifacts" + type: string + default: '' +- name: runStaticAnalysis + type: boolean + default: true +- name: maxParallel + type: number + default: 10 + +jobs: +- job: ${{ parameters.JobName }} + pool: + ${{ if parameters.IsOneBranch }}: + type: windows + ${{ if not( parameters.IsOneBranch ) }}: + type: windows + isCustom: true + name: 'ProjectReunionESPool-2022' + timeoutInMinutes: 120 + strategy: + maxParallel: ${{ parameters.maxParallel }} + matrix: + ${{ each featureArea in parameters.FeatureAreas }}: + ${{ featureArea }}: + feature: ${{ featureArea }} + variables: + ob_outputDirectory: '$(Build.ArtifactStagingDirectory)' + ob_sdl_codeSignValidation_excludes: '-|**\*' + ob_artifactBaseName: '${{ parameters.SamplesArtifactName }}' + ob_artifactSuffix: '_$(feature)' + # Currently, the version of PREfast in Guardian does not correctly detect VSBuild tasks, that is a known issue tracked by: + # Feature 190028: Ingest 1ES Template update to successfully detect VSBuild tasks and run PREfast successfully + # Because of that, we've been explicitly invoking the PREfast task instead of relying on Guardian's PREfast. Recently, the + # non-functional PREfast task injected via Guardian is starting to emit non-fatal errors (previously, it quietly skipped + # itself), to the effect of "no build operation detected to triggger PREfast for", creating noise in the build output page. + # Therefore, turning off Guardian's PREfast altogether to eliminate the noise. Our pipeline still does our own PREfast scan + # and the "Guardian: Post Analysis" task still analyzes the logs produced by our explicit PREfast scan as usual. + ob_sdl_prefast_enabled: false + ob_sdl_checkCompliantCompilerWarnings: true # This setting has no effect unless ob_sdl_msbuildOverride below is also set to true. + ob_sdl_msbuildOverride: true # Because we are calling MSBuild directly instead of through the MSBuild@1 or VSBuild@1 tasks. + ob_sdl_prefast_runDuring: 'Guardian' # The default 'Build' setting does not match the fact that we are calling msbuild.exe directly. + + steps: + - checkout: self + - checkout: WindowsAppSDKSamples + + # Used to restore Windows SDK projection preview package from MSFTNuGet feed + - task: NuGetAuthenticate@1 + displayName: "NuGet authenticate to restore Windows SDK projection" + + - task: PowerShell@2 + displayName: 'Add Windows SDK 10.0.22000' + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + inputs: + targetType: filePath + filePath: $(Build.SourcesDirectory)\$(SelfRepoName)\build\scripts\windows-sdk.ps1 + # TODO: the SdkVersion parameter does not yet support arbitrary versions. + arguments: > + -SdkVersion "10.0.22000" + + # Local variable 'feature' used to potentially contain a '\' and that is handy when using + # 'feature' as part of a file path. However, when we started using 'feature' as part of + # the name of an artifact in OneBranch, the '\' is an illegal character in an artifact + # name. Hence, a '-' replaced the '\' in 'feature', and we convert the '-' to '\' in + # there to produce variable 'featureForFilePath' and use it as part of a file path. + - task: PowerShell@2 + name: MakeFeatureUsableAsFilePath + displayName: Convert feature to featureForFilePath + inputs: + targetType: 'inline' + script: | + $tempFeature = '$(feature)' + if ($tempFeature.Contains("-")) + { + $tempFeature = $tempFeature.Replace("--", "TEMPHYPHEN") + $tempFeature = $tempFeature.Replace("-", "\") + $tempFeature = $tempFeature.Replace("TEMPHYPHEN", "-") + } + Write-Host "##vso[task.setvariable variable=featureForFilePath;isOutput=true]$tempFeature" + Write-Host "##vso[task.setvariable variable=featureForFilePath;]$tempFeature" + + - task: DownloadPipelineArtifact@2 + displayName: 'Download WindowsAppSDK.Foundation' + inputs: + artifactName: 'TransportPackage' + targetPath: '$(Build.SourcesDirectory)\TransportPackage' + + # Copy WindowsAppSDK.Foundation to local package folder + - task: CopyFiles@2 + displayName: 'Copy WindowsAppSDK.Foundation to local package folder' + inputs: + SourceFolder: '$(Build.SourcesDirectory)\TransportPackage' + Contents: | + Microsoft.WindowsAppSDK.Foundation.[0-9]*.nupkg + TargetFolder: '$(Build.SourcesDirectory)\localpackages\NugetPackages' + + # Install Microsoft.WindowsAppSDK.Foundation to get all its dependencies and their version. + - task: NuGetCommand@2 + inputs: + command: 'custom' + arguments: > + install "Microsoft.WindowsAppSDK.Foundation" + -Source "$(Build.SourcesDirectory)\localpackages\NugetPackages" + -Version "$(FoundationVersion)" + -OutputDirectory "$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples\localpackages" + -FallbackSource "https://microsoft.pkgs.visualstudio.com/ProjectReunion/_packaging/Project.Reunion.nuget.internal/nuget/v3/index.json" + + # The environment variable VCToolsInstallDir isn't defined on lab machines, so we need to retrieve it ourselves. + - script: | + "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -Latest -prerelease -requires Microsoft.Component.MSBuild -property InstallationPath > %TEMP%\vsinstalldir.txt + set /p _VSINSTALLDIR15=<%TEMP%\vsinstalldir.txt + del %TEMP%\vsinstalldir.txt + call "%_VSINSTALLDIR15%\Common7\Tools\VsDevCmd.bat" + echo VCToolsInstallDir = %VCToolsInstallDir% + echo ##vso[task.setvariable variable=VCToolsInstallDir]%VCToolsInstallDir% + displayName: 'Retrieve VC tools directory' + + # In Aggregator, we use global.json to specify the .NET SDK version to use. + # However, in Foundation it encounters error when using global.json, so we specify the version and call UseDotNet@2 multiple times instead. + #==================================================================================================================== + - task: UseDotNet@2 + displayName: Use .NET Core SDK 6 + inputs: + packageType: 'sdk' + version: '6.0.427' + + - task: UseDotNet@2 + displayName: Use .NET Core SDK 8 + inputs: + packageType: 'sdk' + version: '8.0.100' + + - task: UseDotNet@2 + displayName: Use .NET Core SDK 9 + inputs: + packageType: 'sdk' + version: '9.0.200' + #==================================================================================================================== + + - task: PowerShell@2 + name: GetWASDKNugetDependencies + displayName: 'Get Nuget Dependencies for WindowsAppSDK' + inputs: + targetType: filePath + filePath: $(Build.SourcesDirectory)\$(SelfRepoName)\test\GetNugetDependencies.ps1 + arguments: > + -PackageName "Microsoft.WindowsAppSDK" + -OutputVariableName "wasdkDependencies" + + - task: PowerShell@2 + displayName: "Modify Sample Apps References" + inputs: + filePath: '$(Build.SourcesDirectory)\$(SelfRepoName)\test\ModifySampleAppsReferences.ps1' + arguments: > + -SampleRepoRoot "$(Build.SourcesDirectory)\$(SamplesRepoName)" + -FoundationVersion "$(FoundationVersion)" + -FoundationPackagesFolder "$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples\localpackages" + -WASDKNugetDependencies variables['wasdkDependencies'] + + # update the nuget.config file to point to the internal feed + - powershell: | + $nugetConfigPath = "$(Build.SourcesDirectory)/$(SamplesRepoName)/Samples/nuget.config" + + $sourceName = "WinAppSDK-SampleDeps" + $newSourceUrl = "https://microsoft.pkgs.visualstudio.com/ProjectReunion/_packaging/Project.Reunion.nuget.internal/nuget/v3/index.json" + + if (Test-Path $nugetConfigPath) { + [xml]$config = Get-Content $nugetConfigPath + + $packageSources = $config.configuration.packageSources.add + + $existingSource = $packageSources | Where-Object { $_.key -eq $sourceName } + + if ($existingSource) { + $existingSource.value = $newSourceUrl + Write-Host "Updated source '$sourceName' to '$newSourceUrl'" + + $config.Save($nugetConfigPath) + Write-Host "nuget.config updated successfully." + } + } else { + Write-Host "nuget.config file does not exist." + } + displayName: 'Modify NuGet.config feed to internal feed' + + - task: NuGetCommand@2 + inputs: + command: 'restore' + feedsToUse: 'config' + nugetConfigPath: '.\$(SamplesRepoName)\Samples\nuget.config' + restoreSolution: '.\$(SamplesRepoName)\Samples\$(featureForFilePath)\**\*.sln' + includeNuGetOrg: false + + - task: CopyFiles@2 + displayName: 'Copy tsaoptions.json from under ~\WindowsAppSDKAggregator\.config to ~\.config for the TSAOption task to find' + inputs: + SourceFolder: '$(Build.SourcesDirectory)\$(SelfRepoName)\.config' + Contents: | + tsaoptions.json + TargetFolder: '$(Build.SourcesDirectory)\.config' + + - task: CopyFiles@2 + displayName: 'Copy OneBranch.gdnsuppress from under ~\WindowsAppSDKAggregator\.gdn to ~\.gdn for the Guardian: Post Analysis task to find' + inputs: + SourceFolder: '$(Build.SourcesDirectory)\$(SelfRepoName)\.gdn' + Contents: | + OneBranch.gdnsuppress + TargetFolder: '$(Build.SourcesDirectory)\.gdn' + + - ${{ each config in parameters.BuildConfig }}: + - ${{ each platform in parameters.BuildPlatform }}: + - task: VSBuild@1 + displayName: 'Restore nuget packages for all solutions' + inputs: + solution: .\$(SamplesRepoName)\Samples\$(featureForFilePath)\**\*.sln + platform: '${{ platform }}' + configuration: '${{ config }}' + msbuildArgs: '/t:restore /p:PublishReadyToRun=true /p:VCToolsInstallDir="$(VCToolsInstallDir)\" /p:RestoreAdditionalProjectSources="$(Build.SourcesDirectory)\localpackages\NugetPackages" /binaryLogger:$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples\$(featureForFilePath)\$(feature).restore.${{ platform }}.${{ config }}.binlog' + ${{ if and(eq( parameters.EnablePREFast, 'true'), eq(parameters.RunSDLBinaryAnalysis, 'true')) }}: + # This property should match that of 'Build all Sample solutions' below. Otherwise, tool + # architecture detection in the PreFast task may (incorrectly) choose the tool architecture + # deduced from this step and inappropriately use it in the MSBuild commandline for triggering + # PREFast. We only need to specify this property for x64, which is currently the only + # architecture for which we run PreFast. Guardian PreFast currently does not support arm any + # way. Also no need for this property if this pipeline run does not enable PREFast. + msbuildArchitecture: x64 + + - task: VSBuild@1 + displayName: 'Build all Sample solutions' + inputs: + solution: .\$(SamplesRepoName)\Samples\$(featureForFilePath)\**\*.sln + platform: '${{ platform }}' + configuration: '${{ config }}' + msbuildArgs: '/p:VCToolsInstallDir="$(VCToolsInstallDir)\" /p:RestoreAdditionalProjectSources="$(Build.SourcesDirectory)\localpackages\NugetPackages" /binaryLogger:$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples\$(featureForFilePath)\$(feature).build.${{ platform }}.${{ config }}.binlog' + ${{ if and(eq( parameters.EnablePREFast, 'true'), eq(parameters.RunSDLBinaryAnalysis, 'true')) }}: + # This property helps prevent tool architecture detection in the PreFast task to pick x86 (default) + # as the "preferred tool architecture", which results in multiple sample apps failing to build due + # to "out of heap space" errors, which in turn is due to unfixed memory leaks in the 32-bit favor + # of a PREFast tool. We only need to specify this property for x64, which is currently the only + # architecture for which we run PreFast. Guardian PreFast currently does not support arm any + # way. Also no need for this property if this pipeline run does not enable PREFast. + msbuildArchitecture: x64 + + - task: CopyFiles@2 + displayName: 'Copy binlogs' + condition: succeededOrFailed() + inputs: + SourceFolder: '$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples\$(featureForFilePath)' + Contents: | + **/*.binlog + TargetFolder: '$(Build.ArtifactStagingDirectory)\$(featureForFilePath)' + + - ${{ if and(eq(parameters.runStaticAnalysis, 'true'), eq(platform, 'x64'), eq(config, 'Release')) }}: + # There are currently challenges with PREFast scanning the sample apps: + # 1) SDLNativeRules@3 does not seem to support the wild card in the input "solution: .\$(SamplesRepoName)\Samples\$(featureForFilePath)\**\*.sln" we pass to VSBuild@1 + # above, so we need to enumerate the .sln files at the target folder and explicitly pass the full path of each .sln file to a corresponding call to SDLNativeRules@3. + # 2) There are currently >71 sample apps. It takes a *long* time for PREFast to finish them. + # Given those challenges and that we currently prioritize Sample code over Product code, in this first round we just pick a few a Sample apps to PREFast scan them. + # In the future, considering the long time required to scan the Sample apps, it might make more sense to create a separate weekly pipeline to PREFast scan the Sample + # apps, for instance. + - task: SDLNativeRules@3 + displayName: PREfast SDL Native Rules for AppLifecycle\Activation\cpp + condition: and(succeeded(), eq(variables['featureForFilePath'], 'AppLifecycle\Activation\cpp\cpp-console-unpackaged')) + inputs: + setupCommandlines: '"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsMSBuildCmd.bat"' + msBuildArchitecture: amd64 + msBuildCommandline: 'msbuild.exe /nologo /nr:false /p:configuration=${{ config }} /p:platform=${{ platform }} $(Build.SourcesDirectory)\$(SamplesRepoName)\Samples\$(featureForFilePath)\CppWinRtConsoleActivation.sln' + # Generally speaking, we leave it to the external repos to scan the bits in their packages. + excludedPaths: | + $(Build.SourcesDirectory)\packages + # Explicitly specify the EO-compliant rule set, as the default Sdl.Recommended.Warning.ruleset is not EO-compliant. + rulesetName: Custom + customRuleset: $(Agent.ToolsDirectory)\NativeCompilerStaticAnalysisRuleset\mandatory_to_fix.ruleset + policyName: 'WindowsUndocked' + continueOnError: true + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + + - task: PowerShell@2 + displayName: Display storage and diagnostic info + condition: succeededOrFailed() + inputs: + targetType: 'inline' + script: | + Get-WmiObject win32_logicaldisk | Format-Table DeviceId, MediaType, @{n="Size";e={[math]::Round($_.Size/1GB,2)}},@{n="FreeSpace";e={[math]::Round($_.FreeSpace/1GB,2)}}, @{n="UsedSpace";e={[math]::Round((($_.Size-$_.FreeSpace)/1GB),2)}} + Write-Host '$(feature), $(featureForFilePath), $(SamplesRepoName).' + + # These steps, done on a per-featureArea basis, are only required if we are going to test Sample apps one way or + # another in the current pipeline run. OneBranch is limited to publishing 1 artifact per job, and different jobs + # can't publish to the same artifact. Therefore, each job in the strategy matrix that builds a specific + # featureArea is forced to publish its own featureArea-specific artifact, resulting in many artfacts. + - ${{ if ne(parameters.SamplesArtifactName, '') }}: + - script: | + md $(Build.ArtifactStagingDirectory)\$(featureForFilePath) + dir $(Build.ArtifactStagingDirectory) + dir $(Build.SourcesDirectory) + displayName: Create target folder for staging artifact + + # Reduce the number of files not required for running the tests later by deletion, before copying + # them to the staging location. The file copying operations takes 20+ min for big sample apps if we + # don't do this filtering first, also the total size of the artifacts gets too big, resulting in + # long download time that causes jobs to timeout, and/or out of disk space issues. + # It seems to be most helpful to filter out the .pch and .pdb files. There might be more + # file types that we can filtered out then those listed below. + - task: DeleteFiles@1 + displayName: 'Delete unneeded Files under: $(Build.ArtifactStagingDirectory)' + condition: succeededOrFailed() + inputs: + SourceFolder: '$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples\$(featureForFilePath)' + Contents: | + **/*.c + **/*.config + **/*.cpp + **/*.cs + **/*.csproj + **/*.editorconfig + **/*.foo + **/*.h + **/*.hpp + **/*.idb + **/*.idl + **/*.ilk + **/*.iobj + **/*.ipdb + **/*.lib + **/*.log + **/*.obj + **/*.pch + **/*.pdb + **/*.props + **/*.pubxml + **/*.sln + **/*.targets + **/*.tlog + **/*.txt + **/*.vcxproj* + **/*.vcxproj*/** + **/*.wapproj + **/*.winmd + **/*.xaml + **/*.xbf + **/*.xsd + **/*.semmle + **/arm/* + **/x86/* + **/*_Debug_Test/* + **/obj/* + **/packages/* + + - task: CopyFiles@2 + displayName: 'Copy Files to: $(Build.ArtifactStagingDirectory)' + condition: succeededOrFailed() + inputs: + SourceFolder: '$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples\$(featureForFilePath)' + TargetFolder: '$(Build.ArtifactStagingDirectory)\$(featureForFilePath)' + + - script: | + dir /s $(Build.ArtifactStagingDirectory) + condition: succeededOrFailed() + displayName: DIAG - Show staging trees to validate filtering + + - ${{ if not( parameters.IsOneBranch ) }}: + - task: PublishBuildArtifacts@1 + inputs: + PathtoPublish: '$(ob_outputDirectory)' + artifactName: '$(ob_artifactBaseName)$(ob_artifactSuffix)' + +# The idea of merging the artifacts for the featureAreas produced by one call to this yml file has +# been attempted and abandoned. Although this will free the subsequent Stages that run the SampleTests +# from needing to know the artifacts of the individual featureAreas and downloading them one by one, +# thus needing to download only 3 bigger artifacts with fixed names, those bigger artifact took up much +# space and they seem to hit download errors more frequently. The E2E run time of the pipeline is +# also longer. Therefore, this idea was not adopted. diff --git a/build/AzurePipelinesTemplates/WindowsAppSDK-Publish-Stage.yml b/build/AzurePipelinesTemplates/WindowsAppSDK-Publish-Stage.yml index 09d5695573..62edef8542 100644 --- a/build/AzurePipelinesTemplates/WindowsAppSDK-Publish-Stage.yml +++ b/build/AzurePipelinesTemplates/WindowsAppSDK-Publish-Stage.yml @@ -5,6 +5,18 @@ parameters: - name: "IgnoreFailures" type: boolean default: true +- name: "TestSampleApps" + displayName: "Test launch of Sample apps separately in the TestSampleApps Stage" + type: boolean + default: false +- name: "TestOnArm64" + displayName: "Enable running of sample tests on arm64 platform (Default: true)" + type: boolean + default: false +- name: "MaestroDependOnTestSamples" + displayName: "Whether Maestro publishing should depend on successful Sample tests" + type: boolean + default: false stages: - stage: Publish dependsOn: @@ -12,6 +24,10 @@ stages: - Test_x64 - Test_arm64 - StaticValidationTest + - ${{ if and(eq( parameters.TestSampleApps, true ), eq( parameters.MaestroDependOnTestSamples, true )) }}: + - TestSampleApps_x64 + - ${{ if eq( parameters.TestOnArm64, true ) }}: + - TestSampleApps_arm64 condition: not(failed()) jobs: # Publish diff --git a/build/AzurePipelinesTemplates/WindowsAppSDK-RunTests-Steps.yml b/build/AzurePipelinesTemplates/WindowsAppSDK-RunTests-Steps.yml index 7ccd53d079..90c33add0c 100644 --- a/build/AzurePipelinesTemplates/WindowsAppSDK-RunTests-Steps.yml +++ b/build/AzurePipelinesTemplates/WindowsAppSDK-RunTests-Steps.yml @@ -5,6 +5,9 @@ parameters: TaefSelect: '*' BinaryCompatSwitch: '' testLocale: '' + SamplesArtifactName: '' + IsOneBranch: false + callingStage: '' steps: - task: powershell@2 @@ -91,6 +94,67 @@ steps: arguments: -NoInteractive -Offline -Verbose -CheckTAEFService -ShowSystemInfo workingDirectory: '$(Build.SourcesDirectory)' + - task: PowerShell@2 + displayName: Display storage info and init variables + inputs: + targetType: 'inline' + script: | + Get-WmiObject win32_logicaldisk | Format-Table DeviceId, MediaType, @{n="Size";e={[math]::Round($_.Size/1GB,2)}},@{n="FreeSpace";e={[math]::Round($_.FreeSpace/1GB,2)}}, @{n="UsedSpace";e={[math]::Round((($_.Size-$_.FreeSpace)/1GB),2)}} + $IsPlatformX86 = (('${{ parameters.buildPlatform }}' -eq 'x86') -or ('${{ parameters.buildPlatform }}' -eq 'X86')) + Write-Output "##vso[task.setvariable variable=isPlatformX86;isOutput=true]$IsPlatformX86" + Write-Output "##vso[task.setvariable variable=isPlatformX86]$IsPlatformX86" + $IsReleaseConfig = (('${{ parameters.buildConfiguration }}' -eq 'release') -or ('${{ parameters.buildConfiguration }}' -eq 'Release')) + Write-Output "##vso[task.setvariable variable=isReleaseConfig;isOutput=true]$IsReleaseConfig" + Write-Output "##vso[task.setvariable variable=isReleaseConfig]$IsReleaseConfig" + Write-Host ${{ parameters.buildPlatform }}, ${{ parameters.buildConfiguration }}, $isPlatformX86, $isReleaseConfig, 'callingStage: ${{ parameters.callingStage }}' + - ${{ if ne(parameters.samplesArtifactName, '') }}: + - ${{ each featureAreas in parameters.sampleFeatureAreasList }}: + - ${{ if parameters.IsOneBranch }}: + - ${{ each artifactNameSuffix in featureAreas.value }}: + # If we are downloading for Release config, omit the *debug* specific stuff to save time and storage. + # If we are running for x86 platform, then don't download Sample app artifacts, as we currently don't build Samples for x86. + - task: DownloadPipelineArtifact@2 + condition: and(and(succeeded(), ne(variables['isPlatformX86'], 'true')), eq(variables['IsReleaseConfig'], 'true')) + displayName: 'Download Release ${{ parameters.SamplesArtifactName }}_${{ artifactNameSuffix }}' + inputs: + source: specific + runVersion: specific + project: $(System.TeamProjectId) + pipeline: $(_useBuildOutputFromPipeline) + pipelineId: $(_useBuildOutputFromBuildId) + artifactName: ${{ parameters.SamplesArtifactName }}_${{ artifactNameSuffix }} + targetPath: '$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples' + patterns: | + ** + !**/*binlog + !**/*debug*/**/* + - ${{ if not( parameters.IsOneBranch ) }}: + - task: DownloadPipelineArtifact@2 + # It is currently by design to _not_ test Sample apps when platform=x86 or config=Debug. + condition: and(and(succeeded(), ne(variables['isPlatformX86'], 'true')), eq(variables['IsReleaseConfig'], 'true')) + displayName: 'Download Release ${{ parameters.SamplesArtifactName }}_${{ featureAreas.key }}' + inputs: + source: specific + runVersion: specific + project: $(System.TeamProjectId) + pipeline: $(_useBuildOutputFromPipeline) + pipelineId: $(_useBuildOutputFromBuildId) + artifactName: ${{ parameters.SamplesArtifactName }}_${{ featureAreas.key }} + targetPath: '$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples' + patterns: | + ** + !**/*binlog + !**/*debug*/**/* + # WindowsAppSDKSampleAppTests requires SAMPLES_ROOT_PATH to be set. + - task: PowerShell@2 + displayName: Set SAMPLES_ROOT_PATH env variable + condition: and(succeeded(), and(ne(variables['isPlatformX86'], 'true'), eq(variables['IsReleaseConfig'], 'true'))) + inputs: + targetType: 'inline' + script: | + Write-Host "##vso[task.setvariable variable=SAMPLES_ROOT_PATH;isOutput=true]$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples" + Write-Host "##vso[task.setvariable variable=SAMPLES_ROOT_PATH;]$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples" + - task: VisualStudioTestPlatformInstaller@1 inputs: versionSelector: latestStable @@ -151,6 +215,15 @@ steps: sc.exe queryex te.service | Write-Host sc.exe qc te.service | Write-Host + # For test sample apps, we need to copy the 'net6' subfolder generated by WindowsAppSDK.Test.NetCore.csproj to WindowsAppSDK.Test.SampleTests folder + - ${{ if eq( parameters.callingStage, 'TestSampleApps' ) }}: + - task: CopyFiles@2 + displayName: 'Copy net6 output to SampleTests folder' + inputs: + SourceFolder: '$(Build.SourcesDirectory)\BuildOutput\$(buildConfiguration)\$(buildPlatform)\WindowsAppSDK.Test.NetCore\net6' + TargetFolder: '$(Build.SourcesDirectory)\BuildOutput\$(buildConfiguration)\$(buildPlatform)\WindowsAppSDK.Test.SampleTests\net6' + Contents: '**' + - task: PowerShell@2 displayName: 'Run TAEF Tests' inputs: @@ -162,6 +235,7 @@ steps: -Test -List -wprProfilePath "$(Build.SourcesDirectory)\WindowsAppSDKConfig\src\test\AppModelProviders.wprp" + -callingStage "${{ parameters.callingStage }}" - template: AzurePipelinesTemplates\WindowsAppSDK-ConvertWttLogToXUnit-Steps.yml@WindowsAppSDKConfig parameters: diff --git a/build/AzurePipelinesTemplates/WindowsAppSDK-RunTestsInPipeline-Job.yml b/build/AzurePipelinesTemplates/WindowsAppSDK-RunTestsInPipeline-Job.yml index 3641d0ccc7..49241481ed 100644 --- a/build/AzurePipelinesTemplates/WindowsAppSDK-RunTestsInPipeline-Job.yml +++ b/build/AzurePipelinesTemplates/WindowsAppSDK-RunTestsInPipeline-Job.yml @@ -6,17 +6,25 @@ parameters: - name: jobName type: string default: PipelineTests -- name: dependsOn +- name: samplesArtifactName + displayName: "Supply a valid base name for Sample Apps BuildOutput to trigger publishing of the artifact" + type: string + default: '' +- name: sampleFeatureAreasList type: object - default: - - '' - # testMatrix is supplied by WindowsAppSDKConfig/WindowsAppSDK-Foundation-TestConfig.yml + default: [] +- name: callingStage + type: string + default: '' - name: testMatrix type: object default: '' - name: buildPlatform type: string default: x64 +- name: "IsOneBranch" + type: boolean + default: true jobs: - job: ${{ parameters.jobName }} @@ -55,3 +63,7 @@ jobs: buildConfiguration: $(buildConfiguration) testLocale: $(testLocale) ImageName: $(imageName) + callingStage: ${{ parameters.callingStage }} + samplesArtifactName: ${{ parameters.samplesArtifactName }} + sampleFeatureAreasList: ${{ parameters.sampleFeatureAreasList }} + IsOneBranch: ${{ parameters.IsOneBranch }} \ No newline at end of file diff --git a/build/ProjectReunion-BuildFoundation.yml b/build/ProjectReunion-BuildFoundation.yml index 35be6a8007..38caae1a3b 100644 --- a/build/ProjectReunion-BuildFoundation.yml +++ b/build/ProjectReunion-BuildFoundation.yml @@ -5,10 +5,32 @@ name: $(version) trigger: none +parameters: +- name: "BuildSampleApps" + displayName: "Test build of sample apps" + type: boolean + default: false +- name: "TestSampleApps" + displayName: "Test launch of Sample apps separately in the TestSampleApps Stage" + type: boolean + default: false +- name: "TestOnArm64" + displayName: "Enable running of sample tests on arm64 platform (Default: true)" + type: boolean + default: false +- name: runStaticAnalysisInBuildAndTestSampleApps + displayName: "Run Static Analysis (e.g., PREFast, APIScan) In BuildAndTestSampleApps Stage" + type: boolean + default: false + variables: - template: WindowsAppSDK-Foundation-TestConfig.yml@WindowsAppSDKConfig - template: AzurePipelinesTemplates\WindowsAppSDK-Versions.yml@WindowsAppSDKVersionConfig - template: WindowsAppSDK-CommonVariables.yml +- name: maxParallelForBuildSamplesCompatJob_x64 + value: 10 +- name: maxParallelForBuildSamplesCompatJob_arm64 + value: 10 resources: repositories: @@ -20,8 +42,14 @@ resources: type: git name: ProjectReunion/WindowsAppSDKConfig ref: refs/heads/main + - repository: WindowsAppSDKSamples + type: github + endpoint: 'GitHub - benkuhn - 2-18' + name: microsoft/WindowsAppSDK-Samples + ref: $(SamplesBranch) stages: + - template: AzurePipelinesTemplates\WindowsAppSDK-BuildInstaller-Stage.yml@self - template: AzurePipelinesTemplates\WindowsAppSDK-BuildVSIX-Stage.yml@self @@ -49,4 +77,13 @@ stages: SignOutput: false PublishPackage: false IsOneBranch: false - BuildMockWindowsAppSDK: false + +- ${{ if eq( parameters.BuildSampleApps, true ) }}: + - template: AzurePipelinesTemplates\WindowsAppSDK-BuildAndTestSampleApps-Stage.yml@self + parameters: + IsOneBranch: false + TestSampleApps: ${{ parameters.TestSampleApps }} + TestOnArm64: ${{ parameters.TestOnArm64 }} + runStaticAnalysis: ${{ parameters.runStaticAnalysisInBuildAndTestSampleApps }} + maxParallel_x64: ${{ variables.maxParallelForBuildSamplesCompatJob_x64 }} + maxParallel_arm64: ${{ variables.maxParallelForBuildSamplesCompatJob_arm64 }} \ No newline at end of file diff --git a/build/WindowsAppSDK-CommonVariables.yml b/build/WindowsAppSDK-CommonVariables.yml index 503740412d..4fbd8e83e3 100644 --- a/build/WindowsAppSDK-CommonVariables.yml +++ b/build/WindowsAppSDK-CommonVariables.yml @@ -8,6 +8,7 @@ variables: versionMinDate: $[format('{0:yyMMdd}', pipeline.startTime)] versionCounter: $[counter(variables['versionDate'], 0)] version: $[format('{0}.{1}.{2}-{3}.{4}', variables['MajorVersion'], variables['MinorVersion'], variables['PatchVersion'], variables['versionDate'], variables['versionCounter'])] + SamplesBranch: "release/experimental" # Used in the Samples Build Compat Test #OneBranch Variables CDP_DEFINITION_BUILD_COUNT: $[counter('', 0)] # needed for onebranch.pipeline.version task https://aka.ms/obpipelines/versioning diff --git a/build/WindowsAppSDK-Foundation-Nightly.yml b/build/WindowsAppSDK-Foundation-Nightly.yml index 9a6e987da7..e5c719c3a9 100644 --- a/build/WindowsAppSDK-Foundation-Nightly.yml +++ b/build/WindowsAppSDK-Foundation-Nightly.yml @@ -33,11 +33,31 @@ parameters: displayName: "Run Static Analysis (e.g., PREFast, APIScan)" type: boolean default: true +- name: "BuildSampleApps" + displayName: "Run Build Sample Apps Stage" + type: boolean + default: true +- name: "TestSampleApps" + displayName: "Test launch of Sample apps separately in the TestSampleApps Stage" + type: boolean + default: true +- name: "TestOnArm64" + displayName: "Enable running of sample tests on arm64 platform (Default: true)" + type: boolean + default: true +- name: "MaestroDependOnTestSamples" + displayName: "Whether Maestro publishing should depend on successful Sample tests" + type: boolean + default: false variables: - template: WindowsAppSDK-Foundation-TestConfig.yml@WindowsAppSDKConfig - template: AzurePipelinesTemplates\WindowsAppSDK-Versions.yml@WindowsAppSDKVersionConfig - template: WindowsAppSDK-CommonVariables.yml +- name: maxParallelForBuildSamplesCompatJob_x64 + value: 20 +- name: maxParallelForBuildSamplesCompatJob_arm64 + value: 20 resources: repositories: @@ -53,6 +73,11 @@ resources: type: git name: ProjectReunion/WindowsAppSDKConfig ref: refs/heads/main + - repository: WindowsAppSDKSamples + type: github + endpoint: 'GitHub - benkuhn - 2-18' + name: microsoft/WindowsAppSDK-Samples + ref: $(SamplesBranch) extends: template: v2/Microsoft.NonOfficial.yml@templates # https://aka.ms/obpipelines/templates @@ -143,3 +168,15 @@ extends: parameters: PublishToMaestro: ${{ parameters.PublishToMaestro }} IgnoreFailures: ${{ parameters.IgnoreFailures }} + TestSampleApps: ${{ parameters.TestSampleApps }} + TestOnArm64: ${{ parameters.TestOnArm64 }} + MaestroDependOnTestSamples: ${{ parameters.MaestroDependOnTestSamples }} + + - ${{ if eq( parameters.BuildSampleApps, true ) }}: + - template: AzurePipelinesTemplates\WindowsAppSDK-BuildAndTestSampleApps-Stage.yml@self + parameters: + TestSampleApps: ${{ parameters.TestSampleApps }} + TestOnArm64: ${{ parameters.TestOnArm64 }} + runStaticAnalysis: ${{ parameters.runStaticAnalysis }} + maxParallel_x64: ${{ variables.maxParallelForBuildSamplesCompatJob_x64 }} + maxParallel_arm64: ${{ variables.maxParallelForBuildSamplesCompatJob_arm64 }} \ No newline at end of file diff --git a/build/WindowsAppSDK-Foundation-Official.yml b/build/WindowsAppSDK-Foundation-Official.yml index 0ef2c4fa58..cc7094b456 100644 --- a/build/WindowsAppSDK-Foundation-Official.yml +++ b/build/WindowsAppSDK-Foundation-Official.yml @@ -33,11 +33,32 @@ parameters: displayName: "Run Static Analysis (e.g., PREFast, APIScan)" type: boolean default: true +- name: "BuildSampleApps" + displayName: "Run Build Sample Apps Stage" + type: boolean + default: true +- name: "TestSampleApps" + displayName: "Test launch of Sample apps separately in the TestSampleApps Stage" + type: boolean + default: true +- name: "TestOnArm64" + displayName: "Enable running of sample tests on arm64 platform (Default: true)" + type: boolean + default: true +- name: "MaestroDependOnTestSamples" + displayName: "Whether Maestro publishing should depend on successful Sample tests" + type: boolean + default: false variables: - template: WindowsAppSDK-Foundation-TestConfig.yml@WindowsAppSDKConfig - template: AzurePipelinesTemplates\WindowsAppSDK-Versions.yml@WindowsAppSDKVersionConfig - template: WindowsAppSDK-CommonVariables.yml +- name: maxParallelForBuildSamplesCompatJob_x64 + value: 20 +- name: maxParallelForBuildSamplesCompatJob_arm64 + value: 20 + resources: repositories: - repository: templates @@ -129,3 +150,16 @@ extends: parameters: PublishToMaestro: ${{ parameters.PublishToMaestro }} IgnoreFailures: ${{ parameters.IgnoreFailures }} + TestSampleApps: ${{ parameters.TestSampleApps }} + TestOnArm64: ${{ parameters.TestOnArm64 }} + MaestroDependOnTestSamples: ${{ parameters.MaestroDependOnTestSamples }} + + + - ${{ if eq( parameters.BuildSampleApps, true ) }}: + - template: AzurePipelinesTemplates\WindowsAppSDK-BuildAndTestSampleApps-Stage.yml@self + parameters: + TestSampleApps: ${{ parameters.TestSampleApps }} + TestOnArm64: ${{ parameters.TestOnArm64 }} + runStaticAnalysis: ${{ parameters.runStaticAnalysis }} + maxParallel_x64: ${{ variables.maxParallelForBuildSamplesCompatJob_x64 }} + maxParallel_arm64: ${{ variables.maxParallelForBuildSamplesCompatJob_arm64 }} \ No newline at end of file diff --git a/build/WindowsAppSDK-Foundation-PR.yml b/build/WindowsAppSDK-Foundation-PR.yml index 37c89952d7..2476ed1a7c 100644 --- a/build/WindowsAppSDK-Foundation-PR.yml +++ b/build/WindowsAppSDK-Foundation-PR.yml @@ -20,6 +20,18 @@ name: $(version) trigger: none parameters: # parameters are shown up in ADO UI in a build queue time +- name: "BuildSampleApps" + displayName: "Run Build Sample Apps Stage" + type: boolean + default: false +- name: "TestSampleApps" + displayName: "Test launch of Sample apps separately in the TestSampleApps Stage" + type: boolean + default: false +- name: "TestOnArm64" + displayName: "Enable running of sample tests on arm64 platform (Default: true)" + type: boolean + default: false - name: runStaticAnalysis displayName: "Run Static Analysis (e.g., PREFast, APIScan)" type: boolean @@ -29,6 +41,10 @@ variables: - template: WindowsAppSDK-Foundation-TestConfig.yml@WindowsAppSDKConfig - template: AzurePipelinesTemplates\WindowsAppSDK-Versions.yml@WindowsAppSDKVersionConfig - template: WindowsAppSDK-CommonVariables.yml +- name: maxParallelForBuildSamplesCompatJob_x64 + value: 20 +- name: maxParallelForBuildSamplesCompatJob_arm64 + value: 20 resources: repositories: @@ -44,6 +60,11 @@ resources: type: git name: ProjectReunion/WindowsAppSDKConfig ref: refs/heads/main + - repository: WindowsAppSDKSamples + type: github + endpoint: 'GitHub - benkuhn - 2-18' + name: microsoft/WindowsAppSDK-Samples + ref: $(SamplesBranch) extends: template: v2/Microsoft.NonOfficial.yml@templates # https://aka.ms/obpipelines/templates @@ -115,3 +136,12 @@ extends: - template: AzurePipelinesTemplates\WindowsAppSDK-Test-Stage.yml@self parameters: testMatrix: ${{ variables.PipelineTests }} + + - ${{ if eq( parameters.BuildSampleApps, true ) }}: + - template: AzurePipelinesTemplates\WindowsAppSDK-BuildAndTestSampleApps-Stage.yml@self + parameters: + TestSampleApps: ${{ parameters.TestSampleApps }} + TestOnArm64: ${{ parameters.TestOnArm64 }} + runStaticAnalysis: ${{ parameters.runStaticAnalysis }} + maxParallel_x64: ${{ variables.maxParallelForBuildSamplesCompatJob_x64 }} + maxParallel_arm64: ${{ variables.maxParallelForBuildSamplesCompatJob_arm64 }} diff --git a/test/GetNugetDependencies.ps1 b/test/GetNugetDependencies.ps1 new file mode 100644 index 0000000000..0d3157f9ea --- /dev/null +++ b/test/GetNugetDependencies.ps1 @@ -0,0 +1,154 @@ +param( + [Parameter(Mandatory = $true)] + [string]$PackageName, + + [string]$Source = "https://microsoft.pkgs.visualstudio.com/ProjectReunion/_packaging/Project.Reunion.nuget.internal/nuget/v3/index.json", + [string]$TempFolder = (Join-Path $env:TEMP "NuGetDeps"), + [string]$OutputVariableName = "dependencyList" +) + +Set-StrictMode -Version 3.0 +$ErrorActionPreference = 'Stop' + +# Clean start +if (Test-Path $TempFolder) { + Remove-Item $TempFolder -Recurse -Force -ErrorAction SilentlyContinue +} +New-Item -Path $TempFolder -ItemType Directory -Force | Out-Null + +$allDependencies = @{} # Use hashtable to track unique dependencies +$processed = @{} # Track processed packages + +Write-Host "=== NuGet Dependency Discovery ===" -ForegroundColor Green +Write-Host "Source: $Source" -ForegroundColor Yellow +Write-Host "Temp Folder: $TempFolder" -ForegroundColor Yellow + +function Extract-DependenciesFromPackage { + param( + [string]$PackageName + ) + + Write-Host "`nProcessing: $PackageName" -ForegroundColor Cyan + + # Build download arguments with unique output directory to avoid conflicts + $packageTempDir = Join-Path $TempFolder "pkg_$PackageName" + $downloadArgs = @("install", $PackageName, "-Source", $Source, "-OutputDirectory", $packageTempDir, "-ExcludeVersion", "-NonInteractive", "-DependencyVersion", "Ignore", "-PreRelease") + + try { + $result = & nuget.exe @downloadArgs 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Warning " Failed to download $PackageName" + Write-Host " Error details: $result" -ForegroundColor Red + return @() + } + + # Find and extract nupkg + $packagePath = Join-Path $packageTempDir $PackageName + $nupkgFile = Get-ChildItem -Path $packagePath -Filter "*.nupkg" | Select-Object -First 1 + + if (-not $nupkgFile) { + Write-Warning " No nupkg found for $PackageName" + return @() + } + + # Extract nupkg to find nuspec + $extractPath = Join-Path $TempFolder "extract_$PackageName" + if (Test-Path $extractPath) { + Remove-Item $extractPath -Recurse -Force -ErrorAction SilentlyContinue + } + New-Item -Path $extractPath -ItemType Directory -Force | Out-Null + + Add-Type -AssemblyName System.IO.Compression.FileSystem + [System.IO.Compression.ZipFile]::ExtractToDirectory($nupkgFile.FullName, $extractPath) + + # Find and parse nuspec + $nuspecFile = Get-ChildItem -Path $extractPath -Filter "*.nuspec" | Select-Object -First 1 + if (-not $nuspecFile) { + Write-Host " No dependencies found (no nuspec)" -ForegroundColor Gray + return @() + } + + [xml]$nuspec = Get-Content $nuspecFile.FullName -Encoding UTF8 + + # Extract dependencies + $dependencies = @() + $ns = $nuspec.DocumentElement.NamespaceURI + + if ($ns) { + $nsManager = New-Object System.Xml.XmlNamespaceManager($nuspec.NameTable) + $nsManager.AddNamespace("n", $ns) + $depNodes = $nuspec.SelectNodes("//n:dependency", $nsManager) + } else { + $depNodes = $nuspec.SelectNodes("//dependency") + } + + foreach ($dep in $depNodes) { + $depId = $dep.GetAttribute("id") + if ($depId -and $depId -ne $PackageName) { + $dependencies += $depId + } + } + + if ($dependencies.Count -gt 0) { + Write-Host " Dependencies: $($dependencies -join ', ')" -ForegroundColor Gray + } else { + Write-Host " No dependencies found" -ForegroundColor Gray + } + + return $dependencies + } + catch { + throw "Error processing $PackageName`: $($_.Exception.Message)" + } +} + +function Process-PackageRecursively { + param( + [string]$PackageName, + [int]$Depth = 0 + ) + + # Avoid infinite recursion + if ($Depth -gt 10) { + Write-Warning "Maximum depth reached for $PackageName" + return + } + + # Skip if already processed + if ($processed.ContainsKey($PackageName)) { + return + } + + $processed[$PackageName] = $true + + # Get dependencies for this package + $dependencies = Extract-DependenciesFromPackage -PackageName $PackageName + + # Add dependencies to global list and process recursively + foreach ($dep in $dependencies) { + if (-not $allDependencies.ContainsKey($dep)) { + $allDependencies[$dep] = $true + } + + Process-PackageRecursively -PackageName $dep -Depth ($Depth + 1) + } +} + +# Start processing +Process-PackageRecursively -PackageName $PackageName + +# Display results +Write-Host "`n=== RESULTS ===" -ForegroundColor Green +Write-Host "Total dependencies found: $($allDependencies.Count)" -ForegroundColor Yellow + +if ($allDependencies.Count -gt 0) { + Write-Host "`nAll Dependencies:" -ForegroundColor Cyan + $allDependencies.Keys | Sort-Object | ForEach-Object { + Write-Host " $_" -ForegroundColor White + } + + Write-Host "`nDependency Names (semicolon-separated string):" -ForegroundColor Cyan + $dependenciesString = $allDependencies.Keys -join ';' + Write-Host "##vso[task.setvariable variable=$OutputVariableName;isOutput=true]$dependenciesString" + Write-Host "##vso[task.setvariable variable=$OutputVariableName;]$dependenciesString" +} \ No newline at end of file diff --git a/test/ModifySampleAppsReferences.ps1 b/test/ModifySampleAppsReferences.ps1 new file mode 100644 index 0000000000..cd54c22b0e --- /dev/null +++ b/test/ModifySampleAppsReferences.ps1 @@ -0,0 +1,91 @@ +# This script is a Foundation version of https://github.com/microsoft/WindowsAppSDK-Samples/blob/release/experimental/UpdateVersions.ps1 +Param( + [string]$SampleRepoRoot = "", + [string]$FoundationVersion = "", + [string]$FoundationPackagesFolder = "", + [string]$WASDKNugetDependencies = "" +) + +Set-StrictMode -Version 3.0 +$ErrorActionPreference = 'Stop' + +$packagesToRemoveList = $WASDKNugetDependencies -split ';' + +$packagesToUpdateTable = @{"Microsoft.WindowsAppSDK.Foundation" = $FoundationVersion} + +# Go through the nuget packages folder to get the versions of all dependency packages. +if (!($FoundationPackagesFolder -eq "")) +{ + Get-ChildItem $FoundationPackagesFolder | + Where-Object { $_.Name -like "Microsoft.WindowsAppSDK.*" -or + $_.Name -like "Microsoft.Windows.SDK.BuildTools.*"} | + Where-Object { $_.Name -notlike "*.nupkg" } | + ForEach-Object { + if ($_.Name -match "^(Microsoft\.WindowsAppSDK\.[a-zA-Z]+)\.([0-9].*)$" -or + $_.Name -match "^(Microsoft\.Windows\.SDK\.BuildTools\.MSIX)\.([0-9].*)$" -or + $_.Name -match "^(Microsoft\.Windows\.SDK\.BuildTools)\.([0-9].*)$") + { + $packagesToUpdateTable[$Matches[1]] = $Matches[2] + Write-Host "Found $($Matches[1]) - $($Matches[2])" + + $packagesToRemoveList = $packagesToRemoveList | Where-Object { $_ -ne $Matches[1] } + } + } +} +Write-Host "NuGet packages to version table: $($packagesToUpdateTable | Out-String)" + +Get-ChildItem -Recurse packages.config -Path $SampleRepoRoot | foreach-object { + $content = Get-Content $_.FullName -Raw + + foreach ($nugetPackageToVersion in $packagesToUpdateTable.GetEnumerator()) + { + $newVersionString = 'package id="' + $nugetPackageToVersion.Key + '" version="' + $nugetPackageToVersion.Value + '"' + $oldVersionString = 'package id="' + $nugetPackageToVersion.Key + '" version="[-.0-9a-zA-Z]*"' + $content = $content -replace $oldVersionString, $newVersionString + } + foreach ($package in $packagesToRemoveList) + { + $packageReferenceString = '(?m)^\s*]*?/>\s*$\r?\n?' + $content = $content -replace $packageReferenceString, '' + } + + Set-Content -Path $_.FullName -Value $content + Write-Host "Modified " $_.FullName +} + +Get-ChildItem -Recurse *.vcxproj -Path $SampleRepoRoot | foreach-object { + $content = Get-Content $_.FullName -Raw + + foreach ($nugetPackageToVersion in $packagesToUpdateTable.GetEnumerator()) + { + $newVersionString = "\$($nugetPackageToVersion.Key)." + $nugetPackageToVersion.Value + '\' + $oldVersionString = "\\$($nugetPackageToVersion.Key).[0-9][-.0-9a-zA-Z]*\\" + $content = $content -replace $oldVersionString, $newVersionString + } + foreach ($package in $packagesToRemoveList) + { + $packageReferenceString = "(?m)^.*\\$package\.[0-9][-.0-9a-zA-Z]*\\.*\r?\n?" + $content = $content -replace $packageReferenceString, '' + } + + Set-Content -Path $_.FullName -Value $content + Write-Host "Modified " $_.FullName +} + +Get-ChildItem -Recurse *.wapproj -Path $SampleRepoRoot | foreach-object { + $newVersionString = 'PackageReference Include="Microsoft.WindowsAppSDK.Foundation" Version="'+ $FoundationVersion + '"' + $oldVersionString = 'PackageReference Include="Microsoft.WindowsAppSDK" Version="[-.0-9a-zA-Z]*"' + $content = Get-Content $_.FullName -Raw + $content = $content -replace $oldVersionString, $newVersionString + Set-Content -Path $_.FullName -Value $content + Write-Host "Modified " $_.FullName +} + +Get-ChildItem -Recurse *.csproj -Path $SampleRepoRoot | foreach-object { + $newVersionString = 'PackageReference Include="Microsoft.WindowsAppSDK.Foundation" Version="'+ $FoundationVersion + '"' + $oldVersionString = 'PackageReference Include="Microsoft.WindowsAppSDK" Version="[-.0-9a-zA-Z]*"' + $content = Get-Content $_.FullName -Raw + $content = $content -replace $oldVersionString, $newVersionString + Set-Content -Path $_.FullName -Value $content + Write-Host "Modified " $_.FullName +} \ No newline at end of file diff --git a/test/WindowsAppSDK.Test.NetCore/Program.cs b/test/WindowsAppSDK.Test.NetCore/Program.cs new file mode 100644 index 0000000000..f8c0904c89 --- /dev/null +++ b/test/WindowsAppSDK.Test.NetCore/Program.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +using System; + +namespace WindowsAppSDK.Test.NetCore +{ + // This app justs exists as a convenient way to output the .net 5 runtime binaries to BuildOutput + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/test/WindowsAppSDK.Test.NetCore/WindowsAppSDK.Test.NetCore.csproj b/test/WindowsAppSDK.Test.NetCore/WindowsAppSDK.Test.NetCore.csproj new file mode 100644 index 0000000000..0af8d387fc --- /dev/null +++ b/test/WindowsAppSDK.Test.NetCore/WindowsAppSDK.Test.NetCore.csproj @@ -0,0 +1,14 @@ + + + + Exe + net6.0 + win10-$(Platform) + win10-x86 + false + $(BaseOutputPath)\$(MSBuildProjectName)\net6\ + False + x86;x64;arm64 + + + diff --git a/test/WindowsAppSDK.Test.SampleTests/SampleTest.testdef b/test/WindowsAppSDK.Test.SampleTests/SampleTest.testdef new file mode 100644 index 0000000000..d611769e96 --- /dev/null +++ b/test/WindowsAppSDK.Test.SampleTests/SampleTest.testdef @@ -0,0 +1,11 @@ +{ + "Tests": [ + { + "Description": "Tests to launch sample apps", + "Filename": "WindowsAppSDK.Test.SampleTests.dll", + "Parameters": "", + "Architectures": ["x64", "arm64"], + "Status": "Enabled" + } + ] +} diff --git a/test/WindowsAppSDK.Test.SampleTests/TestAssembly.cs b/test/WindowsAppSDK.Test.SampleTests/TestAssembly.cs new file mode 100644 index 0000000000..f46275bd32 --- /dev/null +++ b/test/WindowsAppSDK.Test.SampleTests/TestAssembly.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using WEX.Logging.Interop; +using WEX.TestExecution; +using WEX.TestExecution.Markup; + +namespace WindowsAppSDK.Test.SampleTests +{ + [TestClass] + class TestAssembly + { + [AssemblyInitialize] + [TestProperty("CoreClrProfile", "net6")] + [TestProperty("IsolationLevel", "Class")] + public static void AssemblyInitialize(TestContext testContext) + { + Log.Comment("AssemblyInitialize"); + Log.Comment($" CurrentDirectory: {System.IO.Directory.GetCurrentDirectory()}"); + Log.Comment($" Executable: {System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName}"); + Log.Comment($" EntryAssembly: {EntryAssemblyLocation}"); + Log.Comment($"ExecutingAssembly: {System.Reflection.Assembly.GetExecutingAssembly().Location}"); + Log.Comment($" CallingAssembly: {System.Reflection.Assembly.GetCallingAssembly().Location}"); + } + + private static string EntryAssemblyLocation + { + get + { + var assembly = System.Reflection.Assembly.GetEntryAssembly(); + return assembly == null ? "" : assembly.Location; + } + } + } +} diff --git a/test/WindowsAppSDK.Test.SampleTests/WindowsAppSDK.Test.SampleTests.csproj b/test/WindowsAppSDK.Test.SampleTests/WindowsAppSDK.Test.SampleTests.csproj new file mode 100644 index 0000000000..d0f7ee7872 --- /dev/null +++ b/test/WindowsAppSDK.Test.SampleTests/WindowsAppSDK.Test.SampleTests.csproj @@ -0,0 +1,25 @@ + + + + net6.0-windows10.0.18362 + win10-x86;win10-x64;win10-arm64 + $(BaseOutputPath)\$(MSBuildProjectName)\ + False + x86;x64;arm64 + + false + true + + + + + + + + + + + + + + diff --git a/test/WindowsAppSDK.Test.SampleTests/WindowsAppSDKSampleAppTests.cs b/test/WindowsAppSDK.Test.SampleTests/WindowsAppSDKSampleAppTests.cs new file mode 100644 index 0000000000..5722f02d91 --- /dev/null +++ b/test/WindowsAppSDK.Test.SampleTests/WindowsAppSDKSampleAppTests.cs @@ -0,0 +1,1397 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +using System.Linq; +using Microsoft.Windows.Apps.Test.Foundation; +using Microsoft.Windows.Apps.Test.Foundation.Waiters; + +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Collections.Generic; + +using WEX.Logging.Interop; +using WEX.TestExecution; +using WEX.TestExecution.Markup; + +/* + General Notes: + - If you are onboarding a new Sample app, please look for instructions in the "Adding a new Sample app" section below. + - This TAEF test attempts to verify that Sample apps on the Samples repo can be launched successfully given a newly built WASDK Foundation product + (i.e., regression detection). + - Currently, ~70 buildable and launchable Sample apps have been identified on the Samples repo. + - Because there are different kinds of Sample apps, the extend to which the app launch can be verified varies from one app to another. The + basic permutations are: [ Packaged | Unpackaged ] x [ Windowed | Console | wpf ]. + - "Packaged + Windowed" is generally the most rigiously verified, while "Unpackaged + Console" is minimally verified. + - This TAEF test requires the environment variable SAMPLES_ROOT_PATH to be set to point to the root folder of the sample apps, under which folders + such as AppLifeCycle and ResourcesManagement can be found, e.g. "set SAMPLES_ROOT_PATH=C:\\myRepo\\WindowsAppSDK-Samples\\Samples". This approach + seems to be more effective than spending time searching all logical drives the the machine for the samples root folder on each run of the test. + - The environment variable can be set in the pipeline or on a developer's local machine. + - The caller can optionally set environment variables Build_Platform and Build_Configuration to modify the paths to the target sample binaries. + Build_Platform can be one of { x86 | x64 | arm64 }. By default, Build_Platform=x64. + Build_Configuration can be one of { Debug | Release }. By default, Build_Configuration=Release. + - Currently, not all Sample apps are being built for all permutations of { x86 | x64 | arm64 } X { Debug | Release }, with X64+Release being the + greatest common denominator. When a test case exercises the Build_Platform + Build_Configuration which the corresponding Sample app isn't built + for, that test is being skipped. + - Not all Sample apps are working on all versions of Windows supported by WASDK. When a Sample app does not support a particular version of + Windows, the corresponding test case is being skipped on that version of Windows. + - The following information about each Sample app is currently hardcoded: relative path to executable/manifest, process name, window title, app + ID, package full/family names. The benefit of this hardcoding approach is simplicity. + - Test cases are intentionally "flat" structured, such that when any sample app test is failing, it is straight forward to diagnose and fix. + In case some of these per-app pieces are churning frequently enough in the future to create a maintenance burden, we can consider + investing into detection logic for populating most if not all of these pieces. + - It is currently deemed non-critical to maximize the degree of verification for every app on the Samples repo, given the purpose of increasing + test coverage for nightly builds, as there likely is already redundance in the test cases. The ROI on including more sample apps in the future + is anticipated to diminish, unless a new feature or app activation mechansim needs to be verified. +*/ + +namespace WindowsAppSDK.Test.SampleTests +{ + [TestClass] + public class WindowsAppSDKSampleAppTests + { + // 20-second timeout period for general shell commands. + public const int TIMEOUT = 20000; + + public static TestContext TestContext { get; private set; } + public static string SamplesRootPath = null; + public static string BuildArch = null; + public static string BuildConfig = null; + public Process Process { get; private set; } + + private class ProcessComparer : IEqualityComparer + { + public bool Equals(Process x, Process y) + { + // NOTE: The following expression has been updated locally and is now out of sync with its source: + // https://dev.azure.com/microsoft/WinUI/_search?action=history&type=code&text=def%3AProcessComparer&filters=ProjectFilters%7BWinUI%7DRepositoryFilters%7Bmicrosoft-ui-xaml-lift%7D&includeFacets=false&result=DefaultCollection/WinUI/microsoft-ui-xaml-lift/GBmain//controls/test/testinfra/MUXTestInfra/Infra/Application.cs. + return (x == null && y == null) || ((x != null && y != null) && (x.Id == y.Id)); + } + + public int GetHashCode(Process obj) + { + return obj.GetHashCode(); + } + } + + private int GetProcessIdFromAppWindow(string appWindowTitle) + { + var topWindowCondition = UICondition.CreateFromName(appWindowTitle); + if (UIObject.Root.Children.TryFind(topWindowCondition, out UIObject topWindowObj)) + { + Log.Comment("Found topWindowObj for #{0}...", topWindowCondition); + return topWindowObj.ProcessId; + } + + Log.Comment("topWindowObj _NOT FOUND_ for #{0}...", topWindowCondition); + return -1; + } + + private bool KillProcessAndWaitForExit(Process process) + { + Log.Comment($"Killing process {process.Id}"); + if (process.HasExited) + { + return true; + } + + process.Kill(); + return process.WaitForExit(10000 /*milliseconds*/); + } + + // If an explicit appWindowsProcessId is prescribed then add the corresponding process name to a list of processes to try to kill. + // Regardless of that, as long as a valid appProcessName is prescribed then we should be able to just add that name to the kill list. + // Hopefully that would leave us with a list of process names, potentially with duplicates, to kill. + // We then try to kill each unique process name, with verification. + // If the target process is not found, it's unexpected, bubble up an issue. + private void EnsureApplicationProcessHasExited(int appWindowsProcessId, string appProcessName) + { + const int MaxLaunchRetries = 4; + for (int retries = 1; retries <= MaxLaunchRetries; ++retries) + { + var appProcesses = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(appProcessName)).ToList(); + + if (appWindowsProcessId != -1) + { + try + { + appProcesses.Add(Process.GetProcessById(appWindowsProcessId)); + } + catch (Exception) + { + // Ignore. GetProcessById throws if the process has already exited. + } + } + + if (Process != null) + { + appProcesses.Add(Process); + } + + if (appProcesses.Count > 0) + { + foreach (var proc in appProcesses.Distinct(new ProcessComparer())) + { + if (!proc.HasExited && !KillProcessAndWaitForExit(proc)) + { + throw new Exception($"Unable to kill process: {proc}"); + } + } + return; + } + + // appWindowsProcessId==-1 means the caller did not wait for the app's window at launch; so in case we've got + // here too quickly, retry a couple of times to find the app's processes. + if ((appWindowsProcessId != -1) || (retries >= MaxLaunchRetries)) + { + // http://task.ms/58006958. Preliminary investigation revealed that, when we can't detect the expected + // sample app process after the launch command has returned success, even with retries, is probably not + // because we don't wait long enough for the sampple app's process to be found, but is more likely + // because the sample app process has naturally terminated so quickly that the test code doesn't catch + // it soon enough. IOW, increasing the wait time for the app launch is unlikely to help, and the + // intermittent sample app launch failures being reported recently are most likely noise (false positives). + // To unblock pipeline runs, we are making it a non-fatal situation when we hit this code path. Log a + // warning message to maintain visibility to the "process not found" errors. + // When we can properly detect the "sample app terminates too quickly" situation in the future, we might + // want to bring back the following commented out statement to make this code path a fatal problem again. + // Verify.Fail($"Couldn't find the expected process {appProcessName}, not good!"); + Log.Warning($"[WARNING]: Couldn't find the expected process {appProcessName}, not good!"); + return; + } + + Log.Comment("Found no process related to {0} to terminate, trying again {1} after 2 sec.", appProcessName, retries); + Thread.Sleep(TimeSpan.FromSeconds(2)); + } + return; + } + + // Try to launch an unpackaged app by its full exe path, with retries. + // If a valid appWindowTitle is prescribed, then the launch isn't considered successful until the window with the expected + // title is found. Also verify that the expected process name is present, and then use that name to terminate the appp process. + // Process termination is also verified. + private void LaunchAndCloseUnpackagedApp(string unpackagedExeFullPath, string appWindowTitle, string appProcessName) + { + const int MaxLaunchRetries = 3; + + Log.Comment($"Using executable: {unpackagedExeFullPath}"); + if (!File.Exists(unpackagedExeFullPath)) + { + Verify.Fail($"Executable not found at {unpackagedExeFullPath}!"); + return; + } + + for (int retries = 1; retries <= MaxLaunchRetries; ++retries) + { + try + { + Log.Comment("Attempting launch, try #{0}...", retries); + + if (String.IsNullOrEmpty(appWindowTitle)) + { + //Instead of launching the process directly it is invoked through explorer.exe, resulting in the process starting unelevated. + ProcessStartInfo unelevatedProcessStartInfo = new ProcessStartInfo(); + unelevatedProcessStartInfo.FileName = "C:\\Windows\\explorer.exe"; + unelevatedProcessStartInfo.Arguments = unpackagedExeFullPath; + + Process.Start(unelevatedProcessStartInfo); + + // Unlike the case below, there is no window to wait for here. So, as long as we didn't hit an activation error, just + // wait a couple of seconds to give the app a chance to run, then proceed to kill it, at that point the presence of the + // expected process is verified. + Thread.Sleep(TimeSpan.FromSeconds(2)); + } + else + { + // In this case we can verify the window with the expected title is present. We currently don't perform further checks such as the window type. + UICondition WindowCondition = UICondition.CreateFromName(appWindowTitle); + + using (AppLaunchWaiter launchWaiter = new AppLaunchWaiter(WindowCondition)) + { + // Instead of launching the process directly it is invoked through explorer.exe, resulting in the process starting unelevated. + ProcessStartInfo unelevatedProcessStartInfo = new ProcessStartInfo(); + unelevatedProcessStartInfo.FileName = "C:\\Windows\\explorer.exe"; + unelevatedProcessStartInfo.Arguments = unpackagedExeFullPath; + + Process.Start(unelevatedProcessStartInfo); + + launchWaiter.Wait(); + } + } + + Log.Comment("Launch successful!"); + break; + } + catch (Exception ex) + { + Log.Comment("Failed to launch app. Exception: " + ex.ToString()); + + if (retries < MaxLaunchRetries) + { + Log.Comment("UAPApp.Launch might not have waited long enough, trying again {0}", retries); + Thread.Sleep(TimeSpan.FromSeconds(2)); + } + else + { + Log.Error("Could not launch app {0} !", unpackagedExeFullPath); + throw; + } + } + } + + // If appWindowTitle is valid, use it to derive the target process ID to terminate. Otherwise, we fall back to + // using the appProcessName to derive the process ID. + int appWindowsProcessId = -1; + if (!String.IsNullOrEmpty(appWindowTitle)) + { + appWindowsProcessId = GetProcessIdFromAppWindow(appWindowTitle); + if (appWindowsProcessId < 0) + { + throw new Exception($"Could not find window with title: {appWindowTitle}"); + } + } + + EnsureApplicationProcessHasExited(appWindowsProcessId, appProcessName); + + if (Process != null) + { + Process.Dispose(); + Process = null; + } + return; + } + + private static void UpdateBuildArchIfNeeded() + { + if (String.IsNullOrEmpty(BuildArch)) + { + string getEnv = Environment.GetEnvironmentVariable("Build_Platform"); + if (String.IsNullOrEmpty(getEnv)) + { + BuildArch = "x64"; + } + else + { + BuildArch = getEnv; + } + Log.Comment("BuildArch updated to: " + BuildArch); + } + } + + private static void UpdateBuildConfigIfNeeded() + { + if (String.IsNullOrEmpty(BuildConfig)) + { + string getEnv = Environment.GetEnvironmentVariable("Build_Configuration"); + if (String.IsNullOrEmpty(getEnv)) + { + BuildConfig = "Release"; + } + else + { + BuildConfig = getEnv; + } + + Log.Comment("BuildConfig updated to: " + BuildConfig); + } + } + + // The prescribed fileRelativePath is expected to be relative to the root of the samples folder. See example below. + // Construct the full path by appending fileRelativePath to the samples root folder. + // Verify existence of the resulting full path. + // Similarly, use _optional_ environment variables to initialize build architecture and config. + private static string GetFullFilePathFromRelativePath(string fileRelativePath) + { + if (String.IsNullOrEmpty(fileRelativePath)) + { + Verify.Fail($"fileRelativePath cannot be null!"); + return null; + } + + // Fetch the samples root folder from environment variable on demand. + if (String.IsNullOrEmpty(SamplesRootPath)) + { + string getEnv = Environment.GetEnvironmentVariable("SAMPLES_ROOT_PATH"); + if (String.IsNullOrEmpty(getEnv)) + { + Log.Error("**** Please set environment variable SAMPLES_ROOT_PATH before running this test ****"); + Log.Error("SAMPLES_ROOT_PATH should point to a folder that contains sample folders such as AppLifecycle, PhotoEditor, ResourceManagement, etc."); + Verify.Fail($"e.g., set SAMPLES_ROOT_PATH=C:\\myRepo\\WindowsAppSDK-Samples\\Samples"); + Log.Error("You can also optionally set Build_Platform to one of { x86 | x64 | arm64 }, the default is x64."); + Log.Error($"e.g., set Build_Platform=arm64"); + Log.Error("You can also optionally set Build_Configuration to one of { Debug | Release }, the default is Release."); + Log.Error($"Build_Platform and Build_Configuration modifies the file path to the test binary targeted."); + return null; + } + + if (!Directory.Exists(getEnv)) + { + Verify.Fail($"Path {getEnv} in SAMPLES_ROOT_PATH was not found!"); + return null; + } + + SamplesRootPath = getEnv; + Log.Comment("SamplesRootPath updated to: " + SamplesRootPath); + } + + UpdateBuildArchIfNeeded(); + UpdateBuildConfigIfNeeded(); + + var fileFullPath1 = Path.Combine(SamplesRootPath, fileRelativePath); + var fileFullPath2 = fileFullPath1.Replace("[BuildArch]", BuildArch); + var fileFullPath3 = fileFullPath2.Replace("[BuildConfig]", BuildConfig); + if (!File.Exists(fileFullPath3)) + { + Verify.Fail($"File path {fileFullPath3} was not found!"); + return null; + } + + return fileFullPath3; + } + + private static class NativeMethods + { + public enum ActivateOptions + { + None = 0x00000000, // No flags set + DesignMode = 0x00000001, // The application is being activated for design mode, and thus will not be able to + // to create an immersive window. Window creation must be done by design tools which + // load the necessary components by communicating with a designer-specified service on + // the site chain established on the activation manager. The splash screen normally + // shown when an application is activated will also not appear. Most activations + // will not use this flag. + NoErrorUI = 0x00000002, // Do not show an error dialog if the app fails to activate. + NoSplashScreen = 0x00000004, // Do not show the splash screen when activating the app. + } + + public const string CLSID_ApplicationActivationManager_String = "45ba127d-10a8-46ea-8ab7-56ea9078943c"; + public const string CLSID_IApplicationActivationManager_String = "2e941141-7f97-4756-ba1d-9decde894a3d"; + + public static readonly Guid CLSID_ApplicationActivationManager = new Guid(CLSID_ApplicationActivationManager_String); + public static readonly Guid CLSID_IApplicationActivationManager = new Guid(CLSID_IApplicationActivationManager_String); + + [ComImport, Guid(CLSID_IApplicationActivationManager_String), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IApplicationActivationManager + { + // Activates the specified immersive application for the "Launch" contract, passing the provided arguments + // string into the application. Callers can obtain the process Id of the application instance fulfilling this contract. + IntPtr ActivateApplication([In] String appUserModelId, [In] String arguments, [In] ActivateOptions options, [Out] out UInt32 processId); + IntPtr ActivateForFile([In] String appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] String verb, [Out] out UInt32 processId); + IntPtr ActivateForProtocol([In] String appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out UInt32 processId); + } + + [DllImport("api-ms-win-ntuser-ie-window-l1-1-0.dll", SetLastError = true)] + public static extern bool SetForegroundWindow(IntPtr hWnd); + + [DllImport("ole32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] + public static extern UInt32 CoCreateInstance( + [In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid, + IntPtr pUnkOuter, + CLSCTX dwClsContext, + [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid, + [MarshalAs(UnmanagedType.IUnknown)] out object rReturnedComObject); + + [Flags] + public enum CLSCTX : uint + { + CLSCTX_INPROC_SERVER = 0x1, + CLSCTX_INPROC_HANDLER = 0x2, + CLSCTX_LOCAL_SERVER = 0x4, + CLSCTX_INPROC_SERVER16 = 0x8, + CLSCTX_REMOTE_SERVER = 0x10, + CLSCTX_INPROC_HANDLER16 = 0x20, + CLSCTX_RESERVED1 = 0x40, + CLSCTX_RESERVED2 = 0x80, + CLSCTX_RESERVED3 = 0x100, + CLSCTX_RESERVED4 = 0x200, + CLSCTX_NO_CODE_DOWNLOAD = 0x400, + CLSCTX_RESERVED5 = 0x800, + CLSCTX_NO_CUSTOM_MARSHAL = 0x1000, + CLSCTX_ENABLE_CODE_DOWNLOAD = 0x2000, + CLSCTX_NO_FAILURE_LOG = 0x4000, + CLSCTX_DISABLE_AAA = 0x8000, + CLSCTX_ENABLE_AAA = 0x10000, + CLSCTX_FROM_DEFAULT_CONTEXT = 0x20000, + CLSCTX_ACTIVATE_32_BIT_SERVER = 0x40000, + CLSCTX_ACTIVATE_64_BIT_SERVER = 0x80000, + CLSCTX_INPROC = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, + CLSCTX_SERVER = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER, + CLSCTX_ALL = CLSCTX_SERVER | CLSCTX_INPROC_HANDLER + } + } + + // Verify that process creation of the prescribed exe by full path hits no error. + // Generally, there isn't much else we can verify in this basic scenario. + private void LaunchUnpackagedConsoleApp(string unpackagedExeFullPath) + { + Log.Comment($"Using executable: {unpackagedExeFullPath}"); + try + { + if (!File.Exists(unpackagedExeFullPath)) + { + Verify.Fail($"Executable not found at {unpackagedExeFullPath}!"); + return; + } + + ProcessStartInfo unelevatedProcessStartInfo = new ProcessStartInfo(); + unelevatedProcessStartInfo.FileName = unpackagedExeFullPath; + unelevatedProcessStartInfo.Arguments = ""; + + Process.Start(unelevatedProcessStartInfo); + + Log.Comment("Launch successful!"); + } + catch (Exception ex) + { + Log.Comment("Failed to launch app. Exception: " + ex.ToString()); + Log.Error("Could not launch app {0} !", unpackagedExeFullPath); + throw; + } + + return; + } + + // Use the Activation Manager to launch a packaged app by appName (app ID). When a valid appWindowTitle is prescribed, + // the launch isn't deemed finish until a window with expected title appWindowTitle is found. Verify that a process + // with expected name appProcessName can be found, and then the corresponding process is terminated by process ID. + // Termination of the process is also verified. + private void LaunchAndClosePackagedApp(string appName, string appWindowTitle, string appProcessName) + { + const int MaxLaunchRetries = 3; + + Log.Comment($"Using NonUWPApp: {appName}"); + + for (int retries = 1; retries <= MaxLaunchRetries; ++retries) + { + try + { + Log.Comment("Attempting launch, try #{0}...", retries); + + if (NativeMethods.CoCreateInstance( + NativeMethods.CLSID_ApplicationActivationManager, + IntPtr.Zero, + NativeMethods.CLSCTX.CLSCTX_LOCAL_SERVER, + NativeMethods.CLSID_IApplicationActivationManager, + out object applicationActivationManagerAsObject) != 0) + { + throw new Exception("Failed to create ApplicationActivationManager!"); + } + + if (String.IsNullOrEmpty(appWindowTitle)) + { + var applicationActivationManager = (NativeMethods.IApplicationActivationManager)applicationActivationManagerAsObject; + applicationActivationManager.ActivateApplication(appName, null, NativeMethods.ActivateOptions.None, out uint processId); + + // Unlike the case below, there is no window to wait for here. So, as long as we didn't hit an activation error, just + // wait a couple of seconds to give the app a chance to run, proceed to kill it. + Thread.Sleep(TimeSpan.FromSeconds(2)); + } + else + { + UICondition WindowCondition = UICondition.CreateFromName(appWindowTitle); + UIObject window; + using (AppLaunchWaiter launchWaiter = new AppLaunchWaiter(WindowCondition)) + { + var applicationActivationManager = (NativeMethods.IApplicationActivationManager)applicationActivationManagerAsObject; + applicationActivationManager.ActivateApplication(appName, null, NativeMethods.ActivateOptions.None, out uint processId); + + launchWaiter.Wait(); + window = launchWaiter.Source; + } + + NativeMethods.SetForegroundWindow(window.NativeWindowHandle); + } + + Log.Comment("Launch successful!"); + break; + } + catch (Exception ex) + { + Log.Comment("Failed to launch app. Exception: " + ex.ToString()); + + if (retries < MaxLaunchRetries) + { + Log.Comment("UAPApp.Launch might not have waited long enough, trying again {0}", retries); + Thread.Sleep(TimeSpan.FromSeconds(2)); + } + else + { + Log.Error("Could not launch app {0} !", appName); + throw; + } + } + } + + int appWindowsProcessId = -1; + if (!String.IsNullOrEmpty(appWindowTitle)) + { + // appWindowTitle being valid means we would have waited for the app's window with the expected title at launch above. + // Hence, putting the logic below in a retry loop probably won't help. + appWindowsProcessId = GetProcessIdFromAppWindow(appWindowTitle); + if (appWindowsProcessId < 0) + { + throw new Exception($"Could not find window with title: {appWindowTitle}"); + } + } + + EnsureApplicationProcessHasExited(appWindowsProcessId, appProcessName); + + if (Process != null) + { + Process.Dispose(); + Process = null; + } + return; + } + + // Invoke CreateProcess to run exeName with arguments. Wiat for completion. + // Verify no error is returned. + private static void RunCommand(string exeName, string arguments) + { + Log.Comment("Running the following command line: " + exeName + " " + arguments); + + Process process = new Process(); + ProcessStartInfo psi = new ProcessStartInfo(); + + psi.FileName = exeName; + if (arguments != null) + { + psi.Arguments = arguments; + } + psi.UseShellExecute = false; + psi.RedirectStandardOutput = true; + psi.RedirectStandardError = true; + + process.StartInfo = psi; + process.Start(); + string stdout = process.StandardOutput.ReadToEnd(); + string stderr = process.StandardError.ReadToEnd(); + + // Ensure things went smoothly + Verify.IsTrue(process.WaitForExit(TIMEOUT), "Waiting for " + exeName + " to finish"); + Log.Comment("Tool std output: " + stdout); + Log.Comment("Tool error output: " + stderr); + + // Ensure we dont fail for any commands + Verify.IsTrue((process.ExitCode == 0), "Expected an exit code of 0, and got back " + process.ExitCode); + process.Dispose(); + } + + private static void RunPowerShellCommand(string arguments) + { + RunCommand("powershell.exe ", arguments); + } + + private static void RemoveAppxPackage(string packageFullNameTempalte) + { + var packageFullName = packageFullNameTempalte.Replace("[BuildArch]", BuildArch); + RunPowerShellCommand("remove-appxpackage " + packageFullName); + } + + private static void RegisterAppxPackage(string manifestRelativePath) + { + var manifestFullPath = GetFullFilePathFromRelativePath(manifestRelativePath); + + // Powershell needs single quotes around the path in order to cope with spaces in the path. + var manifestFullPathQuoted = "\'" + manifestFullPath + "\'"; + + Log.Comment("manifestFullPathQuoted: " + manifestFullPathQuoted); + RunPowerShellCommand("add-appxpackage " + "-register " + manifestFullPathQuoted); + } + + private bool IsArchX86() + { + UpdateBuildArchIfNeeded(); + return ((BuildArch == "x86") || (BuildArch == "X86")); + } + + private bool IsArchX64() + { + UpdateBuildArchIfNeeded(); + return ((BuildArch == "x64") || (BuildArch == "X64")); + } + + private bool IsBuildConfigDebug() + { + UpdateBuildConfigIfNeeded(); + return ((BuildConfig == "debug") || (BuildConfig == "Debug")); + } + + [ClassInitialize] + [TestProperty("RunAs", "User")] + public static void ClassInitialize(TestContext testContext) + { + return; + } + + [TestCleanup] + public void TestCleanup() + { + return; + } + + [ClassCleanupAttribute] + public static void ClassCleanup() + { + return; + } + +/* + Adding a new Sample app + +- If you are adding a new Sample app to an existing feature area, please find the existing feature area below and add it there. +- Sections are marked by banners that look like "**** XYZ sample apps ***". +- If you are adding a new feature area, please start a new section using existing feature areas, such as "SelfContainedDeployment", + as example. Add new test cses for the Sample apps in the new section. +- Feature areas that currently only have 1 Sample app are grouped at the end in the "Other" section. Feel free to start a new + section if a feature area there is getting more than 1 sample app. +- There is typically one test method per Sample app. Please use the feature area as the prefix of the name of the new test method, + followed by the name/ID of the Sample app. Please see examples below. +- Currently, no Sample apps are being built in the pipeline for x86, hence, your sample app test method typically will have a "if + IsArchX86() then skip the test" check at top. You should be able to find examples below. +- If your Sample app does not support arm64, you might want to use a "if (!IsArchX64()) then skip the test" check at top. Again, + there are examples below. +- If your Sample app is "release only", then you might want to have a "if (IsBuildConfigDebug()) then skip the test" check. +- If your Sample app does not support older Windows version, please use Environment.OSVersion.Version.* to skip your test as needed. + Examples are below. +- Please keep in mind that this TAEF test might be run manually on a local machine, so configuration permutations currently not + being exercised in the pipeline can happen, and your test method should handle that. +- If your Sample app is a _unpackaged_ + - console app (i.e., no UI), please use LaunchUnpackagedConsoleApp() to verify its launch, where is the relative + path to the .exe with your feature area as the root. Please see examples below. + - app that pops a window, please use LaunchAndCloseUnpackagedApp(, , ) to verify its launch. + Please see examples below. + - app that does not pop a window but a process is still expected to be running after launch, please use LaunchAndCloseUnpackagedApp( + , null, ) to verify its launch. Please see examples below. +- If your Sample app is a _packaged_ app + - that pops a window, please use LaunchAndClosePackagedApp(, , ) to verify its launch. + - The packaged app should be explicitly registered using RegisterAppxPackage() and removed using RemoveAppxPackage( + ). Please see examples below. + - that does not pop a window, please use LaunchAndClosePackagedApp(, null, ) to verify its launch. +- Even if for whatever reason a packaged sample app cannot be verified for launch, there is still merit to automate verification of + the Register and Remove of the package. Please see examples below. +- Please strive to maximize the degree of verification for your Sample app. Add runtime checks for your app after launch if applicable. + +*/ + + /**** SelfContainedDeployment sample apps ****/ + + // For unpackaged console apps, generally we verify that launch of the app by full path to the exe does not return an error. + [TestMethod] + public void SelfContainedDeploymentCppConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("SelfContainedDeployment\\cpp\\cpp-console-unpackaged\\[BuildArch]\\[BuildConfig]\\SelfContainedDeployment.exe"); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + [TestProperty("Ignore", "True")] // Temporarily skipping this due to http://task.ms/56699769 + public void SelfContainedDeploymentCsConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("SelfContainedDeployment\\cs\\cs-console-unpackaged\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\win10-[BuildArch]\\SelfContainedDeployment.exe"); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + [TestProperty("Ignore", "True")] // Temporarily skipping this due to http://task.ms/56699769 + public void SelfContainedDeploymentCsWpfUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("SelfContainedDeployment\\cs\\cs-wpf-unpackaged\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.17763.0\\win10-[BuildArch]\\SelfContainedDeployment.exe"); + LaunchAndCloseUnpackagedApp(exePath, "MainWindow", "SelfContainedDeployment.exe"); + return; + } + + /**** AppLifecycle sample apps ****/ + + [TestMethod] + public void AppLifecycleActivationCppConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\Activation\\cpp\\cpp-console-unpackaged\\[BuildArch]\\[BuildConfig]\\CppWinRtConsoleActivation.exe"); + + // TODO: Even when launched in VS, this app is hitting "Error 0x80070491 in MddBootstrapInitialize(0x00010000, , 0.0.0.0)". + // Therefore, resorting to minimal verification for now, improve on this in the future. + LaunchUnpackagedConsoleApp(exePath); + return; + } + + // TODO: This sample app quickly exits after launch, with message: "CsConsoleActivation.exe (process 11628) exited with code 0.", hence not much verification is + // being done currently. Consider improving on that. Same for the next few apps. + [TestMethod] + public void AppLifecycleActivationCsConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\Activation\\cs\\cs-console-unpackaged\\CsConsoleActivation\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\CsConsoleActivation.exe"); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleActivationCppWin32Unpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\Activation\\cpp\\cpp-win32-unpackaged\\[BuildArch]\\[BuildConfig]\\CppWinMainActivation.exe"); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleActivationCsWinformsUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\Activation\\cs\\cs-winforms-unpackaged\\CsWinFormsActivation\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\CsWinFormsActivation.exe"); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleActivationCsWpfUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\Activation\\cs\\cs-wpf-unpackaged\\CsWpfActivation\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\CsWpfActivation.exe"); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + // For unpackaged apps that does not put up a window, generally we verify that + // 1) launch of the app by full path to the exe does not return an error, + // 2) a process with the expected name is present, + // 3) processes associated with the app can be terminated. + // TODO: Missing WASDK 1.2 is blocking WinCppWinRtConsoleEnv.exe from activating the app's main window? Temporarily skip looking for the app's main + // window, but still look for the expected process name to terminate. Same for the next 2 sample apps. + [TestMethod] + public void AppLifecycleEnvironmentVariablesCppConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\EnvironmentVariables\\cpp-console-unpackaged\\[BuildArch]\\[BuildConfig]\\CppWinRtConsoleEnv.exe"); + LaunchAndCloseUnpackagedApp(exePath, null, "CppWinRtConsoleEnv.exe"); + return; + } + + [TestMethod] + public void AppLifecycleEnvironmentVariablesCppWin32Unpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\EnvironmentVariables\\cpp-win32-unpackaged\\[BuildArch]\\[BuildConfig]\\CppWinMainEnv.exe"); + LaunchAndCloseUnpackagedApp(exePath, null, "CppWinMainEnv.exe"); + return; + } + + [TestMethod] + public void AppLifecycleEnvironmentVariablesCsWinfromsUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + if (!IsArchX64()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, + "This Sample app is currently failing to launch on arm64fre builds due to MSVCP140.dll NOT FOUND error."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\EnvironmentVariables\\cs-winforms-unpackaged\\CsWinFormsEnv\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\CsWinFormsEnv.exe"); + LaunchAndCloseUnpackagedApp(exePath, null, "CsWinFormsEnv.exe"); + return; + } + + [TestMethod] + public void AppLifecycleInstancingCppConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\Instancing\\cpp\\cpp-console-unpackaged\\[BuildArch]\\[BuildConfig]\\CppWinRtConsoleInstancing.exe"); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleInstancingCsConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\Instancing\\cs\\cs-console-unpackaged\\CsConsoleInstancing\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\CsConsoleInstancing.exe"); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleInstancingCppWin32Unpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\Instancing\\cpp\\cpp-win32-unpackaged\\[BuildArch]\\[BuildConfig]\\CppWinMainInstancing.exe"); + + // This sample app seems to behave like a console app. + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleInstancingCsWinformsUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\Instancing\\cs\\cs-winforms-unpackaged\\CsWinFormsInstancing\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\CsWinFormsInstancing.exe"); + + // This sample app seems to behave like a console app. + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleInstancingCsWpfUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\Instancing\\cs\\cs-wpf-unpackaged\\CsWpfInstancing\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\CsWpfInstancing.exe"); + + // This sample app seems to behave like a console app. + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleStateNotificationsCppConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\StateNotifications\\cpp\\cpp-console-unpackaged\\[BuildArch]\\[BuildConfig]\\CppWinRtConsoleState.exe"); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleStateNotificationsCsConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\StateNotifications\\cs\\cs-console-unpackaged\\CsConsoleState\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\CsConsoleState.exe"); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleStateNotificationsCppWin32Unpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\StateNotifications\\cpp\\cpp-win32-unpackaged\\[BuildArch]\\[BuildConfig]\\CppWinMainState.exe"); + + // This sample app seems to behave like a console app. + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleStateNotificationsCsWinformsUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\StateNotifications\\cs\\cs-winforms-unpackaged\\CsWinFormsState\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\CsWinFormsState.exe"); + + // This sample app seems to behave like a console app. + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleStateNotificationsCsWpfUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\StateNotifications\\cs\\cs-wpf-unpackaged\\CsWpfState\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\CsWpfState.exe"); + + // This sample app seems to behave like a console app. + LaunchUnpackagedConsoleApp(exePath); + return; + } + + // [TestMethod] + // public void AppLifecycleInstancingCppWin32Packaged() + // { + // if (IsArchX86() || IsBuildConfigDebug()) + // { + // WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + // return; + // } + + // Log.Comment($"OSVersion {Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}.{Environment.OSVersion.Version.Build}"); + // if ((Environment.OSVersion.Version.Major < 10) || ((Environment.OSVersion.Version.Major == 10) && (Environment.OSVersion.Version.Build < 19041))) + // { + // WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This test requires Win 10 Build 19041 or higher."); + // return; + // } + + // if ((Environment.OSVersion.Version.Major == 10) && ((Environment.OSVersion.Version.Build == 19044) || (Environment.OSVersion.Version.Build == 19045))) + // { + // WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "Skipping this test on LTSC 2021 (19044) and 22h2 (19045) until b#53993352 is fixed."); + // return; + // } + + // RegisterAppxPackage("AppLifecycle\\Instancing\\cpp\\cpp-win32-packaged\\CppWinMainInstancingPkg\\bin\\[BuildArch]\\[BuildConfig]\\AppxManifest.xml"); + // LaunchAndClosePackagedApp("d9bcd10a-b42d-4e1e-9656-a2284d39e12d_s9y1p3hwd5qda!App", "CppWinMainInstancing", "CppWinMainInstancing.exe"); + // RemoveAppxPackage("d9bcd10a-b42d-4e1e-9656-a2284d39e12d_1.0.0.0_[BuildArch]__s9y1p3hwd5qda"); + // return; + // } + + [TestMethod] + public void AppLifecycleInstancingCsWpfPackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + Log.Comment($"OSVersion {Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}.{Environment.OSVersion.Version.Build}"); + if ((Environment.OSVersion.Version.Major < 10) || ((Environment.OSVersion.Version.Major == 10) && (Environment.OSVersion.Version.Build < 19041))) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This test requires Win 10 Build 19041 or higher."); + return; + } + + if ((Environment.OSVersion.Version.Major == 10) && ((Environment.OSVersion.Version.Build == 19044) || (Environment.OSVersion.Version.Build == 19045))) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "Skipping this test on LTSC 2021 (19044) and 22h2 (19045) until b#53993352 is fixed."); + return; + } + + RegisterAppxPackage("AppLifecycle\\Instancing\\cs1\\cs-wpf-packaged\\CsWpfInstancingPkg\\bin\\[BuildArch]\\[BuildConfig]\\AppxManifest.xml"); + LaunchAndClosePackagedApp("1b900d33-3e06-4f3e-9797-2fc2ffebb6f1_s9y1p3hwd5qda!App", "CsWpfInstancingPkg", "CsWpfInstancing.exe"); + RemoveAppxPackage("1b900d33-3e06-4f3e-9797-2fc2ffebb6f1_1.0.0.0_[BuildArch]__s9y1p3hwd5qda"); + return; + } + + // [TestMethod] + // public void AppLifecycleStateNotificationsCppWin32Packaged() + // { + // if (IsArchX86() || IsBuildConfigDebug()) + // { + // WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + // return; + // } + + // Log.Comment($"OSVersion {Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}.{Environment.OSVersion.Version.Build}"); + // if ((Environment.OSVersion.Version.Major < 10) || ((Environment.OSVersion.Version.Major == 10) && (Environment.OSVersion.Version.Build < 19041))) + // { + // WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This test requires Win 10 Build 19041 or higher."); + // return; + // } + + // if ((Environment.OSVersion.Version.Major == 10) && ((Environment.OSVersion.Version.Build == 19044) || (Environment.OSVersion.Version.Build == 19045))) + // { + // WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "Skipping this test on LTSC 2021 (19044) and 22h2 (19045) until b#53993352 is fixed."); + // return; + // } + + // RegisterAppxPackage("AppLifecycle\\StateNotifications\\cpp\\cpp-win32-packaged\\CppWinMainStatePkg\\bin\\[BuildArch]\\[BuildConfig]\\AppxManifest.xml"); + // LaunchAndClosePackagedApp("e0d8ad55-f3f2-4d7f-9182-3ee6905208a8_s9y1p3hwd5qda!App", "CppWinMainState", "CppWinMainState.exe"); + // RemoveAppxPackage("e0d8ad55-f3f2-4d7f-9182-3ee6905208a8_1.0.0.0_[BuildArch]__s9y1p3hwd5qda"); + // return; + // } + + [TestMethod] + public void AppLifecycleStateNotificationsCsWpfPackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + Log.Comment($"OSVersion {Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}.{Environment.OSVersion.Version.Build}"); + if ((Environment.OSVersion.Version.Major < 10) || ((Environment.OSVersion.Version.Major == 10) && (Environment.OSVersion.Version.Build < 19041))) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This test requires Win 10 Build 19041 or higher."); + return; + } + + if ((Environment.OSVersion.Version.Major == 10) && ((Environment.OSVersion.Version.Build == 19044) || (Environment.OSVersion.Version.Build == 19045))) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "Skipping this test on LTSC 2021 (19044) and 22h2 (19045) until b#53993352 is fixed."); + return; + } + + RegisterAppxPackage("AppLifecycle\\StateNotifications\\cs1\\cs-wpf-packaged\\CsWpfStatePkg\\bin\\[BuildArch]\\[BuildConfig]\\AppxManifest.xml"); + LaunchAndClosePackagedApp("7718eec3-5345-4d4e-815f-97df705dc89c_s9y1p3hwd5qda!App", "C# WPF Activation", "CsWpfState.exe"); + RemoveAppxPackage("7718eec3-5345-4d4e-815f-97df705dc89c_1.0.0.0_[BuildArch]__s9y1p3hwd5qda"); + return; + } + + /**** ResourceManagement sample apps ****/ + + // TODO: Missing WASDK 1.2 is blocking Unpackaged.exe from activating? Skip looking for the app's main window, but still + // look for the expected process name to terminate. Same for the next app. + [TestMethod] + public void ResourceManagementCppConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("ResourceManagement\\cpp\\cpp-console-unpackaged\\[BuildArch]\\[BuildConfig]\\console_unpackaged_app.exe"); + + Log.Comment($"OSVersion {Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}.{Environment.OSVersion.Version.Build}"); + if ((Environment.OSVersion.Version.Major == 10) && (Environment.OSVersion.Version.Build == 22000)) + { + Log.Comment("This test is currently failing on the 21h2 Enterprise Multi-Session OS image seemingly because a Welcome to MS Teams! window is on top. Doing minimal activation checking until this is resolved."); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + LaunchAndCloseUnpackagedApp(exePath, null, "console_unpackaged_app.exe"); + return; + } + + [TestMethod] + public void ResourceManagementCsWinformsUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + if (!IsArchX64()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, + "This Sample app is currently failing to launch on arm64fre builds due to MSVCP140.dll NOT FOUND error."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("ResourceManagement\\cs\\cs-winforms-unpackaged\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\winforms_unpackaged_app.exe"); + LaunchAndCloseUnpackagedApp(exePath, null, "winforms_unpackaged_app.exe"); + return; + } + + [TestMethod] + public void ResourceManagementCsWpfPackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + if ((Environment.OSVersion.Version.Major == 10) && ((Environment.OSVersion.Version.Build == 19044) || (Environment.OSVersion.Version.Build == 19045))) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "Skipping this test on LTSC 2021 (19044) and 22h2 (19045) until b#53993352 is fixed."); + return; + } + + RegisterAppxPackage("ResourceManagement\\cs1\\cs-wpf\\wpf_packaged_app (Package)\\bin\\[BuildArch]\\[BuildConfig]\\AppxManifest.xml"); + LaunchAndClosePackagedApp("25dee5b5-8e25-431f-a644-e75d98163029_s9y1p3hwd5qda!App", "MRT Core WPF sample", "wpf_packaged_app.exe"); + RemoveAppxPackage("25dee5b5-8e25-431f-a644-e75d98163029_1.0.0.0_[BuildArch]__s9y1p3hwd5qda"); + return; + } + + /**** Windowing sample apps ****/ + + [TestMethod] + public void WindowingCsWinformsUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + if (!IsArchX64()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, + "This Sample app is currently failing to launch on arm64fre builds due to MSVCP140.dll NOT FOUND error."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("Windowing\\cs\\cs-winforms-unpackaged\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\winforms_unpackaged_app.exe"); + + // TODO: Missing WASDK 1.2 is blocking winforms_unpackaged_app.exe from activating the app's main window? Temporarily skip looking for the app's main + // window, but still look for the expected process name to terminate. + LaunchAndCloseUnpackagedApp(exePath, null, "winforms_unpackaged_app.exe"); + return; + } + + [TestMethod] + public void WindowingCppWin32Unpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("Windowing\\cpp\\cpp-win32\\[BuildArch]\\[BuildConfig]\\Windowing.exe"); + + // TODO: Missing WASDK 1.2 is blocking Windowing.exe from activating the app's main window? Temporarily skip looking for the app's main + // window, but still look for the expected process name to terminate. + LaunchAndCloseUnpackagedApp(exePath, null, "Windowing.exe"); + return; + } + + /**** Unpackaged sample apps ****/ + + // TODO: Missing WASDK 1.2 is blocking Unpackaged.exe from activating? Skip looking for the app's main window, but still + // look for the expected process name to terminate. Same for the next app. + [TestMethod] + public void UnpackagedCppConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("Unpackaged\\cpp-console-unpackaged\\[BuildArch]\\[BuildConfig]\\Unpackaged.exe"); + Log.Comment($"OSVersion {Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}.{Environment.OSVersion.Version.Build}"); + if ((Environment.OSVersion.Version.Major == 10) && (Environment.OSVersion.Version.Build == 22000)) + { + Log.Comment("This test is currently failing on the 21h2 Enterprise Multi-Session OS image seemingly because a Welcome to MS Teams! window is on top. Doing minimal activation checking until this is resolved."); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + LaunchAndCloseUnpackagedApp(exePath, null, "Unpackaged.exe"); + return; + } + + [TestMethod] + public void UnpackagedCsConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("Unpackaged\\cs-console-unpackaged\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\Unpackaged.exe"); + LaunchUnpackagedConsoleApp(exePath); + + // TODO: The following stronger verification seems to work fine on a local machine but not in the Azure pipeline. Try harder to enable it. + // LaunchAndCloseUnpackagedApp(exePath, null, "Unpackaged.exe"); + return; + } + + /**** Notifications sample apps ****/ + + [TestMethod] + public void NotificationsPushCppConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("Notifications\\Push\\cpp-console-unpackaged\\[BuildArch]\\[BuildConfig]\\cpp-console-unpackaged.exe"); + + // TODO: Missing WASDK 1.2 is blocking CppUnpackagedAppNotifications.exe from activating? Skip looking for the app's main window, but still + // look for the expected process name to terminate. Same for the next app. + LaunchAndCloseUnpackagedApp(exePath, null, "cpp-console-unpackaged.exe"); + return; + } + + // For packaged console apps which does not put up a window, generally we verify that + // 1) deployment of the app's package is successful, + // 2) launch of the app by appName (appID) does not return an error, + // 3) a process with the expected name is present, + // 4) processes associated with the app can be terminated. + // 5) the app's package can be successfully removed. + // [TestMethod] + // public void NotificationsPushCppConsolePackaged() + // { + // if (IsArchX86() || IsBuildConfigDebug()) + // { + // WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + // return; + // } + + // if ((Environment.OSVersion.Version.Major == 10) && ((Environment.OSVersion.Version.Build == 19044) || (Environment.OSVersion.Version.Build == 19045))) + // { + // WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "Skipping this test on LTSC 2021 (19044) and 22h2 (19045) until b#53993352 is fixed."); + // return; + // } + // if ((Environment.OSVersion.Version.Major == 10) && (Environment.OSVersion.Version.Minor == 0) && ((Environment.OSVersion.Version.Build == 17763))) + // { + // // https://task.ms/54900647 + // Log.Comment("This test is currently failing on the Win 10 Server 2019 OS image. Skipping this launch test to unblock exercising of the image until b#54900647 is fixed."); + // return; + // } + + // RegisterAppxPackage("Notifications\\Push\\cpp-console-packaged\\cpp-console-package\\bin\\[BuildArch]\\[BuildConfig]\\AppxManifest.xml"); + + // // Upon launch, the app throws the following error message, which looks expected: + // // There was an error obtaining the WNS Channel URI + // // The remoteId has not been set.Refer to the readme file accompanying this sample + // // for the instructions on how to obtain and setup a remote id + // // Press 'Enter' at any time to exit App. + // // + // // Therefore, for this app we won't wait for a window with a specific title. Just proceed to look + // // for the expected process name to terminate. + // // + // // TODO: consider automating the above setup steps so that we can verify the main app window is present. + // LaunchAndClosePackagedApp("PushNotificationsSample_ph1m9x8skttmg!App", null, "cpp-console.exe"); + + // RemoveAppxPackage("PushNotificationsSample_1.0.0.0_[BuildArch]__ph1m9x8skttmg"); + // return; + // } + + /**** Other sample apps ****/ + + [TestMethod] + public void InstallerCppConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("Installer\\cpp-console-unpackaged\\[BuildArch]\\[BuildConfig]\\LaunchInstaller.exe"); + Log.Comment($"OSVersion {Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}.{Environment.OSVersion.Version.Build}"); + if ((Environment.OSVersion.Version.Major == 10) && (Environment.OSVersion.Version.Build == 22000)) + { + Log.Comment("This test is currently failing on the 21h2 Enterprise Multi-Session OS image seemingly because a Welcome to MS Teams! window is on top. Doing minimal activation checking until this is resolved."); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + LaunchAndCloseUnpackagedApp(exePath, null, "LaunchInstaller.exe"); + return; + } + + [TestMethod] + public void InsightsCppWin32Unpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("Insights\\cpp-win32\\[BuildArch]\\[BuildConfig]\\Insights.exe"); + + // This sample app seems to behave like a console app. + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void MicaCppWin32Packaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("Mica\\cpp-win32\\[BuildArch]\\[BuildConfig]\\WinAppSDKMicaSample.exe"); + + // TODO: Missing WASDK 1.2 is blocking cpp-console-unpackaged.exe from activating? Skip looking for the app's main window, but still + // look for the expected process name to terminate. + LaunchAndCloseUnpackagedApp(exePath, null, "WinAppSDKMicaSample.exe"); + return; + } + } +}