1<?php
2
3function check_skip_any() {
4    if (dba_handlers() === []) {
5        die('skip no handlers installed');
6    }
7    if (dba_handlers() === ['cdb']) {
8        die('skip only cdb installed which is not suitable');
9    }
10    if (dba_handlers() === ['cdb_make']) {
11        die('skip only cdb_make installed which is not suitable');
12    }
13}
14
15function check_skip(string $handler) {
16    $handlers = dba_handlers();
17    if ($handlers === []) {
18        die('skip no handlers installed');
19    }
20    if (!in_array($handler, $handlers)) {
21        $HND = strtoupper($handler);
22        die("skip $HND handler not available");
23    }
24}
25
26function get_any_handler(): string {
27    foreach (dba_handlers() as $handler) {
28        // Those are weird
29        if ($handler !== 'cdb' && $handler !== 'cdb_make' && $handler !== 'inifile') {
30            echo 'Using handler: "', $handler, '"', \PHP_EOL;
31            return $handler;
32        }
33    }
34    return 'should_not_happen';
35}
36function get_any_db(string $name) {
37    return dba_open($name, 'c', get_any_handler());
38}
39
40enum LockFlag: string {
41    case FileLock = 'l';
42    case DbLock = 'd';
43    case NoLock = '-';
44}
45
46function set_up_db_ex(string $handler, string $name, LockFlag $lock, bool $persistent = false) {
47    $lock_flag = $lock->value;
48    // Open file in creation/truncation mode
49    $func = $persistent ? 'dba_popen' : 'dba_open';
50
51    $db_file = $func($name, 'n'.$lock_flag, $handler);
52
53    if ($db_file === false) {
54        die("Failed to create DB");
55    }
56
57    // Insert some data
58    dba_insert("key1", "Content String 1", $db_file);
59    dba_insert("key2", "Content String 2", $db_file);
60    dba_insert("key3", "Third Content String", $db_file);
61    dba_insert("key4", "Another Content String", $db_file);
62    dba_insert("key5", "The last content string", $db_file);
63
64    // Insert date with array keys
65    dba_insert(["", "name9"], "Content String 9", $db_file);
66    dba_insert(["key10", "name10"] , "Content String 10", $db_file);
67    dba_insert("[key30]name30", "Content String 30", $db_file);
68
69    return $db_file;
70}
71
72function set_up_db(string $handler, string $name, LockFlag $lock = LockFlag::FileLock): void {
73    $db_file = set_up_db_ex($handler, $name, $lock);
74    // Close creation/truncation handler
75    dba_close($db_file);
76}
77
78function run_common_read_only_test($dbHandle): void {
79    $key = dba_firstkey($dbHandle);
80    $result = [];
81    while ($key) {
82        $result[$key] = dba_fetch($key, $dbHandle);
83        $key = dba_nextkey($dbHandle);
84    }
85    ksort($result);
86    var_dump($result);
87}
88
89function run_standard_tests_ex(string $handler, string $name, LockFlag $lock, bool $persistent = false): void
90{
91    $lock_flag = $lock->value;
92    set_up_db($handler, $name, $lock);
93    $db_writer = dba_open($name, 'w'.$lock_flag, $handler);
94    if ($db_writer === false) {
95        die("Failed to open DB for write");
96    }
97
98    echo 'Remove key 1 and 3', \PHP_EOL;
99    var_dump(dba_delete("key3", $db_writer));
100    var_dump(dba_delete("key1", $db_writer));
101
102    echo 'Try to remove key 1 again', \PHP_EOL;
103    var_dump(dba_delete("key1", $db_writer));
104
105    // Fetch data
106    $key = dba_firstkey($db_writer);
107    $total_keys = 0;
108    while ($key) {
109        echo $key, ': ', dba_fetch($key, $db_writer), \PHP_EOL;
110        $key = dba_nextkey($db_writer);
111        $total_keys++;
112    }
113    echo 'Total keys: ', $total_keys, \PHP_EOL;
114    for ($i = 1; $i < 6; $i++) {
115        echo "Key $i exists? ", dba_exists("key$i", $db_writer) ? 'Y' : 'N', \PHP_EOL;
116    }
117
118    echo 'Replace second key data', \PHP_EOL;
119    var_dump(dba_replace('key2', 'Content 2 replaced', $db_writer));
120    echo dba_fetch('key2', $db_writer), \PHP_EOL;
121
122    // Check that read is possible when a lock is used
123    $test_flag = 't';
124    if ($lock === LockFlag::NoLock) {
125        // No point testing when we don't use locks
126        $test_flag = '';
127    }
128    $db_reader = @dba_open($name, 'r'.$lock_flag.$test_flag, $handler);
129    if ($db_reader === false) {
130        echo 'Read during write: not allowed', \PHP_EOL;
131    } else {
132        echo 'Read during write: allowed', \PHP_EOL;
133        dba_close($db_reader);
134    }
135
136    if (dba_insert('key number 6', 'The 6th value', $db_writer)) {
137        echo 'Expected: Added a new data entry', \PHP_EOL;
138    } else {
139        echo 'Unexpected: Failed to add a new data entry', \PHP_EOL;
140    }
141
142    if (dba_insert('key number 6', 'The 6th value inserted again would be an error', $db_writer)) {
143        echo 'Unexpected: Wrote data to already used key', \PHP_EOL;
144    } else {
145        echo 'Expected: Failed to insert data for already used key', \PHP_EOL;
146    }
147
148    echo 'Replace second key data', \PHP_EOL;
149    var_dump(dba_replace('key2', 'Content 2 replaced 2nd time', $db_writer));
150    echo 'Delete "key4"', \PHP_EOL;
151    var_dump(dba_delete('key4', $db_writer));
152    echo 'Fetch "key2": ', dba_fetch('key2', $db_writer), \PHP_EOL;
153    echo 'Fetch "key number 6": ', dba_fetch('key number 6', $db_writer), \PHP_EOL;
154    dba_close($db_writer); // when the writer is open at least db3 would fail because of buffered io.
155
156    $db_reader = dba_open($name, 'r'.$lock_flag, $handler);
157    run_common_read_only_test($db_reader);
158    dba_close($db_reader);
159
160    /* TODO popen test? Old code copied from the previous general test
161    if (($db_file = dba_popen($db_filename, 'r'.($lock_flag==''?'':'-'), $handler))!==FALSE) {
162        if ($handler == 'dbm' || $handler == "tcadb") {
163            dba_close($db_file);
164        }
165    }
166     */
167}
168
169const MODES = ['r', 'w', 'c', 'n'];
170const LOCKS = ['l', 'd', '-', '' /* No lock flag is like 'd' */];
171function run_creation_tests_ex(string $handler, string $file_suffix, string $pre_req): void
172{
173    $db_name = $handler . $file_suffix;
174    foreach (MODES as $mode) {
175        foreach (LOCKS as $lock) {
176            eval($pre_req);
177            $arg = $mode.$lock;
178            echo 'Mode parameter is "', $arg, '":', \PHP_EOL;
179            $db = dba_open($db_name, $arg, $handler);
180            if ($db !== false) {
181                assert(file_exists($db_name));
182                $status = dba_insert("key1", "This is a test insert", $db);
183                if ($status) {
184                    $fetch = dba_fetch("key1", $db);
185                    if ($fetch === false) {
186                        echo 'Cannot fetch insertion', \PHP_EOL;
187                    } else {
188                        echo $fetch, \PHP_EOL;
189                    }
190                } else {
191                    echo 'Insertion failed', \PHP_EOL;
192                }
193                dba_close($db);
194            } else {
195                echo 'Opening DB failed', \PHP_EOL;
196            }
197            cleanup_standard_db($db_name);
198        }
199    }
200}
201
202function run_creation_tests(string $handler): void
203{
204    $extension = $handler === 'tcadb' ? 'tch' : 'db';
205    /* Trying to open a non-existing file */
206    echo '=== OPENING NON-EXISTING FILE ===', \PHP_EOL;
207    run_creation_tests_ex($handler, '_not_existing.'.$extension, '');
208
209    /* Trying to open an existing db file */
210    echo '=== OPENING EXISTING DB FILE ===', \PHP_EOL;
211    run_creation_tests_ex($handler, '_existing.'.$extension, 'dba_open($db_name, "n", $handler);');
212
213    /* Trying to open an existing random file */
214    echo '=== OPENING EXISTING RANDOM FILE ===', \PHP_EOL;
215    run_creation_tests_ex($handler, '_random.txt', 'file_put_contents($db_name, "Dummy contents");');
216}
217
218function clean_creation_tests(string $handler): void {
219    $db_name = $handler . '_not_existing.db';
220    cleanup_standard_db($db_name);
221    $db_name = $handler . '_existing.db';
222    cleanup_standard_db($db_name);
223    $db_name = $handler . '_random.txt';
224    cleanup_standard_db($db_name);
225}
226
227function run_standard_tests(string $handler, string $name): void {
228    echo '=== RUNNING WITH FILE LOCK ===', \PHP_EOL;
229    ob_start();
230    set_up_db($handler, $name, LockFlag::FileLock);
231    run_standard_tests_ex($handler, $name, LockFlag::FileLock);
232    cleanup_standard_db($name);
233    $run1_output = ob_get_flush();
234    echo '=== RUNNING WITH DB LOCK (default) ===', \PHP_EOL;
235    ob_start();
236    set_up_db($handler, $name, LockFlag::DbLock);
237    run_standard_tests_ex($handler, $name, LockFlag::DbLock);
238    cleanup_standard_db($name);
239    $run2_output = ob_get_clean();
240    if ($run1_output === $run2_output) {
241        echo 'SAME OUTPUT AS PREVIOUS RUN', \PHP_EOL;
242    } else {
243        echo $run2_output;
244    }
245
246    echo '=== RUNNING WITH NO LOCK ===', \PHP_EOL;
247    ob_start();
248    set_up_db($handler, $name, LockFlag::NoLock);
249    run_standard_tests_ex($handler, $name, LockFlag::NoLock);
250    $run3_output = ob_get_clean();
251    if ($run2_output === $run3_output) {
252        echo 'SAME OUTPUT AS PREVIOUS RUN', \PHP_EOL;
253    } else if ($run2_output === str_replace( // If only the fact that the lock prevented reads
254            'Read during write: allowed',
255            'Read during write: not allowed',
256            $run3_output
257        )
258    ) {
259        echo 'SAME OUTPUT AS PREVIOUS RUN (modulo read during write due to no lock)', \PHP_EOL;
260    } else {
261        echo $run3_output;
262    }
263}
264
265// TODO Array keys insertion
266// TODO Run all lock flags
267function set_up_cdb_db_and_run(string $name): void {
268    set_up_db('cdb', $name);
269
270    $db_file = dba_open($name, 'rl', 'cdb');
271    if ($db_file === false) {
272        die("Failed to reopen DB");
273    }
274    for ($i = 1; $i < 6; $i++) {
275        echo "Key $i exists? ", dba_exists("key$i", $db_file) ? 'Y' : 'N', \PHP_EOL;
276    }
277    run_common_read_only_test($db_file);
278    dba_close($db_file);
279
280    echo '--NO-LOCK--', \PHP_EOL;
281    cleanup_standard_db($name);
282    set_up_db('cdb', $name, LockFlag::NoLock);
283    $db_file = dba_open($name, 'r-', 'cdb');
284    if ($db_file === false) {
285        die("Failed to reopen DB");
286    }
287    for ($i = 1; $i < 6; $i++) {
288        echo "Key $i exists? ", dba_exists("key$i", $db_file) ? 'Y' : 'N', \PHP_EOL;
289    }
290    run_common_read_only_test($db_file);
291}
292
293function cleanup_standard_db(string $name): void {
294    @unlink($name);
295    @unlink($name.'.lck');
296    @unlink($name.'-lock');
297}
298