Skip to content

Commit 13dc346

Browse files
Mohammad DehghanMohammad Dehghan
authored andcommitted
Implement working automated tests for instrumentation
Fixed-port controller server is tested.
1 parent ec2165f commit 13dc346

File tree

9 files changed

+299
-27
lines changed

9 files changed

+299
-27
lines changed

SG.CodeCoverage.TestConsole/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ static void Main()
1515
{
1616
var tester = new InstrumenterTester();
1717
tester.InstrumentSampleProject();
18-
tester.RunSomeCode();
18+
tester.RunApp();
19+
tester.RunIsPrimeInApp(7);
1920
var result = tester.GetCoverageResult();
2021
Console.WriteLine($"Visited {result.GetVisitedSources().Count()} source files" +
2122
$" and {result.GetVisitedMethods().Count()} methods.");

SG.CodeCoverage.Tests.NetFx/InstrumenterTester.cs

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
using SG.CodeCoverage.Metadata;
77
using System;
88
using System.Collections.Generic;
9+
using System.Diagnostics;
910
using System.IO;
1011
using System.Linq;
1112
using System.Reflection;
13+
using System.Text;
14+
using System.Threading;
1215

1316
namespace SG.CodeCoverage.Tests
1417
{
@@ -21,6 +24,8 @@ public class InstrumenterTester
2124
public IRecordingController RecordingController { get; private set; }
2225
public string InstrumentedAssemblyPath { get; private set; }
2326
private readonly ILogger _logger;
27+
private Process _process;
28+
private List<string> _processOutput;
2429

2530
public InstrumenterTester(ILogger logger = null)
2631
{
@@ -68,14 +73,63 @@ private void CleanDirectory(string dirPath)
6873
Directory.Delete(dir, true);
6974
}
7075

71-
public void RunSomeCode()
76+
public void RunApp(string args = null)
7277
{
7378
if (InstrumentedAssemblyPath == null)
7479
throw new InvalidOperationException("Sample assembly is not instrumented.");
75-
Assembly.LoadFrom(Path.Combine(OutputPath, "SG.CodeCoverage.Recorder.dll"));
76-
var assembly = Assembly.LoadFrom(InstrumentedAssemblyPath);
77-
var calc = assembly.DefinedTypes.Where(x => x.Name == nameof(PrimeCalculator)).FirstOrDefault();
78-
var res = calc.GetMethod("IsPrime").Invoke(calc.DeclaredConstructors.First().Invoke(null), new object[] { 7 });
80+
_process = Process.Start(new ProcessStartInfo()
81+
{
82+
FileName = InstrumentedAssemblyPath,
83+
Arguments = args,
84+
UseShellExecute = false,
85+
WorkingDirectory = OutputPath,
86+
RedirectStandardInput = true,
87+
RedirectStandardOutput = true
88+
});
89+
_processOutput = new List<string>();
90+
_process.OutputDataReceived += _process_OutputDataReceived;
91+
_process.BeginOutputReadLine();
92+
WaitForAppIdle();
93+
}
94+
95+
private void _process_OutputDataReceived(object sender, DataReceivedEventArgs e)
96+
{
97+
if (string.IsNullOrEmpty(e.Data))
98+
return;
99+
_processOutput.Add(e.Data);
100+
}
101+
102+
private void WaitForAppIdle()
103+
{
104+
CheckAppStarted();
105+
while (_processOutput.Count == 0 || _processOutput.Last() != "Enter command:")
106+
Thread.Sleep(1);
107+
}
108+
109+
public bool AppIsRunning
110+
=> _process != null && !_process.HasExited;
111+
112+
public void ExitApp()
113+
{
114+
CheckAppStarted();
115+
_process.StandardInput.WriteLine("exit");
116+
if (!_process.WaitForExit(10000))
117+
throw new Exception("App did not exit withing 10 seconds.");
118+
_process.WaitForExit();
119+
_process = null;
120+
}
121+
122+
public void RunIsPrimeInApp(int number)
123+
{
124+
CheckAppStarted();
125+
_process.StandardInput.WriteLine("IsPrime " + number);
126+
WaitForAppIdle();
127+
}
128+
129+
private void CheckAppStarted()
130+
{
131+
if (_process == null)
132+
throw new InvalidOperationException("Sample application is not started.");
79133
}
80134

81135
public CoverageResult GetCoverageResult()

SG.CodeCoverage.Tests.NetFx/TestInstrumentation.cs

Lines changed: 133 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,141 @@ namespace SG.CodeCoverage.Tests.NetFx
1212
[TestClass]
1313
public class TestInstrumentation
1414
{
15+
InstrumenterTester _tester;
16+
17+
[TestInitialize]
18+
public void Instrument()
19+
{
20+
_tester = new InstrumenterTester();
21+
_tester.InstrumentSampleProject();
22+
}
23+
24+
[TestMethod]
25+
public void Coverage_Startup()
26+
{
27+
_tester.RunApp();
28+
AssertVisitedFilesAndMethods(Files.Startup, Methods.Startup);
29+
}
30+
1531
[TestMethod]
16-
public void TestSampleProjectInstrumented()
32+
public void Coverage_IsPrime1()
33+
{
34+
_tester.RunApp("IsPrime 1");
35+
AssertVisitedFilesAndMethods(
36+
Files.Startup
37+
.Concat(Files.PrimeCalculator),
38+
Methods.Startup
39+
.Concat(Methods.RunCommand)
40+
.Concat(Methods.PrimeCalculator.IsPrimeAndIsLessThan2));
41+
}
42+
43+
[TestMethod]
44+
public void Coverage_IsPrime4()
45+
{
46+
_tester.RunApp("IsPrime 4");
47+
AssertVisitedFilesAndMethods(
48+
Files.Startup
49+
.Concat(Files.PrimeCalculator),
50+
Methods.Startup
51+
.Concat(Methods.RunCommand)
52+
.Concat(Methods.PrimeCalculator.IsPrimeAndIsLessThan2)
53+
.Concat(Methods.PrimeCalculator.GetUpperBound));
54+
}
55+
56+
57+
[TestCleanup]
58+
public void ExitApp()
1759
{
18-
var tester = new InstrumenterTester();
19-
tester.InstrumentSampleProject();
20-
var map = InstrumentationMap.Parse(tester.MapFilePath);
60+
if (_tester.AppIsRunning)
61+
_tester.ExitApp();
62+
}
63+
64+
private void AssertVisitedFilesAndMethods(
65+
IEnumerable<string> expectedVisitedFiles,
66+
IEnumerable<string> expectedVisitedMethods)
67+
{
68+
var result = _tester.GetCoverageResult();
69+
var files = result.GetVisitedSources().ToList();
70+
var methods = result.GetVisitedMethodNames().ToList();
71+
ShouldVisit(expectedVisitedFiles.ToList(), files, "file");
72+
ShouldVisit(expectedVisitedMethods.ToList(), methods, "method");
73+
}
74+
75+
private static void ShouldVisit(IReadOnlyList<string> expectedNames, IReadOnlyList<string> actualNames, string what)
76+
{
77+
Assert.AreEqual(expectedNames.Count, actualNames.Count, $"visited {what}s");
78+
foreach(var expected in expectedNames)
79+
{
80+
bool found = false;
81+
foreach(var actual in actualNames)
82+
{
83+
if(actual.EndsWith(expected))
84+
{
85+
found = true;
86+
break;
87+
}
88+
}
89+
if (!found)
90+
Assert.Fail($"{what} {expected} is not visited.");
91+
}
92+
}
93+
94+
private static class Files
95+
{
96+
public static readonly IEnumerable<string> Startup = new string[]
97+
{
98+
"App.cs",
99+
"Program.cs"
100+
};
101+
102+
public static readonly IEnumerable<string> PrimeCalculator = new string[]
103+
{
104+
"PrimeCalculator.cs"
105+
};
106+
107+
public static readonly IEnumerable<string> SampleStruct = new string[]
108+
{
109+
"SampleStruct.cs"
110+
};
111+
}
112+
113+
private static class Methods
114+
{
115+
public static readonly IEnumerable<string> Startup = new string[]
116+
{
117+
"Program::Main(System.String[])",
118+
"App::.ctor()",
119+
"App::get_Commands()",
120+
"App::Run(System.String[])",
121+
"Command::.ctor(System.String,System.String,System.Action`1<System.String>)",
122+
"Command::get_Help()",
123+
};
124+
125+
public static readonly IEnumerable<string> RunCommand = new string[]
126+
{
127+
"App::RunCommand(System.String[])",
128+
"Command::get_CommandText()",
129+
"Command::get_Operation()",
130+
};
131+
132+
public static class PrimeCalculator
133+
{
134+
public static readonly IEnumerable<string> IsPrime = new string[]
135+
{
136+
"App::IsPrime(System.String)",
137+
"PrimeCalculator::IsPrime(System.Int64)",
138+
};
139+
public static readonly IEnumerable<string> IsLessThan2 = new string[]
140+
{
141+
"PrimeCalculator::IsLessThan2(System.Int64)"
142+
};
143+
public static readonly IEnumerable<string> GetUpperBound = new string[]
144+
{
145+
"PrimeCalculator::GetUpperBound(System.Int64)"
146+
};
147+
public static IEnumerable<string> IsPrimeAndIsLessThan2
148+
=> IsPrime.Concat(IsLessThan2);
149+
}
21150
}
22151
}
23152
}

SampleProjectForTest/App.cs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace SampleProjectForTest
8+
{
9+
public class App
10+
{
11+
public IReadOnlyList<Command> Commands { get; }
12+
private bool _shouldExit = false;
13+
14+
public App()
15+
{
16+
Commands = new List<Command>()
17+
{
18+
new Command( "exit", "exit", Exit),
19+
new Command("isprime", "IsPrime", IsPrime)
20+
};
21+
}
22+
23+
public void Run(string[] args)
24+
{
25+
if (args.Length > 0)
26+
RunCommand(args);
27+
28+
Console.WriteLine("Commands:");
29+
foreach (var cmd in Commands)
30+
Console.WriteLine(" " + cmd.Help);
31+
32+
while (!_shouldExit)
33+
{
34+
Console.WriteLine("Enter command:");
35+
var line = Console.ReadLine();
36+
var parts = line.Split(Array.Empty<char>(), StringSplitOptions.RemoveEmptyEntries);
37+
RunCommand(parts);
38+
}
39+
}
40+
41+
private void RunCommand(string[] parts)
42+
{
43+
bool found = false;
44+
foreach (var cmd in Commands)
45+
{
46+
if (parts[0].Equals(cmd.CommandText, StringComparison.OrdinalIgnoreCase))
47+
{
48+
cmd.Operation(parts.Length > 0 ? parts[1] : null);
49+
found = true;
50+
break;
51+
}
52+
}
53+
54+
if (!found)
55+
throw new Exception($"Command '{parts[0]}' not found.");
56+
}
57+
58+
private void Exit(string _)
59+
{
60+
_shouldExit = true;
61+
}
62+
63+
private void IsPrime(string input)
64+
{
65+
var number = int.Parse(input);
66+
var isPrime = new PrimeCalculator().IsPrime(number);
67+
Console.WriteLine($"{number} is {(isPrime ? "" : "not ")}prime.");
68+
}
69+
}
70+
71+
public class Command
72+
{
73+
public Command(string commandText, string help, Action<string> operation)
74+
{
75+
CommandText = commandText;
76+
Help = help;
77+
Operation = operation;
78+
}
79+
80+
public string CommandText { get; }
81+
public string Help { get; }
82+
public Action<string> Operation { get; }
83+
}
84+
}

SampleProjectForTest/ClassWithStaticInitializer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public ClassWithStaticInitializer()
1616
Console.WriteLine("constructor");
1717
}
1818

