1 /* fs-x-pack-test.c --- tests for the FSX filesystem
2  *
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  */
22 
23 #include <stdlib.h>
24 #include <string.h>
25 #include <apr_pools.h>
26 
27 #include "../svn_test.h"
28 #include "../../libsvn_fs_x/batch_fsync.h"
29 #include "../../libsvn_fs_x/fs.h"
30 #include "../../libsvn_fs_x/reps.h"
31 
32 #include "svn_pools.h"
33 #include "svn_props.h"
34 #include "svn_fs.h"
35 #include "private/svn_string_private.h"
36 
37 #include "../svn_test_fs.h"
38 
39 
40 
41 /*** Helper Functions ***/
42 
43 /* Write the format number and maximum number of files per directory
44    to a new format file in PATH, overwriting a previously existing
45    file.  Use POOL for temporary allocation.
46 
47    (This implementation is largely stolen from libsvn_fs_fs/fs_fs.c.) */
48 static svn_error_t *
write_format(const char * path,int format,int max_files_per_dir,apr_pool_t * pool)49 write_format(const char *path,
50              int format,
51              int max_files_per_dir,
52              apr_pool_t *pool)
53 {
54   const char *contents;
55 
56   path = svn_dirent_join(path, "format", pool);
57   SVN_TEST_ASSERT(max_files_per_dir > 0);
58 
59   contents = apr_psprintf(pool,
60                           "%d\n"
61                           "layout sharded %d\n",
62                           format, max_files_per_dir);
63 
64   SVN_ERR(svn_io_write_atomic2(path, contents, strlen(contents),
65                                NULL /* copy perms */, FALSE, pool));
66 
67   /* And set the perms to make it read only */
68   return svn_io_set_file_read_only(path, FALSE, pool);
69 }
70 
71 /* Return the expected contents of "iota" in revision REV. */
72 static const char *
get_rev_contents(svn_revnum_t rev,apr_pool_t * pool)73 get_rev_contents(svn_revnum_t rev, apr_pool_t *pool)
74 {
75   /* Toss in a bunch of magic numbers for spice. */
76   apr_int64_t num = ((rev * 1234353 + 4358) * 4583 + ((rev % 4) << 1)) / 42;
77   return apr_psprintf(pool, "%" APR_INT64_T_FMT "\n", num);
78 }
79 
80 struct pack_notify_baton
81 {
82   apr_int64_t expected_shard;
83   svn_fs_pack_notify_action_t expected_action;
84 };
85 
86 static svn_error_t *
pack_notify(void * baton,apr_int64_t shard,svn_fs_pack_notify_action_t action,apr_pool_t * pool)87 pack_notify(void *baton,
88             apr_int64_t shard,
89             svn_fs_pack_notify_action_t action,
90             apr_pool_t *pool)
91 {
92   struct pack_notify_baton *pnb = baton;
93 
94   SVN_TEST_ASSERT(shard == pnb->expected_shard);
95   SVN_TEST_ASSERT(action == pnb->expected_action);
96 
97   /* Update expectations. */
98   switch (action)
99     {
100       case svn_fs_pack_notify_start:
101         pnb->expected_action = svn_fs_pack_notify_end;
102         break;
103 
104       case svn_fs_pack_notify_end:
105         pnb->expected_action = svn_fs_pack_notify_start;
106         pnb->expected_shard++;
107         break;
108 
109       default:
110         return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
111                                 "Unknown notification action when packing");
112     }
113 
114   return SVN_NO_ERROR;
115 }
116 
117 #define R1_LOG_MSG "Let's serf"
118 
119 /* Create a packed filesystem in DIR.  Set the shard size to
120    SHARD_SIZE and create NUM_REVS number of revisions (in addition to
121    r0).  Use POOL for allocations.  After this function successfully
122    completes, the filesystem's youngest revision number will be the
123    same as NUM_REVS.  */
124 static svn_error_t *
create_packed_filesystem(const char * dir,const svn_test_opts_t * opts,int num_revs,int shard_size,apr_pool_t * pool)125 create_packed_filesystem(const char *dir,
126                          const svn_test_opts_t *opts,
127                          int num_revs,
128                          int shard_size,
129                          apr_pool_t *pool)
130 {
131   svn_fs_t *fs;
132   svn_fs_txn_t *txn;
133   svn_fs_root_t *txn_root;
134   const char *conflict;
135   svn_revnum_t after_rev;
136   apr_pool_t *subpool = svn_pool_create(pool);
137   struct pack_notify_baton pnb;
138   apr_pool_t *iterpool;
139   int version;
140 
141   /* Bail (with success) on known-untestable scenarios */
142   if (strcmp(opts->fs_type, "fsx") != 0)
143     return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL,
144                             "this will test FSX repositories only");
145 
146   if (opts->server_minor_version && (opts->server_minor_version < 9))
147     return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL,
148                             "pre-1.9 SVN doesn't support FSX");
149 
150   /* Create a filesystem, then close it */
151   SVN_ERR(svn_test__create_fs(&fs, dir, opts, subpool));
152   svn_pool_destroy(subpool);
153 
154   subpool = svn_pool_create(pool);
155 
156   /* Rewrite the format file */
157   SVN_ERR(svn_io_read_version_file(&version,
158                                    svn_dirent_join(dir, "format", subpool),
159                                    subpool));
160   SVN_ERR(write_format(dir, version, shard_size, subpool));
161 
162   /* Reopen the filesystem */
163   SVN_ERR(svn_fs_open2(&fs, dir, NULL, subpool, subpool));
164 
165   /* Revision 1: the Greek tree */
166   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
167   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
168   SVN_ERR(svn_test__create_greek_tree(txn_root, subpool));
169   SVN_ERR(svn_fs_change_txn_prop(txn, SVN_PROP_REVISION_LOG,
170                                  svn_string_create(R1_LOG_MSG, pool),
171                                  pool));
172   SVN_ERR(svn_fs_commit_txn(&conflict, &after_rev, txn, subpool));
173   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(after_rev));
174 
175   /* Revisions 2 thru NUM_REVS-1: content tweaks to "iota". */
176   iterpool = svn_pool_create(subpool);
177   while (after_rev < num_revs)
178     {
179       svn_pool_clear(iterpool);
180       SVN_ERR(svn_fs_begin_txn(&txn, fs, after_rev, iterpool));
181       SVN_ERR(svn_fs_txn_root(&txn_root, txn, iterpool));
182       SVN_ERR(svn_test__set_file_contents(txn_root, "iota",
183                                           get_rev_contents(after_rev + 1,
184                                                            iterpool),
185                                           iterpool));
186       SVN_ERR(svn_fs_commit_txn(&conflict, &after_rev, txn, iterpool));
187       SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(after_rev));
188     }
189   svn_pool_destroy(iterpool);
190   svn_pool_destroy(subpool);
191 
192   /* Now pack the FS */
193   pnb.expected_shard = 0;
194   pnb.expected_action = svn_fs_pack_notify_start;
195   return svn_fs_pack(dir, pack_notify, &pnb, NULL, NULL, pool);
196 }
197 
198 /* Create a packed FSFS filesystem for revprop tests at REPO_NAME with
199  * MAX_REV revisions and the given SHARD_SIZE and OPTS.  Return it in *FS.
200  * Use POOL for allocations.
201  */
202 static svn_error_t *
prepare_revprop_repo(svn_fs_t ** fs,const char * repo_name,int max_rev,int shard_size,const svn_test_opts_t * opts,apr_pool_t * pool)203 prepare_revprop_repo(svn_fs_t **fs,
204                      const char *repo_name,
205                      int max_rev,
206                      int shard_size,
207                      const svn_test_opts_t *opts,
208                      apr_pool_t *pool)
209 {
210   svn_fs_txn_t *txn;
211   svn_fs_root_t *txn_root;
212   const char *conflict;
213   svn_revnum_t after_rev;
214   apr_pool_t *subpool;
215 
216   /* Create the packed FS and open it. */
217   SVN_ERR(create_packed_filesystem(repo_name, opts, max_rev, shard_size, pool));
218   SVN_ERR(svn_fs_open2(fs, repo_name, NULL, pool, pool));
219 
220   subpool = svn_pool_create(pool);
221   /* Do a commit to trigger packing. */
222   SVN_ERR(svn_fs_begin_txn(&txn, *fs, max_rev, subpool));
223   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
224   SVN_ERR(svn_test__set_file_contents(txn_root, "iota", "new-iota",  subpool));
225   SVN_ERR(svn_fs_commit_txn(&conflict, &after_rev, txn, subpool));
226   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(after_rev));
227   svn_pool_destroy(subpool);
228 
229   /* Pack the repository. */
230   SVN_ERR(svn_fs_pack(repo_name, NULL, NULL, NULL, NULL, pool));
231 
232   return SVN_NO_ERROR;
233 }
234 
235 /* For revision REV, return a short log message allocated in POOL.
236  */
237 static svn_string_t *
default_log(svn_revnum_t rev,apr_pool_t * pool)238 default_log(svn_revnum_t rev, apr_pool_t *pool)
239 {
240   return svn_string_createf(pool, "Default message for rev %ld", rev);
241 }
242 
243 /* For revision REV, return a long log message allocated in POOL.
244  */
245 static svn_string_t *
large_log(svn_revnum_t rev,apr_size_t length,apr_pool_t * pool)246 large_log(svn_revnum_t rev, apr_size_t length, apr_pool_t *pool)
247 {
248   svn_stringbuf_t *temp = svn_stringbuf_create_ensure(100000, pool);
249   int i, count = (int)(length - 50) / 6;
250 
251   svn_stringbuf_appendcstr(temp, "A ");
252   for (i = 0; i < count; ++i)
253     svn_stringbuf_appendcstr(temp, "very, ");
254 
255   svn_stringbuf_appendcstr(temp,
256     apr_psprintf(pool, "very long message for rev %ld, indeed", rev));
257 
258   return svn_stringbuf__morph_into_string(temp);
259 }
260 
261 /* For revision REV, return a long log message allocated in POOL.
262  */
263 static svn_string_t *
huge_log(svn_revnum_t rev,apr_pool_t * pool)264 huge_log(svn_revnum_t rev, apr_pool_t *pool)
265 {
266   return large_log(rev, 90000, pool);
267 }
268 
269 
270 /*** Tests ***/
271 
272 /* ------------------------------------------------------------------------ */
273 #define REPO_NAME "test-repo-fsx-pack"
274 #define SHARD_SIZE 7
275 #define MAX_REV 53
276 static svn_error_t *
pack_filesystem(const svn_test_opts_t * opts,apr_pool_t * pool)277 pack_filesystem(const svn_test_opts_t *opts,
278                 apr_pool_t *pool)
279 {
280   int i;
281   svn_node_kind_t kind;
282   const char *path;
283   char buf[80];
284   apr_file_t *file;
285   apr_size_t len;
286 
287   SVN_ERR(create_packed_filesystem(REPO_NAME, opts, MAX_REV, SHARD_SIZE,
288                                    pool));
289 
290   /* Check to see that the pack files exist, and that the rev directories
291      don't. */
292   for (i = 0; i < (MAX_REV + 1) / SHARD_SIZE; i++)
293     {
294       path = svn_dirent_join_many(pool, REPO_NAME, "revs",
295                                   apr_psprintf(pool, "%d.pack", i / SHARD_SIZE),
296                                   "pack", SVN_VA_NULL);
297 
298       /* This file should exist. */
299       SVN_ERR(svn_io_check_path(path, &kind, pool));
300       if (kind != svn_node_file)
301         return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
302                                  "Expected pack file '%s' not found", path);
303 
304       /* This directory should not exist. */
305       path = svn_dirent_join_many(pool, REPO_NAME, "revs",
306                                   apr_psprintf(pool, "%d", i / SHARD_SIZE),
307                                   SVN_VA_NULL);
308       SVN_ERR(svn_io_check_path(path, &kind, pool));
309       if (kind != svn_node_none)
310         return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
311                                  "Unexpected directory '%s' found", path);
312     }
313 
314   /* Ensure the min-unpacked-rev jives with the above operations. */
315   SVN_ERR(svn_io_file_open(&file,
316                            svn_dirent_join(REPO_NAME, PATH_MIN_UNPACKED_REV,
317                                            pool),
318                            APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
319   len = sizeof(buf);
320   SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
321   SVN_ERR(svn_io_file_close(file, pool));
322   if (SVN_STR_TO_REV(buf) != (MAX_REV / SHARD_SIZE) * SHARD_SIZE)
323     return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
324                              "Bad '%s' contents", PATH_MIN_UNPACKED_REV);
325 
326   /* Finally, make sure the final revision directory does exist. */
327   path = svn_dirent_join_many(pool, REPO_NAME, "revs",
328                               apr_psprintf(pool, "%d", (i / SHARD_SIZE) + 1),
329                               SVN_VA_NULL);
330   SVN_ERR(svn_io_check_path(path, &kind, pool));
331   if (kind != svn_node_none)
332     return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
333                              "Expected directory '%s' not found", path);
334 
335 
336   return SVN_NO_ERROR;
337 }
338 #undef REPO_NAME
339 #undef SHARD_SIZE
340 #undef MAX_REV
341 
342 /* ------------------------------------------------------------------------ */
343 #define REPO_NAME "test-repo-fsx-pack-even"
344 #define SHARD_SIZE 4
345 #define MAX_REV 11
346 static svn_error_t *
pack_even_filesystem(const svn_test_opts_t * opts,apr_pool_t * pool)347 pack_even_filesystem(const svn_test_opts_t *opts,
348                      apr_pool_t *pool)
349 {
350   svn_node_kind_t kind;
351   const char *path;
352 
353   SVN_ERR(create_packed_filesystem(REPO_NAME, opts, MAX_REV, SHARD_SIZE,
354                                    pool));
355 
356   path = svn_dirent_join_many(pool, REPO_NAME, "revs", "2.pack", SVN_VA_NULL);
357   SVN_ERR(svn_io_check_path(path, &kind, pool));
358   if (kind != svn_node_dir)
359     return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
360                              "Packing did not complete as expected");
361 
362   return SVN_NO_ERROR;
363 }
364 #undef REPO_NAME
365 #undef SHARD_SIZE
366 #undef MAX_REV
367 
368 /* ------------------------------------------------------------------------ */
369 #define REPO_NAME "test-repo-read-packed-fs"
370 #define SHARD_SIZE 5
371 #define MAX_REV 11
372 static svn_error_t *
read_packed_fs(const svn_test_opts_t * opts,apr_pool_t * pool)373 read_packed_fs(const svn_test_opts_t *opts,
374                apr_pool_t *pool)
375 {
376   svn_fs_t *fs;
377   svn_stream_t *rstream;
378   svn_stringbuf_t *rstring;
379   svn_revnum_t i;
380 
381   SVN_ERR(create_packed_filesystem(REPO_NAME, opts, MAX_REV, SHARD_SIZE, pool));
382   SVN_ERR(svn_fs_open2(&fs, REPO_NAME, NULL, pool, pool));
383 
384   for (i = 1; i < (MAX_REV + 1); i++)
385     {
386       svn_fs_root_t *rev_root;
387       svn_stringbuf_t *sb;
388 
389       SVN_ERR(svn_fs_revision_root(&rev_root, fs, i, pool));
390       SVN_ERR(svn_fs_file_contents(&rstream, rev_root, "iota", pool));
391       SVN_ERR(svn_test__stream_to_string(&rstring, rstream, pool));
392 
393       if (i == 1)
394         sb = svn_stringbuf_create("This is the file 'iota'.\n", pool);
395       else
396         sb = svn_stringbuf_create(get_rev_contents(i, pool), pool);
397 
398       if (! svn_stringbuf_compare(rstring, sb))
399         return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
400                                  "Bad data in revision %ld.", i);
401     }
402 
403   return SVN_NO_ERROR;
404 }
405 #undef REPO_NAME
406 #undef SHARD_SIZE
407 #undef MAX_REV
408 
409 /* ------------------------------------------------------------------------ */
410 #define REPO_NAME "test-repo-commit-packed-fs"
411 #define SHARD_SIZE 5
412 #define MAX_REV 10
413 static svn_error_t *
commit_packed_fs(const svn_test_opts_t * opts,apr_pool_t * pool)414 commit_packed_fs(const svn_test_opts_t *opts,
415                  apr_pool_t *pool)
416 {
417   svn_fs_t *fs;
418   svn_fs_txn_t *txn;
419   svn_fs_root_t *txn_root;
420   const char *conflict;
421   svn_revnum_t after_rev;
422 
423   /* Create the packed FS and open it. */
424   SVN_ERR(create_packed_filesystem(REPO_NAME, opts, MAX_REV, 5, pool));
425   SVN_ERR(svn_fs_open2(&fs, REPO_NAME, NULL, pool, pool));
426 
427   /* Now do a commit. */
428   SVN_ERR(svn_fs_begin_txn(&txn, fs, MAX_REV, pool));
429   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
430   SVN_ERR(svn_test__set_file_contents(txn_root, "iota",
431           "How much better is it to get wisdom than gold! and to get "
432           "understanding rather to be chosen than silver!", pool));
433   SVN_ERR(svn_fs_commit_txn(&conflict, &after_rev, txn, pool));
434   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(after_rev));
435 
436   return SVN_NO_ERROR;
437 }
438 #undef REPO_NAME
439 #undef MAX_REV
440 #undef SHARD_SIZE
441 
442 /* ------------------------------------------------------------------------ */
443 #define REPO_NAME "test-repo-get-set-revprop-packed-fs"
444 #define SHARD_SIZE 4
445 #define MAX_REV 10
446 static svn_error_t *
get_set_revprop_packed_fs(const svn_test_opts_t * opts,apr_pool_t * pool)447 get_set_revprop_packed_fs(const svn_test_opts_t *opts,
448                           apr_pool_t *pool)
449 {
450   svn_fs_t *fs;
451   svn_string_t *prop_value;
452 
453   /* Create the packed FS and open it. */
454   SVN_ERR(prepare_revprop_repo(&fs, REPO_NAME, MAX_REV, SHARD_SIZE, opts,
455                                pool));
456 
457   /* Try to get revprop for revision 0
458    * (non-packed due to special handling). */
459   SVN_ERR(svn_fs_revision_prop(&prop_value, fs, 0, SVN_PROP_REVISION_AUTHOR,
460                                pool));
461 
462   /* Try to change revprop for revision 0
463    * (non-packed due to special handling). */
464   SVN_ERR(svn_fs_change_rev_prop(fs, 0, SVN_PROP_REVISION_AUTHOR,
465                                  svn_string_create("tweaked-author", pool),
466                                  pool));
467 
468   /* verify */
469   SVN_ERR(svn_fs_revision_prop(&prop_value, fs, 0, SVN_PROP_REVISION_AUTHOR,
470                                pool));
471   SVN_TEST_STRING_ASSERT(prop_value->data, "tweaked-author");
472 
473   /* Try to get packed revprop for revision 5. */
474   SVN_ERR(svn_fs_revision_prop(&prop_value, fs, 5, SVN_PROP_REVISION_AUTHOR,
475                                pool));
476 
477   /* Try to change packed revprop for revision 5. */
478   SVN_ERR(svn_fs_change_rev_prop(fs, 5, SVN_PROP_REVISION_AUTHOR,
479                                  svn_string_create("tweaked-author2", pool),
480                                  pool));
481 
482   /* verify */
483   SVN_ERR(svn_fs_revision_prop(&prop_value, fs, 5, SVN_PROP_REVISION_AUTHOR,
484                                pool));
485   SVN_TEST_STRING_ASSERT(prop_value->data, "tweaked-author2");
486 
487   return SVN_NO_ERROR;
488 }
489 #undef REPO_NAME
490 #undef MAX_REV
491 #undef SHARD_SIZE
492 
493 /* ------------------------------------------------------------------------ */
494 #define REPO_NAME "test-repo-get-set-large-revprop-packed-fs"
495 #define SHARD_SIZE 4
496 #define MAX_REV 11
497 static svn_error_t *
get_set_large_revprop_packed_fs(const svn_test_opts_t * opts,apr_pool_t * pool)498 get_set_large_revprop_packed_fs(const svn_test_opts_t *opts,
499                                 apr_pool_t *pool)
500 {
501   svn_fs_t *fs;
502   svn_string_t *prop_value;
503   svn_revnum_t rev;
504 
505   /* Create the packed FS and open it. */
506   SVN_ERR(prepare_revprop_repo(&fs, REPO_NAME, MAX_REV, SHARD_SIZE, opts,
507                                pool));
508 
509   /* Set commit messages to different, large values that fill the pack
510    * files but do not exceed the pack size limit. */
511   for (rev = 0; rev <= MAX_REV; ++rev)
512     SVN_ERR(svn_fs_change_rev_prop(fs, rev, SVN_PROP_REVISION_LOG,
513                                    large_log(rev, 15000, pool),
514                                    pool));
515 
516   /* verify */
517   for (rev = 0; rev <= MAX_REV; ++rev)
518     {
519       SVN_ERR(svn_fs_revision_prop(&prop_value, fs, rev,
520                                    SVN_PROP_REVISION_LOG, pool));
521       SVN_TEST_STRING_ASSERT(prop_value->data,
522                              large_log(rev, 15000, pool)->data);
523     }
524 
525   /* Put a larger revprop into the last, some middle and the first revision
526    * of a pack.  This should cause the packs to split in the middle. */
527   SVN_ERR(svn_fs_change_rev_prop(fs, 3, SVN_PROP_REVISION_LOG,
528                                  /* rev 0 is not packed */
529                                  large_log(3, 37000, pool),
530                                  pool));
531   SVN_ERR(svn_fs_change_rev_prop(fs, 5, SVN_PROP_REVISION_LOG,
532                                  large_log(5, 25000, pool),
533                                  pool));
534   SVN_ERR(svn_fs_change_rev_prop(fs, 8, SVN_PROP_REVISION_LOG,
535                                  large_log(8, 25000, pool),
536                                  pool));
537 
538   /* verify */
539   for (rev = 0; rev <= MAX_REV; ++rev)
540     {
541       SVN_ERR(svn_fs_revision_prop(&prop_value, fs, rev,
542                                    SVN_PROP_REVISION_LOG, pool));
543 
544       if (rev == 3)
545         SVN_TEST_STRING_ASSERT(prop_value->data,
546                                large_log(rev, 37000, pool)->data);
547       else if (rev == 5 || rev == 8)
548         SVN_TEST_STRING_ASSERT(prop_value->data,
549                                large_log(rev, 25000, pool)->data);
550       else
551         SVN_TEST_STRING_ASSERT(prop_value->data,
552                                large_log(rev, 15000, pool)->data);
553     }
554 
555   return SVN_NO_ERROR;
556 }
557 #undef REPO_NAME
558 #undef MAX_REV
559 #undef SHARD_SIZE
560 
561 /* ------------------------------------------------------------------------ */
562 #define REPO_NAME "test-repo-get-set-huge-revprop-packed-fs"
563 #define SHARD_SIZE 4
564 #define MAX_REV 10
565 static svn_error_t *
get_set_huge_revprop_packed_fs(const svn_test_opts_t * opts,apr_pool_t * pool)566 get_set_huge_revprop_packed_fs(const svn_test_opts_t *opts,
567                                apr_pool_t *pool)
568 {
569   svn_fs_t *fs;
570   svn_string_t *prop_value;
571   svn_revnum_t rev;
572 
573   /* Create the packed FS and open it. */
574   SVN_ERR(prepare_revprop_repo(&fs, REPO_NAME, MAX_REV, SHARD_SIZE, opts,
575                                pool));
576 
577   /* Set commit messages to different values */
578   for (rev = 0; rev <= MAX_REV; ++rev)
579     SVN_ERR(svn_fs_change_rev_prop(fs, rev, SVN_PROP_REVISION_LOG,
580                                    default_log(rev, pool),
581                                    pool));
582 
583   /* verify */
584   for (rev = 0; rev <= MAX_REV; ++rev)
585     {
586       SVN_ERR(svn_fs_revision_prop(&prop_value, fs, rev,
587                                    SVN_PROP_REVISION_LOG, pool));
588       SVN_TEST_STRING_ASSERT(prop_value->data, default_log(rev, pool)->data);
589     }
590 
591   /* Put a huge revprop into the last, some middle and the first revision
592    * of a pack.  They will cause the pack files to split accordingly. */
593   SVN_ERR(svn_fs_change_rev_prop(fs, 3, SVN_PROP_REVISION_LOG,
594                                  huge_log(3, pool),
595                                  pool));
596   SVN_ERR(svn_fs_change_rev_prop(fs, 5, SVN_PROP_REVISION_LOG,
597                                  huge_log(5, pool),
598                                  pool));
599   SVN_ERR(svn_fs_change_rev_prop(fs, 8, SVN_PROP_REVISION_LOG,
600                                  huge_log(8, pool),
601                                  pool));
602 
603   /* verify */
604   for (rev = 0; rev <= MAX_REV; ++rev)
605     {
606       SVN_ERR(svn_fs_revision_prop(&prop_value, fs, rev,
607                                    SVN_PROP_REVISION_LOG, pool));
608 
609       if (rev == 3 || rev == 5 || rev == 8)
610         SVN_TEST_STRING_ASSERT(prop_value->data,
611                                huge_log(rev, pool)->data);
612       else
613         SVN_TEST_STRING_ASSERT(prop_value->data,
614                                default_log(rev, pool)->data);
615     }
616 
617   return SVN_NO_ERROR;
618 }
619 #undef REPO_NAME
620 #undef MAX_REV
621 #undef SHARD_SIZE
622 
623 /* ------------------------------------------------------------------------ */
624 /* Regression test for issue #3571 (fsfs 'svnadmin recover' expects
625    youngest revprop to be outside revprops.db). */
626 #define REPO_NAME "test-repo-recover-fully-packed"
627 #define SHARD_SIZE 4
628 #define MAX_REV 7
629 static svn_error_t *
recover_fully_packed(const svn_test_opts_t * opts,apr_pool_t * pool)630 recover_fully_packed(const svn_test_opts_t *opts,
631                      apr_pool_t *pool)
632 {
633   apr_pool_t *subpool;
634   svn_fs_t *fs;
635   svn_fs_txn_t *txn;
636   svn_fs_root_t *txn_root;
637   const char *conflict;
638   svn_revnum_t after_rev;
639   svn_error_t *err;
640 
641   /* Create a packed FS for which every revision will live in a pack
642      digest file, and then recover it. */
643   SVN_ERR(create_packed_filesystem(REPO_NAME, opts, MAX_REV, SHARD_SIZE, pool));
644   SVN_ERR(svn_fs_recover(REPO_NAME, NULL, NULL, pool));
645 
646   /* Add another revision, re-pack, re-recover. */
647   subpool = svn_pool_create(pool);
648   SVN_ERR(svn_fs_open2(&fs, REPO_NAME, NULL, subpool, subpool));
649   SVN_ERR(svn_fs_begin_txn(&txn, fs, MAX_REV, subpool));
650   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
651   SVN_ERR(svn_test__set_file_contents(txn_root, "A/mu", "new-mu", subpool));
652   SVN_ERR(svn_fs_commit_txn(&conflict, &after_rev, txn, subpool));
653   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(after_rev));
654   svn_pool_destroy(subpool);
655   SVN_ERR(svn_fs_pack(REPO_NAME, NULL, NULL, NULL, NULL, pool));
656   SVN_ERR(svn_fs_recover(REPO_NAME, NULL, NULL, pool));
657 
658   /* Now, delete the youngest revprop file, and recover again.  This
659      time we want to see an error! */
660   SVN_ERR(svn_io_remove_file2(
661               svn_dirent_join_many(pool, REPO_NAME, PATH_REVS_DIR,
662                                    apr_psprintf(pool, "%ld/p%ld",
663                                                 after_rev / SHARD_SIZE,
664                                                 after_rev),
665                                    SVN_VA_NULL),
666               FALSE, pool));
667   err = svn_fs_recover(REPO_NAME, NULL, NULL, pool);
668   if (! err)
669     return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
670                             "Expected SVN_ERR_FS_CORRUPT error; got none");
671   if (err->apr_err != SVN_ERR_FS_CORRUPT)
672     return svn_error_create(SVN_ERR_TEST_FAILED, err,
673                             "Expected SVN_ERR_FS_CORRUPT error; got:");
674   svn_error_clear(err);
675   return SVN_NO_ERROR;
676 }
677 #undef REPO_NAME
678 #undef MAX_REV
679 #undef SHARD_SIZE
680 
681 /* ------------------------------------------------------------------------ */
682 /* Regression test for issue #4320 (fsfs file-hinting fails when reading a rep
683    from the transaction that is commiting rev = SHARD_SIZE). */
684 #define REPO_NAME "test-repo-file-hint-at-shard-boundary"
685 #define SHARD_SIZE 4
686 #define MAX_REV (SHARD_SIZE - 1)
687 static svn_error_t *
file_hint_at_shard_boundary(const svn_test_opts_t * opts,apr_pool_t * pool)688 file_hint_at_shard_boundary(const svn_test_opts_t *opts,
689                             apr_pool_t *pool)
690 {
691   apr_pool_t *subpool;
692   svn_fs_t *fs;
693   svn_fs_txn_t *txn;
694   svn_fs_root_t *txn_root;
695   const char *file_contents;
696   svn_stringbuf_t *retrieved_contents;
697   svn_error_t *err = SVN_NO_ERROR;
698 
699   /* Create a packed FS and MAX_REV revisions */
700   SVN_ERR(create_packed_filesystem(REPO_NAME, opts, MAX_REV, SHARD_SIZE, pool));
701 
702   /* Reopen the filesystem */
703   subpool = svn_pool_create(pool);
704   SVN_ERR(svn_fs_open2(&fs, REPO_NAME, NULL, subpool, subpool));
705 
706   /* Revision = SHARD_SIZE */
707   file_contents = get_rev_contents(SHARD_SIZE, subpool);
708   SVN_ERR(svn_fs_begin_txn(&txn, fs, MAX_REV, subpool));
709   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
710   SVN_ERR(svn_test__set_file_contents(txn_root, "iota", file_contents,
711                                       subpool));
712 
713   /* Retrieve the file. */
714   SVN_ERR(svn_test__get_file_contents(txn_root, "iota", &retrieved_contents,
715                                       subpool));
716   if (strcmp(retrieved_contents->data, file_contents))
717     {
718       err = svn_error_create(SVN_ERR_TEST_FAILED, err,
719                               "Retrieved incorrect contents from iota.");
720     }
721 
722   /* Close the repo. */
723   svn_pool_destroy(subpool);
724 
725   return err;
726 }
727 #undef REPO_NAME
728 #undef MAX_REV
729 #undef SHARD_SIZE
730 
731 /* ------------------------------------------------------------------------ */
732 #define REPO_NAME "test-repo-fsx-info"
733 #define SHARD_SIZE 3
734 #define MAX_REV 5
735 static svn_error_t *
test_info(const svn_test_opts_t * opts,apr_pool_t * pool)736 test_info(const svn_test_opts_t *opts,
737           apr_pool_t *pool)
738 {
739   svn_fs_t *fs;
740   const svn_fs_fsx_info_t *fsx_info;
741   const svn_fs_info_placeholder_t *info;
742 
743   SVN_ERR(create_packed_filesystem(REPO_NAME, opts, MAX_REV, SHARD_SIZE,
744                                    pool));
745 
746   SVN_ERR(svn_fs_open2(&fs, REPO_NAME, NULL, pool, pool));
747   SVN_ERR(svn_fs_info(&info, fs, pool, pool));
748   info = svn_fs_info_dup(info, pool, pool);
749 
750   SVN_TEST_STRING_ASSERT(opts->fs_type, info->fs_type);
751 
752   /* Bail (with success) on known-untestable scenarios */
753   if (strcmp(opts->fs_type, "fsx") != 0)
754     return SVN_NO_ERROR;
755 
756   fsx_info = (const void *)info;
757   SVN_TEST_ASSERT(fsx_info->shard_size == SHARD_SIZE);
758   SVN_TEST_ASSERT(fsx_info->min_unpacked_rev
759                   == (MAX_REV + 1) / SHARD_SIZE * SHARD_SIZE);
760 
761   return SVN_NO_ERROR;
762 }
763 #undef REPO_NAME
764 #undef SHARD_SIZE
765 #undef MAX_REV
766 
767 /* ------------------------------------------------------------------------ */
768 #define REPO_NAME "test-repo-fsx-rev-container"
769 #define SHARD_SIZE 3
770 #define MAX_REV 5
771 static svn_error_t *
test_reps(const svn_test_opts_t * opts,apr_pool_t * pool)772 test_reps(const svn_test_opts_t *opts,
773           apr_pool_t *pool)
774 {
775   svn_fs_t *fs = NULL;
776   svn_fs_x__reps_builder_t *builder;
777   svn_fs_x__reps_t *container;
778   svn_stringbuf_t *serialized;
779   svn_stream_t *stream;
780   svn_stringbuf_t *contents = svn_stringbuf_create_ensure(10000, pool);
781   int i;
782 
783   for (i = 0; i < 10000; ++i)
784     {
785       int v, s = 0;
786       for (v = i; v > 0; v /= 10)
787         s += v % 10;
788 
789       svn_stringbuf_appendbyte(contents, (char)(s + ' '));
790     }
791 
792   SVN_ERR(create_packed_filesystem(REPO_NAME, opts, MAX_REV, SHARD_SIZE,
793                                    pool));
794 
795   SVN_ERR(svn_fs_open2(&fs, REPO_NAME, NULL, pool, pool));
796 
797   builder = svn_fs_x__reps_builder_create(fs, pool);
798   for (i = 10000; i > 10; --i)
799     {
800       apr_size_t idx;
801       svn_string_t string;
802       string.data = contents->data;
803       string.len = i;
804 
805       SVN_ERR(svn_fs_x__reps_add(&idx, builder, &string));
806     }
807 
808   serialized = svn_stringbuf_create_empty(pool);
809   stream = svn_stream_from_stringbuf(serialized, pool);
810   SVN_ERR(svn_fs_x__write_reps_container(stream, builder, pool));
811 
812   SVN_ERR(svn_stream_reset(stream));
813   SVN_ERR(svn_fs_x__read_reps_container(&container, stream, pool, pool));
814   SVN_ERR(svn_stream_close(stream));
815 
816   return SVN_NO_ERROR;
817 }
818 
819 #undef REPO_NAME
820 #undef SHARD_SIZE
821 #undef MAX_REV
822 
823 /* ------------------------------------------------------------------------ */
824 #define REPO_NAME "test-repo-fsx-pack-shard-size-one"
825 #define SHARD_SIZE 1
826 #define MAX_REV 4
827 static svn_error_t *
pack_shard_size_one(const svn_test_opts_t * opts,apr_pool_t * pool)828 pack_shard_size_one(const svn_test_opts_t *opts,
829                      apr_pool_t *pool)
830 {
831   svn_string_t *propval;
832   svn_fs_t *fs;
833 
834   SVN_ERR(create_packed_filesystem(REPO_NAME, opts, MAX_REV, SHARD_SIZE,
835                                    pool));
836   SVN_ERR(svn_fs_open2(&fs, REPO_NAME, NULL, pool, pool));
837   /* whitebox: revprop packing special-cases r0, which causes
838      (start_rev==1, end_rev==0) in pack_revprops_shard().  So test that. */
839   SVN_ERR(svn_fs_revision_prop(&propval, fs, 1, SVN_PROP_REVISION_LOG, pool));
840   SVN_TEST_STRING_ASSERT(propval->data, R1_LOG_MSG);
841 
842   return SVN_NO_ERROR;
843 }
844 #undef REPO_NAME
845 #undef SHARD_SIZE
846 #undef MAX_REV
847 /* ------------------------------------------------------------------------ */
848 #define REPO_NAME "test-repo-fsx-batch-fsync"
849 static svn_error_t *
test_batch_fsync(const svn_test_opts_t * opts,apr_pool_t * pool)850 test_batch_fsync(const svn_test_opts_t *opts,
851                  apr_pool_t *pool)
852 {
853   const char *abspath;
854   svn_fs_x__batch_fsync_t *batch;
855   int i;
856 
857   /* Disable this test for non FSX backends because it has no relevance to
858    * them. */
859   if (strcmp(opts->fs_type, "fsx") != 0)
860       return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL,
861       "this will test FSX repositories only");
862 
863   /* Create an empty working directory and let it be cleaned up by the test
864    * harness. */
865   SVN_ERR(svn_dirent_get_absolute(&abspath, REPO_NAME, pool));
866 
867   SVN_ERR(svn_io_remove_dir2(abspath, TRUE, NULL, NULL, pool));
868   SVN_ERR(svn_io_make_dir_recursively(abspath, pool));
869   svn_test_add_dir_cleanup(abspath);
870 
871   /* Initialize infrastructure with a pool that lives as long as this
872    * application. */
873   SVN_ERR(svn_fs_x__batch_fsync_init(pool));
874 
875   /* We use and re-use the same batch object throughout this test. */
876   SVN_ERR(svn_fs_x__batch_fsync_create(&batch, TRUE, pool));
877 
878   /* The working directory is new. */
879   SVN_ERR(svn_fs_x__batch_fsync_new_path(batch, abspath, pool));
880 
881   /* 1st run: Has to fire up worker threads etc. */
882   for (i = 0; i < 10; ++i)
883     {
884       apr_file_t *file;
885       const char *path = svn_dirent_join(abspath,
886                                          apr_psprintf(pool, "file%i", i),
887                                          pool);
888       apr_size_t len = strlen(path);
889 
890       SVN_ERR(svn_fs_x__batch_fsync_open_file(&file, batch, path, pool));
891 
892       SVN_ERR(svn_io_file_write(file, path, &len, pool));
893     }
894 
895   SVN_ERR(svn_fs_x__batch_fsync_run(batch, pool));
896 
897   /* 2nd run: Running a batch must leave the container in an empty,
898    * re-usable state. Hence, try to re-use it. */
899   for (i = 0; i < 10; ++i)
900     {
901       apr_file_t *file;
902       const char *path = svn_dirent_join(abspath,
903                                          apr_psprintf(pool, "new%i", i),
904                                          pool);
905       apr_size_t len = strlen(path);
906 
907       SVN_ERR(svn_fs_x__batch_fsync_open_file(&file, batch, path, pool));
908 
909       SVN_ERR(svn_io_file_write(file, path, &len, pool));
910     }
911 
912   SVN_ERR(svn_fs_x__batch_fsync_run(batch, pool));
913 
914   /* 3rd run: Schedule but don't execute. POOL cleanup shall not fail. */
915   for (i = 0; i < 10; ++i)
916     {
917       apr_file_t *file;
918       const char *path = svn_dirent_join(abspath,
919                                          apr_psprintf(pool, "another%i", i),
920                                          pool);
921       apr_size_t len = strlen(path);
922 
923       SVN_ERR(svn_fs_x__batch_fsync_open_file(&file, batch, path, pool));
924 
925       SVN_ERR(svn_io_file_write(file, path, &len, pool));
926     }
927 
928   return SVN_NO_ERROR;
929 }
930 #undef REPO_NAME
931 /* ------------------------------------------------------------------------ */
932 
933 /* The test table.  */
934 
935 static int max_threads = 4;
936 
937 static struct svn_test_descriptor_t test_funcs[] =
938   {
939     SVN_TEST_NULL,
940     SVN_TEST_OPTS_PASS(pack_filesystem,
941                        "pack a FSX filesystem"),
942     SVN_TEST_OPTS_PASS(pack_even_filesystem,
943                        "pack FSX where revs % shard = 0"),
944     SVN_TEST_OPTS_PASS(read_packed_fs,
945                        "read from a packed FSX filesystem"),
946     SVN_TEST_OPTS_PASS(commit_packed_fs,
947                        "commit to a packed FSX filesystem"),
948     SVN_TEST_OPTS_PASS(get_set_revprop_packed_fs,
949                        "get/set revprop while packing FSX filesystem"),
950     SVN_TEST_OPTS_PASS(get_set_large_revprop_packed_fs,
951                        "get/set large packed revprops in FSX"),
952     SVN_TEST_OPTS_PASS(get_set_huge_revprop_packed_fs,
953                        "get/set huge packed revprops in FSX"),
954     SVN_TEST_OPTS_PASS(recover_fully_packed,
955                        "recover a fully packed filesystem"),
956     SVN_TEST_OPTS_PASS(file_hint_at_shard_boundary,
957                        "test file hint at shard boundary"),
958     SVN_TEST_OPTS_PASS(test_info,
959                        "test svn_fs_info"),
960     SVN_TEST_OPTS_PASS(test_reps,
961                        "test representations container"),
962     SVN_TEST_OPTS_PASS(pack_shard_size_one,
963                        "test packing with shard size = 1"),
964     SVN_TEST_OPTS_PASS(test_batch_fsync,
965                        "test batch fsync"),
966     SVN_TEST_NULL
967   };
968 
969 SVN_TEST_MAIN
970