// *********************************************************************** // Copyright (c) 2011 Charlie Poole // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // *********************************************************************** using System; using System.Collections; using System.Collections.Specialized; using System.IO; using System.Diagnostics; using System.Globalization; using System.Threading; #if !NUNITLITE using System.Security.Principal; #endif using NUnit.Framework.Api; #if !NETCF using System.Runtime.Remoting.Messaging; #endif namespace NUnit.Framework.Internal { /// /// 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. /// public class TestExecutionContext #if !SILVERLIGHT && !NETCF //: ILogicalThreadAffinative #endif { #region Instance Fields /// /// Link to a prior saved context /// public TestExecutionContext prior; /// /// The currently executing test /// private Test currentTest; /// /// The time the test began execution /// private DateTime startTime; /// /// The active TestResult for the current test /// private TestResult currentResult; /// /// The work directory to receive test output /// private string workDirectory; /// /// The object on which tests are currently being executed - i.e. the user fixture object /// private object testObject; /// /// The event listener currently receiving notifications /// private ITestListener listener = TestListener.NULL; /// /// The number of assertions for the current test /// private int assertCount; /// /// Indicates whether execution should terminate after the first error /// private bool stopOnError; /// /// Default timeout for test cases /// private int testCaseTimeout; private RandomGenerator random; #if !NETCF /// /// The current culture /// private CultureInfo currentCulture; /// /// The current UI culture /// private CultureInfo currentUICulture; #endif #if !NETCF && !SILVERLIGHT /// /// Destination for standard output /// private TextWriter outWriter; /// /// Destination for standard error /// private TextWriter errorWriter; /// /// Indicates whether trace is enabled /// private bool tracing; /// /// Destination for Trace output /// private TextWriter traceWriter; #endif #if !NUNITLITE /// /// Indicates whether logging is enabled /// private bool logging; /// /// The current working directory /// private string currentDirectory; private Log4NetCapture logCapture; /// /// The current Principal. /// private IPrincipal currentPrincipal; #endif #endregion #region Constructors /// /// Initializes a new instance of the class. /// public TestExecutionContext() { this.prior = null; this.testCaseTimeout = 0; #if !NETCF this.currentCulture = CultureInfo.CurrentCulture; this.currentUICulture = CultureInfo.CurrentUICulture; #endif #if !NETCF && !SILVERLIGHT this.outWriter = Console.Out; this.errorWriter = Console.Error; this.traceWriter = null; this.tracing = false; #endif #if !NUNITLITE this.logging = false; this.currentDirectory = Environment.CurrentDirectory; this.logCapture = new Log4NetCapture(); this.currentPrincipal = Thread.CurrentPrincipal; #endif } /// /// Initializes a new instance of the class. /// /// An existing instance of TestExecutionContext. public TestExecutionContext( TestExecutionContext other ) { this.prior = other; this.currentTest = other.currentTest; this.currentResult = other.currentResult; this.testObject = other.testObject; this.workDirectory = other.workDirectory; this.listener = other.listener; this.stopOnError = other.stopOnError; this.testCaseTimeout = other.testCaseTimeout; #if !NETCF this.currentCulture = CultureInfo.CurrentCulture; this.currentUICulture = CultureInfo.CurrentUICulture; #endif #if !NETCF && !SILVERLIGHT this.outWriter = other.outWriter; this.errorWriter = other.errorWriter; this.traceWriter = other.traceWriter; this.tracing = other.tracing; #endif #if !NUNITLITE this.logging = other.logging; this.currentDirectory = Environment.CurrentDirectory; this.logCapture = other.logCapture; this.currentPrincipal = Thread.CurrentPrincipal; #endif } #endregion #region Static Singleton Instance /// /// The current context, head of the list of saved contexts. /// private static TestExecutionContext current; /// /// Gets the current context. /// /// The current context. public static TestExecutionContext CurrentContext { get { if (current == null) current = new TestExecutionContext(); return current; } } #endregion #region Static Methods internal static void SetCurrentContext(TestExecutionContext ec) { current = ec; } #endregion #region Properties /// /// Gets or sets the current test /// public Test CurrentTest { get { return currentTest; } set { currentTest = value; } } /// /// The time the current test started execution /// public DateTime StartTime { get { return startTime; } set { startTime = value; } } /// /// Gets or sets the current test result /// public TestResult CurrentResult { get { return currentResult; } set { currentResult = value; } } /// /// The current test object - that is the user fixture /// object on which tests are being executed. /// public object TestObject { get { return testObject; } set { testObject = value; } } /// /// Get or set the working directory /// public string WorkDirectory { get { return workDirectory; } set { workDirectory = value; } } /// /// Get or set indicator that run should stop on the first error /// public bool StopOnError { get { return stopOnError; } set { stopOnError = value; } } /// /// The current test event listener /// internal ITestListener Listener { get { return listener; } set { listener = value; } } /// /// Gets the RandomGenerator specific to this Test /// public RandomGenerator Random { get { if (random == null) { random = new RandomGenerator(currentTest.Seed); } return random; } } /// /// Gets the assert count. /// /// The assert count. internal int AssertCount { get { return assertCount; } set { assertCount = value; } } /// /// Gets or sets the test case timeout vaue /// public int TestCaseTimeout { get { return testCaseTimeout; } set { testCaseTimeout = value; } } #if !NETCF /// /// Saves or restores the CurrentCulture /// public CultureInfo CurrentCulture { get { return currentCulture; } set { currentCulture = value; Thread.CurrentThread.CurrentCulture = currentCulture; } } /// /// Saves or restores the CurrentUICulture /// public CultureInfo CurrentUICulture { get { return currentUICulture; } set { currentUICulture = value; Thread.CurrentThread.CurrentUICulture = currentUICulture; } } #endif #if !NETCF && !SILVERLIGHT /// /// Controls where Console.Out is directed /// internal TextWriter Out { get { return outWriter; } set { if ( outWriter != value ) { outWriter = value; Console.Out.Flush(); Console.SetOut( outWriter ); } } } /// /// Controls where Console.Error is directed /// internal TextWriter Error { get { return errorWriter; } set { if ( errorWriter != value ) { errorWriter = value; Console.Error.Flush(); Console.SetError( errorWriter ); } } } /// /// Controls whether trace and debug output are written /// to the standard output. /// internal bool Tracing { get { return tracing; } set { if (tracing != value) { if (traceWriter != null && tracing) StopTracing(); tracing = value; if (traceWriter != null && tracing) StartTracing(); } } } /// /// Controls where Trace output is directed /// internal TextWriter TraceWriter { get { return traceWriter; } set { if ( traceWriter != value ) { if ( traceWriter != null && tracing ) StopTracing(); traceWriter = value; if ( traceWriter != null && tracing ) StartTracing(); } } } private void StopTracing() { traceWriter.Close(); System.Diagnostics.Trace.Listeners.Remove( "NUnit" ); } private void StartTracing() { System.Diagnostics.Trace.Listeners.Add( new TextWriterTraceListener( traceWriter, "NUnit" ) ); } #endif #if !NUNITLITE /// /// Controls whether log output is captured /// public bool Logging { get { return logCapture.Enabled; } set { logCapture.Enabled = value; } } /// /// 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; } } /// /// Saves and restores the CurrentDirectory /// public string CurrentDirectory { get { return currentDirectory; } set { currentDirectory = value; Environment.CurrentDirectory = currentDirectory; } } /// /// Gets or sets the current for the Thread. /// public IPrincipal CurrentPrincipal { get { return this.currentPrincipal; } set { this.currentPrincipal = value; Thread.CurrentPrincipal = this.currentPrincipal; } } #endif #endregion #region Instance Methods /// /// Saves the old context and returns a fresh one /// with the same settings. /// public TestExecutionContext Save() { return new TestExecutionContext(this); } /// /// Restores the last saved context and puts /// any saved settings back into effect. /// public TestExecutionContext Restore() { if (prior == null) throw new InvalidOperationException("TestContext: too many Restores"); this.TestCaseTimeout = prior.TestCaseTimeout; #if !NETCF this.CurrentCulture = prior.CurrentCulture; this.CurrentUICulture = prior.CurrentUICulture; #endif #if !NETCF && !SILVERLIGHT this.Out = prior.Out; this.Error = prior.Error; this.Tracing = prior.Tracing; #endif #if !NUNITLITE this.CurrentDirectory = prior.CurrentDirectory; this.CurrentPrincipal = prior.CurrentPrincipal; #endif return prior; } /// /// Record any changes in the environment made by /// the test code in the execution context so it /// will be passed on to lower level tests. /// public void UpdateContext() { #if !NETCF this.currentCulture = CultureInfo.CurrentCulture; this.currentUICulture = CultureInfo.CurrentUICulture; #endif #if !NUNITLITE this.currentDirectory = Environment.CurrentDirectory; this.currentPrincipal = System.Threading.Thread.CurrentPrincipal; #endif } /// /// Increments the assert count. /// public void IncrementAssertCount() { System.Threading.Interlocked.Increment(ref assertCount); } #endregion } }