diff --git a/src/Common/src/Interop/Unix/System.Native/Interop.GetWindowWidth.cs b/src/Common/src/Interop/Unix/System.Native/Interop.GetWindowWidth.cs new file mode 100644 index 000000000000..be8e198072fc --- /dev/null +++ b/src/Common/src/Interop/Unix/System.Native/Interop.GetWindowWidth.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + [StructLayout(LayoutKind.Sequential)] + private struct WinSize + { + internal ushort Row; + internal ushort Col; + internal ushort XPixel; + internal ushort YPixel; + }; + + [DllImport(Libraries.SystemNative, SetLastError = true)] + private static extern int GetWindowSize(out WinSize winSize); + + internal static int GetWindowWidth() + { + WinSize winsize; + + if (GetWindowSize(out winsize) == 0) + { + return winsize.Col; + } + + return -1; + } + } +} diff --git a/src/Common/src/Interop/Windows/mincore/Interop.ConsoleCursorInfo.cs b/src/Common/src/Interop/Windows/mincore/Interop.ConsoleCursorInfo.cs new file mode 100644 index 000000000000..eae988fa8949 --- /dev/null +++ b/src/Common/src/Interop/Windows/mincore/Interop.ConsoleCursorInfo.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class mincore + { + [StructLayoutAttribute(LayoutKind.Sequential)] + internal struct CONSOLE_CURSOR_INFO + { + internal int dwSize; + internal bool bVisible; + } + + [DllImport(Libraries.Console_L2, SetLastError = true)] + internal static extern bool GetConsoleCursorInfo(IntPtr hConsoleOutput, out CONSOLE_CURSOR_INFO cci); + + [DllImport(Libraries.Console_L2, SetLastError = true)] + internal static extern bool SetConsoleCursorInfo(IntPtr hConsoleOutput, ref CONSOLE_CURSOR_INFO cci); + } +} diff --git a/src/Common/src/Interop/Windows/mincore/Interop.GetLargestConsoleWindowSize.cs b/src/Common/src/Interop/Windows/mincore/Interop.GetLargestConsoleWindowSize.cs new file mode 100644 index 000000000000..79ed0b9dcbc8 --- /dev/null +++ b/src/Common/src/Interop/Windows/mincore/Interop.GetLargestConsoleWindowSize.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class mincore + { + [DllImport(Libraries.Console_L2, SetLastError = true)] + internal static extern Interop.mincore.COORD GetLargestConsoleWindowSize(IntPtr hConsoleOutput); + } +} diff --git a/src/Common/src/Interop/Windows/mincore/Interop.SetConsoleScreenBufferSize.cs b/src/Common/src/Interop/Windows/mincore/Interop.SetConsoleScreenBufferSize.cs new file mode 100644 index 000000000000..6a80b9dfbf89 --- /dev/null +++ b/src/Common/src/Interop/Windows/mincore/Interop.SetConsoleScreenBufferSize.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class mincore + { + [DllImport(Libraries.Console_L2, SetLastError = true)] + internal static extern bool SetConsoleScreenBufferSize(IntPtr hConsoleOutput, Interop.mincore.COORD size); + } +} diff --git a/src/Common/src/Interop/Windows/mincore/Interop.SetConsoleWindowInfo.cs b/src/Common/src/Interop/Windows/mincore/Interop.SetConsoleWindowInfo.cs new file mode 100644 index 000000000000..a2325ef2d66e --- /dev/null +++ b/src/Common/src/Interop/Windows/mincore/Interop.SetConsoleWindowInfo.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class mincore + { + [DllImport(Libraries.Console_L2, SetLastError = true)] + internal static unsafe extern bool SetConsoleWindowInfo(IntPtr hConsoleOutput, bool absolute, SMALL_RECT* consoleWindow); + } +} diff --git a/src/Common/tests/System/IO/InterceptStreamWriter.cs b/src/Common/tests/System/IO/InterceptStreamWriter.cs new file mode 100644 index 000000000000..7e1d071a9922 --- /dev/null +++ b/src/Common/tests/System/IO/InterceptStreamWriter.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; +using System.Text; + +internal sealed class InterceptStreamWriter : StreamWriter +{ + private readonly StreamWriter _wrappedWriter; + + public InterceptStreamWriter(Stream baseStream, StreamWriter wrappedWriter, Encoding encoding, int bufferSize, bool leaveOpen) : + base(baseStream, encoding, bufferSize, leaveOpen) + { + _wrappedWriter = wrappedWriter; + } + + public override void Write(string value) + { + base.Write(value); + _wrappedWriter.Write(value); + } + + public override void Write(char value) + { + base.Write(value); + _wrappedWriter.Write(value); + } +} \ No newline at end of file diff --git a/src/Native/Common/pal_config.h.in b/src/Native/Common/pal_config.h.in index e4f194fc7b76..925b9a39a8e3 100644 --- a/src/Native/Common/pal_config.h.in +++ b/src/Native/Common/pal_config.h.in @@ -13,6 +13,8 @@ #cmakedefine01 HAVE_POSIX_ADVISE #cmakedefine01 PRIORITY_REQUIRES_INT_WHO #cmakedefine01 HAVE_IN6_U +#cmakedefine01 HAVE_IOCTL +#cmakedefine01 HAVE_TIOCGWINSZ // Mac OS X has stat64, but it is deprecated since plain stat now // provides the same 64-bit aware struct when targeting OS X > 10.5 diff --git a/src/Native/System.Native/pal_io.cpp b/src/Native/System.Native/pal_io.cpp index f64ce6730d81..c8ea19eeba28 100644 --- a/src/Native/System.Native/pal_io.cpp +++ b/src/Native/System.Native/pal_io.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -793,3 +794,22 @@ extern "C" int32_t Write(int32_t fd, const void* buffer, int32_t bufferSize) assert(count >= -1 && count <= bufferSize); return static_cast(count); } + +extern "C" int32_t GetWindowSize(WinSize* windowSize) +{ + assert(windowSize != nullptr); + +#if HAVE_IOCTL && HAVE_TIOCGWINSZ + int error = ioctl(STDOUT_FILENO, TIOCGWINSZ, windowSize); + + if (error != 0) + { + *windowSize = {}; // managed out param must be initialized + } + + return error; +#else + errno = ENOTSUP; + return -1; +#endif +} diff --git a/src/Native/System.Native/pal_io.h b/src/Native/System.Native/pal_io.h index c8d6c0e679dc..8b483c30fea9 100644 --- a/src/Native/System.Native/pal_io.h +++ b/src/Native/System.Native/pal_io.h @@ -22,6 +22,17 @@ struct FileStatus int64_t BirthTime; // time the file was created }; +/* + * Window Size of the terminal +*/ +struct WinSize +{ + uint16_t Row; + uint16_t Col; + uint16_t XPixel; + uint16_t YPixel; +}; + /************ * The values below in the header are fixed and correct for managed callers to use forever. * We must never change them. The implementation must either static_assert that they are equal @@ -610,3 +621,11 @@ extern "C" void Sync(); * Returns the number of bytes written on success; otherwise, returns -1 and sets errno */ extern "C" int32_t Write(int32_t fd, const void* buffer, int32_t bufferSize); + + +/** +* Gets the windows size of the terminal +* +* Returns 0 on success; otherwise, returns errorNo. +*/ +extern "C" int32_t GetWindowSize(WinSize* windowsSize); diff --git a/src/Native/configure.cmake b/src/Native/configure.cmake index 0e3a534112b1..eec5337d5d52 100644 --- a/src/Native/configure.cmake +++ b/src/Native/configure.cmake @@ -3,6 +3,7 @@ include(CheckStructHasMember) include(CheckCXXSourceCompiles) include(CheckCXXSourceRuns) include(CheckPrototypeDefinition) +include(CheckSymbolExists) #CMake does not include /usr/local/include into the include search path #thus add it manually. This is required on FreeBSD. @@ -32,6 +33,15 @@ check_function_exists( posix_fadvise HAVE_POSIX_ADVISE) +check_function_exists( + ioctl + HAVE_IOCTL) + +check_symbol_exists( + TIOCGWINSZ + "sys/ioctl.h" + HAVE_TIOCGWINSZ) + check_struct_has_member( "struct stat" st_birthtime diff --git a/src/System.Console/ref/System.Console.cs b/src/System.Console/ref/System.Console.cs index 7084c06a8216..4e26ceb8026d 100644 --- a/src/System.Console/ref/System.Console.cs +++ b/src/System.Console/ref/System.Console.cs @@ -10,6 +10,7 @@ namespace System public static partial class Console { public static System.ConsoleColor BackgroundColor { get { return default(System.ConsoleColor); } set { } } + public static bool CursorVisible { get { return default(bool); } set { } } public static System.IO.TextWriter Error { get { return default(System.IO.TextWriter); } } public static System.ConsoleColor ForegroundColor { get { return default(System.ConsoleColor); } set { } } public static bool IsInputRedirected { get { return false; } } @@ -27,6 +28,7 @@ public static void ResetColor() { } public static void SetError(System.IO.TextWriter newError) { } public static void SetIn(System.IO.TextReader newIn) { } public static void SetOut(System.IO.TextWriter newOut) { } + public static int WindowWidth { get { return default(int); } set { } } public static void Write(bool value) { } public static void Write(char value) { } public static void Write(char[] buffer) { } diff --git a/src/System.Console/src/Resources/Strings.resx b/src/System.Console/src/Resources/Strings.resx index 94233e7ab95c..bfa74617488c 100644 --- a/src/System.Console/src/Resources/Strings.resx +++ b/src/System.Console/src/Resources/Strings.resx @@ -117,9 +117,18 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + The value must be less than the console's current maximum window size of {0} in that dimension. Note that this value depends on screen resolution and the console font. + + + The new console window size would force the console buffer size to be too large. + Non-negative number required. + + Positive number required. + Buffer cannot be null. diff --git a/src/System.Console/src/System.Console.csproj b/src/System.Console/src/System.Console.csproj index 44ea27f62936..011a16cafd50 100644 --- a/src/System.Console/src/System.Console.csproj +++ b/src/System.Console/src/System.Console.csproj @@ -40,6 +40,9 @@ Common\Interop\Windows\Interop.FormatMessage.cs + + + Common\Interop\Windows\Interop.ConsoleCursorInfo.cs Common\Interop\Windows\Interop.ConsoleScreenBufferInfo.cs @@ -62,6 +65,9 @@ Common\Interop\Windows\Interop.GetConsoleOutputCP.cs + + Common\Interop\Windows\Interop.GetLargestConsoleWindowSize.cs + Common\Interop\Windows\Interop.GetFileType.cs @@ -77,12 +83,18 @@ Common\Interop\Windows\Interop.ReadConsole.cs - + Common\Interop\Windows\Interop.SetConsoleCtrlHandler.cs + + Common\Interop\Windows\Interop.SetConsoleScreenBufferSize.cs + Common\Interop\Windows\Interop.SetConsoleTextAttribute.cs + + Common\Interop\Windows\Interop.SetConsoleWindowInfo.cs + Common\Interop\Windows\Interop.WriteFile.cs @@ -161,6 +173,9 @@ Common\Interop\Unix\Interop.SetConsoleCtrlHandler.cs" + + Common\Interop\Unix\Interop.GetWindowWidth.cs" + diff --git a/src/System.Console/src/System/Console.cs b/src/System.Console/src/System/Console.cs index 6ff032028c19..6034f70af610 100644 --- a/src/System.Console/src/System/Console.cs +++ b/src/System.Console/src/System/Console.cs @@ -124,6 +124,30 @@ public static void ResetColor() ConsolePal.ResetColor(); } + public static int WindowWidth + { + get + { + return ConsolePal.WindowWidth; + } + set + { + ConsolePal.WindowWidth = value; + } + } + + public static bool CursorVisible + { + get + { + return ConsolePal.CursorVisible; + } + set + { + ConsolePal.CursorVisible = value; + } + } + public static event ConsoleCancelEventHandler CancelKeyPress { add diff --git a/src/System.Console/src/System/ConsolePal.Unix.cs b/src/System.Console/src/System/ConsolePal.Unix.cs index bdf974b347c3..7eda87f361ce 100644 --- a/src/System.Console/src/System/ConsolePal.Unix.cs +++ b/src/System.Console/src/System/ConsolePal.Unix.cs @@ -74,8 +74,36 @@ public static void ResetColor() } } - /// - /// Gets whether Console.Out is redirected. + public static int WindowWidth + { + get + { + int cols = Interop.Sys.GetWindowWidth(); + return (cols != -1) ? cols : TerminalBasicInfo.Instance.ColumnFormat; + } + set + { + throw new PlatformNotSupportedException(); + } + } + public static bool CursorVisible + { + get + { + throw new PlatformNotSupportedException(); + } + set + { + if (IsConsoleOutRedirected) return; + string formatString = value ? + TerminalBasicInfo.Instance.CursorVisibleFormat : + TerminalBasicInfo.Instance.CursorInvisibleFormat; + if (!string.IsNullOrEmpty(formatString)) + { + Console.Write(formatString); + } + } + } /// By default we assume that the stream is redirected /// unless it is a character device. /// @@ -309,6 +337,33 @@ private TerminalColorInfo(TermInfo.Database db) }, isThreadSafe: true); } + private struct TerminalBasicInfo + { + /// The no. of columns in a format + public int ColumnFormat; + /// The format string to use to make cursor visible. + public string CursorVisibleFormat; + /// The format string to use to make cursor invisible + public string CursorInvisibleFormat; + + /// The cached instance. + public static TerminalBasicInfo Instance { get { return _instance.Value; } } + + private TerminalBasicInfo(TermInfo.Database db) + { + ColumnFormat = db != null ? db.GetNumber(TermInfo.Database.ColumnIndex) : 0; + CursorVisibleFormat = db != null ? db.GetString(TermInfo.Database.CursorVisibleIndex) : string.Empty; + CursorInvisibleFormat = db != null ? db.GetString(TermInfo.Database.CursorInvisibleIndex) : string.Empty; + } + + /// Lazy initialization of the terminal basic information. + private static Lazy _instance = new Lazy(() => + { + TermInfo.Database db = TermInfo.Database.Instance; // Could be null if TERM is set to a file that doesn't exist + return new TerminalBasicInfo(db); + }, isThreadSafe: true); + } + /// Reads data from the file descriptor into the buffer. /// The file descriptor. /// The buffer to read into. @@ -677,6 +732,14 @@ public int GetNumber(int numberIndex) /// The well-known index of the set_a_background string entry. public const int SetAnsiBackgroundIndex = 360; + /// The well-known index of the columns numeric entry. + public const int ColumnIndex = 0; + /// The well-known index of the cursor_invisible string entry. + public const int CursorInvisibleIndex = 13; + /// The well-known index of the cursor_normal string entry. + public const int CursorVisibleIndex = 16; + + /// Read a 16-bit value from the buffer starting at the specified position. /// The buffer from which to read. /// The position at which to read. diff --git a/src/System.Console/src/System/ConsolePal.Windows.cs b/src/System.Console/src/System/ConsolePal.Windows.cs index b87af19a6790..6dac77b50b44 100644 --- a/src/System.Console/src/System/ConsolePal.Windows.cs +++ b/src/System.Console/src/System/ConsolePal.Windows.cs @@ -224,6 +224,117 @@ public static void ResetColor() Interop.mincore.SetConsoleTextAttribute(OutputHandle, (short)(ushort)_defaultColors); } + public static bool CursorVisible + { + get + { + Interop.mincore.CONSOLE_CURSOR_INFO cci; + if (!Interop.mincore.GetConsoleCursorInfo(OutputHandle, out cci)) + throw Win32Marshal.GetExceptionForWin32Error(Marshal.GetLastWin32Error()); + + return cci.bVisible; + } + set + { + Interop.mincore.CONSOLE_CURSOR_INFO cci; + if (!Interop.mincore.GetConsoleCursorInfo(OutputHandle, out cci)) + throw Win32Marshal.GetExceptionForWin32Error(Marshal.GetLastWin32Error()); + + cci.bVisible = value; + if (!Interop.mincore.SetConsoleCursorInfo(OutputHandle, ref cci)) + throw Win32Marshal.GetExceptionForWin32Error(Marshal.GetLastWin32Error()); + } + } + + public static int WindowWidth + { + get + { + Interop.mincore.CONSOLE_SCREEN_BUFFER_INFO csbi = GetBufferInfo(); + return csbi.srWindow.Right - csbi.srWindow.Left + 1; + } + set + { + SetWindowSize(value, WindowHeight); + } + } + + private static int WindowHeight + { + get + { + Interop.mincore.CONSOLE_SCREEN_BUFFER_INFO csbi = GetBufferInfo(); + return csbi.srWindow.Bottom - csbi.srWindow.Top + 1; + } + set + { + SetWindowSize(WindowWidth, value); + } + } + + public static unsafe void SetWindowSize(int width, int height) + { + if (width <= 0) + throw new ArgumentOutOfRangeException("width", width, SR.ArgumentOutOfRange_NeedPosNum); + if (height <= 0) + throw new ArgumentOutOfRangeException("height", height, SR.ArgumentOutOfRange_NeedPosNum); + + // Get the position of the current console window + Interop.mincore.CONSOLE_SCREEN_BUFFER_INFO csbi = GetBufferInfo(); + + // If the buffer is smaller than this new window size, resize the + // buffer to be large enough. Include window position. + bool resizeBuffer = false; + Interop.mincore.COORD size = new Interop.mincore.COORD(); + size.X = csbi.dwSize.X; + size.Y = csbi.dwSize.Y; + if (csbi.dwSize.X < csbi.srWindow.Left + width) + { + if (csbi.srWindow.Left >= Int16.MaxValue - width) + throw new ArgumentOutOfRangeException("width", SR.ArgumentOutOfRange_ConsoleWindowBufferSize); + size.X = (short)(csbi.srWindow.Left + width); + resizeBuffer = true; + } + if (csbi.dwSize.Y < csbi.srWindow.Top + height) + { + if (csbi.srWindow.Top >= Int16.MaxValue - height) + throw new ArgumentOutOfRangeException("height", SR.ArgumentOutOfRange_ConsoleWindowBufferSize); + size.Y = (short)(csbi.srWindow.Top + height); + resizeBuffer = true; + } + if (resizeBuffer) + { + if (!Interop.mincore.SetConsoleScreenBufferSize(OutputHandle, size)) + throw Win32Marshal.GetExceptionForWin32Error(Marshal.GetLastWin32Error()); + } + + Interop.mincore.SMALL_RECT srWindow = csbi.srWindow; + // Preserve the position, but change the size. + srWindow.Bottom = (short)(srWindow.Top + height - 1); + srWindow.Right = (short)(srWindow.Left + width - 1); + + if (!Interop.mincore.SetConsoleWindowInfo(OutputHandle, true, &srWindow)) + { + int errorCode = Marshal.GetLastWin32Error(); + + // If we resized the buffer, un-resize it. + if (resizeBuffer) + { + Interop.mincore.SetConsoleScreenBufferSize(OutputHandle, csbi.dwSize); + } + + // Try to give a better error message here + Interop.mincore.COORD bounds = Interop.mincore.GetLargestConsoleWindowSize(OutputHandle); + if (width > bounds.X) + throw new ArgumentOutOfRangeException("width", width, SR.Format(SR.ArgumentOutOfRange_ConsoleWindowSize_Size, bounds.X)); + if (height > bounds.Y) + throw new ArgumentOutOfRangeException("height", height, SR.Format(SR.ArgumentOutOfRange_ConsoleWindowSize_Size, bounds.Y)); + + throw Win32Marshal.GetExceptionForWin32Error(errorCode); + } + } + + private static Interop.mincore.Color ConsoleColorToColorAttribute(ConsoleColor color, bool isBackground) { if ((((int)color) & ~0xf) != 0) @@ -248,6 +359,12 @@ private static ConsoleColor ColorAttributeToConsoleColor(Interop.mincore.Color c return (ConsoleColor)c; } + private static Interop.mincore.CONSOLE_SCREEN_BUFFER_INFO GetBufferInfo() + { + bool unused; + return GetBufferInfo(true, out unused); + } + // For apps that don't have a console (like Windows apps), they might // run other code that includes color console output. Allow a mechanism // where that code won't throw an exception for simple errors. diff --git a/src/System.Console/tests/Color.cs b/src/System.Console/tests/Color.cs index e2b357aebc8a..476e2af78d28 100644 --- a/src/System.Console/tests/Color.cs +++ b/src/System.Console/tests/Color.cs @@ -41,11 +41,9 @@ public static void RoundtrippingColor() public static void RedirectedOutputDoesNotUseAnsiSequences() { // Make sure that redirecting to a memory stream causes Console not to write out the ANSI sequences - MemoryStream data = new MemoryStream(); - TextWriter savedOut = Console.Out; - try + + Helpers.RunInRedirectedOutput((data) => { - Console.SetOut(new StreamWriter(data, new UTF8Encoding(false), 0x1000, leaveOpen: true) { AutoFlush = true }); Console.Write('1'); Console.ForegroundColor = ConsoleColor.Blue; Console.Write('2'); @@ -53,15 +51,11 @@ public static void RedirectedOutputDoesNotUseAnsiSequences() Console.Write('3'); Console.ResetColor(); Console.Write('4'); - } - finally - { - Console.SetOut(savedOut); - } - const char Esc = (char)0x1B; - Assert.Equal(0, Encoding.UTF8.GetString(data.ToArray()).ToCharArray().Count(c => c == Esc)); - Assert.Equal("1234", Encoding.UTF8.GetString(data.ToArray())); + const char Esc = (char)0x1B; + Assert.Equal(0, Encoding.UTF8.GetString(data.ToArray()).ToCharArray().Count(c => c == Esc)); + Assert.Equal("1234", Encoding.UTF8.GetString(data.ToArray())); + }); } //[Fact] // the CI system redirects stdout, so we can't easily test non-redirected behavior @@ -70,49 +64,13 @@ public static void NonRedirectedOutputDoesUseAnsiSequences() { // Make sure that when writing out to a UnixConsoleStream, the ANSI escape sequences are properly // written out. - MemoryStream data = new MemoryStream(); - TextWriter savedOut = Console.Out; - try + Helpers.RunInNonRedirectedOutput((data) => { - Console.SetOut( - new InterceptStreamWriter( - Console.OpenStandardOutput(), - new StreamWriter(data, new UTF8Encoding(false), 0x1000, leaveOpen: true) { AutoFlush = true }, - new UTF8Encoding(false), 0x1000, leaveOpen: true) { AutoFlush = true }); Console.ForegroundColor = ConsoleColor.Blue; Console.BackgroundColor = ConsoleColor.Red; Console.ResetColor(); - } - finally - { - Console.SetOut(savedOut); - } - - const char Esc = (char)0x1B; - Assert.Equal(3, Encoding.UTF8.GetString(data.ToArray()).ToCharArray().Count(c => c == Esc)); + const char Esc = (char)0x1B; + Assert.Equal(3, Encoding.UTF8.GetString(data.ToArray()).ToCharArray().Count(c => c == Esc)); + }); } - - private sealed class InterceptStreamWriter : StreamWriter - { - private readonly StreamWriter _wrappedWriter; - - public InterceptStreamWriter(Stream baseStream, StreamWriter wrappedWriter, Encoding encoding, int bufferSize, bool leaveOpen) : - base(baseStream, encoding, bufferSize, leaveOpen) - { - _wrappedWriter = wrappedWriter; - } - - public override void Write(string value) - { - base.Write(value); - _wrappedWriter.Write(value); - } - - public override void Write(char value) - { - base.Write(value); - _wrappedWriter.Write(value); - } - } - } diff --git a/src/System.Console/tests/Helpers.cs b/src/System.Console/tests/Helpers.cs index d9c554a11312..829fa1dc549b 100644 --- a/src/System.Console/tests/Helpers.cs +++ b/src/System.Console/tests/Helpers.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Text; using Xunit; class Helpers @@ -45,4 +46,42 @@ public static void SetAndReadHelper(Action setHelper, Func command) + { + // Make sure that redirecting to a memory stream causes no special writing to the stream when using Console.CursorVisible + MemoryStream data = new MemoryStream(); + TextWriter savedOut = Console.Out; + try + { + Console.SetOut(new StreamWriter(data, new UTF8Encoding(false), 0x1000, leaveOpen: true) { AutoFlush = true }); + command(data); + } + finally + { + Console.SetOut(savedOut); + } + } + + public static void RunInNonRedirectedOutput(Action command) + { + // Make sure that when writing out to a UnixConsoleStream + // written out. + MemoryStream data = new MemoryStream(); + TextWriter savedOut = Console.Out; + try + { + Console.SetOut( + new InterceptStreamWriter( + Console.OpenStandardOutput(), + new StreamWriter(data, new UTF8Encoding(false), 0x1000, leaveOpen: true) { AutoFlush = true }, + new UTF8Encoding(false), 0x1000, leaveOpen: true) + { AutoFlush = true }); + command(data); + } + finally + { + Console.SetOut(savedOut); + } + } } diff --git a/src/System.Console/tests/System.Console.Tests.csproj b/src/System.Console/tests/System.Console.Tests.csproj index d972292b2b47..c9323377ecbe 100644 --- a/src/System.Console/tests/System.Console.Tests.csproj +++ b/src/System.Console/tests/System.Console.Tests.csproj @@ -34,8 +34,11 @@ + + Common\System\IO\InterceptStreamWriter.cs + - Common\tests\System\Diagnostics\AssertWithCallerAttributes.cs + Common\System\Diagnostics\AssertWithCallerAttributes.cs Common\System\Diagnostics\RemoteExecutorTestBase.cs @@ -46,6 +49,7 @@ Common\System\ShouldNotBeInvokedException.cs + diff --git a/src/System.Console/tests/WindowAndCursorProps.cs b/src/System.Console/tests/WindowAndCursorProps.cs new file mode 100644 index 000000000000..28fe5f6b2fc3 --- /dev/null +++ b/src/System.Console/tests/WindowAndCursorProps.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using Xunit; + +public class WindowAndCursorProps +{ + [Fact] + [PlatformSpecific(PlatformID.AnyUnix)] + public static void WindowWidth() + { + Assert.Throws(() => Console.WindowWidth = 100); + + // Validate that Console.WindowWidth returns some value in a non-redirected o/p. + Helpers.RunInNonRedirectedOutput((data) => Console.WriteLine(Console.WindowWidth)); + Helpers.RunInRedirectedOutput((data) => Console.WriteLine(Console.WindowWidth)); + } + + //[Fact] //CI system makes it difficult to run things in a non-redirected environments. + [PlatformSpecific(PlatformID.AnyUnix)] + public static void NonRedirectedCursorVisible() + { + // Validate that Console.CursorVisible adds something to the stream when in a non-redirected environment. + Helpers.RunInNonRedirectedOutput((data) => { Console.CursorVisible = true; Assert.True(data.ToArray().Length > 0); }); + Helpers.RunInNonRedirectedOutput((data) => { Console.CursorVisible = false; Assert.True(data.ToArray().Length > 0); }); + } + + [Fact] + [PlatformSpecific(PlatformID.AnyUnix)] + public static void CursorVisible() + { + Assert.Throws(() => { bool unused = Console.CursorVisible; }); + + // Validate that the Console.CursorVisible does nothing in a redirected stream. + Helpers.RunInRedirectedOutput((data) => { Console.CursorVisible = true; Assert.Equal(0, data.ToArray().Length); }); + Helpers.RunInRedirectedOutput((data) => { Console.CursorVisible = false; Assert.Equal(0, data.ToArray().Length); }); + } +} diff --git a/src/System.Linq.Expressions/tests/System.Linq.Expressions.Tests.csproj b/src/System.Linq.Expressions/tests/System.Linq.Expressions.Tests.csproj index a9fcbd2feea4..a0f6661df952 100644 --- a/src/System.Linq.Expressions/tests/System.Linq.Expressions.Tests.csproj +++ b/src/System.Linq.Expressions/tests/System.Linq.Expressions.Tests.csproj @@ -9,9 +9,7 @@ System.Linq.Expressions.Tests System.Linq.Expressions.Tests true - OSX - - + OSX diff --git a/src/System.Net.Http.WinHttpHandler/tests/UnitTests/ClientCertificateHelper.cs b/src/System.Net.Http.WinHttpHandler/tests/UnitTests/ClientCertificateHelper.cs index e28620c79f5f..211277c39dbc 100644 --- a/src/System.Net.Http.WinHttpHandler/tests/UnitTests/ClientCertificateHelper.cs +++ b/src/System.Net.Http.WinHttpHandler/tests/UnitTests/ClientCertificateHelper.cs @@ -255,6 +255,18 @@ public object[][] ValidClientCertificates } } + public X509Certificate2Collection ValidClientCertificateCollection + { + get + { + X509Certificate2Collection certs = new X509Certificate2Collection(); + certs.Add(_cert_KeyUsageIncludesDigitalSignature_EKUIncludesClientAuth_PrivateKey); + certs.Add(_cert_KeyUsageIncludesDigitalSignature_NoEKU_PrivateKey); + + return certs; + } + } + public object[][] InvalidClientCertificates { get @@ -266,6 +278,33 @@ public object[][] InvalidClientCertificates new object[] { _cert_KeyUsageIncludesDigitalSignature_EKUMissingClientAuth_PrivateKey } }; } - } + } + + public X509Certificate2Collection InvalidClientCertificateCollection + { + get + { + X509Certificate2Collection certs = new X509Certificate2Collection(); + certs.Add(_cert_KeyUsageIncludesDigitalSignature_EKUIncludesClientAuth_NoPrivateKey); + certs.Add(_cert_KeyUsageMissingDigitalSignature_EKUIncludesClientAuth_PrivateKey); + certs.Add(_cert_KeyUsageIncludesDigitalSignature_EKUMissingClientAuth_PrivateKey); + + return certs; + } + } + + public X509Certificate2Collection ValidAndInvalidClientCertificateCollection + { + get + { + X509Certificate2Collection certs = new X509Certificate2Collection(); + certs.Add(_cert_KeyUsageIncludesDigitalSignature_EKUIncludesClientAuth_NoPrivateKey); + certs.Add(_cert_KeyUsageMissingDigitalSignature_EKUIncludesClientAuth_PrivateKey); + certs.Add(_cert_KeyUsageIncludesDigitalSignature_EKUIncludesClientAuth_PrivateKey); + certs.Add(_cert_KeyUsageIncludesDigitalSignature_EKUMissingClientAuth_PrivateKey); + + return certs; + } + } } } diff --git a/src/System.Net.Http.WinHttpHandler/tests/UnitTests/ClientCertificateScenarioTest.cs b/src/System.Net.Http.WinHttpHandler/tests/UnitTests/ClientCertificateScenarioTest.cs index ed498e535af2..dc9f12199766 100644 --- a/src/System.Net.Http.WinHttpHandler/tests/UnitTests/ClientCertificateScenarioTest.cs +++ b/src/System.Net.Http.WinHttpHandler/tests/UnitTests/ClientCertificateScenarioTest.cs @@ -112,6 +112,81 @@ public void SecureRequest_AddInvalidCertificate_NullCertificateContextSet(X509Ce Assert.Equal(IntPtr.Zero, APICallHistory.WinHttpOptionClientCertContext[0]); } } + } + + [Fact] + public void SecureRequest_ClientCertificateOptionAutomatic_CertStoreEmpty_NullCertificateContextSet() + { + using (var handler = new WinHttpHandler()) + { + handler.ClientCertificateOption = ClientCertificateOption.Automatic; + using (HttpResponseMessage response = SendRequestHelper.Send( + handler, + () => { }, + TestServer.FakeSecureServerEndpoint)) + { + Assert.Equal(1, APICallHistory.WinHttpOptionClientCertContext.Count); + Assert.Equal(IntPtr.Zero, APICallHistory.WinHttpOptionClientCertContext[0]); + } + } + } + + [Fact] + public void SecureRequest_ClientCertificateOptionAutomatic_CertStoreHasInvalidCerts_NullCertificateContextSet() + { + using (var handler = new WinHttpHandler()) + { + var helper = new ClientCertificateHelper(); + TestControl.CurrentUserCertificateStore = helper.InvalidClientCertificateCollection; + handler.ClientCertificateOption = ClientCertificateOption.Automatic; + using (HttpResponseMessage response = SendRequestHelper.Send( + handler, + () => { }, + TestServer.FakeSecureServerEndpoint)) + { + Assert.Equal(1, APICallHistory.WinHttpOptionClientCertContext.Count); + Assert.Equal(IntPtr.Zero, APICallHistory.WinHttpOptionClientCertContext[0]); + } + } + } + + [Fact] + public void SecureRequest_ClientCertificateOptionAutomatic_CertStoreHasValidCerts_ValidCertificateContextSet() + { + using (var handler = new WinHttpHandler()) + { + var helper = new ClientCertificateHelper(); + TestControl.CurrentUserCertificateStore = helper.ValidClientCertificateCollection; + handler.ClientCertificateOption = ClientCertificateOption.Automatic; + using (HttpResponseMessage response = SendRequestHelper.Send( + handler, + () => { }, + TestServer.FakeSecureServerEndpoint)) + { + Assert.Equal(1, APICallHistory.WinHttpOptionClientCertContext.Count); + Assert.NotEqual(IntPtr.Zero, APICallHistory.WinHttpOptionClientCertContext[0]); + } + } + } + + + [Fact] + public void SecureRequest_ClientCertificateOptionAutomatic_CertStoreHasValidAndInvalidCerts_ValidCertificateContextSet() + { + using (var handler = new WinHttpHandler()) + { + var helper = new ClientCertificateHelper(); + TestControl.CurrentUserCertificateStore = helper.ValidAndInvalidClientCertificateCollection; + handler.ClientCertificateOption = ClientCertificateOption.Automatic; + using (HttpResponseMessage response = SendRequestHelper.Send( + handler, + () => { }, + TestServer.FakeSecureServerEndpoint)) + { + Assert.Equal(1, APICallHistory.WinHttpOptionClientCertContext.Count); + Assert.NotEqual(IntPtr.Zero, APICallHistory.WinHttpOptionClientCertContext[0]); + } + } } } } diff --git a/src/System.Net.Http.WinHttpHandler/tests/UnitTests/FakeX509Certificates.cs b/src/System.Net.Http.WinHttpHandler/tests/UnitTests/FakeX509Certificates.cs new file mode 100644 index 000000000000..56443ad86318 --- /dev/null +++ b/src/System.Net.Http.WinHttpHandler/tests/UnitTests/FakeX509Certificates.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Net.Http.WinHttpHandlerUnitTests; +using System.Security.Cryptography.X509Certificates; + +namespace System.Net.Http +{ + public class X509Store : IDisposable + { + private bool _disposed; + + public X509Store() + { + } + + public X509Certificate2Collection Certificates + { + get + { + return TestControl.CurrentUserCertificateStore; + } + } + + public void Open(OpenFlags flags) + { + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + _disposed = true; + } + } + } +} diff --git a/src/System.Net.Http.WinHttpHandler/tests/UnitTests/System.Net.Http.WinHttpHandler.Unit.Tests.csproj b/src/System.Net.Http.WinHttpHandler/tests/UnitTests/System.Net.Http.WinHttpHandler.Unit.Tests.csproj index a8a37d208d6f..2f1165fcdcfa 100644 --- a/src/System.Net.Http.WinHttpHandler/tests/UnitTests/System.Net.Http.WinHttpHandler.Unit.Tests.csproj +++ b/src/System.Net.Http.WinHttpHandler/tests/UnitTests/System.Net.Http.WinHttpHandler.Unit.Tests.csproj @@ -78,6 +78,7 @@ + diff --git a/src/System.Net.Http.WinHttpHandler/tests/UnitTests/TestControl.cs b/src/System.Net.Http.WinHttpHandler/tests/UnitTests/TestControl.cs index a36bdd6eb7b1..353f9fa41c37 100644 --- a/src/System.Net.Http.WinHttpHandler/tests/UnitTests/TestControl.cs +++ b/src/System.Net.Http.WinHttpHandler/tests/UnitTests/TestControl.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Security.Cryptography.X509Certificates; using System.Threading; namespace System.Net.Http.WinHttpHandlerUnitTests @@ -26,6 +27,8 @@ public static class Fail public static int ResponseDelayTime { get; set; } public static AutoResetEvent ResponseDelayCompletedEvent { get; internal set; } + + public static X509Certificate2Collection CurrentUserCertificateStore{ get; set; } public static void Reset() { @@ -40,6 +43,8 @@ public static void Reset() PACFileNotDetectedOnNetwork = false; ResponseDelayTime = 0; ResponseDelayCompletedEvent = new AutoResetEvent(true); + + CurrentUserCertificateStore = new X509Certificate2Collection(); } public static void ResetAll()