-
Notifications
You must be signed in to change notification settings - Fork 36
Expand file tree
/
Copy pathVSharp.cs
More file actions
317 lines (283 loc) · 14.7 KB
/
VSharp.cs
File metadata and controls
317 lines (283 loc) · 14.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using VSharp.Interpreter.IL;
using VSharp.Solver;
namespace VSharp
{
/// <summary>
/// Summary of V# test generation process.
/// </summary>
public sealed class Statistics
{
internal Statistics(TimeSpan time, DirectoryInfo outputDir, uint tests, uint errors, IEnumerable<string> iies)
{
TestGenerationTime = time;
OutputDir = outputDir;
TestsCount = tests;
ErrorsCount = errors;
IncompleteBranches = iies;
}
/// <summary>
/// Overall time of test generation.
/// </summary>
public TimeSpan TestGenerationTime { get; }
/// <summary>
/// Directory where *.vst tests are placed
/// </summary>
public DirectoryInfo OutputDir { get; }
/// <summary>
/// The amount of generated unit tests.
/// </summary>
public uint TestsCount { get; }
/// <summary>
/// The amount of errors found.
/// </summary>
public uint ErrorsCount { get; }
/// <summary>
/// Some program branches might be failed to investigate. This enumerates the reasons of such failures.
/// </summary>
public IEnumerable<string> IncompleteBranches { get; }
/// <summary>
/// Writes textual summary of test generation process.
/// </summary>
/// <param name="writer">Output writer.</param>
public void GenerateReport(TextWriter writer)
{
writer.WriteLine("Total time: {0:00}:{1:00}:{2:00}.{3}.", TestGenerationTime.Hours,
TestGenerationTime.Minutes, TestGenerationTime.Seconds, TestGenerationTime.Milliseconds);
var count = IncompleteBranches.Count();
if (count > 0)
{
writer.WriteLine();
writer.WriteLine("{0} branch(es) with insufficient input information!", count);
foreach (var message in IncompleteBranches)
{
writer.WriteLine(message);
}
}
writer.WriteLine("Test results written to {0}", OutputDir.FullName);
}
}
public static class TestGenerator
{
private static Statistics StartExploration(List<MethodBase> methods, string resultsFolder, string[] mainArguments = null, int timeout = -1)
{
var recThreshold = 0u;
UnitTests unitTests = new UnitTests(resultsFolder);
// TODO: customize search strategies via console options
var options =
new SiliOptions(
explorationMode.NewTestCoverageMode(coverageZone.MethodZone, searchMode.DFSMode),
executionMode.SymbolicMode,
unitTests.TestDirectory,
recThreshold,
timeout,
false,
true,
128,
true);
SILI explorer = new SILI(options);
Core.API.ConfigureSolver(SolverPool.mkSolver());
void HandleInternalFail(MethodBase method, Exception exception)
{
Logger.printLogString(Logger.Error, $"Internal exception | {method.DeclaringType}.{method.Name} | {exception.GetType().Name} {exception.Message}");
}
foreach (var method in methods)
{
if (method == method.Module.Assembly.EntryPoint)
{
explorer.InterpretEntryPoint(method, mainArguments, unitTests.GenerateTest, unitTests.GenerateError, _ => { },
e => HandleInternalFail(method, e));
}
else
{
explorer.InterpretIsolated(method, unitTests.GenerateTest, unitTests.GenerateError, _ => { },
e => HandleInternalFail(method, e));
}
}
var statistics = new Statistics(explorer.Statistics.CurrentExplorationTime, unitTests.TestDirectory,
unitTests.UnitTestsCount, unitTests.ErrorsCount,
explorer.Statistics.IncompleteStates.Select(e => e.iie.Value.Message).Distinct());
unitTests.WriteReport(explorer.Statistics.PrintStatistics);
return statistics;
}
public static bool Reproduce(DirectoryInfo testDir)
{
return TestRunner.TestRunner.ReproduceTests(testDir);
}
/// <summary>
/// Generates test coverage for specified method.
/// </summary>
/// <param name="method">Type to be covered with tests.</param>
/// <param name="timeout">Timeout for code exploration in seconds. Negative value means infinite timeout (up to exhaustive coverage or user interuption).</param>
/// <param name="outputDirectory">Directory to place generated *.vst tests. If null or empty, process working directory is used.</param>
/// <returns>Summary of tests generation process.</returns>
public static Statistics Cover(MethodBase method, int timeout = -1, string outputDirectory = "")
{
AssemblyManager.Load(method.Module.Assembly);
List<MethodBase> methods = new List<MethodBase> {method};
return StartExploration(methods, outputDirectory, null, timeout);
}
/// <summary>
/// Generates test coverage for all public methods of specified type.
/// </summary>
/// <param name="type">Type to be covered with tests.</param>
/// <param name="timeout">Timeout for code exploration in seconds. Negative value means infinite timeout (up to exhaustive coverage or user interuption).</param>
/// <param name="outputDirectory">Directory to place generated *.vst tests. If null or empty, process working directory is used.</param>
/// <returns>Summary of tests generation process.</returns>
/// <exception cref="ArgumentException">Thrown if specified class does not contain public methods.</exception>
public static Statistics Cover(Type type, int timeout = -1, string outputDirectory = "")
{
AssemblyManager.Load(type.Module.Assembly);
List<MethodBase> methods = new List<MethodBase>(type.GetConstructors());
BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public |
BindingFlags.DeclaredOnly;
methods.AddRange(type.GetMethods(bindingFlags));
if (methods.Count == 0)
{
throw new ArgumentException("I've not found any public method or constructor of class " + type.FullName);
}
return StartExploration(methods, outputDirectory, null, timeout);
}
/// <summary>
/// Generates test coverage for all public methods of all public classes in the specified assembly.
/// </summary>
/// <param name="assembly">Assembly to be covered with tests.</param>
/// <param name="timeout">Timeout for code exploration in seconds. Negative value means infinite timeout (up to exhaustive coverage or user interuption).</param>
/// <param name="outputDirectory">Directory to place generated *.vst tests. If null or empty, process working directory is used.</param>
/// <returns>Summary of tests generation process.</returns>
/// <exception cref="ArgumentException">Thrown if no public methods found in assembly.
/// </exception>
public static Statistics Cover(Assembly assembly, int timeout = -1, string outputDirectory = "")
{
AssemblyManager.Load(assembly);
BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public |
BindingFlags.DeclaredOnly;
var methods = new List<MethodBase>();
var types = new List<Type>();
try
{
types.AddRange(assembly.GetTypes());
}
catch (ReflectionTypeLoadException e)
{
foreach (var loaderException in e.LoaderExceptions)
{
Logger.printLogString(Logger.Error, $"Cannot load type: {loaderException?.Message}");
}
foreach (var type in e.Types)
{
if (type is not null)
{
types.Add(type);
}
}
}
foreach (var t in types)
{
if (t.IsPublic || t.IsNestedPublic)
{
foreach (var m in t.GetMethods(bindingFlags))
{
global::System.Reflection.MethodBody methodBody = null;
var hasByRefLikes = true;
try
{
methodBody = m.GetMethodBody();
hasByRefLikes = Reflection.hasByRefLikes(m);
}
catch (Exception e)
{
Logger.printLogString(Logger.Error, $"Cannot get method info: {e.Message}");
}
if (methodBody is not null && !hasByRefLikes)
methods.Add(m);
}
}
}
if (methods.Count == 0)
{
throw new ArgumentException("I've not found any public method in assembly");
}
return StartExploration(methods, outputDirectory, null, timeout);
}
/// <summary>
/// Generates test coverage for the entry point of the specified assembly.
/// </summary>
/// <param name="assembly">Assembly to be covered with tests.</param>
/// <param name="args">Command line arguments of entry point</param>
/// <param name="timeout">Timeout for code exploration in seconds. Negative value means infinite timeout (up to exhaustive coverage or user interuption).</param>
/// <param name="outputDirectory">Directory to place generated *.vst tests. If null or empty, process working directory is used.</param>
/// <returns>Summary of tests generation process.</returns>
/// <exception cref="ArgumentException">Thrown if assembly does not contain entry point.
/// </exception>
public static Statistics Cover(Assembly assembly, string[] args, int timeout = -1, string outputDirectory = "")
{
AssemblyManager.Load(assembly);
List<MethodBase> methods;
var entryPoint = assembly.EntryPoint;
if (entryPoint == null)
{
throw new ArgumentException("I've not found entry point in assembly");
}
methods = new List<MethodBase> { entryPoint };
return StartExploration(methods, outputDirectory, args, timeout);
}
/// <summary>
/// Generates test coverage for the specified method and runs all tests.
/// </summary>
/// <param name="method">Type to be covered with tests.</param>
/// <param name="timeout">Timeout for code exploration in seconds. Negative value means infinite timeout (up to exhaustive coverage or user interuption).</param>
/// <param name="outputDirectory">Directory to place generated *.vst tests. If null or empty, process working directory is used.</param>
/// <returns>True if all generated tests have passed.</returns>
public static bool CoverAndRun(MethodBase method, int timeout = -1, string outputDirectory = "")
{
var stats = Cover(method, timeout, outputDirectory);
return Reproduce(stats.OutputDir);
}
/// <summary>
/// Generates test coverage for the specified type and runs all tests.
/// </summary>
/// <param name="type">Type to be covered with tests.</param>
/// <param name="timeout">Timeout for code exploration in seconds. Negative value means infinite timeout (up to exhaustive coverage or user interuption).</param>
/// <param name="outputDirectory">Directory to place generated *.vst tests. If null or empty, process working directory is used.</param>
/// <returns>True if all generated tests have passed.</returns>
/// <exception cref="ArgumentException">Thrown if specified class does not contain public methods.</exception>
public static bool CoverAndRun(Type type, int timeout = -1, string outputDirectory = "")
{
var stats = Cover(type, timeout, outputDirectory);
return Reproduce(stats.OutputDir);
}
/// <summary>
/// Generates test coverage for all public methods of all public classes of the specified assembly and runs all tests.
/// </summary>
/// <param name="assembly">Assembly to be covered with tests.</param>
/// <param name="timeout">Timeout for code exploration in seconds. Negative value means infinite timeout (up to exhaustive coverage or user interuption).</param>
/// <param name="outputDirectory">Directory to place generated *.vst tests. If null or empty, process working directory is used.</param>
/// <returns>True if all generated tests have passed.</returns>
/// <exception cref="ArgumentException">Thrown if no public methods found in assembly.
/// </exception>
public static bool CoverAndRun(Assembly assembly, int timeout = -1, string outputDirectory = "")
{
var stats = Cover(assembly, timeout, outputDirectory);
return Reproduce(stats.OutputDir);
}
/// <summary>
/// Generates test coverage for entry point of the specified assembly and runs all tests.
/// </summary>
/// <param name="assembly">Assembly to be covered with tests.</param>
/// <param name="args">Command line arguments of entry point</param>
/// <param name="timeout">Timeout for code exploration in seconds. Negative value means infinite timeout (up to exhaustive coverage or user interuption).</param>
/// <param name="outputDirectory">Directory to place generated *.vst tests. If null or empty, process working directory is used.</param>
/// <returns>True if all generated tests have passed.</returns>
/// <exception cref="ArgumentException">Thrown if assembly does not contain entry point.</exception>
public static bool CoverAndRun(Assembly assembly, string[] args, int timeout = -1, string outputDirectory = "")
{
var stats = Cover(assembly, args, timeout, outputDirectory);
return Reproduce(stats.OutputDir);
}
}
}