Skip to content

Commit

Permalink
Improved deterministic build support
Browse files Browse the repository at this point in the history
  • Loading branch information
slang25 committed Apr 22, 2023
1 parent b20e06d commit a79e796
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 32 deletions.
1 change: 1 addition & 0 deletions src/DeterministicTests/DeterministicTests.csproj
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Shouldly\buildTransitive\Shouldly.targets" />
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
Expand Down
1 change: 0 additions & 1 deletion src/DeterministicTests/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
<PropertyGroup>
<NoWarn>CS1591;CS0649</NoWarn>
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<ItemGroup>
<SourceRoot Include = " $(MSBuildThisFileDirectory)/ " />
Expand Down
5 changes: 4 additions & 1 deletion src/Shouldly/Configuration/TestMethodInfo.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
using System.Diagnostics;
using System.Reflection;
using Shouldly.Internals;

namespace Shouldly.Configuration;

public class TestMethodInfo
{
public TestMethodInfo(StackFrame callingFrame)
{
SourceFileDirectory = Path.GetDirectoryName(callingFrame.GetFileName());
var fileName = callingFrame.GetFileName();
fileName = DeterministicBuildHelpers.ResolveDeterministicPaths(fileName);
SourceFileDirectory = Path.GetDirectoryName(fileName);

var method = callingFrame.GetMethod();
var originalMethodInfo = GetOriginalMethodInfoForStateMachineMethod(method);
Expand Down
41 changes: 41 additions & 0 deletions src/Shouldly/Internals/DeterministicBuildHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Text.RegularExpressions;

namespace Shouldly.Internals;

internal static class DeterministicBuildHelpers
{
private static readonly Regex DeterministicPathRegex = new(@"^/_\d*/");

private static readonly Lazy<IEnumerable<(string, string)>> LazySourcePathMap
= new(() =>
{
var shouldlySourcePathMap = Environment.GetEnvironmentVariable("SHOULDLY_SOURCE_PATH_MAP") ?? "";
var pathMapPairs = shouldlySourcePathMap
.Split(',')
.Select(x => x.Split('='))
.Where(x => x.Length == 2)
.Select(x => (x[0], x[1]));
return pathMapPairs;
});

private static IEnumerable<(string, string)> SourcePathMap => LazySourcePathMap.Value;

internal static string? ResolveDeterministicPaths(string? fileName)
{
foreach (var (path, placeholder) in SourcePathMap)
{
if (fileName?.StartsWith(placeholder, StringComparison.Ordinal) == true)
{
return fileName.Replace(placeholder, path);
}
}

return fileName;
}

internal static bool PathAppearsToBeDeterministic(string fileName)
{
return DeterministicPathRegex.IsMatch(fileName);
}
}
29 changes: 1 addition & 28 deletions src/Shouldly/Internals/SourceCodeTextGetter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,6 @@ internal class ActualCodeTextGetter : ICodeTextGetter
private bool _determinedOriginatingFrame;
private string? _shouldMethod;

private static readonly Lazy<IEnumerable<(string, string)>> SourcePathMap
= new(() =>
{
var shouldlySourcePathMap = Environment.GetEnvironmentVariable("SHOULDLY_SOURCE_PATH_MAP") ?? "";
var pathMapPairs = shouldlySourcePathMap
.Split(',')
.Select(x => x.Split('='))
.Where(x => x.Length == 2)
.Select(x => (x[0], x[1]));
return pathMapPairs;
});

public int ShouldlyFrameOffset { get; private set; }
public string? FileName { get; private set; }
public int LineNumber { get; private set; }
Expand Down Expand Up @@ -56,27 +43,13 @@ private void ParseStackTrace(StackTrace? stackTrace)
ShouldlyFrameOffset = originatingFrame.index;

var fileName = originatingFrame.frame.GetFileName();
fileName = ResolveDeterministicPaths(fileName);
fileName = DeterministicBuildHelpers.ResolveDeterministicPaths(fileName);
_determinedOriginatingFrame = fileName != null && File.Exists(fileName);
_shouldMethod = shouldlyFrame.method.Name;
FileName = fileName;
LineNumber = originatingFrame.frame.GetFileLineNumber() - 1;
}

private static string? ResolveDeterministicPaths(string? fileName)
{
var sourcePathMap = SourcePathMap.Value;
foreach ((var path, var placeholder) in sourcePathMap)
{
if (fileName?.StartsWith(placeholder, StringComparison.Ordinal) == true)
{
return fileName.Replace(placeholder, path);
}
}

return fileName;
}

private string GetCodePart()
{
var codePart = "Shouldly uses your source code to generate its great error messages, build your test project with full debug information to get better error messages" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public static void ShouldMatchApproved(this string actual, Action<ShouldMatchCon
var outputFolder = testMethodInfo.SourceFileDirectory;
if (string.IsNullOrEmpty(outputFolder))
throw new($"Source information not available, make sure you are compiling with full debug information. Frame: {testMethodInfo.DeclaringTypeName}.{testMethodInfo.MethodName}");
if (DeterministicBuildHelpers.PathAppearsToBeDeterministic(outputFolder))
throw new($"Unable to resolve source file from determinist build source path. Frame: {testMethodInfo.DeclaringTypeName}.{testMethodInfo.MethodName}");

if (!string.IsNullOrEmpty(config.ApprovalFileSubFolder))
{
outputFolder = Path.Combine(outputFolder, config.ApprovalFileSubFolder);
Expand Down
24 changes: 22 additions & 2 deletions src/Shouldly/buildTransitive/Shouldly.targets
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,27 @@
</Task>
</UsingTask>

<Target Name="SetPathMapsForShouldly" BeforeTargets="CoreCompile" DependsOnTargets="_SetPathMapFromSourceRoots" Condition=" '$(PathMap)' != '' AND '$(SHOULDLY_SOURCE_PATH_MAP)' == '' " >
<SetEnvVar EnvName="SHOULDLY_SOURCE_PATH_MAP" EnvValue="$(PathMap)" />
<Target Name="CapturePathMapsForShouldly" BeforeTargets="CoreCompile" DependsOnTargets="_SetPathMapFromSourceRoots" Condition=" '$(DeterministicSourcePaths)' == 'true' " >
<PropertyGroup>
<_ShouldlyPathMapsFilePath>$([MSBuild]::EnsureTrailingSlash('$(OutputPath)'))ShouldlyPathMaps_$(AssemblyName)</_ShouldlyPathMapsFilePath>
</PropertyGroup>
<WriteLinesToFile File="$(_ShouldlyPathMapsFilePath)" Lines="$(PathMap)" Overwrite="true" Encoding="Unicode" Condition=" '$(PathMap)' != '' " WriteOnlyWhenDifferent="true" />
<ItemGroup>
<FileWrites Include="$(_ShouldlyPathMapsFilePath)" Condition=" '$(PathMap)' != '' " />
</ItemGroup>
<SetEnvVar EnvName="SHOULDLY_SOURCE_PATH_MAP" EnvValue="$(PathMap)" Condition=" '$(VSTestNoBuild)' != 'true' " />
</Target>

<Target Name="SetShouldlyPathMaps" BeforeTargets="VSTest" Condition=" '$(VSTestNoBuild)' == 'true' ">
<PropertyGroup>
<_ShouldlyPathMapsFilePath>$([MSBuild]::EnsureTrailingSlash('$(OutputPath)'))ShouldlyPathMaps_$(AssemblyName)</_ShouldlyPathMapsFilePath>
</PropertyGroup>
<ReadLinesFromFile File="$(_ShouldlyPathMapsFilePath)">
<Output TaskParameter="Lines" ItemName="_ShouldlyPathMaps" />
</ReadLinesFromFile>
<PropertyGroup>
<ShouldlyPathMaps>%(_ShouldlyPathMaps.Identity)</ShouldlyPathMaps>
</PropertyGroup>
<SetEnvVar EnvName="SHOULDLY_SOURCE_PATH_MAP" EnvValue="$(ShouldlyPathMaps)" Condition=" '$(ShouldlyPathMaps)' != '' " />
</Target>
</Project>

0 comments on commit a79e796

Please sign in to comment.