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 ;
namespace xharness
{
public enum HarnessAction
{
None ,
Configure ,
Run ,
Install ,
2016-06-06 13:48:53 +03:00
Jenkins ,
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 ; }
2016-05-26 16:06:52 +03:00
// This is the maccore/tests directory.
string root_directory ;
public string RootDirectory {
get {
if ( root_directory = = null )
root_directory = Environment . CurrentDirectory ;
return root_directory ;
}
set {
root_directory = value ;
}
}
2016-06-10 21:56:48 +03:00
public List < TestProject > IOSTestProjects { get ; set ; } = new List < TestProject > ( ) ;
public List < TestProject > MacTestProjects { get ; set ; } = new List < TestProject > ( ) ;
2016-05-26 16:06:52 +03:00
public List < string > BclTests { get ; set ; } = new List < string > ( ) ;
// 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 ; }
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.
public bool INCLUDE_WATCH { 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
public string Target { get ; set ; }
public string SdkRoot { get ; set ; } = "/Applications/Xcode.app" ;
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 > ( ) ;
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 ) ;
}
}
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.
2016-11-09 18:14:59 +03:00
var mlaunch_url = "http://bosstoragemirror.blob.core.windows.net/public-builder/mlaunch/mlaunch-320d91b71c71c4768184c2d2b1ce553c1364970f.zip" ;
var extraction_dir = Path . Combine ( Path . GetTempPath ( ) , Path . GetFileNameWithoutExtension ( mlaunch_url ) ) ;
var mlaunch_path = Path . Combine ( extraction_dir , "bin" , "mlaunch" ) ;
2016-06-16 02:55:06 +03:00
if ( File . Exists ( mlaunch_path ) )
return mlaunch_path ;
2016-11-09 18:14:59 +03:00
2016-06-16 02:55:06 +03:00
try {
2016-11-09 18:14:59 +03:00
var local_zip = extraction_dir + ".zip" ;
Log ( "Downloading mlaunch to: {0}" , local_zip ) ;
2016-06-16 02:55:06 +03:00
var wc = new System . Net . WebClient ( ) ;
2016-11-09 18:14:59 +03:00
wc . DownloadFile ( mlaunch_url , local_zip ) ;
2016-06-16 02:55:06 +03:00
Log ( "Downloaded mlaunch." ) ;
2016-11-09 18:14:59 +03:00
var tmp_extraction_dir = extraction_dir + ".tmp" ;
if ( Directory . Exists ( tmp_extraction_dir ) )
Directory . Delete ( tmp_extraction_dir , true ) ;
Log ( "Extracting mlaunch..." ) ;
using ( var p = new Process ( ) ) {
p . StartInfo . FileName = "unzip" ;
p . StartInfo . Arguments = $"-d {Quote (tmp_extraction_dir)} {Quote (local_zip)}" ;
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 ;
}
}
Directory . Move ( tmp_extraction_dir , extraction_dir ) ;
Log ( "Final mlaunch path: {0}" , mlaunch_path ) ;
2016-06-16 02:55:06 +03:00
} catch ( Exception e ) {
Log ( "Could not download mlaunch: {0}" , e ) ;
}
return mlaunch_path ;
}
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 ) {
2016-06-17 18:21:18 +03:00
var dir = Path . GetFullPath ( RootDirectory ) ;
while ( dir . Length > 3 ) {
var filename = Path . GetFullPath ( Path . Combine ( dir , "maccore" , "tools" , "mlaunch" , "mlaunch" ) ) ;
if ( File . Exists ( filename ) )
return mlaunch = filename ;
dir = Path . GetDirectoryName ( dir ) ;
}
string path = string . Empty ;
Log ( "Could not find mlaunch locally, will try downloading it." ) ;
try {
path = DownloadMlaunch ( ) ;
} catch ( Exception e ) {
Log ( "Could not download mlaunch: {0}" , e ) ;
}
2016-06-07 19:49:20 +03:00
if ( ! File . Exists ( path ) ) {
2016-06-17 18:21:18 +03:00
Log ( "Will try in Xamarin Studio.app." , path ) ;
path = "/Applications/Xamarin Studio.app/Contents/Resources/lib/monodevelop/AddIns/MonoDevelop.IPhone/mlaunch.app/Contents/MacOS/mlaunch" ;
2016-06-07 19:49:20 +03:00
}
if ( ! File . Exists ( path ) )
throw new FileNotFoundException ( string . Format ( "Could not find mlaunch: {0}" , path ) ) ;
2016-06-17 18:21:18 +03:00
Log ( "Found mlaunch: {0}" , path ) ;
2016-06-07 19:49:20 +03:00
mlaunch = path ;
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
}
}
public static string Quote ( string f )
{
if ( f . IndexOf ( ' ' ) = = - 1 & & f . IndexOf ( '\'' ) = = - 1 & & f . IndexOf ( ',' ) = = - 1 )
return f ;
var s = new StringBuilder ( ) ;
s . Append ( '"' ) ;
foreach ( var c in f ) {
if ( c = = '"' | | c = = '\\' )
s . Append ( '\\' ) ;
s . Append ( c ) ;
}
s . Append ( '"' ) ;
return s . ToString ( ) ;
}
void CreateBCLProjects ( )
{
foreach ( var bclTest in BclTests ) {
var target = new BCLTarget ( ) {
Harness = this ,
MonoPath = MONO_PATH ,
WatchMonoPath = WATCH_MONO_PATH ,
TestName = bclTest ,
} ;
target . Convert ( ) ;
}
}
2016-06-10 21:56:48 +03:00
void AutoConfigureCommon ( )
{
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 ;
INCLUDE_WATCH = make_config . ContainsKey ( "INCLUDE_WATCH" ) & & ! string . IsNullOrEmpty ( make_config [ "INCLUDE_WATCH" ] ) ;
JENKINS_RESULTS_DIRECTORY = make_config [ "JENKINS_RESULTS_DIRECTORY" ] ;
2016-06-13 07:20:33 +03:00
MAC_DESTDIR = make_config [ "MAC_DESTDIR" ] ;
IOS_DESTDIR = make_config [ "IOS_DESTDIR" ] ;
2016-06-10 21:56:48 +03:00
}
2016-05-26 16:06:52 +03:00
void AutoConfigureMac ( )
{
var test_suites = new string [ ] { "apitest" , "dontlink-mac" } ;
var hard_coded_test_suites = new string [ ] { "mmptest" , "msbuild-mac" } ;
//var library_projects = new string[] { "BundledResources", "EmbeddedResources", "bindings-test", "bindings-framework-test" };
//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" };
foreach ( var p in test_suites )
2016-06-10 21:56:48 +03:00
MacTestProjects . Add ( new TestProject ( Path . GetFullPath ( Path . Combine ( RootDirectory , p + "/" + p + ".csproj" ) ) ) ) ;
MacTestProjects . Add ( new TestProject ( Path . GetFullPath ( Path . Combine ( RootDirectory , "introspection" , "Mac" , "introspection-mac.csproj" ) ) ) ) ;
2016-05-26 16:06:52 +03:00
foreach ( var p in hard_coded_test_suites )
2016-06-10 21:56:48 +03:00
MacTestProjects . Add ( new TestProject ( Path . GetFullPath ( Path . Combine ( RootDirectory , p + "/" + p + ".csproj" ) ) , generateVariations : false ) ) ;
2016-05-26 16:06:52 +03:00
//foreach (var p in fsharp_test_suites)
// TestProjects.Add (Path.GetFullPath (Path.Combine (RootDirectory, p + "/" + p + ".fsproj")));
//foreach (var p in library_projects)
2016-06-10 21:56:48 +03:00
//TestProjects.Add (Path.GetFullPath (Path.Combine (RootDirectory, p + "/" + p + ".csproj")));
2016-05-26 16:06:52 +03:00
//foreach (var p in fsharp_library_projects)
2016-06-10 21:56:48 +03:00
//TestProjects.Add (Path.GetFullPath (Path.Combine (RootDirectory, p + "/" + p + ".fsproj")));
2016-05-26 16:06:52 +03:00
//foreach (var p in bcl_suites)
2016-06-10 21:56:48 +03:00
//TestProjects.Add (Path.GetFullPath (Path.Combine (RootDirectory, "bcl-test/" + p + "/" + p + ".csproj")));
2016-05-26 16:06:52 +03:00
// BclTests.AddRange (bcl_suites);
2016-06-10 21:56:48 +03:00
AutoConfigureCommon ( ) ;
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
{
2016-06-09 00:39:47 +03:00
var test_suites = new string [ ] { "monotouch-test" , "framework-test" , "mini" } ;
2016-05-26 16:06:52 +03:00
var library_projects = new string [ ] { "BundledResources" , "EmbeddedResources" , "bindings-test" , "bindings-framework-test" } ;
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 ) ) ;
2016-05-26 16:06:52 +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" ) ) ) ) ;
IOSTestProjects . Add ( new TestProject ( Path . GetFullPath ( Path . Combine ( RootDirectory , "introspection" , "iOS" , "introspection-ios.csproj" ) ) ) ) ;
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-05-26 16:06:52 +03:00
BclTests . AddRange ( bcl_suites ) ;
WatchOSContainerTemplate = Path . GetFullPath ( Path . Combine ( RootDirectory , "watchos/Container" ) ) ;
WatchOSAppTemplate = Path . GetFullPath ( Path . Combine ( RootDirectory , "watchos/App" ) ) ;
WatchOSExtensionTemplate = Path . GetFullPath ( Path . Combine ( RootDirectory , "watchos/Extension" ) ) ;
2016-06-10 21:56:48 +03:00
AutoConfigureCommon ( ) ;
2016-05-26 16:06:52 +03:00
}
static Dictionary < string , string > make_config = new Dictionary < string , string > ( ) ;
static IEnumerable < string > FindConfigFiles ( string name )
{
var dir = Environment . CurrentDirectory ;
while ( dir ! = "/" ) {
var file = Path . Combine ( dir , name ) ;
if ( File . Exists ( file ) )
yield return file ;
dir = Path . GetDirectoryName ( dir ) ;
}
}
static void ParseConfigFiles ( )
{
ParseConfigFiles ( FindConfigFiles ( "test.config" ) ) ;
ParseConfigFiles ( FindConfigFiles ( "Make.config.local" ) ) ;
ParseConfigFiles ( FindConfigFiles ( "Make.config" ) ) ;
}
static void ParseConfigFiles ( IEnumerable < string > files )
{
foreach ( var file in files )
ParseConfigFile ( file ) ;
}
static void ParseConfigFile ( string file )
{
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 ( ) ;
CreateBCLProjects ( ) ;
2016-06-10 21:56:48 +03:00
foreach ( var proj in MacTestProjects . Where ( ( v ) = > v . GenerateVariations ) ) {
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 unifiedMobile = new MacUnifiedTarget ( true ) {
TemplateProjectPath = file ,
Harness = this ,
} ;
unifiedMobile . Execute ( ) ;
unified_targets . Add ( unifiedMobile ) ;
var unifiedXM45 = new MacUnifiedTarget ( false ) {
TemplateProjectPath = file ,
Harness = this ,
} ;
unifiedXM45 . Execute ( ) ;
unified_targets . Add ( unifiedXM45 ) ;
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-05-26 16:06:52 +03:00
var unifiedMobile = new MacUnifiedTarget ( true , true )
{
TemplateProjectPath = file ,
Harness = this ,
} ;
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 > ( ) ;
RootDirectory = Path . GetFullPath ( RootDirectory ) . TrimEnd ( '/' ) ;
if ( AutoConf )
2016-06-10 21:56:48 +03:00
AutoConfigureIOS ( ) ;
2016-05-26 16:06:52 +03:00
CreateBCLProjects ( ) ;
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 ) ;
}
SolutionGenerator . CreateSolution ( this , watchos_targets , "watchos" ) ;
SolutionGenerator . CreateSolution ( this , tvos_targets , "tvos" ) ;
2016-09-21 23:55:10 +03:00
MakefileGenerator . CreateMakefile ( this , unified_targets , tvos_targets , watchos_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
} ;
2016-06-17 18:21:18 +03:00
var rv = runner . Install ( HarnessLog ) ;
2016-05-26 16:06:52 +03:00
if ( rv ! = 0 )
return rv ;
}
return 0 ;
}
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 {
return ! string . IsNullOrEmpty ( Environment . GetEnvironmentVariable ( "BUILD_REVISION" ) ) ;
}
}
public int Execute ( )
{
switch ( Action ) {
case HarnessAction . Configure :
return Configure ( ) ;
case HarnessAction . Run :
return Run ( ) ;
case HarnessAction . Install :
return Install ( ) ;
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 , TextWriter output , TimeSpan timeout )
{
return ProcessHelper . ExecuteCommandAsync ( Path . Combine ( XcodeRoot , "Contents" , "Developer" , "usr" , "bin" , executable ) , args , output , timeout : timeout ) ;
}
public Task < ProcessExecutionResult > ExecuteXcodeCommandAsync ( string executable , string args , Log log , TimeSpan timeout )
{
return ProcessHelper . ExecuteCommandAsync ( Path . Combine ( XcodeRoot , "Contents" , "Developer" , "usr" , "bin" , executable ) , args , log . GetWriter ( ) , timeout : timeout ) ;
}
public async Task ShowSimulatorList ( LogStream log )
{
await ExecuteXcodeCommandAsync ( "simctl" , "list" , log . GetWriter ( ) , TimeSpan . FromSeconds ( 10 ) ) ;
}
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 ;
}
var symbolicated = new LogFile ( "Symbolicated crash report" , report . Path + ".symbolicated" ) ;
var environment = new Dictionary < string , string > { { "DEVELOPER_DIR" , Path . Combine ( XcodeRoot , "Contents" , "Developer" ) } } ;
var rv = await ProcessHelper . ExecuteCommandAsync ( symbolicatecrash , Quote ( report . Path ) , symbolicated , TimeSpan . FromMinutes ( 1 ) , environment ) ;
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 ( ) ;
sb . Append ( " --list-crash-reports=" ) . Append ( Quote ( tmp ) ) ;
sb . Append ( " --sdkroot " ) . Append ( Quote ( XcodeRoot ) ) ;
if ( ! string . IsNullOrEmpty ( device ) )
sb . Append ( " --devname " ) . Append ( Quote ( device ) ) ;
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 ( ) ;
sb . Append ( " --download-crash-report=" ) . Append ( Harness . Quote ( file ) ) ;
sb . Append ( " --download-crash-report-to=" ) . Append ( Harness . Quote ( crash_report_target . Path ) ) ;
sb . Append ( " --sdkroot " ) . Append ( Harness . Quote ( Harness . XcodeRoot ) ) ;
if ( ! string . IsNullOrEmpty ( DeviceName ) )
sb . Append ( " --devname " ) . Append ( Harness . Quote ( DeviceName ) ) ;
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
}