Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Enable Windows device tests on net7.0 #16756

Merged
merged 10 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"cake.tool": {
"version": "1.2.0",
"version": "2.3.0",
"commands": [
"dotnet-cake"
]
Expand Down
44 changes: 43 additions & 1 deletion eng/cake/dotnet.cake
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// Contains .NET 6-related Cake targets
// Contains .NET related Cake targets

#addin "nuget:?package=Cake.FileHelpers&version=3.2.1"

var ext = IsRunningOnWindows() ? ".exe" : "";
var dotnetPath = $"./bin/dotnet/dotnet{ext}";
Expand Down Expand Up @@ -728,6 +730,46 @@ void RunTestWithLocalDotNet(string csproj)
});
}

void RunTestWithLocalDotNet(string csproj, string config, string pathDotnet = null, Dictionary<string,string> argsExtra = null, bool noBuild = false)
{
var name = System.IO.Path.GetFileNameWithoutExtension(csproj);
var binlog = $"{GetLogDirectory()}/{name}-{config}.binlog";
var results = $"{name}-{config}.trx";

Information("Run Test binlog: {0}", binlog);

var settings = new DotNetTestSettings
{
Configuration = config,
NoBuild = noBuild,
Loggers = {
$"trx;LogFileName={results}"
},
ResultsDirectory = GetTestResultsDirectory(),
//Verbosity = Cake.Common.Tools.DotNetCore.DotNetCoreVerbosity.Diagnostic,
ArgumentCustomization = args =>
{
args.Append($"-bl:{binlog}");
// args.Append($"/tl");
if(argsExtra != null)
{
foreach(var prop in argsExtra)
{
args.Append($"/p:{prop.Key}={prop.Value}");
}
}
return args;
}
};

if(!string.IsNullOrEmpty(pathDotnet))
{
settings.ToolPath = pathDotnet;
}

DotNetTest(csproj, settings);
}

DirectoryPath PrepareSeparateBuildContext(string dirName, bool generateDirectoryProps = false)
{
var dir = GetTempDirectory().Combine(dirName);
Expand Down
302 changes: 302 additions & 0 deletions eng/devices/windows.cake
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
#load "../cake/helpers.cake"
#load "../cake/dotnet.cake"

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

string TARGET = Argument("target", "Test");

const string defaultVersion = "10.0.19041";
const string dotnetVersion = "net7.0";

// required
FilePath PROJECT = Argument("project", EnvironmentVariable("WINDOWS_TEST_PROJECT") ?? "");
// Not used for Windows. TODO Use this for packaged vs unpackaged?
string TEST_DEVICE = Argument("device", EnvironmentVariable("WINDOWS_TEST_DEVICE") ?? $"");
// Package ID of the WinUI Application
var PACKAGEID = Argument("packageid", EnvironmentVariable("WINDOWS_TEST_PACKAGE_ID") ?? $"");

// optional
var DOTNET_PATH = Argument("dotnet-path", EnvironmentVariable("DOTNET_PATH"));
var TARGET_FRAMEWORK = Argument("tfm", EnvironmentVariable("TARGET_FRAMEWORK") ?? $"{dotnetVersion}-windows{defaultVersion}");
var BINLOG_ARG = Argument("binlog", EnvironmentVariable("WINDOWS_TEST_BINLOG") ?? "");
DirectoryPath BINLOG_DIR = string.IsNullOrEmpty(BINLOG_ARG) && !string.IsNullOrEmpty(PROJECT.FullPath) ? PROJECT.GetDirectory() : BINLOG_ARG;
var TEST_APP = Argument("app", EnvironmentVariable("WINDOWS_TEST_APP") ?? "");
var DEVICETEST_APP = Argument("devicetestapp", EnvironmentVariable("WINDOWS_DEVICETEST_APP") ?? "");
FilePath TEST_APP_PROJECT = Argument("appproject", EnvironmentVariable("WINDOWS_TEST_APP_PROJECT") ?? PROJECT);
FilePath DEVICETEST_APP_PROJECT = Argument("appproject", EnvironmentVariable("WINDOWS_DEVICETEST_APP_PROJECT") ?? PROJECT);
var TEST_RESULTS = Argument("results", EnvironmentVariable("WINDOWS_TEST_RESULTS") ?? "");
string CONFIGURATION = Argument("configuration", "Debug");

var windowsVersion = Argument("apiversion", EnvironmentVariable("WINDOWS_PLATFORM_VERSION") ?? defaultVersion);

// other
string PLATFORM = "windows";
string DOTNET_PLATFORM = $"win10-x64";
bool DEVICE_CLEANUP = Argument("cleanup", true);
string certificateThumbprint = "";

// Certificate Common Name to use/generate (eg: CN=DotNetMauiTests)
var certCN = Argument("commonname", "DotNetMAUITests");

// Uninstall the deployed app
var uninstallPS = new Action(() =>
{
try {
StartProcess("powershell",
"$app = Get-AppxPackage -Name " + PACKAGEID + "; if ($app) { Remove-AppxPackage -Package $app.PackageFullName }");
} catch { }
});

Information("Project File: {0}", PROJECT);
Information("Application ID: {0}", PACKAGEID);
Information("Build Binary Log (binlog): {0}", BINLOG_DIR);
Information("Build Platform: {0}", PLATFORM);
Information("Build Configuration: {0}", CONFIGURATION);

Information("Windows version: {0}", windowsVersion);

Setup(context =>
{
Cleanup();
});

Teardown(context =>
{
Cleanup();
});

void Cleanup()
{
if (!DEVICE_CLEANUP)
return;
}

Task("Cleanup");

Task("GenerateMsixCert")
.Does(() =>
{
// We need the key to be in LocalMachine -> TrustedPeople to install the msix signed with the key
var localTrustedPeopleStore = new X509Store("TrustedPeople", StoreLocation.LocalMachine);
localTrustedPeopleStore.Open(OpenFlags.ReadWrite);

// We need to have the key also in CurrentUser -> My so that the msix can be built and signed
// with the key by passing the key's thumbprint to the build
var currentUserMyStore = new X509Store("My", StoreLocation.CurrentUser);
currentUserMyStore.Open(OpenFlags.ReadWrite);
certificateThumbprint = localTrustedPeopleStore.Certificates.FirstOrDefault(c => c.Subject.Contains(certCN))?.Thumbprint;
Information("Cert thumbprint: " + certificateThumbprint ?? "null");

if (string.IsNullOrEmpty(certificateThumbprint))
{
Information("Generating cert");
var rsa = RSA.Create();
var req = new CertificateRequest("CN=" + certCN, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

req.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection
{
new Oid
{
Value = "1.3.6.1.5.5.7.3.3",
FriendlyName = "Code Signing"
}
}, false));

req.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false));
req.CertificateExtensions.Add(
new X509KeyUsageExtension(
X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.NonRepudiation,
false));

req.CertificateExtensions.Add(
new X509SubjectKeyIdentifierExtension(req.PublicKey, false));
var cert = req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(5));

