1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Diagnostics;
6 using System.Reflection;
7 using System.Threading;
8 using System.Threading.Tasks;
9 using Xunit;
10 
11 namespace System.Transactions.Tests
12 {
13     public class NonMsdtcPromoterTests
14     {
15         public static string PromotedTokenString1 = "Promoted Token String Number 1";
16         public static byte[] PromotedToken1 = StringToByteArray(PromotedTokenString1);
17         public static Guid PromoterType1 = new Guid("D9A34FDF-D02A-4EED-98C3-5AD092355E17");
18 
19         private static bool s_traceEnabled = true;
20 
21         private static MethodInfo s_enlistPromotableSinglePhaseMethodInfo;
22         private static MethodInfo s_setDistributedTransactionIdentifierMethodInfo;
23         private static MethodInfo s_getPromotedTokenMethodInfo;
24         private static PropertyInfo s_promoterTypePropertyInfo;
25         private static FieldInfo s_promoterTypeDtcFieldInfo;
26 
NonMsdtcPromoterTests()27         public NonMsdtcPromoterTests()
28         {
29             // reset the testFailures count back to 0 for each test case.
30             VerifySoftDependencies();
31         }
32 
VerifySoftDependencies()33         private static void VerifySoftDependencies()
34         {
35             // Call test methods here if you need to run them without the TestHost/MsTest harness.
36             // First, let's get the MethodInfo objects for the methods we are going to invoke thru reflection.
37             // We can use this array for both EnlistPromotableSinglePhase and SetDistributedTransactionIdentifier
38             if (s_enlistPromotableSinglePhaseMethodInfo == null)
39             {
40                 Type[] parameterTypes = new Type[] { typeof(IPromotableSinglePhaseNotification), typeof(Guid) };
41                 s_enlistPromotableSinglePhaseMethodInfo = typeof(Transaction).GetTypeInfo().GetMethod("EnlistPromotableSinglePhase", parameterTypes);
42                 s_setDistributedTransactionIdentifierMethodInfo = typeof(Transaction).GetTypeInfo().GetMethod("SetDistributedTransactionIdentifier", parameterTypes);
43                 s_getPromotedTokenMethodInfo = typeof(Transaction).GetTypeInfo().GetMethod("GetPromotedToken");
44 
45                 // And the PropertyInfo objects for PromoterType
46                 s_promoterTypePropertyInfo = typeof(Transaction).GetTypeInfo().GetProperty("PromoterType", typeof(Guid));
47 
48                 // And the FieldInfo for TransactionInterop.PromoterTypeDtc
49                 s_promoterTypeDtcFieldInfo = typeof(TransactionInterop).GetTypeInfo().GetField("PromoterTypeDtc", BindingFlags.Public | BindingFlags.Static);
50             }
51 
52             bool allMethodsAreThere = ((s_enlistPromotableSinglePhaseMethodInfo != null) &&
53                 (s_setDistributedTransactionIdentifierMethodInfo != null) &&
54                 (s_getPromotedTokenMethodInfo != null) &&
55                 (s_promoterTypePropertyInfo != null) &&
56                 (s_promoterTypeDtcFieldInfo != null)
57                 );
58             Assert.True(allMethodsAreThere, "At least one of the expected new methods or properties is not implemented by the available System.Transactions.");
59         }
60 
61         #region Helper Methods
62 
CompleteDependentCloneThread(object stateObject)63         private static void CompleteDependentCloneThread(object stateObject)
64         {
65             DependentTransaction cloneToComplete = (DependentTransaction)stateObject;
66             Trace("CompleteDependentCloneThread started - will sleep for 2 seconds");
67 
68             Task.Delay(TimeSpan.FromSeconds(2)).Wait();
69             Trace("CompletedDependentCloneThread - completing the DependentTransaction...");
70             cloneToComplete.Complete();
71         }
72 
Promote(string testCaseDescription, byte[] promotedTokenToCompare, Transaction txToPromote = null)73         public static void Promote(string testCaseDescription, byte[] promotedTokenToCompare, Transaction txToPromote = null)
74         {
75             if (txToPromote == null)
76             {
77                 txToPromote = Transaction.Current;
78             }
79 
80             IPromotableSinglePhaseNotification shouldBeNull = null;
81             shouldBeNull = CreatePSPEEnlistment(NonMsdtcPromoterTests.PromoterType1,
82                 NonMsdtcPromoterTests.PromotedToken1,
83                 null,
84                 /*nonMSDTC = */ true,
85                 txToPromote,
86                 /*spcResponse=*/TransactionStatus.Committed,
87                 /*expectRejection = */ true);
88 
89             Assert.Null(shouldBeNull);
90 
91             byte[] promotedToken = TxPromotedToken(txToPromote);
92             Assert.True(PromotedTokensMatch(promotedToken, NonMsdtcPromoterTests.PromotedToken1));
93         }
94 
Trace(string stringToTrace)95         public static void Trace(string stringToTrace)
96         {
97             if (s_traceEnabled)
98             {
99                 Debug.WriteLine(stringToTrace);
100             }
101         }
102 
NoStressTrace(string stringToTrace)103         public static void NoStressTrace(string stringToTrace)
104         {
105             //if (NonMsdtcPromoterTests.loopCount == 1)
106             {
107                 Debug.WriteLine(stringToTrace);
108             }
109         }
110 
StringToByteArray(string stringToConvert)111         private static byte[] StringToByteArray(string stringToConvert)
112         {
113             byte[] bytes = new byte[stringToConvert.Length * sizeof(char)];
114             System.Buffer.BlockCopy(stringToConvert.ToCharArray(), 0, bytes, 0, bytes.Length);
115             return bytes;
116         }
117 
TestPassed(bool displayTime = false)118         private static void TestPassed(bool displayTime = false)
119         {
120             if (displayTime)
121             {
122                 NoStressTrace(string.Format("Pass: {0}", DateTime.Now.ToString()));
123             }
124             else
125             {
126                 NoStressTrace("Pass");
127             }
128         }
129 
CreateVolatileEnlistment( AutoResetEvent outcomeReceived, Transaction tx = null, EnlistmentOptions options = EnlistmentOptions.None, bool votePrepared = true)130         private static MyEnlistment CreateVolatileEnlistment(
131             AutoResetEvent outcomeReceived,
132             Transaction tx = null,
133             EnlistmentOptions options = EnlistmentOptions.None,
134             bool votePrepared = true)
135         {
136             MyEnlistment enlistment = new MyEnlistment(outcomeReceived, votePrepared);
137             Transaction txToEnlist = Transaction.Current;
138             if (tx != null)
139             {
140                 txToEnlist = tx;
141             }
142             txToEnlist.EnlistVolatile(enlistment, options);
143             return enlistment;
144         }
145 
CreatePSPEEnlistment( Guid promoterType, byte[] promotedToken, AutoResetEvent outcomeReceived, bool nonMSDTC = true, Transaction tx = null, TransactionStatus spcResponse = TransactionStatus.Committed, bool expectRejection = false, bool comparePromotedToken = false, bool failInitialize = false, bool failPromote = false, bool failSPC = false, bool failGetPromoterType = false, bool failGetId = false, bool incorrectNotificationObjectToSetDistributedTransactionId = false )146         private static IPromotableSinglePhaseNotification CreatePSPEEnlistment(
147             Guid promoterType,
148             byte[] promotedToken,
149             AutoResetEvent outcomeReceived,
150             bool nonMSDTC = true,
151             Transaction tx = null,
152             TransactionStatus spcResponse = TransactionStatus.Committed,
153             bool expectRejection = false,
154             bool comparePromotedToken = false,
155             bool failInitialize = false,
156             bool failPromote = false,
157             bool failSPC = false,
158             bool failGetPromoterType = false,
159             bool failGetId = false,
160             bool incorrectNotificationObjectToSetDistributedTransactionId = false
161            )
162         {
163             IPromotableSinglePhaseNotification enlistment = null;
164 
165             Transaction txToEnlist = Transaction.Current;
166             if (tx != null)
167             {
168                 txToEnlist = tx;
169             }
170             if (nonMSDTC)
171             {
172                 NonMSDTCPromoterEnlistment nonMSDTCEnlistment = new NonMSDTCPromoterEnlistment(promoterType,
173                     promotedToken,
174                     outcomeReceived,
175                     spcResponse,
176                     failInitialize,
177                     failPromote,
178                     failSPC,
179                     failGetPromoterType,
180                     failGetId,
181                     incorrectNotificationObjectToSetDistributedTransactionId);
182                 if (nonMSDTCEnlistment.Enlist(txToEnlist, expectRejection, comparePromotedToken))
183                 {
184                     enlistment = nonMSDTCEnlistment;
185                 }
186 
187                 TryProhibitedOperations(txToEnlist, promoterType);
188             }
189             else
190             {
191                 throw new ApplicationException("normal PSPE not implemented yet.");
192             }
193 
194             return enlistment;
195         }
196 
CreateDependentClone(bool blocking, Transaction tx = null)197         private static DependentTransaction CreateDependentClone(bool blocking, Transaction tx = null)
198         {
199             DependentTransaction clone = null;
200             if (tx == null)
201             {
202                 tx = Transaction.Current;
203             }
204 
205             clone = tx.DependentClone(blocking ? DependentCloneOption.BlockCommitUntilComplete : DependentCloneOption.RollbackIfNotComplete);
206 
207             return clone;
208         }
209 
TryProhibitedOperations(Transaction tx, Guid expectedPromoterType)210         private static void TryProhibitedOperations(Transaction tx, Guid expectedPromoterType)
211         {
212             // First make sure that we can do a simple clone. This should be allowed.
213             tx.Clone();
214 
215             try
216             {
217                 Trace("Attempting TransactionInterop.GetDtcTransaction");
218                 TransactionInterop.GetDtcTransaction(tx);
219                 throw new ApplicationException("TransactionInterop.GetDtcTransaction unexpectedly succeeded.");
220             }
221             catch (TransactionPromotionException ex)
222             {
223                 if (TxPromoterType(tx) != expectedPromoterType)
224                 {
225                     Trace(string.Format("Exception {0} occurred, but transaction has an unexpected PromoterType of {1}", ex.ToString(), TxPromoterType(tx)));
226                     throw new ApplicationException(string.Format("Exception {0} occurred, but transaction has an unexpected PromoterType of {1}", ex.ToString(), TxPromoterType(tx)));
227                 }
228             }
229 
230             try
231             {
232                 Trace("Attempting TransactionInterop.GetExportCookie");
233                 byte[] dummyWhereabouts = new byte[1];
234                 TransactionInterop.GetExportCookie(tx, dummyWhereabouts);
235                 throw new ApplicationException("TransactionInterop.GetExportCookie unexpectedly succeeded.");
236             }
237             catch (TransactionPromotionException ex)
238             {
239                 if (TxPromoterType(tx) != expectedPromoterType)
240                 {
241                     Trace(string.Format("Exception {0} occurred, but transaction has an unexpected PromoterType of {1}", ex.ToString(), TxPromoterType(tx)));
242                     throw new ApplicationException(string.Format("Exception {0} occurred, but transaction has an unexpected PromoterType of {1}", ex.ToString(), TxPromoterType(tx)));
243                 }
244             }
245 
246             try
247             {
248                 Trace("Attempting TransactionInterop.GetTransmitterPropagationToken");
249                 byte[] dummyWhereabouts = new byte[1];
250                 TransactionInterop.GetTransmitterPropagationToken(tx);
251                 throw new ApplicationException("TransactionInterop.GetTransmitterPropagationToken unexpectedly succeeded.");
252             }
253             catch (TransactionPromotionException ex)
254             {
255                 if (TxPromoterType(tx) != expectedPromoterType)
256                 {
257                     Trace(string.Format("Exception {0} occurred, but transaction has an unexpected PromoterType of {1}", ex.ToString(), TxPromoterType(tx)));
258                     throw new ApplicationException(string.Format("Exception {0} occurred, but transaction has an unexpected PromoterType of {1}", ex.ToString(), TxPromoterType(tx)));
259                 }
260             }
261 
262             try
263             {
264                 Trace("Attempting EnlistDurable");
265                 DummyDurableEnlistment enlistment = new DummyDurableEnlistment();
266                 tx.EnlistDurable(new Guid("611653C3-8536-4158-A990-00A8EE08B195"), enlistment, EnlistmentOptions.None);
267                 throw new ApplicationException("EnlistDurable unexpectedly succeeded.");
268             }
269             catch (TransactionPromotionException ex)
270             {
271                 if (TxPromoterType(tx) != expectedPromoterType)
272                 {
273                     Trace(string.Format("Exception {0} occurred, but transaction has an unexpected PromoterType of {1}", ex.ToString(), TxPromoterType(tx)));
274                     throw new ApplicationException(string.Format("Exception {0} occurred, but transaction has an unexpected PromoterType of {1}", ex.ToString(), TxPromoterType(tx)));
275                 }
276             }
277 
278             try
279             {
280                 Trace("Attempting EnlistDurableSPC");
281                 DummyDurableEnlistmentSPC enlistment = new DummyDurableEnlistmentSPC();
282                 tx.EnlistDurable(new Guid("611653C3-8536-4158-A990-00A8EE08B195"), enlistment, EnlistmentOptions.None);
283                 throw new ApplicationException("EnlistDurableSPC unexpectedly succeeded.");
284             }
285             catch (TransactionPromotionException ex)
286             {
287                 if (TxPromoterType(tx) != expectedPromoterType)
288                 {
289                     Trace(string.Format("Exception {0} occurred, but transaction has an unexpected PromoterType of {1}", ex.ToString(), TxPromoterType(tx)));
290                     throw new ApplicationException(string.Format("Exception {0} occurred, but transaction has an unexpected PromoterType of {1}", ex.ToString(), TxPromoterType(tx)));
291                 }
292             }
293 
294             // TODO #9582: Uncomment once IFormatter and BinaryFormatter are available in .NET Core
295             //try
296             //{
297             //    MemoryStream txStream = new MemoryStream();
298             //    IFormatter formatter = new BinaryFormatter();
299             //    formatter.Serialize(txStream, tx);
300             //    throw new ApplicationException("Serialize of transaction unexpectedly succeeded.");
301             //}
302             //catch (TransactionPromotionException ex)
303             //{
304             //    if (TxPromoterType(tx) != expectedPromoterType)
305             //    {
306             //        Trace(string.Format("Exception {0} occurred, but transaction has an unexpected PromoterType of {1}", ex.ToString(), TxPromoterType(tx)));
307             //        throw new ApplicationException(string.Format("Exception {0} occurred, but transaction has an unexpected PromoterType of {1}", ex.ToString(), TxPromoterType(tx)));
308             //    }
309             //}
310         }
311 
PromotedTokensMatch(byte[] one, byte[] two)312         private static bool PromotedTokensMatch(byte[] one, byte[] two)
313         {
314             if (one.Length != two.Length)
315             {
316                 return false;
317             }
318 
319             for (int i = 1; i < one.Length; i++)
320             {
321                 if (one[i] != two[i])
322                 {
323                     return false;
324                 }
325             }
326             return true;
327         }
328 
EnlistPromotable(IPromotableSinglePhaseNotification promotableNotification, Transaction txToEnlist, Guid promoterType)329         private static bool EnlistPromotable(IPromotableSinglePhaseNotification promotableNotification, Transaction txToEnlist, Guid promoterType)
330         {
331             object[] parameters = new object[] { promotableNotification, promoterType };
332             bool returnVal = (bool)s_enlistPromotableSinglePhaseMethodInfo.Invoke(txToEnlist, parameters);
333             return returnVal;
334         }
335 
SetDistributedTransactionId(IPromotableSinglePhaseNotification promotableNotification, Transaction txToSet, Guid distributedId)336         private static void SetDistributedTransactionId(IPromotableSinglePhaseNotification promotableNotification, Transaction txToSet, Guid distributedId)
337         {
338             object[] parameters = new object[] { promotableNotification, distributedId };
339             s_setDistributedTransactionIdentifierMethodInfo.Invoke(txToSet, parameters);
340         }
341 
TxPromoterType(Transaction txToGet)342         private static Guid TxPromoterType(Transaction txToGet)
343         {
344             return (Guid)s_promoterTypePropertyInfo.GetValue(txToGet);
345         }
346 
TxPromotedToken(Transaction txToGet)347         private static byte[] TxPromotedToken(Transaction txToGet)
348         {
349             return (byte[])s_getPromotedTokenMethodInfo.Invoke(txToGet, null);
350         }
351 
352         private static Guid PromoterTypeDtc
353         {
354             get
355             {
356                 return (Guid)s_promoterTypeDtcFieldInfo.GetValue(null);
357             }
358         }
359 
360         #endregion
361 
362         #region NonMSDTCPromoterEnlistment
363         public class NonMSDTCPromoterEnlistment : IPromotableSinglePhaseNotification
364         {
365             private Guid _promoterType;
366             private byte[] _promotedToken;
367             private TransactionStatus _spcResponse;
368             private bool _failPromote;
369             private bool _failInitialize;
370             private bool _failSPC;
371             private bool _failGetPromoterType;
372             private bool _failGetId;
373             private bool _incorrectNotificationObjectToSetDistributedTransactionId;
374             private AutoResetEvent _completionEvent;
375             private Guid _distributedTxId;
376             private Transaction _enlistedTransaction;
377 
NonMSDTCPromoterEnlistment(Guid promoterType, byte[] promotedTokenToReturn, AutoResetEvent completionEvent, TransactionStatus spcResponse = TransactionStatus.Committed, bool failInitialize = false, bool failPromote = false, bool failSPC = false, bool failGetPromoterType = false, bool failGetId = false, bool incorrectNotificationObjectToSetDistributedTransactionId = false )378             public NonMSDTCPromoterEnlistment(Guid promoterType,
379                 byte[] promotedTokenToReturn,
380                 AutoResetEvent completionEvent,
381                 TransactionStatus spcResponse = TransactionStatus.Committed,
382                 bool failInitialize = false,
383                 bool failPromote = false,
384                 bool failSPC = false,
385                 bool failGetPromoterType = false,
386                 bool failGetId = false,
387                 bool incorrectNotificationObjectToSetDistributedTransactionId = false
388                 )
389             {
390                 _promoterType = promoterType;
391                 _promotedToken = promotedTokenToReturn;
392                 _spcResponse = spcResponse;
393                 _completionEvent = completionEvent;
394                 _failInitialize = failInitialize;
395                 _failPromote = failPromote;
396                 _failSPC = failSPC;
397                 _failGetPromoterType = failGetPromoterType;
398                 _failGetId = failGetId;
399                 _incorrectNotificationObjectToSetDistributedTransactionId = incorrectNotificationObjectToSetDistributedTransactionId;
400             }
401 
402             public bool Aborted
403             {
404                 get;
405                 private set;
406             }
407 
408             public bool Promoted
409             {
410                 get;
411                 private set;
412             }
413 
Initialize()414             public void Initialize()
415             {
416                 Trace("NonMSDTCPromoterEnlistment.Initialize");
417                 if (_failInitialize)
418                 {
419                     Trace("NonMSDTCPromoterEnlistment.Initialize - Failing based on configuration");
420                     throw new ApplicationException("Failing Initialize based on configuration");
421                 }
422                 return;
423             }
424 
Enlist(Transaction txToEnlist = null, bool expectRejection = false, bool comparePromotedToken = false)425             public bool Enlist(Transaction txToEnlist = null, bool expectRejection = false, bool comparePromotedToken = false)
426             {
427                 if (txToEnlist == null)
428                 {
429                     txToEnlist = Transaction.Current;
430                 }
431 
432                 // invoke txToEnlist.EnlistPromotableSinglePhase(this, this.promoterType) via reflection.
433                 if (!EnlistPromotable(this, txToEnlist, _promoterType))
434                 {
435                     if (expectRejection)
436                     {
437                         // invoke txToEnlist.PromoterType and txToEnlist.PromotedToken via reflection
438                         if (comparePromotedToken && (TxPromoterType(txToEnlist) == _promoterType))
439                         {
440                             if (TxPromotedToken(txToEnlist) != _promotedToken)
441                             {
442                                 throw new ApplicationException("The PromotedToken does not match");
443                             }
444                         }
445                         return false;
446                     }
447                     else
448                     {
449                         throw new ApplicationException("EnlistPromotableSinglePhase failed when expected to succeed");
450                     }
451                 }
452                 else if (expectRejection)
453                 {
454                     throw new ApplicationException("EnlistPromotableSinglePhase succeeded when expected to fail");
455                 }
456 
457                 _enlistedTransaction = txToEnlist;
458                 return true;
459             }
460 
Rollback(SinglePhaseEnlistment singlePhaseEnlistment)461             public void Rollback(SinglePhaseEnlistment singlePhaseEnlistment)
462             {
463                 Trace("NonMSDTCPromoterEnlistment.Aborted");
464                 this.Aborted = true;
465                 _completionEvent.Set();
466                 singlePhaseEnlistment.Done();
467             }
468 
SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment)469             public void SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment)
470             {
471                 Trace("NonMSDTCPromoterEnlistment.SinglePhaseCommit");
472                 _completionEvent.Set();
473 
474                 if (_failSPC)
475                 {
476                     Trace("NonMSDTCPromoterEnlistment.SinglePhaseCommit - Failing based on configuration");
477                     throw new ApplicationException("Failing SinglePhaseCommit based on configuration");
478                 }
479 
480                 switch (_spcResponse)
481                 {
482                     case TransactionStatus.Committed:
483                         {
484                             singlePhaseEnlistment.Committed();
485                             break;
486                         }
487 
488                     case TransactionStatus.Aborted:
489                         {
490                             singlePhaseEnlistment.Aborted(new ApplicationException("Aborted by NonMSDTCPromoterEnlistment.SinglePhaseCommit"));
491                             break;
492                         }
493 
494                     case TransactionStatus.InDoubt:
495                         {
496                             singlePhaseEnlistment.InDoubt(new ApplicationException("InDoubt by NonMSDTCPromoterEnlistment.SinglePhaseCommit"));
497                             break;
498                         }
499                     default:
500                         {
501                             throw new ApplicationException("InDoubt by NonMSDTCPromoterEnlistment.SinglePhaseCommit because of invalid TransactionStatus outcome value.");
502                         }
503                 }
504             }
505 
Promote()506             public byte[] Promote()
507             {
508                 Trace("NonMSDTCPromoterEnlistment.Promote");
509                 this.Promoted = true;
510                 _distributedTxId = Guid.NewGuid();
511                 if (_failPromote)
512                 {
513                     Trace("NonMSDTCPromoterEnlistment.Promote - Failing based on configuration");
514                     throw new ApplicationException("Failing promotion based on configuration");
515                 }
516 
517                 // invoke this.enlistedTransaction.SetDistributedTransactionIdentifier(this, this.promoterType); via reflection
518                 if (_incorrectNotificationObjectToSetDistributedTransactionId)
519                 {
520                     NonMSDTCPromoterEnlistment incorrectNotificationObject = new NonMSDTCPromoterEnlistment(_promoterType, _promotedToken, _completionEvent);
521                     try
522                     {
523                         SetDistributedTransactionId(incorrectNotificationObject, _enlistedTransaction, _distributedTxId);
524                         throw new ApplicationException("SetDistributedTransactionIdentifier did not throw the expected InvalidOperationException");
525                     }
526                     catch (TargetInvocationException ex)
527                     {
528                         if (!(ex.InnerException is InvalidOperationException))
529                         {
530                             throw new ApplicationException("SetDistributedTransactionIdentifier did not throw the expected InvalidOperationException");
531                         }
532                     }
533                 }
534                 else
535                 {
536                     SetDistributedTransactionId(this, _enlistedTransaction, _distributedTxId);
537                 }
538 
539                 return _promotedToken;
540             }
541         }
542         #endregion
543 
544         #region MyEnlistment
545         public class MyEnlistment : IEnlistmentNotification
546         {
547             private bool _committed;
548             private bool _aborted;
549             private bool _indoubt;
550             private bool _votePrepared;
551             private bool _enlistDuringPrepare;
552             private EnlistmentOptions _enlistOptions;
553             private bool _expectSuccessfulEnlist;
554             private AutoResetEvent _secondEnlistmentCompleted;
555 
556             private AutoResetEvent _outcomeReceived;
557 
MyEnlistment( AutoResetEvent outcomeReceived, bool votePrepared = true, bool enlistDuringPrepare = false, EnlistmentOptions enlistOptions = EnlistmentOptions.None, bool expectSuccessfulEnlist = true, AutoResetEvent secondEnlistmentCompleted = null )558             public MyEnlistment(
559                 AutoResetEvent outcomeReceived,
560                 bool votePrepared = true,
561                 bool enlistDuringPrepare = false,
562                 EnlistmentOptions enlistOptions = EnlistmentOptions.None,
563                 bool expectSuccessfulEnlist = true,
564                 AutoResetEvent secondEnlistmentCompleted = null
565                 )
566             {
567                 _outcomeReceived = outcomeReceived;
568                 _votePrepared = votePrepared;
569                 _enlistDuringPrepare = enlistDuringPrepare;
570                 _enlistOptions = enlistOptions;
571                 _expectSuccessfulEnlist = expectSuccessfulEnlist;
572                 _secondEnlistmentCompleted = secondEnlistmentCompleted;
573             }
574 
575             public Transaction TransactionToEnlist
576             {
577                 get;
578                 set;
579             }
580 
581             public bool CommittedOutcome
582             {
583                 get
584                 {
585                     return _committed;
586                 }
587             }
588 
589             public bool AbortedOutcome
590             {
591                 get
592                 {
593                     return _aborted;
594                 }
595             }
596 
597             public bool InDoubtOutcome
598             {
599                 get
600                 {
601                     return _indoubt;
602                 }
603             }
604 
Commit(Enlistment enlistment)605             public void Commit(Enlistment enlistment)
606             {
607                 Trace("MyEnlistment.Commit");
608                 _committed = true;
609                 enlistment.Done();
610                 _outcomeReceived.Set();
611             }
612 
InDoubt(Enlistment enlistment)613             public void InDoubt(Enlistment enlistment)
614             {
615                 Trace("MyEnlistment.InDoubt");
616                 _indoubt = true;
617                 enlistment.Done();
618                 _outcomeReceived.Set();
619             }
620 
Prepare(PreparingEnlistment preparingEnlistment)621             public void Prepare(PreparingEnlistment preparingEnlistment)
622             {
623                 if (_enlistDuringPrepare)
624                 {
625                     Trace(string.Format("MyEnlistment.Prepare - attempting another enlistment with options {0}", _enlistOptions.ToString()));
626                     try
627                     {
628                         MyEnlistment enlist2 = new MyEnlistment(
629                             _secondEnlistmentCompleted,
630                             /*votePrepared=*/ true,
631                             /*enlistDuringPrepare=*/ false,
632                             _enlistOptions);
633                         this.TransactionToEnlist.EnlistVolatile(enlist2, _enlistOptions);
634                         if (!_expectSuccessfulEnlist)
635                         {
636                             // Force rollback of the transaction because the second enlistment was unsuccessful.
637                             Trace("MyEnlistment.Prepare - Force Rollback because second enlistment succeeded unexpectedly");
638                             _aborted = true;
639                             _outcomeReceived.Set();
640                             preparingEnlistment.ForceRollback(new ApplicationException("MyEnlistment voted ForceRollback"));
641                             return;
642                         }
643                     }
644                     catch (Exception ex)
645                     {
646                         if (_expectSuccessfulEnlist)
647                         {
648                             Trace(string.Format("MyEnlistment.Prepare - Force Rollback because second enlistment failed unexpectedly - {0}; {1}", ex.GetType().ToString(), ex.ToString()));
649                             // Force rollback of the transaction because the second enlistment was unsuccessful.
650                             _aborted = true;
651                             _outcomeReceived.Set();
652                             preparingEnlistment.ForceRollback(new ApplicationException("MyEnlistment voted ForceRollback"));
653                             return;
654                         }
655                     }
656                 }
657 
658                 if (_votePrepared)
659                 {
660                     Trace("MyEnlistment.Prepare voting Prepared");
661                     preparingEnlistment.Prepared();
662                 }
663                 else
664                 {
665                     Trace("MyEnlistment.Prepare - Force Rollback");
666                     _aborted = true;
667                     _outcomeReceived.Set();
668                     preparingEnlistment.ForceRollback(new ApplicationException("MyEnlistment voted ForceRollback"));
669                 }
670             }
671 
Rollback(Enlistment enlistment)672             public void Rollback(Enlistment enlistment)
673             {
674                 Trace("MyEnlistment.Rollback");
675                 _aborted = true;
676                 enlistment.Done();
677                 _outcomeReceived.Set();
678             }
679         }
680         #endregion
681 
682         #region DummyDurableEnlistment
683         public class DummyDurableEnlistment : IEnlistmentNotification
684         {
Commit(Enlistment enlistment)685             public void Commit(Enlistment enlistment)
686             {
687                 throw new NotImplementedException();
688             }
689 
InDoubt(Enlistment enlistment)690             public void InDoubt(Enlistment enlistment)
691             {
692                 throw new NotImplementedException();
693             }
694 
Prepare(PreparingEnlistment preparingEnlistment)695             public void Prepare(PreparingEnlistment preparingEnlistment)
696             {
697                 throw new NotImplementedException();
698             }
699 
Rollback(Enlistment enlistment)700             public void Rollback(Enlistment enlistment)
701             {
702                 throw new NotImplementedException();
703             }
704         }
705         #endregion
706 
707         #region DummyDurableEnlistmentSPC
708         private class DummyDurableEnlistmentSPC : ISinglePhaseNotification
709         {
SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment)710             public void SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment)
711             {
712                 throw new NotImplementedException();
713             }
714 
Commit(Enlistment enlistment)715             public void Commit(Enlistment enlistment)
716             {
717                 throw new NotImplementedException();
718             }
719 
InDoubt(Enlistment enlistment)720             public void InDoubt(Enlistment enlistment)
721             {
722                 throw new NotImplementedException();
723             }
724 
Prepare(PreparingEnlistment preparingEnlistment)725             public void Prepare(PreparingEnlistment preparingEnlistment)
726             {
727                 throw new NotImplementedException();
728             }
729 
Rollback(Enlistment enlistment)730             public void Rollback(Enlistment enlistment)
731             {
732                 throw new NotImplementedException();
733             }
734         }
735         #endregion
736 
737         // This class is used in conjunction with SubordinateTransaction. When asked via the Promote
738         // method, it needs to create a DTC transaction and return the propagation token. Since we
739         // can't just create another CommittableTransaction and promote it and return it's propagation
740         // token in the same AppDomain, we spin up another AppDomain and do it there.
741         private class MySimpleTransactionSuperior : ISimpleTransactionSuperior
742         {
743             private DtcTxCreator _dtcTxCreator = new DtcTxCreator() { TraceEnabled = false };
744             private PromotedTx _promotedTx;
745 
Promote()746             public byte[] Promote()
747             {
748                 byte[] propagationToken = null;
749 
750                 Trace("MySimpleTransactionSuperior.Promote");
751                 propagationToken = _dtcTxCreator.CreatePromotedTx(ref _promotedTx);
752 
753                 return propagationToken;
754             }
755 
Rollback()756             public void Rollback()
757             {
758                 Trace("MySimpleTransactionSuperior.Rollback");
759                 _promotedTx.Rollback();
760             }
761 
Commit()762             public void Commit()
763             {
764                 Trace("MySimpleTransactionSuperior.Commit");
765                 _promotedTx.Commit();
766             }
767         }
768 
769         public class DtcTxCreator // : MarshalByRefObject
770         {
771             private static bool s_trace = false;
772 
773             public bool TraceEnabled
774             {
775                 get { return s_trace; }
776                 set { s_trace = value; }
777             }
Trace(string stringToTrace, params object[] args)778             public static void Trace(string stringToTrace, params object[] args)
779             {
780                 if (s_trace)
781                 {
782                     Debug.WriteLine(stringToTrace, args);
783                 }
784             }
785 
CreatePromotedTx(ref PromotedTx promotedTx)786             public byte[] CreatePromotedTx(ref PromotedTx promotedTx)
787             {
788                 DtcTxCreator.Trace("DtcTxCreator.CreatePromotedTx");
789                 byte[] propagationToken;
790                 CommittableTransaction commitTx = new CommittableTransaction();
791                 promotedTx = new PromotedTx(commitTx);
792                 propagationToken = TransactionInterop.GetTransmitterPropagationToken(commitTx);
793                 return propagationToken;
794             }
795         }
796 
797         // This is the class that is created in the "other" AppDomain to create a
798         // CommittableTransaction, promote it to DTC, and return the propagation token.
799         // It also commits or aborts the transaction. Used by MySimpleTransactionSuperior
800         // to create a DTC transaction when asked to promote.
801         public class PromotedTx // : MarshalByRefObject
802         {
803             private CommittableTransaction _commitTx;
804 
PromotedTx(CommittableTransaction commitTx)805             public PromotedTx(CommittableTransaction commitTx)
806             {
807                 DtcTxCreator.Trace("PromotedTx constructor");
808                 _commitTx = commitTx;
809             }
810 
~PromotedTx()811             ~PromotedTx()
812             {
813                 DtcTxCreator.Trace("PromotedTx destructor");
814                 if (_commitTx != null)
815                 {
816                     DtcTxCreator.Trace("PromotedTx destructor calling Rollback");
817                     _commitTx.Rollback();
818                     _commitTx = null;
819                 }
820             }
821 
Commit()822             public void Commit()
823             {
824                 DtcTxCreator.Trace("PromotedTx.Commit");
825                 _commitTx.Commit();
826                 _commitTx = null;
827             }
828 
Rollback()829             public void Rollback()
830             {
831                 DtcTxCreator.Trace("PromotedTx.Rollback");
832                 _commitTx.Rollback();
833                 _commitTx = null;
834             }
835         }
836 
837         #region TestCase_ methods
TestCase_VolatileEnlistments( int count, TransactionStatus expectedOutcome, EnlistmentOptions options = EnlistmentOptions.None, bool commitTx = true, bool votePrepared = true, Type expectedExceptionType = null)838         private static void TestCase_VolatileEnlistments(
839             int count,
840             TransactionStatus expectedOutcome,
841             EnlistmentOptions options = EnlistmentOptions.None,
842             bool commitTx = true,
843             bool votePrepared = true,
844             Type expectedExceptionType = null)
845         {
846             string testCaseDescription = string.Format("TestCase_VolatileEnlistments; count = {0}; expectedOutcome = {1}; options = {2}; votePrepared = {3}, expectedExceptionType = {4}",
847                         count,
848                         expectedOutcome.ToString(),
849                         options.ToString(),
850                         votePrepared,
851                         expectedExceptionType);
852 
853             Trace("**** " + testCaseDescription + " ****");
854 
855             AutoResetEvent[] enlistmentDoneEvts = new AutoResetEvent[count];
856             MyEnlistment[] vols = new MyEnlistment[count];
857             for (int i = 0; i < count; i++)
858             {
859                 enlistmentDoneEvts[i] = new AutoResetEvent(false);
860             }
861 
862             try
863             {
864                 using (TransactionScope ts = new TransactionScope())
865                 {
866                     for (int i = 0; i < count; i++)
867                     {
868                         vols[i] = CreateVolatileEnlistment(enlistmentDoneEvts[i], null, options, votePrepared);
869                     }
870                     if (commitTx)
871                     {
872                         ts.Complete();
873                     }
874                 }
875             }
876             catch (Exception ex)
877             {
878                 Assert.Equal(expectedExceptionType, ex.GetType());
879             }
880 
881             for (int i = 0; i < count; i++)
882             {
883                 Assert.True(enlistmentDoneEvts[i].WaitOne(TimeSpan.FromSeconds(5)));
884             }
885 
886             int passCount = 0;
887             for (int i = 0; i < count; i++)
888             {
889                 if (((expectedOutcome == TransactionStatus.Committed) && vols[i].CommittedOutcome) ||
890                     ((expectedOutcome == TransactionStatus.Aborted) && vols[i].AbortedOutcome) ||
891                     ((expectedOutcome == TransactionStatus.InDoubt) && vols[i].InDoubtOutcome)
892                     )
893                 {
894                     passCount++;
895                 }
896             }
897             Assert.Equal(count, passCount);
898 
899             TestPassed();
900         }
901 
TestCase_PSPENonMsdtc(bool commit, bool promote, TransactionStatus spcResponse, int p0BeforePSPE = 0, int p0AfterPSPE = 0, int p1BeforePSPE = 0, int p1AfterPSPE = 0, int p0AfterPromote = 0, int p1AfterPromote = 0 )902         private static void TestCase_PSPENonMsdtc(bool commit,
903             bool promote,
904             TransactionStatus spcResponse,
905             int p0BeforePSPE = 0,
906             int p0AfterPSPE = 0,
907             int p1BeforePSPE = 0,
908             int p1AfterPSPE = 0,
909             int p0AfterPromote = 0,
910             int p1AfterPromote = 0
911             )
912         {
913             string testCaseDescription = string.Format(
914                 "TestCase_PSPENonMsdtc commit={0}; promote={1}; spcResponse= {2}; p0BeforePSPE={3}; p0AfterPSPE={4}; p1BeforePSPE={5}; p1AfterPSPE={6}; p0AfterPromote={7}; p1AfterPromote={8}",
915                 commit,
916                 promote,
917                 spcResponse,
918                 p0BeforePSPE,
919                 p0AfterPSPE,
920                 p1BeforePSPE,
921                 p1AfterPSPE,
922                 p0AfterPromote,
923                 p1AfterPromote);
924 
925             Trace("**** " + testCaseDescription + " ****");
926 
927             // It doesn't make sense to have "AfterPromote" enlistments if we aren't going to promote the transaction.
928             if (!promote)
929             {
930                 if ((p0AfterPromote > 0) || (p1AfterPromote > 0))
931                 {
932                     Trace("Not promoting - Resetting p0AfterPromote and p1AfterPromote to 0.");
933                     p0AfterPromote = 0;
934                     p1AfterPromote = 0;
935                 }
936             }
937 
938             AutoResetEvent completedEvent = new AutoResetEvent(false);
939 
940             IPromotableSinglePhaseNotification enlistment = null;
941             Transaction savedTransaction = null;
942 
943             int numVolatiles = p0BeforePSPE + p0AfterPSPE + p1BeforePSPE + p1AfterPSPE + p0AfterPromote + p1AfterPromote;
944 
945             AutoResetEvent[] enlistmentDoneEvts = new AutoResetEvent[numVolatiles];
946             MyEnlistment[] vols = new MyEnlistment[numVolatiles];
947             for (int i = 0; i < numVolatiles; i++)
948             {
949                 enlistmentDoneEvts[i] = new AutoResetEvent(false);
950             }
951 
952             try
953             {
954                 using (TransactionScope ts = new TransactionScope())
955                 {
956                     // For checking status later, outside the scope.
957                     savedTransaction = Transaction.Current.Clone();
958 
959                     if (p0BeforePSPE > 0)
960                     {
961                         for (int i = 0; i < p0BeforePSPE; i++)
962                         {
963                             vols[i] = CreateVolatileEnlistment(enlistmentDoneEvts[i], null, EnlistmentOptions.EnlistDuringPrepareRequired, /*votePrepared=*/ true);
964                         }
965                     }
966 
967                     if (p1BeforePSPE > 0)
968                     {
969                         for (int i = 0; i < p1BeforePSPE; i++)
970                         {
971                             vols[p0BeforePSPE + i] = CreateVolatileEnlistment(enlistmentDoneEvts[p0BeforePSPE + i], null, EnlistmentOptions.None, /*votePrepared=*/ true);
972                         }
973                     }
974 
975                     enlistment = CreatePSPEEnlistment(NonMsdtcPromoterTests.PromoterType1,
976                         NonMsdtcPromoterTests.PromotedToken1,
977                         completedEvent,
978                         /*nonMSDTC = */ true,
979                         /*tx = */ null,
980                         spcResponse,
981                         /*expectRejection=*/ false
982                         );
983 
984                     if (p0AfterPSPE > 0)
985                     {
986                         for (int i = 0; i < p0AfterPSPE; i++)
987                         {
988                             vols[p0BeforePSPE + p1BeforePSPE + i] = CreateVolatileEnlistment(
989                                 enlistmentDoneEvts[p0BeforePSPE + p1BeforePSPE + i],
990                                 null, EnlistmentOptions.EnlistDuringPrepareRequired, /*votePrepared=*/ true);
991                         }
992                     }
993 
994                     if (p1AfterPSPE > 0)
995                     {
996                         for (int i = 0; i < p1AfterPSPE; i++)
997                         {
998                             vols[p0BeforePSPE + p1BeforePSPE + p0AfterPSPE + i] = CreateVolatileEnlistment(
999                                 enlistmentDoneEvts[p0BeforePSPE + p1BeforePSPE + p0AfterPSPE + i],
1000                                 null, EnlistmentOptions.None, /*votePrepared=*/ true);
1001                         }
1002                     }
1003 
1004                     if (promote)
1005                     {
1006                         Promote(testCaseDescription, NonMsdtcPromoterTests.PromotedToken1);
1007 
1008                         if (p0AfterPromote > 0)
1009                         {
1010                             for (int i = 0; i < p0AfterPromote; i++)
1011                             {
1012                                 vols[p0BeforePSPE + p1BeforePSPE + p0AfterPSPE + p1AfterPSPE + i] = CreateVolatileEnlistment(
1013                                     enlistmentDoneEvts[p0BeforePSPE + p1BeforePSPE + p0AfterPSPE + p1AfterPSPE + i],
1014                                     null, EnlistmentOptions.EnlistDuringPrepareRequired, /*votePrepared=*/ true);
1015                             }
1016                         }
1017 
1018                         if (p1AfterPromote > 0)
1019                         {
1020                             for (int i = 0; i < p1AfterPromote; i++)
1021                             {
1022                                 vols[p0BeforePSPE + p1BeforePSPE + p0AfterPSPE + p1AfterPSPE + p0AfterPromote + i] = CreateVolatileEnlistment(
1023                                     enlistmentDoneEvts[p0BeforePSPE + p1BeforePSPE + p0AfterPSPE + p1AfterPSPE + p0AfterPromote + i],
1024                                     null, EnlistmentOptions.None, /*votePrepared=*/ true);
1025                             }
1026                         }
1027                     }
1028 
1029                     if (commit)
1030                     {
1031                         ts.Complete();
1032                     }
1033                 }
1034             }
1035             catch (Exception ex)
1036             {
1037                 TransactionAbortedException abortedEx = ex as TransactionAbortedException;
1038                 if ((abortedEx != null) && (spcResponse != TransactionStatus.Aborted))
1039                 {
1040                     Assert.Equal(spcResponse, TransactionStatus.Aborted);
1041                 }
1042 
1043                 TransactionInDoubtException indoubtEx = ex as TransactionInDoubtException;
1044                 if ((indoubtEx != null) && (spcResponse != TransactionStatus.InDoubt))
1045                 {
1046                     Assert.Equal(spcResponse, TransactionStatus.InDoubt);
1047                 }
1048 
1049                 if (spcResponse == TransactionStatus.Committed)
1050                 {
1051                     Trace(string.Format("Caught unexpected exception {0}:{1}", ex.GetType().ToString(), ex.ToString()));
1052                     return;
1053                 }
1054             }
1055 
1056             NonMSDTCPromoterEnlistment nonDtcEnlistment = enlistment as NonMSDTCPromoterEnlistment;
1057             Assert.NotNull(nonDtcEnlistment);
1058 
1059             if (numVolatiles > 0)
1060             {
1061                 for (int i = 0; i < numVolatiles; i++)
1062                 {
1063                     Assert.True(enlistmentDoneEvts[i].WaitOne(TimeSpan.FromSeconds(5)));
1064                 }
1065 
1066                 int passCount = 0;
1067                 for (int i = 0; i < numVolatiles; i++)
1068                 {
1069                     if (commit)
1070                     {
1071                         if (((spcResponse == TransactionStatus.Committed) && vols[i].CommittedOutcome) ||
1072                             ((spcResponse == TransactionStatus.Aborted) && vols[i].AbortedOutcome) ||
1073                             ((spcResponse == TransactionStatus.InDoubt) && vols[i].InDoubtOutcome)
1074                            )
1075                         {
1076                             passCount++;
1077                         }
1078                     }
1079                     else
1080                     {
1081                         if (vols[i].AbortedOutcome)
1082                         {
1083                             passCount++;
1084                         }
1085                     }
1086                 }
1087                 Assert.Equal(numVolatiles, passCount);
1088             }
1089 
1090             Assert.True(completedEvent.WaitOne(TimeSpan.FromSeconds(5)));
1091 
1092             Assert.False(!promote && nonDtcEnlistment.Promoted);
1093 
1094             Assert.False(promote && !nonDtcEnlistment.Promoted);
1095 
1096             if (commit)
1097             {
1098                 Assert.False((spcResponse == TransactionStatus.Committed) && (nonDtcEnlistment.Aborted));
1099                 Assert.Equal(spcResponse, savedTransaction.TransactionInformation.Status);
1100             }
1101             else
1102             {
1103                 Assert.True(nonDtcEnlistment.Aborted);
1104                 Assert.Equal(TransactionStatus.Aborted, savedTransaction.TransactionInformation.Status);
1105             }
1106 
1107             TestPassed();
1108         }
1109 
TestCase_PSPENonMsdtcWithClones( bool commit, bool promote, TransactionStatus spcResponse, int abortingBeforePSPE = 0, int abortingAfterPSPE = 0, int blockingBeforePSPE = 0, int blockingAfterPSPE = 0, int abortingAfterPromote = 0, int blockingAfterPromote = 0)1110         private static void TestCase_PSPENonMsdtcWithClones(
1111             bool commit,
1112             bool promote,
1113             TransactionStatus spcResponse,
1114             int abortingBeforePSPE = 0,
1115             int abortingAfterPSPE = 0,
1116             int blockingBeforePSPE = 0,
1117             int blockingAfterPSPE = 0,
1118             int abortingAfterPromote = 0,
1119             int blockingAfterPromote = 0)
1120         {
1121             string testCaseDescription = string.Format(
1122                 "TestCase_PSPENonMsdtcWithClones commit={0}; promote={1}; spcResponse= {2}; abortingBeforePSPE={3}; abortingAfterPSPE={4}; blockingBeforePSPE={5}; blockingAfterPSPE={6}; abortingAfterPromote={7}; blockingAfterPromote={8}",
1123                 commit,
1124                 promote,
1125                 spcResponse,
1126                 abortingBeforePSPE,
1127                 abortingAfterPSPE,
1128                 blockingBeforePSPE,
1129                 blockingAfterPSPE,
1130                 abortingAfterPromote,
1131                 blockingAfterPromote);
1132 
1133             Trace("**** " + testCaseDescription + " ****");
1134 
1135             // It doesn't make sense to have "AfterPromote" enlistments if we aren't going to promote the transaction.
1136             if (!promote)
1137             {
1138                 if ((abortingAfterPromote > 0) || (blockingAfterPromote > 0))
1139                 {
1140                     Trace("Not promoting - Resetting abortingAfterPromote and blockingAfterPromote to 0.");
1141                     abortingAfterPromote = 0;
1142                     blockingAfterPromote = 0;
1143                 }
1144             }
1145 
1146             AutoResetEvent completedEvent = new AutoResetEvent(false);
1147 
1148             IPromotableSinglePhaseNotification enlistment = null;
1149 
1150             int numClones = abortingBeforePSPE + abortingAfterPSPE + blockingBeforePSPE + blockingAfterPSPE + abortingAfterPromote + blockingAfterPromote;
1151 
1152 
1153             DependentTransaction[] clones = new DependentTransaction[numClones];
1154 
1155             try
1156             {
1157                 using (TransactionScope ts = new TransactionScope())
1158                 {
1159                     if (abortingBeforePSPE > 0)
1160                     {
1161                         for (int i = 0; i < abortingBeforePSPE; i++)
1162                         {
1163                             clones[i] = CreateDependentClone(/*blocking=*/false);
1164                         }
1165                     }
1166 
1167                     if (blockingBeforePSPE > 0)
1168                     {
1169                         for (int i = 0; i < blockingBeforePSPE; i++)
1170                         {
1171                             clones[abortingBeforePSPE + i] = CreateDependentClone(/*blocking=*/true);
1172                         }
1173                     }
1174 
1175                     enlistment = CreatePSPEEnlistment(NonMsdtcPromoterTests.PromoterType1,
1176                         NonMsdtcPromoterTests.PromotedToken1,
1177                         completedEvent,
1178                         /*nonMSDTC = */ true,
1179                         /*tx = */ null,
1180                         spcResponse,
1181                         /*expectRejection=*/ false
1182                         );
1183 
1184                     if (abortingAfterPSPE > 0)
1185                     {
1186                         for (int i = 0; i < abortingAfterPSPE; i++)
1187                         {
1188                             clones[abortingBeforePSPE + blockingBeforePSPE + i] = CreateDependentClone(/*blocking=*/false);
1189                         }
1190                     }
1191 
1192                     if (blockingAfterPSPE > 0)
1193                     {
1194                         for (int i = 0; i < blockingAfterPSPE; i++)
1195                         {
1196                             clones[abortingBeforePSPE + blockingBeforePSPE + abortingAfterPSPE + i] = CreateDependentClone(/*blocking=*/true);
1197                         }
1198                     }
1199 
1200                     if (promote)
1201                     {
1202                         Promote(testCaseDescription, NonMsdtcPromoterTests.PromotedToken1);
1203 
1204                         if (abortingAfterPromote > 0)
1205                         {
1206                             for (int i = 0; i < abortingAfterPromote; i++)
1207                             {
1208                                 clones[abortingBeforePSPE + blockingBeforePSPE + abortingAfterPSPE + blockingAfterPSPE + i] = CreateDependentClone(/*blocking=*/false);
1209                             }
1210                         }
1211 
1212                         if (blockingAfterPromote > 0)
1213                         {
1214                             for (int i = 0; i < blockingAfterPromote; i++)
1215                             {
1216                                 clones[abortingBeforePSPE + blockingBeforePSPE + abortingAfterPSPE + blockingAfterPSPE + abortingAfterPromote + i] = CreateDependentClone(/*blocking=*/true);
1217                             }
1218                         }
1219                     }
1220 
1221                     // Complete all the clones
1222                     for (int i = 0; i < numClones; i++)
1223                     {
1224                         clones[i].Complete();
1225                     }
1226 
1227                     if (commit)
1228                     {
1229                         ts.Complete();
1230                     }
1231                 }
1232             }
1233             catch (Exception ex)
1234             {
1235                 TransactionAbortedException abortedEx = ex as TransactionAbortedException;
1236                 if ((abortedEx != null) && (spcResponse != TransactionStatus.Aborted))
1237                 {
1238                     Assert.Equal(spcResponse, TransactionStatus.Aborted);
1239                 }
1240 
1241                 TransactionInDoubtException indoubtEx = ex as TransactionInDoubtException;
1242                 if ((indoubtEx != null) && (spcResponse != TransactionStatus.InDoubt))
1243                 {
1244                     Assert.Equal(spcResponse, TransactionStatus.InDoubt);
1245                 }
1246 
1247                 Assert.NotEqual(spcResponse, TransactionStatus.Committed);
1248             }
1249 
1250             TestPassed();
1251         }
1252 
TestCase_AbortFromVolatile(bool promote, EnlistmentOptions enlistmentOptions = EnlistmentOptions.None)1253         public static void TestCase_AbortFromVolatile(bool promote, EnlistmentOptions enlistmentOptions = EnlistmentOptions.None)
1254         {
1255             string testCaseDescription = string.Format(
1256                 "TestCase_AbortFromVolatile promote={0}; enlistmentOptions = {1}",
1257                 promote,
1258                 enlistmentOptions.ToString()
1259                 );
1260 
1261             Trace("**** " + testCaseDescription + " ****");
1262 
1263             AutoResetEvent volCompleted = new AutoResetEvent(false);
1264             AutoResetEvent pspeCompleted = new AutoResetEvent(false);
1265             MyEnlistment vol = null;
1266             NonMSDTCPromoterEnlistment pspe = null;
1267 
1268             Assert.Throws<TransactionAbortedException>(() =>
1269             {
1270                 using (TransactionScope ts = new TransactionScope())
1271                 {
1272                     vol = CreateVolatileEnlistment(volCompleted, null, enlistmentOptions, false);
1273 
1274                     pspe = (NonMSDTCPromoterEnlistment)CreatePSPEEnlistment(NonMsdtcPromoterTests.PromoterType1,
1275                         NonMsdtcPromoterTests.PromotedToken1,
1276                         pspeCompleted,
1277                         /*nonMSDTC = */ true,
1278                         /*tx = */ null,
1279                         /*spcResponse=*/ TransactionStatus.Committed,
1280                         /*expectRejection=*/ false
1281                         );
1282 
1283                     if (promote)
1284                     {
1285                         Promote(testCaseDescription, NonMsdtcPromoterTests.PromotedToken1);
1286                     }
1287 
1288                     ts.Complete();
1289                 }
1290             });
1291 
1292             Assert.True(volCompleted.WaitOne(TimeSpan.FromSeconds(5)) && pspeCompleted.WaitOne(TimeSpan.FromSeconds(5)));
1293 
1294             Assert.True(vol.AbortedOutcome);
1295 
1296             if (promote)
1297             {
1298                 Assert.True(pspe.Promoted);
1299             }
1300             else
1301             {
1302                 Assert.False(pspe.Promoted);
1303             }
1304 
1305             Assert.True(pspe.Aborted);
1306 
1307             TestPassed();
1308         }
1309 
TestCase_AbortingCloneNotCompleted(bool promote)1310         public static void TestCase_AbortingCloneNotCompleted(bool promote)
1311         {
1312             string testCaseDescription = string.Format(
1313                 "TestCase_AbortingCloneNotCompleted promote={0}",
1314                 promote
1315                 );
1316 
1317             Trace("**** " + testCaseDescription + " ****");
1318 
1319             AutoResetEvent pspeCompleted = new AutoResetEvent(false);
1320             NonMSDTCPromoterEnlistment pspe = null;
1321 
1322             try
1323             {
1324                 using (TransactionScope ts = new TransactionScope())
1325                 {
1326                     CreateDependentClone(/*blocking=*/false);
1327                     pspe = (NonMSDTCPromoterEnlistment)CreatePSPEEnlistment(NonMsdtcPromoterTests.PromoterType1,
1328                         NonMsdtcPromoterTests.PromotedToken1,
1329                         pspeCompleted,
1330                         /*nonMSDTC = */ true,
1331                         /*tx = */ null,
1332                         /*spcResponse=*/ TransactionStatus.Committed,
1333                         /*expectRejection=*/ false
1334                         );
1335 
1336                     if (promote)
1337                     {
1338                         Promote(testCaseDescription, NonMsdtcPromoterTests.PromotedToken1);
1339                     }
1340 
1341                     ts.Complete();
1342                 }
1343             }
1344             catch (Exception ex)
1345             {
1346                 Assert.IsType<TransactionAbortedException>(ex);
1347             }
1348 
1349             Assert.True(pspeCompleted.WaitOne(TimeSpan.FromSeconds(5)));
1350 
1351             if (promote)
1352             {
1353                 Assert.True(pspe.Promoted);
1354             }
1355             else
1356             {
1357                 Assert.False(pspe.Promoted);
1358             }
1359 
1360             Assert.True(pspe.Aborted);
1361 
1362             TestPassed();
1363         }
1364 
TestCase_BlockingCloneCompletedAfterCommit(bool promote)1365         public static void TestCase_BlockingCloneCompletedAfterCommit(bool promote)
1366         {
1367             string testCaseDescription = string.Format(
1368                 "TestCase_BlockingCloneCompletedAfterCommit promote={0}",
1369                 promote
1370                 );
1371 
1372             Trace("**** " + testCaseDescription + " ****");
1373 
1374             AutoResetEvent pspeCompleted = new AutoResetEvent(false);
1375             NonMSDTCPromoterEnlistment pspe = null;
1376 
1377             NoStressTrace(string.Format("There will be a 2 second delay here - {0}", DateTime.Now.ToString()));
1378 
1379             try
1380             {
1381                 using (TransactionScope ts = new TransactionScope())
1382                 {
1383                     DependentTransaction clone = CreateDependentClone(/*blocking=*/true);
1384 
1385                     Task.Run(() => CompleteDependentCloneThread(clone));
1386 
1387                     pspe = (NonMSDTCPromoterEnlistment)CreatePSPEEnlistment(NonMsdtcPromoterTests.PromoterType1,
1388                         NonMsdtcPromoterTests.PromotedToken1,
1389                         pspeCompleted,
1390                         /*nonMSDTC = */ true,
1391                         /*tx = */ null,
1392                         /*spcResponse=*/ TransactionStatus.Committed,
1393                         /*expectRejection=*/ false
1394                         );
1395 
1396                     if (promote)
1397                     {
1398                         Promote(testCaseDescription, NonMsdtcPromoterTests.PromotedToken1);
1399                     }
1400 
1401                     ts.Complete();
1402                 }
1403             }
1404             catch (Exception ex)
1405             {
1406                 Assert.Null(ex);
1407             }
1408 
1409             Assert.True(pspeCompleted.WaitOne(TimeSpan.FromSeconds(2)));
1410 
1411             if (promote)
1412             {
1413                 Assert.True(pspe.Promoted);
1414             }
1415             else
1416             {
1417                 Assert.False(pspe.Promoted);
1418             }
1419 
1420             Assert.False(pspe.Aborted);
1421 
1422             TestPassed(true);
1423         }
1424 
TestCase_TransactionTimeout(bool promote)1425         public static void TestCase_TransactionTimeout(bool promote)
1426         {
1427             string testCaseDescription = string.Format(
1428                 "TestCase_TransactionTimeout promote={0}",
1429                 promote
1430                 );
1431 
1432             Trace("**** " + testCaseDescription + " ****");
1433 
1434             AutoResetEvent pspeCompleted = new AutoResetEvent(false);
1435             NonMSDTCPromoterEnlistment pspe = null;
1436 
1437             Assert.Throws<TransactionAbortedException>(() =>
1438             {
1439                 CommittableTransaction tx = new CommittableTransaction(TimeSpan.FromSeconds(1));
1440 
1441                 pspe = (NonMSDTCPromoterEnlistment)CreatePSPEEnlistment(NonMsdtcPromoterTests.PromoterType1,
1442                     NonMsdtcPromoterTests.PromotedToken1,
1443                     pspeCompleted,
1444                     /*nonMSDTC = */ true,
1445                     tx,
1446                     /*spcResponse=*/ TransactionStatus.Committed,
1447                     /*expectRejection=*/ false
1448                     );
1449 
1450                 if (promote)
1451                 {
1452                     Promote(testCaseDescription, NonMsdtcPromoterTests.PromotedToken1, tx);
1453                 }
1454 
1455                 NoStressTrace(string.Format("There will be a 3 second delay here - {0}", DateTime.Now.ToString()));
1456 
1457                 Task.Delay(TimeSpan.FromSeconds(3)).Wait();
1458 
1459                 NoStressTrace(string.Format("Woke up from sleep. Attempting Commit - {0}", DateTime.Now.ToString()));
1460 
1461                 tx.Commit();
1462             });
1463 
1464             Assert.True(pspeCompleted.WaitOne(TimeSpan.FromSeconds(5)));
1465 
1466             if (promote)
1467             {
1468                 Assert.True(pspe.Promoted);
1469             }
1470             else
1471             {
1472                 Assert.False(pspe.Promoted);
1473             }
1474 
1475             Assert.True(pspe.Aborted);
1476 
1477             TestPassed(true);
1478         }
1479 
TestCase_EnlistDuringPrepare(bool promote, bool beforePromote, EnlistmentOptions firstOptions = EnlistmentOptions.None, EnlistmentOptions secondOptions = EnlistmentOptions.None, bool expectSecondEnlistSuccess = true )1480         public static void TestCase_EnlistDuringPrepare(bool promote,
1481             bool beforePromote,
1482             EnlistmentOptions firstOptions = EnlistmentOptions.None,
1483             EnlistmentOptions secondOptions = EnlistmentOptions.None,
1484             bool expectSecondEnlistSuccess = true
1485             )
1486         {
1487             string testCaseDescription = string.Format(
1488                 "TestCase_EnlistDuringPrepare promote={0}; beforePromote={1}, firstOptions={2}, secondOptions={3}, expectSecondEnlistSuccess={4}",
1489                 promote,
1490                 beforePromote,
1491                 firstOptions.ToString(),
1492                 secondOptions.ToString(),
1493                 expectSecondEnlistSuccess
1494                 );
1495 
1496             Trace("**** " + testCaseDescription + " ****");
1497 
1498             AutoResetEvent volCompleted = new AutoResetEvent(false);
1499             AutoResetEvent vol2Completed = new AutoResetEvent(false);
1500             AutoResetEvent pspeCompleted = new AutoResetEvent(false);
1501             MyEnlistment vol = null;
1502             NonMSDTCPromoterEnlistment pspe = null;
1503 
1504             try
1505             {
1506                 using (TransactionScope ts = new TransactionScope())
1507                 {
1508                     if (beforePromote)
1509                     {
1510                         vol = new MyEnlistment(
1511                             volCompleted,
1512                             true,
1513                             true,
1514                             secondOptions,
1515                             /*expectSuccessfulEnlist=*/ expectSecondEnlistSuccess,
1516                             vol2Completed);
1517                         vol.TransactionToEnlist = Transaction.Current;
1518                         Transaction.Current.EnlistVolatile(vol, firstOptions);
1519                     }
1520 
1521                     pspe = (NonMSDTCPromoterEnlistment)CreatePSPEEnlistment(NonMsdtcPromoterTests.PromoterType1,
1522                         NonMsdtcPromoterTests.PromotedToken1,
1523                         pspeCompleted,
1524                         /*nonMSDTC = */ true,
1525                         /*tx = */ null,
1526                         /*spcResponse=*/ TransactionStatus.Committed,
1527                         /*expectRejection=*/ false
1528                         );
1529 
1530                     if (promote)
1531                     {
1532                         Promote(testCaseDescription, NonMsdtcPromoterTests.PromotedToken1);
1533 
1534                         if (!beforePromote)
1535                         {
1536                             vol = new MyEnlistment(
1537                                 volCompleted,
1538                                 true,
1539                                 true,
1540                                 secondOptions,
1541                                 /*expectSuccessfulEnlist=*/ expectSecondEnlistSuccess,
1542                                 vol2Completed);
1543                             vol.TransactionToEnlist = Transaction.Current;
1544                             Transaction.Current.EnlistVolatile(vol, firstOptions);
1545                         }
1546                     }
1547 
1548                     ts.Complete();
1549                 }
1550             }
1551             catch (Exception ex)
1552             {
1553                 Assert.Null(ex);
1554             }
1555 
1556             if (!expectSecondEnlistSuccess)
1557             {
1558                 vol2Completed.Set();
1559             }
1560 
1561             Assert.True(volCompleted.WaitOne(TimeSpan.FromSeconds(5)) && vol2Completed.WaitOne(TimeSpan.FromSeconds(5)) &&
1562                 pspeCompleted.WaitOne(TimeSpan.FromSeconds(5)));
1563 
1564             Assert.False(vol.AbortedOutcome);
1565 
1566             if (promote)
1567             {
1568                 Assert.True(pspe.Promoted);
1569             }
1570             else
1571             {
1572                 Assert.False(pspe.Promoted);
1573             }
1574 
1575             Assert.False(pspe.Aborted);
1576 
1577             TestPassed();
1578         }
1579 
TestCase_GetStatusAndDistributedId()1580         public static void TestCase_GetStatusAndDistributedId()
1581         {
1582             string testCaseDescription = "TestCase_GetDistributedId";
1583 
1584             Trace("**** " + testCaseDescription + " ****");
1585 
1586             AutoResetEvent pspeCompleted = new AutoResetEvent(false);
1587             NonMSDTCPromoterEnlistment pspe = null;
1588 
1589             try
1590             {
1591                 using (TransactionScope ts = new TransactionScope())
1592                 {
1593                     TransactionStatus txStatus = Transaction.Current.TransactionInformation.Status;
1594                     Assert.Equal(TransactionStatus.Active, txStatus);
1595 
1596                     Guid distId = Transaction.Current.TransactionInformation.DistributedIdentifier;
1597                     Assert.Equal(Guid.Empty, distId);
1598 
1599                     pspe = (NonMSDTCPromoterEnlistment)CreatePSPEEnlistment(NonMsdtcPromoterTests.PromoterType1,
1600                         NonMsdtcPromoterTests.PromotedToken1,
1601                         pspeCompleted,
1602                         /*nonMSDTC = */ true,
1603                         /*tx = */ null,
1604                         /*spcResponse=*/ TransactionStatus.Committed,
1605                         /*expectRejection=*/ false
1606                         );
1607 
1608                     txStatus = Transaction.Current.TransactionInformation.Status;
1609                     Assert.Equal(TransactionStatus.Active, txStatus);
1610 
1611                     distId = Transaction.Current.TransactionInformation.DistributedIdentifier;
1612                     Assert.Equal(Guid.Empty, distId);
1613 
1614                     Promote(testCaseDescription, NonMsdtcPromoterTests.PromotedToken1);
1615 
1616                     txStatus = Transaction.Current.TransactionInformation.Status;
1617                     Assert.Equal(TransactionStatus.Active, txStatus);
1618 
1619                     distId = Transaction.Current.TransactionInformation.DistributedIdentifier;
1620                     Assert.NotEqual(distId, Guid.Empty);
1621                     ts.Complete();
1622                 }
1623 
1624                 TestPassed();
1625             }
1626             catch (Exception ex)
1627             {
1628                 Assert.Null(ex);
1629             }
1630         }
1631 
TestCase_DisposeCommittableTransaction(bool promote)1632         public static void TestCase_DisposeCommittableTransaction(bool promote)
1633         {
1634             string testCaseDescription = string.Format(
1635                 "TestCase_DisposeCommittableTransaction promote={0}",
1636                 promote
1637                 );
1638 
1639             Trace("**** " + testCaseDescription + " ****");
1640 
1641             AutoResetEvent pspeCompleted = new AutoResetEvent(false);
1642             NonMSDTCPromoterEnlistment pspe = null;
1643             Transaction savedTransaction = null;
1644 
1645             try
1646             {
1647                 CommittableTransaction tx = new CommittableTransaction(TimeSpan.FromMinutes(1));
1648                 savedTransaction = tx.Clone();
1649 
1650                 pspe = (NonMSDTCPromoterEnlistment)CreatePSPEEnlistment(NonMsdtcPromoterTests.PromoterType1,
1651                     NonMsdtcPromoterTests.PromotedToken1,
1652                     pspeCompleted,
1653                     /*nonMSDTC = */ true,
1654                     tx,
1655                     /*spcResponse=*/ TransactionStatus.Committed,
1656                     /*expectRejection=*/ false
1657                     );
1658 
1659                 if (promote)
1660                 {
1661                     Promote(testCaseDescription, NonMsdtcPromoterTests.PromotedToken1, tx);
1662                 }
1663 
1664                 tx.Dispose();
1665 
1666                 tx.Commit();
1667             }
1668             catch (Exception ex)
1669             {
1670                 Assert.IsType<ObjectDisposedException>(ex);
1671             }
1672 
1673             Assert.True(pspeCompleted.WaitOne(TimeSpan.FromSeconds(5)));
1674 
1675             if (promote)
1676             {
1677                 Assert.True(pspe.Promoted);
1678             }
1679             else
1680             {
1681                 Assert.False(pspe.Promoted);
1682             }
1683 
1684             Assert.True(pspe.Aborted);
1685 
1686             Assert.Equal(TransactionStatus.Aborted, savedTransaction.TransactionInformation.Status);
1687 
1688             TestPassed();
1689         }
1690 
TestCase_OutcomeRegistration(bool promote)1691         public static void TestCase_OutcomeRegistration(bool promote)
1692         {
1693             string testCaseDescription = string.Format(
1694                 "TestCase_OutcomeRegistration promote={0}",
1695                 promote
1696                 );
1697 
1698             Trace("**** " + testCaseDescription + " ****");
1699 
1700             AutoResetEvent pspeCompleted = new AutoResetEvent(false);
1701             NonMSDTCPromoterEnlistment pspe = null;
1702             int numberOfCompletions = 0;
1703             CommittableTransaction tx = null;
1704 
1705             try
1706             {
1707                 tx = new CommittableTransaction(TimeSpan.FromSeconds(5));
1708 
1709                 tx.TransactionCompleted += delegate (object sender, TransactionEventArgs completedArgs)
1710                 {
1711                     Trace("Completed event registered before PSPE");
1712                     numberOfCompletions++;
1713                     Assert.Equal(TransactionStatus.Committed, completedArgs.Transaction.TransactionInformation.Status);
1714                 };
1715 
1716                 pspe = (NonMSDTCPromoterEnlistment)CreatePSPEEnlistment(NonMsdtcPromoterTests.PromoterType1,
1717                     NonMsdtcPromoterTests.PromotedToken1,
1718                     pspeCompleted,
1719                     /*nonMSDTC = */ true,
1720                     tx,
1721                     /*spcResponse=*/ TransactionStatus.Committed,
1722                     /*expectRejection=*/ false
1723                     );
1724 
1725                 tx.TransactionCompleted += delegate (object sender, TransactionEventArgs completedArgs)
1726                 {
1727                     Trace("Completed event registered after PSPE");
1728                     numberOfCompletions++;
1729                     Assert.Equal(TransactionStatus.Committed, completedArgs.Transaction.TransactionInformation.Status);
1730                 };
1731 
1732                 if (promote)
1733                 {
1734                     Promote(testCaseDescription, NonMsdtcPromoterTests.PromotedToken1, tx);
1735 
1736                     tx.TransactionCompleted += delegate (object sender, TransactionEventArgs completedArgs)
1737                     {
1738                         Trace("Completed event registered after promote");
1739                         numberOfCompletions++;
1740                         Assert.Equal(TransactionStatus.Committed, completedArgs.Transaction.TransactionInformation.Status);
1741                     };
1742                 }
1743 
1744                 tx.Commit();
1745             }
1746             catch (Exception ex)
1747             {
1748                 Assert.Null(ex);
1749             }
1750 
1751             tx.TransactionCompleted += delegate (object sender, TransactionEventArgs completedArgs)
1752             {
1753                 Trace("Completed event registered after commit");
1754                 numberOfCompletions++;
1755                 Assert.Equal(TransactionStatus.Committed, completedArgs.Transaction.TransactionInformation.Status);
1756             };
1757 
1758             Assert.True(pspeCompleted.WaitOne(TimeSpan.FromSeconds(5)));
1759 
1760             if (promote)
1761             {
1762                 Assert.True(pspe.Promoted);
1763             }
1764             else
1765             {
1766                 Assert.False(pspe.Promoted);
1767             }
1768 
1769             Assert.Equal((promote ? 4 : 3), numberOfCompletions);
1770 
1771             TestPassed();
1772         }
1773 
TestCase_PromoterType()1774         public static void TestCase_PromoterType()
1775         {
1776             string testCaseDescription = "TestCase_PromoterType";
1777 
1778             Trace("**** " + testCaseDescription + " ****");
1779 
1780             AutoResetEvent pspeCompleted = new AutoResetEvent(false);
1781             NonMSDTCPromoterEnlistment pspe = null;
1782 
1783             try
1784             {
1785                 using (TransactionScope ts = new TransactionScope())
1786                 {
1787                     Assert.Equal(Guid.Empty, TxPromoterType(Transaction.Current));
1788 
1789                     pspe = (NonMSDTCPromoterEnlistment)CreatePSPEEnlistment(NonMsdtcPromoterTests.PromoterType1,
1790                         NonMsdtcPromoterTests.PromotedToken1,
1791                         pspeCompleted,
1792                         /*nonMSDTC = */ true,
1793                         /*tx = */ null,
1794                         /*spcResponse=*/ TransactionStatus.Committed,
1795                         /*expectRejection=*/ false
1796                         );
1797 
1798                     Assert.Equal(NonMsdtcPromoterTests.PromoterType1, TxPromoterType(Transaction.Current));
1799 
1800                     Promote(testCaseDescription, NonMsdtcPromoterTests.PromotedToken1);
1801 
1802                     Assert.Equal(NonMsdtcPromoterTests.PromoterType1, TxPromoterType(Transaction.Current));
1803 
1804                     ts.Complete();
1805                 }
1806             }
1807             catch (Exception ex)
1808             {
1809                 Assert.Null(ex);
1810             }
1811 
1812             Assert.True(pspeCompleted.WaitOne(TimeSpan.FromSeconds(5)));
1813 
1814             Assert.True(pspe.Promoted);
1815 
1816             TestPassed();
1817         }
1818 
TestCase_PromoterTypeMSDTC()1819         public static void TestCase_PromoterTypeMSDTC()
1820         {
1821             string testCaseDescription = "TestCase_PromoterTypeMSDTC";
1822 
1823             Trace("**** " + testCaseDescription + " ****");
1824 
1825             AutoResetEvent volCompleted = new AutoResetEvent(false);
1826             MyEnlistment vol = null;
1827 
1828             try
1829             {
1830                 using (TransactionScope ts = new TransactionScope())
1831                 {
1832                     Assert.Equal(Guid.Empty, TxPromoterType(Transaction.Current));
1833 
1834                     vol = CreateVolatileEnlistment(volCompleted);
1835 
1836                     // Force MSDTC promotion.
1837                     TransactionInterop.GetDtcTransaction(Transaction.Current);
1838 
1839                     // TransactionInterop.PromoterTypeDtc
1840                     Assert.Equal(PromoterTypeDtc, TxPromoterType(Transaction.Current));
1841 
1842                     ts.Complete();
1843                 }
1844             }
1845             catch (Exception ex)
1846             {
1847                 Trace(string.Format("Caught unexpected exception {0}:{1}", ex.GetType().ToString(), ex.ToString()));
1848                 return;
1849             }
1850 
1851             Assert.True(volCompleted.WaitOne(TimeSpan.FromSeconds(5)));
1852 
1853             Assert.True(vol.CommittedOutcome);
1854 
1855             TestPassed();
1856         }
1857 
TestCase_FailPromotableSinglePhaseNotificationCalls()1858         public static void TestCase_FailPromotableSinglePhaseNotificationCalls()
1859         {
1860             string testCaseDescription = "TestCase_FailPromotableSinglePhaseNotificationCalls";
1861 
1862             Trace("**** " + testCaseDescription + " ****");
1863 
1864             AutoResetEvent pspeCompleted = new AutoResetEvent(false);
1865             NonMSDTCPromoterEnlistment pspe = null;
1866 
1867             Trace("Fail Initialize");
1868             try
1869             {
1870                 using (TransactionScope ts = new TransactionScope())
1871                 {
1872                     pspe = (NonMSDTCPromoterEnlistment)CreatePSPEEnlistment(NonMsdtcPromoterTests.PromoterType1,
1873                         NonMsdtcPromoterTests.PromotedToken1,
1874                         pspeCompleted,
1875                         /*nonMSDTC = */ true,
1876                         /*tx = */ null,
1877                         /*spcResponse=*/ TransactionStatus.Committed,
1878                         /*expectRejection=*/ false,
1879                         /*comparePromotedToken=*/ false,
1880                         /*failInitialize=*/ true,
1881                         /*failPromote=*/ false,
1882                         /*failSPC=*/ false,
1883                         /*failGetPromoterType=*/ false,
1884                         /*failGetId=*/ false
1885                         );
1886                     bool shouldNotBeExecuted = true;
1887                     Assert.False(shouldNotBeExecuted);
1888                 }
1889             }
1890             catch (Exception ex)
1891             {
1892                 Assert.True(ex is ApplicationException || (ex is TargetInvocationException && ex.InnerException is ApplicationException));
1893             }
1894 
1895             Trace("Fail Promote");
1896             try
1897             {
1898                 using (TransactionScope ts = new TransactionScope())
1899                 {
1900                     pspe = (NonMSDTCPromoterEnlistment)CreatePSPEEnlistment(NonMsdtcPromoterTests.PromoterType1,
1901                         NonMsdtcPromoterTests.PromotedToken1,
1902                         pspeCompleted,
1903                         /*nonMSDTC = */ true,
1904                         /*tx = */ null,
1905                         /*spcResponse=*/ TransactionStatus.Committed,
1906                         /*expectRejection=*/ false,
1907                         /*comparePromotedToken=*/ false,
1908                         /*failInitialize=*/ false,
1909                         /*failPromote=*/ true,
1910                         /*failSPC=*/ false,
1911                         /*failGetPromoterType=*/ false,
1912                         /*failGetId=*/ false
1913                         );
1914 
1915                     Promote(testCaseDescription, NonMsdtcPromoterTests.PromotedToken1);
1916                     ts.Complete();
1917                 }
1918             }
1919             catch (Exception ex)
1920             {
1921                 Assert.True(ex is ApplicationException || (ex is TargetInvocationException && ex.InnerException is ApplicationException));
1922             }
1923 
1924             // The NonMSDTCPromoterEnlistment is coded to set "Promoted" at the beginning of Promote, before
1925             // throwing.
1926             Assert.True(pspeCompleted.WaitOne(TimeSpan.FromSeconds(5)));
1927 
1928             Assert.True(pspe.Promoted);
1929 
1930             Trace("Fail SinglePhaseCommit");
1931             try
1932             {
1933                 using (TransactionScope ts = new TransactionScope())
1934                 {
1935                     pspe = (NonMSDTCPromoterEnlistment)CreatePSPEEnlistment(NonMsdtcPromoterTests.PromoterType1,
1936                         NonMsdtcPromoterTests.PromotedToken1,
1937                         pspeCompleted,
1938                         /*nonMSDTC = */ true,
1939                         /*tx = */ null,
1940                         /*spcResponse=*/ TransactionStatus.Committed,
1941                         /*expectRejection=*/ false,
1942                         /*comparePromotedToken=*/ false,
1943                         /*failInitialize=*/ false,
1944                         /*failPromote=*/ false,
1945                         /*failSPC=*/ true,
1946                         /*failGetPromoterType=*/ false,
1947                         /*failGetId=*/ false
1948                         );
1949 
1950                     Promote(testCaseDescription, NonMsdtcPromoterTests.PromotedToken1);
1951                     ts.Complete();
1952                 }
1953             }
1954             catch (Exception ex)
1955             {
1956                 Assert.True(ex is ApplicationException || (ex is TargetInvocationException && ex.InnerException is ApplicationException));
1957             }
1958 
1959             // The NonMSDTCPromoterEnlistment is coded to set "Promoted" at the beginning of Promote, before
1960             // throwing.
1961             Assert.True(pspeCompleted.WaitOne(TimeSpan.FromSeconds(5)));
1962 
1963             Assert.True(pspe.Promoted);
1964 
1965             TestPassed();
1966         }
1967 
TestCase_SetDistributedIdAtWrongTime()1968         public static void TestCase_SetDistributedIdAtWrongTime()
1969         {
1970             string testCaseDescription = "TestCase_SetDistributedIdAtWrongTime";
1971 
1972             Trace("**** " + testCaseDescription + " ****");
1973 
1974             AutoResetEvent pspeCompleted = new AutoResetEvent(false);
1975             NonMSDTCPromoterEnlistment pspe = null;
1976             NonMSDTCPromoterEnlistment dummyPSPE = new NonMSDTCPromoterEnlistment(NonMsdtcPromoterTests.PromoterType1, NonMsdtcPromoterTests.PromotedToken1, pspeCompleted);
1977 
1978             Guid guidToSet = new Guid("236BC646-FE3B-41F9-99F7-08BF448D8420");
1979 
1980             using (TransactionScope ts = new TransactionScope())
1981             {
1982                 Trace("Before EnlistPromotable");
1983                 Exception ex = Assert.ThrowsAny<Exception>(() => SetDistributedTransactionId(dummyPSPE, Transaction.Current, guidToSet));
1984                 Assert.True(ex is TransactionException || (ex is TargetInvocationException && ex.InnerException is TransactionException));
1985 
1986                 pspe = (NonMSDTCPromoterEnlistment)CreatePSPEEnlistment(NonMsdtcPromoterTests.PromoterType1,
1987                     NonMsdtcPromoterTests.PromotedToken1,
1988                     pspeCompleted,
1989                     /*nonMSDTC = */ true,
1990                     /*tx = */ null,
1991                     /*spcResponse=*/ TransactionStatus.Committed,
1992                     /*expectRejection=*/ false,
1993                     /*comparePromotedToken=*/ false,
1994                     /*failInitialize=*/ false,
1995                     /*failPromote=*/ false,
1996                     /*failSPC=*/ false,
1997                     /*failGetPromoterType=*/ false,
1998                     /*failGetId=*/ false
1999                     );
2000 
2001                 Trace("After EnlistPromotable");
2002                 ex = Assert.ThrowsAny<Exception>(() => SetDistributedTransactionId(dummyPSPE, Transaction.Current, guidToSet));
2003                 Assert.True(ex is TransactionException || (ex is TargetInvocationException && ex.InnerException is TransactionException));
2004 
2005                 Promote(testCaseDescription, NonMsdtcPromoterTests.PromotedToken1);
2006 
2007                 Trace("After Promotion");
2008                 ex = Assert.ThrowsAny<Exception>(() => SetDistributedTransactionId(dummyPSPE, Transaction.Current, guidToSet));
2009                 Assert.True(ex is TransactionException || (ex is TargetInvocationException && ex.InnerException is TransactionException));
2010 
2011                 ts.Complete();
2012             }
2013 
2014             // The NonMSDTCPromoterEnlistment is coded to set "Promoted" at the beginning of Promote, before
2015             // throwing.
2016             Assert.True(pspeCompleted.WaitOne(TimeSpan.FromSeconds(5)));
2017 
2018             Assert.True(pspe.Promoted);
2019 
2020             TestPassed();
2021         }
2022 
TestCase_SetDistributedIdWithWrongNotificationObject()2023         public static void TestCase_SetDistributedIdWithWrongNotificationObject()
2024         {
2025             string testCaseDescription = "TestCase_SetDistributedIdWithWrongNotificationObject";
2026 
2027             Trace("**** " + testCaseDescription + " ****");
2028 
2029             AutoResetEvent pspeCompleted = new AutoResetEvent(false);
2030             NonMSDTCPromoterEnlistment pspe = null;
2031 
2032             Guid guidToSet = new Guid("236BC646-FE3B-41F9-99F7-08BF448D8420");
2033 
2034             try
2035             {
2036                 using (TransactionScope ts = new TransactionScope())
2037                 {
2038                     pspe = (NonMSDTCPromoterEnlistment)CreatePSPEEnlistment(NonMsdtcPromoterTests.PromoterType1,
2039                         NonMsdtcPromoterTests.PromotedToken1,
2040                         pspeCompleted,
2041                         /*nonMSDTC = */ true,
2042                         /*tx = */ null,
2043                         /*spcResponse=*/ TransactionStatus.Committed,
2044                         /*expectRejection=*/ false,
2045                         /*comparePromotedToken=*/ false,
2046                         /*failInitialize=*/ false,
2047                         /*failPromote=*/ false,
2048                         /*failSPC=*/ false,
2049                         /*failGetPromoterType=*/ false,
2050                         /*failGetId=*/ false,
2051                         /*incorrectNotificationObjectToSetDistributedTransactionId=*/ true
2052                         );
2053 
2054                     Promote(testCaseDescription, NonMsdtcPromoterTests.PromotedToken1);
2055 
2056                     ts.Complete();
2057                 }
2058             }
2059             catch (Exception ex)
2060             {
2061                 Assert.Null(ex);
2062             }
2063 
2064             // The NonMSDTCPromoterEnlistment is coded to set "Promoted" at the beginning of Promote, before
2065             // throwing.
2066             Assert.True(pspeCompleted.WaitOne(TimeSpan.FromSeconds(5)));
2067 
2068             Assert.True(pspe.Promoted);
2069 
2070             TestPassed();
2071         }
2072 
2073         #endregion
2074 
2075 
2076         /// <summary>
2077         /// This test case is very basic Volatile Enlistment test.
2078         /// </summary>
2079         [Fact]
VolatileEnlistments()2080         public void VolatileEnlistments()
2081         {
2082             TestCase_VolatileEnlistments(1, TransactionStatus.Committed);
2083             TestCase_VolatileEnlistments(5, TransactionStatus.Committed);
2084             TestCase_VolatileEnlistments(1, TransactionStatus.Aborted, EnlistmentOptions.None, false);
2085             TestCase_VolatileEnlistments(5, TransactionStatus.Aborted, EnlistmentOptions.None, false);
2086             TestCase_VolatileEnlistments(1, TransactionStatus.Aborted, EnlistmentOptions.None, true, false, typeof(TransactionAbortedException));
2087         }
2088 
2089         /// <summary>
2090         /// Tests PSPE Non-MSDTC with volatile enlistments.
2091         /// </summary>
2092         [Theory]
2093         [InlineData(true, false, TransactionStatus.Committed)]
2094         [InlineData(true, true, TransactionStatus.Committed)]
2095         [InlineData(false, false, TransactionStatus.Committed)]
2096         [InlineData(false, true, TransactionStatus.Committed)]
2097         [InlineData(true, false, TransactionStatus.Aborted)]
2098         [InlineData(true, true, TransactionStatus.Aborted)]
2099         [InlineData(true, false, TransactionStatus.InDoubt)]
2100         [InlineData(true, true, TransactionStatus.InDoubt)]
PSPENonMSDTCWithVolatileEnlistments(bool commit, bool promote, TransactionStatus spcResponse)2101         public void PSPENonMSDTCWithVolatileEnlistments(bool commit, bool promote, TransactionStatus spcResponse)
2102         {
2103             TestCase_PSPENonMsdtc(commit, promote, spcResponse);
2104             TestCase_PSPENonMsdtc(commit, promote, spcResponse, 1);
2105             TestCase_PSPENonMsdtc(commit, promote, spcResponse, 1, 1);
2106             TestCase_PSPENonMsdtc(commit, promote, spcResponse, 1, 1, 1);
2107             TestCase_PSPENonMsdtc(commit, promote, spcResponse, 1, 1, 1, 1);
2108         }
2109 
2110         /// <summary>
2111         /// Tests PSPE Non-MSDTC with dependent clones.
2112         /// </summary>
2113         [Theory]
2114         [InlineData(true, false, TransactionStatus.Committed)]
2115         [InlineData(true, true, TransactionStatus.Committed)]
2116         [InlineData(false, false, TransactionStatus.Committed)]
2117         [InlineData(false, true, TransactionStatus.Committed)]
2118         [InlineData(true, false, TransactionStatus.Aborted)]
2119         [InlineData(true, true, TransactionStatus.Aborted)]
2120         [InlineData(true, false, TransactionStatus.InDoubt)]
2121         [InlineData(true, true, TransactionStatus.InDoubt)]
PSPENonMSDTCWithDependentClones(bool commit, bool promote, TransactionStatus spcResponse)2122         public void PSPENonMSDTCWithDependentClones(bool commit, bool promote, TransactionStatus spcResponse)
2123         {
2124             TestCase_PSPENonMsdtcWithClones(commit, promote, spcResponse);
2125             TestCase_PSPENonMsdtcWithClones(commit, promote, spcResponse, 1);
2126             TestCase_PSPENonMsdtcWithClones(commit, promote, spcResponse, 1, 1);
2127             TestCase_PSPENonMsdtcWithClones(commit, promote, spcResponse, 1, 1, 1);
2128             TestCase_PSPENonMsdtcWithClones(commit, promote, spcResponse, 1, 1, 1, 1);
2129         }
2130 
2131         /// <summary>
2132         /// PSPE Non-MSDTC Abort From Volatile.
2133         /// </summary>
2134         [Theory]
2135         [InlineData(false, EnlistmentOptions.EnlistDuringPrepareRequired)]
2136         [InlineData(true, EnlistmentOptions.EnlistDuringPrepareRequired)]
2137         [InlineData(false, EnlistmentOptions.None)]
2138         [InlineData(true, EnlistmentOptions.None)]
PSPENonMsdtcAbortFromVolatile(bool promote, EnlistmentOptions options)2139         public void PSPENonMsdtcAbortFromVolatile(bool promote, EnlistmentOptions options)
2140         {
2141             // Abort from p0 and p1 volatile, not promoted and promoted.
2142             TestCase_AbortFromVolatile(promote, options);
2143         }
2144 
2145         /// <summary>
2146         /// PSPE Non-MSDTC Aborting Clone Not Completed.
2147         /// </summary>
2148         [Theory]
2149         [InlineData(false)]
2150         [InlineData(true)]
PSPENonMsdtcAbortingCloneNotCompleted(bool promote)2151         public void PSPENonMsdtcAbortingCloneNotCompleted(bool promote)
2152         {
2153             // Aborting clone that isn't completed
2154             TestCase_AbortingCloneNotCompleted(promote);
2155         }
2156 
2157         /// <summary>
2158         /// PSPE Non-MSDTC Blocking Clone Completed After Commit.
2159         /// </summary>
2160         [OuterLoop] // long delay
2161         [Theory]
2162         [InlineData(false)]
2163         [InlineData(true)]
PSPENonMsdtcBlockingCloneCompletedAfterCommit(bool promote)2164         public void PSPENonMsdtcBlockingCloneCompletedAfterCommit(bool promote)
2165         {
2166             // Blocking clone that isn't completed before commit
2167             TestCase_BlockingCloneCompletedAfterCommit(promote);
2168         }
2169 
2170         /// <summary>
2171         /// PSPE Non-MSDTC Timeout.
2172         /// </summary>
2173         [OuterLoop] // long timeout
2174         [Theory]
2175         [InlineData(false)]
2176         [InlineData(true)]
PSPENonMsdtcTimeout(bool promote)2177         public void PSPENonMsdtcTimeout(bool promote)
2178         {
2179             // tx timeout
2180             TestCase_TransactionTimeout(promote);
2181         }
2182 
2183         /// <summary>
2184         /// PSPE Non-MSDTC Enlist During Phase 0.
2185         /// </summary>
2186         [Theory]
2187         [InlineData(false, true, EnlistmentOptions.EnlistDuringPrepareRequired, EnlistmentOptions.EnlistDuringPrepareRequired, true)]
2188         [InlineData(false, true, EnlistmentOptions.EnlistDuringPrepareRequired, EnlistmentOptions.None, true)]
2189         [InlineData(true, true, EnlistmentOptions.EnlistDuringPrepareRequired, EnlistmentOptions.EnlistDuringPrepareRequired, true)]
2190         [InlineData(true, true, EnlistmentOptions.EnlistDuringPrepareRequired, EnlistmentOptions.None, true)]
2191         [InlineData(true, false, EnlistmentOptions.EnlistDuringPrepareRequired, EnlistmentOptions.EnlistDuringPrepareRequired, true)]
2192         [InlineData(true, false, EnlistmentOptions.EnlistDuringPrepareRequired, EnlistmentOptions.EnlistDuringPrepareRequired, true)]
2193         [InlineData(false, true, EnlistmentOptions.None, EnlistmentOptions.None, false)]
2194         [InlineData(false, true, EnlistmentOptions.None, EnlistmentOptions.None, false)]
2195         [InlineData(true, true, EnlistmentOptions.None, EnlistmentOptions.EnlistDuringPrepareRequired, false)]
2196         [InlineData(true, true, EnlistmentOptions.None, EnlistmentOptions.None, false)]
2197         [InlineData(true, false, EnlistmentOptions.None, EnlistmentOptions.EnlistDuringPrepareRequired, false)]
2198         [InlineData(true, false, EnlistmentOptions.None, EnlistmentOptions.None, false)]
PSPENonMsdtcEnlistDuringPhase0( bool promote, bool beforePromote, EnlistmentOptions options, EnlistmentOptions secondOptions, bool expectSecondEnlistSuccess)2199         public void PSPENonMsdtcEnlistDuringPhase0(
2200             bool promote, bool beforePromote, EnlistmentOptions options, EnlistmentOptions secondOptions, bool expectSecondEnlistSuccess)
2201         {
2202             TestCase_EnlistDuringPrepare(promote, beforePromote, options, secondOptions, expectSecondEnlistSuccess);
2203         }
2204 
2205         /// <summary>
2206         /// PSPE Non-MSDTC Get Status and Distributed Id.
2207         /// </summary>
2208         [Fact]
PSPENonMsdtcGetStatusAndDistributedId()2209         public void PSPENonMsdtcGetStatusAndDistributedId()
2210         {
2211             // Retrieve the DistributedId before PSPE, before Promote, and after Promote
2212             TestCase_GetStatusAndDistributedId();
2213         }
2214 
2215         /// <summary>
2216         /// PSPE Non-MSDTC Dispose Committable Transaction.
2217         /// </summary>
2218         [Theory]
2219         [InlineData(false)]
2220         [InlineData(true)]
PSPENonMsdtcDisposeCommittable(bool promote)2221         public void PSPENonMsdtcDisposeCommittable(bool promote)
2222         {
2223             // Dispose a committable transaction early.
2224             TestCase_DisposeCommittableTransaction(promote);
2225         }
2226 
2227         /// <summary>
2228         /// PSPE Non-MSDTC Completed Event.
2229         /// </summary>
2230         [Theory]
2231         [InlineData(false)]
2232         [InlineData(true)]
PSPENonMsdtcCompletedEvent(bool promote)2233         public void PSPENonMsdtcCompletedEvent(bool promote)
2234         {
2235             // Registration for completed event
2236             TestCase_OutcomeRegistration(promote);
2237         }
2238 
2239         /// <summary>
2240         /// PSPE Non-MSDTC Get PromoterType.
2241         /// </summary>
2242         [Fact]
PSPENonMsdtcGetPromoterType()2243         public void PSPENonMsdtcGetPromoterType()
2244         {
2245             // get_PromoterType
2246             TestCase_PromoterType();
2247         }
2248 
2249         /// <summary>
2250         /// PSPE Non-MSDTC Get PromoterType.
2251         /// </summary>
2252         [Fact]
PSPENonMsdtcGetPromoterTypeMSDTC()2253         public void PSPENonMsdtcGetPromoterTypeMSDTC()
2254         {
2255             // get_PromoterType
2256             TestCase_PromoterTypeMSDTC();
2257         }
2258 
2259         /// <summary>
2260         /// PSPE Non-MSDTC Fail PromotableSinglePhaseNotification Calls.
2261         /// </summary>
2262         [Fact]
PSPENonMsdtcFailPromotableSinglePhaseNotificationCalls()2263         public void PSPENonMsdtcFailPromotableSinglePhaseNotificationCalls()
2264         {
2265             // Fail calls to the non-MSDTC Promotable Enlistment
2266             TestCase_FailPromotableSinglePhaseNotificationCalls();
2267         }
2268 
2269         /// <summary>
2270         /// Make SetDistributedTransactionIdentifier calls at the wrong time - negative test.
2271         /// </summary>
2272         [Fact]
PSPENonMsdtcInCorrectSetDistributedTransactionIdentifierCalls()2273         public void PSPENonMsdtcInCorrectSetDistributedTransactionIdentifierCalls()
2274         {
2275             // Call SetDistributedTransactionIdentifier at the wrong time.
2276             TestCase_SetDistributedIdAtWrongTime();
2277         }
2278 
2279         /// <summary>
2280         /// Call SetDistributedTransactionIdentifier with incorrect notification object - negative test.
2281         /// </summary>
2282         [Fact]
PSPENonMsdtcSetDistributedTransactionIdentifierCallWithWrongNotificationObject()2283         public void PSPENonMsdtcSetDistributedTransactionIdentifierCallWithWrongNotificationObject()
2284         {
2285             // Call SetDistributedTransactionIdentifier at the wrong time.
2286             TestCase_SetDistributedIdWithWrongNotificationObject();
2287         }
2288 
2289         [Fact]
2290         [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Expects PNSE due to not being supported on core")]
SimpleTransactionSuperior()2291         public void SimpleTransactionSuperior()
2292         {
2293             MySimpleTransactionSuperior superior = new MySimpleTransactionSuperior();
2294             SubordinateTransaction subTx = new SubordinateTransaction(IsolationLevel.Serializable, superior);
2295 
2296             AutoResetEvent durableCompleted = new AutoResetEvent(false);
2297             MyEnlistment durable = null;
2298 
2299             durable = new MyEnlistment(
2300                 durableCompleted,
2301                 true,
2302                 false,
2303                 EnlistmentOptions.None,
2304                 /*expectSuccessfulEnlist=*/ false,
2305                 /*secondEnlistmentCompleted=*/ null);
2306             durable.TransactionToEnlist = Transaction.Current;
2307 
2308             Assert.Throws<PlatformNotSupportedException>(() => // SubordinateTransaction promotes to MSDTC
2309             {
2310                 subTx.EnlistDurable(Guid.NewGuid(), durable, EnlistmentOptions.None);
2311             });
2312         }
2313     }
2314 }
2315