1//
2//  su-40-secdb.c
3//  utilities
4//
5//  Created by Michael Brouwer on 11/15/12.
6//  Copyright (c) 2012 Apple Inc. All rights reserved.
7//
8
9#include <utilities/SecCFRelease.h>
10#include <utilities/SecDb.h>
11
12#include <CoreFoundation/CoreFoundation.h>
13
14#include "utilities_regressions.h"
15#include <time.h>
16
17#define kTestCount 31
18
19static int count_func(SecDbRef db, const char *name, CFIndex *max_conn_count, bool (*perform)(SecDbRef db, CFErrorRef *error, void (^perform)(SecDbConnectionRef dbconn))) {
20    __block int count = 0;
21    __block int max_count = 0;
22    *max_conn_count = 0;
23    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
24    dispatch_group_t group = dispatch_group_create();
25
26    for (int i = 0; i < 100; ++i) {
27        dispatch_group_async(group, queue, ^{
28            CFIndex conn_count = SecDbIdleConnectionCount(db);
29            if (conn_count > *max_conn_count) {
30                *max_conn_count = conn_count;
31            }
32
33            CFErrorRef error = NULL;
34            if (!perform(db, &error, ^void (SecDbConnectionRef dbconn) {
35                count++;
36                if (count > max_count) {
37                    max_count = count;
38                }
39                struct timespec ts = { .tv_nsec = 200000 };
40                nanosleep(&ts, NULL);
41                count--;
42            })) {
43                fail("perform %s %@", name, error);
44                CFReleaseNull(error);
45            }
46        });
47    }
48    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
49    dispatch_release(group);
50    return max_count;
51}
52
53static void count_connections(SecDbRef db) {
54    __block CFIndex max_conn_count = 0;
55    dispatch_group_t group = dispatch_group_create();
56    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
57    dispatch_group_async(group, queue, ^{
58        cmp_ok(count_func(db, "writers", &max_conn_count, SecDbPerformWrite), <=, kSecDbMaxWriters, "max writers is %d", kSecDbMaxWriters);
59    TODO: {
60        todo("can't guarantee all threads used");
61        is(count_func(db, "writers", &max_conn_count, SecDbPerformWrite), kSecDbMaxWriters, "max writers is %d", kSecDbMaxWriters);
62        }
63    });
64    dispatch_group_async(group, queue, ^{
65        cmp_ok(count_func(db, "readers",  &max_conn_count, SecDbPerformRead), <=, kSecDbMaxReaders, "max readers is %d", kSecDbMaxReaders);
66    TODO: {
67        todo("can't guarantee all threads used");
68        is(count_func(db, "readers",  &max_conn_count, SecDbPerformRead), kSecDbMaxReaders, "max readers is %d", kSecDbMaxReaders);
69        }
70    });
71    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
72    dispatch_release(group);
73    cmp_ok(max_conn_count, <=, kSecDbMaxIdleHandles, "max idle connection count is %d", kSecDbMaxIdleHandles);
74    TODO: {
75        todo("can't guarantee all threads idle");
76        is(max_conn_count, kSecDbMaxIdleHandles, "max idle connection count is %d", kSecDbMaxIdleHandles);
77    }
78
79}
80
81static void tests(void)
82{
83    CFTypeID typeID = SecDbGetTypeID();
84    CFStringRef tid = CFCopyTypeIDDescription(typeID);
85    ok(CFEqual(CFSTR("SecDb"), tid), "tid matches");
86    CFReleaseNull(tid);
87
88    typeID = SecDbConnectionGetTypeID();
89    tid = CFCopyTypeIDDescription(typeID);
90    ok(CFEqual(CFSTR("SecDbConnection"), tid), "tid matches");
91    CFReleaseNull(tid);
92
93    const char *home_var = getenv("HOME");
94    CFStringRef dbName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s/Library/Keychains/su-40-sqldb.db"), home_var ? home_var : "");
95
96    SecDbRef db = SecDbCreate(dbName, NULL);
97    ok(db, "SecDbCreate");
98
99    __block CFErrorRef error = NULL;
100    ok(SecDbPerformWrite(db, &error, ^void (SecDbConnectionRef dbconn) {
101        ok(SecDbExec(dbconn, CFSTR("CREATE TABLE tablea(key TEXT,value BLOB);"), &error),
102           "exec: %@", error);
103        ok(SecDbExec(dbconn, CFSTR("INSERT INTO tablea(key,value)VALUES(1,2);"), &error),
104           "exec: %@", error);
105
106        CFStringRef sql = CFSTR("INSERT INTO tablea(key,value)VALUES(?,?);");
107        ok(SecDbPrepare(dbconn, sql, &error, ^void (sqlite3_stmt *stmt) {
108            ok_status(sqlite3_bind_text(stmt, 1, "key1", 4, NULL), "bind_text[1]");
109            ok_status(sqlite3_bind_blob(stmt, 2, "value1", 6, NULL), "bind_blob[2]");
110            ok(SecDbStep(dbconn, stmt, &error, NULL), "SecDbStep: %@", error);
111            CFReleaseNull(error);
112        }), "SecDbPrepare: %@", error);
113        CFReleaseNull(error);
114
115        sql = CFSTR("SELECT key,value FROM tablea;");
116        ok(SecDbPrepare(dbconn, sql, &error, ^void (sqlite3_stmt *stmt) {
117            ok(SecDbStep(dbconn, stmt, &error, ^(bool *stop) {
118                const unsigned char *key = sqlite3_column_text(stmt, 1);
119                pass("got a row key: %s", key);
120                // A row happened, we're done
121                *stop = true;
122            }), "SecDbStep: %@", error);
123            CFReleaseNull(error);
124        }), "SecDbPrepare: %@", error);
125        CFReleaseNull(error);
126
127        ok(SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &error, ^(bool *commit) {
128            ok(SecDbExec(dbconn, CFSTR("INSERT INTO tablea (key,value)VALUES(13,21);"), &error),
129               "exec: %@", error);
130            ok(SecDbExec(dbconn, CFSTR("INSERT INTO tablea (key,value)VALUES(2,5);"), &error),
131               "exec: %@", error);
132        }), "SecDbTransaction: %@", error);
133
134        ok(SecDbPrepare(dbconn, sql, &error, ^void (sqlite3_stmt *stmt) {
135            ok(SecDbStep(dbconn, stmt, &error, ^(bool *stop) {
136                const unsigned char *key = sqlite3_column_text(stmt, 1);
137                pass("got a row key: %s", key);
138            }), "SecDbStep: %@", error);
139            CFReleaseNull(error);
140            sqlite3_reset(stmt);
141            ok(SecDbStep(dbconn, stmt, &error, ^(bool *stop) {
142                const unsigned char *key = sqlite3_column_text(stmt, 1);
143                pass("got a row key: %s", key);
144                *stop = true;
145            }), "SecDbStep: %@", error);
146            CFReleaseNull(error);
147
148        }), "SecDbPrepare: %@", error);
149
150        ok(SecDbExec(dbconn, CFSTR("DROP TABLE tablea;"), &error),
151           "exec: %@", error);
152    }), "SecDbPerformWrite: %@", error);
153    CFReleaseNull(error);
154
155    count_connections(db);
156
157    CFReleaseNull(db);
158}
159
160int su_40_secdb(int argc, char *const *argv)
161{
162    plan_tests(kTestCount);
163    tests();
164
165    return 0;
166}
167
168#if 0
169// The following still need tests.
170ok(SecDbTransaction(dbconn, kSecDbNoneTransactionType, &error, ^bool {}), "");
171ok(SecDbTransaction(dbconn, kSecDbImmediateTransactionType, &error, ^bool {}), "");
172ok(SecDbTransaction(dbconn, kSecDbNormalTransactionType, &error, ^bool {}), "");
173ok(SecDbPerformRead(SecDbRef db, CFErrorRef *error, void ^(SecDbConnectionRef dbconn){}), "");
174SecDbCheckpoint(SecDbConnectionRef dbconn);
175#endif
176