Skip to content
Merged
Show file tree
Hide file tree
Changes from 62 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
987eca8
Add giant test for MSBuild Server
Forgind May 2, 2022
251a319
Add more tests
Forgind May 4, 2022
2321e53
Real fix
Forgind May 4, 2022
c25c45b
help with debugging test failure
Forgind May 12, 2022
6d0c9b2
I'm stupid.
Forgind May 12, 2022
f61a2a5
More output info
Forgind May 13, 2022
2688ec0
VS broke the build
Forgind May 13, 2022
b2283db
Add comm traces
Forgind May 13, 2022
4ef560e
Merge branch 'feature/msbuild-server' of https://github.com/dotnet/ms…
Forgind May 14, 2022
f0a5420
More info
Forgind May 16, 2022
ded0746
Up verbosity
Forgind May 16, 2022
ac33664
Make parsing smarter
Forgind May 16, 2022
9cec556
Try making processes
Forgind May 17, 2022
0e4c5f5
Make build
Forgind May 17, 2022
8eada1f
Temporarily remove test
Forgind May 18, 2022
21a31ba
Add giant test for MSBuild Server
Forgind May 2, 2022
ec3e179
Add more tests
Forgind May 4, 2022
afb75f9
Real fix
Forgind May 4, 2022
02ea730
help with debugging test failure
Forgind May 12, 2022
e50eb83
I'm stupid.
Forgind May 12, 2022
3e0ab59
More output info
Forgind May 13, 2022
5fc09be
VS broke the build
Forgind May 13, 2022
901a0c3
Add comm traces
Forgind May 13, 2022
a4f382e
More info
Forgind May 16, 2022
758af2e
Up verbosity
Forgind May 16, 2022
8bb5299
Make parsing smarter
Forgind May 16, 2022
6aab346
Try making processes
Forgind May 17, 2022
4dd2afe
Make build
Forgind May 17, 2022
cf78cbf
Temporarily remove test
Forgind May 18, 2022
40d9b7f
Revert "Temporarily remove test"
Forgind May 18, 2022
973d8b7
Revert "Make build"
Forgind May 18, 2022
0effe6f
Revert "Try making processes"
Forgind May 18, 2022
98989de
add extra logging
AR-May May 24, 2022
68a87e3
Add sleep.
AR-May May 25, 2022
8620192
Revert "Add sleep."
AR-May May 27, 2022
ec9c7f7
Revert "Revert "Add sleep.""
AR-May May 27, 2022
d592b19
move sleep
AR-May May 27, 2022
29d8163
Revert "move sleep"
AR-May May 27, 2022
8a258b2
Add possible fix for missing output.
AR-May May 30, 2022
3b3bcbe
Remove extra logging; Add checks.
AR-May May 30, 2022
3d0eac6
More logging info.
AR-May May 30, 2022
7c01bdf
more logging
AR-May May 31, 2022
43800c7
more logging 2
AR-May May 31, 2022
3ffa1c3
sync read
AR-May May 31, 2022
1560796
Revert "sync read"
AR-May May 31, 2022
a34bb46
add redirects to client.
AR-May May 31, 2022
968d485
add windows native call
AR-May May 31, 2022
8bb9fb4
Lots of Task.Run
Forgind May 31, 2022
91e2403
Try to use a modified lunch msbuild node func for msbuild server proc…
AR-May Jun 1, 2022
3512dc2
Revert "Lots of Task.Run"
AR-May Jun 1, 2022
02b9561
Revert "Temporarily remove test"
Forgind Jun 1, 2022
a20befe
Some cleanup
Forgind Jun 1, 2022
c8f5e1f
Try to use a modified lunch msbuild node func for msbuild server proc…
AR-May Jun 1, 2022
0667a62
Merge branch 'feature/msbuild-server' into server-node-test
Forgind Jun 1, 2022
9118409
Make build
Forgind Jun 1, 2022
cbb272f
Merge branch 'server-node-test' of https://github.com/Forgind/msbuild…
Forgind Jun 1, 2022
356a8e0
drop diag
Forgind Jun 1, 2022
7efa083
Merge branch 'extra-server-test' of https://github.com/Forgind/msbuil…
Forgind Jun 1, 2022
bfd6915
Cleanup
Forgind Jun 1, 2022
4fd0490
Remove changes done to msbuild client.
AR-May Jun 5, 2022
5bd041e
Merge branch 'feature/msbuild-server' into server-node-test
AR-May Jun 6, 2022
7dcc13a
Remove test that uses MSBUILDNOINPROCNODE flag: it checks the wrong b…
AR-May Jun 6, 2022
f1234e2
Remove timeout for WaitForExit().
AR-May Jun 6, 2022
4d94117
Add comments about WaitForExit and set a timeout for the process exec…
AR-May Jun 6, 2022
4dd0a0a
Fix KillTree tool sharing.
rokonec Jun 7, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions src/Build.OM.UnitTests/NugetRestoreTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
#endif
using Xunit.Abstractions;

