context: using System;
  using System.Linq;
  using System.Threading;
  using System.Transactions;
  using log4net;
  using log4net.Repository.Hierarchy;
  using NHibernate.Cfg;
  using NHibernate.Engine;
  using NHibernate.Linq;
  using NHibernate.Test.TransactionTest;
  using NUnit.Framework;
  
  namespace NHibernate.Test.SystemTransactions
  {
  	[TestFixture]
  	public class DistributedSystemTransactionFixture : SystemTransactionFixtureBase
  	{
  		private static readonly ILog _log = LogManager.GetLogger(typeof(DistributedSystemTransactionFixture));
  		protected override bool UseConnectionOnSystemTransactionPrepare => true;
  		protected override bool AutoJoinTransaction => true;
  
  		protected override bool AppliesTo(Dialect.Dialect dialect)
  			=> dialect.SupportsDistributedTransactions && base.AppliesTo(dialect);
  
  		protected override void OnTearDown()
  		{
  			DodgeTransactionCompletionDelayIfRequired();
  			base.OnTearDown();
  		}
  
  		[Test]
  		public void SupportsEnlistingInDistributed()
  		{
  			using (new TransactionScope())
  			{
  				ForceEscalationToDistributedTx.Escalate();
  
  				Assert.AreNotEqual(
  					Guid.Empty,
  					System.Transactions.Transaction.Current.TransactionInformation.DistributedIdentifier,
  					"Transaction lacks a distributed identifier");
  
  				using (var s = OpenSession())
  				{
  					s.Save(new Person());
  					// Ensure the connection is acquired (thus enlisted)
  					Assert.DoesNotThrow(s.Flush, "Failure enlisting a connection in a distributed transaction.");
  				}
  			}
  		}
  
  		[Test]
  		public void SupportsPromotingToDistributed()
  		{
  			using (new TransactionScope())
  			{
  				using (var s = OpenSession())
  				{
  					s.Save(new Person());
  					// Ensure the connection is acquired (thus enlisted)
  					s.Flush();
  				}
  				Assert.DoesNotThrow(() => ForceEscalationToDistributedTx.Escalate(),
  					"Failure promoting the transaction to distributed while already having enlisted a connection.");
  				Assert.AreNotEqual(
  					Guid.Empty,
  					System.Transactions.Transaction.Current.TransactionInformation.DistributedIdentifier,
  					"Transaction lacks a distributed identifier");
  			}
  		}
  
  		[Test]
  		public void WillNotCrashOnPrepareFailure()
  		{
  			IgnoreIfUnsupported(false);
  			var tx = new TransactionScope();
  			var disposeCalled = false;
  			try
  			{
  				using (var s = OpenSession())
  				{
  					s.Save(new Person { NotNullData = null }); // Cause a SQL not null constraint violation.
  				}
  
  				ForceEscalationToDistributedTx.Escalate();
  
  				tx.Complete();
  				disposeCalled = true;
  				Assert.Throws<TransactionAbortedException>(tx.Dispose, "Scope disposal has not rollback and throw.");
  			}
  			finally
  			{
  				if (!disposeCalled)
  				{
  					try
  					{
  						tx.Dispose();
  					}
  					catch
  					{
  						// Ignore, if disposed has not been called, another exception has occurred in the try and
  						// we should avoid overriding it by the disposal failure.
  					}
  				}
  			}
  		}
  
  		[Theory]
  		public void CanRollbackTransaction(bool explicitFlush)
  		{
  			IgnoreIfUnsupported(explicitFlush);
  			var tx = new TransactionScope();
  			var disposeCalled = false;
  			try
  			{
  				using (var s = OpenSession())
  				{
  					ForceEscalationToDistributedTx.Escalate(true); //will rollback tx
  					s.Save(new Person());
  
  					if (explicitFlush)
  						s.Flush();
  
  					tx.Complete();
  				}
  				disposeCalled = true;
  				Assert.Throws<TransactionAbortedException>(tx.Dispose, "Scope disposal has not rollback and throw.");
  			}
  			finally
  			{
  				if (!disposeCalled)
  				{
  					try
  					{
  						tx.Dispose();
  					}
  					catch
  					{
  						// Ignore, if disposed has not been called, another exception has occurred in the try and
  						// we should avoid overriding it by the disposal failure.
  					}
  				}
  			}
  
  			AssertNoPersons();
  		}
  
  		[Theory]
  		public void CanRollbackTransactionFromScope(bool explicitFlush)
  		{
  			IgnoreIfUnsupported(explicitFlush);
  			using (new TransactionScope())
  			using (var s = OpenSession())
  			{
  				ForceEscalationToDistributedTx.Escalate();
  				s.Save(new Person());
  
  				if (explicitFlush)
  					s.Flush();
  				// No Complete call for triggering rollback.
  			}
  
  			AssertNoPersons();
  		}
  
  		[Theory]
  		[Description("Another action inside the transaction do the rollBack outside nh-session-scope.")]
  		public void RollbackOutsideNh(bool explicitFlush)
  		{
  			IgnoreIfUnsupported(explicitFlush);
  			try
  			{
  				using (var txscope = new TransactionScope())
  				{
  					using (var s = OpenSession())
  					{
  						var person = new Person();
  						s.Save(person);
  
  						if (explicitFlush)
  							s.Flush();
  					}
  					ForceEscalationToDistributedTx.Escalate(true); //will rollback tx
  
  					txscope.Complete();
  				}
  
  				Assert.Fail("Scope disposal has not rollback and throw.");
  			}
  			catch (TransactionAbortedException)
  			{
  				_log.Debug("Transaction aborted.");
  			}
  
  			AssertNoPersons();
  		}
  
  		[Theory]
  		[Description("rollback inside nh-session-scope should not commit save and the transaction should be aborted.")]
  		public void TransactionInsertWithRollBackFromScope(bool explicitFlush)
  		{
  			IgnoreIfUnsupported(explicitFlush);
  			using (new TransactionScope())
  			{
  				using (var s = OpenSession())
  				{
  					var person = new Person();
  					s.Save(person);
  					ForceEscalationToDistributedTx.Escalate();
  
  					if (explicitFlush)
  						s.Flush();
  				}
  				// No Complete call for triggering rollback.
  			}
  			AssertNoPersons();
  		}
  
  		[Theory]
  		[Description("rollback inside nh-session-scope should not commit save and the transaction should be aborted.")]
  		public void TransactionInsertWithRollBackTask(bool explicitFlush)
  		{
  			IgnoreIfUnsupported(explicitFlush);
  			try
  			{
  				using (var txscope = new TransactionScope())
  				{
  					using (var s = OpenSession())
  					{
  						var person = new Person();
  						s.Save(person);
  						ForceEscalationToDistributedTx.Escalate(true); //will rollback tx
  
  						if (explicitFlush)
  							s.Flush();
  					}
  					txscope.Complete();
  				}
  
  				Assert.Fail("Scope disposal has not rollback and throw.");
  			}
  			catch (TransactionAbortedException)
  			{
  				_log.Debug("Transaction aborted.");
  			}
  
  			AssertNoPersons();
  		}
  
  		[Theory]
  		[Description(@"Two session in two txscope
   (without an explicit NH transaction)
   and with a rollback in the second dtc and a rollback outside nh-session-scope.")]
  		public void TransactionInsertLoadWithRollBackFromScope(bool explicitFlush)
  		{
  			IgnoreIfUnsupported(explicitFlush);
  			object savedId;
  			var createdAt = DateTime.Today;
  			using (var txscope = new TransactionScope())
  			{
  				using (var s = OpenSession())
  				{
  
gt: 					var person = new Person { CreatedAt = createdAt };