Unit tests for Dotnet MSI

- Needs a clean machine without dotnet MSI installed for the tests to run.
- Needs admin privileges to run. Else test script exits silently.
- These xunit based tests run on Netfx46.

For now these tests are disabled until I figure out the right way to run
them in the CI machines.
This commit is contained in:
Sridhar Periyasamy 2015-11-24 17:48:58 -08:00
parent 9ce2063452
commit e131be5aaa
13 changed files with 559 additions and 1 deletions

View file

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{16614B7F-5CA3-45AE-95C2-003AB39CC09F}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Dotnet.Cli.Msi.Tests</RootNamespace>
<AssemblyName>Dotnet.Cli.Msi.Tests</AssemblyName>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="InstallationTests.cs" />
<Compile Include="InstallFixture.cs" />
<Compile Include="MsiManager.cs" />
<Compile Include="PostInstallTests.cs" />
<Compile Include="PostUninstallTests.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Utils.cs" />
</ItemGroup>
<ItemGroup>
<None Include="project.json" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(NuGetPackageRoot)' == ''">
<NuGetPackageRoot>C:\Users\sridhper\.nuget\packages\</NuGetPackageRoot>
</PropertyGroup>
<ImportGroup>
<Import Project="$(NuGetPackageRoot)\WiX\3.10.0.2103-pre1\build\wix.props" Condition="Exists('$(NuGetPackageRoot)\WiX\3.10.0.2103-pre1\build\wix.props')" />
</ImportGroup>
</Project>

