TestBase.cs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System.IO;
  4. using System.Runtime.CompilerServices;
  5. using ImageMagick;
  6. using Avalonia.Controls;
  7. using Avalonia.Media.Imaging;
  8. using Avalonia.Rendering;
  9. using Xunit;
  10. using Avalonia.Platform;
  11. using System.Threading.Tasks;
  12. using System;
  13. using System.Threading;
  14. using Avalonia.Threading;
  15. #if AVALONIA_SKIA
  16. using Avalonia.Skia;
  17. #else
  18. using Avalonia.Direct2D1;
  19. #endif
  20. #if AVALONIA_SKIA
  21. namespace Avalonia.Skia.RenderTests
  22. #else
  23. namespace Avalonia.Direct2D1.RenderTests
  24. #endif
  25. {
  26. public class TestBase
  27. {
  28. private static readonly TestThreadingInterface threadingInterface =
  29. new TestThreadingInterface();
  30. static TestBase()
  31. {
  32. #if AVALONIA_SKIA
  33. SkiaPlatform.Initialize();
  34. #else
  35. Direct2D1Platform.Initialize();
  36. #endif
  37. AvaloniaLocator.CurrentMutable
  38. .Bind<IPlatformThreadingInterface>()
  39. .ToConstant(threadingInterface);
  40. }
  41. public TestBase(string outputPath)
  42. {
  43. var testPath = GetTestsDirectory();
  44. var testFiles = Path.Combine(testPath, "TestFiles");
  45. #if AVALONIA_SKIA
  46. var platform = "Skia";
  47. #else
  48. var platform = "Direct2D1";
  49. #endif
  50. OutputPath = Path.Combine(testFiles, platform, outputPath);
  51. threadingInterface.MainThread = Thread.CurrentThread;
  52. }
  53. public string OutputPath
  54. {
  55. get;
  56. }
  57. protected async Task RenderToFile(Control target, [CallerMemberName] string testName = "", double dpi = 96)
  58. {
  59. if (!Directory.Exists(OutputPath))
  60. {
  61. Directory.CreateDirectory(OutputPath);
  62. }
  63. var immediatePath = Path.Combine(OutputPath, testName + ".immediate.out.png");
  64. var deferredPath = Path.Combine(OutputPath, testName + ".deferred.out.png");
  65. var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
  66. var pixelSize = new PixelSize((int)target.Width, (int)target.Height);
  67. var size = new Size(target.Width, target.Height);
  68. var dpiVector = new Vector(dpi, dpi);
  69. using (RenderTargetBitmap bitmap = new RenderTargetBitmap(pixelSize, dpiVector))
  70. {
  71. target.Measure(size);
  72. target.Arrange(new Rect(size));
  73. bitmap.Render(target);
  74. bitmap.Save(immediatePath);
  75. }
  76. using (var rtb = factory.CreateRenderTargetBitmap(pixelSize, dpiVector))
  77. using (var renderer = new DeferredRenderer(target, rtb))
  78. {
  79. target.Measure(size);
  80. target.Arrange(new Rect(size));
  81. renderer.UnitTestUpdateScene();
  82. // Do the deferred render on a background thread to expose any threading errors in
  83. // the deferred rendering path.
  84. await Task.Run((Action)renderer.UnitTestRender);
  85. rtb.Save(deferredPath);
  86. }
  87. }
  88. protected void CompareImages([CallerMemberName] string testName = "")
  89. {
  90. var expectedPath = Path.Combine(OutputPath, testName + ".expected.png");
  91. var immediatePath = Path.Combine(OutputPath, testName + ".immediate.out.png");
  92. var deferredPath = Path.Combine(OutputPath, testName + ".deferred.out.png");
  93. using (var expected = new MagickImage(expectedPath))
  94. using (var immediate = new MagickImage(immediatePath))
  95. using (var deferred = new MagickImage(deferredPath))
  96. {
  97. double immediateError = expected.Compare(immediate, ErrorMetric.RootMeanSquared);
  98. double deferredError = expected.Compare(deferred, ErrorMetric.RootMeanSquared);
  99. if (immediateError > 0.022)
  100. {
  101. Assert.True(false, immediatePath + ": Error = " + immediateError);
  102. }
  103. if (deferredError > 0.022)
  104. {
  105. Assert.True(false, deferredPath + ": Error = " + deferredError);
  106. }
  107. }
  108. }
  109. protected void CompareImagesNoRenderer([CallerMemberName] string testName = "")
  110. {
  111. var expectedPath = Path.Combine(OutputPath, testName + ".expected.png");
  112. var actualPath = Path.Combine(OutputPath, testName + ".out.png");
  113. using (var expected = new MagickImage(expectedPath))
  114. using (var actual = new MagickImage(actualPath))
  115. {
  116. double immediateError = expected.Compare(actual, ErrorMetric.RootMeanSquared);
  117. if (immediateError > 0.022)
  118. {
  119. Assert.True(false, actualPath + ": Error = " + immediateError);
  120. }
  121. }
  122. }
  123. private string GetTestsDirectory()
  124. {
  125. var path = Directory.GetCurrentDirectory();
  126. while (path.Length > 0 && Path.GetFileName(path) != "tests")
  127. {
  128. path = Path.GetDirectoryName(path);
  129. }
  130. return path;
  131. }
  132. private class TestThreadingInterface : IPlatformThreadingInterface
  133. {
  134. public bool CurrentThreadIsLoopThread => MainThread.ManagedThreadId == Thread.CurrentThread.ManagedThreadId;
  135. public Thread MainThread { get; set; }
  136. #pragma warning disable 67
  137. public event Action<DispatcherPriority?> Signaled;
  138. #pragma warning restore 67
  139. public void RunLoop(CancellationToken cancellationToken)
  140. {
  141. throw new NotImplementedException();
  142. }
  143. public void Signal(DispatcherPriority prio)
  144. {
  145. throw new NotImplementedException();
  146. }
  147. public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
  148. {
  149. throw new NotImplementedException();
  150. }
  151. }
  152. }
  153. }