Skip to content

Commit

Permalink
Merge branch 'master' into develop/github/assembly-tracing
Browse files Browse the repository at this point in the history
# Conflicts:
#	AltCover.Engine/CommandLine.fs
#	AltCover.Engine/Output.fs
#	ReleaseNotes.md
  • Loading branch information
SteveGilham committed Jun 19, 2022
2 parents 85f698f + 4b67a58 commit 0acb0dd
Show file tree
Hide file tree
Showing 23 changed files with 435 additions and 165 deletions.
6 changes: 4 additions & 2 deletions AltCover.Api.Tests/FSApiTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1301,11 +1301,13 @@ module FSApiTests =
|> AltCover.AltCover.CollectOptions.Primitive

let prep2 =
{ pprep with Verbosity = TraceLevel.Error }
{ pprep with
Verbosity = TraceLevel.Error
Dependencies = [ "nonesuch.dll" ] }
|> AltCover.AltCover.PrepareOptions.Primitive

test
<@ DotNet.ToTestArguments prep2 coll2 combined = "/p:AltCover=\"true\" /p:AltCoverReportFormat=\"OpenCover\" /p:AltCoverShowStatic=\"-\" /p:AltCoverVerbosity=\"Error\" /p:AltCoverShowSummary=\"R\" /p:AltCoverForce=\"true\" /p:AltCoverFailFast=\"true\"" @>
<@ DotNet.ToTestArguments prep2 coll2 combined = "/p:AltCover=\"true\" /p:AltCoverDependencyList=\"nonesuch.dll|\" /p:AltCoverReportFormat=\"OpenCover\" /p:AltCoverShowStatic=\"-\" /p:AltCoverVerbosity=\"Error\" /p:AltCoverShowSummary=\"R\" /p:AltCoverForce=\"true\" /p:AltCoverFailFast=\"true\"" @>

[<Test>]
let MergeRejectsNonCoverage () =
Expand Down
23 changes: 18 additions & 5 deletions AltCover.DotNet/DotNet.fs
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,21 @@ module DotNet =
"AvoidMethodWithUnusedGenericTypeRule",
Justification = "Compiler Generated")>]
let internal toPrepareListArgumentList (prepare: Abstract.IPrepareOptions) =
let dependencies = prepare.Dependencies

let extra =
if dependencies |> Seq.isEmpty then
0
else
1

let suffix =
String.Empty |> Seq.replicate extra

let d2 = suffix |> Seq.append dependencies

[ fromList, "SymbolDirectories", prepare.SymbolDirectories //=`"pipe `'|'` separated list of paths"
fromList, "DependencyList", prepare.Dependencies //=`"pipe `'|'` separated list of paths"
fromList, "DependencyList", d2 //=`"pipe `'|'` separated *AND TERMINATED* list of paths"
fromList, "Keys", prepare.Keys //=`"pipe `'|'` separated list of paths to strong-name keys for re-signing assemblies"
fromList, "FileFilter", prepare.FileFilter //=`"pipe `'|'` separated list of file name regexes"
fromList, "AssemblyFilter", prepare.AssemblyFilter //=`"pipe `'|'` separated list of names"
Expand All @@ -108,12 +121,12 @@ module DotNet =
fromArg, "ShowStatic", prepare.ShowStatic ] //=-|+|++` to mark simple code like auto-properties in the coverage file

