Skip to content

Commit

Permalink
Replace Specflow.Internal.Json with System.Text.Json (#373)
Browse files Browse the repository at this point in the history
* Replace Specflow.Internal.Json with System.Text.Json

* ParserDriver: normalize line ending

* MsBuild: Improve Logging when GeneratorContainerBuilder throws a exception

* MsBuild: Ensure needed System.Text.Json dependencies are copied

* Hide System.Runtime.Loader compile-time dependency

* ParserDriver: correct line endings normalisation

* Fix loading dependency when compiling from VS 17.8.3

* small fix in changelog

* small cosmetic fixes

---------

Co-authored-by: Gáspár Nagy <[email protected]>
  • Loading branch information
obligaron and gasparnagy authored Jan 10, 2025
1 parent b58ffa2 commit dc1c3e4
Show file tree
Hide file tree
Showing 55 changed files with 14,382 additions and 203 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

## Bug fixes:

* Fix: Replace deprecated dependency `Specflow.Internal.Json` with `System.Text.Json`. The dependency was used for laoding `reqnroll.json`, for Visual Studio integration and for telemetry. (#373)

*Contributors of this release (in alphabetical order):* @clrudolphi, @obligaron, @olegKoshmeliuk

# v2.2.1 - 2024-11-08
Expand Down
24 changes: 22 additions & 2 deletions Reqnroll.Tools.MsBuild.Generation/AssemblyResolveLogger.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;

Expand All @@ -8,10 +9,12 @@ public sealed class AssemblyResolveLogger : IAssemblyResolveLogger
{
private readonly ITaskLoggingWrapper _taskLoggingWrapper;
private bool _isDisposed;
private readonly string _taskFolder;

public AssemblyResolveLogger(ITaskLoggingWrapper taskLoggingWrapper)
{
_taskLoggingWrapper = taskLoggingWrapper;
_taskFolder = Path.GetDirectoryName(typeof(AssemblyResolveLogger).Assembly.Location);
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
}

Expand All @@ -22,13 +25,30 @@ public AssemblyResolveLogger(ITaskLoggingWrapper taskLoggingWrapper)

public Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
_taskLoggingWrapper.LogMessage(args.Name);
_taskLoggingWrapper.LogMessage($"Resolving {args.Name}");

try
{
var requestedAssemblyName = new AssemblyName(args.Name);

var loadedAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == requestedAssemblyName.Name);
if (loadedAssembly != null) return loadedAssembly;
if (loadedAssembly != null)
{
_taskLoggingWrapper.LogMessage($" Loading {args.Name} from loaded assembly ('{loadedAssembly.FullName}')");
return loadedAssembly;
}

if (_taskFolder != null)
{
var assemblyPath = Path.Combine(_taskFolder, requestedAssemblyName.Name + ".dll");
if (File.Exists(assemblyPath))
{
_taskLoggingWrapper.LogMessage($" Loading {args.Name} from {assemblyPath}");
return Assembly.LoadFrom(assemblyPath);
}
}

_taskLoggingWrapper.LogMessage($" {args.Name} is not in folder {_taskFolder}");
}
catch (Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,34 +43,32 @@ public IResult<IReadOnlyCollection<ITaskItem>> Execute()
_processInfoDumper.DumpProcessInfo();
_taskLoggingWrapper.LogMessage("Starting GenerateFeatureFileCodeBehind");

var reqnrollProject = _reqnrollProjectProvider.GetReqnrollProject();

using (var generatorContainer = _wrappedGeneratorContainerBuilder.BuildGeneratorContainer(
reqnrollProject.ProjectSettings.ConfigurationHolder,
reqnrollProject.ProjectSettings,
_reqnrollProjectInfo.GeneratorPlugins,
_rootObjectContainer))
try
{
var projectCodeBehindGenerator = generatorContainer.Resolve<IProjectCodeBehindGenerator>();
var reqnrollProject = _reqnrollProjectProvider.GetReqnrollProject();

try
{
_ = Task.Run(_msbuildTaskAnalyticsTransmitter.TryTransmitProjectCompilingEventAsync);
using var generatorContainer = _wrappedGeneratorContainerBuilder.BuildGeneratorContainer(
reqnrollProject.ProjectSettings.ConfigurationHolder,
reqnrollProject.ProjectSettings,
_reqnrollProjectInfo.GeneratorPlugins,
_rootObjectContainer);
var projectCodeBehindGenerator = generatorContainer.Resolve<IProjectCodeBehindGenerator>();

var returnValue = projectCodeBehindGenerator.GenerateCodeBehindFilesForProject();
_ = Task.Run(_msbuildTaskAnalyticsTransmitter.TryTransmitProjectCompilingEventAsync);

if (_taskLoggingWrapper.HasLoggedErrors())
{
return Result<IReadOnlyCollection<ITaskItem>>.Failure("Feature file code-behind generation has failed with errors.");
}
var returnValue = projectCodeBehindGenerator.GenerateCodeBehindFilesForProject();

return Result.Success(returnValue);
}
catch (Exception e)
if (_taskLoggingWrapper.HasLoggedErrors())
{
_exceptionTaskLogger.LogException(e);
return Result<IReadOnlyCollection<ITaskItem>>.Failure(e);
return Result<IReadOnlyCollection<ITaskItem>>.Failure("Feature file code-behind generation has failed with errors.");
}

return Result.Success(returnValue);
}
catch (Exception e)
{
_exceptionTaskLogger.LogException(e);
return Result<IReadOnlyCollection<ITaskItem>>.Failure(e);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@
<file src="buildMultiTargeting\**\*" target="buildMultiTargeting" />

<file src="bin\$config$\net462\*.dll" target="tasks\$Reqnroll_FullFramework_Tools_TFM$" />
<file src="bin\$config$\netstandard2.0\*.dll"
exclude="bin\$config$\netstandard2.0\System.*;bin\$config$\netstandard2.0\Microsoft.*"
target="tasks\$Reqnroll_Core_Tools_TFM$" />
<file src="bin\$config$\netstandard2.0\*.dll" target="tasks\$Reqnroll_Core_Tools_TFM$" />
<file src="bin\$config$\netstandard2.0\*.deps.json" target="tasks\$Reqnroll_Core_Tools_TFM$" />

<file src="$SolutionDir$\Licenses\**\*" target="licenses" />
Expand Down
5 changes: 2 additions & 3 deletions Reqnroll/Analytics/AppInsights/AppInsightsEventSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Text;
using SpecFlow.Internal.Json;
using System.Text.Json;

namespace Reqnroll.Analytics.AppInsights
{
Expand All @@ -8,7 +7,7 @@ public class AppInsightsEventSerializer : IAppInsightsEventSerializer
public byte[] SerializeAnalyticsEvent(IAnalyticsEvent analyticsEvent, string instrumentationKey)
{
var eventTelemetry = new AppInsightsEventTelemetry(analyticsEvent, instrumentationKey);
return Encoding.UTF8.GetBytes(eventTelemetry.ToJson());
return JsonSerializer.SerializeToUtf8Bytes(eventTelemetry, AppInsightsEventTelemetryJsonSourceGenerator.Default.AppInsightsEventTelemetry);
}
}
}
22 changes: 11 additions & 11 deletions Reqnroll/Analytics/AppInsights/AppInsightsEventTelemetry.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;

namespace Reqnroll.Analytics.AppInsights
{
Expand All @@ -9,19 +9,19 @@ namespace Reqnroll.Analytics.AppInsights
/// </summary>
public class AppInsightsEventTelemetry
{
[DataMember(Name = "name")]
[JsonPropertyName("name")]
public string DataTypeName { get; set; }

[DataMember(Name = "time")]
[JsonPropertyName("time")]
public string EventDateTime { get; set; }

[DataMember(Name = "iKey")]
[JsonPropertyName("iKey")]
public string InstrumentationKey { get; set; }

[DataMember(Name = "data")]
[JsonPropertyName("data")]
public TelemetryData TelemetryData { get; set; }

[DataMember(Name = "tags")]
[JsonPropertyName("tags")]
public Dictionary<string, string> TelemetryTags { get; set; }

private const string DefaultValue = "undefined";
Expand Down Expand Up @@ -72,20 +72,20 @@ public AppInsightsEventTelemetry(IAnalyticsEvent analyticsEvent, string instrume
}
public class TelemetryData
{
[DataMember(Name = "baseType")]
[JsonPropertyName("baseType")]
public string ItemTypeName { get; set; }

[DataMember(Name = "baseData")]
[JsonPropertyName("baseData")]
public TelemetryDataItem TelemetryDataItem { get; set; }
}

public class TelemetryDataItem
{
[DataMember(Name = "ver")]
[JsonPropertyName("ver")]
public string EndPointSchemaVersion => "2";
[DataMember(Name = "name")]
[JsonPropertyName("name")]
public string EventName { get; set; }
[DataMember(Name = "properties")]
[JsonPropertyName("properties")]
public Dictionary<string, string> Properties { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Text.Json.Serialization;

namespace Reqnroll.Analytics.AppInsights
{
[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.Unspecified, // We specifiy the names explicitly
UseStringEnumConverter = true)] // use strings instead of numbers for enums
[JsonSerializable(typeof(AppInsightsEventTelemetry))]
internal partial class AppInsightsEventTelemetryJsonSourceGenerator : JsonSerializerContext
{

}
}
13 changes: 13 additions & 0 deletions Reqnroll/Bindings/Provider/BindingJsonSourceGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Reqnroll.Bindings.Provider.Data;
using System.Text.Json.Serialization;

namespace Reqnroll.Bindings.Provider;

[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.Unspecified, // We specifiy the names explicitly
UseStringEnumConverter = true)] // use strings instead of numbers for enums
[JsonSerializable(typeof(BindingData))]
internal partial class BindingJsonSourceGenerator : JsonSerializerContext
{

}
14 changes: 7 additions & 7 deletions Reqnroll/Bindings/Provider/BindingProviderService.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
using System;
using System.Linq;
using System.Reflection;
using Reqnroll.BoDi;
using Reqnroll.Bindings.Discovery;
using Reqnroll.Bindings.Discovery;
using Reqnroll.Bindings.Provider.Data;
using Reqnroll.Bindings.Reflection;
using Reqnroll.BoDi;
using Reqnroll.Configuration;
using Reqnroll.Infrastructure;
using SpecFlow.Internal.Json;
using System.Linq;
using System.Reflection;
using System.Text.Json;

namespace Reqnroll.Bindings.Provider;

public class BindingProviderService
{
public static string DiscoverBindings(Assembly testAssembly, string jsonConfiguration)
Expand All @@ -20,7 +20,7 @@ public static string DiscoverBindings(Assembly testAssembly, string jsonConfigur
BuildBindingRegistry(testAssembly, bindingRegistryBuilder);
var bindingRegistry = globalContainer.Resolve<IBindingRegistry>();
var resultData = ParseDiscoveryResult(bindingRegistry, testAssembly);
var jsonString = resultData.ToJson();
var jsonString = JsonSerializer.Serialize(resultData, BindingJsonSourceGenerator.Default.BindingData);
return jsonString;
}

Expand Down
1 change: 1 addition & 0 deletions Reqnroll/Configuration/ConfigDefaults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public static class ConfigDefaults
public const bool TraceSuccessfulSteps = true;
public const bool TraceTimings = false;
public const string MinTracedDuration = "0:0:0.1";
public static TimeSpan MinTracedDurationAsTimeSpan { get; } = TimeSpan.Parse(MinTracedDuration);
public const StepDefinitionSkeletonStyle StepDefinitionSkeletonStyle = Reqnroll.BindingSkeletons.StepDefinitionSkeletonStyle.CucumberExpressionAttribute;
public const ObsoleteBehavior ObsoleteBehavior = Configuration.ObsoleteBehavior.Warn;
public const bool ColoredOutput = false;
Expand Down
4 changes: 1 addition & 3 deletions Reqnroll/Configuration/ConfigurationLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public ConfigurationLoader(IReqnrollJsonLocator reqnrollJsonLocator)

private static bool DefaultTraceSuccessfulSteps => ConfigDefaults.TraceSuccessfulSteps;
private static bool DefaultTraceTimings => ConfigDefaults.TraceTimings;
private static TimeSpan DefaultMinTracedDuration => TimeSpan.Parse(ConfigDefaults.MinTracedDuration);
private static TimeSpan DefaultMinTracedDuration => ConfigDefaults.MinTracedDurationAsTimeSpan;

private static StepDefinitionSkeletonStyle DefaultStepDefinitionSkeletonStyle => ConfigDefaults.StepDefinitionSkeletonStyle;

Expand Down Expand Up @@ -146,7 +146,5 @@ private ReqnrollConfiguration LoadJson(ReqnrollConfiguration reqnrollConfigurati
{
return _jsonConfigurationLoader.LoadJson(reqnrollConfiguration, jsonContent);
}


}
}
7 changes: 2 additions & 5 deletions Reqnroll/Configuration/JsonConfig/BindingCultureElement.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
using System.Runtime.Serialization;

//using Newtonsoft.Json;
using System.Text.Json.Serialization;

namespace Reqnroll.Configuration.JsonConfig
{
// legacy config
public class BindingCultureElement
{
// legacy config
//[JsonProperty("name", DefaultValueHandling = DefaultValueHandling.Populate, NullValueHandling = NullValueHandling.Ignore)]
[DataMember(Name="name")]
[JsonPropertyName("name")]
public string Name { get; set; }
}
}
24 changes: 24 additions & 0 deletions Reqnroll/Configuration/JsonConfig/CustomTimeSpanConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Reqnroll.Configuration.JsonConfig
{
/// <summary>
/// Custom <see cref="TimeSpan"/> converter to stay compatible with old json format/parser
/// </summary>
sealed class CustomTimeSpanConverter : JsonConverter<TimeSpan>
{
public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var content = reader.GetString();
return TimeSpan.Parse(content, CultureInfo.InvariantCulture);
}
public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
{
var content = value.ToString(null, CultureInfo.InvariantCulture);
writer.WriteStringValue(content);
}
}
}
8 changes: 4 additions & 4 deletions Reqnroll/Configuration/JsonConfig/Dependency.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
using System.Runtime.Serialization;
using System.Text.Json.Serialization;

namespace Reqnroll.Configuration.JsonConfig
{
public class Dependency
{
[DataMember(Name = "type")]
[JsonPropertyName("type")]
public string ImplementationType { get; set; }
[DataMember(Name = "as")]
[JsonPropertyName("as")]
public string InterfaceType { get; set; }
[DataMember(Name = "name")]
[JsonPropertyName("name")]
public string Name { get; set; }
}
}
18 changes: 6 additions & 12 deletions Reqnroll/Configuration/JsonConfig/GeneratorElement.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;

namespace Reqnroll.Configuration.JsonConfig
{
public class GeneratorElement
{
//[JsonProperty("allowDebugGeneratedFiles", DefaultValueHandling = DefaultValueHandling.Populate, NullValueHandling = NullValueHandling.Ignore)]
[DataMember(Name = "allowDebugGeneratedFiles")]
[DefaultValue(ConfigDefaults.AllowDebugGeneratedFiles)]
public bool AllowDebugGeneratedFiles { get; set; }
[JsonPropertyName("allowDebugGeneratedFiles")]
public bool AllowDebugGeneratedFiles { get; set; } = ConfigDefaults.AllowDebugGeneratedFiles;

//[JsonProperty("allowRowTests", DefaultValueHandling = DefaultValueHandling.Populate, NullValueHandling = NullValueHandling.Ignore)]
[DefaultValue(ConfigDefaults.AllowRowTests)]
[DataMember(Name = "allowRowTests")]
public bool AllowRowTests { get; set; }
[JsonPropertyName("allowRowTests")]
public bool AllowRowTests { get; set; } = ConfigDefaults.AllowRowTests;

//[JsonProperty("addNonParallelizableMarkerForTags", NullValueHandling = NullValueHandling.Ignore)]
[DataMember(Name = "addNonParallelizableMarkerForTags")]
[JsonPropertyName("addNonParallelizableMarkerForTags")]
public List<string> AddNonParallelizableMarkerForTags { get; set; }
}
}
Loading

0 comments on commit dc1c3e4

Please sign in to comment.