if (OperatingSystem.IsWindows())
{
cert.FriendlyName = certCN;
}

var tmpCert = new X509Certificate2(cert.Export(X509ContentType.Pfx), "", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
certificateThumbprint = tmpCert.Thumbprint;
localTrustedPeopleStore.Add(tmpCert);
currentUserMyStore.Add(tmpCert);
}

localTrustedPeopleStore.Close();
currentUserMyStore.Close();
});

Task("Build")
.IsDependentOn("GenerateMsixCert")
.WithCriteria(!string.IsNullOrEmpty(PROJECT.FullPath))
.WithCriteria(!string.IsNullOrEmpty(PACKAGEID))
.Does(() =>
{
var name = System.IO.Path.GetFileNameWithoutExtension(PROJECT.FullPath);
var binlog = $"{BINLOG_DIR}/{name}-{CONFIGURATION}-windows.binlog";

SetDotNetEnvironmentVariables(DOTNET_PATH);

Information("Building and publishing device test app");

// Build the app in publish mode
// Using the certificate thumbprint for the cert we just created
var s = new DotNetPublishSettings();
s.Configuration = CONFIGURATION;
s.Framework = TARGET_FRAMEWORK;
s.MSBuildSettings = new DotNetMSBuildSettings();
s.MSBuildSettings.Properties.Add("RuntimeIdentifierOverride", new List<string> { "win10-x64" });
s.MSBuildSettings.Properties.Add("PackageCertificateThumbprint", new List<string> { certificateThumbprint });
s.MSBuildSettings.Properties.Add("AppxPackageSigningEnabled", new List<string> { "True" });

DotNetPublish(PROJECT.FullPath, s);
});