let internal toPrepareArgArgumentList (prepare: Abstract.IPrepareOptions) =
[ (arg, "ZipFile", "false", prepare.ZipFile) //="true|false"` - set "true" to store the coverage report in a `.zip` archive
(arg, "MethodPoint", "false", prepare.MethodPoint) //="true|false"` - set "true" to record only the first point of each method
(arg, "Single", "false", prepare.SingleVisit) //="true|false"` - set "true" to record only the first visit to each point
[ (arg, "ZipFile", "true", prepare.ZipFile) //="true|false"` - set "true" to store the coverage report in a `.zip` archive
(arg, "MethodPoint", "true", prepare.MethodPoint) //="true|false"` - set "true" to record only the first point of each method
(arg, "Single", "true", prepare.SingleVisit) //="true|false"` - set "true" to record only the first visit to each point
(arg, "LineCover", "true", prepare.LineCover) //="true|false"` - set "true" to record only line coverage in OpenCover format
(arg, "BranchCover", "true", prepare.BranchCover) //="true|false"` - set "true" to record only branch coverage in OpenCover format
(arg, "SourceLink", "false", prepare.SourceLink) //=true|false` to opt for SourceLink document URLs for tracked files
(arg, "SourceLink", "true", prepare.SourceLink) //=true|false` to opt for SourceLink document URLs for tracked files
(arg, "LocalSource", "true", prepare.LocalSource) //=true|false` to ignore assemblies with `.pdb`s that don't refer to local source
(arg, "VisibleBranches", "true", prepare.VisibleBranches) //=true|false` to ignore compiler generated internal `switch`/`match` branches
(arg, "ShowGenerated", "true", prepare.ShowGenerated) //=true|false` to mark generated code in the coverage file
Expand Down
2 changes: 1 addition & 1 deletion AltCover.Engine/AltCover.Engine.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@
<Compile Include="ProgramDatabase.fs" />
<Compile Include="Filter.fs" />
<Compile Include="Metadata.fs" />
<Compile Include="CecilEx.fs" />
<Compile Include="Visitor.fs" />
<Compile Include="Naming.fs" />
<Compile Include="Report.fs" />
<Compile Include="Gendarme.fs" />
<Compile Include="OpenCover.fs" />
<Compile Include="NativeJson.fs" />
<Compile Include="CommandLine.fs" />
<Compile Include="CecilEx.fs" />
<Compile Include="Instrument.fs" />
<Compile Include="LCov.fs" />
<Compile Include="Cobertura.fs" />
Expand Down
2 changes: 1 addition & 1 deletion AltCover.Engine/AltCover.fs
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ module AltCover =
CommandLine.error <-
String.Format(
System.Globalization.CultureInfo.CurrentCulture,
CommandLine.resources.GetString "Incompatible",
Output.resources.GetString "Incompatible",
"--branchcover",
"--linecover"
)
Expand Down
135 changes: 134 additions & 1 deletion AltCover.Engine/CecilEx.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,131 @@
namespace AltCover

open System
open System.Collections.Generic
open System.Diagnostics.CodeAnalysis
open System.IO
open System.Reflection

open Mono.Cecil
open Mono.Cecil.Cil

module AssemblyConstants =
let internal nugetCache =
Path.Combine(
Path.Combine(
Environment.GetFolderPath Environment.SpecialFolder.UserProfile,
".nuget"
),
"packages"
)

let internal resolutionTable =
Dictionary<string, AssemblyDefinition>()

let internal findAssemblyName f =
try
(AssemblyName.GetAssemblyName f).ToString()
with
| :? ArgumentException
| :? FileNotFoundException
| :? System.Security.SecurityException
| :? BadImageFormatException
| :? FileLoadException -> String.Empty

[<SuppressMessage("Gendarme.Rules.Smells",
"RelaxedAvoidCodeDuplicatedInSameClassRule",
Justification = "minimum size overloads")>]
[<Sealed>]
type internal AssemblyResolver() as self =
inherit DefaultAssemblyResolver()

do
self.add_ResolveFailure
<| new AssemblyResolveEventHandler(AssemblyResolver.ResolveFromNugetCache)

static member private AssemblyRegister (name: string) (path: string) =
let def = AssemblyResolver.ReadAssembly path // recursive
AssemblyConstants.resolutionTable.[name] <- def
def

[<SuppressMessage("Gendarme.Rules.Correctness",
"EnsureLocalDisposalRule",
Justification = "Owned by registration table")>]
static member Register (name: string) (path: string) =
AssemblyResolver.AssemblyRegister name path
|> ignore

static member ReadAssembly(path: String) =
let reader = ReaderParameters()
reader.AssemblyResolver <- new AssemblyResolver()
AssemblyDefinition.ReadAssembly(path, reader)

static member ReadAssembly(file: Stream) =
let reader = ReaderParameters()
reader.AssemblyResolver <- new AssemblyResolver()
AssemblyDefinition.ReadAssembly(file, reader)

[<SuppressMessage("Gendarme.Rules.Performance",
"AvoidUnusedParametersRule",
Justification = "meets an interface")>]
static member internal ResolveFromNugetCache _ (y: AssemblyNameReference) =
let name = y.ToString()

if AssemblyConstants.resolutionTable.ContainsKey name then
AssemblyConstants.resolutionTable.[name]
else
// Placate Gendarme here
let share =
"|usr|share"
.Replace('|', Path.DirectorySeparatorChar)

let shared =
"dotnet|shared"
.Replace('|', Path.DirectorySeparatorChar)

let sources =
[ Environment.GetEnvironmentVariable "NUGET_PACKAGES"
Path.Combine(
Environment.GetEnvironmentVariable "ProgramFiles"
|> Option.ofObj
|> (Option.defaultValue share),
shared
)
Path.Combine(share, shared)
AssemblyConstants.nugetCache ]

let candidate source =
source
|> List.filter (String.IsNullOrWhiteSpace >> not)
|> List.filter Directory.Exists
|> Seq.distinct
|> Seq.collect (fun dir ->
Directory.GetFiles(dir, y.Name + ".*", SearchOption.AllDirectories))
|> Seq.sortDescending
|> Seq.filter (fun f ->
let x = Path.GetExtension f

x.Equals(".exe", StringComparison.OrdinalIgnoreCase)
|| x.Equals(".dll", StringComparison.OrdinalIgnoreCase))
|> Seq.filter (fun f ->
y
.ToString()
.Equals(AssemblyConstants.findAssemblyName f, StringComparison.Ordinal))
|> Seq.tryHead

match candidate sources with
| None -> null
| Some x ->
String.Format(
System.Globalization.CultureInfo.CurrentCulture,
Output.resources.GetString "resolved",
y.ToString(),
x
)
|> (Output.warnOn true)

AssemblyResolver.AssemblyRegister name x

[<AutoOpen>]
module internal CecilExtension =
let internal scopesSeen =
Expand Down Expand Up @@ -287,4 +409,15 @@ module internal CecilExtension =
|> Seq.filter (fun i -> i.OpCode = OpCodes.Tail)
|> Seq.iter (fun i ->
i.OpCode <- OpCodes.Nop
i.Operand <- null)
i.Operand <- null)

let internal hookResolveHandler =
new AssemblyResolveEventHandler(AssemblyResolver.ResolveFromNugetCache)

let internal hookResolver (resolver: IAssemblyResolver) =
if resolver.IsNotNull then
let hook =
resolver.GetType().GetMethod("add_ResolveFailure")

hook.Invoke(resolver, [| hookResolveHandler :> obj |])
|> ignore
41 changes: 13 additions & 28 deletions AltCover.Engine/CommandLine.fs
Original file line number Diff line number Diff line change
Expand Up @@ -111,17 +111,14 @@ module internal CommandLine =

let internal dropReturnCode = ref false // ddFlag

let internal resources =
ResourceManager("AltCover.Strings", Assembly.GetExecutingAssembly())

[<SuppressMessage("Gendarme.Rules.Design",
"AbstractTypesShouldNotHavePublicConstructorsRule",
Justification = "The compiler ignores the 'private ()' declaration")>]
[<System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage>]
[<AbstractClass; Sealed>] // ~ Static class for methods with params array arguments
type internal Format private () =
static member Local(resource, [<ParamArray>] args) =
String.Format(CultureInfo.CurrentCulture, resources.GetString resource, args)
String.Format(CultureInfo.CurrentCulture, Output.resources.GetString resource, args)

module internal I =
let internal conditionalOutput condition output = if condition () then output ()
Expand Down Expand Up @@ -274,16 +271,6 @@ module internal CommandLine =
static member Throw<'T>(e: exn) : 'T =
(e.Message, e) |> SecurityException |> raise

let internal findAssemblyName f =
try
(AssemblyName.GetAssemblyName f).ToString()
with
| :? ArgumentException
| :? FileNotFoundException
| :? System.Security.SecurityException
| :? BadImageFormatException
| :? FileLoadException -> String.Empty

let internal transformCryptographicException f =
try
f ()
Expand Down Expand Up @@ -365,7 +352,7 @@ module internal CommandLine =
(fun () ->
tag |> String.IsNullOrWhiteSpace |> not
&& error |> List.isEmpty |> not)
(fun () -> tag |> resources.GetString |> Output.error)
(fun () -> tag |> Output.resources.GetString |> Output.error)

error |> List.iter Output.error

Expand Down Expand Up @@ -412,7 +399,8 @@ module internal CommandLine =

let internal validateAssembly assembly x =
if I.validateFile assembly x then
let name = I.findAssemblyName x
let name =
AssemblyConstants.findAssemblyName x

if String.IsNullOrWhiteSpace name then
error <-
Expand Down Expand Up @@ -447,9 +435,6 @@ module internal CommandLine =
let internal doPathOperation =
I.doPathOperation

let internal findAssemblyName =
I.findAssemblyName

let internal validateDirectory dir x =
I.validateFileSystemEntity Directory.Exists I.dnf dir x

Expand All @@ -475,25 +460,25 @@ module internal CommandLine =

let internal usageBase u =
I.writeColoured Console.Error ConsoleColor.Yellow (fun w ->
w.WriteLine(resources.GetString u.Intro)
w.WriteLine(Output.resources.GetString u.Intro)
u.Options.WriteOptionDescriptions(w)

if u.Options.Any() && u.Options2.Any() then
w.WriteLine(resources.GetString "orbinder")
w.WriteLine(Output.resources.GetString "orbinder")

if u.Options2.Any() then
w.WriteLine(" Runner")
u.Options2.WriteOptionDescriptions(w)

w.WriteLine(resources.GetString "orbinder")
w.WriteLine(resources.GetString "ImportModule")
w.WriteLine(resources.GetString "orbinder")
w.WriteLine(resources.GetString "Version")
w.WriteLine(resources.GetString "orglobal")
w.WriteLine(resources.GetString "TargetsPath"))
w.WriteLine(Output.resources.GetString "orbinder")
w.WriteLine(Output.resources.GetString "ImportModule")
w.WriteLine(Output.resources.GetString "orbinder")
w.WriteLine(Output.resources.GetString "Version")
w.WriteLine(Output.resources.GetString "orglobal")
w.WriteLine(Output.resources.GetString "TargetsPath"))

let internal writeResource =
resources.GetString >> Output.info
Output.resources.GetString >> Output.info

let internal writeResourceWithFormatItems s x warn =
Format.Local(s, x) |> (Output.warnOn warn)
Expand Down

0 comments on commit 0acb0dd

Please sign in to comment.