Add kestrel tests.
Tests will 'build', 'run', 'publish' and 'execute' a Kestrel Hello World server as a PortableFatApp and as a Standalone app.
This commit is contained in:
parent
f665c28173
commit
8f00b95783
17 changed files with 766 additions and 25 deletions
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-20113",
|
||||
"Microsoft.AspNet.Hosting": "1.0.0-rc2-16253",
|
||||
"Microsoft.Extensions.Logging.Console": "1.0.0-rc2-20254"
|
||||
},
|
||||
"compilationOptions": {
|
||||
"emitEntryPoint": true
|
||||
},
|
||||
"compile": [
|
||||
"../src/*.cs"
|
||||
],
|
||||
|
||||
"frameworks": {
|
||||
"netstandard1.5": {
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"type": "platform",
|
||||
"version": "1.0.0-rc2-23931"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"dnxcore50",
|
||||
"portable-net45+win8"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-20113",
|
||||
"Microsoft.AspNet.Hosting": "1.0.0-rc2-16253",
|
||||
"Microsoft.Extensions.Logging.Console": "1.0.0-rc2-20254"
|
||||
},
|
||||
"compilationOptions": {
|
||||
"emitEntryPoint": true
|
||||
},
|
||||
"compile": [
|
||||
"../src/*.cs"
|
||||
],
|
||||
|
||||
"frameworks": {
|
||||
"netstandardapp1.5": {
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.App": "1.0.0-rc2-23931"
|
||||
},
|
||||
"imports": [
|
||||
"dnxcore50",
|
||||
"portable-net45+win8"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
88
TestAssets/TestProjects/KestrelSample/src/Startup.cs
Normal file
88
TestAssets/TestProjects/KestrelSample/src/Startup.cs
Normal file
|
@ -0,0 +1,88 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Filter;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.PlatformAbstractions;
|
||||
|
||||
namespace SampleApp
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
private static string Args { get; set; }
|
||||
private static CancellationTokenSource ServerCancellationTokenSource { get; set; }
|
||||
|
||||
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IApplicationEnvironment env)
|
||||
{
|
||||
var ksi = app.ServerFeatures.Get<IKestrelServerInformation>();
|
||||
ksi.NoDelay = true;
|
||||
|
||||
loggerFactory.AddConsole(LogLevel.Error);
|
||||
|
||||
app.UseKestrelConnectionLogging();
|
||||
|
||||
app.Run(async context =>
|
||||
{
|
||||
Console.WriteLine("{0} {1}{2}{3}",
|
||||
context.Request.Method,
|
||||
context.Request.PathBase,
|
||||
context.Request.Path,
|
||||
context.Request.QueryString);
|
||||
Console.WriteLine($"Method: {context.Request.Method}");
|
||||
Console.WriteLine($"PathBase: {context.Request.PathBase}");
|
||||
Console.WriteLine($"Path: {context.Request.Path}");
|
||||
Console.WriteLine($"QueryString: {context.Request.QueryString}");
|
||||
|
||||
var connectionFeature = context.Connection;
|
||||
Console.WriteLine($"Peer: {connectionFeature.RemoteIpAddress?.ToString()} {connectionFeature.RemotePort}");
|
||||
Console.WriteLine($"Sock: {connectionFeature.LocalIpAddress?.ToString()} {connectionFeature.LocalPort}");
|
||||
|
||||
var content = $"Hello world!{Environment.NewLine}Received '{Args}' from command line.";
|
||||
context.Response.ContentLength = content.Length;
|
||||
context.Response.ContentType = "text/plain";
|
||||
await context.Response.WriteAsync(content);
|
||||
});
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
Console.WriteLine("KestrelHelloWorld <url to host>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
var url = new Uri(args[0]);
|
||||
Args = string.Join(" ", args);
|
||||
|
||||
var host = new WebHostBuilder()
|
||||
.UseServer("Microsoft.AspNetCore.Server.Kestrel")
|
||||
.UseUrls(url.ToString())
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
|
||||
ServerCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
// shutdown server after 20s.
|
||||
var shutdownTask = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(20000);
|
||||
ServerCancellationTokenSource.Cancel();
|
||||
});
|
||||
|
||||
host.Run(ServerCancellationTokenSource.Token);
|
||||
shutdownTask.Wait();
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,7 +34,8 @@ namespace Microsoft.DotNet.Cli.Build
|
|||
"Microsoft.Extensions.DependencyModel.Tests",
|
||||
"ArgumentForwardingTests",
|
||||
"dotnet-test.UnitTests",
|
||||
"dotnet-test.Tests"
|
||||
"dotnet-test.Tests",
|
||||
"Kestrel.Tests"
|
||||
};
|
||||
|
||||
public static readonly dynamic[] ConditionalTestAssets = new[]
|
||||
|
|
48
test/Kestrel.Tests/DotnetBuildTest.cs
Normal file
48
test/Kestrel.Tests/DotnetBuildTest.cs
Normal file
|
@ -0,0 +1,48 @@
|
|||
using System.IO;
|
||||
using Microsoft.DotNet.Tools.Test.Utilities;
|
||||
using Xunit;
|
||||
using Microsoft.DotNet.TestFramework;
|
||||
|
||||
namespace Microsoft.DotNet.Kestrel.Tests
|
||||
{
|
||||
public class DotnetBuildTest : TestBase
|
||||
{
|
||||
public static string KestrelPortableApp { get; } = "KestrelPortable";
|
||||
|
||||
[Fact]
|
||||
public void BuildingKestrelPortableFatAppProducesExpectedArtifacts()
|
||||
{
|
||||
var testInstance = TestAssetsManager.CreateTestInstance("KestrelSample")
|
||||
.WithLockFiles();
|
||||
|
||||
BuildAndTest(Path.Combine(testInstance.TestRoot, KestrelPortableApp));
|
||||
}
|
||||
|
||||
private static void BuildAndTest(string testRoot)
|
||||
{
|
||||
string appName = Path.GetFileName(testRoot);
|
||||
|
||||
|
||||
var result = new BuildCommand(
|
||||
projectPath: testRoot)
|
||||
.ExecuteWithCapturedOutput();
|
||||
|
||||
result.Should().Pass();
|
||||
|
||||
var outputBase = new DirectoryInfo(Path.Combine(testRoot, "bin", "Debug"));
|
||||
|
||||
var netstandardappOutput = outputBase.Sub("netstandard1.5");
|
||||
|
||||
netstandardappOutput.Should()
|
||||
.Exist().And
|
||||
.OnlyHaveFiles(new[]
|
||||
{
|
||||
$"{appName}.deps.json",
|
||||
$"{appName}.dll",
|
||||
$"{appName}.pdb",
|
||||
$"{appName}.runtimeconfig.json",
|
||||
$"{appName}.runtimeconfig.dev.json"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
65
test/Kestrel.Tests/DotnetRunTest.cs
Normal file
65
test/Kestrel.Tests/DotnetRunTest.cs
Normal file
|
@ -0,0 +1,65 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.DotNet.TestFramework;
|
||||
using Microsoft.DotNet.Tools.Test.Utilities;
|
||||
using Xunit;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace Microsoft.DotNet.Kestrel.Tests
|
||||
{
|
||||
public class DotnetRunTest : TestBase
|
||||
{
|
||||
private const string KestrelSampleBase = "KestrelSample";
|
||||
private const string KestrelPortable = "KestrelPortable";
|
||||
private const string KestrelStandalone = "KestrelStandalone";
|
||||
|
||||
[Fact]
|
||||
public void ItRunsKestrelPortableApp()
|
||||
{
|
||||
TestInstance instance = TestAssetsManager.CreateTestInstance(KestrelSampleBase)
|
||||
.WithLockFiles();
|
||||
|
||||
var url = NetworkHelper.GetLocalhostUrlWithFreePort();
|
||||
var args = $"{url} {Guid.NewGuid().ToString()}";
|
||||
var runCommand = new RunCommand(Path.Combine(instance.TestRoot, KestrelPortable));
|
||||
|
||||
try
|
||||
{
|
||||
runCommand.ExecuteAsync(args);
|
||||
NetworkHelper.IsServerUp(url).Should().BeTrue($"Unable to connect to kestrel server - {KestrelPortable} @ {url}");
|
||||
NetworkHelper.TestGetRequest(url, args);
|
||||
}
|
||||
finally
|
||||
{
|
||||
runCommand.KillTree();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItRunsKestrelStandaloneApp()
|
||||
{
|
||||
TestInstance instance = TestAssetsManager.CreateTestInstance(KestrelSampleBase)
|
||||
.WithLockFiles();
|
||||
|
||||
var url = NetworkHelper.GetLocalhostUrlWithFreePort();
|
||||
var args = $"{url} {Guid.NewGuid().ToString()}";
|
||||
var runCommand = new RunCommand(Path.Combine(instance.TestRoot, KestrelStandalone));
|
||||
|
||||
try
|
||||
{
|
||||
runCommand.ExecuteAsync(args);
|
||||
NetworkHelper.IsServerUp(url).Should().BeTrue($"Unable to connect to kestrel server - {KestrelStandalone} @ {url}");
|
||||
NetworkHelper.TestGetRequest(url, args);
|
||||
}
|
||||
finally
|
||||
{
|
||||
runCommand.KillTree();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
142
test/Kestrel.Tests/DotnetTest.cs
Normal file
142
test/Kestrel.Tests/DotnetTest.cs
Normal file
|
@ -0,0 +1,142 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
using Microsoft.DotNet.TestFramework;
|
||||
using Microsoft.DotNet.Tools.Test.Utilities;
|
||||
using Xunit;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace Microsoft.DotNet.Kestrel.Tests
|
||||
{
|
||||
public class DotnetTest : TestBase
|
||||
{
|
||||
private const string KestrelSampleBase = "KestrelSample";
|
||||
private const string KestrelPortable = "KestrelPortable";
|
||||
private const string KestrelStandalone = "KestrelStandalone";
|
||||
|
||||
[Fact]
|
||||
public void ItRunsKestrelPortableAfterBuild()
|
||||
{
|
||||
TestInstance instance = TestAssetsManager.CreateTestInstance(KestrelSampleBase)
|
||||
.WithLockFiles();
|
||||
|
||||
var url = NetworkHelper.GetLocalhostUrlWithFreePort();
|
||||
var args = $"{url} {Guid.NewGuid().ToString()}";
|
||||
var dotnetCommand = new DotnetCommand();
|
||||
var output = Build(Path.Combine(instance.TestRoot, KestrelPortable));
|
||||
|
||||
try
|
||||
{
|
||||
dotnetCommand.ExecuteAsync($"{output} {args}");
|
||||
NetworkHelper.IsServerUp(url).Should().BeTrue($"Unable to connect to kestrel server - {KestrelPortable} @ {url}");
|
||||
NetworkHelper.TestGetRequest(url, args);
|
||||
}
|
||||
finally
|
||||
{
|
||||
dotnetCommand.KillTree();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItRunsKestrelStandaloneAfterBuild()
|
||||
{
|
||||
TestInstance instance = TestAssetsManager.CreateTestInstance(KestrelSampleBase)
|
||||
.WithLockFiles();
|
||||
|
||||
var url = NetworkHelper.GetLocalhostUrlWithFreePort();
|
||||
var args = $"{url} {Guid.NewGuid().ToString()}";
|
||||
var dotnetCommand = new DotnetCommand();
|
||||
var output = Build(Path.Combine(instance.TestRoot, KestrelStandalone));
|
||||
|
||||
try
|
||||
{
|
||||
dotnetCommand.ExecuteAsync($"{output} {args}");
|
||||
NetworkHelper.IsServerUp(url).Should().BeTrue($"Unable to connect to kestrel server - {KestrelStandalone} @ {url}");
|
||||
NetworkHelper.TestGetRequest(url, args);
|
||||
}
|
||||
finally
|
||||
{
|
||||
dotnetCommand.KillTree();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItRunsKestrelPortableAfterPublish()
|
||||
{
|
||||
TestInstance instance = TestAssetsManager.CreateTestInstance(KestrelSampleBase)
|
||||
.WithLockFiles();
|
||||
|
||||
var url = NetworkHelper.GetLocalhostUrlWithFreePort();
|
||||
var args = $"{url} {Guid.NewGuid().ToString()}";
|
||||
var dotnetCommand = new DotnetCommand();
|
||||
var output = Publish(Path.Combine(instance.TestRoot, KestrelPortable), true);
|
||||
|
||||
try
|
||||
{
|
||||
dotnetCommand.ExecuteAsync($"{output} {args}");
|
||||
NetworkHelper.IsServerUp(url).Should().BeTrue($"Unable to connect to kestrel server - {KestrelPortable} @ {url}");
|
||||
NetworkHelper.TestGetRequest(url, args);
|
||||
}
|
||||
finally
|
||||
{
|
||||
dotnetCommand.KillTree();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItRunsKestrelStandaloneAfterPublish()
|
||||
{
|
||||
TestInstance instance = TestAssetsManager.CreateTestInstance(KestrelSampleBase)
|
||||
.WithLockFiles();
|
||||
|
||||
var url = NetworkHelper.GetLocalhostUrlWithFreePort();
|
||||
var args = $"{url} {Guid.NewGuid().ToString()}";
|
||||
var output = Publish(Path.Combine(instance.TestRoot, KestrelStandalone), false);
|
||||
var command = new TestCommand(output);
|
||||
|
||||
try
|
||||
{
|
||||
command.ExecuteAsync($"{args}");
|
||||
NetworkHelper.IsServerUp(url).Should().BeTrue($"Unable to connect to kestrel server - {KestrelStandalone} @ {url}");
|
||||
NetworkHelper.TestGetRequest(url, args);
|
||||
}
|
||||
finally
|
||||
{
|
||||
command.KillTree();
|
||||
}
|
||||
}
|
||||
|
||||
private static string Build(string testRoot)
|
||||
{
|
||||
string appName = Path.GetFileName(testRoot);
|
||||
|
||||
var result = new BuildCommand(
|
||||
projectPath: testRoot)
|
||||
.ExecuteWithCapturedOutput();
|
||||
|
||||
result.Should().Pass();
|
||||
|
||||
// the correct build assembly is next to its deps.json file
|
||||
var depsJsonFile = Directory.EnumerateFiles(testRoot, appName + FileNameSuffixes.DepsJson, SearchOption.AllDirectories).First();
|
||||
return Path.Combine(Path.GetDirectoryName(depsJsonFile), appName + ".dll");
|
||||
}
|
||||
|
||||
private static string Publish(string testRoot, bool isPortable)
|
||||
{
|
||||
string appName = Path.GetFileName(testRoot);
|
||||
|
||||
var publishCmd = new PublishCommand(projectPath: testRoot, output: Path.Combine(testRoot, "bin"));
|
||||
var result = publishCmd.ExecuteWithCapturedOutput();
|
||||
result.Should().Pass();
|
||||
|
||||
var publishDir = publishCmd.GetOutputDirectory(portable: isPortable).FullName;
|
||||
return Path.Combine(publishDir, appName + (isPortable ? ".dll" : FileNameSuffixes.CurrentPlatform.Exe));
|
||||
}
|
||||
}
|
||||
}
|
19
test/Kestrel.Tests/Kestrel.Tests.xproj
Normal file
19
test/Kestrel.Tests/Kestrel.Tests.xproj
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0.24720" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.24720</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>605AA1EE-82A4-477B-A711-5944BD7B04E0</ProjectGuid>
|
||||
<RootNamespace>Kestrel.Tests</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
24
test/Kestrel.Tests/project.json
Normal file
24
test/Kestrel.Tests/project.json
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.App": "1.0.0-rc2-23931",
|
||||
"System.Runtime.Serialization.Primitives": "4.1.1-rc2-23931",
|
||||
"Microsoft.DotNet.Tools.Tests.Utilities": {
|
||||
"target": "project"
|
||||
},
|
||||
"Microsoft.DotNet.Cli.Utils": {
|
||||
"target": "project"
|
||||
},
|
||||
"xunit": "2.1.0",
|
||||
"dotnet-test-xunit": "1.0.0-dev-128011-22"
|
||||
},
|
||||
"frameworks": {
|
||||
"netstandardapp1.5": {
|
||||
"imports": [
|
||||
"dnxcore50",
|
||||
"portable-net45+win8"
|
||||
]
|
||||
}
|
||||
},
|
||||
"testRunner": "xunit"
|
||||
}
|
|
@ -14,11 +14,11 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
|||
{
|
||||
public class DirectoryInfoAssertions
|
||||
{
|
||||
private DirectoryInfo _dirInfo;
|
||||
private DirectoryInfo _dirInfo;
|
||||
|
||||
public DirectoryInfoAssertions(DirectoryInfo dir)
|
||||
{
|
||||
_dirInfo = dir;
|
||||
_dirInfo = dir;
|
||||
}
|
||||
|
||||
public DirectoryInfo DirectoryInfo => _dirInfo;
|
||||
|
@ -64,5 +64,21 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
|||
|
||||
return new AndConstraint<DirectoryInfoAssertions>(new DirectoryInfoAssertions(dir));
|
||||
}
|
||||
|
||||
public AndConstraint<DirectoryInfoAssertions> OnlyHaveFiles(IEnumerable<string> expectedFiles)
|
||||
{
|
||||
var actualFiles = _dirInfo.EnumerateFiles("*", SearchOption.TopDirectoryOnly).Select(f => f.Name);
|
||||
var missingFiles = Enumerable.Except(expectedFiles, actualFiles);
|
||||
var extraFiles = Enumerable.Except(actualFiles, expectedFiles);
|
||||
var nl = Environment.NewLine;
|
||||
|
||||
Execute.Assertion.ForCondition(!missingFiles.Any())
|
||||
.FailWith($"Following files cannot be found inside directory {_dirInfo.FullName} {nl} {string.Join(nl, missingFiles)}");
|
||||
|
||||
Execute.Assertion.ForCondition(!extraFiles.Any())
|
||||
.FailWith($"Following extra files are found inside directory {_dirInfo.FullName} {nl} {string.Join(nl, extraFiles)}");
|
||||
|
||||
return new AndConstraint<DirectoryInfoAssertions>(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||
{
|
||||
public sealed class DotnetCommand : TestCommand
|
||||
{
|
||||
public DotnetCommand()
|
||||
: base("dotnet")
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||
{
|
||||
|
@ -60,10 +61,10 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
|||
|
||||
public RunCommand(
|
||||
string projectPath,
|
||||
string framework="",
|
||||
string configuration="",
|
||||
bool preserveTemporary=false,
|
||||
string appArgs="")
|
||||
string framework = "",
|
||||
string configuration = "",
|
||||
bool preserveTemporary = false,
|
||||
string appArgs = "")
|
||||
: base("dotnet")
|
||||
{
|
||||
_projectPath = projectPath;
|
||||
|
@ -78,12 +79,19 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
|||
args = $"run {BuildArgs()} {args}";
|
||||
return base.Execute(args);
|
||||
}
|
||||
|
||||
public override CommandResult ExecuteWithCapturedOutput(string args = "")
|
||||
{
|
||||
args = $"run {BuildArgs()} {args}";
|
||||
return base.ExecuteWithCapturedOutput(args);
|
||||
}
|
||||
|
||||
public override Task<CommandResult> ExecuteAsync(string args = "")
|
||||
{
|
||||
args = $"run {BuildArgs()} {args}";
|
||||
return base.ExecuteAsync(args);
|
||||
}
|
||||
|
||||
private string BuildArgs()
|
||||
{
|
||||
return $"{ProjectPathOption} {FrameworkOption} {ConfigurationOption} {PreserveTemporaryOption} {AppArgsArgument}";
|
||||
|
|
|
@ -6,6 +6,8 @@ using System;
|
|||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||
{
|
||||
|
@ -15,6 +17,8 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
|||
|
||||
public string WorkingDirectory { get; set; }
|
||||
|
||||
public Process CurrentProcess { get; set; }
|
||||
|
||||
public Dictionary<string, string> Environment { get; } = new Dictionary<string, string>();
|
||||
|
||||
public TestCommand(string command)
|
||||
|
@ -38,13 +42,29 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
|||
return RunProcess(commandPath, args, stdOut, stdErr);
|
||||
}
|
||||
|
||||
public virtual Task<CommandResult> ExecuteAsync(string args = "")
|
||||
{
|
||||
var commandPath = _command;
|
||||
ResolveCommand(ref commandPath, ref args);
|
||||
|
||||
Console.WriteLine($"Executing - {commandPath} {args}");
|
||||
|
||||
var stdOut = new StreamForwarder();
|
||||
var stdErr = new StreamForwarder();
|
||||
|
||||
stdOut.ForwardTo(writeLine: Reporter.Output.WriteLine);
|
||||
stdErr.ForwardTo(writeLine: Reporter.Output.WriteLine);
|
||||
|
||||
return RunProcessAsync(commandPath, args, stdOut, stdErr);
|
||||
}
|
||||
|
||||
public virtual CommandResult ExecuteWithCapturedOutput(string args = "")
|
||||
{
|
||||
var command = _command;
|
||||
ResolveCommand(ref command, ref args);
|
||||
var commandPath = Env.GetCommandPath(command, ".exe", ".cmd", "") ??
|
||||
Env.GetCommandPathFromRootPath(AppContext.BaseDirectory, command, ".exe", ".cmd", "");
|
||||
|
||||
|
||||
Console.WriteLine($"Executing (Captured Output) - {commandPath} {args}");
|
||||
|
||||
var stdOut = new StreamForwarder();
|
||||
|
@ -55,7 +75,17 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
|||
|
||||
return RunProcess(commandPath, args, stdOut, stdErr);
|
||||
}
|
||||
|
||||
|
||||
public void KillTree()
|
||||
{
|
||||
if (CurrentProcess == null)
|
||||
{
|
||||
throw new InvalidOperationException("No process is available to be killed");
|
||||
}
|
||||
|
||||
CurrentProcess.KillTree();
|
||||
}
|
||||
|
||||
private void ResolveCommand(ref string executable, ref string args)
|
||||
{
|
||||
if (executable.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
|
||||
|
@ -77,13 +107,55 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
|||
}
|
||||
|
||||
private CommandResult RunProcess(string executable, string args, StreamForwarder stdOut, StreamForwarder stdErr)
|
||||
{
|
||||
CurrentProcess = StartProcess(executable, args);
|
||||
var threadOut = stdOut.BeginRead(CurrentProcess.StandardOutput);
|
||||
var threadErr = stdErr.BeginRead(CurrentProcess.StandardError);
|
||||
|
||||
CurrentProcess.WaitForExit();
|
||||
threadOut.Join();
|
||||
threadErr.Join();
|
||||
|
||||
var result = new CommandResult(
|
||||
CurrentProcess.StartInfo,
|
||||
CurrentProcess.ExitCode,
|
||||
stdOut.CapturedOutput,
|
||||
stdErr.CapturedOutput);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Task<CommandResult> RunProcessAsync(string executable, string args, StreamForwarder stdOut, StreamForwarder stdErr)
|
||||
{
|
||||
CurrentProcess = StartProcess(executable, args);
|
||||
var threadOut = stdOut.BeginRead(CurrentProcess.StandardOutput);
|
||||
var threadErr = stdErr.BeginRead(CurrentProcess.StandardError);
|
||||
|
||||
var tcs = new TaskCompletionSource<CommandResult>();
|
||||
CurrentProcess.Exited += (sender, arg) =>
|
||||
{
|
||||
threadOut.Join();
|
||||
threadErr.Join();
|
||||
var result = new CommandResult(
|
||||
CurrentProcess.StartInfo,
|
||||
CurrentProcess.ExitCode,
|
||||
stdOut.CapturedOutput,
|
||||
stdErr.CapturedOutput);
|
||||
tcs.SetResult(result);
|
||||
};
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
private Process StartProcess(string executable, string args)
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = executable,
|
||||
Arguments = args,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardOutput = true
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardInput = true
|
||||
};
|
||||
|
||||
foreach (var item in Environment)
|
||||
|
@ -103,21 +175,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
|||
|
||||
process.EnableRaisingEvents = true;
|
||||
process.Start();
|
||||
|
||||
var threadOut = stdOut.BeginRead(process.StandardOutput);
|
||||
var threadErr = stdErr.BeginRead(process.StandardError);
|
||||
|
||||
process.WaitForExit();
|
||||
threadOut.Join();
|
||||
threadErr.Join();
|
||||
|
||||
var result = new CommandResult(
|
||||
process.StartInfo,
|
||||
process.ExitCode,
|
||||
stdOut.CapturedOutput,
|
||||
stdErr.CapturedOutput);
|
||||
|
||||
return result;
|
||||
return process;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.DotNet.ProjectModel;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||
{
|
||||
public class NetworkHelper
|
||||
{
|
||||
// in milliseconds
|
||||
private const int Timeout = 20000;
|
||||
|
||||
private static Queue<TcpListener> s_PortPool = new Queue<TcpListener>();
|
||||
|
||||
public static string Localhost { get; } = "http://localhost";
|
||||
|
||||
public static bool IsServerUp(string url)
|
||||
{
|
||||
return SpinWait.SpinUntil(() =>
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
try
|
||||
{
|
||||
client.BaseAddress = new Uri(url);
|
||||
HttpResponseMessage response = client.GetAsync("").Result;
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}, Timeout);
|
||||
}
|
||||
|
||||
public static void TestGetRequest(string url, string expectedResponse)
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
client.BaseAddress = new Uri(url);
|
||||
|
||||
HttpResponseMessage response = client.GetAsync("").Result;
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var responseString = response.Content.ReadAsStringAsync().Result;
|
||||
responseString.Should().Contain(expectedResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetFreePort()
|
||||
{
|
||||
lock (s_PortPool)
|
||||
{
|
||||
if (s_PortPool.Count == 0)
|
||||
{
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
var tcpl = new TcpListener(IPAddress.Loopback, 0);
|
||||
tcpl.Start();
|
||||
s_PortPool.Enqueue(tcpl);
|
||||
}
|
||||
|
||||
Console.WriteLine($"Ports Count >>>>>>>>>>>>>>>>>>> {s_PortPool.Count}");
|
||||
}
|
||||
|
||||
var currentTcpl = s_PortPool.Dequeue();
|
||||
var port = ((IPEndPoint)currentTcpl.LocalEndpoint).Port;
|
||||
currentTcpl.Stop();
|
||||
currentTcpl = null;
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
|
||||
return port;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetLocalhostUrlWithFreePort()
|
||||
{
|
||||
return $"{Localhost}:{GetFreePort()}/";
|
||||
}
|
||||
}
|
||||
}
|
110
test/Microsoft.DotNet.Tools.Tests.Utilities/ProcessExtensions.cs
Normal file
110
test/Microsoft.DotNet.Tools.Tests.Utilities/ProcessExtensions.cs
Normal file
|
@ -0,0 +1,110 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||
{
|
||||
internal static class ProcessExtensions
|
||||
{
|
||||
private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
public static void KillTree(this Process process)
|
||||
{
|
||||
process.KillTree(_defaultTimeout);
|
||||
}
|
||||
|
||||
public static void KillTree(this Process process, TimeSpan timeout)
|
||||
{
|
||||
string stdout;
|
||||
if (_isWindows)
|
||||
{
|
||||
RunProcessAndWaitForExit(
|
||||
"taskkill",
|
||||
$"/T /F /PID {process.Id}",
|
||||
timeout,
|
||||
out stdout);
|
||||
}
|
||||
else
|
||||
{
|
||||
var children = new HashSet<int>();
|
||||
GetAllChildIdsUnix(process.Id, children, timeout);
|
||||
foreach (var childId in children)
|
||||
{
|
||||
KillProcessUnix(childId, timeout);
|
||||
}
|
||||
KillProcessUnix(process.Id, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
|
||||
{
|
||||
string stdout;
|
||||
var exitCode = RunProcessAndWaitForExit(
|
||||
"pgrep",
|
||||
$"-P {parentId}",
|
||||
timeout,
|
||||
out stdout);
|
||||
|
||||
if (exitCode == 0 && !string.IsNullOrEmpty(stdout))
|
||||
{
|
||||
using (var reader = new StringReader(stdout))
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var text = reader.ReadLine();
|
||||
if (text == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int id;
|
||||
if (int.TryParse(text, out id))
|
||||
{
|
||||
children.Add(id);
|
||||
// Recursively get the children
|
||||
GetAllChildIdsUnix(id, children, timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void KillProcessUnix(int processId, TimeSpan timeout)
|
||||
{
|
||||
string stdout;
|
||||
RunProcessAndWaitForExit(
|
||||
"kill",
|
||||
$"-TERM {processId}",
|
||||
timeout,
|
||||
out stdout);
|
||||
}
|
||||
|
||||
private static int RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string stdout)
|
||||
{
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = arguments,
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false
|
||||
};
|
||||
|
||||
var process = Process.Start(startInfo);
|
||||
|
||||
stdout = null;
|
||||
if (process.WaitForExit((int)timeout.TotalMilliseconds))
|
||||
{
|
||||
stdout = process.StandardOutput.ReadToEnd();
|
||||
}
|
||||
else
|
||||
{
|
||||
process.Kill();
|
||||
}
|
||||
|
||||
return process.ExitCode;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue