// Copyright Keysight Technologies 2012-2019 // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, you can obtain one at http://mozilla.org/MPL/2.0/. using NUnit.Framework; using OpenTap.Cli; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Xml; namespace OpenTap.Package.UnitTests { static class DummyPackageGenerator { public static string GeneratePackage(PackageDef definition) { foreach (var packageFile in definition.Files) { File.CreateText(packageFile.FileName).Close(); } string defFileName = "generated_package.xml"; using (var stream = File.Create(defFileName)) definition.SaveTo(stream); var proc = OpenTap.Engine.UnitTests.TapProcessContainer.StartFromArgs("package create " + defFileName, TimeSpan.FromMinutes(5)); proc.WaitForEnd(); string output = proc.ConsoleOutput; string outputFile = definition.Name + "." + definition.Version + ".TapPackage"; if (File.Exists(outputFile)) return outputFile; else throw new Exception(output); } public static void AddFile(this PackageDef def, string filename) { def.Files.Add(new PackageFile { RelativeDestinationPath = filename, Plugins = new List() }); } internal static void InstallDummyPackage(string packageName = "Dummy", string version = "1.0", string contentFileName = "Dummy") { string installDir = Path.GetDirectoryName(typeof(Package.PackageDef).Assembly.Location); string packageXmlPath = Path.Combine(installDir, "Packages", packageName, "package.xml"); Directory.CreateDirectory(Path.Combine(installDir, "Packages", packageName)); File.WriteAllText(packageXmlPath, $@" "); string contentFilePath = Path.Combine(installDir, "Packages", packageName, contentFileName); if (!File.Exists(contentFilePath)) File.WriteAllText(contentFilePath, "hello"); } internal static void UninstallDummyPackage(string packageName = "Dummy") { string installDir = Path.GetDirectoryName(typeof(Package.PackageDef).Assembly.Location); string packageDir = Path.Combine(installDir, "Packages", packageName); if (Directory.Exists(packageDir)) { Directory.Delete(packageDir, true); } } } [TestFixture] public class CliTests { [TestCase(true, true, null)] // tap package download --out /tmp/Nested/TargetDir/ pkg -r /tmp [TestCase(true, false, null)] // tap package download --out /tmp/Nested/TargetDir/ /tmp/pkg.TapPackage [TestCase(true, true, "pkg.TapPackage")] // tap package download --out /tmp/Nested/TargetDir/pkg.TapPackage pkg -r /tmp [TestCase(true, false, "pkg.TapPackage")] // tap package download --out /tmp/Nested/TargetDir/pkg.TapPackage /tmp/pkg.TapPackage [TestCase(false, false, null)] // tap package download /tmp/pkg.TapPackage [TestCase(false, true, null)] // tap package download pkg -r /tmp public void DownloadTest(bool useOut, bool useRepo, string outFileName) { var depDef = new PackageDef {Name = "Pkg1", Version = SemanticVersion.Parse("1.0"), OS = "Windows,Linux,MacOS"}; depDef.AddFile("Dependency.txt"); string dep0File = DummyPackageGenerator.GeneratePackage(depDef); string tempFn = Path.Combine(Path.GetTempPath(), Path.GetFileName(dep0File)); if (File.Exists(tempFn)) File.Delete(tempFn); File.Move(dep0File, tempFn); string outArg = Path.Combine(Path.GetTempPath(), "Nested", "TargetDir" + Path.DirectorySeparatorChar); if (outFileName != null) outArg = Path.Combine(outArg, outFileName); string targetFile; // Expected output path differs depending on whether or not we specify --out if (useOut && outFileName == null) targetFile = Path.GetFullPath(Path.Combine(outArg, PackageActionHelpers.GetQualifiedFileName(depDef))); else if (useOut && outFileName != null) targetFile = Path.GetFullPath(outArg); else targetFile = Path.Combine(Path.GetDirectoryName(typeof(Package.PackageDef).Assembly.Location), PackageActionHelpers.GetQualifiedFileName(depDef)); try { var args = $"download "; if (useOut) args += $" --out {outArg} "; if (useRepo) args += $" {depDef.Name} -r {Path.GetDirectoryName(tempFn)} "; else args += $" {Path.GetFileName(tempFn)} "; args += " --no-cache"; string output = RunPackageCli(args, out var exitCode, Path.GetDirectoryName(tempFn)); Assert.AreEqual(0, exitCode, "Unexpected exit code"); StringAssert.Contains($@"Downloaded '{depDef.Name}' to '{targetFile}'.", output); Assert.IsTrue(File.Exists(targetFile)); } finally { File.Delete(dep0File); File.Delete(tempFn); if (useOut && outFileName == null) Directory.Delete(outArg, true); else File.Delete(targetFile); } } [Test] public void InstallLocalFile() { var package = new PackageDef(); package.Name = "Dummy Something"; package.Version = SemanticVersion.Parse("1.0.0"); package.Description = "Cached version"; var file = DummyPackageGenerator.GeneratePackage(package); if (File.Exists(Path.Combine(PackageCacheHelper.PackageCacheDirectory, file))) File.Delete(Path.Combine(PackageCacheHelper.PackageCacheDirectory, file)); File.Move(file, Path.Combine(PackageCacheHelper.PackageCacheDirectory, file)); package.Description = "Right version"; var file2 = DummyPackageGenerator.GeneratePackage(package); var result = RunPackageCli("install -v -f \"" + Path.GetFullPath(file2) + "\"", out int exitcode); Assert.IsTrue(result.ToLower().Contains("installed")); Assert.IsTrue(result.ToLower().Contains("downloading file without searching")); var installedPackage = new Installation(Directory.GetCurrentDirectory()).GetPackages().FirstOrDefault(p => p.Name == package.Name); Assert.IsNotNull(installedPackage, "Package was not installed"); Assert.AreEqual(package.Description, installedPackage.Description); } [Test] public void CheckInvalidPackageName() { // Check if name contains invalid character try { DummyPackageGenerator.GeneratePackage(new PackageDef(){Name = "Op/en:T\\AP"}); Assert.Fail("Path contains invalid character"); } catch (Exception e) { Console.WriteLine(e.Message); Assert.True(e.Message.Contains("invalid file path characters")); } } [TestCase(".test", 1, typeof(XmlException))] [TestCase("t-est", 2, typeof(Exception))] [TestCase("te st", 3, typeof(XmlException))] [TestCase("tes.t", 4, typeof(Exception))] [TestCase("1test", 1, typeof(XmlException))] [TestCase("test?", 5, typeof(XmlException))] public void CheckInvalidMetadata(string invalidMetadataKey, int index, Type exceptionType) { // Check if metadata contains invalid characters var package = new PackageDef() { Name = "test", MetaData = { {invalidMetadataKey,""} }, Version = SemanticVersion.Parse("1.0.0") }; try { DummyPackageGenerator.GeneratePackage(package); } catch (Exception e) { Assert.IsAssignableFrom(exceptionType, e); if (e is XmlException) return; Assert.True(e.Message.Contains($"Found invalid character"), "Package metadata keys contains invalid"); Assert.True(e.Message.Contains($" in package metadata key '{invalidMetadataKey}' at position {index}."), "Package metadata keys contains invalid"); } } [Test] public void ListTest() { int exitCode; string output = RunPackageCli("list", out exitCode); Assert.AreEqual(0, exitCode, $"Unexpected exit code.{Environment.NewLine}{output}"); StringAssert.Contains("OpenTAP ", output); Debug.Write(output); } [Test] public void ShowTest() { int exitCode; string output = RunPackageCli("show OpenTAP", out exitCode); Assert.AreEqual(0, exitCode, $"Unexpected exit code.{Environment.NewLine}{output}"); StringAssert.Contains("OpenTAP", output); Debug.Write(output); } [Ignore("This does not work on build runners")] [Test] public void TAPUninstallSelfTest() { int exitCode; string testDir = "../UninstallOpenTAP"; try { Directory.CreateDirectory(testDir); string output = RunPackageCli($"install Packages/OpenTAP.TapPackage --target {testDir} -f", out exitCode); Debug.Write(output); Assert.AreEqual(0, exitCode, "Unexpected exit code.\r\n" + output); Debug.Write("--------------------------------------------"); string workingDir = Path.Combine(Directory.GetCurrentDirectory(), testDir); output = RunPackageCliWrapped($"uninstall OpenTAP -v", out exitCode, workingDir, Path.Combine(workingDir, "tap.exe")); Debug.Write(output); if (File.Exists(Path.Combine(testDir, "OpenTap.dll")) || File.Exists(Path.Combine(testDir, "OpenTap.Package.dll")) || Directory.Exists(Path.Combine(testDir, "Packages"))) Console.WriteLine(output); Assert.False(File.Exists(Path.Combine(testDir, "OpenTap.dll")), "OpenTap.dll was not deleted!"); Assert.False(File.Exists(Path.Combine(testDir, "OpenTap.Package.dll")), "OpenTap.Package.dll was not deleted!"); Assert.False(Directory.Exists(Path.Combine(testDir, "Packages")), "Packages directory was not deleted!"); } finally { try { Directory.Delete(testDir, true); } catch { } } } [Test] public void InstallOutsideTapDir() { var depDef = new PackageDef(); depDef.Name = "Pkg1"; depDef.Version = SemanticVersion.Parse("1.0"); depDef.AddFile("Dependency.txt"); depDef.OS = OperatingSystem.Current.Name; string dep0File = DummyPackageGenerator.GeneratePackage(depDef); string tempFn = Path.Combine(Path.GetTempPath(), Path.GetFileName(dep0File)); if (File.Exists(tempFn)) File.Delete(tempFn); File.Move(dep0File, tempFn); try { if (File.Exists("Dependency.txt")) File.Delete("Dependency.txt"); int exitCode; string output = RunPackageCli("install " + Path.GetFileName(tempFn), out exitCode, Path.GetDirectoryName(tempFn)); Assert.AreEqual(0, exitCode, "Unexpected exit code"); StringAssert.Contains("Installed Pkg1", output); Assert.IsTrue(File.Exists("Dependency.txt")); PluginInstaller.Uninstall(depDef, Directory.GetCurrentDirectory()); } finally { File.Delete(dep0File); File.Delete(tempFn); } } [Test] public void InstallOutsideTapDirInSubDir() { var depDef = new PackageDef(); depDef.Name = "Pkg1"; depDef.Version = SemanticVersion.Parse("1.0"); depDef.OS = OperatingSystem.Current.Name; depDef.AddFile("Dependency.txt"); string dep0File = DummyPackageGenerator.GeneratePackage(depDef); string tempFn = Path.Combine(Path.GetTempPath(), Path.GetFileName(dep0File)); if (File.Exists(tempFn)) File.Delete(tempFn); File.Move(dep0File, tempFn); string testDir = Path.Combine(Path.GetTempPath(), "lolDir"); try { if (File.Exists("Dependency.txt")) File.Delete("Dependency.txt"); int exitCode; Directory.CreateDirectory(testDir); string output = RunPackageCli($"install --target {testDir} {Path.GetFileName(tempFn)}", out exitCode, Path.GetDirectoryName(tempFn)); Assert.AreEqual(0, exitCode, "Unexpected exit code"); StringAssert.Contains("Installed Pkg1", output); Assert.IsTrue(File.Exists(Path.Combine(testDir, "Dependency.txt"))); } finally { File.Delete(dep0File); File.Delete(tempFn); Directory.Delete(testDir, true); } } [Test, Retry(3)] public void InstallFileWithDependenciesTest() { var depDef = new PackageDef(); depDef.Name = "Dependency"; depDef.Version = SemanticVersion.Parse("1.0"); depDef.AddFile("Dependency.txt"); string dep0File = DummyPackageGenerator.GeneratePackage(depDef); var dummyDef = new PackageDef(); dummyDef.Name = "Dummy"; dummyDef.Version = SemanticVersion.Parse("1.0"); dummyDef.AddFile("Dummy.txt"); dummyDef.Dependencies.Add(new PackageDependency( "Dependency", VersionSpecifier.Parse("1.0"))); DummyPackageGenerator.InstallDummyPackage("Dependency"); // We need to have "Dependency" installed before we can create a package that depends on it. string dummyFile = DummyPackageGenerator.GeneratePackage(dummyDef); DummyPackageGenerator.UninstallDummyPackage("Dependency"); try { if (File.Exists("Dependency.txt")) File.Delete("Dependency.txt"); if (File.Exists("Dummy.txt")) File.Delete("Dummy.txt"); int exitCode; string output = RunPackageCli("install Dummy -y", out exitCode); Assert.AreEqual(0, exitCode, "Unexpected exit code"); StringAssert.Contains("Dummy", output); StringAssert.Contains("Dependency", output); Assert.IsTrue(File.Exists("Dummy.txt")); Assert.IsTrue(File.Exists("Dependency.txt")); } finally { PluginInstaller.Uninstall(dummyDef, Directory.GetCurrentDirectory()); PluginInstaller.Uninstall(depDef, Directory.GetCurrentDirectory()); File.Delete(dep0File); File.Delete(dummyFile); } } [Test] public void CyclicDependenciesTest() { DummyPackageGenerator.InstallDummyPackage(); // We need to have "Dummy" installed before we can create a package that depends on it. var depDef = new PackageDef(); depDef.Name = "Dependency"; depDef.OS="Windows,Linux,MacOS"; depDef.Version = SemanticVersion.Parse("1.0"); depDef.AddFile("Dependency.txt"); depDef.Dependencies.Add(new PackageDependency("Dummy", VersionSpecifier.Parse("1.0"))); string dep0File = DummyPackageGenerator.GeneratePackage(depDef); DummyPackageGenerator.UninstallDummyPackage(); DummyPackageGenerator.InstallDummyPackage("Dependency"); var dummyDef = new PackageDef(); dummyDef.Name = "Dummy"; dummyDef.OS="Windows,Linux"; dummyDef.Version = SemanticVersion.Parse("1.0"); dummyDef.AddFile("Dummy.txt"); dummyDef.Dependencies.Add(new PackageDependency("Dependency", VersionSpecifier.Parse("1.0"))); string dummyFile = DummyPackageGenerator.GeneratePackage(dummyDef); DummyPackageGenerator.UninstallDummyPackage("Dependency"); try { if (File.Exists("Dependency.txt")) File.Delete("Dependency.txt"); if (File.Exists("Dummy.txt")) File.Delete("Dummy.txt"); var output = RunPackageCli("install Dummy -y", out var exitCode); StringAssert.Contains("Dummy", output); StringAssert.Contains("Dependency", output); Assert.AreEqual(0, exitCode, "Unexpected exit code.\r\n" + output); Assert.IsTrue(File.Exists("Dependency.txt")); Assert.IsTrue(File.Exists("Dummy.txt")); } finally { PluginInstaller.Uninstall(dummyDef, Directory.GetCurrentDirectory()); PluginInstaller.Uninstall(depDef, Directory.GetCurrentDirectory()); File.Delete(dep0File); File.Delete(dummyFile); } } [Test] public void UninstallTest() { try { int exitCode; string output = RunPackageCli("install NoDepsPlugin -f -y --os Windows", out exitCode); Assert.AreEqual(0, exitCode, "Unexpected installation exit code"); StringAssert.Contains("NoDepsPlugin", output); Assert.IsTrue(File.Exists("Packages/NoDepsPlugin/Tap.Plugins.NoDepsPlugin.dll")); output = RunPackageCli("uninstall NoDepsPlugin", out exitCode); Assert.AreEqual(0, exitCode, "Unexpected uninstallation exit code"); StringAssert.Contains("NoDepsPlugin", output); Assert.IsFalse(File.Exists("Packages/NoDepsPlugin/Tap.Plugins.NoDepsPlugin.dll")); } finally { if (Directory.Exists("Packages/NoDepsPlugin/Tap.Plugins.NoDepsPlugin.dll")) Directory.Delete("Packages/NoDepsPlugin/Tap.Plugins.NoDepsPlugin.dll",true); } } [Test] public void InstallNonExistentFileTest() { int exitCode; string output = RunPackageCli("install NonExistent.TapPackage", out exitCode); Assert.AreEqual((int)ExitCodes.GeneralException, exitCode, "Unexpected exit code.\n" + output); StringAssert.Contains("Package 'NonExistent.TapPackage' not found.", output); } [Test] public void UninstallFirstTest() { var dep0Def = new PackageDef(); dep0Def.Name = "UninstallPackage"; dep0Def.Version = SemanticVersion.Parse("0.1"); dep0Def.AddFile("UninstallText.txt"); dep0Def.OS = OperatingSystem.Current.Name; string dep0File = DummyPackageGenerator.GeneratePackage(dep0Def); var dep1Def = new PackageDef(); dep1Def.Name = "UninstallPackage"; dep1Def.Version = SemanticVersion.Parse("0.2"); dep1Def.AddFile("SubDir/UninstallText.txt"); dep1Def.OS = OperatingSystem.Current.Name; Directory.CreateDirectory("SubDir"); string dep1File = DummyPackageGenerator.GeneratePackage(dep1Def); int exitCode; string output = RunPackageCli("install " + dep0File, out exitCode); Assert.AreEqual(0, exitCode, "Unexpected exit code1: " + output); Assert.IsTrue(File.Exists("UninstallText.txt"), "File0 should exist"); output = RunPackageCli("install " + dep1File, out exitCode); Assert.AreEqual(0, exitCode, "Unexpected exit code2: " + output); Assert.IsTrue(File.Exists("SubDir/UninstallText.txt"), "File1 should exist"); Assert.IsFalse(File.Exists("UninstallText.txt"), "File0 should not exist"); } [Test] public void UpgradeDependencyTest() { var dep0Def = new PackageDef(); dep0Def.Name = "Dependency"; dep0Def.Version = SemanticVersion.Parse("0.1"); dep0Def.AddFile("Dependency0.txt"); string dep0File = DummyPackageGenerator.GeneratePackage(dep0Def); var dep1Def = new PackageDef(); dep1Def.Name = "Dependency"; dep1Def.Version = SemanticVersion.Parse("1.0"); dep1Def.AddFile("Dependency1.txt"); string dep1File = DummyPackageGenerator.GeneratePackage(dep1Def); var dummyDef = new PackageDef(); dummyDef.Name = "Dummy"; dummyDef.Version = SemanticVersion.Parse("1.0"); dummyDef.AddFile("Dummy.txt"); dummyDef.Dependencies.Add(new PackageDependency("Dependency", VersionSpecifier.Parse("1.0"))); DummyPackageGenerator.InstallDummyPackage("Dependency"); string dummyFile = DummyPackageGenerator.GeneratePackage(dummyDef); DummyPackageGenerator.UninstallDummyPackage("Dependency"); try { int exitCode; string output = RunPackageCli("install " + dep0File + " --force", out exitCode); Assert.AreEqual(0, exitCode, "Unexpected exit code"); output = RunPackageCli("install Dummy -y -f", out exitCode); Assert.AreEqual(0, exitCode, "Unexpected exit code"); //StringAssert.Contains("upgrading", output); Assert.IsTrue(File.Exists("Dependency1.txt")); } finally { PluginInstaller.Uninstall(dummyDef, Directory.GetCurrentDirectory()); PluginInstaller.Uninstall(dep1Def, Directory.GetCurrentDirectory()); File.Delete(dep0File); File.Delete(dep1File); File.Delete(dummyFile); } } [Test] public void InstallFromRepoTest() { int exitCode; // TODO: we need the --version part below because the release version of License Injector does not yet support OpenTAP 9.x, when it does, we can remove it again. string output = RunPackageCli("install \"Demonstration\" -r http://packages.opentap.io", out exitCode); Assert.AreEqual(0, exitCode, "Unexpected exit code: " + output); Assert.IsTrue(output.Contains("Installed Demonstration")); output = RunPackageCli("uninstall \"Demonstration\" -f", out exitCode); Assert.AreEqual(0, exitCode, "Unexpected exit code: " + output); } [Test] public void InstallFileWithMissingDependencyTest() { var def = new PackageDef(); def.Name = "Dummy2"; def.OS = "Windows,Linux,MacOS"; def.Version = SemanticVersion.Parse("1.0"); def.AddFile("Dummy.txt"); def.Dependencies.Add(new PackageDependency("Missing", VersionSpecifier.Parse("1.0"))); DummyPackageGenerator.InstallDummyPackage("Missing"); string pkgFile = DummyPackageGenerator.GeneratePackage(def); DummyPackageGenerator.UninstallDummyPackage("Missing"); try { int exitCode; string output = RunPackageCli("install Dummy2", out exitCode); Assert.AreNotEqual(0, exitCode, "Unexpected exit code"); StringAssert.Contains("Could not install Dummy2", output); } finally { File.Delete(pkgFile); } } [Test] public void InstallPackagesFileTest() { string packageName = "REST-API", prerelease = "rc"; var installation = new Installation(Directory.GetCurrentDirectory()); int exitCode; string output = RunPackageCli("install -v -f \"" + packageName + "\" --version \"1.1.180-" + prerelease + "\" -y", out exitCode); var installedAfter = installation.GetPackages(); if (installedAfter.Any(p => p.Name == packageName) == false) Console.WriteLine(output); Assert.IsTrue(installedAfter.Any(p => p.Name == packageName), "Package '" + packageName + "' was not installed."); Assert.IsTrue(installedAfter.Any(p => p.Name == packageName && p.Version.PreRelease == prerelease), "Package '" + packageName + "' was not installed with '--version'."); output = RunPackageCli("uninstall \"" + packageName + "\"", out exitCode); installedAfter = installation.GetPackages(); Assert.IsFalse(installedAfter.Any(p => p.Name == packageName), "Package '" + packageName + "' was not uninstalled."); } [Test] public void NoDowngradeInstallTest() { var installation = new Installation(Directory.GetCurrentDirectory()); var package = new PackageDef(); package.Name = "NoDowngradeTest"; package.Version = SemanticVersion.Parse("1.0.1"); package.OS = OperatingSystem.Current.Name; var newPath = DummyPackageGenerator.GeneratePackage(package); package.Version = SemanticVersion.Parse("1.0.0"); var oldPath = DummyPackageGenerator.GeneratePackage(package); // Install new version var output = RunPackageCli($"install {newPath} --force", out int exitCode); Assert.IsTrue(exitCode == 0 && output.ToLower().Contains("installed"), "NoDowngradeTest package was not installed."); var installedVersion = installation.GetPackages()?.FirstOrDefault(p => p.Name == "NoDowngradeTest")?.Version; Assert.IsTrue(installedVersion == SemanticVersion.Parse("1.0.1"), $"NoDowngradeTest installed the wrong version: '{installedVersion}'."); // Install older version with --no-downgrade option. This should not install the old version. output = RunPackageCli($"install --no-downgrade {oldPath} --force", out exitCode); Assert.IsTrue(exitCode == 0 && output.ToLower().Contains("no package(s) were upgraded"), "NoDowngradeTest package was not installed."); installedVersion = installation.GetPackages()?.FirstOrDefault(p => p.Name == "NoDowngradeTest")?.Version; Assert.IsTrue(installedVersion == SemanticVersion.Parse("1.0.1"), $"NoDowngradeTest failed to skip the install: '{installedVersion}'."); // Install older version without --no-downgrade option. This should install the old version. output = RunPackageCli($"install {oldPath} --force", out exitCode); Assert.IsTrue(exitCode == 0 && output.ToLower().Contains("installed"), "NoDowngradeTest package was not installed."); installedVersion = installation.GetPackages()?.FirstOrDefault(p => p.Name == "NoDowngradeTest")?.Version; Assert.IsTrue(installedVersion == SemanticVersion.Parse("1.0.0"), $"NoDowngradeTest failed to install the old version: '{installedVersion}'."); } [Test] public void SkipInstallExactVersionAlreadyInstalledTest() { var package = new PackageDef(); package.Name = "ExactVersionTest"; package.Version = SemanticVersion.Parse("1.0.1"); package.OS = "Windows,Linux,MacOS"; var path = DummyPackageGenerator.GeneratePackage(package); // Install var output = RunPackageCli($"install {path}", out int exitCode); Assert.IsTrue(exitCode == 0 && output.ToLower().Contains("installed"), "ExactVersionTest package was not installed."); // Install the exact same version. This should skip. output = RunPackageCli($"install {package.Name} --version 1.0.1", out exitCode); Assert.IsTrue(exitCode == 0 && output.ToLower().Contains("already installed"), "ExactVersionTest package install was not skipped."); // Install the exact same version with --force. This should not skip. output = RunPackageCli($"install {package.Name} --version 1.0.1 -f", out exitCode); Assert.IsTrue(exitCode == 0 && output.ToLower().Contains("already installed") == false, "ExactVersionTest package install with -f was skipped."); // Install the exact same version from file. This should not skip. output = RunPackageCli($"install {path}", out exitCode); Assert.IsTrue(exitCode == 0 && output.ToLower().Contains("already installed") == false, "ExactVersionTest package install was skipped."); } private static string RunPackageCli(string args, out int exitCode, string workingDir = null) { return RunPackageCliWrapped(args, out exitCode, workingDir); } private static string RunPackageCliWrapped(string args, out int exitCode, string workingDir, string fileName = null) { fileName ??= Path.Combine(ExecutorClient.ExeDir, "tap"); var p = new Process(); p.StartInfo = new ProcessStartInfo { FileName = fileName, WorkingDirectory = workingDir, Arguments = "package " + args, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false }; StringBuilder output = new StringBuilder(); var lockObj = new object(); void onOutput(object s, DataReceivedEventArgs e) { if (e.Data != null) lock (lockObj) output.AppendLine(e.Data); } p.OutputDataReceived += onOutput; p.ErrorDataReceived += onOutput; p.Start(); p.BeginErrorReadLine(); p.BeginOutputReadLine(); p.WaitForExit(125000); // TapUninstallSelfTest can hang while waiting for files to be freed up. 125 seconds ought to be enough for everyone! if (!p.HasExited) { p.Kill(); exitCode = -1; } else { exitCode = p.ExitCode; p.WaitForExit(); // The WaitForExit(int) overload called earlier does not wait for output processing to complete, this one does. } lock (lockObj) return output.ToString(); } } }