// Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
// Use of this source code is governed by a BSD-style license that can be | |
// found in the LICENSE file. | |
namespace UnitTests | |
{ | |
using System; | |
using System.Diagnostics; | |
using System.IO; | |
using System.Reflection; | |
using System.Threading; | |
using EnvDTE; | |
using EnvDTE80; | |
using Microsoft.VisualStudio.TestTools.UnitTesting; | |
using NativeClientVSAddIn; | |
/// <summary> | |
/// This is a test class for PluginDebuggerHelperTest and is intended | |
/// to contain all PluginDebuggerHelperTest Unit Tests. | |
/// </summary> | |
[TestClass] | |
public class PluginDebuggerHelperTest | |
{ | |
/// <summary> | |
/// This holds the path to the NaCl solution used in these tests. | |
/// The NaCl solution is a valid nacl/pepper plug-in VS solution. | |
/// It is copied into the testing deployment directory and opened in some tests. | |
/// Because unit-tests run in any order, the solution should not be written to | |
/// in any tests. | |
/// </summary> | |
private static string naclSolution; | |
/// <summary> | |
/// The main visual studio object. | |
/// </summary> | |
private DTE2 dte_; | |
/// <summary> | |
/// Gets or sets the test context which provides information about, | |
/// and functionality for the current test run. | |
/// </summary> | |
public TestContext TestContext { get; set; } | |
/// <summary> | |
/// This is run one time before any test methods are called. Here we set-up a test-copy of a | |
/// new NaCl solution for use in the tests. | |
/// </summary> | |
/// <param name="testContext">Holds information about the current test run</param> | |
[ClassInitialize] | |
public static void ClassSetup(TestContext testContext) | |
{ | |
DTE2 dte = TestUtilities.StartVisualStudioInstance(); | |
try | |
{ | |
naclSolution = TestUtilities.CreateBlankValidNaClSolution( | |
dte, | |
"PluginDebuggerHelperTest", | |
NativeClientVSAddIn.Strings.PepperPlatformName, | |
NativeClientVSAddIn.Strings.NaClPlatformName, | |
testContext); | |
} | |
finally | |
{ | |
TestUtilities.CleanUpVisualStudioInstance(dte); | |
} | |
} | |
/// <summary> | |
/// This is run before each test to create test resources. | |
/// </summary> | |
[TestInitialize] | |
public void TestSetup() | |
{ | |
dte_ = TestUtilities.StartVisualStudioInstance(); | |
try | |
{ | |
TestUtilities.AssertAddinLoaded(dte_, NativeClientVSAddIn.Strings.AddInName); | |
} | |
catch | |
{ | |
TestUtilities.CleanUpVisualStudioInstance(dte_); | |
throw; | |
} | |
} | |
/// <summary> | |
/// This is run after each test to clean up things created in TestSetup(). | |
/// </summary> | |
[TestCleanup] | |
public void TestCleanup() | |
{ | |
TestUtilities.CleanUpVisualStudioInstance(dte_); | |
} | |
/// <summary> | |
/// A test for PluginDebuggerHelper Constructor. | |
/// </summary> | |
[TestMethod] | |
public void PluginDebuggerHelperConstructorTest() | |
{ | |
// Check that a null dte fails. | |
try | |
{ | |
PluginDebuggerHelper nullDte = new PluginDebuggerHelper(null); | |
Assert.Fail("Using null DTE instance did not throw exception"); | |
} | |
catch (ArgumentNullException) | |
{ | |
// This is expected for a correct implementation. | |
} | |
catch | |
{ | |
Assert.Fail("Using null DTE instance threw something other than ArgumentNullException"); | |
} | |
PluginDebuggerHelper_Accessor target = new PluginDebuggerHelper_Accessor(dte_); | |
Assert.AreEqual(dte_, target.dte_); | |
Assert.IsNull(target.webServerOutputPane_); | |
Assert.IsFalse(target.isProperlyInitialized_); | |
} | |
/// <summary> | |
/// This unit test verifies that the gdb init file is written correctly, | |
/// and old-existing GDB processes are cleaned up. | |
/// Verification of actual attachment is the responsibility of integration | |
/// tests and NaCl-GDB itself. | |
/// </summary> | |
[TestMethod] | |
[DeploymentItem("NativeClientVSAddIn.dll")] | |
public void AttachNaClGDBTest() | |
{ | |
PluginDebuggerHelper_Accessor target = new PluginDebuggerHelper_Accessor(dte_); | |
string existingGDB = "AttachNaClGDBTest_existingGDB"; | |
try | |
{ | |
target.pluginProjectDirectory_ = TestContext.DeploymentDirectory; | |
target.pluginAssembly_ = "fakeAssemblyString"; | |
target.irtPath_ = "fakeIrtPath"; | |
target.gdbPath_ = "python.exe"; | |
target.gdbProcess_ = TestUtilities.StartProcessForKilling(existingGDB, 20); | |
string existingInitFileName = Path.GetTempFileName(); | |
target.gdbInitFileName_ = existingInitFileName; | |
target.isProperlyInitialized_ = true; | |
// Visual studio won't allow adding a breakpoint unless it is associated with | |
// an existing file and valid line number, so use DummyLoopSolution. | |
dte_.Solution.Open(naclSolution); | |
string fileName = "main.cpp"; | |
string functionName = "DummyInstance::HandleMessage"; | |
int lineNumber = 35; | |
dte_.Debugger.Breakpoints.Add(Function: functionName); | |
dte_.Debugger.Breakpoints.Add(Line: lineNumber, File: fileName); | |
target.AttachNaClGDB(null, new PluginDebuggerHelper.PluginFoundEventArgs(0)); | |
Assert.IsTrue(File.Exists(target.gdbInitFileName_), "Init file not written"); | |
string[] gdbCommands = File.ReadAllLines(target.gdbInitFileName_); | |
bool functionBP = false; | |
bool lineBP = false; | |
// Validate that the commands contain what we specified. | |
// The syntax itself is not validated since this add-in is not responsible for | |
// the syntax and it could change. | |
foreach (string command in gdbCommands) | |
{ | |
if (command.Contains(fileName) && command.Contains(lineNumber.ToString())) | |
{ | |
lineBP = true; | |
} | |
if (command.Contains(functionName)) | |
{ | |
functionBP = true; | |
} | |
} | |
Assert.IsFalse( | |
TestUtilities.DoesProcessExist("python.exe", existingGDB), | |
"Failed to kill existing GDB process"); | |
Assert.IsFalse( | |
File.Exists(existingInitFileName), | |
"Failed to delete existing temp gdb init file"); | |
Assert.IsTrue(lineBP, "Line breakpoint not properly set"); | |
Assert.IsTrue(functionBP, "Function breakpoint not properly set"); | |
} | |
finally | |
{ | |
if (dte_.Debugger.Breakpoints != null) | |
{ | |
// Remove all breakpoints. | |
foreach (EnvDTE.Breakpoint bp in dte_.Debugger.Breakpoints) | |
{ | |
bp.Delete(); | |
} | |
} | |
if (!string.IsNullOrEmpty(target.gdbInitFileName_) && File.Exists(target.gdbInitFileName_)) | |
{ | |
File.Delete(target.gdbInitFileName_); | |
} | |
if (target.gdbProcess_ != null && !target.gdbProcess_.HasExited) | |
{ | |
target.gdbProcess_.Kill(); | |
target.gdbProcess_.Dispose(); | |
} | |
} | |
} | |
/// <summary> | |
/// A test for FindAndAttachToPlugin. | |
/// </summary> | |
[TestMethod] | |
[DeploymentItem("NativeClientVSAddIn.dll")] | |
public void FindAndAttachToPluginTest() | |
{ | |
PluginDebuggerHelper_Accessor target = new PluginDebuggerHelper_Accessor(dte_); | |
target.isProperlyInitialized_ = true; | |
MockProcessSearcher processResults = new MockProcessSearcher(); | |
target.debuggedChromeMainProcess_ = System.Diagnostics.Process.GetCurrentProcess(); | |
uint currentProcId = (uint)target.debuggedChromeMainProcess_.Id; | |
string naclCommandLine = Strings.NaClLoaderFlag; | |
target.pluginAssembly_ = "testAssemblyPath"; | |
string pluginLoadFlag = string.Format( | |
Strings.PepperProcessPluginFlagFormat, target.pluginAssembly_); | |
string pepperCommandLine = string.Concat( | |
pluginLoadFlag, " ", Strings.ChromeRendererFlag); | |
// Fake the list of processes on the system. | |
processResults.ProcessList.Add( | |
new ProcessInfo( | |
currentProcId, | |
currentProcId, | |
string.Empty, | |
Strings.NaClDebugFlag, | |
Strings.ChromeProcessName)); | |
processResults.ProcessList.Add( | |
new ProcessInfo(1, currentProcId, string.Empty, string.Empty, "MyParentProcess")); | |
processResults.ProcessList.Add( | |
new ProcessInfo(10, 1, string.Empty, pepperCommandLine, Strings.ChromeProcessName)); | |
processResults.ProcessList.Add( | |
new ProcessInfo(11, 1, string.Empty, naclCommandLine, Strings.NaClProcessName)); | |
// These two are missing some relevant command line args, they should not be attached to. | |
processResults.ProcessList.Add( | |
new ProcessInfo(12, 1, string.Empty, pluginLoadFlag, Strings.ChromeProcessName)); | |
processResults.ProcessList.Add( | |
new ProcessInfo(13, 1, string.Empty, string.Empty, Strings.NaClProcessName)); | |
// These two don't have this process as their parent, so they should not be attached to. | |
processResults.ProcessList.Add( | |
new ProcessInfo(14, 14, string.Empty, pepperCommandLine, Strings.ChromeProcessName)); | |
processResults.ProcessList.Add( | |
new ProcessInfo(15, 15, string.Empty, naclCommandLine, Strings.NaClProcessName)); | |
// Set the private value to the mock object (can't use accessor since no valid cast). | |
typeof(PluginDebuggerHelper).GetField( | |
"processSearcher_", | |
BindingFlags.NonPublic | BindingFlags.Instance).SetValue(target.Target, processResults); | |
// Test that the correct processes are attached to. | |
bool goodNaCl = false; | |
bool goodPepper = false; | |
var handler = new EventHandler<NativeClientVSAddIn.PluginDebuggerHelper.PluginFoundEventArgs>( | |
delegate(object unused, NativeClientVSAddIn.PluginDebuggerHelper.PluginFoundEventArgs args) | |
{ | |
switch (args.ProcessID) | |
{ | |
case 10: | |
if (goodPepper) | |
{ | |
Assert.Fail("Should not attach twice to same pepper process"); | |
} | |
if (target.projectPlatformType_ == | |
PluginDebuggerHelper_Accessor.ProjectPlatformType.NaCl) | |
{ | |
Assert.Fail("Attached to pepper process when NaCl was the target"); | |
} | |
goodPepper = true; | |
break; | |
case 11: | |
if (goodNaCl) | |
{ | |
Assert.Fail("Should not attach twice to same nacl process"); | |
} | |
if (target.projectPlatformType_ == | |
PluginDebuggerHelper_Accessor.ProjectPlatformType.Pepper) | |
{ | |
Assert.Fail("Attached to nacl process when pepper was the target"); | |
} | |
goodNaCl = true; | |
break; | |
case 12: | |
Assert.Fail("Should not attach to pepper process with bad args"); | |
break; | |
case 13: | |
Assert.Fail("Should not attach to nacl process with bad args"); | |
break; | |
case 14: | |
Assert.Fail("Should not attach to pepper process that is not " | |
+ "descendant of Visual Studio"); | |
break; | |
case 15: | |
Assert.Fail("Should not attach to nacl process that is not " | |
+ "descendant of Visual Studio"); | |
break; | |
default: | |
Assert.Fail("Should not attach to non-pepper/non-nacl process"); | |
break; | |
} | |
}); | |
target.add_PluginFoundEvent(handler); | |
target.projectPlatformType_ = PluginDebuggerHelper_Accessor.ProjectPlatformType.Pepper; | |
target.FindAndAttachToPlugin(null, null); | |
target.projectPlatformType_ = PluginDebuggerHelper_Accessor.ProjectPlatformType.NaCl; | |
target.FindAndAttachToPlugin(null, null); | |
target.remove_PluginFoundEvent(handler); | |
Assert.IsTrue(goodPepper, "Failed to attach to pepper process"); | |
Assert.IsTrue(goodNaCl, "Failed to attach to NaCl process"); | |
} | |
/// <summary> | |
/// A test for LoadProjectSettings. | |
/// </summary> | |
[TestMethod] | |
public void LoadProjectSettingsTest() | |
{ | |
string expectedSDKRootDir = | |
Environment.GetEnvironmentVariable(Strings.SDKPathEnvironmentVariable); | |
Assert.IsNotNull(expectedSDKRootDir, "SDK Path environment variable not set!"); | |
PluginDebuggerHelper_Accessor target = new PluginDebuggerHelper_Accessor(dte_); | |
target.isProperlyInitialized_ = false; | |
try | |
{ | |
target.LoadProjectSettings(); | |
Assert.Fail("Initializing with no loaded solution shouldn't succeed"); | |
} | |
catch (ArgumentOutOfRangeException) | |
{ | |
// This is expected for a correct implementation. | |
} | |
dte_.Solution.Open(naclSolution); | |
// Setting the start-up project to a non-cpp project should make loading fail. | |
object[] badStartupProj = { TestUtilities.NotNaClProjectUniqueName }; | |
dte_.Solution.SolutionBuild.StartupProjects = badStartupProj; | |
Assert.IsFalse(target.LoadProjectSettings()); | |
Assert.IsFalse(target.isProperlyInitialized_); | |
// Setting the start-up project to correct C++ project, but also setting the platform | |
// to non-nacl/pepper should make loading fail. | |
object[] startupProj = { TestUtilities.BlankNaClProjectUniqueName }; | |
dte_.Solution.SolutionBuild.StartupProjects = startupProj; | |
TestUtilities.SetSolutionConfiguration( | |
dte_, TestUtilities.BlankNaClProjectUniqueName, "Debug", "Win32"); | |
Assert.IsFalse(target.LoadProjectSettings()); | |
Assert.IsFalse(target.isProperlyInitialized_); | |
// Setting the platform to NaCl should make loading succeed. | |
TestUtilities.SetSolutionConfiguration( | |
dte_, TestUtilities.BlankNaClProjectUniqueName, "Debug", Strings.NaClPlatformName); | |
Assert.IsTrue(target.LoadProjectSettings()); | |
Assert.IsTrue(target.isProperlyInitialized_); | |
Assert.AreEqual( | |
PluginDebuggerHelper_Accessor.ProjectPlatformType.NaCl, | |
target.projectPlatformType_); | |
string projectDir = Path.Combine( | |
Path.GetDirectoryName(naclSolution), | |
Path.GetDirectoryName(TestUtilities.BlankNaClProjectUniqueName)) + @"\"; | |
string outputDir = Path.Combine(projectDir, "newlib") + @"\"; | |
string assembly = Path.Combine(outputDir, TestUtilities.BlankNaClProjectName + ".nexe"); | |
Assert.AreEqual(projectDir, target.pluginProjectDirectory_); | |
Assert.AreEqual(outputDir, target.pluginOutputDirectory_); | |
Assert.AreEqual(assembly, target.pluginAssembly_); | |
Assert.AreEqual(expectedSDKRootDir, target.sdkRootDirectory_); | |
Assert.AreEqual("python.exe", target.webServerExecutable_); | |
// Setting platform to Pepper should make succeed. | |
TestUtilities.SetSolutionConfiguration( | |
dte_, TestUtilities.BlankNaClProjectUniqueName, "Debug", Strings.PepperPlatformName); | |
Assert.IsTrue(target.LoadProjectSettings()); | |
Assert.IsTrue(target.isProperlyInitialized_); | |
Assert.AreEqual( | |
PluginDebuggerHelper_Accessor.ProjectPlatformType.Pepper, | |
target.projectPlatformType_); | |
outputDir = Path.Combine(projectDir, "win") + @"\"; | |
assembly = Path.Combine(outputDir, TestUtilities.BlankNaClProjectName + ".dll"); | |
Assert.AreEqual(projectDir, target.pluginProjectDirectory_); | |
Assert.AreEqual(outputDir, target.pluginOutputDirectory_); | |
Assert.AreEqual(assembly, target.pluginAssembly_); | |
Assert.AreEqual(expectedSDKRootDir, target.sdkRootDirectory_); | |
Assert.AreEqual("python.exe", target.webServerExecutable_); | |
} | |
/// <summary> | |
/// Checks that VS properly attaches debugger. | |
/// </summary> | |
[TestMethod] | |
[DeploymentItem("NativeClientVSAddIn.dll")] | |
public void AttachVSDebuggerTest() | |
{ | |
using (System.Diagnostics.Process dummyProc = TestUtilities.StartProcessForKilling( | |
"DummyProc", 20)) | |
{ | |
try | |
{ | |
PluginDebuggerHelper_Accessor target = new PluginDebuggerHelper_Accessor(dte_); | |
target.projectPlatformType_ = PluginDebuggerHelper_Accessor.ProjectPlatformType.Pepper; | |
target.isProperlyInitialized_ = true; | |
var pluginFoundArgs = new NativeClientVSAddIn.PluginDebuggerHelper.PluginFoundEventArgs( | |
(uint)dummyProc.Id); | |
target.AttachVSDebugger(null, pluginFoundArgs); | |
bool isBeingDebugged = false; | |
foreach (EnvDTE.Process proc in dte_.Debugger.DebuggedProcesses) | |
{ | |
if (proc.ProcessID == dummyProc.Id) | |
{ | |
isBeingDebugged = true; | |
} | |
} | |
Assert.IsTrue(isBeingDebugged, "Visual Studio debugger did not attach"); | |
} | |
finally | |
{ | |
if (dummyProc != null && !dummyProc.HasExited) | |
{ | |
dummyProc.Kill(); | |
dummyProc.Dispose(); | |
} | |
} | |
} | |
} | |
/// <summary> | |
/// A test for StartDebugging. | |
/// </summary> | |
[TestMethod] | |
public void StartDebuggingTest() | |
{ | |
PluginDebuggerHelper_Accessor target = new PluginDebuggerHelper_Accessor(dte_); | |
// Neutralize StartWebServer by providing dummy executable settings. | |
target.webServerExecutable_ = "python.exe"; | |
target.webServerArguments_ = "-c \"print 'test'\""; | |
target.pluginProjectDirectory_ = TestContext.DeploymentDirectory; | |
// Have the timer call a function to set success to true. | |
ManualResetEvent finderSuccess = new ManualResetEvent(false); | |
target.pluginFinderTimer_ = new System.Windows.Forms.Timer(); | |
target.pluginFinderTimer_.Tick += (a, b) => { finderSuccess.Set(); }; | |
// Check that an exception is thrown if not initialized properly. | |
target.isProperlyInitialized_ = false; | |
try | |
{ | |
target.StartDebugging(); | |
Assert.Fail("Debugging started when not initialized"); | |
} | |
catch (Exception) | |
{ | |
// Expected in a proper implementation. | |
} | |
// Properly start debugging and wait for event signal. | |
target.isProperlyInitialized_ = true; | |
target.StartDebugging(); | |
// Pump events waiting for signal, time-out after 10 seconds. | |
bool success = false; | |
for (int i = 0; i < 20; i++) | |
{ | |
System.Windows.Forms.Application.DoEvents(); | |
if (finderSuccess.WaitOne(500)) | |
{ | |
success = true; | |
break; | |
} | |
} | |
Assert.IsTrue(success, "Plug-in finder timer did not fire"); | |
} | |
/// <summary> | |
/// This tests that StartWebServer executes webServerExecutable_ with webServerArguments_ | |
/// as arguments, sets the working directory to the project directory, and hooks | |
/// up stdout and stderr from the web server to the Web Server output panel in VS. | |
/// This test implicitly covers WebServerMessageReceive. | |
/// </summary> | |
[TestMethod] | |
[DeploymentItem("NativeClientVSAddIn.dll")] | |
public void StartWebServerTest() | |
{ | |
PluginDebuggerHelper_Accessor target = new PluginDebuggerHelper_Accessor(dte_); | |
try | |
{ | |
string successMessage = "successful test!"; | |
string stderrMessage = "stderr test"; | |
target.webServerExecutable_ = "python.exe"; | |
// To save pain, if modifying this in the future avoid special characters, | |
// or make sure to double escape them. Ex: \n --> \\n. | |
string program = | |
"import os;" + | |
"import sys;" + | |
string.Format("sys.stdout.write('{0}');", successMessage) + | |
string.Format("sys.stderr.write('{0}');", stderrMessage) + | |
"sys.stdout.write(os.getcwd());" + | |
"sys.stdout.flush();" + | |
"sys.stderr.flush()"; | |
target.webServerArguments_ = string.Format("-c \"{0}\"", program); | |
target.pluginProjectDirectory_ = TestContext.DeploymentDirectory; | |
target.isProperlyInitialized_ = true; | |
target.StartWebServer(); | |
// Check that the output pane exists. | |
EnvDTE.OutputWindowPane window = dte_.ToolWindows.OutputWindow.OutputWindowPanes.Item( | |
Strings.WebServerOutputWindowTitle); | |
Assert.IsNotNull(window, "Web server output pane failed to create"); | |
// Wait for results to arrive for up to 10 seconds, checking every 0.5 seconds. | |
string result = TestUtilities.GetPaneText(target.webServerOutputPane_); | |
for (int repeat = 0; repeat < 20; repeat++) | |
{ | |
if (result != null && | |
result.Contains(successMessage) && | |
result.Contains(stderrMessage) && | |
result.Contains(TestContext.DeploymentDirectory)) | |
{ | |
break; | |
} | |
System.Threading.Thread.Sleep(500); | |
result = TestUtilities.GetPaneText(target.webServerOutputPane_); | |
} | |
Assert.IsFalse(string.IsNullOrEmpty(result), "Nothing printed to output pane"); | |
StringAssert.Contains( | |
result, | |
successMessage, | |
"Executable did not successfully run given arguments"); | |
StringAssert.Contains( | |
result, | |
TestContext.DeploymentDirectory, | |
"Web server working directory was not set to project directory"); | |
StringAssert.Contains(result, stderrMessage, "Standard error message was not captured"); | |
} | |
finally | |
{ | |
if (!target.webServer_.WaitForExit(1000)) | |
{ | |
target.webServer_.Kill(); | |
target.webServer_.Dispose(); | |
} | |
} | |
} | |
/// <summary> | |
/// Ensures that StopDebugging() kills GDB and the web server, and resets the state of | |
/// PluginDebuggerHelper to before debugging started. | |
/// Implicitly tests KillGDBProcess(). | |
/// </summary> | |
[TestMethod] | |
public void StopDebuggingTest() | |
{ | |
PluginDebuggerHelper_Accessor target = new PluginDebuggerHelper_Accessor(dte_); | |
string webServerIdentifier = "StartVisualStudioInstance_TestWebServer"; | |
string gdbIdentifier = "StartVisualStudioInstance_TestGDB"; | |
// Set up items that should exist given a successful calling of StartDebugging(). | |
target.gdbInitFileName_ = Path.GetTempFileName(); | |
target.pluginFinderForbiddenPids_.Add(123); | |
target.webServer_ = TestUtilities.StartProcessForKilling(webServerIdentifier, 20); | |
target.gdbProcess_ = TestUtilities.StartProcessForKilling(gdbIdentifier, 20); | |
target.isProperlyInitialized_ = true; | |
target.StopDebugging(); | |
Assert.IsFalse(target.isProperlyInitialized_, "Failed to set initialized state to false"); | |
Assert.IsFalse(target.pluginFinderTimer_.Enabled, "Plug-in finder timer not stopped"); | |
Assert.IsFalse( | |
TestUtilities.DoesProcessExist("python.exe", webServerIdentifier), | |
"Failed to kill web server process"); | |
Assert.IsFalse( | |
TestUtilities.DoesProcessExist("python.exe", gdbIdentifier), | |
"Failed to kill gdb process"); | |
Assert.IsFalse( | |
File.Exists(target.gdbInitFileName_), | |
"Failed to delete temp gdb init file"); | |
Assert.IsTrue( | |
target.pluginFinderForbiddenPids_.Count == 0, | |
"Plugin finder Process IDs not cleared"); | |
} | |
/// <summary> | |
/// A test for WebServerWriteLine. | |
/// </summary> | |
[TestMethod] | |
[DeploymentItem("NativeClientVSAddIn.dll")] | |
public void WebServerWriteLineTest() | |
{ | |
PluginDebuggerHelper_Accessor target = new PluginDebuggerHelper_Accessor(dte_); | |
string successMessage = "successful test!"; | |
target.webServerOutputPane_ = dte_.ToolWindows.OutputWindow.OutputWindowPanes.Add( | |
Strings.WebServerOutputWindowTitle); | |
target.isProperlyInitialized_ = true; | |
target.WebServerWriteLine(successMessage); | |
string result = TestUtilities.GetPaneText(target.webServerOutputPane_); | |
// Wait for results to arrive for up to 10 seconds, checking every 0.5 seconds. | |
for (int repeat = 0; repeat < 20; repeat++) | |
{ | |
if (result != null && | |
result.Contains(successMessage)) | |
{ | |
break; | |
} | |
System.Threading.Thread.Sleep(500); | |
result = TestUtilities.GetPaneText(target.webServerOutputPane_); | |
} | |
StringAssert.Contains(result, successMessage, "Message failed to print"); | |
} | |
} | |
} |