159 lines
5.2 KiB
C#
159 lines
5.2 KiB
C#
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security.AccessControl;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
|
|
namespace EpicGames.Core.Tests;
|
|
|
|
// Suppress call sites not available on all platforms
|
|
// OS check is done in test class initializer below
|
|
#pragma warning disable CA1416
|
|
|
|
[TestClass]
|
|
public class AppContainerTests
|
|
{
|
|
private const string FileContent = "foobar";
|
|
private const string RegeditExePath = @"c:\Windows\regedit.exe";
|
|
private const string DisplayName = "Unreal Engine Horde Test";
|
|
private const string Description = "Created by Unreal Engine tests";
|
|
|
|
private static int s_idCounter = 1000;
|
|
private static readonly HashSet<string> s_allocatedNames = [];
|
|
private readonly DirectoryInfo _tempDir;
|
|
private readonly FileInfo _tempFile;
|
|
|
|
public AppContainerTests()
|
|
{
|
|
string uniqueTempDir = Path.Join(Path.GetTempPath(), Path.GetRandomFileName());
|
|
string uniqueTempFile = Path.Join(uniqueTempDir, Path.GetRandomFileName());
|
|
_tempDir = Directory.CreateDirectory(uniqueTempDir);
|
|
_tempFile = new FileInfo(uniqueTempFile);
|
|
File.WriteAllText(uniqueTempFile, FileContent);
|
|
}
|
|
|
|
[ClassInitialize]
|
|
public static void ClassInit(TestContext _)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
{
|
|
Assert.Inconclusive("AppContainer tests only run on Windows");
|
|
}
|
|
}
|
|
|
|
[TestCleanup]
|
|
public void Cleanup()
|
|
{
|
|
foreach (string containerName in s_allocatedNames)
|
|
{
|
|
AppContainer.Delete(containerName);
|
|
}
|
|
|
|
_tempDir.Delete(true);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void CreateOrUseExisting()
|
|
{
|
|
string containerName = GetName(20);
|
|
AppContainer.Delete(containerName);
|
|
using AppContainer ac = AppContainer.Create(containerName, DisplayName, Description);
|
|
using AppContainer sameAc = AppContainer.Create(containerName, DisplayName, Description);
|
|
|
|
Assert.ThrowsException<AppContainerException>(() =>
|
|
{
|
|
using AppContainer errored = AppContainer.Create(containerName, DisplayName, Description, true);
|
|
});
|
|
Assert.AreEqual(ac.ContainerName, sameAc.ContainerName);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task FileSystem_CanRead_AllowedDir_Async()
|
|
{
|
|
using AppContainer appContainer = Create();
|
|
Assert.IsTrue(await IsFileReadableAsync(null, _tempFile.FullName));
|
|
Assert.IsFalse(await IsFileReadableAsync(appContainer, _tempFile.FullName));
|
|
appContainer.AddDirectoryAccess(_tempDir, FileSystemRights.FullControl);
|
|
Assert.IsTrue(await IsFileReadableAsync(appContainer, _tempFile.FullName));
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task FileSystem_CannotRead_DefaultTempDir_Async()
|
|
{
|
|
using AppContainer appContainer = Create();
|
|
Assert.IsTrue(await IsFileReadableAsync(null));
|
|
Assert.IsFalse(await IsFileReadableAsync(appContainer));
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task FileSystem_CanRead_WindowsDir_Async()
|
|
{
|
|
// Windows dir can be read by restricted apps by default
|
|
using AppContainer appContainer = Create();
|
|
Assert.IsTrue(await IsFileReadableAsync(null, RegeditExePath, "run in DOS mode"));
|
|
Assert.IsTrue(await IsFileReadableAsync(appContainer, RegeditExePath, "run in DOS mode"));
|
|
}
|
|
|
|
private static async Task<bool> IsFileReadableAsync(AppContainer? appContainer, string? filename = null, string? matchString = FileContent)
|
|
{
|
|
if (filename == null)
|
|
{
|
|
filename = Path.GetTempFileName();
|
|
await File.WriteAllTextAsync(filename, FileContent);
|
|
}
|
|
|
|
// Use built-in Windows tool findstr.exe as an external process to launch and read from the file system with
|
|
// findstr.exe finds a string in the given file. If exit code is zero, file could be read and string was found.
|
|
string exe = @"c:\Windows\System32\findstr.exe";
|
|
|
|
if (appContainer != null)
|
|
{
|
|
string destFileName = Path.Join(appContainer.GetFolderPath().FullName, "target.exe");
|
|
File.Copy(exe, destFileName, true);
|
|
exe = destFileName;
|
|
}
|
|
|
|
(int exitCode, string _) = await RunProcessAsync(exe, $"\"{matchString}\" {filename}", appContainer);
|
|
|
|
return exitCode switch
|
|
{
|
|
0 => true,
|
|
1 => false,
|
|
2 => false,
|
|
_ => throw new Exception("Unknown exit code: " + exitCode)
|
|
};
|
|
}
|
|
|
|
private static string GetName(int? id = null)
|
|
{
|
|
id ??= Interlocked.Increment(ref s_idCounter);
|
|
string containerName = "EpicGames-UE-Test-" + id;
|
|
s_allocatedNames.Add(containerName);
|
|
return containerName;
|
|
}
|
|
|
|
private static AppContainer Create()
|
|
{
|
|
string containerName = GetName();
|
|
AppContainer.Delete(containerName);
|
|
return AppContainer.Create(containerName, DisplayName, Description);
|
|
}
|
|
|
|
private static async Task<(int exitCode, string output)> RunProcessAsync(string fileName, string commandLine, AppContainer? appContainer)
|
|
{
|
|
using ManagedProcess process = new (null, fileName, commandLine, null, null, null, ProcessPriorityClass.Normal, appContainer);
|
|
using MemoryStream ms = new(500);
|
|
await process.CopyToAsync((buffer, offset, length) => { ms.Write(buffer, offset, length); }, 4096, CancellationToken.None);
|
|
await process.WaitForExitAsync(CancellationToken.None);
|
|
return (process.ExitCode, Encoding.UTF8.GetString(ms.ToArray()));
|
|
}
|
|
}
|
|
|
|
#pragma warning restore CA1416 |