Skip to content

Commit

Permalink
[MSBuildDeviceIntegration] Handle Debugger errors (#7864)
Browse files Browse the repository at this point in the history
We occasionally see the following error on CI:

	Unhandled error launching soft debugger
	System.AggregateException: One or more errors occurred. (DWP Handshake failed.)
	 ---> System.IO.IOException: DWP Handshake failed.
	   at Mono.Debugger.Soft.Connection.Connect() in /Users/builder/azdo/_work/1/s/xamarin-android/external/debugger-libs/Mono.Debugger.Soft/Mono.Debugger.Soft/Connection.cs:line 1361
	   at Mono.Debugger.Soft.VirtualMachine.connect() in /Users/builder/azdo/_work/1/s/xamarin-android/external/debugger-libs/Mono.Debugger.Soft/Mono.Debugger.Soft/VirtualMachine.cs:line 381
	   at Mono.Debugger.Soft.VirtualMachineManager.Connect(Connection transport, StreamReader standardOutput, StreamReader standardError) in /Users/builder/azdo/_work/1/s/xamarin-android/external/debugger-libs/Mono.Debugger.Soft/Mono.Debugger.Soft/VirtualMachineManager.cs:line 353
	   at Mono.Debugger.Soft.VirtualMachineManager.ConnectInternalAsync(Socket dbg_sock, Socket con_sock, IPEndPoint dbg_ep, IPEndPoint con_ep) in /Users/builder/azdo/_work/1/s/xamarin-android/external/debugger-libs/Mono.Debugger.Soft/Mono.Debugger.Soft/VirtualMachineManager.cs:line 309
	   --- End of inner exception stack trace ---

What is supposed to happen is the test should be retried.  However,
according to [the NUnit docs][1], the `[Retry]` attribute won't work
if an unhandled exception occurs.

Fix this by catching all exceptions and calling `Assert.Fail()` when
an exception is caught.  This allows the `[Retry]` attribute to do
its job and retry the test.

[1]: https://docs.nunit.org/articles/nunit/writing-tests/attributes/retry.html
  • Loading branch information
dellis1972 authored Mar 14, 2023
1 parent 6508203 commit d174b9b
Show file tree
Hide file tree
Showing 2 changed files with 204 additions and 188 deletions.
295 changes: 152 additions & 143 deletions tests/MSBuildDeviceIntegration/Tests/DebuggingTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,64 +207,69 @@ public override void OnCreate ()
var sw = new Stopwatch ();
// setup the debugger
var session = new SoftDebuggerSession ();
session.Breakpoints = new BreakpointStore ();
string file = Path.Combine (Root, b.ProjectDirectory, "MainActivity.cs");
int line = FindTextInFile (file, "base.OnCreate (bundle);");
session.Breakpoints.Add (file, line);
file = Path.Combine (Root, b.ProjectDirectory, "MyApplication.cs");
line = FindTextInFile (file, "base.OnCreate ();");
session.Breakpoints.Add (file, line);
session.TargetHitBreakpoint += (sender, e) => {
TestContext.WriteLine ($"BREAK {e.Type}, {e.Backtrace.GetFrame (0)}");
breakcountHitCount++;
session.Continue ();
};
var rnd = new Random ();
int port = rnd.Next (10000, 20000);
TestContext.Out.WriteLine ($"{port}");
var args = new SoftDebuggerConnectArgs ("", IPAddress.Loopback, port) {
MaxConnectionAttempts = 2000, // we need a long delay here to get a reliable connection
};
var startInfo = new SoftDebuggerStartInfo (args) {
WorkingDirectory = Path.Combine (b.ProjectDirectory, proj.IntermediateOutputPath, "android", "assets"),
};
var options = new DebuggerSessionOptions () {
EvaluationOptions = EvaluationOptions.DefaultOptions,
};
options.EvaluationOptions.UseExternalTypeResolver = true;
RunProjectAndAssert (proj, b, doNotCleanupOnUpdate: true, parameters: new string [] {
$"AndroidSdbTargetPort={port}",
$"AndroidSdbHostPort={port}",
"AndroidAttachDebugger=True",
});

session.LogWriter += (isStderr, text) => { Console.WriteLine (text); };
session.OutputWriter += (isStderr, text) => { Console.WriteLine (text); };
session.DebugWriter += (level, category, message) => { Console.WriteLine (message); };
// do we expect the app to start?
Assert.AreEqual (activityStarts, WaitForDebuggerToStart (Path.Combine (Root, b.ProjectDirectory, "logcat.log")), "Debugger should have started");
if (!activityStarts)
return;
Assert.False (session.HasExited, "Target should not have exited.");
session.Run (startInfo, options);
var expectedTime = TimeSpan.FromSeconds (1);
var actualTime = ProfileFor (() => session.IsConnected);
Assert.True (session.IsConnected, "Debugger should have connected but it did not.");
TestContext.Out.WriteLine ($"Debugger connected in {actualTime}");
Assert.LessOrEqual (actualTime, expectedTime, $"Debugger should have connected within {expectedTime} but it took {actualTime}.");
// we need to wait here for a while to allow the breakpoints to hit
// but we need to timeout
TimeSpan timeout = TimeSpan.FromSeconds (60);
while (session.IsConnected && breakcountHitCount < 2 && timeout >= TimeSpan.Zero) {
Thread.Sleep (10);
timeout = timeout.Subtract (TimeSpan.FromMilliseconds (10));
try {
session.Breakpoints = new BreakpointStore ();
string file = Path.Combine (Root, b.ProjectDirectory, "MainActivity.cs");
int line = FindTextInFile (file, "base.OnCreate (bundle);");
session.Breakpoints.Add (file, line);
file = Path.Combine (Root, b.ProjectDirectory, "MyApplication.cs");
line = FindTextInFile (file, "base.OnCreate ();");
session.Breakpoints.Add (file, line);
session.TargetHitBreakpoint += (sender, e) => {
TestContext.WriteLine ($"BREAK {e.Type}, {e.Backtrace.GetFrame (0)}");
breakcountHitCount++;
session.Continue ();
};
var rnd = new Random ();
int port = rnd.Next (10000, 20000);
TestContext.Out.WriteLine ($"{port}");
var args = new SoftDebuggerConnectArgs ("", IPAddress.Loopback, port) {
MaxConnectionAttempts = 2000, // we need a long delay here to get a reliable connection
};
var startInfo = new SoftDebuggerStartInfo (args) {
WorkingDirectory = Path.Combine (b.ProjectDirectory, proj.IntermediateOutputPath, "android", "assets"),
};
var options = new DebuggerSessionOptions () {
EvaluationOptions = EvaluationOptions.DefaultOptions,
};
options.EvaluationOptions.UseExternalTypeResolver = true;
RunProjectAndAssert (proj, b, doNotCleanupOnUpdate: true, parameters: new string [] {
$"AndroidSdbTargetPort={port}",
$"AndroidSdbHostPort={port}",
"AndroidAttachDebugger=True",
});

session.LogWriter += (isStderr, text) => { Console.WriteLine (text); };
session.OutputWriter += (isStderr, text) => { Console.WriteLine (text); };
session.DebugWriter += (level, category, message) => { Console.WriteLine (message); };
// do we expect the app to start?
Assert.AreEqual (activityStarts, WaitForDebuggerToStart (Path.Combine (Root, b.ProjectDirectory, "logcat.log")), "Debugger should have started");
if (!activityStarts)
return;
Assert.False (session.HasExited, "Target should not have exited.");
session.Run (startInfo, options);
var expectedTime = TimeSpan.FromSeconds (1);
var actualTime = ProfileFor (() => session.IsConnected);
Assert.True (session.IsConnected, "Debugger should have connected but it did not.");
TestContext.Out.WriteLine ($"Debugger connected in {actualTime}");
Assert.LessOrEqual (actualTime, expectedTime, $"Debugger should have connected within {expectedTime} but it took {actualTime}.");
// we need to wait here for a while to allow the breakpoints to hit
// but we need to timeout
TimeSpan timeout = TimeSpan.FromSeconds (60);
while (session.IsConnected && breakcountHitCount < 2 && timeout >= TimeSpan.Zero) {
Thread.Sleep (10);
timeout = timeout.Subtract (TimeSpan.FromMilliseconds (10));
}
WaitFor (2000);
int expected = 2;
Assert.AreEqual (expected, breakcountHitCount, $"Should have hit {expected} breakpoints. Only hit {breakcountHitCount}");
b.BuildLogFile = "uninstall.log";
Assert.True (b.Uninstall (proj), "Project should have uninstalled.");
} catch (Exception ex) {
Assert.Fail ($"Exception occurred {ex}");
} finally {
session.Exit ();
}
WaitFor (2000);
int expected = 2;
Assert.AreEqual (expected, breakcountHitCount, $"Should have hit {expected} breakpoints. Only hit {breakcountHitCount}");
b.BuildLogFile = "uninstall.log";
Assert.True (b.Uninstall (proj), "Project should have uninstalled.");
session.Exit ();
}
}

