// bslma_testallocatormonitor.h                                       -*-C++-*-
#ifndef INCLUDED_BSLMA_TESTALLOCATORMONITOR
#define INCLUDED_BSLMA_TESTALLOCATORMONITOR

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide a mechanism to summarize `bslma::TestAllocator` object use.
//
//@CLASSES:
//  bslma::TestAllocatorMonitor: `bslma::TestAllocator` summary mechanism
//
//@SEE_ALSO: bslma_testallocator
//
//@DESCRIPTION: This component provides a single mechanism class,
// `bslma::TestAllocatorMonitor`, which is used, in concert with
// `bslma::TestAllocator`, in the implementation of test drivers.  The
// `bslma::TestAllocatorMonitor` class provides boolean accessors indicating
// whether associated test allocator state has changed (or not) since
// construction of the monitor.  Using `bslma::TestAllocatorMonitor` objects
// often result in test cases that are more concise, easier to read, and less
// error prone than test cases that directly access the test allocator for
// state information.
//
///Statistics
///----------
// The test allocator statistics tracked by the test allocator monitor along
// with the boolean accessors used to observe a change in those statistics are
// shown in the table below.  The change (or lack of change) reported by these
// accessors are relative to the value of the test allocator statistic at the
// construction of the monitor.  Note that each of these statistics count
// blocks of memory (i.e., number of allocations from the allocator), and do
// not depend on the number of bytes in those allocated blocks.
// ```
// Statistic        Is-Same Method Is-Up Method Is-Down Method
// --------------   -------------- ------------ --------------
// numBlocksInUse   isInUseSame    isInUseUp    isInUseDown
// numBlocksMax     isMaxSame      isMaxUp      none
// numBlocksTotal   isTotalSame    isTotalUp    none
// ```
// The `numBlocksMax` and `numBlocksTotal` statistics have values that are
// monotonically non-decreasing; hence, they need no "Is-Down" methods.  Note
// that if a monitor is created for an allocator with outstanding blocks ("in
// use"), then it is possible for the allocator's count of outstanding blocks
// to drop below the value seen by the monitor at construction.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Standard Usage
///- - - - - - - - - - - - -
// Classes taking `bslma::allocator` objects have many requirements (and thus,
// many testing concerns) that other classes do not.  Here we illustrate how
// `bslma::TestAllocatorMonitor` objects (in conjunction with
// `bslma::TestAllocator` objects) can be used in a test driver to succinctly
// address many concerns of an object's use of allocators.
//
// First, for a test subject, we introduce `MyClass`, an unconstrained
// attribute class having a single, null-terminated ascii string attribute,
// `description`.  For the sake of brevity, `MyClass` defines only a default
// constructor, a primary manipulator (the `setDescription` method), and a
// basic accessor (the `description` method).  These suffice for the purposes
// of these example.  Note that a proper attribute class would also implement
// value and copy constructors, `operator==`, an accessor for the allocator,
// and other methods.
// ```
// /// This unconstrained (value-semantic) attribute class has a single,
// /// null-terminated ascii string attribute, `description`.
// class MyClass {
//
//     // DATA
//     size_t            d_capacity;      // available memory
//     char             *d_description_p; // string data
//     bslma::Allocator *d_allocator_p;   // held, not owned
//
//   public:
//     // CREATORS
//
//     /// Create a `MyClass` object having the (default) attribute values:
//     /// ```
//     ///  description() == ""
//     /// ```
//     /// Optionally specify a `basicAllocator` used to supply memory.  If
//     /// `basicAllocator` is 0, the currently installed default allocator is
//     /// used.
//     explicit MyClass(bslma::Allocator *basicAllocator = 0);
//
//     /// Destroy this object.
//     ~MyClass();
//
//     // MANIPULATORS
//
//     /// Set the null-terminated ascii string `description` attribute of this
//     /// object to the specified `value`.  On completion, the `description`
//     /// method returns the address of a copy of the ascii string at `value`.
//     void setDescription(const char *value);
//
//     // ACCESSORS
//
//     /// Return the value of the null-terminated ascii string `description`
//     /// attribute of this object.
//     const char *description() const;
// };
//
// // ========================================================================
// //                      INLINE FUNCTION DEFINITIONS
// // ========================================================================
//
//                         // -------------
//                         // class MyClass
//                         // -------------
//
// // CREATORS
// inline
// MyClass::MyClass(bslma::Allocator *basicAllocator)
// : d_capacity(0)
// , d_description_p(0)
// , d_allocator_p(bslma::Default::allocator(basicAllocator))
// {
// }
//
// inline
// MyClass::~MyClass()
// {
//     BSLS_ASSERT_SAFE(0 <= d_capacity);
//
//     d_allocator_p->deallocate(d_description_p);
// }
//
// // MANIPULATORS
// inline
// void MyClass::setDescription(const char *value)
// {
//     BSLS_ASSERT_SAFE(value);
//
//     size_t size = std::strlen(value) + 1;
//     if (size > d_capacity) {
//         char *newMemory = (char *) d_allocator_p->allocate(size);
//         d_allocator_p->deallocate(d_description_p);
//         d_description_p = newMemory;
//         d_capacity      = size;
//
//     }
//     std::memcpy(d_description_p, value, size);
// }
// ```
// Notice that the implementation of the manipulator allocates/deallocates
// memory *before* updating the object.  This ordering leaves the object
// unchanged in case the allocator throws an exception (part of the strong
// exception guarantee).  This is an implementation detail, not a part of the
// contract (in this example).
// ```
// // ACCESSORS
// inline
// const char *MyClass::description() const
// {
//     return d_description_p ? d_description_p : "";
// }
// ```
// Then, we design a test-driver for `MyClass`.  Our allocator-related concerns
// for `MyClass` include:
// ```
// Concerns:
// // 1. Any memory allocation is from the object allocator.
// //
// // 2. Every object releases any allocated memory at destruction.
// //
// // 3. No accessor allocates any memory.
// //
// // 4. All memory allocation is exception-neutral.
// //
// // 5. QoI: The default constructor allocates no memory.
// //
// // 6. QoI: When possible, memory is cached for reuse.
// ```
// Notice that some of these concerns (e.g., C-5..6) are not part of the
// class's documented, contractual behavior.  These are classified as Quality
// of Implementation (QoI) concerns.
//
// Next, we define a test plan.  For example, a plan to test these concerns is:
// ```
// Plan:
// // 1. Setup global and default allocators:
// //
// //   1. Create two `bslma::TestAllocator` objects and, for each of these,
// //      create an associated `bslma::TestAllocatorMonitor` object.
// //
// //   2. Install the two allocators as the global and default allocators.
// //
// // 2. Confirm that default construction allocates no memory: (C-5)
// //
// //   1. Construct a `bslma::TestAllocatorMonitor` object to be used passed
// //      to test objects on their construction, and an associated
// //
// //   2. In an inner block, default construct an object of `MyClass` using
// //      the designated "object" test allocator.
// //
// //   3. Allow the object to go out of scope (destroyed).  Confirm that no
// //      memory has been allocated from any of the allocators.
// //
// // 3. Exercise an object of `MyClass` such that memory should be allocated,
// //    and then confirm that the object allocator (only) is used: (C-2..4,6)
// //
// //   1. In another inner block, default construct a new test object using
// //      the (as yet unused) object allocator.
// //
// //   2. Force the test object to allocate memory by setting its
// //      `descriptor` attribute to a value whose size exceeds the size of
// //      the object itself.  Confirm that the attribute was set and that
// //      memory was allocated.
// //
// //   3. Confirm that the primary manipulator (the `setDescription` method)
// //      is exception-neutral (i.e., exceptions from the allocator are
// //      propagated and no memory is leaked).  Use the
// //      `BSLMA_TESTALLOCATOR_EXCEPTION_TEST_*` macros to manage the test,
// //      and use the test allocator monitor to confirm that memory is
// //      allocated on the no-exception code path.  (C-4)
// //
// //   4. When the object is holding memory, create an additional test
// //      allocator monitor allocator for the object allocator.  Use the
// //      basic accessor (i.e., the `description` method) to confirm that the
// //      object has the expected value.  Check this test allocator monitor
// //      to confirm that accessor allocated no memory.  (C-3)
// //
// //   5. Change the attribute to a smaller value and confirm that the
// //      current memory was reused (i.e., no memory is allocated). (C-6)
// //
// //   6. Destroy the test object by allowing it to go out of scope, and
// //      confirm that all allocations are returned.  (C-2)
// //
// // 4. Confirm that at no time were the global allocator or the default
// //    allocator were used.  (C-1)
// ```
// The implementation of the plan is shown below:
//
// Then, we implement the first portion of the plan.  We create the trio of
// test allocators, their respective test allocator monitors, and install two
// of the allocators as the global and default allocators:
// ```
// {
//     if (verbose) cout << "Setup global and default allocators" << endl;
//
//     bslma::TestAllocator        ga("global",  veryVeryVeryVerbose);
//     bslma::TestAllocator        da("default", veryVeryVeryVerbose);
//     bslma::TestAllocatorMonitor gam(&ga);
//     bslma::TestAllocatorMonitor dam(&da);
//
//     bslma::Default::setGlobalAllocator(&ga);
//     assert(0 == bslma::Default::setDefaultAllocator(&da));
// ```
// Then, we default construct a test object using the object allocator, and
// then, immediately destroy it.  The object allocator monitor, `oam`, shows
// that the allocator was not used.
// ```
//     if (verbose) cout << "No allocation by Default Constructor " << endl;
//
//     bslma::TestAllocator        oa("object", veryVeryVeryVerbose);
//     bslma::TestAllocatorMonitor oam(&oa);
//
//     {
//         MyClass obj(&oa);
//         assert(oam.isTotalSame()); // object allocator unused
//     }
// ```
// Next, we pass the (still unused) object allocator to another test object.
// This time, we coerce the object into allocating memory by setting an
// attribute.  (Setting an attribute larger than the receiving object means
// that the object cannot store the data within its own footprint and must
// allocate memory.)
// ```
//     if (verbose) cout << "Exercise object" << endl;
//
//     {
//         MyClass obj(&oa);
//
//         const char DESCRIPTION1[]="abcdefghijklmnopqrstuvwyz"
//                                   "abcdefghijklmnopqrstuvwyz";
//         assert(sizeof(obj) < sizeof(DESCRIPTION1));
//
//         if (veryVerbose) cout << "\tPrimary Manipulator Allocates" << endl;
//
//         BSLMA_TESTALLOCATOR_EXCEPTION_TEST_BEGIN(oa) {
//             if (veryVeryVerbose) { T_ T_ Q(ExceptionTestBody) }
//
//             obj.setDescription(DESCRIPTION1);
//             assert(oam.isTotalUp());  // object allocator was used
//             assert(oam.isInUseUp());  // some outstanding allocation(s)
//             assert(oam.isMaxUp());    // a maximum was set
//         } BSLMA_TESTALLOCATOR_EXCEPTION_TEST_END
// ```
// Notice, as expected, memory was allocated from object allocator.
//
// Now that the allocator has been used, we create a second monitor to capture
// the that state.  Confirm that the basic accessor (the `description` method)
// does not use the allocator.
// ```
//     if (veryVerbose) cout << "\tBasic Accessor does not allocate" << endl;
//
//     bslma::TestAllocatorMonitor oam2(&oa); // Captures state of 'oa'
//                                            // with outstanding
//                                            // allocations.
//
//     assert(0 == strcmp(DESCRIPTION1, obj.description()));
//     assert(oam2.isTotalSame());  // object allocator was not used
// ```
// Next, confirm that when a shorter value is assigned, the existing memory is
// reused.
// ```
//     obj.setDescription("a");
//     assert(0 == std::strcmp("a", obj.description()));
//
//     assert(oam2.isTotalSame());  // no allocations
// ```
// Notice that there are no allocations because the object had sufficient
// capacity in previously allocated memory to store the short string.
//
// Next, as an additional test, we make the object allocate additional memory
// by setting a longer attribute: one that exceeds the capacity allocated for
// `DESCRIPTION1`.  Use the second monitor to confirm that an allocation was
// performed.
//
// There are tests where using a test allocator monitor does not suffice.  Our
// test object is currently holding memory, if we assign a value that exceeds
// its current capacity there will be two operations on the object allocator:
// the allocation of larger memory, and the deallocation of its current memory:
// in that order, as part of the strong exception guarantee.  Thus, the maximum
// number of allocations should go up by one, and no more.
//
// Note that absence of memory leaks due to exceptions (the other part of the
// strong exception guarantee is confirmed during the destruction of the object
// test allocator at the end of this test, which featured exceptions.
// ```
//     bsls::Types::Int64 maxBeforeSet   = oa.numBlocksMax();
//     const char         DESCRIPTION2[] = "abcdefghijklmnopqrstuvwyz"
//                                         "abcdefghijklmnopqrstuvwyz"
//                                         "abcdefghijklmnopqrstuvwyz"
//                                         "abcdefghijklmnopqrstuvwyz"
//                                         "abcdefghijklmnopqrstuvwyz";
//     assert(sizeof(DESCRIPTION1) < sizeof(DESCRIPTION2));
//
//     obj.setDescription(DESCRIPTION2);
//     assert(0 == std::strcmp(DESCRIPTION2, obj.description()));
//
//     assert(oam2.isTotalUp());    // The object allocator used.
//
//     assert(oam2.isInUseSame());  // The outstanding block (allocation)
//                                  // count unchanged (even though byte
//                                  // outstanding byte count increased).
//
//     assert(oam2.isMaxUp());      // Max increased as expected, but was
//                                  // did it change only by one?  The
//                                  // monitor cannot answer that
//                                  // question.
//
// bsls::Types::Int64 maxAfterSet = oa.numBlocksMax();
//
// assert(1 == maxAfterSet - maxBeforeSet);
// ```
// Notice that our test allocator monitor cannot confirm that the allocator's
// maximum increased by exactly one.  In this case, we must extract our
// statistics directly from the test allocator.
//
// Note that increment in "max" occurs only the first time through the
// allocate/deallocate scenario in `setDescription`.
// ```
//     bslma::TestAllocatorMonitor oam3(&oa);
//
//     const char DESCRIPTION3[] = "abcdefghijklmnopqrstuvwyz"
//                                 "abcdefghijklmnopqrstuvwyz"
//                                 "abcdefghijklmnopqrstuvwyz"
//                                 "abcdefghijklmnopqrstuvwyz"
//                                 "abcdefghijklmnopqrstuvwyz"
//                                 "abcdefghijklmnopqrstuvwyz"
//                                 "abcdefghijklmnopqrstuvwyz"
//                                 "abcdefghijklmnopqrstuvwyz"
//                                 "abcdefghijklmnopqrstuvwyz";
//     assert(sizeof(DESCRIPTION2) < sizeof(DESCRIPTION3));
//
//     obj.setDescription(DESCRIPTION3);
//     assert(0 == std::strcmp(DESCRIPTION3, obj.description()));
//
//     assert(oam3.isTotalUp());    // The object allocator used.
//
//     assert(oam3.isInUseSame());  // The outstanding block (allocation)
//                                  // count unchanged (even though byte
//                                  // outstanding byte count increased).
//
//     assert(oam3.isMaxSame());    // A repeat of the scenario for
//                                  // `DESCRIPTION2`, so no change in the
//                                  // allocator's maximum.
// ```
// Now, we close scope and check that all object memory was deallocated
// ```
// }
//
// if (veryVerbose) cout << "\tAll memory returned object allocator"
//                       << endl;
//
// assert(oam.isInUseSame());
// ```
// Finally, we check that none of these operations used the default or global
// allocators.
// ```
// if (verbose) cout << "Global and Default allocators never used" << endl;
//
// assert(gam.isTotalSame());
// assert(dam.isTotalSame());
// ```

