Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.TestPlatform.TestUtilities;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Microsoft.TestPlatform.Common.UnitTests;

[TestClass]
public class SourceNavigationParserTests
{
[TestMethod]
public void FindMethodLocations_ReturnsSignatureAndBodyStartLines()
{
var lines = new[]
{
"public class Foo",
"{",
" [TestMethod]",
" public void MyMethod()",
" {",
" DoStuff();",
" }",
"}",
};

var result = SourceNavigationParser.FindMethodLocations(lines, "MyMethod");

Assert.AreEqual(1, result.Count);
Assert.AreEqual(4, result[0].SignatureLine); // 1-based: " public void MyMethod()"
Assert.AreEqual(5, result[0].BodyStartLine); // 1-based: " {"
}

[TestMethod]
public void FindMethodLocations_OverloadedMethods()
{
var lines = new[]
{
"public class Foo",
"{",
" public void OverLoaded()",
" {",
" }",
"",
" public void OverLoaded(string _)",
" {",
" }",
"}",
};

var result = SourceNavigationParser.FindMethodLocations(lines, "OverLoaded");

Assert.AreEqual(2, result.Count);
Assert.AreEqual(3, result[0].SignatureLine);
Assert.AreEqual(4, result[0].BodyStartLine);
Assert.AreEqual(7, result[1].SignatureLine);
Assert.AreEqual(8, result[1].BodyStartLine);
}

[TestMethod]
public void FindMethodBodyStartLines_SimpleMethod()
{
var lines = new[]
{
"public class Foo",
"{",
" public void MyMethod()",
" {",
" DoStuff();",
" }",
"}",
};

var result = SourceNavigationParser.FindMethodBodyStartLines(lines, "MyMethod");

Assert.AreEqual(1, result.Count);
Assert.AreEqual(4, result[0]); // 1-based: line " {"
}

[TestMethod]
public void FindMethodBodyStartLines_MethodWithAttribute()
{
var lines = new[]
{
"public class Foo",
"{",
" [Test]",
" public void PassTestMethod1()",
" {",
" Assert.AreEqual(5, 5);",
" }",
"}",
};

var result = SourceNavigationParser.FindMethodBodyStartLines(lines, "PassTestMethod1");

Assert.AreEqual(1, result.Count);
Assert.AreEqual(5, result[0]); // 1-based: line " {"
}

[TestMethod]
public void FindMethodBodyStartLines_BraceOnSameLineAsSignature()
{
var lines = new[]
{
"public class Foo",
"{",
" public void Inline() {",
" }",
"}",
};

var result = SourceNavigationParser.FindMethodBodyStartLines(lines, "Inline");

Assert.AreEqual(1, result.Count);
Assert.AreEqual(3, result[0]); // 1-based: brace is on same line as signature
}

[TestMethod]
public void FindMethodBodyStartLines_OverloadedMethods()
{
var lines = new[]
{
"public class Foo",
"{",
" public void OverLoaded()",
" {",
" }",
"",
" public void OverLoaded(string _)",
" {",
" }",
"}",
};

var result = SourceNavigationParser.FindMethodBodyStartLines(lines, "OverLoaded");

Assert.AreEqual(2, result.Count);
Assert.AreEqual(4, result[0]); // first overload brace
Assert.AreEqual(8, result[1]); // second overload brace
}

[TestMethod]
public void FindMethodBodyStartLines_MethodNotFound()
{
var lines = new[]
{
"public class Foo",
"{",
" public void Other()",
" {",
" }",
"}",
};

var result = SourceNavigationParser.FindMethodBodyStartLines(lines, "NotExist");

Assert.AreEqual(0, result.Count);
}

[TestMethod]
public void FindMethodBodyStartLines_DoesNotMatchPropertyOrField()
{
var lines = new[]
{
"public class Foo",
"{",
" public string MyMethod = \"hello\";",
" public string MyMethodProp { get; set; }",
"}",
};

// "MyMethod" followed by ' =' should not match (no '(' after name).
var result = SourceNavigationParser.FindMethodBodyStartLines(lines, "MyMethod");

Assert.AreEqual(0, result.Count);
}

[TestMethod]
public void FindMethodBodyStartLines_AsyncMethod()
{
var lines = new[]
{
"public class Foo",
"{",
" public async Task AsyncTestMethod()",
" {",
" await Task.Delay(0);",
" }",
"}",
};

var result = SourceNavigationParser.FindMethodBodyStartLines(lines, "AsyncTestMethod");

Assert.AreEqual(1, result.Count);
Assert.AreEqual(4, result[0]);
}

[TestMethod]
public void FindMethodBodyStartLines_RealSimpleClassLibrary()
{
// Mimics test/TestAssets/SimpleClassLibrary/Class1.cs
var lines = new[]
{
"// Copyright header",
"",
"using System.Threading.Tasks;",
"",
"namespace SimpleClassLibrary",
"{",
" public class Class1",
" {",
" public void PassingTest()",
" {", // line 10
" if (new System.Random().Next() == 20) { throw new System.NotImplementedException(); }",
" }",
"",
" public async Task AsyncTestMethod()",
" {", // line 15
" await Task.Delay(0);",
" }",
"",
" public void OverLoadedMethod()",
" {", // line 20
" }",
"",
" public void OverLoadedMethod(string _)",
" {", // line 24
" }",
" }",
"}",
};

Assert.AreEqual(10, SourceNavigationParser.FindMethodBodyStartLines(lines, "PassingTest")[0]);
Assert.AreEqual(15, SourceNavigationParser.FindMethodBodyStartLines(lines, "AsyncTestMethod")[0]);

var overloads = SourceNavigationParser.FindMethodBodyStartLines(lines, "OverLoadedMethod");
Assert.AreEqual(2, overloads.Count);
Assert.AreEqual(20, overloads[0]);
Assert.AreEqual(24, overloads[1]);
}

[TestMethod]
public void ContainsMethodSignature_MatchesMethodFollowedByParen()
{
Assert.IsTrue(SourceNavigationParser.ContainsMethodSignature(" public void MyMethod()", "MyMethod"));
}

[TestMethod]
public void ContainsMethodSignature_MatchesWithWhitespaceBeforeParen()
{
Assert.IsTrue(SourceNavigationParser.ContainsMethodSignature(" public void MyMethod ()", "MyMethod"));
}

[TestMethod]
public void ContainsMethodSignature_DoesNotMatchFieldAssignment()
{
Assert.IsFalse(SourceNavigationParser.ContainsMethodSignature(" public string MyMethod = \"hello\";", "MyMethod"));
}

[TestMethod]
public void ContainsMethodSignature_DoesNotMatchSubstring()
{
// "MyMethodExtended(" should not match "MyMethod" because the next char after "MyMethod" is 'E', not '(' or whitespace.
Assert.IsFalse(SourceNavigationParser.ContainsMethodSignature(" public void MyMethodExtended()", "MyMethod"));
}

[TestMethod]
public void ContainsMethodSignature_MatchesWhenNameAppearsMultipleTimes()
{
// First occurrence is a field, second is the method.
Assert.IsTrue(SourceNavigationParser.ContainsMethodSignature(" // calls MyMethod then MyMethod()", "MyMethod"));
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Diagnostics;

using Microsoft.TestPlatform.TestUtilities;
Expand Down Expand Up @@ -38,8 +37,7 @@ public void GetNavigationDataShouldReturnCorrectFileNameAndLineNumber()
Assert.IsNotNull(diaNavigationData, "Failed to get navigation data");
StringAssert.EndsWith(diaNavigationData.FileName!.Replace("\\", "/"), @"\SimpleClassLibrary\Class1.cs".Replace("\\", "/"));

ValidateMinLineNumber(11, diaNavigationData.MinLineNumber);
Assert.AreEqual(13, diaNavigationData.MaxLineNumber, "Incorrect max line number");
SourceAssert.LineIsAtMethodBodyStart(diaNavigationData.FileName!, "PassingTest", diaNavigationData.MinLineNumber, "Incorrect min line number");

_testEnvironment.TargetFramework = currentTargetFrameWork;
}
Expand All @@ -56,8 +54,8 @@ public void GetNavigationDataShouldReturnCorrectDataForAsyncMethod()
Assert.IsNotNull(diaNavigationData, "Failed to get navigation data");
StringAssert.EndsWith(diaNavigationData.FileName!.Replace("\\", "/"), @"\SimpleClassLibrary\Class1.cs".Replace("\\", "/"));

ValidateMinLineNumber(16, diaNavigationData.MinLineNumber);
Assert.AreEqual(18, diaNavigationData.MaxLineNumber, "Incorrect max line number");
// The async state machine's MoveNext maps back to the original async method source lines.
SourceAssert.LineIsAtMethodBodyStart(diaNavigationData.FileName!, "AsyncTestMethod", diaNavigationData.MinLineNumber, "Incorrect min line number");

_testEnvironment.TargetFramework = currentTargetFrameWork;
}
Expand All @@ -74,9 +72,9 @@ public void GetNavigationDataShouldReturnCorrectDataForOverLoadedMethod()
Assert.IsNotNull(diaNavigationData, "Failed to get navigation data");
StringAssert.EndsWith(diaNavigationData.FileName!.Replace("\\", "/"), @"\SimpleClassLibrary\Class1.cs".Replace("\\", "/"));

