Skip to content

Commit 609003e

Browse files
authored
Draft modification to redirect logs to test output (#4710)
* Draft modification to get all channel output to go to test log when running tests * Attempt to fix API compat issue * Reverted changes to MLContext and now using standard logging mechanism * Fixed span bounds checking * Added member variables to LoggingEventArgs to control logging * Added a separate RawMessage variable to preserve the behavior of the original Message member * Fixed crash in DnnRetrainTransformer during a netfx run of TensorFlowTransformMNISTLRTrainingTest * Added RetryFact attribute to UriLoaderTests.can_reload_model * Added attribute to control logging at a per test level
1 parent 6dee938 commit 609003e

File tree

11 files changed

+152
-11
lines changed

11 files changed

+152
-11
lines changed

src/Microsoft.ML.Core/Environment/ConsoleEnvironment.cs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ private sealed class ConsoleWriter
2626
private readonly ConsoleEnvironment _parent;
2727
private readonly TextWriter _out;
2828
private readonly TextWriter _err;
29+
private readonly TextWriter _test;
2930

3031
private readonly bool _colorOut;
3132
private readonly bool _colorErr;
@@ -35,7 +36,7 @@ private sealed class ConsoleWriter
3536
private const int _maxDots = 50;
3637
private int _dots;
3738

38-
public ConsoleWriter(ConsoleEnvironment parent, TextWriter outWriter, TextWriter errWriter)
39+
public ConsoleWriter(ConsoleEnvironment parent, TextWriter outWriter, TextWriter errWriter, TextWriter testWriter = null)
3940
{
4041
Contracts.AssertValue(parent);
4142
Contracts.AssertValue(outWriter);
@@ -44,6 +45,7 @@ public ConsoleWriter(ConsoleEnvironment parent, TextWriter outWriter, TextWriter
4445
_parent = parent;
4546
_out = outWriter;
4647
_err = errWriter;
48+
_test = testWriter;
4749

4850
_colorOut = outWriter == Console.Out;
4951
_colorErr = outWriter == Console.Error;
@@ -86,10 +88,19 @@ public void PrintMessage(IMessageSource sender, ChannelMessage msg)
8688
string prefix = WriteAndReturnLinePrefix(msg.Sensitivity, wr);
8789
var commChannel = sender as PipeBase<ChannelMessage>;
8890
if (commChannel?.Verbose == true)
91+
{
8992
WriteHeader(wr, commChannel);
93+
if (_test != null)
94+
WriteHeader(_test, commChannel);
95+
}
9096
if (msg.Kind == ChannelMessageKind.Warning)
97+
{
9198
wr.Write("Warning: ");
99+
_test?.Write("Warning: ");
100+
}
92101
_parent.PrintMessageNormalized(wr, msg.Message, true, prefix);
102+
if (_test != null)
103+
_parent.PrintMessageNormalized(_test, msg.Message, true);
93104
if (toColor)
94105
Console.ResetColor();
95106
}
@@ -340,6 +351,9 @@ protected override void Dispose(bool disposing)
340351
private volatile ConsoleWriter _consoleWriter;
341352
private readonly MessageSensitivity _sensitivityFlags;
342353

354+
// This object is used to write to the test log along with the console if the host process is a test environment
355+
private TextWriter _testWriter;
356+
343357
/// <summary>
344358
/// Create an ML.NET <see cref="IHostEnvironment"/> for local execution, with console feedback.
345359
/// </summary>
@@ -348,10 +362,11 @@ protected override void Dispose(bool disposing)
348362
/// <param name="sensitivity">Allowed message sensitivity.</param>
349363
/// <param name="outWriter">Text writer to print normal messages to.</param>
350364
/// <param name="errWriter">Text writer to print error messages to.</param>
365+
/// <param name="testWriter">Optional TextWriter to write messages if the host is a test environment.</param>
351366
public ConsoleEnvironment(int? seed = null, bool verbose = false,
352367
MessageSensitivity sensitivity = MessageSensitivity.All,
353-
TextWriter outWriter = null, TextWriter errWriter = null)
354-
: this(RandomUtils.Create(seed), verbose, sensitivity, outWriter, errWriter)
368+
TextWriter outWriter = null, TextWriter errWriter = null, TextWriter testWriter = null)
369+
: this(RandomUtils.Create(seed), verbose, sensitivity, outWriter, errWriter, testWriter)
355370
{
356371
}
357372

@@ -364,14 +379,16 @@ public ConsoleEnvironment(int? seed = null, bool verbose = false,
364379
/// <param name="sensitivity">Allowed message sensitivity.</param>
365380
/// <param name="outWriter">Text writer to print normal messages to.</param>
366381
/// <param name="errWriter">Text writer to print error messages to.</param>
382+
/// <param name="testWriter">Optional TextWriter to write messages if the host is a test environment.</param>
367383
private ConsoleEnvironment(Random rand, bool verbose = false,
368384
MessageSensitivity sensitivity = MessageSensitivity.All,
369-
TextWriter outWriter = null, TextWriter errWriter = null)
385+
TextWriter outWriter = null, TextWriter errWriter = null, TextWriter testWriter = null)
370386
: base(rand, verbose, nameof(ConsoleEnvironment))
371387
{
372388
Contracts.CheckValueOrNull(outWriter);
373389
Contracts.CheckValueOrNull(errWriter);
374-
_consoleWriter = new ConsoleWriter(this, outWriter ?? Console.Out, errWriter ?? Console.Error);
390+
_testWriter = testWriter;
391+
_consoleWriter = new ConsoleWriter(this, outWriter ?? Console.Out, errWriter ?? Console.Error, testWriter);
375392
_sensitivityFlags = sensitivity;
376393
AddListener<ChannelMessage>(PrintMessage);
377394
}
@@ -444,7 +461,7 @@ public OutputRedirector(ConsoleEnvironment env, TextWriter newOutWriter, TextWri
444461
Contracts.AssertValue(newOutWriter);
445462
Contracts.AssertValue(newErrWriter);
446463
_root = env.Root;
447-
_newConsoleWriter = new ConsoleWriter(_root, newOutWriter, newErrWriter);
464+
_newConsoleWriter = new ConsoleWriter(_root, newOutWriter, newErrWriter, _root._testWriter);
448465
_oldConsoleWriter = Interlocked.Exchange(ref _root._consoleWriter, _newConsoleWriter);
449466
Contracts.AssertValue(_oldConsoleWriter);
450467
}

src/Microsoft.ML.Data/LoggingEventArgs.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
using Microsoft.ML.Runtime;
67

78
namespace Microsoft.ML
89
{
@@ -20,9 +21,38 @@ public LoggingEventArgs(string message)
2021
Message = message;
2122
}
2223

24+
/// <summary>
25+
/// Initializes a new instane of <see cref="LoggingEventArgs"/> class that includes the kind and source of the message
26+
/// </summary>
27+
/// <param name="message"> The message being logged </param>
28+
/// <param name="kind"> The type of message <see cref="ChannelMessageKind"/> </param>
29+
/// <param name="source"> The source of the message </param>
30+
public LoggingEventArgs(string message, ChannelMessageKind kind, string source)
31+
{
32+
RawMessage = message;
33+
Kind = kind;
34+
Source = source;
35+
Message = $"[Source={Source}, Kind={Kind}] {RawMessage}";
36+
}
37+
38+
/// <summary>
39+
/// Gets the source component of the event
40+
/// </summary>
41+
public string Source { get; }
42+
43+
/// <summary>
44+
/// Gets the type of message
45+
/// </summary>
46+
public ChannelMessageKind Kind { get; }
47+
2348
/// <summary>
2449
/// Gets the message being logged.
2550
/// </summary>
2651
public string Message { get; }
52+
53+
/// <summary>
54+
/// Gets the original message that doesn't include the source and kind
55+
/// </summary>
56+
public string RawMessage { get; }
2757
}
2858
}

