]> git.saurik.com Git - apple/security.git/blob - OSX/utilities/Regressions/su-41-secdb-stress.c
Security-59754.41.1.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/SecDbInternal.h>
28 #include <utilities/SecDispatchRelease.h>
29
30 #include <CoreFoundation/CoreFoundation.h>
31
32 #include "utilities_regressions.h"
33 #include <time.h>
34
35 #define kTestCount 3422 // God I love magic numbers
36
37 // Queue to protect counters and test_ok invocations
38 static dispatch_queue_t count_queue;
39
40 #define ts_ok(THIS, ...) \
41 ({ \
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); \
46 }); \
47 is_ok; \
48 })
49
50 #define ts_ok_status(THIS, ...) \
51 ({ \
52 OSStatus _this = (THIS); \
53 __block bool is_ok; \
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); \
59 }); \
60 is_ok; \
61 })
62
63
64 typedef void (^SecDbBlock)(SecDbConnectionRef dbconn);
65
66 #define SecDbExecWithSql(dbconn, sql) test_SecDbExecWithSql(dbconn, sql, test_directive, test_reason, __FILE__, __LINE__)
67
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);
74 });
75 CFReleaseNull(execError);
76 CFReleaseSafe(sql);
77 }
78
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);
84 }
85
86 static void SecDbDoReadOp(SecDbConnectionRef dbconn, size_t seed) {
87 switch (seed % 2) {
88 case 0:
89 {
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
98 *stop = true;
99 }), "SecDbStep: %@", stepError);
100 CFReleaseNull(stepError);
101 }), "SecDbPrepare: %@", prepareError);
102 CFReleaseNull(prepareError);
103 break;
104 }
105 case 1:
106 {
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);
116 sqlite3_reset(stmt);
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);
120 *stop = true;
121 }), "SecDbStep: %@", stepError);
122 CFReleaseNull(stepError);
123 }), "SecDbPrepare: %@", prepareError);
124 CFReleaseNull(prepareError);
125 break;
126 }
127 }
128 }
129
130 static void SecDbDoWriteOp(SecDbConnectionRef dbconn, size_t seed) {
131 switch (seed % 6) {
132 case 0:
133 SecDbExecWithSql(dbconn, CFSTR("INSERT INTO tablea(key,value)VALUES(1,2);"));
134 break;
135 case 1:
136 {
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);
148 break;
149 }
150 case 2:
151 {
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);
162 break;
163 }
164 case 3:
165 SecDbDeleteWithInts(dbconn, 1, 2);
166 break;
167 case 4:
168 SecDbDeleteWithInts(dbconn, 13, 21);
169 break;
170 case 5:
171 SecDbDeleteWithInts(dbconn, 2, 5);
172 break;
173 }
174 }
175
176 static void tests(void)
177 {
178 count_queue = dispatch_queue_create("count_queue", DISPATCH_QUEUE_SERIAL);
179
180 CFTypeID typeID = SecDbGetTypeID();
181 CFStringRef tid = CFCopyTypeIDDescription(typeID);
182 ts_ok(CFEqual(CFSTR("SecDb"), tid), "TypeIdDescription is SecDb");
183 CFReleaseNull(tid);
184
185 typeID = SecDbConnectionGetTypeID();
186 tid = CFCopyTypeIDDescription(typeID);
187 ts_ok(CFEqual(CFSTR("SecDbConnection"), tid), "TypeIdDescription is SecDbConnection");
188 CFReleaseNull(tid);
189
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); });
193
194 SecDbRef db = SecDbCreate(dbName, 0600, true, true, true, true, kSecDbMaxIdleHandles,
195 ^bool (SecDbRef db, SecDbConnectionRef dbconn, bool did_create, bool *callMeAgainForNextConnection, CFErrorRef *firstOpenError)
196 {
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);
200 });
201 ts_ok(db, "SecDbCreate");
202
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;
208
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;
218 if (job % 7 == 0) {
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);
224 } else {
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);
231 }
232 CFReleaseNull(performError);
233 dispatch_semaphore_signal(sema);
234 });
235 }
236 dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
237 dispatch_release(group);
238 dispatch_release(sema);
239
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);
245
246 dispatch_release_null(count_queue);
247
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);
255 TODO: {
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);
260 }
261
262 CFReleaseSafe(dbName);
263 CFReleaseNull(db);
264 }
265
266 int su_41_secdb_stress(int argc, char *const *argv)
267 {
268 plan_tests(kTestCount);
269 tests();
270
271 return 0;
272 }