#nullable disable

namespace Microsoft.Build.Engine.OM.UnitTests
{
public sealed class NugetRestoreTests
Expand All @@ -29,7 +27,7 @@ public NugetRestoreTests(ITestOutputHelper output)
[Fact]
public void TestOldNuget()
{
string msbuildExePath = Path.GetDirectoryName(RunnerUtilities.PathToCurrentlyRunningMsBuildExe);
string msbuildExePath = Path.GetDirectoryName(RunnerUtilities.PathToCurrentlyRunningMsBuildExe)!;
using TestEnvironment testEnvironment = TestEnvironment.Create();
TransientTestFolder folder = testEnvironment.CreateFolder(createFolder: true);
// The content of the solution isn't known to matter, but having a custom solution makes it easier to add requirements should they become evident.
Expand Down
4 changes: 2 additions & 2 deletions src/Build/BackEnd/Client/MSBuildClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft. All rights reserved.
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
Expand Down Expand Up @@ -416,7 +416,7 @@ private bool TryConnectToServer(int timeout)
CommunicationsUtilities.Trace("Reading handshake from pipe {0}", _pipeName);

#if NETCOREAPP2_1_OR_GREATER || MONO
_nodeStream.ReadEndOfHandshakeSignal(false, 1000);
_nodeStream.ReadEndOfHandshakeSignal(false, 1000);
#else
_nodeStream.ReadEndOfHandshakeSignal(false);
#endif
Expand Down
5 changes: 5 additions & 0 deletions src/Framework/Microsoft.Build.Framework.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
<Reference Include="System.Xaml" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETStandard'">
<!-- Need Win32 API on .NET Standard to ping registry for some methods in ToolLocationHelper -->
<PackageReference Include="Microsoft.Win32.Registry" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\Shared\Constants.cs">
<Link>Shared\Constants.cs</Link>
Expand Down
217 changes: 217 additions & 0 deletions src/MSBuild.UnitTests/MSBuildServer_Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.UnitTests;
using Microsoft.Build.UnitTests.Shared;
#if NETFRAMEWORK
using Microsoft.IO;
#else
using System.IO;
#endif
using Shouldly;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.Build.Engine.UnitTests
{
public class SleepingTask : Microsoft.Build.Utilities.Task
{
public int SleepTime { get; set; }

/// <summary>
/// Sleep for SleepTime milliseconds.
/// </summary>
/// <returns>Success on success.</returns>
public override bool Execute()
{
Thread.Sleep(SleepTime);
return !Log.HasLoggedErrors;
}
}

public class ProcessIdTask : Microsoft.Build.Utilities.Task
{
[Output]
public int Pid { get; set; }

/// <summary>
/// Log the id for this process.
/// </summary>
/// <returns></returns>
public override bool Execute()
{
Pid = Process.GetCurrentProcess().Id;
return true;
}
}

public class MSBuildServer_Tests : IDisposable
{
private readonly ITestOutputHelper _output;
private readonly TestEnvironment _env;
private static string printPidContents = @$"
<Project>
<UsingTask TaskName=""ProcessIdTask"" AssemblyFile=""{Assembly.GetExecutingAssembly().Location}"" />
<Target Name='AccessPID'>
<ProcessIdTask>
<Output PropertyName=""PID"" TaskParameter=""Pid"" />
</ProcessIdTask>
<Message Text=""Server ID is $(PID)"" Importance=""High"" />
</Target>
</Project>";
private static string sleepingTaskContents = @$"
<Project>
<UsingTask TaskName=""SleepingTask"" AssemblyFile=""{Assembly.GetExecutingAssembly().Location}"" />
<Target Name='Sleep'>
<SleepingTask SleepTime=""100000"" />
</Target>
</Project>";

public MSBuildServer_Tests(ITestOutputHelper output)
{
_output = output;
_env = TestEnvironment.Create(_output);
}

public void Dispose() => _env.Dispose();

[Fact]
public void MSBuildServerTest()
{
TransientTestFile project = _env.CreateFile("testProject.proj", printPidContents);
_env.SetEnvironmentVariable("MSBUILDUSESERVER", "1");
string output = RunnerUtilities.ExecMSBuild(BuildEnvironmentHelper.Instance.CurrentMSBuildExePath, project.Path, out bool success, false, _output);
success.ShouldBeTrue();
int pidOfInitialProcess = ParseNumber(output, "Process ID is ");
int pidOfServerProcess = ParseNumber(output, "Server ID is ");
pidOfInitialProcess.ShouldNotBe(pidOfServerProcess, "We started a server node to execute the target rather than running it in-proc, so its pid should be different.");

output = RunnerUtilities.ExecMSBuild(BuildEnvironmentHelper.Instance.CurrentMSBuildExePath, project.Path, out success, false, _output);
success.ShouldBeTrue();
int newPidOfInitialProcess = ParseNumber(output, "Process ID is ");
newPidOfInitialProcess.ShouldNotBe(pidOfServerProcess, "We started a server node to execute the target rather than running it in-proc, so its pid should be different.");
newPidOfInitialProcess.ShouldNotBe(pidOfInitialProcess, "Process started by two MSBuild executions should be different.");
pidOfServerProcess.ShouldBe(ParseNumber(output, "Server ID is "), "Node used by both the first and second build should be the same.");

// Prep to kill the long-lived task we're about to start.
Task t = Task.Run(() =>
{
// Wait for the long-lived task to start
// If this test seems to fail randomly, increase this time.
Thread.Sleep(1000);

// Kill the server
ProcessExtensions.KillTree(Process.GetProcessById(pidOfServerProcess), 1000);
});

// Start long-lived task execution
TransientTestFile sleepProject = _env.CreateFile("napProject.proj", sleepingTaskContents);
RunnerUtilities.ExecMSBuild(BuildEnvironmentHelper.Instance.CurrentMSBuildExePath, sleepProject.Path, out _);

t.Wait();

// Ensure that a new build can still succeed and that its server node is different.
output = RunnerUtilities.ExecMSBuild(BuildEnvironmentHelper.Instance.CurrentMSBuildExePath, project.Path, out success, false, _output);
success.ShouldBeTrue();
newPidOfInitialProcess = ParseNumber(output, "Process ID is ");
int newServerProcessId = ParseNumber(output, "Server ID is ");
newPidOfInitialProcess.ShouldNotBe(pidOfInitialProcess, "Process started by two MSBuild executions should be different.");
newPidOfInitialProcess.ShouldNotBe(newServerProcessId, "We started a server node to execute the target rather than running it in-proc, so its pid should be different.");
pidOfServerProcess.ShouldNotBe(newServerProcessId, "Node used by both the first and second build should not be the same.");
}

[Fact]
public void VerifyMixedLegacyBehavior()
{
TransientTestFile project = _env.CreateFile("testProject.proj", printPidContents);
_env.SetEnvironmentVariable("MSBUILDUSESERVER", "1");

string output = RunnerUtilities.ExecMSBuild(BuildEnvironmentHelper.Instance.CurrentMSBuildExePath, project.Path, out bool success, false, _output);
success.ShouldBeTrue();
int pidOfInitialProcess = ParseNumber(output, "Process ID is ");
int pidOfServerProcess = ParseNumber(output, "Server ID is ");
pidOfInitialProcess.ShouldNotBe(pidOfServerProcess, "We started a server node to execute the target rather than running it in-proc, so its pid should be different.");

Environment.SetEnvironmentVariable("MSBUILDUSESERVER", "");
output = RunnerUtilities.ExecMSBuild(BuildEnvironmentHelper.Instance.CurrentMSBuildExePath, project.Path, out success, false, _output);
success.ShouldBeTrue();
pidOfInitialProcess = ParseNumber(output, "Process ID is ");
int pidOfNewserverProcess = ParseNumber(output, "Server ID is ");
pidOfInitialProcess.ShouldBe(pidOfNewserverProcess, "We did not start a server node to execute the target, so its pid should be the same.");

Environment.SetEnvironmentVariable("MSBUILDUSESERVER", "1");
output = RunnerUtilities.ExecMSBuild(BuildEnvironmentHelper.Instance.CurrentMSBuildExePath, project.Path, out success, false, _output);
success.ShouldBeTrue();
pidOfInitialProcess = ParseNumber(output, "Process ID is ");
pidOfNewserverProcess = ParseNumber(output, "Server ID is ");
pidOfInitialProcess.ShouldNotBe(pidOfNewserverProcess, "We started a server node to execute the target rather than running it in-proc, so its pid should be different.");
pidOfServerProcess.ShouldBe(pidOfNewserverProcess, "Server node should be the same as from earlier.");
}

[Fact]
public void BuildsWhileBuildIsRunningOnServer()
{
_env.SetEnvironmentVariable("MSBUILDUSESERVER", "1");
TransientTestFile project = _env.CreateFile("testProject.proj", printPidContents);
TransientTestFile sleepProject = _env.CreateFile("napProject.proj", sleepingTaskContents);

int pidOfServerProcess = -1;
Task? t = null;
try
{
// Start a server node and find its PID.
string output = RunnerUtilities.ExecMSBuild(BuildEnvironmentHelper.Instance.CurrentMSBuildExePath, project.Path, out bool success, false, _output);
pidOfServerProcess = ParseNumber(output, "Server ID is ");

t = Task.Run(() =>
{
RunnerUtilities.ExecMSBuild(BuildEnvironmentHelper.Instance.CurrentMSBuildExePath, sleepProject.Path, out _, false, _output);
});

// The server will soon be in use; make sure we don't try to use it before that happens.
Thread.Sleep(1000);

Environment.SetEnvironmentVariable("MSBUILDUSESERVER", "0");

output = RunnerUtilities.ExecMSBuild(BuildEnvironmentHelper.Instance.CurrentMSBuildExePath, project.Path, out success, false, _output);
success.ShouldBeTrue();
ParseNumber(output, "Server ID is ").ShouldBe(ParseNumber(output, "Process ID is "), "There should not be a server node for this build.");

Environment.SetEnvironmentVariable("MSBUILDUSESERVER", "1");

output = RunnerUtilities.ExecMSBuild(BuildEnvironmentHelper.Instance.CurrentMSBuildExePath, project.Path, out success, false, _output);
success.ShouldBeTrue();
pidOfServerProcess.ShouldNotBe(ParseNumber(output, "Server ID is "), "The server should be otherwise occupied.");
pidOfServerProcess.ShouldNotBe(ParseNumber(output, "Process ID is "), "There should not be a server node for this build.");
ParseNumber(output, "Server ID is ").ShouldBe(ParseNumber(output, "Process ID is "), "Process ID and Server ID should coincide.");
}
finally
{
if (pidOfServerProcess > -1)
{
ProcessExtensions.KillTree(Process.GetProcessById(pidOfServerProcess), 1000);
}

if (t is not null)
{
t.Wait();
}
}
}

private int ParseNumber(string searchString, string toFind)
{
Regex regex = new(@$"{toFind}(\d+)");
Match match = regex.Match(searchString);
return int.Parse(match.Groups[1].Value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<Compile Include="..\Shared\RegistryDelegates.cs">
<Link>RegistryDelegates.cs</Link>
</Compile>
<Compile Include="..\Shared\ProcessExtensions.cs" />
<Compile Include="..\Shared\RegistryHelper.cs">
<Link>RegistryHelper.cs</Link>
</Compile>
Expand Down
27 changes: 15 additions & 12 deletions src/UnitTests.Shared/RunnerUtilities.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Microsoft.Build.Shared;
using Microsoft.Build.Utilities;
using System;
using System.Diagnostics;
using Xunit.Abstractions;
Expand All @@ -18,7 +17,7 @@ public static class RunnerUtilities
/// </summary>
public static string ExecMSBuild(string msbuildParameters, out bool successfulExit, ITestOutputHelper outputHelper = null)
{
return ExecMSBuild(PathToCurrentlyRunningMsBuildExe, msbuildParameters, out successfulExit, false, outputHelper);
return ExecMSBuild(PathToCurrentlyRunningMsBuildExe, msbuildParameters, out successfulExit, outputHelper: outputHelper);
}

/// <summary>
Expand Down Expand Up @@ -72,12 +71,15 @@ private static string ResolveRuntimeExecutableName()
/// </summary>
public static string RunProcessAndGetOutput(string process, string parameters, out bool successfulExit, bool shellExecute = false, ITestOutputHelper outputHelper = null)
{
outputHelper?.WriteLine($"{DateTime.Now.ToString("hh:mm:ss tt")}:RunProcessAndGetOutput:1");

if (shellExecute)
{
// we adjust the psi data manually because on net core using ProcessStartInfo.UseShellExecute throws NotImplementedException
AdjustForShellExecution(ref process, ref parameters);
}

outputHelper?.WriteLine($"{DateTime.Now.ToString("hh:mm:ss tt")}:RunProcessAndGetOutput:2");
var psi = new ProcessStartInfo(process)
{
CreateNoWindow = true,
Expand All @@ -87,25 +89,22 @@ public static string RunProcessAndGetOutput(string process, string parameters, o
UseShellExecute = false,
Arguments = parameters
};
var output = string.Empty;
string output = string.Empty;
int pid = -1;

outputHelper?.WriteLine($"{DateTime.Now.ToString("hh:mm:ss tt")}:RunProcessAndGetOutput:3");
using (var p = new Process { EnableRaisingEvents = true, StartInfo = psi })
{
p.OutputDataReceived += delegate (object sender, DataReceivedEventArgs args)
DataReceivedEventHandler handler = delegate (object sender, DataReceivedEventArgs args)
{
if (args != null)
{
output += args.Data + "\r\n";
}
};

p.ErrorDataReceived += delegate (object sender, DataReceivedEventArgs args)
{
if (args != null)
{
output += args.Data + "\r\n";
}
};
p.OutputDataReceived += handler;
p.ErrorDataReceived += handler;

outputHelper?.WriteLine("Executing [{0} {1}]", process, parameters);
Console.WriteLine("Executing [{0} {1}]", process, parameters);
Expand All @@ -114,19 +113,23 @@ public static string RunProcessAndGetOutput(string process, string parameters, o
p.BeginOutputReadLine();
p.BeginErrorReadLine();
p.StandardInput.Dispose();
p.WaitForExit();
p.WaitForExit(30000);

pid = p.Id;
successfulExit = p.ExitCode == 0;
}

outputHelper?.WriteLine("==== OUTPUT ====");
outputHelper?.WriteLine(output);
outputHelper?.WriteLine("Process ID is " + pid + "\r\n");
outputHelper?.WriteLine("==============");

Console.WriteLine("==== OUTPUT ====");
Console.WriteLine(output);
Console.WriteLine("Process ID is " + pid + "\r\n");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm likely not understanding the test, but process IDs get reused, and the algorithm used is not stable, so if a server process exits the next one can potentially get the same id?

@AR-May AR-May Jun 6, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests should check the logic of msbuild server. That is:

  • If the special flag is on, we use msbuild server for task execution. Msbuild server process is a long living one. It lives the same time as the msbuild nodes (15 min).
  • If server is occupied, there should be a fallback to the usual, non-server, build.

The task itself writes down the id of the process that is used for the build, and here we write down the process id of the msbuild process that was created for building a project. If msbuild server was used, these ids would be different (first one would be the id of msbuild server and the second one would be the id of msbuild client). If not used, they would be the same. Thus, we check how the build was executed, in or out the server.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so long as the first process has not exited, then yes of course they have different PID's. Thanks. Otherwise of course you have to check process start time.

Console.WriteLine("==============");

output += "Process ID is " + pid + "\r\n";
return output;
}
}
Expand Down