Skip to content

Commit

Permalink
feat: Enumerate all .NET Framework installations (#572)
Browse files Browse the repository at this point in the history
Co-authored-by: Bruno Garcia <[email protected]>
  • Loading branch information
lucas-zimerman and bruno-garcia authored Nov 6, 2020
1 parent 2e3a433 commit db6d590
Show file tree
Hide file tree
Showing 13 changed files with 402 additions and 16 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## unreleased

* Add a list of .NET Frameworks installed when available. (#531) @lucas-zimerman

## 3.0.0-alpha.4

* Add the client user ip if both SendDefaultPii and IsEnvironmentUser are set. (#1015) @lucas-zimerman
Expand Down
17 changes: 17 additions & 0 deletions src/Sentry/Integrations/NetFxInstallationsIntegration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#if NETFX
using Sentry.PlatformAbstractions;

namespace Sentry.Integrations
{
internal class NetFxInstallationsIntegration : ISdkIntegration
{
public void Register(IHub hub, SentryOptions options)
{
if (!Runtime.Current.IsMono())
{
options.AddEventProcessor(new NetFxInstallationsEventProcessor(options));
}
}
}
}
#endif
8 changes: 4 additions & 4 deletions src/Sentry/PlatformAbstractions/FrameworkInfo.NetFx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public static IEnumerable<FrameworkInstallation> GetInstallations()
if (version != null && versionKey.GetInt("Install") == 1)
{
// 1.0 to 3.5
Version.TryParse(version, out var parsed);
_ = Version.TryParse(version, out var parsed);
yield return new FrameworkInstallation
{
ShortName = versionKeyName,
Expand Down Expand Up @@ -146,7 +146,7 @@ private static FrameworkInstallation GetFromV4(RegistryKey subKey, string subKey

if (version == null)
{
Version.TryParse(subKey.GetString("Version"), out var parsed);
_ = Version.TryParse(subKey.GetString("Version"), out var parsed);
version = parsed;
}

Expand Down Expand Up @@ -174,8 +174,8 @@ private static FrameworkInstallation GetFromV4(RegistryKey subKey, string subKey

internal static Version GetNetFxVersionFromRelease(int release)
{
NetFxReleaseVersionMap.TryGetValue(release, out var version);
Version.TryParse(version, out var parsed);
_ = NetFxReleaseVersionMap.TryGetValue(release, out var version);
_ = Version.TryParse(version, out var parsed);
return parsed;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Sentry.PlatformAbstractions
{
internal static class FrameworkInstallationExtensions
{
internal static string? GetVersionNumber(this FrameworkInstallation frameworkInstall)
=> frameworkInstall?.ShortName
?? (frameworkInstall?.Version != null ? $"v{frameworkInstall.Version}" : null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#if NETFX
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Sentry.Extensibility;

namespace Sentry.PlatformAbstractions
{
internal class NetFxInstallationsEventProcessor : ISentryEventProcessor
{
internal static readonly string NetFxInstallationsKey = ".NET Framework";

private readonly Lazy<Dictionary<string, string>> _netFxInstallations =
new Lazy<Dictionary<string, string>>(() => GetInstallationsDictionary(), LazyThreadSafetyMode.ExecutionAndPublication);

private volatile bool _netFxInstallationEnabled = true;

private readonly SentryOptions _options;

internal NetFxInstallationsEventProcessor(SentryOptions options) => _options = options;

internal static Dictionary<string, string> GetInstallationsDictionary()
{
var versionsDictionary = new Dictionary<string, string>();
var installations = FrameworkInfo.GetInstallations();
foreach (var profile in installations.Select(p => p.Profile).Distinct())
{
versionsDictionary.Add($"{NetFxInstallationsKey} {profile}",
string.Join(", ", installations.Where(p => p.Profile == profile)
.Select(p => $"\"{p.GetVersionNumber()}\"")));
}
return versionsDictionary;
}

public SentryEvent? Process(SentryEvent @event)
{
if (_netFxInstallationEnabled)
{
if (!@event.Contexts.ContainsKey(NetFxInstallationsKey))
{
try
{
@event.Contexts[NetFxInstallationsKey] = _netFxInstallations.Value;
}
catch (Exception ex)
{
_options.DiagnosticLogger?.LogError("Failed to add NetFxInstallations into event.", ex);
//In case of any failure, this process function will be disabled to avoid throwing exceptions for future events.
_netFxInstallationEnabled = false;
}
}
}
else
{
_options.DiagnosticLogger.LogDebug("NetFxInstallation disabled due to previous error.");
}
return @event;
}
}
}
#endif
3 changes: 3 additions & 0 deletions src/Sentry/SentryOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,9 @@ public SentryOptions()
Integrations = new ISdkIntegration[] {
new AppDomainUnhandledExceptionIntegration(),
new AppDomainProcessExitIntegration(),
#if NETFX
new NetFxInstallationsIntegration(),
#endif
};

InAppExclude = new[] {
Expand Down
14 changes: 14 additions & 0 deletions src/Sentry/SentryOptionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Sentry.Infrastructure;
using Sentry.Integrations;
using Sentry.Internal;
using Sentry.PlatformAbstractions;

namespace Sentry
{
Expand Down Expand Up @@ -35,6 +36,19 @@ public static void DisableDuplicateEventDetection(this SentryOptions options)
public static void DisableAppDomainUnhandledExceptionCapture(this SentryOptions options) =>
options.RemoveIntegration<AppDomainUnhandledExceptionIntegration>();

#if NETFX
/// <summary>
/// Disables the list addition of .Net Frameworks into events.
/// </summary>
/// <param name="options">The SentryOptions to remove the integration from.</param>
public static void DisableNetFxInstallationsIntegration(this SentryOptions options)
{
options.EventProcessors =
options.EventProcessors?.Where(p => p.GetType() != typeof(NetFxInstallationsEventProcessor)).ToArray();
options.RemoveIntegration<NetFxInstallationsIntegration>();
}
#endif

/// <summary>
/// Disables the capture of errors through <see cref="AppDomain.ProcessExit"/>
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#if NETFX
using Sentry.Integrations;
using Sentry.PlatformAbstractions;
using Xunit;

namespace Sentry.Tests.Integrations
{
public class NetFxInstallationsIntegrationTests
{
[SkippableFact]
public void Register_CurrentRuntimeIsMono_NetFxInstallationsEventProcessorNotAdded()
{
Skip.If(!Runtime.Current.IsMono());

//Arrance
var options = new SentryOptions();
var integration = new NetFxInstallationsIntegration();

//Act
integration.Register(null, options);

//Assert
Assert.DoesNotContain(options.EventProcessors, p => p.GetType() == typeof(NetFxInstallationsEventProcessor));
}

[SkippableFact]
public void Register_CurrentRuntimeIsNotMono_NetFxInstallationsEventProcessorAdded()
{
Skip.If(Runtime.Current.IsMono());

//Arrance
var options = new SentryOptions();
var integration = new NetFxInstallationsIntegration();

//Act
integration.Register(null, options);

//Assert
Assert.Contains(options.EventProcessors, p => p.GetType() == typeof(NetFxInstallationsEventProcessor));
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using Sentry.PlatformAbstractions;
using Xunit;

namespace Sentry.Tests.PlatformAbstractions
{
public class FrameworkInstallationExtensionsTests
{
[Fact]
public void GetVersionNumber_NullFrameworkInstallation_NullVersion()
{
//Arrange
FrameworkInstallation frameworkInstallation = null;
//Act
var version = frameworkInstallation.GetVersionNumber();

//Assert
Assert.Null(version);
}

[Fact]
public void GetVersionNumber_NullShortVersionAndNullVersion_NullVersion()
{
//Arrange
var frameworkInstallation = new FrameworkInstallation();

//Act
var version = frameworkInstallation.GetVersionNumber();

//Assert
Assert.Null(version);
}

[Fact]
public void GetVersionNumber_ValidShortVersion_ShortVersion()
{
//Arrange
var frameworkInstallation = new FrameworkInstallation();
var expectedShortVersion = "v1.2.3";
frameworkInstallation.ShortName = expectedShortVersion;

//Act
var version = frameworkInstallation.GetVersionNumber();

//Assert
Assert.Equal(expectedShortVersion, version);
}


[Fact]
public void GetVersionNumber_ValidVersionAndNullShortVersion_NullVersion()
{
//Arrange
var frameworkInstallation = new FrameworkInstallation();
frameworkInstallation.Version = new Version("1.2");

//Act
var version = frameworkInstallation.GetVersionNumber();

//Assert
Assert.Equal("v1.2", version);
}

[Fact]
public void GetVersionNumber_ValidMinorMajorVersionAndNullShortVersion_NullVersion()
{
//Arrange
var frameworkInstallation = new FrameworkInstallation();
frameworkInstallation.Version = new Version(1,2);

//Act
var version = frameworkInstallation.GetVersionNumber();

//Assert
Assert.Equal("v1.2", version);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#if NETFX
using Xunit;
using System.Collections.Generic;
using Sentry.PlatformAbstractions;

namespace Sentry.Tests.PlatformAbstractions
{
public class NetFxInstallationsEventProcessorTests
{
private class Fixture
{
public SentryOptions SentryOptions { get; set; } = new SentryOptions();

public NetFxInstallationsEventProcessor GetSut() => new NetFxInstallationsEventProcessor(SentryOptions);
}

private readonly Fixture _fixture = new Fixture();

[SkippableFact]
public void Process_SentryEventWithNetFxList()
{
Skip.If(Runtime.Current.IsMono(), "Mono not supported.");

//Arrange
var @event = new SentryEvent();
var sut = _fixture.GetSut();

//Act
_ = sut.Process(@event);

//Assert
_ = Assert.IsAssignableFrom<Dictionary<string, string>>(@event.Contexts[NetFxInstallationsEventProcessor.NetFxInstallationsKey]);
}

[SkippableFact]
public void Process_ContextWithGetInstallationsData()
{
Skip.If(Runtime.Current.IsMono(), "Mono not supported.");

//Arrange
var @event = new SentryEvent();
var sut = _fixture.GetSut();
var installationList = FrameworkInfo.GetInstallations();
//Act
_ = sut.Process(@event);

//Assert
var dictionary = @event.Contexts[NetFxInstallationsEventProcessor.NetFxInstallationsKey] as Dictionary<string, string>;
foreach(var item in installationList)
{
Assert.Contains($"\"{item.GetVersionNumber()}\"", dictionary[$"{NetFxInstallationsEventProcessor.NetFxInstallationsKey} {item.Profile}"]);
}
}

[SkippableFact]
public void Process_NetFxInstallationsKeyExist_UnchangedSentryEvent()
{
Skip.If(Runtime.Current.IsMono(), "Mono not supported.");

//Arrange
var @event = new SentryEvent();
var sut = _fixture.GetSut();
var userBlob = "user blob";
@event.Contexts[NetFxInstallationsEventProcessor.NetFxInstallationsKey] = userBlob;

//Act
_ = sut.Process(@event);

//Assert
Assert.Equal(userBlob, @event.Contexts[NetFxInstallationsEventProcessor.NetFxInstallationsKey]);
}
}
}
#endif
Loading

0 comments on commit db6d590

Please sign in to comment.