From 207688e42b62ec7332604878fcfaf110ff6e8ebf Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 7 Oct 2017 15:28:36 +0200 Subject: [PATCH] Observable logs (refactor some code) --- src/WireMock.Net/Matchers/MatchScores.cs | 5 + src/WireMock.Net/Owin/OwinSelfHost.cs | 4 +- src/WireMock.Net/Owin/WireMockMiddleware.cs | 7 +- .../Owin/WireMockMiddlewareOptions.cs | 2 +- .../Server/FluentMockServer.Admin.cs | 3 +- .../Server/FluentMockServer.LogEntries.cs | 225 +++++++++--------- src/WireMock.Net/Server/FluentMockServer.cs | 32 +-- .../Util/NamedReaderWriterLocker.cs | 81 +++++++ .../ObservableLogEntriesTest.cs | 17 +- 9 files changed, 221 insertions(+), 155 deletions(-) create mode 100644 src/WireMock.Net/Util/NamedReaderWriterLocker.cs diff --git a/src/WireMock.Net/Matchers/MatchScores.cs b/src/WireMock.Net/Matchers/MatchScores.cs index 789a810a..c4679578 100644 --- a/src/WireMock.Net/Matchers/MatchScores.cs +++ b/src/WireMock.Net/Matchers/MatchScores.cs @@ -23,6 +23,11 @@ namespace WireMock.Matchers /// public const double Perfect = 1.0; + /// + /// The almost perfect match score + /// + public const double AlmostPerfect = 0.99; + /// /// Convert a bool to the score. /// diff --git a/src/WireMock.Net/Owin/OwinSelfHost.cs b/src/WireMock.Net/Owin/OwinSelfHost.cs index 89447c1f..0997c387 100644 --- a/src/WireMock.Net/Owin/OwinSelfHost.cs +++ b/src/WireMock.Net/Owin/OwinSelfHost.cs @@ -25,9 +25,7 @@ namespace WireMock.Owin { Urls.Add(uriPrefix); - int port; - string host; - PortUtil.TryExtractProtocolAndPort(uriPrefix, out host, out port); + PortUtil.TryExtractProtocolAndPort(uriPrefix, out string host, out int port); Ports.Add(port); } diff --git a/src/WireMock.Net/Owin/WireMockMiddleware.cs b/src/WireMock.Net/Owin/WireMockMiddleware.cs index 75ea28db..4724c58b 100644 --- a/src/WireMock.Net/Owin/WireMockMiddleware.cs +++ b/src/WireMock.Net/Owin/WireMockMiddleware.cs @@ -163,7 +163,10 @@ namespace WireMock.Owin if (_options.MaxRequestLogCount != null) { var amount = _options.LogEntries.Count - _options.MaxRequestLogCount.Value; - for (var i = 0; i < amount; i++, _options.LogEntries.RemoveAt(0)) ; + for (int i = 0; i < amount; i++) + { + _options.LogEntries.RemoveAt(0); + } } if (_options.RequestLogExpirationDuration != null) @@ -174,7 +177,9 @@ namespace WireMock.Owin { var le = _options.LogEntries[i]; if (le.RequestMessage.DateTime <= checkTime) + { _options.LogEntries.RemoveAt(i); + } } } } diff --git a/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs b/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs index e9ca46bd..33bea177 100644 --- a/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs +++ b/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs @@ -16,7 +16,7 @@ namespace WireMock.Owin public IList Mappings { get; set; } - public ObservableCollection LogEntries { get; set; } + public ObservableCollection LogEntries { get; } public int? RequestLogExpirationDuration { get; set; } diff --git a/src/WireMock.Net/Server/FluentMockServer.Admin.cs b/src/WireMock.Net/Server/FluentMockServer.Admin.cs index aef64b9e..bb39aa46 100644 --- a/src/WireMock.Net/Server/FluentMockServer.Admin.cs +++ b/src/WireMock.Net/Server/FluentMockServer.Admin.cs @@ -447,7 +447,7 @@ namespace WireMock.Server foreach (var logEntry in LogEntries.Where(le => !le.RequestMessage.Path.StartsWith("/__admin/"))) { var requestMatchResult = new RequestMatchResult(); - if (request.GetMatchingScore(logEntry.RequestMessage, requestMatchResult) > 0.99) + if (request.GetMatchingScore(logEntry.RequestMessage, requestMatchResult) > MatchScores.AlmostPerfect) { dict.Add(logEntry, requestMatchResult); } @@ -559,7 +559,6 @@ namespace WireMock.Server } return responseBuilder.WithProxy(responseModel.ProxyUrl, responseModel.X509Certificate2ThumbprintOrSubjectName); - } if (responseModel.StatusCode.HasValue) diff --git a/src/WireMock.Net/Server/FluentMockServer.LogEntries.cs b/src/WireMock.Net/Server/FluentMockServer.LogEntries.cs index 0fcc77ef..1cb8f595 100644 --- a/src/WireMock.Net/Server/FluentMockServer.LogEntries.cs +++ b/src/WireMock.Net/Server/FluentMockServer.LogEntries.cs @@ -1,113 +1,116 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using JetBrains.Annotations; -using WireMock.Logging; -using WireMock.Matchers.Request; -using System.Linq; - -namespace WireMock.Server -{ - public partial class FluentMockServer - { +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using JetBrains.Annotations; +using WireMock.Logging; +using WireMock.Matchers.Request; +using System.Linq; +using WireMock.Matchers; + +namespace WireMock.Server +{ + public partial class FluentMockServer + { /// /// Log entries notification handler - /// - [PublicAPI] - public event NotifyCollectionChangedEventHandler LogEntriesChanged - { - add - { - lock (((ICollection) _options.LogEntries).SyncRoot) - { - _options.LogEntries.CollectionChanged += value; - } - } - remove - { - lock (((ICollection)_options.LogEntries).SyncRoot) - { - _options.LogEntries.CollectionChanged -= value; - } - } - } - - /// - /// Gets the request logs. - /// - [PublicAPI] - public IEnumerable LogEntries - { - get - { - lock (((ICollection)_options.LogEntries).SyncRoot) - { - return new ReadOnlyCollection(_options.LogEntries); - } - } - } - - /// - /// The search log-entries based on matchers. - /// - /// The matchers. - /// The . - [PublicAPI] - public IEnumerable FindLogEntries([NotNull] params IRequestMatcher[] matchers) - { - lock (((ICollection)_options.LogEntries).SyncRoot) - { - var results = new Dictionary(); - - foreach (var log in _options.LogEntries) - { - var requestMatchResult = new RequestMatchResult(); - foreach (var matcher in matchers) - { - matcher.GetMatchingScore(log.RequestMessage, requestMatchResult); - } - - if (requestMatchResult.AverageTotalScore > 0.99) - results.Add(log, requestMatchResult); - } - - return new ReadOnlyCollection(results.OrderBy(x => x.Value).Select(x => x.Key).ToList()); - } - } - - /// - /// Resets the LogEntries. - /// - [PublicAPI] - public void ResetLogEntries() - { - lock (((ICollection)_options.LogEntries).SyncRoot) - { - _options.LogEntries.Clear(); - } - } - - /// - /// Deletes the mapping. - /// - /// The unique identifier. - [PublicAPI] - public bool DeleteLogEntry(Guid guid) - { - lock (((ICollection)_options.LogEntries).SyncRoot) - { - // Check a logentry exists with the same GUID, if so, remove it. - var existing = _options.LogEntries.FirstOrDefault(m => m.Guid == guid); - if (existing != null) - { - _options.LogEntries.Remove(existing); - return true; - } - - return false; - } - } - } -} + /// + [PublicAPI] + public event NotifyCollectionChangedEventHandler LogEntriesChanged + { + add + { + lock (((ICollection)_options.LogEntries).SyncRoot) + { + _options.LogEntries.CollectionChanged += value; + } + } + remove + { + lock (((ICollection)_options.LogEntries).SyncRoot) + { + _options.LogEntries.CollectionChanged -= value; + } + } + } + + /// + /// Gets the request logs. + /// + [PublicAPI] + public IEnumerable LogEntries + { + get + { + lock (((ICollection)_options.LogEntries).SyncRoot) + { + return new ReadOnlyCollection(_options.LogEntries); + } + } + } + + /// + /// The search log-entries based on matchers. + /// + /// The matchers. + /// The . + [PublicAPI] + public IEnumerable FindLogEntries([NotNull] params IRequestMatcher[] matchers) + { + lock (((ICollection)_options.LogEntries).SyncRoot) + { + var results = new Dictionary(); + + foreach (var log in _options.LogEntries) + { + var requestMatchResult = new RequestMatchResult(); + foreach (var matcher in matchers) + { + matcher.GetMatchingScore(log.RequestMessage, requestMatchResult); + } + + if (requestMatchResult.AverageTotalScore > MatchScores.AlmostPerfect) + { + results.Add(log, requestMatchResult); + } + } + + return new ReadOnlyCollection(results.OrderBy(x => x.Value).Select(x => x.Key).ToList()); + } + } + + /// + /// Resets the LogEntries. + /// + [PublicAPI] + public void ResetLogEntries() + { + lock (((ICollection)_options.LogEntries).SyncRoot) + { + _options.LogEntries.Clear(); + } + } + + /// + /// Deletes the mapping. + /// + /// The unique identifier. + [PublicAPI] + public bool DeleteLogEntry(Guid guid) + { + lock (((ICollection)_options.LogEntries).SyncRoot) + { + // Check a logentry exists with the same GUID, if so, remove it. + var existing = _options.LogEntries.FirstOrDefault(m => m.Guid == guid); + if (existing != null) + { + _options.LogEntries.Remove(existing); + return true; + } + + return false; + } + } + } +} diff --git a/src/WireMock.Net/Server/FluentMockServer.cs b/src/WireMock.Net/Server/FluentMockServer.cs index c205560a..b4d4a139 100644 --- a/src/WireMock.Net/Server/FluentMockServer.cs +++ b/src/WireMock.Net/Server/FluentMockServer.cs @@ -23,7 +23,6 @@ namespace WireMock.Server { private const int ServerStartDelay = 100; private readonly IOwinSelfHost _httpServer; - private readonly object _syncRoot = new object(); private readonly WireMockMiddlewareOptions _options = new WireMockMiddlewareOptions(); /// @@ -293,10 +292,7 @@ namespace WireMock.Server [PublicAPI] public void AddGlobalProcessingDelay(TimeSpan delay) { - lock (_syncRoot) - { - _options.RequestProcessingDelay = delay; - } + _options.RequestProcessingDelay = delay; } /// @@ -305,10 +301,7 @@ namespace WireMock.Server [PublicAPI] public void AllowPartialMapping() { - lock (_syncRoot) - { - _options.AllowPartialMapping = true; - } + _options.AllowPartialMapping = true; } /// @@ -323,10 +316,7 @@ namespace WireMock.Server Check.NotNull(password, nameof(password)); string authorization = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password)); - lock (_syncRoot) - { - _options.AuthorizationMatcher = new RegexMatcher("^(?i)BASIC " + authorization + "$"); - } + _options.AuthorizationMatcher = new RegexMatcher("^(?i)BASIC " + authorization + "$"); } /// @@ -335,10 +325,7 @@ namespace WireMock.Server [PublicAPI] public void RemoveBasicAuthentication() { - lock (_syncRoot) - { - _options.AuthorizationMatcher = null; - } + _options.AuthorizationMatcher = null; } /// @@ -348,10 +335,8 @@ namespace WireMock.Server [PublicAPI] public void SetMaxRequestLogCount([CanBeNull] int? maxRequestLogCount) { - lock (_syncRoot) - { - _options.MaxRequestLogCount = maxRequestLogCount; - } + _options.MaxRequestLogCount = maxRequestLogCount; + } /// @@ -361,10 +346,7 @@ namespace WireMock.Server [PublicAPI] public void SetRequestLogExpirationDuration([CanBeNull] int? requestLogExpirationDuration) { - lock (_syncRoot) - { - _options.RequestLogExpirationDuration = requestLogExpirationDuration; - } + _options.RequestLogExpirationDuration = requestLogExpirationDuration; } /// diff --git a/src/WireMock.Net/Util/NamedReaderWriterLocker.cs b/src/WireMock.Net/Util/NamedReaderWriterLocker.cs new file mode 100644 index 00000000..82e73fd1 --- /dev/null +++ b/src/WireMock.Net/Util/NamedReaderWriterLocker.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace WireMock.Util +{ + /// + /// http://johnculviner.com/achieving-named-lock-locker-functionality-in-c-4-0/ + /// + internal class NamedReaderWriterLocker + { + private readonly ConcurrentDictionary _lockDict = new ConcurrentDictionary(); + + public ReaderWriterLockSlim GetLock(string name) + { + return _lockDict.GetOrAdd(name, s => new ReaderWriterLockSlim()); + } + + public TResult RunWithReadLock(string name, Func body) + { + var rwLock = GetLock(name); + try + { + rwLock.EnterReadLock(); + return body(); + } + finally + { + rwLock.ExitReadLock(); + } + } + + public void RunWithReadLock(string name, Action body) + { + var rwLock = GetLock(name); + try + { + rwLock.EnterReadLock(); + body(); + } + finally + { + rwLock.ExitReadLock(); + } + } + + public TResult RunWithWriteLock(string name, Func body) + { + var rwLock = GetLock(name); + try + { + rwLock.EnterWriteLock(); + return body(); + } + finally + { + rwLock.ExitWriteLock(); + } + } + + public void RunWithWriteLock(string name, Action body) + { + var rwLock = GetLock(name); + try + { + rwLock.EnterWriteLock(); + body(); + } + finally + { + rwLock.ExitWriteLock(); + } + } + + public void RemoveLock(string name) + { + ReaderWriterLockSlim o; + _lockDict.TryRemove(name, out o); + } + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/ObservableLogEntriesTest.cs b/test/WireMock.Net.Tests/ObservableLogEntriesTest.cs index c5178ae5..1bd2f407 100644 --- a/test/WireMock.Net.Tests/ObservableLogEntriesTest.cs +++ b/test/WireMock.Net.Tests/ObservableLogEntriesTest.cs @@ -1,12 +1,6 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; using System.Net.Http; -using System.Text; -using System.Threading.Tasks; using NFluent; -using RestEase; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; using WireMock.Server; @@ -21,7 +15,7 @@ namespace WireMock.Net.Tests [Fact] public async void Test() { - // given + // Assign _server = FluentMockServer.Start(); _server @@ -29,16 +23,15 @@ namespace WireMock.Net.Tests .WithPath("/foo") .UsingGet()) .RespondWith(Response.Create() - .WithStatusCode(200) .WithBody(@"{ msg: ""Hello world!""}")); - var count = 0; + int count = 0; _server.LogEntriesChanged += (sender, args) => count++; - // when - var response = await new HttpClient().GetAsync("http://localhost:" + _server.Ports[0] + "/foo"); + // Act + await new HttpClient().GetAsync("http://localhost:" + _server.Ports[0] + "/foo"); - // then + // Assert Check.That(count).Equals(1); }