Skip to content

Commit

Permalink
Merge pull request #769 from yuehuang010/dev/yuehuang/condition_compare
Browse files Browse the repository at this point in the history
Add a message when MSBuild task or target skip due to condition evalu…
  • Loading branch information
KirillOsenkov committed Apr 8, 2024
2 parents cdb4f5b + 54b25d9 commit 7e1ca2a
Show file tree
Hide file tree
Showing 5 changed files with 927 additions and 17 deletions.
69 changes: 69 additions & 0 deletions src/StructuredLogViewer/Controls/TextViewerControl.xaml.cs
Expand Up @@ -15,6 +15,7 @@
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
using ICSharpCode.AvalonEdit.Search;
using Microsoft.Build.Logging.StructuredLogger;
using Microsoft.Win32;

namespace StructuredLogViewer.Controls
Expand Down Expand Up @@ -159,6 +160,12 @@ public void SetText(string text)
return;
}

if (TryParseCondition(text, out string newText))
{
textEditor.Text = newText;
return;
}

bool looksLikeXml = Utilities.LooksLikeXml(text);
if (looksLikeXml && !IsXml)
{
Expand Down Expand Up @@ -216,6 +223,68 @@ void SetColor(string name, string hex)
}
}

private bool TryParseCondition(string text, out string newText)
{
Match matches = Strings.TargetSkippedFalseConditionRegex.Match(text);
if (!matches.Success)
{
matches = Strings.TaskSkippedFalseConditionRegex.Match(text);
}

if (matches.Success)
{
string unevaluated = matches.Groups[2].Value;
string evaluated = matches.Groups[3].Value;

try
{
var nodeResult = ConditionNode.ParseAndProcess(unevaluated, evaluated);
StringBuilder sb = new StringBuilder();
sb.AppendLine(text);

bool firstPrint = true;

Action<ConditionNode> nodeFormat = null;

nodeFormat = (ConditionNode node) =>
{
if (node.Result)
{
// sb.Append('\u2714'); // check mark
return;
}
if (!string.IsNullOrEmpty(node.Text))
{
if (firstPrint)
{
sb.AppendLine();
sb.AppendLine("Condition Analyzer:");
firstPrint = false;
}
sb.Append("\u274C "); // X marker
sb.AppendLine(node.Text);
}
foreach (var child in node.Children)
{
nodeFormat(child);
}
};

nodeFormat(nodeResult);

newText = sb.ToString();
return true;
}
catch { }
}

newText = null;
return false;
}

public void SetPathDisplay(bool displayPath)
{
var visibility = displayPath ? Visibility.Visible : Visibility.Collapsed;
Expand Down
221 changes: 221 additions & 0 deletions src/StructuredLogger.Tests/ConditionParserTests.cs
@@ -0,0 +1,221 @@
using System.Linq;
using System.Threading;
using StructuredLogViewer;
using Xunit;

namespace StructuredLogger.Tests
{
public class ConditionParserTests
{
[Fact]
public void Empty_Test()
{
ParseAndAssert(@"", 1, evaluate: false);
ParseAndAssert(@"( )", 2, evaluate: false);
ParseAndAssert(@"(() )", 3, evaluate: false);
}

[Fact]
public void EvaluatedNotEqual()
{
ParseAndAssert(@"'statement2' != 'statement2'", 2, expectedResult: false);
}

[Fact]
public void EvaluatedEqual()
{
ParseAndAssert(@"'statement2' == 'statement2'", 2, expectedResult: true);
ParseAndAssert(@"'statement2' == ''", 2, expectedResult: false);
ParseAndAssert(@"'' == 'statement2'", 2, expectedResult: false);
}

[Fact]
public void EvaluatedAnd()
{
ParseAndAssert(@"( 'statement1' != '' And 'statement2' != 'statement2' )", 4, expectedResult: false);
}

[Fact]
public void EvaluatedOr()
{
ParseAndAssert(@"( 'statement1' != '' Or 'statement2' != 'statement2' )", 4, expectedResult: true);
}

[Fact]
public void EvaluatedNestedStatements()
{
string evaluated = @"('statement1' != '' And ('statement2' != 'statement2' or 'statement3' != 'statement3') And 'statement4' != '')";

var node = ConditionNode.Parse(evaluated, true);
Assert.Equal(7, node.Count());
Assert.False(node.Result);
Assert.Equal(2, node.Max(p => p.Level));

string evaluatedTrue = @"('statement1' != '' And ('statement2' == 'statement2' or 'statement3' != 'statement3') And 'statement4' != '')";

var node2 = ConditionNode.Parse(evaluatedTrue, true);
Assert.Equal(7, node2.Count());
Assert.True(node2.Result);
Assert.Equal(2, node2.Max(p => p.Level));
}

[Fact]
public void Properties()
{
string unevaluated = @"('$(Property1)' != '' And '$(Property3)' != '$(Property3)' )";
string evaluated = @"( 'statement1' != '' And 'statement2' != 'statement2' )";

var node = ConditionNode.ParseAndProcess(unevaluated, evaluated);
Assert.Equal(4, node.Count());
Assert.False(node.Result);
}

[Fact]
public void Items()
{
string unevaluated = @"('@(Item1)' != '' And '@(Item2)' != '@(Item2)' )";
string evaluated = @"( 'statement1' != '' And 'statement2' != 'statement2' )";

var node = ConditionNode.ParseAndProcess(unevaluated, evaluated);
Assert.Equal(4, node.Count());
Assert.False(node.Result);
}

[Fact]
public void ItemMetadata()
{
string unevaluated = @"('%(Item.Data1)' != '' And '%(Item.Data2)' != '%(Item.Data2)' )";
string evaluated = @"( 'statement1' != '' And 'statement2' != 'statement2' )";

var node = ConditionNode.ParseAndProcess(unevaluated, evaluated);
Assert.Equal(4, node.Count());
Assert.False(node.Result);
}

[Fact]
public void ItemTransformation()
{
string unevaluated = @"'%(Filename)%(Extension)' != '@(Items->'%(Filename)%(Extension)')'";
string evaluated = @"'file.cs' != 'file.cs'";

var node = ConditionNode.ParseAndProcess(unevaluated, evaluated);
Assert.Equal(2, node.Count());
Assert.False(node.Result);
}

[Fact]
public void ConjunctionAnds()
{
string unevaluated = @" '$(EnableBaseIntermediateOutputPathMismatchWarning)' == 'true' And '$(_InitialBaseIntermediateOutputPath)' != '$(BaseIntermediateOutputPath)' And '$(BaseIntermediateOutputPath)' != '$(MSBuildProjectExtensionsPath)' ";
string evaluatedFalse = @"'' == 'true' And '' != 'obj\' And 'obj\' != 'project\obj\'";

var node = ConditionNode.ParseAndProcess(unevaluated, evaluatedFalse);
Assert.Equal(4, node.Count());
Assert.False(node.Result);

string evaluatedTrue = @"'true' == 'true' And '' != 'obj\' And '' != 'project\obj\'";

var node2 = ConditionNode.ParseAndProcess(unevaluated, evaluatedTrue);
Assert.Equal(4, node2.Count());
Assert.True(node2.Result);
}

[Fact]
public void ConjunctionOrs()
{
string unevaluated = @" '$(EnableBaseIntermediateOutputPathMismatchWarning)' == 'true' Or '$(_InitialBaseIntermediateOutputPath)' != '$(BaseIntermediateOutputPath)' Or '$(BaseIntermediateOutputPath)' != '$(MSBuildProjectExtensionsPath)' ";
string evaluatedTrue = @"'' == 'true' Or 'obj\' != 'obj\' Or 'obj\' != 'project\obj\'";

var node = ConditionNode.ParseAndProcess(unevaluated, evaluatedTrue);
Assert.Equal(4, node.Count());
Assert.True(node.Result);

string evaluatedFalse = @"'' == 'true' Or 'obj\' != 'obj\' Or 'project\obj\' != 'project\obj\'";

var node2 = ConditionNode.ParseAndProcess(unevaluated, evaluatedFalse);
Assert.Equal(4, node2.Count());
Assert.False(node2.Result);
}

[Fact]
public void ExistsNot()
{
var node = ParseAndAssert(@"!Exists($(File))", 2, evaluate: false);
Assert.Equal("!Exists($(File))", node.Children[0].Text);
}

[Fact]
public void Exists()
{
var node = ParseAndAssert(@"Exists($(File))", 2, evaluate: false);
Assert.Equal("Exists($(File))", node.Children[0].Text);
}

[Fact]
public void NoQuotesProperty()
{
var node = ParseAndAssert(@"$(file) == ''", 2, evaluate: false);
Assert.Equal("$(file)==''", node.Children[0].Text);
}

[Fact]
public void NumericCompareDouble()
{
ParseAndAssert(@"'123.456' < '567.123'", 2, expectedResult: true);
ParseAndAssert(@"'123.456' <= '567.123'", 2, expectedResult: true);
ParseAndAssert(@"'123.456' > '567.123'", 2, expectedResult: false);
ParseAndAssert(@"'123.456' >= '567.123'", 2, expectedResult: false);
}

[Fact]
public void NumericCompareVersion()
{
ParseAndAssert(@"'123.456.789' < '567.123.456'", 2, expectedResult: true);
ParseAndAssert(@"'123.456.789' <= '567.123.456'", 2, expectedResult: true);
ParseAndAssert(@"'123.456.789' > '567.123.456'", 2, expectedResult: false);
ParseAndAssert(@"'123.456.789' >= '567.123.456'", 2, expectedResult: false);
}

[Fact]
public void Boolean()
{
// test with whitespace
var node = ParseAndAssert(@" false ", 2, expectedResult: false);
Assert.Equal("false", node.Children[0].Text);

var node2 = ParseAndAssert(@"true", 2, expectedResult: true);
Assert.Equal("true", node2.Children[0].Text);

var node3 = ParseAndAssert(@"!false", 2, expectedResult: true);
Assert.Equal("!false", node3.Children[0].Text);
}

private static ConditionNode ParseAndAssert(string text, int expectedCount, bool evaluate = true, bool expectedResult = true)
{
var node = ConditionNode.Parse(text, evaluate);

if (expectedCount == 1)
{
Assert.Single(node);
}
else
{
Assert.Equal(expectedCount, node.Count());
}

if (evaluate)
{
if (expectedResult)
{
Assert.True(node.Result);
}
else
{
Assert.False(node.Result);
}
}

return node;
}
}
}
24 changes: 12 additions & 12 deletions src/StructuredLogger.Tests/ItemGroupParserTests.cs
Expand Up @@ -99,11 +99,11 @@ public void ParseThereWasAConflict()
[Fact]
public void ParseThereWasAConflictMultiline()
{
var message = @" References which depend on ""System.IO.Compression.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"" [C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.IO.Compression.FileSystem.dll].
C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.IO.Compression.FileSystem.dll
Project file item includes which caused reference ""C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.IO.Compression.FileSystem.dll"".
C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.IO.Compression.FileSystem.dll
References which depend on ""System.IO.Compression.FileSystem"" [].
var message = @" References which depend on ""System.IO.Compression.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"" [C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.IO.Compression.FileSystem.dll].
C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.IO.Compression.FileSystem.dll
Project file item includes which caused reference ""C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.IO.Compression.FileSystem.dll"".
C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.IO.Compression.FileSystem.dll
References which depend on ""System.IO.Compression.FileSystem"" [].
Unresolved primary reference with an item include of ""System.IO.Compression.FileSystem"".".NormalizeLineBreaks();
var stringCache = new StringCache();
var parameter = new Parameter();
Expand All @@ -123,19 +123,19 @@ public void ParseThereWasAConflictMultiline()
[Fact]
public void ParseMultilineMetadata()
{
var parameter = ItemGroupParser.ParsePropertyOrItemList(@"Added Item(s):
_ProjectsFiles=
Project1
var parameter = ItemGroupParser.ParsePropertyOrItemList(@"Added Item(s):
_ProjectsFiles=
Project1
AdditionalProperties=
AutoParameterizationWebConfigConnectionStrings=false;
_PackageTempDir=Out\Dir;
Project2
Project2
AdditionalProperties=
AutoParameterizationWebConfigConnectionStrings=false;
_PackageTempDir=Out\Dir;
Project3
Project3
AdditionalProperties=
AutoParameterizationWebConfigConnectionStrings=false;
_PackageTempDir=Out\Dir;
Expand Down

0 comments on commit 7e1ca2a

Please sign in to comment.