diff --git a/src/extension/Event.cs b/src/extension/Event.cs index a4b8cac..dcd4f8a 100644 --- a/src/extension/Event.cs +++ b/src/extension/Event.cs @@ -1,5 +1,6 @@ namespace NUnit.Engine.Listeners { + using System; using System.Xml; public struct Event @@ -8,9 +9,13 @@ public struct Event public readonly string MessageName; public readonly string FullName; public readonly string Name; + public readonly string Type; public readonly string Id; public readonly string ParentId; + + // ReSharper disable once InconsistentNaming public readonly string TestId; + // ReSharper disable once InconsistentNaming public readonly XmlNode TestEvent; @@ -22,6 +27,7 @@ public Event( string id, string parentId, string testId, + string type, XmlNode testEvent) { RootFlowId = rootFlowId; @@ -31,12 +37,15 @@ public Event( Id = id; ParentId = parentId; TestId = testId; + Type = type; TestEvent = testEvent; } public override string ToString() { - return string.Format("RootFlowId: {0}, MessageName: {1}, FullName: {2}, Id: {3}, ParentId: {4}, Event: {5}", RootFlowId, MessageName, FullName, Id, ParentId, TestEvent.OuterXml); + return string.Format( + "RootFlowId: {0}, MessageName: {1}, FullName: {2}, Id: {3}, ParentId: {4}, Type: {5}, TestEvent.OuterXml: {6}{7}", + RootFlowId, MessageName, FullName, Id, ParentId, Type, Environment.NewLine, TestEvent.OuterXml); } } -} +} \ No newline at end of file diff --git a/src/extension/EventConverter3.cs b/src/extension/EventConverter3.cs index aeaeba1..9ebc2d0 100644 --- a/src/extension/EventConverter3.cs +++ b/src/extension/EventConverter3.cs @@ -26,16 +26,24 @@ namespace NUnit.Engine.Listeners using System; using System.Xml; using System.Collections.Generic; + using System.Collections.Specialized; + using System.IO; - internal class EventConverter3: IEventConverter + internal class EventConverter3 : IEventConverter { private readonly IServiceMessageFactory _serviceMessageFactory; private readonly IHierarchy _hierarchy; private readonly Statistics _statistics; private readonly ITeamCityInfo _teamCityInfo; + + private readonly Dictionary> _testSuiteTestEvents = + new Dictionary>(); + private readonly Dictionary _notStartedNUnit3Tests = new Dictionary(); + private readonly Dictionary _suiteAssembly = new Dictionary(); - public EventConverter3(IServiceMessageFactory serviceMessageFactory, IHierarchy hierarchy, Statistics statistics, ITeamCityInfo teamCityInfo) + public EventConverter3(IServiceMessageFactory serviceMessageFactory, IHierarchy hierarchy, + Statistics statistics, ITeamCityInfo teamCityInfo) { if (serviceMessageFactory == null) throw new ArgumentNullException("serviceMessageFactory"); if (hierarchy == null) throw new ArgumentNullException("hierarchy"); @@ -53,13 +61,15 @@ public IEnumerable> Convert(Event testEvent) if (testEvent.MessageName == "start-run") { _hierarchy.Clear(); + _suiteAssembly.Clear(); _notStartedNUnit3Tests.Clear(); + _testSuiteTestEvents.Clear(); yield break; } var id = testEvent.Id; var parentId = testEvent.ParentId; - + string rootId; var flowId = _hierarchy.TryFindRootId(parentId, out rootId) ? rootId : id; var testFlowId = id != flowId ? id : flowId; @@ -69,6 +79,8 @@ public IEnumerable> Convert(Event testEvent) rootFlowId = "."; } + var testEventId = new EventId(_teamCityInfo, testFlowId, testEvent.FullName); + var eventId = new EventId(_teamCityInfo, flowId, testEvent.FullName); switch (testEvent.MessageName) { @@ -78,33 +90,52 @@ public IEnumerable> Convert(Event testEvent) // Root if (parentId == string.Empty) { + //Save assembly name + _suiteAssembly[flowId] = testEvent.Name; + // Start a flow from a root flow https://youtrack.jetbrains.com/issue/TW-56310 yield return _serviceMessageFactory.FlowStarted(flowId, rootFlowId); _statistics.RegisterSuiteStart(); - yield return _serviceMessageFactory.SuiteStarted(eventId, testEvent); + yield return _serviceMessageFactory.SuiteStarted(eventId, testEvent); } break; case "test-suite": _hierarchy.AddLink(id, parentId); + if (_suiteAssembly.ContainsKey(rootFlowId)) + { + _suiteAssembly[flowId] = _suiteAssembly[rootFlowId]; + } + yield return ProcessNotStartedTests(flowId, id, testEvent.TestEvent); + yield return ProcessTestSuiteProperties(flowId, id, testEvent.TestEvent); yield return _serviceMessageFactory.TestOutputAsMessage(eventId, testEvent.TestEvent); // Root if (parentId == string.Empty) { _statistics.RegisterSuiteFinish(); - yield return _serviceMessageFactory.SuiteFinished(eventId, testEvent); + yield return _serviceMessageFactory.SuiteFinished(eventId, testEvent); // Finish a child flow from a root flow https://youtrack.jetbrains.com/issue/TW-56310 - yield return _serviceMessageFactory.FlowFinished(flowId); + yield return _serviceMessageFactory.FlowFinished(flowId); } break; case "start-test": + List existingEventList; + if (_testSuiteTestEvents.TryGetValue(parentId, out existingEventList)) + { + existingEventList.Add(testEventId); + } + else + { + _testSuiteTestEvents[parentId] = new List() { testEventId }; + } + _hierarchy.AddLink(id, parentId); if (testFlowId != eventId.FlowId) { @@ -112,7 +143,8 @@ public IEnumerable> Convert(Event testEvent) } _statistics.RegisterTestStart(); - yield return _serviceMessageFactory.TestStarted(new EventId(_teamCityInfo, testFlowId, eventId.FullName)); + yield return _serviceMessageFactory.TestStarted(new EventId(_teamCityInfo, testFlowId, + eventId.FullName)); break; case "test-case": @@ -124,11 +156,13 @@ public IEnumerable> Convert(Event testEvent) } _statistics.RegisterTestFinish(); - yield return _serviceMessageFactory.TestFinished(new EventId(_teamCityInfo, testFlowId, testEvent.FullName), testEvent.TestEvent, testEvent.TestEvent); + yield return _serviceMessageFactory.TestFinished(testEventId, testEvent.TestEvent, + testEvent.TestEvent); + if (id != flowId && parentId != null) { yield return _serviceMessageFactory.FlowFinished(id); - } + } break; @@ -137,11 +171,55 @@ public IEnumerable> Convert(Event testEvent) break; case "test-output": - testFlowId = testEvent.TestId ?? rootFlowId; - yield return _serviceMessageFactory.TestOutput(new EventId(_teamCityInfo, testFlowId, testEvent.FullName), testEvent.TestEvent); + yield return _serviceMessageFactory.TestOutput(testEventId, testEvent.TestEvent); break; } - } + } + + private IEnumerable ProcessTestSuiteProperties(string flowId, string suiteId, + XmlNode testSuiteNode) + { + var properties = testSuiteNode.SelectNodes("properties/property"); + List testEventIds; + + if (_testSuiteTestEvents.TryGetValue(suiteId, out testEventIds) && properties != null) + { + var props = new NameValueCollection(); + foreach (var property in properties) + { + var propertyElement = property as XmlNode; + if (propertyElement == null) + { + continue; + } + + var propertyName = propertyElement.GetAttribute("name") ?? string.Empty; + var propertyValue = propertyElement.GetAttribute("value") ?? string.Empty; + + props.Add(propertyName, propertyValue); + } + + if (testEventIds.Count > 0) + { + foreach (var eventId in testEventIds) + { + foreach (var name in props.AllKeys) + { + string assemblyName; + var dllName = _suiteAssembly.TryGetValue(flowId, out assemblyName) ? assemblyName : ""; + var testFullName = dllName == "" ? eventId.FullName : dllName + ": " + eventId.FullName; + var attrs = new List + { + new ServiceMessageAttr(ServiceMessageAttr.Names.TestName, testFullName), + new ServiceMessageAttr(ServiceMessageAttr.Names.Name, name), + new ServiceMessageAttr(ServiceMessageAttr.Names.Value, props[name]) + }; + yield return new ServiceMessage(ServiceMessage.Names.TestMetadata, attrs); + } + } + } + } + } private IEnumerable ProcessNotStartedTests(string flowId, string id, XmlNode currentEvent) { @@ -169,13 +247,15 @@ private IEnumerable ProcessNotStartedTests(string flowId, string continue; } - foreach (var message in _serviceMessageFactory.TestStarted(new EventId(_teamCityInfo, flowId, fullName))) + foreach (var message in + _serviceMessageFactory.TestStarted(new EventId(_teamCityInfo, flowId, fullName))) { _statistics.RegisterTestStart(); yield return message; } - foreach (var message in _serviceMessageFactory.TestFinished(new EventId(_teamCityInfo, flowId, fullName), testEvent, currentEvent)) + foreach (var message in _serviceMessageFactory.TestFinished( + new EventId(_teamCityInfo, flowId, fullName), testEvent, currentEvent)) { _statistics.RegisterTestFinish(); yield return message; @@ -183,4 +263,4 @@ private IEnumerable ProcessNotStartedTests(string flowId, string } } } -} +} \ No newline at end of file diff --git a/src/extension/EventId.cs b/src/extension/EventId.cs index 647b6a9..7992dc2 100644 --- a/src/extension/EventId.cs +++ b/src/extension/EventId.cs @@ -5,7 +5,7 @@ internal struct EventId public readonly string FlowId; public readonly string FullName; - public EventId(ITeamCityInfo tamCityInfo, string flowId, string fullName) + public EventId(ITeamCityInfo teamCityInfo, string flowId, string fullName) { FlowId = flowId; // TeamCity extracts the name of assembly from the test name @@ -21,7 +21,7 @@ public EventId(ITeamCityInfo tamCityInfo, string flowId, string fullName) // assembly name = "abc.dll" // test name = "text1" - FullName = fullName.Replace(":", tamCityInfo.ColonReplacement); + FullName = fullName.Replace(":", teamCityInfo.ColonReplacement); } } } diff --git a/src/extension/IServiceMessageFactory.cs b/src/extension/IServiceMessageFactory.cs index 6124df7..74d5c3c 100644 --- a/src/extension/IServiceMessageFactory.cs +++ b/src/extension/IServiceMessageFactory.cs @@ -10,7 +10,7 @@ internal interface IServiceMessageFactory IEnumerable SuiteStarted(EventId eventId, Event testEvent); IEnumerable SuiteFinished(EventId eventId, Event testEvent); - + IEnumerable FlowStarted(string flowId, string parentFlowId); IEnumerable FlowFinished(string flowId); diff --git a/src/extension/IServiceMessageWriter.cs b/src/extension/IServiceMessageWriter.cs index 0592263..7acb24f 100644 --- a/src/extension/IServiceMessageWriter.cs +++ b/src/extension/IServiceMessageWriter.cs @@ -1,10 +1,9 @@ namespace NUnit.Engine.Listeners { - using System.Collections.Generic; using System.IO; public interface IServiceMessageWriter { - void Write(TextWriter writer, IEnumerable serviceMessages); + void Write(TextWriter writer, ServiceMessage serviceMessages); } } \ No newline at end of file diff --git a/src/extension/ServiceMessage.cs b/src/extension/ServiceMessage.cs index 80813eb..f9bd7ed 100644 --- a/src/extension/ServiceMessage.cs +++ b/src/extension/ServiceMessage.cs @@ -37,12 +37,9 @@ public ServiceMessage(string name, params ServiceMessageAttr[] attributes) { } - public ServiceMessage(string name, IList attributes) - : this() + public ServiceMessage(string name, IList attributes) : this() { - // ReSharper disable once UseNameofExpression if (name == null) throw new ArgumentNullException("name"); - // ReSharper disable once UseNameofExpression if (attributes == null) throw new ArgumentNullException("attributes"); Name = name; @@ -50,12 +47,9 @@ public ServiceMessage(string name, IList attributes) Attributes = new ReadOnlyCollection(attributes); } - public ServiceMessage(string name, string value) - : this() + public ServiceMessage(string name, string value) : this() { - // ReSharper disable once UseNameofExpression if (name == null) throw new ArgumentNullException("name"); - // ReSharper disable once UseNameofExpression if (value == null) throw new ArgumentNullException("value"); Name = name; @@ -69,6 +63,19 @@ public ServiceMessage(string name, string value) public IEnumerable Attributes { get; private set; } + public string Dump(string prefix) + { + var attributes = ""; + foreach (var serviceMessageAttr in Attributes) + { + attributes += " { " + serviceMessageAttr.Name + ": " + serviceMessageAttr.Value + " }" + + Environment.NewLine; + } + + return "MSG " + prefix + ":" + Environment.NewLine + " Name: '" + Name + "', Value: '" + Value + + "', Attributes: " + Environment.NewLine + attributes; + } + [SuppressMessage("ReSharper", "InconsistentNaming")] public static class Names { @@ -83,8 +90,8 @@ public static class Names public const string TestFailed = "testFailed"; public const string TestIgnored = "testIgnored"; public const string Message = "message"; - public const string PublishArtifacts = "publishArtifacts"; + public const string PublishArtifacts = "publishArtifacts"; public const string TestMetadata = "testMetadata"; } - } -} + } +} \ No newline at end of file diff --git a/src/extension/ServiceMessageFactory.cs b/src/extension/ServiceMessageFactory.cs index 89f69d2..b2b0cdb 100644 --- a/src/extension/ServiceMessageFactory.cs +++ b/src/extension/ServiceMessageFactory.cs @@ -16,7 +16,7 @@ internal class ServiceMessageFactory : IServiceMessageFactory private const string TcParseServiceMessagesInside = "tc:parseServiceMessagesInside"; private static readonly IEnumerable EmptyServiceMessages = new ServiceMessage[0]; private static readonly Regex AttachmentDescriptionRegex = new Regex("(.*)=>(.+)", RegexOptions.Compiled); - + public ServiceMessageFactory(ITeamCityInfo teamCityInfo, ISuiteNameReplacer suiteNameReplacer) { _teamCityInfo = teamCityInfo; @@ -72,6 +72,11 @@ public IEnumerable TestFinished(EventId eventId, XmlNode testEve { yield return message; } + + foreach (var message in TestProperties(eventId, testEvent)) + { + yield return message; + } } IEnumerable messages; @@ -154,7 +159,8 @@ private IEnumerable TestFinished(EventId eventId, XmlNode testFi var durationStr = testFinishedEvent.GetAttribute(ServiceMessageAttr.Names.Duration); double durationDecimal; var durationMilliseconds = 0; - if (durationStr != null && double.TryParse(durationStr, NumberStyles.Any, CultureInfo.InvariantCulture, out durationDecimal)) + if (durationStr != null && double.TryParse(durationStr, NumberStyles.Any, CultureInfo.InvariantCulture, + out durationDecimal)) { durationMilliseconds = (int)(durationDecimal * 1000d); } @@ -192,8 +198,10 @@ private IEnumerable TestFailed(EventId eventId, XmlNode testFail yield return new ServiceMessage(ServiceMessage.Names.TestFailed, new ServiceMessageAttr(ServiceMessageAttr.Names.Name, eventId.FullName), - new ServiceMessageAttr(ServiceMessageAttr.Names.Message, errorMessage == null ? string.Empty : errorMessage.InnerText), - new ServiceMessageAttr(ServiceMessageAttr.Names.Details, stackTrace == null ? string.Empty : stackTrace.InnerText), + new ServiceMessageAttr(ServiceMessageAttr.Names.Message, + errorMessage == null ? string.Empty : errorMessage.InnerText), + new ServiceMessageAttr(ServiceMessageAttr.Names.Details, + stackTrace == null ? string.Empty : stackTrace.InnerText), new ServiceMessageAttr(ServiceMessageAttr.Names.FlowId, eventId.FlowId)); foreach (var message in TestFinished(eventId, testFailedEvent)) @@ -218,7 +226,8 @@ private IEnumerable TestSkipped(EventId eventId, XmlNode testSki yield return new ServiceMessage(ServiceMessage.Names.TestIgnored, new ServiceMessageAttr(ServiceMessageAttr.Names.Name, eventId.FullName), - new ServiceMessageAttr(ServiceMessageAttr.Names.Message, reason == null ? string.Empty : reason.InnerText), + new ServiceMessageAttr(ServiceMessageAttr.Names.Message, + reason == null ? string.Empty : reason.InnerText), new ServiceMessageAttr(ServiceMessageAttr.Names.FlowId, eventId.FlowId)); } @@ -314,12 +323,40 @@ private IEnumerable ReasonMessage(EventId eventId, XmlNode ev) yield break; } - foreach (var message in Output(eventId, ServiceMessage.Names.TestStdOut, "Assert.Pass message: " + reasonMessage)) + foreach (var message in Output(eventId, ServiceMessage.Names.TestStdOut, + "Assert.Pass message: " + reasonMessage)) { yield return message; } } + private IEnumerable TestProperties(EventId eventId, XmlNode testEvent) + { + var properties = testEvent.SelectNodes("properties/property"); + if (properties != null) + { + foreach (var property in properties) + { + var propertyElement = property as XmlNode; + if (propertyElement == null) + { + continue; + } + + var propertyName = propertyElement.GetAttribute("name") ?? string.Empty; + var propertyValue = propertyElement.GetAttribute("value") ?? string.Empty; + var attrs = new List + { + new ServiceMessageAttr(ServiceMessageAttr.Names.FlowId, eventId.FlowId), + new ServiceMessageAttr(ServiceMessageAttr.Names.TestName, eventId.FullName), + new ServiceMessageAttr(ServiceMessageAttr.Names.Name, propertyName), + new ServiceMessageAttr(ServiceMessageAttr.Names.Value, propertyValue) + }; + yield return new ServiceMessage(ServiceMessage.Names.TestMetadata, attrs); + } + } + } + private static IEnumerable Attachments(EventId eventId, XmlNode testEvent) { var attachments = testEvent.SelectNodes("attachments/attachment"); @@ -390,7 +427,8 @@ private static IEnumerable Attachments(EventId eventId, XmlNode break; } - yield return new ServiceMessage(ServiceMessage.Names.PublishArtifacts, filePath + " => " + artifactDir); + yield return new ServiceMessage(ServiceMessage.Names.PublishArtifacts, + filePath + " => " + artifactDir); var attrs = new List { @@ -411,4 +449,4 @@ private static IEnumerable Attachments(EventId eventId, XmlNode } } } -} +} \ No newline at end of file diff --git a/src/extension/ServiceMessageWriter.cs b/src/extension/ServiceMessageWriter.cs index 0aeb2ac..0216cf0 100644 --- a/src/extension/ServiceMessageWriter.cs +++ b/src/extension/ServiceMessageWriter.cs @@ -1,41 +1,35 @@ namespace NUnit.Engine.Listeners { using System; - using System.Diagnostics.CodeAnalysis; using System.IO; - using System.Collections.Generic; using System.Text; - [SuppressMessage("ReSharper", "UseNameofExpression")] public class ServiceMessageWriter : IServiceMessageWriter { private const string Header = "##teamcity["; private const string Footer = "]"; - public void Write(TextWriter writer, IEnumerable serviceMessages) + public void Write(TextWriter writer, ServiceMessage serviceMessage) { if (writer == null) throw new ArgumentNullException("writer"); - foreach (var serviceMessage in serviceMessages) - { - writer.Write(Header); - writer.Write(serviceMessage.Name); + writer.Write(Header); + writer.Write(serviceMessage.Name); - if (!string.IsNullOrEmpty(serviceMessage.Value)) + if (!string.IsNullOrEmpty(serviceMessage.Value)) + { + writer.Write(' '); + Write(writer, serviceMessage.Value); + } + else + { + foreach (var attribute in serviceMessage.Attributes) { writer.Write(' '); - Write(writer, serviceMessage.Value); + Write(writer, attribute); } - else - { - foreach (var attribute in serviceMessage.Attributes) - { - writer.Write(' '); - Write(writer, attribute); - } - } - - writer.WriteLine(Footer); } + + writer.WriteLine(Footer); } private static void Write(TextWriter writer, ServiceMessageAttr attribute) diff --git a/src/extension/TeamCityEventListener.cs b/src/extension/TeamCityEventListener.cs index e7c6340..449db76 100644 --- a/src/extension/TeamCityEventListener.cs +++ b/src/extension/TeamCityEventListener.cs @@ -45,10 +45,12 @@ public class TeamCityEventListener : ITestEventListener private readonly ITeamCityInfo _teamCityInfo; private readonly object _lockObject = new object(); private readonly TextWriter _outWriter; - private string _rootFlowId = string.Empty; + private string _rootFlowId = string.Empty; // ReSharper disable once UnusedMember.Global - public TeamCityEventListener() : this(Console.Out, new TeamCityInfo()) { } + public TeamCityEventListener() : this(Console.Out, new TeamCityInfo()) + { + } public TeamCityEventListener(TextWriter outWriter, ITeamCityInfo teamCityInfo) { @@ -60,28 +62,20 @@ public TeamCityEventListener(TextWriter outWriter, ITeamCityInfo teamCityInfo) _serviceMessageWriter = new ServiceMessageWriter(); var serviceMessageFactory = new ServiceMessageFactory(_teamCityInfo, new SuiteNameReplacer(_teamCityInfo)); - var hierarchy = new Hierarchy(); + var hierarchy = new Hierarchy(); _eventConverter2 = new EventConverter2(serviceMessageFactory, hierarchy, _statistics, _teamCityInfo); - _eventConverter3 = new EventConverter3(serviceMessageFactory, hierarchy, _statistics, _teamCityInfo); - RootFlowId = _teamCityInfo.RootFlowId; + _eventConverter3 = + new EventConverter3(serviceMessageFactory, hierarchy, _statistics, _teamCityInfo); + RootFlowId = _teamCityInfo.RootFlowId; } public string RootFlowId { - set - { - _rootFlowId = value ?? string.Empty; - } + set { _rootFlowId = value ?? string.Empty; } } public void OnTestEvent(string report) { - if (_teamCityInfo.AllowDiagnostics) - { - _outWriter.WriteLine(); - _outWriter.WriteLine("PID_" + _teamCityInfo.ProcessId + " !!!!{ " + report + " }!!!!"); - } - var doc = new XmlDocument(); doc.LoadXml(report); @@ -97,7 +91,7 @@ public void RegisterMessage(XmlNode xmlEvent) { return; } - + var fullName = xmlEvent.GetAttribute("fullname"); if (string.IsNullOrEmpty(fullName)) { @@ -114,13 +108,16 @@ public void RegisterMessage(XmlNode xmlEvent) name = fullName; } + var type = xmlEvent.GetAttribute("type"); + var id = xmlEvent.GetAttribute("id") ?? string.Empty; var parentId = xmlEvent.GetAttribute("parentId"); var testId = xmlEvent.GetAttribute("testid"); var isNUnit3 = parentId != null; var eventConverter = isNUnit3 ? _eventConverter3 : _eventConverter2; - var testEvent = new Event(_rootFlowId, messageName.ToLowerInvariant(), fullName, name, GetId(_rootFlowId, id), GetId(_rootFlowId, parentId), GetId(_rootFlowId, testId), xmlEvent); + var testEvent = new Event(_rootFlowId, messageName.ToLowerInvariant(), fullName, name, + GetId(_rootFlowId, id), GetId(_rootFlowId, parentId), GetId(_rootFlowId, testId), type, xmlEvent); lock (_lockObject) { var sb = new StringBuilder(); @@ -128,7 +125,16 @@ public void RegisterMessage(XmlNode xmlEvent) { foreach (var messages in eventConverter.Convert(testEvent)) { - _serviceMessageWriter.Write(writer, messages); + foreach (var message in messages) + { + if (_teamCityInfo.AllowDiagnostics) + { + _outWriter.WriteLine("Sending service message:"); + _outWriter.WriteLine(message.Dump("OnTestEvent")); + } + + _serviceMessageWriter.Write(writer, message); + } } } @@ -137,7 +143,8 @@ public void RegisterMessage(XmlNode xmlEvent) if (_teamCityInfo.AllowDiagnostics) { - _outWriter.WriteLine("@@ NUnit3: " + isNUnit3 + ", " + _statistics + ", " + testEvent); + _outWriter.WriteLine("TeamCityEventListener.RegisterMessage"); + _outWriter.WriteLine(testEvent); } } @@ -151,4 +158,4 @@ private static string GetId(string rootFlowId, string flowId) return rootFlowId + "_" + flowId; } } -} +} \ No newline at end of file diff --git a/teamcity-event-listener.sln.DotSettings b/teamcity-event-listener.sln.DotSettings index 9075622..dca113f 100644 --- a/teamcity-event-listener.sln.DotSettings +++ b/teamcity-event-listener.sln.DotSettings @@ -1,4 +1,5 @@  + 4 True True True