public void MaxRectsAlgorithmTestSingleSquarePackSixteenSquaresIntoLarge4x4Square() { //Arrange var algo = new MaximalRectanglesAlgorithm(); var rects = Enumerable.Repeat(new PPRect(0, 0, 64, 64), 16); //Act var result = algo.PlaceRects(256, 256, rects); //Assert Assert.AreNotEqual(null, result); Assert.AreEqual(256, result.Width); Assert.AreEqual(256, result.Height); Assert.AreEqual(true, TestUtil.IsPackingResultValid(result)); Assert.AreEqual(16, result.Rects.Count()); List <PPRect> expectedSuperset = new List <PPRect>(); for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { expectedSuperset.Add(new PPRect(i * 64, j * 64, i * 64 + 64, j * 64 + 64)); } } CollectionAssert.IsSubsetOf(result.Rects.ToList(), expectedSuperset); }
public void MaxRectsAlgorithmTestSingleSquarePackFourSquaresIntoLarge2x2Square() { //Arrange var algo = new MaximalRectanglesAlgorithm(); var rects = Enumerable.Repeat(new PPRect(0, 0, 64, 64), 4); //Act var result = algo.PlaceRects(128, 128, rects); //Assert Assert.AreNotEqual(null, result); Assert.AreEqual(128, result.Width); Assert.AreEqual(128, result.Height); Assert.AreEqual(true, TestUtil.IsPackingResultValid(result)); Assert.AreEqual(4, result.Rects.Count()); List <PPRect> expectedSuperset = new List <PPRect>() { new PPRect(0, 0, 64, 64), new PPRect(0, 64, 64, 128), new PPRect(64, 0, 128, 64), new PPRect(64, 64, 128, 128) }; CollectionAssert.IsSubsetOf(result.Rects.ToList(), expectedSuperset); }
public void MaxRectsAlgorithmTestNullInput() { //Arrange var algo = new MaximalRectanglesAlgorithm(); IEnumerable <PPRect> rects = null; //Act algo.PlaceRects(63, 63, rects); }
public void MaxRectsAlgorithmTestSingleSquareNoFitInBothDirections() { //Arrange var algo = new MaximalRectanglesAlgorithm(); var rects = Enumerable.Repeat(new PPRect(0, 0, 64, 64), 1); //Act var result = algo.PlaceRects(63, 63, rects); //Assert Assert.AreEqual(null, result); }
public void MaxRectsAlgorithmTestSingleSquarePackSeventeenSquaresIntoLarge4x4Square() { //Arrange var algo = new MaximalRectanglesAlgorithm(); var rects = Enumerable.Repeat(new PPRect(0, 0, 64, 64), 17); //Assert var result = algo.PlaceRects(256, 256, rects); //Act Assert.AreEqual(null, result); }
public void MaxRectsAlgorithmTestSingleSquareFitSideBySideHorizontally() { //Arrange var algo = new MaximalRectanglesAlgorithm(); var rects = Enumerable.Repeat(new PPRect(0, 0, 64, 64), 10); //Act var result = algo.PlaceRects(1000, 64, rects); //Assert Assert.AreNotEqual(null, result); Assert.AreEqual(1000, result.Width); Assert.AreEqual(64, result.Height); Assert.AreEqual(true, TestUtil.IsPackingResultValid(result)); CollectionAssert.AreEqual(Enumerable.Range(0, 10).Select(i => new PPRect(i * 64, 0, i * 64 + 64, 64)).ToList(), result.Rects.ToList()); }
public void MaxRectsAlgorithmTestSingleSquareExactFit() { //Arrange var algo = new MaximalRectanglesAlgorithm(); var rects = Enumerable.Repeat(new PPRect(0, 0, 64, 64), 1); //Act var result = algo.PlaceRects(64, 64, rects); //Assert Assert.AreNotEqual(null, result); Assert.AreEqual(64, result.Width); Assert.AreEqual(64, result.Height); Assert.AreEqual(true, TestUtil.IsPackingResultValid(result)); CollectionAssert.AreEqual(rects.ToList(), result.Rects.ToList()); }
public void MaxRectsAlgorithmTestTwoDistinctRectsThatDoesNotFit() { //Arrange var algo = new MaximalRectanglesAlgorithm(); var rects = new List <PPRect> { new PPRect(0, 0, 32, 32), new PPRect(0, 0, 32, 32), new PPRect(0, 0, 64, 64), new PPRect(0, 0, 300, 300), new PPRect(0, 0, 512, 512) }; //Act var result = algo.PlaceRects(256, 256, rects); //Assert Assert.AreEqual(null, result); }
public void MaxRectsAlgorithmTestEmptyInput() { //Arrange var algo = new MaximalRectanglesAlgorithm(); var rects = Enumerable.Empty <PPRect>(); //Act var result = algo.PlaceRects(63, 63, rects); //Assert Assert.AreNotEqual(null, result); Assert.AreEqual(63, result.Width); Assert.AreEqual(63, result.Height); int actualWidth = result.Rects.Any() ? result.Rects.Max(x => x.Right) : 0; int actualHeight = result.Rects.Any() ? result.Rects.Max(x => x.Bottom) : 0; Assert.AreEqual(0, actualWidth); Assert.AreEqual(0, actualHeight); Assert.AreEqual(0, result.Rects?.Count() ?? 0); }
public void MaxRectsAlgorithmTestOutperformsGuillotine() { //Arrange (both with PreserveOrderImageSorter) var maxRectsAlgo = new MaximalRectanglesAlgorithm(new PreserveOrderImageSorter()); var guillotineAlgo = new GuillotinePlacementAlgorithm(new BestAreaFitFreeRectangleExtractor(), new LongerAxisGuillotineFreeRectangleSplitter(), null, null, null, new PreserveOrderImageSorter()); var rects = new List <PPRect>() { new PPRect(0, 0, 50, 10), new PPRect(0, 0, 10, 100) }; //Act var result = maxRectsAlgo.PlaceRects(90, 100, rects); var result2 = guillotineAlgo.PlaceRects(90, 100, rects); //Assert Assert.AreNotEqual(null, result); Assert.AreEqual(null, result2); }
/// <summary> /// Accepts additional parameters and runs the tests inside the given class /// </summary> /// <typeparam name="T">The type of class that contains the tests</typeparam> private static void RunTests <T>() { if (typeof(T) != typeof(IPlacementAlgorithm) && typeof(T) != typeof(IMinimumBoundingBoxFinder)) { throw new NotSupportedException(); } string str = ""; IImageSorter selectedImageSorter = null; IPlacementAlgorithm selectedPlacementAlgorithm = null; //Ask the user to select an image sorter that will parametrize the placement algorithm / minimum bounding box finder while (true) { do { Console.WriteLine("Select image sorter to be used: "); Console.WriteLine($"\tEnter 'a' to select {typeof(ByHeightAndWidthImageSorter).Name}"); Console.WriteLine($"\tEnter 'b' to select {typeof(ByHeightAndWidthImageSorterDesc).Name}"); Console.WriteLine($"\tEnter 'c' to select {typeof(PreserveOrderImageSorter).Name}"); } while ((str = Console.ReadLine()).Length != 1); switch (str[0]) { case 'a': selectedImageSorter = new ByHeightAndWidthImageSorter(); break; case 'b': selectedImageSorter = new ByHeightAndWidthImageSorterDesc(); break; case 'c': selectedImageSorter = new PreserveOrderImageSorter(); break; default: continue; } break; } //If the users wants to benchmark Minimum bounding box finders // then ask the user to select an placement algorithm that will parametrize tminimum bounding box finders if (typeof(T) == typeof(IMinimumBoundingBoxFinder)) { while (true) { do { Console.WriteLine("Select placement algorithm to be used: "); Console.WriteLine($"\tEnter 'a' to select {typeof(BLAlgorithmPacker).Name}"); Console.WriteLine($"\tEnter 'b' to select {typeof(SkylineAlgorithm).Name}"); Console.WriteLine($"\tEnter 'c' to select {typeof(MaximalRectanglesAlgorithm).Name}"); } while ((str = Console.ReadLine()).Length != 1); switch (str[0]) { case 'a': selectedPlacementAlgorithm = new BLAlgorithmPacker(); break; case 'b': selectedPlacementAlgorithm = new SkylineAlgorithm(); break; case 'c': selectedPlacementAlgorithm = new MaximalRectanglesAlgorithm(); break; default: continue; } break; } } bool isItSquaresTest = true; int numOfRects = 0; int seed = 0; //Ask the user to select test type //Currently, two types of tests are implemented (differing in the input sequence) // 1) Input sequence consisting of n squares with sizes 1x1, ..., nxn (given in random order) // 2) Input sequence consisting of n rectangles with random (but upper-bounded) sizes (given in random order) while (true) { str = ""; do { Console.WriteLine("Select test type:"); Console.WriteLine("\tEnter 'a' to perform test with squares of sizes 1x1, 2x2, ..., nxn"); Console.WriteLine("\tEnter 'b' to perform test with n rectangles of random sizes"); } while ((str = Console.ReadLine()).Length != 1); switch (str[0]) { case 'b': case 'a': isItSquaresTest = str[0] == 'a'; //Ask the user to enter number of rectangles/squares while (true) { Console.WriteLine("\tEnter number of 'n' - rectangles(squares)"); str = Console.ReadLine(); if (Int32.TryParse(str, out numOfRects)) { if (numOfRects > 0) { break; } } } //If it is the random rectangles test then ask the user to enter the random seed // The seed will be user to construct the random generator used to generate the random rectangle sizes if (!isItSquaresTest) { while (true) { Console.WriteLine("\tEnter seed"); str = Console.ReadLine(); if (Int32.TryParse(str, out seed)) { break; } } } break; default: continue; } break; } //Now when the user has selected the type of test and all the parameters // It is time to actaully run the tests. But because we need to test types // that are contained within plugins and also because BenchmarkDotNet (in a standard scenario) // requires to use (static) attribute [Benchmark] on the methods that should be called // we have decided to generate the benchmark class (with benchmark methods) dynamically at runtime // and then compile the generated class and pass the type of this class to the BenchmarkRunner string assemblyName = Path.GetRandomFileName(); //Assemblies referenced by the assembly that will be generated at runtime //For each type that will be used in the generated test, an assembly containing //the used type has to be loaded var references = new List <MetadataReference>() { MetadataReference.CreateFromFile(typeof(BenchmarkDotNet.Attributes.BenchmarkAttribute).Assembly.Location), MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("Microsoft.CSharp")).Location), MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("netstandard")).Location), MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("mscorlib")).Location), MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("System.Runtime")).Location), MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("Unity.Container")).Location), MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("Unity.Abstractions")).Location), MetadataReference.CreateFromFile(typeof(IMinimumBoundingBoxFinder).Assembly.Location), MetadataReference.CreateFromFile(typeof(TestUtil).Assembly.Location), MetadataReference.CreateFromFile(typeof(CancellationToken).Assembly.Location), MetadataReference.CreateFromFile(typeof(UnityContainerExtensions).Assembly.Location), MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location), MetadataReference.CreateFromFile(typeof(Random).Assembly.Location), MetadataReference.CreateFromFile(typeof(System.Collections.Concurrent.ConcurrentDictionary <string, int>).Assembly.Location), MetadataReference.CreateFromFile(typeof(ValueTuple <long, long>).Assembly.Location) }; //Create same IoC here and in the generated class //This is because inside this class we try if (using the same IoC container with same registrations) //a given type could be resolved and if it does not, we exclude benchmark for this type in the generated class using UnityContainer IoC = new UnityContainer(); IoC.RegisterFactory <IImageSorter>(_ => selectedImageSorter); if (selectedPlacementAlgorithm != null) { IoC.RegisterFactory <IPlacementAlgorithm>(_ => selectedPlacementAlgorithm); } //The generated source var benchmarkClassTemplate = new StringBuilder(); //Using statements benchmarkClassTemplate.AppendLine("using System;"); benchmarkClassTemplate.AppendLine("using Unity;"); benchmarkClassTemplate.AppendLine("using System.Linq;"); benchmarkClassTemplate.AppendLine("using PaunPacker.Core.Packing.MBBF;"); benchmarkClassTemplate.AppendLine("using PaunPacker.Core.Packing.Placement;"); benchmarkClassTemplate.AppendLine("using PaunPacker.Core.Packing.Sorting;"); benchmarkClassTemplate.AppendLine("using PaunPacker.Core.Packing.Placement.Skyline;"); benchmarkClassTemplate.AppendLine("using PaunPacker.Core.Packing.Placement.Guillotine;"); benchmarkClassTemplate.AppendLine("using PaunPacker.Core.Packing.Placement.MaximalRectangles;"); benchmarkClassTemplate.AppendLine("using System.Collections.Generic;"); benchmarkClassTemplate.AppendLine("using System.Threading;"); benchmarkClassTemplate.AppendLine("using PaunPacker.Tests;"); //Beginning of the class benchmarkClassTemplate.AppendLine($"public class {typeof(T).Name}Benchmarks"); benchmarkClassTemplate.AppendLine("{"); //Member declarations benchmarkClassTemplate.AppendLine(@" private static UnityContainer IoC { get; set; } private static Random rnd; private static List<PaunPacker.Core.Types.PPRect> randomRects; static System.Collections.Concurrent.ConcurrentDictionary<string, ValueTuple<long, long>> results; "); benchmarkClassTemplate.AppendLine($"\n\tstatic {typeof(T).Name}Benchmarks()"); benchmarkClassTemplate.AppendLine("\t{"); //Constructor code benchmarkClassTemplate.AppendLine($@" IoC = new UnityContainer(); IoC.RegisterInstance<IImageSorter>(new {selectedImageSorter.GetType().Name}()); results = new System.Collections.Concurrent.ConcurrentDictionary<string, ValueTuple<long, long>>(); rnd = new Random(" + seed + @"); randomRects = new List<PaunPacker.Core.Types.PPRect>(" + numOfRects + @"); int dimensionBound = Int32.MaxValue / " + numOfRects + @"; dimensionBound = dimensionBound > 2049 ? 2049 : dimensionBound; for (int i = 0; i < " + numOfRects + @"; i++) { randomRects.Add(new PaunPacker.Core.Types.PPRect(0, 0, rnd.Next(1, dimensionBound), rnd.Next(1, dimensionBound))); } "); if (selectedPlacementAlgorithm != null) { benchmarkClassTemplate.AppendLine($"IoC.RegisterInstance<IPlacementAlgorithm>(new { selectedPlacementAlgorithm.GetType().Name }());"); } benchmarkClassTemplate.AppendLine("\t}"); //Constructor ends here if (isItSquaresTest) { benchmarkClassTemplate.AppendLine($@" [BenchmarkDotNet.Attributes.ParamsSourceAttribute(nameof(ValuesForN))] public int N; public IEnumerable<int> ValuesForN => System.Linq.Enumerable.Range(1, {numOfRects}); "); } int numOfTests = 0; // We are testing minimum bounding box finders, so generate tests for them if (selectedPlacementAlgorithm != null) { foreach (var x in MinimumBoundingBoxFinderTypes) { try { UnityContainerExtensions.Resolve(IoC, Type.GetType(x.AssemblyQualifiedName), null); references.Add(MetadataReference.CreateFromFile(x.Assembly.Location)); benchmarkClassTemplate.AppendLine("\t[BenchmarkDotNet.Attributes.BenchmarkAttribute]"); benchmarkClassTemplate.AppendLine($"\tpublic void Test{x.Name}()"); benchmarkClassTemplate.AppendLine("{"); benchmarkClassTemplate.AppendLine($"\tvar x = Type.GetType(\"{x.AssemblyQualifiedName}\");"); benchmarkClassTemplate.AppendLine($"\tvar res = (UnityContainerExtensions.Resolve(IoC, x, null) as IMinimumBoundingBoxFinder).FindMinimumBoundingBox(TestUtil.Shuffle(" + (isItSquaresTest ? "TestUtil.GetIncreasingSquares(N)" : $"randomRects") + "), CancellationToken.None);"); benchmarkClassTemplate.AppendLine($"results.AddOrUpdate(\"Test{x.Name}\", (res.Width * res.Height, 1), (key, old) => ((long)(old.Item1 + (double)(res.Width * res.Height - old.Item1) / (double)(old.Item2 + 1)), old.Item2 + 1));"); benchmarkClassTemplate.AppendLine("}"); benchmarkClassTemplate.AppendLine(); numOfTests++; } catch (ResolutionFailedException) { //Do not add benchmark for types that could not be resolved (for example types that are extensible by other plugins // via plugin view ...). These types will simply not be benchmarked. } } } else { foreach (var x in PlacementAlgorithmTypes) { try { UnityContainerExtensions.Resolve(IoC, Type.GetType(x.AssemblyQualifiedName), null); references.Add(MetadataReference.CreateFromFile(x.Assembly.Location)); benchmarkClassTemplate.AppendLine("\t[BenchmarkDotNet.Attributes.BenchmarkAttribute]"); benchmarkClassTemplate.AppendLine($"\tpublic void Test{x.Name}()"); benchmarkClassTemplate.AppendLine("{"); benchmarkClassTemplate.AppendLine($"\tvar x = Type.GetType(\"{x.AssemblyQualifiedName}\");"); benchmarkClassTemplate.AppendLine($"\tvar res = (UnityContainerExtensions.Resolve(IoC, x, null) as IPlacementAlgorithm).PlaceRects(Int32.MaxValue, Int32.MaxValue, TestUtil.Shuffle(" + (isItSquaresTest ? "TestUtil.GetIncreasingSquares(N)" : $"randomRects") + "));"); benchmarkClassTemplate.AppendLine("long actualW = res.Rects.Max(y => y.Right); long actualH = res.Rects.Max(y => y.Bottom);"); benchmarkClassTemplate.AppendLine($"checked {{ results.AddOrUpdate(\"Test{x.Name}\", (actualW * actualH, 1), (key, old) => ((long)(old.Item1 + (double)(actualW * actualH - old.Item1) / (double)(old.Item2 + 1)), old.Item2 + 1));"); benchmarkClassTemplate.AppendLine("}}"); benchmarkClassTemplate.AppendLine(); numOfTests++; } catch (ResolutionFailedException) { //Do not add benchmark for types that could not be resolved (for example types that are extensible by other plugins // via plugin view ...). These types will simply not be benchmarked. } } } //Global cleanup method //Because BenchmarkDotNet does not allow to report benchmark methods return values //We had to "hack" around it by using these two methods //And save the method results inside a file and then (when showing the report) load it from the file //The GlobalCleanup should not be measured as a part of the benchmark //However we still need to somewhere remember the return value of the methods //Because writing to file in the benchmark method itself would totally kill the performance, we have decided to store it in dictionary //Which still causes some test distortion but it should be OK (all the tests use it so their base-line is just shifted) benchmarkClassTemplate.AppendLine(@" [BenchmarkDotNet.Attributes.GlobalCleanup] public void GlobalCleanup() { //We want to call it only once, not after every [Benchmark] method if (results.Count() == " + numOfTests + @") { using (var sw = new System.IO.StreamWriter(""results.txt"", true)) { //foreach (var r in randomRects) //for debug purposes // sw.WriteLine(r.Width + ""x"" + r.Height); foreach (var res in results) sw.WriteLine(res.Key + "";"" + res.Value.Item1 + "";"" + res.Value.Item1 + "";"" + res.Value.Item2); } results.Clear(); } } "); benchmarkClassTemplate.AppendLine("}"); //Benchmark class ends here Console.WriteLine("Please wait ... (Compiling the Benchmarks)"); //Create syntax tree and compile it SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(benchmarkClassTemplate.ToString()); CSharpCompilation compilation = CSharpCompilation.Create( assemblyName, syntaxTrees: new[] { syntaxTree }, references: references, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release)); //Emit the CIL into memory stream using var ms = new MemoryStream(); EmitResult result = compilation.Emit(ms); if (!result.Success) { IEnumerable <Microsoft.CodeAnalysis.Diagnostic> failures = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error); foreach (Microsoft.CodeAnalysis.Diagnostic diagnostic in failures) { Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage()); } } else { ms.Seek(0, SeekOrigin.Begin); Assembly assembly = Assembly.Load(ms.ToArray()); Type type = assembly.GetType($"{typeof(T).Name}Benchmarks"); Console.WriteLine("Starting the Benchmarks"); var x = BenchmarkDotNet.Running.BenchmarkRunner.Run(type, new Config()); Console.WriteLine("Done"); } }