2021-09-24 14:20:03 +03:00
#nullable enable
2021-08-13 10:34:19 +03:00
using System.Runtime.InteropServices ;
2021-07-23 19:33:15 +03:00
using Mono.Cecil ;
using Xamarin.Tests ;
namespace Xamarin.Tests {
[TestFixture]
public abstract class TestBaseClass {
protected Dictionary < string , string > verbosity = new Dictionary < string , string > {
2021-09-15 19:10:30 +03:00
{ "_BundlerVerbosity" , "1" } ,
2021-07-23 19:33:15 +03:00
} ;
2021-09-20 14:41:20 +03:00
protected Dictionary < string , string > GetDefaultProperties ( string? runtimeIdentifiers = null )
2021-08-11 11:01:16 +03:00
{
var rv = new Dictionary < string , string > ( verbosity ) ;
if ( ! string . IsNullOrEmpty ( runtimeIdentifiers ) )
SetRuntimeIdentifiers ( rv , runtimeIdentifiers ) ;
return rv ;
}
2021-08-10 08:39:00 +03:00
protected void SetRuntimeIdentifiers ( Dictionary < string , string > properties , string runtimeIdentifiers )
{
var multiRid = runtimeIdentifiers . IndexOf ( ';' ) > = 0 ? "RuntimeIdentifiers" : "RuntimeIdentifier" ;
properties [ multiRid ] = runtimeIdentifiers ;
}
2022-06-30 14:20:04 +03:00
protected string GetProjectPath ( string project , string runtimeIdentifiers , ApplePlatform platform , out string appPath , string? subdir = null , string configuration = "Debug" , string? netVersion = null )
2021-08-10 08:39:00 +03:00
{
2022-06-30 14:20:04 +03:00
return GetProjectPath ( project , null , runtimeIdentifiers , platform , out appPath , configuration , netVersion ) ;
2021-08-10 08:39:00 +03:00
}
2022-06-30 14:20:04 +03:00
protected string GetProjectPath ( string project , string? subdir , string runtimeIdentifiers , ApplePlatform platform , out string appPath , string configuration = "Debug" , string? netVersion = null )
2021-08-10 08:39:00 +03:00
{
var rv = GetProjectPath ( project , subdir , platform ) ;
2022-09-07 12:38:51 +03:00
appPath = Path . Combine ( GetOutputPath ( project , subdir , runtimeIdentifiers , platform , configuration , netVersion ) , project + ".app" ) ;
2021-08-10 08:39:00 +03:00
return rv ;
}
2022-02-01 19:22:53 +03:00
protected string GetAppPath ( string projectPath , ApplePlatform platform , string runtimeIdentifiers , string configuration = "Debug" )
{
var appPathRuntimeIdentifier = runtimeIdentifiers . IndexOf ( ';' ) > = 0 ? "" : runtimeIdentifiers ;
return Path . Combine ( Path . GetDirectoryName ( projectPath ) ! , "bin" , configuration , platform . ToFramework ( ) , appPathRuntimeIdentifier , Path . GetFileNameWithoutExtension ( projectPath ) + ".app" ) ;
}
2022-09-07 12:38:51 +03:00
protected string GetOutputPath ( string project , string? subdir , string runtimeIdentifiers , ApplePlatform platform , string configuration = "Debug" , string? netVersion )
2022-09-07 11:39:05 +03:00
{
var rv = GetProjectPath ( project , subdir , platform ) ;
if ( string . IsNullOrEmpty ( runtimeIdentifiers ) )
runtimeIdentifiers = GetDefaultRuntimeIdentifier ( platform ) ;
var appPathRuntimeIdentifier = runtimeIdentifiers . IndexOf ( ';' ) > = 0 ? "" : runtimeIdentifiers ;
2022-09-07 12:38:51 +03:00
return Path . Combine ( Path . GetDirectoryName ( rv ) ! , "bin" , configuration , platform . ToFramework ( netVersion ) , appPathRuntimeIdentifier ) ;
2022-09-07 11:39:05 +03:00
}
2021-09-27 16:55:30 +03:00
protected string GetDefaultRuntimeIdentifier ( ApplePlatform platform )
{
switch ( platform ) {
case ApplePlatform . iOS :
return "iossimulator-x64" ;
case ApplePlatform . TVOS :
return "tvossimulator-x64" ;
case ApplePlatform . MacOSX :
return "osx-x64" ;
case ApplePlatform . MacCatalyst :
return "maccatalyst-x64" ;
default :
throw new ArgumentOutOfRangeException ( $"Unknown platform: {platform}" ) ;
}
}
2021-09-30 09:21:43 +03:00
protected string GetProjectPath ( string project , string? subdir = null , ApplePlatform ? platform = null )
2021-07-23 19:33:15 +03:00
{
var project_dir = Path . Combine ( Configuration . SourceRoot , "tests" , "dotnet" , project ) ;
if ( ! string . IsNullOrEmpty ( subdir ) )
project_dir = Path . Combine ( project_dir , subdir ) ;
2021-09-27 16:55:30 +03:00
var project_path = Path . Combine ( project_dir , project + ".csproj" ) ;
if ( File . Exists ( project_path ) )
return project_path ;
2021-07-23 19:33:15 +03:00
if ( platform . HasValue )
project_dir = Path . Combine ( project_dir , platform . Value . AsString ( ) ) ;
2021-09-27 16:55:30 +03:00
project_path = Path . Combine ( project_dir , project + ".csproj" ) ;
2021-07-23 19:33:15 +03:00
if ( ! File . Exists ( project_path ) )
project_path = Path . ChangeExtension ( project_path , "sln" ) ;
if ( ! File . Exists ( project_path ) )
throw new FileNotFoundException ( $"Could not find the project or solution {project} - {project_path} does not exist." ) ;
return project_path ;
}
2022-04-01 14:48:09 +03:00
protected string GetPlugInsRelativePath ( ApplePlatform platform )
{
switch ( platform ) {
case ApplePlatform . iOS :
case ApplePlatform . TVOS :
return "PlugIns" ;
case ApplePlatform . MacCatalyst :
case ApplePlatform . MacOSX :
return Path . Combine ( "Contents" , "PlugIns" ) ;
default :
throw new ArgumentOutOfRangeException ( $"Unknown platform: {platform}" ) ;
}
}
2021-07-23 19:33:15 +03:00
protected void Clean ( string project_path )
{
2021-09-30 09:21:43 +03:00
var dirs = Directory . GetDirectories ( Path . GetDirectoryName ( project_path ) ! , "*" , SearchOption . AllDirectories ) ;
2021-07-23 19:33:15 +03:00
dirs = dirs . OrderBy ( v = > v . Length ) . Reverse ( ) . ToArray ( ) ; // If we have nested directories, make sure to delete the nested one first
foreach ( var dir in dirs ) {
var name = Path . GetFileName ( dir ) ;
if ( name ! = "bin" & & name ! = "obj" )
continue ;
Directory . Delete ( dir , true ) ;
}
}
2021-08-12 16:03:11 +03:00
2022-01-10 17:32:05 +03:00
protected static bool CanExecute ( ApplePlatform platform , string runtimeIdentifiers )
2021-08-13 10:34:19 +03:00
{
switch ( platform ) {
case ApplePlatform . iOS :
case ApplePlatform . TVOS :
case ApplePlatform . WatchOS :
return false ;
case ApplePlatform . MacOSX :
case ApplePlatform . MacCatalyst :
// If we're targetting x64, then we can execute everywhere
if ( runtimeIdentifiers . Contains ( "-x64" , StringComparison . Ordinal ) )
return true ;
// If we're not targeting x64, and we're executing on x64, then we're out of luck
if ( RuntimeInformation . ProcessArchitecture = = Architecture . X64 )
return false ;
// Otherwise we can still execute.
return true ;
default :
throw new ArgumentOutOfRangeException ( $"Unknown platform: {platform}" ) ;
}
}
2021-08-12 13:30:57 +03:00
protected string GetRelativeResourcesDirectory ( ApplePlatform platform )
{
switch ( platform ) {
case ApplePlatform . iOS :
case ApplePlatform . TVOS :
case ApplePlatform . WatchOS :
return "Resources" ;
case ApplePlatform . MacOSX :
case ApplePlatform . MacCatalyst :
return Path . Combine ( "Contents" , "Resources" ) ;
default :
throw new ArgumentOutOfRangeException ( $"Unknown platform: {platform}" ) ;
}
}
protected string GetRelativeAssemblyDirectory ( ApplePlatform platform )
{
switch ( platform ) {
case ApplePlatform . iOS :
case ApplePlatform . TVOS :
case ApplePlatform . WatchOS :
return string . Empty ;
case ApplePlatform . MacOSX :
case ApplePlatform . MacCatalyst :
return Path . Combine ( "Contents" , "MonoBundle" ) ;
default :
throw new NotImplementedException ( $"Unknown platform: {platform}" ) ;
}
}
2021-08-12 16:03:11 +03:00
protected string GetInfoPListPath ( ApplePlatform platform , string app_directory )
{
switch ( platform ) {
case ApplePlatform . iOS :
case ApplePlatform . TVOS :
case ApplePlatform . WatchOS :
return Path . Combine ( app_directory , "Info.plist" ) ;
case ApplePlatform . MacOSX :
case ApplePlatform . MacCatalyst :
return Path . Combine ( app_directory , "Contents" , "Info.plist" ) ;
default :
throw new NotImplementedException ( $"Unknown platform: {platform}" ) ;
}
}
2021-10-05 17:43:22 +03:00
protected void AssertBundleAssembliesStripStatus ( string appPath , bool shouldStrip )
{
var assemblies = Directory . GetFiles ( appPath , "*.dll" , SearchOption . AllDirectories ) ;
var assembliesWithOnlyEmptyMethods = new List < String > ( ) ;
foreach ( var assembly in assemblies ) {
ModuleDefinition definition = ModuleDefinition . ReadModule ( assembly , new ReaderParameters { ReadingMode = ReadingMode . Deferred } ) ;
bool onlyHasEmptyMethods = definition . Assembly . MainModule . Types . All ( t = >
t . Methods . Where ( m = > m . HasBody ) . All ( m = > m . Body . Instructions . Count = = 1 ) ) ;
if ( onlyHasEmptyMethods ) {
assembliesWithOnlyEmptyMethods . Add ( assembly ) ;
}
}
// Some assemblies, such as Facades, will be completely empty even when not stripped
Assert . That ( assemblies . Length = = assembliesWithOnlyEmptyMethods . Count , Is . EqualTo ( shouldStrip ) , $"Unexpected stripping status: of {assemblies.Length} assemblies {assembliesWithOnlyEmptyMethods.Count} were empty." ) ;
}
2022-02-11 15:55:22 +03:00
protected void AssertDSymDirectory ( string appPath )
{
var dSYMDirectory = appPath + ".dSYM" ;
Assert . That ( dSYMDirectory , Does . Exist , "dsym directory" ) ;
}
2021-09-17 11:18:09 +03:00
protected string GetNativeExecutable ( ApplePlatform platform , string app_directory )
{
var executableName = Path . GetFileNameWithoutExtension ( app_directory ) ;
switch ( platform ) {
case ApplePlatform . iOS :
case ApplePlatform . TVOS :
case ApplePlatform . WatchOS :
return Path . Combine ( app_directory , executableName ) ;
case ApplePlatform . MacOSX :
case ApplePlatform . MacCatalyst :
return Path . Combine ( app_directory , "Contents" , "MacOS" , executableName ) ;
default :
throw new NotImplementedException ( $"Unknown platform: {platform}" ) ;
}
}
protected string GetResourcesDirectory ( ApplePlatform platform , string app_directory )
{
switch ( platform ) {
case ApplePlatform . iOS :
case ApplePlatform . TVOS :
case ApplePlatform . WatchOS :
return app_directory ;
case ApplePlatform . MacOSX :
case ApplePlatform . MacCatalyst :
return Path . Combine ( app_directory , "Contents" , "Resources" ) ;
default :
throw new NotImplementedException ( $"Unknown platform: {platform}" ) ;
}
}
2021-09-24 14:20:03 +03:00
protected string GenerateProject ( ApplePlatform platform , string name , string runtimeIdentifiers , out string? appPath )
{
var dir = Cache . CreateTemporaryDirectory ( name ) ;
var csproj = Path . Combine ( dir , $"{name}.csproj" ) ;
var sb = new StringBuilder ( ) ;
sb . AppendLine ( $"<?xml version=\" 1.0 \ " encoding=\"utf-8\"?>" ) ;
sb . AppendLine ( $"<Project Sdk=\" Microsoft . NET . Sdk \ ">" ) ;
sb . AppendLine ( $" <PropertyGroup>" ) ;
sb . AppendLine ( $" <TargetFramework>{platform.ToFramework ()}</TargetFramework>" ) ;
sb . AppendLine ( $" <OutputType>Exe</OutputType>" ) ;
sb . AppendLine ( $" <ApplicationTitle>{name}</ApplicationTitle>" ) ;
sb . AppendLine ( $" <ApplicationId>com.xamarin.testproject.{name}</ApplicationId>" ) ;
sb . AppendLine ( $" </PropertyGroup>" ) ;
sb . AppendLine ( $"</Project>" ) ;
File . WriteAllText ( csproj , sb . ToString ( ) ) ;
var appPathRuntimeIdentifier = runtimeIdentifiers . IndexOf ( ';' ) > = 0 ? "" : runtimeIdentifiers ;
appPath = Path . Combine ( dir , "bin" , "Debug" , platform . ToFramework ( ) , appPathRuntimeIdentifier , name + ".app" ) ;
return csproj ;
}
2021-08-12 13:30:57 +03:00
protected void ExecuteWithMagicWordAndAssert ( ApplePlatform platform , string runtimeIdentifiers , string executable )
{
if ( ! CanExecute ( platform , runtimeIdentifiers ) )
return ;
ExecuteWithMagicWordAndAssert ( executable ) ;
}
protected void ExecuteWithMagicWordAndAssert ( string executable )
{
if ( ! File . Exists ( executable ) )
throw new FileNotFoundException ( $"The executable '{executable}' does not exists." ) ;
var magicWord = Guid . NewGuid ( ) . ToString ( ) ;
var env = new Dictionary < string , string? > {
{ "MAGIC_WORD" , magicWord } ,
{ "DYLD_FALLBACK_LIBRARY_PATH" , null } , // VSMac might set this, which may cause tests to crash.
} ;
var output = new StringBuilder ( ) ;
var rv = Execution . RunWithStringBuildersAsync ( executable , Array . Empty < string > ( ) , environment : env , standardOutput : output , standardError : output , timeout : TimeSpan . FromSeconds ( 15 ) ) . Result ;
Assert . That ( output . ToString ( ) , Does . Contain ( magicWord ) , "Contains magic word" ) ;
Assert . AreEqual ( 0 , rv . ExitCode , "ExitCode" ) ;
}
2022-02-09 18:46:04 +03:00
public static StringBuilder AssertExecute ( string executable , params string [ ] arguments )
{
return AssertExecute ( executable , arguments , out _ ) ;
}
public static StringBuilder AssertExecute ( string executable , string [ ] arguments , out StringBuilder output )
{
var rv = ExecutionHelper . Execute ( executable , arguments , out output ) ;
if ( rv ! = 0 ) {
Console . WriteLine ( $"'{executable} {StringUtils.FormatArguments (arguments)}' exited with exit code {rv}:" ) ;
Console . WriteLine ( "\t" + output . ToString ( ) . Replace ( "\n" , "\n\t" ) . TrimEnd ( new char [ ] { '\n' , '\t' } ) ) ;
}
Assert . AreEqual ( 0 , rv , $"Unable to execute '{executable} {StringUtils.FormatArguments (arguments)}': exit code {rv}" ) ;
return output ;
}
2022-03-11 09:30:01 +03:00
protected void ExecuteProjectWithMagicWordAndAssert ( string csproj , ApplePlatform platform , string? runtimeIdentifiers = null )
{
if ( runtimeIdentifiers is null )
runtimeIdentifiers = GetDefaultRuntimeIdentifier ( platform ) ;
var appPath = GetAppPath ( csproj , platform , runtimeIdentifiers ) ;
var appExecutable = GetNativeExecutable ( platform , appPath ) ;
ExecuteWithMagicWordAndAssert ( appExecutable ) ;
}
2022-09-07 11:39:05 +03:00
protected bool IsRuntimeIdentifierSigned ( string runtimeIdentifiers )
{
foreach ( var rid in runtimeIdentifiers . Split ( ';' , StringSplitOptions . RemoveEmptyEntries ) ) {
if ( rid . StartsWith ( "ios-" , StringComparison . OrdinalIgnoreCase ) )
return true ;
if ( rid . StartsWith ( "tvos-" , StringComparison . OrdinalIgnoreCase ) )
return true ;
}
return false ;
}
2021-07-23 19:33:15 +03:00
}
}