public void TestWrapperCollection() {
      WeightedTransaction<Transaction> transaction = new WeightedTransaction<Transaction>(
        Transaction.EndedDummy
      );

      ObservedWeightedTransaction<Transaction> observed =
        new ObservedWeightedTransaction<Transaction>(
          transaction,
          endedCallback,
          progressUpdatedCallback
        );

      WeightedTransactionWrapperCollection<Transaction> wrapper =
        new WeightedTransactionWrapperCollection<Transaction>(
          new ObservedWeightedTransaction<Transaction>[] { observed }
        );

      Assert.AreSame(transaction, wrapper[0]);
    }
    public void TestConstructorWithAlreadyEndedTransaction() {
      WeightedTransaction<Transaction> testTransaction = new WeightedTransaction<Transaction>(
        Transaction.EndedDummy
      );

      IObservationSubscriber subscriber = this.mockery.NewMock<IObservationSubscriber>();

      Expect.AtLeast(0).On(subscriber).Method("ProgressUpdated");
      // This should no be called because otherwise, the 'Ended' event would be raised
      // to the transaction group before all transactions have been added into
      // the internal list, leading to an early ending or even multiple endings.
      Expect.Never.On(subscriber).Method("Ended");

      using(
        ObservedWeightedTransaction<Transaction> test =
          new ObservedWeightedTransaction<Transaction>(
            testTransaction,
            new ObservedWeightedTransaction<Transaction>.ReportDelegate(
              subscriber.ProgressUpdated
            ),
            new ObservedWeightedTransaction<Transaction>.ReportDelegate(
              subscriber.Ended
            )
          )
      ) {
        this.mockery.VerifyAllExpectationsHaveBeenMet();
      }
    }
 /// <summary>
 ///   Checks whether the provided transaction matches the comparison
 ///   transaction of the instance
 /// </summary>
 /// <param name="other">Transaction to match to the comparison transaction</param>
 public bool Matches(ObservedWeightedTransaction<Transaction> other) {
   return ReferenceEquals(other.WeightedTransaction.Transaction, this.toMatch);
 }
    public void TestConstructorWithEndingTransaction() {
      WeightedTransaction<Transaction> testTransaction = new WeightedTransaction<Transaction>(
        new FunkyTransaction()
      );

      IObservationSubscriber subscriber = this.mockery.NewMock<IObservationSubscriber>();

      Expect.AtLeast(0).On(subscriber).Method("ProgressUpdated");
      Expect.Once.On(subscriber).Method("Ended");

      using(
        ObservedWeightedTransaction<Transaction> test =
          new ObservedWeightedTransaction<Transaction>(
            testTransaction,
            new ObservedWeightedTransaction<Transaction>.ReportDelegate(
              subscriber.ProgressUpdated
            ),
            new ObservedWeightedTransaction<Transaction>.ReportDelegate(
              subscriber.Ended
            )
        )
      ) {
        this.mockery.VerifyAllExpectationsHaveBeenMet();
      }
    }
    /// <summary>Begins tracking the specified background transaction</summary>
    /// <param name="transaction">Background transaction to be tracked</param>
    /// <param name="weight">Weight to assign to this background transaction</param>
    public void Track(Transaction transaction, float weight) {

      // Add the new transaction into the tracking list. This has to be done
      // inside a lock to prevent issues with the progressUpdate callback, which could
      // access the totalWeight field before it has been updated to reflect the
      // new transaction added to the collection.
      lock(this.trackedTransactions) {

        bool wasEmpty = (this.trackedTransactions.Count == 0);

        if(transaction.Ended) {

          // If the ended transaction would become the only transaction in the list,
          // there's no sense in doing anything at all because it would have to be
          // thrown right out again. Only add the transaction when there are other
          // running transactions to properly sum total progress for consistency.
          if(!wasEmpty) {

            // Construct a new observation wrapper. This is done inside the lock
            // because as soon as we are subscribed to the events, we can potentially
            // receive them. The lock eliminates the risk of processing a progress update
            // before the transaction has been added to the tracked transactions list.
            this.trackedTransactions.Add(
              new ObservedWeightedTransaction<Transaction>(
                new WeightedTransaction<Transaction>(transaction, weight),
                this.asyncProgressUpdatedDelegate,
                this.asyncEndedDelegate
              )
            );

          }

        } else { // Not ended -- Transaction is still running

          // Construct a new transation observer and add the transaction to our
          // list of tracked transactions.
          ObservedWeightedTransaction<Transaction> observedTransaction =
            new ObservedWeightedTransaction<Transaction>(
              new WeightedTransaction<Transaction>(transaction, weight),
              this.asyncProgressUpdatedDelegate,
              this.asyncEndedDelegate
            );

          this.trackedTransactions.Add(observedTransaction);

          // If this is the first transaction to be added to the list, tell our
          // owner that we're idle no longer!
          if(wasEmpty) {
            setIdle(false);
          }

        } // if transaction ended

        // This can be done after we registered the wrapper to our delegates because
        // any incoming progress updates will be stopped from the danger of a
        // division-by-zero from the potentially still zeroed totalWeight by the lock.
        this.totalWeight += weight;

        // All done, the total progress is different now, so force a recalculation and
        // send out the AsyncProgressUpdated event.
        recalculateProgress();

      } // lock

    }