]> git.saurik.com Git - apple/security.git/blob - OSX/utilities/Regressions/su-41-secdb-stress.c
Security-57740.1.18.tar.gz
[apple/security.git] / OSX / utilities / Regressions / su-41-secdb-stress.c
1 /*
2 * Copyright (c) 2013-2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
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
11 * file.
12 *
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.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25 #include <utilities/SecCFWrappers.h>
26 #include <utilities/SecDb.h>
27 #include <utilities/SecDispatchRelease.h>
28
29 #include <CoreFoundation/CoreFoundation.h>
30
31 #include "utilities_regressions.h"
32 #include <time.h>
33
34 #define kTestCount 3418
35
36 // Queue to protect counters and test_ok invocations
37 static dispatch_queue_t count_queue;
38
39 #define ts_ok(THIS, ...) \
40 ({ \
41 bool is_ok = !!(THIS); \
42 dispatch_sync(count_queue, ^{ \
43 test_ok(is_ok, test_create_description(__VA_ARGS__), test_directive, \
44 test_reason, __FILE__, __LINE__, NULL); \
45 }); \
46 is_ok; \
47 })
48
49 #define ts_ok_status(THIS, ...) \
50 ({ \
51 OSStatus _this = (THIS); \
52 __block bool is_ok; \
53 dispatch_sync(count_queue, ^{ \
54 is_ok = test_ok(!_this, test_create_description(__VA_ARGS__), \
55 test_directive, test_reason, __FILE__, __LINE__, \
56 "# status: %s(%ld)\n", \
57 sec_errstr(_this), _this); \
58 }); \
59 is_ok; \
60 })
61
62
63 typedef void (^SecDbBlock)(SecDbConnectionRef dbconn);
64
65 #define SecDbExecWithSql(dbconn, sql) test_SecDbExecWithSql(dbconn, sql, test_directive, test_reason, __FILE__, __LINE__)
66
67 static void test_SecDbExecWithSql(SecDbConnectionRef dbconn, CFStringRef sql CF_CONSUMED, const char *directive,
68 const char *reason, const char *file, unsigned line) {
69 CFErrorRef execError = NULL;
70 bool is_ok = !!(SecDbExec(dbconn, sql, &execError));
71 dispatch_sync(count_queue, ^{
72 test_ok(is_ok, test_create_description("exec %@: %@", sql, execError), directive, reason, file, line, NULL);
73 });
74 CFReleaseNull(execError);
75 CFReleaseSafe(sql);
76 }
77
78 #define SecDbDeleteWithInts(dbconn, key, value) test_SecDbDeleteWithInts(dbconn, key, value, test_directive, test_reason, __FILE__, __LINE__)
79 static void test_SecDbDeleteWithInts(SecDbConnectionRef dbconn, int key, int value, const char *directive,
80 const char *reason, const char *file, unsigned line) {
81 test_SecDbExecWithSql(dbconn, CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
82 CFSTR("DELETE FROM tablea WHERE key=%d AND value=%d;"), key, value), directive, reason, file, line);
83 }
84
85 static void SecDbDoReadOp(SecDbConnectionRef dbconn, size_t seed) {
86 switch (seed % 2) {
87 case 0:
88 {
89 CFErrorRef prepareError = NULL;
90 CFStringRef sql = CFSTR("SELECT key,value FROM tablea;");
91 ts_ok(SecDbPrepare(dbconn, sql, &prepareError, ^void (sqlite3_stmt *stmt) {
92 CFErrorRef stepError = NULL;
93 ts_ok(SecDbStep(dbconn, stmt, &stepError, ^(bool *stop) {
94 //const unsigned char *key = sqlite3_column_text(stmt, 1);
95 //pass("got a row key: %s", key);
96 // A row happened, we're done
97 *stop = true;
98 }), "SecDbStep: %@", stepError);
99 CFReleaseNull(stepError);
100 }), "SecDbPrepare: %@", prepareError);
101 CFReleaseNull(prepareError);
102 break;
103 }
104 case 1:
105 {
106 CFErrorRef prepareError = NULL;
107 CFStringRef sql = CFSTR("SELECT key,value FROM tablea;");
108 ts_ok(SecDbPrepare(dbconn, sql, &prepareError, ^void (sqlite3_stmt *stmt) {
109 CFErrorRef stepError = NULL;
110 ts_ok(SecDbStep(dbconn, stmt, &stepError, ^(bool *stop) {
111 //const unsigned char *key = sqlite3_column_text(stmt, 1);
112 //pass("got a row key: %s", key);
113 }), "SecDbStep: %@", stepError);
114 CFReleaseNull(stepError);
115 sqlite3_reset(stmt);
116 ts_ok(SecDbStep(dbconn, stmt, &stepError, ^(bool *stop) {
117 //const unsigned char *key = sqlite3_column_text(stmt, 1);
118 //pass("got a row key: %s", key);
119 *stop = true;
120 }), "SecDbStep: %@", stepError);
121 CFReleaseNull(stepError);
122 }), "SecDbPrepare: %@", prepareError);
123 CFReleaseNull(prepareError);
124 break;
125 }
126 }
127 }
128
129 static void SecDbDoWriteOp(SecDbConnectionRef dbconn, size_t seed) {
130 switch (seed % 6) {
131 case 0:
132 SecDbExecWithSql(dbconn, CFSTR("INSERT INTO tablea(key,value)VALUES(1,2);"));
133 break;
134 case 1:
135 {
136 CFErrorRef txnError = NULL;
137 ts_ok(SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &txnError, ^(bool *commit) {
138 CFErrorRef execError = NULL;
139 ts_ok(SecDbExec(dbconn, CFSTR("INSERT INTO tablea (key,value)VALUES(13,21);"), &execError),
140 "exec: %@", execError);
141 CFReleaseNull(execError);
142 ts_ok(SecDbExec(dbconn, CFSTR("INSERT INTO tablea (key,value)VALUES(2,5);"), &execError),
143 "exec: %@", execError);
144 CFReleaseNull(execError);
145 }), "SecDbTransaction: %@", txnError);
146 CFReleaseNull(txnError);
147 break;
148 }
149 case 2:
150 {
151 CFErrorRef prepareError = NULL;
152 CFStringRef sql = CFSTR("INSERT INTO tablea(key,value)VALUES(?,?);");
153 ts_ok(SecDbPrepare(dbconn, sql, &prepareError, ^void (sqlite3_stmt *stmt) {
154 CFErrorRef stepError = NULL;
155 ts_ok_status(sqlite3_bind_text(stmt, 1, "key1", 4, NULL), "bind_text[1]");
156 ts_ok_status(sqlite3_bind_blob(stmt, 2, "value1", 6, NULL), "bind_blob[2]");
157 ts_ok(SecDbStep(dbconn, stmt, &stepError, NULL), "SecDbStep: %@", stepError);
158 CFReleaseNull(stepError);
159 }), "SecDbPrepare: %@", prepareError);
160 CFReleaseNull(prepareError);
161 break;
162 }
163 case 3:
164 SecDbDeleteWithInts(dbconn, 1, 2);
165 break;
166 case 4:
167 SecDbDeleteWithInts(dbconn, 13, 21);
168 break;
169 case 5:
170 SecDbDeleteWithInts(dbconn, 2, 5);
171 break;
172 }
173 }
174
175 static void tests(void)
176 {
177 count_queue = dispatch_queue_create("count_queue", DISPATCH_QUEUE_SERIAL);
178
179 CFTypeID typeID = SecDbGetTypeID();
180 CFStringRef tid = CFCopyTypeIDDescription(typeID);
181 ts_ok(CFEqual(CFSTR("SecDb"), tid), "TypeIdDescription is SecDb");
182 CFReleaseNull(tid);
183
184 typeID = SecDbConnectionGetTypeID();
185 tid = CFCopyTypeIDDescription(typeID);
186 ts_ok(CFEqual(CFSTR("SecDbConnection"), tid), "TypeIdDescription is SecDbConnection");
187 CFReleaseNull(tid);
188
189 const char *home_var = getenv("HOME");
190 CFStringRef dbName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s/Library/Keychains/su-41-sqldb-stress.db"), home_var ? home_var : "");
191 CFStringPerformWithCString(dbName, ^(const char *path) { unlink(path); });
192
193 SecDbRef db = SecDbCreate(dbName, ^bool (SecDbConnectionRef dbconn, bool did_create, bool *callMeAgainForNextConnection, CFErrorRef *firstOpenError) {
194 // This test will run when the database is first opened.
195 return ts_ok(SecDbExec(dbconn, CFSTR("CREATE TABLE tablea(key TEXT,value BLOB);"), firstOpenError),
196 "create table: %@", *firstOpenError);
197 });
198 ts_ok(db, "SecDbCreate");
199
200 __block CFIndex max_idle = 0;
201 __block CFIndex max_readers = 0;
202 __block CFIndex max_writers = 0;
203 __block CFIndex cur_readers = 0;
204 __block CFIndex cur_writers = 0;
205
206 dispatch_group_t group = dispatch_group_create();
207 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
208 dispatch_semaphore_t sema = dispatch_semaphore_create(50); // use semaphore so we dont end all threads an deadlock
209 for (size_t job=0; job < 1000; ++job) {
210 dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
211 dispatch_group_async(group, queue, ^{
212 CFIndex cur_idle = SecDbIdleConnectionCount(db);
213 dispatch_sync(count_queue, ^{ if (max_idle < cur_idle) max_idle = cur_idle; });
214 CFErrorRef performError = NULL;
215 if (job % 7 == 0) {
216 ts_ok(SecDbPerformWrite(db, &performError, ^void (SecDbConnectionRef dbconn) {
217 dispatch_sync(count_queue, ^{ cur_writers++; if (max_writers < cur_writers) max_writers = cur_writers; });
218 SecDbDoWriteOp(dbconn, job);
219 dispatch_sync(count_queue, ^{ cur_writers--; });
220 }), "write %@", performError);
221 } else {
222 CFErrorRef performError = NULL;
223 ts_ok(SecDbPerformRead(db, &performError, ^void (SecDbConnectionRef dbconn) {
224 dispatch_sync(count_queue, ^{ cur_readers++; if (max_readers < cur_readers) max_readers = cur_readers; });
225 SecDbDoReadOp(dbconn, job);
226 dispatch_sync(count_queue, ^{ cur_readers--; });
227 }), "read %@", performError);
228 }
229 CFReleaseNull(performError);
230 dispatch_semaphore_signal(sema);
231 });
232 }
233 dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
234 dispatch_release(group);
235 dispatch_release(sema);
236
237 CFErrorRef writeError = NULL;
238 ts_ok(SecDbPerformWrite(db, &writeError, ^(SecDbConnectionRef dbconn){
239 SecDbExecWithSql(dbconn, CFSTR("DROP TABLE tablea;"));
240 }), "SecDbPerformWrite: %@", writeError);
241 CFReleaseNull(writeError);
242
243 dispatch_release_null(count_queue);
244
245 cmp_ok(max_idle, >=, kSecDbMaxIdleHandles - 1, "max idle at least %d", kSecDbMaxIdleHandles - 1);
246 cmp_ok(max_writers, >=, kSecDbMaxWriters - 1, "max writers at least %d", kSecDbMaxWriters - 1);
247 cmp_ok(max_readers, >=, kSecDbMaxReaders - 1, "max readers at least %d", kSecDbMaxReaders - 1);
248 TODO: {
249 todo("race conditions make us not always hit the limits reliably.");
250 is(max_idle, kSecDbMaxIdleHandles, "max idle connection count is %d", kSecDbMaxIdleHandles);
251 is(max_writers, kSecDbMaxWriters, "max writers is %d", kSecDbMaxWriters);
252 is(max_readers, kSecDbMaxReaders, "max readers is %d", kSecDbMaxReaders);
253 }
254
255 CFReleaseSafe(dbName);
256 CFReleaseNull(db);
257 }
258
259 int su_41_secdb_stress(int argc, char *const *argv)
260 {
261 plan_tests(kTestCount);
262 tests();
263
264 return 0;
265 }