diff --git a/src/Sentry.NLog/SentryTarget.cs b/src/Sentry.NLog/SentryTarget.cs index 75ca00067b..e312ce0edc 100644 --- a/src/Sentry.NLog/SentryTarget.cs +++ b/src/Sentry.NLog/SentryTarget.cs @@ -404,14 +404,14 @@ protected override void Write(LogEventInfo logEvent) if (logEvent.Level >= Options.MinimumBreadcrumbLevel) { var breadcrumbFormatted = RenderLogEvent(BreadcrumbLayout, logEvent); - var breadcrumbCategory = RenderLogEvent(BreadcrumbCategory, logEvent); + string? breadcrumbCategory = RenderLogEvent(BreadcrumbCategory, logEvent); if (string.IsNullOrEmpty(breadcrumbCategory)) { breadcrumbCategory = null; } var message = string.IsNullOrWhiteSpace(breadcrumbFormatted) - ? (exception?.Message ?? logEvent.FormattedMessage) + ? exception?.Message ?? logEvent.FormattedMessage : breadcrumbFormatted; IDictionary? data = null; diff --git a/src/Sentry/PlatformAbstractions/FrameworkInfo.NetFx.cs b/src/Sentry/PlatformAbstractions/FrameworkInfo.NetFx.cs new file mode 100644 index 0000000000..9f3e6e0599 --- /dev/null +++ b/src/Sentry/PlatformAbstractions/FrameworkInfo.NetFx.cs @@ -0,0 +1,184 @@ +#if NETFX +using System; +using System.Collections.Generic; +using Microsoft.Win32; + +namespace Sentry.PlatformAbstractions +{ + /// + /// Information about .NET Framework in the running machine + /// + public static partial class FrameworkInfo + { + /// + /// Get the latest Framework installation for the specified CLR + /// + /// + /// Supports the current 3 CLR versions: + /// CLR 1 => .NET 1.0, 1.1 + /// CLR 2 => .NET 2.0, 3.0, 3.5 + /// CLR 4 => .NET 4.0, 4.5.x, 4.6.x, 4.7.x + /// + /// The CLR version: 1, 2 or 4 + /// The framework installation or null if none is found. + public static FrameworkInstallation? GetLatest(int clrVersion) + { + // CLR versions + // https://docs.microsoft.com/en-us/dotnet/standard/clr + if (clrVersion != 1 && clrVersion != 2 && clrVersion != 4) + { + return null; + } + + if (clrVersion == 4) + { + var release = Get45PlusLatestInstallationFromRegistry(); + if (release != null) + { + return new FrameworkInstallation + { + Version = GetNetFxVersionFromRelease(release.Value), + Release = release + }; + } + } + + FrameworkInstallation latest = null; + foreach (var installation in GetInstallations()) + { + latest ??= installation; + + if (clrVersion == 2) + { + // CLR 2 runs .NET 2 to 3.5 + if ((installation.Version.Major == 2 || installation.Version.Major == 3) + && installation.Version >= latest.Version) + { + latest = installation; + } + else + { + break; + } + } + else if (clrVersion == 4) + { + if (installation.Version.Major == 4 + && installation.Version >= latest.Version) + { + latest = installation; + } + else + { + break; + } + } + } + + return latest; + } + + /// + /// Get all .NET Framework installations in this machine + /// + /// + /// Enumeration of installations + public static IEnumerable GetInstallations() + { + using var ndpKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, string.Empty) + .OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\"); + if (ndpKey == null) + { + yield break; + } + + foreach (var versionKeyName in ndpKey.GetSubKeyNames()) + { + if (!versionKeyName.StartsWith("v") || !(ndpKey.OpenSubKey(versionKeyName) is RegistryKey versionKey)) + { + continue; + } + + var version = versionKey.GetString("Version"); + if (version != null && versionKey.GetInt("Install") == 1) + { + // 1.0 to 3.5 + Version.TryParse(version, out var parsed); + yield return new FrameworkInstallation + { + ShortName = versionKeyName, + Version = parsed, + ServicePack = versionKey.GetInt("SP") + }; + + continue; + } + + // 4.0+ + foreach (var subKeyName in versionKey.GetSubKeyNames()) + { + var subKey = versionKey.OpenSubKey(subKeyName); + if (subKey?.GetInt("Install") != 1) + { + continue; + } + + yield return GetFromV4(subKey, subKeyName); + } + } + } + + private static FrameworkInstallation GetFromV4(RegistryKey subKey, string subKeyName) + { + var hasRelease = int.TryParse( + subKey.GetValue("Release", null)?.ToString(), out var release); + + Version version = null; + if (hasRelease) + { + // 4.5+ + var displayableVersion = GetNetFxVersionFromRelease(release); + if (displayableVersion != null) + { + version = displayableVersion; + } + } + + if (version == null) + { + Version.TryParse(subKey.GetString("Version"), out var parsed); + version = parsed; + } + + return new FrameworkInstallation + { + Profile = subKeyName switch + { + "Full" => FrameworkProfile.Full, + "Client" => FrameworkProfile.Client, + _ => null + }, + Version = version, + ServicePack = subKey.GetInt("SP"), + Release = hasRelease ? release : null as int? + }; + } + + // https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed#to-find-net-framework-versions-by-querying-the-registry-in-code-net-framework-45-and-later + internal static int? Get45PlusLatestInstallationFromRegistry() + { + using var ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32) + .OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\"); + return ndpKey?.GetInt("Release"); + } + + internal static Version GetNetFxVersionFromRelease(int release) + { + NetFxReleaseVersionMap.TryGetValue(release, out var version); + Version.TryParse(version, out var parsed); + return parsed; + } + } +} + +#endif diff --git a/src/Sentry/PlatformAbstractions/FrameworkInfo.NetStandard.cs b/src/Sentry/PlatformAbstractions/FrameworkInfo.NetStandard.cs new file mode 100644 index 0000000000..8a008db228 --- /dev/null +++ b/src/Sentry/PlatformAbstractions/FrameworkInfo.NetStandard.cs @@ -0,0 +1,29 @@ +#if !NETFX + +using System.Collections.Generic; +using System.Linq; + +namespace Sentry.PlatformAbstractions +{ + /// + /// No-op version for netstandard targets + /// + public static partial class FrameworkInfo + { + /// + /// No-op version for netstandard targets + /// + /// + /// + public static FrameworkInstallation? GetLatest(int clr) => null; + + /// + /// No-op version for netstandard targets + /// + /// + public static IEnumerable GetInstallations() + => Enumerable.Empty(); + } +} + +#endif diff --git a/src/Sentry/PlatformAbstractions/FrameworkInfo.cs b/src/Sentry/PlatformAbstractions/FrameworkInfo.cs new file mode 100644 index 0000000000..1b1ccead0f --- /dev/null +++ b/src/Sentry/PlatformAbstractions/FrameworkInfo.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; + +namespace Sentry.PlatformAbstractions +{ + /// + /// Information about .NET Framework in the running machine + /// The purpose of this partial class is to expose the API to all targets + /// For netstandard, the call to methods will be a simple no-op. + /// + public static partial class FrameworkInfo + { + /// + /// The map between release number and version number + /// + /// + public static IReadOnlyDictionary NetFxReleaseVersionMap { get; } + = new Dictionary + { + {378389, "4.5"}, + {378675, "4.5.1"}, + {378758, "4.5.1"}, + {379893, "4.5.2"}, + {393295, "4.6"}, + {393297, "4.6"}, + {394254, "4.6.1"}, + {394271, "4.6.1"}, + {394802, "4.6.2"}, + {394806, "4.6.2"}, + {460798, "4.7"}, + {460805, "4.7"}, + {461308, "4.7.1"}, + {461310, "4.7.1"}, + {461808, "4.7.2"}, + {461814, "4.7.2"}, + {528040, "4.8"}, + {528049, "4.8"}, + {528209, "4.8"}, + {528372, "4.8"}, + }; + } +} diff --git a/src/Sentry/PlatformAbstractions/FrameworkInstallation.cs b/src/Sentry/PlatformAbstractions/FrameworkInstallation.cs new file mode 100644 index 0000000000..41da45b9c7 --- /dev/null +++ b/src/Sentry/PlatformAbstractions/FrameworkInstallation.cs @@ -0,0 +1,75 @@ +using System; + +namespace Sentry.PlatformAbstractions +{ + /// + /// A .NET Framework installation + /// + /// + /// + public class FrameworkInstallation + { + /// + /// Short name + /// + /// + /// v2.0.50727, v3.5, v4.0 + /// + public string? ShortName { get; set; } + /// + /// Version + /// + /// + /// 2.0.50727.4927, 3.0.30729.4926, 3.5.30729.4926 + /// + public Version? Version { get; set; } + /// + /// Service pack number, if any + /// + /// + /// Only relevant prior to .NET 4 + /// + public int? ServicePack { get; set; } + /// + /// Type of Framework profile + /// + /// Only relevant for .NET 3.5 and 4.0 + /// + public FrameworkProfile? Profile { get; set; } + /// + /// A .NET Framework release key + /// + /// + /// Windows registry key: + /// HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\Release + /// Only applicable when on Windows, with full .NET Framework 4.5 and later. + /// + /// + public int? Release { get; set; } + + /// + public override string ToString() + { + return Version?.Build > 0 + ? $"{Version.Major}.{Version.Minor}.{Version.Build}" + : $"{Version?.Major ?? 0}.{Version?.Minor ?? 0}"; + } + } + + /// + /// Type of Framework profile + /// + /// Only relevant for .NET 3.5 and 4.0 + /// + public enum FrameworkProfile + { + /// + /// The .NET Client Profile is a subset of the .NET Framework + /// + Client, + /// + /// The full .NET Framework + /// + Full + } +} diff --git a/src/Sentry/PlatformAbstractions/RegistryKeyExtensions.cs b/src/Sentry/PlatformAbstractions/RegistryKeyExtensions.cs new file mode 100644 index 0000000000..2aa4dd4891 --- /dev/null +++ b/src/Sentry/PlatformAbstractions/RegistryKeyExtensions.cs @@ -0,0 +1,15 @@ +#if NETFX +using Microsoft.Win32; + +namespace Sentry.PlatformAbstractions +{ + internal static class RegistryKeyExtensions + { + public static string? GetString(this RegistryKey key, string value) + => key.GetValue(value) as string; + + public static int? GetInt(this RegistryKey key, string value) + => (int?)key.GetValue(value); + } +} +#endif diff --git a/src/Sentry/PlatformAbstractions/Runtime.cs b/src/Sentry/PlatformAbstractions/Runtime.cs new file mode 100644 index 0000000000..bf9261837e --- /dev/null +++ b/src/Sentry/PlatformAbstractions/Runtime.cs @@ -0,0 +1,138 @@ +using System; + +namespace Sentry.PlatformAbstractions +{ + /// + /// Details of the runtime + /// + /// + public class Runtime + { + private static Runtime? _runtime; + /// + /// Gets the current runtime + /// + /// + /// The current runtime. + /// + public static Runtime Current => _runtime ??= RuntimeInfo.GetRuntime(); + /// + /// The name of the runtime + /// + /// + /// .NET Framework, .NET Native, Mono + /// + public string? Name { get; internal set; } + /// + /// The version of the runtime + /// + /// + /// 4.7.2633.0 + /// + public string? Version { get; internal set; } +#if NETFX + /// + /// The .NET Framework installation which is running the process + /// + /// + /// The framework installation or null if not running .NET Framework + /// + public FrameworkInstallation FrameworkInstallation { get; internal set; } +#endif + /// + /// The raw value parsed to extract Name and Version + /// + /// + /// This property will contain a value when the underlying API + /// returned Name and Version as a single string which required parsing. + /// + public string? Raw { get; internal set; } + + /// + /// Creates a new Runtime instance + /// + public Runtime( + string? name = null, + string? version = null, + #if NETFX + FrameworkInstallation? frameworkInstallation = null, + #endif + string? raw = null) + { + Name = name; + Version = version; +#if NETFX + FrameworkInstallation = frameworkInstallation; +#endif + Raw = raw; + } + + /// + /// The string representation of the Runtime + /// + public override string? ToString() + { + if (Name == null && Version == null) + { + return Raw; + } + + if (Name != null && Version == null) + { + return Raw?.Contains(Name) == true + ? Raw + : $"{Name} {Raw}"; + } + + return $"{Name} {Version}"; + } + + /// + /// Compare instances for equality. + /// + /// The instance to compare against. + /// True if the instances are equal by reference or its state. + public bool Equals(Runtime other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Name, other.Name) + && string.Equals(Version, other.Version) +#if NETFX + && Equals(FrameworkInstallation, other.FrameworkInstallation) +#endif + && string.Equals(Raw, other.Raw); + } + + /// + /// Compare instances for equality. + /// + /// The instance to compare against. + /// True if the instances are equal by reference or its state. + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((Runtime) obj); + } + + /// + /// Get the hashcode of this instance. + /// + /// The hashcode of the instance. + public override int GetHashCode() + { + unchecked + { + var hashCode = Name != null ? Name.GetHashCode() : 0; + hashCode = (hashCode * 397) ^ (Version != null ? Version.GetHashCode() : 0); +#if NETFX + hashCode = (hashCode * 397) ^ (FrameworkInstallation != null ? FrameworkInstallation.GetHashCode() : 0); +#endif + hashCode = (hashCode * 397) ^ (Raw != null ? Raw.GetHashCode() : 0); + return hashCode; + } + } + } +} diff --git a/src/Sentry/PlatformAbstractions/RuntimeExtensions.cs b/src/Sentry/PlatformAbstractions/RuntimeExtensions.cs new file mode 100644 index 0000000000..a614e6fda6 --- /dev/null +++ b/src/Sentry/PlatformAbstractions/RuntimeExtensions.cs @@ -0,0 +1,37 @@ +using System; +using System.ComponentModel; + +namespace Sentry.PlatformAbstractions +{ + /// + /// Extension method to the class. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class RuntimeExtensions + { + /// + /// Is the runtime instance .NET Framework. + /// + /// The runtime instance to check. + /// True if it's .NET Framework, otherwise false. + public static bool IsNetFx(this Runtime runtime) => runtime.IsRuntime(".NET Framework"); + /// + /// Is the runtime instance .NET Core. + /// + /// The runtime instance to check. + /// True if it's .NET Core, otherwise false. + public static bool IsNetCore(this Runtime runtime) => runtime.IsRuntime(".NET Core"); + /// + /// Is the runtime instance Mono. + /// + /// The runtime instance to check. + /// True if it's Mono, otherwise false. + public static bool IsMono(this Runtime runtime) => runtime.IsRuntime("Mono"); + + private static bool IsRuntime(this Runtime? runtime, string runtimeName) + { + return runtime?.Name?.StartsWith(runtimeName, StringComparison.OrdinalIgnoreCase) == true + || runtime?.Raw?.StartsWith(runtimeName, StringComparison.OrdinalIgnoreCase) == true; + } + } +} diff --git a/src/Sentry/PlatformAbstractions/RuntimeInfo.cs b/src/Sentry/PlatformAbstractions/RuntimeInfo.cs new file mode 100644 index 0000000000..240e5db9b6 --- /dev/null +++ b/src/Sentry/PlatformAbstractions/RuntimeInfo.cs @@ -0,0 +1,137 @@ +using System; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Runtime.InteropServices; + +namespace Sentry.PlatformAbstractions +{ + // https://github.com/dotnet/corefx/issues/17452 + internal static class RuntimeInfo + { + private static readonly Regex RuntimeParseRegex = new Regex("^(?[^\\d]*)(?(\\d+\\.)+[^\\s]+)", + RegexOptions.Compiled | RegexOptions.CultureInvariant); + + /// + /// Gets the current runtime. + /// + /// A new instance for the current runtime + internal static Runtime GetRuntime() + { + var runtime = GetFromRuntimeInformation(); + runtime ??= GetFromMonoRuntime(); + + runtime ??= GetFromEnvironmentVariable(); + +#if NETFX + SetNetFxReleaseAndVersion(runtime); +#elif NETSTANDARD || NETCOREAPP // Possibly .NET Core + SetNetCoreVersion(runtime); +#endif + return runtime; + } + + internal static Runtime? Parse(string rawRuntimeDescription, string? name = null) + { + if (rawRuntimeDescription == null) + { + return name == null + ? null + : new Runtime(name); + } + + var match = RuntimeParseRegex.Match(rawRuntimeDescription); + if (match.Success) + { + return new Runtime( + name ?? (match.Groups["name"].Value == string.Empty ? null : match.Groups["name"].Value.Trim()), + match.Groups["version"].Value == string.Empty ? null : match.Groups["version"].Value.Trim(), + raw: rawRuntimeDescription + ); + } + + return new Runtime(name, raw: rawRuntimeDescription); + } + +#if NETFX + internal static void SetNetFxReleaseAndVersion(Runtime runtime) + { + if (runtime?.IsNetFx() == true) + { + var latest = FrameworkInfo.GetLatest(Environment.Version.Major); + + runtime.FrameworkInstallation = latest; + if (latest.Version?.Major < 4) + { + // prior to 4, user-friendly versions are always 2 digit: 1.0, 1.1, 2.0, 3.0, 3.5 + runtime.Version = latest.ServicePack == null + ? $"{latest.Version.Major}.{latest.Version.Minor}" + : $"{latest.Version.Major}.{latest.Version.Minor} SP {latest.ServicePack}"; + } + else + { + runtime.Version = latest.Version?.ToString(); + } + } + } +#endif + +#if NETSTANDARD || NETCOREAPP // Possibly .NET Core + // Known issue on Docker: https://github.com/dotnet/BenchmarkDotNet/issues/448#issuecomment-361027977 + internal static void SetNetCoreVersion(Runtime runtime) + { + if (runtime?.IsNetCore() == true) + { + // https://github.com/dotnet/BenchmarkDotNet/issues/448#issuecomment-308424100 + var assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; + var assemblyPath = assembly.CodeBase.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); + var netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); + if (netCoreAppIndex > 0 + && netCoreAppIndex < assemblyPath.Length - 2) + { + runtime.Version = assemblyPath[netCoreAppIndex + 1]; + } + } + } +#endif + + internal static Runtime? GetFromRuntimeInformation() + { + // Prefered API: netstandard2.0 + // https://github.com/dotnet/corefx/blob/master/src/System.Runtime.InteropServices.RuntimeInformation/src/System/Runtime/InteropServices/RuntimeInformation/RuntimeInformation.cs + // https://github.com/mono/mono/blob/90b49aa3aebb594e0409341f9dca63b74f9df52e/mcs/class/corlib/System.Runtime.InteropServices.RuntimeInformation/RuntimeInformation.cs + // e.g: .NET Framework 4.7.2633.0, .NET Native, WebAssembly + var frameworkDescription = RuntimeInformation.FrameworkDescription; + + return Parse(frameworkDescription); + } + + internal static Runtime? GetFromMonoRuntime() + => Type.GetType("Mono.Runtime", false) + ?.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static) + ?.Invoke(null, null) is string monoVersion + // The implementation of Mono to RuntimeInformation: + // https://github.com/mono/mono/blob/90b49aa3aebb594e0409341f9dca63b74f9df52e/mcs/class/corlib/System.Runtime.InteropServices.RuntimeInformation/RuntimeInformation.cs#L40 + // Examples: + // Mono 5.10.1.47 (2017-12/8eb8f7d5e74 Fri Apr 13 20:18:12 EDT 2018) + // Mono 5.10.1.47 (tarball Tue Apr 17 09:23:16 UTC 2018) + // Mono 5.10.0 (Visual Studio built mono) + ? Parse(monoVersion, "Mono") + : null; + + // This should really only be used on .NET 1.0, 1.1, 2.0, 3.0, 3.5 and 4.0 + internal static Runtime GetFromEnvironmentVariable() + { + // Environment.Version: NET Framework 4, 4.5, 4.5.1, 4.5.2 = 4.0.30319.xxxxx + // .NET Framework 4.6, 4.6.1, 4.6.2, 4.7, 4.7.1 = 4.0.30319.42000 + // Not recommended on NET45+ (which has RuntimeInformation) + var version = Environment.Version; + + var friendlyVersion = version.Major switch + { + 1 => "", + _ => version.ToString() + }; + return new Runtime(".NET Framework", friendlyVersion, raw: "Environment.Version=" + version); + } + } +} diff --git a/src/Sentry/Sentry.csproj b/src/Sentry/Sentry.csproj index 3c68ddd0b9..9b064aafc5 100644 --- a/src/Sentry/Sentry.csproj +++ b/src/Sentry/Sentry.csproj @@ -10,21 +10,26 @@ - + + + NETSTANDARD;$(AdditionalConstants) + + + + NETFX;$(AdditionalConstants) + + + - - - - - + diff --git a/test/Sentry.Tests/PlatformAbstractions/FrameworkInfoNetFxTests.cs b/test/Sentry.Tests/PlatformAbstractions/FrameworkInfoNetFxTests.cs new file mode 100644 index 0000000000..afc6865f1c --- /dev/null +++ b/test/Sentry.Tests/PlatformAbstractions/FrameworkInfoNetFxTests.cs @@ -0,0 +1,42 @@ +using Sentry.PlatformAbstractions; +using Xunit; +#if NETFX +using System; + +namespace Sentry.Tests.PlatformAbstractions +{ + public class FrameworkInfoNetFxTests + { + [SkippableFact] + public void GetLatest_NotNull() + { + Skip.If(RuntimeInfo.GetRuntime().IsMono()); + var latest = FrameworkInfo.GetLatest(Environment.Version.Major); + Assert.NotNull(latest); + } + + [SkippableFact] + public void GetInstallations_NotEmpty() + { + Skip.If(RuntimeInfo.GetRuntime().IsMono()); + var allInstallations = FrameworkInfo.GetInstallations(); + Assert.NotEmpty(allInstallations); + } + + [SkippableFact] + public void GetInstallations_AllReleasesAreMappedToVersion() + { + Skip.If(RuntimeInfo.GetRuntime().IsMono()); + var allInstallations = FrameworkInfo.GetInstallations(); + foreach (var installation in allInstallations) + { + if (installation.Release != null) + { + // Release has no version mapped + Assert.NotNull(installation.Version); + } + } + } + } +} +#endif diff --git a/test/Sentry.Tests/PlatformAbstractions/FrameworkInfoNetStandardTests.cs b/test/Sentry.Tests/PlatformAbstractions/FrameworkInfoNetStandardTests.cs new file mode 100644 index 0000000000..552942e22d --- /dev/null +++ b/test/Sentry.Tests/PlatformAbstractions/FrameworkInfoNetStandardTests.cs @@ -0,0 +1,23 @@ +#if !NETFX +using Xunit; + +namespace Sentry.PlatformAbstractions +{ + public class FrameworkInfoNetStandardTests + { + [Fact] + public void GetLatest_Returns_Null() + { + var latest = FrameworkInfo.GetLatest(4); + Assert.Null(latest); + } + + [Fact] + public void GetInstallations_Returns_Empty() + { + var allInstallations = FrameworkInfo.GetInstallations(); + Assert.Empty(allInstallations); + } + } +} +#endif diff --git a/test/Sentry.Tests/PlatformAbstractions/RuntimeInfoTests.cs b/test/Sentry.Tests/PlatformAbstractions/RuntimeInfoTests.cs new file mode 100644 index 0000000000..cbe2ee659b --- /dev/null +++ b/test/Sentry.Tests/PlatformAbstractions/RuntimeInfoTests.cs @@ -0,0 +1,160 @@ +using System.Collections.Generic; +using Sentry.PlatformAbstractions; +using Xunit; + +namespace Sentry.Tests.PlatformAbstractions +{ + public class RuntimeInfoTests + { + [Fact] // Verifies that some value is extracted anywhere the tests runs + public void GetRuntime_NotNull() + { + var actual = RuntimeInfo.GetRuntime(); + Assert.NotNull(actual); + Assert.NotNull(actual.Name); + Assert.NotNull(actual.Version); + } + + [Theory] + [MemberData(nameof(ParseTestCases))] + public void Parse_TestCases(ParseTestCase parseTestCase) + { + var actual = RuntimeInfo.Parse(parseTestCase.Raw, + parseTestCase.NameProvided); + + if (parseTestCase.Raw == null) + { + if (parseTestCase.NameProvided == null) + { + Assert.Null(actual); + } + else + { + Assert.Equal(parseTestCase.NameProvided, actual.Name); + } + } + else + { + Assert.Equal(parseTestCase.ExpectedName, actual.Name); + Assert.Equal(parseTestCase.ExpectedVersion, actual.Version); + Assert.Equal(parseTestCase.Raw, actual.Raw); + } + } + +#if NETFX + [SkippableFact] + public void SetReleaseAndVersionNetFx_OnNetFx_NonNullReleaseAndVersion() + { + // This test is only relevant when running on CLR. + Skip.If(RuntimeInfo.GetRuntime().IsMono()); + + var input = new Runtime(".NET Framework"); + RuntimeInfo.SetNetFxReleaseAndVersion(input); + + Assert.NotNull(input.Version); + Assert.NotNull(input.FrameworkInstallation); + Assert.NotNull(input.FrameworkInstallation.Version); + } +#endif + +#if NETCOREAPP + [Fact] + public void SetNetCoreVersion_NetCoreAsName() + { + var input = new Runtime(".NET Core"); + RuntimeInfo.SetNetCoreVersion(input); + + Assert.NotNull(input.Version); + Assert.Equal(".NET Core", input.Name); + } +#endif + + public static IEnumerable ParseTestCases() + { + yield return new object[] { new ParseTestCase + { + Raw = "Mono 5.10.1.47 (2017-12/8eb8f7d5e74 Fri Apr 13 20:18:12 EDT 2018)", + ExpectedName = "Mono", + ExpectedVersion = "5.10.1.47" + }}; + yield return new object[] { new ParseTestCase + { + Raw = "5.10.1.47 (2017-12/8eb8f7d5e74 Fri Apr 13 20:18:12 EDT 2018)", + NameProvided = "Mono", + ExpectedName = "Mono", + ExpectedVersion = "5.10.1.47" + }}; + yield return new object[] { new ParseTestCase + { + Raw = "Mono 5.10.0 (Visual Studio built mono)", + ExpectedName = "Mono", + ExpectedVersion = "5.10.0" + }}; + yield return new object[] { new ParseTestCase + { + Raw = ".NET Framework 4.0.30319.17020", + ExpectedName = ".NET Framework", + ExpectedVersion = "4.0.30319.17020" + }}; + yield return new object[] { new ParseTestCase + { + Raw = "4.0.30319.17020", + NameProvided = ".NET Framework", + ExpectedName = ".NET Framework", + ExpectedVersion = "4.0.30319.17020" + }}; + yield return new object[] { new ParseTestCase + { + Raw = ".NET Core 1.0", + ExpectedName = ".NET Core", + ExpectedVersion = "1.0" + }}; + yield return new object[] { new ParseTestCase + { + Raw = "1.0", + NameProvided = ".NET Core", + ExpectedName = ".NET Core", + ExpectedVersion = "1.0" + }}; + yield return new object[] { new ParseTestCase + { + Raw = ".NET Native 2.1.65535-preview123123", + ExpectedName = ".NET Native", + ExpectedVersion = "2.1.65535-preview123123" + }}; + yield return new object[] { new ParseTestCase + { + Raw = ".NET Framework For Windows Mobile 10" + }}; + yield return new object[] { new ParseTestCase + { + Raw = ".NET Framework For Windows Mobile 10.1", + ExpectedName = ".NET Framework For Windows Mobile", + ExpectedVersion = "10.1" + }}; + yield return new object[] { new ParseTestCase + { + Raw = "web" + }}; + yield return new object[] { new ParseTestCase + { + NameProvided = "web", + ExpectedName = "web" + }}; + yield return new object[] { new ParseTestCase + { + Raw = null, + ExpectedName = null, + ExpectedVersion = null + }}; + } + + public class ParseTestCase + { + public string Raw { get; set; } + public string NameProvided { get; set; } + public string ExpectedName { get; set; } + public string ExpectedVersion { get; set; } + } + } +} diff --git a/test/Sentry.Tests/PlatformAbstractions/RuntimeTests.cs b/test/Sentry.Tests/PlatformAbstractions/RuntimeTests.cs new file mode 100644 index 0000000000..e5fa61455e --- /dev/null +++ b/test/Sentry.Tests/PlatformAbstractions/RuntimeTests.cs @@ -0,0 +1,14 @@ +using Sentry.PlatformAbstractions; +using Xunit; + +namespace Sentry.Tests.PlatformAbstractions +{ + public class RuntimeTests + { + [Fact] + public void Current_SameInstance() + { + Assert.Same(Runtime.Current, Runtime.Current); + } + } +} diff --git a/test/Sentry.Tests/Sentry.Tests.csproj b/test/Sentry.Tests/Sentry.Tests.csproj index 962cfc47ab..3d9acf5860 100644 --- a/test/Sentry.Tests/Sentry.Tests.csproj +++ b/test/Sentry.Tests/Sentry.Tests.csproj @@ -13,4 +13,17 @@ + + NETCOREAPP;$(AdditionalConstants) + + + + NETFX;$(AdditionalConstants) + + + + + + +