commit 6caf7968eace405cad6b4a1ada4d9e3df4485b2c parent a6e32af263b72837446150e5999a73d6c80ac2a9 Author: Ruben Guerrero <rubengu@microsoft.com> Date: Wed, 7 Dec 2022 13:15:00 -0800 Infra changes for Microsoft.WinGet.Client and AppInstallerCLIE2ETests (#2746) Diffstat:
54 files changed, 2873 insertions(+), 1233 deletions(-)
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt @@ -389,6 +389,7 @@ wsv wto wwinmain WZDNCRFJ +xcopy XFile XPLATSTR xsi diff --git a/.gitignore b/.gitignore @@ -333,4 +333,11 @@ ASALocalRun/ # Generated files from WinGetServer.idl **/WinGetServer/WinGetServer.h **/WinGetServer/WinGetServer_c.c -**/WinGetServer/WinGetServer_s.c- \ No newline at end of file +**/WinGetServer/WinGetServer_s.c + +# Crescendo generated module files +src/PowerShell/Microsoft.WinGet.Client/Crescendo/*.psd1 +src/PowerShell/Microsoft.WinGet.Client/Crescendo/*.psm1 + +# Dev PowerShell module path +src/PowerShell/scripts/Module diff --git a/azure-pipelines.yml b/azure-pipelines.yml @@ -300,11 +300,11 @@ jobs: TargetFolder: '$(artifactsDir)' condition: succeededOrFailed() - - task: PowerShell@2 - displayName: 'Copy PowerShell Module Binaries to Output' + - task: CopyFiles@2 + displayName: 'Copy PowerShell Module Files' inputs: - filePath: 'src\PowerShell\Microsoft.WinGet.Client\Copy-PlatformBinaries.ps1' - arguments: '-Platform $(buildPlatform) -Configuration $(buildConfiguration) -OutDir $(artifactsDir)\PowerShell' + SourceFolder: '$(buildOutDir)\PowerShell' + TargetFolder: '$(artifactsDir)\PowerShell' condition: always() - task: PublishPipelineArtifact@1 @@ -359,12 +359,6 @@ jobs: Contents: '**\*' TargetFolder: '$(Build.ArtifactStagingDirectory)' - - task: PowerShell@2 - displayName: 'Copy PowerShell Module Files to Output' - inputs: - filePath: 'src\PowerShell\Microsoft.WinGet.Client\Copy-ModuleFiles.ps1' - arguments: '-OutDir $(Build.ArtifactStagingDirectory)' - - task: PublishPipelineArtifact@1 displayName: Publish PowerShell Module Artifacts inputs: diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln @@ -98,6 +98,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject CodeAnalysis.ruleset = CodeAnalysis.ruleset nuget.config = nuget.config + stylecop.json = stylecop.json EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Management.Deployment", "Microsoft.Management.Deployment\Microsoft.Management.Deployment.vcxproj", "{1CC41A9A-AE66-459D-9210-1E572DD7BE69}" @@ -279,6 +280,32 @@ Global {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.TestRelease|x64.Build.0 = Release|x64 {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.TestRelease|x86.ActiveCfg = Release|Win32 {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.TestRelease|x86.Build.0 = Release|Win32 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|ARM.ActiveCfg = Debug|Win32 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|ARM64.ActiveCfg = Debug|Win32 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|x64.ActiveCfg = Debug|x64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|x64.Build.0 = Debug|x64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|x86.ActiveCfg = Debug|Win32 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|x86.Build.0 = Debug|Win32 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Fuzzing|Any CPU.ActiveCfg = Release|x64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Fuzzing|Any CPU.Build.0 = Release|x64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Fuzzing|ARM.ActiveCfg = Release|Win32 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Fuzzing|ARM64.ActiveCfg = Release|Win32 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Fuzzing|x64.ActiveCfg = Release|x64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Fuzzing|x86.ActiveCfg = Release|Win32 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|Any CPU.ActiveCfg = Release|Win32 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|ARM.ActiveCfg = Release|Win32 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|ARM64.ActiveCfg = Release|Win32 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|x64.ActiveCfg = Release|x64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|x64.Build.0 = Release|x64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|x86.ActiveCfg = Release|Win32 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|x86.Build.0 = Release|Win32 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.TestRelease|Any CPU.ActiveCfg = Release|x64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.TestRelease|Any CPU.Build.0 = Release|x64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.TestRelease|ARM.ActiveCfg = Release|x64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.TestRelease|ARM64.ActiveCfg = Release|x64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.TestRelease|x64.ActiveCfg = Release|x64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.TestRelease|x86.ActiveCfg = Release|Win32 {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Debug|Any CPU.ActiveCfg = Debug|Win32 {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Debug|ARM.ActiveCfg = Debug|ARM {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Debug|ARM.Build.0 = Debug|ARM @@ -313,32 +340,6 @@ Global {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.TestRelease|x64.Build.0 = Release|x64 {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.TestRelease|x86.ActiveCfg = Release|Win32 {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.TestRelease|x86.Build.0 = Release|Win32 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|ARM.ActiveCfg = Debug|Win32 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|ARM64.ActiveCfg = Debug|Win32 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|x64.ActiveCfg = Debug|x64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|x64.Build.0 = Debug|x64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|x86.ActiveCfg = Debug|Win32 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|x86.Build.0 = Debug|Win32 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Fuzzing|Any CPU.ActiveCfg = Release|x64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Fuzzing|Any CPU.Build.0 = Release|x64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Fuzzing|ARM.ActiveCfg = Release|Win32 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Fuzzing|ARM64.ActiveCfg = Release|Win32 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Fuzzing|x64.ActiveCfg = Release|x64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Fuzzing|x86.ActiveCfg = Release|Win32 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|Any CPU.ActiveCfg = Release|Win32 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|ARM.ActiveCfg = Release|Win32 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|ARM64.ActiveCfg = Release|Win32 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|x64.ActiveCfg = Release|x64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|x64.Build.0 = Release|x64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|x86.ActiveCfg = Release|Win32 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|x86.Build.0 = Release|Win32 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.TestRelease|Any CPU.ActiveCfg = Release|x64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.TestRelease|Any CPU.Build.0 = Release|x64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.TestRelease|ARM.ActiveCfg = Release|x64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.TestRelease|ARM64.ActiveCfg = Release|x64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.TestRelease|x64.ActiveCfg = Release|x64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.TestRelease|x86.ActiveCfg = Release|Win32 {8BB94BB8-374F-4294-BCA1-C7811514A6B7}.Debug|Any CPU.ActiveCfg = Debug|Win32 {8BB94BB8-374F-4294-BCA1-C7811514A6B7}.Debug|ARM.ActiveCfg = Debug|ARM {8BB94BB8-374F-4294-BCA1-C7811514A6B7}.Debug|ARM.Build.0 = Debug|ARM diff --git a/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj b/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj @@ -6,6 +6,18 @@ <IsPackable>false</IsPackable> <Platforms>x64;x86</Platforms> <OutputType>Library</OutputType> + <DocumentationFile>$(OutDir)\AppInstallerCLIE2ETests.xml</DocumentationFile> + <GenerateAssemblyInfo>false</GenerateAssemblyInfo> + </PropertyGroup> + + <PropertyGroup> + <!-- Disable warning for CS1591 because auto generated Microsoft.Management.Deployment --> + <!-- For our code, we still have stylecops acting on it. --> + <NoWarn>1591</NoWarn> + </PropertyGroup> + + <PropertyGroup Condition="'$(Configuration)'=='Release'"> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> </PropertyGroup> <ItemGroup> @@ -16,6 +28,10 @@ <PackageReference Include="nunit" Version="3.12.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.15.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.1.118"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> </ItemGroup> <!-- CsWinRT properties --> @@ -33,6 +49,7 @@ <Content Include="..\..\doc\admx\DesktopAppInstaller.admx" Link="TestData\DesktopAppInstaller.admx"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> + <AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" /> </ItemGroup> <ItemGroup> diff --git a/src/AppInstallerCLIE2ETests/BaseCommand.cs b/src/AppInstallerCLIE2ETests/BaseCommand.cs @@ -1,5 +1,8 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// ----------------------------------------------------------------------------- +// <copyright file="BaseCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- namespace AppInstallerCLIE2ETests { @@ -8,27 +11,45 @@ namespace AppInstallerCLIE2ETests using Newtonsoft.Json.Linq; using NUnit.Framework; + /// <summary> + /// Base command. + /// </summary> public class BaseCommand { + /// <summary> + /// Set up. + /// </summary> [OneTimeSetUp] public void BaseSetup() { - ResetTestSource(); + this.ResetTestSource(); } + /// <summary> + /// Tear down. + /// </summary> [OneTimeTearDown] public void BaseTeardown() { TestCommon.TearDownTestSource(); } - // TODO: If/when cert pinning is implemented on the packaged index source, useGroupPolicyForTestSource should be set to default true - // to enable testing it by default. Until then, leaving this here... + /// <summary> + /// Reset test source. + /// </summary> + /// <param name="useGroupPolicyForTestSource">Use group policy from test source.</param> public void ResetTestSource(bool useGroupPolicyForTestSource = false) { + // TODO: If/when cert pinning is implemented on the packaged index source, useGroupPolicyForTestSource should be set to default true + // to enable testing it by default. Until then, leaving this here... TestCommon.SetupTestSource(useGroupPolicyForTestSource); } + /// <summary> + /// Configure experimental features. + /// </summary> + /// <param name="featureName">Feature name.</param> + /// <param name="status">Status.</param> public void ConfigureFeature(string featureName, bool status) { string localAppDataPath = Environment.GetEnvironmentVariable(Constants.LocalAppData); @@ -39,6 +60,11 @@ namespace AppInstallerCLIE2ETests File.WriteAllText(Path.Combine(localAppDataPath, TestCommon.SettingsJsonFilePath), settingsJson.ToString()); } + /// <summary> + /// Configure the install behavior. + /// </summary> + /// <param name="settingName">Setting name.</param> + /// <param name="value">Setting value.</param> public void ConfigureInstallBehavior(string settingName, string value) { string localAppDataPath = Environment.GetEnvironmentVariable(Constants.LocalAppData); @@ -49,14 +75,18 @@ namespace AppInstallerCLIE2ETests File.WriteAllText(Path.Combine(localAppDataPath, TestCommon.SettingsJsonFilePath), settingsJson.ToString()); } + /// <summary> + /// Initialize all features. + /// </summary> + /// <param name="status">Initialized feature value.</param> public void InitializeAllFeatures(bool status) { - ConfigureFeature("experimentalArg", status); - ConfigureFeature("experimentalCmd", status); - ConfigureFeature("dependencies", status); - ConfigureFeature("directMSI", status); - ConfigureFeature("zipInstall", status); - ConfigureFeature("openLogsArgument", status); + this.ConfigureFeature("experimentalArg", status); + this.ConfigureFeature("experimentalCmd", status); + this.ConfigureFeature("dependencies", status); + this.ConfigureFeature("directMSI", status); + this.ConfigureFeature("zipInstall", status); + this.ConfigureFeature("openLogsArgument", status); } } } diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs @@ -1,10 +1,19 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// ----------------------------------------------------------------------------- +// <copyright file="Constants.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- namespace AppInstallerCLIE2ETests { + /// <summary> + /// Constants. + /// </summary> public class Constants { +#pragma warning disable SA1600 // ElementsMustBeDocumented +#pragma warning disable SA1310 // Field names should not contain underscore + // Runtime test parameters public const string PackagedContextParameter = "PackagedContext"; public const string AICLIPathParameter = "AICLIPath"; @@ -90,7 +99,7 @@ namespace AppInstallerCLIE2ETests // Package dir public const string PortableExePackageDirName = $"{PortableExePackageId}_{TestSourceIdentifier}"; - public const string PortableExeWithCommandPackageDirName = $"{PortableExeWithCommandPackageId}_{TestSourceIdentifier}"; + public const string PortableExeWithCommandPackageDirName = $"{PortableExeWithCommandPackageId}_{TestSourceIdentifier}"; // Registry keys public const string WinGetPackageIdentifier = "WinGetPackageIdentifier"; @@ -103,6 +112,9 @@ namespace AppInstallerCLIE2ETests public const string PortablePackageUserRoot = "portablePackageUserRoot"; public const string PortablePackageMachineRoot = "portablePackageMachineRoot"; + /// <summary> + /// Error codes. + /// </summary> public class ErrorCode { public const int S_OK = 0; @@ -240,5 +252,8 @@ namespace AppInstallerCLIE2ETests public const int INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK = unchecked((int)0x0A150206); public const int INSTALLED_STATUS_FILE_ACCESS_ERROR = unchecked((int)0x8A150207); } + +#pragma warning restore SA1310 // Field names should not contain underscore +#pragma warning restore SA1600 // ElementsMustBeDocumented } } diff --git a/src/AppInstallerCLIE2ETests/FeaturesCommand.cs b/src/AppInstallerCLIE2ETests/FeaturesCommand.cs @@ -1,41 +1,59 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// ----------------------------------------------------------------------------- +// <copyright file="FeaturesCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- namespace AppInstallerCLIE2ETests { using NUnit.Framework; + /// <summary> + /// Features command tests. + /// </summary> public class FeaturesCommand : BaseCommand { + /// <summary> + /// Set up. + /// </summary> [SetUp] public void Setup() { - InitializeAllFeatures(false); + this.InitializeAllFeatures(false); } + /// <summary> + /// Tear down. + /// </summary> [TearDown] public void TearDown() { - InitializeAllFeatures(false); + this.InitializeAllFeatures(false); } + /// <summary> + /// Tests winget features. + /// </summary> [Test] public void DisplayFeatures() { - var result = TestCommon.RunAICLICommand("features", ""); + var result = TestCommon.RunAICLICommand("features", string.Empty); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Direct MSI Installation")); Assert.False(result.StdOut.Contains("Enabled")); } + /// <summary> + /// Tests enabled winget features. + /// </summary> [Test] public void EnableExperimentalFeatures() { - ConfigureFeature("experimentalArg", true); - ConfigureFeature("experimentalCmd", true); - ConfigureFeature("directMSI", true); - ConfigureFeature("openLogsArgument", true); - var result = TestCommon.RunAICLICommand("features", ""); + this.ConfigureFeature("experimentalArg", true); + this.ConfigureFeature("experimentalCmd", true); + this.ConfigureFeature("directMSI", true); + this.ConfigureFeature("openLogsArgument", true); + var result = TestCommon.RunAICLICommand("features", string.Empty); Assert.True(result.StdOut.Contains("Enabled")); } } diff --git a/src/AppInstallerCLIE2ETests/GroupPolicy.cs b/src/AppInstallerCLIE2ETests/GroupPolicy.cs @@ -1,30 +1,42 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using NUnit.Framework; +// ----------------------------------------------------------------------------- +// <copyright file="GroupPolicy.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- namespace AppInstallerCLIE2ETests { + using NUnit.Framework; + /// <summary> /// Tests for enforcement of Group Policy. /// Behavior is better tested in the unit tests; these tests mostly ensure match between the code and the definition. /// </summary> public class GroupPolicy : BaseCommand { + /// <summary> + /// Set up. + /// </summary> [SetUp] public void Setup() { - InitializeAllFeatures(false); + this.InitializeAllFeatures(false); GroupPolicyHelper.DeleteExistingPolicies(); } + /// <summary> + /// Tear down. + /// </summary> [TearDown] public void TearDown() { - InitializeAllFeatures(false); + this.InitializeAllFeatures(false); GroupPolicyHelper.DeleteExistingPolicies(); } + /// <summary> + /// Test winget search is disabled by policy. + /// </summary> [Test] public void PolicyEnableWinget() { @@ -33,6 +45,9 @@ namespace AppInstallerCLIE2ETests Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); } + /// <summary> + /// Test winget settings is disable by policy. + /// </summary> [Test] public void EnableSettings() { @@ -41,10 +56,13 @@ namespace AppInstallerCLIE2ETests Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); } + /// <summary> + /// Test experimental features policy. + /// </summary> [Test] public void EnableExperimentalFeatures() { - ConfigureFeature("experimentalCmd", true); + this.ConfigureFeature("experimentalCmd", true); var result = TestCommon.RunAICLICommand("experimental", string.Empty); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); @@ -55,6 +73,9 @@ namespace AppInstallerCLIE2ETests Assert.AreEqual(Constants.ErrorCode.ERROR_INVALID_CL_ARGUMENTS, result.ExitCode); } + /// <summary> + /// Test install via manifest is disabled by policy. + /// </summary> [Test] public void EnableLocalManifests() { @@ -63,6 +84,9 @@ namespace AppInstallerCLIE2ETests Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); } + /// <summary> + /// Test install without checking the hash is disabled by policy. + /// </summary> [Test] public void EnableHashOverride() { @@ -71,6 +95,9 @@ namespace AppInstallerCLIE2ETests Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); } + /// <summary> + /// Test winget source is enabled by policy. + /// </summary> [Test] public void EnableDefaultSource() { @@ -83,6 +110,9 @@ namespace AppInstallerCLIE2ETests Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); } + /// <summary> + /// Test store source is enabled by policy. + /// </summary> [Test] public void EnableMicrosoftStoreSource() { @@ -95,6 +125,9 @@ namespace AppInstallerCLIE2ETests Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); } + /// <summary> + /// Test additional sources are enabled by policy. + /// </summary> [Test] public void EnableAdditionalSources() { @@ -112,6 +145,9 @@ namespace AppInstallerCLIE2ETests Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); } + /// <summary> + /// Test enable allowed sources. + /// </summary> [Test] public void EnableAllowedSources() { @@ -140,6 +176,9 @@ namespace AppInstallerCLIE2ETests Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); } + /// <summary> + /// Tests source auto update policy. + /// </summary> [Test] public void SourceAutoUpdateInterval() { diff --git a/src/AppInstallerCLIE2ETests/GroupPolicyHelper.cs b/src/AppInstallerCLIE2ETests/GroupPolicyHelper.cs @@ -1,17 +1,20 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Xml.Linq; -using Microsoft.Win32; -using Newtonsoft.Json; -using NUnit.Framework; +// ----------------------------------------------------------------------------- +// <copyright file="GroupPolicyHelper.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- namespace AppInstallerCLIE2ETests { + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Xml.Linq; + using Microsoft.Win32; + using Newtonsoft.Json; + using NUnit.Framework; + /// <summary> /// Helper for setting Group Policy settings. /// This helper reads the keys and values to use directly from the ADMX file to ensure that the names @@ -31,59 +34,80 @@ namespace AppInstallerCLIE2ETests }); /// <summary> - /// Names of the XML elements and attributes that make up the definition file. + /// Name of the policy. Used to identify it in the file. /// </summary> - private static class XmlNames + private string name; + + /// <summary> + /// ID of the value element of this policy (if it has one). + /// This assumes that each policy has a single value element. + /// </summary> + private string elementId; + + private GroupPolicyHelper(string name) { - private const string Namespace = "http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions"; + this.name = name; + } - // Root element - public static readonly XName PolicyDefinitions = XName.Get("policyDefinitions", Namespace); + private GroupPolicyHelper(string name, string elementId) + { + this.name = name; + this.elementId = elementId; + } - public static readonly XName Policies = XName.Get("policies", Namespace); - public static readonly XName Policy = XName.Get("policy", Namespace); + // Policies available. - public static readonly XName EnabledValue = XName.Get("enabledValue", Namespace); - public static readonly XName DisabledValue = XName.Get("disabledValue", Namespace); - public static readonly XName Elements = XName.Get("elements", Namespace); + /// <summary> + /// Gets the Enable winget policy. + /// </summary> + public static GroupPolicyHelper EnableWinget { get; private set; } = new GroupPolicyHelper("EnableAppInstaller"); - public static readonly XName Decimal = XName.Get("decimal", Namespace); - public static readonly XName List = XName.Get("list", Namespace); + /// <summary> + /// Gets the Enable settings policy. + /// </summary> + public static GroupPolicyHelper EnableSettings { get; private set; } = new GroupPolicyHelper("EnableSettings"); - public static class Attributes - { - public const string Name = "name"; - public const string Value = "value"; - public const string Id = "id"; - public const string Key = "key"; - public const string ValueName = "valueName"; - } - } + /// <summary> + /// Gets the Enable experimental features policy. + /// </summary> + public static GroupPolicyHelper EnableExperimentalFeatures { get; private set; } = new GroupPolicyHelper("EnableExperimentalFeatures"); /// <summary> - /// Name of the policy. Used to identify it in the file. + /// Gets the Enable local manifest policy. /// </summary> - private string name; + public static GroupPolicyHelper EnableLocalManifests { get; private set; } = new GroupPolicyHelper("EnableLocalManifestFiles"); /// <summary> - /// ID of the value element of this policy (if it has one). - /// This assumes that each policy has a single value element. + /// Gets the Enable hash override policy. /// </summary> - private string elementId; + public static GroupPolicyHelper EnableHashOverride { get; private set; } = new GroupPolicyHelper("EnableHashOverride"); - // Policies available. - public static GroupPolicyHelper EnableWinget = new GroupPolicyHelper("EnableAppInstaller"); - public static GroupPolicyHelper EnableSettings = new GroupPolicyHelper("EnableSettings"); - public static GroupPolicyHelper EnableExperimentalFeatures = new GroupPolicyHelper("EnableExperimentalFeatures"); - public static GroupPolicyHelper EnableLocalManifests = new GroupPolicyHelper("EnableLocalManifestFiles"); - public static GroupPolicyHelper EnableHashOverride = new GroupPolicyHelper("EnableHashOverride"); - public static GroupPolicyHelper EnableDefaultSource = new GroupPolicyHelper("EnableDefaultSource"); - public static GroupPolicyHelper EnableMicrosoftStoreSource = new GroupPolicyHelper("EnableMicrosoftStoreSource"); - public static GroupPolicyHelper EnableAdditionalSources = new GroupPolicyHelper("EnableAdditionalSources", "AdditionalSources"); - public static GroupPolicyHelper EnableAllowedSources = new GroupPolicyHelper("EnableAllowedSources", "AllowedSources"); - public static GroupPolicyHelper SourceAutoUpdateInterval = new GroupPolicyHelper("SourceAutoUpdateInterval", "SourceAutoUpdateInterval"); - - private static GroupPolicyHelper[] AllPolicies = new GroupPolicyHelper[] + /// <summary> + /// Gets the Enable default source policy. + /// </summary> + public static GroupPolicyHelper EnableDefaultSource { get; private set; } = new GroupPolicyHelper("EnableDefaultSource"); + + /// <summary> + /// Gets the Enable store source policy. + /// </summary> + public static GroupPolicyHelper EnableMicrosoftStoreSource { get; private set; } = new GroupPolicyHelper("EnableMicrosoftStoreSource"); + + /// <summary> + /// Gets the Enable additional sources policy. + /// </summary> + public static GroupPolicyHelper EnableAdditionalSources { get; private set; } = new GroupPolicyHelper("EnableAdditionalSources", "AdditionalSources"); + + /// <summary> + /// Gets the Enable allowed sources policy. + /// </summary> + public static GroupPolicyHelper EnableAllowedSources { get; private set; } = new GroupPolicyHelper("EnableAllowedSources", "AllowedSources"); + + /// <summary> + /// Gets the Enable auto update interval policy. + /// </summary> + public static GroupPolicyHelper SourceAutoUpdateInterval { get; private set; } = new GroupPolicyHelper("SourceAutoUpdateInterval", "SourceAutoUpdateInterval"); + + private static GroupPolicyHelper[] AllPolicies { get; set; } = new GroupPolicyHelper[] { EnableWinget, EnableSettings, @@ -97,17 +121,6 @@ namespace AppInstallerCLIE2ETests SourceAutoUpdateInterval, }; - private GroupPolicyHelper(string name) - { - this.name = name; - } - - private GroupPolicyHelper(string name, string elementId) - { - this.name = name; - this.elementId = elementId; - } - /// <summary> /// Gets the content of the ADMX file as an XML. /// </summary> @@ -271,7 +284,7 @@ namespace AppInstallerCLIE2ETests int index = 0; foreach (string value in values) { - TestContext.Out.WriteLine($"Setting {name} list value: {value}"); + TestContext.Out.WriteLine($"Setting {this.name} list value: {value}"); listKey.SetValue(index++.ToString(), value); } @@ -286,7 +299,7 @@ namespace AppInstallerCLIE2ETests /// <param name="values">Values to set in the list.</param> public void SetEnabledList(IEnumerable<GroupPolicySource> values) { - SetEnabledList(values.Select(source => JsonConvert.SerializeObject(source))); + this.SetEnabledList(values.Select(source => JsonConvert.SerializeObject(source))); } /// <summary> @@ -312,35 +325,109 @@ namespace AppInstallerCLIE2ETests { return Registry.LocalMachine.CreateSubKey(this.KeyPath); } - } - /// <summary> - /// A group policy source object as used by AdditionalSources and AllowedSources. - /// </summary> - public class GroupPolicySource - { - public string Name { get; set; } - public string Arg { get; set; } - public string Type { get; set; } - public string Data { get; set; } - public string Identifier { get; set; } - public GroupPolicyCertificatePinning CertificatePinning { get; set; } - } + /// <summary> + /// A group policy source object as used by AdditionalSources and AllowedSources. + /// </summary> + public class GroupPolicySource + { + /// <summary> + /// Gets or sets the source name. + /// </summary> + public string Name { get; set; } + + /// <summary> + /// Gets or sets the source arg. + /// </summary> + public string Arg { get; set; } + + /// <summary> + /// Gets or sets the source type. + /// </summary> + public string Type { get; set; } + + /// <summary> + /// Gets or sets the source data. + /// </summary> + public string Data { get; set; } + + /// <summary> + /// Gets or sets the source identifier. + /// </summary> + public string Identifier { get; set; } + + /// <summary> + /// Gets or sets certificate pinning. + /// </summary> + public GroupPolicyCertificatePinning CertificatePinning { get; set; } + } - public class GroupPolicyCertificatePinning - { - public GroupPolicyCertificatePinningChain[] Chains { get; set; } - } + /// <summary> + /// Group policy certificate pinning. + /// </summary> + public class GroupPolicyCertificatePinning + { + /// <summary> + /// Gets or sets the cert pinning chains. + /// </summary> + public GroupPolicyCertificatePinningChain[] Chains { get; set; } + } - public class GroupPolicyCertificatePinningChain - { - public GroupPolicyCertificatePinningDetails[] Chain { get; set; } - } + /// <summary> + /// Group policy certificate pinning chain. + /// </summary> + public class GroupPolicyCertificatePinningChain + { + /// <summary> + /// Gets or sets the cert pinning details. + /// </summary> + public GroupPolicyCertificatePinningDetails[] Chain { get; set; } + } - public class GroupPolicyCertificatePinningDetails - { - public string[] Validation { get; set; } - public string EmbeddedCertificate { get; set; } - } + /// <summary> + /// Group policy certificate pinning details. + /// </summary> + public class GroupPolicyCertificatePinningDetails + { + /// <summary> + /// Gets or sets the validation. + /// </summary> + public string[] Validation { get; set; } + + /// <summary> + /// Gets or sets the embedded cert. + /// </summary> + public string EmbeddedCertificate { get; set; } + } + /// <summary> + /// Names of the XML elements and attributes that make up the definition file. + /// </summary> + private static class XmlNames + { + // Root element + public static readonly XName PolicyDefinitions = XName.Get("policyDefinitions", Namespace); + + public static readonly XName Policies = XName.Get("policies", Namespace); + public static readonly XName Policy = XName.Get("policy", Namespace); + + public static readonly XName EnabledValue = XName.Get("enabledValue", Namespace); + public static readonly XName DisabledValue = XName.Get("disabledValue", Namespace); + public static readonly XName Elements = XName.Get("elements", Namespace); + + public static readonly XName Decimal = XName.Get("decimal", Namespace); + public static readonly XName List = XName.Get("list", Namespace); + + private const string Namespace = "http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions"; + + public static class Attributes + { + public const string Name = "name"; + public const string Value = "value"; + public const string Id = "id"; + public const string Key = "key"; + public const string ValueName = "valueName"; + } + } + } } diff --git a/src/AppInstallerCLIE2ETests/HashCommand.cs b/src/AppInstallerCLIE2ETests/HashCommand.cs @@ -1,13 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// ----------------------------------------------------------------------------- +// <copyright file="HashCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- namespace AppInstallerCLIE2ETests { using NUnit.Framework; using NUnit.Framework.Internal; + /// <summary> + /// Test hash command. + /// </summary> public class HashCommand : BaseCommand { + /// <summary> + /// Test hash file. + /// </summary> [Test] public void HashFile() { @@ -16,6 +25,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("9b4c49ad7e47afd97d2e666e93347745e1647c55f1a7ebba6d31b7dd5f69ee68")); } + /// <summary> + /// Test hash msix. + /// </summary> [Test] public void HashMSIX() { @@ -25,6 +37,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("223b318c4b1154a1fb72b1bc23422810faa5ce899a8e774ba2a02834b2058f00")); } + /// <summary> + /// Test hash invalid msix. + /// </summary> [Test] public void HashInvalidMSIX() { @@ -34,6 +49,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("Please verify that the input file is a valid, signed MSIX.")); } + /// <summary> + /// Test hash file not found. + /// </summary> [Test] public void HashFileNotFound() { diff --git a/src/AppInstallerCLIE2ETests/ImportCommand.cs b/src/AppInstallerCLIE2ETests/ImportCommand.cs @@ -1,87 +1,119 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - +// ----------------------------------------------------------------------------- +// <copyright file="ImportCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests { using System.IO; using NUnit.Framework; + /// <summary> + /// Import command tests. + /// </summary> public class ImportCommand : BaseCommand { + /// <summary> + /// Set up. + /// </summary> [SetUp] public void Setup() { - CleanupTestExe(); + this.CleanupTestExe(); } + /// <summary> + /// Test import v1. + /// </summary> [Test] public void ImportSuccessful_1_0() { - var result = TestCommon.RunAICLICommand("import", GetTestImportFile("ImportFile-Good.1.0.json")); + var result = TestCommon.RunAICLICommand("import", this.GetTestImportFile("ImportFile-Good.1.0.json")); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(VerifyTestExeInstalled()); - UninstallTestExe(); + Assert.True(this.VerifyTestExeInstalled()); + this.UninstallTestExe(); } + /// <summary> + /// Test import v2. + /// </summary> [Test] public void ImportSuccessful_2_0() { - var result = TestCommon.RunAICLICommand("import", GetTestImportFile("ImportFile-Good.2.0.json")); + var result = TestCommon.RunAICLICommand("import", this.GetTestImportFile("ImportFile-Good.2.0.json")); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(VerifyTestExeInstalled()); - UninstallTestExe(); + Assert.True(this.VerifyTestExeInstalled()); + this.UninstallTestExe(); } - // Ignore while we don't have schema validation + /// <summary> + /// Test import invalid file. + /// </summary> [Test] public void ImportInvalidFile() { // Verify failure when trying to import with an invalid file - var result = TestCommon.RunAICLICommand("import", GetTestImportFile("ImportFile-Bad-Invalid.json")); + var result = TestCommon.RunAICLICommand("import", this.GetTestImportFile("ImportFile-Bad-Invalid.json")); Assert.AreEqual(Constants.ErrorCode.ERROR_JSON_INVALID_FILE, result.ExitCode); Assert.True(result.StdOut.Contains("JSON file is not valid")); } + /// <summary> + /// Test import from an unknown source. + /// </summary> [Test] public void ImportUnknownSource() { // Verify failure when trying to import from an unknown source - var result = TestCommon.RunAICLICommand("import", GetTestImportFile("ImportFile-Bad-UnknownSource.json")); + var result = TestCommon.RunAICLICommand("import", this.GetTestImportFile("ImportFile-Bad-UnknownSource.json")); Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); Assert.True(result.StdOut.Contains("Source required for import is not installed")); } + /// <summary> + /// Test import for an unknown package. + /// </summary> [Test] public void ImportUnavailablePackage() { // Verify failure when trying to import an unavailable package - var result = TestCommon.RunAICLICommand("import", GetTestImportFile("ImportFile-Bad-UnknownPackage.json")); + var result = TestCommon.RunAICLICommand("import", this.GetTestImportFile("ImportFile-Bad-UnknownPackage.json")); Assert.AreEqual(Constants.ErrorCode.ERROR_NOT_ALL_PACKAGES_FOUND, result.ExitCode); Assert.True(result.StdOut.Contains("Package not found for import")); } + /// <summary> + /// Test import when the package version is not present. + /// </summary> [Test] public void ImportUnavailableVersion() { // Verify failure when trying to import an unavailable package - var result = TestCommon.RunAICLICommand("import", GetTestImportFile("ImportFile-Bad-UnknownPackageVersion.json")); + var result = TestCommon.RunAICLICommand("import", this.GetTestImportFile("ImportFile-Bad-UnknownPackageVersion.json")); Assert.AreEqual(Constants.ErrorCode.ERROR_NOT_ALL_PACKAGES_FOUND, result.ExitCode); Assert.True(result.StdOut.Contains("Package not found for import")); } + /// <summary> + /// Test import when the package is already installed. + /// </summary> [Test] public void ImportAlreadyInstalled() { // Verify success with message when trying to import a package that is already installed var installDir = TestCommon.GetRandomTestDir(); TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller -l {installDir}"); - var result = TestCommon.RunAICLICommand("import", $"{GetTestImportFile("ImportFile-Good.1.0.json")}"); + var result = TestCommon.RunAICLICommand("import", $"{this.GetTestImportFile("ImportFile-Good.1.0.json")}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Package is already installed")); - Assert.False(VerifyTestExeInstalled()); - UninstallTestExe(); + Assert.False(this.VerifyTestExeInstalled()); + this.UninstallTestExe(); } + /// <summary> + /// Test Import with an exported file. + /// </summary> [Test] public void ImportExportedFile() { @@ -93,13 +125,13 @@ namespace AppInstallerCLIE2ETests TestCommon.RunAICLICommand("export", $"{jsonFile} -s TestSource"); // Uninstall the package to ensure we can install it again - UninstallTestExe(); + this.UninstallTestExe(); // Import the file var result = TestCommon.RunAICLICommand("import", jsonFile); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(VerifyTestExeInstalled()); - UninstallTestExe(); + Assert.True(this.VerifyTestExeInstalled()); + this.UninstallTestExe(); } private string GetTestImportFile(string importFileName) @@ -125,7 +157,7 @@ namespace AppInstallerCLIE2ETests private void CleanupTestExe() { - UninstallTestExe(); + this.UninstallTestExe(); File.Delete(Path.Combine(Path.GetTempPath(), Constants.TestExeInstalledFileName)); File.Delete(Path.Combine(Path.GetTempPath(), Constants.TestExeUninstallerFileName)); } diff --git a/src/AppInstallerCLIE2ETests/InstallCommand.cs b/src/AppInstallerCLIE2ETests/InstallCommand.cs @@ -1,19 +1,31 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// ----------------------------------------------------------------------------- +// <copyright file="InstallCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- namespace AppInstallerCLIE2ETests { - using NUnit.Framework; using System.IO; + using NUnit.Framework; + /// <summary> + /// Test install command. + /// </summary> public class InstallCommand : BaseCommand { + /// <summary> + /// One time setup. + /// </summary> [OneTimeSetUp] public void OneTimeSetup() { - ConfigureFeature("zipInstall", true); + this.ConfigureFeature("zipInstall", true); } + /// <summary> + /// Set up. + /// </summary> [SetUp] public void Setup() { @@ -21,6 +33,9 @@ namespace AppInstallerCLIE2ETests TestCommon.RunAICLICommand("uninstall", "AppInstallerTest.TestExeInstaller"); } + /// <summary> + /// Test package doesn't exist. + /// </summary> [Test] public void InstallAppDoesNotExist() { @@ -29,6 +44,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("No package found matching input criteria.")); } + /// <summary> + /// Test multiple matches found. + /// </summary> [Test] public void InstallWithMultipleAppsMatchingQuery() { @@ -37,6 +55,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("Multiple packages found matching input criteria. Please refine the input.")); } + /// <summary> + /// Test install exe. + /// </summary> [Test] public void InstallExe() { @@ -47,16 +68,23 @@ namespace AppInstallerCLIE2ETests Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/execustom")); } + /// <summary> + /// Test inapplicable os version. + /// </summary> [Test] public void InstallExeWithInsufficientMinOsVersion() { var installDir = TestCommon.GetRandomTestDir(); var result = TestCommon.RunAICLICommand("install", $"InapplicableOsVersion --silent -l {installDir}"); + // MinOSVersion is moved to installer level, the check is performed during installer selection Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICABLE_INSTALLER, result.ExitCode); Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); } + /// <summary> + /// Test install exe hash mismatch. + /// </summary> [Test] public void InstallExeWithHashMismatch() { @@ -67,6 +95,9 @@ namespace AppInstallerCLIE2ETests Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); } + /// <summary> + /// Test install inno. + /// </summary> [Test] public void InstallWithInno() { @@ -78,6 +109,9 @@ namespace AppInstallerCLIE2ETests Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/VERYSILENT")); } + /// <summary> + /// Test install burn. + /// </summary> [Test] public void InstallBurn() { @@ -89,6 +123,9 @@ namespace AppInstallerCLIE2ETests Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/quiet")); } + /// <summary> + /// Test install nullsoft. + /// </summary> [Test] public void InstallNullSoft() { @@ -100,6 +137,9 @@ namespace AppInstallerCLIE2ETests Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/S")); } + /// <summary> + /// Test install msi. + /// </summary> [Test] public void InstallMSI() { @@ -110,6 +150,9 @@ namespace AppInstallerCLIE2ETests Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(installDir)); } + /// <summary> + /// Test install msix. + /// </summary> [Test] public void InstallMSIX() { @@ -119,6 +162,9 @@ namespace AppInstallerCLIE2ETests Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); } + /// <summary> + /// Test install msix with signature hash. + /// </summary> [Test] public void InstallMSIXWithSignature() { @@ -129,6 +175,9 @@ namespace AppInstallerCLIE2ETests Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); } + /// <summary> + /// Test msix hash mismatch. + /// </summary> [Test] public void InstallMSIXWithSignatureHashMismatch() { @@ -138,6 +187,9 @@ namespace AppInstallerCLIE2ETests Assert.False(TestCommon.VerifyTestMsixInstalledAndCleanup()); } + /// <summary> + /// Test install with alternate source failure. + /// </summary> [Test] public void InstallExeWithAlternateSourceFailure() { @@ -155,10 +207,13 @@ namespace AppInstallerCLIE2ETests } finally { - ResetTestSource(); + this.ResetTestSource(); } } + /// <summary> + /// Test install portable package. + /// </summary> [Test] public void InstallPortableExe() { @@ -171,10 +226,14 @@ namespace AppInstallerCLIE2ETests var result = TestCommon.RunAICLICommand("install", $"{packageId}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully installed")); + // If no location specified, default behavior is to create a package directory with the name "{packageId}_{sourceId}" TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); } + /// <summary> + /// Test install portable package with command. + /// </summary> [Test] public void InstallPortableExeWithCommand() { @@ -191,6 +250,9 @@ namespace AppInstallerCLIE2ETests TestCommon.VerifyPortablePackage(installDir, commandAlias, fileName, productCode, true); } + /// <summary> + /// Test install portable package with rename. + /// </summary> [Test] public void InstallPortableExeWithRename() { @@ -206,6 +268,9 @@ namespace AppInstallerCLIE2ETests TestCommon.VerifyPortablePackage(installDir, renameArgValue, renameArgValue, productCode, true); } + /// <summary> + /// Test install portable package invalid rename. + /// </summary> [Test] public void InstallPortableInvalidRename() { @@ -219,6 +284,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("The specified filename is not a valid filename")); } + /// <summary> + /// Test install portable package with reserve names. + /// </summary> [Test] public void InstallPortableReservedNames() { @@ -232,6 +300,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("The specified filename is not a valid filename")); } + /// <summary> + /// Test install portable package to an existing directory. + /// </summary> [Test] public void InstallPortableToExistingDirectory() { @@ -250,6 +321,9 @@ namespace AppInstallerCLIE2ETests TestCommon.VerifyPortablePackage(existingDir, commandAlias, fileName, productCode, true); } + /// <summary> + /// Test install portable package. Symlink is a directory. + /// </summary> [Test] public void InstallPortableFailsWithCleanup() { @@ -272,6 +346,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("Unable to create symlink, path points to a directory.")); } + /// <summary> + /// Test reinstalling portable package. + /// </summary> [Test] public void ReinstallPortable() { @@ -299,11 +376,14 @@ namespace AppInstallerCLIE2ETests TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); } + /// <summary> + /// Test installing portable package user scope. + /// </summary> [Test] public void InstallPortable_UserScope() { string installDir = TestCommon.GetRandomTestDir(); - ConfigureInstallBehavior(Constants.PortablePackageUserRoot, installDir); + this.ConfigureInstallBehavior(Constants.PortablePackageUserRoot, installDir); string packageId, commandAlias, fileName, packageDirName, productCode; packageId = "AppInstallerTest.TestPortableExe"; @@ -311,17 +391,20 @@ namespace AppInstallerCLIE2ETests commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; var result = TestCommon.RunAICLICommand("install", $"{packageId} --scope user"); - ConfigureInstallBehavior(Constants.PortablePackageUserRoot, string.Empty); + this.ConfigureInstallBehavior(Constants.PortablePackageUserRoot, string.Empty); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully installed")); TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); } + /// <summary> + /// Test install portable package machine scope. + /// </summary> [Test] public void InstallPortable_MachineScope() { string installDir = TestCommon.GetRandomTestDir(); - ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, installDir); + this.ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, installDir); string packageId, commandAlias, fileName, packageDirName, productCode; packageId = "AppInstallerTest.TestPortableExe"; @@ -329,12 +412,15 @@ namespace AppInstallerCLIE2ETests commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; var result = TestCommon.RunAICLICommand("install", $"{packageId} --scope machine"); - ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, string.Empty); + this.ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, string.Empty); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully installed")); TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true, TestCommon.Scope.Machine); } + /// <summary> + /// Test install zip exe. + /// </summary> [Test] public void InstallZip_Exe() { @@ -345,6 +431,9 @@ namespace AppInstallerCLIE2ETests Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/execustom")); } + /// <summary> + /// Test install zip portable. + /// </summary> [Test] public void InstallZip_Portable() { @@ -361,6 +450,9 @@ namespace AppInstallerCLIE2ETests TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true, TestCommon.Scope.User); } + /// <summary> + /// Test install zip with invalid relative file path. + /// </summary> [Test] public void InstallZipWithInvalidRelativeFilePath() { @@ -369,6 +461,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("Invalid relative file path to the nested installer; path points to a location outside of the install directory")); } + /// <summary> + /// Test install zip msi. + /// </summary> [Test] public void InstallZipWithMsi() { @@ -379,6 +474,9 @@ namespace AppInstallerCLIE2ETests Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(installDir)); } + /// <summary> + /// Test install zip msix. + /// </summary> [Test] public void InstallZipWithMsix() { @@ -388,6 +486,9 @@ namespace AppInstallerCLIE2ETests Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); } + /// <summary> + /// Test install an installed package and convert to upgrade. + /// </summary> [Test] public void InstallExeFoundExistingConvertToUpgrade() { @@ -407,6 +508,9 @@ namespace AppInstallerCLIE2ETests Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(upgradeDir, "/Version 2.0.0.0")); } + /// <summary> + /// Test install an installed package without an available upgrade. + /// </summary> [Test] public void InstallExeFoundExistingConvertToUpgradeNoAvailableUpgrade() { @@ -425,6 +529,9 @@ namespace AppInstallerCLIE2ETests Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(baseDir)); } + /// <summary> + /// Test force installing a package. + /// </summary> [Test] public void InstallExeWithLatestInstalledWithForce() { diff --git a/src/AppInstallerCLIE2ETests/Interop/BaseInterop.cs b/src/AppInstallerCLIE2ETests/Interop/BaseInterop.cs @@ -1,31 +1,63 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - +// ----------------------------------------------------------------------------- +// <copyright file="BaseInterop.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.Interop { + using System.Collections.Generic; + using System.Linq; using Microsoft.Management.Deployment; using Microsoft.Management.Deployment.Projection; using NUnit.Framework; - using System.Collections.Generic; - using System.Linq; - public class BaseInterop + /// <summary> + /// Base interop class. + /// </summary> + public abstract class BaseInterop { - public WinGetProjectionFactory TestFactory { get; } - + /// <summary> + /// Initializes a new instance of the <see cref="BaseInterop"/> class. + /// </summary> + /// <param name="initializer">Initializer.</param> public BaseInterop(IInstanceInitializer initializer) { - TestFactory = new(initializer); + this.TestFactory = new (initializer); + } + + /// <summary> + /// Gets the test factory. + /// </summary> + public WinGetProjectionFactory TestFactory { get; } + + /// <summary> + /// Find one filtered package from a provided package catalog reference. + /// </summary> + /// <param name="packageCatalogReference">Package catalog reference.</param> + /// <param name="field">Package match field.</param> + /// <param name="option">Package field match option.</param> + /// <param name="value">Package match value.</param> + /// <returns>List of matches.</returns> + public MatchResult FindOnePackage( + PackageCatalogReference packageCatalogReference, + PackageMatchField field, + PackageFieldMatchOption option, + string value) + { + var findPackages = this.FindAllPackages(packageCatalogReference, field, option, value); + Assert.AreEqual(1, findPackages.Count, $"Expected exactly one package but found {findPackages.Count}"); + return findPackages.First(); } /// <summary> /// Find all filtered package from a provided package catalog reference. /// </summary> - /// <param name="packageCatalogReference">Package catalog reference</param> - /// <param name="field">Package match field</param> - /// <param name="option">Package field match option</param> - /// <param name="value">Package match value</param> - /// <returns>List of matches</returns> + /// <param name="packageCatalogReference">Package catalog reference.</param> + /// <param name="field">Package match field.</param> + /// <param name="option">Package field match option.</param> + /// <param name="value">Package match value.</param> + /// <returns>List of matches.</returns> protected IReadOnlyList<MatchResult> FindAllPackages( PackageCatalogReference packageCatalogReference, PackageMatchField field, @@ -35,37 +67,18 @@ namespace AppInstallerCLIE2ETests.Interop Assert.NotNull(packageCatalogReference, "Package catalog reference cannot be null"); // Prepare filter - var filter = TestFactory.CreatePackageMatchFilter(); + var filter = this.TestFactory.CreatePackageMatchFilter(); filter.Field = field; filter.Option = option; filter.Value = value; // Add filter - var findPackageOptions = TestFactory.CreateFindPackagesOptions(); + var findPackageOptions = this.TestFactory.CreateFindPackagesOptions(); findPackageOptions.Filters.Add(filter); // Connect and find package var source = packageCatalogReference.Connect().PackageCatalog; return source.FindPackages(findPackageOptions).Matches; } - - /// <summary> - /// Find one filtered package from a provided package catalog reference. - /// </summary> - /// <param name="packageCatalogReference">Package catalog reference</param> - /// <param name="field">Package match field</param> - /// <param name="option">Package field match option</param> - /// <param name="value">Package match value</param> - /// <returns>List of matches</returns> - public MatchResult FindOnePackage( - PackageCatalogReference packageCatalogReference, - PackageMatchField field, - PackageFieldMatchOption option, - string value) - { - var findPackages = FindAllPackages(packageCatalogReference, field, option, value); - Assert.AreEqual(1, findPackages.Count, $"Expected exactly one package but found {findPackages.Count}"); - return findPackages.First(); - } } } diff --git a/src/AppInstallerCLIE2ETests/Interop/CheckInstalledStatusInterop.cs b/src/AppInstallerCLIE2ETests/Interop/CheckInstalledStatusInterop.cs @@ -1,17 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// ----------------------------------------------------------------------------- +// <copyright file="CheckInstalledStatusInterop.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- namespace AppInstallerCLIE2ETests.Interop { + using System; + using System.IO; + using System.Threading.Tasks; using Microsoft.Management.Deployment; using Microsoft.Management.Deployment.Projection; - using NUnit.Framework; + using NUnit.Framework; using WinRT; - using System; - using System.IO; - using System.Threading.Tasks; - - + + /// <summary> + /// Tests check installed status. + /// </summary> [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] public class CheckInstalledStatusInterop : BaseInterop @@ -21,58 +26,76 @@ namespace AppInstallerCLIE2ETests.Interop private PackageManager packageManager; private PackageCatalogReference testSource; - public CheckInstalledStatusInterop(IInstanceInitializer initializer) : base(initializer) { } + /// <summary> + /// Initializes a new instance of the <see cref="CheckInstalledStatusInterop"/> class. + /// </summary> + /// <param name="initializer">Initializer.</param> + public CheckInstalledStatusInterop(IInstanceInitializer initializer) + : base(initializer) + { + } + /// <summary> + /// Test setup. + /// </summary> [SetUp] public void SetUp() { - packageManager = TestFactory.CreatePackageManager(); - testSource = packageManager.GetPackageCatalogByName(Constants.TestSourceName); - installDir = TestCommon.GetRandomTestDir(); - } - - [TearDown] - public async Task Cleanup() + this.packageManager = this.TestFactory.CreatePackageManager(); + this.testSource = this.packageManager.GetPackageCatalogByName(Constants.TestSourceName); + this.installDir = TestCommon.GetRandomTestDir(); + } + + /// <summary> + /// Clean up. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> + [TearDown] + public async Task Cleanup() { // Find and uninstall the test package if applicable. - var options = TestFactory.CreateCreateCompositePackageCatalogOptions(); - options.Catalogs.Add(testSource); + var options = this.TestFactory.CreateCreateCompositePackageCatalogOptions(); + options.Catalogs.Add(this.testSource); options.CompositeSearchBehavior = CompositeSearchBehavior.AllCatalogs; - var compositeSource = packageManager.CreateCompositePackageCatalog(options); - var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestCheckInstalledStatus"); - if (searchResult.CatalogPackage.InstalledVersion != null) + var compositeSource = this.packageManager.CreateCompositePackageCatalog(options); + var searchResult = this.FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestCheckInstalledStatus"); + if (searchResult.CatalogPackage.InstalledVersion != null) { - var uninstallOptions = TestFactory.CreateUninstallOptions(); - var uninstallResult = await packageManager.UninstallPackageAsync(searchResult.CatalogPackage, uninstallOptions); + var uninstallOptions = this.TestFactory.CreateUninstallOptions(); + var uninstallResult = await this.packageManager.UninstallPackageAsync(searchResult.CatalogPackage, uninstallOptions); Assert.AreEqual(UninstallResultStatus.Ok, uninstallResult.Status); } // Remove default install location - if (Directory.Exists(defaultInstallDir)) + if (Directory.Exists(this.defaultInstallDir)) { - Directory.Delete(defaultInstallDir, true); + Directory.Delete(this.defaultInstallDir, true); } } + /// <summary> + /// Tests arp entries match. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> [Test] public async Task CheckInstalledStatusArpVersionMatched() { // Find and install the test package. - var searchResult = FindOnePackage(testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestCheckInstalledStatus"); - var installOptions = TestFactory.CreateInstallOptions(); - installOptions.ReplacementInstallerArguments = $"/InstallDir {installDir} /ProductID CheckInstalledStatusProductId /DisplayName TestCheckInstalledStatus /Version 1.0"; - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestCheckInstalledStatus"); + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.ReplacementInstallerArguments = $"/InstallDir {this.installDir} /ProductID CheckInstalledStatusProductId /DisplayName TestCheckInstalledStatus /Version 1.0"; + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); // Add the data file listed in the manifest - File.WriteAllText(Path.Combine(installDir, "data.txt"), "Test"); + File.WriteAllText(Path.Combine(this.installDir, "data.txt"), "Test"); // Search from composite source again after installation - var options = TestFactory.CreateCreateCompositePackageCatalogOptions(); - options.Catalogs.Add(testSource); + var options = this.TestFactory.CreateCreateCompositePackageCatalogOptions(); + options.Catalogs.Add(this.testSource); options.CompositeSearchBehavior = CompositeSearchBehavior.AllCatalogs; - var compositeSource = packageManager.CreateCompositePackageCatalog(options); - searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestCheckInstalledStatus"); + var compositeSource = this.packageManager.CreateCompositePackageCatalog(options); + searchResult = this.FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestCheckInstalledStatus"); // Check installed status var checkResult = await searchResult.CatalogPackage.CheckInstalledStatusAsync(); @@ -90,38 +113,42 @@ namespace AppInstallerCLIE2ETests.Interop Assert.AreEqual(InstalledStatusType.AppsAndFeaturesEntry, installerInstalledStatus.InstallerInstalledStatus[0].Type); Assert.AreEqual(Constants.ErrorCode.S_OK, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[0])); Assert.AreEqual(InstalledStatusType.AppsAndFeaturesEntryInstallLocation, installerInstalledStatus.InstallerInstalledStatus[1].Type); - Assert.AreEqual(installDir, installerInstalledStatus.InstallerInstalledStatus[1].Path); + Assert.AreEqual(this.installDir, installerInstalledStatus.InstallerInstalledStatus[1].Path); Assert.AreEqual(Constants.ErrorCode.S_OK, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[1])); Assert.AreEqual(InstalledStatusType.AppsAndFeaturesEntryInstallLocationFile, installerInstalledStatus.InstallerInstalledStatus[2].Type); - Assert.AreEqual(Path.Combine(installDir, "data.txt"), installerInstalledStatus.InstallerInstalledStatus[2].Path); + Assert.AreEqual(Path.Combine(this.installDir, "data.txt"), installerInstalledStatus.InstallerInstalledStatus[2].Path); Assert.AreEqual(Constants.ErrorCode.S_OK, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[2])); Assert.AreEqual(InstalledStatusType.AppsAndFeaturesEntryInstallLocationFile, installerInstalledStatus.InstallerInstalledStatus[3].Type); - Assert.AreEqual(Path.Combine(installDir, "TestExeInstalled.txt"), installerInstalledStatus.InstallerInstalledStatus[3].Path); + Assert.AreEqual(Path.Combine(this.installDir, "TestExeInstalled.txt"), installerInstalledStatus.InstallerInstalledStatus[3].Path); Assert.AreEqual(Constants.ErrorCode.INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[3])); Assert.AreEqual(InstalledStatusType.DefaultInstallLocation, installerInstalledStatus.InstallerInstalledStatus[4].Type); - Assert.AreEqual(defaultInstallDir, Path.GetFullPath(installerInstalledStatus.InstallerInstalledStatus[4].Path)); + Assert.AreEqual(this.defaultInstallDir, Path.GetFullPath(installerInstalledStatus.InstallerInstalledStatus[4].Path)); Assert.AreEqual(Constants.ErrorCode.INSTALLED_STATUS_INSTALL_LOCATION_NOT_FOUND, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[4])); } + /// <summary> + /// Test arp entries no version matched. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> [Test] public async Task CheckInstalledStatusArpVersionNotMatched() { // Find and install the test package. - var searchResult = FindOnePackage(testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestCheckInstalledStatus"); - var installOptions = TestFactory.CreateInstallOptions(); - installOptions.ReplacementInstallerArguments = $"/InstallDir {installDir} /ProductID CheckInstalledStatusProductId /DisplayName TestCheckInstalledStatus /Version 2.0"; - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestCheckInstalledStatus"); + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.ReplacementInstallerArguments = $"/InstallDir {this.installDir} /ProductID CheckInstalledStatusProductId /DisplayName TestCheckInstalledStatus /Version 2.0"; + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); // Add the data file listed in the manifest - File.WriteAllText(Path.Combine(installDir, "data.txt"), "Test"); + File.WriteAllText(Path.Combine(this.installDir, "data.txt"), "Test"); // Search from composite source again after installation - var options = TestFactory.CreateCreateCompositePackageCatalogOptions(); - options.Catalogs.Add(testSource); + var options = this.TestFactory.CreateCreateCompositePackageCatalogOptions(); + options.Catalogs.Add(this.testSource); options.CompositeSearchBehavior = CompositeSearchBehavior.AllCatalogs; - var compositeSource = packageManager.CreateCompositePackageCatalog(options); - searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestCheckInstalledStatus"); + var compositeSource = this.packageManager.CreateCompositePackageCatalog(options); + searchResult = this.FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestCheckInstalledStatus"); // Check installed status var checkResult = await searchResult.CatalogPackage.CheckInstalledStatusAsync(InstalledStatusType.AllAppsAndFeaturesEntryChecks); @@ -132,32 +159,36 @@ namespace AppInstallerCLIE2ETests.Interop Assert.AreEqual(InstalledStatusType.AppsAndFeaturesEntry, installerInstalledStatus.InstallerInstalledStatus[0].Type); Assert.AreEqual(Constants.ErrorCode.S_OK, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[0])); Assert.AreEqual(InstalledStatusType.AppsAndFeaturesEntryInstallLocation, installerInstalledStatus.InstallerInstalledStatus[1].Type); - Assert.AreEqual(installDir, installerInstalledStatus.InstallerInstalledStatus[1].Path); + Assert.AreEqual(this.installDir, installerInstalledStatus.InstallerInstalledStatus[1].Path); Assert.AreEqual(Constants.ErrorCode.S_OK, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[1])); Assert.AreEqual(InstalledStatusType.AppsAndFeaturesEntryInstallLocationFile, installerInstalledStatus.InstallerInstalledStatus[2].Type); - Assert.AreEqual(Path.Combine(installDir, "data.txt"), installerInstalledStatus.InstallerInstalledStatus[2].Path); + Assert.AreEqual(Path.Combine(this.installDir, "data.txt"), installerInstalledStatus.InstallerInstalledStatus[2].Path); Assert.AreEqual(Constants.ErrorCode.INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[2])); Assert.AreEqual(InstalledStatusType.AppsAndFeaturesEntryInstallLocationFile, installerInstalledStatus.InstallerInstalledStatus[3].Type); - Assert.AreEqual(Path.Combine(installDir, "TestExeInstalled.txt"), installerInstalledStatus.InstallerInstalledStatus[3].Path); + Assert.AreEqual(Path.Combine(this.installDir, "TestExeInstalled.txt"), installerInstalledStatus.InstallerInstalledStatus[3].Path); Assert.AreEqual(Constants.ErrorCode.INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[3])); } + /// <summary> + /// Test arp entries file not found. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> [Test] public async Task CheckInstalledStatusArpVersionMatchedFileNotFound() { // Find and install the test package. - var searchResult = FindOnePackage(testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestCheckInstalledStatus"); - var installOptions = TestFactory.CreateInstallOptions(); - installOptions.ReplacementInstallerArguments = $"/InstallDir {installDir} /ProductID CheckInstalledStatusProductId /DisplayName TestCheckInstalledStatus /Version 1.0"; - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestCheckInstalledStatus"); + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.ReplacementInstallerArguments = $"/InstallDir {this.installDir} /ProductID CheckInstalledStatusProductId /DisplayName TestCheckInstalledStatus /Version 1.0"; + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); // Search from composite source again after installation - var options = TestFactory.CreateCreateCompositePackageCatalogOptions(); - options.Catalogs.Add(testSource); + var options = this.TestFactory.CreateCreateCompositePackageCatalogOptions(); + options.Catalogs.Add(this.testSource); options.CompositeSearchBehavior = CompositeSearchBehavior.AllCatalogs; - var compositeSource = packageManager.CreateCompositePackageCatalog(options); - searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestCheckInstalledStatus"); + var compositeSource = this.packageManager.CreateCompositePackageCatalog(options); + searchResult = this.FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestCheckInstalledStatus"); // Check installed status var checkResult = await searchResult.CatalogPackage.CheckInstalledStatusAsync(InstalledStatusType.AllAppsAndFeaturesEntryChecks); @@ -168,35 +199,39 @@ namespace AppInstallerCLIE2ETests.Interop Assert.AreEqual(InstalledStatusType.AppsAndFeaturesEntry, installerInstalledStatus.InstallerInstalledStatus[0].Type); Assert.AreEqual(Constants.ErrorCode.S_OK, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[0])); Assert.AreEqual(InstalledStatusType.AppsAndFeaturesEntryInstallLocation, installerInstalledStatus.InstallerInstalledStatus[1].Type); - Assert.AreEqual(installDir, installerInstalledStatus.InstallerInstalledStatus[1].Path); + Assert.AreEqual(this.installDir, installerInstalledStatus.InstallerInstalledStatus[1].Path); Assert.AreEqual(Constants.ErrorCode.S_OK, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[1])); Assert.AreEqual(InstalledStatusType.AppsAndFeaturesEntryInstallLocationFile, installerInstalledStatus.InstallerInstalledStatus[2].Type); - Assert.AreEqual(Path.Combine(installDir, "data.txt"), installerInstalledStatus.InstallerInstalledStatus[2].Path); + Assert.AreEqual(Path.Combine(this.installDir, "data.txt"), installerInstalledStatus.InstallerInstalledStatus[2].Path); Assert.AreEqual(Constants.ErrorCode.INSTALLED_STATUS_FILE_NOT_FOUND, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[2])); Assert.AreEqual(InstalledStatusType.AppsAndFeaturesEntryInstallLocationFile, installerInstalledStatus.InstallerInstalledStatus[3].Type); - Assert.AreEqual(Path.Combine(installDir, "TestExeInstalled.txt"), installerInstalledStatus.InstallerInstalledStatus[3].Path); + Assert.AreEqual(Path.Combine(this.installDir, "TestExeInstalled.txt"), installerInstalledStatus.InstallerInstalledStatus[3].Path); Assert.AreEqual(Constants.ErrorCode.INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[3])); } + /// <summary> + /// Test arp entries hash mismatch. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> [Test] public async Task CheckInstalledStatusArpVersionMatchedFileHashMisMatch() { // Find and install the test package. - var searchResult = FindOnePackage(testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestCheckInstalledStatus"); - var installOptions = TestFactory.CreateInstallOptions(); - installOptions.ReplacementInstallerArguments = $"/InstallDir {installDir} /ProductID CheckInstalledStatusProductId /DisplayName TestCheckInstalledStatus /Version 1.0"; - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestCheckInstalledStatus"); + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.ReplacementInstallerArguments = $"/InstallDir {this.installDir} /ProductID CheckInstalledStatusProductId /DisplayName TestCheckInstalledStatus /Version 1.0"; + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); // Add the data file listed in the manifest - File.WriteAllText(Path.Combine(installDir, "data.txt"), "WrongData"); + File.WriteAllText(Path.Combine(this.installDir, "data.txt"), "WrongData"); // Search from composite source again after installation - var options = TestFactory.CreateCreateCompositePackageCatalogOptions(); - options.Catalogs.Add(testSource); + var options = this.TestFactory.CreateCreateCompositePackageCatalogOptions(); + options.Catalogs.Add(this.testSource); options.CompositeSearchBehavior = CompositeSearchBehavior.AllCatalogs; - var compositeSource = packageManager.CreateCompositePackageCatalog(options); - searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestCheckInstalledStatus"); + var compositeSource = this.packageManager.CreateCompositePackageCatalog(options); + searchResult = this.FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestCheckInstalledStatus"); // Check installed status var checkResult = await searchResult.CatalogPackage.CheckInstalledStatusAsync(InstalledStatusType.AllAppsAndFeaturesEntryChecks); @@ -207,29 +242,33 @@ namespace AppInstallerCLIE2ETests.Interop Assert.AreEqual(InstalledStatusType.AppsAndFeaturesEntry, installerInstalledStatus.InstallerInstalledStatus[0].Type); Assert.AreEqual(Constants.ErrorCode.S_OK, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[0])); Assert.AreEqual(InstalledStatusType.AppsAndFeaturesEntryInstallLocation, installerInstalledStatus.InstallerInstalledStatus[1].Type); - Assert.AreEqual(installDir, installerInstalledStatus.InstallerInstalledStatus[1].Path); + Assert.AreEqual(this.installDir, installerInstalledStatus.InstallerInstalledStatus[1].Path); Assert.AreEqual(Constants.ErrorCode.S_OK, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[1])); Assert.AreEqual(InstalledStatusType.AppsAndFeaturesEntryInstallLocationFile, installerInstalledStatus.InstallerInstalledStatus[2].Type); - Assert.AreEqual(Path.Combine(installDir, "data.txt"), installerInstalledStatus.InstallerInstalledStatus[2].Path); + Assert.AreEqual(Path.Combine(this.installDir, "data.txt"), installerInstalledStatus.InstallerInstalledStatus[2].Path); Assert.AreEqual(Constants.ErrorCode.INSTALLED_STATUS_FILE_HASH_MISMATCH, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[2])); Assert.AreEqual(InstalledStatusType.AppsAndFeaturesEntryInstallLocationFile, installerInstalledStatus.InstallerInstalledStatus[3].Type); - Assert.AreEqual(Path.Combine(installDir, "TestExeInstalled.txt"), installerInstalledStatus.InstallerInstalledStatus[3].Path); + Assert.AreEqual(Path.Combine(this.installDir, "TestExeInstalled.txt"), installerInstalledStatus.InstallerInstalledStatus[3].Path); Assert.AreEqual(Constants.ErrorCode.INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[3])); } + /// <summary> + /// Test arp entries default install location not found. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> [Test] public async Task CheckInstalledStatusArpNotFoundDefaultInstallLocationFound() { // Add the data file listed in the manifest to default install location. - Directory.CreateDirectory(defaultInstallDir); - File.WriteAllText(Path.Combine(defaultInstallDir, "data.txt"), "Test"); + Directory.CreateDirectory(this.defaultInstallDir); + File.WriteAllText(Path.Combine(this.defaultInstallDir, "data.txt"), "Test"); // Search from composite source without installation - var options = TestFactory.CreateCreateCompositePackageCatalogOptions(); - options.Catalogs.Add(testSource); + var options = this.TestFactory.CreateCreateCompositePackageCatalogOptions(); + options.Catalogs.Add(this.testSource); options.CompositeSearchBehavior = CompositeSearchBehavior.AllCatalogs; - var compositeSource = packageManager.CreateCompositePackageCatalog(options); - var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestCheckInstalledStatus"); + var compositeSource = this.packageManager.CreateCompositePackageCatalog(options); + var searchResult = this.FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestCheckInstalledStatus"); // Check installed status var checkResult = await searchResult.CatalogPackage.CheckInstalledStatusAsync(); @@ -240,30 +279,30 @@ namespace AppInstallerCLIE2ETests.Interop Assert.AreEqual(InstalledStatusType.AppsAndFeaturesEntry, installerInstalledStatus.InstallerInstalledStatus[0].Type); Assert.AreEqual(Constants.ErrorCode.INSTALLED_STATUS_ARP_ENTRY_NOT_FOUND, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[0])); Assert.AreEqual(InstalledStatusType.DefaultInstallLocation, installerInstalledStatus.InstallerInstalledStatus[1].Type); - Assert.AreEqual(defaultInstallDir, Path.GetFullPath(installerInstalledStatus.InstallerInstalledStatus[1].Path)); + Assert.AreEqual(this.defaultInstallDir, Path.GetFullPath(installerInstalledStatus.InstallerInstalledStatus[1].Path)); Assert.AreEqual(Constants.ErrorCode.S_OK, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[1])); Assert.AreEqual(InstalledStatusType.DefaultInstallLocationFile, installerInstalledStatus.InstallerInstalledStatus[2].Type); - Assert.AreEqual(Path.Combine(defaultInstallDir, "data.txt"), Path.GetFullPath(installerInstalledStatus.InstallerInstalledStatus[2].Path)); + Assert.AreEqual(Path.Combine(this.defaultInstallDir, "data.txt"), Path.GetFullPath(installerInstalledStatus.InstallerInstalledStatus[2].Path)); Assert.AreEqual(Constants.ErrorCode.INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[2])); Assert.AreEqual(InstalledStatusType.DefaultInstallLocationFile, installerInstalledStatus.InstallerInstalledStatus[3].Type); - Assert.AreEqual(Path.Combine(defaultInstallDir, "TestExeInstalled.txt"), Path.GetFullPath(installerInstalledStatus.InstallerInstalledStatus[3].Path)); + Assert.AreEqual(Path.Combine(this.defaultInstallDir, "TestExeInstalled.txt"), Path.GetFullPath(installerInstalledStatus.InstallerInstalledStatus[3].Path)); Assert.AreEqual(Constants.ErrorCode.INSTALLED_STATUS_FILE_NOT_FOUND, GetHResultFromInstalledStatus(installerInstalledStatus.InstallerInstalledStatus[3])); } // CsWinrt maps success error codes(e.g. INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE) to null exception. // In this case we cannot get the exact hresult by calling winrt projection api. // This method is created to directly get hresult from the InstalledStatus object for the tests to compare. - unsafe private static int GetHResultFromInstalledStatus(InstalledStatus status) + private static unsafe int GetHResultFromInstalledStatus(InstalledStatus status) { - if (status.Status != null) + if (status.Status != null) { return status.Status.HResult; } - else + else { - IObjectReference objRef = ((IWinRTObject)status).NativeObject; - ABI.System.Exception exception = default; - (*(delegate* unmanaged[Stdcall]<IntPtr, out global::ABI.System.Exception, int>**)objRef.ThisPtr)[8](objRef.ThisPtr, out exception); + IObjectReference objRef = ((IWinRTObject)status).NativeObject; + ABI.System.Exception exception = default; + (*(delegate* unmanaged[Stdcall] <IntPtr, out global::ABI.System.Exception, int>**)objRef.ThisPtr)[8](objRef.ThisPtr, out exception); return exception.hr; } } diff --git a/src/AppInstallerCLIE2ETests/Interop/FindPackagesInterop.cs b/src/AppInstallerCLIE2ETests/Interop/FindPackagesInterop.cs @@ -1,44 +1,66 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - +// ----------------------------------------------------------------------------- +// <copyright file="FindPackagesInterop.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.Interop { using Microsoft.Management.Deployment; using Microsoft.Management.Deployment.Projection; using NUnit.Framework; + /// <summary> + /// Test find package interop. + /// </summary> [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] public class FindPackagesInterop : BaseInterop { private PackageManager packageManager; - private PackageCatalogReference testSource; - - public FindPackagesInterop(IInstanceInitializer initializer) : base(initializer) { } + private PackageCatalogReference testSource; + + /// <summary> + /// Initializes a new instance of the <see cref="FindPackagesInterop"/> class. + /// </summary> + /// <param name="initializer">Initializer.</param> + public FindPackagesInterop(IInstanceInitializer initializer) + : base(initializer) + { + } + /// <summary> + /// Set up. + /// </summary> [SetUp] public void SetUp() { - packageManager = TestFactory.CreatePackageManager(); - testSource = packageManager.GetPackageCatalogByName(Constants.TestSourceName); + this.packageManager = this.TestFactory.CreatePackageManager(); + this.testSource = this.packageManager.GetPackageCatalogByName(Constants.TestSourceName); } + /// <summary> + /// Test find package. no package. + /// </summary> [Test] public void FindPackageDoesNotExist() { // Find package - var searchResult = FindAllPackages(testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "DoesNotExist"); - + var searchResult = this.FindAllPackages(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "DoesNotExist"); + // Assert Assert.AreEqual(0, searchResult.Count); } + /// <summary> + /// Test find package with multiple match. + /// </summary> [Test] public void FindPackagesMultipleMatchingQuery() { // Find package - var searchResult = FindAllPackages(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestExeInstaller"); - + var searchResult = this.FindAllPackages(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestExeInstaller"); + // Assert Assert.AreEqual(2, searchResult.Count); } diff --git a/src/AppInstallerCLIE2ETests/Interop/InstallInterop.cs b/src/AppInstallerCLIE2ETests/Interop/InstallInterop.cs @@ -1,146 +1,190 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - +// ----------------------------------------------------------------------------- +// <copyright file="InstallInterop.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.Interop { - using Microsoft.Management.Deployment; - using Microsoft.Management.Deployment.Projection; - using NUnit.Framework; using System; using System.IO; using System.Threading.Tasks; + using Microsoft.Management.Deployment; + using Microsoft.Management.Deployment.Projection; + using NUnit.Framework; + /// <summary> + /// Install interop. + /// </summary> [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] public class InstallInterop : BaseInterop { private string installDir; private PackageManager packageManager; - private PackageCatalogReference testSource; - - public InstallInterop(IInstanceInitializer initializer) : base(initializer) { } + private PackageCatalogReference testSource; + + /// <summary> + /// Initializes a new instance of the <see cref="InstallInterop"/> class. + /// </summary> + /// <param name="initializer">Initializer.</param> + public InstallInterop(IInstanceInitializer initializer) + : base(initializer) + { + } + /// <summary> + /// Set up. + /// </summary> [SetUp] public void SetUp() { - packageManager = TestFactory.CreatePackageManager(); - testSource = packageManager.GetPackageCatalogByName(Constants.TestSourceName); - installDir = TestCommon.GetRandomTestDir(); - } - + this.packageManager = this.TestFactory.CreatePackageManager(); + this.testSource = this.packageManager.GetPackageCatalogByName(Constants.TestSourceName); + this.installDir = TestCommon.GetRandomTestDir(); + } + + /// <summary> + /// Install exe. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task InstallExe() { // Find package - var searchResult = FindOnePackage(testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); + var installOptions = this.TestFactory.CreateInstallOptions(); installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = installDir; - + installOptions.PreferredInstallLocation = this.installDir; + // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + // Assert Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); } + /// <summary> + /// Test install with inapplicable os version. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task InstallExeWithInsufficientMinOsVersion() { // Find package - var searchResult = FindOnePackage(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "InapplicableOsVersion"); + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "InapplicableOsVersion"); // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); + var installOptions = this.TestFactory.CreateInstallOptions(); installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = installDir; - + installOptions.PreferredInstallLocation = this.installDir; + // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); // Assert Assert.AreEqual(InstallResultStatus.NoApplicableInstallers, installResult.Status); - Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); + Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); } + /// <summary> + /// Test install with hash mismatch. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task InstallExeWithHashMismatch() { // Find package - var searchResult = FindOnePackage(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestExeSha256Mismatch"); + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestExeSha256Mismatch"); // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); + var installOptions = this.TestFactory.CreateInstallOptions(); installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = installDir; + installOptions.PreferredInstallLocation = this.installDir; // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); // Assert Assert.AreEqual(InstallResultStatus.DownloadError, installResult.Status); - Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); + Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); } + /// <summary> + /// Test installing inno installer. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task InstallWithInno() { // Find package - var searchResult = FindOnePackage(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestInnoInstaller"); + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestInnoInstaller"); // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); + var installOptions = this.TestFactory.CreateInstallOptions(); installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = installDir; + installOptions.PreferredInstallLocation = this.installDir; // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); // Assert Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); } + /// <summary> + /// Test installing burn installer. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task InstallBurn() { // Find package - var searchResult = FindOnePackage(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestBurnInstaller"); - + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestBurnInstaller"); + // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); + var installOptions = this.TestFactory.CreateInstallOptions(); installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = installDir; + installOptions.PreferredInstallLocation = this.installDir; // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + // Assert Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); } + /// <summary> + /// Test installing nullsoft installer. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task InstallNullSoft() { // Find package - var searchResult = FindOnePackage(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestNullsoftInstaller"); - + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestNullsoftInstaller"); + // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); + var installOptions = this.TestFactory.CreateInstallOptions(); installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = installDir; - + installOptions.PreferredInstallLocation = this.installDir; + // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); // Assert Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); } + /// <summary> + /// Test installing msi. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task InstallMSI() { @@ -150,38 +194,46 @@ namespace AppInstallerCLIE2ETests.Interop } // Find package - var searchResult = FindOnePackage(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsiInstaller"); - + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsiInstaller"); + // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); + var installOptions = this.TestFactory.CreateInstallOptions(); installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = installDir; + installOptions.PreferredInstallLocation = this.installDir; // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + // Assert Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(installDir)); + Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(this.installDir)); } + /// <summary> + /// Test installing an msix. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task InstallMSIX() { // Find package - var searchResult = FindOnePackage(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixInstaller"); - + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixInstaller"); + // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); + var installOptions = this.TestFactory.CreateInstallOptions(); // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + // Assert Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); } + /// <summary> + /// Test installing msix with signature. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task InstallMSIXWithSignature() { @@ -190,38 +242,45 @@ namespace AppInstallerCLIE2ETests.Interop Assert.Ignore(); // Find package - var searchResult = FindOnePackage(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixWithSignatureHash"); - + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixWithSignatureHash"); + // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); + var installOptions = this.TestFactory.CreateInstallOptions(); installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = installDir; + installOptions.PreferredInstallLocation = this.installDir; // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + // Assert Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); } + /// <summary> + /// Test installing msix with signature hash mismatch. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task InstallMSIXWithSignatureHashMismatch() { // Find package - var searchResult = FindOnePackage(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixSignatureHashMismatch"); - + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixSignatureHashMismatch"); + // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); + var installOptions = this.TestFactory.CreateInstallOptions(); // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + // Assert Assert.AreEqual(InstallResultStatus.DownloadError, installResult.Status); Assert.False(TestCommon.VerifyTestMsixInstalledAndCleanup()); } + /// <summary> + /// Test installing exe. + /// </summary> [Test] public void InstallExeWithAlternateSourceFailure() { @@ -229,10 +288,10 @@ namespace AppInstallerCLIE2ETests.Interop TestCommon.RunAICLICommand("source add", "failSearch \"{ \"\"SearchHR\"\": \"\"0x80070002\"\" }\" Microsoft.Test.Configurable --header \"{}\""); // Get mock source - var failSearchSource = packageManager.GetPackageCatalogByName("failSearch"); + var failSearchSource = this.packageManager.GetPackageCatalogByName("failSearch"); // Find package - var searchResult = FindAllPackages(failSearchSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); + var searchResult = this.FindAllPackages(failSearchSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); // Assert Assert.NotNull(failSearchSource); @@ -242,6 +301,10 @@ namespace AppInstallerCLIE2ETests.Interop TestCommon.RunAICLICommand("source remove", "failSearch"); } + /// <summary> + /// Test installing portable exe. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task InstallPortableExe() { @@ -251,19 +314,23 @@ namespace AppInstallerCLIE2ETests.Interop string fileName = $"{Constants.ExeInstaller}.exe"; // Find package - var searchResult = FindOnePackage(testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); - + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); + // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); - + var installOptions = this.TestFactory.CreateInstallOptions(); + // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); // Assert Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); TestCommon.VerifyPortablePackage(Path.Combine(installDir, Constants.PortableExePackageDirName), commandAlias, fileName, productCode, true); } + /// <summary> + /// Test installing portable exe with command. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task InstallPortableExeWithCommand() { @@ -272,49 +339,57 @@ namespace AppInstallerCLIE2ETests.Interop string commandAlias = Constants.TestCommandExe; // Find package - var searchResult = FindOnePackage(testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExeWithCommandPackageId); + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExeWithCommandPackageId); // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); - installOptions.PreferredInstallLocation = installDir; - + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PreferredInstallLocation = this.installDir; + // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); // Assert Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - TestCommon.VerifyPortablePackage(installDir, commandAlias, fileName, productCode, true); + TestCommon.VerifyPortablePackage(this.installDir, commandAlias, fileName, productCode, true); } + /// <summary> + /// Test installing portable package to existing directory. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task InstallPortableToExistingDirectory() { - var existingDir = Path.Combine(installDir, "testDirectory"); + var existingDir = Path.Combine(this.installDir, "testDirectory"); Directory.CreateDirectory(existingDir); string productCode = Constants.PortableExePackageDirName; - string commandAlias = Constants.AppInstallerTestExeInstallerExe; + string commandAlias = Constants.AppInstallerTestExeInstallerExe; string fileName = Constants.AppInstallerTestExeInstallerExe; // Find package - var searchResult = FindOnePackage(testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); + var installOptions = this.TestFactory.CreateInstallOptions(); installOptions.PreferredInstallLocation = existingDir; - + // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); // Assert Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); TestCommon.VerifyPortablePackage(existingDir, commandAlias, fileName, productCode, true); } + /// <summary> + /// Test installing portable package where it fails on clean up. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task InstallPortableFailsWithCleanup() { - if (TestFactory.Context == ClsidContext.InProc) + if (this.TestFactory.Context == ClsidContext.InProc) { // Task to investigate validation error when running in-process // TODO: https://task.ms/40489822 @@ -334,13 +409,13 @@ namespace AppInstallerCLIE2ETests.Interop Directory.CreateDirectory(conflictDirectory); // Find package - var searchResult = FindOnePackage(testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); + var installOptions = this.TestFactory.CreateInstallOptions(); // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); // Assert Assert.AreEqual(InstallResultStatus.InstallError, installResult.Status); @@ -348,41 +423,47 @@ namespace AppInstallerCLIE2ETests.Interop Directory.Delete(conflictDirectory, true); } - + /// <summary> + /// Test installing a package with user scope. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task InstallRequireUserScope() { // Find package - var searchResult = FindOnePackage(testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); + var installOptions = this.TestFactory.CreateInstallOptions(); installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = installDir; + installOptions.PreferredInstallLocation = this.installDir; installOptions.PackageInstallScope = PackageInstallScope.User; // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); // Assert Assert.AreEqual(InstallResultStatus.NoApplicableInstallers, installResult.Status); } - + /// <summary> + /// Test installing package with user scope or unknown. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task InstallRequireUserScopeAndUnknown() { // Find package - var searchResult = FindOnePackage(testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); + var installOptions = this.TestFactory.CreateInstallOptions(); installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = installDir; + installOptions.PreferredInstallLocation = this.installDir; installOptions.PackageInstallScope = PackageInstallScope.UserOrUnknown; // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); // Assert Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); diff --git a/src/AppInstallerCLIE2ETests/Interop/InstanceInitializersSource.cs b/src/AppInstallerCLIE2ETests/Interop/InstanceInitializersSource.cs @@ -1,33 +1,36 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - +// ----------------------------------------------------------------------------- +// <copyright file="InstanceInitializersSource.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.Interop { using Microsoft.Management.Deployment.Projection; /// <summary> - /// Source class for running tests in-process and out-of-process + /// Source class for running tests in-process and out-of-process. /// </summary> public class InstanceInitializersSource { /// <summary> - /// List of in-process instance initializers passed as argument to the test class constructor + /// List of in-process instance initializers passed as argument to the test class constructor. /// </summary> public static readonly IInstanceInitializer[] InProcess = { - new ActivationFactoryInstanceInitializer() + new ActivationFactoryInstanceInitializer(), }; /// <summary> - /// List of out-of-process instance initializers passed as argument to the test class constructor + /// List of out-of-process instance initializers passed as argument to the test class constructor. /// </summary> public static readonly IInstanceInitializer[] OutOfProcess = { new LocalServerInstanceInitializer() { AllowLowerTrustRegistration = true, - UseDevClsids = true - } + UseDevClsids = true, + }, }; } } diff --git a/src/AppInstallerCLIE2ETests/Interop/InteropSetUpFixture.cs b/src/AppInstallerCLIE2ETests/Interop/InteropSetUpFixture.cs @@ -1,20 +1,32 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - +// ----------------------------------------------------------------------------- +// <copyright file="InteropSetUpFixture.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.Interop { - using NUnit.Framework; using System; + using NUnit.Framework; + /// <summary> + /// Interop set up fixture. + /// </summary> [SetUpFixture] public class InteropSetUpFixture { + /// <summary> + /// One time set up. + /// </summary> [OneTimeSetUp] public void Setup() { TestCommon.SetupTestSource(); } + /// <summary> + /// Tear down. + /// </summary> [OneTimeTearDown] public void TearDown() { diff --git a/src/AppInstallerCLIE2ETests/Interop/UninstallInterop.cs b/src/AppInstallerCLIE2ETests/Interop/UninstallInterop.cs @@ -1,65 +1,89 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - +// ----------------------------------------------------------------------------- +// <copyright file="UninstallInterop.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.Interop { - using Microsoft.Management.Deployment; - using Microsoft.Management.Deployment.Projection; - using NUnit.Framework; using System; using System.IO; using System.Threading.Tasks; + using Microsoft.Management.Deployment; + using Microsoft.Management.Deployment.Projection; + using NUnit.Framework; + /// <summary> + /// Test uninstall interop. + /// </summary> [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] public class UninstallInterop : BaseInterop { private string installDir; private PackageManager packageManager; - private PackageCatalogReference compositeSource; - - public UninstallInterop(IInstanceInitializer initializer) : base(initializer) { } + private PackageCatalogReference compositeSource; + + /// <summary> + /// Initializes a new instance of the <see cref="UninstallInterop"/> class. + /// </summary> + /// <param name="initializer">Initializer.</param> + public UninstallInterop(IInstanceInitializer initializer) + : base(initializer) + { + } + /// <summary> + /// Set up. + /// </summary> [SetUp] public void Init() { - packageManager = TestFactory.CreatePackageManager(); - installDir = TestCommon.GetRandomTestDir(); + this.packageManager = this.TestFactory.CreatePackageManager(); + this.installDir = TestCommon.GetRandomTestDir(); // Create composite package catalog source - var options = TestFactory.CreateCreateCompositePackageCatalogOptions(); - var testSource = packageManager.GetPackageCatalogByName(Constants.TestSourceName); + var options = this.TestFactory.CreateCreateCompositePackageCatalogOptions(); + var testSource = this.packageManager.GetPackageCatalogByName(Constants.TestSourceName); Assert.NotNull(testSource, $"{Constants.TestSourceName} cannot be null"); options.Catalogs.Add(testSource); options.CompositeSearchBehavior = CompositeSearchBehavior.AllCatalogs; - compositeSource = packageManager.CreateCompositePackageCatalog(options); - } - + this.compositeSource = this.packageManager.CreateCompositePackageCatalog(options); + } + + /// <summary> + /// Test uninstall exe. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task UninstallTestExe() { // Find package - var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.ExeInstallerPackageId); + var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.ExeInstallerPackageId); // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); + var installOptions = this.TestFactory.CreateInstallOptions(); installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = installDir; + installOptions.PreferredInstallLocation = this.installDir; // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); // Find package again, but this time it should detect the installed version - searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.ExeInstallerPackageId); + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.ExeInstallerPackageId); Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); // Uninstall - var uninstallResult = await packageManager.UninstallPackageAsync(searchResult.CatalogPackage, TestFactory.CreateUninstallOptions()); + var uninstallResult = await this.packageManager.UninstallPackageAsync(searchResult.CatalogPackage, this.TestFactory.CreateUninstallOptions()); Assert.AreEqual(UninstallResultStatus.Ok, uninstallResult.Status); - Assert.True(TestCommon.VerifyTestExeUninstalled(installDir)); + Assert.True(TestCommon.VerifyTestExeUninstalled(this.installDir)); } + /// <summary> + /// Test uninstall msi. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task UninstallTestMsi() { @@ -69,49 +93,57 @@ namespace AppInstallerCLIE2ETests.Interop } // Find package - var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.MsiInstallerPackageId); + var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.MsiInstallerPackageId); // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); - installOptions.PreferredInstallLocation = installDir; + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PreferredInstallLocation = this.installDir; // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); // Find package again, but this time it should detect the installed version - searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.MsiInstallerPackageId); + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.MsiInstallerPackageId); Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); // Uninstall - var uninstallResult = await packageManager.UninstallPackageAsync(searchResult.CatalogPackage, TestFactory.CreateUninstallOptions()); + var uninstallResult = await this.packageManager.UninstallPackageAsync(searchResult.CatalogPackage, this.TestFactory.CreateUninstallOptions()); Assert.AreEqual(UninstallResultStatus.Ok, uninstallResult.Status); - Assert.True(TestCommon.VerifyTestMsiUninstalled(installDir)); + Assert.True(TestCommon.VerifyTestMsiUninstalled(this.installDir)); } + /// <summary> + /// Test uninstall msix. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task UninstallTestMsix() { // Find package - var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.MsixInstallerPackageId); + var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.MsixInstallerPackageId); // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); + var installOptions = this.TestFactory.CreateInstallOptions(); // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); // Find package again, but this time it should detect the installed version - searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.MsixInstallerPackageId); + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.MsixInstallerPackageId); Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); // Uninstall - var uninstallResult = await packageManager.UninstallPackageAsync(searchResult.CatalogPackage, TestFactory.CreateUninstallOptions()); + var uninstallResult = await this.packageManager.UninstallPackageAsync(searchResult.CatalogPackage, this.TestFactory.CreateUninstallOptions()); Assert.AreEqual(UninstallResultStatus.Ok, uninstallResult.Status); Assert.True(TestCommon.VerifyTestMsixUninstalled()); } + /// <summary> + /// Test uninstall portable package. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task UninstallPortable() { @@ -122,25 +154,29 @@ namespace AppInstallerCLIE2ETests.Interop string fileName = Constants.AppInstallerTestExeInstallerExe; // Find package - var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); + var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); + var installOptions = this.TestFactory.CreateInstallOptions(); // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); // Find package again, but this time it should detect the installed version - searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); // Uninstall - var uninstallResult = await packageManager.UninstallPackageAsync(searchResult.CatalogPackage, TestFactory.CreateUninstallOptions()); + var uninstallResult = await this.packageManager.UninstallPackageAsync(searchResult.CatalogPackage, this.TestFactory.CreateUninstallOptions()); Assert.AreEqual(UninstallResultStatus.Ok, uninstallResult.Status); TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); } + /// <summary> + /// Test uninstall portable package with product code. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task UninstallPortableWithProductCode() { @@ -151,25 +187,29 @@ namespace AppInstallerCLIE2ETests.Interop string fileName = Constants.AppInstallerTestExeInstallerExe; // Find package - var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); + var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); + var installOptions = this.TestFactory.CreateInstallOptions(); // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); // Find package again, but this time it should detect the installed version - searchResult = FindOnePackage(compositeSource, PackageMatchField.ProductCode, PackageFieldMatchOption.Equals, productCode); + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.ProductCode, PackageFieldMatchOption.Equals, productCode); Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); // Uninstall - var uninstallResult = await packageManager.UninstallPackageAsync(searchResult.CatalogPackage, TestFactory.CreateUninstallOptions()); + var uninstallResult = await this.packageManager.UninstallPackageAsync(searchResult.CatalogPackage, this.TestFactory.CreateUninstallOptions()); Assert.AreEqual(UninstallResultStatus.Ok, uninstallResult.Status); TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); } + /// <summary> + /// Test uninstall portable package modified symlink. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task UninstallPortableModifiedSymlink() { @@ -179,13 +219,13 @@ namespace AppInstallerCLIE2ETests.Interop string symlinkPath = Path.Combine(symlinkDirectory, commandAlias); // Find package - var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); + var installOptions = this.TestFactory.CreateInstallOptions(); // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); // Replace symlink with modified symlink @@ -193,11 +233,11 @@ namespace AppInstallerCLIE2ETests.Interop FileSystemInfo modifiedSymlinkInfo = File.CreateSymbolicLink(symlinkPath, "fakeTargetExe"); // Find package again, but this time it should detect the installed version - searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); // Uninstall - var uninstallResult = await packageManager.UninstallPackageAsync(searchResult.CatalogPackage, TestFactory.CreateUninstallOptions()); + var uninstallResult = await this.packageManager.UninstallPackageAsync(searchResult.CatalogPackage, this.TestFactory.CreateUninstallOptions()); Assert.AreEqual(UninstallResultStatus.UninstallError, uninstallResult.Status); Assert.True(modifiedSymlinkInfo.Exists, "Modified symlink should still exist"); @@ -205,30 +245,34 @@ namespace AppInstallerCLIE2ETests.Interop modifiedSymlinkInfo.Delete(); } + /// <summary> + /// Test uninstall not indexed. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task UninstallNotIndexed() { const string customProductCode = "{f08fc03c-0b7e-4fca-9b3c-3a384d18a9f3}"; // Find package - var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.ExeInstallerPackageId); + var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.ExeInstallerPackageId); // Configure installation - var installOptions = TestFactory.CreateInstallOptions(); - installOptions.ReplacementInstallerArguments = $"/ProductID {customProductCode} /InstallDir {installDir}"; + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.ReplacementInstallerArguments = $"/ProductID {customProductCode} /InstallDir {this.installDir}"; // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); // Find package again, but this time it should detect the installed version - searchResult = FindOnePackage(compositeSource, PackageMatchField.ProductCode, PackageFieldMatchOption.Equals, customProductCode); + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.ProductCode, PackageFieldMatchOption.Equals, customProductCode); Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); // Uninstall - var uninstallResult = await packageManager.UninstallPackageAsync(searchResult.CatalogPackage, TestFactory.CreateUninstallOptions()); + var uninstallResult = await this.packageManager.UninstallPackageAsync(searchResult.CatalogPackage, this.TestFactory.CreateUninstallOptions()); Assert.AreEqual(UninstallResultStatus.Ok, uninstallResult.Status); - Assert.True(TestCommon.VerifyTestExeUninstalled(installDir)); + Assert.True(TestCommon.VerifyTestExeUninstalled(this.installDir)); } } } diff --git a/src/AppInstallerCLIE2ETests/Interop/UpgradeInterop.cs b/src/AppInstallerCLIE2ETests/Interop/UpgradeInterop.cs @@ -1,39 +1,59 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - +// ----------------------------------------------------------------------------- +// <copyright file="UpgradeInterop.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.Interop { - using Microsoft.Management.Deployment; - using Microsoft.Management.Deployment.Projection; - using NUnit.Framework; using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; + using Microsoft.Management.Deployment; + using Microsoft.Management.Deployment.Projection; + using NUnit.Framework; + /// <summary> + /// Test upgrade interop. + /// </summary> [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] public class UpgradeInterop : BaseInterop { private PackageManager packageManager; - private PackageCatalogReference compositeSource; - - public UpgradeInterop(IInstanceInitializer initializer) : base(initializer) { } + private PackageCatalogReference compositeSource; + + /// <summary> + /// Initializes a new instance of the <see cref="UpgradeInterop"/> class. + /// </summary> + /// <param name="initializer">Initializer.</param> + public UpgradeInterop(IInstanceInitializer initializer) + : base(initializer) + { + } + /// <summary> + /// Set up. + /// </summary> [SetUp] public void Init() { - packageManager = TestFactory.CreatePackageManager(); + this.packageManager = this.TestFactory.CreatePackageManager(); // Create composite package catalog source - var options = TestFactory.CreateCreateCompositePackageCatalogOptions(); - var testSource = packageManager.GetPackageCatalogByName(Constants.TestSourceName); + var options = this.TestFactory.CreateCreateCompositePackageCatalogOptions(); + var testSource = this.packageManager.GetPackageCatalogByName(Constants.TestSourceName); Assert.NotNull(testSource, $"{Constants.TestSourceName} cannot be null"); options.Catalogs.Add(testSource); options.CompositeSearchBehavior = CompositeSearchBehavior.AllCatalogs; - compositeSource = packageManager.CreateCompositePackageCatalog(options); - } - + this.compositeSource = this.packageManager.CreateCompositePackageCatalog(options); + } + + /// <summary> + /// Tests upgrade portable package. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task UpgradePortable() { @@ -45,34 +65,38 @@ namespace AppInstallerCLIE2ETests.Interop string fileName = Constants.AppInstallerTestExeInstallerExe; // Find package - var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); // Configure install options - var installOptions = TestFactory.CreateInstallOptions(); - installOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, (i => i.Version == "1.0.0.0")); + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, i => i.Version == "1.0.0.0"); // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); // Find package again, but this time it should detect the installed version - searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); Assert.AreEqual(searchResult.CatalogPackage.InstalledVersion?.Version, "1.0.0.0"); // Configure upgrade options - var upgradeOptions = TestFactory.CreateInstallOptions(); - upgradeOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, (i => i.Version == "2.0.0.0")); + var upgradeOptions = this.TestFactory.CreateInstallOptions(); + upgradeOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, i => i.Version == "2.0.0.0"); // Upgrade - var upgradeResult = await packageManager.UpgradePackageAsync(searchResult.CatalogPackage, upgradeOptions); + var upgradeResult = await this.packageManager.UpgradePackageAsync(searchResult.CatalogPackage, upgradeOptions); Assert.AreEqual(InstallResultStatus.Ok, upgradeResult.Status); // Find package again, but this time it should detect the upgraded installed version - searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); Assert.AreEqual(searchResult.CatalogPackage.InstalledVersion?.Version, "2.0.0.0"); TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); } + /// <summary> + /// Test upgrade portable package with arp mismatch. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task UpgradePortableARPMismatch() { @@ -84,38 +108,42 @@ namespace AppInstallerCLIE2ETests.Interop string fileName = Constants.AppInstallerTestExeInstallerExe; // Find package - var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); // Configure install options - var installOptions = TestFactory.CreateInstallOptions(); - installOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, (i => i.Version == "1.0.0.0")); + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, i => i.Version == "1.0.0.0"); // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); // Modify packageId to cause mismatch. TestCommon.ModifyPortableARPEntryValue(productCode, Constants.WinGetPackageIdentifier, "testPackageId"); // Find package again, but this time it should detect the installed version - searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); // Configure upgrade options - var upgradeOptions = TestFactory.CreateInstallOptions(); - upgradeOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, (i => i.Version == "2.0.0.0")); + var upgradeOptions = this.TestFactory.CreateInstallOptions(); + upgradeOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, i => i.Version == "2.0.0.0"); // Upgrade - var upgradeResult = await packageManager.UpgradePackageAsync(searchResult.CatalogPackage, upgradeOptions); + var upgradeResult = await this.packageManager.UpgradePackageAsync(searchResult.CatalogPackage, upgradeOptions); Assert.AreEqual(InstallResultStatus.InstallError, upgradeResult.Status); Assert.AreEqual(Constants.ErrorCode.ERROR_PORTABLE_PACKAGE_ALREADY_EXISTS, upgradeResult.ExtendedErrorCode.HResult); // Find package again, it should have not been upgraded - searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); Assert.AreEqual(searchResult.CatalogPackage.InstalledVersion?.Version, "1.0.0.0"); TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); } + /// <summary> + /// Test upgrade portable package force. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task UpgradePortableForcedOverride() { @@ -127,14 +155,14 @@ namespace AppInstallerCLIE2ETests.Interop string fileName = Constants.AppInstallerTestExeInstallerExe; // Find package - var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); // Configure install options - var installOptions = TestFactory.CreateInstallOptions(); - installOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, (i => i.Version == "1.0.0.0")); + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, i => i.Version == "1.0.0.0"); // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); // Modify packageId and sourceId to cause mismatch. @@ -142,24 +170,28 @@ namespace AppInstallerCLIE2ETests.Interop TestCommon.ModifyPortableARPEntryValue(productCode, Constants.WinGetSourceIdentifier, "testSourceId"); // Find package again, but this time it should detect the installed version - searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); // Configure upgrade options - var upgradeOptions = TestFactory.CreateInstallOptions(); - upgradeOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, (i => i.Version == "2.0.0.0")); + var upgradeOptions = this.TestFactory.CreateInstallOptions(); + upgradeOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, i => i.Version == "2.0.0.0"); upgradeOptions.Force = true; // Upgrade - var upgradeResult = await packageManager.UpgradePackageAsync(searchResult.CatalogPackage, upgradeOptions); + var upgradeResult = await this.packageManager.UpgradePackageAsync(searchResult.CatalogPackage, upgradeOptions); Assert.AreEqual(InstallResultStatus.Ok, upgradeResult.Status); // Find package again, but this time it should detect the upgraded installed version - searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); Assert.AreEqual(searchResult.CatalogPackage.InstalledVersion?.Version, "2.0.0.0"); TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); } + /// <summary> + /// Test upgrade portable package uninstall previous. + /// </summary> + /// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns> [Test] public async Task UpgradePortableUninstallPrevious() { @@ -171,37 +203,37 @@ namespace AppInstallerCLIE2ETests.Interop string fileName = Constants.AppInstallerTestExeInstallerExe; // Find package - var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); // Configure install options - var installOptions = TestFactory.CreateInstallOptions(); - installOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, (i => i.Version == "1.0.0.0")); + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, i => i.Version == "1.0.0.0"); // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); // Find package again, but this time it should detect the installed version - searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); Assert.AreEqual(searchResult.CatalogPackage.InstalledVersion?.Version, "1.0.0.0"); // Configure upgrade options - var upgradeOptions = TestFactory.CreateInstallOptions(); - upgradeOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, (i => i.Version == "3.0.0.0")); + var upgradeOptions = this.TestFactory.CreateInstallOptions(); + upgradeOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, i => i.Version == "3.0.0.0"); // Upgrade - var upgradeResult = await packageManager.UpgradePackageAsync(searchResult.CatalogPackage, upgradeOptions); + var upgradeResult = await this.packageManager.UpgradePackageAsync(searchResult.CatalogPackage, upgradeOptions); Assert.AreEqual(InstallResultStatus.Ok, upgradeResult.Status); // Find package again, but this time it should detect the upgraded installed version - searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); Assert.AreEqual(searchResult.CatalogPackage.InstalledVersion?.Version, "3.0.0.0"); TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); } // Cannot use foreach or Linq for out-of-process IVector // Bug: https://github.com/microsoft/CsWinRT/issues/1205 - public static T First<T>(IReadOnlyList<T> list, Func<T, bool> condition) + private static T First<T>(IReadOnlyList<T> list, Func<T, bool> condition) { if (list == null || condition == null) { diff --git a/src/AppInstallerCLIE2ETests/ListCommand.cs b/src/AppInstallerCLIE2ETests/ListCommand.cs @@ -1,13 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// ----------------------------------------------------------------------------- +// <copyright file="ListCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- namespace AppInstallerCLIE2ETests { - using NUnit.Framework; using System.IO; + using NUnit.Framework; + /// <summary> + /// List command tests. + /// </summary> public class ListCommand : BaseCommand { + /// <summary> + /// Test list winget. + /// </summary> [Test] public void ListSelf() { @@ -16,6 +25,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains(Constants.AICLIPackageFamilyName)); } + /// <summary> + /// Test list after installing a package. + /// </summary> [Test] public void ListAfterInstall() { @@ -42,24 +54,30 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("2.0.0.0")); } + /// <summary> + /// Test expected entries after list. + /// </summary> [Test] public void ListWithArpVersionMapping() { // No mapping performed - ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameVersion", "TestArpVersionSameVersion", "0.5", "0.5", "< 1.0"); + this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameVersion", "TestArpVersionSameVersion", "0.5", "0.5", "< 1.0"); // Partial mapping performed(i.e. only if version falls within arp version range) - ArpVersionMappingTest("AppInstallerTest.TestArpVersionOppositeOrder", "TestArpVersionOppositeOrder", "10.1", "1.0", "10.1"); - ArpVersionMappingTest("AppInstallerTest.TestArpVersionOppositeOrder", "TestArpVersionOppositeOrder", "9.9", "9.9", "> 2.0"); + this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionOppositeOrder", "TestArpVersionOppositeOrder", "10.1", "1.0", "10.1"); + this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionOppositeOrder", "TestArpVersionOppositeOrder", "9.9", "9.9", "> 2.0"); // Full mapping performed - ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameOrder", "TestArpVersionSameOrder", "7.0", "< 1.0", "7.0"); - ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameOrder", "TestArpVersionSameOrder", "10.1", "1.0", "10.1"); - ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameOrder", "TestArpVersionSameOrder", "10.7", "< 2.0", "10.7"); - ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameOrder", "TestArpVersionSameOrder", "11.1", "2.0", "11.1"); - ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameOrder", "TestArpVersionSameOrder", "12.0", "> 2.0", "12.0"); + this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameOrder", "TestArpVersionSameOrder", "7.0", "< 1.0", "7.0"); + this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameOrder", "TestArpVersionSameOrder", "10.1", "1.0", "10.1"); + this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameOrder", "TestArpVersionSameOrder", "10.7", "< 2.0", "10.7"); + this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameOrder", "TestArpVersionSameOrder", "11.1", "2.0", "11.1"); + this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameOrder", "TestArpVersionSameOrder", "12.0", "> 2.0", "12.0"); } + /// <summary> + /// Test list with upgrade code. + /// </summary> [Test] public void ListWithUpgradeCode() { diff --git a/src/AppInstallerCLIE2ETests/PowerShell/PowerShellModule.cs b/src/AppInstallerCLIE2ETests/PowerShell/PowerShellModule.cs @@ -1,177 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace AppInstallerCLIE2ETests.PowerShell -{ - using NUnit.Framework; - using System; - using System.Diagnostics; - using System.Linq; - - /// <summary> - /// Basic E2E smoke tests for verifying the behavior of the PowerShell module cmdlets. - /// Running the x86 PowerShell Module requires PowerShell Core (x86). These tests currently only target PowerShell Core (x64) - /// </summary> - [Category("PowerShell")] - public class PowerShellModule - { - // TODO: Consider using Pester framework for conducting more extensive PowerShell module tests. - - [OneTimeSetUp] - public void Setup() - { - TestCommon.RunAICLICommand("source add", $"-n {Constants.TestSourceName} {Constants.TestSourceUrl}"); - } - - [OneTimeTearDown] - public void TearDown() - { - // TODO: This is a workaround to an issue where the server takes longer than expected to terminate when - // running from the E2E tests. This can cause other E2E tests to fail when attempting to reset the test source. - if (IsRunning(Constants.WindowsPackageManagerServer)) - { - // There should only be one WinGetServer process running at a time. - Process serverProcess = Process.GetProcessesByName(Constants.WindowsPackageManagerServer).First(); - serverProcess.Kill(); - } - - TestCommon.RunAICLICommand("source remove", $"{Constants.TestSourceName}"); - } - - [Test] - public void AssertServerShutdownAfterExecution() - { - if (!Environment.Is64BitProcess) - { - return; - } - - var result = TestCommon.RunPowerShellCommandWithResult(Constants.GetSourceCmdlet, $"-Name {Constants.TestSourceName}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, $"ExitCode: {result.ExitCode} Failed with the following output: {result.StdOut}, {result.StdErr}"); - - Assert.IsTrue(IsRunning(Constants.WindowsPackageManagerServer), $"{Constants.WindowsPackageManagerServer} is not running."); - Process serverProcess = Process.GetProcessesByName(Constants.WindowsPackageManagerServer).First(); - - // Wait a maximum of 30 seconds for the server process to exit. - bool serverProcessExit = serverProcess.WaitForExit(30000); - Assert.IsTrue(serverProcessExit, $"{Constants.WindowsPackageManagerServer} failed to terminate after creating COM object."); - } - - [Test] - public void GetWinGetSource() - { - if (!Environment.Is64BitProcess) - { - return; - } - - var getSourceResult = TestCommon.RunPowerShellCommandWithResult(Constants.GetSourceCmdlet, $"-Name {Constants.TestSourceName}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, getSourceResult.ExitCode, $"ExitCode: {getSourceResult.ExitCode} Failed with the following output: {getSourceResult.StdOut}, {getSourceResult.StdErr}"); - Assert.IsTrue(getSourceResult.StdOut.Contains($"{Constants.TestSourceName}")); - } - - [Test] - public void FindWinGetPackage() - { - if (!Environment.Is64BitProcess) - { - return; - } - - var result = TestCommon.RunPowerShellCommandWithResult(Constants.FindCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, $"ExitCode: {result.ExitCode} Failed with the following output: {result.StdOut}; {result.StdErr}"); - Assert.IsTrue(result.StdOut.Contains("TestExeInstaller")); - } - - [Test] - public void GetWinGetPackage() - { - if (!Environment.Is64BitProcess) - { - return; - } - - var installResult = TestCommon.RunPowerShellCommandWithResult(Constants.InstallCmdlet, $"-Id {Constants.MsiInstallerPackageId}"); - var getResult = TestCommon.RunPowerShellCommandWithResult(Constants.GetCmdlet, $"-Id {Constants.MsiInstallerPackageId}"); - var uninstallResult = TestCommon.RunPowerShellCommandWithResult(Constants.UninstallCmdlet, $"-Id {Constants.MsiInstallerPackageId}"); - - Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode, $"ExitCode: {installResult.ExitCode}; Failed with the following output: {installResult.StdOut}; {installResult.StdErr}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, getResult.ExitCode, $"Failed with the following output: {getResult.StdOut}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, uninstallResult.ExitCode, $"Failed with the following output: {uninstallResult.StdOut}"); - - Assert.IsTrue(!string.IsNullOrEmpty(installResult.StdOut)); - Assert.IsTrue(getResult.StdOut.Contains("TestMsiInstaller")); - Assert.IsTrue(!string.IsNullOrEmpty(uninstallResult.StdOut)); - } - - [Test] - public void InstallWinGetPackage() - { - if (!Environment.Is64BitProcess) - { - return; - } - - var installResult = TestCommon.RunPowerShellCommandWithResult(Constants.InstallCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); - var uninstallResult = TestCommon.RunPowerShellCommandWithResult(Constants.UninstallCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); - - Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode, $"ExitCode: {installResult.ExitCode}; Failed with the following output: {installResult.StdOut}; {installResult.StdErr}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, uninstallResult.ExitCode, $"Failed with the following output: {uninstallResult.StdOut}"); - - Assert.IsTrue(!string.IsNullOrEmpty(installResult.StdOut)); - Assert.IsTrue(!string.IsNullOrEmpty(uninstallResult.StdOut)); - } - - [Test] - public void UpdateWinGetPackage() - { - if (!Environment.Is64BitProcess) - { - return; - } - - var installResult = TestCommon.RunPowerShellCommandWithResult(Constants.InstallCmdlet, $"-Id {Constants.ExeInstallerPackageId} -Version 1.0.0.0"); - var updateResult = TestCommon.RunPowerShellCommandWithResult(Constants.UpdateCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); - var getResult = TestCommon.RunPowerShellCommandWithResult(Constants.GetCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); - var uninstallResult = TestCommon.RunPowerShellCommandWithResult(Constants.UninstallCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); - - Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode, $"Failed with the following output: {installResult.StdOut}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, updateResult.ExitCode, $"Failed with the following output: {updateResult.StdOut}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, getResult.ExitCode, $"Failed with the following output: {getResult.StdOut}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, uninstallResult.ExitCode, $"Failed with the following output: {uninstallResult.StdOut}"); - - Assert.IsTrue(!string.IsNullOrEmpty(installResult.StdOut)); - Assert.IsTrue(!string.IsNullOrEmpty(updateResult.StdOut)); - Assert.IsTrue(getResult.StdOut.Contains("2.0.0.0")); - Assert.IsTrue(!string.IsNullOrEmpty(uninstallResult.StdOut)); - } - - /// <summary> - /// There is a known issue where the server takes an abnormally long time to terminate after the E2E test pwsh processes finish execution. - /// This test verifies that the server does indeed terminate within 5 minutes after running all of the cmdlets. - /// Commented out to reduce the overall duration of the build pipeline. - /// </summary> - // [Test] - public void VerifyServerTermination() - { - TestCommon.RunPowerShellCommandWithResult(Constants.GetSourceCmdlet, $"-Name {Constants.TestSourceName}"); - TestCommon.RunPowerShellCommandWithResult(Constants.FindCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); - TestCommon.RunPowerShellCommandWithResult(Constants.InstallCmdlet, $"-Id {Constants.ExeInstallerPackageId} -Version 1.0.0.0"); - TestCommon.RunPowerShellCommandWithResult(Constants.UpdateCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); - TestCommon.RunPowerShellCommandWithResult(Constants.GetCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); - TestCommon.RunPowerShellCommandWithResult(Constants.UninstallCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); - - Assert.IsTrue(IsRunning(Constants.WindowsPackageManagerServer), $"{Constants.WindowsPackageManagerServer} is not running."); - Process serverProcess = Process.GetProcessesByName(Constants.WindowsPackageManagerServer).First(); - - // Wait a maximum of 5 minutes for the server process to exit. - bool serverProcessExit = serverProcess.WaitForExit(300000); - Assert.IsTrue(serverProcessExit, $"{Constants.WindowsPackageManagerServer} failed to terminate after creating COM object."); - } - - private bool IsRunning(string processName) - { - return Process.GetProcessesByName(processName).Length > 0; - } - } -}- \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/PowerShell/WinGetClientModule.cs b/src/AppInstallerCLIE2ETests/PowerShell/WinGetClientModule.cs @@ -0,0 +1,205 @@ +// ----------------------------------------------------------------------------- +// <copyright file="WinGetClientModule.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests.PowerShell +{ + using System; + using System.Diagnostics; + using System.Linq; + using NUnit.Framework; + + /// <summary> + /// Basic E2E smoke tests for verifying the behavior of the PowerShell Microsoft.WinGet.Client module cmdlets. + /// Running the x86 PowerShell Module requires PowerShell Core (x86). These tests currently only target PowerShell Core (x64). + /// </summary> + [Category("PowerShell")] + public class WinGetClientModule + { + // TODO: Consider using Pester framework for conducting more extensive PowerShell module tests. + + /// <summary> + /// Set setup. + /// </summary> + [OneTimeSetUp] + public void Setup() + { + TestCommon.RunAICLICommand("source add", $"-n {Constants.TestSourceName} {Constants.TestSourceUrl}"); + } + + /// <summary> + /// Tear down. + /// </summary> + [OneTimeTearDown] + public void TearDown() + { + // TODO: This is a workaround to an issue where the server takes longer than expected to terminate when + // running from the E2E tests. This can cause other E2E tests to fail when attempting to reset the test source. + if (this.IsRunning(Constants.WindowsPackageManagerServer)) + { + // There should only be one WinGetServer process running at a time. + Process serverProcess = Process.GetProcessesByName(Constants.WindowsPackageManagerServer).First(); + serverProcess.Kill(); + } + + TestCommon.RunAICLICommand("source remove", $"{Constants.TestSourceName}"); + } + + /// <summary> + /// Assert server shutdown. + /// </summary> + [Test] + public void AssertServerShutdownAfterExecution() + { + if (!Environment.Is64BitProcess) + { + return; + } + + var result = TestCommon.RunPowerShellCoreCommandWithResult(Constants.GetSourceCmdlet, $"-Name {Constants.TestSourceName}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, $"ExitCode: {result.ExitCode} Failed with the following output: {result.StdOut}, {result.StdErr}"); + + Assert.IsTrue(this.IsRunning(Constants.WindowsPackageManagerServer), $"{Constants.WindowsPackageManagerServer} is not running."); + Process serverProcess = Process.GetProcessesByName(Constants.WindowsPackageManagerServer).First(); + + // Wait a maximum of 30 seconds for the server process to exit. + bool serverProcessExit = serverProcess.WaitForExit(30000); + Assert.IsTrue(serverProcessExit, $"{Constants.WindowsPackageManagerServer} failed to terminate after creating COM object."); + } + + /// <summary> + /// Test Get-WinGetSource. + /// </summary> + [Test] + public void GetWinGetSource() + { + if (!Environment.Is64BitProcess) + { + return; + } + + var getSourceResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.GetSourceCmdlet, $"-Name {Constants.TestSourceName}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, getSourceResult.ExitCode, $"ExitCode: {getSourceResult.ExitCode} Failed with the following output: {getSourceResult.StdOut}, {getSourceResult.StdErr}"); + Assert.IsTrue(getSourceResult.StdOut.Contains($"{Constants.TestSourceName}")); + } + + /// <summary> + /// Find-WinGetPackage. + /// </summary> + [Test] + public void FindWinGetPackage() + { + if (!Environment.Is64BitProcess) + { + return; + } + + var result = TestCommon.RunPowerShellCoreCommandWithResult(Constants.FindCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, $"ExitCode: {result.ExitCode} Failed with the following output: {result.StdOut}; {result.StdErr}"); + Assert.IsTrue(result.StdOut.Contains("TestExeInstaller")); + } + + /// <summary> + /// Tests Get-WinGetPackage. + /// </summary> + [Test] + public void GetWinGetPackage() + { + if (!Environment.Is64BitProcess) + { + return; + } + + var installResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.InstallCmdlet, $"-Id {Constants.MsiInstallerPackageId}"); + var getResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.GetCmdlet, $"-Id {Constants.MsiInstallerPackageId}"); + var uninstallResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.UninstallCmdlet, $"-Id {Constants.MsiInstallerPackageId}"); + + Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode, $"ExitCode: {installResult.ExitCode}; Failed with the following output: {installResult.StdOut}; {installResult.StdErr}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, getResult.ExitCode, $"Failed with the following output: {getResult.StdOut}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, uninstallResult.ExitCode, $"Failed with the following output: {uninstallResult.StdOut}"); + + Assert.IsTrue(!string.IsNullOrEmpty(installResult.StdOut)); + Assert.IsTrue(getResult.StdOut.Contains("TestMsiInstaller")); + Assert.IsTrue(!string.IsNullOrEmpty(uninstallResult.StdOut)); + } + + /// <summary> + /// Tests Install-WinGetPackage. + /// </summary> + [Test] + public void InstallWinGetPackage() + { + if (!Environment.Is64BitProcess) + { + return; + } + + var installResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.InstallCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + var uninstallResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.UninstallCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + + Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode, $"ExitCode: {installResult.ExitCode}; Failed with the following output: {installResult.StdOut}; {installResult.StdErr}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, uninstallResult.ExitCode, $"Failed with the following output: {uninstallResult.StdOut}"); + + Assert.IsTrue(!string.IsNullOrEmpty(installResult.StdOut)); + Assert.IsTrue(!string.IsNullOrEmpty(uninstallResult.StdOut)); + } + + /// <summary> + /// Tests Update-WinGetPackage. + /// </summary> + [Test] + public void UpdateWinGetPackage() + { + if (!Environment.Is64BitProcess) + { + return; + } + + var installResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.InstallCmdlet, $"-Id {Constants.ExeInstallerPackageId} -Version 1.0.0.0"); + var updateResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.UpdateCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + var getResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.GetCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + var uninstallResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.UninstallCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + + Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode, $"Failed with the following output: {installResult.StdOut}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, updateResult.ExitCode, $"Failed with the following output: {updateResult.StdOut}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, getResult.ExitCode, $"Failed with the following output: {getResult.StdOut}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, uninstallResult.ExitCode, $"Failed with the following output: {uninstallResult.StdOut}"); + + Assert.IsTrue(!string.IsNullOrEmpty(installResult.StdOut)); + Assert.IsTrue(!string.IsNullOrEmpty(updateResult.StdOut)); + Assert.IsTrue(getResult.StdOut.Contains("2.0.0.0")); + Assert.IsTrue(!string.IsNullOrEmpty(uninstallResult.StdOut)); + } + + /// <summary> + /// There is a known issue where the server takes an abnormally long time to terminate after the E2E test pwsh processes finish execution. + /// This test verifies that the server does indeed terminate within 5 minutes after running all of the cmdlets. + /// Commented out to reduce the overall duration of the build pipeline. + /// </summary> + [Test] + [Ignore("Ignoring")] + public void VerifyServerTermination() + { + TestCommon.RunPowerShellCoreCommandWithResult(Constants.GetSourceCmdlet, $"-Name {Constants.TestSourceName}"); + TestCommon.RunPowerShellCoreCommandWithResult(Constants.FindCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + TestCommon.RunPowerShellCoreCommandWithResult(Constants.InstallCmdlet, $"-Id {Constants.ExeInstallerPackageId} -Version 1.0.0.0"); + TestCommon.RunPowerShellCoreCommandWithResult(Constants.UpdateCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + TestCommon.RunPowerShellCoreCommandWithResult(Constants.GetCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + TestCommon.RunPowerShellCoreCommandWithResult(Constants.UninstallCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + + Assert.IsTrue(this.IsRunning(Constants.WindowsPackageManagerServer), $"{Constants.WindowsPackageManagerServer} is not running."); + Process serverProcess = Process.GetProcessesByName(Constants.WindowsPackageManagerServer).First(); + + // Wait a maximum of 5 minutes for the server process to exit. + bool serverProcessExit = serverProcess.WaitForExit(300000); + Assert.IsTrue(serverProcessExit, $"{Constants.WindowsPackageManagerServer} failed to terminate after creating COM object."); + } + + private bool IsRunning(string processName) + { + return Process.GetProcessesByName(processName).Length > 0; + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Properties/AssemblyInfo.cs b/src/AppInstallerCLIE2ETests/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +// ----------------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +[assembly: System.Runtime.Versioning.SupportedOSPlatform("windows10.0.17763")] diff --git a/src/AppInstallerCLIE2ETests/SearchCommand.cs b/src/AppInstallerCLIE2ETests/SearchCommand.cs @@ -1,19 +1,31 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// ----------------------------------------------------------------------------- +// <copyright file="SearchCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- namespace AppInstallerCLIE2ETests { using NUnit.Framework; + /// <summary> + /// Test search command. + /// </summary> public class SearchCommand : BaseCommand { + /// <summary> + /// Test search without args. + /// </summary> [Test] public void SearchWithoutArgs() { - var result = TestCommon.RunAICLICommand("search", ""); + var result = TestCommon.RunAICLICommand("search", string.Empty); Assert.AreEqual(Constants.ErrorCode.ERROR_INVALID_CL_ARGUMENTS, result.ExitCode); } + /// <summary> + /// Test search with query. + /// </summary> [Test] public void SearchQuery() { @@ -23,6 +35,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); } + /// <summary> + /// Test search with alias. + /// </summary> public void SearchUsingAlias() { var result = TestCommon.RunAICLICommand("find", "TestExampleInstaller"); @@ -31,6 +46,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); } + /// <summary> + /// Test search with name. + /// </summary> [Test] public void SearchWithName() { @@ -40,6 +58,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); } + /// <summary> + /// Test search with Id. + /// </summary> [Test] public void SearchWithID() { @@ -49,6 +70,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); } + /// <summary> + /// Test search with invalid name. + /// </summary> [Test] public void SearchWithInvalidName() { @@ -57,6 +81,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("No package found matching input criteria.")); } + /// <summary> + /// Test search where it returns multiple results. + /// </summary> [Test] public void SearchReturnsMultiple() { @@ -68,6 +95,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); } + /// <summary> + /// Test search with exact name. + /// </summary> [Test] public void SearchWithExactName() { @@ -77,6 +107,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); } + /// <summary> + /// Test search with exact ID. + /// </summary> [Test] public void SearchWithExactID() { @@ -86,6 +119,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); } + /// <summary> + /// Test search with exact case sensitive. + /// </summary> [Test] public void SearchWithExactArgCaseSensitivity() { @@ -94,6 +130,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("No package found matching input criteria.")); } + /// <summary> + /// Test search with a failed source. + /// </summary> [Test] public void SearchWithSingleSourceFailure() { @@ -109,51 +148,55 @@ namespace AppInstallerCLIE2ETests } finally { - ResetTestSource(); + this.ResetTestSource(); } } + /// <summary> + /// Test search with bad pin. + /// </summary> [Test] public void SearchStoreWithBadPin() { // Configure as close as possible to the real chain but use the test cert for everything // This will at least force the public key to be checked rather than simply failing based on chain length - GroupPolicyHelper.EnableAdditionalSources.SetEnabledList(new GroupPolicySource[] + GroupPolicyHelper.EnableAdditionalSources.SetEnabledList(new GroupPolicyHelper.GroupPolicySource[] { - new GroupPolicySource + new GroupPolicyHelper.GroupPolicySource { Name = Constants.TestAlternateSourceName, Arg = Constants.DefaultMSStoreSourceUrl, Type = Constants.DefaultMSStoreSourceType, - Data = "", + Data = string.Empty, Identifier = Constants.DefaultMSStoreSourceIdentifier, - CertificatePinning = new GroupPolicyCertificatePinning + CertificatePinning = new GroupPolicyHelper.GroupPolicyCertificatePinning { - Chains = new GroupPolicyCertificatePinningChain[] { - new GroupPolicyCertificatePinningChain + Chains = new GroupPolicyHelper.GroupPolicyCertificatePinningChain[] + { + new GroupPolicyHelper.GroupPolicyCertificatePinningChain { - Chain = new GroupPolicyCertificatePinningDetails[] + Chain = new GroupPolicyHelper.GroupPolicyCertificatePinningDetails[] { - new GroupPolicyCertificatePinningDetails + new GroupPolicyHelper.GroupPolicyCertificatePinningDetails { Validation = new string[] { "publickey" }, - EmbeddedCertificate = TestCommon.GetTestServerCertificateHexString() + EmbeddedCertificate = TestCommon.GetTestServerCertificateHexString(), }, - new GroupPolicyCertificatePinningDetails + new GroupPolicyHelper.GroupPolicyCertificatePinningDetails { Validation = new string[] { "subject", "issuer" }, - EmbeddedCertificate = TestCommon.GetTestServerCertificateHexString() + EmbeddedCertificate = TestCommon.GetTestServerCertificateHexString(), }, - new GroupPolicyCertificatePinningDetails + new GroupPolicyHelper.GroupPolicyCertificatePinningDetails { Validation = new string[] { "subject", "issuer" }, - EmbeddedCertificate = TestCommon.GetTestServerCertificateHexString() - } - } - } - } - } - } + EmbeddedCertificate = TestCommon.GetTestServerCertificateHexString(), + }, + }, + }, + }, + }, + }, }); try @@ -163,7 +206,7 @@ namespace AppInstallerCLIE2ETests } finally { - ResetTestSource(); + this.ResetTestSource(); } } } diff --git a/src/AppInstallerCLIE2ETests/SetUpFixture.cs b/src/AppInstallerCLIE2ETests/SetUpFixture.cs @@ -1,21 +1,30 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// ----------------------------------------------------------------------------- +// <copyright file="SetUpFixture.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- namespace AppInstallerCLIE2ETests { + using System; + using System.IO; using Microsoft.Win32; using Newtonsoft.Json; using NUnit.Framework; - using System; - using System.IO; + /// <summary> + /// Set up fixture. + /// </summary> [SetUpFixture] public class SetUpFixture { - private static bool ShouldDisableDevModeOnExit = true; - private static bool ShouldRevertDefaultFileTypeRiskOnExit = true; - private static string DefaultFileTypes = string.Empty; + private static bool shouldDisableDevModeOnExit = true; + private static bool shouldRevertDefaultFileTypeRiskOnExit = true; + private static string defaultFileTypes = string.Empty; + /// <summary> + /// Set up. + /// </summary> [OneTimeSetUp] public void Setup() { @@ -63,9 +72,9 @@ namespace AppInstallerCLIE2ETests TestCommon.AICLIPath = Path.Combine(TestCommon.AICLIPackagePath, TestCommon.AICLIPath); } - ShouldDisableDevModeOnExit = EnableDevMode(true); + shouldDisableDevModeOnExit = this.EnableDevMode(true); - ShouldRevertDefaultFileTypeRiskOnExit = DecreaseFileTypeRisk(".exe;.msi", false); + shouldRevertDefaultFileTypeRiskOnExit = this.DecreaseFileTypeRisk(".exe;.msi", false); Assert.True(TestCommon.RunCommand("certutil.exe", "-addstore -f \"TRUSTEDPEOPLE\" " + TestCommon.GetTestDataFile(Constants.AppInstallerTestCert)), "Add AppInstallerTestCert"); @@ -100,24 +109,27 @@ namespace AppInstallerCLIE2ETests TestCommon.PowerShellModulePath = TestContext.Parameters.Get(Constants.PowerShellModulePathParameter); } - ReadTestInstallerPaths(); + this.ReadTestInstallerPaths(); TestIndexSetup.GenerateTestDirectory(); - InitializeWingetSettings(); + this.InitializeWingetSettings(); } + /// <summary> + /// Tear down. + /// </summary> [OneTimeTearDown] public void TearDown() { - if (ShouldDisableDevModeOnExit) + if (shouldDisableDevModeOnExit) { - EnableDevMode(false); + this.EnableDevMode(false); } - if (ShouldRevertDefaultFileTypeRiskOnExit) + if (shouldRevertDefaultFileTypeRiskOnExit) { - DecreaseFileTypeRisk(DefaultFileTypes, true); + this.DecreaseFileTypeRisk(defaultFileTypes, true); } TestCommon.RunCommand("certutil.exe", $"-delstore \"TRUSTEDPEOPLE\" {Constants.AppInstallerTestCertThumbprint}"); @@ -130,6 +142,42 @@ namespace AppInstallerCLIE2ETests } } + /// <summary> + /// Initialize settings. + /// </summary> + public void InitializeWingetSettings() + { + string localAppDataPath = Environment.GetEnvironmentVariable(Constants.LocalAppData); + + var settingsJson = new + { + experimentalFeatures = new + { + experimentalArg = false, + experimentalCmd = false, + dependencies = false, + directMSI = false, + openLogsArgument = false, + }, + debugging = new + { + enableSelfInitiatedMinidump = true, + }, + installBehavior = new + { + portablePackageUserRoot = string.Empty, + portablePackageMachineRoot = string.Empty, + }, + }; + + // Run winget one time to initialize settings directory + // when running in unpackaged context + TestCommon.RunAICLICommand(string.Empty, "-v"); + + var serializedSettingsJson = JsonConvert.SerializeObject(settingsJson, Formatting.Indented); + File.WriteAllText(Path.Combine(localAppDataPath, TestCommon.SettingsJsonFilePath), serializedSettingsJson); + } + // Returns whether there's a change to the dev mode state after execution private bool EnableDevMode(bool enable) { @@ -138,7 +186,7 @@ namespace AppInstallerCLIE2ETests if (enable) { var value = appModelUnlockKey.GetValue("AllowDevelopmentWithoutDevLicense"); - if (value == null || (Int32)value == 0) + if (value == null || (int)value == 0) { appModelUnlockKey.SetValue("AllowDevelopmentWithoutDevLicense", 1, RegistryValueKind.DWord); return true; @@ -147,12 +195,13 @@ namespace AppInstallerCLIE2ETests else { var value = appModelUnlockKey.GetValue("AllowDevelopmentWithoutDevLicense"); - if (value != null && ((Int32)value) != 0) + if (value != null && ((int)value) != 0) { appModelUnlockKey.SetValue("AllowDevelopmentWithoutDevLicense", 0, RegistryValueKind.DWord); return true; } } + return false; } @@ -170,21 +219,22 @@ namespace AppInstallerCLIE2ETests { if (string.IsNullOrEmpty(value)) { - DefaultFileTypes = string.Empty; + defaultFileTypes = string.Empty; defaultFileTypeRiskKey.SetValue("LowRiskFileTypes", fileTypes); } else { - DefaultFileTypes = value; + defaultFileTypes = value; defaultFileTypeRiskKey.SetValue("LowRiskFileTypes", string.Concat(value, fileTypes)); } + return true; } } private void ReadTestInstallerPaths() { - if (TestContext.Parameters.Exists(Constants.ExeInstallerPathParameter) + if (TestContext.Parameters.Exists(Constants.ExeInstallerPathParameter) && File.Exists(TestContext.Parameters.Get(Constants.ExeInstallerPathParameter))) { TestCommon.ExeInstallerPath = TestContext.Parameters.Get(Constants.ExeInstallerPathParameter); @@ -202,38 +252,5 @@ namespace AppInstallerCLIE2ETests TestCommon.MsixInstallerPath = TestContext.Parameters.Get(Constants.MsixInstallerPathParameter); } } - - public void InitializeWingetSettings() - { - string localAppDataPath = Environment.GetEnvironmentVariable(Constants.LocalAppData); - - var settingsJson = new - { - experimentalFeatures = new - { - experimentalArg = false, - experimentalCmd = false, - dependencies = false, - directMSI = false, - openLogsArgument = false, - }, - debugging = new - { - enableSelfInitiatedMinidump = true - }, - installBehavior = new - { - portablePackageUserRoot = "", - portablePackageMachineRoot = "", - } - }; - - // Run winget one time to initialize settings directory - // when running in unpackaged context - TestCommon.RunAICLICommand(string.Empty, "-v"); - - var serializedSettingsJson = JsonConvert.SerializeObject(settingsJson, Formatting.Indented); - File.WriteAllText(Path.Combine(localAppDataPath, TestCommon.SettingsJsonFilePath), serializedSettingsJson); - } } } diff --git a/src/AppInstallerCLIE2ETests/ShowCommand.cs b/src/AppInstallerCLIE2ETests/ShowCommand.cs @@ -1,19 +1,31 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// ----------------------------------------------------------------------------- +// <copyright file="ShowCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- namespace AppInstallerCLIE2ETests { using NUnit.Framework; + /// <summary> + /// Test show command. + /// </summary> public class ShowCommand : BaseCommand { + /// <summary> + /// Test show with no args. + /// </summary> [Test] public void ShowWithNoArgs() { - var result = TestCommon.RunAICLICommand("show", ""); + var result = TestCommon.RunAICLICommand("show", string.Empty); Assert.AreEqual(Constants.ErrorCode.ERROR_INVALID_CL_ARGUMENTS, result.ExitCode); } + /// <summary> + /// Test show no match. + /// </summary> [Test] public void ShowWithNoMatches() { @@ -23,6 +35,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("No package found matching input criteria.")); } + /// <summary> + /// Test show with substring match. + /// </summary> [Test] public void ShowWithSubstringMatch() { @@ -32,6 +47,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("No package found matching input criteria.")); } + /// <summary> + /// Test show with name match. + /// </summary> [Test] public void ShowWithNameMatch() { @@ -42,9 +60,12 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); } + /// <summary> + /// Test show with id match. + /// </summary> [Test] public void ShowWithIDMatch() - { + { var result = TestCommon.RunAICLICommand("show", $"--id appinstallertest.testexampleinstaller"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Found TestExampleInstaller [AppInstallerTest.TestExampleInstaller]")); @@ -52,6 +73,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); } + /// <summary> + /// Test show versions. + /// </summary> [Test] public void ShowWithVersions() { @@ -63,6 +87,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("1.2.3.4")); } + /// <summary> + /// Test show with exact match name. + /// </summary> [Test] public void ShowWithExactName() { @@ -73,6 +100,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); } + /// <summary> + /// Test show with exact id. + /// </summary> [Test] public void ShowWithExactID() { @@ -83,6 +113,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); } + /// <summary> + /// Test show with exact args. + /// </summary> [Test] public void ShowWithExactArgCaseSensitivity() { diff --git a/src/AppInstallerCLIE2ETests/SourceCommand.cs b/src/AppInstallerCLIE2ETests/SourceCommand.cs @@ -1,18 +1,30 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// ----------------------------------------------------------------------------- +// <copyright file="SourceCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- namespace AppInstallerCLIE2ETests { using NUnit.Framework; + /// <summary> + /// Test source command. + /// </summary> public class SourceCommand : BaseCommand { + /// <summary> + /// Test set up. + /// </summary> [SetUp] public void Setup() { - ResetTestSource(false); + this.ResetTestSource(false); } + /// <summary> + /// Test source add. + /// </summary> [Test] public void SourceAdd() { @@ -22,6 +34,9 @@ namespace AppInstallerCLIE2ETests TestCommon.RunAICLICommand("source remove", $"-n SourceTest"); } + /// <summary> + /// Test source add with duplicate name. + /// </summary> [Test] public void SourceAddWithDuplicateName() { @@ -31,6 +46,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("A source with the given name already exists and refers to a different location")); } + /// <summary> + /// Test source add with invalid url. + /// </summary> [Test] public void SourceAddWithInvalidURL() { @@ -40,7 +58,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("An unexpected error occurred while executing the command")); } - + /// <summary> + /// Test source add with http url. + /// </summary> [Test] public void SourceAddWithHttpURL() { @@ -50,15 +70,21 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("error occurred while executing the command")); } + /// <summary> + /// Test source list with no args. + /// </summary> [Test] public void SourceListWithNoArgs() { // List with no args should list all available sources - var result = TestCommon.RunAICLICommand("source list", ""); + var result = TestCommon.RunAICLICommand("source list", string.Empty); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains(Constants.TestSourceUrl)); } + /// <summary> + /// Test source list with name. + /// </summary> [Test] public void SourceListWithName() { @@ -70,6 +96,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("Updated")); } + /// <summary> + /// Test source list name mismatch. + /// </summary> [Test] public void SourceListNameMismatch() { @@ -78,6 +107,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("Did not find a source named")); } + /// <summary> + /// Test source update. + /// </summary> [Test] public void SourceUpdate() { @@ -86,6 +118,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("Done")); } + /// <summary> + /// Test source update with invalid name. + /// </summary> [Test] public void SourceUpdateWithInvalidName() { @@ -94,15 +129,21 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("Did not find a source named: UnknownName")); } + /// <summary> + /// Test source remove by name. + /// </summary> [Test] public void SourceRemoveValidName() { var result = TestCommon.RunAICLICommand("source remove", $"-n {Constants.TestSourceName}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Done")); - ResetTestSource(false); + this.ResetTestSource(false); } + /// <summary> + /// Test source remove with invalid name. + /// </summary> [Test] public void SourceRemoveInvalidName() { @@ -111,15 +152,21 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("Did not find a source named: UnknownName")); } + /// <summary> + /// Test source reset. + /// </summary> [Test] public void SourceReset() { - var result = TestCommon.RunAICLICommand("source reset", ""); + var result = TestCommon.RunAICLICommand("source reset", string.Empty); Assert.True(result.StdOut.Contains("The following sources will be reset if the --force option is given:")); Assert.True(result.StdOut.Contains(Constants.TestSourceName)); Assert.True(result.StdOut.Contains(Constants.TestSourceUrl)); } + /// <summary> + /// Test source reset force. + /// </summary> [Test] public void SourceForceReset() { @@ -128,8 +175,8 @@ namespace AppInstallerCLIE2ETests Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Resetting all sources...Done")); - //Verify sources have been reset - result = TestCommon.RunAICLICommand("source list", ""); + // Verify sources have been reset + result = TestCommon.RunAICLICommand("source list", string.Empty); Assert.True(result.StdOut.Contains("winget")); Assert.True(result.StdOut.Contains("https://cdn.winget.microsoft.com/cache")); Assert.False(result.StdOut.Contains(Constants.TestSourceName)); diff --git a/src/AppInstallerCLIE2ETests/TestCommon.cs b/src/AppInstallerCLIE2ETests/TestCommon.cs @@ -1,44 +1,109 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// ----------------------------------------------------------------------------- +// <copyright file="TestCommon.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- namespace AppInstallerCLIE2ETests { - using Microsoft.Win32; - using NUnit.Framework; using System; using System.Diagnostics; using System.IO; using System.Threading; + using Microsoft.Win32; + using NUnit.Framework; + /// <summary> + /// Test common. + /// </summary> public class TestCommon { + /// <summary> + /// Scope. + /// </summary> + public enum Scope + { + /// <summary> + /// User. + /// </summary> + User, + + /// <summary> + /// Machine. + /// </summary> + Machine, + } + + /// <summary> + /// Gets or sets the cli path. + /// </summary> public static string AICLIPath { get; set; } + /// <summary> + /// Gets or sets the package path. + /// </summary> public static string AICLIPackagePath { get; set; } + /// <summary> + /// Gets or sets a value indicating whether the test runs in package context. + /// </summary> public static bool PackagedContext { get; set; } + /// <summary> + /// Gets or sets a value indicating whether the test uses verbose logging. + /// </summary> public static bool VerboseLogging { get; set; } + /// <summary> + /// Gets or sets a value indicating whether to use loose file registration. + /// </summary> public static bool LooseFileRegistration { get; set; } + /// <summary> + /// Gets or sets a value indicating whether to invoke command in desktop package. + /// </summary> public static bool InvokeCommandInDesktopPackage { get; set; } + /// <summary> + /// Gets or sets the static file root path. + /// </summary> public static string StaticFileRootPath { get; set; } + /// <summary> + /// Gets or sets the exe installer path. + /// </summary> public static string ExeInstallerPath { get; set; } + /// <summary> + /// Gets or sets the msi installer path. + /// </summary> public static string MsiInstallerPath { get; set; } + /// <summary> + /// Gets or sets the msix installer path. + /// </summary> public static string MsixInstallerPath { get; set; } + /// <summary> + /// Gets or sets the zip installer path. + /// </summary> public static string ZipInstallerPath { get; set; } - + + /// <summary> + /// Gets or sets the package cert path. + /// </summary> public static string PackageCertificatePath { get; set; } + /// <summary> + /// Gets or sets the PowerShell module path. + /// </summary> public static string PowerShellModulePath { get; set; } - public static string SettingsJsonFilePath { + /// <summary> + /// Gets the settings json path. + /// </summary> + public static string SettingsJsonFilePath + { get { return PackagedContext ? @@ -47,26 +112,21 @@ namespace AppInstallerCLIE2ETests } } - public enum Scope - { - User, - Machine - } - - public struct RunCommandResult - { - public int ExitCode; - public string StdOut; - public string StdErr; - } - + /// <summary> + /// Run winget command. + /// </summary> + /// <param name="command">Command to run.</param> + /// <param name="parameters">Parameters.</param> + /// <param name="stdIn">Optional std in.</param> + /// <param name="timeOut">Optional timeout.</param> + /// <returns>The result of the command.</returns> public static RunCommandResult RunAICLICommand(string command, string parameters, string stdIn = null, int timeOut = 60000) { string inputMsg = "AICLI path: " + AICLIPath + " Command: " + command + " Parameters: " + parameters + - (string.IsNullOrEmpty(stdIn) ? "" : " StdIn: " + stdIn) + + (string.IsNullOrEmpty(stdIn) ? string.Empty : " StdIn: " + stdIn) + " Timeout: " + timeOut; TestContext.Out.WriteLine($"Starting command run. {inputMsg} InvokeCommandInDesktopPackage: {InvokeCommandInDesktopPackage}"); @@ -81,9 +141,17 @@ namespace AppInstallerCLIE2ETests } } + /// <summary> + /// Run winget command via direct process. + /// </summary> + /// <param name="command">Command to run.</param> + /// <param name="parameters">Parameters.</param> + /// <param name="stdIn">Optional std in.</param> + /// <param name="timeOut">Optional timeout.</param> + /// <returns>The result of the command.</returns> public static RunCommandResult RunAICLICommandViaDirectProcess(string command, string parameters, string stdIn = null, int timeOut = 60000) { - RunCommandResult result = new RunCommandResult(); + RunCommandResult result = new (); Process p = new Process(); p.StartInfo = new ProcessStartInfo(AICLIPath, command + ' ' + parameters); p.StartInfo.UseShellExecute = false; @@ -128,17 +196,24 @@ namespace AppInstallerCLIE2ETests return result; } - // This method is used when the test is run in an OS that does not support AppExecutionAlias. E,g, our build machine. - // There is not any existing API that'll activate a packaged app and wait for result, and not possible to capture the stdIn and stdOut. - // This method tries to call Invoke-CommandInDesktopPackage PS command to make test executable run in packaged context. - // Since Invoke-CommandInDesktopPackage just launches the executable and return, we use cmd pipe to get execution results. - // The final constructed command will look like: - // Invoke-CommandInDesktopPackage ...... -Command cmd.exe -Args '-c <cmd command>' - // where <cmd command> will look like: "echo stdIn | appinst.exe args > stdout.txt 2> stderr.txt & echo %ERRORLEVEL% > exitcode.txt" - // Then this method will read the piped result and return as RunCommandResult. + /// <summary> + /// This method is used when the test is run in an OS that does not support AppExecutionAlias. E,g, our build machine. + /// There is not any existing API that'll activate a packaged app and wait for result, and not possible to capture the stdIn and stdOut. + /// This method tries to call Invoke-CommandInDesktopPackage PS command to make test executable run in packaged context. + /// Since Invoke-CommandInDesktopPackage just launches the executable and return, we use cmd pipe to get execution results. + /// The final constructed command will look like: + /// Invoke-CommandInDesktopPackage ...... -Command cmd.exe -Args '-c [cmd command]' + /// where [cmd command] will look like: "echo stdIn | appinst.exe args > stdout.txt 2> stderr.txt &amp; echo %ERRORLEVEL% > exitcode.txt" + /// Then this method will read the piped result and return as RunCommandResult. + /// </summary> + /// <param name="command">Command to run.</param> + /// <param name="parameters">Parameters.</param> + /// <param name="stdIn">Optional std in.</param> + /// <param name="timeOut">Optional timeout.</param> + /// <returns>The result of the command.</returns> public static RunCommandResult RunAICLICommandViaInvokeCommandInDesktopPackage(string command, string parameters, string stdIn = null, int timeOut = 60000) { - string cmdCommandPiped = ""; + string cmdCommandPiped = string.Empty; if (!string.IsNullOrEmpty(stdIn)) { cmdCommandPiped += $"echo {stdIn} | "; @@ -177,7 +252,7 @@ namespace AppInstallerCLIE2ETests throw new TimeoutException($"Packaged winget command run timed out: {command} {parameters}"); } - RunCommandResult result = new RunCommandResult(); + RunCommandResult result = new (); // Sometimes the files are still in use; allow for this with a wait and retry loop. for (int retryCount = 0; retryCount < 4; ++retryCount) @@ -187,8 +262,8 @@ namespace AppInstallerCLIE2ETests try { result.ExitCode = File.Exists(exitCodeFile) ? int.Parse(File.ReadAllText(exitCodeFile).Trim()) : unchecked((int)0x80004005); - result.StdOut = File.Exists(stdOutFile) ? File.ReadAllText(stdOutFile) : ""; - result.StdErr = File.Exists(stdErrFile) ? File.ReadAllText(stdErrFile) : ""; + result.StdOut = File.Exists(stdOutFile) ? File.ReadAllText(stdOutFile) : string.Empty; + result.StdErr = File.Exists(stdErrFile) ? File.ReadAllText(stdErrFile) : string.Empty; success = true; } catch (Exception e) @@ -209,6 +284,13 @@ namespace AppInstallerCLIE2ETests return result; } + /// <summary> + /// Run command. + /// </summary> + /// <param name="fileName">File name.</param> + /// <param name="args">Args.</param> + /// <param name="timeOut">Time out.</param> + /// <returns>True if exit code is 0.</returns> public static bool RunCommand(string fileName, string args = "", int timeOut = 60000) { RunCommandResult result = RunCommandWithResult(fileName, args, timeOut); @@ -224,6 +306,13 @@ namespace AppInstallerCLIE2ETests } } + /// <summary> + /// Run command with result. + /// </summary> + /// <param name="fileName">File name.</param> + /// <param name="args">Args.</param> + /// <param name="timeOut">Optional timeout.</param> + /// <returns>Command result.</returns> public static RunCommandResult RunCommandWithResult(string fileName, string args, int timeOut = 60000) { TestContext.Out.WriteLine($"Running command: {fileName} {args}"); @@ -234,7 +323,7 @@ namespace AppInstallerCLIE2ETests p.StartInfo.RedirectStandardError = true; p.Start(); - RunCommandResult result = new RunCommandResult(); + RunCommandResult result = new (); if (p.WaitForExit(timeOut)) { result.ExitCode = p.ExitCode; @@ -254,21 +343,42 @@ namespace AppInstallerCLIE2ETests return result; } - public static RunCommandResult RunPowerShellCommandWithResult(string cmdlet, string args, int timeOut = 60000) + /// <summary> + /// Run PowerShell Core command with result. + /// </summary> + /// <param name="cmdlet">Cmdlet to run.</param> + /// <param name="args">Args.</param> + /// <param name="timeOut">Optional timeout.</param> + /// <returns>Command result.</returns> + public static RunCommandResult RunPowerShellCoreCommandWithResult(string cmdlet, string args, int timeOut = 60000) { return RunCommandWithResult("pwsh.exe", $"-Command ipmo {PowerShellModulePath}; {cmdlet} {args}", timeOut); } + /// <summary> + /// Get test file path. + /// </summary> + /// <param name="fileName">Test file name.</param> + /// <returns>Path of test file.</returns> public static string GetTestFile(string fileName) { return Path.Combine(TestContext.CurrentContext.TestDirectory, fileName); } + /// <summary> + /// Get test data file path. + /// </summary> + /// <param name="fileName">File name.</param> + /// <returns>Test file data path.</returns> public static string GetTestDataFile(string fileName) { return GetTestFile(Path.Combine("TestData", fileName)); } + /// <summary> + /// Get test work directory. Creates if not exists. + /// </summary> + /// <returns>The work directory.</returns> public static string GetTestWorkDir() { string workDir = Path.Combine(TestContext.CurrentContext.TestDirectory, "WorkDirectory"); @@ -276,6 +386,10 @@ namespace AppInstallerCLIE2ETests return workDir; } + /// <summary> + /// Create random test directory. + /// </summary> + /// <returns>Path of new test directory.</returns> public static string GetRandomTestDir() { string randDir = Path.Combine(GetTestWorkDir(), Path.GetRandomFileName()); @@ -283,27 +397,52 @@ namespace AppInstallerCLIE2ETests return randDir; } + /// <summary> + /// Creates new random file name. File is not created. + /// </summary> + /// <param name="extension">Extension of random file.</param> + /// <returns>Path of random file.</returns> public static string GetRandomTestFile(string extension) { return Path.Combine(GetTestWorkDir(), Path.GetRandomFileName() + extension); } + /// <summary> + /// Install msix package via PowerShell. + /// </summary> + /// <param name="file">Msix file.</param> + /// <returns>True if installed.</returns> public static bool InstallMsix(string file) { return RunCommand("powershell", $"Add-AppxPackage \"{file}\""); } + /// <summary> + /// Install and register msix package via appx manifest. + /// </summary> + /// <param name="packagePath">Path to package.</param> + /// <returns>True if installed correctly.</returns> public static bool InstallMsixRegister(string packagePath) { string manifestFile = Path.Combine(packagePath, "AppxManifest.xml"); return RunCommand("powershell", $"Add-AppxPackage -Register \"{manifestFile}\""); } + /// <summary> + /// Remove msix package. + /// </summary> + /// <param name="name">Package to remove.</param> + /// <returns>True if removed correctly.</returns> public static bool RemoveMsix(string name) { return RunCommand("powershell", $"Get-AppxPackage \"{name}\" | Remove-AppxPackage"); } + /// <summary> + /// Get portable symlink dir. + /// </summary> + /// <param name="scope">Scope.</param> + /// <returns>The path of the symlinks.</returns> public static string GetPortableSymlinkDirectory(Scope scope) { if (scope == Scope.User) @@ -316,17 +455,30 @@ namespace AppInstallerCLIE2ETests } } + /// <summary> + /// Get portable package directory. + /// </summary> + /// <returns>The portable package directory.</returns> public static string GetPortablePackagesDirectory() { return Path.Combine(Environment.GetEnvironmentVariable("LocalAppData"), "Microsoft", "WinGet", "Packages"); } + /// <summary> + /// Verify portable package. + /// </summary> + /// <param name="installDir">Install dir.</param> + /// <param name="commandAlias">Command alias.</param> + /// <param name="filename">File name.</param> + /// <param name="productCode">Product code.</param> + /// <param name="shouldExist">Should exists.</param> + /// <param name="scope">Scope.</param> public static void VerifyPortablePackage( string installDir, string commandAlias, string filename, string productCode, - bool shouldExist, + bool shouldExist, Scope scope = Scope.User) { string exePath = Path.Combine(installDir, filename); @@ -367,7 +519,7 @@ namespace AppInstallerCLIE2ETests } /// <summary> - /// Copies log files to the path %TEMP%\E2ETestLogs + /// Copies log files to the path %TEMP%\E2ETestLogs. /// </summary> public static void PublishE2ETestLogs() { @@ -399,11 +551,18 @@ namespace AppInstallerCLIE2ETests /// <summary> /// Gets the server certificate as a hex string. /// </summary> + /// <returns>Hex string.</returns> public static string GetTestServerCertificateHexString() { return Convert.ToHexString(File.ReadAllBytes(Path.Combine(StaticFileRootPath, Constants.TestSourceServerCertificateFileName))); } + /// <summary> + /// Verify exe installer correctly. + /// </summary> + /// <param name="installDir">Install directory.</param> + /// <param name="expectedContent">Optional expected content.</param> + /// <returns>True if success.</returns> public static bool VerifyTestExeInstalledAndCleanup(string installDir, string expectedContent = null) { bool verifyInstallSuccess = true; @@ -431,6 +590,11 @@ namespace AppInstallerCLIE2ETests return verifyInstallSuccess; } + /// <summary> + /// Verify msi installed correctly. + /// </summary> + /// <param name="installDir">Installed directory.</param> + /// <returns>True if success.</returns> public static bool VerifyTestMsiInstalledAndCleanup(string installDir) { string pathToCheck = Path.Combine(installDir, Constants.AppInstallerTestExeInstallerExe); @@ -443,6 +607,10 @@ namespace AppInstallerCLIE2ETests return RunCommand("msiexec.exe", $"/qn /x {Constants.MsiInstallerProductCode}"); } + /// <summary> + /// Verify msix installed correctly. + /// </summary> + /// <returns>True if success.</returns> public static bool VerifyTestMsixInstalledAndCleanup() { var result = RunCommandWithResult("powershell", $"Get-AppxPackage {Constants.MsixInstallerName}"); @@ -455,22 +623,42 @@ namespace AppInstallerCLIE2ETests return RemoveMsix(Constants.MsixInstallerName); } + /// <summary> + /// Verify test exe uninstalled. + /// </summary> + /// <param name="installDir">Installed directory.</param> + /// <returns>True if success.</returns> public static bool VerifyTestExeUninstalled(string installDir) { return File.Exists(Path.Combine(installDir, Constants.TestExeUninstalledFileName)); } + /// <summary> + /// Verify msi uninstalled. + /// </summary> + /// <param name="installDir">Install directory.</param> + /// <returns>True if success.</returns> public static bool VerifyTestMsiUninstalled(string installDir) { return !File.Exists(Path.Combine(installDir, Constants.AppInstallerTestExeInstallerExe)); } + /// <summary> + /// Verify msix uninstalled. + /// </summary> + /// <returns>True if success.</returns> public static bool VerifyTestMsixUninstalled() { var result = RunCommandWithResult("powershell", $"Get-AppxPackage {Constants.MsixInstallerName}"); return string.IsNullOrWhiteSpace(result.StdOut); } + /// <summary> + /// Modify uninstalled registry key. + /// </summary> + /// <param name="productCode">Product code.</param> + /// <param name="name">Name.</param> + /// <param name="value">Value.</param> public static void ModifyPortableARPEntryValue(string productCode, string name, string value) { const string uninstallSubKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall"; @@ -481,6 +669,10 @@ namespace AppInstallerCLIE2ETests } } + /// <summary> + /// Set up test source. + /// </summary> + /// <param name="useGroupPolicyForTestSource">Use group policy.</param> public static void SetupTestSource(bool useGroupPolicyForTestSource = false) { TestCommon.RunAICLICommand("source reset", "--force"); @@ -491,32 +683,33 @@ namespace AppInstallerCLIE2ETests // to enable testing it by default. Until then, leaving this here... if (useGroupPolicyForTestSource) { - GroupPolicyHelper.EnableAdditionalSources.SetEnabledList(new GroupPolicySource[] + GroupPolicyHelper.EnableAdditionalSources.SetEnabledList(new GroupPolicyHelper.GroupPolicySource[] { - new GroupPolicySource + new GroupPolicyHelper.GroupPolicySource { Name = Constants.TestSourceName, Arg = Constants.TestSourceUrl, Type = Constants.TestSourceType, Data = Constants.TestSourceIdentifier, Identifier = Constants.TestSourceIdentifier, - CertificatePinning = new GroupPolicyCertificatePinning + CertificatePinning = new GroupPolicyHelper.GroupPolicyCertificatePinning { - Chains = new GroupPolicyCertificatePinningChain[] { - new GroupPolicyCertificatePinningChain + Chains = new GroupPolicyHelper.GroupPolicyCertificatePinningChain[] + { + new GroupPolicyHelper.GroupPolicyCertificatePinningChain { - Chain = new GroupPolicyCertificatePinningDetails[] + Chain = new GroupPolicyHelper.GroupPolicyCertificatePinningDetails[] { - new GroupPolicyCertificatePinningDetails + new GroupPolicyHelper.GroupPolicyCertificatePinningDetails { Validation = new string[] { "publickey" }, - EmbeddedCertificate = TestCommon.GetTestServerCertificateHexString() - } - } - } - } - } - } + EmbeddedCertificate = TestCommon.GetTestServerCertificateHexString(), + }, + }, + }, + }, + }, + }, }); } else @@ -528,10 +721,34 @@ namespace AppInstallerCLIE2ETests Thread.Sleep(2000); } + /// <summary> + /// Tear down test source. + /// </summary> public static void TearDownTestSource() { RunAICLICommand("source remove", Constants.TestSourceName); RunAICLICommand("source reset", "--force"); } + + /// <summary> + /// Run command result. + /// </summary> + public struct RunCommandResult + { + /// <summary> + /// Exit code. + /// </summary> + public int ExitCode; + + /// <summary> + /// StdOut. + /// </summary> + public string StdOut; + + /// <summary> + /// StdErr. + /// </summary> + public string StdErr; + } } } diff --git a/src/AppInstallerCLIE2ETests/TestHashHelper.cs b/src/AppInstallerCLIE2ETests/TestHashHelper.cs @@ -1,24 +1,49 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -using System; -using System.IO; -using System.Security.Cryptography; -using Microsoft.Msix.Utils.ProcessRunner; +// ----------------------------------------------------------------------------- +// <copyright file="TestHashHelper.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- namespace AppInstallerCLIE2ETests { + using System; + using System.IO; + using System.Security.Cryptography; + using Microsoft.Msix.Utils.ProcessRunner; + + /// <summary> + /// TestHashHelper. + /// </summary> public class TestHashHelper { + /// <summary> + /// Gets or sets the exe installer hash value. + /// </summary> public static string ExeInstallerHashValue { get; set; } + /// <summary> + /// Gets or sets the msi installer hash value. + /// </summary> public static string MsiInstallerHashValue { get; set; } + /// <summary> + /// Gets or sets the msix installer hash value. + /// </summary> public static string MsixInstallerHashValue { get; set; } + /// <summary> + /// Gets or sets the zip installer hash value. + /// </summary> public static string ZipInstallerHashValue { get; set; } + /// <summary> + /// Gets or sets the signature hash value. + /// </summary> public static string SignatureHashValue { get; set; } + /// <summary> + /// Sets the hash of the installers. + /// </summary> public static void HashInstallers() { if (!string.IsNullOrEmpty(TestCommon.ExeInstallerPath)) @@ -44,10 +69,10 @@ namespace AppInstallerCLIE2ETests } /// <summary> - /// Iterates through all manifest files in a directory and replaces the hash <token> with the + /// Iterates through all manifest files in a directory and replaces the hash token with the /// corresponding installer hash token. /// </summary> - /// <param name="pathToManifestDir"></param> + /// <param name="pathToManifestDir">Path to manifest directory.</param> public static void ReplaceManifestHashToken(string pathToManifestDir) { var dir = new DirectoryInfo(pathToManifestDir); @@ -86,6 +111,11 @@ namespace AppInstallerCLIE2ETests } } + /// <summary> + /// Gets hash of the AppxSignature.p7x file in the msix. + /// </summary> + /// <param name="packageFilePath">Package file path.</param> + /// <returns>Hash of signature file.</returns> public static string HashSignatureFromMSIX(string packageFilePath) { // Obtain MakeAppX Executable Path @@ -111,6 +141,11 @@ namespace AppInstallerCLIE2ETests return HashFile(packageSignaturePath); } + /// <summary> + /// Gets the hash of the specified file. + /// </summary> + /// <param name="filePath">File path.</param> + /// <returns>Hash of file.</returns> public static string HashFile(string filePath) { FileInfo file; @@ -152,6 +187,11 @@ namespace AppInstallerCLIE2ETests return hash; } + /// <summary> + /// Converts the byte hash into its string format. + /// </summary> + /// <param name="array">Hash.</param> + /// <returns>Hash as string.</returns> public static string ConvertHashByteToString(byte[] array) { string hashValue = string.Empty; diff --git a/src/AppInstallerCLIE2ETests/TestIndexSetup.cs b/src/AppInstallerCLIE2ETests/TestIndexSetup.cs @@ -1,13 +1,20 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -using System; -using System.Diagnostics; -using System.IO; -using System.IO.Compression; -using Microsoft.Msix.Utils.ProcessRunner; +// ----------------------------------------------------------------------------- +// <copyright file="TestIndexSetup.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- namespace AppInstallerCLIE2ETests { + using System; + using System.Diagnostics; + using System.IO; + using System.IO.Compression; + using Microsoft.Msix.Utils.ProcessRunner; + + /// <summary> + /// Test index setup. + /// </summary> public class TestIndexSetup { private const string TestDataName = "TestData"; @@ -21,7 +28,7 @@ namespace AppInstallerCLIE2ETests /// 2. Copies and signs installer files (EXE or MSIX) /// 3. Hashes installer Files /// 4. Replaces manifests with corresponding hash values - /// 5. Generates a source package for TestData using makeappx/signtool + /// 5. Generates a source package for TestData using makeappx/signtool. /// </summary> public static void GenerateTestDirectory() { @@ -52,6 +59,95 @@ namespace AppInstallerCLIE2ETests SetupSourcePackage(); } + /// <summary> + /// Sign a file using signtool.exe . + /// </summary> + /// <param name="filePath">File to sign.</param> + public static void SignFile(string filePath) + { + string pathToSDK = SDKDetector.Instance.LatestSDKBinPath; + string signtoolExecutable = Path.Combine(pathToSDK, "signtool.exe"); + RunCommand(signtoolExecutable, $"sign /a /fd sha256 /f {TestCommon.PackageCertificatePath} {filePath}"); + } + + /// <summary> + /// Deletes the contents of a given directory. + /// </summary> + /// <param name="directory">Directory info.</param> + public static void DeleteDirectoryContents(DirectoryInfo directory) + { + foreach (FileInfo file in directory.GetFiles()) + { + // Leave the server certificate file if present + if (file.Name.ToLower() != Constants.TestSourceServerCertificateFileName) + { + file.Delete(); + } + } + + foreach (DirectoryInfo dir in directory.GetDirectories()) + { + dir.Delete(true); + } + } + + /// <summary> + /// Copies the contents of a given directory from a source path to a destination path. + /// </summary> + /// <param name="sourceDirName">Source directory name.</param> + /// <param name="destDirName">Destination directory name.</param> + public static void CopyDirectory(string sourceDirName, string destDirName) + { + DirectoryInfo dir = new DirectoryInfo(sourceDirName); + DirectoryInfo[] dirs = dir.GetDirectories(); + + if (!Directory.Exists(destDirName)) + { + Directory.CreateDirectory(destDirName); + } + + FileInfo[] files = dir.GetFiles(); + foreach (FileInfo file in files) + { + string temppath = Path.Combine(destDirName, file.Name); + file.CopyTo(temppath, false); + } + + foreach (DirectoryInfo subdir in dirs) + { + string temppath = Path.Combine(destDirName, subdir.Name); + CopyDirectory(subdir.FullName, temppath); + } + } + + /// <summary> + /// Run a command. + /// </summary> + /// <param name="command">Command.</param> + /// <param name="args">Arguments.</param> + public static void RunCommand(string command, string args) + { + Process p = new Process(); + p.StartInfo = new ProcessStartInfo(command, args); + p.Start(); + p.WaitForExit(); + } + + /// <summary> + /// Run a command from a working directory. + /// </summary> + /// <param name="command">Command.</param> + /// <param name="args">Arguments.</param> + /// <param name="workingDirectory">Working directory.</param> + public static void RunCommand(string command, string args, string workingDirectory) + { + Process p = new Process(); + p.StartInfo = new ProcessStartInfo(command, args); + p.StartInfo.WorkingDirectory = workingDirectory; + p.Start(); + p.WaitForExit(); + } + private static void SetupSourcePackage() { string indexDestPath = Path.Combine(TestCommon.StaticFileRootPath, PackageName, PublicName); @@ -67,7 +163,7 @@ namespace AppInstallerCLIE2ETests { if (!Directory.Exists(indexDestPath)) { - Directory.CreateDirectory(indexDestPath); + Directory.CreateDirectory(indexDestPath); } // Generate Index.db file using IndexCreationTool.exe @@ -159,7 +255,7 @@ namespace AppInstallerCLIE2ETests File.Copy(TestCommon.MsixInstallerPath, msixInstallerSourceDestPath, true); } - string destArchiveFullPath = Path.Combine(zipInstallerDir.FullName, Constants.ZipInstallerFileName); ; + string destArchiveFullPath = Path.Combine(zipInstallerDir.FullName, Constants.ZipInstallerFileName); ZipFile.CreateFromDirectory(zipSourceDirFullPath, destArchiveFullPath); TestCommon.ZipInstallerPath = destArchiveFullPath; } @@ -175,79 +271,5 @@ namespace AppInstallerCLIE2ETests CopyDirectory(sourcePath, TestCommon.StaticFileRootPath); } - - public static void SignFile(string filePath) - { - string pathToSDK = SDKDetector.Instance.LatestSDKBinPath; - string signtoolExecutable = Path.Combine(pathToSDK, "signtool.exe"); - RunCommand(signtoolExecutable, $"sign /a /fd sha256 /f {TestCommon.PackageCertificatePath} {filePath}"); - } - - /// <summary> - /// Deletes the contents of a given directory - /// </summary> - /// <param name="directory"></param> - public static void DeleteDirectoryContents(DirectoryInfo directory) - { - foreach (FileInfo file in directory.GetFiles()) - { - // Leave the server certificate file if present - if (file.Name.ToLower() != Constants.TestSourceServerCertificateFileName) - { - file.Delete(); - } - } - - foreach (DirectoryInfo dir in directory.GetDirectories()) - { - dir.Delete(true); - } - } - - /// <summary> - /// Copies the contents of a given directory from a source path to a destination path - /// </summary> - /// <param name="sourceDirName"></param> - /// <param name="destDirName"></param> - public static void CopyDirectory(string sourceDirName, string destDirName) - { - DirectoryInfo dir = new DirectoryInfo(sourceDirName); - DirectoryInfo[] dirs = dir.GetDirectories(); - - if (!Directory.Exists(destDirName)) - { - Directory.CreateDirectory(destDirName); - } - - FileInfo[] files = dir.GetFiles(); - foreach (FileInfo file in files) - { - string temppath = Path.Combine(destDirName, file.Name); - file.CopyTo(temppath, false); - } - - foreach (DirectoryInfo subdir in dirs) - { - string temppath = Path.Combine(destDirName, subdir.Name); - CopyDirectory(subdir.FullName, temppath); - } - } - - public static void RunCommand(string command, string args) - { - Process p = new Process(); - p.StartInfo = new ProcessStartInfo(command, args); - p.Start(); - p.WaitForExit(); - } - - public static void RunCommand (string command, string args, string workingDirectory) - { - Process p = new Process(); - p.StartInfo = new ProcessStartInfo(command, args); - p.StartInfo.WorkingDirectory = workingDirectory; - p.Start(); - p.WaitForExit(); - } } } diff --git a/src/AppInstallerCLIE2ETests/UninstallCommand.cs b/src/AppInstallerCLIE2ETests/UninstallCommand.cs @@ -1,66 +1,84 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace AppInstallerCLIE2ETests -{ - using NUnit.Framework; +// ----------------------------------------------------------------------------- +// <copyright file="UninstallCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ using System.IO; + using NUnit.Framework; + + /// <summary> + /// Test uninstall command. + /// </summary> + public class UninstallCommand : BaseCommand + { + // Custom product code for overriding the default in the test exe + private const string CustomProductCode = "{f08fc03c-0b7e-4fca-9b3c-3a384d18a9f3}"; + + // File written when uninstalling the test exe + private const string UninstallTestExeUninstalledFile = "TestExeUninstalled.txt"; + + // Name of a file installed by the MSI that will be removed during uninstall + private const string UninstallTestMsiInstalledFile = "AppInstallerTestExeInstaller.exe"; + + // Package name of the test MSIX package + private const string UninstallTestMsixName = "6c6338fe-41b7-46ca-8ba6-b5ad5312bb0e"; + + /// <summary> + /// Test uninstall exe. + /// </summary> + [Test] + public void UninstallTestExe() + { + // Uninstall an Exe + var installDir = TestCommon.GetRandomTestDir(); + TestCommon.RunAICLICommand("install", $"{Constants.ExeInstallerPackageId} --silent -l {installDir}"); + var result = TestCommon.RunAICLICommand("uninstall", Constants.ExeInstallerPackageId); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully uninstalled")); + Assert.True(TestCommon.VerifyTestExeUninstalled(installDir)); + } + + /// <summary> + /// Test uninstall msi. + /// </summary> + [Test] + public void UninstallTestMsi() + { + if (string.IsNullOrEmpty(TestCommon.MsiInstallerPath)) + { + Assert.Ignore("MSI installer not available"); + } + + // Uninstall an MSI + var installDir = TestCommon.GetRandomTestDir(); + TestCommon.RunAICLICommand("install", $"{Constants.MsiInstallerPackageId} -l {installDir}"); + var result = TestCommon.RunAICLICommand("uninstall", Constants.MsiInstallerPackageId); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully uninstalled")); + Assert.True(TestCommon.VerifyTestMsiUninstalled(installDir)); + } + + /// <summary> + /// Test uninstall msix. + /// </summary> + [Test] + public void UninstallTestMsix() + { + // Uninstall an MSIX + TestCommon.RunAICLICommand("install", Constants.MsixInstallerPackageId); + var result = TestCommon.RunAICLICommand("uninstall", Constants.MsixInstallerPackageId); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully uninstalled")); + Assert.True(TestCommon.VerifyTestMsixUninstalled()); + } - public class UninstallCommand : BaseCommand - { - // Custom product code for overriding the default in the test exe - private const string CustomProductCode = "{f08fc03c-0b7e-4fca-9b3c-3a384d18a9f3}"; - - // File written when uninstalling the test exe - private const string UninstallTestExeUninstalledFile = "TestExeUninstalled.txt"; - - // Name of a file installed by the MSI that will be removed during uninstall - private const string UninstallTestMsiInstalledFile = "AppInstallerTestExeInstaller.exe"; - - // Package name of the test MSIX package - private const string UninstallTestMsixName = "6c6338fe-41b7-46ca-8ba6-b5ad5312bb0e"; - - [Test] - public void UninstallTestExe() - { - // Uninstall an Exe - var installDir = TestCommon.GetRandomTestDir(); - TestCommon.RunAICLICommand("install", $"{Constants.ExeInstallerPackageId} --silent -l {installDir}"); - var result = TestCommon.RunAICLICommand("uninstall", Constants.ExeInstallerPackageId); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully uninstalled")); - Assert.True(TestCommon.VerifyTestExeUninstalled(installDir)); - } - - [Test] - public void UninstallTestMsi() - { - if (string.IsNullOrEmpty(TestCommon.MsiInstallerPath)) - { - Assert.Ignore("MSI installer not available"); - } - - // Uninstall an MSI - var installDir = TestCommon.GetRandomTestDir(); - TestCommon.RunAICLICommand("install", $"{Constants.MsiInstallerPackageId} -l {installDir}"); - var result = TestCommon.RunAICLICommand("uninstall", Constants.MsiInstallerPackageId); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully uninstalled")); - Assert.True(TestCommon.VerifyTestMsiUninstalled(installDir)); - } - - [Test] - public void UninstallTestMsix() - { - // Uninstall an MSIX - TestCommon.RunAICLICommand("install", Constants.MsixInstallerPackageId); - var result = TestCommon.RunAICLICommand("uninstall", Constants.MsixInstallerPackageId); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully uninstalled")); - Assert.True(TestCommon.VerifyTestMsixUninstalled()); - } - - [Test] + /// <summary> + /// Test uninstall portable package. + /// </summary> + [Test] public void UninstallPortable() { // Uninstall a Portable @@ -72,12 +90,15 @@ namespace AppInstallerCLIE2ETests TestCommon.RunAICLICommand("install", $"{packageId}"); var result = TestCommon.RunAICLICommand("uninstall", $"{packageId}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully uninstalled")); TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); } - [Test] + /// <summary> + /// Test uninstall portable package with product code. + /// </summary> + [Test] public void UninstallPortableWithProductCode() { // Uninstall a Portable with ProductCode @@ -89,11 +110,14 @@ namespace AppInstallerCLIE2ETests TestCommon.RunAICLICommand("install", $"{packageId}"); var result = TestCommon.RunAICLICommand("uninstall", $"--product-code {productCode}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully uninstalled")); TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); } + /// <summary> + /// Test uninstall portable package with modified symlink. + /// </summary> [Test] public void UninstallPortableModifiedSymlink() { @@ -113,7 +137,7 @@ namespace AppInstallerCLIE2ETests FileSystemInfo modifiedSymlinkInfo = File.CreateSymbolicLink(symlinkPath, "fakeTargetExe"); var result = TestCommon.RunAICLICommand("uninstall", $"{packageId}"); - Assert.AreEqual(Constants.ErrorCode.ERROR_PORTABLE_UNINSTALL_FAILED, result.ExitCode); + Assert.AreEqual(Constants.ErrorCode.ERROR_PORTABLE_UNINSTALL_FAILED, result.ExitCode); Assert.True(result.StdOut.Contains("Unable to remove Portable package as it has been modified; to override this check use --force")); Assert.True(modifiedSymlinkInfo.Exists, "Modified symlink should still exist"); @@ -124,9 +148,12 @@ namespace AppInstallerCLIE2ETests Assert.True(result2.StdOut.Contains("Successfully uninstalled")); TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); - } - - [Test] + } + + /// <summary> + /// Test uninstall zip portable package. + /// </summary> + [Test] public void UninstallZip_Portable() { string installDir = TestCommon.GetPortablePackagesDirectory(); @@ -138,33 +165,38 @@ namespace AppInstallerCLIE2ETests var testResult = TestCommon.RunAICLICommand("install", $"{packageId}"); var result = TestCommon.RunAICLICommand("uninstall", $"{packageId}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully uninstalled")); TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); - } - - - [Test] - public void UninstallNotIndexed() - { - // Uninstalls a package found with ARP not matching any known manifest. - // Install the test EXE providing a custom Product Code so that it cannot be mapped - // back to its manifest, then uninstall it using its Product Code - var installDir = TestCommon.GetRandomTestDir(); - TestCommon.RunAICLICommand("install", $"{Constants.ExeInstallerPackageId} --override \"/ProductID {CustomProductCode} /InstallDir {installDir}"); - var result = TestCommon.RunAICLICommand("uninstall", CustomProductCode); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully uninstalled")); - Assert.True(TestCommon.VerifyTestExeUninstalled(installDir)); - } - - [Test] - public void UninstallAppNotInstalled() - { - // Verify failure when trying to uninstall an app that is not installed. - var result = TestCommon.RunAICLICommand("uninstall", $"TestMsixInstaller"); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); - Assert.True(result.StdOut.Contains("No installed package found matching input criteria.")); - } - } -} + } + + /// <summary> + /// Test uninstall not indexed. + /// </summary> + [Test] + public void UninstallNotIndexed() + { + // Uninstalls a package found with ARP not matching any known manifest. + // Install the test EXE providing a custom Product Code so that it cannot be mapped + // back to its manifest, then uninstall it using its Product Code + var installDir = TestCommon.GetRandomTestDir(); + TestCommon.RunAICLICommand("install", $"{Constants.ExeInstallerPackageId} --override \"/ProductID {CustomProductCode} /InstallDir {installDir}"); + var result = TestCommon.RunAICLICommand("uninstall", CustomProductCode); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully uninstalled")); + Assert.True(TestCommon.VerifyTestExeUninstalled(installDir)); + } + + /// <summary> + /// Test uninstalled app not found. + /// </summary> + [Test] + public void UninstallAppNotInstalled() + { + // Verify failure when trying to uninstall an app that is not installed. + var result = TestCommon.RunAICLICommand("uninstall", $"TestMsixInstaller"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("No installed package found matching input criteria.")); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/UpgradeCommand.cs b/src/AppInstallerCLIE2ETests/UpgradeCommand.cs @@ -1,13 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// ----------------------------------------------------------------------------- +// <copyright file="UpgradeCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- namespace AppInstallerCLIE2ETests { - using NUnit.Framework; using System.IO; + using NUnit.Framework; + /// <summary> + /// Test upgrade command. + /// </summary> public class UpgradeCommand : BaseCommand { + /// <summary> + /// Test upgrade portable package. + /// </summary> [Test] public void UpgradePortable() { @@ -20,13 +29,16 @@ namespace AppInstallerCLIE2ETests var result = TestCommon.RunAICLICommand("install", "AppInstallerTest.TestPortableExe -v 1.0.0.0"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully installed")); - + var result2 = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 2.0.0.0"); Assert.AreEqual(Constants.ErrorCode.S_OK, result2.ExitCode); Assert.True(result2.StdOut.Contains("Successfully installed")); TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); } + /// <summary> + /// Test upgrade portable package with arp mismatch. + /// </summary> [Test] public void UpgradePortableARPMismatch() { @@ -50,6 +62,9 @@ namespace AppInstallerCLIE2ETests Assert.True(upgradeResult.StdOut.Contains("Portable package from a different source already exists")); } + /// <summary> + /// Test upgrade portable package force override. + /// </summary> [Test] public void UpgradePortableForcedOverride() { @@ -73,6 +88,9 @@ namespace AppInstallerCLIE2ETests TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); } + /// <summary> + /// Test upgrade portable package uninstall previous version. + /// </summary> [Test] public void UpgradePortableUninstallPrevious() { @@ -92,11 +110,14 @@ namespace AppInstallerCLIE2ETests TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); } + /// <summary> + /// Test upgrade portable package machine scope. + /// </summary> [Test] public void UpgradePortableMachineScope() { string installDir = TestCommon.GetRandomTestDir(); - ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, installDir); + this.ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, installDir); string packageId, commandAlias, fileName, packageDirName, productCode; packageId = "AppInstallerTest.TestPortableExe"; @@ -108,12 +129,15 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("Successfully installed")); var result2 = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 2.0.0.0"); - ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, string.Empty); + this.ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, string.Empty); Assert.AreEqual(Constants.ErrorCode.S_OK, result2.ExitCode); Assert.True(result2.StdOut.Contains("Successfully installed")); TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true, TestCommon.Scope.Machine); } + /// <summary> + /// Test upgrade zip portable package. + /// </summary> [Test] public void UpgradeZip_Portable() { diff --git a/src/AppInstallerCLIE2ETests/ValidateCommand.cs b/src/AppInstallerCLIE2ETests/ValidateCommand.cs @@ -1,13 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// ----------------------------------------------------------------------------- +// <copyright file="ValidateCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- namespace AppInstallerCLIE2ETests { using NUnit.Framework; + /// <summary> + /// Test validate command. + /// </summary> public class ValidateCommand : BaseCommand { - [Test] + /// <summary> + /// Test validate manifest. + /// </summary> + [Test] public void ValidateManifest() { var result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestValidManifest.yaml")); @@ -15,6 +24,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("Manifest validation succeeded.")); } + /// <summary> + /// Test validate manifest with extended characters. + /// </summary> [Test] public void ValidateManifestWithExtendedCharacter() { @@ -23,6 +35,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("Manifest validation succeeded.")); } + /// <summary> + /// Test validate invalid manifest. + /// </summary> [Test] public void ValidateInvalidManifest() { @@ -31,6 +46,9 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("Manifest validation failed.")); } + /// <summary> + /// Test validate manifest that doesn't exist. + /// </summary> [Test] public void ValidateManifestDoesNotExist() { diff --git a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilCompareVersions.cs b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilCompareVersions.cs @@ -1,24 +1,36 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - +// ----------------------------------------------------------------------------- +// <copyright file="WinGetUtilCompareVersions.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.WinGetUtil { using NUnit.Framework; + /// <summary> + /// Test winget util compare versions. + /// </summary> public class WinGetUtilCompareVersions { + /// <summary> + /// Test compare versions. + /// </summary> + /// <param name="version1">Version 1.</param> + /// <param name="version2">Version 2.</param> + /// <param name="expectedResult">Expected result.</param> [Test] - // V1 = V2 + //// V1 = V2 [TestCase("1.0.0.0", "1.0.0.0", 0)] [TestCase("1.0.0", "1.0.0.0", 0)] [TestCase("1.0", "1.0.0.0", 0)] [TestCase("1", "1.0.0.0", 0)] - // V1 > V2 + //// V1 > V2 [TestCase("1.0.0.1", "1.0.0.0", 1)] [TestCase("1.0.1.0", "1.0.0.0", 1)] [TestCase("1.1.0.0", "1.0.0.0", 1)] [TestCase("2.0.0.0", "1.0.0.0", 1)] - // V1 < V2 + //// V1 < V2 [TestCase("1.0.0.0", "1.0.0.1", -1)] [TestCase("1.0.0.0", "1.0.1.0", -1)] [TestCase("1.0.0.0", "1.1.0.0", -1)] diff --git a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilDownload.cs b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilDownload.cs @@ -1,5 +1,8 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// ----------------------------------------------------------------------------- +// <copyright file="WinGetUtilDownload.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- namespace AppInstallerCLIE2ETests.WinGetUtil { @@ -7,8 +10,14 @@ namespace AppInstallerCLIE2ETests.WinGetUtil using System.Linq; using NUnit.Framework; + /// <summary> + /// Test winget util download. + /// </summary> public class WinGetUtilDownload { + /// <summary> + /// Test download. + /// </summary> [Test] public void WinGetUtil_Download() { diff --git a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilInstallerMetadataCollection.cs b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilInstallerMetadataCollection.cs @@ -1,16 +1,25 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - +// ----------------------------------------------------------------------------- +// <copyright file="WinGetUtilInstallerMetadataCollection.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.WinGetUtil { - using Newtonsoft.Json; - using NUnit.Framework; using System; using System.IO; using System.Runtime.InteropServices; + using Newtonsoft.Json; + using NUnit.Framework; + /// <summary> + /// Test winget util installer metadata. + /// </summary> public class WinGetUtilInstallerMetadataCollection { + /// <summary> + /// Test begin complete installer metadata. + /// </summary> [Test] public void WinGetUtil_BeginCompleteInstallerMetadataCollection() { @@ -36,6 +45,9 @@ namespace AppInstallerCLIE2ETests.WinGetUtil Assert.IsNotEmpty(JsonConvert.DeserializeObject(outputJson).ToString()); } + /// <summary> + /// Test merge installer metadata. + /// </summary> [Test] public void WinGetUtil_MergeInstallerMetadata_Success() { @@ -54,6 +66,9 @@ namespace AppInstallerCLIE2ETests.WinGetUtil Assert.IsNotEmpty(JsonConvert.DeserializeObject(outputJson).ToString()); } + /// <summary> + /// Test merge installer metadata failed. + /// </summary> [Test] public void WinGetUtil_MergeInstallerMetadata_Fail_SubmissionMismatch() { diff --git a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilLog.cs b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilLog.cs @@ -1,13 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - +// ----------------------------------------------------------------------------- +// <copyright file="WinGetUtilLog.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.WinGetUtil { using System.IO; using NUnit.Framework; + /// <summary> + /// Test winget util log. + /// </summary> public class WinGetUtilLog { + /// <summary> + /// Test logging functions. + /// </summary> [Test] public void WinGetUtil_Logging() { diff --git a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilManifest.cs b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilManifest.cs @@ -1,16 +1,25 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - +// ----------------------------------------------------------------------------- +// <copyright file="WinGetUtilManifest.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.WinGetUtil { using System; using System.IO; using NUnit.Framework; + /// <summary> + /// Test winget util manifest. + /// </summary> public class WinGetUtilManifest { private IntPtr indexHandle; + /// <summary> + /// Set up. + /// </summary> [SetUp] public void SetUp() { @@ -18,15 +27,22 @@ namespace AppInstallerCLIE2ETests.WinGetUtil var sqliteFile = TestCommon.GetRandomTestFile(".db"); uint majorVersion = 1; uint minorVersion = 2; - WinGetUtilWrapper.WinGetSQLiteIndexCreate(sqliteFile, majorVersion, minorVersion, out this.indexHandle); ; + WinGetUtilWrapper.WinGetSQLiteIndexCreate(sqliteFile, majorVersion, minorVersion, out this.indexHandle); } + /// <summary> + /// Tear down. + /// </summary> [TearDown] public void TearDown() { WinGetUtilWrapper.WinGetSQLiteIndexClose(this.indexHandle); } + /// <summary> + /// Test validate manifest. + /// </summary> + /// <param name="createManifestOption">Create manifest options.</param> [Test] [TestCase(WinGetUtilWrapper.CreateManifestOption.NoValidation)] [TestCase(WinGetUtilWrapper.CreateManifestOption.SchemaAndSemanticValidation)] @@ -52,7 +68,7 @@ namespace AppInstallerCLIE2ETests.WinGetUtil // Validate manifest WinGetUtilWrapper.WinGetValidateManifestV3( manifestHandle, - indexHandle, + this.indexHandle, out WinGetUtilWrapper.ValidateManifestResultCode resultCode, out string validateFailureMessage, WinGetUtilWrapper.ValidateManifestOptionV2.ArpVersionValidation, diff --git a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilSQLiteIndex.cs b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilSQLiteIndex.cs @@ -1,5 +1,8 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// ----------------------------------------------------------------------------- +// <copyright file="WinGetUtilSQLiteIndex.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- namespace AppInstallerCLIE2ETests.WinGetUtil { @@ -8,109 +11,137 @@ namespace AppInstallerCLIE2ETests.WinGetUtil using System.Runtime.InteropServices; using NUnit.Framework; + /// <summary> + /// WinGetUtil sql index tests. + /// </summary> public class WinGetUtilSQLiteIndex { - private string sqlitePath; private readonly uint majorVersion = 1; private readonly uint minorVersion = 2; // Manifest example 1 - private readonly string addManifestsFile_1 = TestCommon.GetTestDataFile(@"WinGetUtil\Manifests\Merged\WinGetUtilTest.Add.yaml"); - private readonly string updateManifestsFile_1 = TestCommon.GetTestDataFile(@"WinGetUtil\Manifests\Merged\WinGetUtilTest.Update.yaml"); - private readonly string relativePath_1 = @"manifests\a\AppInstallerTest\WinGetUtilTest\1.0.0.0\WinGetTest.yaml"; + private readonly string addManifestsFile = TestCommon.GetTestDataFile(@"WinGetUtil\Manifests\Merged\WinGetUtilTest.Add.yaml"); + private readonly string updateManifestsFile = TestCommon.GetTestDataFile(@"WinGetUtil\Manifests\Merged\WinGetUtilTest.Update.yaml"); + private readonly string relativePath = @"manifests\a\AppInstallerTest\WinGetUtilTest\1.0.0.0\WinGetTest.yaml"; + + private string sqlitePath; + /// <summary> + /// Set up. + /// </summary> [SetUp] public void SetUp() { this.sqlitePath = TestCommon.GetRandomTestFile(".sql"); } + /// <summary> + /// Test add manifest. + /// </summary> [Test] public void WinGetUtil_SQLiteIndex_AddManifest() { - SQLiteIndex((indexHandle) => + this.SQLiteIndex((indexHandle) => { // Add manifest - WinGetUtilWrapper.WinGetSQLiteIndexAddManifest(indexHandle, addManifestsFile_1, relativePath_1); + WinGetUtilWrapper.WinGetSQLiteIndexAddManifest(indexHandle, this.addManifestsFile, this.relativePath); }); } + /// <summary> + /// Test update manifest. + /// </summary> [Test] public void WinGetUtil_SQLiteIndex_UpdateManifest_Success() { - SQLiteIndex((indexHandle) => + this.SQLiteIndex((indexHandle) => { // Add manifest - WinGetUtilWrapper.WinGetSQLiteIndexAddManifest(indexHandle, addManifestsFile_1, relativePath_1); - + WinGetUtilWrapper.WinGetSQLiteIndexAddManifest(indexHandle, this.addManifestsFile, this.relativePath); + // Update manifest - WinGetUtilWrapper.WinGetSQLiteIndexUpdateManifest(indexHandle, updateManifestsFile_1, relativePath_1, out bool indexModified); + WinGetUtilWrapper.WinGetSQLiteIndexUpdateManifest(indexHandle, this.updateManifestsFile, this.relativePath, out bool indexModified); Assert.True(indexModified); }); } + /// <summary> + /// Test update manifest file not found. + /// </summary> [Test] public void WinGetUtil_SQLiteIndex_UpdateManifest_Fail_NotFound() { - SQLiteIndex((indexHandle) => + this.SQLiteIndex((indexHandle) => { // Update non-existing manifest Assert.Throws<COMException>(() => { - WinGetUtilWrapper.WinGetSQLiteIndexUpdateManifest(indexHandle, updateManifestsFile_1, relativePath_1, out bool indexModified); + WinGetUtilWrapper.WinGetSQLiteIndexUpdateManifest(indexHandle, this.updateManifestsFile, this.relativePath, out bool indexModified); }); }); } + /// <summary> + /// Test remove manifest. + /// </summary> [Test] public void WinGetUtil_SQLiteIndex_RemoveManifest_Success() { - SQLiteIndex((indexHandle) => + this.SQLiteIndex((indexHandle) => { // Add manifest - WinGetUtilWrapper.WinGetSQLiteIndexAddManifest(indexHandle, addManifestsFile_1, relativePath_1); - + WinGetUtilWrapper.WinGetSQLiteIndexAddManifest(indexHandle, this.addManifestsFile, this.relativePath); + // Remove manifest - WinGetUtilWrapper.WinGetSQLiteIndexRemoveManifest(indexHandle, addManifestsFile_1, relativePath_1); + WinGetUtilWrapper.WinGetSQLiteIndexRemoveManifest(indexHandle, this.addManifestsFile, this.relativePath); }); } + /// <summary> + /// Test remove manifest file not found. + /// </summary> [Test] public void WinGetUtil_SQLiteIndex_RemoveManifest_Fail_NotFound() { - SQLiteIndex((indexHandle) => + this.SQLiteIndex((indexHandle) => { // Remove non-existing manifest Assert.Throws<COMException>(() => { - WinGetUtilWrapper.WinGetSQLiteIndexRemoveManifest(indexHandle, addManifestsFile_1, relativePath_1); + WinGetUtilWrapper.WinGetSQLiteIndexRemoveManifest(indexHandle, this.addManifestsFile, this.relativePath); }); }); } + /// <summary> + /// Test open and closing index. + /// </summary> [Test] public void WinGetUtil_SQLiteIndex_OpenClose() { - SQLiteIndex((_) => + this.SQLiteIndex((_) => { // Open - WinGetUtilWrapper.WinGetSQLiteIndexOpen(sqlitePath, out IntPtr indexHandle); + WinGetUtilWrapper.WinGetSQLiteIndexOpen(this.sqlitePath, out IntPtr indexHandle); // Add manifest - WinGetUtilWrapper.WinGetSQLiteIndexAddManifest(indexHandle, addManifestsFile_1, relativePath_1); + WinGetUtilWrapper.WinGetSQLiteIndexAddManifest(indexHandle, this.addManifestsFile, this.relativePath); // Close WinGetUtilWrapper.WinGetSQLiteIndexClose(indexHandle); }); } + /// <summary> + /// Test check consistency. + /// </summary> [Test] public void WinGetUtil_SQLiteIndex_CheckConsistency() { - SQLiteIndex((indexHandle) => + this.SQLiteIndex((indexHandle) => { // Add manifest - WinGetUtilWrapper.WinGetSQLiteIndexAddManifest(indexHandle, addManifestsFile_1, relativePath_1); + WinGetUtilWrapper.WinGetSQLiteIndexAddManifest(indexHandle, this.addManifestsFile, this.relativePath); // Prepare for packaging WinGetUtilWrapper.WinGetSQLiteIndexPrepareForPackaging(indexHandle); @@ -124,16 +155,16 @@ namespace AppInstallerCLIE2ETests.WinGetUtil /// <summary> /// Create and close an sqlite index file. /// </summary> - /// <param name="Execute">Function to execute.</param> - private void SQLiteIndex(Action<IntPtr> Execute) + /// <param name="execute">Function to execute.</param> + private void SQLiteIndex(Action<IntPtr> execute) { // Create - WinGetUtilWrapper.WinGetSQLiteIndexCreate(sqlitePath, majorVersion, minorVersion, out IntPtr indexHandle); - Assert.True(File.Exists(sqlitePath)); + WinGetUtilWrapper.WinGetSQLiteIndexCreate(this.sqlitePath, this.majorVersion, this.minorVersion, out IntPtr indexHandle); + Assert.True(File.Exists(this.sqlitePath)); Assert.AreNotEqual(IntPtr.Zero, indexHandle); - + // Execute provided function - Execute(indexHandle); + execute(indexHandle); // Close WinGetUtilWrapper.WinGetSQLiteIndexClose(indexHandle); diff --git a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilWrapper.cs b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilWrapper.cs @@ -1,6 +1,9 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - +// ----------------------------------------------------------------------------- +// <copyright file="WinGetUtilWrapper.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.WinGetUtil { using System; @@ -14,76 +17,228 @@ namespace AppInstallerCLIE2ETests.WinGetUtil { private const string DllName = @"WinGetUtil.dll"; + /// <summary> + /// Create manifest flags. + /// </summary> [Flags] public enum CreateManifestOption { + /// <summary> + /// No validation. + /// </summary> NoValidation = 0, + + /// <summary> + /// Schema validation. + /// </summary> SchemaValidation = 0x1, + + /// <summary> + /// Schema and semantic validation. + /// </summary> SchemaAndSemanticValidation = 0x2, + + /// <summary> + /// Return error on verified publisher. + /// </summary> ReturnErrorOnVerifiedPublisherFields = 0x1000, } + /// <summary> + /// Validate manifests results. + /// </summary> [Flags] public enum ValidateManifestResultCode { + /// <summary> + /// Success. + /// </summary> Success = 0, + + /// <summary> + /// Dependencies validation failure. + /// </summary> DependenciesValidationFailure = 0x1, + + /// <summary> + /// Arp version validation failure. + /// </summary> ArpVersionValidationFailure = 0x2, + + /// <summary> + /// Installer validation failure. + /// </summary> InstallerValidationFailure = 0x4, + + /// <summary> + /// Single manifest package has dependencies. + /// </summary> SingleManifestPackageHasDependencies = 0x10000, + + /// <summary> + /// Multi manifest package has dependencies. + /// </summary> MultiManifestPackageHasDependencies = 0x20000, + + /// <summary> + /// Missing manifest dependencies. + /// </summary> MissingManifestDependenciesNode = 0x40000, + + /// <summary> + /// No suitable min version dependencies. + /// </summary> NoSuitableMinVersionDependency = 0x80000, + + /// <summary> + /// Found dependency loop. + /// </summary> FoundDependencyLoop = 0x100000, + + /// <summary> + /// Internal error. + /// </summary> InternalError = 0x1000, } + /// <summary> + /// ValidateManifestOptionV2 flags. + /// </summary> [Flags] public enum ValidateManifestOptionV2 { + /// <summary> + /// None. + /// </summary> None = 0, + + /// <summary> + /// Dependencies validation. + /// </summary> DependenciesValidation = 0x1, + + /// <summary> + /// Arp version validation. + /// </summary> ArpVersionValidation = 0x2, + + /// <summary> + /// Installer validation. + /// </summary> InstallerValidation = 0x4, } + /// <summary> + /// Validate manifest operation type. + /// </summary> public enum ValidateManifestOperationType { + /// <summary> + /// Add. + /// </summary> Add = 0, + + /// <summary> + /// Update. + /// </summary> Update = 1, + + /// <summary> + /// Delete. + /// </summary> Delete = 2, } + /// <summary> + /// Begin installer metadata collection options. + /// </summary> public enum WinGetBeginInstallerMetadataCollectionOptions { + /// <summary> + /// None. + /// </summary> WinGetBeginInstallerMetadataCollectionOption_None = 0, + + /// <summary> + /// Input is file path. + /// </summary> WinGetBeginInstallerMetadataCollectionOption_InputIsFilePath = 0x1, + + /// <summary> + /// Input is URI. + /// </summary> WinGetBeginInstallerMetadataCollectionOption_InputIsURI = 0x2, - }; + } + /// <summary> + /// Complete installer metadata collection. + /// </summary> public enum WinGetCompleteInstallerMetadataCollectionOptions { + /// <summary> + /// None. + /// </summary> WinGetCompleteInstallerMetadataCollectionOption_None = 0, + + /// <summary> + /// Abandon. + /// </summary> WinGetCompleteInstallerMetadataCollectionOption_Abandon = 0x1, - }; + } + /// <summary> + /// Merge installer metadata. + /// </summary> public enum WinGetMergeInstallerMetadataOptions { + /// <summary> + /// None. + /// </summary> WinGetMergeInstallerMetadataOptions_None = 0, - }; + } + /// <summary> + /// WinGetCompareVersion from wingetutil.dll . + /// </summary> + /// <param name="version1">Version.</param> + /// <param name="version2">Other version.</param> + /// <param name="comparisonResult">Result of comparison.</param> [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetCompareVersions(string version1, string version2, [MarshalAs(UnmanagedType.U4)] out int comparisonResult); + /// <summary> + /// WinGetDownload from wingetutil.dll . + /// </summary> + /// <param name="url">Url.</param> + /// <param name="filePath">File path where to download.</param> + /// <param name="sha256Hash">SHA256 hash.</param> + /// <param name="sha256HashLength">SHA256 hash length.</param> [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] - public static extern void WinGetDownload(string url, string filePath, [MarshalAs(UnmanagedType.LPArray)] byte[] sha26Hash, uint sha256HashLength); + public static extern void WinGetDownload(string url, string filePath, [MarshalAs(UnmanagedType.LPArray)] byte[] sha256Hash, uint sha256HashLength); + /// <summary> + /// WinGetLoggingInit from wingetutil.dll . + /// </summary> + /// <param name="logPath">Log path.</param> [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetLoggingInit(string logPath); + /// <summary> + /// WinGetLoggingTerm from wingetutil.dll . + /// </summary> + /// <param name="logPath">Log path.</param> [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetLoggingTerm(string logPath); + /// <summary> + /// WinGetCreateManifest from wingetutil.dll . + /// </summary> + /// <param name="inputPath">Input path.</param> + /// <param name="succeeded">Succeeded.</param> + /// <param name="manifestHandle">Manifest handle.</param> + /// <param name="failureMessage">Failure message.</param> + /// <param name="mergedManifestPath">Merge manifest path.</param> + /// <param name="option">Option.</param> [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetCreateManifest( string inputPath, @@ -93,9 +248,22 @@ namespace AppInstallerCLIE2ETests.WinGetUtil string mergedManifestPath, CreateManifestOption option); + /// <summary> + /// WinGetCloseManifest from wingetutil.dll . + /// </summary> + /// <param name="manifest">Manifest.</param> [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetCloseManifest(IntPtr manifest); + /// <summary> + /// WinGetValidateManifestV3 from wingetutil.dll . + /// </summary> + /// <param name="manifestHandle">Manifest handle.</param> + /// <param name="indexHandle">Index handle.</param> + /// <param name="result">Result.</param> + /// <param name="failureMessage">Failure message.</param> + /// <param name="option">Option.</param> + /// <param name="operationType">Operation type.</param> [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetValidateManifestV3( IntPtr manifestHandle, @@ -105,18 +273,47 @@ namespace AppInstallerCLIE2ETests.WinGetUtil ValidateManifestOptionV2 option, ValidateManifestOperationType operationType); + /// <summary> + /// WinGetSQLiteIndexCreate from wingetutil.dll . + /// </summary> + /// <param name="filePath">File path.</param> + /// <param name="majorVersion">Major version.</param> + /// <param name="minorVersion">Minor version.</param> + /// <param name="index">Index.</param> [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetSQLiteIndexCreate(string filePath, uint majorVersion, uint minorVersion, out IntPtr index); + /// <summary> + /// WinGetSQLiteIndexOpen from wingetutil.dll . + /// </summary> + /// <param name="filePath">File path.</param> + /// <param name="index">Index.</param> [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetSQLiteIndexOpen(string filePath, out IntPtr index); + /// <summary> + /// WinGetSQLiteIndexClose from wingetutil.dll . + /// </summary> + /// <param name="index">Index.</param> [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetSQLiteIndexClose(IntPtr index); + /// <summary> + /// WinGetSQLiteIndexAddManifest from wingetutil.dll . + /// </summary> + /// <param name="index">Index.</param> + /// <param name="manifestPath">Manifest path.</param> + /// <param name="relativePath">Relative path.</param> [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetSQLiteIndexAddManifest(IntPtr index, string manifestPath, string relativePath); + /// <summary> + /// WinGetSQLiteIndexUpdateManifest from wingetutil.dll . + /// </summary> + /// <param name="index">Index.</param> + /// <param name="manifestPath">Manifest path.</param> + /// <param name="relativePath">Relative path.</param> + /// <param name="indexModified">Index modified.</param> [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetSQLiteIndexUpdateManifest( IntPtr index, @@ -124,15 +321,37 @@ namespace AppInstallerCLIE2ETests.WinGetUtil string relativePath, [MarshalAs(UnmanagedType.U1)] out bool indexModified); + /// <summary> + /// WinGetSQLiteIndexRemoveManifest from wingetutil.dll . + /// </summary> + /// <param name="index">Index.</param> + /// <param name="manifestPath">Manifest path.</param> + /// <param name="relativePath">Relative path.</param> [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetSQLiteIndexRemoveManifest(IntPtr index, string manifestPath, string relativePath); + /// <summary> + /// WinGetSQLiteIndexPrepareForPackaging from wingetutil.dll . + /// </summary> + /// <param name="index">Index.</param> [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetSQLiteIndexPrepareForPackaging(IntPtr index); + /// <summary> + /// WinGetSQLiteIndexCheckConsistency from wingetutil.dll . + /// </summary> + /// <param name="index">Index.</param> + /// <param name="succeeded">Succeeded.</param> [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetSQLiteIndexCheckConsistency(IntPtr index, [MarshalAs(UnmanagedType.U1)] out bool succeeded); + /// <summary> + /// WinGetBeginInstallerMetadataCollection from wingetutil.dll . + /// </summary> + /// <param name="inputJSON">Input json.</param> + /// <param name="logFilePath">Log file path.</param> + /// <param name="options">Options.</param> + /// <param name="collectionHandle">Collection handle.</param> [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetBeginInstallerMetadataCollection( string inputJSON, @@ -140,12 +359,26 @@ namespace AppInstallerCLIE2ETests.WinGetUtil WinGetBeginInstallerMetadataCollectionOptions options, out IntPtr collectionHandle); + /// <summary> + /// WinGetCompleteInstallerMetadataCollection from wingetutil.dll . + /// </summary> + /// <param name="collectionHandle">Collection handle.</param> + /// <param name="outputFilePath">Output file path.</param> + /// <param name="options">Options.</param> [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetCompleteInstallerMetadataCollection( IntPtr collectionHandle, string outputFilePath, WinGetCompleteInstallerMetadataCollectionOptions options); + /// <summary> + /// WinGetMergeInstallerMetadata from wingetutil.dll . + /// </summary> + /// <param name="inputJSON">Input json.</param> + /// <param name="outputJSON">Output json.</param> + /// <param name="maximumOutputSizeInBytes">Maximum output size in bytes.</param> + /// <param name="logFilePath">Log file path.</param> + /// <param name="options">Options.</param> [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetMergeInstallerMetadata( string inputJSON, diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/BasePackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/BasePackageCommand.cs @@ -14,7 +14,7 @@ namespace Microsoft.WinGet.Client.Common using Microsoft.WinGet.Client.Errors; /// <summary> - /// This is the base class for commands which operate on a specific package & version i.e., + /// This is the base class for commands which operate on a specific package and version i.e., /// the "install", "uninstall", and "upgrade" commands. /// </summary> public class BasePackageCommand : BaseFinderCommand @@ -25,7 +25,7 @@ namespace Microsoft.WinGet.Client.Common /// Gets or sets the package to directly install. /// </summary> /// <remarks> - /// Must match the name of the <see cref="Deployment.CatalogPackage" /> field on the <see cref="MatchResult" /> class. + /// Must match the name of the <see cref="CatalogPackage" /> field on the <see cref="MatchResult" /> class. /// </remarks> [Alias("InputObject")] [ValidateNotNull] diff --git a/src/PowerShell/Microsoft.WinGet.Client/Crescendo/Create-CrescendoFunctions.ps1 b/src/PowerShell/Microsoft.WinGet.Client/Crescendo/Create-CrescendoFunctions.ps1 @@ -0,0 +1,53 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# + .SYNOPSIS + Creates crescendo module for Microsoft.WinGet.Client and merge module manifests. + + .PARAMETER ConfigurationFile + The crescendo configuration file. + + .PARAMETER ModuleName + The name of the module to be created. + + .PARAMETER ModuleOutputDirectory + Where to output the crescendo output files. +#> + +[CmdletBinding()] +param ( + [Parameter(Mandatory)] + [string] + $ConfigurationFile, + + [Parameter(Mandatory)] + [string] + $ModuleName, + + [Parameter(Mandatory)] + [string] + $ModuleOutputDirectory +) + +if (-not (Get-Module Microsoft.PowerShell.Crescendo)) +{ + Install-Module Microsoft.PowerShell.Crescendo -Force +} + +$dir = $pwd +Set-Location $PSScriptRoot +Write-Host "Generating crescendo module" +Export-CrescendoModule -ConfigurationFile $ConfigurationFile -ModuleName $ModuleName -Force +Set-Location $dir + +Copy-Item "$PSScriptRoot\$ModuleName.psm1" "$ModuleOutputDirectory\Microsoft.WinGet.Client.psm1" -Force -ErrorAction Stop + +# In a perfect world we would check if $ModuleOutputDirectory\$ModuleName.psd1 exists and if it does then load the data +# via Import-PowerShellDataFile and make sure FunctionsToExport contains all the exported functions from the generated +# psd1 file of the Export-CrescendoModule command. We have dynamic expressions on ..\Module\Microsoft.WinGet.Client.psd1 +# so that can't happen easily, so we will just nicely remind you :( +$config = Import-PowerShellDataFile -Path "$PSScriptRoot\$ModuleName.psd1" + +Write-Host "Crescendo module generated. Please verify the FunctionsToExport is updated in ..\Module\Microsoft.WinGet.Client.psd1 if needed" +Write-Host "Generated FunctionsToExport $($config.FunctionsToExport)" diff --git a/src/PowerShell/Microsoft.WinGet.Client/Crescendo.json b/src/PowerShell/Microsoft.WinGet.Client/Crescendo/Crescendo.json diff --git a/src/PowerShell/Microsoft.WinGet.Client/Microsoft.WinGet.Client.csproj b/src/PowerShell/Microsoft.WinGet.Client/Microsoft.WinGet.Client.csproj @@ -5,23 +5,24 @@ <CoreFramework>net6.0-windows$(TargetWindowsVersion)</CoreFramework> <DesktopFramework>net48</DesktopFramework> </PropertyGroup> - + <PropertyGroup> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> - <LangVersion>9</LangVersion> + <LangVersion>10</LangVersion> <BuildOutputDirectory>$(SolutionDir)$(Platform)\$(Configuration)\</BuildOutputDirectory> <OutputPath>$(BuildOutputDirectory)$(MSBuildProjectName)</OutputPath> - <PowerShellModuleOutputDirectory>$(BuildOutputDirectory)PowerShell</PowerShellModuleOutputDirectory> + <PowerShellModuleOutputDirectory>$(BuildOutputDirectory)PowerShell\Microsoft.WinGet.Client</PowerShellModuleOutputDirectory> <Platforms>x64;x86;ARM64</Platforms> <TargetFrameworks>$(CoreFramework);$(DesktopFramework)</TargetFrameworks> + <DocumentationFile>$(OutputPath)\Microsoft.WinGet.Client.xml</DocumentationFile> </PropertyGroup> <PropertyGroup Condition="'$(WingetUseProdClsids)' == 'true'"> <DefineConstants>USE_PROD_CLSIDS</DefineConstants> </PropertyGroup> - + <ItemGroup> - <AdditionalFiles Include="stylecop.json" /> + <AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" /> </ItemGroup> <ItemGroup> @@ -44,81 +45,92 @@ </ProjectReference> </ItemGroup> - <ItemGroup Condition="'$(TargetFramework)' == '$(CoreFramework)'"> - <PackageReference Include="Microsoft.Windows.CsWinRT" Version="1.6.5" /> - </ItemGroup> + <PropertyGroup> + <!-- Disable warning for CS1591 because auto generated Microsoft.Management.Deployment --> + <!-- For our code, we still have stylecops acting on it. --> + <NoWarn>1591</NoWarn> + </PropertyGroup> + + <PropertyGroup Condition="'$(Configuration)'=='Release'"> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + </PropertyGroup> + + <ItemGroup Condition="'$(TargetFramework)' == '$(CoreFramework)'"> + <PackageReference Include="Microsoft.Windows.CsWinRT" Version="1.6.5" /> + </ItemGroup> - <ItemGroup Condition="'$(TargetFramework)' == '$(DesktopFramework)'"> + <ItemGroup Condition="'$(TargetFramework)' == '$(DesktopFramework)'"> <PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.22000.196" PrivateAssets="all" /> - </ItemGroup> + </ItemGroup> - <ItemGroup> - <Compile Update="Properties\Resources.Designer.cs"> - <DesignTime>True</DesignTime> - <AutoGen>True</AutoGen> - <DependentUpon>Resources.resx</DependentUpon> - </Compile> - </ItemGroup> - - <ItemGroup> - <EmbeddedResource Update="Properties\Resources.resx"> - <Generator>ResXFileCodeGenerator</Generator> - <LastGenOutput>Resources.Designer.cs</LastGenOutput> - </EmbeddedResource> - </ItemGroup> - - <PropertyGroup Condition="'$(TargetFramework)' == '$(CoreFramework)'"> - <CsWinRTIncludes>Microsoft.Management.Deployment</CsWinRTIncludes> - <CsWinRTWindowsMetadata>$(TargetWindowsVersion)</CsWinRTWindowsMetadata> + <ItemGroup> + <Compile Update="Properties\Resources.Designer.cs"> + <DesignTime>True</DesignTime> + <AutoGen>True</AutoGen> + <DependentUpon>Resources.resx</DependentUpon> + </Compile> + </ItemGroup> + + <ItemGroup> + <EmbeddedResource Update="Properties\Resources.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>Resources.Designer.cs</LastGenOutput> + </EmbeddedResource> + </ItemGroup> + + <PropertyGroup Condition="'$(TargetFramework)' == '$(CoreFramework)'"> + <CsWinRTIncludes>Microsoft.Management.Deployment</CsWinRTIncludes> + <CsWinRTWindowsMetadata>$(TargetWindowsVersion)</CsWinRTWindowsMetadata> <!-- Ensure Support for Windows 10, Version 1809 --> <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion> - </PropertyGroup> - - <!-- Copy WinRTAct.dll to PowerShell module directory --> - <PropertyGroup Condition="'$(Platform)' == 'x64'"> - <TargetPlatform>x64</TargetPlatform> - </PropertyGroup> - - <PropertyGroup Condition="'$(Platform)' == 'x86'"> - <TargetPlatform>Win32</TargetPlatform> - </PropertyGroup> - - <PropertyGroup Condition="'$(Platform)' == 'ARM64'"> - <TargetPlatform>Arm64</TargetPlatform> - </PropertyGroup> - - <Target Name="CopyWinRTAct" AfterTargets="Build" Condition="'$(Platform)' != 'ARM'"> - <ItemGroup> - <WinRTActDll Include="$(SolutionDir)**\$(TargetPlatform)\$(Configuration)\UndockedRegFreeWinRT\winrtact.dll" /> - </ItemGroup> - <Message Importance="high" Text="Copying '@(WinRTActDll)'" /> - <Copy SourceFiles="@(WinRTActDll)" DestinationFolder="$(OutputPath)" /> - <Copy SourceFiles="@(WinRTActDll)" DestinationFolder="$(PowerShellModuleOutputDirectory)\$(Platform)\Core" /> - <Copy SourceFiles="@(WinRTActDll)" DestinationFolder="$(PowerShellModuleOutputDirectory)\$(Platform)\Desktop" /> - </Target> - - <!-- Build Microsoft.Winget.Client PowerShell Module --> - <Target Name="CopyModuleFiles" AfterTargets="AfterBuild"> - <Message Importance="high" Text="Copying PowerShell module files to '$(PowerShellModuleOutputDirectory)'" /> - <Copy SourceFiles="Format.ps1xml" DestinationFolder="$(PowerShellModuleOutputDirectory)" /> - <Copy SourceFiles="Microsoft.WinGet.Client.psd1" DestinationFolder="$(PowerShellModuleOutputDirectory)" /> - <Copy SourceFiles="Microsoft.WinGet.Client.psm1" DestinationFolder="$(PowerShellModuleOutputDirectory)" /> - </Target> - - <Target Name="CopyCoreBinaries" AfterTargets="AfterBuild" Condition="'$(TargetFramework)' == '$(CoreFramework)'"> - <ItemGroup> - <CoreBinaries Include="$(OutputPath)*" /> - </ItemGroup> - <Message Importance="high" Text="Copying @(CoreBinaries) to '$(PowerShellModuleOutputDirectory)\$(Platform)\Core'" /> - <Copy SourceFiles="@(CoreBinaries)" DestinationFolder="$(PowerShellModuleOutputDirectory)\$(Platform)\Core" /> - </Target> - - <Target Name="CopyDesktopBinaries" AfterTargets="AfterBuild" Condition="'$(TargetFramework)' == '$(DesktopFramework)'"> - <ItemGroup> - <DesktopBinaries Include="$(OutputPath)*" /> - </ItemGroup> - <Message Importance="high" Text="Copying @(DesktopBinaries) to '$(PowerShellModuleOutputDirectory)\$(Platform)\Desktop'" /> - <Copy SourceFiles="@(DesktopBinaries)" DestinationFolder="$(PowerShellModuleOutputDirectory)\$(Platform)\Desktop" /> - </Target> - -</Project>- \ No newline at end of file + </PropertyGroup> + + <!-- Copy WinRTAct.dll to PowerShell module directory --> + <PropertyGroup Condition="'$(Platform)' == 'x64'"> + <TargetPlatform>x64</TargetPlatform> + </PropertyGroup> + + <PropertyGroup Condition="'$(Platform)' == 'x86'"> + <TargetPlatform>Win32</TargetPlatform> + </PropertyGroup> + + <PropertyGroup Condition="'$(Platform)' == 'ARM64'"> + <TargetPlatform>Arm64</TargetPlatform> + </PropertyGroup> + + <Target Name="CopyWinRTAct" AfterTargets="Build" Condition="'$(Platform)' != 'ARM'"> + <ItemGroup> + <WinRTActDll Include="$(SolutionDir)**\$(TargetPlatform)\$(Configuration)\UndockedRegFreeWinRT\winrtact.dll" /> + </ItemGroup> + <Message Importance="high" Text="Copying '@(WinRTActDll)'" /> + <Copy SourceFiles="@(WinRTActDll)" DestinationFolder="$(OutputPath)" /> + <Copy SourceFiles="@(WinRTActDll)" DestinationFolder="$(PowerShellModuleOutputDirectory)\$(Platform)\Core" /> + <Copy SourceFiles="@(WinRTActDll)" DestinationFolder="$(PowerShellModuleOutputDirectory)\$(Platform)\Desktop" /> + </Target> + + <!-- Build Microsoft.Winget.Client PowerShell Module --> + <Target Name="CopyModuleFiles" AfterTargets="AfterBuild"> + <ItemGroup> + <ModuleFiles Include="$(ProjectDir)\Module\**\*.*" /> + </ItemGroup> + <Message Importance="high" Text="Copying PowerShell module files to '$(PowerShellModuleOutputDirectory)'" /> + <Copy SourceFiles="@(ModuleFiles)" DestinationFiles="$(PowerShellModuleOutputDirectory)\%(RecursiveDir)%(Filename)%(Extension)" /> + </Target> + + <Target Name="CopyCoreBinaries" AfterTargets="AfterBuild" Condition="'$(TargetFramework)' == '$(CoreFramework)'"> + <ItemGroup> + <CoreBinaries Include="$(OutputPath)*" /> + </ItemGroup> + <Message Importance="high" Text="Copying @(CoreBinaries) to '$(PowerShellModuleOutputDirectory)\$(Platform)\Core'" /> + <Copy SourceFiles="@(CoreBinaries)" DestinationFolder="$(PowerShellModuleOutputDirectory)\$(Platform)\Core" /> + </Target> + + <Target Name="CopyDesktopBinaries" AfterTargets="AfterBuild" Condition="'$(TargetFramework)' == '$(DesktopFramework)'"> + <ItemGroup> + <DesktopBinaries Include="$(OutputPath)*" /> + </ItemGroup> + <Message Importance="high" Text="Copying @(DesktopBinaries) to '$(PowerShellModuleOutputDirectory)\$(Platform)\Desktop'" /> + <Copy SourceFiles="@(DesktopBinaries)" DestinationFolder="$(PowerShellModuleOutputDirectory)\$(Platform)\Desktop" /> + </Target> + +</Project> diff --git a/src/PowerShell/Microsoft.WinGet.Client/Format.ps1xml b/src/PowerShell/Microsoft.WinGet.Client/Module/Format.ps1xml diff --git a/src/PowerShell/Microsoft.WinGet.Client/Microsoft.WinGet.Client.psd1 b/src/PowerShell/Microsoft.WinGet.Client/Module/Microsoft.WinGet.Client.psd1 diff --git a/src/PowerShell/Microsoft.WinGet.Client/Microsoft.WinGet.Client.psm1 b/src/PowerShell/Microsoft.WinGet.Client/Module/Microsoft.WinGet.Client.psm1 diff --git a/src/PowerShell/Microsoft.WinGet.Client/stylecop.json b/src/PowerShell/Microsoft.WinGet.Client/stylecop.json @@ -1,10 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", - "settings": { - "documentationRules": { - "companyName": "Microsoft Corporation", - "copyrightText": " Copyright (c) {companyName}. Licensed under the MIT License.", - "headerDecoration": "-----------------------------------------------------------------------------" - } - } -} diff --git a/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 b/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 @@ -0,0 +1,90 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# + .SYNOPSIS + Helper script to setup the modules locally. + - Copies the PowerShell modules output into this location. + - Copies the modules files from the project because there's no guarantee they are updated in the module output + location. + - Adds the module location to PSModulePath if not there. + - Import Microsoft.WinGet.* modules. + + .PARAMETER Platform + The platform we are building for. + + .PARAMETER Configuration + The configuration we are building in. +#> + +[CmdletBinding()] +param ( + [Parameter(Mandatory)] + [string] + $Platform, + + [Parameter(Mandatory)] + [string] + $Configuration +) + +class WinGetModule +{ + [string]$Name + [string]$ModuleRoot + [bool]$HasBinary + + WinGetModule([string]$n, [string]$m, [bool]$b) + { + $this.Name = $n + $this.ModuleRoot = $m + $this.HasBinary = $b + } +} + +# I know it makes sense, but please don't do a clean up of $moduleRootOutput. When the modules are loaded +# there's no way to tell PowerShell to release the binary dlls that are loaded. +$moduleRootOutput = "$PSScriptRoot\Module\" + +# Add here new modules +[WinGetModule[]]$modules = + [WinGetModule]::new("Microsoft.WinGet.Client", "$PSScriptRoot\..\Microsoft.WinGet.Client\Module\", $true) + +foreach($module in $modules) +{ + # Import-Module with Force just changes functions in the root module, not any nested ones. There's no way to load any + # updated classes. To ensure that you are running the latest version run Remove-Module + if (Get-Module -ListAvailable -Name $module.Name) + { + Write-Host "Removing module $($module.Name)" -ForegroundColor Green + Remove-Module $module.Name -Force + } + + # Use xcopy to copy only files that have changed. + if ($module.HasBinary) + { + # Copy output files from VS. + Write-Host "Coping binary module $($module.Name)" -ForegroundColor Green + xcopy "$PSScriptRoot\..\..\$Platform\$Configuration\PowerShell\$($module.Name)\" "$moduleRootOutput\$($module.Name)\" /d /s /f /y + } + + # Copy PowerShell files even for modules with binary resources. + # VS won't update the files if there's nothing to build... + Write-Host "Coping module $($module.Name)" -ForegroundColor Green + xcopy $module.ModuleRoot "$moduleRootOutput\$($module.Name)\" /d /s /f /y + +} + +# Add it to module path if not there. +if (-not $env:PSModulePath.Contains($moduleRootOutput)) +{ + Write-Host "Added $moduleRootOutput to PSModulePath" -ForegroundColor Green + $env:PSModulePath += ";$moduleRootOutput" +} + +# Now import modules. +foreach($module in $modules) +{ + Write-Host "Importing module $($module.Name)" -ForegroundColor Green + Import-Module $module.Name -Force +} diff --git a/src/binver/Update-BinVer.ps1 b/src/binver/Update-BinVer.ps1 @@ -91,3 +91,5 @@ if (![String]::IsNullOrEmpty($TargetFile)) Write-Error "Did not find target file: $TargetFile" } } + +exit 0 diff --git a/src/stylecop.json b/src/stylecop.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "maintainabilityRules": { + "topLevelTypes": [ "class", "interface" ] + }, + "documentationRules": { + "companyName": "Microsoft Corporation", + "copyrightText": " Copyright (c) {companyName}. Licensed under the MIT License.", + "headerDecoration": "-----------------------------------------------------------------------------" + } + } +} diff --git a/templates/e2e-test.template.yml b/templates/e2e-test.template.yml @@ -26,7 +26,7 @@ steps: -MsixTestInstallerPath $(Build.ArtifactStagingDirectory)\AppInstallerTestMsixInstaller.msix -ExeTestInstallerPath $(buildOutDir)\AppInstallerTestExeInstaller\AppInstallerTestExeInstaller.exe -PackageCertificatePath $(AppInstallerTest.secureFilePath) - -PowerShellModulePath $(buildOutDir)\PowerShell\Microsoft.WinGet.Client.psd1' + -PowerShellModulePath $(buildOutDir)\PowerShell\Microsoft.WinGet.Client\Microsoft.WinGet.Client.psd1' ${{ else }}: overrideTestrunParameters: '-PackagedContext false -AICLIPath $(System.DefaultWorkingDirectory)\src\AppInstallerCLIPackage\bin\$(buildPlatform)\$(buildConfiguration)\AppInstallerCLI\winget.exe @@ -36,4 +36,4 @@ steps: -MsixTestInstallerPath $(Build.ArtifactStagingDirectory)\AppInstallerTestMsixInstaller.msix -ExeTestInstallerPath $(buildOutDir)\AppInstallerTestExeInstaller\AppInstallerTestExeInstaller.exe -PackageCertificatePath $(AppInstallerTest.secureFilePath) - -PowerShellModulePath $(buildOutDir)\PowerShell\Microsoft.WinGet.Client.psd1' + -PowerShellModulePath $(buildOutDir)\PowerShell\Microsoft.WinGet.Client\Microsoft.WinGet.Client.psd1'