2017-07-10 13:46:31 +03:00
using System ;
using System.Collections.Generic ;
using System.IO ;
2017-11-20 16:55:16 +03:00
using System.Linq ;
2017-07-10 13:46:31 +03:00
using System.Text ;
using NUnit.Framework ;
2017-11-20 16:55:16 +03:00
using Mono.Cecil ;
using Mono.Cecil.Cil ;
2017-07-10 13:46:31 +03:00
using Xamarin.Utils ;
namespace Xamarin.Tests
{
2017-11-20 16:55:16 +03:00
public enum Profile
{
2017-07-10 13:46:31 +03:00
None ,
2017-11-20 16:55:16 +03:00
iOS ,
tvOS ,
2017-07-10 13:46:31 +03:00
watchOS ,
macClassic ,
macModern ,
macFull ,
macSystem ,
}
2017-11-20 16:55:16 +03:00
2017-07-10 13:46:31 +03:00
class BGenTool : Tool
{
2017-11-20 16:55:16 +03:00
AssemblyDefinition assembly ;
2017-07-10 13:46:31 +03:00
public Profile Profile ;
2017-11-13 19:54:21 +03:00
public bool ProcessEnums ;
2017-07-10 13:46:31 +03:00
public List < string > ApiDefinitions = new List < string > ( ) ;
2017-11-20 16:55:16 +03:00
public List < string > Sources = new List < string > ( ) ;
public string [ ] Defines ;
2017-07-10 13:46:31 +03:00
public string TmpDirectory ;
2017-11-16 18:02:34 +03:00
public string ResponseFile ;
2017-11-20 16:55:16 +03:00
public string WarnAsError ; // Set to empty string to pass /warnaserror, set to non-empty string to pass /warnaserror:<nonemptystring>
public string NoWarn ; // Set to empty string to pass /nowarn, set to non-empty string to pass /nowarn:<nonemptystring>
2017-07-10 13:46:31 +03:00
protected override string ToolPath { get { return Configuration . BGenPath ; } }
protected override string MessagePrefix { get { return "BI" ; } }
protected override string MessageToolName { get { return "bgen" ; } }
2017-11-20 16:55:16 +03:00
public BGenTool ( )
{
EnvironmentVariables = new Dictionary < string , string > {
{ "MD_MTOUCH_SDK_ROOT" , Configuration . SdkRootXI } ,
{ "XamarinMacFrameworkRoot" , Configuration . SdkRootXM } ,
} ;
}
public void AddTestApiDefinition ( string filename )
{
ApiDefinitions . Add ( Path . Combine ( Configuration . SourceRoot , "tests" , "generator" , filename ) ) ;
}
public AssemblyDefinition ApiAssembly {
get {
LoadAssembly ( ) ;
return assembly ;
}
}
2017-07-10 13:46:31 +03:00
string BuildArguments ( )
{
var sb = new StringBuilder ( ) ;
var targetFramework = ( string ) null ;
switch ( Profile ) {
case Profile . None :
break ;
case Profile . iOS :
targetFramework = "Xamarin.iOS,v1.0" ;
break ;
case Profile . tvOS :
targetFramework = "Xamarin.TVOS,v1.0" ;
break ;
case Profile . watchOS :
targetFramework = "Xamarin.WatchOS,v1.0" ;
break ;
case Profile . macClassic :
targetFramework = "XamMac,v1.0" ;
break ;
case Profile . macFull :
targetFramework = "Xamarin.Mac,Version=v4.5,Profile=Full" ;
break ;
case Profile . macModern :
targetFramework = "Xamarin.Mac,Version=v2.0,Profile=Mobile" ;
break ;
case Profile . macSystem :
targetFramework = "Xamarin.Mac,Version=v4.5,Profile=System" ;
break ;
default :
throw new NotImplementedException ( $"Profile: {Profile}" ) ;
}
if ( ! string . IsNullOrEmpty ( targetFramework ) )
sb . Append ( " --target-framework=" ) . Append ( targetFramework ) ;
2017-11-20 16:55:16 +03:00
2017-07-10 13:46:31 +03:00
foreach ( var ad in ApiDefinitions )
sb . Append ( " --api=" ) . Append ( StringUtils . Quote ( ad ) ) ;
2017-11-20 16:55:16 +03:00
foreach ( var s in Sources )
sb . Append ( " -s=" ) . Append ( StringUtils . Quote ( s ) ) ;
2017-07-10 13:46:31 +03:00
if ( ! string . IsNullOrEmpty ( TmpDirectory ) )
sb . Append ( " --tmpdir=" ) . Append ( StringUtils . Quote ( TmpDirectory ) ) ;
2017-11-16 18:02:34 +03:00
if ( ! string . IsNullOrEmpty ( ResponseFile ) )
sb . Append ( " @" ) . Append ( StringUtils . Quote ( ResponseFile ) ) ;
2017-11-13 19:54:21 +03:00
if ( ProcessEnums )
sb . Append ( " --process-enums" ) ;
2017-11-20 16:55:16 +03:00
if ( Defines ! = null ) {
foreach ( var d in Defines )
sb . Append ( " -d " ) . Append ( StringUtils . Quote ( d ) ) ;
}
if ( WarnAsError ! = null ) {
sb . Append ( " --warnaserror" ) ;
if ( WarnAsError . Length > 0 )
sb . Append ( ":" ) . Append ( StringUtils . Quote ( WarnAsError ) ) ;
}
if ( NoWarn ! = null ) {
sb . Append ( " --nowarn" ) ;
if ( NoWarn . Length > 0 )
sb . Append ( ":" ) . Append ( StringUtils . Quote ( NoWarn ) ) ;
}
2017-07-10 13:46:31 +03:00
return sb . ToString ( ) ;
}
public void AssertExecute ( string message )
{
2017-11-20 16:55:16 +03:00
Assert . AreEqual ( 0 , Execute ( BuildArguments ( ) , always_show_output : true ) , message ) ;
2017-07-10 13:46:31 +03:00
}
public void AssertExecuteError ( string message )
{
Assert . AreNotEqual ( 0 , Execute ( BuildArguments ( ) ) , message ) ;
}
2017-11-20 16:55:16 +03:00
public void AssertApiCallsMethod ( string caller_namespace , string caller_type , string caller_method , string @called_method , string message )
{
var type = ApiAssembly . MainModule . GetType ( caller_namespace , caller_type ) ;
var method = type . Methods . First ( ( v ) = > v . Name = = caller_method ) ;
AssertApiCallsMethod ( method , called_method , message ) ;
}
public void AssertApiCallsMethod ( MethodReference method , string called_method , string message )
{
var instructions = method . Resolve ( ) . Body . Instructions ;
foreach ( var ins in instructions ) {
if ( ins . OpCode . FlowControl ! = FlowControl . Call )
continue ;
var mr = ins . Operand as MethodReference ;
if ( mr = = null )
continue ;
if ( mr . Name = = called_method )
return ;
}
Assert . Fail ( $"Could not find any instructions calling {called_method} in {method.FullName}: {message}\n\t{string.Join (" \ n \ t ", instructions)}" ) ;
}
public void AssertApiLoadsField ( string caller_type , string caller_method , string declaring_type , string field , string message )
{
var type = ApiAssembly . MainModule . GetTypes ( ) . First ( ( v ) = > v . FullName = = caller_type ) ;
var method = type . Methods . First ( ( v ) = > v . Name = = caller_method ) ;
AssertApiLoadsField ( method , declaring_type , field , message ) ;
}
public void AssertApiLoadsField ( MethodReference method , string declaring_type , string field , string message )
{
var instructions = method . Resolve ( ) . Body . Instructions ;
foreach ( var ins in instructions ) {
if ( ins . OpCode . Code ! = Code . Ldsfld & & ins . OpCode . Code ! = Code . Ldfld )
continue ;
var fr = ins . Operand as FieldReference ;
if ( fr = = null )
continue ;
if ( fr . DeclaringType . FullName ! = declaring_type )
continue ;
if ( fr . Name = = field )
return ;
}
Assert . Fail ( $"Could not find any instructions loading the field {declaring_type}.{field} in {method.FullName}: {message}\n\t{string.Join (" \ n \ t ", instructions)}" ) ;
}
public void AssertPublicTypeCount ( int count , string message = null )
{
LoadAssembly ( ) ;
var actual = assembly . MainModule . Types . Where ( ( v ) = > v . IsPublic | | v . IsNestedPublic ) ;
if ( actual . Count ( ) ! = count )
Assert . Fail ( $"Expected {count} public type(s), found {actual} public type(s). {message}\n\t{string.Join (" \ n \ t ", actual.Select ((v) => v.FullName).ToArray ())}" ) ;
}
public void AssertPublicMethodCount ( string typename , int count , string message = null )
{
LoadAssembly ( ) ;
var t = assembly . MainModule . Types . FirstOrDefault ( ( v ) = > v . FullName = = typename ) ;
var actual = t . Methods . Count ( ( v ) = > {
if ( v . IsPrivate | | v . IsFamily | | v . IsFamilyAndAssembly )
return false ;
return true ;
} ) ;
if ( actual ! = count )
Assert . Fail ( $"Expected {count} publicly accessible method(s) in {typename}, found {actual} publicly accessible method(s). {message}" ) ;
}
public void AssertType ( string fullname , TypeAttributes ? attributes = null , string message = null )
{
LoadAssembly ( ) ;
var allTypes = assembly . MainModule . GetTypes ( ) . ToArray ( ) ;
var t = allTypes . FirstOrDefault ( ( v ) = > v . FullName = = fullname ) ;
if ( t = = null )
Assert . Fail ( $"No type named '{fullname}' in the generated assembly. {message}\nList of types:\n\t{string.Join (" \ n \ t ", allTypes.Select ((v) => v.FullName))}" ) ;
if ( attributes ! = null )
Assert . AreEqual ( attributes . Value , t . Attributes , $"Incorrect attributes for type {fullname}." ) ;
}
public void AssertMethod ( string typename , string method , string returnType = null , params string [ ] parameterTypes )
{
AssertMethod ( typename , method , null , returnType , parameterTypes ) ;
}
public void AssertMethod ( string typename , string method , MethodAttributes ? attributes = null , string returnType = null , params string [ ] parameterTypes )
{
LoadAssembly ( ) ;
var t = assembly . MainModule . Types . First ( ( v ) = > v . FullName = = typename ) ;
var m = t . Methods . FirstOrDefault ( ( v ) = > {
if ( v . Name ! = method )
return false ;
if ( v . Parameters . Count ! = parameterTypes . Length )
return false ;
for ( int i = 0 ; i < v . Parameters . Count ; i + + )
if ( v . Parameters [ i ] . ParameterType . FullName ! = parameterTypes [ i ] )
return false ;
return true ;
} ) ;
if ( m = = null )
Assert . Fail ( $"No method '{method}' with signature '{string.Join (" ' , ' ", parameterTypes)}' was found." ) ;
if ( attributes . HasValue )
Assert . AreEqual ( attributes . Value , m . Attributes , "Attributes for {0}" , m . FullName ) ;
}
void LoadAssembly ( )
{
if ( assembly = = null )
2017-12-01 17:18:20 +03:00
assembly = AssemblyDefinition . ReadAssembly ( Path . Combine ( TmpDirectory , Path . GetFileNameWithoutExtension ( ApiDefinitions [ 0 ] ) . Replace ( '-' , '_' ) + ".dll" ) ) ;
2017-11-20 16:55:16 +03:00
}
2017-07-10 13:46:31 +03:00
void EnsureTempDir ( )
{
if ( TmpDirectory = = null )
TmpDirectory = Cache . CreateTemporaryDirectory ( ) ;
}
2017-11-20 16:55:16 +03:00
public void CreateTemporaryBinding ( params string [ ] api_definition )
2017-07-10 13:46:31 +03:00
{
EnsureTempDir ( ) ;
2017-11-20 16:55:16 +03:00
for ( int i = 0 ; i < api_definition . Length ; i + + ) {
var api = Path . Combine ( TmpDirectory , $"api{i}.cs" ) ;
File . WriteAllText ( api , api_definition [ i ] ) ;
ApiDefinitions . Add ( api ) ;
}
2017-07-10 13:46:31 +03:00
WorkingDirectory = TmpDirectory ;
}
2017-11-20 16:55:16 +03:00
public static string [ ] GetDefaultDefines ( Profile profile )
{
switch ( profile ) {
case Profile . macFull :
case Profile . macModern :
case Profile . macSystem :
return new string [ ] { "MONOMAC" , "XAMCORE_2_0" } ;
case Profile . macClassic :
return new string [ ] { "MONOMAC" } ;
case Profile . iOS :
return new string [ ] { "IOS" , "XAMCORE_2_0" } ;
default :
throw new NotImplementedException ( profile . ToString ( ) ) ;
}
}
2017-07-10 13:46:31 +03:00
}
}