Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log more content hash files #229

Merged
merged 6 commits into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
"dockerfile": "Dockerfile"
},
"features": {
"ghcr.io/devcontainers-contrib/features/bash-command:1": {}
"ghcr.io/devcontainers-contrib/features/bash-command:1": {},
"ghcr.io/devcontainers/features/sshd:1": {
"version": "latest"
}
},

// Features to add to the dev container. More info: https://containers.dev/features.
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ jobs:
8.0.x
9.0.x

- name: List .NET Runtimes
run: dotnet --list-runtimes

- name: Restore dependencies
run: dotnet restore
- name: Build
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CS0436</NoWarn>
<NoWarn Condition="'$(TargetFramework)' == 'net472'">$(NoWarn);Nullable</NoWarn>
<OutputType>Exe</OutputType>
</PropertyGroup>

Expand Down
7 changes: 4 additions & 3 deletions src/Basic.CompilerLog.UnitTests/BinaryLogReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public sealed class BinaryLogReaderTests : TestBase
public CompilerLogFixture Fixture { get; }

public BinaryLogReaderTests(ITestOutputHelper testOutputHelper, ITestContextAccessor testContextAccessor, CompilerLogFixture fixture)
: base(testOutputHelper, testContextAccessor, nameof(CompilerLogReaderTests))
: base(testOutputHelper, testContextAccessor, nameof(BinaryLogReaderTests))
{
Fixture = fixture;
}
Expand Down Expand Up @@ -174,9 +174,10 @@ public void ReadAllGeneratedSourceTextsDeletedPdb()

using var reader = BinaryLogReader.Create(Path.Combine(dir, "msbuild.binlog"), BasicAnalyzerKind.None);
var data = reader.ReadAllCompilationData().Single();
var diagnostic = data.GetDiagnostics(CancellationToken).Where(x => x.Severity == DiagnosticSeverity.Error).Single();
var diagnostics = data.GetDiagnostics(CancellationToken).Where(x => x.Severity == DiagnosticSeverity.Error);
Assert.Single(diagnostics);
var diagnostic = diagnostics.Single();
Assert.Contains("Can't find portable pdb file for", diagnostic.GetMessage());

Assert.Throws<InvalidOperationException>(() => reader.ReadAllGeneratedSourceTexts(data.CompilerCall));
}

