/********************************************************************
 * test-kvp_frame.c: GLib g_test test suite for kvp_frame.c.		    *
 * Copyright 2011 John Ralls <jralls@ceridwen.us>		    *
 *                                                                  *
 * This program is free software; you can redistribute it and/or    *
 * modify it under the terms of the GNU General Public License as   *
 * published by the Free Software Foundation; either version 2 of   *
 * the License, or (at your option) any later version.              *
 *                                                                  *
 * This program is distributed in the hope that it will be useful,  *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
 * GNU General Public License for more details.                     *
 *                                                                  *
 * You should have received a copy of the GNU General Public License*
 * along with this program; if not, contact:                        *
 *                                                                  *
 * Free Software Foundation           Voice:  +1-617-542-5942       *
 * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
 * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
********************************************************************/
#include "config.h"
#include <string.h>
#include <glib.h>
#include "unittest-support.h"
#include "qof.h"

static const gchar *suitename = "/qof/kvp_frame";
void test_suite_kvp_frame ( void );

typedef struct
{
    KvpFrame *frame;
} Fixture;

static void
setup( Fixture *fixture, gconstpointer pData )
{
    fixture->frame = kvp_frame_new();
}

static void
teardown( Fixture *fixture, gconstpointer pData )
{
    kvp_frame_delete( fixture->frame );
}

extern KvpFrame* ( *p_get_trailer_make )( KvpFrame *frame, const char *key_path, char **end_key );
extern gchar* ( *p_kvp_value_glist_to_string )( const GList *list );
extern KvpFrame* ( *p_get_or_make )( KvpFrame *fr, const char * key );
extern const KvpFrame* ( *p_kvp_frame_get_frame_or_null_slash_trash )( const KvpFrame *frame, char *key_path );
extern const KvpFrame* ( *p_get_trailer_or_null )( const KvpFrame * frame, const char * key_path, char **end_key );

extern void init_static_test_pointers( void );

static void
setup_static( Fixture *fixture, gconstpointer pData )
{
    fixture->frame = kvp_frame_new();
    init_static_test_pointers();
    g_assert( p_get_trailer_make && p_kvp_value_glist_to_string &&
              p_get_or_make && p_kvp_frame_get_frame_or_null_slash_trash &&
              p_get_trailer_or_null );
}

static void
teardown_static( Fixture *fixture, gconstpointer pData )
{
    kvp_frame_delete( fixture->frame );
    p_get_trailer_make = NULL;
    p_kvp_value_glist_to_string = NULL;
    p_get_or_make = NULL;
    p_kvp_frame_get_frame_or_null_slash_trash = NULL;
    p_get_trailer_or_null = NULL;
}

static void
test_kvp_frame_new_delete( void )
{
    KvpFrame *frame;
    Timespec ts;
    GncGUID *guid;

    ts.tv_sec = 1;
    ts.tv_nsec = 1;
    guid = guid_malloc();
    guid_new( guid );

    frame = kvp_frame_new();
    g_assert( frame );
    g_assert( kvp_frame_is_empty( frame ) );

    kvp_frame_set_gint64( frame, "gint64-type", 100 );
    kvp_frame_set_double( frame, "double-type", 3.14159 );
    kvp_frame_set_numeric( frame, "numeric-type", gnc_numeric_zero() );
    kvp_frame_set_timespec( frame, "timespec-type", ts );
    kvp_frame_set_string( frame, "string-type", "abcdefghijklmnop" );
    kvp_frame_set_guid( frame, "guid-type", guid );
    kvp_frame_set_frame( frame, "frame-type", kvp_frame_new() );

    g_assert( !kvp_frame_is_empty( frame ) );

    kvp_frame_delete( frame );
    g_assert( frame );

    guid_free( guid );
}

static void
test_kvp_frame_copy( Fixture *fixture, gconstpointer pData )
{
    KvpFrame *to_copy = NULL;
    gint64 test_gint64, copy_gint64;
    double test_double, copy_double;
    gnc_numeric test_gnc_numeric, copy_gnc_numeric;
    Timespec test_ts, copy_ts;
    const char* test_str, *copy_str;
    GncGUID *test_guid, *copy_guid;
    KvpFrame *test_frame, *copy_frame;

    /* init data in source frame */
    test_gint64 = 111;
    test_double = 1.1;
    test_gnc_numeric = gnc_numeric_zero();
    test_ts.tv_sec = 1;
    test_ts.tv_nsec = 1;
    test_str = "abcdefghijklmnop";
    test_guid = guid_malloc();
    guid_new( test_guid );
    test_frame = kvp_frame_new();

    g_assert( fixture->frame );
    kvp_frame_set_gint64( fixture->frame, "gint64-type", test_gint64 );
    kvp_frame_set_double( fixture->frame, "double-type", test_double );
    kvp_frame_set_numeric( fixture->frame, "numeric-type", test_gnc_numeric );
    kvp_frame_set_timespec( fixture->frame, "timespec-type", test_ts );
    kvp_frame_set_string( fixture->frame, "string-type", test_str );
    kvp_frame_set_guid( fixture->frame, "guid-type", test_guid );
    kvp_frame_set_frame( fixture->frame, "frame-type", test_frame );
    g_assert( !kvp_frame_is_empty( fixture->frame ) );

    g_test_message( "Test frame copy" );
    to_copy = kvp_frame_copy( fixture->frame );
    g_assert( to_copy );
    g_assert( !kvp_frame_is_empty( to_copy ) );
    g_assert( to_copy != fixture->frame );
    g_assert_cmpint( kvp_frame_compare( fixture->frame, to_copy ), == , 0 );
    copy_gint64 = kvp_frame_get_gint64( to_copy, "gint64-type" );
    g_assert( &copy_gint64 != &test_gint64 );
    g_assert_cmpint( copy_gint64, == , test_gint64 );
    copy_double = kvp_frame_get_double( to_copy, "double-type" );
    g_assert( &copy_double != &test_double );
    g_assert_cmpfloat( copy_double, == , test_double );
    copy_gnc_numeric = kvp_frame_get_numeric( to_copy, "numeric-type" );
    g_assert( &copy_gnc_numeric != &test_gnc_numeric );
    g_assert_cmpfloat( copy_gnc_numeric.num, == , test_gnc_numeric.num );
    g_assert_cmpfloat( copy_gnc_numeric.denom, == , test_gnc_numeric.denom );
    copy_ts = kvp_frame_get_timespec( to_copy, "timespec-type" );
    g_assert( &copy_ts != &test_ts );
    g_assert_cmpfloat( copy_ts.tv_sec, == , test_ts.tv_sec );
    g_assert_cmpfloat( copy_ts.tv_nsec, == , test_ts.tv_nsec );
    copy_str = kvp_frame_get_string( to_copy, "string-type" );
    g_assert( copy_str != test_str );
    g_assert_cmpstr( copy_str, == , test_str );
    copy_guid = kvp_frame_get_guid( to_copy, "guid-type" );
    g_assert( copy_guid != test_guid );
    g_assert( guid_equal( copy_guid, test_guid ) );
    copy_frame = kvp_frame_get_frame( to_copy, "frame-type");
    g_assert( copy_frame );
    g_assert( kvp_frame_is_empty( copy_frame ) );
    g_assert( copy_frame != test_frame );
    g_assert_cmpint( kvp_frame_compare( copy_frame, test_frame ), == , 0 );

    kvp_frame_delete( to_copy );
    guid_free( test_guid );
}

static void
test_kvp_frame_set_foo( Fixture *fixture, gconstpointer pData )
{
    gnc_numeric test_gnc_numeric, copy_gnc_numeric;
    Timespec test_ts, copy_ts;
    GncGUID *test_guid, *copy_guid;

    test_gnc_numeric = gnc_numeric_zero();
    test_ts.tv_sec = 1;
    test_ts.tv_nsec = 1;
    test_guid = guid_malloc();
    guid_new( test_guid );

    g_assert( fixture->frame );
    g_assert( kvp_frame_is_empty( fixture->frame ) );

    g_test_message( "Test gint64 setup and replace, test frame is created" );
    g_assert( kvp_frame_get_frame( fixture->frame, "/test" ) == NULL );
    kvp_frame_set_gint64( fixture->frame, "/test/gint64", 1 );
    g_assert( kvp_frame_get_frame( fixture->frame, "/test" ) != NULL );
    g_assert_cmpint( kvp_frame_get_gint64( fixture->frame, "/test/gint64" ), == , 1 );
    kvp_frame_set_gint64( fixture->frame, "/test/gint64", 5 );
    g_assert_cmpint( kvp_frame_get_gint64( fixture->frame, "/test/gint64" ), == , 5 );

    g_test_message( "Test double setup and replace, test2 frame is created" );
    g_assert( kvp_frame_get_frame( fixture->frame, "/test2" ) == NULL );
    kvp_frame_set_double( fixture->frame, "/test2/double", 1.1 );
    g_assert( kvp_frame_get_frame( fixture->frame, "/test2" ) != NULL );
    g_assert_cmpfloat( kvp_frame_get_double( fixture->frame, "/test2/double" ), == , 1.1 );
    kvp_frame_set_double( fixture->frame, "/test2/double", 5.5 );
    g_assert_cmpfloat( kvp_frame_get_double( fixture->frame, "/test2/double" ), == , 5.5 );

    g_test_message( "Test double setup and replace, test3 frame is created" );
    g_assert( kvp_frame_get_frame( fixture->frame, "/test3" ) == NULL );
    kvp_frame_set_numeric( fixture->frame, "/test3/numeric", test_gnc_numeric );
    g_assert( kvp_frame_get_frame( fixture->frame, "/test3" ) != NULL );
    copy_gnc_numeric = kvp_frame_get_numeric( fixture->frame, "/test3/numeric" );
    g_assert_cmpint( copy_gnc_numeric.num, == , 0 );
    g_assert_cmpint( copy_gnc_numeric.denom, == , 1 );
    test_gnc_numeric.num = 2;
    test_gnc_numeric.denom = 3;
    kvp_frame_set_numeric( fixture->frame, "/test3/numeric", test_gnc_numeric );
    copy_gnc_numeric = kvp_frame_get_numeric( fixture->frame, "/test3/numeric" );
    g_assert_cmpint( copy_gnc_numeric.num, == , 2 );
    g_assert_cmpint( copy_gnc_numeric.denom, == , 3 );

    g_test_message( "Test timespec setup and replace, test4 frame is created" );
    g_assert( kvp_frame_get_frame( fixture->frame, "/test4" ) == NULL );
    kvp_frame_set_timespec( fixture->frame, "/test4/timespec", test_ts );
    g_assert( kvp_frame_get_frame( fixture->frame, "/test4" ) != NULL );
    copy_ts = kvp_frame_get_timespec( fixture->frame, "/test4/timespec" );
    g_assert_cmpint( copy_ts.tv_sec, == , 1 );
    g_assert_cmpint( copy_ts.tv_nsec, == , 1 );
    test_ts.tv_sec = 7;
    test_ts.tv_nsec = 13;
    kvp_frame_set_timespec( fixture->frame, "/test4/timespec", test_ts );
    copy_ts = kvp_frame_get_timespec( fixture->frame, "/test4/timespec" );
    g_assert_cmpint( copy_ts.tv_sec, == , 7 );
    g_assert_cmpint( copy_ts.tv_nsec, == , 13 );

    g_test_message( "Test string setup and replace, test5 frame is created" );
    g_assert( kvp_frame_get_frame( fixture->frame, "/test5" ) == NULL );
    kvp_frame_set_string( fixture->frame, "/test5/string", "one string" );
    g_assert( kvp_frame_get_frame( fixture->frame, "/test5" ) != NULL );
    g_assert_cmpstr( kvp_frame_get_string( fixture->frame, "/test5/string" ), == , "one string" );
    kvp_frame_set_string( fixture->frame, "/test5/string", "another string" );
    g_assert_cmpstr( kvp_frame_get_string( fixture->frame, "/test5/string" ), == , "another string" );

    g_test_message( "Test guid setup and replace, test6 frame is created" );
    g_assert( kvp_frame_get_frame( fixture->frame, "/test6" ) == NULL );
    kvp_frame_set_guid( fixture->frame, "/test6/guid", test_guid );
    g_assert( kvp_frame_get_frame( fixture->frame, "/test6" ) != NULL );
    copy_guid = kvp_frame_get_guid( fixture->frame, "/test6/guid" );
    g_assert( guid_equal( copy_guid, test_guid ) );
    kvp_frame_set_guid( fixture->frame, "/test6/guid", guid_null() );
    copy_guid = kvp_frame_get_guid( fixture->frame, "/test6/guid" );
    g_assert( guid_equal( copy_guid, guid_null() ) );

    g_test_message( "Test frame setup and replace, test7 frame is created" );
    g_assert( kvp_frame_get_frame( fixture->frame, "/test7" ) == NULL );
    kvp_frame_set_frame( fixture->frame, "/test7", kvp_frame_new() );
    g_assert( kvp_frame_get_frame( fixture->frame, "/test7" ) != NULL );
    kvp_frame_set_frame( fixture->frame, "/test7", NULL );
    g_assert( kvp_frame_get_frame( fixture->frame, "/test7" ) == NULL );
}

static void
test_kvp_frame_get_frame_slash( Fixture *fixture, gconstpointer pData )
{
    KvpFrame *result_frame = NULL;
    /* Mostly testing static routine kvp_frmae_get_frame_slash_trash */
    g_assert( fixture->frame );

    g_test_message( "Test path with one slash same frame should be returned" );
    result_frame = kvp_frame_get_frame_slash( fixture->frame, "/" );
    g_assert( result_frame );
    g_assert( result_frame == fixture->frame );

    g_test_message( "Test path with trailing slash same frame should be returned" );
    result_frame = kvp_frame_get_frame_slash( fixture->frame, "/////" );
    g_assert( result_frame );
    g_assert( result_frame == fixture->frame );

    g_test_message( "Test new frame is created" );
    result_frame = kvp_frame_get_frame_slash( fixture->frame, "/test" );
    g_assert( result_frame );
    g_assert( result_frame != fixture->frame );
    g_assert( result_frame == kvp_frame_get_frame( fixture->frame, "/test" ) );

    g_test_message( "Test trailing slashes are ignored and frame created" );
    result_frame = kvp_frame_get_frame_slash( fixture->frame, "////test2/////" );
    g_assert( result_frame );
    g_assert( result_frame != fixture->frame );
    g_assert( result_frame == kvp_frame_get_frame( fixture->frame, "/test2" ) );
    g_assert( result_frame != kvp_frame_get_frame( fixture->frame, "/test" ) );

    g_test_message( "Test frames are created along the path if not exist and last frame is returned" );
    result_frame = kvp_frame_get_frame_slash( fixture->frame, "////test3/////test4//////" );
    g_assert( result_frame );
    g_assert( result_frame != fixture->frame );
    g_assert( result_frame != kvp_frame_get_frame( fixture->frame, "/test2" ) );
    g_assert( result_frame != kvp_frame_get_frame( fixture->frame, "/test" ) );
    g_assert( kvp_frame_get_frame( fixture->frame, "/test3" ) != NULL );
    g_assert( result_frame != kvp_frame_get_frame( fixture->frame, "/test3" ) );
    g_assert( result_frame == kvp_frame_get_frame( fixture->frame, "/test3/test4" ) );

    g_test_message( "Test existing frame is returned" );
    g_assert( result_frame == kvp_frame_get_frame_slash( fixture->frame, "////test3/////test4//////" ) );
}

static void
test_kvp_frame_get_slot_path( Fixture *fixture, gconstpointer pData )
{
    KvpValue *result_value = NULL ;

    g_assert( fixture->frame );
    g_assert( kvp_frame_is_empty( fixture->frame ) );

    g_test_message( "Test with non existing path should return NULL" );
    result_value = kvp_frame_get_slot_path( fixture->frame, "test", "test2", NULL );
    g_assert( !result_value );

    g_test_message( "Test with existing value set to current frame" );
    kvp_frame_set_gint64( fixture->frame, "/test", 1 );
    result_value = kvp_frame_get_slot_path( fixture->frame, "test", NULL );
    g_assert( result_value );
    g_assert( kvp_value_get_type( result_value ) == KVP_TYPE_GINT64 );
    g_assert_cmpint( kvp_value_get_gint64( result_value ), == , 1 );

    g_test_message( "Test should return null as test is not a frame" );
    kvp_frame_set_gint64( fixture->frame, "/test/test2", 2 );
    result_value = kvp_frame_get_slot_path( fixture->frame, "test", "test2", NULL );
    g_assert( !result_value );

    g_test_message( "Test should return last value in the path" );
    kvp_frame_set_gint64( fixture->frame, "/test2/test3", 2 );
    result_value = kvp_frame_get_slot_path( fixture->frame, "test2", "test3", NULL );
    g_assert( result_value );
    g_assert( kvp_value_get_type( result_value ) == KVP_TYPE_GINT64 );
    g_assert_cmpint( kvp_value_get_gint64( result_value ), == , 2 );

    g_test_message( "Test should return null as last value in the path does not exist" );
    result_value = kvp_frame_get_slot_path( fixture->frame, "test2", "test3", "test4", NULL );
    g_assert( !result_value );
}

static void
test_kvp_frame_get_slot_path_gslist( Fixture *fixture, gconstpointer pData )
{
    /* similar to previous test except path is passed as GSList*/
    GSList *path_list = NULL;
    KvpValue *result_value = NULL ;

    g_assert( fixture->frame );
    g_assert( kvp_frame_is_empty( fixture->frame ) );

    g_test_message( "Test with non existing path should return NULL" );
    path_list = g_slist_append (path_list, "test");
    path_list = g_slist_append (path_list, "test2");
    result_value = kvp_frame_get_slot_path_gslist( fixture->frame, path_list );
    g_assert( !result_value );

    g_test_message( "Test with existing value set to current frame" );
    path_list = g_slist_remove( path_list, "test2" );
    kvp_frame_set_gint64( fixture->frame, "/test", 1 );
    result_value = kvp_frame_get_slot_path_gslist( fixture->frame, path_list );
    g_assert( result_value );
    g_assert( kvp_value_get_type( result_value ) == KVP_TYPE_GINT64 );
    g_assert_cmpint( kvp_value_get_gint64( result_value ), == , 1 );

    g_test_message( "Test should return null as test is not a frame" );
    path_list = g_slist_append (path_list, "test2");
    kvp_frame_set_gint64( fixture->frame, "/test/test2", 2 );
    result_value = kvp_frame_get_slot_path_gslist( fixture->frame, path_list );
    g_assert( !result_value );

    g_test_message( "Test should return last value in the path" );
    path_list = g_slist_remove( path_list, "test" );
    path_list = g_slist_append (path_list, "test3");
    kvp_frame_set_gint64( fixture->frame, "/test2/test3", 2 );
    result_value = kvp_frame_get_slot_path_gslist( fixture->frame, path_list );
    g_assert( result_value );
    g_assert( kvp_value_get_type( result_value ) == KVP_TYPE_GINT64 );
    g_assert_cmpint( kvp_value_get_gint64( result_value ), == , 2 );

    g_test_message( "Test should return null as last value in the path does not exist" );
    path_list = g_slist_append (path_list, "test4");
    result_value = kvp_frame_get_slot_path_gslist( fixture->frame, path_list );
    g_assert( !result_value );
    g_slist_free( path_list );
}

