ApiApprovalTests.cs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT License.
  3. // See the LICENSE file in the project root for more information.
  4. using PublicApiGenerator;
  5. using System;
  6. using System.Diagnostics;
  7. using System.Linq;
  8. using System.Reflection;
  9. using System.Runtime.CompilerServices;
  10. using System.Text;
  11. using System.Threading.Tasks;
  12. using VerifyTests;
  13. using VerifyXunit;
  14. using Xunit;
  15. namespace ReactiveTests.Tests.Api
  16. {
  17. public class ApiApprovalTests : VerifyBase
  18. {
  19. static ApiApprovalTests()
  20. {
  21. VerifierSettings.OnVerifyMismatch((filePair, message) => DiffPlexReporter.Report(filePair.ReceivedPath, filePair.VerifiedPath, message));
  22. }
  23. public ApiApprovalTests()
  24. : base()
  25. {
  26. }
  27. // Note:
  28. // System.Reactive (now a facade) uses the .NET SDK's built in package validation, specifically the
  29. // PackageValidationBaselineVersion feature to ensure backwards compatibility
  30. // System.Reactive.Net is using Microsoft.CodeAnalysis.PublicApiAnalyzers to ensure stability of
  31. // its public API.
  32. // TODO:
  33. // Move Aliases and Testing packages over to one of the mechanisms above
  34. // Add similar API checking to:
  35. // The old facade packages
  36. // The new FrameworkIntegrations packages
  37. [Fact]
  38. public Task Aliases()
  39. {
  40. var publicApi = GeneratePublicApi(typeof(System.Reactive.Observable.Aliases.QueryLanguage).Assembly);
  41. return Verify(publicApi, "cs");
  42. }
  43. [Fact]
  44. public Task Testing()
  45. {
  46. var publicApi = GeneratePublicApi(typeof(Microsoft.Reactive.Testing.TestScheduler).Assembly);
  47. return Verify(publicApi, "cs");
  48. }
  49. private string GeneratePublicApi(Assembly assembly)
  50. {
  51. var options = MakeGeneratorOptions();
  52. return Filter(ApiGenerator.GeneratePublicApi(assembly, options));
  53. }
  54. private static ApiGeneratorOptions MakeGeneratorOptions()
  55. {
  56. return new()
  57. {
  58. AllowNamespacePrefixes = ["System", "Microsoft"]
  59. };
  60. }
  61. private string GeneratePublicApiIncludingTypeForwarders(Assembly assembly)
  62. {
  63. var ts = assembly.GetTypes();
  64. var ets = assembly.GetExportedTypes();
  65. var ets2 = assembly.ExportedTypes;
  66. var attrs = assembly.GetCustomAttributes();
  67. var asmDef = Mono.Cecil.AssemblyDefinition.ReadAssembly(assembly.Location);
  68. //foreach (var exportedType in asmDef.MainModule.ExportedTypes)
  69. //{
  70. // if (exportedType.IsForwarder)
  71. // {
  72. // Console.WriteLine($"Forwarded Type: {exportedType.FullName}");
  73. // }
  74. //}
  75. var options = MakeGeneratorOptions();
  76. Type[] types = asmDef.MainModule.ExportedTypes
  77. .Where(t => t.IsForwarder)
  78. .Select(t =>
  79. {
  80. var type = assembly.GetType(t.FullName);
  81. if (type == null)
  82. {
  83. Debugger.Break();
  84. }
  85. return type;
  86. })
  87. .Concat(assembly.ExportedTypes) // DOESN'T WORK!
  88. .ToArray();
  89. return Filter(ApiGenerator.GeneratePublicApi(types, options));
  90. //.GroupBy(t => t.Namespace)
  91. //.OrderBy(ns => ns.Key)
  92. //.Select(ns =>
  93. //{
  94. // StringBuilder sb = new();
  95. // sb.AppendLine($"namespace {ns.Key}");
  96. // sb.AppendLine("{");
  97. // foreach (var type in ns.OrderBy(t => t.Name))
  98. // {v
  99. // string typePublicApi = ApiGenerator.GeneratePublicApi(type, options);
  100. // sb.AppendLine(typePublicApi);
  101. // }
  102. // sb.AppendLine("}");
  103. // return sb.ToString();
  104. //}));
  105. }
  106. private static string Filter(string text)
  107. {
  108. return string.Join(Environment.NewLine, text.Split(
  109. [
  110. Environment.NewLine
  111. ], StringSplitOptions.RemoveEmptyEntries)
  112. .Where(l => !l.StartsWith("[assembly: AssemblyVersion("))
  113. .Where(l => !l.StartsWith("[assembly: AssemblyFileVersion("))
  114. .Where(l => !l.StartsWith("[assembly: AssemblyInformationalVersion("))
  115. .Where(l => !l.StartsWith("[assembly: System.Reflection.AssemblyMetadata(\"CommitHash\""))
  116. .Where(l => !l.StartsWith("[assembly: System.Reflection.AssemblyMetadata(\"RepositoryUrl\""))
  117. .Where(l => !string.IsNullOrWhiteSpace(l))
  118. );
  119. }
  120. }
  121. }