]>
Commit | Line | Data |
---|---|---|
d8f41ccd A |
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 | ||
427c49bc A |
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 | ||
5c19dc3a | 34 | #define kTestCount 3418 |
427c49bc A |
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 | ||
866f8763 | 193 | SecDbRef db = SecDbCreate(dbName, ^bool (SecDbRef db, SecDbConnectionRef dbconn, bool did_create, bool *callMeAgainForNextConnection, CFErrorRef *firstOpenError) { |
427c49bc A |
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); | |
e3d460c9 | 208 | dispatch_semaphore_t sema = dispatch_semaphore_create(50); // use semaphore so we dont end all threads an deadlock |
427c49bc | 209 | for (size_t job=0; job < 1000; ++job) { |
e3d460c9 | 210 | dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); |
427c49bc A |
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); | |
e3d460c9 | 230 | dispatch_semaphore_signal(sema); |
427c49bc A |
231 | }); |
232 | } | |
233 | dispatch_group_wait(group, DISPATCH_TIME_FOREVER); | |
234 | dispatch_release(group); | |
fa7225c8 | 235 | dispatch_release(sema); |
427c49bc A |
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 | ||
5c19dc3a | 245 | cmp_ok(max_idle, >=, kSecDbMaxIdleHandles - 1, "max idle at least %d", kSecDbMaxIdleHandles - 1); |
d8f41ccd A |
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."); | |
5c19dc3a | 250 | is(max_idle, kSecDbMaxIdleHandles, "max idle connection count is %d", kSecDbMaxIdleHandles); |
d8f41ccd A |
251 | is(max_writers, kSecDbMaxWriters, "max writers is %d", kSecDbMaxWriters); |
252 | is(max_readers, kSecDbMaxReaders, "max readers is %d", kSecDbMaxReaders); | |
253 | } | |
427c49bc | 254 | |
fa7225c8 | 255 | CFReleaseSafe(dbName); |
427c49bc A |
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 | } |