diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..1227fcc9b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,215 @@ +# To learn more about .editorconfig see https://aka.ms/editorconfigdocs + +# top-most EditorConfig file +root = true + +############################### +# Core EditorConfig Options # +############################### +# All files +[*] +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +# XML project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 +charset = utf-8 + +# XML config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# Xml build files +[*.builds] +indent_size = 2 + +# Xml files +[*.{xml,stylecop,resx,ruleset}] +indent_size = 2 + +# YAML config files +[*.{yml,yaml}] +indent_size = 2 + +# Shell scripts +[*.sh] +end_of_line = lf +[*.{cmd,bat}] +end_of_line = crlf + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = true +charset = utf-8-bom +############################### +# .NET Coding Conventions # +############################### +[*.{cs,vb}] +# Organize usings +dotnet_sort_system_directives_first = true +## IDE0005: Using directive is unnecessary. +dotnet_diagnostic.IDE0005.severity = warning +# License header +file_header_template = Copyright (c) Toni Solarin-Sodara\nLicensed under the MIT license. See LICENSE file in the project root for full license information. +## IDE0073: The file header is missing or not located at the top of the file +dotnet_diagnostic.IDE0073.severity = warning +# this. preferences +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_style_readonly_field = true:warning +## IDE0044: Add readonly modifier +dotnet_diagnostic.IDE0044.severity = warning +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +# CA1805: Do not initialize unnecessarily +dotnet_diagnostic.CA1805.severity = warning +# IDE0063: Use simple 'using' statement +dotnet_diagnostic.IDE0063.severity = warning +# IDE0057: Use range operator +dotnet_diagnostic.IDE0057.severity = warning +# IDE0075: Simplify conditional expression +dotnet_diagnostic.IDE0075.severity = warning +# IDE0071: Simplify interpolation +dotnet_diagnostic.IDE0071.severity = warning +# CA1829: Use Length/Count property instead of Count() when available +dotnet_diagnostic.CA1829.severity = warning +# CA1827: Do not use Count() or LongCount() when Any() can be used +dotnet_diagnostic.CA1827.severity = warning +############################### +# Naming Conventions # +############################### +# Name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const +dotnet_naming_style.pascal_case_style.capitalization = pascal_case +# Static fields should have s_ prefix +dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion +dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields +dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected +dotnet_naming_style.static_prefix_style.required_prefix = s_ +dotnet_naming_style.static_prefix_style.capitalization = camel_case +# Internal and private fields should be _camelCase +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case +# IDE1006: Naming Styles +dotnet_diagnostic.IDE1006.severity = warning +# IDE0090: Use 'new(...)' +dotnet_diagnostic.IDE0090.severity = warning +############################### +# C# Coding Conventions # +############################### +[*.cs] +# Organize usings +csharp_using_directive_placement = outside_namespace:warning +# var preferences - use keywords instead of BCL types, and permit var only when the type is clear +csharp_style_var_for_built_in_types = false:warning +csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = false:warning +# Expression-bodied members +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +# Pattern matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +csharp_style_prefer_not_pattern = true +## IDE0083: Use pattern matching +dotnet_diagnostic.IDE0083.severity = warning +# Null-checking preferences +csharp_style_throw_expression = true:warning +csharp_style_conditional_delegate_call = true:warning +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning +# Expression-level preferences +csharp_prefer_braces = true:silent +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_prefer_simple_default_expression = true:warning +## IDE0034: Simplify 'default' expression +dotnet_diagnostic.IDE0034.severity = warning +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_inlined_variable_declaration = true:warning +############################### +# C# Formatting Rules # +############################### +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true +dotnet_style_allow_multiple_blank_lines_experimental=false:warning +# Indentation preferences +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false +# Wrapping preferences +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true diff --git a/DeterministicBuild.targets b/DeterministicBuild.targets index 78052937d..13208d103 100644 --- a/DeterministicBuild.targets +++ b/DeterministicBuild.targets @@ -9,7 +9,7 @@ https://github.com/dotnet/sourcelink/issues/572 --> - + true snupkg true + true + preview + true preview - $(NoWarn);NU5105 + $(NoWarn);NU5105 https://api.nuget.org/v3/index.json; diff --git a/Directory.Build.targets b/Directory.Build.targets index f6cd1b61d..d970b1531 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -4,13 +4,12 @@ - - + - + - + @@ -22,8 +21,8 @@ We can check minimum supported package version here https://github.com/Microsoft/vstest/blob/master/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj#L37 --> - - + + diff --git a/Documentation/Changelog.md b/Documentation/Changelog.md index 5c551ceec..3575567a4 100644 --- a/Documentation/Changelog.md +++ b/Documentation/Changelog.md @@ -4,6 +4,38 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Fixed +-Could not write lines to file CoverletSourceRootsMapping - in use by another process [#1155](https://github.com/coverlet-coverage/coverlet/issues/1155) +-Incorrect coverage for methods returning IAsyncEnumerable in generic classes [#1383](https://github.com/coverlet-coverage/coverlet/issues/1383) +-Wrong branch coverage for async methods .NET Standard 1.x [#1376](https://github.com/coverlet-coverage/coverlet/issues/1376) +-Empty path exception in visual basic projects [#775](https://github.com/coverlet-coverage/coverlet/issues/775) +-Allign published nuget package version to github release version [#1413](https://github.com/coverlet-coverage/coverlet/issues/1413) +-Sync nuget and github release versions [#1122](https://github.com/coverlet-coverage/coverlet/issues/1122) + +### Improvements +-Migration of the project to .NET 6.0 [#1473](https://github.com/coverlet-coverage/coverlet/pull/1473) + +### Breaking changes +- New parameter `ExcludeAssembliesWithoutSources` to control automatic assembly exclusion [1164](https://github.com/coverlet-coverage/coverlet/issues/1164). The parameter `InstrumentModulesWithoutLocalSources` has been removed. since it can be handled by setting `ExcludeAssembliesWithoutSources` to `None`. +- The default heuristics for determining whether to instrument an assembly has been changed. In previous versions any missing source file was taken as a signal that it was a third-party project that shouldn't be instrumented, with exceptions for some common file name patterns for source generators. Now only assemblies where no source files at all can be found are excluded from instrumentation, and the code for detecting source generator files have been removed. To get back to the behaviour that at least one missing file is sufficient to exclude an assembly, set `ExcludeAssembliesWithoutSources` to `MissingAny`, or use assembly exclusion filters for more fine-grained control. + +## Release date 2022-10-29 +### Packages +coverlet.msbuild 3.2.0 +coverlet.console 3.2.0 +coverlet.collector 3.2.0 + +### Fixed +-Fix TypeLoadException when referencing Microsoft.Extensions.DependencyInjection v6.0.1 [#1390](https://github.com/coverlet-coverage/coverlet/issues/1390) +-Source Link for code generators fails [#1322](https://github.com/coverlet-coverage/coverlet/issues/1322) +-Await foreach has wrong branch coverage when method is generic [#1210](https://github.com/coverlet-coverage/coverlet/issues/1210) +-ExcludeFromCodeCoverage attribute on local functions ignores lambda expression [#1302](https://github.com/coverlet-coverage/coverlet/issues/1302) + +### Added +-Added InstrumentModulesWithoutLocalSources setting [#1360](https://github.com/coverlet-coverage/coverlet/pull/1360) by @TFTomSun + ## Release date 2022-02-06 ### Packages coverlet.msbuild 3.1.2 diff --git a/Documentation/DeterministicBuild.md b/Documentation/DeterministicBuild.md index a48865ce9..aa984e2dd 100644 --- a/Documentation/DeterministicBuild.md +++ b/Documentation/DeterministicBuild.md @@ -47,7 +47,7 @@ https://github.com/dotnet/sourcelink/issues/572 --> - + - + - + origin) -λ dotnet test --collect:"XPlat Code Coverage" /p:DeterministicSourcePaths=true +λ dotnet test --collect:"XPlat Code Coverage" /p:DeterministicSourcePaths=true -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.DeterministicReport=true Test run for C:\git\coverlet\Documentation\Examples\VSTest\DeterministicBuild\XUnitTestProject1\bin\Debug\netcoreapp3.1\XUnitTestProject1.dll(.NETCoreApp,Version=v3.1) Microsoft (R) Test Execution Command Line Tool Version 16.5.0 Copyright (c) Microsoft Corporation. All rights reserved. @@ -78,5 +78,5 @@ Total tests: 1 You should see on output folder the coverlet source root mapping file generated. This is the confirmation that you're running coverage on deterministic build. ``` -Documentation\Examples\VSTest\DeterministicBuild\XUnitTestProject1\bin\Debug\netcoreapp3.1\CoverletSourceRootsMapping +Documentation\Examples\VSTest\DeterministicBuild\XUnitTestProject1\bin\Debug\netcoreapp3.1\CoverletSourceRootsMapping_XUnitTestProject1 ``` \ No newline at end of file diff --git a/Documentation/GlobalTool.md b/Documentation/GlobalTool.md index 231da2ff5..455f34b39 100644 --- a/Documentation/GlobalTool.md +++ b/Documentation/GlobalTool.md @@ -14,30 +14,31 @@ Cross platform .NET Core code coverage tool 3.0.0.0 Usage: coverlet [arguments] [options] Arguments: - Path to the test assembly or application directory. + Path to the test assembly or application directory. Options: - -h|--help Show help information - -v|--version Show version information - -t|--target Path to the test runner application. - -a|--targetargs Arguments to be passed to the test runner. - -o|--output Output of the generated coverage report - -v|--verbosity Sets the verbosity level of the command. Allowed values are quiet, minimal, normal, detailed. - -f|--format Format of the generated coverage report. - --threshold Exits with error if the coverage % is below value. - --threshold-type Coverage type to apply the threshold to. - --threshold-stat Coverage statistic used to enforce the threshold value. - --exclude Filter expressions to exclude specific modules and types. - --include Filter expressions to include only specific modules and types. - --exclude-by-file Glob patterns specifying source files to exclude. - --include-directory Include directories containing additional assemblies to be instrumented. - --exclude-by-attribute Attributes to exclude from code coverage. - --include-test-assembly Specifies whether to report code coverage of the test assembly. - --single-hit Specifies whether to limit code coverage hit reporting to a single hit for each location - --skipautoprops Neither track nor record auto-implemented properties. - --merge-with Path to existing coverage result to merge. - --use-source-link Specifies whether to use SourceLink URIs in place of file system paths. - --does-not-return-attribute Attributes that mark methods that do not return. + -h|--help Show help information + -v|--version Show version information + -t|--target Path to the test runner application. + -a|--targetargs Arguments to be passed to the test runner. + -o|--output Output of the generated coverage report + -v|--verbosity Sets the verbosity level of the command. Allowed values are quiet, minimal, normal, detailed. + -f|--format Format of the generated coverage report. + --threshold Exits with error if the coverage % is below value. + --threshold-type Coverage type to apply the threshold to. + --threshold-stat Coverage statistic used to enforce the threshold value. + --exclude Filter expressions to exclude specific modules and types. + --include Filter expressions to include only specific modules and types. + --exclude-by-file Glob patterns specifying source files to exclude. + --include-directory Include directories containing additional assemblies to be instrumented. + --exclude-by-attribute Attributes to exclude from code coverage. + --include-test-assembly Specifies whether to report code coverage of the test assembly. + --single-hit Specifies whether to limit code coverage hit reporting to a single hit for each location + --skipautoprops Neither track nor record auto-implemented properties. + --merge-with Path to existing coverage result to merge. + --use-source-link Specifies whether to use SourceLink URIs in place of file system paths. + --does-not-return-attribute Attributes that mark methods that do not return. + --exclude-assemblies-without-sources Specifies behaviour of heuristic to ignore assemblies with missing source documents. ``` NB. For a [multiple value] options you have to specify values multiple times i.e. diff --git a/Documentation/KnownIssues.md b/Documentation/KnownIssues.md index bf1a24d95..0b5b842ec 100644 --- a/Documentation/KnownIssues.md +++ b/Documentation/KnownIssues.md @@ -201,3 +201,33 @@ NB. Workaround doesn't work if test method itself explicitly creates an appdomai SUT (System Under Test) assembly is also not listed in MSBuild logs - "Instrumented module" is missing for your dll. *Solution*: Check whether deterministic build is turned on for your solution, if so, follow the [instructions](DeterministicBuild.md) on how to handle deterministic builds. + +## Failure to produce Code Coverage Report + +*Symptoms:* + +```log +C:\Users\REDACTED\.nuget\packages\coverlet.msbuild\3.2.0\build\coverlet.msbuild.targets(39,5): error : Stream was too long. [REDACTED.csproj] +C:\Users\REDACTED\.nuget\packages\coverlet.msbuild\3.2.0\build\coverlet.msbuild.targets(39,5): error : at System.IO.MemoryStream.Write(Byte[] buffer, Int32 offset, Int32 count) [REDACTED.csproj] +C:\Users\REDACTED\.nuget\packages\coverlet.msbuild\3.2.0\build\coverlet.msbuild.targets(39,5): error : at System.Xml.XmlStreamNodeWriter.FlushBuffer() [REDACTED.csproj] +C:\Users\REDACTED\.nuget\packages\coverlet.msbuild\3.2.0\build\coverlet.msbuild.targets(39,5): error : at System.Xml.XmlStreamNodeWriter.GetBuffer(Int32 count, Int32& offset) [REDACTED.csproj] +C:\Users\REDACTED\.nuget\packages\coverlet.msbuild\3.2.0\build\coverlet.msbuild.targets(39,5): error : at System.Xml.XmlStreamNodeWriter.UnsafeWriteUTF8Chars(Char* chars, Int32 charCount) [REDACTED.csproj] +C:\Users\REDACTED\.nuget\packages\coverlet.msbuild\3.2.0\build\coverlet.msbuild.targets(39,5): error : at System.Xml.XmlUTF8NodeWriter.WriteEscapedText(String s) [REDACTED.csproj] +C:\Users\REDACTED\.nuget\packages\coverlet.msbuild\3.2.0\build\coverlet.msbuild.targets(39,5): error : at System.Xml.XmlBaseWriter.WriteString(String value) [REDACTED.csproj] +C:\Users\REDACTED\.nuget\packages\coverlet.msbuild\3.2.0\build\coverlet.msbuild.targets(39,5): error : at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteString(XmlWriterDelegator xmlWriter, String value, XmlDictionaryString name, XmlDictionaryString ns) [REDACTED.csproj] +C:\Users\REDACTED\.nuget\packages\coverlet.msbuild\3.2.0\build\coverlet.msbuild.targets(39,5): error : at WriteLineToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , ClassDataContract ) [REDACTED.csproj] + +.... + +Calculating coverage result... +C:\Users\REDACTED\.nuget\packages\coverlet.msbuild\3.2.0\build\coverlet.msbuild.targets(71,5): error : Unexpected end of file. [REDACTED.csproj] +C:\Users\REDACTED\.nuget\packages\coverlet.msbuild\3.2.0\build\coverlet.msbuild.targets(71,5): error : at System.Xml.EncodingStreamWrapper.ReadBOMEncoding(Boolean notOutOfBand) [REDACTED.csproj] +C:\Users\REDACTED\.nuget\packages\coverlet.msbuild\3.2.0\build\coverlet.msbuild.targets(71,5): error : at System.Xml.EncodingStreamWrapper..ctor(Stream stream, Encoding encoding) [REDACTED.csproj] +C:\Users\REDACTED\.nuget\packages\coverlet.msbuild\3.2.0\build\coverlet.msbuild.targets(71,5): error : at System.Xml.XmlUTF8TextReader.SetInput(Stream stream, Encoding encoding, XmlDictionaryReaderQuotas quotas, OnXmlDictionaryReaderClose onClose) [REDACTED.csproj] +``` + +The XML code coverage report is too large for the coverlet to parse. + +*Potential Solutions:* +* Reduce noise from auto generated code, for example excluding your EntityFrameworkCore Migrations namespace or automatically generated typed Http Clients. See [Excluding From Coverage](./MSBuildIntegration.md#excluding-from-coverage) for more information on ignoring namespaces from code coverage. + diff --git a/Documentation/MSBuildIntegration.md b/Documentation/MSBuildIntegration.md index bde9f364e..a937665be 100644 --- a/Documentation/MSBuildIntegration.md +++ b/Documentation/MSBuildIntegration.md @@ -181,6 +181,10 @@ You can also include coverage of the test assembly itself by setting `/p:Include Neither track nor record auto-implemented properties. Syntax: `/p:SkipAutoProps=true` +### Instrument module wihtout local sources file. + +Syntax: `/p:InstrumentModulesWithoutLocalSources=true` + ### Methods that do not return Methods that do not return can be marked with attributes to cause statements after them to be excluded from coverage. @@ -224,3 +228,19 @@ To generate deterministc report the parameter is: ``` /p:DeterministicReport=true ``` + +## Exclude assemblies without sources from coverage + +The heuristic coverlet uses to determine if an assembly is a third-party dependency is based on the matching of the assembly`s source documents and the corresponding source files. +This parameter has three different values to control the automatic assembly exclusion. + +| Parameter | Description | +|-----------|-------------| +| MissingAll | Includes the assembly if at least one document is matching. In case the `ExcludeAssembliesWithoutSources` parameter is not specified the default value is `MissingAll`. | +| MissingAny | Includes the assembly only if all documents can be matched to corresponding source files. | +| None | No assembly is excluded. | + +Here is an example of how to specifiy the parameter: +``` +/p:ExcludeAssembliesWithoutSources="MissingAny" +``` \ No newline at end of file diff --git a/Documentation/ReleasePlan.md b/Documentation/ReleasePlan.md index 630f2941c..3472cf8f6 100644 --- a/Documentation/ReleasePlan.md +++ b/Documentation/ReleasePlan.md @@ -23,13 +23,14 @@ We release 3 components as NuGet packages: | Package | Version | |:----------------------|:--------| -|**coverlet.msbuild** | 3.1.2 | -|**coverlet.console** | 3.1.2 | -|**coverlet.collector** | 3.1.2 | +|**coverlet.msbuild** | 3.2.0 | +|**coverlet.console** | 3.2.0 | +|**coverlet.collector** | 3.2.0 | | Release Date | coverlet.msbuild | coverlet.console | coverlet.collector| commit hash | notes | | :-----------------|:-----------------|:------------------|:------------------|:-----------------------------------------|:-------------------------------| +| 29 Oct 2022 | 3.2.0 | 3.2.0 | 3.2.0 | e2c9d84a84a9d2d240ac15feb70f9198c6f8e173 | | | 06 Feb 2022 | 3.1.2 | 3.1.2 | 3.1.2 | e335b1a8025e49e2f2de6b40ef12ec9d3ed11ceb | Fix CoreLib coverage issues | | 30 Jan 2022 | 3.1.1 | 3.1.1 | 3.1.1 | e4278c06faba63122a870df15a1a1b934f6bc81d | | | 19 July 2021 | 3.1.0 | 3.1.0 | 3.1.0 | 5a0ecc1e92fd754e2439dc3e4c828ff7386aa1a7 | Support for determistic build | @@ -81,8 +82,6 @@ This is the steps to release new packages to nuget.org 1. Update projects version in file `version.json` in root of repo (remove `-preview.{height}` and adjust version) -Update core lib project file version https://github.com/coverlet-coverage/coverlet/blob/master/src/coverlet.core/coverlet.core.csproj. - Do a PR and merge to master. 2. Clone repo, **remember to build packages from master and not from your fork or metadata links will point to your forked repo.** . Run `git log -5` from repo root to verify last commit. diff --git a/Documentation/VSTestIntegration.md b/Documentation/VSTestIntegration.md index 07c17a092..4cc67383d 100644 --- a/Documentation/VSTestIntegration.md +++ b/Documentation/VSTestIntegration.md @@ -77,9 +77,20 @@ We're working to fill the gaps. ### Default option (if you don't specify a runsettings file) -| Option | Summary | -|:-------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -|Format | Results format in which coverage output is generated. Default format is cobertura. Supported format lcov, opencover, cobertura, teamcity, json (default coverlet proprietary format) | +Without specifying a runsettings file and calling coverlet by just the name of the collector, the result of the generated coverage output is by default in cobertura format. +``` +dotnet test --collect:"XPlat Code Coverage" +``` + +The output format of the coverage report can also be changed without a runsettings file by specifying it in a parameter. The supported formats are lcov, opencover, cobertura, teamcity, json (default coverlet proprietary format). +``` +dotnet test --collect:"XPlat Code Coverage;Format=json" +``` + +It is even possible to specify the coverage output in multiple formats. +``` +dotnet test --collect:"XPlat Code Coverage;Format=json,lcov,cobertura" +``` ### Advanced Options (Supported via runsettings) @@ -97,7 +108,8 @@ These are a list of options that are supported by coverlet. These can be specifi | IncludeTestAssembly | Include coverage of the test assembly. | | SkipAutoProps | Neither track nor record auto-implemented properties. | | DoesNotReturnAttribute | Methods marked with these attributes are known not to return, statements following them will be excluded from coverage | -| DeterministicReport | Generates deterministic report in context of deterministic build. Take a look at [documentation](DeterministicBuild.md) for further informations. | +| DeterministicReport | Generates deterministic report in context of deterministic build. Take a look at [documentation](DeterministicBuild.md) for further informations. +| ExcludeAssembliesWithoutSources | Specifies whether to exclude assemblies without source. Options are either MissingAll, MissingAny or None. Default is MissingAll.| How to specify these options via runsettings? @@ -119,6 +131,7 @@ How to specify these options via runsettings? true true false + MissingAll,MissingAny,None diff --git a/README.md b/README.md index e008ec44a..c60b9d37c 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Coverlet can be used through three different *drivers* Coverlet supports only SDK-style projects https://docs.microsoft.com/en-us/visualstudio/msbuild/how-to-use-project-sdk?view=vs-2019 -### VSTest Integration (preferred due to [known issue](https://github.com/coverlet-coverage/coverlet/blob/master/Documentation/KnownIssues.md#1-vstest-stops-process-execution-earlydotnet-test) supports only .NET Core application) +### VSTest Integration (preferred due to [known issue](https://github.com/coverlet-coverage/coverlet/blob/master/Documentation/KnownIssues.md#1-vstest-stops-process-execution-earlydotnet-test)) ### Installation ```bash diff --git a/coverlet.sln b/coverlet.sln index 34e7257db..efccfa31f 100644 --- a/coverlet.sln +++ b/coverlet.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28902.138 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32208.508 MinimumVisualStudioVersion = 15.0.26124.0 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E877EBA4-E78B-4F7D-A2D3-1E070FED04CD}" EndProject @@ -27,6 +27,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.tests.projectsampl EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{77A15177-8262-488F-AF2B-91B9055715DA}" ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig .gitignore = .gitignore eng\azure-pipelines-nightly.yml = eng\azure-pipelines-nightly.yml eng\azure-pipelines.yml = eng\azure-pipelines.yml @@ -53,9 +54,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{9A8B19D4 test\Directory.Build.targets = test\Directory.Build.targets EndProjectSection EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "coverlet.tests.projectsample.fsharp", "test\coverlet.tests.projectsample.fsharp\coverlet.tests.projectsample.fsharp.fsproj", "{1CBF6966-2A67-4D2C-8598-D174B83072F4}" +Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "coverlet.tests.projectsample.vbmynamespace", "test\coverlet.tests.projectsample.vbmynamespace\coverlet.tests.projectsample.vbmynamespace.vbproj", "{C9B7DC34-3E04-4F20-AED4-73791AF8020D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "coverlet.tests.projectsample.netframework", "test\coverlet.tests.projectsample.netframework\coverlet.tests.projectsample.netframework.csproj", "{E69D68C9-78ED-4076-A14B-D07295A4B2A5}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "coverlet.tests.projectsample.fsharp", "test\coverlet.tests.projectsample.fsharp\coverlet.tests.projectsample.fsharp.fsproj", "{1CBF6966-2A67-4D2C-8598-D174B83072F4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.tests.projectsample.netframework", "test\coverlet.tests.projectsample.netframework\coverlet.tests.projectsample.netframework.csproj", "{E69D68C9-78ED-4076-A14B-D07295A4B2A5}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -119,6 +122,10 @@ Global {F8199E19-FA9A-4559-9101-CAD7028121B4}.Debug|Any CPU.Build.0 = Debug|Any CPU {F8199E19-FA9A-4559-9101-CAD7028121B4}.Release|Any CPU.ActiveCfg = Release|Any CPU {F8199E19-FA9A-4559-9101-CAD7028121B4}.Release|Any CPU.Build.0 = Release|Any CPU + {C9B7DC34-3E04-4F20-AED4-73791AF8020D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9B7DC34-3E04-4F20-AED4-73791AF8020D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9B7DC34-3E04-4F20-AED4-73791AF8020D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9B7DC34-3E04-4F20-AED4-73791AF8020D}.Release|Any CPU.Build.0 = Release|Any CPU {1CBF6966-2A67-4D2C-8598-D174B83072F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1CBF6966-2A67-4D2C-8598-D174B83072F4}.Debug|Any CPU.Build.0 = Debug|Any CPU {1CBF6966-2A67-4D2C-8598-D174B83072F4}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -149,6 +156,7 @@ Global {9A8B19D4-4A24-4217-AEFE-159B68F029A1} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134} {1CBF6966-2A67-4D2C-8598-D174B83072F4} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134} {E69D68C9-78ED-4076-A14B-D07295A4B2A5} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134} + {C9B7DC34-3E04-4F20-AED4-73791AF8020D} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9CA57C02-97B0-4C38-A027-EA61E8741F10} diff --git a/eng/build.yml b/eng/build.yml index fb3a5b9c9..e4db0b0a6 100644 --- a/eng/build.yml +++ b/eng/build.yml @@ -9,6 +9,16 @@ steps: version: 5.0.401 displayName: Install .NET Core SDK 5.0.401 +- task: UseDotNet@2 + inputs: + version: 6.0.408 + displayName: Install .NET Core SDK 6.0.408 + +- task: UseDotNet@2 + inputs: + version: 7.0.203 + displayName: Install .NET Core SDK 7.0.203 + - script: dotnet restore displayName: Restore packages diff --git a/global.json b/global.json index c07142494..2dbcd442b 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "5.0.401", + "version": "6.0.408", "rollForward": "latestMajor" } } diff --git a/src/coverlet.collector/DataCollection/AttachmentManager.cs b/src/coverlet.collector/DataCollection/AttachmentManager.cs index a7b2d24a0..8259f71f3 100644 --- a/src/coverlet.collector/DataCollection/AttachmentManager.cs +++ b/src/coverlet.collector/DataCollection/AttachmentManager.cs @@ -1,7 +1,9 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using System.ComponentModel; using System.IO; - using coverlet.collector.Resources; using Coverlet.Collector.Utilities; using Coverlet.Collector.Utilities.Interfaces; @@ -50,7 +52,7 @@ public AttachmentManager(DataCollectionSink dataSink, DataCollectionContext data _reportDirectory = Path.Combine(Path.GetTempPath(), reportDirectoryName); // Register events - _dataSink.SendFileCompleted += this.OnSendFileCompleted; + _dataSink.SendFileCompleted += OnSendFileCompleted; } /// @@ -61,10 +63,10 @@ public AttachmentManager(DataCollectionSink dataSink, DataCollectionContext data public void SendCoverageReport(string coverageReport, string coverageReportFileName) { // Save coverage report to file - string coverageReportPath = this.SaveCoverageReport(coverageReport, coverageReportFileName); + string coverageReportPath = SaveCoverageReport(coverageReport, coverageReportFileName); // Send coverage attachment to test platform. - this.SendAttachment(coverageReportPath); + SendAttachment(coverageReportPath); } /// @@ -78,9 +80,9 @@ public void Dispose() _countDownEvent.Wait(); if (_dataSink != null) { - _dataSink.SendFileCompleted -= this.OnSendFileCompleted; + _dataSink.SendFileCompleted -= OnSendFileCompleted; } - this.CleanupReportDirectory(); + CleanupReportDirectory(); } catch (Exception ex) { diff --git a/src/coverlet.collector/DataCollection/CoverageManager.cs b/src/coverlet.collector/DataCollection/CoverageManager.cs index ce12489f7..89ec41eb1 100644 --- a/src/coverlet.collector/DataCollection/CoverageManager.cs +++ b/src/coverlet.collector/DataCollection/CoverageManager.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -77,8 +80,8 @@ public void InstrumentModules() public IEnumerable<(string report, string fileName)> GetCoverageReports() { // Get coverage result - CoverageResult coverageResult = this.GetCoverageResult(); - return this.GetCoverageReports(coverageResult); + CoverageResult coverageResult = GetCoverageResult(); + return GetCoverageReports(coverageResult); } /// diff --git a/src/coverlet.collector/DataCollection/CoverageWrapper.cs b/src/coverlet.collector/DataCollection/CoverageWrapper.cs index 12f4005e7..0569ab277 100644 --- a/src/coverlet.collector/DataCollection/CoverageWrapper.cs +++ b/src/coverlet.collector/DataCollection/CoverageWrapper.cs @@ -1,4 +1,7 @@ -using Coverlet.Collector.Utilities.Interfaces; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Coverlet.Collector.Utilities.Interfaces; using Coverlet.Core; using Coverlet.Core.Abstractions; @@ -30,7 +33,8 @@ public Coverage CreateCoverage(CoverletSettings settings, ILogger coverletLogger UseSourceLink = settings.UseSourceLink, SkipAutoProps = settings.SkipAutoProps, DoesNotReturnAttributes = settings.DoesNotReturnAttributes, - DeterministicReport = settings.DeterministicReport + DeterministicReport = settings.DeterministicReport, + ExcludeAssembliesWithoutSources = settings.ExcludeAssembliesWithoutSources }; return new Coverage( diff --git a/src/coverlet.collector/DataCollection/CoverletCoverageCollector.cs b/src/coverlet.collector/DataCollection/CoverletCoverageCollector.cs index d5094dc91..d40a02e86 100644 --- a/src/coverlet.collector/DataCollection/CoverletCoverageCollector.cs +++ b/src/coverlet.collector/DataCollection/CoverletCoverageCollector.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -124,7 +127,7 @@ private void OnSessionStart(object sender, SessionStartEventArgs sessionStartEve try { // Get coverlet settings - IEnumerable testModules = this.GetTestModules(sessionStartEventArgs); + IEnumerable testModules = GetTestModules(sessionStartEventArgs); var coverletSettingsParser = new CoverletSettingsParser(_eqtTrace); CoverletSettings coverletSettings = coverletSettingsParser.Parse(_configurationElement, testModules); @@ -142,7 +145,7 @@ private void OnSessionStart(object sender, SessionStartEventArgs sessionStartEve catch (Exception ex) { _logger.LogWarning(ex.ToString()); - this.Dispose(true); + Dispose(true); } } @@ -160,15 +163,13 @@ private void OnSessionEnd(object sender, SessionEndEventArgs e) // Get coverage reports IEnumerable<(string report, string fileName)> coverageReports = _coverageManager?.GetCoverageReports(); - if (coverageReports != null && coverageReports.Count() > 0) + if (coverageReports != null && coverageReports.Any()) { // Send result attachments to test platform. - using (var attachmentManager = new AttachmentManager(_dataSink, _dataCollectionContext, _logger, _eqtTrace, _countDownEventFactory.Create(coverageReports.Count(), TimeSpan.FromSeconds(30)))) + using var attachmentManager = new AttachmentManager(_dataSink, _dataCollectionContext, _logger, _eqtTrace, _countDownEventFactory.Create(coverageReports.Count(), TimeSpan.FromSeconds(30))); + foreach ((string report, string fileName) in coverageReports) { - foreach ((string report, string fileName) in coverageReports) - { - attachmentManager.SendCoverageReport(report, fileName); - } + attachmentManager.SendCoverageReport(report, fileName); } } else @@ -179,7 +180,7 @@ private void OnSessionEnd(object sender, SessionEndEventArgs e) catch (Exception ex) { _logger.LogWarning(ex.ToString()); - this.Dispose(true); + Dispose(true); } } @@ -223,11 +224,13 @@ private static IServiceCollection GetDefaultServiceCollection(TestPlatformEqtTra serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); + serviceCollection.AddTransient(); serviceCollection.AddTransient(_ => new CoverletLogger(eqtTrace, logger)); // We need to keep singleton/static semantics serviceCollection.AddSingleton(); // We cache resolutions - serviceCollection.AddSingleton(serviceProvider => new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService())); + serviceCollection.AddSingleton(serviceProvider => + new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService())); serviceCollection.AddSingleton(); return serviceCollection; } diff --git a/src/coverlet.collector/DataCollection/CoverletLogger.cs b/src/coverlet.collector/DataCollection/CoverletLogger.cs index 144d1ccf1..42eb6a1b2 100644 --- a/src/coverlet.collector/DataCollection/CoverletLogger.cs +++ b/src/coverlet.collector/DataCollection/CoverletLogger.cs @@ -1,5 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using Coverlet.Collector.Utilities; using Coverlet.Core.Abstractions; diff --git a/src/coverlet.collector/DataCollection/CoverletSettings.cs b/src/coverlet.collector/DataCollection/CoverletSettings.cs index 347173c76..2ebde799f 100644 --- a/src/coverlet.collector/DataCollection/CoverletSettings.cs +++ b/src/coverlet.collector/DataCollection/CoverletSettings.cs @@ -1,4 +1,7 @@ -using System.Linq; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Linq; using System.Text; namespace Coverlet.Collector.DataCollection @@ -78,6 +81,11 @@ internal class CoverletSettings /// public bool DeterministicReport { get; set; } + /// + /// Switch for heuristic to automatically exclude assemblies without source. + /// + public string ExcludeAssembliesWithoutSources { get; set; } + public override string ToString() { var builder = new StringBuilder(); @@ -95,6 +103,7 @@ public override string ToString() builder.AppendFormat("SkipAutoProps: '{0}'", SkipAutoProps); builder.AppendFormat("DoesNotReturnAttributes: '{0}'", string.Join(",", DoesNotReturnAttributes ?? Enumerable.Empty())); builder.AppendFormat("DeterministicReport: '{0}'", DeterministicReport); + builder.AppendFormat("ExcludeAssembliesWithoutSources: '{0}'", ExcludeAssembliesWithoutSources); return builder.ToString(); } diff --git a/src/coverlet.collector/DataCollection/CoverletSettingsParser.cs b/src/coverlet.collector/DataCollection/CoverletSettingsParser.cs index 7fe778173..3776c98d4 100644 --- a/src/coverlet.collector/DataCollection/CoverletSettingsParser.cs +++ b/src/coverlet.collector/DataCollection/CoverletSettingsParser.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using System.Collections.Generic; using System.Linq; using System.Xml; @@ -45,6 +48,7 @@ public CoverletSettings Parse(XmlElement configurationElement, IEnumerable /// Test modules /// Test module - private string ParseTestModule(IEnumerable testModules) + private static string ParseTestModule(IEnumerable testModules) { // Validate if at least one source present. if (testModules == null || !testModules.Any()) @@ -83,13 +87,13 @@ private string ParseTestModule(IEnumerable testModules) /// /// Configuration element /// Report formats - private string[] ParseReportFormats(XmlElement configurationElement) + private static string[] ParseReportFormats(XmlElement configurationElement) { string[] formats = Array.Empty(); if (configurationElement != null) { XmlElement reportFormatElement = configurationElement[CoverletConstants.ReportFormatElementName]; - formats = this.SplitElement(reportFormatElement); + formats = SplitElement(reportFormatElement); } return formats is null || formats.Length == 0 ? new[] { CoverletConstants.DefaultReportFormat } : formats; @@ -100,10 +104,10 @@ private string[] ParseReportFormats(XmlElement configurationElement) /// /// Configuration element /// Filters to include - private string[] ParseIncludeFilters(XmlElement configurationElement) + private static string[] ParseIncludeFilters(XmlElement configurationElement) { XmlElement includeFiltersElement = configurationElement[CoverletConstants.IncludeFiltersElementName]; - return this.SplitElement(includeFiltersElement); + return SplitElement(includeFiltersElement); } /// @@ -111,10 +115,10 @@ private string[] ParseIncludeFilters(XmlElement configurationElement) /// /// Configuration element /// Directories to include - private string[] ParseIncludeDirectories(XmlElement configurationElement) + private static string[] ParseIncludeDirectories(XmlElement configurationElement) { XmlElement includeDirectoriesElement = configurationElement[CoverletConstants.IncludeDirectoriesElementName]; - return this.SplitElement(includeDirectoriesElement); + return SplitElement(includeDirectoriesElement); } /// @@ -122,14 +126,14 @@ private string[] ParseIncludeDirectories(XmlElement configurationElement) /// /// Configuration element /// Filters to exclude - private string[] ParseExcludeFilters(XmlElement configurationElement) + private static string[] ParseExcludeFilters(XmlElement configurationElement) { - List excludeFilters = new List { CoverletConstants.DefaultExcludeFilter }; + var excludeFilters = new List { CoverletConstants.DefaultExcludeFilter }; if (configurationElement != null) { XmlElement excludeFiltersElement = configurationElement[CoverletConstants.ExcludeFiltersElementName]; - string[] filters = this.SplitElement(excludeFiltersElement); + string[] filters = SplitElement(excludeFiltersElement); if (filters != null) { excludeFilters.AddRange(filters); @@ -144,10 +148,10 @@ private string[] ParseExcludeFilters(XmlElement configurationElement) /// /// Configuration element /// Source files to exclude - private string[] ParseExcludeSourceFiles(XmlElement configurationElement) + private static string[] ParseExcludeSourceFiles(XmlElement configurationElement) { XmlElement excludeSourceFilesElement = configurationElement[CoverletConstants.ExcludeSourceFilesElementName]; - return this.SplitElement(excludeSourceFilesElement); + return SplitElement(excludeSourceFilesElement); } /// @@ -155,10 +159,10 @@ private string[] ParseExcludeSourceFiles(XmlElement configurationElement) /// /// Configuration element /// Attributes to exclude - private string[] ParseExcludeAttributes(XmlElement configurationElement) + private static string[] ParseExcludeAttributes(XmlElement configurationElement) { XmlElement excludeAttributesElement = configurationElement[CoverletConstants.ExcludeAttributesElementName]; - return this.SplitElement(excludeAttributesElement); + return SplitElement(excludeAttributesElement); } /// @@ -166,7 +170,7 @@ private string[] ParseExcludeAttributes(XmlElement configurationElement) /// /// Configuration element /// Merge with attribute - private string ParseMergeWith(XmlElement configurationElement) + private static string ParseMergeWith(XmlElement configurationElement) { XmlElement mergeWithElement = configurationElement[CoverletConstants.MergeWithElementName]; return mergeWithElement?.InnerText; @@ -177,7 +181,7 @@ private string ParseMergeWith(XmlElement configurationElement) /// /// Configuration element /// Use source link flag - private bool ParseUseSourceLink(XmlElement configurationElement) + private static bool ParseUseSourceLink(XmlElement configurationElement) { XmlElement useSourceLinkElement = configurationElement[CoverletConstants.UseSourceLinkElementName]; bool.TryParse(useSourceLinkElement?.InnerText, out bool useSourceLink); @@ -189,7 +193,7 @@ private bool ParseUseSourceLink(XmlElement configurationElement) /// /// Configuration element /// Single hit flag - private bool ParseSingleHit(XmlElement configurationElement) + private static bool ParseSingleHit(XmlElement configurationElement) { XmlElement singleHitElement = configurationElement[CoverletConstants.SingleHitElementName]; bool.TryParse(singleHitElement?.InnerText, out bool singleHit); @@ -201,19 +205,30 @@ private bool ParseSingleHit(XmlElement configurationElement) /// /// Configuration element /// ParseDeterministicReport flag - private bool ParseDeterministicReport(XmlElement configurationElement) + private static bool ParseDeterministicReport(XmlElement configurationElement) { XmlElement deterministicReportElement = configurationElement[CoverletConstants.DeterministicReport]; bool.TryParse(deterministicReportElement?.InnerText, out bool deterministicReport); return deterministicReport; } + /// + /// Parse ExcludeAssembliesWithoutSources flag + /// + /// Configuration element + /// ExcludeAssembliesWithoutSources flag + private static string ParseExcludeAssembliesWithoutSources(XmlElement configurationElement) + { + XmlElement instrumentModulesWithoutLocalSourcesElement = configurationElement[CoverletConstants.ExcludeAssembliesWithoutSources]; + return instrumentModulesWithoutLocalSourcesElement?.InnerText; + } + /// /// Parse include test assembly flag /// /// Configuration element /// Include Test Assembly Flag - private bool ParseIncludeTestAssembly(XmlElement configurationElement) + private static bool ParseIncludeTestAssembly(XmlElement configurationElement) { XmlElement includeTestAssemblyElement = configurationElement[CoverletConstants.IncludeTestAssemblyElementName]; bool.TryParse(includeTestAssemblyElement?.InnerText, out bool includeTestAssembly); @@ -225,7 +240,7 @@ private bool ParseIncludeTestAssembly(XmlElement configurationElement) /// /// Configuration element /// Include Test Assembly Flag - private bool ParseSkipAutoProps(XmlElement configurationElement) + private static bool ParseSkipAutoProps(XmlElement configurationElement) { XmlElement skipAutoPropsElement = configurationElement[CoverletConstants.SkipAutoProps]; bool.TryParse(skipAutoPropsElement?.InnerText, out bool skipAutoProps); @@ -237,10 +252,10 @@ private bool ParseSkipAutoProps(XmlElement configurationElement) /// /// Configuration element /// DoesNotReturn attributes - private string[] ParseDoesNotReturnAttributes(XmlElement configurationElement) + private static string[] ParseDoesNotReturnAttributes(XmlElement configurationElement) { XmlElement doesNotReturnAttributesElement = configurationElement[CoverletConstants.DoesNotReturnAttributesElementName]; - return this.SplitElement(doesNotReturnAttributesElement); + return SplitElement(doesNotReturnAttributesElement); } /// @@ -248,7 +263,7 @@ private string[] ParseDoesNotReturnAttributes(XmlElement configurationElement) /// /// The element to split /// An array of the values in the element - private string[] SplitElement(XmlElement element) + private static string[] SplitElement(XmlElement element) { return element?.InnerText?.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Where(value => !string.IsNullOrWhiteSpace(value)).Select(value => value.Trim()).ToArray(); } diff --git a/src/coverlet.collector/InProcDataCollection/CoverletInProcDataCollector.cs b/src/coverlet.collector/InProcDataCollection/CoverletInProcDataCollector.cs index 5539d6b04..f682b2687 100644 --- a/src/coverlet.collector/InProcDataCollection/CoverletInProcDataCollector.cs +++ b/src/coverlet.collector/InProcDataCollection/CoverletInProcDataCollector.cs @@ -1,8 +1,10 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using System.Diagnostics; using System.Reflection; using System.Text; - using coverlet.collector.Resources; using Coverlet.Collector.Utilities; using Coverlet.Core.Instrumentation; @@ -15,7 +17,7 @@ namespace Coverlet.Collector.DataCollection public class CoverletInProcDataCollector : InProcDataCollection { private TestPlatformEqtTrace _eqtTrace; - private bool _enableExceptionLog = false; + private bool _enableExceptionLog; private void AttachDebugger() { @@ -53,7 +55,7 @@ public void TestCaseStart(TestCaseStartArgs testCaseStartArgs) public void TestSessionEnd(TestSessionEndArgs testSessionEndArgs) { - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { Type injectedInstrumentationClass = GetInstrumentationClass(assembly); if (injectedInstrumentationClass is null) @@ -64,7 +66,7 @@ public void TestSessionEnd(TestSessionEndArgs testSessionEndArgs) try { _eqtTrace.Verbose($"Calling ModuleTrackerTemplate.UnloadModule for '{injectedInstrumentationClass.Assembly.FullName}'"); - var unloadModule = injectedInstrumentationClass.GetMethod(nameof(ModuleTrackerTemplate.UnloadModule), new[] { typeof(object), typeof(EventArgs) }); + MethodInfo unloadModule = injectedInstrumentationClass.GetMethod(nameof(ModuleTrackerTemplate.UnloadModule), new[] { typeof(object), typeof(EventArgs) }); unloadModule.Invoke(null, new[] { (object)this, EventArgs.Empty }); injectedInstrumentationClass.GetField("FlushHitFile", BindingFlags.Static | BindingFlags.Public).SetValue(null, false); _eqtTrace.Verbose($"Called ModuleTrackerTemplate.UnloadModule for '{injectedInstrumentationClass.Assembly.FullName}'"); @@ -89,7 +91,7 @@ private Type GetInstrumentationClass(Assembly assembly) { try { - foreach (var type in assembly.GetTypes()) + foreach (Type type in assembly.GetTypes()) { if (type.Namespace == "Coverlet.Core.Instrumentation.Tracker" && type.Name.StartsWith(assembly.GetName().Name + "_")) @@ -104,7 +106,7 @@ private Type GetInstrumentationClass(Assembly assembly) { if (_enableExceptionLog) { - StringBuilder exceptionString = new StringBuilder(); + var exceptionString = new StringBuilder(); exceptionString.AppendFormat("{0}: Failed to get Instrumentation class for assembly '{1}' with error: {2}", CoverletConstants.InProcDataCollectorName, assembly, ex); exceptionString.AppendLine(); diff --git a/src/coverlet.collector/Properties/AssemblyInfo.cs b/src/coverlet.collector/Properties/AssemblyInfo.cs index 35079a9c9..4d4a63712 100644 --- a/src/coverlet.collector/Properties/AssemblyInfo.cs +++ b/src/coverlet.collector/Properties/AssemblyInfo.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System.Reflection; using System.Runtime.CompilerServices; diff --git a/src/coverlet.collector/Utilities/CountDownEvent.cs b/src/coverlet.collector/Utilities/CountDownEvent.cs index a5a19b6f7..265c54103 100644 --- a/src/coverlet.collector/Utilities/CountDownEvent.cs +++ b/src/coverlet.collector/Utilities/CountDownEvent.cs @@ -1,6 +1,8 @@ -using System; -using System.Threading; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; +using System.Threading; using Coverlet.Collector.Utilities.Interfaces; namespace Coverlet.Collector.Utilities diff --git a/src/coverlet.collector/Utilities/CoverletConstants.cs b/src/coverlet.collector/Utilities/CoverletConstants.cs index 431beafc6..3a7bfa998 100644 --- a/src/coverlet.collector/Utilities/CoverletConstants.cs +++ b/src/coverlet.collector/Utilities/CoverletConstants.cs @@ -1,4 +1,7 @@ -namespace Coverlet.Collector.Utilities +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Coverlet.Collector.Utilities { internal static class CoverletConstants { @@ -23,5 +26,6 @@ internal static class CoverletConstants public const string SkipAutoProps = "SkipAutoProps"; public const string DoesNotReturnAttributesElementName = "DoesNotReturnAttribute"; public const string DeterministicReport = "DeterministicReport"; + public const string ExcludeAssembliesWithoutSources = "ExcludeAssembliesWithoutSources"; } } diff --git a/src/coverlet.collector/Utilities/CoverletDataCollectorException.cs b/src/coverlet.collector/Utilities/CoverletDataCollectorException.cs index e0665673b..189b363f8 100644 --- a/src/coverlet.collector/Utilities/CoverletDataCollectorException.cs +++ b/src/coverlet.collector/Utilities/CoverletDataCollectorException.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; namespace Coverlet.Collector.Utilities { diff --git a/src/coverlet.collector/Utilities/DirectoryHelper.cs b/src/coverlet.collector/Utilities/DirectoryHelper.cs index d03d212cc..c9800a21b 100644 --- a/src/coverlet.collector/Utilities/DirectoryHelper.cs +++ b/src/coverlet.collector/Utilities/DirectoryHelper.cs @@ -1,4 +1,7 @@ -using System.IO; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; using Coverlet.Collector.Utilities.Interfaces; namespace Coverlet.Collector.Utilities diff --git a/src/coverlet.collector/Utilities/FileHelper.cs b/src/coverlet.collector/Utilities/FileHelper.cs index f6e578380..f8a85b13e 100644 --- a/src/coverlet.collector/Utilities/FileHelper.cs +++ b/src/coverlet.collector/Utilities/FileHelper.cs @@ -1,4 +1,7 @@ -using System.IO; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; using Coverlet.Collector.Utilities.Interfaces; namespace Coverlet.Collector.Utilities diff --git a/src/coverlet.collector/Utilities/Interfaces/ICountDown.cs b/src/coverlet.collector/Utilities/Interfaces/ICountDown.cs index 3c884b0bb..69eddbce4 100644 --- a/src/coverlet.collector/Utilities/Interfaces/ICountDown.cs +++ b/src/coverlet.collector/Utilities/Interfaces/ICountDown.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; namespace Coverlet.Collector.Utilities.Interfaces { diff --git a/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs b/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs index 1358fab1b..1a34612c0 100644 --- a/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs +++ b/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs @@ -1,4 +1,7 @@ -using Coverlet.Collector.DataCollection; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Coverlet.Collector.DataCollection; using Coverlet.Core; using Coverlet.Core.Abstractions; diff --git a/src/coverlet.collector/Utilities/Interfaces/IDirectoryHelper.cs b/src/coverlet.collector/Utilities/Interfaces/IDirectoryHelper.cs index 8e26c0dcf..f148f21ea 100644 --- a/src/coverlet.collector/Utilities/Interfaces/IDirectoryHelper.cs +++ b/src/coverlet.collector/Utilities/Interfaces/IDirectoryHelper.cs @@ -1,4 +1,7 @@ -namespace Coverlet.Collector.Utilities.Interfaces +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Coverlet.Collector.Utilities.Interfaces { interface IDirectoryHelper { diff --git a/src/coverlet.collector/Utilities/Interfaces/IFileHelper.cs b/src/coverlet.collector/Utilities/Interfaces/IFileHelper.cs index 8fd0ab9ea..1ddcc8678 100644 --- a/src/coverlet.collector/Utilities/Interfaces/IFileHelper.cs +++ b/src/coverlet.collector/Utilities/Interfaces/IFileHelper.cs @@ -1,4 +1,7 @@ -namespace Coverlet.Collector.Utilities.Interfaces +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Coverlet.Collector.Utilities.Interfaces { internal interface IFileHelper { diff --git a/src/coverlet.collector/Utilities/TestPlatformEqtTrace.cs b/src/coverlet.collector/Utilities/TestPlatformEqtTrace.cs index 31f5d4409..7c02f6d03 100644 --- a/src/coverlet.collector/Utilities/TestPlatformEqtTrace.cs +++ b/src/coverlet.collector/Utilities/TestPlatformEqtTrace.cs @@ -1,4 +1,7 @@ -using Microsoft.VisualStudio.TestPlatform.ObjectModel; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; namespace Coverlet.Collector.Utilities { diff --git a/src/coverlet.collector/Utilities/TestPlatformLogger.cs b/src/coverlet.collector/Utilities/TestPlatformLogger.cs index 47f55c897..105e4c9d9 100644 --- a/src/coverlet.collector/Utilities/TestPlatformLogger.cs +++ b/src/coverlet.collector/Utilities/TestPlatformLogger.cs @@ -1,4 +1,7 @@ -using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; namespace Coverlet.Collector.Utilities { diff --git a/src/coverlet.collector/build/netstandard1.0/coverlet.collector.targets b/src/coverlet.collector/build/netstandard1.0/coverlet.collector.targets index 2e4adb4c5..7bd7b28e7 100644 --- a/src/coverlet.collector/build/netstandard1.0/coverlet.collector.targets +++ b/src/coverlet.collector/build/netstandard1.0/coverlet.collector.targets @@ -44,7 +44,7 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and <_mapping Include="@(_byProject->'%(Identity)|%(OriginalPath)=%(MappedPath)')" /> - <_sourceRootMappingFilePath>$([MSBuild]::EnsureTrailingSlash('$(OutputPath)'))CoverletSourceRootsMapping + <_sourceRootMappingFilePath>$([MSBuild]::EnsureTrailingSlash('$(OutputPath)'))CoverletSourceRootsMapping_$(AssemblyName) /// Exit Codes returned from Coverlet console process. diff --git a/src/coverlet.console/Logging/ConsoleLogger.cs b/src/coverlet.console/Logging/ConsoleLogger.cs index 98e620f16..1e41af504 100644 --- a/src/coverlet.console/Logging/ConsoleLogger.cs +++ b/src/coverlet.console/Logging/ConsoleLogger.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using Coverlet.Core.Abstractions; using static System.Console; @@ -6,7 +9,8 @@ namespace Coverlet.Console.Logging { class ConsoleLogger : ILogger { - private static readonly object _sync = new object(); + private static readonly object s_sync = new(); + public LogLevel Level { get; set; } = LogLevel.Normal; public void LogError(string message) => Log(LogLevel.Quiet, message, ConsoleColor.Red); @@ -23,7 +27,7 @@ private void Log(LogLevel level, string message, ConsoleColor color) { if (level < Level) return; - lock (_sync) + lock (s_sync) { ConsoleColor currentForegroundColor; if (color != (currentForegroundColor = ForegroundColor)) @@ -39,4 +43,4 @@ private void Log(LogLevel level, string message, ConsoleColor color) } } } -} \ No newline at end of file +} diff --git a/src/coverlet.console/Logging/LogLevel.cs b/src/coverlet.console/Logging/LogLevel.cs index 5ec47ef90..2e0cf7320 100644 --- a/src/coverlet.console/Logging/LogLevel.cs +++ b/src/coverlet.console/Logging/LogLevel.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + namespace Coverlet.Console.Logging { /// diff --git a/src/coverlet.console/Program.cs b/src/coverlet.console/Program.cs index ae3b02d03..d967f90d6 100644 --- a/src/coverlet.console/Program.cs +++ b/src/coverlet.console/Program.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; @@ -6,7 +9,6 @@ using System.IO; using System.Linq; using System.Text; - using ConsoleTables; using Coverlet.Console.Logging; using Coverlet.Core; @@ -37,7 +39,7 @@ static int Main(string[] args) ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider(); var logger = (ConsoleLogger)serviceProvider.GetService(); - var fileSystem = serviceProvider.GetService(); + IFileSystem fileSystem = serviceProvider.GetService(); var app = new CommandLineApplication { @@ -68,6 +70,7 @@ static int Main(string[] args) CommandOption mergeWith = app.Option("--merge-with", "Path to existing coverage result to merge.", CommandOptionType.SingleValue); CommandOption useSourceLink = app.Option("--use-source-link", "Specifies whether to use SourceLink URIs in place of file system paths.", CommandOptionType.NoValue); CommandOption doesNotReturnAttributes = app.Option("--does-not-return-attribute", "Attributes that mark methods that do not return.", CommandOptionType.MultipleValue); + CommandOption excludeAssembliesWithoutSources = app.Option("--exclude-assemblies-without-sources", "Specifies behaviour of heuristic to ignore assemblies with missing source documents.", CommandOptionType.SingleValue); app.OnExecute(() => { @@ -95,7 +98,8 @@ static int Main(string[] args) MergeWith = mergeWith.Value(), UseSourceLink = useSourceLink.HasValue(), SkipAutoProps = skipAutoProp.HasValue(), - DoesNotReturnAttributes = doesNotReturnAttributes.Values.ToArray() + DoesNotReturnAttributes = doesNotReturnAttributes.Values.ToArray(), + ExcludeAssembliesWithoutSources = excludeAssembliesWithoutSources.Value() }; ISourceRootTranslator sourceRootTranslator = serviceProvider.GetRequiredService(); @@ -134,15 +138,15 @@ static int Main(string[] args) process.WaitForExit(); - var dOutput = output.HasValue() ? output.Value() : Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar.ToString(); - var dThresholdTypes = thresholdTypes.HasValue() ? thresholdTypes.Values : new List(new string[] { "line", "branch", "method" }); - var dThresholdStat = thresholdStat.HasValue() ? Enum.Parse(thresholdStat.Value(), true) : Enum.Parse("minimum", true); + string dOutput = output.HasValue() ? output.Value() : Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar.ToString(); + List dThresholdTypes = thresholdTypes.HasValue() ? thresholdTypes.Values : new List(new string[] { "line", "branch", "method" }); + ThresholdStatistic dThresholdStat = thresholdStat.HasValue() ? Enum.Parse(thresholdStat.Value(), true) : Enum.Parse("minimum", true); logger.LogInformation("\nCalculating coverage result..."); - var result = coverage.GetCoverageResult(); - - var directory = Path.GetDirectoryName(dOutput); + CoverageResult result = coverage.GetCoverageResult(); + + string directory = Path.GetDirectoryName(dOutput); if (directory == string.Empty) { directory = Directory.GetCurrentDirectory(); @@ -152,9 +156,9 @@ static int Main(string[] args) Directory.CreateDirectory(directory); } - foreach (var format in formats.HasValue() ? formats.Values : new List(new string[] { "json" })) + foreach (string format in formats.HasValue() ? formats.Values : new List(new string[] { "json" })) { - var reporter = new ReporterFactory(format).CreateReporter(); + Core.Abstractions.IReporter reporter = new ReporterFactory(format).CreateReporter(); if (reporter == null) { throw new Exception($"Specified output format '{format}' is not supported"); @@ -169,19 +173,19 @@ static int Main(string[] args) else { // Output to file - var filename = Path.GetFileName(dOutput); + string filename = Path.GetFileName(dOutput); filename = (filename == string.Empty) ? $"coverage.{reporter.Extension}" : filename; filename = Path.HasExtension(filename) ? filename : $"{filename}.{reporter.Extension}"; - var report = Path.Combine(directory, filename); + string report = Path.Combine(directory, filename); logger.LogInformation($" Generating report '{report}'", important: true); fileSystem.WriteAllText(report, reporter.Report(result, sourceRootTranslator)); } } var thresholdTypeFlagQueue = new Queue(); - - foreach (var thresholdType in dThresholdTypes) + + foreach (string thresholdType in dThresholdTypes) { if (thresholdType.Equals("line", StringComparison.OrdinalIgnoreCase)) { @@ -196,19 +200,19 @@ static int Main(string[] args) thresholdTypeFlagQueue.Enqueue(ThresholdTypeFlags.Method); } } - - Dictionary thresholdTypeFlagValues = new Dictionary(); + + var thresholdTypeFlagValues = new Dictionary(); if (threshold.HasValue() && threshold.Value().Contains(',')) { - var thresholdValues = threshold.Value().Split(',', StringSplitOptions.RemoveEmptyEntries).Select(t => t.Trim()); - if (thresholdValues.Count() != thresholdTypeFlagQueue.Count()) + IEnumerable thresholdValues = threshold.Value().Split(',', StringSplitOptions.RemoveEmptyEntries).Select(t => t.Trim()); + if (thresholdValues.Count() != thresholdTypeFlagQueue.Count) { - throw new Exception($"Threshold type flag count ({thresholdTypeFlagQueue.Count()}) and values count ({thresholdValues.Count()}) doesn't match"); + throw new Exception($"Threshold type flag count ({thresholdTypeFlagQueue.Count}) and values count ({thresholdValues.Count()}) doesn't match"); } - foreach (var thresholdValue in thresholdValues) + foreach (string thresholdValue in thresholdValues) { - if (double.TryParse(thresholdValue, out var value)) + if (double.TryParse(thresholdValue, out double value)) { thresholdTypeFlagValues[thresholdTypeFlagQueue.Dequeue()] = value; } @@ -231,23 +235,23 @@ static int Main(string[] args) var coverageTable = new ConsoleTable("Module", "Line", "Branch", "Method"); var summary = new CoverageSummary(); - var linePercentCalculation = summary.CalculateLineCoverage(result.Modules); - var branchPercentCalculation = summary.CalculateBranchCoverage(result.Modules); - var methodPercentCalculation = summary.CalculateMethodCoverage(result.Modules); + CoverageDetails linePercentCalculation = summary.CalculateLineCoverage(result.Modules); + CoverageDetails branchPercentCalculation = summary.CalculateBranchCoverage(result.Modules); + CoverageDetails methodPercentCalculation = summary.CalculateMethodCoverage(result.Modules); - var totalLinePercent = linePercentCalculation.Percent; - var totalBranchPercent = branchPercentCalculation.Percent; - var totalMethodPercent = methodPercentCalculation.Percent; + double totalLinePercent = linePercentCalculation.Percent; + double totalBranchPercent = branchPercentCalculation.Percent; + double totalMethodPercent = methodPercentCalculation.Percent; - var averageLinePercent = linePercentCalculation.AverageModulePercent; - var averageBranchPercent = branchPercentCalculation.AverageModulePercent; - var averageMethodPercent = methodPercentCalculation.AverageModulePercent; + double averageLinePercent = linePercentCalculation.AverageModulePercent; + double averageBranchPercent = branchPercentCalculation.AverageModulePercent; + double averageMethodPercent = methodPercentCalculation.AverageModulePercent; - foreach (var _module in result.Modules) + foreach (KeyValuePair _module in result.Modules) { - var linePercent = summary.CalculateLineCoverage(_module.Value).Percent; - var branchPercent = summary.CalculateBranchCoverage(_module.Value).Percent; - var methodPercent = summary.CalculateMethodCoverage(_module.Value).Percent; + double linePercent = summary.CalculateLineCoverage(_module.Value).Percent; + double branchPercent = summary.CalculateBranchCoverage(_module.Value).Percent; + double methodPercent = summary.CalculateMethodCoverage(_module.Value).Percent; coverageTable.AddRow(Path.GetFileNameWithoutExtension(_module.Key), $"{InvariantFormat(linePercent)}%", $"{InvariantFormat(branchPercent)}%", $"{InvariantFormat(methodPercent)}%"); } @@ -266,15 +270,15 @@ static int Main(string[] args) { exitCode += (int)CommandExitCodes.TestFailed; } - - var thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, thresholdTypeFlagValues, dThresholdStat); + + ThresholdTypeFlags thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, thresholdTypeFlagValues, dThresholdStat); if (thresholdTypeFlags != ThresholdTypeFlags.None) { exitCode += (int)CommandExitCodes.CoverageBelowThreshold; var exceptionMessageBuilder = new StringBuilder(); if ((thresholdTypeFlags & ThresholdTypeFlags.Line) != ThresholdTypeFlags.None) { - exceptionMessageBuilder.AppendLine($"The {dThresholdStat.ToString().ToLower()} line coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Line]}"); + exceptionMessageBuilder.AppendLine($"The {dThresholdStat.ToString().ToLower()} line coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Line]}"); } if ((thresholdTypeFlags & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None) @@ -284,7 +288,7 @@ static int Main(string[] args) if ((thresholdTypeFlags & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None) { - exceptionMessageBuilder.AppendLine($"The {dThresholdStat.ToString().ToLower()} method coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Method]}"); + exceptionMessageBuilder.AppendLine($"The {dThresholdStat.ToString().ToLower()} method coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Method]}"); } throw new Exception(exceptionMessageBuilder.ToString()); } diff --git a/src/coverlet.console/Properties/AssemblyInfo.cs b/src/coverlet.console/Properties/AssemblyInfo.cs index 27cbf8a13..59db2a82f 100644 --- a/src/coverlet.console/Properties/AssemblyInfo.cs +++ b/src/coverlet.console/Properties/AssemblyInfo.cs @@ -1 +1,4 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + [assembly: System.Reflection.AssemblyKeyFileAttribute("coverlet.console.snk")] \ No newline at end of file diff --git a/src/coverlet.console/coverlet.console.csproj b/src/coverlet.console/coverlet.console.csproj index 5d33ebec8..b44e05dff 100644 --- a/src/coverlet.console/coverlet.console.csproj +++ b/src/coverlet.console/coverlet.console.csproj @@ -1,8 +1,8 @@ - + Exe - net5.0 + net6.0 coverlet true coverlet.console diff --git a/src/coverlet.core/Abstractions/IAssemblyAdapter.cs b/src/coverlet.core/Abstractions/IAssemblyAdapter.cs new file mode 100644 index 000000000..48b23084b --- /dev/null +++ b/src/coverlet.core/Abstractions/IAssemblyAdapter.cs @@ -0,0 +1,10 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Coverlet.Core.Abstractions +{ + internal interface IAssemblyAdapter + { + string GetAssemblyName(string assemblyPath); + } +} diff --git a/src/coverlet.core/Abstractions/ICecilSymbolHelper.cs b/src/coverlet.core/Abstractions/ICecilSymbolHelper.cs index 10bf1719a..06cdde3d1 100644 --- a/src/coverlet.core/Abstractions/ICecilSymbolHelper.cs +++ b/src/coverlet.core/Abstractions/ICecilSymbolHelper.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; using Coverlet.Core.Symbols; using Mono.Cecil; using Mono.Cecil.Cil; diff --git a/src/coverlet.core/Abstractions/IConsole.cs b/src/coverlet.core/Abstractions/IConsole.cs index 9bc7e4e0b..72991cac5 100644 --- a/src/coverlet.core/Abstractions/IConsole.cs +++ b/src/coverlet.core/Abstractions/IConsole.cs @@ -1,4 +1,7 @@ -namespace Coverlet.Core.Abstractions +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Coverlet.Core.Abstractions { internal interface IConsole { diff --git a/src/coverlet.core/Abstractions/IFileSystem.cs b/src/coverlet.core/Abstractions/IFileSystem.cs index 54185dbf5..cb710c758 100644 --- a/src/coverlet.core/Abstractions/IFileSystem.cs +++ b/src/coverlet.core/Abstractions/IFileSystem.cs @@ -1,4 +1,7 @@ -using System.IO; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; namespace Coverlet.Core.Abstractions { diff --git a/src/coverlet.core/Abstractions/IInstrumentationHelper.cs b/src/coverlet.core/Abstractions/IInstrumentationHelper.cs index 295a111d1..65af40011 100644 --- a/src/coverlet.core/Abstractions/IInstrumentationHelper.cs +++ b/src/coverlet.core/Abstractions/IInstrumentationHelper.cs @@ -1,4 +1,9 @@ -namespace Coverlet.Core.Abstractions +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Coverlet.Core.Enums; + +namespace Coverlet.Core.Abstractions { internal interface IInstrumentationHelper { @@ -12,8 +17,8 @@ internal interface IInstrumentationHelper bool IsTypeExcluded(string module, string type, string[] excludeFilters); bool IsTypeIncluded(string module, string type, string[] includeFilters); void RestoreOriginalModule(string module, string identifier); - bool EmbeddedPortablePdbHasLocalSource(string module, out string firstNotFoundDocument); - bool PortablePdbHasLocalSource(string module, out string firstNotFoundDocument); + bool EmbeddedPortablePdbHasLocalSource(string module, AssemblySearchType excludeAssembliesWithoutSources); + bool PortablePdbHasLocalSource(string module, AssemblySearchType excludeAssembliesWithoutSources); bool IsLocalMethod(string method); void SetLogger(ILogger logger); } diff --git a/src/coverlet.core/Abstractions/ILogger.cs b/src/coverlet.core/Abstractions/ILogger.cs index bc7e78c01..c3e6ef15e 100644 --- a/src/coverlet.core/Abstractions/ILogger.cs +++ b/src/coverlet.core/Abstractions/ILogger.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; namespace Coverlet.Core.Abstractions diff --git a/src/coverlet.core/Abstractions/IProcessExitHandler.cs b/src/coverlet.core/Abstractions/IProcessExitHandler.cs index fc5262d89..635015946 100644 --- a/src/coverlet.core/Abstractions/IProcessExitHandler.cs +++ b/src/coverlet.core/Abstractions/IProcessExitHandler.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; namespace Coverlet.Core.Abstractions { diff --git a/src/coverlet.core/Abstractions/IReporter.cs b/src/coverlet.core/Abstractions/IReporter.cs index 5a76d1858..9e497d62a 100644 --- a/src/coverlet.core/Abstractions/IReporter.cs +++ b/src/coverlet.core/Abstractions/IReporter.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + namespace Coverlet.Core.Abstractions { internal interface IReporter diff --git a/src/coverlet.core/Abstractions/IRetryHelper.cs b/src/coverlet.core/Abstractions/IRetryHelper.cs index c0e6e14cb..88a1b29d5 100644 --- a/src/coverlet.core/Abstractions/IRetryHelper.cs +++ b/src/coverlet.core/Abstractions/IRetryHelper.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; namespace Coverlet.Core.Abstractions { diff --git a/src/coverlet.core/Abstractions/ISourceRootTranslator.cs b/src/coverlet.core/Abstractions/ISourceRootTranslator.cs index c91077950..4af6cfddf 100644 --- a/src/coverlet.core/Abstractions/ISourceRootTranslator.cs +++ b/src/coverlet.core/Abstractions/ISourceRootTranslator.cs @@ -1,10 +1,14 @@ -using System.Collections.Generic; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; using Coverlet.Core.Helpers; namespace Coverlet.Core.Abstractions { internal interface ISourceRootTranslator { + bool AddMappingInCache(string originalFileName, string targetFileName); string ResolveFilePath(string originalFileName); string ResolveDeterministicPath(string originalFileName); IReadOnlyList ResolvePathRoot(string pathRoot); diff --git a/src/coverlet.core/Attributes/DoesNotReturnAttribute.cs b/src/coverlet.core/Attributes/DoesNotReturnAttribute.cs index a35be1336..cebe198f1 100644 --- a/src/coverlet.core/Attributes/DoesNotReturnAttribute.cs +++ b/src/coverlet.core/Attributes/DoesNotReturnAttribute.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; namespace Coverlet.Core.Attributes { diff --git a/src/coverlet.core/Attributes/ExcludeFromCoverage.cs b/src/coverlet.core/Attributes/ExcludeFromCoverage.cs index f7281ca04..71efcb7bc 100644 --- a/src/coverlet.core/Attributes/ExcludeFromCoverage.cs +++ b/src/coverlet.core/Attributes/ExcludeFromCoverage.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; namespace Coverlet.Core.Attributes diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index 30714c0dd..8a3def37b 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.Collections.Generic; using System.IO; @@ -6,7 +9,6 @@ using Coverlet.Core.Abstractions; using Coverlet.Core.Helpers; using Coverlet.Core.Instrumentation; - using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -41,24 +43,22 @@ internal class CoverageParameters public bool SkipAutoProps { get; set; } [DataMember] public bool DeterministicReport { get; set; } + [DataMember] + public string ExcludeAssembliesWithoutSources { get; set; } } internal class Coverage { - private string _moduleOrAppDirectory; - private string _identifier; - private ILogger _logger; - private IInstrumentationHelper _instrumentationHelper; - private IFileSystem _fileSystem; - private ISourceRootTranslator _sourceRootTranslator; - private ICecilSymbolHelper _cecilSymbolHelper; - private List _results; - private CoverageParameters _parameters; - - public string Identifier - { - get { return _identifier; } - } + private readonly string _moduleOrAppDirectory; + private readonly ILogger _logger; + private readonly IInstrumentationHelper _instrumentationHelper; + private readonly IFileSystem _fileSystem; + private readonly ISourceRootTranslator _sourceRootTranslator; + private readonly ICecilSymbolHelper _cecilSymbolHelper; + private readonly List _results; + private readonly CoverageParameters _parameters; + + public string Identifier { get; } public Coverage(string moduleOrDirectory, CoverageParameters parameters, @@ -76,7 +76,7 @@ public Coverage(string moduleOrDirectory, _fileSystem = fileSystem; _sourceRootTranslator = sourceRootTranslator; _cecilSymbolHelper = cecilSymbolHelper; - _identifier = Guid.NewGuid().ToString(); + Identifier = Guid.NewGuid().ToString(); _results = new List(); } @@ -86,7 +86,7 @@ public Coverage(CoveragePrepareResult prepareResult, IFileSystem fileSystem, ISourceRootTranslator sourceRootTranslator) { - _identifier = prepareResult.Identifier; + Identifier = prepareResult.Identifier; _moduleOrAppDirectory = prepareResult.ModuleOrDirectory; _parameters = prepareResult.Parameters; _results = new List(prepareResult.Results); @@ -107,7 +107,7 @@ public CoveragePrepareResult PrepareModules() _parameters.ExcludeFilters = _parameters.ExcludeFilters?.Where(f => _instrumentationHelper.IsValidFilterExpression(f)).ToArray(); _parameters.IncludeFilters = _parameters.IncludeFilters?.Where(f => _instrumentationHelper.IsValidFilterExpression(f)).ToArray(); - foreach (var module in modules) + foreach (string module in modules) { if (_instrumentationHelper.IsModuleExcluded(module, _parameters.ExcludeFilters) || !_instrumentationHelper.IsModuleIncluded(module, _parameters.IncludeFilters)) @@ -117,7 +117,7 @@ public CoveragePrepareResult PrepareModules() } var instrumenter = new Instrumenter(module, - _identifier, + Identifier, _parameters, _logger, _instrumentationHelper, @@ -127,7 +127,7 @@ public CoveragePrepareResult PrepareModules() if (instrumenter.CanInstrument()) { - _instrumentationHelper.BackupOriginalModule(module, _identifier); + _instrumentationHelper.BackupOriginalModule(module, Identifier); // Guard code path and restore if instrumentation fails. try @@ -142,14 +142,14 @@ public CoveragePrepareResult PrepareModules() catch (Exception ex) { _logger.LogWarning($"Unable to instrument module: {module}\n{ex}"); - _instrumentationHelper.RestoreOriginalModule(module, _identifier); + _instrumentationHelper.RestoreOriginalModule(module, Identifier); } } } return new CoveragePrepareResult() { - Identifier = _identifier, + Identifier = Identifier, ModuleOrDirectory = _moduleOrAppDirectory, Parameters = _parameters, Results = _results.ToArray() @@ -160,14 +160,14 @@ public CoverageResult GetCoverageResult() { CalculateCoverage(); - Modules modules = new Modules(); - foreach (var result in _results) + var modules = new Modules(); + foreach (InstrumenterResult result in _results) { - Documents documents = new Documents(); - foreach (var doc in result.Documents.Values) + var documents = new Documents(); + foreach (Document doc in result.Documents.Values) { // Construct Line Results - foreach (var line in doc.Lines.Values) + foreach (Line line in doc.Lines.Values) { if (documents.TryGetValue(doc.Path, out Classes classes)) { @@ -200,7 +200,7 @@ public CoverageResult GetCoverageResult() } // Construct Branch Results - foreach (var branch in doc.Branches.Values) + foreach (Branch branch in doc.Branches.Values) { if (documents.TryGetValue(doc.Path, out Classes classes)) { @@ -242,7 +242,7 @@ public CoverageResult GetCoverageResult() } modules.Add(Path.GetFileName(result.ModulePath), documents); - _instrumentationHelper.RestoreOriginalModule(result.ModulePath, _identifier); + _instrumentationHelper.RestoreOriginalModule(result.ModulePath, Identifier); } // In case of anonymous delegate compiler generate a custom class and passes it as type.method delegate. @@ -250,11 +250,11 @@ public CoverageResult GetCoverageResult() // We search "method" with same "Line" of closure class method and add missing branches to it, // in this way we correctly report missing branch inside compiled generated anonymous delegate. List compileGeneratedClassToRemove = null; - foreach (var module in modules) + foreach (KeyValuePair module in modules) { - foreach (var document in module.Value) + foreach (KeyValuePair document in module.Value) { - foreach (var @class in document.Value) + foreach (KeyValuePair @class in document.Value) { // We fix only lamda generated class // https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameKind.cs#L18 @@ -263,9 +263,9 @@ public CoverageResult GetCoverageResult() continue; } - foreach (var method in @class.Value) + foreach (KeyValuePair method in @class.Value) { - foreach (var branch in method.Value.Branches) + foreach (BranchInfo branch in method.Value.Branches) { if (BranchInCompilerGeneratedClass(method.Key)) { @@ -295,13 +295,13 @@ public CoverageResult GetCoverageResult() } // After method/branches analysis of compiled generated class we can remove noise from reports - if (!(compileGeneratedClassToRemove is null)) + if (compileGeneratedClassToRemove is not null) { - foreach (var module in modules) + foreach (KeyValuePair module in modules) { - foreach (var document in module.Value) + foreach (KeyValuePair document in module.Value) { - foreach (var classToRemove in compileGeneratedClassToRemove) + foreach (string classToRemove in compileGeneratedClassToRemove) { document.Value.Remove(classToRemove); } @@ -309,7 +309,7 @@ public CoverageResult GetCoverageResult() } } - var coverageResult = new CoverageResult { Identifier = _identifier, Modules = modules, InstrumentedResults = _results, Parameters = _parameters }; + var coverageResult = new CoverageResult { Identifier = Identifier, Modules = modules, InstrumentedResults = _results, Parameters = _parameters }; if (!string.IsNullOrEmpty(_parameters.MergeWith) && !string.IsNullOrWhiteSpace(_parameters.MergeWith) && _fileSystem.Exists(_parameters.MergeWith)) { @@ -322,7 +322,7 @@ public CoverageResult GetCoverageResult() private bool BranchInCompilerGeneratedClass(string methodName) { - foreach (var instrumentedResult in _results) + foreach (InstrumenterResult instrumentedResult in _results) { if (instrumentedResult.BranchesInCompiledGeneratedClass.Contains(methodName)) { @@ -332,18 +332,18 @@ private bool BranchInCompilerGeneratedClass(string methodName) return false; } - private Method GetMethodWithSameLineInSameDocument(Classes documentClasses, string compilerGeneratedClassName, int branchLine) + private static Method GetMethodWithSameLineInSameDocument(Classes documentClasses, string compilerGeneratedClassName, int branchLine) { - foreach (var @class in documentClasses) + foreach (KeyValuePair @class in documentClasses) { if (@class.Key == compilerGeneratedClassName) { continue; } - foreach (var method in @class.Value) + foreach (KeyValuePair method in @class.Value) { - foreach (var line in method.Value.Lines) + foreach (KeyValuePair line in method.Value.Lines) { if (line.Key == branchLine) { @@ -357,7 +357,7 @@ private Method GetMethodWithSameLineInSameDocument(Classes documentClasses, stri private void CalculateCoverage() { - foreach (var result in _results) + foreach (InstrumenterResult result in _results) { if (!_fileSystem.Exists(result.HitsFilePath)) { @@ -369,12 +369,12 @@ private void CalculateCoverage() continue; } - List documents = result.Documents.Values.ToList(); + var documents = result.Documents.Values.ToList(); if (_parameters.UseSourceLink && result.SourceLink != null) { - var jObject = JObject.Parse(result.SourceLink)["documents"]; - var sourceLinkDocuments = JsonConvert.DeserializeObject>(jObject.ToString()); - foreach (var document in documents) + JToken jObject = JObject.Parse(result.SourceLink)["documents"]; + Dictionary sourceLinkDocuments = JsonConvert.DeserializeObject>(jObject.ToString()); + foreach (Document document in documents) { document.Path = GetSourceLinkUrl(sourceLinkDocuments, document.Path); } @@ -408,7 +408,7 @@ private void CalculateCoverage() } var documentsList = result.Documents.Values.ToList(); - using (var fs = _fileSystem.NewFileStream(result.HitsFilePath, FileMode.Open, FileAccess.Read)) + using (Stream fs = _fileSystem.NewFileStream(result.HitsFilePath, FileMode.Open, FileAccess.Read)) using (var br = new BinaryReader(fs)) { int hitCandidatesCount = br.ReadInt32(); @@ -417,8 +417,8 @@ private void CalculateCoverage() for (int i = 0; i < hitCandidatesCount; ++i) { - var hitLocation = result.HitCandidates[i]; - var document = documentsList[hitLocation.docIndex]; + HitCandidate hitLocation = result.HitCandidates[i]; + Document document = documentsList[hitLocation.docIndex]; int hits = br.ReadInt32(); if (hits == 0) @@ -428,7 +428,7 @@ private void CalculateCoverage() if (hitLocation.isBranch) { - var branch = document.Branches[new BranchKey(hitLocation.start, hitLocation.end)]; + Branch branch = document.Branches[new BranchKey(hitLocation.start, hitLocation.end)]; branch.Hits += hits; if (branch.Hits < 0) @@ -443,7 +443,7 @@ private void CalculateCoverage() continue; } - var line = document.Lines[j]; + Line line = document.Lines[j]; line.Hits += hits; if (line.Hits < 0) @@ -472,10 +472,10 @@ private string GetSourceLinkUrl(Dictionary sourceLinkDocuments, return url; } - var keyWithBestMatch = string.Empty; - var relativePathOfBestMatch = string.Empty; + string keyWithBestMatch = string.Empty; + string relativePathOfBestMatch = string.Empty; - foreach (var sourceLinkDocument in sourceLinkDocuments) + foreach (KeyValuePair sourceLinkDocument in sourceLinkDocuments) { string key = sourceLinkDocument.Key; if (Path.GetFileName(key) != "*") continue; @@ -515,8 +515,12 @@ private string GetSourceLinkUrl(Dictionary sourceLinkDocuments, string replacement = Path.Combine(relativePathOfBestMatch, Path.GetFileName(document)); replacement = replacement.Replace('\\', '/'); - url = sourceLinkDocuments[keyWithBestMatch]; - return url.Replace("*", replacement); + if (sourceLinkDocuments.TryGetValue(keyWithBestMatch, out url)) + { + return url.Replace("*", replacement); + } + + return document; } } } diff --git a/src/coverlet.core/CoverageDetails.cs b/src/coverlet.core/CoverageDetails.cs index be2ccd4e2..59db863c2 100644 --- a/src/coverlet.core/CoverageDetails.cs +++ b/src/coverlet.core/CoverageDetails.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; namespace Coverlet.Core diff --git a/src/coverlet.core/CoveragePrepareResult.cs b/src/coverlet.core/CoveragePrepareResult.cs index 9bf9037f8..7c15d76c3 100644 --- a/src/coverlet.core/CoveragePrepareResult.cs +++ b/src/coverlet.core/CoveragePrepareResult.cs @@ -1,4 +1,7 @@ -using System.IO; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; using System.Runtime.Serialization; using Coverlet.Core.Instrumentation; @@ -30,7 +33,7 @@ public static CoveragePrepareResult Deserialize(Stream serializedInstrumentState public static Stream Serialize(CoveragePrepareResult instrumentState) { - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); new DataContractSerializer(typeof(CoveragePrepareResult)).WriteObject(ms, instrumentState); ms.Position = 0; return ms; diff --git a/src/coverlet.core/CoverageResult.cs b/src/coverlet.core/CoverageResult.cs index e02e5e0f5..4e981346b 100644 --- a/src/coverlet.core/CoverageResult.cs +++ b/src/coverlet.core/CoverageResult.cs @@ -1,10 +1,10 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System.Collections.Generic; -using System.IO; using System.Linq; using Coverlet.Core.Enums; using Coverlet.Core.Instrumentation; -using Coverlet.Core.Symbols; namespace Coverlet.Core { @@ -48,54 +48,54 @@ public CoverageResult() { } public void Merge(Modules modules) { - foreach (var module in modules) + foreach (KeyValuePair module in modules) { - if (!this.Modules.Keys.Contains(module.Key)) + if (!Modules.Keys.Contains(module.Key)) { - this.Modules.Add(module.Key, module.Value); + Modules.Add(module.Key, module.Value); } else { - foreach (var document in module.Value) + foreach (KeyValuePair document in module.Value) { - if (!this.Modules[module.Key].ContainsKey(document.Key)) + if (!Modules[module.Key].ContainsKey(document.Key)) { - this.Modules[module.Key].Add(document.Key, document.Value); + Modules[module.Key].Add(document.Key, document.Value); } else { - foreach (var @class in document.Value) + foreach (KeyValuePair @class in document.Value) { - if (!this.Modules[module.Key][document.Key].ContainsKey(@class.Key)) + if (!Modules[module.Key][document.Key].ContainsKey(@class.Key)) { - this.Modules[module.Key][document.Key].Add(@class.Key, @class.Value); + Modules[module.Key][document.Key].Add(@class.Key, @class.Value); } else { - foreach (var method in @class.Value) + foreach (KeyValuePair method in @class.Value) { - if (!this.Modules[module.Key][document.Key][@class.Key].ContainsKey(method.Key)) + if (!Modules[module.Key][document.Key][@class.Key].ContainsKey(method.Key)) { - this.Modules[module.Key][document.Key][@class.Key].Add(method.Key, method.Value); + Modules[module.Key][document.Key][@class.Key].Add(method.Key, method.Value); } else { - foreach (var line in method.Value.Lines) + foreach (KeyValuePair line in method.Value.Lines) { - if (!this.Modules[module.Key][document.Key][@class.Key][method.Key].Lines.ContainsKey(line.Key)) + if (!Modules[module.Key][document.Key][@class.Key][method.Key].Lines.ContainsKey(line.Key)) { - this.Modules[module.Key][document.Key][@class.Key][method.Key].Lines.Add(line.Key, line.Value); + Modules[module.Key][document.Key][@class.Key][method.Key].Lines.Add(line.Key, line.Value); } else { - this.Modules[module.Key][document.Key][@class.Key][method.Key].Lines[line.Key] += line.Value; + Modules[module.Key][document.Key][@class.Key][method.Key].Lines[line.Key] += line.Value; } } - foreach (var branch in method.Value.Branches) + foreach (BranchInfo branch in method.Value.Branches) { - var branches = this.Modules[module.Key][document.Key][@class.Key][method.Key].Branches; - var branchInfo = branches.FirstOrDefault(b => b.EndOffset == branch.EndOffset && b.Line == branch.Line && b.Offset == branch.Offset && b.Ordinal == branch.Ordinal && b.Path == branch.Path); + Branches branches = Modules[module.Key][document.Key][@class.Key][method.Key].Branches; + BranchInfo branchInfo = branches.FirstOrDefault(b => b.EndOffset == branch.EndOffset && b.Line == branch.Line && b.Offset == branch.Offset && b.Ordinal == branch.Ordinal && b.Path == branch.Path); if (branchInfo == null) branches.Add(branch); else @@ -113,7 +113,7 @@ public void Merge(Modules modules) public ThresholdTypeFlags GetThresholdTypesBelowThreshold(CoverageSummary summary, Dictionary thresholdTypeFlagValues, ThresholdStatistic thresholdStat) { - var thresholdTypeFlags = ThresholdTypeFlags.None; + ThresholdTypeFlags thresholdTypeFlags = ThresholdTypeFlags.None; switch (thresholdStat) { case ThresholdStatistic.Minimum: @@ -121,7 +121,7 @@ public ThresholdTypeFlags GetThresholdTypesBelowThreshold(CoverageSummary summar if (!Modules.Any()) thresholdTypeFlags = CompareThresholdValues(thresholdTypeFlagValues, thresholdTypeFlags, 0, 0, 0); - foreach (var module in Modules) + foreach (KeyValuePair module in Modules) { double line = summary.CalculateLineCoverage(module.Value).Percent; double branch = summary.CalculateBranchCoverage(module.Value).Percent; @@ -158,19 +158,19 @@ private static ThresholdTypeFlags CompareThresholdValues( Dictionary thresholdTypeFlagValues, ThresholdTypeFlags thresholdTypeFlags, double line, double branch, double method) { - if (thresholdTypeFlagValues.TryGetValue(ThresholdTypeFlags.Line, out var lineThresholdValue) && + if (thresholdTypeFlagValues.TryGetValue(ThresholdTypeFlags.Line, out double lineThresholdValue) && lineThresholdValue > line) { thresholdTypeFlags |= ThresholdTypeFlags.Line; } - if (thresholdTypeFlagValues.TryGetValue(ThresholdTypeFlags.Branch, out var branchThresholdValue) && + if (thresholdTypeFlagValues.TryGetValue(ThresholdTypeFlags.Branch, out double branchThresholdValue) && branchThresholdValue > branch) { thresholdTypeFlags |= ThresholdTypeFlags.Branch; } - if (thresholdTypeFlagValues.TryGetValue(ThresholdTypeFlags.Method, out var methodThresholdValue) && + if (thresholdTypeFlagValues.TryGetValue(ThresholdTypeFlags.Method, out double methodThresholdValue) && methodThresholdValue > method) { thresholdTypeFlags |= ThresholdTypeFlags.Method; @@ -179,4 +179,4 @@ private static ThresholdTypeFlags CompareThresholdValues( return thresholdTypeFlags; } } -} \ No newline at end of file +} diff --git a/src/coverlet.core/CoverageSummary.cs b/src/coverlet.core/CoverageSummary.cs index 19700deac..a56cda454 100644 --- a/src/coverlet.core/CoverageSummary.cs +++ b/src/coverlet.core/CoverageSummary.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.Collections.Generic; using System.Linq; @@ -17,9 +20,9 @@ public CoverageDetails CalculateLineCoverage(Lines lines) public CoverageDetails CalculateLineCoverage(Methods methods) { var details = new CoverageDetails(); - foreach (var method in methods) + foreach (KeyValuePair method in methods) { - var methodCoverage = CalculateLineCoverage(method.Value.Lines); + CoverageDetails methodCoverage = CalculateLineCoverage(method.Value.Lines); details.Covered += methodCoverage.Covered; details.Total += methodCoverage.Total; } @@ -29,9 +32,9 @@ public CoverageDetails CalculateLineCoverage(Methods methods) public CoverageDetails CalculateLineCoverage(Classes classes) { var details = new CoverageDetails(); - foreach (var @class in classes) + foreach (KeyValuePair @class in classes) { - var classCoverage = CalculateLineCoverage(@class.Value); + CoverageDetails classCoverage = CalculateLineCoverage(@class.Value); details.Covered += classCoverage.Covered; details.Total += classCoverage.Total; } @@ -41,9 +44,9 @@ public CoverageDetails CalculateLineCoverage(Classes classes) public CoverageDetails CalculateLineCoverage(Documents documents) { var details = new CoverageDetails(); - foreach (var document in documents) + foreach (KeyValuePair document in documents) { - var documentCoverage = CalculateLineCoverage(document.Value); + CoverageDetails documentCoverage = CalculateLineCoverage(document.Value); details.Covered += documentCoverage.Covered; details.Total += documentCoverage.Total; } @@ -52,15 +55,15 @@ public CoverageDetails CalculateLineCoverage(Documents documents) public CoverageDetails CalculateLineCoverage(Modules modules) { - var details = new CoverageDetails{Modules = modules}; - var accumPercent = 0.0D; + var details = new CoverageDetails { Modules = modules }; + double accumPercent = 0.0D; if (modules.Count == 0) return details; - foreach (var module in modules) + foreach (KeyValuePair module in modules) { - var moduleCoverage = CalculateLineCoverage(module.Value); + CoverageDetails moduleCoverage = CalculateLineCoverage(module.Value); details.Covered += moduleCoverage.Covered; details.Total += moduleCoverage.Total; accumPercent += moduleCoverage.Percent; @@ -86,7 +89,7 @@ public int CalculateNpathComplexity(IList branches) } var paths = new Dictionary(); - foreach (var branch in branches) + foreach (BranchInfo branch in branches) { if (!paths.TryGetValue(branch.Offset, out int count)) { @@ -96,7 +99,7 @@ public int CalculateNpathComplexity(IList branches) } int npath = 1; - foreach (var branchPoints in paths.Values) + foreach (int branchPoints in paths.Values) { try { @@ -154,9 +157,9 @@ public int CalculateCyclomaticComplexity(Documents documents) public CoverageDetails CalculateBranchCoverage(Methods methods) { var details = new CoverageDetails(); - foreach (var method in methods) + foreach (KeyValuePair method in methods) { - var methodCoverage = CalculateBranchCoverage(method.Value.Branches); + CoverageDetails methodCoverage = CalculateBranchCoverage(method.Value.Branches); details.Covered += methodCoverage.Covered; details.Total += methodCoverage.Total; } @@ -166,9 +169,9 @@ public CoverageDetails CalculateBranchCoverage(Methods methods) public CoverageDetails CalculateBranchCoverage(Classes classes) { var details = new CoverageDetails(); - foreach (var @class in classes) + foreach (KeyValuePair @class in classes) { - var classCoverage = CalculateBranchCoverage(@class.Value); + CoverageDetails classCoverage = CalculateBranchCoverage(@class.Value); details.Covered += classCoverage.Covered; details.Total += classCoverage.Total; } @@ -178,9 +181,9 @@ public CoverageDetails CalculateBranchCoverage(Classes classes) public CoverageDetails CalculateBranchCoverage(Documents documents) { var details = new CoverageDetails(); - foreach (var document in documents) + foreach (KeyValuePair document in documents) { - var documentCoverage = CalculateBranchCoverage(document.Value); + CoverageDetails documentCoverage = CalculateBranchCoverage(document.Value); details.Covered += documentCoverage.Covered; details.Total += documentCoverage.Total; } @@ -189,15 +192,15 @@ public CoverageDetails CalculateBranchCoverage(Documents documents) public CoverageDetails CalculateBranchCoverage(Modules modules) { - var details = new CoverageDetails{ Modules = modules }; - var accumPercent = 0.0D; + var details = new CoverageDetails { Modules = modules }; + double accumPercent = 0.0D; if (modules.Count == 0) return details; - foreach (var module in modules) + foreach (KeyValuePair module in modules) { - var moduleCoverage = CalculateBranchCoverage(module.Value); + CoverageDetails moduleCoverage = CalculateBranchCoverage(module.Value); details.Covered += moduleCoverage.Covered; details.Total += moduleCoverage.Total; accumPercent += moduleCoverage.Percent; @@ -217,10 +220,10 @@ public CoverageDetails CalculateMethodCoverage(Lines lines) public CoverageDetails CalculateMethodCoverage(Methods methods) { var details = new CoverageDetails(); - var methodsWithLines = methods.Where(m => m.Value.Lines.Count > 0); - foreach (var method in methodsWithLines) + IEnumerable> methodsWithLines = methods.Where(m => m.Value.Lines.Count > 0); + foreach (KeyValuePair method in methodsWithLines) { - var methodCoverage = CalculateMethodCoverage(method.Value.Lines); + CoverageDetails methodCoverage = CalculateMethodCoverage(method.Value.Lines); details.Covered += methodCoverage.Covered; } details.Total = methodsWithLines.Count(); @@ -230,9 +233,9 @@ public CoverageDetails CalculateMethodCoverage(Methods methods) public CoverageDetails CalculateMethodCoverage(Classes classes) { var details = new CoverageDetails(); - foreach (var @class in classes) + foreach (KeyValuePair @class in classes) { - var classCoverage = CalculateMethodCoverage(@class.Value); + CoverageDetails classCoverage = CalculateMethodCoverage(@class.Value); details.Covered += classCoverage.Covered; details.Total += classCoverage.Total; } @@ -242,9 +245,9 @@ public CoverageDetails CalculateMethodCoverage(Classes classes) public CoverageDetails CalculateMethodCoverage(Documents documents) { var details = new CoverageDetails(); - foreach (var document in documents) + foreach (KeyValuePair document in documents) { - var documentCoverage = CalculateMethodCoverage(document.Value); + CoverageDetails documentCoverage = CalculateMethodCoverage(document.Value); details.Covered += documentCoverage.Covered; details.Total += documentCoverage.Total; } @@ -253,15 +256,15 @@ public CoverageDetails CalculateMethodCoverage(Documents documents) public CoverageDetails CalculateMethodCoverage(Modules modules) { - var details = new CoverageDetails{ Modules = modules }; - var accumPercent = 0.0D; + var details = new CoverageDetails { Modules = modules }; + double accumPercent = 0.0D; if (modules.Count == 0) return details; - foreach (var module in modules) + foreach (KeyValuePair module in modules) { - var moduleCoverage = CalculateMethodCoverage(module.Value); + CoverageDetails moduleCoverage = CalculateMethodCoverage(module.Value); details.Covered += moduleCoverage.Covered; details.Total += moduleCoverage.Total; accumPercent += moduleCoverage.Percent; @@ -270,4 +273,4 @@ public CoverageDetails CalculateMethodCoverage(Modules modules) return details; } } -} \ No newline at end of file +} diff --git a/src/coverlet.core/Enums/AssemblySearchType.cs b/src/coverlet.core/Enums/AssemblySearchType.cs new file mode 100644 index 000000000..099e54217 --- /dev/null +++ b/src/coverlet.core/Enums/AssemblySearchType.cs @@ -0,0 +1,12 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Coverlet.Core.Enums +{ + internal enum AssemblySearchType + { + MissingAny, + MissingAll, + None + } +} diff --git a/src/coverlet.core/Enums/ThresholdStatistic.cs b/src/coverlet.core/Enums/ThresholdStatistic.cs index 9b7dd18ba..1dbb55dc0 100644 --- a/src/coverlet.core/Enums/ThresholdStatistic.cs +++ b/src/coverlet.core/Enums/ThresholdStatistic.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + namespace Coverlet.Core.Enums { internal enum ThresholdStatistic diff --git a/src/coverlet.core/Enums/ThresholdTypeFlags.cs b/src/coverlet.core/Enums/ThresholdTypeFlags.cs index 11a082178..9b24222a5 100644 --- a/src/coverlet.core/Enums/ThresholdTypeFlags.cs +++ b/src/coverlet.core/Enums/ThresholdTypeFlags.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; namespace Coverlet.Core.Enums diff --git a/src/coverlet.core/Exceptions.cs b/src/coverlet.core/Exceptions.cs index 81a6809aa..4eefd76ca 100644 --- a/src/coverlet.core/Exceptions.cs +++ b/src/coverlet.core/Exceptions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; namespace Coverlet.Core.Exceptions { diff --git a/src/coverlet.core/Extensions/HelperExtensions.cs b/src/coverlet.core/Extensions/HelperExtensions.cs index fe8f45cae..30439c0c9 100644 --- a/src/coverlet.core/Extensions/HelperExtensions.cs +++ b/src/coverlet.core/Extensions/HelperExtensions.cs @@ -1,3 +1,5 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using Coverlet.Core.Attributes; @@ -7,10 +9,10 @@ namespace Coverlet.Core.Extensions internal static class HelperExtensions { [ExcludeFromCoverage] - public static TRet Maybe(this T value, Func action, TRet defValue = default(TRet)) + public static TRet Maybe(this T value, Func action, TRet defValue = default) where T : class { return (value != null) ? action(value) : defValue; } } -} \ No newline at end of file +} diff --git a/src/coverlet.core/Helpers/AssemblyAdapter.cs b/src/coverlet.core/Helpers/AssemblyAdapter.cs new file mode 100644 index 000000000..f4626d2ff --- /dev/null +++ b/src/coverlet.core/Helpers/AssemblyAdapter.cs @@ -0,0 +1,16 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Reflection; +using Coverlet.Core.Abstractions; + +namespace Coverlet.Core.Helpers +{ + internal class AssemblyAdapter : IAssemblyAdapter + { + public string GetAssemblyName(string assemblyPath) + { + return AssemblyName.GetAssemblyName(assemblyPath).Name; + } + } +} diff --git a/src/coverlet.core/Helpers/Console.cs b/src/coverlet.core/Helpers/Console.cs index eacddd323..8781b49de 100644 --- a/src/coverlet.core/Helpers/Console.cs +++ b/src/coverlet.core/Helpers/Console.cs @@ -1,5 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using Coverlet.Core.Abstractions; namespace Coverlet.Core.Helpers diff --git a/src/coverlet.core/Helpers/FileSystem.cs b/src/coverlet.core/Helpers/FileSystem.cs index 7213d3b4e..10dfc8f0a 100644 --- a/src/coverlet.core/Helpers/FileSystem.cs +++ b/src/coverlet.core/Helpers/FileSystem.cs @@ -1,5 +1,8 @@ -using Coverlet.Core.Abstractions; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System.IO; +using Coverlet.Core.Abstractions; namespace Coverlet.Core.Helpers { diff --git a/src/coverlet.core/Helpers/InstrumentationHelper.cs b/src/coverlet.core/Helpers/InstrumentationHelper.cs index 8a6827621..6723fc733 100644 --- a/src/coverlet.core/Helpers/InstrumentationHelper.cs +++ b/src/coverlet.core/Helpers/InstrumentationHelper.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -8,15 +11,15 @@ using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Text.RegularExpressions; - using Coverlet.Core.Abstractions; +using Coverlet.Core.Enums; namespace Coverlet.Core.Helpers { internal class InstrumentationHelper : IInstrumentationHelper { private const int RetryAttempts = 12; - private readonly ConcurrentDictionary _backupList = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _backupList = new(); private readonly IRetryHelper _retryHelper; private readonly IFileSystem _fileSystem; private readonly ISourceRootTranslator _sourceRootTranslator; @@ -81,51 +84,59 @@ public string[] GetCoverableModules(string moduleOrAppDirectory, string[] direct public bool HasPdb(string module, out bool embedded) { embedded = false; - using (var moduleStream = _fileSystem.OpenRead(module)) - using (var peReader = new PEReader(moduleStream)) + using Stream moduleStream = _fileSystem.OpenRead(module); + using var peReader = new PEReader(moduleStream); + foreach (DebugDirectoryEntry entry in peReader.ReadDebugDirectory()) { - foreach (var entry in peReader.ReadDebugDirectory()) + if (entry.Type == DebugDirectoryEntryType.CodeView) { - if (entry.Type == DebugDirectoryEntryType.CodeView) + CodeViewDebugDirectoryData codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry); + string modulePdbFileName = $"{Path.GetFileNameWithoutExtension(module)}.pdb"; + if (_sourceRootTranslator.ResolveFilePath(codeViewData.Path) == modulePdbFileName) { - var codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry); - if (_sourceRootTranslator.ResolveFilePath(codeViewData.Path) == $"{Path.GetFileNameWithoutExtension(module)}.pdb") - { - // PDB is embedded - embedded = true; - return true; - } - - return _fileSystem.Exists(_sourceRootTranslator.ResolveFilePath(codeViewData.Path)); + // PDB is embedded + embedded = true; + return true; } - } - return false; + if (_fileSystem.Exists(_sourceRootTranslator.ResolveFilePath(codeViewData.Path))) + { + // local PDB is located within original build location + embedded = false; + return true; + } + + string localPdbFileName = Path.Combine(Path.GetDirectoryName(module), modulePdbFileName); + if (_fileSystem.Exists(localPdbFileName)) + { + // local PDB is located within same folder as module + embedded = false; + + // mapping need to be registered in _sourceRootTranslator to use that discovery + _sourceRootTranslator.AddMappingInCache(codeViewData.Path, localPdbFileName); + + return true; + } + } } + + return false; } - public bool EmbeddedPortablePdbHasLocalSource(string module, out string firstNotFoundDocument) + public bool EmbeddedPortablePdbHasLocalSource(string module, AssemblySearchType excludeAssembliesWithoutSources) { - firstNotFoundDocument = ""; - using (Stream moduleStream = _fileSystem.OpenRead(module)) - using (var peReader = new PEReader(moduleStream)) + using Stream moduleStream = _fileSystem.OpenRead(module); + using var peReader = new PEReader(moduleStream); + foreach (DebugDirectoryEntry entry in peReader.ReadDebugDirectory()) { - foreach (DebugDirectoryEntry entry in peReader.ReadDebugDirectory()) + if (entry.Type == DebugDirectoryEntryType.EmbeddedPortablePdb) { - if (entry.Type == DebugDirectoryEntryType.EmbeddedPortablePdb) + using MetadataReaderProvider embeddedMetadataProvider = peReader.ReadEmbeddedPortablePdbDebugDirectoryData(entry); + MetadataReader metadataReader = embeddedMetadataProvider.GetMetadataReader(); + + if (!MatchDocumentsWithSources(module, excludeAssembliesWithoutSources, metadataReader)) { - using (MetadataReaderProvider embeddedMetadataProvider = peReader.ReadEmbeddedPortablePdbDebugDirectoryData(entry)) - { - MetadataReader metadataReader = embeddedMetadataProvider.GetMetadataReader(); - - var matchingResult = MatchDocumentsWithSources(metadataReader); - - if (!matchingResult.allDocumentsMatch) - { - firstNotFoundDocument = matchingResult.notFoundDocument; - return false; - } - } + return false; } } } @@ -135,37 +146,31 @@ public bool EmbeddedPortablePdbHasLocalSource(string module, out string firstNot return true; } - public bool PortablePdbHasLocalSource(string module, out string firstNotFoundDocument) + public bool PortablePdbHasLocalSource(string module, AssemblySearchType excludeAssembliesWithoutSources) { - firstNotFoundDocument = ""; - using (var moduleStream = _fileSystem.OpenRead(module)) - using (var peReader = new PEReader(moduleStream)) + using Stream moduleStream = _fileSystem.OpenRead(module); + using var peReader = new PEReader(moduleStream); + foreach (DebugDirectoryEntry entry in peReader.ReadDebugDirectory()) { - foreach (var entry in peReader.ReadDebugDirectory()) + if (entry.Type == DebugDirectoryEntryType.CodeView) { - if (entry.Type == DebugDirectoryEntryType.CodeView) + CodeViewDebugDirectoryData codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry); + using Stream pdbStream = _fileSystem.OpenRead(_sourceRootTranslator.ResolveFilePath(codeViewData.Path)); + using var metadataReaderProvider = MetadataReaderProvider.FromPortablePdbStream(pdbStream); + MetadataReader metadataReader = null; + try + { + metadataReader = metadataReaderProvider.GetMetadataReader(); + } + catch (BadImageFormatException) + { + _logger.LogWarning($"{nameof(BadImageFormatException)} during MetadataReaderProvider.FromPortablePdbStream in InstrumentationHelper.PortablePdbHasLocalSource, unable to check if module has got local source."); + return true; + } + + if (!MatchDocumentsWithSources(module, excludeAssembliesWithoutSources, metadataReader)) { - var codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry); - using Stream pdbStream = _fileSystem.OpenRead(_sourceRootTranslator.ResolveFilePath(codeViewData.Path)); - using MetadataReaderProvider metadataReaderProvider = MetadataReaderProvider.FromPortablePdbStream(pdbStream); - MetadataReader metadataReader = null; - try - { - metadataReader = metadataReaderProvider.GetMetadataReader(); - } - catch (BadImageFormatException) - { - _logger.LogWarning($"{nameof(BadImageFormatException)} during MetadataReaderProvider.FromPortablePdbStream in InstrumentationHelper.PortablePdbHasLocalSource, unable to check if module has got local source."); - return true; - } - - var matchingResult = MatchDocumentsWithSources(metadataReader); - - if (!matchingResult.allDocumentsMatch) - { - firstNotFoundDocument = matchingResult.notFoundDocument; - return false; - } + return false; } } } @@ -173,38 +178,71 @@ public bool PortablePdbHasLocalSource(string module, out string firstNotFoundDoc return true; } - private (bool allDocumentsMatch, string notFoundDocument) MatchDocumentsWithSources(MetadataReader metadataReader) + private bool MatchDocumentsWithSources(string module, AssemblySearchType excludeAssembliesWithoutSources, + MetadataReader metadataReader) { - foreach (DocumentHandle docHandle in metadataReader.Documents) + if (excludeAssembliesWithoutSources.Equals(AssemblySearchType.MissingAll)) { - Document document = metadataReader.GetDocument(docHandle); - string docName = _sourceRootTranslator.ResolveFilePath(metadataReader.GetString(document.Name)); - Guid languageGuid = metadataReader.GetGuid(document.Language); - // We verify all docs and return false if not all are present in local - // We could have false negative if doc is not a source - // Btw check for all possible extension could be weak approach - // We exlude from the check the autogenerated source file(i.e. source generators) - // We exclude special F# construct https://github.com/coverlet-coverage/coverlet/issues/1145 - if (!_fileSystem.Exists(docName) && !docName.EndsWith(".g.cs") && - !IsUnknownModuleInFSharpAssembly(languageGuid, docName)) + bool anyDocumentMatches = MatchDocumentsWithSourcesMissingAll(metadataReader); + if (!anyDocumentMatches) { - return (false, docName); + _logger.LogVerbose($"Excluding module from instrumentation: {module}, pdb without any local source files"); + return false; } } + + if (excludeAssembliesWithoutSources.Equals(AssemblySearchType.MissingAny)) + { + (bool allDocumentsMatch, string notFoundDocument) = MatchDocumentsWithSourcesMissingAny(metadataReader); + + if (!allDocumentsMatch) + { + _logger.LogVerbose( + $"Excluding module from instrumentation: {module}, pdb without local source files, [{FileSystem.EscapeFileName(notFoundDocument)}]"); + return false; + } + } + + return true; + } + + private IEnumerable<(string documentName, bool documentExists)> DocumentSourceMap(MetadataReader metadataReader) + { + return metadataReader.Documents.Select(docHandle => + { + Document document = metadataReader.GetDocument(docHandle); + string docName = _sourceRootTranslator.ResolveFilePath(metadataReader.GetString(document.Name)); + return (docName, _fileSystem.Exists(docName)); + }); + } + + private bool MatchDocumentsWithSourcesMissingAll(MetadataReader metadataReader) + { + return DocumentSourceMap(metadataReader).Any(x => x.documentExists); + } + + private (bool allDocumentsMatch, string notFoundDocument) MatchDocumentsWithSourcesMissingAny( + MetadataReader metadataReader) + { + var documentSourceMap = DocumentSourceMap(metadataReader).ToList(); + + if (documentSourceMap.Any(x => !x.documentExists)) + return (false, documentSourceMap.FirstOrDefault(x => !x.documentExists).documentName); + return (true, string.Empty); } public void BackupOriginalModule(string module, string identifier) { - var backupPath = GetBackupPath(module, identifier); - var backupSymbolPath = Path.ChangeExtension(backupPath, ".pdb"); + string backupPath = GetBackupPath(module, identifier); + string backupSymbolPath = Path.ChangeExtension(backupPath, ".pdb"); _fileSystem.Copy(module, backupPath, true); if (!_backupList.TryAdd(module, backupPath)) { throw new ArgumentException($"Key already added '{module}'"); } - var symbolFile = Path.ChangeExtension(module, ".pdb"); + string symbolFile = Path.ChangeExtension(module, ".pdb"); if (_fileSystem.Exists(symbolFile)) { _fileSystem.Copy(symbolFile, backupSymbolPath, true); @@ -217,12 +255,12 @@ public void BackupOriginalModule(string module, string identifier) public virtual void RestoreOriginalModule(string module, string identifier) { - var backupPath = GetBackupPath(module, identifier); - var backupSymbolPath = Path.ChangeExtension(backupPath, ".pdb"); + string backupPath = GetBackupPath(module, identifier); + string backupSymbolPath = Path.ChangeExtension(backupPath, ".pdb"); // Restore the original module - retry up to 10 times, since the destination file could be locked // See: https://github.com/tonerdo/coverlet/issues/25 - var retryStrategy = CreateRetryStrategy(); + Func retryStrategy = CreateRetryStrategy(); _retryHelper.Retry(() => { @@ -247,7 +285,7 @@ public virtual void RestoreOriginalModules() { // Restore the original module - retry up to 10 times, since the destination file could be locked // See: https://github.com/tonerdo/coverlet/issues/25 - var retryStrategy = CreateRetryStrategy(); + Func retryStrategy = CreateRetryStrategy(); foreach (string key in _backupList.Keys.ToList()) { @@ -263,7 +301,7 @@ public virtual void RestoreOriginalModules() public void DeleteHitsFile(string path) { - var retryStrategy = CreateRetryStrategy(); + Func retryStrategy = CreateRetryStrategy(); _retryHelper.Retry(() => _fileSystem.Delete(path), retryStrategy, RetryAttempts); } @@ -308,7 +346,7 @@ public bool IsModuleExcluded(string module, string[] excludeFilters) if (module == null) return false; - foreach (var filter in excludeFilters) + foreach (string filter in excludeFilters) { string typePattern = filter.Substring(filter.IndexOf(']') + 1); @@ -336,7 +374,7 @@ public bool IsModuleIncluded(string module, string[] includeFilters) if (module == null) return false; - foreach (var filter in includeFilters) + foreach (string filter in includeFilters) { string modulePattern = filter.Substring(1, filter.IndexOf(']') - 1); @@ -386,12 +424,12 @@ public void SetLogger(ILogger logger) _logger = logger; } - private bool IsTypeFilterMatch(string module, string type, string[] filters) + private static bool IsTypeFilterMatch(string module, string type, string[] filters) { Debug.Assert(module != null); Debug.Assert(filters != null); - foreach (var filter in filters) + foreach (string filter in filters) { string typePattern = filter.Substring(filter.IndexOf(']') + 1); string modulePattern = filter.Substring(1, filter.IndexOf(']') - 1); @@ -406,7 +444,7 @@ private bool IsTypeFilterMatch(string module, string type, string[] filters) return false; } - private string GetBackupPath(string module, string identifier) + private static string GetBackupPath(string module, string identifier) { return Path.Combine( Path.GetTempPath(), @@ -426,14 +464,14 @@ TimeSpan retryStrategy() return retryStrategy; } - private string WildcardToRegex(string pattern) + private static string WildcardToRegex(string pattern) { return "^" + Regex.Escape(pattern). Replace("\\*", ".*"). Replace("\\?", "?") + "$"; } - private bool IsAssembly(string filePath) + private static bool IsAssembly(string filePath) { Debug.Assert(filePath != null); @@ -450,12 +488,5 @@ private bool IsAssembly(string filePath) return false; } } - - private bool IsUnknownModuleInFSharpAssembly(Guid languageGuid, string docName) - { - // https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md#document-table-0x30 - return languageGuid.Equals(new Guid("ab4f38c9-b6e6-43ba-be3b-58080b2ccce3")) - && docName.EndsWith("unknown"); - } } -} \ No newline at end of file +} diff --git a/src/coverlet.core/Helpers/ProcessExitHandler.cs b/src/coverlet.core/Helpers/ProcessExitHandler.cs index 7083e27ae..1e570f07f 100644 --- a/src/coverlet.core/Helpers/ProcessExitHandler.cs +++ b/src/coverlet.core/Helpers/ProcessExitHandler.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using Coverlet.Core.Abstractions; namespace Coverlet.Core.Helpers diff --git a/src/coverlet.core/Helpers/RetryHelper.cs b/src/coverlet.core/Helpers/RetryHelper.cs index 652cb1cfb..dcf3fa9d0 100644 --- a/src/coverlet.core/Helpers/RetryHelper.cs +++ b/src/coverlet.core/Helpers/RetryHelper.cs @@ -1,7 +1,10 @@ -using Coverlet.Core.Abstractions; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.Collections.Generic; using System.Threading; +using Coverlet.Core.Abstractions; namespace Coverlet.Core.Helpers { @@ -59,4 +62,4 @@ public T Do( throw new AggregateException(exceptions); } } -} \ No newline at end of file +} diff --git a/src/coverlet.core/Helpers/SourceRootTranslator.cs b/src/coverlet.core/Helpers/SourceRootTranslator.cs index b75901ada..7fea89516 100644 --- a/src/coverlet.core/Helpers/SourceRootTranslator.cs +++ b/src/coverlet.core/Helpers/SourceRootTranslator.cs @@ -1,8 +1,11 @@ -using Coverlet.Core.Abstractions; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using Coverlet.Core.Abstractions; namespace Coverlet.Core.Helpers { @@ -19,7 +22,7 @@ internal class SourceRootTranslator : ISourceRootTranslator private readonly IFileSystem _fileSystem; private readonly Dictionary> _sourceRootMapping; private readonly Dictionary> _sourceToDeterministicPathMapping; - private const string MappingFileName = "CoverletSourceRootsMapping"; + private readonly string _mappingFileName; private Dictionary _resolutionCacheFiles; public SourceRootTranslator(ILogger logger, IFileSystem fileSystem) @@ -29,7 +32,7 @@ public SourceRootTranslator(ILogger logger, IFileSystem fileSystem) _sourceRootMapping = new Dictionary>(); } - public SourceRootTranslator(string moduleTestPath, ILogger logger, IFileSystem fileSystem) + public SourceRootTranslator(string moduleTestPath, ILogger logger, IFileSystem fileSystem, IAssemblyAdapter assemblyAdapter) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); @@ -41,18 +44,22 @@ public SourceRootTranslator(string moduleTestPath, ILogger logger, IFileSystem f { throw new FileNotFoundException($"Module test path '{moduleTestPath}' not found", moduleTestPath); } + + string assemblyName = assemblyAdapter.GetAssemblyName(moduleTestPath); + _mappingFileName = $"CoverletSourceRootsMapping_{assemblyName}"; + _sourceRootMapping = LoadSourceRootMapping(Path.GetDirectoryName(moduleTestPath)); _sourceToDeterministicPathMapping = LoadSourceToDeterministicPathMapping(_sourceRootMapping); } - private Dictionary> LoadSourceToDeterministicPathMapping(Dictionary> sourceRootMapping) + private static Dictionary> LoadSourceToDeterministicPathMapping(Dictionary> sourceRootMapping) { if (sourceRootMapping is null) { throw new ArgumentNullException(nameof(sourceRootMapping)); } - Dictionary> sourceToDeterministicPathMapping = new Dictionary>(); + var sourceToDeterministicPathMapping = new Dictionary>(); foreach (KeyValuePair> sourceRootMappingEntry in sourceRootMapping) { foreach (SourceRootMapping originalPath in sourceRootMappingEntry.Value) @@ -70,9 +77,9 @@ private Dictionary> LoadSourceToDeterministicPathMapping(Di private Dictionary> LoadSourceRootMapping(string directory) { - Dictionary> mapping = new Dictionary>(); + var mapping = new Dictionary>(); - string mappingFilePath = Path.Combine(directory, MappingFileName); + string mappingFilePath = Path.Combine(directory, _mappingFileName); if (!_fileSystem.Exists(mappingFilePath)) { return mapping; @@ -105,6 +112,17 @@ private Dictionary> LoadSourceRootMapping(string return mapping; } + public bool AddMappingInCache(string originalFileName, string targetFileName) + { + if (_resolutionCacheFiles != null && _resolutionCacheFiles.ContainsKey(originalFileName)) + { + return false; + } + + (_resolutionCacheFiles ??= new Dictionary()).Add(originalFileName, targetFileName); + return true; + } + public IReadOnlyList ResolvePathRoot(string pathRoot) { return _sourceRootMapping.TryGetValue(pathRoot, out List sourceRootMapping) ? sourceRootMapping.AsReadOnly() : null; @@ -138,7 +156,7 @@ public string ResolveFilePath(string originalFileName) public string ResolveDeterministicPath(string originalFileName) { - foreach (var originalPath in _sourceToDeterministicPathMapping) + foreach (KeyValuePair> originalPath in _sourceToDeterministicPathMapping) { if (originalFileName.StartsWith(originalPath.Key)) { diff --git a/src/coverlet.core/Instrumentation/CecilAssemblyResolver.cs b/src/coverlet.core/Instrumentation/CecilAssemblyResolver.cs index 2624475f3..6a781485d 100644 --- a/src/coverlet.core/Instrumentation/CecilAssemblyResolver.cs +++ b/src/coverlet.core/Instrumentation/CecilAssemblyResolver.cs @@ -1,8 +1,10 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using System.Collections.Generic; using System.IO; using System.Linq; - using Coverlet.Core.Abstractions; using Coverlet.Core.Exceptions; using Microsoft.Extensions.DependencyModel; @@ -15,21 +17,21 @@ namespace Coverlet.Core.Instrumentation /// In case of testing different runtime i.e. netfx we could find netstandard.dll in folder. /// netstandard.dll is a forward only lib, there is no IL but only forwards to "runtime" implementation. /// For some classes implementation are in different assembly for different runtime for instance: - /// + /// /// For NetFx 4.7 /// // Token: 0x2700072C RID: 1836 /// .class extern forwarder System.Security.Cryptography.X509Certificates.StoreName /// { /// .assembly extern System - /// } - /// + /// } + /// /// For netcoreapp2.2 /// Token: 0x2700072C RID: 1836 /// .class extern forwarder System.Security.Cryptography.X509Certificates.StoreName /// { /// .assembly extern System.Security.Cryptography.X509Certificates /// } - /// + /// /// There is a concrete possibility that Cecil cannot find implementation and throws StackOverflow exception https://github.com/jbevain/cecil/issues/575 /// This custom resolver check if requested lib is a "official" netstandard.dll and load once of "current runtime" with /// correct forwards. @@ -37,10 +39,10 @@ namespace Coverlet.Core.Instrumentation /// internal class NetstandardAwareAssemblyResolver : DefaultAssemblyResolver { - private static readonly System.Reflection.Assembly _netStandardAssembly; - private static readonly string _name; - private static readonly byte[] _publicKeyToken; - private static readonly AssemblyDefinition _assemblyDefinition; + private static readonly System.Reflection.Assembly s_netStandardAssembly; + private static readonly string s_name; + private static readonly byte[] s_publicKeyToken; + private static readonly AssemblyDefinition s_assemblyDefinition; private readonly string _modulePath; private readonly Lazy _compositeResolver; @@ -51,11 +53,11 @@ static NetstandardAwareAssemblyResolver() try { // To be sure to load information of "real" runtime netstandard implementation - _netStandardAssembly = System.Reflection.Assembly.LoadFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), "netstandard.dll")); - System.Reflection.AssemblyName name = _netStandardAssembly.GetName(); - _name = name.Name; - _publicKeyToken = name.GetPublicKeyToken(); - _assemblyDefinition = AssemblyDefinition.ReadAssembly(_netStandardAssembly.Location); + s_netStandardAssembly = System.Reflection.Assembly.LoadFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), "netstandard.dll")); + System.Reflection.AssemblyName name = s_netStandardAssembly.GetName(); + s_name = name.Name; + s_publicKeyToken = name.GetPublicKeyToken(); + s_assemblyDefinition = AssemblyDefinition.ReadAssembly(s_netStandardAssembly.Location); } catch (FileNotFoundException) { @@ -68,7 +70,7 @@ public NetstandardAwareAssemblyResolver(string modulePath, ILogger logger) _modulePath = modulePath; _logger = logger; - // this is lazy because we cannot create AspNetCoreSharedFrameworkResolver if not on .NET Core runtime, + // this is lazy because we cannot create AspNetCoreSharedFrameworkResolver if not on .NET Core runtime, // runtime folders are different _compositeResolver = new Lazy(() => new CompositeCompilationAssemblyResolver(new ICompilationAssemblyResolver[] { @@ -80,26 +82,26 @@ public NetstandardAwareAssemblyResolver(string modulePath, ILogger logger) } // Check name and public key but not version that could be different - private bool CheckIfSearchingNetstandard(AssemblyNameReference name) + private static bool CheckIfSearchingNetstandard(AssemblyNameReference name) { - if (_netStandardAssembly is null) + if (s_netStandardAssembly is null) { return false; } - if (_name != name.Name) + if (s_name != name.Name) { return false; } - if (name.PublicKeyToken.Length != _publicKeyToken.Length) + if (name.PublicKeyToken.Length != s_publicKeyToken.Length) { return false; } for (int i = 0; i < name.PublicKeyToken.Length; i++) { - if (_publicKeyToken[i] != name.PublicKeyToken[i]) + if (s_publicKeyToken[i] != name.PublicKeyToken[i]) { return false; } @@ -112,7 +114,7 @@ public override AssemblyDefinition Resolve(AssemblyNameReference name) { if (CheckIfSearchingNetstandard(name)) { - return _assemblyDefinition; + return s_assemblyDefinition; } else { @@ -134,21 +136,21 @@ public override AssemblyDefinition Resolve(AssemblyNameReference name) } } - private bool IsDotNetCore() + private static bool IsDotNetCore() { // object for .NET Framework is inside mscorlib.dll return Path.GetFileName(typeof(object).Assembly.Location) == "System.Private.CoreLib.dll"; } /// - /// + /// /// We try to manually load assembly. /// To work test project needs to use /// /// /// true /// - /// + /// /// Runtime configuration file doc https://github.com/dotnet/cli/blob/master/Documentation/specs/runtime-configuration-file.md /// /// @@ -167,8 +169,8 @@ internal AssemblyDefinition TryWithCustomResolverOnDotNetCore(AssemblyNameRefere throw new AssemblyResolutionException(name); } - using DependencyContextJsonReader contextJsonReader = new DependencyContextJsonReader(); - Dictionary> libraries = new Dictionary>(); + using var contextJsonReader = new DependencyContextJsonReader(); + var libraries = new Dictionary>(); foreach (string fileName in Directory.GetFiles(Path.GetDirectoryName(_modulePath), "*.deps.json")) { @@ -200,7 +202,7 @@ internal AssemblyDefinition TryWithCustomResolverOnDotNetCore(AssemblyNameRefere catch (Exception ex) { // if we don't find a lib go on - _logger.LogVerbose($"TryWithCustomResolverOnDotNetCore exception: {ex.ToString()}"); + _logger.LogVerbose($"TryWithCustomResolverOnDotNetCore exception: {ex}"); } } } @@ -216,8 +218,8 @@ internal AssemblyDefinition TryWithCustomResolverOnDotNetCore(AssemblyNameRefere internal class AspNetCoreSharedFrameworkResolver : ICompilationAssemblyResolver { - private readonly string[] _aspNetSharedFrameworkDirs = null; - private readonly ILogger _logger = null; + private readonly string[] _aspNetSharedFrameworkDirs; + private readonly ILogger _logger; public AspNetCoreSharedFrameworkResolver(ILogger logger) { @@ -248,7 +250,7 @@ public bool TryResolveAssemblyPaths(CompilationLibrary library, List ass continue; } - foreach (var file in Directory.GetFiles(sharedFrameworkPath)) + foreach (string file in Directory.GetFiles(sharedFrameworkPath)) { if (Path.GetFileName(file).Equals(dllName, StringComparison.OrdinalIgnoreCase)) { diff --git a/src/coverlet.core/Instrumentation/Instrumenter.cs b/src/coverlet.core/Instrumentation/Instrumenter.cs index cf80e90a3..df43496c9 100644 --- a/src/coverlet.core/Instrumentation/Instrumenter.cs +++ b/src/coverlet.core/Instrumentation/Instrumenter.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.Collections.Generic; using System.Diagnostics; @@ -5,11 +8,11 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; - -using Coverlet.Core.Instrumentation.Reachability; using Coverlet.Core.Abstractions; using Coverlet.Core.Attributes; +using Coverlet.Core.Enums; using Coverlet.Core.Helpers; +using Coverlet.Core.Instrumentation.Reachability; using Coverlet.Core.Symbols; using Microsoft.Extensions.FileSystemGlobbing; using Mono.Cecil; @@ -31,6 +34,8 @@ internal class Instrumenter private readonly IFileSystem _fileSystem; private readonly ISourceRootTranslator _sourceRootTranslator; private readonly ICecilSymbolHelper _cecilSymbolHelper; + private readonly string[] _doesNotReturnAttributes; + private readonly AssemblySearchType _excludeAssembliesWithoutSources; private InstrumenterResult _result; private FieldDefinition _customTrackerHitsArray; private FieldDefinition _customTrackerHitsFilePath; @@ -43,11 +48,11 @@ internal class Instrumenter private List _excludedSourceFiles; private List _branchesInCompiledGeneratedClass; private List<(MethodDefinition, int)> _excludedMethods; + private List _excludedLambdaMethods; private List _excludedCompilerGeneratedTypes; - private readonly string[] _doesNotReturnAttributes; private ReachabilityHelper _reachabilityHelper; - public bool SkipModule { get; set; } = false; + public bool SkipModule { get; set; } public Instrumenter( string module, @@ -71,6 +76,16 @@ public Instrumenter( _sourceRootTranslator = sourceRootTranslator; _cecilSymbolHelper = cecilSymbolHelper; _doesNotReturnAttributes = PrepareAttributes(parameters.DoesNotReturnAttributes); + _excludeAssembliesWithoutSources = DetermineHeuristics(parameters.ExcludeAssembliesWithoutSources); + } + + private AssemblySearchType DetermineHeuristics(string parametersExcludeAssembliesWithoutSources) + { + if (Enum.TryParse(parametersExcludeAssembliesWithoutSources, true, out AssemblySearchType option)) + { + return option; + } + return AssemblySearchType.MissingAll; } private static string[] PrepareAttributes(IEnumerable providedAttrs, params string[] defaultAttrs) @@ -91,29 +106,18 @@ public bool CanInstrument() { if (_instrumentationHelper.HasPdb(_module, out bool embeddedPdb)) { + if (_excludeAssembliesWithoutSources.Equals(AssemblySearchType.None)) + { + return true; + } + if (embeddedPdb) { - if (_instrumentationHelper.EmbeddedPortablePdbHasLocalSource(_module, out string firstNotFoundDocument)) - { - return true; - } - else - { - _logger.LogVerbose($"Unable to instrument module: {_module}, embedded pdb without local source files, [{FileSystem.EscapeFileName(firstNotFoundDocument)}]"); - return false; - } + return _instrumentationHelper.EmbeddedPortablePdbHasLocalSource(_module, _excludeAssembliesWithoutSources); } else { - if (_instrumentationHelper.PortablePdbHasLocalSource(_module, out string firstNotFoundDocument)) - { - return true; - } - else - { - _logger.LogVerbose($"Unable to instrument module: {_module}, pdb without local source files, [{FileSystem.EscapeFileName(firstNotFoundDocument)}]"); - return false; - } + return _instrumentationHelper.PortablePdbHasLocalSource(_module, _excludeAssembliesWithoutSources); } } else @@ -183,163 +187,155 @@ private bool Is_System_Threading_Interlocked_CoreLib_Type(TypeDefinition type) // locking issues if we do it while writing. private void CreateReachabilityHelper() { - using (var stream = _fileSystem.NewFileStream(_module, FileMode.Open, FileAccess.Read)) - using (var resolver = new NetstandardAwareAssemblyResolver(_module, _logger)) + using Stream stream = _fileSystem.NewFileStream(_module, FileMode.Open, FileAccess.Read); + using var resolver = new NetstandardAwareAssemblyResolver(_module, _logger); + resolver.AddSearchDirectory(Path.GetDirectoryName(_module)); + var parameters = new ReaderParameters { ReadSymbols = true, AssemblyResolver = resolver }; + if (_isCoreLibrary) { - resolver.AddSearchDirectory(Path.GetDirectoryName(_module)); - var parameters = new ReaderParameters { ReadSymbols = true, AssemblyResolver = resolver }; - if (_isCoreLibrary) - { - parameters.MetadataImporterProvider = new CoreLibMetadataImporterProvider(); - } - - using (var module = ModuleDefinition.ReadModule(stream, parameters)) - { - _reachabilityHelper = ReachabilityHelper.CreateForModule(module, _doesNotReturnAttributes, _logger); - } + parameters.MetadataImporterProvider = new CoreLibMetadataImporterProvider(); } + + using var module = ModuleDefinition.ReadModule(stream, parameters); + _reachabilityHelper = ReachabilityHelper.CreateForModule(module, _doesNotReturnAttributes, _logger); } private void InstrumentModule() { CreateReachabilityHelper(); - using (var stream = _fileSystem.NewFileStream(_module, FileMode.Open, FileAccess.ReadWrite)) - using (var resolver = new NetstandardAwareAssemblyResolver(_module, _logger)) + using Stream stream = _fileSystem.NewFileStream(_module, FileMode.Open, FileAccess.ReadWrite); + using var resolver = new NetstandardAwareAssemblyResolver(_module, _logger); + resolver.AddSearchDirectory(Path.GetDirectoryName(_module)); + var parameters = new ReaderParameters { ReadSymbols = true, AssemblyResolver = resolver }; + if (_isCoreLibrary) { - resolver.AddSearchDirectory(Path.GetDirectoryName(_module)); - var parameters = new ReaderParameters { ReadSymbols = true, AssemblyResolver = resolver }; - if (_isCoreLibrary) + parameters.MetadataImporterProvider = new CoreLibMetadataImporterProvider(); + } + + using var module = ModuleDefinition.ReadModule(stream, parameters); + foreach (CustomAttribute customAttribute in module.Assembly.CustomAttributes) + { + if (IsExcludeAttribute(customAttribute)) { - parameters.MetadataImporterProvider = new CoreLibMetadataImporterProvider(); + _logger.LogVerbose($"Excluded module: '{module}' for assembly level attribute {customAttribute.AttributeType.FullName}"); + SkipModule = true; + return; } + } - using (var module = ModuleDefinition.ReadModule(stream, parameters)) - { - foreach (CustomAttribute customAttribute in module.Assembly.CustomAttributes) - { - if (IsExcludeAttribute(customAttribute)) - { - _logger.LogVerbose($"Excluded module: '{module}' for assembly level attribute {customAttribute.AttributeType.FullName}"); - SkipModule = true; - return; - } - } + bool containsAppContext = module.GetType(nameof(System), nameof(AppContext)) != null; + IEnumerable types = module.GetTypes(); + AddCustomModuleTrackerToModule(module); - var containsAppContext = module.GetType(nameof(System), nameof(AppContext)) != null; - var types = module.GetTypes(); - AddCustomModuleTrackerToModule(module); + CustomDebugInformation sourceLinkDebugInfo = module.CustomDebugInformations.FirstOrDefault(c => c.Kind == CustomDebugInformationKind.SourceLink); + if (sourceLinkDebugInfo != null) + { + _result.SourceLink = ((SourceLinkDebugInformation)sourceLinkDebugInfo).Content; + } - var sourceLinkDebugInfo = module.CustomDebugInformations.FirstOrDefault(c => c.Kind == CustomDebugInformationKind.SourceLink); - if (sourceLinkDebugInfo != null) + foreach (TypeDefinition type in types) + { + if ( + !Is_System_Threading_Interlocked_CoreLib_Type(type) && + !IsTypeExcluded(type) && + _instrumentationHelper.IsTypeIncluded(_module, type.FullName, _parameters.IncludeFilters) + ) + { + if (IsSynthesizedMemberToBeExcluded(type)) { - _result.SourceLink = ((SourceLinkDebugInformation)sourceLinkDebugInfo).Content; + (_excludedCompilerGeneratedTypes ??= new List()).Add(type.FullName); } - - foreach (TypeDefinition type in types) + else { - if ( - !Is_System_Threading_Interlocked_CoreLib_Type(type) && - !IsTypeExcluded(type) && - _instrumentationHelper.IsTypeIncluded(_module, type.FullName, _parameters.IncludeFilters) - ) - { - if (IsSynthesizedMemberToBeExcluded(type)) - { - (_excludedCompilerGeneratedTypes ??= new List()).Add(type.FullName); - } - else - { - InstrumentType(type); - } - } + InstrumentType(type); } + } + } - // Fixup the custom tracker class constructor, according to all instrumented types - if (_customTrackerRegisterUnloadEventsMethod == null) - { - _customTrackerRegisterUnloadEventsMethod = new MethodReference( - nameof(ModuleTrackerTemplate.RegisterUnloadEvents), module.TypeSystem.Void, _customTrackerTypeDef); - } + // Fixup the custom tracker class constructor, according to all instrumented types + if (_customTrackerRegisterUnloadEventsMethod == null) + { + _customTrackerRegisterUnloadEventsMethod = new MethodReference( + nameof(ModuleTrackerTemplate.RegisterUnloadEvents), module.TypeSystem.Void, _customTrackerTypeDef); + } - Instruction lastInstr = _customTrackerClassConstructorIl.Body.Instructions.Last(); + Instruction lastInstr = _customTrackerClassConstructorIl.Body.Instructions.Last(); - if (!containsAppContext) - { - // For "normal" cases, where the instrumented assembly is not the core library, we add a call to - // RegisterUnloadEvents to the static constructor of the generated custom tracker. Due to static - // initialization constraints, the core library is handled separately below. - _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Call, _customTrackerRegisterUnloadEventsMethod)); - } + if (!containsAppContext) + { + // For "normal" cases, where the instrumented assembly is not the core library, we add a call to + // RegisterUnloadEvents to the static constructor of the generated custom tracker. Due to static + // initialization constraints, the core library is handled separately below. + _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Call, _customTrackerRegisterUnloadEventsMethod)); + } - _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldc_I4, _result.HitCandidates.Count)); - _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Newarr, module.TypeSystem.Int32)); - _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsArray)); - _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldstr, _result.HitsFilePath)); - _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsFilePath)); - _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(_parameters.SingleHit ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0)); - _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerSingleHit)); - _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldc_I4_1)); - _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerFlushHitFile)); - - if (containsAppContext) + _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldc_I4, _result.HitCandidates.Count)); + _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Newarr, module.TypeSystem.Int32)); + _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsArray)); + _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldstr, _result.HitsFilePath)); + _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsFilePath)); + _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(_parameters.SingleHit ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0)); + _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerSingleHit)); + _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldc_I4_1)); + _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerFlushHitFile)); + + if (containsAppContext) + { + // Handle the core library by instrumenting System.AppContext.OnProcessExit to directly call + // the UnloadModule method of the custom tracker type. This avoids loops between the static + // initialization of the custom tracker and the static initialization of the hosting AppDomain + // (which for the core library case will be instrumented code). + var eventArgsType = new TypeReference(nameof(System), nameof(EventArgs), module, module.TypeSystem.CoreLibrary); + var customTrackerUnloadModule = new MethodReference(nameof(ModuleTrackerTemplate.UnloadModule), module.TypeSystem.Void, _customTrackerTypeDef); + customTrackerUnloadModule.Parameters.Add(new ParameterDefinition(module.TypeSystem.Object)); + customTrackerUnloadModule.Parameters.Add(new ParameterDefinition(eventArgsType)); + + var appContextType = new TypeReference(nameof(System), nameof(AppContext), module, module.TypeSystem.CoreLibrary); + MethodDefinition onProcessExitMethod = new MethodReference("OnProcessExit", module.TypeSystem.Void, appContextType).Resolve(); + ILProcessor onProcessExitIl = onProcessExitMethod.Body.GetILProcessor(); + + // Put the OnProcessExit body inside try/finally to ensure the call to the UnloadModule. + Instruction lastInst = onProcessExitMethod.Body.Instructions.Last(); + var firstNullParam = Instruction.Create(OpCodes.Ldnull); + var secondNullParam = Instruction.Create(OpCodes.Ldnull); + var callUnload = Instruction.Create(OpCodes.Call, customTrackerUnloadModule); + onProcessExitIl.InsertAfter(lastInst, firstNullParam); + onProcessExitIl.InsertAfter(firstNullParam, secondNullParam); + onProcessExitIl.InsertAfter(secondNullParam, callUnload); + var endFinally = Instruction.Create(OpCodes.Endfinally); + onProcessExitIl.InsertAfter(callUnload, endFinally); + Instruction ret = onProcessExitIl.Create(OpCodes.Ret); + Instruction leaveAfterFinally = onProcessExitIl.Create(OpCodes.Leave, ret); + onProcessExitIl.InsertAfter(endFinally, ret); + foreach (Instruction inst in onProcessExitMethod.Body.Instructions.ToArray()) + { + // Patch ret to leave after the finally + if (inst.OpCode == OpCodes.Ret && inst != ret) { - // Handle the core library by instrumenting System.AppContext.OnProcessExit to directly call - // the UnloadModule method of the custom tracker type. This avoids loops between the static - // initialization of the custom tracker and the static initialization of the hosting AppDomain - // (which for the core library case will be instrumented code). - var eventArgsType = new TypeReference(nameof(System), nameof(EventArgs), module, module.TypeSystem.CoreLibrary); - var customTrackerUnloadModule = new MethodReference(nameof(ModuleTrackerTemplate.UnloadModule), module.TypeSystem.Void, _customTrackerTypeDef); - customTrackerUnloadModule.Parameters.Add(new ParameterDefinition(module.TypeSystem.Object)); - customTrackerUnloadModule.Parameters.Add(new ParameterDefinition(eventArgsType)); - - var appContextType = new TypeReference(nameof(System), nameof(AppContext), module, module.TypeSystem.CoreLibrary); - var onProcessExitMethod = new MethodReference("OnProcessExit", module.TypeSystem.Void, appContextType).Resolve(); - var onProcessExitIl = onProcessExitMethod.Body.GetILProcessor(); - - // Put the OnProcessExit body inside try/finally to ensure the call to the UnloadModule. - var lastInst = onProcessExitMethod.Body.Instructions.Last(); - var firstNullParam = Instruction.Create(OpCodes.Ldnull); - var secondNullParam = Instruction.Create(OpCodes.Ldnull); - var callUnload = Instruction.Create(OpCodes.Call, customTrackerUnloadModule); - onProcessExitIl.InsertAfter(lastInst, firstNullParam); - onProcessExitIl.InsertAfter(firstNullParam, secondNullParam); - onProcessExitIl.InsertAfter(secondNullParam, callUnload); - var endFinally = Instruction.Create(OpCodes.Endfinally); - onProcessExitIl.InsertAfter(callUnload, endFinally); - var ret = onProcessExitIl.Create(OpCodes.Ret); - var leaveAfterFinally = onProcessExitIl.Create(OpCodes.Leave, ret); - onProcessExitIl.InsertAfter(endFinally, ret); - foreach (var inst in onProcessExitMethod.Body.Instructions.ToArray()) - { - // Patch ret to leave after the finally - if (inst.OpCode == OpCodes.Ret && inst != ret) - { - var leaveBodyInstAfterFinally = onProcessExitIl.Create(OpCodes.Leave, ret); - var prevInst = inst.Previous; - onProcessExitMethod.Body.Instructions.Remove(inst); - onProcessExitIl.InsertAfter(prevInst, leaveBodyInstAfterFinally); - } - } - var handler = new ExceptionHandler(ExceptionHandlerType.Finally) - { - TryStart = onProcessExitIl.Body.Instructions.First(), - TryEnd = firstNullParam, - HandlerStart = firstNullParam, - HandlerEnd = ret - }; - - onProcessExitMethod.Body.ExceptionHandlers.Add(handler); + Instruction leaveBodyInstAfterFinally = onProcessExitIl.Create(OpCodes.Leave, ret); + Instruction prevInst = inst.Previous; + onProcessExitMethod.Body.Instructions.Remove(inst); + onProcessExitIl.InsertAfter(prevInst, leaveBodyInstAfterFinally); } - - module.Write(stream, new WriterParameters { WriteSymbols = true }); } + var handler = new ExceptionHandler(ExceptionHandlerType.Finally) + { + TryStart = onProcessExitIl.Body.Instructions.First(), + TryEnd = firstNullParam, + HandlerStart = firstNullParam, + HandlerEnd = ret + }; + + onProcessExitMethod.Body.ExceptionHandlers.Add(handler); } + + module.Write(stream, new WriterParameters { WriteSymbols = true }); } private void AddCustomModuleTrackerToModule(ModuleDefinition module) { - using (AssemblyDefinition coverletInstrumentationAssembly = AssemblyDefinition.ReadAssembly(typeof(ModuleTrackerTemplate).Assembly.Location)) + using (var coverletInstrumentationAssembly = AssemblyDefinition.ReadAssembly(typeof(ModuleTrackerTemplate).Assembly.Location)) { TypeDefinition moduleTrackerTemplate = coverletInstrumentationAssembly.MainModule.GetType( "Coverlet.Core.Instrumentation", nameof(ModuleTrackerTemplate)); @@ -367,14 +363,14 @@ private void AddCustomModuleTrackerToModule(ModuleDefinition module) foreach (MethodDefinition methodDef in moduleTrackerTemplate.Methods) { - MethodDefinition methodOnCustomType = new MethodDefinition(methodDef.Name, methodDef.Attributes, methodDef.ReturnType); + var methodOnCustomType = new MethodDefinition(methodDef.Name, methodDef.Attributes, methodDef.ReturnType); - foreach (var parameter in methodDef.Parameters) + foreach (ParameterDefinition parameter in methodDef.Parameters) { methodOnCustomType.Parameters.Add(new ParameterDefinition(module.ImportReference(parameter.ParameterType))); } - foreach (var variable in methodDef.Body.Variables) + foreach (VariableDefinition variable in methodDef.Body.Variables) { methodOnCustomType.Body.Variables.Add(new VariableDefinition(module.ImportReference(variable.VariableType))); } @@ -398,7 +394,7 @@ private void AddCustomModuleTrackerToModule(ModuleDefinition module) { // Move to the custom type var updatedMethodReference = new MethodReference(methodReference.Name, methodReference.ReturnType, _customTrackerTypeDef); - foreach (var parameter in methodReference.Parameters) + foreach (ParameterDefinition parameter in methodReference.Parameters) updatedMethodReference.Parameters.Add(new ParameterDefinition(parameter.Name, parameter.Attributes, module.ImportReference(parameter.ParameterType))); instr.Operand = updatedMethodReference; @@ -416,7 +412,7 @@ private void AddCustomModuleTrackerToModule(ModuleDefinition module) ilProcessor.Append(instr); } - foreach (var handler in methodDef.Body.ExceptionHandlers) + foreach (ExceptionHandler handler in methodDef.Body.ExceptionHandlers) { if (handler.CatchType != null) { @@ -469,12 +465,12 @@ private bool IsMethodOfCompilerGeneratedClassOfAsyncStateMachineToBeExcluded(Met private void InstrumentType(TypeDefinition type) { - var methods = type.GetMethods(); + IEnumerable methods = type.GetMethods(); // We keep ordinal index because it's the way used by compiler for generated types/methods to // avoid ambiguity int ordinal = -1; - foreach (var method in methods) + foreach (MethodDefinition method in methods) { MethodDefinition actualMethod = method; IEnumerable customAttributes = method.CustomAttributes; @@ -506,18 +502,24 @@ private void InstrumentType(TypeDefinition type) continue; } + if (_excludedLambdaMethods != null && _excludedLambdaMethods.Contains(method.FullName)) + { + continue; + } + if (!customAttributes.Any(IsExcludeAttribute)) { InstrumentMethod(method); } else { + (_excludedLambdaMethods ??= new List()).AddRange(CollectLambdaMethodsInsideLocalFunction(method)); (_excludedMethods ??= new List<(MethodDefinition, int)>()).Add((method, ordinal)); } } - var ctors = type.GetConstructors(); - foreach (var ctor in ctors) + IEnumerable ctors = type.GetConstructors(); + foreach (MethodDefinition ctor in ctors) { if (!ctor.CustomAttributes.Any(IsExcludeAttribute)) { @@ -528,7 +530,10 @@ private void InstrumentType(TypeDefinition type) private void InstrumentMethod(MethodDefinition method) { - var sourceFile = method.DebugInformation.SequencePoints.Select(s => _sourceRootTranslator.ResolveFilePath(s.Document.Url)).FirstOrDefault(); + string sourceFile = method.DebugInformation.SequencePoints.Select(s => _sourceRootTranslator.ResolveFilePath(s.Document.Url)).FirstOrDefault(); + + if (string.IsNullOrEmpty(sourceFile)) return; + if (!string.IsNullOrEmpty(sourceFile) && _excludedFilesHelper.Exclude(sourceFile)) { if (!(_excludedSourceFiles ??= new List()).Contains(sourceFile)) @@ -538,7 +543,7 @@ private void InstrumentMethod(MethodDefinition method) return; } - var methodBody = GetMethodBody(method); + MethodBody methodBody = GetMethodBody(method); if (methodBody == null) return; @@ -563,29 +568,30 @@ private void InstrumentIL(MethodDefinition method) { method.Body.SimplifyMacros(); ILProcessor processor = method.Body.GetILProcessor(); - var index = 0; - var count = processor.Body.Instructions.Count; - var branchPoints = _cecilSymbolHelper.GetBranchPoints(method); - var unreachableRanges = _reachabilityHelper.FindUnreachableIL(processor.Body.Instructions, processor.Body.ExceptionHandlers); - var currentUnreachableRangeIx = 0; + int index = 0; + int count = processor.Body.Instructions.Count; + IReadOnlyList branchPoints = _cecilSymbolHelper.GetBranchPoints(method); + IDictionary targetsMap = new Dictionary(); + System.Collections.Immutable.ImmutableArray unreachableRanges = _reachabilityHelper.FindUnreachableIL(processor.Body.Instructions, processor.Body.ExceptionHandlers); + int currentUnreachableRangeIx = 0; for (int n = 0; n < count; n++) { - var currentInstruction = processor.Body.Instructions[index]; - var sequencePoint = method.DebugInformation.GetSequencePoint(currentInstruction); - var targetedBranchPoints = branchPoints.Where(p => p.EndOffset == currentInstruction.Offset); + Instruction currentInstruction = processor.Body.Instructions[index]; + SequencePoint sequencePoint = method.DebugInformation.GetSequencePoint(currentInstruction); + IEnumerable targetedBranchPoints = branchPoints.Where(p => p.EndOffset == currentInstruction.Offset); // make sure we're looking at the correct unreachable range (if any) - var instrOffset = currentInstruction.Offset; + int instrOffset = currentInstruction.Offset; while (currentUnreachableRangeIx < unreachableRanges.Length && instrOffset > unreachableRanges[currentUnreachableRangeIx].EndOffset) { currentUnreachableRangeIx++; } // determine if the unreachable - var isUnreachable = false; + bool isUnreachable = false; if (currentUnreachableRangeIx < unreachableRanges.Length) { - var range = unreachableRanges[currentUnreachableRangeIx]; + ReachabilityHelper.UnreachableRange range = unreachableRanges[currentUnreachableRangeIx]; isUnreachable = instrOffset >= range.StartOffset && instrOffset <= range.EndOffset; } @@ -604,17 +610,13 @@ private void InstrumentIL(MethodDefinition method) continue; } - var firstInjectedInstrumentedOpCode = AddInstrumentationCode(method, processor, currentInstruction, sequencePoint); - foreach (var bodyInstruction in processor.Body.Instructions) - ReplaceInstructionTarget(bodyInstruction, currentInstruction, firstInjectedInstrumentedOpCode); - - foreach (ExceptionHandler handler in processor.Body.ExceptionHandlers) - ReplaceExceptionHandlerBoundary(handler, currentInstruction, firstInjectedInstrumentedOpCode); + Instruction firstInjectedInstrumentedOpCode = AddInstrumentationCode(method, processor, currentInstruction, sequencePoint); + targetsMap.Add(currentInstruction.Offset, firstInjectedInstrumentedOpCode); index += 2; } - foreach (var branchTarget in targetedBranchPoints) + foreach (BranchPoint branchTarget in targetedBranchPoints) { /* * Skip branches with no sequence point reference for now. @@ -625,12 +627,9 @@ private void InstrumentIL(MethodDefinition method) if (branchTarget.StartLine == -1 || branchTarget.Document == null) continue; - var firstInjectedInstrumentedOpCode = AddInstrumentationCode(method, processor, currentInstruction, branchTarget); - foreach (var bodyInstruction in processor.Body.Instructions) - ReplaceInstructionTarget(bodyInstruction, currentInstruction, firstInjectedInstrumentedOpCode); - - foreach (ExceptionHandler handler in processor.Body.ExceptionHandlers) - ReplaceExceptionHandlerBoundary(handler, currentInstruction, firstInjectedInstrumentedOpCode); + Instruction firstInjectedInstrumentedOpCode = AddInstrumentationCode(method, processor, currentInstruction, branchTarget); + if (!targetsMap.ContainsKey(currentInstruction.Offset)) + targetsMap.Add(currentInstruction.Offset, firstInjectedInstrumentedOpCode); index += 2; } @@ -638,12 +637,18 @@ private void InstrumentIL(MethodDefinition method) index++; } + foreach (Instruction bodyInstruction in processor.Body.Instructions) + ReplaceInstructionTarget(bodyInstruction, targetsMap); + + foreach (ExceptionHandler handler in processor.Body.ExceptionHandlers) + ReplaceExceptionHandlerBoundary(handler, targetsMap); + method.Body.OptimizeMacros(); } private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor processor, Instruction instruction, SequencePoint sequencePoint) { - if (!_result.Documents.TryGetValue(_sourceRootTranslator.ResolveFilePath(sequencePoint.Document.Url), out var document)) + if (!_result.Documents.TryGetValue(_sourceRootTranslator.ResolveFilePath(sequencePoint.Document.Url), out Document document)) { document = new Document { Path = _sourceRootTranslator.ResolveFilePath(sequencePoint.Document.Url) }; document.Index = _result.Documents.Count; @@ -663,14 +668,14 @@ private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor processor, Instruction instruction, BranchPoint branchPoint) { - if (!_result.Documents.TryGetValue(_sourceRootTranslator.ResolveFilePath(branchPoint.Document), out var document)) + if (!_result.Documents.TryGetValue(_sourceRootTranslator.ResolveFilePath(branchPoint.Document), out Document document)) { document = new Document { Path = _sourceRootTranslator.ResolveFilePath(branchPoint.Document) }; document.Index = _result.Documents.Count; _result.Documents.Add(document.Path, document); } - BranchKey key = new BranchKey(branchPoint.StartLine, (int)branchPoint.Ordinal); + var key = new BranchKey(branchPoint.StartLine, (int)branchPoint.Ordinal); if (!document.Branches.ContainsKey(key)) { document.Branches.Add( @@ -738,42 +743,41 @@ private Instruction AddInstrumentationInstructions(MethodDefinition method, ILPr return indxInstr; } - private static void ReplaceInstructionTarget(Instruction instruction, Instruction oldTarget, Instruction newTarget) + private static void ReplaceInstructionTarget(Instruction instruction, IDictionary targetsMap) { if (instruction.Operand is Instruction operandInstruction) { - if (operandInstruction == oldTarget) + if (targetsMap.TryGetValue(operandInstruction.Offset, out Instruction newTarget)) { instruction.Operand = newTarget; - return; } } else if (instruction.Operand is Instruction[] operandInstructions) { for (int i = 0; i < operandInstructions.Length; i++) { - if (operandInstructions[i] == oldTarget) + if (targetsMap.TryGetValue(operandInstructions[i].Offset, out Instruction newTarget)) operandInstructions[i] = newTarget; } } } - private static void ReplaceExceptionHandlerBoundary(ExceptionHandler handler, Instruction oldTarget, Instruction newTarget) + private static void ReplaceExceptionHandlerBoundary(ExceptionHandler handler, IDictionary targetsMap) { - if (handler.FilterStart == oldTarget) - handler.FilterStart = newTarget; + if (handler.FilterStart is not null && targetsMap.TryGetValue(handler.FilterStart.Offset, out Instruction newFilterStart)) + handler.FilterStart = newFilterStart; - if (handler.HandlerEnd == oldTarget) - handler.HandlerEnd = newTarget; + if (handler.HandlerEnd is not null && targetsMap.TryGetValue(handler.HandlerEnd.Offset, out Instruction newHandlerEnd)) + handler.HandlerEnd = newHandlerEnd; - if (handler.HandlerStart == oldTarget) - handler.HandlerStart = newTarget; + if (handler.HandlerStart is not null && targetsMap.TryGetValue(handler.HandlerStart.Offset, out Instruction newHandlerStart)) + handler.HandlerStart = newHandlerStart; - if (handler.TryEnd == oldTarget) - handler.TryEnd = newTarget; + if (handler.TryEnd is not null && targetsMap.TryGetValue(handler.TryEnd.Offset, out Instruction newTryEnd)) + handler.TryEnd = newTryEnd; - if (handler.TryStart == oldTarget) - handler.TryStart = newTarget; + if (handler.TryStart is not null && targetsMap.TryGetValue(handler.TryStart.Offset, out Instruction newTryStart)) + handler.TryStart = newTryStart; } private bool IsExcludeAttribute(CustomAttribute customAttribute) @@ -815,7 +819,7 @@ private bool IsSynthesizedMemberToBeExcluded(IMemberDefinition definition) } // Check methods members and compiler generated types - foreach (var excludedMethods in _excludedMethods) + foreach ((MethodDefinition, int) excludedMethods in _excludedMethods) { // Exclude this member if declaring type is the same of the excluded method and // the name is synthesized from the name of the excluded method. @@ -848,6 +852,19 @@ internal bool IsSynthesizedNameOf(string name, string methodName, int methodOrdi (name.IndexOf($"<{methodName}>g__") != -1 && name.IndexOf($"|{methodOrdinal}_") != -1); } + private static IEnumerable CollectLambdaMethodsInsideLocalFunction(MethodDefinition methodDefinition) + { + if (!methodDefinition.Name.Contains(">g__")) yield break; + + foreach (Instruction instruction in methodDefinition.Body.Instructions.ToList()) + { + if (instruction.OpCode == OpCodes.Ldftn && instruction.Operand is MethodReference mr && mr.Name.Contains(">b__")) + { + yield return mr.FullName; + } + } + } + /// /// A custom importer created specifically to allow the instrumentation of System.Private.CoreLib by /// removing the external references to netstandard that are generated when instrumenting a typical @@ -862,52 +879,52 @@ public IMetadataImporter GetMetadataImporter(ModuleDefinition module) private class CoreLibMetadataImporter : IMetadataImporter { - private readonly ModuleDefinition module; - private readonly DefaultMetadataImporter defaultMetadataImporter; + private readonly ModuleDefinition _module; + private readonly DefaultMetadataImporter _defaultMetadataImporter; public CoreLibMetadataImporter(ModuleDefinition module) { - this.module = module; - this.defaultMetadataImporter = new DefaultMetadataImporter(module); + _module = module; + _defaultMetadataImporter = new DefaultMetadataImporter(module); } public AssemblyNameReference ImportReference(AssemblyNameReference reference) { - return this.defaultMetadataImporter.ImportReference(reference); + return _defaultMetadataImporter.ImportReference(reference); } public TypeReference ImportReference(TypeReference type, IGenericParameterProvider context) { - var importedRef = this.defaultMetadataImporter.ImportReference(type, context); - importedRef.GetElementType().Scope = module.TypeSystem.CoreLibrary; + TypeReference importedRef = _defaultMetadataImporter.ImportReference(type, context); + importedRef.GetElementType().Scope = _module.TypeSystem.CoreLibrary; return importedRef; } public FieldReference ImportReference(FieldReference field, IGenericParameterProvider context) { - var importedRef = this.defaultMetadataImporter.ImportReference(field, context); - importedRef.FieldType.GetElementType().Scope = module.TypeSystem.CoreLibrary; + FieldReference importedRef = _defaultMetadataImporter.ImportReference(field, context); + importedRef.FieldType.GetElementType().Scope = _module.TypeSystem.CoreLibrary; return importedRef; } public MethodReference ImportReference(MethodReference method, IGenericParameterProvider context) { - var importedRef = this.defaultMetadataImporter.ImportReference(method, context); - importedRef.DeclaringType.GetElementType().Scope = module.TypeSystem.CoreLibrary; + MethodReference importedRef = _defaultMetadataImporter.ImportReference(method, context); + importedRef.DeclaringType.GetElementType().Scope = _module.TypeSystem.CoreLibrary; - foreach (var parameter in importedRef.Parameters) + foreach (ParameterDefinition parameter in importedRef.Parameters) { - if (parameter.ParameterType.Scope == module.TypeSystem.CoreLibrary) + if (parameter.ParameterType.Scope == _module.TypeSystem.CoreLibrary) { continue; } - parameter.ParameterType.GetElementType().Scope = module.TypeSystem.CoreLibrary; + parameter.ParameterType.GetElementType().Scope = _module.TypeSystem.CoreLibrary; } - if (importedRef.ReturnType.Scope != module.TypeSystem.CoreLibrary) + if (importedRef.ReturnType.Scope != _module.TypeSystem.CoreLibrary) { - importedRef.ReturnType.GetElementType().Scope = module.TypeSystem.CoreLibrary; + importedRef.ReturnType.GetElementType().Scope = _module.TypeSystem.CoreLibrary; } return importedRef; @@ -919,14 +936,14 @@ public MethodReference ImportReference(MethodReference method, IGenericParameter // Exclude files helper https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.filesystemglobbing.matcher?view=aspnetcore-2.2 internal class ExcludedFilesHelper { - Matcher _matcher; + readonly Matcher _matcher; public ExcludedFilesHelper(string[] excludes, ILogger logger) { if (excludes != null && excludes.Length > 0) { _matcher = new Matcher(); - foreach (var excludeRule in excludes) + foreach (string excludeRule in excludes) { if (excludeRule is null) { diff --git a/src/coverlet.core/Instrumentation/InstrumenterResult.cs b/src/coverlet.core/Instrumentation/InstrumenterResult.cs index a575f7739..69a6ab24a 100644 --- a/src/coverlet.core/Instrumentation/InstrumenterResult.cs +++ b/src/coverlet.core/Instrumentation/InstrumenterResult.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.Collections.Generic; using System.Diagnostics; @@ -47,11 +50,11 @@ internal class BranchKey : IEquatable public override bool Equals(object obj) => Equals(obj); - public bool Equals(BranchKey other) => other is BranchKey branchKey && branchKey.Line == this.Line && branchKey.Ordinal == this.Ordinal; + public bool Equals(BranchKey other) => other is BranchKey branchKey && branchKey.Line == Line && branchKey.Ordinal == Ordinal; public override int GetHashCode() { - return (this.Line, this.Ordinal).GetHashCode(); + return (Line, Ordinal).GetHashCode(); } } diff --git a/src/coverlet.core/Instrumentation/ModuleTrackerTemplate.cs b/src/coverlet.core/Instrumentation/ModuleTrackerTemplate.cs index 8321a704f..600fe91b1 100644 --- a/src/coverlet.core/Instrumentation/ModuleTrackerTemplate.cs +++ b/src/coverlet.core/Instrumentation/ModuleTrackerTemplate.cs @@ -1,5 +1,7 @@ -using System; -using System.Diagnostics; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Reflection; @@ -24,8 +26,8 @@ internal static class ModuleTrackerTemplate public static int[] HitsArray; public static bool SingleHit; public static bool FlushHitFile; - private static readonly bool _enableLog = int.TryParse(Environment.GetEnvironmentVariable("COVERLET_ENABLETRACKERLOG"), out int result) ? result == 1 : false; - private static string _sessionId = Guid.NewGuid().ToString(); + private static readonly bool s_enableLog = int.TryParse(Environment.GetEnvironmentVariable("COVERLET_ENABLETRACKERLOG"), out int result) && result == 1; + private static readonly string s_sessionId = Guid.NewGuid().ToString(); static ModuleTrackerTemplate() { @@ -81,100 +83,94 @@ public static void UnloadModule(object sender, EventArgs e) { // The same module can be unloaded multiple times in the same process via different app domains. // Use a global mutex to ensure no concurrent access. - using (var mutex = new Mutex(true, Path.GetFileNameWithoutExtension(HitsFilePath) + "_Mutex", out bool createdNew)) + using var mutex = new Mutex(true, Path.GetFileNameWithoutExtension(HitsFilePath) + "_Mutex", out bool createdNew); + if (!createdNew) { - if (!createdNew) - { - mutex.WaitOne(); - } + mutex.WaitOne(); + } - if (FlushHitFile) + if (FlushHitFile) + { + try { - try - { - // Claim the current hits array and reset it to prevent double-counting scenarios. - int[] hitsArray = Interlocked.Exchange(ref HitsArray, new int[HitsArray.Length]); + // Claim the current hits array and reset it to prevent double-counting scenarios. + int[] hitsArray = Interlocked.Exchange(ref HitsArray, new int[HitsArray.Length]); - WriteLog($"Unload called for '{Assembly.GetExecutingAssembly().Location}' by '{sender ?? "null"}'"); - WriteLog($"Flushing hit file '{HitsFilePath}'"); + WriteLog($"Unload called for '{Assembly.GetExecutingAssembly().Location}' by '{sender ?? "null"}'"); + WriteLog($"Flushing hit file '{HitsFilePath}'"); - bool failedToCreateNewHitsFile = false; - try + bool failedToCreateNewHitsFile = false; + try + { + using var fs = new FileStream(HitsFilePath, FileMode.CreateNew); + using var bw = new BinaryWriter(fs); + bw.Write(hitsArray.Length); + foreach (int hitCount in hitsArray) { - using (var fs = new FileStream(HitsFilePath, FileMode.CreateNew)) - using (var bw = new BinaryWriter(fs)) - { - bw.Write(hitsArray.Length); - foreach (int hitCount in hitsArray) - { - bw.Write(hitCount); - } - } + bw.Write(hitCount); } - catch (Exception ex) + } + catch (Exception ex) + { + WriteLog($"Failed to create new hits file '{HitsFilePath}' -> '{ex.Message}'"); + failedToCreateNewHitsFile = true; + } + + if (failedToCreateNewHitsFile) + { + // Update the number of hits by adding value on disk with the ones on memory. + // This path should be triggered only in the case of multiple AppDomain unloads. + using var fs = new FileStream(HitsFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None); + using var br = new BinaryReader(fs); + using var bw = new BinaryWriter(fs); + int hitsLength = br.ReadInt32(); + WriteLog($"Current hits found '{hitsLength}'"); + + if (hitsLength != hitsArray.Length) { - WriteLog($"Failed to create new hits file '{HitsFilePath}' -> '{ex.Message}'"); - failedToCreateNewHitsFile = true; + throw new InvalidOperationException($"{HitsFilePath} has {hitsLength} entries but on memory {nameof(HitsArray)} has {hitsArray.Length}"); } - if (failedToCreateNewHitsFile) + for (int i = 0; i < hitsLength; ++i) { - // Update the number of hits by adding value on disk with the ones on memory. - // This path should be triggered only in the case of multiple AppDomain unloads. - using (var fs = new FileStream(HitsFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) - using (var br = new BinaryReader(fs)) - using (var bw = new BinaryWriter(fs)) + int oldHitCount = br.ReadInt32(); + bw.Seek(-sizeof(int), SeekOrigin.Current); + if (SingleHit) { - int hitsLength = br.ReadInt32(); - WriteLog($"Current hits found '{hitsLength}'"); - - if (hitsLength != hitsArray.Length) - { - throw new InvalidOperationException($"{HitsFilePath} has {hitsLength} entries but on memory {nameof(HitsArray)} has {hitsArray.Length}"); - } - - for (int i = 0; i < hitsLength; ++i) - { - int oldHitCount = br.ReadInt32(); - bw.Seek(-sizeof(int), SeekOrigin.Current); - if (SingleHit) - { - bw.Write(hitsArray[i] + oldHitCount > 0 ? 1 : 0); - } - else - { - bw.Write(hitsArray[i] + oldHitCount); - } - } + bw.Write(hitsArray[i] + oldHitCount > 0 ? 1 : 0); + } + else + { + bw.Write(hitsArray[i] + oldHitCount); } } + } - WriteHits(sender); + WriteHits(sender); - WriteLog($"Hit file '{HitsFilePath}' flushed, size {new FileInfo(HitsFilePath).Length}"); - WriteLog("--------------------------------"); - } - catch (Exception ex) - { - WriteLog(ex.ToString()); - throw; - } + WriteLog($"Hit file '{HitsFilePath}' flushed, size {new FileInfo(HitsFilePath).Length}"); + WriteLog("--------------------------------"); + } + catch (Exception ex) + { + WriteLog(ex.ToString()); + throw; } - - // On purpose this is not under a try-finally: it is better to have an exception if there was any error writing the hits file - // this case is relevant when instrumenting corelib since multiple processes can be running against the same instrumented dll. - mutex.ReleaseMutex(); } + + // On purpose this is not under a try-finally: it is better to have an exception if there was any error writing the hits file + // this case is relevant when instrumenting corelib since multiple processes can be running against the same instrumented dll. + mutex.ReleaseMutex(); } private static void WriteHits(object sender) { - if (_enableLog) + if (s_enableLog) { - Assembly currentAssembly = Assembly.GetExecutingAssembly(); - DirectoryInfo location = new DirectoryInfo(Path.Combine(Path.GetDirectoryName(currentAssembly.Location), "TrackersHitsLog")); + var currentAssembly = Assembly.GetExecutingAssembly(); + var location = new DirectoryInfo(Path.Combine(Path.GetDirectoryName(currentAssembly.Location), "TrackersHitsLog")); location.Create(); - string logFile = Path.Combine(location.FullName, $"{Path.GetFileName(currentAssembly.Location)}_{DateTime.UtcNow.Ticks}_{_sessionId}.txt"); + string logFile = Path.Combine(location.FullName, $"{Path.GetFileName(currentAssembly.Location)}_{DateTime.UtcNow.Ticks}_{s_sessionId}.txt"); using (var fs = new FileStream(HitsFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) using (var log = new FileStream(logFile, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None)) using (var logWriter = new StreamWriter(log)) @@ -193,12 +189,12 @@ private static void WriteHits(object sender) private static void WriteLog(string logText) { - if (_enableLog) + if (s_enableLog) { // We don't set path as global var to keep benign possible errors inside try/catch // I'm not sure that location will be ok in every scenario string location = Assembly.GetExecutingAssembly().Location; - File.AppendAllText(Path.Combine(Path.GetDirectoryName(location), Path.GetFileName(location) + "_tracker.txt"), $"[{DateTime.UtcNow} S:{_sessionId} T:{Thread.CurrentThread.ManagedThreadId}]{logText}{Environment.NewLine}"); + File.AppendAllText(Path.Combine(Path.GetDirectoryName(location), Path.GetFileName(location) + "_tracker.txt"), $"[{DateTime.UtcNow} S:{s_sessionId} T:{Thread.CurrentThread.ManagedThreadId}]{logText}{Environment.NewLine}"); } } } diff --git a/src/coverlet.core/Instrumentation/ReachabilityHelper.cs b/src/coverlet.core/Instrumentation/ReachabilityHelper.cs index 4d7474ea6..1b4840d85 100644 --- a/src/coverlet.core/Instrumentation/ReachabilityHelper.cs +++ b/src/coverlet.core/Instrumentation/ReachabilityHelper.cs @@ -1,10 +1,13 @@ -using Coverlet.Core.Abstractions; -using Mono.Cecil; -using Mono.Cecil.Cil; -using Mono.Collections.Generic; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.Collections.Immutable; using System.Linq; +using Coverlet.Core.Abstractions; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Collections.Generic; namespace Coverlet.Core.Instrumentation.Reachability { @@ -97,9 +100,9 @@ private readonly struct BranchInstruction /// /// Returns true if this branch has multiple targets. /// - public bool HasMultiTargets => _TargetOffset == -1; + public bool HasMultiTargets => _targetOffset == -1; - private readonly int _TargetOffset; + private readonly int _targetOffset; /// /// Target of the branch, assuming it has a single target. @@ -115,11 +118,11 @@ public int TargetOffset throw new InvalidOperationException($"{HasMultiTargets} is true"); } - return _TargetOffset; + return _targetOffset; } } - private readonly ImmutableArray _TargetOffsets; + private readonly ImmutableArray _targetOffsets; /// /// Targets of the branch, assuming it has multiple targets. @@ -135,15 +138,15 @@ public ImmutableArray TargetOffsets throw new InvalidOperationException($"{HasMultiTargets} is false"); } - return _TargetOffsets; + return _targetOffsets; } } public BranchInstruction(int offset, int targetOffset) { Offset = offset; - _TargetOffset = targetOffset; - _TargetOffsets = ImmutableArray.Empty; + _targetOffset = targetOffset; + _targetOffsets = ImmutableArray.Empty; } public BranchInstruction(int offset, ImmutableArray targetOffset) @@ -154,8 +157,8 @@ public BranchInstruction(int offset, ImmutableArray targetOffset) } Offset = offset; - _TargetOffset = -1; - _TargetOffsets = targetOffset; + _targetOffset = -1; + _targetOffsets = targetOffset; } public override string ToString() @@ -166,7 +169,7 @@ public override string ToString() /// OpCodes that transfer control code, even if they do not /// introduce branch points. /// - private static readonly ImmutableHashSet BRANCH_OPCODES = + private static readonly ImmutableHashSet s_branchOpCodes = ImmutableHashSet.CreateRange( new[] { @@ -221,7 +224,7 @@ public override string ToString() /// OpCodes that unconditionally transfer control, so there /// is not "fall through" branch target. /// - private static readonly ImmutableHashSet UNCONDITIONAL_BRANCH_OPCODES = + private static readonly ImmutableHashSet s_unconditionalBranchOpCodes = ImmutableHashSet.CreateRange( new[] { @@ -232,11 +235,11 @@ public override string ToString() } ); - private readonly ImmutableHashSet DoesNotReturnMethods; + private readonly ImmutableHashSet _doesNotReturnMethods; private ReachabilityHelper(ImmutableHashSet doesNotReturnMethods) { - DoesNotReturnMethods = doesNotReturnMethods; + _doesNotReturnMethods = doesNotReturnMethods; } /// @@ -252,11 +255,11 @@ public static ReachabilityHelper CreateForModule(ModuleDefinition module, string return new ReachabilityHelper(ImmutableHashSet.Empty); } - var processedMethods = ImmutableHashSet.Empty; - var doNotReturn = ImmutableHashSet.CreateBuilder(); - foreach (var type in module.Types) + ImmutableHashSet processedMethods = ImmutableHashSet.Empty; + ImmutableHashSet.Builder doNotReturn = ImmutableHashSet.CreateBuilder(); + foreach (TypeDefinition type in module.Types) { - foreach (var mtd in type.Methods) + foreach (MethodDefinition mtd in type.Methods) { if (mtd.IsNative) { @@ -278,14 +281,14 @@ public static ReachabilityHelper CreateForModule(ModuleDefinition module, string continue; } - foreach (var instr in body.Instructions) + foreach (Instruction instr in body.Instructions) { - if (!IsCall(instr, out var calledMtd)) + if (!IsCall(instr, out MethodReference calledMtd)) { continue; } - var token = calledMtd.MetadataToken; + MetadataToken token = calledMtd.MetadataToken; if (processedMethods.Contains(token)) { continue; @@ -314,8 +317,8 @@ public static ReachabilityHelper CreateForModule(ModuleDefinition module, string continue; } - var hasDoesNotReturnAttribute = false; - foreach (var attr in mtdDef.CustomAttributes) + bool hasDoesNotReturnAttribute = false; + foreach (CustomAttribute attr in mtdDef.CustomAttributes) { if (Array.IndexOf(doesNotReturnAttributes, attr.AttributeType.Name) != -1) { @@ -333,7 +336,7 @@ public static ReachabilityHelper CreateForModule(ModuleDefinition module, string } } - var doNoReturnTokens = doNotReturn.ToImmutable(); + ImmutableHashSet doNoReturnTokens = doNotReturn.ToImmutable(); return new ReachabilityHelper(doNoReturnTokens); } @@ -369,12 +372,12 @@ public ImmutableArray FindUnreachableIL(Collection.Empty; } - var (mayContainUnreachableCode, branches) = AnalyzeInstructions(instrs, exceptionHandlers); + (bool mayContainUnreachableCode, ImmutableArray branches) = AnalyzeInstructions(instrs, exceptionHandlers); // no need to do any more work, nothing unreachable here if (!mayContainUnreachableCode) @@ -382,9 +385,9 @@ public ImmutableArray FindUnreachableIL(Collection.Empty; } - var lastInstr = instrs[instrs.Count - 1]; + Instruction lastInstr = instrs[instrs.Count - 1]; - var blocks = CreateBasicBlocks(instrs, exceptionHandlers, branches); + ImmutableArray blocks = CreateBasicBlocks(instrs, exceptionHandlers, branches); DetermineHeadReachability(blocks); return DetermineUnreachableRanges(blocks, lastInstr.Offset); @@ -396,16 +399,16 @@ public ImmutableArray FindUnreachableIL(Collection private (bool MayContainUnreachableCode, ImmutableArray Branches) AnalyzeInstructions(Collection instrs, Collection exceptionHandlers) { - var containsDoesNotReturnCall = false; + bool containsDoesNotReturnCall = false; - var ret = ImmutableArray.CreateBuilder(); - foreach (var i in instrs) + ImmutableArray.Builder ret = ImmutableArray.CreateBuilder(); + foreach (Instruction i in instrs) { containsDoesNotReturnCall = containsDoesNotReturnCall || DoesNotReturn(i); - if (BRANCH_OPCODES.Contains(i.OpCode)) + if (s_branchOpCodes.Contains(i.OpCode)) { - var (singleTargetOffset, multiTargetOffsets) = GetInstructionTargets(i, exceptionHandlers); + (int? singleTargetOffset, ImmutableArray multiTargetOffsets) = GetInstructionTargets(i, exceptionHandlers); if (singleTargetOffset != null) { @@ -435,7 +438,7 @@ private static (int? SingleTargetOffset, ImmutableArray MultiTargetOffsets) singleTargetOffset = null; multiTargetOffsets = ImmutableArray.Create(i.Next.Offset); - foreach (var instr in multiTarget) + foreach (Instruction instr in multiTarget) { // in practice these are small arrays, so a scan should be fine if (multiTargetOffsets.Contains(instr.Offset)) @@ -450,7 +453,7 @@ private static (int? SingleTargetOffset, ImmutableArray MultiTargetOffsets) { // it's any of the B.*(_S)? or Leave(_S)? instructions - if (UNCONDITIONAL_BRANCH_OPCODES.Contains(i.OpCode)) + if (s_unconditionalBranchOpCodes.Contains(i.OpCode)) { multiTargetOffsets = ImmutableArray.Empty; singleTargetOffset = targetInstr.Offset; @@ -467,15 +470,15 @@ private static (int? SingleTargetOffset, ImmutableArray MultiTargetOffsets) // flow is allowed so we can scan backwards to see find the block ExceptionHandler filterForHandler = null; - foreach (var handler in exceptionHandlers) + foreach (ExceptionHandler handler in exceptionHandlers) { if (handler.FilterStart == null) { continue; } - var startsAt = handler.FilterStart; - var cur = startsAt; + Instruction startsAt = handler.FilterStart; + Instruction cur = startsAt; while (cur != null && cur.Offset < i.Offset) { cur = cur.Next; @@ -524,15 +527,15 @@ private static (int? SingleTargetOffset, ImmutableArray MultiTargetOffsets) /// /// Calculates which ranges of IL are unreachable, given blocks which have head and tail reachability calculated. /// - private ImmutableArray DetermineUnreachableRanges(ImmutableArray blocks, int lastInstructionOffset) + private static ImmutableArray DetermineUnreachableRanges(ImmutableArray blocks, int lastInstructionOffset) { - var ret = ImmutableArray.CreateBuilder(); + ImmutableArray.Builder ret = ImmutableArray.CreateBuilder(); - var endOfMethodOffset = lastInstructionOffset + 1; // add 1 so we point _past_ the end of the method + int endOfMethodOffset = lastInstructionOffset + 1; // add 1 so we point _past_ the end of the method - for (var curBlockIx = 0; curBlockIx < blocks.Length; curBlockIx++) + for (int curBlockIx = 0; curBlockIx < blocks.Length; curBlockIx++) { - var curBlock = blocks[curBlockIx]; + BasicBlock curBlock = blocks[curBlockIx]; int endOfCurBlockOffset; if (curBlockIx == blocks.Length - 1) @@ -553,11 +556,11 @@ private ImmutableArray DetermineUnreachableRanges(ImmutableArr } // tail isn't reachable, which means there's a call to something that doesn't return... - var doesNotReturnInstr = curBlock.UnreachableAfter; + Instruction doesNotReturnInstr = curBlock.UnreachableAfter; // and it's everything _after_ the following instruction that is unreachable // so record the following instruction through the end of the block - var followingInstr = doesNotReturnInstr.Next; + Instruction followingInstr = doesNotReturnInstr.Next; ret.Add(new UnreachableRange(followingInstr.Offset, endOfCurBlockOffset)); } @@ -577,17 +580,17 @@ private ImmutableArray DetermineUnreachableRanges(ImmutableArr /// /// "Tail reachability" will have already been determined in CreateBlocks. /// - private void DetermineHeadReachability(ImmutableArray blocks) + private static void DetermineHeadReachability(ImmutableArray blocks) { var blockLookup = blocks.ToImmutableDictionary(b => b.StartOffset); - var headBlock = blockLookup[0]; + BasicBlock headBlock = blockLookup[0]; var knownLive = ImmutableStack.Create(headBlock); while (!knownLive.IsEmpty) { - knownLive = knownLive.Pop(out var block); + knownLive = knownLive.Pop(out BasicBlock block); if (block.HeadReachable) { @@ -601,18 +604,18 @@ private void DetermineHeadReachability(ImmutableArray blocks) if (block.TailReachable) { // we can reach all the blocks it might flow to - foreach (var reachableOffset in block.BranchesTo) + foreach (int reachableOffset in block.BranchesTo) { - var reachableBlock = blockLookup[reachableOffset]; + BasicBlock reachableBlock = blockLookup[reachableOffset]; knownLive = knownLive.Push(reachableBlock); } } // if the block is covered by an exception handler, then executing _any_ instruction in it // could conceivably cause those handlers to be visited - foreach (var exceptionHandlerOffset in block.ExceptionBranchesTo) + foreach (int exceptionHandlerOffset in block.ExceptionBranchesTo) { - var reachableHandler = blockLookup[exceptionHandlerOffset]; + BasicBlock reachableHandler = blockLookup[exceptionHandlerOffset]; knownLive = knownLive.Push(reachableHandler); } } @@ -629,16 +632,16 @@ private void DetermineHeadReachability(ImmutableArray blocks) private ImmutableArray CreateBasicBlocks(Collection instrs, Collection exceptionHandlers, ImmutableArray branches) { // every branch-like instruction starts or stops a block - var branchInstrLocs = branches.ToLookup(i => i.Offset); + ILookup branchInstrLocs = branches.ToLookup(i => i.Offset); var branchInstrOffsets = branchInstrLocs.Select(k => k.Key).ToImmutableHashSet(); // every target that might be branched to starts or stops a block - var branchTargetOffsetsBuilder = ImmutableHashSet.CreateBuilder(); - foreach (var branch in branches) + ImmutableHashSet.Builder branchTargetOffsetsBuilder = ImmutableHashSet.CreateBuilder(); + foreach (BranchInstruction branch in branches) { if (branch.HasMultiTargets) { - foreach (var target in branch.TargetOffsets) + foreach (int target in branch.TargetOffsets) { branchTargetOffsetsBuilder.Add(target); } @@ -651,7 +654,7 @@ private ImmutableArray CreateBasicBlocks(Collection ins // every exception handler an entry point // either it's handler, or it's filter (if present) - foreach (var handler in exceptionHandlers) + foreach (ExceptionHandler handler in exceptionHandlers) { if (handler.FilterStart != null) { @@ -663,18 +666,18 @@ private ImmutableArray CreateBasicBlocks(Collection ins } } - var branchTargetOffsets = branchTargetOffsetsBuilder.ToImmutable(); + ImmutableHashSet branchTargetOffsets = branchTargetOffsetsBuilder.ToImmutable(); // ending the method is also important - var endOfMethodOffset = instrs[instrs.Count - 1].Offset; + int endOfMethodOffset = instrs[instrs.Count - 1].Offset; - var blocks = ImmutableArray.Empty; + ImmutableArray blocks = ImmutableArray.Empty; int? blockStartedAt = null; Instruction unreachableAfter = null; - foreach (var i in instrs) + foreach (Instruction i in instrs) { - var offset = i.Offset; - var branchesAtLoc = branchInstrLocs[offset]; + int offset = i.Offset; + System.Collections.Generic.IEnumerable branchesAtLoc = branchInstrLocs[offset]; if (blockStartedAt == null) { @@ -682,19 +685,19 @@ private ImmutableArray CreateBasicBlocks(Collection ins unreachableAfter = null; } - var isBranch = branchInstrOffsets.Contains(offset); - var isFollowedByBranchTarget = i.Next != null && branchTargetOffsets.Contains(i.Next.Offset); - var isEndOfMtd = endOfMethodOffset == offset; + bool isBranch = branchInstrOffsets.Contains(offset); + bool isFollowedByBranchTarget = i.Next != null && branchTargetOffsets.Contains(i.Next.Offset); + bool isEndOfMtd = endOfMethodOffset == offset; if (unreachableAfter == null && DoesNotReturn(i)) { unreachableAfter = i; } - var blockEnds = isBranch || isFollowedByBranchTarget || isEndOfMtd; + bool blockEnds = isBranch || isFollowedByBranchTarget || isEndOfMtd; if (blockEnds) { - var nextInstr = i.Next; + Instruction nextInstr = i.Next; // figure out all the different places the basic block could lead to ImmutableArray goesTo; @@ -702,7 +705,7 @@ private ImmutableArray CreateBasicBlocks(Collection ins { // it ends in a branch, where all does it branch? goesTo = ImmutableArray.Empty; - foreach (var branch in branchesAtLoc) + foreach (BranchInstruction branch in branchesAtLoc) { if (branch.HasMultiTargets) { @@ -725,30 +728,30 @@ private ImmutableArray CreateBasicBlocks(Collection ins goesTo = ImmutableArray.Empty; } - var exceptionSwitchesTo = ImmutableArray.Empty; + ImmutableArray exceptionSwitchesTo = ImmutableArray.Empty; // if the block is covered by any exception handlers then // it is possible that it will branch to its handler block - foreach (var handler in exceptionHandlers) + foreach (ExceptionHandler handler in exceptionHandlers) { - var tryStart = handler.TryStart.Offset; - var tryEnd = handler.TryEnd.Offset; + int tryStart = handler.TryStart.Offset; + int tryEnd = handler.TryEnd.Offset; - var containsStartOfTry = + bool containsStartOfTry = tryStart >= blockStartedAt.Value && tryStart <= i.Offset; - var containsEndOfTry = + bool containsEndOfTry = tryEnd >= blockStartedAt.Value && tryEnd <= i.Offset; - var blockInsideTry = blockStartedAt.Value >= tryStart && i.Offset <= tryEnd; + bool blockInsideTry = blockStartedAt.Value >= tryStart && i.Offset <= tryEnd; // blocks do not necessarily align to the TRY part of exception handlers, so we need to handle three cases: // - the try _starts_ in the block // - the try _ends_ in the block // - the try complete covers the block, but starts and ends before and after it (respectively) - var tryOverlapsBlock = containsStartOfTry || containsEndOfTry || blockInsideTry; + bool tryOverlapsBlock = containsStartOfTry || containsEndOfTry || blockInsideTry; if (!tryOverlapsBlock) { @@ -783,12 +786,12 @@ private ImmutableArray CreateBasicBlocks(Collection ins /// private bool DoesNotReturn(Instruction instr) { - if (!IsCall(instr, out var mtd)) + if (!IsCall(instr, out MethodReference mtd)) { return false; } - return DoesNotReturnMethods.Contains(mtd.MetadataToken); + return _doesNotReturnMethods.Contains(mtd.MetadataToken); } /// @@ -798,7 +801,7 @@ private bool DoesNotReturn(Instruction instr) /// private static bool IsCall(Instruction instr, out MethodReference mtd) { - var opcode = instr.OpCode; + OpCode opcode = instr.OpCode; if (opcode != OpCodes.Call && opcode != OpCodes.Callvirt) { mtd = null; diff --git a/src/coverlet.core/Properties/AssemblyInfo.cs b/src/coverlet.core/Properties/AssemblyInfo.cs index 8eb19aee7..7e65be514 100644 --- a/src/coverlet.core/Properties/AssemblyInfo.cs +++ b/src/coverlet.core/Properties/AssemblyInfo.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System.Reflection; using System.Runtime.CompilerServices; diff --git a/src/coverlet.core/Reporters/CoberturaReporter.cs b/src/coverlet.core/Reporters/CoberturaReporter.cs index e9e13c719..e3d299fda 100644 --- a/src/coverlet.core/Reporters/CoberturaReporter.cs +++ b/src/coverlet.core/Reporters/CoberturaReporter.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.Collections.Generic; using System.Diagnostics; @@ -21,42 +24,42 @@ internal class CoberturaReporter : IReporter public string Report(CoverageResult result, ISourceRootTranslator sourceRootTranslator) { - CoverageSummary summary = new CoverageSummary(); + var summary = new CoverageSummary(); - var lineCoverage = summary.CalculateLineCoverage(result.Modules); - var branchCoverage = summary.CalculateBranchCoverage(result.Modules); + CoverageDetails lineCoverage = summary.CalculateLineCoverage(result.Modules); + CoverageDetails branchCoverage = summary.CalculateBranchCoverage(result.Modules); - XDocument xml = new XDocument(); - XElement coverage = new XElement("coverage"); + var xml = new XDocument(); + var coverage = new XElement("coverage"); coverage.Add(new XAttribute("line-rate", (summary.CalculateLineCoverage(result.Modules).Percent / 100).ToString(CultureInfo.InvariantCulture))); coverage.Add(new XAttribute("branch-rate", (summary.CalculateBranchCoverage(result.Modules).Percent / 100).ToString(CultureInfo.InvariantCulture))); coverage.Add(new XAttribute("version", "1.9")); coverage.Add(new XAttribute("timestamp", (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds)); - XElement sources = new XElement("sources"); + var sources = new XElement("sources"); - List absolutePaths = new List(); + var absolutePaths = new List(); if (!result.Parameters.DeterministicReport) { absolutePaths = GetBasePaths(result.Modules, result.Parameters.UseSourceLink).ToList(); absolutePaths.ForEach(x => sources.Add(new XElement("source", x))); } - XElement packages = new XElement("packages"); - foreach (var module in result.Modules) + var packages = new XElement("packages"); + foreach (KeyValuePair module in result.Modules) { - XElement package = new XElement("package"); + var package = new XElement("package"); package.Add(new XAttribute("name", Path.GetFileNameWithoutExtension(module.Key))); package.Add(new XAttribute("line-rate", (summary.CalculateLineCoverage(module.Value).Percent / 100).ToString(CultureInfo.InvariantCulture))); package.Add(new XAttribute("branch-rate", (summary.CalculateBranchCoverage(module.Value).Percent / 100).ToString(CultureInfo.InvariantCulture))); package.Add(new XAttribute("complexity", summary.CalculateCyclomaticComplexity(module.Value))); - XElement classes = new XElement("classes"); - foreach (var document in module.Value) + var classes = new XElement("classes"); + foreach (KeyValuePair document in module.Value) { - foreach (var cls in document.Value) + foreach (KeyValuePair cls in document.Value) { - XElement @class = new XElement("class"); + var @class = new XElement("class"); @class.Add(new XAttribute("name", cls.Key)); string fileName; if (!result.Parameters.DeterministicReport) @@ -72,27 +75,27 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran @class.Add(new XAttribute("branch-rate", (summary.CalculateBranchCoverage(cls.Value).Percent / 100).ToString(CultureInfo.InvariantCulture))); @class.Add(new XAttribute("complexity", summary.CalculateCyclomaticComplexity(cls.Value))); - XElement classLines = new XElement("lines"); - XElement methods = new XElement("methods"); + var classLines = new XElement("lines"); + var methods = new XElement("methods"); - foreach (var meth in cls.Value) + foreach (KeyValuePair meth in cls.Value) { // Skip all methods with no lines if (meth.Value.Lines.Count == 0) continue; - XElement method = new XElement("method"); + var method = new XElement("method"); method.Add(new XAttribute("name", meth.Key.Split(':').Last().Split('(').First())); method.Add(new XAttribute("signature", "(" + meth.Key.Split(':').Last().Split('(').Last())); method.Add(new XAttribute("line-rate", (summary.CalculateLineCoverage(meth.Value.Lines).Percent / 100).ToString(CultureInfo.InvariantCulture))); method.Add(new XAttribute("branch-rate", (summary.CalculateBranchCoverage(meth.Value.Branches).Percent / 100).ToString(CultureInfo.InvariantCulture))); method.Add(new XAttribute("complexity", summary.CalculateCyclomaticComplexity(meth.Value.Branches))); - XElement lines = new XElement("lines"); - foreach (var ln in meth.Value.Lines) + var lines = new XElement("lines"); + foreach (KeyValuePair ln in meth.Value.Lines) { bool isBranchPoint = meth.Value.Branches.Any(b => b.Line == ln.Key); - XElement line = new XElement("line"); + var line = new XElement("line"); line.Add(new XAttribute("number", ln.Key.ToString())); line.Add(new XAttribute("hits", ln.Value.ToString())); line.Add(new XAttribute("branch", isBranchPoint.ToString())); @@ -100,15 +103,15 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran if (isBranchPoint) { var branches = meth.Value.Branches.Where(b => b.Line == ln.Key).ToList(); - var branchInfoCoverage = summary.CalculateBranchCoverage(branches); + CoverageDetails branchInfoCoverage = summary.CalculateBranchCoverage(branches); line.Add(new XAttribute("condition-coverage", $"{branchInfoCoverage.Percent.ToString(CultureInfo.InvariantCulture)}% ({branchInfoCoverage.Covered.ToString(CultureInfo.InvariantCulture)}/{branchInfoCoverage.Total.ToString(CultureInfo.InvariantCulture)})")); - XElement conditions = new XElement("conditions"); + var conditions = new XElement("conditions"); var byOffset = branches.GroupBy(b => b.Offset).ToDictionary(b => b.Key, b => b.ToList()); - foreach (var entry in byOffset) + foreach (KeyValuePair> entry in byOffset) { - XElement condition = new XElement("condition"); + var condition = new XElement("condition"); condition.Add(new XAttribute("number", entry.Key)); - condition.Add(new XAttribute("type", entry.Value.Count() > 2 ? "switch" : "jump")); // Just guessing here + condition.Add(new XAttribute("type", entry.Value.Count > 2 ? "switch" : "jump")); // Just guessing here condition.Add(new XAttribute("coverage", $"{summary.CalculateBranchCoverage(entry.Value).Percent.ToString(CultureInfo.InvariantCulture)}%")); conditions.Add(condition); } @@ -116,7 +119,6 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran line.Add(conditions); } - lines.Add(line); classLines.Add(line); } @@ -218,17 +220,14 @@ private static string GetRelativePathFromBase(IEnumerable basePaths, str return path; } - foreach (var basePath in basePaths) + foreach (string basePath in basePaths) { if (path.StartsWith(basePath)) { return path.Substring(basePath.Length); } } - - Debug.Assert(false, "Unexpected, we should find at least one path starts with one pre-build roots list"); - return path; } } -} \ No newline at end of file +} diff --git a/src/coverlet.core/Reporters/JsonReporter.cs b/src/coverlet.core/Reporters/JsonReporter.cs index bbcb4fa31..39ecc6e2f 100644 --- a/src/coverlet.core/Reporters/JsonReporter.cs +++ b/src/coverlet.core/Reporters/JsonReporter.cs @@ -1,7 +1,8 @@ -using Newtonsoft.Json; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. using Coverlet.Core.Abstractions; -using System; +using Newtonsoft.Json; namespace Coverlet.Core.Reporters { @@ -18,4 +19,4 @@ public string Report(CoverageResult result, ISourceRootTranslator _) return JsonConvert.SerializeObject(result.Modules, Formatting.Indented); } } -} \ No newline at end of file +} diff --git a/src/coverlet.core/Reporters/LcovReporter.cs b/src/coverlet.core/Reporters/LcovReporter.cs index f1ede2437..5b7471f42 100644 --- a/src/coverlet.core/Reporters/LcovReporter.cs +++ b/src/coverlet.core/Reporters/LcovReporter.cs @@ -1,7 +1,9 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; -using System.Linq; using System.Collections.Generic; - +using System.Linq; using Coverlet.Core.Abstractions; namespace Coverlet.Core.Reporters @@ -21,21 +23,21 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran throw new NotSupportedException("Deterministic report not supported by lcov reporter"); } - CoverageSummary summary = new CoverageSummary(); - List lcov = new List(); + var summary = new CoverageSummary(); + var lcov = new List(); - foreach (var module in result.Modules) + foreach (KeyValuePair module in result.Modules) { - foreach (var doc in module.Value) + foreach (KeyValuePair doc in module.Value) { - var docLineCoverage = summary.CalculateLineCoverage(doc.Value); - var docBranchCoverage = summary.CalculateBranchCoverage(doc.Value); - var docMethodCoverage = summary.CalculateMethodCoverage(doc.Value); + CoverageDetails docLineCoverage = summary.CalculateLineCoverage(doc.Value); + CoverageDetails docBranchCoverage = summary.CalculateBranchCoverage(doc.Value); + CoverageDetails docMethodCoverage = summary.CalculateMethodCoverage(doc.Value); lcov.Add("SF:" + doc.Key); - foreach (var @class in doc.Value) + foreach (KeyValuePair @class in doc.Value) { - foreach (var method in @class.Value) + foreach (KeyValuePair method in @class.Value) { // Skip all methods with no lines if (method.Value.Lines.Count == 0) @@ -44,10 +46,10 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran lcov.Add($"FN:{method.Value.Lines.First().Key - 1},{method.Key}"); lcov.Add($"FNDA:{method.Value.Lines.First().Value},{method.Key}"); - foreach (var line in method.Value.Lines) + foreach (KeyValuePair line in method.Value.Lines) lcov.Add($"DA:{line.Key},{line.Value}"); - foreach (var branch in method.Value.Branches) + foreach (BranchInfo branch in method.Value.Branches) { lcov.Add($"BRDA:{branch.Line},{branch.Offset},{branch.Path},{branch.Hits}"); } @@ -70,4 +72,4 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran return string.Join(Environment.NewLine, lcov); } } -} \ No newline at end of file +} diff --git a/src/coverlet.core/Reporters/OpenCoverReporter.cs b/src/coverlet.core/Reporters/OpenCoverReporter.cs index 6d4c7b6ba..2e42c2894 100644 --- a/src/coverlet.core/Reporters/OpenCoverReporter.cs +++ b/src/coverlet.core/Reporters/OpenCoverReporter.cs @@ -1,10 +1,12 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Xml.Linq; - using Coverlet.Core.Abstractions; namespace Coverlet.Core.Reporters @@ -24,63 +26,63 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran throw new NotSupportedException("Deterministic report not supported by openCover reporter"); } - CoverageSummary summary = new CoverageSummary(); - XDocument xml = new XDocument(); - XElement coverage = new XElement("CoverageSession"); - XElement coverageSummary = new XElement("Summary"); - XElement modules = new XElement("Modules"); + var summary = new CoverageSummary(); + var xml = new XDocument(); + var coverage = new XElement("CoverageSession"); + var coverageSummary = new XElement("Summary"); + var modules = new XElement("Modules"); int numClasses = 0, numMethods = 0; int visitedClasses = 0, visitedMethods = 0; int i = 1; - foreach (var mod in result.Modules) + foreach (System.Collections.Generic.KeyValuePair mod in result.Modules) { - XElement module = new XElement("Module"); + var module = new XElement("Module"); module.Add(new XAttribute("hash", Guid.NewGuid().ToString().ToUpper())); - XElement path = new XElement("ModulePath", mod.Key); - XElement time = new XElement("ModuleTime", DateTime.UtcNow.ToString("yyyy-MM-ddThh:mm:ss")); - XElement name = new XElement("ModuleName", Path.GetFileNameWithoutExtension(mod.Key)); + var path = new XElement("ModulePath", mod.Key); + var time = new XElement("ModuleTime", DateTime.UtcNow.ToString("yyyy-MM-ddThh:mm:ss")); + var name = new XElement("ModuleName", Path.GetFileNameWithoutExtension(mod.Key)); module.Add(path); module.Add(time); module.Add(name); - XElement files = new XElement("Files"); - XElement classes = new XElement("Classes"); + var files = new XElement("Files"); + var classes = new XElement("Classes"); - foreach (var doc in mod.Value) + foreach (System.Collections.Generic.KeyValuePair doc in mod.Value) { - XElement file = new XElement("File"); + var file = new XElement("File"); file.Add(new XAttribute("uid", i.ToString())); file.Add(new XAttribute("fullPath", doc.Key)); files.Add(file); - foreach (var cls in doc.Value) + foreach (System.Collections.Generic.KeyValuePair cls in doc.Value) { - XElement @class = new XElement("Class"); - XElement classSummary = new XElement("Summary"); + var @class = new XElement("Class"); + var classSummary = new XElement("Summary"); - XElement className = new XElement("FullName", cls.Key); + var className = new XElement("FullName", cls.Key); - XElement methods = new XElement("Methods"); + var methods = new XElement("Methods"); int j = 0; - var classVisited = false; + bool classVisited = false; - foreach (var meth in cls.Value) + foreach (System.Collections.Generic.KeyValuePair meth in cls.Value) { // Skip all methods with no lines if (meth.Value.Lines.Count == 0) continue; - var methLineCoverage = summary.CalculateLineCoverage(meth.Value.Lines); - var methBranchCoverage = summary.CalculateBranchCoverage(meth.Value.Branches); - var methCyclomaticComplexity = summary.CalculateCyclomaticComplexity(meth.Value.Branches); - var methNpathComplexity = summary.CalculateNpathComplexity(meth.Value.Branches); + CoverageDetails methLineCoverage = summary.CalculateLineCoverage(meth.Value.Lines); + CoverageDetails methBranchCoverage = summary.CalculateBranchCoverage(meth.Value.Branches); + int methCyclomaticComplexity = summary.CalculateCyclomaticComplexity(meth.Value.Branches); + int methNpathComplexity = summary.CalculateNpathComplexity(meth.Value.Branches); - XElement method = new XElement("Method"); + var method = new XElement("Method"); method.Add(new XAttribute("cyclomaticComplexity", methCyclomaticComplexity.ToString())); method.Add(new XAttribute("nPathComplexity", methCyclomaticComplexity.ToString())); @@ -91,12 +93,12 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran method.Add(new XAttribute("isSetter", meth.Key.Contains("set_").ToString())); method.Add(new XAttribute("isStatic", (!meth.Key.Contains("get_") || !meth.Key.Contains("set_")).ToString())); - XElement methodName = new XElement("Name", meth.Key); + var methodName = new XElement("Name", meth.Key); - XElement fileRef = new XElement("FileRef"); + var fileRef = new XElement("FileRef"); fileRef.Add(new XAttribute("uid", i.ToString())); - XElement methodPoint = new XElement("MethodPoint"); + var methodPoint = new XElement("MethodPoint"); methodPoint.Add(new XAttribute("vc", methLineCoverage.Covered.ToString())); methodPoint.Add(new XAttribute("uspid", "0")); methodPoint.Add(new XAttribute(XName.Get("type", "xsi"), "SequencePoint")); @@ -111,19 +113,19 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran methodPoint.Add(new XAttribute("fileid", i.ToString())); // They're really just lines - XElement sequencePoints = new XElement("SequencePoints"); - XElement branchPoints = new XElement("BranchPoints"); - XElement methodSummary = new XElement("Summary"); + var sequencePoints = new XElement("SequencePoints"); + var branchPoints = new XElement("BranchPoints"); + var methodSummary = new XElement("Summary"); int k = 0; int kBr = 0; - var methodVisited = false; + bool methodVisited = false; - foreach (var lines in meth.Value.Lines) + foreach (System.Collections.Generic.KeyValuePair lines in meth.Value.Lines) { - var lineBranches = meth.Value.Branches.Where(branchInfo => branchInfo.Line == lines.Key).ToArray(); - var branchCoverage = summary.CalculateBranchCoverage(lineBranches); - - XElement sequencePoint = new XElement("SequencePoint"); + BranchInfo[] lineBranches = meth.Value.Branches.Where(branchInfo => branchInfo.Line == lines.Key).ToArray(); + CoverageDetails branchCoverage = summary.CalculateBranchCoverage(lineBranches); + + var sequencePoint = new XElement("SequencePoint"); sequencePoint.Add(new XAttribute("vc", lines.Value.ToString())); sequencePoint.Add(new XAttribute("uspid", lines.Key.ToString())); sequencePoint.Add(new XAttribute("ordinal", k.ToString())); @@ -145,9 +147,9 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran k++; } - foreach (var branche in meth.Value.Branches) + foreach (BranchInfo branche in meth.Value.Branches) { - XElement branchPoint = new XElement("BranchPoint"); + var branchPoint = new XElement("BranchPoint"); branchPoint.Add(new XAttribute("vc", branche.Hits.ToString())); branchPoint.Add(new XAttribute("uspid", branche.Line.ToString())); branchPoint.Add(new XAttribute("ordinal", branche.Ordinal.ToString())); @@ -192,11 +194,11 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran if (classVisited) visitedClasses++; - var classLineCoverage = summary.CalculateLineCoverage(cls.Value); - var classBranchCoverage = summary.CalculateBranchCoverage(cls.Value); - var classMethodCoverage = summary.CalculateMethodCoverage(cls.Value); - var classMaxCyclomaticComplexity = summary.CalculateMaxCyclomaticComplexity(cls.Value); - var classMinCyclomaticComplexity = summary.CalculateMinCyclomaticComplexity(cls.Value); + CoverageDetails classLineCoverage = summary.CalculateLineCoverage(cls.Value); + CoverageDetails classBranchCoverage = summary.CalculateBranchCoverage(cls.Value); + CoverageDetails classMethodCoverage = summary.CalculateMethodCoverage(cls.Value); + int classMaxCyclomaticComplexity = summary.CalculateMaxCyclomaticComplexity(cls.Value); + int classMinCyclomaticComplexity = summary.CalculateMinCyclomaticComplexity(cls.Value); classSummary.Add(new XAttribute("numSequencePoints", classLineCoverage.Total.ToString())); classSummary.Add(new XAttribute("visitedSequencePoints", classLineCoverage.Covered.ToString())); @@ -224,10 +226,10 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran modules.Add(module); } - var moduleLineCoverage = summary.CalculateLineCoverage(result.Modules); - var moduleBranchCoverage = summary.CalculateBranchCoverage(result.Modules); - var moduleMaxCyclomaticComplexity = summary.CalculateMaxCyclomaticComplexity(result.Modules); - var moduleMinCyclomaticComplexity = summary.CalculateMinCyclomaticComplexity(result.Modules); + CoverageDetails moduleLineCoverage = summary.CalculateLineCoverage(result.Modules); + CoverageDetails moduleBranchCoverage = summary.CalculateBranchCoverage(result.Modules); + int moduleMaxCyclomaticComplexity = summary.CalculateMaxCyclomaticComplexity(result.Modules); + int moduleMinCyclomaticComplexity = summary.CalculateMinCyclomaticComplexity(result.Modules); coverageSummary.Add(new XAttribute("numSequencePoints", moduleLineCoverage.Total.ToString())); coverageSummary.Add(new XAttribute("visitedSequencePoints", moduleLineCoverage.Covered.ToString())); @@ -252,4 +254,4 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran return Encoding.UTF8.GetString(stream.ToArray()); } } -} \ No newline at end of file +} diff --git a/src/coverlet.core/Reporters/ReporterFactory.cs b/src/coverlet.core/Reporters/ReporterFactory.cs index 0d13520cb..2c9dc95ee 100644 --- a/src/coverlet.core/Reporters/ReporterFactory.cs +++ b/src/coverlet.core/Reporters/ReporterFactory.cs @@ -1,14 +1,16 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.Linq; - using Coverlet.Core.Abstractions; namespace Coverlet.Core.Reporters { internal class ReporterFactory { - private string _format; - private IReporter[] _reporters; + private readonly string _format; + private readonly IReporter[] _reporters; public ReporterFactory(string format) { @@ -28,4 +30,4 @@ public bool IsValidFormat() public IReporter CreateReporter() => _reporters.FirstOrDefault(r => string.Equals(r.Format, _format, StringComparison.OrdinalIgnoreCase)); } -} \ No newline at end of file +} diff --git a/src/coverlet.core/Reporters/TeamCityReporter.cs b/src/coverlet.core/Reporters/TeamCityReporter.cs index acb5d4ff1..8ccb0d997 100644 --- a/src/coverlet.core/Reporters/TeamCityReporter.cs +++ b/src/coverlet.core/Reporters/TeamCityReporter.cs @@ -1,7 +1,9 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using System.Globalization; using System.Text; - using Coverlet.Core.Abstractions; namespace Coverlet.Core.Reporters @@ -23,9 +25,9 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran // Calculate coverage var summary = new CoverageSummary(); - var overallLineCoverage = summary.CalculateLineCoverage(result.Modules); - var overallBranchCoverage = summary.CalculateBranchCoverage(result.Modules); - var overallMethodCoverage = summary.CalculateMethodCoverage(result.Modules); + CoverageDetails overallLineCoverage = summary.CalculateLineCoverage(result.Modules); + CoverageDetails overallBranchCoverage = summary.CalculateBranchCoverage(result.Modules); + CoverageDetails overallMethodCoverage = summary.CalculateMethodCoverage(result.Modules); // Report coverage var stringBuilder = new StringBuilder(); @@ -37,7 +39,7 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran return stringBuilder.ToString(); } - private void OutputLineCoverage(CoverageDetails coverageDetails, StringBuilder builder) + private static void OutputLineCoverage(CoverageDetails coverageDetails, StringBuilder builder) { // The number of covered lines OutputTeamCityServiceMessage("CodeCoverageAbsLCovered", coverageDetails.Covered, builder); @@ -46,7 +48,7 @@ private void OutputLineCoverage(CoverageDetails coverageDetails, StringBuilder b OutputTeamCityServiceMessage("CodeCoverageAbsLTotal", coverageDetails.Total, builder); } - private void OutputBranchCoverage(CoverageDetails coverageDetails, StringBuilder builder) + private static void OutputBranchCoverage(CoverageDetails coverageDetails, StringBuilder builder) { // The number of covered branches OutputTeamCityServiceMessage("CodeCoverageAbsBCovered", coverageDetails.Covered, builder); @@ -55,7 +57,7 @@ private void OutputBranchCoverage(CoverageDetails coverageDetails, StringBuilder OutputTeamCityServiceMessage("CodeCoverageAbsBTotal", coverageDetails.Total, builder); } - private void OutputMethodCoverage(CoverageDetails coverageDetails, StringBuilder builder) + private static void OutputMethodCoverage(CoverageDetails coverageDetails, StringBuilder builder) { // The number of covered methods OutputTeamCityServiceMessage("CodeCoverageAbsMCovered", coverageDetails.Covered, builder); @@ -64,7 +66,7 @@ private void OutputMethodCoverage(CoverageDetails coverageDetails, StringBuilder OutputTeamCityServiceMessage("CodeCoverageAbsMTotal", coverageDetails.Total, builder); } - private void OutputTeamCityServiceMessage(string key, double value, StringBuilder builder) + private static void OutputTeamCityServiceMessage(string key, double value, StringBuilder builder) { builder.AppendLine($"##teamcity[buildStatisticValue key='{key}' value='{value.ToString("0.##", new CultureInfo("en-US"))}']"); } diff --git a/src/coverlet.core/Symbols/BranchPoint.cs b/src/coverlet.core/Symbols/BranchPoint.cs index 077d569d2..77a675e82 100644 --- a/src/coverlet.core/Symbols/BranchPoint.cs +++ b/src/coverlet.core/Symbols/BranchPoint.cs @@ -1,6 +1,8 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.Diagnostics; -using System.Text.RegularExpressions; namespace Coverlet.Core.Symbols { diff --git a/src/coverlet.core/Symbols/CecilSymbolHelper.cs b/src/coverlet.core/Symbols/CecilSymbolHelper.cs index b6727de46..930d19ede 100644 --- a/src/coverlet.core/Symbols/CecilSymbolHelper.cs +++ b/src/coverlet.core/Symbols/CecilSymbolHelper.cs @@ -1,12 +1,12 @@ -// -// This class is based heavily on the work of the OpenCover -// team in OpenCover.Framework.Symbols.CecilSymbolManager -// +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; + using Coverlet.Core.Abstractions; using Coverlet.Core.Extensions; @@ -20,8 +20,8 @@ internal class CecilSymbolHelper : ICecilSymbolHelper { private const int StepOverLineCode = 0xFEEFEE; // Create single instance, we cannot collide because we use full method name as key - private readonly ConcurrentDictionary _compilerGeneratedBranchesToExclude = new ConcurrentDictionary(); - private readonly ConcurrentDictionary> _sequencePointOffsetToSkip = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary _compilerGeneratedBranchesToExclude = new(); + private readonly ConcurrentDictionary> _sequencePointOffsetToSkip = new(); // In case of nested compiler generated classes, only the root one presents the CompilerGenerated attribute. // So let's search up to the outermost declaring type to find the attribute @@ -83,7 +83,8 @@ private static bool IsMoveNextInsideEnumerator(MethodDefinition methodDefinition { return false; } - if (methodDefinition.DeclaringType.CustomAttributes.Count(ca => ca.AttributeType.FullName == typeof(CompilerGeneratedAttribute).FullName) > 0) + + if (methodDefinition.DeclaringType.CustomAttributes.Any(ca => ca.AttributeType.FullName == typeof(CompilerGeneratedAttribute).FullName)) { foreach (InterfaceImplementation implementedInterface in methodDefinition.DeclaringType.Interfaces) { @@ -198,6 +199,7 @@ instruction.Previous.Operand is MethodReference operand && operand.DeclaringType.Scope.Name == "System.Runtime" || operand.DeclaringType.Scope.Name == "netstandard" || operand.DeclaringType.Scope.Name == "mscorlib" || + operand.DeclaringType.Scope.Name == "System.Threading.Tasks" || operand.DeclaringType.Scope.Name == "System.Threading.Tasks.Extensions" ) ) @@ -272,10 +274,10 @@ private static bool SkipGeneratedBranchForExceptionRethrown(List in int branchIndex = instructions.BinarySearch(instruction, new InstructionByOffsetComparer()); - return new[] {2,3}.Any(x => branchIndex >= x && - instructions[branchIndex - x].OpCode == OpCodes.Isinst && - instructions[branchIndex - x].Operand is TypeReference tr && - tr.FullName == "System.Exception") + return new[] { 2, 3 }.Any(x => branchIndex >= x && + instructions[branchIndex - x].OpCode == OpCodes.Isinst && + instructions[branchIndex - x].Operand is TypeReference tr && + tr.FullName == "System.Exception") && // check for throw opcode after branch instructions.Count - branchIndex >= 3 && @@ -399,11 +401,11 @@ private bool SkipGeneratedBranchesForExceptionHandlers(MethodDefinition methodDe */ if (!_compilerGeneratedBranchesToExclude.ContainsKey(methodDefinition.FullName)) { - List detectedBranches = new List(); + var detectedBranches = new List(); Collection handlers = methodDefinition.Body.ExceptionHandlers; int numberOfCatchBlocks = 1; - foreach (var handler in handlers) + foreach (ExceptionHandler handler in handlers) { if (handlers.Any(h => h.HandlerStart == handler.HandlerEnd)) { @@ -465,7 +467,6 @@ private static bool SkipGeneratedBranchesForAwaitForeach(List instr CheckIfExceptionThrown(instructions, instruction, currentIndex) || CheckThrownExceptionType(instructions, instruction, currentIndex); - // The pattern for the "should we stay in the loop or not?", which we don't // want to skip (so we have no method to try to find it), looks like this: // @@ -477,7 +478,6 @@ private static bool SkipGeneratedBranchesForAwaitForeach(List instr // the "call" and branch, but it's the same idea either way: branch // if GetResult() returned true. - static bool CheckForAsyncEnumerator(List instructions, Instruction instruction, int currentIndex) { // We're looking for the following pattern, which checks whether a @@ -497,8 +497,11 @@ static bool CheckForAsyncEnumerator(List instructions, Instruction (instructions[currentIndex - 2].OpCode == OpCodes.Ldarg || instructions[currentIndex - 2].OpCode == OpCodes.Ldarg_0) && instructions[currentIndex - 1].OpCode == OpCodes.Ldfld && - instructions[currentIndex - 1].Operand is FieldDefinition field && - IsCompilerGenerated(field) && field.FieldType.FullName.StartsWith("System.Collections.Generic.IAsyncEnumerator")) + ( + (instructions[currentIndex - 1].Operand is FieldDefinition field && IsCompilerGenerated(field) && field.FieldType.FullName.StartsWith("System.Collections.Generic.IAsyncEnumerator")) || + (instructions[currentIndex - 1].Operand is FieldReference fieldRef && IsCompilerGenerated(fieldRef.Resolve()) && fieldRef.FieldType.FullName.StartsWith("System.Collections.Generic.IAsyncEnumerator")) + ) + ) { return true; } @@ -506,7 +509,6 @@ static bool CheckForAsyncEnumerator(List instructions, Instruction return false; } - static bool CheckIfExceptionThrown(List instructions, Instruction instruction, int currentIndex) { // Here, we want to find a pattern where we're checking whether a @@ -540,8 +542,10 @@ static bool CheckIfExceptionThrown(List instructions, Instruction i for (int i = currentIndex - 1; i >= minFieldIndex; --i) { if (instructions[i].OpCode == OpCodes.Ldfld && - instructions[i].Operand is FieldDefinition field && - IsCompilerGenerated(field) && field.FieldType.FullName == "System.Object") + ( + (instructions[i].Operand is FieldDefinition field && IsCompilerGenerated(field) && field.FieldType.FullName == "System.Object") || + (instructions[i].Operand is FieldReference fieldRef && IsCompilerGenerated(fieldRef.Resolve()) && fieldRef.FieldType.FullName == "System.Object") + )) { // We expect the call to GetResult() to be no more than four // instructions before the loading of the field's value. @@ -564,7 +568,6 @@ instructions[j].Operand is MethodReference callRef && return false; } - static bool CheckThrownExceptionType(List instructions, Instruction instruction, int currentIndex) { // In this case, we're looking for a branch generated by the compiler to @@ -617,7 +620,6 @@ private static bool SkipGeneratedBranchesForAwaitUsing(List instruc return CheckForSkipDisposal(instructions, instruction, currentIndex) || CheckForCleanup(instructions, instruction, currentIndex); - static bool CheckForSkipDisposal(List instructions, Instruction instruction, int currentIndex) { // The async state machine generated for an "await using" contains a branch @@ -663,7 +665,7 @@ static bool CheckForSkipDisposal(List instructions, Instruction ins { return false; } - + bool isFollowedByDisposeAsync = false; if (instructions[currentIndex - 1].OpCode == OpCodes.Ldfld && @@ -727,7 +729,6 @@ instructions[i].Operand is FieldDefinition reloadedField && return false; } - static bool CheckForCleanup(List instructions, Instruction instruction, int currentIndex) { // The pattern we're looking for here is this: @@ -812,7 +813,6 @@ private static bool SkipGeneratedBranchesForAsyncIterator(List inst return CheckForStateSwitch(instructions, instruction, currentIndex) || DisposeCheck(instructions, instruction, currentIndex); - static bool CheckForStateSwitch(List instructions, Instruction instruction, int currentIndex) { // The pattern we're looking for here is this one: @@ -878,8 +878,10 @@ static bool DisposeCheck(List instructions, Instruction instruction if (currentIndex >= 2 && instructions[currentIndex - 1].OpCode == OpCodes.Ldfld && - instructions[currentIndex - 1].Operand is FieldDefinition field && - IsCompilerGenerated(field) && field.FullName.EndsWith("__disposeMode") && + ( + (instructions[currentIndex - 1].Operand is FieldDefinition field && IsCompilerGenerated(field) && field.FullName.EndsWith("__disposeMode")) || + (instructions[currentIndex - 1].Operand is FieldReference fieldRef && IsCompilerGenerated(fieldRef.Resolve()) && fieldRef.FullName.EndsWith("__disposeMode")) + ) && (instructions[currentIndex - 2].OpCode == OpCodes.Ldarg || instructions[currentIndex - 2].OpCode == OpCodes.Ldarg_0)) { @@ -890,7 +892,7 @@ static bool DisposeCheck(List instructions, Instruction instruction } } - private bool SkipGeneratedBranchesForEnumeratorCancellationAttribute(List instructions, Instruction instruction) + private static bool SkipGeneratedBranchesForEnumeratorCancellationAttribute(List instructions, Instruction instruction) { // For async-enumerable methods an additional cancellation token despite the default one can be passed. // The EnumeratorCancellation attribute marks the parameter whose value is received by GetAsyncEnumerator(CancellationToken). @@ -1021,13 +1023,13 @@ public IReadOnlyList GetBranchPoints(MethodDefinition methodDefinit continue; } - var pathCounter = 0; + int pathCounter = 0; // store branch origin offset - var branchOffset = instruction.Offset; - var closestSeqPt = FindClosestInstructionWithSequencePoint(methodDefinition.Body, instruction).Maybe(i => methodDefinition.DebugInformation.GetSequencePoint(i)); - var branchingInstructionLine = closestSeqPt.Maybe(x => x.StartLine, -1); - var document = closestSeqPt.Maybe(x => x.Document.Url); + int branchOffset = instruction.Offset; + SequencePoint closestSeqPt = FindClosestInstructionWithSequencePoint(methodDefinition.Body, instruction).Maybe(i => methodDefinition.DebugInformation.GetSequencePoint(i)); + int branchingInstructionLine = closestSeqPt.Maybe(x => x.StartLine, -1); + string document = closestSeqPt.Maybe(x => x.Document.Url); if (instruction.Next == null) { @@ -1054,9 +1056,9 @@ private static bool BuildPointsForConditionalBranch(List list, Inst // Add Default branch (Path=0) // Follow else/default instruction - var @else = instruction.Next; + Instruction @else = instruction.Next; - var pathOffsetList = GetBranchPath(@else); + List pathOffsetList = GetBranchPath(@else); // add Path 0 var path0 = new BranchPoint @@ -1077,8 +1079,7 @@ private static bool BuildPointsForConditionalBranch(List list, Inst if (instruction.OpCode.Code != Code.Switch) { // Follow instruction at operand - var @then = instruction.Operand as Instruction; - if (@then == null) + if (instruction.Operand is not Instruction @then) return false; ordinal = BuildPointsForBranch(list, then, branchingInstructionLine, document, branchOffset, @@ -1086,8 +1087,7 @@ private static bool BuildPointsForConditionalBranch(List list, Inst } else // instruction.OpCode.Code == Code.Switch { - var branchInstructions = instruction.Operand as Instruction[]; - if (branchInstructions == null || branchInstructions.Length == 0) + if (instruction.Operand is not Instruction[] branchInstructions || branchInstructions.Length == 0) return false; ordinal = BuildPointsForSwitchCases(list, path0, branchInstructions, branchingInstructionLine, @@ -1099,7 +1099,7 @@ private static bool BuildPointsForConditionalBranch(List list, Inst private static uint BuildPointsForBranch(List list, Instruction then, int branchingInstructionLine, string document, int branchOffset, uint ordinal, int pathCounter, BranchPoint path0, List instructions, MethodDefinition methodDefinition) { - var pathOffsetList1 = GetBranchPath(@then); + List pathOffsetList1 = GetBranchPath(@then); // Add path 1 var path1 = new BranchPoint @@ -1119,7 +1119,7 @@ private static uint BuildPointsForBranch(List list, Instruction the // only add branch if branch does not match a known sequence // e.g. auto generated field assignment // or encapsulates at least one sequence point - var offsets = new[] + int[] offsets = new[] { path0.Offset, path0.EndOffset, @@ -1127,22 +1127,22 @@ private static uint BuildPointsForBranch(List list, Instruction the path1.EndOffset }; - var ignoreSequences = new[] + Code[][] ignoreSequences = new[] { // we may need other samples new[] {Code.Brtrue_S, Code.Pop, Code.Ldsfld, Code.Ldftn, Code.Newobj, Code.Dup, Code.Stsfld, Code.Newobj}, // CachedAnonymousMethodDelegate field allocation }; - var bs = offsets.Min(); - var be = offsets.Max(); + int bs = offsets.Min(); + int be = offsets.Max(); var range = instructions.Where(i => (i.Offset >= bs) && (i.Offset <= be)).ToList(); - var match = ignoreSequences + bool match = ignoreSequences .Where(ignoreSequence => range.Count >= ignoreSequence.Length) .Any(ignoreSequence => range.Zip(ignoreSequence, (instruction, code) => instruction.OpCode.Code == code).All(x => x)); - var count = range + int count = range .Count(i => methodDefinition.DebugInformation.GetSequencePoint(i) != null); if (!match || count > 0) @@ -1156,7 +1156,7 @@ private static uint BuildPointsForBranch(List list, Instruction the private static uint BuildPointsForSwitchCases(List list, BranchPoint path0, Instruction[] branchInstructions, int branchingInstructionLine, string document, int branchOffset, uint ordinal, ref int pathCounter) { - var counter = pathCounter; + int counter = pathCounter; list.Add(path0); // Add Conditional Branches (Path>0) list.AddRange(branchInstructions.Select(GetBranchPath) @@ -1342,7 +1342,7 @@ instance void .ctor () cil managed } ... */ - var autogeneratedBackingFields = methodDefinition.DeclaringType.Fields.Where(x => + IEnumerable autogeneratedBackingFields = methodDefinition.DeclaringType.Fields.Where(x => x.CustomAttributes.Any(ca => ca.AttributeType.FullName.Equals(typeof(CompilerGeneratedAttribute).FullName)) && x.FullName.EndsWith("k__BackingField")); @@ -1354,15 +1354,15 @@ instance void .ctor () cil managed private static bool SkipDefaultInitializationSystemObject(Instruction instruction) { - /* - A type always has a constructor with a default instantiation of System.Object. For record types these - instructions can have a own sequence point. This means that even the default constructor would be instrumented. - To skip this we search for call instructions with a method reference that declares System.Object. + /* + A type always has a constructor with a default instantiation of System.Object. For record types these + instructions can have a own sequence point. This means that even the default constructor would be instrumented. + To skip this we search for call instructions with a method reference that declares System.Object. - IL_0000: ldarg.0 - IL_0001: call instance void [System.Runtime]System.Object::.ctor() - IL_0006: ret - */ + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: ret + */ return instruction.OpCode == OpCodes.Ldarg && instruction.Next?.OpCode == OpCodes.Call && instruction.Next?.Operand is MethodReference mr && mr.DeclaringType.FullName.Equals(typeof(System.Object).FullName); @@ -1378,7 +1378,7 @@ private static bool SkipBranchGeneratedExceptionFilter(Instruction branchInstruc .Where(e => e.HandlerType == ExceptionHandlerType.Filter) .ToList(); - foreach (var exceptionHandler in handlers) + foreach (ExceptionHandler exceptionHandler in handlers) { Instruction startFilter = exceptionHandler.FilterStart; Instruction endFilter = startFilter; @@ -1418,7 +1418,7 @@ private static bool SkipBranchGeneratedFinallyBlock(Instruction branchInstructio private static int GetOffsetOfNextEndfinally(MethodBody body, int startOffset) { - var lastOffset = body.Instructions.LastOrDefault().Maybe(i => i.Offset, int.MaxValue); + int lastOffset = body.Instructions.LastOrDefault().Maybe(i => i.Offset, int.MaxValue); return body.Instructions.FirstOrDefault(i => i.Offset >= startOffset && i.OpCode.Code == Code.Endfinally).Maybe(i => i.Offset, lastOffset); } @@ -1428,12 +1428,11 @@ private static List GetBranchPath(Instruction instruction) if (instruction != null) { - var point = instruction; + Instruction point = instruction; offsetList.Add(point.Offset); while (point.OpCode == OpCodes.Br || point.OpCode == OpCodes.Br_S) { - var nextPoint = point.Operand as Instruction; - if (nextPoint != null) + if (point.Operand is Instruction nextPoint) { point = nextPoint; offsetList.Add(point.Offset); @@ -1453,12 +1452,12 @@ private static Instruction FindClosestInstructionWithSequencePoint(MethodBody me var sequencePointsInMethod = methodBody.Instructions.Where(i => HasValidSequencePoint(i, methodBody.Method)).ToList(); if (!sequencePointsInMethod.Any()) return null; - var idx = sequencePointsInMethod.BinarySearch(instruction, new InstructionByOffsetComparer()); + int idx = sequencePointsInMethod.BinarySearch(instruction, new InstructionByOffsetComparer()); Instruction prev; if (idx < 0) { // no exact match, idx corresponds to the next, larger element - var lower = Math.Max(~idx - 1, 0); + int lower = Math.Max(~idx - 1, 0); prev = sequencePointsInMethod[lower]; } else @@ -1472,7 +1471,7 @@ private static Instruction FindClosestInstructionWithSequencePoint(MethodBody me private static bool HasValidSequencePoint(Instruction instruction, MethodDefinition methodDefinition) { - var sp = methodDefinition.DebugInformation.GetSequencePoint(instruction); + SequencePoint sp = methodDefinition.DebugInformation.GetSequencePoint(instruction); return sp != null && sp.StartLine != StepOverLineCode; } diff --git a/src/coverlet.core/coverlet.core.csproj b/src/coverlet.core/coverlet.core.csproj index 7ef394311..93a6c972d 100644 --- a/src/coverlet.core/coverlet.core.csproj +++ b/src/coverlet.core/coverlet.core.csproj @@ -3,7 +3,6 @@ Library netstandard2.0 - 5.7.2 false @@ -14,7 +13,6 @@ - diff --git a/src/coverlet.msbuild.tasks/BaseTask.cs b/src/coverlet.msbuild.tasks/BaseTask.cs index 1cf3aa6ce..99606bda8 100644 --- a/src/coverlet.msbuild.tasks/BaseTask.cs +++ b/src/coverlet.msbuild.tasks/BaseTask.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using Microsoft.Build.Utilities; namespace Coverlet.MSbuild.Tasks diff --git a/src/coverlet.msbuild.tasks/CoverageResultTask.cs b/src/coverlet.msbuild.tasks/CoverageResultTask.cs index ebc17a0e4..23a29f7a5 100644 --- a/src/coverlet.msbuild.tasks/CoverageResultTask.cs +++ b/src/coverlet.msbuild.tasks/CoverageResultTask.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.Collections.Generic; using System.Globalization; @@ -17,7 +20,7 @@ namespace Coverlet.MSbuild.Tasks { public class CoverageResultTask : BaseTask { - private MSBuildLogger _logger; + private readonly MSBuildLogger _logger; [Required] public string Output { get; set; } @@ -63,11 +66,11 @@ public override bool Execute() Coverage coverage = null; using (Stream instrumenterStateStream = fileSystem.NewFileStream(InstrumenterState.ItemSpec, FileMode.Open)) { - var instrumentationHelper = ServiceProvider.GetService(); + IInstrumentationHelper instrumentationHelper = ServiceProvider.GetService(); // Task.Log is teared down after a task and thus the new MSBuildLogger must be passed to the InstrumentationHelper // https://github.com/microsoft/msbuild/issues/5153 instrumentationHelper.SetLogger(_logger); - coverage = new Coverage(CoveragePrepareResult.Deserialize(instrumenterStateStream), this._logger, ServiceProvider.GetService(), fileSystem, ServiceProvider.GetService()); + coverage = new Coverage(CoveragePrepareResult.Deserialize(instrumenterStateStream), _logger, ServiceProvider.GetService(), fileSystem, ServiceProvider.GetService()); } try @@ -82,7 +85,7 @@ public override bool Execute() CoverageResult result = coverage.GetCoverageResult(); - var directory = Path.GetDirectoryName(Output); + string directory = Path.GetDirectoryName(Output); if (directory == string.Empty) { directory = Directory.GetCurrentDirectory(); @@ -92,12 +95,12 @@ public override bool Execute() Directory.CreateDirectory(directory); } - var formats = OutputFormat.Split(','); + string[] formats = OutputFormat.Split(','); var coverageReportPaths = new List(formats.Length); ISourceRootTranslator sourceRootTranslator = ServiceProvider.GetService(); - foreach (var format in formats) + foreach (string format in formats) { - var reporter = new ReporterFactory(format).CreateReporter(); + IReporter reporter = new ReporterFactory(format).CreateReporter(); if (reporter == null) { throw new Exception($"Specified output format '{format}' is not supported"); @@ -119,7 +122,7 @@ public override bool Execute() ServiceProvider.GetService(), result, sourceRootTranslator); - var path = writer.WriteReport(); + string path = writer.WriteReport(); var metadata = new Dictionary { ["Format"] = format }; coverageReportPaths.Add(new TaskItem(path, metadata)); } @@ -129,7 +132,7 @@ public override bool Execute() var thresholdTypeFlagQueue = new Queue(); - foreach (var thresholdType in ThresholdType.Split(',').Select(t => t.Trim())) + foreach (string thresholdType in ThresholdType.Split(',').Select(t => t.Trim())) { if (thresholdType.Equals("line", StringComparison.OrdinalIgnoreCase)) { @@ -144,19 +147,19 @@ public override bool Execute() thresholdTypeFlagQueue.Enqueue(ThresholdTypeFlags.Method); } } - - Dictionary thresholdTypeFlagValues = new Dictionary(); + + var thresholdTypeFlagValues = new Dictionary(); if (Threshold.Contains(',')) { - var thresholdValues = Threshold.Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries).Select(t => t.Trim()); - if(thresholdValues.Count() != thresholdTypeFlagQueue.Count()) + IEnumerable thresholdValues = Threshold.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(t => t.Trim()); + if (thresholdValues.Count() != thresholdTypeFlagQueue.Count) { - throw new Exception($"Threshold type flag count ({thresholdTypeFlagQueue.Count()}) and values count ({thresholdValues.Count()}) doesn't match"); + throw new Exception($"Threshold type flag count ({thresholdTypeFlagQueue.Count}) and values count ({thresholdValues.Count()}) doesn't match"); } - foreach (var threshold in thresholdValues) + foreach (string threshold in thresholdValues) { - if (double.TryParse(threshold, out var value)) + if (double.TryParse(threshold, out double value)) { thresholdTypeFlagValues[thresholdTypeFlagQueue.Dequeue()] = value; } @@ -175,8 +178,8 @@ public override bool Execute() thresholdTypeFlagValues[thresholdTypeFlagQueue.Dequeue()] = thresholdValue; } } - - var thresholdStat = ThresholdStatistic.Minimum; + + ThresholdStatistic thresholdStat = ThresholdStatistic.Minimum; if (ThresholdStat.Equals("average", StringComparison.OrdinalIgnoreCase)) { thresholdStat = ThresholdStatistic.Average; @@ -189,23 +192,23 @@ public override bool Execute() var coverageTable = new ConsoleTable("Module", "Line", "Branch", "Method"); var summary = new CoverageSummary(); - var linePercentCalculation = summary.CalculateLineCoverage(result.Modules); - var branchPercentCalculation = summary.CalculateBranchCoverage(result.Modules); - var methodPercentCalculation = summary.CalculateMethodCoverage(result.Modules); + CoverageDetails linePercentCalculation = summary.CalculateLineCoverage(result.Modules); + CoverageDetails branchPercentCalculation = summary.CalculateBranchCoverage(result.Modules); + CoverageDetails methodPercentCalculation = summary.CalculateMethodCoverage(result.Modules); - var totalLinePercent = linePercentCalculation.Percent; - var totalBranchPercent = branchPercentCalculation.Percent; - var totalMethodPercent = methodPercentCalculation.Percent; + double totalLinePercent = linePercentCalculation.Percent; + double totalBranchPercent = branchPercentCalculation.Percent; + double totalMethodPercent = methodPercentCalculation.Percent; - var averageLinePercent = linePercentCalculation.AverageModulePercent; - var averageBranchPercent = branchPercentCalculation.AverageModulePercent; - var averageMethodPercent = methodPercentCalculation.AverageModulePercent; + double averageLinePercent = linePercentCalculation.AverageModulePercent; + double averageBranchPercent = branchPercentCalculation.AverageModulePercent; + double averageMethodPercent = methodPercentCalculation.AverageModulePercent; - foreach (var module in result.Modules) + foreach (KeyValuePair module in result.Modules) { - var linePercent = summary.CalculateLineCoverage(module.Value).Percent; - var branchPercent = summary.CalculateBranchCoverage(module.Value).Percent; - var methodPercent = summary.CalculateMethodCoverage(module.Value).Percent; + double linePercent = summary.CalculateLineCoverage(module.Value).Percent; + double branchPercent = summary.CalculateBranchCoverage(module.Value).Percent; + double methodPercent = summary.CalculateMethodCoverage(module.Value).Percent; coverageTable.AddRow(Path.GetFileNameWithoutExtension(module.Key), $"{InvariantFormat(linePercent)}%", $"{InvariantFormat(branchPercent)}%", $"{InvariantFormat(methodPercent)}%"); } @@ -222,7 +225,7 @@ public override bool Execute() Console.WriteLine(coverageTable.ToStringAlternative()); - var thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, thresholdTypeFlagValues, thresholdStat); + ThresholdTypeFlags thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, thresholdTypeFlagValues, thresholdStat); if (thresholdTypeFlags != ThresholdTypeFlags.None) { var exceptionMessageBuilder = new StringBuilder(); diff --git a/src/coverlet.msbuild.tasks/InstrumentationTask.cs b/src/coverlet.msbuild.tasks/InstrumentationTask.cs index 9be72b5c6..9a0506f51 100644 --- a/src/coverlet.msbuild.tasks/InstrumentationTask.cs +++ b/src/coverlet.msbuild.tasks/InstrumentationTask.cs @@ -1,7 +1,9 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using System.Diagnostics; using System.IO; - using Coverlet.Core; using Coverlet.Core.Abstractions; using Coverlet.Core.Helpers; @@ -9,6 +11,7 @@ using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using Microsoft.Extensions.DependencyInjection; + using ILogger = Coverlet.Core.Abstractions.ILogger; namespace Coverlet.MSbuild.Tasks @@ -44,6 +47,8 @@ public class InstrumentationTask : BaseTask public bool DeterministicReport { get; set; } + public string ExcludeAssembliesWithoutSources { get; set; } + [Output] public ITaskItem InstrumenterState { get; set; } @@ -68,11 +73,13 @@ public override bool Execute() IServiceCollection serviceCollection = new ServiceCollection(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); + serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(_ => _logger); serviceCollection.AddTransient(); // We cache resolutions - serviceCollection.AddSingleton(serviceProvider => new SourceRootTranslator(Path, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService())); + serviceCollection.AddSingleton(serviceProvider => + new SourceRootTranslator(Path, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService())); // We need to keep singleton/static semantics serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); @@ -81,9 +88,9 @@ public override bool Execute() try { - var fileSystem = ServiceProvider.GetService(); + IFileSystem fileSystem = ServiceProvider.GetService(); - CoverageParameters parameters = new CoverageParameters + var parameters = new CoverageParameters { IncludeFilters = Include?.Split(','), IncludeDirectories = IncludeDirectory?.Split(','), @@ -96,10 +103,11 @@ public override bool Execute() UseSourceLink = UseSourceLink, SkipAutoProps = SkipAutoProps, DeterministicReport = DeterministicReport, + ExcludeAssembliesWithoutSources = ExcludeAssembliesWithoutSources, DoesNotReturnAttributes = DoesNotReturnAttribute?.Split(',') }; - Coverage coverage = new Coverage(Path, + var coverage = new Coverage(Path, parameters, _logger, ServiceProvider.GetService(), @@ -109,13 +117,9 @@ public override bool Execute() CoveragePrepareResult prepareResult = coverage.PrepareModules(); InstrumenterState = new TaskItem(System.IO.Path.GetTempFileName()); - using (var instrumentedStateFile = fileSystem.NewFileStream(InstrumenterState.ItemSpec, FileMode.Open, FileAccess.Write)) - { - using (Stream serializedState = CoveragePrepareResult.Serialize(prepareResult)) - { - serializedState.CopyTo(instrumentedStateFile); - } - } + using Stream instrumentedStateFile = fileSystem.NewFileStream(InstrumenterState.ItemSpec, FileMode.Open, FileAccess.Write); + using Stream serializedState = CoveragePrepareResult.Serialize(prepareResult); + serializedState.CopyTo(instrumentedStateFile); } catch (Exception ex) { diff --git a/src/coverlet.msbuild.tasks/MSBuildLogger.cs b/src/coverlet.msbuild.tasks/MSBuildLogger.cs index e35f0af82..33fe3afaf 100644 --- a/src/coverlet.msbuild.tasks/MSBuildLogger.cs +++ b/src/coverlet.msbuild.tasks/MSBuildLogger.cs @@ -1,5 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using ILogger = Coverlet.Core.Abstractions.ILogger; diff --git a/src/coverlet.msbuild.tasks/Properties/AssemblyInfo.cs b/src/coverlet.msbuild.tasks/Properties/AssemblyInfo.cs index 944948378..4ff5754c3 100644 --- a/src/coverlet.msbuild.tasks/Properties/AssemblyInfo.cs +++ b/src/coverlet.msbuild.tasks/Properties/AssemblyInfo.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System.Reflection; using System.Runtime.CompilerServices; diff --git a/src/coverlet.msbuild.tasks/ReportWriter.cs b/src/coverlet.msbuild.tasks/ReportWriter.cs index 1261ce76c..8b72b9767 100644 --- a/src/coverlet.msbuild.tasks/ReportWriter.cs +++ b/src/coverlet.msbuild.tasks/ReportWriter.cs @@ -1,8 +1,9 @@ -using System.IO; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.IO; using Coverlet.Core; using Coverlet.Core.Abstractions; -using Coverlet.Core.Reporters; namespace Coverlet.MSbuild.Tasks { diff --git a/src/coverlet.msbuild.tasks/coverlet.msbuild.props b/src/coverlet.msbuild.tasks/coverlet.msbuild.props index 3821845ec..9356dbdf7 100644 --- a/src/coverlet.msbuild.tasks/coverlet.msbuild.props +++ b/src/coverlet.msbuild.tasks/coverlet.msbuild.props @@ -16,6 +16,7 @@ 0 line,branch,method minimum + $(MSBuildThisFileDirectory) diff --git a/src/coverlet.msbuild.tasks/coverlet.msbuild.targets b/src/coverlet.msbuild.tasks/coverlet.msbuild.targets index c271ec2d1..ed288de59 100644 --- a/src/coverlet.msbuild.tasks/coverlet.msbuild.targets +++ b/src/coverlet.msbuild.tasks/coverlet.msbuild.targets @@ -24,7 +24,7 @@ <_mapping Include="@(_byProject->'%(Identity)|%(OriginalPath)=%(MappedPath)')" /> - <_sourceRootMappingFilePath>$([MSBuild]::EnsureTrailingSlash('$(OutputPath)'))CoverletSourceRootsMapping + <_sourceRootMappingFilePath>$([MSBuild]::EnsureTrailingSlash('$(OutputPath)'))CoverletSourceRootsMapping_$(AssemblyName) + DoesNotReturnAttribute="$(DoesNotReturnAttribute)" + ExcludeAssembliesWithoutSources="$(ExcludeAssembliesWithoutSources)"> diff --git a/test/coverlet.collector.tests/AttachmentManagerTests.cs b/test/coverlet.collector.tests/AttachmentManagerTests.cs index 9adb4c731..9e021212d 100644 --- a/test/coverlet.collector.tests/AttachmentManagerTests.cs +++ b/test/coverlet.collector.tests/AttachmentManagerTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using System.ComponentModel; using System.IO; using Microsoft.VisualStudio.TestPlatform.ObjectModel; @@ -14,14 +17,14 @@ namespace Coverlet.Collector.Tests public class AttachmentManagerTests { private AttachmentManager _attachmentManager; - private Mock _mockDataCollectionSink; - private DataCollectionContext _dataCollectionContext; - private TestPlatformLogger _testPlatformLogger; - private TestPlatformEqtTrace _eqtTrace; - private Mock _mockFileHelper; - private Mock _mockDirectoryHelper; - private Mock _mockCountDownEvent; - private Mock _mockDataCollectionLogger; + private readonly Mock _mockDataCollectionSink; + private readonly DataCollectionContext _dataCollectionContext; + private readonly TestPlatformLogger _testPlatformLogger; + private readonly TestPlatformEqtTrace _eqtTrace; + private readonly Mock _mockFileHelper; + private readonly Mock _mockDirectoryHelper; + private readonly Mock _mockCountDownEvent; + private readonly Mock _mockDataCollectionLogger; public AttachmentManagerTests() { @@ -71,7 +74,7 @@ public void SendCoverageReportShouldThrowExceptionWhenFailedToSaveReportToFile() [Fact] public void SendCoverageReportShouldSendAttachmentToTestPlatform() { - var directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); + DirectoryInfo directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); _attachmentManager = new AttachmentManager(_mockDataCollectionSink.Object, _dataCollectionContext, _testPlatformLogger, _eqtTrace, directory.ToString(), new FileHelper(), _mockDirectoryHelper.Object, _mockCountDownEvent.Object); diff --git a/test/coverlet.collector.tests/CoverletCoverageDataCollectorTests.cs b/test/coverlet.collector.tests/CoverletCoverageDataCollectorTests.cs index 3c0346d20..6bb1af73b 100644 --- a/test/coverlet.collector.tests/CoverletCoverageDataCollectorTests.cs +++ b/test/coverlet.collector.tests/CoverletCoverageDataCollectorTests.cs @@ -1,9 +1,11 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; using Moq; using Microsoft.VisualStudio.TestPlatform.ObjectModel; @@ -22,29 +24,32 @@ namespace Coverlet.Collector.Tests { public class CoverletCoverageDataCollectorTests { - private DataCollectionEnvironmentContext _context; + private readonly DataCollectionEnvironmentContext _context; private CoverletCoverageCollector _coverletCoverageDataCollector; - private DataCollectionContext _dataCollectionContext; - private Mock _mockDataColectionEvents; - private Mock _mockDataCollectionSink; - private Mock _mockCoverageWrapper; - private Mock _mockCountDownEventFactory; + private readonly DataCollectionContext _dataCollectionContext; + private readonly Mock _mockDataCollectionEvents; + private readonly Mock _mockDataCollectionSink; + private readonly Mock _mockCoverageWrapper; + private readonly Mock _mockCountDownEventFactory; private XmlElement _configurationElement; - private Mock _mockLogger; + private readonly Mock _mockLogger; + private readonly Mock _mockAssemblyAdapter; public CoverletCoverageDataCollectorTests() { - _mockDataColectionEvents = new Mock(); + _mockDataCollectionEvents = new Mock(); _mockDataCollectionSink = new Mock(); _mockLogger = new Mock(); _configurationElement = null; - TestCase testcase = new TestCase { Id = Guid.NewGuid() }; + var testcase = new TestCase { Id = Guid.NewGuid() }; _dataCollectionContext = new DataCollectionContext(testcase); _context = new DataCollectionEnvironmentContext(_dataCollectionContext); _mockCoverageWrapper = new Mock(); _mockCountDownEventFactory = new Mock(); _mockCountDownEventFactory.Setup(def => def.Create(It.IsAny(), It.IsAny())).Returns(new Mock().Object); + _mockAssemblyAdapter = new Mock(); + _mockAssemblyAdapter.Setup(x => x.GetAssemblyName(It.IsAny())).Returns("abc"); } [Fact] @@ -53,7 +58,7 @@ public void OnSessionStartShouldInitializeCoverageWithCorrectCoverletSettings() Func serviceCollectionFactory = (TestPlatformEqtTrace eqtTrace, TestPlatformLogger logger, string testModule) => { IServiceCollection serviceCollection = new ServiceCollection(); - Mock fileSystem = new Mock(); + var fileSystem = new Mock(); fileSystem.Setup(f => f.Exists(It.IsAny())).Returns((string testLib) => testLib == "abc.dll"); serviceCollection.AddTransient(_ => fileSystem.Object); @@ -61,14 +66,14 @@ public void OnSessionStartShouldInitializeCoverageWithCorrectCoverletSettings() serviceCollection.AddTransient(); serviceCollection.AddTransient(_ => new CoverletLogger(eqtTrace, logger)); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(serviceProvider => new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService())); + serviceCollection.AddSingleton(serviceProvider => new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService(), _mockAssemblyAdapter.Object)); serviceCollection.AddSingleton(); return serviceCollection; }; _coverletCoverageDataCollector = new CoverletCoverageCollector(new TestPlatformEqtTrace(), _mockCoverageWrapper.Object, _mockCountDownEventFactory.Object, serviceCollectionFactory); _coverletCoverageDataCollector.Initialize( _configurationElement, - _mockDataColectionEvents.Object, + _mockDataCollectionEvents.Object, _mockDataCollectionSink.Object, _mockLogger.Object, _context); @@ -76,7 +81,7 @@ public void OnSessionStartShouldInitializeCoverageWithCorrectCoverletSettings() sessionStartProperties.Add("TestSources", new List { "abc.dll" }); - _mockDataColectionEvents.Raise(x => x.SessionStart += null, new SessionStartEventArgs(sessionStartProperties)); + _mockDataCollectionEvents.Raise(x => x.SessionStart += null, new SessionStartEventArgs(sessionStartProperties)); _mockCoverageWrapper.Verify(x => x.CreateCoverage(It.Is(y => string.Equals(y.TestModule, "abc.dll")), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } @@ -87,7 +92,7 @@ public void OnSessionStartShouldPrepareModulesForCoverage() Func serviceCollectionFactory = (TestPlatformEqtTrace eqtTrace, TestPlatformLogger logger, string testModule) => { IServiceCollection serviceCollection = new ServiceCollection(); - Mock fileSystem = new Mock(); + var fileSystem = new Mock(); fileSystem.Setup(f => f.Exists(It.IsAny())).Returns((string testLib) => testLib == "abc.dll"); serviceCollection.AddTransient(_ => fileSystem.Object); @@ -95,14 +100,14 @@ public void OnSessionStartShouldPrepareModulesForCoverage() serviceCollection.AddTransient(); serviceCollection.AddTransient(_ => new CoverletLogger(eqtTrace, logger)); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(serviceProvider => new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService())); + serviceCollection.AddSingleton(serviceProvider => new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService(), _mockAssemblyAdapter.Object)); serviceCollection.AddSingleton(); return serviceCollection; }; _coverletCoverageDataCollector = new CoverletCoverageCollector(new TestPlatformEqtTrace(), _mockCoverageWrapper.Object, _mockCountDownEventFactory.Object, serviceCollectionFactory); _coverletCoverageDataCollector.Initialize( _configurationElement, - _mockDataColectionEvents.Object, + _mockDataCollectionEvents.Object, _mockDataCollectionSink.Object, null, _context); @@ -114,7 +119,7 @@ public void OnSessionStartShouldPrepareModulesForCoverage() new Mock().Object, new Mock().Object); - CoverageParameters parameters = new CoverageParameters + var parameters = new CoverageParameters { IncludeFilters = null, IncludeDirectories = null, @@ -126,12 +131,12 @@ public void OnSessionStartShouldPrepareModulesForCoverage() UseSourceLink = true }; - Coverage coverage = new Coverage("abc.dll", parameters, It.IsAny(), instrumentationHelper, new Mock().Object, new Mock().Object, new Mock().Object); + var coverage = new Coverage("abc.dll", parameters, It.IsAny(), instrumentationHelper, new Mock().Object, new Mock().Object, new Mock().Object); sessionStartProperties.Add("TestSources", new List { "abc.dll" }); _mockCoverageWrapper.Setup(x => x.CreateCoverage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(coverage); - _mockDataColectionEvents.Raise(x => x.SessionStart += null, new SessionStartEventArgs(sessionStartProperties)); + _mockDataCollectionEvents.Raise(x => x.SessionStart += null, new SessionStartEventArgs(sessionStartProperties)); _mockCoverageWrapper.Verify(x => x.CreateCoverage(It.Is(y => y.TestModule.Contains("abc.dll")), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); _mockCoverageWrapper.Verify(x => x.PrepareModules(It.IsAny()), Times.Once); @@ -148,14 +153,14 @@ public void OnSessionEndShouldSendGetCoverageReportToTestPlatform() serviceCollection.AddTransient(); serviceCollection.AddTransient(_ => new CoverletLogger(eqtTrace, logger)); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(serviceProvider => new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService())); + serviceCollection.AddSingleton(serviceProvider => new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService(), _mockAssemblyAdapter.Object)); serviceCollection.AddSingleton(); return serviceCollection; }; _coverletCoverageDataCollector = new CoverletCoverageCollector(new TestPlatformEqtTrace(), new CoverageWrapper(), _mockCountDownEventFactory.Object, serviceCollectionFactory); _coverletCoverageDataCollector.Initialize( _configurationElement, - _mockDataColectionEvents.Object, + _mockDataCollectionEvents.Object, _mockDataCollectionSink.Object, _mockLogger.Object, _context); @@ -163,7 +168,7 @@ public void OnSessionEndShouldSendGetCoverageReportToTestPlatform() string module = GetType().Assembly.Location; string pdb = Path.Combine(Path.GetDirectoryName(module), Path.GetFileNameWithoutExtension(module) + ".pdb"); - var directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); + DirectoryInfo directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); File.Copy(module, Path.Combine(directory.FullName, Path.GetFileName(module)), true); File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true); @@ -171,8 +176,8 @@ public void OnSessionEndShouldSendGetCoverageReportToTestPlatform() IDictionary sessionStartProperties = new Dictionary(); sessionStartProperties.Add("TestSources", new List { Path.Combine(directory.FullName, Path.GetFileName(module)) }); - _mockDataColectionEvents.Raise(x => x.SessionStart += null, new SessionStartEventArgs(sessionStartProperties)); - _mockDataColectionEvents.Raise(x => x.SessionEnd += null, new SessionEndEventArgs()); + _mockDataCollectionEvents.Raise(x => x.SessionStart += null, new SessionStartEventArgs(sessionStartProperties)); + _mockDataCollectionEvents.Raise(x => x.SessionEnd += null, new SessionEndEventArgs()); _mockDataCollectionSink.Verify(x => x.SendFileAsync(It.IsAny()), Times.Once); @@ -188,22 +193,23 @@ public void OnSessionEndShouldSendCoverageReportsForMultipleFormatsToTestPlatfor Func serviceCollectionFactory = (TestPlatformEqtTrace eqtTrace, TestPlatformLogger logger, string testModule) => { IServiceCollection serviceCollection = new ServiceCollection(); - Mock fileSystem = new Mock(); + var fileSystem = new Mock(); fileSystem.Setup(f => f.Exists(It.IsAny())).Returns((string testLib) => testLib == "Test"); serviceCollection.AddTransient(_ => fileSystem.Object); serviceCollection.AddTransient(); serviceCollection.AddTransient(); + serviceCollection.AddTransient(); serviceCollection.AddTransient(_ => new CoverletLogger(eqtTrace, logger)); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(serviceProvider => new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService())); + serviceCollection.AddSingleton(serviceProvider => new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService(), _mockAssemblyAdapter.Object)); serviceCollection.AddSingleton(); return serviceCollection; }; _coverletCoverageDataCollector = new CoverletCoverageCollector(new TestPlatformEqtTrace(), new CoverageWrapper(), _mockCountDownEventFactory.Object, serviceCollectionFactory); IList reporters = formats.Split(',').Select(f => new ReporterFactory(f).CreateReporter()).Where(x => x != null).ToList(); - Mock mockDataCollectionSink = new Mock(); + var mockDataCollectionSink = new Mock(); mockDataCollectionSink.Setup(m => m.SendFileAsync(It.IsAny())).Callback(fti => { reporters.Remove(reporters.First(x => @@ -212,8 +218,8 @@ public void OnSessionEndShouldSendCoverageReportsForMultipleFormatsToTestPlatfor }); var doc = new XmlDocument(); - var root = doc.CreateElement("Configuration"); - var element = doc.CreateElement("Format"); + XmlElement root = doc.CreateElement("Configuration"); + XmlElement element = doc.CreateElement("Format"); element.AppendChild(doc.CreateTextNode(formats)); root.AppendChild(element); @@ -221,15 +227,15 @@ public void OnSessionEndShouldSendCoverageReportsForMultipleFormatsToTestPlatfor _coverletCoverageDataCollector.Initialize( _configurationElement, - _mockDataColectionEvents.Object, + _mockDataCollectionEvents.Object, mockDataCollectionSink.Object, _mockLogger.Object, _context); var sessionStartProperties = new Dictionary { { "TestSources", new List { "Test" } } }; - _mockDataColectionEvents.Raise(x => x.SessionStart += null, new SessionStartEventArgs(sessionStartProperties)); - _mockDataColectionEvents.Raise(x => x.SessionEnd += null, new SessionEndEventArgs()); + _mockDataCollectionEvents.Raise(x => x.SessionStart += null, new SessionStartEventArgs(sessionStartProperties)); + _mockDataCollectionEvents.Raise(x => x.SessionEnd += null, new SessionEndEventArgs()); mockDataCollectionSink.Verify(x => x.SendFileAsync(It.IsAny()), Times.Exactly(sendReportsCount)); Assert.Empty(reporters); @@ -241,22 +247,23 @@ public void OnSessionStartShouldLogWarningIfInstrumentationFailed() Func serviceCollectionFactory = (TestPlatformEqtTrace eqtTrace, TestPlatformLogger logger, string testModule) => { IServiceCollection serviceCollection = new ServiceCollection(); - Mock fileSystem = new Mock(); + var fileSystem = new Mock(); fileSystem.Setup(f => f.Exists(It.IsAny())).Returns((string testLib) => testLib == "abc.dll"); serviceCollection.AddTransient(_ => fileSystem.Object); serviceCollection.AddTransient(); serviceCollection.AddTransient(); + serviceCollection.AddTransient(); serviceCollection.AddTransient(_ => new CoverletLogger(eqtTrace, logger)); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(serviceProvider => new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService())); + serviceCollection.AddSingleton(serviceProvider => new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService(), _mockAssemblyAdapter.Object)); serviceCollection.AddSingleton(); return serviceCollection; }; _coverletCoverageDataCollector = new CoverletCoverageCollector(new TestPlatformEqtTrace(), _mockCoverageWrapper.Object, _mockCountDownEventFactory.Object, serviceCollectionFactory); _coverletCoverageDataCollector.Initialize( _configurationElement, - _mockDataColectionEvents.Object, + _mockDataCollectionEvents.Object, _mockDataCollectionSink.Object, _mockLogger.Object, _context); @@ -266,7 +273,7 @@ public void OnSessionStartShouldLogWarningIfInstrumentationFailed() _mockCoverageWrapper.Setup(x => x.PrepareModules(It.IsAny())).Throws(new FileNotFoundException()); - _mockDataColectionEvents.Raise(x => x.SessionStart += null, new SessionStartEventArgs(sessionStartProperties)); + _mockDataCollectionEvents.Raise(x => x.SessionStart += null, new SessionStartEventArgs(sessionStartProperties)); _mockLogger.Verify(x => x.LogWarning(_dataCollectionContext, It.Is(y => y.Contains("CoverletDataCollectorException")))); diff --git a/test/coverlet.collector.tests/CoverletSettingsParserTests.cs b/test/coverlet.collector.tests/CoverletSettingsParserTests.cs index ebcba0b3f..191bdb232 100644 --- a/test/coverlet.collector.tests/CoverletSettingsParserTests.cs +++ b/test/coverlet.collector.tests/CoverletSettingsParserTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; using System.Linq; using System.Xml; using Coverlet.Collector.DataCollection; @@ -9,7 +12,7 @@ namespace Coverlet.Collector.Tests { public class CoverletSettingsParserTests { - private CoverletSettingsParser _coverletSettingsParser; + private readonly CoverletSettingsParser _coverletSettingsParser; public CoverletSettingsParserTests() { @@ -64,19 +67,19 @@ public void ParseShouldCorrectlyParseConfigurationElement(string includeFilters, { var testModules = new List { "abc.dll" }; var doc = new XmlDocument(); - var configElement = doc.CreateElement("Configuration"); - this.CreateCoverletNodes(doc, configElement, CoverletConstants.IncludeFiltersElementName, includeFilters); - this.CreateCoverletNodes(doc, configElement, CoverletConstants.ExcludeFiltersElementName, excludeFilters); - this.CreateCoverletNodes(doc, configElement, CoverletConstants.IncludeDirectoriesElementName, includeDirectories); - this.CreateCoverletNodes(doc, configElement, CoverletConstants.ExcludeSourceFilesElementName, excludeSourceFiles); - this.CreateCoverletNodes(doc, configElement, CoverletConstants.ExcludeAttributesElementName, excludeAttributes); - this.CreateCoverletNodes(doc, configElement, CoverletConstants.MergeWithElementName, "/path/to/result.json"); - this.CreateCoverletNodes(doc, configElement, CoverletConstants.UseSourceLinkElementName, "false"); - this.CreateCoverletNodes(doc, configElement, CoverletConstants.SingleHitElementName, "true"); - this.CreateCoverletNodes(doc, configElement, CoverletConstants.IncludeTestAssemblyElementName, "true"); - this.CreateCoverletNodes(doc, configElement, CoverletConstants.SkipAutoProps, "true"); - this.CreateCoverletNodes(doc, configElement, CoverletConstants.DeterministicReport, "true"); - this.CreateCoverletNodes(doc, configElement, CoverletConstants.DoesNotReturnAttributesElementName, doesNotReturnAttributes); + XmlElement configElement = doc.CreateElement("Configuration"); + CreateCoverletNodes(doc, configElement, CoverletConstants.IncludeFiltersElementName, includeFilters); + CreateCoverletNodes(doc, configElement, CoverletConstants.ExcludeFiltersElementName, excludeFilters); + CreateCoverletNodes(doc, configElement, CoverletConstants.IncludeDirectoriesElementName, includeDirectories); + CreateCoverletNodes(doc, configElement, CoverletConstants.ExcludeSourceFilesElementName, excludeSourceFiles); + CreateCoverletNodes(doc, configElement, CoverletConstants.ExcludeAttributesElementName, excludeAttributes); + CreateCoverletNodes(doc, configElement, CoverletConstants.MergeWithElementName, "/path/to/result.json"); + CreateCoverletNodes(doc, configElement, CoverletConstants.UseSourceLinkElementName, "false"); + CreateCoverletNodes(doc, configElement, CoverletConstants.SingleHitElementName, "true"); + CreateCoverletNodes(doc, configElement, CoverletConstants.IncludeTestAssemblyElementName, "true"); + CreateCoverletNodes(doc, configElement, CoverletConstants.SkipAutoProps, "true"); + CreateCoverletNodes(doc, configElement, CoverletConstants.DeterministicReport, "true"); + CreateCoverletNodes(doc, configElement, CoverletConstants.DoesNotReturnAttributesElementName, doesNotReturnAttributes); CoverletSettings coverletSettings = _coverletSettingsParser.Parse(configElement, testModules); @@ -109,12 +112,12 @@ public void ParseShouldCorrectlyParseConfigurationElementWithNullInnerText() { var testModules = new List { "abc.dll" }; var doc = new XmlDocument(); - var configElement = doc.CreateElement("Configuration"); - this.CreateCoverletNullInnerTextNodes(doc, configElement, CoverletConstants.IncludeFiltersElementName); - this.CreateCoverletNullInnerTextNodes(doc, configElement, CoverletConstants.ExcludeFiltersElementName); - this.CreateCoverletNullInnerTextNodes(doc, configElement, CoverletConstants.IncludeDirectoriesElementName); - this.CreateCoverletNullInnerTextNodes(doc, configElement, CoverletConstants.ExcludeSourceFilesElementName); - this.CreateCoverletNullInnerTextNodes(doc, configElement, CoverletConstants.ExcludeAttributesElementName); + XmlElement configElement = doc.CreateElement("Configuration"); + CreateCoverletNullInnerTextNodes(doc, configElement, CoverletConstants.IncludeFiltersElementName); + CreateCoverletNullInnerTextNodes(doc, configElement, CoverletConstants.ExcludeFiltersElementName); + CreateCoverletNullInnerTextNodes(doc, configElement, CoverletConstants.IncludeDirectoriesElementName); + CreateCoverletNullInnerTextNodes(doc, configElement, CoverletConstants.ExcludeSourceFilesElementName); + CreateCoverletNullInnerTextNodes(doc, configElement, CoverletConstants.ExcludeAttributesElementName); CoverletSettings coverletSettings = _coverletSettingsParser.Parse(configElement, testModules); @@ -131,7 +134,7 @@ public void ParseShouldCorrectlyParseConfigurationElementWithNullElements() { var testModules = new List { "abc.dll" }; var doc = new XmlDocument(); - var configElement = doc.CreateElement("Configuration"); + XmlElement configElement = doc.CreateElement("Configuration"); CoverletSettings coverletSettings = _coverletSettingsParser.Parse(configElement, testModules); @@ -160,8 +163,8 @@ public void ParseShouldCorrectlyParseMultipleFormats(string formats, int formats { var testModules = new List { "abc.dll" }; var doc = new XmlDocument(); - var configElement = doc.CreateElement("Configuration"); - this.CreateCoverletNodes(doc, configElement, CoverletConstants.ReportFormatElementName, formats); + XmlElement configElement = doc.CreateElement("Configuration"); + CreateCoverletNodes(doc, configElement, CoverletConstants.ReportFormatElementName, formats); CoverletSettings coverletSettings = _coverletSettingsParser.Parse(configElement, testModules); @@ -175,26 +178,26 @@ public void ParseShouldCorrectlyParseMultipleFormats(string formats, int formats public void ParseShouldUseDefaultFormatWhenNoFormatSpecified(string formats) { var testModules = new List { "abc.dll" }; - var defaultFormat = CoverletConstants.DefaultReportFormat; + string defaultFormat = CoverletConstants.DefaultReportFormat; var doc = new XmlDocument(); - var configElement = doc.CreateElement("Configuration"); - this.CreateCoverletNodes(doc, configElement, CoverletConstants.ReportFormatElementName, formats); + XmlElement configElement = doc.CreateElement("Configuration"); + CreateCoverletNodes(doc, configElement, CoverletConstants.ReportFormatElementName, formats); CoverletSettings coverletSettings = _coverletSettingsParser.Parse(configElement, testModules); Assert.Equal(defaultFormat, coverletSettings.ReportFormats[0]); } - private void CreateCoverletNodes(XmlDocument doc, XmlElement configElement, string nodeSetting, string nodeValue) + private static void CreateCoverletNodes(XmlDocument doc, XmlElement configElement, string nodeSetting, string nodeValue) { - var node = doc.CreateNode("element", nodeSetting, string.Empty); + XmlNode node = doc.CreateNode("element", nodeSetting, string.Empty); node.InnerText = nodeValue; configElement.AppendChild(node); } - private void CreateCoverletNullInnerTextNodes(XmlDocument doc, XmlElement configElement, string nodeSetting) + private static void CreateCoverletNullInnerTextNodes(XmlDocument doc, XmlElement configElement, string nodeSetting) { - var node = doc.CreateNode("element", nodeSetting, string.Empty); + XmlNode node = doc.CreateNode("element", nodeSetting, string.Empty); node.InnerText = null; configElement.AppendChild(node); } diff --git a/test/coverlet.collector.tests/Properties/AssemblyInfo.cs b/test/coverlet.collector.tests/Properties/AssemblyInfo.cs index b80d4b319..0d10a5c3c 100644 --- a/test/coverlet.collector.tests/Properties/AssemblyInfo.cs +++ b/test/coverlet.collector.tests/Properties/AssemblyInfo.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System.Reflection; [assembly: AssemblyKeyFile("coverlet.collector.tests.snk")] diff --git a/test/coverlet.collector.tests/coverlet.collector.tests.csproj b/test/coverlet.collector.tests/coverlet.collector.tests.csproj index 822ddda6f..fa828394a 100644 --- a/test/coverlet.collector.tests/coverlet.collector.tests.csproj +++ b/test/coverlet.collector.tests/coverlet.collector.tests.csproj @@ -2,7 +2,7 @@ - net5.0 + net6.0 false diff --git a/test/coverlet.core.performancetest/.editorconfig b/test/coverlet.core.performancetest/.editorconfig new file mode 100644 index 000000000..d66ee0772 --- /dev/null +++ b/test/coverlet.core.performancetest/.editorconfig @@ -0,0 +1,8 @@ +# top-most EditorConfig file +# We don't want to import other EditorConfig files and we want +# to ensure no rules are enabled for these asset source files. +root = true + +[*.cs] +# Default severity for all analyzer diagnostics +dotnet_analyzer_diagnostic.severity = none diff --git a/test/coverlet.core.performancetest/coverlet.core.performancetest.csproj b/test/coverlet.core.performancetest/coverlet.core.performancetest.csproj index be9cd0e08..41f8d5bd3 100644 --- a/test/coverlet.core.performancetest/coverlet.core.performancetest.csproj +++ b/test/coverlet.core.performancetest/coverlet.core.performancetest.csproj @@ -2,13 +2,14 @@ - net5.0 + net6.0 false + diff --git a/test/coverlet.core.tests.samples.netstandard/.editorconfig b/test/coverlet.core.tests.samples.netstandard/.editorconfig new file mode 100644 index 000000000..d66ee0772 --- /dev/null +++ b/test/coverlet.core.tests.samples.netstandard/.editorconfig @@ -0,0 +1,8 @@ +# top-most EditorConfig file +# We don't want to import other EditorConfig files and we want +# to ensure no rules are enabled for these asset source files. +root = true + +[*.cs] +# Default severity for all analyzer diagnostics +dotnet_analyzer_diagnostic.severity = none diff --git a/test/coverlet.core.tests/Coverage/CoverageSummaryTests.cs b/test/coverlet.core.tests/Coverage/CoverageSummaryTests.cs index d66b72573..1a04e2021 100644 --- a/test/coverlet.core.tests/Coverage/CoverageSummaryTests.cs +++ b/test/coverlet.core.tests/Coverage/CoverageSummaryTests.cs @@ -1,9 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. -using Coverlet.Core; -using Moq; +using System.Linq; using Xunit; namespace Coverlet.Core.Tests @@ -23,29 +21,29 @@ public CoverageSummaryTests() private void SetupDataForArithmeticPrecision() { - Lines lines = new Lines(); + var lines = new Lines(); lines.Add(1, 1); for (int i = 2; i <= 6; i++) { lines.Add(i, 0); } - Branches branches = new Branches(); + var branches = new Branches(); branches.Add(new BranchInfo { Line = 1, Hits = 1, Offset = 1, Path = 0, Ordinal = 1 }); for (int i = 2; i <= 6; i++) { branches.Add(new BranchInfo { Line = 1, Hits = 0, Offset = 1, Path = 1, Ordinal = (uint)i }); } - Methods methods = new Methods(); - var methodString = "System.Void Coverlet.Core.Tests.CoverageSummaryTests::TestCalculateSummary()"; + var methods = new Methods(); + string methodString = "System.Void Coverlet.Core.Tests.CoverageSummaryTests::TestCalculateSummary()"; methods.Add(methodString, new Method()); methods[methodString].Lines = lines; methods[methodString].Branches = branches; - Classes classes = new Classes(); + var classes = new Classes(); classes.Add("Coverlet.Core.Tests.CoverageSummaryTests", methods); - Documents documents = new Documents(); + var documents = new Documents(); documents.Add("doc.cs", classes); _moduleArithmeticPrecision = new Modules(); @@ -54,23 +52,23 @@ private void SetupDataForArithmeticPrecision() private void SetupDataSingleModule() { - Lines lines = new Lines(); + var lines = new Lines(); lines.Add(1, 1); lines.Add(2, 0); - Branches branches = new Branches(); + var branches = new Branches(); branches.Add(new BranchInfo { Line = 1, Hits = 1, Offset = 1, Path = 0, Ordinal = 1 }); branches.Add(new BranchInfo { Line = 1, Hits = 1, Offset = 1, Path = 1, Ordinal = 2 }); - Methods methods = new Methods(); - var methodString = "System.Void Coverlet.Core.Tests.CoverageSummaryTests::TestCalculateSummary()"; + var methods = new Methods(); + string methodString = "System.Void Coverlet.Core.Tests.CoverageSummaryTests::TestCalculateSummary()"; methods.Add(methodString, new Method()); methods[methodString].Lines = lines; methods[methodString].Branches = branches; - Classes classes = new Classes(); + var classes = new Classes(); classes.Add("Coverlet.Core.Tests.CoverageSummaryTests", methods); - Documents documents = new Documents(); + var documents = new Documents(); documents.Add("doc.cs", classes); _averageCalculationSingleModule = new Modules(); @@ -79,21 +77,21 @@ private void SetupDataSingleModule() private void SetupDataMultipleModule() { - Lines lines = new Lines + var lines = new Lines { { 1, 1 }, // covered { 2, 0 }, // not covered { 3, 0 } // not covered }; - Branches branches = new Branches + var branches = new Branches { new BranchInfo { Line = 1, Hits = 1, Offset = 1, Path = 0, Ordinal = 1 }, // covered new BranchInfo { Line = 1, Hits = 1, Offset = 1, Path = 1, Ordinal = 2 }, // covered new BranchInfo { Line = 1, Hits = 0, Offset = 1, Path = 1, Ordinal = 2 } // not covered }; - Methods methods = new Methods(); + var methods = new Methods(); string[] methodString = { "System.Void Coverlet.Core.Tests.CoverageSummaryTests::TestCalculateSummary()", // covered "System.Void Coverlet.Core.Tests.CoverageSummaryTests::TestAditionalCalculateSummary()" // not covered @@ -108,12 +106,12 @@ private void SetupDataMultipleModule() { 1, 0 } // not covered }; - Classes classes = new Classes + var classes = new Classes { { "Coverlet.Core.Tests.CoverageSummaryTests", methods } }; - Documents documents = new Documents + var documents = new Documents { { "doc.cs", classes } }; @@ -128,7 +126,7 @@ private void SetupDataMultipleModule() [Fact] public void TestCalculateLineCoverage_NoModules() { - CoverageSummary summary = new CoverageSummary(); + var summary = new CoverageSummary(); var modules = new Modules(); Assert.Equal(0, summary.CalculateLineCoverage(modules).Percent); @@ -142,12 +140,12 @@ public void TestCalculateLineCoverage_NoModules() [Fact] public void TestCalculateLineCoverage_SingleModule() { - CoverageSummary summary = new CoverageSummary(); + var summary = new CoverageSummary(); - var module = _averageCalculationSingleModule.First(); - var document = module.Value.First(); - var @class = document.Value.First(); - var method = @class.Value.First(); + System.Collections.Generic.KeyValuePair module = _averageCalculationSingleModule.First(); + System.Collections.Generic.KeyValuePair document = module.Value.First(); + System.Collections.Generic.KeyValuePair @class = document.Value.First(); + System.Collections.Generic.KeyValuePair method = @class.Value.First(); Assert.Equal(50, summary.CalculateLineCoverage(_averageCalculationSingleModule).AverageModulePercent); Assert.Equal(50, summary.CalculateLineCoverage(module.Value).Percent); @@ -159,9 +157,9 @@ public void TestCalculateLineCoverage_SingleModule() [Fact] public void TestCalculateLineCoverage_MultiModule() { - CoverageSummary summary = new CoverageSummary(); - var documentsFirstModule = _averageCalculationMultiModule["module"]; - var documentsSecondModule = _averageCalculationMultiModule["aditionalModule"]; + var summary = new CoverageSummary(); + Documents documentsFirstModule = _averageCalculationMultiModule["module"]; + Documents documentsSecondModule = _averageCalculationMultiModule["aditionalModule"]; Assert.Equal(37.5, summary.CalculateLineCoverage(_averageCalculationMultiModule).AverageModulePercent); Assert.Equal(50, summary.CalculateLineCoverage(documentsFirstModule.First().Value).Percent); @@ -174,12 +172,12 @@ public void TestCalculateLineCoverage_MultiModule() [Fact] public void TestCalculateBranchCoverage_SingleModule() { - CoverageSummary summary = new CoverageSummary(); + var summary = new CoverageSummary(); - var module = _averageCalculationSingleModule.First(); - var document = module.Value.First(); - var @class = document.Value.First(); - var method = @class.Value.First(); + System.Collections.Generic.KeyValuePair module = _averageCalculationSingleModule.First(); + System.Collections.Generic.KeyValuePair document = module.Value.First(); + System.Collections.Generic.KeyValuePair @class = document.Value.First(); + System.Collections.Generic.KeyValuePair method = @class.Value.First(); Assert.Equal(100, summary.CalculateBranchCoverage(_averageCalculationSingleModule).AverageModulePercent); Assert.Equal(100, summary.CalculateBranchCoverage(module.Value).Percent); @@ -191,9 +189,9 @@ public void TestCalculateBranchCoverage_SingleModule() [Fact] public void TestCalculateBranchCoverage_MultiModule() { - CoverageSummary summary = new CoverageSummary(); - var documentsFirstModule = _averageCalculationMultiModule["module"]; - var documentsSecondModule = _averageCalculationMultiModule["aditionalModule"]; + var summary = new CoverageSummary(); + Documents documentsFirstModule = _averageCalculationMultiModule["module"]; + Documents documentsSecondModule = _averageCalculationMultiModule["aditionalModule"]; Assert.Equal(83.33, summary.CalculateBranchCoverage(_averageCalculationMultiModule).AverageModulePercent); Assert.Equal(100, summary.CalculateBranchCoverage(documentsFirstModule.First().Value).Percent); @@ -203,12 +201,12 @@ public void TestCalculateBranchCoverage_MultiModule() [Fact] public void TestCalculateMethodCoverage_SingleModule() { - CoverageSummary summary = new CoverageSummary(); + var summary = new CoverageSummary(); - var module = _averageCalculationSingleModule.First(); - var document = module.Value.First(); - var @class = document.Value.First(); - var method = @class.Value.First(); + System.Collections.Generic.KeyValuePair module = _averageCalculationSingleModule.First(); + System.Collections.Generic.KeyValuePair document = module.Value.First(); + System.Collections.Generic.KeyValuePair @class = document.Value.First(); + System.Collections.Generic.KeyValuePair method = @class.Value.First(); Assert.Equal(100, summary.CalculateMethodCoverage(_averageCalculationSingleModule).AverageModulePercent); Assert.Equal(100, summary.CalculateMethodCoverage(module.Value).Percent); @@ -220,9 +218,9 @@ public void TestCalculateMethodCoverage_SingleModule() [Fact] public void TestCalculateMethodCoverage_MultiModule() { - CoverageSummary summary = new CoverageSummary(); - var documentsFirstModule = _averageCalculationMultiModule["module"]; - var documentsSecondModule = _averageCalculationMultiModule["aditionalModule"]; + var summary = new CoverageSummary(); + Documents documentsFirstModule = _averageCalculationMultiModule["module"]; + Documents documentsSecondModule = _averageCalculationMultiModule["aditionalModule"]; Assert.Equal(75, summary.CalculateMethodCoverage(_averageCalculationMultiModule).AverageModulePercent); Assert.Equal(100, summary.CalculateMethodCoverage(documentsFirstModule.First().Value).Percent); @@ -232,12 +230,12 @@ public void TestCalculateMethodCoverage_MultiModule() [Fact] public void TestCalculateLineCoveragePercentage_ArithmeticPrecisionCheck() { - CoverageSummary summary = new CoverageSummary(); + var summary = new CoverageSummary(); - var module = _moduleArithmeticPrecision.First(); - var document = module.Value.First(); - var @class = document.Value.First(); - var method = @class.Value.First(); + System.Collections.Generic.KeyValuePair module = _moduleArithmeticPrecision.First(); + System.Collections.Generic.KeyValuePair document = module.Value.First(); + System.Collections.Generic.KeyValuePair @class = document.Value.First(); + System.Collections.Generic.KeyValuePair method = @class.Value.First(); Assert.Equal(16.66, summary.CalculateLineCoverage(_moduleArithmeticPrecision).AverageModulePercent); Assert.Equal(16.66, summary.CalculateLineCoverage(module.Value).Percent); @@ -249,12 +247,12 @@ public void TestCalculateLineCoveragePercentage_ArithmeticPrecisionCheck() [Fact] public void TestCalculateBranchCoveragePercentage_ArithmeticPrecisionCheck() { - CoverageSummary summary = new CoverageSummary(); + var summary = new CoverageSummary(); - var module = _moduleArithmeticPrecision.First(); - var document = module.Value.First(); - var @class = document.Value.First(); - var method = @class.Value.First(); + System.Collections.Generic.KeyValuePair module = _moduleArithmeticPrecision.First(); + System.Collections.Generic.KeyValuePair document = module.Value.First(); + System.Collections.Generic.KeyValuePair @class = document.Value.First(); + System.Collections.Generic.KeyValuePair method = @class.Value.First(); Assert.Equal(16.66, summary.CalculateBranchCoverage(_moduleArithmeticPrecision).AverageModulePercent); Assert.Equal(16.66, summary.CalculateBranchCoverage(module.Value).Percent); @@ -263,4 +261,4 @@ public void TestCalculateBranchCoveragePercentage_ArithmeticPrecisionCheck() Assert.Equal(16.66, summary.CalculateBranchCoverage(method.Value.Branches).Percent); } } -} \ No newline at end of file +} diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.AsyncAwait.cs b/test/coverlet.core.tests/Coverage/CoverageTests.AsyncAwait.cs index 40d733b7f..8d0b70ec2 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.AsyncAwait.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.AsyncAwait.cs @@ -1,10 +1,11 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System.IO; -using System.Linq; +using System.Reflection; using System.Threading; using System.Threading.Tasks; - using Coverlet.Core.Samples.Tests; -using Coverlet.Tests.Xunit.Extensions; using Xunit; namespace Coverlet.Core.Tests @@ -112,7 +113,8 @@ public void AsyncAwait_Issue_669_2() ((ValueTask)instance.SendRequest()).ConfigureAwait(false).GetAwaiter().GetResult(); return Task.CompletedTask; }, - persistPrepareResultToFile: pathSerialize[0]); + persistPrepareResultToFile: pathSerialize[0], + assemblyLocation: Assembly.GetExecutingAssembly().Location); return 0; }, new string[] { path }); @@ -146,7 +148,7 @@ public void AsyncAwait_Issue_1177() return 0; }, new string[] { path }); - var document = TestInstrumentationHelper.GetCoverageResult(path).Document("Instrumentation.AsyncAwait.cs"); + Core.Instrumentation.Document document = TestInstrumentationHelper.GetCoverageResult(path).Document("Instrumentation.AsyncAwait.cs"); document.AssertLinesCovered(BuildConfiguration.Debug, (133, 1), (134, 1), (135, 1), (136, 1), (137, 1)); Assert.DoesNotContain(document.Branches, x => x.Key.Line == 134); } @@ -174,7 +176,7 @@ public void AsyncAwait_Issue_1233() return 0; }, new string[] { path }); - var document = TestInstrumentationHelper.GetCoverageResult(path).Document("Instrumentation.AsyncAwait.cs"); + Core.Instrumentation.Document document = TestInstrumentationHelper.GetCoverageResult(path).Document("Instrumentation.AsyncAwait.cs"); document.AssertLinesCovered(BuildConfiguration.Debug, (150, 1)); Assert.DoesNotContain(document.Branches, x => x.Key.Line == 150); } @@ -203,7 +205,7 @@ public void AsyncAwait_Issue_1275() return 0; }, new string[] { path }); - var document = TestInstrumentationHelper.GetCoverageResult(path).Document("Instrumentation.AsyncAwait.cs"); + Core.Instrumentation.Document document = TestInstrumentationHelper.GetCoverageResult(path).Document("Instrumentation.AsyncAwait.cs"); document.AssertLinesCoveredFromTo(BuildConfiguration.Debug, 170, 176); document.AssertBranchesCovered(BuildConfiguration.Debug, (171, 0, 1), (171, 1, 1)); Assert.DoesNotContain(document.Branches, x => x.Key.Line == 176); @@ -214,4 +216,4 @@ public void AsyncAwait_Issue_1275() } } } -} \ No newline at end of file +} diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.AsyncAwaitValueTask.cs b/test/coverlet.core.tests/Coverage/CoverageTests.AsyncAwaitValueTask.cs index 0438dc66d..dc7ebddfc 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.AsyncAwaitValueTask.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.AsyncAwaitValueTask.cs @@ -1,8 +1,9 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System.IO; using System.Threading.Tasks; - using Coverlet.Core.Samples.Tests; -using Coverlet.Tests.Xunit.Extensions; using Xunit; namespace Coverlet.Core.Tests diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.AsyncForeach.cs b/test/coverlet.core.tests/Coverage/CoverageTests.AsyncForeach.cs index abeee16cd..77450d19b 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.AsyncForeach.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.AsyncForeach.cs @@ -1,10 +1,10 @@ -using System.Collections.Generic; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System.IO; using System.Linq; using System.Threading.Tasks; - using Coverlet.Core.Samples.Tests; -using Coverlet.Tests.Xunit.Extensions; using Xunit; namespace Coverlet.Core.Tests @@ -24,6 +24,7 @@ public void AsyncForeach() int res = ((ValueTask)instance.SumWithATwist(AsyncEnumerable.Range(1, 5))).GetAwaiter().GetResult(); res += ((ValueTask)instance.Sum(AsyncEnumerable.Range(1, 3))).GetAwaiter().GetResult(); res += ((ValueTask)instance.SumEmpty()).GetAwaiter().GetResult(); + ((ValueTask)instance.GenericAsyncForeach(AsyncEnumerable.Range(1, 3))).GetAwaiter().GetResult(); return Task.CompletedTask; }, persistPrepareResultToFile: pathSerialize[0]); @@ -43,7 +44,9 @@ public void AsyncForeach() // Sum(IAsyncEnumerable) (34, 1), (35, 1), (37, 9), (38, 3), (39, 3), (40, 3), (42, 1), (43, 1), // SumEmpty() - (47, 1), (48, 1), (50, 3), (51, 0), (52, 0), (53, 0), (55, 1), (56, 1) + (47, 1), (48, 1), (50, 3), (51, 0), (52, 0), (53, 0), (55, 1), (56, 1), + // GenericAsyncForeach + (59,1), (60, 9), (61, 3), (62, 3), (63, 3), (64, 1) ) .AssertBranchesCovered(BuildConfiguration.Debug, // SumWithATwist(IAsyncEnumerable) @@ -53,9 +56,10 @@ public void AsyncForeach() // SumEmpty() // If we never entered the loop, that's a branch not taken, which is // what we want to see. - (50, 0, 1), (50, 1, 0) + (50, 0, 1), (50, 1, 0), + (60, 0, 1), (60, 1, 3) ) - .ExpectedTotalNumberOfBranches(BuildConfiguration.Debug, 4); + .ExpectedTotalNumberOfBranches(BuildConfiguration.Debug, 5); } finally { diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.AsyncIterator.cs b/test/coverlet.core.tests/Coverage/CoverageTests.AsyncIterator.cs index 67aa6b323..a14157c42 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.AsyncIterator.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.AsyncIterator.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System.IO; using System.Threading.Tasks; - using Coverlet.Core.Samples.Tests; -using Coverlet.Tests.Xunit.Extensions; using Xunit; namespace Coverlet.Core.Tests diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.AutoProps.cs b/test/coverlet.core.tests/Coverage/CoverageTests.AutoProps.cs index 8b923da86..134a61bad 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.AutoProps.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.AutoProps.cs @@ -1,4 +1,7 @@ -using System.IO; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; using System.Threading.Tasks; using Coverlet.Core.Samples.Tests; using Xunit; diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.AwaitUsing.cs b/test/coverlet.core.tests/Coverage/CoverageTests.AwaitUsing.cs index a5849090f..82bab4694 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.AwaitUsing.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.AwaitUsing.cs @@ -1,8 +1,9 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System.IO; using System.Threading.Tasks; - using Coverlet.Core.Samples.Tests; -using Coverlet.Tests.Xunit.Extensions; using Xunit; namespace Coverlet.Core.Tests diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.CatchBlock.cs b/test/coverlet.core.tests/Coverage/CoverageTests.CatchBlock.cs index 9c356b968..fc6e1cb13 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.CatchBlock.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.CatchBlock.cs @@ -1,8 +1,9 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System.IO; using System.Threading.Tasks; - using Coverlet.Core.Samples.Tests; -using Coverlet.Tests.Xunit.Extensions; using Xunit; namespace Coverlet.Core.Tests @@ -64,7 +65,7 @@ public void CatchBlock_Issue465() return 0; }, new string[] { path }); - var res = TestInstrumentationHelper.GetCoverageResult(path); + CoverageResult res = TestInstrumentationHelper.GetCoverageResult(path); res.Document("Instrumentation.CatchBlock.cs") .AssertLinesCoveredAllBut(BuildConfiguration.Debug, 45, 59, 113, 127, 137, 138, 139, 153, 154, 155, 156, 175, 189, 199, 200, 201, 222, 223, 224, 225, 252, 266, 335, 349) .ExpectedTotalNumberOfBranches(BuildConfiguration.Debug, 6) @@ -76,4 +77,4 @@ public void CatchBlock_Issue465() } } } -} \ No newline at end of file +} diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.DoesNotReturn.cs b/test/coverlet.core.tests/Coverage/CoverageTests.DoesNotReturn.cs index 67219a6ec..e5950232a 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.DoesNotReturn.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.DoesNotReturn.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using System.IO; using System.Threading.Tasks; using Coverlet.Core.Samples.Tests; diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.ExcludeFromCoverageAttribute.cs b/test/coverlet.core.tests/Coverage/CoverageTests.ExcludeFromCoverageAttribute.cs index 6f75f76da..ed8906a5f 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.ExcludeFromCoverageAttribute.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.ExcludeFromCoverageAttribute.cs @@ -1,14 +1,14 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.IO; using System.Linq; -using System.Runtime.InteropServices; using System.Threading.Tasks; - using Coverlet.Core.Abstractions; using Coverlet.Core.Helpers; using Coverlet.Core.Samples.Tests; using Coverlet.Core.Symbols; -using Coverlet.Tests.Xunit.Extensions; using Moq; using Xunit; @@ -19,7 +19,7 @@ public partial class CoverageTests [Fact] public void TestCoverageSkipModule__AssemblyMarkedAsExcludeFromCodeCoverage() { - Mock partialMockFileSystem = new Mock(); + var partialMockFileSystem = new Mock(); partialMockFileSystem.CallBase = true; partialMockFileSystem.Setup(fs => fs.NewFileStream(It.IsAny(), It.IsAny(), It.IsAny())).Returns((string path, FileMode mode, FileAccess access) => { @@ -29,11 +29,11 @@ public void TestCoverageSkipModule__AssemblyMarkedAsExcludeFromCodeCoverage() string excludedbyattributeDll = Directory.GetFiles(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets"), "coverlet.tests.projectsample.excludedbyattribute.dll").First(); - InstrumentationHelper instrumentationHelper = + var instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object, - new SourceRootTranslator(excludedbyattributeDll, new Mock().Object, new FileSystem())); + new SourceRootTranslator(excludedbyattributeDll, new Mock().Object, new FileSystem(), new AssemblyAdapter())); - CoverageParameters parameters = new CoverageParameters + var parameters = new CoverageParameters { IncludeFilters = new string[] { "[coverlet.tests.projectsample.excludedbyattribute*]*" }, IncludeDirectories = Array.Empty(), @@ -74,7 +74,7 @@ public void ExcludeFromCodeCoverage_CompilerGeneratedMethodsAndTypes() CoverageResult result = TestInstrumentationHelper.GetCoverageResult(path); - var document = result.Document("Instrumentation.ExcludeFromCoverage.cs"); + Core.Instrumentation.Document document = result.Document("Instrumentation.ExcludeFromCoverage.cs"); // Invoking method "Test" of class "MethodsWithExcludeFromCodeCoverageAttr" we expect to cover 100% lines for MethodsWithExcludeFromCodeCoverageAttr Assert.DoesNotContain(document.Lines, l => @@ -277,5 +277,32 @@ public void ExcludeFromCodeCoverageAutoGeneratedGet() File.Delete(path); } } + + [Fact] + public void ExcludeFromCodeCoverage_Issue1302() + { + string path = Path.GetTempFileName(); + try + { + FunctionExecutor.Run(async (string[] pathSerialize) => + { + CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => + { + instance.Run(); + return Task.CompletedTask; + }, persistPrepareResultToFile: pathSerialize[0]); + + return 0; + }, new string[] { path }); + + TestInstrumentationHelper.GetCoverageResult(path) + .Document("Instrumentation.ExcludeFromCoverage.Issue1302.cs") + .AssertNonInstrumentedLines(BuildConfiguration.Debug, 10, 13); + } + finally + { + File.Delete(path); + } + } } -} \ No newline at end of file +} diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.Filters.cs b/test/coverlet.core.tests/Coverage/CoverageTests.Filters.cs index 2845d5c1c..e86e61a3f 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.Filters.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.Filters.cs @@ -1,10 +1,11 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.IO; using System.Reflection; using System.Threading.Tasks; - using Coverlet.Core.Samples.Tests; -using Coverlet.Tests.Xunit.Extensions; using Xunit; namespace Coverlet.Core.Tests @@ -128,4 +129,4 @@ public void ExcludeFilteredNestedTypes() } } } -} \ No newline at end of file +} diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.GenericAsyncIterator.cs b/test/coverlet.core.tests/Coverage/CoverageTests.GenericAsyncIterator.cs new file mode 100644 index 000000000..8aa18b285 --- /dev/null +++ b/test/coverlet.core.tests/Coverage/CoverageTests.GenericAsyncIterator.cs @@ -0,0 +1,42 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Coverlet.Core.Samples.Tests; +using Xunit; + +namespace Coverlet.Core.Tests +{ + public partial class CoverageTests + { + [Fact] + public void GenericAsyncIterator() + { + string path = Path.GetTempFileName(); + try + { + FunctionExecutor.Run(async (string[] pathSerialize) => + { + CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run>(instance => + { + List res = ((Task>)instance.Issue1383()).GetAwaiter().GetResult(); + + return Task.CompletedTask; + }, persistPrepareResultToFile: pathSerialize[0]); + return 0; + }, new string[] { path }); + + TestInstrumentationHelper.GetCoverageResult(path) + .Document("Instrumentation.GenericAsyncIterator.cs") + .AssertLinesCovered(BuildConfiguration.Debug, (13, 1), (14, 1), (20, 1), (21, 1), (22, 1)) + .ExpectedTotalNumberOfBranches(BuildConfiguration.Debug, 0); + } + finally + { + File.Delete(path); + } + } + } +} diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.IntegerOverflow.cs b/test/coverlet.core.tests/Coverage/CoverageTests.IntegerOverflow.cs index 5b9121f81..5e206fa34 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.IntegerOverflow.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.IntegerOverflow.cs @@ -1,4 +1,7 @@ -using System.IO; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; using Coverlet.Core.Abstractions; using Coverlet.Core.Instrumentation; using Moq; @@ -11,7 +14,7 @@ public partial class CoverageTests [Fact] public void CoverageResult_NegativeLineCoverage_TranslatedToMaxValueOfInt32() { - InstrumenterResult instrumenterResult = new InstrumenterResult + var instrumenterResult = new InstrumenterResult { HitsFilePath = "HitsFilePath", SourceLink = "SourceLink", @@ -36,7 +39,7 @@ public void CoverageResult_NegativeLineCoverage_TranslatedToMaxValueOfInt32() instrumenterResult.Documents.Add("document", document); - CoveragePrepareResult coveragePrepareResult = new CoveragePrepareResult + var coveragePrepareResult = new CoveragePrepareResult { UseSourceLink = true, Results = new[] {instrumenterResult}, @@ -44,7 +47,7 @@ public void CoverageResult_NegativeLineCoverage_TranslatedToMaxValueOfInt32() }; Stream memoryStream = new MemoryStream(); - BinaryWriter binaryWriter = new BinaryWriter(memoryStream); + var binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(1); binaryWriter.Write(-1); memoryStream.Position = 0; @@ -57,7 +60,7 @@ public void CoverageResult_NegativeLineCoverage_TranslatedToMaxValueOfInt32() var coverage = new Coverage(coveragePrepareResult, new Mock().Object, new Mock().Object, fileSystemMock.Object, new Mock().Object); - var coverageResult = coverage.GetCoverageResult(); + CoverageResult coverageResult = coverage.GetCoverageResult(); coverageResult.Document("document").AssertLinesCovered(BuildConfiguration.Debug, (1, int.MaxValue)); } diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.Lambda.cs b/test/coverlet.core.tests/Coverage/CoverageTests.Lambda.cs index 37b3e76aa..24a87a681 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.Lambda.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.Lambda.cs @@ -1,8 +1,9 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System.IO; using System.Threading.Tasks; - using Coverlet.Core.Samples.Tests; -using Coverlet.Tests.Xunit.Extensions; using Xunit; namespace Coverlet.Core.Tests @@ -138,4 +139,4 @@ public void Issue_1056() } } } -} \ No newline at end of file +} diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.SelectionStatements.cs b/test/coverlet.core.tests/Coverage/CoverageTests.SelectionStatements.cs index 367b9b592..3e551daf7 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.SelectionStatements.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.SelectionStatements.cs @@ -1,8 +1,9 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System.IO; using System.Threading.Tasks; - using Coverlet.Core.Samples.Tests; -using Coverlet.Tests.Xunit.Extensions; using Tmds.Utils; using Xunit; @@ -150,4 +151,4 @@ public void SelectionStatements_Switch_CSharp8_AllBranches() } } } -} \ No newline at end of file +} diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.Yield.cs b/test/coverlet.core.tests/Coverage/CoverageTests.Yield.cs index f2aca5360..e491d9003 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.Yield.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.Yield.cs @@ -1,6 +1,8 @@ -using System.IO; -using System.Threading.Tasks; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.IO; +using System.Threading.Tasks; using Coverlet.Core.Samples.Tests; using Tmds.Utils; using Xunit; @@ -19,7 +21,7 @@ public void Yield_Single() { CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => { - foreach (var _ in instance.One()) ; + foreach (dynamic _ in instance.One()) ; return Task.CompletedTask; }, persistPrepareResultToFile: pathSerialize[0]); @@ -50,7 +52,7 @@ public void Yield_Two() { CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => { - foreach (var _ in instance.Two()) ; + foreach (dynamic _ in instance.Two()) ; return Task.CompletedTask; }, persistPrepareResultToFile: pathSerialize[0]); @@ -80,7 +82,7 @@ public void Yield_SingleWithSwitch() { CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => { - foreach (var _ in instance.OneWithSwitch(2)) ; + foreach (dynamic _ in instance.OneWithSwitch(2)) ; return Task.CompletedTask; }, persistPrepareResultToFile: pathSerialize[0]); @@ -111,7 +113,7 @@ public void Yield_Three() { CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => { - foreach (var _ in instance.Three()) ; + foreach (dynamic _ in instance.Three()) ; return Task.CompletedTask; }, persistPrepareResultToFile: pathSerialize[0]); @@ -141,7 +143,7 @@ public void Yield_Enumerable() { CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => { - foreach (var _ in instance.Enumerable(new[] { "one", "two", "three", "four" })) ; + foreach (dynamic _ in instance.Enumerable(new[] { "one", "two", "three", "four" })) ; return Task.CompletedTask; }, persistPrepareResultToFile: pathSerialize[0]); diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.cs b/test/coverlet.core.tests/Coverage/CoverageTests.cs index 10074bab3..4f0465fe2 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.cs @@ -1,6 +1,8 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.IO; - using Coverlet.Core.Abstractions; using Coverlet.Core.Helpers; using Coverlet.Core.Symbols; @@ -11,7 +13,7 @@ namespace Coverlet.Core.Tests { public partial class CoverageTests { - private readonly Mock _mockLogger = new Mock(); + private readonly Mock _mockLogger = new(); [Fact] public void TestCoverage() @@ -19,17 +21,17 @@ public void TestCoverage() string module = GetType().Assembly.Location; string pdb = Path.Combine(Path.GetDirectoryName(module), Path.GetFileNameWithoutExtension(module) + ".pdb"); - var directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); + DirectoryInfo directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); File.Copy(module, Path.Combine(directory.FullName, Path.GetFileName(module)), true); File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true); // TODO: Find a way to mimick hits - InstrumentationHelper instrumentationHelper = + var instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object, - new SourceRootTranslator(module, new Mock().Object, new FileSystem())); + new SourceRootTranslator(module, new Mock().Object, new FileSystem(), new AssemblyAdapter())); - CoverageParameters parameters = new CoverageParameters + var parameters = new CoverageParameters { IncludeFilters = new string[] { "[coverlet.tests.projectsample.excludedbyattribute*]*" }, IncludeDirectories = Array.Empty(), @@ -45,7 +47,7 @@ public void TestCoverage() var coverage = new Coverage(Path.Combine(directory.FullName, Path.GetFileName(module)), parameters, _mockLogger.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(_mockLogger.Object, new FileSystem()), new CecilSymbolHelper()); coverage.PrepareModules(); - var result = coverage.GetCoverageResult(); + CoverageResult result = coverage.GetCoverageResult(); Assert.Empty(result.Modules); @@ -58,16 +60,16 @@ public void TestCoverageWithTestAssembly() string module = GetType().Assembly.Location; string pdb = Path.Combine(Path.GetDirectoryName(module), Path.GetFileNameWithoutExtension(module) + ".pdb"); - var directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); + DirectoryInfo directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); File.Copy(module, Path.Combine(directory.FullName, Path.GetFileName(module)), true); File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true); - InstrumentationHelper instrumentationHelper = + var instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object, - new SourceRootTranslator(module, new Mock().Object, new FileSystem())); + new SourceRootTranslator(module, new Mock().Object, new FileSystem(), new AssemblyAdapter())); - CoverageParameters parameters = new CoverageParameters + var parameters = new CoverageParameters { IncludeFilters = Array.Empty(), IncludeDirectories = Array.Empty(), @@ -81,14 +83,14 @@ public void TestCoverageWithTestAssembly() }; var coverage = new Coverage(Path.Combine(directory.FullName, Path.GetFileName(module)), parameters, _mockLogger.Object, instrumentationHelper, new FileSystem(), - new SourceRootTranslator(module, _mockLogger.Object, new FileSystem()), new CecilSymbolHelper()); + new SourceRootTranslator(module, _mockLogger.Object, new FileSystem(), new AssemblyAdapter()), new CecilSymbolHelper()); coverage.PrepareModules(); - var result = coverage.GetCoverageResult(); + CoverageResult result = coverage.GetCoverageResult(); Assert.NotEmpty(result.Modules); directory.Delete(true); } } -} \ No newline at end of file +} diff --git a/test/coverlet.core.tests/Coverage/InstrumenterHelper.Assertions.cs b/test/coverlet.core.tests/Coverage/InstrumenterHelper.Assertions.cs index 07f3e2bad..24f567312 100644 --- a/test/coverlet.core.tests/Coverage/InstrumenterHelper.Assertions.cs +++ b/test/coverlet.core.tests/Coverage/InstrumenterHelper.Assertions.cs @@ -1,10 +1,12 @@ -using Coverlet.Core.Instrumentation; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Coverlet.Core.Instrumentation; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using Xunit.Sdk; @@ -90,18 +92,18 @@ public static Document Method(this Document document, string methodName) if (document.Lines.Values.All(l => l.Method != methodName) && document.Branches.Values.All(l => l.Method != methodName)) { - var methods = document.Lines.Values.Select(l => $"'{l.Method}'") + IEnumerable methods = document.Lines.Values.Select(l => $"'{l.Method}'") .Concat(document.Branches.Values.Select(b => $"'{b.Method}'")) .Distinct(); throw new XunitException($"Method '{methodName}' not found. Methods in document: {string.Join(", ", methods)}"); } - foreach (var line in document.Lines.Where(l => l.Value.Method == methodName)) + foreach (KeyValuePair line in document.Lines.Where(l => l.Value.Method == methodName)) { methodDoc.Lines[line.Key] = line.Value; } - foreach (var branch in document.Branches.Where(b => b.Value.Method == methodName)) + foreach (KeyValuePair branch in document.Branches.Where(b => b.Value.Method == methodName)) { methodDoc.Branches[branch.Key] = branch.Value; } @@ -150,7 +152,7 @@ public static string ToStringBranches(this Document document) throw new ArgumentNullException(nameof(document)); } - StringBuilder builder = new StringBuilder(); + var builder = new StringBuilder(); foreach (KeyValuePair branch in document.Branches) { builder.AppendLine($"({branch.Value.Number}, {branch.Value.Ordinal}, {branch.Value.Hits}),"); @@ -172,7 +174,7 @@ public static Document AssertBranchesCovered(this Document document, BuildConfig return document; } - List branchesToCover = new List(lines.Select(b => $"[line {b.line} ordinal {b.ordinal}]")); + var branchesToCover = new List(lines.Select(b => $"[line {b.line} ordinal {b.ordinal}]")); foreach (KeyValuePair branch in document.Branches) { foreach ((int lineToCheck, int ordinalToCheck, int expectedHits) in lines) @@ -270,7 +272,7 @@ public static Document AssertLinesCoveredFromTo(this Document document, BuildCon throw new ArgumentException("to cannot be lower than from"); } - List lines = new List(); + var lines = new List(); foreach (KeyValuePair line in document.Lines) { if (line.Value.Number >= from && line.Value.Number <= to && line.Value.Hits > 0) @@ -301,7 +303,7 @@ public static Document AssertLinesCovered(this Document document, BuildConfigura return document; } - List linesToCover = new List(lines.Select(l => l.line)); + var linesToCover = new List(lines.Select(l => l.line)); foreach (KeyValuePair line in document.Lines) { foreach ((int lineToCheck, int expectedHits) in lines) @@ -349,7 +351,7 @@ private static Document AssertLinesCoveredInternal(this Document document, Build return document; } - List linesToCover = new List(lines); + var linesToCover = new List(lines); foreach (KeyValuePair line in document.Lines) { foreach (int lineToCheck in lines) @@ -409,7 +411,7 @@ public static Document AssertNonInstrumentedLines(this Document document, BuildC return document; } - var unexpectedlyInstrumented = document.Lines.Select(l => l.Value.Number).Intersect(lines); + IEnumerable unexpectedlyInstrumented = document.Lines.Select(l => l.Value.Number).Intersect(lines); if (unexpectedlyInstrumented.Any()) { @@ -435,7 +437,7 @@ public static Document AssertInstrumentLines(this Document document, BuildConfig var instrumentedLines = document.Lines.Select(l => l.Value.Number).ToHashSet(); - var missing = lines.Where(l => !instrumentedLines.Contains(l)); + IEnumerable missing = lines.Where(l => !instrumentedLines.Contains(l)); if (missing.Any()) { diff --git a/test/coverlet.core.tests/Coverage/InstrumenterHelper.cs b/test/coverlet.core.tests/Coverage/InstrumenterHelper.cs index 65365163a..c2a218592 100644 --- a/test/coverlet.core.tests/Coverage/InstrumenterHelper.cs +++ b/test/coverlet.core.tests/Coverage/InstrumenterHelper.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -20,7 +23,7 @@ namespace Coverlet.Core.Tests { static class TestInstrumentationHelper { - private static IServiceProvider _processWideContainer; + private static IServiceProvider s_processWideContainer; /// /// caller sample: TestInstrumentationHelper.GenerateHtmlReport(result, sourceFileFilter: @"+**\Samples\Instrumentation.cs"); @@ -28,7 +31,7 @@ static class TestInstrumentationHelper /// public static void GenerateHtmlReport(CoverageResult coverageResult, IReporter reporter = null, string sourceFileFilter = "", [CallerMemberName] string directory = "") { - JsonReporter defaultReporter = new JsonReporter(); + var defaultReporter = new JsonReporter(); reporter ??= new CoberturaReporter(); DirectoryInfo dir = Directory.CreateDirectory(directory); dir.Delete(true); @@ -61,19 +64,20 @@ public static CoverageResult GetCoverageResult(string filePath) { Assert.DoesNotContain("not found for module: ", message); }); - _processWideContainer.GetRequiredService().SetLogger(logger.Object); - CoveragePrepareResult coveragePrepareResultLoaded = CoveragePrepareResult.Deserialize(result); - Coverage coverage = new Coverage(coveragePrepareResultLoaded, logger.Object, _processWideContainer.GetService(), new FileSystem(), new SourceRootTranslator(new Mock().Object, new FileSystem())); + s_processWideContainer.GetRequiredService().SetLogger(logger.Object); + var coveragePrepareResultLoaded = CoveragePrepareResult.Deserialize(result); + var coverage = new Coverage(coveragePrepareResultLoaded, logger.Object, s_processWideContainer.GetService(), new FileSystem(), new SourceRootTranslator(new Mock().Object, new FileSystem())); return coverage.GetCoverageResult(); } - async public static Task Run(Func callMethod, + public static async Task Run(Func callMethod, Func includeFilter = null, Func excludeFilter = null, Func doesNotReturnAttributes = null, string persistPrepareResultToFile = null, bool disableRestoreModules = false, - bool skipAutoProps = false) + bool skipAutoProps = false, + string assemblyLocation = null) { if (persistPrepareResultToFile is null) { @@ -89,16 +93,17 @@ async public static Task Run(Func callM File.Copy(location, newPath); File.Copy(Path.ChangeExtension(location, ".pdb"), Path.ChangeExtension(newPath, ".pdb")); - SetTestContainer(newPath, disableRestoreModules); - + string sourceRootTranslatorModulePath = assemblyLocation ?? newPath; + SetTestContainer(sourceRootTranslatorModulePath, disableRestoreModules); + static string[] defaultFilters(string _) => Array.Empty(); - CoverageParameters parameters = new CoverageParameters + var parameters = new CoverageParameters { IncludeFilters = (includeFilter is null ? defaultFilters(fileName) : includeFilter(fileName)).Concat( new string[] { - $"[{Path.GetFileNameWithoutExtension(fileName)}*]{typeof(T).FullName}*" + $"[{Path.GetFileNameWithoutExtension(fileName)}*]{GetTypeFullName()}*" }).ToArray(), IncludeDirectories = Array.Empty(), ExcludeFilters = (excludeFilter is null ? defaultFilters(fileName) : excludeFilter(fileName)).Concat(new string[] @@ -117,14 +122,14 @@ async public static Task Run(Func callM }; // Instrument module - Coverage coverage = new Coverage(newPath, parameters, new Logger(logFile), - _processWideContainer.GetService(), _processWideContainer.GetService(), _processWideContainer.GetService(), _processWideContainer.GetService()); + var coverage = new Coverage(newPath, parameters, new Logger(logFile), + s_processWideContainer.GetService(), s_processWideContainer.GetService(), s_processWideContainer.GetService(), s_processWideContainer.GetService()); CoveragePrepareResult prepareResult = coverage.PrepareModules(); Assert.Single(prepareResult.Results); // Load new assembly - Assembly asm = Assembly.LoadFile(newPath); + var asm = Assembly.LoadFile(newPath); // Instance type and call method await callMethod(Activator.CreateInstance(asm.GetType(typeof(T).FullName))); @@ -140,7 +145,7 @@ async public static Task Run(Func callM tracker.GetTypeInfo().GetMethod("UnloadModule").Invoke(null, new object[2] { null, null }); // Persist CoveragePrepareResult - using (FileStream fs = new FileStream(persistPrepareResultToFile, FileMode.Open)) + using (var fs = new FileStream(persistPrepareResultToFile, FileMode.Open)) { await CoveragePrepareResult.Serialize(prepareResult).CopyToAsync(fs); } @@ -150,12 +155,13 @@ async public static Task Run(Func callM private static void SetTestContainer(string testModule = null, bool disableRestoreModules = false) { - LazyInitializer.EnsureInitialized(ref _processWideContainer, () => + LazyInitializer.EnsureInitialized(ref s_processWideContainer, () => { var serviceCollection = new ServiceCollection(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); + serviceCollection.AddTransient(); serviceCollection.AddTransient(_ => new Mock().Object); // We need to keep singleton/static semantics @@ -170,13 +176,24 @@ private static void SetTestContainer(string testModule = null, bool disableResto serviceCollection.AddSingleton(serviceProvider => string.IsNullOrEmpty(testModule) ? new SourceRootTranslator(serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService()) : - new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService())); + new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService())); serviceCollection.AddSingleton(); return serviceCollection.BuildServiceProvider(); }); } + + private static string GetTypeFullName() + { + string name = typeof(T).FullName; + if (typeof(T).IsGenericType && name != null) + { + int index = name.IndexOf('`'); + return index == -1 ? name : name[..index]; + } + return name; + } } class CustomProcessExitHandler : IProcessExitHandler @@ -237,7 +254,7 @@ public void Retry(Action action, Func backoffStrategy, int maxAttemptC // We log to files for debugging pourpose, we can check if instrumentation is ok class Logger : ILogger { - string _logFile; + readonly string _logFile; public Logger(string logFile) => _logFile = logFile; @@ -288,7 +305,7 @@ public override void RestoreOriginalModules() public abstract class ExternalProcessExecutionTest { - protected FunctionExecutor FunctionExecutor = new FunctionExecutor( + protected FunctionExecutor FunctionExecutor = new( o => { o.StartInfo.RedirectStandardError = true; diff --git a/test/coverlet.core.tests/CoverageResultTests.cs b/test/coverlet.core.tests/CoverageResultTests.cs index d391ac45c..fd5450909 100644 --- a/test/coverlet.core.tests/CoverageResultTests.cs +++ b/test/coverlet.core.tests/CoverageResultTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; using Coverlet.Core.Enums; using Xunit; @@ -6,22 +9,22 @@ namespace Coverlet.Core.Tests { public class CoverageResultTests { - private Modules _modules; + private readonly Modules _modules; public CoverageResultTests() { - Lines lines = new Lines(); + var lines = new Lines(); lines.Add(1, 1); lines.Add(2, 1); lines.Add(3, 1); - Branches branches = new Branches(); + var branches = new Branches(); branches.Add(new BranchInfo { Line = 1, Hits = 1, Offset = 1, Path = 0, Ordinal = 1 }); branches.Add(new BranchInfo { Line = 1, Hits = 1, Offset = 1, Path = 1, Ordinal = 2 }); branches.Add(new BranchInfo { Line = 2, Hits = 0, Offset = 1, Path = 0, Ordinal = 1 }); // System.Void Coverlet.Core.Tests.CoverageResultTests::CoverageResultTests - 3/3 100% line 2/3 66.7% branch coverage - Methods methods = new Methods(); - var methodString = "System.Void Coverlet.Core.Tests.CoverageResultTests::CoverageResultTests()"; + var methods = new Methods(); + string methodString = "System.Void Coverlet.Core.Tests.CoverageResultTests::CoverageResultTests()"; methods.Add(methodString, new Method()); methods[methodString].Lines = lines; methods[methodString].Branches = branches; @@ -35,13 +38,13 @@ public CoverageResultTests() {2, 0}, }; - Classes classes = new Classes(); + var classes = new Classes(); classes.Add("Coverlet.Core.Tests.CoverageResultTests", methods); // Methods - 1/2 (50%) // Lines - 3/5 (60%) // Branches - 2/3 (66.67%) - Documents documents = new Documents(); + var documents = new Documents(); documents.Add("doc.cs", classes); _modules = new Modules(); @@ -51,11 +54,11 @@ public CoverageResultTests() [Fact] public void TestGetThresholdTypesBelowThresholdLine() { - CoverageResult result = new CoverageResult(); + var result = new CoverageResult(); result.Modules = _modules; - CoverageSummary summary = new CoverageSummary(); - Dictionary thresholdTypeFlagValues = new Dictionary() + var summary = new CoverageSummary(); + var thresholdTypeFlagValues = new Dictionary() { { ThresholdTypeFlags.Line, 90 }, { ThresholdTypeFlags.Method, 10 }, @@ -71,11 +74,11 @@ public void TestGetThresholdTypesBelowThresholdLine() [Fact] public void TestGetThresholdTypesBelowThresholdMethod() { - CoverageResult result = new CoverageResult(); + var result = new CoverageResult(); result.Modules = _modules; - CoverageSummary summary = new CoverageSummary(); - Dictionary thresholdTypeFlagValues = new Dictionary() + var summary = new CoverageSummary(); + var thresholdTypeFlagValues = new Dictionary() { { ThresholdTypeFlags.Line, 50 }, { ThresholdTypeFlags.Method, 75 }, @@ -91,11 +94,11 @@ public void TestGetThresholdTypesBelowThresholdMethod() [Fact] public void TestGetThresholdTypesBelowThresholdBranch() { - CoverageResult result = new CoverageResult(); + var result = new CoverageResult(); result.Modules = _modules; - CoverageSummary summary = new CoverageSummary(); - Dictionary thresholdTypeFlagValues = new Dictionary() + var summary = new CoverageSummary(); + var thresholdTypeFlagValues = new Dictionary() { { ThresholdTypeFlags.Line, 50 }, { ThresholdTypeFlags.Method, 50 }, @@ -111,11 +114,11 @@ public void TestGetThresholdTypesBelowThresholdBranch() [Fact] public void TestGetThresholdTypesBelowThresholdAllGood() { - CoverageResult result = new CoverageResult(); + var result = new CoverageResult(); result.Modules = _modules; - CoverageSummary summary = new CoverageSummary(); - Dictionary thresholdTypeFlagValues = new Dictionary() + var summary = new CoverageSummary(); + var thresholdTypeFlagValues = new Dictionary() { { ThresholdTypeFlags.Line, 50 }, { ThresholdTypeFlags.Method, 50 }, @@ -131,11 +134,11 @@ public void TestGetThresholdTypesBelowThresholdAllGood() [Fact] public void TestGetThresholdTypesBelowThresholdAllFail() { - CoverageResult result = new CoverageResult(); + var result = new CoverageResult(); result.Modules = _modules; - CoverageSummary summary = new CoverageSummary(); - Dictionary thresholdTypeFlagValues = new Dictionary() + var summary = new CoverageSummary(); + var thresholdTypeFlagValues = new Dictionary() { { ThresholdTypeFlags.Line, 100 }, { ThresholdTypeFlags.Method, 100 }, @@ -152,11 +155,11 @@ public void TestGetThresholdTypesBelowThresholdAllFail() [Fact] public void TestGetThresholdTypesBelowThresholdWhenNoModuleInstrumented() { - CoverageResult result = new CoverageResult(); + var result = new CoverageResult(); result.Modules = new Modules(); - CoverageSummary summary = new CoverageSummary(); - Dictionary thresholdTypeFlagValues = new Dictionary() + var summary = new CoverageSummary(); + var thresholdTypeFlagValues = new Dictionary() { { ThresholdTypeFlags.Line, 80 }, { ThresholdTypeFlags.Method, 80 }, @@ -170,4 +173,4 @@ public void TestGetThresholdTypesBelowThresholdWhenNoModuleInstrumented() Assert.Equal(thresholdTypeFlags, resThresholdTypeFlags); } } -} \ No newline at end of file +} diff --git a/test/coverlet.core.tests/Helpers/FileSystemTests.cs b/test/coverlet.core.tests/Helpers/FileSystemTests.cs index 618a94f89..10f4aa289 100644 --- a/test/coverlet.core.tests/Helpers/FileSystemTests.cs +++ b/test/coverlet.core.tests/Helpers/FileSystemTests.cs @@ -1,4 +1,6 @@ -using Coverlet.Core.Helpers; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using Xunit; namespace Coverlet.Core.Helpers.Tests @@ -12,7 +14,7 @@ public class FileSystemTests [InlineData("filename{T}.cs", "filename{{T}}.cs")] public void TestEscapeFileName(string fileName, string expected) { - var actual = FileSystem.EscapeFileName(fileName); + string actual = FileSystem.EscapeFileName(fileName); Assert.Equal(expected, actual); } diff --git a/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs b/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs index 745009c72..8c6917042 100644 --- a/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs +++ b/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.IO; using Xunit; @@ -6,19 +9,20 @@ using Castle.Core.Internal; using Moq; using Coverlet.Core.Abstractions; +using Coverlet.Core.Enums; namespace Coverlet.Core.Helpers.Tests { public class InstrumentationHelperTests { private readonly InstrumentationHelper _instrumentationHelper = - new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object, new SourceRootTranslator(typeof(InstrumentationHelperTests).Assembly.Location, new Mock().Object, new FileSystem())); + new(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object, new SourceRootTranslator(typeof(InstrumentationHelperTests).Assembly.Location, new Mock().Object, new FileSystem(), new AssemblyAdapter())); [Fact] public void TestGetDependencies() { string module = typeof(InstrumentationHelperTests).Assembly.Location; - var modules = _instrumentationHelper.GetCoverableModules(module, Array.Empty(), false); + string[] modules = _instrumentationHelper.GetCoverableModules(module, Array.Empty(), false); Assert.False(Array.Exists(modules, m => m == module)); } @@ -26,37 +30,72 @@ public void TestGetDependencies() public void TestGetDependenciesWithTestAssembly() { string module = typeof(InstrumentationHelperTests).Assembly.Location; - var modules = _instrumentationHelper.GetCoverableModules(module, Array.Empty(), true); + string[] modules = _instrumentationHelper.GetCoverableModules(module, Array.Empty(), true); Assert.True(Array.Exists(modules, m => m == module)); } [Fact] - public void EmbeddedPortablePDPHasLocalSource_DocumentDoesNotExist_ReturnsFalse() + public void EmbeddedPortablePDPHasLocalSource_NoDocumentsExist_ReturnsFalse() { var fileSystem = new Mock {CallBase = true}; fileSystem.Setup(x => x.Exists(It.IsAny())).Returns(false); - InstrumentationHelper instrumentationHelper = - new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), fileSystem.Object, new Mock().Object, new SourceRootTranslator(typeof(InstrumentationHelperTests).Assembly.Location, new Mock().Object, new FileSystem())); + var instrumentationHelper = + new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), fileSystem.Object, new Mock().Object, new SourceRootTranslator(typeof(InstrumentationHelperTests).Assembly.Location, new Mock().Object, new FileSystem(), new AssemblyAdapter())); - Assert.False(instrumentationHelper.PortablePdbHasLocalSource(typeof(InstrumentationHelperTests).Assembly.Location, out string notFoundDocument)); - Assert.False(notFoundDocument.IsNullOrEmpty()); + Assert.False(instrumentationHelper.PortablePdbHasLocalSource(typeof(InstrumentationHelperTests).Assembly.Location, AssemblySearchType.MissingAny)); + Assert.False(instrumentationHelper.PortablePdbHasLocalSource(typeof(InstrumentationHelperTests).Assembly.Location, AssemblySearchType.MissingAll)); } [Fact] public void EmbeddedPortablePDPHasLocalSource_AllDocumentsExist_ReturnsTrue() { - Assert.True(_instrumentationHelper.PortablePdbHasLocalSource(typeof(InstrumentationHelperTests).Assembly.Location, out string notFoundDocument)); - Assert.True(notFoundDocument.IsNullOrEmpty()); + Assert.True(_instrumentationHelper.PortablePdbHasLocalSource(typeof(InstrumentationHelperTests).Assembly.Location, AssemblySearchType.MissingAny)); + Assert.True(_instrumentationHelper.PortablePdbHasLocalSource(typeof(InstrumentationHelperTests).Assembly.Location, AssemblySearchType.MissingAll)); + } + + [Theory] + [InlineData(AssemblySearchType.MissingAny, false)] + [InlineData(AssemblySearchType.MissingAll, true)] + public void EmbeddedPortablePDPHasLocalSource_FirstDocumentDoesNotExist_ReturnsExpectedValue(object assemblySearchType, bool result) + { + var fileSystem = new Mock { CallBase = true }; + fileSystem.SetupSequence(x => x.Exists(It.IsAny())) + .Returns(false) + .Returns(() => + { + fileSystem.Setup(y => y.Exists(It.IsAny())).Returns(true); + return true; + }); + + var instrumentationHelper = + new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), fileSystem.Object, new Mock().Object, new SourceRootTranslator(typeof(InstrumentationHelperTests).Assembly.Location, new Mock().Object, new FileSystem(), new AssemblyAdapter())); + + Assert.Equal(result, instrumentationHelper.PortablePdbHasLocalSource(typeof(InstrumentationHelperTests).Assembly.Location, (AssemblySearchType) assemblySearchType)); } [Fact] - public void TestHasPdb() + public void TestHasPdbOfLocalAssembly() { Assert.True(_instrumentationHelper.HasPdb(typeof(InstrumentationHelperTests).Assembly.Location, out bool embeddedPdb)); Assert.False(embeddedPdb); } + [Fact] + public void TestHasPdbOfExternalAssembly() + { + string testAssemblyLocation = GetType().Assembly.Location; + + string externalAssemblyFileName = Path.Combine( + Path.GetDirectoryName(testAssemblyLocation), + "TestAssets", + "75d9f96508d74def860a568f426ea4a4.dll" + ); + + Assert.True(_instrumentationHelper.HasPdb(externalAssemblyFileName, out bool embeddedPdb)); + Assert.False(embeddedPdb); + } + [Fact] public void TestBackupOriginalModule() { @@ -65,7 +104,7 @@ public void TestBackupOriginalModule() _instrumentationHelper.BackupOriginalModule(module, identifier); - var backupPath = Path.Combine( + string backupPath = Path.Combine( Path.GetTempPath(), Path.GetFileNameWithoutExtension(module) + "_" + identifier + ".dll" ); @@ -91,7 +130,7 @@ public void TestIsValidFilterExpression() [Fact] public void TestDeleteHitsFile() { - var tempFile = Path.GetTempFileName(); + string tempFile = Path.GetTempFileName(); Assert.True(File.Exists(tempFile)); _instrumentationHelper.DeleteHitsFile(tempFile); @@ -101,7 +140,7 @@ public void TestDeleteHitsFile() [Fact] public void TestIsModuleExcludedWithoutFilter() { - var result = _instrumentationHelper.IsModuleExcluded("Module.dll", new string[0]); + bool result = _instrumentationHelper.IsModuleExcluded("Module.dll", new string[0]); Assert.False(result); } @@ -109,7 +148,7 @@ public void TestIsModuleExcludedWithoutFilter() [Fact] public void TestIsModuleIncludedWithoutFilter() { - var result = _instrumentationHelper.IsModuleIncluded("Module.dll", new string[0]); + bool result = _instrumentationHelper.IsModuleIncluded("Module.dll", new string[0]); Assert.True(result); } @@ -119,7 +158,7 @@ public void TestIsModuleIncludedWithoutFilter() [InlineData("[Mismatch]*")] public void TestIsModuleExcludedWithSingleMismatchFilter(string filter) { - var result = _instrumentationHelper.IsModuleExcluded("Module.dll", new[] { filter }); + bool result = _instrumentationHelper.IsModuleExcluded("Module.dll", new[] { filter }); Assert.False(result); } @@ -127,7 +166,7 @@ public void TestIsModuleExcludedWithSingleMismatchFilter(string filter) [Fact] public void TestIsModuleIncludedWithSingleMismatchFilter() { - var result = _instrumentationHelper.IsModuleIncluded("Module.dll", new[] { "[Mismatch]*" }); + bool result = _instrumentationHelper.IsModuleIncluded("Module.dll", new[] { "[Mismatch]*" }); Assert.False(result); } @@ -136,7 +175,7 @@ public void TestIsModuleIncludedWithSingleMismatchFilter() [MemberData(nameof(ValidModuleFilterData))] public void TestIsModuleExcludedAndIncludedWithFilter(string filter) { - var result = _instrumentationHelper.IsModuleExcluded("Module.dll", new[] { filter }); + bool result = _instrumentationHelper.IsModuleExcluded("Module.dll", new[] { filter }); Assert.True(result); result = _instrumentationHelper.IsModuleIncluded("Module.dll", new[] { filter }); @@ -147,9 +186,9 @@ public void TestIsModuleExcludedAndIncludedWithFilter(string filter) [MemberData(nameof(ValidModuleFilterData))] public void TestIsModuleExcludedAndIncludedWithMatchingAndMismatchingFilter(string filter) { - var filters = new[] { "[Mismatch]*", filter, "[Mismatch]*" }; + string[] filters = new[] { "[Mismatch]*", filter, "[Mismatch]*" }; - var result = _instrumentationHelper.IsModuleExcluded("Module.dll", filters); + bool result = _instrumentationHelper.IsModuleExcluded("Module.dll", filters); Assert.True(result); result = _instrumentationHelper.IsModuleIncluded("Module.dll", filters); @@ -159,7 +198,7 @@ public void TestIsModuleExcludedAndIncludedWithMatchingAndMismatchingFilter(stri [Fact] public void TestIsTypeExcludedWithoutFilter() { - var result = _instrumentationHelper.IsTypeExcluded("Module.dll", "a.b.Dto", new string[0]); + bool result = _instrumentationHelper.IsTypeExcluded("Module.dll", "a.b.Dto", new string[0]); Assert.False(result); } @@ -167,7 +206,7 @@ public void TestIsTypeExcludedWithoutFilter() [Fact] public void TestIsTypeExcludedNamespace() { - var result = _instrumentationHelper.IsTypeExcluded("Module.dll", "Namespace.Namespace.Type", new string[] { "[Module]Namespace.Namespace.*" }); + bool result = _instrumentationHelper.IsTypeExcluded("Module.dll", "Namespace.Namespace.Type", new string[] { "[Module]Namespace.Namespace.*" }); Assert.True(result); result = _instrumentationHelper.IsTypeExcluded("Module.dll", "Namespace.Namespace.TypeB", new string[] { "[Module]Namespace.Namespace.*" }); @@ -183,7 +222,7 @@ public void TestIsTypeExcludedNamespace() [Fact] public void TestIsTypeIncludedWithoutFilter() { - var result = _instrumentationHelper.IsTypeIncluded("Module.dll", "a.b.Dto", new string[0]); + bool result = _instrumentationHelper.IsTypeIncluded("Module.dll", "a.b.Dto", new string[0]); Assert.True(result); } @@ -194,7 +233,7 @@ public void TestIsTypeIncludedWithoutFilter() [InlineData("[Mismatch]a.b.Dto")] public void TestIsTypeExcludedAndIncludedWithSingleMismatchFilter(string filter) { - var result = _instrumentationHelper.IsTypeExcluded("Module.dll", "a.b.Dto", new[] { filter }); + bool result = _instrumentationHelper.IsTypeExcluded("Module.dll", "a.b.Dto", new[] { filter }); Assert.False(result); result = _instrumentationHelper.IsTypeIncluded("Module.dll", "a.b.Dto", new[] { filter }); @@ -205,7 +244,7 @@ public void TestIsTypeExcludedAndIncludedWithSingleMismatchFilter(string filter) [MemberData(nameof(ValidModuleAndNamespaceFilterData))] public void TestIsTypeExcludedAndIncludedWithFilter(string filter) { - var result = _instrumentationHelper.IsTypeExcluded("Module.dll", "a.b.Dto", new[] { filter }); + bool result = _instrumentationHelper.IsTypeExcluded("Module.dll", "a.b.Dto", new[] { filter }); Assert.True(result); result = _instrumentationHelper.IsTypeIncluded("Module.dll", "a.b.Dto", new[] { filter }); @@ -216,9 +255,9 @@ public void TestIsTypeExcludedAndIncludedWithFilter(string filter) [MemberData(nameof(ValidModuleAndNamespaceFilterData))] public void TestIsTypeExcludedAndIncludedWithMatchingAndMismatchingFilter(string filter) { - var filters = new[] { "[Mismatch]*", filter, "[Mismatch]*" }; + string[] filters = new[] { "[Mismatch]*", filter, "[Mismatch]*" }; - var result = _instrumentationHelper.IsTypeExcluded("Module.dll", "a.b.Dto", filters); + bool result = _instrumentationHelper.IsTypeExcluded("Module.dll", "a.b.Dto", filters); Assert.True(result); result = _instrumentationHelper.IsTypeIncluded("Module.dll", "a.b.Dto", filters); @@ -241,11 +280,11 @@ public void TestIncludeDirectories() File.Copy("coverlet.msbuild.tasks.dll", Path.Combine(newDir.FullName, "coverlet.msbuild.tasks.dll")); File.Copy("coverlet.core.dll", Path.Combine(newDir2.FullName, "coverlet.core.dll")); - var currentDirModules = _instrumentationHelper.GetCoverableModules(module, Array.Empty(), false); + string[] currentDirModules = _instrumentationHelper.GetCoverableModules(module, Array.Empty(), false); Assert.Single(currentDirModules); Assert.Equal("coverlet.msbuild.tasks.dll", Path.GetFileName(currentDirModules[0])); - var moreThanOneDirectory = _instrumentationHelper + string[] moreThanOneDirectory = _instrumentationHelper .GetCoverableModules(module, new string[] { newDir2.FullName }, false) .OrderBy(f => f).ToArray(); @@ -253,7 +292,7 @@ public void TestIncludeDirectories() Assert.Equal("coverlet.msbuild.tasks.dll", Path.GetFileName(moreThanOneDirectory[0])); Assert.Equal("coverlet.core.dll", Path.GetFileName(moreThanOneDirectory[1])); - var moreThanOneDirectoryPlusTestAssembly = _instrumentationHelper + string[] moreThanOneDirectoryPlusTestAssembly = _instrumentationHelper .GetCoverableModules(module, new string[] { newDir2.FullName }, true) .OrderBy(f => f).ToArray(); @@ -290,5 +329,3 @@ public void TestIncludeDirectories() .Concat(ValidModuleFilterData); } } - - diff --git a/test/coverlet.core.tests/Helpers/RetryHelperTests.cs b/test/coverlet.core.tests/Helpers/RetryHelperTests.cs index 93ce3359c..acbcbd41f 100644 --- a/test/coverlet.core.tests/Helpers/RetryHelperTests.cs +++ b/test/coverlet.core.tests/Helpers/RetryHelperTests.cs @@ -1,5 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using Xunit; namespace Coverlet.Core.Helpers.Tests @@ -28,7 +30,7 @@ public void TestRetryWithFixedRetryBackoff() [Fact] public void TestRetryWithExponentialRetryBackoff() { - var currentSleep = 6; + int currentSleep = 6; Func retryStrategy = () => { var sleep = TimeSpan.FromMilliseconds(currentSleep); @@ -77,4 +79,4 @@ public void TargetActionThrows5Times() if (Calls < 6) throw new Exception("Simulating Failure"); } } -} \ No newline at end of file +} diff --git a/test/coverlet.core.tests/Helpers/SourceRootTranslatorTests.cs b/test/coverlet.core.tests/Helpers/SourceRootTranslatorTests.cs index 590f1a233..e464635a3 100644 --- a/test/coverlet.core.tests/Helpers/SourceRootTranslatorTests.cs +++ b/test/coverlet.core.tests/Helpers/SourceRootTranslatorTests.cs @@ -1,5 +1,7 @@ -using System.IO; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.IO; using Coverlet.Core.Abstractions; using Coverlet.Tests.Xunit.Extensions; using Moq; @@ -15,15 +17,17 @@ public class SourceRootTranslatorTests public void Translate_Success() { string fileToTranslate = "/_/src/coverlet.core/obj/Debug/netstandard2.0/coverlet.core.pdb"; - Mock logger = new Mock(); - Mock fileSystem = new Mock(); + var logger = new Mock(); + var assemblyAdapter = new Mock(); + assemblyAdapter.Setup(x => x.GetAssemblyName(It.IsAny())).Returns("testLib"); + var fileSystem = new Mock(); fileSystem.Setup(f => f.Exists(It.IsAny())).Returns((string p) => { - if (p == "testLib.dll" || p == @"C:\git\coverlet\src\coverlet.core\obj\Debug\netstandard2.0\coverlet.core.pdb" || p == "CoverletSourceRootsMapping") return true; + if (p == "testLib.dll" || p == @"C:\git\coverlet\src\coverlet.core\obj\Debug\netstandard2.0\coverlet.core.pdb" || p == "CoverletSourceRootsMapping_testLib") return true; return false; }); fileSystem.Setup(f => f.ReadAllLines(It.IsAny())).Returns(File.ReadAllLines(@"TestAssets/CoverletSourceRootsMappingTest")); - SourceRootTranslator translator = new SourceRootTranslator("testLib.dll", logger.Object, fileSystem.Object); + var translator = new SourceRootTranslator("testLib.dll", logger.Object, fileSystem.Object, assemblyAdapter.Object); Assert.Equal(@"C:\git\coverlet\src\coverlet.core\obj\Debug\netstandard2.0\coverlet.core.pdb", translator.ResolveFilePath(fileToTranslate)); Assert.Equal(@"C:\git\coverlet\src\coverlet.core\obj\Debug\netstandard2.0\coverlet.core.pdb", translator.ResolveFilePath(fileToTranslate)); } @@ -33,15 +37,17 @@ public void Translate_Success() [SkipOnOS(OS.MacOS, "Windows path format only")] public void TranslatePathRoot_Success() { - Mock logger = new Mock(); - Mock fileSystem = new Mock(); + var logger = new Mock(); + var assemblyAdapter = new Mock(); + assemblyAdapter.Setup(x => x.GetAssemblyName(It.IsAny())).Returns("testLib"); + var fileSystem = new Mock(); fileSystem.Setup(f => f.Exists(It.IsAny())).Returns((string p) => { - if (p == "testLib.dll" || p == @"C:\git\coverlet\src\coverlet.core\obj\Debug\netstandard2.0\coverlet.core.pdb" || p == "CoverletSourceRootsMapping") return true; + if (p == "testLib.dll" || p == @"C:\git\coverlet\src\coverlet.core\obj\Debug\netstandard2.0\coverlet.core.pdb" || p == "CoverletSourceRootsMapping_testLib") return true; return false; }); fileSystem.Setup(f => f.ReadAllLines(It.IsAny())).Returns(File.ReadAllLines(@"TestAssets/CoverletSourceRootsMappingTest")); - SourceRootTranslator translator = new SourceRootTranslator("testLib.dll", logger.Object, fileSystem.Object); + var translator = new SourceRootTranslator("testLib.dll", logger.Object, fileSystem.Object, assemblyAdapter.Object); Assert.Equal(@"C:\git\coverlet\", translator.ResolvePathRoot("/_/")[0].OriginalPath); } @@ -49,15 +55,17 @@ public void TranslatePathRoot_Success() public void Translate_EmptyFile() { string fileToTranslate = "/_/src/coverlet.core/obj/Debug/netstandard2.0/coverlet.core.pdb"; - Mock logger = new Mock(); - Mock fileSystem = new Mock(); + var logger = new Mock(); + var assemblyAdapter = new Mock(); + assemblyAdapter.Setup(x => x.GetAssemblyName(It.IsAny())).Returns("testLib"); + var fileSystem = new Mock(); fileSystem.Setup(f => f.Exists(It.IsAny())).Returns((string p) => { - if (p == "testLib.dll" || p == "CoverletSourceRootsMapping") return true; + if (p == "testLib.dll" || p == "CoverletSourceRootsMapping_testLib") return true; return false; }); fileSystem.Setup(f => f.ReadAllLines(It.IsAny())).Returns(new string[0]); - SourceRootTranslator translator = new SourceRootTranslator("testLib.dll", logger.Object, fileSystem.Object); + var translator = new SourceRootTranslator("testLib.dll", logger.Object, fileSystem.Object, assemblyAdapter.Object); Assert.Equal(fileToTranslate, translator.ResolveFilePath(fileToTranslate)); } @@ -65,15 +73,17 @@ public void Translate_EmptyFile() public void Translate_MalformedFile() { string fileToTranslate = "/_/src/coverlet.core/obj/Debug/netstandard2.0/coverlet.core.pdb"; - Mock logger = new Mock(); - Mock fileSystem = new Mock(); + var logger = new Mock(); + var assemblyAdapter = new Mock(); + assemblyAdapter.Setup(x => x.GetAssemblyName(It.IsAny())).Returns("testLib"); + var fileSystem = new Mock(); fileSystem.Setup(f => f.Exists(It.IsAny())).Returns((string p) => { - if (p == "testLib.dll" || p == "CoverletSourceRootsMapping") return true; + if (p == "testLib.dll" || p == "CoverletSourceRootsMapping_testLib") return true; return false; }); fileSystem.Setup(f => f.ReadAllLines(It.IsAny())).Returns(new string[1] { "malformedRow" }); - SourceRootTranslator translator = new SourceRootTranslator("testLib.dll", logger.Object, fileSystem.Object); + var translator = new SourceRootTranslator("testLib.dll", logger.Object, fileSystem.Object, assemblyAdapter.Object); Assert.Equal(fileToTranslate, translator.ResolveFilePath(fileToTranslate)); logger.Verify(l => l.LogWarning(It.IsAny()), Times.Once); } diff --git a/test/coverlet.core.tests/Instrumentation/InstrumenterResultTests.cs b/test/coverlet.core.tests/Instrumentation/InstrumenterResultTests.cs index 7e2774a8c..f3ca69aed 100644 --- a/test/coverlet.core.tests/Instrumentation/InstrumenterResultTests.cs +++ b/test/coverlet.core.tests/Instrumentation/InstrumenterResultTests.cs @@ -1,7 +1,7 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. using Xunit; -using Coverlet.Core.Instrumentation; -using System.Collections.Generic; using System.Linq; namespace Coverlet.Core.Instrumentation.Tests @@ -11,14 +11,14 @@ public class InstrumenterResultTests [Fact] public void TestEnsureDocumentsPropertyNotNull() { - InstrumenterResult result = new InstrumenterResult(); + var result = new InstrumenterResult(); Assert.NotNull(result.Documents); } [Fact] public void TestEnsureLinesAndBranchesPropertyNotNull() { - Document document = new Document(); + var document = new Document(); Assert.NotNull(document.Lines); Assert.NotNull(document.Branches); } @@ -26,13 +26,13 @@ public void TestEnsureLinesAndBranchesPropertyNotNull() [Fact] public void CoveragePrepareResult_SerializationRoundTrip() { - CoveragePrepareResult cpr = new CoveragePrepareResult(); + var cpr = new CoveragePrepareResult(); cpr.Identifier = "Identifier"; cpr.MergeWith = "MergeWith"; cpr.ModuleOrDirectory = "Module"; cpr.UseSourceLink = true; - InstrumenterResult ir = new InstrumenterResult(); + var ir = new InstrumenterResult(); ir.HitsFilePath = "HitsFilePath"; ir.Module = "Module"; ir.ModulePath = "ModulePath"; @@ -95,7 +95,7 @@ public void CoveragePrepareResult_SerializationRoundTrip() ir.Documents.Add("key2", doc2); cpr.Results = new InstrumenterResult[] { ir }; - CoveragePrepareResult roundTrip = CoveragePrepareResult.Deserialize(CoveragePrepareResult.Serialize(cpr)); + var roundTrip = CoveragePrepareResult.Deserialize(CoveragePrepareResult.Serialize(cpr)); Assert.Equal(cpr.Identifier, roundTrip.Identifier); Assert.Equal(cpr.MergeWith, roundTrip.MergeWith); @@ -119,8 +119,8 @@ public void CoveragePrepareResult_SerializationRoundTrip() for (int k = 0; k < cpr.Results[i].Documents.Count; k++) { - var documents = cpr.Results[i].Documents.ToArray(); - var documentsRoundTrip = roundTrip.Results[i].Documents.ToArray(); + System.Collections.Generic.KeyValuePair[] documents = cpr.Results[i].Documents.ToArray(); + System.Collections.Generic.KeyValuePair[] documentsRoundTrip = roundTrip.Results[i].Documents.ToArray(); for (int j = 0; j < documents.Length; j++) { Assert.Equal(documents[j].Key, documentsRoundTrip[j].Key); @@ -129,8 +129,8 @@ public void CoveragePrepareResult_SerializationRoundTrip() for (int v = 0; v < documents[j].Value.Lines.Count; v++) { - var lines = documents[j].Value.Lines.ToArray(); - var linesRoundTrip = documentsRoundTrip[j].Value.Lines.ToArray(); + System.Collections.Generic.KeyValuePair[] lines = documents[j].Value.Lines.ToArray(); + System.Collections.Generic.KeyValuePair[] linesRoundTrip = documentsRoundTrip[j].Value.Lines.ToArray(); Assert.Equal(lines[v].Key, linesRoundTrip[v].Key); Assert.Equal(lines[v].Value.Class, lines[v].Value.Class); @@ -141,8 +141,8 @@ public void CoveragePrepareResult_SerializationRoundTrip() for (int v = 0; v < documents[j].Value.Branches.Count; v++) { - var branches = documents[j].Value.Branches.ToArray(); - var branchesRoundTrip = documentsRoundTrip[j].Value.Branches.ToArray(); + System.Collections.Generic.KeyValuePair[] branches = documents[j].Value.Branches.ToArray(); + System.Collections.Generic.KeyValuePair[] branchesRoundTrip = documentsRoundTrip[j].Value.Branches.ToArray(); Assert.Equal(branches[v].Key, branchesRoundTrip[v].Key); Assert.Equal(branches[v].Value.Class, branchesRoundTrip[v].Value.Class); @@ -159,4 +159,4 @@ public void CoveragePrepareResult_SerializationRoundTrip() } } } -} \ No newline at end of file +} diff --git a/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs b/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs index dcdbc9499..a8c31da33 100644 --- a/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs +++ b/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs @@ -1,10 +1,12 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; - using Coverlet.Core.Helpers; using Coverlet.Core.Abstractions; using Coverlet.Core.Samples.Tests; @@ -24,15 +26,12 @@ namespace Coverlet.Core.Instrumentation.Tests { public class InstrumenterTests : IDisposable { - private readonly Mock _mockLogger = new Mock(); - private Action disposeAction; + private readonly Mock _mockLogger = new(); + private Action _disposeAction; public void Dispose() { - if (disposeAction != null) - { - disposeAction(); - } + _disposeAction?.Invoke(); } [ConditionalFact] @@ -47,12 +46,12 @@ public void TestCoreLibInstrumentation() "System.Private.CoreLib.pdb" }; - foreach (var file in files) + foreach (string file in files) { File.Copy(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", file), Path.Combine(directory.FullName, file), overwrite: true); } - Mock partialMockFileSystem = new Mock(); + var partialMockFileSystem = new Mock(); partialMockFileSystem.CallBase = true; partialMockFileSystem.Setup(fs => fs.OpenRead(It.IsAny())).Returns((string path) => { @@ -84,10 +83,10 @@ public void TestCoreLibInstrumentation() } }); var sourceRootTranslator = new SourceRootTranslator(_mockLogger.Object, new FileSystem()); - CoverageParameters parameters = new CoverageParameters(); - InstrumentationHelper instrumentationHelper = + var parameters = new CoverageParameters(); + var instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), partialMockFileSystem.Object, _mockLogger.Object, sourceRootTranslator); - Instrumenter instrumenter = new Instrumenter(Path.Combine(directory.FullName, files[0]), "_coverlet_instrumented", parameters, _mockLogger.Object, instrumentationHelper, partialMockFileSystem.Object, sourceRootTranslator, new CecilSymbolHelper()); + var instrumenter = new Instrumenter(Path.Combine(directory.FullName, files[0]), "_coverlet_instrumented", parameters, _mockLogger.Object, instrumentationHelper, partialMockFileSystem.Object, sourceRootTranslator, new CecilSymbolHelper()); Assert.True(instrumenter.CanInstrument()); InstrumenterResult result = instrumenter.Instrument(); @@ -105,9 +104,9 @@ public void TestCoreLibInstrumentation() [InlineData(false)] public void TestInstrument(bool singleHit) { - var instrumenterTest = CreateInstrumentor(singleHit: singleHit); + InstrumenterTest instrumenterTest = CreateInstrumentor(singleHit: singleHit); - var result = instrumenterTest.Instrumenter.Instrument(); + InstrumenterResult result = instrumenterTest.Instrumenter.Instrument(); Assert.Equal(Path.GetFileNameWithoutExtension(instrumenterTest.Module), result.Module); Assert.Equal(instrumenterTest.Module, result.ModulePath); @@ -120,9 +119,9 @@ public void TestInstrument(bool singleHit) [InlineData(false)] public void TestInstrumentCoreLib(bool singleHit) { - var instrumenterTest = CreateInstrumentor(fakeCoreLibModule: true, singleHit: singleHit); + InstrumenterTest instrumenterTest = CreateInstrumentor(fakeCoreLibModule: true, singleHit: singleHit); - var result = instrumenterTest.Instrumenter.Instrument(); + InstrumenterResult result = instrumenterTest.Instrumenter.Instrument(); Assert.Equal(Path.GetFileNameWithoutExtension(instrumenterTest.Module), result.Module); Assert.Equal(instrumenterTest.Module, result.ModulePath); @@ -135,13 +134,13 @@ public void TestInstrumentCoreLib(bool singleHit) [InlineData(typeof(ClassExcludedByCoverletCodeCoverageAttr))] public void TestInstrument_ClassesWithExcludeAttributeAreExcluded(Type excludedType) { - var instrumenterTest = CreateInstrumentor(); - var result = instrumenterTest.Instrumenter.Instrument(); + InstrumenterTest instrumenterTest = CreateInstrumentor(); + InstrumenterResult result = instrumenterTest.Instrumenter.Instrument(); - var doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs"); + Document doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs"); Assert.NotNull(doc); - var found = doc.Lines.Values.Any(l => l.Class == excludedType.FullName); + bool found = doc.Lines.Values.Any(l => l.Class == excludedType.FullName); Assert.False(found, "Class decorated with with exclude attribute should be excluded"); instrumenterTest.Directory.Delete(true); @@ -151,13 +150,13 @@ public void TestInstrument_ClassesWithExcludeAttributeAreExcluded(Type excludedT [InlineData(typeof(ClassExcludedByAttrWithoutAttributeNameSuffix), nameof(TestSDKAutoGeneratedCode))] public void TestInstrument_ClassesWithExcludeAttributeWithoutAttributeNameSuffixAreExcluded(Type excludedType, string excludedAttribute) { - var instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute }); - var result = instrumenterTest.Instrumenter.Instrument(); + InstrumenterTest instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute }); + InstrumenterResult result = instrumenterTest.Instrumenter.Instrument(); - var doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs"); + Document doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs"); Assert.NotNull(doc); - var found = doc.Lines.Values.Any(l => l.Class == excludedType.FullName); + bool found = doc.Lines.Values.Any(l => l.Class == excludedType.FullName); Assert.False(found, "Class decorated with with exclude attribute should be excluded"); instrumenterTest.Directory.Delete(true); @@ -169,13 +168,13 @@ public void TestInstrument_ClassesWithExcludeAttributeWithoutAttributeNameSuffix [InlineData(nameof(TestSDKAutoGeneratedCode))] public void TestInstrument_ClassesWithCustomExcludeAttributeAreExcluded(string excludedAttribute) { - var instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute }); - var result = instrumenterTest.Instrumenter.Instrument(); + InstrumenterTest instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute }); + InstrumenterResult result = instrumenterTest.Instrumenter.Instrument(); - var doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs"); + Document doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs"); Assert.NotNull(doc); #pragma warning disable CS0612 // Type or member is obsolete - var found = doc.Lines.Values.Any(l => l.Class.Equals(nameof(ClassExcludedByObsoleteAttr))); + bool found = doc.Lines.Values.Any(l => l.Class.Equals(nameof(ClassExcludedByObsoleteAttr))); #pragma warning restore CS0612 // Type or member is obsolete Assert.False(found, "Class decorated with with exclude attribute should be excluded"); @@ -188,14 +187,12 @@ public void TestInstrument_ClassesWithCustomExcludeAttributeAreExcluded(string e [InlineData(nameof(TestSDKAutoGeneratedCode), "ClassExcludedByAttrWithoutAttributeNameSuffix")] public void TestInstrument_ClassesWithMethodWithCustomExcludeAttributeAreExcluded(string excludedAttribute, string testClassName) { - var instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute }); - var result = instrumenterTest.Instrumenter.Instrument(); + InstrumenterTest instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute }); + InstrumenterResult result = instrumenterTest.Instrumenter.Instrument(); - var doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs"); + Document doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs"); Assert.NotNull(doc); -#pragma warning disable CS0612 // Type or member is obsolete - var found = doc.Lines.Values.Any(l => l.Method.Equals($"System.String Coverlet.Core.Samples.Tests.{testClassName}::Method(System.String)")); -#pragma warning restore CS0612 // Type or member is obsolete + bool found = doc.Lines.Values.Any(l => l.Method.Equals($"System.String Coverlet.Core.Samples.Tests.{testClassName}::Method(System.String)")); Assert.False(found, "Method decorated with with exclude attribute should be excluded"); instrumenterTest.Directory.Delete(true); @@ -207,19 +204,15 @@ public void TestInstrument_ClassesWithMethodWithCustomExcludeAttributeAreExclude [InlineData(nameof(TestSDKAutoGeneratedCode), "ClassExcludedByAttrWithoutAttributeNameSuffix")] public void TestInstrument_ClassesWithPropertyWithCustomExcludeAttributeAreExcluded(string excludedAttribute, string testClassName) { - var instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute }); - var result = instrumenterTest.Instrumenter.Instrument(); + InstrumenterTest instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute }); + InstrumenterResult result = instrumenterTest.Instrumenter.Instrument(); - var doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs"); + Document doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs"); Assert.NotNull(doc); -#pragma warning disable CS0612 // Type or member is obsolete - var getFound = doc.Lines.Values.Any(l => l.Method.Equals($"System.String Coverlet.Core.Samples.Tests.{testClassName}::get_Property()")); -#pragma warning restore CS0612 // Type or member is obsolete + bool getFound = doc.Lines.Values.Any(l => l.Method.Equals($"System.String Coverlet.Core.Samples.Tests.{testClassName}::get_Property()")); Assert.False(getFound, "Property getter decorated with with exclude attribute should be excluded"); -#pragma warning disable CS0612 // Type or member is obsolete - var setFound = doc.Lines.Values.Any(l => l.Method.Equals($"System.String Coverlet.Core.Samples.Tests.{testClassName}::set_Property()")); -#pragma warning restore CS0612 // Type or member is obsolete + bool setFound = doc.Lines.Values.Any(l => l.Method.Equals($"System.String Coverlet.Core.Samples.Tests.{testClassName}::set_Property()")); Assert.False(setFound, "Property setter decorated with with exclude attribute should be excluded"); instrumenterTest.Directory.Delete(true); @@ -248,7 +241,7 @@ private InstrumenterTest CreateInstrumentor(bool fakeCoreLibModule = false, stri File.Copy(module, Path.Combine(directory.FullName, destModule), true); File.Copy(pdb, Path.Combine(directory.FullName, destPdb), true); - InstrumentationHelper instrumentationHelper = + var instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object, new SourceRootTranslator(new Mock().Object, new FileSystem())); module = Path.Combine(directory.FullName, destModule); @@ -257,7 +250,7 @@ private InstrumenterTest CreateInstrumentor(bool fakeCoreLibModule = false, stri ExcludeAttributes = attributesToIgnore, DoesNotReturnAttributes = new string[] { "DoesNotReturnAttribute" } }; - Instrumenter instrumenter = new Instrumenter(module, identifier, parameters, _mockLogger.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(_mockLogger.Object, new FileSystem()), new CecilSymbolHelper()); + var instrumenter = new Instrumenter(module, identifier, parameters, _mockLogger.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(_mockLogger.Object, new FileSystem()), new CecilSymbolHelper()); return new InstrumenterTest { Instrumenter = instrumenter, @@ -281,7 +274,7 @@ class InstrumenterTest [Fact] public void TestInstrument_NetStandardAwareAssemblyResolver_FromRuntime() { - NetstandardAwareAssemblyResolver netstandardResolver = new NetstandardAwareAssemblyResolver(null, _mockLogger.Object); + var netstandardResolver = new NetstandardAwareAssemblyResolver(null, _mockLogger.Object); // We ask for "official" netstandard.dll implementation with know MS public key cc7b13ffcd2ddd51 same in all runtime AssemblyDefinition resolved = netstandardResolver.Resolve(AssemblyNameReference.Parse("netstandard, Version=0.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51")); @@ -298,7 +291,7 @@ public void TestInstrument_NetStandardAwareAssemblyResolver_FromFolder() // conflicts with "official" resolution // We create dummy netstandard.dll - CSharpCompilation compilation = CSharpCompilation.Create( + var compilation = CSharpCompilation.Create( "netstandard", new[] { CSharpSyntaxTree.ParseText("") }, new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) }, @@ -315,7 +308,7 @@ public void TestInstrument_NetStandardAwareAssemblyResolver_FromFolder() File.WriteAllBytes("netstandard.dll", dllStream.ToArray()); } - NetstandardAwareAssemblyResolver netstandardResolver = new NetstandardAwareAssemblyResolver(newAssemlby.Location, _mockLogger.Object); + var netstandardResolver = new NetstandardAwareAssemblyResolver(newAssemlby.Location, _mockLogger.Object); AssemblyDefinition resolved = netstandardResolver.Resolve(AssemblyNameReference.Parse(newAssemlby.FullName)); // We check if final netstandard.dll resolved is local folder one and not "official" netstandard.dll @@ -429,11 +422,11 @@ public void SkipEmbeddedPpdbWithoutLocalSource() string xunitDll = Directory.GetFiles(Directory.GetCurrentDirectory(), "xunit.core.dll").First(); var loggerMock = new Mock(); - InstrumentationHelper instrumentationHelper = - new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object, - new SourceRootTranslator(xunitDll, new Mock().Object, new FileSystem())); + var instrumentationHelper = + new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), loggerMock.Object, + new SourceRootTranslator(xunitDll, new Mock().Object, new FileSystem(), new AssemblyAdapter())); - Instrumenter instrumenter = new Instrumenter(xunitDll, "_xunit_instrumented", new CoverageParameters(), loggerMock.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(xunitDll, loggerMock.Object, new FileSystem()), new CecilSymbolHelper()); + var instrumenter = new Instrumenter(xunitDll, "_xunit_instrumented", new CoverageParameters(), loggerMock.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(xunitDll, loggerMock.Object, new FileSystem(), new AssemblyAdapter()), new CecilSymbolHelper()); Assert.True(instrumentationHelper.HasPdb(xunitDll, out bool embedded)); Assert.True(embedded); Assert.False(instrumenter.CanInstrument()); @@ -443,9 +436,9 @@ public void SkipEmbeddedPpdbWithoutLocalSource() string sample = Directory.GetFiles(Directory.GetCurrentDirectory(), "coverlet.tests.projectsample.empty.dll").First(); instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object, - new SourceRootTranslator(sample, new Mock().Object, new FileSystem())); + new SourceRootTranslator(Assembly.GetExecutingAssembly().Location, new Mock().Object, new FileSystem(), new AssemblyAdapter())); - instrumenter = new Instrumenter(sample, "_coverlet_tests_projectsample_empty", new CoverageParameters(), loggerMock.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(sample, loggerMock.Object, new FileSystem()), new CecilSymbolHelper()); + instrumenter = new Instrumenter(sample, "_coverlet_tests_projectsample_empty", new CoverageParameters(), loggerMock.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(Assembly.GetExecutingAssembly().Location, loggerMock.Object, new FileSystem(), new AssemblyAdapter()), new CecilSymbolHelper()); Assert.True(instrumentationHelper.HasPdb(sample, out embedded)); Assert.False(embedded); @@ -461,7 +454,7 @@ public void SkipPpdbWithoutLocalSource() string dllFileName = "75d9f96508d74def860a568f426ea4a4.dll"; string pdbFileName = "75d9f96508d74def860a568f426ea4a4.pdb"; - Mock partialMockFileSystem = new Mock(); + var partialMockFileSystem = new Mock(); partialMockFileSystem.CallBase = true; partialMockFileSystem.Setup(fs => fs.OpenRead(It.IsAny())).Returns((string path) => { @@ -486,16 +479,16 @@ public void SkipPpdbWithoutLocalSource() } }); - InstrumentationHelper instrumentationHelper = + var instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), partialMockFileSystem.Object, _mockLogger.Object, new SourceRootTranslator(_mockLogger.Object, new FileSystem())); string sample = Directory.GetFiles(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets"), dllFileName).First(); var loggerMock = new Mock(); - Instrumenter instrumenter = new Instrumenter(sample, "_75d9f96508d74def860a568f426ea4a4_instrumented", new CoverageParameters(), loggerMock.Object, instrumentationHelper, partialMockFileSystem.Object, new SourceRootTranslator(loggerMock.Object, new FileSystem()), new CecilSymbolHelper()); + var instrumenter = new Instrumenter(sample, "_75d9f96508d74def860a568f426ea4a4_instrumented", new CoverageParameters(), loggerMock.Object, instrumentationHelper, partialMockFileSystem.Object, new SourceRootTranslator(loggerMock.Object, new FileSystem()), new CecilSymbolHelper()); Assert.True(instrumentationHelper.HasPdb(sample, out bool embedded)); Assert.False(embedded); Assert.False(instrumenter.CanInstrument()); - loggerMock.Verify(l => l.LogVerbose(It.IsAny())); + _mockLogger.Verify(l => l.LogVerbose(It.IsAny())); } [Fact] @@ -503,7 +496,7 @@ public void TestInstrument_MissingModule() { var loggerMock = new Mock(); - InstrumentationHelper instrumentationHelper = + var instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object, new SourceRootTranslator(new Mock().Object, new FileSystem())); @@ -519,18 +512,32 @@ public void CanInstrumentFSharpAssemblyWithAnonymousRecord() var loggerMock = new Mock(); string sample = Directory.GetFiles(Directory.GetCurrentDirectory(), "coverlet.tests.projectsample.fsharp.dll").First(); - InstrumentationHelper instrumentationHelper = + var instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object, - new SourceRootTranslator(sample, new Mock().Object, new FileSystem())); + new SourceRootTranslator(Assembly.GetExecutingAssembly().Location, new Mock().Object, new FileSystem(), new AssemblyAdapter())); var instrumenter = new Instrumenter(sample, "_coverlet_tests_projectsample_fsharp", new CoverageParameters(), loggerMock.Object, instrumentationHelper, - new FileSystem(), new SourceRootTranslator(sample, loggerMock.Object, new FileSystem()), new CecilSymbolHelper()); - + new FileSystem(), new SourceRootTranslator(Assembly.GetExecutingAssembly().Location, loggerMock.Object, new FileSystem(), new AssemblyAdapter()), new CecilSymbolHelper()); + Assert.True(instrumentationHelper.HasPdb(sample, out bool embedded)); Assert.False(embedded); Assert.True(instrumenter.CanInstrument()); } + [Fact] + public void CanInstrument_AssemblySearchTypeNone_ReturnsTrue() + { + var loggerMock = new Mock(); + var instrumentationHelper = new Mock(); + bool embeddedPdb; + instrumentationHelper.Setup(x => x.HasPdb(It.IsAny(), out embeddedPdb)).Returns(true); + + var instrumenter = new Instrumenter(It.IsAny(), It.IsAny(), new CoverageParameters{ExcludeAssembliesWithoutSources = "None"}, + loggerMock.Object, instrumentationHelper.Object, new Mock().Object, new Mock().Object, new CecilSymbolHelper()); + + Assert.True(instrumenter.CanInstrument()); + } + [Theory] [InlineData("NotAMatch", new string[] { }, false)] [InlineData("ExcludeFromCoverageAttribute", new string[] { }, true)] @@ -542,8 +549,8 @@ public void TestInstrument_AssemblyMarkedAsExcludeFromCodeCoverage(string attrib { string EmitAssemblyToInstrument(string outputFolder) { - var attributeClassSyntaxTree = CSharpSyntaxTree.ParseText("[System.AttributeUsage(System.AttributeTargets.Assembly)]public class " + attributeName + ":System.Attribute{}"); - var instrumentableClassSyntaxTree = CSharpSyntaxTree.ParseText($@" + SyntaxTree attributeClassSyntaxTree = CSharpSyntaxTree.ParseText("[System.AttributeUsage(System.AttributeTargets.Assembly)]public class " + attributeName + ":System.Attribute{}"); + SyntaxTree instrumentableClassSyntaxTree = CSharpSyntaxTree.ParseText($@" [assembly:{attributeName}] namespace coverlet.tests.projectsample.excludedbyattribute{{ public class SampleClass @@ -556,26 +563,26 @@ public int SampleMethod() }} "); - var compilation = CSharpCompilation.Create(attributeName, new List + CSharpCompilation compilation = CSharpCompilation.Create(attributeName, new List { attributeClassSyntaxTree,instrumentableClassSyntaxTree }).AddReferences( MetadataReference.CreateFromFile(typeof(Attribute).Assembly.Location)). WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, false)); - var dllPath = Path.Combine(outputFolder, $"{attributeName}.dll"); - var pdbPath = Path.Combine(outputFolder, $"{attributeName}.pdb"); + string dllPath = Path.Combine(outputFolder, $"{attributeName}.dll"); + string pdbPath = Path.Combine(outputFolder, $"{attributeName}.pdb"); - using (var outputStream = File.Create(dllPath)) - using (var pdbStream = File.Create(pdbPath)) + using (FileStream outputStream = File.Create(dllPath)) + using (FileStream pdbStream = File.Create(pdbPath)) { - var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); var emitOptions = new EmitOptions(pdbFilePath: pdbPath); - var emitResult = compilation.Emit(outputStream, pdbStream, options: isWindows ? emitOptions : emitOptions.WithDebugInformationFormat(DebugInformationFormat.PortablePdb)); + EmitResult emitResult = compilation.Emit(outputStream, pdbStream, options: isWindows ? emitOptions : emitOptions.WithDebugInformationFormat(DebugInformationFormat.PortablePdb)); if (!emitResult.Success) { - var message = "Failure to dynamically create dll"; - foreach (var diagnostic in emitResult.Diagnostics) + string message = "Failure to dynamically create dll"; + foreach (Diagnostic diagnostic in emitResult.Diagnostics) { message += Environment.NewLine; message += diagnostic.GetMessage(); @@ -588,9 +595,9 @@ public int SampleMethod() string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(tempDirectory); - disposeAction = () => Directory.Delete(tempDirectory, true); + _disposeAction = () => Directory.Delete(tempDirectory, true); - Mock partialMockFileSystem = new Mock(); + var partialMockFileSystem = new Mock(); partialMockFileSystem.CallBase = true; partialMockFileSystem.Setup(fs => fs.NewFileStream(It.IsAny(), It.IsAny(), It.IsAny())).Returns((string path, FileMode mode, FileAccess access) => { @@ -600,30 +607,23 @@ public int SampleMethod() string excludedbyattributeDll = EmitAssemblyToInstrument(tempDirectory); - InstrumentationHelper instrumentationHelper = + var instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object, new SourceRootTranslator(new Mock().Object, new FileSystem())); CoverageParameters parametes = new(); parametes.ExcludeAttributes = excludedAttributes; - Instrumenter instrumenter = new Instrumenter(excludedbyattributeDll, "_xunit_excludedbyattribute", parametes, loggerMock.Object, instrumentationHelper, partialMockFileSystem.Object, new SourceRootTranslator(loggerMock.Object, new FileSystem()), new CecilSymbolHelper()); + var instrumenter = new Instrumenter(excludedbyattributeDll, "_xunit_excludedbyattribute", parametes, loggerMock.Object, instrumentationHelper, partialMockFileSystem.Object, new SourceRootTranslator(loggerMock.Object, new FileSystem()), new CecilSymbolHelper()); InstrumenterResult result = instrumenter.Instrument(); - if (expectedExcludes) - { - Assert.Empty(result.Documents); - loggerMock.Verify(l => l.LogVerbose(It.IsAny())); - } - else - { - Assert.NotEmpty(result.Documents); - } + Assert.Empty(result.Documents); + if (expectedExcludes) { loggerMock.Verify(l => l.LogVerbose(It.IsAny())); } } [Fact] public void TestInstrument_AspNetCoreSharedFrameworkResolver() { - AspNetCoreSharedFrameworkResolver resolver = new AspNetCoreSharedFrameworkResolver(_mockLogger.Object); - CompilationLibrary compilationLibrary = new CompilationLibrary( + var resolver = new AspNetCoreSharedFrameworkResolver(_mockLogger.Object); + var compilationLibrary = new CompilationLibrary( "package", "Microsoft.Extensions.Logging.Abstractions", "2.2.0", @@ -632,7 +632,7 @@ public void TestInstrument_AspNetCoreSharedFrameworkResolver() Enumerable.Empty(), true); - List assemblies = new List(); + var assemblies = new List(); Assert.True(resolver.TryResolveAssemblyPaths(compilationLibrary, assemblies)); Assert.NotEmpty(assemblies); } @@ -640,7 +640,7 @@ public void TestInstrument_AspNetCoreSharedFrameworkResolver() [Fact] public void TestInstrument_NetstandardAwareAssemblyResolver_PreserveCompilationContext() { - NetstandardAwareAssemblyResolver netstandardResolver = new NetstandardAwareAssemblyResolver(Assembly.GetExecutingAssembly().Location, _mockLogger.Object); + var netstandardResolver = new NetstandardAwareAssemblyResolver(Assembly.GetExecutingAssembly().Location, _mockLogger.Object); AssemblyDefinition asm = netstandardResolver.TryWithCustomResolverOnDotNetCore(new AssemblyNameReference("Microsoft.Extensions.Logging.Abstractions", new Version("2.2.0"))); Assert.NotNull(asm); } @@ -648,10 +648,10 @@ public void TestInstrument_NetstandardAwareAssemblyResolver_PreserveCompilationC [Fact] public void TestInstrument_LambdaInsideMethodWithExcludeAttributeAreExcluded() { - var instrumenterTest = CreateInstrumentor(); - var result = instrumenterTest.Instrumenter.Instrument(); + InstrumenterTest instrumenterTest = CreateInstrumentor(); + InstrumenterResult result = instrumenterTest.Instrumenter.Instrument(); - var doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Instrumentation.ExcludeFromCoverage.cs"); + Document doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Instrumentation.ExcludeFromCoverage.cs"); Assert.NotNull(doc); Assert.Contains(doc.Lines.Values, l => l.Method == "System.Int32 Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr::TestLambda(System.String,System.Int32)"); @@ -669,10 +669,10 @@ public void TestInstrument_LambdaInsideMethodWithExcludeAttributeAreExcluded() [Fact] public void TestInstrument_LocalFunctionInsideMethodWithExcludeAttributeAreExcluded() { - var instrumenterTest = CreateInstrumentor(); - var result = instrumenterTest.Instrumenter.Instrument(); + InstrumenterTest instrumenterTest = CreateInstrumentor(); + InstrumenterResult result = instrumenterTest.Instrumenter.Instrument(); - var doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Instrumentation.ExcludeFromCoverage.cs"); + Document doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Instrumentation.ExcludeFromCoverage.cs"); Assert.NotNull(doc); Assert.Contains(doc.Lines.Values, l => l.Method == "System.Int32 Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr::TestLocalFunction(System.String,System.Int32)"); @@ -694,10 +694,10 @@ public void TestInstrument_LocalFunctionInsideMethodWithExcludeAttributeAreExclu [Fact] public void TestInstrument_YieldInsideMethodWithExcludeAttributeAreExcluded() { - var instrumenterTest = CreateInstrumentor(); - var result = instrumenterTest.Instrumenter.Instrument(); + InstrumenterTest instrumenterTest = CreateInstrumentor(); + InstrumenterResult result = instrumenterTest.Instrumenter.Instrument(); - var doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Instrumentation.ExcludeFromCoverage.cs"); + Document doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Instrumentation.ExcludeFromCoverage.cs"); Assert.NotNull(doc); Assert.DoesNotContain(doc.Lines.Values, l => l.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr/") && @@ -715,10 +715,10 @@ public void TestInstrument_YieldInsideMethodWithExcludeAttributeAreExcluded() [Fact] public void TestInstrument_AsyncAwaitInsideMethodWithExcludeAttributeAreExcluded() { - var instrumenterTest = CreateInstrumentor(); - var result = instrumenterTest.Instrumenter.Instrument(); + InstrumenterTest instrumenterTest = CreateInstrumentor(); + InstrumenterResult result = instrumenterTest.Instrumenter.Instrument(); - var doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Instrumentation.ExcludeFromCoverage.cs"); + Document doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Instrumentation.ExcludeFromCoverage.cs"); Assert.NotNull(doc); Assert.DoesNotContain(doc.Lines.Values, l => l.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr/") && @@ -736,7 +736,7 @@ public void TestInstrument_AsyncAwaitInsideMethodWithExcludeAttributeAreExcluded [Fact] public void TestReachabilityHelper() { - var allInstrumentableLines = + int[] allInstrumentableLines = new[] { // Throws @@ -764,7 +764,7 @@ public void TestReachabilityHelper() // FiltersAndFinallies 171, 173, 174, 175, 176, 177, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 192, 193, 194, 195, 196, 197 }; - var notReachableLines = + int[] notReachableLines = new[] { // NoBranches @@ -787,12 +787,12 @@ public void TestReachabilityHelper() 176, 177, 183, 184, 189, 190, 195, 196, 197 }; - var expectedToBeInstrumented = allInstrumentableLines.Except(notReachableLines).ToArray(); + int[] expectedToBeInstrumented = allInstrumentableLines.Except(notReachableLines).ToArray(); - var instrumenterTest = CreateInstrumentor(); - var result = instrumenterTest.Instrumenter.Instrument(); + InstrumenterTest instrumenterTest = CreateInstrumentor(); + InstrumenterResult result = instrumenterTest.Instrumenter.Instrument(); - var doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Instrumentation.DoesNotReturn.cs"); + Document doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Instrumentation.DoesNotReturn.cs"); // check for instrumented lines doc.AssertNonInstrumentedLines(BuildConfiguration.Debug, notReachableLines); @@ -801,6 +801,35 @@ public void TestReachabilityHelper() instrumenterTest.Directory.Delete(true); } + [Fact] + public void Instrumenter_MethodsWithoutReferenceToSource_AreSkipped() + { + var loggerMock = new Mock(); + + string module = Directory.GetFiles(Directory.GetCurrentDirectory(), "coverlet.tests.projectsample.vbmynamespace.dll").First(); + string pdb = Path.Combine(Path.GetDirectoryName(module), Path.GetFileNameWithoutExtension(module) + ".pdb"); + + DirectoryInfo directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); + File.Copy(module, Path.Combine(directory.FullName, Path.GetFileName(module)), true); + File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true); + + var instrumentationHelper = + new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object, + new SourceRootTranslator(module, new Mock().Object, new FileSystem(), new AssemblyAdapter())); + + CoverageParameters parameters = new(); + + var instrumenter = new Instrumenter(Path.Combine(directory.FullName, Path.GetFileName(module)), "_coverlet_tests_projectsample_vbmynamespace", parameters, + loggerMock.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(Path.Combine(directory.FullName, Path.GetFileName(module)), loggerMock.Object, new FileSystem(), new AssemblyAdapter()), new CecilSymbolHelper()); + + instrumentationHelper.BackupOriginalModule(Path.Combine(directory.FullName, Path.GetFileName(module)), "_coverlet_tests_projectsample_vbmynamespace"); + + InstrumenterResult result = instrumenter.Instrument(); + + Assert.False(result.Documents.ContainsKey(string.Empty)); + + directory.Delete(true); + } } } diff --git a/test/coverlet.core.tests/Instrumentation/ModuleTrackerTemplateTests.cs b/test/coverlet.core.tests/Instrumentation/ModuleTrackerTemplateTests.cs index 7ca7d5c4d..7cd5f2943 100644 --- a/test/coverlet.core.tests/Instrumentation/ModuleTrackerTemplateTests.cs +++ b/test/coverlet.core.tests/Instrumentation/ModuleTrackerTemplateTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using System.Collections.Generic; using System.IO; using System.Threading; @@ -27,7 +30,7 @@ public void Dispose() public class ModuleTrackerTemplateTests : ExternalProcessExecutionTest { - private static readonly Task _success = Task.FromResult(0); + private static readonly Task s_success = Task.FromResult(0); [Fact] public void HitsFileCorrectlyWritten() @@ -38,10 +41,10 @@ public void HitsFileCorrectlyWritten() ModuleTrackerTemplate.HitsArray = new[] { 1, 2, 0, 3 }; ModuleTrackerTemplate.UnloadModule(null, null); - var expectedHitsArray = new[] { 1, 2, 0, 3 }; + int[] expectedHitsArray = new[] { 1, 2, 0, 3 }; Assert.Equal(expectedHitsArray, ReadHitsFile()); - return _success; + return s_success; }); } @@ -54,7 +57,7 @@ public void HitsFileWithDifferentNumberOfEntriesCausesExceptionOnUnload() WriteHitsFile(new[] { 1, 2, 3 }); ModuleTrackerTemplate.HitsArray = new[] { 1 }; Assert.Throws(() => ModuleTrackerTemplate.UnloadModule(null, null)); - return _success; + return s_success; }); } @@ -63,7 +66,7 @@ public void HitsOnMultipleThreadsCorrectlyCounted() { FunctionExecutor.Run(() => { - List threads = new List(); + var threads = new List(); using var ctx = new TrackerContext(); ModuleTrackerTemplate.HitsArray = new[] { 0, 0, 0, 0 }; for (int i = 0; i < ModuleTrackerTemplate.HitsArray.Length; ++i) @@ -79,19 +82,19 @@ public void HitsOnMultipleThreadsCorrectlyCounted() } ModuleTrackerTemplate.UnloadModule(null, null); - var expectedHitsArray = new[] { 4, 3, 2, 1 }; + int[] expectedHitsArray = new[] { 4, 3, 2, 1 }; Assert.Equal(expectedHitsArray, ReadHitsFile()); static void HitIndex(object index) { - var hitIndex = (int)index; + int hitIndex = (int)index; for (int i = 0; i <= hitIndex; ++i) { ModuleTrackerTemplate.RecordHit(i); } } - return _success; + return s_success; }); } @@ -107,10 +110,10 @@ public void MultipleSequentialUnloadsHaveCorrectTotalData() ModuleTrackerTemplate.HitsArray = new[] { 0, 1, 2, 3 }; ModuleTrackerTemplate.UnloadModule(null, null); - var expectedHitsArray = new[] { 0, 4, 4, 4 }; + int[] expectedHitsArray = new[] { 0, 4, 4, 4 }; Assert.Equal(expectedHitsArray, ReadHitsFile()); - return _success; + return s_success; }); } @@ -137,7 +140,7 @@ public void MutexBlocksMultipleWriters() mutex.ReleaseMutex(); await unloadTask; - var expectedHitsArray = new[] { 0, 4, 4, 4 }; + int[] expectedHitsArray = new[] { 0, 4, 4, 4 }; Assert.Equal(expectedHitsArray, ReadHitsFile()); } @@ -146,32 +149,28 @@ public void MutexBlocksMultipleWriters() } - private void WriteHitsFile(int[] hitsArray) + private static void WriteHitsFile(int[] hitsArray) { - using (var fs = new FileStream(ModuleTrackerTemplate.HitsFilePath, FileMode.Create)) - using (var bw = new BinaryWriter(fs)) + using var fs = new FileStream(ModuleTrackerTemplate.HitsFilePath, FileMode.Create); + using var bw = new BinaryWriter(fs); + bw.Write(hitsArray.Length); + foreach (int hitCount in hitsArray) { - bw.Write(hitsArray.Length); - foreach (int hitCount in hitsArray) - { - bw.Write(hitCount); - } + bw.Write(hitCount); } } - private int[] ReadHitsFile() + private static int[] ReadHitsFile() { - using (var fs = new FileStream(ModuleTrackerTemplate.HitsFilePath, FileMode.Open)) - using (var br = new BinaryReader(fs)) + using var fs = new FileStream(ModuleTrackerTemplate.HitsFilePath, FileMode.Open); + using var br = new BinaryReader(fs); + int[] hitsArray = new int[br.ReadInt32()]; + for (int i = 0; i < hitsArray.Length; ++i) { - var hitsArray = new int[br.ReadInt32()]; - for (int i = 0; i < hitsArray.Length; ++i) - { - hitsArray[i] = br.ReadInt32(); - } - - return hitsArray; + hitsArray[i] = br.ReadInt32(); } + + return hitsArray; } } } diff --git a/test/coverlet.core.tests/Properties/AssemblyInfo.cs b/test/coverlet.core.tests/Properties/AssemblyInfo.cs index 78d7bf29b..33d7581ec 100644 --- a/test/coverlet.core.tests/Properties/AssemblyInfo.cs +++ b/test/coverlet.core.tests/Properties/AssemblyInfo.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System.Reflection; [assembly: AssemblyKeyFile("coverlet.core.tests.snk")] \ No newline at end of file diff --git a/test/coverlet.core.tests/Reporters/CoberturaReporterTests.cs b/test/coverlet.core.tests/Reporters/CoberturaReporterTests.cs index f92c922d2..f54174b70 100644 --- a/test/coverlet.core.tests/Reporters/CoberturaReporterTests.cs +++ b/test/coverlet.core.tests/Reporters/CoberturaReporterTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.Collections.Generic; using System.Globalization; @@ -7,7 +10,6 @@ using System.Text; using System.Threading; using System.Xml.Linq; - using Coverlet.Core.Abstractions; using Moq; using Xunit; @@ -19,27 +21,27 @@ public class CoberturaReporterTests [Fact] public void TestReport() { - CoverageResult result = new CoverageResult(); + var result = new CoverageResult(); result.Identifier = Guid.NewGuid().ToString(); - Lines lines = new Lines(); + var lines = new Lines(); lines.Add(1, 1); lines.Add(2, 0); - Branches branches = new Branches(); + var branches = new Branches(); branches.Add(new BranchInfo { Line = 1, Hits = 1, Offset = 23, EndOffset = 24, Path = 0, Ordinal = 1 }); branches.Add(new BranchInfo { Line = 1, Hits = 0, Offset = 23, EndOffset = 27, Path = 1, Ordinal = 2 }); - Methods methods = new Methods(); - var methodString = "System.Void Coverlet.Core.Reporters.Tests.CoberturaReporterTests::TestReport()"; + var methods = new Methods(); + string methodString = "System.Void Coverlet.Core.Reporters.Tests.CoberturaReporterTests::TestReport()"; methods.Add(methodString, new Method()); methods[methodString].Lines = lines; methods[methodString].Branches = branches; - Classes classes = new Classes(); + var classes = new Classes(); classes.Add("Coverlet.Core.Reporters.Tests.CoberturaReporterTests", methods); - Documents documents = new Documents(); + var documents = new Documents(); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -62,15 +64,15 @@ public void TestReport() // where decimal char is comma. Assert.Equal("1,5", (1.5).ToString()); - CoberturaReporter reporter = new CoberturaReporter(); + var reporter = new CoberturaReporter(); string report = reporter.Report(result, new Mock().Object); Assert.NotEmpty(report); var doc = XDocument.Load(new MemoryStream(Encoding.UTF8.GetBytes(report))); - var matchingRateAttributes = doc.Descendants().Attributes().Where(attr => attr.Name.LocalName.EndsWith("-rate")); - var rateParentNodeNames = matchingRateAttributes.Select(attr => attr.Parent.Name.LocalName); + IEnumerable matchingRateAttributes = doc.Descendants().Attributes().Where(attr => attr.Name.LocalName.EndsWith("-rate")); + IEnumerable rateParentNodeNames = matchingRateAttributes.Select(attr => attr.Parent.Name.LocalName); Assert.Contains("package", rateParentNodeNames); Assert.Contains("class", rateParentNodeNames); Assert.Contains("method", rateParentNodeNames); @@ -82,8 +84,8 @@ public void TestReport() Assert.Equal(0.5, double.Parse(value, CultureInfo.InvariantCulture)); }); - var matchingComplexityAttributes = doc.Descendants().Attributes().Where(attr => attr.Name.LocalName.Equals("complexity")); - var complexityParentNodeNames = matchingComplexityAttributes.Select(attr => attr.Parent.Name.LocalName); + IEnumerable matchingComplexityAttributes = doc.Descendants().Attributes().Where(attr => attr.Name.LocalName.Equals("complexity")); + IEnumerable complexityParentNodeNames = matchingComplexityAttributes.Select(attr => attr.Parent.Name.LocalName); Assert.Contains("package", complexityParentNodeNames); Assert.Contains("class", complexityParentNodeNames); Assert.Contains("method", complexityParentNodeNames); @@ -113,25 +115,25 @@ public void TestEnsureParseMethodStringCorrectly( string expectedMethodName, string expectedSignature) { - CoverageResult result = new CoverageResult(); + var result = new CoverageResult(); result.Parameters = new CoverageParameters(); result.Identifier = Guid.NewGuid().ToString(); - Lines lines = new Lines(); + var lines = new Lines(); lines.Add(1, 1); - Branches branches = new Branches(); + var branches = new Branches(); branches.Add(new BranchInfo { Line = 1, Hits = 1, Offset = 23, EndOffset = 24, Path = 0, Ordinal = 1 }); - Methods methods = new Methods(); + var methods = new Methods(); methods.Add(methodString, new Method()); methods[methodString].Lines = lines; methods[methodString].Branches = branches; - Classes classes = new Classes(); + var classes = new Classes(); classes.Add("Google.Protobuf.Reflection.MessageDescriptor", methods); - Documents documents = new Documents(); + var documents = new Documents(); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { documents.Add(@"C:\doc.cs", classes); @@ -144,7 +146,7 @@ public void TestEnsureParseMethodStringCorrectly( result.Modules = new Modules(); result.Modules.Add("module", documents); - CoberturaReporter reporter = new CoberturaReporter(); + var reporter = new CoberturaReporter(); string report = reporter.Report(result, new Mock().Object); Assert.NotEmpty(report); @@ -161,7 +163,7 @@ public void TestEnsureParseMethodStringCorrectly( [Fact] public void TestReportWithDifferentDirectories() { - CoverageResult result = new CoverageResult(); + var result = new CoverageResult(); result.Parameters = new CoverageParameters(); result.Identifier = Guid.NewGuid().ToString(); @@ -215,16 +217,16 @@ public void TestReportWithDifferentDirectories() result.Modules = new Modules { { "Module", documents } }; - CoberturaReporter reporter = new CoberturaReporter(); + var reporter = new CoberturaReporter(); string report = reporter.Report(result, new Mock().Object); var doc = XDocument.Load(new MemoryStream(Encoding.UTF8.GetBytes(report))); - List basePaths = doc.Element("coverage").Element("sources").Elements().Select(e => e.Value).ToList(); - List relativePaths = doc.Element("coverage").Element("packages").Element("package") + var basePaths = doc.Element("coverage").Element("sources").Elements().Select(e => e.Value).ToList(); + var relativePaths = doc.Element("coverage").Element("packages").Element("package") .Element("classes").Elements().Select(e => e.Attribute("filename").Value).ToList(); - List possiblePaths = new List(); + var possiblePaths = new List(); foreach (string basePath in basePaths) { foreach (string relativePath in relativePaths) @@ -247,9 +249,9 @@ public void TestReportWithDifferentDirectories() [Fact] public void TestReportWithSourcelinkPaths() { - CoverageResult result = new CoverageResult { Parameters = new CoverageParameters() { UseSourceLink = true }, Identifier = Guid.NewGuid().ToString() }; + var result = new CoverageResult { Parameters = new CoverageParameters() { UseSourceLink = true }, Identifier = Guid.NewGuid().ToString() }; - var absolutePath = + string absolutePath = @"https://raw.githubusercontent.com/johndoe/Coverlet/02c09baa8bfdee3b6cdf4be89bd98c8157b0bc08/Demo.cs"; var classes = new Classes { { "Class", new Methods() } }; @@ -257,14 +259,14 @@ public void TestReportWithSourcelinkPaths() result.Modules = new Modules { { "Module", documents } }; - CoberturaReporter reporter = new CoberturaReporter(); + var reporter = new CoberturaReporter(); string report = reporter.Report(result, new Mock().Object); var doc = XDocument.Load(new MemoryStream(Encoding.UTF8.GetBytes(report))); - var fileName = doc.Element("coverage").Element("packages").Element("package").Element("classes").Elements() + string fileName = doc.Element("coverage").Element("packages").Element("package").Element("classes").Elements() .Select(e => e.Attribute("filename").Value).Single(); Assert.Equal(absolutePath, fileName); } } -} \ No newline at end of file +} diff --git a/test/coverlet.core.tests/Reporters/JsonReporterTests.cs b/test/coverlet.core.tests/Reporters/JsonReporterTests.cs index 910b891a0..b6215befe 100644 --- a/test/coverlet.core.tests/Reporters/JsonReporterTests.cs +++ b/test/coverlet.core.tests/Reporters/JsonReporterTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using Coverlet.Core.Abstractions; using Moq; using System; @@ -10,28 +13,28 @@ public class JsonReporterTests [Fact] public void TestReport() { - CoverageResult result = new CoverageResult(); + var result = new CoverageResult(); result.Identifier = Guid.NewGuid().ToString(); - Lines lines = new Lines(); + var lines = new Lines(); lines.Add(1, 1); lines.Add(2, 0); - Methods methods = new Methods(); - var methodString = "System.Void Coverlet.Core.Reporters.Tests.JsonReporterTests.TestReport()"; + var methods = new Methods(); + string methodString = "System.Void Coverlet.Core.Reporters.Tests.JsonReporterTests.TestReport()"; methods.Add(methodString, new Method()); methods[methodString].Lines = lines; - Classes classes = new Classes(); + var classes = new Classes(); classes.Add("Coverlet.Core.Reporters.Tests.JsonReporterTests", methods); - Documents documents = new Documents(); + var documents = new Documents(); documents.Add("doc.cs", classes); result.Modules = new Modules(); result.Modules.Add("module", documents); - JsonReporter reporter = new JsonReporter(); + var reporter = new JsonReporter(); Assert.NotEqual("{\n}", reporter.Report(result, new Mock().Object)); Assert.NotEqual(string.Empty, reporter.Report(result, new Mock().Object)); } diff --git a/test/coverlet.core.tests/Reporters/LcovReporterTests.cs b/test/coverlet.core.tests/Reporters/LcovReporterTests.cs index 9df9f33ca..b7462f152 100644 --- a/test/coverlet.core.tests/Reporters/LcovReporterTests.cs +++ b/test/coverlet.core.tests/Reporters/LcovReporterTests.cs @@ -1,7 +1,9 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using Coverlet.Core.Abstractions; using Moq; using System; -using System.Collections.Generic; using Xunit; namespace Coverlet.Core.Reporters.Tests @@ -11,33 +13,33 @@ public class LcovReporterTests [Fact] public void TestReport() { - CoverageResult result = new CoverageResult(); + var result = new CoverageResult(); result.Parameters = new CoverageParameters(); result.Identifier = Guid.NewGuid().ToString(); - Lines lines = new Lines(); + var lines = new Lines(); lines.Add(1, 1); lines.Add(2, 0); - Branches branches = new Branches(); + var branches = new Branches(); branches.Add(new BranchInfo { Line = 1, Hits = 1, Offset = 23, EndOffset = 24, Path = 0, Ordinal = 1 }); branches.Add(new BranchInfo { Line = 1, Hits = 0, Offset = 23, EndOffset = 27, Path = 1, Ordinal = 2 }); - Methods methods = new Methods(); - var methodString = "System.Void Coverlet.Core.Reporters.Tests.LcovReporterTests.TestReport()"; + var methods = new Methods(); + string methodString = "System.Void Coverlet.Core.Reporters.Tests.LcovReporterTests.TestReport()"; methods.Add(methodString, new Method()); methods[methodString].Lines = lines; methods[methodString].Branches = branches; - Classes classes = new Classes(); + var classes = new Classes(); classes.Add("Coverlet.Core.Reporters.Tests.LcovReporterTests", methods); - Documents documents = new Documents(); + var documents = new Documents(); documents.Add("doc.cs", classes); result.Modules = new Modules(); result.Modules.Add("module", documents); - LcovReporter reporter = new LcovReporter(); + var reporter = new LcovReporter(); string report = reporter.Report(result, new Mock().Object); Assert.NotEmpty(report); diff --git a/test/coverlet.core.tests/Reporters/OpenCoverReporterTests.cs b/test/coverlet.core.tests/Reporters/OpenCoverReporterTests.cs index 828e4ab42..678ea9ae2 100644 --- a/test/coverlet.core.tests/Reporters/OpenCoverReporterTests.cs +++ b/test/coverlet.core.tests/Reporters/OpenCoverReporterTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using Coverlet.Core.Abstractions; using Moq; using System; @@ -14,17 +17,17 @@ public class OpenCoverReporterTests [Fact] public void TestReport() { - CoverageResult result = new CoverageResult(); + var result = new CoverageResult(); result.Parameters = new CoverageParameters(); result.Identifier = Guid.NewGuid().ToString(); result.Modules = new Modules(); result.Modules.Add("Coverlet.Core.Reporters.Tests", CreateFirstDocuments()); - OpenCoverReporter reporter = new OpenCoverReporter(); + var reporter = new OpenCoverReporter(); string report = reporter.Report(result, new Mock().Object); Assert.NotEmpty(report); - XDocument doc = XDocument.Load(new MemoryStream(Encoding.UTF8.GetBytes(report))); + var doc = XDocument.Load(new MemoryStream(Encoding.UTF8.GetBytes(report))); Assert.Empty(doc.Descendants().Attributes("sequenceCoverage").Where(v => v.Value != "33.33")); Assert.Empty(doc.Descendants().Attributes("branchCoverage").Where(v => v.Value != "25")); Assert.Empty(doc.Descendants().Attributes("nPathComplexity").Where(v => v.Value != "4")); @@ -33,7 +36,7 @@ public void TestReport() [Fact] public void TestFilesHaveUniqueIdsOverMultipleModules() { - CoverageResult result = new CoverageResult(); + var result = new CoverageResult(); result.Parameters = new CoverageParameters(); result.Identifier = Guid.NewGuid().ToString(); @@ -41,8 +44,8 @@ public void TestFilesHaveUniqueIdsOverMultipleModules() result.Modules.Add("Coverlet.Core.Reporters.Tests", CreateFirstDocuments()); result.Modules.Add("Some.Other.Module", CreateSecondDocuments()); - OpenCoverReporter reporter = new OpenCoverReporter(); - var xml = reporter.Report(result, new Mock().Object); + var reporter = new OpenCoverReporter(); + string xml = reporter.Report(result, new Mock().Object); Assert.NotEqual(string.Empty, xml); Assert.Contains(@"", xml); @@ -59,7 +62,7 @@ public void TestLineBranchCoverage() Parameters = new CoverageParameters() }; - var xml = new OpenCoverReporter().Report(result, new Mock().Object); + string xml = new OpenCoverReporter().Report(result, new Mock().Object); // Line 1: Two branches, no coverage (bec = 2, bev = 0) Assert.Contains(@"", xml); @@ -76,27 +79,27 @@ public void TestLineBranchCoverage() private static Documents CreateFirstDocuments() { - Lines lines = new Lines(); + var lines = new Lines(); lines.Add(1, 1); lines.Add(2, 0); lines.Add(3, 0); - Branches branches = new Branches(); + var branches = new Branches(); branches.Add(new BranchInfo { Line = 1, Hits = 1, Offset = 23, EndOffset = 24, Path = 0, Ordinal = 1 }); branches.Add(new BranchInfo { Line = 1, Hits = 0, Offset = 23, EndOffset = 27, Path = 1, Ordinal = 2 }); branches.Add(new BranchInfo { Line = 1, Hits = 0, Offset = 40, EndOffset = 41, Path = 0, Ordinal = 3 }); branches.Add(new BranchInfo { Line = 1, Hits = 0, Offset = 40, EndOffset = 44, Path = 1, Ordinal = 4 }); - Methods methods = new Methods(); - var methodString = "System.Void Coverlet.Core.Reporters.Tests.OpenCoverReporterTests.TestReport()"; + var methods = new Methods(); + string methodString = "System.Void Coverlet.Core.Reporters.Tests.OpenCoverReporterTests.TestReport()"; methods.Add(methodString, new Method()); methods[methodString].Lines = lines; methods[methodString].Branches = branches; - Classes classes = new Classes(); + var classes = new Classes(); classes.Add("Coverlet.Core.Reporters.Tests.OpenCoverReporterTests", methods); - Documents documents = new Documents(); + var documents = new Documents(); documents.Add("doc.cs", classes); return documents; @@ -104,16 +107,16 @@ private static Documents CreateFirstDocuments() private static Documents CreateSecondDocuments() { - Lines lines = new Lines(); + var lines = new Lines(); lines.Add(1, 1); lines.Add(2, 0); - Methods methods = new Methods(); - var methodString = "System.Void Some.Other.Module.TestClass.TestMethod()"; + var methods = new Methods(); + string methodString = "System.Void Some.Other.Module.TestClass.TestMethod()"; methods.Add(methodString, new Method()); methods[methodString].Lines = lines; - Classes classes2 = new Classes(); + var classes2 = new Classes(); classes2.Add("Some.Other.Module.TestClass", methods); var documents = new Documents(); diff --git a/test/coverlet.core.tests/Reporters/ReporterFactoryTests.cs b/test/coverlet.core.tests/Reporters/ReporterFactoryTests.cs index e148fa378..1b0f7ce04 100644 --- a/test/coverlet.core.tests/Reporters/ReporterFactoryTests.cs +++ b/test/coverlet.core.tests/Reporters/ReporterFactoryTests.cs @@ -1,4 +1,6 @@ -using Coverlet.Core.Reporters; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using Xunit; namespace Coverlet.Core.Reporters.Tests diff --git a/test/coverlet.core.tests/Reporters/Reporters.cs b/test/coverlet.core.tests/Reporters/Reporters.cs index 5c65f0770..f62886407 100644 --- a/test/coverlet.core.tests/Reporters/Reporters.cs +++ b/test/coverlet.core.tests/Reporters/Reporters.cs @@ -1,5 +1,7 @@ -using System.IO; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.IO; using Coverlet.Core.Abstractions; using Coverlet.MSbuild.Tasks; using Moq; @@ -29,10 +31,10 @@ public class Reporters [InlineData("netcoreapp2.2", "file", "cobertura", "file.netcoreapp2.2.cobertura.xml")] public void Msbuild_ReportWriter(string coverletMultiTargetFrameworksCurrentTFM, string coverletOutput, string reportFormat, string expectedFileName) { - Mock fileSystem = new Mock(); - Mock console = new Mock(); + var fileSystem = new Mock(); + var console = new Mock(); - ReportWriter reportWriter = new ReportWriter( + var reportWriter = new ReportWriter( coverletMultiTargetFrameworksCurrentTFM, // mimic code inside CoverageResultTask.cs Path.GetDirectoryName(coverletOutput), @@ -42,7 +44,7 @@ public void Msbuild_ReportWriter(string coverletMultiTargetFrameworksCurrentTFM, console.Object, new CoverageResult() { Modules = new Modules(), Parameters = new CoverageParameters() }, null); - var path = reportWriter.WriteReport(); + string path = reportWriter.WriteReport(); // Path.Combine depends on OS so we can change only win side to avoid duplication Assert.Equal(path.Replace('/', Path.DirectorySeparatorChar), expectedFileName.Replace('/', Path.DirectorySeparatorChar)); } diff --git a/test/coverlet.core.tests/Reporters/TeamCityReporter.cs b/test/coverlet.core.tests/Reporters/TeamCityReporter.cs index 7ff12a0b1..95fb2642f 100644 --- a/test/coverlet.core.tests/Reporters/TeamCityReporter.cs +++ b/test/coverlet.core.tests/Reporters/TeamCityReporter.cs @@ -1,5 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using Coverlet.Core.Abstractions; using Moq; using Xunit; @@ -52,7 +54,7 @@ public TestCreateReporterTests() }; var methods = new Methods(); - var methodString = "System.Void Coverlet.Core.Reporters.Tests.CoberturaReporterTests::TestReport()"; + string methodString = "System.Void Coverlet.Core.Reporters.Tests.CoberturaReporterTests::TestReport()"; methods.Add(methodString, new Method()); methods[methodString].Lines = lines; methods[methodString].Branches = branches; @@ -89,7 +91,7 @@ public void Format_IsNull() public void Report_ReturnsNonNullString() { // Act - var output = _reporter.Report(_result, new Mock().Object); + string output = _reporter.Report(_result, new Mock().Object); // Assert Assert.False(string.IsNullOrWhiteSpace(output), "Output is not null or whitespace"); @@ -99,7 +101,7 @@ public void Report_ReturnsNonNullString() public void Report_ReportsLineCoverage() { // Act - var output = _reporter.Report(_result, new Mock().Object); + string output = _reporter.Report(_result, new Mock().Object); // Assert Assert.Contains("##teamcity[buildStatisticValue key='CodeCoverageAbsLCovered' value='1']", output); @@ -110,7 +112,7 @@ public void Report_ReportsLineCoverage() public void Report_ReportsBranchCoverage() { // Act - var output = _reporter.Report(_result, new Mock().Object); + string output = _reporter.Report(_result, new Mock().Object); // Assert Assert.Contains("##teamcity[buildStatisticValue key='CodeCoverageAbsBCovered' value='1']", output); @@ -121,11 +123,11 @@ public void Report_ReportsBranchCoverage() public void Report_ReportsMethodCoverage() { // Act - var output = _reporter.Report(_result, new Mock().Object); + string output = _reporter.Report(_result, new Mock().Object); // Assert Assert.Contains("##teamcity[buildStatisticValue key='CodeCoverageAbsMCovered' value='1']", output); Assert.Contains("##teamcity[buildStatisticValue key='CodeCoverageAbsMTotal' value='1']", output); } } -} \ No newline at end of file +} diff --git a/test/coverlet.core.tests/Samples/.editorconfig b/test/coverlet.core.tests/Samples/.editorconfig new file mode 100644 index 000000000..d66ee0772 --- /dev/null +++ b/test/coverlet.core.tests/Samples/.editorconfig @@ -0,0 +1,8 @@ +# top-most EditorConfig file +# We don't want to import other EditorConfig files and we want +# to ensure no rules are enabled for these asset source files. +root = true + +[*.cs] +# Default severity for all analyzer diagnostics +dotnet_analyzer_diagnostic.severity = none diff --git a/test/coverlet.core.tests/Samples/Instrumentation.AsyncForeach.cs b/test/coverlet.core.tests/Samples/Instrumentation.AsyncForeach.cs index 961f9df31..d8765cfd2 100644 --- a/test/coverlet.core.tests/Samples/Instrumentation.AsyncForeach.cs +++ b/test/coverlet.core.tests/Samples/Instrumentation.AsyncForeach.cs @@ -54,5 +54,13 @@ async public ValueTask SumEmpty() return sum; } + + public async ValueTask GenericAsyncForeach(IAsyncEnumerable ints) + { + await foreach (int obj in ints) + { + await Task.Delay(1); + } + } } } diff --git a/test/coverlet.core.tests/Samples/Instrumentation.ExcludeFromCoverage.Issue1302.cs b/test/coverlet.core.tests/Samples/Instrumentation.ExcludeFromCoverage.Issue1302.cs new file mode 100644 index 000000000..8bf8dd2d4 --- /dev/null +++ b/test/coverlet.core.tests/Samples/Instrumentation.ExcludeFromCoverage.Issue1302.cs @@ -0,0 +1,18 @@ +using System; + +namespace Coverlet.Core.Samples.Tests +{ + public class Issue1302 + { + public void Run() + { + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + static Func LocalFunction() + { + return myString => myString.Length == 10; + } + + LocalFunction(); + } + } +} diff --git a/test/coverlet.core.tests/Samples/Instrumentation.GenericAsyncIterator.cs b/test/coverlet.core.tests/Samples/Instrumentation.GenericAsyncIterator.cs new file mode 100644 index 000000000..cc6311506 --- /dev/null +++ b/test/coverlet.core.tests/Samples/Instrumentation.GenericAsyncIterator.cs @@ -0,0 +1,25 @@ +// Remember to use full name because adding new using directives change line numbers + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Coverlet.Core.Samples.Tests +{ + public class GenericAsyncIterator + { + public async Task> Issue1383() + { + var sequence = await CreateSequenceAsync().ToListAsync(); + return sequence; + } + + + public async IAsyncEnumerable CreateSequenceAsync() + { + await Task.CompletedTask; + yield return 5; + yield return 2; + } + } +} diff --git a/test/coverlet.core.tests/Symbols/CecilSymbolHelperTests.cs b/test/coverlet.core.tests/Symbols/CecilSymbolHelperTests.cs index bfd659a61..8b6e46c6d 100644 --- a/test/coverlet.core.tests/Symbols/CecilSymbolHelperTests.cs +++ b/test/coverlet.core.tests/Symbols/CecilSymbolHelperTests.cs @@ -1,7 +1,9 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System.IO; using System.Linq; using System.Reflection; - using Xunit; using Coverlet.Core.Samples.Tests; using coverlet.tests.projectsample.netframework; @@ -19,7 +21,7 @@ public class CecilSymbolHelperTests public CecilSymbolHelperTests() { - var location = GetType().Assembly.Location; + string location = GetType().Assembly.Location; _resolver = new DefaultAssemblyResolver(); _resolver.AddSearchDirectory(Path.GetDirectoryName(location)); _parameters = new ReaderParameters { ReadSymbols = true, AssemblyResolver = _resolver }; @@ -31,15 +33,15 @@ public CecilSymbolHelperTests() public void GetBranchPoints_OneBranch() { // arrange - var type = _module.Types.First(x => x.FullName == typeof(DeclaredConstructorClass).FullName); - var method = type.Methods.First(x => x.FullName.Contains($"::{nameof(DeclaredConstructorClass.HasSingleDecision)}")); + TypeDefinition type = _module.Types.First(x => x.FullName == typeof(DeclaredConstructorClass).FullName); + MethodDefinition method = type.Methods.First(x => x.FullName.Contains($"::{nameof(DeclaredConstructorClass.HasSingleDecision)}")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); // assert Assert.NotNull(points); - Assert.Equal(2, points.Count()); + Assert.Equal(2, points.Count); Assert.Equal(points[0].Offset, points[1].Offset); Assert.Equal(0, points[0].Path); Assert.Equal(1, points[1].Path); @@ -53,24 +55,24 @@ public void GetBranchPoints_OneBranch() public void GetBranchPoints_Using_Where_GeneratedBranchesIgnored() { // arrange - var type = _module.Types.First(x => x.FullName == typeof(DeclaredConstructorClass).FullName); - var method = type.Methods.First(x => x.FullName.Contains($"::{nameof(DeclaredConstructorClass.HasSimpleUsingStatement)}")); + TypeDefinition type = _module.Types.First(x => x.FullName == typeof(DeclaredConstructorClass).FullName); + MethodDefinition method = type.Methods.First(x => x.FullName.Contains($"::{nameof(DeclaredConstructorClass.HasSimpleUsingStatement)}")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); - Assert.Equal(2, points.Count()); + Assert.Equal(2, points.Count); } [Fact] public void GetBranchPoints_GeneratedBranches_DueToCachedAnonymousMethodDelegate_Ignored() { // arrange - var type = _module.Types.First(x => x.FullName == typeof(DeclaredConstructorClass).FullName); - var method = type.Methods.First(x => x.FullName.Contains($"::{nameof(DeclaredConstructorClass.HasSimpleTaskWithLambda)}")); + TypeDefinition type = _module.Types.First(x => x.FullName == typeof(DeclaredConstructorClass).FullName); + MethodDefinition method = type.Methods.First(x => x.FullName.Contains($"::{nameof(DeclaredConstructorClass.HasSimpleTaskWithLambda)}")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); Assert.Empty(points); } @@ -79,15 +81,15 @@ public void GetBranchPoints_GeneratedBranches_DueToCachedAnonymousMethodDelegate public void GetBranchPoints_TwoBranch() { // arrange - var type = _module.Types.First(x => x.FullName == typeof(DeclaredConstructorClass).FullName); - var method = type.Methods.First(x => x.FullName.Contains($"::{nameof(DeclaredConstructorClass.HasTwoDecisions)}")); + TypeDefinition type = _module.Types.First(x => x.FullName == typeof(DeclaredConstructorClass).FullName); + MethodDefinition method = type.Methods.First(x => x.FullName.Contains($"::{nameof(DeclaredConstructorClass.HasTwoDecisions)}")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); // assert Assert.NotNull(points); - Assert.Equal(4, points.Count()); + Assert.Equal(4, points.Count); Assert.Equal(points[0].Offset, points[1].Offset); Assert.Equal(points[2].Offset, points[3].Offset); Assert.Equal(28, points[0].StartLine); @@ -98,15 +100,15 @@ public void GetBranchPoints_TwoBranch() public void GetBranchPoints_CompleteIf() { // arrange - var type = _module.Types.First(x => x.FullName == typeof(DeclaredConstructorClass).FullName); - var method = type.Methods.First(x => x.FullName.Contains($"::{nameof(DeclaredConstructorClass.HasCompleteIf)}")); + TypeDefinition type = _module.Types.First(x => x.FullName == typeof(DeclaredConstructorClass).FullName); + MethodDefinition method = type.Methods.First(x => x.FullName.Contains($"::{nameof(DeclaredConstructorClass.HasCompleteIf)}")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); // assert Assert.NotNull(points); - Assert.Equal(2, points.Count()); + Assert.Equal(2, points.Count); Assert.Equal(points[0].Offset, points[1].Offset); Assert.Equal(35, points[0].StartLine); Assert.Equal(35, points[1].StartLine); @@ -117,15 +119,15 @@ public void GetBranchPoints_CompleteIf() public void GetBranchPoints_Switch() { // arrange - var type = _module.Types.First(x => x.FullName == typeof(DeclaredConstructorClass).FullName); - var method = type.Methods.First(x => x.FullName.Contains($"::{nameof(DeclaredConstructorClass.HasSwitch)}")); + TypeDefinition type = _module.Types.First(x => x.FullName == typeof(DeclaredConstructorClass).FullName); + MethodDefinition method = type.Methods.First(x => x.FullName.Contains($"::{nameof(DeclaredConstructorClass.HasSwitch)}")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); // assert Assert.NotNull(points); - Assert.Equal(4, points.Count()); + Assert.Equal(4, points.Count); Assert.Equal(points[0].Offset, points[1].Offset); Assert.Equal(points[0].Offset, points[2].Offset); Assert.Equal(3, points[3].Path); @@ -140,15 +142,15 @@ public void GetBranchPoints_Switch() public void GetBranchPoints_SwitchWithDefault() { // arrange - var type = _module.Types.First(x => x.FullName == typeof(DeclaredConstructorClass).FullName); - var method = type.Methods.First(x => x.FullName.Contains($"::{nameof(DeclaredConstructorClass.HasSwitchWithDefault)}")); + TypeDefinition type = _module.Types.First(x => x.FullName == typeof(DeclaredConstructorClass).FullName); + MethodDefinition method = type.Methods.First(x => x.FullName.Contains($"::{nameof(DeclaredConstructorClass.HasSwitchWithDefault)}")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); // assert Assert.NotNull(points); - Assert.Equal(4, points.Count()); + Assert.Equal(4, points.Count); Assert.Equal(points[0].Offset, points[1].Offset); Assert.Equal(points[0].Offset, points[2].Offset); Assert.Equal(3, points[3].Path); @@ -163,15 +165,15 @@ public void GetBranchPoints_SwitchWithDefault() public void GetBranchPoints_SwitchWithBreaks() { // arrange - var type = _module.Types.First(x => x.FullName == typeof(DeclaredConstructorClass).FullName); - var method = type.Methods.First(x => x.FullName.Contains($"::{nameof(DeclaredConstructorClass.HasSwitchWithBreaks)}")); + TypeDefinition type = _module.Types.First(x => x.FullName == typeof(DeclaredConstructorClass).FullName); + MethodDefinition method = type.Methods.First(x => x.FullName.Contains($"::{nameof(DeclaredConstructorClass.HasSwitchWithBreaks)}")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); // assert Assert.NotNull(points); - Assert.Equal(4, points.Count()); + Assert.Equal(4, points.Count); Assert.Equal(points[0].Offset, points[1].Offset); Assert.Equal(points[0].Offset, points[2].Offset); Assert.Equal(3, points[3].Path); @@ -186,15 +188,15 @@ public void GetBranchPoints_SwitchWithBreaks() public void GetBranchPoints_SwitchWithMultipleCases() { // arrange - var type = _module.Types.First(x => x.FullName == typeof(DeclaredConstructorClass).FullName); - var method = type.Methods.First(x => x.FullName.Contains($"::{nameof(DeclaredConstructorClass.HasSwitchWithMultipleCases)}")); + TypeDefinition type = _module.Types.First(x => x.FullName == typeof(DeclaredConstructorClass).FullName); + MethodDefinition method = type.Methods.First(x => x.FullName.Contains($"::{nameof(DeclaredConstructorClass.HasSwitchWithMultipleCases)}")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); // assert Assert.NotNull(points); - Assert.Equal(4, points.Count()); + Assert.Equal(4, points.Count); Assert.Equal(points[0].Offset, points[1].Offset); Assert.Equal(points[0].Offset, points[2].Offset); Assert.Equal(points[0].Offset, points[3].Offset); @@ -215,15 +217,15 @@ public void GetBranchPoints_AssignsNegativeLineNumberToBranchesInMethodsThatHave * in this case for an anonymous class the compiler will dynamically create an Equals 'utility' method. */ // arrange - var type = _module.Types.First(x => x.FullName.Contains("f__AnonymousType")); - var method = type.Methods.First(x => x.FullName.Contains("::Equals")); + TypeDefinition type = _module.Types.First(x => x.FullName.Contains("f__AnonymousType")); + MethodDefinition method = type.Methods.First(x => x.FullName.Contains("::Equals")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); // assert Assert.NotNull(points); - foreach (var branchPoint in points) + foreach (BranchPoint branchPoint in points) Assert.Equal(-1, branchPoint.StartLine); } @@ -231,8 +233,8 @@ public void GetBranchPoints_AssignsNegativeLineNumberToBranchesInMethodsThatHave public void GetBranchPoints_UsingWithException_Issue243_IgnoresBranchInFinallyBlock() { // arrange - var type = _module.Types.First(x => x.FullName == typeof(DeclaredConstructorClass).FullName); - var method = type.Methods.First(x => x.FullName.Contains($"::{nameof(DeclaredConstructorClass.UsingWithException_Issue243)}")); + TypeDefinition type = _module.Types.First(x => x.FullName == typeof(DeclaredConstructorClass).FullName); + MethodDefinition method = type.Methods.First(x => x.FullName.Contains($"::{nameof(DeclaredConstructorClass.UsingWithException_Issue243)}")); // check that the method is laid out the way we discovered it to be during the defect // @see https://github.com/OpenCover/opencover/issues/243 @@ -243,7 +245,7 @@ public void GetBranchPoints_UsingWithException_Issue243_IgnoresBranchInFinallyBl Assert.True(method.Body.Instructions.First(i => i.OpCode.FlowControl == FlowControl.Cond_Branch).Offset > method.Body.ExceptionHandlers[0].HandlerStart.Offset); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); // assert Assert.Empty(points); @@ -253,13 +255,13 @@ public void GetBranchPoints_UsingWithException_Issue243_IgnoresBranchInFinallyBl public void GetBranchPoints_IgnoresSwitchIn_GeneratedMoveNext() { // arrange - var nestedName = typeof(Iterator).GetNestedTypes(BindingFlags.NonPublic).First().Name; - var type = _module.Types.FirstOrDefault(x => x.FullName == typeof(Iterator).FullName); - var nestedType = type.NestedTypes.FirstOrDefault(x => x.FullName.EndsWith(nestedName)); - var method = nestedType.Methods.First(x => x.FullName.EndsWith("::MoveNext()")); + string nestedName = typeof(Iterator).GetNestedTypes(BindingFlags.NonPublic).First().Name; + TypeDefinition type = _module.Types.FirstOrDefault(x => x.FullName == typeof(Iterator).FullName); + TypeDefinition nestedType = type.NestedTypes.FirstOrDefault(x => x.FullName.EndsWith(nestedName)); + MethodDefinition method = nestedType.Methods.First(x => x.FullName.EndsWith("::MoveNext()")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); // assert Assert.Empty(points); @@ -269,13 +271,13 @@ public void GetBranchPoints_IgnoresSwitchIn_GeneratedMoveNext() public void GetBranchPoints_IgnoresBranchesIn_GeneratedMoveNextForSingletonIterator() { // arrange - var nestedName = typeof(SingletonIterator).GetNestedTypes(BindingFlags.NonPublic).First().Name; - var type = _module.Types.FirstOrDefault(x => x.FullName == typeof(SingletonIterator).FullName); - var nestedType = type.NestedTypes.FirstOrDefault(x => x.FullName.EndsWith(nestedName)); - var method = nestedType.Methods.First(x => x.FullName.EndsWith("::MoveNext()")); + string nestedName = typeof(SingletonIterator).GetNestedTypes(BindingFlags.NonPublic).First().Name; + TypeDefinition type = _module.Types.FirstOrDefault(x => x.FullName == typeof(SingletonIterator).FullName); + TypeDefinition nestedType = type.NestedTypes.FirstOrDefault(x => x.FullName.EndsWith(nestedName)); + MethodDefinition method = nestedType.Methods.First(x => x.FullName.EndsWith("::MoveNext()")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); // assert Assert.Empty(points); @@ -285,13 +287,13 @@ public void GetBranchPoints_IgnoresBranchesIn_GeneratedMoveNextForSingletonItera public void GetBranchPoints_IgnoresBranchesIn_AsyncAwaitStateMachine() { // arrange - var nestedName = typeof(AsyncAwaitStateMachine).GetNestedTypes(BindingFlags.NonPublic).First().Name; - var type = _module.Types.FirstOrDefault(x => x.FullName == typeof(AsyncAwaitStateMachine).FullName); - var nestedType = type.NestedTypes.FirstOrDefault(x => x.FullName.EndsWith(nestedName)); - var method = nestedType.Methods.First(x => x.FullName.EndsWith("::MoveNext()")); + string nestedName = typeof(AsyncAwaitStateMachine).GetNestedTypes(BindingFlags.NonPublic).First().Name; + TypeDefinition type = _module.Types.FirstOrDefault(x => x.FullName == typeof(AsyncAwaitStateMachine).FullName); + TypeDefinition nestedType = type.NestedTypes.FirstOrDefault(x => x.FullName.EndsWith(nestedName)); + MethodDefinition method = nestedType.Methods.First(x => x.FullName.EndsWith("::MoveNext()")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); // assert Assert.Empty(points); @@ -305,13 +307,13 @@ public void GetBranchPoints_IgnoresBranchesIn_AsyncAwaitStateMachineNetFramework _resolver.AddSearchDirectory(Path.GetDirectoryName(location)); _module = ModuleDefinition.ReadModule(location, _parameters); - var nestedName = typeof(AsyncAwaitStateMachineNetFramework).GetNestedTypes(BindingFlags.NonPublic).First().Name; - var type = _module.Types.FirstOrDefault(x => x.FullName == typeof(AsyncAwaitStateMachineNetFramework).FullName); - var nestedType = type.NestedTypes.FirstOrDefault(x => x.FullName.EndsWith(nestedName)); - var method = nestedType.Methods.First(x => x.FullName.EndsWith("::MoveNext()")); + string nestedName = typeof(AsyncAwaitStateMachineNetFramework).GetNestedTypes(BindingFlags.NonPublic).First().Name; + TypeDefinition type = _module.Types.FirstOrDefault(x => x.FullName == typeof(AsyncAwaitStateMachineNetFramework).FullName); + TypeDefinition nestedType = type.NestedTypes.FirstOrDefault(x => x.FullName.EndsWith(nestedName)); + MethodDefinition method = nestedType.Methods.First(x => x.FullName.EndsWith("::MoveNext()")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); // assert Assert.Empty(points); @@ -321,13 +323,13 @@ public void GetBranchPoints_IgnoresBranchesIn_AsyncAwaitStateMachineNetFramework public void GetBranchPoints_IgnoresBranchesIn_AsyncAwaitValueTaskStateMachine() { // arrange - var nestedName = typeof(AsyncAwaitValueTaskStateMachine).GetNestedTypes(BindingFlags.NonPublic).First().Name; - var type = _module.Types.FirstOrDefault(x => x.FullName == typeof(AsyncAwaitValueTaskStateMachine).FullName); - var nestedType = type.NestedTypes.FirstOrDefault(x => x.FullName.EndsWith(nestedName)); - var method = nestedType.Methods.First(x => x.FullName.EndsWith("::MoveNext()")); + string nestedName = typeof(AsyncAwaitValueTaskStateMachine).GetNestedTypes(BindingFlags.NonPublic).First().Name; + TypeDefinition type = _module.Types.FirstOrDefault(x => x.FullName == typeof(AsyncAwaitValueTaskStateMachine).FullName); + TypeDefinition nestedType = type.NestedTypes.FirstOrDefault(x => x.FullName.EndsWith(nestedName)); + MethodDefinition method = nestedType.Methods.First(x => x.FullName.EndsWith("::MoveNext()")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); // assert Assert.Empty(points); @@ -337,19 +339,19 @@ public void GetBranchPoints_IgnoresBranchesIn_AsyncAwaitValueTaskStateMachine() public void GetBranchPoints_IgnoresMostBranchesIn_AwaitForeachStateMachine() { // arrange - var nestedName = typeof(AwaitForeachStateMachine).GetNestedTypes(BindingFlags.NonPublic).First().Name; - var type = _module.Types.FirstOrDefault(x => x.FullName == typeof(AwaitForeachStateMachine).FullName); - var nestedType = type.NestedTypes.FirstOrDefault(x => x.FullName.EndsWith(nestedName)); - var method = nestedType.Methods.First(x => x.FullName.EndsWith("::MoveNext()")); + string nestedName = typeof(AwaitForeachStateMachine).GetNestedTypes(BindingFlags.NonPublic).First().Name; + TypeDefinition type = _module.Types.FirstOrDefault(x => x.FullName == typeof(AwaitForeachStateMachine).FullName); + TypeDefinition nestedType = type.NestedTypes.FirstOrDefault(x => x.FullName.EndsWith(nestedName)); + MethodDefinition method = nestedType.Methods.First(x => x.FullName.EndsWith("::MoveNext()")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); // assert // We do expect there to be a two-way branch (stay in the loop or not?) on // the line containing "await foreach". Assert.NotNull(points); - Assert.Equal(2, points.Count()); + Assert.Equal(2, points.Count); Assert.Equal(points[0].Offset, points[1].Offset); Assert.Equal(204, points[0].StartLine); Assert.Equal(204, points[1].StartLine); @@ -359,13 +361,13 @@ public void GetBranchPoints_IgnoresMostBranchesIn_AwaitForeachStateMachine() public void GetBranchPoints_IgnoresMostBranchesIn_AwaitForeachStateMachine_WithBranchesWithinIt() { // arrange - var nestedName = typeof(AwaitForeachStateMachine_WithBranches).GetNestedTypes(BindingFlags.NonPublic).First().Name; - var type = _module.Types.FirstOrDefault(x => x.FullName == typeof(AwaitForeachStateMachine_WithBranches).FullName); - var nestedType = type.NestedTypes.FirstOrDefault(x => x.FullName.EndsWith(nestedName)); - var method = nestedType.Methods.First(x => x.FullName.EndsWith("::MoveNext()")); + string nestedName = typeof(AwaitForeachStateMachine_WithBranches).GetNestedTypes(BindingFlags.NonPublic).First().Name; + TypeDefinition type = _module.Types.FirstOrDefault(x => x.FullName == typeof(AwaitForeachStateMachine_WithBranches).FullName); + TypeDefinition nestedType = type.NestedTypes.FirstOrDefault(x => x.FullName.EndsWith(nestedName)); + MethodDefinition method = nestedType.Methods.First(x => x.FullName.EndsWith("::MoveNext()")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); // assert // We do expect there to be four branch points (two places where we can branch @@ -373,7 +375,7 @@ public void GetBranchPoints_IgnoresMostBranchesIn_AwaitForeachStateMachine_WithB // containing "await foreach" and the other being the "if" statement inside // the loop. Assert.NotNull(points); - Assert.Equal(4, points.Count()); + Assert.Equal(4, points.Count); Assert.Equal(points[0].Offset, points[1].Offset); Assert.Equal(points[2].Offset, points[3].Offset); Assert.Equal(219, points[0].StartLine); @@ -386,18 +388,18 @@ public void GetBranchPoints_IgnoresMostBranchesIn_AwaitForeachStateMachine_WithB public void GetBranchPoints_IgnoresExtraBranchesIn_AsyncIteratorStateMachine() { // arrange - var nestedName = typeof(AsyncIteratorStateMachine).GetNestedTypes(BindingFlags.NonPublic).First().Name; - var type = _module.Types.FirstOrDefault(x => x.FullName == typeof(AsyncIteratorStateMachine).FullName); - var nestedType = type.NestedTypes.FirstOrDefault(x => x.FullName.EndsWith(nestedName)); - var method = nestedType.Methods.First(x => x.FullName.EndsWith("::MoveNext()")); + string nestedName = typeof(AsyncIteratorStateMachine).GetNestedTypes(BindingFlags.NonPublic).First().Name; + TypeDefinition type = _module.Types.FirstOrDefault(x => x.FullName == typeof(AsyncIteratorStateMachine).FullName); + TypeDefinition nestedType = type.NestedTypes.FirstOrDefault(x => x.FullName.EndsWith(nestedName)); + MethodDefinition method = nestedType.Methods.First(x => x.FullName.EndsWith("::MoveNext()")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); // assert // We do expect the "for" loop to be a branch with two branch points, but that's it. Assert.NotNull(points); - Assert.Equal(2, points.Count()); + Assert.Equal(2, points.Count); Assert.Equal(237, points[0].StartLine); Assert.Equal(237, points[1].StartLine); } @@ -406,13 +408,13 @@ public void GetBranchPoints_IgnoresExtraBranchesIn_AsyncIteratorStateMachine() public void GetBranchPoints_IgnoreBranchesIn_AwaitUsingStateMachine() { // arrange - var nestedName = typeof(AwaitUsingStateMachine).GetNestedTypes(BindingFlags.NonPublic).First().Name; - var type = _module.Types.FirstOrDefault(x => x.FullName == typeof(AwaitUsingStateMachine).FullName); - var nestedType = type.NestedTypes.FirstOrDefault(x => x.FullName.EndsWith(nestedName)); - var method = nestedType.Methods.First(x => x.FullName.EndsWith("::MoveNext()")); + string nestedName = typeof(AwaitUsingStateMachine).GetNestedTypes(BindingFlags.NonPublic).First().Name; + TypeDefinition type = _module.Types.FirstOrDefault(x => x.FullName == typeof(AwaitUsingStateMachine).FullName); + TypeDefinition nestedType = type.NestedTypes.FirstOrDefault(x => x.FullName.EndsWith(nestedName)); + MethodDefinition method = nestedType.Methods.First(x => x.FullName.EndsWith("::MoveNext()")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); // assert Assert.Empty(points); @@ -422,13 +424,13 @@ public void GetBranchPoints_IgnoreBranchesIn_AwaitUsingStateMachine() public void GetBranchPoints_IgnoreBranchesIn_ScopedAwaitUsingStateMachine() { // arrange - var nestedName = typeof(ScopedAwaitUsingStateMachine).GetNestedTypes(BindingFlags.NonPublic).First().Name; - var type = _module.Types.FirstOrDefault(x => x.FullName == typeof(ScopedAwaitUsingStateMachine).FullName); - var nestedType = type.NestedTypes.FirstOrDefault(x => x.FullName.EndsWith(nestedName)); - var method = nestedType.Methods.First(x => x.FullName.EndsWith("::MoveNext()")); + string nestedName = typeof(ScopedAwaitUsingStateMachine).GetNestedTypes(BindingFlags.NonPublic).First().Name; + TypeDefinition type = _module.Types.FirstOrDefault(x => x.FullName == typeof(ScopedAwaitUsingStateMachine).FullName); + TypeDefinition nestedType = type.NestedTypes.FirstOrDefault(x => x.FullName.EndsWith(nestedName)); + MethodDefinition method = nestedType.Methods.First(x => x.FullName.EndsWith("::MoveNext()")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); // assert Assert.Empty(points); @@ -438,12 +440,12 @@ public void GetBranchPoints_IgnoreBranchesIn_ScopedAwaitUsingStateMachine() public void GetBranchPoints_ExceptionFilter() { // arrange - var type = _module.Types.Single(x => x.FullName == typeof(ExceptionFilter).FullName); - var method = type.Methods.Single(x => x.FullName.Contains($"::{nameof(ExceptionFilter.Test)}")); + TypeDefinition type = _module.Types.Single(x => x.FullName == typeof(ExceptionFilter).FullName); + MethodDefinition method = type.Methods.Single(x => x.FullName.Contains($"::{nameof(ExceptionFilter.Test)}")); // act - var points = _cecilSymbolHelper.GetBranchPoints(method); + System.Collections.Generic.IReadOnlyList points = _cecilSymbolHelper.GetBranchPoints(method); Assert.Empty(points); } } -} \ No newline at end of file +} diff --git a/test/coverlet.core.tests/coverlet.core.tests.csproj b/test/coverlet.core.tests/coverlet.core.tests.csproj index d5fdc3257..f040aa9c3 100644 --- a/test/coverlet.core.tests/coverlet.core.tests.csproj +++ b/test/coverlet.core.tests/coverlet.core.tests.csproj @@ -2,7 +2,7 @@ - net5.0 + net6.0 false $(NoWarn);CS8002 NU1702 @@ -29,6 +29,7 @@ + diff --git a/test/coverlet.integration.determisticbuild/.editorconfig b/test/coverlet.integration.determisticbuild/.editorconfig new file mode 100644 index 000000000..d66ee0772 --- /dev/null +++ b/test/coverlet.integration.determisticbuild/.editorconfig @@ -0,0 +1,8 @@ +# top-most EditorConfig file +# We don't want to import other EditorConfig files and we want +# to ensure no rules are enabled for these asset source files. +root = true + +[*.cs] +# Default severity for all analyzer diagnostics +dotnet_analyzer_diagnostic.severity = none diff --git a/test/coverlet.integration.determisticbuild/coverlet.integration.determisticbuild.csproj b/test/coverlet.integration.determisticbuild/coverlet.integration.determisticbuild.csproj index 3c828c96d..b79d89433 100644 --- a/test/coverlet.integration.determisticbuild/coverlet.integration.determisticbuild.csproj +++ b/test/coverlet.integration.determisticbuild/coverlet.integration.determisticbuild.csproj @@ -3,7 +3,7 @@ - net5.0 + net6.0 false coverletsample.integration.determisticbuild diff --git a/test/coverlet.integration.template/.editorconfig b/test/coverlet.integration.template/.editorconfig new file mode 100644 index 000000000..d66ee0772 --- /dev/null +++ b/test/coverlet.integration.template/.editorconfig @@ -0,0 +1,8 @@ +# top-most EditorConfig file +# We don't want to import other EditorConfig files and we want +# to ensure no rules are enabled for these asset source files. +root = true + +[*.cs] +# Default severity for all analyzer diagnostics +dotnet_analyzer_diagnostic.severity = none diff --git a/test/coverlet.integration.template/coverlet.integration.template.csproj b/test/coverlet.integration.template/coverlet.integration.template.csproj index 60bfec072..d1639d96f 100644 --- a/test/coverlet.integration.template/coverlet.integration.template.csproj +++ b/test/coverlet.integration.template/coverlet.integration.template.csproj @@ -1,7 +1,7 @@  - net5.0 + net6.0 false coverletsamplelib.integration.template false @@ -10,8 +10,8 @@ - - + + diff --git a/test/coverlet.integration.tests/AssertHelper.cs b/test/coverlet.integration.tests/AssertHelper.cs index 4fa6e0658..a8ab085db 100644 --- a/test/coverlet.integration.tests/AssertHelper.cs +++ b/test/coverlet.integration.tests/AssertHelper.cs @@ -1,8 +1,10 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using System.Collections.Generic; using System.IO; using System.Linq; - using Coverlet.Core; using Xunit.Sdk; @@ -17,7 +19,6 @@ public static Classes Document(this Modules modules, string docName) throw new ArgumentNullException(nameof(docName)); } - foreach (KeyValuePair module in modules) { foreach (KeyValuePair document in module.Value) @@ -39,7 +40,6 @@ public static Methods Class(this Classes classes, string className) throw new ArgumentNullException(nameof(className)); } - foreach (KeyValuePair @class in classes) { if (@class.Key == className) @@ -58,7 +58,6 @@ public static Method Method(this Methods methods, string methodName) throw new ArgumentNullException(nameof(methodName)); } - foreach (KeyValuePair method in methods) { if (method.Key.Contains(methodName)) @@ -77,7 +76,7 @@ public static void AssertLinesCovered(this Method method, params (int line, int throw new ArgumentNullException(nameof(lines)); } - List linesToCover = new List(lines.Select(l => l.line)); + var linesToCover = new List(lines.Select(l => l.line)); foreach (KeyValuePair line in method.Lines) { diff --git a/test/coverlet.integration.tests/BaseTest.cs b/test/coverlet.integration.tests/BaseTest.cs index be15680dd..2d44555e0 100644 --- a/test/coverlet.integration.tests/BaseTest.cs +++ b/test/coverlet.integration.tests/BaseTest.cs @@ -1,11 +1,12 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using System.Diagnostics; using System.IO; using System.Linq; -using System.Reflection; using System.Threading; using System.Xml.Linq; - using Coverlet.Core; using Newtonsoft.Json; using NuGet.Packaging; @@ -23,7 +24,7 @@ public enum BuildConfiguration public abstract class BaseTest { - private static int _folderSuffix = 0; + private static int s_folderSuffix; protected BuildConfiguration GetAssemblyBuildConfiguration() { @@ -38,21 +39,35 @@ protected BuildConfiguration GetAssemblyBuildConfiguration() private protected string GetPackageVersion(string filter) { - if (!Directory.Exists($"../../../../../bin/{GetAssemblyBuildConfiguration()}/Packages")) + string packagesPath = $"../../../../../bin/{GetAssemblyBuildConfiguration()}/Packages"; + + if (!Directory.Exists(packagesPath)) { throw new DirectoryNotFoundException("Package directory not found, run 'dotnet pack' on repository root"); } - using Stream pkg = File.OpenRead(Directory.GetFiles($"../../../../../bin/{GetAssemblyBuildConfiguration()}/Packages", filter).Single()); - using var reader = new PackageArchiveReader(pkg); - using Stream nuspecStream = reader.GetNuspec(); - Manifest manifest = Manifest.ReadFrom(nuspecStream, false); - return manifest.Metadata.Version.OriginalVersion; + var files = Directory.GetFiles(packagesPath, filter).ToList(); + if (files.Count == 0) + { + throw new InvalidOperationException($"Could not find any package using filter '{filter}' in folder '{Path.GetFullPath(packagesPath)}'. Make sure 'dotnet pack' was called."); + } + else if (files.Count > 1) + { + throw new InvalidOperationException($"Found more than one package using filter '{filter}' in folder '{Path.GetFullPath(packagesPath)}'. Make sure 'dotnet pack' was only called once."); + } + else + { + using Stream pkg = File.OpenRead(files[0]); + using var reader = new PackageArchiveReader(pkg); + using Stream nuspecStream = reader.GetNuspec(); + var manifest = Manifest.ReadFrom(nuspecStream, false); + return manifest.Metadata.Version.OriginalVersion; + } } - private protected ClonedTemplateProject CloneTemplateProject(bool cleanupOnDispose = true, string testSDKVersion = "16.5.0") + private protected ClonedTemplateProject CloneTemplateProject(bool cleanupOnDispose = true, string testSDKVersion = "17.5.0") { - DirectoryInfo finalRoot = Directory.CreateDirectory($"{Guid.NewGuid().ToString("N").Substring(0, 6)}{Interlocked.Increment(ref _folderSuffix)}"); + DirectoryInfo finalRoot = Directory.CreateDirectory($"{Guid.NewGuid().ToString("N")[..6]}{Interlocked.Increment(ref s_folderSuffix)}"); foreach (string file in (Directory.GetFiles($"../../../../coverlet.integration.template", "*.cs") .Union(Directory.GetFiles($"../../../../coverlet.integration.template", "*.csproj") .Union(Directory.GetFiles($"../../../../coverlet.integration.template", "nuget.config"))))) @@ -81,7 +96,7 @@ private protected ClonedTemplateProject CloneTemplateProject(bool cleanupOnDispo private protected bool RunCommand(string command, string arguments, out string standardOutput, out string standardError, string workingDirectory = "") { Debug.WriteLine($"BaseTest.RunCommand: {command} {arguments}\nWorkingDirectory: {workingDirectory}"); - ProcessStartInfo psi = new ProcessStartInfo(command, arguments); + var psi = new ProcessStartInfo(command, arguments); psi.WorkingDirectory = workingDirectory; psi.RedirectStandardError = true; psi.RedirectStandardOutput = true; @@ -108,7 +123,7 @@ private protected void UpdateNugeConfigtWithLocalPackageFolder(string projectPat throw new FileNotFoundException("Nuget.config not found", "nuget.config"); } XDocument xml; - using (var nugetFileStream = File.OpenRead(nugetFile)) + using (FileStream? nugetFileStream = File.OpenRead(nugetFile)) { xml = XDocument.Load(nugetFileStream); } @@ -130,7 +145,7 @@ private void SetIsTestProjectTrue(string projectPath) throw new FileNotFoundException("coverlet.integration.template.csproj not found", "coverlet.integration.template.csproj"); } XDocument xml; - using (var csprojStream = File.OpenRead(csproj)) + using (FileStream? csprojStream = File.OpenRead(csproj)) { xml = XDocument.Load(csprojStream); } @@ -150,7 +165,7 @@ private protected void AddMicrosoftNETTestSdkRef(string projectPath, string vers throw new FileNotFoundException("coverlet.integration.template.csproj not found", "coverlet.integration.template.csproj"); } XDocument xml; - using (var csprojStream = File.OpenRead(csproj)) + using (FileStream? csprojStream = File.OpenRead(csproj)) { xml = XDocument.Load(csprojStream); } @@ -170,7 +185,7 @@ private protected void AddCoverletMsbuildRef(string projectPath) throw new FileNotFoundException("coverlet.integration.template.csproj not found", "coverlet.integration.template.csproj"); } XDocument xml; - using (var csprojStream = File.OpenRead(csproj)) + using (FileStream? csprojStream = File.OpenRead(csproj)) { xml = XDocument.Load(csprojStream); } @@ -189,7 +204,7 @@ private protected void AddCoverletCollectosRef(string projectPath) throw new FileNotFoundException("coverlet.integration.template.csproj not found", "coverlet.integration.template.csproj"); } XDocument xml; - using (var csprojStream = File.OpenRead(csproj)) + using (FileStream? csprojStream = File.OpenRead(csproj)) { xml = XDocument.Load(csprojStream); } @@ -233,12 +248,14 @@ private protected void AssertCoverage(ClonedTemplateProject clonedTemplateProjec bool coverageChecked = false; foreach (string coverageFile in clonedTemplateProject.GetFiles(filter)) { - JsonConvert.DeserializeObject(File.ReadAllText(coverageFile)) - .Document("DeepThought.cs") - .Class("Coverlet.Integration.Template.DeepThought") - .Method("System.Int32 Coverlet.Integration.Template.DeepThought::AnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything()") - .AssertLinesCovered((6, 1), (7, 1), (8, 1)); - coverageChecked = true; + Classes? document = JsonConvert.DeserializeObject(File.ReadAllText(coverageFile))?.Document("DeepThought.cs"); + if (document != null) + { + document.Class("Coverlet.Integration.Template.DeepThought") + .Method("System.Int32 Coverlet.Integration.Template.DeepThought::AnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything()") + .AssertLinesCovered((6, 1), (7, 1), (8, 1)); + coverageChecked = true; + } } Assert.True(coverageChecked, $"Coverage check fail\n{standardOutput}"); @@ -257,7 +274,7 @@ private protected void UpdateProjectTargetFramework(ClonedTemplateProject projec throw new FileNotFoundException("coverlet.integration.template.csproj not found", "coverlet.integration.template.csproj"); } XDocument xml; - using (var csprojStream = File.OpenRead(project.ProjectFileNamePath)) + using (FileStream? csprojStream = File.OpenRead(project.ProjectFileNamePath)) { xml = XDocument.Load(csprojStream); } @@ -327,15 +344,15 @@ public ClonedTemplateProject(string? projectRootPath, bool cleanupOnDispose) public bool IsMultipleTargetFramework() { - using var csprojStream = File.OpenRead(ProjectFileNamePath); - XDocument xml = XDocument.Load(csprojStream); + using FileStream? csprojStream = File.OpenRead(ProjectFileNamePath); + var xml = XDocument.Load(csprojStream); return xml.Element("Project")!.Element("PropertyGroup")!.Element("TargetFramework") == null; } public string[] GetTargetFrameworks() { - using var csprojStream = File.OpenRead(ProjectFileNamePath); - XDocument xml = XDocument.Load(csprojStream); + using FileStream? csprojStream = File.OpenRead(ProjectFileNamePath); + var xml = XDocument.Load(csprojStream); XElement element = xml.Element("Project")!.Element("PropertyGroup")!.Element("TargetFramework") ?? xml.Element("Project")!.Element("PropertyGroup")!.Element("TargetFrameworks")!; if (element is null) { diff --git a/test/coverlet.integration.tests/Collectors.cs b/test/coverlet.integration.tests/Collectors.cs index 52adb820b..262f29d58 100644 --- a/test/coverlet.integration.tests/Collectors.cs +++ b/test/coverlet.integration.tests/Collectors.cs @@ -1,7 +1,9 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using System.IO; using System.Linq; - using Xunit; namespace Coverlet.Integration.Tests @@ -42,7 +44,7 @@ public TestSDK_Preview() public abstract class Collectors : BaseTest { - private string _buildConfiguration; + private readonly string _buildConfiguration; public Collectors() { diff --git a/test/coverlet.integration.tests/DeterministicBuild.cs b/test/coverlet.integration.tests/DeterministicBuild.cs index 1013e2303..2607b5e8e 100644 --- a/test/coverlet.integration.tests/DeterministicBuild.cs +++ b/test/coverlet.integration.tests/DeterministicBuild.cs @@ -1,8 +1,10 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using System.IO; using System.Linq; using System.Xml.Linq; - using Coverlet.Core; using Newtonsoft.Json; using Xunit; @@ -23,7 +25,7 @@ public DeterministicBuild() private void CreateDeterministicTestPropsFile() { - XDocument deterministicTestProps = new XDocument(); + var deterministicTestProps = new XDocument(); deterministicTestProps.Add( new XElement("Project", new XElement("PropertyGroup", @@ -43,13 +45,15 @@ private protected void AssertCoverage(string standardOutput = "", bool checkDete string reportFilePath = ""; foreach (string coverageFile in Directory.GetFiles(_testProjectPath, "coverage.json", SearchOption.AllDirectories)) { - JsonConvert.DeserializeObject(File.ReadAllText(coverageFile)) - .Document("DeepThought.cs") - .Class("Coverlet.Integration.DeterministicBuild.DeepThought") - .Method("System.Int32 Coverlet.Integration.DeterministicBuild.DeepThought::AnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything()") - .AssertLinesCovered((6, 1), (7, 1), (8, 1)); - coverageChecked = true; - reportFilePath = coverageFile; + Classes? document = JsonConvert.DeserializeObject(File.ReadAllText(coverageFile))?.Document("DeepThought.cs"); + if (document != null) + { + document.Class("Coverlet.Integration.DeterministicBuild.DeepThought") + .Method("System.Int32 Coverlet.Integration.DeterministicBuild.DeepThought::AnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything()") + .AssertLinesCovered((6, 1), (7, 1), (8, 1)); + coverageChecked = true; + reportFilePath = coverageFile; + } } Assert.True(coverageChecked, $"Coverage check fail\n{standardOutput}"); File.Delete(reportFilePath); @@ -73,7 +77,7 @@ public void Msbuild() CreateDeterministicTestPropsFile(); DotnetCli($"build -c {_buildConfiguration} /p:DeterministicSourcePaths=true", out string standardOutput, out string _, _testProjectPath); Assert.Contains("Build succeeded.", standardOutput); - string sourceRootMappingFilePath = Path.Combine(_testProjectPath, "bin", _buildConfiguration, _testProjectTfm!, "CoverletSourceRootsMapping"); + string sourceRootMappingFilePath = Path.Combine(_testProjectPath, "bin", _buildConfiguration, _testProjectTfm!, "CoverletSourceRootsMapping_coverletsample.integration.determisticbuild"); Assert.True(File.Exists(sourceRootMappingFilePath), sourceRootMappingFilePath); Assert.True(!string.IsNullOrEmpty(File.ReadAllText(sourceRootMappingFilePath)), "Empty CoverletSourceRootsMapping file"); Assert.Contains("=/_/", File.ReadAllText(sourceRootMappingFilePath)); @@ -96,7 +100,7 @@ public void Msbuild_SourceLink() CreateDeterministicTestPropsFile(); DotnetCli($"build -c {_buildConfiguration} /p:DeterministicSourcePaths=true", out string standardOutput, out string _, _testProjectPath); Assert.Contains("Build succeeded.", standardOutput); - string sourceRootMappingFilePath = Path.Combine(_testProjectPath, "bin", _buildConfiguration, _testProjectTfm!, "CoverletSourceRootsMapping"); + string sourceRootMappingFilePath = Path.Combine(_testProjectPath, "bin", _buildConfiguration, _testProjectTfm!, "CoverletSourceRootsMapping_coverletsample.integration.determisticbuild"); Assert.True(File.Exists(sourceRootMappingFilePath), sourceRootMappingFilePath); Assert.True(!string.IsNullOrEmpty(File.ReadAllText(sourceRootMappingFilePath)), "Empty CoverletSourceRootsMapping file"); Assert.Contains("=/_/", File.ReadAllText(sourceRootMappingFilePath)); @@ -120,7 +124,7 @@ public void Collectors() CreateDeterministicTestPropsFile(); DotnetCli($"build -c {_buildConfiguration} /p:DeterministicSourcePaths=true", out string standardOutput, out string _, _testProjectPath); Assert.Contains("Build succeeded.", standardOutput); - string sourceRootMappingFilePath = Path.Combine(_testProjectPath, "bin", GetAssemblyBuildConfiguration().ToString(), _testProjectTfm!, "CoverletSourceRootsMapping"); + string sourceRootMappingFilePath = Path.Combine(_testProjectPath, "bin", GetAssemblyBuildConfiguration().ToString(), _testProjectTfm!, "CoverletSourceRootsMapping_coverletsample.integration.determisticbuild"); Assert.True(File.Exists(sourceRootMappingFilePath), sourceRootMappingFilePath); Assert.NotEmpty(File.ReadAllText(sourceRootMappingFilePath)); Assert.Contains("=/_/", File.ReadAllText(sourceRootMappingFilePath)); @@ -148,7 +152,7 @@ public void Collectors_SourceLink() CreateDeterministicTestPropsFile(); DotnetCli($"build -c {_buildConfiguration} /p:DeterministicSourcePaths=true", out string standardOutput, out string _, _testProjectPath); Assert.Contains("Build succeeded.", standardOutput); - string sourceRootMappingFilePath = Path.Combine(_testProjectPath, "bin", GetAssemblyBuildConfiguration().ToString(), _testProjectTfm!, "CoverletSourceRootsMapping"); + string sourceRootMappingFilePath = Path.Combine(_testProjectPath, "bin", GetAssemblyBuildConfiguration().ToString(), _testProjectTfm!, "CoverletSourceRootsMapping_coverletsample.integration.determisticbuild"); Assert.True(File.Exists(sourceRootMappingFilePath), sourceRootMappingFilePath); Assert.NotEmpty(File.ReadAllText(sourceRootMappingFilePath)); Assert.Contains("=/_/", File.ReadAllText(sourceRootMappingFilePath)); diff --git a/test/coverlet.integration.tests/DotnetTool.cs b/test/coverlet.integration.tests/DotnetTool.cs index da5d96628..58f2521ac 100644 --- a/test/coverlet.integration.tests/DotnetTool.cs +++ b/test/coverlet.integration.tests/DotnetTool.cs @@ -1,7 +1,9 @@ -using Coverlet.Tests.Xunit.Extensions; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Coverlet.Tests.Xunit.Extensions; using System.IO; using System.Linq; -using System.Runtime.InteropServices; using Xunit; namespace Coverlet.Integration.Tests diff --git a/test/coverlet.integration.tests/Msbuild.cs b/test/coverlet.integration.tests/Msbuild.cs index 856b512b5..83c635934 100644 --- a/test/coverlet.integration.tests/Msbuild.cs +++ b/test/coverlet.integration.tests/Msbuild.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System.IO; using System.Linq; using Xunit; @@ -6,7 +9,7 @@ namespace Coverlet.Integration.Tests { public class Msbuild : BaseTest { - private string _buildConfiguration; + private readonly string _buildConfiguration; public Msbuild() { @@ -93,7 +96,7 @@ public void TestMsbuild_CoverletOutput_Folder_FileNameWithDoubleExtension() public void Test_MultipleTargetFrameworkReport_NoCoverletOutput() { using ClonedTemplateProject clonedTemplateProject = PrepareTemplateProject(); - string[] targetFrameworks = new string[] { "net5.0", "netcoreapp3.1" }; + string[] targetFrameworks = new string[] { "net6.0", "net7.0" }; UpdateProjectTargetFramework(clonedTemplateProject, targetFrameworks); Assert.True(DotnetCli($"test -c {_buildConfiguration} \"{clonedTemplateProject.ProjectRootPath}\" /p:CollectCoverage=true /p:Include=\"[{ClonedTemplateProject.AssemblyName}]*DeepThought\" /p:IncludeTestAssembly=true", out string standardOutput, out string standardError, clonedTemplateProject.ProjectRootPath!), standardOutput); Assert.Contains("Passed!", standardOutput); @@ -111,7 +114,7 @@ public void Test_MultipleTargetFrameworkReport_NoCoverletOutput() public void Test_MultipleTargetFrameworkReport_CoverletOutput_Folder() { using ClonedTemplateProject clonedTemplateProject = PrepareTemplateProject(); - string[] targetFrameworks = new string[] { "netcoreapp3.1", "net5.0" }; + string[] targetFrameworks = new string[] { "net6.0", "net7.0" }; UpdateProjectTargetFramework(clonedTemplateProject, targetFrameworks); Assert.True(DotnetCli($"test -c {_buildConfiguration} \"{clonedTemplateProject.ProjectRootPath}\" /p:CollectCoverage=true /p:Include=\"[{ClonedTemplateProject.AssemblyName}]*DeepThought\" /p:IncludeTestAssembly=true /p:CoverletOutput=\"{clonedTemplateProject.ProjectRootPath}\"\\", out string standardOutput, out string standardError, clonedTemplateProject.ProjectRootPath!), standardOutput); Assert.Contains("Passed!", standardOutput); @@ -130,7 +133,7 @@ public void Test_MultipleTargetFrameworkReport_CoverletOutput_Folder() public void Test_MultipleTargetFrameworkReport_CoverletOutput_Folder_FileNameWithoutExtension() { using ClonedTemplateProject clonedTemplateProject = PrepareTemplateProject(); - string[] targetFrameworks = new string[] { "net5.0", "netcoreapp3.1" }; + string[] targetFrameworks = new string[] {"net6.0", "net7.0" }; UpdateProjectTargetFramework(clonedTemplateProject, targetFrameworks); Assert.True(DotnetCli($"test -c {_buildConfiguration} \"{clonedTemplateProject.ProjectRootPath}\" /p:CollectCoverage=true /p:Include=\"[{ClonedTemplateProject.AssemblyName}]*DeepThought\" /p:IncludeTestAssembly=true /p:CoverletOutput=\"{clonedTemplateProject.ProjectRootPath}\"\\file", out string standardOutput, out string standardError, clonedTemplateProject.ProjectRootPath!), standardOutput); Assert.Contains("Passed!", standardOutput); @@ -148,7 +151,7 @@ public void Test_MultipleTargetFrameworkReport_CoverletOutput_Folder_FileNameWit public void Test_MultipleTargetFrameworkReport_CoverletOutput_Folder_FileNameWithExtension_SpecifyFramework() { using ClonedTemplateProject clonedTemplateProject = PrepareTemplateProject(); - string[] targetFrameworks = new string[] { "net5.0", "netcoreapp3.1" }; + string[] targetFrameworks = new string[] {"net6.0", "net7.0" }; UpdateProjectTargetFramework(clonedTemplateProject, targetFrameworks); Assert.True(clonedTemplateProject.IsMultipleTargetFramework()); string[] frameworks = clonedTemplateProject.GetTargetFrameworks(); @@ -177,7 +180,7 @@ public void Test_MultipleTargetFrameworkReport_CoverletOutput_Folder_FileNameWit public void Test_MultipleTargetFrameworkReport_CoverletOutput_Folder_FileNameWithExtension() { using ClonedTemplateProject clonedTemplateProject = PrepareTemplateProject(); - string[] targetFrameworks = new string[] {"net5.0", "netcoreapp3.1" }; + string[] targetFrameworks = new string[] {"net6.0", "net7.0" }; UpdateProjectTargetFramework(clonedTemplateProject, targetFrameworks); Assert.True(DotnetCli($"test -c {_buildConfiguration} \"{clonedTemplateProject.ProjectRootPath}\" /p:CollectCoverage=true /p:Include=\"[{ClonedTemplateProject.AssemblyName}]*DeepThought\" /p:IncludeTestAssembly=true /p:CoverletOutput=\"{clonedTemplateProject.ProjectRootPath}\"\\file.ext", out string standardOutput, out string standardError, clonedTemplateProject.ProjectRootPath!), standardOutput); Assert.Contains("Passed!", standardOutput); @@ -195,7 +198,7 @@ public void Test_MultipleTargetFrameworkReport_CoverletOutput_Folder_FileNameWit public void Test_MultipleTargetFrameworkReport_CoverletOutput_Folder_FileNameWithDoubleExtension() { using ClonedTemplateProject clonedTemplateProject = PrepareTemplateProject(); - string[] targetFrameworks = new string[] { "net5.0", "netcoreapp3.1" }; + string[] targetFrameworks = new string[] {"net6.0", "net7.0" }; UpdateProjectTargetFramework(clonedTemplateProject, targetFrameworks); Assert.True(DotnetCli($"test -c {_buildConfiguration} \"{clonedTemplateProject.ProjectRootPath}\" /p:CollectCoverage=true /p:Include=\"[{ClonedTemplateProject.AssemblyName}]*DeepThought\" /p:IncludeTestAssembly=true /p:CoverletOutput=\"{clonedTemplateProject.ProjectRootPath}\"\\file.ext1.ext2", out string standardOutput, out string standardError, clonedTemplateProject.ProjectRootPath!), standardOutput); Assert.Contains("Passed!", standardOutput); diff --git a/test/coverlet.integration.tests/Properties/AssemblyInfo.cs b/test/coverlet.integration.tests/Properties/AssemblyInfo.cs index c01eb0d58..73e98ccb6 100644 --- a/test/coverlet.integration.tests/Properties/AssemblyInfo.cs +++ b/test/coverlet.integration.tests/Properties/AssemblyInfo.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System.Reflection; [assembly: AssemblyKeyFile("coverlet.integration.tests.snk")] diff --git a/test/coverlet.integration.tests/coverlet.integration.tests.csproj b/test/coverlet.integration.tests/coverlet.integration.tests.csproj index b80f7c1a2..6de30285a 100644 --- a/test/coverlet.integration.tests/coverlet.integration.tests.csproj +++ b/test/coverlet.integration.tests/coverlet.integration.tests.csproj @@ -1,7 +1,7 @@ - + - net5.0 + net6.0 false enable diff --git a/test/coverlet.tests.projectsample.empty/.editorconfig b/test/coverlet.tests.projectsample.empty/.editorconfig new file mode 100644 index 000000000..d66ee0772 --- /dev/null +++ b/test/coverlet.tests.projectsample.empty/.editorconfig @@ -0,0 +1,8 @@ +# top-most EditorConfig file +# We don't want to import other EditorConfig files and we want +# to ensure no rules are enabled for these asset source files. +root = true + +[*.cs] +# Default severity for all analyzer diagnostics +dotnet_analyzer_diagnostic.severity = none diff --git a/test/coverlet.tests.projectsample.excludedbyattribute/.editorconfig b/test/coverlet.tests.projectsample.excludedbyattribute/.editorconfig new file mode 100644 index 000000000..d66ee0772 --- /dev/null +++ b/test/coverlet.tests.projectsample.excludedbyattribute/.editorconfig @@ -0,0 +1,8 @@ +# top-most EditorConfig file +# We don't want to import other EditorConfig files and we want +# to ensure no rules are enabled for these asset source files. +root = true + +[*.cs] +# Default severity for all analyzer diagnostics +dotnet_analyzer_diagnostic.severity = none diff --git a/test/coverlet.tests.projectsample.excludedbyattribute/coverlet.tests.projectsample.excludedbyattribute.csproj b/test/coverlet.tests.projectsample.excludedbyattribute/coverlet.tests.projectsample.excludedbyattribute.csproj index 6acabd989..3020bf383 100644 --- a/test/coverlet.tests.projectsample.excludedbyattribute/coverlet.tests.projectsample.excludedbyattribute.csproj +++ b/test/coverlet.tests.projectsample.excludedbyattribute/coverlet.tests.projectsample.excludedbyattribute.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 false false diff --git a/test/coverlet.tests.projectsample.fsharp/coverlet.tests.projectsample.fsharp.fsproj b/test/coverlet.tests.projectsample.fsharp/coverlet.tests.projectsample.fsharp.fsproj index 03637dad4..cd7a95969 100644 --- a/test/coverlet.tests.projectsample.fsharp/coverlet.tests.projectsample.fsharp.fsproj +++ b/test/coverlet.tests.projectsample.fsharp/coverlet.tests.projectsample.fsharp.fsproj @@ -1,7 +1,7 @@  - net5.0 + net6.0 true false false diff --git a/test/coverlet.tests.projectsample.netframework/.editorconfig b/test/coverlet.tests.projectsample.netframework/.editorconfig new file mode 100644 index 000000000..d66ee0772 --- /dev/null +++ b/test/coverlet.tests.projectsample.netframework/.editorconfig @@ -0,0 +1,8 @@ +# top-most EditorConfig file +# We don't want to import other EditorConfig files and we want +# to ensure no rules are enabled for these asset source files. +root = true + +[*.cs] +# Default severity for all analyzer diagnostics +dotnet_analyzer_diagnostic.severity = none diff --git a/test/coverlet.tests.projectsample.vbmynamespace/SampleVbClass.vb b/test/coverlet.tests.projectsample.vbmynamespace/SampleVbClass.vb new file mode 100644 index 000000000..bce7be710 --- /dev/null +++ b/test/coverlet.tests.projectsample.vbmynamespace/SampleVbClass.vb @@ -0,0 +1,5 @@ +Public Class SampleVbClass + Sub SampleSub() + Return + End Sub +End Class diff --git a/test/coverlet.tests.projectsample.vbmynamespace/coverlet.tests.projectsample.vbmynamespace.vbproj b/test/coverlet.tests.projectsample.vbmynamespace/coverlet.tests.projectsample.vbmynamespace.vbproj new file mode 100644 index 000000000..3b626df5a --- /dev/null +++ b/test/coverlet.tests.projectsample.vbmynamespace/coverlet.tests.projectsample.vbmynamespace.vbproj @@ -0,0 +1,9 @@ + + + + coverlet.tests.projectsample.vbmynamespace + net48 + latest + + + \ No newline at end of file diff --git a/test/coverlet.tests.xunit.extensions/ConditionalFact.cs b/test/coverlet.tests.xunit.extensions/ConditionalFact.cs index 02870708a..fbcbc82f6 100644 --- a/test/coverlet.tests.xunit.extensions/ConditionalFact.cs +++ b/test/coverlet.tests.xunit.extensions/ConditionalFact.cs @@ -1,5 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using Xunit; using Xunit.Abstractions; using Xunit.Sdk; @@ -37,4 +39,4 @@ protected override string GetSkipReason(IAttributeInfo factAttribute) return _skipReason ?? base.GetSkipReason(factAttribute); } } -} \ No newline at end of file +} diff --git a/test/coverlet.tests.xunit.extensions/Extensions.cs b/test/coverlet.tests.xunit.extensions/Extensions.cs index 42fb3e8d0..2c3ee4a97 100644 --- a/test/coverlet.tests.xunit.extensions/Extensions.cs +++ b/test/coverlet.tests.xunit.extensions/Extensions.cs @@ -1,5 +1,7 @@ -using System.Linq; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Linq; using Xunit.Abstractions; using Xunit.Sdk; @@ -9,9 +11,9 @@ internal static class TestMethodExtensions { public static string EvaluateSkipConditions(this ITestMethod testMethod) { - var testClass = testMethod.TestClass.Class; - var assembly = testMethod.TestClass.TestCollection.TestAssembly.Assembly; - var conditionAttributes = testMethod.Method + ITypeInfo testClass = testMethod.TestClass.Class; + IAssemblyInfo assembly = testMethod.TestClass.TestCollection.TestAssembly.Assembly; + System.Collections.Generic.IEnumerable conditionAttributes = testMethod.Method .GetCustomAttributes(typeof(ITestCondition)) .Concat(testClass.GetCustomAttributes(typeof(ITestCondition))) .Concat(assembly.GetCustomAttributes(typeof(ITestCondition))) diff --git a/test/coverlet.tests.xunit.extensions/ITestCondition.cs b/test/coverlet.tests.xunit.extensions/ITestCondition.cs index a7b3778c3..874ca7b63 100644 --- a/test/coverlet.tests.xunit.extensions/ITestCondition.cs +++ b/test/coverlet.tests.xunit.extensions/ITestCondition.cs @@ -1,4 +1,7 @@ -namespace Coverlet.Tests.Xunit.Extensions +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Coverlet.Tests.Xunit.Extensions { public interface ITestCondition { diff --git a/test/coverlet.tests.xunit.extensions/Properties/AssemblyInfo.cs b/test/coverlet.tests.xunit.extensions/Properties/AssemblyInfo.cs index b030dc19f..3e03952d4 100644 --- a/test/coverlet.tests.xunit.extensions/Properties/AssemblyInfo.cs +++ b/test/coverlet.tests.xunit.extensions/Properties/AssemblyInfo.cs @@ -1,3 +1,6 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System.Reflection; [assembly: AssemblyKeyFile("coverlet.tests.xunit.extensions.snk")] diff --git a/test/coverlet.tests.xunit.extensions/SkipOnOS.cs b/test/coverlet.tests.xunit.extensions/SkipOnOS.cs index 329a799e4..9463171fb 100644 --- a/test/coverlet.tests.xunit.extensions/SkipOnOS.cs +++ b/test/coverlet.tests.xunit.extensions/SkipOnOS.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; using System.Runtime.InteropServices; namespace Coverlet.Tests.Xunit.Extensions diff --git a/test/coverlet.testsubject/.editorconfig b/test/coverlet.testsubject/.editorconfig new file mode 100644 index 000000000..d66ee0772 --- /dev/null +++ b/test/coverlet.testsubject/.editorconfig @@ -0,0 +1,8 @@ +# top-most EditorConfig file +# We don't want to import other EditorConfig files and we want +# to ensure no rules are enabled for these asset source files. +root = true + +[*.cs] +# Default severity for all analyzer diagnostics +dotnet_analyzer_diagnostic.severity = none diff --git a/test/coverlet.testsubject/coverlet.testsubject.csproj b/test/coverlet.testsubject/coverlet.testsubject.csproj index 6acabd989..3020bf383 100644 --- a/test/coverlet.testsubject/coverlet.testsubject.csproj +++ b/test/coverlet.testsubject/coverlet.testsubject.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 false false diff --git a/version.json b/version.json index 8db7c19a6..6176e4b7a 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "3.1.3-preview.{height}", + "version": "6.0.1-preview.{height}", "publicReleaseRefSpec": [ "^refs/heads/master$" ],