Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
9167701
Initial commit.
clrudolphi Jul 30, 2025
77593d8
First working example. Takes command line input as a --logger and con…
clrudolphi Jul 30, 2025
dca83fb
Merge branch 'main' into TestLogger
gasparnagy Aug 1, 2025
35bf0a3
Modified per reveiw comments. The format string for the command line …
clrudolphi Aug 6, 2025
ac846fe
Fixed tests to be OS-path aware.
clrudolphi Aug 6, 2025
f99c7d3
Created two Formatter Loggers (one for each built-in formatter) that …
clrudolphi Aug 7, 2025
0775dad
Corrected encoding of nuspec file
clrudolphi Aug 8, 2025
c611111
Renamed the built-in loggers to html-formatter and message-formatter
clrudolphi Aug 8, 2025
d52172c
Corrrecting file encoding (again - sorry)
clrudolphi Aug 8, 2025
3393e7c
Documentation
clrudolphi Aug 8, 2025
54dfa0b
Cleanup of Reqnroll.csproj project references
clrudolphi Aug 11, 2025
bef61cd
Changed name of Logger project Reqnroll.FormatterTestLogger.
clrudolphi Aug 11, 2025
003a724
Merge remote-tracking branch 'origin/main' into TestLogger
gasparnagy Aug 16, 2025
a8a7839
code cleanup
gasparnagy Aug 16, 2025
1a7a35c
more code cleanup
gasparnagy Aug 16, 2025
4ddfe53
Cleanup Reqnroll.FormatterTestLogger
gasparnagy Aug 16, 2025
7146af9
code cleanup
gasparnagy Aug 16, 2025
c591ddc
remove Reqnroll.FormatterTestLogger project
gasparnagy Aug 16, 2025
47aaa63
remove docs
gasparnagy Aug 16, 2025
cc6ce8e
remove Reqnroll.FormatterTestLogger project 2
gasparnagy Aug 16, 2025
25dcd03
Make IEnvironmentWrapper.GetEnvironmentVariableNames return a simple …
gasparnagy Aug 16, 2025
4b5b347
replace GetEnvironmentVariableNames with GetEnvironmentVariables
gasparnagy Aug 16, 2025
f14e21c
remove IFormatterLoggerConfigurationProvider
gasparnagy Aug 16, 2025
01e0d92
fix build
gasparnagy Aug 16, 2025
f784d02
Update FormattersConfigurationProvider.cs
gasparnagy Aug 16, 2025
ee182e3
Add KeyValueEnvironmentConfigurationResolver
gasparnagy Aug 16, 2025
badb809
rename EnvironmentConfigurationResolver to JsonEnvironmentConfigurati…
gasparnagy Aug 16, 2025
c35cddb
move FileBasedConfigurationResolver to specific interface resolved pr…
gasparnagy Aug 16, 2025
b256e83
fix docs
gasparnagy Aug 16, 2025
fea18de
Update Reqnroll/EnvironmentAccess/EnvironmentWrapper.cs
gasparnagy Aug 16, 2025
5f28b28
Update Reqnroll/Formatters/Configuration/KeyValueEnvironmentConfigura…
gasparnagy Aug 16, 2025
e6847d2
the formatter name and the setting names should be treated case insen…
gasparnagy Aug 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion Reqnroll/EnvironmentAccess/EnvironmentWrapper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System;
using Reqnroll.CommonModels;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace Reqnroll.EnvironmentAccess
{
Expand Down Expand Up @@ -36,5 +39,17 @@ public void SetEnvironmentVariable(string name, string value)
}

public string GetCurrentDirectory() => Environment.CurrentDirectory;

public IDictionary<string, string> GetEnvironmentVariables(string prefix)
{
if (string.IsNullOrEmpty(prefix))
throw new ArgumentException("Argument cannot be null or empty", nameof(prefix));

return Environment.GetEnvironmentVariables()
.OfType<DictionaryEntry>()
.Select(e => (Key: e.Key?.ToString() ?? "", Value: e.Value?.ToString() ?? ""))
.Where(e => e.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
.ToDictionary(e => e.Key, e => e.Value);
}
}
}
3 changes: 3 additions & 0 deletions Reqnroll/EnvironmentAccess/IEnvironmentWrapper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Reqnroll.CommonModels;
using System.Collections.Generic;

