Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bump Verify.Xunit from 23.3.0 to 23.4.0 #8

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions Chisel.sln
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{89268D80-B21
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{AC8C6685-EDF9-443A-BAF6-A5E7CF777B2A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApp", "tests\TestApp\TestApp.csproj", "{8B1B3D6A-7100-4DFB-97C9-CF5ACF1A3B08}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -43,10 +45,13 @@ Global
{845EDA2A-5207-4C6D-ABE9-9635F4630D90}.Debug|Any CPU.Build.0 = Debug|Any CPU
{845EDA2A-5207-4C6D-ABE9-9635F4630D90}.Release|Any CPU.ActiveCfg = Release|Any CPU
{845EDA2A-5207-4C6D-ABE9-9635F4630D90}.Release|Any CPU.Build.0 = Release|Any CPU
{8B1B3D6A-7100-4DFB-97C9-CF5ACF1A3B08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8B1B3D6A-7100-4DFB-97C9-CF5ACF1A3B08}.Release|Any CPU.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{845EDA2A-5207-4C6D-ABE9-9635F4630D90} = {0CC84E67-19D2-480B-B36A-6BB15A9109E7}
{F4CAEC64-3B0C-4ACD-BF87-760A838A5D86} = {89268D80-B21D-4C76-AF7F-796AAD1E00D9}
{EC3CCB92-1AA1-4C33-B296-D7111EEF84E4} = {AC8C6685-EDF9-443A-BAF6-A5E7CF777B2A}
{8B1B3D6A-7100-4DFB-97C9-CF5ACF1A3B08} = {AC8C6685-EDF9-443A-BAF6-A5E7CF777B2A}
EndGlobalSection
EndGlobal
6 changes: 5 additions & 1 deletion tests/Chisel.Tests/Chisel.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,21 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CliWrap" Version="3.6.6" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="GitHubActionsTestLogger" Version="2.3.3" PrivateAssets="all" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="PublicApiGenerator" Version="11.1.0" />
<PackageReference Include="System.Text.Json" Version="8.0.2" />
<PackageReference Include="Verify.Xunit" Version="23.2.3" />
<PackageReference Include="Verify.Xunit" Version="23.4.0" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
<Reference Include="NuGet.Frameworks">
<HintPath>$([MSBuild]::NormalizePath('$(MSBuildExtensionsPath)', 'NuGet.Frameworks.dll'))</HintPath>
</Reference>
<Reference Include="NuGet.ProjectModel">
<HintPath>$([MSBuild]::NormalizePath('$(MSBuildExtensionsPath)', 'NuGet.ProjectModel.dll'))</HintPath>
</Reference>
Expand Down
92 changes: 92 additions & 0 deletions tests/Chisel.Tests/ChiseledAppTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CliWrap;
using CliWrap.Exceptions;
using FluentAssertions;
using FluentAssertions.Execution;
using Xunit;
using Xunit.Abstractions;

namespace Chisel.Tests;

[Trait("Category", "Integration")]
public sealed class ChiseledAppTests : IDisposable, IClassFixture<TestApp>
{
private readonly ITestOutputHelper _outputHelper;
private readonly TestApp _testApp;
private readonly AssertionScope _scope;

public ChiseledAppTests(ITestOutputHelper outputHelper, TestApp testApp)
{
_outputHelper = outputHelper;
_testApp = testApp;
_scope = new AssertionScope();
}

public void Dispose()
{
_scope.Dispose();
}

public static readonly TheoryData<PublishMode> PublishModeData = new(Enum.GetValues<PublishMode>());

[Theory]
[MemberData(nameof(PublishModeData))]
public async Task RunTestApp(PublishMode publishMode)
{
var (stdOut, stdErr) = await RunTestAppAsync(publishMode);
var allDlls = stdOut.Split(Environment.NewLine).Where(e => e.EndsWith(".dll"));
var expectedDlls = new[]
{
"Microsoft.Data.SqlClient.dll",
"Microsoft.Data.SqlClient.SNI.dll",
"Microsoft.Extensions.DependencyModel.dll",
"Microsoft.Identity.Client.dll",
"Microsoft.IdentityModel.Abstractions.dll",
"Microsoft.SqlServer.Server.dll",
"System.Configuration.ConfigurationManager.dll",
"System.Diagnostics.EventLog.dll",
"System.Diagnostics.EventLog.Messages.dll",
"System.Runtime.Caching.dll",
"System.Security.Cryptography.ProtectedData.dll",
"TestApp.dll",
};
allDlls.Except(expectedDlls).Should().BeEmpty();
stdOut.Should().Contain("✅");
stdErr.Should().BeEmpty();
}

private async Task<(string StdOut, string StdErr)> RunTestAppAsync(PublishMode publishMode, params string[] args)
{
var stdOutBuilder = new StringBuilder();
var stdErrBuilder = new StringBuilder();

var command = Cli.Wrap(_testApp.GetExecutablePath(publishMode))
.WithArguments(args)
.WithValidation(CommandResultValidation.None)
.WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuilder))
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuilder));

_outputHelper.WriteLine(command.ToString());

var stopwatch = Stopwatch.StartNew();
var result = await command.ExecuteAsync();
var executionTime = stopwatch.ElapsedMilliseconds;

var stdOut = stdOutBuilder.ToString().Trim();
var stdErr = stdErrBuilder.ToString().Trim();

_outputHelper.WriteLine($"⌚ Executed in {executionTime} ms");
_outputHelper.WriteLine(stdOut);

if (result.ExitCode != 0)
{
throw new CommandExecutionException(command, result.ExitCode, $"An unexpected exception has occurred while running {command}{Environment.NewLine}{stdErr}".Trim());
}

return (stdOut, stdErr);
}
}
13 changes: 13 additions & 0 deletions tests/Chisel.Tests/Support/DirectoryInfoExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.IO;
using System.Linq;

namespace Chisel.Tests;

internal static class DirectoryInfoExtensions
{
public static DirectoryInfo SubDirectory(this DirectoryInfo directory, params string[] paths)
=> new(Path.GetFullPath(Path.Combine(paths.Prepend(directory.FullName).ToArray())));

public static FileInfo File(this DirectoryInfo directory, params string[] paths)
=> new(Path.GetFullPath(Path.Combine(paths.Prepend(directory.FullName).ToArray())));
}
18 changes: 18 additions & 0 deletions tests/Chisel.Tests/Support/PublishMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Chisel.Tests;

/// <summary>
/// The possible application publish modes for the TestApp.
/// See also the <a href="https://learn.microsoft.com/en-us/dotnet/core/deploying/">.NET application publishing overview</a> documentation.
/// </summary>
public enum PublishMode
{
/// <summary>
/// Standard app publish, all dlls and related files are copied along the main executable.
/// </summary>
Standard,

/// <summary>
/// Publish a single file as a framework-dependent binary.
/// </summary>
SingleFile,
}
160 changes: 160 additions & 0 deletions tests/Chisel.Tests/Support/TestApp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading.Tasks;
using CliWrap;
using CliWrap.Exceptions;
using FluentAssertions;
using NuGet.Frameworks;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;

namespace Chisel.Tests;

public class TestApp : IAsyncLifetime
{
private readonly IMessageSink _messageSink;
private readonly DirectoryInfo _workingDirectory;
private readonly Dictionary<PublishMode, FileInfo> _executables;

public TestApp(IMessageSink messageSink)
{
_messageSink = messageSink;
var tfm = NuGetFramework.Parse(typeof(TestApp).Assembly.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName ?? throw new InvalidOperationException("TargetFrameworkAttribute not found"));
_workingDirectory = GetDirectory("tests", $"TestApp-{tfm}");
_workingDirectory.Create();
foreach (var file in GetDirectory("tests", "TestApp").EnumerateFiles())
{
file.CopyTo(_workingDirectory.File(file.Name).FullName, overwrite: true);
}
_executables = new Dictionary<PublishMode, FileInfo>();
}

async Task IAsyncLifetime.InitializeAsync()
{
await CreateTestAppAsync();
}

Task IAsyncLifetime.DisposeAsync()
{
_workingDirectory.Delete(recursive: true);
return Task.CompletedTask;
}

public string GetExecutablePath(PublishMode publishMode) => _executables[publishMode].FullName;

private async Task CreateTestAppAsync()
{
// It might be tempting to do pack -> restore -> build --no-restore -> publish --no-build (and parallelize over publish modes)
// But this would fail because of https://github.com/dotnet/sdk/issues/17526 and probably because of other unforeseen bugs
// preventing from running multiple `dotnet publish` commands with different parameters.

await PackAsync();
await RestoreAsync();
foreach (var publishMode in Enum.GetValues<PublishMode>())
{
await PublishAsync(publishMode);
}
}

private async Task PackAsync()
{
var projectFile = GetFile("src", "Chisel", "Chisel.csproj");
var packArgs = new[] {
"pack", projectFile.FullName,
"--configuration", "Release",
"--output", _workingDirectory.FullName,
"-p:MinVerSkip=true",
"-p:Version=0.0.0-IntegrationTest.0",
};
await RunDotnetAsync(_workingDirectory, packArgs);
}

private async Task RestoreAsync()
{
// Can't use "--source . --source https://api.nuget.org/v3/index.json" because of https://github.com/dotnet/sdk/issues/27202 => a nuget.config file is used instead.
// It also has the benefit of using settings _only_ from the specified config file, ignoring the global nuget.config where package source mapping could interfere with the local source.
var restoreArgs = new[] {
"restore",
"--configfile", "nuget.config",
"-p:Configuration=Release",
};
await RunDotnetAsync(_workingDirectory, restoreArgs);
}

private async Task PublishAsync(PublishMode publishMode)
{
var publishDirectory = _workingDirectory.SubDirectory("publish");

var outputDirectory = publishDirectory.SubDirectory(publishMode.ToString());

var publishArgsBase = new[] {
"publish",
"--no-restore",
"--configuration", "Release",
"--output", outputDirectory.FullName,
};
var publishSingleFile = $"-p:PublishSingleFile={publishMode is PublishMode.SingleFile}";
var publishArgs = publishArgsBase.Append(publishSingleFile).ToArray();
await RunDotnetAsync(_workingDirectory, publishArgs);

var executableFileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "TestApp.exe" : "TestApp";
var executableFile = new FileInfo(Path.Combine(outputDirectory.FullName, executableFileName));
executableFile.Exists.Should().BeTrue();
var dlls = executableFile.Directory!.EnumerateFiles("*.dll");
if (publishMode == PublishMode.Standard)
{
dlls.Should().NotBeEmpty(because: $"the test app was _not_ published as single-file ({publishMode})");
}
else
{
dlls.Should().BeEmpty(because: $"the test app was published as single-file ({publishMode})");
executableFile.Directory.EnumerateFiles().Should().ContainSingle().Which.FullName.Should().Be(executableFile.FullName);
}

_executables[publishMode] = executableFile;
}

private async Task RunDotnetAsync(DirectoryInfo workingDirectory, params string[] arguments)
{
var outBuilder = new StringBuilder();
var errBuilder = new StringBuilder();
var command = Cli.Wrap("dotnet")
.WithValidation(CommandResultValidation.None)
.WithWorkingDirectory(workingDirectory.FullName)
.WithArguments(arguments)
.WithStandardOutputPipe(PipeTarget.ToDelegate(line =>
{
outBuilder.AppendLine(line);
_messageSink.OnMessage(new DiagnosticMessage($"==> out: {line}"));
}))
.WithStandardErrorPipe(PipeTarget.ToDelegate(line =>
{
errBuilder.AppendLine(line);
_messageSink.OnMessage(new DiagnosticMessage($"==> err: {line}"));
}));

_messageSink.OnMessage(new DiagnosticMessage($"📁 {workingDirectory.FullName} 🛠️ {command}"));

var result = await command.ExecuteAsync();
if (result.ExitCode != 0)
{
throw new CommandExecutionException(command, result.ExitCode, $"An unexpected exception has occurred while running {command}{Environment.NewLine}{errBuilder}{outBuilder}".Trim());
}
}

private static DirectoryInfo GetDirectory(params string[] paths) => new(GetFullPath(paths));

private static FileInfo GetFile(params string[] paths) => new(GetFullPath(paths));

private static string GetFullPath(params string[] paths) => Path.GetFullPath(Path.Combine(new[] { GetThisDirectory(), "..", "..", ".." }.Concat(paths).ToArray()));

private static string GetThisDirectory([CallerFilePath] string path = "") => Path.GetDirectoryName(path)!;
}
Loading
Loading