diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 496525d..37ec665 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,44 +1,38 @@ -image: mcr.microsoft.com/dotnet/sdk +# The image has to use the same version as the .NET UnitTest project +image: mcr.microsoft.com/dotnet/sdk:6.0 variables: TZ: "Europe/Berlin" + LANG: "de" -stages: - - build - - test - - deploy -build: - stage: build - tags: - - docker - script: - - bash build.sh - artifacts: - paths: - - artifacts/*.nupkg - - artifacts/*.snupkg - expire_in: 1 day - -test: - stage: test +debug-job: tags: - docker + except: + - tags # branch-coverage - # coverage: '/Total[^|]*\|[^|]*\|\s*([0-9.%]+)/' + #coverage: '/Total[^|]*\|[^|]*\|\s*([0-9.%]+)/' # line-coverage coverage: '/Total[^|]*\|\s*([0-9.%]+)/' script: - - dotnet test -c Release - dependencies: - - build + - dotnet restore --no-cache --force + - dotnet build -c Debug --nologo --no-restore --no-incremental + - dotnet test -c Debug --nologo --no-restore --no-build + - dotnet nuget push -k $BAGET_APIKEY -s https://nuget.am-wd.de/v3/index.json --skip-duplicate **/*.nupkg -deploy: - stage: deploy + +release-job: tags: - docker + only: + - tags + # branch-coverage + #coverage: '/Total[^|]*\|[^|]*\|\s*([0-9.%]+)/' + # line-coverage + coverage: '/Total[^|]*\|\s*([0-9.%]+)/' script: - - dotnet nuget push -k $APIKEY -s https://nuget.am-wd.de/v3/index.json --skip-duplicate artifacts/*.nupkg - dependencies: - - build - - test + - dotnet restore --no-cache --force + - dotnet build -c Release --nologo --no-restore --no-incremental + - dotnet test -c Release --nologo --no-restore --no-build + - dotnet nuget push -k $BAGET_APIKEY -s https://nuget.am-wd.de/v3/index.json --skip-duplicate **/*.nupkg diff --git a/AMWD.Common.Moq/AMWD.Common.Moq.csproj b/AMWD.Common.Moq/AMWD.Common.Moq.csproj index 11bd45c..9f28ded 100644 --- a/AMWD.Common.Moq/AMWD.Common.Moq.csproj +++ b/AMWD.Common.Moq/AMWD.Common.Moq.csproj @@ -29,7 +29,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/AMWD.Common/Extensions/EnumExtensions.cs b/AMWD.Common/Extensions/EnumExtensions.cs index 1a5cece..ee4d431 100644 --- a/AMWD.Common/Extensions/EnumExtensions.cs +++ b/AMWD.Common/Extensions/EnumExtensions.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.ComponentModel; +using System.ComponentModel.DataAnnotations; using System.Linq; namespace System @@ -40,5 +41,13 @@ namespace System /// The description or the string representation of the value. public static string GetDescription(this Enum value) => value.GetAttribute()?.Description ?? value.ToString(); + + /// + /// Returns the name from . + /// + /// The enum value. + /// The display name or the string representation of the value. + public static string GetDisplayName(this Enum value) + => value.GetAttribute()?.Name ?? value.ToString(); } } diff --git a/AMWD.Common/Logging/FileLogger.cs b/AMWD.Common/Logging/FileLogger.cs new file mode 100644 index 0000000..73d233a --- /dev/null +++ b/AMWD.Common/Logging/FileLogger.cs @@ -0,0 +1,329 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("UnitTests")] + +namespace AMWD.Common.Logging +{ + /// + /// Implements a file logging based on the interface. + /// + /// + /// This implementation is also implementing the interface! + ///
+ /// Inspired by + ///
+ /// + /// + public class FileLogger : ILogger, IDisposable + { + #region Fields + + private bool isDisposed = false; + private readonly CancellationTokenSource cancellationTokenSource = new(); + + private readonly StreamWriter fileWriter; + private readonly Task writeTask; + + private readonly AsyncQueue queue = new(); + + #endregion Fields + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The log file. + /// A value indicating whether to append lines to an existing file (Default: false). + /// The file encoding (Default: ). + public FileLogger(string file, bool append = false, Encoding encoding = null) + { + FileName = file; + fileWriter = new StreamWriter(FileName, append, encoding ?? Encoding.UTF8); + writeTask = Task.Run(() => WriteFileAsync(cancellationTokenSource.Token)); + } + + /// + /// Initializes a new named instance of the class. + /// + /// The log file. + /// The logger name. + /// A value indicating whether to append lines to an existing file. + /// The file encoding. + public FileLogger(string file, string name, bool append = false, Encoding encoding = null) + : this(file, append, encoding) + { + Name = name; + } + + /// + /// Initializes a new instance of the class. + /// + /// The log file. + /// The logger name. + /// The parent logger. + /// A value indicating whether to append lines to an existing file. + /// The file encoding. + public FileLogger(string file, string name, FileLogger parentLogger, bool append = false, Encoding encoding = null) + : this(file, name, append, encoding) + { + ParentLogger = parentLogger; + } + + /// + /// Initializes a new instance of the class. + /// + /// The log file. + /// The logger name. + /// The scope provider. + /// A value indicating whether to append lines to an existing file. + /// The file encoding. + public FileLogger(string file, string name, IExternalScopeProvider scopeProvider, bool append = false, Encoding encoding = null) + : this(file, name, append, encoding) + { + ScopeProvider = scopeProvider; + } + + /// + /// Initializes a new instance of the class. + /// + /// The log file. + /// The logger name. + /// The parent logger. + /// The scope provider. + /// A value indicating whether to append lines to an existing file. + /// The file encoding. + public FileLogger(string file, string name, FileLogger parentLogger, IExternalScopeProvider scopeProvider, bool append = false, Encoding encoding = null) + : this(file, name, parentLogger, append, encoding) + { + ScopeProvider = scopeProvider; + } + + #endregion Constructors + + #region Properties + + /// + /// Gets the log file. + /// + public string FileName { get; } + + /// + /// Gets the name of the logger. + /// + public string Name { get; } + + /// + /// Gets the parent logger. + /// + public FileLogger ParentLogger { get; } + + /// + /// Gets or sets the timestamp format. + /// + public string TimestampFormat { get; set; } + + /// + /// Gets or sets a value indicating whether the timestamp is in UTC. + /// + public bool UseUtcTimestamp { get; set; } + + /// + /// Gets or sets the minimum level to log. + /// + public LogLevel MinLevel { get; set; } + + internal IExternalScopeProvider ScopeProvider { get; } + + #endregion Properties + + #region ILogger implementation + + /// + public IDisposable BeginScope(TState state) + { + if (isDisposed) + throw new ObjectDisposedException(GetType().FullName); + + return ScopeProvider?.Push(state) ?? NullScope.Instance; + } + + /// + public bool IsEnabled(LogLevel logLevel) + { + if (isDisposed) + throw new ObjectDisposedException(GetType().FullName); + + return logLevel >= MinLevel; + } + + /// + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + if (isDisposed) + throw new ObjectDisposedException(GetType().FullName); + + if (!IsEnabled(logLevel)) + return; + + if (formatter == null) + throw new ArgumentNullException(nameof(formatter)); + + string message = formatter(state, exception); + + if (!string.IsNullOrEmpty(message) || exception != null) + { + if (ParentLogger == null) + { + WriteMessage(Name, logLevel, eventId.Id, message, exception); + } + else + { + ParentLogger.WriteMessage(Name, logLevel, eventId.Id, message, exception); + } + } + } + + #endregion ILogger implementation + + #region IDisposable implementation + + /// + public void Dispose() + { + if (!isDisposed) + { + isDisposed = true; + + cancellationTokenSource.Cancel(); + writeTask.GetAwaiter().GetResult(); + fileWriter.Dispose(); + } + } + + #endregion IDisposable implementation + + #region Private methods + + private void WriteMessage(string name, LogLevel logLevel, int eventId, string message, Exception exception) + { + queue.Enqueue(new QueueItem + { + Timestamp = UseUtcTimestamp ? DateTime.UtcNow : DateTime.Now, + Name = name, + LogLevel = logLevel, + EventId = eventId, + Message = message, + Exception = exception + }); + } + + private async Task WriteFileAsync(CancellationToken token) + { + string timestampPadding = ""; + string logLevelPadding = new(' ', 7); + + var sb = new StringBuilder(); + + while (!token.IsCancellationRequested) + { + QueueItem[] items; + try + { + items = await queue.DequeueAvailableAsync(cancellationToken: token); + } + catch (OperationCanceledException) + { + return; + } + + foreach (var item in items) + { + sb.Clear(); + + string timestamp = ""; + string message = item.Message; + + if (!string.IsNullOrEmpty(TimestampFormat)) + { + timestamp = item.Timestamp.ToString(TimestampFormat) + " | "; + sb.Append(timestamp); + timestampPadding = new string(' ', timestamp.Length); + } + + string logLevel = item.LogLevel switch + { + LogLevel.Trace => "TRCE | ", + LogLevel.Debug => "DBUG | ", + LogLevel.Information => "INFO | ", + LogLevel.Warning => "WARN | ", + LogLevel.Error => "FAIL | ", + LogLevel.Critical => "CRIT | ", + _ => " | ", + }; + sb.Append(logLevel); + logLevelPadding = new string(' ', logLevel.Length); + + if (ScopeProvider != null) + { + int initLength = sb.Length; + + ScopeProvider.ForEachScope((scope, state) => + { + var (builder, length) = state; + bool first = length == builder.Length; + builder.Append(first ? "=>" : " => ").Append(scope); + }, (sb, initLength)); + + if (sb.Length > initLength) + sb.Insert(initLength, timestampPadding + logLevelPadding); + } + + if (item.Exception != null) + { + if (!string.IsNullOrWhiteSpace(message)) + { + message += Environment.NewLine + item.Exception.ToString(); + } + else + { + message = item.Exception.ToString(); + } + } + + if (!string.IsNullOrWhiteSpace(item.Name)) + sb.Append($"[{item.Name}] "); + + sb.Append(message.Replace("\n", "\n" + timestampPadding + logLevelPadding)); + + await fileWriter.WriteLineAsync(sb.ToString()); + } + + await fileWriter.FlushAsync(); + } + } + + #endregion Private methods + + private class QueueItem + { + public DateTime Timestamp { get; set; } + + public string Name { get; set; } + + public LogLevel LogLevel { get; set; } + + public EventId EventId { get; set; } + + public string Message { get; set; } + + public Exception Exception { get; set; } + } + } +} diff --git a/AMWD.Common/Logging/NullScope.cs b/AMWD.Common/Logging/NullScope.cs new file mode 100644 index 0000000..260e8b0 --- /dev/null +++ b/AMWD.Common/Logging/NullScope.cs @@ -0,0 +1,25 @@ +using System; + +namespace AMWD.Common.Logging +{ + /// + /// An empty scope without any logic. + /// + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + internal class NullScope : IDisposable + { + /// + /// The instance to use. + /// + public static NullScope Instance { get; } = new NullScope(); + + private NullScope() + { + } + + /// + public void Dispose() + { + } + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index d95ecb7..f0d6bbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Added `directory.build.props` and `directory.build.targets` -- Added `ArReader` and `ArWriter` for Unix archives -- Added `TarReader` and `TarWriter` for TAR archives +- `directory.build.props` and `directory.build.targets` +- `GetDisplayName()` extension for enums (`DisplayAttribute(Name = "")`) +- `FileLogger` as additional `ILogger` implementation (from `Microsoft.Extensions.Logging`) +- `ArReader` and `ArWriter` for Unix archives +- `TarReader` and `TarWriter` for TAR archives ## [v1.10.0](https://git.am-wd.de/AM.WD/common/compare/v1.9.0...v1.10.0) - 2022-09-18 diff --git a/UnitTests/Common/Extensions/EnumExtensionsTests.cs b/UnitTests/Common/Extensions/EnumExtensionsTests.cs index babac92..7f0cd93 100644 --- a/UnitTests/Common/Extensions/EnumExtensionsTests.cs +++ b/UnitTests/Common/Extensions/EnumExtensionsTests.cs @@ -1,7 +1,8 @@ using System; +using System.ComponentModel.DataAnnotations; using System.Linq; -using UnitTests.Common.Utils; using Microsoft.VisualStudio.TestTools.UnitTesting; +using UnitTests.Common.Utils; using DescriptionAttribute = System.ComponentModel.DescriptionAttribute; namespace UnitTests.Common.Extensions @@ -107,6 +108,22 @@ namespace UnitTests.Common.Extensions Assert.IsFalse(list.Any()); } + [TestMethod] + public void ShouldReturnDisplayNameOrStringRepresentation() + { + // arrange + var enumWithDisplayName = TestEnum.Two; + var enumWithoutDisplayName = TestEnum.Zero; + + // act + string displayName = enumWithDisplayName.GetDisplayName(); + string noDisplayName = enumWithoutDisplayName.GetDisplayName(); + + // assert + Assert.AreEqual("Zwei", displayName); + Assert.AreEqual(enumWithoutDisplayName.ToString(), noDisplayName); + } + internal enum TestEnum { [CustomMultiple("nix")] @@ -115,6 +132,7 @@ namespace UnitTests.Common.Extensions Zero, [Description("Eins")] One, + [Display(Name = "Zwei")] Two, } } diff --git a/UnitTests/Common/Logging/FileLoggerTests.cs b/UnitTests/Common/Logging/FileLoggerTests.cs new file mode 100644 index 0000000..935e243 --- /dev/null +++ b/UnitTests/Common/Logging/FileLoggerTests.cs @@ -0,0 +1,371 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using AMWD.Common.Logging; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace UnitTests.Common.Logging +{ + [TestClass] + public class FileLoggerTests + { + private Mock streamWriterMock; + + private List lines; + + [TestInitialize] + public void Initialize() + { + lines = new List(); + } + + [TestMethod] + public void ShouldCreateInstance() + { + // arrange + string path = Path.GetTempFileName(); + + try + { + // act + using var logger = new FileLogger(path); + + // assert + Assert.IsNotNull(logger); + Assert.IsTrue(File.Exists(path)); + + Assert.AreEqual(path, logger.FileName); + Assert.AreEqual(LogLevel.Trace, logger.MinLevel); + Assert.IsNull(logger.Name); + Assert.IsNull(logger.ParentLogger); + Assert.IsNull(logger.TimestampFormat); + Assert.IsNull(logger.ScopeProvider); + } + finally + { + File.Delete(path); + } + } + + [TestMethod] + public void ShouldCreateInstanceAsNamedInstance() + { + // arrange + string name = "NamedInstance"; + string path = Path.GetTempFileName(); + + try + { + // act + using var logger = new FileLogger(path, name); + + // assert + Assert.IsNotNull(logger); + Assert.IsTrue(File.Exists(path)); + + Assert.AreEqual(path, logger.FileName); + Assert.AreEqual(LogLevel.Trace, logger.MinLevel); + Assert.AreEqual(name, logger.Name); + + Assert.IsNull(logger.ParentLogger); + Assert.IsNull(logger.TimestampFormat); + Assert.IsNull(logger.ScopeProvider); + } + finally + { + File.Delete(path); + } + } + + [TestMethod] + [ExpectedException(typeof(ObjectDisposedException))] + public void ShouldThrowDisposedOnIsEnabled() + { + // arrange + var logger = GetFileLogger(); + + // act + logger.Dispose(); + logger.IsEnabled(LogLevel.Error); + + // assert - ObjectDisposedException + Assert.Fail(); + } + + [TestMethod] + [ExpectedException(typeof(ObjectDisposedException))] + public void ShouldThrowDisposedOnLog() + { + // arrange + var logger = GetFileLogger(); + + // act + logger.Dispose(); + logger.Log(LogLevel.None, "Some Message"); + + // assert - ObjectDisposedException + Assert.Fail(); + } + + [TestMethod] + [ExpectedException(typeof(ObjectDisposedException))] + public void ShouldThrowDisposedOnBeginScope() + { + // arrange + var logger = GetFileLogger(); + + // act + logger.Dispose(); + logger.BeginScope("foo"); + + // assert - ObjectDisposedException + Assert.Fail(); + } + + [DataTestMethod] + [DataRow(LogLevel.Trace, false)] + [DataRow(LogLevel.Debug, false)] + [DataRow(LogLevel.Information, false)] + [DataRow(LogLevel.Warning, true)] + [DataRow(LogLevel.Error, true)] + [DataRow(LogLevel.Critical, true)] + [DataRow(LogLevel.None, true)] + public void ShouldReturnIsEnabled(LogLevel logLevel, bool expectedResult) + { + // arrange + using var logger = GetFileLogger(); + logger.MinLevel = LogLevel.Warning; + + // act + bool result = logger.IsEnabled(logLevel); + + // assert + Assert.AreEqual(expectedResult, result); + } + + [TestMethod] + public void ShouldLogMessage() + { + // arrange + using var logger = GetFileLogger(); + + // act + logger.Log(LogLevel.Information, "Test Message"); + SpinWait.SpinUntil(() => lines.Count == 1); + + // assert + Assert.AreEqual("INFO | Test Message", lines.First()); + + streamWriterMock.Verify(sw => sw.WriteLineAsync(It.IsAny()), Times.Once); + streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once); + streamWriterMock.VerifyNoOtherCalls(); + } + + [TestMethod] + public void ShouldLogAllLevels() + { + // arrange + using var logger = GetFileLogger(); + + // act + foreach (LogLevel level in Enum.GetValues()) + logger.Log(level, "Test Message"); + + SpinWait.SpinUntil(() => lines.Count == 7); + + // assert + streamWriterMock.Verify(sw => sw.WriteLineAsync("TRCE | Test Message"), Times.Once); + streamWriterMock.Verify(sw => sw.WriteLineAsync("DBUG | Test Message"), Times.Once); + streamWriterMock.Verify(sw => sw.WriteLineAsync("INFO | Test Message"), Times.Once); + streamWriterMock.Verify(sw => sw.WriteLineAsync("WARN | Test Message"), Times.Once); + streamWriterMock.Verify(sw => sw.WriteLineAsync("FAIL | Test Message"), Times.Once); + streamWriterMock.Verify(sw => sw.WriteLineAsync("CRIT | Test Message"), Times.Once); + streamWriterMock.Verify(sw => sw.WriteLineAsync(" | Test Message"), Times.Once); + streamWriterMock.Verify(sw => sw.FlushAsync(), Times.AtLeastOnce); + streamWriterMock.VerifyNoOtherCalls(); + } + + [TestMethod] + public void ShouldLogOnlyAboveMinLevel() + { + // arrange + using var logger = GetFileLogger(); + logger.MinLevel = LogLevel.Error; + + // act + foreach (LogLevel level in Enum.GetValues()) + logger.Log(level, "Test Message"); + + SpinWait.SpinUntil(() => lines.Count == 3); + + // assert + streamWriterMock.Verify(sw => sw.WriteLineAsync("FAIL | Test Message"), Times.Once); + streamWriterMock.Verify(sw => sw.WriteLineAsync("CRIT | Test Message"), Times.Once); + streamWriterMock.Verify(sw => sw.WriteLineAsync(" | Test Message"), Times.Once); + streamWriterMock.Verify(sw => sw.FlushAsync(), Times.AtLeastOnce); + streamWriterMock.VerifyNoOtherCalls(); + } + + [TestMethod] + public void ShouldLogWithLocalTimestamp() + { + // arrange + using var logger = GetFileLogger(); + logger.UseUtcTimestamp = false; + logger.TimestampFormat = "yyyy-MM-dd HH:mm"; + + // act + logger.LogWarning("Some Warning"); + SpinWait.SpinUntil(() => lines.Count == 1); + + // assert + + streamWriterMock.Verify(sw => sw.WriteLineAsync($"{DateTime.Now:yyyy-MM-dd HH:mm} | WARN | Some Warning"), Times.Once); + streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once); + streamWriterMock.VerifyNoOtherCalls(); + } + + [TestMethod] + public void ShouldLogWithUtcTimestamp() + { + // arrange + using var logger = GetFileLogger(); + logger.UseUtcTimestamp = true; + logger.TimestampFormat = "yyyy-MM-dd HH:mm"; + + // act + logger.LogWarning("Some Warning"); + SpinWait.SpinUntil(() => lines.Count == 1); + + // assert + streamWriterMock.Verify(sw => sw.WriteLineAsync($"{DateTime.UtcNow:yyyy-MM-dd HH:mm} | WARN | Some Warning"), Times.Once); + streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once); + streamWriterMock.VerifyNoOtherCalls(); + } + + [TestMethod] + public void ShouldUseParentLogger() + { + // arrange + string file = Path.GetTempFileName(); + try + { + using var parent = GetFileLogger(); + parent.UseUtcTimestamp = false; + parent.TimestampFormat = "yyyy-MM-dd HH:mm"; + + using var logger = new FileLogger(file, "NamedInstance", parent); + + // act + logger.LogWarning("Some Warning"); + SpinWait.SpinUntil(() => lines.Count == 1); + + // assert + streamWriterMock.Verify(sw => sw.WriteLineAsync($"{DateTime.Now:yyyy-MM-dd HH:mm} | WARN | [NamedInstance] Some Warning"), Times.Once); + streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once); + streamWriterMock.VerifyNoOtherCalls(); + + Assert.AreEqual(0, new FileInfo(file).Length); + } + finally + { + File.Delete(file); + } + } + + [TestMethod] + public void ShouldUseScopeProvider() + { + // arrange + var scopeProvider = new Mock(); + using var logger = GetFileLogger("NamedInstance", scopeProvider.Object); + + // act + using (var scope = logger.BeginScope("scope")) + { + logger.LogError("Test"); + SpinWait.SpinUntil(() => lines.Count == 1); + } + + // assert + streamWriterMock.Verify(sw => sw.WriteLineAsync("FAIL | [NamedInstance] Test"), Times.Once); + streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once); + streamWriterMock.VerifyNoOtherCalls(); + + scopeProvider.Verify(sp => sp.Push("scope"), Times.Once); + scopeProvider.Verify(sp => sp.ForEachScope(It.IsAny>(), It.IsAny()), Times.Once); + scopeProvider.VerifyNoOtherCalls(); + } + + [TestMethod] + public void ShouldLogException() + { + // arrange + using var logger = GetFileLogger(); + + // act + logger.LogCritical(new Exception("TestException"), ""); + SpinWait.SpinUntil(() => lines.Count == 1); + + // assert + streamWriterMock.Verify(sw => sw.WriteLineAsync("CRIT | System.Exception: TestException"), Times.Once); + streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once); + streamWriterMock.VerifyNoOtherCalls(); + } + + [TestMethod] + public void ShouldLogExceptionWithMessage() + { + // arrange + using var logger = GetFileLogger(); + + // act + logger.LogCritical(new Exception("TestException"), "Bad things happen..."); + SpinWait.SpinUntil(() => lines.Count == 1); + + // assert + streamWriterMock.Verify(sw => sw.WriteLineAsync($"CRIT | Bad things happen...{Environment.NewLine} System.Exception: TestException"), Times.Once); + streamWriterMock.Verify(sw => sw.FlushAsync(), Times.Once); + streamWriterMock.VerifyNoOtherCalls(); + } + + private FileLogger GetFileLogger(string name = null, IExternalScopeProvider scopeProvider = null) + { + string tmpFilePath = Path.GetTempFileName(); + try + { + streamWriterMock = new Mock(Stream.Null); + streamWriterMock + .Setup(sw => sw.WriteLineAsync(It.IsAny())) + .Callback(line => lines.Add(line)) + .Returns(Task.CompletedTask); + + FileLogger fileLogger; + if (name == null || scopeProvider == null) + { + fileLogger = new FileLogger(tmpFilePath); + } + else + { + fileLogger = new FileLogger(tmpFilePath, name, scopeProvider); + } + + var fieldInfo = fileLogger.GetType().GetField("fileWriter", BindingFlags.NonPublic | BindingFlags.Instance); + (fieldInfo.GetValue(fileLogger) as StreamWriter).Dispose(); + fieldInfo.SetValue(fileLogger, streamWriterMock.Object); + + return fileLogger; + } + finally + { + File.Delete(tmpFilePath); + } + } + } +} diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index 40fee09..26dc979 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -9,15 +9,15 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + diff --git a/build.cmd b/build.cmd deleted file mode 100644 index bff55b0..0000000 --- a/build.cmd +++ /dev/null @@ -1,52 +0,0 @@ -@echo off -set Configuration=Release - -cd "%~dp0" -powershell write-host -fore Blue Restoring solution - -rmdir /S /Q artifacts -mkdir artifacts - -dotnet restore -v q - -cd "%~dp0" -cd "AMWD.Common" -powershell write-host -fore Blue Building AMWD.Common - -rmdir /S /Q bin -dotnet build -c %Configuration% --nologo --no-incremental -move bin\%Configuration%\*.nupkg ..\artifacts -move bin\%Configuration%\*.snupkg ..\artifacts - - - -cd "%~dp0" -cd "AMWD.Common.AspNetCore" -powershell write-host -fore Blue Building AMWD.Common.AspNetCore - -rmdir /S /Q bin -dotnet build -c %Configuration% --nologo --no-incremental -move bin\%Configuration%\*.nupkg ..\artifacts -move bin\%Configuration%\*.snupkg ..\artifacts - - - -cd "%~dp0" -cd "AMWD.Common.EntityFrameworkCore" -powershell write-host -fore Blue Building AMWD.Common.EntityFrameworkCore - -rmdir /S /Q bin -dotnet build -c %Configuration% --nologo --no-incremental -move bin\%Configuration%\*.nupkg ..\artifacts -move bin\%Configuration%\*.snupkg ..\artifacts - - - -cd "%~dp0" -cd "AMWD.Common.Moq" -powershell write-host -fore Blue Building AMWD.Common.Moq - -rmdir /S /Q bin -dotnet build -c %Configuration% --nologo --no-incremental -move bin\%Configuration%\*.nupkg ..\artifacts -move bin\%Configuration%\*.snupkg ..\artifacts diff --git a/build.sh b/build.sh deleted file mode 100644 index e11297a..0000000 --- a/build.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -CONFIGURATION=Release - -cd "$(dirname "${0}")" -rm -rf artifacts -mkdir artifacts - -dotnet restore -v q - -pushd AMWD.Common -rm -rf bin -dotnet build -c ${CONFIGURATION} --nologo --no-incremental -mv bin/${CONFIGURATION}/*.nupkg ../artifacts -mv bin/${CONFIGURATION}/*.snupkg ../artifacts -popd - -pushd AMWD.Common.AspNetCore -rm -rf bin -dotnet build -c ${CONFIGURATION} --nologo --no-incremental -mv bin/${CONFIGURATION}/*.nupkg ../artifacts -mv bin/${CONFIGURATION}/*.snupkg ../artifacts -popd - -pushd AMWD.Common.EntityFrameworkCore -rm -rf bin -dotnet build -c ${CONFIGURATION} --nologo --no-incremental -mv bin/${CONFIGURATION}/*.nupkg ../artifacts -mv bin/${CONFIGURATION}/*.snupkg ../artifacts -popd - -pushd AMWD.Common.Moq -rm -rf bin -dotnet build -c ${CONFIGURATION} --nologo --no-incremental -mv bin/${CONFIGURATION}/*.nupkg ../artifacts -mv bin/${CONFIGURATION}/*.snupkg ../artifacts -popd