src/Microsoft.ML.Data/MLContext.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,7 @@ private void ProcessMessage(IMessageSource source, ChannelMessage message)
131131
if (log == null)
132132
return;
133133

134-
var msg = $"[Source={source.FullName}, Kind={message.Kind}] {message.Message}";
135-
136-
log(this, new LoggingEventArgs(msg));
134+
log(this, new LoggingEventArgs(message.Message, message.Kind, source.FullName));
137135
}
138136

139137
string IExceptionContext.ContextDescription => _env.ContextDescription;

src/Microsoft.ML.Mkl.Components/SymSgdClassificationTrainer.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,8 @@ private TPredictor TrainCore(IChannel ch, RoleMappedData data, LinearModelParame
809809
if (stateGCHandle.IsAllocated)
810810
stateGCHandle.Free();
811811
}
812+
813+
ch.Info($"Bias: {bias}, Weights: [{String.Join(",", weights.DenseValues())}]");
812814
return CreatePredictor(weights, bias);
813815
}
814816

src/Microsoft.ML.Vision/DnnRetrainTransform.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ internal DnnRetrainTransformer(IHostEnvironment env, Session session, string[] o
528528

529529
_env = env;
530530
_session = session;
531-
_modelLocation = modelLocation;
531+
_modelLocation = Path.IsPathRooted(modelLocation) ? modelLocation : Path.Combine(Directory.GetCurrentDirectory(), modelLocation);
532532
_isTemporarySavedModel = isTemporarySavedModel;
533533
_addBatchDimensionInput = addBatchDimensionInput;
534534
_inputs = inputColumnNames;

test/BaselineOutput/Common/SymSGD/SymSGD-CV-breast-cancer-out.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ maml.exe CV tr=SymSGD{nt=1} threads=- norm=No dout=%Output% data=%Data% seed=1
22
Not adding a normalizer.
33
Data fully loaded into memory.
44
Initial learning rate is tuned to 100.000000
5+
Bias: -468.3528, Weights: [4.515409,75.74901,22.2914,-10.50209,-28.58107,44.81024,23.8734,13.20304,2.448269]
56
Not training a calibrator because it is not needed.
67
Not adding a normalizer.
78
Data fully loaded into memory.
89
Initial learning rate is tuned to 100.000000
10+
Bias: -484.2862, Weights: [-12.78704,140.4291,121.9383,37.5274,-129.8139,70.9061,-89.37057,81.64314,-32.32779]
911
Not training a calibrator because it is not needed.
1012
Warning: The predictor produced non-finite prediction values on 8 instances during testing. Possible causes: abnormal data or the predictor is numerically unstable.
1113
TEST POSITIVE RATIO: 0.3785 (134.0/(134.0+220.0))

test/BaselineOutput/Common/SymSGD/SymSGD-TrainTest-breast-cancer-out.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ maml.exe TrainTest test=%Data% tr=SymSGD{nt=1} norm=No dout=%Output% data=%Data%
22
Not adding a normalizer.
33
Data fully loaded into memory.
44
Initial learning rate is tuned to 100.000000
5+
Bias: -448.1, Weights: [-0.3852913,49.29393,-3.424153,16.76877,-25.15009,23.68305,-6.658058,13.76585,4.843107]
56
Not training a calibrator because it is not needed.
67
Warning: The predictor produced non-finite prediction values on 16 instances during testing. Possible causes: abnormal data or the predictor is numerically unstable.
78
TEST POSITIVE RATIO: 0.3499 (239.0/(239.0+444.0))

test/Microsoft.ML.TestFramework/BaseTestBaseline.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ protected BaseTestBaseline(ITestOutputHelper output) : base(output)
7474
private string _baselineBuildStringDir;
7575

7676
// The writer to write to test log files.
77+
protected TestLogger TestLogger;
7778
protected StreamWriter LogWriter;
7879
private protected ConsoleEnvironment _env;
7980
protected IHostEnvironment Env => _env;
@@ -97,12 +98,21 @@ protected override void Initialize()
9798

9899
string logPath = Path.Combine(logDir, FullTestName + LogSuffix);
99100
LogWriter = OpenWriter(logPath);
100-
_env = new ConsoleEnvironment(42, outWriter: LogWriter, errWriter: LogWriter)
101+
102+
TestLogger = new TestLogger(Output);
103+
_env = new ConsoleEnvironment(42, outWriter: LogWriter, errWriter: LogWriter, testWriter: TestLogger)
101104
.AddStandardComponents();
102105
ML = new MLContext(42);
106+
ML.Log += LogTestOutput;
103107
ML.AddStandardComponents();
104108
}
105109