// Weird why DiaSession is now returning the first overloaded method
// as compared to before when it used to return second method
ValidateLineNumbers(diaNavigationData.MinLineNumber, diaNavigationData.MaxLineNumber);
// DiaSession may return any overload; verify min line falls within one of them.
SourceAssert.LineIsAtMethodBodyStart(diaNavigationData.FileName!, "OverLoadedMethod", diaNavigationData.MinLineNumber,
$"Min line number ({diaNavigationData.MinLineNumber}) is not at the body start of any OverLoadedMethod overload.");

_testEnvironment.TargetFramework = currentTargetFrameWork;
}
Expand Down Expand Up @@ -114,48 +112,12 @@ public void DiaSessionPerfTest()

Assert.IsNotNull(diaNavigationData, "Failed to get navigation data");
StringAssert.EndsWith(diaNavigationData.FileName!.Replace("\\", "/"), @"\SimpleClassLibrary\HugeMethodSet.cs".Replace("\\", "/"));
ValidateMinLineNumber(9, diaNavigationData.MinLineNumber);
Assert.AreEqual(10, diaNavigationData.MaxLineNumber);

SourceAssert.LineIsAtMethodBodyStart(diaNavigationData.FileName!, "MSTest_D1_01", diaNavigationData.MinLineNumber, "Incorrect min line number");