#include <bslscm_version.h>

#include <bslma_testallocator.h>

#include <bsls_assert.h>

namespace BloombergLP {

namespace bslma {

                        // ==========================
                        // class TestAllocatorMonitor
                        // ==========================

/// This mechanism provides a set of boolean accessor methods indicating
/// whether a change has occurred in the state of the `TestAllocator` object
/// (supplied at construction) since the construction of the monitor.  See the
/// [](#Statistics) section for the statics tracked.
class TestAllocatorMonitor {

    // DATA
    bsls::Types::Int64   d_initialInUse;    // 'numBlocksInUse'
    bsls::Types::Int64   d_initialMax;      // 'numBlocksMax'
    bsls::Types::Int64   d_initialTotal;    // 'numBlocksTotal'
    const TestAllocator *d_testAllocator_p; // held, not owned

    // PRIVATE CLASS METHODS

    /// Return the specified `allocator`, and, if compiled in "SAFE" mode,
    /// assert that `allocator` is not 0.  Note that this static function is
    /// needed to perform validation on the allocator address supplied at
    /// construction, prior to that address being dereferenced to initialize
    /// the `const` data members of this type.
    static const TestAllocator *validateArgument(
                                               const TestAllocator *allocator);

  private:
    // NOT IMPLEMENTED
    TestAllocatorMonitor(const TestAllocatorMonitor&);             // = delete
    TestAllocatorMonitor& operator=(const TestAllocatorMonitor&);  // = delete

  public:
    // CREATORS

    /// Create a `TestAllocatorMonitor` object to track changes in
    /// statistics of the specified `testAllocator`.
    explicit TestAllocatorMonitor(const TestAllocator *testAllocator);

    /// Destroy this object.
    ~TestAllocatorMonitor();

    // MANIPULATOR

    /// Change the allocator monitored by this object to the specified
    /// `testAllocator` and initialize the allocator properties monitored by
    /// this object to the current state of `testAllocator`.  If no
    /// `testAllocator` is passed, do not modify the allocator held by this
    /// object and re-initialize the allocator properties monitored by this
    /// object to the current state of that allocator.
    void reset(const bslma::TestAllocator *testAllocator = 0);

    // ACCESSORS

    /// Return `true` if the `numBlocksInUse` statistic of the tracked test
    /// allocator has decreased since construction of this monitor, and
    /// `false` otherwise.
    bool isInUseDown() const;

    /// Return `true` if the `numBlocksInUse` statistic of the tracked test
    /// allocator has not changed since construction of this monitor, and
    /// `false` otherwise.
    bool isInUseSame() const;

    /// Return `true` if the `numBlocksInUse` statistic of the tracked test
    /// allocator has increased since construction of this monitor, and
    /// `false` otherwise.
    bool isInUseUp() const;

    /// Return `true` if the `numBlocksMax` statistic of the tracked test
    /// allocator has not changed since construction of this monitor, and
    /// `false` otherwise.
    bool isMaxSame() const;

    /// Return `true` if the `numBlocksMax` statistic of the tracked test
    /// allocator has increased since construction of this monitor, and
    /// `false` otherwise.
    bool isMaxUp() const;

    /// Return `true` if the `numBlocksTotal` statistic of the tracked test
    /// allocator has not changed since construction of this monitor, and
    /// `false` otherwise.
    bool isTotalSame() const;

    /// Return `true` if the `numBlocksTotal` statistic of the tracked test
    /// allocator has increased since construction of this monitor, and
    /// `false` otherwise.
    bool isTotalUp() const;

    /// Return the change in the `numBlocksInUse` statistic of the tracked
    /// test allocator since construction of this monitor.
    bsls::Types::Int64 numBlocksInUseChange() const;

    /// Return the change in the `numBlocksMax` statistic of the tracked
    /// test allocator since construction of this monitor.
    bsls::Types::Int64 numBlocksMaxChange() const;

    /// Return the change in the `numBlocksTotal` statistic of the tracked
    /// test allocator since construction of this monitor.
    bsls::Types::Int64 numBlocksTotalChange() const;
};

// ============================================================================
//                          INLINE DEFINITIONS
// ============================================================================