namespace Reqnroll.EnvironmentAccess
{
Expand All @@ -10,6 +11,8 @@ public interface IEnvironmentWrapper

IResult<string> GetEnvironmentVariable(string name);

IDictionary<string,string> GetEnvironmentVariables(string prefix);

void SetEnvironmentVariable(string name, string value);

string GetCurrentDirectory();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Reqnroll.Formatters.Configuration;

public class FileBasedConfigurationResolver : FormattersConfigurationResolverBase, IFormattersConfigurationResolver
public class FileBasedConfigurationResolver : FormattersConfigurationResolverBase, IFileBasedConfigurationResolver
{
private readonly IReqnrollJsonLocator _configFileLocator;
private readonly IFileSystem _fileSystem;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
public static class FormattersConfigurationConstants
{
public const string REQNROLL_FORMATTERS_ENVIRONMENT_VARIABLE = "REQNROLL_FORMATTERS";
public const string REQNROLL_FORMATTERS_ENVIRONMENT_VARIABLE_PREFIX = "REQNROLL_FORMATTERS_";
public const string REQNROLL_FORMATTERS_DISABLED_ENVIRONMENT_VARIABLE = "REQNROLL_FORMATTERS_DISABLED";
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Reqnroll.Formatters.Configuration;
/// the class will resolve the configuration (only once).
///
/// One or more profiles may be read from the configuration file (<see cref="FileBasedConfigurationResolver"/>)
/// then environment variable overrides are applied (<see cref="EnvironmentConfigurationResolver"/>).
/// then environment variable overrides are applied (first <see cref="JsonEnvironmentConfigurationResolver"/>, then <see cref="KeyValueEnvironmentConfigurationResolver"/>).
/// </summary>
public class FormattersConfigurationProvider : IFormattersConfigurationProvider
{
Expand All @@ -20,10 +20,9 @@ public class FormattersConfigurationProvider : IFormattersConfigurationProvider
private readonly IFormattersConfigurationDisableOverrideProvider _envVariableDisableFlagProvider;
public bool Enabled => _resolvedConfiguration.Value.Enabled;

public FormattersConfigurationProvider(IDictionary<string, IFormattersConfigurationResolver> resolvers, IFormattersEnvironmentOverrideConfigurationResolver environmentOverrideConfigurationResolver, IFormattersConfigurationDisableOverrideProvider envVariableDisableFlagProvider)
public FormattersConfigurationProvider(IFileBasedConfigurationResolver fileBasedConfigurationResolver, IJsonEnvironmentConfigurationResolver jsonEnvironmentConfigurationResolver, IKeyValueEnvironmentConfigurationResolver keyValueEnvironmentConfigurationResolver, IFormattersConfigurationDisableOverrideProvider envVariableDisableFlagProvider)
{
var fileResolver = resolvers["fileBasedResolver"];
_resolvers = [fileResolver, environmentOverrideConfigurationResolver];
_resolvers = [fileBasedConfigurationResolver, jsonEnvironmentConfigurationResolver, keyValueEnvironmentConfigurationResolver];
_resolvedConfiguration = new Lazy<FormattersConfiguration>(ResolveConfiguration);
_envVariableDisableFlagProvider = envVariableDisableFlagProvider;
}
Expand All @@ -38,13 +37,16 @@ public IDictionary<string, object> GetFormatterConfigurationByName(string format

private FormattersConfiguration ResolveConfiguration()
{
var combinedConfig = new Dictionary<string, IDictionary<string, object>>();
var combinedConfig = new Dictionary<string, IDictionary<string, object>>(StringComparer.OrdinalIgnoreCase);

foreach (var resolver in _resolvers)
{
foreach (var entry in resolver.Resolve())
{
combinedConfig[entry.Key] = entry.Value;
if (entry.Value == null)
combinedConfig.Remove(entry.Key);
else
combinedConfig[entry.Key] = entry.Value;
}
}
bool enabled = combinedConfig.Count > 0 && !_envVariableDisableFlagProvider.Disabled();
Expand All @@ -55,4 +57,4 @@ private FormattersConfiguration ResolveConfiguration()
Enabled = enabled
};
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text.Json;

Expand All @@ -9,7 +10,7 @@ public abstract class FormattersConfigurationResolverBase : IFormattersConfigura

public IDictionary<string, IDictionary<string, object>> Resolve()
{
var result = new Dictionary<string, IDictionary<string, object>>();
var result = new Dictionary<string, IDictionary<string, object>>(StringComparer.OrdinalIgnoreCase);
JsonDocument jsonDocument = GetJsonDocument();

if (jsonDocument != null)
Expand All @@ -28,20 +29,21 @@ protected virtual void ProcessJsonDocument(JsonDocument jsonDocument, Dictionary
{
foreach(JsonProperty formatterProperty in formatters.EnumerateObject())
{
var configValues = new Dictionary<string, object>();
var configValues = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

if (formatterProperty.Value.ValueKind == JsonValueKind.Object)
{
foreach (JsonProperty configProperty in formatterProperty.Value.EnumerateObject())
{
configValues.Add(configProperty.Name, GetConfigValue(configProperty.Value));
configValues.Add(configProperty.Name, GetConfigValue(configProperty.Value));
}
}

result.Add(formatterProperty.Name, configValues);
}
}
}

private object GetConfigValue(JsonElement valueElement)
{
switch (valueElement.ValueKind)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Reqnroll.Formatters.Configuration;

public interface IFileBasedConfigurationResolver : IFormattersConfigurationResolverBase;
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,4 @@ namespace Reqnroll.Formatters.Configuration;
public interface IFormattersConfigurationResolverBase
{
IDictionary<string, IDictionary<string, object>> Resolve();
}

public interface IFormattersConfigurationResolver : IFormattersConfigurationResolverBase;

public interface IFormattersEnvironmentOverrideConfigurationResolver : IFormattersConfigurationResolverBase;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Reqnroll.Formatters.Configuration;

public interface IJsonEnvironmentConfigurationResolver : IFormattersConfigurationResolverBase;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Reqnroll.Formatters.Configuration;

public interface IKeyValueEnvironmentConfigurationResolver : IFormattersConfigurationResolverBase;
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,42 @@

namespace Reqnroll.Formatters.Configuration;

public class EnvironmentConfigurationResolver : FormattersConfigurationResolverBase, IFormattersEnvironmentOverrideConfigurationResolver
public class JsonEnvironmentConfigurationResolver : FormattersConfigurationResolverBase, IJsonEnvironmentConfigurationResolver
{
private readonly IEnvironmentWrapper _environmentWrapper;
private readonly IFormatterLog _log;
private readonly string _environmentVariableName;

public EnvironmentConfigurationResolver(
public JsonEnvironmentConfigurationResolver(
IEnvironmentWrapper environmentWrapper,
IFormatterLog log = null)
{
_environmentWrapper = environmentWrapper;
_log = log;
_environmentVariableName = FormattersConfigurationConstants.REQNROLL_FORMATTERS_ENVIRONMENT_VARIABLE;
}

internal JsonEnvironmentConfigurationResolver(
IEnvironmentWrapper environmentWrapper,
string environmentVariableName,
IFormatterLog log = null)
{
_environmentWrapper = environmentWrapper ?? throw new ArgumentNullException(nameof(environmentWrapper));
_log = log;
_environmentVariableName = environmentVariableName ?? throw new ArgumentNullException(nameof(environmentVariableName));
}

protected override JsonDocument GetJsonDocument()
{
try
{
var formatters = _environmentWrapper.GetEnvironmentVariable(FormattersConfigurationConstants.REQNROLL_FORMATTERS_ENVIRONMENT_VARIABLE);
var formatters = _environmentWrapper.GetEnvironmentVariable(_environmentVariableName);

if (formatters is Success<string> formattersSuccess)
{
if (string.IsNullOrWhiteSpace(formattersSuccess.Result))
{
_log?.WriteMessage($"Environment variable {FormattersConfigurationConstants.REQNROLL_FORMATTERS_ENVIRONMENT_VARIABLE} is empty");
_log?.WriteMessage($"Environment variable {_environmentVariableName} is empty");
return null;
}

Expand All @@ -43,12 +55,12 @@ protected override JsonDocument GetJsonDocument()
}
catch (JsonException ex)
{
_log?.WriteMessage($"Failed to parse JSON from environment variable {FormattersConfigurationConstants.REQNROLL_FORMATTERS_ENVIRONMENT_VARIABLE}: {ex.Message}");
_log?.WriteMessage($"Failed to parse JSON from environment variable {_environmentVariableName}: {ex.Message}");
}
}
else if (formatters is Failure<string> failure)
{
_log?.WriteMessage($"Could not retrieve environment variable {FormattersConfigurationConstants.REQNROLL_FORMATTERS_ENVIRONMENT_VARIABLE}: {failure.Description}");
_log?.WriteMessage($"Could not retrieve environment variable {_environmentVariableName}: {failure.Description}");
}
}
catch (Exception ex) when (ex is not JsonException)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using Reqnroll.EnvironmentAccess;
using Reqnroll.Formatters.RuntimeSupport;
using System.Collections.Generic;

namespace Reqnroll.Formatters.Configuration;

internal class KeyValueEnvironmentConfigurationResolver(IEnvironmentWrapper environmentWrapper, IFormatterLog log = null) : IKeyValueEnvironmentConfigurationResolver
{
private readonly IEnvironmentWrapper _environmentWrapper = environmentWrapper ?? throw new ArgumentNullException(nameof(environmentWrapper));

public IDictionary<string, IDictionary<string, object>> Resolve()
{
var result = new Dictionary<string, IDictionary<string, object>>(StringComparer.OrdinalIgnoreCase);

var environmentVariables = _environmentWrapper.GetEnvironmentVariables(FormattersConfigurationConstants.REQNROLL_FORMATTERS_ENVIRONMENT_VARIABLE_PREFIX);
foreach (var formatterEnvironmentVariable in environmentVariables)
{
var formatterName = formatterEnvironmentVariable.Key.Substring(FormattersConfigurationConstants.REQNROLL_FORMATTERS_ENVIRONMENT_VARIABLE_PREFIX.Length);
var formatterConfiguration = formatterEnvironmentVariable.Value?.Trim();
if (string.IsNullOrEmpty(formatterConfiguration))
continue;

log?.WriteMessage($"Configuring formatter '{formatterName}' via environment variable {formatterEnvironmentVariable.Key}={formatterEnvironmentVariable.Value}");

if (formatterConfiguration.Equals("true", StringComparison.InvariantCultureIgnoreCase))
{
result[formatterName] = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
continue;
}

if (formatterConfiguration.Equals("false", StringComparison.InvariantCultureIgnoreCase))
{
result[formatterName] = null;
continue;
}

var settings = formatterConfiguration.Split(';');

var configValues = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
foreach (string setting in settings)
{
var keyValue = setting.Split(['='], 2);
if (keyValue.Length == 1)
throw new ReqnrollException($"Could not parse setting '{setting}' for formatter '{formatterName}' when processing the environment variable {formatterEnvironmentVariable.Key}. Please use semicolon separated list of 'key=value' settings or 'true'.");

configValues[keyValue[0].Trim()] = keyValue[1].Trim();
}

result[formatterName] = configValues;
}

return result;
}
}
5 changes: 3 additions & 2 deletions Reqnroll/Infrastructure/DefaultDependencyProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,9 @@ public virtual void RegisterGlobalContainerDefaults(ObjectContainer container)
container.RegisterTypeAs<NullFormatterLog, IFormatterLog>();
container.RegisterTypeAs<FileSystem, IFileSystem>();
container.RegisterTypeAs<FormattersDisabledOverrideProvider, IFormattersConfigurationDisableOverrideProvider>();
container.RegisterTypeAs<FileBasedConfigurationResolver, IFormattersConfigurationResolver>("fileBasedResolver");
container.RegisterTypeAs<EnvironmentConfigurationResolver, IFormattersEnvironmentOverrideConfigurationResolver>();
container.RegisterTypeAs<FileBasedConfigurationResolver, IFileBasedConfigurationResolver>();
container.RegisterTypeAs<JsonEnvironmentConfigurationResolver, IJsonEnvironmentConfigurationResolver>();
container.RegisterTypeAs<KeyValueEnvironmentConfigurationResolver, IKeyValueEnvironmentConfigurationResolver>();
container.RegisterTypeAs<FormattersConfigurationProvider, IFormattersConfigurationProvider>();
container.RegisterTypeAs<MessageFormatter, ICucumberMessageFormatter>("message");
container.RegisterTypeAs<HtmlFormatter, ICucumberMessageFormatter>("html");
Expand Down
15 changes: 9 additions & 6 deletions Tests/Reqnroll.Formatters.Tests/MessagesCompatibilityTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,16 @@ protected static string ActualResultLocationDirectory()
var fileSystem = new FileSystem();
var fileService = new FileService();
var configFileResolver = new FileBasedConfigurationResolver(jsonConfigFileLocator, fileSystem, fileService);
var configEnvResolver = new EnvironmentConfigurationResolver(env);
var resolvers = new Dictionary<string, IFormattersConfigurationResolver>
{
{"fileBasedResolver", configFileResolver }
};
var jsonEnvConfigResolver = new JsonEnvironmentConfigurationResolver(env);

var keyValueEnvironmentConfigurationResolverMock = new Mock<IKeyValueEnvironmentConfigurationResolver>();
keyValueEnvironmentConfigurationResolverMock.Setup(r => r.Resolve()).Returns(new Dictionary<string, IDictionary<string, object>>());

FormattersConfigurationProvider configurationProvider = new FormattersConfigurationProvider(resolvers, configEnvResolver, new FormattersDisabledOverrideProvider(env));
FormattersConfigurationProvider configurationProvider = new FormattersConfigurationProvider(
configFileResolver,
jsonEnvConfigResolver,
keyValueEnvironmentConfigurationResolverMock.Object,
new FormattersDisabledOverrideProvider(env));
configurationProvider.GetFormatterConfigurationByName("message").TryGetValue("outputFilePath", out var outputFilePathElement);

var outputFilePath = outputFilePathElement!.ToString();
Expand Down
2 changes: 2 additions & 0 deletions Tests/Reqnroll.RuntimeTests/EnvironmentWrapperStub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public IResult<string> GetEnvironmentVariable(string name)
? Result<string>.Success(value)
: Result<string>.Failure($"Environment variable '{name}' not set in stub");

public IDictionary<string, string> GetEnvironmentVariables(string prefix) => throw new NotSupportedException();

public bool IsEnvironmentVariableSet(string name)
=> EnvironmentVariables.ContainsKey(name);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,23 @@ namespace Reqnroll.RuntimeTests.Formatters.Configuration;
public class CucumberConfigurationTests
{
private readonly Mock<IFormattersConfigurationDisableOverrideProvider> _disableOverrideProviderMock;
private readonly Mock<IFormattersConfigurationResolver> _fileResolverMock;
private readonly Mock<IFormattersEnvironmentOverrideConfigurationResolver> _environmentResolverMock;
private readonly Mock<IFileBasedConfigurationResolver> _fileResolverMock;
private readonly Mock<IJsonEnvironmentConfigurationResolver> _environmentResolverMock;
private readonly FormattersConfigurationProvider _sut;

public CucumberConfigurationTests()
{
_disableOverrideProviderMock = new Mock<IFormattersConfigurationDisableOverrideProvider>();
_fileResolverMock = new Mock<IFormattersConfigurationResolver>();
_environmentResolverMock = new Mock<IFormattersEnvironmentOverrideConfigurationResolver>();

var resolvers = new Dictionary<string, IFormattersConfigurationResolver>
{
{ "fileBasedResolver", _fileResolverMock.Object }
};

_sut = new FormattersConfigurationProvider(resolvers, _environmentResolverMock.Object, _disableOverrideProviderMock.Object);
_fileResolverMock = new Mock<IFileBasedConfigurationResolver>();
_environmentResolverMock = new Mock<IJsonEnvironmentConfigurationResolver>();
var keyValueEnvironmentConfigurationResolverMock = new Mock<IKeyValueEnvironmentConfigurationResolver>();
keyValueEnvironmentConfigurationResolverMock.Setup(r => r.Resolve()).Returns(new Dictionary<string, IDictionary<string, object>>());

_sut = new FormattersConfigurationProvider(
_fileResolverMock.Object,
_environmentResolverMock.Object,
keyValueEnvironmentConfigurationResolverMock.Object,
_disableOverrideProviderMock.Object);
}

[Fact]
Expand Down
Loading