Stream Forwarding changes to not wait on buffer full before writing. Instead input streams will be read character by character as they Console.Write or Console.WriteLine. Upon finding a newline character, the line will be printed to the parent process's console.
This commit is contained in:
parent
129bd03098
commit
ccaaebf6e5
9 changed files with 256 additions and 153 deletions
|
@ -111,8 +111,8 @@ namespace Microsoft.DotNet.Cli.Utils
|
||||||
return new CommandResult(
|
return new CommandResult(
|
||||||
this._process.StartInfo,
|
this._process.StartInfo,
|
||||||
exitCode,
|
exitCode,
|
||||||
_stdOut.GetCapturedOutput(),
|
_stdOut.CapturedOutput,
|
||||||
_stdErr.GetCapturedOutput());
|
_stdErr.CapturedOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Command WorkingDirectory(string projectDirectory)
|
public Command WorkingDirectory(string projectDirectory)
|
||||||
|
@ -148,11 +148,11 @@ namespace Microsoft.DotNet.Cli.Utils
|
||||||
{
|
{
|
||||||
if (to == null)
|
if (to == null)
|
||||||
{
|
{
|
||||||
_stdOut.ForwardTo(write: Reporter.Output.Write, writeLine: Reporter.Output.WriteLine);
|
_stdOut.ForwardTo(writeLine: Reporter.Output.WriteLine);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_stdOut.ForwardTo(write: to.Write, writeLine: to.WriteLine);
|
_stdOut.ForwardTo(writeLine: to.WriteLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
|
@ -165,11 +165,11 @@ namespace Microsoft.DotNet.Cli.Utils
|
||||||
{
|
{
|
||||||
if (to == null)
|
if (to == null)
|
||||||
{
|
{
|
||||||
_stdErr.ForwardTo(write: Reporter.Error.Write, writeLine: Reporter.Error.WriteLine);
|
_stdErr.ForwardTo(writeLine: Reporter.Error.WriteLine);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_stdErr.ForwardTo(write: to.Write, writeLine: to.WriteLine);
|
_stdErr.ForwardTo(writeLine: to.WriteLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
|
@ -178,14 +178,14 @@ namespace Microsoft.DotNet.Cli.Utils
|
||||||
public Command OnOutputLine(Action<string> handler)
|
public Command OnOutputLine(Action<string> handler)
|
||||||
{
|
{
|
||||||
ThrowIfRunning();
|
ThrowIfRunning();
|
||||||
_stdOut.ForwardTo(write: null, writeLine: handler);
|
_stdOut.ForwardTo(writeLine: handler);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Command OnErrorLine(Action<string> handler)
|
public Command OnErrorLine(Action<string> handler)
|
||||||
{
|
{
|
||||||
ThrowIfRunning();
|
ThrowIfRunning();
|
||||||
_stdErr.ForwardTo(write: null, writeLine: handler);
|
_stdErr.ForwardTo(writeLine: handler);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace Microsoft.DotNet.Cli.Utils
|
||||||
// Stupid-simple console manager
|
// Stupid-simple console manager
|
||||||
public class Reporter
|
public class Reporter
|
||||||
{
|
{
|
||||||
private static readonly Reporter Null = new Reporter(console: null);
|
private static readonly Reporter NullReporter = new Reporter(console: null);
|
||||||
private static object _lock = new object();
|
private static object _lock = new object();
|
||||||
|
|
||||||
private readonly AnsiConsole _console;
|
private readonly AnsiConsole _console;
|
||||||
|
@ -20,8 +20,8 @@ namespace Microsoft.DotNet.Cli.Utils
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Reporter Output { get; } = Create(AnsiConsole.GetOutput);
|
public static Reporter Output { get; } = Create(AnsiConsole.GetOutput);
|
||||||
public static Reporter Error { get; } = Create(AnsiConsole.GetOutput);
|
public static Reporter Error { get; } = Create(AnsiConsole.GetError);
|
||||||
public static Reporter Verbose { get; } = CommandContext.IsVerbose() ? Create(AnsiConsole.GetOutput) : Null;
|
public static Reporter Verbose { get; } = CommandContext.IsVerbose() ? Create(AnsiConsole.GetOutput) : NullReporter;
|
||||||
|
|
||||||
public static Reporter Create(Func<bool, AnsiConsole> getter)
|
public static Reporter Create(Func<bool, AnsiConsole> getter)
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,45 +7,45 @@ namespace Microsoft.DotNet.Cli.Utils
|
||||||
{
|
{
|
||||||
public sealed class StreamForwarder
|
public sealed class StreamForwarder
|
||||||
{
|
{
|
||||||
private const int DefaultBufferSize = 256;
|
|
||||||
|
|
||||||
private readonly int _bufferSize;
|
|
||||||
private StringBuilder _builder;
|
private StringBuilder _builder;
|
||||||
private StringWriter _capture;
|
private StringWriter _capture;
|
||||||
private Action<string> _write;
|
private Action<string> _write;
|
||||||
private Action<string> _writeLine;
|
private Action<string> _writeLine;
|
||||||
|
|
||||||
public StreamForwarder(int bufferSize = DefaultBufferSize)
|
public string CapturedOutput
|
||||||
{
|
{
|
||||||
_bufferSize = bufferSize;
|
get
|
||||||
|
{
|
||||||
|
return _capture?.GetStringBuilder()?.ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Capture()
|
public StreamForwarder Capture()
|
||||||
{
|
{
|
||||||
if (_capture != null)
|
if (_capture != null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Already capturing stream!");
|
throw new InvalidOperationException("Already capturing stream!");
|
||||||
}
|
}
|
||||||
_capture = new StringWriter();
|
_capture = new StringWriter();
|
||||||
|
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetCapturedOutput()
|
public StreamForwarder ForwardTo(Action<string> writeLine)
|
||||||
{
|
|
||||||
return _capture?.GetStringBuilder()?.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ForwardTo(Action<string> write, Action<string> writeLine)
|
|
||||||
{
|
{
|
||||||
if (writeLine == null)
|
if (writeLine == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(writeLine));
|
throw new ArgumentNullException(nameof(writeLine));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_writeLine != null)
|
if (_writeLine != null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Already handling stream!");
|
throw new InvalidOperationException("WriteLine forwarder set previously");
|
||||||
}
|
}
|
||||||
_write = write;
|
|
||||||
_writeLine = writeLine;
|
_writeLine = writeLine;
|
||||||
|
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Thread BeginRead(TextReader reader)
|
public Thread BeginRead(TextReader reader)
|
||||||
|
@ -57,91 +57,59 @@ namespace Microsoft.DotNet.Cli.Utils
|
||||||
|
|
||||||
public void Read(TextReader reader)
|
public void Read(TextReader reader)
|
||||||
{
|
{
|
||||||
|
var bufferSize = 1;
|
||||||
|
|
||||||
|
int readCharacterCount;
|
||||||
|
char currentCharacter;
|
||||||
|
|
||||||
|
var buffer = new char[bufferSize];
|
||||||
_builder = new StringBuilder();
|
_builder = new StringBuilder();
|
||||||
var buffer = new char[_bufferSize];
|
|
||||||
int n;
|
|
||||||
while ((n = reader.Read(buffer, 0, _bufferSize)) > 0)
|
|
||||||
{
|
|
||||||
_builder.Append(buffer, 0, n);
|
|
||||||
WriteBlocks();
|
|
||||||
}
|
|
||||||
WriteRemainder();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WriteBlocks()
|
// Using Read with buffer size 1 to prevent looping endlessly
|
||||||
{
|
// like we would when using Read() with no buffer
|
||||||
int n = _builder.Length;
|
while ((readCharacterCount = reader.Read(buffer, 0, bufferSize)) > 0)
|
||||||
if (n == 0)
|
|
||||||
{
|
{
|
||||||
return;
|
currentCharacter = buffer[0];
|
||||||
}
|
|
||||||
|
|
||||||
int offset = 0;
|
// Flush per line
|
||||||
bool sawReturn = false;
|
if (currentCharacter == '\n')
|
||||||
for (int i = 0; i < n; i++)
|
|
||||||
{
|
|
||||||
char c = _builder[i];
|
|
||||||
switch (c)
|
|
||||||
{
|
{
|
||||||
case '\r':
|
WriteBuilder();
|
||||||
sawReturn = true;
|
}
|
||||||
continue;
|
else
|
||||||
case '\n':
|
{
|
||||||
WriteLine(_builder.ToString(offset, i - offset - (sawReturn ? 1 : 0)));
|
// Ignore \r
|
||||||
offset = i + 1;
|
if (currentCharacter != '\r')
|
||||||
break;
|
{
|
||||||
|
_builder.Append(currentCharacter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sawReturn = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the buffer contains no line breaks and _write is
|
// Flush anything else when the stream is closed
|
||||||
// supported, send the buffer content.
|
// Which should only happen if someone used console.Write
|
||||||
if (!sawReturn &&
|
WriteBuilder();
|
||||||
(offset == 0) &&
|
|
||||||
((_write != null) || (_writeLine == null)))
|
|
||||||
{
|
|
||||||
WriteRemainder();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_builder.Remove(0, offset);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteRemainder()
|
private void WriteBuilder()
|
||||||
{
|
{
|
||||||
if (_builder.Length == 0)
|
if (_builder.Length == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Write(_builder.ToString());
|
|
||||||
|
WriteLine(_builder.ToString());
|
||||||
_builder.Clear();
|
_builder.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteLine(string str)
|
private void WriteLine(string str)
|
||||||
{
|
{
|
||||||
if (_capture != null)
|
if (_capture != null)
|
||||||
{
|
{
|
||||||
_capture.WriteLine(str);
|
_capture.WriteLine(str);
|
||||||
}
|
}
|
||||||
// If _write is supported, so is _writeLine.
|
|
||||||
if (_writeLine != null)
|
|
||||||
{
|
|
||||||
_writeLine(str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Write(string str)
|
if (_writeLine != null)
|
||||||
{
|
|
||||||
if (_capture != null)
|
|
||||||
{
|
|
||||||
_capture.Write(str);
|
|
||||||
}
|
|
||||||
if (_write != null)
|
|
||||||
{
|
|
||||||
_write(str);
|
|
||||||
}
|
|
||||||
else if (_writeLine != null)
|
|
||||||
{
|
{
|
||||||
_writeLine(str);
|
_writeLine(str);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ namespace Microsoft.DotNet.Tools.Restore
|
||||||
{
|
{
|
||||||
private static readonly string DefaultRid = PlatformServices.Default.Runtime.GetLegacyRestoreRuntimeIdentifier();
|
private static readonly string DefaultRid = PlatformServices.Default.Runtime.GetLegacyRestoreRuntimeIdentifier();
|
||||||
|
|
||||||
|
|
||||||
public static int Run(string[] args)
|
public static int Run(string[] args)
|
||||||
{
|
{
|
||||||
DebugHelper.HandleDebugSwitch(ref args);
|
DebugHelper.HandleDebugSwitch(ref args);
|
||||||
|
|
|
@ -32,8 +32,8 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||||
var stdOut = new StreamForwarder();
|
var stdOut = new StreamForwarder();
|
||||||
var stdErr = new StreamForwarder();
|
var stdErr = new StreamForwarder();
|
||||||
|
|
||||||
stdOut.ForwardTo(write: Reporter.Output.Write, writeLine: Reporter.Output.WriteLine);
|
stdOut.ForwardTo(writeLine: Reporter.Output.WriteLine);
|
||||||
stdErr.ForwardTo(write: Reporter.Error.Write, writeLine: Reporter.Output.WriteLine);
|
stdErr.ForwardTo(writeLine: Reporter.Output.WriteLine);
|
||||||
|
|
||||||
return RunProcess(commandPath, args, stdOut, stdErr);
|
return RunProcess(commandPath, args, stdOut, stdErr);
|
||||||
}
|
}
|
||||||
|
@ -82,8 +82,8 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
|
||||||
var result = new CommandResult(
|
var result = new CommandResult(
|
||||||
process.StartInfo,
|
process.StartInfo,
|
||||||
process.ExitCode,
|
process.ExitCode,
|
||||||
stdOut.GetCapturedOutput(),
|
stdOut.CapturedOutput,
|
||||||
stdErr.GetCapturedOutput());
|
stdErr.CapturedOutput);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,109 +10,192 @@ using Xunit;
|
||||||
using Microsoft.DotNet.Cli.Utils;
|
using Microsoft.DotNet.Cli.Utils;
|
||||||
using Microsoft.DotNet.ProjectModel;
|
using Microsoft.DotNet.ProjectModel;
|
||||||
using Microsoft.DotNet.Tools.Test.Utilities;
|
using Microsoft.DotNet.Tools.Test.Utilities;
|
||||||
|
using Microsoft.Extensions.PlatformAbstractions;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace StreamForwarderTests
|
namespace StreamForwarderTests
|
||||||
{
|
{
|
||||||
public class StreamForwarderTests
|
public class StreamForwarderTests : TestBase
|
||||||
{
|
{
|
||||||
|
private static readonly string s_rid = PlatformServices.Default.Runtime.GetLegacyRestoreRuntimeIdentifier();
|
||||||
|
private static readonly string s_testProjectRoot = Path.Combine(AppContext.BaseDirectory, "TestProjects");
|
||||||
|
|
||||||
|
private TempDirectory _root;
|
||||||
|
|
||||||
public static void Main()
|
public static void Main()
|
||||||
{
|
{
|
||||||
Console.WriteLine("Dummy Entrypoint");
|
Console.WriteLine("Dummy Entrypoint");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
public StreamForwarderTests()
|
||||||
public void Unbuffered()
|
|
||||||
{
|
{
|
||||||
Forward(4, true, "");
|
_root = Temp.CreateDirectory();
|
||||||
Forward(4, true, "123", "123");
|
|
||||||
Forward(4, true, "1234", "1234");
|
|
||||||
Forward(3, true, "123456789", "123", "456", "789");
|
|
||||||
Forward(4, true, "\r\n", "\n");
|
|
||||||
Forward(4, true, "\r\n34", "\n", "34");
|
|
||||||
Forward(4, true, "1\r\n4", "1\n", "4");
|
|
||||||
Forward(4, true, "12\r\n", "12\n");
|
|
||||||
Forward(4, true, "123\r\n", "123\n");
|
|
||||||
Forward(4, true, "1234\r\n", "1234", "\n");
|
|
||||||
Forward(3, true, "\r\n3456\r\n9", "\n", "3456", "\n", "9");
|
|
||||||
Forward(4, true, "\n", "\n");
|
|
||||||
Forward(4, true, "\n234", "\n", "234");
|
|
||||||
Forward(4, true, "1\n34", "1\n", "34");
|
|
||||||
Forward(4, true, "12\n4", "12\n", "4");
|
|
||||||
Forward(4, true, "123\n", "123\n");
|
|
||||||
Forward(4, true, "1234\n", "1234", "\n");
|
|
||||||
Forward(3, true, "\n23456\n89", "\n", "23456", "\n", "89");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
public static IEnumerable<object[]> ForwardingTheoryVariations
|
||||||
public void LineBuffered()
|
|
||||||
{
|
{
|
||||||
Forward(4, false, "");
|
get
|
||||||
Forward(4, false, "123", "123\n");
|
{
|
||||||
Forward(4, false, "1234", "1234\n");
|
return new[]
|
||||||
Forward(3, false, "123456789", "123456789\n");
|
{
|
||||||
Forward(4, false, "\r\n", "\n");
|
new object[] { "123", new string[]{"123"} },
|
||||||
Forward(4, false, "\r\n34", "\n", "34\n");
|
new object[] { "123\n", new string[] {"123"} },
|
||||||
Forward(4, false, "1\r\n4", "1\n", "4\n");
|
new object[] { "123\r\n", new string[] {"123"} },
|
||||||
Forward(4, false, "12\r\n", "12\n");
|
new object[] { "1234\n5678", new string[] {"1234", "5678"} },
|
||||||
Forward(4, false, "123\r\n", "123\n");
|
new object[] { "1234\r\n5678", new string[] {"1234", "5678"} },
|
||||||
Forward(4, false, "1234\r\n", "1234\n");
|
new object[] { "1234\n5678\n", new string[] {"1234", "5678"} },
|
||||||
Forward(3, false, "\r\n3456\r\n9", "\n", "3456\n", "9\n");
|
new object[] { "1234\r\n5678\r\n", new string[] {"1234", "5678"} },
|
||||||
Forward(4, false, "\n", "\n");
|
new object[] { "1234\n5678\nabcdefghijklmnopqrstuvwxyz", new string[] {"1234", "5678", "abcdefghijklmnopqrstuvwxyz"} },
|
||||||
Forward(4, false, "\n234", "\n", "234\n");
|
new object[] { "1234\r\n5678\r\nabcdefghijklmnopqrstuvwxyz", new string[] {"1234", "5678", "abcdefghijklmnopqrstuvwxyz"} },
|
||||||
Forward(4, false, "1\n34", "1\n", "34\n");
|
new object[] { "1234\n5678\nabcdefghijklmnopqrstuvwxyz\n", new string[] {"1234", "5678", "abcdefghijklmnopqrstuvwxyz"} },
|
||||||
Forward(4, false, "12\n4", "12\n", "4\n");
|
new object[] { "1234\r\n5678\r\nabcdefghijklmnopqrstuvwxyz\r\n", new string[] {"1234", "5678", "abcdefghijklmnopqrstuvwxyz"} }
|
||||||
Forward(4, false, "123\n", "123\n");
|
};
|
||||||
Forward(4, false, "1234\n", "1234\n");
|
}
|
||||||
Forward(3, false, "\n23456\n89", "\n", "23456\n", "89\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Forward(int bufferSize, bool unbuffered, string str, params string[] expectedWrites)
|
[Theory]
|
||||||
|
[InlineData("123")]
|
||||||
|
[InlineData("123\n")]
|
||||||
|
public void TestNoForwardingNoCapture(string inputStr)
|
||||||
|
{
|
||||||
|
TestCapturingAndForwardingHelper(ForwardOptions.None, inputStr, null, new string[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData("ForwardingTheoryVariations")]
|
||||||
|
public void TestForwardingOnly(string inputStr, string[] expectedWrites)
|
||||||
{
|
{
|
||||||
var expectedCaptured = str.Replace("\r", "").Replace("\n", Environment.NewLine);
|
for(int i = 0; i < expectedWrites.Length; ++i)
|
||||||
|
{
|
||||||
|
expectedWrites[i] += Environment.NewLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestCapturingAndForwardingHelper(ForwardOptions.WriteLine, inputStr, null, expectedWrites);
|
||||||
|
}
|
||||||
|
|
||||||
// No forwarding.
|
[Theory]
|
||||||
Forward(bufferSize, ForwardOptions.None, str, null, new string[0]);
|
[MemberData("ForwardingTheoryVariations")]
|
||||||
|
public void TestCaptureOnly(string inputStr, string[] expectedWrites)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < expectedWrites.Length; ++i)
|
||||||
|
{
|
||||||
|
expectedWrites[i] += Environment.NewLine;
|
||||||
|
}
|
||||||
|
|
||||||
// Capture only.
|
var expectedCaptured = string.Join("", expectedWrites);
|
||||||
Forward(bufferSize, ForwardOptions.Capture, str, expectedCaptured, new string[0]);
|
|
||||||
|
TestCapturingAndForwardingHelper(ForwardOptions.Capture, inputStr, expectedCaptured, new string[0]);
|
||||||
|
}
|
||||||
|
|
||||||
var writeOptions = unbuffered ?
|
[Theory]
|
||||||
ForwardOptions.Write | ForwardOptions.WriteLine :
|
[MemberData("ForwardingTheoryVariations")]
|
||||||
ForwardOptions.WriteLine;
|
public void TestCaptureAndForwardingTogether(string inputStr, string[] expectedWrites)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < expectedWrites.Length; ++i)
|
||||||
|
{
|
||||||
|
expectedWrites[i] += Environment.NewLine;
|
||||||
|
}
|
||||||
|
|
||||||
// Forward.
|
var expectedCaptured = string.Join("", expectedWrites);
|
||||||
Forward(bufferSize, writeOptions, str, null, expectedWrites);
|
|
||||||
|
|
||||||
// Forward and capture.
|
TestCapturingAndForwardingHelper(ForwardOptions.WriteLine | ForwardOptions.Capture, inputStr, expectedCaptured, expectedWrites);
|
||||||
Forward(bufferSize, writeOptions | ForwardOptions.Capture, str, expectedCaptured, expectedWrites);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum ForwardOptions
|
private enum ForwardOptions
|
||||||
{
|
{
|
||||||
None = 0x0,
|
None = 0x0,
|
||||||
Capture = 0x1,
|
Capture = 0x1,
|
||||||
Write = 0x02,
|
WriteLine = 0x02,
|
||||||
WriteLine = 0x04,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Forward(int bufferSize, ForwardOptions options, string str, string expectedCaptured, string[] expectedWrites)
|
private void TestCapturingAndForwardingHelper(ForwardOptions options, string str, string expectedCaptured, string[] expectedWrites)
|
||||||
{
|
{
|
||||||
var forwarder = new StreamForwarder(bufferSize);
|
var forwarder = new StreamForwarder();
|
||||||
var writes = new List<string>();
|
var writes = new List<string>();
|
||||||
|
|
||||||
if ((options & ForwardOptions.WriteLine) != 0)
|
if ((options & ForwardOptions.WriteLine) != 0)
|
||||||
{
|
{
|
||||||
forwarder.ForwardTo(
|
forwarder.ForwardTo(writeLine: s => writes.Add(s + Environment.NewLine));
|
||||||
write: (options & ForwardOptions.Write) == 0 ? (Action<string>)null : writes.Add,
|
|
||||||
writeLine: s => writes.Add(s + "\n"));
|
|
||||||
}
|
}
|
||||||
if ((options & ForwardOptions.Capture) != 0)
|
if ((options & ForwardOptions.Capture) != 0)
|
||||||
{
|
{
|
||||||
forwarder.Capture();
|
forwarder.Capture();
|
||||||
}
|
}
|
||||||
|
|
||||||
forwarder.Read(new StringReader(str));
|
forwarder.Read(new StringReader(str));
|
||||||
Assert.Equal(expectedWrites, writes);
|
Assert.Equal(expectedWrites, writes);
|
||||||
var captured = forwarder.GetCapturedOutput();
|
|
||||||
|
var captured = forwarder.CapturedOutput;
|
||||||
Assert.Equal(expectedCaptured, captured);
|
Assert.Equal(expectedCaptured, captured);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestAsyncOrdering()
|
||||||
|
{
|
||||||
|
var expectedOutputLines = new string[]
|
||||||
|
{
|
||||||
|
"** Standard Out 1 **",
|
||||||
|
"** Standard Error 1 **",
|
||||||
|
"** Standard Out 2 **",
|
||||||
|
"** Standard Error 2 **"
|
||||||
|
};
|
||||||
|
|
||||||
|
var expectedOutput = string.Join(Environment.NewLine, expectedOutputLines) + Environment.NewLine;
|
||||||
|
|
||||||
|
var testProjectDllPath = SetupTestProject();
|
||||||
|
|
||||||
|
var testReporter = new TestReporter();
|
||||||
|
|
||||||
|
var testCommand = Command.Create(testProjectDllPath, new string[0])
|
||||||
|
.OnOutputLine(testReporter.WriteLine)
|
||||||
|
.OnErrorLine(testReporter.WriteLine);
|
||||||
|
|
||||||
|
testCommand.Execute();
|
||||||
|
|
||||||
|
var resultString = testReporter.InternalStringWriter.GetStringBuilder().ToString();
|
||||||
|
Console.WriteLine(expectedOutput);
|
||||||
|
Console.WriteLine(resultString);
|
||||||
|
|
||||||
|
Assert.Equal(expectedOutput, resultString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string SetupTestProject()
|
||||||
|
{
|
||||||
|
var sourceTestProjectPath = Path.Combine(s_testProjectRoot, "OutputStandardOutputAndError");
|
||||||
|
|
||||||
|
var binTestProjectPath = _root.CopyDirectory(sourceTestProjectPath).Path;
|
||||||
|
|
||||||
|
var buildCommand = new BuildCommand(Path.Combine(binTestProjectPath, "project.json"));
|
||||||
|
buildCommand.Execute();
|
||||||
|
|
||||||
|
var buildOutputExe = "OutputStandardOutputAndError" + Constants.ExeSuffix;
|
||||||
|
var buildOutputPath = Path.Combine(binTestProjectPath, "bin/Debug/dnxcore50", buildOutputExe);
|
||||||
|
|
||||||
|
return buildOutputPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestReporter
|
||||||
|
{
|
||||||
|
private static object _lock = new object();
|
||||||
|
private StringWriter _stringWriter;
|
||||||
|
|
||||||
|
public StringWriter InternalStringWriter
|
||||||
|
{
|
||||||
|
get { return _stringWriter; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestReporter()
|
||||||
|
{
|
||||||
|
_stringWriter = new StringWriter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteLine(string message)
|
||||||
|
{
|
||||||
|
lock(_lock)
|
||||||
|
{
|
||||||
|
_stringWriter.WriteLine(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||||
|
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<ProjectGuid>58808bbc-371e-47d6-a3d0-4902145eda4e</ProjectGuid>
|
||||||
|
<RootNamespace>OutputStandardOutputAndError</RootNamespace>
|
||||||
|
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||||
|
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
</Project>
|
19
test/TestProjects/OutputStandardOutputAndError/Program.cs
Normal file
19
test/TestProjects/OutputStandardOutputAndError/Program.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace TestApp
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
Console.Out.WriteLine("** Standard Out 1 **");
|
||||||
|
Console.Error.WriteLine("** Standard Error 1 **");
|
||||||
|
Console.Out.WriteLine("** Standard Out 2 **");
|
||||||
|
Console.Error.WriteLine("** Standard Error 2 **");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
test/TestProjects/OutputStandardOutputAndError/project.json
Normal file
14
test/TestProjects/OutputStandardOutputAndError/project.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"version": "1.0.0-*",
|
||||||
|
"compilationOptions": {
|
||||||
|
"emitEntryPoint": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"dependencies": {
|
||||||
|
"NETStandard.Library": "1.0.0-rc2-23728"
|
||||||
|
},
|
||||||
|
|
||||||
|
"frameworks": {
|
||||||
|
"dnxcore50": { }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue