// **************************************************************** // Copyright 2007, Charlie Poole // This is free software licensed under the NUnit license. You may // obtain a copy of the license at http://nunit.org/?p=license&r=2.4 // **************************************************************** using System; using System.Collections.Specialized; using System.Configuration; using System.IO; using System.Diagnostics; using System.Globalization; using System.Threading; namespace NUnit.Core { /// /// Helper class used to save and restore certain static or /// singleton settings in the environment that affect tests /// or which might be changed by the user tests. /// /// An internal class is used to hold settings and a stack /// of these objects is pushed and popped as Save and Restore /// are called. /// /// Static methods for each setting forward to the internal /// object on the top of the stack. /// /// When TestContext itself is instantiated, it is used to /// save and restore settings for a block. It should be /// used with using() or Disposed in a finally block. /// public class TestContext : IDisposable { #region Instance Variables /// /// The current context, head of the list of saved contexts. /// private static ContextHolder current = new ContextHolder(); #endregion #region Static Methods public static bool Tracing { get { return current.Tracing; } set { current.Tracing = value; } } public static bool Logging { get { return current.Logging; } set { current.Logging = value; } } /// /// Controls where Console.Out is directed /// public static TextWriter Out { get { return current.Out; } set { current.Out = value; } } /// /// Controls where Console.Error is directed /// public static TextWriter Error { get { return current.Error; } set { current.Error = value; } } /// /// Controls where Trace output is directed /// public static TextWriter TraceWriter { get { return current.TraceWriter; } set { current.TraceWriter = value; } } public static TextWriter LogWriter { get { return current.LogWriter; } set { current.LogWriter = value; } } /// /// The current directory setting /// public static string CurrentDirectory { get { return current.CurrentDirectory; } set { current.CurrentDirectory = value; } } public static CultureInfo CurrentCulture { get { return current.CurrentCulture; } set { current.CurrentCulture = value; } } /// /// Saves the old context and makes a fresh one /// current without changing any settings. /// public static void Save() { TestContext.current = new ContextHolder( current ); } /// /// Restores the last saved context and puts /// any saved settings back into effect. /// public static void Restore() { current.ReverseChanges(); current = current.prior; } #endregion #region Construct and Dispose /// /// The constructor saves the current context. /// public TestContext() { TestContext.Save(); } /// /// Dispose restores the old context /// public void Dispose() { TestContext.Restore(); } #endregion #region ContextHolder internal class private class ContextHolder { /// /// Indicates whether trace is enabled /// private bool tracing; /// /// Indicates whether logging is enabled /// private bool logging; /// /// Destination for standard output /// private TextWriter outWriter; /// /// Destination for standard error /// private TextWriter errorWriter; /// /// Destination for Trace output /// private TextWriter traceWriter; private Log4NetCapture logCapture; /// /// The current working directory /// private string currentDirectory; /// /// The current culture /// private CultureInfo currentCulture; /// /// Link to a prior saved context /// public ContextHolder prior; public ContextHolder() { this.prior = null; this.tracing = false; this.logging = false; this.outWriter = Console.Out; this.errorWriter = Console.Error; this.traceWriter = null; this.logCapture = new Log4NetCapture(); this.currentDirectory = Environment.CurrentDirectory; this.currentCulture = CultureInfo.CurrentCulture; } public ContextHolder( ContextHolder other ) { this.prior = other; this.tracing = other.tracing; this.logging = other.logging; this.outWriter = other.outWriter; this.errorWriter = other.errorWriter; this.traceWriter = other.traceWriter; this.logCapture = other.logCapture; this.currentDirectory = Environment.CurrentDirectory; this.currentCulture = CultureInfo.CurrentCulture; } /// /// Used to restore settings to their prior /// values before reverting to a prior context. /// public void ReverseChanges() { if ( prior == null ) throw new InvalidOperationException( "TestContext: too many Restores" ); this.Tracing = prior.Tracing; this.Out = prior.Out; this.Error = prior.Error; this.CurrentDirectory = prior.CurrentDirectory; this.CurrentCulture = prior.CurrentCulture; } /// /// Controls whether trace and debug output are written /// to the standard output. /// public bool Tracing { get { return tracing; } set { if ( tracing != value ) { if ( traceWriter != null && tracing ) StopTracing(); tracing = value; if ( traceWriter != null && tracing ) StartTracing(); } } } /// /// Controls whether log output is captured /// public bool Logging { get { return logCapture.Enabled; } set { logCapture.Enabled = value; } } /// /// Controls where Console.Out is directed /// public TextWriter Out { get { return outWriter; } set { if ( outWriter != value ) { outWriter = value; Console.Out.Flush(); Console.SetOut( outWriter ); } } } /// /// Controls where Console.Error is directed /// public TextWriter Error { get { return errorWriter; } set { if ( errorWriter != value ) { errorWriter = value; Console.Error.Flush(); Console.SetError( errorWriter ); } } } public TextWriter TraceWriter { get { return traceWriter; } set { if ( traceWriter != value ) { if ( traceWriter != null && tracing ) StopTracing(); traceWriter = value; if ( traceWriter != null && tracing ) StartTracing(); } } } /// /// Gets or sets the Log writer, which is actually held by a log4net /// TextWriterAppender. When first set, the appender will be created /// and will thereafter send any log events to the writer. /// /// In normal operation, LogWriter is set to an EventListenerTextWriter /// connected to the EventQueue in the test domain. The events are /// subsequently captured in the Gui an the output displayed in /// the Log tab. The application under test does not need to define /// any additional appenders. /// public TextWriter LogWriter { get { return logCapture.Writer; } set { logCapture.Writer = value; } } private void StopTracing() { traceWriter.Close(); System.Diagnostics.Trace.Listeners.Remove( "NUnit" ); } private void StartTracing() { System.Diagnostics.Trace.Listeners.Add( new TextWriterTraceListener( traceWriter, "NUnit" ) ); } public string CurrentDirectory { get { return currentDirectory; } set { currentDirectory = value; Environment.CurrentDirectory = currentDirectory; } } public CultureInfo CurrentCulture { get { return currentCulture; } set { currentCulture = value; Thread.CurrentThread.CurrentCulture = currentCulture; } } } #endregion } }