зеркало из https://github.com/microsoft/testfx.git
Convert to file-scoped namespaces (#1197)
This commit is contained in:
Родитель
993fe848a2
Коммит
f84ea2b842
104
.editorconfig
104
.editorconfig
|
@ -47,3 +47,107 @@ indent_size = 4
|
||||||
tab_width = 4
|
tab_width = 4
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.{cs,vb}]
|
||||||
|
end_of_line = crlf
|
||||||
|
|
||||||
|
#### .NET Coding Conventions ####
|
||||||
|
|
||||||
|
# Organize usings
|
||||||
|
|
||||||
|
dotnet_separate_import_directive_groups = true
|
||||||
|
dotnet_sort_system_directives_first = true
|
||||||
|
|
||||||
|
# License header
|
||||||
|
|
||||||
|
file_header_template = Copyright (c) Microsoft Corporation. All rights reserved.\nLicensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
# Styling conventions
|
||||||
|
|
||||||
|
csharp_indent_labels = one_less_than_current
|
||||||
|
csharp_prefer_braces = true:silent
|
||||||
|
csharp_prefer_simple_default_expression = true:suggestion
|
||||||
|
csharp_prefer_simple_using_statement = true:suggestion
|
||||||
|
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||||
|
csharp_style_expression_bodied_accessors = true:silent
|
||||||
|
csharp_style_expression_bodied_constructors = false:silent
|
||||||
|
csharp_style_expression_bodied_indexers = true:silent
|
||||||
|
csharp_style_expression_bodied_lambdas = true:silent
|
||||||
|
csharp_style_expression_bodied_local_functions = false:silent
|
||||||
|
csharp_style_expression_bodied_methods = false:silent
|
||||||
|
csharp_style_expression_bodied_operators = false:silent
|
||||||
|
csharp_style_expression_bodied_properties = true:silent
|
||||||
|
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
|
||||||
|
csharp_style_inlined_variable_declaration = true:suggestion
|
||||||
|
csharp_style_namespace_declarations = file_scoped:silent
|
||||||
|
csharp_style_namespace_declarations = file_scoped:warning
|
||||||
|
csharp_style_prefer_index_operator = true:suggestion
|
||||||
|
csharp_style_prefer_local_over_anonymous_function = true:suggestion
|
||||||
|
csharp_style_prefer_method_group_conversion = true:silent
|
||||||
|
csharp_style_prefer_null_check_over_type_check = true:suggestion
|
||||||
|
csharp_style_prefer_range_operator = true:suggestion
|
||||||
|
csharp_style_prefer_top_level_statements = true:silent
|
||||||
|
csharp_style_prefer_tuple_swap = true:suggestion
|
||||||
|
csharp_style_prefer_utf8_string_literals = true:suggestion
|
||||||
|
csharp_style_throw_expression = true:suggestion
|
||||||
|
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
|
||||||
|
csharp_using_directive_placement = outside_namespace:silent
|
||||||
|
|
||||||
|
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||||
|
dotnet_style_coalesce_expression = true:suggestion
|
||||||
|
dotnet_style_null_propagation = true:suggestion
|
||||||
|
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||||
|
dotnet_style_prefer_auto_properties = true:silent
|
||||||
|
dotnet_style_object_initializer = true:suggestion
|
||||||
|
dotnet_style_collection_initializer = true:suggestion
|
||||||
|
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||||
|
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||||
|
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||||
|
dotnet_style_explicit_tuple_names = true:suggestion
|
||||||
|
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||||
|
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||||
|
dotnet_style_prefer_compound_assignment = true:suggestion
|
||||||
|
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||||
|
dotnet_style_namespace_match_folder = true:suggestion
|
||||||
|
|
||||||
|
#### Naming styles ####
|
||||||
|
|
||||||
|
# Naming rules
|
||||||
|
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||||
|
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
# Symbol specifications
|
||||||
|
|
||||||
|
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||||
|
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.interface.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||||
|
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.types.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||||
|
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||||
|
|
||||||
|
# Naming styles
|
||||||
|
|
||||||
|
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||||
|
dotnet_naming_style.begins_with_i.required_suffix =
|
||||||
|
dotnet_naming_style.begins_with_i.word_separator =
|
||||||
|
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.pascal_case.required_prefix =
|
||||||
|
dotnet_naming_style.pascal_case.required_suffix =
|
||||||
|
dotnet_naming_style.pascal_case.word_separator =
|
||||||
|
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||||
|
|
|
@ -2,4 +2,8 @@
|
||||||
<Project>
|
<Project>
|
||||||
<Import Project="scripts\build\TestFx.props" />
|
<Import Project="scripts\build\TestFx.props" />
|
||||||
<Import Project="eng\TFMConfiguration.props" />
|
<Import Project="eng\TFMConfiguration.props" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<LangVersion>Latest</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -1,46 +1,45 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace MSTest.Extensibility.Samples
|
namespace MSTest.Extensibility.Samples;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
public static class AssertEx
|
||||||
{
|
{
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
private static AssertIs assertis;
|
||||||
|
|
||||||
public static class AssertEx
|
/// <summary>
|
||||||
|
/// A simple assert extension to validate if an object is of a given type.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type to check.</typeparam>
|
||||||
|
/// <param name="assert">Assert class.</param>
|
||||||
|
/// <param name="obj">The object.</param>
|
||||||
|
/// <returns>True if object is of the given type.</returns>
|
||||||
|
/// <exception cref="AssertFailedException">If object is not of the given type.</exception>
|
||||||
|
public static bool IsOfType<T>(this Assert assert, object obj)
|
||||||
{
|
{
|
||||||
private static AssertIs assertis;
|
if (obj is T)
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A simple assert extension to validate if an object is of a given type.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type to check.</typeparam>
|
|
||||||
/// <param name="assert">Assert class.</param>
|
|
||||||
/// <param name="obj">The object.</param>
|
|
||||||
/// <returns>True if object is of the given type.</returns>
|
|
||||||
/// <exception cref="AssertFailedException">If object is not of the given type.</exception>
|
|
||||||
public static bool IsOfType<T>(this Assert assert, object obj)
|
|
||||||
{
|
{
|
||||||
if (obj is T)
|
return true;
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new AssertFailedException(
|
|
||||||
string.Format(
|
|
||||||
"Expected object of type {0} but found object of type {1}",
|
|
||||||
typeof(T),
|
|
||||||
obj ?? obj.GetType()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
throw new AssertFailedException(
|
||||||
/// A chain/grouping of assert statements.
|
string.Format(
|
||||||
/// </summary>
|
"Expected object of type {0} but found object of type {1}",
|
||||||
/// <param name="assert">The Assert class.</param>
|
typeof(T),
|
||||||
/// <returns>The class that contains the assert methods for this grouping.</returns>
|
obj ?? obj.GetType()));
|
||||||
public static AssertIs Is(this Assert assert)
|
}
|
||||||
{
|
|
||||||
assertis ??= new AssertIs();
|
|
||||||
|
|
||||||
return assertis;
|
/// <summary>
|
||||||
}
|
/// A chain/grouping of assert statements.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assert">The Assert class.</param>
|
||||||
|
/// <returns>The class that contains the assert methods for this grouping.</returns>
|
||||||
|
public static AssertIs Is(this Assert assert)
|
||||||
|
{
|
||||||
|
assertis ??= new AssertIs();
|
||||||
|
|
||||||
|
return assertis;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,45 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace MSTest.Extensibility.Samples
|
namespace MSTest.Extensibility.Samples;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A grouping of Assert statements with similar functionality.
|
||||||
|
/// </summary>
|
||||||
|
public class AssertIs
|
||||||
{
|
{
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
/// <summary>
|
||||||
|
/// Determines if the 'divisor' is actually a divisor of 'number'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="number">A number.</param>
|
||||||
|
/// <param name="divisor">Its proclaimed divisor.</param>
|
||||||
|
/// <returns>True if it is a divisor</returns>
|
||||||
|
/// <exception cref="AssertFailedException">If it is not a divisor.</exception>
|
||||||
|
public bool Divisor(int number, int divisor)
|
||||||
|
{
|
||||||
|
if (number % divisor == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AssertFailedException(string.Format("{0} is not a divisor of {1}", divisor, number));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A grouping of Assert statements with similar functionality.
|
/// Determines if a number is positive.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AssertIs
|
/// <param name="number">The number.</param>
|
||||||
|
/// <returns>True if it is positive.</returns>
|
||||||
|
/// <exception cref="AssertFailedException">If the number is not positive.</exception>
|
||||||
|
public bool Positive(int number)
|
||||||
{
|
{
|
||||||
/// <summary>
|
if (number > 0)
|
||||||
/// Determines if the 'divisor' is actually a divisor of 'number'.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="number">A number.</param>
|
|
||||||
/// <param name="divisor">Its proclaimed divisor.</param>
|
|
||||||
/// <returns>True if it is a divisor</returns>
|
|
||||||
/// <exception cref="AssertFailedException">If it is not a divisor.</exception>
|
|
||||||
public bool Divisor(int number, int divisor)
|
|
||||||
{
|
{
|
||||||
if (number % divisor == 0)
|
return true;
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new AssertFailedException(string.Format("{0} is not a divisor of {1}", divisor, number));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
throw new AssertFailedException(string.Format("{0} is not positive", number));
|
||||||
/// Determines if a number is positive.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="number">The number.</param>
|
|
||||||
/// <returns>True if it is positive.</returns>
|
|
||||||
/// <exception cref="AssertFailedException">If the number is not positive.</exception>
|
|
||||||
public bool Positive(int number)
|
|
||||||
{
|
|
||||||
if (number > 0)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new AssertFailedException(string.Format("{0} is not positive", number));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net462</TargetFrameworks>
|
<TargetFrameworks>net462</TargetFrameworks>
|
||||||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
||||||
<LangVersion>Latest</LangVersion>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|
|
@ -1,137 +1,136 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter;
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constants used throughout.
|
||||||
|
/// </summary>
|
||||||
|
internal static class Constants
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Uri of the MSTest executor.
|
||||||
|
/// </summary>
|
||||||
|
internal const string ExecutorUriString = "executor://MSTestAdapter/v2";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constants used throughout.
|
/// The name of test run parameters node in the runsettings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class Constants
|
internal const string TestRunParametersName = "TestRunParameters";
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Uri of the MSTest executor.
|
|
||||||
/// </summary>
|
|
||||||
internal const string ExecutorUriString = "executor://MSTestAdapter/v2";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The name of test run parameters node in the runsettings.
|
/// The executor uri for this adapter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal const string TestRunParametersName = "TestRunParameters";
|
internal static readonly Uri ExecutorUri = new(ExecutorUriString);
|
||||||
|
|
||||||
/// <summary>
|
#region Test Property registration
|
||||||
/// The executor uri for this adapter.
|
internal static readonly TestProperty DescriptionProperty = TestProperty.Register("Description", DescriptionLabel, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
/// </summary>
|
|
||||||
internal static readonly Uri ExecutorUri = new(ExecutorUriString);
|
|
||||||
|
|
||||||
#region Test Property registration
|
internal static readonly TestProperty WorkItemIdsProperty = TestProperty.Register("WorkItemIds", WorkItemIdsLabel, typeof(string[]), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
internal static readonly TestProperty DescriptionProperty = TestProperty.Register("Description", DescriptionLabel, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
|
||||||
|
|
||||||
internal static readonly TestProperty WorkItemIdsProperty = TestProperty.Register("WorkItemIds", WorkItemIdsLabel, typeof(string[]), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty CssIterationProperty = TestProperty.Register("CssIteration", CssIterationLabel, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty CssIterationProperty = TestProperty.Register("CssIteration", CssIterationLabel, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty CssProjectStructureProperty = TestProperty.Register("CssProjectStructure", CssProjectStructureLabel, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty CssProjectStructureProperty = TestProperty.Register("CssProjectStructure", CssProjectStructureLabel, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty TestClassNameProperty = TestProperty.Register("MSTestDiscoverer.TestClassName", TestClassNameLabel, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty TestClassNameProperty = TestProperty.Register("MSTestDiscoverer.TestClassName", TestClassNameLabel, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty DeclaringClassNameProperty = TestProperty.Register("MSTestDiscoverer.DeclaringClassName", DeclaringClassNameLabel, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty DeclaringClassNameProperty = TestProperty.Register("MSTestDiscoverer.DeclaringClassName", DeclaringClassNameLabel, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty AsyncTestProperty = TestProperty.Register("MSTestDiscoverer.IsAsync", IsAsyncLabel, typeof(bool), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty AsyncTestProperty = TestProperty.Register("MSTestDiscoverer.IsAsync", IsAsyncLabel, typeof(bool), TestPropertyAttributes.Hidden, typeof(TestCase));
|
|
||||||
|
|
||||||
#pragma warning disable CS0618 // Type or member is obsolete
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
internal static readonly TestProperty TestCategoryProperty = TestProperty.Register("MSTestDiscoverer.TestCategory", TestCategoryLabel, typeof(string[]), TestPropertyAttributes.Hidden | TestPropertyAttributes.Trait, typeof(TestCase));
|
internal static readonly TestProperty TestCategoryProperty = TestProperty.Register("MSTestDiscoverer.TestCategory", TestCategoryLabel, typeof(string[]), TestPropertyAttributes.Hidden | TestPropertyAttributes.Trait, typeof(TestCase));
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
|
|
||||||
internal static readonly TestProperty PriorityProperty = TestProperty.Register("MSTestDiscoverer.Priority", PriorityLabel, typeof(int), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty PriorityProperty = TestProperty.Register("MSTestDiscoverer.Priority", PriorityLabel, typeof(int), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty DeploymentItemsProperty = TestProperty.Register("MSTestDiscoverer.DeploymentItems", DeploymentItemsLabel, typeof(KeyValuePair<string, string>[]), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty DeploymentItemsProperty = TestProperty.Register("MSTestDiscoverer.DeploymentItems", DeploymentItemsLabel, typeof(KeyValuePair<string, string>[]), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty DoNotParallelizeProperty = TestProperty.Register("MSTestDiscoverer.DoNotParallelize", DoNotParallelizeLabel, typeof(bool), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty DoNotParallelizeProperty = TestProperty.Register("MSTestDiscoverer.DoNotParallelize", DoNotParallelizeLabel, typeof(bool), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty ExecutionIdProperty = TestProperty.Register("ExecutionId", ExecutionIdLabel, typeof(Guid), TestPropertyAttributes.Hidden, typeof(TestResult));
|
internal static readonly TestProperty ExecutionIdProperty = TestProperty.Register("ExecutionId", ExecutionIdLabel, typeof(Guid), TestPropertyAttributes.Hidden, typeof(TestResult));
|
||||||
|
|
||||||
internal static readonly TestProperty ParentExecIdProperty = TestProperty.Register("ParentExecId", ParentExecIdLabel, typeof(Guid), TestPropertyAttributes.Hidden, typeof(TestResult));
|
internal static readonly TestProperty ParentExecIdProperty = TestProperty.Register("ParentExecId", ParentExecIdLabel, typeof(Guid), TestPropertyAttributes.Hidden, typeof(TestResult));
|
||||||
|
|
||||||
internal static readonly TestProperty InnerResultsCountProperty = TestProperty.Register("InnerResultsCount", InnerResultsCountLabel, typeof(int), TestPropertyAttributes.Hidden, typeof(TestResult));
|
internal static readonly TestProperty InnerResultsCountProperty = TestProperty.Register("InnerResultsCount", InnerResultsCountLabel, typeof(int), TestPropertyAttributes.Hidden, typeof(TestResult));
|
||||||
|
|
||||||
internal static readonly TestProperty TestRunIdProperty = TestProperty.Register(TestRunId, TestRunId, typeof(int), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty TestRunIdProperty = TestProperty.Register(TestRunId, TestRunId, typeof(int), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty TestPlanIdProperty = TestProperty.Register(TestPlanId, TestPlanId, typeof(int), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty TestPlanIdProperty = TestProperty.Register(TestPlanId, TestPlanId, typeof(int), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty TestCaseIdProperty = TestProperty.Register(TestCaseId, TestCaseId, typeof(int), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty TestCaseIdProperty = TestProperty.Register(TestCaseId, TestCaseId, typeof(int), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty TestPointIdProperty = TestProperty.Register(TestPointId, TestPointId, typeof(int), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty TestPointIdProperty = TestProperty.Register(TestPointId, TestPointId, typeof(int), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty TestConfigurationIdProperty = TestProperty.Register(TestConfigurationId, TestConfigurationId, typeof(int), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty TestConfigurationIdProperty = TestProperty.Register(TestConfigurationId, TestConfigurationId, typeof(int), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty TestConfigurationNameProperty = TestProperty.Register(TestConfigurationName, TestConfigurationName, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty TestConfigurationNameProperty = TestProperty.Register(TestConfigurationName, TestConfigurationName, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty IsInLabEnvironmentProperty = TestProperty.Register(IsInLabEnvironment, IsInLabEnvironment, typeof(bool), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty IsInLabEnvironmentProperty = TestProperty.Register(IsInLabEnvironment, IsInLabEnvironment, typeof(bool), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty BuildConfigurationIdProperty = TestProperty.Register(BuildConfigurationId, BuildConfigurationId, typeof(int), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty BuildConfigurationIdProperty = TestProperty.Register(BuildConfigurationId, BuildConfigurationId, typeof(int), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty BuildDirectoryProperty = TestProperty.Register(BuildDirectory, BuildDirectory, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty BuildDirectoryProperty = TestProperty.Register(BuildDirectory, BuildDirectory, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty BuildFlavorProperty = TestProperty.Register(BuildFlavor, BuildFlavor, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty BuildFlavorProperty = TestProperty.Register(BuildFlavor, BuildFlavor, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty BuildNumberProperty = TestProperty.Register(BuildNumber, BuildNumber, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty BuildNumberProperty = TestProperty.Register(BuildNumber, BuildNumber, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty BuildPlatformProperty = TestProperty.Register(BuildPlatform, BuildPlatform, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty BuildPlatformProperty = TestProperty.Register(BuildPlatform, BuildPlatform, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty BuildUriProperty = TestProperty.Register(BuildUri, BuildUri, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty BuildUriProperty = TestProperty.Register(BuildUri, BuildUri, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty TfsServerCollectionUrlProperty = TestProperty.Register(TfsServerCollectionUrl, TfsServerCollectionUrl, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty TfsServerCollectionUrlProperty = TestProperty.Register(TfsServerCollectionUrl, TfsServerCollectionUrl, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty TfsTeamProjectProperty = TestProperty.Register(TfsTeamProject, TfsTeamProject, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty TfsTeamProjectProperty = TestProperty.Register(TfsTeamProject, TfsTeamProject, typeof(string), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty TestDynamicDataTypeProperty = TestProperty.Register("MSTest.DynamicDataType", "DynamicDataType", typeof(int), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty TestDynamicDataTypeProperty = TestProperty.Register("MSTest.DynamicDataType", "DynamicDataType", typeof(int), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
internal static readonly TestProperty TestDynamicDataProperty = TestProperty.Register("MSTest.DynamicData", "DynamicData", typeof(string[]), TestPropertyAttributes.Hidden, typeof(TestCase));
|
internal static readonly TestProperty TestDynamicDataProperty = TestProperty.Register("MSTest.DynamicData", "DynamicData", typeof(string[]), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Private Constants
|
#region Private Constants
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// These are the Test properties used by the adapter, which essentially correspond
|
/// These are the Test properties used by the adapter, which essentially correspond
|
||||||
/// to attributes on tests, and may be available in command line/TeamBuild to filter tests.
|
/// to attributes on tests, and may be available in command line/TeamBuild to filter tests.
|
||||||
/// These Property names should not be localized.
|
/// These Property names should not be localized.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const string TestClassNameLabel = "ClassName";
|
private const string TestClassNameLabel = "ClassName";
|
||||||
private const string DeclaringClassNameLabel = "DeclaringClassName";
|
private const string DeclaringClassNameLabel = "DeclaringClassName";
|
||||||
private const string IsAsyncLabel = "IsAsync";
|
private const string IsAsyncLabel = "IsAsync";
|
||||||
private const string TestCategoryLabel = "TestCategory";
|
private const string TestCategoryLabel = "TestCategory";
|
||||||
private const string PriorityLabel = "Priority";
|
private const string PriorityLabel = "Priority";
|
||||||
private const string DeploymentItemsLabel = "DeploymentItems";
|
private const string DeploymentItemsLabel = "DeploymentItems";
|
||||||
private const string DoNotParallelizeLabel = "DoNotParallelize";
|
private const string DoNotParallelizeLabel = "DoNotParallelize";
|
||||||
private const string ExecutionIdLabel = "ExecutionId";
|
private const string ExecutionIdLabel = "ExecutionId";
|
||||||
private const string ParentExecIdLabel = "ParentExecId";
|
private const string ParentExecIdLabel = "ParentExecId";
|
||||||
private const string InnerResultsCountLabel = "InnerResultsCount";
|
private const string InnerResultsCountLabel = "InnerResultsCount";
|
||||||
private const string DescriptionLabel = "Description";
|
private const string DescriptionLabel = "Description";
|
||||||
private const string CssIterationLabel = "CssIteration";
|
private const string CssIterationLabel = "CssIteration";
|
||||||
private const string CssProjectStructureLabel = "CssProjectStructure";
|
private const string CssProjectStructureLabel = "CssProjectStructure";
|
||||||
private const string WorkItemIdsLabel = "WorkItemIds";
|
private const string WorkItemIdsLabel = "WorkItemIds";
|
||||||
|
|
||||||
private const string TestRunId = "__Tfs_TestRunId__";
|
private const string TestRunId = "__Tfs_TestRunId__";
|
||||||
private const string TestPlanId = "__Tfs_TestPlanId__";
|
private const string TestPlanId = "__Tfs_TestPlanId__";
|
||||||
private const string TestCaseId = "__Tfs_TestCaseId__";
|
private const string TestCaseId = "__Tfs_TestCaseId__";
|
||||||
private const string TestPointId = "__Tfs_TestPointId__";
|
private const string TestPointId = "__Tfs_TestPointId__";
|
||||||
private const string TestConfigurationId = "__Tfs_TestConfigurationId__";
|
private const string TestConfigurationId = "__Tfs_TestConfigurationId__";
|
||||||
private const string TestConfigurationName = "__Tfs_TestConfigurationName__";
|
private const string TestConfigurationName = "__Tfs_TestConfigurationName__";
|
||||||
private const string IsInLabEnvironment = "__Tfs_IsInLabEnvironment__";
|
private const string IsInLabEnvironment = "__Tfs_IsInLabEnvironment__";
|
||||||
private const string BuildConfigurationId = "__Tfs_BuildConfigurationId__";
|
private const string BuildConfigurationId = "__Tfs_BuildConfigurationId__";
|
||||||
private const string BuildDirectory = "__Tfs_BuildDirectory__";
|
private const string BuildDirectory = "__Tfs_BuildDirectory__";
|
||||||
private const string BuildFlavor = "__Tfs_BuildFlavor__";
|
private const string BuildFlavor = "__Tfs_BuildFlavor__";
|
||||||
private const string BuildNumber = "__Tfs_BuildNumber__";
|
private const string BuildNumber = "__Tfs_BuildNumber__";
|
||||||
private const string BuildPlatform = "__Tfs_BuildPlatform__";
|
private const string BuildPlatform = "__Tfs_BuildPlatform__";
|
||||||
private const string BuildUri = "__Tfs_BuildUri__";
|
private const string BuildUri = "__Tfs_BuildUri__";
|
||||||
private const string TfsServerCollectionUrl = "__Tfs_TfsServerCollectionUrl__";
|
private const string TfsServerCollectionUrl = "__Tfs_TfsServerCollectionUrl__";
|
||||||
private const string TfsTeamProject = "__Tfs_TeamProject__";
|
private const string TfsTeamProject = "__Tfs_TeamProject__";
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,417 +1,416 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using System.Security;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
|
||||||
|
using UTF = Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerates through all types in the assembly in search of valid test methods.
|
||||||
|
/// </summary>
|
||||||
|
internal class AssemblyEnumerator : MarshalByRefObject
|
||||||
{
|
{
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Collections.Generic;
|
/// Helper for reflection API's.
|
||||||
using System.Diagnostics;
|
/// </summary>
|
||||||
using System.Globalization;
|
private static readonly ReflectHelper ReflectHelper = ReflectHelper.Instance;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using System.Security;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution;
|
/// <summary>
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
/// Type cache
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
/// </summary>
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
private readonly TypeCache typeCache = new(ReflectHelper);
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
|
||||||
|
|
||||||
using UTF = Microsoft.VisualStudio.TestTools.UnitTesting;
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AssemblyEnumerator"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public AssemblyEnumerator()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AssemblyEnumerator"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="settings">The settings for the session.</param>
|
||||||
|
/// <remarks>Use this constructor when creating this object in a new app domain so the settings for this app domain are set.</remarks>
|
||||||
|
public AssemblyEnumerator(MSTestSettings settings)
|
||||||
|
{
|
||||||
|
// Populate the settings into the domain(Desktop workflow) performing discovery.
|
||||||
|
// This would just be resettings the settings to itself in non desktop workflows.
|
||||||
|
MSTestSettings.PopulateSettings(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the run settings to use for current discovery session.
|
||||||
|
/// </summary>
|
||||||
|
public string RunSettingsXml { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns object to be used for controlling lifetime, null means infinite lifetime.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The <see cref="object"/>.
|
||||||
|
/// </returns>
|
||||||
|
[SecurityCritical]
|
||||||
|
#if NET5_0_OR_GREATER
|
||||||
|
[Obsolete]
|
||||||
|
#endif
|
||||||
|
public override object InitializeLifetimeService()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enumerates through all types in the assembly in search of valid test methods.
|
/// Enumerates through all types in the assembly in search of valid test methods.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class AssemblyEnumerator : MarshalByRefObject
|
/// <param name="assemblyFileName">The assembly file name.</param>
|
||||||
|
/// <param name="warnings">Contains warnings if any, that need to be passed back to the caller.</param>
|
||||||
|
/// <returns>A collection of Test Elements.</returns>
|
||||||
|
internal ICollection<UnitTestElement> EnumerateAssembly(string assemblyFileName, out ICollection<string> warnings)
|
||||||
{
|
{
|
||||||
/// <summary>
|
Debug.Assert(!string.IsNullOrWhiteSpace(assemblyFileName), "Invalid assembly file name.");
|
||||||
/// Helper for reflection API's.
|
|
||||||
/// </summary>
|
|
||||||
private static readonly ReflectHelper ReflectHelper = ReflectHelper.Instance;
|
|
||||||
|
|
||||||
/// <summary>
|
var runSettingsXml = this.RunSettingsXml;
|
||||||
/// Type cache
|
var warningMessages = new List<string>();
|
||||||
/// </summary>
|
var tests = new List<UnitTestElement>();
|
||||||
private readonly TypeCache typeCache = new(ReflectHelper);
|
|
||||||
|
|
||||||
/// <summary>
|
var assembly = PlatformServiceProvider.Instance.FileOperations.LoadAssembly(assemblyFileName, isReflectionOnly: false);
|
||||||
/// Initializes a new instance of the <see cref="AssemblyEnumerator"/> class.
|
|
||||||
/// </summary>
|
var types = this.GetTypes(assembly, assemblyFileName, warningMessages);
|
||||||
public AssemblyEnumerator()
|
var discoverInternals = assembly.GetCustomAttribute<UTF.DiscoverInternalsAttribute>() != null;
|
||||||
|
var testDataSourceDiscovery = assembly.GetCustomAttribute<UTF.TestDataSourceDiscoveryAttribute>()?.DiscoveryOption ?? UTF.TestDataSourceDiscoveryOption.DuringDiscovery;
|
||||||
|
|
||||||
|
foreach (var type in types)
|
||||||
{
|
{
|
||||||
}
|
if (type == null)
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="AssemblyEnumerator"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="settings">The settings for the session.</param>
|
|
||||||
/// <remarks>Use this constructor when creating this object in a new app domain so the settings for this app domain are set.</remarks>
|
|
||||||
public AssemblyEnumerator(MSTestSettings settings)
|
|
||||||
{
|
|
||||||
// Populate the settings into the domain(Desktop workflow) performing discovery.
|
|
||||||
// This would just be resettings the settings to itself in non desktop workflows.
|
|
||||||
MSTestSettings.PopulateSettings(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the run settings to use for current discovery session.
|
|
||||||
/// </summary>
|
|
||||||
public string RunSettingsXml { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns object to be used for controlling lifetime, null means infinite lifetime.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// The <see cref="object"/>.
|
|
||||||
/// </returns>
|
|
||||||
[SecurityCritical]
|
|
||||||
#if NET5_0_OR_GREATER
|
|
||||||
[Obsolete]
|
|
||||||
#endif
|
|
||||||
public override object InitializeLifetimeService()
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Enumerates through all types in the assembly in search of valid test methods.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assemblyFileName">The assembly file name.</param>
|
|
||||||
/// <param name="warnings">Contains warnings if any, that need to be passed back to the caller.</param>
|
|
||||||
/// <returns>A collection of Test Elements.</returns>
|
|
||||||
internal ICollection<UnitTestElement> EnumerateAssembly(string assemblyFileName, out ICollection<string> warnings)
|
|
||||||
{
|
|
||||||
Debug.Assert(!string.IsNullOrWhiteSpace(assemblyFileName), "Invalid assembly file name.");
|
|
||||||
|
|
||||||
var runSettingsXml = this.RunSettingsXml;
|
|
||||||
var warningMessages = new List<string>();
|
|
||||||
var tests = new List<UnitTestElement>();
|
|
||||||
|
|
||||||
var assembly = PlatformServiceProvider.Instance.FileOperations.LoadAssembly(assemblyFileName, isReflectionOnly: false);
|
|
||||||
|
|
||||||
var types = this.GetTypes(assembly, assemblyFileName, warningMessages);
|
|
||||||
var discoverInternals = assembly.GetCustomAttribute<UTF.DiscoverInternalsAttribute>() != null;
|
|
||||||
var testDataSourceDiscovery = assembly.GetCustomAttribute<UTF.TestDataSourceDiscoveryAttribute>()?.DiscoveryOption ?? UTF.TestDataSourceDiscoveryOption.DuringDiscovery;
|
|
||||||
|
|
||||||
foreach (var type in types)
|
|
||||||
{
|
{
|
||||||
if (type == null)
|
continue;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var testsInType = this.DiscoverTestsInType(assemblyFileName, runSettingsXml, assembly, type, warningMessages, discoverInternals, testDataSourceDiscovery);
|
|
||||||
tests.AddRange(testsInType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
warnings = warningMessages;
|
var testsInType = this.DiscoverTestsInType(assemblyFileName, runSettingsXml, assembly, type, warningMessages, discoverInternals, testDataSourceDiscovery);
|
||||||
return tests;
|
tests.AddRange(testsInType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
warnings = warningMessages;
|
||||||
/// Gets the types defined in an assembly.
|
return tests;
|
||||||
/// </summary>
|
}
|
||||||
/// <param name="assembly">The reflected assembly.</param>
|
|
||||||
/// <param name="assemblyFileName">The file name of the assembly.</param>
|
/// <summary>
|
||||||
/// <param name="warningMessages">Contains warnings if any, that need to be passed back to the caller.</param>
|
/// Gets the types defined in an assembly.
|
||||||
/// <returns>Gets the types defined in the provided assembly.</returns>
|
/// </summary>
|
||||||
internal Type[] GetTypes(Assembly assembly, string assemblyFileName, ICollection<string> warningMessages)
|
/// <param name="assembly">The reflected assembly.</param>
|
||||||
|
/// <param name="assemblyFileName">The file name of the assembly.</param>
|
||||||
|
/// <param name="warningMessages">Contains warnings if any, that need to be passed back to the caller.</param>
|
||||||
|
/// <returns>Gets the types defined in the provided assembly.</returns>
|
||||||
|
internal Type[] GetTypes(Assembly assembly, string assemblyFileName, ICollection<string> warningMessages)
|
||||||
|
{
|
||||||
|
var types = new List<Type>();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var types = new List<Type>();
|
types.AddRange(assembly.DefinedTypes.Select(typeinfo => typeinfo.AsType()));
|
||||||
try
|
|
||||||
{
|
|
||||||
types.AddRange(assembly.DefinedTypes.Select(typeinfo => typeinfo.AsType()));
|
|
||||||
}
|
|
||||||
catch (ReflectionTypeLoadException ex)
|
|
||||||
{
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning($"MSTestExecutor.TryGetTests: {Resource.TestAssembly_AssemblyDiscoveryFailure}", assemblyFileName, ex);
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning(Resource.ExceptionsThrown);
|
|
||||||
|
|
||||||
if (ex.LoaderExceptions != null)
|
|
||||||
{
|
|
||||||
// If not able to load all type, log a warning and continue with loaded types.
|
|
||||||
var message = string.Format(CultureInfo.CurrentCulture, Resource.TypeLoadFailed, assemblyFileName, this.GetLoadExceptionDetails(ex));
|
|
||||||
|
|
||||||
warningMessages?.Add(message);
|
|
||||||
|
|
||||||
foreach (var loaderEx in ex.LoaderExceptions)
|
|
||||||
{
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning("{0}", loaderEx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ex.Types;
|
|
||||||
}
|
|
||||||
|
|
||||||
return types.ToArray();
|
|
||||||
}
|
}
|
||||||
|
catch (ReflectionTypeLoadException ex)
|
||||||
/// <summary>
|
|
||||||
/// Formats load exception as multi-line string, each line contains load error message.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="ex">The exception.</param>
|
|
||||||
/// <returns>Returns loader exceptions as a multi-line string.</returns>
|
|
||||||
internal string GetLoadExceptionDetails(ReflectionTypeLoadException ex)
|
|
||||||
{
|
{
|
||||||
Debug.Assert(ex != null, "exception should not be null.");
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning($"MSTestExecutor.TryGetTests: {Resource.TestAssembly_AssemblyDiscoveryFailure}", assemblyFileName, ex);
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning(Resource.ExceptionsThrown);
|
||||||
var map = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); // Exception -> null.
|
|
||||||
var errorDetails = new StringBuilder();
|
|
||||||
|
|
||||||
if (ex.LoaderExceptions != null)
|
if (ex.LoaderExceptions != null)
|
||||||
{
|
{
|
||||||
// Loader exceptions can contain duplicates, leave only unique exceptions.
|
// If not able to load all type, log a warning and continue with loaded types.
|
||||||
foreach (var loaderException in ex.LoaderExceptions)
|
var message = string.Format(CultureInfo.CurrentCulture, Resource.TypeLoadFailed, assemblyFileName, this.GetLoadExceptionDetails(ex));
|
||||||
|
|
||||||
|
warningMessages?.Add(message);
|
||||||
|
|
||||||
|
foreach (var loaderEx in ex.LoaderExceptions)
|
||||||
{
|
{
|
||||||
Debug.Assert(loaderException != null, "loader exception should not be null.");
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning("{0}", loaderEx);
|
||||||
var line = string.Format(CultureInfo.CurrentCulture, Resource.EnumeratorLoadTypeErrorFormat, loaderException.GetType(), loaderException.Message);
|
|
||||||
if (!map.ContainsKey(line))
|
|
||||||
{
|
|
||||||
map.Add(line, null);
|
|
||||||
errorDetails.AppendLine(line);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
return ex.Types;
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Formats load exception as multi-line string, each line contains load error message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ex">The exception.</param>
|
||||||
|
/// <returns>Returns loader exceptions as a multi-line string.</returns>
|
||||||
|
internal string GetLoadExceptionDetails(ReflectionTypeLoadException ex)
|
||||||
|
{
|
||||||
|
Debug.Assert(ex != null, "exception should not be null.");
|
||||||
|
|
||||||
|
var map = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); // Exception -> null.
|
||||||
|
var errorDetails = new StringBuilder();
|
||||||
|
|
||||||
|
if (ex.LoaderExceptions != null)
|
||||||
|
{
|
||||||
|
// Loader exceptions can contain duplicates, leave only unique exceptions.
|
||||||
|
foreach (var loaderException in ex.LoaderExceptions)
|
||||||
{
|
{
|
||||||
errorDetails.AppendLine(ex.Message);
|
Debug.Assert(loaderException != null, "loader exception should not be null.");
|
||||||
|
var line = string.Format(CultureInfo.CurrentCulture, Resource.EnumeratorLoadTypeErrorFormat, loaderException.GetType(), loaderException.Message);
|
||||||
|
if (!map.ContainsKey(line))
|
||||||
|
{
|
||||||
|
map.Add(line, null);
|
||||||
|
errorDetails.AppendLine(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
errorDetails.AppendLine(ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorDetails.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an instance of the <see cref="TypeEnumerator"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type to enumerate.</param>
|
||||||
|
/// <param name="assemblyFileName">The reflected assembly name.</param>
|
||||||
|
/// <param name="discoverInternals">True to discover test classes which are declared internal in
|
||||||
|
/// addition to test classes which are declared public.</param>
|
||||||
|
/// <returns>a TypeEnumerator instance.</returns>
|
||||||
|
internal virtual TypeEnumerator GetTypeEnumerator(Type type, string assemblyFileName, bool discoverInternals = false)
|
||||||
|
{
|
||||||
|
var typeValidator = new TypeValidator(ReflectHelper, discoverInternals);
|
||||||
|
var testMethodValidator = new TestMethodValidator(ReflectHelper, discoverInternals);
|
||||||
|
|
||||||
|
return new TypeEnumerator(type, assemblyFileName, ReflectHelper, typeValidator, testMethodValidator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<UnitTestElement> DiscoverTestsInType(string assemblyFileName, string runSettingsXml, Assembly assembly, Type type, List<string> warningMessages, bool discoverInternals = false, UTF.TestDataSourceDiscoveryOption discoveryOption = UTF.TestDataSourceDiscoveryOption.DuringExecution)
|
||||||
|
{
|
||||||
|
var sourceLevelParameters = PlatformServiceProvider.Instance.SettingsProvider.GetProperties(assemblyFileName);
|
||||||
|
sourceLevelParameters = RunSettingsUtilities.GetTestRunParameters(runSettingsXml)?.ConcatWithOverwrites(sourceLevelParameters)
|
||||||
|
?? sourceLevelParameters
|
||||||
|
?? new Dictionary<string, object>();
|
||||||
|
|
||||||
|
string typeFullName = null;
|
||||||
|
var tests = new List<UnitTestElement>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
typeFullName = type.FullName;
|
||||||
|
var testTypeEnumerator = this.GetTypeEnumerator(type, assemblyFileName, discoverInternals);
|
||||||
|
var unitTestCases = testTypeEnumerator.Enumerate(out var warningsFromTypeEnumerator);
|
||||||
|
var typeIgnored = ReflectHelper.IsAttributeDefined(type, typeof(UTF.IgnoreAttribute), false);
|
||||||
|
|
||||||
|
if (warningsFromTypeEnumerator != null)
|
||||||
|
{
|
||||||
|
warningMessages.AddRange(warningsFromTypeEnumerator);
|
||||||
}
|
}
|
||||||
|
|
||||||
return errorDetails.ToString();
|
if (unitTestCases != null)
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns an instance of the <see cref="TypeEnumerator"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">The type to enumerate.</param>
|
|
||||||
/// <param name="assemblyFileName">The reflected assembly name.</param>
|
|
||||||
/// <param name="discoverInternals">True to discover test classes which are declared internal in
|
|
||||||
/// addition to test classes which are declared public.</param>
|
|
||||||
/// <returns>a TypeEnumerator instance.</returns>
|
|
||||||
internal virtual TypeEnumerator GetTypeEnumerator(Type type, string assemblyFileName, bool discoverInternals = false)
|
|
||||||
{
|
|
||||||
var typeValidator = new TypeValidator(ReflectHelper, discoverInternals);
|
|
||||||
var testMethodValidator = new TestMethodValidator(ReflectHelper, discoverInternals);
|
|
||||||
|
|
||||||
return new TypeEnumerator(type, assemblyFileName, ReflectHelper, typeValidator, testMethodValidator);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<UnitTestElement> DiscoverTestsInType(string assemblyFileName, string runSettingsXml, Assembly assembly, Type type, List<string> warningMessages, bool discoverInternals = false, UTF.TestDataSourceDiscoveryOption discoveryOption = UTF.TestDataSourceDiscoveryOption.DuringExecution)
|
|
||||||
{
|
|
||||||
var sourceLevelParameters = PlatformServiceProvider.Instance.SettingsProvider.GetProperties(assemblyFileName);
|
|
||||||
sourceLevelParameters = RunSettingsUtilities.GetTestRunParameters(runSettingsXml)?.ConcatWithOverwrites(sourceLevelParameters)
|
|
||||||
?? sourceLevelParameters
|
|
||||||
?? new Dictionary<string, object>();
|
|
||||||
|
|
||||||
string typeFullName = null;
|
|
||||||
var tests = new List<UnitTestElement>();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
typeFullName = type.FullName;
|
foreach (var test in unitTestCases)
|
||||||
var testTypeEnumerator = this.GetTypeEnumerator(type, assemblyFileName, discoverInternals);
|
|
||||||
var unitTestCases = testTypeEnumerator.Enumerate(out var warningsFromTypeEnumerator);
|
|
||||||
var typeIgnored = ReflectHelper.IsAttributeDefined(type, typeof(UTF.IgnoreAttribute), false);
|
|
||||||
|
|
||||||
if (warningsFromTypeEnumerator != null)
|
|
||||||
{
|
{
|
||||||
warningMessages.AddRange(warningsFromTypeEnumerator);
|
if (discoveryOption == UTF.TestDataSourceDiscoveryOption.DuringDiscovery)
|
||||||
}
|
|
||||||
|
|
||||||
if (unitTestCases != null)
|
|
||||||
{
|
|
||||||
foreach (var test in unitTestCases)
|
|
||||||
{
|
{
|
||||||
if (discoveryOption == UTF.TestDataSourceDiscoveryOption.DuringDiscovery)
|
if (this.DynamicDataAttached(sourceLevelParameters, assembly, test, tests))
|
||||||
{
|
{
|
||||||
if (this.DynamicDataAttached(sourceLevelParameters, assembly, test, tests))
|
continue;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tests.Add(test);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
// If we fail to discover type from a class, then don't abort the discovery
|
|
||||||
// Move to the next type.
|
|
||||||
string message = string.Format(CultureInfo.CurrentCulture, Resource.CouldNotInspectTypeDuringDiscovery, typeFullName, assemblyFileName, exception.Message);
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo($"AssemblyEnumerator: {message}");
|
|
||||||
warningMessages.Add(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tests;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool DynamicDataAttached(IDictionary<string, object> sourceLevelParameters, Assembly assembly, UnitTestElement test, List<UnitTestElement> tests)
|
|
||||||
{
|
|
||||||
// It should always be `true`, but if any part of the chain is obsolete; it might not contain those.
|
|
||||||
// Since we depend on those properties, if they don't exist, we bail out early.
|
|
||||||
if (!test.TestMethod.HasManagedMethodAndTypeProperties)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var writer = new ThreadSafeStringWriter(CultureInfo.InvariantCulture, "all");
|
|
||||||
var testMethod = test.TestMethod;
|
|
||||||
var testContext = PlatformServiceProvider.Instance.GetTestContext(testMethod, writer, sourceLevelParameters);
|
|
||||||
var testMethodInfo = this.typeCache.GetTestMethodInfo(testMethod, testContext, MSTestSettings.CurrentSettings.CaptureDebugTraces);
|
|
||||||
if (testMethodInfo == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return /* DataSourceAttribute discovery is disabled for now, since we cannot serialize DataRow values.
|
|
||||||
this.TryProcessDataSource(test, testMethodInfo, testContext, tests) || */
|
|
||||||
this.TryProcessTestDataSourceTests(test, testMethodInfo, tests);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryProcessDataSource(UnitTestElement test, TestMethodInfo testMethodInfo, ITestContext testContext, List<UnitTestElement> tests)
|
|
||||||
{
|
|
||||||
var dataSourceAttributes = ReflectHelper.GetAttributes<UTF.DataSourceAttribute>(testMethodInfo.MethodInfo, false);
|
|
||||||
if (dataSourceAttributes == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dataSourceAttributes.Length > 1)
|
|
||||||
{
|
|
||||||
var message = string.Format(CultureInfo.CurrentCulture, Resource.CannotEnumerateDataSourceAttribute_MoreThenOneDefined, test.TestMethod.ManagedTypeName, test.TestMethod.ManagedMethodName, dataSourceAttributes.Length);
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo($"DynamicDataEnumarator: {message}");
|
|
||||||
throw new InvalidOperationException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// dataSourceAttributes.Length == 1
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return this.ProcessDataSourceTests(test, testMethodInfo, testContext, tests);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var message = string.Format(CultureInfo.CurrentCulture, Resource.CannotEnumerateDataSourceAttribute, test.TestMethod.ManagedTypeName, test.TestMethod.ManagedMethodName, ex);
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo($"DynamicDataEnumarator: {message}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ProcessDataSourceTests(UnitTestElement test, TestMethodInfo testMethodInfo, ITestContext testContext, List<UnitTestElement> tests)
|
|
||||||
{
|
|
||||||
var dataRows = PlatformServiceProvider.Instance.TestDataSource.GetData(testMethodInfo, testContext);
|
|
||||||
if (dataRows == null || !dataRows.Any())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
int rowIndex = 0;
|
|
||||||
|
|
||||||
foreach (var dataRow in dataRows)
|
|
||||||
{
|
|
||||||
// TODO: Test serialization
|
|
||||||
rowIndex++;
|
|
||||||
|
|
||||||
var displayName = string.Format(CultureInfo.CurrentCulture, Resource.DataDrivenResultDisplayName, test.DisplayName, rowIndex);
|
|
||||||
var discoveredTest = test.Clone();
|
|
||||||
discoveredTest.DisplayName = displayName;
|
|
||||||
discoveredTest.TestMethod.DataType = DynamicDataType.DataSourceAttribute;
|
|
||||||
discoveredTest.TestMethod.SerializedData = DataSerializationHelper.Serialize(new[] { (object)rowIndex });
|
|
||||||
tests.Add(discoveredTest);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
testContext.SetDataConnection(null);
|
|
||||||
testContext.SetDataRow(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryProcessTestDataSourceTests(UnitTestElement test, TestMethodInfo testMethodInfo, List<UnitTestElement> tests)
|
|
||||||
{
|
|
||||||
var methodInfo = testMethodInfo.MethodInfo;
|
|
||||||
var testDataSources = ReflectHelper.GetAttributes<Attribute>(methodInfo, false)?.Where(a => a is UTF.ITestDataSource).OfType<UTF.ITestDataSource>().ToArray();
|
|
||||||
if (testDataSources == null || testDataSources.Length == 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return this.ProcessTestDataSourceTests(test, (MethodInfo)methodInfo, testDataSources, tests);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var message = string.Format(CultureInfo.CurrentCulture, Resource.CannotEnumerateIDataSourceAttribute, test.TestMethod.ManagedTypeName, test.TestMethod.ManagedMethodName, ex);
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo($"DynamicDataEnumarator: {message}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ProcessTestDataSourceTests(UnitTestElement test, MethodInfo methodInfo, UTF.ITestDataSource[] testDataSources, List<UnitTestElement> tests)
|
|
||||||
{
|
|
||||||
foreach (var dataSource in testDataSources)
|
|
||||||
{
|
|
||||||
var data = dataSource.GetData(methodInfo);
|
|
||||||
var discoveredTests = new Dictionary<string, UnitTestElement>();
|
|
||||||
var discoveredIndex = new Dictionary<string, int>();
|
|
||||||
var serializationFailed = false;
|
|
||||||
var index = 0;
|
|
||||||
|
|
||||||
foreach (var d in data)
|
|
||||||
{
|
|
||||||
var discoveredTest = test.Clone();
|
|
||||||
discoveredTest.DisplayName = dataSource.GetDisplayName(methodInfo, d) ?? discoveredTest.DisplayName;
|
|
||||||
|
|
||||||
// if we have a duplicate test name don't expand the test, bail out.
|
|
||||||
if (discoveredTests.ContainsKey(discoveredTest.DisplayName))
|
|
||||||
{
|
|
||||||
var firstSeen = discoveredIndex[discoveredTest.DisplayName];
|
|
||||||
var warning = string.Format(CultureInfo.CurrentCulture, Resource.CannotExpandIDataSourceAttribute_DuplicateDisplayName, firstSeen, index, discoveredTest.DisplayName);
|
|
||||||
warning = string.Format(CultureInfo.CurrentUICulture, Resource.CannotExpandIDataSourceAttribute, test.TestMethod.ManagedTypeName, test.TestMethod.ManagedMethodName, warning);
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning($"DynamicDataEnumarator: {warning}");
|
|
||||||
|
|
||||||
serializationFailed = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
discoveredTest.TestMethod.SerializedData = DataSerializationHelper.Serialize(d);
|
|
||||||
discoveredTest.TestMethod.DataType = DynamicDataType.ITestDataSource;
|
|
||||||
}
|
|
||||||
catch (SerializationException)
|
|
||||||
{
|
|
||||||
var firstSeen = discoveredIndex[discoveredTest.DisplayName];
|
|
||||||
var warning = string.Format(CultureInfo.CurrentCulture, Resource.CannotExpandIDataSourceAttribute_CannotSerialize, index, discoveredTest.DisplayName);
|
|
||||||
warning = string.Format(CultureInfo.CurrentUICulture, Resource.CannotExpandIDataSourceAttribute, test.TestMethod.ManagedTypeName, test.TestMethod.ManagedMethodName, warning);
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning($"DynamicDataEnumarator: {warning}");
|
|
||||||
|
|
||||||
serializationFailed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
discoveredTests[discoveredTest.DisplayName] = discoveredTest;
|
|
||||||
discoveredIndex[discoveredTest.DisplayName] = index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialization failed for the type, bail out.
|
|
||||||
if (serializationFailed)
|
|
||||||
{
|
|
||||||
tests.Add(test);
|
tests.Add(test);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
// If we fail to discover type from a class, then don't abort the discovery
|
||||||
|
// Move to the next type.
|
||||||
|
string message = string.Format(CultureInfo.CurrentCulture, Resource.CouldNotInspectTypeDuringDiscovery, typeFullName, assemblyFileName, exception.Message);
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo($"AssemblyEnumerator: {message}");
|
||||||
|
warningMessages.Add(message);
|
||||||
|
}
|
||||||
|
|
||||||
tests.AddRange(discoveredTests.Values);
|
return tests;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DynamicDataAttached(IDictionary<string, object> sourceLevelParameters, Assembly assembly, UnitTestElement test, List<UnitTestElement> tests)
|
||||||
|
{
|
||||||
|
// It should always be `true`, but if any part of the chain is obsolete; it might not contain those.
|
||||||
|
// Since we depend on those properties, if they don't exist, we bail out early.
|
||||||
|
if (!test.TestMethod.HasManagedMethodAndTypeProperties)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var writer = new ThreadSafeStringWriter(CultureInfo.InvariantCulture, "all");
|
||||||
|
var testMethod = test.TestMethod;
|
||||||
|
var testContext = PlatformServiceProvider.Instance.GetTestContext(testMethod, writer, sourceLevelParameters);
|
||||||
|
var testMethodInfo = this.typeCache.GetTestMethodInfo(testMethod, testContext, MSTestSettings.CurrentSettings.CaptureDebugTraces);
|
||||||
|
if (testMethodInfo == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return /* DataSourceAttribute discovery is disabled for now, since we cannot serialize DataRow values.
|
||||||
|
this.TryProcessDataSource(test, testMethodInfo, testContext, tests) || */
|
||||||
|
this.TryProcessTestDataSourceTests(test, testMethodInfo, tests);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryProcessDataSource(UnitTestElement test, TestMethodInfo testMethodInfo, ITestContext testContext, List<UnitTestElement> tests)
|
||||||
|
{
|
||||||
|
var dataSourceAttributes = ReflectHelper.GetAttributes<UTF.DataSourceAttribute>(testMethodInfo.MethodInfo, false);
|
||||||
|
if (dataSourceAttributes == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataSourceAttributes.Length > 1)
|
||||||
|
{
|
||||||
|
var message = string.Format(CultureInfo.CurrentCulture, Resource.CannotEnumerateDataSourceAttribute_MoreThenOneDefined, test.TestMethod.ManagedTypeName, test.TestMethod.ManagedMethodName, dataSourceAttributes.Length);
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo($"DynamicDataEnumarator: {message}");
|
||||||
|
throw new InvalidOperationException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// dataSourceAttributes.Length == 1
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return this.ProcessDataSourceTests(test, testMethodInfo, testContext, tests);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var message = string.Format(CultureInfo.CurrentCulture, Resource.CannotEnumerateDataSourceAttribute, test.TestMethod.ManagedTypeName, test.TestMethod.ManagedMethodName, ex);
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo($"DynamicDataEnumarator: {message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ProcessDataSourceTests(UnitTestElement test, TestMethodInfo testMethodInfo, ITestContext testContext, List<UnitTestElement> tests)
|
||||||
|
{
|
||||||
|
var dataRows = PlatformServiceProvider.Instance.TestDataSource.GetData(testMethodInfo, testContext);
|
||||||
|
if (dataRows == null || !dataRows.Any())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int rowIndex = 0;
|
||||||
|
|
||||||
|
foreach (var dataRow in dataRows)
|
||||||
|
{
|
||||||
|
// TODO: Test serialization
|
||||||
|
rowIndex++;
|
||||||
|
|
||||||
|
var displayName = string.Format(CultureInfo.CurrentCulture, Resource.DataDrivenResultDisplayName, test.DisplayName, rowIndex);
|
||||||
|
var discoveredTest = test.Clone();
|
||||||
|
discoveredTest.DisplayName = displayName;
|
||||||
|
discoveredTest.TestMethod.DataType = DynamicDataType.DataSourceAttribute;
|
||||||
|
discoveredTest.TestMethod.SerializedData = DataSerializationHelper.Serialize(new[] { (object)rowIndex });
|
||||||
|
tests.Add(discoveredTest);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
testContext.SetDataConnection(null);
|
||||||
|
testContext.SetDataRow(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryProcessTestDataSourceTests(UnitTestElement test, TestMethodInfo testMethodInfo, List<UnitTestElement> tests)
|
||||||
|
{
|
||||||
|
var methodInfo = testMethodInfo.MethodInfo;
|
||||||
|
var testDataSources = ReflectHelper.GetAttributes<Attribute>(methodInfo, false)?.Where(a => a is UTF.ITestDataSource).OfType<UTF.ITestDataSource>().ToArray();
|
||||||
|
if (testDataSources == null || testDataSources.Length == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return this.ProcessTestDataSourceTests(test, (MethodInfo)methodInfo, testDataSources, tests);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var message = string.Format(CultureInfo.CurrentCulture, Resource.CannotEnumerateIDataSourceAttribute, test.TestMethod.ManagedTypeName, test.TestMethod.ManagedMethodName, ex);
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo($"DynamicDataEnumarator: {message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ProcessTestDataSourceTests(UnitTestElement test, MethodInfo methodInfo, UTF.ITestDataSource[] testDataSources, List<UnitTestElement> tests)
|
||||||
|
{
|
||||||
|
foreach (var dataSource in testDataSources)
|
||||||
|
{
|
||||||
|
var data = dataSource.GetData(methodInfo);
|
||||||
|
var discoveredTests = new Dictionary<string, UnitTestElement>();
|
||||||
|
var discoveredIndex = new Dictionary<string, int>();
|
||||||
|
var serializationFailed = false;
|
||||||
|
var index = 0;
|
||||||
|
|
||||||
|
foreach (var d in data)
|
||||||
|
{
|
||||||
|
var discoveredTest = test.Clone();
|
||||||
|
discoveredTest.DisplayName = dataSource.GetDisplayName(methodInfo, d) ?? discoveredTest.DisplayName;
|
||||||
|
|
||||||
|
// if we have a duplicate test name don't expand the test, bail out.
|
||||||
|
if (discoveredTests.ContainsKey(discoveredTest.DisplayName))
|
||||||
|
{
|
||||||
|
var firstSeen = discoveredIndex[discoveredTest.DisplayName];
|
||||||
|
var warning = string.Format(CultureInfo.CurrentCulture, Resource.CannotExpandIDataSourceAttribute_DuplicateDisplayName, firstSeen, index, discoveredTest.DisplayName);
|
||||||
|
warning = string.Format(CultureInfo.CurrentUICulture, Resource.CannotExpandIDataSourceAttribute, test.TestMethod.ManagedTypeName, test.TestMethod.ManagedMethodName, warning);
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning($"DynamicDataEnumarator: {warning}");
|
||||||
|
|
||||||
|
serializationFailed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
discoveredTest.TestMethod.SerializedData = DataSerializationHelper.Serialize(d);
|
||||||
|
discoveredTest.TestMethod.DataType = DynamicDataType.ITestDataSource;
|
||||||
|
}
|
||||||
|
catch (SerializationException)
|
||||||
|
{
|
||||||
|
var firstSeen = discoveredIndex[discoveredTest.DisplayName];
|
||||||
|
var warning = string.Format(CultureInfo.CurrentCulture, Resource.CannotExpandIDataSourceAttribute_CannotSerialize, index, discoveredTest.DisplayName);
|
||||||
|
warning = string.Format(CultureInfo.CurrentUICulture, Resource.CannotExpandIDataSourceAttribute, test.TestMethod.ManagedTypeName, test.TestMethod.ManagedMethodName, warning);
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning($"DynamicDataEnumarator: {warning}");
|
||||||
|
|
||||||
|
serializationFailed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
discoveredTests[discoveredTest.DisplayName] = discoveredTest;
|
||||||
|
discoveredIndex[discoveredTest.DisplayName] = index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialization failed for the type, bail out.
|
||||||
|
if (serializationFailed)
|
||||||
|
{
|
||||||
|
tests.Add(test);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tests.AddRange(discoveredTests.Values);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,123 +1,122 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery;
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
using System;
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
|
using System.Collections.Generic;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerates through an assembly to get a set of test methods.
|
||||||
|
/// </summary>
|
||||||
|
internal class AssemblyEnumeratorWrapper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Assembly name for UTF
|
||||||
|
/// </summary>
|
||||||
|
private static readonly AssemblyName UnitTestFrameworkAssemblyName = typeof(TestMethodAttribute).GetTypeInfo().Assembly.GetName();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enumerates through an assembly to get a set of test methods.
|
/// Gets test elements from an assembly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class AssemblyEnumeratorWrapper
|
/// <param name="assemblyFileName"> The assembly file name. </param>
|
||||||
|
/// <param name="runSettings"> The run Settings. </param>
|
||||||
|
/// <param name="warnings"> Contains warnings if any, that need to be passed back to the caller. </param>
|
||||||
|
/// <returns> A collection of test elements. </returns>
|
||||||
|
internal ICollection<UnitTestElement> GetTests(string assemblyFileName, IRunSettings runSettings, out ICollection<string> warnings)
|
||||||
{
|
{
|
||||||
/// <summary>
|
warnings = new List<string>();
|
||||||
/// Assembly name for UTF
|
|
||||||
/// </summary>
|
|
||||||
private static readonly AssemblyName UnitTestFrameworkAssemblyName = typeof(TestMethodAttribute).GetTypeInfo().Assembly.GetName();
|
|
||||||
|
|
||||||
/// <summary>
|
if (string.IsNullOrEmpty(assemblyFileName))
|
||||||
/// Gets test elements from an assembly.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assemblyFileName"> The assembly file name. </param>
|
|
||||||
/// <param name="runSettings"> The run Settings. </param>
|
|
||||||
/// <param name="warnings"> Contains warnings if any, that need to be passed back to the caller. </param>
|
|
||||||
/// <returns> A collection of test elements. </returns>
|
|
||||||
internal ICollection<UnitTestElement> GetTests(string assemblyFileName, IRunSettings runSettings, out ICollection<string> warnings)
|
|
||||||
{
|
{
|
||||||
warnings = new List<string>();
|
return null;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(assemblyFileName))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var fullFilePath = PlatformServiceProvider.Instance.FileOperations.GetFullFilePath(assemblyFileName);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!PlatformServiceProvider.Instance.FileOperations.DoesFileExist(fullFilePath))
|
|
||||||
{
|
|
||||||
var message = string.Format(CultureInfo.CurrentCulture, Resource.TestAssembly_FileDoesNotExist, fullFilePath);
|
|
||||||
throw new FileNotFoundException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PlatformServiceProvider.Instance.TestSource.IsAssemblyReferenced(UnitTestFrameworkAssemblyName, fullFilePath))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the assembly in isolation if required.
|
|
||||||
return this.GetTestsInIsolation(fullFilePath, runSettings, out warnings);
|
|
||||||
}
|
|
||||||
catch (FileNotFoundException ex)
|
|
||||||
{
|
|
||||||
var message = string.Format(CultureInfo.CurrentCulture, Resource.TestAssembly_AssemblyDiscoveryFailure, fullFilePath, ex.Message);
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning($"{nameof(AssemblyEnumeratorWrapper)}.{nameof(this.GetTests)}: {Resource.TestAssembly_AssemblyDiscoveryFailure}", fullFilePath, ex);
|
|
||||||
warnings.Add(message);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch (ReflectionTypeLoadException ex)
|
|
||||||
{
|
|
||||||
var message = string.Format(CultureInfo.CurrentCulture, Resource.TestAssembly_AssemblyDiscoveryFailure, fullFilePath, ex.Message);
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning($"{nameof(AssemblyEnumeratorWrapper)}.{nameof(this.GetTests)}: {Resource.TestAssembly_AssemblyDiscoveryFailure}", fullFilePath, ex);
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning(Resource.ExceptionsThrown);
|
|
||||||
warnings.Add(message);
|
|
||||||
|
|
||||||
if (ex.LoaderExceptions != null)
|
|
||||||
{
|
|
||||||
foreach (var loaderEx in ex.LoaderExceptions)
|
|
||||||
{
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning("{0}", loaderEx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch (BadImageFormatException)
|
|
||||||
{
|
|
||||||
// Ignore BadImageFormatException when loading native dll in managed adapter.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// Catch all exceptions, if discoverer fails to load the dll then let caller continue with other sources.
|
|
||||||
// Discover test doesn't work if there is a managed C++ project in solution
|
|
||||||
// Assembly.Load() fails to load the managed cpp executable, with FileLoadException. It can load the dll
|
|
||||||
// successfully though. This is known CLR issue.
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning($"{nameof(AssemblyEnumeratorWrapper)}.{nameof(this.GetTests)}: {Resource.TestAssembly_AssemblyDiscoveryFailure}", fullFilePath, ex);
|
|
||||||
var message = ex is FileNotFoundException fileNotFoundEx ? fileNotFoundEx.Message : string.Format(CultureInfo.CurrentCulture, Resource.TestAssembly_AssemblyDiscoveryFailure, fullFilePath, ex.Message);
|
|
||||||
|
|
||||||
warnings.Add(message);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ICollection<UnitTestElement> GetTestsInIsolation(string fullFilePath, IRunSettings runSettings, out ICollection<string> warnings)
|
var fullFilePath = PlatformServiceProvider.Instance.FileOperations.GetFullFilePath(assemblyFileName);
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
using var isolationHost = PlatformServiceProvider.Instance.CreateTestSourceHost(fullFilePath, runSettings, frameworkHandle: null);
|
if (!PlatformServiceProvider.Instance.FileOperations.DoesFileExist(fullFilePath))
|
||||||
// Create an instance of a type defined in adapter so that adapter gets loaded in the child app domain
|
|
||||||
var assemblyEnumerator = isolationHost.CreateInstanceForType(typeof(AssemblyEnumerator), new object[] { MSTestSettings.CurrentSettings }) as AssemblyEnumerator;
|
|
||||||
|
|
||||||
// This might not be supported if an older version of "PlatformServices" (Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices) assembly is already loaded into the App Domain.
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
assemblyEnumerator.RunSettingsXml = runSettings?.SettingsXml;
|
var message = string.Format(CultureInfo.CurrentCulture, Resource.TestAssembly_FileDoesNotExist, fullFilePath);
|
||||||
}
|
throw new FileNotFoundException(message);
|
||||||
catch
|
|
||||||
{
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning(Resource.OlderTFMVersionFound);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return assemblyEnumerator.EnumerateAssembly(fullFilePath, out warnings);
|
if (!PlatformServiceProvider.Instance.TestSource.IsAssemblyReferenced(UnitTestFrameworkAssemblyName, fullFilePath))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the assembly in isolation if required.
|
||||||
|
return this.GetTestsInIsolation(fullFilePath, runSettings, out warnings);
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException ex)
|
||||||
|
{
|
||||||
|
var message = string.Format(CultureInfo.CurrentCulture, Resource.TestAssembly_AssemblyDiscoveryFailure, fullFilePath, ex.Message);
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning($"{nameof(AssemblyEnumeratorWrapper)}.{nameof(this.GetTests)}: {Resource.TestAssembly_AssemblyDiscoveryFailure}", fullFilePath, ex);
|
||||||
|
warnings.Add(message);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (ReflectionTypeLoadException ex)
|
||||||
|
{
|
||||||
|
var message = string.Format(CultureInfo.CurrentCulture, Resource.TestAssembly_AssemblyDiscoveryFailure, fullFilePath, ex.Message);
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning($"{nameof(AssemblyEnumeratorWrapper)}.{nameof(this.GetTests)}: {Resource.TestAssembly_AssemblyDiscoveryFailure}", fullFilePath, ex);
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning(Resource.ExceptionsThrown);
|
||||||
|
warnings.Add(message);
|
||||||
|
|
||||||
|
if (ex.LoaderExceptions != null)
|
||||||
|
{
|
||||||
|
foreach (var loaderEx in ex.LoaderExceptions)
|
||||||
|
{
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning("{0}", loaderEx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (BadImageFormatException)
|
||||||
|
{
|
||||||
|
// Ignore BadImageFormatException when loading native dll in managed adapter.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Catch all exceptions, if discoverer fails to load the dll then let caller continue with other sources.
|
||||||
|
// Discover test doesn't work if there is a managed C++ project in solution
|
||||||
|
// Assembly.Load() fails to load the managed cpp executable, with FileLoadException. It can load the dll
|
||||||
|
// successfully though. This is known CLR issue.
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning($"{nameof(AssemblyEnumeratorWrapper)}.{nameof(this.GetTests)}: {Resource.TestAssembly_AssemblyDiscoveryFailure}", fullFilePath, ex);
|
||||||
|
var message = ex is FileNotFoundException fileNotFoundEx ? fileNotFoundEx.Message : string.Format(CultureInfo.CurrentCulture, Resource.TestAssembly_AssemblyDiscoveryFailure, fullFilePath, ex.Message);
|
||||||
|
|
||||||
|
warnings.Add(message);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ICollection<UnitTestElement> GetTestsInIsolation(string fullFilePath, IRunSettings runSettings, out ICollection<string> warnings)
|
||||||
|
{
|
||||||
|
using var isolationHost = PlatformServiceProvider.Instance.CreateTestSourceHost(fullFilePath, runSettings, frameworkHandle: null);
|
||||||
|
// Create an instance of a type defined in adapter so that adapter gets loaded in the child app domain
|
||||||
|
var assemblyEnumerator = isolationHost.CreateInstanceForType(typeof(AssemblyEnumerator), new object[] { MSTestSettings.CurrentSettings }) as AssemblyEnumerator;
|
||||||
|
|
||||||
|
// This might not be supported if an older version of "PlatformServices" (Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices) assembly is already loaded into the App Domain.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
assemblyEnumerator.RunSettingsXml = runSettings?.SettingsXml;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning(Resource.OlderTFMVersionFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
return assemblyEnumerator.EnumerateAssembly(fullFilePath, out warnings);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,87 +1,86 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery;
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
|
using System;
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
using System.Collections.Generic;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using System.Globalization;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if a method is a valid test method.
|
||||||
|
/// </summary>
|
||||||
|
internal class TestMethodValidator
|
||||||
|
{
|
||||||
|
private readonly ReflectHelper reflectHelper;
|
||||||
|
private readonly bool discoverInternals;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TestMethodValidator"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reflectHelper">An instance to reflection helper for type information.</param>
|
||||||
|
internal TestMethodValidator(ReflectHelper reflectHelper)
|
||||||
|
: this(reflectHelper, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TestMethodValidator"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reflectHelper">An instance to reflection helper for type information.</param>
|
||||||
|
/// <param name="discoverInternals">True to discover methods which are declared internal in addition to methods
|
||||||
|
/// which are declared public.</param>
|
||||||
|
internal TestMethodValidator(ReflectHelper reflectHelper, bool discoverInternals)
|
||||||
|
{
|
||||||
|
this.reflectHelper = reflectHelper;
|
||||||
|
this.discoverInternals = discoverInternals;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines if a method is a valid test method.
|
/// Determines if a method is a valid test method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class TestMethodValidator
|
/// <param name="testMethodInfo"> The reflected method. </param>
|
||||||
|
/// <param name="type"> The reflected type. </param>
|
||||||
|
/// <param name="warnings"> Contains warnings if any, that need to be passed back to the caller. </param>
|
||||||
|
/// <returns> Return true if a method is a valid test method. </returns>
|
||||||
|
internal virtual bool IsValidTestMethod(MethodInfo testMethodInfo, Type type, ICollection<string> warnings)
|
||||||
{
|
{
|
||||||
private readonly ReflectHelper reflectHelper;
|
if (!this.reflectHelper.IsAttributeDefined(testMethodInfo, typeof(TestMethodAttribute), false)
|
||||||
private readonly bool discoverInternals;
|
&& !this.reflectHelper.HasAttributeDerivedFrom(testMethodInfo, typeof(TestMethodAttribute), false))
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="TestMethodValidator"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="reflectHelper">An instance to reflection helper for type information.</param>
|
|
||||||
internal TestMethodValidator(ReflectHelper reflectHelper)
|
|
||||||
: this(reflectHelper, false)
|
|
||||||
{
|
{
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// Generic method Definitions are not valid.
|
||||||
/// Initializes a new instance of the <see cref="TestMethodValidator"/> class.
|
if (testMethodInfo.IsGenericMethodDefinition)
|
||||||
/// </summary>
|
|
||||||
/// <param name="reflectHelper">An instance to reflection helper for type information.</param>
|
|
||||||
/// <param name="discoverInternals">True to discover methods which are declared internal in addition to methods
|
|
||||||
/// which are declared public.</param>
|
|
||||||
internal TestMethodValidator(ReflectHelper reflectHelper, bool discoverInternals)
|
|
||||||
{
|
{
|
||||||
this.reflectHelper = reflectHelper;
|
var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorGenericTestMethod, testMethodInfo.DeclaringType.FullName, testMethodInfo.Name);
|
||||||
this.discoverInternals = discoverInternals;
|
warnings.Add(message);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
var isAccessible = testMethodInfo.IsPublic
|
||||||
/// Determines if a method is a valid test method.
|
|| (this.discoverInternals && testMethodInfo.IsAssembly);
|
||||||
/// </summary>
|
|
||||||
/// <param name="testMethodInfo"> The reflected method. </param>
|
// Todo: Decide whether parameter count matters.
|
||||||
/// <param name="type"> The reflected type. </param>
|
// The isGenericMethod check below id to verify that there are no closed generic methods slipping through.
|
||||||
/// <param name="warnings"> Contains warnings if any, that need to be passed back to the caller. </param>
|
// Closed generic methods being GenericMethod<int> and open being GenericMethod<T>.
|
||||||
/// <returns> Return true if a method is a valid test method. </returns>
|
var isValidTestMethod = isAccessible && !testMethodInfo.IsAbstract && !testMethodInfo.IsStatic
|
||||||
internal virtual bool IsValidTestMethod(MethodInfo testMethodInfo, Type type, ICollection<string> warnings)
|
&& !testMethodInfo.IsGenericMethod
|
||||||
|
&& testMethodInfo.IsVoidOrTaskReturnType();
|
||||||
|
|
||||||
|
if (!isValidTestMethod)
|
||||||
{
|
{
|
||||||
if (!this.reflectHelper.IsAttributeDefined(testMethodInfo, typeof(TestMethodAttribute), false)
|
var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorIncorrectTestMethodSignature, type.FullName, testMethodInfo.Name);
|
||||||
&& !this.reflectHelper.HasAttributeDerivedFrom(testMethodInfo, typeof(TestMethodAttribute), false))
|
warnings.Add(message);
|
||||||
{
|
return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic method Definitions are not valid.
|
|
||||||
if (testMethodInfo.IsGenericMethodDefinition)
|
|
||||||
{
|
|
||||||
var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorGenericTestMethod, testMethodInfo.DeclaringType.FullName, testMethodInfo.Name);
|
|
||||||
warnings.Add(message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var isAccessible = testMethodInfo.IsPublic
|
|
||||||
|| (this.discoverInternals && testMethodInfo.IsAssembly);
|
|
||||||
|
|
||||||
// Todo: Decide whether parameter count matters.
|
|
||||||
// The isGenericMethod check below id to verify that there are no closed generic methods slipping through.
|
|
||||||
// Closed generic methods being GenericMethod<int> and open being GenericMethod<T>.
|
|
||||||
var isValidTestMethod = isAccessible && !testMethodInfo.IsAbstract && !testMethodInfo.IsStatic
|
|
||||||
&& !testMethodInfo.IsGenericMethod
|
|
||||||
&& testMethodInfo.IsVoidOrTaskReturnType();
|
|
||||||
|
|
||||||
if (!isValidTestMethod)
|
|
||||||
{
|
|
||||||
var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorIncorrectTestMethodSignature, type.FullName, testMethodInfo.Name);
|
|
||||||
warnings.Add(message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,210 +1,209 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery;
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
|
using System;
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
using System.Collections.Generic;
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerates through the type looking for Valid Test Methods to execute.
|
||||||
|
/// </summary>
|
||||||
|
internal class TypeEnumerator
|
||||||
|
{
|
||||||
|
private readonly Type type;
|
||||||
|
private readonly string assemblyName;
|
||||||
|
private readonly TypeValidator typeValidator;
|
||||||
|
private readonly TestMethodValidator testMethodValidator;
|
||||||
|
private readonly ReflectHelper reflectHelper;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enumerates through the type looking for Valid Test Methods to execute.
|
/// Initializes a new instance of the <see cref="TypeEnumerator"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class TypeEnumerator
|
/// <param name="type"> The reflected type. </param>
|
||||||
|
/// <param name="assemblyName"> The name of the assembly being reflected. </param>
|
||||||
|
/// <param name="reflectHelper"> An instance to reflection helper for type information. </param>
|
||||||
|
/// <param name="typeValidator"> The validator for test classes. </param>
|
||||||
|
/// <param name="testMethodValidator"> The validator for test methods. </param>
|
||||||
|
internal TypeEnumerator(Type type, string assemblyName, ReflectHelper reflectHelper, TypeValidator typeValidator, TestMethodValidator testMethodValidator)
|
||||||
{
|
{
|
||||||
private readonly Type type;
|
this.type = type;
|
||||||
private readonly string assemblyName;
|
this.assemblyName = assemblyName;
|
||||||
private readonly TypeValidator typeValidator;
|
this.reflectHelper = reflectHelper;
|
||||||
private readonly TestMethodValidator testMethodValidator;
|
this.typeValidator = typeValidator;
|
||||||
private readonly ReflectHelper reflectHelper;
|
this.testMethodValidator = testMethodValidator;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="TypeEnumerator"/> class.
|
/// Walk through all methods in the type, and find out the test methods
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="type"> The reflected type. </param>
|
/// <param name="warnings"> Contains warnings if any, that need to be passed back to the caller. </param>
|
||||||
/// <param name="assemblyName"> The name of the assembly being reflected. </param>
|
/// <returns> list of test cases.</returns>
|
||||||
/// <param name="reflectHelper"> An instance to reflection helper for type information. </param>
|
internal virtual ICollection<UnitTestElement> Enumerate(out ICollection<string> warnings)
|
||||||
/// <param name="typeValidator"> The validator for test classes. </param>
|
{
|
||||||
/// <param name="testMethodValidator"> The validator for test methods. </param>
|
warnings = new Collection<string>();
|
||||||
internal TypeEnumerator(Type type, string assemblyName, ReflectHelper reflectHelper, TypeValidator typeValidator, TestMethodValidator testMethodValidator)
|
|
||||||
|
if (!this.typeValidator.IsValidTestClass(this.type, warnings))
|
||||||
{
|
{
|
||||||
this.type = type;
|
return null;
|
||||||
this.assemblyName = assemblyName;
|
|
||||||
this.reflectHelper = reflectHelper;
|
|
||||||
this.typeValidator = typeValidator;
|
|
||||||
this.testMethodValidator = testMethodValidator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// If test class is valid, then get the tests
|
||||||
/// Walk through all methods in the type, and find out the test methods
|
return this.GetTests(warnings);
|
||||||
/// </summary>
|
}
|
||||||
/// <param name="warnings"> Contains warnings if any, that need to be passed back to the caller. </param>
|
|
||||||
/// <returns> list of test cases.</returns>
|
|
||||||
internal virtual ICollection<UnitTestElement> Enumerate(out ICollection<string> warnings)
|
|
||||||
{
|
|
||||||
warnings = new Collection<string>();
|
|
||||||
|
|
||||||
if (!this.typeValidator.IsValidTestClass(this.type, warnings))
|
/// <summary>
|
||||||
|
/// Gets a list of valid tests in a type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="warnings"> Contains warnings if any, that need to be passed back to the caller. </param>
|
||||||
|
/// <returns> List of Valid Tests. </returns>
|
||||||
|
internal Collection<UnitTestElement> GetTests(ICollection<string> warnings)
|
||||||
|
{
|
||||||
|
bool foundDuplicateTests = false;
|
||||||
|
var foundTests = new HashSet<string>();
|
||||||
|
var tests = new Collection<UnitTestElement>();
|
||||||
|
|
||||||
|
// Test class is already valid. Verify methods.
|
||||||
|
foreach (var method in this.type.GetRuntimeMethods())
|
||||||
|
{
|
||||||
|
var isMethodDeclaredInTestTypeAssembly = this.reflectHelper.IsMethodDeclaredInSameAssemblyAsType(method, this.type);
|
||||||
|
var enableMethodsFromOtherAssemblies = MSTestSettings.CurrentSettings.EnableBaseClassTestMethodsFromOtherAssemblies;
|
||||||
|
|
||||||
|
if (!isMethodDeclaredInTestTypeAssembly && !enableMethodsFromOtherAssemblies)
|
||||||
{
|
{
|
||||||
return null;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If test class is valid, then get the tests
|
if (this.testMethodValidator.IsValidTestMethod(method, this.type, warnings))
|
||||||
return this.GetTests(warnings);
|
{
|
||||||
|
foundDuplicateTests = foundDuplicateTests || !foundTests.Add(method.Name);
|
||||||
|
tests.Add(this.GetTestFromMethod(method, isMethodDeclaredInTestTypeAssembly, warnings));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
if (!foundDuplicateTests)
|
||||||
/// Gets a list of valid tests in a type.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="warnings"> Contains warnings if any, that need to be passed back to the caller. </param>
|
|
||||||
/// <returns> List of Valid Tests. </returns>
|
|
||||||
internal Collection<UnitTestElement> GetTests(ICollection<string> warnings)
|
|
||||||
{
|
{
|
||||||
bool foundDuplicateTests = false;
|
return tests;
|
||||||
var foundTests = new HashSet<string>();
|
|
||||||
var tests = new Collection<UnitTestElement>();
|
|
||||||
|
|
||||||
// Test class is already valid. Verify methods.
|
|
||||||
foreach (var method in this.type.GetRuntimeMethods())
|
|
||||||
{
|
|
||||||
var isMethodDeclaredInTestTypeAssembly = this.reflectHelper.IsMethodDeclaredInSameAssemblyAsType(method, this.type);
|
|
||||||
var enableMethodsFromOtherAssemblies = MSTestSettings.CurrentSettings.EnableBaseClassTestMethodsFromOtherAssemblies;
|
|
||||||
|
|
||||||
if (!isMethodDeclaredInTestTypeAssembly && !enableMethodsFromOtherAssemblies)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.testMethodValidator.IsValidTestMethod(method, this.type, warnings))
|
|
||||||
{
|
|
||||||
foundDuplicateTests = foundDuplicateTests || !foundTests.Add(method.Name);
|
|
||||||
tests.Add(this.GetTestFromMethod(method, isMethodDeclaredInTestTypeAssembly, warnings));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!foundDuplicateTests)
|
|
||||||
{
|
|
||||||
return tests;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove duplicate test methods by taking the first one of each name
|
|
||||||
// that is declared closest to the test class in the hierarchy.
|
|
||||||
var inheritanceDepths = new Dictionary<string, int>();
|
|
||||||
var currentType = this.type;
|
|
||||||
int currentDepth = 0;
|
|
||||||
|
|
||||||
while (currentType != null)
|
|
||||||
{
|
|
||||||
inheritanceDepths[currentType.FullName] = currentDepth;
|
|
||||||
++currentDepth;
|
|
||||||
currentType = currentType.GetTypeInfo().BaseType;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Collection<UnitTestElement>(
|
|
||||||
tests.GroupBy(
|
|
||||||
t => t.TestMethod.Name,
|
|
||||||
(_, elements) =>
|
|
||||||
elements.OrderBy(t => inheritanceDepths[t.TestMethod.DeclaringClassFullName ?? t.TestMethod.FullClassName]).First())
|
|
||||||
.ToList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// Remove duplicate test methods by taking the first one of each name
|
||||||
/// Gets a UnitTestElement from a MethodInfo object filling it up with appropriate values.
|
// that is declared closest to the test class in the hierarchy.
|
||||||
/// </summary>
|
var inheritanceDepths = new Dictionary<string, int>();
|
||||||
/// <param name="method">The reflected method.</param>
|
var currentType = this.type;
|
||||||
/// <param name="isDeclaredInTestTypeAssembly">True if the reflected method is declared in the same assembly as the current type.</param>
|
int currentDepth = 0;
|
||||||
/// <param name="warnings">Contains warnings if any, that need to be passed back to the caller.</param>
|
|
||||||
/// <returns> Returns a UnitTestElement.</returns>
|
while (currentType != null)
|
||||||
internal UnitTestElement GetTestFromMethod(MethodInfo method, bool isDeclaredInTestTypeAssembly, ICollection<string> warnings)
|
|
||||||
{
|
{
|
||||||
// null if the current instance represents a generic type parameter.
|
inheritanceDepths[currentType.FullName] = currentDepth;
|
||||||
Debug.Assert(this.type.AssemblyQualifiedName != null, "AssemblyQualifiedName for method is null.");
|
++currentDepth;
|
||||||
|
currentType = currentType.GetTypeInfo().BaseType;
|
||||||
// This allows void returning async test method to be valid test method. Though they will be executed similar to non-async test method.
|
|
||||||
var isAsync = ReflectHelper.MatchReturnType(method, typeof(Task));
|
|
||||||
|
|
||||||
var testMethod = new TestMethod(method, method.Name, this.type.FullName, this.assemblyName, isAsync);
|
|
||||||
|
|
||||||
if (!method.DeclaringType.FullName.Equals(this.type.FullName))
|
|
||||||
{
|
|
||||||
testMethod.DeclaringClassFullName = method.DeclaringType.FullName;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isDeclaredInTestTypeAssembly)
|
|
||||||
{
|
|
||||||
testMethod.DeclaringAssemblyName =
|
|
||||||
PlatformServiceProvider.Instance.FileOperations.GetAssemblyPath(
|
|
||||||
method.DeclaringType.GetTypeInfo().Assembly);
|
|
||||||
}
|
|
||||||
|
|
||||||
var testElement = new UnitTestElement(testMethod);
|
|
||||||
|
|
||||||
// Get compiler generated type name for async test method (either void returning or task returning).
|
|
||||||
var asyncTypeName = method.GetAsyncTypeName();
|
|
||||||
testElement.AsyncTypeName = asyncTypeName;
|
|
||||||
|
|
||||||
testElement.TestCategory = this.reflectHelper.GetCategories(method, this.type);
|
|
||||||
|
|
||||||
testElement.DoNotParallelize = this.reflectHelper.IsDoNotParallelizeSet(method, this.type);
|
|
||||||
|
|
||||||
var traits = this.reflectHelper.GetTestPropertiesAsTraits(method);
|
|
||||||
|
|
||||||
var ownerTrait = this.reflectHelper.GetTestOwnerAsTraits(method);
|
|
||||||
if (ownerTrait != null)
|
|
||||||
{
|
|
||||||
traits = traits.Concat(new[] { ownerTrait });
|
|
||||||
}
|
|
||||||
|
|
||||||
testElement.Priority = this.reflectHelper.GetPriority(method);
|
|
||||||
|
|
||||||
var priorityTrait = this.reflectHelper.GetTestPriorityAsTraits(testElement.Priority);
|
|
||||||
if (priorityTrait != null)
|
|
||||||
{
|
|
||||||
traits = traits.Concat(new[] { priorityTrait });
|
|
||||||
}
|
|
||||||
|
|
||||||
testElement.Traits = traits.ToArray();
|
|
||||||
|
|
||||||
if (this.reflectHelper.GetCustomAttribute(method, typeof(CssIterationAttribute)) is CssIterationAttribute cssIteration)
|
|
||||||
{
|
|
||||||
testElement.CssIteration = cssIteration.CssIteration;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.reflectHelper.GetCustomAttribute(method, typeof(CssProjectStructureAttribute)) is CssProjectStructureAttribute cssProjectStructure)
|
|
||||||
{
|
|
||||||
testElement.CssProjectStructure = cssProjectStructure.CssProjectStructure;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.reflectHelper.GetCustomAttribute(method, typeof(DescriptionAttribute)) is DescriptionAttribute descriptionAttribute)
|
|
||||||
{
|
|
||||||
testElement.Description = descriptionAttribute.Description;
|
|
||||||
}
|
|
||||||
|
|
||||||
var workItemAttributes = this.reflectHelper.GetCustomAttributes(method, typeof(WorkItemAttribute)).Cast<WorkItemAttribute>().ToArray();
|
|
||||||
if (workItemAttributes.Any())
|
|
||||||
{
|
|
||||||
testElement.WorkItemIds = workItemAttributes.Select(x => x.Id.ToString()).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
testElement.Ignored = this.reflectHelper.IsAttributeDefined(method, typeof(IgnoreAttribute), false);
|
|
||||||
|
|
||||||
// Get Deployment items if any.
|
|
||||||
testElement.DeploymentItems = PlatformServiceProvider.Instance.TestDeployment.GetDeploymentItems(method, this.type, warnings);
|
|
||||||
|
|
||||||
// get DisplayName from TestMethodAttribute
|
|
||||||
var testMethodAttribute = this.reflectHelper.GetCustomAttribute(method, typeof(TestMethodAttribute)) as TestMethodAttribute;
|
|
||||||
testElement.DisplayName = testMethodAttribute?.DisplayName ?? method.Name;
|
|
||||||
|
|
||||||
return testElement;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new Collection<UnitTestElement>(
|
||||||
|
tests.GroupBy(
|
||||||
|
t => t.TestMethod.Name,
|
||||||
|
(_, elements) =>
|
||||||
|
elements.OrderBy(t => inheritanceDepths[t.TestMethod.DeclaringClassFullName ?? t.TestMethod.FullClassName]).First())
|
||||||
|
.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a UnitTestElement from a MethodInfo object filling it up with appropriate values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method">The reflected method.</param>
|
||||||
|
/// <param name="isDeclaredInTestTypeAssembly">True if the reflected method is declared in the same assembly as the current type.</param>
|
||||||
|
/// <param name="warnings">Contains warnings if any, that need to be passed back to the caller.</param>
|
||||||
|
/// <returns> Returns a UnitTestElement.</returns>
|
||||||
|
internal UnitTestElement GetTestFromMethod(MethodInfo method, bool isDeclaredInTestTypeAssembly, ICollection<string> warnings)
|
||||||
|
{
|
||||||
|
// null if the current instance represents a generic type parameter.
|
||||||
|
Debug.Assert(this.type.AssemblyQualifiedName != null, "AssemblyQualifiedName for method is null.");
|
||||||
|
|
||||||
|
// This allows void returning async test method to be valid test method. Though they will be executed similar to non-async test method.
|
||||||
|
var isAsync = ReflectHelper.MatchReturnType(method, typeof(Task));
|
||||||
|
|
||||||
|
var testMethod = new TestMethod(method, method.Name, this.type.FullName, this.assemblyName, isAsync);
|
||||||
|
|
||||||
|
if (!method.DeclaringType.FullName.Equals(this.type.FullName))
|
||||||
|
{
|
||||||
|
testMethod.DeclaringClassFullName = method.DeclaringType.FullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDeclaredInTestTypeAssembly)
|
||||||
|
{
|
||||||
|
testMethod.DeclaringAssemblyName =
|
||||||
|
PlatformServiceProvider.Instance.FileOperations.GetAssemblyPath(
|
||||||
|
method.DeclaringType.GetTypeInfo().Assembly);
|
||||||
|
}
|
||||||
|
|
||||||
|
var testElement = new UnitTestElement(testMethod);
|
||||||
|
|
||||||
|
// Get compiler generated type name for async test method (either void returning or task returning).
|
||||||
|
var asyncTypeName = method.GetAsyncTypeName();
|
||||||
|
testElement.AsyncTypeName = asyncTypeName;
|
||||||
|
|
||||||
|
testElement.TestCategory = this.reflectHelper.GetCategories(method, this.type);
|
||||||
|
|
||||||
|
testElement.DoNotParallelize = this.reflectHelper.IsDoNotParallelizeSet(method, this.type);
|
||||||
|
|
||||||
|
var traits = this.reflectHelper.GetTestPropertiesAsTraits(method);
|
||||||
|
|
||||||
|
var ownerTrait = this.reflectHelper.GetTestOwnerAsTraits(method);
|
||||||
|
if (ownerTrait != null)
|
||||||
|
{
|
||||||
|
traits = traits.Concat(new[] { ownerTrait });
|
||||||
|
}
|
||||||
|
|
||||||
|
testElement.Priority = this.reflectHelper.GetPriority(method);
|
||||||
|
|
||||||
|
var priorityTrait = this.reflectHelper.GetTestPriorityAsTraits(testElement.Priority);
|
||||||
|
if (priorityTrait != null)
|
||||||
|
{
|
||||||
|
traits = traits.Concat(new[] { priorityTrait });
|
||||||
|
}
|
||||||
|
|
||||||
|
testElement.Traits = traits.ToArray();
|
||||||
|
|
||||||
|
if (this.reflectHelper.GetCustomAttribute(method, typeof(CssIterationAttribute)) is CssIterationAttribute cssIteration)
|
||||||
|
{
|
||||||
|
testElement.CssIteration = cssIteration.CssIteration;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.reflectHelper.GetCustomAttribute(method, typeof(CssProjectStructureAttribute)) is CssProjectStructureAttribute cssProjectStructure)
|
||||||
|
{
|
||||||
|
testElement.CssProjectStructure = cssProjectStructure.CssProjectStructure;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.reflectHelper.GetCustomAttribute(method, typeof(DescriptionAttribute)) is DescriptionAttribute descriptionAttribute)
|
||||||
|
{
|
||||||
|
testElement.Description = descriptionAttribute.Description;
|
||||||
|
}
|
||||||
|
|
||||||
|
var workItemAttributes = this.reflectHelper.GetCustomAttributes(method, typeof(WorkItemAttribute)).Cast<WorkItemAttribute>().ToArray();
|
||||||
|
if (workItemAttributes.Any())
|
||||||
|
{
|
||||||
|
testElement.WorkItemIds = workItemAttributes.Select(x => x.Id.ToString()).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
testElement.Ignored = this.reflectHelper.IsAttributeDefined(method, typeof(IgnoreAttribute), false);
|
||||||
|
|
||||||
|
// Get Deployment items if any.
|
||||||
|
testElement.DeploymentItems = PlatformServiceProvider.Instance.TestDeployment.GetDeploymentItems(method, this.type, warnings);
|
||||||
|
|
||||||
|
// get DisplayName from TestMethodAttribute
|
||||||
|
var testMethodAttribute = this.reflectHelper.GetCustomAttribute(method, typeof(TestMethodAttribute)) as TestMethodAttribute;
|
||||||
|
testElement.DisplayName = testMethodAttribute?.DisplayName ?? method.Name;
|
||||||
|
|
||||||
|
return testElement;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,215 +1,214 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery;
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
using System;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether a type is a valid test class for this adapter.
|
||||||
|
/// </summary>
|
||||||
|
internal class TypeValidator
|
||||||
|
{
|
||||||
|
// Setting this to a string representation instead of a typeof(TestContext).FullName
|
||||||
|
// since the later would require a load of the Test Framework extension assembly at this point.
|
||||||
|
private const string TestContextFullName = "Microsoft.VisualStudio.TestTools.UnitTesting.TestContext";
|
||||||
|
private readonly ReflectHelper reflectHelper;
|
||||||
|
private readonly bool discoverInternals;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether a type is a valid test class for this adapter.
|
/// Initializes a new instance of the <see cref="TypeValidator"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class TypeValidator
|
/// <param name="reflectHelper">An instance to reflection helper for type information.</param>
|
||||||
|
internal TypeValidator(ReflectHelper reflectHelper)
|
||||||
|
: this(reflectHelper, false)
|
||||||
{
|
{
|
||||||
// Setting this to a string representation instead of a typeof(TestContext).FullName
|
}
|
||||||
// since the later would require a load of the Test Framework extension assembly at this point.
|
|
||||||
private const string TestContextFullName = "Microsoft.VisualStudio.TestTools.UnitTesting.TestContext";
|
|
||||||
private readonly ReflectHelper reflectHelper;
|
|
||||||
private readonly bool discoverInternals;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="TypeValidator"/> class.
|
/// Initializes a new instance of the <see cref="TypeValidator"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="reflectHelper">An instance to reflection helper for type information.</param>
|
/// <param name="reflectHelper">An instance to reflection helper for type information.</param>
|
||||||
internal TypeValidator(ReflectHelper reflectHelper)
|
/// <param name="discoverInternals">True to discover test classes which are declared internal in
|
||||||
: this(reflectHelper, false)
|
/// addition to test classes which are declared public.</param>
|
||||||
|
internal TypeValidator(ReflectHelper reflectHelper, bool discoverInternals)
|
||||||
|
{
|
||||||
|
this.reflectHelper = reflectHelper;
|
||||||
|
this.discoverInternals = discoverInternals;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if a type is a valid test class for this adapter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The reflected type.</param>
|
||||||
|
/// <param name="warnings">Contains warnings if any, that need to be passed back to the caller.</param>
|
||||||
|
/// <returns>Return true if it is a valid test class.</returns>
|
||||||
|
internal virtual bool IsValidTestClass(Type type, ICollection<string> warnings)
|
||||||
|
{
|
||||||
|
if (type.GetTypeInfo().IsClass &&
|
||||||
|
(this.reflectHelper.IsAttributeDefined(type, typeof(TestClassAttribute), false) ||
|
||||||
|
this.reflectHelper.HasAttributeDerivedFrom(type, typeof(TestClassAttribute), false)))
|
||||||
{
|
{
|
||||||
}
|
// inaccessible class
|
||||||
|
if (!this.TypeHasValidAccessibility(type.GetTypeInfo(), this.discoverInternals))
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="TypeValidator"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="reflectHelper">An instance to reflection helper for type information.</param>
|
|
||||||
/// <param name="discoverInternals">True to discover test classes which are declared internal in
|
|
||||||
/// addition to test classes which are declared public.</param>
|
|
||||||
internal TypeValidator(ReflectHelper reflectHelper, bool discoverInternals)
|
|
||||||
{
|
|
||||||
this.reflectHelper = reflectHelper;
|
|
||||||
this.discoverInternals = discoverInternals;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines if a type is a valid test class for this adapter.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">The reflected type.</param>
|
|
||||||
/// <param name="warnings">Contains warnings if any, that need to be passed back to the caller.</param>
|
|
||||||
/// <returns>Return true if it is a valid test class.</returns>
|
|
||||||
internal virtual bool IsValidTestClass(Type type, ICollection<string> warnings)
|
|
||||||
{
|
|
||||||
if (type.GetTypeInfo().IsClass &&
|
|
||||||
(this.reflectHelper.IsAttributeDefined(type, typeof(TestClassAttribute), false) ||
|
|
||||||
this.reflectHelper.HasAttributeDerivedFrom(type, typeof(TestClassAttribute), false)))
|
|
||||||
{
|
{
|
||||||
// inaccessible class
|
var warning = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorNonPublicTestClass, type.FullName);
|
||||||
if (!this.TypeHasValidAccessibility(type.GetTypeInfo(), this.discoverInternals))
|
warnings.Add(warning);
|
||||||
{
|
|
||||||
var warning = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorNonPublicTestClass, type.FullName);
|
|
||||||
warnings.Add(warning);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic class
|
|
||||||
if (type.GetTypeInfo().IsGenericTypeDefinition && !type.GetTypeInfo().IsAbstract)
|
|
||||||
{
|
|
||||||
// In IDE generic classes that are not abstract are treated as not runnable. Keep consistence.
|
|
||||||
var warning = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorNonPublicTestClass, type.FullName);
|
|
||||||
warnings.Add(warning);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Class is not valid if the testContext property is incorrect
|
|
||||||
if (!this.HasCorrectTestContextSignature(type))
|
|
||||||
{
|
|
||||||
var warning = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorInValidTestContextSignature, type.FullName);
|
|
||||||
warnings.Add(warning);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Abstract test classes can be base classes for derived test classes.
|
|
||||||
// There is no way to see if there are derived test classes.
|
|
||||||
// Thus if a test class is abstract, just ignore all test methods from it
|
|
||||||
// (they will be visible in derived classes). No warnings (such as test method, deployment item,
|
|
||||||
// etc attribute is defined on the class) will be generated for this class:
|
|
||||||
// What we do is:
|
|
||||||
// - report the class as "not valid" test class. This will cause to skip enumerating tests from it.
|
|
||||||
// - Do not generate warnings/do not create NOT RUNNABLE tests.
|
|
||||||
if (type.GetTypeInfo().IsAbstract)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines if the type has a valid TestContext property definition.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">The reflected type.</param>
|
|
||||||
/// <returns>Returns true if type has a valid TestContext property definition.</returns>
|
|
||||||
internal bool HasCorrectTestContextSignature(Type type)
|
|
||||||
{
|
|
||||||
Debug.Assert(type != null, "HasCorrectTestContextSignature type is null");
|
|
||||||
|
|
||||||
var propertyInfoEnumerable = type.GetTypeInfo().DeclaredProperties;
|
|
||||||
var propertyInfo = new List<PropertyInfo>();
|
|
||||||
|
|
||||||
foreach (var pinfo in propertyInfoEnumerable)
|
|
||||||
{
|
|
||||||
// PropertyType.FullName can be null if the property is a generic type.
|
|
||||||
if (TestContextFullName.Equals(pinfo.PropertyType.FullName, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
propertyInfo.Add(pinfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (propertyInfo.Count == 0)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var pinfo in propertyInfo)
|
|
||||||
{
|
|
||||||
var setInfo = pinfo.SetMethod;
|
|
||||||
if (setInfo == null)
|
|
||||||
{
|
|
||||||
// we have a getter, but not a setter.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setInfo.IsPrivate || setInfo.IsStatic || setInfo.IsAbstract)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal bool TypeHasValidAccessibility(TypeInfo type, bool discoverInternals)
|
|
||||||
{
|
|
||||||
if (type.IsVisible)
|
|
||||||
{
|
|
||||||
// The type is public or a public nested class of entirely public container classes.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!discoverInternals)
|
|
||||||
{
|
|
||||||
// The type is not externally visible and internal test classes are not to be discovered.
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Either the type is not public or it is a nested class and itself or one of its containers is not public.
|
// Generic class
|
||||||
if (type.IsNested)
|
if (type.GetTypeInfo().IsGenericTypeDefinition && !type.GetTypeInfo().IsAbstract)
|
||||||
{
|
{
|
||||||
// Assembly is CLR term for internal visibility:
|
// In IDE generic classes that are not abstract are treated as not runnable. Keep consistence.
|
||||||
// Private == private,
|
var warning = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorNonPublicTestClass, type.FullName);
|
||||||
// FamilyANDAssembly == private protected,
|
warnings.Add(warning);
|
||||||
// Assembly == internal,
|
return false;
|
||||||
// Family == protected,
|
}
|
||||||
// FamilyORAssembly == protected internal,
|
|
||||||
// Public == public.
|
// Class is not valid if the testContext property is incorrect
|
||||||
// So this reads IsNestedInternal || IsNestedPublic:
|
if (!this.HasCorrectTestContextSignature(type))
|
||||||
var isNestedPublicOrInternal = type.IsNestedAssembly || type.IsNestedPublic;
|
{
|
||||||
|
var warning = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorInValidTestContextSignature, type.FullName);
|
||||||
if (!isNestedPublicOrInternal)
|
warnings.Add(warning);
|
||||||
{
|
return false;
|
||||||
// This type is nested, but is not public or internal.
|
}
|
||||||
return false;
|
|
||||||
}
|
// Abstract test classes can be base classes for derived test classes.
|
||||||
|
// There is no way to see if there are derived test classes.
|
||||||
// The type itself is nested and is public, or internal, but could be in hierarchy of types
|
// Thus if a test class is abstract, just ignore all test methods from it
|
||||||
// where some of the parent types is private (or other modifier that is not public and is not internal)
|
// (they will be visible in derived classes). No warnings (such as test method, deployment item,
|
||||||
// if we looked for just public types we could just look at IsVisible, but internal type nested in internal type
|
// etc attribute is defined on the class) will be generated for this class:
|
||||||
// is not Visible, so we need to check all the parents and make sure they are all either public or internal.
|
// What we do is:
|
||||||
var parentsArePublicOrInternal = true;
|
// - report the class as "not valid" test class. This will cause to skip enumerating tests from it.
|
||||||
var declaringType = type.DeclaringType;
|
// - Do not generate warnings/do not create NOT RUNNABLE tests.
|
||||||
while (declaringType != null && parentsArePublicOrInternal)
|
if (type.GetTypeInfo().IsAbstract)
|
||||||
{
|
{
|
||||||
var declaringTypeIsPublicOrInternal =
|
return false;
|
||||||
|
|
||||||
// Declaring type is non-nested type, and we are looking for internal or public, which are the only
|
|
||||||
// two valid options that non-nested type can be.
|
|
||||||
!declaringType.IsNested
|
|
||||||
|
|
||||||
// Or the type is nested internal, or nested public type, but not any other
|
|
||||||
// like nested protected internal type, or nested private type.
|
|
||||||
|| declaringType.GetTypeInfo().IsNestedAssembly || declaringType.GetTypeInfo().IsNestedPublic;
|
|
||||||
|
|
||||||
if (!declaringTypeIsPublicOrInternal)
|
|
||||||
{
|
|
||||||
parentsArePublicOrInternal = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
declaringType = declaringType.DeclaringType;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parentsArePublicOrInternal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The type is not public and is not nested. Non-nested types can be only public or internal
|
|
||||||
// so this type must be internal.
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the type has a valid TestContext property definition.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The reflected type.</param>
|
||||||
|
/// <returns>Returns true if type has a valid TestContext property definition.</returns>
|
||||||
|
internal bool HasCorrectTestContextSignature(Type type)
|
||||||
|
{
|
||||||
|
Debug.Assert(type != null, "HasCorrectTestContextSignature type is null");
|
||||||
|
|
||||||
|
var propertyInfoEnumerable = type.GetTypeInfo().DeclaredProperties;
|
||||||
|
var propertyInfo = new List<PropertyInfo>();
|
||||||
|
|
||||||
|
foreach (var pinfo in propertyInfoEnumerable)
|
||||||
|
{
|
||||||
|
// PropertyType.FullName can be null if the property is a generic type.
|
||||||
|
if (TestContextFullName.Equals(pinfo.PropertyType.FullName, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
propertyInfo.Add(pinfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propertyInfo.Count == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var pinfo in propertyInfo)
|
||||||
|
{
|
||||||
|
var setInfo = pinfo.SetMethod;
|
||||||
|
if (setInfo == null)
|
||||||
|
{
|
||||||
|
// we have a getter, but not a setter.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setInfo.IsPrivate || setInfo.IsStatic || setInfo.IsAbstract)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool TypeHasValidAccessibility(TypeInfo type, bool discoverInternals)
|
||||||
|
{
|
||||||
|
if (type.IsVisible)
|
||||||
|
{
|
||||||
|
// The type is public or a public nested class of entirely public container classes.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!discoverInternals)
|
||||||
|
{
|
||||||
|
// The type is not externally visible and internal test classes are not to be discovered.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either the type is not public or it is a nested class and itself or one of its containers is not public.
|
||||||
|
if (type.IsNested)
|
||||||
|
{
|
||||||
|
// Assembly is CLR term for internal visibility:
|
||||||
|
// Private == private,
|
||||||
|
// FamilyANDAssembly == private protected,
|
||||||
|
// Assembly == internal,
|
||||||
|
// Family == protected,
|
||||||
|
// FamilyORAssembly == protected internal,
|
||||||
|
// Public == public.
|
||||||
|
// So this reads IsNestedInternal || IsNestedPublic:
|
||||||
|
var isNestedPublicOrInternal = type.IsNestedAssembly || type.IsNestedPublic;
|
||||||
|
|
||||||
|
if (!isNestedPublicOrInternal)
|
||||||
|
{
|
||||||
|
// This type is nested, but is not public or internal.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The type itself is nested and is public, or internal, but could be in hierarchy of types
|
||||||
|
// where some of the parent types is private (or other modifier that is not public and is not internal)
|
||||||
|
// if we looked for just public types we could just look at IsVisible, but internal type nested in internal type
|
||||||
|
// is not Visible, so we need to check all the parents and make sure they are all either public or internal.
|
||||||
|
var parentsArePublicOrInternal = true;
|
||||||
|
var declaringType = type.DeclaringType;
|
||||||
|
while (declaringType != null && parentsArePublicOrInternal)
|
||||||
|
{
|
||||||
|
var declaringTypeIsPublicOrInternal =
|
||||||
|
|
||||||
|
// Declaring type is non-nested type, and we are looking for internal or public, which are the only
|
||||||
|
// two valid options that non-nested type can be.
|
||||||
|
!declaringType.IsNested
|
||||||
|
|
||||||
|
// Or the type is nested internal, or nested public type, but not any other
|
||||||
|
// like nested protected internal type, or nested private type.
|
||||||
|
|| declaringType.GetTypeInfo().IsNestedAssembly || declaringType.GetTypeInfo().IsNestedPublic;
|
||||||
|
|
||||||
|
if (!declaringTypeIsPublicOrInternal)
|
||||||
|
{
|
||||||
|
parentsArePublicOrInternal = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
declaringType = declaringType.DeclaringType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parentsArePublicOrInternal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The type is not public and is not nested. Non-nested types can be only public or internal
|
||||||
|
// so this type must be internal.
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,169 +1,168 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
|
||||||
|
|
||||||
|
internal class UnitTestDiscoverer
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
private readonly AssemblyEnumeratorWrapper assemblyEnumeratorWrapper;
|
||||||
using System.Globalization;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
|
|
||||||
|
|
||||||
internal class UnitTestDiscoverer
|
internal UnitTestDiscoverer()
|
||||||
{
|
{
|
||||||
private readonly AssemblyEnumeratorWrapper assemblyEnumeratorWrapper;
|
this.assemblyEnumeratorWrapper = new AssemblyEnumeratorWrapper();
|
||||||
|
this.TestMethodFilter = new TestMethodFilter();
|
||||||
|
}
|
||||||
|
|
||||||
internal UnitTestDiscoverer()
|
/// <summary>
|
||||||
|
/// Gets or sets method filter for filtering tests
|
||||||
|
/// </summary>
|
||||||
|
private TestMethodFilter TestMethodFilter { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Discovers the tests available from the provided sources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sources"> The sources. </param>
|
||||||
|
/// <param name="logger"> The logger. </param>
|
||||||
|
/// <param name="discoverySink"> The discovery Sink. </param>
|
||||||
|
/// <param name="discoveryContext"> The discovery context. </param>
|
||||||
|
internal void DiscoverTests(
|
||||||
|
IEnumerable<string> sources,
|
||||||
|
IMessageLogger logger,
|
||||||
|
ITestCaseDiscoverySink discoverySink,
|
||||||
|
IDiscoveryContext discoveryContext)
|
||||||
|
{
|
||||||
|
foreach (var source in sources)
|
||||||
{
|
{
|
||||||
this.assemblyEnumeratorWrapper = new AssemblyEnumeratorWrapper();
|
this.DiscoverTestsInSource(source, logger, discoverySink, discoveryContext);
|
||||||
this.TestMethodFilter = new TestMethodFilter();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the tests from the parameter source
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source"> The source. </param>
|
||||||
|
/// <param name="logger"> The logger. </param>
|
||||||
|
/// <param name="discoverySink"> The discovery Sink. </param>
|
||||||
|
/// <param name="discoveryContext"> The discovery context. </param>
|
||||||
|
internal virtual void DiscoverTestsInSource(
|
||||||
|
string source,
|
||||||
|
IMessageLogger logger,
|
||||||
|
ITestCaseDiscoverySink discoverySink,
|
||||||
|
IDiscoveryContext discoveryContext)
|
||||||
|
{
|
||||||
|
var testElements = this.assemblyEnumeratorWrapper.GetTests(source, discoveryContext?.RunSettings, out var warnings);
|
||||||
|
|
||||||
|
// log the warnings
|
||||||
|
foreach (var warning in warnings)
|
||||||
|
{
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo(
|
||||||
|
"MSTestDiscoverer: Warning during discovery from {0}. {1} ",
|
||||||
|
source,
|
||||||
|
warning);
|
||||||
|
var message = string.Format(CultureInfo.CurrentCulture, Resource.DiscoveryWarning, source, warning);
|
||||||
|
logger.SendMessage(TestMessageLevel.Warning, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// No tests found => nothing to do
|
||||||
/// Gets or sets method filter for filtering tests
|
if (testElements == null || testElements.Count == 0)
|
||||||
/// </summary>
|
|
||||||
private TestMethodFilter TestMethodFilter { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Discovers the tests available from the provided sources.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sources"> The sources. </param>
|
|
||||||
/// <param name="logger"> The logger. </param>
|
|
||||||
/// <param name="discoverySink"> The discovery Sink. </param>
|
|
||||||
/// <param name="discoveryContext"> The discovery context. </param>
|
|
||||||
internal void DiscoverTests(
|
|
||||||
IEnumerable<string> sources,
|
|
||||||
IMessageLogger logger,
|
|
||||||
ITestCaseDiscoverySink discoverySink,
|
|
||||||
IDiscoveryContext discoveryContext)
|
|
||||||
{
|
{
|
||||||
foreach (var source in sources)
|
return;
|
||||||
{
|
|
||||||
this.DiscoverTestsInSource(source, logger, discoverySink, discoveryContext);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo(
|
||||||
/// Get the tests from the parameter source
|
"MSTestDiscoverer: Found {0} tests from source {1}",
|
||||||
/// </summary>
|
testElements.Count,
|
||||||
/// <param name="source"> The source. </param>
|
source);
|
||||||
/// <param name="logger"> The logger. </param>
|
|
||||||
/// <param name="discoverySink"> The discovery Sink. </param>
|
|
||||||
/// <param name="discoveryContext"> The discovery context. </param>
|
|
||||||
internal virtual void DiscoverTestsInSource(
|
|
||||||
string source,
|
|
||||||
IMessageLogger logger,
|
|
||||||
ITestCaseDiscoverySink discoverySink,
|
|
||||||
IDiscoveryContext discoveryContext)
|
|
||||||
{
|
|
||||||
var testElements = this.assemblyEnumeratorWrapper.GetTests(source, discoveryContext?.RunSettings, out var warnings);
|
|
||||||
|
|
||||||
// log the warnings
|
this.SendTestCases(source, testElements, discoverySink, discoveryContext, logger);
|
||||||
foreach (var warning in warnings)
|
}
|
||||||
|
|
||||||
|
internal void SendTestCases(string source, IEnumerable<UnitTestElement> testElements, ITestCaseDiscoverySink discoverySink, IDiscoveryContext discoveryContext, IMessageLogger logger)
|
||||||
|
{
|
||||||
|
var shouldCollectSourceInformation = MSTestSettings.RunConfigurationSettings.CollectSourceInformation;
|
||||||
|
|
||||||
|
var navigationSessions = new Dictionary<string, object>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (shouldCollectSourceInformation)
|
||||||
{
|
{
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo(
|
navigationSessions.Add(source, PlatformServiceProvider.Instance.FileOperations.CreateNavigationSession(source));
|
||||||
"MSTestDiscoverer: Warning during discovery from {0}. {1} ",
|
|
||||||
source,
|
|
||||||
warning);
|
|
||||||
var message = string.Format(CultureInfo.CurrentCulture, Resource.DiscoveryWarning, source, warning);
|
|
||||||
logger.SendMessage(TestMessageLevel.Warning, message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// No tests found => nothing to do
|
// Get filter expression and skip discovery in case filter expression has parsing error.
|
||||||
if (testElements == null || testElements.Count == 0)
|
ITestCaseFilterExpression filterExpression = this.TestMethodFilter.GetFilterExpression(discoveryContext, logger, out var filterHasError);
|
||||||
|
if (filterHasError)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo(
|
foreach (var testElement in testElements)
|
||||||
"MSTestDiscoverer: Found {0} tests from source {1}",
|
|
||||||
testElements.Count,
|
|
||||||
source);
|
|
||||||
|
|
||||||
this.SendTestCases(source, testElements, discoverySink, discoveryContext, logger);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void SendTestCases(string source, IEnumerable<UnitTestElement> testElements, ITestCaseDiscoverySink discoverySink, IDiscoveryContext discoveryContext, IMessageLogger logger)
|
|
||||||
{
|
|
||||||
var shouldCollectSourceInformation = MSTestSettings.RunConfigurationSettings.CollectSourceInformation;
|
|
||||||
|
|
||||||
var navigationSessions = new Dictionary<string, object>();
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
|
var testCase = testElement.ToTestCase();
|
||||||
|
|
||||||
|
// Filter tests based on test case filters
|
||||||
|
if (filterExpression != null && filterExpression.MatchTestCase(testCase, (p) => this.TestMethodFilter.PropertyValueProvider(testCase, p)) == false)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (shouldCollectSourceInformation)
|
if (shouldCollectSourceInformation)
|
||||||
{
|
{
|
||||||
navigationSessions.Add(source, PlatformServiceProvider.Instance.FileOperations.CreateNavigationSession(source));
|
string testSource = testElement.TestMethod.DeclaringAssemblyName ?? source;
|
||||||
}
|
|
||||||
|
|
||||||
// Get filter expression and skip discovery in case filter expression has parsing error.
|
if (!navigationSessions.TryGetValue(testSource, out var testNavigationSession))
|
||||||
ITestCaseFilterExpression filterExpression = this.TestMethodFilter.GetFilterExpression(discoveryContext, logger, out var filterHasError);
|
|
||||||
if (filterHasError)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var testElement in testElements)
|
|
||||||
{
|
|
||||||
var testCase = testElement.ToTestCase();
|
|
||||||
|
|
||||||
// Filter tests based on test case filters
|
|
||||||
if (filterExpression != null && filterExpression.MatchTestCase(testCase, (p) => this.TestMethodFilter.PropertyValueProvider(testCase, p)) == false)
|
|
||||||
{
|
{
|
||||||
continue;
|
testNavigationSession = PlatformServiceProvider.Instance.FileOperations.CreateNavigationSession(testSource);
|
||||||
|
navigationSessions.Add(testSource, testNavigationSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldCollectSourceInformation)
|
if (testNavigationSession != null)
|
||||||
{
|
{
|
||||||
string testSource = testElement.TestMethod.DeclaringAssemblyName ?? source;
|
var className = testElement.TestMethod.DeclaringClassFullName
|
||||||
|
?? testElement.TestMethod.FullClassName;
|
||||||
|
|
||||||
if (!navigationSessions.TryGetValue(testSource, out var testNavigationSession))
|
var methodName = testElement.TestMethod.Name;
|
||||||
|
|
||||||
|
// If it is async test method use compiler generated type and method name for navigation data.
|
||||||
|
if (!string.IsNullOrEmpty(testElement.AsyncTypeName))
|
||||||
{
|
{
|
||||||
testNavigationSession = PlatformServiceProvider.Instance.FileOperations.CreateNavigationSession(testSource);
|
className = testElement.AsyncTypeName;
|
||||||
navigationSessions.Add(testSource, testNavigationSession);
|
|
||||||
|
// compiler generated method name is "MoveNext".
|
||||||
|
methodName = "MoveNext";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (testNavigationSession != null)
|
PlatformServiceProvider.Instance.FileOperations.GetNavigationData(
|
||||||
|
testNavigationSession,
|
||||||
|
className,
|
||||||
|
methodName,
|
||||||
|
out var minLineNumber,
|
||||||
|
out var fileName);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(fileName))
|
||||||
{
|
{
|
||||||
var className = testElement.TestMethod.DeclaringClassFullName
|
testCase.LineNumber = minLineNumber;
|
||||||
?? testElement.TestMethod.FullClassName;
|
testCase.CodeFilePath = fileName;
|
||||||
|
|
||||||
var methodName = testElement.TestMethod.Name;
|
|
||||||
|
|
||||||
// If it is async test method use compiler generated type and method name for navigation data.
|
|
||||||
if (!string.IsNullOrEmpty(testElement.AsyncTypeName))
|
|
||||||
{
|
|
||||||
className = testElement.AsyncTypeName;
|
|
||||||
|
|
||||||
// compiler generated method name is "MoveNext".
|
|
||||||
methodName = "MoveNext";
|
|
||||||
}
|
|
||||||
|
|
||||||
PlatformServiceProvider.Instance.FileOperations.GetNavigationData(
|
|
||||||
testNavigationSession,
|
|
||||||
className,
|
|
||||||
methodName,
|
|
||||||
out var minLineNumber,
|
|
||||||
out var fileName);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(fileName))
|
|
||||||
{
|
|
||||||
testCase.LineNumber = minLineNumber;
|
|
||||||
testCase.CodeFilePath = fileName;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
discoverySink.SendTestCase(testCase);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
discoverySink.SendTestCase(testCase);
|
||||||
}
|
}
|
||||||
finally
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
foreach (object navigationSession in navigationSessions.Values)
|
||||||
{
|
{
|
||||||
foreach (object navigationSession in navigationSessions.Values)
|
PlatformServiceProvider.Instance.FileOperations.DisposeNavigationSession(navigationSession);
|
||||||
{
|
|
||||||
PlatformServiceProvider.Instance.FileOperations.DisposeNavigationSession(navigationSession);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,184 +1,183 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting.Logging;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Listens for log messages and Debug.WriteLine
|
||||||
|
/// Note that this class is not thread-safe and thus should only be used when unit tests are being run serially.
|
||||||
|
/// </summary>
|
||||||
|
public class LogMessageListener : IDisposable
|
||||||
{
|
{
|
||||||
using System;
|
private static object traceLock = new();
|
||||||
using System.Globalization;
|
private static int listenerCount;
|
||||||
using System.IO;
|
private static ThreadSafeStringWriter redirectedDebugTrace;
|
||||||
using System.Threading;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting.Logging;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Listens for log messages and Debug.WriteLine
|
/// Trace listener to capture Trace.WriteLines in the test cases
|
||||||
/// Note that this class is not thread-safe and thus should only be used when unit tests are being run serially.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LogMessageListener : IDisposable
|
private static ITraceListener traceListener;
|
||||||
|
private readonly ThreadSafeStringWriter redirectedStandardOutput;
|
||||||
|
private readonly ThreadSafeStringWriter redirectedStandardError;
|
||||||
|
private readonly bool captureDebugTraces;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Trace listener Manager to perform operation on tracelistener objects.
|
||||||
|
/// </summary>
|
||||||
|
private ITraceListenerManager traceListenerManager;
|
||||||
|
private bool isDisposed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="LogMessageListener"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="captureDebugTraces">Captures debug traces if true.</param>
|
||||||
|
public LogMessageListener(bool captureDebugTraces)
|
||||||
{
|
{
|
||||||
private static object traceLock = new();
|
this.captureDebugTraces = captureDebugTraces;
|
||||||
private static int listenerCount;
|
|
||||||
private static ThreadSafeStringWriter redirectedDebugTrace;
|
|
||||||
|
|
||||||
/// <summary>
|
// Cache the original output/error streams and replace it with the own stream.
|
||||||
/// Trace listener to capture Trace.WriteLines in the test cases
|
this.redirectedStandardOutput = new ThreadSafeStringWriter(CultureInfo.InvariantCulture, "out");
|
||||||
/// </summary>
|
this.redirectedStandardError = new ThreadSafeStringWriter(CultureInfo.InvariantCulture, "err");
|
||||||
private static ITraceListener traceListener;
|
|
||||||
private readonly ThreadSafeStringWriter redirectedStandardOutput;
|
|
||||||
private readonly ThreadSafeStringWriter redirectedStandardError;
|
|
||||||
private readonly bool captureDebugTraces;
|
|
||||||
|
|
||||||
/// <summary>
|
Logger.OnLogMessage += this.redirectedStandardOutput.WriteLine;
|
||||||
/// Trace listener Manager to perform operation on tracelistener objects.
|
|
||||||
/// </summary>
|
|
||||||
private ITraceListenerManager traceListenerManager;
|
|
||||||
private bool isDisposed;
|
|
||||||
|
|
||||||
/// <summary>
|
if (this.captureDebugTraces)
|
||||||
/// Initializes a new instance of the <see cref="LogMessageListener"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="captureDebugTraces">Captures debug traces if true.</param>
|
|
||||||
public LogMessageListener(bool captureDebugTraces)
|
|
||||||
{
|
{
|
||||||
this.captureDebugTraces = captureDebugTraces;
|
// This is awkward, it has a side-effect of setting up Console output redirection, but the naming is suggesting that we are
|
||||||
|
// just getting TraceListener manager.
|
||||||
|
this.traceListenerManager = PlatformServiceProvider.Instance.GetTraceListenerManager(this.redirectedStandardOutput, this.redirectedStandardError);
|
||||||
|
|
||||||
// Cache the original output/error streams and replace it with the own stream.
|
// The Debug listener uses Debug.WriteLine and Debug.Write to write the messages, which end up written into Trace.Listeners.
|
||||||
this.redirectedStandardOutput = new ThreadSafeStringWriter(CultureInfo.InvariantCulture, "out");
|
// These listeners are static and hence shared across the whole process. We need to capture Debug output only for the current
|
||||||
this.redirectedStandardError = new ThreadSafeStringWriter(CultureInfo.InvariantCulture, "err");
|
// test, which was historically done by registering a listener in constructor of this class, and by removing the listener on Dispose.
|
||||||
|
// The newly created listener replaced previously registered listener, which was remembered, and put back on dispose.
|
||||||
|
//
|
||||||
|
// This works well as long as there are no tests running in parallel. But as soon as there are tests running in parallel. Then all the
|
||||||
|
// debug output of all tests will be output into the test that was most recently created (because it registered the listener most recently).
|
||||||
|
//
|
||||||
|
// To prevent mixing of outputs, the ThreadSafeStringWriter was re-implemented for net46 and newer to leverage AsyncLocal, which allows the writer to
|
||||||
|
// write only to the output of the current test. This leaves the LogMessageListener with only one task. Make sure that a trace listener is registered
|
||||||
|
// as long as there is any active test. This is still done by constructor and Dispose, but instead of replacing the listener every time, we use listenerCount
|
||||||
|
// to only add the listener when there is none, and remove it when we are the last one to dispose.
|
||||||
|
//
|
||||||
|
// This would break the behavior for net451, but that functionality was moved further into ThreadSafeStringWriter.
|
||||||
|
lock (traceLock)
|
||||||
|
{
|
||||||
|
if (listenerCount == 0)
|
||||||
|
{
|
||||||
|
redirectedDebugTrace = new ThreadSafeStringWriter(CultureInfo.InvariantCulture, "trace");
|
||||||
|
traceListener = PlatformServiceProvider.Instance.GetTraceListener(redirectedDebugTrace);
|
||||||
|
this.traceListenerManager.Add(traceListener);
|
||||||
|
}
|
||||||
|
|
||||||
Logger.OnLogMessage += this.redirectedStandardOutput.WriteLine;
|
listenerCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~LogMessageListener()
|
||||||
|
{
|
||||||
|
this.Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets logger output
|
||||||
|
/// </summary>
|
||||||
|
public string StandardOutput => this.redirectedStandardOutput.ToString();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets 'Error' Output from the redirected stream
|
||||||
|
/// </summary>
|
||||||
|
public string StandardError => this.redirectedStandardError.ToString();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets 'Trace' Output from the redirected stream
|
||||||
|
/// </summary>
|
||||||
|
public string DebugTrace
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return redirectedDebugTrace?.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetAndClearStandardOutput()
|
||||||
|
{
|
||||||
|
var output = this.redirectedStandardOutput.ToStringAndClear();
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetAndClearStandardError()
|
||||||
|
{
|
||||||
|
var output = this.redirectedStandardError.ToStringAndClear();
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetAndClearDebugTrace()
|
||||||
|
{
|
||||||
|
if (redirectedDebugTrace == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var output = redirectedDebugTrace.ToStringAndClear();
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing && !this.isDisposed)
|
||||||
|
{
|
||||||
|
this.isDisposed = true;
|
||||||
|
Logger.OnLogMessage -= this.redirectedStandardOutput.WriteLine;
|
||||||
|
Logger.OnLogMessage -= this.redirectedStandardError.WriteLine;
|
||||||
|
|
||||||
|
this.redirectedStandardOutput.Dispose();
|
||||||
|
this.redirectedStandardError.Dispose();
|
||||||
|
|
||||||
if (this.captureDebugTraces)
|
if (this.captureDebugTraces)
|
||||||
{
|
{
|
||||||
// This is awkward, it has a side-effect of setting up Console output redirection, but the naming is suggesting that we are
|
|
||||||
// just getting TraceListener manager.
|
|
||||||
this.traceListenerManager = PlatformServiceProvider.Instance.GetTraceListenerManager(this.redirectedStandardOutput, this.redirectedStandardError);
|
|
||||||
|
|
||||||
// The Debug listener uses Debug.WriteLine and Debug.Write to write the messages, which end up written into Trace.Listeners.
|
|
||||||
// These listeners are static and hence shared across the whole process. We need to capture Debug output only for the current
|
|
||||||
// test, which was historically done by registering a listener in constructor of this class, and by removing the listener on Dispose.
|
|
||||||
// The newly created listener replaced previously registered listener, which was remembered, and put back on dispose.
|
|
||||||
//
|
|
||||||
// This works well as long as there are no tests running in parallel. But as soon as there are tests running in parallel. Then all the
|
|
||||||
// debug output of all tests will be output into the test that was most recently created (because it registered the listener most recently).
|
|
||||||
//
|
|
||||||
// To prevent mixing of outputs, the ThreadSafeStringWriter was re-implemented for net46 and newer to leverage AsyncLocal, which allows the writer to
|
|
||||||
// write only to the output of the current test. This leaves the LogMessageListener with only one task. Make sure that a trace listener is registered
|
|
||||||
// as long as there is any active test. This is still done by constructor and Dispose, but instead of replacing the listener every time, we use listenerCount
|
|
||||||
// to only add the listener when there is none, and remove it when we are the last one to dispose.
|
|
||||||
//
|
|
||||||
// This would break the behavior for net451, but that functionality was moved further into ThreadSafeStringWriter.
|
|
||||||
lock (traceLock)
|
lock (traceLock)
|
||||||
{
|
{
|
||||||
if (listenerCount == 0)
|
if (listenerCount == 1)
|
||||||
{
|
{
|
||||||
redirectedDebugTrace = new ThreadSafeStringWriter(CultureInfo.InvariantCulture, "trace");
|
try
|
||||||
traceListener = PlatformServiceProvider.Instance.GetTraceListener(redirectedDebugTrace);
|
|
||||||
this.traceListenerManager.Add(traceListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
listenerCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
~LogMessageListener()
|
|
||||||
{
|
|
||||||
this.Dispose(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets logger output
|
|
||||||
/// </summary>
|
|
||||||
public string StandardOutput => this.redirectedStandardOutput.ToString();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets 'Error' Output from the redirected stream
|
|
||||||
/// </summary>
|
|
||||||
public string StandardError => this.redirectedStandardError.ToString();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets 'Trace' Output from the redirected stream
|
|
||||||
/// </summary>
|
|
||||||
public string DebugTrace
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return redirectedDebugTrace?.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetAndClearStandardOutput()
|
|
||||||
{
|
|
||||||
var output = this.redirectedStandardOutput.ToStringAndClear();
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetAndClearStandardError()
|
|
||||||
{
|
|
||||||
var output = this.redirectedStandardError.ToStringAndClear();
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetAndClearDebugTrace()
|
|
||||||
{
|
|
||||||
if (redirectedDebugTrace == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var output = redirectedDebugTrace.ToStringAndClear();
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
this.Dispose(true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (disposing && !this.isDisposed)
|
|
||||||
{
|
|
||||||
this.isDisposed = true;
|
|
||||||
Logger.OnLogMessage -= this.redirectedStandardOutput.WriteLine;
|
|
||||||
Logger.OnLogMessage -= this.redirectedStandardError.WriteLine;
|
|
||||||
|
|
||||||
this.redirectedStandardOutput.Dispose();
|
|
||||||
this.redirectedStandardError.Dispose();
|
|
||||||
|
|
||||||
if (this.captureDebugTraces)
|
|
||||||
{
|
|
||||||
lock (traceLock)
|
|
||||||
{
|
|
||||||
if (listenerCount == 1)
|
|
||||||
{
|
{
|
||||||
try
|
|
||||||
{
|
|
||||||
if (traceListener != null)
|
|
||||||
{
|
|
||||||
this.traceListenerManager.Remove(traceListener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
// Catch all exceptions since Dispose should not throw.
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogError("ConsoleOutputRedirector.Dispose threw exception: {0}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (traceListener != null)
|
if (traceListener != null)
|
||||||
{
|
{
|
||||||
// Dispose trace manager and listeners
|
this.traceListenerManager.Remove(traceListener);
|
||||||
this.traceListenerManager.Dispose(traceListener);
|
|
||||||
this.traceListenerManager = null;
|
|
||||||
traceListener = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// Catch all exceptions since Dispose should not throw.
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogError("ConsoleOutputRedirector.Dispose threw exception: {0}", e);
|
||||||
|
}
|
||||||
|
|
||||||
listenerCount--;
|
if (traceListener != null)
|
||||||
|
{
|
||||||
|
// Dispose trace manager and listeners
|
||||||
|
this.traceListenerManager.Dispose(traceListener);
|
||||||
|
this.traceListenerManager = null;
|
||||||
|
traceListener = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listenerCount--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,34 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Result of the run cleanup operation
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
internal class RunCleanupResult
|
||||||
{
|
{
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Collections.Generic;
|
/// Gets or sets the standard out of the cleanup methods
|
||||||
|
/// </summary>
|
||||||
|
internal string StandardOut { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Result of the run cleanup operation
|
/// Gets or sets the standard error of the cleanup methods
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Serializable]
|
internal string StandardError { get; set; }
|
||||||
internal class RunCleanupResult
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the standard out of the cleanup methods
|
|
||||||
/// </summary>
|
|
||||||
internal string StandardOut { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the standard error of the cleanup methods
|
/// Gets or sets the Debug trace of the cleanup methods.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal string StandardError { get; set; }
|
internal string DebugTrace { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Debug trace of the cleanup methods.
|
/// Gets or sets the Warnings from the RunCleanup method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal string DebugTrace { get; set; }
|
internal IList<string> Warnings { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Warnings from the RunCleanup method
|
|
||||||
/// </summary>
|
|
||||||
internal IList<string> Warnings { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,243 +1,242 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution;
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides helper methods to parse stack trace.
|
||||||
|
/// </summary>
|
||||||
|
internal static class StackTraceHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Type that need to be excluded.
|
||||||
|
/// </summary>
|
||||||
|
private static List<string> typesToBeExcluded = new() { typeof(Microsoft.VisualStudio.TestTools.UnitTesting.Assert).Namespace, typeof(MSTestExecutor).Namespace };
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides helper methods to parse stack trace.
|
/// Gets the types whose methods should be ignored in the reported call stacks.
|
||||||
|
/// This is used to remove our stack that the user will not care about.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class StackTraceHelper
|
private static List<string> TypeToBeExcluded
|
||||||
{
|
{
|
||||||
/// <summary>
|
get
|
||||||
/// Type that need to be excluded.
|
|
||||||
/// </summary>
|
|
||||||
private static List<string> typesToBeExcluded = new() { typeof(Microsoft.VisualStudio.TestTools.UnitTesting.Assert).Namespace, typeof(MSTestExecutor).Namespace };
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the types whose methods should be ignored in the reported call stacks.
|
|
||||||
/// This is used to remove our stack that the user will not care about.
|
|
||||||
/// </summary>
|
|
||||||
private static List<string> TypeToBeExcluded
|
|
||||||
{
|
{
|
||||||
get
|
return typesToBeExcluded;
|
||||||
{
|
|
||||||
return typesToBeExcluded;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the stack trace for an exception, including all stack traces for inner
|
|
||||||
/// exceptions.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="ex">
|
|
||||||
/// The exception.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// The <see cref="StackTraceInformation"/> for the provided exception.
|
|
||||||
/// </returns>
|
|
||||||
internal static StackTraceInformation GetStackTraceInformation(Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Assert(ex != null, "exception should not be null.");
|
|
||||||
|
|
||||||
Stack<string> stackTraces = new();
|
|
||||||
|
|
||||||
for (Exception curException = ex;
|
|
||||||
curException != null;
|
|
||||||
curException = curException.InnerException)
|
|
||||||
{
|
|
||||||
// TODO:Fix the shadow stack-trace used in Private Object
|
|
||||||
// (Look-in Assertion.cs in the UnitTestFramework assembly)
|
|
||||||
|
|
||||||
// Sometimes the stack trace can be null, but the inner stack trace
|
|
||||||
// contains information. We are not interested in null stack traces
|
|
||||||
// so we simply ignore this case
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (curException.StackTrace != null)
|
|
||||||
{
|
|
||||||
stackTraces.Push(curException.StackTrace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
// curException.StackTrace can throw exception, Although MSDN doc doesn't say that.
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// try to get stack trace
|
|
||||||
if (e.StackTrace != null)
|
|
||||||
{
|
|
||||||
stackTraces.Push(e.StackTrace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogError(
|
|
||||||
"StackTraceHelper.GetStackTraceInformation: Failed to get stack trace info.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder result = new();
|
|
||||||
bool first = true;
|
|
||||||
while (stackTraces.Count != 0)
|
|
||||||
{
|
|
||||||
result.AppendFormat(
|
|
||||||
CultureInfo.CurrentCulture,
|
|
||||||
"{0} {1}{2}",
|
|
||||||
first ? string.Empty : (Resource.UTA_EndOfInnerExceptionTrace + Environment.NewLine),
|
|
||||||
stackTraces.Pop(),
|
|
||||||
Environment.NewLine);
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CreateStackTraceInformation(ex, true, result.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes all stack frames that refer to Microsoft.VisualStudio.TestTools.UnitTesting.Assertion
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="stackTrace">
|
|
||||||
/// The stack Trace.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// The trimmed stack trace removing traces of the framework and adapter from the stack.
|
|
||||||
/// </returns>
|
|
||||||
internal static string TrimStackTrace(string stackTrace)
|
|
||||||
{
|
|
||||||
Debug.Assert(!string.IsNullOrEmpty(stackTrace), "stack trace should be non-empty.");
|
|
||||||
|
|
||||||
StringBuilder result = new(stackTrace.Length);
|
|
||||||
string[] stackFrames = Regex.Split(stackTrace, Environment.NewLine);
|
|
||||||
|
|
||||||
foreach (string stackFrame in stackFrames)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(stackFrame))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the frame to the result if it does not refer to
|
|
||||||
// the assertion class in the test framework
|
|
||||||
bool hasReference = HasReferenceToUTF(stackFrame);
|
|
||||||
if (!hasReference)
|
|
||||||
{
|
|
||||||
result.Append(stackFrame);
|
|
||||||
result.Append(Environment.NewLine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the exception messages, including the messages for all inner exceptions
|
|
||||||
/// recursively
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="ex">
|
|
||||||
/// The exception.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// The aggregated exception message that considers inner exceptions.
|
|
||||||
/// </returns>
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
|
||||||
internal static string GetExceptionMessage(Exception ex)
|
|
||||||
{
|
|
||||||
Debug.Assert(ex != null, "exception should not be null.");
|
|
||||||
|
|
||||||
StringBuilder result = new();
|
|
||||||
bool first = true;
|
|
||||||
for (Exception curException = ex;
|
|
||||||
curException != null;
|
|
||||||
curException = curException.InnerException)
|
|
||||||
{
|
|
||||||
// Get the exception message. Need to check for errors because the Message property
|
|
||||||
// may have been overridden by the exception type in user code.
|
|
||||||
string msg;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
msg = curException.Message;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
msg = string.Format(CultureInfo.CurrentCulture, Resource.UTF_FailedToGetExceptionMessage, curException.GetType());
|
|
||||||
}
|
|
||||||
|
|
||||||
result.AppendFormat(
|
|
||||||
CultureInfo.CurrentCulture,
|
|
||||||
"{0}{1}: {2}",
|
|
||||||
first ? string.Empty : " ---> ",
|
|
||||||
curException.GetType(),
|
|
||||||
msg);
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create stack trace information
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="ex">
|
|
||||||
/// The exception.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="checkInnerExceptions">
|
|
||||||
/// Whether the inner exception needs to be checked too.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="stackTraceString">
|
|
||||||
/// The stack Trace String.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// The <see cref="StackTraceInformation"/>.
|
|
||||||
/// </returns>
|
|
||||||
internal static StackTraceInformation CreateStackTraceInformation(
|
|
||||||
Exception ex,
|
|
||||||
bool checkInnerExceptions,
|
|
||||||
string stackTraceString)
|
|
||||||
{
|
|
||||||
if (checkInnerExceptions && ex.InnerException != null)
|
|
||||||
{
|
|
||||||
return CreateStackTraceInformation(ex.InnerException, checkInnerExceptions, stackTraceString);
|
|
||||||
}
|
|
||||||
|
|
||||||
var stackTrace = StackTraceHelper.TrimStackTrace(stackTraceString);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(stackTrace))
|
|
||||||
{
|
|
||||||
return new StackTraceInformation(stackTrace, null, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns whether the parameter stackFrame has reference to UTF
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="stackFrame">
|
|
||||||
/// The stack Frame.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// True if the framework or the adapter methods are in the stack frame.
|
|
||||||
/// </returns>
|
|
||||||
internal static bool HasReferenceToUTF(string stackFrame)
|
|
||||||
{
|
|
||||||
foreach (var type in TypeToBeExcluded)
|
|
||||||
{
|
|
||||||
if (stackFrame.IndexOf(type, StringComparison.Ordinal) > -1)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the stack trace for an exception, including all stack traces for inner
|
||||||
|
/// exceptions.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ex">
|
||||||
|
/// The exception.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The <see cref="StackTraceInformation"/> for the provided exception.
|
||||||
|
/// </returns>
|
||||||
|
internal static StackTraceInformation GetStackTraceInformation(Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Assert(ex != null, "exception should not be null.");
|
||||||
|
|
||||||
|
Stack<string> stackTraces = new();
|
||||||
|
|
||||||
|
for (Exception curException = ex;
|
||||||
|
curException != null;
|
||||||
|
curException = curException.InnerException)
|
||||||
|
{
|
||||||
|
// TODO:Fix the shadow stack-trace used in Private Object
|
||||||
|
// (Look-in Assertion.cs in the UnitTestFramework assembly)
|
||||||
|
|
||||||
|
// Sometimes the stack trace can be null, but the inner stack trace
|
||||||
|
// contains information. We are not interested in null stack traces
|
||||||
|
// so we simply ignore this case
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (curException.StackTrace != null)
|
||||||
|
{
|
||||||
|
stackTraces.Push(curException.StackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// curException.StackTrace can throw exception, Although MSDN doc doesn't say that.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// try to get stack trace
|
||||||
|
if (e.StackTrace != null)
|
||||||
|
{
|
||||||
|
stackTraces.Push(e.StackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogError(
|
||||||
|
"StackTraceHelper.GetStackTraceInformation: Failed to get stack trace info.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder result = new();
|
||||||
|
bool first = true;
|
||||||
|
while (stackTraces.Count != 0)
|
||||||
|
{
|
||||||
|
result.AppendFormat(
|
||||||
|
CultureInfo.CurrentCulture,
|
||||||
|
"{0} {1}{2}",
|
||||||
|
first ? string.Empty : (Resource.UTA_EndOfInnerExceptionTrace + Environment.NewLine),
|
||||||
|
stackTraces.Pop(),
|
||||||
|
Environment.NewLine);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateStackTraceInformation(ex, true, result.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all stack frames that refer to Microsoft.VisualStudio.TestTools.UnitTesting.Assertion
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stackTrace">
|
||||||
|
/// The stack Trace.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The trimmed stack trace removing traces of the framework and adapter from the stack.
|
||||||
|
/// </returns>
|
||||||
|
internal static string TrimStackTrace(string stackTrace)
|
||||||
|
{
|
||||||
|
Debug.Assert(!string.IsNullOrEmpty(stackTrace), "stack trace should be non-empty.");
|
||||||
|
|
||||||
|
StringBuilder result = new(stackTrace.Length);
|
||||||
|
string[] stackFrames = Regex.Split(stackTrace, Environment.NewLine);
|
||||||
|
|
||||||
|
foreach (string stackFrame in stackFrames)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(stackFrame))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the frame to the result if it does not refer to
|
||||||
|
// the assertion class in the test framework
|
||||||
|
bool hasReference = HasReferenceToUTF(stackFrame);
|
||||||
|
if (!hasReference)
|
||||||
|
{
|
||||||
|
result.Append(stackFrame);
|
||||||
|
result.Append(Environment.NewLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the exception messages, including the messages for all inner exceptions
|
||||||
|
/// recursively
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ex">
|
||||||
|
/// The exception.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The aggregated exception message that considers inner exceptions.
|
||||||
|
/// </returns>
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
||||||
|
internal static string GetExceptionMessage(Exception ex)
|
||||||
|
{
|
||||||
|
Debug.Assert(ex != null, "exception should not be null.");
|
||||||
|
|
||||||
|
StringBuilder result = new();
|
||||||
|
bool first = true;
|
||||||
|
for (Exception curException = ex;
|
||||||
|
curException != null;
|
||||||
|
curException = curException.InnerException)
|
||||||
|
{
|
||||||
|
// Get the exception message. Need to check for errors because the Message property
|
||||||
|
// may have been overridden by the exception type in user code.
|
||||||
|
string msg;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
msg = curException.Message;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
msg = string.Format(CultureInfo.CurrentCulture, Resource.UTF_FailedToGetExceptionMessage, curException.GetType());
|
||||||
|
}
|
||||||
|
|
||||||
|
result.AppendFormat(
|
||||||
|
CultureInfo.CurrentCulture,
|
||||||
|
"{0}{1}: {2}",
|
||||||
|
first ? string.Empty : " ---> ",
|
||||||
|
curException.GetType(),
|
||||||
|
msg);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create stack trace information
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ex">
|
||||||
|
/// The exception.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="checkInnerExceptions">
|
||||||
|
/// Whether the inner exception needs to be checked too.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="stackTraceString">
|
||||||
|
/// The stack Trace String.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The <see cref="StackTraceInformation"/>.
|
||||||
|
/// </returns>
|
||||||
|
internal static StackTraceInformation CreateStackTraceInformation(
|
||||||
|
Exception ex,
|
||||||
|
bool checkInnerExceptions,
|
||||||
|
string stackTraceString)
|
||||||
|
{
|
||||||
|
if (checkInnerExceptions && ex.InnerException != null)
|
||||||
|
{
|
||||||
|
return CreateStackTraceInformation(ex.InnerException, checkInnerExceptions, stackTraceString);
|
||||||
|
}
|
||||||
|
|
||||||
|
var stackTrace = StackTraceHelper.TrimStackTrace(stackTraceString);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(stackTrace))
|
||||||
|
{
|
||||||
|
return new StackTraceInformation(stackTrace, null, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether the parameter stackFrame has reference to UTF
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stackFrame">
|
||||||
|
/// The stack Frame.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// True if the framework or the adapter methods are in the stack frame.
|
||||||
|
/// </returns>
|
||||||
|
internal static bool HasReferenceToUTF(string stackFrame)
|
||||||
|
{
|
||||||
|
foreach (var type in TypeToBeExcluded)
|
||||||
|
{
|
||||||
|
if (stackFrame.IndexOf(type, StringComparison.Ordinal) > -1)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,52 +1,51 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using TestPlatformObjectModel = Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads and parses the TcmTestProperties in order to populate them in TestRunParameters.
|
||||||
|
/// </summary>
|
||||||
|
internal static class TcmTestPropertiesProvider
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
|
||||||
using TestPlatformObjectModel = Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads and parses the TcmTestProperties in order to populate them in TestRunParameters.
|
/// Gets tcm properties from test case.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class TcmTestPropertiesProvider
|
/// <param name="testCase">Test case.</param>
|
||||||
|
/// <returns>Tcm properties.</returns>
|
||||||
|
public static IDictionary<TestPlatformObjectModel.TestProperty, object> GetTcmProperties(TestPlatformObjectModel.TestCase testCase)
|
||||||
{
|
{
|
||||||
/// <summary>
|
var tcmProperties = new Dictionary<TestPlatformObjectModel.TestProperty, object>();
|
||||||
/// Gets tcm properties from test case.
|
|
||||||
/// </summary>
|
// Return empty properties when testCase is null or when test case id is zero.
|
||||||
/// <param name="testCase">Test case.</param>
|
if (testCase == null ||
|
||||||
/// <returns>Tcm properties.</returns>
|
testCase.GetPropertyValue<int>(Constants.TestCaseIdProperty, default) == 0)
|
||||||
public static IDictionary<TestPlatformObjectModel.TestProperty, object> GetTcmProperties(TestPlatformObjectModel.TestCase testCase)
|
|
||||||
{
|
{
|
||||||
var tcmProperties = new Dictionary<TestPlatformObjectModel.TestProperty, object>();
|
|
||||||
|
|
||||||
// Return empty properties when testCase is null or when test case id is zero.
|
|
||||||
if (testCase == null ||
|
|
||||||
testCase.GetPropertyValue<int>(Constants.TestCaseIdProperty, default) == 0)
|
|
||||||
{
|
|
||||||
return tcmProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 1: Add common properties.
|
|
||||||
tcmProperties[Constants.TestRunIdProperty] = testCase.GetPropertyValue<int>(Constants.TestRunIdProperty, default);
|
|
||||||
tcmProperties[Constants.TestPlanIdProperty] = testCase.GetPropertyValue<int>(Constants.TestPlanIdProperty, default);
|
|
||||||
tcmProperties[Constants.BuildConfigurationIdProperty] = testCase.GetPropertyValue<int>(Constants.BuildConfigurationIdProperty, default);
|
|
||||||
tcmProperties[Constants.BuildDirectoryProperty] = testCase.GetPropertyValue<string>(Constants.BuildDirectoryProperty, default);
|
|
||||||
tcmProperties[Constants.BuildFlavorProperty] = testCase.GetPropertyValue<string>(Constants.BuildFlavorProperty, default);
|
|
||||||
tcmProperties[Constants.BuildNumberProperty] = testCase.GetPropertyValue<string>(Constants.BuildNumberProperty, default);
|
|
||||||
tcmProperties[Constants.BuildPlatformProperty] = testCase.GetPropertyValue<string>(Constants.BuildPlatformProperty, default);
|
|
||||||
tcmProperties[Constants.BuildUriProperty] = testCase.GetPropertyValue<string>(Constants.BuildUriProperty, default);
|
|
||||||
tcmProperties[Constants.TfsServerCollectionUrlProperty] = testCase.GetPropertyValue<string>(Constants.TfsServerCollectionUrlProperty, default);
|
|
||||||
tcmProperties[Constants.TfsTeamProjectProperty] = testCase.GetPropertyValue<string>(Constants.TfsTeamProjectProperty, default);
|
|
||||||
tcmProperties[Constants.IsInLabEnvironmentProperty] = testCase.GetPropertyValue<bool>(Constants.IsInLabEnvironmentProperty, default);
|
|
||||||
|
|
||||||
// Step 2: Add test case specific properties.
|
|
||||||
tcmProperties[Constants.TestCaseIdProperty] = testCase.GetPropertyValue<int>(Constants.TestCaseIdProperty, default);
|
|
||||||
tcmProperties[Constants.TestConfigurationIdProperty] = testCase.GetPropertyValue<int>(Constants.TestConfigurationIdProperty, default);
|
|
||||||
tcmProperties[Constants.TestConfigurationNameProperty] = testCase.GetPropertyValue<string>(Constants.TestConfigurationNameProperty, default);
|
|
||||||
tcmProperties[Constants.TestPointIdProperty] = testCase.GetPropertyValue<int>(Constants.TestPointIdProperty, default);
|
|
||||||
|
|
||||||
return tcmProperties;
|
return tcmProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 1: Add common properties.
|
||||||
|
tcmProperties[Constants.TestRunIdProperty] = testCase.GetPropertyValue<int>(Constants.TestRunIdProperty, default);
|
||||||
|
tcmProperties[Constants.TestPlanIdProperty] = testCase.GetPropertyValue<int>(Constants.TestPlanIdProperty, default);
|
||||||
|
tcmProperties[Constants.BuildConfigurationIdProperty] = testCase.GetPropertyValue<int>(Constants.BuildConfigurationIdProperty, default);
|
||||||
|
tcmProperties[Constants.BuildDirectoryProperty] = testCase.GetPropertyValue<string>(Constants.BuildDirectoryProperty, default);
|
||||||
|
tcmProperties[Constants.BuildFlavorProperty] = testCase.GetPropertyValue<string>(Constants.BuildFlavorProperty, default);
|
||||||
|
tcmProperties[Constants.BuildNumberProperty] = testCase.GetPropertyValue<string>(Constants.BuildNumberProperty, default);
|
||||||
|
tcmProperties[Constants.BuildPlatformProperty] = testCase.GetPropertyValue<string>(Constants.BuildPlatformProperty, default);
|
||||||
|
tcmProperties[Constants.BuildUriProperty] = testCase.GetPropertyValue<string>(Constants.BuildUriProperty, default);
|
||||||
|
tcmProperties[Constants.TfsServerCollectionUrlProperty] = testCase.GetPropertyValue<string>(Constants.TfsServerCollectionUrlProperty, default);
|
||||||
|
tcmProperties[Constants.TfsTeamProjectProperty] = testCase.GetPropertyValue<string>(Constants.TfsTeamProjectProperty, default);
|
||||||
|
tcmProperties[Constants.IsInLabEnvironmentProperty] = testCase.GetPropertyValue<bool>(Constants.IsInLabEnvironmentProperty, default);
|
||||||
|
|
||||||
|
// Step 2: Add test case specific properties.
|
||||||
|
tcmProperties[Constants.TestCaseIdProperty] = testCase.GetPropertyValue<int>(Constants.TestCaseIdProperty, default);
|
||||||
|
tcmProperties[Constants.TestConfigurationIdProperty] = testCase.GetPropertyValue<int>(Constants.TestConfigurationIdProperty, default);
|
||||||
|
tcmProperties[Constants.TestConfigurationNameProperty] = testCase.GetPropertyValue<string>(Constants.TestConfigurationNameProperty, default);
|
||||||
|
tcmProperties[Constants.TestPointIdProperty] = testCase.GetPropertyValue<int>(Constants.TestPointIdProperty, default);
|
||||||
|
|
||||||
|
return tcmProperties;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,242 +1,241 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Extensions;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
using ObjectModel;
|
||||||
|
|
||||||
|
using UnitTestOutcome = Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel.UnitTestOutcome;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines TestAssembly Info object
|
||||||
|
/// </summary>
|
||||||
|
public class TestAssemblyInfo
|
||||||
{
|
{
|
||||||
using System;
|
private MethodInfo assemblyCleanupMethod;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
using Extensions;
|
private MethodInfo assemblyInitializeMethod;
|
||||||
|
private readonly object assemblyInfoExecuteSyncObject;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
|
|
||||||
using ObjectModel;
|
|
||||||
|
|
||||||
using UnitTestOutcome = Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel.UnitTestOutcome;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines TestAssembly Info object
|
/// Initializes a new instance of the <see cref="TestAssemblyInfo"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TestAssemblyInfo
|
/// <param name="assembly">Sets the <see cref="Assembly"/> this class is representing. </param>
|
||||||
|
internal TestAssemblyInfo(Assembly assembly)
|
||||||
{
|
{
|
||||||
private MethodInfo assemblyCleanupMethod;
|
this.assemblyInfoExecuteSyncObject = new object();
|
||||||
|
this.Assembly = assembly;
|
||||||
|
}
|
||||||
|
|
||||||
private MethodInfo assemblyInitializeMethod;
|
/// <summary>
|
||||||
private readonly object assemblyInfoExecuteSyncObject;
|
/// Gets <c>AssemblyInitialize</c> method for the assembly.
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
public MethodInfo AssemblyInitializeMethod
|
||||||
/// Initializes a new instance of the <see cref="TestAssemblyInfo"/> class.
|
{
|
||||||
/// </summary>
|
get
|
||||||
/// <param name="assembly">Sets the <see cref="Assembly"/> this class is representing. </param>
|
|
||||||
internal TestAssemblyInfo(Assembly assembly)
|
|
||||||
{
|
{
|
||||||
this.assemblyInfoExecuteSyncObject = new object();
|
return this.assemblyInitializeMethod;
|
||||||
this.Assembly = assembly;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
internal set
|
||||||
/// Gets <c>AssemblyInitialize</c> method for the assembly.
|
|
||||||
/// </summary>
|
|
||||||
public MethodInfo AssemblyInitializeMethod
|
|
||||||
{
|
{
|
||||||
get
|
if (this.assemblyInitializeMethod != null)
|
||||||
{
|
{
|
||||||
return this.assemblyInitializeMethod;
|
var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorMultiAssemblyInit, this.assemblyInitializeMethod.DeclaringType.FullName);
|
||||||
|
throw new TypeInspectionException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal set
|
this.assemblyInitializeMethod = value;
|
||||||
{
|
}
|
||||||
if (this.assemblyInitializeMethod != null)
|
}
|
||||||
{
|
|
||||||
var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorMultiAssemblyInit, this.assemblyInitializeMethod.DeclaringType.FullName);
|
|
||||||
throw new TypeInspectionException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.assemblyInitializeMethod = value;
|
/// <summary>
|
||||||
}
|
/// Gets <c>AssemblyCleanup</c> method for the assembly.
|
||||||
|
/// </summary>
|
||||||
|
public MethodInfo AssemblyCleanupMethod
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.assemblyCleanupMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
internal set
|
||||||
/// Gets <c>AssemblyCleanup</c> method for the assembly.
|
|
||||||
/// </summary>
|
|
||||||
public MethodInfo AssemblyCleanupMethod
|
|
||||||
{
|
{
|
||||||
get
|
if (this.assemblyCleanupMethod != null)
|
||||||
{
|
{
|
||||||
return this.assemblyCleanupMethod;
|
string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorMultiAssemblyClean, this.assemblyCleanupMethod.DeclaringType.FullName);
|
||||||
|
throw new TypeInspectionException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal set
|
this.assemblyCleanupMethod = value;
|
||||||
{
|
|
||||||
if (this.assemblyCleanupMethod != null)
|
|
||||||
{
|
|
||||||
string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorMultiAssemblyClean, this.assemblyCleanupMethod.DeclaringType.FullName);
|
|
||||||
throw new TypeInspectionException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.assemblyCleanupMethod = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether <c>AssemblyInitialize</c> has been executed.
|
/// Gets a value indicating whether <c>AssemblyInitialize</c> has been executed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsAssemblyInitializeExecuted { get; internal set; }
|
public bool IsAssemblyInitializeExecuted { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the assembly initialization exception.
|
/// Gets the assembly initialization exception.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Exception AssemblyInitializationException { get; internal set; }
|
public Exception AssemblyInitializationException { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this assembly has an executable <c>AssemblyCleanup</c> method.
|
/// Gets a value indicating whether this assembly has an executable <c>AssemblyCleanup</c> method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HasExecutableCleanupMethod
|
public bool HasExecutableCleanupMethod
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
|
||||||
// If no assembly cleanup, then continue with the next one.
|
|
||||||
if (this.AssemblyCleanupMethod == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the <see cref="Assembly"/> this class represents.
|
|
||||||
/// </summary>
|
|
||||||
internal Assembly Assembly { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Runs assembly initialize method.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="testContext"> The test context. </param>
|
|
||||||
/// <exception cref="TestFailedException"> Throws a test failed exception if the initialization method throws an exception. </exception>
|
|
||||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
|
||||||
public void RunAssemblyInitialize(TestContext testContext)
|
|
||||||
{
|
|
||||||
// No assembly initialize => nothing to do.
|
|
||||||
if (this.AssemblyInitializeMethod == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (testContext == null)
|
|
||||||
{
|
|
||||||
throw new NullReferenceException(Resource.TestContextIsNull);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If assembly initialization is not done, then do it.
|
|
||||||
if (!this.IsAssemblyInitializeExecuted)
|
|
||||||
{
|
|
||||||
// Acquiring a lock is usually a costly operation which does not need to be
|
|
||||||
// performed every time if the assembly init is already executed.
|
|
||||||
lock (this.assemblyInfoExecuteSyncObject)
|
|
||||||
{
|
|
||||||
// Perform a check again.
|
|
||||||
if (!this.IsAssemblyInitializeExecuted)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.AssemblyInitializeMethod.InvokeAsSynchronousTask(null, testContext);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
this.AssemblyInitializationException = ex;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
this.IsAssemblyInitializeExecuted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If assemblyInitialization was successful, then don't do anything
|
|
||||||
if (this.AssemblyInitializationException == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache and return an already created TestFailedException.
|
|
||||||
if (this.AssemblyInitializationException is TestFailedException)
|
|
||||||
{
|
|
||||||
throw this.AssemblyInitializationException;
|
|
||||||
}
|
|
||||||
|
|
||||||
var realException = this.AssemblyInitializationException.InnerException ?? this.AssemblyInitializationException;
|
|
||||||
|
|
||||||
var outcome = realException is AssertInconclusiveException ? UnitTestOutcome.Inconclusive : UnitTestOutcome.Failed;
|
|
||||||
|
|
||||||
// Do not use StackTraceHelper.GetExceptionMessage(realException) as it prefixes the message with the exception type name.
|
|
||||||
var exceptionMessage = realException.TryGetMessage();
|
|
||||||
var errorMessage = string.Format(
|
|
||||||
CultureInfo.CurrentCulture,
|
|
||||||
Resource.UTA_AssemblyInitMethodThrows,
|
|
||||||
this.AssemblyInitializeMethod.DeclaringType.FullName,
|
|
||||||
this.AssemblyInitializeMethod.Name,
|
|
||||||
realException.GetType().ToString(),
|
|
||||||
exceptionMessage);
|
|
||||||
var exceptionStackTraceInfo = StackTraceHelper.GetStackTraceInformation(realException);
|
|
||||||
|
|
||||||
var testFailedException = new TestFailedException(outcome, errorMessage, exceptionStackTraceInfo, realException);
|
|
||||||
this.AssemblyInitializationException = testFailedException;
|
|
||||||
|
|
||||||
throw testFailedException;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Run assembly cleanup methods
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// Any exception that can be thrown as part of a assembly cleanup as warning messages.
|
|
||||||
/// </returns>
|
|
||||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
|
||||||
public string RunAssemblyCleanup()
|
|
||||||
{
|
{
|
||||||
|
// If no assembly cleanup, then continue with the next one.
|
||||||
if (this.AssemblyCleanupMethod == null)
|
if (this.AssemblyCleanupMethod == null)
|
||||||
{
|
{
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="Assembly"/> this class represents.
|
||||||
|
/// </summary>
|
||||||
|
internal Assembly Assembly { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs assembly initialize method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="testContext"> The test context. </param>
|
||||||
|
/// <exception cref="TestFailedException"> Throws a test failed exception if the initialization method throws an exception. </exception>
|
||||||
|
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
||||||
|
public void RunAssemblyInitialize(TestContext testContext)
|
||||||
|
{
|
||||||
|
// No assembly initialize => nothing to do.
|
||||||
|
if (this.AssemblyInitializeMethod == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (testContext == null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException(Resource.TestContextIsNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If assembly initialization is not done, then do it.
|
||||||
|
if (!this.IsAssemblyInitializeExecuted)
|
||||||
|
{
|
||||||
|
// Acquiring a lock is usually a costly operation which does not need to be
|
||||||
|
// performed every time if the assembly init is already executed.
|
||||||
lock (this.assemblyInfoExecuteSyncObject)
|
lock (this.assemblyInfoExecuteSyncObject)
|
||||||
{
|
{
|
||||||
try
|
// Perform a check again.
|
||||||
|
if (!this.IsAssemblyInitializeExecuted)
|
||||||
{
|
{
|
||||||
this.AssemblyCleanupMethod.InvokeAsSynchronousTask(null);
|
try
|
||||||
|
{
|
||||||
return null;
|
this.AssemblyInitializeMethod.InvokeAsSynchronousTask(null, testContext);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.AssemblyInitializationException = ex;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.IsAssemblyInitializeExecuted = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If assemblyInitialization was successful, then don't do anything
|
||||||
|
if (this.AssemblyInitializationException == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache and return an already created TestFailedException.
|
||||||
|
if (this.AssemblyInitializationException is TestFailedException)
|
||||||
|
{
|
||||||
|
throw this.AssemblyInitializationException;
|
||||||
|
}
|
||||||
|
|
||||||
|
var realException = this.AssemblyInitializationException.InnerException ?? this.AssemblyInitializationException;
|
||||||
|
|
||||||
|
var outcome = realException is AssertInconclusiveException ? UnitTestOutcome.Inconclusive : UnitTestOutcome.Failed;
|
||||||
|
|
||||||
|
// Do not use StackTraceHelper.GetExceptionMessage(realException) as it prefixes the message with the exception type name.
|
||||||
|
var exceptionMessage = realException.TryGetMessage();
|
||||||
|
var errorMessage = string.Format(
|
||||||
|
CultureInfo.CurrentCulture,
|
||||||
|
Resource.UTA_AssemblyInitMethodThrows,
|
||||||
|
this.AssemblyInitializeMethod.DeclaringType.FullName,
|
||||||
|
this.AssemblyInitializeMethod.Name,
|
||||||
|
realException.GetType().ToString(),
|
||||||
|
exceptionMessage);
|
||||||
|
var exceptionStackTraceInfo = StackTraceHelper.GetStackTraceInformation(realException);
|
||||||
|
|
||||||
|
var testFailedException = new TestFailedException(outcome, errorMessage, exceptionStackTraceInfo, realException);
|
||||||
|
this.AssemblyInitializationException = testFailedException;
|
||||||
|
|
||||||
|
throw testFailedException;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run assembly cleanup methods
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// Any exception that can be thrown as part of a assembly cleanup as warning messages.
|
||||||
|
/// </returns>
|
||||||
|
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
||||||
|
public string RunAssemblyCleanup()
|
||||||
|
{
|
||||||
|
if (this.AssemblyCleanupMethod == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (this.assemblyInfoExecuteSyncObject)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.AssemblyCleanupMethod.InvokeAsSynchronousTask(null);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var realException = ex.InnerException ?? ex;
|
||||||
|
|
||||||
|
string errorMessage;
|
||||||
|
|
||||||
|
// special case AssertFailedException to trim off part of the stack trace
|
||||||
|
if (realException is AssertFailedException ||
|
||||||
|
realException is AssertInconclusiveException)
|
||||||
{
|
{
|
||||||
var realException = ex.InnerException ?? ex;
|
errorMessage = realException.Message;
|
||||||
|
|
||||||
string errorMessage;
|
|
||||||
|
|
||||||
// special case AssertFailedException to trim off part of the stack trace
|
|
||||||
if (realException is AssertFailedException ||
|
|
||||||
realException is AssertInconclusiveException)
|
|
||||||
{
|
|
||||||
errorMessage = realException.Message;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
errorMessage = StackTraceHelper.GetExceptionMessage(realException);
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Format(
|
|
||||||
CultureInfo.CurrentCulture,
|
|
||||||
Resource.UTA_AssemblyCleanupMethodWasUnsuccesful,
|
|
||||||
this.AssemblyCleanupMethod.DeclaringType.Name,
|
|
||||||
this.AssemblyCleanupMethod.Name,
|
|
||||||
errorMessage,
|
|
||||||
StackTraceHelper.GetStackTraceInformation(realException)?.ErrorStackTrace);
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
errorMessage = StackTraceHelper.GetExceptionMessage(realException);
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Format(
|
||||||
|
CultureInfo.CurrentCulture,
|
||||||
|
Resource.UTA_AssemblyCleanupMethodWasUnsuccesful,
|
||||||
|
this.AssemblyCleanupMethod.DeclaringType.Name,
|
||||||
|
this.AssemblyCleanupMethod.Name,
|
||||||
|
errorMessage,
|
||||||
|
StackTraceHelper.GetStackTraceInformation(realException)?.ErrorStackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,72 +1,71 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Security;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
|
||||||
|
internal class TestAssemblySettingsProvider : MarshalByRefObject
|
||||||
{
|
{
|
||||||
using System;
|
private readonly ReflectHelper reflectHelper;
|
||||||
using System.Security;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
public TestAssemblySettingsProvider()
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
: this(ReflectHelper.Instance)
|
||||||
|
|
||||||
internal class TestAssemblySettingsProvider : MarshalByRefObject
|
|
||||||
{
|
{
|
||||||
private readonly ReflectHelper reflectHelper;
|
}
|
||||||
|
|
||||||
public TestAssemblySettingsProvider()
|
internal TestAssemblySettingsProvider(ReflectHelper reflectHelper)
|
||||||
: this(ReflectHelper.Instance)
|
{
|
||||||
{
|
this.reflectHelper = reflectHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal TestAssemblySettingsProvider(ReflectHelper reflectHelper)
|
/// <summary>
|
||||||
{
|
/// Returns object to be used for controlling lifetime, null means infinite lifetime.
|
||||||
this.reflectHelper = reflectHelper;
|
/// </summary>
|
||||||
}
|
/// <returns>
|
||||||
|
/// The <see cref="object"/>.
|
||||||
/// <summary>
|
/// </returns>
|
||||||
/// Returns object to be used for controlling lifetime, null means infinite lifetime.
|
[SecurityCritical]
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// The <see cref="object"/>.
|
|
||||||
/// </returns>
|
|
||||||
[SecurityCritical]
|
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
[Obsolete]
|
[Obsolete]
|
||||||
#endif
|
#endif
|
||||||
public override object InitializeLifetimeService()
|
public override object InitializeLifetimeService()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal TestAssemblySettings GetSettings(string source)
|
||||||
|
{
|
||||||
|
var testAssemblySettings = new TestAssemblySettings();
|
||||||
|
|
||||||
|
// Load the source.
|
||||||
|
var testAssembly = PlatformServiceProvider.Instance.FileOperations.LoadAssembly(source, isReflectionOnly: false);
|
||||||
|
|
||||||
|
var parallelizeAttribute = this.reflectHelper.GetParallelizeAttribute(testAssembly);
|
||||||
|
|
||||||
|
if (parallelizeAttribute != null)
|
||||||
{
|
{
|
||||||
return null;
|
testAssemblySettings.Workers = parallelizeAttribute.Workers;
|
||||||
|
testAssemblySettings.Scope = parallelizeAttribute.Scope;
|
||||||
|
|
||||||
|
if (testAssemblySettings.Workers == 0)
|
||||||
|
{
|
||||||
|
testAssemblySettings.Workers = Environment.ProcessorCount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal TestAssemblySettings GetSettings(string source)
|
testAssemblySettings.CanParallelizeAssembly = !this.reflectHelper.IsDoNotParallelizeSet(testAssembly);
|
||||||
|
|
||||||
|
var classCleanupSequencingAttribute = this.reflectHelper.GetClassCleanupAttribute(testAssembly);
|
||||||
|
if (classCleanupSequencingAttribute != null)
|
||||||
{
|
{
|
||||||
var testAssemblySettings = new TestAssemblySettings();
|
testAssemblySettings.ClassCleanupLifecycle = classCleanupSequencingAttribute.CleanupBehavior;
|
||||||
|
|
||||||
// Load the source.
|
|
||||||
var testAssembly = PlatformServiceProvider.Instance.FileOperations.LoadAssembly(source, isReflectionOnly: false);
|
|
||||||
|
|
||||||
var parallelizeAttribute = this.reflectHelper.GetParallelizeAttribute(testAssembly);
|
|
||||||
|
|
||||||
if (parallelizeAttribute != null)
|
|
||||||
{
|
|
||||||
testAssemblySettings.Workers = parallelizeAttribute.Workers;
|
|
||||||
testAssemblySettings.Scope = parallelizeAttribute.Scope;
|
|
||||||
|
|
||||||
if (testAssemblySettings.Workers == 0)
|
|
||||||
{
|
|
||||||
testAssemblySettings.Workers = Environment.ProcessorCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
testAssemblySettings.CanParallelizeAssembly = !this.reflectHelper.IsDoNotParallelizeSet(testAssembly);
|
|
||||||
|
|
||||||
var classCleanupSequencingAttribute = this.reflectHelper.GetClassCleanupAttribute(testAssembly);
|
|
||||||
if (classCleanupSequencingAttribute != null)
|
|
||||||
{
|
|
||||||
testAssemblySettings.ClassCleanupLifecycle = classCleanupSequencingAttribute.CleanupBehavior;
|
|
||||||
}
|
|
||||||
|
|
||||||
return testAssemblySettings;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return testAssemblySettings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,41 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution;
|
||||||
{
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
using System.Collections.Generic;
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The test case discovery sink.
|
||||||
|
/// </summary>
|
||||||
|
internal class TestCaseDiscoverySink : ITestCaseDiscoverySink
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TestCaseDiscoverySink"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public TestCaseDiscoverySink()
|
||||||
|
{
|
||||||
|
this.Tests = new Collection<TestCase>();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The test case discovery sink.
|
/// Gets the tests.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class TestCaseDiscoverySink : ITestCaseDiscoverySink
|
public ICollection<TestCase> Tests { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends the test case.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="discoveredTest"> The discovered test. </param>
|
||||||
|
public void SendTestCase(TestCase discoveredTest)
|
||||||
{
|
{
|
||||||
/// <summary>
|
if (discoveredTest != null)
|
||||||
/// Initializes a new instance of the <see cref="TestCaseDiscoverySink"/> class.
|
|
||||||
/// </summary>
|
|
||||||
public TestCaseDiscoverySink()
|
|
||||||
{
|
{
|
||||||
this.Tests = new Collection<TestCase>();
|
this.Tests.Add(discoveredTest);
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the tests.
|
|
||||||
/// </summary>
|
|
||||||
public ICollection<TestCase> Tests { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends the test case.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="discoveredTest"> The discovered test. </param>
|
|
||||||
public void SendTestCase(TestCase discoveredTest)
|
|
||||||
{
|
|
||||||
if (discoveredTest != null)
|
|
||||||
{
|
|
||||||
this.Tests.Add(discoveredTest);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,429 +1,428 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Extensions;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using ObjectModel;
|
||||||
|
|
||||||
|
using ObjectModelUnitTestOutcome = ObjectModel.UnitTestOutcome;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the TestClassInfo object
|
||||||
|
/// </summary>
|
||||||
|
public class TestClassInfo
|
||||||
{
|
{
|
||||||
using System;
|
private readonly object testClassExecuteSyncObject;
|
||||||
using System.Collections.Generic;
|
private MethodInfo classCleanupMethod;
|
||||||
using System.Diagnostics;
|
private MethodInfo classInitializeMethod;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
private MethodInfo testCleanupMethod;
|
||||||
using System.Globalization;
|
private MethodInfo testInitializeMethod;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
using Extensions;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using ObjectModel;
|
|
||||||
|
|
||||||
using ObjectModelUnitTestOutcome = ObjectModel.UnitTestOutcome;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the TestClassInfo object
|
/// Initializes a new instance of the <see cref="TestClassInfo"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TestClassInfo
|
/// <param name="type">Underlying test class type.</param>
|
||||||
|
/// <param name="constructor">Constructor for the test class.</param>
|
||||||
|
/// <param name="testContextProperty">Reference to the <see cref="TestContext"/> property in test class.</param>
|
||||||
|
/// <param name="classAttribute">Test class attribute.</param>
|
||||||
|
/// <param name="parent">Parent assembly info.</param>
|
||||||
|
internal TestClassInfo(
|
||||||
|
Type type,
|
||||||
|
ConstructorInfo constructor,
|
||||||
|
PropertyInfo testContextProperty,
|
||||||
|
TestClassAttribute classAttribute,
|
||||||
|
TestAssemblyInfo parent)
|
||||||
{
|
{
|
||||||
private readonly object testClassExecuteSyncObject;
|
Debug.Assert(type != null, "Type should not be null");
|
||||||
private MethodInfo classCleanupMethod;
|
Debug.Assert(constructor != null, "Constructor should not be null");
|
||||||
private MethodInfo classInitializeMethod;
|
Debug.Assert(parent != null, "Parent should not be null");
|
||||||
private MethodInfo testCleanupMethod;
|
Debug.Assert(classAttribute != null, "ClassAtribute should not be null");
|
||||||
private MethodInfo testInitializeMethod;
|
|
||||||
|
|
||||||
/// <summary>
|
this.ClassType = type;
|
||||||
/// Initializes a new instance of the <see cref="TestClassInfo"/> class.
|
this.Constructor = constructor;
|
||||||
/// </summary>
|
this.TestContextProperty = testContextProperty;
|
||||||
/// <param name="type">Underlying test class type.</param>
|
this.BaseClassCleanupMethodsStack = new Stack<MethodInfo>();
|
||||||
/// <param name="constructor">Constructor for the test class.</param>
|
this.BaseClassInitAndCleanupMethods = new Queue<Tuple<MethodInfo, MethodInfo>>();
|
||||||
/// <param name="testContextProperty">Reference to the <see cref="TestContext"/> property in test class.</param>
|
this.BaseTestInitializeMethodsQueue = new Queue<MethodInfo>();
|
||||||
/// <param name="classAttribute">Test class attribute.</param>
|
this.BaseTestCleanupMethodsQueue = new Queue<MethodInfo>();
|
||||||
/// <param name="parent">Parent assembly info.</param>
|
this.Parent = parent;
|
||||||
internal TestClassInfo(
|
this.ClassAttribute = classAttribute;
|
||||||
Type type,
|
this.testClassExecuteSyncObject = new object();
|
||||||
ConstructorInfo constructor,
|
}
|
||||||
PropertyInfo testContextProperty,
|
|
||||||
TestClassAttribute classAttribute,
|
/// <summary>
|
||||||
TestAssemblyInfo parent)
|
/// Gets the class attribute.
|
||||||
|
/// </summary>
|
||||||
|
public TestClassAttribute ClassAttribute { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the class type.
|
||||||
|
/// </summary>
|
||||||
|
public Type ClassType { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the constructor.
|
||||||
|
/// </summary>
|
||||||
|
public ConstructorInfo Constructor { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the test context property.
|
||||||
|
/// </summary>
|
||||||
|
public PropertyInfo TestContextProperty { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the parent <see cref="TestAssemblyInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
public TestAssemblyInfo Parent { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the class initialize method.
|
||||||
|
/// </summary>
|
||||||
|
public MethodInfo ClassInitializeMethod
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
Debug.Assert(type != null, "Type should not be null");
|
return this.classInitializeMethod;
|
||||||
Debug.Assert(constructor != null, "Constructor should not be null");
|
|
||||||
Debug.Assert(parent != null, "Parent should not be null");
|
|
||||||
Debug.Assert(classAttribute != null, "ClassAtribute should not be null");
|
|
||||||
|
|
||||||
this.ClassType = type;
|
|
||||||
this.Constructor = constructor;
|
|
||||||
this.TestContextProperty = testContextProperty;
|
|
||||||
this.BaseClassCleanupMethodsStack = new Stack<MethodInfo>();
|
|
||||||
this.BaseClassInitAndCleanupMethods = new Queue<Tuple<MethodInfo, MethodInfo>>();
|
|
||||||
this.BaseTestInitializeMethodsQueue = new Queue<MethodInfo>();
|
|
||||||
this.BaseTestCleanupMethodsQueue = new Queue<MethodInfo>();
|
|
||||||
this.Parent = parent;
|
|
||||||
this.ClassAttribute = classAttribute;
|
|
||||||
this.testClassExecuteSyncObject = new object();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
internal set
|
||||||
/// Gets the class attribute.
|
|
||||||
/// </summary>
|
|
||||||
public TestClassAttribute ClassAttribute { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the class type.
|
|
||||||
/// </summary>
|
|
||||||
public Type ClassType { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the constructor.
|
|
||||||
/// </summary>
|
|
||||||
public ConstructorInfo Constructor { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the test context property.
|
|
||||||
/// </summary>
|
|
||||||
public PropertyInfo TestContextProperty { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the parent <see cref="TestAssemblyInfo"/>.
|
|
||||||
/// </summary>
|
|
||||||
public TestAssemblyInfo Parent { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the class initialize method.
|
|
||||||
/// </summary>
|
|
||||||
public MethodInfo ClassInitializeMethod
|
|
||||||
{
|
{
|
||||||
get
|
if (this.classInitializeMethod != null)
|
||||||
{
|
{
|
||||||
return this.classInitializeMethod;
|
var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorMultiClassInit, this.ClassType.FullName);
|
||||||
|
throw new TypeInspectionException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal set
|
this.classInitializeMethod = value;
|
||||||
{
|
|
||||||
if (this.classInitializeMethod != null)
|
|
||||||
{
|
|
||||||
var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorMultiClassInit, this.ClassType.FullName);
|
|
||||||
throw new TypeInspectionException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.classInitializeMethod = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether class initialize has executed.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsClassInitializeExecuted { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether class cleanup has executed.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsClassCleanupExecuted { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a stack of class cleanup methods to be executed.
|
|
||||||
/// </summary>
|
|
||||||
public Stack<MethodInfo> BaseClassCleanupMethodsStack { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the exception thrown during <see cref="ClassInitializeAttribute"/> method invocation.
|
|
||||||
/// </summary>
|
|
||||||
public Exception ClassInitializationException { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the exception thrown during <see cref="ClassCleanupAttribute"/> method invocation.
|
|
||||||
/// </summary>
|
|
||||||
public Exception ClassCleanupException { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the class cleanup method.
|
|
||||||
/// </summary>
|
|
||||||
public MethodInfo ClassCleanupMethod
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.classCleanupMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal set
|
|
||||||
{
|
|
||||||
if (this.classCleanupMethod != null)
|
|
||||||
{
|
|
||||||
var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorMultiClassClean, this.ClassType.FullName);
|
|
||||||
throw new TypeInspectionException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.classCleanupMethod = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this class info has a executable cleanup method.
|
|
||||||
/// </summary>
|
|
||||||
public bool HasExecutableCleanupMethod
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (this.BaseClassCleanupMethodsStack.Any())
|
|
||||||
{
|
|
||||||
// If any base cleanups were pushed to the stack we need to run them
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no class cleanup, then continue with the next one.
|
|
||||||
if (this.ClassCleanupMethod == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a tuples' queue of class initialize/cleanup methods to call for this type.
|
|
||||||
/// </summary>
|
|
||||||
public Queue<Tuple<MethodInfo, MethodInfo>> BaseClassInitAndCleanupMethods { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the test initialize method.
|
|
||||||
/// </summary>
|
|
||||||
public MethodInfo TestInitializeMethod
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.testInitializeMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal set
|
|
||||||
{
|
|
||||||
if (this.testInitializeMethod != null)
|
|
||||||
{
|
|
||||||
var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorMultiInit, this.ClassType.FullName);
|
|
||||||
throw new TypeInspectionException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.testInitializeMethod = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the test cleanup method.
|
|
||||||
/// </summary>
|
|
||||||
public MethodInfo TestCleanupMethod
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.testCleanupMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal set
|
|
||||||
{
|
|
||||||
if (this.testCleanupMethod != null)
|
|
||||||
{
|
|
||||||
var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorMultiClean, this.ClassType.FullName);
|
|
||||||
throw new TypeInspectionException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.testCleanupMethod = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a queue of test initialize methods to call for this type.
|
|
||||||
/// </summary>
|
|
||||||
public Queue<MethodInfo> BaseTestInitializeMethodsQueue { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a queue of test cleanup methods to call for this type.
|
|
||||||
/// </summary>
|
|
||||||
public Queue<MethodInfo> BaseTestCleanupMethodsQueue { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Runs the class initialize method.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="testContext"> The test context. </param>
|
|
||||||
/// <exception cref="TestFailedException"> Throws a test failed exception if the initialization method throws an exception. </exception>
|
|
||||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
|
||||||
public void RunClassInitialize(TestContext testContext)
|
|
||||||
{
|
|
||||||
// If no class initialize and no base class initialize, return
|
|
||||||
if (this.ClassInitializeMethod is null && !this.BaseClassInitAndCleanupMethods.Any(p => p.Item1 != null))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (testContext == null)
|
|
||||||
{
|
|
||||||
throw new NullReferenceException(Resource.TestContextIsNull);
|
|
||||||
}
|
|
||||||
|
|
||||||
MethodInfo initializeMethod = null;
|
|
||||||
string failedClassInitializeMethodName = string.Empty;
|
|
||||||
|
|
||||||
// If class initialization is not done, then do it.
|
|
||||||
if (!this.IsClassInitializeExecuted)
|
|
||||||
{
|
|
||||||
// Acquiring a lock is usually a costly operation which does not need to be
|
|
||||||
// performed every time if the class init is already executed.
|
|
||||||
lock (this.testClassExecuteSyncObject)
|
|
||||||
{
|
|
||||||
// Perform a check again.
|
|
||||||
if (!this.IsClassInitializeExecuted)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// ClassInitialize methods for base classes are called in reverse order of discovery
|
|
||||||
// Base -> Child TestClass
|
|
||||||
var baseClassInitializeStack = new Stack<Tuple<MethodInfo, MethodInfo>>(
|
|
||||||
this.BaseClassInitAndCleanupMethods.Where(p => p.Item1 != null));
|
|
||||||
|
|
||||||
while (baseClassInitializeStack.Count > 0)
|
|
||||||
{
|
|
||||||
var baseInitCleanupMethods = baseClassInitializeStack.Pop();
|
|
||||||
initializeMethod = baseInitCleanupMethods.Item1;
|
|
||||||
initializeMethod?.InvokeAsSynchronousTask(null, testContext);
|
|
||||||
|
|
||||||
if (baseInitCleanupMethods.Item2 != null)
|
|
||||||
{
|
|
||||||
this.BaseClassCleanupMethodsStack.Push(baseInitCleanupMethods.Item2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeMethod = null;
|
|
||||||
|
|
||||||
if (this.classInitializeMethod != null)
|
|
||||||
{
|
|
||||||
this.ClassInitializeMethod.InvokeAsSynchronousTask(null, testContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
this.ClassInitializationException = ex;
|
|
||||||
failedClassInitializeMethodName = initializeMethod?.Name ?? this.ClassInitializeMethod.Name;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
this.IsClassInitializeExecuted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If classInitialization was successful, then don't do anything
|
|
||||||
if (this.ClassInitializationException == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.ClassInitializationException is TestFailedException)
|
|
||||||
{
|
|
||||||
throw this.ClassInitializationException;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fail the current test if it was a failure.
|
|
||||||
var realException = this.ClassInitializationException.InnerException ?? this.ClassInitializationException;
|
|
||||||
|
|
||||||
var outcome = realException is AssertInconclusiveException ? ObjectModelUnitTestOutcome.Inconclusive : ObjectModelUnitTestOutcome.Failed;
|
|
||||||
|
|
||||||
// Do not use StackTraceHelper.GetExceptionMessage(realException) as it prefixes the message with the exception type name.
|
|
||||||
var exceptionMessage = realException.TryGetMessage();
|
|
||||||
var errorMessage = string.Format(
|
|
||||||
CultureInfo.CurrentCulture,
|
|
||||||
Resource.UTA_ClassInitMethodThrows,
|
|
||||||
this.ClassType.FullName,
|
|
||||||
failedClassInitializeMethodName,
|
|
||||||
realException.GetType().ToString(),
|
|
||||||
exceptionMessage);
|
|
||||||
var exceptionStackTraceInfo = StackTraceHelper.GetStackTraceInformation(realException);
|
|
||||||
|
|
||||||
var testFailedException = new TestFailedException(outcome, errorMessage, exceptionStackTraceInfo, realException);
|
|
||||||
this.ClassInitializationException = testFailedException;
|
|
||||||
|
|
||||||
throw testFailedException;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Run class cleanup methods
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="classCleanupLifecycle">The current lifecyle position that ClassCleanup is executing from</param>
|
|
||||||
/// <returns>
|
|
||||||
/// Any exception that can be thrown as part of a class cleanup as warning messages.
|
|
||||||
/// </returns>
|
|
||||||
public string RunClassCleanup(ClassCleanupBehavior classCleanupLifecycle = ClassCleanupBehavior.EndOfAssembly)
|
|
||||||
{
|
|
||||||
if (this.ClassCleanupMethod is null && this.BaseClassInitAndCleanupMethods.All(p => p.Item2 == null))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.IsClassCleanupExecuted)
|
|
||||||
{
|
|
||||||
lock (this.testClassExecuteSyncObject)
|
|
||||||
{
|
|
||||||
if (this.IsClassCleanupExecuted)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.IsClassInitializeExecuted || this.ClassInitializeMethod is null)
|
|
||||||
{
|
|
||||||
MethodInfo classCleanupMethod = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
classCleanupMethod = this.ClassCleanupMethod;
|
|
||||||
classCleanupMethod?.InvokeAsSynchronousTask(null);
|
|
||||||
var baseClassCleanupQueue = new Queue<MethodInfo>(this.BaseClassCleanupMethodsStack);
|
|
||||||
while (baseClassCleanupQueue.Count > 0)
|
|
||||||
{
|
|
||||||
classCleanupMethod = baseClassCleanupQueue.Dequeue();
|
|
||||||
classCleanupMethod?.InvokeAsSynchronousTask(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.IsClassCleanupExecuted = true;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
var realException = exception.InnerException ?? exception;
|
|
||||||
this.ClassCleanupException = realException;
|
|
||||||
|
|
||||||
string errorMessage;
|
|
||||||
|
|
||||||
// special case AssertFailedException to trim off part of the stack trace
|
|
||||||
if (realException is AssertFailedException ||
|
|
||||||
realException is AssertInconclusiveException)
|
|
||||||
{
|
|
||||||
errorMessage = realException.Message;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
errorMessage = StackTraceHelper.GetExceptionMessage(realException);
|
|
||||||
}
|
|
||||||
|
|
||||||
var exceptionStackTraceInfo = realException.TryGetStackTraceInformation();
|
|
||||||
|
|
||||||
errorMessage = string.Format(
|
|
||||||
CultureInfo.CurrentCulture,
|
|
||||||
Resource.UTA_ClassCleanupMethodWasUnsuccesful,
|
|
||||||
classCleanupMethod.DeclaringType.Name,
|
|
||||||
classCleanupMethod.Name,
|
|
||||||
errorMessage,
|
|
||||||
exceptionStackTraceInfo?.ErrorStackTrace);
|
|
||||||
|
|
||||||
if (classCleanupLifecycle == ClassCleanupBehavior.EndOfClass)
|
|
||||||
{
|
|
||||||
var testFailedException = new TestFailedException(ObjectModelUnitTestOutcome.Failed, errorMessage, exceptionStackTraceInfo);
|
|
||||||
this.ClassCleanupException = testFailedException;
|
|
||||||
throw testFailedException;
|
|
||||||
}
|
|
||||||
|
|
||||||
return errorMessage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether class initialize has executed.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsClassInitializeExecuted { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether class cleanup has executed.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsClassCleanupExecuted { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a stack of class cleanup methods to be executed.
|
||||||
|
/// </summary>
|
||||||
|
public Stack<MethodInfo> BaseClassCleanupMethodsStack { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the exception thrown during <see cref="ClassInitializeAttribute"/> method invocation.
|
||||||
|
/// </summary>
|
||||||
|
public Exception ClassInitializationException { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the exception thrown during <see cref="ClassCleanupAttribute"/> method invocation.
|
||||||
|
/// </summary>
|
||||||
|
public Exception ClassCleanupException { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the class cleanup method.
|
||||||
|
/// </summary>
|
||||||
|
public MethodInfo ClassCleanupMethod
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.classCleanupMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal set
|
||||||
|
{
|
||||||
|
if (this.classCleanupMethod != null)
|
||||||
|
{
|
||||||
|
var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorMultiClassClean, this.ClassType.FullName);
|
||||||
|
throw new TypeInspectionException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.classCleanupMethod = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this class info has a executable cleanup method.
|
||||||
|
/// </summary>
|
||||||
|
public bool HasExecutableCleanupMethod
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (this.BaseClassCleanupMethodsStack.Any())
|
||||||
|
{
|
||||||
|
// If any base cleanups were pushed to the stack we need to run them
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no class cleanup, then continue with the next one.
|
||||||
|
if (this.ClassCleanupMethod == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a tuples' queue of class initialize/cleanup methods to call for this type.
|
||||||
|
/// </summary>
|
||||||
|
public Queue<Tuple<MethodInfo, MethodInfo>> BaseClassInitAndCleanupMethods { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the test initialize method.
|
||||||
|
/// </summary>
|
||||||
|
public MethodInfo TestInitializeMethod
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.testInitializeMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal set
|
||||||
|
{
|
||||||
|
if (this.testInitializeMethod != null)
|
||||||
|
{
|
||||||
|
var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorMultiInit, this.ClassType.FullName);
|
||||||
|
throw new TypeInspectionException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.testInitializeMethod = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the test cleanup method.
|
||||||
|
/// </summary>
|
||||||
|
public MethodInfo TestCleanupMethod
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.testCleanupMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal set
|
||||||
|
{
|
||||||
|
if (this.testCleanupMethod != null)
|
||||||
|
{
|
||||||
|
var message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorMultiClean, this.ClassType.FullName);
|
||||||
|
throw new TypeInspectionException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.testCleanupMethod = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a queue of test initialize methods to call for this type.
|
||||||
|
/// </summary>
|
||||||
|
public Queue<MethodInfo> BaseTestInitializeMethodsQueue { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a queue of test cleanup methods to call for this type.
|
||||||
|
/// </summary>
|
||||||
|
public Queue<MethodInfo> BaseTestCleanupMethodsQueue { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the class initialize method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="testContext"> The test context. </param>
|
||||||
|
/// <exception cref="TestFailedException"> Throws a test failed exception if the initialization method throws an exception. </exception>
|
||||||
|
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
||||||
|
public void RunClassInitialize(TestContext testContext)
|
||||||
|
{
|
||||||
|
// If no class initialize and no base class initialize, return
|
||||||
|
if (this.ClassInitializeMethod is null && !this.BaseClassInitAndCleanupMethods.Any(p => p.Item1 != null))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (testContext == null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException(Resource.TestContextIsNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodInfo initializeMethod = null;
|
||||||
|
string failedClassInitializeMethodName = string.Empty;
|
||||||
|
|
||||||
|
// If class initialization is not done, then do it.
|
||||||
|
if (!this.IsClassInitializeExecuted)
|
||||||
|
{
|
||||||
|
// Acquiring a lock is usually a costly operation which does not need to be
|
||||||
|
// performed every time if the class init is already executed.
|
||||||
|
lock (this.testClassExecuteSyncObject)
|
||||||
|
{
|
||||||
|
// Perform a check again.
|
||||||
|
if (!this.IsClassInitializeExecuted)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// ClassInitialize methods for base classes are called in reverse order of discovery
|
||||||
|
// Base -> Child TestClass
|
||||||
|
var baseClassInitializeStack = new Stack<Tuple<MethodInfo, MethodInfo>>(
|
||||||
|
this.BaseClassInitAndCleanupMethods.Where(p => p.Item1 != null));
|
||||||
|
|
||||||
|
while (baseClassInitializeStack.Count > 0)
|
||||||
|
{
|
||||||
|
var baseInitCleanupMethods = baseClassInitializeStack.Pop();
|
||||||
|
initializeMethod = baseInitCleanupMethods.Item1;
|
||||||
|
initializeMethod?.InvokeAsSynchronousTask(null, testContext);
|
||||||
|
|
||||||
|
if (baseInitCleanupMethods.Item2 != null)
|
||||||
|
{
|
||||||
|
this.BaseClassCleanupMethodsStack.Push(baseInitCleanupMethods.Item2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeMethod = null;
|
||||||
|
|
||||||
|
if (this.classInitializeMethod != null)
|
||||||
|
{
|
||||||
|
this.ClassInitializeMethod.InvokeAsSynchronousTask(null, testContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.ClassInitializationException = ex;
|
||||||
|
failedClassInitializeMethodName = initializeMethod?.Name ?? this.ClassInitializeMethod.Name;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.IsClassInitializeExecuted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If classInitialization was successful, then don't do anything
|
||||||
|
if (this.ClassInitializationException == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.ClassInitializationException is TestFailedException)
|
||||||
|
{
|
||||||
|
throw this.ClassInitializationException;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail the current test if it was a failure.
|
||||||
|
var realException = this.ClassInitializationException.InnerException ?? this.ClassInitializationException;
|
||||||
|
|
||||||
|
var outcome = realException is AssertInconclusiveException ? ObjectModelUnitTestOutcome.Inconclusive : ObjectModelUnitTestOutcome.Failed;
|
||||||
|
|
||||||
|
// Do not use StackTraceHelper.GetExceptionMessage(realException) as it prefixes the message with the exception type name.
|
||||||
|
var exceptionMessage = realException.TryGetMessage();
|
||||||
|
var errorMessage = string.Format(
|
||||||
|
CultureInfo.CurrentCulture,
|
||||||
|
Resource.UTA_ClassInitMethodThrows,
|
||||||
|
this.ClassType.FullName,
|
||||||
|
failedClassInitializeMethodName,
|
||||||
|
realException.GetType().ToString(),
|
||||||
|
exceptionMessage);
|
||||||
|
var exceptionStackTraceInfo = StackTraceHelper.GetStackTraceInformation(realException);
|
||||||
|
|
||||||
|
var testFailedException = new TestFailedException(outcome, errorMessage, exceptionStackTraceInfo, realException);
|
||||||
|
this.ClassInitializationException = testFailedException;
|
||||||
|
|
||||||
|
throw testFailedException;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run class cleanup methods
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="classCleanupLifecycle">The current lifecyle position that ClassCleanup is executing from</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Any exception that can be thrown as part of a class cleanup as warning messages.
|
||||||
|
/// </returns>
|
||||||
|
public string RunClassCleanup(ClassCleanupBehavior classCleanupLifecycle = ClassCleanupBehavior.EndOfAssembly)
|
||||||
|
{
|
||||||
|
if (this.ClassCleanupMethod is null && this.BaseClassInitAndCleanupMethods.All(p => p.Item2 == null))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.IsClassCleanupExecuted)
|
||||||
|
{
|
||||||
|
lock (this.testClassExecuteSyncObject)
|
||||||
|
{
|
||||||
|
if (this.IsClassCleanupExecuted)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.IsClassInitializeExecuted || this.ClassInitializeMethod is null)
|
||||||
|
{
|
||||||
|
MethodInfo classCleanupMethod = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
classCleanupMethod = this.ClassCleanupMethod;
|
||||||
|
classCleanupMethod?.InvokeAsSynchronousTask(null);
|
||||||
|
var baseClassCleanupQueue = new Queue<MethodInfo>(this.BaseClassCleanupMethodsStack);
|
||||||
|
while (baseClassCleanupQueue.Count > 0)
|
||||||
|
{
|
||||||
|
classCleanupMethod = baseClassCleanupQueue.Dequeue();
|
||||||
|
classCleanupMethod?.InvokeAsSynchronousTask(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.IsClassCleanupExecuted = true;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
var realException = exception.InnerException ?? exception;
|
||||||
|
this.ClassCleanupException = realException;
|
||||||
|
|
||||||
|
string errorMessage;
|
||||||
|
|
||||||
|
// special case AssertFailedException to trim off part of the stack trace
|
||||||
|
if (realException is AssertFailedException ||
|
||||||
|
realException is AssertInconclusiveException)
|
||||||
|
{
|
||||||
|
errorMessage = realException.Message;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
errorMessage = StackTraceHelper.GetExceptionMessage(realException);
|
||||||
|
}
|
||||||
|
|
||||||
|
var exceptionStackTraceInfo = realException.TryGetStackTraceInformation();
|
||||||
|
|
||||||
|
errorMessage = string.Format(
|
||||||
|
CultureInfo.CurrentCulture,
|
||||||
|
Resource.UTA_ClassCleanupMethodWasUnsuccesful,
|
||||||
|
classCleanupMethod.DeclaringType.Name,
|
||||||
|
classCleanupMethod.Name,
|
||||||
|
errorMessage,
|
||||||
|
exceptionStackTraceInfo?.ErrorStackTrace);
|
||||||
|
|
||||||
|
if (classCleanupLifecycle == ClassCleanupBehavior.EndOfClass)
|
||||||
|
{
|
||||||
|
var testFailedException = new TestFailedException(ObjectModelUnitTestOutcome.Failed, errorMessage, exceptionStackTraceInfo);
|
||||||
|
this.ClassCleanupException = testFailedException;
|
||||||
|
throw testFailedException;
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,517 +1,516 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution;
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
|
using System;
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
using System.Collections.Concurrent;
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
using System.Collections.Generic;
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
using System.Diagnostics;
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
|
using System.Globalization;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class responsible for execution of tests at assembly level and sending tests via framework handle
|
||||||
|
/// </summary>
|
||||||
|
public class TestExecutionManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies whether the test run is canceled or not
|
||||||
|
/// </summary>
|
||||||
|
private TestRunCancellationToken cancellationToken;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class responsible for execution of tests at assembly level and sending tests via framework handle
|
/// Dictionary for test run parameters
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TestExecutionManager
|
private readonly IDictionary<string, object> sessionParameters;
|
||||||
|
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Need to over-write the keys in dictionary.")]
|
||||||
|
public TestExecutionManager()
|
||||||
{
|
{
|
||||||
/// <summary>
|
this.TestMethodFilter = new TestMethodFilter();
|
||||||
/// Specifies whether the test run is canceled or not
|
this.sessionParameters = new Dictionary<string, object>();
|
||||||
/// </summary>
|
}
|
||||||
private TestRunCancellationToken cancellationToken;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dictionary for test run parameters
|
/// Gets or sets method filter for filtering tests
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly IDictionary<string, object> sessionParameters;
|
private TestMethodFilter TestMethodFilter { get; set; }
|
||||||
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Need to over-write the keys in dictionary.")]
|
/// <summary>
|
||||||
public TestExecutionManager()
|
/// Gets or sets a value indicating whether any test executed has failed.
|
||||||
|
/// </summary>
|
||||||
|
private bool HasAnyTestFailed { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the tests.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tests">Tests to be run.</param>
|
||||||
|
/// <param name="runContext">Context to use when executing the tests.</param>
|
||||||
|
/// <param name="frameworkHandle">Handle to the framework to record results and to do framework operations.</param>
|
||||||
|
/// <param name="runCancellationToken">Test run cancellation token</param>
|
||||||
|
public void RunTests(IEnumerable<TestCase> tests, IRunContext runContext, IFrameworkHandle frameworkHandle, TestRunCancellationToken runCancellationToken)
|
||||||
|
{
|
||||||
|
Debug.Assert(tests != null, "tests");
|
||||||
|
Debug.Assert(runContext != null, "runContext");
|
||||||
|
Debug.Assert(frameworkHandle != null, "frameworkHandle");
|
||||||
|
Debug.Assert(runCancellationToken != null, "runCancellationToken");
|
||||||
|
|
||||||
|
this.cancellationToken = runCancellationToken;
|
||||||
|
|
||||||
|
var isDeploymentDone = PlatformServiceProvider.Instance.TestDeployment.Deploy(tests, runContext, frameworkHandle);
|
||||||
|
|
||||||
|
// Placing this after deployment since we need information post deployment that we pass in as properties.
|
||||||
|
this.CacheSessionParameters(runContext, frameworkHandle);
|
||||||
|
|
||||||
|
// Execute the tests
|
||||||
|
this.ExecuteTests(tests, runContext, frameworkHandle, isDeploymentDone);
|
||||||
|
|
||||||
|
if (!this.HasAnyTestFailed)
|
||||||
{
|
{
|
||||||
this.TestMethodFilter = new TestMethodFilter();
|
PlatformServiceProvider.Instance.TestDeployment.Cleanup();
|
||||||
this.sessionParameters = new Dictionary<string, object>();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RunTests(IEnumerable<string> sources, IRunContext runContext, IFrameworkHandle frameworkHandle, TestRunCancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
this.cancellationToken = cancellationToken;
|
||||||
|
|
||||||
|
var discoverySink = new TestCaseDiscoverySink();
|
||||||
|
|
||||||
|
var tests = new List<TestCase>();
|
||||||
|
|
||||||
|
// deploy everything first.
|
||||||
|
foreach (var source in sources)
|
||||||
|
{
|
||||||
|
if (this.cancellationToken.Canceled)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var logger = (IMessageLogger)frameworkHandle;
|
||||||
|
|
||||||
|
// discover the tests
|
||||||
|
this.GetUnitTestDiscoverer().DiscoverTestsInSource(source, logger, discoverySink, runContext);
|
||||||
|
tests.AddRange(discoverySink.Tests);
|
||||||
|
|
||||||
|
// Clear discoverSinksTests so that it just stores test for one source at one point of time
|
||||||
|
discoverySink.Tests.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
bool isDeploymentDone = PlatformServiceProvider.Instance.TestDeployment.Deploy(tests, runContext, frameworkHandle);
|
||||||
/// Gets or sets method filter for filtering tests
|
|
||||||
/// </summary>
|
|
||||||
private TestMethodFilter TestMethodFilter { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
// Placing this after deployment since we need information post deployment that we pass in as properties.
|
||||||
/// Gets or sets a value indicating whether any test executed has failed.
|
this.CacheSessionParameters(runContext, frameworkHandle);
|
||||||
/// </summary>
|
|
||||||
private bool HasAnyTestFailed { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
// Run tests.
|
||||||
/// Runs the tests.
|
this.ExecuteTests(tests, runContext, frameworkHandle, isDeploymentDone);
|
||||||
/// </summary>
|
|
||||||
/// <param name="tests">Tests to be run.</param>
|
if (!this.HasAnyTestFailed)
|
||||||
/// <param name="runContext">Context to use when executing the tests.</param>
|
|
||||||
/// <param name="frameworkHandle">Handle to the framework to record results and to do framework operations.</param>
|
|
||||||
/// <param name="runCancellationToken">Test run cancellation token</param>
|
|
||||||
public void RunTests(IEnumerable<TestCase> tests, IRunContext runContext, IFrameworkHandle frameworkHandle, TestRunCancellationToken runCancellationToken)
|
|
||||||
{
|
{
|
||||||
Debug.Assert(tests != null, "tests");
|
PlatformServiceProvider.Instance.TestDeployment.Cleanup();
|
||||||
Debug.Assert(runContext != null, "runContext");
|
}
|
||||||
Debug.Assert(frameworkHandle != null, "frameworkHandle");
|
}
|
||||||
Debug.Assert(runCancellationToken != null, "runCancellationToken");
|
|
||||||
|
|
||||||
this.cancellationToken = runCancellationToken;
|
/// <summary>
|
||||||
|
/// Execute the parameter tests
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tests">Tests to execute.</param>
|
||||||
|
/// <param name="runContext">The run context.</param>
|
||||||
|
/// <param name="frameworkHandle">Handle to record test start/end/results.</param>
|
||||||
|
/// <param name="isDeploymentDone">Indicates if deployment is done.</param>
|
||||||
|
internal virtual void ExecuteTests(IEnumerable<TestCase> tests, IRunContext runContext, IFrameworkHandle frameworkHandle, bool isDeploymentDone)
|
||||||
|
{
|
||||||
|
var testsBySource = from test in tests
|
||||||
|
group test by test.Source into testGroup
|
||||||
|
select new { Source = testGroup.Key, Tests = testGroup };
|
||||||
|
|
||||||
var isDeploymentDone = PlatformServiceProvider.Instance.TestDeployment.Deploy(tests, runContext, frameworkHandle);
|
foreach (var group in testsBySource)
|
||||||
|
{
|
||||||
|
this.ExecuteTestsInSource(group.Tests, runContext, frameworkHandle, group.Source, isDeploymentDone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Placing this after deployment since we need information post deployment that we pass in as properties.
|
internal virtual UnitTestDiscoverer GetUnitTestDiscoverer()
|
||||||
this.CacheSessionParameters(runContext, frameworkHandle);
|
{
|
||||||
|
return new UnitTestDiscoverer();
|
||||||
|
}
|
||||||
|
|
||||||
// Execute the tests
|
internal void SendTestResults(TestCase test, UnitTestResult[] unitTestResults, DateTimeOffset startTime, DateTimeOffset endTime, ITestExecutionRecorder testExecutionRecorder)
|
||||||
this.ExecuteTests(tests, runContext, frameworkHandle, isDeploymentDone);
|
{
|
||||||
|
if (!(unitTestResults?.Length > 0))
|
||||||
if (!this.HasAnyTestFailed)
|
{
|
||||||
{
|
return;
|
||||||
PlatformServiceProvider.Instance.TestDeployment.Cleanup();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RunTests(IEnumerable<string> sources, IRunContext runContext, IFrameworkHandle frameworkHandle, TestRunCancellationToken cancellationToken)
|
foreach (var unitTestResult in unitTestResults)
|
||||||
{
|
{
|
||||||
this.cancellationToken = cancellationToken;
|
if (test == null)
|
||||||
|
|
||||||
var discoverySink = new TestCaseDiscoverySink();
|
|
||||||
|
|
||||||
var tests = new List<TestCase>();
|
|
||||||
|
|
||||||
// deploy everything first.
|
|
||||||
foreach (var source in sources)
|
|
||||||
{
|
{
|
||||||
if (this.cancellationToken.Canceled)
|
continue;
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var logger = (IMessageLogger)frameworkHandle;
|
|
||||||
|
|
||||||
// discover the tests
|
|
||||||
this.GetUnitTestDiscoverer().DiscoverTestsInSource(source, logger, discoverySink, runContext);
|
|
||||||
tests.AddRange(discoverySink.Tests);
|
|
||||||
|
|
||||||
// Clear discoverSinksTests so that it just stores test for one source at one point of time
|
|
||||||
discoverySink.Tests.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isDeploymentDone = PlatformServiceProvider.Instance.TestDeployment.Deploy(tests, runContext, frameworkHandle);
|
var testResult = unitTestResult.ToTestResult(test, startTime, endTime, MSTestSettings.CurrentSettings);
|
||||||
|
|
||||||
// Placing this after deployment since we need information post deployment that we pass in as properties.
|
if (unitTestResult.DatarowIndex >= 0)
|
||||||
this.CacheSessionParameters(runContext, frameworkHandle);
|
|
||||||
|
|
||||||
// Run tests.
|
|
||||||
this.ExecuteTests(tests, runContext, frameworkHandle, isDeploymentDone);
|
|
||||||
|
|
||||||
if (!this.HasAnyTestFailed)
|
|
||||||
{
|
{
|
||||||
PlatformServiceProvider.Instance.TestDeployment.Cleanup();
|
testResult.DisplayName = string.Format(CultureInfo.CurrentCulture, Resource.DataDrivenResultDisplayName, test.DisplayName, unitTestResult.DatarowIndex);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Execute the parameter tests
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tests">Tests to execute.</param>
|
|
||||||
/// <param name="runContext">The run context.</param>
|
|
||||||
/// <param name="frameworkHandle">Handle to record test start/end/results.</param>
|
|
||||||
/// <param name="isDeploymentDone">Indicates if deployment is done.</param>
|
|
||||||
internal virtual void ExecuteTests(IEnumerable<TestCase> tests, IRunContext runContext, IFrameworkHandle frameworkHandle, bool isDeploymentDone)
|
|
||||||
{
|
|
||||||
var testsBySource = from test in tests
|
|
||||||
group test by test.Source into testGroup
|
|
||||||
select new { Source = testGroup.Key, Tests = testGroup };
|
|
||||||
|
|
||||||
foreach (var group in testsBySource)
|
|
||||||
{
|
|
||||||
this.ExecuteTestsInSource(group.Tests, runContext, frameworkHandle, group.Source, isDeploymentDone);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal virtual UnitTestDiscoverer GetUnitTestDiscoverer()
|
|
||||||
{
|
|
||||||
return new UnitTestDiscoverer();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void SendTestResults(TestCase test, UnitTestResult[] unitTestResults, DateTimeOffset startTime, DateTimeOffset endTime, ITestExecutionRecorder testExecutionRecorder)
|
|
||||||
{
|
|
||||||
if (!(unitTestResults?.Length > 0))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var unitTestResult in unitTestResults)
|
testExecutionRecorder.RecordEnd(test, testResult.Outcome);
|
||||||
|
|
||||||
|
if (testResult.Outcome == TestOutcome.Failed)
|
||||||
{
|
{
|
||||||
if (test == null)
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("MSTestExecutor:Test {0} failed. ErrorMessage:{1}, ErrorStackTrace:{2}.", testResult.TestCase.FullyQualifiedName, testResult.ErrorMessage, testResult.ErrorStackTrace);
|
||||||
{
|
this.HasAnyTestFailed = true;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var testResult = unitTestResult.ToTestResult(test, startTime, endTime, MSTestSettings.CurrentSettings);
|
|
||||||
|
|
||||||
if (unitTestResult.DatarowIndex >= 0)
|
|
||||||
{
|
|
||||||
testResult.DisplayName = string.Format(CultureInfo.CurrentCulture, Resource.DataDrivenResultDisplayName, test.DisplayName, unitTestResult.DatarowIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
testExecutionRecorder.RecordEnd(test, testResult.Outcome);
|
|
||||||
|
|
||||||
if (testResult.Outcome == TestOutcome.Failed)
|
|
||||||
{
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("MSTestExecutor:Test {0} failed. ErrorMessage:{1}, ErrorStackTrace:{2}.", testResult.TestCase.FullyQualifiedName, testResult.ErrorMessage, testResult.ErrorStackTrace);
|
|
||||||
this.HasAnyTestFailed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
testExecutionRecorder.RecordResult(testResult);
|
|
||||||
}
|
|
||||||
catch (TestCanceledException)
|
|
||||||
{
|
|
||||||
// Ignore this exception
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static bool MatchTestFilter(ITestCaseFilterExpression filterExpression, TestCase test, TestMethodFilter testMethodFilter)
|
|
||||||
{
|
|
||||||
if (filterExpression != null && filterExpression.MatchTestCase(test, p => testMethodFilter.PropertyValueProvider(test, p)) == false)
|
|
||||||
{
|
|
||||||
// Skip test if not fitting filter criteria.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Execute the parameter tests present in parameter source
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tests">Tests to execute.</param>
|
|
||||||
/// <param name="runContext">The run context.</param>
|
|
||||||
/// <param name="frameworkHandle">Handle to record test start/end/results.</param>
|
|
||||||
/// <param name="source">The test container for the tests.</param>
|
|
||||||
/// <param name="isDeploymentDone">Indicates if deployment is done.</param>
|
|
||||||
private void ExecuteTestsInSource(IEnumerable<TestCase> tests, IRunContext runContext, IFrameworkHandle frameworkHandle, string source, bool isDeploymentDone)
|
|
||||||
{
|
|
||||||
Debug.Assert(!string.IsNullOrEmpty(source), "Source cannot be empty");
|
|
||||||
|
|
||||||
if (isDeploymentDone)
|
|
||||||
{
|
|
||||||
source = Path.Combine(PlatformServiceProvider.Instance.TestDeployment.GetDeploymentDirectory(), Path.GetFileName(source));
|
|
||||||
}
|
|
||||||
|
|
||||||
using var isolationHost = PlatformServiceProvider.Instance.CreateTestSourceHost(source, runContext?.RunSettings, frameworkHandle);
|
|
||||||
// Create an instance of a type defined in adapter so that adapter gets loaded in the child app domain
|
|
||||||
var testRunner = isolationHost.CreateInstanceForType(
|
|
||||||
typeof(UnitTestRunner),
|
|
||||||
new object[] { MSTestSettings.CurrentSettings }) as UnitTestRunner;
|
|
||||||
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Created unit-test runner {0}", source);
|
|
||||||
|
|
||||||
// Default test set is filtered tests based on user provided filter criteria
|
|
||||||
ICollection<TestCase> testsToRun = new TestCase[0];
|
|
||||||
var filterExpression = this.TestMethodFilter.GetFilterExpression(runContext, frameworkHandle, out var filterHasError);
|
|
||||||
if (filterHasError)
|
|
||||||
{
|
|
||||||
// Bail out without processing everything else below.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
testsToRun = tests.Where(t => MatchTestFilter(filterExpression, t, this.TestMethodFilter)).ToArray();
|
|
||||||
|
|
||||||
// this is done so that appropriate values of test context properties are set at source level
|
|
||||||
// and are merged with session level parameters
|
|
||||||
var sourceLevelParameters = PlatformServiceProvider.Instance.SettingsProvider.GetProperties(source);
|
|
||||||
|
|
||||||
if (this.sessionParameters != null && this.sessionParameters.Count > 0)
|
|
||||||
{
|
|
||||||
sourceLevelParameters = this.sessionParameters.ConcatWithOverwrites(sourceLevelParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
TestAssemblySettingsProvider sourceSettingsProvider = null;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
sourceSettingsProvider = isolationHost.CreateInstanceForType(
|
testExecutionRecorder.RecordResult(testResult);
|
||||||
typeof(TestAssemblySettingsProvider),
|
|
||||||
null) as TestAssemblySettingsProvider;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (TestCanceledException)
|
||||||
{
|
{
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Could not create TestAssemblySettingsProvider instance in child app-domain", ex);
|
// Ignore this exception
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var sourceSettings = (sourceSettingsProvider != null) ? sourceSettingsProvider.GetSettings(source) : new TestAssemblySettings();
|
private static bool MatchTestFilter(ITestCaseFilterExpression filterExpression, TestCase test, TestMethodFilter testMethodFilter)
|
||||||
var parallelWorkers = sourceSettings.Workers;
|
{
|
||||||
var parallelScope = sourceSettings.Scope;
|
if (filterExpression != null && filterExpression.MatchTestCase(test, p => testMethodFilter.PropertyValueProvider(test, p)) == false)
|
||||||
this.InitializeClassCleanupManager(source, testRunner, testsToRun, sourceSettings);
|
{
|
||||||
|
// Skip test if not fitting filter criteria.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (MSTestSettings.CurrentSettings.ParallelizationWorkers.HasValue)
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Execute the parameter tests present in parameter source
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tests">Tests to execute.</param>
|
||||||
|
/// <param name="runContext">The run context.</param>
|
||||||
|
/// <param name="frameworkHandle">Handle to record test start/end/results.</param>
|
||||||
|
/// <param name="source">The test container for the tests.</param>
|
||||||
|
/// <param name="isDeploymentDone">Indicates if deployment is done.</param>
|
||||||
|
private void ExecuteTestsInSource(IEnumerable<TestCase> tests, IRunContext runContext, IFrameworkHandle frameworkHandle, string source, bool isDeploymentDone)
|
||||||
|
{
|
||||||
|
Debug.Assert(!string.IsNullOrEmpty(source), "Source cannot be empty");
|
||||||
|
|
||||||
|
if (isDeploymentDone)
|
||||||
|
{
|
||||||
|
source = Path.Combine(PlatformServiceProvider.Instance.TestDeployment.GetDeploymentDirectory(), Path.GetFileName(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
using var isolationHost = PlatformServiceProvider.Instance.CreateTestSourceHost(source, runContext?.RunSettings, frameworkHandle);
|
||||||
|
// Create an instance of a type defined in adapter so that adapter gets loaded in the child app domain
|
||||||
|
var testRunner = isolationHost.CreateInstanceForType(
|
||||||
|
typeof(UnitTestRunner),
|
||||||
|
new object[] { MSTestSettings.CurrentSettings }) as UnitTestRunner;
|
||||||
|
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Created unit-test runner {0}", source);
|
||||||
|
|
||||||
|
// Default test set is filtered tests based on user provided filter criteria
|
||||||
|
ICollection<TestCase> testsToRun = new TestCase[0];
|
||||||
|
var filterExpression = this.TestMethodFilter.GetFilterExpression(runContext, frameworkHandle, out var filterHasError);
|
||||||
|
if (filterHasError)
|
||||||
|
{
|
||||||
|
// Bail out without processing everything else below.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
testsToRun = tests.Where(t => MatchTestFilter(filterExpression, t, this.TestMethodFilter)).ToArray();
|
||||||
|
|
||||||
|
// this is done so that appropriate values of test context properties are set at source level
|
||||||
|
// and are merged with session level parameters
|
||||||
|
var sourceLevelParameters = PlatformServiceProvider.Instance.SettingsProvider.GetProperties(source);
|
||||||
|
|
||||||
|
if (this.sessionParameters != null && this.sessionParameters.Count > 0)
|
||||||
|
{
|
||||||
|
sourceLevelParameters = this.sessionParameters.ConcatWithOverwrites(sourceLevelParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
TestAssemblySettingsProvider sourceSettingsProvider = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sourceSettingsProvider = isolationHost.CreateInstanceForType(
|
||||||
|
typeof(TestAssemblySettingsProvider),
|
||||||
|
null) as TestAssemblySettingsProvider;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Could not create TestAssemblySettingsProvider instance in child app-domain", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sourceSettings = (sourceSettingsProvider != null) ? sourceSettingsProvider.GetSettings(source) : new TestAssemblySettings();
|
||||||
|
var parallelWorkers = sourceSettings.Workers;
|
||||||
|
var parallelScope = sourceSettings.Scope;
|
||||||
|
this.InitializeClassCleanupManager(source, testRunner, testsToRun, sourceSettings);
|
||||||
|
|
||||||
|
if (MSTestSettings.CurrentSettings.ParallelizationWorkers.HasValue)
|
||||||
|
{
|
||||||
|
// The runsettings value takes precedence over an assembly level setting. Reset the level.
|
||||||
|
parallelWorkers = MSTestSettings.CurrentSettings.ParallelizationWorkers.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MSTestSettings.CurrentSettings.ParallelizationScope.HasValue)
|
||||||
|
{
|
||||||
|
// The runsettings value takes precedence over an assembly level setting. Reset the level.
|
||||||
|
parallelScope = MSTestSettings.CurrentSettings.ParallelizationScope.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!MSTestSettings.CurrentSettings.DisableParallelization && sourceSettings.CanParallelizeAssembly && parallelWorkers > 0)
|
||||||
|
{
|
||||||
|
// Parallelization is enabled. Let's do further classification for sets.
|
||||||
|
var logger = (IMessageLogger)frameworkHandle;
|
||||||
|
logger.SendMessage(
|
||||||
|
TestMessageLevel.Informational,
|
||||||
|
string.Format(CultureInfo.CurrentCulture, Resource.TestParallelizationBanner, source, parallelWorkers, parallelScope));
|
||||||
|
|
||||||
|
// Create test sets for execution, we can execute them in parallel based on parallel settings
|
||||||
|
IEnumerable<IGrouping<bool, TestCase>> testsets = Enumerable.Empty<IGrouping<bool, TestCase>>();
|
||||||
|
|
||||||
|
// Parallel and not parallel sets.
|
||||||
|
testsets = testsToRun.GroupBy(t => t.GetPropertyValue<bool>(TestAdapter.Constants.DoNotParallelizeProperty, false));
|
||||||
|
|
||||||
|
var parallelizableTestSet = testsets.FirstOrDefault(g => g.Key == false);
|
||||||
|
var nonparallelizableTestSet = testsets.FirstOrDefault(g => g.Key == true);
|
||||||
|
|
||||||
|
if (parallelizableTestSet != null)
|
||||||
{
|
{
|
||||||
// The runsettings value takes precedence over an assembly level setting. Reset the level.
|
ConcurrentQueue<IEnumerable<TestCase>> queue = null;
|
||||||
parallelWorkers = MSTestSettings.CurrentSettings.ParallelizationWorkers.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MSTestSettings.CurrentSettings.ParallelizationScope.HasValue)
|
// Chunk the sets into further groups based on parallel level
|
||||||
{
|
switch (parallelScope)
|
||||||
// The runsettings value takes precedence over an assembly level setting. Reset the level.
|
|
||||||
parallelScope = MSTestSettings.CurrentSettings.ParallelizationScope.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!MSTestSettings.CurrentSettings.DisableParallelization && sourceSettings.CanParallelizeAssembly && parallelWorkers > 0)
|
|
||||||
{
|
|
||||||
// Parallelization is enabled. Let's do further classification for sets.
|
|
||||||
var logger = (IMessageLogger)frameworkHandle;
|
|
||||||
logger.SendMessage(
|
|
||||||
TestMessageLevel.Informational,
|
|
||||||
string.Format(CultureInfo.CurrentCulture, Resource.TestParallelizationBanner, source, parallelWorkers, parallelScope));
|
|
||||||
|
|
||||||
// Create test sets for execution, we can execute them in parallel based on parallel settings
|
|
||||||
IEnumerable<IGrouping<bool, TestCase>> testsets = Enumerable.Empty<IGrouping<bool, TestCase>>();
|
|
||||||
|
|
||||||
// Parallel and not parallel sets.
|
|
||||||
testsets = testsToRun.GroupBy(t => t.GetPropertyValue<bool>(TestAdapter.Constants.DoNotParallelizeProperty, false));
|
|
||||||
|
|
||||||
var parallelizableTestSet = testsets.FirstOrDefault(g => g.Key == false);
|
|
||||||
var nonparallelizableTestSet = testsets.FirstOrDefault(g => g.Key == true);
|
|
||||||
|
|
||||||
if (parallelizableTestSet != null)
|
|
||||||
{
|
{
|
||||||
ConcurrentQueue<IEnumerable<TestCase>> queue = null;
|
case ExecutionScope.MethodLevel:
|
||||||
|
queue = new ConcurrentQueue<IEnumerable<TestCase>>(parallelizableTestSet.Select(t => new[] { t }));
|
||||||
|
break;
|
||||||
|
|
||||||
// Chunk the sets into further groups based on parallel level
|
case ExecutionScope.ClassLevel:
|
||||||
switch (parallelScope)
|
queue = new ConcurrentQueue<IEnumerable<TestCase>>(parallelizableTestSet.GroupBy(t => t.GetPropertyValue(TestAdapter.Constants.TestClassNameProperty) as string));
|
||||||
{
|
break;
|
||||||
case ExecutionScope.MethodLevel:
|
|
||||||
queue = new ConcurrentQueue<IEnumerable<TestCase>>(parallelizableTestSet.Select(t => new[] { t }));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ExecutionScope.ClassLevel:
|
|
||||||
queue = new ConcurrentQueue<IEnumerable<TestCase>>(parallelizableTestSet.GroupBy(t => t.GetPropertyValue(TestAdapter.Constants.TestClassNameProperty) as string));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var tasks = new List<Task>();
|
|
||||||
|
|
||||||
for (int i = 0; i < parallelWorkers; i++)
|
|
||||||
{
|
|
||||||
tasks.Add(Task.Factory.StartNew(
|
|
||||||
() =>
|
|
||||||
{
|
|
||||||
while (!queue.IsEmpty)
|
|
||||||
{
|
|
||||||
if (this.cancellationToken != null && this.cancellationToken.Canceled)
|
|
||||||
{
|
|
||||||
// if a cancellation has been requested, do not queue any more test runs.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (queue.TryDequeue(out IEnumerable<TestCase> testSet))
|
|
||||||
{
|
|
||||||
this.ExecuteTestsWithTestRunner(testSet, runContext, frameworkHandle, source, sourceLevelParameters, testRunner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
CancellationToken.None,
|
|
||||||
TaskCreationOptions.LongRunning,
|
|
||||||
TaskScheduler.Default));
|
|
||||||
}
|
|
||||||
|
|
||||||
Task.WaitAll(tasks.ToArray());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Queue the non parallel set
|
var tasks = new List<Task>();
|
||||||
if (nonparallelizableTestSet != null)
|
|
||||||
|
for (int i = 0; i < parallelWorkers; i++)
|
||||||
{
|
{
|
||||||
this.ExecuteTestsWithTestRunner(nonparallelizableTestSet, runContext, frameworkHandle, source, sourceLevelParameters, testRunner);
|
tasks.Add(Task.Factory.StartNew(
|
||||||
}
|
() =>
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.ExecuteTestsWithTestRunner(testsToRun, runContext, frameworkHandle, source, sourceLevelParameters, testRunner);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.RunCleanup(frameworkHandle, testRunner);
|
|
||||||
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Executed tests belonging to source {0}", source);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeClassCleanupManager(string source, UnitTestRunner testRunner, ICollection<TestCase> testsToRun, TestAssemblySettings sourceSettings)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var unitTestElements = testsToRun.Select(e => e.ToUnitTestElement(source)).ToArray();
|
|
||||||
testRunner.InitializeClassCleanupManager(unitTestElements, (int)sourceSettings.ClassCleanupLifecycle);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// source might not support this if it's legacy make sure it's supported by checking for the type
|
|
||||||
if (ex.GetType().FullName != "System.Runtime.Remoting.RemotingException")
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ExecuteTestsWithTestRunner(
|
|
||||||
IEnumerable<TestCase> tests,
|
|
||||||
IRunContext runContext,
|
|
||||||
ITestExecutionRecorder testExecutionRecorder,
|
|
||||||
string source,
|
|
||||||
IDictionary<string, object> sourceLevelParameters,
|
|
||||||
UnitTestRunner testRunner)
|
|
||||||
{
|
|
||||||
foreach (var currentTest in tests)
|
|
||||||
{
|
|
||||||
if (this.cancellationToken != null && this.cancellationToken.Canceled)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var unitTestElement = currentTest.ToUnitTestElement(source);
|
|
||||||
|
|
||||||
testExecutionRecorder.RecordStart(currentTest);
|
|
||||||
|
|
||||||
var startTime = DateTimeOffset.Now;
|
|
||||||
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Executing test {0}", unitTestElement.TestMethod.Name);
|
|
||||||
|
|
||||||
// Run single test passing test context properties to it.
|
|
||||||
var tcmProperties = TcmTestPropertiesProvider.GetTcmProperties(currentTest);
|
|
||||||
var testContextProperties = this.GetTestContextProperties(tcmProperties, sourceLevelParameters);
|
|
||||||
var unitTestResult = testRunner.RunSingleTest(unitTestElement.TestMethod, testContextProperties);
|
|
||||||
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Executed test {0}", unitTestElement.TestMethod.Name);
|
|
||||||
|
|
||||||
var endTime = DateTimeOffset.Now;
|
|
||||||
|
|
||||||
this.SendTestResults(currentTest, unitTestResult, startTime, endTime, testExecutionRecorder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get test context properties.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tcmProperties">Tcm properties.</param>
|
|
||||||
/// <param name="sourceLevelParameters">Source level parameters.</param>
|
|
||||||
/// <returns>Test context properties.</returns>
|
|
||||||
private IDictionary<string, object> GetTestContextProperties(IDictionary<TestProperty, object> tcmProperties, IDictionary<string, object> sourceLevelParameters)
|
|
||||||
{
|
|
||||||
var testContextProperties = new Dictionary<string, object>();
|
|
||||||
|
|
||||||
// Add tcm properties.
|
|
||||||
foreach (var propertyPair in tcmProperties)
|
|
||||||
{
|
|
||||||
testContextProperties[propertyPair.Key.Id] = propertyPair.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add source level parameters.
|
|
||||||
foreach (var propertyPair in sourceLevelParameters)
|
|
||||||
{
|
|
||||||
testContextProperties[propertyPair.Key] = propertyPair.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return testContextProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RunCleanup(
|
|
||||||
ITestExecutionRecorder testExecutionRecorder,
|
|
||||||
UnitTestRunner testRunner)
|
|
||||||
{
|
|
||||||
// All cleanups (Class and Assembly) run at the end of test execution. Failures in these cleanup
|
|
||||||
// methods will be reported as Warnings.
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Executing cleanup methods.");
|
|
||||||
var cleanupResult = testRunner.RunCleanup();
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Executed cleanup methods.");
|
|
||||||
if (cleanupResult != null)
|
|
||||||
{
|
|
||||||
// Do not attach the standard output and error messages to any test result. It is not
|
|
||||||
// guaranteed that a test method of same class would have run last. We will end up
|
|
||||||
// adding stdout to test method of another class.
|
|
||||||
this.LogCleanupResult(testExecutionRecorder, cleanupResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle errors in user specified run parameters")]
|
|
||||||
private void CacheSessionParameters(IRunContext runContext, ITestExecutionRecorder testExecutionRecorder)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(runContext?.RunSettings?.SettingsXml))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var testRunParameters = RunSettingsUtilities.GetTestRunParameters(runContext.RunSettings.SettingsXml);
|
|
||||||
if (testRunParameters != null)
|
|
||||||
{
|
|
||||||
// Clear sessionParameters to prevent key collisions of test run parameters in case
|
|
||||||
// "Keep Test Execution Engine Alive" is selected in VS.
|
|
||||||
this.sessionParameters.Clear();
|
|
||||||
foreach (var kvp in testRunParameters)
|
|
||||||
{
|
{
|
||||||
this.sessionParameters.Add(kvp);
|
while (!queue.IsEmpty)
|
||||||
}
|
{
|
||||||
}
|
if (this.cancellationToken != null && this.cancellationToken.Canceled)
|
||||||
}
|
{
|
||||||
catch (Exception ex)
|
// if a cancellation has been requested, do not queue any more test runs.
|
||||||
{
|
break;
|
||||||
testExecutionRecorder.SendMessage(TestMessageLevel.Error, ex.Message);
|
}
|
||||||
|
|
||||||
|
if (queue.TryDequeue(out IEnumerable<TestCase> testSet))
|
||||||
|
{
|
||||||
|
this.ExecuteTestsWithTestRunner(testSet, runContext, frameworkHandle, source, sourceLevelParameters, testRunner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CancellationToken.None,
|
||||||
|
TaskCreationOptions.LongRunning,
|
||||||
|
TaskScheduler.Default));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Task.WaitAll(tasks.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queue the non parallel set
|
||||||
|
if (nonparallelizableTestSet != null)
|
||||||
|
{
|
||||||
|
this.ExecuteTestsWithTestRunner(nonparallelizableTestSet, runContext, frameworkHandle, source, sourceLevelParameters, testRunner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
/// <summary>
|
|
||||||
/// Log the parameter warnings on the parameter logger
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="testExecutionRecorder">Handle to record test start/end/results/messages.</param>
|
|
||||||
/// <param name="result">Result of the run operation.</param>
|
|
||||||
private void LogCleanupResult(ITestExecutionRecorder testExecutionRecorder, RunCleanupResult result)
|
|
||||||
{
|
{
|
||||||
Debug.Assert(testExecutionRecorder != null, "Logger should not be null");
|
this.ExecuteTestsWithTestRunner(testsToRun, runContext, frameworkHandle, source, sourceLevelParameters, testRunner);
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(result.StandardOut))
|
this.RunCleanup(frameworkHandle, testRunner);
|
||||||
|
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Executed tests belonging to source {0}", source);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeClassCleanupManager(string source, UnitTestRunner testRunner, ICollection<TestCase> testsToRun, TestAssemblySettings sourceSettings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var unitTestElements = testsToRun.Select(e => e.ToUnitTestElement(source)).ToArray();
|
||||||
|
testRunner.InitializeClassCleanupManager(unitTestElements, (int)sourceSettings.ClassCleanupLifecycle);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// source might not support this if it's legacy make sure it's supported by checking for the type
|
||||||
|
if (ex.GetType().FullName != "System.Runtime.Remoting.RemotingException")
|
||||||
{
|
{
|
||||||
testExecutionRecorder.SendMessage(TestMessageLevel.Informational, result.StandardOut);
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteTestsWithTestRunner(
|
||||||
|
IEnumerable<TestCase> tests,
|
||||||
|
IRunContext runContext,
|
||||||
|
ITestExecutionRecorder testExecutionRecorder,
|
||||||
|
string source,
|
||||||
|
IDictionary<string, object> sourceLevelParameters,
|
||||||
|
UnitTestRunner testRunner)
|
||||||
|
{
|
||||||
|
foreach (var currentTest in tests)
|
||||||
|
{
|
||||||
|
if (this.cancellationToken != null && this.cancellationToken.Canceled)
|
||||||
|
{
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(result.DebugTrace))
|
var unitTestElement = currentTest.ToUnitTestElement(source);
|
||||||
{
|
|
||||||
testExecutionRecorder.SendMessage(TestMessageLevel.Informational, result.DebugTrace);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(result.StandardError))
|
testExecutionRecorder.RecordStart(currentTest);
|
||||||
{
|
|
||||||
testExecutionRecorder.SendMessage(
|
|
||||||
MSTestSettings.CurrentSettings.TreatClassAndAssemblyCleanupWarningsAsErrors ? TestMessageLevel.Error : TestMessageLevel.Warning,
|
|
||||||
result.StandardError);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.Warnings != null)
|
var startTime = DateTimeOffset.Now;
|
||||||
|
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Executing test {0}", unitTestElement.TestMethod.Name);
|
||||||
|
|
||||||
|
// Run single test passing test context properties to it.
|
||||||
|
var tcmProperties = TcmTestPropertiesProvider.GetTcmProperties(currentTest);
|
||||||
|
var testContextProperties = this.GetTestContextProperties(tcmProperties, sourceLevelParameters);
|
||||||
|
var unitTestResult = testRunner.RunSingleTest(unitTestElement.TestMethod, testContextProperties);
|
||||||
|
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Executed test {0}", unitTestElement.TestMethod.Name);
|
||||||
|
|
||||||
|
var endTime = DateTimeOffset.Now;
|
||||||
|
|
||||||
|
this.SendTestResults(currentTest, unitTestResult, startTime, endTime, testExecutionRecorder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get test context properties.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tcmProperties">Tcm properties.</param>
|
||||||
|
/// <param name="sourceLevelParameters">Source level parameters.</param>
|
||||||
|
/// <returns>Test context properties.</returns>
|
||||||
|
private IDictionary<string, object> GetTestContextProperties(IDictionary<TestProperty, object> tcmProperties, IDictionary<string, object> sourceLevelParameters)
|
||||||
|
{
|
||||||
|
var testContextProperties = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
// Add tcm properties.
|
||||||
|
foreach (var propertyPair in tcmProperties)
|
||||||
|
{
|
||||||
|
testContextProperties[propertyPair.Key.Id] = propertyPair.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add source level parameters.
|
||||||
|
foreach (var propertyPair in sourceLevelParameters)
|
||||||
|
{
|
||||||
|
testContextProperties[propertyPair.Key] = propertyPair.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return testContextProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunCleanup(
|
||||||
|
ITestExecutionRecorder testExecutionRecorder,
|
||||||
|
UnitTestRunner testRunner)
|
||||||
|
{
|
||||||
|
// All cleanups (Class and Assembly) run at the end of test execution. Failures in these cleanup
|
||||||
|
// methods will be reported as Warnings.
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Executing cleanup methods.");
|
||||||
|
var cleanupResult = testRunner.RunCleanup();
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Executed cleanup methods.");
|
||||||
|
if (cleanupResult != null)
|
||||||
|
{
|
||||||
|
// Do not attach the standard output and error messages to any test result. It is not
|
||||||
|
// guaranteed that a test method of same class would have run last. We will end up
|
||||||
|
// adding stdout to test method of another class.
|
||||||
|
this.LogCleanupResult(testExecutionRecorder, cleanupResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle errors in user specified run parameters")]
|
||||||
|
private void CacheSessionParameters(IRunContext runContext, ITestExecutionRecorder testExecutionRecorder)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(runContext?.RunSettings?.SettingsXml))
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
foreach (string warning in result.Warnings)
|
var testRunParameters = RunSettingsUtilities.GetTestRunParameters(runContext.RunSettings.SettingsXml);
|
||||||
|
if (testRunParameters != null)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(warning))
|
// Clear sessionParameters to prevent key collisions of test run parameters in case
|
||||||
|
// "Keep Test Execution Engine Alive" is selected in VS.
|
||||||
|
this.sessionParameters.Clear();
|
||||||
|
foreach (var kvp in testRunParameters)
|
||||||
{
|
{
|
||||||
testExecutionRecorder.SendMessage(
|
this.sessionParameters.Add(kvp);
|
||||||
MSTestSettings.CurrentSettings.TreatClassAndAssemblyCleanupWarningsAsErrors ? TestMessageLevel.Error : TestMessageLevel.Warning,
|
|
||||||
warning);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
testExecutionRecorder.SendMessage(TestMessageLevel.Error, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Log the parameter warnings on the parameter logger
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="testExecutionRecorder">Handle to record test start/end/results/messages.</param>
|
||||||
|
/// <param name="result">Result of the run operation.</param>
|
||||||
|
private void LogCleanupResult(ITestExecutionRecorder testExecutionRecorder, RunCleanupResult result)
|
||||||
|
{
|
||||||
|
Debug.Assert(testExecutionRecorder != null, "Logger should not be null");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(result.StandardOut))
|
||||||
|
{
|
||||||
|
testExecutionRecorder.SendMessage(TestMessageLevel.Informational, result.StandardOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(result.DebugTrace))
|
||||||
|
{
|
||||||
|
testExecutionRecorder.SendMessage(TestMessageLevel.Informational, result.DebugTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(result.StandardError))
|
||||||
|
{
|
||||||
|
testExecutionRecorder.SendMessage(
|
||||||
|
MSTestSettings.CurrentSettings.TreatClassAndAssemblyCleanupWarningsAsErrors ? TestMessageLevel.Error : TestMessageLevel.Warning,
|
||||||
|
result.StandardError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.Warnings != null)
|
||||||
|
{
|
||||||
|
foreach (string warning in result.Warnings)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(warning))
|
||||||
|
{
|
||||||
|
testExecutionRecorder.SendMessage(
|
||||||
|
MSTestSettings.CurrentSettings.TreatClassAndAssemblyCleanupWarningsAsErrors ? TestMessageLevel.Error : TestMessageLevel.Warning,
|
||||||
|
warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,451 +1,450 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Extensions;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
|
||||||
|
using UTF = Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class is responsible to running tests and converting framework TestResults to adapter TestResults.
|
||||||
|
/// </summary>
|
||||||
|
internal class TestMethodRunner
|
||||||
{
|
{
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Collections.Generic;
|
/// Test context which needs to be passed to the various methods of the test
|
||||||
using System.Diagnostics;
|
/// </summary>
|
||||||
using System.Diagnostics.CodeAnalysis;
|
private readonly ITestContext testContext;
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
using Extensions;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
|
||||||
|
|
||||||
using UTF = Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class is responsible to running tests and converting framework TestResults to adapter TestResults.
|
/// TestMethod that needs to be executed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class TestMethodRunner
|
private readonly TestMethod test;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TestMethod referred by the above test element
|
||||||
|
/// </summary>
|
||||||
|
private readonly TestMethodInfo testMethodInfo;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies whether debug traces should be captured or not
|
||||||
|
/// </summary>
|
||||||
|
private readonly bool captureDebugTraces;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper for reflection API's.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ReflectHelper reflectHelper;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TestMethodRunner"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="testMethodInfo">
|
||||||
|
/// The test method info.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="testMethod">
|
||||||
|
/// The test method.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="testContext">
|
||||||
|
/// The test context.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="captureDebugTraces">
|
||||||
|
/// The capture debug traces.
|
||||||
|
/// </param>
|
||||||
|
public TestMethodRunner(TestMethodInfo testMethodInfo, TestMethod testMethod, ITestContext testContext, bool captureDebugTraces)
|
||||||
|
: this(testMethodInfo, testMethod, testContext, captureDebugTraces, ReflectHelper.Instance)
|
||||||
{
|
{
|
||||||
/// <summary>
|
}
|
||||||
/// Test context which needs to be passed to the various methods of the test
|
|
||||||
/// </summary>
|
|
||||||
private readonly ITestContext testContext;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// TestMethod that needs to be executed.
|
/// Initializes a new instance of the <see cref="TestMethodRunner"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly TestMethod test;
|
/// <param name="testMethodInfo">
|
||||||
|
/// The test method info.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="testMethod">
|
||||||
|
/// The test method.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="testContext">
|
||||||
|
/// The test context.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="captureDebugTraces">
|
||||||
|
/// The capture debug traces.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="reflectHelper">
|
||||||
|
/// The reflect Helper object.
|
||||||
|
/// </param>
|
||||||
|
public TestMethodRunner(TestMethodInfo testMethodInfo, TestMethod testMethod, ITestContext testContext, bool captureDebugTraces, ReflectHelper reflectHelper)
|
||||||
|
{
|
||||||
|
Debug.Assert(testMethodInfo != null, "testMethodInfo should not be null");
|
||||||
|
Debug.Assert(testMethod != null, "testMethod should not be null");
|
||||||
|
Debug.Assert(testContext != null, "testContext should not be null");
|
||||||
|
|
||||||
/// <summary>
|
this.testMethodInfo = testMethodInfo;
|
||||||
/// TestMethod referred by the above test element
|
this.test = testMethod;
|
||||||
/// </summary>
|
this.testContext = testContext;
|
||||||
private readonly TestMethodInfo testMethodInfo;
|
this.captureDebugTraces = captureDebugTraces;
|
||||||
|
this.reflectHelper = reflectHelper;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specifies whether debug traces should be captured or not
|
/// Executes a test
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly bool captureDebugTraces;
|
/// <returns>The test results.</returns>
|
||||||
|
internal UnitTestResult[] Execute()
|
||||||
|
{
|
||||||
|
string initLogs = string.Empty;
|
||||||
|
string initTrace = string.Empty;
|
||||||
|
string initErrorLogs = string.Empty;
|
||||||
|
string inittestContextMessages = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
UnitTestResult[] result = null;
|
||||||
/// Helper for reflection API's.
|
|
||||||
/// </summary>
|
|
||||||
private readonly ReflectHelper reflectHelper;
|
|
||||||
|
|
||||||
/// <summary>
|
try
|
||||||
/// Initializes a new instance of the <see cref="TestMethodRunner"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="testMethodInfo">
|
|
||||||
/// The test method info.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="testMethod">
|
|
||||||
/// The test method.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="testContext">
|
|
||||||
/// The test context.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="captureDebugTraces">
|
|
||||||
/// The capture debug traces.
|
|
||||||
/// </param>
|
|
||||||
public TestMethodRunner(TestMethodInfo testMethodInfo, TestMethod testMethod, ITestContext testContext, bool captureDebugTraces)
|
|
||||||
: this(testMethodInfo, testMethod, testContext, captureDebugTraces, ReflectHelper.Instance)
|
|
||||||
{
|
{
|
||||||
|
using (LogMessageListener logListener = new(this.captureDebugTraces))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Run the assembly and class Initialize methods if required.
|
||||||
|
// Assembly or class initialize can throw exceptions in which case we need to ensure that we fail the test.
|
||||||
|
this.testMethodInfo.Parent.Parent.RunAssemblyInitialize(this.testContext.Context);
|
||||||
|
this.testMethodInfo.Parent.RunClassInitialize(this.testContext.Context);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
initLogs = logListener.GetAndClearStandardOutput();
|
||||||
|
initTrace = logListener.GetAndClearDebugTrace();
|
||||||
|
initErrorLogs = logListener.GetAndClearStandardError();
|
||||||
|
inittestContextMessages = this.testContext.GetAndClearDiagnosticMessages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listening to log messages when running the test method with its Test Initialize and cleanup later on in the stack.
|
||||||
|
// This allows us to differentiate logging when data driven methods are used.
|
||||||
|
result = this.RunTestMethod();
|
||||||
|
}
|
||||||
|
catch (TestFailedException ex)
|
||||||
|
{
|
||||||
|
result = new[] { new UnitTestResult(ex) };
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (result == null || result.Length == 0)
|
||||||
|
{
|
||||||
|
result = new[] { new UnitTestResult() };
|
||||||
|
}
|
||||||
|
|
||||||
|
var newResult = new UnitTestResult(new TestFailedException(UnitTestOutcome.Error, ex.TryGetMessage(), ex.TryGetStackTraceInformation()))
|
||||||
|
{
|
||||||
|
StandardOut = result[result.Length - 1].StandardOut,
|
||||||
|
StandardError = result[result.Length - 1].StandardError,
|
||||||
|
DebugTrace = result[result.Length - 1].DebugTrace,
|
||||||
|
TestContextMessages = result[result.Length - 1].TestContextMessages,
|
||||||
|
Duration = result[result.Length - 1].Duration
|
||||||
|
};
|
||||||
|
result[result.Length - 1] = newResult;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
var firstResult = result[0];
|
||||||
|
firstResult.StandardOut = initLogs + firstResult.StandardOut;
|
||||||
|
firstResult.StandardError = initErrorLogs + firstResult.StandardError;
|
||||||
|
firstResult.DebugTrace = initTrace + firstResult.DebugTrace;
|
||||||
|
firstResult.TestContextMessages = inittestContextMessages + firstResult.TestContextMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return result;
|
||||||
/// Initializes a new instance of the <see cref="TestMethodRunner"/> class.
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="testMethodInfo">
|
|
||||||
/// The test method info.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="testMethod">
|
|
||||||
/// The test method.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="testContext">
|
|
||||||
/// The test context.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="captureDebugTraces">
|
|
||||||
/// The capture debug traces.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="reflectHelper">
|
|
||||||
/// The reflect Helper object.
|
|
||||||
/// </param>
|
|
||||||
public TestMethodRunner(TestMethodInfo testMethodInfo, TestMethod testMethod, ITestContext testContext, bool captureDebugTraces, ReflectHelper reflectHelper)
|
|
||||||
{
|
|
||||||
Debug.Assert(testMethodInfo != null, "testMethodInfo should not be null");
|
|
||||||
Debug.Assert(testMethod != null, "testMethod should not be null");
|
|
||||||
Debug.Assert(testContext != null, "testContext should not be null");
|
|
||||||
|
|
||||||
this.testMethodInfo = testMethodInfo;
|
/// <summary>
|
||||||
this.test = testMethod;
|
/// Runs the test method
|
||||||
this.testContext = testContext;
|
/// </summary>
|
||||||
this.captureDebugTraces = captureDebugTraces;
|
/// <returns>The test results.</returns>
|
||||||
this.reflectHelper = reflectHelper;
|
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
||||||
|
internal UnitTestResult[] RunTestMethod()
|
||||||
|
{
|
||||||
|
Debug.Assert(this.test != null, "Test should not be null.");
|
||||||
|
Debug.Assert(this.testMethodInfo.TestMethod != null, "Test method should not be null.");
|
||||||
|
|
||||||
|
List<UTF.TestResult> results = new();
|
||||||
|
var isDataDriven = false;
|
||||||
|
|
||||||
|
if (this.testMethodInfo.TestMethodOptions.Executor != null)
|
||||||
|
{
|
||||||
|
if (this.test.DataType == DynamicDataType.ITestDataSource)
|
||||||
|
{
|
||||||
|
var data = DataSerializationHelper.Deserialize(this.test.SerializedData);
|
||||||
|
var testResults = this.ExecuteTestWithDataSource(null, data);
|
||||||
|
results.AddRange(testResults);
|
||||||
|
}
|
||||||
|
else if (this.ExecuteDataSourceBasedTests(results))
|
||||||
|
{
|
||||||
|
isDataDriven = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var testResults = this.ExecuteTest(this.testMethodInfo);
|
||||||
|
|
||||||
|
foreach (var testResult in testResults)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(testResult.DisplayName))
|
||||||
|
{
|
||||||
|
testResult.DisplayName = this.test.DisplayName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results.AddRange(testResults);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogError(
|
||||||
|
"Not able to get executor for method {0}.{1}",
|
||||||
|
this.testMethodInfo.TestClassName,
|
||||||
|
this.testMethodInfo.TestMethodName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// Get aggregate outcome.
|
||||||
/// Executes a test
|
var aggregateOutcome = this.GetAggregateOutcome(results);
|
||||||
/// </summary>
|
this.testContext.SetOutcome(aggregateOutcome);
|
||||||
/// <returns>The test results.</returns>
|
|
||||||
internal UnitTestResult[] Execute()
|
|
||||||
{
|
|
||||||
string initLogs = string.Empty;
|
|
||||||
string initTrace = string.Empty;
|
|
||||||
string initErrorLogs = string.Empty;
|
|
||||||
string inittestContextMessages = string.Empty;
|
|
||||||
|
|
||||||
UnitTestResult[] result = null;
|
// Set a result in case no result is present.
|
||||||
|
if (!results.Any())
|
||||||
|
{
|
||||||
|
results.Add(new UTF.TestResult() { Outcome = aggregateOutcome, TestFailureException = new TestFailedException(UnitTestOutcome.Error, Resource.UTA_NoTestResult) });
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case of data driven, set parent info in results.
|
||||||
|
if (isDataDriven)
|
||||||
|
{
|
||||||
|
results = this.UpdateResultsWithParentInfo(results, Guid.NewGuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
return results.ToArray().ToUnitTestResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ExecuteDataSourceBasedTests(List<UTF.TestResult> results)
|
||||||
|
{
|
||||||
|
var isDataDriven = false;
|
||||||
|
|
||||||
|
UTF.DataSourceAttribute[] dataSourceAttribute = this.testMethodInfo.GetAttributes<UTF.DataSourceAttribute>(false);
|
||||||
|
if (dataSourceAttribute != null && dataSourceAttribute.Length == 1)
|
||||||
|
{
|
||||||
|
isDataDriven = true;
|
||||||
|
Stopwatch watch = new();
|
||||||
|
watch.Start();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (LogMessageListener logListener = new(this.captureDebugTraces))
|
IEnumerable<object> dataRows = PlatformServiceProvider.Instance.TestDataSource.GetData(this.testMethodInfo, this.testContext);
|
||||||
|
|
||||||
|
if (dataRows == null)
|
||||||
{
|
{
|
||||||
try
|
watch.Stop();
|
||||||
{
|
var inconclusiveResult = new UTF.TestResult();
|
||||||
// Run the assembly and class Initialize methods if required.
|
inconclusiveResult.Outcome = UTF.UnitTestOutcome.Inconclusive;
|
||||||
// Assembly or class initialize can throw exceptions in which case we need to ensure that we fail the test.
|
inconclusiveResult.Duration = watch.Elapsed;
|
||||||
this.testMethodInfo.Parent.Parent.RunAssemblyInitialize(this.testContext.Context);
|
results.Add(inconclusiveResult);
|
||||||
this.testMethodInfo.Parent.RunClassInitialize(this.testContext.Context);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
initLogs = logListener.GetAndClearStandardOutput();
|
|
||||||
initTrace = logListener.GetAndClearDebugTrace();
|
|
||||||
initErrorLogs = logListener.GetAndClearStandardError();
|
|
||||||
inittestContextMessages = this.testContext.GetAndClearDiagnosticMessages();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listening to log messages when running the test method with its Test Initialize and cleanup later on in the stack.
|
|
||||||
// This allows us to differentiate logging when data driven methods are used.
|
|
||||||
result = this.RunTestMethod();
|
|
||||||
}
|
|
||||||
catch (TestFailedException ex)
|
|
||||||
{
|
|
||||||
result = new[] { new UnitTestResult(ex) };
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
if (result == null || result.Length == 0)
|
|
||||||
{
|
|
||||||
result = new[] { new UnitTestResult() };
|
|
||||||
}
|
|
||||||
|
|
||||||
var newResult = new UnitTestResult(new TestFailedException(UnitTestOutcome.Error, ex.TryGetMessage(), ex.TryGetStackTraceInformation()))
|
|
||||||
{
|
|
||||||
StandardOut = result[result.Length - 1].StandardOut,
|
|
||||||
StandardError = result[result.Length - 1].StandardError,
|
|
||||||
DebugTrace = result[result.Length - 1].DebugTrace,
|
|
||||||
TestContextMessages = result[result.Length - 1].TestContextMessages,
|
|
||||||
Duration = result[result.Length - 1].Duration
|
|
||||||
};
|
|
||||||
result[result.Length - 1] = newResult;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
var firstResult = result[0];
|
|
||||||
firstResult.StandardOut = initLogs + firstResult.StandardOut;
|
|
||||||
firstResult.StandardError = initErrorLogs + firstResult.StandardError;
|
|
||||||
firstResult.DebugTrace = initTrace + firstResult.DebugTrace;
|
|
||||||
firstResult.TestContextMessages = inittestContextMessages + firstResult.TestContextMessages;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Runs the test method
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The test results.</returns>
|
|
||||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
|
||||||
internal UnitTestResult[] RunTestMethod()
|
|
||||||
{
|
|
||||||
Debug.Assert(this.test != null, "Test should not be null.");
|
|
||||||
Debug.Assert(this.testMethodInfo.TestMethod != null, "Test method should not be null.");
|
|
||||||
|
|
||||||
List<UTF.TestResult> results = new();
|
|
||||||
var isDataDriven = false;
|
|
||||||
|
|
||||||
if (this.testMethodInfo.TestMethodOptions.Executor != null)
|
|
||||||
{
|
|
||||||
if (this.test.DataType == DynamicDataType.ITestDataSource)
|
|
||||||
{
|
|
||||||
var data = DataSerializationHelper.Deserialize(this.test.SerializedData);
|
|
||||||
var testResults = this.ExecuteTestWithDataSource(null, data);
|
|
||||||
results.AddRange(testResults);
|
|
||||||
}
|
|
||||||
else if (this.ExecuteDataSourceBasedTests(results))
|
|
||||||
{
|
|
||||||
isDataDriven = true;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var testResults = this.ExecuteTest(this.testMethodInfo);
|
try
|
||||||
|
|
||||||
foreach (var testResult in testResults)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(testResult.DisplayName))
|
int rowIndex = 0;
|
||||||
|
|
||||||
|
foreach (object dataRow in dataRows)
|
||||||
{
|
{
|
||||||
testResult.DisplayName = this.test.DisplayName;
|
UTF.TestResult[] testResults = this.ExecuteTestWithDataRow(dataRow, rowIndex++);
|
||||||
|
results.AddRange(testResults);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
results.AddRange(testResults);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogError(
|
|
||||||
"Not able to get executor for method {0}.{1}",
|
|
||||||
this.testMethodInfo.TestClassName,
|
|
||||||
this.testMethodInfo.TestMethodName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get aggregate outcome.
|
|
||||||
var aggregateOutcome = this.GetAggregateOutcome(results);
|
|
||||||
this.testContext.SetOutcome(aggregateOutcome);
|
|
||||||
|
|
||||||
// Set a result in case no result is present.
|
|
||||||
if (!results.Any())
|
|
||||||
{
|
|
||||||
results.Add(new UTF.TestResult() { Outcome = aggregateOutcome, TestFailureException = new TestFailedException(UnitTestOutcome.Error, Resource.UTA_NoTestResult) });
|
|
||||||
}
|
|
||||||
|
|
||||||
// In case of data driven, set parent info in results.
|
|
||||||
if (isDataDriven)
|
|
||||||
{
|
|
||||||
results = this.UpdateResultsWithParentInfo(results, Guid.NewGuid());
|
|
||||||
}
|
|
||||||
|
|
||||||
return results.ToArray().ToUnitTestResults();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ExecuteDataSourceBasedTests(List<UTF.TestResult> results)
|
|
||||||
{
|
|
||||||
var isDataDriven = false;
|
|
||||||
|
|
||||||
UTF.DataSourceAttribute[] dataSourceAttribute = this.testMethodInfo.GetAttributes<UTF.DataSourceAttribute>(false);
|
|
||||||
if (dataSourceAttribute != null && dataSourceAttribute.Length == 1)
|
|
||||||
{
|
|
||||||
isDataDriven = true;
|
|
||||||
Stopwatch watch = new();
|
|
||||||
watch.Start();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IEnumerable<object> dataRows = PlatformServiceProvider.Instance.TestDataSource.GetData(this.testMethodInfo, this.testContext);
|
|
||||||
|
|
||||||
if (dataRows == null)
|
|
||||||
{
|
{
|
||||||
watch.Stop();
|
this.testContext.SetDataConnection(null);
|
||||||
var inconclusiveResult = new UTF.TestResult();
|
this.testContext.SetDataRow(null);
|
||||||
inconclusiveResult.Outcome = UTF.UnitTestOutcome.Inconclusive;
|
|
||||||
inconclusiveResult.Duration = watch.Elapsed;
|
|
||||||
results.Add(inconclusiveResult);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
int rowIndex = 0;
|
|
||||||
|
|
||||||
foreach (object dataRow in dataRows)
|
|
||||||
{
|
|
||||||
UTF.TestResult[] testResults = this.ExecuteTestWithDataRow(dataRow, rowIndex++);
|
|
||||||
results.AddRange(testResults);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
this.testContext.SetDataConnection(null);
|
|
||||||
this.testContext.SetDataRow(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
watch.Stop();
|
|
||||||
var failedResult = new UTF.TestResult();
|
|
||||||
failedResult.Outcome = UTF.UnitTestOutcome.Error;
|
|
||||||
failedResult.TestFailureException = ex;
|
|
||||||
failedResult.Duration = watch.Elapsed;
|
|
||||||
results.Add(failedResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UTF.ITestDataSource[] testDataSources = this.testMethodInfo.GetAttributes<Attribute>(false)?.Where(a => a is UTF.ITestDataSource).OfType<UTF.ITestDataSource>().ToArray();
|
|
||||||
|
|
||||||
if (testDataSources != null && testDataSources.Length > 0)
|
|
||||||
{
|
|
||||||
isDataDriven = true;
|
|
||||||
foreach (var testDataSource in testDataSources)
|
|
||||||
{
|
|
||||||
foreach (var data in testDataSource.GetData(this.testMethodInfo.MethodInfo))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var testResults = this.ExecuteTestWithDataSource(testDataSource, data);
|
|
||||||
|
|
||||||
results.AddRange(testResults);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
this.testMethodInfo.SetArguments(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return isDataDriven;
|
|
||||||
}
|
|
||||||
|
|
||||||
private UTF.TestResult[] ExecuteTestWithDataSource(UTF.ITestDataSource testDataSource, object[] data)
|
|
||||||
{
|
|
||||||
var stopwatch = Stopwatch.StartNew();
|
|
||||||
|
|
||||||
this.testMethodInfo.SetArguments(data);
|
|
||||||
var testResults = this.ExecuteTest(this.testMethodInfo);
|
|
||||||
stopwatch.Stop();
|
|
||||||
|
|
||||||
var hasDisplayName = !string.IsNullOrWhiteSpace(this.test.DisplayName);
|
|
||||||
foreach (var testResult in testResults)
|
|
||||||
{
|
|
||||||
if (testResult.Duration == TimeSpan.Zero)
|
|
||||||
{
|
|
||||||
testResult.Duration = stopwatch.Elapsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
var displayName = this.test.Name;
|
|
||||||
if (testDataSource != null)
|
|
||||||
{
|
|
||||||
displayName = testDataSource.GetDisplayName(this.testMethodInfo.MethodInfo, data);
|
|
||||||
}
|
|
||||||
else if (hasDisplayName)
|
|
||||||
{
|
|
||||||
displayName = this.test.DisplayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
testResult.DisplayName = displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
return testResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
private UTF.TestResult[] ExecuteTestWithDataRow(object dataRow, int rowIndex)
|
|
||||||
{
|
|
||||||
var displayName = string.Format(CultureInfo.CurrentCulture, Resource.DataDrivenResultDisplayName, this.test.DisplayName, rowIndex);
|
|
||||||
Stopwatch stopwatch = null;
|
|
||||||
|
|
||||||
UTF.TestResult[] testResults = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
stopwatch = Stopwatch.StartNew();
|
|
||||||
this.testContext.SetDataRow(dataRow);
|
|
||||||
testResults = this.ExecuteTest(this.testMethodInfo);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
stopwatch?.Stop();
|
|
||||||
this.testContext.SetDataRow(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var testResult in testResults)
|
|
||||||
{
|
|
||||||
testResult.DisplayName = displayName;
|
|
||||||
testResult.DatarowIndex = rowIndex;
|
|
||||||
testResult.Duration = stopwatch.Elapsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
return testResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
private UTF.TestResult[] ExecuteTest(TestMethodInfo testMethodInfo)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return this.testMethodInfo.TestMethodOptions.Executor.Execute(testMethodInfo);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return new[]
|
watch.Stop();
|
||||||
|
var failedResult = new UTF.TestResult();
|
||||||
|
failedResult.Outcome = UTF.UnitTestOutcome.Error;
|
||||||
|
failedResult.TestFailureException = ex;
|
||||||
|
failedResult.Duration = watch.Elapsed;
|
||||||
|
results.Add(failedResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UTF.ITestDataSource[] testDataSources = this.testMethodInfo.GetAttributes<Attribute>(false)?.Where(a => a is UTF.ITestDataSource).OfType<UTF.ITestDataSource>().ToArray();
|
||||||
|
|
||||||
|
if (testDataSources != null && testDataSources.Length > 0)
|
||||||
|
{
|
||||||
|
isDataDriven = true;
|
||||||
|
foreach (var testDataSource in testDataSources)
|
||||||
{
|
{
|
||||||
new UTF.TestResult()
|
foreach (var data in testDataSource.GetData(this.testMethodInfo.MethodInfo))
|
||||||
{
|
{
|
||||||
TestFailureException = new Exception(string.Format(CultureInfo.CurrentCulture, Resource.UTA_ExecuteThrewException, ex?.Message, ex?.StackTrace), ex)
|
try
|
||||||
|
{
|
||||||
|
var testResults = this.ExecuteTestWithDataSource(testDataSource, data);
|
||||||
|
|
||||||
|
results.AddRange(testResults);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.testMethodInfo.SetArguments(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return isDataDriven;
|
||||||
/// Gets aggregate outcome.
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="results">Results.</param>
|
private UTF.TestResult[] ExecuteTestWithDataSource(UTF.ITestDataSource testDataSource, object[] data)
|
||||||
/// <returns>Aggregate outcome.</returns>
|
{
|
||||||
private UTF.UnitTestOutcome GetAggregateOutcome(List<UTF.TestResult> results)
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
this.testMethodInfo.SetArguments(data);
|
||||||
|
var testResults = this.ExecuteTest(this.testMethodInfo);
|
||||||
|
stopwatch.Stop();
|
||||||
|
|
||||||
|
var hasDisplayName = !string.IsNullOrWhiteSpace(this.test.DisplayName);
|
||||||
|
foreach (var testResult in testResults)
|
||||||
{
|
{
|
||||||
// In case results are not present, set outcome as unknown.
|
if (testResult.Duration == TimeSpan.Zero)
|
||||||
if (!results.Any())
|
|
||||||
{
|
{
|
||||||
return UTF.UnitTestOutcome.Unknown;
|
testResult.Duration = stopwatch.Elapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get aggregate outcome.
|
var displayName = this.test.Name;
|
||||||
var aggregateOutcome = results[0].Outcome;
|
if (testDataSource != null)
|
||||||
foreach (var result in results)
|
|
||||||
{
|
{
|
||||||
aggregateOutcome = UnitTestOutcomeExtensions.GetMoreImportantOutcome(aggregateOutcome, result.Outcome);
|
displayName = testDataSource.GetDisplayName(this.testMethodInfo.MethodInfo, data);
|
||||||
|
}
|
||||||
|
else if (hasDisplayName)
|
||||||
|
{
|
||||||
|
displayName = this.test.DisplayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
return aggregateOutcome;
|
testResult.DisplayName = displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return testResults;
|
||||||
/// Updates given results with parent info if results are greater than 1.
|
}
|
||||||
/// Add parent results as first result in updated result.
|
|
||||||
/// </summary>
|
private UTF.TestResult[] ExecuteTestWithDataRow(object dataRow, int rowIndex)
|
||||||
/// <param name="results">Results.</param>
|
{
|
||||||
/// <param name="executionId">Current execution id.</param>
|
var displayName = string.Format(CultureInfo.CurrentCulture, Resource.DataDrivenResultDisplayName, this.test.DisplayName, rowIndex);
|
||||||
/// <returns>Updated results which contains parent result as first result. All other results contains parent result info.</returns>
|
Stopwatch stopwatch = null;
|
||||||
private List<UTF.TestResult> UpdateResultsWithParentInfo(List<UTF.TestResult> results, Guid executionId)
|
|
||||||
|
UTF.TestResult[] testResults = null;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
// Return results in case there are no results.
|
stopwatch = Stopwatch.StartNew();
|
||||||
if (!results.Any())
|
this.testContext.SetDataRow(dataRow);
|
||||||
|
testResults = this.ExecuteTest(this.testMethodInfo);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
stopwatch?.Stop();
|
||||||
|
this.testContext.SetDataRow(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var testResult in testResults)
|
||||||
|
{
|
||||||
|
testResult.DisplayName = displayName;
|
||||||
|
testResult.DatarowIndex = rowIndex;
|
||||||
|
testResult.Duration = stopwatch.Elapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return testResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UTF.TestResult[] ExecuteTest(TestMethodInfo testMethodInfo)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return this.testMethodInfo.TestMethodOptions.Executor.Execute(testMethodInfo);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
{
|
{
|
||||||
return results;
|
new UTF.TestResult()
|
||||||
}
|
{
|
||||||
|
TestFailureException = new Exception(string.Format(CultureInfo.CurrentCulture, Resource.UTA_ExecuteThrewException, ex?.Message, ex?.StackTrace), ex)
|
||||||
// UpdatedResults contain parent result at first position and remaining results has parent info updated.
|
}
|
||||||
var updatedResults = new List<UTF.TestResult>();
|
};
|
||||||
|
|
||||||
foreach (var result in results)
|
|
||||||
{
|
|
||||||
result.ExecutionId = Guid.NewGuid();
|
|
||||||
result.ParentExecId = executionId;
|
|
||||||
|
|
||||||
updatedResults.Add(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return updatedResults;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets aggregate outcome.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="results">Results.</param>
|
||||||
|
/// <returns>Aggregate outcome.</returns>
|
||||||
|
private UTF.UnitTestOutcome GetAggregateOutcome(List<UTF.TestResult> results)
|
||||||
|
{
|
||||||
|
// In case results are not present, set outcome as unknown.
|
||||||
|
if (!results.Any())
|
||||||
|
{
|
||||||
|
return UTF.UnitTestOutcome.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get aggregate outcome.
|
||||||
|
var aggregateOutcome = results[0].Outcome;
|
||||||
|
foreach (var result in results)
|
||||||
|
{
|
||||||
|
aggregateOutcome = UnitTestOutcomeExtensions.GetMoreImportantOutcome(aggregateOutcome, result.Outcome);
|
||||||
|
}
|
||||||
|
|
||||||
|
return aggregateOutcome;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates given results with parent info if results are greater than 1.
|
||||||
|
/// Add parent results as first result in updated result.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="results">Results.</param>
|
||||||
|
/// <param name="executionId">Current execution id.</param>
|
||||||
|
/// <returns>Updated results which contains parent result as first result. All other results contains parent result info.</returns>
|
||||||
|
private List<UTF.TestResult> UpdateResultsWithParentInfo(List<UTF.TestResult> results, Guid executionId)
|
||||||
|
{
|
||||||
|
// Return results in case there are no results.
|
||||||
|
if (!results.Any())
|
||||||
|
{
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedResults contain parent result at first position and remaining results has parent info updated.
|
||||||
|
var updatedResults = new List<UTF.TestResult>();
|
||||||
|
|
||||||
|
foreach (var result in results)
|
||||||
|
{
|
||||||
|
result.ExecutionId = Guid.NewGuid();
|
||||||
|
result.ParentExecId = executionId;
|
||||||
|
|
||||||
|
updatedResults.Add(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedResults;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,74 +1,73 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancellation token supporting cancellation of a test run.
|
||||||
|
/// </summary>
|
||||||
|
public class TestRunCancellationToken
|
||||||
{
|
{
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Diagnostics;
|
/// Stores whether the test run is canceled or not.
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
/// </summary>
|
||||||
|
private bool canceled;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cancellation token supporting cancellation of a test run.
|
/// Callback to be invoked when canceled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TestRunCancellationToken
|
private Action registeredCallback;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the test run is canceled.
|
||||||
|
/// </summary>
|
||||||
|
public bool Canceled
|
||||||
{
|
{
|
||||||
/// <summary>
|
get
|
||||||
/// Stores whether the test run is canceled or not.
|
|
||||||
/// </summary>
|
|
||||||
private bool canceled;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Callback to be invoked when canceled.
|
|
||||||
/// </summary>
|
|
||||||
private Action registeredCallback;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the test run is canceled.
|
|
||||||
/// </summary>
|
|
||||||
public bool Canceled
|
|
||||||
{
|
{
|
||||||
get
|
return this.canceled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
this.canceled = value;
|
||||||
|
if (this.canceled)
|
||||||
{
|
{
|
||||||
return this.canceled;
|
this.registeredCallback?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
private set
|
|
||||||
{
|
|
||||||
this.canceled = value;
|
|
||||||
if (this.canceled)
|
|
||||||
{
|
|
||||||
this.registeredCallback?.Invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cancels the execution of a test run.
|
|
||||||
/// </summary>
|
|
||||||
public void Cancel()
|
|
||||||
{
|
|
||||||
this.Canceled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers a callback method to be invoked when canceled.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="callback">Callback delegate for handling cancellation.</param>
|
|
||||||
public void Register(Action callback)
|
|
||||||
{
|
|
||||||
ValidateArg.NotNull(callback, "callback");
|
|
||||||
|
|
||||||
Debug.Assert(this.registeredCallback == null, "Callback delegate is already registered, use a new cancellationToken");
|
|
||||||
|
|
||||||
this.registeredCallback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unregister the callback method.
|
|
||||||
/// </summary>
|
|
||||||
public void Unregister()
|
|
||||||
{
|
|
||||||
this.registeredCallback = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancels the execution of a test run.
|
||||||
|
/// </summary>
|
||||||
|
public void Cancel()
|
||||||
|
{
|
||||||
|
this.Canceled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a callback method to be invoked when canceled.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="callback">Callback delegate for handling cancellation.</param>
|
||||||
|
public void Register(Action callback)
|
||||||
|
{
|
||||||
|
ValidateArg.NotNull(callback, "callback");
|
||||||
|
|
||||||
|
Debug.Assert(this.registeredCallback == null, "Callback delegate is already registered, use a new cancellationToken");
|
||||||
|
|
||||||
|
this.registeredCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregister the callback method.
|
||||||
|
/// </summary>
|
||||||
|
public void Unregister()
|
||||||
|
{
|
||||||
|
this.registeredCallback = null;
|
||||||
|
}
|
||||||
}
|
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,375 +1,374 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution;
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Security;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
|
using System;
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
using System.Collections.Generic;
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
using System.Diagnostics;
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
using System.Globalization;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using System.Linq;
|
||||||
using TPOM = Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
using System.Reflection;
|
||||||
using UTF = Microsoft.VisualStudio.TestTools.UnitTesting;
|
using System.Security;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using TPOM = Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
using UTF = Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The runner that runs a single unit test. Also manages the assembly and class cleanup methods at the end of the run.
|
||||||
|
/// </summary>
|
||||||
|
internal class UnitTestRunner : MarshalByRefObject
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Type cache
|
||||||
|
/// </summary>
|
||||||
|
private readonly TypeCache typeCache;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The runner that runs a single unit test. Also manages the assembly and class cleanup methods at the end of the run.
|
/// Reflect helper
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class UnitTestRunner : MarshalByRefObject
|
private readonly ReflectHelper reflectHelper;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class cleanup manager
|
||||||
|
/// </summary>
|
||||||
|
private ClassCleanupManager classCleanupManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="UnitTestRunner"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="settings"> Specifies adapter settings that need to be instantiated in the domain running these tests. </param>
|
||||||
|
public UnitTestRunner(MSTestSettings settings)
|
||||||
|
: this(settings, ReflectHelper.Instance)
|
||||||
{
|
{
|
||||||
/// <summary>
|
}
|
||||||
/// Type cache
|
|
||||||
/// </summary>
|
|
||||||
private readonly TypeCache typeCache;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reflect helper
|
/// Initializes a new instance of the <see cref="UnitTestRunner"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ReflectHelper reflectHelper;
|
/// <param name="settings"> Specifies adapter settings. </param>
|
||||||
|
/// <param name="reflectHelper"> The reflect Helper. </param>
|
||||||
|
internal UnitTestRunner(MSTestSettings settings, ReflectHelper reflectHelper)
|
||||||
|
{
|
||||||
|
this.reflectHelper = reflectHelper;
|
||||||
|
this.typeCache = new TypeCache(reflectHelper);
|
||||||
|
|
||||||
/// <summary>
|
// Populate the settings into the domain(Desktop workflow) performing discovery.
|
||||||
/// Class cleanup manager
|
// This would just be resettings the settings to itself in non desktop workflows.
|
||||||
/// </summary>
|
MSTestSettings.PopulateSettings(settings);
|
||||||
private ClassCleanupManager classCleanupManager;
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="UnitTestRunner"/> class.
|
/// Returns object to be used for controlling lifetime, null means infinite lifetime.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="settings"> Specifies adapter settings that need to be instantiated in the domain running these tests. </param>
|
/// <returns>
|
||||||
public UnitTestRunner(MSTestSettings settings)
|
/// The <see cref="object"/>.
|
||||||
: this(settings, ReflectHelper.Instance)
|
/// </returns>
|
||||||
{
|
[SecurityCritical]
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="UnitTestRunner"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="settings"> Specifies adapter settings. </param>
|
|
||||||
/// <param name="reflectHelper"> The reflect Helper. </param>
|
|
||||||
internal UnitTestRunner(MSTestSettings settings, ReflectHelper reflectHelper)
|
|
||||||
{
|
|
||||||
this.reflectHelper = reflectHelper;
|
|
||||||
this.typeCache = new TypeCache(reflectHelper);
|
|
||||||
|
|
||||||
// Populate the settings into the domain(Desktop workflow) performing discovery.
|
|
||||||
// This would just be resettings the settings to itself in non desktop workflows.
|
|
||||||
MSTestSettings.PopulateSettings(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns object to be used for controlling lifetime, null means infinite lifetime.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// The <see cref="object"/>.
|
|
||||||
/// </returns>
|
|
||||||
[SecurityCritical]
|
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
[Obsolete]
|
[Obsolete]
|
||||||
#endif
|
#endif
|
||||||
public override object InitializeLifetimeService()
|
public override object InitializeLifetimeService()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialized the class cleanup manager for the unit test runner. Note, this can run over process-isolation,
|
||||||
|
/// and all inputs must be serializable from host process.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="testsToRun">the list of tests that will be run in this execution</param>
|
||||||
|
/// <param name="classCleanupLifecycle">The assembly level class cleanup lifecycle</param>
|
||||||
|
internal void InitializeClassCleanupManager(ICollection<UnitTestElement> testsToRun, int? classCleanupLifecycle)
|
||||||
|
{
|
||||||
|
// We can't transport the enum across AppDomain boundaries because of backwards and forwards compatibility.
|
||||||
|
// So we're converting here if we can, or falling back to the default.
|
||||||
|
var lifecycle = ClassCleanupBehavior.EndOfAssembly;
|
||||||
|
if (classCleanupLifecycle != null && Enum.IsDefined(typeof(ClassCleanupBehavior), classCleanupLifecycle))
|
||||||
|
{
|
||||||
|
lifecycle = (ClassCleanupBehavior)classCleanupLifecycle;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.classCleanupManager = new ClassCleanupManager(
|
||||||
|
testsToRun,
|
||||||
|
MSTestSettings.CurrentSettings.ClassCleanupLifecycle,
|
||||||
|
lifecycle,
|
||||||
|
this.reflectHelper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs a single test.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="testMethod"> The test Method. </param>
|
||||||
|
/// <param name="testContextProperties"> The test context properties. </param>
|
||||||
|
/// <returns> The <see cref="UnitTestResult"/>. </returns>
|
||||||
|
internal UnitTestResult[] RunSingleTest(TestMethod testMethod, IDictionary<string, object> testContextProperties)
|
||||||
|
{
|
||||||
|
if (testMethod == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(testMethod));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var writer = new ThreadSafeStringWriter(CultureInfo.InvariantCulture, "context");
|
||||||
|
var properties = new Dictionary<string, object>(testContextProperties);
|
||||||
|
var testContext = PlatformServiceProvider.Instance.GetTestContext(testMethod, writer, properties);
|
||||||
|
testContext.SetOutcome(TestTools.UnitTesting.UnitTestOutcome.InProgress);
|
||||||
|
|
||||||
|
// Get the testMethod
|
||||||
|
var testMethodInfo = this.typeCache.GetTestMethodInfo(
|
||||||
|
testMethod,
|
||||||
|
testContext,
|
||||||
|
MSTestSettings.CurrentSettings.CaptureDebugTraces);
|
||||||
|
|
||||||
|
if (this.classCleanupManager == null && testMethodInfo != null && testMethodInfo.Parent.HasExecutableCleanupMethod)
|
||||||
|
{
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning(Resource.OlderTFMVersionFoundClassCleanup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.IsTestMethodRunnable(testMethod, testMethodInfo, out var notRunnableResult))
|
||||||
|
{
|
||||||
|
bool shouldRunClassCleanup = false;
|
||||||
|
this.classCleanupManager?.MarkTestComplete(testMethodInfo, testMethod, out shouldRunClassCleanup);
|
||||||
|
if (shouldRunClassCleanup)
|
||||||
|
{
|
||||||
|
testMethodInfo.Parent.RunClassCleanup(ClassCleanupBehavior.EndOfClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
return notRunnableResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new TestMethodRunner(testMethodInfo, testMethod, testContext, MSTestSettings.CurrentSettings.CaptureDebugTraces, this.reflectHelper).Execute();
|
||||||
|
this.RunClassCleanupIfEndOfClass(testMethodInfo, testMethod, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (TypeInspectionException ex)
|
||||||
|
{
|
||||||
|
// Catch any exception thrown while inspecting the test method and return failure.
|
||||||
|
return new UnitTestResult[] { new UnitTestResult(ObjectModel.UnitTestOutcome.Failed, ex.Message) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs the class cleanup method.
|
||||||
|
/// It returns any error information during the execution of the cleanup method
|
||||||
|
/// </summary>
|
||||||
|
/// <returns> The <see cref="RunCleanupResult"/>. </returns>
|
||||||
|
internal RunCleanupResult RunCleanup()
|
||||||
|
{
|
||||||
|
// No cleanup methods to execute, then return.
|
||||||
|
var assemblyInfoCache = this.typeCache.AssemblyInfoListWithExecutableCleanupMethods;
|
||||||
|
var classInfoCache = this.typeCache.ClassInfoListWithExecutableCleanupMethods;
|
||||||
|
if (!assemblyInfoCache.Any() && !classInfoCache.Any())
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
var result = new RunCleanupResult { Warnings = new List<string>() };
|
||||||
/// Initialized the class cleanup manager for the unit test runner. Note, this can run over process-isolation,
|
|
||||||
/// and all inputs must be serializable from host process.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="testsToRun">the list of tests that will be run in this execution</param>
|
|
||||||
/// <param name="classCleanupLifecycle">The assembly level class cleanup lifecycle</param>
|
|
||||||
internal void InitializeClassCleanupManager(ICollection<UnitTestElement> testsToRun, int? classCleanupLifecycle)
|
|
||||||
{
|
|
||||||
// We can't transport the enum across AppDomain boundaries because of backwards and forwards compatibility.
|
|
||||||
// So we're converting here if we can, or falling back to the default.
|
|
||||||
var lifecycle = ClassCleanupBehavior.EndOfAssembly;
|
|
||||||
if (classCleanupLifecycle != null && Enum.IsDefined(typeof(ClassCleanupBehavior), classCleanupLifecycle))
|
|
||||||
{
|
|
||||||
lifecycle = (ClassCleanupBehavior)classCleanupLifecycle;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.classCleanupManager = new ClassCleanupManager(
|
using (var redirector = new LogMessageListener(MSTestSettings.CurrentSettings.CaptureDebugTraces))
|
||||||
testsToRun,
|
{
|
||||||
MSTestSettings.CurrentSettings.ClassCleanupLifecycle,
|
try
|
||||||
lifecycle,
|
{
|
||||||
this.reflectHelper);
|
this.RunClassCleanupMethods(classInfoCache, result.Warnings);
|
||||||
|
this.RunAssemblyCleanup(assemblyInfoCache, result.Warnings);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Replacing the null character with a string.replace should work.
|
||||||
|
// If this does not work for a specific dotnet version a custom function doing the same needs to be put in place.
|
||||||
|
result.StandardOut = redirector.GetAndClearStandardOutput()?.Replace("\0", "\\0");
|
||||||
|
result.StandardError = redirector.GetAndClearStandardError()?.Replace("\0", "\\0");
|
||||||
|
result.DebugTrace = redirector.GetAndClearDebugTrace()?.Replace("\0", "\\0");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return result;
|
||||||
/// Runs a single test.
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="testMethod"> The test Method. </param>
|
private void RunClassCleanupIfEndOfClass(TestMethodInfo testMethodInfo, TestMethod testMethod, UnitTestResult[] results)
|
||||||
/// <param name="testContextProperties"> The test context properties. </param>
|
{
|
||||||
/// <returns> The <see cref="UnitTestResult"/>. </returns>
|
bool shouldRunClassCleanup = false;
|
||||||
internal UnitTestResult[] RunSingleTest(TestMethod testMethod, IDictionary<string, object> testContextProperties)
|
this.classCleanupManager?.MarkTestComplete(testMethodInfo, testMethod, out shouldRunClassCleanup);
|
||||||
|
if (shouldRunClassCleanup)
|
||||||
{
|
{
|
||||||
if (testMethod == null)
|
string cleanupLogs = string.Empty;
|
||||||
{
|
string cleanupTrace = string.Empty;
|
||||||
throw new ArgumentNullException(nameof(testMethod));
|
string cleanupErrorLogs = string.Empty;
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var writer = new ThreadSafeStringWriter(CultureInfo.InvariantCulture, "context");
|
using LogMessageListener logListener =
|
||||||
var properties = new Dictionary<string, object>(testContextProperties);
|
new(MSTestSettings.CurrentSettings.CaptureDebugTraces);
|
||||||
var testContext = PlatformServiceProvider.Instance.GetTestContext(testMethod, writer, properties);
|
|
||||||
testContext.SetOutcome(TestTools.UnitTesting.UnitTestOutcome.InProgress);
|
|
||||||
|
|
||||||
// Get the testMethod
|
|
||||||
var testMethodInfo = this.typeCache.GetTestMethodInfo(
|
|
||||||
testMethod,
|
|
||||||
testContext,
|
|
||||||
MSTestSettings.CurrentSettings.CaptureDebugTraces);
|
|
||||||
|
|
||||||
if (this.classCleanupManager == null && testMethodInfo != null && testMethodInfo.Parent.HasExecutableCleanupMethod)
|
|
||||||
{
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning(Resource.OlderTFMVersionFoundClassCleanup);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.IsTestMethodRunnable(testMethod, testMethodInfo, out var notRunnableResult))
|
|
||||||
{
|
|
||||||
bool shouldRunClassCleanup = false;
|
|
||||||
this.classCleanupManager?.MarkTestComplete(testMethodInfo, testMethod, out shouldRunClassCleanup);
|
|
||||||
if (shouldRunClassCleanup)
|
|
||||||
{
|
|
||||||
testMethodInfo.Parent.RunClassCleanup(ClassCleanupBehavior.EndOfClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
return notRunnableResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = new TestMethodRunner(testMethodInfo, testMethod, testContext, MSTestSettings.CurrentSettings.CaptureDebugTraces, this.reflectHelper).Execute();
|
|
||||||
this.RunClassCleanupIfEndOfClass(testMethodInfo, testMethod, result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
catch (TypeInspectionException ex)
|
|
||||||
{
|
|
||||||
// Catch any exception thrown while inspecting the test method and return failure.
|
|
||||||
return new UnitTestResult[] { new UnitTestResult(ObjectModel.UnitTestOutcome.Failed, ex.Message) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Runs the class cleanup method.
|
|
||||||
/// It returns any error information during the execution of the cleanup method
|
|
||||||
/// </summary>
|
|
||||||
/// <returns> The <see cref="RunCleanupResult"/>. </returns>
|
|
||||||
internal RunCleanupResult RunCleanup()
|
|
||||||
{
|
|
||||||
// No cleanup methods to execute, then return.
|
|
||||||
var assemblyInfoCache = this.typeCache.AssemblyInfoListWithExecutableCleanupMethods;
|
|
||||||
var classInfoCache = this.typeCache.ClassInfoListWithExecutableCleanupMethods;
|
|
||||||
if (!assemblyInfoCache.Any() && !classInfoCache.Any())
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = new RunCleanupResult { Warnings = new List<string>() };
|
|
||||||
|
|
||||||
using (var redirector = new LogMessageListener(MSTestSettings.CurrentSettings.CaptureDebugTraces))
|
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.RunClassCleanupMethods(classInfoCache, result.Warnings);
|
// Class cleanup can throw exceptions in which case we need to ensure that we fail the test.
|
||||||
this.RunAssemblyCleanup(assemblyInfoCache, result.Warnings);
|
testMethodInfo.Parent.RunClassCleanup(ClassCleanupBehavior.EndOfClass);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// Replacing the null character with a string.replace should work.
|
cleanupLogs = logListener.StandardOutput;
|
||||||
// If this does not work for a specific dotnet version a custom function doing the same needs to be put in place.
|
cleanupTrace = logListener.DebugTrace;
|
||||||
result.StandardOut = redirector.GetAndClearStandardOutput()?.Replace("\0", "\\0");
|
cleanupErrorLogs = logListener.StandardError;
|
||||||
result.StandardError = redirector.GetAndClearStandardError()?.Replace("\0", "\\0");
|
var lastResult = results[results.Length - 1];
|
||||||
result.DebugTrace = redirector.GetAndClearDebugTrace()?.Replace("\0", "\\0");
|
lastResult.StandardOut += cleanupLogs;
|
||||||
|
lastResult.StandardError += cleanupErrorLogs;
|
||||||
|
lastResult.DebugTrace += cleanupTrace;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RunClassCleanupIfEndOfClass(TestMethodInfo testMethodInfo, TestMethod testMethod, UnitTestResult[] results)
|
|
||||||
{
|
|
||||||
bool shouldRunClassCleanup = false;
|
|
||||||
this.classCleanupManager?.MarkTestComplete(testMethodInfo, testMethod, out shouldRunClassCleanup);
|
|
||||||
if (shouldRunClassCleanup)
|
|
||||||
{
|
{
|
||||||
string cleanupLogs = string.Empty;
|
results[results.Length - 1].Outcome = ObjectModel.UnitTestOutcome.Failed;
|
||||||
string cleanupTrace = string.Empty;
|
results[results.Length - 1].ErrorMessage = e.Message;
|
||||||
string cleanupErrorLogs = string.Empty;
|
results[results.Length - 1].ErrorStackTrace = e.StackTrace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try
|
/// <summary>
|
||||||
|
/// Whether the given testMethod is runnable
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="testMethod">The testMethod</param>
|
||||||
|
/// <param name="testMethodInfo">The testMethodInfo</param>
|
||||||
|
/// <param name="notRunnableResult">The results to return if the test method is not runnable</param>
|
||||||
|
/// <returns>whether the given testMethod is runnable</returns>
|
||||||
|
private bool IsTestMethodRunnable(
|
||||||
|
TestMethod testMethod,
|
||||||
|
TestMethodInfo testMethodInfo,
|
||||||
|
out UnitTestResult[] notRunnableResult)
|
||||||
|
{
|
||||||
|
// If the specified TestMethod could not be found, return a NotFound result.
|
||||||
|
if (testMethodInfo == null)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
notRunnableResult = new UnitTestResult[]
|
||||||
{
|
{
|
||||||
using LogMessageListener logListener =
|
new UnitTestResult(
|
||||||
new(MSTestSettings.CurrentSettings.CaptureDebugTraces);
|
ObjectModel.UnitTestOutcome.NotFound,
|
||||||
try
|
string.Format(CultureInfo.CurrentCulture, Resource.TestNotFound, testMethod.Name))
|
||||||
{
|
};
|
||||||
// Class cleanup can throw exceptions in which case we need to ensure that we fail the test.
|
return false;
|
||||||
testMethodInfo.Parent.RunClassCleanup(ClassCleanupBehavior.EndOfClass);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
cleanupLogs = logListener.StandardOutput;
|
|
||||||
cleanupTrace = logListener.DebugTrace;
|
|
||||||
cleanupErrorLogs = logListener.StandardError;
|
|
||||||
var lastResult = results[results.Length - 1];
|
|
||||||
lastResult.StandardOut += cleanupLogs;
|
|
||||||
lastResult.StandardError += cleanupErrorLogs;
|
|
||||||
lastResult.DebugTrace += cleanupTrace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
results[results.Length - 1].Outcome = ObjectModel.UnitTestOutcome.Failed;
|
|
||||||
results[results.Length - 1].ErrorMessage = e.Message;
|
|
||||||
results[results.Length - 1].ErrorStackTrace = e.StackTrace;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// If test cannot be executed, then bail out.
|
||||||
/// Whether the given testMethod is runnable
|
if (!testMethodInfo.IsRunnable)
|
||||||
/// </summary>
|
|
||||||
/// <param name="testMethod">The testMethod</param>
|
|
||||||
/// <param name="testMethodInfo">The testMethodInfo</param>
|
|
||||||
/// <param name="notRunnableResult">The results to return if the test method is not runnable</param>
|
|
||||||
/// <returns>whether the given testMethod is runnable</returns>
|
|
||||||
private bool IsTestMethodRunnable(
|
|
||||||
TestMethod testMethod,
|
|
||||||
TestMethodInfo testMethodInfo,
|
|
||||||
out UnitTestResult[] notRunnableResult)
|
|
||||||
{
|
{
|
||||||
// If the specified TestMethod could not be found, return a NotFound result.
|
|
||||||
if (testMethodInfo == null)
|
|
||||||
{
|
{
|
||||||
{
|
notRunnableResult = new UnitTestResult[]
|
||||||
notRunnableResult = new UnitTestResult[]
|
{ new UnitTestResult(ObjectModel.UnitTestOutcome.NotRunnable, testMethodInfo.NotRunnableReason) };
|
||||||
{
|
return false;
|
||||||
new UnitTestResult(
|
|
||||||
ObjectModel.UnitTestOutcome.NotFound,
|
|
||||||
string.Format(CultureInfo.CurrentCulture, Resource.TestNotFound, testMethod.Name))
|
|
||||||
};
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If test cannot be executed, then bail out.
|
|
||||||
if (!testMethodInfo.IsRunnable)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
notRunnableResult = new UnitTestResult[]
|
|
||||||
{ new UnitTestResult(ObjectModel.UnitTestOutcome.NotRunnable, testMethodInfo.NotRunnableReason) };
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string ignoreMessage = null;
|
|
||||||
var isIgnoreAttributeOnClass =
|
|
||||||
this.reflectHelper.IsAttributeDefined(testMethodInfo.Parent.ClassType, typeof(UTF.IgnoreAttribute), false);
|
|
||||||
var isIgnoreAttributeOnMethod =
|
|
||||||
this.reflectHelper.IsAttributeDefined(testMethodInfo.TestMethod, typeof(UTF.IgnoreAttribute), false);
|
|
||||||
|
|
||||||
if (isIgnoreAttributeOnClass)
|
|
||||||
{
|
|
||||||
ignoreMessage = this.reflectHelper.GetIgnoreMessage(testMethodInfo.Parent.ClassType.GetTypeInfo());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(ignoreMessage) && isIgnoreAttributeOnMethod)
|
|
||||||
{
|
|
||||||
ignoreMessage = this.reflectHelper.GetIgnoreMessage(testMethodInfo.TestMethod);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isIgnoreAttributeOnClass || isIgnoreAttributeOnMethod)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
notRunnableResult = new[] { new UnitTestResult(ObjectModel.UnitTestOutcome.Ignored, ignoreMessage) };
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
notRunnableResult = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Run assembly cleanup methods
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assemblyInfoCache"> The assembly Info Cache. </param>
|
|
||||||
/// <param name="warnings"> The warnings. </param>
|
|
||||||
private void RunAssemblyCleanup(IEnumerable<TestAssemblyInfo> assemblyInfoCache, IList<string> warnings)
|
|
||||||
{
|
|
||||||
foreach (var assemblyInfo in assemblyInfoCache)
|
|
||||||
{
|
|
||||||
Debug.Assert(assemblyInfo.HasExecutableCleanupMethod, "HasExecutableCleanupMethod should be true.");
|
|
||||||
|
|
||||||
var warning = assemblyInfo.RunAssemblyCleanup();
|
|
||||||
if (warning != null)
|
|
||||||
{
|
|
||||||
warnings.Add(warning);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
string ignoreMessage = null;
|
||||||
/// Run class cleanup methods
|
var isIgnoreAttributeOnClass =
|
||||||
/// </summary>
|
this.reflectHelper.IsAttributeDefined(testMethodInfo.Parent.ClassType, typeof(UTF.IgnoreAttribute), false);
|
||||||
/// <param name="classInfoCache"> The class Info Cache. </param>
|
var isIgnoreAttributeOnMethod =
|
||||||
/// <param name="warnings"> The warnings. </param>
|
this.reflectHelper.IsAttributeDefined(testMethodInfo.TestMethod, typeof(UTF.IgnoreAttribute), false);
|
||||||
private void RunClassCleanupMethods(IEnumerable<TestClassInfo> classInfoCache, IList<string> warnings)
|
|
||||||
{
|
|
||||||
foreach (var classInfo in classInfoCache)
|
|
||||||
{
|
|
||||||
Debug.Assert(classInfo.HasExecutableCleanupMethod, "HasExecutableCleanupMethod should be true.");
|
|
||||||
|
|
||||||
var warning = classInfo.RunClassCleanup();
|
if (isIgnoreAttributeOnClass)
|
||||||
if (warning != null)
|
{
|
||||||
{
|
ignoreMessage = this.reflectHelper.GetIgnoreMessage(testMethodInfo.Parent.ClassType.GetTypeInfo());
|
||||||
warnings.Add(warning);
|
}
|
||||||
}
|
|
||||||
|
if (string.IsNullOrEmpty(ignoreMessage) && isIgnoreAttributeOnMethod)
|
||||||
|
{
|
||||||
|
ignoreMessage = this.reflectHelper.GetIgnoreMessage(testMethodInfo.TestMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isIgnoreAttributeOnClass || isIgnoreAttributeOnMethod)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
notRunnableResult = new[] { new UnitTestResult(ObjectModel.UnitTestOutcome.Ignored, ignoreMessage) };
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ClassCleanupManager
|
notRunnableResult = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run assembly cleanup methods
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assemblyInfoCache"> The assembly Info Cache. </param>
|
||||||
|
/// <param name="warnings"> The warnings. </param>
|
||||||
|
private void RunAssemblyCleanup(IEnumerable<TestAssemblyInfo> assemblyInfoCache, IList<string> warnings)
|
||||||
|
{
|
||||||
|
foreach (var assemblyInfo in assemblyInfoCache)
|
||||||
{
|
{
|
||||||
private readonly ClassCleanupBehavior? lifecycleFromMsTest;
|
Debug.Assert(assemblyInfo.HasExecutableCleanupMethod, "HasExecutableCleanupMethod should be true.");
|
||||||
private readonly ClassCleanupBehavior lifecycleFromAssembly;
|
|
||||||
private readonly ReflectHelper reflectHelper;
|
|
||||||
private readonly Dictionary<string, HashSet<string>> remainingTestsByClass;
|
|
||||||
|
|
||||||
public ClassCleanupManager(
|
var warning = assemblyInfo.RunAssemblyCleanup();
|
||||||
IEnumerable<UnitTestElement> testsToRun,
|
if (warning != null)
|
||||||
ClassCleanupBehavior? lifecycleFromMsTest,
|
|
||||||
ClassCleanupBehavior lifecycleFromAssembly,
|
|
||||||
ReflectHelper reflectHelper = null)
|
|
||||||
{
|
{
|
||||||
this.remainingTestsByClass = testsToRun.GroupBy(t => t.TestMethod.FullClassName)
|
warnings.Add(warning);
|
||||||
.ToDictionary(
|
|
||||||
g => g.Key,
|
|
||||||
g => new HashSet<string>(g.Select(t => t.DisplayName)));
|
|
||||||
this.lifecycleFromMsTest = lifecycleFromMsTest;
|
|
||||||
this.lifecycleFromAssembly = lifecycleFromAssembly;
|
|
||||||
this.reflectHelper = reflectHelper ?? new ReflectHelper();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void MarkTestComplete(TestMethodInfo testMethodInfo, TestMethod testMethod, out bool shouldCleanup)
|
/// <summary>
|
||||||
|
/// Run class cleanup methods
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="classInfoCache"> The class Info Cache. </param>
|
||||||
|
/// <param name="warnings"> The warnings. </param>
|
||||||
|
private void RunClassCleanupMethods(IEnumerable<TestClassInfo> classInfoCache, IList<string> warnings)
|
||||||
|
{
|
||||||
|
foreach (var classInfo in classInfoCache)
|
||||||
|
{
|
||||||
|
Debug.Assert(classInfo.HasExecutableCleanupMethod, "HasExecutableCleanupMethod should be true.");
|
||||||
|
|
||||||
|
var warning = classInfo.RunClassCleanup();
|
||||||
|
if (warning != null)
|
||||||
{
|
{
|
||||||
shouldCleanup = false;
|
warnings.Add(warning);
|
||||||
var testsByClass = this.remainingTestsByClass[testMethodInfo.TestClassName];
|
}
|
||||||
lock (testsByClass)
|
}
|
||||||
{
|
}
|
||||||
testsByClass.Remove(testMethod.DisplayName);
|
|
||||||
if (testsByClass.Count == 0 && testMethodInfo.Parent.HasExecutableCleanupMethod)
|
|
||||||
{
|
|
||||||
var cleanupLifecycle = this.reflectHelper.GetClassCleanupBehavior(testMethodInfo.Parent)
|
|
||||||
?? this.lifecycleFromMsTest
|
|
||||||
?? this.lifecycleFromAssembly;
|
|
||||||
|
|
||||||
shouldCleanup = cleanupLifecycle == ClassCleanupBehavior.EndOfClass;
|
private class ClassCleanupManager
|
||||||
}
|
{
|
||||||
|
private readonly ClassCleanupBehavior? lifecycleFromMsTest;
|
||||||
|
private readonly ClassCleanupBehavior lifecycleFromAssembly;
|
||||||
|
private readonly ReflectHelper reflectHelper;
|
||||||
|
private readonly Dictionary<string, HashSet<string>> remainingTestsByClass;
|
||||||
|
|
||||||
|
public ClassCleanupManager(
|
||||||
|
IEnumerable<UnitTestElement> testsToRun,
|
||||||
|
ClassCleanupBehavior? lifecycleFromMsTest,
|
||||||
|
ClassCleanupBehavior lifecycleFromAssembly,
|
||||||
|
ReflectHelper reflectHelper = null)
|
||||||
|
{
|
||||||
|
this.remainingTestsByClass = testsToRun.GroupBy(t => t.TestMethod.FullClassName)
|
||||||
|
.ToDictionary(
|
||||||
|
g => g.Key,
|
||||||
|
g => new HashSet<string>(g.Select(t => t.DisplayName)));
|
||||||
|
this.lifecycleFromMsTest = lifecycleFromMsTest;
|
||||||
|
this.lifecycleFromAssembly = lifecycleFromAssembly;
|
||||||
|
this.reflectHelper = reflectHelper ?? new ReflectHelper();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MarkTestComplete(TestMethodInfo testMethodInfo, TestMethod testMethod, out bool shouldCleanup)
|
||||||
|
{
|
||||||
|
shouldCleanup = false;
|
||||||
|
var testsByClass = this.remainingTestsByClass[testMethodInfo.TestClassName];
|
||||||
|
lock (testsByClass)
|
||||||
|
{
|
||||||
|
testsByClass.Remove(testMethod.DisplayName);
|
||||||
|
if (testsByClass.Count == 0 && testMethodInfo.Parent.HasExecutableCleanupMethod)
|
||||||
|
{
|
||||||
|
var cleanupLifecycle = this.reflectHelper.GetClassCleanupBehavior(testMethodInfo.Parent)
|
||||||
|
?? this.lifecycleFromMsTest
|
||||||
|
?? this.lifecycleFromAssembly;
|
||||||
|
|
||||||
|
shouldCleanup = cleanupLifecycle == ClassCleanupBehavior.EndOfClass;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,89 +1,88 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
using Execution;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
using UTF = Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extensions to <see cref="Exception"/> type.
|
||||||
|
/// </summary>
|
||||||
|
internal static class ExceptionExtensions
|
||||||
{
|
{
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Globalization;
|
/// Get the InnerException if available, else return the current Exception.
|
||||||
using System.Text;
|
/// </summary>
|
||||||
using Execution;
|
/// <param name="exception">The exception.</param>
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
/// <returns>
|
||||||
using UTF = Microsoft.VisualStudio.TestTools.UnitTesting;
|
/// An <see cref="Exception"/> instance.
|
||||||
|
/// </returns>
|
||||||
|
internal static Exception GetInnerExceptionOrDefault(this Exception exception)
|
||||||
|
{
|
||||||
|
return exception?.InnerException ?? exception;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extensions to <see cref="Exception"/> type.
|
/// Get the exception message if available, empty otherwise.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class ExceptionExtensions
|
/// <param name="exception">An <see cref="Exception"/> object</param>
|
||||||
|
/// <returns>Exception message</returns>
|
||||||
|
internal static string TryGetMessage(this Exception exception)
|
||||||
{
|
{
|
||||||
/// <summary>
|
if (exception == null)
|
||||||
/// Get the InnerException if available, else return the current Exception.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="exception">The exception.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// An <see cref="Exception"/> instance.
|
|
||||||
/// </returns>
|
|
||||||
internal static Exception GetInnerExceptionOrDefault(this Exception exception)
|
|
||||||
{
|
{
|
||||||
return exception?.InnerException ?? exception;
|
return string.Format(CultureInfo.CurrentCulture, Resource.UTF_FailedToGetExceptionMessage, "null");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// It is safe to retrieve an exception message, it should not throw in any case.
|
||||||
/// Get the exception message if available, empty otherwise.
|
return exception.Message ?? string.Empty;
|
||||||
/// </summary>
|
}
|
||||||
/// <param name="exception">An <see cref="Exception"/> object</param>
|
|
||||||
/// <returns>Exception message</returns>
|
|
||||||
internal static string TryGetMessage(this Exception exception)
|
|
||||||
{
|
|
||||||
if (exception == null)
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, Resource.UTF_FailedToGetExceptionMessage, "null");
|
|
||||||
}
|
|
||||||
|
|
||||||
// It is safe to retrieve an exception message, it should not throw in any case.
|
/// <summary>
|
||||||
return exception.Message ?? string.Empty;
|
/// Gets the <see cref="StackTraceInformation"/> for an exception.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="exception">An <see cref="Exception"/> instance.</param>
|
||||||
|
/// <returns>StackTraceInformation for the exception</returns>
|
||||||
|
internal static StackTraceInformation TryGetStackTraceInformation(this Exception exception)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(exception?.StackTrace))
|
||||||
|
{
|
||||||
|
return StackTraceHelper.CreateStackTraceInformation(exception, false, exception.StackTrace);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return null;
|
||||||
/// Gets the <see cref="StackTraceInformation"/> for an exception.
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="exception">An <see cref="Exception"/> instance.</param>
|
|
||||||
/// <returns>StackTraceInformation for the exception</returns>
|
|
||||||
internal static StackTraceInformation TryGetStackTraceInformation(this Exception exception)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(exception?.StackTrace))
|
|
||||||
{
|
|
||||||
return StackTraceHelper.CreateStackTraceInformation(exception, false, exception.StackTrace);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
/// <summary>
|
||||||
|
/// Checks whether exception is an Assert exception
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="exception">An <see cref="Exception"/> instance.</param>
|
||||||
|
/// <param name="outcome"> Framework's Outcome depending on type of assertion.</param>
|
||||||
|
/// <param name="exceptionMessage">Exception message.</param>
|
||||||
|
/// <param name="exceptionStackTrace">StackTraceInformation for the exception</param>
|
||||||
|
/// <returns>True, if Assert exception. False, otherwise.</returns>
|
||||||
|
internal static bool TryGetUnitTestAssertException(this Exception exception, out UTF.UnitTestOutcome outcome, out string exceptionMessage, out StackTraceInformation exceptionStackTrace)
|
||||||
|
{
|
||||||
|
if (exception is UTF.UnitTestAssertException)
|
||||||
|
{
|
||||||
|
outcome = exception is UTF.AssertInconclusiveException ?
|
||||||
|
UTF.UnitTestOutcome.Inconclusive : UTF.UnitTestOutcome.Failed;
|
||||||
|
|
||||||
|
exceptionMessage = exception.TryGetMessage();
|
||||||
|
exceptionStackTrace = exception.TryGetStackTraceInformation();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
/// <summary>
|
|
||||||
/// Checks whether exception is an Assert exception
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="exception">An <see cref="Exception"/> instance.</param>
|
|
||||||
/// <param name="outcome"> Framework's Outcome depending on type of assertion.</param>
|
|
||||||
/// <param name="exceptionMessage">Exception message.</param>
|
|
||||||
/// <param name="exceptionStackTrace">StackTraceInformation for the exception</param>
|
|
||||||
/// <returns>True, if Assert exception. False, otherwise.</returns>
|
|
||||||
internal static bool TryGetUnitTestAssertException(this Exception exception, out UTF.UnitTestOutcome outcome, out string exceptionMessage, out StackTraceInformation exceptionStackTrace)
|
|
||||||
{
|
{
|
||||||
if (exception is UTF.UnitTestAssertException)
|
outcome = UTF.UnitTestOutcome.Failed;
|
||||||
{
|
exceptionMessage = null;
|
||||||
outcome = exception is UTF.AssertInconclusiveException ?
|
exceptionStackTrace = null;
|
||||||
UTF.UnitTestOutcome.Inconclusive : UTF.UnitTestOutcome.Failed;
|
return false;
|
||||||
|
|
||||||
exceptionMessage = exception.TryGetMessage();
|
|
||||||
exceptionStackTrace = exception.TryGetStackTraceInformation();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
outcome = UTF.UnitTestOutcome.Failed;
|
|
||||||
exceptionMessage = null;
|
|
||||||
exceptionStackTrace = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,164 +1,163 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
internal static class MethodInfoExtensions
|
||||||
{
|
{
|
||||||
using System.Diagnostics;
|
/// <summary>
|
||||||
using System.Linq;
|
/// Verifies that the class initialize has the correct signature
|
||||||
using System.Reflection;
|
/// </summary>
|
||||||
using System.Runtime.CompilerServices;
|
/// <param name="method">The method to verify.</param>
|
||||||
using System.Threading.Tasks;
|
/// <returns>True if the method has the right Assembly/Class initialize signature.</returns>
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
internal static bool HasCorrectClassOrAssemblyInitializeSignature(this MethodInfo method)
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
|
|
||||||
internal static class MethodInfoExtensions
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
Debug.Assert(method != null, "method should not be null.");
|
||||||
/// Verifies that the class initialize has the correct signature
|
|
||||||
/// </summary>
|
ParameterInfo[] parameters = method.GetParameters();
|
||||||
/// <param name="method">The method to verify.</param>
|
|
||||||
/// <returns>True if the method has the right Assembly/Class initialize signature.</returns>
|
return
|
||||||
internal static bool HasCorrectClassOrAssemblyInitializeSignature(this MethodInfo method)
|
method.IsStatic &&
|
||||||
|
method.IsPublic &&
|
||||||
|
(parameters.Length == 1) &&
|
||||||
|
parameters[0].ParameterType == typeof(TestContext) &&
|
||||||
|
method.IsVoidOrTaskReturnType();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that the class cleanup has the correct signature
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method">The method to verify.</param>
|
||||||
|
/// <returns>True if the method has the right Assembly/Class cleanup signature.</returns>
|
||||||
|
internal static bool HasCorrectClassOrAssemblyCleanupSignature(this MethodInfo method)
|
||||||
|
{
|
||||||
|
Debug.Assert(method != null, "method should not be null.");
|
||||||
|
|
||||||
|
return
|
||||||
|
method.IsStatic &&
|
||||||
|
method.IsPublic &&
|
||||||
|
(method.GetParameters().Length == 0) &&
|
||||||
|
method.IsVoidOrTaskReturnType();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that the test Initialize/cleanup has the correct signature
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method">The method to verify.</param>
|
||||||
|
/// <returns>True if the method has the right test init/cleanup signature.</returns>
|
||||||
|
internal static bool HasCorrectTestInitializeOrCleanupSignature(this MethodInfo method)
|
||||||
|
{
|
||||||
|
Debug.Assert(method != null, "method should not be null.");
|
||||||
|
|
||||||
|
return
|
||||||
|
!method.IsStatic &&
|
||||||
|
method.IsPublic &&
|
||||||
|
(method.GetParameters().Length == 0) &&
|
||||||
|
method.IsVoidOrTaskReturnType();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that the test method has the correct signature
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method">The method to verify.</param>
|
||||||
|
/// <param name="ignoreParameterLength">Indicates whether parameter length is to be ignored.</param>
|
||||||
|
/// <param name="discoverInternals">True if internal test classes and test methods should be discovered in
|
||||||
|
/// addition to public test classes and methods.</param>
|
||||||
|
/// <returns>True if the method has the right test method signature.</returns>
|
||||||
|
internal static bool HasCorrectTestMethodSignature(this MethodInfo method, bool ignoreParameterLength, bool discoverInternals = false)
|
||||||
|
{
|
||||||
|
Debug.Assert(method != null, "method should not be null.");
|
||||||
|
|
||||||
|
return
|
||||||
|
!method.IsAbstract &&
|
||||||
|
!method.IsStatic &&
|
||||||
|
!method.IsGenericMethod &&
|
||||||
|
(method.IsPublic || (discoverInternals && method.IsAssembly)) &&
|
||||||
|
(method.GetParameters().Length == 0 || ignoreParameterLength) &&
|
||||||
|
method.IsVoidOrTaskReturnType(); // Match return type Task for async methods only. Else return type void.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether test method has correct Timeout attribute.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method">The method to verify.</param>
|
||||||
|
/// <returns>True if the method has the right test timeout signature.</returns>
|
||||||
|
internal static bool HasCorrectTimeout(this MethodInfo method)
|
||||||
|
{
|
||||||
|
Debug.Assert(method != null, "method should not be null.");
|
||||||
|
|
||||||
|
// There should be one and only one TimeoutAttribute.
|
||||||
|
var attributes = ReflectHelper.GetCustomAttributes(method, typeof(TimeoutAttribute), false);
|
||||||
|
if (attributes?.Length != 1)
|
||||||
{
|
{
|
||||||
Debug.Assert(method != null, "method should not be null.");
|
return false;
|
||||||
|
|
||||||
ParameterInfo[] parameters = method.GetParameters();
|
|
||||||
|
|
||||||
return
|
|
||||||
method.IsStatic &&
|
|
||||||
method.IsPublic &&
|
|
||||||
(parameters.Length == 1) &&
|
|
||||||
parameters[0].ParameterType == typeof(TestContext) &&
|
|
||||||
method.IsVoidOrTaskReturnType();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// Timeout cannot be less than 0.
|
||||||
/// Verifies that the class cleanup has the correct signature
|
var attribute = attributes[0] as TimeoutAttribute;
|
||||||
/// </summary>
|
|
||||||
/// <param name="method">The method to verify.</param>
|
|
||||||
/// <returns>True if the method has the right Assembly/Class cleanup signature.</returns>
|
|
||||||
internal static bool HasCorrectClassOrAssemblyCleanupSignature(this MethodInfo method)
|
|
||||||
{
|
|
||||||
Debug.Assert(method != null, "method should not be null.");
|
|
||||||
|
|
||||||
return
|
return !(attribute?.Timeout < 0);
|
||||||
method.IsStatic &&
|
}
|
||||||
method.IsPublic &&
|
|
||||||
(method.GetParameters().Length == 0) &&
|
/// <summary>
|
||||||
method.IsVoidOrTaskReturnType();
|
/// Check is return type is void for non async and Task for async methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method">The method to verify.</param>
|
||||||
|
/// <returns>True if the method has a void/task return type..</returns>
|
||||||
|
internal static bool IsVoidOrTaskReturnType(this MethodInfo method)
|
||||||
|
{
|
||||||
|
return ReflectHelper.MatchReturnType(method, typeof(Task))
|
||||||
|
|| (ReflectHelper.MatchReturnType(method, typeof(void)) && method.GetAsyncTypeName() == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For async methods compiler generates different type and method.
|
||||||
|
/// Gets the compiler generated type name for given async test method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method">The method to verify.</param>
|
||||||
|
/// <returns>Compiler generated type name for given async test method..</returns>
|
||||||
|
internal static string GetAsyncTypeName(this MethodInfo method)
|
||||||
|
{
|
||||||
|
var asyncStateMachineAttribute = ReflectHelper.GetCustomAttributes(method, typeof(AsyncStateMachineAttribute), false).FirstOrDefault() as AsyncStateMachineAttribute;
|
||||||
|
|
||||||
|
return asyncStateMachineAttribute?.StateMachineType?.FullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoke a <see cref="MethodInfo"/> as a synchronous <see cref="Task"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="methodInfo">
|
||||||
|
/// <see cref="MethodInfo"/> instance.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="classInstance">
|
||||||
|
/// Instance of the on which methodInfo is invoked.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="parameters">
|
||||||
|
/// Arguments for the methodInfo invoke.
|
||||||
|
/// </param>
|
||||||
|
internal static void InvokeAsSynchronousTask(this MethodInfo methodInfo, object classInstance, params object[] parameters)
|
||||||
|
{
|
||||||
|
var methodParameters = methodInfo.GetParameters();
|
||||||
|
|
||||||
|
// check if testmethod expected parameter values but no testdata was provided,
|
||||||
|
// throw error with appropriate message.
|
||||||
|
if (methodParameters != null && methodParameters.Length > 0 && parameters == null)
|
||||||
|
{
|
||||||
|
throw new TestFailedException(ObjectModel.UnitTestOutcome.Error, Resource.UTA_TestMethodExpectedParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
var task = methodInfo.Invoke(classInstance, parameters) as Task;
|
||||||
/// Verifies that the test Initialize/cleanup has the correct signature
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method">The method to verify.</param>
|
|
||||||
/// <returns>True if the method has the right test init/cleanup signature.</returns>
|
|
||||||
internal static bool HasCorrectTestInitializeOrCleanupSignature(this MethodInfo method)
|
|
||||||
{
|
|
||||||
Debug.Assert(method != null, "method should not be null.");
|
|
||||||
|
|
||||||
return
|
// If methodInfo is an Async method, wait for returned task
|
||||||
!method.IsStatic &&
|
task?.GetAwaiter().GetResult();
|
||||||
method.IsPublic &&
|
|
||||||
(method.GetParameters().Length == 0) &&
|
|
||||||
method.IsVoidOrTaskReturnType();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Verifies that the test method has the correct signature
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method">The method to verify.</param>
|
|
||||||
/// <param name="ignoreParameterLength">Indicates whether parameter length is to be ignored.</param>
|
|
||||||
/// <param name="discoverInternals">True if internal test classes and test methods should be discovered in
|
|
||||||
/// addition to public test classes and methods.</param>
|
|
||||||
/// <returns>True if the method has the right test method signature.</returns>
|
|
||||||
internal static bool HasCorrectTestMethodSignature(this MethodInfo method, bool ignoreParameterLength, bool discoverInternals = false)
|
|
||||||
{
|
|
||||||
Debug.Assert(method != null, "method should not be null.");
|
|
||||||
|
|
||||||
return
|
|
||||||
!method.IsAbstract &&
|
|
||||||
!method.IsStatic &&
|
|
||||||
!method.IsGenericMethod &&
|
|
||||||
(method.IsPublic || (discoverInternals && method.IsAssembly)) &&
|
|
||||||
(method.GetParameters().Length == 0 || ignoreParameterLength) &&
|
|
||||||
method.IsVoidOrTaskReturnType(); // Match return type Task for async methods only. Else return type void.
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks whether test method has correct Timeout attribute.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method">The method to verify.</param>
|
|
||||||
/// <returns>True if the method has the right test timeout signature.</returns>
|
|
||||||
internal static bool HasCorrectTimeout(this MethodInfo method)
|
|
||||||
{
|
|
||||||
Debug.Assert(method != null, "method should not be null.");
|
|
||||||
|
|
||||||
// There should be one and only one TimeoutAttribute.
|
|
||||||
var attributes = ReflectHelper.GetCustomAttributes(method, typeof(TimeoutAttribute), false);
|
|
||||||
if (attributes?.Length != 1)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timeout cannot be less than 0.
|
|
||||||
var attribute = attributes[0] as TimeoutAttribute;
|
|
||||||
|
|
||||||
return !(attribute?.Timeout < 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Check is return type is void for non async and Task for async methods.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method">The method to verify.</param>
|
|
||||||
/// <returns>True if the method has a void/task return type..</returns>
|
|
||||||
internal static bool IsVoidOrTaskReturnType(this MethodInfo method)
|
|
||||||
{
|
|
||||||
return ReflectHelper.MatchReturnType(method, typeof(Task))
|
|
||||||
|| (ReflectHelper.MatchReturnType(method, typeof(void)) && method.GetAsyncTypeName() == null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// For async methods compiler generates different type and method.
|
|
||||||
/// Gets the compiler generated type name for given async test method.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method">The method to verify.</param>
|
|
||||||
/// <returns>Compiler generated type name for given async test method..</returns>
|
|
||||||
internal static string GetAsyncTypeName(this MethodInfo method)
|
|
||||||
{
|
|
||||||
var asyncStateMachineAttribute = ReflectHelper.GetCustomAttributes(method, typeof(AsyncStateMachineAttribute), false).FirstOrDefault() as AsyncStateMachineAttribute;
|
|
||||||
|
|
||||||
return asyncStateMachineAttribute?.StateMachineType?.FullName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoke a <see cref="MethodInfo"/> as a synchronous <see cref="Task"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="methodInfo">
|
|
||||||
/// <see cref="MethodInfo"/> instance.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="classInstance">
|
|
||||||
/// Instance of the on which methodInfo is invoked.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="parameters">
|
|
||||||
/// Arguments for the methodInfo invoke.
|
|
||||||
/// </param>
|
|
||||||
internal static void InvokeAsSynchronousTask(this MethodInfo methodInfo, object classInstance, params object[] parameters)
|
|
||||||
{
|
|
||||||
var methodParameters = methodInfo.GetParameters();
|
|
||||||
|
|
||||||
// check if testmethod expected parameter values but no testdata was provided,
|
|
||||||
// throw error with appropriate message.
|
|
||||||
if (methodParameters != null && methodParameters.Length > 0 && parameters == null)
|
|
||||||
{
|
|
||||||
throw new TestFailedException(ObjectModel.UnitTestOutcome.Error, Resource.UTA_TestMethodExpectedParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
var task = methodInfo.Invoke(classInstance, parameters) as Task;
|
|
||||||
|
|
||||||
// If methodInfo is an Async method, wait for returned task
|
|
||||||
task?.GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,168 +1,167 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Microsoft.TestPlatform.AdapterUtilities;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
|
||||||
|
using Constants = Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Constants;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension Methods for TestCase Class
|
||||||
|
/// </summary>
|
||||||
|
internal static class TestCaseExtensions
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
internal static readonly TestProperty ManagedTypeProperty = TestProperty.Register(
|
||||||
using System.Linq;
|
id: ManagedNameConstants.ManagedTypePropertyId,
|
||||||
|
label: ManagedNameConstants.ManagedTypeLabel,
|
||||||
|
category: string.Empty,
|
||||||
|
description: string.Empty,
|
||||||
|
valueType: typeof(string),
|
||||||
|
validateValueCallback: o => !string.IsNullOrWhiteSpace(o as string),
|
||||||
|
attributes: TestPropertyAttributes.Hidden,
|
||||||
|
owner: typeof(TestCase));
|
||||||
|
|
||||||
using Microsoft.TestPlatform.AdapterUtilities;
|
internal static readonly TestProperty ManagedMethodProperty = TestProperty.Register(
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
id: ManagedNameConstants.ManagedMethodPropertyId,
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
label: ManagedNameConstants.ManagedMethodLabel,
|
||||||
|
category: string.Empty,
|
||||||
|
description: string.Empty,
|
||||||
|
valueType: typeof(string),
|
||||||
|
validateValueCallback: o => !string.IsNullOrWhiteSpace(o as string),
|
||||||
|
attributes: TestPropertyAttributes.Hidden,
|
||||||
|
owner: typeof(TestCase));
|
||||||
|
|
||||||
using Constants = Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Constants;
|
internal static readonly TestProperty HierarchyProperty = TestProperty.Register(
|
||||||
|
id: HierarchyConstants.HierarchyPropertyId,
|
||||||
|
label: HierarchyConstants.HierarchyLabel,
|
||||||
|
category: string.Empty,
|
||||||
|
description: string.Empty,
|
||||||
|
valueType: typeof(string[]),
|
||||||
|
validateValueCallback: null,
|
||||||
|
attributes: TestPropertyAttributes.Immutable,
|
||||||
|
owner: typeof(TestCase));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extension Methods for TestCase Class
|
/// The test name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class TestCaseExtensions
|
/// <param name="testCase"> The test case. </param>
|
||||||
|
/// <param name="testClassName"> The test case's class name. </param>
|
||||||
|
/// <returns> The test name, without the class name, if provided. </returns>
|
||||||
|
internal static string GetTestName(this TestCase testCase, string testClassName)
|
||||||
{
|
{
|
||||||
internal static readonly TestProperty ManagedTypeProperty = TestProperty.Register(
|
var fullyQualifiedName = testCase.FullyQualifiedName;
|
||||||
id: ManagedNameConstants.ManagedTypePropertyId,
|
|
||||||
label: ManagedNameConstants.ManagedTypeLabel,
|
|
||||||
category: string.Empty,
|
|
||||||
description: string.Empty,
|
|
||||||
valueType: typeof(string),
|
|
||||||
validateValueCallback: o => !string.IsNullOrWhiteSpace(o as string),
|
|
||||||
attributes: TestPropertyAttributes.Hidden,
|
|
||||||
owner: typeof(TestCase));
|
|
||||||
|
|
||||||
internal static readonly TestProperty ManagedMethodProperty = TestProperty.Register(
|
// Not using Replace because there can be multiple instances of that string.
|
||||||
id: ManagedNameConstants.ManagedMethodPropertyId,
|
var name = fullyQualifiedName.StartsWith($"{testClassName}.")
|
||||||
label: ManagedNameConstants.ManagedMethodLabel,
|
? fullyQualifiedName.Remove(0, $"{testClassName}.".Length)
|
||||||
category: string.Empty,
|
: fullyQualifiedName;
|
||||||
description: string.Empty,
|
|
||||||
valueType: typeof(string),
|
|
||||||
validateValueCallback: o => !string.IsNullOrWhiteSpace(o as string),
|
|
||||||
attributes: TestPropertyAttributes.Hidden,
|
|
||||||
owner: typeof(TestCase));
|
|
||||||
|
|
||||||
internal static readonly TestProperty HierarchyProperty = TestProperty.Register(
|
return name;
|
||||||
id: HierarchyConstants.HierarchyPropertyId,
|
|
||||||
label: HierarchyConstants.HierarchyLabel,
|
|
||||||
category: string.Empty,
|
|
||||||
description: string.Empty,
|
|
||||||
valueType: typeof(string[]),
|
|
||||||
validateValueCallback: null,
|
|
||||||
attributes: TestPropertyAttributes.Immutable,
|
|
||||||
owner: typeof(TestCase));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The test name
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="testCase"> The test case. </param>
|
|
||||||
/// <param name="testClassName"> The test case's class name. </param>
|
|
||||||
/// <returns> The test name, without the class name, if provided. </returns>
|
|
||||||
internal static string GetTestName(this TestCase testCase, string testClassName)
|
|
||||||
{
|
|
||||||
var fullyQualifiedName = testCase.FullyQualifiedName;
|
|
||||||
|
|
||||||
// Not using Replace because there can be multiple instances of that string.
|
|
||||||
var name = fullyQualifiedName.StartsWith($"{testClassName}.")
|
|
||||||
? fullyQualifiedName.Remove(0, $"{testClassName}.".Length)
|
|
||||||
: fullyQualifiedName;
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The to unit test element.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="testCase"> The test case. </param>
|
|
||||||
/// <param name="source"> The source. If deployed this is the full path of the source in the deployment directory. </param>
|
|
||||||
/// <returns> The converted <see cref="UnitTestElement"/>. </returns>
|
|
||||||
internal static UnitTestElement ToUnitTestElement(this TestCase testCase, string source)
|
|
||||||
{
|
|
||||||
var isAsync = (testCase.GetPropertyValue(Constants.AsyncTestProperty) as bool?) ?? false;
|
|
||||||
var testClassName = testCase.GetPropertyValue(Constants.TestClassNameProperty) as string;
|
|
||||||
var name = testCase.GetTestName(testClassName);
|
|
||||||
|
|
||||||
TestMethod testMethod;
|
|
||||||
if (testCase.ContainsManagedMethodAndType())
|
|
||||||
{
|
|
||||||
testMethod = new TestMethod(testCase.GetManagedType(), testCase.GetManagedMethod(), testCase.GetHierarchy(), name, testClassName, source, isAsync);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
testMethod = new TestMethod(name, testClassName, source, isAsync);
|
|
||||||
}
|
|
||||||
|
|
||||||
var dataType = (DynamicDataType)testCase.GetPropertyValue(Constants.TestDynamicDataTypeProperty, (int)DynamicDataType.None);
|
|
||||||
if (dataType != DynamicDataType.None)
|
|
||||||
{
|
|
||||||
var data = testCase.GetPropertyValue<string[]>(Constants.TestDynamicDataProperty, null);
|
|
||||||
|
|
||||||
testMethod.DataType = dataType;
|
|
||||||
testMethod.SerializedData = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
testMethod.DisplayName = testCase.DisplayName;
|
|
||||||
|
|
||||||
if (testCase.GetPropertyValue(Constants.DeclaringClassNameProperty) is string declaringClassName && declaringClassName != testClassName)
|
|
||||||
{
|
|
||||||
testMethod.DeclaringClassFullName = declaringClassName;
|
|
||||||
}
|
|
||||||
|
|
||||||
UnitTestElement testElement = new(testMethod)
|
|
||||||
{
|
|
||||||
IsAsync = isAsync,
|
|
||||||
TestCategory = testCase.GetPropertyValue(Constants.TestCategoryProperty) as string[],
|
|
||||||
Priority = testCase.GetPropertyValue(Constants.PriorityProperty) as int?,
|
|
||||||
DisplayName = testCase.DisplayName
|
|
||||||
};
|
|
||||||
|
|
||||||
if (testCase.Traits.Any())
|
|
||||||
{
|
|
||||||
testElement.Traits = testCase.Traits.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
var cssIteration = testCase.GetPropertyValue<string>(Constants.CssIterationProperty, null);
|
|
||||||
if (!string.IsNullOrWhiteSpace(cssIteration))
|
|
||||||
{
|
|
||||||
testElement.CssIteration = cssIteration;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cssProjectStructure = testCase.GetPropertyValue<string>(Constants.CssProjectStructureProperty, null);
|
|
||||||
if (!string.IsNullOrWhiteSpace(cssIteration))
|
|
||||||
{
|
|
||||||
testElement.CssProjectStructure = cssProjectStructure;
|
|
||||||
}
|
|
||||||
|
|
||||||
var description = testCase.GetPropertyValue<string>(Constants.DescriptionProperty, null);
|
|
||||||
if (!string.IsNullOrWhiteSpace(description))
|
|
||||||
{
|
|
||||||
testElement.Description = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
var workItemIds = testCase.GetPropertyValue<string[]>(Constants.WorkItemIdsProperty, null);
|
|
||||||
if (workItemIds != null && workItemIds.Length > 0)
|
|
||||||
{
|
|
||||||
testElement.WorkItemIds = workItemIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
var deploymentItems = testCase.GetPropertyValue<KeyValuePair<string, string>[]>(Constants.DeploymentItemsProperty, null);
|
|
||||||
if (deploymentItems != null && deploymentItems.Length > 0)
|
|
||||||
{
|
|
||||||
testElement.DeploymentItems = deploymentItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
testElement.DoNotParallelize = testCase.GetPropertyValue(Constants.DoNotParallelizeProperty, false);
|
|
||||||
|
|
||||||
return testElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static string GetManagedType(this TestCase testCase) => testCase.GetPropertyValue<string>(ManagedTypeProperty, null);
|
|
||||||
|
|
||||||
internal static void SetManagedType(this TestCase testCase, string value) => testCase.SetPropertyValue<string>(ManagedTypeProperty, value);
|
|
||||||
|
|
||||||
internal static string GetManagedMethod(this TestCase testCase) => testCase.GetPropertyValue<string>(ManagedMethodProperty, null);
|
|
||||||
|
|
||||||
internal static void SetManagedMethod(this TestCase testCase, string value) => testCase.SetPropertyValue<string>(ManagedMethodProperty, value);
|
|
||||||
|
|
||||||
internal static bool ContainsManagedMethodAndType(this TestCase testCase) => !string.IsNullOrWhiteSpace(testCase.GetManagedMethod()) && !string.IsNullOrWhiteSpace(testCase.GetManagedType());
|
|
||||||
|
|
||||||
internal static string[] GetHierarchy(this TestCase testCase) => testCase.GetPropertyValue<string[]>(HierarchyProperty, null);
|
|
||||||
|
|
||||||
internal static void SetHierarchy(this TestCase testCase, params string[] value) => testCase.SetPropertyValue<string[]>(HierarchyProperty, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The to unit test element.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="testCase"> The test case. </param>
|
||||||
|
/// <param name="source"> The source. If deployed this is the full path of the source in the deployment directory. </param>
|
||||||
|
/// <returns> The converted <see cref="UnitTestElement"/>. </returns>
|
||||||
|
internal static UnitTestElement ToUnitTestElement(this TestCase testCase, string source)
|
||||||
|
{
|
||||||
|
var isAsync = (testCase.GetPropertyValue(Constants.AsyncTestProperty) as bool?) ?? false;
|
||||||
|
var testClassName = testCase.GetPropertyValue(Constants.TestClassNameProperty) as string;
|
||||||
|
var name = testCase.GetTestName(testClassName);
|
||||||
|
|
||||||
|
TestMethod testMethod;
|
||||||
|
if (testCase.ContainsManagedMethodAndType())
|
||||||
|
{
|
||||||
|
testMethod = new TestMethod(testCase.GetManagedType(), testCase.GetManagedMethod(), testCase.GetHierarchy(), name, testClassName, source, isAsync);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
testMethod = new TestMethod(name, testClassName, source, isAsync);
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataType = (DynamicDataType)testCase.GetPropertyValue(Constants.TestDynamicDataTypeProperty, (int)DynamicDataType.None);
|
||||||
|
if (dataType != DynamicDataType.None)
|
||||||
|
{
|
||||||
|
var data = testCase.GetPropertyValue<string[]>(Constants.TestDynamicDataProperty, null);
|
||||||
|
|
||||||
|
testMethod.DataType = dataType;
|
||||||
|
testMethod.SerializedData = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
testMethod.DisplayName = testCase.DisplayName;
|
||||||
|
|
||||||
|
if (testCase.GetPropertyValue(Constants.DeclaringClassNameProperty) is string declaringClassName && declaringClassName != testClassName)
|
||||||
|
{
|
||||||
|
testMethod.DeclaringClassFullName = declaringClassName;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnitTestElement testElement = new(testMethod)
|
||||||
|
{
|
||||||
|
IsAsync = isAsync,
|
||||||
|
TestCategory = testCase.GetPropertyValue(Constants.TestCategoryProperty) as string[],
|
||||||
|
Priority = testCase.GetPropertyValue(Constants.PriorityProperty) as int?,
|
||||||
|
DisplayName = testCase.DisplayName
|
||||||
|
};
|
||||||
|
|
||||||
|
if (testCase.Traits.Any())
|
||||||
|
{
|
||||||
|
testElement.Traits = testCase.Traits.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
var cssIteration = testCase.GetPropertyValue<string>(Constants.CssIterationProperty, null);
|
||||||
|
if (!string.IsNullOrWhiteSpace(cssIteration))
|
||||||
|
{
|
||||||
|
testElement.CssIteration = cssIteration;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cssProjectStructure = testCase.GetPropertyValue<string>(Constants.CssProjectStructureProperty, null);
|
||||||
|
if (!string.IsNullOrWhiteSpace(cssIteration))
|
||||||
|
{
|
||||||
|
testElement.CssProjectStructure = cssProjectStructure;
|
||||||
|
}
|
||||||
|
|
||||||
|
var description = testCase.GetPropertyValue<string>(Constants.DescriptionProperty, null);
|
||||||
|
if (!string.IsNullOrWhiteSpace(description))
|
||||||
|
{
|
||||||
|
testElement.Description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
var workItemIds = testCase.GetPropertyValue<string[]>(Constants.WorkItemIdsProperty, null);
|
||||||
|
if (workItemIds != null && workItemIds.Length > 0)
|
||||||
|
{
|
||||||
|
testElement.WorkItemIds = workItemIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
var deploymentItems = testCase.GetPropertyValue<KeyValuePair<string, string>[]>(Constants.DeploymentItemsProperty, null);
|
||||||
|
if (deploymentItems != null && deploymentItems.Length > 0)
|
||||||
|
{
|
||||||
|
testElement.DeploymentItems = deploymentItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
testElement.DoNotParallelize = testCase.GetPropertyValue(Constants.DoNotParallelizeProperty, false);
|
||||||
|
|
||||||
|
return testElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string GetManagedType(this TestCase testCase) => testCase.GetPropertyValue<string>(ManagedTypeProperty, null);
|
||||||
|
|
||||||
|
internal static void SetManagedType(this TestCase testCase, string value) => testCase.SetPropertyValue<string>(ManagedTypeProperty, value);
|
||||||
|
|
||||||
|
internal static string GetManagedMethod(this TestCase testCase) => testCase.GetPropertyValue<string>(ManagedMethodProperty, null);
|
||||||
|
|
||||||
|
internal static void SetManagedMethod(this TestCase testCase, string value) => testCase.SetPropertyValue<string>(ManagedMethodProperty, value);
|
||||||
|
|
||||||
|
internal static bool ContainsManagedMethodAndType(this TestCase testCase) => !string.IsNullOrWhiteSpace(testCase.GetManagedMethod()) && !string.IsNullOrWhiteSpace(testCase.GetManagedType());
|
||||||
|
|
||||||
|
internal static string[] GetHierarchy(this TestCase testCase) => testCase.GetPropertyValue<string[]>(HierarchyProperty, null);
|
||||||
|
|
||||||
|
internal static void SetHierarchy(this TestCase testCase, params string[] value) => testCase.SetPropertyValue<string[]>(HierarchyProperty, value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,23 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
|
||||||
|
internal static class TestContextExtensions
|
||||||
{
|
{
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
/// <summary>
|
||||||
|
/// Returns diagnostic messages written to test context and clears from this instance.
|
||||||
internal static class TestContextExtensions
|
/// </summary>
|
||||||
|
/// <param name="testContext">The test context instance.</param>
|
||||||
|
/// <returns>The diagnostic messages.</returns>
|
||||||
|
internal static string GetAndClearDiagnosticMessages(this ITestContext testContext)
|
||||||
{
|
{
|
||||||
/// <summary>
|
var messages = testContext.GetDiagnosticMessages();
|
||||||
/// Returns diagnostic messages written to test context and clears from this instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="testContext">The test context instance.</param>
|
|
||||||
/// <returns>The diagnostic messages.</returns>
|
|
||||||
internal static string GetAndClearDiagnosticMessages(this ITestContext testContext)
|
|
||||||
{
|
|
||||||
var messages = testContext.GetDiagnosticMessages();
|
|
||||||
|
|
||||||
testContext.ClearDiagnosticMessages();
|
testContext.ClearDiagnosticMessages();
|
||||||
|
|
||||||
return messages;
|
return messages;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,57 +1,56 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
|
||||||
|
using UTF = Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
public static class TestResultExtensions
|
||||||
{
|
{
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
/// <summary>
|
||||||
|
/// Converts the test framework's TestResult objects array to a serializable UnitTestResult objects array.
|
||||||
using UTF = Microsoft.VisualStudio.TestTools.UnitTesting;
|
/// </summary>
|
||||||
|
/// <param name="testResults">The test framework's TestResult object array.</param>
|
||||||
public static class TestResultExtensions
|
/// <returns>The serializable UnitTestResult object array.</returns>
|
||||||
|
public static UnitTestResult[] ToUnitTestResults(this UTF.TestResult[] testResults)
|
||||||
{
|
{
|
||||||
/// <summary>
|
UnitTestResult[] unitTestResults = new UnitTestResult[testResults.Length];
|
||||||
/// Converts the test framework's TestResult objects array to a serializable UnitTestResult objects array.
|
|
||||||
/// </summary>
|
for (int i = 0; i < testResults.Length; ++i)
|
||||||
/// <param name="testResults">The test framework's TestResult object array.</param>
|
|
||||||
/// <returns>The serializable UnitTestResult object array.</returns>
|
|
||||||
public static UnitTestResult[] ToUnitTestResults(this UTF.TestResult[] testResults)
|
|
||||||
{
|
{
|
||||||
UnitTestResult[] unitTestResults = new UnitTestResult[testResults.Length];
|
UnitTestResult unitTestResult = null;
|
||||||
|
UnitTestOutcome outcome = testResults[i].Outcome.ToUnitTestOutcome();
|
||||||
|
|
||||||
for (int i = 0; i < testResults.Length; ++i)
|
if (testResults[i].TestFailureException != null)
|
||||||
{
|
{
|
||||||
UnitTestResult unitTestResult = null;
|
unitTestResult =
|
||||||
UnitTestOutcome outcome = testResults[i].Outcome.ToUnitTestOutcome();
|
new UnitTestResult(
|
||||||
|
new TestFailedException(
|
||||||
if (testResults[i].TestFailureException != null)
|
outcome,
|
||||||
{
|
testResults[i].TestFailureException.TryGetMessage(),
|
||||||
unitTestResult =
|
testResults[i].TestFailureException is TestFailedException testException ? testException.StackTraceInformation : testResults[i].TestFailureException.TryGetStackTraceInformation()));
|
||||||
new UnitTestResult(
|
}
|
||||||
new TestFailedException(
|
else
|
||||||
outcome,
|
{
|
||||||
testResults[i].TestFailureException.TryGetMessage(),
|
unitTestResult = new UnitTestResult { Outcome = outcome };
|
||||||
testResults[i].TestFailureException is TestFailedException testException ? testException.StackTraceInformation : testResults[i].TestFailureException.TryGetStackTraceInformation()));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
unitTestResult = new UnitTestResult { Outcome = outcome };
|
|
||||||
}
|
|
||||||
|
|
||||||
unitTestResult.StandardOut = testResults[i].LogOutput;
|
|
||||||
unitTestResult.StandardError = testResults[i].LogError;
|
|
||||||
unitTestResult.DebugTrace = testResults[i].DebugTrace;
|
|
||||||
unitTestResult.TestContextMessages = testResults[i].TestContextMessages;
|
|
||||||
unitTestResult.Duration = testResults[i].Duration;
|
|
||||||
unitTestResult.DisplayName = testResults[i].DisplayName;
|
|
||||||
unitTestResult.DatarowIndex = testResults[i].DatarowIndex;
|
|
||||||
unitTestResult.ResultFiles = testResults[i].ResultFiles;
|
|
||||||
unitTestResult.ExecutionId = testResults[i].ExecutionId;
|
|
||||||
unitTestResult.ParentExecId = testResults[i].ParentExecId;
|
|
||||||
unitTestResult.InnerResultsCount = testResults[i].InnerResultsCount;
|
|
||||||
unitTestResults[i] = unitTestResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return unitTestResults;
|
unitTestResult.StandardOut = testResults[i].LogOutput;
|
||||||
|
unitTestResult.StandardError = testResults[i].LogError;
|
||||||
|
unitTestResult.DebugTrace = testResults[i].DebugTrace;
|
||||||
|
unitTestResult.TestContextMessages = testResults[i].TestContextMessages;
|
||||||
|
unitTestResult.Duration = testResults[i].Duration;
|
||||||
|
unitTestResult.DisplayName = testResults[i].DisplayName;
|
||||||
|
unitTestResult.DatarowIndex = testResults[i].DatarowIndex;
|
||||||
|
unitTestResult.ResultFiles = testResults[i].ResultFiles;
|
||||||
|
unitTestResult.ExecutionId = testResults[i].ExecutionId;
|
||||||
|
unitTestResult.ParentExecId = testResults[i].ParentExecId;
|
||||||
|
unitTestResult.InnerResultsCount = testResults[i].InnerResultsCount;
|
||||||
|
unitTestResults[i] = unitTestResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return unitTestResults;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,45 +1,44 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
|
||||||
|
using UTF = Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
public static class UnitTestOutcomeExtensions
|
||||||
{
|
{
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
/// <summary>
|
||||||
|
/// Converts the test framework's UnitTestOutcome object to adapter's UnitTestOutcome object.
|
||||||
using UTF = Microsoft.VisualStudio.TestTools.UnitTesting;
|
/// </summary>
|
||||||
|
/// <param name="frameworkTestOutcome">The test framework's UnitTestOutcome object.</param>
|
||||||
public static class UnitTestOutcomeExtensions
|
/// <returns>The adapter's UnitTestOutcome object.</returns>
|
||||||
|
public static UnitTestOutcome ToUnitTestOutcome(this UTF.UnitTestOutcome frameworkTestOutcome)
|
||||||
{
|
{
|
||||||
/// <summary>
|
UnitTestOutcome outcome = frameworkTestOutcome switch
|
||||||
/// Converts the test framework's UnitTestOutcome object to adapter's UnitTestOutcome object.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="frameworkTestOutcome">The test framework's UnitTestOutcome object.</param>
|
|
||||||
/// <returns>The adapter's UnitTestOutcome object.</returns>
|
|
||||||
public static UnitTestOutcome ToUnitTestOutcome(this UTF.UnitTestOutcome frameworkTestOutcome)
|
|
||||||
{
|
{
|
||||||
UnitTestOutcome outcome = frameworkTestOutcome switch
|
UTF.UnitTestOutcome.Failed => UnitTestOutcome.Failed,
|
||||||
{
|
UTF.UnitTestOutcome.Inconclusive => UnitTestOutcome.Inconclusive,
|
||||||
UTF.UnitTestOutcome.Failed => UnitTestOutcome.Failed,
|
UTF.UnitTestOutcome.InProgress => UnitTestOutcome.InProgress,
|
||||||
UTF.UnitTestOutcome.Inconclusive => UnitTestOutcome.Inconclusive,
|
UTF.UnitTestOutcome.Passed => UnitTestOutcome.Passed,
|
||||||
UTF.UnitTestOutcome.InProgress => UnitTestOutcome.InProgress,
|
UTF.UnitTestOutcome.Timeout => UnitTestOutcome.Timeout,
|
||||||
UTF.UnitTestOutcome.Passed => UnitTestOutcome.Passed,
|
UTF.UnitTestOutcome.NotRunnable => UnitTestOutcome.NotRunnable,
|
||||||
UTF.UnitTestOutcome.Timeout => UnitTestOutcome.Timeout,
|
_ => UnitTestOutcome.Error,
|
||||||
UTF.UnitTestOutcome.NotRunnable => UnitTestOutcome.NotRunnable,
|
};
|
||||||
_ => UnitTestOutcome.Error,
|
return outcome;
|
||||||
};
|
}
|
||||||
return outcome;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns more important outcome of two.
|
/// Returns more important outcome of two.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="outcome1"> First outcome that needs to be compared. </param>
|
/// <param name="outcome1"> First outcome that needs to be compared. </param>
|
||||||
/// <param name="outcome2"> Second outcome that needs to be compared. </param>
|
/// <param name="outcome2"> Second outcome that needs to be compared. </param>
|
||||||
/// <returns> Outcome which has higher importance.</returns>
|
/// <returns> Outcome which has higher importance.</returns>
|
||||||
internal static UTF.UnitTestOutcome GetMoreImportantOutcome(this UTF.UnitTestOutcome outcome1, UTF.UnitTestOutcome outcome2)
|
internal static UTF.UnitTestOutcome GetMoreImportantOutcome(this UTF.UnitTestOutcome outcome1, UTF.UnitTestOutcome outcome2)
|
||||||
{
|
{
|
||||||
var unitTestOutcome1 = outcome1.ToUnitTestOutcome();
|
var unitTestOutcome1 = outcome1.ToUnitTestOutcome();
|
||||||
var unitTestOutcome2 = outcome2.ToUnitTestOutcome();
|
var unitTestOutcome2 = outcome2.ToUnitTestOutcome();
|
||||||
return unitTestOutcome1 < unitTestOutcome2 ? outcome1 : outcome2;
|
return unitTestOutcome1 < unitTestOutcome2 ? outcome1 : outcome2;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,116 +1,115 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.Serialization.Json;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
internal static class DataSerializationHelper
|
||||||
{
|
{
|
||||||
using System;
|
private static readonly ConcurrentDictionary<string, DataContractJsonSerializer> SerializerCache = new();
|
||||||
using System.Collections.Concurrent;
|
private static readonly DataContractJsonSerializerSettings SerializerSettings = new()
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.Serialization.Json;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
internal static class DataSerializationHelper
|
|
||||||
{
|
{
|
||||||
private static readonly ConcurrentDictionary<string, DataContractJsonSerializer> SerializerCache = new();
|
UseSimpleDictionaryFormat = true,
|
||||||
private static readonly DataContractJsonSerializerSettings SerializerSettings = new()
|
EmitTypeInformation = System.Runtime.Serialization.EmitTypeInformation.Always,
|
||||||
|
DateTimeFormat = new System.Runtime.Serialization.DateTimeFormat("O", System.Globalization.CultureInfo.InvariantCulture)
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serializes the date in such a way that won't throw exceptions during deserialization in Test Platform.
|
||||||
|
/// The result can be deserialized using <see cref="Deserialize(string[])"/> method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Data array to serialize.</param>
|
||||||
|
/// <returns>Serialized array.</returns>
|
||||||
|
public static string[] Serialize(object[] data)
|
||||||
|
{
|
||||||
|
if (data == null)
|
||||||
{
|
{
|
||||||
UseSimpleDictionaryFormat = true,
|
return null;
|
||||||
EmitTypeInformation = System.Runtime.Serialization.EmitTypeInformation.Always,
|
|
||||||
DateTimeFormat = new System.Runtime.Serialization.DateTimeFormat("O", System.Globalization.CultureInfo.InvariantCulture)
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Serializes the date in such a way that won't throw exceptions during deserialization in Test Platform.
|
|
||||||
/// The result can be deserialized using <see cref="Deserialize(string[])"/> method.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="data">Data array to serialize.</param>
|
|
||||||
/// <returns>Serialized array.</returns>
|
|
||||||
public static string[] Serialize(object[] data)
|
|
||||||
{
|
|
||||||
if (data == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var serializedData = new string[data.Length * 2];
|
|
||||||
for (int i = 0; i < data.Length; i++)
|
|
||||||
{
|
|
||||||
var typeIndex = i * 2;
|
|
||||||
var dataIndex = typeIndex + 1;
|
|
||||||
if (data[i] == null)
|
|
||||||
{
|
|
||||||
serializedData[typeIndex] = null;
|
|
||||||
serializedData[dataIndex] = null;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var type = data[i].GetType();
|
|
||||||
var typeName = type.AssemblyQualifiedName;
|
|
||||||
|
|
||||||
serializedData[typeIndex] = typeName;
|
|
||||||
|
|
||||||
var serializer = GetSerializer(type);
|
|
||||||
|
|
||||||
using var memoryStream = new MemoryStream();
|
|
||||||
serializer.WriteObject(memoryStream, data[i]);
|
|
||||||
var serializerData = memoryStream.ToArray();
|
|
||||||
|
|
||||||
serializedData[dataIndex] = Encoding.UTF8.GetString(serializerData, 0, serializerData.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
return serializedData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
var serializedData = new string[data.Length * 2];
|
||||||
/// Deserializes the data serialized by <see cref="Serialize(object[])" /> method.
|
for (int i = 0; i < data.Length; i++)
|
||||||
/// </summary>
|
|
||||||
/// <param name="serializedData">Serialized data array to deserialize.</param>
|
|
||||||
/// <returns>Deserialized array.</returns>
|
|
||||||
public static object[] Deserialize(string[] serializedData)
|
|
||||||
{
|
{
|
||||||
if (serializedData == null || serializedData.Length % 2 != 0)
|
var typeIndex = i * 2;
|
||||||
|
var dataIndex = typeIndex + 1;
|
||||||
|
if (data[i] == null)
|
||||||
{
|
{
|
||||||
return null;
|
serializedData[typeIndex] = null;
|
||||||
|
serializedData[dataIndex] = null;
|
||||||
|
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var length = serializedData.Length / 2;
|
var type = data[i].GetType();
|
||||||
var data = new object[length];
|
var typeName = type.AssemblyQualifiedName;
|
||||||
|
|
||||||
for (int i = 0; i < length; i++)
|
serializedData[typeIndex] = typeName;
|
||||||
|
|
||||||
|
var serializer = GetSerializer(type);
|
||||||
|
|
||||||
|
using var memoryStream = new MemoryStream();
|
||||||
|
serializer.WriteObject(memoryStream, data[i]);
|
||||||
|
var serializerData = memoryStream.ToArray();
|
||||||
|
|
||||||
|
serializedData[dataIndex] = Encoding.UTF8.GetString(serializerData, 0, serializerData.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return serializedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deserializes the data serialized by <see cref="Serialize(object[])" /> method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serializedData">Serialized data array to deserialize.</param>
|
||||||
|
/// <returns>Deserialized array.</returns>
|
||||||
|
public static object[] Deserialize(string[] serializedData)
|
||||||
|
{
|
||||||
|
if (serializedData == null || serializedData.Length % 2 != 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var length = serializedData.Length / 2;
|
||||||
|
var data = new object[length];
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
var typeIndex = i * 2;
|
||||||
|
var assemblyQualifiedName = serializedData[typeIndex];
|
||||||
|
var serializedValue = serializedData[typeIndex + 1];
|
||||||
|
|
||||||
|
if (serializedValue == null || assemblyQualifiedName == null)
|
||||||
{
|
{
|
||||||
var typeIndex = i * 2;
|
data[i] = null;
|
||||||
var assemblyQualifiedName = serializedData[typeIndex];
|
continue;
|
||||||
var serializedValue = serializedData[typeIndex + 1];
|
|
||||||
|
|
||||||
if (serializedValue == null || assemblyQualifiedName == null)
|
|
||||||
{
|
|
||||||
data[i] = null;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var serializer = GetSerializer(assemblyQualifiedName);
|
|
||||||
|
|
||||||
var serialzedDataBytes = Encoding.UTF8.GetBytes(serializedValue);
|
|
||||||
using var memoryStream = new MemoryStream(serialzedDataBytes);
|
|
||||||
data[i] = serializer.ReadObject(memoryStream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
var serializer = GetSerializer(assemblyQualifiedName);
|
||||||
|
|
||||||
|
var serialzedDataBytes = Encoding.UTF8.GetBytes(serializedValue);
|
||||||
|
using var memoryStream = new MemoryStream(serialzedDataBytes);
|
||||||
|
data[i] = serializer.ReadObject(memoryStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DataContractJsonSerializer GetSerializer(string assemblyQualifiedName)
|
return data;
|
||||||
{
|
}
|
||||||
return SerializerCache.GetOrAdd(
|
|
||||||
assemblyQualifiedName,
|
|
||||||
_ => new DataContractJsonSerializer(Type.GetType(assemblyQualifiedName) ?? typeof(object), SerializerSettings));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DataContractJsonSerializer GetSerializer(Type type)
|
private static DataContractJsonSerializer GetSerializer(string assemblyQualifiedName)
|
||||||
{
|
{
|
||||||
return SerializerCache.GetOrAdd(
|
return SerializerCache.GetOrAdd(
|
||||||
type.AssemblyQualifiedName,
|
assemblyQualifiedName,
|
||||||
_ => new DataContractJsonSerializer(type, SerializerSettings));
|
_ => new DataContractJsonSerializer(Type.GetType(assemblyQualifiedName) ?? typeof(object), SerializerSettings));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static DataContractJsonSerializer GetSerializer(Type type)
|
||||||
|
{
|
||||||
|
return SerializerCache.GetOrAdd(
|
||||||
|
type.AssemblyQualifiedName,
|
||||||
|
_ => new DataContractJsonSerializer(type, SerializerSettings));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,61 +1,60 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
internal static class DictionaryHelper
|
||||||
{
|
{
|
||||||
using System;
|
public static IDictionary<TKey, TValue> ConcatWithOverwrites<TKey, TValue>(
|
||||||
using System.Collections.Generic;
|
this IDictionary<TKey, TValue> source,
|
||||||
using System.Linq;
|
IDictionary<TKey, TValue> overwrite,
|
||||||
|
string sourceFriendlyName = "source",
|
||||||
internal static class DictionaryHelper
|
string overwriteFriendlyName = "overwrite")
|
||||||
|
where TKey : IEquatable<TKey>
|
||||||
{
|
{
|
||||||
public static IDictionary<TKey, TValue> ConcatWithOverwrites<TKey, TValue>(
|
if ((source == null || source?.Count == 0) && (overwrite == null || overwrite?.Count == 0))
|
||||||
this IDictionary<TKey, TValue> source,
|
|
||||||
IDictionary<TKey, TValue> overwrite,
|
|
||||||
string sourceFriendlyName = "source",
|
|
||||||
string overwriteFriendlyName = "overwrite")
|
|
||||||
where TKey : IEquatable<TKey>
|
|
||||||
{
|
{
|
||||||
if ((source == null || source?.Count == 0) && (overwrite == null || overwrite?.Count == 0))
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("DictionaryHelper.ConcatWithOverwrites: Both {0} and {1} dictionaries are null or empty, returning empty dictionary.", sourceFriendlyName, overwriteFriendlyName);
|
||||||
{
|
return new Dictionary<TKey, TValue>();
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("DictionaryHelper.ConcatWithOverwrites: Both {0} and {1} dictionaries are null or empty, returning empty dictionary.", sourceFriendlyName, overwriteFriendlyName);
|
|
||||||
return new Dictionary<TKey, TValue>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (overwrite == null || overwrite?.Count == 0)
|
|
||||||
{
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("DictionaryHelper.ConcatWithOverwrites: The {0} is null or empty, returning the {1} dictionary.", overwriteFriendlyName, sourceFriendlyName);
|
|
||||||
return source.ToDictionary(p => p.Key, p => p.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (source == null || source?.Count == 0)
|
|
||||||
{
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("DictionaryHelper.ConcatWithOverwrites: The {0} is null or empty, returning the {1} dictionary.", sourceFriendlyName, overwriteFriendlyName);
|
|
||||||
return overwrite.ToDictionary(p => p.Key, p => p.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("DictionaryHelper.ConcatWithOverwrites: The {0} has {1} keys. And {2} has {3} keys. Merging them.", sourceFriendlyName, source.Count, overwriteFriendlyName, overwrite.Count);
|
|
||||||
var destination = source.ToDictionary(p => p.Key, p => p.Value);
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("DictionaryHelper.ConcatWithOverwrites: Taking all keys from {0}: {1}.", sourceFriendlyName, string.Join(", ", source.Keys));
|
|
||||||
var overwrites = new List<TKey>();
|
|
||||||
foreach (var k in overwrite.Keys)
|
|
||||||
{
|
|
||||||
if (destination.ContainsKey(k))
|
|
||||||
{
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("DictionaryHelper.ConcatWithOverwrites: The {0} already contains key {1}. Overwriting it with value from {2}.", sourceFriendlyName, k, overwriteFriendlyName);
|
|
||||||
destination[k] = overwrite[k];
|
|
||||||
overwrites.Add(k);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("DictionaryHelper.ConcatWithOverwrites: The {0} does not contain key {1}. Adding it from {2}.", sourceFriendlyName, k, overwriteFriendlyName);
|
|
||||||
destination.Add(k, overwrite[k]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("DictionaryHelper.ConcatWithOverwrites: Merging done: Resulting dictionary has keys {0}, overwrites {1}.", string.Join(", ", destination.Keys), string.Join(", ", overwrites));
|
|
||||||
|
|
||||||
return destination;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (overwrite == null || overwrite?.Count == 0)
|
||||||
|
{
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("DictionaryHelper.ConcatWithOverwrites: The {0} is null or empty, returning the {1} dictionary.", overwriteFriendlyName, sourceFriendlyName);
|
||||||
|
return source.ToDictionary(p => p.Key, p => p.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source == null || source?.Count == 0)
|
||||||
|
{
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("DictionaryHelper.ConcatWithOverwrites: The {0} is null or empty, returning the {1} dictionary.", sourceFriendlyName, overwriteFriendlyName);
|
||||||
|
return overwrite.ToDictionary(p => p.Key, p => p.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("DictionaryHelper.ConcatWithOverwrites: The {0} has {1} keys. And {2} has {3} keys. Merging them.", sourceFriendlyName, source.Count, overwriteFriendlyName, overwrite.Count);
|
||||||
|
var destination = source.ToDictionary(p => p.Key, p => p.Value);
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("DictionaryHelper.ConcatWithOverwrites: Taking all keys from {0}: {1}.", sourceFriendlyName, string.Join(", ", source.Keys));
|
||||||
|
var overwrites = new List<TKey>();
|
||||||
|
foreach (var k in overwrite.Keys)
|
||||||
|
{
|
||||||
|
if (destination.ContainsKey(k))
|
||||||
|
{
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("DictionaryHelper.ConcatWithOverwrites: The {0} already contains key {1}. Overwriting it with value from {2}.", sourceFriendlyName, k, overwriteFriendlyName);
|
||||||
|
destination[k] = overwrite[k];
|
||||||
|
overwrites.Add(k);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("DictionaryHelper.ConcatWithOverwrites: The {0} does not contain key {1}. Adding it from {2}.", sourceFriendlyName, k, overwriteFriendlyName);
|
||||||
|
destination.Add(k, overwrite[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("DictionaryHelper.ConcatWithOverwrites: Merging done: Resulting dictionary has keys {0}, overwrites {1}.", string.Join(", ", destination.Keys), string.Join(", ", overwrites));
|
||||||
|
|
||||||
|
return destination;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,103 +1,102 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;
|
||||||
|
|
||||||
|
using TestPlatform.ObjectModel;
|
||||||
|
|
||||||
|
internal class RunSettingsUtilities
|
||||||
{
|
{
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Collections.Generic;
|
/// Gets the settings to be used while creating XmlReader for runsettings.
|
||||||
using System.Globalization;
|
/// </summary>
|
||||||
using System.IO;
|
internal static XmlReaderSettings ReaderSettings
|
||||||
using System.Xml;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;
|
|
||||||
|
|
||||||
using TestPlatform.ObjectModel;
|
|
||||||
|
|
||||||
internal class RunSettingsUtilities
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
get
|
||||||
/// Gets the settings to be used while creating XmlReader for runsettings.
|
|
||||||
/// </summary>
|
|
||||||
internal static XmlReaderSettings ReaderSettings
|
|
||||||
{
|
{
|
||||||
get
|
var settings = new XmlReaderSettings();
|
||||||
{
|
settings.IgnoreComments = true;
|
||||||
var settings = new XmlReaderSettings();
|
settings.IgnoreWhitespace = true;
|
||||||
settings.IgnoreComments = true;
|
return settings;
|
||||||
settings.IgnoreWhitespace = true;
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the set of user defined test run parameters from settings xml as key value pairs.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="settingsXml">The runsettings xml.</param>
|
|
||||||
/// <returns>The test run parameters.</returns>
|
|
||||||
/// <remarks>If there is no test run parameters section defined in the settingsxml a blank dictionary is returned.</remarks>
|
|
||||||
internal static Dictionary<string, object> GetTestRunParameters(string settingsXml)
|
|
||||||
{
|
|
||||||
var nodeValue = GetNodeValue(settingsXml, TestAdapter.Constants.TestRunParametersName, TestRunParameters.FromXml);
|
|
||||||
if (nodeValue == default(Dictionary<string, object>))
|
|
||||||
{
|
|
||||||
// Return default.
|
|
||||||
nodeValue = new Dictionary<string, object>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return nodeValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Throws if the node has an attribute.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="reader"> The reader. </param>
|
|
||||||
/// <exception cref="SettingsException"> Thrown if the node has an attribute. </exception>
|
|
||||||
internal static void ThrowOnHasAttributes(XmlReader reader)
|
|
||||||
{
|
|
||||||
if (reader.HasAttributes)
|
|
||||||
{
|
|
||||||
reader.MoveToNextAttribute();
|
|
||||||
throw new SettingsException(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.CurrentCulture,
|
|
||||||
Resource.InvalidSettingsXmlAttribute,
|
|
||||||
TestPlatform.ObjectModel.Constants.RunConfigurationSettingsName,
|
|
||||||
reader.Name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static T GetNodeValue<T>(string settingsXml, string nodeName, Func<XmlReader, T> nodeParser)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(settingsXml))
|
|
||||||
{
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
// use XmlReader to avoid loading of the plugins in client code (mainly from VS).
|
|
||||||
using (StringReader stringReader = new(settingsXml))
|
|
||||||
{
|
|
||||||
XmlReader reader = XmlReader.Create(stringReader, ReaderSettings);
|
|
||||||
|
|
||||||
// read to the fist child
|
|
||||||
XmlReaderUtilities.ReadToRootNode(reader);
|
|
||||||
reader.ReadToNextElement();
|
|
||||||
|
|
||||||
// Read till we reach nodeName element or reach EOF
|
|
||||||
while (!string.Equals(reader.Name, nodeName, StringComparison.OrdinalIgnoreCase)
|
|
||||||
&&
|
|
||||||
!reader.EOF)
|
|
||||||
{
|
|
||||||
reader.SkipToNextElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!reader.EOF)
|
|
||||||
{
|
|
||||||
// read nodeName element.
|
|
||||||
return nodeParser(reader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the set of user defined test run parameters from settings xml as key value pairs.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="settingsXml">The runsettings xml.</param>
|
||||||
|
/// <returns>The test run parameters.</returns>
|
||||||
|
/// <remarks>If there is no test run parameters section defined in the settingsxml a blank dictionary is returned.</remarks>
|
||||||
|
internal static Dictionary<string, object> GetTestRunParameters(string settingsXml)
|
||||||
|
{
|
||||||
|
var nodeValue = GetNodeValue(settingsXml, TestAdapter.Constants.TestRunParametersName, TestRunParameters.FromXml);
|
||||||
|
if (nodeValue == default(Dictionary<string, object>))
|
||||||
|
{
|
||||||
|
// Return default.
|
||||||
|
nodeValue = new Dictionary<string, object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Throws if the node has an attribute.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reader"> The reader. </param>
|
||||||
|
/// <exception cref="SettingsException"> Thrown if the node has an attribute. </exception>
|
||||||
|
internal static void ThrowOnHasAttributes(XmlReader reader)
|
||||||
|
{
|
||||||
|
if (reader.HasAttributes)
|
||||||
|
{
|
||||||
|
reader.MoveToNextAttribute();
|
||||||
|
throw new SettingsException(
|
||||||
|
string.Format(
|
||||||
|
CultureInfo.CurrentCulture,
|
||||||
|
Resource.InvalidSettingsXmlAttribute,
|
||||||
|
TestPlatform.ObjectModel.Constants.RunConfigurationSettingsName,
|
||||||
|
reader.Name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T GetNodeValue<T>(string settingsXml, string nodeName, Func<XmlReader, T> nodeParser)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(settingsXml))
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use XmlReader to avoid loading of the plugins in client code (mainly from VS).
|
||||||
|
using (StringReader stringReader = new(settingsXml))
|
||||||
|
{
|
||||||
|
XmlReader reader = XmlReader.Create(stringReader, ReaderSettings);
|
||||||
|
|
||||||
|
// read to the fist child
|
||||||
|
XmlReaderUtilities.ReadToRootNode(reader);
|
||||||
|
reader.ReadToNextElement();
|
||||||
|
|
||||||
|
// Read till we reach nodeName element or reach EOF
|
||||||
|
while (!string.Equals(reader.Name, nodeName, StringComparison.OrdinalIgnoreCase)
|
||||||
|
&&
|
||||||
|
!reader.EOF)
|
||||||
|
{
|
||||||
|
reader.SkipToNextElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!reader.EOF)
|
||||||
|
{
|
||||||
|
// read nodeName element.
|
||||||
|
return nodeParser(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,67 +1,66 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
|
||||||
|
internal static class TestRunParameters
|
||||||
{
|
{
|
||||||
using System;
|
internal static Dictionary<string, object> FromXml(XmlReader reader)
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Xml;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
|
||||||
|
|
||||||
internal static class TestRunParameters
|
|
||||||
{
|
{
|
||||||
internal static Dictionary<string, object> FromXml(XmlReader reader)
|
var testParameters = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
if (!reader.IsEmptyElement)
|
||||||
{
|
{
|
||||||
var testParameters = new Dictionary<string, object>();
|
RunSettingsUtilities.ThrowOnHasAttributes(reader);
|
||||||
|
reader.Read();
|
||||||
|
|
||||||
if (!reader.IsEmptyElement)
|
while (reader.NodeType == XmlNodeType.Element)
|
||||||
{
|
{
|
||||||
RunSettingsUtilities.ThrowOnHasAttributes(reader);
|
var elementName = reader.Name;
|
||||||
reader.Read();
|
switch (elementName)
|
||||||
|
|
||||||
while (reader.NodeType == XmlNodeType.Element)
|
|
||||||
{
|
{
|
||||||
var elementName = reader.Name;
|
case "Parameter":
|
||||||
switch (elementName)
|
string paramName = null;
|
||||||
{
|
string paramValue = null;
|
||||||
case "Parameter":
|
for (var attIndex = 0; attIndex < reader.AttributeCount; attIndex++)
|
||||||
string paramName = null;
|
{
|
||||||
string paramValue = null;
|
reader.MoveToAttribute(attIndex);
|
||||||
for (var attIndex = 0; attIndex < reader.AttributeCount; attIndex++)
|
if (string.Equals(reader.Name, "Name", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
reader.MoveToAttribute(attIndex);
|
paramName = reader.Value;
|
||||||
if (string.Equals(reader.Name, "Name", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
paramName = reader.Value;
|
|
||||||
}
|
|
||||||
else if (string.Equals(reader.Name, "Value", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
paramValue = reader.Value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else if (string.Equals(reader.Name, "Value", StringComparison.OrdinalIgnoreCase))
|
||||||
if (paramName != null && paramValue != null)
|
|
||||||
{
|
{
|
||||||
testParameters[paramName] = paramValue;
|
paramValue = reader.Value;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
if (paramName != null && paramValue != null)
|
||||||
default:
|
{
|
||||||
throw new SettingsException(
|
testParameters[paramName] = paramValue;
|
||||||
string.Format(
|
}
|
||||||
CultureInfo.CurrentCulture,
|
|
||||||
Resource.InvalidSettingsXmlElement,
|
|
||||||
TestAdapter.Constants.TestRunParametersName,
|
|
||||||
reader.Name));
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.Read();
|
break;
|
||||||
|
default:
|
||||||
|
throw new SettingsException(
|
||||||
|
string.Format(
|
||||||
|
CultureInfo.CurrentCulture,
|
||||||
|
Resource.InvalidSettingsXmlElement,
|
||||||
|
TestAdapter.Constants.TestRunParametersName,
|
||||||
|
reader.Name));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return testParameters;
|
reader.Read();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return testParameters;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,62 +1,61 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
|
||||||
|
using UTF = Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
internal static class UnitTestOutcomeHelper
|
||||||
{
|
{
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
/// <summary>
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
/// Converts the parameter unitTestOutcome to testOutcome
|
||||||
|
/// </summary>
|
||||||
using UTF = Microsoft.VisualStudio.TestTools.UnitTesting;
|
/// <param name="unitTestOutcome"> The unit Test Outcome. </param>
|
||||||
|
/// <param name="currentSettings">Current MSTest settings</param>
|
||||||
internal static class UnitTestOutcomeHelper
|
/// <returns>The Test platforms outcome.</returns>
|
||||||
|
internal static TestOutcome ToTestOutcome(UnitTestOutcome unitTestOutcome, MSTestSettings currentSettings)
|
||||||
{
|
{
|
||||||
/// <summary>
|
switch (unitTestOutcome)
|
||||||
/// Converts the parameter unitTestOutcome to testOutcome
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="unitTestOutcome"> The unit Test Outcome. </param>
|
|
||||||
/// <param name="currentSettings">Current MSTest settings</param>
|
|
||||||
/// <returns>The Test platforms outcome.</returns>
|
|
||||||
internal static TestOutcome ToTestOutcome(UnitTestOutcome unitTestOutcome, MSTestSettings currentSettings)
|
|
||||||
{
|
{
|
||||||
switch (unitTestOutcome)
|
case UnitTestOutcome.Passed:
|
||||||
{
|
return TestOutcome.Passed;
|
||||||
case UnitTestOutcome.Passed:
|
|
||||||
return TestOutcome.Passed;
|
|
||||||
|
|
||||||
case UnitTestOutcome.Failed:
|
case UnitTestOutcome.Failed:
|
||||||
case UnitTestOutcome.Error:
|
case UnitTestOutcome.Error:
|
||||||
case UnitTestOutcome.Timeout:
|
case UnitTestOutcome.Timeout:
|
||||||
return TestOutcome.Failed;
|
return TestOutcome.Failed;
|
||||||
|
|
||||||
case UnitTestOutcome.NotRunnable:
|
case UnitTestOutcome.NotRunnable:
|
||||||
|
{
|
||||||
|
if (currentSettings.MapNotRunnableToFailed)
|
||||||
{
|
{
|
||||||
if (currentSettings.MapNotRunnableToFailed)
|
return TestOutcome.Failed;
|
||||||
{
|
|
||||||
return TestOutcome.Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
return TestOutcome.None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case UnitTestOutcome.Ignored:
|
|
||||||
return TestOutcome.Skipped;
|
|
||||||
|
|
||||||
case UnitTestOutcome.Inconclusive:
|
|
||||||
{
|
|
||||||
if (currentSettings.MapInconclusiveToFailed)
|
|
||||||
{
|
|
||||||
return TestOutcome.Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
return TestOutcome.Skipped;
|
|
||||||
}
|
|
||||||
|
|
||||||
case UnitTestOutcome.NotFound:
|
|
||||||
return TestOutcome.NotFound;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return TestOutcome.None;
|
return TestOutcome.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case UnitTestOutcome.Ignored:
|
||||||
|
return TestOutcome.Skipped;
|
||||||
|
|
||||||
|
case UnitTestOutcome.Inconclusive:
|
||||||
|
{
|
||||||
|
if (currentSettings.MapInconclusiveToFailed)
|
||||||
|
{
|
||||||
|
return TestOutcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TestOutcome.Skipped;
|
||||||
|
}
|
||||||
|
|
||||||
|
case UnitTestOutcome.NotFound:
|
||||||
|
return TestOutcome.NotFound;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return TestOutcome.None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,124 +1,123 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter;
|
||||||
{
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
using System.Collections.Generic;
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.ObjectModel;
|
using System.IO;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.ObjectModel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The definition of a PlatformServiceProvider with a hook to all the services.
|
||||||
|
/// </summary>
|
||||||
|
internal interface IPlatformServiceProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instance to the platform service validator for test sources.
|
||||||
|
/// </summary>
|
||||||
|
ITestSource TestSource { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The definition of a PlatformServiceProvider with a hook to all the services.
|
/// Gets an instance to the platform service to data drive a test.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal interface IPlatformServiceProvider
|
ITestDataSource TestDataSource { get; }
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an instance to the platform service validator for test sources.
|
|
||||||
/// </summary>
|
|
||||||
ITestSource TestSource { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an instance to the platform service to data drive a test.
|
/// Gets an instance to the platform service for file operations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ITestDataSource TestDataSource { get; }
|
IFileOperations FileOperations { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an instance to the platform service for file operations.
|
/// Gets an instance to the platform service for trace logging.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IFileOperations FileOperations { get; }
|
IAdapterTraceLogger AdapterTraceLogger { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an instance to the platform service for trace logging.
|
/// Gets an instance of the test deployment service.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IAdapterTraceLogger AdapterTraceLogger { get; }
|
ITestDeployment TestDeployment { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an instance of the test deployment service.
|
/// Gets an instance to the platform service for a Settings Provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ITestDeployment TestDeployment { get; }
|
ISettingsProvider SettingsProvider { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an instance to the platform service for a Settings Provider.
|
/// Gets an instance to the platform service for thread operations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ISettingsProvider SettingsProvider { get; }
|
IThreadOperations ThreadOperations { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an instance to the platform service for thread operations.
|
/// Gets an instance to the platform service for reflection operations specific to a platform.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IThreadOperations ThreadOperations { get; }
|
IReflectionOperations ReflectionOperations { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an instance to the platform service for reflection operations specific to a platform.
|
/// Creates an instance to the platform service for a test source host.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IReflectionOperations ReflectionOperations { get; }
|
/// <param name="source">
|
||||||
|
/// The source.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="runSettings">
|
||||||
|
/// The run Settings for the session.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="frameworkHandle">
|
||||||
|
/// The handle to the test platform.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Returns the host for the source provided.
|
||||||
|
/// </returns>
|
||||||
|
ITestSourceHost CreateTestSourceHost(
|
||||||
|
string source,
|
||||||
|
TestPlatform.ObjectModel.Adapter.IRunSettings runSettings,
|
||||||
|
TestPlatform.ObjectModel.Adapter.IFrameworkHandle frameworkHandle);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an instance to the platform service for a test source host.
|
/// Gets an instance to the platform service listener who monitors trace and debug output
|
||||||
/// </summary>
|
/// on provided text writer.
|
||||||
/// <param name="source">
|
/// </summary>
|
||||||
/// The source.
|
/// <param name="textWriter">
|
||||||
/// </param>
|
/// The text Writer.
|
||||||
/// <param name="runSettings">
|
/// </param>
|
||||||
/// The run Settings for the session.
|
/// <returns>
|
||||||
/// </param>
|
/// The <see cref="ITraceListener"/>.
|
||||||
/// <param name="frameworkHandle">
|
/// </returns>
|
||||||
/// The handle to the test platform.
|
ITraceListener GetTraceListener(TextWriter textWriter);
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// Returns the host for the source provided.
|
|
||||||
/// </returns>
|
|
||||||
ITestSourceHost CreateTestSourceHost(
|
|
||||||
string source,
|
|
||||||
TestPlatform.ObjectModel.Adapter.IRunSettings runSettings,
|
|
||||||
TestPlatform.ObjectModel.Adapter.IFrameworkHandle frameworkHandle);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an instance to the platform service listener who monitors trace and debug output
|
/// Gets an instance to the platform service trace-listener manager which updates the output/error streams
|
||||||
/// on provided text writer.
|
/// with redirected streams and performs operations on the listener provided as argument.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="textWriter">
|
/// <param name="outputWriter">
|
||||||
/// The text Writer.
|
/// The redirected output stream writer.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <returns>
|
/// <param name="errorWriter">
|
||||||
/// The <see cref="ITraceListener"/>.
|
/// The redirected error stream writer.
|
||||||
/// </returns>
|
/// </param>
|
||||||
ITraceListener GetTraceListener(TextWriter textWriter);
|
/// <returns>
|
||||||
|
/// The manager for trace listeners.
|
||||||
|
/// </returns>
|
||||||
|
ITraceListenerManager GetTraceListenerManager(TextWriter outputWriter, TextWriter errorWriter);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an instance to the platform service trace-listener manager which updates the output/error streams
|
/// Gets the TestContext object for a platform.
|
||||||
/// with redirected streams and performs operations on the listener provided as argument.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="testMethod">
|
||||||
/// <param name="outputWriter">
|
/// The test method.
|
||||||
/// The redirected output stream writer.
|
/// </param>
|
||||||
/// </param>
|
/// <param name="writer">
|
||||||
/// <param name="errorWriter">
|
/// The writer instance for logging.
|
||||||
/// The redirected error stream writer.
|
/// </param>
|
||||||
/// </param>
|
/// <param name="properties">
|
||||||
/// <returns>
|
/// The default set of properties the test context needs to be filled with.
|
||||||
/// The manager for trace listeners.
|
/// </param>
|
||||||
/// </returns>
|
/// <returns>
|
||||||
ITraceListenerManager GetTraceListenerManager(TextWriter outputWriter, TextWriter errorWriter);
|
/// The <see cref="ITestContext"/> instance.
|
||||||
|
/// </returns>
|
||||||
/// <summary>
|
/// <remarks>
|
||||||
/// Gets the TestContext object for a platform.
|
/// This was required for compatibility reasons since the TestContext object that the V1 adapter had for desktop is not .Net Core compliant.
|
||||||
/// </summary>
|
/// </remarks>
|
||||||
/// <param name="testMethod">
|
ITestContext GetTestContext(ITestMethod testMethod, StringWriter writer, IDictionary<string, object> properties);
|
||||||
/// The test method.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="writer">
|
|
||||||
/// The writer instance for logging.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="properties">
|
|
||||||
/// The default set of properties the test context needs to be filled with.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// The <see cref="ITestContext"/> instance.
|
|
||||||
/// </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// This was required for compatibility reasons since the TestContext object that the V1 adapter had for desktop is not .Net Core compliant.
|
|
||||||
/// </remarks>
|
|
||||||
ITestContext GetTestContext(ITestMethod testMethod, StringWriter writer, IDictionary<string, object> properties);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard2.0;$(NetCoreAppCurrent);$(NetCoreAppMinimum);$(NetFrameworkMinimum)</TargetFrameworks>
|
<TargetFrameworks>netstandard2.0;$(NetCoreAppCurrent);$(NetCoreAppMinimum);$(NetFrameworkMinimum)</TargetFrameworks>
|
||||||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
||||||
<LangVersion>Latest</LangVersion>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
@ -63,4 +62,4 @@
|
||||||
<Output TaskParameter="DestinationFiles" ItemName="FileWrites" />
|
<Output TaskParameter="DestinationFiles" ItemName="FileWrites" />
|
||||||
</Copy>
|
</Copy>
|
||||||
</Target>
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -1,86 +1,85 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the discovery logic for this adapter.
|
||||||
|
/// </summary>
|
||||||
|
[DefaultExecutorUri(TestAdapter.Constants.ExecutorUriString)]
|
||||||
|
[FileExtension(".xap")]
|
||||||
|
[FileExtension(".appx")]
|
||||||
|
[FileExtension(".dll")]
|
||||||
|
[FileExtension(".exe")]
|
||||||
|
public class MSTestDiscoverer : ITestDiscoverer
|
||||||
{
|
{
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Collections.Generic;
|
/// Discovers the tests available from the provided source. Not supported for .xap source.
|
||||||
using System.IO;
|
/// </summary>
|
||||||
using System.Linq;
|
/// <param name="sources">Collection of test containers.</param>
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
/// <param name="discoveryContext">Context in which discovery is being performed.</param>
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
/// <param name="logger">Logger used to log messages.</param>
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
|
/// <param name="discoverySink">Used to send testcases and discovery related events back to Discoverer manager.</param>
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
|
[System.Security.SecurityCritical]
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification ="Discovery context can be null.")]
|
||||||
|
public void DiscoverTests(
|
||||||
|
IEnumerable<string> sources,
|
||||||
|
IDiscoveryContext discoveryContext,
|
||||||
|
IMessageLogger logger,
|
||||||
|
ITestCaseDiscoverySink discoverySink)
|
||||||
|
{
|
||||||
|
ValidateArg.NotNull(sources, "sources");
|
||||||
|
ValidateArg.NotNull(logger, "logger");
|
||||||
|
ValidateArg.NotNull(discoverySink, "discoverySink");
|
||||||
|
|
||||||
|
if (!this.AreValidSources(sources))
|
||||||
|
{
|
||||||
|
throw new NotSupportedException(Resource.SourcesNotSupported);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the runsettings.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MSTestSettings.PopulateSettings(discoveryContext);
|
||||||
|
}
|
||||||
|
catch (AdapterSettingsException ex)
|
||||||
|
{
|
||||||
|
logger.SendMessage(TestMessageLevel.Error, ex.Message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenarios that include testsettings or forcing a run via the legacy adapter are currently not supported in MSTestAdapter.
|
||||||
|
if (MSTestSettings.IsLegacyScenario(logger))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new UnitTestDiscoverer().DiscoverTests(sources, logger, discoverySink, discoveryContext);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains the discovery logic for this adapter.
|
/// Verifies if the sources are valid for the target platform.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DefaultExecutorUri(TestAdapter.Constants.ExecutorUriString)]
|
/// <param name="sources">The test sources</param>
|
||||||
[FileExtension(".xap")]
|
/// <remarks>Sources cannot be null.</remarks>
|
||||||
[FileExtension(".appx")]
|
/// <returns>True if the source has a valid extension for the current platform.</returns>
|
||||||
[FileExtension(".dll")]
|
internal bool AreValidSources(IEnumerable<string> sources)
|
||||||
[FileExtension(".exe")]
|
|
||||||
public class MSTestDiscoverer : ITestDiscoverer
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
// ValidSourceExtensions is always expected to return a non-null list.
|
||||||
/// Discovers the tests available from the provided source. Not supported for .xap source.
|
return
|
||||||
/// </summary>
|
sources.Any(
|
||||||
/// <param name="sources">Collection of test containers.</param>
|
source =>
|
||||||
/// <param name="discoveryContext">Context in which discovery is being performed.</param>
|
PlatformServiceProvider.Instance.TestSource.ValidSourceExtensions.Any(
|
||||||
/// <param name="logger">Logger used to log messages.</param>
|
extension =>
|
||||||
/// <param name="discoverySink">Used to send testcases and discovery related events back to Discoverer manager.</param>
|
string.Equals(Path.GetExtension(source), extension, StringComparison.OrdinalIgnoreCase)));
|
||||||
[System.Security.SecurityCritical]
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification ="Discovery context can be null.")]
|
|
||||||
public void DiscoverTests(
|
|
||||||
IEnumerable<string> sources,
|
|
||||||
IDiscoveryContext discoveryContext,
|
|
||||||
IMessageLogger logger,
|
|
||||||
ITestCaseDiscoverySink discoverySink)
|
|
||||||
{
|
|
||||||
ValidateArg.NotNull(sources, "sources");
|
|
||||||
ValidateArg.NotNull(logger, "logger");
|
|
||||||
ValidateArg.NotNull(discoverySink, "discoverySink");
|
|
||||||
|
|
||||||
if (!this.AreValidSources(sources))
|
|
||||||
{
|
|
||||||
throw new NotSupportedException(Resource.SourcesNotSupported);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate the runsettings.
|
|
||||||
try
|
|
||||||
{
|
|
||||||
MSTestSettings.PopulateSettings(discoveryContext);
|
|
||||||
}
|
|
||||||
catch (AdapterSettingsException ex)
|
|
||||||
{
|
|
||||||
logger.SendMessage(TestMessageLevel.Error, ex.Message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scenarios that include testsettings or forcing a run via the legacy adapter are currently not supported in MSTestAdapter.
|
|
||||||
if (MSTestSettings.IsLegacyScenario(logger))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
new UnitTestDiscoverer().DiscoverTests(sources, logger, discoverySink, discoveryContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Verifies if the sources are valid for the target platform.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sources">The test sources</param>
|
|
||||||
/// <remarks>Sources cannot be null.</remarks>
|
|
||||||
/// <returns>True if the source has a valid extension for the current platform.</returns>
|
|
||||||
internal bool AreValidSources(IEnumerable<string> sources)
|
|
||||||
{
|
|
||||||
// ValidSourceExtensions is always expected to return a non-null list.
|
|
||||||
return
|
|
||||||
sources.Any(
|
|
||||||
source =>
|
|
||||||
PlatformServiceProvider.Instance.TestSource.ValidSourceExtensions.Any(
|
|
||||||
extension =>
|
|
||||||
string.Equals(Path.GetExtension(source), extension, StringComparison.OrdinalIgnoreCase)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,119 +1,118 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the execution logic for this adapter.
|
||||||
|
/// </summary>
|
||||||
|
[ExtensionUri(TestAdapter.Constants.ExecutorUriString)]
|
||||||
|
public class MSTestExecutor : ITestExecutor
|
||||||
{
|
{
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Collections.Generic;
|
/// Token for canceling the test run.
|
||||||
using System.Linq;
|
/// </summary>
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution;
|
private TestRunCancellationToken cancellationToken = null;
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains the execution logic for this adapter.
|
/// Initializes a new instance of the <see cref="MSTestExecutor"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ExtensionUri(TestAdapter.Constants.ExecutorUriString)]
|
public MSTestExecutor()
|
||||||
public class MSTestExecutor : ITestExecutor
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
this.TestExecutionManager = new TestExecutionManager();
|
||||||
/// Token for canceling the test run.
|
this.MSTestDiscoverer = new MSTestDiscoverer();
|
||||||
/// </summary>
|
}
|
||||||
private TestRunCancellationToken cancellationToken = null;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="MSTestExecutor"/> class.
|
/// Gets or sets the ms test execution manager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MSTestExecutor()
|
public TestExecutionManager TestExecutionManager { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets discoverer used for validating the sources.
|
||||||
|
/// </summary>
|
||||||
|
private MSTestDiscoverer MSTestDiscoverer { get; }
|
||||||
|
|
||||||
|
public void RunTests(IEnumerable<TestCase> tests, IRunContext runContext, IFrameworkHandle frameworkHandle)
|
||||||
|
{
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("MSTestExecutor.RunTests: Running tests from testcases.");
|
||||||
|
|
||||||
|
ValidateArg.NotNull(frameworkHandle, "frameworkHandle");
|
||||||
|
ValidateArg.NotNullOrEmpty(tests, "tests");
|
||||||
|
|
||||||
|
if (!this.MSTestDiscoverer.AreValidSources(from test in tests select test.Source))
|
||||||
{
|
{
|
||||||
this.TestExecutionManager = new TestExecutionManager();
|
throw new NotSupportedException();
|
||||||
this.MSTestDiscoverer = new MSTestDiscoverer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// Populate the runsettings.
|
||||||
/// Gets or sets the ms test execution manager.
|
try
|
||||||
/// </summary>
|
|
||||||
public TestExecutionManager TestExecutionManager { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets discoverer used for validating the sources.
|
|
||||||
/// </summary>
|
|
||||||
private MSTestDiscoverer MSTestDiscoverer { get; }
|
|
||||||
|
|
||||||
public void RunTests(IEnumerable<TestCase> tests, IRunContext runContext, IFrameworkHandle frameworkHandle)
|
|
||||||
{
|
{
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("MSTestExecutor.RunTests: Running tests from testcases.");
|
MSTestSettings.PopulateSettings(runContext);
|
||||||
|
}
|
||||||
ValidateArg.NotNull(frameworkHandle, "frameworkHandle");
|
catch (AdapterSettingsException ex)
|
||||||
ValidateArg.NotNullOrEmpty(tests, "tests");
|
{
|
||||||
|
frameworkHandle.SendMessage(TestMessageLevel.Error, ex.Message);
|
||||||
if (!this.MSTestDiscoverer.AreValidSources(from test in tests select test.Source))
|
return;
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate the runsettings.
|
|
||||||
try
|
|
||||||
{
|
|
||||||
MSTestSettings.PopulateSettings(runContext);
|
|
||||||
}
|
|
||||||
catch (AdapterSettingsException ex)
|
|
||||||
{
|
|
||||||
frameworkHandle.SendMessage(TestMessageLevel.Error, ex.Message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scenarios that include testsettings or forcing a run via the legacy adapter are currently not supported in MSTestAdapter.
|
|
||||||
if (MSTestSettings.IsLegacyScenario(frameworkHandle))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cancellationToken = new TestRunCancellationToken();
|
|
||||||
this.TestExecutionManager.RunTests(tests, runContext, frameworkHandle, this.cancellationToken);
|
|
||||||
this.cancellationToken = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RunTests(IEnumerable<string> sources, IRunContext runContext, IFrameworkHandle frameworkHandle)
|
// Scenarios that include testsettings or forcing a run via the legacy adapter are currently not supported in MSTestAdapter.
|
||||||
|
if (MSTestSettings.IsLegacyScenario(frameworkHandle))
|
||||||
{
|
{
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("MSTestExecutor.RunTests: Running tests from sources.");
|
return;
|
||||||
ValidateArg.NotNull(frameworkHandle, "frameworkHandle");
|
|
||||||
ValidateArg.NotNullOrEmpty(sources, "sources");
|
|
||||||
|
|
||||||
if (!this.MSTestDiscoverer.AreValidSources(sources))
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate the runsettings.
|
|
||||||
try
|
|
||||||
{
|
|
||||||
MSTestSettings.PopulateSettings(runContext);
|
|
||||||
}
|
|
||||||
catch (AdapterSettingsException ex)
|
|
||||||
{
|
|
||||||
frameworkHandle.SendMessage(TestMessageLevel.Error, ex.Message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scenarios that include testsettings or forcing a run via the legacy adapter are currently not supported in MSTestAdapter.
|
|
||||||
if (MSTestSettings.IsLegacyScenario(frameworkHandle))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sources = PlatformServiceProvider.Instance.TestSource.GetTestSources(sources);
|
|
||||||
this.cancellationToken = new TestRunCancellationToken();
|
|
||||||
this.TestExecutionManager.RunTests(sources, runContext, frameworkHandle, this.cancellationToken);
|
|
||||||
|
|
||||||
this.cancellationToken = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Cancel()
|
this.cancellationToken = new TestRunCancellationToken();
|
||||||
|
this.TestExecutionManager.RunTests(tests, runContext, frameworkHandle, this.cancellationToken);
|
||||||
|
this.cancellationToken = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RunTests(IEnumerable<string> sources, IRunContext runContext, IFrameworkHandle frameworkHandle)
|
||||||
|
{
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("MSTestExecutor.RunTests: Running tests from sources.");
|
||||||
|
ValidateArg.NotNull(frameworkHandle, "frameworkHandle");
|
||||||
|
ValidateArg.NotNullOrEmpty(sources, "sources");
|
||||||
|
|
||||||
|
if (!this.MSTestDiscoverer.AreValidSources(sources))
|
||||||
{
|
{
|
||||||
this.cancellationToken?.Cancel();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Populate the runsettings.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MSTestSettings.PopulateSettings(runContext);
|
||||||
|
}
|
||||||
|
catch (AdapterSettingsException ex)
|
||||||
|
{
|
||||||
|
frameworkHandle.SendMessage(TestMessageLevel.Error, ex.Message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenarios that include testsettings or forcing a run via the legacy adapter are currently not supported in MSTestAdapter.
|
||||||
|
if (MSTestSettings.IsLegacyScenario(frameworkHandle))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sources = PlatformServiceProvider.Instance.TestSource.GetTestSources(sources);
|
||||||
|
this.cancellationToken = new TestRunCancellationToken();
|
||||||
|
this.TestExecutionManager.RunTests(sources, runContext, frameworkHandle, this.cancellationToken);
|
||||||
|
|
||||||
|
this.cancellationToken = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Cancel()
|
||||||
|
{
|
||||||
|
this.cancellationToken?.Cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,15 +1,14 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
{
|
|
||||||
using System;
|
|
||||||
|
|
||||||
internal class AdapterSettingsException : Exception
|
using System;
|
||||||
|
|
||||||
|
internal class AdapterSettingsException : Exception
|
||||||
|
{
|
||||||
|
internal AdapterSettingsException(string message)
|
||||||
|
: base(message)
|
||||||
{
|
{
|
||||||
internal AdapterSettingsException(string message)
|
|
||||||
: base(message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
|
||||||
|
internal enum DynamicDataType : int
|
||||||
{
|
{
|
||||||
internal enum DynamicDataType : int
|
None = 0,
|
||||||
{
|
DataSourceAttribute = 1,
|
||||||
None = 0,
|
ITestDataSource = 2
|
||||||
DataSourceAttribute = 1,
|
|
||||||
ITestDataSource = 2
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,49 +1,48 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
internal class StackTraceInformation
|
||||||
{
|
{
|
||||||
using System;
|
public StackTraceInformation(string stackTrace)
|
||||||
using System.Diagnostics;
|
: this(stackTrace, null, 0, 0)
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
internal class StackTraceInformation
|
|
||||||
{
|
{
|
||||||
public StackTraceInformation(string stackTrace)
|
|
||||||
: this(stackTrace, null, 0, 0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public StackTraceInformation(string stackTrace, string filePath, int lineNumber, int columnNumber)
|
|
||||||
{
|
|
||||||
Debug.Assert(!string.IsNullOrEmpty(stackTrace), "StackTrace message should not be empty");
|
|
||||||
Debug.Assert(lineNumber >= 0, "Line number should be greater than or equal to 0");
|
|
||||||
Debug.Assert(columnNumber >= 0, "Column number should be greater than or equal to 0");
|
|
||||||
|
|
||||||
this.ErrorStackTrace = stackTrace;
|
|
||||||
this.ErrorFilePath = filePath;
|
|
||||||
this.ErrorLineNumber = lineNumber;
|
|
||||||
this.ErrorColumnNumber = columnNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets stack Trace associated with the test failure
|
|
||||||
/// </summary>
|
|
||||||
public string ErrorStackTrace { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets source code FilePath where the error occurred
|
|
||||||
/// </summary>
|
|
||||||
public string ErrorFilePath { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets line number in the source code file where the error occurred.
|
|
||||||
/// </summary>
|
|
||||||
public int ErrorLineNumber { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets column number in the source code file where the error occurred.
|
|
||||||
/// </summary>
|
|
||||||
public int ErrorColumnNumber { get; private set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StackTraceInformation(string stackTrace, string filePath, int lineNumber, int columnNumber)
|
||||||
|
{
|
||||||
|
Debug.Assert(!string.IsNullOrEmpty(stackTrace), "StackTrace message should not be empty");
|
||||||
|
Debug.Assert(lineNumber >= 0, "Line number should be greater than or equal to 0");
|
||||||
|
Debug.Assert(columnNumber >= 0, "Column number should be greater than or equal to 0");
|
||||||
|
|
||||||
|
this.ErrorStackTrace = stackTrace;
|
||||||
|
this.ErrorFilePath = filePath;
|
||||||
|
this.ErrorLineNumber = lineNumber;
|
||||||
|
this.ErrorColumnNumber = columnNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets stack Trace associated with the test failure
|
||||||
|
/// </summary>
|
||||||
|
public string ErrorStackTrace { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets source code FilePath where the error occurred
|
||||||
|
/// </summary>
|
||||||
|
public string ErrorFilePath { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets line number in the source code file where the error occurred.
|
||||||
|
/// </summary>
|
||||||
|
public int ErrorLineNumber { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets column number in the source code file where the error occurred.
|
||||||
|
/// </summary>
|
||||||
|
public int ErrorColumnNumber { get; private set; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,36 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
internal class TestAssemblySettings
|
||||||
{
|
{
|
||||||
using System;
|
public TestAssemblySettings()
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
internal class TestAssemblySettings
|
|
||||||
{
|
{
|
||||||
public TestAssemblySettings()
|
this.Workers = -1;
|
||||||
{
|
|
||||||
this.Workers = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the parallelization level.
|
|
||||||
/// </summary>
|
|
||||||
internal int Workers { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the mode of parallelization.
|
|
||||||
/// </summary>
|
|
||||||
internal ExecutionScope Scope { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether the assembly can be parallelized.
|
|
||||||
/// </summary>
|
|
||||||
internal bool CanParallelizeAssembly { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the class cleanup lifecycle timing.
|
|
||||||
/// </summary>
|
|
||||||
internal ClassCleanupBehavior ClassCleanupLifecycle { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the parallelization level.
|
||||||
|
/// </summary>
|
||||||
|
internal int Workers { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the mode of parallelization.
|
||||||
|
/// </summary>
|
||||||
|
internal ExecutionScope Scope { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the assembly can be parallelized.
|
||||||
|
/// </summary>
|
||||||
|
internal bool CanParallelizeAssembly { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the class cleanup lifecycle timing.
|
||||||
|
/// </summary>
|
||||||
|
internal ClassCleanupBehavior ClassCleanupLifecycle { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,49 +1,48 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal class to indicate Test Execution failure
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
internal class TestFailedException : Exception
|
||||||
{
|
{
|
||||||
using System;
|
public TestFailedException(UnitTestOutcome outcome, string errorMessage)
|
||||||
using System.Diagnostics;
|
: this(outcome, errorMessage, null, null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestFailedException(UnitTestOutcome outcome, string errorMessage, StackTraceInformation stackTraceInformation)
|
||||||
|
: this(outcome, errorMessage, stackTraceInformation, null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestFailedException(UnitTestOutcome outcome, string errorMessage, Exception realException)
|
||||||
|
: this(outcome, errorMessage, null, realException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestFailedException(UnitTestOutcome outcome, string errorMessage, StackTraceInformation stackTraceInformation, Exception realException)
|
||||||
|
: base(errorMessage, realException)
|
||||||
|
{
|
||||||
|
Debug.Assert(!string.IsNullOrEmpty(errorMessage), "ErrorMessage should not be empty");
|
||||||
|
|
||||||
|
this.Outcome = outcome;
|
||||||
|
this.StackTraceInformation = stackTraceInformation;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal class to indicate Test Execution failure
|
/// Gets stack trace information associated with the test failure
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Serializable]
|
public StackTraceInformation StackTraceInformation { get; private set; }
|
||||||
internal class TestFailedException : Exception
|
|
||||||
{
|
|
||||||
public TestFailedException(UnitTestOutcome outcome, string errorMessage)
|
|
||||||
: this(outcome, errorMessage, null, null)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public TestFailedException(UnitTestOutcome outcome, string errorMessage, StackTraceInformation stackTraceInformation)
|
/// <summary>
|
||||||
: this(outcome, errorMessage, stackTraceInformation, null)
|
/// Gets outcome of the test case
|
||||||
{
|
/// </summary>
|
||||||
}
|
public UnitTestOutcome Outcome { get; private set; }
|
||||||
|
|
||||||
public TestFailedException(UnitTestOutcome outcome, string errorMessage, Exception realException)
|
|
||||||
: this(outcome, errorMessage, null, realException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public TestFailedException(UnitTestOutcome outcome, string errorMessage, StackTraceInformation stackTraceInformation, Exception realException)
|
|
||||||
: base(errorMessage, realException)
|
|
||||||
{
|
|
||||||
Debug.Assert(!string.IsNullOrEmpty(errorMessage), "ErrorMessage should not be empty");
|
|
||||||
|
|
||||||
this.Outcome = outcome;
|
|
||||||
this.StackTraceInformation = stackTraceInformation;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets stack trace information associated with the test failure
|
|
||||||
/// </summary>
|
|
||||||
public StackTraceInformation StackTraceInformation { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets outcome of the test case
|
|
||||||
/// </summary>
|
|
||||||
public UnitTestOutcome Outcome { get; private set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,167 +1,166 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Microsoft.TestPlatform.AdapterUtilities;
|
||||||
|
using Microsoft.TestPlatform.AdapterUtilities.ManagedNameUtilities;
|
||||||
|
|
||||||
|
using MSTestAdapter.PlatformServices.Interface.ObjectModel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TestMethod contains information about a unit test method that needs to be executed
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public sealed class TestMethod : ITestMethod
|
||||||
{
|
{
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Collections.Generic;
|
/// Number of elements in <see cref="Hierarchy"/>.
|
||||||
using System.Collections.ObjectModel;
|
/// </summary>
|
||||||
using System.Diagnostics;
|
public const int TotalHierarchyLevels = HierarchyConstants.Levels.TotalLevelCount;
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
using Microsoft.TestPlatform.AdapterUtilities;
|
#region Fields
|
||||||
using Microsoft.TestPlatform.AdapterUtilities.ManagedNameUtilities;
|
private IReadOnlyCollection<string> hierarchy;
|
||||||
|
private string declaringClassFullName = null;
|
||||||
|
private string declaringAssemblyName = null;
|
||||||
|
#endregion
|
||||||
|
|
||||||
using MSTestAdapter.PlatformServices.Interface.ObjectModel;
|
public TestMethod(string name, string fullClassName, string assemblyName, bool isAsync)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(assemblyName))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(assemblyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(!string.IsNullOrEmpty(name), "TestName cannot be empty");
|
||||||
|
Debug.Assert(!string.IsNullOrEmpty(fullClassName), "Full className cannot be empty");
|
||||||
|
|
||||||
|
this.Name = name;
|
||||||
|
this.FullClassName = fullClassName;
|
||||||
|
this.AssemblyName = assemblyName;
|
||||||
|
this.IsAsync = isAsync;
|
||||||
|
|
||||||
|
var hierarchy = new string[HierarchyConstants.Levels.TotalLevelCount];
|
||||||
|
hierarchy[HierarchyConstants.Levels.ContainerIndex] = null;
|
||||||
|
hierarchy[HierarchyConstants.Levels.NamespaceIndex] = fullClassName;
|
||||||
|
hierarchy[HierarchyConstants.Levels.ClassIndex] = name;
|
||||||
|
hierarchy[HierarchyConstants.Levels.TestGroupIndex] = name;
|
||||||
|
|
||||||
|
this.hierarchy = new ReadOnlyCollection<string>(hierarchy);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal TestMethod(MethodBase method, string name, string fullClassName, string assemblyName, bool isAsync)
|
||||||
|
: this(name, fullClassName, assemblyName, isAsync)
|
||||||
|
{
|
||||||
|
if (method == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(method));
|
||||||
|
}
|
||||||
|
|
||||||
|
ManagedNameHelper.GetManagedName(method, out var managedType, out var managedMethod, out var hierarchyValues);
|
||||||
|
hierarchyValues[HierarchyConstants.Levels.ContainerIndex] = null; // This one will be set by test windows to current test project name.
|
||||||
|
|
||||||
|
this.ManagedTypeName = managedType;
|
||||||
|
this.ManagedMethodName = managedMethod;
|
||||||
|
this.hierarchy = new ReadOnlyCollection<string>(hierarchyValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal TestMethod(string managedTypeName, string managedMethodName, string[] hierarchyValues, string name, string fullClassName, string assemblyName, bool isAsync)
|
||||||
|
: this(name, fullClassName, assemblyName, isAsync)
|
||||||
|
{
|
||||||
|
this.ManagedTypeName = managedTypeName;
|
||||||
|
this.ManagedMethodName = managedMethodName;
|
||||||
|
this.hierarchy = new ReadOnlyCollection<string>(hierarchyValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string FullClassName { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// TestMethod contains information about a unit test method that needs to be executed
|
/// Gets or sets the declaring assembly full name. This will be used while getting navigation data.
|
||||||
|
/// This will be null if AssemblyName is same as DeclaringAssemblyName.
|
||||||
|
/// Reason to set to null in the above case is to minimize the transfer of data across appdomains and not have a performance hit.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Serializable]
|
public string DeclaringAssemblyName
|
||||||
public sealed class TestMethod : ITestMethod
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
get
|
||||||
/// Number of elements in <see cref="Hierarchy"/>.
|
|
||||||
/// </summary>
|
|
||||||
public const int TotalHierarchyLevels = HierarchyConstants.Levels.TotalLevelCount;
|
|
||||||
|
|
||||||
#region Fields
|
|
||||||
private IReadOnlyCollection<string> hierarchy;
|
|
||||||
private string declaringClassFullName = null;
|
|
||||||
private string declaringAssemblyName = null;
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
public TestMethod(string name, string fullClassName, string assemblyName, bool isAsync)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(assemblyName))
|
return this.declaringAssemblyName;
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(assemblyName));
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Assert(!string.IsNullOrEmpty(name), "TestName cannot be empty");
|
|
||||||
Debug.Assert(!string.IsNullOrEmpty(fullClassName), "Full className cannot be empty");
|
|
||||||
|
|
||||||
this.Name = name;
|
|
||||||
this.FullClassName = fullClassName;
|
|
||||||
this.AssemblyName = assemblyName;
|
|
||||||
this.IsAsync = isAsync;
|
|
||||||
|
|
||||||
var hierarchy = new string[HierarchyConstants.Levels.TotalLevelCount];
|
|
||||||
hierarchy[HierarchyConstants.Levels.ContainerIndex] = null;
|
|
||||||
hierarchy[HierarchyConstants.Levels.NamespaceIndex] = fullClassName;
|
|
||||||
hierarchy[HierarchyConstants.Levels.ClassIndex] = name;
|
|
||||||
hierarchy[HierarchyConstants.Levels.TestGroupIndex] = name;
|
|
||||||
|
|
||||||
this.hierarchy = new ReadOnlyCollection<string>(hierarchy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal TestMethod(MethodBase method, string name, string fullClassName, string assemblyName, bool isAsync)
|
set
|
||||||
: this(name, fullClassName, assemblyName, isAsync)
|
|
||||||
{
|
{
|
||||||
if (method == null)
|
Debug.Assert(value != this.AssemblyName, "DeclaringAssemblyName should not be the same as AssemblyName.");
|
||||||
{
|
this.declaringAssemblyName = value;
|
||||||
throw new ArgumentNullException(nameof(method));
|
|
||||||
}
|
|
||||||
|
|
||||||
ManagedNameHelper.GetManagedName(method, out var managedType, out var managedMethod, out var hierarchyValues);
|
|
||||||
hierarchyValues[HierarchyConstants.Levels.ContainerIndex] = null; // This one will be set by test windows to current test project name.
|
|
||||||
|
|
||||||
this.ManagedTypeName = managedType;
|
|
||||||
this.ManagedMethodName = managedMethod;
|
|
||||||
this.hierarchy = new ReadOnlyCollection<string>(hierarchyValues);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal TestMethod(string managedTypeName, string managedMethodName, string[] hierarchyValues, string name, string fullClassName, string assemblyName, bool isAsync)
|
|
||||||
: this(name, fullClassName, assemblyName, isAsync)
|
|
||||||
{
|
|
||||||
this.ManagedTypeName = managedTypeName;
|
|
||||||
this.ManagedMethodName = managedMethodName;
|
|
||||||
this.hierarchy = new ReadOnlyCollection<string>(hierarchyValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Name { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string FullClassName { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the declaring assembly full name. This will be used while getting navigation data.
|
|
||||||
/// This will be null if AssemblyName is same as DeclaringAssemblyName.
|
|
||||||
/// Reason to set to null in the above case is to minimize the transfer of data across appdomains and not have a performance hit.
|
|
||||||
/// </summary>
|
|
||||||
public string DeclaringAssemblyName
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.declaringAssemblyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
set
|
|
||||||
{
|
|
||||||
Debug.Assert(value != this.AssemblyName, "DeclaringAssemblyName should not be the same as AssemblyName.");
|
|
||||||
this.declaringAssemblyName = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the declaring class full name.
|
|
||||||
/// This will be used to resolve overloads and while getting navigation data.
|
|
||||||
/// This will be null if FullClassName is same as DeclaringClassFullName.
|
|
||||||
/// Reason to set to null in the above case is to minimize the transfer of data across appdomains and not have a perf hit.
|
|
||||||
/// </summary>
|
|
||||||
public string DeclaringClassFullName
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.declaringClassFullName;
|
|
||||||
}
|
|
||||||
|
|
||||||
set
|
|
||||||
{
|
|
||||||
Debug.Assert(value != this.FullClassName, "DeclaringClassFullName should not be the same as FullClassName.");
|
|
||||||
this.declaringClassFullName = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string AssemblyName { get; private set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsAsync { get; private set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string ManagedTypeName { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string ManagedMethodName { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool HasManagedMethodAndTypeProperties => !string.IsNullOrWhiteSpace(this.ManagedTypeName) && !string.IsNullOrWhiteSpace(this.ManagedMethodName);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IReadOnlyCollection<string> Hierarchy => this.hierarchy;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets type of dynamic data if any
|
|
||||||
/// </summary>
|
|
||||||
internal DynamicDataType DataType { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the serialized data
|
|
||||||
/// </summary>
|
|
||||||
internal string[] SerializedData { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the test group set during discovery
|
|
||||||
/// </summary>
|
|
||||||
internal string TestGroup { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the display name set during discovery
|
|
||||||
/// </summary>
|
|
||||||
internal string DisplayName { get; set; }
|
|
||||||
|
|
||||||
internal TestMethod Clone() => this.MemberwiseClone() as TestMethod;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the declaring class full name.
|
||||||
|
/// This will be used to resolve overloads and while getting navigation data.
|
||||||
|
/// This will be null if FullClassName is same as DeclaringClassFullName.
|
||||||
|
/// Reason to set to null in the above case is to minimize the transfer of data across appdomains and not have a perf hit.
|
||||||
|
/// </summary>
|
||||||
|
public string DeclaringClassFullName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.declaringClassFullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Debug.Assert(value != this.FullClassName, "DeclaringClassFullName should not be the same as FullClassName.");
|
||||||
|
this.declaringClassFullName = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string AssemblyName { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsAsync { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string ManagedTypeName { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string ManagedMethodName { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool HasManagedMethodAndTypeProperties => !string.IsNullOrWhiteSpace(this.ManagedTypeName) && !string.IsNullOrWhiteSpace(this.ManagedMethodName);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyCollection<string> Hierarchy => this.hierarchy;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets type of dynamic data if any
|
||||||
|
/// </summary>
|
||||||
|
internal DynamicDataType DataType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the serialized data
|
||||||
|
/// </summary>
|
||||||
|
internal string[] SerializedData { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the test group set during discovery
|
||||||
|
/// </summary>
|
||||||
|
internal string TestGroup { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the display name set during discovery
|
||||||
|
/// </summary>
|
||||||
|
internal string DisplayName { get; set; }
|
||||||
|
|
||||||
|
internal TestMethod Clone() => this.MemberwiseClone() as TestMethod;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,38 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A facade service for options passed to a test method.
|
||||||
|
/// </summary>
|
||||||
|
internal class TestMethodOptions
|
||||||
{
|
{
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
/// <summary>
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
/// Gets or sets the timeout specified for a test method.
|
||||||
|
/// </summary>
|
||||||
|
internal int Timeout { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A facade service for options passed to a test method.
|
/// Gets or sets the ExpectedException attribute adorned on a test method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class TestMethodOptions
|
internal ExpectedExceptionBaseAttribute ExpectedException { get; set; }
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the timeout specified for a test method.
|
|
||||||
/// </summary>
|
|
||||||
internal int Timeout { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the ExpectedException attribute adorned on a test method.
|
/// Gets or sets the testcontext passed into the test method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal ExpectedExceptionBaseAttribute ExpectedException { get; set; }
|
internal ITestContext TestContext { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the testcontext passed into the test method.
|
/// Gets or sets a value indicating whether debug traces should be captured when running the test.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal ITestContext TestContext { get; set; }
|
internal bool CaptureDebugTraces { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether debug traces should be captured when running the test.
|
/// Gets or sets the test method executor that invokes the test.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal bool CaptureDebugTraces { get; set; }
|
internal TestMethodAttribute Executor { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the test method executor that invokes the test.
|
|
||||||
/// </summary>
|
|
||||||
internal TestMethodAttribute Executor { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,28 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal class to indicate type inspection failure
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
internal class TypeInspectionException : Exception
|
||||||
{
|
{
|
||||||
using System;
|
public TypeInspectionException()
|
||||||
|
: base()
|
||||||
/// <summary>
|
|
||||||
/// Internal class to indicate type inspection failure
|
|
||||||
/// </summary>
|
|
||||||
[Serializable]
|
|
||||||
internal class TypeInspectionException : Exception
|
|
||||||
{
|
{
|
||||||
public TypeInspectionException()
|
}
|
||||||
: base()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public TypeInspectionException(string message)
|
public TypeInspectionException(string message)
|
||||||
: base(message)
|
: base(message)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public TypeInspectionException(string message, Exception innerException)
|
public TypeInspectionException(string message, Exception innerException)
|
||||||
: base(message, innerException)
|
: base(message, innerException)
|
||||||
{
|
{
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,272 +1,271 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
using Microsoft.TestPlatform.AdapterUtilities;
|
using System;
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
|
using System.Collections.Generic;
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Microsoft.TestPlatform.AdapterUtilities;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The unit test element.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
[DebuggerDisplay("{GetDisplayName()} ({TestMethod.ManagedTypeName})")]
|
||||||
|
internal class UnitTestElement
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="UnitTestElement"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="testMethod"> The test method. </param>
|
||||||
|
/// <exception cref="ArgumentNullException"> Thrown when method is null. </exception>
|
||||||
|
public UnitTestElement(TestMethod testMethod)
|
||||||
|
{
|
||||||
|
if (testMethod == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(testMethod));
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(testMethod.FullClassName != null, "Full className cannot be empty");
|
||||||
|
this.TestMethod = testMethod;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The unit test element.
|
/// Gets the test method which should be executed as part of this test case
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Serializable]
|
public TestMethod TestMethod { get; private set; }
|
||||||
[DebuggerDisplay("{GetDisplayName()} ({TestMethod.ManagedTypeName})")]
|
|
||||||
internal class UnitTestElement
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the unit test should be ignored at run-time
|
||||||
|
/// </summary>
|
||||||
|
public bool Ignored { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether it is a async test
|
||||||
|
/// </summary>
|
||||||
|
public bool IsAsync { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the test categories for test method.
|
||||||
|
/// </summary>
|
||||||
|
public string[] TestCategory { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the traits for test method.
|
||||||
|
/// </summary>
|
||||||
|
public Trait[] Traits { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the priority of the test method, if any.
|
||||||
|
/// </summary>
|
||||||
|
public int? Priority { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether this test method should not execute in parallel.
|
||||||
|
/// </summary>
|
||||||
|
public bool DoNotParallelize { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the deployment items for the test method.
|
||||||
|
/// </summary>
|
||||||
|
public KeyValuePair<string, string>[] DeploymentItems { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the DisplayName
|
||||||
|
/// </summary>
|
||||||
|
public string DisplayName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the compiler generated type name for async test method.
|
||||||
|
/// </summary>
|
||||||
|
internal string AsyncTypeName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Css Iteration for the test method.
|
||||||
|
/// </summary>
|
||||||
|
internal string CssIteration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Css Project Structure for the test method.
|
||||||
|
/// </summary>
|
||||||
|
internal string CssProjectStructure { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Description for the test method.
|
||||||
|
/// </summary>
|
||||||
|
internal string Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Work Item Ids for the test method.
|
||||||
|
/// </summary>
|
||||||
|
internal string[] WorkItemIds { get; set; }
|
||||||
|
|
||||||
|
internal UnitTestElement Clone()
|
||||||
{
|
{
|
||||||
/// <summary>
|
var clone = this.MemberwiseClone() as UnitTestElement;
|
||||||
/// Initializes a new instance of the <see cref="UnitTestElement"/> class.
|
if (this.TestMethod != null)
|
||||||
/// </summary>
|
|
||||||
/// <param name="testMethod"> The test method. </param>
|
|
||||||
/// <exception cref="ArgumentNullException"> Thrown when method is null. </exception>
|
|
||||||
public UnitTestElement(TestMethod testMethod)
|
|
||||||
{
|
{
|
||||||
if (testMethod == null)
|
clone.TestMethod = this.TestMethod.Clone();
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(testMethod));
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Assert(testMethod.FullClassName != null, "Full className cannot be empty");
|
|
||||||
this.TestMethod = testMethod;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return clone;
|
||||||
/// Gets the test method which should be executed as part of this test case
|
}
|
||||||
/// </summary>
|
|
||||||
public TestMethod TestMethod { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether the unit test should be ignored at run-time
|
/// Convert the UnitTestElement instance to an Object Model testCase instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Ignored { get; set; }
|
/// <returns> An instance of <see cref="TestCase"/>. </returns>
|
||||||
|
internal TestCase ToTestCase()
|
||||||
|
{
|
||||||
|
// This causes compatibility problems with older runners.
|
||||||
|
// string fullName = this.TestMethod.HasManagedMethodAndTypeProperties
|
||||||
|
// ? string.Format(CultureInfo.InvariantCulture, "{0}.{1}", this.TestMethod.ManagedTypeName, this.TestMethod.ManagedMethodName)
|
||||||
|
// : string.Format(CultureInfo.InvariantCulture, "{0}.{1}", this.TestMethod.FullClassName, this.TestMethod.Name);
|
||||||
|
var fullName = string.Format(CultureInfo.InvariantCulture, "{0}.{1}", this.TestMethod.FullClassName, this.TestMethod.Name);
|
||||||
|
|
||||||
/// <summary>
|
TestCase testCase = new(fullName, TestAdapter.Constants.ExecutorUri, this.TestMethod.AssemblyName);
|
||||||
/// Gets or sets a value indicating whether it is a async test
|
testCase.DisplayName = this.GetDisplayName();
|
||||||
/// </summary>
|
|
||||||
public bool IsAsync { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
if (this.TestMethod.HasManagedMethodAndTypeProperties)
|
||||||
/// Gets or sets the test categories for test method.
|
|
||||||
/// </summary>
|
|
||||||
public string[] TestCategory { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the traits for test method.
|
|
||||||
/// </summary>
|
|
||||||
public Trait[] Traits { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the priority of the test method, if any.
|
|
||||||
/// </summary>
|
|
||||||
public int? Priority { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this test method should not execute in parallel.
|
|
||||||
/// </summary>
|
|
||||||
public bool DoNotParallelize { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the deployment items for the test method.
|
|
||||||
/// </summary>
|
|
||||||
public KeyValuePair<string, string>[] DeploymentItems { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the DisplayName
|
|
||||||
/// </summary>
|
|
||||||
public string DisplayName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the compiler generated type name for async test method.
|
|
||||||
/// </summary>
|
|
||||||
internal string AsyncTypeName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Css Iteration for the test method.
|
|
||||||
/// </summary>
|
|
||||||
internal string CssIteration { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Css Project Structure for the test method.
|
|
||||||
/// </summary>
|
|
||||||
internal string CssProjectStructure { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Description for the test method.
|
|
||||||
/// </summary>
|
|
||||||
internal string Description { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Work Item Ids for the test method.
|
|
||||||
/// </summary>
|
|
||||||
internal string[] WorkItemIds { get; set; }
|
|
||||||
|
|
||||||
internal UnitTestElement Clone()
|
|
||||||
{
|
{
|
||||||
var clone = this.MemberwiseClone() as UnitTestElement;
|
testCase.SetPropertyValue(TestCaseExtensions.ManagedTypeProperty, this.TestMethod.ManagedTypeName);
|
||||||
if (this.TestMethod != null)
|
testCase.SetPropertyValue(TestCaseExtensions.ManagedMethodProperty, this.TestMethod.ManagedMethodName);
|
||||||
{
|
testCase.SetPropertyValue(TestAdapter.Constants.TestClassNameProperty, this.TestMethod.ManagedTypeName);
|
||||||
clone.TestMethod = this.TestMethod.Clone();
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
return clone;
|
testCase.SetPropertyValue(TestAdapter.Constants.TestClassNameProperty, this.TestMethod.FullClassName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
var hierarchy = this.TestMethod.Hierarchy;
|
||||||
/// Convert the UnitTestElement instance to an Object Model testCase instance.
|
if (hierarchy != null && hierarchy.Count > 0)
|
||||||
/// </summary>
|
|
||||||
/// <returns> An instance of <see cref="TestCase"/>. </returns>
|
|
||||||
internal TestCase ToTestCase()
|
|
||||||
{
|
{
|
||||||
|
testCase.SetHierarchy(hierarchy.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set declaring type if present so the correct method info can be retrieved
|
||||||
|
if (this.TestMethod.DeclaringClassFullName != null)
|
||||||
|
{
|
||||||
|
testCase.SetPropertyValue(TestAdapter.Constants.DeclaringClassNameProperty, this.TestMethod.DeclaringClassFullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Many of the tests will not be async, so there is no point in sending extra data
|
||||||
|
if (this.IsAsync)
|
||||||
|
{
|
||||||
|
testCase.SetPropertyValue(TestAdapter.Constants.AsyncTestProperty, this.IsAsync);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set only if some test category is present
|
||||||
|
if (this.TestCategory != null && this.TestCategory.Length > 0)
|
||||||
|
{
|
||||||
|
testCase.SetPropertyValue(TestAdapter.Constants.TestCategoryProperty, this.TestCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set priority if present
|
||||||
|
if (this.Priority != null)
|
||||||
|
{
|
||||||
|
testCase.SetPropertyValue(TestAdapter.Constants.PriorityProperty, this.Priority.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.Traits != null)
|
||||||
|
{
|
||||||
|
testCase.Traits.AddRange(this.Traits);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(this.CssIteration))
|
||||||
|
{
|
||||||
|
testCase.SetPropertyValue(TestAdapter.Constants.CssIterationProperty, this.CssIteration);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(this.CssProjectStructure))
|
||||||
|
{
|
||||||
|
testCase.SetPropertyValue(TestAdapter.Constants.CssProjectStructureProperty, this.CssProjectStructure);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(this.Description))
|
||||||
|
{
|
||||||
|
testCase.SetPropertyValue(TestAdapter.Constants.DescriptionProperty, this.Description);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.WorkItemIds != null)
|
||||||
|
{
|
||||||
|
testCase.SetPropertyValue(TestAdapter.Constants.WorkItemIdsProperty, this.WorkItemIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The list of items to deploy before running this test.
|
||||||
|
if (this.DeploymentItems != null && this.DeploymentItems.Length > 0)
|
||||||
|
{
|
||||||
|
testCase.SetPropertyValue(TestAdapter.Constants.DeploymentItemsProperty, this.DeploymentItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the Do not parallelize state if present
|
||||||
|
if (this.DoNotParallelize)
|
||||||
|
{
|
||||||
|
testCase.SetPropertyValue(TestAdapter.Constants.DoNotParallelizeProperty, this.DoNotParallelize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store resolved data if any
|
||||||
|
if (this.TestMethod.DataType != DynamicDataType.None)
|
||||||
|
{
|
||||||
|
var data = this.TestMethod.SerializedData;
|
||||||
|
|
||||||
|
testCase.SetPropertyValue(TestAdapter.Constants.TestDynamicDataTypeProperty, (int)this.TestMethod.DataType);
|
||||||
|
testCase.SetPropertyValue(TestAdapter.Constants.TestDynamicDataProperty, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
string fileName = testCase.Source;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fileName = Path.GetFileName(fileName);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
var idProvider = new TestIdProvider();
|
||||||
|
idProvider.AppendString(testCase.ExecutorUri?.ToString());
|
||||||
|
idProvider.AppendString(fileName);
|
||||||
|
if (this.TestMethod.HasManagedMethodAndTypeProperties)
|
||||||
|
{
|
||||||
|
idProvider.AppendString(this.TestMethod.ManagedTypeName);
|
||||||
|
idProvider.AppendString(this.TestMethod.ManagedMethodName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
idProvider.AppendString(testCase.FullyQualifiedName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.TestMethod.DataType != DynamicDataType.None)
|
||||||
|
{
|
||||||
|
idProvider.AppendString(testCase.DisplayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
testCase.Id = idProvider.GetId();
|
||||||
|
|
||||||
|
return testCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetDisplayName()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(this.DisplayName))
|
||||||
|
{
|
||||||
|
return this.TestMethod.Name;
|
||||||
|
|
||||||
// This causes compatibility problems with older runners.
|
// This causes compatibility problems with older runners.
|
||||||
// string fullName = this.TestMethod.HasManagedMethodAndTypeProperties
|
// return string.IsNullOrWhiteSpace(this.TestMethod.ManagedMethodName)
|
||||||
// ? string.Format(CultureInfo.InvariantCulture, "{0}.{1}", this.TestMethod.ManagedTypeName, this.TestMethod.ManagedMethodName)
|
// ? this.TestMethod.Name
|
||||||
// : string.Format(CultureInfo.InvariantCulture, "{0}.{1}", this.TestMethod.FullClassName, this.TestMethod.Name);
|
// : this.TestMethod.ManagedMethodName;
|
||||||
var fullName = string.Format(CultureInfo.InvariantCulture, "{0}.{1}", this.TestMethod.FullClassName, this.TestMethod.Name);
|
|
||||||
|
|
||||||
TestCase testCase = new(fullName, TestAdapter.Constants.ExecutorUri, this.TestMethod.AssemblyName);
|
|
||||||
testCase.DisplayName = this.GetDisplayName();
|
|
||||||
|
|
||||||
if (this.TestMethod.HasManagedMethodAndTypeProperties)
|
|
||||||
{
|
|
||||||
testCase.SetPropertyValue(TestCaseExtensions.ManagedTypeProperty, this.TestMethod.ManagedTypeName);
|
|
||||||
testCase.SetPropertyValue(TestCaseExtensions.ManagedMethodProperty, this.TestMethod.ManagedMethodName);
|
|
||||||
testCase.SetPropertyValue(TestAdapter.Constants.TestClassNameProperty, this.TestMethod.ManagedTypeName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
testCase.SetPropertyValue(TestAdapter.Constants.TestClassNameProperty, this.TestMethod.FullClassName);
|
|
||||||
}
|
|
||||||
|
|
||||||
var hierarchy = this.TestMethod.Hierarchy;
|
|
||||||
if (hierarchy != null && hierarchy.Count > 0)
|
|
||||||
{
|
|
||||||
testCase.SetHierarchy(hierarchy.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set declaring type if present so the correct method info can be retrieved
|
|
||||||
if (this.TestMethod.DeclaringClassFullName != null)
|
|
||||||
{
|
|
||||||
testCase.SetPropertyValue(TestAdapter.Constants.DeclaringClassNameProperty, this.TestMethod.DeclaringClassFullName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Many of the tests will not be async, so there is no point in sending extra data
|
|
||||||
if (this.IsAsync)
|
|
||||||
{
|
|
||||||
testCase.SetPropertyValue(TestAdapter.Constants.AsyncTestProperty, this.IsAsync);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set only if some test category is present
|
|
||||||
if (this.TestCategory != null && this.TestCategory.Length > 0)
|
|
||||||
{
|
|
||||||
testCase.SetPropertyValue(TestAdapter.Constants.TestCategoryProperty, this.TestCategory);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set priority if present
|
|
||||||
if (this.Priority != null)
|
|
||||||
{
|
|
||||||
testCase.SetPropertyValue(TestAdapter.Constants.PriorityProperty, this.Priority.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.Traits != null)
|
|
||||||
{
|
|
||||||
testCase.Traits.AddRange(this.Traits);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(this.CssIteration))
|
|
||||||
{
|
|
||||||
testCase.SetPropertyValue(TestAdapter.Constants.CssIterationProperty, this.CssIteration);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(this.CssProjectStructure))
|
|
||||||
{
|
|
||||||
testCase.SetPropertyValue(TestAdapter.Constants.CssProjectStructureProperty, this.CssProjectStructure);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(this.Description))
|
|
||||||
{
|
|
||||||
testCase.SetPropertyValue(TestAdapter.Constants.DescriptionProperty, this.Description);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.WorkItemIds != null)
|
|
||||||
{
|
|
||||||
testCase.SetPropertyValue(TestAdapter.Constants.WorkItemIdsProperty, this.WorkItemIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The list of items to deploy before running this test.
|
|
||||||
if (this.DeploymentItems != null && this.DeploymentItems.Length > 0)
|
|
||||||
{
|
|
||||||
testCase.SetPropertyValue(TestAdapter.Constants.DeploymentItemsProperty, this.DeploymentItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the Do not parallelize state if present
|
|
||||||
if (this.DoNotParallelize)
|
|
||||||
{
|
|
||||||
testCase.SetPropertyValue(TestAdapter.Constants.DoNotParallelizeProperty, this.DoNotParallelize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store resolved data if any
|
|
||||||
if (this.TestMethod.DataType != DynamicDataType.None)
|
|
||||||
{
|
|
||||||
var data = this.TestMethod.SerializedData;
|
|
||||||
|
|
||||||
testCase.SetPropertyValue(TestAdapter.Constants.TestDynamicDataTypeProperty, (int)this.TestMethod.DataType);
|
|
||||||
testCase.SetPropertyValue(TestAdapter.Constants.TestDynamicDataProperty, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
string fileName = testCase.Source;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
fileName = Path.GetFileName(fileName);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
var idProvider = new TestIdProvider();
|
|
||||||
idProvider.AppendString(testCase.ExecutorUri?.ToString());
|
|
||||||
idProvider.AppendString(fileName);
|
|
||||||
if (this.TestMethod.HasManagedMethodAndTypeProperties)
|
|
||||||
{
|
|
||||||
idProvider.AppendString(this.TestMethod.ManagedTypeName);
|
|
||||||
idProvider.AppendString(this.TestMethod.ManagedMethodName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
idProvider.AppendString(testCase.FullyQualifiedName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.TestMethod.DataType != DynamicDataType.None)
|
|
||||||
{
|
|
||||||
idProvider.AppendString(testCase.DisplayName);
|
|
||||||
}
|
|
||||||
|
|
||||||
testCase.Id = idProvider.GetId();
|
|
||||||
|
|
||||||
return testCase;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
private string GetDisplayName()
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(this.DisplayName))
|
return this.DisplayName;
|
||||||
{
|
|
||||||
return this.TestMethod.Name;
|
|
||||||
|
|
||||||
// This causes compatibility problems with older runners.
|
|
||||||
// return string.IsNullOrWhiteSpace(this.TestMethod.ManagedMethodName)
|
|
||||||
// ? this.TestMethod.Name
|
|
||||||
// : this.TestMethod.ManagedMethodName;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return this.DisplayName;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,59 +1,58 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Outcome of a test
|
||||||
|
/// </summary>
|
||||||
|
public enum UnitTestOutcome : int
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Outcome of a test
|
/// There was a system error while we were trying to execute a test.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum UnitTestOutcome : int
|
Error,
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// There was a system error while we were trying to execute a test.
|
|
||||||
/// </summary>
|
|
||||||
Error,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test was executed, but there were issues.
|
/// Test was executed, but there were issues.
|
||||||
/// Issues may involve exceptions or failed assertions.
|
/// Issues may involve exceptions or failed assertions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Failed,
|
Failed,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The test timed out
|
/// The test timed out
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Timeout,
|
Timeout,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test has completed, but we can't say if it passed or failed.
|
/// Test has completed, but we can't say if it passed or failed.
|
||||||
/// (Used in Assert.InConclusive scenario)
|
/// (Used in Assert.InConclusive scenario)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Inconclusive,
|
Inconclusive,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test had it chance for been executed but was not, as Ignore == true.
|
/// Test had it chance for been executed but was not, as Ignore == true.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Ignored,
|
Ignored,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test cannot be executed.
|
/// Test cannot be executed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NotRunnable,
|
NotRunnable,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test was executed w/o any issues.
|
/// Test was executed w/o any issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Passed,
|
Passed,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The specific test cannot be found.
|
/// The specific test cannot be found.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NotFound,
|
NotFound,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When test is handed over to runner for execution, it goes into progress state.
|
/// When test is handed over to runner for execution, it goes into progress state.
|
||||||
/// It is added so that the right status can be set in TestContext.
|
/// It is added so that the right status can be set in TestContext.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
InProgress,
|
InProgress,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,214 +1,213 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
|
||||||
|
using Constants = Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Constants;
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
[DebuggerDisplay("{DisplayName} ({Outcome})")]
|
||||||
|
public class UnitTestResult
|
||||||
{
|
{
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Collections.Generic;
|
/// Initializes a new instance of the <see cref="UnitTestResult"/> class.
|
||||||
using System.Diagnostics;
|
/// </summary>
|
||||||
using System.Globalization;
|
internal UnitTestResult()
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
|
||||||
|
|
||||||
using Constants = Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Constants;
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
[DebuggerDisplay("{DisplayName} ({Outcome})")]
|
|
||||||
public class UnitTestResult
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
this.DatarowIndex = -1;
|
||||||
/// Initializes a new instance of the <see cref="UnitTestResult"/> class.
|
}
|
||||||
/// </summary>
|
|
||||||
internal UnitTestResult()
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="UnitTestResult"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="testFailedException"> The test failed exception. </param>
|
||||||
|
internal UnitTestResult(TestFailedException testFailedException)
|
||||||
|
: this()
|
||||||
|
{
|
||||||
|
this.Outcome = testFailedException.Outcome;
|
||||||
|
this.ErrorMessage = testFailedException.Message;
|
||||||
|
|
||||||
|
if (testFailedException.StackTraceInformation != null)
|
||||||
{
|
{
|
||||||
this.DatarowIndex = -1;
|
this.ErrorStackTrace = testFailedException.StackTraceInformation.ErrorStackTrace;
|
||||||
}
|
this.ErrorLineNumber = testFailedException.StackTraceInformation.ErrorLineNumber;
|
||||||
|
this.ErrorFilePath = testFailedException.StackTraceInformation.ErrorFilePath;
|
||||||
/// <summary>
|
this.ErrorColumnNumber = testFailedException.StackTraceInformation.ErrorColumnNumber;
|
||||||
/// Initializes a new instance of the <see cref="UnitTestResult"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="testFailedException"> The test failed exception. </param>
|
|
||||||
internal UnitTestResult(TestFailedException testFailedException)
|
|
||||||
: this()
|
|
||||||
{
|
|
||||||
this.Outcome = testFailedException.Outcome;
|
|
||||||
this.ErrorMessage = testFailedException.Message;
|
|
||||||
|
|
||||||
if (testFailedException.StackTraceInformation != null)
|
|
||||||
{
|
|
||||||
this.ErrorStackTrace = testFailedException.StackTraceInformation.ErrorStackTrace;
|
|
||||||
this.ErrorLineNumber = testFailedException.StackTraceInformation.ErrorLineNumber;
|
|
||||||
this.ErrorFilePath = testFailedException.StackTraceInformation.ErrorFilePath;
|
|
||||||
this.ErrorColumnNumber = testFailedException.StackTraceInformation.ErrorColumnNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="UnitTestResult"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="outcome"> The outcome. </param>
|
|
||||||
/// <param name="errorMessage"> The error message. </param>
|
|
||||||
internal UnitTestResult(UnitTestOutcome outcome, string errorMessage)
|
|
||||||
: this()
|
|
||||||
{
|
|
||||||
this.Outcome = outcome;
|
|
||||||
this.ErrorMessage = errorMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the display name for the result
|
|
||||||
/// </summary>
|
|
||||||
public string DisplayName { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the outcome of the result
|
|
||||||
/// </summary>
|
|
||||||
public UnitTestOutcome Outcome { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the errorMessage of the result
|
|
||||||
/// </summary>
|
|
||||||
public string ErrorMessage { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the stackTrace of the result
|
|
||||||
/// </summary>
|
|
||||||
public string ErrorStackTrace { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the execution id of the result
|
|
||||||
/// </summary>
|
|
||||||
public Guid ExecutionId { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the parent execution id of the result
|
|
||||||
/// </summary>
|
|
||||||
public Guid ParentExecId { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the inner results count of the result
|
|
||||||
/// </summary>
|
|
||||||
public int InnerResultsCount { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the duration of the result
|
|
||||||
/// </summary>
|
|
||||||
public TimeSpan Duration { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the standard output of the result
|
|
||||||
/// </summary>
|
|
||||||
public string StandardOut { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the Standard Error of the result
|
|
||||||
/// </summary>
|
|
||||||
public string StandardError { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the debug trace of the result
|
|
||||||
/// </summary>
|
|
||||||
public string DebugTrace { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets additional information messages generated by TestContext.WriteLine.
|
|
||||||
/// </summary>
|
|
||||||
public string TestContextMessages { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the source code FilePath where the error was thrown.
|
|
||||||
/// </summary>
|
|
||||||
public string ErrorFilePath { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the line number in the source code file where the error was thrown.
|
|
||||||
/// </summary>
|
|
||||||
public int ErrorLineNumber { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the column number in the source code file where the error was thrown.
|
|
||||||
/// </summary>
|
|
||||||
public int ErrorColumnNumber { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets data row index in data source. Set only for results of individual
|
|
||||||
/// run of data row of a data driven test.
|
|
||||||
/// </summary>
|
|
||||||
public int DatarowIndex { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the result files attached by the test.
|
|
||||||
/// </summary>
|
|
||||||
public IList<string> ResultFiles { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert parameter unitTestResult to testResult
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="testCase"> The test Case. </param>
|
|
||||||
/// <param name="startTime"> The start Time. </param>
|
|
||||||
/// <param name="endTime"> The end Time. </param>
|
|
||||||
/// <param name="currentSettings">Current MSTest settings.</param>
|
|
||||||
/// <returns> The <see cref="TestResult"/>. </returns>
|
|
||||||
internal TestResult ToTestResult(TestCase testCase, DateTimeOffset startTime, DateTimeOffset endTime, MSTestSettings currentSettings)
|
|
||||||
{
|
|
||||||
Debug.Assert(testCase != null, "testCase");
|
|
||||||
|
|
||||||
var testResult = new TestResult(testCase)
|
|
||||||
{
|
|
||||||
DisplayName = this.DisplayName,
|
|
||||||
Duration = this.Duration,
|
|
||||||
ErrorMessage = this.ErrorMessage,
|
|
||||||
ErrorStackTrace = this.ErrorStackTrace,
|
|
||||||
Outcome = UnitTestOutcomeHelper.ToTestOutcome(this.Outcome, currentSettings),
|
|
||||||
StartTime = startTime,
|
|
||||||
EndTime = endTime
|
|
||||||
};
|
|
||||||
|
|
||||||
testResult.SetPropertyValue<Guid>(Constants.ExecutionIdProperty, this.ExecutionId);
|
|
||||||
testResult.SetPropertyValue<Guid>(Constants.ParentExecIdProperty, this.ParentExecId);
|
|
||||||
testResult.SetPropertyValue<int>(Constants.InnerResultsCountProperty, this.InnerResultsCount);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(this.StandardOut))
|
|
||||||
{
|
|
||||||
TestResultMessage message = new(TestResultMessage.StandardOutCategory, this.StandardOut);
|
|
||||||
testResult.Messages.Add(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(this.StandardError))
|
|
||||||
{
|
|
||||||
TestResultMessage message = new(TestResultMessage.StandardErrorCategory, this.StandardError);
|
|
||||||
testResult.Messages.Add(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(this.DebugTrace))
|
|
||||||
{
|
|
||||||
string debugTraceMessagesinStdOut = string.Format(CultureInfo.InvariantCulture, "\n\n{0}\n{1}", Resource.DebugTraceBanner, this.DebugTrace);
|
|
||||||
TestResultMessage debugTraceMessage = new(TestResultMessage.StandardOutCategory, debugTraceMessagesinStdOut);
|
|
||||||
testResult.Messages.Add(debugTraceMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(this.TestContextMessages))
|
|
||||||
{
|
|
||||||
string testContextMessagesInStdOut = string.Format(CultureInfo.InvariantCulture, "\n\n{0}\n{1}", Resource.TestContextMessageBanner, this.TestContextMessages);
|
|
||||||
TestResultMessage testContextMessage = new(TestResultMessage.StandardOutCategory, testContextMessagesInStdOut);
|
|
||||||
testResult.Messages.Add(testContextMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.ResultFiles != null && this.ResultFiles.Count > 0)
|
|
||||||
{
|
|
||||||
AttachmentSet attachmentSet = new(Constants.ExecutorUri, Resource.AttachmentSetDisplayName);
|
|
||||||
foreach (var resultFile in this.ResultFiles)
|
|
||||||
{
|
|
||||||
string pathToResultFile = PlatformServiceProvider.Instance.FileOperations.GetFullFilePath(resultFile);
|
|
||||||
UriDataAttachment attachment = new(new Uri(pathToResultFile), resultFile);
|
|
||||||
attachmentSet.Attachments.Add(attachment);
|
|
||||||
}
|
|
||||||
|
|
||||||
testResult.Attachments.Add(attachmentSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
return testResult;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="UnitTestResult"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outcome"> The outcome. </param>
|
||||||
|
/// <param name="errorMessage"> The error message. </param>
|
||||||
|
internal UnitTestResult(UnitTestOutcome outcome, string errorMessage)
|
||||||
|
: this()
|
||||||
|
{
|
||||||
|
this.Outcome = outcome;
|
||||||
|
this.ErrorMessage = errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the display name for the result
|
||||||
|
/// </summary>
|
||||||
|
public string DisplayName { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the outcome of the result
|
||||||
|
/// </summary>
|
||||||
|
public UnitTestOutcome Outcome { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the errorMessage of the result
|
||||||
|
/// </summary>
|
||||||
|
public string ErrorMessage { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the stackTrace of the result
|
||||||
|
/// </summary>
|
||||||
|
public string ErrorStackTrace { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the execution id of the result
|
||||||
|
/// </summary>
|
||||||
|
public Guid ExecutionId { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the parent execution id of the result
|
||||||
|
/// </summary>
|
||||||
|
public Guid ParentExecId { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the inner results count of the result
|
||||||
|
/// </summary>
|
||||||
|
public int InnerResultsCount { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the duration of the result
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan Duration { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the standard output of the result
|
||||||
|
/// </summary>
|
||||||
|
public string StandardOut { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Standard Error of the result
|
||||||
|
/// </summary>
|
||||||
|
public string StandardError { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the debug trace of the result
|
||||||
|
/// </summary>
|
||||||
|
public string DebugTrace { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets additional information messages generated by TestContext.WriteLine.
|
||||||
|
/// </summary>
|
||||||
|
public string TestContextMessages { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the source code FilePath where the error was thrown.
|
||||||
|
/// </summary>
|
||||||
|
public string ErrorFilePath { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the line number in the source code file where the error was thrown.
|
||||||
|
/// </summary>
|
||||||
|
public int ErrorLineNumber { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the column number in the source code file where the error was thrown.
|
||||||
|
/// </summary>
|
||||||
|
public int ErrorColumnNumber { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets data row index in data source. Set only for results of individual
|
||||||
|
/// run of data row of a data driven test.
|
||||||
|
/// </summary>
|
||||||
|
public int DatarowIndex { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the result files attached by the test.
|
||||||
|
/// </summary>
|
||||||
|
public IList<string> ResultFiles { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert parameter unitTestResult to testResult
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="testCase"> The test Case. </param>
|
||||||
|
/// <param name="startTime"> The start Time. </param>
|
||||||
|
/// <param name="endTime"> The end Time. </param>
|
||||||
|
/// <param name="currentSettings">Current MSTest settings.</param>
|
||||||
|
/// <returns> The <see cref="TestResult"/>. </returns>
|
||||||
|
internal TestResult ToTestResult(TestCase testCase, DateTimeOffset startTime, DateTimeOffset endTime, MSTestSettings currentSettings)
|
||||||
|
{
|
||||||
|
Debug.Assert(testCase != null, "testCase");
|
||||||
|
|
||||||
|
var testResult = new TestResult(testCase)
|
||||||
|
{
|
||||||
|
DisplayName = this.DisplayName,
|
||||||
|
Duration = this.Duration,
|
||||||
|
ErrorMessage = this.ErrorMessage,
|
||||||
|
ErrorStackTrace = this.ErrorStackTrace,
|
||||||
|
Outcome = UnitTestOutcomeHelper.ToTestOutcome(this.Outcome, currentSettings),
|
||||||
|
StartTime = startTime,
|
||||||
|
EndTime = endTime
|
||||||
|
};
|
||||||
|
|
||||||
|
testResult.SetPropertyValue<Guid>(Constants.ExecutionIdProperty, this.ExecutionId);
|
||||||
|
testResult.SetPropertyValue<Guid>(Constants.ParentExecIdProperty, this.ParentExecId);
|
||||||
|
testResult.SetPropertyValue<int>(Constants.InnerResultsCountProperty, this.InnerResultsCount);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(this.StandardOut))
|
||||||
|
{
|
||||||
|
TestResultMessage message = new(TestResultMessage.StandardOutCategory, this.StandardOut);
|
||||||
|
testResult.Messages.Add(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(this.StandardError))
|
||||||
|
{
|
||||||
|
TestResultMessage message = new(TestResultMessage.StandardErrorCategory, this.StandardError);
|
||||||
|
testResult.Messages.Add(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(this.DebugTrace))
|
||||||
|
{
|
||||||
|
string debugTraceMessagesinStdOut = string.Format(CultureInfo.InvariantCulture, "\n\n{0}\n{1}", Resource.DebugTraceBanner, this.DebugTrace);
|
||||||
|
TestResultMessage debugTraceMessage = new(TestResultMessage.StandardOutCategory, debugTraceMessagesinStdOut);
|
||||||
|
testResult.Messages.Add(debugTraceMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(this.TestContextMessages))
|
||||||
|
{
|
||||||
|
string testContextMessagesInStdOut = string.Format(CultureInfo.InvariantCulture, "\n\n{0}\n{1}", Resource.TestContextMessageBanner, this.TestContextMessages);
|
||||||
|
TestResultMessage testContextMessage = new(TestResultMessage.StandardOutCategory, testContextMessagesInStdOut);
|
||||||
|
testResult.Messages.Add(testContextMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.ResultFiles != null && this.ResultFiles.Count > 0)
|
||||||
|
{
|
||||||
|
AttachmentSet attachmentSet = new(Constants.ExecutorUri, Resource.AttachmentSetDisplayName);
|
||||||
|
foreach (var resultFile in this.ResultFiles)
|
||||||
|
{
|
||||||
|
string pathToResultFile = PlatformServiceProvider.Instance.FileOperations.GetFullFilePath(resultFile);
|
||||||
|
UriDataAttachment attachment = new(new Uri(pathToResultFile), resultFile);
|
||||||
|
attachmentSet.Attachments.Add(attachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
testResult.Attachments.Add(attachmentSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
return testResult;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,221 +1,220 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter;
|
||||||
{
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
using System.Collections.Generic;
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
using System.IO;
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.ObjectModel;
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.ObjectModel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The main service provider class that exposes all the platform services available.
|
||||||
|
/// </summary>
|
||||||
|
internal class PlatformServiceProvider : IPlatformServiceProvider
|
||||||
|
{
|
||||||
|
private static IPlatformServiceProvider instance;
|
||||||
|
private ITestSource testSource;
|
||||||
|
private IFileOperations fileOperations;
|
||||||
|
private IAdapterTraceLogger traceLogger;
|
||||||
|
private ITestDeployment testDeployment;
|
||||||
|
private ISettingsProvider settingsProvider;
|
||||||
|
private ITestDataSource testDataSource;
|
||||||
|
private IThreadOperations threadOperations;
|
||||||
|
private IReflectionOperations reflectionOperations;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The main service provider class that exposes all the platform services available.
|
/// Initializes a new instance of the <see cref="PlatformServiceProvider"/> class - a singleton.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class PlatformServiceProvider : IPlatformServiceProvider
|
private PlatformServiceProvider()
|
||||||
{
|
{
|
||||||
private static IPlatformServiceProvider instance;
|
}
|
||||||
private ITestSource testSource;
|
|
||||||
private IFileOperations fileOperations;
|
|
||||||
private IAdapterTraceLogger traceLogger;
|
|
||||||
private ITestDeployment testDeployment;
|
|
||||||
private ISettingsProvider settingsProvider;
|
|
||||||
private ITestDataSource testDataSource;
|
|
||||||
private IThreadOperations threadOperations;
|
|
||||||
private IReflectionOperations reflectionOperations;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="PlatformServiceProvider"/> class - a singleton.
|
/// Gets an instance to the platform service validator for test sources.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private PlatformServiceProvider()
|
public ITestSource TestSource
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
}
|
return this.testSource ??= new TestSource();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an instance to the platform service validator for test sources.
|
|
||||||
/// </summary>
|
|
||||||
public ITestSource TestSource
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.testSource ??= new TestSource();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an instance to the platform service validator for data sources for tests.
|
|
||||||
/// </summary>
|
|
||||||
public ITestDataSource TestDataSource
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.testDataSource ??= new TestDataSource();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an instance to the platform service for file operations.
|
|
||||||
/// </summary>
|
|
||||||
public IFileOperations FileOperations
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.fileOperations ??= new FileOperations();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an instance to the platform service for trace logging.
|
|
||||||
/// </summary>
|
|
||||||
public IAdapterTraceLogger AdapterTraceLogger
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.traceLogger ??= new AdapterTraceLogger();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an instance of the test deployment service.
|
|
||||||
/// </summary>
|
|
||||||
public ITestDeployment TestDeployment
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.testDeployment ??= new TestDeployment();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an instance to the platform service for a Settings Provider.
|
|
||||||
/// </summary>
|
|
||||||
public ISettingsProvider SettingsProvider
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.settingsProvider ??= new MSTestSettingsProvider();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an instance to the platform service for thread operations.
|
|
||||||
/// </summary>
|
|
||||||
public IThreadOperations ThreadOperations
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.threadOperations ??= new ThreadOperations();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an instance to the platform service for reflection operations specific to a platform.
|
|
||||||
/// </summary>
|
|
||||||
public IReflectionOperations ReflectionOperations
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.reflectionOperations ??= new ReflectionOperations();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the instance for the platform service.
|
|
||||||
/// </summary>
|
|
||||||
internal static IPlatformServiceProvider Instance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return instance ??= new PlatformServiceProvider();
|
|
||||||
}
|
|
||||||
|
|
||||||
set
|
|
||||||
{
|
|
||||||
instance = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates an instance to the platform service for a test source host.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">
|
|
||||||
/// The source.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="runSettings">
|
|
||||||
/// The run Settings for the session.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="frameworkHandle">
|
|
||||||
/// The handle to the test platform.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// Returns the host for the source provided.
|
|
||||||
/// </returns>
|
|
||||||
public ITestSourceHost CreateTestSourceHost(
|
|
||||||
string source,
|
|
||||||
TestPlatform.ObjectModel.Adapter.IRunSettings runSettings,
|
|
||||||
TestPlatform.ObjectModel.Adapter.IFrameworkHandle frameworkHandle)
|
|
||||||
{
|
|
||||||
var testSourceHost = new TestSourceHost(source, runSettings, frameworkHandle);
|
|
||||||
testSourceHost.SetupHost();
|
|
||||||
|
|
||||||
return testSourceHost;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an instance to the platform service listener who monitors trace and debug output
|
|
||||||
/// on provided text writer.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="textWriter">
|
|
||||||
/// The text Writer.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// The <see cref="ITraceListener"/>.
|
|
||||||
/// </returns>
|
|
||||||
public ITraceListener GetTraceListener(TextWriter textWriter)
|
|
||||||
{
|
|
||||||
return new TraceListenerWrapper(textWriter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an instance to the platform service trace-listener manager which updates the output/error streams
|
|
||||||
/// with redirected streams and performs operations on the listener provided as argument.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="outputWriter">
|
|
||||||
/// The redirected output stream writer.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="errorWriter">
|
|
||||||
/// The redirected error stream writer.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// The manager for trace listeners.
|
|
||||||
/// </returns>
|
|
||||||
public ITraceListenerManager GetTraceListenerManager(TextWriter outputWriter, TextWriter errorWriter)
|
|
||||||
{
|
|
||||||
return new TraceListenerManager(outputWriter, errorWriter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the TestContext object for a platform.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="testMethod">
|
|
||||||
/// The test method.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="writer">
|
|
||||||
/// The writer instance for logging.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="properties">
|
|
||||||
/// The default set of properties the test context needs to be filled with.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// The <see cref="ITestContext"/> instance.
|
|
||||||
/// </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// This was required for compatibility reasons since the TestContext object that the V1 adapter had for desktop is not .Net Core compliant.
|
|
||||||
/// </remarks>
|
|
||||||
public ITestContext GetTestContext(ITestMethod testMethod, StringWriter writer, IDictionary<string, object> properties)
|
|
||||||
{
|
|
||||||
return new TestContextImplementation(testMethod, writer, properties);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instance to the platform service validator for data sources for tests.
|
||||||
|
/// </summary>
|
||||||
|
public ITestDataSource TestDataSource
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.testDataSource ??= new TestDataSource();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instance to the platform service for file operations.
|
||||||
|
/// </summary>
|
||||||
|
public IFileOperations FileOperations
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.fileOperations ??= new FileOperations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instance to the platform service for trace logging.
|
||||||
|
/// </summary>
|
||||||
|
public IAdapterTraceLogger AdapterTraceLogger
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.traceLogger ??= new AdapterTraceLogger();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instance of the test deployment service.
|
||||||
|
/// </summary>
|
||||||
|
public ITestDeployment TestDeployment
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.testDeployment ??= new TestDeployment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instance to the platform service for a Settings Provider.
|
||||||
|
/// </summary>
|
||||||
|
public ISettingsProvider SettingsProvider
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.settingsProvider ??= new MSTestSettingsProvider();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instance to the platform service for thread operations.
|
||||||
|
/// </summary>
|
||||||
|
public IThreadOperations ThreadOperations
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.threadOperations ??= new ThreadOperations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instance to the platform service for reflection operations specific to a platform.
|
||||||
|
/// </summary>
|
||||||
|
public IReflectionOperations ReflectionOperations
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.reflectionOperations ??= new ReflectionOperations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the instance for the platform service.
|
||||||
|
/// </summary>
|
||||||
|
internal static IPlatformServiceProvider Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return instance ??= new PlatformServiceProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
instance = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an instance to the platform service for a test source host.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">
|
||||||
|
/// The source.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="runSettings">
|
||||||
|
/// The run Settings for the session.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="frameworkHandle">
|
||||||
|
/// The handle to the test platform.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Returns the host for the source provided.
|
||||||
|
/// </returns>
|
||||||
|
public ITestSourceHost CreateTestSourceHost(
|
||||||
|
string source,
|
||||||
|
TestPlatform.ObjectModel.Adapter.IRunSettings runSettings,
|
||||||
|
TestPlatform.ObjectModel.Adapter.IFrameworkHandle frameworkHandle)
|
||||||
|
{
|
||||||
|
var testSourceHost = new TestSourceHost(source, runSettings, frameworkHandle);
|
||||||
|
testSourceHost.SetupHost();
|
||||||
|
|
||||||
|
return testSourceHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instance to the platform service listener who monitors trace and debug output
|
||||||
|
/// on provided text writer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="textWriter">
|
||||||
|
/// The text Writer.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The <see cref="ITraceListener"/>.
|
||||||
|
/// </returns>
|
||||||
|
public ITraceListener GetTraceListener(TextWriter textWriter)
|
||||||
|
{
|
||||||
|
return new TraceListenerWrapper(textWriter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instance to the platform service trace-listener manager which updates the output/error streams
|
||||||
|
/// with redirected streams and performs operations on the listener provided as argument.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outputWriter">
|
||||||
|
/// The redirected output stream writer.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="errorWriter">
|
||||||
|
/// The redirected error stream writer.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The manager for trace listeners.
|
||||||
|
/// </returns>
|
||||||
|
public ITraceListenerManager GetTraceListenerManager(TextWriter outputWriter, TextWriter errorWriter)
|
||||||
|
{
|
||||||
|
return new TraceListenerManager(outputWriter, errorWriter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the TestContext object for a platform.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="testMethod">
|
||||||
|
/// The test method.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="writer">
|
||||||
|
/// The writer instance for logging.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="properties">
|
||||||
|
/// The default set of properties the test context needs to be filled with.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The <see cref="ITestContext"/> instance.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// This was required for compatibility reasons since the TestContext object that the V1 adapter had for desktop is not .Net Core compliant.
|
||||||
|
/// </remarks>
|
||||||
|
public ITestContext GetTestContext(ITestMethod testMethod, StringWriter writer, IDictionary<string, object> properties)
|
||||||
|
{
|
||||||
|
return new TestContextImplementation(testMethod, writer, properties);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,20 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform
|
namespace Microsoft.VisualStudio.TestPlatform;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Custom Attribute to specify the exact types which should be loaded from assembly
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)]
|
||||||
|
internal sealed class TestExtensionTypesAttribute : Attribute
|
||||||
{
|
{
|
||||||
using System;
|
public TestExtensionTypesAttribute(params Type[] types)
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Custom Attribute to specify the exact types which should be loaded from assembly
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)]
|
|
||||||
internal sealed class TestExtensionTypesAttribute : Attribute
|
|
||||||
{
|
{
|
||||||
public TestExtensionTypesAttribute(params Type[] types)
|
this.Types = types;
|
||||||
{
|
|
||||||
this.Types = types;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Type[] Types { get; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Type[] Types { get; }
|
||||||
}
|
}
|
|
@ -1,148 +1,147 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;
|
||||||
|
|
||||||
|
public class RunConfigurationSettings
|
||||||
{
|
{
|
||||||
using System;
|
/// <summary>
|
||||||
using System.IO;
|
/// The settings name.
|
||||||
using System.Xml;
|
/// </summary>
|
||||||
|
public const string SettingsName = "RunConfiguration";
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
/// <summary>
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
|
/// Initializes a new instance of the <see cref="RunConfigurationSettings"/> class.
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;
|
/// </summary>
|
||||||
|
public RunConfigurationSettings()
|
||||||
public class RunConfigurationSettings
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
this.CollectSourceInformation = true;
|
||||||
/// The settings name.
|
}
|
||||||
/// </summary>
|
|
||||||
public const string SettingsName = "RunConfiguration";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="RunConfigurationSettings"/> class.
|
/// Gets a value indicating whether source information needs to be collected or not.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RunConfigurationSettings()
|
public bool CollectSourceInformation { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Populate adapter settings from the context
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">
|
||||||
|
/// The discovery context that contains the runsettings.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>Populated RunConfigurationSettings from the discovery context.</returns>
|
||||||
|
public static RunConfigurationSettings PopulateSettings(IDiscoveryContext context)
|
||||||
|
{
|
||||||
|
if (context == null || context.RunSettings == null || string.IsNullOrEmpty(context.RunSettings.SettingsXml))
|
||||||
{
|
{
|
||||||
this.CollectSourceInformation = true;
|
// This will contain default configuration settings
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether source information needs to be collected or not.
|
|
||||||
/// </summary>
|
|
||||||
public bool CollectSourceInformation { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Populate adapter settings from the context
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">
|
|
||||||
/// The discovery context that contains the runsettings.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>Populated RunConfigurationSettings from the discovery context.</returns>
|
|
||||||
public static RunConfigurationSettings PopulateSettings(IDiscoveryContext context)
|
|
||||||
{
|
|
||||||
if (context == null || context.RunSettings == null || string.IsNullOrEmpty(context.RunSettings.SettingsXml))
|
|
||||||
{
|
|
||||||
// This will contain default configuration settings
|
|
||||||
return new RunConfigurationSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
var settings = GetSettings(context.RunSettings.SettingsXml, SettingsName);
|
|
||||||
|
|
||||||
if (settings != null)
|
|
||||||
{
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new RunConfigurationSettings();
|
return new RunConfigurationSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
var settings = GetSettings(context.RunSettings.SettingsXml, SettingsName);
|
||||||
/// Gets the configuration settings from the xml.
|
|
||||||
/// </summary>
|
if (settings != null)
|
||||||
/// <param name="runsettingsXml"> The xml with the settings passed from the test platform. </param>
|
|
||||||
/// <param name="settingName"> The name of the settings to fetch.</param>
|
|
||||||
/// <returns> The settings if found. Null otherwise. </returns>
|
|
||||||
internal static RunConfigurationSettings GetSettings(string runsettingsXml, string settingName)
|
|
||||||
{
|
{
|
||||||
using (var stringReader = new StringReader(runsettingsXml))
|
|
||||||
{
|
|
||||||
XmlReader reader = XmlReader.Create(stringReader, XmlRunSettingsUtilities.ReaderSettings);
|
|
||||||
|
|
||||||
// read to the fist child
|
|
||||||
XmlReaderUtilities.ReadToRootNode(reader);
|
|
||||||
reader.ReadToNextElement();
|
|
||||||
|
|
||||||
// Read till we reach nodeName element or reach EOF
|
|
||||||
while (!string.Equals(reader.Name, settingName, StringComparison.OrdinalIgnoreCase)
|
|
||||||
&&
|
|
||||||
!reader.EOF)
|
|
||||||
{
|
|
||||||
reader.SkipToNextElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!reader.EOF)
|
|
||||||
{
|
|
||||||
// read nodeName element.
|
|
||||||
return ToSettings(reader.ReadSubtree());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert the parameter xml to TestSettings
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="reader">Reader to load the settings from.</param>
|
|
||||||
/// <returns>An instance of the <see cref="MSTestSettings"/> class</returns>
|
|
||||||
private static RunConfigurationSettings ToSettings(XmlReader reader)
|
|
||||||
{
|
|
||||||
ValidateArg.NotNull<XmlReader>(reader, "reader");
|
|
||||||
|
|
||||||
// Expected format of the xml is: -
|
|
||||||
//
|
|
||||||
// <Runsettings>
|
|
||||||
// <RunConfiguration>
|
|
||||||
// <CollectSourceInformation>true</CollectSourceInformation>
|
|
||||||
// </RunConfiguration>
|
|
||||||
// </Runsettings>
|
|
||||||
RunConfigurationSettings settings = new();
|
|
||||||
|
|
||||||
// Read the first element in the section
|
|
||||||
reader.ReadToNextElement();
|
|
||||||
|
|
||||||
if (!reader.IsEmptyElement)
|
|
||||||
{
|
|
||||||
reader.Read();
|
|
||||||
|
|
||||||
while (reader.NodeType == XmlNodeType.Element)
|
|
||||||
{
|
|
||||||
string elementName = reader.Name.ToUpperInvariant();
|
|
||||||
switch (elementName)
|
|
||||||
{
|
|
||||||
case "COLLECTSOURCEINFORMATION":
|
|
||||||
{
|
|
||||||
if (bool.TryParse(reader.ReadInnerXml(), out var result))
|
|
||||||
{
|
|
||||||
settings.CollectSourceInformation = result;
|
|
||||||
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo(
|
|
||||||
"CollectSourceInformation value Found : {0} ",
|
|
||||||
result);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
reader.SkipToNextElement();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new RunConfigurationSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the configuration settings from the xml.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="runsettingsXml"> The xml with the settings passed from the test platform. </param>
|
||||||
|
/// <param name="settingName"> The name of the settings to fetch.</param>
|
||||||
|
/// <returns> The settings if found. Null otherwise. </returns>
|
||||||
|
internal static RunConfigurationSettings GetSettings(string runsettingsXml, string settingName)
|
||||||
|
{
|
||||||
|
using (var stringReader = new StringReader(runsettingsXml))
|
||||||
|
{
|
||||||
|
XmlReader reader = XmlReader.Create(stringReader, XmlRunSettingsUtilities.ReaderSettings);
|
||||||
|
|
||||||
|
// read to the fist child
|
||||||
|
XmlReaderUtilities.ReadToRootNode(reader);
|
||||||
|
reader.ReadToNextElement();
|
||||||
|
|
||||||
|
// Read till we reach nodeName element or reach EOF
|
||||||
|
while (!string.Equals(reader.Name, settingName, StringComparison.OrdinalIgnoreCase)
|
||||||
|
&&
|
||||||
|
!reader.EOF)
|
||||||
|
{
|
||||||
|
reader.SkipToNextElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!reader.EOF)
|
||||||
|
{
|
||||||
|
// read nodeName element.
|
||||||
|
return ToSettings(reader.ReadSubtree());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert the parameter xml to TestSettings
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reader">Reader to load the settings from.</param>
|
||||||
|
/// <returns>An instance of the <see cref="MSTestSettings"/> class</returns>
|
||||||
|
private static RunConfigurationSettings ToSettings(XmlReader reader)
|
||||||
|
{
|
||||||
|
ValidateArg.NotNull<XmlReader>(reader, "reader");
|
||||||
|
|
||||||
|
// Expected format of the xml is: -
|
||||||
|
//
|
||||||
|
// <Runsettings>
|
||||||
|
// <RunConfiguration>
|
||||||
|
// <CollectSourceInformation>true</CollectSourceInformation>
|
||||||
|
// </RunConfiguration>
|
||||||
|
// </Runsettings>
|
||||||
|
RunConfigurationSettings settings = new();
|
||||||
|
|
||||||
|
// Read the first element in the section
|
||||||
|
reader.ReadToNextElement();
|
||||||
|
|
||||||
|
if (!reader.IsEmptyElement)
|
||||||
|
{
|
||||||
|
reader.Read();
|
||||||
|
|
||||||
|
while (reader.NodeType == XmlNodeType.Element)
|
||||||
|
{
|
||||||
|
string elementName = reader.Name.ToUpperInvariant();
|
||||||
|
switch (elementName)
|
||||||
|
{
|
||||||
|
case "COLLECTSOURCEINFORMATION":
|
||||||
|
{
|
||||||
|
if (bool.TryParse(reader.ReadInnerXml(), out var result))
|
||||||
|
{
|
||||||
|
settings.CollectSourceInformation = result;
|
||||||
|
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo(
|
||||||
|
"CollectSourceInformation value Found : {0} ",
|
||||||
|
result);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
reader.SkipToNextElement();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,136 +1,135 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter
|
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
|
||||||
|
|
||||||
|
internal class TestMethodFilter
|
||||||
{
|
{
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Collections.Generic;
|
/// Supported properties for filtering
|
||||||
using System.Diagnostics;
|
/// </summary>
|
||||||
using System.Linq;
|
private readonly Dictionary<string, TestProperty> supportedProperties;
|
||||||
using System.Reflection;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
|
|
||||||
|
|
||||||
internal class TestMethodFilter
|
internal TestMethodFilter()
|
||||||
{
|
{
|
||||||
/// <summary>
|
this.supportedProperties = new Dictionary<string, TestProperty>(StringComparer.OrdinalIgnoreCase)
|
||||||
/// Supported properties for filtering
|
|
||||||
/// </summary>
|
|
||||||
private readonly Dictionary<string, TestProperty> supportedProperties;
|
|
||||||
|
|
||||||
internal TestMethodFilter()
|
|
||||||
{
|
{
|
||||||
this.supportedProperties = new Dictionary<string, TestProperty>(StringComparer.OrdinalIgnoreCase)
|
[Constants.TestCategoryProperty.Label] = Constants.TestCategoryProperty,
|
||||||
{
|
[Constants.PriorityProperty.Label] = Constants.PriorityProperty,
|
||||||
[Constants.TestCategoryProperty.Label] = Constants.TestCategoryProperty,
|
[TestCaseProperties.FullyQualifiedName.Label] = TestCaseProperties.FullyQualifiedName,
|
||||||
[Constants.PriorityProperty.Label] = Constants.PriorityProperty,
|
[TestCaseProperties.DisplayName.Label] = TestCaseProperties.DisplayName,
|
||||||
[TestCaseProperties.FullyQualifiedName.Label] = TestCaseProperties.FullyQualifiedName,
|
[Constants.TestClassNameProperty.Label] = Constants.TestClassNameProperty
|
||||||
[TestCaseProperties.DisplayName.Label] = TestCaseProperties.DisplayName,
|
};
|
||||||
[Constants.TestClassNameProperty.Label] = Constants.TestClassNameProperty
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns ITestCaseFilterExpression for TestProperties supported by adapter.
|
/// Returns ITestCaseFilterExpression for TestProperties supported by adapter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">The current context of the run.</param>
|
/// <param name="context">The current context of the run.</param>
|
||||||
/// <param name="logger">Handler to report test messages/start/end and results.</param>
|
/// <param name="logger">Handler to report test messages/start/end and results.</param>
|
||||||
/// <param name="filterHasError">Indicates that the filter is unsupported/has an error.</param>
|
/// <param name="filterHasError">Indicates that the filter is unsupported/has an error.</param>
|
||||||
/// <returns>A filter expression.</returns>
|
/// <returns>A filter expression.</returns>
|
||||||
internal ITestCaseFilterExpression GetFilterExpression(IDiscoveryContext context, IMessageLogger logger, out bool filterHasError)
|
internal ITestCaseFilterExpression GetFilterExpression(IDiscoveryContext context, IMessageLogger logger, out bool filterHasError)
|
||||||
{
|
{
|
||||||
filterHasError = false;
|
filterHasError = false;
|
||||||
ITestCaseFilterExpression filter = null;
|
ITestCaseFilterExpression filter = null;
|
||||||
if (context != null)
|
if (context != null)
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
filter = (context is IRunContext) ? this.GetTestCaseFilterFromRunContext(context as IRunContext) : this.GetTestCaseFilterFromDiscoveryContext(context, logger);
|
|
||||||
}
|
|
||||||
catch (TestPlatformFormatException ex)
|
|
||||||
{
|
|
||||||
filterHasError = true;
|
|
||||||
logger.SendMessage(TestMessageLevel.Error, ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides TestProperty for property name 'propertyName' as used in filter.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="propertyName">The property name.</param>
|
|
||||||
/// <returns>a TestProperty instance.</returns>
|
|
||||||
internal TestProperty PropertyProvider(string propertyName)
|
|
||||||
{
|
|
||||||
this.supportedProperties.TryGetValue(propertyName, out var testProperty);
|
|
||||||
Debug.Assert(testProperty != null, "Invalid property queried");
|
|
||||||
return testProperty;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides value of TestProperty corresponding to property name 'propertyName' as used in filter.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="currentTest">The current test case.</param>
|
|
||||||
/// <param name="propertyName">Property name.</param>
|
|
||||||
/// <returns>The property value.</returns>
|
|
||||||
internal object PropertyValueProvider(TestCase currentTest, string propertyName)
|
|
||||||
{
|
|
||||||
if (currentTest != null && propertyName != null)
|
|
||||||
{
|
|
||||||
if (this.supportedProperties.TryGetValue(propertyName, out var testProperty))
|
|
||||||
{
|
|
||||||
// Test case might not have defined this property. In that case GetPropertyValue()
|
|
||||||
// would return default value. For filtering, if property is not defined return null.
|
|
||||||
if (currentTest.Properties.Contains(testProperty))
|
|
||||||
{
|
|
||||||
return currentTest.GetPropertyValue(testProperty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets filter expression from run context.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">Run context</param>
|
|
||||||
/// <returns>Filter expression.</returns>
|
|
||||||
private ITestCaseFilterExpression GetTestCaseFilterFromRunContext(IRunContext context)
|
|
||||||
{
|
|
||||||
return context.GetTestCaseFilter(this.supportedProperties.Keys, this.PropertyProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets filter expression from discovery context.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">Discovery context</param>
|
|
||||||
/// <param name="logger">The logger to log exception messages too.</param>
|
|
||||||
/// <returns>Filter expression.</returns>
|
|
||||||
private ITestCaseFilterExpression GetTestCaseFilterFromDiscoveryContext(IDiscoveryContext context, IMessageLogger logger)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// GetTestCaseFilter is present in DiscoveryContext but not in IDiscoveryContext interface.
|
filter = (context is IRunContext) ? this.GetTestCaseFilterFromRunContext(context as IRunContext) : this.GetTestCaseFilterFromDiscoveryContext(context, logger);
|
||||||
MethodInfo methodGetTestCaseFilter = context.GetType().GetRuntimeMethod("GetTestCaseFilter", new[] { typeof(IEnumerable<string>), typeof(Func<string, TestProperty>) });
|
|
||||||
return (ITestCaseFilterExpression)methodGetTestCaseFilter?.Invoke(context, new object[] { this.supportedProperties.Keys, (Func<string, TestProperty>)this.PropertyProvider });
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (TestPlatformFormatException ex)
|
||||||
{
|
{
|
||||||
// In case of UWP .Net Native Tool Chain compilation. Invoking methods via Reflection doesn't work, hence discovery always fails.
|
filterHasError = true;
|
||||||
// Hence throwing exception only if it is of type TargetInvocationException(i.e. Method got invoked but something went wrong in GetTestCaseFilter Method)
|
logger.SendMessage(TestMessageLevel.Error, ex.Message);
|
||||||
if (ex is TargetInvocationException)
|
}
|
||||||
{
|
}
|
||||||
throw ex.InnerException;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.SendMessage(TestMessageLevel.Warning, ex.Message);
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides TestProperty for property name 'propertyName' as used in filter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="propertyName">The property name.</param>
|
||||||
|
/// <returns>a TestProperty instance.</returns>
|
||||||
|
internal TestProperty PropertyProvider(string propertyName)
|
||||||
|
{
|
||||||
|
this.supportedProperties.TryGetValue(propertyName, out var testProperty);
|
||||||
|
Debug.Assert(testProperty != null, "Invalid property queried");
|
||||||
|
return testProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides value of TestProperty corresponding to property name 'propertyName' as used in filter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="currentTest">The current test case.</param>
|
||||||
|
/// <param name="propertyName">Property name.</param>
|
||||||
|
/// <returns>The property value.</returns>
|
||||||
|
internal object PropertyValueProvider(TestCase currentTest, string propertyName)
|
||||||
|
{
|
||||||
|
if (currentTest != null && propertyName != null)
|
||||||
|
{
|
||||||
|
if (this.supportedProperties.TryGetValue(propertyName, out var testProperty))
|
||||||
|
{
|
||||||
|
// Test case might not have defined this property. In that case GetPropertyValue()
|
||||||
|
// would return default value. For filtering, if property is not defined return null.
|
||||||
|
if (currentTest.Properties.Contains(testProperty))
|
||||||
|
{
|
||||||
|
return currentTest.GetPropertyValue(testProperty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets filter expression from run context.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">Run context</param>
|
||||||
|
/// <returns>Filter expression.</returns>
|
||||||
|
private ITestCaseFilterExpression GetTestCaseFilterFromRunContext(IRunContext context)
|
||||||
|
{
|
||||||
|
return context.GetTestCaseFilter(this.supportedProperties.Keys, this.PropertyProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets filter expression from discovery context.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">Discovery context</param>
|
||||||
|
/// <param name="logger">The logger to log exception messages too.</param>
|
||||||
|
/// <returns>Filter expression.</returns>
|
||||||
|
private ITestCaseFilterExpression GetTestCaseFilterFromDiscoveryContext(IDiscoveryContext context, IMessageLogger logger)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// GetTestCaseFilter is present in DiscoveryContext but not in IDiscoveryContext interface.
|
||||||
|
MethodInfo methodGetTestCaseFilter = context.GetType().GetRuntimeMethod("GetTestCaseFilter", new[] { typeof(IEnumerable<string>), typeof(Func<string, TestProperty>) });
|
||||||
|
return (ITestCaseFilterExpression)methodGetTestCaseFilter?.Invoke(context, new object[] { this.supportedProperties.Keys, (Func<string, TestProperty>)this.PropertyProvider });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// In case of UWP .Net Native Tool Chain compilation. Invoking methods via Reflection doesn't work, hence discovery always fails.
|
||||||
|
// Hence throwing exception only if it is of type TargetInvocationException(i.e. Method got invoked but something went wrong in GetTestCaseFilter Method)
|
||||||
|
if (ex is TargetInvocationException)
|
||||||
|
{
|
||||||
|
throw ex.InnerException;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
logger.SendMessage(TestMessageLevel.Warning, ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,44 +1,43 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
|
||||||
|
internal class Constants
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
/// <summary>
|
||||||
|
/// Constants for detecting .net framework.
|
||||||
|
/// </summary>
|
||||||
|
public const string TargetFrameworkAttributeFullName = "System.Runtime.Versioning.TargetFrameworkAttribute";
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
public const string DotNetFrameWorkStringPrefix = ".NETFramework,Version=";
|
||||||
|
|
||||||
internal class Constants
|
public const string TargetFrameworkName = "TargetFrameworkName";
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Constants for detecting .net framework.
|
|
||||||
/// </summary>
|
|
||||||
public const string TargetFrameworkAttributeFullName = "System.Runtime.Versioning.TargetFrameworkAttribute";
|
|
||||||
|
|
||||||
public const string DotNetFrameWorkStringPrefix = ".NETFramework,Version=";
|
/// <summary>
|
||||||
|
/// Constants for MSTest in Portable Mode
|
||||||
|
/// </summary>
|
||||||
|
public const string PortableVsTestLocation = "PortableVsTestLocation";
|
||||||
|
|
||||||
public const string TargetFrameworkName = "TargetFrameworkName";
|
public const string PublicAssemblies = "PublicAssemblies";
|
||||||
|
|
||||||
/// <summary>
|
public const string PrivateAssemblies = "PrivateAssemblies";
|
||||||
/// Constants for MSTest in Portable Mode
|
|
||||||
/// </summary>
|
|
||||||
public const string PortableVsTestLocation = "PortableVsTestLocation";
|
|
||||||
|
|
||||||
public const string PublicAssemblies = "PublicAssemblies";
|
public static readonly TestProperty DeploymentItemsProperty = TestProperty.Register("MSTestDiscoverer.DeploymentItems", DeploymentItemsLabel, typeof(KeyValuePair<string, string>[]), TestPropertyAttributes.Hidden, typeof(TestCase));
|
||||||
|
|
||||||
public const string PrivateAssemblies = "PrivateAssemblies";
|
internal const string DllExtension = ".dll";
|
||||||
|
internal const string ExeExtension = ".exe";
|
||||||
|
internal const string PhoneAppxPackageExtension = ".appx";
|
||||||
|
|
||||||
public static readonly TestProperty DeploymentItemsProperty = TestProperty.Register("MSTestDiscoverer.DeploymentItems", DeploymentItemsLabel, typeof(KeyValuePair<string, string>[]), TestPropertyAttributes.Hidden, typeof(TestCase));
|
// These are tied to a specific VS version. Can be changed to have a list of supported version instead.
|
||||||
|
internal const string VisualStudioRootRegKey32ForDev14 = @"SOFTWARE\Microsoft\VisualStudio\" + VisualStudioVersion;
|
||||||
|
internal const string VisualStudioRootRegKey64ForDev14 = @"SOFTWARE\Wow6432Node\Microsoft\VisualStudio\" + VisualStudioVersion;
|
||||||
|
|
||||||
internal const string DllExtension = ".dll";
|
internal const string VisualStudioVersion = "14.0";
|
||||||
internal const string ExeExtension = ".exe";
|
|
||||||
internal const string PhoneAppxPackageExtension = ".appx";
|
|
||||||
|
|
||||||
// These are tied to a specific VS version. Can be changed to have a list of supported version instead.
|
private const string DeploymentItemsLabel = "DeploymentItems";
|
||||||
internal const string VisualStudioRootRegKey32ForDev14 = @"SOFTWARE\Microsoft\VisualStudio\" + VisualStudioVersion;
|
|
||||||
internal const string VisualStudioRootRegKey64ForDev14 = @"SOFTWARE\Wow6432Node\Microsoft\VisualStudio\" + VisualStudioVersion;
|
|
||||||
|
|
||||||
internal const string VisualStudioVersion = "14.0";
|
|
||||||
|
|
||||||
private const string DeploymentItemsLabel = "DeploymentItems";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,168 +1,167 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Data
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Data;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Data.OleDb;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Utility classes to access databases, and to handle quoted strings etc for comma separated value files.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class CsvDataConnection : TestDataConnection
|
||||||
{
|
{
|
||||||
using System;
|
// Template used to map from a filename to a DB connection string
|
||||||
using System.Collections;
|
private const string CsvConnectionTemplate = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Persist Security Info=False;Extended Properties=\"text;HDR=YES;FMT=Delimited\"";
|
||||||
using System.Collections.Generic;
|
private const string CsvConnectionTemplate64 = "Provider=Microsoft.Ace.OLEDB.12.0;Data Source={0};Persist Security Info=False;Extended Properties=\"text;HDR=YES;FMT=Delimited\"";
|
||||||
using System.Data;
|
|
||||||
using System.Data.OleDb;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
|
||||||
|
|
||||||
/// <summary>
|
private readonly string fileName;
|
||||||
/// Utility classes to access databases, and to handle quoted strings etc for comma separated value files.
|
|
||||||
/// </summary>
|
public CsvDataConnection(string fileName, List<string> dataFolders)
|
||||||
internal sealed class CsvDataConnection : TestDataConnection
|
: base(dataFolders)
|
||||||
{
|
{
|
||||||
// Template used to map from a filename to a DB connection string
|
Debug.Assert(!string.IsNullOrEmpty(fileName), "fileName");
|
||||||
private const string CsvConnectionTemplate = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Persist Security Info=False;Extended Properties=\"text;HDR=YES;FMT=Delimited\"";
|
this.fileName = fileName;
|
||||||
private const string CsvConnectionTemplate64 = "Provider=Microsoft.Ace.OLEDB.12.0;Data Source={0};Persist Security Info=False;Extended Properties=\"text;HDR=YES;FMT=Delimited\"";
|
}
|
||||||
|
|
||||||
private readonly string fileName;
|
private string TableName
|
||||||
|
{
|
||||||
public CsvDataConnection(string fileName, List<string> dataFolders)
|
get
|
||||||
: base(dataFolders)
|
|
||||||
{
|
{
|
||||||
Debug.Assert(!string.IsNullOrEmpty(fileName), "fileName");
|
// Only one table based on the name of the file, with dots converted to # signs
|
||||||
this.fileName = fileName;
|
return Path.GetFileName(this.fileName).Replace('.', '#');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private string TableName
|
public override List<string> GetDataTablesAndViews()
|
||||||
|
{
|
||||||
|
List<string> tableNames = new(1);
|
||||||
|
tableNames.Add(this.TableName);
|
||||||
|
return tableNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
||||||
|
public override List<string> GetColumns(string tableName)
|
||||||
|
{
|
||||||
|
// Somewhat heavy, this could be improved, right now I simply
|
||||||
|
// read the table in then check the columns...
|
||||||
|
try
|
||||||
{
|
{
|
||||||
get
|
DataTable table = this.ReadTable(tableName, null);
|
||||||
|
if (table != null)
|
||||||
{
|
{
|
||||||
// Only one table based on the name of the file, with dots converted to # signs
|
List<string> columnNames = new();
|
||||||
return Path.GetFileName(this.fileName).Replace('.', '#');
|
foreach (DataColumn column in table.Columns)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override List<string> GetDataTablesAndViews()
|
|
||||||
{
|
|
||||||
List<string> tableNames = new(1);
|
|
||||||
tableNames.Add(this.TableName);
|
|
||||||
return tableNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
|
||||||
public override List<string> GetColumns(string tableName)
|
|
||||||
{
|
|
||||||
// Somewhat heavy, this could be improved, right now I simply
|
|
||||||
// read the table in then check the columns...
|
|
||||||
try
|
|
||||||
{
|
|
||||||
DataTable table = this.ReadTable(tableName, null);
|
|
||||||
if (table != null)
|
|
||||||
{
|
{
|
||||||
List<string> columnNames = new();
|
columnNames.Add(column.ColumnName);
|
||||||
foreach (DataColumn column in table.Columns)
|
|
||||||
{
|
|
||||||
columnNames.Add(column.ColumnName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return columnNames;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
EqtTrace.ErrorIf(EqtTrace.IsErrorEnabled, exception.Message + " for CSV data source " + this.fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Untested. Leaving as-is.")]
|
|
||||||
[SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security", Justification = "Not passed in from user.")]
|
|
||||||
public DataTable ReadTable(string tableName, IEnumerable columns, int maxRows)
|
|
||||||
{
|
|
||||||
// We specifically use OleDb to read a CSV file...
|
|
||||||
WriteDiagnostics("ReadTable: {0}", tableName);
|
|
||||||
WriteDiagnostics("Current Directory: {0}", Directory.GetCurrentDirectory());
|
|
||||||
|
|
||||||
// We better work with a full path, if nothing else, errors become easier to report
|
|
||||||
string fullPath = this.FixPath(this.fileName) ?? Path.GetFullPath(this.fileName);
|
|
||||||
|
|
||||||
// We can map simplified CSVs to an OLEDB/Text connection, then proceed as normal
|
|
||||||
using OleDbConnection connection = new();
|
|
||||||
using OleDbDataAdapter dataAdapter = new();
|
|
||||||
using OleDbCommandBuilder commandBuilder = new();
|
|
||||||
using OleDbCommand command = new();
|
|
||||||
// We have to use the name of the folder which contains the CSV file in the connection string
|
|
||||||
// If target platform is x64, then use CsvConnectionTemplate64 connection string.
|
|
||||||
if (IntPtr.Size == 8)
|
|
||||||
{
|
|
||||||
connection.ConnectionString = string.Format(CultureInfo.InvariantCulture, CsvConnectionTemplate64, Path.GetDirectoryName(fullPath));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
connection.ConnectionString = string.Format(CultureInfo.InvariantCulture, CsvConnectionTemplate, Path.GetDirectoryName(fullPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteDiagnostics("Connection String: {0}", connection.ConnectionString);
|
|
||||||
|
|
||||||
// We have to open the connection now, before we try to quote
|
|
||||||
// the table name, otherwise QuoteIdentifier fails (for OleDb, go figure!)
|
|
||||||
// The connection will get closed when we dispose of it
|
|
||||||
connection.Open();
|
|
||||||
|
|
||||||
string quotedTableName = commandBuilder.QuoteIdentifier(tableName, connection);
|
|
||||||
|
|
||||||
command.Connection = connection;
|
|
||||||
|
|
||||||
string topClause;
|
|
||||||
if (maxRows >= 0)
|
|
||||||
{
|
|
||||||
topClause = string.Format(CultureInfo.InvariantCulture, " top {0}", maxRows.ToString(NumberFormatInfo.InvariantInfo));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
topClause = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
string columnsClause;
|
|
||||||
if (columns != null)
|
|
||||||
{
|
|
||||||
StringBuilder builder = new();
|
|
||||||
foreach (string columnName in columns)
|
|
||||||
{
|
|
||||||
if (builder.Length > 0)
|
|
||||||
{
|
|
||||||
builder.Append(',');
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.Append(commandBuilder.QuoteIdentifier(columnName, connection));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
columnsClause = builder.ToString();
|
return columnNames;
|
||||||
if (columnsClause.Length == 0)
|
|
||||||
{
|
|
||||||
columnsClause = "*";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
EqtTrace.ErrorIf(EqtTrace.IsErrorEnabled, exception.Message + " for CSV data source " + this.fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Untested. Leaving as-is.")]
|
||||||
|
[SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security", Justification = "Not passed in from user.")]
|
||||||
|
public DataTable ReadTable(string tableName, IEnumerable columns, int maxRows)
|
||||||
|
{
|
||||||
|
// We specifically use OleDb to read a CSV file...
|
||||||
|
WriteDiagnostics("ReadTable: {0}", tableName);
|
||||||
|
WriteDiagnostics("Current Directory: {0}", Directory.GetCurrentDirectory());
|
||||||
|
|
||||||
|
// We better work with a full path, if nothing else, errors become easier to report
|
||||||
|
string fullPath = this.FixPath(this.fileName) ?? Path.GetFullPath(this.fileName);
|
||||||
|
|
||||||
|
// We can map simplified CSVs to an OLEDB/Text connection, then proceed as normal
|
||||||
|
using OleDbConnection connection = new();
|
||||||
|
using OleDbDataAdapter dataAdapter = new();
|
||||||
|
using OleDbCommandBuilder commandBuilder = new();
|
||||||
|
using OleDbCommand command = new();
|
||||||
|
// We have to use the name of the folder which contains the CSV file in the connection string
|
||||||
|
// If target platform is x64, then use CsvConnectionTemplate64 connection string.
|
||||||
|
if (IntPtr.Size == 8)
|
||||||
|
{
|
||||||
|
connection.ConnectionString = string.Format(CultureInfo.InvariantCulture, CsvConnectionTemplate64, Path.GetDirectoryName(fullPath));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
connection.ConnectionString = string.Format(CultureInfo.InvariantCulture, CsvConnectionTemplate, Path.GetDirectoryName(fullPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteDiagnostics("Connection String: {0}", connection.ConnectionString);
|
||||||
|
|
||||||
|
// We have to open the connection now, before we try to quote
|
||||||
|
// the table name, otherwise QuoteIdentifier fails (for OleDb, go figure!)
|
||||||
|
// The connection will get closed when we dispose of it
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
string quotedTableName = commandBuilder.QuoteIdentifier(tableName, connection);
|
||||||
|
|
||||||
|
command.Connection = connection;
|
||||||
|
|
||||||
|
string topClause;
|
||||||
|
if (maxRows >= 0)
|
||||||
|
{
|
||||||
|
topClause = string.Format(CultureInfo.InvariantCulture, " top {0}", maxRows.ToString(NumberFormatInfo.InvariantInfo));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
topClause = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
string columnsClause;
|
||||||
|
if (columns != null)
|
||||||
|
{
|
||||||
|
StringBuilder builder = new();
|
||||||
|
foreach (string columnName in columns)
|
||||||
|
{
|
||||||
|
if (builder.Length > 0)
|
||||||
|
{
|
||||||
|
builder.Append(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append(commandBuilder.QuoteIdentifier(columnName, connection));
|
||||||
|
}
|
||||||
|
|
||||||
|
columnsClause = builder.ToString();
|
||||||
|
if (columnsClause.Length == 0)
|
||||||
{
|
{
|
||||||
columnsClause = "*";
|
columnsClause = "*";
|
||||||
}
|
}
|
||||||
|
|
||||||
command.CommandText = string.Format(CultureInfo.InvariantCulture, "select {0} {1} from {2}", topClause, columnsClause, quotedTableName);
|
|
||||||
WriteDiagnostics("Query: " + command.CommandText);
|
|
||||||
|
|
||||||
dataAdapter.SelectCommand = command;
|
|
||||||
|
|
||||||
DataTable table = new();
|
|
||||||
table.Locale = CultureInfo.InvariantCulture;
|
|
||||||
dataAdapter.Fill(table);
|
|
||||||
return table;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
public override DataTable ReadTable(string tableName, IEnumerable columns)
|
|
||||||
{
|
{
|
||||||
return this.ReadTable(tableName, columns, -1);
|
columnsClause = "*";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
command.CommandText = string.Format(CultureInfo.InvariantCulture, "select {0} {1} from {2}", topClause, columnsClause, quotedTableName);
|
||||||
|
WriteDiagnostics("Query: " + command.CommandText);
|
||||||
|
|
||||||
|
dataAdapter.SelectCommand = command;
|
||||||
|
|
||||||
|
DataTable table = new();
|
||||||
|
table.Locale = CultureInfo.InvariantCulture;
|
||||||
|
dataAdapter.Fill(table);
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override DataTable ReadTable(string tableName, IEnumerable columns)
|
||||||
|
{
|
||||||
|
return this.ReadTable(tableName, columns, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,121 +1,120 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Data
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Data;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data.Odbc;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Utility classes to access databases, and to handle quoted strings etc for ODBC.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class OdbcDataConnection : TestDataConnectionSql
|
||||||
{
|
{
|
||||||
using System;
|
private readonly bool isMSSql;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data.Odbc;
|
public OdbcDataConnection(string invariantProviderName, string connectionString, List<string> dataFolders)
|
||||||
using System.Diagnostics;
|
: base(invariantProviderName, FixConnectionString(connectionString, dataFolders), dataFolders)
|
||||||
|
{
|
||||||
|
// Need open connection to get Connection.Driver.
|
||||||
|
Debug.Assert(this.IsOpen(), "The connection must be open!");
|
||||||
|
|
||||||
|
this.isMSSql = this.Connection != null && IsMSSql(this.Connection.Driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
public new OdbcCommandBuilder CommandBuilder
|
||||||
|
{
|
||||||
|
get { return (OdbcCommandBuilder)base.CommandBuilder; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public new OdbcConnection Connection
|
||||||
|
{
|
||||||
|
get { return (OdbcConnection)base.Connection; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Utility classes to access databases, and to handle quoted strings etc for ODBC.
|
/// This is overridden because we need manually get quote literals, OleDb does not fill those automatically.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class OdbcDataConnection : TestDataConnectionSql
|
public override void GetQuoteLiterals()
|
||||||
{
|
{
|
||||||
private readonly bool isMSSql;
|
this.GetQuoteLiteralsHelper();
|
||||||
|
}
|
||||||
|
|
||||||
public OdbcDataConnection(string invariantProviderName, string connectionString, List<string> dataFolders)
|
public override string GetDefaultSchema()
|
||||||
: base(invariantProviderName, FixConnectionString(connectionString, dataFolders), dataFolders)
|
{
|
||||||
|
if (this.isMSSql)
|
||||||
{
|
{
|
||||||
// Need open connection to get Connection.Driver.
|
return this.GetDefaultSchemaMSSql();
|
||||||
Debug.Assert(this.IsOpen(), "The connection must be open!");
|
|
||||||
|
|
||||||
this.isMSSql = this.Connection != null && IsMSSql(this.Connection.Driver);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public new OdbcCommandBuilder CommandBuilder
|
return base.GetDefaultSchema();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override SchemaMetaData[] GetSchemaMetaData()
|
||||||
|
{
|
||||||
|
// The following may fail for Oracle ODBC, need to test that...
|
||||||
|
SchemaMetaData data1 = new()
|
||||||
{
|
{
|
||||||
get { return (OdbcCommandBuilder)base.CommandBuilder; }
|
SchemaTable = "Tables",
|
||||||
|
SchemaColumn = "TABLE_SCHEM",
|
||||||
|
NameColumn = "TABLE_NAME",
|
||||||
|
TableTypeColumn = "TABLE_TYPE",
|
||||||
|
ValidTableTypes = new string[] { "TABLE", "SYSTEM TABLE" },
|
||||||
|
InvalidSchemas = null
|
||||||
|
};
|
||||||
|
SchemaMetaData data2 = new()
|
||||||
|
{
|
||||||
|
SchemaTable = "Views",
|
||||||
|
SchemaColumn = "TABLE_SCHEM",
|
||||||
|
NameColumn = "TABLE_NAME",
|
||||||
|
TableTypeColumn = "TABLE_TYPE",
|
||||||
|
ValidTableTypes = new string[] { "VIEW" },
|
||||||
|
InvalidSchemas = new string[] { "sys", "INFORMATION_SCHEMA" }
|
||||||
|
};
|
||||||
|
return new SchemaMetaData[] { data1, data2 };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string QuoteIdentifier(string identifier)
|
||||||
|
{
|
||||||
|
Debug.Assert(!string.IsNullOrEmpty(identifier), "identifier");
|
||||||
|
return this.CommandBuilder.QuoteIdentifier(identifier, this.Connection); // Must pass connection.
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string UnquoteIdentifier(string identifier)
|
||||||
|
{
|
||||||
|
Debug.Assert(!string.IsNullOrEmpty(identifier), "identifier");
|
||||||
|
return this.CommandBuilder.UnquoteIdentifier(identifier, this.Connection); // Must pass connection.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to fix up excel connections
|
||||||
|
private static string FixConnectionString(string connectionString, List<string> dataFolders)
|
||||||
|
{
|
||||||
|
OdbcConnectionStringBuilder builder = new(connectionString);
|
||||||
|
|
||||||
|
// only fix this for excel
|
||||||
|
if (!string.Equals(builder.Dsn, "Excel Files"))
|
||||||
|
{
|
||||||
|
return connectionString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public new OdbcConnection Connection
|
string fileName = builder["dbq"] as string;
|
||||||
{
|
|
||||||
get { return (OdbcConnection)base.Connection; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
if (string.IsNullOrEmpty(fileName))
|
||||||
/// This is overridden because we need manually get quote literals, OleDb does not fill those automatically.
|
|
||||||
/// </summary>
|
|
||||||
public override void GetQuoteLiterals()
|
|
||||||
{
|
{
|
||||||
this.GetQuoteLiteralsHelper();
|
return connectionString;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
public override string GetDefaultSchema()
|
|
||||||
{
|
{
|
||||||
if (this.isMSSql)
|
// Fix-up magic file paths
|
||||||
|
string fixedFilePath = FixPath(fileName, dataFolders);
|
||||||
|
if (fixedFilePath != null)
|
||||||
{
|
{
|
||||||
return this.GetDefaultSchemaMSSql();
|
builder["dbq"] = fixedFilePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.GetDefaultSchema();
|
return builder.ConnectionString;
|
||||||
}
|
|
||||||
|
|
||||||
protected override SchemaMetaData[] GetSchemaMetaData()
|
|
||||||
{
|
|
||||||
// The following may fail for Oracle ODBC, need to test that...
|
|
||||||
SchemaMetaData data1 = new()
|
|
||||||
{
|
|
||||||
SchemaTable = "Tables",
|
|
||||||
SchemaColumn = "TABLE_SCHEM",
|
|
||||||
NameColumn = "TABLE_NAME",
|
|
||||||
TableTypeColumn = "TABLE_TYPE",
|
|
||||||
ValidTableTypes = new string[] { "TABLE", "SYSTEM TABLE" },
|
|
||||||
InvalidSchemas = null
|
|
||||||
};
|
|
||||||
SchemaMetaData data2 = new()
|
|
||||||
{
|
|
||||||
SchemaTable = "Views",
|
|
||||||
SchemaColumn = "TABLE_SCHEM",
|
|
||||||
NameColumn = "TABLE_NAME",
|
|
||||||
TableTypeColumn = "TABLE_TYPE",
|
|
||||||
ValidTableTypes = new string[] { "VIEW" },
|
|
||||||
InvalidSchemas = new string[] { "sys", "INFORMATION_SCHEMA" }
|
|
||||||
};
|
|
||||||
return new SchemaMetaData[] { data1, data2 };
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override string QuoteIdentifier(string identifier)
|
|
||||||
{
|
|
||||||
Debug.Assert(!string.IsNullOrEmpty(identifier), "identifier");
|
|
||||||
return this.CommandBuilder.QuoteIdentifier(identifier, this.Connection); // Must pass connection.
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override string UnquoteIdentifier(string identifier)
|
|
||||||
{
|
|
||||||
Debug.Assert(!string.IsNullOrEmpty(identifier), "identifier");
|
|
||||||
return this.CommandBuilder.UnquoteIdentifier(identifier, this.Connection); // Must pass connection.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Need to fix up excel connections
|
|
||||||
private static string FixConnectionString(string connectionString, List<string> dataFolders)
|
|
||||||
{
|
|
||||||
OdbcConnectionStringBuilder builder = new(connectionString);
|
|
||||||
|
|
||||||
// only fix this for excel
|
|
||||||
if (!string.Equals(builder.Dsn, "Excel Files"))
|
|
||||||
{
|
|
||||||
return connectionString;
|
|
||||||
}
|
|
||||||
|
|
||||||
string fileName = builder["dbq"] as string;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(fileName))
|
|
||||||
{
|
|
||||||
return connectionString;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Fix-up magic file paths
|
|
||||||
string fixedFilePath = FixPath(fileName, dataFolders);
|
|
||||||
if (fixedFilePath != null)
|
|
||||||
{
|
|
||||||
builder["dbq"] = fixedFilePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.ConnectionString;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,109 +1,108 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Data
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Data;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data.OleDb;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Utility classes to access databases, and to handle quoted strings etc for OLE DB.
|
||||||
|
/// </summary>
|
||||||
|
[SuppressMessage("Microsoft.Naming", "CA1706", Justification = "OleDb instead of Oledb to match System.Data.OleDb")]
|
||||||
|
internal sealed class OleDataConnection : TestDataConnectionSql
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
private readonly bool isMSSql;
|
||||||
using System.Data.OleDb;
|
|
||||||
using System.Diagnostics;
|
public OleDataConnection(string invariantProviderName, string connectionString, List<string> dataFolders)
|
||||||
using System.Diagnostics.CodeAnalysis;
|
: base(invariantProviderName, FixConnectionString(connectionString, dataFolders), dataFolders)
|
||||||
|
{
|
||||||
|
// Need open connection to get Connection.Provider.
|
||||||
|
Debug.Assert(this.IsOpen(), "The connection must be open!");
|
||||||
|
|
||||||
|
// Fill m_isMSSql.
|
||||||
|
this.isMSSql = this.Connection != null && IsMSSql(this.Connection.Provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
public new OleDbCommandBuilder CommandBuilder
|
||||||
|
{
|
||||||
|
get { return (OleDbCommandBuilder)base.CommandBuilder; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public new OleDbConnection Connection
|
||||||
|
{
|
||||||
|
get { return (OleDbConnection)base.Connection; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Utility classes to access databases, and to handle quoted strings etc for OLE DB.
|
/// This is overridden because we need manually get quote literals, OleDb does not fill those automatically.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SuppressMessage("Microsoft.Naming", "CA1706", Justification = "OleDb instead of Oledb to match System.Data.OleDb")]
|
public override void GetQuoteLiterals()
|
||||||
internal sealed class OleDataConnection : TestDataConnectionSql
|
|
||||||
{
|
{
|
||||||
private readonly bool isMSSql;
|
this.GetQuoteLiteralsHelper();
|
||||||
|
}
|
||||||
|
|
||||||
public OleDataConnection(string invariantProviderName, string connectionString, List<string> dataFolders)
|
public override string GetDefaultSchema()
|
||||||
: base(invariantProviderName, FixConnectionString(connectionString, dataFolders), dataFolders)
|
{
|
||||||
|
if (this.isMSSql)
|
||||||
{
|
{
|
||||||
// Need open connection to get Connection.Provider.
|
return this.GetDefaultSchemaMSSql();
|
||||||
Debug.Assert(this.IsOpen(), "The connection must be open!");
|
|
||||||
|
|
||||||
// Fill m_isMSSql.
|
|
||||||
this.isMSSql = this.Connection != null && IsMSSql(this.Connection.Provider);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public new OleDbCommandBuilder CommandBuilder
|
return base.GetDefaultSchema();
|
||||||
{
|
}
|
||||||
get { return (OleDbCommandBuilder)base.CommandBuilder; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public new OleDbConnection Connection
|
protected override SchemaMetaData[] GetSchemaMetaData()
|
||||||
|
{
|
||||||
|
// Note, in older iterations of the code there seemed to be
|
||||||
|
// cases when we also need to look in the "views" table
|
||||||
|
// but I do not see that in my test cases
|
||||||
|
SchemaMetaData data = new()
|
||||||
{
|
{
|
||||||
get { return (OleDbConnection)base.Connection; }
|
SchemaTable = "Tables",
|
||||||
}
|
SchemaColumn = "TABLE_SCHEMA",
|
||||||
|
NameColumn = "TABLE_NAME",
|
||||||
|
TableTypeColumn = "TABLE_TYPE",
|
||||||
|
ValidTableTypes = new string[] { "VIEW", "TABLE" },
|
||||||
|
InvalidSchemas = null
|
||||||
|
};
|
||||||
|
return new SchemaMetaData[] { data };
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected override string QuoteIdentifier(string identifier)
|
||||||
/// This is overridden because we need manually get quote literals, OleDb does not fill those automatically.
|
{
|
||||||
/// </summary>
|
Debug.Assert(!string.IsNullOrEmpty(identifier), "identifier");
|
||||||
public override void GetQuoteLiterals()
|
return this.CommandBuilder.QuoteIdentifier(identifier, this.Connection);
|
||||||
{
|
}
|
||||||
this.GetQuoteLiteralsHelper();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string GetDefaultSchema()
|
protected override string UnquoteIdentifier(string identifier)
|
||||||
|
{
|
||||||
|
Debug.Assert(!string.IsNullOrEmpty(identifier), "identifier");
|
||||||
|
return this.CommandBuilder.UnquoteIdentifier(identifier, this.Connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FixConnectionString(string connectionString, List<string> dataFolders)
|
||||||
|
{
|
||||||
|
OleDbConnectionStringBuilder oleDbBuilder = new(connectionString);
|
||||||
|
|
||||||
|
string fileName = oleDbBuilder.DataSource;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(fileName))
|
||||||
{
|
{
|
||||||
if (this.isMSSql)
|
return connectionString;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Fix-up magic file paths
|
||||||
|
string fixedFilePath = FixPath(fileName, dataFolders);
|
||||||
|
if (fixedFilePath != null)
|
||||||
{
|
{
|
||||||
return this.GetDefaultSchemaMSSql();
|
oleDbBuilder.DataSource = fixedFilePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.GetDefaultSchema();
|
return oleDbBuilder.ConnectionString;
|
||||||
}
|
|
||||||
|
|
||||||
protected override SchemaMetaData[] GetSchemaMetaData()
|
|
||||||
{
|
|
||||||
// Note, in older iterations of the code there seemed to be
|
|
||||||
// cases when we also need to look in the "views" table
|
|
||||||
// but I do not see that in my test cases
|
|
||||||
SchemaMetaData data = new()
|
|
||||||
{
|
|
||||||
SchemaTable = "Tables",
|
|
||||||
SchemaColumn = "TABLE_SCHEMA",
|
|
||||||
NameColumn = "TABLE_NAME",
|
|
||||||
TableTypeColumn = "TABLE_TYPE",
|
|
||||||
ValidTableTypes = new string[] { "VIEW", "TABLE" },
|
|
||||||
InvalidSchemas = null
|
|
||||||
};
|
|
||||||
return new SchemaMetaData[] { data };
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override string QuoteIdentifier(string identifier)
|
|
||||||
{
|
|
||||||
Debug.Assert(!string.IsNullOrEmpty(identifier), "identifier");
|
|
||||||
return this.CommandBuilder.QuoteIdentifier(identifier, this.Connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override string UnquoteIdentifier(string identifier)
|
|
||||||
{
|
|
||||||
Debug.Assert(!string.IsNullOrEmpty(identifier), "identifier");
|
|
||||||
return this.CommandBuilder.UnquoteIdentifier(identifier, this.Connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string FixConnectionString(string connectionString, List<string> dataFolders)
|
|
||||||
{
|
|
||||||
OleDbConnectionStringBuilder oleDbBuilder = new(connectionString);
|
|
||||||
|
|
||||||
string fileName = oleDbBuilder.DataSource;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(fileName))
|
|
||||||
{
|
|
||||||
return connectionString;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Fix-up magic file paths
|
|
||||||
string fixedFilePath = FixPath(fileName, dataFolders);
|
|
||||||
if (fixedFilePath != null)
|
|
||||||
{
|
|
||||||
oleDbBuilder.DataSource = fixedFilePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
return oleDbBuilder.ConnectionString;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,73 +1,72 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Data
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Data;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Utility classes to access databases, and to handle quoted strings etc for SQL Server.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class SqlDataConnection : TestDataConnectionSql
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
public SqlDataConnection(string invariantProviderName, string connectionString, List<string> dataFolders)
|
||||||
using System.Data.SqlClient;
|
: base(invariantProviderName, FixConnectionString(connectionString, dataFolders), dataFolders)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Utility classes to access databases, and to handle quoted strings etc for SQL Server.
|
/// Returns default database schema.
|
||||||
|
/// this.Connection must be already opened.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class SqlDataConnection : TestDataConnectionSql
|
/// <returns>The default database schema.</returns>
|
||||||
|
public override string GetDefaultSchema()
|
||||||
{
|
{
|
||||||
public SqlDataConnection(string invariantProviderName, string connectionString, List<string> dataFolders)
|
return this.GetDefaultSchemaMSSql();
|
||||||
: base(invariantProviderName, FixConnectionString(connectionString, dataFolders), dataFolders)
|
}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
protected override SchemaMetaData[] GetSchemaMetaData()
|
||||||
/// Returns default database schema.
|
{
|
||||||
/// this.Connection must be already opened.
|
SchemaMetaData data = new()
|
||||||
/// </summary>
|
|
||||||
/// <returns>The default database schema.</returns>
|
|
||||||
public override string GetDefaultSchema()
|
|
||||||
{
|
{
|
||||||
return this.GetDefaultSchemaMSSql();
|
SchemaTable = "Tables",
|
||||||
}
|
SchemaColumn = "TABLE_SCHEMA",
|
||||||
|
NameColumn = "TABLE_NAME",
|
||||||
|
TableTypeColumn = "TABLE_TYPE",
|
||||||
|
ValidTableTypes = new string[] { "VIEW", "BASE TABLE" },
|
||||||
|
InvalidSchemas = null
|
||||||
|
};
|
||||||
|
return new SchemaMetaData[] { data };
|
||||||
|
}
|
||||||
|
|
||||||
protected override SchemaMetaData[] GetSchemaMetaData()
|
private static string FixConnectionString(string connectionString, List<string> dataFolders)
|
||||||
|
{
|
||||||
|
SqlConnectionStringBuilder sqlBuilder = new(connectionString);
|
||||||
|
|
||||||
|
string attachedFile = sqlBuilder.AttachDBFilename;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(attachedFile))
|
||||||
{
|
{
|
||||||
SchemaMetaData data = new()
|
// No file, so no need to rewrite the connection string
|
||||||
|
return connectionString;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Force pooling off for SQL when there is a file involved
|
||||||
|
// Without this, after the connection is closed, an exclusive lock persists
|
||||||
|
// for a long time, preventing us from moving files around
|
||||||
|
sqlBuilder.Pooling = false;
|
||||||
|
|
||||||
|
// Fix-up magic file paths
|
||||||
|
string fixedFilePath = FixPath(attachedFile, dataFolders);
|
||||||
|
if (fixedFilePath != null)
|
||||||
{
|
{
|
||||||
SchemaTable = "Tables",
|
sqlBuilder.AttachDBFilename = fixedFilePath;
|
||||||
SchemaColumn = "TABLE_SCHEMA",
|
|
||||||
NameColumn = "TABLE_NAME",
|
|
||||||
TableTypeColumn = "TABLE_TYPE",
|
|
||||||
ValidTableTypes = new string[] { "VIEW", "BASE TABLE" },
|
|
||||||
InvalidSchemas = null
|
|
||||||
};
|
|
||||||
return new SchemaMetaData[] { data };
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string FixConnectionString(string connectionString, List<string> dataFolders)
|
|
||||||
{
|
|
||||||
SqlConnectionStringBuilder sqlBuilder = new(connectionString);
|
|
||||||
|
|
||||||
string attachedFile = sqlBuilder.AttachDBFilename;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(attachedFile))
|
|
||||||
{
|
|
||||||
// No file, so no need to rewrite the connection string
|
|
||||||
return connectionString;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// Force pooling off for SQL when there is a file involved
|
|
||||||
// Without this, after the connection is closed, an exclusive lock persists
|
|
||||||
// for a long time, preventing us from moving files around
|
|
||||||
sqlBuilder.Pooling = false;
|
|
||||||
|
|
||||||
// Fix-up magic file paths
|
// Return modified connection string
|
||||||
string fixedFilePath = FixPath(attachedFile, dataFolders);
|
return sqlBuilder.ConnectionString;
|
||||||
if (fixedFilePath != null)
|
|
||||||
{
|
|
||||||
sqlBuilder.AttachDBFilename = fixedFilePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return modified connection string
|
|
||||||
return sqlBuilder.ConnectionString;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,168 +1,167 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Data
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Data;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Data.Common;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This used to be "DataUtility", a helper class to handle quoted strings etc for different
|
||||||
|
/// data providers but the purpose has been expanded to be a general abstraction over a
|
||||||
|
/// connection, including the ability to read data and metadata (tables and columns)
|
||||||
|
/// </summary>
|
||||||
|
internal abstract class TestDataConnection : IDisposable
|
||||||
{
|
{
|
||||||
using System;
|
internal const string ConnectionDirectoryKey = "|DataDirectory|\\";
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
private static bool? extendedDiagnosticsEnabled;
|
||||||
using System.Data;
|
|
||||||
using System.Data.Common;
|
// List of places to look for files when substituting |DataDirectory|
|
||||||
using System.Diagnostics;
|
private readonly List<string> dataFolders;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
internal protected TestDataConnection(List<string> dataFolders)
|
||||||
using System.Security;
|
{
|
||||||
|
this.dataFolders = dataFolders;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This used to be "DataUtility", a helper class to handle quoted strings etc for different
|
/// Gets the connection.
|
||||||
/// data providers but the purpose has been expanded to be a general abstraction over a
|
|
||||||
/// connection, including the ability to read data and metadata (tables and columns)
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal abstract class TestDataConnection : IDisposable
|
/// <remarks>This will only return non-null for true DB based connections (TestDataConnectionSql)</remarks>
|
||||||
|
public virtual DbConnection Connection
|
||||||
{
|
{
|
||||||
internal const string ConnectionDirectoryKey = "|DataDirectory|\\";
|
get { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
private static bool? extendedDiagnosticsEnabled;
|
private static bool ExtendedDiagnosticsEnabled
|
||||||
|
{
|
||||||
// List of places to look for files when substituting |DataDirectory|
|
get
|
||||||
private readonly List<string> dataFolders;
|
|
||||||
|
|
||||||
internal protected TestDataConnection(List<string> dataFolders)
|
|
||||||
{
|
{
|
||||||
this.dataFolders = dataFolders;
|
if (!extendedDiagnosticsEnabled.HasValue)
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the connection.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This will only return non-null for true DB based connections (TestDataConnectionSql)</remarks>
|
|
||||||
public virtual DbConnection Connection
|
|
||||||
{
|
|
||||||
get { return null; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool ExtendedDiagnosticsEnabled
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
{
|
||||||
if (!extendedDiagnosticsEnabled.HasValue)
|
// We use an environment variable so that we can enable this extended
|
||||||
|
// diagnostic trace
|
||||||
|
try
|
||||||
{
|
{
|
||||||
// We use an environment variable so that we can enable this extended
|
string value = Environment.GetEnvironmentVariable("VSTS_DIAGNOSTICS");
|
||||||
// diagnostic trace
|
extendedDiagnosticsEnabled = (value != null) && value.Contains("TestDataConnection");
|
||||||
try
|
|
||||||
{
|
|
||||||
string value = Environment.GetEnvironmentVariable("VSTS_DIAGNOSTICS");
|
|
||||||
extendedDiagnosticsEnabled = (value != null) && value.Contains("TestDataConnection");
|
|
||||||
}
|
|
||||||
catch (SecurityException)
|
|
||||||
{
|
|
||||||
extendedDiagnosticsEnabled = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (SecurityException)
|
||||||
return extendedDiagnosticsEnabled.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a list of tables and views for this connection. Filters out "system" tables
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>List of names or null if error</returns>
|
|
||||||
public abstract List<string> GetDataTablesAndViews();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Given a table name, return a list of column names
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tableName">The name of the table.</param>
|
|
||||||
/// <returns>List of names or null if error</returns>
|
|
||||||
public abstract List<string> GetColumns(string tableName);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read the content of a table or view into memory
|
|
||||||
/// Try to limit to columns specified, if columns is null, read all columns
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tableName">Minimally quoted table name</param>
|
|
||||||
/// <param name="columns">Array of columns</param>
|
|
||||||
/// <returns>Data table or null if error</returns>
|
|
||||||
public abstract DataTable ReadTable(string tableName, IEnumerable columns);
|
|
||||||
|
|
||||||
// It is critical that is class be disposed of properly, otherwise
|
|
||||||
// data connections may be left open. In general it is best to use create instances
|
|
||||||
// in a "using"
|
|
||||||
public virtual void Dispose()
|
|
||||||
{
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static bool PathNeedsFixup(string path)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(path))
|
|
||||||
{
|
|
||||||
if (path.StartsWith(ConnectionDirectoryKey, StringComparison.Ordinal))
|
|
||||||
{
|
{
|
||||||
return true;
|
extendedDiagnosticsEnabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return extendedDiagnosticsEnabled.Value;
|
||||||
}
|
|
||||||
|
|
||||||
// Only use this if "PathNeedsFixup" returns true
|
|
||||||
internal static string GetRelativePart(string path)
|
|
||||||
{
|
|
||||||
Debug.Assert(PathNeedsFixup(path), "Incorrect path.");
|
|
||||||
return path.Substring(ConnectionDirectoryKey.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check a string to see if it has our magic prefix
|
|
||||||
// and if it does, assume what follows is a relative
|
|
||||||
// path, which we then convert by making it a full path
|
|
||||||
// otherwise return null
|
|
||||||
internal static string FixPath(string path, List<string> foldersToCheck)
|
|
||||||
{
|
|
||||||
if (PathNeedsFixup(path))
|
|
||||||
{
|
|
||||||
string relPath = GetRelativePart(path);
|
|
||||||
|
|
||||||
// First bet, relative to the current directory
|
|
||||||
string fullPath = Path.GetFullPath(relPath);
|
|
||||||
if (File.Exists(fullPath))
|
|
||||||
{
|
|
||||||
return fullPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Second bet, any on our folders foldersToCheck list
|
|
||||||
if (foldersToCheck != null)
|
|
||||||
{
|
|
||||||
foreach (string folder in foldersToCheck)
|
|
||||||
{
|
|
||||||
fullPath = Path.GetFullPath(Path.Combine(folder, relPath));
|
|
||||||
if (File.Exists(fullPath))
|
|
||||||
{
|
|
||||||
return fullPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally assume the file ended up directly in the current directory.
|
|
||||||
return Path.GetFullPath(Path.GetFileName(relPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Conditional("DEBUG")]
|
|
||||||
protected internal static void WriteDiagnostics(string formatString, params object[] parameters)
|
|
||||||
{
|
|
||||||
if (ExtendedDiagnosticsEnabled)
|
|
||||||
{
|
|
||||||
Debug.WriteLine("TestDataConnection: " + string.Format(CultureInfo.InvariantCulture, formatString, parameters));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected string FixPath(string path)
|
|
||||||
{
|
|
||||||
return FixPath(path, this.dataFolders);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a list of tables and views for this connection. Filters out "system" tables
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>List of names or null if error</returns>
|
||||||
|
public abstract List<string> GetDataTablesAndViews();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Given a table name, return a list of column names
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The name of the table.</param>
|
||||||
|
/// <returns>List of names or null if error</returns>
|
||||||
|
public abstract List<string> GetColumns(string tableName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read the content of a table or view into memory
|
||||||
|
/// Try to limit to columns specified, if columns is null, read all columns
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">Minimally quoted table name</param>
|
||||||
|
/// <param name="columns">Array of columns</param>
|
||||||
|
/// <returns>Data table or null if error</returns>
|
||||||
|
public abstract DataTable ReadTable(string tableName, IEnumerable columns);
|
||||||
|
|
||||||
|
// It is critical that is class be disposed of properly, otherwise
|
||||||
|
// data connections may be left open. In general it is best to use create instances
|
||||||
|
// in a "using"
|
||||||
|
public virtual void Dispose()
|
||||||
|
{
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool PathNeedsFixup(string path)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
if (path.StartsWith(ConnectionDirectoryKey, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only use this if "PathNeedsFixup" returns true
|
||||||
|
internal static string GetRelativePart(string path)
|
||||||
|
{
|
||||||
|
Debug.Assert(PathNeedsFixup(path), "Incorrect path.");
|
||||||
|
return path.Substring(ConnectionDirectoryKey.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check a string to see if it has our magic prefix
|
||||||
|
// and if it does, assume what follows is a relative
|
||||||
|
// path, which we then convert by making it a full path
|
||||||
|
// otherwise return null
|
||||||
|
internal static string FixPath(string path, List<string> foldersToCheck)
|
||||||
|
{
|
||||||
|
if (PathNeedsFixup(path))
|
||||||
|
{
|
||||||
|
string relPath = GetRelativePart(path);
|
||||||
|
|
||||||
|
// First bet, relative to the current directory
|
||||||
|
string fullPath = Path.GetFullPath(relPath);
|
||||||
|
if (File.Exists(fullPath))
|
||||||
|
{
|
||||||
|
return fullPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second bet, any on our folders foldersToCheck list
|
||||||
|
if (foldersToCheck != null)
|
||||||
|
{
|
||||||
|
foreach (string folder in foldersToCheck)
|
||||||
|
{
|
||||||
|
fullPath = Path.GetFullPath(Path.Combine(folder, relPath));
|
||||||
|
if (File.Exists(fullPath))
|
||||||
|
{
|
||||||
|
return fullPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally assume the file ended up directly in the current directory.
|
||||||
|
return Path.GetFullPath(Path.GetFileName(relPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Conditional("DEBUG")]
|
||||||
|
protected internal static void WriteDiagnostics(string formatString, params object[] parameters)
|
||||||
|
{
|
||||||
|
if (ExtendedDiagnosticsEnabled)
|
||||||
|
{
|
||||||
|
Debug.WriteLine("TestDataConnection: " + string.Format(CultureInfo.InvariantCulture, formatString, parameters));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected string FixPath(string path)
|
||||||
|
{
|
||||||
|
return FixPath(path, this.dataFolders);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,85 +1,84 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Data
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Data;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a class that creates TestDataConnection instances to connect to data sources.
|
||||||
|
/// </summary>
|
||||||
|
internal class TestDataConnectionFactory
|
||||||
{
|
{
|
||||||
using System;
|
// These are not "real" providers, but are recognized by the test runtime
|
||||||
using System.Collections.Generic;
|
private const string CsvProvider = "Microsoft.VisualStudio.TestTools.DataSource.CSV";
|
||||||
using System.Diagnostics;
|
private const string XmlProvider = "Microsoft.VisualStudio.TestTools.DataSource.XML";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines a class that creates TestDataConnection instances to connect to data sources.
|
/// Test Specific Providers: maps provider name to provider factory that we lookup prior to using (by default) SqlTestDataConnection.
|
||||||
|
/// Notes
|
||||||
|
/// - the key (provider name is case-sensitive).
|
||||||
|
/// - other providers can be registered using RegisterProvider (per app domain).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class TestDataConnectionFactory
|
private static Dictionary<string, TestDataConnectionFactory> specializedProviders = new()
|
||||||
{
|
{
|
||||||
// These are not "real" providers, but are recognized by the test runtime
|
// The XML case is quite unlike all others, as there is no real DB connection at all!
|
||||||
private const string CsvProvider = "Microsoft.VisualStudio.TestTools.DataSource.CSV";
|
{ XmlProvider, new XmlTestDataConnectionFactory() },
|
||||||
private const string XmlProvider = "Microsoft.VisualStudio.TestTools.DataSource.XML";
|
|
||||||
|
|
||||||
/// <summary>
|
// The CSV case does use a database connection, but it is hidden, and schema
|
||||||
/// Test Specific Providers: maps provider name to provider factory that we lookup prior to using (by default) SqlTestDataConnection.
|
// queries are highly specialized
|
||||||
/// Notes
|
{ CsvProvider, new CsvTestDataConnectionFactory() },
|
||||||
/// - the key (provider name is case-sensitive).
|
};
|
||||||
/// - other providers can be registered using RegisterProvider (per app domain).
|
|
||||||
/// </summary>
|
/// <summary>
|
||||||
private static Dictionary<string, TestDataConnectionFactory> specializedProviders = new()
|
/// Construct a wrapper for a database connection, what is actually returned indirectly depends
|
||||||
|
/// on the invariantProviderName, and the specific call knows how to deal with database variations
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="invariantProviderName">The provider name.</param>
|
||||||
|
/// <param name="connectionString">The connection string.</param>
|
||||||
|
/// <param name="dataFolders">null, or a list of locations to check when fixing up connection string</param>
|
||||||
|
/// <returns>The TestDataConnection instance.</returns>
|
||||||
|
public virtual TestDataConnection Create(string invariantProviderName, string connectionString, List<string> dataFolders)
|
||||||
|
{
|
||||||
|
Debug.Assert(!string.IsNullOrEmpty(invariantProviderName), "invariantProviderName");
|
||||||
|
Debug.Assert(!string.IsNullOrEmpty(connectionString), "connectionString");
|
||||||
|
|
||||||
|
TestDataConnection.WriteDiagnostics("Create {0}, {1}", invariantProviderName, connectionString);
|
||||||
|
|
||||||
|
// Most, but not all, connections are actually database based,
|
||||||
|
// here we look for special cases
|
||||||
|
if (specializedProviders.TryGetValue(invariantProviderName, out var factory))
|
||||||
{
|
{
|
||||||
// The XML case is quite unlike all others, as there is no real DB connection at all!
|
Debug.Assert(factory != null, "factory");
|
||||||
{ XmlProvider, new XmlTestDataConnectionFactory() },
|
return factory.Create(invariantProviderName, connectionString, dataFolders);
|
||||||
|
|
||||||
// The CSV case does use a database connection, but it is hidden, and schema
|
|
||||||
// queries are highly specialized
|
|
||||||
{ CsvProvider, new CsvTestDataConnectionFactory() },
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Construct a wrapper for a database connection, what is actually returned indirectly depends
|
|
||||||
/// on the invariantProviderName, and the specific call knows how to deal with database variations
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="invariantProviderName">The provider name.</param>
|
|
||||||
/// <param name="connectionString">The connection string.</param>
|
|
||||||
/// <param name="dataFolders">null, or a list of locations to check when fixing up connection string</param>
|
|
||||||
/// <returns>The TestDataConnection instance.</returns>
|
|
||||||
public virtual TestDataConnection Create(string invariantProviderName, string connectionString, List<string> dataFolders)
|
|
||||||
{
|
|
||||||
Debug.Assert(!string.IsNullOrEmpty(invariantProviderName), "invariantProviderName");
|
|
||||||
Debug.Assert(!string.IsNullOrEmpty(connectionString), "connectionString");
|
|
||||||
|
|
||||||
TestDataConnection.WriteDiagnostics("Create {0}, {1}", invariantProviderName, connectionString);
|
|
||||||
|
|
||||||
// Most, but not all, connections are actually database based,
|
|
||||||
// here we look for special cases
|
|
||||||
if (specializedProviders.TryGetValue(invariantProviderName, out var factory))
|
|
||||||
{
|
|
||||||
Debug.Assert(factory != null, "factory");
|
|
||||||
return factory.Create(invariantProviderName, connectionString, dataFolders);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Default is to use a conventional SQL based connection, this create method in turn
|
|
||||||
// handles variations between DB based implementations
|
|
||||||
return TestDataConnectionSql.Create(invariantProviderName, connectionString, dataFolders);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
#region TestDataConnectionFactories
|
|
||||||
|
|
||||||
private class XmlTestDataConnectionFactory : TestDataConnectionFactory
|
|
||||||
{
|
{
|
||||||
public override TestDataConnection Create(string invariantProviderName, string connectionString, List<string> dataFolders)
|
// Default is to use a conventional SQL based connection, this create method in turn
|
||||||
{
|
// handles variations between DB based implementations
|
||||||
return new XmlDataConnection(connectionString, dataFolders);
|
return TestDataConnectionSql.Create(invariantProviderName, connectionString, dataFolders);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CsvTestDataConnectionFactory : TestDataConnectionFactory
|
|
||||||
{
|
|
||||||
public override TestDataConnection Create(string invariantProviderName, string connectionString, List<string> dataFolders)
|
|
||||||
{
|
|
||||||
return new CsvDataConnection(connectionString, dataFolders);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion TestDataConnectionFactories
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region TestDataConnectionFactories
|
||||||
|
|
||||||
|
private class XmlTestDataConnectionFactory : TestDataConnectionFactory
|
||||||
|
{
|
||||||
|
public override TestDataConnection Create(string invariantProviderName, string connectionString, List<string> dataFolders)
|
||||||
|
{
|
||||||
|
return new XmlDataConnection(connectionString, dataFolders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CsvTestDataConnectionFactory : TestDataConnectionFactory
|
||||||
|
{
|
||||||
|
public override TestDataConnection Create(string invariantProviderName, string connectionString, List<string> dataFolders)
|
||||||
|
{
|
||||||
|
return new CsvDataConnection(connectionString, dataFolders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion TestDataConnectionFactories
|
||||||
}
|
}
|
||||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,129 +1,128 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Data
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Data;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security;
|
||||||
|
using System.Xml;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Utility classes to access databases, and to handle quoted strings etc for XML data.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class XmlDataConnection : TestDataConnection
|
||||||
{
|
{
|
||||||
using System;
|
private string fileName;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Security;
|
|
||||||
using System.Xml;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
|
||||||
|
|
||||||
/// <summary>
|
public XmlDataConnection(string fileName, List<string> dataFolders)
|
||||||
/// Utility classes to access databases, and to handle quoted strings etc for XML data.
|
: base(dataFolders)
|
||||||
/// </summary>
|
|
||||||
internal sealed class XmlDataConnection : TestDataConnection
|
|
||||||
{
|
{
|
||||||
private string fileName;
|
Debug.Assert(!string.IsNullOrEmpty(fileName), "fileName");
|
||||||
|
this.fileName = fileName;
|
||||||
|
}
|
||||||
|
|
||||||
public XmlDataConnection(string fileName, List<string> dataFolders)
|
public override List<string> GetDataTablesAndViews()
|
||||||
: base(dataFolders)
|
{
|
||||||
|
DataSet dataSet = this.LoadDataSet(true);
|
||||||
|
|
||||||
|
if (dataSet != null)
|
||||||
{
|
{
|
||||||
Debug.Assert(!string.IsNullOrEmpty(fileName), "fileName");
|
List<string> tableNames = new();
|
||||||
this.fileName = fileName;
|
|
||||||
|
int tableCount = dataSet.Tables.Count;
|
||||||
|
for (int i = 0; i < tableCount; i++)
|
||||||
|
{
|
||||||
|
DataTable table = dataSet.Tables[i];
|
||||||
|
tableNames.Add(table.TableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableNames;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
public override List<string> GetDataTablesAndViews()
|
|
||||||
{
|
{
|
||||||
DataSet dataSet = this.LoadDataSet(true);
|
|
||||||
|
|
||||||
if (dataSet != null)
|
|
||||||
{
|
|
||||||
List<string> tableNames = new();
|
|
||||||
|
|
||||||
int tableCount = dataSet.Tables.Count;
|
|
||||||
for (int i = 0; i < tableCount; i++)
|
|
||||||
{
|
|
||||||
DataTable table = dataSet.Tables[i];
|
|
||||||
tableNames.Add(table.TableName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tableNames;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override List<string> GetColumns(string tableName)
|
|
||||||
{
|
|
||||||
DataSet dataSet = this.LoadDataSet(true);
|
|
||||||
if (dataSet != null)
|
|
||||||
{
|
|
||||||
DataTable table = dataSet.Tables[tableName];
|
|
||||||
if (table != null)
|
|
||||||
{
|
|
||||||
List<string> columnNames = new();
|
|
||||||
foreach (DataColumn column in table.Columns)
|
|
||||||
{
|
|
||||||
// Only show "normal" columns, we try to hide derived columns used as part
|
|
||||||
// of the support for relations
|
|
||||||
if (column.ColumnMapping != MappingType.Hidden)
|
|
||||||
{
|
|
||||||
columnNames.Add(column.ColumnName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return columnNames;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override DataTable ReadTable(string tableName, IEnumerable columns)
|
|
||||||
{
|
|
||||||
// Reading XML is very simple...
|
|
||||||
// We do not ask it to just load a specific table, or specific columns
|
|
||||||
// so there is inefficiency since we will reload the entire file
|
|
||||||
// once for every table in it. Oh well. Reading XML is pretty quick
|
|
||||||
// compared to other forms of data source
|
|
||||||
DataSet ds = this.LoadDataSet(false);
|
|
||||||
return ds != null ? ds.Tables[tableName] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
|
||||||
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Un-tested. Preserving behavior.")]
|
|
||||||
private DataSet LoadDataSet(bool schemaOnly)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
DataSet dataSet = new();
|
|
||||||
dataSet.Locale = CultureInfo.CurrentCulture;
|
|
||||||
string path = this.FixPath(this.fileName) ?? Path.GetFullPath(this.fileName);
|
|
||||||
if (schemaOnly)
|
|
||||||
{
|
|
||||||
dataSet.ReadXmlSchema(path);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dataSet.ReadXml(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dataSet;
|
|
||||||
}
|
|
||||||
catch (SecurityException securityException)
|
|
||||||
{
|
|
||||||
EqtTrace.ErrorIf(EqtTrace.IsErrorEnabled, securityException.Message + " for XML data source " + this.fileName);
|
|
||||||
}
|
|
||||||
catch (XmlException xmlException)
|
|
||||||
{
|
|
||||||
EqtTrace.ErrorIf(EqtTrace.IsErrorEnabled, xmlException.Message + " for XML data source " + this.fileName);
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
// Yes, we get other exceptions too!
|
|
||||||
EqtTrace.ErrorIf(EqtTrace.IsErrorEnabled, exception.Message + " for XML data source " + this.fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override List<string> GetColumns(string tableName)
|
||||||
|
{
|
||||||
|
DataSet dataSet = this.LoadDataSet(true);
|
||||||
|
if (dataSet != null)
|
||||||
|
{
|
||||||
|
DataTable table = dataSet.Tables[tableName];
|
||||||
|
if (table != null)
|
||||||
|
{
|
||||||
|
List<string> columnNames = new();
|
||||||
|
foreach (DataColumn column in table.Columns)
|
||||||
|
{
|
||||||
|
// Only show "normal" columns, we try to hide derived columns used as part
|
||||||
|
// of the support for relations
|
||||||
|
if (column.ColumnMapping != MappingType.Hidden)
|
||||||
|
{
|
||||||
|
columnNames.Add(column.ColumnName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return columnNames;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override DataTable ReadTable(string tableName, IEnumerable columns)
|
||||||
|
{
|
||||||
|
// Reading XML is very simple...
|
||||||
|
// We do not ask it to just load a specific table, or specific columns
|
||||||
|
// so there is inefficiency since we will reload the entire file
|
||||||
|
// once for every table in it. Oh well. Reading XML is pretty quick
|
||||||
|
// compared to other forms of data source
|
||||||
|
DataSet ds = this.LoadDataSet(false);
|
||||||
|
return ds != null ? ds.Tables[tableName] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
||||||
|
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Un-tested. Preserving behavior.")]
|
||||||
|
private DataSet LoadDataSet(bool schemaOnly)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DataSet dataSet = new();
|
||||||
|
dataSet.Locale = CultureInfo.CurrentCulture;
|
||||||
|
string path = this.FixPath(this.fileName) ?? Path.GetFullPath(this.fileName);
|
||||||
|
if (schemaOnly)
|
||||||
|
{
|
||||||
|
dataSet.ReadXmlSchema(path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dataSet.ReadXml(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataSet;
|
||||||
|
}
|
||||||
|
catch (SecurityException securityException)
|
||||||
|
{
|
||||||
|
EqtTrace.ErrorIf(EqtTrace.IsErrorEnabled, securityException.Message + " for XML data source " + this.fileName);
|
||||||
|
}
|
||||||
|
catch (XmlException xmlException)
|
||||||
|
{
|
||||||
|
EqtTrace.ErrorIf(EqtTrace.IsErrorEnabled, xmlException.Message + " for XML data source " + this.fileName);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
// Yes, we get other exceptions too!
|
||||||
|
EqtTrace.ErrorIf(EqtTrace.IsErrorEnabled, exception.Message + " for XML data source " + this.fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,266 +1,265 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Utility function for Assembly related info
|
||||||
|
/// The caller is supposed to create AppDomain and create instance of given class in there.
|
||||||
|
/// </summary>
|
||||||
|
internal class AssemblyLoadWorker : MarshalByRefObject
|
||||||
{
|
{
|
||||||
using System;
|
private readonly IAssemblyUtility assemblyUtility;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
public AssemblyLoadWorker()
|
||||||
using System.Globalization;
|
: this(new AssemblyUtility())
|
||||||
using System.IO;
|
{
|
||||||
using System.Reflection;
|
}
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
internal AssemblyLoadWorker(IAssemblyUtility assemblyUtility)
|
||||||
|
{
|
||||||
|
this.assemblyUtility = assemblyUtility;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Utility function for Assembly related info
|
/// Returns the full path to the dependent assemblies of the parameter managed assembly recursively.
|
||||||
/// The caller is supposed to create AppDomain and create instance of given class in there.
|
/// It does not report GAC assemblies.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class AssemblyLoadWorker : MarshalByRefObject
|
/// <param name="assemblyPath"> Path to the assembly file to load from. </param>
|
||||||
|
/// <param name="warnings"> The warnings. </param>
|
||||||
|
/// <returns> Full path to dependent assemblies. </returns>
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
||||||
|
public string[] GetFullPathToDependentAssemblies(string assemblyPath, out IList<string> warnings)
|
||||||
{
|
{
|
||||||
private readonly IAssemblyUtility assemblyUtility;
|
Debug.Assert(!string.IsNullOrEmpty(assemblyPath), "assemblyPath");
|
||||||
|
|
||||||
public AssemblyLoadWorker()
|
warnings = new List<string>();
|
||||||
: this(new AssemblyUtility())
|
Assembly assembly = null;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
|
EqtTrace.Verbose($"AssemblyLoadWorker.GetFullPathToDependentAssemblies: Reflection loading {assemblyPath}.");
|
||||||
|
|
||||||
|
// First time we load in LoadFromContext to avoid issues.
|
||||||
|
assembly = this.assemblyUtility.ReflectionOnlyLoadFrom(assemblyPath);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
EqtTrace.Error($"AssemblyLoadWorker.GetFullPathToDependentAssemblies: Reflection loading of {assemblyPath} failed:");
|
||||||
|
EqtTrace.Error(ex);
|
||||||
|
|
||||||
|
warnings.Add(ex.Message);
|
||||||
|
return new string[0]; // Otherwise just return no dependencies.
|
||||||
}
|
}
|
||||||
|
|
||||||
internal AssemblyLoadWorker(IAssemblyUtility assemblyUtility)
|
Debug.Assert(assembly != null, "assembly");
|
||||||
{
|
|
||||||
this.assemblyUtility = assemblyUtility;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
List<string> result = new();
|
||||||
/// Returns the full path to the dependent assemblies of the parameter managed assembly recursively.
|
HashSet<string> visitedAssemblies = new();
|
||||||
/// It does not report GAC assemblies.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assemblyPath"> Path to the assembly file to load from. </param>
|
|
||||||
/// <param name="warnings"> The warnings. </param>
|
|
||||||
/// <returns> Full path to dependent assemblies. </returns>
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
|
||||||
public string[] GetFullPathToDependentAssemblies(string assemblyPath, out IList<string> warnings)
|
|
||||||
{
|
|
||||||
Debug.Assert(!string.IsNullOrEmpty(assemblyPath), "assemblyPath");
|
|
||||||
|
|
||||||
warnings = new List<string>();
|
visitedAssemblies.Add(assembly.FullName);
|
||||||
Assembly assembly = null;
|
|
||||||
|
this.ProcessChildren(assembly, result, visitedAssemblies, warnings);
|
||||||
|
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// initialize the lifetime service.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns> The <see cref="object"/>. </returns>
|
||||||
|
public override object InitializeLifetimeService()
|
||||||
|
{
|
||||||
|
// Infinite.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the target dotNet framework string for the assembly
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">Path of the assembly file</param>
|
||||||
|
/// <returns> String representation of the target dotNet framework e.g. .NETFramework,Version=v4.0 </returns>
|
||||||
|
internal string GetTargetFrameworkVersionStringFromPath(string path)
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
EqtTrace.Verbose($"AssemblyLoadWorker.GetFullPathToDependentAssemblies: Reflection loading {assemblyPath}.");
|
Assembly a = this.assemblyUtility.ReflectionOnlyLoadFrom(path);
|
||||||
|
return this.GetTargetFrameworkStringFromAssembly(a);
|
||||||
// First time we load in LoadFromContext to avoid issues.
|
}
|
||||||
assembly = this.assemblyUtility.ReflectionOnlyLoadFrom(assemblyPath);
|
catch (BadImageFormatException)
|
||||||
|
{
|
||||||
|
if (EqtTrace.IsErrorEnabled)
|
||||||
|
{
|
||||||
|
EqtTrace.Error("AssemblyHelper:GetTargetFrameworkVersionString() caught BadImageFormatException. Falling to native binary.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
EqtTrace.Error($"AssemblyLoadWorker.GetFullPathToDependentAssemblies: Reflection loading of {assemblyPath} failed:");
|
if (EqtTrace.IsErrorEnabled)
|
||||||
EqtTrace.Error(ex);
|
|
||||||
|
|
||||||
warnings.Add(ex.Message);
|
|
||||||
return new string[0]; // Otherwise just return no dependencies.
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Assert(assembly != null, "assembly");
|
|
||||||
|
|
||||||
List<string> result = new();
|
|
||||||
HashSet<string> visitedAssemblies = new();
|
|
||||||
|
|
||||||
visitedAssemblies.Add(assembly.FullName);
|
|
||||||
|
|
||||||
this.ProcessChildren(assembly, result, visitedAssemblies, warnings);
|
|
||||||
|
|
||||||
return result.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// initialize the lifetime service.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns> The <see cref="object"/>. </returns>
|
|
||||||
public override object InitializeLifetimeService()
|
|
||||||
{
|
|
||||||
// Infinite.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the target dotNet framework string for the assembly
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">Path of the assembly file</param>
|
|
||||||
/// <returns> String representation of the target dotNet framework e.g. .NETFramework,Version=v4.0 </returns>
|
|
||||||
internal string GetTargetFrameworkVersionStringFromPath(string path)
|
|
||||||
{
|
|
||||||
if (File.Exists(path))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
Assembly a = this.assemblyUtility.ReflectionOnlyLoadFrom(path);
|
EqtTrace.Error("AssemblyHelper:GetTargetFrameworkVersionString() Returning default. Unhandled exception: {0}.", ex);
|
||||||
return this.GetTargetFrameworkStringFromAssembly(a);
|
|
||||||
}
|
|
||||||
catch (BadImageFormatException)
|
|
||||||
{
|
|
||||||
if (EqtTrace.IsErrorEnabled)
|
|
||||||
{
|
|
||||||
EqtTrace.Error("AssemblyHelper:GetTargetFrameworkVersionString() caught BadImageFormatException. Falling to native binary.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
if (EqtTrace.IsErrorEnabled)
|
|
||||||
{
|
|
||||||
EqtTrace.Error("AssemblyHelper:GetTargetFrameworkVersionString() Returning default. Unhandled exception: {0}.", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the target dot net framework string for the assembly
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assembly">Assembly from which target framework has to find</param>
|
|
||||||
/// <returns>String representation of the target dot net framework e.g. .NETFramework,Version=v4.0 </returns>
|
|
||||||
private string GetTargetFrameworkStringFromAssembly(Assembly assembly)
|
|
||||||
{
|
|
||||||
string dotNetVersion = string.Empty;
|
|
||||||
foreach (CustomAttributeData data in CustomAttributeData.GetCustomAttributes(assembly))
|
|
||||||
{
|
|
||||||
if (data?.NamedArguments?.Count > 0)
|
|
||||||
{
|
|
||||||
var declaringType = data.NamedArguments[0].MemberInfo.DeclaringType;
|
|
||||||
if (declaringType != null)
|
|
||||||
{
|
|
||||||
string attributeName = declaringType.FullName;
|
|
||||||
if (string.Equals(
|
|
||||||
attributeName,
|
|
||||||
PlatformServices.Constants.TargetFrameworkAttributeFullName,
|
|
||||||
StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
dotNetVersion = data.ConstructorArguments[0].Value.ToString();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dotNetVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Processes references, modules, satellites.
|
|
||||||
/// Fills parameter results.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assembly"> The assembly. </param>
|
|
||||||
/// <param name="result"> The result. </param>
|
|
||||||
/// <param name="visitedAssemblies"> The visited Assemblies. </param>
|
|
||||||
/// <param name="warnings"> The warnings. </param>
|
|
||||||
private void ProcessChildren(Assembly assembly, IList<string> result, ISet<string> visitedAssemblies, IList<string> warnings)
|
|
||||||
{
|
|
||||||
Debug.Assert(assembly != null, "assembly");
|
|
||||||
|
|
||||||
EqtTrace.Verbose($"AssemblyLoadWorker.GetFullPathToDependentAssemblies: Processing assembly {assembly.FullName}.");
|
|
||||||
foreach (AssemblyName reference in assembly.GetReferencedAssemblies())
|
|
||||||
{
|
|
||||||
this.GetDependentAssembliesInternal(reference.FullName, result, visitedAssemblies, warnings);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take care of .netmodule's.
|
|
||||||
var modules = new Module[0];
|
|
||||||
try
|
|
||||||
{
|
|
||||||
EqtTrace.Verbose($"AssemblyLoadWorker.GetFullPathToDependentAssemblies: Getting modules of {assembly.FullName}.");
|
|
||||||
modules = assembly.GetModules();
|
|
||||||
}
|
|
||||||
catch (FileNotFoundException e)
|
|
||||||
{
|
|
||||||
string warning = string.Format(CultureInfo.CurrentCulture, Resource.MissingDeploymentDependency, e.FileName, e.Message);
|
|
||||||
warnings.Add(warning);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assembly.GetModules() returns all modules including main one.
|
|
||||||
if (modules.Length > 1)
|
|
||||||
{
|
|
||||||
// The modules must be in the same directory as assembly that references them.
|
|
||||||
foreach (Module m in modules)
|
|
||||||
{
|
|
||||||
// Module.Name ~ MyModule.netmodule. Module.FullyQualifiedName ~ C:\dir\MyModule.netmodule.
|
|
||||||
string shortName = m.Name;
|
|
||||||
|
|
||||||
// Note that "MyModule" may contain dots:
|
|
||||||
int dotIndex = shortName.LastIndexOf('.');
|
|
||||||
if (dotIndex > 0)
|
|
||||||
{
|
|
||||||
shortName = shortName.Substring(0, dotIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(shortName, assembly.GetName().Name, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
// This is main assembly module.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!visitedAssemblies.Add(m.Name))
|
|
||||||
{
|
|
||||||
// The assembly was already in the set, meaning that we already visited it.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!File.Exists(m.FullyQualifiedName))
|
|
||||||
{
|
|
||||||
string warning = string.Format(CultureInfo.CurrentCulture, Resource.MissingDeploymentDependencyWithoutReason, m.FullyQualifiedName);
|
|
||||||
warnings.Add(warning);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Add(m.FullyQualifiedName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return string.Empty;
|
||||||
/// Loads in Load Context. Fills private members.
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="assemblyString"> Full or partial assembly name passed to Assembly.Load. </param>
|
/// <summary>
|
||||||
/// <param name="result"> The result. </param>
|
/// Get the target dot net framework string for the assembly
|
||||||
/// <param name="visitedAssemblies"> The visited Assemblies. </param>
|
/// </summary>
|
||||||
/// <param name="warnings"> The warnings. </param>
|
/// <param name="assembly">Assembly from which target framework has to find</param>
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
/// <returns>String representation of the target dot net framework e.g. .NETFramework,Version=v4.0 </returns>
|
||||||
private void GetDependentAssembliesInternal(string assemblyString, IList<string> result, ISet<string> visitedAssemblies, IList<string> warnings)
|
private string GetTargetFrameworkStringFromAssembly(Assembly assembly)
|
||||||
|
{
|
||||||
|
string dotNetVersion = string.Empty;
|
||||||
|
foreach (CustomAttributeData data in CustomAttributeData.GetCustomAttributes(assembly))
|
||||||
{
|
{
|
||||||
Debug.Assert(!string.IsNullOrEmpty(assemblyString), "assemblyString");
|
if (data?.NamedArguments?.Count > 0)
|
||||||
|
|
||||||
if (!visitedAssemblies.Add(assemblyString))
|
|
||||||
{
|
{
|
||||||
// The assembly was already in the hashset, so we already visited it.
|
var declaringType = data.NamedArguments[0].MemberInfo.DeclaringType;
|
||||||
return;
|
if (declaringType != null)
|
||||||
|
{
|
||||||
|
string attributeName = declaringType.FullName;
|
||||||
|
if (string.Equals(
|
||||||
|
attributeName,
|
||||||
|
PlatformServices.Constants.TargetFrameworkAttributeFullName,
|
||||||
|
StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
dotNetVersion = data.ConstructorArguments[0].Value.ToString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Assembly assembly = null;
|
return dotNetVersion;
|
||||||
try
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes references, modules, satellites.
|
||||||
|
/// Fills parameter results.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assembly"> The assembly. </param>
|
||||||
|
/// <param name="result"> The result. </param>
|
||||||
|
/// <param name="visitedAssemblies"> The visited Assemblies. </param>
|
||||||
|
/// <param name="warnings"> The warnings. </param>
|
||||||
|
private void ProcessChildren(Assembly assembly, IList<string> result, ISet<string> visitedAssemblies, IList<string> warnings)
|
||||||
|
{
|
||||||
|
Debug.Assert(assembly != null, "assembly");
|
||||||
|
|
||||||
|
EqtTrace.Verbose($"AssemblyLoadWorker.GetFullPathToDependentAssemblies: Processing assembly {assembly.FullName}.");
|
||||||
|
foreach (AssemblyName reference in assembly.GetReferencedAssemblies())
|
||||||
|
{
|
||||||
|
this.GetDependentAssembliesInternal(reference.FullName, result, visitedAssemblies, warnings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take care of .netmodule's.
|
||||||
|
var modules = new Module[0];
|
||||||
|
try
|
||||||
|
{
|
||||||
|
EqtTrace.Verbose($"AssemblyLoadWorker.GetFullPathToDependentAssemblies: Getting modules of {assembly.FullName}.");
|
||||||
|
modules = assembly.GetModules();
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException e)
|
||||||
|
{
|
||||||
|
string warning = string.Format(CultureInfo.CurrentCulture, Resource.MissingDeploymentDependency, e.FileName, e.Message);
|
||||||
|
warnings.Add(warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assembly.GetModules() returns all modules including main one.
|
||||||
|
if (modules.Length > 1)
|
||||||
|
{
|
||||||
|
// The modules must be in the same directory as assembly that references them.
|
||||||
|
foreach (Module m in modules)
|
||||||
{
|
{
|
||||||
EqtTrace.Verbose($"AssemblyLoadWorker.GetDependentAssembliesInternal: Reflection loading {assemblyString}.");
|
// Module.Name ~ MyModule.netmodule. Module.FullyQualifiedName ~ C:\dir\MyModule.netmodule.
|
||||||
|
string shortName = m.Name;
|
||||||
|
|
||||||
string postPolicyAssembly = AppDomain.CurrentDomain.ApplyPolicy(assemblyString);
|
// Note that "MyModule" may contain dots:
|
||||||
Debug.Assert(!string.IsNullOrEmpty(postPolicyAssembly), "postPolicyAssembly");
|
int dotIndex = shortName.LastIndexOf('.');
|
||||||
|
if (dotIndex > 0)
|
||||||
|
{
|
||||||
|
shortName = shortName.Substring(0, dotIndex);
|
||||||
|
}
|
||||||
|
|
||||||
assembly = this.assemblyUtility.ReflectionOnlyLoad(postPolicyAssembly);
|
if (string.Equals(shortName, assembly.GetName().Name, StringComparison.OrdinalIgnoreCase))
|
||||||
visitedAssemblies.Add(assembly.FullName); // Just in case.
|
{
|
||||||
|
// This is main assembly module.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!visitedAssemblies.Add(m.Name))
|
||||||
|
{
|
||||||
|
// The assembly was already in the set, meaning that we already visited it.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(m.FullyQualifiedName))
|
||||||
|
{
|
||||||
|
string warning = string.Format(CultureInfo.CurrentCulture, Resource.MissingDeploymentDependencyWithoutReason, m.FullyQualifiedName);
|
||||||
|
warnings.Add(warning);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add(m.FullyQualifiedName);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
EqtTrace.Error($"AssemblyLoadWorker.GetDependentAssembliesInternal: Reflection loading {assemblyString} failed:.");
|
|
||||||
EqtTrace.Error(ex);
|
|
||||||
|
|
||||||
string warning = string.Format(CultureInfo.CurrentCulture, Resource.MissingDeploymentDependency, assemblyString, ex.Message);
|
|
||||||
warnings.Add(warning);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
EqtTrace.Verbose($"AssemblyLoadWorker.GetDependentAssembliesInternal: Assembly {assemblyString} was added as dependency.");
|
|
||||||
result.Add(assembly.Location);
|
|
||||||
|
|
||||||
this.ProcessChildren(assembly, result, visitedAssemblies, warnings);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads in Load Context. Fills private members.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assemblyString"> Full or partial assembly name passed to Assembly.Load. </param>
|
||||||
|
/// <param name="result"> The result. </param>
|
||||||
|
/// <param name="visitedAssemblies"> The visited Assemblies. </param>
|
||||||
|
/// <param name="warnings"> The warnings. </param>
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
||||||
|
private void GetDependentAssembliesInternal(string assemblyString, IList<string> result, ISet<string> visitedAssemblies, IList<string> warnings)
|
||||||
|
{
|
||||||
|
Debug.Assert(!string.IsNullOrEmpty(assemblyString), "assemblyString");
|
||||||
|
|
||||||
|
if (!visitedAssemblies.Add(assemblyString))
|
||||||
|
{
|
||||||
|
// The assembly was already in the hashset, so we already visited it.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assembly assembly = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
EqtTrace.Verbose($"AssemblyLoadWorker.GetDependentAssembliesInternal: Reflection loading {assemblyString}.");
|
||||||
|
|
||||||
|
string postPolicyAssembly = AppDomain.CurrentDomain.ApplyPolicy(assemblyString);
|
||||||
|
Debug.Assert(!string.IsNullOrEmpty(postPolicyAssembly), "postPolicyAssembly");
|
||||||
|
|
||||||
|
assembly = this.assemblyUtility.ReflectionOnlyLoad(postPolicyAssembly);
|
||||||
|
visitedAssemblies.Add(assembly.FullName); // Just in case.
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
EqtTrace.Error($"AssemblyLoadWorker.GetDependentAssembliesInternal: Reflection loading {assemblyString} failed:.");
|
||||||
|
EqtTrace.Error(ex);
|
||||||
|
|
||||||
|
string warning = string.Format(CultureInfo.CurrentCulture, Resource.MissingDeploymentDependency, assemblyString, ex.Message);
|
||||||
|
warnings.Add(warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EqtTrace.Verbose($"AssemblyLoadWorker.GetDependentAssembliesInternal: Assembly {assemblyString} was added as dependency.");
|
||||||
|
result.Add(assembly.Location);
|
||||||
|
|
||||||
|
this.ProcessChildren(assembly, result, visitedAssemblies, warnings);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,78 +1,77 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The test run directories.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
#pragma warning disable SA1649 // File name must match first type name
|
||||||
|
public class TestRunDirectories
|
||||||
|
#pragma warning restore SA1649 // File name must match first type name
|
||||||
{
|
{
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Diagnostics;
|
/// The default deployment root directory. We do not want to localize it.
|
||||||
using System.IO;
|
/// </summary>
|
||||||
|
internal const string DefaultDeploymentRootDirectory = "TestResults";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The test run directories.
|
/// The deployment in directory suffix.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Serializable]
|
internal const string DeploymentInDirectorySuffix = "In";
|
||||||
#pragma warning disable SA1649 // File name must match first type name
|
|
||||||
public class TestRunDirectories
|
/// <summary>
|
||||||
#pragma warning restore SA1649 // File name must match first type name
|
/// The deployment out directory suffix.
|
||||||
|
/// </summary>
|
||||||
|
internal const string DeploymentOutDirectorySuffix = "Out";
|
||||||
|
|
||||||
|
public TestRunDirectories(string rootDirectory)
|
||||||
{
|
{
|
||||||
/// <summary>
|
Debug.Assert(!string.IsNullOrEmpty(rootDirectory), "rootDirectory");
|
||||||
/// The default deployment root directory. We do not want to localize it.
|
|
||||||
/// </summary>
|
|
||||||
internal const string DefaultDeploymentRootDirectory = "TestResults";
|
|
||||||
|
|
||||||
/// <summary>
|
this.RootDeploymentDirectory = rootDirectory;
|
||||||
/// The deployment in directory suffix.
|
}
|
||||||
/// </summary>
|
|
||||||
internal const string DeploymentInDirectorySuffix = "In";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The deployment out directory suffix.
|
/// Gets or sets the root deployment directory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal const string DeploymentOutDirectorySuffix = "Out";
|
public string RootDeploymentDirectory { get; set; }
|
||||||
|
|
||||||
public TestRunDirectories(string rootDirectory)
|
/// <summary>
|
||||||
|
/// Gets the In directory
|
||||||
|
/// </summary>
|
||||||
|
public string InDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
Debug.Assert(!string.IsNullOrEmpty(rootDirectory), "rootDirectory");
|
return Path.Combine(this.RootDeploymentDirectory, DeploymentInDirectorySuffix);
|
||||||
|
|
||||||
this.RootDeploymentDirectory = rootDirectory;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the root deployment directory
|
/// Gets the Out directory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string RootDeploymentDirectory { get; set; }
|
public string OutDirectory
|
||||||
|
{
|
||||||
/// <summary>
|
get
|
||||||
/// Gets the In directory
|
|
||||||
/// </summary>
|
|
||||||
public string InDirectory
|
|
||||||
{
|
{
|
||||||
get
|
return Path.Combine(this.RootDeploymentDirectory, DeploymentOutDirectorySuffix);
|
||||||
{
|
|
||||||
return Path.Combine(this.RootDeploymentDirectory, DeploymentInDirectorySuffix);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the Out directory
|
/// Gets In\MachineName directory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string OutDirectory
|
public string InMachineNameDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
get
|
return Path.Combine(Path.Combine(this.RootDeploymentDirectory, DeploymentInDirectorySuffix), Environment.MachineName);
|
||||||
{
|
|
||||||
return Path.Combine(this.RootDeploymentDirectory, DeploymentOutDirectorySuffix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets In\MachineName directory
|
|
||||||
/// </summary>
|
|
||||||
public string InMachineNameDirectory
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Path.Combine(Path.Combine(this.RootDeploymentDirectory, DeploymentInDirectorySuffix), Environment.MachineName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>$(NetFrameworkMinimum)</TargetFrameworks>
|
<TargetFrameworks>$(NetFrameworkMinimum)</TargetFrameworks>
|
||||||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
||||||
<LangVersion>Latest</LangVersion>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|
|
@ -1,62 +1,61 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
||||||
{
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
|
||||||
#pragma warning disable SA1649 // SA1649FileNameMustMatchTypeName
|
#pragma warning disable SA1649 // SA1649FileNameMustMatchTypeName
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A service to log any trace messages from the adapter that would be shown in *.TpTrace files.
|
||||||
|
/// </summary>
|
||||||
|
public class AdapterTraceLogger : IAdapterTraceLogger
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A service to log any trace messages from the adapter that would be shown in *.TpTrace files.
|
/// Log an error in a given format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AdapterTraceLogger : IAdapterTraceLogger
|
/// <param name="format"> The format. </param>
|
||||||
|
/// <param name="args"> The args. </param>
|
||||||
|
public void LogError(string format, params object[] args)
|
||||||
{
|
{
|
||||||
/// <summary>
|
if (EqtTrace.IsErrorEnabled)
|
||||||
/// Log an error in a given format.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="format"> The format. </param>
|
|
||||||
/// <param name="args"> The args. </param>
|
|
||||||
public void LogError(string format, params object[] args)
|
|
||||||
{
|
{
|
||||||
if (EqtTrace.IsErrorEnabled)
|
EqtTrace.Error(this.PrependAdapterName(format), args);
|
||||||
{
|
|
||||||
EqtTrace.Error(this.PrependAdapterName(format), args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Log a warning in a given format.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="format"> The format. </param>
|
|
||||||
/// <param name="args"> The args. </param>
|
|
||||||
public void LogWarning(string format, params object[] args)
|
|
||||||
{
|
|
||||||
if (EqtTrace.IsWarningEnabled)
|
|
||||||
{
|
|
||||||
EqtTrace.Warning(this.PrependAdapterName(format), args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Log an information message in a given format.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="format"> The format. </param>
|
|
||||||
/// <param name="args"> The args. </param>
|
|
||||||
public void LogInfo(string format, params object[] args)
|
|
||||||
{
|
|
||||||
if (EqtTrace.IsInfoEnabled)
|
|
||||||
{
|
|
||||||
EqtTrace.Info(this.PrependAdapterName(format), args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string PrependAdapterName(string format)
|
|
||||||
{
|
|
||||||
return $"MSTest - {format}";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning restore SA1649 // SA1649FileNameMustMatchTypeName
|
/// <summary>
|
||||||
|
/// Log a warning in a given format.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format"> The format. </param>
|
||||||
|
/// <param name="args"> The args. </param>
|
||||||
|
public void LogWarning(string format, params object[] args)
|
||||||
|
{
|
||||||
|
if (EqtTrace.IsWarningEnabled)
|
||||||
|
{
|
||||||
|
EqtTrace.Warning(this.PrependAdapterName(format), args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Log an information message in a given format.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format"> The format. </param>
|
||||||
|
/// <param name="args"> The args. </param>
|
||||||
|
public void LogInfo(string format, params object[] args)
|
||||||
|
{
|
||||||
|
if (EqtTrace.IsInfoEnabled)
|
||||||
|
{
|
||||||
|
EqtTrace.Info(this.PrependAdapterName(format), args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string PrependAdapterName(string format)
|
||||||
|
{
|
||||||
|
return $"MSTest - {format}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning restore SA1649 // SA1649FileNameMustMatchTypeName
|
||||||
|
|
|
@ -1,134 +1,133 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This service is responsible for any file based operations.
|
||||||
|
/// </summary>
|
||||||
|
public class FileOperations : IFileOperations
|
||||||
{
|
{
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Reflection;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This service is responsible for any file based operations.
|
/// Loads an assembly into the current context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FileOperations : IFileOperations
|
/// <param name="assemblyFileName">The name of the assembly.</param>
|
||||||
|
/// <param name="isReflectionOnly">
|
||||||
|
/// Indicates whether this should be a reflection only load.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A handle to the loaded assembly.</returns>
|
||||||
|
public Assembly LoadAssembly(string assemblyFileName, bool isReflectionOnly)
|
||||||
{
|
{
|
||||||
/// <summary>
|
if (isReflectionOnly)
|
||||||
/// Loads an assembly into the current context.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assemblyFileName">The name of the assembly.</param>
|
|
||||||
/// <param name="isReflectionOnly">
|
|
||||||
/// Indicates whether this should be a reflection only load.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>A handle to the loaded assembly.</returns>
|
|
||||||
public Assembly LoadAssembly(string assemblyFileName, bool isReflectionOnly)
|
|
||||||
{
|
{
|
||||||
if (isReflectionOnly)
|
return Assembly.ReflectionOnlyLoadFrom(assemblyFileName);
|
||||||
{
|
|
||||||
return Assembly.ReflectionOnlyLoadFrom(assemblyFileName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return Assembly.LoadFrom(assemblyFileName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
/// <summary>
|
|
||||||
/// Gets the path to the .DLL of the assembly.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assembly">The assembly.</param>
|
|
||||||
/// <returns>Path to the .DLL of the assembly.</returns>
|
|
||||||
public string GetAssemblyPath(Assembly assembly)
|
|
||||||
{
|
{
|
||||||
return assembly.Location;
|
return Assembly.LoadFrom(assemblyFileName);
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Verify if a file exists in the current context.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assemblyFileName"> The assembly file name. </param>
|
|
||||||
/// <returns> true if the file exists. </returns>
|
|
||||||
public bool DoesFileExist(string assemblyFileName)
|
|
||||||
{
|
|
||||||
return (SafeInvoke<bool>(() => File.Exists(assemblyFileName)) as bool?) ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a Navigation session for the source file.
|
|
||||||
/// This is used to get file path and line number information for its components.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source"> The source file. </param>
|
|
||||||
/// <returns> A Navigation session instance for the current platform. </returns>
|
|
||||||
public object CreateNavigationSession(string source)
|
|
||||||
{
|
|
||||||
var messageFormatOnException =
|
|
||||||
string.Join("MSTestDiscoverer:DiaSession: Could not create diaSession for source:", source, ". Reason:{0}");
|
|
||||||
return SafeInvoke<DiaSession>(() => new DiaSession(source), messageFormatOnException) as DiaSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the navigation data for a navigation session.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="navigationSession"> The navigation session. </param>
|
|
||||||
/// <param name="className"> The class name. </param>
|
|
||||||
/// <param name="methodName"> The method name. </param>
|
|
||||||
/// <param name="minLineNumber"> The min line number. </param>
|
|
||||||
/// <param name="fileName"> The file name. </param>
|
|
||||||
public void GetNavigationData(object navigationSession, string className, string methodName, out int minLineNumber, out string fileName)
|
|
||||||
{
|
|
||||||
fileName = null;
|
|
||||||
minLineNumber = -1;
|
|
||||||
|
|
||||||
var diasession = navigationSession as DiaSession;
|
|
||||||
var navigationData = diasession?.GetNavigationData(className, methodName);
|
|
||||||
|
|
||||||
if (navigationData != null)
|
|
||||||
{
|
|
||||||
minLineNumber = navigationData.MinLineNumber;
|
|
||||||
fileName = navigationData.FileName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disposes the navigation session instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="navigationSession"> The navigation session. </param>
|
|
||||||
public void DisposeNavigationSession(object navigationSession)
|
|
||||||
{
|
|
||||||
var diasession = navigationSession as DiaSession;
|
|
||||||
diasession?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the full file path of an assembly file.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assemblyFileName"> The file name. </param>
|
|
||||||
/// <returns> The full file path. </returns>
|
|
||||||
public string GetFullFilePath(string assemblyFileName)
|
|
||||||
{
|
|
||||||
return (SafeInvoke<string>(() => Path.GetFullPath(assemblyFileName)) as string) ?? assemblyFileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static object SafeInvoke<T>(Func<T> action, string messageFormatOnException = null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return action.Invoke();
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(messageFormatOnException))
|
|
||||||
{
|
|
||||||
messageFormatOnException = "{0}";
|
|
||||||
}
|
|
||||||
|
|
||||||
EqtTrace.ErrorIf(EqtTrace.IsErrorEnabled, messageFormatOnException, exception.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning restore SA1649 // SA1649FileNameMustMatchTypeName
|
/// <summary>
|
||||||
|
/// Gets the path to the .DLL of the assembly.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assembly">The assembly.</param>
|
||||||
|
/// <returns>Path to the .DLL of the assembly.</returns>
|
||||||
|
public string GetAssemblyPath(Assembly assembly)
|
||||||
|
{
|
||||||
|
return assembly.Location;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verify if a file exists in the current context.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assemblyFileName"> The assembly file name. </param>
|
||||||
|
/// <returns> true if the file exists. </returns>
|
||||||
|
public bool DoesFileExist(string assemblyFileName)
|
||||||
|
{
|
||||||
|
return (SafeInvoke<bool>(() => File.Exists(assemblyFileName)) as bool?) ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a Navigation session for the source file.
|
||||||
|
/// This is used to get file path and line number information for its components.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source"> The source file. </param>
|
||||||
|
/// <returns> A Navigation session instance for the current platform. </returns>
|
||||||
|
public object CreateNavigationSession(string source)
|
||||||
|
{
|
||||||
|
var messageFormatOnException =
|
||||||
|
string.Join("MSTestDiscoverer:DiaSession: Could not create diaSession for source:", source, ". Reason:{0}");
|
||||||
|
return SafeInvoke<DiaSession>(() => new DiaSession(source), messageFormatOnException) as DiaSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the navigation data for a navigation session.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="navigationSession"> The navigation session. </param>
|
||||||
|
/// <param name="className"> The class name. </param>
|
||||||
|
/// <param name="methodName"> The method name. </param>
|
||||||
|
/// <param name="minLineNumber"> The min line number. </param>
|
||||||
|
/// <param name="fileName"> The file name. </param>
|
||||||
|
public void GetNavigationData(object navigationSession, string className, string methodName, out int minLineNumber, out string fileName)
|
||||||
|
{
|
||||||
|
fileName = null;
|
||||||
|
minLineNumber = -1;
|
||||||
|
|
||||||
|
var diasession = navigationSession as DiaSession;
|
||||||
|
var navigationData = diasession?.GetNavigationData(className, methodName);
|
||||||
|
|
||||||
|
if (navigationData != null)
|
||||||
|
{
|
||||||
|
minLineNumber = navigationData.MinLineNumber;
|
||||||
|
fileName = navigationData.FileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes the navigation session instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="navigationSession"> The navigation session. </param>
|
||||||
|
public void DisposeNavigationSession(object navigationSession)
|
||||||
|
{
|
||||||
|
var diasession = navigationSession as DiaSession;
|
||||||
|
diasession?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the full file path of an assembly file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assemblyFileName"> The file name. </param>
|
||||||
|
/// <returns> The full file path. </returns>
|
||||||
|
public string GetFullFilePath(string assemblyFileName)
|
||||||
|
{
|
||||||
|
return (SafeInvoke<string>(() => Path.GetFullPath(assemblyFileName)) as string) ?? assemblyFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object SafeInvoke<T>(Func<T> action, string messageFormatOnException = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return action.Invoke();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(messageFormatOnException))
|
||||||
|
{
|
||||||
|
messageFormatOnException = "{0}";
|
||||||
|
}
|
||||||
|
|
||||||
|
EqtTrace.ErrorIf(EqtTrace.IsErrorEnabled, messageFormatOnException, exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning restore SA1649 // SA1649FileNameMustMatchTypeName
|
||||||
|
|
|
@ -1,68 +1,67 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
using System;
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities;
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This service is responsible for platform specific reflection operations.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The test platform triggers discovery of test assets built for all architectures including ARM on desktop. In such cases we would need to load
|
||||||
|
/// these sources in a reflection only context. Since Reflection-Only context currently is primarily prevalent in .Net Framework only, this service is required
|
||||||
|
/// so that some operations like fetching attributes in a reflection only context can be performed.
|
||||||
|
/// </remarks>
|
||||||
|
public class ReflectionOperations : IReflectionOperations
|
||||||
|
{
|
||||||
|
private readonly ReflectionUtility reflectionUtility;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This service is responsible for platform specific reflection operations.
|
/// Initializes a new instance of the <see cref="ReflectionOperations"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
public ReflectionOperations()
|
||||||
/// The test platform triggers discovery of test assets built for all architectures including ARM on desktop. In such cases we would need to load
|
|
||||||
/// these sources in a reflection only context. Since Reflection-Only context currently is primarily prevalent in .Net Framework only, this service is required
|
|
||||||
/// so that some operations like fetching attributes in a reflection only context can be performed.
|
|
||||||
/// </remarks>
|
|
||||||
public class ReflectionOperations : IReflectionOperations
|
|
||||||
{
|
{
|
||||||
private readonly ReflectionUtility reflectionUtility;
|
this.reflectionUtility = new ReflectionUtility();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="ReflectionOperations"/> class.
|
|
||||||
/// </summary>
|
|
||||||
public ReflectionOperations()
|
|
||||||
{
|
|
||||||
this.reflectionUtility = new ReflectionUtility();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets all the custom attributes adorned on a member.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="memberInfo"> The member. </param>
|
|
||||||
/// <param name="inherit"> True to inspect the ancestors of element; otherwise, false. </param>
|
|
||||||
/// <returns> The list of attributes on the member. Empty list if none found. </returns>
|
|
||||||
public object[] GetCustomAttributes(MemberInfo memberInfo, bool inherit)
|
|
||||||
{
|
|
||||||
return this.reflectionUtility.GetCustomAttributes(memberInfo, inherit);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets all the custom attributes of a given type adorned on a member.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="memberInfo"> The member info. </param>
|
|
||||||
/// <param name="type"> The attribute type. </param>
|
|
||||||
/// <param name="inherit"> True to inspect the ancestors of element; otherwise, false. </param>
|
|
||||||
/// <returns> The list of attributes on the member. Empty list if none found. </returns>
|
|
||||||
public object[] GetCustomAttributes(MemberInfo memberInfo, Type type, bool inherit)
|
|
||||||
{
|
|
||||||
return this.reflectionUtility.GetCustomAttributes(memberInfo, type, inherit);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets all the custom attributes of a given type on an assembly.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assembly"> The assembly. </param>
|
|
||||||
/// <param name="type"> The attribute type. </param>
|
|
||||||
/// <returns> The list of attributes of the given type on the member. Empty list if none found. </returns>
|
|
||||||
public object[] GetCustomAttributes(Assembly assembly, Type type)
|
|
||||||
{
|
|
||||||
return this.reflectionUtility.GetCustomAttributes(assembly, type);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning restore SA1649 // SA1649FileNameMustMatchTypeName
|
/// <summary>
|
||||||
|
/// Gets all the custom attributes adorned on a member.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="memberInfo"> The member. </param>
|
||||||
|
/// <param name="inherit"> True to inspect the ancestors of element; otherwise, false. </param>
|
||||||
|
/// <returns> The list of attributes on the member. Empty list if none found. </returns>
|
||||||
|
public object[] GetCustomAttributes(MemberInfo memberInfo, bool inherit)
|
||||||
|
{
|
||||||
|
return this.reflectionUtility.GetCustomAttributes(memberInfo, inherit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all the custom attributes of a given type adorned on a member.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="memberInfo"> The member info. </param>
|
||||||
|
/// <param name="type"> The attribute type. </param>
|
||||||
|
/// <param name="inherit"> True to inspect the ancestors of element; otherwise, false. </param>
|
||||||
|
/// <returns> The list of attributes on the member. Empty list if none found. </returns>
|
||||||
|
public object[] GetCustomAttributes(MemberInfo memberInfo, Type type, bool inherit)
|
||||||
|
{
|
||||||
|
return this.reflectionUtility.GetCustomAttributes(memberInfo, type, inherit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all the custom attributes of a given type on an assembly.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assembly"> The assembly. </param>
|
||||||
|
/// <param name="type"> The attribute type. </param>
|
||||||
|
/// <returns> The list of attributes of the given type on the member. Empty list if none found. </returns>
|
||||||
|
public object[] GetCustomAttributes(Assembly assembly, Type type)
|
||||||
|
{
|
||||||
|
return this.reflectionUtility.GetCustomAttributes(assembly, type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning restore SA1649 // SA1649FileNameMustMatchTypeName
|
||||||
|
|
|
@ -1,506 +1,505 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Data.Common;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.ObjectModel;
|
||||||
|
|
||||||
|
using UTF = Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal implementation of TestContext exposed to the user.
|
||||||
|
/// The virtual string properties of the TestContext are retrieved from the property dictionary
|
||||||
|
/// like GetProperty<string>("TestName") or GetProperty<string>("FullyQualifiedTestClassName");
|
||||||
|
/// </summary>
|
||||||
|
public class TestContextImplementation : UTF.TestContext, ITestContext
|
||||||
{
|
{
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Collections;
|
/// List of result files associated with the test
|
||||||
using System.Collections.Generic;
|
/// </summary>
|
||||||
using System.Data;
|
private readonly IList<string> testResultFiles;
|
||||||
using System.Data.Common;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.ObjectModel;
|
|
||||||
|
|
||||||
using UTF = Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal implementation of TestContext exposed to the user.
|
/// Properties
|
||||||
/// The virtual string properties of the TestContext are retrieved from the property dictionary
|
|
||||||
/// like GetProperty<string>("TestName") or GetProperty<string>("FullyQualifiedTestClassName");
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TestContextImplementation : UTF.TestContext, ITestContext
|
private IDictionary<string, object> properties;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unit test outcome
|
||||||
|
/// </summary>
|
||||||
|
private UTF.UnitTestOutcome outcome;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writer on which the messages given by the user should be written
|
||||||
|
/// </summary>
|
||||||
|
private readonly ThreadSafeStringWriter threadSafeStringWriter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies whether the writer is disposed or not
|
||||||
|
/// </summary>
|
||||||
|
private bool stringWriterDisposed = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test Method
|
||||||
|
/// </summary>
|
||||||
|
private readonly ITestMethod testMethod;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DB connection for test context
|
||||||
|
/// </summary>
|
||||||
|
private DbConnection dbConnection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Data row for TestContext
|
||||||
|
/// </summary>
|
||||||
|
private DataRow dataRow;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TestContextImplementation"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="testMethod">The test method.</param>
|
||||||
|
/// <param name="stringWriter">The writer where diagnostic messages are written to.</param>
|
||||||
|
/// <param name="properties">Properties/configuration passed in.</param>
|
||||||
|
public TestContextImplementation(ITestMethod testMethod, StringWriter stringWriter, IDictionary<string, object> properties)
|
||||||
{
|
{
|
||||||
/// <summary>
|
Debug.Assert(testMethod != null, "TestMethod is not null");
|
||||||
/// List of result files associated with the test
|
Debug.Assert(stringWriter != null, "StringWriter is not null");
|
||||||
/// </summary>
|
Debug.Assert(properties != null, "properties is not null");
|
||||||
private readonly IList<string> testResultFiles;
|
|
||||||
|
|
||||||
/// <summary>
|
this.testMethod = testMethod;
|
||||||
/// Properties
|
|
||||||
/// </summary>
|
|
||||||
private IDictionary<string, object> properties;
|
|
||||||
|
|
||||||
/// <summary>
|
// Cannot get this type in constructor directly, because all signatures for all platforms need to be the same.
|
||||||
/// Unit test outcome
|
this.threadSafeStringWriter = (ThreadSafeStringWriter)stringWriter;
|
||||||
/// </summary>
|
this.properties = new Dictionary<string, object>(properties);
|
||||||
private UTF.UnitTestOutcome outcome;
|
this.CancellationTokenSource = new CancellationTokenSource();
|
||||||
|
this.InitializeProperties();
|
||||||
|
|
||||||
/// <summary>
|
this.testResultFiles = new List<string>();
|
||||||
/// Writer on which the messages given by the user should be written
|
}
|
||||||
/// </summary>
|
|
||||||
private readonly ThreadSafeStringWriter threadSafeStringWriter;
|
|
||||||
|
|
||||||
/// <summary>
|
#region TestContext impl
|
||||||
/// Specifies whether the writer is disposed or not
|
|
||||||
/// </summary>
|
|
||||||
private bool stringWriterDisposed = false;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Test Method
|
public override UTF.UnitTestOutcome CurrentTestOutcome
|
||||||
/// </summary>
|
{
|
||||||
private readonly ITestMethod testMethod;
|
get
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// DB connection for test context
|
|
||||||
/// </summary>
|
|
||||||
private DbConnection dbConnection;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Data row for TestContext
|
|
||||||
/// </summary>
|
|
||||||
private DataRow dataRow;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="TestContextImplementation"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="testMethod">The test method.</param>
|
|
||||||
/// <param name="stringWriter">The writer where diagnostic messages are written to.</param>
|
|
||||||
/// <param name="properties">Properties/configuration passed in.</param>
|
|
||||||
public TestContextImplementation(ITestMethod testMethod, StringWriter stringWriter, IDictionary<string, object> properties)
|
|
||||||
{
|
{
|
||||||
Debug.Assert(testMethod != null, "TestMethod is not null");
|
return this.outcome;
|
||||||
Debug.Assert(stringWriter != null, "StringWriter is not null");
|
|
||||||
Debug.Assert(properties != null, "properties is not null");
|
|
||||||
|
|
||||||
this.testMethod = testMethod;
|
|
||||||
|
|
||||||
// Cannot get this type in constructor directly, because all signatures for all platforms need to be the same.
|
|
||||||
this.threadSafeStringWriter = (ThreadSafeStringWriter)stringWriter;
|
|
||||||
this.properties = new Dictionary<string, object>(properties);
|
|
||||||
this.CancellationTokenSource = new CancellationTokenSource();
|
|
||||||
this.InitializeProperties();
|
|
||||||
|
|
||||||
this.testResultFiles = new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
#region TestContext impl
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override UTF.UnitTestOutcome CurrentTestOutcome
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.outcome;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override DbConnection DataConnection
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.dbConnection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override DataRow DataRow
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.dataRow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override IDictionary Properties
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.properties as IDictionary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string TestRunDirectory
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.GetStringPropertyValue(TestContextPropertyStrings.TestRunDirectory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string DeploymentDirectory
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.GetStringPropertyValue(TestContextPropertyStrings.DeploymentDirectory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string ResultsDirectory
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.GetStringPropertyValue(TestContextPropertyStrings.ResultsDirectory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string TestRunResultsDirectory
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.GetStringPropertyValue(TestContextPropertyStrings.TestRunResultsDirectory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", Justification = "TestResultsDirectory is what we need.")]
|
|
||||||
public override string TestResultsDirectory
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
// In MSTest, it is actually "In\697105f7-004f-42e8-bccf-eb024870d3e9\User1", but
|
|
||||||
// we are setting it to "In" only because MSTest does not create this directory.
|
|
||||||
return this.GetStringPropertyValue(TestContextPropertyStrings.TestResultsDirectory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string TestDir
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.GetStringPropertyValue(TestContextPropertyStrings.TestDir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string TestDeploymentDir
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.GetStringPropertyValue(TestContextPropertyStrings.TestDeploymentDir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string TestLogsDir
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.GetStringPropertyValue(TestContextPropertyStrings.TestLogsDir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string FullyQualifiedTestClassName
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.GetStringPropertyValue(TestContextPropertyStrings.FullyQualifiedTestClassName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string ManagedType
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.GetStringPropertyValue(TestContextPropertyStrings.ManagedType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string ManagedMethod
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.GetStringPropertyValue(TestContextPropertyStrings.ManagedMethod);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string TestName
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.GetStringPropertyValue(TestContextPropertyStrings.TestName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public UTF.TestContext Context
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this as UTF.TestContext;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void AddResultFile(string fileName)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(fileName))
|
|
||||||
{
|
|
||||||
throw new ArgumentException(Resource.Common_CannotBeNullOrEmpty, nameof(fileName));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.testResultFiles.Add(Path.GetFullPath(fileName));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void BeginTimer(string timerName)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void EndTimer(string timerName)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// When overridden in a derived class, used to write trace messages while the
|
|
||||||
/// test is running.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">The formatted string that contains the trace message.</param>
|
|
||||||
public override void Write(string message)
|
|
||||||
{
|
|
||||||
if (this.stringWriterDisposed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var msg = message?.Replace("\0", "\\0");
|
|
||||||
this.threadSafeStringWriter.Write(msg);
|
|
||||||
}
|
|
||||||
catch (ObjectDisposedException)
|
|
||||||
{
|
|
||||||
this.stringWriterDisposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// When overridden in a derived class, used to write trace messages while the
|
|
||||||
/// test is running.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="format">The string that contains the trace message.</param>
|
|
||||||
/// <param name="args">Arguments to add to the trace message.</param>
|
|
||||||
public override void Write(string format, params object[] args)
|
|
||||||
{
|
|
||||||
if (this.stringWriterDisposed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string message = string.Format(CultureInfo.CurrentCulture, format?.Replace("\0", "\\0"), args);
|
|
||||||
this.threadSafeStringWriter.Write(message);
|
|
||||||
}
|
|
||||||
catch (ObjectDisposedException)
|
|
||||||
{
|
|
||||||
this.stringWriterDisposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// When overridden in a derived class, used to write trace messages while the
|
|
||||||
/// test is running.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">The formatted string that contains the trace message.</param>
|
|
||||||
public override void WriteLine(string message)
|
|
||||||
{
|
|
||||||
if (this.stringWriterDisposed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var msg = message?.Replace("\0", "\\0");
|
|
||||||
this.threadSafeStringWriter.WriteLine(msg);
|
|
||||||
}
|
|
||||||
catch (ObjectDisposedException)
|
|
||||||
{
|
|
||||||
this.stringWriterDisposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// When overridden in a derived class, used to write trace messages while the
|
|
||||||
/// test is running.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="format">The string that contains the trace message.</param>
|
|
||||||
/// <param name="args">Arguments to add to the trace message.</param>
|
|
||||||
public override void WriteLine(string format, params object[] args)
|
|
||||||
{
|
|
||||||
if (this.stringWriterDisposed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string message = string.Format(CultureInfo.CurrentCulture, format?.Replace("\0", "\\0"), args);
|
|
||||||
this.threadSafeStringWriter.WriteLine(message);
|
|
||||||
}
|
|
||||||
catch (ObjectDisposedException)
|
|
||||||
{
|
|
||||||
this.stringWriterDisposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set the unit-test outcome
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="outcome">The test outcome.</param>
|
|
||||||
public void SetOutcome(UTF.UnitTestOutcome outcome)
|
|
||||||
{
|
|
||||||
this.outcome = ToUTF(outcome);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set data row for particular run of TestMethod.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dataRow">data row.</param>
|
|
||||||
public void SetDataRow(object dataRow)
|
|
||||||
{
|
|
||||||
this.dataRow = dataRow as DataRow;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set connection for TestContext
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dbConnection">db Connection.</param>
|
|
||||||
public void SetDataConnection(object dbConnection)
|
|
||||||
{
|
|
||||||
this.dbConnection = dbConnection as DbConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns whether property with parameter name is present or not
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="propertyName">The property name.</param>
|
|
||||||
/// <param name="propertyValue">The property value.</param>
|
|
||||||
/// <returns>True if found.</returns>
|
|
||||||
public bool TryGetPropertyValue(string propertyName, out object propertyValue)
|
|
||||||
{
|
|
||||||
if (this.properties == null)
|
|
||||||
{
|
|
||||||
propertyValue = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.properties.TryGetValue(propertyName, out propertyValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds the parameter name/value pair to property bag
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="propertyName">The property name.</param>
|
|
||||||
/// <param name="propertyValue">The property value.</param>
|
|
||||||
public void AddProperty(string propertyName, string propertyValue)
|
|
||||||
{
|
|
||||||
this.properties ??= new Dictionary<string, object>();
|
|
||||||
|
|
||||||
this.properties.Add(propertyName, propertyValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Result files attached
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Results files generated in run.</returns>
|
|
||||||
public IList<string> GetResultFiles()
|
|
||||||
{
|
|
||||||
if (!this.testResultFiles.Any())
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<string> results = this.testResultFiles.ToList();
|
|
||||||
|
|
||||||
// clear the result files to handle data driven tests
|
|
||||||
this.testResultFiles.Clear();
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets messages from the testContext writeLines
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The test context messages added so far.</returns>
|
|
||||||
public string GetDiagnosticMessages()
|
|
||||||
{
|
|
||||||
return this.threadSafeStringWriter.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears the previous testContext writeline messages.
|
|
||||||
/// </summary>
|
|
||||||
public void ClearDiagnosticMessages()
|
|
||||||
{
|
|
||||||
this.threadSafeStringWriter.ToStringAndClear();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts the parameter outcome to UTF outcome
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="outcome">The UTF outcome.</param>
|
|
||||||
/// <returns>test outcome</returns>
|
|
||||||
private static UTF.UnitTestOutcome ToUTF(UTF.UnitTestOutcome outcome)
|
|
||||||
{
|
|
||||||
switch (outcome)
|
|
||||||
{
|
|
||||||
case UTF.UnitTestOutcome.Error:
|
|
||||||
case UTF.UnitTestOutcome.Failed:
|
|
||||||
case UTF.UnitTestOutcome.Inconclusive:
|
|
||||||
case UTF.UnitTestOutcome.Passed:
|
|
||||||
case UTF.UnitTestOutcome.Timeout:
|
|
||||||
case UTF.UnitTestOutcome.InProgress:
|
|
||||||
return outcome;
|
|
||||||
|
|
||||||
default:
|
|
||||||
Debug.Fail("Unknown outcome " + outcome);
|
|
||||||
return UTF.UnitTestOutcome.Unknown;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper to safely fetch a property value.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="propertyName">Property Name</param>
|
|
||||||
/// <returns>Property value</returns>
|
|
||||||
private string GetStringPropertyValue(string propertyName)
|
|
||||||
{
|
|
||||||
this.properties.TryGetValue(propertyName, out var propertyValue);
|
|
||||||
return propertyValue as string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper to initialize the properties.
|
|
||||||
/// </summary>
|
|
||||||
private void InitializeProperties()
|
|
||||||
{
|
|
||||||
this.properties[TestContextPropertyStrings.FullyQualifiedTestClassName] = this.testMethod.FullClassName;
|
|
||||||
this.properties[TestContextPropertyStrings.ManagedType] = this.testMethod.ManagedTypeName;
|
|
||||||
this.properties[TestContextPropertyStrings.ManagedMethod] = this.testMethod.ManagedMethodName;
|
|
||||||
this.properties[TestContextPropertyStrings.TestName] = this.testMethod.Name;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override DbConnection DataConnection
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.dbConnection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override DataRow DataRow
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.dataRow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override IDictionary Properties
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.properties as IDictionary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string TestRunDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.GetStringPropertyValue(TestContextPropertyStrings.TestRunDirectory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string DeploymentDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.GetStringPropertyValue(TestContextPropertyStrings.DeploymentDirectory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string ResultsDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.GetStringPropertyValue(TestContextPropertyStrings.ResultsDirectory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string TestRunResultsDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.GetStringPropertyValue(TestContextPropertyStrings.TestRunResultsDirectory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", Justification = "TestResultsDirectory is what we need.")]
|
||||||
|
public override string TestResultsDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// In MSTest, it is actually "In\697105f7-004f-42e8-bccf-eb024870d3e9\User1", but
|
||||||
|
// we are setting it to "In" only because MSTest does not create this directory.
|
||||||
|
return this.GetStringPropertyValue(TestContextPropertyStrings.TestResultsDirectory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string TestDir
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.GetStringPropertyValue(TestContextPropertyStrings.TestDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string TestDeploymentDir
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.GetStringPropertyValue(TestContextPropertyStrings.TestDeploymentDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string TestLogsDir
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.GetStringPropertyValue(TestContextPropertyStrings.TestLogsDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string FullyQualifiedTestClassName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.GetStringPropertyValue(TestContextPropertyStrings.FullyQualifiedTestClassName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string ManagedType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.GetStringPropertyValue(TestContextPropertyStrings.ManagedType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string ManagedMethod
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.GetStringPropertyValue(TestContextPropertyStrings.ManagedMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string TestName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.GetStringPropertyValue(TestContextPropertyStrings.TestName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public UTF.TestContext Context
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this as UTF.TestContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void AddResultFile(string fileName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(fileName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(Resource.Common_CannotBeNullOrEmpty, nameof(fileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.testResultFiles.Add(Path.GetFullPath(fileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void BeginTimer(string timerName)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void EndTimer(string timerName)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When overridden in a derived class, used to write trace messages while the
|
||||||
|
/// test is running.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The formatted string that contains the trace message.</param>
|
||||||
|
public override void Write(string message)
|
||||||
|
{
|
||||||
|
if (this.stringWriterDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var msg = message?.Replace("\0", "\\0");
|
||||||
|
this.threadSafeStringWriter.Write(msg);
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
this.stringWriterDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When overridden in a derived class, used to write trace messages while the
|
||||||
|
/// test is running.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The string that contains the trace message.</param>
|
||||||
|
/// <param name="args">Arguments to add to the trace message.</param>
|
||||||
|
public override void Write(string format, params object[] args)
|
||||||
|
{
|
||||||
|
if (this.stringWriterDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string message = string.Format(CultureInfo.CurrentCulture, format?.Replace("\0", "\\0"), args);
|
||||||
|
this.threadSafeStringWriter.Write(message);
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
this.stringWriterDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When overridden in a derived class, used to write trace messages while the
|
||||||
|
/// test is running.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The formatted string that contains the trace message.</param>
|
||||||
|
public override void WriteLine(string message)
|
||||||
|
{
|
||||||
|
if (this.stringWriterDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var msg = message?.Replace("\0", "\\0");
|
||||||
|
this.threadSafeStringWriter.WriteLine(msg);
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
this.stringWriterDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When overridden in a derived class, used to write trace messages while the
|
||||||
|
/// test is running.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The string that contains the trace message.</param>
|
||||||
|
/// <param name="args">Arguments to add to the trace message.</param>
|
||||||
|
public override void WriteLine(string format, params object[] args)
|
||||||
|
{
|
||||||
|
if (this.stringWriterDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string message = string.Format(CultureInfo.CurrentCulture, format?.Replace("\0", "\\0"), args);
|
||||||
|
this.threadSafeStringWriter.WriteLine(message);
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
this.stringWriterDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the unit-test outcome
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outcome">The test outcome.</param>
|
||||||
|
public void SetOutcome(UTF.UnitTestOutcome outcome)
|
||||||
|
{
|
||||||
|
this.outcome = ToUTF(outcome);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set data row for particular run of TestMethod.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dataRow">data row.</param>
|
||||||
|
public void SetDataRow(object dataRow)
|
||||||
|
{
|
||||||
|
this.dataRow = dataRow as DataRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set connection for TestContext
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dbConnection">db Connection.</param>
|
||||||
|
public void SetDataConnection(object dbConnection)
|
||||||
|
{
|
||||||
|
this.dbConnection = dbConnection as DbConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether property with parameter name is present or not
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="propertyName">The property name.</param>
|
||||||
|
/// <param name="propertyValue">The property value.</param>
|
||||||
|
/// <returns>True if found.</returns>
|
||||||
|
public bool TryGetPropertyValue(string propertyName, out object propertyValue)
|
||||||
|
{
|
||||||
|
if (this.properties == null)
|
||||||
|
{
|
||||||
|
propertyValue = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.properties.TryGetValue(propertyName, out propertyValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the parameter name/value pair to property bag
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="propertyName">The property name.</param>
|
||||||
|
/// <param name="propertyValue">The property value.</param>
|
||||||
|
public void AddProperty(string propertyName, string propertyValue)
|
||||||
|
{
|
||||||
|
this.properties ??= new Dictionary<string, object>();
|
||||||
|
|
||||||
|
this.properties.Add(propertyName, propertyValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Result files attached
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Results files generated in run.</returns>
|
||||||
|
public IList<string> GetResultFiles()
|
||||||
|
{
|
||||||
|
if (!this.testResultFiles.Any())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<string> results = this.testResultFiles.ToList();
|
||||||
|
|
||||||
|
// clear the result files to handle data driven tests
|
||||||
|
this.testResultFiles.Clear();
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets messages from the testContext writeLines
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The test context messages added so far.</returns>
|
||||||
|
public string GetDiagnosticMessages()
|
||||||
|
{
|
||||||
|
return this.threadSafeStringWriter.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the previous testContext writeline messages.
|
||||||
|
/// </summary>
|
||||||
|
public void ClearDiagnosticMessages()
|
||||||
|
{
|
||||||
|
this.threadSafeStringWriter.ToStringAndClear();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the parameter outcome to UTF outcome
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outcome">The UTF outcome.</param>
|
||||||
|
/// <returns>test outcome</returns>
|
||||||
|
private static UTF.UnitTestOutcome ToUTF(UTF.UnitTestOutcome outcome)
|
||||||
|
{
|
||||||
|
switch (outcome)
|
||||||
|
{
|
||||||
|
case UTF.UnitTestOutcome.Error:
|
||||||
|
case UTF.UnitTestOutcome.Failed:
|
||||||
|
case UTF.UnitTestOutcome.Inconclusive:
|
||||||
|
case UTF.UnitTestOutcome.Passed:
|
||||||
|
case UTF.UnitTestOutcome.Timeout:
|
||||||
|
case UTF.UnitTestOutcome.InProgress:
|
||||||
|
return outcome;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Debug.Fail("Unknown outcome " + outcome);
|
||||||
|
return UTF.UnitTestOutcome.Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper to safely fetch a property value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="propertyName">Property Name</param>
|
||||||
|
/// <returns>Property value</returns>
|
||||||
|
private string GetStringPropertyValue(string propertyName)
|
||||||
|
{
|
||||||
|
this.properties.TryGetValue(propertyName, out var propertyValue);
|
||||||
|
return propertyValue as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper to initialize the properties.
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeProperties()
|
||||||
|
{
|
||||||
|
this.properties[TestContextPropertyStrings.FullyQualifiedTestClassName] = this.testMethod.FullClassName;
|
||||||
|
this.properties[TestContextPropertyStrings.ManagedType] = this.testMethod.ManagedTypeName;
|
||||||
|
this.properties[TestContextPropertyStrings.ManagedMethod] = this.testMethod.ManagedMethodName;
|
||||||
|
this.properties[TestContextPropertyStrings.TestName] = this.testMethod.Name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,145 +1,144 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Configuration;
|
|
||||||
using System.Data;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Data;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Extensions;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
|
||||||
|
|
||||||
using UTF = Microsoft.VisualStudio.TestTools.UnitTesting;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Configuration;
|
||||||
|
using System.Data;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Data;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Extensions;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
|
||||||
|
using UTF = Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
#pragma warning disable SA1649 // SA1649FileNameMustMatchTypeName
|
#pragma warning disable SA1649 // SA1649FileNameMustMatchTypeName
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The platform service that provides values from data source when data driven tests are run.
|
/// The platform service that provides values from data source when data driven tests are run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// NOTE NOTE NOTE: This platform service refers to the inbox UTF extension assembly for UTF.ITestMethod which can only be loadable inside of the app domain that discovers/runs
|
/// NOTE NOTE NOTE: This platform service refers to the inbox UTF extension assembly for UTF.ITestMethod which can only be loadable inside of the app domain that discovers/runs
|
||||||
/// the tests since it can only be found at the test output directory. DO NOT call into this platform service outside of the appdomain context if you do not want to hit
|
/// the tests since it can only be found at the test output directory. DO NOT call into this platform service outside of the appdomain context if you do not want to hit
|
||||||
/// a ReflectionTypeLoadException.
|
/// a ReflectionTypeLoadException.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class TestDataSource : ITestDataSource
|
public class TestDataSource : ITestDataSource
|
||||||
|
{
|
||||||
|
public IEnumerable<object> GetData(UTF.ITestMethod testMethodInfo, ITestContext testContext)
|
||||||
{
|
{
|
||||||
public IEnumerable<object> GetData(UTF.ITestMethod testMethodInfo, ITestContext testContext)
|
// Figure out where (as well as the current directory) we could look for data files
|
||||||
|
// for unit tests this means looking at the location of the test itself
|
||||||
|
List<string> dataFolders = new();
|
||||||
|
dataFolders.Add(Path.GetDirectoryName(new Uri(testMethodInfo.MethodInfo.Module.Assembly.CodeBase).LocalPath));
|
||||||
|
|
||||||
|
List<UTF.TestResult> dataRowResults = new();
|
||||||
|
|
||||||
|
// Connect to data source.
|
||||||
|
TestDataConnectionFactory factory = new();
|
||||||
|
|
||||||
|
string providerNameInvariant;
|
||||||
|
string connectionString;
|
||||||
|
string tableName;
|
||||||
|
UTF.DataAccessMethod dataAccessMethod;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
// Figure out where (as well as the current directory) we could look for data files
|
this.GetConnectionProperties(testMethodInfo.GetAttributes<UTF.DataSourceAttribute>(false)[0], out providerNameInvariant, out connectionString, out tableName, out dataAccessMethod);
|
||||||
// for unit tests this means looking at the location of the test itself
|
}
|
||||||
List<string> dataFolders = new();
|
catch (Exception ex)
|
||||||
dataFolders.Add(Path.GetDirectoryName(new Uri(testMethodInfo.MethodInfo.Module.Assembly.CodeBase).LocalPath));
|
{
|
||||||
|
throw ex;
|
||||||
List<UTF.TestResult> dataRowResults = new();
|
|
||||||
|
|
||||||
// Connect to data source.
|
|
||||||
TestDataConnectionFactory factory = new();
|
|
||||||
|
|
||||||
string providerNameInvariant;
|
|
||||||
string connectionString;
|
|
||||||
string tableName;
|
|
||||||
UTF.DataAccessMethod dataAccessMethod;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.GetConnectionProperties(testMethodInfo.GetAttributes<UTF.DataSourceAttribute>(false)[0], out providerNameInvariant, out connectionString, out tableName, out dataAccessMethod);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using TestDataConnection connection = factory.Create(providerNameInvariant, connectionString, dataFolders);
|
|
||||||
DataTable table = connection.ReadTable(tableName, null);
|
|
||||||
DataRow[] rows = table.Select();
|
|
||||||
Debug.Assert(rows != null, "rows should not be null.");
|
|
||||||
|
|
||||||
// check for row length is 0
|
|
||||||
if (rows.Length == 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerable<int> permutation = this.GetPermutation(dataAccessMethod, rows.Length);
|
|
||||||
|
|
||||||
object[] rowsAfterPermutation = new object[rows.Length];
|
|
||||||
int index = 0;
|
|
||||||
foreach (int rowIndex in permutation)
|
|
||||||
{
|
|
||||||
rowsAfterPermutation[index++] = rows[rowIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
testContext.SetDataConnection(connection.Connection);
|
|
||||||
return rowsAfterPermutation;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
string message = ExceptionExtensions.GetExceptionMessage(ex);
|
|
||||||
throw new Exception(string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorDataConnectionFailed, ex.Message), ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
try
|
||||||
/// Get permutations for data row access
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dataAccessMethod">The data access method.</param>
|
|
||||||
/// <param name="length">Number of permutations.</param>
|
|
||||||
/// <returns>Permutations.</returns>
|
|
||||||
private IEnumerable<int> GetPermutation(UTF.DataAccessMethod dataAccessMethod, int length)
|
|
||||||
{
|
{
|
||||||
switch (dataAccessMethod)
|
using TestDataConnection connection = factory.Create(providerNameInvariant, connectionString, dataFolders);
|
||||||
|
DataTable table = connection.ReadTable(tableName, null);
|
||||||
|
DataRow[] rows = table.Select();
|
||||||
|
Debug.Assert(rows != null, "rows should not be null.");
|
||||||
|
|
||||||
|
// check for row length is 0
|
||||||
|
if (rows.Length == 0)
|
||||||
{
|
{
|
||||||
case UTF.DataAccessMethod.Sequential:
|
return null;
|
||||||
return new SequentialIntPermutation(length);
|
|
||||||
|
|
||||||
case UTF.DataAccessMethod.Random:
|
|
||||||
return new RandomIntPermutation(length);
|
|
||||||
|
|
||||||
default:
|
|
||||||
Debug.Fail("Unknown DataAccessMehtod: " + dataAccessMethod);
|
|
||||||
return new SequentialIntPermutation(length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IEnumerable<int> permutation = this.GetPermutation(dataAccessMethod, rows.Length);
|
||||||
|
|
||||||
|
object[] rowsAfterPermutation = new object[rows.Length];
|
||||||
|
int index = 0;
|
||||||
|
foreach (int rowIndex in permutation)
|
||||||
|
{
|
||||||
|
rowsAfterPermutation[index++] = rows[rowIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
testContext.SetDataConnection(connection.Connection);
|
||||||
|
return rowsAfterPermutation;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
/// <summary>
|
|
||||||
/// Get connection property based on DataSourceAttribute. If its in config file then read it from config.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dataSourceAttribute">The dataSourceAttribute.</param>
|
|
||||||
/// <param name="providerNameInvariant">The provider name.</param>
|
|
||||||
/// <param name="connectionString">The connection string.</param>
|
|
||||||
/// <param name="tableName">The table name.</param>
|
|
||||||
/// <param name="dataAccessMethod">The data access method.</param>
|
|
||||||
private void GetConnectionProperties(UTF.DataSourceAttribute dataSourceAttribute, out string providerNameInvariant, out string connectionString, out string tableName, out UTF.DataAccessMethod dataAccessMethod)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(dataSourceAttribute.DataSourceSettingName) == false)
|
string message = ExceptionExtensions.GetExceptionMessage(ex);
|
||||||
{
|
throw new Exception(string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorDataConnectionFailed, ex.Message), ex);
|
||||||
UTF.DataSourceElement elem = UTF.TestConfiguration.ConfigurationSection.DataSources[dataSourceAttribute.DataSourceSettingName];
|
|
||||||
if (elem == null)
|
|
||||||
{
|
|
||||||
throw new Exception(string.Format(CultureInfo.CurrentCulture, Resource.UTA_DataSourceConfigurationSectionMissing, dataSourceAttribute.DataSourceSettingName));
|
|
||||||
}
|
|
||||||
|
|
||||||
providerNameInvariant = ConfigurationManager.ConnectionStrings[elem.ConnectionString].ProviderName;
|
|
||||||
connectionString = ConfigurationManager.ConnectionStrings[elem.ConnectionString].ConnectionString;
|
|
||||||
tableName = elem.DataTableName;
|
|
||||||
dataAccessMethod = (UTF.DataAccessMethod)Enum.Parse(typeof(UTF.DataAccessMethod), elem.DataAccessMethod);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
providerNameInvariant = dataSourceAttribute.ProviderInvariantName;
|
|
||||||
connectionString = dataSourceAttribute.ConnectionString;
|
|
||||||
tableName = dataSourceAttribute.TableName;
|
|
||||||
dataAccessMethod = dataSourceAttribute.DataAccessMethod;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning restore SA1649 // SA1649FileNameMustMatchTypeName
|
/// <summary>
|
||||||
|
/// Get permutations for data row access
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dataAccessMethod">The data access method.</param>
|
||||||
|
/// <param name="length">Number of permutations.</param>
|
||||||
|
/// <returns>Permutations.</returns>
|
||||||
|
private IEnumerable<int> GetPermutation(UTF.DataAccessMethod dataAccessMethod, int length)
|
||||||
|
{
|
||||||
|
switch (dataAccessMethod)
|
||||||
|
{
|
||||||
|
case UTF.DataAccessMethod.Sequential:
|
||||||
|
return new SequentialIntPermutation(length);
|
||||||
|
|
||||||
|
case UTF.DataAccessMethod.Random:
|
||||||
|
return new RandomIntPermutation(length);
|
||||||
|
|
||||||
|
default:
|
||||||
|
Debug.Fail("Unknown DataAccessMehtod: " + dataAccessMethod);
|
||||||
|
return new SequentialIntPermutation(length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get connection property based on DataSourceAttribute. If its in config file then read it from config.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dataSourceAttribute">The dataSourceAttribute.</param>
|
||||||
|
/// <param name="providerNameInvariant">The provider name.</param>
|
||||||
|
/// <param name="connectionString">The connection string.</param>
|
||||||
|
/// <param name="tableName">The table name.</param>
|
||||||
|
/// <param name="dataAccessMethod">The data access method.</param>
|
||||||
|
private void GetConnectionProperties(UTF.DataSourceAttribute dataSourceAttribute, out string providerNameInvariant, out string connectionString, out string tableName, out UTF.DataAccessMethod dataAccessMethod)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(dataSourceAttribute.DataSourceSettingName) == false)
|
||||||
|
{
|
||||||
|
UTF.DataSourceElement elem = UTF.TestConfiguration.ConfigurationSection.DataSources[dataSourceAttribute.DataSourceSettingName];
|
||||||
|
if (elem == null)
|
||||||
|
{
|
||||||
|
throw new Exception(string.Format(CultureInfo.CurrentCulture, Resource.UTA_DataSourceConfigurationSectionMissing, dataSourceAttribute.DataSourceSettingName));
|
||||||
|
}
|
||||||
|
|
||||||
|
providerNameInvariant = ConfigurationManager.ConnectionStrings[elem.ConnectionString].ProviderName;
|
||||||
|
connectionString = ConfigurationManager.ConnectionStrings[elem.ConnectionString].ConnectionString;
|
||||||
|
tableName = elem.DataTableName;
|
||||||
|
dataAccessMethod = (UTF.DataAccessMethod)Enum.Parse(typeof(UTF.DataAccessMethod), elem.DataAccessMethod);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
providerNameInvariant = dataSourceAttribute.ProviderInvariantName;
|
||||||
|
connectionString = dataSourceAttribute.ConnectionString;
|
||||||
|
tableName = dataSourceAttribute.TableName;
|
||||||
|
dataAccessMethod = dataSourceAttribute.DataAccessMethod;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning restore SA1649 // SA1649FileNameMustMatchTypeName
|
||||||
|
|
|
@ -1,70 +1,69 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
||||||
{
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
using System.Collections.Generic;
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;
|
||||||
|
|
||||||
#pragma warning disable SA1649 // SA1649FileNameMustMatchTypeName
|
#pragma warning disable SA1649 // SA1649FileNameMustMatchTypeName
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This platform service is responsible for any data or operations to validate
|
||||||
|
/// the test sources provided to the adapter.
|
||||||
|
/// </summary>
|
||||||
|
public class TestSource : ITestSource
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This platform service is responsible for any data or operations to validate
|
/// Gets the set of valid extensions for sources targeting this platform.
|
||||||
/// the test sources provided to the adapter.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TestSource : ITestSource
|
public IEnumerable<string> ValidSourceExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
get
|
||||||
/// Gets the set of valid extensions for sources targeting this platform.
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<string> ValidSourceExtensions
|
|
||||||
{
|
{
|
||||||
get
|
// Since desktop Platform service would also discover other platform tests on desktop,
|
||||||
{
|
// this extension list needs to be updated with all platforms supported file extensions.
|
||||||
// Since desktop Platform service would also discover other platform tests on desktop,
|
return new List<string>
|
||||||
// this extension list needs to be updated with all platforms supported file extensions.
|
{
|
||||||
return new List<string>
|
Constants.DllExtension,
|
||||||
{
|
Constants.PhoneAppxPackageExtension,
|
||||||
Constants.DllExtension,
|
Constants.ExeExtension
|
||||||
Constants.PhoneAppxPackageExtension,
|
};
|
||||||
Constants.ExeExtension
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Verifies if the assembly provided is referenced by the source.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assemblyName"> The assembly name. </param>
|
|
||||||
/// <param name="source"> The source. </param>
|
|
||||||
/// <returns> True if the assembly is referenced. </returns>
|
|
||||||
public bool IsAssemblyReferenced(AssemblyName assemblyName, string source)
|
|
||||||
{
|
|
||||||
// This loads the dll in a different app domain. We can optimize this to load in the current domain since this code could be run in a new app domain anyway.
|
|
||||||
bool? utfReference = AssemblyHelper.DoesReferencesAssembly(source, assemblyName);
|
|
||||||
|
|
||||||
// If no reference to UTF don't run discovery. Take conservative approach. If not able to find proceed with discovery.
|
|
||||||
if (utfReference.HasValue && utfReference.Value == false)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the set of sources (dll's/exe's) that contain tests. If a source is a package(appx), return the file(dll/exe) that contains tests from it.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sources"> Sources given to the adapter. </param>
|
|
||||||
/// <returns> Sources that contains tests. <see cref="IEnumerable{T}"/>. </returns>
|
|
||||||
public IEnumerable<string> GetTestSources(IEnumerable<string> sources)
|
|
||||||
{
|
|
||||||
return sources;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning restore SA1649 // SA1649FileNameMustMatchTypeName
|
/// <summary>
|
||||||
|
/// Verifies if the assembly provided is referenced by the source.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assemblyName"> The assembly name. </param>
|
||||||
|
/// <param name="source"> The source. </param>
|
||||||
|
/// <returns> True if the assembly is referenced. </returns>
|
||||||
|
public bool IsAssemblyReferenced(AssemblyName assemblyName, string source)
|
||||||
|
{
|
||||||
|
// This loads the dll in a different app domain. We can optimize this to load in the current domain since this code could be run in a new app domain anyway.
|
||||||
|
bool? utfReference = AssemblyHelper.DoesReferencesAssembly(source, assemblyName);
|
||||||
|
|
||||||
|
// If no reference to UTF don't run discovery. Take conservative approach. If not able to find proceed with discovery.
|
||||||
|
if (utfReference.HasValue && utfReference.Value == false)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the set of sources (dll's/exe's) that contain tests. If a source is a package(appx), return the file(dll/exe) that contains tests from it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sources"> Sources given to the adapter. </param>
|
||||||
|
/// <returns> Sources that contains tests. <see cref="IEnumerable{T}"/>. </returns>
|
||||||
|
public IEnumerable<string> GetTestSources(IEnumerable<string> sources)
|
||||||
|
{
|
||||||
|
return sources;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning restore SA1649 // SA1649FileNameMustMatchTypeName
|
||||||
|
|
|
@ -1,370 +1,369 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
using System;
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities;
|
using System.Collections.Generic;
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
using System.IO;
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
|
using System.Reflection;
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A host that loads the test source.This can be in isolation for desktop using an AppDomain or just loading the source in the current context.
|
||||||
|
/// </summary>
|
||||||
|
public class TestSourceHost : ITestSourceHost
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Child AppDomain used to discover/execute tests
|
||||||
|
/// </summary>
|
||||||
|
private AppDomain domain;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A host that loads the test source.This can be in isolation for desktop using an AppDomain or just loading the source in the current context.
|
/// Assembly resolver used in the current app-domain
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TestSourceHost : ITestSourceHost
|
private AssemblyResolver parentDomainAssemblyResolver;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Assembly resolver used in the new child app-domain created for discovery/execution
|
||||||
|
/// </summary>
|
||||||
|
private AssemblyResolver childDomainAssemblyResolver;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether child-appdomain needs to be created based on DisableAppDomain Flag set in runsettings
|
||||||
|
/// </summary>
|
||||||
|
private readonly bool isAppDomainCreationDisabled;
|
||||||
|
|
||||||
|
private readonly string sourceFileName;
|
||||||
|
private readonly IRunSettings runSettings;
|
||||||
|
private readonly IFrameworkHandle frameworkHandle;
|
||||||
|
|
||||||
|
private string currentDirectory = null;
|
||||||
|
private readonly IAppDomain appDomain;
|
||||||
|
|
||||||
|
private string targetFrameworkVersion;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TestSourceHost"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sourceFileName"> The source file name. </param>
|
||||||
|
/// <param name="runSettings"> The run-settings provided for this session. </param>
|
||||||
|
/// <param name="frameworkHandle"> The handle to the test platform. </param>
|
||||||
|
public TestSourceHost(string sourceFileName, IRunSettings runSettings, IFrameworkHandle frameworkHandle)
|
||||||
|
: this(sourceFileName, runSettings, frameworkHandle, new AppDomainWrapper())
|
||||||
{
|
{
|
||||||
/// <summary>
|
}
|
||||||
/// Child AppDomain used to discover/execute tests
|
|
||||||
/// </summary>
|
|
||||||
private AppDomain domain;
|
|
||||||
|
|
||||||
/// <summary>
|
internal TestSourceHost(string sourceFileName, IRunSettings runSettings, IFrameworkHandle frameworkHandle, IAppDomain appDomain)
|
||||||
/// Assembly resolver used in the current app-domain
|
{
|
||||||
/// </summary>
|
this.sourceFileName = sourceFileName;
|
||||||
private AssemblyResolver parentDomainAssemblyResolver;
|
this.runSettings = runSettings;
|
||||||
|
this.frameworkHandle = frameworkHandle;
|
||||||
|
this.appDomain = appDomain;
|
||||||
|
|
||||||
/// <summary>
|
// Set the environment context.
|
||||||
/// Assembly resolver used in the new child app-domain created for discovery/execution
|
this.SetContext(sourceFileName);
|
||||||
/// </summary>
|
|
||||||
private AssemblyResolver childDomainAssemblyResolver;
|
|
||||||
|
|
||||||
/// <summary>
|
// Set isAppDomainCreationDisabled flag
|
||||||
/// Determines whether child-appdomain needs to be created based on DisableAppDomain Flag set in runsettings
|
this.isAppDomainCreationDisabled = (this.runSettings != null) && MSTestAdapterSettings.IsAppDomainCreationDisabled(this.runSettings.SettingsXml);
|
||||||
/// </summary>
|
}
|
||||||
private readonly bool isAppDomainCreationDisabled;
|
|
||||||
|
|
||||||
private readonly string sourceFileName;
|
internal AppDomain AppDomain
|
||||||
private readonly IRunSettings runSettings;
|
{
|
||||||
private readonly IFrameworkHandle frameworkHandle;
|
get
|
||||||
|
|
||||||
private string currentDirectory = null;
|
|
||||||
private readonly IAppDomain appDomain;
|
|
||||||
|
|
||||||
private string targetFrameworkVersion;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="TestSourceHost"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sourceFileName"> The source file name. </param>
|
|
||||||
/// <param name="runSettings"> The run-settings provided for this session. </param>
|
|
||||||
/// <param name="frameworkHandle"> The handle to the test platform. </param>
|
|
||||||
public TestSourceHost(string sourceFileName, IRunSettings runSettings, IFrameworkHandle frameworkHandle)
|
|
||||||
: this(sourceFileName, runSettings, frameworkHandle, new AppDomainWrapper())
|
|
||||||
{
|
{
|
||||||
}
|
return this.domain;
|
||||||
|
|
||||||
internal TestSourceHost(string sourceFileName, IRunSettings runSettings, IFrameworkHandle frameworkHandle, IAppDomain appDomain)
|
|
||||||
{
|
|
||||||
this.sourceFileName = sourceFileName;
|
|
||||||
this.runSettings = runSettings;
|
|
||||||
this.frameworkHandle = frameworkHandle;
|
|
||||||
this.appDomain = appDomain;
|
|
||||||
|
|
||||||
// Set the environment context.
|
|
||||||
this.SetContext(sourceFileName);
|
|
||||||
|
|
||||||
// Set isAppDomainCreationDisabled flag
|
|
||||||
this.isAppDomainCreationDisabled = (this.runSettings != null) && MSTestAdapterSettings.IsAppDomainCreationDisabled(this.runSettings.SettingsXml);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal AppDomain AppDomain
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.domain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Setup the isolation host.
|
|
||||||
/// </summary>
|
|
||||||
public void SetupHost()
|
|
||||||
{
|
|
||||||
List<string> resolutionPaths = this.GetResolutionPaths(this.sourceFileName, VSInstallationUtilities.IsCurrentProcessRunningInPortableMode());
|
|
||||||
|
|
||||||
if (EqtTrace.IsInfoEnabled)
|
|
||||||
{
|
|
||||||
EqtTrace.Info("DesktopTestSourceHost.SetupHost(): Creating assembly resolver with resolution paths {0}.", string.Join(",", resolutionPaths.ToArray()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Case when DisableAppDomain setting is present in runsettings and no child-appdomain needs to be created
|
|
||||||
if (this.isAppDomainCreationDisabled)
|
|
||||||
{
|
|
||||||
this.parentDomainAssemblyResolver = new AssemblyResolver(resolutionPaths);
|
|
||||||
this.AddSearchDirectoriesSpecifiedInRunSettingsToAssemblyResolver(this.parentDomainAssemblyResolver, Path.GetDirectoryName(this.sourceFileName));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create child-appdomain and set assembly resolver on it
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Setup app-domain
|
|
||||||
var appDomainSetup = new AppDomainSetup();
|
|
||||||
this.targetFrameworkVersion = this.GetTargetFrameworkVersionString(this.sourceFileName);
|
|
||||||
AppDomainUtilities.SetAppDomainFrameworkVersionBasedOnTestSource(appDomainSetup, this.targetFrameworkVersion);
|
|
||||||
|
|
||||||
appDomainSetup.ApplicationBase = this.GetAppBaseAsPerPlatform();
|
|
||||||
var configFile = this.GetConfigFileForTestSource(this.sourceFileName);
|
|
||||||
AppDomainUtilities.SetConfigurationFile(appDomainSetup, configFile);
|
|
||||||
|
|
||||||
EqtTrace.Info("DesktopTestSourceHost.SetupHost(): Creating app-domain for source {0} with application base path {1}.", this.sourceFileName, appDomainSetup.ApplicationBase);
|
|
||||||
|
|
||||||
string domainName = string.Format("TestSourceHost: Enumerating source ({0})", this.sourceFileName);
|
|
||||||
this.domain = this.appDomain.CreateDomain(domainName, null, appDomainSetup);
|
|
||||||
|
|
||||||
// Load objectModel before creating assembly resolver otherwise in 3.5 process, we run into a recursive assembly resolution
|
|
||||||
// which is trigged by AppContainerUtilities.AttachEventToResolveWinmd method.
|
|
||||||
EqtTrace.SetupRemoteEqtTraceListeners(this.domain);
|
|
||||||
|
|
||||||
// Add an assembly resolver in the child app-domain...
|
|
||||||
Type assemblyResolverType = typeof(AssemblyResolver);
|
|
||||||
|
|
||||||
EqtTrace.Info("DesktopTestSourceHost.SetupHost(): assemblyenumerator location: {0} , fullname: {1} ", assemblyResolverType.Assembly.Location, assemblyResolverType.FullName);
|
|
||||||
|
|
||||||
var resolver = AppDomainUtilities.CreateInstance(
|
|
||||||
this.domain,
|
|
||||||
assemblyResolverType,
|
|
||||||
new object[] { resolutionPaths });
|
|
||||||
|
|
||||||
EqtTrace.Info(
|
|
||||||
"DesktopTestSourceHost.SetupHost(): resolver type: {0} , resolve type assembly: {1} ",
|
|
||||||
resolver.GetType().FullName,
|
|
||||||
resolver.GetType().Assembly.Location);
|
|
||||||
|
|
||||||
this.childDomainAssemblyResolver = (AssemblyResolver)resolver;
|
|
||||||
|
|
||||||
this.AddSearchDirectoriesSpecifiedInRunSettingsToAssemblyResolver(this.childDomainAssemblyResolver, Path.GetDirectoryName(this.sourceFileName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates an instance of a given type in the test source host.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type"> The type that needs to be created in the host. </param>
|
|
||||||
/// <param name="args">The arguments to pass to the constructor.
|
|
||||||
/// This array of arguments must match in number, order, and type the parameters of the constructor to invoke.
|
|
||||||
/// Pass in null for a constructor with no arguments.
|
|
||||||
/// </param>
|
|
||||||
/// <returns> An instance of the type created in the host. </returns>
|
|
||||||
/// <remarks> If a type is to be created in isolation then it needs to be a MarshalByRefObject. </remarks>
|
|
||||||
public object CreateInstanceForType(Type type, object[] args)
|
|
||||||
{
|
|
||||||
// Honor DisableAppDomain setting if it is present in runsettings
|
|
||||||
if (this.isAppDomainCreationDisabled)
|
|
||||||
{
|
|
||||||
return Activator.CreateInstance(type, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
return AppDomainUtilities.CreateInstance(this.domain, type, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (this.parentDomainAssemblyResolver != null)
|
|
||||||
{
|
|
||||||
this.parentDomainAssemblyResolver.Dispose();
|
|
||||||
this.parentDomainAssemblyResolver = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.childDomainAssemblyResolver != null)
|
|
||||||
{
|
|
||||||
this.childDomainAssemblyResolver.Dispose();
|
|
||||||
this.childDomainAssemblyResolver = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.domain != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.appDomain.Unload(this.domain);
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
// This happens usually when a test spawns off a thread and fails to clean it up.
|
|
||||||
EqtTrace.Error("DesktopTestSourceHost.Dispose(): The app domain running tests could not be unloaded. Exception: {0}", exception);
|
|
||||||
|
|
||||||
if (this.frameworkHandle != null)
|
|
||||||
{
|
|
||||||
// Let the test platform know that it should tear down the test host process
|
|
||||||
// since we have issues in unloading appdomain. We do so to avoid any assembly locking issues.
|
|
||||||
this.frameworkHandle.EnableShutdownAfterTestRun = true;
|
|
||||||
|
|
||||||
EqtTrace.Verbose("DesktopTestSourceHost.Dispose(): Notifying the test platform that the test host process should be shut down because the app domain running tests could not be unloaded successfully.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.domain = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ResetContext();
|
|
||||||
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets child-domain's appbase to point to appropriate location.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Appbase path that should be set for child appdomain</returns>
|
|
||||||
internal string GetAppBaseAsPerPlatform()
|
|
||||||
{
|
|
||||||
// The below logic of preferential setting the appdomains appbase is needed because:
|
|
||||||
// 1. We set this to the location of the test source if it is built for Full CLR -> Ideally this needs to be done in all situations.
|
|
||||||
// 2. We set this to the location where the current adapter is being picked up from for UWP and .Net Core scenarios -> This needs to be
|
|
||||||
// different especially for UWP because we use the desktop adapter(from %temp%\VisualStudioTestExplorerExtensions) itself for test discovery
|
|
||||||
// in IDE scenarios. If the app base is set to the test source location, discovery will not work because we drop the
|
|
||||||
// UWP platform service assembly at the test source location and since CLR starts looking for assemblies from the app base location,
|
|
||||||
// there would be a mismatch of platform service assemblies during discovery.
|
|
||||||
if (this.targetFrameworkVersion.Contains(PlatformServices.Constants.DotNetFrameWorkStringPrefix))
|
|
||||||
{
|
|
||||||
return Path.GetDirectoryName(this.sourceFileName) ?? Path.GetDirectoryName(typeof(TestSourceHost).Assembly.Location);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return Path.GetDirectoryName(typeof(TestSourceHost).Assembly.Location);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the probing paths to load the test assembly dependencies.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sourceFileName">
|
|
||||||
/// The source File Name.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="isPortableMode">
|
|
||||||
/// True if running in portable mode else false.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// A list of path.
|
|
||||||
/// </returns>
|
|
||||||
internal virtual List<string> GetResolutionPaths(string sourceFileName, bool isPortableMode)
|
|
||||||
{
|
|
||||||
List<string> resolutionPaths = new();
|
|
||||||
|
|
||||||
// Add path of test assembly in resolution path. Mostly will be used for resolving winmd.
|
|
||||||
resolutionPaths.Add(Path.GetDirectoryName(sourceFileName));
|
|
||||||
|
|
||||||
if (!isPortableMode)
|
|
||||||
{
|
|
||||||
EqtTrace.Info("DesktopTestSourceHost.GetResolutionPaths(): Not running in portable mode");
|
|
||||||
|
|
||||||
string pathToPublicAssemblies = VSInstallationUtilities.PathToPublicAssemblies;
|
|
||||||
if (!StringUtilities.IsNullOrWhiteSpace(pathToPublicAssemblies))
|
|
||||||
{
|
|
||||||
resolutionPaths.Add(pathToPublicAssemblies);
|
|
||||||
}
|
|
||||||
|
|
||||||
string pathToPrivateAssemblies = VSInstallationUtilities.PathToPrivateAssemblies;
|
|
||||||
if (!StringUtilities.IsNullOrWhiteSpace(pathToPrivateAssemblies))
|
|
||||||
{
|
|
||||||
resolutionPaths.Add(pathToPrivateAssemblies);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adding adapter folder to resolution paths
|
|
||||||
if (!resolutionPaths.Contains(Path.GetDirectoryName(typeof(TestSourceHost).Assembly.Location)))
|
|
||||||
{
|
|
||||||
resolutionPaths.Add(Path.GetDirectoryName(typeof(TestSourceHost).Assembly.Location));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adding TestPlatform folder to resolution paths
|
|
||||||
if (!resolutionPaths.Contains(Path.GetDirectoryName(typeof(AssemblyHelper).Assembly.Location)))
|
|
||||||
{
|
|
||||||
resolutionPaths.Add(Path.GetDirectoryName(typeof(AssemblyHelper).Assembly.Location));
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolutionPaths;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal virtual string GetTargetFrameworkVersionString(string sourceFileName)
|
|
||||||
{
|
|
||||||
return AppDomainUtilities.GetTargetFrameworkVersionString(sourceFileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetConfigFileForTestSource(string sourceFileName)
|
|
||||||
{
|
|
||||||
return new DeploymentUtility().GetConfigFile(sourceFileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets context required for running tests.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">
|
|
||||||
/// source parameter used for setting context
|
|
||||||
/// </param>
|
|
||||||
private void SetContext(string source)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(source))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Exception setWorkingDirectoryException = null;
|
|
||||||
this.currentDirectory = Environment.CurrentDirectory;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Environment.CurrentDirectory = Path.GetDirectoryName(source);
|
|
||||||
EqtTrace.Info("MSTestExecutor: Changed the working directory to {0}", Environment.CurrentDirectory);
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
setWorkingDirectoryException = ex;
|
|
||||||
}
|
|
||||||
catch (System.Security.SecurityException ex)
|
|
||||||
{
|
|
||||||
setWorkingDirectoryException = ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setWorkingDirectoryException != null)
|
|
||||||
{
|
|
||||||
EqtTrace.Error("MSTestExecutor.SetWorkingDirectory: Failed to set the working directory to '{0}'. {1}", Path.GetDirectoryName(source), setWorkingDirectoryException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resets the context as it was before calling SetContext()
|
|
||||||
/// </summary>
|
|
||||||
private void ResetContext()
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(this.currentDirectory))
|
|
||||||
{
|
|
||||||
Environment.CurrentDirectory = this.currentDirectory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddSearchDirectoriesSpecifiedInRunSettingsToAssemblyResolver(AssemblyResolver assemblyResolver, string baseDirectory)
|
|
||||||
{
|
|
||||||
// Check if user specified any adapter settings
|
|
||||||
MSTestAdapterSettings adapterSettings = MSTestSettingsProvider.Settings;
|
|
||||||
|
|
||||||
if (adapterSettings != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var additionalSearchDirectories = adapterSettings.GetDirectoryListWithRecursiveProperty(baseDirectory);
|
|
||||||
if (additionalSearchDirectories?.Count > 0)
|
|
||||||
{
|
|
||||||
assemblyResolver.AddSearchDirectoriesFromRunSetting(additionalSearchDirectories);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
EqtTrace.Error(
|
|
||||||
"DesktopTestSourceHost.AddSearchDirectoriesSpecifiedInRunSettingsToAssemblyResolver(): Exception hit while trying to set assembly resolver for domain. Exception : {0} \n Message : {1}",
|
|
||||||
exception,
|
|
||||||
exception.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning restore SA1649 // SA1649FileNameMustMatchTypeName
|
/// <summary>
|
||||||
|
/// Setup the isolation host.
|
||||||
|
/// </summary>
|
||||||
|
public void SetupHost()
|
||||||
|
{
|
||||||
|
List<string> resolutionPaths = this.GetResolutionPaths(this.sourceFileName, VSInstallationUtilities.IsCurrentProcessRunningInPortableMode());
|
||||||
|
|
||||||
|
if (EqtTrace.IsInfoEnabled)
|
||||||
|
{
|
||||||
|
EqtTrace.Info("DesktopTestSourceHost.SetupHost(): Creating assembly resolver with resolution paths {0}.", string.Join(",", resolutionPaths.ToArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case when DisableAppDomain setting is present in runsettings and no child-appdomain needs to be created
|
||||||
|
if (this.isAppDomainCreationDisabled)
|
||||||
|
{
|
||||||
|
this.parentDomainAssemblyResolver = new AssemblyResolver(resolutionPaths);
|
||||||
|
this.AddSearchDirectoriesSpecifiedInRunSettingsToAssemblyResolver(this.parentDomainAssemblyResolver, Path.GetDirectoryName(this.sourceFileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create child-appdomain and set assembly resolver on it
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Setup app-domain
|
||||||
|
var appDomainSetup = new AppDomainSetup();
|
||||||
|
this.targetFrameworkVersion = this.GetTargetFrameworkVersionString(this.sourceFileName);
|
||||||
|
AppDomainUtilities.SetAppDomainFrameworkVersionBasedOnTestSource(appDomainSetup, this.targetFrameworkVersion);
|
||||||
|
|
||||||
|
appDomainSetup.ApplicationBase = this.GetAppBaseAsPerPlatform();
|
||||||
|
var configFile = this.GetConfigFileForTestSource(this.sourceFileName);
|
||||||
|
AppDomainUtilities.SetConfigurationFile(appDomainSetup, configFile);
|
||||||
|
|
||||||
|
EqtTrace.Info("DesktopTestSourceHost.SetupHost(): Creating app-domain for source {0} with application base path {1}.", this.sourceFileName, appDomainSetup.ApplicationBase);
|
||||||
|
|
||||||
|
string domainName = string.Format("TestSourceHost: Enumerating source ({0})", this.sourceFileName);
|
||||||
|
this.domain = this.appDomain.CreateDomain(domainName, null, appDomainSetup);
|
||||||
|
|
||||||
|
// Load objectModel before creating assembly resolver otherwise in 3.5 process, we run into a recursive assembly resolution
|
||||||
|
// which is trigged by AppContainerUtilities.AttachEventToResolveWinmd method.
|
||||||
|
EqtTrace.SetupRemoteEqtTraceListeners(this.domain);
|
||||||
|
|
||||||
|
// Add an assembly resolver in the child app-domain...
|
||||||
|
Type assemblyResolverType = typeof(AssemblyResolver);
|
||||||
|
|
||||||
|
EqtTrace.Info("DesktopTestSourceHost.SetupHost(): assemblyenumerator location: {0} , fullname: {1} ", assemblyResolverType.Assembly.Location, assemblyResolverType.FullName);
|
||||||
|
|
||||||
|
var resolver = AppDomainUtilities.CreateInstance(
|
||||||
|
this.domain,
|
||||||
|
assemblyResolverType,
|
||||||
|
new object[] { resolutionPaths });
|
||||||
|
|
||||||
|
EqtTrace.Info(
|
||||||
|
"DesktopTestSourceHost.SetupHost(): resolver type: {0} , resolve type assembly: {1} ",
|
||||||
|
resolver.GetType().FullName,
|
||||||
|
resolver.GetType().Assembly.Location);
|
||||||
|
|
||||||
|
this.childDomainAssemblyResolver = (AssemblyResolver)resolver;
|
||||||
|
|
||||||
|
this.AddSearchDirectoriesSpecifiedInRunSettingsToAssemblyResolver(this.childDomainAssemblyResolver, Path.GetDirectoryName(this.sourceFileName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an instance of a given type in the test source host.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"> The type that needs to be created in the host. </param>
|
||||||
|
/// <param name="args">The arguments to pass to the constructor.
|
||||||
|
/// This array of arguments must match in number, order, and type the parameters of the constructor to invoke.
|
||||||
|
/// Pass in null for a constructor with no arguments.
|
||||||
|
/// </param>
|
||||||
|
/// <returns> An instance of the type created in the host. </returns>
|
||||||
|
/// <remarks> If a type is to be created in isolation then it needs to be a MarshalByRefObject. </remarks>
|
||||||
|
public object CreateInstanceForType(Type type, object[] args)
|
||||||
|
{
|
||||||
|
// Honor DisableAppDomain setting if it is present in runsettings
|
||||||
|
if (this.isAppDomainCreationDisabled)
|
||||||
|
{
|
||||||
|
return Activator.CreateInstance(type, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return AppDomainUtilities.CreateInstance(this.domain, type, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (this.parentDomainAssemblyResolver != null)
|
||||||
|
{
|
||||||
|
this.parentDomainAssemblyResolver.Dispose();
|
||||||
|
this.parentDomainAssemblyResolver = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.childDomainAssemblyResolver != null)
|
||||||
|
{
|
||||||
|
this.childDomainAssemblyResolver.Dispose();
|
||||||
|
this.childDomainAssemblyResolver = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.domain != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.appDomain.Unload(this.domain);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
// This happens usually when a test spawns off a thread and fails to clean it up.
|
||||||
|
EqtTrace.Error("DesktopTestSourceHost.Dispose(): The app domain running tests could not be unloaded. Exception: {0}", exception);
|
||||||
|
|
||||||
|
if (this.frameworkHandle != null)
|
||||||
|
{
|
||||||
|
// Let the test platform know that it should tear down the test host process
|
||||||
|
// since we have issues in unloading appdomain. We do so to avoid any assembly locking issues.
|
||||||
|
this.frameworkHandle.EnableShutdownAfterTestRun = true;
|
||||||
|
|
||||||
|
EqtTrace.Verbose("DesktopTestSourceHost.Dispose(): Notifying the test platform that the test host process should be shut down because the app domain running tests could not be unloaded successfully.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.domain = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ResetContext();
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets child-domain's appbase to point to appropriate location.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Appbase path that should be set for child appdomain</returns>
|
||||||
|
internal string GetAppBaseAsPerPlatform()
|
||||||
|
{
|
||||||
|
// The below logic of preferential setting the appdomains appbase is needed because:
|
||||||
|
// 1. We set this to the location of the test source if it is built for Full CLR -> Ideally this needs to be done in all situations.
|
||||||
|
// 2. We set this to the location where the current adapter is being picked up from for UWP and .Net Core scenarios -> This needs to be
|
||||||
|
// different especially for UWP because we use the desktop adapter(from %temp%\VisualStudioTestExplorerExtensions) itself for test discovery
|
||||||
|
// in IDE scenarios. If the app base is set to the test source location, discovery will not work because we drop the
|
||||||
|
// UWP platform service assembly at the test source location and since CLR starts looking for assemblies from the app base location,
|
||||||
|
// there would be a mismatch of platform service assemblies during discovery.
|
||||||
|
if (this.targetFrameworkVersion.Contains(PlatformServices.Constants.DotNetFrameWorkStringPrefix))
|
||||||
|
{
|
||||||
|
return Path.GetDirectoryName(this.sourceFileName) ?? Path.GetDirectoryName(typeof(TestSourceHost).Assembly.Location);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Path.GetDirectoryName(typeof(TestSourceHost).Assembly.Location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the probing paths to load the test assembly dependencies.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sourceFileName">
|
||||||
|
/// The source File Name.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="isPortableMode">
|
||||||
|
/// True if running in portable mode else false.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// A list of path.
|
||||||
|
/// </returns>
|
||||||
|
internal virtual List<string> GetResolutionPaths(string sourceFileName, bool isPortableMode)
|
||||||
|
{
|
||||||
|
List<string> resolutionPaths = new();
|
||||||
|
|
||||||
|
// Add path of test assembly in resolution path. Mostly will be used for resolving winmd.
|
||||||
|
resolutionPaths.Add(Path.GetDirectoryName(sourceFileName));
|
||||||
|
|
||||||
|
if (!isPortableMode)
|
||||||
|
{
|
||||||
|
EqtTrace.Info("DesktopTestSourceHost.GetResolutionPaths(): Not running in portable mode");
|
||||||
|
|
||||||
|
string pathToPublicAssemblies = VSInstallationUtilities.PathToPublicAssemblies;
|
||||||
|
if (!StringUtilities.IsNullOrWhiteSpace(pathToPublicAssemblies))
|
||||||
|
{
|
||||||
|
resolutionPaths.Add(pathToPublicAssemblies);
|
||||||
|
}
|
||||||
|
|
||||||
|
string pathToPrivateAssemblies = VSInstallationUtilities.PathToPrivateAssemblies;
|
||||||
|
if (!StringUtilities.IsNullOrWhiteSpace(pathToPrivateAssemblies))
|
||||||
|
{
|
||||||
|
resolutionPaths.Add(pathToPrivateAssemblies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding adapter folder to resolution paths
|
||||||
|
if (!resolutionPaths.Contains(Path.GetDirectoryName(typeof(TestSourceHost).Assembly.Location)))
|
||||||
|
{
|
||||||
|
resolutionPaths.Add(Path.GetDirectoryName(typeof(TestSourceHost).Assembly.Location));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding TestPlatform folder to resolution paths
|
||||||
|
if (!resolutionPaths.Contains(Path.GetDirectoryName(typeof(AssemblyHelper).Assembly.Location)))
|
||||||
|
{
|
||||||
|
resolutionPaths.Add(Path.GetDirectoryName(typeof(AssemblyHelper).Assembly.Location));
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolutionPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal virtual string GetTargetFrameworkVersionString(string sourceFileName)
|
||||||
|
{
|
||||||
|
return AppDomainUtilities.GetTargetFrameworkVersionString(sourceFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetConfigFileForTestSource(string sourceFileName)
|
||||||
|
{
|
||||||
|
return new DeploymentUtility().GetConfigFile(sourceFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets context required for running tests.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">
|
||||||
|
/// source parameter used for setting context
|
||||||
|
/// </param>
|
||||||
|
private void SetContext(string source)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(source))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Exception setWorkingDirectoryException = null;
|
||||||
|
this.currentDirectory = Environment.CurrentDirectory;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Environment.CurrentDirectory = Path.GetDirectoryName(source);
|
||||||
|
EqtTrace.Info("MSTestExecutor: Changed the working directory to {0}", Environment.CurrentDirectory);
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
setWorkingDirectoryException = ex;
|
||||||
|
}
|
||||||
|
catch (System.Security.SecurityException ex)
|
||||||
|
{
|
||||||
|
setWorkingDirectoryException = ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setWorkingDirectoryException != null)
|
||||||
|
{
|
||||||
|
EqtTrace.Error("MSTestExecutor.SetWorkingDirectory: Failed to set the working directory to '{0}'. {1}", Path.GetDirectoryName(source), setWorkingDirectoryException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the context as it was before calling SetContext()
|
||||||
|
/// </summary>
|
||||||
|
private void ResetContext()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(this.currentDirectory))
|
||||||
|
{
|
||||||
|
Environment.CurrentDirectory = this.currentDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddSearchDirectoriesSpecifiedInRunSettingsToAssemblyResolver(AssemblyResolver assemblyResolver, string baseDirectory)
|
||||||
|
{
|
||||||
|
// Check if user specified any adapter settings
|
||||||
|
MSTestAdapterSettings adapterSettings = MSTestSettingsProvider.Settings;
|
||||||
|
|
||||||
|
if (adapterSettings != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var additionalSearchDirectories = adapterSettings.GetDirectoryListWithRecursiveProperty(baseDirectory);
|
||||||
|
if (additionalSearchDirectories?.Count > 0)
|
||||||
|
{
|
||||||
|
assemblyResolver.AddSearchDirectoriesFromRunSetting(additionalSearchDirectories);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
EqtTrace.Error(
|
||||||
|
"DesktopTestSourceHost.AddSearchDirectoriesSpecifiedInRunSettingsToAssemblyResolver(): Exception hit while trying to set assembly resolver for domain. Exception : {0} \n Message : {1}",
|
||||||
|
exception,
|
||||||
|
exception.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning restore SA1649 // SA1649FileNameMustMatchTypeName
|
||||||
|
|
|
@ -1,118 +1,117 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
|
||||||
#pragma warning disable SA1649 // SA1649FileNameMustMatchTypeName
|
#pragma warning disable SA1649 // SA1649FileNameMustMatchTypeName
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This service is responsible for any Async operations specific to a platform.
|
||||||
|
/// </summary>
|
||||||
|
public class ThreadOperations : IThreadOperations
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This service is responsible for any Async operations specific to a platform.
|
/// Execute the given action synchronously on a background thread in the given timeout.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ThreadOperations : IThreadOperations
|
/// <param name="action">The action to execute.</param>
|
||||||
|
/// <param name="timeout">Timeout for the specified action in milliseconds.</param>
|
||||||
|
/// <param name="cancelToken">Token to cancel the execution</param>
|
||||||
|
/// <returns>Returns true if the action executed before the timeout. returns false otherwise.</returns>
|
||||||
|
public bool Execute(Action action, int timeout, CancellationToken cancelToken)
|
||||||
{
|
{
|
||||||
/// <summary>
|
bool executionAborted = false;
|
||||||
/// Execute the given action synchronously on a background thread in the given timeout.
|
Thread executionThread = new(new ThreadStart(action))
|
||||||
/// </summary>
|
|
||||||
/// <param name="action">The action to execute.</param>
|
|
||||||
/// <param name="timeout">Timeout for the specified action in milliseconds.</param>
|
|
||||||
/// <param name="cancelToken">Token to cancel the execution</param>
|
|
||||||
/// <returns>Returns true if the action executed before the timeout. returns false otherwise.</returns>
|
|
||||||
public bool Execute(Action action, int timeout, CancellationToken cancelToken)
|
|
||||||
{
|
{
|
||||||
bool executionAborted = false;
|
IsBackground = true,
|
||||||
Thread executionThread = new(new ThreadStart(action))
|
Name = "MSTestAdapter Thread"
|
||||||
{
|
};
|
||||||
IsBackground = true,
|
|
||||||
Name = "MSTestAdapter Thread"
|
|
||||||
};
|
|
||||||
|
|
||||||
executionThread.SetApartmentState(Thread.CurrentThread.GetApartmentState());
|
executionThread.SetApartmentState(Thread.CurrentThread.GetApartmentState());
|
||||||
executionThread.Start();
|
executionThread.Start();
|
||||||
cancelToken.Register(() =>
|
cancelToken.Register(() =>
|
||||||
{
|
{
|
||||||
executionAborted = true;
|
executionAborted = true;
|
||||||
AbortThread(executionThread);
|
AbortThread(executionThread);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (JoinThread(timeout, executionThread))
|
if (JoinThread(timeout, executionThread))
|
||||||
|
{
|
||||||
|
if (executionAborted)
|
||||||
{
|
{
|
||||||
if (executionAborted)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Successfully completed
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (executionAborted)
|
|
||||||
{
|
|
||||||
// Execution aborted due to user choice
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Timed out
|
|
||||||
AbortThread(executionThread);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Successfully completed
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
else if (executionAborted)
|
||||||
/// <summary>
|
|
||||||
/// Execute an action with handling for Thread Aborts (if possible) so the main thread of the adapter does not die.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="action"> The action to execute. </param>
|
|
||||||
public void ExecuteWithAbortSafety(Action action)
|
|
||||||
{
|
{
|
||||||
try
|
// Execution aborted due to user choice
|
||||||
{
|
|
||||||
action.Invoke();
|
|
||||||
}
|
|
||||||
catch (ThreadAbortException exception)
|
|
||||||
{
|
|
||||||
Thread.ResetAbort();
|
|
||||||
|
|
||||||
// Throwing an exception so that the test is marked as failed.
|
|
||||||
// This is a TargetInvocation exception because we want just the ThreadAbort exception to be shown to the user and not something we create here.
|
|
||||||
// TargetInvocation exceptions are stripped off by the test failure handler surfacing the actual exception.
|
|
||||||
throw new TargetInvocationException(exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool JoinThread(int timeout, Thread executionThread)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return executionThread.Join(timeout);
|
|
||||||
}
|
|
||||||
catch (ThreadStateException)
|
|
||||||
{
|
|
||||||
// Join was called on a thread not started
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
private static void AbortThread(Thread executionThread)
|
|
||||||
{
|
{
|
||||||
try
|
// Timed out
|
||||||
{
|
AbortThread(executionThread);
|
||||||
// Abort test thread after timeout.
|
return false;
|
||||||
executionThread.Abort();
|
}
|
||||||
}
|
}
|
||||||
catch (ThreadStateException)
|
|
||||||
{
|
/// <summary>
|
||||||
// Catch and discard ThreadStateException. If Abort is called on a thread that has been suspended,
|
/// Execute an action with handling for Thread Aborts (if possible) so the main thread of the adapter does not die.
|
||||||
// a ThreadStateException is thrown in the thread that called Abort,
|
/// </summary>
|
||||||
// and AbortRequested is added to the ThreadState property of the thread being aborted.
|
/// <param name="action"> The action to execute. </param>
|
||||||
// A ThreadAbortException is not thrown in the suspended thread until Resume is called.
|
public void ExecuteWithAbortSafety(Action action)
|
||||||
}
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action.Invoke();
|
||||||
|
}
|
||||||
|
catch (ThreadAbortException exception)
|
||||||
|
{
|
||||||
|
Thread.ResetAbort();
|
||||||
|
|
||||||
|
// Throwing an exception so that the test is marked as failed.
|
||||||
|
// This is a TargetInvocation exception because we want just the ThreadAbort exception to be shown to the user and not something we create here.
|
||||||
|
// TargetInvocation exceptions are stripped off by the test failure handler surfacing the actual exception.
|
||||||
|
throw new TargetInvocationException(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool JoinThread(int timeout, Thread executionThread)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return executionThread.Join(timeout);
|
||||||
|
}
|
||||||
|
catch (ThreadStateException)
|
||||||
|
{
|
||||||
|
// Join was called on a thread not started
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AbortThread(Thread executionThread)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Abort test thread after timeout.
|
||||||
|
executionThread.Abort();
|
||||||
|
}
|
||||||
|
catch (ThreadStateException)
|
||||||
|
{
|
||||||
|
// Catch and discard ThreadStateException. If Abort is called on a thread that has been suspended,
|
||||||
|
// a ThreadStateException is thrown in the thread that called Abort,
|
||||||
|
// and AbortRequested is added to the ThreadState property of the thread being aborted.
|
||||||
|
// A ThreadAbortException is not thrown in the suspended thread until Resume is called.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#pragma warning restore SA1649 // SA1649FileNameMustMatchTypeName
|
|
||||||
}
|
}
|
||||||
|
#pragma warning restore SA1649 // SA1649FileNameMustMatchTypeName
|
||||||
|
|
|
@ -1,249 +1,248 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities;
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.IO;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment;
|
using System;
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Utilities for AppDomain
|
||||||
|
/// </summary>
|
||||||
|
internal static class AppDomainUtilities
|
||||||
|
{
|
||||||
|
private const string ObjectModelVersionBuiltAgainst = "11.0.0.0";
|
||||||
|
|
||||||
|
private static Version defaultVersion = new();
|
||||||
|
private static Version version45 = new("4.5");
|
||||||
|
|
||||||
|
private static XmlUtilities xmlUtilities = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Utilities for AppDomain
|
/// Gets or sets the Xml Utilities instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class AppDomainUtilities
|
internal static XmlUtilities XmlUtilities
|
||||||
{
|
{
|
||||||
private const string ObjectModelVersionBuiltAgainst = "11.0.0.0";
|
get
|
||||||
|
|
||||||
private static Version defaultVersion = new();
|
|
||||||
private static Version version45 = new("4.5");
|
|
||||||
|
|
||||||
private static XmlUtilities xmlUtilities = null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Xml Utilities instance.
|
|
||||||
/// </summary>
|
|
||||||
internal static XmlUtilities XmlUtilities
|
|
||||||
{
|
{
|
||||||
get
|
xmlUtilities ??= new XmlUtilities();
|
||||||
{
|
|
||||||
xmlUtilities ??= new XmlUtilities();
|
|
||||||
|
|
||||||
return xmlUtilities;
|
return xmlUtilities;
|
||||||
}
|
|
||||||
|
|
||||||
set
|
|
||||||
{
|
|
||||||
xmlUtilities = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
set
|
||||||
/// Set the target framework for app domain setup if target framework of dll is > 4.5
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="setup">AppdomainSetup for app domain creation</param>
|
|
||||||
/// <param name="frameworkVersionString">The target framework version of the test source.</param>
|
|
||||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")]
|
|
||||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here.")]
|
|
||||||
internal static void SetAppDomainFrameworkVersionBasedOnTestSource(AppDomainSetup setup, string frameworkVersionString)
|
|
||||||
{
|
{
|
||||||
if (GetTargetFrameworkVersionFromVersionString(frameworkVersionString).CompareTo(version45) > 0)
|
xmlUtilities = value;
|
||||||
{
|
|
||||||
PropertyInfo pInfo = typeof(AppDomainSetup).GetProperty(PlatformServices.Constants.TargetFrameworkName);
|
|
||||||
if (pInfo != null)
|
|
||||||
{
|
|
||||||
pInfo.SetValue(setup, frameworkVersionString, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get target framework version string from the given dll
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="testSourcePath">
|
|
||||||
/// The path of the dll
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// Framework string
|
|
||||||
/// TODO: Need to add components/E2E tests to cover these scenarios.
|
|
||||||
/// </returns>
|
|
||||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")]
|
|
||||||
internal static string GetTargetFrameworkVersionString(string testSourcePath)
|
|
||||||
{
|
|
||||||
AppDomainSetup appDomainSetup = new();
|
|
||||||
|
|
||||||
appDomainSetup.LoaderOptimization = LoaderOptimization.MultiDomainHost;
|
|
||||||
|
|
||||||
AppDomainUtilities.SetConfigurationFile(appDomainSetup, new DeploymentUtility().GetConfigFile(testSourcePath));
|
|
||||||
|
|
||||||
if (File.Exists(testSourcePath))
|
|
||||||
{
|
|
||||||
AppDomain appDomain = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
appDomain = AppDomain.CreateDomain("Framework Version String Domain", null, appDomainSetup);
|
|
||||||
|
|
||||||
// Wire the eqttrace logs in this domain to the current domain.
|
|
||||||
EqtTrace.SetupRemoteEqtTraceListeners(appDomain);
|
|
||||||
|
|
||||||
// Add an assembly resolver to resolve ObjectModel or any Test Platform dependencies.
|
|
||||||
// Not moving to IMetaDataImport APIs because the time taken for this operation is <20 ms and
|
|
||||||
// IMetaDataImport needs COM registration which is not a guarantee in Dev15.
|
|
||||||
var assemblyResolverType = typeof(AssemblyResolver);
|
|
||||||
|
|
||||||
var resolutionPaths = new List<string> { Path.GetDirectoryName(typeof(TestCase).Assembly.Location) };
|
|
||||||
resolutionPaths.Add(Path.GetDirectoryName(testSourcePath));
|
|
||||||
|
|
||||||
AppDomainUtilities.CreateInstance(
|
|
||||||
appDomain,
|
|
||||||
assemblyResolverType,
|
|
||||||
new object[] { resolutionPaths });
|
|
||||||
|
|
||||||
var assemblyLoadWorker =
|
|
||||||
(AssemblyLoadWorker)AppDomainUtilities.CreateInstance(
|
|
||||||
appDomain,
|
|
||||||
typeof(AssemblyLoadWorker),
|
|
||||||
null);
|
|
||||||
|
|
||||||
return assemblyLoadWorker.GetTargetFrameworkVersionStringFromPath(testSourcePath);
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
if (EqtTrace.IsErrorEnabled)
|
|
||||||
{
|
|
||||||
EqtTrace.Error(exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (appDomain != null)
|
|
||||||
{
|
|
||||||
AppDomain.Unload(appDomain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set configuration file on the parameter appDomain.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="appDomainSetup"> The app Domain Setup. </param>
|
|
||||||
/// <param name="testSourceConfigFile"> The test Source Config File. </param>
|
|
||||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
|
||||||
internal static void SetConfigurationFile(AppDomainSetup appDomainSetup, string testSourceConfigFile)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(testSourceConfigFile))
|
|
||||||
{
|
|
||||||
if (EqtTrace.IsInfoEnabled)
|
|
||||||
{
|
|
||||||
EqtTrace.Info("UnitTestAdapter: Using configuration file {0} to setup appdomain for test source {1}.", testSourceConfigFile, Path.GetFileNameWithoutExtension(testSourceConfigFile));
|
|
||||||
}
|
|
||||||
|
|
||||||
appDomainSetup.ConfigurationFile = Path.GetFullPath(testSourceConfigFile);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Add redirection of the built 11.0 Object Model assembly to the current version if that is not 11.0
|
|
||||||
var currentVersionOfObjectModel = typeof(TestCase).Assembly.GetName().Version.ToString();
|
|
||||||
if (!string.Equals(currentVersionOfObjectModel, ObjectModelVersionBuiltAgainst))
|
|
||||||
{
|
|
||||||
var assemblyName = typeof(TestCase).Assembly.GetName();
|
|
||||||
var configurationBytes =
|
|
||||||
XmlUtilities.AddAssemblyRedirection(
|
|
||||||
testSourceConfigFile,
|
|
||||||
assemblyName,
|
|
||||||
ObjectModelVersionBuiltAgainst,
|
|
||||||
assemblyName.Version.ToString());
|
|
||||||
appDomainSetup.SetConfigurationBytes(configurationBytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
if (EqtTrace.IsErrorEnabled)
|
|
||||||
{
|
|
||||||
EqtTrace.Error("Exception hit while adding binding redirects to test source config file. Exception : {0}", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Use the current domains configuration setting.
|
|
||||||
appDomainSetup.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static object CreateInstance(AppDomain appDomain, Type type, object[] arguments)
|
|
||||||
{
|
|
||||||
Debug.Assert(appDomain != null, "appDomain is null");
|
|
||||||
Debug.Assert(type != null, "type is null");
|
|
||||||
|
|
||||||
var typeAssemblyLocation = type.Assembly.Location;
|
|
||||||
var fullFilePath = typeAssemblyLocation == null ? null : Path.Combine(appDomain.SetupInformation.ApplicationBase, Path.GetFileName(typeAssemblyLocation));
|
|
||||||
|
|
||||||
if (fullFilePath == null || File.Exists(fullFilePath))
|
|
||||||
{
|
|
||||||
// If the assembly exists in the app base directory, load it from there itself.
|
|
||||||
// Even if it does not exist, Create the type in the default Load Context and let the CLR resolve the assembly path.
|
|
||||||
// This would load the assembly in the Default Load context.
|
|
||||||
return appDomain.CreateInstanceAndUnwrap(
|
|
||||||
type.Assembly.FullName,
|
|
||||||
type.FullName,
|
|
||||||
false,
|
|
||||||
BindingFlags.Default,
|
|
||||||
null,
|
|
||||||
arguments,
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// This means that the file is not present in the app base directory. Load it from Path instead.
|
|
||||||
// NOTE: We expect that all types that we are creating from here are types we know the location for.
|
|
||||||
// This would load the assembly in the Load-From context.
|
|
||||||
// While the above if condition is satisfied for most common cases, there could be a case where the adapter dlls
|
|
||||||
// do not get copied over to where the test assembly is, in which case we load them from where the parent AppDomain is picking them up from.
|
|
||||||
return appDomain.CreateInstanceFromAndUnwrap(
|
|
||||||
typeAssemblyLocation,
|
|
||||||
type.FullName,
|
|
||||||
false,
|
|
||||||
BindingFlags.Default,
|
|
||||||
null,
|
|
||||||
arguments,
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the Version for the target framework version string
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="version">Target framework string</param>
|
|
||||||
/// <returns>Framework Version</returns>
|
|
||||||
internal static Version GetTargetFrameworkVersionFromVersionString(string version)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (version.Length > PlatformServices.Constants.DotNetFrameWorkStringPrefix.Length + 1)
|
|
||||||
{
|
|
||||||
string versionPart = version.Substring(PlatformServices.Constants.DotNetFrameWorkStringPrefix.Length + 1);
|
|
||||||
return new Version(versionPart);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (FormatException ex)
|
|
||||||
{
|
|
||||||
// if the version is ".NETPortable,Version=v4.5,Profile=Profile259", then above code will throw exception.
|
|
||||||
EqtTrace.Warning(string.Format("AppDomainUtilities.GetTargetFrameworkVersionFromVersionString: Could not create version object from version string '{0}' due to error '{1}':", version, ex.Message));
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultVersion;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the target framework for app domain setup if target framework of dll is > 4.5
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="setup">AppdomainSetup for app domain creation</param>
|
||||||
|
/// <param name="frameworkVersionString">The target framework version of the test source.</param>
|
||||||
|
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")]
|
||||||
|
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here.")]
|
||||||
|
internal static void SetAppDomainFrameworkVersionBasedOnTestSource(AppDomainSetup setup, string frameworkVersionString)
|
||||||
|
{
|
||||||
|
if (GetTargetFrameworkVersionFromVersionString(frameworkVersionString).CompareTo(version45) > 0)
|
||||||
|
{
|
||||||
|
PropertyInfo pInfo = typeof(AppDomainSetup).GetProperty(PlatformServices.Constants.TargetFrameworkName);
|
||||||
|
if (pInfo != null)
|
||||||
|
{
|
||||||
|
pInfo.SetValue(setup, frameworkVersionString, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get target framework version string from the given dll
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="testSourcePath">
|
||||||
|
/// The path of the dll
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Framework string
|
||||||
|
/// TODO: Need to add components/E2E tests to cover these scenarios.
|
||||||
|
/// </returns>
|
||||||
|
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")]
|
||||||
|
internal static string GetTargetFrameworkVersionString(string testSourcePath)
|
||||||
|
{
|
||||||
|
AppDomainSetup appDomainSetup = new();
|
||||||
|
|
||||||
|
appDomainSetup.LoaderOptimization = LoaderOptimization.MultiDomainHost;
|
||||||
|
|
||||||
|
AppDomainUtilities.SetConfigurationFile(appDomainSetup, new DeploymentUtility().GetConfigFile(testSourcePath));
|
||||||
|
|
||||||
|
if (File.Exists(testSourcePath))
|
||||||
|
{
|
||||||
|
AppDomain appDomain = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
appDomain = AppDomain.CreateDomain("Framework Version String Domain", null, appDomainSetup);
|
||||||
|
|
||||||
|
// Wire the eqttrace logs in this domain to the current domain.
|
||||||
|
EqtTrace.SetupRemoteEqtTraceListeners(appDomain);
|
||||||
|
|
||||||
|
// Add an assembly resolver to resolve ObjectModel or any Test Platform dependencies.
|
||||||
|
// Not moving to IMetaDataImport APIs because the time taken for this operation is <20 ms and
|
||||||
|
// IMetaDataImport needs COM registration which is not a guarantee in Dev15.
|
||||||
|
var assemblyResolverType = typeof(AssemblyResolver);
|
||||||
|
|
||||||
|
var resolutionPaths = new List<string> { Path.GetDirectoryName(typeof(TestCase).Assembly.Location) };
|
||||||
|
resolutionPaths.Add(Path.GetDirectoryName(testSourcePath));
|
||||||
|
|
||||||
|
AppDomainUtilities.CreateInstance(
|
||||||
|
appDomain,
|
||||||
|
assemblyResolverType,
|
||||||
|
new object[] { resolutionPaths });
|
||||||
|
|
||||||
|
var assemblyLoadWorker =
|
||||||
|
(AssemblyLoadWorker)AppDomainUtilities.CreateInstance(
|
||||||
|
appDomain,
|
||||||
|
typeof(AssemblyLoadWorker),
|
||||||
|
null);
|
||||||
|
|
||||||
|
return assemblyLoadWorker.GetTargetFrameworkVersionStringFromPath(testSourcePath);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
if (EqtTrace.IsErrorEnabled)
|
||||||
|
{
|
||||||
|
EqtTrace.Error(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (appDomain != null)
|
||||||
|
{
|
||||||
|
AppDomain.Unload(appDomain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set configuration file on the parameter appDomain.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="appDomainSetup"> The app Domain Setup. </param>
|
||||||
|
/// <param name="testSourceConfigFile"> The test Source Config File. </param>
|
||||||
|
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
||||||
|
internal static void SetConfigurationFile(AppDomainSetup appDomainSetup, string testSourceConfigFile)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(testSourceConfigFile))
|
||||||
|
{
|
||||||
|
if (EqtTrace.IsInfoEnabled)
|
||||||
|
{
|
||||||
|
EqtTrace.Info("UnitTestAdapter: Using configuration file {0} to setup appdomain for test source {1}.", testSourceConfigFile, Path.GetFileNameWithoutExtension(testSourceConfigFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
appDomainSetup.ConfigurationFile = Path.GetFullPath(testSourceConfigFile);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Add redirection of the built 11.0 Object Model assembly to the current version if that is not 11.0
|
||||||
|
var currentVersionOfObjectModel = typeof(TestCase).Assembly.GetName().Version.ToString();
|
||||||
|
if (!string.Equals(currentVersionOfObjectModel, ObjectModelVersionBuiltAgainst))
|
||||||
|
{
|
||||||
|
var assemblyName = typeof(TestCase).Assembly.GetName();
|
||||||
|
var configurationBytes =
|
||||||
|
XmlUtilities.AddAssemblyRedirection(
|
||||||
|
testSourceConfigFile,
|
||||||
|
assemblyName,
|
||||||
|
ObjectModelVersionBuiltAgainst,
|
||||||
|
assemblyName.Version.ToString());
|
||||||
|
appDomainSetup.SetConfigurationBytes(configurationBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (EqtTrace.IsErrorEnabled)
|
||||||
|
{
|
||||||
|
EqtTrace.Error("Exception hit while adding binding redirects to test source config file. Exception : {0}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Use the current domains configuration setting.
|
||||||
|
appDomainSetup.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static object CreateInstance(AppDomain appDomain, Type type, object[] arguments)
|
||||||
|
{
|
||||||
|
Debug.Assert(appDomain != null, "appDomain is null");
|
||||||
|
Debug.Assert(type != null, "type is null");
|
||||||
|
|
||||||
|
var typeAssemblyLocation = type.Assembly.Location;
|
||||||
|
var fullFilePath = typeAssemblyLocation == null ? null : Path.Combine(appDomain.SetupInformation.ApplicationBase, Path.GetFileName(typeAssemblyLocation));
|
||||||
|
|
||||||
|
if (fullFilePath == null || File.Exists(fullFilePath))
|
||||||
|
{
|
||||||
|
// If the assembly exists in the app base directory, load it from there itself.
|
||||||
|
// Even if it does not exist, Create the type in the default Load Context and let the CLR resolve the assembly path.
|
||||||
|
// This would load the assembly in the Default Load context.
|
||||||
|
return appDomain.CreateInstanceAndUnwrap(
|
||||||
|
type.Assembly.FullName,
|
||||||
|
type.FullName,
|
||||||
|
false,
|
||||||
|
BindingFlags.Default,
|
||||||
|
null,
|
||||||
|
arguments,
|
||||||
|
null,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This means that the file is not present in the app base directory. Load it from Path instead.
|
||||||
|
// NOTE: We expect that all types that we are creating from here are types we know the location for.
|
||||||
|
// This would load the assembly in the Load-From context.
|
||||||
|
// While the above if condition is satisfied for most common cases, there could be a case where the adapter dlls
|
||||||
|
// do not get copied over to where the test assembly is, in which case we load them from where the parent AppDomain is picking them up from.
|
||||||
|
return appDomain.CreateInstanceFromAndUnwrap(
|
||||||
|
typeAssemblyLocation,
|
||||||
|
type.FullName,
|
||||||
|
false,
|
||||||
|
BindingFlags.Default,
|
||||||
|
null,
|
||||||
|
arguments,
|
||||||
|
null,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the Version for the target framework version string
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="version">Target framework string</param>
|
||||||
|
/// <returns>Framework Version</returns>
|
||||||
|
internal static Version GetTargetFrameworkVersionFromVersionString(string version)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (version.Length > PlatformServices.Constants.DotNetFrameWorkStringPrefix.Length + 1)
|
||||||
|
{
|
||||||
|
string versionPart = version.Substring(PlatformServices.Constants.DotNetFrameWorkStringPrefix.Length + 1);
|
||||||
|
return new Version(versionPart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (FormatException ex)
|
||||||
|
{
|
||||||
|
// if the version is ".NETPortable,Version=v4.5,Profile=Profile259", then above code will throw exception.
|
||||||
|
EqtTrace.Warning(string.Format("AppDomainUtilities.GetTargetFrameworkVersionFromVersionString: Could not create version object from version string '{0}' due to error '{1}':", version, ex.Message));
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultVersion;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,23 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Security.Policy;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Abstraction over the AppDomain APIs.
|
||||||
|
/// </summary>
|
||||||
|
internal class AppDomainWrapper : IAppDomain
|
||||||
{
|
{
|
||||||
using System;
|
public AppDomain CreateDomain(string friendlyName, Evidence securityInfo, AppDomainSetup info)
|
||||||
using System.Security.Policy;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Abstraction over the AppDomain APIs.
|
|
||||||
/// </summary>
|
|
||||||
internal class AppDomainWrapper : IAppDomain
|
|
||||||
{
|
{
|
||||||
public AppDomain CreateDomain(string friendlyName, Evidence securityInfo, AppDomainSetup info)
|
return AppDomain.CreateDomain(friendlyName, securityInfo, info);
|
||||||
{
|
}
|
||||||
return AppDomain.CreateDomain(friendlyName, securityInfo, info);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Unload(AppDomain appDomain)
|
public void Unload(AppDomain appDomain)
|
||||||
{
|
{
|
||||||
AppDomain.Unload(appDomain);
|
AppDomain.Unload(appDomain);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,279 +1,278 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities;
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment;
|
using System;
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Utility for assembly specific functionality.
|
||||||
|
/// </summary>
|
||||||
|
internal class AssemblyUtility : IAssemblyUtility
|
||||||
|
{
|
||||||
|
private static Dictionary<string, object> cultures;
|
||||||
|
private readonly string[] assemblyExtensions = new string[] { ".dll", ".exe" };
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Utility for assembly specific functionality.
|
/// Gets all supported culture names in Keys. The Values are always null.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class AssemblyUtility : IAssemblyUtility
|
private static Dictionary<string, object> Cultures
|
||||||
{
|
{
|
||||||
private static Dictionary<string, object> cultures;
|
get
|
||||||
private readonly string[] assemblyExtensions = new string[] { ".dll", ".exe" };
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets all supported culture names in Keys. The Values are always null.
|
|
||||||
/// </summary>
|
|
||||||
private static Dictionary<string, object> Cultures
|
|
||||||
{
|
{
|
||||||
get
|
if (cultures == null)
|
||||||
{
|
{
|
||||||
if (cultures == null)
|
cultures = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var info in CultureInfo.GetCultures(CultureTypes.AllCultures))
|
||||||
{
|
{
|
||||||
cultures = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
cultures.Add(info.Name, null);
|
||||||
foreach (var info in CultureInfo.GetCultures(CultureTypes.AllCultures))
|
|
||||||
{
|
|
||||||
cultures.Add(info.Name, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cultures;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Loads an assembly into the reflection-only context, given its path.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assemblyPath">The path of the file that contains the manifest of the assembly.</param>
|
|
||||||
/// <returns>The loaded assembly.</returns>
|
|
||||||
public Assembly ReflectionOnlyLoadFrom(string assemblyPath)
|
|
||||||
{
|
|
||||||
return Assembly.ReflectionOnlyLoadFrom(assemblyPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Loads an assembly into the reflection-only context, given its display name.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assemblyString">The display name of the assembly, as returned by the System.Reflection.AssemblyName.FullName property.</param>
|
|
||||||
/// <returns>The loaded assembly.</returns>
|
|
||||||
public Assembly ReflectionOnlyLoad(string assemblyString)
|
|
||||||
{
|
|
||||||
return Assembly.ReflectionOnlyLoad(assemblyString);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether file extension is an assembly file extension.
|
|
||||||
/// Returns true for .exe and .dll, otherwise false.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="extensionWithLeadingDot"> Extension containing leading dot, e.g. ".exe". </param>
|
|
||||||
/// <remarks> Path.GetExtension() returns extension with leading dot. </remarks>
|
|
||||||
/// <returns> True if this is an assembly extension. </returns>
|
|
||||||
internal bool IsAssemblyExtension(string extensionWithLeadingDot)
|
|
||||||
{
|
|
||||||
foreach (var realExtension in this.assemblyExtensions)
|
|
||||||
{
|
|
||||||
if (string.Equals(extensionWithLeadingDot, realExtension, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return cultures;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether given file is managed assembly. Does not load the assembly. Does not check file extension.
|
|
||||||
/// Performance: takes ~0.1 seconds on 2x CPU P4.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path"> The path to the assembly. </param>
|
|
||||||
/// <returns> True if managed assembly. </returns>
|
|
||||||
internal bool IsAssembly(string path)
|
|
||||||
{
|
|
||||||
Debug.Assert(!string.IsNullOrEmpty(path), "path");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// AssemblyName.GetAssemblyName: causes the file to be opened and closed, but the assembly is not added to this domain.
|
|
||||||
// Also if there are dependencies, they are never loaded.
|
|
||||||
AssemblyName.GetAssemblyName(path);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (FileLoadException)
|
|
||||||
{
|
|
||||||
// This is an executable image but not an assembly.
|
|
||||||
}
|
|
||||||
catch (BadImageFormatException)
|
|
||||||
{
|
|
||||||
// Happens when file is not a DLL/EXE, etc.
|
|
||||||
}
|
|
||||||
|
|
||||||
// If file cannot be found we will throw.
|
|
||||||
// If there's anything else like SecurityException - we just pass exception through.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns satellite assemblies. Returns full canonicalized paths.
|
|
||||||
/// If the file is not an assembly returns empty list.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assemblyPath"> The assembly to get satellites for. </param>
|
|
||||||
/// <returns> List of satellite assemblies. </returns>
|
|
||||||
internal virtual List<string> GetSatelliteAssemblies(string assemblyPath)
|
|
||||||
{
|
|
||||||
if (!this.IsAssemblyExtension(Path.GetExtension(assemblyPath)) || !this.IsAssembly(assemblyPath))
|
|
||||||
{
|
|
||||||
EqtTrace.ErrorIf(
|
|
||||||
EqtTrace.IsErrorEnabled,
|
|
||||||
"AssemblyUtilities.GetSatelliteAssemblies: the specified file '{0}' is not managed assembly.",
|
|
||||||
assemblyPath);
|
|
||||||
Debug.Fail("AssemblyUtilities.GetSatelliteAssemblies: the file '" + assemblyPath + "' is not an assembly.");
|
|
||||||
|
|
||||||
// If e.g. this is unmanaged dll, we don't care about the satellites.
|
|
||||||
return new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
assemblyPath = Path.GetFullPath(assemblyPath);
|
|
||||||
var assemblyDir = Path.GetDirectoryName(assemblyPath);
|
|
||||||
var satellites = new List<string>();
|
|
||||||
|
|
||||||
// Directory.Exists for 266 dirs takes 9ms while Path.GetDirectories can take up to 80ms on 10k dirs.
|
|
||||||
foreach (string dir in Cultures.Keys)
|
|
||||||
{
|
|
||||||
var dirPath = Path.Combine(assemblyDir, dir);
|
|
||||||
if (!Directory.Exists(dirPath))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the satellite exists in this dir.
|
|
||||||
// We check filenames like: MyAssembly.dll -> MyAssembly.resources.dll.
|
|
||||||
// Surprisingly, but both DLL and EXE are found by resource manager.
|
|
||||||
foreach (var extension in this.assemblyExtensions)
|
|
||||||
{
|
|
||||||
// extension contains leading dot.
|
|
||||||
string satellite = Path.ChangeExtension(Path.GetFileName(assemblyPath), "resources" + extension);
|
|
||||||
string satellitePath = Path.Combine(assemblyDir, Path.Combine(dir, satellite));
|
|
||||||
|
|
||||||
// We don't use Assembly.LoadFrom/Assembly.GetSatelliteAssebmlies because this is rather slow
|
|
||||||
// (1620ms for 266 cultures when directories do not exist).
|
|
||||||
if (File.Exists(satellitePath))
|
|
||||||
{
|
|
||||||
// If the satellite found is not a managed assembly we do not report it as a reference.
|
|
||||||
if (!this.IsAssembly(satellitePath))
|
|
||||||
{
|
|
||||||
EqtTrace.ErrorIf(
|
|
||||||
EqtTrace.IsErrorEnabled,
|
|
||||||
"AssemblyUtilities.GetSatelliteAssemblies: found assembly '{0}' installed as satellite but it's not managed assembly.",
|
|
||||||
satellitePath);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If both .exe and .dll exist we return both silently.
|
|
||||||
satellites.Add(satellitePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return satellites;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the dependent assemblies of the parameter assembly.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assemblyPath"> Path to assembly to get dependencies for. </param>
|
|
||||||
/// <param name="configFile"> Config file to use while trying to resolve dependencies. </param>
|
|
||||||
/// <param name="warnings"> The warnings. </param>
|
|
||||||
/// <returns> The <see cref="T:string[]"/>. </returns>
|
|
||||||
internal virtual string[] GetFullPathToDependentAssemblies(string assemblyPath, string configFile, out IList<string> warnings)
|
|
||||||
{
|
|
||||||
Debug.Assert(!string.IsNullOrEmpty(assemblyPath), "assemblyPath");
|
|
||||||
|
|
||||||
EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "AssemblyDependencyFinder.GetDependentAssemblies: start.");
|
|
||||||
|
|
||||||
AppDomainSetup setupInfo = new();
|
|
||||||
var dllDirectory = Path.GetDirectoryName(Path.GetFullPath(assemblyPath));
|
|
||||||
setupInfo.ApplicationBase = dllDirectory;
|
|
||||||
|
|
||||||
Debug.Assert(string.IsNullOrEmpty(configFile) || File.Exists(configFile), "Config file is specified but does not exist: {0}", configFile);
|
|
||||||
|
|
||||||
AppDomainUtilities.SetConfigurationFile(setupInfo, configFile);
|
|
||||||
|
|
||||||
EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "AssemblyDependencyFinder.GetDependentAssemblies: Using config file: '{0}'.", setupInfo.ConfigurationFile);
|
|
||||||
|
|
||||||
setupInfo.LoaderOptimization = LoaderOptimization.MultiDomainHost;
|
|
||||||
|
|
||||||
AppDomain appDomain = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
appDomain = AppDomain.CreateDomain("Dependency finder domain", null, setupInfo);
|
|
||||||
if (EqtTrace.IsInfoEnabled)
|
|
||||||
{
|
|
||||||
EqtTrace.Info("AssemblyDependencyFinder.GetDependentAssemblies: Created AppDomain.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var assemblyResolverType = typeof(AssemblyResolver);
|
|
||||||
|
|
||||||
EqtTrace.SetupRemoteEqtTraceListeners(appDomain);
|
|
||||||
|
|
||||||
// This has to be LoadFrom, otherwise we will have to use AssemblyResolver to find self.
|
|
||||||
using AssemblyResolver resolver =
|
|
||||||
(AssemblyResolver)AppDomainUtilities.CreateInstance(
|
|
||||||
appDomain,
|
|
||||||
assemblyResolverType,
|
|
||||||
new object[] { this.GetResolutionPaths() });
|
|
||||||
// This has to be Load, otherwise Serialization of argument types will not work correctly.
|
|
||||||
AssemblyLoadWorker worker =
|
|
||||||
(AssemblyLoadWorker)AppDomainUtilities.CreateInstance(appDomain, typeof(AssemblyLoadWorker), null);
|
|
||||||
|
|
||||||
EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "AssemblyDependencyFinder.GetDependentAssemblies: loaded the worker.");
|
|
||||||
|
|
||||||
var allDependencies = worker.GetFullPathToDependentAssemblies(assemblyPath, out warnings);
|
|
||||||
var dependenciesFromDllDirectory = new List<string>();
|
|
||||||
var dllDirectoryUppercase = dllDirectory.ToUpperInvariant();
|
|
||||||
foreach (var dependency in allDependencies)
|
|
||||||
{
|
|
||||||
if (dependency.ToUpperInvariant().Contains(dllDirectoryUppercase))
|
|
||||||
{
|
|
||||||
dependenciesFromDllDirectory.Add(dependency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dependenciesFromDllDirectory.ToArray();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (appDomain != null)
|
|
||||||
{
|
|
||||||
EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "AssemblyDependencyFinder.GetDependentAssemblies: unloading AppDomain...");
|
|
||||||
AppDomain.Unload(appDomain);
|
|
||||||
EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "AssemblyDependencyFinder.GetDependentAssemblies: unloading AppDomain succeeded.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the resolution paths for app domain creation.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns> The <see cref="IList{T}"/> of resolution paths. </returns>
|
|
||||||
internal IList<string> GetResolutionPaths()
|
|
||||||
{
|
|
||||||
// Use dictionary to ensure we get a list of unique paths, but keep a list as the
|
|
||||||
// dictionary does not guarantee order.
|
|
||||||
Dictionary<string, object> resolutionPathsDictionary = new(StringComparer.OrdinalIgnoreCase);
|
|
||||||
List<string> resolutionPaths = new();
|
|
||||||
|
|
||||||
// Add the path of the currently executing assembly (use Uri(CodeBase).LocalPath as Location can be on shadow dir).
|
|
||||||
string currentlyExecutingAssembly = Path.GetDirectoryName(Path.GetFullPath(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath));
|
|
||||||
resolutionPaths.Add(currentlyExecutingAssembly);
|
|
||||||
resolutionPathsDictionary[currentlyExecutingAssembly] = null;
|
|
||||||
|
|
||||||
// Add the application base for this domain.
|
|
||||||
if (!resolutionPathsDictionary.ContainsKey(AppDomain.CurrentDomain.BaseDirectory))
|
|
||||||
{
|
|
||||||
resolutionPaths.Add(AppDomain.CurrentDomain.BaseDirectory);
|
|
||||||
resolutionPathsDictionary[AppDomain.CurrentDomain.BaseDirectory] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolutionPaths;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads an assembly into the reflection-only context, given its path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assemblyPath">The path of the file that contains the manifest of the assembly.</param>
|
||||||
|
/// <returns>The loaded assembly.</returns>
|
||||||
|
public Assembly ReflectionOnlyLoadFrom(string assemblyPath)
|
||||||
|
{
|
||||||
|
return Assembly.ReflectionOnlyLoadFrom(assemblyPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads an assembly into the reflection-only context, given its display name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assemblyString">The display name of the assembly, as returned by the System.Reflection.AssemblyName.FullName property.</param>
|
||||||
|
/// <returns>The loaded assembly.</returns>
|
||||||
|
public Assembly ReflectionOnlyLoad(string assemblyString)
|
||||||
|
{
|
||||||
|
return Assembly.ReflectionOnlyLoad(assemblyString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether file extension is an assembly file extension.
|
||||||
|
/// Returns true for .exe and .dll, otherwise false.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="extensionWithLeadingDot"> Extension containing leading dot, e.g. ".exe". </param>
|
||||||
|
/// <remarks> Path.GetExtension() returns extension with leading dot. </remarks>
|
||||||
|
/// <returns> True if this is an assembly extension. </returns>
|
||||||
|
internal bool IsAssemblyExtension(string extensionWithLeadingDot)
|
||||||
|
{
|
||||||
|
foreach (var realExtension in this.assemblyExtensions)
|
||||||
|
{
|
||||||
|
if (string.Equals(extensionWithLeadingDot, realExtension, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether given file is managed assembly. Does not load the assembly. Does not check file extension.
|
||||||
|
/// Performance: takes ~0.1 seconds on 2x CPU P4.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path"> The path to the assembly. </param>
|
||||||
|
/// <returns> True if managed assembly. </returns>
|
||||||
|
internal bool IsAssembly(string path)
|
||||||
|
{
|
||||||
|
Debug.Assert(!string.IsNullOrEmpty(path), "path");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// AssemblyName.GetAssemblyName: causes the file to be opened and closed, but the assembly is not added to this domain.
|
||||||
|
// Also if there are dependencies, they are never loaded.
|
||||||
|
AssemblyName.GetAssemblyName(path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (FileLoadException)
|
||||||
|
{
|
||||||
|
// This is an executable image but not an assembly.
|
||||||
|
}
|
||||||
|
catch (BadImageFormatException)
|
||||||
|
{
|
||||||
|
// Happens when file is not a DLL/EXE, etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
// If file cannot be found we will throw.
|
||||||
|
// If there's anything else like SecurityException - we just pass exception through.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns satellite assemblies. Returns full canonicalized paths.
|
||||||
|
/// If the file is not an assembly returns empty list.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assemblyPath"> The assembly to get satellites for. </param>
|
||||||
|
/// <returns> List of satellite assemblies. </returns>
|
||||||
|
internal virtual List<string> GetSatelliteAssemblies(string assemblyPath)
|
||||||
|
{
|
||||||
|
if (!this.IsAssemblyExtension(Path.GetExtension(assemblyPath)) || !this.IsAssembly(assemblyPath))
|
||||||
|
{
|
||||||
|
EqtTrace.ErrorIf(
|
||||||
|
EqtTrace.IsErrorEnabled,
|
||||||
|
"AssemblyUtilities.GetSatelliteAssemblies: the specified file '{0}' is not managed assembly.",
|
||||||
|
assemblyPath);
|
||||||
|
Debug.Fail("AssemblyUtilities.GetSatelliteAssemblies: the file '" + assemblyPath + "' is not an assembly.");
|
||||||
|
|
||||||
|
// If e.g. this is unmanaged dll, we don't care about the satellites.
|
||||||
|
return new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
assemblyPath = Path.GetFullPath(assemblyPath);
|
||||||
|
var assemblyDir = Path.GetDirectoryName(assemblyPath);
|
||||||
|
var satellites = new List<string>();
|
||||||
|
|
||||||
|
// Directory.Exists for 266 dirs takes 9ms while Path.GetDirectories can take up to 80ms on 10k dirs.
|
||||||
|
foreach (string dir in Cultures.Keys)
|
||||||
|
{
|
||||||
|
var dirPath = Path.Combine(assemblyDir, dir);
|
||||||
|
if (!Directory.Exists(dirPath))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the satellite exists in this dir.
|
||||||
|
// We check filenames like: MyAssembly.dll -> MyAssembly.resources.dll.
|
||||||
|
// Surprisingly, but both DLL and EXE are found by resource manager.
|
||||||
|
foreach (var extension in this.assemblyExtensions)
|
||||||
|
{
|
||||||
|
// extension contains leading dot.
|
||||||
|
string satellite = Path.ChangeExtension(Path.GetFileName(assemblyPath), "resources" + extension);
|
||||||
|
string satellitePath = Path.Combine(assemblyDir, Path.Combine(dir, satellite));
|
||||||
|
|
||||||
|
// We don't use Assembly.LoadFrom/Assembly.GetSatelliteAssebmlies because this is rather slow
|
||||||
|
// (1620ms for 266 cultures when directories do not exist).
|
||||||
|
if (File.Exists(satellitePath))
|
||||||
|
{
|
||||||
|
// If the satellite found is not a managed assembly we do not report it as a reference.
|
||||||
|
if (!this.IsAssembly(satellitePath))
|
||||||
|
{
|
||||||
|
EqtTrace.ErrorIf(
|
||||||
|
EqtTrace.IsErrorEnabled,
|
||||||
|
"AssemblyUtilities.GetSatelliteAssemblies: found assembly '{0}' installed as satellite but it's not managed assembly.",
|
||||||
|
satellitePath);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If both .exe and .dll exist we return both silently.
|
||||||
|
satellites.Add(satellitePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return satellites;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the dependent assemblies of the parameter assembly.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assemblyPath"> Path to assembly to get dependencies for. </param>
|
||||||
|
/// <param name="configFile"> Config file to use while trying to resolve dependencies. </param>
|
||||||
|
/// <param name="warnings"> The warnings. </param>
|
||||||
|
/// <returns> The <see cref="T:string[]"/>. </returns>
|
||||||
|
internal virtual string[] GetFullPathToDependentAssemblies(string assemblyPath, string configFile, out IList<string> warnings)
|
||||||
|
{
|
||||||
|
Debug.Assert(!string.IsNullOrEmpty(assemblyPath), "assemblyPath");
|
||||||
|
|
||||||
|
EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "AssemblyDependencyFinder.GetDependentAssemblies: start.");
|
||||||
|
|
||||||
|
AppDomainSetup setupInfo = new();
|
||||||
|
var dllDirectory = Path.GetDirectoryName(Path.GetFullPath(assemblyPath));
|
||||||
|
setupInfo.ApplicationBase = dllDirectory;
|
||||||
|
|
||||||
|
Debug.Assert(string.IsNullOrEmpty(configFile) || File.Exists(configFile), "Config file is specified but does not exist: {0}", configFile);
|
||||||
|
|
||||||
|
AppDomainUtilities.SetConfigurationFile(setupInfo, configFile);
|
||||||
|
|
||||||
|
EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "AssemblyDependencyFinder.GetDependentAssemblies: Using config file: '{0}'.", setupInfo.ConfigurationFile);
|
||||||
|
|
||||||
|
setupInfo.LoaderOptimization = LoaderOptimization.MultiDomainHost;
|
||||||
|
|
||||||
|
AppDomain appDomain = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
appDomain = AppDomain.CreateDomain("Dependency finder domain", null, setupInfo);
|
||||||
|
if (EqtTrace.IsInfoEnabled)
|
||||||
|
{
|
||||||
|
EqtTrace.Info("AssemblyDependencyFinder.GetDependentAssemblies: Created AppDomain.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var assemblyResolverType = typeof(AssemblyResolver);
|
||||||
|
|
||||||
|
EqtTrace.SetupRemoteEqtTraceListeners(appDomain);
|
||||||
|
|
||||||
|
// This has to be LoadFrom, otherwise we will have to use AssemblyResolver to find self.
|
||||||
|
using AssemblyResolver resolver =
|
||||||
|
(AssemblyResolver)AppDomainUtilities.CreateInstance(
|
||||||
|
appDomain,
|
||||||
|
assemblyResolverType,
|
||||||
|
new object[] { this.GetResolutionPaths() });
|
||||||
|
// This has to be Load, otherwise Serialization of argument types will not work correctly.
|
||||||
|
AssemblyLoadWorker worker =
|
||||||
|
(AssemblyLoadWorker)AppDomainUtilities.CreateInstance(appDomain, typeof(AssemblyLoadWorker), null);
|
||||||
|
|
||||||
|
EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "AssemblyDependencyFinder.GetDependentAssemblies: loaded the worker.");
|
||||||
|
|
||||||
|
var allDependencies = worker.GetFullPathToDependentAssemblies(assemblyPath, out warnings);
|
||||||
|
var dependenciesFromDllDirectory = new List<string>();
|
||||||
|
var dllDirectoryUppercase = dllDirectory.ToUpperInvariant();
|
||||||
|
foreach (var dependency in allDependencies)
|
||||||
|
{
|
||||||
|
if (dependency.ToUpperInvariant().Contains(dllDirectoryUppercase))
|
||||||
|
{
|
||||||
|
dependenciesFromDllDirectory.Add(dependency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dependenciesFromDllDirectory.ToArray();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (appDomain != null)
|
||||||
|
{
|
||||||
|
EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "AssemblyDependencyFinder.GetDependentAssemblies: unloading AppDomain...");
|
||||||
|
AppDomain.Unload(appDomain);
|
||||||
|
EqtTrace.InfoIf(EqtTrace.IsInfoEnabled, "AssemblyDependencyFinder.GetDependentAssemblies: unloading AppDomain succeeded.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the resolution paths for app domain creation.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns> The <see cref="IList{T}"/> of resolution paths. </returns>
|
||||||
|
internal IList<string> GetResolutionPaths()
|
||||||
|
{
|
||||||
|
// Use dictionary to ensure we get a list of unique paths, but keep a list as the
|
||||||
|
// dictionary does not guarantee order.
|
||||||
|
Dictionary<string, object> resolutionPathsDictionary = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
List<string> resolutionPaths = new();
|
||||||
|
|
||||||
|
// Add the path of the currently executing assembly (use Uri(CodeBase).LocalPath as Location can be on shadow dir).
|
||||||
|
string currentlyExecutingAssembly = Path.GetDirectoryName(Path.GetFullPath(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath));
|
||||||
|
resolutionPaths.Add(currentlyExecutingAssembly);
|
||||||
|
resolutionPathsDictionary[currentlyExecutingAssembly] = null;
|
||||||
|
|
||||||
|
// Add the application base for this domain.
|
||||||
|
if (!resolutionPathsDictionary.ContainsKey(AppDomain.CurrentDomain.BaseDirectory))
|
||||||
|
{
|
||||||
|
resolutionPaths.Add(AppDomain.CurrentDomain.BaseDirectory);
|
||||||
|
resolutionPathsDictionary[AppDomain.CurrentDomain.BaseDirectory] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolutionPaths;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,249 +1,248 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Extensions;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
|
||||||
|
internal class DeploymentUtility : DeploymentUtilityBase
|
||||||
{
|
{
|
||||||
using System;
|
public DeploymentUtility()
|
||||||
using System.Collections.Generic;
|
: base()
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Extensions;
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
|
||||||
|
|
||||||
internal class DeploymentUtility : DeploymentUtilityBase
|
|
||||||
{
|
{
|
||||||
public DeploymentUtility()
|
}
|
||||||
: base()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public DeploymentUtility(DeploymentItemUtility deploymentItemUtility, AssemblyUtility assemblyUtility, FileUtility fileUtility)
|
public DeploymentUtility(DeploymentItemUtility deploymentItemUtility, AssemblyUtility assemblyUtility, FileUtility fileUtility)
|
||||||
: base(deploymentItemUtility, assemblyUtility, fileUtility)
|
: base(deploymentItemUtility, assemblyUtility, fileUtility)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void AddDeploymentItemsBasedOnMsTestSetting(string testSource, IList<DeploymentItem> deploymentItems, List<string> warnings)
|
public override void AddDeploymentItemsBasedOnMsTestSetting(string testSource, IList<DeploymentItem> deploymentItems, List<string> warnings)
|
||||||
|
{
|
||||||
|
if (MSTestSettingsProvider.Settings.DeployTestSourceDependencies)
|
||||||
{
|
{
|
||||||
if (MSTestSettingsProvider.Settings.DeployTestSourceDependencies)
|
EqtTrace.Info("Adding the references and satellite assemblies to the deployment items list");
|
||||||
|
|
||||||
|
// Get the referenced assemblies.
|
||||||
|
this.ProcessNewStorage(testSource, deploymentItems, warnings);
|
||||||
|
|
||||||
|
// Get the satellite assemblies
|
||||||
|
var satelliteItems = this.GetSatellites(deploymentItems, testSource, warnings);
|
||||||
|
foreach (var satelliteItem in satelliteItems)
|
||||||
{
|
{
|
||||||
EqtTrace.Info("Adding the references and satellite assemblies to the deployment items list");
|
this.DeploymentItemUtility.AddDeploymentItem(deploymentItems, satelliteItem);
|
||||||
|
|
||||||
// Get the referenced assemblies.
|
|
||||||
this.ProcessNewStorage(testSource, deploymentItems, warnings);
|
|
||||||
|
|
||||||
// Get the satellite assemblies
|
|
||||||
var satelliteItems = this.GetSatellites(deploymentItems, testSource, warnings);
|
|
||||||
foreach (var satelliteItem in satelliteItems)
|
|
||||||
{
|
|
||||||
this.DeploymentItemUtility.AddDeploymentItem(deploymentItems, satelliteItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
EqtTrace.Info("Adding the test source directory to the deployment items list");
|
|
||||||
this.DeploymentItemUtility.AddDeploymentItem(deploymentItems, new DeploymentItem(Path.GetDirectoryName(testSource)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
/// <summary>
|
|
||||||
/// Get root deployment directory
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="baseDirectory">The base directory.</param>
|
|
||||||
/// <returns>Root deployment directory.</returns>
|
|
||||||
public override string GetRootDeploymentDirectory(string baseDirectory)
|
|
||||||
{
|
{
|
||||||
string dateTimeSufix = DateTime.Now.ToString("yyyyMMddTHHmmss", DateTimeFormatInfo.InvariantInfo);
|
EqtTrace.Info("Adding the test source directory to the deployment items list");
|
||||||
string directoryName = string.Format(CultureInfo.InvariantCulture, Resource.TestRunName, DeploymentFolderPrefix, Environment.UserName, dateTimeSufix);
|
this.DeploymentItemUtility.AddDeploymentItem(deploymentItems, new DeploymentItem(Path.GetDirectoryName(testSource)));
|
||||||
directoryName = this.FileUtility.ReplaceInvalidFileNameCharacters(directoryName);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return this.FileUtility.GetNextIterationDirectoryName(baseDirectory, directoryName);
|
/// <summary>
|
||||||
|
/// Get root deployment directory
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseDirectory">The base directory.</param>
|
||||||
|
/// <returns>Root deployment directory.</returns>
|
||||||
|
public override string GetRootDeploymentDirectory(string baseDirectory)
|
||||||
|
{
|
||||||
|
string dateTimeSufix = DateTime.Now.ToString("yyyyMMddTHHmmss", DateTimeFormatInfo.InvariantInfo);
|
||||||
|
string directoryName = string.Format(CultureInfo.InvariantCulture, Resource.TestRunName, DeploymentFolderPrefix, Environment.UserName, dateTimeSufix);
|
||||||
|
directoryName = this.FileUtility.ReplaceInvalidFileNameCharacters(directoryName);
|
||||||
|
|
||||||
|
return this.FileUtility.GetNextIterationDirectoryName(baseDirectory, directoryName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
||||||
|
protected void ProcessNewStorage(string testSource, IList<DeploymentItem> deploymentItems, IList<string> warnings)
|
||||||
|
{
|
||||||
|
// Add deployment items and process .config files only for storages we have not processed before.
|
||||||
|
if (!this.DeploymentItemUtility.IsValidDeploymentItem(testSource, string.Empty, out var errorMessage))
|
||||||
|
{
|
||||||
|
warnings.Add(errorMessage);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
|
this.DeploymentItemUtility.AddDeploymentItem(deploymentItems, new DeploymentItem(testSource, string.Empty, DeploymentItemOriginType.TestStorage));
|
||||||
protected void ProcessNewStorage(string testSource, IList<DeploymentItem> deploymentItems, IList<string> warnings)
|
|
||||||
|
// Deploy .config file if exists, only for assemblies, i.e. DLL and EXE.
|
||||||
|
// First check <TestStorage>.config, then if not found check for App.Config
|
||||||
|
// and deploy AppConfig to <TestStorage>.config.
|
||||||
|
if (this.AssemblyUtility.IsAssemblyExtension(Path.GetExtension(testSource)))
|
||||||
{
|
{
|
||||||
// Add deployment items and process .config files only for storages we have not processed before.
|
var configFile = this.AddTestSourceConfigFileIfExists(testSource, deploymentItems);
|
||||||
if (!this.DeploymentItemUtility.IsValidDeploymentItem(testSource, string.Empty, out var errorMessage))
|
|
||||||
|
// Deal with test dependencies: update dependencyDeploymentItems and missingDependentAssemblies.
|
||||||
|
try
|
||||||
{
|
{
|
||||||
warnings.Add(errorMessage);
|
// We look for dependent assemblies only for DLL and EXE's.
|
||||||
return;
|
this.AddDependencies(testSource, configFile, deploymentItems, warnings);
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
this.DeploymentItemUtility.AddDeploymentItem(deploymentItems, new DeploymentItem(testSource, string.Empty, DeploymentItemOriginType.TestStorage));
|
|
||||||
|
|
||||||
// Deploy .config file if exists, only for assemblies, i.e. DLL and EXE.
|
|
||||||
// First check <TestStorage>.config, then if not found check for App.Config
|
|
||||||
// and deploy AppConfig to <TestStorage>.config.
|
|
||||||
if (this.AssemblyUtility.IsAssemblyExtension(Path.GetExtension(testSource)))
|
|
||||||
{
|
{
|
||||||
var configFile = this.AddTestSourceConfigFileIfExists(testSource, deploymentItems);
|
string warning = string.Format(CultureInfo.CurrentCulture, Resource.DeploymentErrorFailedToDeployDependencies, testSource, e);
|
||||||
|
warnings.Add(warning);
|
||||||
// Deal with test dependencies: update dependencyDeploymentItems and missingDependentAssemblies.
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// We look for dependent assemblies only for DLL and EXE's.
|
|
||||||
this.AddDependencies(testSource, configFile, deploymentItems, warnings);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
string warning = string.Format(CultureInfo.CurrentCulture, Resource.DeploymentErrorFailedToDeployDependencies, testSource, e);
|
|
||||||
warnings.Add(warning);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void AddDependenciesOfDeploymentItem(string deploymentItemFile, IList<string> filesToDeploy, IList<string> warnings)
|
protected override void AddDependenciesOfDeploymentItem(string deploymentItemFile, IList<string> filesToDeploy, IList<string> warnings)
|
||||||
|
{
|
||||||
|
var dependencies = new List<DeploymentItem>();
|
||||||
|
|
||||||
|
this.AddDependencies(deploymentItemFile, null, dependencies, warnings);
|
||||||
|
|
||||||
|
foreach (var dependencyItem in dependencies)
|
||||||
{
|
{
|
||||||
var dependencies = new List<DeploymentItem>();
|
Debug.Assert(Path.IsPathRooted(dependencyItem.SourcePath), "Path of the dependency " + dependencyItem.SourcePath + " is not rooted.");
|
||||||
|
|
||||||
this.AddDependencies(deploymentItemFile, null, dependencies, warnings);
|
// Add dependencies to filesToDeploy.
|
||||||
|
filesToDeploy.Add(dependencyItem.SourcePath);
|
||||||
foreach (var dependencyItem in dependencies)
|
|
||||||
{
|
|
||||||
Debug.Assert(Path.IsPathRooted(dependencyItem.SourcePath), "Path of the dependency " + dependencyItem.SourcePath + " is not rooted.");
|
|
||||||
|
|
||||||
// Add dependencies to filesToDeploy.
|
|
||||||
filesToDeploy.Add(dependencyItem.SourcePath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected IEnumerable<DeploymentItem> GetSatellites(IEnumerable<DeploymentItem> deploymentItems, string testSource, IList<string> warnings)
|
protected IEnumerable<DeploymentItem> GetSatellites(IEnumerable<DeploymentItem> deploymentItems, string testSource, IList<string> warnings)
|
||||||
|
{
|
||||||
|
List<DeploymentItem> satellites = new();
|
||||||
|
foreach (DeploymentItem item in deploymentItems)
|
||||||
{
|
{
|
||||||
List<DeploymentItem> satellites = new();
|
// We do not care about deployment items which are directories because in that case we deploy all files underneath anyway.
|
||||||
foreach (DeploymentItem item in deploymentItems)
|
string path = null;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
// We do not care about deployment items which are directories because in that case we deploy all files underneath anyway.
|
path = this.GetFullPathToDeploymentItemSource(item.SourcePath, testSource);
|
||||||
string path = null;
|
path = Path.GetFullPath(path);
|
||||||
try
|
|
||||||
{
|
|
||||||
path = this.GetFullPathToDeploymentItemSource(item.SourcePath, testSource);
|
|
||||||
path = Path.GetFullPath(path);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(path) || !this.AssemblyUtility.IsAssemblyExtension(Path.GetExtension(path))
|
if (string.IsNullOrEmpty(path) || !this.AssemblyUtility.IsAssemblyExtension(Path.GetExtension(path))
|
||||||
|| !this.FileUtility.DoesFileExist(path) || !this.AssemblyUtility.IsAssembly(path))
|
|| !this.FileUtility.DoesFileExist(path) || !this.AssemblyUtility.IsAssembly(path))
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
{
|
||||||
EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex);
|
continue;
|
||||||
}
|
|
||||||
catch (SecurityException ex)
|
|
||||||
{
|
|
||||||
EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex);
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
// This covers PathTooLongException.
|
|
||||||
EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex);
|
|
||||||
}
|
|
||||||
catch (NotSupportedException ex)
|
|
||||||
{
|
|
||||||
EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex);
|
||||||
|
}
|
||||||
|
catch (SecurityException ex)
|
||||||
|
{
|
||||||
|
EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex);
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
// This covers PathTooLongException.
|
||||||
|
EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex);
|
||||||
|
}
|
||||||
|
catch (NotSupportedException ex)
|
||||||
|
{
|
||||||
|
EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex);
|
||||||
|
}
|
||||||
|
|
||||||
// Note: now Path operations with itemPath should not result in any exceptions.
|
// Note: now Path operations with itemPath should not result in any exceptions.
|
||||||
// path is already canonicalized.
|
// path is already canonicalized.
|
||||||
|
|
||||||
// If we cannot access satellite due to security, etc, we report warning.
|
// If we cannot access satellite due to security, etc, we report warning.
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
string itemDir = Path.GetDirectoryName(path).TrimEnd(
|
||||||
|
new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar });
|
||||||
|
List<string> itemSatellites = this.AssemblyUtility.GetSatelliteAssemblies(path);
|
||||||
|
foreach (string satellite in itemSatellites)
|
||||||
{
|
{
|
||||||
string itemDir = Path.GetDirectoryName(path).TrimEnd(
|
Debug.Assert(!string.IsNullOrEmpty(satellite), "DeploymentManager.DoDeployment: got empty satellite!");
|
||||||
|
Debug.Assert(
|
||||||
|
satellite.IndexOf(itemDir, StringComparison.OrdinalIgnoreCase) == 0,
|
||||||
|
"DeploymentManager.DoDeployment: Got satellite that does not start with original item path");
|
||||||
|
|
||||||
|
string satelliteDir = Path.GetDirectoryName(satellite).TrimEnd(
|
||||||
new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar });
|
new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar });
|
||||||
List<string> itemSatellites = this.AssemblyUtility.GetSatelliteAssemblies(path);
|
|
||||||
foreach (string satellite in itemSatellites)
|
|
||||||
{
|
|
||||||
Debug.Assert(!string.IsNullOrEmpty(satellite), "DeploymentManager.DoDeployment: got empty satellite!");
|
|
||||||
Debug.Assert(
|
|
||||||
satellite.IndexOf(itemDir, StringComparison.OrdinalIgnoreCase) == 0,
|
|
||||||
"DeploymentManager.DoDeployment: Got satellite that does not start with original item path");
|
|
||||||
|
|
||||||
string satelliteDir = Path.GetDirectoryName(satellite).TrimEnd(
|
Debug.Assert(!string.IsNullOrEmpty(satelliteDir), "DeploymentManager.DoDeployment: got empty satellite dir!");
|
||||||
new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar });
|
Debug.Assert(satelliteDir.Length > itemDir.Length + 1, "DeploymentManager.DoDeployment: wrong satellite dir length!");
|
||||||
|
|
||||||
Debug.Assert(!string.IsNullOrEmpty(satelliteDir), "DeploymentManager.DoDeployment: got empty satellite dir!");
|
string localeDir = satelliteDir.Substring(itemDir.Length + 1);
|
||||||
Debug.Assert(satelliteDir.Length > itemDir.Length + 1, "DeploymentManager.DoDeployment: wrong satellite dir length!");
|
Debug.Assert(!string.IsNullOrEmpty(localeDir), "DeploymentManager.DoDeployment: got empty dir name for satellite dir!");
|
||||||
|
|
||||||
string localeDir = satelliteDir.Substring(itemDir.Length + 1);
|
string relativeOutputDir = Path.Combine(item.RelativeOutputDirectory, localeDir);
|
||||||
Debug.Assert(!string.IsNullOrEmpty(localeDir), "DeploymentManager.DoDeployment: got empty dir name for satellite dir!");
|
|
||||||
|
|
||||||
string relativeOutputDir = Path.Combine(item.RelativeOutputDirectory, localeDir);
|
// Now finally add the item!
|
||||||
|
DeploymentItem satelliteItem = new(satellite, relativeOutputDir, DeploymentItemOriginType.Satellite);
|
||||||
// Now finally add the item!
|
this.DeploymentItemUtility.AddDeploymentItem(satellites, satelliteItem);
|
||||||
DeploymentItem satelliteItem = new(satellite, relativeOutputDir, DeploymentItemOriginType.Satellite);
|
|
||||||
this.DeploymentItemUtility.AddDeploymentItem(satellites, satelliteItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex);
|
|
||||||
string warning = string.Format(CultureInfo.CurrentCulture, Resource.DeploymentErrorGettingSatellite, item, ex.GetType(), ex.GetExceptionMessage());
|
|
||||||
warnings.Add(warning);
|
|
||||||
}
|
|
||||||
catch (SecurityException ex)
|
|
||||||
{
|
|
||||||
EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex);
|
|
||||||
string warning = string.Format(CultureInfo.CurrentCulture, Resource.DeploymentErrorGettingSatellite, item, ex.GetType(), ex.GetExceptionMessage());
|
|
||||||
warnings.Add(warning);
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
// This covers PathTooLongException.
|
|
||||||
EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex);
|
|
||||||
string warning = string.Format(CultureInfo.CurrentCulture, Resource.DeploymentErrorGettingSatellite, item, ex.GetType(), ex.GetExceptionMessage());
|
|
||||||
warnings.Add(warning);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
return satellites;
|
{
|
||||||
|
EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex);
|
||||||
|
string warning = string.Format(CultureInfo.CurrentCulture, Resource.DeploymentErrorGettingSatellite, item, ex.GetType(), ex.GetExceptionMessage());
|
||||||
|
warnings.Add(warning);
|
||||||
|
}
|
||||||
|
catch (SecurityException ex)
|
||||||
|
{
|
||||||
|
EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex);
|
||||||
|
string warning = string.Format(CultureInfo.CurrentCulture, Resource.DeploymentErrorGettingSatellite, item, ex.GetType(), ex.GetExceptionMessage());
|
||||||
|
warnings.Add(warning);
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
// This covers PathTooLongException.
|
||||||
|
EqtTrace.WarningIf(EqtTrace.IsWarningEnabled, "DeploymentManager.GetSatellites: {0}", ex);
|
||||||
|
string warning = string.Format(CultureInfo.CurrentCulture, Resource.DeploymentErrorGettingSatellite, item, ex.GetType(), ex.GetExceptionMessage());
|
||||||
|
warnings.Add(warning);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return satellites;
|
||||||
/// Process test storage and add dependent assemblies to dependencyDeploymentItems.
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="testSource">The test source.</param>
|
/// <summary>
|
||||||
/// <param name="configFile">The config file.</param>
|
/// Process test storage and add dependent assemblies to dependencyDeploymentItems.
|
||||||
/// <param name="deploymentItems">Deployment items.</param>
|
/// </summary>
|
||||||
/// <param name="warnings">Warnings.</param>
|
/// <param name="testSource">The test source.</param>
|
||||||
private void AddDependencies(string testSource, string configFile, IList<DeploymentItem> deploymentItems, IList<string> warnings)
|
/// <param name="configFile">The config file.</param>
|
||||||
|
/// <param name="deploymentItems">Deployment items.</param>
|
||||||
|
/// <param name="warnings">Warnings.</param>
|
||||||
|
private void AddDependencies(string testSource, string configFile, IList<DeploymentItem> deploymentItems, IList<string> warnings)
|
||||||
|
{
|
||||||
|
Debug.Assert(!string.IsNullOrEmpty(testSource), "testSource should not be null or empty.");
|
||||||
|
|
||||||
|
// config file can be null.
|
||||||
|
Debug.Assert(deploymentItems != null, "deploymentItems should not be null.");
|
||||||
|
Debug.Assert(Path.IsPathRooted(testSource), "path should be rooted.");
|
||||||
|
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
// Note: if this is not an assembly we simply return empty array, also:
|
||||||
|
// we do recursive search and report missing.
|
||||||
|
string[] references = this.AssemblyUtility.GetFullPathToDependentAssemblies(testSource, configFile, out var warningList);
|
||||||
|
if (warningList != null && warningList.Count > 0)
|
||||||
{
|
{
|
||||||
Debug.Assert(!string.IsNullOrEmpty(testSource), "testSource should not be null or empty.");
|
warnings = warnings.Concat(warningList).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
// config file can be null.
|
if (EqtTrace.IsInfoEnabled)
|
||||||
Debug.Assert(deploymentItems != null, "deploymentItems should not be null.");
|
{
|
||||||
Debug.Assert(Path.IsPathRooted(testSource), "path should be rooted.");
|
EqtTrace.Info("DeploymentManager: Source:{0} has following references", testSource);
|
||||||
|
EqtTrace.Info("DeploymentManager: Resolving dependencies took {0} ms", sw.ElapsedMilliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
var sw = Stopwatch.StartNew();
|
foreach (string reference in references)
|
||||||
|
{
|
||||||
// Note: if this is not an assembly we simply return empty array, also:
|
DeploymentItem deploymentItem = new(reference, string.Empty, DeploymentItemOriginType.Dependency);
|
||||||
// we do recursive search and report missing.
|
this.DeploymentItemUtility.AddDeploymentItem(deploymentItems, deploymentItem);
|
||||||
string[] references = this.AssemblyUtility.GetFullPathToDependentAssemblies(testSource, configFile, out var warningList);
|
|
||||||
if (warningList != null && warningList.Count > 0)
|
|
||||||
{
|
|
||||||
warnings = warnings.Concat(warningList).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EqtTrace.IsInfoEnabled)
|
if (EqtTrace.IsInfoEnabled)
|
||||||
{
|
{
|
||||||
EqtTrace.Info("DeploymentManager: Source:{0} has following references", testSource);
|
EqtTrace.Info("DeploymentManager: Reference:{0} ", reference);
|
||||||
EqtTrace.Info("DeploymentManager: Resolving dependencies took {0} ms", sw.ElapsedMilliseconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (string reference in references)
|
|
||||||
{
|
|
||||||
DeploymentItem deploymentItem = new(reference, string.Empty, DeploymentItemOriginType.Dependency);
|
|
||||||
this.DeploymentItemUtility.AddDeploymentItem(deploymentItems, deploymentItem);
|
|
||||||
|
|
||||||
if (EqtTrace.IsInfoEnabled)
|
|
||||||
{
|
|
||||||
EqtTrace.Info("DeploymentManager: Reference:{0} ", reference);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,310 +1,309 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Utility for reflection API's
|
||||||
|
/// </summary>
|
||||||
|
internal class ReflectionUtility
|
||||||
{
|
{
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Collections;
|
/// Gets the custom attributes of the provided type on a memberInfo
|
||||||
using System.Collections.Generic;
|
/// </summary>
|
||||||
using System.IO;
|
/// <param name="attributeProvider"> The member to reflect on. </param>
|
||||||
using System.Linq;
|
/// <param name="type"> The attribute type. </param>
|
||||||
using System.Reflection;
|
/// <returns> The vale of the custom attribute. </returns>
|
||||||
|
internal virtual object[] GetCustomAttributes(MemberInfo attributeProvider, Type type)
|
||||||
|
{
|
||||||
|
return this.GetCustomAttributes(attributeProvider, type, true);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Utility for reflection API's
|
/// Gets all the custom attributes adorned on a member.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class ReflectionUtility
|
/// <param name="memberInfo"> The member. </param>
|
||||||
|
/// <param name="inherit"> True to inspect the ancestors of element; otherwise, false. </param>
|
||||||
|
/// <returns> The list of attributes on the member. Empty list if none found. </returns>
|
||||||
|
internal object[] GetCustomAttributes(MemberInfo memberInfo, bool inherit)
|
||||||
{
|
{
|
||||||
/// <summary>
|
return this.GetCustomAttributes(memberInfo, type: null, inherit: inherit);
|
||||||
/// Gets the custom attributes of the provided type on a memberInfo
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="attributeProvider"> The member to reflect on. </param>
|
/// <summary>
|
||||||
/// <param name="type"> The attribute type. </param>
|
/// Get custom attributes on a member for both normal and reflection only load.
|
||||||
/// <returns> The vale of the custom attribute. </returns>
|
/// </summary>
|
||||||
internal virtual object[] GetCustomAttributes(MemberInfo attributeProvider, Type type)
|
/// <param name="memberInfo">Member for which attributes needs to be retrieved.</param>
|
||||||
|
/// <param name="type">Type of attribute to retrieve.</param>
|
||||||
|
/// <param name="inherit">If inherited type of attribute.</param>
|
||||||
|
/// <returns>All attributes of give type on member.</returns>
|
||||||
|
internal object[] GetCustomAttributes(MemberInfo memberInfo, Type type, bool inherit)
|
||||||
|
{
|
||||||
|
if (memberInfo == null)
|
||||||
{
|
{
|
||||||
return this.GetCustomAttributes(attributeProvider, type, true);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
bool shouldGetAllAttributes = type == null;
|
||||||
/// Gets all the custom attributes adorned on a member.
|
|
||||||
/// </summary>
|
if (!this.IsReflectionOnlyLoad(memberInfo))
|
||||||
/// <param name="memberInfo"> The member. </param>
|
|
||||||
/// <param name="inherit"> True to inspect the ancestors of element; otherwise, false. </param>
|
|
||||||
/// <returns> The list of attributes on the member. Empty list if none found. </returns>
|
|
||||||
internal object[] GetCustomAttributes(MemberInfo memberInfo, bool inherit)
|
|
||||||
{
|
{
|
||||||
return this.GetCustomAttributes(memberInfo, type: null, inherit: inherit);
|
if (shouldGetAllAttributes)
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get custom attributes on a member for both normal and reflection only load.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="memberInfo">Member for which attributes needs to be retrieved.</param>
|
|
||||||
/// <param name="type">Type of attribute to retrieve.</param>
|
|
||||||
/// <param name="inherit">If inherited type of attribute.</param>
|
|
||||||
/// <returns>All attributes of give type on member.</returns>
|
|
||||||
internal object[] GetCustomAttributes(MemberInfo memberInfo, Type type, bool inherit)
|
|
||||||
{
|
|
||||||
if (memberInfo == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
return memberInfo.GetCustomAttributes(inherit);
|
||||||
}
|
|
||||||
|
|
||||||
bool shouldGetAllAttributes = type == null;
|
|
||||||
|
|
||||||
if (!this.IsReflectionOnlyLoad(memberInfo))
|
|
||||||
{
|
|
||||||
if (shouldGetAllAttributes)
|
|
||||||
{
|
|
||||||
return memberInfo.GetCustomAttributes(inherit);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return memberInfo.GetCustomAttributes(type, inherit);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
List<object> nonUniqueAttributes = new();
|
return memberInfo.GetCustomAttributes(type, inherit);
|
||||||
Dictionary<string, object> uniqueAttributes = new();
|
|
||||||
|
|
||||||
var inheritanceThreshold = 10;
|
|
||||||
var inheritanceLevel = 0;
|
|
||||||
|
|
||||||
if (inherit && memberInfo.MemberType == MemberTypes.TypeInfo)
|
|
||||||
{
|
|
||||||
// This code is based on the code for fetching CustomAttributes in System.Reflection.CustomAttribute(RuntimeType type, RuntimeType caType, bool inherit)
|
|
||||||
var tempTypeInfo = memberInfo as TypeInfo;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
var attributes = CustomAttributeData.GetCustomAttributes(tempTypeInfo);
|
|
||||||
this.AddNewAttributes(
|
|
||||||
attributes,
|
|
||||||
shouldGetAllAttributes,
|
|
||||||
type,
|
|
||||||
uniqueAttributes,
|
|
||||||
nonUniqueAttributes);
|
|
||||||
tempTypeInfo = tempTypeInfo.BaseType?.GetTypeInfo();
|
|
||||||
inheritanceLevel++;
|
|
||||||
}
|
|
||||||
while (tempTypeInfo != null && tempTypeInfo != typeof(object).GetTypeInfo()
|
|
||||||
&& inheritanceLevel < inheritanceThreshold);
|
|
||||||
}
|
|
||||||
else if (inherit && memberInfo.MemberType == MemberTypes.Method)
|
|
||||||
{
|
|
||||||
// This code is based on the code for fetching CustomAttributes in System.Reflection.CustomAttribute(RuntimeMethodInfo method, RuntimeType caType, bool inherit).
|
|
||||||
var tempMethodInfo = memberInfo as MethodInfo;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
var attributes = CustomAttributeData.GetCustomAttributes(tempMethodInfo);
|
|
||||||
this.AddNewAttributes(
|
|
||||||
attributes,
|
|
||||||
shouldGetAllAttributes,
|
|
||||||
type,
|
|
||||||
uniqueAttributes,
|
|
||||||
nonUniqueAttributes);
|
|
||||||
var baseDefinition = tempMethodInfo.GetBaseDefinition();
|
|
||||||
|
|
||||||
if (baseDefinition != null)
|
|
||||||
{
|
|
||||||
if (string.Equals(
|
|
||||||
string.Concat(tempMethodInfo.DeclaringType.FullName, tempMethodInfo.Name),
|
|
||||||
string.Concat(baseDefinition.DeclaringType.FullName, baseDefinition.Name)))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tempMethodInfo = baseDefinition;
|
|
||||||
inheritanceLevel++;
|
|
||||||
}
|
|
||||||
while (tempMethodInfo != null && inheritanceLevel < inheritanceThreshold);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Ideally we should not be reaching here. We only query for attributes on types/methods currently.
|
|
||||||
// Return the attributes that CustomAttributeData returns in this cases not considering inheritance.
|
|
||||||
var firstLevelAttributes =
|
|
||||||
CustomAttributeData.GetCustomAttributes(memberInfo);
|
|
||||||
this.AddNewAttributes(firstLevelAttributes, shouldGetAllAttributes, type, uniqueAttributes, nonUniqueAttributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
nonUniqueAttributes.AddRange(uniqueAttributes.Values);
|
|
||||||
return nonUniqueAttributes.ToArray();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
internal object[] GetCustomAttributes(Assembly assembly, Type type)
|
|
||||||
{
|
{
|
||||||
if (assembly.ReflectionOnly)
|
List<object> nonUniqueAttributes = new();
|
||||||
|
Dictionary<string, object> uniqueAttributes = new();
|
||||||
|
|
||||||
|
var inheritanceThreshold = 10;
|
||||||
|
var inheritanceLevel = 0;
|
||||||
|
|
||||||
|
if (inherit && memberInfo.MemberType == MemberTypes.TypeInfo)
|
||||||
{
|
{
|
||||||
List<CustomAttributeData> customAttributes = new();
|
// This code is based on the code for fetching CustomAttributes in System.Reflection.CustomAttribute(RuntimeType type, RuntimeType caType, bool inherit)
|
||||||
customAttributes.AddRange(CustomAttributeData.GetCustomAttributes(assembly));
|
var tempTypeInfo = memberInfo as TypeInfo;
|
||||||
|
|
||||||
List<object> attributesArray = new();
|
do
|
||||||
|
|
||||||
foreach (var attribute in customAttributes)
|
|
||||||
{
|
{
|
||||||
if (this.IsTypeInheriting(attribute.Constructor.DeclaringType, type)
|
var attributes = CustomAttributeData.GetCustomAttributes(tempTypeInfo);
|
||||||
|| attribute.Constructor.DeclaringType.AssemblyQualifiedName.Equals(
|
this.AddNewAttributes(
|
||||||
type.AssemblyQualifiedName))
|
attributes,
|
||||||
|
shouldGetAllAttributes,
|
||||||
|
type,
|
||||||
|
uniqueAttributes,
|
||||||
|
nonUniqueAttributes);
|
||||||
|
tempTypeInfo = tempTypeInfo.BaseType?.GetTypeInfo();
|
||||||
|
inheritanceLevel++;
|
||||||
|
}
|
||||||
|
while (tempTypeInfo != null && tempTypeInfo != typeof(object).GetTypeInfo()
|
||||||
|
&& inheritanceLevel < inheritanceThreshold);
|
||||||
|
}
|
||||||
|
else if (inherit && memberInfo.MemberType == MemberTypes.Method)
|
||||||
|
{
|
||||||
|
// This code is based on the code for fetching CustomAttributes in System.Reflection.CustomAttribute(RuntimeMethodInfo method, RuntimeType caType, bool inherit).
|
||||||
|
var tempMethodInfo = memberInfo as MethodInfo;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
var attributes = CustomAttributeData.GetCustomAttributes(tempMethodInfo);
|
||||||
|
this.AddNewAttributes(
|
||||||
|
attributes,
|
||||||
|
shouldGetAllAttributes,
|
||||||
|
type,
|
||||||
|
uniqueAttributes,
|
||||||
|
nonUniqueAttributes);
|
||||||
|
var baseDefinition = tempMethodInfo.GetBaseDefinition();
|
||||||
|
|
||||||
|
if (baseDefinition != null)
|
||||||
{
|
{
|
||||||
Attribute attributeInstance = CreateAttributeInstance(attribute);
|
if (string.Equals(
|
||||||
if (attributeInstance != null)
|
string.Concat(tempMethodInfo.DeclaringType.FullName, tempMethodInfo.Name),
|
||||||
|
string.Concat(baseDefinition.DeclaringType.FullName, baseDefinition.Name)))
|
||||||
{
|
{
|
||||||
attributesArray.Add(attributeInstance);
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return attributesArray.ToArray();
|
tempMethodInfo = baseDefinition;
|
||||||
|
inheritanceLevel++;
|
||||||
|
}
|
||||||
|
while (tempMethodInfo != null && inheritanceLevel < inheritanceThreshold);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return assembly.GetCustomAttributes(type).ToArray();
|
// Ideally we should not be reaching here. We only query for attributes on types/methods currently.
|
||||||
|
// Return the attributes that CustomAttributeData returns in this cases not considering inheritance.
|
||||||
|
var firstLevelAttributes =
|
||||||
|
CustomAttributeData.GetCustomAttributes(memberInfo);
|
||||||
|
this.AddNewAttributes(firstLevelAttributes, shouldGetAllAttributes, type, uniqueAttributes, nonUniqueAttributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nonUniqueAttributes.AddRange(uniqueAttributes.Values);
|
||||||
|
return nonUniqueAttributes.ToArray();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
internal object[] GetCustomAttributes(Assembly assembly, Type type)
|
||||||
/// Create instance of the attribute for reflection only load.
|
{
|
||||||
/// </summary>
|
if (assembly.ReflectionOnly)
|
||||||
/// <param name="attributeData">The attribute data.</param>
|
|
||||||
/// <returns>An attribute.</returns>
|
|
||||||
private static Attribute CreateAttributeInstance(CustomAttributeData attributeData)
|
|
||||||
{
|
{
|
||||||
object attribute = null;
|
List<CustomAttributeData> customAttributes = new();
|
||||||
try
|
customAttributes.AddRange(CustomAttributeData.GetCustomAttributes(assembly));
|
||||||
|
|
||||||
|
List<object> attributesArray = new();
|
||||||
|
|
||||||
|
foreach (var attribute in customAttributes)
|
||||||
{
|
{
|
||||||
// Create instance of attribute. For some case, constructor param is returned as ReadOnlyCollection
|
if (this.IsTypeInheriting(attribute.Constructor.DeclaringType, type)
|
||||||
// instead of array. So convert it to array else constructor invoke will fail.
|
|| attribute.Constructor.DeclaringType.AssemblyQualifiedName.Equals(
|
||||||
Type attributeType = Type.GetType(attributeData.Constructor.DeclaringType.AssemblyQualifiedName);
|
type.AssemblyQualifiedName))
|
||||||
|
|
||||||
List<Type> constructorParameters = new();
|
|
||||||
List<object> constructorArguments = new();
|
|
||||||
foreach (var parameter in attributeData.ConstructorArguments)
|
|
||||||
{
|
{
|
||||||
Type parameterType = Type.GetType(parameter.ArgumentType.AssemblyQualifiedName);
|
Attribute attributeInstance = CreateAttributeInstance(attribute);
|
||||||
constructorParameters.Add(parameterType);
|
if (attributeInstance != null)
|
||||||
if (parameterType.IsArray)
|
|
||||||
{
|
{
|
||||||
if (parameter.Value is IEnumerable enumerable)
|
attributesArray.Add(attributeInstance);
|
||||||
{
|
}
|
||||||
ArrayList list = new();
|
}
|
||||||
foreach (var item in enumerable)
|
}
|
||||||
{
|
|
||||||
if (item is CustomAttributeTypedArgument argument)
|
|
||||||
{
|
|
||||||
list.Add(argument.Value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
list.Add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constructorArguments.Add(list.ToArray(parameterType.GetElementType()));
|
return attributesArray.ToArray();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
return assembly.GetCustomAttributes(type).ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create instance of the attribute for reflection only load.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="attributeData">The attribute data.</param>
|
||||||
|
/// <returns>An attribute.</returns>
|
||||||
|
private static Attribute CreateAttributeInstance(CustomAttributeData attributeData)
|
||||||
|
{
|
||||||
|
object attribute = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Create instance of attribute. For some case, constructor param is returned as ReadOnlyCollection
|
||||||
|
// instead of array. So convert it to array else constructor invoke will fail.
|
||||||
|
Type attributeType = Type.GetType(attributeData.Constructor.DeclaringType.AssemblyQualifiedName);
|
||||||
|
|
||||||
|
List<Type> constructorParameters = new();
|
||||||
|
List<object> constructorArguments = new();
|
||||||
|
foreach (var parameter in attributeData.ConstructorArguments)
|
||||||
|
{
|
||||||
|
Type parameterType = Type.GetType(parameter.ArgumentType.AssemblyQualifiedName);
|
||||||
|
constructorParameters.Add(parameterType);
|
||||||
|
if (parameterType.IsArray)
|
||||||
|
{
|
||||||
|
if (parameter.Value is IEnumerable enumerable)
|
||||||
|
{
|
||||||
|
ArrayList list = new();
|
||||||
|
foreach (var item in enumerable)
|
||||||
{
|
{
|
||||||
constructorArguments.Add(parameter.Value);
|
if (item is CustomAttributeTypedArgument argument)
|
||||||
|
{
|
||||||
|
list.Add(argument.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
list.Add(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructorArguments.Add(list.ToArray(parameterType.GetElementType()));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
constructorArguments.Add(parameter.Value);
|
constructorArguments.Add(parameter.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
ConstructorInfo constructor = attributeType.GetConstructor(constructorParameters.ToArray());
|
|
||||||
attribute = constructor.Invoke(constructorArguments.ToArray());
|
|
||||||
|
|
||||||
foreach (var namedArgument in attributeData.NamedArguments)
|
|
||||||
{
|
{
|
||||||
attributeType.GetProperty(namedArgument.MemberInfo.Name).SetValue(attribute, namedArgument.TypedValue.Value, null);
|
constructorArguments.Add(parameter.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not able to create instance of attribute ignore attribute. (May happen for custom user defined attributes).
|
ConstructorInfo constructor = attributeType.GetConstructor(constructorParameters.ToArray());
|
||||||
catch (BadImageFormatException)
|
attribute = constructor.Invoke(constructorArguments.ToArray());
|
||||||
{
|
|
||||||
}
|
|
||||||
catch (FileLoadException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
catch (TypeLoadException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
return attribute as Attribute;
|
foreach (var namedArgument in attributeData.NamedArguments)
|
||||||
|
{
|
||||||
|
attributeType.GetProperty(namedArgument.MemberInfo.Name).SetValue(attribute, namedArgument.TypedValue.Value, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddNewAttributes(
|
// If not able to create instance of attribute ignore attribute. (May happen for custom user defined attributes).
|
||||||
IList<CustomAttributeData> customAttributes,
|
catch (BadImageFormatException)
|
||||||
bool shouldGetAllAttributes,
|
|
||||||
Type type,
|
|
||||||
Dictionary<string, object> uniqueAttributes,
|
|
||||||
List<object> nonUniqueAttributes)
|
|
||||||
{
|
{
|
||||||
foreach (var attribute in customAttributes)
|
}
|
||||||
|
catch (FileLoadException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (TypeLoadException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
return attribute as Attribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddNewAttributes(
|
||||||
|
IList<CustomAttributeData> customAttributes,
|
||||||
|
bool shouldGetAllAttributes,
|
||||||
|
Type type,
|
||||||
|
Dictionary<string, object> uniqueAttributes,
|
||||||
|
List<object> nonUniqueAttributes)
|
||||||
|
{
|
||||||
|
foreach (var attribute in customAttributes)
|
||||||
|
{
|
||||||
|
if (shouldGetAllAttributes
|
||||||
|
|| (this.IsTypeInheriting(attribute.Constructor.DeclaringType, type)
|
||||||
|
|| attribute.Constructor.DeclaringType.AssemblyQualifiedName.Equals(
|
||||||
|
type.AssemblyQualifiedName)))
|
||||||
{
|
{
|
||||||
if (shouldGetAllAttributes
|
Attribute attributeInstance = CreateAttributeInstance(attribute);
|
||||||
|| (this.IsTypeInheriting(attribute.Constructor.DeclaringType, type)
|
if (attributeInstance != null)
|
||||||
|| attribute.Constructor.DeclaringType.AssemblyQualifiedName.Equals(
|
|
||||||
type.AssemblyQualifiedName)))
|
|
||||||
{
|
{
|
||||||
Attribute attributeInstance = CreateAttributeInstance(attribute);
|
if (this.GetCustomAttributes(
|
||||||
if (attributeInstance != null)
|
attributeInstance.GetType().GetTypeInfo(),
|
||||||
|
typeof(AttributeUsageAttribute),
|
||||||
|
true).FirstOrDefault() is AttributeUsageAttribute attributeUsageAttribute && !attributeUsageAttribute.AllowMultiple)
|
||||||
{
|
{
|
||||||
if (this.GetCustomAttributes(
|
if (!uniqueAttributes.ContainsKey(attributeInstance.GetType().FullName))
|
||||||
attributeInstance.GetType().GetTypeInfo(),
|
|
||||||
typeof(AttributeUsageAttribute),
|
|
||||||
true).FirstOrDefault() is AttributeUsageAttribute attributeUsageAttribute && !attributeUsageAttribute.AllowMultiple)
|
|
||||||
{
|
{
|
||||||
if (!uniqueAttributes.ContainsKey(attributeInstance.GetType().FullName))
|
uniqueAttributes.Add(attributeInstance.GetType().FullName, attributeInstance);
|
||||||
{
|
|
||||||
uniqueAttributes.Add(attributeInstance.GetType().FullName, attributeInstance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
nonUniqueAttributes.Add(attributeInstance);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nonUniqueAttributes.Add(attributeInstance);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check whether the member is loaded in a reflection only context.
|
/// Check whether the member is loaded in a reflection only context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="memberInfo"> The member Info. </param>
|
/// <param name="memberInfo"> The member Info. </param>
|
||||||
/// <returns> True if the member is loaded in a reflection only context. </returns>
|
/// <returns> True if the member is loaded in a reflection only context. </returns>
|
||||||
private bool IsReflectionOnlyLoad(MemberInfo memberInfo)
|
private bool IsReflectionOnlyLoad(MemberInfo memberInfo)
|
||||||
|
{
|
||||||
|
if (memberInfo != null)
|
||||||
{
|
{
|
||||||
if (memberInfo != null)
|
return memberInfo.Module.Assembly.ReflectionOnly;
|
||||||
{
|
|
||||||
return memberInfo.Module.Assembly.ReflectionOnly;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsTypeInheriting(Type type1, Type type2)
|
return false;
|
||||||
{
|
}
|
||||||
while (type1 != null)
|
|
||||||
{
|
|
||||||
if (type1.AssemblyQualifiedName.Equals(type2.AssemblyQualifiedName))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
type1 = type1.GetTypeInfo().BaseType;
|
private bool IsTypeInheriting(Type type1, Type type2)
|
||||||
|
{
|
||||||
|
while (type1 != null)
|
||||||
|
{
|
||||||
|
if (type1.AssemblyQualifiedName.Equals(type2.AssemblyQualifiedName))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
type1 = type1.GetTypeInfo().BaseType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,28 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Security.Policy;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This interface is an abstraction over the AppDomain APIs
|
||||||
|
/// </summary>
|
||||||
|
internal interface IAppDomain
|
||||||
{
|
{
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Security.Policy;
|
/// Unloads the specified application domain.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="appDomain">An application domain to unload.</param>
|
||||||
|
void Unload(AppDomain appDomain);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This interface is an abstraction over the AppDomain APIs
|
/// Creates a new application domain using the specified name, evidence, and application domain setup information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal interface IAppDomain
|
/// <param name="friendlyName">The friendly name of the domain.</param>
|
||||||
{
|
/// <param name="securityInfo">Evidence that establishes the identity of the code that runs in the application domain. Pass null to use the evidence of the current application domain.</param>
|
||||||
/// <summary>
|
/// <param name="info">An object that contains application domain initialization information.</param>
|
||||||
/// Unloads the specified application domain.
|
/// <returns>The newly created application domain.</returns>
|
||||||
/// </summary>
|
AppDomain CreateDomain(string friendlyName, Evidence securityInfo, AppDomainSetup info);
|
||||||
/// <param name="appDomain">An application domain to unload.</param>
|
|
||||||
void Unload(AppDomain appDomain);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new application domain using the specified name, evidence, and application domain setup information.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="friendlyName">The friendly name of the domain.</param>
|
|
||||||
/// <param name="securityInfo">Evidence that establishes the identity of the code that runs in the application domain. Pass null to use the evidence of the current application domain.</param>
|
|
||||||
/// <param name="info">An object that contains application domain initialization information.</param>
|
|
||||||
/// <returns>The newly created application domain.</returns>
|
|
||||||
AppDomain CreateDomain(string friendlyName, Evidence securityInfo, AppDomainSetup info);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,23 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities;
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
internal interface IAssemblyUtility
|
||||||
{
|
{
|
||||||
using System.Reflection;
|
/// <summary>
|
||||||
|
/// Loads an assembly into the reflection-only context, given its path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assemblyPath">The path of the file that contains the manifest of the assembly.</param>
|
||||||
|
/// <returns>The loaded assembly.</returns>
|
||||||
|
Assembly ReflectionOnlyLoadFrom(string assemblyPath);
|
||||||
|
|
||||||
internal interface IAssemblyUtility
|
/// <summary>
|
||||||
{
|
/// Loads an assembly into the reflection-only context, given its display name.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Loads an assembly into the reflection-only context, given its path.
|
/// <param name="assemblyString">The display name of the assembly, as returned by the System.Reflection.AssemblyName.FullName property.</param>
|
||||||
/// </summary>
|
/// <returns>The loaded assembly.</returns>
|
||||||
/// <param name="assemblyPath">The path of the file that contains the manifest of the assembly.</param>
|
Assembly ReflectionOnlyLoad(string assemblyString);
|
||||||
/// <returns>The loaded assembly.</returns>
|
|
||||||
Assembly ReflectionOnlyLoadFrom(string assemblyPath);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Loads an assembly into the reflection-only context, given its display name.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assemblyString">The display name of the assembly, as returned by the System.Reflection.AssemblyName.FullName property.</param>
|
|
||||||
/// <returns>The loaded assembly.</returns>
|
|
||||||
Assembly ReflectionOnlyLoad(string assemblyString);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,53 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Permutation of integers from 0 to (numberOfObjects - 1), in random order and in the end all values are returned.
|
||||||
|
/// Used to get random permutation for data row access in data driven test.
|
||||||
|
/// </summary>
|
||||||
|
internal class RandomIntPermutation : IEnumerable<int>
|
||||||
{
|
{
|
||||||
using System;
|
private int[] objects;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
/// <summary>
|
public RandomIntPermutation(int numberOfObjects)
|
||||||
/// Permutation of integers from 0 to (numberOfObjects - 1), in random order and in the end all values are returned.
|
|
||||||
/// Used to get random permutation for data row access in data driven test.
|
|
||||||
/// </summary>
|
|
||||||
internal class RandomIntPermutation : IEnumerable<int>
|
|
||||||
{
|
{
|
||||||
private int[] objects;
|
if (numberOfObjects < 0)
|
||||||
|
|
||||||
public RandomIntPermutation(int numberOfObjects)
|
|
||||||
{
|
{
|
||||||
if (numberOfObjects < 0)
|
throw new ArgumentException(Resource.WrongNumberOfObjects, nameof(numberOfObjects));
|
||||||
{
|
|
||||||
throw new ArgumentException(Resource.WrongNumberOfObjects, nameof(numberOfObjects));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.objects = new int[numberOfObjects];
|
|
||||||
for (int i = 0; i < numberOfObjects; ++i)
|
|
||||||
{
|
|
||||||
this.objects[i] = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
Random random = new();
|
|
||||||
for (int last = this.objects.Length - 1; last > 0; --last)
|
|
||||||
{
|
|
||||||
// Swap last and at random position which can be last in which case we don't swap.
|
|
||||||
int position = random.Next(last); // 0 .. last - 1
|
|
||||||
(this.objects[position], this.objects[last]) = (this.objects[last], this.objects[position]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerator<int> GetEnumerator()
|
this.objects = new int[numberOfObjects];
|
||||||
|
for (int i = 0; i < numberOfObjects; ++i)
|
||||||
{
|
{
|
||||||
// Iterate over created permutation, do not change it.
|
this.objects[i] = i;
|
||||||
for (int i = 0; i < this.objects.Length; ++i)
|
|
||||||
{
|
|
||||||
yield return this.objects[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
Random random = new();
|
||||||
|
for (int last = this.objects.Length - 1; last > 0; --last)
|
||||||
{
|
{
|
||||||
return this.GetEnumerator();
|
// Swap last and at random position which can be last in which case we don't swap.
|
||||||
|
int position = random.Next(last); // 0 .. last - 1
|
||||||
|
(this.objects[position], this.objects[last]) = (this.objects[last], this.objects[position]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerator<int> GetEnumerator()
|
||||||
|
{
|
||||||
|
// Iterate over created permutation, do not change it.
|
||||||
|
for (int i = 0; i < this.objects.Length; ++i)
|
||||||
|
{
|
||||||
|
yield return this.objects[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return this.GetEnumerator();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,40 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Permutation of integers from 0 to (numberOfObjects - 1) returned by increment of 1.
|
||||||
|
/// Used to get sequential permutation for data row access in data driven test.
|
||||||
|
/// </summary>
|
||||||
|
internal class SequentialIntPermutation : IEnumerable<int>
|
||||||
{
|
{
|
||||||
using System;
|
private readonly int numberOfObjects;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
/// <summary>
|
public SequentialIntPermutation(int numberOfObjects)
|
||||||
/// Permutation of integers from 0 to (numberOfObjects - 1) returned by increment of 1.
|
|
||||||
/// Used to get sequential permutation for data row access in data driven test.
|
|
||||||
/// </summary>
|
|
||||||
internal class SequentialIntPermutation : IEnumerable<int>
|
|
||||||
{
|
{
|
||||||
private readonly int numberOfObjects;
|
if (numberOfObjects < 0)
|
||||||
|
|
||||||
public SequentialIntPermutation(int numberOfObjects)
|
|
||||||
{
|
{
|
||||||
if (numberOfObjects < 0)
|
throw new ArgumentException(Resource.WrongNumberOfObjects, nameof(numberOfObjects));
|
||||||
{
|
|
||||||
throw new ArgumentException(Resource.WrongNumberOfObjects, nameof(numberOfObjects));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.numberOfObjects = numberOfObjects;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerator<int> GetEnumerator()
|
this.numberOfObjects = numberOfObjects;
|
||||||
{
|
}
|
||||||
for (int i = 0; i < this.numberOfObjects; ++i)
|
|
||||||
{
|
|
||||||
yield return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
public IEnumerator<int> GetEnumerator()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < this.numberOfObjects; ++i)
|
||||||
{
|
{
|
||||||
return this.GetEnumerator();
|
yield return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return this.GetEnumerator();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,262 +1,261 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using static System.String;
|
||||||
|
|
||||||
|
public static class VSInstallationUtilities
|
||||||
{
|
{
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Diagnostics;
|
/// Public assemblies directory name
|
||||||
using System.IO;
|
/// </summary>
|
||||||
using System.Runtime.InteropServices;
|
private const string PublicAssembliesDirectoryName = "PublicAssemblies";
|
||||||
using static System.String;
|
|
||||||
|
|
||||||
public static class VSInstallationUtilities
|
/// <summary>
|
||||||
|
/// Folder name of private assemblies
|
||||||
|
/// </summary>
|
||||||
|
private const string PrivateAssembliesFolderName = "PrivateAssemblies";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The manifest file name to determine if it is running in portable mode
|
||||||
|
/// </summary>
|
||||||
|
private const string PortableVsTestManifestFilename = "Portable.VsTest.Manifest";
|
||||||
|
|
||||||
|
private static string vsInstallPath = null;
|
||||||
|
|
||||||
|
private static bool vsInstallPathEvaluated = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the visual studio installation path on the local machine.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>VS install path</returns>
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Need to ignore failures to read the registry settings")]
|
||||||
|
public static string VSInstallPath
|
||||||
{
|
{
|
||||||
/// <summary>
|
get
|
||||||
/// Public assemblies directory name
|
|
||||||
/// </summary>
|
|
||||||
private const string PublicAssembliesDirectoryName = "PublicAssemblies";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Folder name of private assemblies
|
|
||||||
/// </summary>
|
|
||||||
private const string PrivateAssembliesFolderName = "PrivateAssemblies";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The manifest file name to determine if it is running in portable mode
|
|
||||||
/// </summary>
|
|
||||||
private const string PortableVsTestManifestFilename = "Portable.VsTest.Manifest";
|
|
||||||
|
|
||||||
private static string vsInstallPath = null;
|
|
||||||
|
|
||||||
private static bool vsInstallPathEvaluated = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the visual studio installation path on the local machine.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>VS install path</returns>
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Need to ignore failures to read the registry settings")]
|
|
||||||
public static string VSInstallPath
|
|
||||||
{
|
{
|
||||||
get
|
if (!vsInstallPathEvaluated)
|
||||||
{
|
{
|
||||||
if (!vsInstallPathEvaluated)
|
try
|
||||||
{
|
{
|
||||||
try
|
vsInstallPath = null;
|
||||||
{
|
|
||||||
vsInstallPath = null;
|
|
||||||
|
|
||||||
// Use the Setup API to find the installation folder for currently running VS instance.
|
// Use the Setup API to find the installation folder for currently running VS instance.
|
||||||
if (new SetupConfiguration() is ISetupConfiguration setupConfiguration)
|
if (new SetupConfiguration() is ISetupConfiguration setupConfiguration)
|
||||||
{
|
|
||||||
var currentConfiguration = setupConfiguration.GetInstanceForCurrentProcess();
|
|
||||||
var currentInstallationPath = currentConfiguration.GetInstallationPath();
|
|
||||||
vsInstallPath = Path.Combine(currentInstallationPath, @"Common7\IDE");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
{
|
||||||
// SetupConfiguration won't work if VS is not installed or VS is pre-vs2017 version.
|
var currentConfiguration = setupConfiguration.GetInstanceForCurrentProcess();
|
||||||
// So ignore all exception from it.
|
var currentInstallationPath = currentConfiguration.GetInstallationPath();
|
||||||
}
|
vsInstallPath = Path.Combine(currentInstallationPath, @"Common7\IDE");
|
||||||
finally
|
|
||||||
{
|
|
||||||
vsInstallPathEvaluated = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
return vsInstallPath;
|
{
|
||||||
}
|
// SetupConfiguration won't work if VS is not installed or VS is pre-vs2017 version.
|
||||||
}
|
// So ignore all exception from it.
|
||||||
|
}
|
||||||
/// <summary>
|
finally
|
||||||
/// Gets path to public assemblies.
|
{
|
||||||
///
|
vsInstallPathEvaluated = true;
|
||||||
/// Returns null if VS is not installed on this machine.
|
}
|
||||||
/// </summary>
|
|
||||||
public static string PathToPublicAssemblies => GetFullPath(PublicAssembliesDirectoryName);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets path to private assemblies.
|
|
||||||
///
|
|
||||||
/// Returns null if VS is not installed on this machine.
|
|
||||||
/// </summary>
|
|
||||||
public static string PathToPrivateAssemblies => GetFullPath(PrivateAssembliesFolderName);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Is Current process running in Portable Mode
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True, if portable mode; false, otherwise</returns>
|
|
||||||
public static bool IsCurrentProcessRunningInPortableMode()
|
|
||||||
{
|
|
||||||
return IsProcessRunningInPortableMode(Process.GetCurrentProcess().MainModule.FileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Is the EXE specified running in Portable Mode
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="exeName">EXE name.</param>
|
|
||||||
/// <returns>True, if portable mode; false, otherwise</returns>
|
|
||||||
public static bool IsProcessRunningInPortableMode(string exeName)
|
|
||||||
{
|
|
||||||
// Get the directory of the exe
|
|
||||||
var exeDir = Path.GetDirectoryName(exeName);
|
|
||||||
if (!string.IsNullOrEmpty(exeDir))
|
|
||||||
{
|
|
||||||
return File.Exists(Path.Combine(exeDir, PortableVsTestManifestFilename));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return vsInstallPath;
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetFullPath(string folderName)
|
|
||||||
{
|
|
||||||
var vsInstallDir = VSInstallPath;
|
|
||||||
return IsNullOrWhiteSpace(vsInstallDir?.Trim()) ? null : Path.Combine(vsInstallDir, folderName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Information about an instance of a product.
|
|
||||||
/// </summary>
|
|
||||||
[Guid("B41463C3-8866-43B5-BC33-2B0676F7F42E")]
|
|
||||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
||||||
#pragma warning disable SA1201 // Elements must appear in the correct order
|
|
||||||
public interface ISetupInstance
|
|
||||||
#pragma warning restore SA1201 // Elements must appear in the correct order
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the instance identifier (should match the name of the parent instance directory).
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The instance identifier.</returns>
|
|
||||||
[return: MarshalAs(UnmanagedType.BStr)]
|
|
||||||
string GetInstanceId();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the local date and time when the installation was originally installed.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The local date and time when the installation was originally installed.</returns>
|
|
||||||
[return: MarshalAs(UnmanagedType.Struct)]
|
|
||||||
System.Runtime.InteropServices.ComTypes.FILETIME GetInstallDate();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the unique name of the installation, often indicating the branch and other information used for telemetry.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The unique name of the installation, often indicating the branch and other information used for telemetry.</returns>
|
|
||||||
[return: MarshalAs(UnmanagedType.BStr)]
|
|
||||||
string GetInstallationName();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the path to the installation root of the product.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The path to the installation root of the product.</returns>
|
|
||||||
[return: MarshalAs(UnmanagedType.BStr)]
|
|
||||||
string GetInstallationPath();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the version of the product installed in this instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The version of the product installed in this instance.</returns>
|
|
||||||
[return: MarshalAs(UnmanagedType.BStr)]
|
|
||||||
string GetInstallationVersion();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the display name (title) of the product installed in this instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="lcid">The LCID for the display name.</param>
|
|
||||||
/// <returns>The display name (title) of the product installed in this instance.</returns>
|
|
||||||
[return: MarshalAs(UnmanagedType.BStr)]
|
|
||||||
string GetDisplayName([In, MarshalAs(UnmanagedType.U4)] int lcid);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the description of the product installed in this instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="lcid">The LCID for the description.</param>
|
|
||||||
/// <returns>The description of the product installed in this instance.</returns>
|
|
||||||
[return: MarshalAs(UnmanagedType.BStr)]
|
|
||||||
string GetDescription([In, MarshalAs(UnmanagedType.U4)] int lcid);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resolves the optional relative path to the root path of the instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="relativePath">A relative path within the instance to resolve, or NULL to get the root path.</param>
|
|
||||||
/// <returns>The full path to the optional relative path within the instance. If the relative path is NULL, the root path will always terminate in a backslash.</returns>
|
|
||||||
[return: MarshalAs(UnmanagedType.BStr)]
|
|
||||||
string ResolvePath([In, MarshalAs(UnmanagedType.LPWStr)] string relativePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A enumerator of installed <see cref="ISetupInstance"/> objects.
|
|
||||||
/// </summary>
|
|
||||||
[Guid("6380BCFF-41D3-4B2E-8B2E-BF8A6810C848")]
|
|
||||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
||||||
public interface IEnumSetupInstances
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieves the next set of product instances in the enumeration sequence.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="celt">The number of product instances to retrieve.</param>
|
|
||||||
/// <param name="rgelt">A pointer to an array of <see cref="ISetupInstance"/>.</param>
|
|
||||||
/// <param name="pceltFetched">A pointer to the number of product instances retrieved. If celt is 1 this parameter may be NULL.</param>
|
|
||||||
void Next(
|
|
||||||
[In] int celt,
|
|
||||||
[Out, MarshalAs(UnmanagedType.Interface)] out ISetupInstance rgelt,
|
|
||||||
[Out] out int pceltFetched);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Skips the next set of product instances in the enumeration sequence.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="celt">The number of product instances to skip.</param>
|
|
||||||
void Skip([In, MarshalAs(UnmanagedType.U4)] int celt);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resets the enumeration sequence to the beginning.
|
|
||||||
/// </summary>
|
|
||||||
void Reset();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new enumeration object in the same state as the current enumeration object: the new object points to the same place in the enumeration sequence.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A pointer to a pointer to a new <see cref="IEnumSetupInstances"/> interface. If the method fails, this parameter is undefined.</returns>
|
|
||||||
[return: MarshalAs(UnmanagedType.Interface)]
|
|
||||||
IEnumSetupInstances Clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets information about product instances set up on the machine.
|
|
||||||
/// </summary>
|
|
||||||
[Guid("42843719-DB4C-46C2-8E7C-64F1816EFD5B")]
|
|
||||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
||||||
public interface ISetupConfiguration
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Enumerates all product instances installed.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>An enumeration of installed product instances.</returns>
|
|
||||||
[return: MarshalAs(UnmanagedType.Interface)]
|
|
||||||
IEnumSetupInstances EnumInstances();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the instance for the current process path.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The instance for the current process path.</returns>
|
|
||||||
[return: MarshalAs(UnmanagedType.Interface)]
|
|
||||||
ISetupInstance GetInstanceForCurrentProcess();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the instance for the given path.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="wzPath">Path used to determine instance</param>
|
|
||||||
/// <returns>The instance for the given path.</returns>
|
|
||||||
[return: MarshalAs(UnmanagedType.Interface)]
|
|
||||||
ISetupInstance GetInstanceForPath([In, MarshalAs(UnmanagedType.LPWStr)] string wzPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// CoClass that implements <see cref="ISetupConfiguration"/>.
|
|
||||||
/// </summary>
|
|
||||||
[ComImport]
|
|
||||||
[Guid("177F0C4A-1CD3-4DE7-A32C-71DBBB9FA36D")]
|
|
||||||
public class SetupConfiguration
|
|
||||||
{
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets path to public assemblies.
|
||||||
|
///
|
||||||
|
/// Returns null if VS is not installed on this machine.
|
||||||
|
/// </summary>
|
||||||
|
public static string PathToPublicAssemblies => GetFullPath(PublicAssembliesDirectoryName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets path to private assemblies.
|
||||||
|
///
|
||||||
|
/// Returns null if VS is not installed on this machine.
|
||||||
|
/// </summary>
|
||||||
|
public static string PathToPrivateAssemblies => GetFullPath(PrivateAssembliesFolderName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is Current process running in Portable Mode
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True, if portable mode; false, otherwise</returns>
|
||||||
|
public static bool IsCurrentProcessRunningInPortableMode()
|
||||||
|
{
|
||||||
|
return IsProcessRunningInPortableMode(Process.GetCurrentProcess().MainModule.FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is the EXE specified running in Portable Mode
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="exeName">EXE name.</param>
|
||||||
|
/// <returns>True, if portable mode; false, otherwise</returns>
|
||||||
|
public static bool IsProcessRunningInPortableMode(string exeName)
|
||||||
|
{
|
||||||
|
// Get the directory of the exe
|
||||||
|
var exeDir = Path.GetDirectoryName(exeName);
|
||||||
|
if (!string.IsNullOrEmpty(exeDir))
|
||||||
|
{
|
||||||
|
return File.Exists(Path.Combine(exeDir, PortableVsTestManifestFilename));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetFullPath(string folderName)
|
||||||
|
{
|
||||||
|
var vsInstallDir = VSInstallPath;
|
||||||
|
return IsNullOrWhiteSpace(vsInstallDir?.Trim()) ? null : Path.Combine(vsInstallDir, folderName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Information about an instance of a product.
|
||||||
|
/// </summary>
|
||||||
|
[Guid("B41463C3-8866-43B5-BC33-2B0676F7F42E")]
|
||||||
|
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
#pragma warning disable SA1201 // Elements must appear in the correct order
|
||||||
|
public interface ISetupInstance
|
||||||
|
#pragma warning restore SA1201 // Elements must appear in the correct order
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instance identifier (should match the name of the parent instance directory).
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The instance identifier.</returns>
|
||||||
|
[return: MarshalAs(UnmanagedType.BStr)]
|
||||||
|
string GetInstanceId();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the local date and time when the installation was originally installed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The local date and time when the installation was originally installed.</returns>
|
||||||
|
[return: MarshalAs(UnmanagedType.Struct)]
|
||||||
|
System.Runtime.InteropServices.ComTypes.FILETIME GetInstallDate();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the unique name of the installation, often indicating the branch and other information used for telemetry.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The unique name of the installation, often indicating the branch and other information used for telemetry.</returns>
|
||||||
|
[return: MarshalAs(UnmanagedType.BStr)]
|
||||||
|
string GetInstallationName();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the path to the installation root of the product.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The path to the installation root of the product.</returns>
|
||||||
|
[return: MarshalAs(UnmanagedType.BStr)]
|
||||||
|
string GetInstallationPath();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the version of the product installed in this instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The version of the product installed in this instance.</returns>
|
||||||
|
[return: MarshalAs(UnmanagedType.BStr)]
|
||||||
|
string GetInstallationVersion();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the display name (title) of the product installed in this instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lcid">The LCID for the display name.</param>
|
||||||
|
/// <returns>The display name (title) of the product installed in this instance.</returns>
|
||||||
|
[return: MarshalAs(UnmanagedType.BStr)]
|
||||||
|
string GetDisplayName([In, MarshalAs(UnmanagedType.U4)] int lcid);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the description of the product installed in this instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lcid">The LCID for the description.</param>
|
||||||
|
/// <returns>The description of the product installed in this instance.</returns>
|
||||||
|
[return: MarshalAs(UnmanagedType.BStr)]
|
||||||
|
string GetDescription([In, MarshalAs(UnmanagedType.U4)] int lcid);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves the optional relative path to the root path of the instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="relativePath">A relative path within the instance to resolve, or NULL to get the root path.</param>
|
||||||
|
/// <returns>The full path to the optional relative path within the instance. If the relative path is NULL, the root path will always terminate in a backslash.</returns>
|
||||||
|
[return: MarshalAs(UnmanagedType.BStr)]
|
||||||
|
string ResolvePath([In, MarshalAs(UnmanagedType.LPWStr)] string relativePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A enumerator of installed <see cref="ISetupInstance"/> objects.
|
||||||
|
/// </summary>
|
||||||
|
[Guid("6380BCFF-41D3-4B2E-8B2E-BF8A6810C848")]
|
||||||
|
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
public interface IEnumSetupInstances
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the next set of product instances in the enumeration sequence.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="celt">The number of product instances to retrieve.</param>
|
||||||
|
/// <param name="rgelt">A pointer to an array of <see cref="ISetupInstance"/>.</param>
|
||||||
|
/// <param name="pceltFetched">A pointer to the number of product instances retrieved. If celt is 1 this parameter may be NULL.</param>
|
||||||
|
void Next(
|
||||||
|
[In] int celt,
|
||||||
|
[Out, MarshalAs(UnmanagedType.Interface)] out ISetupInstance rgelt,
|
||||||
|
[Out] out int pceltFetched);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Skips the next set of product instances in the enumeration sequence.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="celt">The number of product instances to skip.</param>
|
||||||
|
void Skip([In, MarshalAs(UnmanagedType.U4)] int celt);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the enumeration sequence to the beginning.
|
||||||
|
/// </summary>
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new enumeration object in the same state as the current enumeration object: the new object points to the same place in the enumeration sequence.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A pointer to a pointer to a new <see cref="IEnumSetupInstances"/> interface. If the method fails, this parameter is undefined.</returns>
|
||||||
|
[return: MarshalAs(UnmanagedType.Interface)]
|
||||||
|
IEnumSetupInstances Clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets information about product instances set up on the machine.
|
||||||
|
/// </summary>
|
||||||
|
[Guid("42843719-DB4C-46C2-8E7C-64F1816EFD5B")]
|
||||||
|
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
public interface ISetupConfiguration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerates all product instances installed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An enumeration of installed product instances.</returns>
|
||||||
|
[return: MarshalAs(UnmanagedType.Interface)]
|
||||||
|
IEnumSetupInstances EnumInstances();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instance for the current process path.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The instance for the current process path.</returns>
|
||||||
|
[return: MarshalAs(UnmanagedType.Interface)]
|
||||||
|
ISetupInstance GetInstanceForCurrentProcess();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instance for the given path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="wzPath">Path used to determine instance</param>
|
||||||
|
/// <returns>The instance for the given path.</returns>
|
||||||
|
[return: MarshalAs(UnmanagedType.Interface)]
|
||||||
|
ISetupInstance GetInstanceForPath([In, MarshalAs(UnmanagedType.LPWStr)] string wzPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CoClass that implements <see cref="ISetupConfiguration"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ComImport]
|
||||||
|
[Guid("177F0C4A-1CD3-4DE7-A32C-71DBBB9FA36D")]
|
||||||
|
public class SetupConfiguration
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,154 +1,153 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
internal class XmlUtilities
|
||||||
{
|
{
|
||||||
using System;
|
private const string XmlNamespace = "urn:schemas-microsoft-com:asm.v1";
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
using System.Xml;
|
|
||||||
|
|
||||||
internal class XmlUtilities
|
/// <summary>
|
||||||
|
/// Adds assembly redirection and converts the resulting config file to a byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="configFile"> The config File. </param>
|
||||||
|
/// <param name="assemblyName">The assembly name.</param>
|
||||||
|
/// <param name="oldVersion">The old version.</param>
|
||||||
|
/// <param name="newVersion">The new version.</param>
|
||||||
|
/// <returns> A byte array of the config file with the redirections added. </returns>
|
||||||
|
internal byte[] AddAssemblyRedirection(string configFile, AssemblyName assemblyName, string oldVersion, string newVersion)
|
||||||
{
|
{
|
||||||
private const string XmlNamespace = "urn:schemas-microsoft-com:asm.v1";
|
var doc = this.GetXmlDocument(configFile);
|
||||||
|
|
||||||
/// <summary>
|
var configurationElement = FindOrCreateElement(doc, doc, "configuration");
|
||||||
/// Adds assembly redirection and converts the resulting config file to a byte array.
|
var assemblyBindingSection = FindOrCreateAssemblyBindingSection(doc, configurationElement);
|
||||||
/// </summary>
|
AddAssemblyBindingRedirect(doc, assemblyBindingSection, assemblyName, oldVersion, newVersion);
|
||||||
/// <param name="configFile"> The config File. </param>
|
using var ms = new MemoryStream();
|
||||||
/// <param name="assemblyName">The assembly name.</param>
|
doc.Save(ms);
|
||||||
/// <param name="oldVersion">The old version.</param>
|
return ms.ToArray();
|
||||||
/// <param name="newVersion">The new version.</param>
|
}
|
||||||
/// <returns> A byte array of the config file with the redirections added. </returns>
|
|
||||||
internal byte[] AddAssemblyRedirection(string configFile, AssemblyName assemblyName, string oldVersion, string newVersion)
|
/// <summary>
|
||||||
|
/// Gets the Xml document from the config file. This is virtual for unit testing.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="configFile">The config file.</param>
|
||||||
|
/// <returns>An XmlDocument.</returns>
|
||||||
|
internal virtual XmlDocument GetXmlDocument(string configFile)
|
||||||
|
{
|
||||||
|
var doc = new XmlDocument();
|
||||||
|
if (!string.IsNullOrEmpty(configFile?.Trim()))
|
||||||
{
|
{
|
||||||
var doc = this.GetXmlDocument(configFile);
|
using var xmlReader = new XmlTextReader(configFile);
|
||||||
|
xmlReader.DtdProcessing = DtdProcessing.Prohibit;
|
||||||
var configurationElement = FindOrCreateElement(doc, doc, "configuration");
|
xmlReader.XmlResolver = null;
|
||||||
var assemblyBindingSection = FindOrCreateAssemblyBindingSection(doc, configurationElement);
|
doc.Load(xmlReader);
|
||||||
AddAssemblyBindingRedirect(doc, assemblyBindingSection, assemblyName, oldVersion, newVersion);
|
|
||||||
using var ms = new MemoryStream();
|
|
||||||
doc.Save(ms);
|
|
||||||
return ms.ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return doc;
|
||||||
/// Gets the Xml document from the config file. This is virtual for unit testing.
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="configFile">The config file.</param>
|
private static XmlElement FindOrCreateElement(XmlDocument doc, XmlNode parent, string name)
|
||||||
/// <returns>An XmlDocument.</returns>
|
{
|
||||||
internal virtual XmlDocument GetXmlDocument(string configFile)
|
var ret = parent[name];
|
||||||
|
|
||||||
|
if (ret != null)
|
||||||
{
|
{
|
||||||
var doc = new XmlDocument();
|
|
||||||
if (!string.IsNullOrEmpty(configFile?.Trim()))
|
|
||||||
{
|
|
||||||
using var xmlReader = new XmlTextReader(configFile);
|
|
||||||
xmlReader.DtdProcessing = DtdProcessing.Prohibit;
|
|
||||||
xmlReader.XmlResolver = null;
|
|
||||||
doc.Load(xmlReader);
|
|
||||||
}
|
|
||||||
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static XmlElement FindOrCreateElement(XmlDocument doc, XmlNode parent, string name)
|
|
||||||
{
|
|
||||||
var ret = parent[name];
|
|
||||||
|
|
||||||
if (ret != null)
|
|
||||||
{
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = doc.CreateElement(name, parent.NamespaceURI);
|
|
||||||
parent.AppendChild(ret);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static XmlElement FindOrCreateAssemblyBindingSection(XmlDocument doc, XmlElement configurationElement)
|
ret = doc.CreateElement(name, parent.NamespaceURI);
|
||||||
|
parent.AppendChild(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static XmlElement FindOrCreateAssemblyBindingSection(XmlDocument doc, XmlElement configurationElement)
|
||||||
|
{
|
||||||
|
// Each section must be created with the xmlns specified so that
|
||||||
|
// we don't end up with xmlns="" on each element.
|
||||||
|
|
||||||
|
// Find or create the runtime section (this one should not have an xmlns on it).
|
||||||
|
var runtimeSection = FindOrCreateElement(doc, configurationElement, "runtime");
|
||||||
|
|
||||||
|
// Use the assemblyBinding section if it exists; otherwise, create one.
|
||||||
|
var assemblyBindingSection = runtimeSection["assemblyBinding"];
|
||||||
|
if (assemblyBindingSection != null)
|
||||||
{
|
{
|
||||||
// Each section must be created with the xmlns specified so that
|
|
||||||
// we don't end up with xmlns="" on each element.
|
|
||||||
|
|
||||||
// Find or create the runtime section (this one should not have an xmlns on it).
|
|
||||||
var runtimeSection = FindOrCreateElement(doc, configurationElement, "runtime");
|
|
||||||
|
|
||||||
// Use the assemblyBinding section if it exists; otherwise, create one.
|
|
||||||
var assemblyBindingSection = runtimeSection["assemblyBinding"];
|
|
||||||
if (assemblyBindingSection != null)
|
|
||||||
{
|
|
||||||
return assemblyBindingSection;
|
|
||||||
}
|
|
||||||
|
|
||||||
assemblyBindingSection = doc.CreateElement("assemblyBinding", XmlNamespace);
|
|
||||||
runtimeSection.AppendChild(assemblyBindingSection);
|
|
||||||
return assemblyBindingSection;
|
return assemblyBindingSection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
assemblyBindingSection = doc.CreateElement("assemblyBinding", XmlNamespace);
|
||||||
/// Add an assembly binding redirect entry to the config file.
|
runtimeSection.AppendChild(assemblyBindingSection);
|
||||||
/// </summary>
|
return assemblyBindingSection;
|
||||||
/// <param name="doc"> The doc. </param>
|
}
|
||||||
/// <param name="assemblyBindingSection"> The assembly Binding Section. </param>
|
|
||||||
/// <param name="assemblyName"> The assembly Name. </param>
|
/// <summary>
|
||||||
/// <param name="fromVersion"> The from Version. </param>
|
/// Add an assembly binding redirect entry to the config file.
|
||||||
/// <param name="toVersion"> The to Version. </param>
|
/// </summary>
|
||||||
private static void AddAssemblyBindingRedirect(
|
/// <param name="doc"> The doc. </param>
|
||||||
XmlDocument doc,
|
/// <param name="assemblyBindingSection"> The assembly Binding Section. </param>
|
||||||
XmlElement assemblyBindingSection,
|
/// <param name="assemblyName"> The assembly Name. </param>
|
||||||
AssemblyName assemblyName,
|
/// <param name="fromVersion"> The from Version. </param>
|
||||||
string fromVersion,
|
/// <param name="toVersion"> The to Version. </param>
|
||||||
string toVersion)
|
private static void AddAssemblyBindingRedirect(
|
||||||
|
XmlDocument doc,
|
||||||
|
XmlElement assemblyBindingSection,
|
||||||
|
AssemblyName assemblyName,
|
||||||
|
string fromVersion,
|
||||||
|
string toVersion)
|
||||||
|
{
|
||||||
|
Debug.Assert(assemblyName != null, "assemblyName should not be null.");
|
||||||
|
if (assemblyName == null)
|
||||||
{
|
{
|
||||||
Debug.Assert(assemblyName != null, "assemblyName should not be null.");
|
throw new ArgumentNullException(nameof(assemblyName));
|
||||||
if (assemblyName == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(assemblyName));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the public key token into a string.
|
|
||||||
StringBuilder publicKeyTokenString = null;
|
|
||||||
var publicKeyToken = assemblyName.GetPublicKeyToken();
|
|
||||||
if (publicKeyToken != null)
|
|
||||||
{
|
|
||||||
publicKeyTokenString = new StringBuilder(publicKeyToken.GetLength(0) * 2);
|
|
||||||
for (var i = 0; i < publicKeyToken.GetLength(0); i++)
|
|
||||||
{
|
|
||||||
publicKeyTokenString.AppendFormat(
|
|
||||||
System.Globalization.CultureInfo.InvariantCulture,
|
|
||||||
"{0:x2}",
|
|
||||||
new object[] { publicKeyToken[i] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the culture as a string.
|
|
||||||
var cultureString = assemblyName.CultureInfo.ToString();
|
|
||||||
if (string.IsNullOrEmpty(cultureString))
|
|
||||||
{
|
|
||||||
cultureString = "neutral";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the dependentAssembly section.
|
|
||||||
var dependentAssemblySection = doc.CreateElement("dependentAssembly", XmlNamespace);
|
|
||||||
assemblyBindingSection.AppendChild(dependentAssemblySection);
|
|
||||||
|
|
||||||
// Add the assemblyIdentity element.
|
|
||||||
var assemblyIdentityElement = doc.CreateElement("assemblyIdentity", XmlNamespace);
|
|
||||||
assemblyIdentityElement.SetAttribute("name", assemblyName.Name);
|
|
||||||
if (publicKeyTokenString != null)
|
|
||||||
{
|
|
||||||
assemblyIdentityElement.SetAttribute("publicKeyToken", publicKeyTokenString.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
assemblyIdentityElement.SetAttribute("culture", cultureString);
|
|
||||||
dependentAssemblySection.AppendChild(assemblyIdentityElement);
|
|
||||||
|
|
||||||
var bindingRedirectElement = doc.CreateElement("bindingRedirect", XmlNamespace);
|
|
||||||
bindingRedirectElement.SetAttribute("oldVersion", fromVersion);
|
|
||||||
bindingRedirectElement.SetAttribute("newVersion", toVersion);
|
|
||||||
dependentAssemblySection.AppendChild(bindingRedirectElement);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert the public key token into a string.
|
||||||
|
StringBuilder publicKeyTokenString = null;
|
||||||
|
var publicKeyToken = assemblyName.GetPublicKeyToken();
|
||||||
|
if (publicKeyToken != null)
|
||||||
|
{
|
||||||
|
publicKeyTokenString = new StringBuilder(publicKeyToken.GetLength(0) * 2);
|
||||||
|
for (var i = 0; i < publicKeyToken.GetLength(0); i++)
|
||||||
|
{
|
||||||
|
publicKeyTokenString.AppendFormat(
|
||||||
|
System.Globalization.CultureInfo.InvariantCulture,
|
||||||
|
"{0:x2}",
|
||||||
|
new object[] { publicKeyToken[i] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the culture as a string.
|
||||||
|
var cultureString = assemblyName.CultureInfo.ToString();
|
||||||
|
if (string.IsNullOrEmpty(cultureString))
|
||||||
|
{
|
||||||
|
cultureString = "neutral";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the dependentAssembly section.
|
||||||
|
var dependentAssemblySection = doc.CreateElement("dependentAssembly", XmlNamespace);
|
||||||
|
assemblyBindingSection.AppendChild(dependentAssemblySection);
|
||||||
|
|
||||||
|
// Add the assemblyIdentity element.
|
||||||
|
var assemblyIdentityElement = doc.CreateElement("assemblyIdentity", XmlNamespace);
|
||||||
|
assemblyIdentityElement.SetAttribute("name", assemblyName.Name);
|
||||||
|
if (publicKeyTokenString != null)
|
||||||
|
{
|
||||||
|
assemblyIdentityElement.SetAttribute("publicKeyToken", publicKeyTokenString.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
assemblyIdentityElement.SetAttribute("culture", cultureString);
|
||||||
|
dependentAssemblySection.AppendChild(assemblyIdentityElement);
|
||||||
|
|
||||||
|
var bindingRedirectElement = doc.CreateElement("bindingRedirect", XmlNamespace);
|
||||||
|
bindingRedirectElement.SetAttribute("oldVersion", fromVersion);
|
||||||
|
bindingRedirectElement.SetAttribute("newVersion", toVersion);
|
||||||
|
dependentAssemblySection.AppendChild(bindingRedirectElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,31 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A service to log any trace messages from the adapter that would be shown in *.TpTrace files.
|
||||||
|
/// </summary>
|
||||||
|
public interface IAdapterTraceLogger
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A service to log any trace messages from the adapter that would be shown in *.TpTrace files.
|
/// Log an error in a given format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IAdapterTraceLogger
|
/// <param name="format"> The format. </param>
|
||||||
{
|
/// <param name="args"> The args. </param>
|
||||||
/// <summary>
|
void LogError(string format, params object[] args);
|
||||||
/// Log an error in a given format.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="format"> The format. </param>
|
|
||||||
/// <param name="args"> The args. </param>
|
|
||||||
void LogError(string format, params object[] args);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Log a warning in a given format.
|
/// Log a warning in a given format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="format"> The format. </param>
|
/// <param name="format"> The format. </param>
|
||||||
/// <param name="args"> The args. </param>
|
/// <param name="args"> The args. </param>
|
||||||
void LogWarning(string format, params object[] args);
|
void LogWarning(string format, params object[] args);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Log an information message in a given format.
|
/// Log an information message in a given format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="format"> The format. </param>
|
/// <param name="format"> The format. </param>
|
||||||
/// <param name="args"> The args. </param>
|
/// <param name="args"> The args. </param>
|
||||||
void LogInfo(string format, params object[] args);
|
void LogInfo(string format, params object[] args);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,87 +1,86 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This service is responsible for any file based operations.
|
||||||
|
/// </summary>
|
||||||
|
public interface IFileOperations
|
||||||
{
|
{
|
||||||
using System.Reflection;
|
/// <summary>
|
||||||
|
/// Loads an assembly into the current context.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assemblyName">
|
||||||
|
/// The name of the assembly.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="isReflectionOnly">
|
||||||
|
/// Indicates whether this should be a reflection only load.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// A handle to the loaded assembly.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// A platform can choose how it wants the assembly loaded. (ReflectionOnlyLoad/Load etc).
|
||||||
|
/// </remarks>
|
||||||
|
Assembly LoadAssembly(string assemblyName, bool isReflectionOnly);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This service is responsible for any file based operations.
|
/// Gets the path to the .DLL of the assembly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IFileOperations
|
/// <param name="assembly">The assembly.</param>
|
||||||
{
|
/// <returns>Path to the .DLL of the assembly.</returns>
|
||||||
/// <summary>
|
string GetAssemblyPath(Assembly assembly);
|
||||||
/// Loads an assembly into the current context.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assemblyName">
|
|
||||||
/// The name of the assembly.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="isReflectionOnly">
|
|
||||||
/// Indicates whether this should be a reflection only load.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// A handle to the loaded assembly.
|
|
||||||
/// </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// A platform can choose how it wants the assembly loaded. (ReflectionOnlyLoad/Load etc).
|
|
||||||
/// </remarks>
|
|
||||||
Assembly LoadAssembly(string assemblyName, bool isReflectionOnly);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the .DLL of the assembly.
|
/// Verify if a file exists in the current context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="assembly">The assembly.</param>
|
/// <param name="assemblyFileName"> The assembly file name. </param>
|
||||||
/// <returns>Path to the .DLL of the assembly.</returns>
|
/// <returns> true if the file exists. </returns>
|
||||||
string GetAssemblyPath(Assembly assembly);
|
bool DoesFileExist(string assemblyFileName);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Verify if a file exists in the current context.
|
/// Creates a Navigation session for the source file.
|
||||||
/// </summary>
|
/// This is used to get file path and line number information for its components.
|
||||||
/// <param name="assemblyFileName"> The assembly file name. </param>
|
/// </summary>
|
||||||
/// <returns> true if the file exists. </returns>
|
/// <param name="source"> The source file. </param>
|
||||||
bool DoesFileExist(string assemblyFileName);
|
/// <returns> A Navigation session instance for the current platform. </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Unfortunately we cannot use INavigationSession introduced in Object Model in Dev14 update-2 because
|
||||||
|
/// the adapter needs to work with older VS versions as well where this new type would not be defined resulting in a type not found exception.
|
||||||
|
/// </remarks>
|
||||||
|
object CreateNavigationSession(string source);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a Navigation session for the source file.
|
/// Gets the navigation data for a navigation session.
|
||||||
/// This is used to get file path and line number information for its components.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="navigationSession"> The navigation session. </param>
|
||||||
/// <param name="source"> The source file. </param>
|
/// <param name="className"> The class name. </param>
|
||||||
/// <returns> A Navigation session instance for the current platform. </returns>
|
/// <param name="methodName"> The method name. </param>
|
||||||
/// <remarks>
|
/// <param name="minLineNumber"> The min line number. </param>
|
||||||
/// Unfortunately we cannot use INavigationSession introduced in Object Model in Dev14 update-2 because
|
/// <param name="fileName"> The file name. </param>
|
||||||
/// the adapter needs to work with older VS versions as well where this new type would not be defined resulting in a type not found exception.
|
/// <remarks>
|
||||||
/// </remarks>
|
/// Unfortunately we cannot use INavigationSession introduced in Object Model in Dev14 update-2 because
|
||||||
object CreateNavigationSession(string source);
|
/// the adapter needs to work with older VS versions as well where this new type would not be defined resulting in a type not found exception.
|
||||||
|
/// </remarks>
|
||||||
|
void GetNavigationData(object navigationSession, string className, string methodName, out int minLineNumber, out string fileName);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the navigation data for a navigation session.
|
/// Disposes the navigation session instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="navigationSession"> The navigation session. </param>
|
/// <param name="navigationSession"> The navigation session. </param>
|
||||||
/// <param name="className"> The class name. </param>
|
/// <remarks>
|
||||||
/// <param name="methodName"> The method name. </param>
|
/// Unfortunately we cannot use INavigationSession introduced in Object Model in Dev14 update-2 because
|
||||||
/// <param name="minLineNumber"> The min line number. </param>
|
/// the adapter needs to work with older VS versions as well where this new type would not be defined resulting in a type not found exception.
|
||||||
/// <param name="fileName"> The file name. </param>
|
/// </remarks>
|
||||||
/// <remarks>
|
void DisposeNavigationSession(object navigationSession);
|
||||||
/// Unfortunately we cannot use INavigationSession introduced in Object Model in Dev14 update-2 because
|
|
||||||
/// the adapter needs to work with older VS versions as well where this new type would not be defined resulting in a type not found exception.
|
|
||||||
/// </remarks>
|
|
||||||
void GetNavigationData(object navigationSession, string className, string methodName, out int minLineNumber, out string fileName);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disposes the navigation session instance.
|
/// Gets the full file path of an assembly file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="navigationSession"> The navigation session. </param>
|
/// <param name="assemblyFileName"> The file name. </param>
|
||||||
/// <remarks>
|
/// <returns> The full file path. </returns>
|
||||||
/// Unfortunately we cannot use INavigationSession introduced in Object Model in Dev14 update-2 because
|
string GetFullFilePath(string assemblyFileName);
|
||||||
/// the adapter needs to work with older VS versions as well where this new type would not be defined resulting in a type not found exception.
|
|
||||||
/// </remarks>
|
|
||||||
void DisposeNavigationSession(object navigationSession);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the full file path of an assembly file.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assemblyFileName"> The file name. </param>
|
|
||||||
/// <returns> The full file path. </returns>
|
|
||||||
string GetFullFilePath(string assemblyFileName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,38 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This service is responsible for platform specific reflection operations.
|
||||||
|
/// </summary>
|
||||||
|
public interface IReflectionOperations
|
||||||
{
|
{
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Reflection;
|
/// Gets all the custom attributes adorned on a member.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="memberInfo"> The member. </param>
|
||||||
|
/// <param name="inherit"> True to inspect the ancestors of element; otherwise, false. </param>
|
||||||
|
/// <returns> The list of attributes on the member. Empty list if none found. </returns>
|
||||||
|
object[] GetCustomAttributes(MemberInfo memberInfo, bool inherit);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This service is responsible for platform specific reflection operations.
|
/// Gets all the custom attributes of a given type adorned on a member.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IReflectionOperations
|
/// <param name="memberInfo"> The member info. </param>
|
||||||
{
|
/// <param name="type"> The attribute type. </param>
|
||||||
/// <summary>
|
/// <param name="inherit"> True to inspect the ancestors of element; otherwise, false. </param>
|
||||||
/// Gets all the custom attributes adorned on a member.
|
/// <returns> The list of attributes on the member. Empty list if none found. </returns>
|
||||||
/// </summary>
|
object[] GetCustomAttributes(MemberInfo memberInfo, Type type, bool inherit);
|
||||||
/// <param name="memberInfo"> The member. </param>
|
|
||||||
/// <param name="inherit"> True to inspect the ancestors of element; otherwise, false. </param>
|
|
||||||
/// <returns> The list of attributes on the member. Empty list if none found. </returns>
|
|
||||||
object[] GetCustomAttributes(MemberInfo memberInfo, bool inherit);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all the custom attributes of a given type adorned on a member.
|
/// Gets all the custom attributes of a given type on an assembly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="memberInfo"> The member info. </param>
|
/// <param name="assembly"> The assembly. </param>
|
||||||
/// <param name="type"> The attribute type. </param>
|
/// <param name="type"> The attribute type. </param>
|
||||||
/// <param name="inherit"> True to inspect the ancestors of element; otherwise, false. </param>
|
/// <returns> The list of attributes of the given type on the member. Empty list if none found. </returns>
|
||||||
/// <returns> The list of attributes on the member. Empty list if none found. </returns>
|
object[] GetCustomAttributes(Assembly assembly, Type type);
|
||||||
object[] GetCustomAttributes(MemberInfo memberInfo, Type type, bool inherit);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets all the custom attributes of a given type on an assembly.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assembly"> The assembly. </param>
|
|
||||||
/// <param name="type"> The attribute type. </param>
|
|
||||||
/// <returns> The list of attributes of the given type on the member. Empty list if none found. </returns>
|
|
||||||
object[] GetCustomAttributes(Assembly assembly, Type type);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,31 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// To read settings from the runsettings xml for the corresponding platform service.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISettingsProvider
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
/// <summary>
|
||||||
using System.Xml;
|
/// Load settings from the xml reader instance which are specific
|
||||||
|
/// for the corresponding platform service.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reader">
|
||||||
|
/// Reader that can be used to read current node and all its descendants,
|
||||||
|
/// to load the settings from.</param>
|
||||||
|
void Load(XmlReader reader);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// To read settings from the runsettings xml for the corresponding platform service.
|
/// The set of properties/settings specific to the platform, that will be surfaced to the user through the test context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ISettingsProvider
|
/// <param name="source">
|
||||||
{
|
/// source is used to find application base directory used for setting test context properties.
|
||||||
/// <summary>
|
/// </param>
|
||||||
/// Load settings from the xml reader instance which are specific
|
/// <returns>Properties specific to the platform.</returns>
|
||||||
/// for the corresponding platform service.
|
IDictionary<string, object> GetProperties(string source);
|
||||||
/// </summary>
|
|
||||||
/// <param name="reader">
|
|
||||||
/// Reader that can be used to read current node and all its descendants,
|
|
||||||
/// to load the settings from.</param>
|
|
||||||
void Load(XmlReader reader);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The set of properties/settings specific to the platform, that will be surfaced to the user through the test context.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">
|
|
||||||
/// source is used to find application base directory used for setting test context properties.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>Properties specific to the platform.</returns>
|
|
||||||
IDictionary<string, object> GetProperties(string source);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,71 +1,70 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Operations on the TestContext object that is implemented differently for each platform.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITestContext
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
/// <summary>
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
/// Gets the inner test context object.
|
||||||
|
/// </summary>
|
||||||
|
TestContext Context { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Operations on the TestContext object that is implemented differently for each platform.
|
/// Returns whether property with parameter name is present.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ITestContext
|
/// <param name="propertyName"> The property Name. </param>
|
||||||
{
|
/// <param name="propertyValue"> The property Value. </param>
|
||||||
/// <summary>
|
/// <returns> True if the property is present. </returns>
|
||||||
/// Gets the inner test context object.
|
bool TryGetPropertyValue(string propertyName, out object propertyValue);
|
||||||
/// </summary>
|
|
||||||
TestContext Context { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns whether property with parameter name is present.
|
/// Adds the parameter name/value pair to property bag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="propertyName"> The property Name. </param>
|
/// <param name="propertyName"> The property Name. </param>
|
||||||
/// <param name="propertyValue"> The property Value. </param>
|
/// <param name="propertyValue"> The property Value. </param>
|
||||||
/// <returns> True if the property is present. </returns>
|
void AddProperty(string propertyName, string propertyValue);
|
||||||
bool TryGetPropertyValue(string propertyName, out object propertyValue);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the parameter name/value pair to property bag.
|
/// Sets the outcome of a Test Method in the TestContext.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="propertyName"> The property Name. </param>
|
/// <param name="outcome"> The outcome. </param>
|
||||||
/// <param name="propertyValue"> The property Value. </param>
|
void SetOutcome(UnitTestOutcome outcome);
|
||||||
void AddProperty(string propertyName, string propertyValue);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the outcome of a Test Method in the TestContext.
|
/// Set data row for particular run of TestMethod.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="outcome"> The outcome. </param>
|
/// <param name="dataRow">data row</param>
|
||||||
void SetOutcome(UnitTestOutcome outcome);
|
void SetDataRow(object dataRow);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set data row for particular run of TestMethod.
|
/// Set connection for TestContext
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dataRow">data row</param>
|
/// <param name="dbConnection">db Connection.</param>
|
||||||
void SetDataRow(object dataRow);
|
void SetDataConnection(object dbConnection);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set connection for TestContext
|
/// Gets the attached Result files
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dbConnection">db Connection.</param>
|
/// <returns>
|
||||||
void SetDataConnection(object dbConnection);
|
/// The list of result files.
|
||||||
|
/// </returns>
|
||||||
|
IList<string> GetResultFiles();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the attached Result files
|
/// Gets the diagnostics messages written to TestContext.WriteLine()
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>
|
/// <returns>The test context messages added so far.</returns>
|
||||||
/// The list of result files.
|
string GetDiagnosticMessages();
|
||||||
/// </returns>
|
|
||||||
IList<string> GetResultFiles();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the diagnostics messages written to TestContext.WriteLine()
|
/// Clears the previous testContext writeline messages.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The test context messages added so far.</returns>
|
void ClearDiagnosticMessages();
|
||||||
string GetDiagnosticMessages();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears the previous testContext writeline messages.
|
|
||||||
/// </summary>
|
|
||||||
void ClearDiagnosticMessages();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,27 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
{
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using UTF = Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UTF = Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface that provides values from data source when data driven tests are run.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITestDataSource
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface that provides values from data source when data driven tests are run.
|
/// Gets the test data from custom test data source and sets dbconnection in testContext object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ITestDataSource
|
/// <param name="testMethodInfo">
|
||||||
{
|
/// The info of test method.
|
||||||
/// <summary>
|
/// </param>
|
||||||
/// Gets the test data from custom test data source and sets dbconnection in testContext object.
|
/// <param name="testContext">
|
||||||
/// </summary>
|
/// Test Context object
|
||||||
/// <param name="testMethodInfo">
|
/// </param>
|
||||||
/// The info of test method.
|
/// <returns>
|
||||||
/// </param>
|
/// Test data for calling test method.
|
||||||
/// <param name="testContext">
|
/// </returns>
|
||||||
/// Test Context object
|
IEnumerable<object> GetData(UTF.ITestMethod testMethodInfo, ITestContext testContext);
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// Test data for calling test method.
|
|
||||||
/// </returns>
|
|
||||||
IEnumerable<object> GetData(UTF.ITestMethod testMethodInfo, ITestContext testContext);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,47 +1,46 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
using System;
|
||||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
|
||||||
|
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The TestDeployment interface.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITestDeployment
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Deploy deployment items for the specified test cases.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="testCases"> The test cases. </param>
|
||||||
|
/// <param name="runContext"> The run context. </param>
|
||||||
|
/// <param name="frameworkHandle"> The framework handle. </param>
|
||||||
|
/// <returns> True if deployment is done. </returns>
|
||||||
|
bool Deploy(IEnumerable<TestCase> testCases, IRunContext runContext, IFrameworkHandle frameworkHandle);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The TestDeployment interface.
|
/// Gets the set of deployment items on a method and its corresponding class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ITestDeployment
|
/// <param name="method"> The method. </param>
|
||||||
{
|
/// <param name="type"> The type. </param>
|
||||||
/// <summary>
|
/// <param name="warnings"> The warnings. </param>
|
||||||
/// Deploy deployment items for the specified test cases.
|
/// <returns> A KeyValuePair of deployment items. </returns>
|
||||||
/// </summary>
|
KeyValuePair<string, string>[] GetDeploymentItems(MethodInfo method, Type type, ICollection<string> warnings);
|
||||||
/// <param name="testCases"> The test cases. </param>
|
|
||||||
/// <param name="runContext"> The run context. </param>
|
|
||||||
/// <param name="frameworkHandle"> The framework handle. </param>
|
|
||||||
/// <returns> True if deployment is done. </returns>
|
|
||||||
bool Deploy(IEnumerable<TestCase> testCases, IRunContext runContext, IFrameworkHandle frameworkHandle);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the set of deployment items on a method and its corresponding class.
|
/// Cleanup deployment item directories.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="method"> The method. </param>
|
void Cleanup();
|
||||||
/// <param name="type"> The type. </param>
|
|
||||||
/// <param name="warnings"> The warnings. </param>
|
|
||||||
/// <returns> A KeyValuePair of deployment items. </returns>
|
|
||||||
KeyValuePair<string, string>[] GetDeploymentItems(MethodInfo method, Type type, ICollection<string> warnings);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cleanup deployment item directories.
|
/// Gets the deployment output directory where the source file along with all its dependencies is dropped.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Cleanup();
|
/// <returns> The deployment output directory. </returns>
|
||||||
|
string GetDeploymentDirectory();
|
||||||
/// <summary>
|
|
||||||
/// Gets the deployment output directory where the source file along with all its dependencies is dropped.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns> The deployment output directory. </returns>
|
|
||||||
string GetDeploymentDirectory();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,34 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This platform service is responsible for any data or operations to validate
|
||||||
|
/// the test sources provided to the adapter.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITestSource
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
/// <summary>
|
||||||
using System.Reflection;
|
/// Gets the set of valid extensions for sources targeting this platform.
|
||||||
|
/// </summary>
|
||||||
|
IEnumerable<string> ValidSourceExtensions { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This platform service is responsible for any data or operations to validate
|
/// Verifies if the assembly provided is referenced by the source.
|
||||||
/// the test sources provided to the adapter.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ITestSource
|
/// <param name="assemblyName"> The assembly name. </param>
|
||||||
{
|
/// <param name="source"> The source. </param>
|
||||||
/// <summary>
|
/// <returns> True if the assembly is referenced. </returns>
|
||||||
/// Gets the set of valid extensions for sources targeting this platform.
|
bool IsAssemblyReferenced(AssemblyName assemblyName, string source);
|
||||||
/// </summary>
|
|
||||||
IEnumerable<string> ValidSourceExtensions { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Verifies if the assembly provided is referenced by the source.
|
/// Gets the set of sources (dll's/exe's) that contain tests. If a source is a package(appx), return the file(dll/exe) that contains tests from it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="assemblyName"> The assembly name. </param>
|
/// <param name="sources"> Sources given to the adapter. </param>
|
||||||
/// <param name="source"> The source. </param>
|
/// <returns> Sources that contains tests. </returns>
|
||||||
/// <returns> True if the assembly is referenced. </returns>
|
IEnumerable<string> GetTestSources(IEnumerable<string> sources);
|
||||||
bool IsAssemblyReferenced(AssemblyName assemblyName, string source);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the set of sources (dll's/exe's) that contain tests. If a source is a package(appx), return the file(dll/exe) that contains tests from it.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sources"> Sources given to the adapter. </param>
|
|
||||||
/// <returns> Sources that contains tests. </returns>
|
|
||||||
IEnumerable<string> GetTestSources(IEnumerable<string> sources);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,29 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A host that loads the test source.This can be in isolation for desktop using an AppDomain or just loading the source in the current context.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITestSourceHost : IDisposable
|
||||||
{
|
{
|
||||||
using System;
|
/// <summary>
|
||||||
|
/// Sets up the isolation host.
|
||||||
|
/// </summary>
|
||||||
|
void SetupHost();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A host that loads the test source.This can be in isolation for desktop using an AppDomain or just loading the source in the current context.
|
/// Creates an instance of a given type in the test source host.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ITestSourceHost : IDisposable
|
/// <param name="type"> The type that needs to be created in the host. </param>
|
||||||
{
|
/// <param name="args">The arguments to pass to the constructor.
|
||||||
/// <summary>
|
/// This array of arguments must match in number, order, and type the parameters of the constructor to invoke.
|
||||||
/// Sets up the isolation host.
|
/// Pass in null for a constructor with no arguments.
|
||||||
/// </summary>
|
/// </param>
|
||||||
void SetupHost();
|
/// <returns> An instance of the type created in the host. </returns>
|
||||||
|
/// <remarks> If a type is to be created in isolation then it needs to be a MarshalByRefObject. </remarks>
|
||||||
/// <summary>
|
object CreateInstanceForType(Type type, object[] args);
|
||||||
/// Creates an instance of a given type in the test source host.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type"> The type that needs to be created in the host. </param>
|
|
||||||
/// <param name="args">The arguments to pass to the constructor.
|
|
||||||
/// This array of arguments must match in number, order, and type the parameters of the constructor to invoke.
|
|
||||||
/// Pass in null for a constructor with no arguments.
|
|
||||||
/// </param>
|
|
||||||
/// <returns> An instance of the type created in the host. </returns>
|
|
||||||
/// <remarks> If a type is to be created in isolation then it needs to be a MarshalByRefObject. </remarks>
|
|
||||||
object CreateInstanceForType(Type type, object[] args);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,28 @@
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface
|
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This service is responsible for any thread operations specific to a platform.
|
||||||
|
/// </summary>
|
||||||
|
public interface IThreadOperations
|
||||||
{
|
{
|
||||||
using System;
|
/// <summary>
|
||||||
using System.Threading;
|
/// Execute the given action synchronously on a background thread in the given timeout.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="action">The action to execute.</param>
|
||||||
|
/// <param name="timeout">Timeout for the specified action in milliseconds.</param>
|
||||||
|
/// <param name="cancelToken">Token to cancel the execution</param>
|
||||||
|
/// <returns>Returns true if the action executed before the timeout. returns false otherwise.</returns>
|
||||||
|
bool Execute(Action action, int timeout, CancellationToken cancelToken);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This service is responsible for any thread operations specific to a platform.
|
/// Execute an action with handling for Thread Aborts (if possible) so the main thread of the adapter does not die.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IThreadOperations
|
/// <param name="action"> The action to execute. </param>
|
||||||
{
|
void ExecuteWithAbortSafety(Action action);
|
||||||
/// <summary>
|
|
||||||
/// Execute the given action synchronously on a background thread in the given timeout.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="action">The action to execute.</param>
|
|
||||||
/// <param name="timeout">Timeout for the specified action in milliseconds.</param>
|
|
||||||
/// <param name="cancelToken">Token to cancel the execution</param>
|
|
||||||
/// <returns>Returns true if the action executed before the timeout. returns false otherwise.</returns>
|
|
||||||
bool Execute(Action action, int timeout, CancellationToken cancelToken);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Execute an action with handling for Thread Aborts (if possible) so the main thread of the adapter does not die.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="action"> The action to execute. </param>
|
|
||||||
void ExecuteWithAbortSafety(Action action);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче