diff --git a/ModuleManager/Cats/CatAnimator.cs b/ModuleManager/Cats/CatAnimator.cs index ed5b2c70..7806d4aa 100644 --- a/ModuleManager/Cats/CatAnimator.cs +++ b/ModuleManager/Cats/CatAnimator.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Diagnostics.CodeAnalysis; using UnityEngine; namespace ModuleManager.Cats @@ -12,9 +13,10 @@ class CatAnimator : MonoBehaviour private SpriteRenderer spriteRenderer; private int spriteIdx; + [SuppressMessage("CodeQuality", "IDE0051", Justification = "Called by Unity")] void Start() { - spriteRenderer = this.GetComponent(); + spriteRenderer = GetComponent(); spriteRenderer.sortingOrder = 3; StartCoroutine(Animate()); } diff --git a/ModuleManager/Cats/CatManager.cs b/ModuleManager/Cats/CatManager.cs index 0567de54..05f671d6 100644 --- a/ModuleManager/Cats/CatManager.cs +++ b/ModuleManager/Cats/CatManager.cs @@ -14,7 +14,7 @@ public static void LaunchCat() InitCats(); GameObject cat = LaunchCat(scale); - CatMover catMover = cat.AddComponent(); + cat.AddComponent(); } public static void LaunchCats() @@ -94,7 +94,7 @@ private static GameObject LaunchCat(int scale) sr.sprite = catFrames[0]; - trail.material = new Material(Shader.Find("Particles/Alpha Blended")); + trail.material = new Material(Shader.Find("Legacy Shaders/Particles/Alpha Blended")); Debug.Log("material = " + trail.material); trail.material.mainTexture = rainbow; diff --git a/ModuleManager/Cats/CatMover.cs b/ModuleManager/Cats/CatMover.cs index f5dd7678..d97a3f7c 100644 --- a/ModuleManager/Cats/CatMover.cs +++ b/ModuleManager/Cats/CatMover.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Diagnostics.CodeAnalysis; using UnityEngine; namespace ModuleManager.Cats @@ -21,13 +21,16 @@ public class CatMover : MonoBehaviour private const float time = 5; private const float trailTime = time / 4; + private bool clearTrail = false; + // Use this for initialization + [SuppressMessage("CodeQuality", "IDE0051", Justification = "Called by Unity")] void Start() { - trail = this.GetComponent(); + trail = GetComponent(); trail.sortingOrder = 2; - spriteRenderer = this.GetComponent(); + spriteRenderer = GetComponent(); offsetY = Mathf.FloorToInt(0.2f * Screen.height); @@ -36,8 +39,10 @@ void Start() totalLenth = (int) (Screen.width / time * trail.time) + 150; trail.time = trailTime; trail.widthCurve = new AnimationCurve(new Keyframe(0, trail.startWidth ), new Keyframe(0.7f, trail.startWidth), new Keyframe(1, trail.startWidth * 0.9f)); + clearTrail = true; } + [SuppressMessage("CodeQuality", "IDE0051", Justification = "Called by Unity")] void Update() { if (trail.time <= 0f) @@ -50,7 +55,7 @@ void Update() if (activePos > (Screen.width + totalLenth)) { activePos = -spriteRenderer.sprite.rect.width; - trail.time = 0; + clearTrail = true; } float f = 2f * Mathf.PI * (activePos) / (Screen.width * 0.5f); @@ -62,6 +67,13 @@ void Update() transform.position = KSP.UI.UIMainCamera.Camera.ScreenToWorldPoint(spos); transform.rotation = Quaternion.Euler(0, 0, Mathf.Cos(f) * 0.25f * Mathf.PI * Mathf.Rad2Deg); + + if (clearTrail) + { + trail.Clear(); + clearTrail = false; + } + } diff --git a/ModuleManager/Cats/CatOrbiter.cs b/ModuleManager/Cats/CatOrbiter.cs index 17621c6f..34b1f6e8 100644 --- a/ModuleManager/Cats/CatOrbiter.cs +++ b/ModuleManager/Cats/CatOrbiter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using KSP.UI; using UnityEngine; using Random = UnityEngine.Random; @@ -8,7 +9,7 @@ namespace ModuleManager.Cats { class CatOrbiter : MonoBehaviour { - private static List orbiters = new List(); + private static readonly List orbiters = new List(); private static CatOrbiter sun; @@ -20,7 +21,7 @@ class CatOrbiter : MonoBehaviour private Vector2d force; private float scale = 1; - private double G = 6.67408E-11; + private const double G = 6.67408E-11; public double Mass { @@ -113,13 +114,14 @@ private void DoForces() } } - + [SuppressMessage("CodeQuality", "IDE0051", Justification = "Called by Unity")] void OnDestroy() { orbiters.Remove(this); TimingManager.FixedUpdateRemove(TimingManager.TimingStage.Earlyish, DoForces); } + [SuppressMessage("CodeQuality", "IDE0051", Justification = "Called by Unity")] void FixedUpdate() { //if (this == sun) diff --git a/ModuleManager/Collections/ImmutableStack.cs b/ModuleManager/Collections/ImmutableStack.cs index 5d181567..d710c423 100644 --- a/ModuleManager/Collections/ImmutableStack.cs +++ b/ModuleManager/Collections/ImmutableStack.cs @@ -8,7 +8,7 @@ public class ImmutableStack : IEnumerable { public struct Enumerator : IEnumerator { - private ImmutableStack head; + private readonly ImmutableStack head; private ImmutableStack currentStack; public Enumerator(ImmutableStack stack) diff --git a/ModuleManager/Collections/KeyValueCache.cs b/ModuleManager/Collections/KeyValueCache.cs new file mode 100644 index 00000000..0aaee61b --- /dev/null +++ b/ModuleManager/Collections/KeyValueCache.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace ModuleManager.Collections +{ + public class KeyValueCache + { + private readonly Dictionary dict = new Dictionary(); + private readonly object lockObject = new object(); + + public TValue Fetch(TKey key, Func createValue) + { + if (createValue == null) throw new ArgumentNullException(nameof(createValue)); + lock(lockObject) + { + if (dict.TryGetValue(key, out TValue value)) + { + return value; + } + else + { + TValue newValue = createValue(); + dict.Add(key, newValue); + return newValue; + } + } + } + } +} diff --git a/ModuleManager/Collections/MessageQueue.cs b/ModuleManager/Collections/MessageQueue.cs index 321a3503..b6038a77 100644 --- a/ModuleManager/Collections/MessageQueue.cs +++ b/ModuleManager/Collections/MessageQueue.cs @@ -12,7 +12,7 @@ public interface IMessageQueue : IEnumerable public class MessageQueue : IMessageQueue, IEnumerable { - public class Enumerator : IEnumerator + public sealed class Enumerator : IEnumerator { private readonly MessageQueue queue; private Node current; diff --git a/ModuleManager/CustomConfigsManager.cs b/ModuleManager/CustomConfigsManager.cs index e2b8689c..d7444363 100644 --- a/ModuleManager/CustomConfigsManager.cs +++ b/ModuleManager/CustomConfigsManager.cs @@ -16,16 +16,6 @@ internal void Start() Log("Setting modded tech tree as the active one"); HighLogic.CurrentGame.Parameters.Career.TechTreeUrl = techTreeFile; } - - if (PhysicsGlobals.PhysicsDatabaseFilename != physicsFile && File.Exists(physicsPath)) - { - Log("Setting modded physics as the active one"); - - PhysicsGlobals.PhysicsDatabaseFilename = physicsFile; - - if (!PhysicsGlobals.Instance.LoadDatabase()) - Log("Something went wrong while setting the active physics config."); - } } public static void Log(String s) diff --git a/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs b/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs new file mode 100644 index 00000000..471b1fa8 --- /dev/null +++ b/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace ModuleManager.UnityLogHandle +{ + class InterceptLogHandler : ILogHandler + { + private readonly ILogHandler baseLogHandler; + private readonly List brokenAssemblies = new List(); + private readonly int gamePathLength; + + public static string Warnings { get; private set; } = ""; + + public InterceptLogHandler(ILogHandler baseLogHandler) + { + this.baseLogHandler = baseLogHandler ?? throw new ArgumentNullException(nameof(baseLogHandler)); + gamePathLength = Path.GetFullPath(KSPUtil.ApplicationRootPath).Length; + } + + public void LogFormat(LogType logType, Object context, string format, params object[] args) + { + baseLogHandler.LogFormat(logType, context, format, args); + } + + public void LogException(Exception exception, Object context) + { + baseLogHandler.LogException(exception, context); + + if (exception is ReflectionTypeLoadException ex) + { + string message = "Intercepted a ReflectionTypeLoadException. List of broken DLLs:\n"; + try + { + var assemblies = ex.Types.Where(x => x != null).Select(x => x.Assembly).Distinct(); + foreach (Assembly assembly in assemblies) + { + if (Warnings == "") + { + Warnings = "Mod(s) DLL that are not compatible with this version of KSP\n"; + } + string modInfo = assembly.GetName().Name + " " + assembly.GetName().Version + " " + + assembly.Location.Remove(0, gamePathLength) + "\n"; + if (!brokenAssemblies.Contains(assembly)) + { + brokenAssemblies.Add(assembly); + Warnings += modInfo; + } + message += modInfo; + } + } + catch (Exception e) + { + message += "Exception " + e.GetType().Name + " while handling the exception..."; + } + ModuleManager.Log(message); + } + } + } +} diff --git a/ModuleManager/Extensions/ByteArrayExtensions.cs b/ModuleManager/Extensions/ByteArrayExtensions.cs new file mode 100644 index 00000000..520393df --- /dev/null +++ b/ModuleManager/Extensions/ByteArrayExtensions.cs @@ -0,0 +1,29 @@ +using System; + +namespace ModuleManager.Extensions +{ + public static class ByteArrayExtensions + { + public static string ToHex(this byte[] data) + { + if (data == null) throw new ArgumentNullException(nameof(data)); + char[] result = new char[data.Length * 2]; + + for (int i = 0; i < data.Length; i++) + { + result[i * 2] = GetHexValue(data[i] / 16); + result[i * 2 + 1] = GetHexValue(data[i] % 16); + } + + return new string(result); + } + + private static char GetHexValue(int i) + { + if (i < 10) + return (char)(i + '0'); + else + return (char)(i - 10 + 'a'); + } + } +} diff --git a/ModuleManager/Extensions/ConfigNodeExtensions.cs b/ModuleManager/Extensions/ConfigNodeExtensions.cs index 6940048a..9f552566 100644 --- a/ModuleManager/Extensions/ConfigNodeExtensions.cs +++ b/ModuleManager/Extensions/ConfigNodeExtensions.cs @@ -19,7 +19,7 @@ public static ConfigNode DeepCopy(this ConfigNode from) { ConfigNode to = new ConfigNode(from.name); foreach (ConfigNode.Value value in from.values) - to.AddValue(value.name, value.value); + to.AddValueSafe(value.name, value.value); foreach (ConfigNode node in from.nodes) { ConfigNode newNode = DeepCopy(node); @@ -68,5 +68,40 @@ public static void PrettyPrint(this ConfigNode node, ref StringBuilder sb, strin sb.AppendFormat("{0}}}\n", indent); } + + public static void AddValueSafe(this ConfigNode node, string name, string value) + { + node.values.Add(new ConfigNode.Value(name, value)); + } + + public static void EscapeValuesRecursive(this ConfigNode theNode) + { + foreach (ConfigNode subNode in theNode.nodes) + { + subNode.EscapeValuesRecursive(); + } + + foreach (ConfigNode.Value value in theNode.values) + { + value.value = value.value.Replace("\n", "\\n"); + value.value = value.value.Replace("\r", "\\r"); + value.value = value.value.Replace("\t", "\\t"); + } + } + + public static void UnescapeValuesRecursive(this ConfigNode theNode) + { + foreach (ConfigNode subNode in theNode.nodes) + { + subNode.UnescapeValuesRecursive(); + } + + foreach (ConfigNode.Value value in theNode.values) + { + value.value = value.value.Replace("\\n", "\n"); + value.value = value.value.Replace("\\r", "\r"); + value.value = value.value.Replace("\\t", "\t"); + } + } } } diff --git a/ModuleManager/Extensions/IBasicLoggerExtensions.cs b/ModuleManager/Extensions/IBasicLoggerExtensions.cs index 46c4c5a4..9fd29be3 100644 --- a/ModuleManager/Extensions/IBasicLoggerExtensions.cs +++ b/ModuleManager/Extensions/IBasicLoggerExtensions.cs @@ -6,8 +6,21 @@ namespace ModuleManager.Extensions { public static class IBasicLoggerExtensions { - public static void Info(this IBasicLogger logger, string message) => logger.Log(LogType.Log, message); - public static void Warning(this IBasicLogger logger, string message) => logger.Log(LogType.Warning, message); - public static void Error(this IBasicLogger logger, string message) => logger.Log(LogType.Error, message); + public static void Info(this IBasicLogger logger, string message) => logger.Log(new LogMessage(LogType.Log, message)); + public static void Warning(this IBasicLogger logger, string message) => logger.Log(new LogMessage(LogType.Warning, message)); + public static void Error(this IBasicLogger logger, string message) => logger.Log(new LogMessage(LogType.Error, message)); + + public static void Exception(this IBasicLogger logger, Exception exception) + { + if (exception == null) throw new ArgumentNullException(nameof(exception)); + logger.Log(new LogMessage(LogType.Exception, exception.ToString())); + } + + public static void Exception(this IBasicLogger logger, string message, Exception exception) + { + if (message == null) throw new ArgumentNullException(nameof(message)); + if (exception == null) throw new ArgumentNullException(nameof(exception)); + logger.Log(new LogMessage(LogType.Exception, message + ": " + exception.ToString())); + } } } diff --git a/ModuleManager/Extensions/StringExtensions.cs b/ModuleManager/Extensions/StringExtensions.cs index c4190045..634fab7a 100644 --- a/ModuleManager/Extensions/StringExtensions.cs +++ b/ModuleManager/Extensions/StringExtensions.cs @@ -18,11 +18,20 @@ public static bool IsBracketBalanced(this string s) return level == 0; } - private static Regex whitespaceRegex = new Regex(@"\s+"); + private static readonly Regex whitespaceRegex = new Regex(@"\s+"); public static string RemoveWS(this string withWhite) { return whitespaceRegex.Replace(withWhite, ""); } + + public static bool Contains(this string str, string value, out int index) + { + if (str == null) throw new ArgumentNullException(nameof(str)); + if (value == null) throw new ArgumentNullException(nameof(value)); + + index = str.IndexOf(value, StringComparison.CurrentCultureIgnoreCase); + return index != -1; + } } } diff --git a/ModuleManager/Extensions/UrlDirExtensions.cs b/ModuleManager/Extensions/UrlDirExtensions.cs new file mode 100644 index 00000000..3a57bf6a --- /dev/null +++ b/ModuleManager/Extensions/UrlDirExtensions.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; + +namespace ModuleManager.Extensions +{ + public static class UrlDirExtensions + { + public static UrlDir.UrlFile Find(this UrlDir urlDir, string url) + { + if (urlDir == null) throw new ArgumentNullException(nameof(urlDir)); + if (url == null) throw new ArgumentNullException(nameof(url)); + string[] splits = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + + UrlDir currentDir = urlDir; + + for (int i = 0; i < splits.Length - 1; i++) + { + currentDir = currentDir.children.FirstOrDefault(subDir => subDir.name == splits[i]); + if (currentDir == null) return null; + } + + string fileName = splits[splits.Length - 1]; + string fileExtension = null; + + int idx = fileName.LastIndexOf('.'); + + if (idx > -1) + { + fileExtension = fileName.Substring(idx + 1); + fileName = fileName.Substring(0, idx); + } + + foreach (UrlDir.UrlFile file in currentDir.files) + { + if (file.name != fileName) continue; + if (fileExtension != null && fileExtension != file.fileExtension) continue; + return file; + } + + return null; + } + } +} diff --git a/ModuleManager/Extensions/UrlFileExtensions.cs b/ModuleManager/Extensions/UrlFileExtensions.cs new file mode 100644 index 00000000..3d203b24 --- /dev/null +++ b/ModuleManager/Extensions/UrlFileExtensions.cs @@ -0,0 +1,16 @@ +using System; + +namespace ModuleManager.Extensions +{ + public static class UrlFileExtensions + { + public static string GetUrlWithExtension(this UrlDir.UrlFile urlFile) + { + return $"{urlFile.url}.{urlFile.fileExtension}"; + } + public static string GetNameWithExtension(this UrlDir.UrlFile urlFile) + { + return $"{urlFile.name}.{urlFile.fileExtension}"; + } + } +} diff --git a/ModuleManager/FilePathRepository.cs b/ModuleManager/FilePathRepository.cs index 4ba34321..0d1448d7 100644 --- a/ModuleManager/FilePathRepository.cs +++ b/ModuleManager/FilePathRepository.cs @@ -5,20 +5,21 @@ namespace ModuleManager { internal static class FilePathRepository { - internal static readonly string cachePath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigCache"); + internal static readonly string normalizedRootPath = Path.GetFullPath(KSPUtil.ApplicationRootPath); + internal static readonly string cachePath = Path.Combine(normalizedRootPath, "GameData", "ModuleManager.ConfigCache"); internal static readonly string techTreeFile = Path.Combine("GameData", "ModuleManager.TechTree"); - internal static readonly string techTreePath = Path.Combine(KSPUtil.ApplicationRootPath, techTreeFile); + internal static readonly string techTreePath = Path.Combine(normalizedRootPath, techTreeFile); internal static readonly string physicsFile = Path.Combine("GameData", "ModuleManager.Physics"); - internal static readonly string physicsPath = Path.Combine(KSPUtil.ApplicationRootPath, physicsFile); - internal static readonly string defaultPhysicsPath = Path.Combine(KSPUtil.ApplicationRootPath, "Physics.cfg"); + internal static readonly string physicsPath = Path.Combine(normalizedRootPath, physicsFile); + internal static readonly string defaultPhysicsPath = Path.Combine(normalizedRootPath, "Physics.cfg"); - internal static readonly string partDatabasePath = Path.Combine(KSPUtil.ApplicationRootPath, "PartDatabase.cfg"); + internal static readonly string partDatabasePath = Path.Combine(normalizedRootPath, "PartDatabase.cfg"); - internal static readonly string shaPath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigSHA"); + internal static readonly string shaPath = Path.Combine(normalizedRootPath, "GameData", "ModuleManager.ConfigSHA"); - internal static readonly string logsDirPath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "Logs"), "ModuleManager"); + internal static readonly string logsDirPath = Path.Combine(normalizedRootPath, "Logs", "ModuleManager"); internal static readonly string logPath = Path.Combine(logsDirPath, "ModuleManager.log"); internal static readonly string patchLogPath = Path.Combine(logsDirPath, "MMPatch.log"); } diff --git a/ModuleManager/Fix16.cs b/ModuleManager/Fix16.cs index ab20e6ae..b567eb56 100644 --- a/ModuleManager/Fix16.cs +++ b/ModuleManager/Fix16.cs @@ -1,9 +1,11 @@ using System.Collections; +using System.Diagnostics.CodeAnalysis; namespace ModuleManager { class Fix16 : LoadingSystem { + [SuppressMessage("CodeQuality", "IDE0051", Justification = "Called by Unity")] private void Awake() { if (Instance != null) diff --git a/ModuleManager/Logging/ExceptionMessage.cs b/ModuleManager/Logging/ExceptionMessage.cs deleted file mode 100644 index fceb166d..00000000 --- a/ModuleManager/Logging/ExceptionMessage.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace ModuleManager.Logging -{ - public class ExceptionMessage : ILogMessage - { - public readonly string message; - public readonly Exception exception; - - public ExceptionMessage(string message, Exception exception) - { - this.message = message; - this.exception = exception; - } - - public void LogTo(IBasicLogger logger) - { - logger.Exception(message, exception); - } - } -} diff --git a/ModuleManager/Logging/IBasicLogger.cs b/ModuleManager/Logging/IBasicLogger.cs index c2fdd85e..25cd9367 100644 --- a/ModuleManager/Logging/IBasicLogger.cs +++ b/ModuleManager/Logging/IBasicLogger.cs @@ -1,12 +1,10 @@ using System; -using UnityEngine; namespace ModuleManager.Logging { // Stripped down version of UnityEngine.ILogger public interface IBasicLogger { - void Log(LogType logType, string message); - void Exception(string message, Exception exception); + void Log(ILogMessage message); } } diff --git a/ModuleManager/Logging/ILogMessage.cs b/ModuleManager/Logging/ILogMessage.cs index 3193f973..bf85f488 100644 --- a/ModuleManager/Logging/ILogMessage.cs +++ b/ModuleManager/Logging/ILogMessage.cs @@ -1,9 +1,13 @@ using System; +using UnityEngine; namespace ModuleManager.Logging { public interface ILogMessage { - void LogTo(IBasicLogger logger); + LogType LogType { get; } + DateTime Timestamp { get; } + string Message { get; } + string ToLogString(); } } diff --git a/ModuleManager/Logging/LogMessage.cs b/ModuleManager/Logging/LogMessage.cs new file mode 100644 index 00000000..c6554aaf --- /dev/null +++ b/ModuleManager/Logging/LogMessage.cs @@ -0,0 +1,53 @@ +using System; +using UnityEngine; + +namespace ModuleManager.Logging +{ + public class LogMessage : ILogMessage + { + private const string DATETIME_FORMAT_STRING = "HH:mm:ss.fff"; + + public LogType LogType { get; } + public DateTime Timestamp { get; } + public string Message { get; } + + public LogMessage(LogType logType, string message) + { + LogType = logType; + Timestamp = DateTime.Now; + Message = message ?? throw new ArgumentNullException(nameof(message)); + } + + public LogMessage(ILogMessage logMessage, string newMessage) + { + if (logMessage == null) throw new ArgumentNullException(nameof(logMessage)); + LogType = logMessage.LogType; + Timestamp = logMessage.Timestamp; + Message = newMessage ?? throw new ArgumentNullException(nameof(newMessage)); + } + + public string ToLogString() + { + string prefix; + if (LogType == LogType.Log) + prefix = "LOG"; + else if (LogType == LogType.Warning) + prefix = "WRN"; + else if (LogType == LogType.Error) + prefix = "ERR"; + else if (LogType == LogType.Assert) + prefix = "AST"; + else if (LogType == LogType.Exception) + prefix = "EXC"; + else + prefix = "???"; + + return $"[{prefix} {Timestamp.ToString(DATETIME_FORMAT_STRING)}] {Message}"; + } + + public override string ToString() + { + return $"[{GetType().FullName} LogType={LogType} Message={Message}]"; + } + } +} diff --git a/ModuleManager/Logging/LogSplitter.cs b/ModuleManager/Logging/LogSplitter.cs index 53712f20..b1d2c89f 100644 --- a/ModuleManager/Logging/LogSplitter.cs +++ b/ModuleManager/Logging/LogSplitter.cs @@ -1,5 +1,4 @@ using System; -using UnityEngine; namespace ModuleManager.Logging { @@ -14,16 +13,11 @@ public LogSplitter(IBasicLogger logger1, IBasicLogger logger2) this.logger2 = logger2 ?? throw new ArgumentNullException(nameof(logger2)); } - public void Log(LogType logType, string message) + public void Log(ILogMessage message) { - logger1.Log(logType, message); - logger2.Log(logType, message); - } - - public void Exception(string message, Exception exception) - { - logger1.Exception(message, exception); - logger2.Exception(message, exception); + if (message == null) throw new ArgumentNullException(nameof(message)); + logger1.Log(message); + logger2.Log(message); } } } diff --git a/ModuleManager/Logging/ModLogger.cs b/ModuleManager/Logging/ModLogger.cs deleted file mode 100644 index eb2d3699..00000000 --- a/ModuleManager/Logging/ModLogger.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using UnityEngine; - -namespace ModuleManager.Logging -{ - public class ModLogger : IBasicLogger - { - private string prefix; - private IBasicLogger logger; - - public ModLogger(string prefix, IBasicLogger logger) - { - if (string.IsNullOrEmpty(prefix)) throw new ArgumentNullException(nameof(prefix)); - this.prefix = "[" + prefix + "] "; - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public void Log(LogType logType, string message) => logger.Log(logType, prefix + message); - public void Exception(string message, Exception exception) => logger.Exception(prefix + message, exception); - } -} diff --git a/ModuleManager/Logging/NormalMessage.cs b/ModuleManager/Logging/NormalMessage.cs deleted file mode 100644 index 549f9aa8..00000000 --- a/ModuleManager/Logging/NormalMessage.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using UnityEngine; - -namespace ModuleManager.Logging -{ - public class NormalMessage : ILogMessage - { - public readonly LogType logType; - public readonly string message; - - public NormalMessage(LogType logType, string message) - { - this.logType = logType; - this.message = message; - } - - public void LogTo(IBasicLogger logger) - { - logger.Log(logType, message); - } - } -} diff --git a/ModuleManager/Logging/PrefixLogger.cs b/ModuleManager/Logging/PrefixLogger.cs new file mode 100644 index 00000000..410473fa --- /dev/null +++ b/ModuleManager/Logging/PrefixLogger.cs @@ -0,0 +1,23 @@ +using System; + +namespace ModuleManager.Logging +{ + public class PrefixLogger : IBasicLogger + { + private readonly string prefix; + private readonly IBasicLogger logger; + + public PrefixLogger(string prefix, IBasicLogger logger) + { + if (string.IsNullOrEmpty(prefix)) throw new ArgumentNullException(nameof(prefix)); + this.prefix = $"[{prefix}] "; + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public void Log(ILogMessage message) + { + if (message == null) throw new ArgumentNullException(nameof(message)); + logger.Log(new LogMessage(message, prefix + message.Message)); + } + } +} diff --git a/ModuleManager/Logging/QueueLogRunner.cs b/ModuleManager/Logging/QueueLogRunner.cs index 2ed5c300..f78cf611 100644 --- a/ModuleManager/Logging/QueueLogRunner.cs +++ b/ModuleManager/Logging/QueueLogRunner.cs @@ -47,7 +47,7 @@ public void Run(IBasicLogger logger) foreach (ILogMessage message in logQueue.TakeAll()) { - message.LogTo(logger); + logger.Log(message); } long timeRemaining = timeToWaitForLogsMs - stopwatch.ElapsedMilliseconds; @@ -59,7 +59,7 @@ public void Run(IBasicLogger logger) foreach (ILogMessage message in logQueue.TakeAll()) { - message.LogTo(logger); + logger.Log(message); } state = State.Stopped; diff --git a/ModuleManager/Logging/QueueLogger.cs b/ModuleManager/Logging/QueueLogger.cs index 82d0bb8e..b7cd288a 100644 --- a/ModuleManager/Logging/QueueLogger.cs +++ b/ModuleManager/Logging/QueueLogger.cs @@ -1,5 +1,4 @@ using System; -using UnityEngine; using ModuleManager.Collections; namespace ModuleManager.Logging @@ -10,10 +9,13 @@ public class QueueLogger : IBasicLogger public QueueLogger(IMessageQueue queue) { - this.queue = queue; + this.queue = queue ?? throw new ArgumentNullException(nameof(queue)); } - public void Log(LogType logType, string message) => queue.Add(new NormalMessage(logType, message)); - public void Exception(string message, Exception exception) => queue.Add(new ExceptionMessage(message, exception)); + public void Log(ILogMessage message) + { + if (message == null) throw new ArgumentNullException(nameof(message)); + queue.Add(message); + } } } diff --git a/ModuleManager/Logging/StreamLogger.cs b/ModuleManager/Logging/StreamLogger.cs index 8f4b131a..d020f64c 100644 --- a/ModuleManager/Logging/StreamLogger.cs +++ b/ModuleManager/Logging/StreamLogger.cs @@ -1,48 +1,24 @@ using System; using System.IO; -using UnityEngine; namespace ModuleManager.Logging { - public class StreamLogger : IBasicLogger, IDisposable + public sealed class StreamLogger : IBasicLogger, IDisposable { - private const string DATETIME_FORMAT_STRING = "yyyy-MM-dd HH:mm:ss.fff"; - - private readonly Stream stream; private readonly StreamWriter streamWriter; private bool disposed = false; public StreamLogger(Stream stream) { - this.stream = stream ?? throw new ArgumentNullException(nameof(stream)); - if (!stream.CanWrite) throw new ArgumentException("must be writable", nameof(stream)); streamWriter = new StreamWriter(stream); } - public void Log(LogType logType, string message) + public void Log(ILogMessage message) { if (disposed) throw new InvalidOperationException("Object has already been disposed"); + if (message == null) throw new ArgumentNullException(nameof(message)); - string prefix; - if (logType == LogType.Log) - prefix = "LOG"; - else if (logType == LogType.Warning) - prefix = "WRN"; - else if (logType == LogType.Error) - prefix = "ERR"; - else if (logType == LogType.Assert) - prefix = "AST"; - else if (logType == LogType.Exception) - prefix = "EXC"; - else - prefix = "UNK"; - - streamWriter.WriteLine("[{0} {1}] {2}", prefix, DateTime.Now.ToString(DATETIME_FORMAT_STRING), message); - } - - public void Exception(string message, Exception exception) - { - Log(LogType.Exception, exception?.ToString() ?? ""); + streamWriter.WriteLine(message.ToLogString()); } public void Dispose() diff --git a/ModuleManager/Logging/UnityLogger.cs b/ModuleManager/Logging/UnityLogger.cs index 0655b807..d51063c4 100644 --- a/ModuleManager/Logging/UnityLogger.cs +++ b/ModuleManager/Logging/UnityLogger.cs @@ -1,24 +1,21 @@ using System; using UnityEngine; -using ModuleManager.Extensions; namespace ModuleManager.Logging { public class UnityLogger : IBasicLogger { - private ILogger logger; + private readonly ILogger logger; public UnityLogger(ILogger logger) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - public void Log(LogType logType, string message) => logger.Log(logType, message); - - public void Exception(string message, Exception exception) + public void Log(ILogMessage message) { - this.Error(message); - logger.LogException(exception); + if (message == null) throw new ArgumentNullException(nameof(message)); + logger.Log(message.LogType, message.Message); } } } diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index f3e0ab91..5278758b 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -8,8 +8,6 @@ using System.Reflection; using System.Text; using System.Text.RegularExpressions; -using UnityEngine; -using Debug = UnityEngine.Debug; using ModuleManager.Collections; using ModuleManager.Logging; @@ -24,8 +22,6 @@ namespace ModuleManager { - [SuppressMessage("ReSharper", "StringLastIndexOfIsCultureSpecific.1")] - [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] public class MMPatchLoader { private const string PHYSICS_NODE_NAME = "PHYSICSGLOBALS"; @@ -37,10 +33,10 @@ public class MMPatchLoader public static bool keepPartDB = false; - private static readonly Dictionary regexCache = new Dictionary(); + private static readonly KeyValueCache regexCache = new KeyValueCache(); private string configSha; - private Dictionary filesSha = new Dictionary(); + private readonly Dictionary filesSha = new Dictionary(); private const int STATUS_UPDATE_INVERVAL_MS = 33; @@ -89,11 +85,10 @@ public IEnumerable Run() QueueLogRunner logRunner = new QueueLogRunner(patchLogQueue); ITaskStatus loggingThreadStatus = BackgroundTask.Start(delegate { - using (StreamLogger streamLogger = new StreamLogger(new FileStream(patchLogPath, FileMode.Create))) - { - logRunner.Run(streamLogger); - streamLogger.Info("Done!"); - } + using StreamLogger streamLogger = new StreamLogger(new FileStream(patchLogPath, FileMode.Create)); + streamLogger.Info("Log started at " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")); + logRunner.Run(streamLogger); + streamLogger.Info("Done!"); }); IBasicLogger patchLogger = new LogSplitter(logger, new QueueLogger(patchLogQueue)); @@ -236,6 +231,9 @@ public IEnumerable Run() } } + if (KSP.Localization.Localizer.Instance != null) + KSP.Localization.Localizer.SwitchToLanguage(KSP.Localization.Localizer.CurrentLanguage); + logger.Info(status + "\n" + errors); patchSw.Stop(); @@ -283,16 +281,17 @@ private bool IsCacheUpToDate() Stopwatch sw = new Stopwatch(); sw.Start(); - System.Security.Cryptography.SHA256 sha = System.Security.Cryptography.SHA256.Create(); - System.Security.Cryptography.SHA256 filesha = System.Security.Cryptography.SHA256.Create(); + using System.Security.Cryptography.SHA256 sha = System.Security.Cryptography.SHA256.Create(); + using System.Security.Cryptography.SHA256 filesha = System.Security.Cryptography.SHA256.Create(); UrlDir.UrlFile[] files = GameDatabase.Instance.root.AllConfigFiles.ToArray(); filesSha.Clear(); for (int i = 0; i < files.Length; i++) { + string url = files[i].GetUrlWithExtension(); // Hash the file path so the checksum change if files are moved - byte[] pathBytes = Encoding.UTF8.GetBytes(files[i].url); + byte[] pathBytes = Encoding.UTF8.GetBytes(url); sha.TransformBlock(pathBytes, 0, pathBytes.Length, pathBytes, 0); // hash the file content @@ -300,13 +299,13 @@ private bool IsCacheUpToDate() sha.TransformBlock(contentBytes, 0, contentBytes.Length, contentBytes, 0); filesha.ComputeHash(contentBytes); - if (!filesSha.ContainsKey(files[i].url)) + if (!filesSha.ContainsKey(url)) { - filesSha.Add(files[i].url, BitConverter.ToString(filesha.Hash)); + filesSha.Add(url, BitConverter.ToString(filesha.Hash)); } else { - logger.Warning("Duplicate fileSha key. This should not append. The key is " + files[i].url); + logger.Warning("Duplicate fileSha key. This should not append. The key is " + url); } } @@ -369,13 +368,14 @@ private bool CheckFilesChange(UrlDir.UrlFile[] files, ConfigNode shaConfigNode) for (int i = 0; i < files.Length; i++) { - ConfigNode fileNode = GetFileNode(shaConfigNode, files[i].url); + string url = files[i].GetUrlWithExtension(); + ConfigNode fileNode = GetFileNode(shaConfigNode, url); string fileSha = fileNode?.GetValue("SHA"); if (fileNode == null) continue; - if (fileSha == null || filesSha[files[i].url] != fileSha) + if (fileSha == null || filesSha[url] != fileSha) { changes.Append("Changed : " + fileNode.GetValue("filename") + ".cfg\n"); noChange = false; @@ -383,18 +383,19 @@ private bool CheckFilesChange(UrlDir.UrlFile[] files, ConfigNode shaConfigNode) } for (int i = 0; i < files.Length; i++) { - ConfigNode fileNode = GetFileNode(shaConfigNode, files[i].url); + string url = files[i].GetUrlWithExtension(); + ConfigNode fileNode = GetFileNode(shaConfigNode, url); if (fileNode == null) { - changes.Append("Added : " + files[i].url + ".cfg\n"); + changes.Append("Added : " + url + "\n"); noChange = false; } shaConfigNode.RemoveNode(fileNode); } foreach (ConfigNode fileNode in shaConfigNode.GetNodes()) { - changes.Append("Deleted : " + fileNode.GetValue("filename") + ".cfg\n"); + changes.Append("Deleted : " + fileNode.GetValue("filename") + "\n"); noChange = false; } if (!noChange) @@ -429,19 +430,24 @@ private void CreateCache(IEnumerable databaseConfigs, int patch foreach (IProtoUrlConfig urlConfig in databaseConfigs) { ConfigNode node = cache.AddNode("UrlConfig"); - node.AddValue("parentUrl", urlConfig.UrlFile.url); - node.AddNode(urlConfig.Node); + node.AddValue("parentUrl", urlConfig.UrlFile.GetUrlWithExtension()); + + ConfigNode urlNode = urlConfig.Node.DeepCopy(); + urlNode.EscapeValuesRecursive(); + + node.AddNode(urlNode); } foreach (var file in GameDatabase.Instance.root.AllConfigFiles) { + string url = file.GetUrlWithExtension(); // "/Physics" is the node we created manually to loads the PHYSIC config - if (file.url != "/Physics" && filesSha.ContainsKey(file.url)) + if (file.url != "/Physics" && filesSha.ContainsKey(url)) { ConfigNode shaNode = filesSHANode.AddNode("FILE"); - shaNode.AddValue("filename", file.url); - shaNode.AddValue("SHA", filesSha[file.url]); - filesSha.Remove(file.url); + shaNode.AddValue("filename", url); + shaNode.AddValue("SHA", filesSha[url]); + filesSha.Remove(url); } } @@ -523,9 +529,10 @@ private IEnumerable LoadCache() { string parentUrl = node.GetValue("parentUrl"); - UrlDir.UrlFile parent = GameDatabase.Instance.root.AllConfigFiles.FirstOrDefault(f => f.url == parentUrl); + UrlDir.UrlFile parent = gameDataDir.Find(parentUrl); if (parent != null) { + node.nodes[0].UnescapeValuesRecursive(); databaseConfigs.Add(new ProtoUrlConfig(parent, node.nodes[0])); } else @@ -560,7 +567,7 @@ private void StatusUpdate(IPatchProgress progress, string activity = null) #region Applying Patches // Name is group 1, index is group 2, vector related filed is group 3, vector separator is group 4, operator is group 5 - private static Regex parseValue = new Regex(@"([\w\&\-\.\?\*+/^!\(\) ]+(?:,[^*\d][\w\&\-\.\?\*\(\) ]*)*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?"); + private static readonly Regex parseValue = new Regex(@"([\w\&\-\.\?\*\#+/^!\(\) ]+(?:,[^*\d][\w\&\-\.\?\*\(\) ]*)*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?"); // ModifyNode applies the ConfigNode mod as a 'patch' to ConfigNode original, then returns the patched ConfigNode. // it uses FindConfigNodeIn(src, nodeType, nodeName, nodeTag) to recurse. @@ -720,7 +727,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon if (varValue != null) { newNode.RemoveValues(valName); - newNode.AddValue(valName, varValue); + newNode.AddValueSafe(valName, varValue); } else { @@ -766,7 +773,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon if (cmd != Command.Copy) origVal.value = value; else - newNode.AddValue(valName, value); + newNode.AddValueSafe(valName, value); } } else @@ -842,7 +849,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon if (varValue != null) { if (!newNode.HasValue(valName)) - newNode.AddValue(valName, varValue); + newNode.AddValueSafe(valName, varValue); } else { @@ -916,7 +923,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon ConfigNode newSubMod = new ConfigNode(toPaste.name); newSubMod = ModifyNode(nodeStack.Push(newSubMod), toPaste, context); - if (subName.LastIndexOf(",") > 0 && int.TryParse(subName.Substring(subName.LastIndexOf(",") + 1), out int index)) + if (subName.LastIndexOf(',') > 0 && int.TryParse(subName.Substring(subName.LastIndexOf(',') + 1), out int index)) { // In this case insert the node at position index InsertNode(newNode, newSubMod, index); @@ -939,11 +946,10 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon // NODE,n will match the nth node (NODE is the same as NODE,0) // NODE,* will match ALL nodes // NODE:HAS[condition] will match ALL nodes with condition - if (subName.Contains(":HAS[")) + if (subName.Contains(":HAS[", out int hasStart)) { - int start = subName.IndexOf(":HAS["); - constraints = subName.Substring(start + 5, subName.LastIndexOf(']') - start - 5); - subName = subName.Substring(0, start); + constraints = subName.Substring(hasStart + 5, subName.LastIndexOf(']') - hasStart - 5); + subName = subName.Substring(0, hasStart); } if (subName.Contains(",")) @@ -1005,8 +1011,8 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon msg += " Applying subnode " + subMod.name + "\n"; #endif ConfigNode newSubNode = ModifyNode(nodeStack.Push(subNodes[0]), subMod, context); - subNodes[0].ClearData(); - newSubNode.CopyTo(subNodes[0], newSubNode.name); + subNodes[0].ShallowCopyFrom(newSubNode); + subNodes[0].name = newSubNode.name; } else { @@ -1018,7 +1024,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon ConfigNode copy = new ConfigNode(nodeType); if (nodeName != null) - copy.AddValue("name", nodeName); + copy.AddValueSafe("name", nodeName); ConfigNode newSubNode = ModifyNode(nodeStack.Push(copy), subMod, context); newNode.nodes.Add(newSubNode); @@ -1035,7 +1041,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon ConfigNode copy = new ConfigNode(nodeType); if (nodeName != null) - copy.AddValue("name", nodeName); + copy.AddValueSafe("name", nodeName); ConfigNode newSubNode = ModifyNode(nodeStack.Push(copy), subMod, context); newNode.nodes.Add(newSubNode); @@ -1061,8 +1067,8 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon // Edit in place newSubNode = ModifyNode(nodeStack.Push(subNode), subMod, context); - subNode.ClearData(); - newSubNode.CopyTo(subNode, newSubNode.name); + subNode.ShallowCopyFrom(newSubNode); + subNode.name = newSubNode.name; break; case Command.Delete: @@ -1111,11 +1117,10 @@ private static ConfigNode RecurseNodeSearch(string path, NodeStack nodeStack, Pa string constraint = ""; int index = 0; - if (subName.Contains(":HAS[")) + if (subName.Contains(":HAS[", out int hasStart)) { - int start = subName.IndexOf(":HAS["); - constraint = subName.Substring(start + 5, subName.LastIndexOf(']') - start - 5); - subName = subName.Substring(0, start); + constraint = subName.Substring(hasStart + 5, subName.LastIndexOf(']') - hasStart - 5); + subName = subName.Substring(0, hasStart); } else if (subName.Contains(",")) { @@ -1284,13 +1289,12 @@ private static ConfigNode.Value RecurseVariableSearch(string path, NodeStack nod string constraint = ""; string nodeType, nodeName; int index = 0; - if (subName.Contains(":HAS[")) + if (subName.Contains(":HAS[", out int hasStart)) { - int start = subName.IndexOf(":HAS["); - constraint = subName.Substring(start + 5, subName.LastIndexOf(']') - start - 5); - subName = subName.Substring(0, start); + constraint = subName.Substring(hasStart + 5, subName.LastIndexOf(']') - hasStart - 5); + subName = subName.Substring(0, hasStart); } - else if (subName.Contains(",")) + else if (subName.Contains(',')) { string tag = subName.Split(',')[1]; subName = subName.Split(',')[0]; @@ -1391,7 +1395,7 @@ private static string ProcessVariableSearch(string value, NodeStack nodeStack, P StringBuilder builder = new StringBuilder(); builder.Append(split[0].Substring(1)); - for (int i = 1; i < split.Length - 1; i = i + 2) + for (int i = 1; i < split.Length - 1; i += 2) { ConfigNode.Value result = RecurseVariableSearch(split[i], nodeStack, context); if (result == null || result.value == null) @@ -1447,14 +1451,10 @@ private static string FindAndReplaceValue( { string[] split = value.Split(value[0]); - Regex replace; - if (regexCache.ContainsKey(split[1])) - replace = regexCache[split[1]]; - else + Regex replace = regexCache.Fetch(split[1], delegate { - replace = new Regex(split[1], RegexOptions.None); - regexCache.Add(split[1], replace); - } + return new Regex(split[1]); + }); value = replace.Replace(oValue, split[2]); } @@ -1549,11 +1549,11 @@ public static bool CheckConstraints(ConfigNode node, string constraints) constraints = constraintList[0]; string remainingConstraints = ""; - if (constraints.Contains("HAS[")) + if (constraints.Contains(":HAS[", out int hasStart)) { - int start = constraints.IndexOf("HAS[") + 4; - remainingConstraints = constraints.Substring(start, constraintList[0].LastIndexOf(']') - start); - constraints = constraints.Substring(0, start - 5); + hasStart += 5; + remainingConstraints = constraints.Substring(hasStart, constraintList[0].LastIndexOf(']') - hasStart); + constraints = constraints.Substring(0, hasStart - 5); } string[] splits = constraints.Split(contraintSeparators, 3); @@ -1659,11 +1659,10 @@ public static bool WildcardMatch(string s, string wildcard) return true; string pattern = "^" + Regex.Escape(wildcard).Replace(@"\*", ".*").Replace(@"\?", ".") + "$"; - if (!regexCache.TryGetValue(pattern, out Regex regex)) + Regex regex = regexCache.Fetch(pattern, delegate { - regex = new Regex(pattern); - regexCache.Add(pattern, regex); - } + return new Regex(pattern); + }); return regex.IsMatch(s); } @@ -1698,13 +1697,13 @@ private static void InsertValue(ConfigNode newNode, int index, string name, stri newNode.RemoveValues(name); int i = 0; for (; i < index; ++i) - newNode.AddValue(name, oldValues[i]); - newNode.AddValue(name, value); + newNode.AddValueSafe(name, oldValues[i]); + newNode.AddValueSafe(name, value); for (; i < oldValues.Length; ++i) - newNode.AddValue(name, oldValues[i]); + newNode.AddValueSafe(name, oldValues[i]); return; } - newNode.AddValue(name, value); + newNode.AddValueSafe(name, value); } //FindConfigNodeIn finds and returns a ConfigNode in src of type nodeType. @@ -1716,19 +1715,26 @@ public static ConfigNode FindConfigNodeIn( string nodeName = null, int index = 0) { - ConfigNode[] nodes = src.GetNodes(nodeType); - if (nodes.Length == 0) + List nodes = new List(); + int c = src.nodes.Count; + for(int i = 0; i < c; ++i) + { + if (WildcardMatch(src.nodes[i].name, nodeType)) + nodes.Add(src.nodes[i]); + } + int nodeCount = nodes.Count; + if (nodeCount == 0) return null; if (nodeName == null) { if (index >= 0) - return nodes[Math.Min(index, nodes.Length - 1)]; - return nodes[Math.Max(0, nodes.Length + index)]; + return nodes[Math.Min(index, nodeCount - 1)]; + return nodes[Math.Max(0, nodeCount + index)]; } ConfigNode last = null; if (index >= 0) { - for (int i = 0; i < nodes.Length; ++i) + for (int i = 0; i < nodeCount; ++i) { if (nodes[i].HasValue("name") && WildcardMatch(nodes[i].GetValue("name"), nodeName)) { @@ -1739,7 +1745,7 @@ public static ConfigNode FindConfigNodeIn( } return last; } - for (int i = nodes.Length - 1; i >= 0; --i) + for (int i = nodeCount - 1; i >= 0; --i) { if (nodes[i].HasValue("name") && WildcardMatch(nodes[i].GetValue("name"), nodeName)) { diff --git a/ModuleManager/MMPatchRunner.cs b/ModuleManager/MMPatchRunner.cs index 802b76fb..6c3d63a0 100644 --- a/ModuleManager/MMPatchRunner.cs +++ b/ModuleManager/MMPatchRunner.cs @@ -2,7 +2,6 @@ using System.Collections; using System.Collections.Generic; using System.IO; -using UnityEngine; using ModuleManager.Collections; using ModuleManager.Extensions; using ModuleManager.Logging; @@ -14,8 +13,6 @@ namespace ModuleManager { public class MMPatchRunner { - private const float TIME_TO_WAIT_FOR_LOGS = 0.05f; - private readonly IBasicLogger kspLogger; public string Status { get; private set; } = ""; @@ -38,11 +35,10 @@ public IEnumerator Run() QueueLogRunner logRunner = new QueueLogRunner(mmLogQueue); ITaskStatus loggingThreadStatus = BackgroundTask.Start(delegate { - using (StreamLogger streamLogger = new StreamLogger(new FileStream(logPath, FileMode.Create))) - { - logRunner.Run(streamLogger); - streamLogger.Info("Done!"); - } + using StreamLogger streamLogger = new StreamLogger(new FileStream(logPath, FileMode.Create)); + streamLogger.Info("Log started at " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")); + logRunner.Run(streamLogger); + streamLogger.Info("Done!"); }); // Wait for game database to be initialized for the 2nd time and wait for any plugins to initialize @@ -79,12 +75,14 @@ public IEnumerator Run() { kspLogger.Exception("The patching thread threw an exception", patchingThreadStatus.Exception); FatalErrorHandler.HandleFatalError("The patching thread threw an exception"); + yield break; } if (loggingThreadStatus.IsExitedWithError) { kspLogger.Exception("The logging thread threw an exception", loggingThreadStatus.Exception); FatalErrorHandler.HandleFatalError("The logging thread threw an exception"); + yield break; } if (databaseConfigs == null) diff --git a/ModuleManager/ModListGenerator.cs b/ModuleManager/ModListGenerator.cs index 97292ba0..5d017232 100644 --- a/ModuleManager/ModListGenerator.cs +++ b/ModuleManager/ModListGenerator.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 4d7e76de..dd5e04e5 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; @@ -9,7 +10,9 @@ using UnityEngine; using Debug = UnityEngine.Debug; using ModuleManager.Cats; +using ModuleManager.Extensions; using ModuleManager.Logging; +using ModuleManager.UnityLogHandle; namespace ModuleManager { @@ -21,12 +24,8 @@ public class ModuleManager : MonoBehaviour private bool inRnDCenter; public bool showUI = false; - - private Rect windowPos = new Rect(80f, 60f, 240f, 40f); private float textPos = 0; - private string version = ""; - //private Texture2D tex; //private Texture2D tex2; @@ -58,7 +57,7 @@ public static void Log(String s) print("[ModuleManager] " + s); } - private Stopwatch totalTime = new Stopwatch(); + private readonly Stopwatch totalTime = new Stopwatch(); internal void Awake() { @@ -80,7 +79,9 @@ internal void Awake() totalTime.Start(); - // Allow loading the background in the laoding screen + Debug.unityLogger.logHandler = new InterceptLogHandler(Debug.unityLogger.logHandler); + + // Allow loading the background in the loading screen Application.runInBackground = true; QualitySettings.vSyncCount = 0; Application.targetFrameRate = -1; @@ -101,9 +102,6 @@ internal void Awake() } DontDestroyOnLoad(gameObject); - Version v = Assembly.GetExecutingAssembly().GetName().Version; - version = v.Major + "." + v.Minor + "." + v.Build; - // Subscribe to the RnD center spawn/deSpawn events GameEvents.onGUIRnDComplexSpawn.Add(OnRnDCenterSpawn); GameEvents.onGUIRnDComplexDespawn.Add(OnRnDCenterDeSpawn); @@ -132,7 +130,7 @@ internal void Awake() Log(string.Format("Adding post patch to the loading screen {0}", list.Count)); list.Insert(gameDatabaseIndex + 1, aGameObject.AddComponent()); - patchRunner = new MMPatchRunner(new ModLogger("ModuleManager", new UnityLogger(Debug.unityLogger))); + patchRunner = new MMPatchRunner(new PrefixLogger("ModuleManager", new UnityLogger(Debug.unityLogger))); StartCoroutine(patchRunner.Run()); // Workaround for 1.6.0 Editor bug after a PartDatabase rebuild. @@ -162,7 +160,7 @@ internal void Awake() private TextMeshProUGUI errors; private TextMeshProUGUI warning; - + [SuppressMessage("Code Quality", "IDE0051", Justification = "Called by Unity")] private void Start() { if (nCats) @@ -183,6 +181,18 @@ private void Start() // //if (GUI.Button(new Rect(Screen.width / 2f - 100, offsetY, 200, 20), "Click to open the Forum thread")) // // Application.OpenURL("http://forum.kerbalspaceprogram.com/index.php?/topic/124998-silent-patch-for-ksp-105-published/"); //} + + if (Versioning.version_major == 1 && Versioning.version_minor >= 8) + { + foreach (AssemblyLoader.LoadedAssembly assembly in AssemblyLoader.loadedAssemblies) + { + AssemblyName assemblyName = assembly.assembly.GetName(); + if (assemblyName.Name == "Firespitter" && assemblyName.Version <= Version.Parse("7.3.7175.38653")) + { + warning.text = "You are using a version of Firespitter that does not run properly on KSP 1.8+\nThis version may prevent the game from loading properly and may create problems for other mods"; + } + } + } } private TextMeshProUGUI CreateTextObject(Canvas canvas, string name) @@ -276,8 +286,9 @@ internal void Update() { if (warning) { + warning.text = InterceptLogHandler.Warnings; h = warning.text.Length > 0 ? warning.textBounds.size.y : 0; - offsetY = offsetY + h; + offsetY += h; warning.rectTransform.localPosition = new Vector3(0, offsetY); } @@ -285,7 +296,7 @@ internal void Update() { status.text = patchRunner.Status; h = status.text.Length > 0 ? status.textBounds.size.y : 0; - offsetY = offsetY + h; + offsetY += h; status.transform.localPosition = new Vector3(0, offsetY); } @@ -293,7 +304,7 @@ internal void Update() { errors.text = patchRunner.Errors; h = errors.text.Length > 0 ? errors.textBounds.size.y : 0; - offsetY = offsetY + h; + offsetY += h; errors.transform.localPosition = new Vector3(0, offsetY); } } @@ -314,7 +325,7 @@ private IEnumerator DataBaseReloadWithMM(bool dump = false) QualitySettings.vSyncCount = 0; Application.targetFrameRate = -1; - patchRunner = new MMPatchRunner(new ModLogger("ModuleManager", new UnityLogger(Debug.unityLogger))); + patchRunner = new MMPatchRunner(new PrefixLogger("ModuleManager", new UnityLogger(Debug.unityLogger))); float totalLoadWeight = GameDatabase.Instance.LoadWeight() + PartLoader.Instance.LoadWeight(); bool startedReload = false; @@ -343,7 +354,7 @@ private IEnumerator DataBaseReloadWithMM(bool dump = false) { progressFraction = 0f; } - else if (!GameDatabase.Instance.IsReady()) + else if (!GameDatabase.Instance.IsReady() || !PostPatchLoader.Instance.IsReady()) { progressFraction = GameDatabase.Instance.ProgressFraction() * GameDatabase.Instance.LoadWeight(); progressFraction /= totalLoadWeight; @@ -429,7 +440,7 @@ private IEnumerator DataBaseReloadWithMM(bool dump = false) public static void OutputAllConfigs() { - string path = KSPUtil.ApplicationRootPath + "/_MMCfgOutput/"; + string path = Path.GetFullPath(Path.Combine(KSPUtil.ApplicationRootPath, "_MMCfgOutput")); try { Directory.CreateDirectory(path); @@ -450,51 +461,50 @@ public static void OutputAllConfigs() { Log("Exception while cleaning the export dir\n" + unauthorizedAccessException); } - Stack dirs = new Stack(); - dirs.Push(GameDatabase.Instance.root); - Stack paths = new Stack(); - paths.Push(""); - try + static void WriteDirectoryRecursive(UrlDir currentDir, string dirPath) { - while (dirs.Count > 0) + if (currentDir.files.Count > 0) Directory.CreateDirectory(dirPath); + + foreach (UrlDir.UrlFile urlFile in currentDir.files) { - var currentDir = dirs.Pop(); - string currentPath = paths.Pop(); - - foreach (UrlDir.UrlFile urlFile in currentDir.files) + if (urlFile.fileType != UrlDir.FileType.Config) continue; + + Log("Exporting " + urlFile.GetUrlWithExtension()); + string filePath = Path.Combine(dirPath, urlFile.GetNameWithExtension()); + + bool first = true; + + using FileStream stream = new FileStream(filePath, FileMode.Create); + using StreamWriter writer = new StreamWriter(stream); + foreach (UrlDir.UrlConfig urlConfig in urlFile.configs) { - if (urlFile.fileType == UrlDir.FileType.Config) + try { - string dirPath = path + currentPath; - if (!Directory.Exists(dirPath)) - { - Directory.CreateDirectory(dirPath); - } + if (first) first = false; + else writer.Write("\n"); - Log("Exporting " + currentPath + urlFile.name + "." + urlFile.fileExtension); - string filePath = dirPath + urlFile.name + "." + urlFile.fileExtension; - foreach (UrlDir.UrlConfig urlConfig in urlFile.configs) - { - try - { - File.AppendAllText(filePath, urlConfig.config.ToString()); - } - catch (Exception e) - { - Log("Exception while trying to write the file " + filePath + "\n" + e); - } - } + ConfigNode copy = urlConfig.config.DeepCopy(); + copy.EscapeValuesRecursive(); + writer.Write(copy.ToString()); + } + catch (Exception e) + { + Log("Exception while trying to write the file " + filePath + "\n" + e); } } + } - foreach (UrlDir urlDir in currentDir.children) - { - dirs.Push(urlDir); - paths.Push(currentPath + urlDir.name + "/"); - } + foreach (UrlDir urlDir in currentDir.children) + { + WriteDirectoryRecursive(urlDir, Path.Combine(dirPath, urlDir.name)); } } + + try + { + WriteDirectoryRecursive(GameDatabase.Instance.root, path); + } catch (DirectoryNotFoundException directoryNotFoundException) { Log("Exception while exporting the cfg\n" + directoryNotFoundException); diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 92ce283a..20fe0da1 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -1,5 +1,5 @@  - + Debug AnyCPU @@ -9,7 +9,8 @@ Library ModuleManager ModuleManager - v3.5 + v4.7.1 + True @@ -21,6 +22,7 @@ 4 False default + false none @@ -29,39 +31,46 @@ prompt 4 False + false + + + 8.0 - + + + + + - - + + + - - + - + - @@ -71,7 +80,11 @@ + + + + @@ -81,10 +94,6 @@ - - - - @@ -102,6 +111,7 @@ + @@ -112,15 +122,43 @@ False False + + False + False + - + + False False + + False + False + + + False + False + + + False + False + + + False + False + + + False + False + False - C:\Games\KSPSteamController\KSPSteamCtrlr\KSPUnity-Steam-Symlinks\UnityEngine.UI.dll + False + + + False False @@ -169,6 +207,7 @@ + sh -c "TARGET_PATH='$(TargetPath)' TARGET_DIR='$(TargetDir)' TARGET_NAME='$(TargetName)' sh '$(ProjectDir)/copy_build.sh'" diff --git a/ModuleManager/NeedsChecker.cs b/ModuleManager/NeedsChecker.cs index 7e452642..94f2ad29 100644 --- a/ModuleManager/NeedsChecker.cs +++ b/ModuleManager/NeedsChecker.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using ModuleManager.Extensions; using ModuleManager.Logging; @@ -20,6 +21,7 @@ public class NeedsChecker : INeedsChecker private readonly IEnumerable mods; private readonly UrlDir gameData; private readonly IPatchProgress progress; + [SuppressMessage("CodeQuality", "IDE0052", Justification = "Reserved for future use")] private readonly IBasicLogger logger; public NeedsChecker(IEnumerable mods, UrlDir gameData, IPatchProgress progress, IBasicLogger logger) diff --git a/ModuleManager/NodeMatcher.cs b/ModuleManager/NodeMatcher.cs index 5c3a59ca..8777de97 100644 --- a/ModuleManager/NodeMatcher.cs +++ b/ModuleManager/NodeMatcher.cs @@ -10,11 +10,9 @@ public interface INodeMatcher public class NodeMatcher : INodeMatcher { - private static readonly char[] sep = { '[', ']' }; - - private string type; - private string[] namePatterns = null; - private string constraints = ""; + private readonly string type; + private readonly string[] namePatterns = null; + private readonly string constraints = ""; public NodeMatcher(string type, string name, string constraints) { diff --git a/ModuleManager/PatchExtractor.cs b/ModuleManager/PatchExtractor.cs index cf476a64..6c8765f4 100644 --- a/ModuleManager/PatchExtractor.cs +++ b/ModuleManager/PatchExtractor.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using ModuleManager.Extensions; using ModuleManager.Logging; using ModuleManager.Patches; @@ -10,6 +11,7 @@ namespace ModuleManager public class PatchExtractor { private readonly IPatchProgress progress; + [SuppressMessage("CodeQuality", "IDE0052", Justification = "Reserved for future use")] private readonly IBasicLogger logger; private readonly INeedsChecker needsChecker; private readonly ITagListParser tagListParser; diff --git a/ModuleManager/PatchList.cs b/ModuleManager/PatchList.cs index 9716de22..d3990540 100644 --- a/ModuleManager/PatchList.cs +++ b/ModuleManager/PatchList.cs @@ -1,8 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; -using ModuleManager.Collections; using ModuleManager.Patches; using ModuleManager.Patches.PassSpecifiers; using ModuleManager.Progress; @@ -37,52 +35,25 @@ public ModPass(string name) public void AddLastPatch(IPatch patch) => lastPass.Add(patch ?? throw new ArgumentNullException(nameof(patch))); } - private class ModPassCollection : IEnumerable - { - private readonly ModPass[] passesArray; - private readonly Dictionary passesDict; - - public ModPassCollection(IEnumerable modList) - { - int count = modList.Count(); - passesArray = new ModPass[count]; - passesDict = new Dictionary(count); - - int i = 0; - foreach (string mod in modList) - { - ModPass pass = new ModPass(mod); - passesArray[i] = pass; - passesDict.Add(mod.ToLowerInvariant(), pass); - i++; - } - } - - public ModPass this[string name] => passesDict[name.ToLowerInvariant()]; - public ModPass this[int index] => passesArray[index]; - - public bool HasMod(string name) => passesDict.ContainsKey(name.ToLowerInvariant()); - - public int Count => passesArray.Length; - - public ArrayEnumerator GetEnumerator() => new ArrayEnumerator(passesArray); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - private readonly Pass insertPatches = new Pass(":INSERT (initial)"); private readonly Pass firstPatches = new Pass(":FIRST"); private readonly Pass legacyPatches = new Pass(":LEGACY (default)"); private readonly Pass finalPatches = new Pass(":FINAL"); - private readonly ModPassCollection modPasses; + private readonly SortedDictionary modPasses = new SortedDictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly SortedDictionary lastPasses = new SortedDictionary(StringComparer.InvariantCultureIgnoreCase); public PatchList(IEnumerable modList, IEnumerable patches, IPatchProgress progress) { - modPasses = new ModPassCollection(modList ?? throw new ArgumentNullException(nameof(modList))); + if (modList == null) throw new ArgumentNullException(nameof(modList)); if (patches == null) throw new ArgumentNullException(nameof(patches)); if (progress == null) throw new ArgumentNullException(nameof(progress)); + foreach (string mod in modList) + { + modPasses.Add(mod, new ModPass(mod)); + } + foreach (IPatch patch in patches) { if (patch.PassSpecifier is InsertPassSpecifier) @@ -114,8 +85,12 @@ public PatchList(IEnumerable modList, IEnumerable patches, IPatc } else if (patch.PassSpecifier is LastPassSpecifier lastPassSpecifier) { - EnsureMod(lastPassSpecifier.mod); - modPasses[lastPassSpecifier.mod].AddLastPatch(patch); + if (!lastPasses.TryGetValue(lastPassSpecifier.mod, out Pass thisPass)) + { + thisPass = new Pass($":LAST[{lastPassSpecifier.mod.ToUpperInvariant()}]"); + lastPasses.Add(lastPassSpecifier.mod.ToLowerInvariant(), thisPass); + } + thisPass.Add(patch); } else if (patch.PassSpecifier is FinalPassSpecifier) { @@ -136,16 +111,16 @@ public IEnumerator GetEnumerator() yield return firstPatches; yield return legacyPatches; - foreach (ModPass modPass in modPasses) + foreach (ModPass modPass in modPasses.Values) { yield return modPass.beforePass; yield return modPass.forPass; yield return modPass.afterPass; } - foreach (ModPass modPass in modPasses) + foreach (Pass lastPass in lastPasses.Values) { - yield return modPass.lastPass; + yield return lastPass; } yield return finalPatches; @@ -157,7 +132,7 @@ private void EnsureMod(string mod) { if (mod == null) throw new ArgumentNullException(nameof(mod)); if (mod == string.Empty) throw new ArgumentException("can't be empty", nameof(mod)); - if (!modPasses.HasMod(mod)) throw new KeyNotFoundException($"Mod '{mod}' not found"); + if (!modPasses.ContainsKey(mod)) throw new KeyNotFoundException($"Mod '{mod}' not found"); } } } diff --git a/ModuleManager/Patches/PatchCompiler.cs b/ModuleManager/Patches/PatchCompiler.cs index 67387372..6c7e7f30 100644 --- a/ModuleManager/Patches/PatchCompiler.cs +++ b/ModuleManager/Patches/PatchCompiler.cs @@ -13,23 +13,14 @@ public IPatch CompilePatch(ProtoPatch protoPatch) { if (protoPatch == null) throw new ArgumentNullException(nameof(protoPatch)); - switch (protoPatch.command) + return protoPatch.command switch { - case Command.Insert: - return new InsertPatch(protoPatch.urlConfig, protoPatch.nodeType, protoPatch.passSpecifier); - - case Command.Edit: - return new EditPatch(protoPatch.urlConfig, new NodeMatcher(protoPatch.nodeType, protoPatch.nodeName, protoPatch.has), protoPatch.passSpecifier); - - case Command.Copy: - return new CopyPatch(protoPatch.urlConfig, new NodeMatcher(protoPatch.nodeType, protoPatch.nodeName, protoPatch.has), protoPatch.passSpecifier); - - case Command.Delete: - return new DeletePatch(protoPatch.urlConfig, new NodeMatcher(protoPatch.nodeType, protoPatch.nodeName, protoPatch.has), protoPatch.passSpecifier); - - default: - throw new ArgumentException("has an invalid command for a root node: " + protoPatch.command, nameof(protoPatch)); - } + Command.Insert => new InsertPatch(protoPatch.urlConfig, protoPatch.nodeType, protoPatch.passSpecifier), + Command.Edit => new EditPatch(protoPatch.urlConfig, new NodeMatcher(protoPatch.nodeType, protoPatch.nodeName, protoPatch.has), protoPatch.passSpecifier), + Command.Copy => new CopyPatch(protoPatch.urlConfig, new NodeMatcher(protoPatch.nodeType, protoPatch.nodeName, protoPatch.has), protoPatch.passSpecifier), + Command.Delete => new DeletePatch(protoPatch.urlConfig, new NodeMatcher(protoPatch.nodeType, protoPatch.nodeName, protoPatch.has), protoPatch.passSpecifier), + _ => throw new ArgumentException("has an invalid command for a root node: " + protoPatch.command, nameof(protoPatch)), + }; } } } diff --git a/ModuleManager/PostPatchLoader.cs b/ModuleManager/PostPatchLoader.cs index 758ab0ae..1bccce78 100644 --- a/ModuleManager/PostPatchLoader.cs +++ b/ModuleManager/PostPatchLoader.cs @@ -9,6 +9,7 @@ using ModuleManager.Logging; using static ModuleManager.FilePathRepository; +using System.Diagnostics.CodeAnalysis; namespace ModuleManager { @@ -22,7 +23,7 @@ public class PostPatchLoader : LoadingSystem private static readonly List postPatchCallbacks = new List(); - private readonly IBasicLogger logger = new ModLogger("ModuleManager", new UnityLogger(UnityEngine.Debug.unityLogger)); + private readonly IBasicLogger logger = new PrefixLogger("ModuleManager", new UnityLogger(UnityEngine.Debug.unityLogger)); private bool ready = false; @@ -34,6 +35,7 @@ public static void AddPostPatchCallback(ModuleManagerPostPatchCallback callback) postPatchCallbacks.Add(callback); } + [SuppressMessage("CodeQuality", "IDE0051", Justification = "Called by Unity")] private void Awake() { if (Instance != null) @@ -128,6 +130,8 @@ private IEnumerator Run() logger.Info("Reloading Part Upgrades"); PartUpgradeManager.Handler.FillUpgrades(); + LoadModdedPhysics(); + yield return null; progressTitle = "ModuleManager: Running post patch callbacks"; @@ -207,5 +211,21 @@ private IEnumerator Run() ready = true; } + + private void LoadModdedPhysics() + { + if (!File.Exists(physicsPath)) + { + logger.Error("Physics file not found"); + return; + } + + logger.Info("Setting modded physics as the active one"); + + PhysicsGlobals.PhysicsDatabaseFilename = physicsPath; + + if (!PhysicsGlobals.Instance.LoadDatabase()) + logger.Error("Something went wrong while setting the active physics config."); + } } } diff --git a/ModuleManager/Progress/PatchProgress.cs b/ModuleManager/Progress/PatchProgress.cs index b1d92721..61fd0d33 100644 --- a/ModuleManager/Progress/PatchProgress.cs +++ b/ModuleManager/Progress/PatchProgress.cs @@ -1,7 +1,6 @@ using System; using ModuleManager.Extensions; using ModuleManager.Logging; -using NodeStack = ModuleManager.Collections.ImmutableStack; namespace ModuleManager.Progress { @@ -9,7 +8,7 @@ public class PatchProgress : IPatchProgress { public ProgressCounter Counter { get; private set; } - private IBasicLogger logger; + private readonly IBasicLogger logger; public float ProgressFraction { @@ -140,7 +139,7 @@ public void Exception(UrlDir.UrlConfig url, string message, Exception exception) private void RecordWarningFile(UrlDir.UrlConfig url) { - string key = url.parent.url + "." + url.parent.fileExtension; + string key = url.parent.GetUrlWithExtension(); if (key[0] == '/') key = key.Substring(1); @@ -152,7 +151,7 @@ private void RecordWarningFile(UrlDir.UrlConfig url) private void RecordErrorFile(UrlDir.UrlConfig url) { - string key = url.parent.url + "." + url.parent.fileExtension; + string key = url.parent.GetUrlWithExtension(); if (key[0] == '/') key = key.Substring(1); diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index 43b2dc37..12a660e1 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("4.0.2")] +[assembly: AssemblyVersion("4.2.3")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, diff --git a/ModuleManager/ProtoUrlConfig.cs b/ModuleManager/ProtoUrlConfig.cs index 74533948..71f39df3 100644 --- a/ModuleManager/ProtoUrlConfig.cs +++ b/ModuleManager/ProtoUrlConfig.cs @@ -29,6 +29,9 @@ public ProtoUrlConfig(UrlDir.UrlFile urlFile, ConfigNode node) Node = node ?? throw new ArgumentNullException(nameof(node)); FileUrl = UrlFile.url + '.' + urlFile.fileExtension; FullUrl = FileUrl + '/' + Node.name; + + if (node.GetValue("name") is string nameValue) + FullUrl += '[' + nameValue + ']'; } } } diff --git a/ModuleManager/Threading/TaskStatus.cs b/ModuleManager/Threading/TaskStatus.cs index 92f8c237..1d2c6b67 100644 --- a/ModuleManager/Threading/TaskStatus.cs +++ b/ModuleManager/Threading/TaskStatus.cs @@ -4,12 +4,10 @@ namespace ModuleManager.Threading { public class TaskStatus : ITaskStatus { - private bool isRunning = true; - private Exception exception = null; - private object lockObject = new object(); + private readonly object lockObject = new object(); - public bool IsRunning => isRunning; - public Exception Exception => exception; + public bool IsRunning { get; private set; } = true; + public Exception Exception { get; private set; } = null; public bool IsFinished { @@ -17,7 +15,7 @@ public bool IsFinished { lock (lockObject) { - return !isRunning && exception == null; + return !IsRunning && Exception == null; } } } @@ -28,7 +26,7 @@ public bool IsExitedWithError { lock (lockObject) { - return !isRunning && exception != null; + return !IsRunning && Exception != null; } } } @@ -37,8 +35,8 @@ public void Finished() { lock (lockObject) { - if (!isRunning) throw new InvalidOperationException("Task is not running"); - isRunning = false; + if (!IsRunning) throw new InvalidOperationException("Task is not running"); + IsRunning = false; } } @@ -46,9 +44,9 @@ public void Error(Exception exception) { lock(lockObject) { - if (!isRunning) throw new InvalidOperationException("Task is not running"); - this.exception = exception ?? throw new ArgumentNullException(nameof(exception)); - isRunning = false; + if (!IsRunning) throw new InvalidOperationException("Task is not running"); + this.Exception = exception ?? throw new ArgumentNullException(nameof(exception)); + IsRunning = false; } } } diff --git a/ModuleManager/Threading/TaskStatusWrapper.cs b/ModuleManager/Threading/TaskStatusWrapper.cs index eff24f78..1750633c 100644 --- a/ModuleManager/Threading/TaskStatusWrapper.cs +++ b/ModuleManager/Threading/TaskStatusWrapper.cs @@ -4,7 +4,7 @@ namespace ModuleManager.Threading { public class TaskStatusWrapper : ITaskStatus { - private ITaskStatus inner; + private readonly ITaskStatus inner; public TaskStatusWrapper(ITaskStatus inner) { diff --git a/ModuleManager/Utils/FileUtils.cs b/ModuleManager/Utils/FileUtils.cs index 73cd8dba..fcf04482 100644 --- a/ModuleManager/Utils/FileUtils.cs +++ b/ModuleManager/Utils/FileUtils.cs @@ -1,5 +1,7 @@ using System; using System.IO; +using System.Security.Cryptography; +using ModuleManager.Extensions; namespace ModuleManager.Utils { @@ -9,22 +11,11 @@ public static string FileSHA(string filename) { if (!File.Exists(filename)) throw new FileNotFoundException("File does not exist", filename); - System.Security.Cryptography.SHA256 sha = System.Security.Cryptography.SHA256.Create(); + using SHA256 sha = SHA256.Create(); + using FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + byte[] data = sha.ComputeHash(fs); - byte[] data = null; - using (FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read)) - { - data = sha.ComputeHash(fs); - } - - string hashedValue = string.Empty; - - foreach (byte b in data) - { - hashedValue += String.Format("{0,2:x2}", b); - } - - return hashedValue; + return data.ToHex(); } } } diff --git a/ModuleManagerTests/Collections/KeyValueCacheTest.cs b/ModuleManagerTests/Collections/KeyValueCacheTest.cs new file mode 100644 index 00000000..4fc5a9e6 --- /dev/null +++ b/ModuleManagerTests/Collections/KeyValueCacheTest.cs @@ -0,0 +1,53 @@ +using System; +using Xunit; +using ModuleManager.Collections; + +namespace ModuleManagerTests.Collections +{ + public class KeyValueCacheTest + { + [Fact] + public void TestFetch__CreateValueNull() + { + KeyValueCache cache = new KeyValueCache(); + ArgumentNullException ex = Assert.Throws(delegate + { + cache.Fetch(new object(), null); + }); + + Assert.Equal("createValue", ex.ParamName); + } + + [Fact] + public void TestFetch__KeyNotPresent() + { + object key = new object(); + object value = new object(); + KeyValueCache cache = new KeyValueCache(); + + object fetchedValue = cache.Fetch(key, () => value); + + Assert.Same(value, fetchedValue); + } + + [Fact] + public void TestFetch__KeyPresent() + { + object key = new object(); + object value = new object(); + KeyValueCache cache = new KeyValueCache(); + + cache.Fetch(key, () => value); + + bool called2ndTime = false; + object fetchedValue = cache.Fetch(key, delegate + { + called2ndTime = true; + return null; + }); + + Assert.Same(value, fetchedValue); + Assert.False(called2ndTime); + } + } +} diff --git a/ModuleManagerTests/Collections/MessageQueueTest.cs b/ModuleManagerTests/Collections/MessageQueueTest.cs index 83bf1217..baf35d20 100644 --- a/ModuleManagerTests/Collections/MessageQueueTest.cs +++ b/ModuleManagerTests/Collections/MessageQueueTest.cs @@ -8,7 +8,7 @@ public class MessageQueueTest { private class TestClass { } - private MessageQueue queue = new MessageQueue(); + private readonly MessageQueue queue = new MessageQueue(); [Fact] public void Test__Empty() diff --git a/ModuleManagerTests/Extensions/ByteArrayExtensionsTest.cs b/ModuleManagerTests/Extensions/ByteArrayExtensionsTest.cs new file mode 100644 index 00000000..ee53dcc8 --- /dev/null +++ b/ModuleManagerTests/Extensions/ByteArrayExtensionsTest.cs @@ -0,0 +1,28 @@ +using System; +using Xunit; +using ModuleManager.Extensions; + +namespace ModuleManagerTests.Extensions +{ + public class ByteArrayExtensionsTest + { + [Fact] + public void TestToHex() + { + byte[] data = { 0x00, 0xff, 0x01, 0xfe, 0x02, 0xfd, 0x9a }; + + Assert.Equal("00ff01fe02fd9a", data.ToHex()); + } + + [Fact] + public void TestToHex__NullData() + { + ArgumentNullException ex = Assert.Throws(delegate + { + ByteArrayExtensions.ToHex(null); + }); + + Assert.Equal("data", ex.ParamName); + } + } +} diff --git a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs index 7e4030ad..471be9cb 100644 --- a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs @@ -49,13 +49,11 @@ public void TestShallowCopyFrom() Assert.Same(value1, fromNode.values[0]); Assert.Same(value1, toNode.values[0]); - Assert.Equal("abc", value1.name); - Assert.Equal("def", value1.value); + AssertValue("abc", "def", value1); Assert.Same(value2, fromNode.values[1]); Assert.Same(value2, toNode.values[1]); - Assert.Equal("ghi", value2.name); - Assert.Equal("jkl", value2.value); + AssertValue("ghi", "jkl", value2); Assert.Equal(2, fromNode.nodes.Count); Assert.Equal(2, toNode.nodes.Count); @@ -64,23 +62,21 @@ public void TestShallowCopyFrom() Assert.Same(innerNode1, toNode.nodes[0]); Assert.Equal("INNER_NODE_1", innerNode1.name); Assert.Equal(1, innerNode1.values.Count); - Assert.Equal("mno", innerNode1.values[0].name); - Assert.Equal("pqr", innerNode1.values[0].value); + AssertValue("mno", "pqr", innerNode1.values[0]); Assert.Equal(1, innerNode1.nodes.Count); Assert.Equal("INNER_INNER_NODE_1", innerNode1.nodes[0].name); - Assert.Equal(0, innerNode1.nodes[0].values.Count); - Assert.Equal(0, innerNode1.nodes[0].nodes.Count); + Assert.Empty(innerNode1.nodes[0].values); + Assert.Empty(innerNode1.nodes[0].nodes); Assert.Same(innerNode2, fromNode.nodes[1]); Assert.Same(innerNode2, toNode.nodes[1]); Assert.Equal("INNER_NODE_2", innerNode2.name); Assert.Equal(1, innerNode2.values.Count); - Assert.Equal("stu", innerNode2.values[0].name); - Assert.Equal("vwx", innerNode2.values[0].value); + AssertValue("stu", "vwx", innerNode2.values[0]); Assert.Equal(1, innerNode2.nodes.Count); Assert.Equal("INNER_INNER_NODE_2", innerNode2.nodes[0].name); - Assert.Equal(0, innerNode2.nodes[0].values.Count); - Assert.Equal(0, innerNode2.nodes[0].nodes.Count); + Assert.Empty(innerNode2.nodes[0].values); + Assert.Empty(innerNode2.nodes[0].nodes); } [Fact] @@ -93,6 +89,7 @@ public void TestDeepCopy() new TestConfigNode("INNER_NODE_1") { { "mno", "pqr" }, + { "weird_values", "some\r\n\tstuff" }, new TestConfigNode("INNER_INNER_NODE_1"), }, new TestConfigNode("INNER_NODE_2") @@ -109,40 +106,38 @@ public void TestDeepCopy() Assert.Equal(2, toNode.values.Count); Assert.NotSame(fromNode.values[0], toNode.values[0]); - Assert.Equal("abc", toNode.values[0].name); - Assert.Equal("def", toNode.values[0].value); - + AssertValue("abc", "def", toNode.values[0]); + Assert.NotSame(fromNode.values[1], toNode.values[1]); - Assert.Equal("ghi", toNode.values[1].name); - Assert.Equal("jkl", toNode.values[1].value); - + AssertValue("ghi", "jkl", toNode.values[1]); + Assert.Equal(2, toNode.nodes.Count); ConfigNode innerNode1 = toNode.nodes[0]; Assert.NotSame(fromNode.nodes[0], innerNode1); Assert.Equal("INNER_NODE_1", innerNode1.name); - Assert.Equal(1, innerNode1.values.Count); + Assert.Equal(2, innerNode1.values.Count); Assert.NotSame(fromNode.nodes[0].values[0], innerNode1.values[0]); - Assert.Equal("mno", innerNode1.values[0].name); - Assert.Equal("pqr", innerNode1.values[0].value); + AssertValue("mno", "pqr", innerNode1.values[0]); + Assert.NotSame(fromNode.nodes[0].values[1], innerNode1.values[1]); + AssertValue("weird_values", "some\r\n\tstuff", innerNode1.values[1]); Assert.Equal(1, toNode.nodes[0].nodes.Count); Assert.NotSame(fromNode.nodes[0].nodes[0], innerNode1.nodes[0]); Assert.Equal("INNER_INNER_NODE_1", innerNode1.nodes[0].name); - Assert.Equal(0, innerNode1.nodes[0].values.Count); - Assert.Equal(0, innerNode1.nodes[0].nodes.Count); + Assert.Empty(innerNode1.nodes[0].values); + Assert.Empty(innerNode1.nodes[0].nodes); ConfigNode innerNode2 = toNode.nodes[1]; Assert.NotSame(fromNode.nodes[1], innerNode2); Assert.Equal("INNER_NODE_2", innerNode2.name); Assert.Equal(1, innerNode2.values.Count); Assert.NotSame(fromNode.nodes[1].values[0], innerNode2.values[0]); - Assert.Equal("stu", innerNode2.values[0].name); - Assert.Equal("vwx", innerNode2.values[0].value); + AssertValue("stu", "vwx", innerNode2.values[0]); Assert.Equal(1, innerNode2.nodes.Count); Assert.NotSame(fromNode.nodes[1].nodes[0], innerNode2.nodes[0]); Assert.Equal("INNER_INNER_NODE_2", innerNode2.nodes[0].name); - Assert.Equal(0, innerNode2.nodes[0].values.Count); - Assert.Equal(0, innerNode2.nodes[0].nodes.Count); + Assert.Empty(innerNode2.nodes[0].values); + Assert.Empty(innerNode2.nodes[0].nodes); } [Fact] @@ -273,5 +268,72 @@ XX INNER_NODE node.PrettyPrint(ref sb, "XX"); Assert.Equal(expected, sb.ToString()); } + + [Fact] + public void TestAddValueSafe() + { + ConfigNode node = new TestConfigNode + { + { "key1", "value1" }, + }; + + node.AddValueSafe("weird_values", "some\r\n\tstuff"); + + Assert.Equal(2, node.values.Count); + AssertValue("key1", "value1", node.values[0]); + AssertValue("weird_values", "some\r\n\tstuff", node.values[1]); + } + + [Fact] + public void TestEscapeValuesRecursive() + { + ConfigNode node = new TestConfigNode + { + { "key1", "value1" }, + { "key2", "value\nwith\rescped\tchars" }, + new TestConfigNode("SUBNODE") + { + { "key3", "value\nwith\rescped\tchars2" }, + }, + }; + + node.EscapeValuesRecursive(); + + Assert.Equal(2, node.values.Count); + AssertValue("key1", "value1", node.values[0]); + AssertValue("key2", "value\\nwith\\rescped\\tchars", node.values[1]); + Assert.Equal(1, node.nodes.Count); + Assert.Equal(1, node.nodes[0].values.Count); + AssertValue("key3", "value\\nwith\\rescped\\tchars2", node.nodes[0].values[0]); + } + + [Fact] + public void TestUnescapeValuesRecursive() + { + ConfigNode node = new TestConfigNode + { + { "key1", "value1" }, + { "key2", "value\\nwith\\rescped\\tchars" }, + new TestConfigNode("SUBNODE") + { + { "key3", "value\\nwith\\rescped\\tchars2" }, + }, + }; + + node.UnescapeValuesRecursive(); + + Assert.Equal(2, node.values.Count); + AssertValue("key1", "value1", node.values[0]); + AssertValue("key2", "value\nwith\rescped\tchars", node.values[1]); + Assert.Equal(1, node.nodes.Count); + Assert.Equal(1, node.nodes[0].values.Count); + AssertValue("key3", "value\nwith\rescped\tchars2", node.nodes[0].values[0]); + } + + private void AssertValue(string name, string value, ConfigNode.Value nodeValue) + { + Assert.Equal(name, nodeValue.name); + Assert.Equal(value, nodeValue.value); + } } } diff --git a/ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs b/ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs index 781cccb6..74e82d88 100644 --- a/ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs @@ -1,7 +1,6 @@ using System; using Xunit; using NSubstitute; -using UnityEngine; using ModuleManager.Logging; using ModuleManager.Extensions; @@ -9,32 +8,76 @@ namespace ModuleManagerTests.Extensions { public class IBasicLoggerExtensionsTest { - private IBasicLogger logger; - - public IBasicLoggerExtensionsTest() - { - logger = Substitute.For(); - } + private readonly IBasicLogger logger = Substitute.For(); [Fact] public void TestInfo() { logger.Info("well hi there"); - logger.Received().Log(LogType.Log, "well hi there"); + logger.AssertInfo("well hi there"); } [Fact] public void TestWarning() { logger.Warning("I'm warning you"); - logger.Received().Log(LogType.Warning, "I'm warning you"); + logger.AssertWarning("I'm warning you"); } [Fact] public void TestError() { logger.Error("You have made a grave mistake"); - logger.Received().Log(LogType.Error, "You have made a grave mistake"); + logger.AssertError("You have made a grave mistake"); + } + + [Fact] + public void TestException() + { + Exception ex = new Exception(); + logger.Exception(ex); + logger.AssertException(ex); + } + + [Fact] + public void TestException__Null() + { + ArgumentNullException ex = Assert.Throws(delegate + { + logger.Exception(null); + }); + + Assert.Equal("exception", ex.ParamName); + } + + [Fact] + public void TestException__Message() + { + Exception ex = new Exception(); + logger.Exception("a message", ex); + logger.AssertException("a message", ex); + } + + [Fact] + public void TestException__Message__MessageNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + logger.Exception(null, new Exception()); + }); + + Assert.Equal("message", ex.ParamName); + } + + [Fact] + public void TestException__Message__ExceptionNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + logger.Exception("a message", null); + }); + + Assert.Equal("exception", ex.ParamName); } } } diff --git a/ModuleManagerTests/Extensions/StringExtensionsTest.cs b/ModuleManagerTests/Extensions/StringExtensionsTest.cs index fdf3b33f..acf9274e 100644 --- a/ModuleManagerTests/Extensions/StringExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/StringExtensionsTest.cs @@ -39,5 +39,35 @@ public void TestRemoveWS() { Assert.Equal("abcdef", " abc \tdef\r\n\t ".RemoveWS()); } + + + [InlineData("abc", "b", true, 1)] + [InlineData("abc", "x", false, -1)] + [Theory] + public void TestContains(string str, string test, bool expectedResult, int expectedIndex) + { + bool result = str.Contains(test, out int index); + Assert.Equal(expectedResult, result); + Assert.Equal(expectedIndex, index); + } + + [Fact] + public void TestContains__NullStr() + { + string s = null; + Assert.Throws(delegate + { + s.Contains("x", out int _x); + }); + } + + [Fact] + public void TestContains__NullValue() + { + Assert.Throws(delegate + { + "abc".Contains(null, out int _x); + }); + } } } diff --git a/ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs b/ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs new file mode 100644 index 00000000..d7ff2316 --- /dev/null +++ b/ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs @@ -0,0 +1,88 @@ +using System; +using Xunit; +using TestUtils; +using ModuleManager.Extensions; + +namespace ModuleManagerTests.Extensions +{ + public class UrlDirExtensionsTest + { + + [Fact] + public void TestFind__IndirectChild() + { + UrlDir urlDir = UrlBuilder.CreateDir("abc"); + UrlDir.UrlFile urlFile = UrlBuilder.CreateFile("def/ghi.cfg", urlDir); + + Assert.Equal(urlFile, urlDir.Find("def/ghi")); + } + + [Fact] + public void TestFind__DirectChild() + { + UrlDir urlDir = UrlBuilder.CreateDir("abc"); + UrlDir.UrlFile urlFile = UrlBuilder.CreateFile("def.cfg", urlDir); + + Assert.Equal(urlFile, urlDir.Find("def")); + } + + [Fact] + public void TestFind__Extension() + { + UrlDir urlDir = UrlBuilder.CreateDir("abc"); + UrlBuilder.CreateFile("def/ghi.yyy", urlDir); + UrlDir.UrlFile urlFile = UrlBuilder.CreateFile("def/ghi.cfg", urlDir); + UrlBuilder.CreateFile("def/ghi.zzz", urlDir); + + Assert.Equal(urlFile, urlDir.Find("def/ghi.cfg")); + } + + [Fact] + public void TestFind__NotFound() + { + UrlDir urlDir = UrlBuilder.CreateDir("abc"); + UrlBuilder.CreateDir("def", urlDir); + + Assert.Null(urlDir.Find("def/ghi")); + } + + [Fact] + public void TestFind__Extension__NotFound() + { + UrlDir urlDir = UrlBuilder.CreateDir("abc"); + UrlBuilder.CreateFile("def/ghi.yyy", urlDir); + UrlBuilder.CreateFile("def/ghi.zzz", urlDir); + + Assert.Null(urlDir.Find("def/ghi.cfg")); + } + + [Fact] + public void TestFind__IntermediateDirectoryNotFound() + { + UrlDir urlDir = UrlBuilder.CreateDir("abc"); + Assert.Null(urlDir.Find("def/ghi")); + } + + [Fact] + public void TestFind__UrlDirNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + UrlDirExtensions.Find(null, "abc"); + }); + + Assert.Equal("urlDir", ex.ParamName); + } + + [Fact] + public void TestFind__UrlNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + UrlDirExtensions.Find(UrlBuilder.CreateDir("abc"), null); + }); + + Assert.Equal("url", ex.ParamName); + } + } +} diff --git a/ModuleManagerTests/Extensions/UrlFileExtensionsTest.cs b/ModuleManagerTests/Extensions/UrlFileExtensionsTest.cs new file mode 100644 index 00000000..147e603e --- /dev/null +++ b/ModuleManagerTests/Extensions/UrlFileExtensionsTest.cs @@ -0,0 +1,17 @@ +using System; +using Xunit; +using TestUtils; +using ModuleManager.Extensions; + +namespace ModuleManagerTests.Extensions +{ + public static class UrlFileExtensionsTest + { + [Fact] + public static void TestGetUrlWithExtension() + { + UrlDir.UrlFile urlFile = UrlBuilder.CreateFile("abc/def/ghi.cfg"); + Assert.Equal("abc/def/ghi.cfg", urlFile.GetUrlWithExtension()); + } + } +} diff --git a/ModuleManagerTests/InGameTestRunnerTest.cs b/ModuleManagerTests/InGameTestRunnerTest.cs index cd860bc2..a75897f4 100644 --- a/ModuleManagerTests/InGameTestRunnerTest.cs +++ b/ModuleManagerTests/InGameTestRunnerTest.cs @@ -73,12 +73,12 @@ public void TestRunTestCases__WrongNumberOfNodes() Received.InOrder(delegate { - logger.Log(LogType.Log, "Running tests..."); - logger.Log(LogType.Error, $"Test blah1 failed as expected number of nodes differs expected: 1 found: 2"); - logger.Log(LogType.Log, testNode1.ToString()); - logger.Log(LogType.Log, testNode2.ToString()); - logger.Log(LogType.Log, expectNode.ToString()); - logger.Log(LogType.Log, "tests complete."); + logger.AssertInfo("Running tests..."); + logger.AssertError($"Test blah1 failed as expected number of nodes differs expected: 1 found: 2"); + logger.AssertInfo(testNode1.ToString()); + logger.AssertInfo(testNode2.ToString()); + logger.AssertInfo(expectNode.ToString()); + logger.AssertInfo("tests complete."); }); Assert.Equal(3, file1.configs.Count); @@ -131,11 +131,11 @@ public void TestRunTestCases__AllPassing() Received.InOrder(delegate { - logger.Log(LogType.Log, "Running tests..."); - logger.Log(LogType.Log, "tests complete."); + logger.AssertInfo("Running tests..."); + logger.AssertInfo("tests complete."); }); - logger.DidNotReceive().Log(LogType.Error, Arg.Any()); + logger.AssertNoError(); Assert.Empty(file1.configs); Assert.Empty(file2.configs); @@ -176,9 +176,9 @@ public void TestRunTestCases__Failure() Received.InOrder(delegate { - logger.Log(LogType.Log, "Running tests..."); - logger.Log(LogType.Error, $"Test blah1[0] failed as expected output and actual output differ.\nexpected:\n{expectNode1}\nActually got:\n{testNode1}"); - logger.Log(LogType.Log, "tests complete."); + logger.AssertInfo("Running tests..."); + logger.AssertError($"Test blah1[0] failed as expected output and actual output differ.\nexpected:\n{expectNode1}\nActually got:\n{testNode1}"); + logger.AssertInfo("tests complete."); }); diff --git a/ModuleManagerTests/Logging/ExceptionMessageTest.cs b/ModuleManagerTests/Logging/ExceptionMessageTest.cs deleted file mode 100644 index b072ce89..00000000 --- a/ModuleManagerTests/Logging/ExceptionMessageTest.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using Xunit; -using NSubstitute; -using ModuleManager.Logging; - -namespace ModuleManagerTests.Logging -{ - public class ExceptionMessageTest - { - [Fact] - public void TestLogTo() - { - IBasicLogger logger = Substitute.For(); - - Exception e = new Exception(); - ExceptionMessage message = new ExceptionMessage("An exception was thrown", e); - message.LogTo(logger); - - logger.Received().Exception("An exception was thrown", e); - } - } -} diff --git a/ModuleManagerTests/Logging/LogMessageTest.cs b/ModuleManagerTests/Logging/LogMessageTest.cs new file mode 100644 index 00000000..d70fe8db --- /dev/null +++ b/ModuleManagerTests/Logging/LogMessageTest.cs @@ -0,0 +1,126 @@ +using System; +using Xunit; +using NSubstitute; +using UnityEngine; +using ModuleManager.Logging; + +namespace ModuleManagerTests.Logging +{ + public class LogMessageTest + { + [Fact] + public void TestConstructor() + { + LogMessage logMessage = new LogMessage(LogType.Log, "a message"); + Assert.Equal(LogType.Log, logMessage.LogType); + Assert.True(logMessage.Timestamp <= DateTime.Now); + Assert.True(logMessage.Timestamp > DateTime.Now - new TimeSpan(0, 0, 5)); + Assert.Equal("a message", logMessage.Message); + } + + [Fact] + public void TestConstructor__NullMessage() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new LogMessage(LogType.Log, null); + }); + + Assert.Equal("message", ex.ParamName); + } + + [Fact] + public void TestConstructor__FromOtherMessage() + { + ILogMessage logMessage = Substitute.For(); + logMessage.LogType.Returns(LogType.Log); + logMessage.Message.Returns("the old message"); + logMessage.Timestamp.Returns(new DateTime(2000, 1, 1, 12, 34, 45, 678)); + LogMessage newLogMessage = new LogMessage(logMessage, "a new message"); + Assert.Equal(LogType.Log, newLogMessage.LogType); + Assert.Equal(logMessage.Timestamp, newLogMessage.Timestamp); + Assert.Equal("a new message", newLogMessage.Message); + } + + [Fact] + public void TestConstructor__FromOtherMessage__LogMessageNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new LogMessage(null, "a new message"); + }); + + Assert.Equal("logMessage", ex.ParamName); + } + + [Fact] + public void TestConstructor__FromOtherMessage__NewMessageNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new LogMessage(Substitute.For(), null); + }); + + Assert.Equal("newMessage", ex.ParamName); + } + + [Fact] + public void TestToLogMessage__Info() + { + LogMessage message = new LogMessage(LogType.Log, "everything is ok"); + Assert.Matches(@"^\[LOG \d\d:\d\d:\d\d.\d\d\d\] everything is ok$", message.ToLogString()); + } + + [Fact] + public void TestToLogMessage__Warning() + { + LogMessage message = new LogMessage(LogType.Warning, "I'm warning you"); + Assert.Matches(@"^\[WRN \d\d:\d\d:\d\d.\d\d\d\] I'm warning you$", message.ToLogString()); + } + + [Fact] + public void TestToLogMessage__Error() + { + LogMessage message = new LogMessage(LogType.Error, "You went too far"); + Assert.Matches(@"^\[ERR \d\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); + } + + [Fact] + public void TestToLogMessage__Exception() + { + LogMessage message = new LogMessage(LogType.Exception, "You went too far"); + Assert.Matches(@"^\[EXC \d\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); + } + + [Fact] + public void TestToLogMessage__Assert() + { + LogMessage message = new LogMessage(LogType.Assert, "You went too far"); + Assert.Matches(@"^\[AST \d\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); + } + + [Fact] + public void TestToLogMessage__Unknown() + { + LogMessage message = new LogMessage((LogType)9999, "You went too far"); + Assert.Matches(@"^\[\?\?\? \d\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); + } + + [Fact] + public void TestToString() + { + LogMessage message = new LogMessage(LogType.Log, "everything is ok"); + Assert.Equal("[ModuleManager.Logging.LogMessage LogType=Log Message=everything is ok]", message.ToString()); + } + + [Fact] + public void TestToLogMessage__Timestamp() + { + ILogMessage logMessage = Substitute.For(); + logMessage.LogType.Returns(LogType.Log); + logMessage.Timestamp.Returns(new DateTime(2000, 1, 1, 12, 34, 56, 789)); + LogMessage message = new LogMessage(logMessage, "everything is ok"); + Assert.Equal("[LOG 12:34:56.789] everything is ok", message.ToLogString()); + } + } +} diff --git a/ModuleManagerTests/Logging/LogSplitterTest.cs b/ModuleManagerTests/Logging/LogSplitterTest.cs index 2375a3a3..4135f92d 100644 --- a/ModuleManagerTests/Logging/LogSplitterTest.cs +++ b/ModuleManagerTests/Logging/LogSplitterTest.cs @@ -36,21 +36,22 @@ public void TestLog() IBasicLogger logger1 = Substitute.For(); IBasicLogger logger2 = Substitute.For(); LogSplitter logSplitter = new LogSplitter(logger1, logger2); - logSplitter.Log(LogType.Log, "some stuff"); - logger1.Received().Log(LogType.Log, "some stuff"); - logger2.Received().Log(LogType.Log, "some stuff"); + ILogMessage message = Substitute.For(); + logSplitter.Log(message); + logger1.Received().Log(message); + logger2.Received().Log(message); } [Fact] - public void TestException() + public void TestLog__MessageNull() { - IBasicLogger logger1 = Substitute.For(); - IBasicLogger logger2 = Substitute.For(); - LogSplitter logSplitter = new LogSplitter(logger1, logger2); - Exception ex = new Exception(); - logSplitter.Exception("some stuff", ex); - logger1.Received().Exception("some stuff", ex); - logger2.Received().Exception("some stuff", ex); + LogSplitter logSplitter = new LogSplitter(Substitute.For(), Substitute.For()); + ArgumentNullException ex = Assert.Throws(delegate + { + logSplitter.Log(null); + }); + + Assert.Equal("message", ex.ParamName); } } } diff --git a/ModuleManagerTests/Logging/ModLoggerTest.cs b/ModuleManagerTests/Logging/ModLoggerTest.cs deleted file mode 100644 index 7c4bf427..00000000 --- a/ModuleManagerTests/Logging/ModLoggerTest.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using Xunit; -using NSubstitute; -using UnityEngine; -using ModuleManager.Logging; - -namespace ModuleManagerTests.Logging -{ - public class ModLoggerTest - { - private IBasicLogger innerLogger; - private ModLogger logger; - - public ModLoggerTest() - { - innerLogger = Substitute.For(); - logger = new ModLogger("MyMod", innerLogger); - } - - [Fact] - public void TestConstructor__PrefixNull() - { - ArgumentNullException e = Assert.Throws(delegate - { - new ModLogger(null, innerLogger); - }); - - Assert.Equal("prefix", e.ParamName); - } - - [Fact] - public void TestConstructor__PrefixBlank() - { - ArgumentNullException e = Assert.Throws(delegate - { - new ModLogger("", innerLogger); - }); - - Assert.Equal("prefix", e.ParamName); - } - - [Fact] - public void TestConstructor__LoggerNull() - { - ArgumentNullException e = Assert.Throws(delegate - { - new ModLogger("blah", null); - }); - - Assert.Equal("logger", e.ParamName); - } - - [Fact] - public void TestLog__Info() - { - logger.Log(LogType.Log, "well hi there"); - - innerLogger.Received().Log(LogType.Log, "[MyMod] well hi there"); - } - - [Fact] - public void TestLog__Warning() - { - logger.Log(LogType.Warning, "I'm warning you"); - - innerLogger.Received().Log(LogType.Warning, "[MyMod] I'm warning you"); - } - - [Fact] - public void TestLog__Error() - { - logger.Log(LogType.Error, "You have made a grave mistake"); - - innerLogger.Received().Log(LogType.Error, "[MyMod] You have made a grave mistake"); - } - - [Fact] - public void TestException() - { - Exception e = new Exception(); - logger.Exception("An exception was thrown", e); - - innerLogger.Received().Exception("[MyMod] An exception was thrown", e); - } - } -} diff --git a/ModuleManagerTests/Logging/NormalMessageTest.cs b/ModuleManagerTests/Logging/NormalMessageTest.cs deleted file mode 100644 index c63dd985..00000000 --- a/ModuleManagerTests/Logging/NormalMessageTest.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using Xunit; -using NSubstitute; -using UnityEngine; -using ModuleManager.Logging; - -namespace ModuleManagerTests.Logging -{ - public class NormalMessageTest - { - private IBasicLogger logger = Substitute.For(); - - [Fact] - public void TestLogTo__Info() - { - NormalMessage message = new NormalMessage(LogType.Log, "everything is ok"); - message.LogTo(logger); - logger.Received().Log(LogType.Log, "everything is ok"); - } - - [Fact] - public void TestLogTo__Warning() - { - NormalMessage message = new NormalMessage(LogType.Warning, "I'm warning you"); - message.LogTo(logger); - logger.Received().Log(LogType.Warning, "I'm warning you"); - } - - [Fact] - public void TestLogTo__Error() - { - NormalMessage message = new NormalMessage(LogType.Error, "You went too far"); - message.LogTo(logger); - logger.Received().Log(LogType.Error, "You went too far"); - } - } -} diff --git a/ModuleManagerTests/Logging/PrefixLoggerTest.cs b/ModuleManagerTests/Logging/PrefixLoggerTest.cs new file mode 100644 index 00000000..5aaf0113 --- /dev/null +++ b/ModuleManagerTests/Logging/PrefixLoggerTest.cs @@ -0,0 +1,80 @@ +using System; +using Xunit; +using NSubstitute; +using UnityEngine; +using ModuleManager.Logging; + +namespace ModuleManagerTests.Logging +{ + public class PrefixLoggerTest + { + private readonly IBasicLogger innerLogger = Substitute.For(); + private readonly PrefixLogger logger; + + public PrefixLoggerTest() + { + logger = new PrefixLogger("MyMod", innerLogger); + } + + [Fact] + public void TestConstructor__PrefixNull() + { + ArgumentNullException e = Assert.Throws(delegate + { + new PrefixLogger(null, innerLogger); + }); + + Assert.Equal("prefix", e.ParamName); + } + + [Fact] + public void TestConstructor__PrefixBlank() + { + ArgumentNullException e = Assert.Throws(delegate + { + new PrefixLogger("", innerLogger); + }); + + Assert.Equal("prefix", e.ParamName); + } + + [Fact] + public void TestConstructor__LoggerNull() + { + ArgumentNullException e = Assert.Throws(delegate + { + new PrefixLogger("blah", null); + }); + + Assert.Equal("logger", e.ParamName); + } + + [Fact] + public void TestLog() + { + ILogMessage logMessage = Substitute.For(); + logMessage.LogType.Returns(LogType.Log); + logMessage.Message.Returns("well hi there"); + logMessage.Timestamp.Returns(new DateTime(2000, 1, 1, 12, 34, 45, 678)); + + logger.Log(logMessage); + + innerLogger.Received().Log(Arg.Is(msg => + msg.LogType == LogType.Log && + msg.Timestamp == logMessage.Timestamp && + msg.Message == "[MyMod] well hi there" + )); + } + + [Fact] + public void TestLog__Null() + { + ArgumentNullException ex = Assert.Throws(delegate + { + logger.Log(null); + }); + + Assert.Equal("message", ex.ParamName); + } + } +} diff --git a/ModuleManagerTests/Logging/QueueLogRunnerTest.cs b/ModuleManagerTests/Logging/QueueLogRunnerTest.cs index a52e7add..6a975bd2 100644 --- a/ModuleManagerTests/Logging/QueueLogRunnerTest.cs +++ b/ModuleManagerTests/Logging/QueueLogRunnerTest.cs @@ -73,12 +73,12 @@ public void TestRun() Received.InOrder(delegate { - message1.LogTo(logger); - message2.LogTo(logger); - message3.LogTo(logger); - message4.LogTo(logger); - message5.LogTo(logger); - message6.LogTo(logger); + logger.Log(message1); + logger.Log(message2); + logger.Log(message3); + logger.Log(message4); + logger.Log(message5); + logger.Log(message6); }); } diff --git a/ModuleManagerTests/Logging/QueueLoggerTest.cs b/ModuleManagerTests/Logging/QueueLoggerTest.cs index b984f8c2..25e1abbe 100644 --- a/ModuleManagerTests/Logging/QueueLoggerTest.cs +++ b/ModuleManagerTests/Logging/QueueLoggerTest.cs @@ -1,7 +1,6 @@ using System; using Xunit; using NSubstitute; -using UnityEngine; using ModuleManager.Collections; using ModuleManager.Logging; @@ -9,43 +8,43 @@ namespace ModuleManagerTests.Logging { public class QueueLoggerTest { - private IMessageQueue queue; - private QueueLogger logger; + private readonly IMessageQueue queue = Substitute.For>(); + private readonly QueueLogger logger; public QueueLoggerTest() { - queue = Substitute.For>(); logger = new QueueLogger(queue); } [Fact] - public void TestLog__Info() + public void TestConstructor__QueueNull() { - logger.Log(LogType.Log, "useful information"); - queue.Received().Add(Arg.Is(m => m.logType == LogType.Log && m.message == "useful information")); - } + ArgumentNullException ex = Assert.Throws(delegate + { + new QueueLogger(null); + }); - [Fact] - public void TestLog__Warning() - { - logger.Log(LogType.Warning, "not to alarm you, but something might be wrong"); - queue.Received().Add(Arg.Is(m => m.logType == LogType.Warning && m.message == "not to alarm you, but something might be wrong")); + Assert.Equal("queue", ex.ParamName); } [Fact] - public void TestLog__Error() + public void TestLog() { - logger.Log(LogType.Error, "you broke everything"); - queue.Received().Add(Arg.Is(m => m.logType == LogType.Error && m.message == "you broke everything")); + ILogMessage message = Substitute.For(); + logger.Log(message); + queue.Received().Add(message); } - [Fact] - public void TestException() + public void TestLog__MessageNull() { - Exception e = new Exception(); - logger.Exception("An exception was thrown", e); - queue.Received().Add(Arg.Is(m => m.message == "An exception was thrown" && m.exception == e)); + ArgumentNullException ex = Assert.Throws(delegate + { + logger.Log(null); + }); + + Assert.Equal("message", ex.ParamName); } + } } diff --git a/ModuleManagerTests/Logging/StreamLoggerTest.cs b/ModuleManagerTests/Logging/StreamLoggerTest.cs index fdcb832c..296eeac2 100644 --- a/ModuleManagerTests/Logging/StreamLoggerTest.cs +++ b/ModuleManagerTests/Logging/StreamLoggerTest.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using UnityEngine; using Xunit; using NSubstitute; using ModuleManager.Logging; @@ -12,12 +11,10 @@ public class StreamLoggerTest [Fact] public void TestConstructor__StreamNull() { - ArgumentNullException ex = Assert.Throws(delegate + Assert.Throws(delegate { new StreamLogger(null); }); - - Assert.Equal("stream", ex.ParamName); } [Fact] @@ -25,13 +22,10 @@ public void TestConstructor__CantWrite() { using (MemoryStream stream = new MemoryStream(new byte[0], false)) { - ArgumentException ex = Assert.Throws(delegate + Assert.Throws(delegate { new StreamLogger(stream); }); - - Assert.Equal("stream", ex.ParamName); - Assert.Contains("must be writable", ex.Message); } } @@ -45,7 +39,7 @@ public void TestLog__AlreadyDisposed() InvalidOperationException ex = Assert.Throws(delegate { - streamLogger.Log(LogType.Log, "a message"); + streamLogger.Log(Substitute.For()); }); Assert.Contains("Object has already been disposed", ex.Message); @@ -53,14 +47,16 @@ public void TestLog__AlreadyDisposed() } [Fact] - public void TestLog__Log() + public void TestLog() { - byte[] bytes = new byte[50]; + ILogMessage message = Substitute.For(); + message.ToLogString().Returns("[OMG wtf] bbq"); + byte[] bytes = new byte[15]; using (MemoryStream stream = new MemoryStream(bytes, true)) { using (StreamLogger streamLogger = new StreamLogger(stream)) { - streamLogger.Log(LogType.Log, "a message"); + streamLogger.Log(message); } } @@ -68,125 +64,25 @@ public void TestLog__Log() { using (StreamReader reader = new StreamReader(stream)) { - string result = reader.ReadToEnd(); - Assert.Contains("[LOG ", result); - Assert.Contains("] a message", result); + string result = reader.ReadToEnd().Trim('\r', '\n', '\0'); + Assert.Equal("[OMG wtf] bbq", result); } } } [Fact] - public void TestLog__Assert() + public void TestLog__MessageNull() { - byte[] bytes = new byte[50]; - using (MemoryStream stream = new MemoryStream(bytes, true)) - { - using (StreamLogger streamLogger = new StreamLogger(stream)) - { - streamLogger.Log(LogType.Assert, "a message"); - } - } - - using (MemoryStream stream = new MemoryStream(bytes, false)) - { - using (StreamReader reader = new StreamReader(stream)) - { - string result = reader.ReadToEnd(); - Assert.Contains("[AST ", result); - Assert.Contains("] a message", result); - } - } - } - - [Fact] - public void TestLog__Warning() - { - byte[] bytes = new byte[50]; - using (MemoryStream stream = new MemoryStream(bytes, true)) - { - using (StreamLogger streamLogger = new StreamLogger(stream)) - { - streamLogger.Log(LogType.Warning, "a message"); - } - } - - using (MemoryStream stream = new MemoryStream(bytes, false)) - { - using (StreamReader reader = new StreamReader(stream)) - { - string result = reader.ReadToEnd(); - Assert.Contains("[WRN ", result); - Assert.Contains("] a message", result); - } - } - } - - [Fact] - public void TestLog__Error() - { - byte[] bytes = new byte[50]; - using (MemoryStream stream = new MemoryStream(bytes, true)) - { - using (StreamLogger streamLogger = new StreamLogger(stream)) - { - streamLogger.Log(LogType.Error, "a message"); - } - } - - using (MemoryStream stream = new MemoryStream(bytes, false)) - { - using (StreamReader reader = new StreamReader(stream)) - { - string result = reader.ReadToEnd(); - Assert.Contains("[ERR ", result); - Assert.Contains("] a message", result); - } - } - } - - [Fact] - public void TestLog__Exception() - { - byte[] bytes = new byte[50]; - using (MemoryStream stream = new MemoryStream(bytes, true)) - { - using (StreamLogger streamLogger = new StreamLogger(stream)) - { - streamLogger.Log(LogType.Exception, "a message"); - } - } - - using (MemoryStream stream = new MemoryStream(bytes, false)) + using (MemoryStream stream = new MemoryStream(new byte[0], true)) { - using (StreamReader reader = new StreamReader(stream)) - { - string result = reader.ReadToEnd(); - Assert.Contains("[EXC ", result); - Assert.Contains("] a message", result); - } - } - } + StreamLogger streamLogger = new StreamLogger(stream); - [Fact] - public void TestLog__Unknown() - { - byte[] bytes = new byte[50]; - using (MemoryStream stream = new MemoryStream(bytes, true)) - { - using (StreamLogger streamLogger = new StreamLogger(stream)) + ArgumentNullException ex = Assert.Throws(delegate { - streamLogger.Log((LogType)1000, "a message"); - } - } + streamLogger.Log(null); + }); - using (MemoryStream stream = new MemoryStream(bytes, false)) - { - using (StreamReader reader = new StreamReader(stream)) - { - string result = reader.ReadToEnd(); - Assert.Contains("[UNK ", result); - Assert.Contains("] a message", result); - } + Assert.Equal("message", ex.ParamName); } } } diff --git a/ModuleManagerTests/Logging/UnityLoggerTest.cs b/ModuleManagerTests/Logging/UnityLoggerTest.cs index b2e22d4e..7c4dc4a1 100644 --- a/ModuleManagerTests/Logging/UnityLoggerTest.cs +++ b/ModuleManagerTests/Logging/UnityLoggerTest.cs @@ -2,18 +2,18 @@ using Xunit; using NSubstitute; using UnityEngine; +using ModuleManager.Extensions; using ModuleManager.Logging; namespace ModuleManagerTests.Logging { public class UnityLoggerTest { - private ILogger innerLogger; - private UnityLogger logger; + private readonly ILogger innerLogger = Substitute.For(); + private readonly UnityLogger logger; public UnityLoggerTest() { - innerLogger = Substitute.For(); logger = new UnityLogger(innerLogger); } @@ -31,7 +31,7 @@ public void TestConstructor__LoggerNull() [Fact] public void TestLog__Info() { - logger.Log(LogType.Log, "well hi there"); + logger.Info("well hi there"); innerLogger.Received().Log(LogType.Log, "well hi there"); } @@ -39,27 +39,20 @@ public void TestLog__Info() [Fact] public void TestLog__Warning() { - logger.Log(LogType.Warning, "I'm warning you"); + logger.Warning("I'm warning you"); innerLogger.Received().Log(LogType.Warning, "I'm warning you"); } [Fact] - public void TestLog__Error() + public void TestLog__MessageNull() { - logger.Log(LogType.Error, "You have made a grave mistake"); - - innerLogger.Received().Log(LogType.Error, "You have made a grave mistake"); - } - - [Fact] - public void TestException() - { - Exception e = new Exception(); - logger.Exception("An exception was thrown", e); + ArgumentNullException e = Assert.Throws(delegate + { + logger.Log(null); + }); - innerLogger.Received().Log(LogType.Error, "An exception was thrown"); - innerLogger.Received().LogException(e); + Assert.Equal("message", e.ParamName); } } } diff --git a/ModuleManagerTests/LoggingAssertionHelpers.cs b/ModuleManagerTests/LoggingAssertionHelpers.cs new file mode 100644 index 00000000..74ae5cc4 --- /dev/null +++ b/ModuleManagerTests/LoggingAssertionHelpers.cs @@ -0,0 +1,70 @@ +using System; +using UnityEngine; +using NSubstitute; +using ModuleManager.Logging; + +namespace ModuleManagerTests +{ + public static class LoggingAssertionHelpers + { + public static void AssertInfo(this IBasicLogger logger, string message) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + logger.Received().Log(Arg.Is(msg => msg.LogType == LogType.Log && msg.Message == message)); + } + + public static void AssertNoInfo(this IBasicLogger logger) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + logger.DidNotReceive().Log(Arg.Is(msg => msg.LogType == LogType.Log)); + } + + public static void AssertWarning(this IBasicLogger logger, string message) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + logger.Received().Log(Arg.Is(msg => msg.LogType == LogType.Warning && msg.Message == message)); + } + + public static void AssertNoWarning(this IBasicLogger logger) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + logger.DidNotReceive().Log(Arg.Is(msg => msg.LogType == LogType.Warning)); + } + + public static void AssertError(this IBasicLogger logger, string message) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + logger.Received().Log(Arg.Is(msg => msg.LogType == LogType.Error && msg.Message == message)); + } + + public static void AssertNoError(this IBasicLogger logger) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + logger.DidNotReceive().Log(Arg.Is(msg => msg.LogType == LogType.Error)); + } + + public static void AssertException(this IBasicLogger logger, string message, Exception exception) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + logger.Received().Log(Arg.Is(msg => msg.LogType == LogType.Exception && msg.Message == message + ": " + exception.ToString())); + } + + public static void AssertException(this IBasicLogger logger, Exception exception) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + logger.Received().Log(Arg.Is(msg => msg.LogType == LogType.Exception && msg.Message == exception.ToString())); + } + + public static void AssertNoException(this IBasicLogger logger) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + logger.DidNotReceive().Log(Arg.Is(msg => msg.LogType == LogType.Exception)); + } + + public static void AssertNoLog(this IBasicLogger logger) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + logger.DidNotReceiveWithAnyArgs().Log(null); + } + } +} diff --git a/ModuleManagerTests/MMPatchLoaderTest.cs b/ModuleManagerTests/MMPatchLoaderTest.cs index ac687909..b0617fe8 100644 --- a/ModuleManagerTests/MMPatchLoaderTest.cs +++ b/ModuleManagerTests/MMPatchLoaderTest.cs @@ -71,6 +71,84 @@ public void TestModifyNode__MultiplyValue() }, c3); } + [Fact] + public void TestModifyNode__EditNode__SpecialCharacters() + { + ConfigNode c1 = new TestConfigNode("NODE") + { + new TestConfigNode("INNER_NODE") + { + { "weird_values", "some\r\n\tstuff" }, + }, + }; + + UrlDir.UrlConfig c2u = UrlBuilder.CreateConfig("abc/def", new TestConfigNode("@NODE") + { + new TestConfigNode("@INNER_NODE") + { + { "another_weird_value", "some\r\nmore\tstuff" }, + }, + }); + + PatchContext context = new PatchContext(c2u, Enumerable.Empty(), logger, progress); + + ConfigNode c3 = MMPatchLoader.ModifyNode(new NodeStack(c1), c2u.config, context); + + EnsureNoErrors(); + + AssertConfigNodesEqual(new TestConfigNode("NODE") + { + new TestConfigNode("INNER_NODE") + { + { "weird_values", "some\r\n\tstuff" }, + { "another_weird_value", "some\r\nmore\tstuff" }, + }, + }, c3); + } + + [Fact] + public void TestModifyNode__ReplaceNode__SpecialCharacters() + { + ConfigNode c1 = new TestConfigNode("NODE") + { + new TestConfigNode("INNER_NODE") + { + { "weird_values", "some\r\n\tstuff" }, + }, + }; + + UrlDir.UrlConfig c2u = UrlBuilder.CreateConfig("abc/def", new TestConfigNode("@NODE") + { + new TestConfigNode("%INNER_NODE") + { + { "another_weird_value", "some\r\nmore\tstuff" }, + }, + new TestConfigNode("%OTHER_INNER_NODE") + { + { "another_weirder_value", "even\r\nmore\tstuff" }, + }, + }); + + PatchContext context = new PatchContext(c2u, Enumerable.Empty(), logger, progress); + + ConfigNode c3 = MMPatchLoader.ModifyNode(new NodeStack(c1), c2u.config, context); + + EnsureNoErrors(); + + AssertConfigNodesEqual(new TestConfigNode("NODE") + { + new TestConfigNode("INNER_NODE") + { + { "weird_values", "some\r\n\tstuff" }, + { "another_weird_value", "some\r\nmore\tstuff" }, + }, + new TestConfigNode("OTHER_INNER_NODE") + { + { "another_weirder_value", "even\r\nmore\tstuff" }, + }, + }, c3); + } + private void AssertConfigNodesEqual(ConfigNode expected, ConfigNode observed) { Assert.Equal(expected.ToString(), observed.ToString()); @@ -82,9 +160,9 @@ private void EnsureNoErrors() progress.DidNotReceiveWithAnyArgs().Exception(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - logger.DidNotReceive().Log(LogType.Warning, Arg.Any()); - logger.DidNotReceive().Log(LogType.Error, Arg.Any()); - logger.DidNotReceive().Log(LogType.Exception, Arg.Any()); + logger.AssertNoWarning(); + logger.AssertNoError(); + logger.AssertNoException(); } } } diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 7e1bd948..f3afbbf5 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -1,8 +1,8 @@  - + - + Debug @@ -37,29 +37,33 @@ 4 false + + 8.0 + - ..\packages\Castle.Core.4.3.1\lib\net45\Castle.Core.dll + ..\packages\Castle.Core.4.4.1\lib\net45\Castle.Core.dll - - ..\packages\NSubstitute.3.1.0\lib\net46\NSubstitute.dll + + ..\packages\NSubstitute.4.2.2\lib\net46\NSubstitute.dll - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll - - ..\packages\System.Threading.Tasks.Extensions.4.5.1\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + ..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll True @@ -75,32 +79,43 @@ + + + + + + + + + + + + - - - - - + + + + + + + - - - @@ -108,13 +123,7 @@ - - - - - - - + @@ -150,10 +159,10 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/ModuleManagerTests/NeedsCheckerTest.cs b/ModuleManagerTests/NeedsCheckerTest.cs index d0414a0b..4d3540b9 100644 --- a/ModuleManagerTests/NeedsCheckerTest.cs +++ b/ModuleManagerTests/NeedsCheckerTest.cs @@ -5,7 +5,6 @@ using ModuleManager; using ModuleManager.Logging; using ModuleManager.Progress; -using NodeStack = ModuleManager.Collections.ImmutableStack; namespace ModuleManagerTests { @@ -13,17 +12,13 @@ public class NeedsCheckerTest { private readonly UrlDir gameData; - private readonly IPatchProgress progress; - private readonly IBasicLogger logger; - private NeedsChecker needsChecker; + private readonly IPatchProgress progress = Substitute.For(); + private readonly IBasicLogger logger = Substitute.For(); + private readonly NeedsChecker needsChecker; public NeedsCheckerTest() { gameData = UrlBuilder.CreateGameData(); - - progress = Substitute.For(); - logger = Substitute.For(); - needsChecker = new NeedsChecker(new[] { "mod1", "mod2", "mod/2" }, gameData, progress, logger); } diff --git a/ModuleManagerTests/PassTest.cs b/ModuleManagerTests/PassTest.cs index d2cce882..bc829816 100644 --- a/ModuleManagerTests/PassTest.cs +++ b/ModuleManagerTests/PassTest.cs @@ -2,7 +2,6 @@ using System.Linq; using Xunit; using NSubstitute; -using TestUtils; using ModuleManager; using ModuleManager.Patches; @@ -10,13 +9,6 @@ namespace ModuleManagerTests { public class PassTest { - private UrlDir.UrlFile file; - - public PassTest() - { - file = UrlBuilder.CreateFile("abc/def.cfg"); - } - [Fact] public void TestConstructor__NameNull() { diff --git a/ModuleManagerTests/PatchApplierTest.cs b/ModuleManagerTests/PatchApplierTest.cs index d7fb0eb1..d54ac286 100644 --- a/ModuleManagerTests/PatchApplierTest.cs +++ b/ModuleManagerTests/PatchApplierTest.cs @@ -89,9 +89,9 @@ public void TestApplyPatches() progress.DidNotReceiveWithAnyArgs().Exception(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - logger.DidNotReceive().Log(LogType.Warning, Arg.Any()); - logger.DidNotReceive().Log(LogType.Error, Arg.Any()); - logger.DidNotReceiveWithAnyArgs().Exception(null, null); + logger.AssertNoWarning(); + logger.AssertNoError(); + logger.AssertNoException(); Received.InOrder(delegate { diff --git a/ModuleManagerTests/PatchExtractorTest.cs b/ModuleManagerTests/PatchExtractorTest.cs index 4106486e..49a12583 100644 --- a/ModuleManagerTests/PatchExtractorTest.cs +++ b/ModuleManagerTests/PatchExtractorTest.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Xunit; using NSubstitute; using TestUtils; @@ -386,10 +385,5 @@ private void AssertNoErrors() progress.DidNotReceiveWithAnyArgs().Exception(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); } - - private void AssertConfigNodesEqual(ConfigNode expected, ConfigNode observed) - { - Assert.Equal(expected.ToString(), observed.ToString()); - } } } diff --git a/ModuleManagerTests/PatchListTest.cs b/ModuleManagerTests/PatchListTest.cs index 09b1c1dc..4dc5c751 100644 --- a/ModuleManagerTests/PatchListTest.cs +++ b/ModuleManagerTests/PatchListTest.cs @@ -113,6 +113,7 @@ public void Test__Lifecycle() Substitute.For(), Substitute.For(), Substitute.For(), + Substitute.For(), }; UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE")); @@ -139,8 +140,9 @@ public void Test__Lifecycle() patches[19].PassSpecifier.Returns(new AfterPassSpecifier("MOD2", urlConfig)); patches[20].PassSpecifier.Returns(new LastPassSpecifier("mod2")); patches[21].PassSpecifier.Returns(new LastPassSpecifier("MOD2")); - patches[22].PassSpecifier.Returns(new FinalPassSpecifier()); + patches[22].PassSpecifier.Returns(new LastPassSpecifier("mod3")); patches[23].PassSpecifier.Returns(new FinalPassSpecifier()); + patches[24].PassSpecifier.Returns(new FinalPassSpecifier()); patches[00].CountsAsPatch.Returns(false); patches[01].CountsAsPatch.Returns(false); @@ -166,6 +168,7 @@ public void Test__Lifecycle() patches[21].CountsAsPatch.Returns(true); patches[22].CountsAsPatch.Returns(true); patches[23].CountsAsPatch.Returns(true); + patches[24].CountsAsPatch.Returns(true); IPatchProgress progress = Substitute.For(); @@ -173,7 +176,7 @@ public void Test__Lifecycle() IPass[] passes = patchList.ToArray(); - Assert.Equal(12, passes.Length); + Assert.Equal(13, passes.Length); Assert.Equal(":INSERT (initial)", passes[0].Name); Assert.Equal(new[] { patches[0], patches[1] }, passes[0]); @@ -208,10 +211,13 @@ public void Test__Lifecycle() Assert.Equal(":LAST[MOD2]", passes[10].Name); Assert.Equal(new[] { patches[20], patches[21] }, passes[10]); - Assert.Equal(":FINAL", passes[11].Name); - Assert.Equal(new[] { patches[22], patches[23] }, passes[11]); + Assert.Equal(":LAST[MOD3]", passes[11].Name); + Assert.Equal(new[] { patches[22] }, passes[11]); + + Assert.Equal(":FINAL", passes[12].Name); + Assert.Equal(new[] { patches[23], patches[24] }, passes[12]); - progress.Received(22).PatchAdded(); + progress.Received(23).PatchAdded(); } } } diff --git a/ModuleManagerTests/Patches/EditPatchTest.cs b/ModuleManagerTests/Patches/EditPatchTest.cs index 50efd3b1..7c76cf48 100644 --- a/ModuleManagerTests/Patches/EditPatchTest.cs +++ b/ModuleManagerTests/Patches/EditPatchTest.cs @@ -236,7 +236,7 @@ public void TestApply__Loop() Received.InOrder(delegate { - logger.Log(LogType.Log, "Looping on ghi/jkl/@NODE to abc/def.cfg/NODE"); + logger.AssertInfo("Looping on ghi/jkl/@NODE to abc/def.cfg/NODE"); progress.ApplyingUpdate(urlConfig, patch.UrlConfig); progress.ApplyingUpdate(modifiedUrlConfigs[1], patch.UrlConfig); progress.ApplyingUpdate(modifiedUrlConfigs[2], patch.UrlConfig); diff --git a/ModuleManagerTests/Patches/PatchCompilerTest.cs b/ModuleManagerTests/Patches/PatchCompilerTest.cs index 5ad3c794..bb786dc5 100644 --- a/ModuleManagerTests/Patches/PatchCompilerTest.cs +++ b/ModuleManagerTests/Patches/PatchCompilerTest.cs @@ -44,7 +44,7 @@ public void TestCompilePatch__Insert() patch.Apply(configs, progress, logger); - Assert.Equal(1, configs.Count); + Assert.Single(configs); Assert.NotSame(protoPatch.urlConfig.config, configs.First.Value.Node); AssertNodesEqual(new TestConfigNode("NODE") { diff --git a/ModuleManagerTests/Progress/PatchProgressTest.cs b/ModuleManagerTests/Progress/PatchProgressTest.cs index ee61fd46..c78964e3 100644 --- a/ModuleManagerTests/Progress/PatchProgressTest.cs +++ b/ModuleManagerTests/Progress/PatchProgressTest.cs @@ -1,7 +1,6 @@ using System; using Xunit; using NSubstitute; -using UnityEngine; using TestUtils; using ModuleManager; using ModuleManager.Logging; @@ -11,12 +10,11 @@ namespace ModuleManagerTests { public class PatchProgressTest { - private IBasicLogger logger; - private PatchProgress progress; + private readonly IBasicLogger logger = Substitute.For(); + private readonly PatchProgress progress; public PatchProgressTest() { - logger = Substitute.For(); progress = new PatchProgress(logger); } @@ -36,8 +34,8 @@ public void Test__Constructor__Nested() progress2.ApplyingUpdate(original, patch1); Assert.Equal(1, progress.Counter.patchedNodes); - logger.DidNotReceiveWithAnyArgs().Log(LogType.Log, null); - logger2.Received().Log(LogType.Log, "Applying update ghi/jkl/@SOME_NODE to abc/def.cfg/SOME_NODE"); + logger.AssertNoLog(); + logger2.AssertInfo("Applying update ghi/jkl/@SOME_NODE to abc/def.cfg/SOME_NODE"); } [Fact] @@ -62,11 +60,11 @@ public void TestApplyingUpdate() progress.ApplyingUpdate(original, patch1); Assert.Equal(1, progress.Counter.patchedNodes); - logger.Received().Log(LogType.Log, "Applying update ghi/jkl/@SOME_NODE to abc/def.cfg/SOME_NODE"); + logger.AssertInfo("Applying update ghi/jkl/@SOME_NODE to abc/def.cfg/SOME_NODE"); progress.ApplyingUpdate(original, patch2); Assert.Equal(2, progress.Counter.patchedNodes); - logger.Received().Log(LogType.Log, "Applying update pqr/stu/@SOME_NODE to abc/def.cfg/SOME_NODE"); + logger.AssertInfo("Applying update pqr/stu/@SOME_NODE to abc/def.cfg/SOME_NODE"); } [Fact] @@ -81,11 +79,11 @@ public void TesApplyingCopy() progress.ApplyingCopy(original, patch1); Assert.Equal(1, progress.Counter.patchedNodes); - logger.Received().Log(LogType.Log, "Applying copy ghi/jkl/+SOME_NODE to abc/def.cfg/SOME_NODE"); + logger.AssertInfo("Applying copy ghi/jkl/+SOME_NODE to abc/def.cfg/SOME_NODE"); progress.ApplyingCopy(original, patch2); Assert.Equal(2, progress.Counter.patchedNodes); - logger.Received().Log(LogType.Log, "Applying copy pqr/stu/+SOME_NODE to abc/def.cfg/SOME_NODE"); + logger.AssertInfo("Applying copy pqr/stu/+SOME_NODE to abc/def.cfg/SOME_NODE"); } [Fact] @@ -100,11 +98,11 @@ public void TesApplyingDelete() progress.ApplyingDelete(original, patch1); Assert.Equal(1, progress.Counter.patchedNodes); - logger.Received().Log(LogType.Log, "Applying delete ghi/jkl/!SOME_NODE to abc/def.cfg/SOME_NODE"); + logger.AssertInfo("Applying delete ghi/jkl/!SOME_NODE to abc/def.cfg/SOME_NODE"); progress.ApplyingDelete(original, patch2); Assert.Equal(2, progress.Counter.patchedNodes); - logger.Received().Log(LogType.Log, "Applying delete pqr/stu/!SOME_NODE to abc/def.cfg/SOME_NODE"); + logger.AssertInfo("Applying delete pqr/stu/!SOME_NODE to abc/def.cfg/SOME_NODE"); } [Fact] @@ -131,11 +129,11 @@ public void TestNeedsUnsatisfiedRoot() progress.NeedsUnsatisfiedRoot(config1); Assert.Equal(1, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its NEEDS"); + logger.AssertInfo("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its NEEDS"); progress.NeedsUnsatisfiedRoot(config2); Assert.Equal(2, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its NEEDS"); + logger.AssertInfo("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its NEEDS"); } [Fact] @@ -148,11 +146,11 @@ public void TestNeedsUnsatisfiedNode() progress.NeedsUnsatisfiedNode(config1, "SOME/NODE/PATH/SOME_CHILD_NODE"); Assert.Equal(0, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting node in file abc/def subnode: SOME/NODE/PATH/SOME_CHILD_NODE as it can't satisfy its NEEDS"); + logger.AssertInfo("Deleting node in file abc/def subnode: SOME/NODE/PATH/SOME_CHILD_NODE as it can't satisfy its NEEDS"); progress.NeedsUnsatisfiedNode(config2, "SOME/NODE/PATH/SOME_OTHER_CHILD_NODE"); Assert.Equal(0, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting node in file ghi/jkl subnode: SOME/NODE/PATH/SOME_OTHER_CHILD_NODE as it can't satisfy its NEEDS"); + logger.AssertInfo("Deleting node in file ghi/jkl subnode: SOME/NODE/PATH/SOME_OTHER_CHILD_NODE as it can't satisfy its NEEDS"); } [Fact] @@ -165,11 +163,11 @@ public void TestNeedsUnsatisfiedValue() progress.NeedsUnsatisfiedValue(config1, "SOME/NODE/PATH/some_value"); Assert.Equal(0, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting value in file abc/def value: SOME/NODE/PATH/some_value as it can't satisfy its NEEDS"); + logger.AssertInfo("Deleting value in file abc/def value: SOME/NODE/PATH/some_value as it can't satisfy its NEEDS"); progress.NeedsUnsatisfiedValue(config2, "SOME/NODE/PATH/some_other_value"); Assert.Equal(0, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting value in file ghi/jkl value: SOME/NODE/PATH/some_other_value as it can't satisfy its NEEDS"); + logger.AssertInfo("Deleting value in file ghi/jkl value: SOME/NODE/PATH/some_other_value as it can't satisfy its NEEDS"); } [Fact] @@ -182,11 +180,11 @@ public void TestNeedsUnsatisfiedBefore() progress.NeedsUnsatisfiedBefore(config1); Assert.Equal(1, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its BEFORE"); + logger.AssertInfo("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its BEFORE"); progress.NeedsUnsatisfiedBefore(config2); Assert.Equal(2, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its BEFORE"); + logger.AssertInfo("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its BEFORE"); } [Fact] @@ -199,11 +197,11 @@ public void TestNeedsUnsatisfiedFor() progress.NeedsUnsatisfiedFor(config1); Assert.Equal(1, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Warning, "Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its FOR (this shouldn't happen)"); + logger.AssertWarning("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its FOR (this shouldn't happen)"); progress.NeedsUnsatisfiedFor(config2); Assert.Equal(2, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Warning, "Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its FOR (this shouldn't happen)"); + logger.AssertWarning("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its FOR (this shouldn't happen)"); } [Fact] @@ -216,11 +214,11 @@ public void TestNeedsUnsatisfiedAfter() progress.NeedsUnsatisfiedAfter(config1); Assert.Equal(1, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its AFTER"); + logger.AssertInfo("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its AFTER"); progress.NeedsUnsatisfiedAfter(config2); Assert.Equal(2, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its AFTER"); + logger.AssertInfo("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its AFTER"); } [Fact] @@ -233,7 +231,7 @@ public void TestStartingPass() progress.PassStarted(pass1); - logger.Received().Log(LogType.Log, ":SOME_PASS pass"); + logger.AssertInfo(":SOME_PASS pass"); onEvent.Received()(pass1); } @@ -250,7 +248,7 @@ public void TestStartingPass__NullArgument() Assert.Equal("pass", ex.ParamName); - logger.DidNotReceiveWithAnyArgs().Log(LogType.Log, null); + logger.AssertNoLog(); onEvent.DidNotReceiveWithAnyArgs()(null); } @@ -265,12 +263,12 @@ public void TestWarning() progress.Warning(config1, "I'm warning you"); Assert.Equal(1, progress.Counter.warnings); Assert.Equal(1, progress.Counter.warningFiles["abc/def.cfg"]); - logger.Received().Log(LogType.Warning, "I'm warning you"); + logger.AssertWarning("I'm warning you"); progress.Warning(config2, "You should probably pay attention to this"); Assert.Equal(2, progress.Counter.warnings); Assert.Equal(2, progress.Counter.warningFiles["abc/def.cfg"]); - logger.Received().Log(LogType.Warning, "You should probably pay attention to this"); + logger.AssertWarning("You should probably pay attention to this"); } [Fact] @@ -297,12 +295,12 @@ public void TestError__Config() progress.Error(config1, "An error message no one is going to read"); Assert.Equal(1, progress.Counter.errors); Assert.Equal(1, progress.Counter.errorFiles["abc/def.cfg"]); - logger.Received().Log(LogType.Error, "An error message no one is going to read"); + logger.AssertError("An error message no one is going to read"); progress.Error(config2, "Maybe someone will read this one"); Assert.Equal(2, progress.Counter.errors); Assert.Equal(2, progress.Counter.errorFiles["abc/def.cfg"]); - logger.Received().Log(LogType.Error, "Maybe someone will read this one"); + logger.AssertError("Maybe someone will read this one"); } [Fact] @@ -315,11 +313,11 @@ public void TestException() progress.Exception("An exception was thrown", e1); Assert.Equal(1, progress.Counter.exceptions); - logger.Received().Exception("An exception was thrown", e1); + logger.AssertException("An exception was thrown", e1); progress.Exception("An exception was tossed", e2); Assert.Equal(2, progress.Counter.exceptions); - logger.Received().Exception("An exception was tossed", e2); + logger.AssertException("An exception was tossed", e2); } [Fact] @@ -336,12 +334,12 @@ public void TestException__Url() progress.Exception(config1, "An exception was thrown", e1); Assert.Equal(1, progress.Counter.exceptions); Assert.Equal(1, progress.Counter.errorFiles["abc/def.cfg"]); - logger.Received().Exception("An exception was thrown", e1); + logger.AssertException("An exception was thrown", e1); progress.Exception(config2, "An exception was tossed", e2); Assert.Equal(2, progress.Counter.exceptions); Assert.Equal(2, progress.Counter.errorFiles["abc/def.cfg"]); - logger.Received().Exception("An exception was tossed", e2); + logger.AssertException("An exception was tossed", e2); } [Fact] diff --git a/ModuleManagerTests/ProtoUrlConfigTest.cs b/ModuleManagerTests/ProtoUrlConfigTest.cs new file mode 100644 index 00000000..f16cd1bd --- /dev/null +++ b/ModuleManagerTests/ProtoUrlConfigTest.cs @@ -0,0 +1,87 @@ +using System; +using Xunit; +using TestUtils; +using ModuleManager; + +namespace ModuleManagerTests +{ + public class ProtoUrlConfigTest + { + [Fact] + public void TestContructor__UrlFileNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new ProtoUrlConfig(null, new ConfigNode()); + }); + + Assert.Equal("urlFile", ex.ParamName); + } + + [Fact] + public void TestContructor__NodeNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new ProtoUrlConfig(UrlBuilder.CreateFile("foo/bar"), null); + }); + + Assert.Equal("node", ex.ParamName); + } + + [Fact] + public void TestUrlFile() + { + UrlDir.UrlFile urlFile = UrlBuilder.CreateFile("abc/def.cfg"); + ProtoUrlConfig protoUrlConfig = new ProtoUrlConfig(urlFile, new ConfigNode()); + + Assert.Same(urlFile, protoUrlConfig.UrlFile); + } + + [Fact] + public void TestNode() + { + ConfigNode node = new ConfigNode("NODE"); + ProtoUrlConfig protoUrlConfig = new ProtoUrlConfig(UrlBuilder.CreateFile("foo/bar"), node); + + Assert.Same(node, protoUrlConfig.Node); + } + + [Fact] + public void TestFileUrl() + { + ProtoUrlConfig protoUrlConfig = new ProtoUrlConfig(UrlBuilder.CreateFile("abc/def.cfg"), new ConfigNode()); + + Assert.Equal("abc/def.cfg", protoUrlConfig.FileUrl); + } + + [Fact] + public void TestNodeType() + { + ProtoUrlConfig protoUrlConfig = new ProtoUrlConfig(UrlBuilder.CreateFile("abc/def"), new ConfigNode("SOME_NODE")); + + Assert.Equal("SOME_NODE", protoUrlConfig.NodeType); + } + + [Fact] + public void TestFullUrl() + { + ProtoUrlConfig protoUrlConfig = new ProtoUrlConfig(UrlBuilder.CreateFile("abc/def.cfg"), new ConfigNode("SOME_NODE")); + + Assert.Equal("abc/def.cfg/SOME_NODE", protoUrlConfig.FullUrl); + } + + [Fact] + public void TestFullUrl__NameValue() + { + ConfigNode node = new TestConfigNode("SOME_NODE") + { + { "name", "some_value" }, + }; + + ProtoUrlConfig protoUrlConfig = new ProtoUrlConfig(UrlBuilder.CreateFile("abc/def.cfg"), node); + + Assert.Equal("abc/def.cfg/SOME_NODE[some_value]", protoUrlConfig.FullUrl); + } + } +} diff --git a/ModuleManagerTests/app.config b/ModuleManagerTests/app.config index 1d152980..fe205877 100644 --- a/ModuleManagerTests/app.config +++ b/ModuleManagerTests/app.config @@ -4,11 +4,11 @@ - + - + diff --git a/ModuleManagerTests/packages.config b/ModuleManagerTests/packages.config index dc5635be..b159434d 100644 --- a/ModuleManagerTests/packages.config +++ b/ModuleManagerTests/packages.config @@ -1,9 +1,9 @@  - - - - + + + + @@ -11,6 +11,6 @@ - - + + \ No newline at end of file diff --git a/TestUtils/TestConfigNode.cs b/TestUtils/TestConfigNode.cs index a954c107..694fc55e 100644 --- a/TestUtils/TestConfigNode.cs +++ b/TestUtils/TestConfigNode.cs @@ -8,8 +8,8 @@ public class TestConfigNode : ConfigNode, IEnumerable public TestConfigNode() : base() { } public TestConfigNode(string name) : base(name) { } - public void Add(string name, string value) => AddValue(name, value); - public void Add(ConfigNode.Value value) => values.Add(value); + public void Add(string name, string value) => Add(new Value(name, value)); + public void Add(Value value) => values.Add(value); public void Add(string name, ConfigNode node) => AddNode(name, node); public void Add(ConfigNode node) => AddNode(node); diff --git a/TestUtils/TestUtils.csproj b/TestUtils/TestUtils.csproj index 10073bd0..5c53b27c 100644 --- a/TestUtils/TestUtils.csproj +++ b/TestUtils/TestUtils.csproj @@ -32,6 +32,9 @@ 4 false + + 8.0 + diff --git a/TestUtils/UrlBuilder.cs b/TestUtils/UrlBuilder.cs index 2fff4a47..e8e60bd5 100644 --- a/TestUtils/UrlBuilder.cs +++ b/TestUtils/UrlBuilder.cs @@ -118,6 +118,39 @@ public static UrlDir.UrlFile CreateFile(string path, UrlDir parent = null) bool cfg = false; string newName = name; + UrlDir.FileType fileType = UrlDir.FileType.Unknown; + + switch (extension) + { + case "dll": + fileType = UrlDir.FileType.Assembly; + break; + case "ksp": + fileType = UrlDir.FileType.AssetBundle; + break; + case "wav": + case "ogg": + fileType = UrlDir.FileType.Audio; + break; + case "cfg": + fileType = UrlDir.FileType.Config; + break; + case "dae": + case "mu": + fileType = UrlDir.FileType.Model; + break; + case "dds": + case "jpg": + case "jpeg": + case "mbm": + case "png": + case "tga": + case "truecolor": + fileType = UrlDir.FileType.Texture; + break; + } + + // KSP tries to load .cfg files so need to have special handling if (extension == "cfg") { cfg = true; @@ -126,11 +159,12 @@ public static UrlDir.UrlFile CreateFile(string path, UrlDir parent = null) UrlDir.UrlFile file = new UrlDir.UrlFile(parent, new FileInfo(newName)); + UrlFile__field__fileType.SetValue(file, fileType); + if (cfg) { UrlFile__field__name.SetValue(file, nameWithoutExtension); UrlFile__field__fileExtension.SetValue(file, "cfg"); - UrlFile__field__fileType.SetValue(file, UrlDir.FileType.Config); } parent.files.Add(file); diff --git a/TestUtilsTests/TestConfigNodeTest.cs b/TestUtilsTests/TestConfigNodeTest.cs index 6b00b4de..e544da78 100644 --- a/TestUtilsTests/TestConfigNodeTest.cs +++ b/TestUtilsTests/TestConfigNodeTest.cs @@ -16,6 +16,7 @@ public void TestTestConfigNode() { "multiple", "first" }, { "multiple", "second" }, new ConfigNode.Value("foo", "bar"), + { "weird_values", "some\r\n\tstuff" }, { "NODE_1", new TestConfigNode { { "name", "something" }, @@ -34,31 +35,41 @@ public void TestTestConfigNode() }, }; - Assert.Equal("something", node.GetValue("value1")); - Assert.Equal("something else", node.GetValue("value2")); - Assert.Equal(new[] { "first", "second" }, node.GetValues("multiple")); - Assert.Equal("bar", node.GetValue("foo")); + Assert.Equal(6, node.values.Count); + AssertValue("value1", "something", node.values[0]); + AssertValue("value2", "something else", node.values[1]); + AssertValue("multiple", "first", node.values[2]); + AssertValue("multiple", "second", node.values[3]); + AssertValue("foo", "bar", node.values[4]); + AssertValue("weird_values", "some\r\n\tstuff", node.values[5]); + Assert.Equal(3, node.nodes.Count); ConfigNode innerNode1 = node.GetNode("NODE_1"); Assert.NotNull(innerNode1); - Assert.Equal("NODE_1", innerNode1.name); - Assert.Equal("something", innerNode1.GetValue("name")); - Assert.Equal("something else", innerNode1.GetValue("stuff")); + Assert.Equal("NODE_1", node.nodes[0].name); + Assert.Equal(2, node.nodes[0].values.Count); + AssertValue("name", "something", node.nodes[0].values[0]); + AssertValue("stuff", "something else", node.nodes[0].values[1]); + Assert.Empty(node.nodes[0].nodes); - ConfigNode[] innerNodes2 = node.GetNodes("MULTIPLE"); - Assert.NotNull(innerNodes2); - Assert.Equal(2, innerNodes2.Length); + Assert.Equal("MULTIPLE", node.nodes[1].name); + Assert.Equal(2, node.nodes[1].values.Count); + AssertValue("value3", "blah", node.nodes[1].values[0]); + AssertValue("value4", "bleh", node.nodes[1].values[1]); + Assert.Empty(node.nodes[1].nodes); - ConfigNode innerNode2a = innerNodes2[0]; - Assert.NotNull(innerNode2a); - Assert.Equal("blah", innerNode2a.GetValue("value3")); - Assert.Equal("bleh", innerNode2a.GetValue("value4")); + Assert.Equal("MULTIPLE", node.nodes[2].name); + Assert.Equal(2, node.nodes[2].values.Count); + AssertValue("value3", "blih", node.nodes[2].values[0]); + AssertValue("value4", "bloh", node.nodes[2].values[1]); + Assert.Empty(node.nodes[2].nodes); + } - ConfigNode innerNode2b = innerNodes2[1]; - Assert.NotNull(innerNode2b); - Assert.Equal("blih", innerNode2b.GetValue("value3")); - Assert.Equal("bloh", innerNode2b.GetValue("value4")); + private void AssertValue(string name, string value, ConfigNode.Value nodeValue) + { + Assert.Equal(name, nodeValue.name); + Assert.Equal(value, nodeValue.value); } } } diff --git a/TestUtilsTests/TestUtilsTests.csproj b/TestUtilsTests/TestUtilsTests.csproj index 619e6bb4..cfe71bf2 100644 --- a/TestUtilsTests/TestUtilsTests.csproj +++ b/TestUtilsTests/TestUtilsTests.csproj @@ -1,8 +1,8 @@  - + - + Debug @@ -37,6 +37,9 @@ 4 false + + 8.0 + @@ -86,10 +89,10 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/TestUtilsTests/UrlBuilderTest.cs b/TestUtilsTests/UrlBuilderTest.cs index 956350dd..38a788f0 100644 --- a/TestUtilsTests/UrlBuilderTest.cs +++ b/TestUtilsTests/UrlBuilderTest.cs @@ -209,23 +209,37 @@ public void TestCreateFile__Parent() Assert.Contains(file, root.AllFiles); } - // KSP tries to load .cfg files so need to have special handling - [Fact] - public void TestCreateFile__cfg() + [InlineData("dll", UrlDir.FileType.Assembly)] + [InlineData("ksp", UrlDir.FileType.AssetBundle)] + [InlineData("wav", UrlDir.FileType.Audio)] + [InlineData("ogg", UrlDir.FileType.Audio)] + [InlineData("cfg", UrlDir.FileType.Config)] + [InlineData("dae", UrlDir.FileType.Model)] + [InlineData("mu", UrlDir.FileType.Model)] + [InlineData("dds", UrlDir.FileType.Texture)] + [InlineData("jpg", UrlDir.FileType.Texture)] + [InlineData("jpeg", UrlDir.FileType.Texture)] + [InlineData("mbm", UrlDir.FileType.Texture)] + [InlineData("png", UrlDir.FileType.Texture)] + [InlineData("tga", UrlDir.FileType.Texture)] + [InlineData("truecolor", UrlDir.FileType.Texture)] + [InlineData("txt", UrlDir.FileType.Unknown)] + [InlineData("xml", UrlDir.FileType.Unknown)] + [Theory] + public void TestCreateFile__Extension(string extension, UrlDir.FileType fileType) { - UrlDir root = UrlBuilder.CreateRoot(); - UrlDir dir = UrlBuilder.CreateDir("someDir", root); - UrlDir.UrlFile file = UrlBuilder.CreateFile("someFile.cfg", dir); + UrlDir.UrlFile file = UrlBuilder.CreateFile("someFile." + extension); Assert.Equal("someFile", file.name); - Assert.Equal("cfg", file.fileExtension); - Assert.Equal(UrlDir.FileType.Config, file.fileType); - Assert.Same(dir, file.parent); - Assert.Same(root, file.root); + Assert.Equal(extension, file.fileExtension); + Assert.Equal(fileType, file.fileType); - Assert.Equal("someDir/someFile", file.url); - Assert.Contains(file, dir.files); - Assert.Contains(file, root.AllConfigFiles); + UrlDir root = file.parent; + Assert.NotNull(root); + Assert.Equal("root", root.name); + Assert.Null(root.parent); + Assert.Contains(file, root.files); + Assert.Same(root, file.root); } [Fact] diff --git a/TestUtilsTests/packages.config b/TestUtilsTests/packages.config index bbb4cb82..8cf69e4f 100644 --- a/TestUtilsTests/packages.config +++ b/TestUtilsTests/packages.config @@ -7,6 +7,6 @@ - - + + \ No newline at end of file