// ***********************************************************************
// 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
}
}