110+
private void LogTestOutput(object sender, LoggingEventArgs e)
111+
{
112+
if (e.Kind >= MessageKindToLog)
113+
Output.WriteLine(e.Message);
114+
}
115+
106116
// This method is used by subclass to dispose of disposable objects
107117
// such as LocalEnvironment.
108118
// It is called as a first step in test clean up.

test/Microsoft.ML.TestFramework/BaseTestClass.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
using System.Reflection;
99
using System.Threading;
1010
using Microsoft.ML.Internal.Internallearn.Test;
11+
using Microsoft.ML.Runtime;
1112
using Microsoft.ML.TestFrameworkCommon;
13+
using Microsoft.ML.TestFrameworkCommon.Attributes;
14+
using Xunit;
1215
using Xunit.Abstractions;
1316

1417
namespace Microsoft.ML.TestFramework
@@ -18,6 +21,8 @@ public class BaseTestClass : IDisposable
1821
public string TestName { get; set; }
1922
public string FullTestName { get; set; }
2023

24+
public ChannelMessageKind MessageKindToLog;
25+
2126
static BaseTestClass()
2227
{
2328
AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
@@ -54,6 +59,13 @@ public BaseTestClass(ITestOutputHelper output)
5459
FullTestName = test.TestCase.TestMethod.TestClass.Class.Name + "." + test.TestCase.TestMethod.Method.Name;
5560
TestName = test.TestCase.TestMethod.Method.Name;
5661

62+
MessageKindToLog = ChannelMessageKind.Error;
63+
var attributes = test.TestCase.TestMethod.Method.GetCustomAttributes(typeof(LogMessageKind));
64+
foreach (var attrib in attributes)
65+
{
66+
MessageKindToLog = attrib.GetNamedArgument<ChannelMessageKind>("MessageKind");
67+
}
68+
5769
// write to the console when a test starts and stops so we can identify any test hangs/deadlocks in CI
5870
Console.WriteLine($"Starting test: {FullTestName}");
5971
Initialize();
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.ComponentModel.DataAnnotations;
4+
using System.IO;
5+
using System.Text;
6+
using Xunit.Abstractions;
7+
8+
namespace Microsoft.ML.TestFramework
9+
{
10+
public sealed class TestLogger : TextWriter
11+
{
12+
private Encoding _encoding;
13+
private ITestOutputHelper _testOutput;
14+
15+
public override Encoding Encoding => _encoding;
16+
17+
public TestLogger(ITestOutputHelper testOutput)
18+
{
19+
_testOutput = testOutput;
20+
_encoding = new UnicodeEncoding();
21+
}
22+
23+
public override void Write(char value)
24+
{
25+
_testOutput.WriteLine($"{value}");
26+
}
27+
28+
public override void Write(string value)
29+
{
30+
if (value.EndsWith("\r\n"))
31+
value = value.Substring(0, value.Length - 2);
32+
_testOutput.WriteLine(value);
33+
}
34+
35+
public override void Write(string format, params object[] args)
36+
{
37+
if (format.EndsWith("\r\n"))
38+
format = format.Substring(0, format.Length - 2);
39+
40+
_testOutput.WriteLine(format, args);
41+
}
42+
43+
public override void Write(char[] buffer, int index, int count)
44+
{
45+
var span = buffer.AsSpan(index, count);
46+
if ((span.Length >= 2) && (span[count - 2] == '\r') && (span[count - 1] == '\n'))
47+
span = span.Slice(0, count - 2);
48+
_testOutput.WriteLine(span.ToString());
49+
}
50+
}
51+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using Microsoft.ML.Runtime;
7+
8+
namespace Microsoft.ML.TestFrameworkCommon.Attributes
9+
{
10+
public sealed class LogMessageKind : Attribute
11+
{
12+
public ChannelMessageKind MessageKind { get; }
13+
public LogMessageKind(ChannelMessageKind messageKind)
14+
{
15+
MessageKind = messageKind;
16+
}
17+
}
18+
}

0 commit comments

Comments
 (0)