2016-05-26 16:06:52 +03:00
using System ;
using System.Collections.Generic ;
2016-06-17 18:21:18 +03:00
using System.Diagnostics ;
2016-05-26 16:06:52 +03:00
using System.IO ;
using System.Linq ;
using System.Text ;
2016-06-17 18:21:18 +03:00
using System.Threading ;
using System.Threading.Tasks ;
2016-05-26 16:06:52 +03:00
using System.Xml ;
2017-06-06 23:32:25 +03:00
using Xamarin.Utils ;
2016-05-26 16:06:52 +03:00
namespace xharness
{
public enum HarnessAction
{
None ,
Configure ,
Run ,
Install ,
2017-01-02 10:58:46 +03:00
Uninstall ,
2016-06-06 13:48:53 +03:00
Jenkins ,
2016-05-26 16:06:52 +03:00
}
2017-02-01 19:05:08 +03:00
public class BCLTest
{
public string Name { get ; private set ; }
public BCLTest ( string name )
{
Name = name ;
}
public virtual void Convert ( Harness harness ) {
var target = new BCLTarget ( ) {
Harness = harness ,
MonoPath = harness . MONO_PATH ,
TestName = Name ,
WatchMonoPath = harness . WATCH_MONO_PATH
} ;
target . Convert ( ) ;
}
}
public class MacBCLTest : BCLTest
{
public MacBCLTest ( string name ) : base ( name )
{
}
public override void Convert ( Harness harness ) {
var target = new MacBCLTarget ( ) {
Harness = harness ,
MonoPath = harness . MONO_PATH ,
TestName = Name ,
} ;
target . Convert ( ) ;
}
}
2016-05-26 16:06:52 +03:00
public class Harness
{
public HarnessAction Action { get ; set ; }
public int Verbosity { get ; set ; }
2016-06-17 18:21:18 +03:00
public Log HarnessLog { get ; set ; }
2017-01-16 09:24:14 +03:00
public bool UseSystem { get ; set ; } // if the system XI/XM should be used, or the locally build XI/XM.
2017-08-03 15:31:02 +03:00
public HashSet < string > Labels { get ; } = new HashSet < string > ( ) ;
2016-05-26 16:06:52 +03:00
// This is the maccore/tests directory.
string root_directory ;
public string RootDirectory {
get {
2017-01-16 09:24:14 +03:00
if ( root_directory = = null ) {
var testAssemblyDirectory = Path . GetDirectoryName ( System . Reflection . Assembly . GetExecutingAssembly ( ) . Location ) ;
var dir = testAssemblyDirectory ;
var path = Path . Combine ( testAssemblyDirectory , ".git" ) ;
while ( ! Directory . Exists ( path ) & & path . Length > 3 ) {
dir = Path . GetDirectoryName ( dir ) ;
path = Path . Combine ( dir , ".git" ) ;
}
if ( ! Directory . Exists ( path ) )
throw new Exception ( "Could not find the xamarin-macios repo." ) ;
path = Path . Combine ( Path . GetDirectoryName ( path ) , "tests" ) ;
if ( ! Directory . Exists ( path ) )
throw new Exception ( "Could not find the tests directory." ) ;
root_directory = path ;
}
2016-05-26 16:06:52 +03:00
return root_directory ;
}
set {
root_directory = value ;
}
}
2016-06-10 21:56:48 +03:00
public List < TestProject > IOSTestProjects { get ; set ; } = new List < TestProject > ( ) ;
2016-11-30 18:38:32 +03:00
public List < MacTestProject > MacTestProjects { get ; set ; } = new List < MacTestProject > ( ) ;
2017-02-01 19:05:08 +03:00
public List < BCLTest > IOSBclTests { get ; set ; } = new List < BCLTest > ( ) ;
public List < MacBCLTest > MacBclTests { get ; set ; } = new List < MacBCLTest > ( ) ;
2016-05-26 16:06:52 +03:00
// Configure
public bool AutoConf { get ; set ; }
2016-06-07 19:49:20 +03:00
public bool Mac { get ; set ; }
2016-05-26 16:06:52 +03:00
public string WatchOSContainerTemplate { get ; set ; }
public string WatchOSAppTemplate { get ; set ; }
public string WatchOSExtensionTemplate { get ; set ; }
2017-01-04 21:45:26 +03:00
public string TodayContainerTemplate { get ; set ; }
public string TodayExtensionTemplate { get ; set ; }
2016-05-26 16:06:52 +03:00
public string MONO_PATH { get ; set ; } // Use same name as in Makefiles, so that a grep finds it.
public string WATCH_MONO_PATH { get ; set ; } // Use same name as in Makefiles, so that a grep finds it.
public string TVOS_MONO_PATH { get ; set ; } // Use same name as in Makefiles, so that a grep finds it.
2017-01-27 12:52:49 +03:00
public bool INCLUDE_IOS { get ; set ; }
public bool INCLUDE_TVOS { get ; set ; }
2016-05-26 16:06:52 +03:00
public bool INCLUDE_WATCH { get ; set ; }
2017-01-27 12:52:49 +03:00
public bool INCLUDE_MAC { get ; set ; }
2016-06-06 13:48:53 +03:00
public string JENKINS_RESULTS_DIRECTORY { get ; set ; } // Use same name as in Makefiles, so that a grep finds it.
2016-06-13 07:20:33 +03:00
public string MAC_DESTDIR { get ; set ; }
public string IOS_DESTDIR { get ; set ; }
2016-05-26 16:06:52 +03:00
// Run
2016-11-16 17:23:11 +03:00
public AppRunnerTarget Target { get ; set ; }
2017-08-23 19:22:53 +03:00
public string SdkRoot { get ; set ; }
2016-05-26 16:06:52 +03:00
public string Configuration { get ; set ; } = "Debug" ;
public string LogFile { get ; set ; }
2016-06-06 13:48:53 +03:00
public string LogDirectory { get ; set ; } = Environment . CurrentDirectory ;
2016-05-26 16:06:52 +03:00
public double Timeout { get ; set ; } = 10 ; // in minutes
public double LaunchTimeout { get ; set ; } // in minutes
2016-06-06 13:48:53 +03:00
public bool DryRun { get ; set ; } // Most things don't support this. If you need it somewhere, implement it!
public string JenkinsConfiguration { get ; set ; }
2016-08-05 23:28:13 +03:00
public Dictionary < string , string > EnvironmentVariables { get ; set ; } = new Dictionary < string , string > ( ) ;
2017-08-29 15:59:37 +03:00
public string MarkdownSummaryPath { get ; set ; }
2016-05-26 16:06:52 +03:00
public Harness ( )
{
2016-06-18 18:45:56 +03:00
LaunchTimeout = InWrench ? 3 : 120 ;
2016-05-26 16:06:52 +03:00
}
public string XcodeRoot {
get {
var p = SdkRoot ;
do {
if ( p = = "/" ) {
throw new Exception ( string . Format ( "Could not find Xcode.app in {0}" , SdkRoot ) ) ;
} else if ( File . Exists ( Path . Combine ( p , "Contents" , "MacOS" , "Xcode" ) ) ) {
return p ;
}
p = Path . GetDirectoryName ( p ) ;
} while ( true ) ;
}
}
2017-06-09 17:24:01 +03:00
Version xcode_version ;
public Version XcodeVersion {
get {
if ( xcode_version = = null ) {
var doc = new XmlDocument ( ) ;
doc . Load ( Path . Combine ( XcodeRoot , "Contents" , "version.plist" ) ) ;
xcode_version = Version . Parse ( doc . SelectSingleNode ( "//key[text() = 'CFBundleShortVersionString']/following-sibling::string" ) . InnerText ) ;
}
return xcode_version ;
}
}
2016-11-24 17:12:51 +03:00
object mlaunch_lock = new object ( ) ;
2016-06-16 02:55:06 +03:00
string DownloadMlaunch ( )
{
2016-08-02 19:14:28 +03:00
// NOTE: the filename part in the url must be unique so that the caching logic works properly.
2017-06-09 17:24:01 +03:00
var mlaunch_url = "https://dl.xamarin.com/uploads/3euiqmcoizk/mlaunch-18deb964b64886af65fb1760b19adeee58dd8bea.zip" ;
2016-11-09 18:14:59 +03:00
var extraction_dir = Path . Combine ( Path . GetTempPath ( ) , Path . GetFileNameWithoutExtension ( mlaunch_url ) ) ;
var mlaunch_path = Path . Combine ( extraction_dir , "bin" , "mlaunch" ) ;
2016-11-24 17:12:51 +03:00
lock ( mlaunch_lock ) {
if ( File . Exists ( mlaunch_path ) )
return mlaunch_path ;
try {
var local_zip = extraction_dir + ".zip" ;
Log ( "Downloading mlaunch to: {0}" , local_zip ) ;
var wc = new System . Net . WebClient ( ) ;
wc . DownloadFile ( mlaunch_url , local_zip ) ;
Log ( "Downloaded mlaunch." ) ;
var tmp_extraction_dir = extraction_dir + ".tmp" ;
if ( Directory . Exists ( tmp_extraction_dir ) )
Directory . Delete ( tmp_extraction_dir , true ) ;
2016-11-25 13:27:08 +03:00
if ( Directory . Exists ( extraction_dir ) )
Directory . Delete ( extraction_dir , true ) ;
2016-11-24 17:12:51 +03:00
Log ( "Extracting mlaunch..." ) ;
using ( var p = new Process ( ) ) {
p . StartInfo . FileName = "unzip" ;
2017-06-06 23:32:25 +03:00
p . StartInfo . Arguments = $"-d {StringUtils.Quote (tmp_extraction_dir)} {StringUtils.Quote (local_zip)}" ;
2016-11-24 17:12:51 +03:00
Log ( "{0} {1}" , p . StartInfo . FileName , p . StartInfo . Arguments ) ;
p . Start ( ) ;
p . WaitForExit ( ) ;
if ( p . ExitCode ! = 0 ) {
Log ( "Could not unzip mlaunch, exit code: {0}" , p . ExitCode ) ;
return mlaunch_path ;
}
2016-11-09 18:14:59 +03:00
}
2016-11-24 17:12:51 +03:00
Directory . Move ( tmp_extraction_dir , extraction_dir ) ;
2016-11-09 18:14:59 +03:00
2016-11-24 17:12:51 +03:00
Log ( "Final mlaunch path: {0}" , mlaunch_path ) ;
} catch ( Exception e ) {
Log ( "Could not download mlaunch: {0}" , e ) ;
}
return mlaunch_path ;
2016-06-16 02:55:06 +03:00
}
}
2016-11-25 14:37:02 +03:00
public string MtouchPath {
get {
return Path . Combine ( IOS_DESTDIR , "Library" , "Frameworks" , "Xamarin.iOS.framework" , "Versions" , "Current" , "bin" , "mtouch" ) ;
}
}
2016-06-07 19:49:20 +03:00
string mlaunch ;
2016-05-26 16:06:52 +03:00
public string MlaunchPath {
get {
2016-06-07 19:49:20 +03:00
if ( mlaunch = = null ) {
2017-01-12 17:12:16 +03:00
// First check if we've built mlaunch locally.
var filename = Path . GetFullPath ( Path . Combine ( IOS_DESTDIR , "Library" , "Frameworks" , "Xamarin.iOS.framework" , "Versions" , "Current" , "bin" , "mlaunch" ) ) ;
if ( File . Exists ( filename ) ) {
Log ( "Found mlaunch: {0}" , filename ) ;
2017-03-08 22:01:10 +03:00
Environment . SetEnvironmentVariable ( "MLAUNCH_PATH" , filename ) ;
2017-01-12 17:12:16 +03:00
return mlaunch = filename ;
2016-06-17 18:21:18 +03:00
}
2017-01-12 17:12:16 +03:00
// Then check if we can download mlaunch.
Log ( "Could not find a locally built mlaunch, will try downloading it." ) ;
2016-06-17 18:21:18 +03:00
try {
2017-01-12 17:12:16 +03:00
filename = DownloadMlaunch ( ) ;
2016-06-17 18:21:18 +03:00
} catch ( Exception e ) {
Log ( "Could not download mlaunch: {0}" , e ) ;
}
2017-01-12 17:12:16 +03:00
if ( File . Exists ( filename ) ) {
Log ( "Found mlaunch: {0}" , filename ) ;
2017-03-08 22:01:10 +03:00
Environment . SetEnvironmentVariable ( "MLAUNCH_PATH" , filename ) ;
2017-01-12 17:12:16 +03:00
return mlaunch = filename ;
2016-06-07 19:49:20 +03:00
}
2017-01-12 17:12:16 +03:00
// Then check if the system version of Xamarin.iOS has mlaunch.
// This may be a version of mlaunch we're not compatible with, since we don't control which XI version the system has.
Log ( "Could not download mlaunch, will try the system's Xamarin.iOS." ) ;
filename = "/Library/Frameworks/Xamarin.iOS.framework/Versions/Current/bin/mlaunch" ;
if ( File . Exists ( filename ) ) {
Log ( "Found mlaunch: {0}" , filename ) ;
2017-03-08 22:01:10 +03:00
Environment . SetEnvironmentVariable ( "MLAUNCH_PATH" , filename ) ;
2017-01-12 17:12:16 +03:00
return mlaunch = filename ;
}
2016-06-17 18:21:18 +03:00
2017-01-12 17:12:16 +03:00
throw new FileNotFoundException ( string . Format ( "Could not find mlaunch: {0}" , filename ) ) ;
2016-05-26 16:15:08 +03:00
}
2016-06-07 19:49:20 +03:00
return mlaunch ;
2016-05-26 16:06:52 +03:00
}
}
2016-11-25 14:37:02 +03:00
void LoadConfig ( )
2016-06-10 21:56:48 +03:00
{
ParseConfigFiles ( ) ;
var src_root = Path . GetDirectoryName ( RootDirectory ) ;
MONO_PATH = Path . GetFullPath ( Path . Combine ( src_root , "external" , "mono" ) ) ;
WATCH_MONO_PATH = make_config [ "WATCH_MONO_PATH" ] ;
TVOS_MONO_PATH = MONO_PATH ;
2017-01-27 12:52:49 +03:00
INCLUDE_IOS = make_config . ContainsKey ( "INCLUDE_IOS" ) & & ! string . IsNullOrEmpty ( make_config [ "INCLUDE_IOS" ] ) ;
INCLUDE_TVOS = make_config . ContainsKey ( "INCLUDE_TVOS" ) & & ! string . IsNullOrEmpty ( make_config [ "INCLUDE_TVOS" ] ) ;
2016-06-10 21:56:48 +03:00
JENKINS_RESULTS_DIRECTORY = make_config [ "JENKINS_RESULTS_DIRECTORY" ] ;
2017-01-27 12:52:49 +03:00
INCLUDE_WATCH = make_config . ContainsKey ( "INCLUDE_WATCH" ) & & ! string . IsNullOrEmpty ( make_config [ "INCLUDE_WATCH" ] ) ;
INCLUDE_MAC = make_config . ContainsKey ( "INCLUDE_MAC" ) & & ! string . IsNullOrEmpty ( make_config [ "INCLUDE_MAC" ] ) ;
2016-06-13 07:20:33 +03:00
MAC_DESTDIR = make_config [ "MAC_DESTDIR" ] ;
IOS_DESTDIR = make_config [ "IOS_DESTDIR" ] ;
2017-08-23 19:22:53 +03:00
if ( string . IsNullOrEmpty ( SdkRoot ) )
SdkRoot = make_config [ "XCODE_DEVELOPER_ROOT" ] ;
2016-06-10 21:56:48 +03:00
}
2016-05-26 16:06:52 +03:00
void AutoConfigureMac ( )
{
2017-02-02 17:44:32 +03:00
var test_suites = new [ ] { new { ProjectFile = "apitest" , Name = "apitest" } , new { ProjectFile = "dontlink-mac" , Name = "dont link" } } ;
2016-05-26 16:06:52 +03:00
foreach ( var p in test_suites )
2017-03-30 19:01:15 +03:00
MacTestProjects . Add ( new MacTestProject ( Path . GetFullPath ( Path . Combine ( RootDirectory , p . ProjectFile + "/" + p . ProjectFile + ".sln" ) ) ) { Name = p . Name } ) ;
2017-02-01 19:05:08 +03:00
2017-05-10 18:17:39 +03:00
MacTestProjects . Add ( new MacTestProject ( Path . GetFullPath ( Path . Combine ( RootDirectory , "introspection" , "Mac" , "introspection-mac.csproj" ) ) , skipXMVariations : true ) { Name = "introspection" } ) ;
2017-02-01 19:05:08 +03:00
2017-05-08 14:20:01 +03:00
var hard_coded_test_suites = new [ ] {
2017-05-25 08:11:36 +03:00
new { ProjectFile = "mmptest" , Name = "mmptest" , IsNUnit = true , Configurations = ( string [ ] ) null } ,
new { ProjectFile = "msbuild-mac" , Name = "MSBuild tests" , IsNUnit = false , Configurations = ( string [ ] ) null } ,
new { ProjectFile = "xammac_tests" , Name = "xammac tests" , IsNUnit = false , Configurations = new string [ ] { "Debug" , "Release" } } ,
2017-05-08 14:20:01 +03:00
} ;
foreach ( var p in hard_coded_test_suites ) {
MacTestProjects . Add ( new MacTestProject ( Path . GetFullPath ( Path . Combine ( RootDirectory , p . ProjectFile + "/" + p . ProjectFile + ".csproj" ) ) , generateVariations : false ) {
Name = p . Name ,
IsNUnitProject = p . IsNUnit ,
SolutionPath = Path . GetFullPath ( Path . Combine ( RootDirectory , "tests-mac.sln" ) ) ,
2017-05-25 08:11:36 +03:00
Configurations = p . Configurations ,
2017-05-08 14:20:01 +03:00
} ) ;
}
2017-02-01 19:05:08 +03:00
var bcl_suites = new string [ ] { "mscorlib" , "System" , "System.Core" , "System.Data" , "System.Net.Http" , "System.Numerics" , "System.Runtime.Serialization" , "System.Transactions" , "System.Web.Services" , "System.Xml" , "System.Xml.Linq" , "Mono.Security" , "System.ComponentModel.DataAnnotations" , "System.Json" , "System.ServiceModel.Web" , "Mono.Data.Sqlite" } ;
foreach ( var p in bcl_suites ) {
2017-02-02 17:44:32 +03:00
MacTestProjects . Add ( new MacTestProject ( Path . GetFullPath ( Path . Combine ( RootDirectory , "bcl-test/" + p + "/" + p + "-Mac.csproj" ) ) , generateVariations : false ) { Name = p } ) ;
2017-02-01 19:05:08 +03:00
MacBclTests . Add ( new MacBCLTest ( p ) ) ;
}
2016-05-26 16:06:52 +03:00
}
2016-06-10 21:56:48 +03:00
void AutoConfigureIOS ( )
2016-05-26 16:06:52 +03:00
{
2017-01-26 15:38:39 +03:00
var test_suites = new string [ ] { "monotouch-test" , "framework-test" , "mini" , "interdependent-binding-projects" } ;
var library_projects = new string [ ] { "BundledResources" , "EmbeddedResources" , "bindings-test" , "bindings-test2" , "bindings-framework-test" } ;
2016-05-26 16:06:52 +03:00
var fsharp_test_suites = new string [ ] { "fsharp" } ;
var fsharp_library_projects = new string [ ] { "fsharplibrary" } ;
var bcl_suites = new string [ ] { "mscorlib" , "System" , "System.Core" , "System.Data" , "System.Net.Http" , "System.Numerics" , "System.Runtime.Serialization" , "System.Transactions" , "System.Web.Services" , "System.Xml" , "System.Xml.Linq" , "Mono.Security" , "System.ComponentModel.DataAnnotations" , "System.Json" , "System.ServiceModel.Web" , "Mono.Data.Sqlite" } ;
2016-08-04 16:47:05 +03:00
IOSTestProjects . Add ( new TestProject ( Path . GetFullPath ( Path . Combine ( RootDirectory , "bcl-test/mscorlib/mscorlib-0.csproj" ) ) , false ) ) ;
IOSTestProjects . Add ( new TestProject ( Path . GetFullPath ( Path . Combine ( RootDirectory , "bcl-test/mscorlib/mscorlib-1.csproj" ) ) , false ) ) ;
2016-05-26 16:06:52 +03:00
foreach ( var p in test_suites )
2016-06-10 21:56:48 +03:00
IOSTestProjects . Add ( new TestProject ( Path . GetFullPath ( Path . Combine ( RootDirectory , p + "/" + p + ".csproj" ) ) ) ) ;
2016-05-26 16:06:52 +03:00
foreach ( var p in fsharp_test_suites )
2016-06-10 21:56:48 +03:00
IOSTestProjects . Add ( new TestProject ( Path . GetFullPath ( Path . Combine ( RootDirectory , p + "/" + p + ".fsproj" ) ) ) ) ;
2016-05-26 16:06:52 +03:00
foreach ( var p in library_projects )
2016-06-10 21:56:48 +03:00
IOSTestProjects . Add ( new TestProject ( Path . GetFullPath ( Path . Combine ( RootDirectory , p + "/" + p + ".csproj" ) ) , false ) ) ;
2016-05-26 16:06:52 +03:00
foreach ( var p in fsharp_library_projects )
2016-06-10 21:56:48 +03:00
IOSTestProjects . Add ( new TestProject ( Path . GetFullPath ( Path . Combine ( RootDirectory , p + "/" + p + ".fsproj" ) ) , false ) ) ;
2017-02-01 19:05:08 +03:00
foreach ( var p in bcl_suites ) {
2016-06-10 21:56:48 +03:00
IOSTestProjects . Add ( new TestProject ( Path . GetFullPath ( Path . Combine ( RootDirectory , "bcl-test/" + p + "/" + p + ".csproj" ) ) ) ) ;
2017-02-01 19:05:08 +03:00
IOSBclTests . Add ( new BCLTest ( p ) ) ;
}
2017-05-10 18:17:39 +03:00
IOSTestProjects . Add ( new TestProject ( Path . GetFullPath ( Path . Combine ( RootDirectory , "introspection" , "iOS" , "introspection-ios.csproj" ) ) ) { Name = "introspection" } ) ;
2016-06-10 21:56:48 +03:00
IOSTestProjects . Add ( new TestProject ( Path . GetFullPath ( Path . Combine ( RootDirectory , "linker-ios" , "dont link" , "dont link.csproj" ) ) ) ) ;
IOSTestProjects . Add ( new TestProject ( Path . GetFullPath ( Path . Combine ( RootDirectory , "linker-ios" , "link all" , "link all.csproj" ) ) ) ) ;
IOSTestProjects . Add ( new TestProject ( Path . GetFullPath ( Path . Combine ( RootDirectory , "linker-ios" , "link sdk" , "link sdk.csproj" ) ) ) ) ;
2016-06-06 13:48:53 +03:00
2016-11-25 19:36:04 +03:00
WatchOSContainerTemplate = Path . GetFullPath ( Path . Combine ( RootDirectory , "templates/WatchContainer" ) ) ;
WatchOSAppTemplate = Path . GetFullPath ( Path . Combine ( RootDirectory , "templates/WatchApp" ) ) ;
WatchOSExtensionTemplate = Path . GetFullPath ( Path . Combine ( RootDirectory , "templates/WatchExtension" ) ) ;
2017-01-04 21:45:26 +03:00
TodayContainerTemplate = Path . GetFullPath ( Path . Combine ( RootDirectory , "templates" , "TodayContainer" ) ) ;
TodayExtensionTemplate = Path . GetFullPath ( Path . Combine ( RootDirectory , "templates" , "TodayExtension" ) ) ;
2016-05-26 16:06:52 +03:00
}
2017-01-05 01:05:04 +03:00
Dictionary < string , string > make_config = new Dictionary < string , string > ( ) ;
IEnumerable < string > FindConfigFiles ( string name )
2016-05-26 16:06:52 +03:00
{
2017-01-05 01:05:04 +03:00
var dir = Path . GetFullPath ( RootDirectory ) ;
2016-05-26 16:06:52 +03:00
while ( dir ! = "/" ) {
var file = Path . Combine ( dir , name ) ;
if ( File . Exists ( file ) )
yield return file ;
dir = Path . GetDirectoryName ( dir ) ;
}
}
2017-01-05 01:05:04 +03:00
void ParseConfigFiles ( )
2016-05-26 16:06:52 +03:00
{
2017-01-16 09:24:14 +03:00
ParseConfigFiles ( FindConfigFiles ( UseSystem ? "test-system.config" : "test.config" ) ) ;
2016-05-26 16:06:52 +03:00
ParseConfigFiles ( FindConfigFiles ( "Make.config.local" ) ) ;
ParseConfigFiles ( FindConfigFiles ( "Make.config" ) ) ;
}
2017-01-05 01:05:04 +03:00
void ParseConfigFiles ( IEnumerable < string > files )
2016-05-26 16:06:52 +03:00
{
foreach ( var file in files )
ParseConfigFile ( file ) ;
}
2017-01-05 01:05:04 +03:00
void ParseConfigFile ( string file )
2016-05-26 16:06:52 +03:00
{
if ( string . IsNullOrEmpty ( file ) )
return ;
foreach ( var line in File . ReadAllLines ( file ) ) {
var eq = line . IndexOf ( '=' ) ;
if ( eq = = - 1 )
continue ;
var key = line . Substring ( 0 , eq ) ;
if ( ! make_config . ContainsKey ( key ) )
make_config [ key ] = line . Substring ( eq + 1 ) ;
}
}
public int Configure ( )
{
if ( Mac )
ConfigureMac ( ) ;
else
ConfigureIOS ( ) ;
return 0 ;
}
void ConfigureMac ( )
{
var classic_targets = new List < MacClassicTarget > ( ) ;
var unified_targets = new List < MacUnifiedTarget > ( ) ;
var hardcoded_unified_targets = new List < MacUnifiedTarget > ( ) ;
RootDirectory = Path . GetFullPath ( RootDirectory ) . TrimEnd ( '/' ) ;
if ( AutoConf )
AutoConfigureMac ( ) ;
2017-02-01 19:05:08 +03:00
foreach ( var proj in MacBclTests ) {
proj . Convert ( this ) ;
}
2016-05-26 16:06:52 +03:00
2016-06-10 21:56:48 +03:00
foreach ( var proj in MacTestProjects . Where ( ( v ) = > v . GenerateVariations ) ) {
2017-03-30 19:01:15 +03:00
var file = Path . ChangeExtension ( proj . Path , "csproj" ) ;
2016-05-26 16:06:52 +03:00
if ( ! File . Exists ( file ) )
throw new FileNotFoundException ( file ) ;
2016-11-30 18:38:32 +03:00
foreach ( bool thirtyTwoBit in new bool [ ] { false , true } )
{
var unifiedMobile = new MacUnifiedTarget ( true , thirtyTwoBit )
{
TemplateProjectPath = file ,
Harness = this ,
2017-05-08 14:20:01 +03:00
IsNUnitProject = proj . IsNUnitProject ,
2016-11-30 18:38:32 +03:00
} ;
unifiedMobile . Execute ( ) ;
unified_targets . Add ( unifiedMobile ) ;
if ( ! proj . SkipXMVariations ) {
var unifiedXM45 = new MacUnifiedTarget ( false , thirtyTwoBit )
{
TemplateProjectPath = file ,
Harness = this ,
} ;
unifiedXM45 . Execute ( ) ;
unified_targets . Add ( unifiedXM45 ) ;
}
}
2016-05-26 16:06:52 +03:00
var classic = new MacClassicTarget ( ) {
TemplateProjectPath = file ,
Harness = this ,
} ;
classic . Execute ( ) ;
classic_targets . Add ( classic ) ;
}
2016-06-10 21:56:48 +03:00
foreach ( var proj in MacTestProjects . Where ( ( v ) = > ! v . GenerateVariations ) ) {
var file = proj . Path ;
2016-11-30 18:38:32 +03:00
var unifiedMobile = new MacUnifiedTarget ( true , false , true )
2016-05-26 16:06:52 +03:00
{
TemplateProjectPath = file ,
Harness = this ,
2017-05-08 14:20:01 +03:00
IsNUnitProject = proj . IsNUnitProject ,
2016-05-26 16:06:52 +03:00
} ;
unifiedMobile . Execute ( ) ;
hardcoded_unified_targets . Add ( unifiedMobile ) ;
}
MakefileGenerator . CreateMacMakefile ( this , classic_targets . Union < MacTarget > ( unified_targets ) . Union ( hardcoded_unified_targets ) ) ;
}
void ConfigureIOS ( )
{
var unified_targets = new List < UnifiedTarget > ( ) ;
var tvos_targets = new List < TVOSTarget > ( ) ;
var watchos_targets = new List < WatchOSTarget > ( ) ;
2017-01-04 21:45:26 +03:00
var today_targets = new List < TodayExtensionTarget > ( ) ;
2016-05-26 16:06:52 +03:00
RootDirectory = Path . GetFullPath ( RootDirectory ) . TrimEnd ( '/' ) ;
if ( AutoConf )
2016-06-10 21:56:48 +03:00
AutoConfigureIOS ( ) ;
2016-05-26 16:06:52 +03:00
2017-02-01 19:05:08 +03:00
foreach ( var proj in IOSBclTests ) {
proj . Convert ( this ) ;
}
2016-05-26 16:06:52 +03:00
2016-06-10 21:56:48 +03:00
foreach ( var proj in IOSTestProjects ) {
2016-06-06 13:48:53 +03:00
var file = proj . Path ;
2016-05-26 16:06:52 +03:00
if ( ! File . Exists ( file ) )
throw new FileNotFoundException ( file ) ;
var watchos = new WatchOSTarget ( ) {
TemplateProjectPath = file ,
Harness = this ,
} ;
watchos . Execute ( ) ;
watchos_targets . Add ( watchos ) ;
var tvos = new TVOSTarget ( ) {
TemplateProjectPath = file ,
Harness = this ,
} ;
tvos . Execute ( ) ;
tvos_targets . Add ( tvos ) ;
var unified = new UnifiedTarget ( ) {
TemplateProjectPath = file ,
Harness = this ,
} ;
unified . Execute ( ) ;
unified_targets . Add ( unified ) ;
2017-01-04 21:45:26 +03:00
var today = new TodayExtensionTarget
{
TemplateProjectPath = file ,
Harness = this ,
} ;
today . Execute ( ) ;
today_targets . Add ( today ) ;
2016-05-26 16:06:52 +03:00
}
SolutionGenerator . CreateSolution ( this , watchos_targets , "watchos" ) ;
SolutionGenerator . CreateSolution ( this , tvos_targets , "tvos" ) ;
2017-01-04 21:45:26 +03:00
SolutionGenerator . CreateSolution ( this , today_targets , "today" ) ;
MakefileGenerator . CreateMakefile ( this , unified_targets , tvos_targets , watchos_targets , today_targets ) ;
2016-05-26 16:06:52 +03:00
}
public int Install ( )
{
2016-06-17 18:21:18 +03:00
if ( HarnessLog = = null )
HarnessLog = new ConsoleLog ( ) ;
2016-06-10 21:56:48 +03:00
foreach ( var project in IOSTestProjects ) {
2016-05-26 16:06:52 +03:00
var runner = new AppRunner ( ) {
Harness = this ,
2016-06-06 13:48:53 +03:00
ProjectFile = project . Path ,
2016-06-17 18:21:18 +03:00
MainLog = HarnessLog ,
2016-05-26 16:06:52 +03:00
} ;
2017-01-04 21:45:26 +03:00
var rv = runner . InstallAsync ( ) . Result ;
if ( ! rv . Succeeded )
return rv . ExitCode ;
2016-05-26 16:06:52 +03:00
}
return 0 ;
}
2017-01-02 10:58:46 +03:00
public int Uninstall ( )
{
if ( HarnessLog = = null )
HarnessLog = new ConsoleLog ( ) ;
foreach ( var project in IOSTestProjects ) {
var runner = new AppRunner ( )
{
Harness = this ,
ProjectFile = project . Path ,
MainLog = HarnessLog ,
} ;
2017-01-04 21:45:26 +03:00
var rv = runner . UninstallAsync ( ) . Result ;
if ( ! rv . Succeeded )
return rv . ExitCode ;
2017-01-02 10:58:46 +03:00
}
return 0 ;
}
2016-05-26 16:06:52 +03:00
public int Run ( )
{
2016-06-17 18:21:18 +03:00
if ( HarnessLog = = null )
HarnessLog = new ConsoleLog ( ) ;
2016-06-10 21:56:48 +03:00
foreach ( var project in IOSTestProjects ) {
2016-05-26 16:06:52 +03:00
var runner = new AppRunner ( ) {
Harness = this ,
2016-06-06 13:48:53 +03:00
ProjectFile = project . Path ,
2016-06-17 18:21:18 +03:00
MainLog = HarnessLog ,
2016-05-26 16:06:52 +03:00
} ;
2016-06-17 18:21:18 +03:00
var rv = runner . RunAsync ( ) . Result ;
2016-05-26 16:06:52 +03:00
if ( rv ! = 0 )
return rv ;
}
return 0 ;
}
public void Log ( int min_level , string message )
{
if ( Verbosity < min_level )
return ;
Console . WriteLine ( message ) ;
2016-06-06 13:48:53 +03:00
HarnessLog ? . WriteLine ( message ) ;
2016-05-26 16:06:52 +03:00
}
public void Log ( int min_level , string message , params object [ ] args )
{
if ( Verbosity < min_level )
return ;
Console . WriteLine ( message , args ) ;
2016-06-06 13:48:53 +03:00
HarnessLog ? . WriteLine ( message , args ) ;
2016-05-26 16:06:52 +03:00
}
public void Log ( string message )
{
Log ( 0 , message ) ;
}
public void Log ( string message , params object [ ] args )
{
Log ( 0 , message , args ) ;
}
public void LogWrench ( string message , params object [ ] args )
{
if ( ! InWrench )
return ;
Console . WriteLine ( message , args ) ;
}
public void LogWrench ( string message )
{
if ( ! InWrench )
return ;
Console . WriteLine ( message ) ;
}
public bool InWrench {
get {
2016-11-15 21:04:37 +03:00
var buildRev = Environment . GetEnvironmentVariable ( "BUILD_REVISION" ) ;
return ! string . IsNullOrEmpty ( buildRev ) & & buildRev ! = "jenkins" ;
}
}
public bool InJenkins {
get {
var buildRev = Environment . GetEnvironmentVariable ( "BUILD_REVISION" ) ;
return ! string . IsNullOrEmpty ( buildRev ) & & buildRev = = "jenkins" ;
2016-05-26 16:06:52 +03:00
}
}
public int Execute ( )
{
2016-11-25 14:37:02 +03:00
LoadConfig ( ) ;
2016-05-26 16:06:52 +03:00
switch ( Action ) {
case HarnessAction . Configure :
return Configure ( ) ;
case HarnessAction . Run :
return Run ( ) ;
case HarnessAction . Install :
return Install ( ) ;
2017-01-02 10:58:46 +03:00
case HarnessAction . Uninstall :
return Uninstall ( ) ;
2016-06-06 13:48:53 +03:00
case HarnessAction . Jenkins :
return Jenkins ( ) ;
2016-05-26 16:06:52 +03:00
default :
throw new NotImplementedException ( Action . ToString ( ) ) ;
}
}
2016-06-06 13:48:53 +03:00
public int Jenkins ( )
{
2016-06-10 21:56:48 +03:00
if ( AutoConf ) {
AutoConfigureIOS ( ) ;
AutoConfigureMac ( ) ;
}
2016-06-06 13:48:53 +03:00
var jenkins = new Jenkins ( )
{
Harness = this ,
} ;
return jenkins . Run ( ) ;
}
2016-05-26 16:06:52 +03:00
public void Save ( XmlDocument doc , string path )
{
if ( ! File . Exists ( path ) ) {
doc . Save ( path ) ;
Log ( 1 , "Created {0}" , path ) ;
} else {
var tmpPath = path + ".tmp" ;
doc . Save ( tmpPath ) ;
var existing = File . ReadAllText ( path ) ;
var updated = File . ReadAllText ( tmpPath ) ;
if ( existing = = updated ) {
File . Delete ( tmpPath ) ;
Log ( 1 , "Not saved {0}, no change" , path ) ;
} else {
File . Delete ( path ) ;
File . Move ( tmpPath , path ) ;
Log ( 1 , "Updated {0}" , path ) ;
}
}
}
public void Save ( StringWriter doc , string path )
{
if ( ! File . Exists ( path ) ) {
File . WriteAllText ( path , doc . ToString ( ) ) ;
Log ( 1 , "Created {0}" , path ) ;
} else {
var existing = File . ReadAllText ( path ) ;
var updated = doc . ToString ( ) ;
if ( existing = = updated ) {
Log ( 1 , "Not saved {0}, no change" , path ) ;
} else {
File . WriteAllText ( path , updated ) ;
Log ( 1 , "Updated {0}" , path ) ;
}
}
}
public void Save ( string doc , string path )
{
if ( ! File . Exists ( path ) ) {
File . WriteAllText ( path , doc ) ;
Log ( 1 , "Created {0}" , path ) ;
} else {
var existing = File . ReadAllText ( path ) ;
if ( existing = = doc ) {
Log ( 1 , "Not saved {0}, no change" , path ) ;
} else {
File . WriteAllText ( path , doc ) ;
Log ( 1 , "Updated {0}" , path ) ;
}
}
}
// We want guids that nobody else has, but we also want to generate the same guid
// on subsequent invocations (so that csprojs don't change unnecessarily, which is
// annoying when XS reloads the projects, and also causes unnecessary rebuilds).
// Nothing really breaks when the sequence isn't identical from run to run, so
// this is just a best minimal effort.
static Random guid_generator = new Random ( unchecked ( ( int ) 0xdeadf00d ) ) ;
public Guid NewStableGuid ( )
{
var bytes = new byte [ 16 ] ;
guid_generator . NextBytes ( bytes ) ;
return new Guid ( bytes ) ;
}
bool? disable_watchos_on_wrench ;
public bool DisableWatchOSOnWrench {
get {
if ( ! disable_watchos_on_wrench . HasValue )
disable_watchos_on_wrench = ! string . IsNullOrEmpty ( Environment . GetEnvironmentVariable ( "DISABLE_WATCH_ON_WRENCH" ) ) ;
return disable_watchos_on_wrench . Value ;
}
}
2016-06-17 18:21:18 +03:00
public Task < ProcessExecutionResult > ExecuteXcodeCommandAsync ( string executable , string args , Log log , TimeSpan timeout )
{
2016-12-16 14:24:08 +03:00
return ProcessHelper . ExecuteCommandAsync ( Path . Combine ( XcodeRoot , "Contents" , "Developer" , "usr" , "bin" , executable ) , args , log , timeout : timeout ) ;
2016-06-17 18:21:18 +03:00
}
public async Task ShowSimulatorList ( LogStream log )
{
2016-12-16 14:24:08 +03:00
await ExecuteXcodeCommandAsync ( "simctl" , "list" , log , TimeSpan . FromSeconds ( 10 ) ) ;
2016-06-17 18:21:18 +03:00
}
public async Task < LogFile > SymbolicateCrashReportAsync ( Log log , LogFile report )
{
var symbolicatecrash = Path . Combine ( XcodeRoot , "Contents/SharedFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash" ) ;
if ( ! File . Exists ( symbolicatecrash ) )
symbolicatecrash = Path . Combine ( XcodeRoot , "Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash" ) ;
if ( ! File . Exists ( symbolicatecrash ) ) {
log . WriteLine ( "Can't symbolicate {0} because the symbolicatecrash script {1} does not exist" , report . Path , symbolicatecrash ) ;
return report ;
}
2017-08-02 23:15:18 +03:00
var symbolicated = new LogFile ( "Symbolicated crash report" , Path . ChangeExtension ( report . Path , ".symbolicated.log" ) ) ;
2016-06-17 18:21:18 +03:00
var environment = new Dictionary < string , string > { { "DEVELOPER_DIR" , Path . Combine ( XcodeRoot , "Contents" , "Developer" ) } } ;
2017-06-06 23:32:25 +03:00
var rv = await ProcessHelper . ExecuteCommandAsync ( symbolicatecrash , StringUtils . Quote ( report . Path ) , symbolicated , TimeSpan . FromMinutes ( 1 ) , environment ) ;
2016-06-17 18:21:18 +03:00
if ( rv . Succeeded ) { ;
log . WriteLine ( "Symbolicated {0} successfully." , report . Path ) ;
return symbolicated ;
} else {
log . WriteLine ( "Failed to symbolicate {0}." , report . Path ) ;
return report ;
}
}
2016-10-11 20:30:11 +03:00
public async Task < HashSet < string > > CreateCrashReportsSnapshotAsync ( Log log , bool simulatorOrDesktop , string device )
2016-06-17 18:21:18 +03:00
{
var rv = new HashSet < string > ( ) ;
2016-06-29 19:21:03 +03:00
if ( simulatorOrDesktop ) {
2016-06-17 18:21:18 +03:00
var dir = Path . Combine ( Environment . GetEnvironmentVariable ( "HOME" ) , "Library" , "Logs" , "DiagnosticReports" ) ;
if ( Directory . Exists ( dir ) )
rv . UnionWith ( Directory . EnumerateFiles ( dir ) ) ;
} else {
var tmp = Path . GetTempFileName ( ) ;
try {
2016-10-11 20:30:11 +03:00
var sb = new StringBuilder ( ) ;
2017-06-06 23:32:25 +03:00
sb . Append ( " --list-crash-reports=" ) . Append ( StringUtils . Quote ( tmp ) ) ;
sb . Append ( " --sdkroot " ) . Append ( StringUtils . Quote ( XcodeRoot ) ) ;
2016-10-11 20:30:11 +03:00
if ( ! string . IsNullOrEmpty ( device ) )
2017-06-06 23:32:25 +03:00
sb . Append ( " --devname " ) . Append ( StringUtils . Quote ( device ) ) ;
2016-10-11 20:30:11 +03:00
var result = await ProcessHelper . ExecuteCommandAsync ( MlaunchPath , sb . ToString ( ) , log , TimeSpan . FromMinutes ( 1 ) ) ;
2016-06-17 18:21:18 +03:00
if ( result . Succeeded )
rv . UnionWith ( File . ReadAllLines ( tmp ) ) ;
} finally {
File . Delete ( tmp ) ;
}
}
return rv ;
}
2016-05-26 16:06:52 +03:00
}
2016-06-29 19:21:03 +03:00
public class CrashReportSnapshot
{
public Harness Harness { get ; set ; }
public Log Log { get ; set ; }
public Logs Logs { get ; set ; }
public string LogDirectory { get ; set ; }
public bool Device { get ; set ; }
2016-10-11 20:30:11 +03:00
public string DeviceName { get ; set ; }
2016-06-29 19:21:03 +03:00
public HashSet < string > InitialSet { get ; private set ; }
public IEnumerable < string > Reports { get ; private set ; }
public async Task StartCaptureAsync ( )
{
2016-10-11 20:30:11 +03:00
InitialSet = await Harness . CreateCrashReportsSnapshotAsync ( Log , ! Device , DeviceName ) ;
2016-06-29 19:21:03 +03:00
}
public async Task EndCaptureAsync ( TimeSpan timeout )
{
// Check for crash reports
var crash_report_search_done = false ;
var crash_report_search_timeout = timeout . TotalSeconds ;
var watch = new Stopwatch ( ) ;
watch . Start ( ) ;
do {
2016-10-11 20:30:11 +03:00
var end_crashes = await Harness . CreateCrashReportsSnapshotAsync ( Log , ! Device , DeviceName ) ;
2016-06-29 19:21:03 +03:00
end_crashes . ExceptWith ( InitialSet ) ;
Reports = end_crashes ;
if ( end_crashes . Count > 0 ) {
Log . WriteLine ( "Found {0} new crash report(s)" , end_crashes . Count ) ;
List < LogFile > crash_reports ;
if ( ! Device ) {
crash_reports = new List < LogFile > ( end_crashes . Count ) ;
foreach ( var path in end_crashes ) {
var logPath = Path . Combine ( LogDirectory , Path . GetFileName ( path ) ) ;
File . Copy ( path , logPath , true ) ;
crash_reports . Add ( Logs . CreateFile ( "Crash report: " + Path . GetFileName ( path ) , logPath ) ) ;
}
} else {
// Download crash reports from the device. We put them in the project directory so that they're automatically deleted on wrench
// (if we put them in /tmp, they'd never be deleted).
var downloaded_crash_reports = new List < LogFile > ( ) ;
foreach ( var file in end_crashes ) {
var crash_report_target = Logs . CreateFile ( "Crash report: " + Path . GetFileName ( file ) , Path . Combine ( LogDirectory , Path . GetFileName ( file ) ) ) ;
2016-10-11 20:30:11 +03:00
var sb = new StringBuilder ( ) ;
2017-06-06 23:32:25 +03:00
sb . Append ( " --download-crash-report=" ) . Append ( StringUtils . Quote ( file ) ) ;
sb . Append ( " --download-crash-report-to=" ) . Append ( StringUtils . Quote ( crash_report_target . Path ) ) ;
sb . Append ( " --sdkroot " ) . Append ( StringUtils . Quote ( Harness . XcodeRoot ) ) ;
2016-10-11 20:30:11 +03:00
if ( ! string . IsNullOrEmpty ( DeviceName ) )
2017-06-06 23:32:25 +03:00
sb . Append ( " --devname " ) . Append ( StringUtils . Quote ( DeviceName ) ) ;
2016-10-11 20:30:11 +03:00
var result = await ProcessHelper . ExecuteCommandAsync ( Harness . MlaunchPath , sb . ToString ( ) , Log , TimeSpan . FromMinutes ( 1 ) ) ;
2016-06-29 19:21:03 +03:00
if ( result . Succeeded ) {
Log . WriteLine ( "Downloaded crash report {0} to {1}" , file , crash_report_target . Path ) ;
crash_report_target = await Harness . SymbolicateCrashReportAsync ( Log , crash_report_target ) ;
Logs . Add ( crash_report_target ) ;
downloaded_crash_reports . Add ( crash_report_target ) ;
} else {
Log . WriteLine ( "Could not download crash report {0}" , file ) ;
}
}
crash_reports = downloaded_crash_reports ;
}
foreach ( var cp in crash_reports ) {
Harness . LogWrench ( "@MonkeyWrench: AddFile: {0}" , cp . Path ) ;
Log . WriteLine ( " {0}" , cp . Path ) ;
}
crash_report_search_done = true ;
} else {
if ( watch . Elapsed . TotalSeconds > crash_report_search_timeout ) {
crash_report_search_done = true ;
} else {
Log . WriteLine ( "No crash reports, waiting a second to see if the crash report service just didn't complete in time ({0})" , ( int ) ( crash_report_search_timeout - watch . Elapsed . TotalSeconds ) ) ;
Thread . Sleep ( TimeSpan . FromSeconds ( 1 ) ) ;
}
}
} while ( ! crash_report_search_done ) ;
}
}
2016-05-26 16:06:52 +03:00
}