static void
test_kvp_frame_add_frame_nc( Fixture *fixture, gconstpointer pData )
{
    /* basically we test static function kvp_frame_add_value_nc
     * if path not exist it's created
     * if gslist exist on the path new value added to the list
     * if any other value exist it's converted to gslist and newvalue added
     */
    KvpFrame *test_frame = NULL,
              *test_frame2 = NULL,
               *test_frame3 = NULL,
                *result_frame = NULL;
    KvpValue *result_value = NULL;
    GList *result_list = NULL;

    g_assert( fixture->frame );
    g_assert( kvp_frame_is_empty( fixture->frame ) );

    g_test_message( "Test when path does not exist it is created" );
    result_frame = kvp_frame_get_frame( fixture->frame, "/test/test2/test3" );
    g_assert( !result_frame );
    test_frame = kvp_frame_new();
    kvp_frame_add_frame_nc( fixture->frame, "/test/test2/test3", test_frame );
    result_frame = kvp_frame_get_frame( fixture->frame, "/test/test2/test3" );
    g_assert( result_frame );
    g_assert( result_frame == test_frame ); /* no copying done */
    result_frame = kvp_frame_get_frame( fixture->frame, "/test/test2" );
    g_assert( result_frame != test_frame );
    result_frame = kvp_frame_get_frame( fixture->frame, "/test" );
    g_assert( result_frame != test_frame );

    g_test_message( "Test when value exist on the path it's converted to bag and new value added" );
    test_frame2 = kvp_frame_new();
    kvp_frame_add_frame_nc( fixture->frame, "/test/test2/test3", test_frame2 );
    result_value = kvp_frame_get_value( fixture->frame, "/test/test2/test3" );
    result_list = kvp_value_get_glist( result_value );
    g_assert( result_list );
    g_assert_cmpint( g_list_length( result_list ), == , 2 );
    result_value = g_list_first( result_list )->data;
    g_assert( result_value );
    g_assert( kvp_value_get_type( result_value ) == KVP_TYPE_FRAME );
    g_assert( kvp_value_get_frame( result_value ) == test_frame );
    result_value = g_list_next( result_list )->data;
    g_assert( result_value );
    g_assert( kvp_value_get_type( result_value ) == KVP_TYPE_FRAME );
    g_assert( kvp_value_get_frame( result_value ) == test_frame2 );

    g_test_message( "Test when bag exists on the path new values are added to it" );
    test_frame3 = kvp_frame_new();
    kvp_frame_add_frame_nc( fixture->frame, "/test/test2/test3", test_frame3 );
    result_value = kvp_frame_get_value( fixture->frame, "/test/test2/test3" );
    g_assert( result_list == kvp_value_get_glist( result_value ) ); /* same list used */
    g_assert_cmpint( g_list_length( result_list ), == , 3 );
    result_value = g_list_first( result_list )->data;
    g_assert( result_value );
    g_assert( kvp_value_get_type( result_value ) == KVP_TYPE_FRAME );
    g_assert( kvp_value_get_frame( result_value ) == test_frame );
    result_value = g_list_next( result_list )->data;
    g_assert( result_value );
    g_assert( kvp_value_get_type( result_value ) == KVP_TYPE_FRAME );
    g_assert( kvp_value_get_frame( result_value ) == test_frame2 );
    result_value = g_list_last( result_list )->data;
    g_assert( result_value );
    g_assert( kvp_value_get_type( result_value ) == KVP_TYPE_FRAME );
    g_assert( kvp_value_get_frame( result_value ) == test_frame3 );
}

static void
test_kvp_value_copy( void )
{
    KvpValue *gint64_orig_value, *gint64_copy_value;
    KvpValue *double_orig_value, *double_copy_value;
    KvpValue *numeric_orig_value, *numeric_copy_value;
    KvpValue *string_orig_value, *string_copy_value;
    KvpValue *guid_orig_value, *guid_copy_value;
    KvpValue *timespec_orig_value, *timespec_copy_value;
    KvpValue *glist_orig_value, *glist_copy_value;
    KvpValue *frame_orig_value, *frame_copy_value;

    /* data init */
    gnc_numeric gnc_numeric_orig, gnc_numeric_copy;
    GncGUID *guid_orig, *guid_copy;
    Timespec ts_orig, ts_copy;
    GList *list_orig, *list_copy;
    KvpFrame *frame_orig, *frame_copy;

    gnc_numeric_orig = gnc_numeric_zero();
    guid_orig = guid_malloc();
    guid_new( guid_orig );
    ts_orig.tv_sec = 1;
    ts_orig.tv_nsec = 1;
    list_orig = NULL;
    list_orig = g_list_append( list_orig, kvp_value_new_string( "abcdefghijklmnop" ) );
    frame_orig = kvp_frame_new();

    g_test_message( "Test creates original values and checks copies of them" );
    gint64_orig_value = kvp_value_new_gint64( 2 );
    double_orig_value = kvp_value_new_double( 3.3 );
    numeric_orig_value = kvp_value_new_gnc_numeric( gnc_numeric_orig );
    string_orig_value = kvp_value_new_string( "abcdefghijklmnop" );
    guid_orig_value = kvp_value_new_guid( guid_orig );
    timespec_orig_value = kvp_value_new_timespec( ts_orig );
    glist_orig_value = kvp_value_new_glist( list_orig );
    frame_orig_value = kvp_value_new_frame( frame_orig );
    g_assert( gint64_orig_value && double_orig_value && numeric_orig_value && string_orig_value
              && guid_orig_value && timespec_orig_value && glist_orig_value && frame_orig_value );

    /* copy values */
    gint64_copy_value = kvp_value_copy( gint64_orig_value );
    g_assert( gint64_copy_value );
    g_assert( gint64_copy_value != gint64_orig_value );
    g_assert( kvp_value_get_type( gint64_copy_value ) == KVP_TYPE_GINT64 );
    g_assert_cmpint( kvp_value_get_gint64( gint64_copy_value ), == , 2 );

    double_copy_value = kvp_value_copy( double_orig_value );
    g_assert( double_copy_value );
    g_assert( double_copy_value != double_orig_value );
    g_assert( kvp_value_get_type( double_copy_value ) == KVP_TYPE_DOUBLE );
    g_assert_cmpfloat( kvp_value_get_double( double_copy_value ), == , 3.3 );

    numeric_copy_value = kvp_value_copy( numeric_orig_value );
    g_assert( numeric_copy_value );
    g_assert( numeric_copy_value != numeric_orig_value );
    g_assert( kvp_value_get_type( numeric_copy_value ) == KVP_TYPE_NUMERIC );
    gnc_numeric_copy = kvp_value_get_numeric( numeric_copy_value );
    g_assert_cmpfloat( gnc_numeric_copy.num, == , gnc_numeric_orig.num );
    g_assert_cmpfloat( gnc_numeric_copy.denom, == , gnc_numeric_orig.denom );

    string_copy_value = kvp_value_copy( string_orig_value );
    g_assert( string_copy_value );
    g_assert( string_copy_value != string_orig_value );
    g_assert( kvp_value_get_type( string_copy_value ) == KVP_TYPE_STRING );
    g_assert_cmpstr( kvp_value_get_string( string_copy_value ), == , "abcdefghijklmnop" );

    guid_copy_value = kvp_value_copy( guid_orig_value );
    g_assert( guid_copy_value );
    g_assert( guid_copy_value != guid_orig_value );
    g_assert( kvp_value_get_type( guid_copy_value ) == KVP_TYPE_GUID );
    guid_copy = kvp_value_get_guid( guid_copy_value );
    g_assert( guid_orig != guid_copy );
    g_assert( guid_equal( guid_orig, guid_copy ) );

    timespec_copy_value = kvp_value_copy( timespec_orig_value );
    g_assert( timespec_copy_value );
    g_assert( timespec_copy_value != timespec_orig_value );
    g_assert( kvp_value_get_type( timespec_copy_value ) == KVP_TYPE_TIMESPEC );
    ts_copy = kvp_value_get_timespec( timespec_copy_value );
    g_assert_cmpfloat( ts_copy.tv_sec, == , ts_orig.tv_sec );
    g_assert_cmpfloat( ts_copy.tv_nsec, == , ts_orig.tv_nsec );

    glist_copy_value = kvp_value_copy( glist_orig_value );
    g_assert( glist_copy_value );
    g_assert( glist_copy_value != glist_orig_value );
    g_assert( kvp_value_get_type( glist_copy_value ) == KVP_TYPE_GLIST );
    list_copy = kvp_value_get_glist( glist_copy_value );
    g_assert( list_copy != list_orig );
    g_assert_cmpint( g_list_length( list_copy ), == , g_list_length( list_orig ) );
    g_assert_cmpint( kvp_glist_compare( list_orig, list_copy ), == , 0 );

    frame_copy_value = kvp_value_copy( frame_orig_value );
    g_assert( frame_copy_value );
    g_assert( frame_copy_value != frame_orig_value );
    g_assert( kvp_value_get_type( frame_copy_value ) == KVP_TYPE_FRAME );
    frame_copy = kvp_value_get_frame( frame_copy_value );
    g_assert_cmpint( kvp_frame_compare( frame_orig, frame_copy ), == , 0 );

    /* destroy objects */
    kvp_value_delete( gint64_orig_value );
    kvp_value_delete( double_orig_value );
    kvp_value_delete( numeric_orig_value );
    kvp_value_delete( string_orig_value );
    kvp_value_delete( guid_orig_value );
    kvp_value_delete( timespec_orig_value );
    kvp_value_delete( glist_orig_value );
    kvp_value_delete( frame_orig_value );

    kvp_value_delete( gint64_copy_value );
    kvp_value_delete( double_copy_value );
    kvp_value_delete( numeric_copy_value );
    kvp_value_delete( string_copy_value );
    kvp_value_delete( guid_copy_value );
    kvp_value_delete( timespec_copy_value );
    kvp_value_delete( glist_copy_value );
    kvp_value_delete( frame_copy_value );
}

static void
test_kvp_glist_copy( void )
{
    GList *value_list = NULL, *copy_list = NULL, *lp1 = NULL, *lp2 = NULL;
    KvpValue *gint64_value;
    KvpValue *double_value;
    KvpValue *numeric_value;
    KvpValue *string_value;
    KvpValue *guid_value;
    KvpValue *timespec_value;
    KvpValue *glist_value;
    KvpValue *frame_value;

    gnc_numeric gnc_numeric_orig;
    GncGUID *guid_orig;
    Timespec ts_orig;
    GList *list_orig;
    KvpFrame *frame_orig;

    gnc_numeric_orig = gnc_numeric_zero();
    guid_orig = guid_malloc();
    guid_new( guid_orig );
    ts_orig.tv_sec = 1;
    ts_orig.tv_nsec = 1;
    list_orig = NULL;
    list_orig = g_list_append( list_orig, kvp_value_new_string( "abcdefghijklmnop" ) );
    frame_orig = kvp_frame_new();

    gint64_value = kvp_value_new_gint64( 2 );
    double_value = kvp_value_new_double( 3.3 );
    numeric_value = kvp_value_new_gnc_numeric( gnc_numeric_orig );
    string_value = kvp_value_new_string( "abcdefghijklmnop" );
    guid_value = kvp_value_new_guid( guid_orig );
    timespec_value = kvp_value_new_timespec( ts_orig );
    glist_value = kvp_value_new_glist( list_orig );
    frame_value = kvp_value_new_frame( frame_orig );

    value_list = g_list_append( value_list, gint64_value );
    value_list = g_list_append( value_list, double_value );
    value_list = g_list_append( value_list, numeric_value );
    value_list = g_list_append( value_list, string_value );
    value_list = g_list_append( value_list, guid_value );
    value_list = g_list_append( value_list, timespec_value );
    value_list = g_list_append( value_list, glist_value );
    value_list = g_list_append( value_list, frame_value );
    g_assert( value_list );
    g_assert_cmpint( g_list_length( value_list ), == , 8 );

    g_test_message( "Test list and all values are copied to new list" );
    copy_list = kvp_glist_copy( value_list );
    g_assert( copy_list );
    g_assert( copy_list != value_list );
    g_assert_cmpint( g_list_length( copy_list ), == , 8 );
    lp1 = value_list;
    lp2 = copy_list;
    while (lp1 && lp2)
    {
        KvpValue *v1 = (KvpValue *) lp1->data;
        KvpValue *v2 = (KvpValue *) lp2->data;
        g_assert( v1 != v2 );
        g_assert_cmpint( kvp_value_compare(v1, v2), == , 0 );
        lp1 = lp1->next;
        lp2 = lp2->next;
    }
    g_assert_cmpint( kvp_glist_compare( value_list, copy_list ), == , 0 );

    /* destroy */
    kvp_glist_delete( value_list );
    kvp_glist_delete( copy_list );
}

static void
test_kvp_glist_compare( void )
{
    GList *list1 = NULL, *list2 = NULL;

    KvpValue *gint64_value;
    KvpValue *double_value;
    KvpValue *numeric_value;
    KvpValue *string_value;
    KvpValue *guid_value;
    KvpValue *timespec_value;
    KvpValue *glist_value;
    KvpValue *frame_value;

    gnc_numeric gnc_numeric_orig;
    GncGUID *guid_orig;
    Timespec ts_orig;
    GList *list_orig;
    KvpFrame *frame_orig;

    gnc_numeric_orig = gnc_numeric_zero();
    guid_orig = guid_malloc();
    guid_new( guid_orig );
    ts_orig.tv_sec = 1;
    ts_orig.tv_nsec = 1;
    list_orig = NULL;
    list_orig = g_list_append( list_orig, kvp_value_new_string( "abcdefghijklmnop" ) );
    frame_orig = kvp_frame_new();

    gint64_value = kvp_value_new_gint64( 2 );
    double_value = kvp_value_new_double( 3.3 );
    numeric_value = kvp_value_new_gnc_numeric( gnc_numeric_orig );
    string_value = kvp_value_new_string( "abcdefghijklmnop" );
    guid_value = kvp_value_new_guid( guid_orig );
    timespec_value = kvp_value_new_timespec( ts_orig );
    glist_value = kvp_value_new_glist( list_orig );
    frame_value = kvp_value_new_frame( frame_orig );

    /* init list 1 */
    list1 = g_list_append( list1, gint64_value );
    list1 = g_list_append( list1, double_value );
    list1 = g_list_append( list1, numeric_value );
    list1 = g_list_append( list1, string_value );
    list1 = g_list_append( list1, guid_value );
    list1 = g_list_append( list1, timespec_value );
    list1 = g_list_append( list1, glist_value );
    list1 = g_list_append( list1, frame_value );
    g_assert( list1 );
    g_assert_cmpint( g_list_length( list1 ), == , 8 );

    g_test_message( "Test when list is the same" );
    list2 = list1;
    g_assert_cmpint( kvp_glist_compare( list1, list2 ), == , 0 );

    g_test_message( "Test when list1 is null" );
    g_assert_cmpint( kvp_glist_compare( NULL, list2 ), == , -1 );

    g_test_message( "Test when list2 is null" );
    g_assert_cmpint( kvp_glist_compare( list1, NULL ), == , 1 );

    g_test_message( "Copy list and test they are equal" );
    list2 = kvp_glist_copy( list1 );
    g_assert( list1 != list2 );
    g_assert_cmpint( g_list_length( list1 ), == , g_list_length( list2 ) );
    g_assert_cmpint( kvp_glist_compare( list1, list2 ), == , 0 );

    g_test_message( "Test when list 1 is shorter lists are not equal" );
    list1 = g_list_remove( list1, frame_value );
    g_assert_cmpint( g_list_length( list1 ), == , 7 );
    g_assert_cmpint( g_list_length( list2 ), == , 8 );
    g_assert_cmpint( kvp_glist_compare( list1, list2 ), == , -1 );

    g_test_message( "Test when list 2 is shorter lists are not equal" );
    list1 = g_list_append( list1, frame_value );
    list1 = g_list_append( list1, frame_value );
    g_assert_cmpint( g_list_length( list1 ), == , 9 );
    g_assert_cmpint( g_list_length( list2 ), == , 8 );
    g_assert_cmpint( kvp_glist_compare( list1, list2 ), == , 1 );

    g_test_message( "Test when data is not equal lists are not equal" );
    list1 = g_list_remove( list1, frame_value );
    g_assert_cmpint( g_list_length( list1 ), == , g_list_length( list2 ) );
    g_assert_cmpint( kvp_glist_compare( list1, list2 ), == , 0 );
    list1 = g_list_remove( list1, gint64_value );
    kvp_value_delete( gint64_value );
    list1 = g_list_prepend( list1, kvp_value_new_gint64( 5 ) );
    g_assert_cmpint( g_list_length( list1 ), == , g_list_length( list2 ) );
    g_assert_cmpint( kvp_glist_compare( list1, list2 ), != , 0 );

    /* delete lists */
    kvp_glist_delete( list1 );
    kvp_glist_delete( list2 );
}

static void
test_kvp_value_compare( void )
{
    KvpValue *gint64_orig_value, *gint64_copy_value;
    KvpValue *double_orig_value, *double_copy_value;
    KvpValue *numeric_orig_value, *numeric_copy_value;
    KvpValue *string_orig_value, *string_copy_value;
    KvpValue *guid_orig_value, *guid_copy_value;
    KvpValue *timespec_orig_value, *timespec_copy_value;
    KvpValue *glist_orig_value, *glist_copy_value;
    KvpValue *frame_orig_value, *frame_copy_value;

    /* data init */
    gnc_numeric gnc_numeric_orig, gnc_numeric_copy;
    GncGUID *guid_orig, *guid_copy;
    Timespec ts_orig, ts_copy;
    GList *list_orig, *list_copy;
    KvpFrame *frame_orig, *frame_copy;

    gnc_numeric_orig = gnc_numeric_zero();
    gnc_numeric_copy = gnc_numeric_zero();
    guid_orig = guid_malloc();
    guid_new( guid_orig );
    guid_copy = guid_malloc();
    guid_new( guid_copy );
    ts_orig.tv_sec = 1;
    ts_orig.tv_nsec = 1;
    ts_copy.tv_sec = 2;
    ts_copy.tv_nsec = 2;
    list_orig = NULL;
    list_orig = g_list_append( list_orig, kvp_value_new_string( "abcdefghijklmnop" ) );
    list_copy = NULL;
    list_copy = g_list_append( list_copy, kvp_value_new_string( "abcdefg" ) );
    frame_orig = kvp_frame_new();
    frame_copy = kvp_frame_new();

    gint64_orig_value = kvp_value_new_gint64( 2 );
    gint64_copy_value = kvp_value_new_gint64( 5 );
    double_orig_value = kvp_value_new_double( 3.3 );
    double_copy_value = kvp_value_new_double( 3.5 );
    numeric_orig_value = kvp_value_new_gnc_numeric( gnc_numeric_orig );
    numeric_copy_value = kvp_value_new_gnc_numeric( gnc_numeric_copy );
    string_orig_value = kvp_value_new_string( "abcdefghijklmnop" );
    string_copy_value = kvp_value_new_string( "abcdefghijklmnop" );
    guid_orig_value = kvp_value_new_guid( guid_orig );
    guid_copy_value = kvp_value_new_guid( guid_copy );
    timespec_orig_value = kvp_value_new_timespec( ts_orig );
    timespec_copy_value = kvp_value_new_timespec( ts_copy );
    glist_orig_value = kvp_value_new_glist( list_orig );
    glist_copy_value = kvp_value_new_glist( list_copy );
    frame_orig_value = kvp_value_new_frame( frame_orig );
    frame_copy_value = kvp_value_new_frame( frame_copy );

    g_test_message( "Test the same kvpvalue is equal" );
    g_assert_cmpint( kvp_value_compare( gint64_orig_value, gint64_orig_value ), == , 0 );

    g_test_message( "Test first value is null" );
    g_assert_cmpint( kvp_value_compare( NULL, gint64_orig_value ), == , -1 );

    g_test_message( "Test second value is null" );
    g_assert_cmpint( kvp_value_compare( gint64_orig_value, NULL ), == , 1 );

    g_test_message( "Test diffrent data types first is lesser" );
    g_assert_cmpint( kvp_value_compare( gint64_orig_value, double_orig_value ), == , -1 );

    g_test_message( "Test diffrent data types second is lesser" );
    g_assert_cmpint( kvp_value_compare( double_orig_value, gint64_orig_value ), == , 1 );

    /* testing all different cases of data equality is not the aim
     * of this test. Rather we check that all data types are being compared.
     */
    g_test_message( "Test different kvpvalues of all the types" );
    g_assert_cmpint( kvp_value_compare( gint64_orig_value, gint64_copy_value ), == , -1 );
    g_assert_cmpint( kvp_value_compare( gint64_copy_value, gint64_orig_value ), == , 1 );
    g_assert_cmpint( kvp_value_compare( double_orig_value, double_copy_value ), == , double_compare( 3.3, 3.5 ) );
    g_assert_cmpint( kvp_value_compare( numeric_orig_value, numeric_copy_value ), == , gnc_numeric_compare( gnc_numeric_orig, gnc_numeric_copy ) );
    g_assert_cmpint( kvp_value_compare( string_orig_value, string_copy_value ), == , strcmp( "abcdefghijklmnop", "abcdefghijklmnop" ) );
    g_assert_cmpint( kvp_value_compare( guid_orig_value, guid_copy_value ), == , guid_compare( guid_orig, guid_copy ) );
    g_assert_cmpint( kvp_value_compare( timespec_orig_value, timespec_copy_value ), == , timespec_cmp( &ts_orig, &ts_copy ) );
    g_assert_cmpint( kvp_value_compare( glist_orig_value, glist_copy_value ), == , kvp_glist_compare( list_orig, list_copy ) );
    g_assert_cmpint( kvp_value_compare( frame_orig_value, frame_copy_value ), == , kvp_frame_compare( frame_orig, frame_copy ) );

    /* destroy objects */
    kvp_value_delete( gint64_orig_value );
    kvp_value_delete( double_orig_value );
    kvp_value_delete( numeric_orig_value );
    kvp_value_delete( string_orig_value );
    kvp_value_delete( guid_orig_value );
    kvp_value_delete( timespec_orig_value );
    kvp_value_delete( glist_orig_value );
    kvp_value_delete( frame_orig_value );

    kvp_value_delete( gint64_copy_value );
    kvp_value_delete( double_copy_value );
    kvp_value_delete( numeric_copy_value );
    kvp_value_delete( string_copy_value );
    kvp_value_delete( guid_copy_value );
    kvp_value_delete( timespec_copy_value );
    kvp_value_delete( glist_copy_value );
    kvp_value_delete( frame_copy_value );
}

static void
test_kvp_value_new_foo_nc( void )
{
    KvpValue *binary_value_nc, *glist_value_nc, *frame_value_nc;
    void *val;
    guint64 size;
    GList *list = NULL;
    KvpFrame *frame = NULL;

    g_test_message( "Test new binary values are not copied" );
    val = g_new0( char, 5 );
    size = sizeof( val );
    binary_value_nc = kvp_value_new_binary_nc( val, size );
    g_assert( binary_value_nc );
    g_assert( kvp_value_get_type( binary_value_nc ) == KVP_TYPE_BINARY );
    g_assert( kvp_value_get_binary( binary_value_nc, &size ) == val );

    g_test_message( "Test new glist is not copied" );
    list = g_list_append( list, kvp_value_new_gint64( 2 ) );
    g_assert_cmpint( g_list_length( list ), == , 1 );
    glist_value_nc = kvp_value_new_glist_nc( list );
    g_assert( glist_value_nc );
    g_assert( kvp_value_get_type( glist_value_nc ) == KVP_TYPE_GLIST );
    g_assert( kvp_value_get_glist( glist_value_nc ) == list );

    g_test_message( "Test new frame is not copied" );
    frame = kvp_frame_new();
    frame_value_nc = kvp_value_new_frame_nc( frame );
    g_assert( frame_value_nc );
    g_assert( kvp_value_get_type( frame_value_nc ) == KVP_TYPE_FRAME );
    g_assert( kvp_value_get_frame( frame_value_nc ) == frame );

    kvp_value_delete( binary_value_nc );
    kvp_value_delete( glist_value_nc );
    kvp_value_delete( frame_value_nc );
}

static void
test_kvp_frame_compare( Fixture *fixture, gconstpointer pData )
{
    KvpFrame *cmp_frame = NULL;

    cmp_frame = kvp_frame_new();
    g_assert( cmp_frame );

    g_test_message( "Test the same frame is equal with itself" );
    g_assert_cmpint( kvp_frame_compare( fixture->frame, fixture->frame ), == , 0 );

    g_test_message( "Test first frame null second not null" );
    g_assert_cmpint( kvp_frame_compare( NULL, fixture->frame ), == , -1 );

    g_test_message( "Test first frame not null second null" );
    g_assert_cmpint( kvp_frame_compare( fixture->frame, NULL ), == , 1 );

    g_test_message( "Test first frame is empty second not empty" );
    kvp_frame_set_gint64( fixture->frame, "/test/test2", 64 );
    g_assert( !kvp_frame_is_empty( fixture->frame ) );
    g_assert( kvp_frame_is_empty( cmp_frame ) );
    g_assert_cmpint( kvp_frame_compare( cmp_frame, fixture->frame ), == , -1 );

    g_test_message( "Test first frame is not empty second is empty" );
    g_assert_cmpint( kvp_frame_compare( fixture->frame, cmp_frame ), == , 1 );

    g_test_message( "Test when frames are equal" );
    kvp_frame_set_gint64( cmp_frame, "/test/test2", 64 );
    g_assert( !kvp_frame_is_empty( cmp_frame ) );
    g_assert_cmpint( kvp_frame_compare( fixture->frame, cmp_frame ), == , 0 );

    g_test_message( "Test when frames have equal data but second frame has additional slot set" );
    kvp_frame_set_string( fixture->frame, "/test/test3", "abcdefghijklmnop" );
    g_assert_cmpint( kvp_frame_compare( cmp_frame, fixture->frame ), == , -1 );

    g_test_message( "Test when frames have equal data but first frame has additional slot set" );
    g_assert_cmpint( kvp_frame_compare( fixture->frame, cmp_frame ), == , 1 );

    g_test_message( "Test when frames have equal number of slots second frame has different data in one slot" );
    kvp_frame_set_string( cmp_frame, "/test/test3", "abcdefg" );
    g_assert_cmpint( kvp_frame_compare( cmp_frame, fixture->frame ), < , 0 );

    g_test_message( "Test when frames have equal number of slots second frame has different data in one slot" );
    g_assert_cmpint( kvp_frame_compare( fixture->frame, cmp_frame ), > , 0 );

    kvp_frame_delete( cmp_frame );
}

static void
test_binary_to_string( void )
{
    gchar *result;
    guchar *val;
    guint32 size;
    val = g_new0( guchar, 5 );
    size = 5 * sizeof( guchar );
    val[0] = (guchar) 0;
    val[1] = (guchar) 9;
    val[2] = (guchar) 10;
    val[3] = (guchar) 255;
    val[4] = (guchar) 256;

    result = binary_to_string( val, size );
    g_assert( result );
    g_assert_cmpstr( result, == , "00090aff00" );

    g_free( val );
    g_free( result );
}

static void
test_kvp_value_to_string( void )
{
    const gchar *str_tmp;
    gchar *str_tmp2, *str_tmp3;
    gchar *result;
    KvpValue *gint64_value;
    KvpValue *double_value;
    KvpValue *numeric_value;
    KvpValue *string_value;
    KvpValue *guid_value;
    KvpValue *timespec_value;
    KvpValue *glist_value;
    KvpValue *frame_value;

    gnc_numeric gnc_numeric_orig;
    GncGUID *guid_orig;
    Timespec ts_orig;
    GList *list_orig;
    KvpFrame *frame_orig;

    gnc_numeric_orig = gnc_numeric_zero();
    guid_orig = guid_malloc();
    guid_new( guid_orig );
    ts_orig.tv_sec = 1;
    ts_orig.tv_nsec = 1;
    list_orig = NULL;
    list_orig = g_list_append( list_orig, kvp_value_new_string( "abcdefghijklmnop" ) );
    frame_orig = kvp_frame_new();

    gint64_value = kvp_value_new_gint64( 2 );
    double_value = kvp_value_new_double( 3.3 );
    numeric_value = kvp_value_new_gnc_numeric( gnc_numeric_orig );
    string_value = kvp_value_new_string( "abcdefghijklmnop" );
    guid_value = kvp_value_new_guid( guid_orig );
    timespec_value = kvp_value_new_timespec( ts_orig );
    glist_value = kvp_value_new_glist( list_orig );
    frame_value = kvp_value_new_frame( frame_orig );

    g_test_message( "Test value string representation with different data types" );
    result = kvp_value_to_string( gint64_value );
    g_assert( result );
    g_assert_cmpstr( result, == , "KVP_VALUE_GINT64(2)" );
    g_free( result );

    result = kvp_value_to_string( double_value );
    g_assert( result );
    g_assert_cmpstr( result, == , "KVP_VALUE_DOUBLE(3.3)" );
    g_free( result );

    result = kvp_value_to_string( numeric_value );
    g_assert( result );
    g_assert_cmpstr( result, == , "KVP_VALUE_NUMERIC(0/1)" );
    g_free( result );

    result = kvp_value_to_string( string_value );
    g_assert( result );
    g_assert_cmpstr( result, == , "KVP_VALUE_STRING(abcdefghijklmnop)" );
    g_free( result );

    result = kvp_value_to_string( guid_value );
    g_assert( result );
    str_tmp = guid_to_string( kvp_value_get_guid( guid_value ) );
    str_tmp2 = g_strdup_printf("KVP_VALUE_GUID(%s)", str_tmp ? str_tmp : "");
    g_assert_cmpstr( result, == , str_tmp2 );
    g_free( result );
    g_free( str_tmp2 );

    result = kvp_value_to_string( timespec_value );
    g_assert( result );
    str_tmp2 = g_new0 (char, 40);
    gnc_timespec_to_iso8601_buff( kvp_value_get_timespec( timespec_value ), str_tmp2 );
    str_tmp3 = g_strdup_printf("KVP_VALUE_TIMESPEC(%s)", str_tmp2);
    g_assert_cmpstr( result, == , str_tmp3 );
    g_free( result );
    g_free( str_tmp2 );
    g_free( str_tmp3 );

    result = kvp_value_to_string( glist_value );
    g_assert( result );
    g_assert_cmpstr( result, == , "KVP_VALUE_GLIST([  KVP_VALUE_STRING(abcdefghijklmnop), ])" );
    g_free( result );

    result = kvp_value_to_string( frame_value );
    g_assert( result );
    g_assert_cmpstr( result, == , "KVP_VALUE_FRAME({\n}\n)" );
    g_free( result );

    kvp_value_delete( gint64_value );
    kvp_value_delete( double_value );
    kvp_value_delete( numeric_value );
    kvp_value_delete( string_value );
    kvp_value_delete( guid_value );
    kvp_value_delete( timespec_value );
    kvp_value_delete( glist_value );
    kvp_value_delete( frame_value );
}

static void
test_kvp_frame_to_string( Fixture *fixture, gconstpointer pData )
{
    gchar *result;
    gnc_numeric test_gnc_numeric;
    GncGUID *test_guid;
    Timespec test_ts;
    KvpFrame *test_frame;

    test_gnc_numeric = gnc_numeric_zero();
    test_guid = guid_malloc();
    guid_new( test_guid );
    test_ts.tv_sec = 1;
    test_ts.tv_nsec = 1;
    test_frame = kvp_frame_new();

    g_assert( fixture->frame );
    g_assert( kvp_frame_is_empty( fixture->frame ) );

    g_test_message( "Test empty frame" );
    result = kvp_frame_to_string( fixture->frame );
    g_assert_cmpstr( result, == , "{\n}\n" );
    g_free( result );

    /* slots can be randomly distributed in hash table
     * instead of checking the whole return string we rather check if certain entries exist in it
     */
    g_test_message( "Test with all data types and nested frames" );
    kvp_frame_set_gint64( fixture->frame, "/gint64-type", 2 );
    result = kvp_frame_to_string( fixture->frame );
    g_assert( g_strrstr( result, "    gint64-type => KVP_VALUE_GINT64(2),\n" ) != NULL );
    g_free( result );

    kvp_frame_set_double( fixture->frame, "/double-type", 3.3 );
    result = kvp_frame_to_string( fixture->frame );
    g_assert( g_strrstr( result, "    double-type => KVP_VALUE_DOUBLE(3.3),\n" ) != NULL );
    g_free( result );

    kvp_frame_set_numeric( fixture->frame, "/numeric-type", test_gnc_numeric );
    result = kvp_frame_to_string( fixture->frame );
    g_assert( g_strrstr( result, "    numeric-type => KVP_VALUE_NUMERIC(0/1),\n" ) != NULL );
    g_free( result );

    kvp_frame_set_timespec( fixture->frame, "/timespec-type", test_ts );
    result = kvp_frame_to_string( fixture->frame );
    g_assert( g_strrstr( result, "    timespec-type => KVP_VALUE_TIMESPEC" ) != NULL );
    g_free( result );

    kvp_frame_set_string( fixture->frame, "/string-type", "abcdefghijklmnop" );
    result = kvp_frame_to_string( fixture->frame );
    g_assert( g_strrstr( result, "    string-type => KVP_VALUE_STRING(abcdefghijklmnop),\n" ) != NULL );
    g_free( result );

    kvp_frame_set_guid( fixture->frame, "/guid-type", test_guid );
    result = kvp_frame_to_string( fixture->frame );
    g_assert( g_strrstr( result, "    guid-type => KVP_VALUE_GUID" ) != NULL );
    g_free( result );

    kvp_frame_set_frame( fixture->frame, "/nested/frame-type", test_frame );
    result = kvp_frame_to_string( fixture->frame );
    g_assert( g_strrstr( result, "    nested => KVP_VALUE_FRAME({\n    frame-type => KVP_VALUE_FRAME({\n}\n),\n}\n),\n" ) != NULL );
    g_free( result );
}

static void
test_kvp_frame_set_slot_path( Fixture *fixture, gconstpointer pData )
{
    GHashTable *frame_hash = NULL;
    KvpValue *input_value, *output_value;

    g_assert( fixture->frame );
    g_assert( kvp_frame_is_empty( fixture->frame ) );

    g_test_message( "Test with a simple value added to the empty frame" );
    input_value = kvp_value_new_gint64( 2 );
    kvp_frame_set_slot_path( fixture->frame, input_value, "test", NULL );
    output_value = kvp_frame_get_slot_path( fixture->frame, "test", NULL );
    g_assert( output_value );
    g_assert( input_value != output_value ); /* copied */
    g_assert_cmpint( kvp_value_compare( output_value, input_value ), == , 0 );
    kvp_value_delete( input_value );

    g_test_message( "Test when value is being replaced" );
    input_value = kvp_value_new_double( 3.3 );
    kvp_frame_set_slot_path( fixture->frame, input_value, "test", NULL );
    output_value = kvp_frame_get_slot_path( fixture->frame, "test", NULL );
    g_assert( output_value );
    g_assert( input_value != output_value ); /* copied */
    g_assert_cmpint( kvp_value_compare( output_value, input_value ), == , 0 ); /* old value removed */
    frame_hash = kvp_frame_get_hash( fixture->frame );
    g_assert( frame_hash );
    g_assert_cmpint( g_hash_table_size( frame_hash ), == , 1 ); /* be sure it was replaced */
    kvp_value_delete( input_value );

    g_test_message( "Test when existing path elements are not frames" );
    input_value = kvp_value_new_string( "abcdefghijklmnop" );
    kvp_frame_set_slot_path( fixture->frame, input_value, "test", "test2", NULL );
    g_assert( kvp_frame_get_slot_path( fixture->frame, "test2", NULL ) == NULL );/* was not added */
    g_assert_cmpint( kvp_value_compare( output_value, kvp_frame_get_slot_path( fixture->frame, "test", NULL ) ), == , 0 ); /* nothing changed */
    g_assert_cmpint( g_hash_table_size( frame_hash ), == , 1 ); /* didn't change */
    kvp_value_delete( input_value );

    g_test_message( "Test frames are created along the path when needed" );
    input_value = kvp_value_new_string( "abcdefghijklmnop" );
    kvp_frame_set_slot_path( fixture->frame, input_value, "test2", "test3", NULL );
    output_value = kvp_frame_get_slot_path( fixture->frame, "test2", NULL );
    g_assert( output_value );
    g_assert( kvp_value_get_type( output_value ) == KVP_TYPE_FRAME );
    output_value = kvp_frame_get_slot_path( fixture->frame, "test2", "test3", NULL );
    g_assert( output_value );
    g_assert( input_value != output_value ); /* copied */
    g_assert_cmpint( kvp_value_compare( output_value, input_value ), == , 0 );
    g_assert_cmpint( g_hash_table_size( frame_hash ), == , 2 );
    kvp_value_delete( input_value );
}

static void
test_kvp_frame_set_slot_path_gslist( Fixture *fixture, gconstpointer pData )
{
    /* similar to previous test except path is passed as GSList*/
    GSList *path_list = NULL;
    GHashTable *frame_hash = NULL;
    KvpValue *input_value, *output_value;

    g_assert( fixture->frame );
    g_assert( kvp_frame_is_empty( fixture->frame ) );

    g_test_message( "Test with a simple value added to the empty frame" );
    path_list = g_slist_append( path_list, "test" );
    input_value = kvp_value_new_gint64( 2 );
    kvp_frame_set_slot_path_gslist( fixture->frame, input_value, path_list );
    output_value = kvp_frame_get_slot_path( fixture->frame, "test", NULL );
    g_assert( output_value );
    g_assert( input_value != output_value ); /* copied */
    g_assert_cmpint( kvp_value_compare( output_value, input_value ), == , 0 );
    kvp_value_delete( input_value );

    g_test_message( "Test when value is being replaced" );
    input_value = kvp_value_new_double( 3.3 );
    kvp_frame_set_slot_path_gslist( fixture->frame, input_value, path_list );
    output_value = kvp_frame_get_slot_path( fixture->frame, "test", NULL );
    g_assert( output_value );
    g_assert( input_value != output_value ); /* copied */
    g_assert_cmpint( kvp_value_compare( output_value, input_value ), == , 0 ); /* old value removed */
    frame_hash = kvp_frame_get_hash( fixture->frame );
    g_assert( frame_hash );
    g_assert_cmpint( g_hash_table_size( frame_hash ), == , 1 ); /* be sure it was replaced */
    kvp_value_delete( input_value );

    g_test_message( "Test when existing path elements are not frames" );
    path_list = g_slist_append( path_list, "test2");
    input_value = kvp_value_new_string( "abcdefghijklmnop" );
    kvp_frame_set_slot_path_gslist( fixture->frame, input_value, path_list );
    g_assert( kvp_frame_get_slot_path( fixture->frame, "test2", NULL ) == NULL );/* was not added */
    g_assert_cmpint( kvp_value_compare( output_value, kvp_frame_get_slot_path( fixture->frame, "test", NULL ) ), == , 0 ); /* nothing changed */
    g_assert_cmpint( g_hash_table_size( frame_hash ), == , 1 ); /* didn't change */
    kvp_value_delete( input_value );

    g_test_message( "Test frames are created along the path when needed" );
    path_list = g_slist_remove( path_list, "test" );
    path_list = g_slist_append( path_list, "test3");
    input_value = kvp_value_new_string( "abcdefghijklmnop" );
    kvp_frame_set_slot_path_gslist( fixture->frame, input_value, path_list );
    output_value = kvp_frame_get_slot_path( fixture->frame, "test2", NULL );
    g_assert( output_value );
    g_assert( kvp_value_get_type( output_value ) == KVP_TYPE_FRAME );
    output_value = kvp_frame_get_slot_path( fixture->frame, "test2", "test3", NULL );
    g_assert( output_value );
    g_assert( input_value != output_value ); /* copied */
    g_assert_cmpint( kvp_value_compare( output_value, input_value ), == , 0 );
    g_assert_cmpint( g_hash_table_size( frame_hash ), == , 2 );
    kvp_value_delete( input_value );

    g_slist_free( path_list );
}

static void
test_kvp_frame_replace_slot_nc( Fixture *fixture, gconstpointer pData )
{
    GHashTable *frame_hash;
    KvpValue *orig_value, *orig_value2, *copy_value;
    /* test indirectly static function kvp_frame_replace_slot_nc */
    g_assert( fixture->frame );
    g_assert( kvp_frame_is_empty( fixture->frame ) );

    g_test_message( "Test when new value is created frame hash init and value stored in hash" );
    orig_value = kvp_value_new_gint64( 2 );
    kvp_frame_set_slot( fixture->frame, "test", orig_value );
    g_assert( !kvp_frame_is_empty( fixture->frame ) );
    frame_hash = kvp_frame_get_hash( fixture->frame );
    g_assert( frame_hash );
    g_assert_cmpint( g_hash_table_size( frame_hash ), == , 1 );
    copy_value = g_hash_table_lookup( frame_hash, "test" );
    g_assert( orig_value != copy_value );
    g_assert_cmpint( kvp_value_compare( orig_value, copy_value ), == , 0 );

    g_test_message( "Test when value is replaced" );
    orig_value2 = kvp_value_new_gint64( 5 );
    kvp_frame_set_slot( fixture->frame, "test", orig_value2 );
    frame_hash = kvp_frame_get_hash( fixture->frame );
    g_assert( frame_hash );
    g_assert_cmpint( g_hash_table_size( frame_hash ), == , 1 );
    copy_value = g_hash_table_lookup( frame_hash, "test" );
    g_assert( orig_value2 != copy_value );
    g_assert_cmpint( kvp_value_compare( orig_value2, copy_value ), == , 0 );
    g_assert_cmpint( kvp_value_compare( orig_value, copy_value ), != , 0 );

    kvp_value_delete( orig_value );
    kvp_value_delete( orig_value2 );
}

static void
test_get_trailer_make( Fixture *fixture, gconstpointer pData )
{
    char *last_key = NULL;
    KvpValue *frame_value = NULL;
    KvpFrame *frame = NULL, *frame2 = NULL;

    g_test_message( "Test null frame and empty string checks" );
    g_assert( p_get_trailer_make( NULL, "test", &last_key ) == NULL );
    g_assert( !last_key );
    g_assert( p_get_trailer_make( fixture->frame, NULL, &last_key ) == NULL );
    g_assert( !last_key );
    g_assert( p_get_trailer_make( fixture->frame, "", &last_key ) == NULL );
    g_assert( !last_key );

    g_test_message( "Test single frame on the path with no slash" );
    g_assert( p_get_trailer_make( fixture->frame, "test", &last_key ) == fixture->frame );
    g_assert_cmpstr( last_key, == , "test" );

    g_test_message( "Test single frame on the path with slash" );
    last_key = NULL;
    g_assert( p_get_trailer_make( fixture->frame, "/test", &last_key ) == fixture->frame );
    g_assert_cmpstr( last_key, == , "test" );

    g_test_message( "Test path of trailing slash" );
    last_key = NULL;
    g_assert( p_get_trailer_make( fixture->frame, "test/", &last_key ) == NULL );
    g_assert( !last_key );

    g_test_message( "Test path of two entries: frame for test should be created" );
    /* test is considered to be last frame on the path
     * and it is returned. Currently it doesn't exist and will be created
     * test2 is stripped away and returned as last entry of the path
     */
    last_key = NULL;
    frame = p_get_trailer_make( fixture->frame, "/test/test2", &last_key );
    g_assert( frame );
    g_assert( frame != fixture->frame );
    frame_value = kvp_frame_get_slot( fixture->frame, "test" );
    g_assert( frame_value );
    g_assert( kvp_value_get_frame( frame_value ) == frame );
    frame_value = kvp_frame_get_slot( frame, "test2" );
    g_assert( !frame_value );
    g_assert_cmpstr( last_key, == , "test2" );

    g_test_message( "Test path of two entries: test frame already exist" );
    /* here test frame already exist and should be returned
     */
    last_key = NULL;
    g_assert( frame == p_get_trailer_make( fixture->frame, "/test/test2", &last_key ) );
    g_assert_cmpstr( last_key, == , "test2" );

    g_test_message( "Test path of three entries: neither frame exist" );
    /* test3 and test4 frames will be created. test4 will be created inside test3 frame
     * while test3 inside fixture->frame. test4 will be returned
     * test5 stripped away and returned in last_key
     */
    last_key = NULL;
    frame = p_get_trailer_make( fixture->frame, "/test3/test4/test5", &last_key );
    g_assert( frame );
    g_assert( frame != fixture->frame );
    frame_value = kvp_frame_get_slot( fixture->frame, "test3" );
    g_assert( frame_value );
    frame2 = kvp_value_get_frame( frame_value );
    g_assert( frame2 != frame );
    g_assert( frame2 != fixture->frame );
    frame_value = kvp_frame_get_slot( frame2, "test4" );
    g_assert( frame_value );
    g_assert( kvp_value_get_frame( frame_value ) == frame );
    frame_value = kvp_frame_get_slot( frame, "test5" );
    g_assert( !frame_value );
    g_assert_cmpstr( last_key, == , "test5" );
}

