// // Test the generated API for common protocol support // // Authors: // Sebastien Pouliot // // Copyright 2013, 2015 Xamarin Inc. // using System; using System.Reflection; using System.Runtime.InteropServices; using NUnit.Framework; #if XAMCORE_2_0 using Foundation; using ObjCRuntime; #elif MONOMAC using MonoMac.Foundation; using MonoMac.ObjCRuntime; #else using MonoTouch.Foundation; using MonoTouch.ObjCRuntime; #endif namespace Introspection { public abstract class ApiProtocolTest : ApiBaseTest { static IntPtr conform_to = Selector.GetHandle ("conformsToProtocol:"); public ApiProtocolTest () { ContinueOnFailure = true; } static bool ConformTo (IntPtr klass, IntPtr protocol) { return bool_objc_msgSend_IntPtr (klass, conform_to, protocol); } protected virtual bool Skip (Type type) { switch (type.Name) { // *** NSForwarding: warning: object 0x5cbd078 of class 'JSExport' does not implement methodSignatureForSelector: -- trouble ahead // *** NSForwarding: warning: object 0x5cbd078 of class 'JSExport' does not implement doesNotRecognizeSelector: -- abort case "JSExport": return true; default: #if !XAMCORE_2_0 // in Classic our internal delegates _inherits_ the type with the [Protocol] attribute // in Unified our internal delegates _implements_ the interface that has the [Protocol] attribute if (type.GetCustomAttribute (true) != null) return true; #endif return SkipDueToAttribute (type); } } IntPtr GetClass (Type type) { return Class.GetHandle (type); } protected virtual bool Skip (Type type, string protocolName) { switch (protocolName) { case "NSCopying": switch (type.Name) { // undocumented conformance (up to 7.0) and conformity varies between iOS versions case "CAEmitterCell": case "GKAchievement": case "GKScore": // new in iOS8 and 10.0 case "NSExtensionContext": return true; // skip } break; case "NSCoding": switch (type.Name) { // only documented to support NSCopying - not NSCoding (fails on iOS 7.1 but not 8.0) case "NSUrlSessionTask": case "NSUrlSessionDataTask": case "NSUrlSessionUploadTask": case "NSUrlSessionDownloadTask": // case "NSUrlSessionConfiguration": case "NSMergeConflict": // new in iOS8 and 10.0 case "NSExtensionContext": case "NSItemProvider": // undocumented return true; } break; case "NSSecureCoding": switch (type.Name) { case "NSMergeConflict": // undocumented // only documented to support NSCopying (and OSX side only does that) case "NSUrlSessionTask": case "NSUrlSessionDataTask": case "NSUrlSessionUploadTask": case "NSUrlSessionDownloadTask": case "NSUrlSessionConfiguration": // new in iOS8 and 10.0 case "NSExtensionContext": case "NSItemProvider": case "NSParagraphStyle": //17770106 case "NSMutableParagraphStyle": //17770106 return true; // skip // iOS9 / 10.11 case "NSPersonNameComponentsFormatter": return true; // skip } break; } return false; } void CheckProtocol (string protocolName, Action action) { IntPtr protocol = Runtime.GetProtocol (protocolName); Assert.AreNotEqual (protocol, IntPtr.Zero, protocolName); int n = 0; foreach (Type t in Assembly.GetTypes ()) { if (!NSObjectType.IsAssignableFrom (t)) continue; if (Skip (t) || Skip (t, protocolName)) continue; if (LogProgress) Console.WriteLine ("{0}. {1} conforms to {2}", ++n, t.FullName, protocolName); IntPtr klass = GetClass (t); action (t, klass, ConformTo (klass, protocol)); } } [Test] public void Coding () { Errors = 0; CheckProtocol ("NSCoding", delegate (Type type, IntPtr klass, bool result) { if (result) { // `type` conforms to (native) NSCoding so... if (result) { // the type should implements INSCoding if (!typeof (INSCoding).IsAssignableFrom (type)) { ReportError ("{0} conforms to NSCoding but does not implement INSCoding", type.Name); } // FIXME: and implement the .ctor(NSCoder) } } }); Assert.AreEqual (Errors, 0, "{0} types conforms to NSCoding but does not implement INSCoding", Errors); } // [Test] -> iOS 6.0+ and Mountain Lion (10.8) + public virtual void SecureCoding () { Errors = 0; CheckProtocol ("NSSecureCoding", delegate (Type type, IntPtr klass, bool result) { if (result) { // the type should implements INSSecureCoding if (!typeof (INSSecureCoding).IsAssignableFrom (type)) { ReportError ("{0} conforms to NSSecureCoding but does not implement INSSecureCoding", type.Name); } } }); Assert.AreEqual (Errors, 0, "{0} types conforms to NSSecureCoding but does not implement INSSecureCoding", Errors); } bool SupportsSecureCoding (Type type) { Class cls = new Class (type); if (!bool_objc_msgSend_IntPtr (cls.Handle, Selector.GetHandle ("respondsToSelector:"), Selector.GetHandle ("supportsSecureCoding"))) return false; return NSSecureCoding.SupportsSecureCoding (type); } // [Test] -> iOS 6.0+ and Mountain Lion (10.8) + public virtual void SupportsSecureCoding () { Errors = 0; CheckProtocol ("NSSecureCoding", delegate (Type type, IntPtr klass, bool result) { bool supports = SupportsSecureCoding (type); if (result) { // check that +supportsSecureCoding returns YES if (!supports) { ReportError ("{0} conforms to NSSecureCoding but SupportsSecureCoding returned false", type.Name); } } else if (type.IsPublic && supports) { // there are internal types, e.g. DataWrapper : NSData, that subclass NSSecureCoding-types without // [re-]declaring their allegiance - but we can live with those small betrayals Assert.IsFalse (NSSecureCoding.SupportsSecureCoding (type), "{0} !SupportsSecureCoding", type.Name); ReportError ("SupportsSecureCoding returns true but {0} does not conforms to NSSecureCoding", type.Name); } }); Assert.AreEqual (Errors, 0, "{0} types conforms to NSCoding but does not implement INSSecureCoding", Errors); } [Test] public void Copying () { Errors = 0; CheckProtocol ("NSCopying", delegate (Type type, IntPtr klass, bool result) { // `type` conforms to (native) NSCopying so... if (result) { // the type should implements INSCopying if (!typeof (INSCopying).IsAssignableFrom (type)) { ReportError ("{0} conforms to NSCopying but does not implement INSCopying", type.Name); } } }); Assert.AreEqual (Errors, 0, "{0} types conforms to NSCopying but does not implement INSCopying", Errors); } [Test] public void MutableCopying () { Errors = 0; CheckProtocol ("NSMutableCopying", delegate (Type type, IntPtr klass, bool result) { // `type` conforms to (native) NSMutableCopying so... if (result) { // the type should implements INSMutableCopying if (!typeof (INSMutableCopying).IsAssignableFrom (type)) { ReportError ("{0} conforms to NSMutableCopying but does not implement INSMutableCopying", type.Name); } } }); Assert.AreEqual (Errors, 0, "{0} types conforms to NSMutableCopying but does not implement INSMutableCopying", Errors); } [Test] public void GeneralCase () { Errors = 0; foreach (Type t in Assembly.GetTypes ()) { if (!NSObjectType.IsAssignableFrom (t)) continue; if (Skip (t)) continue; foreach (var intf in t.GetInterfaces ()) { if (SkipDueToAttribute (intf)) continue; string protocolName = intf.Name.Substring (1); switch (protocolName) { case "NSCoding": case "NSSecureCoding": case "NSCopying": case "NSMutableCopying": // we have special test cases for them continue; default: #if !XAMCORE_2_0 // in Classic our internal delegates _inherits_ the type with the [Protocol] attribute // in Unified our internal delegates _implements_ the interface that has the [Protocol] attribute var pt = Type.GetType (intf.Namespace + "." + protocolName + ", " + intf.Assembly.FullName); if (SkipDueToAttribute (pt)) continue; #endif if (Skip (t, protocolName)) continue; break; } var a = intf.GetCustomAttribute (true); if (a == null || a.IsInformal) continue; IntPtr protocol = Runtime.GetProtocol (protocolName); if (protocol == IntPtr.Zero) continue; // not a protocol if (LogProgress) Console.WriteLine ("{0} conforms to {1}", t.FullName, protocolName); var klass = new Class (t); if (klass.Handle == IntPtr.Zero) { AddErrorLine ("Could not load {0}", t.FullName); } else if (t.IsPublic && !ConformTo (klass.Handle, protocol)) { // note: some internal types, e.g. like UIAppearance subclasses, return false (and there's not much value in changing this) ReportError ("Type {0} (native: {1}) does not conform {2}", t.FullName, klass.Name, protocolName); } } } AssertIfErrors ("{0} types do not really conform to the protocol interfaces", Errors); } } }