diff --git a/src/libraries/System.Console/src/System.Console.csproj b/src/libraries/System.Console/src/System.Console.csproj
index ea763aa3a46310..c60368c33b8b50 100644
--- a/src/libraries/System.Console/src/System.Console.csproj
+++ b/src/libraries/System.Console/src/System.Console.csproj
@@ -194,8 +194,6 @@
Link="Common\Interop\Unix\Interop.GetControlCharacters.cs" />
-
Reads data from the file descriptor into the buffer.
/// The file descriptor.
/// The buffer to read into.
- /// The number of bytes read, or a negative value if there's an error.
- internal static unsafe int Read(SafeFileHandle fd, Span buffer)
+ /// The number of bytes read, or an exception if there's an error.
+ private static unsafe int Read(SafeFileHandle fd, Span buffer)
{
fixed (byte* bufPtr = buffer)
{
diff --git a/src/libraries/System.Console/src/System/TermInfo.DatabaseFactory.cs b/src/libraries/System.Console/src/System/TermInfo.DatabaseFactory.cs
index 844475e4851d59..0db908b2729bbd 100644
--- a/src/libraries/System.Console/src/System/TermInfo.DatabaseFactory.cs
+++ b/src/libraries/System.Console/src/System/TermInfo.DatabaseFactory.cs
@@ -15,7 +15,7 @@ internal sealed class DatabaseFactory
/// The default locations in which to search for terminfo databases.
/// This is the ordering of well-known locations used by ncurses.
///
- private static readonly string[] _terminfoLocations = new string[] {
+ internal static readonly string[] s_terminfoLocations = {
"/etc/terminfo",
"/lib/terminfo",
"/usr/share/terminfo",
@@ -34,7 +34,7 @@ internal sealed class DatabaseFactory
/// Read the database for the specified terminal.
/// The identifier for the terminal.
/// The database, or null if it could not be found.
- private static Database? ReadDatabase(string term)
+ internal static Database? ReadDatabase(string term)
{
// This follows the same search order as prescribed by ncurses.
Database? db;
@@ -54,7 +54,7 @@ internal sealed class DatabaseFactory
}
// Then try a set of well-known locations.
- foreach (string terminfoLocation in _terminfoLocations)
+ foreach (string terminfoLocation in s_terminfoLocations)
{
if ((db = ReadDatabase(term, terminfoLocation)) != null)
{
@@ -88,7 +88,7 @@ private static bool TryOpen(string filePath, [NotNullWhen(true)] out SafeFileHan
/// The identifier for the terminal.
/// The path to the directory containing terminfo database files.
/// The database, or null if it could not be found.
- private static Database? ReadDatabase(string? term, string? directoryPath)
+ internal static Database? ReadDatabase(string? term, string? directoryPath)
{
if (string.IsNullOrEmpty(term) || string.IsNullOrEmpty(directoryPath))
{
@@ -106,8 +106,7 @@ private static bool TryOpen(string filePath, [NotNullWhen(true)] out SafeFileHan
using (fd)
{
// Read in all of the terminfo data
- long termInfoLength = Interop.CheckIo(Interop.Sys.LSeek(fd, 0, Interop.Sys.SeekWhence.SEEK_END)); // jump to the end to get the file length
- Interop.CheckIo(Interop.Sys.LSeek(fd, 0, Interop.Sys.SeekWhence.SEEK_SET)); // reset back to beginning
+ long termInfoLength = RandomAccess.GetLength(fd);
const int MaxTermInfoLength = 4096; // according to the term and tic man pages, 4096 is the terminfo file size max
const int HeaderLength = 12;
if (termInfoLength <= HeaderLength || termInfoLength > MaxTermInfoLength)
@@ -116,10 +115,17 @@ private static bool TryOpen(string filePath, [NotNullWhen(true)] out SafeFileHan
}
byte[] data = new byte[(int)termInfoLength];
- if (ConsolePal.Read(fd, data) != data.Length)
+ long fileOffset = 0;
+ do
{
- throw new InvalidOperationException(SR.IO_TermInfoInvalid);
- }
+ int bytesRead = RandomAccess.Read(fd, new Span(data, (int)fileOffset, (int)(termInfoLength - fileOffset)), fileOffset);
+ if (bytesRead == 0)
+ {
+ throw new InvalidOperationException(SR.IO_TermInfoInvalid);
+ }
+
+ fileOffset += bytesRead;
+ } while (fileOffset < termInfoLength);
// Create the database from the data
return new Database(term, data);
diff --git a/src/libraries/System.Console/tests/System.Console.Tests.csproj b/src/libraries/System.Console/tests/System.Console.Tests.csproj
index 311abe1aaf8735..f4b451d30e147b 100644
--- a/src/libraries/System.Console/tests/System.Console.Tests.csproj
+++ b/src/libraries/System.Console/tests/System.Console.Tests.csproj
@@ -22,7 +22,6 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/libraries/System.Console/tests/TermInfo.Unix.cs b/src/libraries/System.Console/tests/TermInfo.Unix.cs
new file mode 100644
index 00000000000000..03da592c759e6c
--- /dev/null
+++ b/src/libraries/System.Console/tests/TermInfo.Unix.cs
@@ -0,0 +1,105 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using Xunit;
+
+[SkipOnPlatform(TestPlatforms.Android | TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS, "Not supported on Android, Browser, iOS, MacCatalyst, or tvOS.")]
+public class TermInfoTests
+{
+ [Fact]
+ [PlatformSpecific(TestPlatforms.AnyUnix)] // Tests TermInfo
+ public void VerifyInstalledTermInfosParse()
+ {
+ bool foundAtLeastOne = false;
+
+ foreach (string location in TermInfo.DatabaseFactory.s_terminfoLocations)
+ {
+ if (!Directory.Exists(location))
+ continue;
+
+ foreach (string term in Directory.EnumerateFiles(location, "*", SearchOption.AllDirectories))
+ {
+ if (term.ToUpper().Contains("README")) continue;
+ foundAtLeastOne = true;
+
+ TerminalFormatStrings info = new(TermInfo.DatabaseFactory.ReadDatabase(Path.GetFileName(term)));
+
+ if (!string.IsNullOrEmpty(info.Foreground))
+ {
+ Assert.NotEmpty(TermInfo.ParameterizedStrings.Evaluate(info.Foreground, 0 /* irrelevant, just an integer to put into the formatting*/));
+ }
+
+ if (!string.IsNullOrEmpty(info.Background))
+ {
+ Assert.NotEmpty(TermInfo.ParameterizedStrings.Evaluate(info.Background, 0 /* irrelevant, just an integer to put into the formatting*/));
+ }
+ }
+ }
+
+ Assert.True(foundAtLeastOne, "Didn't find any terminfo files");
+ }
+
+ [Fact]
+ [PlatformSpecific(TestPlatforms.AnyUnix)] // Tests TermInfo
+ public void VerifyTermInfoSupportsNewAndLegacyNcurses()
+ {
+ Assert.NotNull(TermInfo.DatabaseFactory.ReadDatabase("xterm", "ncursesFormats")); // This will throw InvalidOperationException in case we don't support the legacy format
+ Assert.NotNull(TermInfo.DatabaseFactory.ReadDatabase("screen-256color", "ncursesFormats")); // This will throw InvalidOperationException if we can't parse the new format
+ }
+
+ [Theory]
+ [PlatformSpecific(TestPlatforms.AnyUnix)] // Tests TermInfo
+ [InlineData("xterm-256color", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)]
+ [InlineData("xterm-256color", "\u001B\u005B\u00331m", "\u001B\u005B\u00341m", 1)]
+ [InlineData("xterm-256color", "\u001B\u005B90m", "\u001B\u005B100m", 8)]
+ [InlineData("screen", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)]
+ [InlineData("screen", "\u001B\u005B\u00332m", "\u001B\u005B\u00342m", 2)]
+ [InlineData("screen", "\u001B\u005B\u00339m", "\u001B\u005B\u00349m", 9)]
+ [InlineData("Eterm", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)]
+ [InlineData("Eterm", "\u001B\u005B\u00333m", "\u001B\u005B\u00343m", 3)]
+ [InlineData("Eterm", "\u001B\u005B\u003310m", "\u001B\u005B\u003410m", 10)]
+ [InlineData("wsvt25", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)]
+ [InlineData("wsvt25", "\u001B\u005B\u00334m", "\u001B\u005B\u00344m", 4)]
+ [InlineData("wsvt25", "\u001B\u005B\u003311m", "\u001B\u005B\u003411m", 11)]
+ [InlineData("mach-color", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)]
+ [InlineData("mach-color", "\u001B\u005B\u00335m", "\u001B\u005B\u00345m", 5)]
+ [InlineData("mach-color", "\u001B\u005B\u003312m", "\u001B\u005B\u003412m", 12)]
+ public void TermInfoVerification(string termToTest, string expectedForeground, string expectedBackground, int colorValue)
+ {
+ TermInfo.Database db = TermInfo.DatabaseFactory.ReadDatabase(termToTest);
+ if (db != null)
+ {
+ TerminalFormatStrings info = new(db);
+ Assert.Equal(expectedForeground, TermInfo.ParameterizedStrings.Evaluate(info.Foreground, colorValue));
+ Assert.Equal(expectedBackground, TermInfo.ParameterizedStrings.Evaluate(info.Background, colorValue));
+ Assert.InRange(info.MaxColors, 1, int.MaxValue);
+ }
+ }
+
+ [Fact]
+ [PlatformSpecific(TestPlatforms.OSX)] // The file being tested is available by default only on OSX
+ public void EmuTermInfoDoesntBreakParser()
+ {
+ // This file (available by default on OS X) is called out specifically since it contains a format where it has %i
+ // but only one variable instead of two. Make sure we don't break in this case
+ TermInfoVerification("emu", "\u001Br1;", "\u001Bs1;", 0);
+ }
+
+ [Fact]
+ [PlatformSpecific(TestPlatforms.AnyUnix)] // Tests TermInfo
+ public void TryingToLoadTermThatDoesNotExistDoesNotThrow()
+ {
+ const string NonexistentTerm = "foobar____";
+ TermInfo.Database db = TermInfo.DatabaseFactory.ReadDatabase(NonexistentTerm);
+ TerminalFormatStrings info = new(db);
+ Assert.Null(db);
+ Assert.Null(info.Background);
+ Assert.Null(info.Foreground);
+ Assert.Equal(0, info.MaxColors);
+ Assert.Null(info.Reset);
+ }
+}
diff --git a/src/libraries/System.Console/tests/TermInfo.cs b/src/libraries/System.Console/tests/TermInfo.cs
deleted file mode 100644
index 60336b23a0646f..00000000000000
--- a/src/libraries/System.Console/tests/TermInfo.cs
+++ /dev/null
@@ -1,208 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using Xunit;
-
-[SkipOnPlatform(TestPlatforms.Android | TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS, "Not supported on Android, Browser, iOS, MacCatalyst, or tvOS.")]
-public class TermInfo
-{
- // Names of internal members accessed via reflection
- private const string TerminfoType = "System.TermInfo";
- private const string TerminfoDatabaseFactoryType = TerminfoType + "+DatabaseFactory";
- private const string ParameterizedStringsType = TerminfoType + "+ParameterizedStrings";
- private const string FormatParamType = ParameterizedStringsType + "+FormatParam";
- private const string TerminalFormatStringsType = "System.TerminalFormatStrings";
- private const string ReadDatabaseMethod = "ReadDatabase";
- private const string EvaluateMethod = "Evaluate";
- private const string ForegroundFormatField = "Foreground";
- private const string BackgroundFormatField = "Background";
- private const string ResetFormatField = "Reset";
- private const string MaxColorsField = "MaxColors";
- private const string TerminfoLocationsField = "_terminfoLocations";
-
- [Fact]
- [PlatformSpecific(TestPlatforms.AnyUnix)] // Tests TermInfo
- public void VerifyInstalledTermInfosParse()
- {
- bool foundAtLeastOne = false;
-
- string[] locations = GetFieldValueOnObject(TerminfoLocationsField, null, typeof(Console).GetTypeInfo().Assembly.GetType(TerminfoDatabaseFactoryType));
- foreach (string location in locations)
- {
- if (!Directory.Exists(location))
- continue;
-
- foreach (string term in Directory.EnumerateFiles(location, "*", SearchOption.AllDirectories))
- {
- if (term.ToUpper().Contains("README")) continue;
- foundAtLeastOne = true;
-
- object info = CreateTermColorInfo(ReadTermInfoDatabase(Path.GetFileName(term)));
-
- if (!string.IsNullOrEmpty(GetForegroundFormat(info)))
- {
- Assert.NotEmpty(EvaluateParameterizedStrings(GetForegroundFormat(info), 0 /* irrelevant, just an integer to put into the formatting*/));
- }
-
- if (!string.IsNullOrEmpty(GetBackgroundFormat(info)))
- {
- Assert.NotEmpty(EvaluateParameterizedStrings(GetBackgroundFormat(info), 0 /* irrelevant, just an integer to put into the formatting*/));
- }
- }
- }
-
- Assert.True(foundAtLeastOne, "Didn't find any terminfo files");
- }
-
- [Fact]
- [PlatformSpecific(TestPlatforms.AnyUnix)] // Tests TermInfo
- public void VerifyTermInfoSupportsNewAndLegacyNcurses()
- {
- MethodInfo readDbMethod = typeof(Console).GetTypeInfo().Assembly.GetType(TerminfoDatabaseFactoryType).GetTypeInfo().GetDeclaredMethods(ReadDatabaseMethod).Where(m => m.GetParameters().Count() == 2).Single();
- readDbMethod.Invoke(null, new object[] { "xterm", "ncursesFormats" }); // This will throw InvalidOperationException in case we don't support the legacy format
- readDbMethod.Invoke(null, new object[] { "screen-256color", "ncursesFormats" }); // This will throw InvalidOperationException if we can't parse the new format
- }
-
- [Theory]
- [PlatformSpecific(TestPlatforms.AnyUnix)] // Tests TermInfo
- [InlineData("xterm-256color", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)]
- [InlineData("xterm-256color", "\u001B\u005B\u00331m", "\u001B\u005B\u00341m", 1)]
- [InlineData("xterm-256color", "\u001B\u005B90m", "\u001B\u005B100m", 8)]
- [InlineData("screen", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)]
- [InlineData("screen", "\u001B\u005B\u00332m", "\u001B\u005B\u00342m", 2)]
- [InlineData("screen", "\u001B\u005B\u00339m", "\u001B\u005B\u00349m", 9)]
- [InlineData("Eterm", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)]
- [InlineData("Eterm", "\u001B\u005B\u00333m", "\u001B\u005B\u00343m", 3)]
- [InlineData("Eterm", "\u001B\u005B\u003310m", "\u001B\u005B\u003410m", 10)]
- [InlineData("wsvt25", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)]
- [InlineData("wsvt25", "\u001B\u005B\u00334m", "\u001B\u005B\u00344m", 4)]
- [InlineData("wsvt25", "\u001B\u005B\u003311m", "\u001B\u005B\u003411m", 11)]
- [InlineData("mach-color", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)]
- [InlineData("mach-color", "\u001B\u005B\u00335m", "\u001B\u005B\u00345m", 5)]
- [InlineData("mach-color", "\u001B\u005B\u003312m", "\u001B\u005B\u003412m", 12)]
- public void TermInfoVerification(string termToTest, string expectedForeground, string expectedBackground, int colorValue)
- {
- object db = ReadTermInfoDatabase(termToTest);
- if (db != null)
- {
- object info = CreateTermColorInfo(db);
- Assert.Equal(expectedForeground, EvaluateParameterizedStrings(GetForegroundFormat(info), colorValue));
- Assert.Equal(expectedBackground, EvaluateParameterizedStrings(GetBackgroundFormat(info), colorValue));
- Assert.InRange(GetMaxColors(info), 1, int.MaxValue);
- }
- }
-
- [Fact]
- [PlatformSpecific(TestPlatforms.OSX)] // The file being tested is available by default only on OSX
- public void EmuTermInfoDoesntBreakParser()
- {
- // This file (available by default on OS X) is called out specifically since it contains a format where it has %i
- // but only one variable instead of two. Make sure we don't break in this case
- TermInfoVerification("emu", "\u001Br1;", "\u001Bs1;", 0);
- }
-
- [Fact]
- [PlatformSpecific(TestPlatforms.AnyUnix)] // Tests TermInfo
- public void TryingToLoadTermThatDoesNotExistDoesNotThrow()
- {
- const string NonexistentTerm = "foobar____";
- object db = ReadTermInfoDatabase(NonexistentTerm);
- object info = CreateTermColorInfo(db);
- Assert.Null(db);
- Assert.Null(GetBackgroundFormat(info));
- Assert.Null(GetForegroundFormat(info));
- Assert.Equal(0, GetMaxColors(info));
- Assert.Null(GetResetFormat(info));
- }
-
- private object ReadTermInfoDatabase(string term)
- {
- MethodInfo readDbMethod = typeof(Console).GetTypeInfo().Assembly.GetType(TerminfoDatabaseFactoryType).GetTypeInfo().GetDeclaredMethods(ReadDatabaseMethod).Where(m => m.GetParameters().Count() == 1).Single();
- return readDbMethod.Invoke(null, new object[] { term });
- }
-
- private object CreateTermColorInfo(object db)
- {
- return typeof(Console).GetTypeInfo().Assembly.GetType(TerminalFormatStringsType).GetTypeInfo().DeclaredConstructors
- .Where(c => c.GetParameters().Count() == 1).Single().Invoke(new object[] { db });
- }
-
- private string GetForegroundFormat(object colorInfo)
- {
- return GetFieldValueOnObject(ForegroundFormatField, colorInfo, typeof(Console).GetTypeInfo().Assembly.GetType(TerminalFormatStringsType));
- }
-
- private string GetBackgroundFormat(object colorInfo)
- {
- return GetFieldValueOnObject(BackgroundFormatField, colorInfo, typeof(Console).GetTypeInfo().Assembly.GetType(TerminalFormatStringsType));
- }
-
- private int GetMaxColors(object colorInfo)
- {
- return GetFieldValueOnObject(MaxColorsField, colorInfo, typeof(Console).GetTypeInfo().Assembly.GetType(TerminalFormatStringsType));
- }
-
- private string GetResetFormat(object colorInfo)
- {
- return GetFieldValueOnObject(ResetFormatField, colorInfo, typeof(Console).GetTypeInfo().Assembly.GetType(TerminalFormatStringsType));
- }
-
- private T GetFieldValueOnObject(string name, object instance, Type baseType)
- {
- return (T)baseType.GetTypeInfo().GetDeclaredField(name).GetValue(instance);
- }
-
- private object CreateFormatParam(object o)
- {
- Assert.True((o.GetType() == typeof(int)) || (o.GetType() == typeof(string)));
-
- TypeInfo ti = typeof(Console).GetTypeInfo().Assembly.GetType(FormatParamType).GetTypeInfo();
- ConstructorInfo ci = null;
-
- foreach (ConstructorInfo c in ti.DeclaredConstructors)
- {
- Type paramType = c.GetParameters().ElementAt(0).ParameterType;
- if ((paramType == typeof(string)) && (o.GetType() == typeof(string)))
- {
- ci = c;
- break;
- }
- else if ((paramType == typeof(int)) && (o.GetType() == typeof(int)))
- {
- ci = c;
- break;
- }
- }
-
- Assert.True(ci != null);
- return ci.Invoke(new object[] { o });
- }
-
- private string EvaluateParameterizedStrings(string format, params object[] parameters)
- {
- Type formatArrayType = typeof(Console).GetTypeInfo().Assembly.GetType(FormatParamType).MakeArrayType();
- MethodInfo mi = typeof(Console).GetTypeInfo().Assembly.GetType(ParameterizedStringsType).GetTypeInfo()
- .GetDeclaredMethods(EvaluateMethod).First(m => m.GetParameters()[1].ParameterType.IsArray);
-
- // Create individual FormatParams
- object[] stringParams = new object[parameters.Length];
- for (int i = 0; i < parameters.Length; i++)
- stringParams[i] = CreateFormatParam(parameters[i]);
-
- // Create the array of format params and then put the individual params in their location
- Array typeArray = (Array)Activator.CreateInstance(formatArrayType, new object[] { stringParams.Length });
- for (int i = 0; i < parameters.Length; i++)
- typeArray.SetValue(stringParams[i], i);
-
- // Setup the params to evaluate
- object[] evalParams = new object[2];
- evalParams[0] = format;
- evalParams[1] = typeArray;
-
- return (string)mi.Invoke(null, evalParams);
- }
-}
\ No newline at end of file