                        // --------------------------
                        // class TestAllocatorMonitor
                        // --------------------------

// CLASS METHODS
inline
const TestAllocator *
TestAllocatorMonitor::validateArgument(const TestAllocator *allocator)
{
    BSLS_ASSERT_SAFE(allocator);

    return allocator;
}

// MANIPULATOR
inline
void TestAllocatorMonitor::reset(const TestAllocator *testAllocator)
{
    // This method is called inline by c'tor, hence it should precede it.

    if (testAllocator) {
        d_testAllocator_p = testAllocator;
    }

    d_initialInUse = d_testAllocator_p->numBlocksInUse();
    d_initialMax   = d_testAllocator_p->numBlocksMax();
    d_initialTotal = d_testAllocator_p->numBlocksTotal();

    BSLS_ASSERT_SAFE(0 <= d_initialMax);
    BSLS_ASSERT_SAFE(0 <= d_initialTotal);
}

// CREATORS
inline
TestAllocatorMonitor::TestAllocatorMonitor(const TestAllocator *testAllocator)
: d_testAllocator_p(testAllocator)
{
    BSLS_ASSERT_SAFE(d_testAllocator_p);

    reset();
}

}  // close package namespace

namespace bslma {

inline
TestAllocatorMonitor::~TestAllocatorMonitor()
{
    BSLS_ASSERT_SAFE(d_testAllocator_p);
    BSLS_ASSERT_SAFE(0 <= d_initialMax);
    BSLS_ASSERT_SAFE(0 <= d_initialTotal);
}

}  // close package namespace

namespace bslma {

// ACCESSORS
inline
bool TestAllocatorMonitor::isInUseDown() const
{
    return d_testAllocator_p->numBlocksInUse() < d_initialInUse;
}

inline
bool TestAllocatorMonitor::isInUseSame() const
{
    return d_testAllocator_p->numBlocksInUse() == d_initialInUse;
}

inline
bool TestAllocatorMonitor::isInUseUp() const
{
    return d_testAllocator_p->numBlocksInUse() > d_initialInUse;
}

inline
bool TestAllocatorMonitor::isMaxSame() const
{
    return d_initialMax == d_testAllocator_p->numBlocksMax();
}

inline
bool TestAllocatorMonitor::isMaxUp() const
{
    return d_testAllocator_p->numBlocksMax() != d_initialMax;
}

inline
bool TestAllocatorMonitor::isTotalSame() const
{
    return d_testAllocator_p->numBlocksTotal() == d_initialTotal;
}

inline
bool TestAllocatorMonitor::isTotalUp() const
{
    return d_testAllocator_p->numBlocksTotal() != d_initialTotal;
}

inline
bsls::Types::Int64 TestAllocatorMonitor::numBlocksInUseChange() const
{
    return d_testAllocator_p->numBlocksInUse() - d_initialInUse;
}

inline
bsls::Types::Int64 TestAllocatorMonitor::numBlocksMaxChange() const
{
    return d_testAllocator_p->numBlocksMax() - d_initialMax;
}

inline
bsls::Types::Int64 TestAllocatorMonitor::numBlocksTotalChange() const
{
    return d_testAllocator_p->numBlocksTotal() - d_initialTotal;
}

}  // close package namespace

#ifndef BDE_OPENSOURCE_PUBLICATION  // BACKWARD_COMPATIBILITY
// ============================================================================
//                           BACKWARD COMPATIBILITY
// ============================================================================

/// This alias is defined for backward compatibility.
typedef bslma::TestAllocatorMonitor bslma_TestAllocatorMonitor;
#endif  // BDE_OPENSOURCE_PUBLICATION -- BACKWARD_COMPATIBILITY

}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2013 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------- END-OF-FILE ----------------------------------
