/// <summary> /// Install the given packages in the correct order to avoid missing dependencies /// </summary> /// <param name="packages">The list of packages with their dependencies</param> /// <returns>A response object that contains the status of the install and a comma separated string of package names in the order of install, such that a package's dependency will always precede that package</returns> public static PackageInstallResponse InstallPackages(string[] packages) { PackageInstallResponse response = new PackageInstallResponse(); try { // Parse packages to a list of PackageInfo objects List <PackageInfo> packageInfoList = GetPackageInfoList(packages); List <PackageInfo> installedPackages = new List <PackageInfo>(); // Get and install the packages with no dependencies first List <PackageInfo> installQueue = packageInfoList.Where(x => x.Dependency == string.Empty).ToList(); // Filter the packages to exclude the ones with no dependencies packageInfoList = packageInfoList.Where(x => x.Dependency != string.Empty).ToList(); // Perform a topological sort, while the install queue is not empty while (installQueue.Count > 0) { // Remove the first package from the install queue, and install the package PackageInfo currPackage = installQueue.First(); installQueue.Remove(currPackage); installedPackages.Add(currPackage); // Find all packages that have a dependency on the current package and add them to the install queue List <PackageInfo> packagesWithCurrentDependency = packageInfoList.Where(x => x.Dependency == currPackage.Name).ToList(); foreach (PackageInfo package in packagesWithCurrentDependency) { // We are handling the package, so remove it from the original "graph" i.e. packageInfoList packageInfoList.Remove(package); // Because packages only have one dependency, we know the current package dependencies are already installed, so just add this to the install queue installQueue.Add(package); } } // If there are still items in the original list, the package dependencies contain a cycle or a dependency on a missing package if (packageInfoList.Count > 0) { response.Status = PackageInstallStatuses.CONTAINS_CYCLE; } else { // The package dependencies did not contain a cycle, get the formatted string of the install order string installedPackagesFormatted = GetInstalledPackagesFormatted(installedPackages); response.Status = PackageInstallStatuses.SUCCESS; response.InstalledPackages = installedPackagesFormatted; } } catch { // If given incorrectly formatted input, we will break here and just return an error status response.Status = PackageInstallStatuses.ERROR; } return(response); }
public void TestInstallPackagesInvalid_Easy() { string[] input = new string[] { "KittenService: ", "Leetmeme: Cyberportal", }; PackageInstallResponse response = PackageInstallService.InstallPackages(input); // The response should never be null Assert.AreNotEqual(null, response); // This should report that it contains a package with a dependency on a missing package and no packages should be installed Assert.AreEqual(PackageInstallStatuses.CONTAINS_CYCLE, response.Status); Assert.AreEqual(string.Empty, response.InstalledPackages); }
public void TestInstallPackagesValid_Easy() { string[] input = new string[] { "KittenService: CamelCaser", "CamelCaser: " }; PackageInstallResponse response = PackageInstallService.InstallPackages(input); // The response should never be null Assert.AreNotEqual(null, response); // The response should be successful with this result Assert.AreEqual(PackageInstallStatuses.SUCCESS, response.Status); Assert.AreEqual("CamelCaser, KittenService", response.InstalledPackages); }
public void TestInstallPackagesInvalid_Medium() { string[] input = new string[] { "KittenService: ", "Leetmeme: Cyberportal", "Cyberportal: Ice", "CamelCaser: KittenService", "Fraudstream: ", "Ice: Leetmeme" }; PackageInstallResponse response = PackageInstallService.InstallPackages(input); // The response should never be null Assert.AreNotEqual(null, response); // This should report that it contains a cycle and no packages should be installed Assert.AreEqual(PackageInstallStatuses.CONTAINS_CYCLE, response.Status); Assert.AreEqual(string.Empty, response.InstalledPackages); }
public void TestInstallPackagesValid_Hard() { string[] input = new string[] { "Texture: Cabbage", "Bread: Amusement", "Cabbage: Giraffe", "Amusement: Giraffe", "Giraffe: Flower", "Flower: " }; PackageInstallResponse response = PackageInstallService.InstallPackages(input); // The response should never be null Assert.AreNotEqual(null, response); // The response should be successful with this result Assert.AreEqual(PackageInstallStatuses.SUCCESS, response.Status); Assert.AreEqual("Flower, Giraffe, Cabbage, Amusement, Texture, Bread", response.InstalledPackages); }
public void TestInstallPackagesValid_Medium() { string[] input = new string[] { "KittenService: ", "Leetmeme: Cyberportal", "Cyberportal: Ice", "CamelCaser: KittenService", "Fraudstream: Leetmeme", "Ice: " }; PackageInstallResponse response = PackageInstallService.InstallPackages(input); // The response should never be null Assert.AreNotEqual(null, response); // The response should be successful with this result Assert.AreEqual(PackageInstallStatuses.SUCCESS, response.Status); Assert.AreEqual("KittenService, Ice, CamelCaser, Cyberportal, Leetmeme, Fraudstream", response.InstalledPackages); }
static void Main(string[] args) { // Allow package input as arguments or wait for it in the program string[] packages = args; if (args.Length == 0) { try { /* * Accepts input in any of the following formats: * [ "PackageName: Dependency", "PackageName: " ] * "PackageName: Dependency", "PackageName: " * PackageName: Dependency, PackageName: */ string input = Console.ReadLine(); packages = PackageInstallService.GetCleanedPackageListFromInput(input); } catch { Console.WriteLine("Invalid input given, exiting program"); return; } } // Exit the program if no valid input was given. if (packages.Length == 0) { Console.WriteLine("There is nothing to install, exiting program"); return; } // Install the given packages based on their dependencies PackageInstallResponse response = PackageInstallService.InstallPackages(packages); // Build a message based on the result of the install string message = string.Empty; switch (response.Status) { case PackageInstallStatuses.SUCCESS: { message = response.InstalledPackages; break; } case PackageInstallStatuses.CONTAINS_CYCLE: { message = "No Packages were installed, invalid dependency specification that contains cycles or a dependency on a missing package."; break; } case PackageInstallStatuses.ERROR: case PackageInstallStatuses.DEFAULT_NOT_SET: default: { message = "No Packages were installed, unknown error occurred."; break; } } // Print the result to the console Console.WriteLine(message); }