Task("Test")
.IsDependentOn("Build")
.IsDependentOn("SetupTestPaths")
.Does(() =>
{
CleanDirectories(TEST_RESULTS);

Information("Cleaned directories");

// Try to uninstall the app if it exists from before
uninstallPS();

Information("Uninstalled previously deployed app");
var projectDir = PROJECT.GetDirectory();
var cerPath = GetFiles(projectDir.FullPath + "/**/AppPackages/*/*.cer").First();
var msixPath = GetFiles(projectDir.FullPath + "/**/AppPackages/*/*.msix").First();

var testResultsFile = MakeAbsolute((DirectoryPath)TEST_RESULTS).FullPath.Replace("/", "\\") + $"\\TestResults-{PACKAGEID.Replace(".", "_")}.xml";

Information($"Found MSIX, installing: {msixPath}");
Information($"Test Results File: {testResultsFile}");

if (FileExists(testResultsFile))
{
DeleteFile(testResultsFile);
}

// Install dependencies
var dependencies = GetFiles(projectDir.FullPath + "/**/AppPackages/**/Dependencies/x64/*.msix");
foreach (var dep in dependencies) {
Information("Installing Dependency MSIX: {0}", dep);
StartProcess("powershell", "Add-AppxPackage -Path \"" + MakeAbsolute(dep).FullPath + "\"");
}

// Install the DeviceTests app
StartProcess("powershell", "Add-AppxPackage -Path \"" + MakeAbsolute(msixPath).FullPath + "\"");

var startArgs = "Start-Process shell:AppsFolder\\$((Get-AppxPackage -Name \"" + PACKAGEID + "\").PackageFamilyName)!App -Args \"" + testResultsFile + "\"";

Information(startArgs);

// Start the DeviceTests app
StartProcess("powershell", startArgs);

var waited = 0;
while (!FileExists(testResultsFile)) {
System.Threading.Thread.Sleep(1000);
waited++;

Information($"Waiting {waited} second(s) for tests to finish...");
if (waited >= 120)
break;
}

if(!FileExists(testResultsFile))
{
throw new Exception($"Test results file not found after {waited} seconds, process might have crashed or not completed yet.");
}

Information($"Tests Finished");
});


Task("SetupTestPaths")
.Does(() => {

// UI Tests
if (string.IsNullOrEmpty(TEST_APP) )
{
if (string.IsNullOrEmpty(TEST_APP_PROJECT.FullPath))
{
throw new Exception("If no app was specified, an app must be provided.");
}

var binDir = TEST_APP_PROJECT.GetDirectory().Combine("bin").Combine(CONFIGURATION + "/" + $"{dotnetVersion}-windows{windowsVersion}").Combine(DOTNET_PLATFORM).FullPath;
Information("BinDir: {0}", binDir);
var apps = GetFiles(binDir + "/*.exe").Where(c => !c.FullPath.EndsWith("createdump.exe"));
TEST_APP = apps.First().FullPath;
}

if (string.IsNullOrEmpty(TEST_RESULTS))
{
TEST_RESULTS = TEST_APP + "-results";
}

// Device Tests
if (string.IsNullOrEmpty(DEVICETEST_APP) )
{
if (string.IsNullOrEmpty(DEVICETEST_APP_PROJECT.FullPath))
{
throw new Exception("If no app was specified, an app must be provided.");
}

DEVICETEST_APP = DEVICETEST_APP_PROJECT.GetFilenameWithoutExtension().ToString();
}

if (string.IsNullOrEmpty(TEST_RESULTS))
{
TEST_RESULTS = DEVICETEST_APP + "-results";
}

CreateDirectory(TEST_RESULTS);

Information("Test Device: {0}", TEST_DEVICE);
Information("UITest App: {0}", TEST_APP);
Information("DeviceTest App: {0}", DEVICETEST_APP);
Information("Test Results Directory: {0}", TEST_RESULTS);
});

Task("uitest")
.IsDependentOn("SetupTestPaths")
.Does(() =>
{
CleanDirectories(TEST_RESULTS);

Information("Build UITests project {0}",PROJECT.FullPath);
var name = System.IO.Path.GetFileNameWithoutExtension(PROJECT.FullPath);
var binlog = $"{BINLOG_DIR}/{name}-{CONFIGURATION}-windows.binlog";

var dd = MakeAbsolute(Directory("../../bin/dotnet/"));
Information("DOTNET_PATH: {0}", dd);

var toolPath = $"{dd}/dotnet.exe";

Information("toolPath: {0}", toolPath);

SetDotNetEnvironmentVariables(dd.FullPath);

DotNetBuild(PROJECT.FullPath, new DotNetBuildSettings {
Configuration = CONFIGURATION,
ToolPath = toolPath,
ArgumentCustomization = args => args
.Append("/p:ExtraDefineConstants=WINTEST")
.Append("/bl:" + binlog)
.Append("/maxcpucount:1")
//.Append("/tl")
});

SetEnvironmentVariable("WINDOWS_APP_PATH", TEST_APP);
SetEnvironmentVariable("APPIUM_LOG_FILE", $"{BINLOG_DIR}/appium_windows.log");

Information("Run UITests project {0}",PROJECT.FullPath);
RunTestWithLocalDotNet(PROJECT.FullPath, CONFIGURATION, toolPath, noBuild: true);
});

RunTarget(TARGET);
Loading
Loading