using System; using NUnit.Framework; using OpenTap.Package; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using NUnit.Framework.Constraints; namespace OpenTap.Image.Tests { public class DependencyResolverGraphTest { [Test] public void TestMergeGraphs() { var graph1 = PackageDependencyQuery.LoadGraph(Assembly.GetExecutingAssembly() .GetManifestResourceStream(packagesResourceName), true); var graph2 = PackageDependencyQuery.LoadGraph(Assembly.GetExecutingAssembly() .GetManifestResourceStream(packagesResourceName), true); var graph3 = new PackageDependencyGraph(); var g1 = graph1.PackageSpecifiers().ToArray(); graph3.Absorb(graph1); var g3 = graph3.PackageSpecifiers().ToArray(); graph3.Absorb(graph2); var g4 = graph3.PackageSpecifiers().ToArray(); Assert.AreEqual(g1.Length, g3.Length); Assert.AreEqual(g1.Length, g4.Length); } [Test] public void TestQueryGraph() { IQueryPrereleases repo = new HttpPackageRepository("https://packages.opentap.io"); var graph = repo.QueryPrereleases("Windows", CpuArchitecture.x64, "", null); } [Test] public void TestCollapseQueryGraph() { List repositories = Repositories.Select(PackageRepositoryHelpers.DetermineRepositoryType).ToList(); var graph0 = new PackageDependencyGraph(); foreach (var repo in repositories) { if (repo is IQueryPrereleases http) { var graph = http.QueryPrereleases("Windows", CpuArchitecture.x64,"", null); graph0.Absorb(graph); } else if (repo is FilePackageRepository fpkg) { var graph = new PackageDependencyGraph(); var packages = fpkg.GetAllPackages(TapThread.Current.AbortToken); graph.LoadFromPackageDefs(packages); graph0.Absorb(graph); } } } public List Repositories => new List { PackageCacheHelper.PackageCacheDirectory, "https://packages.opentap.io" }; static PackageSpecifier[] str2packages(string csv) { return csv.Split(new char[]{';'}, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Split(',')) .Select(x => new PackageSpecifier(x[0].Trim(), x.Length == 1 ? VersionSpecifier.AnyRelease : VersionSpecifier.Parse(x[1].Trim()))) .ToArray(); } static ImageSpecifier image(string csv) { var packages = str2packages(csv); var image = new ImageSpecifier(); image.Packages.AddRange(packages); return image; } static string unresolvable = nameof(unresolvable); static IEnumerable<(string spec, string result)> Specifiers { get{ yield return ( "OpenTAP, 9.17.4; SDK, any; Developer's System, any; REST-API, ^2.6.0; Keysight Floating Licensing, ^1.4", "CSV, 9.11.0+98498e58;Developer's System, 9.17.4+5e508f08;Editor, 9.17.4+5e508f08;" + "Keysight Floating Licensing, 1.4.1+e5816333;Keysight Licensing, 1.1.1+7a2a1fe3;" + "OpenTAP, 9.17.4;OSIntegration, 1.4.2+15f32a31;REST-API, 2.6.0+895d94fa;" + "Results Viewer, 9.17.4+5e508f08;SDK, 9.17.4+ea67c63a;SQLite and PostgreSQL, 9.4.2+3009aace;" + "Timing Analyzer, 9.17.4+5e508f08;Visual Studio SDK, 1.2.6+ef0ad804;WPF Controls, 9.17.4+5e508f08"); yield return ("OpenTAP, 9.17.4; OSIntegration, ^1", "OpenTAP, 9.17.4;OSIntegration, 1.4.2+15f32a31"); yield return ("OpenTAP, 9.17.4; OpenTAP, 9.17.2", null); yield return ("OpenTAP, 9.17.4;Developer's System CE, any", "CSV, 9.11.0+98498e58;Developer's System CE, 9.17.4+5e508f08;Editor CE, 9.17.4+5e508f08;" + "Keysight Licensing, 1.1.1+7a2a1fe3;OpenTAP, 9.17.4;OSIntegration, 1.4.2+15f32a31;" + "Results Viewer CE, 9.17.4+5e508f08;SDK, 9.17.4+ea67c63a;SQLite and PostgreSQL, 9.4.2+3009aace;" + "Visual Studio SDK CE, 1.2.6+ef0ad804;WPF Controls, 9.17.4+5e508f08"); yield return ("OpenTAP, 9.17.4; REST-API, any", "Keysight Licensing, 1.1.0+fc48665d;" + "OpenTAP, 9.17.4;REST-API, 2.8.0-beta.19+b95ad922"); yield return ("OpenTAP, 9.17; OSIntegration, ^1", "OpenTAP, 9.17.4;OSIntegration, 1.4.2+15f32a31"); yield return ("OpenTAP, 9.18.4; TUI, beta", "OpenTAP, 9.18.4;TUI, 0.1.0-beta.145+6c192a43"); yield return ("OpenTAP, 9.18; TUI, beta", "OpenTAP, 9.18.4;TUI, 0.1.0-beta.145+6c192a43"); yield return ("OpenTAP; TUI, beta", "OpenTAP, 9.18.4;TUI, 0.1.0-beta.145+6c192a43"); yield return ("OpenTAP; REST-API; CSV", "CSV, 9.11.0+98498e58;Keysight Licensing, 1.1.1+7a2a1fe3;OpenTAP, 9.18.4+7dec4717;REST-API, 2.9.1+e5319b91"); yield return ("TEST-A", "TEST-A, 1.0.0; TEST-B,1.0.0;TEST-C,1.0.0"); // requesting a beta, but the newest is actually a release. The release versions should then be resolved. yield return ("Developer's System, beta", "CSV, 9.11.0+98498e58;Developer's System, 9.17.2-beta.12+a3f06537;" + "Editor, 9.17.2-beta.12+a3f06537;Keysight Licensing, 1.1.0-rc.3+fc48665d;" + "OpenTAP, 9.17.4-rc.1+3ffb292e;OSIntegration, 1.4.2+15f32a31;Results Viewer, 9.17.2-beta.12+a3f06537;" + "SDK, 9.17.4-rc.1+3ffb292e;SQLite and PostgreSQL, 9.4.2+3009aace;" + "Timing Analyzer, 9.17.2-beta.12+a3f06537;Visual Studio SDK, 1.2.5+08b71a6e;WPF Controls, 9.17.2-beta.12+a3f06537"); yield return ("OpenTAP, 9.17.4;OpenTAP, ^9.17.4;OpenTAP, 9.17.4", "OpenTAP, 9.17.4"); yield return ("OpenTAP, 9.17.4;OpenTAP, ^9.17.4+ea67c63a;OpenTAP, 9.17.4", "OpenTAP, 9.17.4"); }} static IEnumerable Specifiers2 { get { foreach (var elem in Specifiers) { yield return new [] { elem.spec, elem.result }; } } } const string packagesResourceName = "OpenTap.Package.UnitTests.Image.opentap-packages.json.gz"; readonly PackageDependencyGraph graph; public DependencyResolverGraphTest() { graph = PackageDependencyQuery.LoadGraph(Assembly.GetExecutingAssembly() .GetManifestResourceStream(packagesResourceName), true); // now add a circular reference so we can test it. var pa = new PackageDef() { Name = "TEST-A", Version = SemanticVersion.Parse("1.0")}; var pb = new PackageDef() { Name = "TEST-B", Version = SemanticVersion.Parse("1.0") }; var pc = new PackageDef() { Name = "TEST-C", Version = SemanticVersion.Parse("1.0") }; pa.Dependencies.Add(new PackageDependency("TEST-C", VersionSpecifier.Parse("1.0.0"))); pb.Dependencies.Add(new PackageDependency("TEST-A", VersionSpecifier.Parse("1.0.0"))); pc.Dependencies.Add(new PackageDependency("TEST-B", VersionSpecifier.Parse("1.0.0"))); var graph2 = new PackageDependencyGraph(); graph2.LoadFromPackageDefs(new []{pa, pb ,pc}); graph.Absorb(graph2); } [TestCaseSource(nameof(Specifiers2))] public void TestResolvePackages(string spec, string result) { var resolver2 = new ImageResolver(TapThread.Current.AbortToken); var img = image(spec); var r = resolver2.ResolveImage(img, graph); if (result == null) { Assert.IsFalse(r.Success); } else { Assert.IsTrue(r.Success); foreach (var pkg in str2packages(result)) { Assert.IsTrue(r.Packages.Any(x => x.Name == pkg.Name && x.Version.Equals(pkg.Version))); } } // Ensure that each package occurs only once in the image resolution var members = r.Packages.ToLookup(pkg => pkg.Name); foreach (var m in members) { Assert.IsTrue(m.Count() == 1); } } [TestCase("I,0.2.0-beta.5","H,0.2.0-beta.1,I,0.2.0-beta.5")] [TestCase("H,^0.1.0;I,0.2.0-beta.5","H,0.2.0-beta.1,I,0.2.0-beta.5")] public void TestResolvePackagesDependencyExpansion(string spec, string result) { var resolver2 = new ImageResolver(TapThread.Current.AbortToken); var img = image(spec); var dep = new PackageDependencyCache("Windows", CpuArchitecture.AnyCPU, [MockRepository.Instance.Url]); dep.LoadFromRepositories(); var r = resolver2.ResolveImage(img, dep.Graph); Assert.IsTrue(r.Success); foreach (var pkg in str2packages(result)) { Assert.IsTrue(r.Packages.Any(x => x.Name == pkg.Name && x.Version.Equals(pkg.Version))); } } [Test] public void TestPartialVersionSort2() { var wrt = new PackageSpecifier("Basic Mixins", VersionSpecifier.Parse("^0")); var unordered = new[] { "0.4.0-alpha.1", "0.1.0", "0.3.0-beta.1", "0.2.0-rc.1", }.Select(SemanticVersion.Parse); var expected = new[] { "0.1.0", "0.2.0-rc.1", "0.3.0-beta.1", "0.4.0-alpha.1", }.Select(SemanticVersion.Parse).ToArray(); var sorted = unordered.OrderBy(x => x, wrt.Version.SortPartial).ToList(); CollectionAssert.AreEqual(expected, sorted); } [Test] public void TestPartialVersionSort() { var wrt = new PackageSpecifier("Basic Mixins", VersionSpecifier.Parse("^0")); var versions = new[] { "0.2.0-alpha.1.1", "0.2.0-beta.1", "0.0.1-alpha.11.1", "0.0.1-alpha.19.2", "0.0.1-beta.9", "0.2.1-alpha.1.1", "0.0.1-beta.13", "0.0.1-beta.20", "0.0.1-beta.21", "0.1.0", }.Select(SemanticVersion.Parse); var expected = new[] { "0.1.0", "0.2.0-beta.1", "0.0.1-beta.21", "0.0.1-beta.20", "0.0.1-beta.13", "0.0.1-beta.9", "0.2.1-alpha.1.1", "0.2.0-alpha.1.1", "0.0.1-alpha.19.2", "0.0.1-alpha.11.1", }.Select(SemanticVersion.Parse); var sorted = versions.OrderBy(x => x, wrt.Version.SortPartial).ToList(); CollectionAssert.AreEqual(expected, sorted); } [TestCase("J,^0-beta","J,0.2.0-beta.1", false)] [TestCase("J,^0.1-beta","J,0.1.2-rc.2", false)] [TestCase("J,^0.1.0-beta","J,0.1.2-rc.2", false)] [TestCase("J,^0.2-beta","J,0.2.0-beta.1", false)] [TestCase("J,^0.2.0-beta","J,0.2.0-beta.1", false)] [TestCase("J,^0","J,0.1.2-rc.2", true)] [TestCase("J,^0.0","J,0.1.2-rc.2", true)] [TestCase("J,^0.1","J,0.2.0-beta.1", true)] [TestCase("J,^0.2","J,0.3.1-alpha.1.2", true)] [TestCase("J,^0.1.0","J,0.1.1-alpha.1.2", true)] [TestCase("J,^0.1.1","J,0.1.2-rc.2", true)] public void TestResolvePackagesDependencyExpansion2(string spec, string result, bool fetchAlpha) { var resolver2 = new ImageResolver(TapThread.Current.AbortToken); var img = image(spec); var dep = new PackageDependencyCache("Windows", CpuArchitecture.AnyCPU, [MockRepository.Instance.Url]); dep.LoadFromRepositories(); var r = resolver2.ResolveImage(img, dep.Graph); var fetchedPackages = dep.Graph.PackageSpecifiers().ToLookup(pkg => pkg.Name); var js = fetchedPackages["J"].ToArray(); if (fetchAlpha) { Assert.That(js.Any(j => j.Version.PreRelease?.StartsWith("alpha") == true), Is.True); } else { Assert.That(js.Any(j => j.Version.PreRelease?.StartsWith("alpha") == true), Is.False); } Assert.That(js.Any(j => j.Version.PreRelease?.StartsWith("beta") == true), Is.True); Assert.That(js.Any(j => j.Version.PreRelease?.StartsWith("rc") == true), Is.True); if (result == null) { Assert.IsFalse(r.Success); } { Assert.IsTrue(r.Success); foreach (var pkg in str2packages(result)) { Assert.IsTrue(r.Packages.Any(x => x.Name == pkg.Name && x.Version.Equals(pkg.Version))); } } } [Test] public void TestResolvePackagesFromXml() { PackageDef defFromXml(byte[] xml) { using var ms = new MemoryStream(); ms.Write(xml.ToArray(), 0, xml.Length); ms.Seek(0, SeekOrigin.Begin); return PackageDef.FromXml(ms); } var pkg1Xml = """ """u8; var pkg2Xml = """"""u8; var pkg1 = defFromXml(pkg1Xml.ToArray()); var pkg2 = defFromXml(pkg2Xml.ToArray()); var resolver2 = new ImageResolver(TapThread.Current.AbortToken); // This image should fail to resolve because: // Pkg1 depends on Pkg2 version ^1.0.1-beta.2 // The latest version of Pkg2 is 1.0.1-beta.1 var img = image("Pkg1,1.1.1"); var g = new PackageDependencyGraph(); g.LoadFromPackageDefs([pkg1, pkg2]); var r = resolver2.ResolveImage(img, g); Assert.IsFalse(r.Success); } } }