Skip to content
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
117 changes: 117 additions & 0 deletions src/Build.UnitTests/BinaryLogger_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;
using FakeItEasy;
using FluentAssertions;
using Microsoft.Build.BackEnd.Logging;
Expand Down Expand Up @@ -967,6 +968,122 @@ public void ProcessParameters_MixedConfigsWithDuplicates_HandledCorrectly()

#endregion

#region Forward Compatibility Replay Tests
// These tests exercise in-memory streams rather than .binlog files,
// but the fixture's _logFile must exist at Dispose time.

[Fact]
public void OpenBuildEventsReader_ThrowsForIncompatibleVersion()
{
// fileFormatVersion > current AND minimumReaderVersion > current => fatal
var stream = new MemoryStream();
using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
writer.Write(BinaryLogger.FileFormatVersion + 10); // fileFormatVersion
writer.Write(BinaryLogger.FileFormatVersion + 5); // minimumReaderVersion (too high)
writer.Flush();
stream.Position = 0;

using var reader = new BinaryReader(stream);
Should.Throw<NotSupportedException>(() =>
BinaryLogReplayEventSource.OpenBuildEventsReader(reader, closeInput: false, allowForwardCompatibility: true));

Comment thread
AlesProkop marked this conversation as resolved.
CreateExpectedLogFile();
}

[Fact]
public void OpenBuildEventsReader_SucceedsForForwardCompatibleVersion()
{
// fileFormatVersion > current but minimumReaderVersion <= current => should succeed
var stream = new MemoryStream();
using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
writer.Write(BinaryLogger.FileFormatVersion + 5); // fileFormatVersion (newer)
writer.Write(BinaryLogger.ForwardCompatibilityMinimalVersion); // minimumReaderVersion (compatible)
writer.Flush();
stream.Position = 0;

using var reader = new BinaryReader(stream);
using var eventsReader = BinaryLogReplayEventSource.OpenBuildEventsReader(reader, closeInput: false, allowForwardCompatibility: true);
eventsReader.ShouldNotBeNull();
eventsReader.FileFormatVersion.ShouldBe(BinaryLogger.FileFormatVersion + 5);

CreateExpectedLogFile();
}

[Fact]
public void OpenBuildEventsReader_ThrowsWithoutForwardCompatibility()
{
// fileFormatVersion > current, allowForwardCompatibility = false => fatal even if minimumReaderVersion is ok
var stream = new MemoryStream();
using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
writer.Write(BinaryLogger.FileFormatVersion + 5); // fileFormatVersion (newer)
writer.Write(BinaryLogger.ForwardCompatibilityMinimalVersion); // minimumReaderVersion (compatible)
writer.Flush();
stream.Position = 0;

using var reader = new BinaryReader(stream);
Should.Throw<NotSupportedException>(() =>
BinaryLogReplayEventSource.OpenBuildEventsReader(reader, closeInput: false, allowForwardCompatibility: false));

CreateExpectedLogFile();
}

/// <summary>
/// Creates an empty placeholder so the fixture's Dispose doesn't fail
/// when the test didn't produce a real .binlog file.
/// </summary>
private void CreateExpectedLogFile() => File.Create(_logFile).Dispose();

[Fact]
public void FormatVersionMismatchWarning_NullForCurrentVersion()
{
_env.SetEnvironmentVariable("MSBUILDTARGETOUTPUTLOGGING", "1");

var binaryLogger = new BinaryLogger { Parameters = _logFile };

using (ProjectCollection collection = new())
{
Project project = ObjectModelHelpers.CreateInMemoryProject(collection, s_testProject);
project.Build(new ILogger[] { binaryLogger }).ShouldBeTrue();
}

var replayEventSource = new BinaryLogReplayEventSource();
replayEventSource.RecoverableReadError += _ => { };
replayEventSource.BuildFinished += (_, _) => { };
replayEventSource.Replay(_logFile);

replayEventSource.FormatVersionMismatchWarning.ShouldBeNull();
}

[Fact]
public void FormatVersionMismatchWarning_NonNullForNewerVersion()
{
int newerVersion = BinaryLogger.FileFormatVersion + 5;

var stream = new MemoryStream();
using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
writer.Write(newerVersion); // fileFormatVersion
writer.Write(BinaryLogger.ForwardCompatibilityMinimalVersion); // minimumReaderVersion
stream.WriteByte(0); // EndOfFile record
writer.Flush();
stream.Position = 0;

using var binaryReader = new BinaryReader(stream);
using var eventsReader = BinaryLogReplayEventSource.OpenBuildEventsReader(binaryReader, closeInput: false, allowForwardCompatibility: true);

var replayEventSource = new BinaryLogReplayEventSource();
replayEventSource.RecoverableReadError += _ => { };
replayEventSource.BuildFinished += (_, _) => { };
replayEventSource.Replay(eventsReader, CancellationToken.None);

replayEventSource.FormatVersionMismatchWarning.ShouldNotBeNull();
replayEventSource.FormatVersionMismatchWarning.ShouldContain(newerVersion.ToString());
replayEventSource.FormatVersionMismatchWarning.ShouldContain(BinaryLogger.FileFormatVersion.ToString());

CreateExpectedLogFile();
}