Expand Down Expand Up @@ -405,93 +410,97 @@ public Foo ()
var sw = new Stopwatch ();
// setup the debugger
var session = new SoftDebuggerSession ();

session.Breakpoints = new BreakpointStore ();
string file = Path.Combine (Root, appBuilder.ProjectDirectory, "MainActivity.cs");
int line = FindTextInFile (file, "base.OnCreate (savedInstanceState);");
session.Breakpoints.Add (file, line);

file = Path.Combine (Root, appBuilder.ProjectDirectory, "MainPage.xaml.cs");
line = FindTextInFile (file, "InitializeComponent ();");
session.Breakpoints.Add (file, line);

file = Path.Combine (Root, appBuilder.ProjectDirectory, "MainPage.xaml.cs");
line = FindTextInFile (file, "Console.WriteLine (");
session.Breakpoints.Add (file, line);

file = Path.Combine (Root, appBuilder.ProjectDirectory, "App.xaml.cs");
line = FindTextInFile (file, "InitializeComponent ();");
session.Breakpoints.Add (file, line);

file = Path.Combine (Root, libBuilder.ProjectDirectory, "Foo.cs");
line = FindTextInFile (file, "public Foo ()");
// Add one to the line so we get the '{' under the constructor
session.Breakpoints.Add (file, line++);

