TestApp.c   [plain text]


#include <stdint.h>
#include <OpenDirectory/OpenDirectory.h>
#include <DirectoryService/DirectoryService.h>
#include <unistd.h>
#include <pthread.h>

uint32_t    gTestCase       = 0;
bool     gLogErrors      = false;
bool     gVerbose        = false;
char        *gLogPath       = NULL;
char        *gAdminAccount  = NULL;
char        *gAdminPassword = NULL;
char        *gProxyHost     = NULL;
char        *gProxyPassword = NULL;
char        *gProxyUser     = NULL;
uint32_t    gTestTimes      = 1;
uint32_t    gCopies         = 1;

void usage( char *argv[] )
{
    fprintf( stderr, "Usage:  TestAppCF -a adminPass -p adminPass [-v] [-e] [-l path] [-N runNumber]\n", argv[0] );
    fprintf( stderr, "                 [-c copies] [-t times] [-u proxyUser -P proxyPass -h proxyHost]\n" );
    fprintf( stderr, "             -v  verbose output\n" );
    fprintf( stderr, "             -e  output error log\n" );
    fprintf( stderr, "             -l  log path (default \"./Logs/<runNumber>/\"\n" );
    fprintf( stderr, "             -N  test run number\n" );
    fprintf( stderr, "             -c  number of copies to fork for stress testing\n" );
    fprintf( stderr, "             -t  number of times to run the test\n" );
    fprintf( stderr, "             -u  Proxy username\n" );
    fprintf( stderr, "             -P  Proxy username password\n" );
    fprintf( stderr, "             -h  Proxy host\n" );
    fprintf( stderr, "             -a  local admin account\n" );
    fprintf( stderr, "             -p  local admin password\n" );
}

void parseOptions( int argc, char *argv[] )
{
    int                 ch;
    
    gTestCase = (uint32_t) CFAbsoluteTimeGetCurrent();
    
    while ((ch = getopt(argc, argv, "vel:N:c:t:u:P:h:a:p:")) != -1)
    {
        switch (ch)
        {
            case 'v':
                gVerbose = true;
                break;
            case 'e':
                gLogErrors = true;
                break;
            case 'l':
                gLogPath = optarg;
                break;
            case 'N':
                gTestCase = strtol( optarg, NULL, 10 );
                break;
            case 'c':
                if( NULL != optarg )
                {
                    gCopies = strtol( optarg, NULL, 10 );
                    if( gCopies > 1024 )
                        gCopies = 1024;
                }
                else
                    usage( argv );
                break;
            case 't':
                if( NULL != optarg )
                {
                    gTestTimes = strtol( optarg, NULL, 10 );
                    if( 0 == gTestTimes )
                        gTestTimes = 1;
                }
                else
                    usage( argv );
                break;
            case 'u':   // proxy user
                gProxyUser = optarg;
                break;
            case 'P':   // proxy password
                gProxyPassword = optarg;
                break;
            case 'h':   // host
                gProxyHost = optarg;
                break;
            case 'a':   // admin
                gAdminAccount = optarg;
                break;
            case 'p':   // local admin password
                gAdminPassword = optarg;
                break;
            case '?':
            default:
                usage( argv );
                exit(1);
        }
    }
    
    if( NULL == gAdminAccount || NULL == gAdminPassword )
    {
        usage( argv );
        exit( 1 );
    }
    else if( NULL != gProxyHost || NULL != gProxyUser || NULL != gProxyHost  )
    {
        if( NULL == gProxyHost || NULL == gProxyUser || NULL == gProxyHost  )
        {
            usage( argv );
            exit( 1 );
        }
    }
}

void searchCallback( ODContextRef inContext, CFMutableArrayRef inResults, CFErrorRef inResultCode )
{
    if( NULL != inResults )
    {
        printf( "Got %d results\n", (int) CFArrayGetCount(inResults) );
    }
    else
    {
        printf( "Got no more results\n" );
    }
}

