xamarin-macios/tests/introspection/ApiSelectorTest.cs

1160 строки
33 KiB
C#

//
// Test the generated API selectors against typos or non-existing cases
//
// Authors:
// Sebastien Pouliot <sebastien@xamarin.com>
//
// Copyright 2012-2013 Xamarin Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
using System;
using System.Reflection;
using NUnit.Framework;
using Foundation;
using ObjCRuntime;
namespace Introspection {
public abstract class ApiSelectorTest : ApiBaseTest {
// not everything should be even tried
protected virtual bool Skip (Type type)
{
if (type.ContainsGenericParameters)
return true;
// skip delegate (and other protocol references)
foreach (object ca in type.GetCustomAttributes (false)) {
if (ca is ProtocolAttribute)
return true;
if (ca is ModelAttribute)
return true;
}
switch (type.FullName) {
case "MetalPerformanceShaders.MPSCommandBuffer":
// The reflectable type metadata contains no selectors.
return true;
}
return SkipDueToAttribute (type);
}
protected virtual bool Skip (Type type, string selectorName)
{
// The MapKit types/selectors are optional protocol members pulled in from MKAnnotation/MKOverlay.
// These concrete (wrapper) subclasses do not implement all of those optional members, but we
// still need to provide a binding for them, so that user subclasses can implement those members.
switch (type.Name) {
case "AVAggregateAssetDownloadTask":
switch (selectorName) {
case "URLAsset": // added in Xcode 9 and it is present.
return true;
}
break;
case "AVAssetDownloadStorageManager":
switch (selectorName) {
case "sharedDownloadStorageManager": // added in Xcode 9 and it is present.
return true;
}
break;
case "MKCircle":
case "MKPolygon":
case "MKPolyline":
switch (selectorName) {
case "canReplaceMapContent":
return true;
}
break;
case "MKShape":
switch (selectorName) {
case "setCoordinate:":
return true;
}
break;
case "MKPlacemark":
switch (selectorName) {
case "setCoordinate:":
case "subtitle":
return true;
}
break;
case "MKTileOverlay":
switch (selectorName) {
case "intersectsMapRect:":
return true;
}
break;
// AVAudioChannelLayout and AVAudioFormat started conforming to NSSecureCoding in OSX 10.11 and iOS 9
case "AVAudioChannelLayout":
case "AVAudioFormat":
// NSSecureCoding added in iOS 10 / macOS 10.12
case "CNContactFetchRequest":
case "GKEntity":
case "GKPolygonObstacle":
case "GKComponent":
case "GKGraphNode":
case "WKUserContentController":
case "WKProcessPool":
case "WKWebViewConfiguration":
case "WKWebsiteDataStore":
switch (selectorName) {
case "encodeWithCoder:":
return true;
}
break;
// SKTransition started conforming to NSCopying in OSX 10.11 and iOS 9
case "SKTransition":
// iOS 10 beta 2
case "GKBehavior":
case "MDLTransform":
// UISceneActivationRequestOptions started conforming to NSCopying oin Xcode 13
case "UISceneActivationRequestOptions":
switch (selectorName) {
case "copyWithZone:":
return true;
}
break;
case "MDLMaterialProperty":
switch (selectorName) {
case "copyWithZone:":
// not working before iOS 10, macOS 10.12
return !TestRuntime.CheckXcodeVersion (8, 0);
}
break;
// Xcode 8 beta 2
case "GKGraph":
case "GKAgent":
case "GKAgent2D":
case "NEFlowMetaData":
case "NWEndpoint":
switch (selectorName) {
case "copyWithZone:":
case "encodeWithCoder:":
return true;
}
break;
// now conforms to MDLName
case "MTKMeshBuffer":
switch (selectorName) {
case "name":
case "setName:":
return true;
}
break;
// Xcode 9
case "CIQRCodeFeature":
switch (selectorName) {
case "copyWithZone:":
case "encodeWithCoder:":
return !TestRuntime.CheckXcodeVersion (9, 0);
}
break;
case "CKFetchRecordZoneChangesOptions":
switch (selectorName) {
case "copyWithZone:":
return !TestRuntime.CheckXcodeVersion (9, 0);
}
break;
#if !XAMCORE_4_0
case "NSUrl":
case "ARQuickLookPreviewItem":
switch (selectorName) {
case "previewItemTitle":
// 'previewItemTitle' is inlined from the QLPreviewItem protocol and should be optional (fixed in XAMCORE_4_0)
return true;
}
break;
#endif
case "MKMapItem": // Selector not available on iOS 32-bit
switch (selectorName) {
case "encodeWithCoder:":
return !TestRuntime.CheckXcodeVersion (9, 0);
}
break;
#if !MONOMAC
case "MTLCaptureManager":
case "NEHotspotEapSettings": // Wireless Accessory Configuration is not supported in the simulator.
case "NEHotspotConfigurationManager":
case "NEHotspotHS20Settings":
if (Runtime.Arch == Arch.SIMULATOR)
return true;
break;
case "ARBodyTrackingConfiguration":
case "ARGeoTrackingConfiguration":
switch (selectorName) {
case "supportsAppClipCodeTracking": // Only available on device
return Runtime.Arch == Arch.SIMULATOR;
}
break;
case "CSImportExtension":
switch (selectorName) {
case "beginRequestWithExtensionContext:":
case "updateAttributes:forFileAtURL:error:":
if (Runtime.Arch == Arch.SIMULATOR) // not available in the sim
return true;
break;
}
break;
case "HKQuery":
switch (selectorName) {
case "predicateForVerifiableClinicalRecordsWithRelevantDateWithinDateInterval:": // not available in the sim
if (Runtime.Arch == Arch.SIMULATOR) // not available in the sim
return true;
break;
}
break;
#endif
case "WKPreferences":
switch (selectorName) {
case "encodeWithCoder:": // from iOS 10
return true;
case "textInteractionEnabled": // xcode 13 renamed this to `isTextInteractionEnabled` but does not respond to the old one
return true;
}
break;
}
// This ctors needs to be manually bound
switch (type.Name) {
case "AVCaptureVideoPreviewLayer":
switch (selectorName) {
case "initWithSession:":
case "initWithSessionWithNoConnection:":
return true;
}
break;
case "GKPath":
switch (selectorName) {
case "initWithPoints:count:radius:cyclical:":
case "initWithFloat3Points:count:radius:cyclical:":
return true;
}
break;
case "GKPolygonObstacle":
switch (selectorName) {
case "initWithPoints:count:":
return true;
}
break;
case "MDLMesh":
switch (selectorName) {
case "initCapsuleWithExtent:cylinderSegments:hemisphereSegments:inwardNormals:geometryType:allocator:":
case "initConeWithExtent:segments:inwardNormals:cap:geometryType:allocator:":
case "initHemisphereWithExtent:segments:inwardNormals:cap:geometryType:allocator:":
case "initMeshBySubdividingMesh:submeshIndex:subdivisionLevels:allocator:":
case "initSphereWithExtent:segments:inwardNormals:geometryType:allocator:":
case "initBoxWithExtent:segments:inwardNormals:geometryType:allocator:":
case "initCylinderWithExtent:segments:inwardNormals:topCap:bottomCap:geometryType:allocator:":
case "initIcosahedronWithExtent:inwardNormals:geometryType:allocator:":
case "initPlaneWithExtent:segments:geometryType:allocator:":
return true;
}
break;
case "MDLNoiseTexture":
switch (selectorName) {
case "initCellularNoiseWithFrequency:name:textureDimensions:channelEncoding:":
case "initVectorNoiseWithSmoothness:name:textureDimensions:channelEncoding:":
return true;
}
break;
case "NSImage":
switch (selectorName) {
case "initByReferencingFile:":
return true;
}
break;
case "OSLogMessageComponent":
switch (selectorName) {
case "encodeWithCoder:":
if (!TestRuntime.CheckXcodeVersion (13, 0))
return true;
break;
}
break;
// Conform to SKWarpable
case "SKEffectNode":
case "SKSpriteNode":
switch (selectorName) {
case "setSubdivisionLevels:":
case "setWarpGeometry:":
return true;
}
break;
case "SKAttribute":
case "SKAttributeValue":
switch (selectorName) {
case "encodeWithCoder:":
if (!TestRuntime.CheckXcodeVersion (8, 0))
return true;
break;
}
break;
case "SKUniform":
switch (selectorName) {
// New selectors
case "initWithName:vectorFloat2:":
case "initWithName:vectorFloat3:":
case "initWithName:vectorFloat4:":
case "initWithName:matrixFloat2x2:":
case "initWithName:matrixFloat3x3:":
case "initWithName:matrixFloat4x4:":
// Old selectors
case "initWithName:floatVector2:":
case "initWithName:floatVector3:":
case "initWithName:floatVector4:":
case "initWithName:floatMatrix2:":
case "initWithName:floatMatrix3:":
case "initWithName:floatMatrix4:":
return true;
}
break;
case "SKVideoNode":
switch (selectorName) {
case "initWithFileNamed:":
case "initWithURL:":
case "initWithVideoFileNamed:":
case "initWithVideoURL:":
case "videoNodeWithFileNamed:":
case "videoNodeWithURL:":
return true;
}
break;
case "SKWarpGeometryGrid":
switch (selectorName) {
case "initWithColumns:rows:sourcePositions:destPositions:":
return true;
}
break;
case "INPriceRange":
switch (selectorName) {
case "initWithMaximumPrice:currencyCode:":
case "initWithMinimumPrice:currencyCode:":
return true;
}
break;
case "CKUserIdentityLookupInfo":
switch (selectorName) {
case "initWithEmailAddress:":
case "initWithPhoneNumber:":
case "lookupInfosWithRecordIDs:": // FAILs on watch yet we do have a unittest for it
case "lookupInfosWithEmails:": // FAILs on watch yet we do have a unittest for it
case "lookupInfosWithPhoneNumbers:": // FAILs on watch yet we do have a unittest for it
return true;
}
break;
case "AVPlayerItemVideoOutput":
switch (selectorName) {
case "initWithOutputSettings:":
case "initWithPixelBufferAttributes:":
return true;
}
break;
case "MTLBufferLayoutDescriptor": // We do have unit tests under monotouch-tests for this properties
switch (selectorName){
case "stepFunction":
case "setStepFunction:":
case "stepRate":
case "setStepRate:":
case "stride":
case "setStride:":
return true;
}
break;
case "MTLFunctionConstant": // we do have unit tests under monotouch-tests for this properties
switch (selectorName){
case "name":
case "type":
case "index":
case "required":
return true;
}
break;
case "MTLStageInputOutputDescriptor": // we do have unit tests under monotouch-tests for this properties
switch (selectorName){
case "attributes":
case "indexBufferIndex":
case "setIndexBufferIndex:":
case "indexType":
case "setIndexType:":
case "layouts":
return true;
}
break;
case "MTLAttributeDescriptor": // we do have unit tests under monotouch-tests for this properties
switch (selectorName){
case "bufferIndex":
case "setBufferIndex:":
case "format":
case "setFormat:":
case "offset":
case "setOffset:":
return true;
}
break;
case "MTLAttribute": // we do have unit tests under monotouch-tests for this properties
switch (selectorName){
case "isActive":
case "attributeIndex":
case "attributeType":
case "isPatchControlPointData":
case "isPatchData":
case "name":
case "isDepthTexture":
return true;
}
break;
case "MTLArgument": // we do have unit tests under monotouch-tests for this properties
switch (selectorName){
case "isDepthTexture":
return true;
}
break;
case "MTLArgumentDescriptor":
switch (selectorName) {
case "access":
case "setAccess:":
case "arrayLength":
case "setArrayLength:":
case "constantBlockAlignment":
case "setConstantBlockAlignment:":
case "dataType":
case "setDataType:":
case "index":
case "setIndex:":
case "textureType":
case "setTextureType:":
return true;
}
break;
case "MTLHeapDescriptor":
switch (selectorName) {
case "cpuCacheMode":
case "setCpuCacheMode:":
case "size":
case "setSize:":
case "storageMode":
case "setStorageMode:":
return true;
}
break;
case "MTLIndirectCommandBufferDescriptor": // we do have unit tests under monotouch-tests for this properties
switch (selectorName) {
case "commandTypes":
case "setCommandTypes:":
case "inheritPipelineState":
case "setInheritPipelineState:":
case "inheritBuffers":
case "setInheritBuffers:":
case "maxFragmentBufferBindCount":
case "setMaxFragmentBufferBindCount:":
case "maxVertexBufferBindCount":
case "setMaxVertexBufferBindCount:":
return true;
}
break;
case "MTLPipelineBufferDescriptor":
switch (selectorName) {
case "mutability":
case "setMutability:":
return true;
}
break;
case "MTLPointerType":
switch (selectorName) {
case "access":
case "alignment":
case "dataSize":
case "elementIsArgumentBuffer":
case "elementType":
return true;
}
break;
case "MTLSharedEventListener":
switch (selectorName) {
case "dispatchQueue":
return true;
}
break;
case "MTLTextureReferenceType":
switch (selectorName) {
case "access":
case "isDepthTexture":
case "textureDataType":
case "textureType":
return true;
}
break;
case "MTLType":
switch (selectorName) {
case "dataType":
return true;
}
break;
case "MTLTileRenderPipelineColorAttachmentDescriptor":
switch (selectorName) {
case "pixelFormat":
case "setPixelFormat:":
return true;
}
break;
case "MTLTileRenderPipelineDescriptor":
switch (selectorName) {
case "colorAttachments":
case "label":
case "setLabel:":
case "rasterSampleCount":
case "setRasterSampleCount:":
case "threadgroupSizeMatchesTileSize":
case "setThreadgroupSizeMatchesTileSize:":
case "tileBuffers":
case "tileFunction":
case "setTileFunction:":
case "maxTotalThreadsPerThreadgroup":
case "setMaxTotalThreadsPerThreadgroup:":
case "binaryArchives":
case "setBinaryArchives:":
return true;
}
break;
case "MTLBlitPassDescriptor":
switch (selectorName) {
case "sampleBufferAttachments":
return true;
}
break;
case "MTLBlitPassSampleBufferAttachmentDescriptor":
switch (selectorName) {
case "endOfEncoderSampleIndex":
case "setEndOfEncoderSampleIndex:":
case "sampleBuffer":
case "setSampleBuffer:":
case "startOfEncoderSampleIndex":
case "setStartOfEncoderSampleIndex:":
return true;
}
break;
case "MTLComputePassDescriptor":
switch (selectorName) {
case "dispatchType":
case "setDispatchType:":
case "sampleBufferAttachments":
return true;
}
break;
case "MTLComputePassSampleBufferAttachmentDescriptor":
switch (selectorName) {
case "sampleBuffer":
case "setSampleBuffer:":
case "startOfEncoderSampleIndex":
case "setStartOfEncoderSampleIndex:":
case "endOfEncoderSampleIndex":
case "setEndOfEncoderSampleIndex:":
return true;
}
break;
case "MTLCounterSampleBufferDescriptor":
switch (selectorName) {
case "counterSet":
case "setCounterSet:":
case "label":
case "setLabel:":
case "sampleCount":
case "setSampleCount:":
case "storageMode":
case "setStorageMode:":
return true;
}
break;
case "MTLLinkedFunctions":
switch (selectorName) {
case "binaryFunctions":
case "setBinaryFunctions:":
case "functions":
case "setFunctions:":
case "groups":
case "setGroups:":
return true;
}
break;
case "MTLRenderPassSampleBufferAttachmentDescriptor":
switch (selectorName) {
case "endOfFragmentSampleIndex":
case "setEndOfFragmentSampleIndex:":
case "endOfVertexSampleIndex":
case "setEndOfVertexSampleIndex:":
case "sampleBuffer":
case "setSampleBuffer:":
case "startOfFragmentSampleIndex":
case "setStartOfFragmentSampleIndex:":
case "startOfVertexSampleIndex":
case "setStartOfVertexSampleIndex:":
return true;
}
break;
case "MTLIntersectionFunctionTableDescriptor":
switch (selectorName) {
case "functionCount":
case "setFunctionCount:":
return true;
}
break;
case "MTLResourceStatePassDescriptor":
switch (selectorName) {
case "sampleBufferAttachments":
return true;
}
break;
case "MTLResourceStatePassSampleBufferAttachmentDescriptor":
switch (selectorName) {
case "endOfEncoderSampleIndex":
case "setEndOfEncoderSampleIndex:":
case "sampleBuffer":
case "setSampleBuffer:":
case "startOfEncoderSampleIndex":
case "setStartOfEncoderSampleIndex:":
return true;
}
break;
case "MTLVisibleFunctionTableDescriptor":
switch (selectorName) {
case "functionCount":
case "setFunctionCount:":
return true;
}
break;
case "AVPlayerLooper": // This API got introduced in Xcode 8.0 binding but is not currently present nor in Xcode 8.3 or Xcode 9.0 needs research
switch (selectorName) {
case "isLoopingEnabled":
return true;
}
break;
case "NSQueryGenerationToken": // A test was added in monotouch tests to ensure the selector works
switch (selectorName) {
case "encodeWithCoder:":
return true;
}
break;
case "INSpeakableString":
switch (selectorName) {
case "initWithVocabularyIdentifier:spokenPhrase:pronunciationHint:":
case "initWithIdentifier:spokenPhrase:pronunciationHint:":
return true;
}
break;
case "HMCharacteristicEvent":
switch (selectorName) {
case "copyWithZone:":
case "mutableCopyWithZone:":
// Added in Xcode9 (i.e. only 64 bits) so skip 32 bits
return !TestRuntime.CheckXcodeVersion (9,0);
}
break;
case "MPSCnnConvolution":
switch (selectorName) {
case "initWithDevice:convolutionDescriptor:kernelWeights:biasTerms:flags:":
return true;
}
break;
case "MPSCnnFullyConnected":
switch (selectorName) {
case "initWithDevice:convolutionDescriptor:kernelWeights:biasTerms:flags:":
return true;
}
break;
case "MPSImageConversion":
switch (selectorName) {
case "initWithDevice:srcAlpha:destAlpha:backgroundColor:conversionInfo:":
return true;
}
break;
case "MPSImageDilate":
switch (selectorName) {
case "initWithDevice:kernelWidth:kernelHeight:values:":
return true;
}
break;
case "MPSImageGaussianPyramid":
switch (selectorName) {
case "initWithDevice:kernelWidth:kernelHeight:weights:":
return true;
}
break;
case "MPSImagePyramid":
switch (selectorName) {
case "initWithDevice:kernelWidth:kernelHeight:weights:":
return true;
}
break;
case "MPSImageSobel":
switch (selectorName) {
case "initWithDevice:linearGrayColorTransform:":
return true;
}
break;
case "MPSImageThresholdBinary":
switch (selectorName) {
case "initWithDevice:thresholdValue:maximumValue:linearGrayColorTransform:":
return true;
}
break;
case "MPSImageThresholdBinaryInverse":
switch (selectorName) {
case "initWithDevice:thresholdValue:maximumValue:linearGrayColorTransform:":
return true;
}
break;
case "MPSImageThresholdToZero":
switch (selectorName) {
case "initWithDevice:thresholdValue:linearGrayColorTransform:":
return true;
}
break;
case "MPSImageThresholdToZeroInverse":
switch (selectorName) {
case "initWithDevice:thresholdValue:linearGrayColorTransform:":
return true;
}
break;
case "MPSImageThresholdTruncate":
switch (selectorName) {
case "initWithDevice:thresholdValue:linearGrayColorTransform:":
return true;
}
break;
case "MPSCnnBinaryKernel":
switch (selectorName) {
// Xcode 9.4 removed both selectors from MPSCnnBinaryKernel, reported radar https://trello.com/c/7EAM0qk1
// but apple says this was intentional.
case "kernelHeight":
case "kernelWidth":
return true;
}
break;
case "MPSImageLaplacianPyramid":
case "MPSImageLaplacianPyramidSubtract":
case "MPSImageLaplacianPyramidAdd":
switch (selectorName) {
case "initWithDevice:kernelWidth:kernelHeight:weights:":
return true;
}
break;
case "CPMessageListItem":
switch (selectorName) {
case "initWithConversationIdentifier:text:leadingConfiguration:trailingConfiguration:detailText:trailingText:":
case "initWithFullName:phoneOrEmailAddress:leadingConfiguration:trailingConfiguration:detailText:trailingText:":
return true;
}
break;
case "VNFaceLandmarkRegion":
case "VNFaceLandmarks":
case "PHLivePhoto":
switch (selectorName) {
case "copyWithZone:":
case "encodeWithCoder:":
case "requestRevision":
// Conformance added in Xcode 11
if (!TestRuntime.CheckXcodeVersion (11, 0))
return true;
break;
}
break;
case "MPSNNNeuronDescriptor":
case "MLDictionaryConstraint":
case "MLFeatureDescription":
case "MLImageConstraint":
case "MLImageSize":
case "MLImageSizeConstraint":
case "MLModelConfiguration":
case "MLModelDescription":
case "MLMultiArrayConstraint":
case "MLMultiArrayShapeConstraint":
case "MLSequenceConstraint":
switch (selectorName) {
case "encodeWithCoder:":
// Conformance added in Xcode 11
if (!TestRuntime.CheckXcodeVersion (11, 0))
return true;
break;
}
break;
#if __MACOS__ || __MACCATALYST__ || __WATCHOS__
case "MLDictionaryFeatureProvider":
case "MLMultiArray":
case "MLFeatureValue":
case "MLSequence":
switch (selectorName) {
case "encodeWithCoder:":
if (!TestRuntime.CheckXcodeVersion (12, TestRuntime.MinorXcode12APIMismatch))
return true;
break;
}
break;
#endif
case "BGTaskScheduler":
switch (selectorName) {
case "sharedScheduler":
return true;
}
break;
#if !__MACOS__
case "ARSkeletonDefinition":
switch (selectorName) {
case "indexForJointName:":
case "defaultBody2DSkeletonDefinition":
case "defaultBody3DSkeletonDefinition":
// This selector does not exist in the simulator
if (Runtime.Arch == Arch.SIMULATOR)
return true;
break;
}
break;
#endif
case "INParameter":
switch (selectorName) {
case "copyWithZone:":
if (!TestRuntime.CheckXcodeVersion (10, 0))
return true;
break;
}
break;
case "MTLCommandBufferDescriptor":
switch (selectorName) {
case "errorOptions":
case "setErrorOptions:":
case "retainedReferences":
case "setRetainedReferences:":
// iOS 15 sim (and macOS 12) fails, API added in 14.0
if (TestRuntime.CheckXcodeVersion (13, 0))
return true;
break;
}
break;
case "NSTask":
// category, NSTask won't respond -> @interface NSTask (NSTaskConveniences)
if (selectorName == "waitUntilExit")
return true;
break;
case "MPSImageDescriptor":
switch (selectorName) {
case "copyWithZone:":
if (!TestRuntime.CheckXcodeVersion (10, 0))
return true;
break;
}
break;
}
// old binding mistake
return (selectorName == "initWithCoder:");
}
protected virtual bool CheckResponse (bool value, Type actualType, MethodBase method, ref string name)
{
if (value)
return true;
var mname = method.Name;
// properties getter and setter will be methods in the _Extensions type
if (method.IsSpecialName)
mname = mname.Replace ("get_", "Get").Replace ("set_", "Set");
// it's possible that the selector was inlined for an OPTIONAL protocol member
// we do not want those reported (too many false positives) and we have other tests to find such mistakes
foreach (var intf in actualType.GetInterfaces ()) {
if (intf.GetCustomAttributes<ProtocolAttribute> () == null)
continue;
var ext = Type.GetType (intf.Namespace + "." + intf.Name.Remove (0, 1) + "_Extensions, " + intf.Assembly.FullName);
if (ext == null)
continue;
foreach (var m in ext.GetMethods ()) {
if (mname != m.Name)
continue;
var parameters = method.GetParameters ();
var ext_params = m.GetParameters ();
// first parameters is `this XXX This`
if (parameters.Length == ext_params.Length - 1) {
bool match = true;
for (int i = 1; i < ext_params.Length; i++) {
match |= (parameters [i - 1].ParameterType == ext_params [i].ParameterType);
}
if (match)
return true;
}
}
}
name = actualType.FullName + " : " + name;
return false;
}
static IntPtr responds_handle = Selector.GetHandle ("instancesRespondToSelector:");
[Test]
public void Protocols ()
{
Errors = 0;
int n = 0;
foreach (Type t in Assembly.GetTypes ()) {
if (t.IsNested || !NSObjectType.IsAssignableFrom (t))
continue;
foreach (object ca in t.GetCustomAttributes (false)) {
if (ca is ProtocolAttribute) {
foreach (var c in t.GetConstructors (BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)) {
ProcessProtocolMember (t, c, ref n);
}
foreach (var m in t.GetMethods (BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)) {
ProcessProtocolMember (t, m, ref n);
}
}
}
}
Assert.AreEqual (0, Errors, "{0} errors found in {1} protocol selectors validated", Errors, n);
}
void ProcessProtocolMember (Type t, MethodBase m, ref int n)
{
if (SkipDueToAttribute (m))
return;
foreach (object ca in m.GetCustomAttributes (true)) {
ExportAttribute export = (ca as ExportAttribute);
if (export == null)
continue;
string name = export.Selector;
if (Skip (t, name))
continue;
CheckInit (t, m, name);
n++;
}
}
protected virtual IntPtr GetClassForType (Type type)
{
var fi = type.GetField ("class_ptr", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
if (fi == null)
return IntPtr.Zero; // e.g. *Delegate
return (IntPtr) fi.GetValue (null);
}
[Test]
public void InstanceMethods ()
{
Errors = 0;
ErrorData.Clear ();
int n = 0;
foreach (Type t in Assembly.GetTypes ()) {
if (t.IsNested || !NSObjectType.IsAssignableFrom (t))
continue;
if (Skip (t) || SkipDueToAttribute (t))
continue;
IntPtr class_ptr = GetClassForType (t);
if (class_ptr == IntPtr.Zero)
continue;
foreach (var c in t.GetConstructors (BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)) {
Process (class_ptr, t, c, ref n);
}
foreach (var m in t.GetMethods (BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)) {
Process (class_ptr, t, m, ref n);
}
}
Assert.AreEqual (0, Errors, "{0} errors found in {1} instance selector validated{2}", Errors, n, Errors == 0 ? string.Empty : ":\n" + ErrorData.ToString () + "\n");
}
void Process (IntPtr class_ptr, Type t, MethodBase m, ref int n)
{
if (m.DeclaringType != t || SkipDueToAttribute (m))
return;
foreach (object ca in m.GetCustomAttributes (true)) {
ExportAttribute export = (ca as ExportAttribute);
if (export == null)
continue;
string name = export.Selector;
if (Skip (t, name))
continue;
CheckInit (t, m, name);
bool result = bool_objc_msgSend_IntPtr (class_ptr, responds_handle, Selector.GetHandle (name));
bool response = CheckResponse (result, t, m, ref name);
if (!response)
ReportError ("Selector not found for {0} in {1} on {2}", name, m, t.FullName);
n++;
}
}
void CheckInit (Type t, MethodBase m, string name)
{
if (SkipInit (name, m))
return;
bool init = IsInitLike (name);
if (m is ConstructorInfo) {
if (!init)
ReportError ("Selector {0} used on a constructor (not a method) on {1}", name, t.FullName);
} else {
if (init)
ReportError ("Selector {0} used on a method (not a constructor) on {1}", name, t.FullName);
}
}
bool IsInitLike (string selector)
{
if (!selector.StartsWith ("init", StringComparison.OrdinalIgnoreCase))
return false;
return selector.Length < 5 || Char.IsUpper (selector [4]);
}
protected virtual bool SkipInit (string selector, MethodBase m)
{
switch (selector) {
// NSAttributedString
case "initWithHTML:documentAttributes:":
case "initWithRTF:documentAttributes:":
case "initWithRTFD:documentAttributes:":
case "initWithURL:options:documentAttributes:error:":
case "initWithFileURL:options:documentAttributes:error:":
// AVAudioRecorder
case "initWithURL:settings:error:":
case "initWithURL:format:error:":
// NSUrlProtectionSpace
case "initWithHost:port:protocol:realm:authenticationMethod:":
case "initWithProxyHost:port:type:realm:authenticationMethod:":
// NSUserDefaults
case "initWithSuiteName:":
case "initWithUser:":
// GKScore
case "initWithCategory:":
case "initWithLeaderboardIdentifier:":
// MCSession
case "initWithPeer:securityIdentity:encryptionPreference:":
// INSetProfileInCarIntent and INSaveProfileInCarIntent
case "initWithProfileNumber:profileName:defaultProfile:":
case "initWithProfileNumber:profileLabel:defaultProfile:":
case "initWithProfileNumber:profileName:":
case "initWithProfileNumber:profileLabel:":
// MPSCnnBinaryConvolutionNode and MPSCnnBinaryFullyConnectedNode
case "initWithSource:weights:outputBiasTerms:outputScaleTerms:inputBiasTerms:inputScaleTerms:type:flags:":
// UISegmentedControl
case "initWithItems:":
// CLBeaconRegion
case "initWithUUID:identifier:":
case "initWithUUID:major:identifier:":
case "initWithUUID:major:minor:identifier:":
// Intents
case "initWithPersonHandle:nameComponents:displayName:image:contactIdentifier:customIdentifier:isMe:suggestionType:":
case "initWithPersonHandle:nameComponents:displayName:image:contactIdentifier:customIdentifier:isContactSuggestion:suggestionType:":
// NEHotspotConfiguration
case "initWithSSID:":
case "initWithSSID:passphrase:isWEP:":
case "initWithSSIDPrefix:":
case "initWithSSIDPrefix:passphrase:isWEP:":
// MapKit
case "initWithMaxCenterCoordinateDistance:":
case "initWithMinCenterCoordinateDistance:":
case "initExcludingCategories:":
case "initIncludingCategories:":
// Vision
case "initWithCenter:diameter:":
case "initWithCenter:radius:":
case "initWithR:theta:":
// NSImage
case "initWithDataIgnoringOrientation:":
var mi = m as MethodInfo;
return mi != null && !mi.IsPublic && mi.ReturnType.Name == "IntPtr";
// NSAppleEventDescriptor
case "initListDescriptor":
case "initRecordDescriptor":
return true;
default:
return false;
}
}
protected virtual void Dispose (NSObject obj, Type type)
{
obj.Dispose ();
}
// funny, this is how I envisioned the instance version... before hitting run :|
protected virtual bool CheckStaticResponse (bool value, Type actualType, Type declaredType, ref string name)
{
if (value)
return true;
name = actualType.FullName + " : " + name;
return false;
}
[Test]
public void StaticMethods ()
{
Errors = 0;
ErrorData.Clear ();
int n = 0;
IntPtr responds_handle = Selector.GetHandle ("respondsToSelector:");
foreach (Type t in Assembly.GetTypes ()) {
if (t.IsNested || !NSObjectType.IsAssignableFrom (t))
continue;
if (Skip (t) || SkipDueToAttribute (t))
continue;
FieldInfo fi = t.GetField ("class_ptr", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
if (fi == null)
continue; // e.g. *Delegate
IntPtr class_ptr = (IntPtr) fi.GetValue (null);
foreach (var m in t.GetMethods (BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)) {
if (SkipDueToAttribute (m))
continue;
foreach (object ca in m.GetCustomAttributes (true)) {
if (ca is ExportAttribute) {
string name = (ca as ExportAttribute).Selector;
if (Skip (t, name))
continue;
bool result = bool_objc_msgSend_IntPtr (class_ptr, responds_handle, Selector.GetHandle (name));
bool response = CheckStaticResponse (result, t, m.DeclaringType, ref name);
if (!response)
ReportError (name);
n++;
}
}
}
}
Assert.AreEqual (0, Errors, "{0} errors found in {1} static selector validated{2}", Errors, n, Errors == 0 ? string.Empty : ":\n" + ErrorData.ToString () + "\n");
}
}
}