2 // su-41-secdb-stress.c
5 // Created by Michael Brouwer on 7/25/13.
6 // Copyright (c) 2013 Apple Inc. All rights reserved.
9 #include <utilities/SecCFWrappers.h>
10 #include <utilities/SecDb.h>
11 #include <utilities/SecDispatchRelease.h>
13 #include <CoreFoundation/CoreFoundation.h>
15 #include "utilities_regressions.h"
18 #define kTestCount 3415
20 // Queue to protect counters and test_ok invocations
21 static dispatch_queue_t count_queue
;
23 #define ts_ok(THIS, ...) \
25 bool is_ok = !!(THIS); \
26 dispatch_sync(count_queue, ^{ \
27 test_ok(is_ok, test_create_description(__VA_ARGS__), test_directive, \
28 test_reason, __FILE__, __LINE__, NULL); \
33 #define ts_ok_status(THIS, ...) \
35 OSStatus _this = (THIS); \
37 dispatch_sync(count_queue, ^{ \
38 is_ok = test_ok(!_this, test_create_description(__VA_ARGS__), \
39 test_directive, test_reason, __FILE__, __LINE__, \
40 "# status: %s(%ld)\n", \
41 sec_errstr(_this), _this); \
47 typedef void (^SecDbBlock
)(SecDbConnectionRef dbconn
);
49 #define SecDbExecWithSql(dbconn, sql) test_SecDbExecWithSql(dbconn, sql, test_directive, test_reason, __FILE__, __LINE__)
51 static void test_SecDbExecWithSql(SecDbConnectionRef dbconn
, CFStringRef sql CF_CONSUMED
, const char *directive
,
52 const char *reason
, const char *file
, unsigned line
) {
53 CFErrorRef execError
= NULL
;
54 bool is_ok
= !!(SecDbExec(dbconn
, sql
, &execError
));
55 dispatch_sync(count_queue
, ^{
56 test_ok(is_ok
, test_create_description("exec %@: %@", sql
, execError
), directive
, reason
, file
, line
, NULL
);
58 CFReleaseNull(execError
);
62 #define SecDbDeleteWithInts(dbconn, key, value) test_SecDbDeleteWithInts(dbconn, key, value, test_directive, test_reason, __FILE__, __LINE__)
63 static void test_SecDbDeleteWithInts(SecDbConnectionRef dbconn
, int key
, int value
, const char *directive
,
64 const char *reason
, const char *file
, unsigned line
) {
65 test_SecDbExecWithSql(dbconn
, CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
66 CFSTR("DELETE FROM tablea WHERE key=%d AND value=%d;"), key
, value
), directive
, reason
, file
, line
);
69 static void SecDbDoReadOp(SecDbConnectionRef dbconn
, size_t seed
) {
73 CFErrorRef prepareError
= NULL
;
74 CFStringRef sql
= CFSTR("SELECT key,value FROM tablea;");
75 ts_ok(SecDbPrepare(dbconn
, sql
, &prepareError
, ^void (sqlite3_stmt
*stmt
) {
76 CFErrorRef stepError
= NULL
;
77 ts_ok(SecDbStep(dbconn
, stmt
, &stepError
, ^(bool *stop
) {
78 //const unsigned char *key = sqlite3_column_text(stmt, 1);
79 //pass("got a row key: %s", key);
80 // A row happened, we're done
82 }), "SecDbStep: %@", stepError
);
83 CFReleaseNull(stepError
);
84 }), "SecDbPrepare: %@", prepareError
);
85 CFReleaseNull(prepareError
);
90 CFErrorRef prepareError
= NULL
;
91 CFStringRef sql
= CFSTR("SELECT key,value FROM tablea;");
92 ts_ok(SecDbPrepare(dbconn
, sql
, &prepareError
, ^void (sqlite3_stmt
*stmt
) {
93 CFErrorRef stepError
= NULL
;
94 ts_ok(SecDbStep(dbconn
, stmt
, &stepError
, ^(bool *stop
) {
95 //const unsigned char *key = sqlite3_column_text(stmt, 1);
96 //pass("got a row key: %s", key);
97 }), "SecDbStep: %@", stepError
);
98 CFReleaseNull(stepError
);
100 ts_ok(SecDbStep(dbconn
, stmt
, &stepError
, ^(bool *stop
) {
101 //const unsigned char *key = sqlite3_column_text(stmt, 1);
102 //pass("got a row key: %s", key);
104 }), "SecDbStep: %@", stepError
);
105 CFReleaseNull(stepError
);
106 }), "SecDbPrepare: %@", prepareError
);
107 CFReleaseNull(prepareError
);
113 static void SecDbDoWriteOp(SecDbConnectionRef dbconn
, size_t seed
) {
116 SecDbExecWithSql(dbconn
, CFSTR("INSERT INTO tablea(key,value)VALUES(1,2);"));
120 CFErrorRef txnError
= NULL
;
121 ts_ok(SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &txnError
, ^(bool *commit
) {
122 CFErrorRef execError
= NULL
;
123 ts_ok(SecDbExec(dbconn
, CFSTR("INSERT INTO tablea (key,value)VALUES(13,21);"), &execError
),
124 "exec: %@", execError
);
125 CFReleaseNull(execError
);
126 ts_ok(SecDbExec(dbconn
, CFSTR("INSERT INTO tablea (key,value)VALUES(2,5);"), &execError
),
127 "exec: %@", execError
);
128 CFReleaseNull(execError
);
129 }), "SecDbTransaction: %@", txnError
);
130 CFReleaseNull(txnError
);
135 CFErrorRef prepareError
= NULL
;
136 CFStringRef sql
= CFSTR("INSERT INTO tablea(key,value)VALUES(?,?);");
137 ts_ok(SecDbPrepare(dbconn
, sql
, &prepareError
, ^void (sqlite3_stmt
*stmt
) {
138 CFErrorRef stepError
= NULL
;
139 ts_ok_status(sqlite3_bind_text(stmt
, 1, "key1", 4, NULL
), "bind_text[1]");
140 ts_ok_status(sqlite3_bind_blob(stmt
, 2, "value1", 6, NULL
), "bind_blob[2]");
141 ts_ok(SecDbStep(dbconn
, stmt
, &stepError
, NULL
), "SecDbStep: %@", stepError
);
142 CFReleaseNull(stepError
);
143 }), "SecDbPrepare: %@", prepareError
);
144 CFReleaseNull(prepareError
);
148 SecDbDeleteWithInts(dbconn
, 1, 2);
151 SecDbDeleteWithInts(dbconn
, 13, 21);
154 SecDbDeleteWithInts(dbconn
, 2, 5);
159 static void tests(void)
161 count_queue
= dispatch_queue_create("count_queue", DISPATCH_QUEUE_SERIAL
);
163 CFTypeID typeID
= SecDbGetTypeID();
164 CFStringRef tid
= CFCopyTypeIDDescription(typeID
);
165 ts_ok(CFEqual(CFSTR("SecDb"), tid
), "TypeIdDescription is SecDb");
168 typeID
= SecDbConnectionGetTypeID();
169 tid
= CFCopyTypeIDDescription(typeID
);
170 ts_ok(CFEqual(CFSTR("SecDbConnection"), tid
), "TypeIdDescription is SecDbConnection");
173 const char *home_var
= getenv("HOME");
174 CFStringRef dbName
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%s/Library/Keychains/su-41-sqldb-stress.db"), home_var
? home_var
: "");
175 CFStringPerformWithCString(dbName
, ^(const char *path
) { unlink(path
); });
177 SecDbRef db
= SecDbCreate(dbName
, ^bool (SecDbConnectionRef dbconn
, bool did_create
, CFErrorRef
*firstOpenError
) {
178 // This test will run when the database is first opened.
179 return ts_ok(SecDbExec(dbconn
, CFSTR("CREATE TABLE tablea(key TEXT,value BLOB);"), firstOpenError
),
180 "create table: %@", *firstOpenError
);
182 ts_ok(db
, "SecDbCreate");
184 __block CFIndex max_idle
= 0;
185 __block CFIndex max_readers
= 0;
186 __block CFIndex max_writers
= 0;
187 __block CFIndex cur_readers
= 0;
188 __block CFIndex cur_writers
= 0;
190 dispatch_group_t group
= dispatch_group_create();
191 dispatch_queue_t queue
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0);
192 for (size_t job
=0; job
< 1000; ++job
) {
193 dispatch_group_async(group
, queue
, ^{
194 CFIndex cur_idle
= SecDbIdleConnectionCount(db
);
195 dispatch_sync(count_queue
, ^{ if (max_idle
< cur_idle
) max_idle
= cur_idle
; });
196 CFErrorRef performError
= NULL
;
198 ts_ok(SecDbPerformWrite(db
, &performError
, ^void (SecDbConnectionRef dbconn
) {
199 dispatch_sync(count_queue
, ^{ cur_writers
++; if (max_writers
< cur_writers
) max_writers
= cur_writers
; });
200 SecDbDoWriteOp(dbconn
, job
);
201 dispatch_sync(count_queue
, ^{ cur_writers
--; });
202 }), "write %@", performError
);
204 CFErrorRef performError
= NULL
;
205 ts_ok(SecDbPerformRead(db
, &performError
, ^void (SecDbConnectionRef dbconn
) {
206 dispatch_sync(count_queue
, ^{ cur_readers
++; if (max_readers
< cur_readers
) max_readers
= cur_readers
; });
207 SecDbDoReadOp(dbconn
, job
);
208 dispatch_sync(count_queue
, ^{ cur_readers
--; });
209 }), "read %@", performError
);
211 CFReleaseNull(performError
);
214 dispatch_group_wait(group
, DISPATCH_TIME_FOREVER
);
215 dispatch_release(group
);
217 CFErrorRef writeError
= NULL
;
218 ts_ok(SecDbPerformWrite(db
, &writeError
, ^(SecDbConnectionRef dbconn
){
219 SecDbExecWithSql(dbconn
, CFSTR("DROP TABLE tablea;"));
220 }), "SecDbPerformWrite: %@", writeError
);
221 CFReleaseNull(writeError
);
223 dispatch_release_null(count_queue
);
225 is(max_idle
, kSecDbMaxIdleHandles
, "max idle connection count is %d", kSecDbMaxIdleHandles
);
226 is(max_writers
, kSecDbMaxWriters
, "max writers is %d", kSecDbMaxWriters
);
227 is(max_readers
, kSecDbMaxReaders
, "max readers is %d", kSecDbMaxReaders
);
232 int su_41_secdb_stress(int argc
, char *const *argv
)
234 plan_tests(kTestCount
);