# GitConverter.TestsApp.Logging Integration test suite for file-based logging functionality in GitConverter. --- ## Table of Contents - [Purpose](#purpose) - [Test Structure](#test-structure) - [Key Test Patterns](#key-test-patterns) - [Test Coverage](#test-coverage) - [Running Tests](#running-tests) - [Writing New Tests](#writing-new-tests) - [Troubleshooting](#troubleshooting) --- ## Purpose Provide comprehensive integration tests for the `GitConverter.Lib.Logging` namespace, specifically: - **File logging configuration** (`Log.SetFile`) - **Log level filtering** (All, Debug, Info, Warn, Error, Fatal, Off) - **UTF-8 encoding support** (international characters, emoji) - **Exception logging with stack traces** - **Configuration reset behavior** (multiple `SetFile` calls) These tests validate the **end-to-end behavior** of the logging system in realistic scenarios. --- ## Test Structure ### Base Test Class: `FileLoggingTests` Located in `GitConverter.TestsApp/Logging/FileLoggingTests.cs` **Responsibilities:** - Create isolated temporary directories for each test run - Configure file logging via `Log.SetFile` - Clean up resources in `Dispose` (delete temp files, disable logging) - Use helper methods to wait for file writes (async file I/O) **Isolation:** - Each test instance gets a unique temp directory: `Path.GetTempPath()/GitConverter.Tests/{GUID}` - Tests disable global logging in `Dispose` to avoid cross-test interference - Consider running in a separate test collection if parallel execution causes issues --- ## Key Test Patterns ### 1. Console Capture (CLI Tests) **Pattern:** - Redirect `Console.Out` / `Console.Error` to `StringWriter` in test setup and restore in teardown. - Combine and assert on captured output for usage text, repair messages, warnings, or conversion summaries. **Example:** ```csharp using var outWriter = new StringWriter(); using var errWriter = new StringWriter(); var originalOut = Console.Out; var originalErr = Console.Error; Console.SetOut(outWriter); Console.SetError(errWriter); try { var exitCode = Program.Run(args); var stdout = outWriter.ToString(); var stderr = errWriter.ToString(); Assert.Contains("Expected", stdout + stderr); } finally { Console.SetOut(originalOut); Console.SetError(originalErr); } ``` ### 2. File Logging Tests **Pattern:** - Use `Log.SetFile(logFilePath, logLevel)` then `Log.Enable()`. - Use a unique temporary directory per test (recommended pattern in `FileLoggingTests`). - Wait briefly for log4net to flush and for the file to appear before reading. - Read using `FileTestHelpers.ReadAllTextAllowLocked` (opens with `FileShare.ReadWrite` and retries) to avoid transient locks. **Example:** ```csharp var logFile = Path.Combine(_tempDir, "test.log"); Log.SetFile(logFile, "Info"); Log.Enable(); Log.Debug("should be filtered"); Log.Info("should appear"); WaitForFileWrite(logFile); var content = FileTestHelpers.ReadAllTextAllowLocked(logFile); Assert.DoesNotContain("should be filtered", content); Assert.Contains("should appear", content); Log.Disable(); ``` ### 3. WaitForFileWrite Helper **Pattern:** Use a retry loop to accommodate asynchronous writes on CI: ```csharp private static void WaitForFileWrite(string path, int attempts = 10, int delayMs = 100) { for (int i = 0; i < attempts; i++) { if (File.Exists(path)) return; Thread.Sleep(delayMs); } } ``` --- ## Test Coverage ### Covered Scenarios - Basic write verification (default level) - `Info`, `Warn`, `Error`, `Off`, `Debug` level behaviors - Invalid level falls back to `All` - Case-insensitive log level parsing - UTF‑8 encoding and emoji support - Exception logging with stack traces - Multiple `SetFile` calls reset configuration and avoid duplicate appenders --- ## Running Tests ### Execute Logging Tests - Run all logging tests: ```bash dotnet test --filter "FullyQualifiedName~GitConverter.TestsApp.Logging" ``` - Run a single test: ```bash dotnet test --filter "FullyQualifiedName~FileLoggingTests.Log_SetFile_WithInfoLevel_FiltersDebugMessages" ``` ### CI Considerations - On CI, increase `WaitForFileWrite` attempts/delay for stability. --- ## Writing New Tests ### Checklist - Use unique log file names inside a unique temp directory - Configure `Log.SetFile(...)` then `Log.Enable()` - Use `WaitForFileWrite` before reading - Use `FileTestHelpers.ReadAllTextAllowLocked` to read logs - Call `Log.Disable()` in test teardown - Assert both presence and absence of messages when testing filtering --- ## Troubleshooting ### Common Issues - **Log file not created** - Confirm `Log.Enable()` was called after `SetFile`. - Ensure the test process has write permissions to the temp directory. - Log diagnostic info in the test (temp dir path, directory existence). - **Missing or filtered messages** - Verify the configured log level. `Info` filters `Debug`, `Warn` filters `Debug`/`Info`, etc. - For debugging tests, set level to `Debug` or `All`. - Ensure `Log.Current` is `Log4NetAdapter` (not `NullLogger`) before emitting messages. - **Unicode characters appear as `?`** - Ensure `Log.SetFile` configures UTF-8 (the code sets `Encoding = Encoding.UTF8`). - Read the log with UTF-8: `File.ReadAllText(path, Encoding.UTF8)`. - Use a UTF-8 capable viewer (VS Code, Notepad++). - **Tests interfering with each other** - Always call `Log.Disable()` in cleanup (Dispose). - Use unique temp directories per test (GUID-based). - If necessary, group logging tests into a test collection with `DisableParallelization = true`. - **CI flakiness** - Increase wait attempts/delay in `WaitForFileWrite`. - Ensure CI environment allows writing to the OS temp folder or provide a dedicated writable path. --- ## Contact / Notes - If you add long-running logging tests, mark them or place them in a separate collection to avoid slowing the main test run. - Keep tests deterministic: assert on the specific strings produced by the logging layout.