session.TargetHitBreakpoint += (sender, e) => {
TestContext.WriteLine ($"BREAK {e.Type}, {e.Backtrace.GetFrame (0)}");
breakcountHitCount++;
session.Continue ();
};
var rnd = new Random ();
int port = rnd.Next (10000, 20000);
TestContext.Out.WriteLine ($"{port}");
var args = new SoftDebuggerConnectArgs ("", IPAddress.Loopback, port) {
MaxConnectionAttempts = 2000,
};
var startInfo = new SoftDebuggerStartInfo (args) {
WorkingDirectory = Path.Combine (appBuilder.ProjectDirectory, app.IntermediateOutputPath, "android", "assets"),
};
var options = new DebuggerSessionOptions () {
EvaluationOptions = EvaluationOptions.DefaultOptions,
};
options.EvaluationOptions.UseExternalTypeResolver = true;

parameters.Add ($"AndroidSdbTargetPort={port}");
parameters.Add ($"AndroidSdbHostPort={port}");
parameters.Add ("AndroidAttachDebugger=True");

RunProjectAndAssert (app, appBuilder, doNotCleanupOnUpdate: true, parameters: parameters.ToArray ());

session.LogWriter += (isStderr, text) => {
TestContext.Out.WriteLine (text);
};
session.OutputWriter += (isStderr, text) => {
TestContext.Out.WriteLine (text);
};
session.DebugWriter += (level, category, message) => {
TestContext.Out.WriteLine (message);
};
Assert.IsTrue (WaitForDebuggerToStart (Path.Combine (Root, appBuilder.ProjectDirectory, "logcat.log")), "Debugger should have started");
session.Run (startInfo, options);
TestContext.Out.WriteLine ($"Detected debugger startup in log");
Assert.False (session.HasExited, "Target should not have exited.");
WaitFor (TimeSpan.FromSeconds (30), () => session.IsConnected );
Assert.True (session.IsConnected, "Debugger should have connected but it did not.");
// we need to wait here for a while to allow the breakpoints to hit
// but we need to timeout
TestContext.Out.WriteLine ($"Debugger connected.");
TimeSpan timeout = TimeSpan.FromSeconds (60);
int expected = 4;
while (session.IsConnected && breakcountHitCount < 3 && timeout >= TimeSpan.Zero) {
Thread.Sleep (10);
timeout = timeout.Subtract (TimeSpan.FromMilliseconds (10));
}
WaitFor (2000);
Assert.AreEqual (expected, breakcountHitCount, $"Should have hit {expected} breakpoints. Only hit {breakcountHitCount}");
breakcountHitCount = 0;
ClearAdbLogcat ();
ClearBlockingDialogs ();
Assert.True (ClickButton (app.PackageName, "myXFButton", "CLICK ME"), "Button should have been clicked!");
while (session.IsConnected && breakcountHitCount < 1 && timeout >= TimeSpan.Zero) {
Thread.Sleep (10);
timeout = timeout.Subtract (TimeSpan.FromMilliseconds (10));
try {
session.Breakpoints = new BreakpointStore ();
string file = Path.Combine (Root, appBuilder.ProjectDirectory, "MainActivity.cs");
int line = FindTextInFile (file, "base.OnCreate (savedInstanceState);");
session.Breakpoints.Add (file, line);

file = Path.Combine (Root, appBuilder.ProjectDirectory, "MainPage.xaml.cs");
line = FindTextInFile (file, "InitializeComponent ();");
session.Breakpoints.Add (file, line);

file = Path.Combine (Root, appBuilder.ProjectDirectory, "MainPage.xaml.cs");
line = FindTextInFile (file, "Console.WriteLine (");
session.Breakpoints.Add (file, line);

file = Path.Combine (Root, appBuilder.ProjectDirectory, "App.xaml.cs");
line = FindTextInFile (file, "InitializeComponent ();");
session.Breakpoints.Add (file, line);

file = Path.Combine (Root, libBuilder.ProjectDirectory, "Foo.cs");
line = FindTextInFile (file, "public Foo ()");
// Add one to the line so we get the '{' under the constructor
session.Breakpoints.Add (file, line++);

session.TargetHitBreakpoint += (sender, e) => {
TestContext.WriteLine ($"BREAK {e.Type}, {e.Backtrace.GetFrame (0)}");
breakcountHitCount++;
session.Continue ();
};
var rnd = new Random ();
int port = rnd.Next (10000, 20000);
TestContext.Out.WriteLine ($"{port}");
var args = new SoftDebuggerConnectArgs ("", IPAddress.Loopback, port) {
MaxConnectionAttempts = 2000,
};
var startInfo = new SoftDebuggerStartInfo (args) {
WorkingDirectory = Path.Combine (appBuilder.ProjectDirectory, app.IntermediateOutputPath, "android", "assets"),
};
var options = new DebuggerSessionOptions () {
EvaluationOptions = EvaluationOptions.DefaultOptions,
};
options.EvaluationOptions.UseExternalTypeResolver = true;

parameters.Add ($"AndroidSdbTargetPort={port}");
parameters.Add ($"AndroidSdbHostPort={port}");
parameters.Add ("AndroidAttachDebugger=True");

RunProjectAndAssert (app, appBuilder, doNotCleanupOnUpdate: true, parameters: parameters.ToArray ());

session.LogWriter += (isStderr, text) => {
TestContext.Out.WriteLine (text);
};
session.OutputWriter += (isStderr, text) => {
TestContext.Out.WriteLine (text);
};
session.DebugWriter += (level, category, message) => {
TestContext.Out.WriteLine (message);
};
Assert.IsTrue (WaitForDebuggerToStart (Path.Combine (Root, appBuilder.ProjectDirectory, "logcat.log")), "Debugger should have started");
session.Run (startInfo, options);
TestContext.Out.WriteLine ($"Detected debugger startup in log");
Assert.False (session.HasExited, "Target should not have exited.");
WaitFor (TimeSpan.FromSeconds (30), () => session.IsConnected );
Assert.True (session.IsConnected, "Debugger should have connected but it did not.");
// we need to wait here for a while to allow the breakpoints to hit
// but we need to timeout
TestContext.Out.WriteLine ($"Debugger connected.");
TimeSpan timeout = TimeSpan.FromSeconds (60);
int expected = 4;
while (session.IsConnected && breakcountHitCount < 3 && timeout >= TimeSpan.Zero) {
Thread.Sleep (10);
timeout = timeout.Subtract (TimeSpan.FromMilliseconds (10));
}
WaitFor (2000);
Assert.AreEqual (expected, breakcountHitCount, $"Should have hit {expected} breakpoints. Only hit {breakcountHitCount}");
breakcountHitCount = 0;
ClearAdbLogcat ();
ClearBlockingDialogs ();
Assert.True (ClickButton (app.PackageName, "myXFButton", "CLICK ME"), "Button should have been clicked!");
while (session.IsConnected && breakcountHitCount < 1 && timeout >= TimeSpan.Zero) {
Thread.Sleep (10);
timeout = timeout.Subtract (TimeSpan.FromMilliseconds (10));
}
expected = 1;
Assert.AreEqual (expected, breakcountHitCount, $"Should have hit {expected} breakpoints. Only hit {breakcountHitCount}");
appBuilder.BuildLogFile = "uninstall.log";
Assert.True (appBuilder.Uninstall (app), "Project should have uninstalled.");
} catch (Exception ex) {
Assert.Fail($"Exception occurred {ex}");
} finally {
session.Exit ();
}
expected = 1;
Assert.AreEqual (expected, breakcountHitCount, $"Should have hit {expected} breakpoints. Only hit {breakcountHitCount}");
appBuilder.BuildLogFile = "uninstall.log";
Assert.True (appBuilder.Uninstall (app), "Project should have uninstalled.");
session.Exit ();
}
}
}
Expand Down
Loading

0 comments on commit d174b9b

Please sign in to comment.