diff --git a/Issue1167/.runsettings b/Issue1167/.runsettings new file mode 100644 index 0000000..d1187e1 --- /dev/null +++ b/Issue1167/.runsettings @@ -0,0 +1,16 @@ + + + + + 1 + 0 + false + + + + + + + + + diff --git a/Issue1167/ConsoleRecorder.PoC/ConsoleOutput.cs b/Issue1167/ConsoleRecorder.PoC/ConsoleOutput.cs new file mode 100644 index 0000000..db749c0 --- /dev/null +++ b/Issue1167/ConsoleRecorder.PoC/ConsoleOutput.cs @@ -0,0 +1,99 @@ +using ConsoleRecorder.PoC.Utilities; + +namespace ConsoleRecorder.PoC.Environment +{ + /// + /// Provides services for interacting with the console output from the test host. + /// This class cannot be inherited. + /// + internal static class ConsoleOutput + { + /// + /// Gets a value indicating whether console output from the test host is enabled. + /// + public static bool IsEnabled { get; private set; } + + /// + /// Returns a reader for the recorded standard output stream. + /// + public static TextReader Out => new StringReader( + _recorder.GetRecording(ConsoleRecorder.ChannelSelection.Out)); + + /// + /// Returns a reader for the recorded standard error stream. + /// + public static TextReader Error => new StringReader( + _recorder.GetRecording(ConsoleRecorder.ChannelSelection.Error)); + + /// + /// Returns a reader for the recorded console output. + /// + public static TextReader All => new StringReader( + _recorder.GetRecording( + ConsoleRecorder.ChannelSelection.Out | ConsoleRecorder.ChannelSelection.Error)); + + /// + /// Gets the interface to the console recorder. + /// + public static IRecorder Recorder => _recorder; + + /// + /// A recorder for capturing console output. + /// + private static readonly ConsoleRecorder _recorder; + + /// + /// The original standard output stream of the test host. + /// + private static readonly TextWriter _testHostStdOut; + + /// + /// The original standard error stream of the test host. + /// + private static readonly TextWriter _testHostStdErr; + + /// + /// Initializes static members of the class. + /// + static ConsoleOutput() + { + _recorder = new ConsoleRecorder(); + + _testHostStdOut = Console.Out; + _testHostStdErr = Console.Error; + + // Intercept the console output and split it between console and recorder. + Console.SetOut(new DualWriter(_testHostStdOut, _recorder.Channels.Out)); + Console.SetError(new DualWriter(_testHostStdErr, _recorder.Channels.Error)); + IsEnabled = true; + } + + /// + /// Enables console output from the test host. + /// + public static void Enable() + { + if (IsEnabled) + return; + + Console.SetOut(new DualWriter(_testHostStdOut, _recorder.Channels.Out)); + Console.SetError(new DualWriter(_testHostStdErr, _recorder.Channels.Error)); + + IsEnabled = true; + } + + /// + /// Disables console output from the test host. + /// + public static void Disable() + { + if (!IsEnabled) + return; + + Console.SetOut(new DualWriter(TextWriter.Null, _recorder.Channels.Out)); + Console.SetError(new DualWriter(TextWriter.Null, _recorder.Channels.Error)); + + IsEnabled = false; + } + } +} diff --git a/Issue1167/ConsoleRecorder.PoC/ConsoleRecorder.PoC.csproj b/Issue1167/ConsoleRecorder.PoC/ConsoleRecorder.PoC.csproj new file mode 100644 index 0000000..f693d66 --- /dev/null +++ b/Issue1167/ConsoleRecorder.PoC/ConsoleRecorder.PoC.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + + + diff --git a/Issue1167/ConsoleRecorder.PoC/ConsoleRecorder.cs b/Issue1167/ConsoleRecorder.PoC/ConsoleRecorder.cs new file mode 100644 index 0000000..b8979a8 --- /dev/null +++ b/Issue1167/ConsoleRecorder.PoC/ConsoleRecorder.cs @@ -0,0 +1,125 @@ +namespace ConsoleRecorder.PoC +{ + using global::ConsoleRecorder.PoC.Utilities; + using System; + using System.Collections.Generic; + using System.IO; + using System.Runtime.CompilerServices; + using System.Text; + + /// + /// Provides recording capabilities for console output. + /// + public class ConsoleRecorder : IRecorder + { + /// + /// Selects the recorded channels. + /// + [Flags] + public enum ChannelSelection + { + /// + /// The recording of the standard output channel. + /// + Out = 0x1, + + /// + /// The recording of the standard error channel. + /// + Error = 0x2 + } + + /// + /// Gets a value indicating whether a recording is running. + /// + public bool IsRecording { get; private set; } + + internal RecorderChannels Channels { get; private set; } + + /// + /// The list of recordings. + /// + /// + /// Retains individual recordings for each channel plus one combined recording. + /// + private readonly StringWriter[] _recordings; + + /// + /// Initializes a new instance of the class. + /// + public ConsoleRecorder() + { + _recordings = Enumerable.Range(0, 3).Select(sw => new StringWriter()).ToArray(); + + var outRecordWriter = new RecordWriter( + recorder: this, record: new DualWriter(_recordings[0], _recordings[2])); + var errorRecordWriter = new RecordWriter( + recorder: this, record: new DualWriter(_recordings[1], _recordings[2])); + + Channels = new RecorderChannels() + { + Out = outRecordWriter, + Error = errorRecordWriter + }; + + IsRecording = false; + } + + /// + public void Start() + { + if (IsRecording) + { + throw new InvalidOperationException( + "Cannot start recording. A recording is already in progress. " + + "Stop the current recording before starting a new one."); + } + + IsRecording = true; + } + + /// + public void Stop() + { + if (!IsRecording) + { + throw new InvalidOperationException( + "Cannot stop recording. No recording is currently in progress. " + + "Start a recording before attempting to stop it."); + } + + IsRecording = false; + } + + /// + public void Reset() + { + if (IsRecording) + { + throw new InvalidOperationException( + "Cannot reset the recorder while a recording is in progress. " + + "Stop the current recording before resetting."); + } + + foreach (var recording in _recordings) + recording.GetStringBuilder().Clear(); + } + + /// + /// Returns the recorded console output. + /// + /// The type of recording. + /// A string containing the recorded console output. + public string GetRecording(ChannelSelection channels) + { + if (channels.HasFlag(ChannelSelection.Out) && channels.HasFlag(ChannelSelection.Error)) + return _recordings[2].ToString(); + else if (channels.HasFlag(ChannelSelection.Out)) + return _recordings[0].ToString(); + else if (channels.HasFlag(ChannelSelection.Error)) + return _recordings[1].ToString(); + else + return string.Empty; + } + } +} diff --git a/Issue1167/ConsoleRecorder.PoC/DualWriter.cs b/Issue1167/ConsoleRecorder.PoC/DualWriter.cs new file mode 100644 index 0000000..cf852d1 --- /dev/null +++ b/Issue1167/ConsoleRecorder.PoC/DualWriter.cs @@ -0,0 +1,78 @@ +using System.Text; + +namespace ConsoleRecorder.PoC.Utilities +{ + /// + /// Implements a for writing text to two writers in turn. + /// + /// + /// This writer does not implement a backing store. + /// + internal class DualWriter : TextWriter + { + private readonly TextWriter _primaryWriter; + private readonly TextWriter _secondaryWriter; + + /// + public override Encoding Encoding => Encoding.Default; + + /// + /// Initializes a new instance of the class. + /// + /// The primary writer to write a copy to. + /// The secondary writer to write a copy to. + /// Thrown if any of the arguments is null. + public DualWriter(TextWriter primaryWriter, TextWriter secondaryWriter) + { + ArgumentNullException.ThrowIfNull(primaryWriter); + ArgumentNullException.ThrowIfNull(secondaryWriter); + + _primaryWriter = primaryWriter; + _secondaryWriter = secondaryWriter; + } + + /// + public override void Write(char value) + { + _primaryWriter.Write(value); + _secondaryWriter.Write(value); + } + + /// + /// + /// This override exists as a workaround for the erroneous writing + /// of single characters to the standard error stream when using NUnit. + /// For more information, refer to issue #4414. + /// + public override void Write(string? value) + { + if (value is not null) + { + _primaryWriter.Write(value); + _secondaryWriter.Write(value); + } + } + + /// + /// + /// This override exists as a workaround for the erroneous writing + /// of single characters to the standard error stream when using NUnit. + /// For more information, refer to issue #4414. + /// + public override void WriteLine(string? value) + { + if (value is not null) + { + _primaryWriter.WriteLine(value); + _secondaryWriter.WriteLine(value); + } + } + + /// + public override void Flush() + { + _primaryWriter.Flush(); + _secondaryWriter.Flush(); + } + } +} diff --git a/Issue1167/ConsoleRecorder.PoC/IRecorder.cs b/Issue1167/ConsoleRecorder.PoC/IRecorder.cs new file mode 100644 index 0000000..79275ec --- /dev/null +++ b/Issue1167/ConsoleRecorder.PoC/IRecorder.cs @@ -0,0 +1,28 @@ +namespace ConsoleRecorder.PoC +{ + /// + /// Provides mechanisms for recording data. + /// + public interface IRecorder + { + /// + /// Gets a value indicating whether a recording is running. + /// + bool IsRecording { get; } + + /// + /// Starts the recording. + /// + void Start(); + + /// + /// Stops the recording. + /// + void Stop(); + + /// + /// Erases the recorded data and prepares the recorder for a new recording. + /// + void Reset(); + } +} diff --git a/Issue1167/ConsoleRecorder.PoC/RecordWriter.cs b/Issue1167/ConsoleRecorder.PoC/RecordWriter.cs new file mode 100644 index 0000000..378fc1c --- /dev/null +++ b/Issue1167/ConsoleRecorder.PoC/RecordWriter.cs @@ -0,0 +1,46 @@ +using System.Text; + +namespace ConsoleRecorder.PoC +{ + /// + /// Implements a that records data for an . + /// + /// This writer does not implement a backing store. + internal class RecordWriter : TextWriter + { + /// + /// The recorder that controls the recording. + /// + private readonly IRecorder _recorder; + + /// + /// The record to which data is written. + /// + private readonly TextWriter _record; + + /// + public override Encoding Encoding => _record.Encoding; + + /// + /// Initializes a new instance of the class. + /// + /// The recorder that controls the recording. + /// The record to which data is written. + /// Thrown if any of the arguments is null. + public RecordWriter(IRecorder recorder, TextWriter record) + { + ArgumentNullException.ThrowIfNull(recorder); + _recorder = recorder; + + ArgumentNullException.ThrowIfNull(record); + _record = record; + } + + /// + public override void Write(char value) + { + if (_recorder.IsRecording) + _record.Write(value); + } + } +} diff --git a/Issue1167/ConsoleRecorder.PoC/RecorderChannels.cs b/Issue1167/ConsoleRecorder.PoC/RecorderChannels.cs new file mode 100644 index 0000000..dfcf99a --- /dev/null +++ b/Issue1167/ConsoleRecorder.PoC/RecorderChannels.cs @@ -0,0 +1,11 @@ +namespace ConsoleRecorder.PoC +{ + /// + /// Represents the channel set of a . + /// + internal struct RecorderChannels + { + public TextWriter Out; + public TextWriter Error; + } +} diff --git a/Issue1167/ConsoleRecorder.PoC/SystemTests.cs b/Issue1167/ConsoleRecorder.PoC/SystemTests.cs new file mode 100644 index 0000000..abc4a28 --- /dev/null +++ b/Issue1167/ConsoleRecorder.PoC/SystemTests.cs @@ -0,0 +1,56 @@ +using ConsoleRecorder.PoC.Environment; +using NUnit.Framework.Constraints; + +namespace ConsoleRecorder.PoC +{ + public class SystemTests + { + [TestCase(TestName = "Warning is output if called with multiple arguments")] + public void Program_MultipleArguments_OutputsWarning() + { + const string ignoredArgsWarning = "Ignoring excess arguments"; + + ConsoleOutput.Recorder.Start(); + Program.Main(new string[] { "John", "42" }); + ConsoleOutput.Recorder.Stop(); + + string errorOutput = ConsoleOutput.Error.ReadToEnd(); + Assert.That(errorOutput, Contains.Substring(ignoredArgsWarning)); + + ConsoleOutput.Recorder.Reset(); + } + + [TestCase(TestName = "Warning is output before greeting if called with multiple arguments")] + public void Program_MultipleArguments_WarningBeforeGreeting() + { + const string warning = "Warning:"; + const string greeting = "Hello"; + + ConsoleOutput.Recorder.Start(); + Program.Main(new string[] { "John", "42" }); + ConsoleOutput.Recorder.Stop(); + + var output = ConsoleOutput.All; + string? line = output.ReadLine(); + Assert.That(line, Is.Not.Null); + Assert.That(line!.StartsWith(warning)); + line = output.ReadLine(); + Assert.That(line, Is.Not.Null); + Assert.That(line!.Contains(greeting)); + + ConsoleOutput.Recorder.Reset(); + } + + [TestCase(TestName = "Test case with suppressed console output")] + public void Program_NoConsoleOutput() + { + ConsoleOutput.Disable(); + Console.Out.WriteLine("This output will be suppressed!"); + Console.Error.WriteLine("And so will be this!"); + ConsoleOutput.Enable(); + + Program.Main(new string[] { "John", "42" }); + Assert.Pass(); + } + } +} \ No newline at end of file diff --git a/Issue1167/ConsoleRecorder.PoC/Usings.cs b/Issue1167/ConsoleRecorder.PoC/Usings.cs new file mode 100644 index 0000000..7453056 --- /dev/null +++ b/Issue1167/ConsoleRecorder.PoC/Usings.cs @@ -0,0 +1,2 @@ +global using MyApp; +global using NUnit.Framework; \ No newline at end of file diff --git a/Issue1167/MyApp.Tests.MSTest/MyApp.Tests.MSTest.csproj b/Issue1167/MyApp.Tests.MSTest/MyApp.Tests.MSTest.csproj new file mode 100644 index 0000000..1d75267 --- /dev/null +++ b/Issue1167/MyApp.Tests.MSTest/MyApp.Tests.MSTest.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + + + diff --git a/Issue1167/MyApp.Tests.MSTest/UnitTests.cs b/Issue1167/MyApp.Tests.MSTest/UnitTests.cs new file mode 100644 index 0000000..9c91be2 --- /dev/null +++ b/Issue1167/MyApp.Tests.MSTest/UnitTests.cs @@ -0,0 +1,29 @@ +using Microsoft.VisualStudio.TestPlatform.TestHost; + +namespace MyApp.Tests.MSTest +{ + [TestClass] + public class UnitTests + { + [TestMethod("Main method returns zero exit code if called with single argument")] + public void Main_SingleArgument_ReturnsZeroExitCode() + { + int exitCode = Program.Main(new string[] { "John" }); + Assert.AreEqual(exitCode, 0); + } + + [TestMethod("Main method returns non-zero exit code if called with no arguments")] + public void Main_NoArguments_ReturnsNonZeroExitCode() + { + int exitCode = Program.Main(Array.Empty()); + Assert.AreNotEqual(exitCode, 0); + } + + [TestMethod("Main method returns zero exit code if called with multiple arguments")] + public void Main_MultipleArguments_ReturnsZeroExitCode() + { + int exitCode = Program.Main(new string[] { "John", "42" }); + Assert.AreEqual(exitCode, 0); + } + } +} \ No newline at end of file diff --git a/Issue1167/MyApp.Tests.MSTest/Usings.cs b/Issue1167/MyApp.Tests.MSTest/Usings.cs new file mode 100644 index 0000000..ab67c7e --- /dev/null +++ b/Issue1167/MyApp.Tests.MSTest/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/Issue1167/MyApp.Tests.NUnit/MyApp.Tests.NUnit.csproj b/Issue1167/MyApp.Tests.NUnit/MyApp.Tests.NUnit.csproj new file mode 100644 index 0000000..f693d66 --- /dev/null +++ b/Issue1167/MyApp.Tests.NUnit/MyApp.Tests.NUnit.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + + + diff --git a/Issue1167/MyApp.Tests.NUnit/UnitTests.cs b/Issue1167/MyApp.Tests.NUnit/UnitTests.cs new file mode 100644 index 0000000..3714ea6 --- /dev/null +++ b/Issue1167/MyApp.Tests.NUnit/UnitTests.cs @@ -0,0 +1,28 @@ +using Microsoft.VisualStudio.TestPlatform.TestHost; + +namespace MyApp.Tests.NUnit +{ + public class UnitTests + { + [TestCase(TestName = "Main method returns zero exit code if called with single argument")] + public void Main_SingleArgument_ReturnsZeroExitCode() + { + int exitCode = Program.Main(new string[] { "John" }); + Assert.That(exitCode, Is.EqualTo(0)); + } + + [TestCase(TestName = "Main method returns non-zero exit code if called with no arguments")] + public void Main_NoArguments_ReturnsNonZeroExitCode() + { + int exitCode = Program.Main(Array.Empty()); + Assert.That(exitCode, Is.Not.EqualTo(0)); + } + + [TestCase(TestName = "Main method returns zero exit code if called with multiple arguments")] + public void Main_MultipleArguments_ReturnsZeroExitCode() + { + int exitCode = Program.Main(new string[] { "John", "42" }); + Assert.That(exitCode, Is.EqualTo(0)); + } + } +} \ No newline at end of file diff --git a/Issue1167/MyApp.Tests.NUnit/Usings.cs b/Issue1167/MyApp.Tests.NUnit/Usings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/Issue1167/MyApp.Tests.NUnit/Usings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/Issue1167/MyApp.Tests.xUnit/MyApp.Tests.xUnit.csproj b/Issue1167/MyApp.Tests.xUnit/MyApp.Tests.xUnit.csproj new file mode 100644 index 0000000..402aecf --- /dev/null +++ b/Issue1167/MyApp.Tests.xUnit/MyApp.Tests.xUnit.csproj @@ -0,0 +1,24 @@ + + + + net6.0 + enable + enable + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/Issue1167/MyApp.Tests.xUnit/UnitTests.cs b/Issue1167/MyApp.Tests.xUnit/UnitTests.cs new file mode 100644 index 0000000..5adaf18 --- /dev/null +++ b/Issue1167/MyApp.Tests.xUnit/UnitTests.cs @@ -0,0 +1,28 @@ +using Microsoft.VisualStudio.TestPlatform.TestHost; + +namespace MyApp.Tests.xUnit +{ + public class UnitTests + { + [Fact(DisplayName = "Main method returns zero exit code if called with single argument")] + public void Main_SingleArgument_ReturnsZeroExitCode() + { + int exitCode = Program.Main(new string[] { "John" }); + Assert.Equal(0, exitCode); + } + + [Fact(DisplayName = "Main method returns non-zero exit code if called with no arguments")] + public void Main_NoArguments_ReturnsNonZeroExitCode() + { + int exitCode = Program.Main(Array.Empty()); + Assert.NotEqual(0, exitCode); + } + + [Fact(DisplayName = "Main method returns zero exit code if called with multiple arguments")] + public void Main_MultipleArguments_ReturnsZeroExitCode() + { + int exitCode = Program.Main(new string[] { "John", "42" }); + Assert.Equal(0, exitCode); + } + } +} \ No newline at end of file diff --git a/Issue1167/MyApp.Tests.xUnit/Usings.cs b/Issue1167/MyApp.Tests.xUnit/Usings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/Issue1167/MyApp.Tests.xUnit/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/Issue1167/MyApp.sln b/Issue1167/MyApp.sln new file mode 100644 index 0000000..f09d50f --- /dev/null +++ b/Issue1167/MyApp.sln @@ -0,0 +1,49 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyApp", "MyApp\MyApp.csproj", "{EE6C42F3-BFD6-45B4-9D50-7F0065388F9C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyApp.Tests.NUnit", "MyApp.Tests.NUnit\MyApp.Tests.NUnit.csproj", "{8BD72ECF-5E52-4504-AA76-47D9572F87C6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyApp.Tests.MSTest", "MyApp.Tests.MSTest\MyApp.Tests.MSTest.csproj", "{92C4947C-FB98-4B2A-9752-6998A64C7214}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyApp.Tests.xUnit", "MyApp.Tests.xUnit\MyApp.Tests.xUnit.csproj", "{9C36E3E8-62D1-4ED8-BC06-7AEE6C81EAEB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleRecorder.PoC", "ConsoleRecorder.PoC\ConsoleRecorder.PoC.csproj", "{7581E09A-13AC-49D0-B80B-661A67EBF86A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EE6C42F3-BFD6-45B4-9D50-7F0065388F9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE6C42F3-BFD6-45B4-9D50-7F0065388F9C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE6C42F3-BFD6-45B4-9D50-7F0065388F9C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE6C42F3-BFD6-45B4-9D50-7F0065388F9C}.Release|Any CPU.Build.0 = Release|Any CPU + {8BD72ECF-5E52-4504-AA76-47D9572F87C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BD72ECF-5E52-4504-AA76-47D9572F87C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BD72ECF-5E52-4504-AA76-47D9572F87C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BD72ECF-5E52-4504-AA76-47D9572F87C6}.Release|Any CPU.Build.0 = Release|Any CPU + {92C4947C-FB98-4B2A-9752-6998A64C7214}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92C4947C-FB98-4B2A-9752-6998A64C7214}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92C4947C-FB98-4B2A-9752-6998A64C7214}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92C4947C-FB98-4B2A-9752-6998A64C7214}.Release|Any CPU.Build.0 = Release|Any CPU + {9C36E3E8-62D1-4ED8-BC06-7AEE6C81EAEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9C36E3E8-62D1-4ED8-BC06-7AEE6C81EAEB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9C36E3E8-62D1-4ED8-BC06-7AEE6C81EAEB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9C36E3E8-62D1-4ED8-BC06-7AEE6C81EAEB}.Release|Any CPU.Build.0 = Release|Any CPU + {7581E09A-13AC-49D0-B80B-661A67EBF86A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7581E09A-13AC-49D0-B80B-661A67EBF86A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7581E09A-13AC-49D0-B80B-661A67EBF86A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7581E09A-13AC-49D0-B80B-661A67EBF86A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0A0C85E3-CFEA-42A8-A371-87FE41380154} + EndGlobalSection +EndGlobal diff --git a/Issue1167/MyApp/MyApp.csproj b/Issue1167/MyApp/MyApp.csproj new file mode 100644 index 0000000..a964a26 --- /dev/null +++ b/Issue1167/MyApp/MyApp.csproj @@ -0,0 +1,17 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + + diff --git a/Issue1167/MyApp/Program.cs b/Issue1167/MyApp/Program.cs new file mode 100644 index 0000000..eba80e3 --- /dev/null +++ b/Issue1167/MyApp/Program.cs @@ -0,0 +1,24 @@ +namespace MyApp +{ + internal class Program + { + const int ExitCodeSuccess = 0; + const int ExitCodeFailure = 1; + + internal static int Main(string[] args) + { + if (args.Length == 0) + { + Console.Error.WriteLine("Error: No user name provided."); + return ExitCodeFailure; + } + else if (args.Length > 1) + Console.Error.WriteLine("Warning: Ignoring excess arguments."); + + string userName = args[0]; + Console.WriteLine("Hello, {0}!", userName); + + return ExitCodeSuccess; + } + } +} \ No newline at end of file