var expectedTime = 150;
Assert.IsTrue(watch.Elapsed.Milliseconds < expectedTime, $"DiaSession Perf test Actual time:{watch.Elapsed.Milliseconds} ms Expected time:{expectedTime} ms");

_testEnvironment.TargetFramework = currentTargetFrameWork;
}

private static void ValidateLineNumbers(int min, int max)
{
// Release builds optimize code, hence min line numbers are different.
if (IntegrationTestEnvironment.BuildConfiguration.StartsWith("release", StringComparison.OrdinalIgnoreCase))
{
Assert.AreEqual(min, max, "Incorrect min line number");
}
else
{
if (max == 22)
{
Assert.AreEqual(min + 1, max, "Incorrect min line number");
}
else if (max == 26)
{
Assert.AreEqual(min + 1, max, "Incorrect min line number");
}
else
{
Assert.Fail($"Incorrect min/max line number. Expected Max to be 22 or 26. And Min to be 21 or 25. But Min was {min}, and Max was {max}.");
}
}
}

private static void ValidateMinLineNumber(int expected, int actual)
{
// Release builds optimize code, hence min line numbers are different.
if (IntegrationTestEnvironment.BuildConfiguration.StartsWith("release", StringComparison.OrdinalIgnoreCase))
{
Assert.AreEqual(expected + 1, actual, "Incorrect min line number");
}
else
{
Assert.AreEqual(expected, actual, "Incorrect min line number");
}
}
}
Loading