19-
public void PringAssemblyLocation()
19+
public void PrintAssemblyLocation()
2020
{
2121
Console.WriteLine(AssemblyLocation.Value);
2222
}

SampleProjectForTest/GlobalSuppressions.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,9 @@
33
// Project-level suppressions either have no target or are given
44
// a specific target and scoped to a namespace, type, member, etc.
55

6-
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1810:Initialize reference type static fields inline", Justification = "<Pending>", Scope = "member", Target = "~M:SampleProjectForTest.SampleClassWithStaticConstructor.#cctor")]
7-
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:SampleProjectForTest.SampleClassWithStaticInitializer.PringAssemblyLocation")]
86
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "<Pending>", Scope = "member", Target = "~M:SampleProjectForTest.Program.Main(System.String[])")]
9-
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:SampleProjectForTest.SampleClassWithStaticInitializer.PrintAnotherValueAndWait(System.String)")]
10-
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:SampleProjectForTest.SampleClassWithStaticInitializer.EmptyMethod")]
117
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1810:Initialize reference type static fields inline", Justification = "<Pending>", Scope = "member", Target = "~M:SampleProjectForTest.StaticClassWithStaticConstructor.#cctor")]
128
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:SampleProjectForTest.PrimeCalculator.IsPrime(System.Int64)~System.Boolean")]
139
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:SampleProjectForTest.ClassWithStaticInitializer.EmptyMethod")]
1410
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:SampleProjectForTest.ClassWithStaticInitializer.PrintAnotherValueAndWait(System.String)")]
15-
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:SampleProjectForTest.ClassWithStaticInitializer.PringAssemblyLocation")]
11+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:SampleProjectForTest.ClassWithStaticInitializer.PrintAssemblyLocation")]

SampleProjectForTest/PrimeCalculator.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,29 @@ public class PrimeCalculator
1010
{
1111
public bool IsPrime(long number)
1212
{
13-
var sqrt = Math.Sqrt(number);
14-
if (number < 2)
13+
if (IsLessThan2(number))
1514
return false;
1615
if (number == 2)
1716
return true;
18-
for (int i = 3; i <= sqrt; i++)
17+
var up = GetUpperBound(number);
18+
for (int i = 2; i <= up; i++)
1919
if (number % i == 0)
2020
return false;
21+
22+
var value = new SampleStruct(4);
23+
value.Multiply(3);
24+
2125
return true;
2226
}
27+
28+
private static bool IsLessThan2(long number)
29+
{
30+
return number < 2;
31+
}
32+
33+
private long GetUpperBound(long number)
34+
{
35+
return (long)Math.Ceiling(Math.Sqrt(number));
36+
}
2337
}
2438
}

0 commit comments

Comments
 (0)