TestBase.cs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  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 = "")
  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. using (RenderTargetBitmap bitmap = new RenderTargetBitmap(
  67. (int)target.Width,
  68. (int)target.Height))
  69. {
  70. Size size = new Size(target.Width, target.Height);
  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((int)target.Width, (int)target.Height, 96, 96))
  77. using (var renderer = new DeferredRenderer(target, rtb))
  78. {
  79. Size size = new Size(target.Width, target.Height);
  80. target.Measure(size);
  81. target.Arrange(new Rect(size));
  82. renderer.UnitTestUpdateScene();
  83. // Do the deferred render on a background thread to expose any threading errors in
  84. // the deferred rendering path.
  85. await Task.Run((Action)renderer.UnitTestRender);
  86. rtb.Save(deferredPath);
  87. }
  88. }
  89. protected void CompareImages([CallerMemberName] string testName = "")
  90. {
  91. var expectedPath = Path.Combine(OutputPath, testName + ".expected.png");
  92. var immediatePath = Path.Combine(OutputPath, testName + ".immediate.out.png");
  93. var deferredPath = Path.Combine(OutputPath, testName + ".deferred.out.png");
  94. using (var expected = new MagickImage(expectedPath))
  95. using (var immediate = new MagickImage(immediatePath))
  96. using (var deferred = new MagickImage(deferredPath))
  97. {
  98. double immediateError = expected.Compare(immediate, ErrorMetric.RootMeanSquared);
  99. double deferredError = expected.Compare(deferred, ErrorMetric.RootMeanSquared);
  100. if (immediateError > 0.022)
  101. {
  102. Assert.True(false, immediatePath + ": Error = " + immediateError);
  103. }
  104. if (deferredError > 0.022)
  105. {
  106. Assert.True(false, deferredPath + ": Error = " + deferredError);
  107. }
  108. }
  109. }
  110. protected void CompareImagesNoRenderer([CallerMemberName] string testName = "")
  111. {
  112. var expectedPath = Path.Combine(OutputPath, testName + ".expected.png");
  113. var actualPath = Path.Combine(OutputPath, testName + ".out.png");
  114. using (var expected = new MagickImage(expectedPath))
  115. using (var actual = new MagickImage(actualPath))
  116. {
  117. double immediateError = expected.Compare(actual, ErrorMetric.RootMeanSquared);
  118. if (immediateError > 0.022)
  119. {
  120. Assert.True(false, actualPath + ": Error = " + immediateError);
  121. }
  122. }
  123. }
  124. private string GetTestsDirectory()
  125. {
  126. var path = Directory.GetCurrentDirectory();
  127. while (path.Length > 0 && Path.GetFileName(path) != "tests")
  128. {
  129. path = Path.GetDirectoryName(path);
  130. }
  131. return path;
  132. }
  133. private class TestThreadingInterface : IPlatformThreadingInterface
  134. {
  135. public bool CurrentThreadIsLoopThread => MainThread.ManagedThreadId == Thread.CurrentThread.ManagedThreadId;
  136. public Thread MainThread { get; set; }
  137. public event Action<DispatcherPriority?> Signaled;
  138. public void RunLoop(CancellationToken cancellationToken)
  139. {
  140. throw new NotImplementedException();
  141. }
  142. public void Signal(DispatcherPriority prio)
  143. {
  144. throw new NotImplementedException();
  145. }
  146. public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
  147. {
  148. throw new NotImplementedException();
  149. }
  150. }
  151. }
  152. }