#endregion

public void Dispose()
{
_env.Dispose();
Comment thread
ViktorHofer marked this conversation as resolved.
Expand Down
21 changes: 17 additions & 4 deletions src/Build/Logging/BinaryLogger/BinaryLogReplayEventSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,17 @@ public sealed class BinaryLogReplayEventSource :
{
private int? _fileFormatVersion;
private int? _minimumReaderVersion;
private string? _formatVersionMismatchWarning;

public int FileFormatVersion => _fileFormatVersion ?? throw new InvalidOperationException(ResourceUtilities.GetResourceString("Binlog_Source_VersionUninitialized"));
public int MinimumReaderVersion => _minimumReaderVersion ?? throw new InvalidOperationException(ResourceUtilities.GetResourceString("Binlog_Source_VersionUninitialized"));

/// <summary>
/// After replay, contains a warning message if the binlog was produced by a newer version of MSBuild.
/// Returns <see langword="null" /> if <see cref="Replay(string)"/> has not been called yet or if the versions match.
/// </summary>
public string? FormatVersionMismatchWarning => _formatVersionMismatchWarning;
Comment thread
ViktorHofer marked this conversation as resolved.

/// Touches the <see cref="ItemGroupLoggingHelper"/> static constructor
/// to ensure it initializes <see cref="TaskParameterEventArgs.MessageGetter"/>
/// and <see cref="TaskParameterEventArgs.DictionaryFactory"/>
Expand Down Expand Up @@ -197,7 +204,7 @@ public static BuildEventArgsReader OpenBuildEventsReader(string sourceFilePath)
/// <param name="cancellationToken">A <see cref="CancellationToken"/> indicating the replay should stop as soon as possible.</param>
public void Replay(string sourceFilePath, CancellationToken cancellationToken)
{
using var eventsReader = OpenBuildEventsReader(sourceFilePath);
using var eventsReader = OpenBuildEventsReader(OpenReader(sourceFilePath), true, AllowForwardCompatibility);
Comment thread
ViktorHofer marked this conversation as resolved.
Replay(eventsReader, cancellationToken);
}

Expand Down Expand Up @@ -230,6 +237,9 @@ public void Replay(BuildEventArgsReader reader, CancellationToken cancellationTo
{
_fileFormatVersion = reader.FileFormatVersion;
_minimumReaderVersion = reader.MinimumReaderVersion;
_formatVersionMismatchWarning = reader.FileFormatVersion > BinaryLogger.FileFormatVersion
? ResourceUtilities.FormatResourceStringStripCodeAndKeyword("BinlogFormatVersionMismatch", reader.FileFormatVersion, BinaryLogger.FileFormatVersion)
: null;
bool supportsForwardCompatibility = reader.FileFormatVersion >= BinaryLogger.ForwardCompatibilityMinimalVersion;

// Allow any possible deferred subscriptions to be registered
Expand All @@ -254,9 +264,12 @@ public void Replay(BuildEventArgsReader reader, CancellationToken cancellationTo
ResourceUtilities.GetResourceString("Binlog_Source_MultiSubscribeError"));
}

// Forward compatible reading makes sense only for structured events reading.
reader.SkipUnknownEvents = supportsForwardCompatibility && AllowForwardCompatibility;
reader.SkipUnknownEventParts = supportsForwardCompatibility && AllowForwardCompatibility;
// Forward compatible reading makes sense only for structured events reading
// and only when the binlog is actually from a newer version.
bool skipUnknown = supportsForwardCompatibility && AllowForwardCompatibility
&& reader.FileFormatVersion > BinaryLogger.FileFormatVersion;
reader.SkipUnknownEvents = skipUnknown;
reader.SkipUnknownEventParts = skipUnknown;
reader.RecoverableReadError += RecoverableReadError;

while (!cancellationToken.IsCancellationRequested && reader.Read() is { } instance)
Expand Down
6 changes: 5 additions & 1 deletion src/Build/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -2135,6 +2135,10 @@ Utilization: {0} Average Utilization: {1:###.0}</value>
LOCALIZATION: {0} is integer number denoting number of bytes. 'int.MaxValue' should not be translated.
</comment>
</data>
<data name="BinlogFormatVersionMismatch" xml:space="preserve">
<value>MSB4281: The binary log file was created with a newer version of MSBuild (format version {0}) than the current version (format version {1}). Some data may be missing from the replay.</value>
<comment>{StrBegin="MSB4281: "}{0} is the binlog file format version. {1} is the current MSBuild file format version.</comment>
</data>
<data name="CustomCheckSuccessfulAcquisition" xml:space="preserve">
<value>Custom check rule: '{0}' has been registered successfully.</value>
<comment>The message is emitted on successful loading of the custom check rule in process.</comment>
Expand Down Expand Up @@ -2477,7 +2481,7 @@ Utilization: {0} Average Utilization: {1:###.0}</value>
<!--
The Build message bucket is: MSB4000 - MSB4999

Next message code should be MSB4281
Next message code should be MSB4282

Don't forget to update this comment after using a new code.
-->
Expand Down
5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Build/Resources/xlf/Strings.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading