Skip to content

Commit

Permalink
Merge pull request #2 from chickensoft-games/test/file-writer
Browse files Browse the repository at this point in the history
test: file writer
  • Loading branch information
wlsnmrk authored Feb 1, 2025
2 parents c6c74b6 + cd9d28c commit 12423dc
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 18 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/auto_release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ jobs:
- name: 🧾 Checkout
uses: actions/checkout@v4
with:
# Use your GitHub Personal Access Token variable here.
token: ${{ secrets.GH_BASIC }}
lfs: true
submodules: 'recursive'

Expand Down
1 change: 0 additions & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ jobs:
- name: 🧾 Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.GH_BASIC }}
lfs: true
submodules: 'recursive'
fetch-depth: 0 # So we can get all tags.
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ jobs:
- name: 🧾 Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.GH_BASIC }}
lfs: true
submodules: 'recursive'

Expand Down
4 changes: 2 additions & 2 deletions Chickensoft.Log.Tests/badges/branch_coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions Chickensoft.Log.Tests/badges/line_coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified Chickensoft.Log.Tests/coverage.sh
100644 → 100755
Empty file.
115 changes: 115 additions & 0 deletions Chickensoft.Log.Tests/test/src/FileWriterTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
namespace Chickensoft.Log.Tests;

using System;
using System.IO;
using Shouldly;

public class FileWriterStreamTester : IDisposable {
private bool _isDisposed;
private readonly MemoryStream _memoryStream;

public FileWriterStreamTester(
string filename = FileWriter.DEFAULT_FILE_NAME
) {
_memoryStream = new MemoryStream();

FileWriter.AppendText = fileName => new StreamWriter(
_memoryStream,
System.Text.Encoding.UTF8,
bufferSize: 1024,
leaveOpen: true
);
}

public string GetString() {
_memoryStream.Position = 0;
using var reader = new StreamReader(_memoryStream);
var result = reader.ReadToEnd();
return result;
}

public void Dispose() {
if (_isDisposed) { return; }

GC.SuppressFinalize(this);
_isDisposed = true;

FileWriter.AppendText = FileWriter.AppendTextDefault;
_memoryStream.Dispose();
}
}

public class FileWriterTest {
[Fact]
public void DefaultFileName() {
FileWriter.DefaultFileName.ShouldBe(FileWriter.DEFAULT_FILE_NAME);

var filename = "test.log";
FileWriter.DefaultFileName = filename;
FileWriter.DefaultFileName.ShouldBe(filename);

FileWriter.DefaultFileName = FileWriter.DEFAULT_FILE_NAME;
}

[Fact]
public void DefaultInstance() {
var writer = FileWriter.Instance();
writer.ShouldNotBeNull();
writer.ShouldBeOfType<FileWriter>();
}

[Fact]
public void NewInstance() {
var filename = "test.log";
var writer = FileWriter.Instance(filename);
writer.ShouldNotBeNull();
writer.ShouldBeOfType<FileWriter>();
}

[Fact]
public void ReusesInstanceAndRemoves() {
var filename = "test.log";
var writer1 = FileWriter.Instance(filename);
var writer2 = FileWriter.Instance(filename);
writer1.ShouldBeSameAs(writer2);

FileWriter.Remove(filename).ShouldBeSameAs(writer1);
FileWriter.Remove(filename).ShouldBeNull();
}

[Fact]
public void WriteMessage() {
using var tester = new FileWriterStreamTester();

var writer = FileWriter.Instance();
var value = "test message";

writer.WriteMessage(value);

tester.GetString().ShouldBe(value + Environment.NewLine);
}

[Fact]
public void WriteWarning() {
using var tester = new FileWriterStreamTester();

var writer = FileWriter.Instance();
var value = "test message";

writer.WriteWarning(value);

tester.GetString().ShouldBe(value + Environment.NewLine);
}

[Fact]
public void WriteError() {
using var tester = new FileWriterStreamTester();

var writer = FileWriter.Instance();
var value = "test message";

writer.WriteError(value);

tester.GetString().ShouldBe(value + Environment.NewLine);
}
}
15 changes: 15 additions & 0 deletions Chickensoft.Log.Tests/test/src/LogTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,19 @@ public void PrintsStackTraceWithMessage() {
Invoked.Once
);
}

[Fact]
public void AddsAndRemovesWriters() {
var writerA = new Mock<ILogWriter>();
var writerB = new Mock<ILogWriter>();
var log = new Log(nameof(LogTest), [writerA.Object]);

log.AddWriter(writerB.Object);

log._writers.ShouldBe([writerA.Object, writerB.Object]);

log.RemoveWriter(writerA.Object);

log._writers.ShouldBe([writerB.Object]);
}
}
3 changes: 3 additions & 0 deletions Chickensoft.Log/src/Assembly.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Chickensoft.Log.Tests")]
42 changes: 33 additions & 9 deletions Chickensoft.Log/src/FileWriter.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
namespace Chickensoft.Log;

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;

/// <summary>
/// An <see cref="ILogWriter"/> that directs output of an <see cref="ILog"/>
/// to a file.
/// </summary>
[ExcludeFromCodeCoverage(Justification = "File output is untestable")]
public sealed class FileWriter : ILogWriter {
internal delegate StreamWriter AppendTextDelegate(string text);
internal static AppendTextDelegate AppendTextDefault { get; } =
File.AppendText;
internal static AppendTextDelegate AppendText { get; set; } =
AppendTextDefault;

// protect static members from simultaneous thread access
private static readonly object _singletonLock = new();
// Implemented as a pseudo-singleton to enforce one truncation per file per
// execution
private static readonly Dictionary<string, FileWriter> _instances = [];

private static string _defaultFileName = "output.log";
/// <summary>The default filename for logs.</summary>
public const string DEFAULT_FILE_NAME = "output.log";

#pragma warning disable IDE0032 // Use auto property
private static string _defaultFileName = DEFAULT_FILE_NAME;
#pragma warning restore IDE0032 // Use auto property

/// <summary>
/// The default file name that will be used when creating a
/// <see cref="FileWriter"/> if no filename is specified. Defaults to
Expand Down Expand Up @@ -87,6 +97,24 @@ public static FileWriter Instance() {
}
}

/// <summary>
/// Remove a <see cref="FileWriter"/> that had previously been created.
/// While not necessary, this can free up resources if writing to many
/// different log files.
/// </summary>
/// <param name="fileName">Filename for the log.</param>
/// <returns>The file writer, if one existed for the given filename.
/// Otherwise, just null.</returns>
public static FileWriter? Remove(string fileName) {
lock (_singletonLock) {
if (_instances.TryGetValue(fileName, out var writer)) {
_instances.Remove(fileName);
return writer;
}
}
return null;
}

private readonly object _writingLock = new();

/// <summary>
Expand All @@ -102,14 +130,10 @@ private FileWriter(string fileName) {
}
}

[SuppressMessage("Style",
"IDE0063:Use simple 'using' statement",
Justification = "Prefer block, to explicitly delineate scope")]
private void WriteLine(string message) {
lock (_writingLock) {
using (var sw = File.AppendText(FileName)) {
sw.WriteLine(message);
}
using var sw = AppendText(FileName);
sw.WriteLine(message);
}
}

Expand Down
2 changes: 1 addition & 1 deletion Chickensoft.Log/src/Log.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public sealed class Log : ILog {
/// <inheritdoc/>
public string Name { get; }

private readonly List<ILogWriter> _writers = [];
internal readonly List<ILogWriter> _writers = [];

/// <summary>
/// The formatter that will be used to format messages before writing them
Expand Down

0 comments on commit 12423dc

Please sign in to comment.