diff --git a/src/Adapter/MSTest.CoreAdapter/Execution/TestMethodInfo.cs b/src/Adapter/MSTest.CoreAdapter/Execution/TestMethodInfo.cs index 059adf2d0a..937f0ecd55 100644 --- a/src/Adapter/MSTest.CoreAdapter/Execution/TestMethodInfo.cs +++ b/src/Adapter/MSTest.CoreAdapter/Execution/TestMethodInfo.cs @@ -171,7 +171,94 @@ public virtual TestResult Invoke(object[] arguments) internal void SetArguments(object[] arguments) { - this.arguments = arguments; + if (arguments == null) + { + this.arguments = null; + } + else + { + this.arguments = this.ResolveArguments(arguments); + } + } + + internal object[] ResolveArguments(object[] arguments) + { + ParameterInfo[] parameterInfos = this.TestMethod.GetParameters(); + int requiredParameterCount = 0; + bool hasParamsValue = false; + object paramsValues = null; + foreach (var parameter in parameterInfos) + { + // If this is a params array parameter, create an instance to + // populate with any extra values provided. Don't increment + // required parameter count - params arguments are not actually required + if (parameter.GetCustomAttribute(typeof(ParamArrayAttribute)) != null) + { + hasParamsValue = true; + break; + } + + // Count required parameters from method + if (!parameter.IsOptional) + { + requiredParameterCount++; + } + } + + // If all the parameters are required, we have fewer arguments + // supplied than required, or more arguments than the method takes + // and it doesn't have a params paramenter don't try and resolve anything + if (requiredParameterCount == parameterInfos.Length || + arguments.Length < requiredParameterCount || + (!hasParamsValue && arguments.Length > parameterInfos.Length)) + { + return arguments; + } + + object[] newParameters = new object[parameterInfos.Length]; + for (int argumentIndex = 0; argumentIndex < arguments.Length; argumentIndex++) + { + // We have reached the end of the regular parameters and any additional + // values will go in a params array + if (argumentIndex >= parameterInfos.Length - 1 && hasParamsValue) + { + // If this is the params parameter, instantiate a new object of that type + if (argumentIndex == parameterInfos.Length - 1) + { + paramsValues = Activator.CreateInstance(parameterInfos[argumentIndex].ParameterType, new object[] { arguments.Length - argumentIndex }); + newParameters[argumentIndex] = paramsValues; + } + + // The params parameters is an array but the type is not known + // set the values as a generic array + if (paramsValues is Array paramsArray) + { + paramsArray.SetValue(arguments[argumentIndex], argumentIndex - (parameterInfos.Length - 1)); + } + } + else + { + newParameters[argumentIndex] = arguments[argumentIndex]; + } + } + + // If arguments supplied are less than total possible arguments set + // the values supplied to the default values for those parameters + for (int parameterNotProvidedIndex = arguments.Length; parameterNotProvidedIndex < parameterInfos.Length; parameterNotProvidedIndex++) + { + // If this is the params parameters, set it to an empty + // array of that type as DefaultValue is DBNull + if (hasParamsValue && parameterNotProvidedIndex == parameterInfos.Length - 1) + { + newParameters[parameterNotProvidedIndex] = Activator.CreateInstance(parameterInfos[parameterNotProvidedIndex].ParameterType, 0); + } + else + { + newParameters[parameterNotProvidedIndex] = parameterInfos[parameterNotProvidedIndex].DefaultValue; + } + } + + return newParameters; } /// diff --git a/src/TestFramework/MSTest.Core/Attributes/DataRowAttribute.cs b/src/TestFramework/MSTest.Core/Attributes/DataRowAttribute.cs index 93832693eb..05b3e57e1e 100644 --- a/src/TestFramework/MSTest.Core/Attributes/DataRowAttribute.cs +++ b/src/TestFramework/MSTest.Core/Attributes/DataRowAttribute.cs @@ -15,6 +15,14 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public class DataRowAttribute : Attribute, ITestDataSource { + /// + /// Initializes a new instance of the class. + /// + public DataRowAttribute() + { + this.Data = new object[0]; + } + /// /// Initializes a new instance of the class. /// diff --git a/test/E2ETests/Smoke.E2E.Tests/DataRowTests.cs b/test/E2ETests/Smoke.E2E.Tests/DataRowTests.cs index 6967befb57..a1658cca3e 100644 --- a/test/E2ETests/Smoke.E2E.Tests/DataRowTests.cs +++ b/test/E2ETests/Smoke.E2E.Tests/DataRowTests.cs @@ -12,9 +12,9 @@ public class DataRowTests : CLITestBase private const string TestAssembly = "DataRowTestProject.dll"; [TestMethod] - public void ExecuteOnlyDerivedClassDataRowsWhenBothBaseAndDerviedClassHasDataRows() + public void ExecuteOnlyDerivedClassDataRowsWhenBothBaseAndDerviedClassHasDataRows_SimpleDataRows() { - this.InvokeVsTestForExecution(new string[] { TestAssembly }); + this.InvokeVsTestForExecution(new string[] { TestAssembly }, testCaseFilter: "TestCategory~DataRowSimple"); this.ValidatePassedTestsContain( "DataRowTestMethod (BaseString1)", @@ -23,23 +23,81 @@ public void ExecuteOnlyDerivedClassDataRowsWhenBothBaseAndDerviedClassHasDataRow "DataRowTestMethod (DerivedString1)", "DataRowTestMethod (DerivedString2)"); - // 4 tests of BaseClass - 3 datarow result and 1 parent result - // 3 tests of DerivedClass - 2 datarow result and 1 parent result + // 4 tests of BaseClass.DataRowTestMethod - 3 datarow result and 1 parent result + // 3 tests of DerivedClass.DataRowTestMethod - 2 datarow result and 1 parent result // Total 7 tests - Making sure that DerivedClass doesn't run BaseClass tests this.ValidatePassedTestsCount(7); } [TestMethod] - public void ExecuteOnlyDerivedClassDataRowsWhenItOverridesBaseClassDataRows() + public void ExecuteOnlyDerivedClassDataRowsWhenItOverridesBaseClassDataRows_SimpleDataRows() { - this.InvokeVsTestForExecution(new string[] { TestAssembly }, testCaseFilter: "FullyQualifiedName~DerivedClass"); + this.InvokeVsTestForExecution(new string[] { TestAssembly }, testCaseFilter: "FullyQualifiedName~DerivedClass&TestCategory~DataRowSimple"); this.ValidatePassedTestsContain( "DataRowTestMethod (DerivedString1)", "DataRowTestMethod (DerivedString2)"); - // 3 tests of DerivedClass - 2 datarow result and 1 parent result + // 3 tests of DerivedClass.DataRowTestMethod - 2 datarow result and 1 parent result this.ValidatePassedTestsCount(3); } + + [TestMethod] + public void DataRowsExecuteWithRequiredAndOptionalParameters() + { + this.InvokeVsTestForExecution(new string[] { TestAssembly }, testCaseFilter: "TestCategory~DataRowSomeOptional"); + + this.ValidatePassedTestsContain( + "DataRowTestMethodWithSomeOptionalParameters (123)", + "DataRowTestMethodWithSomeOptionalParameters (123,DerivedOptionalString1)", + "DataRowTestMethodWithSomeOptionalParameters (123,DerivedOptionalString2,DerivedOptionalString3)"); + + // 4 tests of DerivedClass.DataRowTestMethodWithSomeOptionalParameters - 3 datarow result and 1 parent result + this.ValidatePassedTestsCount(4); + } + + [TestMethod] + public void DataRowsExecuteWithAllOptionalParameters() + { + this.InvokeVsTestForExecution(new string[] { TestAssembly }, testCaseFilter: "TestCategory~DataRowAllOptional"); + + this.ValidatePassedTestsContain( + "DataRowTestMethodWithAllOptionalParameters ()", + "DataRowTestMethodWithAllOptionalParameters (123)", + "DataRowTestMethodWithAllOptionalParameters (123,DerivedOptionalString4)", + "DataRowTestMethodWithAllOptionalParameters (123,DerivedOptionalString5,DerivedOptionalString6)"); + + // 5 tests of DerivedClass.DataRowTestMethodWithAllOptionalParameters - 4 datarow result and 1 parent result + this.ValidatePassedTestsCount(5); + } + + [TestMethod] + public void DataRowsExecuteWithParamsArrayParameter() + { + this.InvokeVsTestForExecution(new string[] { TestAssembly }, testCaseFilter: "TestCategory~DataRowParamsArgument"); + + this.ValidatePassedTestsContain( + "DataRowTestMethodWithParamsParameters (2)", + "DataRowTestMethodWithParamsParameters (2,DerivedSingleParamsArg)", + "DataRowTestMethodWithParamsParameters (2,DerivedParamsArg1,DerivedParamsArg2)", + "DataRowTestMethodWithParamsParameters (2,DerivedParamsArg1,DerivedParamsArg2,DerivedParamsArg3)"); + + // 5 tests of DerivedClass.DataRowTestMethodWithParamsParameters - 4 datarow result and 1 parent result + this.ValidatePassedTestsCount(5); + } + + [TestMethod] + public void DataRowsFailWhenInvalidArgumentsProvided() + { + this.InvokeVsTestForExecution(new string[] { TestAssembly }, testCaseFilter: "TestCategory~DataRowOptionalInvalidArguments"); + + this.ValidatePassedTestsContain( + "DataRowTestMethodFailsWithInvalidArguments ()", + "DataRowTestMethodFailsWithInvalidArguments (2)", + "DataRowTestMethodFailsWithInvalidArguments (2,DerivedRequiredArgument,DerivedOptionalArgument,DerivedExtraArgument)"); + + // 4 tests of DerivedClass.DataRowTestMethodFailsWithInvalidArguments - 3 datarow result and 1 parent result + this.ValidatePassedTestsCount(4); + } } } diff --git a/test/E2ETests/TestAssets/DataRowTestProject/BaseClass.cs b/test/E2ETests/TestAssets/DataRowTestProject/BaseClass.cs index 75183e05a5..688a365e0d 100644 --- a/test/E2ETests/TestAssets/DataRowTestProject/BaseClass.cs +++ b/test/E2ETests/TestAssets/DataRowTestProject/BaseClass.cs @@ -8,6 +8,7 @@ namespace DataRowTestProject [TestClass] public class BaseClass { + [TestCategory("DataRowSimple")] [TestMethod] [DataRow("BaseString1")] [DataRow("BaseString2")] diff --git a/test/E2ETests/TestAssets/DataRowTestProject/DerivedClass.cs b/test/E2ETests/TestAssets/DataRowTestProject/DerivedClass.cs index 0ecc6ecf05..f7a94f63e5 100644 --- a/test/E2ETests/TestAssets/DataRowTestProject/DerivedClass.cs +++ b/test/E2ETests/TestAssets/DataRowTestProject/DerivedClass.cs @@ -8,6 +8,7 @@ namespace DataRowTestProject [TestClass] public class DerivedClass : BaseClass { + [TestCategory("DataRowSimple")] [TestMethod] [DataRow("DerivedString1")] [DataRow("DerivedString2")] @@ -15,5 +16,48 @@ public override void DataRowTestMethod(string a) { Assert.IsTrue(true); } + + [TestCategory("DataRowSomeOptional")] + [TestMethod] + [DataRow(123)] + [DataRow(123, "DerivedOptionalString1")] + [DataRow(123, "DerivedOptionalString2", "DerivedOptionalString3")] + public void DataRowTestMethodWithSomeOptionalParameters(int i, string s1 = null, string s2 = null) + { + Assert.IsTrue(true); + } + + [TestCategory("DataRowAllOptional")] + [TestMethod] + [DataRow()] + [DataRow(123)] + [DataRow(123, "DerivedOptionalString4")] + [DataRow(123, "DerivedOptionalString5", "DerivedOptionalString6")] + public void DataRowTestMethodWithAllOptionalParameters(int i = 0, string s1 = null, string s2 = null) + { + Assert.IsTrue(true); + } + + [TestCategory("DataRowParamsArgument")] + [TestMethod] + [DataRow(2)] + [DataRow(2, "DerivedSingleParamsArg")] + [DataRow(2, "DerivedParamsArg1", "DerivedParamsArg2")] + [DataRow(2, "DerivedParamsArg1", "DerivedParamsArg2","DerivedParamsArg3")] + public void DataRowTestMethodWithParamsParameters(int i, params string[] args) + { + Assert.IsTrue(true); + } + + [TestCategory("DataRowOptionalInvalidArguments")] + [TestMethod] + [ExpectedException(typeof(System.Reflection.TargetParameterCountException))] + [DataRow()] + [DataRow(2)] + [DataRow(2, "DerivedRequiredArgument", "DerivedOptionalArgument", "DerivedExtraArgument")] + public void DataRowTestMethodFailsWithInvalidArguments(int i1, string requiredString, string s1 = null) + { + Assert.Fail(); + } } } diff --git a/test/UnitTests/MSTest.Core.Unit.Tests/Attributes/DataRowAttributeTests.cs b/test/UnitTests/MSTest.Core.Unit.Tests/Attributes/DataRowAttributeTests.cs index 095c0c91a0..93b8f5076f 100644 --- a/test/UnitTests/MSTest.Core.Unit.Tests/Attributes/DataRowAttributeTests.cs +++ b/test/UnitTests/MSTest.Core.Unit.Tests/Attributes/DataRowAttributeTests.cs @@ -20,6 +20,14 @@ public class DataRowAttributeTests private DummyTestClass dummyTestClass; private MethodInfo testMethodInfo; + [TestMethod] + public void DefaultConstructorSetsEmptyArrayPassed() + { + var dataRow = new DataRowAttribute(); + + CollectionAssert.AreEqual(new object[] { }, dataRow.Data); + } + [TestMethod] public void ConstructorShouldSetDataPassed() { diff --git a/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Execution/TestMethodInfoTests.cs b/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Execution/TestMethodInfoTests.cs index 2d6408bc16..6ef9d380cf 100644 --- a/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Execution/TestMethodInfoTests.cs +++ b/test/UnitTests/MSTest.CoreAdapter.Unit.Tests/Execution/TestMethodInfoTests.cs @@ -1301,6 +1301,118 @@ public void TestMethodInfoInvokeShouldFailOnTokenSourceCancellation() #endregion + [TestMethodV1] + public void ResolveArgumentsShouldReturnProvidedArgumentsWhenTooFewParameters() + { + var simpleArgumentsMethod = typeof(DummyTestClass).GetMethod("DummySimpleArgumentsMethod"); + + var method = new TestMethodInfo( + simpleArgumentsMethod, + this.testClassInfo, + this.testMethodOptions); + + object[] arguments = new object[] { "RequiredStr1" }; + object[] expectedArguments = new object[] { "RequiredStr1" }; + var resolvedArguments = method.ResolveArguments(arguments); + + Assert.AreEqual(1, resolvedArguments.Length); + CollectionAssert.AreEqual(expectedArguments, resolvedArguments); + } + + [TestMethodV1] + public void ResolveArgumentsShouldReturnProvidedArgumentsWhenTooManyParameters() + { + var simpleArgumentsMethod = typeof(DummyTestClass).GetMethod("DummySimpleArgumentsMethod"); + + var method = new TestMethodInfo( + simpleArgumentsMethod, + this.testClassInfo, + this.testMethodOptions); + + object[] arguments = new object[] { "RequiredStr1", "RequiredStr2", "ExtraStr3" }; + object[] expectedArguments = new object[] { "RequiredStr1", "RequiredStr2", "ExtraStr3" }; + var resolvedArguments = method.ResolveArguments(arguments); + + Assert.AreEqual(3, resolvedArguments.Length); + CollectionAssert.AreEqual(expectedArguments, resolvedArguments); + } + + [TestMethodV1] + public void ResolveArgumentsShouldReturnAdditionalOptionalParametersWithNoneProvided() + { + var optionalArgumentsMethod = typeof(DummyTestClass).GetMethod("DummyOptionalArgumentsMethod"); + + var method = new TestMethodInfo( + optionalArgumentsMethod, + this.testClassInfo, + this.testMethodOptions); + + object[] arguments = new object[] { "RequiredStr1" }; + object[] expectedArguments = new object[] { "RequiredStr1", null, null }; + var resolvedArguments = method.ResolveArguments(arguments); + + Assert.AreEqual(3, resolvedArguments.Length); + CollectionAssert.AreEqual(expectedArguments, resolvedArguments); + } + + [TestMethodV1] + public void ResolveArgumentsShouldReturnAdditionalOptionalParametersWithSomeProvided() + { + var optionalArgumentsMethod = typeof(DummyTestClass).GetMethod("DummyOptionalArgumentsMethod"); + + var method = new TestMethodInfo( + optionalArgumentsMethod, + this.testClassInfo, + this.testMethodOptions); + + object[] arguments = new object[] { "RequiredStr1", "OptionalStr1" }; + object[] expectedArguments = new object[] { "RequiredStr1", "OptionalStr1", null }; + var resolvedArguments = method.ResolveArguments(arguments); + + Assert.AreEqual(3, resolvedArguments.Length); + CollectionAssert.AreEqual(expectedArguments, resolvedArguments); + } + + [TestMethodV1] + public void ResolveArgumentsShouldReturnEmptyParamsWithNoneProvided() + { + var paramsArgumentMethod = typeof(DummyTestClass).GetMethod("DummyParamsArgumentMethod"); + + var method = new TestMethodInfo( + paramsArgumentMethod, + this.testClassInfo, + this.testMethodOptions); + + object[] arguments = new object[] { 1 }; + object[] expectedArguments = new object[] { 1, new string[] { } }; + var resolvedArguments = method.ResolveArguments(arguments); + + Assert.AreEqual(2, resolvedArguments.Length); + Assert.AreEqual(expectedArguments[0], resolvedArguments[0]); + Assert.IsInstanceOfType(resolvedArguments[1], typeof(string[])); + CollectionAssert.AreEqual((string[])expectedArguments[1], (string[])resolvedArguments[1]); + } + + [TestMethodV1] + public void ResolveArgumentsShouldReturnPopulatedParamsWithAllProvided() + { + var paramsArgumentMethod = typeof(DummyTestClass).GetMethod("DummyParamsArgumentMethod"); + + var method = new TestMethodInfo( + paramsArgumentMethod, + this.testClassInfo, + this.testMethodOptions); + + object[] arguments = new object[] { 1, "str1", "str2", "str3" }; + object[] expectedArguments = new object[] { 1, new string[] { "str1", "str2", "str3" } }; + var resolvedArguments = method.ResolveArguments(arguments); + + Assert.AreEqual(2, resolvedArguments.Length); + Assert.AreEqual(expectedArguments[0], resolvedArguments[0]); + Assert.IsInstanceOfType(resolvedArguments[1], typeof(string[])); + CollectionAssert.AreEqual((string[])expectedArguments[1], (string[])resolvedArguments[1]); + } + #region helper methods private void RunWithTestablePlatformService(TestablePlatformServiceProvider testablePlatformServiceProvider, Action action) @@ -1399,6 +1511,21 @@ public Task DummyAsyncTestMethod() // We use this method to validate async TestInitialize, TestCleanup, TestMethod return DummyAsyncTestMethodBody(); } + + public void DummySimpleArgumentsMethod(string str1, string str2) + { + TestMethodBody(this); + } + + public void DummyOptionalArgumentsMethod(string str1, string str2 = null, string str3 = null) + { + TestMethodBody(this); + } + + public void DummyParamsArgumentMethod(int i, params string[] args) + { + TestMethodBody(this); + } } public class DummyTestClassWithParameterizedCtor