bool doTestMembership( ODNodeRef inNodeRef, ODRecordType inGroupType, ODRecordType inMemberType )
{
    CFErrorRef  error           = NULL;
    ODRecordRef testmember      = NULL;
    ODRecordRef testcontainer   = NULL;
    bool        bReturn         = false;
    
    CFArrayRef emptyValue = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks );
    
    bool (^testFailure)( bool, const char *, CFErrorRef *) = ^( bool bSuccess, const char *testCase, CFErrorRef *inError )
    {
        bool bFailed = true;
        
        CFErrorRef theError = (*inError);
        if ( theError != NULL ) {
            if ( bSuccess == false ) {
                printf( "%s - FAIL", testCase );
            }
            else {
                printf( "%s - FAIL (error but status says success)", testCase );
            }
            
            CFStringRef errorDesc = CFErrorCopyDescription( theError );
            const char *errorStr = "no error details";
            char buffer[512];
            
            if ( errorDesc != NULL ) {
                CFStringGetCString( errorDesc, buffer, sizeof(buffer), kCFStringEncodingUTF8 );
                errorStr = buffer;
            }
            
            printf( " - (%d) - %s\n", (int) CFErrorGetCode(theError), errorStr );
        }
        else {
            if ( bSuccess == false ) {
                printf( "%s - FAIL - failed but no Error\n", testCase );
            }
            else {
                printf( "%s - SUCCESS\n", testCase );
                bFailed = false;
            }
        }
        
        return bFailed;
    };
    
    char groupType[64];
    char memberType[64];
    
    CFStringGetCString( inGroupType, groupType, sizeof(groupType), kCFStringEncodingUTF8 );

    if ( inMemberType != NULL ) {
        CFStringGetCString( inMemberType, memberType, sizeof(memberType), kCFStringEncodingUTF8 );

        printf( "\n-- Container '%s' - Member '%s'\n", groupType, memberType );

        testmember = ODNodeCreateRecord( inNodeRef, inMemberType, CFSTR("testmember"), NULL, &error );
        if ( testFailure(testmember != NULL, "ODNodeCreateRecord 'testmember'", &error) == true ) {
            goto fail;
        }
    }
    else {
        printf( "\n-- Container '%s' - Member 'NULL'\n", groupType );
    }
    
    testcontainer = ODNodeCreateRecord( inNodeRef, inGroupType, CFSTR("testcontainer"), NULL, &error );
    if ( testFailure(testcontainer != NULL, "ODNodeCreateRecord 'testcontainer'", &error) == true ) {
        goto fail;
    }
    
    if ( inMemberType == NULL )
    {
        if ( ODRecordAddMember(testcontainer, NULL, &error) == true ) {
            printf( "ODRecordAddMember (add NULL member) - FAIL (succeeded)\n" );
        }
        else {
            printf( "ODRecordAddMember (add NULL member) - SUCCESS (got error)\n" );
        }
        goto finish;
    }
    
    bool (^badMixture)( void ) = ^(void) {
        if ( (inGroupType == kODRecordTypeGroups && (inMemberType == kODRecordTypeUsers || inMemberType == kODRecordTypeGroups)) ||
             (inGroupType == kODRecordTypeComputerGroups && (inMemberType == kODRecordTypeComputers || inMemberType == kODRecordTypeComputerGroups)) ||
             (inGroupType == kODRecordTypeComputerLists && (inMemberType == kODRecordTypeComputers || inMemberType == kODRecordTypeComputerLists)) )
        {
            return (bool) false;
        }
        
        return (bool) true;
    };
    
    bool (^addMember)(const char *) = ^(const char *testCase) {
        char errorStr[256];
        CFErrorRef localErr = NULL;
        
        snprintf( errorStr, sizeof(errorStr), "ODRecordAddMember (%s)", testCase );
        
        if ( ODRecordAddMember(testcontainer, testmember, &localErr) == false )
        {
            // need to check types, may expected failure
            if ( badMixture() == false )
            {
                if ( testFailure(false, errorStr, &localErr) == true ) {
                    return (bool) false;
                }
            }
            else {
                printf( "%s - SUCCESS (got error)\n", errorStr );
                CFRelease( localErr );
                localErr = NULL;
            }
        }
        else {
            printf( "%s - SUCCESS\n", errorStr );            
        }
        
        return (bool) true;
    };
    
    //
    // add member with valid UUID
    if ( addMember("has UUID") == false ) goto fail;
    
    //
    // add member without UUID nor UID
    if ( testFailure(ODRecordSetValue(testmember, kODAttributeTypeGUID, emptyValue, &error), 
                     "ODRecordSetValue testmember (remove GUID)", &error) == true )
    {
        goto fail;
    }
    
    // can only do this test if we are users cause group-in-group requires UUID or GID
    if ( inMemberType == kODRecordTypeUsers && addMember("no UUID nor UID/GID, if applicable") == false ) goto fail;
    
    ODAttributeType idAttr = NULL;
    
    if ( inMemberType == kODRecordTypeUsers ) {
        idAttr = kODAttributeTypeUniqueID;
    }
    else if ( inMemberType == kODRecordTypeGroups ) {
        idAttr = kODAttributeTypePrimaryGroupID;
    }
    
    if ( idAttr != NULL )
    {
        //
        // set ID so we have a synthetic UUID
        if ( testFailure(ODRecordSetValue(testmember, idAttr, CFSTR("199"), &error), 
                         "ODRecordSetValue testmember (set UID)", &error) == true )
        {
            goto fail;
        }
        
        if ( addMember("synthetic UUID") == false ) goto fail;
    }
    
    ODAttributeType mbrAttrib   = NULL;
    ODAttributeType clearAttrib = NULL;

    if ( inMemberType == kODRecordTypeUsers || inMemberType == kODRecordTypeComputers ) {
        if ( inGroupType == kODRecordTypeComputerLists ) mbrAttrib = kODAttributeTypeComputers;
        else mbrAttrib = kODAttributeTypeGroupMembers;
    }
    else if ( inMemberType == kODRecordTypeGroups || inMemberType == kODRecordTypeComputerGroups ) {
        mbrAttrib = kODAttributeTypeNestedGroups;
    }
    else if ( inMemberType == kODRecordTypeComputerLists ) {
        mbrAttrib = kODAttributeTypeGroup;
    }
    else {
        goto finish;
    }
    
    if ( testFailure(ODRecordSetValue(testmember, kODAttributeTypeGUID, CFSTR("12345678-1234-1234-123456789011"), &error), 
                     "ODRecordSetValue testmember (put a UUID back)", &error) == true )
    {
        goto fail;
    }    
    
    // now add to fake GUIDs
    CFTypeRef values[] = { CFSTR("12345678-1234-1234-123456789012"), CFSTR("12345678-1234-1234-123456789013"), CFSTR("12345678-1234-1234-123456789014") };
    CFArrayRef cfGUIDList = CFArrayCreate( kCFAllocatorDefault, values, sizeof(values) / sizeof(CFTypeRef), &kCFTypeArrayCallBacks );
    if ( testFailure(ODRecordSetValue(testcontainer, mbrAttrib, cfGUIDList, &error), 
                     "ODRecordSetValue testcontainer (adding fake values)", &error) == true )
    {
        goto fail;
    }
    
    if ( inGroupType == kODRecordTypeGroups && 
         inMemberType == kODRecordTypeUsers && 
         testFailure(ODRecordSetValue(testcontainer, kODAttributeTypeGroupMembership, emptyValue, &error), 
                     "ODRecordSetValue testcontainer (removing name list)", &error) == true )
    {
        goto fail;
    }
    
    if ( addMember("adding member (missing legacy membership, if applicable)") == false ) goto fail;
    else if ( badMixture() == true ){
        printf( "Stopping test because incompatible mix to do addition validation\n" );
        goto finish;
    }
    
    
    CFArrayRef groupMembers = ODRecordCopyValues( testcontainer, mbrAttrib, &error );
    if ( testFailure(groupMembers != NULL, "ODRecordCopyValues testcontainer", &error) == true ) {
        goto fail;
    }
    
    // should have 4 members now
    CFIndex count = CFArrayGetCount( groupMembers );
    printf( "ODRecordCopyValues testcontainer (expected 4 got %d) - %s\n", (int) count, (count == 4 ? "SUCCESS" : "FAIL") );
    if ( count != 4 ) {
        goto fail;
    }
    
    if ( addMember("adding member (twice)") == false ) goto fail;
    
    groupMembers = ODRecordCopyValues( testcontainer, mbrAttrib, &error );
    if ( testFailure(groupMembers != NULL, "ODRecordCopyValues testcontainer", &error) == true ) {
        goto fail;
    }
    
    // should still have 4 members
    count = CFArrayGetCount( groupMembers );
    printf( "ODRecordCopyValues testcontainer (expected 4 got %d) - %s\n", (int) count, (count == 4 ? "SUCCESS" : "FAIL") );
    if ( count != 4 ) goto fail;
    
    // TODO: do we need to care about the name membership count?
    
