Merge pull request #2269 from Sridhar-MS/kestrel-tests2
Add kestrel tests.
This commit is contained in:
commit
b772db9b29
19 changed files with 751 additions and 25 deletions
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"version": "1.0.0-*",
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-20113",
|
||||||
|
"Microsoft.AspNetCore.Hosting": "1.0.0-rc2-20113",
|
||||||
|
"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.AspNetCore.Hosting": "1.0.0-rc2-20113",
|
||||||
|
"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",
|
"Microsoft.Extensions.DependencyModel.Tests",
|
||||||
"ArgumentForwardingTests",
|
"ArgumentForwardingTests",
|
||||||
"dotnet-test.UnitTests",
|
"dotnet-test.UnitTests",
|
||||||
"dotnet-test.Tests"
|
"dotnet-test.Tests",
|
||||||
|
"Kestrel.Tests"
|
||||||
};
|
};
|
||||||
|
|
||||||
public static readonly dynamic[] ConditionalTestAssets = new[]
|
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
|
public class DirectoryInfoAssertions
|
||||||
{
|
{
|
||||||
private DirectoryInfo _dirInfo;
|
private DirectoryInfo _dirInfo;
|
||||||
|
|
||||||
public DirectoryInfoAssertions(DirectoryInfo dir)
|
public DirectoryInfoAssertions(DirectoryInfo dir)
|
||||||
{
|
{
|
||||||
_dirInfo = dir;
|
_dirInfo = dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DirectoryInfo DirectoryInfo => _dirInfo;
|
public DirectoryInfo DirectoryInfo => _dirInfo;
|
||||||
|
@ -64,5 +64,21 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||||
|
|
||||||
return new AndConstraint<DirectoryInfoAssertions>(new DirectoryInfoAssertions(dir));
|
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.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
using Microsoft.DotNet.Cli.Utils;
|
using Microsoft.DotNet.Cli.Utils;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||||
{
|
{
|
||||||
|
@ -60,10 +61,10 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||||
|
|
||||||
public RunCommand(
|
public RunCommand(
|
||||||
string projectPath,
|
string projectPath,
|
||||||
string framework="",
|
string framework = "",
|
||||||
string configuration="",
|
string configuration = "",
|
||||||
bool preserveTemporary=false,
|
bool preserveTemporary = false,
|
||||||
string appArgs="")
|
string appArgs = "")
|
||||||
: base("dotnet")
|
: base("dotnet")
|
||||||
{
|
{
|
||||||
_projectPath = projectPath;
|
_projectPath = projectPath;
|
||||||
|
@ -78,12 +79,19 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||||
args = $"run {BuildArgs()} {args}";
|
args = $"run {BuildArgs()} {args}";
|
||||||
return base.Execute(args);
|
return base.Execute(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override CommandResult ExecuteWithCapturedOutput(string args = "")
|
public override CommandResult ExecuteWithCapturedOutput(string args = "")
|
||||||
{
|
{
|
||||||
args = $"run {BuildArgs()} {args}";
|
args = $"run {BuildArgs()} {args}";
|
||||||
return base.ExecuteWithCapturedOutput(args);
|
return base.ExecuteWithCapturedOutput(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Task<CommandResult> ExecuteAsync(string args = "")
|
||||||
|
{
|
||||||
|
args = $"run {BuildArgs()} {args}";
|
||||||
|
return base.ExecuteAsync(args);
|
||||||
|
}
|
||||||
|
|
||||||
private string BuildArgs()
|
private string BuildArgs()
|
||||||
{
|
{
|
||||||
return $"{ProjectPathOption} {FrameworkOption} {ConfigurationOption} {PreserveTemporaryOption} {AppArgsArgument}";
|
return $"{ProjectPathOption} {FrameworkOption} {ConfigurationOption} {PreserveTemporaryOption} {AppArgsArgument}";
|
||||||
|
|
|
@ -6,6 +6,8 @@ using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Tools.Test.Utilities
|
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||||
{
|
{
|
||||||
|
@ -15,6 +17,8 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||||
|
|
||||||
public string WorkingDirectory { get; set; }
|
public string WorkingDirectory { get; set; }
|
||||||
|
|
||||||
|
public Process CurrentProcess { get; set; }
|
||||||
|
|
||||||
public Dictionary<string, string> Environment { get; } = new Dictionary<string, string>();
|
public Dictionary<string, string> Environment { get; } = new Dictionary<string, string>();
|
||||||
|
|
||||||
public TestCommand(string command)
|
public TestCommand(string command)
|
||||||
|
@ -38,13 +42,29 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||||
return RunProcess(commandPath, args, stdOut, stdErr);
|
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 = "")
|
public virtual CommandResult ExecuteWithCapturedOutput(string args = "")
|
||||||
{
|
{
|
||||||
var command = _command;
|
var command = _command;
|
||||||
ResolveCommand(ref command, ref args);
|
ResolveCommand(ref command, ref args);
|
||||||
var commandPath = Env.GetCommandPath(command, ".exe", ".cmd", "") ??
|
var commandPath = Env.GetCommandPath(command, ".exe", ".cmd", "") ??
|
||||||
Env.GetCommandPathFromRootPath(AppContext.BaseDirectory, command, ".exe", ".cmd", "");
|
Env.GetCommandPathFromRootPath(AppContext.BaseDirectory, command, ".exe", ".cmd", "");
|
||||||
|
|
||||||
Console.WriteLine($"Executing (Captured Output) - {commandPath} {args}");
|
Console.WriteLine($"Executing (Captured Output) - {commandPath} {args}");
|
||||||
|
|
||||||
var stdOut = new StreamForwarder();
|
var stdOut = new StreamForwarder();
|
||||||
|
@ -55,7 +75,17 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||||
|
|
||||||
return RunProcess(commandPath, args, stdOut, stdErr);
|
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)
|
private void ResolveCommand(ref string executable, ref string args)
|
||||||
{
|
{
|
||||||
if (executable.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
|
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)
|
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
|
var psi = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
FileName = executable,
|
FileName = executable,
|
||||||
Arguments = args,
|
Arguments = args,
|
||||||
RedirectStandardError = true,
|
RedirectStandardError = true,
|
||||||
RedirectStandardOutput = true
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardInput = true
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var item in Environment)
|
foreach (var item in Environment)
|
||||||
|
@ -103,21 +175,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||||
|
|
||||||
process.EnableRaisingEvents = true;
|
process.EnableRaisingEvents = true;
|
||||||
process.Start();
|
process.Start();
|
||||||
|
return process;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FluentAssertions;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||||
|
{
|
||||||
|
public class NetworkHelper
|
||||||
|
{
|
||||||
|
// in milliseconds
|
||||||
|
private const int Timeout = 20000;
|
||||||
|
|
||||||
|
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 string GetLocalhostUrlWithFreePort()
|
||||||
|
{
|
||||||
|
return $"{Localhost}:{PortManager.GetPort()}/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||||
|
{
|
||||||
|
public static class PortManager
|
||||||
|
{
|
||||||
|
private static int s_nextPort = 8001;
|
||||||
|
|
||||||
|
public static int GetPort()
|
||||||
|
{
|
||||||
|
return Interlocked.Increment(ref s_nextPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@
|
||||||
"Microsoft.NETCore.App": "1.0.0-rc2-23931",
|
"Microsoft.NETCore.App": "1.0.0-rc2-23931",
|
||||||
"System.Runtime.Serialization.Primitives": "4.1.1-rc2-23931",
|
"System.Runtime.Serialization.Primitives": "4.1.1-rc2-23931",
|
||||||
"System.Collections.Immutable": "1.2.0-rc2-23931",
|
"System.Collections.Immutable": "1.2.0-rc2-23931",
|
||||||
|
"System.Net.NetworkInformation": "4.1.0-rc2-23931",
|
||||||
"FluentAssertions": "4.0.0",
|
"FluentAssertions": "4.0.0",
|
||||||
"xunit": "2.1.0",
|
"xunit": "2.1.0",
|
||||||
"dotnet-test-xunit": "1.0.0-dev-128011-22",
|
"dotnet-test-xunit": "1.0.0-dev-128011-22",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue