using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using NUnit.Framework; using Mono.Cecil; using Mono.Cecil.Cil; using Xamarin.Tests; using Xamarin.Utils; #nullable enable namespace Cecil.Tests { [TestFixture] public partial class ApiTest { [TestCaseSource (typeof (Helper), nameof (Helper.PlatformAssemblyDefinitions))] [TestCaseSource (typeof (Helper), nameof (Helper.NetPlatformAssemblyDefinitions))] public void ARConfiguration_GetSupportedVideoFormats (AssemblyInfo info) { // all subclasses of ARConfiguration must (re)export 'GetSupportedVideoFormats' var assembly = info.Assembly; List? failures = null; if (!assembly.EnumerateTypes ((type) => type.Is ("ARKit", "ARConfiguration")).Any ()) Assert.Ignore (); // This assembly doesn't contain ARKit.ARConfiguration var subclasses = assembly.EnumerateTypes ((type) => !type.Is ("ARKit", "ARConfiguration") && type.IsSubclassOf ("ARKit", "ARConfiguration")); Assert.That (subclasses, Is.Not.Empty, "At least some subclasses"); foreach (var type in subclasses) { var method = type.Methods.SingleOrDefault (m => m.Name == "GetSupportedVideoFormats" && m.IsPublic && m.IsStatic && !m.HasParameters); if (method is null) AddFailure (ref failures, $"The type {type.FullName} does not implement the method GetSupportedVideoFormats."); } Assert.That (failures, Is.Null.Or.Empty, "All subclasses from ARConfiguration must explicitly implement GetSupportedVideoFormats."); } static void AddFailure (ref List? failures, string failure) { if (failures is null) failures = new List (); failures.Add (failure); Console.WriteLine (failure); } // Capitalization rules: // * All types, methods, properties, fields, events must start with an upper-cased letter. // * All parameters must start with a lower-cased letter [Test] public void MustStartWithCapitalLetter () { Configuration.IgnoreIfAnyIgnoredPlatforms (); var failures = new Dictionary (); foreach (var info in Helper.NetPlatformImplementationAssemblyDefinitions) { var assembly = info.Assembly; foreach (var member in assembly.EnumeratePublicMembers ()) { // Presumably obsolete members have been obsoleted for a reason, so assume there's a correctly capitalized version. if (((ICustomAttributeProvider) member).IsObsolete ()) continue; if (member is MethodDefinition method) { // Check parameter names foreach (var param in method.Parameters) { if (param.Index == 0 && method.IsExtensionMethod ()) continue; if (!char.IsLower (param.Name [0])) { var msg = $"The parameter '{param.Name}' in the method '{method.FullName}' has incorrect capitalization: first letter is not lower case."; failures [msg] = new (msg, method.RenderLocation ()); } } // skip constructors if (method.IsConstructor) continue; // skip property accessors if (method.IsPropertyAccessor ()) continue; // skip event methods if (method.IsEventMethod ()) continue; // skip operators if (method.IsOperator ()) continue; } else if (member is FieldDefinition field) { if (field.Name == "value__" && !field.IsStatic && field.DeclaringType.IsEnum) continue; } if (!char.IsUpper (member.Name [0]) && !IsAcceptableCapitalization (member)) { var msg = $"The {member.GetType ().Name.Replace ("Definition", "").ToLower ()} '{member.FullName}' has incorrect capitalization: first letter is not upper case."; failures [msg] = new (msg, member.RenderLocation ()); } } } Helper.AssertFailures (failures, knownFailuresMustStartWithCapitalizationLetter, nameof (knownFailuresMustStartWithCapitalizationLetter), "Incorrect capitalization", (v) => $"{v.Location}: {v.Message}"); } // The difference between this and the known failures is that these aren't failures (that should be fixed), they're exceptions we've deemed acceptable (and shouldn't be fixed). bool IsAcceptableCapitalization (MemberReference mr) { if (mr is FieldDefinition field) { switch (field.DeclaringType.FullName) { case "Metal.MTLFeatureSet": if (field.Name.StartsWith ("iOS_", StringComparison.Ordinal)) return true; if (field.Name.StartsWith ("macOS_", StringComparison.Ordinal)) return true; if (field.Name.StartsWith ("tvOS_", StringComparison.Ordinal)) return true; break; case "Metal.MTLGpuFamily": if (field.Name.StartsWith ("iOSMac", StringComparison.Ordinal)) return true; break; } } else if (mr is PropertyDefinition property) { switch (property.DeclaringType.FullName) { case "AVFoundation.AVMetadata": if (property.Name.StartsWith ("iTunes", StringComparison.Ordinal)) return true; break; } } return false; } [Test] public void InvalidStrings () { Configuration.IgnoreIfAnyIgnoredPlatforms (); var invalidStrings = new [] { new { Find = "URL", Replacement = "Url" } }; var failures = new Dictionary (); foreach (var info in Helper.NetPlatformImplementationAssemblyDefinitions) { var assembly = info.Assembly; foreach (var member in assembly.EnumeratePublicMembers ()) { foreach (var term in invalidStrings) { if (member.Name.Contains (term.Find)) { var msg = $"The {member.GetType ().Name.Replace ("Definition", "").ToLower ()} '{member.FullName}' has an invalid term '{term.Find}'. Replace with: '{term.Replacement}'."; failures [msg] = new (msg, member.RenderLocation ()); } } } } Helper.AssertFailures (failures, knownFailuresInvalidStrings, nameof (knownFailuresInvalidStrings), "In the file tests/cecil-tests/ApiTest.cs, read the guide carefully.", (v) => $"{v.Location}: {v.Message}"); } } }