finish:
    
    bReturn = true;
    
fail:
    
    ODRecordDelete( testmember, NULL );
    ODRecordDelete( testcontainer, NULL );
    
    return bReturn;
};

void *doTest( void *inData )
{
    ODSessionRef        cfRef               = NULL;
    ODContextRef        cfContext           = 0;
    ODQueryRef          cfQuery             = NULL;
    CFArrayRef          cfResults           = NULL;
    CFStringRef         cfProxyHost         = NULL;
    CFStringRef         cfProxyUser         = NULL;
    CFStringRef         cfProxyPassword     = NULL;
    int                 ii;
    CFErrorRef          cfError;
    ODNodeRef           cfNodeRef           = NULL;
    ODNodeRef           cfLocalNodeRef      = (ODNodeRef) inData;
    CFStringRef         cfRecordName        = CFStringCreateWithFormat( kCFAllocatorDefault, NULL, CFSTR("TestCFFramework%ld"), (long) pthread_self() );
    CFStringRef         cfGroupName         = CFStringCreateWithFormat( kCFAllocatorDefault, NULL, CFSTR("TestGroup%ld"), (long) pthread_self() );
    CFStringRef         cfAddRecordName     = CFStringCreateWithFormat( kCFAllocatorDefault, NULL, CFSTR("TestAddRecord%ld"), (long) pthread_self() );
    CFStringRef         cfAddRecordAlias    = CFStringCreateWithFormat( kCFAllocatorDefault, NULL, CFSTR("TestAddRecordAlias%ld"), (long) pthread_self() );
    
    if( NULL != gProxyHost )
    {
        cfProxyHost     = CFStringCreateWithCString( kCFAllocatorDefault, gProxyHost, kCFStringEncodingUTF8 );
        cfProxyUser     = CFStringCreateWithCString( kCFAllocatorDefault, gProxyUser, kCFStringEncodingUTF8 );
        cfProxyPassword = CFStringCreateWithCString( kCFAllocatorDefault, gProxyPassword, kCFStringEncodingUTF8 );
    }
    
    CFStringRef cfAdminAccount  = CFStringCreateWithCString( kCFAllocatorDefault, gAdminAccount, kCFStringEncodingUTF8 );
    CFStringRef cfAdminPassword = CFStringCreateWithCString( kCFAllocatorDefault, gAdminPassword, kCFStringEncodingUTF8 );
    
    for( ii = 0; ii < gTestTimes; ii++ )
    {
        // test all of the APIs
        if( NULL != gProxyHost )
        {
            CFMutableDictionaryRef  cfOptions   = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, 
                                                                             &kCFTypeDictionaryValueCallBacks );
            
            CFDictionarySetValue( cfOptions, kODSessionProxyAddress, cfProxyHost );
            CFDictionarySetValue( cfOptions, kODSessionProxyUsername, cfProxyUser );
            CFDictionarySetValue( cfOptions, kODSessionProxyPassword, cfProxyPassword );
            
            cfRef = ODSessionCreate( kCFAllocatorDefault, cfOptions, NULL );
            if( NULL != cfRef )
            {
                printf("ODSessionCreate (with options) proxy - PASS\n");
                
                CFRelease( cfRef );
                cfRef = NULL;
            }
            else
            {
                printf( "ODSessionCreate (with options) proxy - FAIL\n" );
            }
            
            CFRelease( cfOptions );
        }
        else
        {
            printf("ODSessionCreate (with options) proxy - SKIP\n");
        }
        
        cfRef = ODSessionCreate( kCFAllocatorDefault, NULL, NULL );
        if( NULL != cfRef )
        {
            printf( "ODSessionCreate - PASS\n" );

            CFRelease( cfRef );
            cfRef = NULL;
        }
        
        cfNodeRef = ODNodeCreateWithNodeType( kCFAllocatorDefault, kODSessionDefault, kODNodeTypeAuthentication, &cfError );
        if( NULL != cfNodeRef )
        {
            printf( "ODNodeCreate - PASS\n" );
            
            CFArrayRef  cfNodes = ODNodeCopySubnodeNames( cfNodeRef, NULL );
            if( NULL != cfNodes )
            {
                printf( "ODNodeCopySubnodeNames (Auth search node) - PASS\n" );
                
                CFArrayRef  cfUnreachable = ODNodeCopyUnreachableSubnodeNames( cfNodeRef, NULL );
                if ( cfUnreachable != NULL )
                {
                    printf( "ODNodeCopyUnreachableSubnodeNames - FAIL - returned %d\n", (int) CFArrayGetCount(cfUnreachable) );
                    
                    CFRelease( cfUnreachable );
                    cfUnreachable = NULL;
                }
                else
                {
                    printf( "ODNodeCopyUnreachableSubnodeNames - PASS\n" );
                }
                
                CFRelease( cfNodes );
                cfNodes = NULL;
            }
            else
            {
                printf( "ODNodeCopySubnodeNames (Auth search node) - FAIL\n" );
            }
            
            CFRelease( cfNodeRef );
            cfNodeRef = NULL;
        }
        else
        {
            printf( "ODNodeCreateWithNodeType with kODTypeAuthenticationSearchNode - FAIL (%d)\n", (int) CFErrorGetCode(cfError) );
            CFRelease( cfError );
        }
        
        cfNodeRef = ODNodeCreateWithName( kCFAllocatorDefault, kODSessionDefault, CFSTR("/LDAPv3/od.apple.com"), &cfError );
        if( NULL != cfNodeRef )
        {
            printf( "ODNodeCreateWithName with /LDAPv3/od.apple.com - PASS\n" );
            
            ODNodeRef cfNodeRef2 = ODNodeCreateCopy( kCFAllocatorDefault, cfNodeRef, NULL );
            if( NULL != cfNodeRef2 )
            {
                printf( "ODNodeCreateCopy - PASS\n" );
                CFRelease( cfNodeRef2 );
                cfNodeRef2 = NULL;
            }
            else
            {
                printf( "ODNodeCreateCopy - FAIL\n" );
            }
            
            CFRelease( cfNodeRef );
            cfNodeRef = NULL;
        }
        else
        {
            printf( "ODNodeCreateWithName with /LDAPv3/od.apple.com - FAIL (%d)\n", (int) CFErrorGetCode(cfError) );
            CFRelease( cfError );
        }
        
        cfLocalNodeRef = ODNodeCreateWithNodeType( kCFAllocatorDefault, kODSessionDefault, kODNodeTypeLocalNodes, &cfError );
        if( NULL != cfLocalNodeRef )
        {
            CFTypeRef   cfValues[] = { cfAdminAccount };
            CFArrayRef cfSearchValue = CFArrayCreate( kCFAllocatorDefault, cfValues, 1, &kCFTypeArrayCallBacks );
            
            printf( "ODNodeCreateWithNodeType with kODTypeLocalNode - PASS\n" );
            
            cfQuery = ODQueryCreateWithNode( kCFAllocatorDefault, cfLocalNodeRef, CFSTR(kDSStdRecordTypeUsers), CFSTR(kDSNAttrRecordName), 
                                             kODMatchEqualTo, cfSearchValue, NULL, 0, NULL );
            
            if( NULL != cfQuery )
            {
                printf( "ODQueryCreateWithNode - PASS\n" );

                cfResults = ODQueryCopyResults( cfQuery, false, NULL );
                
                if( NULL != cfResults )
                {
                    printf( "ODQueryCopyResults returned non-NULL - PASS\n" );
                    
                    if( CFArrayGetCount( cfResults ) != 0 ) 
                    {
                        printf( "ODQueryCopyResults returned results (%d) - PASS\n", (int) CFArrayGetCount(cfResults) );
                    }
                    else
                    {
                        printf( "ODQueryCopyResults returned results - FAIL\n" );
                    }
                    
                    CFRelease( cfResults );
                    cfResults = NULL;
                }
                else
                {
                    printf( "ODQueryCopyResults returned non-NULL - FAIL\n" );
                }
                
                CFRelease( cfQuery );
                cfQuery = NULL;
            }
            
            ODRecordRef cfRecord = ODNodeCopyRecord( cfLocalNodeRef, CFSTR(kDSStdRecordTypeUsers), cfAdminAccount, NULL, NULL );
            if( NULL != cfRecord )
            {
                printf( "ODNodeCopyRecord - PASS\n" );

                CFDictionaryRef cfPolicy = ODRecordCopyPasswordPolicy( kCFAllocatorDefault, cfRecord, NULL );
                if( NULL != cfPolicy )
                {
                    printf( "ODNodeCopyUserPolicy - PASS\n" );
                    
                    CFRelease( cfPolicy );
                    cfPolicy = NULL;
                }
                else
                {
                    printf( "ODNodeCopyUserPolicy - FAIL\n" );
                }
                
                if( ODRecordVerifyPassword(cfRecord, cfAdminPassword, NULL) == true )
                {
                    printf( "ODNodeVerifyCredentials - PASS\n" );
                }
                else
                {
                    printf( "ODNodeVerifyCredentials - FAIL\n" );
                }
                
                CFStringRef     cfAttribute = CFSTR(kDSAttributesAll);
                CFArrayRef      cfAttribs   = CFArrayCreate( kCFAllocatorDefault, (const void **) &cfAttribute, 1, &kCFTypeArrayCallBacks );
                CFDictionaryRef cfValues    = ODRecordCopyDetails( cfRecord, cfAttribs, NULL );
                if( NULL != cfValues )
                {
                    printf( "ODRecordCopyDetails (kDSAttributesAll) - non-NULL - PASS\n" );
                    
                    if( CFDictionaryGetCount(cfValues) != 0 )
                    {
                        printf( "ODRecordCopyDetails (kDSAttributesAll) - has values - PASS\n" );
                    }
                    else
                    {
                        printf( "ODRecordCopyDetails (kDSAttributesAll) - has values - FAIL\n" );
                    }
                    
                    CFRelease( cfValues );
                    cfValues = NULL;
                }
                else
                {
                    printf( "ODRecordCopyAllAttributes - FAIL\n" );
                }
                
                CFArrayRef cfAttribValues = ODRecordCopyValues( cfRecord, CFSTR(kDS1AttrNFSHomeDirectory), NULL );
                if( NULL != cfAttribValues )
                {
                    printf( "ODRecordCopyValues - PASS\n" );
                    
                    CFRelease( cfAttribValues );
                    cfAttribValues = NULL;
                }
                else
                {
                    printf( "ODRecordCopyAttribute - FAIL\n" );
                }
                
#warning needs ODNodeAuthenticateExtended
//                dsStatus = ODNodeAuthenticateExtended( dsNodeRef, CFSTR(kDSStdRecordTypeUsers), CFSTR(), inAuthItems, 
//                                                                    &outAuthItems, &dsContext );
//                if( eDSNoErr == dsStatus )
//                {
//                    printf( "ODNodeAuthenticateExtended - PASS\n" );
//                }
//                else
//                {
//                    printf( "ODNodeAuthenticateExtended - FAIL\n" );
//                }
                
                if( ODRecordSetNodeCredentials(cfRecord, cfAdminAccount, cfAdminPassword, NULL) )
                {
                    printf( "ODRecordSetNodeCredentials - PASS\n" );
                }
                else
                {
                    printf( "ODRecordSetNodeCredentials - FAIL\n" );
                }                
            }
            else
            {
                printf( "ODNodeCopyRecord - FAIL\n" );
            }
            
            CFStringRef cfNodeName = ODNodeGetName( cfLocalNodeRef );
            if( NULL != cfNodeName )
            {
                printf( "ODNodeGetName - PASS\n" );
            }
            else
            {
                printf( "ODNodeGetName - FAIL\n" );
            }

            CFDictionaryRef cfNodeInfo = ODNodeCopyDetails( cfLocalNodeRef, NULL, NULL );
            if( NULL != cfNodeInfo )
            {
                printf( "ODNodeCopyDetails - PASS\n" );
                
                CFRelease( cfNodeInfo );
                cfNodeInfo = NULL;
            }
            else
            {
                printf( "ODNodeCopyDetails - FAIL\n" );
            }

            CFArrayRef cfRecTypes = ODNodeCopySupportedRecordTypes( cfLocalNodeRef, NULL );
            if( NULL != cfRecTypes )
            {
                printf( "ODNodeCopySupportedRecordTypes - PASS\n" );
                
                CFRelease( cfRecTypes );
                cfRecTypes = NULL;
            }
            else
            {
                printf( "ODNodeCopySupportedRecordTypes - FAIL\n" );
            }
            
            CFArrayRef cfAttribTypes = ODNodeCopySupportedAttributes( cfLocalNodeRef, NULL, NULL );
            if( NULL != cfAttribTypes )
            {
                printf( "ODNodeCopySupportedAttributes - PASS\n" );
                
                CFRelease( cfAttribTypes );
                cfAttribTypes = NULL;
            }
            else
            {
                printf( "ODNodeCopySupportedAttributes - FAIL\n" );
            }

            if( ODNodeSetCredentials(cfLocalNodeRef, CFSTR(kDSStdRecordTypeUsers), cfAdminAccount, cfAdminPassword, NULL) )
            {
                ODRecordRef    cfRecord = NULL;
                
                printf( "ODNodeSetCredentials - PASS\n" );
                
                cfRecord = ODNodeCreateRecord( cfLocalNodeRef, CFSTR(kDSStdRecordTypeUsers), cfRecordName, NULL, NULL );
                if( NULL != cfRecord )
                {
                    printf( "ODNodeCreateRecord - PASS\n" );
                    
                    ODRecordRef cfRecordTemp = ODNodeCreateRecord( cfLocalNodeRef, CFSTR(kDSStdRecordTypeUsers), cfRecordName, NULL, &cfError );
                    if( NULL != cfRecordTemp )
                    {
                        printf( "ODNodeCreateRecord (create duplicate) - FAIL record returned\n" );
                    }
                    else
                    {
                        CFIndex errCode = CFErrorGetCode( cfError );
                        if( errCode == kODErrorRecordAlreadyExists )
                        {
                            printf( "ODNodeCreateRecord (create duplicate) - PASS\n" );
                        }
                        else
                        {
                            printf( "ODNodeCreateRecord (create duplicate) - FAIL (%d)\n", (int) errCode );
                            CFRelease( cfError );
                        }
                    }
                    
                    if( ODRecordDelete(cfRecord, &cfError) )
                    {
                        printf( "ODRecordDelete - PASS\n" );
                    }
                    else
                    {
                        printf( "ODRecordDelete - FAIL (%d)\n", (int) CFErrorGetCode(cfError) );
                    }
                    
                    CFRelease( cfRecord );
                    cfRecord = NULL;
                }
                else
                {
                    printf( "ODNodeCreateRecord - FAIL\n" );
                }
                
                CFMutableDictionaryRef cfAttributes = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, 
                                                                                 &kCFTypeDictionaryValueCallBacks );
                CFStringRef     cfHome[]    = { CFSTR("/Users/blah"), NULL };
                CFStringRef     cfNames[]   = { cfAddRecordName, cfAddRecordAlias, NULL };
                
                CFArrayRef      cfHomeAttrib    = CFArrayCreate( kCFAllocatorDefault, (const void **) &cfHome, 1, &kCFTypeArrayCallBacks );
                CFArrayRef      cfNamesAttrib   = CFArrayCreate( kCFAllocatorDefault, (const void **) &cfNames, 2, &kCFTypeArrayCallBacks );

                CFDictionarySetValue( cfAttributes, CFSTR(kDS1AttrNFSHomeDirectory), cfHomeAttrib );
                CFDictionarySetValue( cfAttributes, CFSTR(kDSNAttrRecordName), cfNamesAttrib );
                
                CFRelease( cfNamesAttrib );
                CFRelease( cfHomeAttrib );
                
                cfRecord = ODNodeCreateRecord( cfLocalNodeRef, CFSTR(kDSStdRecordTypeUsers), cfAddRecordName, cfAttributes, &cfError );
                if( NULL != cfRecord )
                {
                    printf( "ODNodeCreateRecord (with attributes) - PASS\n" );
                    
                    if( NULL != cfRecord )
                    {
                        printf( "ODNodeAddRecordWithAttributes (record returned) - PASS\n" );
                        
                        CFArrayRef cfValue = ODRecordCopyValues( cfRecord, CFSTR(kDS1AttrNFSHomeDirectory), NULL );
                        if( NULL != cfValue && CFArrayGetCount(cfValue) == 1 )
                        {
                            printf( "ODNodeAddRecordWithAttributes (value exist) - PASS\n" );
                        }
                        else
                        {
                            printf( "ODNodeAddRecordWithAttributes (value exist) - FAIL\n" );
                        }
                        
                        if( NULL != cfValue )
                            CFRelease( cfValue );
                        
                        if( ODRecordChangePassword(cfRecord, NULL, CFSTR("test"), &cfError) )
                        {
                            printf( "ODNodeSetPassword - PASS\n" );
                        }
                        else
                        {
                            printf( "ODNodeSetPassword - FAIL (%d)\n", (int) CFErrorGetCode(cfError) );
                            CFRelease( cfError );
                        }
                        
                        if( ODRecordChangePassword(cfRecord, CFSTR("test"), CFSTR("test2"), &cfError) )
                        {
                            printf( "ODNodeChangePassword - PASS\n" );
                        }
                        else
                        {
                            printf( "ODNodeChangePassword - FAIL (%d)\n", (int) CFErrorGetCode(cfError) );
                            CFRelease( cfError );
                        }
                        
                        if( ODRecordSetValue( cfRecord, CFSTR(kDSNAttrState), CFSTR("Test street"), &cfError ) )
                        {
                            printf( "ODRecordSetValueOrValues - PASS\n" );
                        }
                        else
                        {
                            printf( "ODRecordSetValueOrValues - FAIL (%d)\n", (int) CFErrorGetCode(cfError) );
                            CFRelease( cfError );
                        }
                        
                        if( ODRecordAddValue( cfRecord, CFSTR(kDSNAttrState), CFSTR("Test street 2"), &cfError ) )
                        {
                            printf( "ODRecordAddValue - PASS\n" );
                        }
                        else
                        {
                            printf( "ODRecordAddValue - FAIL (%d)\n", (int) CFErrorGetCode(cfError) );
                            CFRelease( cfError );
                        }
                        
                        if( ODRecordRemoveValue(cfRecord, CFSTR(kDSNAttrState), CFSTR("Test street 2"), &cfError) )
                        {
                            printf( "ODRecordRemoveValue - PASS\n" );
                        }
                        else
                        {
                            printf( "ODRecordRemoveValue - FAIL (%d)\n", (int) CFErrorGetCode(cfError) );
                        }
                        
                        CFArrayRef  cfArray = CFArrayCreate( kCFAllocatorDefault, NULL, 0, &kCFTypeArrayCallBacks );
                        if( ODRecordSetValue(cfRecord, CFSTR(kDSNAttrState), cfArray, &cfError) )
                        {
                            printf( "ODRecordSetValue (remove attribute) - PASS\n" );
                            
                            CFArrayRef cfValues = ODRecordCopyValues( cfRecord, CFSTR(kDSNAttrState), NULL );
                            if( NULL == cfValues || CFArrayGetCount(cfValues) == 0 )
                            {
                                printf( "ODRecordSetValue (attrib gone) - PASS\n" );
                            }
                            else
                            {
                                printf( "ODRecordSetValue (attrib gone) - FAIL\n" );
                            }
                            
                            if( NULL != cfValues )
                                CFRelease( cfValues );
                        }
                        else
                        {
                            printf( "ODRecordSetValue (deleting attrib) - FAIL (%d)\n", (int) CFErrorGetCode(cfError) );
                        }
                        
                        CFRelease( cfArray );
                        
                        ODRecordDelete( cfRecord, NULL );
                    }
                    else
                    {
                        printf( "ODNodeAddRecordWithAttributes (record returned) - FAIL\n" );
                    }
                    
                    CFRelease( cfRecord );
                    cfRecord = NULL;
                }
                else
                {
                    printf( "ODNodeCreateRecord (with attributes) - FAIL (%d)\n", (int) CFErrorGetCode(cfError) );
                    CFRelease( cfError );
                }
                
                CFRelease( cfAttributes );
                cfAttributes = NULL;
  
                // test various combinations
                ODRecordType groupTypes[] = { kODRecordTypeConfiguration, kODRecordTypeUsers, kODRecordTypeGroups, kODRecordTypeComputerGroups, kODRecordTypeComputerLists };
                ODRecordType userTypes[] = { NULL, kODRecordTypeUsers, kODRecordTypeComputers, kODRecordTypeGroups,
                                             kODRecordTypeComputerGroups, kODRecordTypeComputerLists, kODRecordTypeConfiguration };
                
                int groupX;
                int userX;
                bool bSuccess = true;
                for ( groupX = 0; bSuccess == true && groupX < (sizeof(groupTypes) / sizeof(ODRecordType)); groupX++ )
                {
                    for ( userX = 0; bSuccess == true && userX < (sizeof(userTypes) / sizeof(ODRecordType)); userX++ )
                    {
                        bSuccess = doTestMembership( cfLocalNodeRef, groupTypes[groupX], userTypes[userX] );
                    }
                }
                
                if ( bSuccess == false ) return NULL;

                // now test membership checks
                ODRecordRef cfGroup = ODNodeCopyRecord( cfLocalNodeRef, kODRecordTypeGroups, CFSTR("admin"), NULL, NULL );
                if( NULL != cfGroup )
                {
                    printf( "\n--Test Memberships\n" );
                    printf( "ODNodeCopyRecord (admin) - PASS\n" );
                    
                    cfRecord = ODNodeCopyRecord( cfLocalNodeRef, kODRecordTypeUsers, cfAdminAccount, NULL, NULL );
                    
                    if ( ODRecordContainsMember( cfGroup, cfRecord, NULL ) == true )
                    {
                        printf( "ODRecordContainsMember - PASS\n" );
                    }
                    else
                    {
                        printf( "ODRecordContainsMember - FAIL\n" );
                    }
                    
                    // test failure - group in user
                    if ( ODRecordContainsMember(cfRecord, cfGroup, NULL) == false ) {
                        printf( "ODRecordContainsMember (group is member of user (failure)) - PASS (got error)\n" );
                    }
                    else {
                        printf( "ODRecordContainsMember (group is member of user (failure)) - FAIL (no error)\n" );
                    }
                    
                    // test failure - group in group
                    if ( ODRecordContainsMember(cfRecord, cfGroup, NULL) == false ) {
                        printf( "ODRecordContainsMember (group is member of group (failure)) - PASS (got error)\n" );
                    }
                    else {
                        printf( "ODRecordContainsMember (group is member of group (failure)) - FAIL (no error)\n" );
                    }
                    
                    CFRelease( cfGroup );
                    cfGroup = NULL;
                }
                else
                {
                    printf( "ODNodeCopyRecord (admin) - FAIL\n" );
                }
            }
            else
            {
                printf( "ODNodeSetCredentials - FAIL\n" );
            }

            CFRelease( cfLocalNodeRef );
            cfLocalNodeRef = NULL;
        }
        else
        {
            printf( "ODNodeCreateWithNodeType with kODTypeLocalNode - FAIL (%d)\n", (int) CFErrorGetCode(cfError) );
            CFRelease( cfError );
        }
    }
    
    CFRelease( cfRecordName );
    CFRelease( cfGroupName );
    CFRelease( cfAddRecordName );
    CFRelease( cfAddRecordAlias );
    
    return NULL;
}

int main( int argc, char *argv[] )
{
    parseOptions( argc, argv );
    
    argc -= optind;
    argv += optind;    
    
    ODNodeRef   cfLocalNodeRef = ODNodeCreateWithNodeType( kCFAllocatorDefault, kODSessionDefault, kODNodeTypeLocalNodes, NULL );

    if ( gCopies > 0 ) {
        
        int         ii;
        void        *junk;
        pthread_t   *threads    = (pthread_t *) calloc( gCopies, sizeof(pthread_t) );
        
        
        for ( ii = 0; ii < gCopies; ii++ )
        {
            while( pthread_create( &threads[ii], NULL, doTest, cfLocalNodeRef ) != 0 )
            {
                usleep( 1000 );
            }
        }
        
        // now wait for each thread to finish
        for ( ii = 0; ii < gCopies; ii++ ) {
            pthread_join( threads[ii], &junk );
        }
        
    } else {
        doTest( cfLocalNodeRef );
    }
    
    return 0;
}