From 918b0a24de7f604b31a058395e4e2709c7dc8ede Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 19 Apr 2019 17:15:36 -0700 Subject: [PATCH 01/67] add thread-safe KeyValueCache --- ModuleManager/Collections/KeyValueCache.cs | 29 ++++++++++ ModuleManager/ModuleManager.csproj | 1 + .../Collections/KeyValueCacheTest.cs | 53 +++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 4 files changed, 84 insertions(+) create mode 100644 ModuleManager/Collections/KeyValueCache.cs create mode 100644 ModuleManagerTests/Collections/KeyValueCacheTest.cs 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/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 92ce283a..c4913ef7 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -38,6 +38,7 @@ + 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/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 7e1bd948..d9aabe53 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -110,6 +110,7 @@ + From 6e6a465c4a04578772eb989b9f2132b3dd4b4d56 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 19 Apr 2019 17:34:03 -0700 Subject: [PATCH 02/67] use new KeyValueCache class as regex cache Makes it thread safe - doesn't matter in production but might matter in tests which can be run in parallel --- ModuleManager/MMPatchLoader.cs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index f3e0ab91..4ab1827a 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -37,7 +37,7 @@ 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(); @@ -1447,14 +1447,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]); } @@ -1659,11 +1655,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); } From a0c1dfc2c7342c8ee1010bee9a3019a3d4eb2b31 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 19 Apr 2019 16:33:05 -0700 Subject: [PATCH 03/67] add test for ProtoUrlConfig --- ModuleManagerTests/ModuleManagerTests.csproj | 1 + ModuleManagerTests/ProtoUrlConfigTest.cs | 74 ++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 ModuleManagerTests/ProtoUrlConfigTest.cs diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index d9aabe53..a550a59a 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -108,6 +108,7 @@ + diff --git a/ModuleManagerTests/ProtoUrlConfigTest.cs b/ModuleManagerTests/ProtoUrlConfigTest.cs new file mode 100644 index 00000000..9a1cfcdb --- /dev/null +++ b/ModuleManagerTests/ProtoUrlConfigTest.cs @@ -0,0 +1,74 @@ +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); + } + } +} From 72717253e9d96fa05a9ca19603b8dbbe3964c444 Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 19 Apr 2019 20:15:10 -0700 Subject: [PATCH 04/67] Add name value to applying patch messages Makes debugging patches easier, since multiple root nodes can save the same url Resolves #143 --- ModuleManager/ProtoUrlConfig.cs | 3 +++ ModuleManagerTests/ProtoUrlConfigTest.cs | 13 +++++++++++++ 2 files changed, 16 insertions(+) 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/ModuleManagerTests/ProtoUrlConfigTest.cs b/ModuleManagerTests/ProtoUrlConfigTest.cs index 9a1cfcdb..f16cd1bd 100644 --- a/ModuleManagerTests/ProtoUrlConfigTest.cs +++ b/ModuleManagerTests/ProtoUrlConfigTest.cs @@ -70,5 +70,18 @@ public void TestFullUrl() 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); + } } } From cfaac6aa5031db26d8d84216f5e6885314a10306 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 29 Apr 2019 21:25:15 -0700 Subject: [PATCH 05/67] Fix StreamLogger ignoring messages Resolves #145 --- ModuleManager/Logging/StreamLogger.cs | 4 ++- .../Logging/StreamLoggerTest.cs | 31 +++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/ModuleManager/Logging/StreamLogger.cs b/ModuleManager/Logging/StreamLogger.cs index 8f4b131a..6e157698 100644 --- a/ModuleManager/Logging/StreamLogger.cs +++ b/ModuleManager/Logging/StreamLogger.cs @@ -42,7 +42,9 @@ public void Log(LogType logType, string message) public void Exception(string message, Exception exception) { - Log(LogType.Exception, exception?.ToString() ?? ""); + if (!string.IsNullOrEmpty(message)) message += ": "; + message += exception?.ToString() ?? ""; + Log(LogType.Exception, message); } public void Dispose() diff --git a/ModuleManagerTests/Logging/StreamLoggerTest.cs b/ModuleManagerTests/Logging/StreamLoggerTest.cs index fdcb832c..f577adae 100644 --- a/ModuleManagerTests/Logging/StreamLoggerTest.cs +++ b/ModuleManagerTests/Logging/StreamLoggerTest.cs @@ -147,12 +147,13 @@ public void TestLog__Error() [Fact] public void TestLog__Exception() { - byte[] bytes = new byte[50]; + Exception ex = new Exception("something went wrong"); + byte[] bytes = new byte[100]; using (MemoryStream stream = new MemoryStream(bytes, true)) { using (StreamLogger streamLogger = new StreamLogger(stream)) { - streamLogger.Log(LogType.Exception, "a message"); + streamLogger.Exception("a message", ex); } } @@ -162,7 +163,31 @@ public void TestLog__Exception() { string result = reader.ReadToEnd(); Assert.Contains("[EXC ", result); - Assert.Contains("] a message", result); + Assert.Contains("] a message: " + ex.ToString(), result); + } + } + } + + [Fact] + public void TestLog__Exception__NullMessage() + { + Exception ex = new Exception("something went wrong"); + byte[] bytes = new byte[100]; + using (MemoryStream stream = new MemoryStream(bytes, true)) + { + using (StreamLogger streamLogger = new StreamLogger(stream)) + { + streamLogger.Exception(null, ex); + } + } + + using (MemoryStream stream = new MemoryStream(bytes, false)) + { + using (StreamReader reader = new StreamReader(stream)) + { + string result = reader.ReadToEnd(); + Assert.Contains("[EXC ", result); + Assert.Contains("] " + ex.ToString(), result); } } } From d4d0fb78126ad4979c7df55f47dd4e9fc1e55b9b Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 30 Apr 2019 00:24:38 -0700 Subject: [PATCH 06/67] Redo logging interface * Convert exceptions to strings rather than keeping a separate interface for exceptions everywhere * Log messages know how to convert themselves to log strings * Simplified interface (including assertions) provided by extension methods --- .../Extensions/IBasicLoggerExtensions.cs | 19 ++- ModuleManager/Logging/ExceptionMessage.cs | 21 --- ModuleManager/Logging/IBasicLogger.cs | 4 +- ModuleManager/Logging/ILogMessage.cs | 5 +- ModuleManager/Logging/LogMessage.cs | 43 +++++ ModuleManager/Logging/LogSplitter.cs | 14 +- ModuleManager/Logging/ModLogger.cs | 14 +- ModuleManager/Logging/NormalMessage.cs | 22 --- ModuleManager/Logging/QueueLogRunner.cs | 4 +- ModuleManager/Logging/QueueLogger.cs | 10 +- ModuleManager/Logging/StreamLogger.cs | 29 +--- ModuleManager/Logging/UnityLogger.cs | 9 +- ModuleManager/ModuleManager.csproj | 3 +- .../Extensions/IBasicLoggerExtensionsTest.cs | 55 ++++++- ModuleManagerTests/InGameTestRunnerTest.cs | 24 +-- .../Logging/ExceptionMessageTest.cs | 22 --- ModuleManagerTests/Logging/LogMessageTest.cs | 70 ++++++++ ModuleManagerTests/Logging/LogSplitterTest.cs | 23 +-- ModuleManagerTests/Logging/ModLoggerTest.cs | 30 ++-- .../Logging/NormalMessageTest.cs | 37 ----- .../Logging/QueueLogRunnerTest.cs | 12 +- ModuleManagerTests/Logging/QueueLoggerTest.cs | 36 ++--- .../Logging/StreamLoggerTest.cs | 153 ++---------------- ModuleManagerTests/Logging/UnityLoggerTest.cs | 24 ++- ModuleManagerTests/LoggingAssertionHelpers.cs | 70 ++++++++ ModuleManagerTests/MMPatchLoaderTest.cs | 6 +- ModuleManagerTests/ModuleManagerTests.csproj | 4 +- ModuleManagerTests/PatchApplierTest.cs | 6 +- ModuleManagerTests/Patches/EditPatchTest.cs | 2 +- .../Progress/PatchProgressTest.cs | 60 +++---- 30 files changed, 406 insertions(+), 425 deletions(-) delete mode 100644 ModuleManager/Logging/ExceptionMessage.cs create mode 100644 ModuleManager/Logging/LogMessage.cs delete mode 100644 ModuleManager/Logging/NormalMessage.cs delete mode 100644 ModuleManagerTests/Logging/ExceptionMessageTest.cs create mode 100644 ModuleManagerTests/Logging/LogMessageTest.cs delete mode 100644 ModuleManagerTests/Logging/NormalMessageTest.cs create mode 100644 ModuleManagerTests/LoggingAssertionHelpers.cs 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/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..55137cc7 100644 --- a/ModuleManager/Logging/ILogMessage.cs +++ b/ModuleManager/Logging/ILogMessage.cs @@ -1,9 +1,12 @@ using System; +using UnityEngine; namespace ModuleManager.Logging { public interface ILogMessage { - void LogTo(IBasicLogger logger); + LogType LogType { get; } + string Message { get; } + string ToLogString(); } } diff --git a/ModuleManager/Logging/LogMessage.cs b/ModuleManager/Logging/LogMessage.cs new file mode 100644 index 00000000..53f3c0f8 --- /dev/null +++ b/ModuleManager/Logging/LogMessage.cs @@ -0,0 +1,43 @@ +using System; +using UnityEngine; + +namespace ModuleManager.Logging +{ + public class LogMessage : ILogMessage + { + private const string DATETIME_FORMAT_STRING = "yyyy-MM-dd HH:mm:ss.fff"; + + public LogType LogType { get; } + public string Message { get; } + + public LogMessage(LogType logType, string message) + { + LogType = logType; + Message = message ?? throw new ArgumentNullException(nameof(message)); + } + + 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} {DateTime.Now.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 index eb2d3699..89a05066 100644 --- a/ModuleManager/Logging/ModLogger.cs +++ b/ModuleManager/Logging/ModLogger.cs @@ -1,21 +1,23 @@ using System; -using UnityEngine; namespace ModuleManager.Logging { public class ModLogger : IBasicLogger { - private string prefix; - private IBasicLogger logger; + private readonly string prefix; + private readonly IBasicLogger logger; public ModLogger(string prefix, IBasicLogger logger) { if (string.IsNullOrEmpty(prefix)) throw new ArgumentNullException(nameof(prefix)); - this.prefix = "[" + 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); + public void Log(ILogMessage message) + { + if (message == null) throw new ArgumentNullException(nameof(message)); + logger.Log(new LogMessage(message.LogType, prefix + message.Message)); + } } } 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/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 6e157698..bfbb1053 100644 --- a/ModuleManager/Logging/StreamLogger.cs +++ b/ModuleManager/Logging/StreamLogger.cs @@ -1,13 +1,10 @@ using System; using System.IO; -using UnityEngine; namespace ModuleManager.Logging { public 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; @@ -19,32 +16,12 @@ public StreamLogger(Stream 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) - { - if (!string.IsNullOrEmpty(message)) message += ": "; - message += exception?.ToString() ?? ""; - Log(LogType.Exception, message); + streamWriter.WriteLine(message.ToLogString()); } public void Dispose() diff --git a/ModuleManager/Logging/UnityLogger.cs b/ModuleManager/Logging/UnityLogger.cs index 0655b807..b31c1003 100644 --- a/ModuleManager/Logging/UnityLogger.cs +++ b/ModuleManager/Logging/UnityLogger.cs @@ -1,6 +1,5 @@ using System; using UnityEngine; -using ModuleManager.Extensions; namespace ModuleManager.Logging { @@ -13,12 +12,10 @@ 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/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index c4913ef7..1a2d77f5 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -47,13 +47,12 @@ - - + diff --git a/ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs b/ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs index 781cccb6..f6c1fad7 100644 --- a/ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs @@ -20,21 +20,70 @@ public IBasicLoggerExtensionsTest() 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/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..0a0d42c3 --- /dev/null +++ b/ModuleManagerTests/Logging/LogMessageTest.cs @@ -0,0 +1,70 @@ +using System; +using Xunit; +using UnityEngine; +using ModuleManager.Logging; + +namespace ModuleManagerTests.Logging +{ + public class LogMessageTest + { + [Fact] + public void TestConstructor__NullMessage() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new LogMessage(LogType.Log, null); + }); + + Assert.Equal("message", 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\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\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\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\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\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\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()); + } + } +} 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 index 7c4bf427..0ca45679 100644 --- a/ModuleManagerTests/Logging/ModLoggerTest.cs +++ b/ModuleManagerTests/Logging/ModLoggerTest.cs @@ -1,7 +1,7 @@ using System; using Xunit; using NSubstitute; -using UnityEngine; +using ModuleManager.Extensions; using ModuleManager.Logging; namespace ModuleManagerTests.Logging @@ -51,36 +51,30 @@ public void TestConstructor__LoggerNull() } [Fact] - public void TestLog__Info() + public void TestLog() { - logger.Log(LogType.Log, "well hi there"); + logger.Info("well hi there"); - innerLogger.Received().Log(LogType.Log, "[MyMod] well hi there"); + innerLogger.AssertInfo("[MyMod] well hi there"); } [Fact] public void TestLog__Warning() { - logger.Log(LogType.Warning, "I'm warning you"); + logger.Warning("I'm warning you"); - innerLogger.Received().Log(LogType.Warning, "[MyMod] I'm warning you"); + innerLogger.AssertWarning("[MyMod] I'm warning you"); } [Fact] - public void TestLog__Error() + public void TestLog__Null() { - logger.Log(LogType.Error, "You have made a grave mistake"); - - innerLogger.Received().Log(LogType.Error, "[MyMod] You have made a grave mistake"); - } + ArgumentNullException ex = Assert.Throws(delegate + { + logger.Log(null); + }); - [Fact] - public void TestException() - { - Exception e = new Exception(); - logger.Exception("An exception was thrown", e); - - innerLogger.Received().Exception("[MyMod] An exception was thrown", e); + Assert.Equal("message", ex.ParamName); } } } 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/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..dc8a1dfd 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; @@ -19,33 +18,34 @@ public QueueLoggerTest() } [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 f577adae..61535387 100644 --- a/ModuleManagerTests/Logging/StreamLoggerTest.cs +++ b/ModuleManagerTests/Logging/StreamLoggerTest.cs @@ -45,7 +45,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 +53,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,150 +70,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(); + 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() - { - Exception ex = new Exception("something went wrong"); - byte[] bytes = new byte[100]; - using (MemoryStream stream = new MemoryStream(bytes, true)) - { - using (StreamLogger streamLogger = new StreamLogger(stream)) - { - streamLogger.Exception("a message", ex); - } - } - - using (MemoryStream stream = new MemoryStream(bytes, false)) - { - using (StreamReader reader = new StreamReader(stream)) - { - string result = reader.ReadToEnd(); - Assert.Contains("[EXC ", result); - Assert.Contains("] a message: " + ex.ToString(), result); - } - } - } - - [Fact] - public void TestLog__Exception__NullMessage() - { - Exception ex = new Exception("something went wrong"); - byte[] bytes = new byte[100]; - using (MemoryStream stream = new MemoryStream(bytes, true)) - { - using (StreamLogger streamLogger = new StreamLogger(stream)) - { - streamLogger.Exception(null, ex); - } - } - - 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("] " + ex.ToString(), 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..5d820c25 100644 --- a/ModuleManagerTests/Logging/UnityLoggerTest.cs +++ b/ModuleManagerTests/Logging/UnityLoggerTest.cs @@ -2,6 +2,7 @@ using Xunit; using NSubstitute; using UnityEngine; +using ModuleManager.Extensions; using ModuleManager.Logging; namespace ModuleManagerTests.Logging @@ -31,7 +32,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 +40,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..29682448 100644 --- a/ModuleManagerTests/MMPatchLoaderTest.cs +++ b/ModuleManagerTests/MMPatchLoaderTest.cs @@ -82,9 +82,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 a550a59a..12379fd6 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -79,14 +79,14 @@ + - - + 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/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/Progress/PatchProgressTest.cs b/ModuleManagerTests/Progress/PatchProgressTest.cs index ee61fd46..8cde86c2 100644 --- a/ModuleManagerTests/Progress/PatchProgressTest.cs +++ b/ModuleManagerTests/Progress/PatchProgressTest.cs @@ -36,8 +36,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 +62,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 +81,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 +100,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 +131,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 +148,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 +165,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 +182,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 +199,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 +216,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 +233,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 +250,7 @@ public void TestStartingPass__NullArgument() Assert.Equal("pass", ex.ParamName); - logger.DidNotReceiveWithAnyArgs().Log(LogType.Log, null); + logger.AssertNoLog(); onEvent.DidNotReceiveWithAnyArgs()(null); } @@ -265,12 +265,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 +297,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 +315,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 +336,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] From fc74f1fcda46031eb9b3139c7a56830b6728d01d Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 30 Apr 2019 00:32:39 -0700 Subject: [PATCH 07/67] Initialize LogMessage from old LogMessage with new message only uses LogType from old message for now but more coming --- ModuleManager/Logging/LogMessage.cs | 7 ++++ ModuleManager/Logging/ModLogger.cs | 2 +- ModuleManagerTests/Logging/LogMessageTest.cs | 34 ++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/ModuleManager/Logging/LogMessage.cs b/ModuleManager/Logging/LogMessage.cs index 53f3c0f8..82c5a0fe 100644 --- a/ModuleManager/Logging/LogMessage.cs +++ b/ModuleManager/Logging/LogMessage.cs @@ -16,6 +16,13 @@ public LogMessage(LogType logType, string message) Message = message ?? throw new ArgumentNullException(nameof(message)); } + public LogMessage(ILogMessage logMessage, string newMessage) + { + if (logMessage == null) throw new ArgumentNullException(nameof(logMessage)); + LogType = logMessage.LogType; + Message = newMessage ?? throw new ArgumentNullException(nameof(newMessage)); + } + public string ToLogString() { string prefix; diff --git a/ModuleManager/Logging/ModLogger.cs b/ModuleManager/Logging/ModLogger.cs index 89a05066..6fead909 100644 --- a/ModuleManager/Logging/ModLogger.cs +++ b/ModuleManager/Logging/ModLogger.cs @@ -17,7 +17,7 @@ public ModLogger(string prefix, IBasicLogger logger) public void Log(ILogMessage message) { if (message == null) throw new ArgumentNullException(nameof(message)); - logger.Log(new LogMessage(message.LogType, prefix + message.Message)); + logger.Log(new LogMessage(message, prefix + message.Message)); } } } diff --git a/ModuleManagerTests/Logging/LogMessageTest.cs b/ModuleManagerTests/Logging/LogMessageTest.cs index 0a0d42c3..f44c8276 100644 --- a/ModuleManagerTests/Logging/LogMessageTest.cs +++ b/ModuleManagerTests/Logging/LogMessageTest.cs @@ -1,5 +1,6 @@ using System; using Xunit; +using NSubstitute; using UnityEngine; using ModuleManager.Logging; @@ -18,6 +19,39 @@ public void TestConstructor__NullMessage() 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 newLogMessage = new LogMessage(logMessage, "a new message"); + Assert.Equal(LogType.Log, newLogMessage.LogType); + 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() { From 07afe29d79f4260bbc6a3e6f517535ebc6afbba9 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 30 Apr 2019 00:53:04 -0700 Subject: [PATCH 08/67] Don't put date on every log message Instead put it once at the top of the log --- ModuleManager/Logging/LogMessage.cs | 1 + ModuleManager/MMPatchLoader.cs | 1 + ModuleManager/MMPatchRunner.cs | 1 + ModuleManagerTests/Logging/LogMessageTest.cs | 12 ++++++------ 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ModuleManager/Logging/LogMessage.cs b/ModuleManager/Logging/LogMessage.cs index 82c5a0fe..86348df3 100644 --- a/ModuleManager/Logging/LogMessage.cs +++ b/ModuleManager/Logging/LogMessage.cs @@ -6,6 +6,7 @@ namespace ModuleManager.Logging public class LogMessage : ILogMessage { private const string DATETIME_FORMAT_STRING = "yyyy-MM-dd HH:mm:ss.fff"; + private const string DATETIME_FORMAT_STRING = "HH:mm:ss.fff"; public LogType LogType { get; } public string Message { get; } diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 4ab1827a..56909ece 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -91,6 +91,7 @@ public IEnumerable Run() { 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!"); } diff --git a/ModuleManager/MMPatchRunner.cs b/ModuleManager/MMPatchRunner.cs index 802b76fb..739ecd82 100644 --- a/ModuleManager/MMPatchRunner.cs +++ b/ModuleManager/MMPatchRunner.cs @@ -40,6 +40,7 @@ public IEnumerator Run() { 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!"); } diff --git a/ModuleManagerTests/Logging/LogMessageTest.cs b/ModuleManagerTests/Logging/LogMessageTest.cs index f44c8276..6e7d78ff 100644 --- a/ModuleManagerTests/Logging/LogMessageTest.cs +++ b/ModuleManagerTests/Logging/LogMessageTest.cs @@ -56,42 +56,42 @@ public void TestConstructor__FromOtherMessage__NewMessageNull() 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\d:\d\d:\d\d.\d\d\d\] everything is ok$", message.ToLogString()); + 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\d:\d\d:\d\d.\d\d\d\] I'm warning you$", message.ToLogString()); + 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\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); + 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\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); + 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\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); + 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\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); + Assert.Matches(@"^\[\?\?\? \d\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); } [Fact] From 0a2c842ae3b141416b4a176f0fdb5fc1df438760 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 30 Apr 2019 01:03:16 -0700 Subject: [PATCH 09/67] Initialize timestamp with log message So if it takes some time to actually get to the log it will display the originating timestamp rather than the timestamp it was logged at --- ModuleManager/Logging/ILogMessage.cs | 1 + ModuleManager/Logging/LogMessage.cs | 6 ++++-- ModuleManagerTests/Logging/LogMessageTest.cs | 22 ++++++++++++++++++++ ModuleManagerTests/Logging/ModLoggerTest.cs | 21 ++++++++++--------- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/ModuleManager/Logging/ILogMessage.cs b/ModuleManager/Logging/ILogMessage.cs index 55137cc7..bf85f488 100644 --- a/ModuleManager/Logging/ILogMessage.cs +++ b/ModuleManager/Logging/ILogMessage.cs @@ -6,6 +6,7 @@ namespace ModuleManager.Logging public interface ILogMessage { LogType LogType { get; } + DateTime Timestamp { get; } string Message { get; } string ToLogString(); } diff --git a/ModuleManager/Logging/LogMessage.cs b/ModuleManager/Logging/LogMessage.cs index 86348df3..c6554aaf 100644 --- a/ModuleManager/Logging/LogMessage.cs +++ b/ModuleManager/Logging/LogMessage.cs @@ -5,15 +5,16 @@ namespace ModuleManager.Logging { public class LogMessage : ILogMessage { - private const string DATETIME_FORMAT_STRING = "yyyy-MM-dd HH:mm:ss.fff"; 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)); } @@ -21,6 +22,7 @@ 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)); } @@ -40,7 +42,7 @@ public string ToLogString() else prefix = "???"; - return $"[{prefix} {DateTime.Now.ToString(DATETIME_FORMAT_STRING)}] {Message}"; + return $"[{prefix} {Timestamp.ToString(DATETIME_FORMAT_STRING)}] {Message}"; } public override string ToString() diff --git a/ModuleManagerTests/Logging/LogMessageTest.cs b/ModuleManagerTests/Logging/LogMessageTest.cs index 6e7d78ff..d70fe8db 100644 --- a/ModuleManagerTests/Logging/LogMessageTest.cs +++ b/ModuleManagerTests/Logging/LogMessageTest.cs @@ -8,6 +8,16 @@ 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() { @@ -25,8 +35,10 @@ 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); } @@ -100,5 +112,15 @@ 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/ModLoggerTest.cs b/ModuleManagerTests/Logging/ModLoggerTest.cs index 0ca45679..fa15d6be 100644 --- a/ModuleManagerTests/Logging/ModLoggerTest.cs +++ b/ModuleManagerTests/Logging/ModLoggerTest.cs @@ -1,7 +1,7 @@ using System; using Xunit; using NSubstitute; -using ModuleManager.Extensions; +using UnityEngine; using ModuleManager.Logging; namespace ModuleManagerTests.Logging @@ -53,17 +53,18 @@ public void TestConstructor__LoggerNull() [Fact] public void TestLog() { - logger.Info("well hi there"); + 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)); - innerLogger.AssertInfo("[MyMod] well hi there"); - } - - [Fact] - public void TestLog__Warning() - { - logger.Warning("I'm warning you"); + logger.Log(logMessage); - innerLogger.AssertWarning("[MyMod] I'm warning you"); + innerLogger.Received().Log(Arg.Is(msg => + msg.LogType == LogType.Log && + msg.Timestamp == logMessage.Timestamp && + msg.Message == "[MyMod] well hi there" + )); } [Fact] From a8bcad2adbb10413dd8b3175a0b61ef44390533c Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 30 Apr 2019 22:00:25 -0700 Subject: [PATCH 10/67] ModLogger -> PrefixLogger Explains what it does better --- .../Logging/{ModLogger.cs => PrefixLogger.cs} | 4 ++-- ModuleManager/ModuleManager.cs | 4 ++-- ModuleManager/ModuleManager.csproj | 2 +- ModuleManager/PostPatchLoader.cs | 2 +- .../{ModLoggerTest.cs => PrefixLoggerTest.cs} | 14 +++++++------- ModuleManagerTests/ModuleManagerTests.csproj | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) rename ModuleManager/Logging/{ModLogger.cs => PrefixLogger.cs} (85%) rename ModuleManagerTests/Logging/{ModLoggerTest.cs => PrefixLoggerTest.cs} (85%) diff --git a/ModuleManager/Logging/ModLogger.cs b/ModuleManager/Logging/PrefixLogger.cs similarity index 85% rename from ModuleManager/Logging/ModLogger.cs rename to ModuleManager/Logging/PrefixLogger.cs index 6fead909..410473fa 100644 --- a/ModuleManager/Logging/ModLogger.cs +++ b/ModuleManager/Logging/PrefixLogger.cs @@ -2,12 +2,12 @@ namespace ModuleManager.Logging { - public class ModLogger : IBasicLogger + public class PrefixLogger : IBasicLogger { private readonly string prefix; private readonly IBasicLogger logger; - public ModLogger(string prefix, IBasicLogger logger) + public PrefixLogger(string prefix, IBasicLogger logger) { if (string.IsNullOrEmpty(prefix)) throw new ArgumentNullException(nameof(prefix)); this.prefix = $"[{prefix}] "; diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 4d7e76de..50072d91 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -132,7 +132,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. @@ -314,7 +314,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; diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 1a2d77f5..6739e50d 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -51,7 +51,7 @@ - + diff --git a/ModuleManager/PostPatchLoader.cs b/ModuleManager/PostPatchLoader.cs index 758ab0ae..f92f1fad 100644 --- a/ModuleManager/PostPatchLoader.cs +++ b/ModuleManager/PostPatchLoader.cs @@ -22,7 +22,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; diff --git a/ModuleManagerTests/Logging/ModLoggerTest.cs b/ModuleManagerTests/Logging/PrefixLoggerTest.cs similarity index 85% rename from ModuleManagerTests/Logging/ModLoggerTest.cs rename to ModuleManagerTests/Logging/PrefixLoggerTest.cs index fa15d6be..28a82edc 100644 --- a/ModuleManagerTests/Logging/ModLoggerTest.cs +++ b/ModuleManagerTests/Logging/PrefixLoggerTest.cs @@ -6,15 +6,15 @@ namespace ModuleManagerTests.Logging { - public class ModLoggerTest + public class PrefixLoggerTest { private IBasicLogger innerLogger; - private ModLogger logger; + private PrefixLogger logger; - public ModLoggerTest() + public PrefixLoggerTest() { innerLogger = Substitute.For(); - logger = new ModLogger("MyMod", innerLogger); + logger = new PrefixLogger("MyMod", innerLogger); } [Fact] @@ -22,7 +22,7 @@ public void TestConstructor__PrefixNull() { ArgumentNullException e = Assert.Throws(delegate { - new ModLogger(null, innerLogger); + new PrefixLogger(null, innerLogger); }); Assert.Equal("prefix", e.ParamName); @@ -33,7 +33,7 @@ public void TestConstructor__PrefixBlank() { ArgumentNullException e = Assert.Throws(delegate { - new ModLogger("", innerLogger); + new PrefixLogger("", innerLogger); }); Assert.Equal("prefix", e.ParamName); @@ -44,7 +44,7 @@ public void TestConstructor__LoggerNull() { ArgumentNullException e = Assert.Throws(delegate { - new ModLogger("blah", null); + new PrefixLogger("blah", null); }); Assert.Equal("logger", e.ParamName); diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 12379fd6..17d1b48d 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -116,7 +116,7 @@ - + From 5711994c101b86891f9403a5c08c47492665810d Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 5 May 2019 13:55:42 -0700 Subject: [PATCH 11/67] Unnecessary using --- ModuleManager/Progress/PatchProgress.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ModuleManager/Progress/PatchProgress.cs b/ModuleManager/Progress/PatchProgress.cs index b1d92721..a1d53ed3 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 { From 3650d6927de303a7be7b27fdf4d0a144ee496737 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 5 May 2019 13:58:08 -0700 Subject: [PATCH 12/67] Use better assertion --- ModuleManagerTests/Patches/PatchCompilerTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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") { From 5f7071d88369e69beb0e2fc5a74d1a8d3305c159 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 5 May 2019 14:06:28 -0700 Subject: [PATCH 13/67] Mark disposables as sealed Microsoft seems to recommend this --- ModuleManager/Collections/MessageQueue.cs | 2 +- ModuleManager/Logging/StreamLogger.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/Logging/StreamLogger.cs b/ModuleManager/Logging/StreamLogger.cs index bfbb1053..74115d3e 100644 --- a/ModuleManager/Logging/StreamLogger.cs +++ b/ModuleManager/Logging/StreamLogger.cs @@ -3,7 +3,7 @@ namespace ModuleManager.Logging { - public class StreamLogger : IBasicLogger, IDisposable + public sealed class StreamLogger : IBasicLogger, IDisposable { private readonly Stream stream; private readonly StreamWriter streamWriter; From 28bd925fbe75663c34b67d0bad6c012e01135602 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 5 May 2019 14:06:37 -0700 Subject: [PATCH 14/67] Another unnecessary using --- ModuleManager/ModListGenerator.cs | 1 - 1 file changed, 1 deletion(-) 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; From 5e5314e2d73e4ec237684793ec8975c2a2bc84ca Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 5 May 2019 22:29:35 -0700 Subject: [PATCH 15/67] Use custom code to lookup file by url Should be less gc-happy and potentially faster, though this step rarely takes any signicant amount of time. Allows extensions to be specified optionally. Doesn't support /Parts or /Internals but probably a bunch of assumptions in ModuleManager about that too. --- ModuleManager/Extensions/UrlDirExtensions.cs | 43 +++++++++ ModuleManager/MMPatchLoader.cs | 2 +- ModuleManager/ModuleManager.csproj | 1 + .../Extensions/UrlDirExtensionsTest.cs | 88 +++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 5 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 ModuleManager/Extensions/UrlDirExtensions.cs create mode 100644 ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs 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/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 56909ece..fffd5e3a 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -524,7 +524,7 @@ 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) { databaseConfigs.Add(new ProtoUrlConfig(parent, node.nodes[0])); diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 6739e50d..c69f36b6 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -31,6 +31,7 @@ False + 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/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 17d1b48d..7e97430e 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -78,6 +78,7 @@ + From ae0d7a7c50df234e141909eedb2ceb6c5de6f3d2 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 5 May 2019 22:48:22 -0700 Subject: [PATCH 16/67] Put extension on cache URLs Makes trailing spaces not break things Resolves #121 --- ModuleManager/Extensions/UrlFileExtensions.cs | 12 +++++++ ModuleManager/MMPatchLoader.cs | 32 +++++++++++-------- ModuleManager/ModuleManager.cs | 5 +-- ModuleManager/ModuleManager.csproj | 1 + ModuleManager/Progress/PatchProgress.cs | 4 +-- .../Extensions/UrlFileExtensionsTest.cs | 17 ++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 7 files changed, 54 insertions(+), 18 deletions(-) create mode 100644 ModuleManager/Extensions/UrlFileExtensions.cs create mode 100644 ModuleManagerTests/Extensions/UrlFileExtensionsTest.cs diff --git a/ModuleManager/Extensions/UrlFileExtensions.cs b/ModuleManager/Extensions/UrlFileExtensions.cs new file mode 100644 index 00000000..6fe1cbe9 --- /dev/null +++ b/ModuleManager/Extensions/UrlFileExtensions.cs @@ -0,0 +1,12 @@ +using System; + +namespace ModuleManager.Extensions +{ + public static class UrlFileExtensions + { + public static string GetUrlWithExtension(this UrlDir.UrlFile urlFile) + { + return $"{urlFile.url}.{urlFile.fileExtension}"; + } + } +} diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index fffd5e3a..b7e28179 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -292,8 +292,9 @@ private bool IsCacheUpToDate() 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 @@ -301,13 +302,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); } } @@ -370,13 +371,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; @@ -384,18 +386,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) @@ -430,19 +433,20 @@ private void CreateCache(IEnumerable databaseConfigs, int patch foreach (IProtoUrlConfig urlConfig in databaseConfigs) { ConfigNode node = cache.AddNode("UrlConfig"); - node.AddValue("parentUrl", urlConfig.UrlFile.url); + node.AddValue("parentUrl", urlConfig.UrlFile.GetUrlWithExtension()); node.AddNode(urlConfig.Node); } 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); } } diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 50072d91..75b4f6ec 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -9,6 +9,7 @@ using UnityEngine; using Debug = UnityEngine.Debug; using ModuleManager.Cats; +using ModuleManager.Extensions; using ModuleManager.Logging; namespace ModuleManager @@ -472,8 +473,8 @@ public static void OutputAllConfigs() Directory.CreateDirectory(dirPath); } - Log("Exporting " + currentPath + urlFile.name + "." + urlFile.fileExtension); - string filePath = dirPath + urlFile.name + "." + urlFile.fileExtension; + Log("Exporting " + currentPath + urlFile.GetUrlWithExtension()); + string filePath = dirPath + urlFile.GetUrlWithExtension(); foreach (UrlDir.UrlConfig urlConfig in urlFile.configs) { try diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index c69f36b6..3c4b0366 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -32,6 +32,7 @@ + diff --git a/ModuleManager/Progress/PatchProgress.cs b/ModuleManager/Progress/PatchProgress.cs index a1d53ed3..3cebf994 100644 --- a/ModuleManager/Progress/PatchProgress.cs +++ b/ModuleManager/Progress/PatchProgress.cs @@ -139,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); @@ -151,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/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/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 7e97430e..37a2edaf 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -79,6 +79,7 @@ + From 69ab085da68e4ed3c80f444f0a04bfa65e6171f5 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 27 May 2019 14:35:04 -0700 Subject: [PATCH 17/67] Alphabetize .csproj files --- ModuleManager/ModuleManager.csproj | 22 +++++++-------- ModuleManagerTests/ModuleManagerTests.csproj | 28 ++++++++++---------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 3c4b0366..ac7665b4 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -31,9 +31,6 @@ False - - - @@ -41,6 +38,7 @@ + @@ -48,22 +46,24 @@ + + - + + + - - + - @@ -73,7 +73,11 @@ + + + + @@ -83,10 +87,6 @@ - - - - diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 37a2edaf..49e52d41 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -75,7 +75,15 @@ + + + + + + + + @@ -84,25 +92,25 @@ - - - + + + + + + - - - @@ -111,14 +119,6 @@ - - - - - - - - From e1bed9419ca20a15e04ba24e8bfe7e69563259e4 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 28 May 2019 21:58:27 -0700 Subject: [PATCH 18/67] improve file sha generator * Actually dispose the sha when we're done * Use a better way of converting bytes to hex strings (hopefully less gc) * Test byte array to hex string conversion --- .../Extensions/ByteArrayExtensions.cs | 29 +++++++++++++++++++ ModuleManager/ModuleManager.csproj | 1 + ModuleManager/Utils/FileUtils.cs | 19 +++++------- .../Extensions/ByteArrayExtensionsTest.cs | 28 ++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 5 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 ModuleManager/Extensions/ByteArrayExtensions.cs create mode 100644 ModuleManagerTests/Extensions/ByteArrayExtensionsTest.cs 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/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index ac7665b4..04a043ae 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -41,6 +41,7 @@ + diff --git a/ModuleManager/Utils/FileUtils.cs b/ModuleManager/Utils/FileUtils.cs index 73cd8dba..e069e14c 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,17 @@ 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(); - 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) + using (SHA256 sha = SHA256.Create()) { - hashedValue += String.Format("{0,2:x2}", b); + using (FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read)) + { + data = sha.ComputeHash(fs); + } } - return hashedValue; + return data.ToHex(); } } } 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/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 49e52d41..9824ace1 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -81,6 +81,7 @@ + From a27cb3df58b5b2218cfff2b0027959ce7332864f Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 30 May 2019 22:18:20 -0700 Subject: [PATCH 19/67] Convert UrlBuilder cfg test to theory allows more inline data to be added --- TestUtils/UrlBuilder.cs | 1 + TestUtilsTests/UrlBuilderTest.cs | 23 +++++++++++------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/TestUtils/UrlBuilder.cs b/TestUtils/UrlBuilder.cs index 2fff4a47..9e7f1393 100644 --- a/TestUtils/UrlBuilder.cs +++ b/TestUtils/UrlBuilder.cs @@ -118,6 +118,7 @@ public static UrlDir.UrlFile CreateFile(string path, UrlDir parent = null) bool cfg = false; string newName = name; + // KSP tries to load .cfg files so need to have special handling if (extension == "cfg") { cfg = true; diff --git a/TestUtilsTests/UrlBuilderTest.cs b/TestUtilsTests/UrlBuilderTest.cs index 956350dd..8a685779 100644 --- a/TestUtilsTests/UrlBuilderTest.cs +++ b/TestUtilsTests/UrlBuilderTest.cs @@ -209,23 +209,22 @@ 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("cfg", UrlDir.FileType.Config)] + [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(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] From 3d8594dd855febd0024a0ac876ad7e8ce03d2848 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 30 May 2019 22:37:47 -0700 Subject: [PATCH 20/67] Set FileType based on common types Makes checking against them easier --- TestUtils/UrlBuilder.cs | 35 +++++++++++++++++++++++++++++++- TestUtilsTests/UrlBuilderTest.cs | 17 +++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/TestUtils/UrlBuilder.cs b/TestUtils/UrlBuilder.cs index 9e7f1393..e8e60bd5 100644 --- a/TestUtils/UrlBuilder.cs +++ b/TestUtils/UrlBuilder.cs @@ -118,6 +118,38 @@ 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") { @@ -127,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/UrlBuilderTest.cs b/TestUtilsTests/UrlBuilderTest.cs index 8a685779..38a788f0 100644 --- a/TestUtilsTests/UrlBuilderTest.cs +++ b/TestUtilsTests/UrlBuilderTest.cs @@ -209,14 +209,29 @@ public void TestCreateFile__Parent() Assert.Contains(file, root.AllFiles); } + [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.UrlFile file = UrlBuilder.CreateFile("someFile." + extension); Assert.Equal("someFile", file.name); - Assert.Equal("cfg", file.fileExtension); + Assert.Equal(extension, file.fileExtension); Assert.Equal(fileType, file.fileType); UrlDir root = file.parent; From eb3db605b24f56af6e4da5a17a8dbf3b9795292e Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 31 May 2019 00:33:33 -0700 Subject: [PATCH 21/67] unnecessary using --- ModuleManagerTests/Logging/StreamLoggerTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ModuleManagerTests/Logging/StreamLoggerTest.cs b/ModuleManagerTests/Logging/StreamLoggerTest.cs index 61535387..734a3521 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; From 9795e3656b9e73a7ed1622d2b832002ad9b70d38 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 31 May 2019 00:40:53 -0700 Subject: [PATCH 22/67] make StreamLoggerTest.TestLog work on \n platforms line breaks are shorter which causes an extra null character to exist at the end of the string, meaning that the default Trim() wasn't removing that and the newline --- ModuleManagerTests/Logging/StreamLoggerTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManagerTests/Logging/StreamLoggerTest.cs b/ModuleManagerTests/Logging/StreamLoggerTest.cs index 734a3521..bc613038 100644 --- a/ModuleManagerTests/Logging/StreamLoggerTest.cs +++ b/ModuleManagerTests/Logging/StreamLoggerTest.cs @@ -69,7 +69,7 @@ public void TestLog() { using (StreamReader reader = new StreamReader(stream)) { - string result = reader.ReadToEnd().Trim(); + string result = reader.ReadToEnd().Trim('\r', '\n', '\0'); Assert.Equal("[OMG wtf] bbq", result); } } From 9311cfb45507323f3f56f315dbda61f5034a31b0 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Wed, 26 Jun 2019 22:55:57 -0700 Subject: [PATCH 23/67] Improve TestConfigNodeTest Strictly assert the structure of the node --- TestUtilsTests/TestConfigNodeTest.cs | 45 +++++++++++++++++----------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/TestUtilsTests/TestConfigNodeTest.cs b/TestUtilsTests/TestConfigNodeTest.cs index 6b00b4de..0858ece6 100644 --- a/TestUtilsTests/TestConfigNodeTest.cs +++ b/TestUtilsTests/TestConfigNodeTest.cs @@ -34,31 +34,40 @@ 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(5, 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]); + 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); } } } From ee5995f6196cfe58f2f646b1f8f8f847b32b9f0d Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Wed, 26 Jun 2019 22:58:26 -0700 Subject: [PATCH 24/67] Remove redundant parent class --- TestUtils/TestConfigNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TestUtils/TestConfigNode.cs b/TestUtils/TestConfigNode.cs index a954c107..6b71a3ae 100644 --- a/TestUtils/TestConfigNode.cs +++ b/TestUtils/TestConfigNode.cs @@ -9,7 +9,7 @@ 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(Value value) => values.Add(value); public void Add(string name, ConfigNode node) => AddNode(name, node); public void Add(ConfigNode node) => AddNode(node); From 3e4a9f998ecd40118e07bce7c655da6fb15a8442 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Wed, 26 Jun 2019 23:03:27 -0700 Subject: [PATCH 25/67] Fix TestConfigNode's handling of escaped chars KSP removes these automatically, so we want to avoid that by constructing the Value ourselves --- TestUtils/TestConfigNode.cs | 2 +- TestUtilsTests/TestConfigNodeTest.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/TestUtils/TestConfigNode.cs b/TestUtils/TestConfigNode.cs index 6b71a3ae..694fc55e 100644 --- a/TestUtils/TestConfigNode.cs +++ b/TestUtils/TestConfigNode.cs @@ -8,7 +8,7 @@ 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(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/TestUtilsTests/TestConfigNodeTest.cs b/TestUtilsTests/TestConfigNodeTest.cs index 0858ece6..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,12 +35,13 @@ public void TestTestConfigNode() }, }; - Assert.Equal(5, node.values.Count); + 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"); From 8e5c29ad0983157c88116085d4745e125941a416 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Wed, 26 Jun 2019 23:18:01 -0700 Subject: [PATCH 26/67] Clean up test a bit Use AssertValue method Use AssertEmpty rather than asserting count zero --- .../Extensions/ConfigNodeExtensionsTest.cs | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs index 7e4030ad..fcf9533e 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] @@ -109,13 +105,11 @@ 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]; @@ -123,26 +117,24 @@ public void TestDeepCopy() Assert.Equal("INNER_NODE_1", innerNode1.name); Assert.Equal(1, 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.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 +265,11 @@ XX INNER_NODE node.PrettyPrint(ref sb, "XX"); Assert.Equal(expected, sb.ToString()); } + + private void AssertValue(string name, string value, ConfigNode.Value nodeValue) + { + Assert.Equal(name, nodeValue.name); + Assert.Equal(value, nodeValue.value); + } } } From 0feef88557d938ee16b889ba5685db307be2c798 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Wed, 26 Jun 2019 23:26:15 -0700 Subject: [PATCH 27/67] Fix DeepCopy handling of escaped values KSP removes these in AddValue, we can circumvent that by constructing the Value ourselves --- ModuleManager/Extensions/ConfigNodeExtensions.cs | 2 +- ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ModuleManager/Extensions/ConfigNodeExtensions.cs b/ModuleManager/Extensions/ConfigNodeExtensions.cs index 6940048a..79c192b5 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.values.Add(new ConfigNode.Value(value.name, value.value)); foreach (ConfigNode node in from.nodes) { ConfigNode newNode = DeepCopy(node); diff --git a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs index fcf9533e..d771bf0d 100644 --- a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs @@ -89,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") @@ -115,9 +116,11 @@ public void TestDeepCopy() 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]); 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); From 96201a72d6c1037c95dc7f98da8177c25ee867f0 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Thu, 27 Jun 2019 00:23:57 -0700 Subject: [PATCH 28/67] Fix ModifyNode handling of escaped characters new AddValueSafe method to handle this use safe ShallowCopyFrom rather than ConfigNode's CopyTo as recursion is not necessary and it handles escaped characters correctly --- .../Extensions/ConfigNodeExtensions.cs | 7 +- ModuleManager/MMPatchLoader.cs | 26 +++---- .../Extensions/ConfigNodeExtensionsTest.cs | 15 ++++ ModuleManagerTests/MMPatchLoaderTest.cs | 78 +++++++++++++++++++ 4 files changed, 112 insertions(+), 14 deletions(-) diff --git a/ModuleManager/Extensions/ConfigNodeExtensions.cs b/ModuleManager/Extensions/ConfigNodeExtensions.cs index 79c192b5..a54100ba 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.values.Add(new ConfigNode.Value(value.name, value.value)); + to.AddValueSafe(value.name, value.value); foreach (ConfigNode node in from.nodes) { ConfigNode newNode = DeepCopy(node); @@ -68,5 +68,10 @@ 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)); + } } } diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index b7e28179..367805db 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -725,7 +725,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 { @@ -771,7 +771,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 @@ -847,7 +847,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 { @@ -1010,8 +1010,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 { @@ -1023,7 +1023,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); @@ -1040,7 +1040,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); @@ -1066,8 +1066,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: @@ -1698,13 +1698,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. diff --git a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs index d771bf0d..cb0c5e1f 100644 --- a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs @@ -269,6 +269,21 @@ XX INNER_NODE 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]); + } + private void AssertValue(string name, string value, ConfigNode.Value nodeValue) { Assert.Equal(name, nodeValue.name); diff --git a/ModuleManagerTests/MMPatchLoaderTest.cs b/ModuleManagerTests/MMPatchLoaderTest.cs index 29682448..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()); From f2fba86ce98bcba5ab13b7d3ce6c6fd7e51e74ea Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Thu, 27 Jun 2019 00:40:17 -0700 Subject: [PATCH 29/67] Ensure tabs and newlines don't break cache Explicitly escape \n and \t which the localizer unescapes when game database is initially loaded --- ModuleManager/MMPatchLoader.cs | 37 +++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 367805db..243aa0bb 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -430,11 +430,30 @@ private void CreateCache(IEnumerable databaseConfigs, int patch cache.AddValue("patchedNodeCount", patchedNodeCount.ToString()); + // Undoes escaping done by the localizer + void FixValuesRecursive(ConfigNode theNode) + { + foreach (ConfigNode subNode in theNode.nodes) + { + FixValuesRecursive(subNode); + } + + foreach (ConfigNode.Value value in theNode.values) + { + value.value = value.value.Replace("\n", "\\n"); + value.value = value.value.Replace("\t", "\\t"); + } + } + foreach (IProtoUrlConfig urlConfig in databaseConfigs) { ConfigNode node = cache.AddNode("UrlConfig"); node.AddValue("parentUrl", urlConfig.UrlFile.GetUrlWithExtension()); - node.AddNode(urlConfig.Node); + + ConfigNode urlNode = urlConfig.Node.DeepCopy(); + FixValuesRecursive(urlNode); + + node.AddNode(urlNode); } foreach (var file in GameDatabase.Instance.root.AllConfigFiles) @@ -524,6 +543,21 @@ private IEnumerable LoadCache() List databaseConfigs = new List(cache.nodes.Count); + // Evaluate escape sequences + void FixValuesRecursive(ConfigNode theNode) + { + foreach (ConfigNode subNode in theNode.nodes) + { + FixValuesRecursive(subNode); + } + + foreach (ConfigNode.Value value in theNode.values) + { + value.value = value.value.Replace("\\n", "\n"); + value.value = value.value.Replace("\\t", "\t"); + } + } + foreach (ConfigNode node in cache.nodes) { string parentUrl = node.GetValue("parentUrl"); @@ -531,6 +565,7 @@ private IEnumerable LoadCache() UrlDir.UrlFile parent = gameDataDir.Find(parentUrl); if (parent != null) { + FixValuesRecursive(node.nodes[0]); databaseConfigs.Add(new ProtoUrlConfig(parent, node.nodes[0])); } else From 3d85ddca5fcf62e81e21904fcb04364039081bbf Mon Sep 17 00:00:00 2001 From: Sarbian Date: Wed, 7 Aug 2019 01:03:23 +0200 Subject: [PATCH 30/67] v4.0.3 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index 43b2dc37..26449b74 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.0.3")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From aa6ff2e35e6dc06c7b5ea3f2941d2d621fd90791 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Mon, 12 Aug 2019 21:08:53 -0700 Subject: [PATCH 31/67] Extract escaping/unescaping of node values Allows it to be tested --- .../Extensions/ConfigNodeExtensions.cs | 30 ++++++++++++ ModuleManager/MMPatchLoader.cs | 34 +------------- .../Extensions/ConfigNodeExtensionsTest.cs | 46 +++++++++++++++++++ 3 files changed, 78 insertions(+), 32 deletions(-) diff --git a/ModuleManager/Extensions/ConfigNodeExtensions.cs b/ModuleManager/Extensions/ConfigNodeExtensions.cs index a54100ba..9f552566 100644 --- a/ModuleManager/Extensions/ConfigNodeExtensions.cs +++ b/ModuleManager/Extensions/ConfigNodeExtensions.cs @@ -73,5 +73,35 @@ 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/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 243aa0bb..41e28bd3 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -430,28 +430,13 @@ private void CreateCache(IEnumerable databaseConfigs, int patch cache.AddValue("patchedNodeCount", patchedNodeCount.ToString()); - // Undoes escaping done by the localizer - void FixValuesRecursive(ConfigNode theNode) - { - foreach (ConfigNode subNode in theNode.nodes) - { - FixValuesRecursive(subNode); - } - - foreach (ConfigNode.Value value in theNode.values) - { - value.value = value.value.Replace("\n", "\\n"); - value.value = value.value.Replace("\t", "\\t"); - } - } - foreach (IProtoUrlConfig urlConfig in databaseConfigs) { ConfigNode node = cache.AddNode("UrlConfig"); node.AddValue("parentUrl", urlConfig.UrlFile.GetUrlWithExtension()); ConfigNode urlNode = urlConfig.Node.DeepCopy(); - FixValuesRecursive(urlNode); + urlNode.EscapeValuesRecursive(); node.AddNode(urlNode); } @@ -543,21 +528,6 @@ private IEnumerable LoadCache() List databaseConfigs = new List(cache.nodes.Count); - // Evaluate escape sequences - void FixValuesRecursive(ConfigNode theNode) - { - foreach (ConfigNode subNode in theNode.nodes) - { - FixValuesRecursive(subNode); - } - - foreach (ConfigNode.Value value in theNode.values) - { - value.value = value.value.Replace("\\n", "\n"); - value.value = value.value.Replace("\\t", "\t"); - } - } - foreach (ConfigNode node in cache.nodes) { string parentUrl = node.GetValue("parentUrl"); @@ -565,7 +535,7 @@ void FixValuesRecursive(ConfigNode theNode) UrlDir.UrlFile parent = gameDataDir.Find(parentUrl); if (parent != null) { - FixValuesRecursive(node.nodes[0]); + node.nodes[0].UnescapeValuesRecursive(); databaseConfigs.Add(new ProtoUrlConfig(parent, node.nodes[0])); } else diff --git a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs index cb0c5e1f..471be9cb 100644 --- a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs @@ -284,6 +284,52 @@ public void TestAddValueSafe() 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); From 4201d5fd486810d249327e76482a617181a03cc7 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Mon, 12 Aug 2019 21:09:47 -0700 Subject: [PATCH 32/67] Fix dump database to files * Fix paths getting duplicated resulting in files not being written * Escape node values before writing --- ModuleManager/Extensions/UrlFileExtensions.cs | 4 ++ ModuleManager/ModuleManager.cs | 65 ++++++++++--------- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/ModuleManager/Extensions/UrlFileExtensions.cs b/ModuleManager/Extensions/UrlFileExtensions.cs index 6fe1cbe9..3d203b24 100644 --- a/ModuleManager/Extensions/UrlFileExtensions.cs +++ b/ModuleManager/Extensions/UrlFileExtensions.cs @@ -8,5 +8,9 @@ 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/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 75b4f6ec..18425f3f 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -430,7 +430,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); @@ -451,51 +451,52 @@ 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 + 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)) { - if (urlFile.fileType == UrlDir.FileType.Config) + foreach (UrlDir.UrlConfig urlConfig in urlFile.configs) { - string dirPath = path + currentPath; - if (!Directory.Exists(dirPath)) + try { - Directory.CreateDirectory(dirPath); - } + if (first) first = false; + else writer.Write("\n"); - Log("Exporting " + currentPath + urlFile.GetUrlWithExtension()); - string filePath = dirPath + urlFile.GetUrlWithExtension(); - foreach (UrlDir.UrlConfig urlConfig in urlFile.configs) + ConfigNode copy = urlConfig.config.DeepCopy(); + copy.EscapeValuesRecursive(); + writer.Write(copy.ToString()); + } + catch (Exception e) { - try - { - File.AppendAllText(filePath, urlConfig.config.ToString()); - } - catch (Exception e) - { - Log("Exception while trying to write the file " + filePath + "\n" + 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); From cc11776ad6576ae756ae1c03f1903734a191c735 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Mon, 12 Aug 2019 21:31:23 -0700 Subject: [PATCH 33/67] Fix reload dialog showing 100% when it shouldn't PartLoader reports 100% done before it has started loading --- ModuleManager/ModuleManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 18425f3f..b10c977c 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -344,7 +344,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; From a79618e0d1c040ef2ec572e06acc1709a3284716 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Wed, 16 Oct 2019 22:37:47 +0200 Subject: [PATCH 34/67] KSP 1.8 ! With less cat bugs --- ModuleManager/Cats/CatManager.cs | 2 +- ModuleManager/Cats/CatMover.cs | 12 ++++++++++- ModuleManager/ModuleManager.cs | 2 +- ModuleManager/ModuleManager.csproj | 34 ++++++++++++++++++++++++------ 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/ModuleManager/Cats/CatManager.cs b/ModuleManager/Cats/CatManager.cs index 0567de54..744a0ca8 100644 --- a/ModuleManager/Cats/CatManager.cs +++ b/ModuleManager/Cats/CatManager.cs @@ -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..2e37ac9a 100644 --- a/ModuleManager/Cats/CatMover.cs +++ b/ModuleManager/Cats/CatMover.cs @@ -21,6 +21,8 @@ public class CatMover : MonoBehaviour private const float time = 5; private const float trailTime = time / 4; + private bool clearTrail = false; + // Use this for initialization void Start() { @@ -36,6 +38,7 @@ 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; } void Update() @@ -50,7 +53,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 +65,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/ModuleManager.cs b/ModuleManager/ModuleManager.cs index b10c977c..cfc88979 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -81,7 +81,7 @@ internal void Awake() totalTime.Start(); - // Allow loading the background in the laoding screen + // Allow loading the background in the loading screen Application.runInBackground = true; QualitySettings.vSyncCount = 0; Application.targetFrameRate = -1; diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 04a043ae..8110ed4f 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 + True @@ -21,6 +22,7 @@ 4 False default + false none @@ -29,6 +31,7 @@ prompt 4 False + false @@ -115,16 +118,35 @@ False False + + False + - + + False - False + + + False + + + False + + + False + + + False + + + False False - C:\Games\KSPSteamController\KSPSteamCtrlr\KSPUnity-Steam-Symlinks\UnityEngine.UI.dll - False + + + False From 5aa82999b419e5c05ab7b8076b3034cb8e861b6d Mon Sep 17 00:00:00 2001 From: Sarbian Date: Wed, 16 Oct 2019 22:38:00 +0200 Subject: [PATCH 35/67] v4.1.0 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index 26449b74..c9549df2 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.3")] +[assembly: AssemblyVersion("4.1.0")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From ff637237458fbd372ca64a8da4d105ae0c0d8719 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sun, 27 Oct 2019 15:36:21 +0100 Subject: [PATCH 36/67] v4.1.1 - Outdated Firespitter warning --- ModuleManager/ModuleManager.cs | 12 ++++++++++++ ModuleManager/Properties/AssemblyInfo.cs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index cfc88979..30db5f59 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -184,6 +184,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) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index c9549df2..b9e12e75 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.1.0")] +[assembly: AssemblyVersion("4.1.1")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From d172fc66ff56ffdc665ea47a3a143df8f17f441c Mon Sep 17 00:00:00 2001 From: Sarbian Date: Fri, 15 Nov 2019 22:32:36 +0100 Subject: [PATCH 37/67] Add an Exception interceptor to catch ReflectionTypeLoadException and properly blame broken DLLs --- .../ExceptionIntercept/InterceptLogHandler.cs | 56 +++++++++++++++++++ ModuleManager/ModuleManager.cs | 5 ++ ModuleManager/ModuleManager.csproj | 2 + 3 files changed, 63 insertions(+) create mode 100644 ModuleManager/ExceptionIntercept/InterceptLogHandler.cs diff --git a/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs b/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs new file mode 100644 index 00000000..a1af8d5c --- /dev/null +++ b/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs @@ -0,0 +1,56 @@ +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() + { + baseLogHandler = Debug.unityLogger.logHandler; + Debug.unityLogger.logHandler = this; + 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) + { + ModuleManager.Log("Intercepted a ReflectionTypeLoadException. List of broken DLLs:"); + var assemblies = ex.Types.Where(x => x != null).Select(x => x.Assembly).Distinct(); + foreach (Assembly assembly in assemblies) + { + if (Warnings == "") + { + Warnings = "ModuleManager mod(s) DLL that are not compatible with this version of KSP\n"; + } + if (!brokenAssemblies.Contains(assembly)) + { + brokenAssemblies.Add(assembly); + Warnings += assembly.GetName().Name + " " + assembly.GetName().Version + " " + assembly.Location.Remove(0, gamePathLength) + "\n"; + } + ModuleManager.Log(assembly.GetName().Name + " " + assembly.GetName().Version + " " + assembly.Location.Remove(0, gamePathLength)); + } + } + baseLogHandler.LogException(exception, context); + } + } +} diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 30db5f59..4b8689ba 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -11,6 +11,7 @@ using ModuleManager.Cats; using ModuleManager.Extensions; using ModuleManager.Logging; +using ModuleManager.UnityLogHandle; namespace ModuleManager { @@ -40,6 +41,8 @@ public class ModuleManager : MonoBehaviour private MMPatchRunner patchRunner; + private InterceptLogHandler interceptLogHandler; + #endregion state private static bool loadedInScene; @@ -196,6 +199,7 @@ private void Start() } } } + interceptLogHandler = new InterceptLogHandler(); } private TextMeshProUGUI CreateTextObject(Canvas canvas, string name) @@ -289,6 +293,7 @@ internal void Update() { if (warning) { + warning.text = InterceptLogHandler.Warnings; h = warning.text.Length > 0 ? warning.textBounds.size.y : 0; offsetY = offsetY + h; warning.rectTransform.localPosition = new Vector3(0, offsetY); diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 8110ed4f..d256b235 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -108,6 +108,7 @@ + @@ -194,6 +195,7 @@ + sh -c "TARGET_PATH='$(TargetPath)' TARGET_DIR='$(TargetDir)' TARGET_NAME='$(TargetName)' sh '$(ProjectDir)/copy_build.sh'" From 1adea49112048cec35c3a971042ee5517ae0d416 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Fri, 15 Nov 2019 22:32:50 +0100 Subject: [PATCH 38/67] v4.1.2 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index b9e12e75..58db010d 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.1.1")] +[assembly: AssemblyVersion("4.1.2")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From ae4d27ace9ef61603a813d368609c9d7eba4d378 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sat, 30 Nov 2019 15:46:59 +0100 Subject: [PATCH 39/67] Cleanup the InterceptLogHandler, remove double logging and avoid any risk of throwing more --- .../ExceptionIntercept/InterceptLogHandler.cs | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs b/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs index a1af8d5c..6cd3dcd0 100644 --- a/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs +++ b/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs @@ -34,23 +34,32 @@ public void LogException(Exception exception, Object context) if (exception is ReflectionTypeLoadException ex) { - ModuleManager.Log("Intercepted a ReflectionTypeLoadException. List of broken DLLs:"); - var assemblies = ex.Types.Where(x => x != null).Select(x => x.Assembly).Distinct(); - foreach (Assembly assembly in assemblies) + string message = "Intercepted a ReflectionTypeLoadException. List of broken DLLs:\n"; + try { - if (Warnings == "") + var assemblies = ex.Types.Where(x => x != null).Select(x => x.Assembly).Distinct(); + foreach (Assembly assembly in assemblies) { - Warnings = "ModuleManager mod(s) DLL that are not compatible with this version of KSP\n"; + 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; } - if (!brokenAssemblies.Contains(assembly)) - { - brokenAssemblies.Add(assembly); - Warnings += assembly.GetName().Name + " " + assembly.GetName().Version + " " + assembly.Location.Remove(0, gamePathLength) + "\n"; - } - ModuleManager.Log(assembly.GetName().Name + " " + assembly.GetName().Version + " " + assembly.Location.Remove(0, gamePathLength)); } + catch (Exception e) + { + message += "Exception " + e.GetType().Name + " while handling the exception..."; + } + ModuleManager.Log(message); } - baseLogHandler.LogException(exception, context); } } } From 51cdc23933193823a45e711174bd4a9591593fd3 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sat, 30 Nov 2019 15:47:17 +0100 Subject: [PATCH 40/67] v4.1.3 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index 58db010d..cf5d1a0c 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.1.2")] +[assembly: AssemblyVersion("4.1.3")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 4b7319c9da475e6d7598723151264b9de7112e06 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sat, 30 Nov 2019 15:54:06 +0100 Subject: [PATCH 41/67] Start the Handler earlier --- ModuleManager/ModuleManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 4b8689ba..ac452b97 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -83,6 +83,8 @@ internal void Awake() } totalTime.Start(); + + interceptLogHandler = new InterceptLogHandler(); // Allow loading the background in the loading screen Application.runInBackground = true; @@ -199,7 +201,6 @@ private void Start() } } } - interceptLogHandler = new InterceptLogHandler(); } private TextMeshProUGUI CreateTextObject(Canvas canvas, string name) From 16ca42a586661ba24be3086dfa73e75386752081 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 8 Nov 2019 22:20:00 -0800 Subject: [PATCH 42/67] Put both projects on .NET 4.7.1 --- ModuleManager/ModuleManager.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index d256b235..d58d8d14 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -9,7 +9,7 @@ Library ModuleManager ModuleManager - v4.7 + v4.7.1 From 8be22e017db270080393ad6e4cc7e0846b5816d1 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 8 Nov 2019 22:20:11 -0800 Subject: [PATCH 43/67] Add UnityEngine.CoreModule to test project --- ModuleManagerTests/ModuleManagerTests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 9824ace1..b4fafe96 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -60,6 +60,7 @@ + ..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll True From 35b02dff99eea2d83c6315c264bc46534fa9b9fc Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 8 Nov 2019 22:21:45 -0800 Subject: [PATCH 44/67] Use multi-argument Path.Combine now available in .NET 4 --- ModuleManager/FilePathRepository.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ModuleManager/FilePathRepository.cs b/ModuleManager/FilePathRepository.cs index 4ba34321..db15419e 100644 --- a/ModuleManager/FilePathRepository.cs +++ b/ModuleManager/FilePathRepository.cs @@ -5,7 +5,7 @@ namespace ModuleManager { internal static class FilePathRepository { - internal static readonly string cachePath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigCache"); + internal static readonly string cachePath = Path.Combine(KSPUtil.ApplicationRootPath, "GameData", "ModuleManager.ConfigCache"); internal static readonly string techTreeFile = Path.Combine("GameData", "ModuleManager.TechTree"); internal static readonly string techTreePath = Path.Combine(KSPUtil.ApplicationRootPath, techTreeFile); @@ -16,9 +16,9 @@ internal static class FilePathRepository internal static readonly string partDatabasePath = Path.Combine(KSPUtil.ApplicationRootPath, "PartDatabase.cfg"); - internal static readonly string shaPath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigSHA"); + internal static readonly string shaPath = Path.Combine(KSPUtil.ApplicationRootPath, "GameData", "ModuleManager.ConfigSHA"); - internal static readonly string logsDirPath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "Logs"), "ModuleManager"); + internal static readonly string logsDirPath = Path.Combine(KSPUtil.ApplicationRootPath, "Logs", "ModuleManager"); internal static readonly string logPath = Path.Combine(logsDirPath, "ModuleManager.log"); internal static readonly string patchLogPath = Path.Combine(logsDirPath, "MMPatch.log"); } From 51290e18ab97ab990bffbe3f3886310d500530d8 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Sat, 9 Nov 2019 21:12:39 -0800 Subject: [PATCH 45/67] Adress a bunch of messages Delete unused stuff, make stuff readonly, simplify stuff, add suppressions to legitimate things --- ModuleManager/MMPatchLoader.cs | 20 +++++++------- ModuleManager/ModuleManager.cs | 48 ++++++++++++++-------------------- 2 files changed, 29 insertions(+), 39 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 41e28bd3..1cffe055 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -40,7 +40,7 @@ public class MMPatchLoader 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,12 +89,10 @@ public IEnumerable Run() QueueLogRunner logRunner = new QueueLogRunner(patchLogQueue); ITaskStatus loggingThreadStatus = BackgroundTask.Start(delegate { - 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!"); - } + 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)); @@ -284,8 +282,8 @@ 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(); @@ -570,7 +568,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. @@ -1401,7 +1399,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) diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index ac452b97..361cb540 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; @@ -23,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; @@ -62,7 +59,7 @@ public static void Log(String s) print("[ModuleManager] " + s); } - private Stopwatch totalTime = new Stopwatch(); + private readonly Stopwatch totalTime = new Stopwatch(); internal void Awake() { @@ -107,9 +104,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); @@ -168,7 +162,7 @@ internal void Awake() private TextMeshProUGUI errors; private TextMeshProUGUI warning; - + [SuppressMessage("Code Quality", "IDE0051", Justification = "Called by Unity")] private void Start() { if (nCats) @@ -296,7 +290,7 @@ internal void Update() { 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); } @@ -304,7 +298,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); } @@ -312,7 +306,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); } } @@ -470,7 +464,7 @@ public static void OutputAllConfigs() Log("Exception while cleaning the export dir\n" + unauthorizedAccessException); } - void WriteDirectoryRecursive(UrlDir currentDir, string dirPath) + static void WriteDirectoryRecursive(UrlDir currentDir, string dirPath) { if (currentDir.files.Count > 0) Directory.CreateDirectory(dirPath); @@ -483,24 +477,22 @@ void WriteDirectoryRecursive(UrlDir currentDir, string dirPath) bool first = true; - using (FileStream stream = new FileStream(filePath, FileMode.Create)) - using (StreamWriter writer = new StreamWriter(stream)) + using FileStream stream = new FileStream(filePath, FileMode.Create); + using StreamWriter writer = new StreamWriter(stream); + foreach (UrlDir.UrlConfig urlConfig in urlFile.configs) { - foreach (UrlDir.UrlConfig urlConfig in urlFile.configs) + try { - try - { - if (first) first = false; - else writer.Write("\n"); + if (first) first = false; + else writer.Write("\n"); - 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); - } + 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); } } } From a2fdef4c38b6aeb066b7f23c212e357eedeacb3d Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Sun, 5 Jul 2020 23:22:43 -0700 Subject: [PATCH 46/67] Don't overwrite other fatal messages --- ModuleManager/MMPatchRunner.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ModuleManager/MMPatchRunner.cs b/ModuleManager/MMPatchRunner.cs index 739ecd82..b812ceed 100644 --- a/ModuleManager/MMPatchRunner.cs +++ b/ModuleManager/MMPatchRunner.cs @@ -80,12 +80,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) From c4ad2c3dd5defcdd37154831be4fa7040d3d1616 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Sun, 5 Jul 2020 23:23:45 -0700 Subject: [PATCH 47/67] Fix :LAST when mod doesn't exist Per the original feature design (#96) it should still run --- ModuleManager/PatchList.cs | 13 +++++++++---- ModuleManagerTests/PatchListTest.cs | 16 +++++++++++----- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/ModuleManager/PatchList.cs b/ModuleManager/PatchList.cs index 9716de22..e9193f74 100644 --- a/ModuleManager/PatchList.cs +++ b/ModuleManager/PatchList.cs @@ -76,6 +76,7 @@ public ModPassCollection(IEnumerable modList) private readonly Pass finalPatches = new Pass(":FINAL"); private readonly ModPassCollection modPasses; + private readonly SortedDictionary lastPasses = new SortedDictionary(StringComparer.InvariantCultureIgnoreCase); public PatchList(IEnumerable modList, IEnumerable patches, IPatchProgress progress) { @@ -114,8 +115,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) { @@ -143,9 +148,9 @@ public IEnumerator GetEnumerator() 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; 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(); } } } From 8a95d3709f0dd651deb1fc8294007b847e562045 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Mon, 6 Jul 2020 22:55:39 -0700 Subject: [PATCH 48/67] PatchList handles sorting consistently eliminates private class that mostly just passed methods through --- ModuleManager/PatchList.cs | 46 ++++++++------------------------------ 1 file changed, 9 insertions(+), 37 deletions(-) diff --git a/ModuleManager/PatchList.cs b/ModuleManager/PatchList.cs index e9193f74..f97c8b49 100644 --- a/ModuleManager/PatchList.cs +++ b/ModuleManager/PatchList.cs @@ -37,53 +37,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) @@ -141,7 +113,7 @@ 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; @@ -162,7 +134,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"); } } } From 5168b5c743371838de869745081fda8849c86eb0 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Mon, 6 Jul 2020 23:52:39 -0700 Subject: [PATCH 49/67] Address some messages Do some dependency in jection on InterceptLogHandler, no need to hold onto a reference to it as Unity will Remove paramter checks in StreamLogger as StreamWriter does the same checks Ibut keep basic tests for them) --- ModuleManager/Cats/CatAnimator.cs | 4 +++- ModuleManager/Cats/CatManager.cs | 2 +- ModuleManager/Cats/CatMover.cs | 8 ++++--- ModuleManager/Cats/CatOrbiter.cs | 8 ++++--- ModuleManager/Collections/ImmutableStack.cs | 2 +- .../ExceptionIntercept/InterceptLogHandler.cs | 5 ++-- ModuleManager/Extensions/StringExtensions.cs | 2 +- ModuleManager/Fix16.cs | 2 ++ ModuleManager/Logging/StreamLogger.cs | 3 --- ModuleManager/Logging/UnityLogger.cs | 2 +- ModuleManager/MMPatchLoader.cs | 2 -- ModuleManager/MMPatchRunner.cs | 13 ++++------- ModuleManager/ModuleManager.cs | 6 ++--- ModuleManager/NeedsChecker.cs | 2 ++ ModuleManager/NodeMatcher.cs | 8 +++---- ModuleManager/PatchExtractor.cs | 2 ++ ModuleManager/PatchList.cs | 2 -- ModuleManager/Patches/PatchCompiler.cs | 23 ++++++------------- ModuleManager/PostPatchLoader.cs | 2 ++ ModuleManager/Progress/PatchProgress.cs | 2 +- ModuleManager/Threading/TaskStatus.cs | 22 ++++++++---------- ModuleManager/Threading/TaskStatusWrapper.cs | 2 +- ModuleManager/Utils/FileUtils.cs | 12 +++------- .../Collections/MessageQueueTest.cs | 2 +- .../Extensions/IBasicLoggerExtensionsTest.cs | 8 +------ .../Logging/PrefixLoggerTest.cs | 5 ++-- ModuleManagerTests/Logging/QueueLoggerTest.cs | 5 ++-- .../Logging/StreamLoggerTest.cs | 9 ++------ ModuleManagerTests/Logging/UnityLoggerTest.cs | 5 ++-- ModuleManagerTests/NeedsCheckerTest.cs | 11 +++------ ModuleManagerTests/PassTest.cs | 8 ------- ModuleManagerTests/PatchExtractorTest.cs | 6 ----- .../Progress/PatchProgressTest.cs | 6 ++--- 33 files changed, 73 insertions(+), 128 deletions(-) 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 744a0ca8..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() diff --git a/ModuleManager/Cats/CatMover.cs b/ModuleManager/Cats/CatMover.cs index 2e37ac9a..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 @@ -24,12 +24,13 @@ public class CatMover : MonoBehaviour 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); @@ -41,6 +42,7 @@ void Start() clearTrail = true; } + [SuppressMessage("CodeQuality", "IDE0051", Justification = "Called by Unity")] void Update() { if (trail.time <= 0f) 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/ExceptionIntercept/InterceptLogHandler.cs b/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs index 6cd3dcd0..471b1fa8 100644 --- a/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs +++ b/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs @@ -16,10 +16,9 @@ class InterceptLogHandler : ILogHandler public static string Warnings { get; private set; } = ""; - public InterceptLogHandler() + public InterceptLogHandler(ILogHandler baseLogHandler) { - baseLogHandler = Debug.unityLogger.logHandler; - Debug.unityLogger.logHandler = this; + this.baseLogHandler = baseLogHandler ?? throw new ArgumentNullException(nameof(baseLogHandler)); gamePathLength = Path.GetFullPath(KSPUtil.ApplicationRootPath).Length; } diff --git a/ModuleManager/Extensions/StringExtensions.cs b/ModuleManager/Extensions/StringExtensions.cs index c4190045..b9136e30 100644 --- a/ModuleManager/Extensions/StringExtensions.cs +++ b/ModuleManager/Extensions/StringExtensions.cs @@ -18,7 +18,7 @@ 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) { 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/StreamLogger.cs b/ModuleManager/Logging/StreamLogger.cs index 74115d3e..d020f64c 100644 --- a/ModuleManager/Logging/StreamLogger.cs +++ b/ModuleManager/Logging/StreamLogger.cs @@ -5,14 +5,11 @@ namespace ModuleManager.Logging { public sealed class StreamLogger : IBasicLogger, IDisposable { - 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); } diff --git a/ModuleManager/Logging/UnityLogger.cs b/ModuleManager/Logging/UnityLogger.cs index b31c1003..d51063c4 100644 --- a/ModuleManager/Logging/UnityLogger.cs +++ b/ModuleManager/Logging/UnityLogger.cs @@ -5,7 +5,7 @@ namespace ModuleManager.Logging { public class UnityLogger : IBasicLogger { - private ILogger logger; + private readonly ILogger logger; public UnityLogger(ILogger logger) { diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 1cffe055..d90e29a1 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; diff --git a/ModuleManager/MMPatchRunner.cs b/ModuleManager/MMPatchRunner.cs index b812ceed..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,12 +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))) - { - streamLogger.Info("Log started at " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")); - 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 diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 361cb540..dd5e04e5 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -38,8 +38,6 @@ public class ModuleManager : MonoBehaviour private MMPatchRunner patchRunner; - private InterceptLogHandler interceptLogHandler; - #endregion state private static bool loadedInScene; @@ -80,8 +78,8 @@ internal void Awake() } totalTime.Start(); - - interceptLogHandler = new InterceptLogHandler(); + + Debug.unityLogger.logHandler = new InterceptLogHandler(Debug.unityLogger.logHandler); // Allow loading the background in the loading screen Application.runInBackground = true; 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 f97c8b49..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; 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 f92f1fad..619f09d0 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 { @@ -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) diff --git a/ModuleManager/Progress/PatchProgress.cs b/ModuleManager/Progress/PatchProgress.cs index 3cebf994..61fd0d33 100644 --- a/ModuleManager/Progress/PatchProgress.cs +++ b/ModuleManager/Progress/PatchProgress.cs @@ -8,7 +8,7 @@ public class PatchProgress : IPatchProgress { public ProgressCounter Counter { get; private set; } - private IBasicLogger logger; + private readonly IBasicLogger logger; public float ProgressFraction { 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 e069e14c..818d3cc6 100644 --- a/ModuleManager/Utils/FileUtils.cs +++ b/ModuleManager/Utils/FileUtils.cs @@ -11,15 +11,9 @@ public static string FileSHA(string filename) { if (!File.Exists(filename)) throw new FileNotFoundException("File does not exist", filename); - byte[] data = null; - - using (SHA256 sha = SHA256.Create()) - { - using (FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read)) - { - data = sha.ComputeHash(fs); - } - } + using SHA256 sha = SHA256.Create(); + using FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read); + byte[] data = sha.ComputeHash(fs); return data.ToHex(); } 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/IBasicLoggerExtensionsTest.cs b/ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs index f6c1fad7..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,12 +8,7 @@ namespace ModuleManagerTests.Extensions { public class IBasicLoggerExtensionsTest { - private IBasicLogger logger; - - public IBasicLoggerExtensionsTest() - { - logger = Substitute.For(); - } + private readonly IBasicLogger logger = Substitute.For(); [Fact] public void TestInfo() diff --git a/ModuleManagerTests/Logging/PrefixLoggerTest.cs b/ModuleManagerTests/Logging/PrefixLoggerTest.cs index 28a82edc..5aaf0113 100644 --- a/ModuleManagerTests/Logging/PrefixLoggerTest.cs +++ b/ModuleManagerTests/Logging/PrefixLoggerTest.cs @@ -8,12 +8,11 @@ namespace ModuleManagerTests.Logging { public class PrefixLoggerTest { - private IBasicLogger innerLogger; - private PrefixLogger logger; + private readonly IBasicLogger innerLogger = Substitute.For(); + private readonly PrefixLogger logger; public PrefixLoggerTest() { - innerLogger = Substitute.For(); logger = new PrefixLogger("MyMod", innerLogger); } diff --git a/ModuleManagerTests/Logging/QueueLoggerTest.cs b/ModuleManagerTests/Logging/QueueLoggerTest.cs index dc8a1dfd..25e1abbe 100644 --- a/ModuleManagerTests/Logging/QueueLoggerTest.cs +++ b/ModuleManagerTests/Logging/QueueLoggerTest.cs @@ -8,12 +8,11 @@ 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); } diff --git a/ModuleManagerTests/Logging/StreamLoggerTest.cs b/ModuleManagerTests/Logging/StreamLoggerTest.cs index bc613038..296eeac2 100644 --- a/ModuleManagerTests/Logging/StreamLoggerTest.cs +++ b/ModuleManagerTests/Logging/StreamLoggerTest.cs @@ -11,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] @@ -24,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); } } diff --git a/ModuleManagerTests/Logging/UnityLoggerTest.cs b/ModuleManagerTests/Logging/UnityLoggerTest.cs index 5d820c25..7c4dc4a1 100644 --- a/ModuleManagerTests/Logging/UnityLoggerTest.cs +++ b/ModuleManagerTests/Logging/UnityLoggerTest.cs @@ -9,12 +9,11 @@ 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); } 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/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/Progress/PatchProgressTest.cs b/ModuleManagerTests/Progress/PatchProgressTest.cs index 8cde86c2..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); } From e0da6ba13bca4dea7181cd009b49691d9fd47df0 Mon Sep 17 00:00:00 2001 From: sarbian Date: Tue, 7 Jul 2020 11:42:00 +0200 Subject: [PATCH 50/67] v4.1.4 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index cf5d1a0c..fba8c4bf 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.1.3")] +[assembly: AssemblyVersion("4.1.4")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 0261dc6dac6b3340357a02c8322d21770be9c09c Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Wed, 30 Sep 2020 22:15:15 -0700 Subject: [PATCH 51/67] Normalize KSP root path KSP makes it weird Addresses confusion in #164 --- ModuleManager/FilePathRepository.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/ModuleManager/FilePathRepository.cs b/ModuleManager/FilePathRepository.cs index db15419e..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(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(KSPUtil.ApplicationRootPath, "GameData", "ModuleManager.ConfigSHA"); + internal static readonly string shaPath = Path.Combine(normalizedRootPath, "GameData", "ModuleManager.ConfigSHA"); - internal static readonly string logsDirPath = 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"); } From f6669bf7b7a9a2f0cb818ee757b3d2ed870898bb Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Thu, 14 Jan 2021 23:41:11 -0800 Subject: [PATCH 52/67] Set modded physics and reload earlier Do it in post patch, this allows the part loader to pick up changes (e.g. rigidbody min mass) --- ModuleManager/CustomConfigsManager.cs | 10 ---------- ModuleManager/PostPatchLoader.cs | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 10 deletions(-) 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/PostPatchLoader.cs b/ModuleManager/PostPatchLoader.cs index 619f09d0..fa1b9df6 100644 --- a/ModuleManager/PostPatchLoader.cs +++ b/ModuleManager/PostPatchLoader.cs @@ -130,6 +130,8 @@ private IEnumerator Run() logger.Info("Reloading Part Upgrades"); PartUpgradeManager.Handler.FillUpgrades(); + LoadModdedPhysics(); + yield return null; progressTitle = "ModuleManager: Running post patch callbacks"; @@ -209,5 +211,23 @@ private IEnumerator Run() ready = true; } + + private void LoadModdedPhysics() + { + if (PhysicsGlobals.PhysicsDatabaseFilename == physicsFile) return; + + if (!File.Exists(physicsPath)) + { + logger.Error("Physics file not found"); + return; + } + + logger.Info("Setting modded physics as the active one"); + + PhysicsGlobals.PhysicsDatabaseFilename = physicsFile; + + if (!PhysicsGlobals.Instance.LoadDatabase()) + logger.Error("Something went wrong while setting the active physics config."); + } } } From 983b330b259547f05d1ff2ff10bc5828153c7883 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Thu, 14 Jan 2021 23:52:56 -0800 Subject: [PATCH 53/67] mark dependencies as copy local false prevents them from showing up in the target directory --- ModuleManager/ModuleManager.csproj | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index d58d8d14..4556194b 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -121,33 +121,42 @@ False + False False + False False + False False + False False + False False + False False + False False + False False + False From b3d5ad47526c43b6d175c775cec77b967055cae7 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Thu, 14 Jan 2021 23:56:16 -0800 Subject: [PATCH 54/67] fix remaining .NET 3.5 nuget packages upgrade visual studio runner --- ModuleManagerTests/ModuleManagerTests.csproj | 8 ++++---- ModuleManagerTests/packages.config | 4 ++-- TestUtilsTests/TestUtilsTests.csproj | 8 ++++---- TestUtilsTests/packages.config | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index b4fafe96..7e311b3b 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -1,8 +1,8 @@  - + - + Debug @@ -156,10 +156,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/packages.config b/ModuleManagerTests/packages.config index dc5635be..63061ffe 100644 --- a/ModuleManagerTests/packages.config +++ b/ModuleManagerTests/packages.config @@ -11,6 +11,6 @@ - - + + \ No newline at end of file diff --git a/TestUtilsTests/TestUtilsTests.csproj b/TestUtilsTests/TestUtilsTests.csproj index 619e6bb4..1d3ff25e 100644 --- a/TestUtilsTests/TestUtilsTests.csproj +++ b/TestUtilsTests/TestUtilsTests.csproj @@ -1,8 +1,8 @@  - + - + Debug @@ -86,10 +86,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/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 From 6a32ebb8213c2d7f8bca1d3532b15dcced12a598 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Thu, 14 Jan 2021 23:57:18 -0800 Subject: [PATCH 55/67] Update remaining NuGet packages --- ModuleManagerTests/ModuleManagerTests.csproj | 14 +++++++------- ModuleManagerTests/app.config | 4 ++-- ModuleManagerTests/packages.config | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 7e311b3b..1a82db26 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -40,20 +40,20 @@ - ..\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 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 63061ffe..b159434d 100644 --- a/ModuleManagerTests/packages.config +++ b/ModuleManagerTests/packages.config @@ -1,9 +1,9 @@  - - - - + + + + From da38958f48b34bff2052169e5f84885ec2b7daa1 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 15 Jan 2021 00:04:06 -0800 Subject: [PATCH 56/67] Add specific language markers to all project files seems to get confused otherwise --- ModuleManager/ModuleManager.csproj | 3 +++ ModuleManagerTests/ModuleManagerTests.csproj | 3 +++ TestUtils/TestUtils.csproj | 3 +++ TestUtilsTests/TestUtilsTests.csproj | 3 +++ 4 files changed, 12 insertions(+) diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 4556194b..20fe0da1 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -33,6 +33,9 @@ False false + + 8.0 + diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 1a82db26..f3afbbf5 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -37,6 +37,9 @@ 4 false + + 8.0 + 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/TestUtilsTests/TestUtilsTests.csproj b/TestUtilsTests/TestUtilsTests.csproj index 1d3ff25e..cfe71bf2 100644 --- a/TestUtilsTests/TestUtilsTests.csproj +++ b/TestUtilsTests/TestUtilsTests.csproj @@ -37,6 +37,9 @@ 4 false + + 8.0 + From 838677db5f2d2c17c1ce653489a3caa974110009 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 22 Jan 2021 22:34:18 -0800 Subject: [PATCH 57/67] Ensure string comparison is culture invariant And get rid of message suppresions related to it --- ModuleManager/Extensions/StringExtensions.cs | 9 +++++ ModuleManager/MMPatchLoader.cs | 35 ++++++++----------- .../Extensions/StringExtensionsTest.cs | 30 ++++++++++++++++ 3 files changed, 54 insertions(+), 20 deletions(-) diff --git a/ModuleManager/Extensions/StringExtensions.cs b/ModuleManager/Extensions/StringExtensions.cs index b9136e30..634fab7a 100644 --- a/ModuleManager/Extensions/StringExtensions.cs +++ b/ModuleManager/Extensions/StringExtensions.cs @@ -24,5 +24,14 @@ 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/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index d90e29a1..61d993a3 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -22,8 +22,6 @@ namespace ModuleManager { - [SuppressMessage("ReSharper", "StringLastIndexOfIsCultureSpecific.1")] - [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] public class MMPatchLoader { private const string PHYSICS_NODE_NAME = "PHYSICSGLOBALS"; @@ -922,7 +920,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); @@ -945,11 +943,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(",")) @@ -1117,11 +1114,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(",")) { @@ -1290,13 +1286,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]; @@ -1551,11 +1546,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 += 4; + remainingConstraints = constraints.Substring(hasStart, constraintList[0].LastIndexOf(']') - hasStart); + constraints = constraints.Substring(0, hasStart - 5); } string[] splits = constraints.Split(contraintSeparators, 3); 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); + }); + } } } From c6163a2e495628e59822d2074da027e70aa85136 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 22 Jan 2021 22:36:24 -0800 Subject: [PATCH 58/67] Always replace physics On a database reload this will already be true but we still want physics reloading to happen --- ModuleManager/PostPatchLoader.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/ModuleManager/PostPatchLoader.cs b/ModuleManager/PostPatchLoader.cs index fa1b9df6..d850e4e1 100644 --- a/ModuleManager/PostPatchLoader.cs +++ b/ModuleManager/PostPatchLoader.cs @@ -214,8 +214,6 @@ private IEnumerator Run() private void LoadModdedPhysics() { - if (PhysicsGlobals.PhysicsDatabaseFilename == physicsFile) return; - if (!File.Exists(physicsPath)) { logger.Error("Physics file not found"); From 3894ce10ac129611594f2524bf87fef27aafbd85 Mon Sep 17 00:00:00 2001 From: sarbian Date: Sun, 1 Aug 2021 10:42:05 +0200 Subject: [PATCH 59/67] v4.2.0 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index fba8c4bf..0cd2c63f 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.1.4")] +[assembly: AssemblyVersion("4.2.0")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From d9e9264fb2f04bb3e4f0026133bf5b110bd4296d Mon Sep 17 00:00:00 2001 From: Alvin Meng Date: Sun, 1 Aug 2021 11:51:32 -0400 Subject: [PATCH 60/67] Fix off-by-one string indexing in constraint checking Also change string comparison type to `StringComparison.Ordinal`, which should be the correct type according to https://docs.microsoft.com/en-us/dotnet/standard/base-types/best-practices-strings. --- ModuleManager/Extensions/StringExtensions.cs | 2 +- ModuleManager/MMPatchLoader.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ModuleManager/Extensions/StringExtensions.cs b/ModuleManager/Extensions/StringExtensions.cs index 634fab7a..55720367 100644 --- a/ModuleManager/Extensions/StringExtensions.cs +++ b/ModuleManager/Extensions/StringExtensions.cs @@ -30,7 +30,7 @@ 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); + index = str.IndexOf(value, StringComparison.Ordinal); return index != -1; } } diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 61d993a3..cf3418c3 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -1548,7 +1548,7 @@ public static bool CheckConstraints(ConfigNode node, string constraints) string remainingConstraints = ""; if (constraints.Contains(":HAS[", out int hasStart)) { - hasStart += 4; + hasStart += 5; remainingConstraints = constraints.Substring(hasStart, constraintList[0].LastIndexOf(']') - hasStart); constraints = constraints.Substring(0, hasStart - 5); } From c60e3c537a352de35b02d5fa79a1a6c712a83e9a Mon Sep 17 00:00:00 2001 From: Alvin Meng Date: Sun, 1 Aug 2021 15:08:13 -0400 Subject: [PATCH 61/67] Undo string comparison change. --- ModuleManager/Extensions/StringExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Extensions/StringExtensions.cs b/ModuleManager/Extensions/StringExtensions.cs index 55720367..634fab7a 100644 --- a/ModuleManager/Extensions/StringExtensions.cs +++ b/ModuleManager/Extensions/StringExtensions.cs @@ -30,7 +30,7 @@ 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.Ordinal); + index = str.IndexOf(value, StringComparison.CurrentCultureIgnoreCase); return index != -1; } } From 86e60c36036c9fa6cd24d9abdc679f452059a8b2 Mon Sep 17 00:00:00 2001 From: sarbian Date: Sun, 1 Aug 2021 21:52:17 +0200 Subject: [PATCH 62/67] v4.2.1 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index 0cd2c63f..ef0b7100 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.2.0")] +[assembly: AssemblyVersion("4.2.1")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From ebe2895e3a55384fc8c39c9e0f964a5e39d3ede9 Mon Sep 17 00:00:00 2001 From: NathanKell Date: Thu, 16 Jun 2022 21:35:54 -0700 Subject: [PATCH 63/67] Support patching Localization tokens. * Support wildcards in nodetype matching so you can do @*,* {} * Support # in value names since loc names start with # * Tell Localizer to reload the language after MM finishes --- ModuleManager/MMPatchLoader.cs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index cf3418c3..5278758b 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -231,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(); @@ -564,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 readonly 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. @@ -1712,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)) { @@ -1735,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)) { From a303e0a7118f3506cc1546b70b03acc04263a6b7 Mon Sep 17 00:00:00 2001 From: sarbian Date: Sat, 18 Jun 2022 20:22:56 +0200 Subject: [PATCH 64/67] v4.2.2 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index ef0b7100..e4e038f0 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.2.1")] +[assembly: AssemblyVersion("4.2.2")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 2ba651e41aebba1758e57b679f01bb8592b2d536 Mon Sep 17 00:00:00 2001 From: Jonathan Bayer Date: Sun, 14 May 2023 18:19:50 -0400 Subject: [PATCH 65/67] Added , FileShare.ReadWrite to the File.Open, to allow MM to read files which are already opened by KSP --- ModuleManager/Utils/FileUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Utils/FileUtils.cs b/ModuleManager/Utils/FileUtils.cs index 818d3cc6..fcf04482 100644 --- a/ModuleManager/Utils/FileUtils.cs +++ b/ModuleManager/Utils/FileUtils.cs @@ -12,7 +12,7 @@ public static string FileSHA(string filename) if (!File.Exists(filename)) throw new FileNotFoundException("File does not exist", filename); using SHA256 sha = SHA256.Create(); - using FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read); + using FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); byte[] data = sha.ComputeHash(fs); return data.ToHex(); From b22136a8302b592626533f864413cb7b37a32126 Mon Sep 17 00:00:00 2001 From: siimav Date: Fri, 19 May 2023 23:51:58 +0300 Subject: [PATCH 66/67] Fix invalid modded physics cfg path being fed to KSP when using the faulty PDLauncher workaround --- ModuleManager/PostPatchLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/PostPatchLoader.cs b/ModuleManager/PostPatchLoader.cs index d850e4e1..1bccce78 100644 --- a/ModuleManager/PostPatchLoader.cs +++ b/ModuleManager/PostPatchLoader.cs @@ -222,7 +222,7 @@ private void LoadModdedPhysics() logger.Info("Setting modded physics as the active one"); - PhysicsGlobals.PhysicsDatabaseFilename = physicsFile; + PhysicsGlobals.PhysicsDatabaseFilename = physicsPath; if (!PhysicsGlobals.Instance.LoadDatabase()) logger.Error("Something went wrong while setting the active physics config."); From c4561925f983e7ae81d9dfd4d11356a35cb6b9b6 Mon Sep 17 00:00:00 2001 From: sarbian Date: Mon, 3 Jul 2023 20:01:38 +0200 Subject: [PATCH 67/67] v4.2.3 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index e4e038f0..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.2.2")] +[assembly: AssemblyVersion("4.2.3")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly,