Skip to content

Commit 208086f

Browse files
committed
Adjust functional tests and add failure cases
1 parent 4c1a361 commit 208086f

File tree

5 files changed

+152
-36
lines changed

5 files changed

+152
-36
lines changed

src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -347,8 +347,6 @@ internal async Task<int> Collect(CancellationToken ct, CommandLineConfiguration
347347
{
348348
ConsoleWriteLine($"Trace Duration : {duration:dd\\:hh\\:mm\\:ss}");
349349
}
350-
351-
ConsoleWriteLine();
352350
ConsoleWriteLine();
353351

354352
EventMonitor eventMonitor = null;
@@ -391,10 +389,19 @@ internal async Task<int> Collect(CancellationToken ct, CommandLineConfiguration
391389
}
392390

393391
FileInfo fileInfo = new(output.FullName);
392+
bool wroteStatus = false;
394393
Action printStatus = () => {
395394
if (printStatusOverTime && rewriter.IsRewriteConsoleLineSupported)
396395
{
397-
rewriter?.RewriteConsoleLine();
396+
if (wroteStatus)
397+
{
398+
rewriter?.RewriteConsoleLine();
399+
}
400+
else
401+
{
402+
// First time writing status, so don't rewrite console yet.
403+
wroteStatus = true;
404+
}
398405
fileInfo.Refresh();
399406
ConsoleWriteLine($"[{stopwatch.Elapsed:dd\\:hh\\:mm\\:ss}]\tRecording trace {GetSize(fileInfo.Length)}");
400407
ConsoleWriteLine("Press <Enter> or <Ctrl+C> to exit...");

src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ namespace Microsoft.Diagnostics.Tools.Trace
1818
{
1919
internal partial class CollectLinuxCommandHandler
2020
{
21-
private static bool s_stopTracing;
22-
private static Stopwatch s_stopwatch = new();
23-
private static LineRewriter s_rewriter;
24-
private static bool s_printingStatus;
21+
private bool s_stopTracing;
22+
private Stopwatch s_stopwatch = new();
23+
private LineRewriter s_rewriter;
24+
private bool s_printingStatus;
2525

2626
internal sealed record CollectLinuxArgs(
2727
CancellationToken Ct,
@@ -48,7 +48,7 @@ internal int CollectLinux(CollectLinuxArgs args)
4848
if (!OperatingSystem.IsLinux())
4949
{
5050
Console.Error.WriteLine("The collect-linux command is only supported on Linux.");
51-
return (int)ReturnCode.ArgumentError;
51+
return (int)ReturnCode.PlatformNotSupportedError;
5252
}
5353

5454
args.Ct.Register(() => s_stopTracing = true);
@@ -72,6 +72,11 @@ internal int CollectLinux(CollectLinuxArgs args)
7272
s_stopwatch.Start();
7373
ret = RecordTraceInvoker(command, (UIntPtr)command.Length, OutputHandler);
7474
}
75+
catch (Exception ex)
76+
{
77+
Console.Error.WriteLine($"[ERROR] {ex}");
78+
ret = (int)ReturnCode.TracingError;
79+
}
7580
finally
7681
{
7782
if (!string.IsNullOrEmpty(scriptPath))

src/tests/Common/MockConsole.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ public void AssertLinesEqual(int startLine, params string[] expectedLines)
158158
}
159159
}
160160

161-
public void AssertSanitizedLinesEqual(Func<string[], string[]> sanitizer, params string[] expectedLines)
161+
public void AssertSanitizedLinesEqual(Func<string[], string[]> sanitizer, bool ignorePastExpected = false, params string[] expectedLines)
162162
{
163163
string[] actualLines = Lines;
164164
if (sanitizer is not null)
@@ -178,9 +178,12 @@ public void AssertSanitizedLinesEqual(Func<string[], string[]> sanitizer, params
178178
$"Line {i,2} Actual : {actualLines[i]}");
179179
}
180180
}
181-
for (int i = expectedLines.Length; i < actualLines.Length; i++)
181+
if (!ignorePastExpected)
182182
{
183-
Assert.True(string.IsNullOrEmpty(actualLines[i]), $"Actual line #{i} beyond expected lines is not empty: {actualLines[i]}");
183+
for (int i = expectedLines.Length; i < actualLines.Length; i++)
184+
{
185+
Assert.True(string.IsNullOrEmpty(actualLines[i]), $"Actual line #{i} beyond expected lines is not empty: {actualLines[i]}");
186+
}
184187
}
185188
}
186189
}

src/tests/dotnet-trace/CollectCommandFunctionalTests.cs

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Threading.Tasks;
1111
using Microsoft.Diagnostics.Tests.Common;
1212
using Microsoft.Diagnostics.Tools.Trace;
13+
using Microsoft.Internal.Common.Utils;
1314
using Xunit;
1415

1516
namespace Microsoft.Diagnostics.Tools.Trace
@@ -51,22 +52,33 @@ public sealed record CollectArgs(
5152
public async Task CollectCommandProviderConfigurationConsolidation(CollectArgs args, string[] expectedSubset)
5253
{
5354
MockConsole console = new(200, 30);
54-
string[] rawLines = await RunAsync(args, console).ConfigureAwait(true);
55-
console.AssertSanitizedLinesEqual(CollectSanitizer, expectedSubset);
55+
int exitCode = await RunAsync(args, console).ConfigureAwait(true);
56+
Assert.Equal((int)ReturnCode.Ok, exitCode);
57+
console.AssertSanitizedLinesEqual(CollectSanitizer, expectedLines: expectedSubset);
5658

5759
byte[] expected = Encoding.UTF8.GetBytes(ExpectedPayload);
5860
Assert.Equal(expected, args.EventStream.ToArray());
5961
}
6062

61-
private static async Task<string[]> RunAsync(CollectArgs config, MockConsole console)
63+
[Theory]
64+
[MemberData(nameof(InvalidProviders))]
65+
public async Task CollectCommandInvalidProviderConfiguration_Throws(CollectArgs args, string[] expectedException)
66+
{
67+
MockConsole console = new(200, 30);
68+
int exitCode = await RunAsync(args, console).ConfigureAwait(true);
69+
Assert.Equal((int)ReturnCode.TracingError, exitCode);
70+
console.AssertSanitizedLinesEqual(CollectSanitizer, true, expectedException);
71+
}
72+
73+
private static async Task<int> RunAsync(CollectArgs config, MockConsole console)
6274
{
6375
var handler = new CollectCommandHandler();
6476
handler.StartTraceSessionAsync = (client, cfg, ct) => Task.FromResult<CollectCommandHandler.ICollectSession>(new TestCollectSession());
6577
handler.ResumeRuntimeAsync = (client, ct) => Task.CompletedTask;
6678
handler.CollectSessionEventStream = (name) => config.EventStream;
6779
handler.Console = console;
6880

69-
int exit = await handler.Collect(
81+
return await handler.Collect(
7082
config.ct,
7183
config.cliConfig,
7284
config.ProcessId,
@@ -87,12 +99,7 @@ private static async Task<string[]> RunAsync(CollectArgs config, MockConsole con
8799
config.stoppingEventPayloadFilter,
88100
config.rundown,
89101
config.dsrouter
90-
).ConfigureAwait(true);
91-
if (exit != 0)
92-
{
93-
throw new InvalidOperationException($"Collect exited with return code {exit}.");
94-
}
95-
return console.Lines;
102+
).ConfigureAwait(false);
96103
}
97104

98105
private static string[] CollectSanitizer(string[] lines)
@@ -123,7 +130,6 @@ public void Stop() {}
123130

124131
public static IEnumerable<object[]> BasicCases()
125132
{
126-
FileInfo fi = new("trace.nettrace");
127133
yield return new object[]
128134
{
129135
new CollectArgs(),
@@ -214,14 +220,46 @@ public static IEnumerable<object[]> BasicCases()
214220
};
215221
}
216222

223+
public static IEnumerable<object[]> InvalidProviders()
224+
{
225+
yield return new object[]
226+
{
227+
new CollectArgs(profile: new[] { "cpu-sampling" }),
228+
new [] { FormatException("The specified profile 'cpu-sampling' does not apply to `dotnet-trace collect`.", "System.ArgumentException") }
229+
};
230+
231+
yield return new object[]
232+
{
233+
new CollectArgs(profile: new[] { "unknown" }),
234+
new [] { FormatException("Invalid profile name: unknown", "System.ArgumentException") }
235+
};
236+
237+
yield return new object[]
238+
{
239+
new CollectArgs(providers: new[] { "Foo:::Bar=0", "Foo:::Bar=1" }),
240+
new [] { FormatException($"Provider \"Foo\" is declared multiple times with filter arguments.", "System.ArgumentException") }
241+
};
242+
243+
yield return new object[]
244+
{
245+
new CollectArgs(clrevents: "unknown"),
246+
new [] { FormatException("unknown is not a valid CLR event keyword", "System.ArgumentException") }
247+
};
248+
249+
yield return new object[]
250+
{
251+
new CollectArgs(clrevents: "gc", clreventlevel: "unknown"),
252+
new [] { FormatException("Unknown EventLevel: unknown", "System.ArgumentException") }
253+
};
254+
}
255+
217256
private static string outputFile = $"Output File : {Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar}trace.nettrace";
218257
private const string ProviderHeader = "Provider Name Keywords Level Enabled By";
219258
private static readonly string[] CommonTail = [
220259
"Process : <PROCESS>",
221260
outputFile,
222261
"",
223262
"",
224-
"",
225263
"Trace completed."
226264
];
227265

@@ -238,5 +276,7 @@ private static string FormatProvider(string name, string keywordsHex, string lev
238276
string.Format("{0, -8}", $"{levelName}({levelValue})");
239277
return string.Format("{0, -80}", display) + enabledBy;
240278
}
279+
280+
private static string FormatException(string message, string exceptionType) => $"[ERROR] {exceptionType}: {message}";
241281
}
242282
}

src/tests/dotnet-trace/CollectLinuxCommandFunctionalTests.cs

Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System.Linq;
1212
using Microsoft.Diagnostics.Tests.Common;
1313
using Microsoft.Diagnostics.Tools.Trace;
14+
using Microsoft.Internal.Common.Utils;
1415
using Xunit;
1516

1617
namespace Microsoft.Diagnostics.Tools.Trace
@@ -23,7 +24,7 @@ private static CollectLinuxCommandHandler.CollectLinuxArgs TestArgs(
2324
string clrEventLevel = "",
2425
string clrEvents = "",
2526
string[] perfEvents = null,
26-
string[] profiles = null,
27+
string[] profile = null,
2728
FileInfo output = null,
2829
TimeSpan duration = default)
2930
{
@@ -32,7 +33,7 @@ private static CollectLinuxCommandHandler.CollectLinuxArgs TestArgs(
3233
clrEventLevel,
3334
clrEvents,
3435
perfEvents ?? Array.Empty<string>(),
35-
profiles ?? Array.Empty<string>(),
36+
profile ?? Array.Empty<string>(),
3637
output ?? new FileInfo(CommonOptions.DefaultTraceName),
3738
duration);
3839
}
@@ -42,23 +43,49 @@ private static CollectLinuxCommandHandler.CollectLinuxArgs TestArgs(
4243
public void CollectLinuxCommandProviderConfigurationConsolidation(object testArgs, string[] expectedLines)
4344
{
4445
MockConsole console = new(200, 30);
45-
var handler = new CollectLinuxCommandHandler(console);
46-
handler.RecordTraceInvoker = (cmd, len, cb) => 0;
47-
int exit = handler.CollectLinux((CollectLinuxCommandHandler.CollectLinuxArgs)testArgs);
46+
int exitCode = Run(testArgs, console);
4847
if (OperatingSystem.IsLinux())
4948
{
50-
Assert.Equal(0, exit);
51-
console.AssertSanitizedLinesEqual(null, expectedLines);
49+
Assert.Equal((int)ReturnCode.Ok, exitCode);
50+
console.AssertSanitizedLinesEqual(null, expectedLines: expectedLines);
5251
}
5352
else
5453
{
55-
Assert.Equal(3, exit);
56-
console.AssertSanitizedLinesEqual(null, new string[] {
54+
Assert.Equal((int)ReturnCode.PlatformNotSupportedError, exitCode);
55+
console.AssertSanitizedLinesEqual(null, expectedLines: new string[] {
5756
"The collect-linux command is only supported on Linux.",
5857
});
5958
}
6059
}
6160

61+
[Theory]
62+
[MemberData(nameof(InvalidProviders))]
63+
public void CollectLinuxCommandProviderConfigurationConsolidation_Throws(object testArgs, string[] expectedException)
64+
{
65+
MockConsole console = new(200, 30);
66+
int exitCode = Run(testArgs, console);
67+
if (OperatingSystem.IsLinux())
68+
{
69+
Assert.Equal((int)ReturnCode.TracingError, exitCode);
70+
console.AssertSanitizedLinesEqual(null, true, expectedLines: expectedException);
71+
}
72+
else
73+
{
74+
Assert.Equal((int)ReturnCode.PlatformNotSupportedError, exitCode);
75+
console.AssertSanitizedLinesEqual(null, expectedLines: new string[] {
76+
"The collect-linux command is only supported on Linux.",
77+
});
78+
}
79+
}
80+
81+
private static int Run(object args, MockConsole console)
82+
{
83+
var handler = new CollectLinuxCommandHandler(console);
84+
handler.RecordTraceInvoker = (cmd, len, cb) => 0;
85+
return handler.CollectLinux((CollectLinuxCommandHandler.CollectLinuxArgs)args);
86+
}
87+
88+
6289
public static IEnumerable<object[]> BasicCases()
6390
{
6491
yield return new object[] {
@@ -98,7 +125,7 @@ public static IEnumerable<object[]> BasicCases()
98125
}
99126
};
100127
yield return new object[] {
101-
TestArgs(profiles: new[]{"cpu-sampling"}),
128+
TestArgs(profile: new[]{"cpu-sampling"}),
102129
new string[] {
103130
"No .NET providers were configured.",
104131
"",
@@ -108,7 +135,7 @@ public static IEnumerable<object[]> BasicCases()
108135
}
109136
};
110137
yield return new object[] {
111-
TestArgs(providers: new[]{"Foo:0x1:4"}, profiles: new[]{"cpu-sampling"}),
138+
TestArgs(providers: new[]{"Foo:0x1:4"}, profile: new[]{"cpu-sampling"}),
112139
new string[] {
113140
"",
114141
ProviderHeader,
@@ -120,7 +147,7 @@ public static IEnumerable<object[]> BasicCases()
120147
}
121148
};
122149
yield return new object[] {
123-
TestArgs(clrEvents: "gc", profiles: new[]{"cpu-sampling"}),
150+
TestArgs(clrEvents: "gc", profile: new[]{"cpu-sampling"}),
124151
new string[] {
125152
"",
126153
ProviderHeader,
@@ -132,7 +159,7 @@ public static IEnumerable<object[]> BasicCases()
132159
}
133160
};
134161
yield return new object[] {
135-
TestArgs(providers: new[]{"Microsoft-Windows-DotNETRuntime:0x1:4"}, profiles: new[]{"cpu-sampling"}),
162+
TestArgs(providers: new[]{"Microsoft-Windows-DotNETRuntime:0x1:4"}, profile: new[]{"cpu-sampling"}),
136163
new string[] {
137164
"",
138165
ProviderHeader,
@@ -189,6 +216,39 @@ public static IEnumerable<object[]> BasicCases()
189216
};
190217
}
191218

219+
public static IEnumerable<object[]> InvalidProviders()
220+
{
221+
yield return new object[]
222+
{
223+
TestArgs(profile: new[] { "dotnet-sampled-thread-time" }),
224+
new [] { FormatException("The specified profile 'dotnet-sampled-thread-time' does not apply to `dotnet-trace collect-linux`.", "System.ArgumentException") }
225+
};
226+
227+
yield return new object[]
228+
{
229+
TestArgs(profile: new[] { "unknown" }),
230+
new [] { FormatException("Invalid profile name: unknown", "System.ArgumentException") }
231+
};
232+
233+
yield return new object[]
234+
{
235+
TestArgs(providers: new[] { "Foo:::Bar=0", "Foo:::Bar=1" }),
236+
new [] { FormatException($"Provider \"Foo\" is declared multiple times with filter arguments.", "System.ArgumentException") }
237+
};
238+
239+
yield return new object[]
240+
{
241+
TestArgs(clrEvents: "unknown"),
242+
new [] { FormatException("unknown is not a valid CLR event keyword", "System.ArgumentException") }
243+
};
244+
245+
yield return new object[]
246+
{
247+
TestArgs(clrEvents: "gc", clrEventLevel: "unknown"),
248+
new [] { FormatException("Unknown EventLevel: unknown", "System.ArgumentException") }
249+
};
250+
}
251+
192252
private const string ProviderHeader = "Provider Name Keywords Level Enabled By";
193253
private static string LinuxHeader => $"{"Linux Events",-80}Enabled By";
194254
private static string LinuxProfile(string name) => $"{name,-80}--profile";
@@ -200,5 +260,6 @@ private static string FormatProvider(string name, string keywordsHex, string lev
200260
string.Format("{0, -8}", $"{levelName}({levelValue})");
201261
return string.Format("{0, -80}", display) + enabledBy;
202262
}
263+
private static string FormatException(string message, string exceptionType) => $"[ERROR] {exceptionType}: {message}";
203264
}
204265
}

0 commit comments

Comments
 (0)