1
1
namespace VSharp.CoverageTool
2
+
3
+ open System.Collections .Generic
2
4
open System.Diagnostics
3
5
open System.IO
4
6
open System.Reflection
5
7
open System.Runtime .InteropServices
6
8
open Microsoft.FSharp .NativeInterop
9
+
10
+ open VSharp
7
11
open VSharp.Utils .EnvironmentUtils
8
12
open VSharp.CSharpUtils
9
13
10
- open VSharp
11
14
12
15
#nowarn " 9"
13
16
@@ -21,9 +24,6 @@ module private ExternalCalls =
21
24
[<DllImport( " libvsharpCoverage" , CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi) >]
22
25
extern void SetCurrentThreadId( int id)
23
26
24
- let inline castPtr ptr =
25
- ptr |> NativePtr.toVoidPtr |> NativePtr.ofVoidPtr
26
-
27
27
module private Configuration =
28
28
29
29
let (| Windows | MacOs | Linux |) _ =
@@ -66,39 +66,48 @@ module private Configuration =
66
66
methodToken: string
67
67
}
68
68
69
- let private withCoverageToolConfiguration mainOnly =
70
- withConfiguration {
71
- coreclrProfiler = " {2800fea6-9667-4b42-a2b6-45dc98e77e9e}"
72
- coreclrProfilerPath = $" {Directory.GetCurrentDirectory()}{Path.DirectorySeparatorChar}libvsharpCoverage{libExtension}"
73
- coreclrEnableProfiling = enabled
74
- instrumentMainOnly = if mainOnly then enabled else " "
75
- }
69
+ let private withCoverageToolConfiguration mainOnly processInfo =
70
+ let currentDirectory = Directory.GetCurrentDirectory()
71
+ let configuration =
72
+ {
73
+ coreclrProfiler = " {2800fea6-9667-4b42-a2b6-45dc98e77e9e}"
74
+ coreclrProfilerPath = $" {currentDirectory}{Path.DirectorySeparatorChar}libvsharpCoverage{libExtension}"
75
+ coreclrEnableProfiling = enabled
76
+ instrumentMainOnly = if mainOnly then enabled else " "
77
+ }
78
+ withConfiguration configuration processInfo
76
79
77
80
let withMainOnlyCoverageToolConfiguration =
78
81
withCoverageToolConfiguration true
79
82
80
83
let withAllMethodsCoverageToolConfiguration =
81
84
withCoverageToolConfiguration false
82
85
83
- let withPassiveModeConfiguration ( method : MethodBase ) resultName =
84
- withConfiguration {
85
- passiveModeEnable = enabled
86
- resultName = resultName
87
- assemblyName = method.Module.Assembly.FullName
88
- moduleName = method.Module.FullyQualifiedName
89
- methodToken = method.MetadataToken.ToString()
90
- }
86
+ let withPassiveModeConfiguration ( method : MethodBase ) resultName processInfo =
87
+ let configuration =
88
+ {
89
+ passiveModeEnable = enabled
90
+ resultName = resultName
91
+ assemblyName = method.Module.Assembly.FullName
92
+ moduleName = method.Module.FullyQualifiedName
93
+ methodToken = method.MetadataToken.ToString()
94
+ }
95
+ withConfiguration configuration processInfo
91
96
92
97
let isCoverageToolAttached () = isConfigured< BaseCoverageToolConfiguration> ()
93
98
94
99
type InteractionCoverageTool () =
95
100
let mutable entryMainWasSet = false
96
101
102
+ let castPtr ptr =
103
+ NativePtr.toVoidPtr ptr |> NativePtr.ofVoidPtr
104
+
97
105
do
98
106
if Configuration.isCoverageToolAttached () |> not then internalfail " Coverage tool wasn't attached"
99
107
100
108
member this.GetRawHistory () =
101
- if not entryMainWasSet then Prelude.internalfail " Try call GetRawHistory, while entryMain wasn't set"
109
+ if not entryMainWasSet then
110
+ Prelude.internalfail " Try call GetRawHistory, while entryMain wasn't set"
102
111
let sizePtr = NativePtr.stackalloc< uint> 1
103
112
let dataPtrPtr = NativePtr.stackalloc< nativeint> 1
104
113
@@ -111,97 +120,83 @@ type InteractionCoverageTool() =
111
120
Marshal.Copy( dataPtr, data, 0 , size)
112
121
data
113
122
114
- member this.SetEntryMain ( assembly : Assembly ) ( moduleName : string ) ( methodToken : int ) =
123
+ member this.SetEntryMain ( assembly : Assembly ) ( moduleName : string ) ( methodToken : int ) =
115
124
entryMainWasSet <- true
116
125
let assemblyNamePtr = fixed assembly.FullName.ToCharArray()
117
126
let moduleNamePtr = fixed moduleName.ToCharArray()
118
127
let assemblyNameLength = assembly.FullName.Length
119
128
let moduleNameLength = moduleName.Length
120
129
121
130
ExternalCalls.SetEntryMain(
122
- ExternalCalls. castPtr assemblyNamePtr,
131
+ castPtr assemblyNamePtr,
123
132
assemblyNameLength,
124
- ExternalCalls. castPtr moduleNamePtr,
133
+ castPtr moduleNamePtr,
125
134
moduleNameLength,
126
135
methodToken
127
136
)
128
137
129
138
member this.SetCurrentThreadId id =
130
139
ExternalCalls.SetCurrentThreadId( id)
131
140
132
- static member WithCoverageTool ( procInfo : ProcessStartInfo ) =
141
+ static member WithCoverageTool ( procInfo : ProcessStartInfo ) =
133
142
Configuration.withMainOnlyCoverageToolConfiguration procInfo
134
143
135
144
type PassiveCoverageTool ( workingDirectory : DirectoryInfo , method : MethodBase ) =
136
145
137
146
let resultName = " coverage.cov"
138
147
139
148
let getHistory () =
140
- workingDirectory.EnumerateFiles( resultName)
141
- |> Seq.tryHead
142
- |> Option.map ( fun x -> File.ReadAllBytes( x.FullName))
143
- |> Option.map CoverageDeserializer.getRawReports
144
- |> Option.map CoverageDeserializer.reportsFromRawReports
145
-
146
-
147
- let printCoverage ( allBlocks : seq < BasicBlock >) ( visited : seq < BasicBlock >) =
149
+ let coverageFile = workingDirectory.EnumerateFiles( resultName) |> Seq.tryHead
150
+ match coverageFile with
151
+ | Some coverageFile ->
152
+ File.ReadAllBytes( coverageFile.FullName)
153
+ |> CoverageDeserializer.getRawReports
154
+ |> CoverageDeserializer.reportsFromRawReports
155
+ |> Some
156
+ | None -> None
157
+
158
+ let printCoverage ( allBlocks : ResizeArray < BasicBlock >) ( visited : HashSet < BasicBlock >) =
148
159
Logger.writeLine $" Coverage for method {method.Name}:"
149
160
150
- let hasNonCovered = allBlocks |> Seq.exists ( fun b -> Seq.contains b visited |> not )
151
-
152
- if hasNonCovered then
153
- allBlocks
154
- |> Seq.iter ( fun block ->
155
- if Seq.contains block visited |> not then
156
- Logger.writeLine $" Block [0x{block.StartOffset:X} .. 0x{block.FinalOffset:X}] not covered"
157
- )
158
- else
161
+ let mutable allCovered = true
162
+ for block in allBlocks do
163
+ if visited.Contains block |> not then
164
+ Logger.writeLine $" Block [0x{block.StartOffset:X} .. 0x{block.FinalOffset:X}] not covered"
165
+ allCovered <- false
166
+ if allCovered then
159
167
Logger.writeLine " All blocks are covered"
160
168
161
- let computeCoverage ( cfg : CfgInfo ) ( visited : seq < CoverageReport >) =
162
- // filtering coverage records that are only relevant to this method
163
- let visitedInMethod =
164
- visited
165
- |> Seq.map ( fun x -> x.coverageLocations)
166
- |> Seq.concat
167
- |> Seq.filter ( fun x ->
168
- x.methodToken = method.MetadataToken
169
- && x.moduleName = method.Module.FullyQualifiedName
170
- )
171
-
172
- let visitedBlocks = System.Collections.Generic.HashSet< BasicBlock>()
169
+ let computeCoverage ( cfg : CfgInfo ) ( visited : CoverageReport []) =
170
+ let visitedBlocks = HashSet< BasicBlock>()
173
171
174
- for location in visitedInMethod do
175
- let offset = LanguagePrimitives.Int32WithMeasure location.offset
176
- let block = cfg.ResolveBasicBlock( offset)
177
- if block.FinalOffset = offset then
178
- visitedBlocks.Add block |> ignore
172
+ let token = method.MetadataToken
173
+ let moduleName = method.Module.FullyQualifiedName
174
+ for coverageReport in visited do
175
+ for loc in coverageReport.coverageLocations do
176
+ // Filtering coverage records that are only relevant to this method
177
+ if loc.methodToken = token && loc.moduleName = moduleName then
178
+ let offset = Offset.from loc.offset
179
+ let block = cfg.ResolveBasicBlock offset
180
+ if block.FinalOffset = offset then
181
+ visitedBlocks.Add block |> ignore
179
182
180
183
printCoverage cfg.SortedBasicBlocks visitedBlocks
181
-
182
184
let coveredSize = visitedBlocks |> Seq.sumBy ( fun x -> x.BlockSize)
183
-
184
185
( double coveredSize) / ( double cfg.MethodSize) * 100. |> int
185
186
186
187
member this.RunWithCoverage ( args : string ) =
187
- let procInfo =
188
- ProcessStartInfo()
189
- |> ( fun x ->
190
- x.Arguments <- args
191
- x.FileName <- DotnetExecutablePath.ExecutablePath
192
- x.WorkingDirectory <- workingDirectory.FullName
193
- x
194
- )
195
- |> Configuration.withMainOnlyCoverageToolConfiguration
196
- |> Configuration.withPassiveModeConfiguration method resultName
197
-
198
- let applicationMethod = Application.getMethod( method)
188
+ let procInfo = ProcessStartInfo()
189
+ procInfo.Arguments <- args
190
+ procInfo.FileName <- DotnetExecutablePath.ExecutablePath
191
+ procInfo.WorkingDirectory <- workingDirectory.FullName
192
+ Configuration.withMainOnlyCoverageToolConfiguration procInfo
193
+ Configuration.withPassiveModeConfiguration method resultName procInfo
199
194
200
- if applicationMethod.HasBody |> not then
201
- Logger.warning $" CoverageRunner was given a method without body; 100%% coverage assumed"
195
+ let method = Application.getMethod method
196
+ if not method.HasBody then
197
+ Logger.warning " CoverageRunner was given a method without body; 100%% coverage assumed"
202
198
100
203
199
else
204
-
205
200
let proc = procInfo.StartWithLogging(
206
201
( fun x -> Logger.info $" {x}" ),
207
202
( fun x -> Logger.error $" {x}" )
@@ -213,7 +208,7 @@ type PassiveCoverageTool(workingDirectory: DirectoryInfo, method: MethodBase) =
213
208
- 1
214
209
else
215
210
match getHistory () with
216
- | Some history -> computeCoverage applicationMethod .CFG history
211
+ | Some history -> computeCoverage method .CFG history
217
212
| None ->
218
- Logger.error $ " Failed to retrieve coverage locations"
213
+ Logger.error " Failed to retrieve coverage locations"
219
214
- 1
0 commit comments