View file

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace Dotnet.Cli.Msi.Tests
{
public class InstallFixture : IDisposable
{
private MsiManager _msiMgr = null;
// all the tests assume that the msi to be tested is available via environment variable %CLI_MSI%
public InstallFixture()
{
string msiFile = Environment.GetEnvironmentVariable("CLI_MSI");
_msiMgr = new MsiManager(msiFile);
// make sure that the msi is not already installed, if so the machine is in a bad state
Assert.False(_msiMgr.IsInstalled, "The dotnet CLI msi is already installed");
_msiMgr.Install(InstallLocation);
Assert.True(_msiMgr.IsInstalled);
}
public MsiManager MsiManager
{
get
{
return _msiMgr;
}
}
public string InstallLocation
{
get
{
return Environment.ExpandEnvironmentVariables(@"%SystemDrive%\dotnet\");
}
}
public void Dispose()
{
if (!_msiMgr.IsInstalled)
{
return;
}
_msiMgr.UnInstall();
Assert.False(_msiMgr.IsInstalled, "Unable to cleanup by uninstalling dotnet");
}
}
}

View file

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Xunit;
[assembly: CollectionBehavior(DisableTestParallelization = true)]
namespace Dotnet.Cli.Msi.Tests
{
public class InstallationTests : IDisposable
{
private string _msiFile;
private MsiManager _msiMgr;
public InstallationTests()
{
// all the tests assume that the msi to be tested is available via environment variable %CLI_MSI%
_msiFile = Environment.GetEnvironmentVariable("CLI_MSI");
if(string.IsNullOrEmpty(_msiFile))
{
throw new InvalidOperationException("%CLI_MSI% must point to the msi that is to be tested");
}
_msiMgr = new MsiManager(_msiFile);
}
[Theory]
[InlineData("")]
[InlineData(@"%SystemDrive%\dotnet")]
public void InstallTest(string installLocation)
{
installLocation = Environment.ExpandEnvironmentVariables(installLocation);
string expectedInstallLocation = string.IsNullOrEmpty(installLocation) ?
Environment.ExpandEnvironmentVariables(@"%ProgramFiles%\dotnet") :
installLocation;
// make sure that the msi is not already installed, if so the machine is in a bad state
Assert.False(_msiMgr.IsInstalled, "The dotnet CLI msi is already installed");
Assert.False(Directory.Exists(expectedInstallLocation));
_msiMgr.Install(installLocation);
Assert.True(_msiMgr.IsInstalled);
Assert.True(Directory.Exists(expectedInstallLocation));
_msiMgr.UnInstall();
Assert.False(_msiMgr.IsInstalled);
Assert.False(Directory.Exists(expectedInstallLocation));
}
public void Dispose()
{
if (!_msiMgr.IsInstalled)
{
return;
}
_msiMgr.UnInstall();
Assert.False(_msiMgr.IsInstalled, "Unable to cleanup by uninstalling dotnet");
}
}
}

View file

@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Deployment.WindowsInstaller;
using Microsoft.Deployment.WindowsInstaller.Package;
namespace Dotnet.Cli.Msi.Tests
{
public class MsiManager
{
private string _msiFile;
private string _productCode;
private InstallPackage _installPackage;
public ProductInstallation Installation
{
get
{
return ProductInstallation.AllProducts.SingleOrDefault(p => p.ProductCode == _productCode);
}
}
public string InstallLocation
{
get
{
return IsInstalled ? Installation.InstallLocation : null;
}
}
public bool IsInstalled
{
get
{
var prodInstall = Installation;
return Installation == null ? false : prodInstall.IsInstalled;
}
}
public string UpgradeCode
{
get
{
return _installPackage.Property["UpgradeCode"];
}
}
public MsiManager(string msiFile)
{
_msiFile = msiFile;
var ispackage = Installer.VerifyPackage(msiFile);
if (!ispackage)
{
throw new ArgumentException("Not a valid MSI file", msiFile);
}
_installPackage = new InstallPackage(msiFile, DatabaseOpenMode.ReadOnly);
_productCode = _installPackage.Property["ProductCode"];
}
public bool Install(string customLocation = null)
{
string dotnetHome = "";
if (!string.IsNullOrEmpty(customLocation))
{
dotnetHome = $"DOTNETHOME={customLocation}";
}
Installer.SetInternalUI(InstallUIOptions.Silent);
Installer.InstallProduct(_msiFile, $"ACTION=INSTALL ALLUSERS=2 ACCEPTEULA=1 {dotnetHome}");
return IsInstalled;
}
public bool UnInstall()
{
if (!IsInstalled)
{
throw new InvalidOperationException($"UnInstall Error: Msi at {_msiFile} is not installed.");
}
Installer.SetInternalUI(InstallUIOptions.Silent);
Installer.InstallProduct(_msiFile, "REMOVE=ALL");
return !IsInstalled;
}
}
}

View file

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Win32;
using Xunit;
namespace Dotnet.Cli.Msi.Tests
{
public class PostInstallTests : IClassFixture<InstallFixture>
{
InstallFixture _fixture;
MsiManager _msiMgr;
public PostInstallTests(InstallFixture fixture)
{
_fixture = fixture;
_msiMgr = fixture.MsiManager;
}
[Fact]
public void DotnetOnPathTest()
{
Assert.True(_msiMgr.IsInstalled);
Assert.True(Utils.ExistsOnPath("dotnet.exe"), "After installation dotnet tools must be on path");
}
[Fact]
public void Dotnetx64RegKeysTest()
{
var hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
CheckRegKeys(hklm);
}
[Fact]
public void Dotnetx86RegKeysTest()
{
var hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
CheckRegKeys(hklm);
}
private void CheckRegKeys(RegistryKey rootKey)
{
var regKey = rootKey.OpenSubKey(@"SOFTWARE\dotnet\Setup", false);
Assert.NotNull(regKey);
Assert.Equal(1, regKey.GetValue("Install"));
Assert.Equal(_fixture.InstallLocation, regKey.GetValue("InstallDir"));
Assert.NotNull(regKey.GetValue("Version"));
}
[Fact]
public void UpgradeCodeTest()
{
// magic number found in https://github.com/dotnet/cli/blob/master/packaging/windows/variables.wxi
Assert.Equal("{7D73E4F7-71E2-4236-8CF5-1C499BA3FF50}", _msiMgr.UpgradeCode);
}
}
}

View file

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Win32;
using Xunit;
namespace Dotnet.Cli.Msi.Tests
{
public class PostUninstallTests : InstallFixture
{
private MsiManager _msiMgr;
public PostUninstallTests()
{
_msiMgr = base.MsiManager;
}
[Fact]
public void DotnetOnPathTest()
{
Assert.True(_msiMgr.IsInstalled);
_msiMgr.UnInstall();
Assert.False(_msiMgr.IsInstalled);
Assert.False(Utils.ExistsOnPath("dotnet.exe"), "After uninstallation dotnet tools must not be on path");
}
[Fact]
public void DotnetRegKeysTest()
{
Assert.True(_msiMgr.IsInstalled);
_msiMgr.UnInstall();
Assert.False(_msiMgr.IsInstalled);
var hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
Assert.Null(hklm.OpenSubKey(@"SOFTWARE\dotnet\Setup", false));
hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
Assert.Null(hklm.OpenSubKey(@"SOFTWARE\dotnet\Setup", false));
}
}
}

View file

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Dotnet.Cli.Msi.Tests
{
class Program
{
// A main method is currently required because of https://github.com/dotnet/cli/issues/314
public static void Main(string[] args)
{
return;
}
}
}

View file

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Dotnet.Cli.Msi.Tests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Dotnet.Cli.Msi.Tests")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("16614b7f-5ca3-45ae-95c2-003ab39cc09f")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View file

@ -0,0 +1,27 @@
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Dotnet.Cli.Msi.Tests
{
class Utils
{
internal static bool ExistsOnPath(string fileName)
{
var paths = GetCurrentPathEnvironmentVariable();
return paths
.Split(';')
.Any(path => File.Exists(Path.Combine(path, fileName)));
}
internal static string GetCurrentPathEnvironmentVariable()
{
var hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
var regKey = hklm.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\Session Manager\Environment", false);
return (string)regKey.GetValue("Path");
}
}
}

View file

@ -0,0 +1,17 @@
{
"compilationOptions": {
"emitEntryPoint": true
},
"dependencies": {
"xunit": "2.1.0",
"xunit.runner.console": "2.1.0",
"Microsoft.Deployment.WindowsInstaller": "1.0.0"
},
"frameworks": {
"net46": {
"frameworkAssemblies": {
"System.Runtime": ""
}
}
}
}

View file

@ -148,7 +148,15 @@ if(!(Test-Path $DotnetMSIOutput))
return -1
}
Write-Host -ForegroundColor Green "Successfully create dotnet MSI - $DotnetMSIOutput"
Write-Host -ForegroundColor Green "Successfully created dotnet MSI - $DotnetMSIOutput"
& $PSScriptRoot\testmsi.ps1 -inputMsi $DotnetMSIOutput
if($LastExitCode -ne 0)
{
Write-Host -ForegroundColor Red "Msi testing failed."
Exit 1
}
$PublishScript = Join-Path $PSScriptRoot "..\..\scripts\publish\publish.ps1"
& $PublishScript -file $DotnetMSIOutput

View file

@ -0,0 +1,71 @@
# Copyright (c) .NET Foundation and contributors. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for full license information.
param(
[string]$inputMsi = $(throw "Specify the full path to the msi which needs to be tested")
)
. "$PSScriptRoot\..\..\scripts\_common.ps1"
function Test-Administrator
{
$user = [Security.Principal.WindowsIdentity]::GetCurrent();
(New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
Write-Host "Running tests for MSI installer at $inputMsi.."
if(!(Test-Path $inputMsi))
{
throw "$inputMsi not found"
}
$env:CLI_MSI=$inputMsi
$testBin="$RepoRoot\artifacts\tests\Dotnet.Cli.Msi.Tests"
$xunitRunner="$env:USERPROFILE\.dnx\packages\xunit.runner.console\2.1.0\tools\xunit.console.exe"
pushd "$Stage2Dir\bin"
try {
.\dotnet restore `
--runtime win-anycpu `
$RepoRoot\packaging\windows\Dotnet.Cli.Msi.Tests\project.json `
-f https://www.myget.org/F/dotnet-buildtools/api/v3/index.json | Out-Host
if($LastExitCode -ne 0)
{
throw "dotnet restore failed with exit code $LastExitCode."
}
.\dotnet publish `
--framework net46 `
--runtime win-anycpu `
--output $testBin `
$RepoRoot\packaging\windows\Dotnet.Cli.Msi.Tests\project.json | Out-Host
if($LastExitCode -ne 0)
{
throw "dotnet publish failed with exit code $LastExitCode."
}
<#
if(-Not (Test-Administrator))
{
Write-Host -ForegroundColor Yellow "Current script testmsi.ps1 is not run as admin."
Write-Host -ForegroundColor Yellow "Executing MSI tests require admin privileges."
Write-Host -ForegroundColor Yellow "Failing silently."
Exit 0
}
& $xunitRunner $testBin\Dotnet.Cli.Msi.Tests.exe | Out-Host
if($LastExitCode -ne 0)
{
throw "xunit runner failed with exit code $LastExitCode."
}
#>
}
finally {
popd
}
Exit 0