310 строки
12 KiB
C#
310 строки
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Xml;
|
|
|
|
using NUnit.Framework;
|
|
|
|
using Xamarin;
|
|
using Xamarin.Tests;
|
|
using Xamarin.Utils;
|
|
|
|
public static class ProcessHelper
|
|
{
|
|
static int counter;
|
|
|
|
static string log_directory;
|
|
static string LogDirectory {
|
|
get {
|
|
if (log_directory == null)
|
|
log_directory = Cache.CreateTemporaryDirectory ("execution-logs");
|
|
return log_directory;
|
|
}
|
|
|
|
}
|
|
|
|
public static void AssertRunProcess (string filename, string[] arguments, TimeSpan timeout, string workingDirectory, Dictionary<string, string> environment_variables, string message)
|
|
{
|
|
AssertRunProcess (filename, arguments, timeout, workingDirectory, environment_variables, message, out _);
|
|
}
|
|
|
|
public static void AssertRunProcess (string filename, string[] arguments, TimeSpan timeout, string workingDirectory, Dictionary<string, string> environment_variables, string message, out string logfile)
|
|
{
|
|
var exitCode = 0;
|
|
var output = new List<string> ();
|
|
|
|
Action<string> output_callback = (v) => {
|
|
lock (output)
|
|
output.Add ($"{DateTime.Now.ToString ("HH:mm:ss.fffffff")}: {v}");
|
|
};
|
|
|
|
if (environment_variables == null)
|
|
environment_variables = new Dictionary<string, string> ();
|
|
environment_variables ["XCODE_DEVELOPER_DIR_PATH"] = null;
|
|
environment_variables ["DEVELOPER_DIR"] = Configuration.XcodeLocation;
|
|
|
|
var watch = Stopwatch.StartNew ();
|
|
exitCode = ExecutionHelper.Execute (filename, arguments, out var timed_out, workingDirectory, environment_variables, output_callback, output_callback, timeout);
|
|
watch.Stop ();
|
|
|
|
output_callback ($"Exit code: {exitCode} Timed out: {timed_out} Total duration: {watch.Elapsed.ToString ()}");
|
|
|
|
// Write execution log to disk (and print the path)
|
|
logfile = Path.Combine (LogDirectory, $"{filename}-{Interlocked.Increment (ref counter)}.log");
|
|
File.WriteAllLines (logfile, output);
|
|
TestContext.AddTestAttachment (logfile, $"Execution log for {filename}");
|
|
Console.WriteLine ("Execution log for {0}: {1}", filename, logfile);
|
|
|
|
var errors = new List<string> ();
|
|
var errorMessage = "";
|
|
if ((!timed_out || exitCode != 0) && output.Count > 0) {
|
|
var regex = new Regex (@"error\s*(MSB....)?(CS....)?(MT....)?(MM....)?:", RegexOptions.IgnoreCase | RegexOptions.Singleline);
|
|
foreach (var line in output) {
|
|
if (regex.IsMatch (line) && !errors.Contains (line))
|
|
errors.Add (line);
|
|
}
|
|
if (errors.Count > 0)
|
|
errorMessage = "\n\t[Summary of errors from the build output below]\n\t" + string.Join ("\n\t", errors);
|
|
}
|
|
Assert.IsFalse (timed_out, $"{message} timed out after {timeout.TotalMinutes} minutes{errorMessage}");
|
|
Assert.AreEqual (0, exitCode, $"{message} failed (unexpected exit code){errorMessage}");
|
|
}
|
|
|
|
public static void BuildSolution (string solution, string platform, string configuration, Dictionary<string, string> environment_variables, TimeSpan timeout, string target = "", string codesignKey = null)
|
|
{
|
|
// nuget restore
|
|
var solution_dir = string.Empty;
|
|
var solutions = new string [] { solution };
|
|
var nuget_args = new List<string> ();
|
|
nuget_args.Add ("restore");
|
|
nuget_args.Add ("sln"); // replaced later
|
|
nuget_args.Add ("-Verbosity");
|
|
nuget_args.Add ("detailed");
|
|
var slndir = Path.GetDirectoryName (solution);
|
|
if (!solution.EndsWith (".sln", StringComparison.Ordinal)) {
|
|
while ((solutions = Directory.GetFiles (slndir, "*.sln", SearchOption.TopDirectoryOnly)).Length == 0 && slndir.Length > 1)
|
|
slndir = Path.GetDirectoryName (slndir);
|
|
nuget_args.Add ("-SolutionDir");
|
|
nuget_args.Add (slndir);
|
|
}
|
|
|
|
foreach (var sln in solutions) {
|
|
nuget_args [1] = sln; // replacing here
|
|
AssertRunProcess ("nuget", nuget_args.ToArray (), timeout, Configuration.SampleRootDirectory, environment_variables, "nuget restore");
|
|
}
|
|
|
|
// msbuild
|
|
var sb = new List<string> ();
|
|
sb.Add ("/verbosity:diag");
|
|
if (!string.IsNullOrEmpty (platform))
|
|
sb.Add ($"/p:Platform={platform}");
|
|
if (!string.IsNullOrEmpty (configuration))
|
|
sb.Add ($"/p:Configuration={configuration}");
|
|
sb.Add (solution);
|
|
if (!string.IsNullOrEmpty (target))
|
|
sb.Add ($"/t:{target}");
|
|
if (!string.IsNullOrEmpty (codesignKey))
|
|
sb.Add ($"/p:CodesignKey={codesignKey}");
|
|
|
|
environment_variables ["MTOUCH_ENV_OPTIONS"] = "--time --time --time --time -vvvv";
|
|
environment_variables ["MMP_ENV_OPTIONS"] = "--time --time --time --time -vvvv";
|
|
|
|
var watch = Stopwatch.StartNew ();
|
|
var failed = false;
|
|
string msbuild_logfile;
|
|
try {
|
|
AssertRunProcess ("msbuild", sb.ToArray (), timeout, Configuration.SampleRootDirectory, environment_variables, "build", out msbuild_logfile);
|
|
} catch {
|
|
failed = true;
|
|
throw;
|
|
} finally {
|
|
watch.Stop ();
|
|
}
|
|
|
|
// Write performance data to disk
|
|
var subdirs = Directory.GetDirectories (slndir, "*", SearchOption.AllDirectories);
|
|
|
|
// First figure out which .app subdirectory is the actual .app. This is a bit more complicated than it would seem...
|
|
var apps = subdirs.Where ((v) => {
|
|
var names = v.Substring (slndir.Length).Split (Path.DirectorySeparatorChar);
|
|
if (names.Length < 2)
|
|
return false;
|
|
|
|
if (!names [names.Length - 1].EndsWith (".app", StringComparison.Ordinal))
|
|
return false;
|
|
|
|
if (names.Any ((v2) => v2 == "copySceneKitAssets"))
|
|
return false;
|
|
|
|
var bin_idx = Array.IndexOf (names, "bin");
|
|
var conf_idx = Array.IndexOf (names, configuration);
|
|
if (bin_idx < 0 || conf_idx < 0)
|
|
return false;
|
|
|
|
if (bin_idx > conf_idx)
|
|
return false;
|
|
|
|
if (platform.Length > 0) {
|
|
var platform_idx = Array.IndexOf (names, platform);
|
|
if (platform_idx < 0)
|
|
return false;
|
|
|
|
if (bin_idx > platform_idx)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}).ToArray ();
|
|
|
|
if (apps.Length > 1) {
|
|
// Found more than one .app subdirectory, use additional logic to choose between them.
|
|
var filtered_apps = apps.Where ((v) => {
|
|
// If one .app is a subdirectory of another .app, we don't care about the former.
|
|
if (apps.Any ((v2) => v2.Length < v.Length && v.StartsWith (v2, StringComparison.Ordinal)))
|
|
return false;
|
|
|
|
// If one .app is contained within another .app, we don't care about the former.
|
|
var vname = Path.GetFileName (v);
|
|
var otherApps = apps.Where ((v2) => v != v2);
|
|
if (otherApps.Any ((v2) => {
|
|
var otherSubdirs = subdirs.Where ((v3) => v3.StartsWith (v2, StringComparison.Ordinal));
|
|
return otherSubdirs.Any ((v3) => Path.GetFileName (v3) == vname);
|
|
}))
|
|
return false;
|
|
|
|
return true;
|
|
}).ToArray ();
|
|
if (apps.Length == 0)
|
|
Assert.Fail ($"Filtered away all the .apps, from:\n\t{string.Join ("\n\t", apps)}");
|
|
apps = filtered_apps;
|
|
}
|
|
|
|
if (apps.Length > 1) {
|
|
Assert.Fail ($"Found more than one .app directory:\n\t{string.Join ("\n\t", apps)}");
|
|
} else if (apps.Length == 0) {
|
|
Assert.Fail ($"Found no .app directories for platform: {platform} configuration: {configuration} target: {target}. All directories:\n\t{string.Join ("\n\t", subdirs)}");
|
|
}
|
|
|
|
var logfile = Path.Combine (LogDirectory, $"{Path.GetFileNameWithoutExtension (solution)}-perfdata-{Interlocked.Increment (ref counter)}.xml");
|
|
|
|
var xmlSettings = new XmlWriterSettings {
|
|
Indent = true,
|
|
};
|
|
var xml = XmlWriter.Create (logfile, xmlSettings);
|
|
xml.WriteStartDocument (true);
|
|
xml.WriteStartElement ("performance");
|
|
xml.WriteStartElement ("sample-build");
|
|
xml.WriteAttributeString ("mono-version", Configuration.MonoVersion);
|
|
xml.WriteAttributeString ("os-version", Configuration.OSVersion);
|
|
xml.WriteAttributeString ("xamarin-macios-hash", Configuration.TestedHash);
|
|
xml.WriteAttributeString ("sample-repository", Configuration.GetCurrentRemoteUrl (slndir));
|
|
xml.WriteAttributeString ("sample-hash", Configuration.GetCurrentHash (slndir));
|
|
xml.WriteAttributeString ("agent-machinename", Environment.GetEnvironmentVariable ("AGENT_MACHINENAME"));
|
|
xml.WriteAttributeString ("agent-name", Environment.GetEnvironmentVariable ("AGENT_NAME"));
|
|
foreach (var app in apps) {
|
|
xml.WriteStartElement ("test");
|
|
xml.WriteAttributeString ("name", TestContext.CurrentContext.Test.FullName);
|
|
xml.WriteAttributeString ("result", failed ? "failed" : "success");
|
|
if (platform.Length > 0)
|
|
xml.WriteAttributeString ("platform", platform);
|
|
xml.WriteAttributeString ("configuration", configuration);
|
|
if (!failed) {
|
|
xml.WriteAttributeString ("duration", watch.ElapsedTicks.ToString ());
|
|
xml.WriteAttributeString ("duration-formatted", watch.Elapsed.ToString ());
|
|
|
|
var files = Directory.GetFiles (app, "*", SearchOption.AllDirectories).OrderBy ((v) => v).ToArray ();
|
|
var lengths = files.Select ((v) => new FileInfo (v).Length).ToArray ();
|
|
var total_size = lengths.Sum ();
|
|
|
|
xml.WriteAttributeString ("total-size", total_size.ToString ());
|
|
var appstart = Path.GetDirectoryName (app).Length;
|
|
for (var i = 0; i < files.Length; i++) {
|
|
xml.WriteStartElement ("file");
|
|
xml.WriteAttributeString ("name", files [i].Substring (appstart + 1));
|
|
xml.WriteAttributeString ("size", lengths [i].ToString ());
|
|
xml.WriteEndElement ();
|
|
}
|
|
|
|
if (File.Exists (msbuild_logfile)) {
|
|
var lines = File.ReadAllLines (msbuild_logfile);
|
|
var target_perf_summary = new List<string> ();
|
|
var task_perf_summary = new List<string> ();
|
|
var timestamps = new List<string> ();
|
|
for (var i = lines.Length - 1; i >= 0; i--) {
|
|
if (lines [i].EndsWith ("Target Performance Summary:", StringComparison.Ordinal)) {
|
|
for (var k = i + 1; k < lines.Length && lines [k].EndsWith ("calls", StringComparison.Ordinal); k++) {
|
|
target_perf_summary.Add (lines [k].Substring (18).Trim ());
|
|
}
|
|
} else if (lines [i].EndsWith ("Task Performance Summary:", StringComparison.Ordinal)) {
|
|
for (var k = i + 1; k < lines.Length && lines [k].EndsWith ("calls", StringComparison.Ordinal); k++) {
|
|
task_perf_summary.Add (lines [k].Substring (18).Trim ());
|
|
}
|
|
} else if (lines [i].Contains ("!Timestamp")) {
|
|
timestamps.Add (lines [i]);
|
|
}
|
|
}
|
|
foreach (var tps in target_perf_summary) {
|
|
var split = tps.Split (new char [] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
|
xml.WriteStartElement ("target");
|
|
xml.WriteAttributeString ("name", split [2]);
|
|
xml.WriteAttributeString ("ms", split [0]);
|
|
xml.WriteAttributeString ("calls", split [3]);
|
|
xml.WriteEndElement ();
|
|
}
|
|
foreach (var tps in task_perf_summary) {
|
|
var split = tps.Split (new char [] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
|
xml.WriteStartElement ("task");
|
|
xml.WriteAttributeString ("name", split [2]);
|
|
xml.WriteAttributeString ("ms", split [0]);
|
|
xml.WriteAttributeString ("calls", split [3]);
|
|
xml.WriteEndElement ();
|
|
}
|
|
foreach (var ts in timestamps) {
|
|
// Sample line:
|
|
// 15:04:50.4609520: !Timestamp Setup: 28 ms (TaskId:137)
|
|
var splitFirst = ts.Split (new char [] { ':' }, StringSplitOptions.RemoveEmptyEntries);
|
|
var splitSecondA = splitFirst [3].Split (new char [] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
|
var splitSecondB = splitFirst [4].Split (new char [] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
|
var name = string.Join (" ", splitSecondA.Skip (1));
|
|
var level = splitSecondA [0].Count ((v) => v == '!').ToString ();
|
|
var ms = splitSecondB [0];
|
|
xml.WriteStartElement ("timestamp");
|
|
xml.WriteAttributeString ("name", name);
|
|
xml.WriteAttributeString ("level", level);
|
|
xml.WriteAttributeString ("ms", ms);
|
|
xml.WriteEndElement ();
|
|
}
|
|
}
|
|
|
|
xml.WriteEndElement ();
|
|
}
|
|
|
|
xml.WriteEndElement (); // sample-build
|
|
xml.WriteEndElement (); // performance
|
|
xml.WriteEndDocument ();
|
|
xml.Dispose ();
|
|
|
|
TestContext.AddTestAttachment (logfile, $"Performance data");
|
|
}
|
|
}
|
|
|
|
internal static string RunProcess (string filename, string arguments = "", string working_directory = null)
|
|
{
|
|
using (var p = Process.Start (filename, arguments)) {
|
|
p.StartInfo.RedirectStandardOutput = true;
|
|
p.StartInfo.UseShellExecute = false;
|
|
if (!string.IsNullOrEmpty (working_directory))
|
|
p.StartInfo.WorkingDirectory = working_directory;
|
|
p.Start ();
|
|
var output = p.StandardOutput.ReadToEnd ();
|
|
p.WaitForExit ();
|
|
return output;
|
|
}
|
|
}
|
|
}
|