Skip to content

Commit

Permalink
Add forward compatibility reading unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
JanKrivanek committed Dec 15, 2023
1 parent 7c58067 commit b8cc013
Showing 1 changed file with 225 additions and 10 deletions.
235 changes: 225 additions & 10 deletions src/StructuredLogger.Tests/BinaryLoggerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using System.Runtime.CompilerServices;
using FluentAssertions;
using Microsoft.Build.Framework;
using Microsoft.Build.Logging;
using Microsoft.Build.Logging.StructuredLogger;
using StructuredLogger.Tests;
Expand All @@ -14,7 +15,7 @@

namespace Microsoft.Build.UnitTests
{
public class BinaryLoggerTests : IDisposable
public class BinaryLoggerTests
{
private readonly ITestOutputHelper output;

Expand Down Expand Up @@ -146,11 +147,9 @@ public void TestReaderWriterRoundtripEquality()
//assert here
AssertBinlogsHaveEqualContent(binLog, replayedBinlog);

//TODO: temporarily disabling - as we do not have guarantee for binary equality of events
// there are few mismatches between null and empty - will be fixed along with porting in-flight changes in MSBuild (needs log version update)
//// If this assertation complicates development - it can possibly be removed
//// The structured equality above should be enough.
//AssertFilesAreBinaryEqualAfterUnpack(binLog, replayedBinlog);
// If this assertation complicates development - it can possibly be removed
// The structured equality above should be enough.
AssertFilesAreBinaryEqualAfterUnpack(binLog, replayedBinlog);
}

private static void AssertFilesAreBinaryEqualAfterUnpack(string firstPath, string secondPath)
Expand Down Expand Up @@ -208,14 +207,230 @@ void AddArchiveFile(Dictionary<string, string> files, ArchiveFileEventArgs arg)
}
}

private static string GetProperty(Logging.StructuredLogger.Build build)
[Fact]
public void ForwardCompatibleRead_HandleAppendOnlyChanges()
{
// Let's not write any strings prior the first event - to make locating (and overwriting) the size byte(s) easier.
BuildErrorEventArgs error = new(null, null, null, 1, 2, 3, 4, null, null, null);
BuildFinishedEventArgs finished = new("Message", "HelpKeyword", true);

var memoryStream = new MemoryStream();
var binaryWriter = new BinaryWriter(memoryStream);
var binaryReader = new BinaryReader(memoryStream);
var buildEventArgsWriter = new BuildEventArgsWriter(binaryWriter);

buildEventArgsWriter.Write(error);

// Some future data that are not known in current version
binaryWriter.Write(new byte[] { 1, 2, 3, 4 });


int positionAfterFirstEvent = (int)memoryStream.Position;
memoryStream.Position = 0;
// event type
Serialization.Read7BitEncodedInt(binaryReader);
int eventSizePos = (int)memoryStream.Position;
int eventSize = Serialization.Read7BitEncodedInt(binaryReader);
int positionAfterFirstEventSize = (int)memoryStream.Position;
memoryStream.Position = eventSizePos;
// the extra 4 bytes
Serialization.Write7BitEncodedInt(binaryWriter, eventSize + 4);
memoryStream.Position.Should().Be(positionAfterFirstEventSize, "The event size need to be overwritten in place - without overwriting any bytes after the size info");
memoryStream.Position = positionAfterFirstEvent;

buildEventArgsWriter.Write(finished);

// Remember num of bytes written - we should read them all.
long length = memoryStream.Length;
// Now move back to the beginning of the stream and start reading.
memoryStream.Position = 0;

using var buildEventArgsReader = new Logging.StructuredLogger.BuildEventArgsReader(binaryReader, BinaryLogger.FileFormatVersion)
{
SkipUnknownEventParts = true
};

List<BinaryLogReaderErrorEventArgs> readerErrors = new();
buildEventArgsReader.RecoverableReadError += readerErrors.Add;

var deserializedError = (BuildErrorEventArgs)buildEventArgsReader.Read();

readerErrors.Count.Should().Be(1);
readerErrors[0].ErrorType.Should().Be(ReaderErrorType.UnknownEventData);
readerErrors[0].RecordKind.Should().Be(BinaryLogRecordKind.Error);

deserializedError.Should().BeEquivalentTo(error);

var deserializedFinished = (BuildFinishedEventArgs)buildEventArgsReader.Read();

readerErrors.Count.Should().Be(1);

deserializedFinished.Should().BeEquivalentTo(finished);

// There is nothing else in the stream
memoryStream.Position.Should().Be(length);
}

[Fact]
public void ForwardCompatibleRead_HandleUnknownEvent()
{
// Let's not write any strings prior the first event - to make locating (and overwriting) the event type byte(s) easier.
BuildErrorEventArgs error = new(null, null, null, 1, 2, 3, 4, null, null, null);
BuildFinishedEventArgs finished = new("Message", "HelpKeyword", true);

var memoryStream = new MemoryStream();
var binaryWriter = new BinaryWriter(memoryStream);
var binaryReader = new BinaryReader(memoryStream);
var buildEventArgsWriter = new BuildEventArgsWriter(binaryWriter);

buildEventArgsWriter.Write(error);

int positionAfterFirstEvent = (int)memoryStream.Position;
memoryStream.Position = 0;
// event type
Serialization.Read7BitEncodedInt(binaryReader);
int eventSizePos = (int)memoryStream.Position;
memoryStream.Position = 0;

// some future type that is not known in current version
BinaryLogRecordKind unknownType = (BinaryLogRecordKind)Enum.GetValues(typeof(BinaryLogRecordKind)).Cast<BinaryLogRecordKind>().Select(e => (int)e).Max() + 2;
Serialization.Write7BitEncodedInt(binaryWriter, (int)unknownType);
memoryStream.Position.Should().Be(eventSizePos, "The event type need to be overwritten in place - without overwriting any bytes after the type info");
memoryStream.Position = positionAfterFirstEvent;

buildEventArgsWriter.Write(finished);

// Remember num of bytes written - we should read them all.
long length = memoryStream.Length;
// Now move back to the beginning of the stream and start reading.
memoryStream.Position = 0;

List<BinaryLogReaderErrorEventArgs> readerErrors = new();
using var buildEventArgsReader = new Logging.StructuredLogger.BuildEventArgsReader(binaryReader, BinaryLogger.FileFormatVersion)
{
SkipUnknownEvents = true
};

buildEventArgsReader.RecoverableReadError += readerErrors.Add;

var deserializedEvent = buildEventArgsReader.Read();

readerErrors.Count.Should().Be(1);
readerErrors[0].ErrorType.Should().Be(ReaderErrorType.UnkownEventType);
readerErrors[0].RecordKind.Should().Be(unknownType);

deserializedEvent.Should().BeEquivalentTo(finished);

// There is nothing else in the stream
memoryStream.Position.Should().Be(length);
}

[Fact]
public void ForwardCompatibleRead_HandleMismatchedFormatOfEvent()
{
var property = build.FindFirstDescendant<Project>().FindChild<Folder>("Properties").FindChild<Property>(p => p.Name == "FrameworkSDKRoot").Value;
return property;
// BuildErrorEventArgs error = new("Subcategory", "Code", "File", 1, 2, 3, 4, "Message", "HelpKeyword", "SenderName");
BuildErrorEventArgs error = new(null, null, null, 1, 2, 3, 4, null, null, null);
BuildFinishedEventArgs finished = new("Message", "HelpKeyword", true);

var memoryStream = new MemoryStream();
var binaryWriter = new BinaryWriter(memoryStream);
var binaryReader = new BinaryReader(memoryStream);
var buildEventArgsWriter = new BuildEventArgsWriter(binaryWriter);

buildEventArgsWriter.Write(error);

int positionAfterFirstEvent = (int)memoryStream.Position;
memoryStream.Position = 0;
// event type
Serialization.Read7BitEncodedInt(binaryReader);
int eventSize = Serialization.Read7BitEncodedInt(binaryReader);
// overwrite the entire event with garbage
binaryWriter.Write(Enumerable.Repeat(byte.MaxValue, eventSize).ToArray());

memoryStream.Position.Should().Be(positionAfterFirstEvent, "The event need to be overwritten in place - without overwriting any bytes after the size info");

buildEventArgsWriter.Write(finished);

// Remember num of bytes written - we should read them all.
long length = memoryStream.Length;
// Now move back to the beginning of the stream and start reading.
memoryStream.Position = 0;

using var buildEventArgsReader = new Logging.StructuredLogger.BuildEventArgsReader(binaryReader, BinaryLogger.FileFormatVersion)
{
SkipUnknownEvents = true
};

List<BinaryLogReaderErrorEventArgs> readerErrors = new();
buildEventArgsReader.RecoverableReadError += readerErrors.Add;

var deserializedEvent = buildEventArgsReader.Read();

readerErrors.Count.Should().Be(1);
readerErrors[0].ErrorType.Should().Be(ReaderErrorType.UnknownFormatOfEventData);
readerErrors[0].RecordKind.Should().Be(BinaryLogRecordKind.Error);
readerErrors[0].GetFormattedMessage().Should().Contain("FormatException");

deserializedEvent.Should().BeEquivalentTo(finished);

// There is nothing else in the stream
memoryStream.Position.Should().Be(length);
}

public void Dispose()
[Fact]
public void ForwardCompatibleRead_HandleRemovalOfDataFromEventDefinition()
{
// BuildErrorEventArgs error = new("Subcategory", "Code", "File", 1, 2, 3, 4, "Message", "HelpKeyword", "SenderName");
BuildErrorEventArgs error = new(null, null, null, 1, 2, 3, 4, null, null, null);
BuildFinishedEventArgs finished = new("Message", "HelpKeyword", true);

var memoryStream = new MemoryStream();
var binaryWriter = new BinaryWriter(memoryStream);
var binaryReader = new BinaryReader(memoryStream);
var buildEventArgsWriter = new BuildEventArgsWriter(binaryWriter);

buildEventArgsWriter.Write(error);

int positionAfterFirstEvent = (int)memoryStream.Position;
memoryStream.Position = 0;
// event type
Serialization.Read7BitEncodedInt(binaryReader);
int eventSizePos = (int)memoryStream.Position;
int eventSize = Serialization.Read7BitEncodedInt(binaryReader);
int positionAfterFirstEventSize = (int)memoryStream.Position;
memoryStream.Position = eventSizePos;
// simulate there are 4 bytes less in the future version of the event - while our reader expects those
Serialization.Write7BitEncodedInt(binaryWriter, eventSize - 4);
memoryStream.Position.Should().Be(positionAfterFirstEventSize, "The event size need to be overwritten in place - without overwriting any bytes after the size info");
// remove the 4 bytes - so that actual size of event is inline with it's written size.
memoryStream.Position = positionAfterFirstEvent - 4;

buildEventArgsWriter.Write(finished);

// Remember num of bytes written - we should read them all.
long length = memoryStream.Length;
// Now move back to the beginning of the stream and start reading.
memoryStream.Position = 0;

using var buildEventArgsReader = new Logging.StructuredLogger.BuildEventArgsReader(binaryReader, BinaryLogger.FileFormatVersion)
{
SkipUnknownEvents = true
};

List<BinaryLogReaderErrorEventArgs> readerErrors = new();
buildEventArgsReader.RecoverableReadError += readerErrors.Add;

var deserializedEvent = buildEventArgsReader.Read();

readerErrors.Count.Should().Be(1);
readerErrors[0].ErrorType.Should().Be(ReaderErrorType.UnknownFormatOfEventData);
readerErrors[0].RecordKind.Should().Be(BinaryLogRecordKind.Error);
readerErrors[0].GetFormattedMessage().Should().Contain("EndOfStreamException");

deserializedEvent.Should().BeEquivalentTo(finished);

// There is nothing else in the stream
memoryStream.Position.Should().Be(length);
}
}
}

0 comments on commit b8cc013

Please sign in to comment.