Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
feat(dotnet): Add TUnit integration tests for the NuGet launcher
Tests cover both the framework-dependent fallback (expects exit code 1
and an unsupported-platform error) and the platform-specific happy path
(publishes with the current RID, copies the native binary, and asserts
the version output).

Includes test helpers: DotnetProject, PathUtilities, PlatformUtilities,
ProcessResult, and JsonUtilities.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
  • Loading branch information
Flash0ver and claude committed Feb 19, 2026
commit a374ae413629f8c0dcbd93897b6b3cb50cde751a
67 changes: 67 additions & 0 deletions src/dotnet/Sentry.Cli.Tests/DotnetProject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
namespace Sentry.Cli.Tests;

internal sealed class DotnetProject
{
private readonly FileInfo _project;
private readonly string _configuration;

public DotnetProject(string projectPath)
: this(new FileInfo(projectPath))
{
}

public DotnetProject(FileInfo project)
{
_project = project;

#if DEBUG
_configuration = "Debug";
#else
_configuration = "Release";
#endif
}

public Task<ProcessResult> RunAsync()
{
return ExecAsync("dotnet", ["run",
"--project", _project.FullName,
"--configuration", _configuration]);
}

public Task<ProcessResult> PublishAsync(string rid, string outputDirectory)
{
return ExecAsync("dotnet", ["publish", _project.FullName,
"--configuration", _configuration,
"--runtime", rid,
"--output", outputDirectory,
"--property:PublishAot=true"]);
}

public static Task<ProcessResult> ExecAsync(string fileName)
{
return ExecAsync(fileName, []);
}

public static async Task<ProcessResult> ExecAsync(string fileName, ICollection<string> arguments)
{
ProcessStartInfo startInfo = new(fileName, arguments)
{
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
};

using var process = Process.Start(startInfo);

await Assert.That(process).IsNotNull();

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await process.WaitForExitAsync(cts.Token);

var stdout = await process.StandardOutput.ReadToEndAsync(CancellationToken.None);
var stderr = await process.StandardError.ReadToEndAsync(CancellationToken.None);

return new ProcessResult(process.ExitCode, stdout.Trim(), stderr.Trim());
}
}
15 changes: 15 additions & 0 deletions src/dotnet/Sentry.Cli.Tests/JsonUtilities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Text.Json;

namespace Sentry.Cli.Tests;

internal static class JsonUtilities
{
internal static async Task<string> GetVersionAsync(FileInfo packageJson)
{
await using var stream = File.OpenRead(packageJson.FullName);
using var document = await JsonDocument.ParseAsync(stream);

var version = document.RootElement.GetProperty("version");
return version.GetString()!;
}
}
39 changes: 39 additions & 0 deletions src/dotnet/Sentry.Cli.Tests/LauncherTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
namespace Sentry.Cli.Tests;

[NotInParallel]
public class LauncherTests
{
[Test]
public async Task Launch_FrameworkDependent_HasNoSentryCli()
{
var project = PathUtilities.LauncherProject;

var result = await project.RunAsync();

await result.AssertFailureAsync();
await result.AssertErrorAsync($"Unsupported platform: {RuntimeInformation.OSDescription} ({RuntimeInformation.OSArchitecture})");
}

[Test]
public async Task Launch_PlatformSpecific_HasSentryCli()
{
var project = PathUtilities.LauncherProject;
var artifacts = PathUtilities.ArtifactsDirectory;

var output = Path.Combine(artifacts.FullName, "test");
var result = await project.PublishAsync(RuntimeInformation.RuntimeIdentifier, output);
await result.AssertSuccessAsync();

// copy from dist-bin to test artifacts
var sourceFileName = Path.Combine(PathUtilities.BinaryDirectory.FullName, PlatformUtilities.GetNativeExecutableName());
var destFileName = Path.Combine(output, PlatformUtilities.GetNativeExecutableName());
File.Copy(sourceFileName, destFileName, true);

var executable = Path.Combine(output, "Sentry.Cli");
var exec = await DotnetProject.ExecAsync(executable, ["--version"]);

var version = await JsonUtilities.GetVersionAsync(PathUtilities.PackageFile);
await exec.AssertSuccessAsync();
await exec.AssertOutputAsync(version);
}
}
10 changes: 0 additions & 10 deletions src/dotnet/Sentry.Cli.Tests/MyTests.cs

This file was deleted.

85 changes: 85 additions & 0 deletions src/dotnet/Sentry.Cli.Tests/PathUtilities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System.Runtime.CompilerServices;

namespace Sentry.Cli.Tests;

internal static class PathUtilities
{
private static readonly Lazy<DirectoryInfo> s_testProjectDirectory = new(() => GetTestProjectDirectory());
private static readonly Lazy<DotnetProject> s_launcherProject = new(GetLauncherProject);
private static readonly Lazy<DirectoryInfo> s_artifactsDirectory = new(GetArtifactsDirectory);
private static readonly Lazy<DirectoryInfo> s_binaryDirectory = new(GetBinaryDirectory);
private static readonly Lazy<FileInfo> s_packageFile = new(GetPackageFile);

internal static DotnetProject LauncherProject => s_launcherProject.Value;
internal static DirectoryInfo ArtifactsDirectory => s_artifactsDirectory.Value;
internal static DirectoryInfo BinaryDirectory => s_binaryDirectory.Value;
internal static FileInfo PackageFile => s_packageFile.Value;

private static DirectoryInfo GetTestProjectDirectory([CallerFilePath] string? sourceFilePath = null)
{
var testProjectPath = Path.GetDirectoryName(sourceFilePath);
Assert.NotNull(testProjectPath);

FileInfo testProject = new(Path.Combine(testProjectPath, "Sentry.Cli.Tests.csproj"));

if (!testProject.Exists)
{
Assert.Fail("Test project not found.");
}

Assert.NotNull(testProject.Directory);
return testProject.Directory;
}

private static DotnetProject GetLauncherProject()
{
var testProjectDirectory = s_testProjectDirectory.Value;
FileInfo project = new(Path.Combine(testProjectDirectory.FullName, "../Sentry.Cli/Sentry.Cli.csproj"));

if (!project.Exists)
{
Assert.Fail("Launcher project not found.");
}

return new DotnetProject(project);
}

private static DirectoryInfo GetArtifactsDirectory()
{
var testProjectDirectory = s_testProjectDirectory.Value;
DirectoryInfo artifacts = new(Path.Combine(testProjectDirectory.FullName, "../artifacts"));

if (!artifacts.Exists)
{
Assert.Fail("Artifacts path not found.");
}

return artifacts;
}

private static DirectoryInfo GetBinaryDirectory()
{
var testProjectDirectory = s_testProjectDirectory.Value;
DirectoryInfo binary = new(Path.Combine(testProjectDirectory.FullName, "../../../dist-bin"));

if (!binary.Exists)
{
Assert.Fail("Binary path not found.");
}

return binary;
}

private static FileInfo GetPackageFile()
{
var testProjectDirectory = s_testProjectDirectory.Value;
FileInfo package = new(Path.Combine(testProjectDirectory.FullName, "../../../package.json"));

if (!package.Exists)
{
Assert.Fail("Package JSON not found.");
}

return package;
}
}
23 changes: 23 additions & 0 deletions src/dotnet/Sentry.Cli.Tests/PlatformUtilities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Sentry.Cli.Tests;

internal static class PlatformUtilities
{
internal static string GetNativeExecutableName()
{
var platform = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "linux" :
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "darwin" :
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "windows" :
throw new PlatformNotSupportedException($"Unsupported platform: {RuntimeInformation.OSDescription} ({RuntimeInformation.OSArchitecture})");

var architecture = RuntimeInformation.OSArchitecture switch
{
Architecture.Arm64 => "arm64",
Architecture.X64 => "x64",
_ => throw new PlatformNotSupportedException($"Unsupported platform: {RuntimeInformation.OSDescription} ({RuntimeInformation.OSArchitecture})"),
};

return OperatingSystem.IsWindows()
? $"sentry-{platform}-{architecture}.exe"
: $"sentry-{platform}-{architecture}";
}
}
46 changes: 46 additions & 0 deletions src/dotnet/Sentry.Cli.Tests/ProcessResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
namespace Sentry.Cli.Tests;

internal sealed class ProcessResult
{
private readonly int _exitCode;
private readonly string _output;
private readonly string _error;

public ProcessResult(int exitCode, string output, string error)
{
_exitCode = exitCode;
_output = output;
_error = error;
}

public int ExitCode => _exitCode;
public string Output => _output;
public string Error => _error;

public async Task AssertSuccessAsync()
{
await Assert.That(_exitCode).IsZero();
}

public async Task AssertFailureAsync()
{
await Assert.That(_exitCode).IsNotZero();
}

public async Task AssertFailureAsync(int exitCode)
{
await Assert.That(_exitCode).IsEqualTo(exitCode);
}

public async Task AssertOutputAsync(string output)
{
await Assert.That(_output).IsEqualTo(output);
await Assert.That(_error).IsEmpty();
}

public async Task AssertErrorAsync(string error)
{
await Assert.That(_output).IsEmpty();
await Assert.That(_error).IsEqualTo(error);
}
}