Expand Down
23 changes: 9 additions & 14 deletions src/Basic.CompilerLog.UnitTests/CompilerLogFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,18 +108,16 @@ public sealed class CompilerLogFixture : FixtureBase, IDisposable
public CompilerLogFixture(IMessageSink messageSink)
: base(messageSink)
{
StorageDirectory = Path.Combine(Path.GetTempPath(), nameof(CompilerLogFixture), Guid.NewGuid().ToString("N"));
StorageDirectory = Path.Combine(TestUtil.TestTempRoot, "compilerlogfixture");
ComplogDirectory = Path.Combine(StorageDirectory, "logs");
ScratchDirectory = Path.Combine(StorageDirectory, "scratch dir");
Directory.CreateDirectory(ComplogDirectory);
Directory.CreateDirectory(ScratchDirectory);

string? testArtifactsDir = null;
if (TestUtil.InGitHubActions)
{
testArtifactsDir = Path.Combine(TestUtil.GitHubActionsTestArtifactsDirectory, "compilerlogfixture");
Directory.CreateDirectory(testArtifactsDir);
}
RunDotnetCommand("new globaljson --sdk-version 9.0.100 --roll-forward minor", ScratchDirectory);

var testArtifactsDir = Path.Combine(TestUtil.TestArtifactsDirectory, "compilerlogfixture");
Directory.CreateDirectory(testArtifactsDir);

var builder = ImmutableArray.CreateBuilder<Lazy<LogData>>();
Console = WithBuild("console.complog", void (string scratchPath) =>
Expand Down Expand Up @@ -542,10 +540,10 @@ Lazy<LogData> WithBuild(string name, Action<string> action, bool expectDiagnosti
var start = DateTime.UtcNow;
try
{
var scratchPath = Path.Combine(ScratchDirectory, Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(scratchPath);
var scratchPath = Path.Combine(ScratchDirectory, Path.GetFileNameWithoutExtension(name));
Assert.False(Directory.Exists(scratchPath));
_ = Directory.CreateDirectory(scratchPath);
messageSink.OnDiagnosticMessage($"Starting {name} in {scratchPath}");
RunDotnetCommand("new globaljson --sdk-version 9.0.100 --roll-forward minor", scratchPath);
action(scratchPath);
var binlogFilePath = Path.Combine(scratchPath, "msbuild.binlog");
Assert.True(File.Exists(binlogFilePath));
Expand All @@ -557,10 +555,7 @@ Lazy<LogData> WithBuild(string name, Action<string> action, bool expectDiagnosti
return true;
});

if (testArtifactsDir is not null)
{
File.Copy(binlogFilePath, Path.Combine(testArtifactsDir, Path.ChangeExtension(name, ".binlog")));
}
File.Copy(binlogFilePath, Path.Combine(testArtifactsDir, Path.ChangeExtension(name, ".binlog")), overwrite: !TestUtil.InGitHubActions);

if (!expectDiagnosticMessages)
{
Expand Down
64 changes: 36 additions & 28 deletions src/Basic.CompilerLog.UnitTests/ProgramTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,6 @@ private void AssertCorrectReader(ICompilerCallReader reader, string logFilePath)
}
}

private static string GetIdentityHashConsole() =>
RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? "0E662CF750EE1DD812AB28EEF043007BFE72655681838EB7AA35EC9BD48541FC"
: "3100BA355001A464D45D7636823F4B7E1729368DAFBA20BC1C482B9F6FA9E5E4";

private static string GetIdentityHashExample() =>
RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? "7EEAE6122F14D721453ED7DEAE7E1BE1D7AC3E0D69657834FF376D91888A7B11"
: "D339B2B333F7C2344D6AFD47135FF7BF6F7DF64FB9E5E5E76690B093FC302BF9";

private void RunWithBoth(Action<string> action)
{
// Run with the binary log
Expand Down Expand Up @@ -344,19 +334,44 @@ public void HashHelp()
[Fact]
public void HashPrintSimple()
{
AddContentHashToTestArtifacts();

var (exitCode, output) = RunCompLogEx($"hash print -p {Fixture.ConsoleProjectName} {Fixture.SolutionBinaryLogPath}");
Assert.Equal(Constants.ExitSuccess, exitCode);
var expected = GetIdentityHashConsole();
Assert.Contains($"console {expected}", output);
Assert.Matches($"console [0-9A-F]+", output);

// Save the full content to test artifacts so we can compare it to what is
// seen locally.
void AddContentHashToTestArtifacts()
{
var reader = CompilerLogReader.Create(Fixture.SolutionBinaryLogPath, BasicAnalyzerKind.None);
var compilerCall = reader.ReadAllCompilerCalls(x => x.ProjectFileName == Fixture.ConsoleProjectName).Single();
var compilationData = reader.ReadCompilationData(compilerCall);
var contentHash = compilationData.GetContentHash();
AddContentToTestArtifacts("console-hash.txt", contentHash);
}
}

[Fact]
public void HashPrintAll()
{
AddContentHashToTestArtifacts();
var (exitCode, output) = RunCompLogEx($"hash print {Fixture.SolutionBinaryLogPath}");
Assert.Equal(Constants.ExitSuccess, exitCode);
var expected = GetIdentityHashConsole();
Assert.Contains($"console {expected}", output);
Assert.Matches($"console [0-9A-F]+", output);

// Save the full content to test artifacts so we can compare it to what is
// seen locally.
void AddContentHashToTestArtifacts()
{
var reader = CompilerLogReader.Create(Fixture.SolutionBinaryLogPath, BasicAnalyzerKind.None);
foreach (var compilerCall in reader.ReadAllCompilerCalls())
{
var compilationData = reader.ReadCompilationData(compilerCall);
var contentHash = compilationData.GetContentHash();
AddContentToTestArtifacts($"{compilerCall.GetDiagnosticName()}.txt", contentHash);
}
}
}

[Fact]
Expand Down Expand Up @@ -386,20 +401,13 @@ public void HashExportInline()

var identityFilePath = Path.Combine(dir, "build-identity-hash.txt");
var contentFilePath = Path.Combine(dir, "build-content-hash.txt");
try
{
Assert.True(File.Exists(identityFilePath));
Assert.Equal(GetIdentityHashExample(), File.ReadAllText(identityFilePath));
Assert.True(File.Exists(contentFilePath));
var actualContentHash = File.ReadAllText(contentFilePath);
Assert.Contains(@"""outputKind"": ""ConsoleApplication""", actualContentHash);
Assert.Contains(@"""moduleName"": ""example.dll""", actualContentHash);
}
catch (Exception)
{
AddFileToTestArtifacts(contentFilePath);
throw;
}
AddFileToTestArtifacts(contentFilePath);

Assert.True(File.Exists(identityFilePath));
Assert.True(File.Exists(contentFilePath));
var actualContentHash = File.ReadAllText(contentFilePath);
Assert.Contains(@"""outputKind"": ""ConsoleApplication""", actualContentHash);
Assert.Contains(@"""moduleName"": ""example.dll""", actualContentHash);
}

[Fact]
Expand Down
2 changes: 1 addition & 1 deletion src/Basic.CompilerLog.UnitTests/SolutionFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public sealed class SolutionFixture : FixtureBase, IDisposable
public SolutionFixture(IMessageSink messageSink)
: base(messageSink)
{
StorageDirectory = Path.Combine(Path.GetTempPath(), nameof(CompilerLogFixture), Guid.NewGuid().ToString("N"));
StorageDirectory = Path.Combine(TestUtil.TestTempRoot, "solutionlogfixture");
Directory.CreateDirectory(StorageDirectory);
SolutionPath = Path.Combine(StorageDirectory, "Solution.sln");
var binlogDir = Path.Combine(StorageDirectory, "binlogs");
Expand Down
2 changes: 1 addition & 1 deletion src/Basic.CompilerLog.UnitTests/TempDir.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ internal sealed class TempDir : IDisposable

public TempDir(string? name = null)
{
DirectoryPath = Path.Combine(Path.GetTempPath(), "Basic.CompilerLog", Guid.NewGuid().ToString());
DirectoryPath = TestUtil.CreateUniqueSubDirectory(Path.Combine(TestUtil.TestTempRoot, "temps"));
if (name != null)
{
DirectoryPath = Path.Combine(DirectoryPath, name);
Expand Down
47 changes: 19 additions & 28 deletions src/Basic.CompilerLog.UnitTests/TestBase.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
using Basic.CompilerLog.Util;
using Basic.CompilerLog.Util.Impl;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Xaml;
using Xunit;
using Xunit.Sdk;

namespace Basic.CompilerLog.UnitTests;

Expand Down Expand Up @@ -294,32 +285,32 @@ private void OnAssemblyLoad(object? sender, AssemblyLoadEventArgs e)
}
}

protected void AddFileToTestArtifacts(string filePath, [CallerMemberName] string? memberName = null)
protected void AddContentToTestArtifacts(string fileName, string content, [CallerMemberName] string? memberName = null)
{
Debug.Assert(memberName is not null);
var (memberDir, _) = GetMemberTestArtifactDirectory(memberName);
var filePath = Path.Combine(memberDir, fileName);
File.WriteAllText(filePath, content);
}

string testResultsDir;
bool overwrite;
if (TestUtil.InGitHubActions)
{
overwrite = false;
testResultsDir = TestUtil.GitHubActionsTestArtifactsDirectory;
}
else
{
var assemblyDir = Path.GetDirectoryName(typeof(TestBase).Assembly.Location)!;
testResultsDir = Path.Combine(assemblyDir, "test-artifacts");
protected void AddFileToTestArtifacts(string filePath, [CallerMemberName] string? memberName = null)
{
Debug.Assert(memberName is not null);
var (memberDir, overwrite) = GetMemberTestArtifactDirectory(memberName);
TestOutputHelper.WriteLine($"Saving {filePath} to test artifacts dir {memberDir}");
File.Copy(filePath, Path.Combine(memberDir, Path.GetFileName(filePath)), overwrite);
}

// Need to overwrite locally or else every time you re-run the test you need to go and
// delete the test-artifacts directory
overwrite = true;
}
private (string MemberDir, bool Overwrite) GetMemberTestArtifactDirectory(string memberName)
{
// Need to overwrite locally or else every time you re-run the test you need to go and
// delete the test-artifacts directory
var overwrite = !TestUtil.InGitHubActions;
var testResultsDir = TestUtil.TestArtifactsDirectory;

var typeName = this.GetType().FullName;
var memberDir = Path.Combine(testResultsDir, $"{typeName}.{memberName}");
Directory.CreateDirectory(memberDir);

TestOutputHelper.WriteLine($"Saving {filePath} to test artifacts dir {memberDir}");
File.Copy(filePath, Path.Combine(memberDir, Path.GetFileName(filePath)), overwrite);
return (memberDir, overwrite);
}
}
53 changes: 53 additions & 0 deletions src/Basic.CompilerLog.UnitTests/TestUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@ internal static class TestUtil

internal static bool InGitHubActions => Environment.GetEnvironmentVariable("GITHUB_ACTIONS") is not null;

internal static string TestArtifactsDirectory
{
get
{
if (InGitHubActions)
{
return TestUtil.GitHubActionsTestArtifactsDirectory;
}

var assemblyDir = Path.GetDirectoryName(typeof(TestBase).Assembly.Location)!;
return Path.Combine(assemblyDir, "test-artifacts");
}
}

internal static string GitHubActionsTestArtifactsDirectory
{
get
Expand All @@ -41,6 +55,45 @@ internal static string GitHubActionsTestArtifactsDirectory
}
}

internal static string TestTempRoot { get; } = CreateUniqueSubDirectory(Path.Combine(Path.GetTempPath(), "Basic.CompilerLog.UnitTests"));

/// <summary>
/// This code will generate a unique subdirectory under <paramref name="path"/>. This is done instead of using
/// GUIDs because that leads to long path issues on .NET Framework.
/// </summary>
/// <remarks>
/// This method is not entirely foolproof. But it does serve the purpose of creating unique directory names
/// when tests are run in parallel on the same machine provided that we own <see cref="path"/>.
/// </remarks>
internal static string CreateUniqueSubDirectory(string path)
{
_ = Directory.CreateDirectory(path);

var id = 0;
while (true)
{
try
{
var filePath = Path.Combine(path, $"{id}.txt");
var dirPath = Path.Combine(path, $"{id}");
if (!File.Exists(filePath) && !Directory.Exists(dirPath))
{
var fileStream = new FileStream(filePath, FileMode.CreateNew);
fileStream.Dispose();

_ = Directory.CreateDirectory(dirPath);
return dirPath;
}
}
catch
{
// Don't care why we couldn't create the file or directory, just that it failed
}

id++;
}
}

/// <summary>
/// Internally a <see cref="IIncrementalGenerator" /> is wrapped in a type called IncrementalGeneratorWrapper.
/// This method will dig through that and return the original type.
Expand Down
22 changes: 22 additions & 0 deletions src/Basic.CompilerLog.UnitTests/UnitTestsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;

namespace Basic.CompilerLog.UnitTests;

public sealed class UnitTestsTests
{
[Fact]
public void CreateUniqueSubDirectory()
{
var root = new TempDir();
var path1 = TestUtil.CreateUniqueSubDirectory(root.DirectoryPath);
Assert.True(Directory.Exists(path1));
var path2 = TestUtil.CreateUniqueSubDirectory(root.DirectoryPath);
Assert.True(Directory.Exists(path2));
Assert.NotEqual(path1, path2);
}
}