diff --git a/samples/unity-of-bugs/Assets/Resources/Sentry/SentryOptions.asset b/samples/unity-of-bugs/Assets/Resources/Sentry/SentryOptions.asset deleted file mode 100644 index 3bce2bfd2..000000000 --- a/samples/unity-of-bugs/Assets/Resources/Sentry/SentryOptions.asset +++ /dev/null @@ -1,23 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &11400000 -MonoBehaviour: - m_ObjectHideFlags: 32 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: -158719865, guid: c5eeaf71c719ce94db8fd48adc5d0ce7, type: 3} - m_Name: SentryOptions - m_EditorClassIdentifier: - k__BackingField: 1 - k__BackingField: 1 - k__BackingField: https://94677106febe46b88b9b9ae5efd18a00@o447951.ingest.sentry.io/5439417 - k__BackingField: 1 - k__BackingField: 1 - k__BackingField: 0 - k__BackingField: 0 - k__BackingField: 0 - k__BackingField: 1 diff --git a/samples/unity-of-bugs/Assets/Resources/Sentry/SentryOptions.asset.meta b/samples/unity-of-bugs/Assets/Resources/Sentry/SentryOptions.asset.meta deleted file mode 100644 index eb32c4c93..000000000 --- a/samples/unity-of-bugs/Assets/Resources/Sentry/SentryOptions.asset.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 141ffa924e87df04199ddf3de9a04079 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 11400000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/src/Sentry.Unity.Editor/AndroidManifestConfiguration.cs b/src/Sentry.Unity.Editor/AndroidManifestConfiguration.cs deleted file mode 100644 index 2ec29252a..000000000 --- a/src/Sentry.Unity.Editor/AndroidManifestConfiguration.cs +++ /dev/null @@ -1,202 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Text; -using System.Xml; -using UnityEditor; -using UnityEditor.Android; -using UnityEngine; - -namespace Sentry.Unity.Editor -{ - // https://github.com/getsentry/sentry-java/blob/db4dfc92f202b1cefc48d019fdabe24d487db923/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java#L66-L187 - public class AndroidManifestConfiguration : IPostGenerateGradleAndroidProject - { - private const string SentryOptionsAssetPath = "Assets/Resources/Sentry/SentryOptions.asset"; - - public void OnPostGenerateGradleAndroidProject(string basePath) - { - if (!ShouldInit(basePath, out var result)) - { - return; - } - - var (androidManifest, options) = result.Value; - - options.Logger?.Log(SentryLevel.Debug, - "Configuring Sentry Android SDK on build.gradle at: {0}", args: basePath); - - var gradleBuildPath = Path.Combine(basePath, "build.gradle"); - var gradleConfigContent = File.ReadAllText(gradleBuildPath); - // TODO: Have a opt-out to installing the SDK here - if (!gradleConfigContent.Contains("'io.sentry:sentry-android:")) - { - // TODO: Fragile, regex something like: ^dependencies +\{(\n|$){ - var deps = "dependencies {"; - var lastIndex = gradleConfigContent.LastIndexOf(deps, StringComparison.Ordinal); - if (lastIndex > 0) - { - gradleConfigContent = gradleConfigContent.Insert(lastIndex + deps.Length, - // TODO: Configurable version - $"{Environment.NewLine} implementation 'io.sentry:sentry-android:4.0.0-alpha.1'{Environment.NewLine}"); - File.WriteAllText(gradleBuildPath, gradleConfigContent); - } - options.Logger?.Log(SentryLevel.Debug, - "Sentry Android SDK already in the gradle build file."); - } - else - { - options.Logger?.Log(SentryLevel.Debug, - "Sentry Android SDK already in the gradle build file."); - } - - options.Logger?.Log(SentryLevel.Debug, - "Configuring Sentry options on AndroidManifest: {0}", args: basePath); - - options.Logger?.Log(SentryLevel.Debug, "Setting DSN: {0}", args: options.Dsn); - - if (options.Dsn is not null) - { - androidManifest.SetDsn(options.Dsn); - } - - // Since logcat is only an editor thing, disregarding options.DebugOnlyInEditor - options.Logger?.Log(SentryLevel.Debug, "Setting Debug: {0}", args: options.Debug); - androidManifest.SetDebug(options.Debug); - options.Logger?.Log(SentryLevel.Debug, "Setting DiagnosticsLevel: {0}", args: options.DiagnosticsLevel); - androidManifest.SetLevel(options.DiagnosticsLevel); - - // TODO: All SentryOptions and create specific Android options - - _ = androidManifest.Save(); - } - - private static bool ShouldInit( - string basePath, - [NotNullWhen(true)] - out (AndroidManifest androidManifest, UnitySentryOptions options)? result) - { - result = null; - var manifestPath = GetManifestPath(basePath); - if (!File.Exists(manifestPath)) - { - Debug.LogError($"Manifest not found at {manifestPath}"); - return false; - } - - var androidManifest = new AndroidManifest(manifestPath); - - if (!(AssetDatabase.LoadAssetAtPath(SentryOptionsAssetPath) is { } options)) - { - Debug.LogError( - "SentryOptions asset not found. Sentry will be disabled! Did you configure it on Component/Sentry?"); - androidManifest.DisableSentryAndSave(); - return false; - } - - if (!options.Enabled) - { - options.Logger?.Log(SentryLevel.Debug, "Sentry Disabled In Options. Disabling sentry-android auto-init."); - androidManifest.DisableSentryAndSave(); - return false; - } - - if (string.IsNullOrWhiteSpace(options.Dsn)) - { - options.Logger?.Log(SentryLevel.Warning, "No Sentry DSN configured. Sentry will be disabled."); - // Otherwise sentry-android attempts to init and logs out: - // Unable to get provider io.sentry.android.core.SentryInitProvider: java.lang.IllegalArgumentException: DSN is required. Use empty string to disable SDK. - androidManifest.DisableSentryAndSave(); - return false; - } - - result = (androidManifest, options); - return true; - } - - public int callbackOrder => 1; - - private static string GetManifestPath(string basePath) => - new StringBuilder(basePath) - .Append(Path.DirectorySeparatorChar) - .Append("src") - .Append(Path.DirectorySeparatorChar) - .Append("main") - .Append(Path.DirectorySeparatorChar) - .Append("AndroidManifest.xml") - .ToString(); - } - - - internal class AndroidXmlDocument : XmlDocument - { - private readonly string _path; - protected const string AndroidXmlNamespace = "http://schemas.android.com/apk/res/android"; - - protected AndroidXmlDocument(string path) - { - _path = path; - using (var reader = new XmlTextReader(_path)) - { - _ = reader.Read(); - Load(reader); - } - var nsManager = new XmlNamespaceManager(NameTable); - nsManager.AddNamespace("android", AndroidXmlNamespace); - } - - public string Save() => SaveAs(_path); - - private string SaveAs(string path) - { - using var writer = new XmlTextWriter(path, new UTF8Encoding(false)) { Formatting = Formatting.Indented }; - Save(writer); - return path; - } - } - - internal class AndroidManifest : AndroidXmlDocument - { - private readonly XmlElement _applicationElement; - - public AndroidManifest(string path) : base(path) => - _applicationElement = (XmlElement)SelectSingleNode("/manifest/application"); - - internal void DisableSentryAndSave() - { - SetMetaData("io.sentry.auto-init", "false"); - _ = Save(); - } - - internal void SetDsn(string dsn) => SetMetaData("io.sentry.dsn", dsn); - - internal void SetDebug(bool debug) => SetMetaData("io.sentry.debug", debug ? "true" : "false"); - - // https://github.com/getsentry/sentry-java/blob/db4dfc92f202b1cefc48d019fdabe24d487db923/sentry/src/main/java/io/sentry/SentryLevel.java#L4-L9 - internal void SetLevel(SentryLevel level) => - SetMetaData("io.sentry.debug.level", level switch - { - SentryLevel.Debug => "DEBUG", - SentryLevel.Error => "ERROR", - SentryLevel.Fatal => "FATAL", - SentryLevel.Info => "INFO", - SentryLevel.Warning => "WARNING", - _ => "DEBUG" - }); - - private void SetMetaData(string key, string value) - { - var element = _applicationElement.AppendChild(_applicationElement.OwnerDocument - .CreateElement("meta-data")); - _ = element.Attributes.Append(CreateAndroidAttribute("name", key)); - _ = element.Attributes.Append(CreateAndroidAttribute("value", value)); - } - - private XmlAttribute CreateAndroidAttribute(string key, string value) - { - var attr = CreateAttribute("android", key, AndroidXmlNamespace); - attr.Value = value; - return attr; - } - } -} diff --git a/src/Sentry.Unity.Editor/SentryTestWindow.cs b/src/Sentry.Unity.Editor/SentryTestWindow.cs index 5d625fb87..08a89d6dc 100644 --- a/src/Sentry.Unity.Editor/SentryTestWindow.cs +++ b/src/Sentry.Unity.Editor/SentryTestWindow.cs @@ -14,7 +14,8 @@ public static SentryTestWindow Open() public void Dispose() { Close(); // calls 'OnLostFocus' implicitly - AssetDatabase.DeleteAsset(SentryOptionsAssetPath); + File.Delete(SentryOptionsAssetPath); + AssetDatabase.Refresh(); } } } diff --git a/src/Sentry.Unity.Editor/SentryWindow.cs b/src/Sentry.Unity.Editor/SentryWindow.cs index 5d342270e..78547daa1 100644 --- a/src/Sentry.Unity.Editor/SentryWindow.cs +++ b/src/Sentry.Unity.Editor/SentryWindow.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using Sentry.Extensibility; using UnityEditor; using UnityEngine; @@ -11,9 +12,11 @@ public class SentryWindow : EditorWindow public static SentryWindow OpenSentryWindow() => (SentryWindow)GetWindow(typeof(SentryWindow)); - protected virtual string SentryOptionsAssetName { get; } = "SentryOptions"; + protected virtual string SentryOptionsAssetName { get; } = UnitySentryOptions.ConfigName; - protected string SentryOptionsAssetPath => $"Assets/Resources/Sentry/{SentryOptionsAssetName}.asset"; + // Will be used only from Unity Editor + protected string SentryOptionsAssetPath + => $"{Application.dataPath}/Resources/{UnitySentryOptions.ConfigRootFolder}/{SentryOptionsAssetName}.json"; public UnitySentryOptions Options { get; set; } = null!; // Set by OnEnable() @@ -23,22 +26,26 @@ private void OnEnable() { SetTitle(); - Options = AssetDatabase.LoadAssetAtPath(SentryOptionsAssetPath); - if (Options is null) + TryCreateSentryFolder(); + + Options = LoadUnitySentryOptions(); + + TryCopyLinkXml(Options.Logger); + } + + private UnitySentryOptions LoadUnitySentryOptions() + { + if (File.Exists(SentryOptionsAssetPath)) { - Options = CreateInstance(); - if (!AssetDatabase.IsValidFolder("Assets/Resources")) - { - AssetDatabase.CreateFolder("Assets", "Resources"); - } - if (!AssetDatabase.IsValidFolder("Assets/Resources/Sentry")) - { - AssetDatabase.CreateFolder("Assets/Resources", "Sentry"); - } - AssetDatabase.CreateAsset(Options, SentryOptionsAssetPath); + return UnitySentryOptions.LoadFromUnity(); } - EditorUtility.SetDirty(Options); + var unitySentryOptions = new UnitySentryOptions { Enabled = true }; + unitySentryOptions + .TryAttachLogger() + .SaveToUnity(SentryOptionsAssetPath); + + return unitySentryOptions; } private void SetTitle() @@ -90,8 +97,8 @@ private void ValidateDsn() public void OnLostFocus() { Validate(); - AssetDatabase.SaveAssets(); - // TODO: This should be gone + + Options.SaveToUnity(SentryOptionsAssetPath); AssetDatabase.Refresh(); } @@ -155,6 +162,75 @@ private void OnGUI() // project = EditorGUILayout.TextField("Project", project); // EditorGUILayout.EndToggleGroup(); } + + /// + /// Creates Sentry folder for storing its configs - Assets/Resources/Sentry + /// + private static void TryCreateSentryFolder() + { + // TODO: revise, 'Resources' is a special Unity folder which is created by default. Not sure this check is needed. + if (!AssetDatabase.IsValidFolder("Assets/Resources")) + { + AssetDatabase.CreateFolder("Assets", "Resources"); + } + if (!AssetDatabase.IsValidFolder($"Assets/Resources/{UnitySentryOptions.ConfigRootFolder}")) + { + AssetDatabase.CreateFolder("Assets/Resources", UnitySentryOptions.ConfigRootFolder); + } + } + + /// + /// Find and copy 'link.xml' into current Unity project for IL2CPP builds + /// + private static void TryCopyLinkXml(IDiagnosticLogger? logger) + { + const string linkXmlFileName = "link.xml"; + + var linkXmlPath = $"{Application.dataPath}/Resources/{UnitySentryOptions.ConfigRootFolder}/{linkXmlFileName}"; + if (File.Exists(linkXmlPath)) + { + return; + } + + logger?.Log(SentryLevel.Debug, $"'{linkXmlFileName}' is not found. Creating one!"); + + var linkPath = GetLinkXmlPath(linkXmlFileName); + if (linkPath == null) + { + logger?.Log(SentryLevel.Fatal, $"Couldn't locate '{linkXmlFileName}' in 'Packages'."); + return; + } + + var linkXmlAsset = AssetDatabase.LoadAssetAtPath(linkPath); + File.WriteAllBytes(linkXmlPath, linkXmlAsset.bytes); + } + + /// + /// Get Unity path to 'link.xml' file from `Packages` folder. + /// + /// Release UPM: + /// Given: link.xml + /// Returns: Packages/io.sentry.unity/Runtime/link.xml + /// + /// Dev UPM: + /// Given: link.xml + /// Returns: Packages/io.sentry.unity.dev/Runtime/link.xml + /// + private static string? GetLinkXmlPath(string linkXmlFileName) + { + var assetIds = AssetDatabase.FindAssets(UnitySentryOptions.PackageName, new [] { "Packages" }); + for (var i = 0; i < assetIds.Length; i++) + { + var assetName = AssetDatabase.GUIDToAssetPath(assetIds[i]); + if (assetName.Contains("Runtime")) + { + var linkFolderPath = Path.GetDirectoryName(assetName)!; + return Path.Combine(linkFolderPath, linkXmlFileName); + } + } + + return null; + } } public readonly struct ValidationError diff --git a/src/Sentry.Unity/SentryInitialization.cs b/src/Sentry.Unity/SentryInitialization.cs index b7e3536db..904ddee5d 100644 --- a/src/Sentry.Unity/SentryInitialization.cs +++ b/src/Sentry.Unity/SentryInitialization.cs @@ -30,11 +30,7 @@ public static class SentryInitialization [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] public static void Init() { - if (!(Resources.Load("Sentry/SentryOptions") is UnitySentryOptions options)) - { - Debug.LogWarning("Sentry Options asset not found. Did you configure it on Component/Sentry?"); - return; - } + var options = UnitySentryOptions.LoadFromUnity(); if (!options.Enabled) { diff --git a/src/Sentry.Unity/UnitySentryOptions.cs b/src/Sentry.Unity/UnitySentryOptions.cs index e59d1b21a..dd1b0ee6f 100644 --- a/src/Sentry.Unity/UnitySentryOptions.cs +++ b/src/Sentry.Unity/UnitySentryOptions.cs @@ -1,7 +1,8 @@ using System; +using System.IO; +using System.Text.Json; using Sentry.Extensibility; using UnityEngine; -using CompressionLevel = System.IO.Compression.CompressionLevel; namespace Sentry.Unity { @@ -12,32 +13,155 @@ public enum SentryUnityCompression Fastest = 2, NoCompression = 3 } - [Serializable] - public sealed class UnitySentryOptions : ScriptableObject + + public sealed class UnitySentryOptions { - [field: SerializeField] public bool Enabled { get; set; } = true; - [field: SerializeField] public bool CaptureInEditor { get; set; } = true; // Lower entry barrier, likely set to false after initial setup. - [field: SerializeField] public string? Dsn { get; set; } - [field: SerializeField] public bool Debug { get; set; } = true; // By default on only - [field: SerializeField] public bool DebugOnlyInEditor { get; set; } = true; - [field: SerializeField] public SentryLevel DiagnosticsLevel { get; set; } = SentryLevel.Error; // By default logs out Error or higher. + /// + /// Relative to Assets/Resources + /// + public const string ConfigRootFolder = "Sentry"; + + /// + /// Main Sentry config name for Unity + /// + public const string ConfigName = "SentryOptions"; + + /// + /// UPM name of Sentry Unity SDK (package.json) + /// + public const string PackageName = "io.sentry.unity"; + + public bool Enabled { get; set; } = true; + public bool CaptureInEditor { get; set; } = true; // Lower entry barrier, likely set to false after initial setup. + public string? Dsn { get; set; } + public bool Debug { get; set; } = true; // By default on only + public bool DebugOnlyInEditor { get; set; } = true; + public SentryLevel DiagnosticsLevel { get; set; } = SentryLevel.Error; // By default logs out Error or higher. // Ideally this would be per platform // Auto allows us to try figure out things in the SDK depending on the platform. Any other value means an explicit user choice. - [field: SerializeField] public SentryUnityCompression RequestBodyCompressionLevel { get; set; } = SentryUnityCompression.Auto; - [field: SerializeField] public bool AttachStacktrace { get; set; } - [field: SerializeField] public float SampleRate { get; set; } = 1.0f; + public SentryUnityCompression RequestBodyCompressionLevel { get; set; } = SentryUnityCompression.Auto; + public bool AttachStacktrace { get; set; } + public float SampleRate { get; set; } = 1.0f; - [field: NonSerialized] public IDiagnosticLogger? Logger { get; private set; } - [field: NonSerialized] public string? Release { get; set; } - [field: NonSerialized] public string? Environment { get; set; } + public IDiagnosticLogger? Logger { get; private set; } + public string? Release { get; set; } + public string? Environment { get; set; } - public void OnEnable() + // Can't rely on Unity's OnEnable() hook. + public UnitySentryOptions TryAttachLogger() { - hideFlags = HideFlags.DontUnloadUnusedAsset; Logger = Debug && (!DebugOnlyInEditor || Application.isEditor) ? new UnityLogger(DiagnosticsLevel) : null; + + return this; + } + + public void WriteTo(Utf8JsonWriter writer) + { + writer.WriteStartObject(); + + writer.WriteBoolean("enabled", Enabled); + writer.WriteBoolean("captureInEditor", CaptureInEditor); + + if (!string.IsNullOrWhiteSpace(Dsn)) + { + writer.WriteString("dsn", Dsn); + } + + writer.WriteBoolean("debug", Debug); + writer.WriteBoolean("debugOnlyInEditor", DebugOnlyInEditor); + writer.WriteNumber("diagnosticsLevel", (int)DiagnosticsLevel); + writer.WriteNumber("requestBodyCompressionLevel", (int)RequestBodyCompressionLevel); + writer.WriteBoolean("attachStacktrace", AttachStacktrace); + writer.WriteNumber("sampleRate", SampleRate); + + if (!string.IsNullOrWhiteSpace(Release)) + { + writer.WriteString("release", Release); + } + + if (!string.IsNullOrWhiteSpace(Environment)) + { + writer.WriteString("environment", Environment); + } + + writer.WriteEndObject(); + writer.Flush(); + } + + public static UnitySentryOptions FromJson(JsonElement json) + => new() + { + Enabled = json.GetPropertyOrNull("enabled")?.GetBoolean() ?? true, + Dsn = json.GetPropertyOrNull("dsn")?.GetString(), + CaptureInEditor = json.GetPropertyOrNull("captureInEditor")?.GetBoolean() ?? false, + Debug = json.GetPropertyOrNull("debug")?.GetBoolean() ?? true, + DebugOnlyInEditor = json.GetPropertyOrNull("debugOnlyInEditor")?.GetBoolean() ?? true, + DiagnosticsLevel = json.GetEnumOrNull("diagnosticsLevel") ?? SentryLevel.Error, + RequestBodyCompressionLevel = json.GetEnumOrNull("requestBodyCompressionLevel") ?? SentryUnityCompression.Auto, + AttachStacktrace = json.GetPropertyOrNull("attachStacktrace")?.GetBoolean() ?? false, + SampleRate = json.GetPropertyOrNull("sampleRate")?.GetSingle() ?? 1.0f, + Release = json.GetPropertyOrNull("release")?.GetString(), + Environment = json.GetPropertyOrNull("environment")?.GetString() + }; + + public static UnitySentryOptions LoadFromUnity() + { + // We should use `TextAsset` for read-only access in runtime. It's platform agnostic. + var sentryOptionsTextAsset = Resources.Load($"{ConfigRootFolder}/{ConfigName}"); + using var jsonDocument = JsonDocument.Parse(sentryOptionsTextAsset.bytes); + return FromJson(jsonDocument.RootElement).TryAttachLogger(); + } + + public void SaveToUnity(string path) + { + using var fileStream = new FileStream(path, FileMode.Create); + using var writer = new Utf8JsonWriter(fileStream); + WriteTo(writer); + } + } + + internal static class JsonExtensions + { + // From Sentry.Internal.Extensions.JsonExtensions + public static JsonElement? GetPropertyOrNull(this JsonElement json, string name) + { + if (json.ValueKind != JsonValueKind.Object) + { + return null; + } + + if (json.TryGetProperty(name, out var result)) + { + if (json.ValueKind == JsonValueKind.Undefined || + json.ValueKind == JsonValueKind.Null) + { + return null; + } + + return result; + } + + return null; + } + + public static TEnum? GetEnumOrNull(this JsonElement json, string name) + where TEnum : struct + { + var enumString = json.GetPropertyOrNull(name)?.ToString(); + if (string.IsNullOrWhiteSpace(enumString)) + { + return null; + } + + if (!Enum.TryParse(enumString, true, out TEnum value)) + { + return null; + } + + return value; } } } diff --git a/src/Sentry.Unity/link.xml b/src/Sentry.Unity/link.xml index 527c6b6a4..17f19c9b9 100644 --- a/src/Sentry.Unity/link.xml +++ b/src/Sentry.Unity/link.xml @@ -1,5 +1,7 @@  + + @@ -29,6 +31,7 @@ + diff --git a/src/test/Sentry.Unity.Editor.Tests/EditorModeTests.cs b/src/test/Sentry.Unity.Editor.Tests/EditorModeTests.cs index 5111a5288..a59f7fdd6 100644 --- a/src/test/Sentry.Unity.Editor.Tests/EditorModeTests.cs +++ b/src/test/Sentry.Unity.Editor.Tests/EditorModeTests.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Generic; +using System.IO; using System.Linq; using NUnit.Framework; using UnityEngine; @@ -21,9 +22,11 @@ public IEnumerator InitializeOptions() // and we should find a proper way to solve this. if (!SentryInitialization.IsInit) { - var options = ScriptableObject.CreateInstance(); - options.Dsn = "https://94677106febe46b88b9b9ae5efd18a00@o447951.ingest.sentry.io/5439417"; - options.Enabled = true; + var options = new UnitySentryOptions + { + Dsn = "https://94677106febe46b88b9b9ae5efd18a00@o447951.ingest.sentry.io/5439417", + Enabled = true + }; SentryInitialization.Init(options); @@ -55,5 +58,18 @@ public void OptionsDsnField_WrongFormat_CreatesError() Assert.AreEqual(1, validationErrors.Count); Assert.NotNull(validationErrors.SingleOrDefault(e => e.PropertyName.Contains(nameof(SentryTestWindow.Options.Dsn)))); } + + // This test method has a side effect of creating 'link.xml' if file doesn't exits. + [Test] + public void SentryTestWindow_OpenAndLinkXmlCopied_Successful() + { + LogAssert.ignoreFailingMessages = true; // mandatory + + // Open & Close window to trigger 'link.xml' logic + SentryTestWindow.Open().Dispose(); + + var linkXmlPath = $"{Application.dataPath}/Resources/{UnitySentryOptions.ConfigRootFolder}/link.xml"; + Assert.IsTrue(File.Exists(linkXmlPath)); + } } } diff --git a/src/test/Sentry.Unity.Tests/PlayModeTests.cs b/src/test/Sentry.Unity.Tests/PlayModeTests.cs index f8c35306c..b022b1991 100644 --- a/src/test/Sentry.Unity.Tests/PlayModeTests.cs +++ b/src/test/Sentry.Unity.Tests/PlayModeTests.cs @@ -23,9 +23,11 @@ public IEnumerator InitializeOptions() // and we should find a proper way to solve this. if (!SentryInitialization.IsInit) { - var options = ScriptableObject.CreateInstance(); - options.Dsn = "https://94677106febe46b88b9b9ae5efd18a00@o447951.ingest.sentry.io/5439417"; - options.Enabled = true; + var options = new UnitySentryOptions + { + Dsn = "https://94677106febe46b88b9b9ae5efd18a00@o447951.ingest.sentry.io/5439417", + Enabled = true + }; SentryInitialization.Init(options); diff --git a/src/test/Sentry.Unity.Tests/Sentry.Unity.Tests.csproj b/src/test/Sentry.Unity.Tests/Sentry.Unity.Tests.csproj index 3054e39cc..f37ef0817 100644 --- a/src/test/Sentry.Unity.Tests/Sentry.Unity.Tests.csproj +++ b/src/test/Sentry.Unity.Tests/Sentry.Unity.Tests.csproj @@ -3,4 +3,9 @@ netstandard2.0 $(PackageRuntimeTestsPath) + + + PreserveNewest + + diff --git a/src/test/Sentry.Unity.Tests/TestSentryOptions.json b/src/test/Sentry.Unity.Tests/TestSentryOptions.json new file mode 100644 index 000000000..c33d0c4aa --- /dev/null +++ b/src/test/Sentry.Unity.Tests/TestSentryOptions.json @@ -0,0 +1 @@ +{"enabled":true,"captureInEditor":true,"dsn":"http://test.com","debug":true,"debugOnlyInEditor":true,"diagnosticsLevel":3,"requestBodyCompressionLevel":1,"attachStacktrace":true,"sampleRate":1,"release":"release","environment":"test"} diff --git a/src/test/Sentry.Unity.Tests/UnitySentryOptionsTest.cs b/src/test/Sentry.Unity.Tests/UnitySentryOptionsTest.cs new file mode 100644 index 000000000..4a11ab8ee --- /dev/null +++ b/src/test/Sentry.Unity.Tests/UnitySentryOptionsTest.cs @@ -0,0 +1,80 @@ +using System; +using System.IO; +using System.Reflection; +using System.Text; +using System.Text.Json; +using NUnit.Framework; + +namespace Sentry.Unity.Tests +{ + public sealed class UnitySentryOptionsTest + { + private const string TestSentryOptionsFileName = "TestSentryOptions.json"; + + [Test] + public void Options_ReadFromFile_Success() + { + var optionsFilePath = GetTestOptionsFilePath(); + Assert.IsTrue(File.Exists(optionsFilePath)); + + var jsonRaw = File.ReadAllText(optionsFilePath); + using var jsonDocument = JsonDocument.Parse(jsonRaw); + + UnitySentryOptions.FromJson(jsonDocument.RootElement); + } + + [Test] + public void Options_WriteRead_Equals() + { + // arrange + var optionsExpected = new UnitySentryOptions + { + Enabled = true, + Dsn = "http://test.com", + CaptureInEditor = true, + Debug = true, + DebugOnlyInEditor = false, + DiagnosticsLevel = SentryLevel.Info, + RequestBodyCompressionLevel = SentryUnityCompression.Optimal, + AttachStacktrace = true, + SampleRate = 1.15f, + Release = "release", + Environment = "test" + }; + + // act + using var memory = new MemoryStream(); + using var writer = new Utf8JsonWriter(memory); + optionsExpected.WriteTo(writer); + + var jsonRaw = Encoding.UTF8.GetString(memory.ToArray()); + using var jsonDocument = JsonDocument.Parse(jsonRaw); + var optionsActual = UnitySentryOptions.FromJson(jsonDocument.RootElement); + + // assert + AssertOptions(optionsActual, optionsExpected); + } + + private static void AssertOptions(UnitySentryOptions actual, UnitySentryOptions expected) + { + Assert.AreEqual(expected.Enabled, actual.Enabled); + Assert.AreEqual(expected.Dsn, actual.Dsn); + Assert.AreEqual(expected.CaptureInEditor, actual.CaptureInEditor); + Assert.AreEqual(expected.Debug, actual.Debug); + Assert.AreEqual(expected.DebugOnlyInEditor, actual.DebugOnlyInEditor); + Assert.AreEqual(expected.DiagnosticsLevel, actual.DiagnosticsLevel); + Assert.AreEqual(expected.RequestBodyCompressionLevel, actual.RequestBodyCompressionLevel); + Assert.AreEqual(expected.AttachStacktrace, actual.AttachStacktrace); + Assert.AreEqual(expected.SampleRate, actual.SampleRate); + Assert.AreEqual(expected.Release, actual.Release); + Assert.AreEqual(expected.Environment, actual.Environment); + } + + private static string GetTestOptionsFilePath() + { + var assemblyFolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + Assert.NotNull(assemblyFolderPath); + return Path.Combine(assemblyFolderPath!, TestSentryOptionsFileName); + } + } +}