static void
test_kvp_value_glist_to_string( Fixture *fixture, gconstpointer pData )
{
    /*
     * kvp_value_glist_to_string and kvp_value_to_string call each other
     */
    GList *value_list = NULL;
    gchar *result;

    gnc_numeric gnc_numeric_orig;
    GList *list_orig;
    KvpFrame *frame_orig;

    gnc_numeric_orig = gnc_numeric_zero();
    list_orig = NULL;
    list_orig = g_list_append( list_orig, kvp_value_new_string( "abcdefghijklmnop" ) );
    frame_orig = kvp_frame_new();

    g_test_message( "Test empty list" );
    result = p_kvp_value_glist_to_string( value_list );
    g_assert_cmpstr( result, == , "[  ]" );
    g_free( result );

    g_test_message( "Test list with simple and complex values" );
    value_list = g_list_append( value_list, kvp_value_new_gint64( 2 ) );
    value_list = g_list_append( value_list, kvp_value_new_double( 3.3 ) );
    value_list = g_list_append( value_list, kvp_value_new_gnc_numeric( gnc_numeric_orig ) );
    value_list = g_list_append( value_list, kvp_value_new_string( "abcdefghijklmnop" ) );
    value_list = g_list_append( value_list, kvp_value_new_glist( list_orig ) );
    value_list = g_list_append( value_list, kvp_value_new_frame( frame_orig ) );
    g_assert( value_list );
    g_assert_cmpint( g_list_length( value_list ), == , 6 );
    result = p_kvp_value_glist_to_string( value_list );

    g_assert_cmpstr( result, == , "[  KVP_VALUE_GINT64(2), KVP_VALUE_DOUBLE(3.3), KVP_VALUE_NUMERIC(0/1), KVP_VALUE_STRING(abcdefghijklmnop), KVP_VALUE_GLIST([  KVP_VALUE_STRING(abcdefghijklmnop), ]), KVP_VALUE_FRAME({\n}\n), ]" );
    g_free( result );

    kvp_glist_delete( value_list );
}

static void
test_get_or_make( Fixture *fixture, gconstpointer pData )
{
    KvpFrame *test_frame = NULL;

    g_assert( fixture->frame );
    g_assert( kvp_frame_is_empty( fixture->frame ) );

    g_test_message( "Test new frame is created" );
    test_frame = p_get_or_make( fixture->frame, "test" );
    g_assert( test_frame );
    g_assert( test_frame != fixture->frame );
    g_assert( kvp_frame_get_frame( fixture->frame, "test" ) == test_frame );

    g_test_message( "Test existing frame is returned" );
    g_assert( test_frame == p_get_or_make( fixture->frame, "test" ) );
    g_assert( kvp_frame_get_frame( fixture->frame, "test" ) == test_frame );
}

static void
test_kvp_frame_get_frame_or_null_slash_trash( Fixture *fixture, gconstpointer pData )
{
    g_test_message( "Test null checks" );
    g_assert( p_kvp_frame_get_frame_or_null_slash_trash( NULL, "test" ) == NULL );
    g_assert( p_kvp_frame_get_frame_or_null_slash_trash( fixture->frame, NULL ) == NULL );

    g_test_message( "Test single slash and trailing slash path" );
    g_assert( p_kvp_frame_get_frame_or_null_slash_trash( fixture->frame, "/" ) == fixture->frame );
    g_assert( p_kvp_frame_get_frame_or_null_slash_trash( fixture->frame, "////" ) == fixture->frame );

    g_test_message( "Test non existing path" );
    g_assert( p_kvp_frame_get_frame_or_null_slash_trash( fixture->frame, "/test" ) == NULL );

    g_test_message( "Test existing path when value is not frame" );
    kvp_frame_set_gint64( fixture->frame, "/test", 2 );
    g_assert( p_kvp_frame_get_frame_or_null_slash_trash( fixture->frame, "/test" ) == NULL );

    g_test_message( "Test existing path when value is frame" );
    kvp_frame_set_frame( fixture->frame, "/test2", kvp_frame_new() );
    g_assert( p_kvp_frame_get_frame_or_null_slash_trash( fixture->frame, "/test2" ) != NULL );
}

static void
test_get_trailer_or_null( Fixture *fixture, gconstpointer pData )
{
    char *last_key = NULL;
    KvpFrame *frame = NULL;
    const KvpFrame* frame2 = NULL;

    g_test_message( "Test null frame and empty string checks" );
    g_assert( p_get_trailer_or_null( NULL, "test", &last_key ) == NULL );
    g_assert( !last_key );
    g_assert( p_get_trailer_or_null( fixture->frame, NULL, &last_key ) == NULL );
    g_assert( !last_key );
    g_assert( p_get_trailer_or_null( fixture->frame, "", &last_key ) == NULL );
    g_assert( !last_key );

    g_test_message( "Test single frame on the path with no slash" );
    g_assert( p_get_trailer_or_null( fixture->frame, "test", &last_key ) == fixture->frame );
    g_assert_cmpstr( last_key, == , "test" );

    g_test_message( "Test single frame on the path with slash" );
    last_key = NULL;
    g_assert( p_get_trailer_or_null( fixture->frame, "/test", &last_key ) == fixture->frame );
    g_assert_cmpstr( last_key, == , "test" );

    g_test_message( "Test path of trailing slash" );
    last_key = NULL;
    g_assert( p_get_trailer_or_null( fixture->frame, "test/", &last_key ) == NULL );
    g_assert( !last_key );

    g_test_message( "Test with non existing path" );
    last_key = NULL;
    g_assert( p_get_trailer_or_null( fixture->frame, "/test/test2", &last_key ) == NULL );
    g_assert_cmpstr( last_key, == , "test2" );

    g_test_message( "Test with existing path" );
    last_key = NULL;
    frame = kvp_frame_new();
    kvp_frame_set_frame( fixture->frame, "/test/test2", frame );
    frame2 = p_get_trailer_or_null( fixture->frame, "/test/test2", &last_key );
    g_assert( kvp_frame_get_frame( fixture->frame, "/test") == frame2 );
    g_assert_cmpstr( last_key, == , "test2" );
}

void
test_suite_kvp_frame( void )
{
    GNC_TEST_ADD_FUNC( suitename, "kvp frame new and delete", test_kvp_frame_new_delete );
    GNC_TEST_ADD( suitename, "kvp frame copy", Fixture, NULL, setup, test_kvp_frame_copy, teardown );
    GNC_TEST_ADD( suitename, "kvp frame set foo", Fixture, NULL, setup, test_kvp_frame_set_foo, teardown );
    GNC_TEST_ADD( suitename, "kvp frame get frame slash", Fixture, NULL, setup, test_kvp_frame_get_frame_slash, teardown );
    GNC_TEST_ADD( suitename, "kvp frame get slot path", Fixture, NULL, setup, test_kvp_frame_get_slot_path, teardown );
    GNC_TEST_ADD( suitename, "kvp frame get slot path gslist", Fixture, NULL, setup, test_kvp_frame_get_slot_path_gslist, teardown );
    GNC_TEST_ADD( suitename, "kvp frame add frame nc", Fixture, NULL, setup, test_kvp_frame_add_frame_nc, teardown );
    GNC_TEST_ADD_FUNC( suitename, "kvp value copy", test_kvp_value_copy );
    GNC_TEST_ADD_FUNC( suitename, "kvp glist copy", test_kvp_glist_copy );
    GNC_TEST_ADD_FUNC( suitename, "kvp glist compare", test_kvp_glist_compare );
    GNC_TEST_ADD_FUNC( suitename, "kvp value compare", test_kvp_value_compare );
    GNC_TEST_ADD_FUNC( suitename, "kvp value new foo no copy", test_kvp_value_new_foo_nc );
    GNC_TEST_ADD( suitename, "kvp frame compare", Fixture, NULL, setup, test_kvp_frame_compare, teardown );
    GNC_TEST_ADD_FUNC( suitename, "binary to string", test_binary_to_string );
    GNC_TEST_ADD_FUNC( suitename, "kvp value to string", test_kvp_value_to_string );
    GNC_TEST_ADD( suitename, "kvp frame to string", Fixture, NULL, setup, test_kvp_frame_to_string, teardown );
    GNC_TEST_ADD( suitename, "kvp frame set slot path", Fixture, NULL, setup, test_kvp_frame_set_slot_path, teardown );
    GNC_TEST_ADD( suitename, "kvp frame set slot path gslist", Fixture, NULL, setup, test_kvp_frame_set_slot_path_gslist, teardown );
    GNC_TEST_ADD( suitename, "kvp frame replace slot nc", Fixture, NULL, setup, test_kvp_frame_replace_slot_nc, teardown );
    GNC_TEST_ADD( suitename, "get trailer make", Fixture, NULL, setup_static, test_get_trailer_make, teardown_static );
    GNC_TEST_ADD( suitename, "kvp value glist to string", Fixture, NULL, setup_static, test_kvp_value_glist_to_string, teardown_static );
    GNC_TEST_ADD( suitename, "get or make", Fixture, NULL, setup_static, test_get_or_make, teardown_static );
    GNC_TEST_ADD( suitename, "kvp frame get frame or null slash trash", Fixture, NULL, setup_static, test_kvp_frame_get_frame_or_null_slash_trash, teardown_static );
    GNC_TEST_ADD( suitename, "get trailer or null", Fixture, NULL, setup_static, test_get_trailer_or_null, teardown_static );
}
