2 * Copyright (c) 2013-2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 #include <utilities/SecCFWrappers.h>
26 #include <utilities/SecDb.h>
27 #include <utilities/SecDbInternal.h>
28 #include <utilities/SecDispatchRelease.h>
30 #include <CoreFoundation/CoreFoundation.h>
32 #include "utilities_regressions.h"
35 #define kTestCount 3422 // God I love magic numbers
37 // Queue to protect counters and test_ok invocations
38 static dispatch_queue_t count_queue
;
40 #define ts_ok(THIS, ...) \
42 bool is_ok = !!(THIS); \
43 dispatch_sync(count_queue, ^{ \
44 test_ok(is_ok, test_create_description(__VA_ARGS__), test_directive, \
45 test_reason, __FILE__, __LINE__, NULL); \
50 #define ts_ok_status(THIS, ...) \
52 OSStatus _this = (THIS); \
54 dispatch_sync(count_queue, ^{ \
55 is_ok = test_ok(!_this, test_create_description(__VA_ARGS__), \
56 test_directive, test_reason, __FILE__, __LINE__, \
57 "# status: %s(%ld)\n", \
58 sec_errstr(_this), _this); \
64 typedef void (^SecDbBlock
)(SecDbConnectionRef dbconn
);
66 #define SecDbExecWithSql(dbconn, sql) test_SecDbExecWithSql(dbconn, sql, test_directive, test_reason, __FILE__, __LINE__)
68 static void test_SecDbExecWithSql(SecDbConnectionRef dbconn
, CFStringRef sql CF_CONSUMED
, const char *directive
,
69 const char *reason
, const char *file
, unsigned line
) {
70 CFErrorRef execError
= NULL
;
71 bool is_ok
= !!(SecDbExec(dbconn
, sql
, &execError
));
72 dispatch_sync(count_queue
, ^{
73 test_ok(is_ok
, test_create_description("exec %@: %@", sql
, execError
), directive
, reason
, file
, line
, NULL
);
75 CFReleaseNull(execError
);
79 #define SecDbDeleteWithInts(dbconn, key, value) test_SecDbDeleteWithInts(dbconn, key, value, test_directive, test_reason, __FILE__, __LINE__)
80 static void test_SecDbDeleteWithInts(SecDbConnectionRef dbconn
, int key
, int value
, const char *directive
,
81 const char *reason
, const char *file
, unsigned line
) {
82 test_SecDbExecWithSql(dbconn
, CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
83 CFSTR("DELETE FROM tablea WHERE key=%d AND value=%d;"), key
, value
), directive
, reason
, file
, line
);
86 static void SecDbDoReadOp(SecDbConnectionRef dbconn
, size_t seed
) {
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 // A row happened, we're done
99 }), "SecDbStep: %@", stepError
);
100 CFReleaseNull(stepError
);
101 }), "SecDbPrepare: %@", prepareError
);
102 CFReleaseNull(prepareError
);
107 CFErrorRef prepareError
= NULL
;
108 CFStringRef sql
= CFSTR("SELECT key,value FROM tablea;");
109 ts_ok(SecDbPrepare(dbconn
, sql
, &prepareError
, ^void (sqlite3_stmt
*stmt
) {
110 CFErrorRef stepError
= NULL
;
111 ts_ok(SecDbStep(dbconn
, stmt
, &stepError
, ^(bool *stop
) {
112 //const unsigned char *key = sqlite3_column_text(stmt, 1);
113 //pass("got a row key: %s", key);
114 }), "SecDbStep: %@", stepError
);
115 CFReleaseNull(stepError
);
117 ts_ok(SecDbStep(dbconn
, stmt
, &stepError
, ^(bool *stop
) {
118 //const unsigned char *key = sqlite3_column_text(stmt, 1);
119 //pass("got a row key: %s", key);
121 }), "SecDbStep: %@", stepError
);
122 CFReleaseNull(stepError
);
123 }), "SecDbPrepare: %@", prepareError
);
124 CFReleaseNull(prepareError
);
130 static void SecDbDoWriteOp(SecDbConnectionRef dbconn
, size_t seed
) {
133 SecDbExecWithSql(dbconn
, CFSTR("INSERT INTO tablea(key,value)VALUES(1,2);"));
137 CFErrorRef txnError
= NULL
;
138 ts_ok(SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &txnError
, ^(bool *commit
) {
139 CFErrorRef execError
= NULL
;
140 ts_ok(SecDbExec(dbconn
, CFSTR("INSERT INTO tablea (key,value)VALUES(13,21);"), &execError
),
141 "exec: %@", execError
);
142 CFReleaseNull(execError
);
143 ts_ok(SecDbExec(dbconn
, CFSTR("INSERT INTO tablea (key,value)VALUES(2,5);"), &execError
),
144 "exec: %@", execError
);
145 CFReleaseNull(execError
);
146 }), "SecDbTransaction: %@", txnError
);
147 CFReleaseNull(txnError
);
152 CFErrorRef prepareError
= NULL
;
153 CFStringRef sql
= CFSTR("INSERT INTO tablea(key,value)VALUES(?,?);");
154 ts_ok(SecDbPrepare(dbconn
, sql
, &prepareError
, ^void (sqlite3_stmt
*stmt
) {
155 CFErrorRef stepError
= NULL
;
156 ts_ok_status(sqlite3_bind_text(stmt
, 1, "key1", 4, NULL
), "bind_text[1]");
157 ts_ok_status(sqlite3_bind_blob(stmt
, 2, "value1", 6, NULL
), "bind_blob[2]");
158 ts_ok(SecDbStep(dbconn
, stmt
, &stepError
, NULL
), "SecDbStep: %@", stepError
);
159 CFReleaseNull(stepError
);
160 }), "SecDbPrepare: %@", prepareError
);
161 CFReleaseNull(prepareError
);
165 SecDbDeleteWithInts(dbconn
, 1, 2);
168 SecDbDeleteWithInts(dbconn
, 13, 21);
171 SecDbDeleteWithInts(dbconn
, 2, 5);
176 static void tests(void)
178 count_queue
= dispatch_queue_create("count_queue", DISPATCH_QUEUE_SERIAL
);
180 CFTypeID typeID
= SecDbGetTypeID();
181 CFStringRef tid
= CFCopyTypeIDDescription(typeID
);
182 ts_ok(CFEqual(CFSTR("SecDb"), tid
), "TypeIdDescription is SecDb");
185 typeID
= SecDbConnectionGetTypeID();
186 tid
= CFCopyTypeIDDescription(typeID
);
187 ts_ok(CFEqual(CFSTR("SecDbConnection"), tid
), "TypeIdDescription is SecDbConnection");
190 const char *home_var
= getenv("HOME");
191 CFStringRef dbName
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%s/Library/Keychains/su-41-secdb-stress.db"), home_var
? home_var
: "/var/tmp");
192 CFStringPerformWithCString(dbName
, ^(const char *path
) { unlink(path
); });
194 SecDbRef db
= SecDbCreate(dbName
, 0600, true, true, true, true, kSecDbMaxIdleHandles
,
195 ^bool (SecDbRef db
, SecDbConnectionRef dbconn
, bool did_create
, bool *callMeAgainForNextConnection
, CFErrorRef
*firstOpenError
)
197 // This test will run when the database is first opened.
198 return ts_ok(SecDbExec(dbconn
, CFSTR("CREATE TABLE tablea(key TEXT,value BLOB);"), firstOpenError
),
199 "create table: %@", *firstOpenError
);
201 ts_ok(db
, "SecDbCreate");
203 __block CFIndex max_idle
= 0;
204 __block CFIndex max_readers
= 0;
205 __block CFIndex max_writers
= 0;
206 __block CFIndex cur_readers
= 0;
207 __block CFIndex cur_writers
= 0;
209 dispatch_group_t group
= dispatch_group_create();
210 dispatch_queue_t queue
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0);
211 dispatch_semaphore_t sema
= dispatch_semaphore_create(50); // use semaphore so we dont end all threads an deadlock
212 for (size_t job
=0; job
< 1000; ++job
) {
213 dispatch_semaphore_wait(sema
, DISPATCH_TIME_FOREVER
);
214 dispatch_group_async(group
, queue
, ^{
215 CFIndex cur_idle
= SecDbIdleConnectionCount(db
);
216 dispatch_sync(count_queue
, ^{ if (max_idle
< cur_idle
) max_idle
= cur_idle
; });
217 CFErrorRef performError
= NULL
;
219 ts_ok(SecDbPerformWrite(db
, &performError
, ^void (SecDbConnectionRef dbconn
) {
220 dispatch_sync(count_queue
, ^{ cur_writers
++; if (max_writers
< cur_writers
) max_writers
= cur_writers
; });
221 SecDbDoWriteOp(dbconn
, job
);
222 dispatch_sync(count_queue
, ^{ cur_writers
--; });
223 }), "write %@", performError
);
225 CFErrorRef performError
= NULL
;
226 ts_ok(SecDbPerformRead(db
, &performError
, ^void (SecDbConnectionRef dbconn
) {
227 dispatch_sync(count_queue
, ^{ cur_readers
++; if (max_readers
< cur_readers
) max_readers
= cur_readers
; });
228 SecDbDoReadOp(dbconn
, job
);
229 dispatch_sync(count_queue
, ^{ cur_readers
--; });
230 }), "read %@", performError
);
232 CFReleaseNull(performError
);
233 dispatch_semaphore_signal(sema
);
236 dispatch_group_wait(group
, DISPATCH_TIME_FOREVER
);
237 dispatch_release(group
);
238 dispatch_release(sema
);
240 CFErrorRef writeError
= NULL
;
241 ts_ok(SecDbPerformWrite(db
, &writeError
, ^(SecDbConnectionRef dbconn
){
242 SecDbExecWithSql(dbconn
, CFSTR("DROP TABLE tablea;"));
243 }), "SecDbPerformWrite: %@", writeError
);
244 CFReleaseNull(writeError
);
246 dispatch_release_null(count_queue
);
248 cmp_ok(SecDbIdleConnectionCount(db
), >=, kSecDbMaxIdleHandles
- 1, "cur idle at least %lu", kSecDbMaxIdleHandles
- 1);
249 cmp_ok(SecDbIdleConnectionCount(db
), <=, kSecDbMaxIdleHandles
, "cur idle at most %lu", kSecDbMaxIdleHandles
);
250 cmp_ok(max_idle
, <=, kSecDbMaxIdleHandles
, "max idle at most %lu", kSecDbMaxIdleHandles
- 1);
251 cmp_ok(max_writers
, >=, kSecDbMaxWriters
- 1, "max writers at least %lu", kSecDbMaxWriters
- 1);
252 cmp_ok(max_readers
, >=, kSecDbMaxReaders
- 1, "max readers at least %lu", kSecDbMaxReaders
- 1);
253 cmp_ok(max_writers
, <=, kSecDbMaxWriters
, "max writers at most %lu", kSecDbMaxWriters
);
254 cmp_ok(max_readers
, <=, kSecDbMaxReaders
, "max readers at most %lu", kSecDbMaxReaders
);
256 todo("race conditions make us not always hit the limits reliably.");
257 is(max_idle
, kSecDbMaxIdleHandles
, "max idle connection count is %zu", kSecDbMaxIdleHandles
);
258 is(max_writers
, kSecDbMaxWriters
, "max writers is %zu", kSecDbMaxWriters
);
259 is(max_readers
, kSecDbMaxReaders
, "max readers is %zu", kSecDbMaxReaders
);
262 CFReleaseSafe(dbName
);
266 int su_41_secdb_stress(int argc
, char *const *argv
)
268 plan_tests(kTestCount
);