diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidUpdateResourcesTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidUpdateResourcesTest.cs index bd2905b787d..d554b0019a9 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidUpdateResourcesTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidUpdateResourcesTest.cs @@ -27,11 +27,15 @@ public void RepetitiveBuild () b.Verbosity = Microsoft.Build.Framework.LoggerVerbosity.Diagnostic; b.ThrowOnBuildFailure = false; Assert.IsTrue (b.Build (proj), "first build failed"); + + b.AssertTargetSkipped ("_Sign"); Assert.IsTrue (b.Build (proj), "second build failed"); - Assert.IsTrue (b.Output.IsTargetSkipped ("_Sign"), "failed to skip some build"); + AssertBuild (b); + + b.AssertTargetIsBuilt ("_Sign"); proj.AndroidResources.First ().Timestamp = null; // means "always build" Assert.IsTrue (b.Build (proj), "third build failed"); - Assert.IsFalse (b.Output.IsTargetSkipped ("_Sign"), "incorrectly skipped some build"); + AssertBuild (b); } } @@ -85,10 +89,13 @@ public void DesignTimeBuild ([Values(false, true)] bool isRelease, [Values (fals b.Verbosity = LoggerVerbosity.Diagnostic; b.ThrowOnBuildFailure = false; b.Target = "Compile"; + b.Assertions.Add (new Assertion (o => o.Contains ("Skipping GetAdditionalResourcesFromAssemblies"), "failed to skip the downloading of files.")); Assert.IsTrue (b.Build (proj, doNotCleanupOnUpdate: true, parameters: new string [] { "DesignTimeBuild=true" }, environmentVariables: envVar), "first build failed"); - Assert.AreEqual (!useManagedParser, b.LastBuildOutput.Contains ("Skipping GetAdditionalResourcesFromAssemblies"), - "failed to skip the downloading of files."); + if (useManagedParser) + AssertBuildDidNotPass (b); + else + AssertBuild (b); var items = new List (); string first = null; if (!useManagedParser) { @@ -101,9 +108,10 @@ public void DesignTimeBuild ([Values(false, true)] bool isRelease, [Values (fals } WaitFor (1000); b.Target = "Build"; + b.AssertTargetIsBuilt ("_BuildAdditionalResourcesCache"); + b.Assertions.Add (new Assertion (o => o.Contains ($"Downloading {url}") || o.Contains ($"reusing existing archive: {zipPath}"))); Assert.IsTrue (b.Build (proj, doNotCleanupOnUpdate: true, parameters: new string [] { "DesignTimeBuild=false" }, environmentVariables: envVar), "second build failed"); - Assert.IsFalse(b.Output.IsTargetSkipped ("_BuildAdditionalResourcesCache"), "_BuildAdditionalResourcesCache should have run."); - Assert.IsTrue (b.LastBuildOutput.Contains ($"Downloading {url}") || b.LastBuildOutput.Contains ($"reusing existing archive: {zipPath}"), $"{url} should have been downloaded."); + AssertBuild (b); Assert.IsTrue (File.Exists (Path.Combine (extractedDir, "1", "content", "android-N", "aapt")), $"Files should have been extracted to {extractedDir}"); items.Clear (); if (!useManagedParser) { @@ -157,9 +165,10 @@ public void MoveResource () var oldpath = image.Include ().Replace ('\\', Path.DirectorySeparatorChar); image.Include = () => "Resources/drawable/NewImage.png"; image.Timestamp = DateTimeOffset.UtcNow.AddMinutes (1); + b.AssertTargetIsBuilt ("_Sign"); Assert.IsTrue (b.Build (proj), "Second build should have succeeded."); + AssertBuild (b); Assert.IsFalse (File.Exists (Path.Combine (b.ProjectDirectory, oldpath)), "XamarinProject.UpdateProjectFiles() failed to delete file"); - Assert.IsFalse (b.Output.IsTargetSkipped ("_Sign"), "incorrectly skipped some build"); } } @@ -170,8 +179,9 @@ public void ReportAaptErrorsInOriginalFileName () proj.LayoutMain = @"\n" + proj.LayoutMain; using (var b = CreateApkBuilder ("temp/ErroneousResource")) { b.ThrowOnBuildFailure = false; + b.Assertions.Add (new Assertion (o => o.Contains (string.Format ("Resources{0}layout{0}Main.axml", Path.DirectorySeparatorChar)) && o.Contains (": error "), "error with expected file name is not found")); Assert.IsFalse (b.Build (proj), "Build should have failed."); - Assert.IsTrue (b.LastBuildOutput.Split ('\n').Any (s => s.Contains (string.Format ("Resources{0}layout{0}Main.axml", Path.DirectorySeparatorChar)) && s.Contains (": error ")), "error with expected file name is not found"); + AssertBuild (b); Assert.IsTrue (b.Clean (proj), "Clean should have succeeded."); } } @@ -194,27 +204,18 @@ public void RepetiviteBuildUpdateSingleResource () b.ThrowOnBuildFailure = false; Assert.IsTrue (b.Build (proj), "First build was supposed to build without errors"); var firstBuildTime = b.LastBuildTime; + b.AssertTargetSkipped ("_GenerateAndroidResourceDir"); + b.AssertTargetSkipped ("_CompileJava"); Assert.IsTrue (b.Build (proj), "Second build was supposed to build without errors"); + AssertBuild (b); Assert.IsTrue (firstBuildTime > b.LastBuildTime, "Second build was supposed to be quicker than the first"); - Assert.IsTrue ( - b.Output.IsTargetSkipped ("_GenerateAndroidResourceDir"), - "The Target _GenerateAndroidResourceDir should have been skipped"); - Assert.IsTrue ( - b.Output.IsTargetSkipped ("_CompileJava"), - "The Target _CompileJava should have been skipped"); image1.Timestamp = DateTime.UtcNow; var layout = proj.AndroidResources.First (x => x.Include() == "Resources\\layout\\Main.axml"); layout.Timestamp = DateTime.UtcNow; + b.AssertTargetIsBuilt ("_GenerateAndroidResourceDir"); + b.AssertTargetSkipped ("_CompileJava"); + b.AssertTargetIsBuilt ("_CreateBaseApk"); Assert.IsTrue (b.Build (proj, doNotCleanupOnUpdate:true, saveProject: false), "Third build was supposed to build without errors"); - Assert.IsFalse ( - b.Output.IsTargetSkipped ("_GenerateAndroidResourceDir"), - "The Target _GenerateAndroidResourceDir should not have been skipped"); - Assert.IsTrue ( - b.Output.IsTargetSkipped ("_CompileJava"), - "The Target _CompileJava (2) should have been skipped"); - Assert.IsFalse ( - b.Output.IsTargetSkipped ("_CreateBaseApk"), - "The Target _CreateBaseApk should not have been skipped"); } } @@ -346,7 +347,9 @@ protected override void OnClick() proj.SetProperty (proj.ReleaseProperties, "JavaMaximumHeapSize", "1G"); using (var b = CreateApkBuilder (Path.Combine (projectPath))) { b.Verbosity = LoggerVerbosity.Diagnostic; + b.Assertions.Add (new Assertion (o => o.Contains ("AndroidResgen: Warning while updating Resource XML"), "Warning while processing resources should not have been raised.")); Assert.IsTrue (b.Build (proj), "Build should have succeeded."); + AssertBuildDidNotPass (b); var preferencesPath = Path.Combine (Root, projectPath, proj.IntermediateOutputPath, "res","xml","preferences.xml"); Assert.IsTrue (File.Exists (preferencesPath), "Preferences.xml should have been renamed to preferences.xml"); var doc = XDocument.Load (preferencesPath); @@ -368,8 +371,6 @@ protected override void OnClick() .FirstOrDefault (x => x.Attribute("name").Value == "colorAccent"); Assert.IsNotNull (item, "The Style should contain an Item"); Assert.AreEqual ("@color/deep_purple_A200", item.Value, "item value should be @color/deep_purple_A200"); - StringAssert.DoesNotContain ("AndroidResgen: Warning while updating Resource XML", b.LastBuildOutput, - "Warning while processing resources should not have been raised."); Assert.IsTrue (b.Clean (proj), "Clean should have succeeded."); } } @@ -499,21 +500,21 @@ public void TargetGenerateJavaDesignerForComponentIsSkipped ([Values(false, true proj.SetProperty ("TargetFrameworkVersion", "v5.0"); using (var b = CreateApkBuilder (Path.Combine ("temp", TestContext.CurrentContext.Test.Name))) { b.Verbosity = LoggerVerbosity.Diagnostic; + b.AssertTargetIsBuilt ("_GenerateJavaDesignerForComponent"); Assert.IsTrue (b.Build (proj), "Build should have succeeded."); - StringAssert.DoesNotContain ("Skipping target \"_GenerateJavaDesignerForComponent\" because", - b.LastBuildOutput, "Target _GenerateJavaDesignerForComponent should not have been skipped"); + AssertBuild (b); + b.AssertAllTargetsSkipped ("_GenerateJavaDesignerForComponent"); Assert.IsTrue (b.Build (proj), "Build should have succeeded."); - StringAssert.Contains ("Skipping target \"_GenerateJavaDesignerForComponent\" because", - b.LastBuildOutput, "Target _GenerateJavaDesignerForComponent should have been skipped"); + AssertBuild (b); var files = Directory.EnumerateFiles (Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath, "resourcecache") , "abc_fade_in.xml", SearchOption.AllDirectories); Assert.AreEqual (1, files.Count (), "There should only be one abc_fade_in.xml in the resourcecache"); var resFile = files.First (); Assert.IsTrue (File.Exists (resFile), "{0} should exist", resFile); File.SetLastWriteTime (resFile, DateTime.UtcNow); + b.AssertTargetIsBuilt ("_GenerateJavaDesignerForComponent"); Assert.IsTrue (b.Build (proj), "Build should have succeeded."); - StringAssert.DoesNotContain ("Skipping target \"_GenerateJavaDesignerForComponent\" because", - b.LastBuildOutput, "Target _GenerateJavaDesignerForComponent should not have been skipped"); + AssertBuild (b); } } @@ -539,9 +540,10 @@ public void CheckAaptErrorRaisedForMissingResource () using (var b = CreateApkBuilder (Path.Combine (projectPath, "UnamedApp"), false, false)) { b.Verbosity = LoggerVerbosity.Diagnostic; b.ThrowOnBuildFailure = false; + b.Assertions.Add (new Assertion (o => o.Contains ("APT0000: "))); + b.Assertions.Add (new Assertion (o => o.Contains ("2 Error(s)"))); Assert.IsFalse (b.Build (proj), "Build should have failed"); - StringAssert.Contains ("APT0000: ", b.LastBuildOutput); - StringAssert.Contains ("2 Error(s)", b.LastBuildOutput); + AssertBuild (b); } } @@ -558,9 +560,10 @@ public void CheckAaptErrorRaisedForInvalidDirectoryName () using (var b = CreateApkBuilder (Path.Combine (projectPath, "UnamedApp"), false, false)) { b.Verbosity = LoggerVerbosity.Diagnostic; b.ThrowOnBuildFailure = false; + b.Assertions.Add (new Assertion (o => o.Contains ("APT0000: "))); + b.Assertions.Add (new Assertion (o => o.Contains ("1 Error(s)"))); Assert.IsFalse (b.Build (proj), "Build should have failed"); - StringAssert.Contains ("APT0000: ", b.LastBuildOutput); - StringAssert.Contains ("1 Error(s)", b.LastBuildOutput); + AssertBuild (b); } } @@ -575,9 +578,10 @@ public void CheckAaptErrorRaisedForInvalidFileName () using (var b = CreateApkBuilder (Path.Combine (projectPath, "UnamedApp"), false, false)) { b.Verbosity = LoggerVerbosity.Diagnostic; b.ThrowOnBuildFailure = false; + b.Assertions.Add (new Assertion (o => o.Contains ("Invalid file name:"))); + b.Assertions.Add (new Assertion (o => o.Contains ("1 Error(s)"))); Assert.IsFalse (b.Build (proj), "Build should have failed"); - StringAssert.Contains ("Invalid file name:", b.LastBuildOutput); - StringAssert.Contains ("1 Error(s)", b.LastBuildOutput); + AssertBuild (b); } } @@ -597,9 +601,11 @@ public void CheckAaptErrorRaisedForDuplicateResourceinApp () using (var b = CreateApkBuilder (Path.Combine (projectPath, "UnamedApp"), false, false)) { b.Verbosity = LoggerVerbosity.Diagnostic; b.ThrowOnBuildFailure = false; + + b.Assertions.Add (new Assertion (o => o.Contains ("APT0000: "))); + b.Assertions.Add (new Assertion (o => o.Contains ("2 Error(s)"))); Assert.IsFalse (b.Build (proj), "Build should have failed"); - StringAssert.Contains ("APT0000: ", b.LastBuildOutput); - StringAssert.Contains ("2 Error(s)", b.LastBuildOutput); + AssertBuild (b); } } @@ -916,25 +922,30 @@ public string GetFoo () { Assert.IsTrue (libBuilder.Build (libProj), "Library project should have built"); using (var appBuilder = CreateApkBuilder (Path.Combine (path, appProj.ProjectName), false, false)) { appBuilder.Verbosity = LoggerVerbosity.Diagnostic; + appBuilder.AssertTargetIsBuilt ("_UpdateAndroidResgen"); Assert.IsTrue (appBuilder.Build (appProj), "Application Build should have succeeded."); - Assert.IsFalse (appBuilder.Output.IsTargetSkipped ("_UpdateAndroidResgen"), "_UpdateAndroidResgen target not should be skipped."); + AssertBuild (appBuilder); foo.Timestamp = DateTime.UtcNow; + libBuilder.AssertTargetSkipped ("_AddLibraryProjectsEmbeddedResourceToProject"); Assert.IsTrue (libBuilder.Build (libProj, doNotCleanupOnUpdate: true, saveProject: false), "Library project should have built"); - Assert.IsTrue (libBuilder.Output.IsTargetSkipped ("_AddLibraryProjectsEmbeddedResourceToProject"), "_AddLibraryProjectsEmbeddedResourceToProject should be skipped."); + AssertBuild (libBuilder); + appBuilder.AssertTargetSkipped ("_UpdateAndroidResgen"); Assert.IsTrue (appBuilder.Build (appProj, doNotCleanupOnUpdate: true, saveProject: false), "Application Build should have succeeded."); - Assert.IsTrue (appBuilder.Output.IsTargetSkipped ("_UpdateAndroidResgen"), "_UpdateAndroidResgen target should be skipped."); + AssertBuild (appBuilder); theme.TextContent = () => @" #00000000 #ffffffff "; theme.Timestamp = DateTime.UtcNow; + libBuilder.AssertTargetIsBuilt ("_AddLibraryProjectsEmbeddedResourceToProject"); Assert.IsTrue (libBuilder.Build (libProj, doNotCleanupOnUpdate: true, saveProject: false), "Library project should have built"); - Assert.IsFalse (libBuilder.Output.IsTargetSkipped ("_AddLibraryProjectsEmbeddedResourceToProject"), "_AddLibraryProjectsEmbeddedResourceToProject should not be skipped."); + AssertBuild (libBuilder); + appBuilder.AssertTargetIsBuilt ("_UpdateAndroidResgen"); Assert.IsTrue (appBuilder.Build (appProj, doNotCleanupOnUpdate: true, saveProject: false), "Application Build should have succeeded."); + AssertBuild (appBuilder); string text = File.ReadAllText (Path.Combine (Root, path, appProj.ProjectName, "Resources", "Resource.designer.cs")); Assert.IsTrue (text.Contains ("theme_devicedefault_background2"), "Resource.designer.cs was not updated."); - Assert.IsFalse (appBuilder.Output.IsTargetSkipped ("_UpdateAndroidResgen"), "_UpdateAndroidResgen target should NOT be skipped."); theme.Deleted = true; theme.Timestamp = DateTime.UtcNow; Assert.IsTrue (libBuilder.Build (libProj, saveProject: true), "Library project should have built"); @@ -958,10 +969,10 @@ public void BuildAppWithManagedResourceParser() using (var appBuilder = CreateApkBuilder (Path.Combine (path, appProj.ProjectName))) { appBuilder.Verbosity = LoggerVerbosity.Diagnostic; appBuilder.Target = "Compile"; + appBuilder.AssertTargetIsBuilt ("_ManagedUpdateAndroidResgen"); Assert.IsTrue (appBuilder.Build (appProj, parameters: new string[] { "DesignTimeBuild=true", "BuildingInsideVisualStudio=true" } ), "DesignTime Application Build should have succeeded."); - Assert.IsFalse (appProj.CreateBuildOutput (appBuilder).IsTargetSkipped ("_ManagedUpdateAndroidResgen"), - "Target '_ManagedUpdateAndroidResgen' should have run."); + AssertBuild (appBuilder); var designerFile = Path.Combine (Root, path, appProj.ProjectName, appProj.IntermediateOutputPath, "designtime", "Resource.Designer.cs"); FileAssert.Exists (designerFile, $"'{designerFile}' should have been created."); @@ -972,14 +983,13 @@ public void BuildAppWithManagedResourceParser() StringAssert.Contains ("Icon", designerContents, $"{designerFile} should contain Resources.Drawable.Icon"); StringAssert.Contains ("Main", designerContents, $"{designerFile} should contain Resources.Layout.Main"); appBuilder.Target = "SignAndroidPackage"; + appBuilder.AssertTargetSkipped ("_ManagedUpdateAndroidResgen"); Assert.IsTrue (appBuilder.Build (appProj), "Normal Application Build should have succeeded."); - Assert.IsTrue (appProj.CreateBuildOutput (appBuilder).IsTargetSkipped ("_ManagedUpdateAndroidResgen"), - "Target '_ManagedUpdateAndroidResgen' should not have run."); + AssertBuild (appBuilder); Assert.IsTrue (appBuilder.Clean (appProj), "Clean should have succeeded"); Assert.IsFalse (File.Exists (designerFile), $"'{designerFile}' should have been cleaned."); - } } @@ -1028,15 +1038,15 @@ public void BuildAppWithManagedResourceParserAndLibraries () appBuilder.Verbosity = LoggerVerbosity.Diagnostic; appBuilder.ThrowOnBuildFailure = false; libBuilder.Target = "Compile"; + libBuilder.AssertTargetIsBuilt ("_ManagedUpdateAndroidResgen"); Assert.IsTrue (libBuilder.Build (libProj, parameters: new string [] { "DesignTimeBuild=true", "BuildingInsideVisualStudio=true" }), "Library project should have built"); Assert.LessOrEqual (libBuilder.LastBuildTime.TotalMilliseconds, maxBuildTimeMs, $"DesignTime build should be less than {maxBuildTimeMs} milliseconds."); - Assert.IsFalse (libProj.CreateBuildOutput (libBuilder).IsTargetSkipped ("_ManagedUpdateAndroidResgen"), - "Target '_ManagedUpdateAndroidResgen' should have run."); + AssertBuild (libBuilder); appBuilder.Target = "Compile"; - Assert.AreEqual (!appBuilder.RunningMSBuild, appBuilder.Build (appProj, parameters: new string [] { "DesignTimeBuild=true", "BuildingInsideVisualStudio=true" }), "Application project should have built"); + appBuilder.AssertTargetIsBuilt ("_ManagedUpdateAndroidResgen"); + Assert.IsTrue(appBuilder.Build (appProj, parameters: new string [] { "DesignTimeBuild=true", "BuildingInsideVisualStudio=true" }), "Application project should have built"); Assert.LessOrEqual (appBuilder.LastBuildTime.TotalMilliseconds, maxBuildTimeMs, $"DesignTime build should be less than {maxBuildTimeMs} milliseconds."); - Assert.IsFalse (appProj.CreateBuildOutput (appBuilder).IsTargetSkipped ("_ManagedUpdateAndroidResgen"), - "Target '_ManagedUpdateAndroidResgen' should have run."); + AssertBuild (appBuilder); var designerFile = Path.Combine (Root, path, appProj.ProjectName, appProj.IntermediateOutputPath, "designtime", "Resource.Designer.cs"); FileAssert.Exists (designerFile, $"'{designerFile}' should have been created."); @@ -1049,14 +1059,14 @@ public void BuildAppWithManagedResourceParserAndLibraries () StringAssert.Contains ("material_grey_50", designerContents, $"{designerFile} should contain Resources.Color.material_grey_50"); StringAssert.DoesNotContain ("theme_devicedefault_background", designerContents, $"{designerFile} should not contain Resources.Color.theme_devicedefault_background"); libBuilder.Target = "Build"; + libBuilder.AssertTargetSkipped ("_ManagedUpdateAndroidResgen"); Assert.IsTrue (libBuilder.Build (libProj), "Library project should have built"); - Assert.IsTrue (libProj.CreateBuildOutput (libBuilder).IsTargetSkipped ("_ManagedUpdateAndroidResgen"), - "Target '_ManagedUpdateAndroidResgen' should not have run."); + AssertBuild (libBuilder); appBuilder.Target = "Compile"; + appBuilder.AssertTargetIsBuilt ("_ManagedUpdateAndroidResgen"); Assert.IsTrue (appBuilder.Build (appProj, parameters: new string [] { "DesignTimeBuild=true", "BuildingInsideVisualStudio=true" }), "App project should have built"); + AssertBuild (appBuilder); Assert.LessOrEqual (appBuilder.LastBuildTime.TotalMilliseconds, maxBuildTimeMs, $"DesignTime build should be less than {maxBuildTimeMs} milliseconds."); - Assert.IsFalse (appProj.CreateBuildOutput (appBuilder).IsTargetSkipped ("_ManagedUpdateAndroidResgen"), - "Target '_ManagedUpdateAndroidResgen' should have run."); FileAssert.Exists (designerFile, $"'{designerFile}' should have been created."); designerContents = File.ReadAllText (designerFile); @@ -1078,8 +1088,6 @@ public void BuildAppWithManagedResourceParserAndLibraries () designerFile = Path.Combine (Root, path, libProj.ProjectName, libProj.IntermediateOutputPath, "designtime", "Resource.Designer.cs"); Assert.IsTrue (libBuilder.Clean (libProj), "Clean should have succeeded"); Assert.IsFalse (File.Exists (designerFile), $"'{designerFile}' should have been cleaned."); - - } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/BaseTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/BaseTest.cs index 361e16fccbe..5f542c5c3ed 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/BaseTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/BaseTest.cs @@ -177,6 +177,26 @@ protected ProjectBuilder CreateDllBuilder (string directory, bool cleanupAfterSu return BuildHelper.CreateDllBuilder (directory, cleanupAfterSuccessfulBuild, cleanupOnDispose); } + protected void AssertBuild(ProjectBuilder builder) + { + foreach (var assertion in builder.Assertions) { + if (!assertion.Passed) { + Assert.Fail (assertion.ToString ()); + } + } + builder.Assertions.Clear (); + } + + protected void AssertBuildDidNotPass (ProjectBuilder builder) + { + foreach (var assertion in builder.Assertions) { + if (assertion.Passed) { + Assert.Fail (assertion.ToString ()); + } + } + builder.Assertions.Clear (); + } + [OneTimeSetUp] public void FixtureSetup () { @@ -215,8 +235,17 @@ protected virtual void CleanupTest () return; if (TestContext.CurrentContext.Result.Outcome.Status == NUnit.Framework.Interfaces.TestStatus.Passed || TestContext.CurrentContext.Result.Outcome.Status == NUnit.Framework.Interfaces.TestStatus.Skipped) { - FileSystemUtils.SetDirectoryWriteable (output); - Directory.Delete (output, recursive: true); + + do { + try { + FileSystemUtils.SetDirectoryWriteable (output); + Directory.Delete (output, recursive: true); + break; + } catch (IOException) { + //NOTE: seems to happen quite a bit on Windows + Thread.Sleep (25); + } + } while (true); } else { foreach (var file in Directory.GetFiles (Path.Combine (output), "build.log", SearchOption.AllDirectories)) { TestContext.Out.WriteLine ("*************************************************************************"); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/Assertion.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/Assertion.cs new file mode 100644 index 00000000000..245e939b28b --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/Assertion.cs @@ -0,0 +1,41 @@ +using System; +using System.Linq.Expressions; + +namespace Xamarin.ProjectTools +{ + /// + /// Represents an "assertion" that a condition is true as the build output streams through a callback + /// + public class Assertion + { + readonly Expression> expression; + Func func; + + public bool Passed { get; private set; } + + public string Message { get; private set; } + + public Assertion (Expression> expression, string message = null) + { + this.expression = expression; + Message = message; + } + + public void Assert(string line) + { + if (!Passed) { + if (func == null) + func = expression.Compile (); + Passed = func (line); + } + } + + public override string ToString () + { + if (!string.IsNullOrEmpty (Message)) + return Message; + + return $"Expression was false: {expression.Body}"; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/BuildOutput.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/BuildOutput.cs index 0bef5cd05c9..70cd326f2cd 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/BuildOutput.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/BuildOutput.cs @@ -44,30 +44,10 @@ public string GetIntermediaryAsText (string file) return File.ReadAllText (GetIntermediaryPath (file)); } - public bool IsTargetSkipped (string target) + //TODO: remove + public bool IsTargetSkipped (string a) { - if (!Builder.LastBuildOutput.Contains (target)) - throw new ArgumentException (string.Format ("Target '{0}' is not even in the build output.", target)); - return Builder.LastBuildOutput.Contains (string.Format ("Target {0} skipped due to ", target)) - || Builder.LastBuildOutput.Contains (string.Format ("Skipping target \"{0}\" because it has no outputs.", target)) - || Builder.LastBuildOutput.Contains (string.Format ("Target \"{0}\" skipped, due to", target)) - || Builder.LastBuildOutput.Contains (string.Format ("Skipping target \"{0}\" because its outputs are up-to-date", target)) - || Builder.LastBuildOutput.Contains (string.Format ("target {0}, skipping", target)) - || Builder.LastBuildOutput.Contains ($"Skipping target \"{target}\" because all output files are up-to-date"); - } - - public bool IsApkInstalled { - get { return Builder.LastBuildOutput.Contains (" pm install "); } - } - - public bool AreTargetsAllSkipped (params string [] targets) - { - return targets.All (t => IsTargetSkipped (t)); - } - - public bool AreTargetsAllBuilt (params string [] targets) - { - return targets.All (t => !IsTargetSkipped (t)); + throw new NotImplementedException (); } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/Builder.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/Builder.cs index 90741e1077f..8f57ef71d3d 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/Builder.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/Builder.cs @@ -14,11 +14,60 @@ public class Builder : IDisposable public bool IsUnix { get; set; } public bool RunningMSBuild { get; set; } public LoggerVerbosity Verbosity { get; set; } - public string LastBuildOutput { get; set; } public TimeSpan LastBuildTime { get; protected set; } public string BuildLogFile { get; set; } public bool ThrowOnBuildFailure { get; set; } + //TODO: remove + public string LastBuildOutput { + get { throw new NotImplementedException (); } + } + + public List Assertions { get; set; } = new List (); + + public void AssertTargetSkipped (string target) + { + Assertions.Add (new Assertion (o => o.Contains (target), $"Target '{target}' is not even in the build output.")); + + Assertions.Add (new Assertion (o => o.Contains ($"Target {target} skipped due to ") || + o.Contains ($"Skipping target \"{target}\" because it has no outputs.") || + o.Contains ($"Target \"{target}\" skipped, due to") || + o.Contains ($"Skipping target \"{target}\" because its outputs are up-to-date") || + o.Contains ($"target {target}, skipping") || + o.Contains ($"Skipping target \"{target}\" because all output files are up-to-date"), $"Target {target} was not skipped.")); + } + + public void AssertApkInstalled () + { + Assertions.Add (new Assertion (o => o.Contains (" pm install "))); + } + + public void AssertAllTargetsSkipped (params string [] targets) + { + foreach (var t in targets) { + AssertTargetSkipped (t); + } + } + + public void AssertTargetIsBuilt (string target) + { + Assertions.Add (new Assertion (o => o.Contains (target), $"Target '{target}' is not even in the build output.")); + + Assertions.Add (new Assertion (o => !o.Contains ($"Target {target} skipped due to ") && + !o.Contains ($"Skipping target \"{target}\" because it has no outputs.") && + !o.Contains ($"Target \"{target}\" skipped, due to") && + !o.Contains ($"Skipping target \"{target}\" because its outputs are up-to-date") && + !o.Contains ($"target {target}, skipping") && + !o.Contains ($"Skipping target \"{target}\" because all output files are up-to-date"), $"Target {target} was not built.")); + } + + public void AssertAllTargetsBuilt (params string [] targets) + { + foreach (var t in targets) { + AssertTargetIsBuilt (t); + } + } + string GetVisualStudio2017Directory () { var editions = new [] { @@ -187,11 +236,6 @@ protected bool BuildInternal (string projectOrSolution, string target, string [] ? Path.GetFullPath (Path.Combine (Root, Path.GetDirectoryName (projectOrSolution), BuildLogFile)) : null; - var logger = buildLogFullPath == null - ? string.Empty - : string.Format ("/noconsolelogger \"/flp1:LogFile={0};Encoding=UTF-8;Verbosity={1}\"", - buildLogFullPath, Verbosity.ToString ().ToLower ()); - var start = DateTime.UtcNow; var homeDirectory = Environment.GetFolderPath (Environment.SpecialFolder.Personal); var androidSdkToolPath = Path.Combine (homeDirectory, "android-toolchain"); @@ -208,8 +252,8 @@ protected bool BuildInternal (string projectOrSolution, string target, string [] var args = new StringBuilder (); var psi = new ProcessStartInfo (XABuildExe); - args.AppendFormat ("{0} /t:{1} {2}", - QuoteFileName(Path.Combine (Root, projectOrSolution)), target, logger); + args.AppendFormat ("{0} /t:{1} /v:{2}", + QuoteFileName(Path.Combine (Root, projectOrSolution)), target, Verbosity); if (RunningMSBuild) args.Append (" /p:BuildingOutOfProcess=true"); else @@ -244,57 +288,75 @@ protected bool BuildInternal (string projectOrSolution, string target, string [] bool nativeCrashDetected = false; bool result = false; + bool lastBuildTimeSet = false; int attempts = 1; for (int attempt = 0; attempt < attempts; attempt++) { - var p = Process.Start (psi); - var ranToCompletion = p.WaitForExit ((int)new TimeSpan (0, 10, 0).TotalMilliseconds); - result = ranToCompletion && p.ExitCode == 0; - - LastBuildTime = DateTime.UtcNow - start; - - var sb = new StringBuilder (); - sb.AppendLine (psi.FileName + " " + args.ToString () + Environment.NewLine); - if (!ranToCompletion) - sb.AppendLine ("Build Timed Out!"); - if (buildLogFullPath != null && File.Exists (buildLogFullPath)) { - using (var fs = File.OpenRead (buildLogFullPath)) { - using (var sr = new StreamReader (fs, Encoding.UTF8, true, 65536)) { - string line; - while ((line = sr.ReadLine ()) != null) { - sb.AppendLine (line); - if (line.StartsWith ("Time Elapsed", StringComparison.OrdinalIgnoreCase)) { - var match = timeElapsedRegEx.Match (line); - if (match.Success) { - LastBuildTime = TimeSpan.Parse (match.Groups ["TimeSpan"].Value); - Console.WriteLine ($"Found Time Elapsed {LastBuildTime}"); - } - } - if (line.StartsWith ("Got a SIGSEGV while executing native code", StringComparison.OrdinalIgnoreCase)) { - nativeCrashDetected = true; - break; + using (var p = new Process { StartInfo = psi }) { + StreamWriter file = null; + if (buildLogFullPath != null) { + Directory.CreateDirectory (Path.GetDirectoryName (buildLogFullPath)); + file = File.CreateText (buildLogFullPath); + } + try { + var standardError = new StringBuilder (); + file?.WriteLine ("#stdout begin"); + + p.OutputDataReceived += (sender, e) => { + if (e.Data == null) + return; + if (e.Data.StartsWith ("Time Elapsed", StringComparison.OrdinalIgnoreCase)) { + var match = timeElapsedRegEx.Match (e.Data); + if (match.Success) { + LastBuildTime = TimeSpan.Parse (match.Groups ["TimeSpan"].Value); + lastBuildTimeSet = true; + Console.WriteLine ($"Found Time Elapsed {LastBuildTime}"); } } + + if (e.Data.StartsWith ("Got a SIGSEGV while executing native code", StringComparison.OrdinalIgnoreCase)) { + nativeCrashDetected = true; + } + + foreach (var assertion in Assertions) { + assertion.Assert (e.Data); + } + + file?.WriteLine (e.Data); + }; + p.ErrorDataReceived += (sender, e) => standardError.AppendLine (e.Data); + + p.Start (); + p.BeginOutputReadLine (); + p.BeginErrorReadLine (); + + var ranToCompletion = p.WaitForExit ((int)new TimeSpan (0, 10, 0).TotalMilliseconds); + if (nativeCrashDetected) { + Console.WriteLine ($"Native crash detected! Running the build for {projectOrSolution} again."); + continue; } - } - } - sb.AppendFormat ("\n#stdout begin\n{0}\n#stdout end\n", p.StandardOutput.ReadToEnd ()); - sb.AppendFormat ("\n#stderr begin\n{0}\n#stderr end\n", p.StandardError.ReadToEnd ()); + result = ranToCompletion && p.ExitCode == 0; + if (!lastBuildTimeSet) + LastBuildTime = DateTime.UtcNow - start; - LastBuildOutput = sb.ToString (); - if (nativeCrashDetected) { - Console.WriteLine ($"Native crash detected! Running the build for {projectOrSolution} again."); - continue; + if (file != null) { + file.WriteLine (); + file.WriteLine ("#stdout end"); + file.WriteLine (); + file.WriteLine ("#stderr begin"); + file.WriteLine (standardError.ToString ()); + file.WriteLine ("#stderr end"); + } + } finally { + file?.Dispose (); + } } } - - if (buildLogFullPath != null) { - Directory.CreateDirectory (Path.GetDirectoryName (buildLogFullPath)); - File.WriteAllText (buildLogFullPath, LastBuildOutput); - } if (!result && ThrowOnBuildFailure) { string message = "Build failure: " + Path.GetFileName (projectOrSolution) + (BuildLogFile != null && File.Exists (buildLogFullPath) ? "Build log recorded at " + buildLogFullPath : null); - throw new FailedBuildException (message, null, LastBuildOutput); + + //TODO: do we really need the full build log here? It seems to lock up my VS test runner + throw new FailedBuildException (message, null); } return result; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Xamarin.ProjectTools.csproj b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Xamarin.ProjectTools.csproj index 0aa46a15113..42ab8d720de 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Xamarin.ProjectTools.csproj +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Xamarin.ProjectTools.csproj @@ -41,6 +41,7 @@ +