1 /*
2  * wc-test.c :  test WC APIs
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23 
24 #include <apr_pools.h>
25 #include <apr_general.h>
26 #include <apr_md5.h>
27 
28 #define SVN_DEPRECATED
29 
30 #include "svn_types.h"
31 #include "svn_io.h"
32 #include "svn_dirent_uri.h"
33 #include "svn_pools.h"
34 #include "svn_repos.h"
35 #include "svn_wc.h"
36 #include "svn_client.h"
37 #include "svn_hash.h"
38 
39 #include "utils.h"
40 
41 #include "private/svn_wc_private.h"
42 #include "private/svn_sqlite.h"
43 #include "private/svn_dep_compat.h"
44 #include "../../libsvn_wc/wc.h"
45 #include "../../libsvn_wc/wc_db.h"
46 #define SVN_WC__I_AM_WC_DB
47 #include "../../libsvn_wc/wc_db_private.h"
48 
49 #include "../svn_test.h"
50 
51 #ifdef _MSC_VER
52 #pragma warning(disable: 4221) /* nonstandard extension used */
53 #endif
54 
55 
56 /* ---------------------------------------------------------------------- */
57 /* The test functions */
58 
59 /* Structure for testing node_get_base and node_get_origin. */
60 struct base_origin_t
61 {
62   /* Path to create and test, WC-relative */
63   const char *path;
64   /* Expected base rev.  "-1" means no base.  (Expected base path
65    *   == base_rev valid ? path : NULL) */
66   svn_revnum_t base_rev;
67   /* Path to copy from, WC-relative */
68   const char *src_path;
69   /* Expected "origin" */
70   struct {
71       const char *path;
72       svn_revnum_t rev;
73   } origin;
74 };
75 
76 /* Data for testing node_get_base and node_get_origin. */
77 static struct base_origin_t base_origin_subtests[] =
78   {
79     /* file copied onto nothing */
80     { "A/C/copy1",  -1, "iota",   {"iota", 1} },
81 
82     /* dir copied onto nothing */
83     { "A/C/copy2",  -1, "A/B/E",  {"A/B/E", 1} },
84 
85     /* replacement: file copied over a schedule-delete file */
86     { "A/B/lambda", 1,  "iota",   {"iota", 1} },
87 
88     /* replacement: dir copied over a schedule-delete dir */
89     { "A/D/G",      1,  "A/B/E",  {"A/B/E", 1} },
90 
91     /* replacement: dir copied over a schedule-delete file */
92     { "A/D/gamma",  1,  "A/B/E",  {"A/B/E", 1} },
93 
94     /* replacement: file copied over a schedule-delete dir */
95     { "A/D/H",      1,  "iota",   {"iota", 1} },
96 
97     { 0 }
98   };
99 
100 /* Create a WC containing lots of different node states, in the sandbox B. */
101 static svn_error_t *
create_wc_for_base_and_origin_tests(svn_test__sandbox_t * b)102 create_wc_for_base_and_origin_tests(svn_test__sandbox_t *b)
103 {
104   struct base_origin_t *copy;
105 
106   SVN_ERR(sbox_add_and_commit_greek_tree(b));
107 
108   /* Copy various things */
109   for (copy = base_origin_subtests; copy->src_path; copy++)
110     {
111       if (SVN_IS_VALID_REVNUM(copy->base_rev))
112         SVN_ERR(sbox_wc_delete(b, copy->path));
113       SVN_ERR(sbox_wc_copy(b, copy->src_path, copy->path));
114     }
115 
116   return SVN_NO_ERROR;
117 }
118 
119 /* Test svn_wc__node_get_base(). */
120 static svn_error_t *
test_node_get_base(const svn_test_opts_t * opts,apr_pool_t * pool)121 test_node_get_base(const svn_test_opts_t *opts, apr_pool_t *pool)
122 {
123   svn_test__sandbox_t *b = apr_palloc(pool, sizeof(*b));
124 
125   SVN_ERR(svn_test__sandbox_create(b, "node_get_base", opts, pool));
126 
127   SVN_ERR(create_wc_for_base_and_origin_tests(b));
128 
129   {
130     struct base_origin_t *subtest;
131 
132     for (subtest = base_origin_subtests; subtest->path; subtest++)
133       {
134         const char *local_abspath
135           = svn_dirent_join(b->wc_abspath, subtest->path, b->pool);
136         svn_revnum_t revision;
137         const char *repos_relpath, *repos_root_url, *repos_uuid;
138 
139         SVN_ERR(svn_wc__node_get_base(NULL, &revision, &repos_relpath,
140                                       &repos_root_url, &repos_uuid,
141                                       NULL,
142                                       b->wc_ctx, local_abspath,
143                                       TRUE /* ignore_enoent */,
144                                       b->pool, b->pool));
145         SVN_TEST_ASSERT(revision == subtest->base_rev);
146         if (SVN_IS_VALID_REVNUM(subtest->base_rev))
147           {
148             SVN_TEST_STRING_ASSERT(repos_relpath, subtest->path);
149             SVN_TEST_STRING_ASSERT(repos_root_url, b->repos_url);
150             SVN_TEST_ASSERT(repos_uuid != NULL);
151           }
152         else
153           {
154             SVN_TEST_STRING_ASSERT(repos_relpath, NULL);
155             SVN_TEST_STRING_ASSERT(repos_root_url, NULL);
156             SVN_TEST_STRING_ASSERT(repos_uuid, NULL);
157           }
158       }
159   }
160 
161   return SVN_NO_ERROR;
162 }
163 
164 /* Test svn_wc__node_get_origin(). */
165 static svn_error_t *
test_node_get_origin(const svn_test_opts_t * opts,apr_pool_t * pool)166 test_node_get_origin(const svn_test_opts_t *opts, apr_pool_t *pool)
167 {
168   svn_test__sandbox_t *b = apr_palloc(pool, sizeof(*b));
169 
170   SVN_ERR(svn_test__sandbox_create(b, "node_get_origin", opts, pool));
171 
172   SVN_ERR(create_wc_for_base_and_origin_tests(b));
173 
174   {
175     struct base_origin_t *subtest;
176 
177     for (subtest = base_origin_subtests; subtest->path; subtest++)
178       {
179         const char *local_abspath
180           = svn_dirent_join(b->wc_abspath, subtest->path, b->pool);
181         svn_revnum_t revision;
182         const char *repos_relpath, *repos_root_url, *repos_uuid;
183 
184         SVN_ERR(svn_wc__node_get_origin(NULL, &revision, &repos_relpath,
185                                         &repos_root_url, &repos_uuid, NULL,
186                                         NULL,
187                                         b->wc_ctx, local_abspath, FALSE,
188                                         b->pool, b->pool));
189         SVN_TEST_ASSERT(revision == subtest->origin.rev);
190         if (SVN_IS_VALID_REVNUM(subtest->origin.rev))
191           {
192             SVN_TEST_STRING_ASSERT(repos_relpath, subtest->origin.path);
193             SVN_TEST_STRING_ASSERT(repos_root_url, b->repos_url);
194             SVN_TEST_ASSERT(repos_uuid != NULL);
195           }
196         else
197           {
198             SVN_TEST_STRING_ASSERT(repos_relpath, NULL);
199             SVN_TEST_STRING_ASSERT(repos_root_url, NULL);
200             SVN_TEST_STRING_ASSERT(repos_uuid, NULL);
201           }
202       }
203   }
204 
205   return SVN_NO_ERROR;
206 }
207 
208 static svn_error_t *
test_externals_parse(const svn_test_opts_t * opts,apr_pool_t * pool)209 test_externals_parse(const svn_test_opts_t *opts, apr_pool_t *pool)
210 {
211   int i;
212   struct external_info
213     {
214       const char *line;
215       const char *url;
216       const char *local_path;
217       svn_revnum_t peg_rev;
218       svn_revnum_t rev;
219 
220     } items[] = {
221         {
222             "dir http://server/svn/a",
223             "http://server/svn/a",
224             "dir"
225         },
226         {
227             "/svn/home dir",
228             "u://svr/svn/home",
229             "dir"
230         },
231         {
232             "//server/home dir",
233             "u://server/home",
234             "dir"
235         },
236         {
237             "../../../../home dir",
238             "u://svr/svn/home",
239             "dir",
240         },
241         {
242             "^/../repB/tools/scripts scripts",
243             "u://svr/svn/cur/repB/tools/scripts",
244             "scripts"
245         },
246         {
247             "^/../repB/tools/README.txt scripts/README.txt",
248             "u://svr/svn/cur/repB/tools/README.txt",
249             "scripts/README.txt"
250         },
251     };
252 
253   for (i = 0; i < sizeof(items) / sizeof(items[0]); i++)
254     {
255       apr_array_header_t *results;
256       svn_wc_external_item2_t *external_item;
257       const char *resolved_url;
258       SVN_ERR(svn_wc_parse_externals_description3(&results, "/my/current/dir",
259                                                   items[i].line, FALSE, pool));
260 
261       SVN_TEST_ASSERT(results && results->nelts == 1);
262 
263       external_item = APR_ARRAY_IDX(results, 0, svn_wc_external_item2_t *);
264 
265       SVN_ERR(svn_wc__resolve_relative_external_url(&resolved_url,
266                                                     external_item,
267                                                     "u://svr/svn/cur/dir",
268                                                     "u://svr/svn/cur/dir/sd/fl",
269                                                     pool, pool));
270 
271       SVN_TEST_STRING_ASSERT(resolved_url, items[i].url);
272       SVN_TEST_STRING_ASSERT(external_item->target_dir, items[i].local_path);
273 
274       if (items[i].peg_rev != 0)
275         SVN_TEST_ASSERT(external_item->peg_revision.value.number
276                                 == items[i].peg_rev);
277       if (items[i].rev != 0)
278         SVN_TEST_ASSERT(external_item->revision.value.number == items[i].rev);
279       SVN_TEST_ASSERT(svn_uri_is_canonical(resolved_url, pool));
280     }
281 
282 
283   return SVN_NO_ERROR;
284 
285 }
286 
287 static svn_error_t *
test_externals_parse_erratic(apr_pool_t * pool)288 test_externals_parse_erratic(apr_pool_t *pool)
289 {
290   svn_error_t *err;
291   apr_array_header_t *list = NULL;
292 
293   err = svn_wc_parse_externals_description3(
294           &list, "parent_dir",
295           "^/valid/but/should/not/be/on/record wc_target\n"
296            "because_this_is_an_error",
297           FALSE, pool);
298 
299   /* DESC above has an error, so expect one. */
300   SVN_TEST_ASSERT(err != NULL);
301   svn_error_clear(err);
302 
303   /* svn_wc_parse_externals_description3() should not
304      touch LIST when DESC had an error.*/
305   SVN_TEST_ASSERT(list == NULL);
306 
307   return SVN_NO_ERROR;
308 }
309 
310 static svn_error_t *
test_legacy_commit1(const svn_test_opts_t * opts,apr_pool_t * pool)311 test_legacy_commit1(const svn_test_opts_t *opts, apr_pool_t *pool)
312 {
313   svn_test__sandbox_t b;
314   svn_wc_adm_access_t *adm_access;
315   const char *lambda;
316 
317   SVN_ERR(svn_test__sandbox_create(&b, "legacy_commit1", opts, pool));
318   SVN_ERR(sbox_add_and_commit_greek_tree(&b));
319 
320   SVN_ERR(sbox_wc_copy(&b, "A", "A_copied"));
321 
322   lambda = sbox_wc_path(&b, "A_copied/B/lambda");
323 
324 
325   SVN_ERR(svn_io_remove_file2(lambda, FALSE, pool));
326   SVN_ERR(svn_io_copy_file(sbox_wc_path(&b, "iota"), lambda, FALSE, pool));
327   SVN_ERR(svn_wc_adm_open3(&adm_access, NULL, b.wc_abspath, TRUE, -1,
328                            NULL, NULL, pool));
329 
330   {
331     svn_wc_status2_t *status;
332 
333     SVN_ERR(svn_wc_status2(&status, lambda, adm_access, pool));
334 
335     SVN_TEST_ASSERT(status != NULL);
336     SVN_TEST_ASSERT(status->text_status == svn_wc_status_modified);
337     SVN_TEST_ASSERT(status->copied == TRUE);
338   }
339 
340   /* Simulate a very old style svn ci . -m "QQQ" on the WC root */
341   SVN_ERR(svn_wc_process_committed4(sbox_wc_path(&b, "A_copied"), adm_access,
342                                     TRUE, 12, "2014-10-01T19:00:50.966679Z",
343                                     "me", NULL, TRUE, TRUE,
344                                     NULL, pool));
345 
346   {
347     unsigned char digest[APR_MD5_DIGESTSIZE];
348 
349     /* Use the fact that iota has the same checksum to ease committing */
350 
351     SVN_ERR(svn_io_file_checksum (digest, lambda, pool));
352 
353     SVN_ERR(svn_wc_process_committed4(lambda, adm_access,
354                                       TRUE, 12, "2014-10-01T19:00:50.966679Z",
355                                       "me", NULL, TRUE, TRUE,
356                                       digest, pool));
357   }
358 
359   {
360     svn_wc_status2_t *status;
361 
362     SVN_ERR(svn_wc_status2(&status, lambda, adm_access, pool));
363 
364     /* Node is still modified, as we didn't change the text base! */
365     SVN_TEST_ASSERT(status != NULL);
366     SVN_TEST_ASSERT(status->text_status == svn_wc_status_normal);
367     SVN_TEST_ASSERT(status->copied == FALSE);
368   }
369 
370   return SVN_NO_ERROR;
371 }
372 
373 static svn_error_t *
test_legacy_commit2(const svn_test_opts_t * opts,apr_pool_t * pool)374 test_legacy_commit2(const svn_test_opts_t *opts, apr_pool_t *pool)
375 {
376   svn_test__sandbox_t b;
377   svn_wc_adm_access_t *adm_access;
378   const char *lambda;
379   svn_wc_committed_queue_t *queue;
380 
381   SVN_ERR(svn_test__sandbox_create(&b, "legacy_commit2", opts, pool));
382   SVN_ERR(sbox_add_and_commit_greek_tree(&b));
383 
384   SVN_ERR(sbox_wc_copy(&b, "A", "A_copied"));
385 
386   lambda = sbox_wc_path(&b, "A_copied/B/lambda");
387 
388   SVN_ERR(svn_io_remove_file2(lambda, FALSE, pool));
389   SVN_ERR(svn_io_copy_file(sbox_wc_path(&b, "iota"), lambda, FALSE, pool));
390 
391   SVN_ERR(svn_wc_adm_open3(&adm_access, NULL, b.wc_abspath, TRUE, -1,
392                            NULL, NULL, pool));
393 
394   {
395     svn_wc_status2_t *status;
396 
397     SVN_ERR(svn_wc_status2(&status, lambda, adm_access, pool));
398 
399     SVN_TEST_ASSERT(status != NULL);
400     SVN_TEST_ASSERT(status->text_status == svn_wc_status_modified);
401     SVN_TEST_ASSERT(status->copied == TRUE);
402   }
403 
404   /* Simulate an old style svn ci . -m "QQQ" on the WC root */
405   queue = svn_wc_committed_queue_create(pool);
406   SVN_ERR(svn_wc_queue_committed(&queue, sbox_wc_path(&b, "A_copied"), adm_access,
407                                  TRUE, NULL, FALSE, FALSE, NULL, pool));
408   {
409     unsigned char digest[APR_MD5_DIGESTSIZE];
410 
411     /* Use the fact that iota has the same checksum to ease committing */
412 
413     SVN_ERR(svn_io_file_checksum(digest, lambda, pool));
414 
415     SVN_ERR(svn_wc_queue_committed(&queue, lambda, adm_access, FALSE, NULL,
416                                    FALSE, FALSE, digest, pool));
417   }
418 
419   SVN_ERR(svn_wc_process_committed_queue(queue, adm_access,
420                                          12, "2014-10-01T19:00:50.966679Z",
421                                         "me", pool));
422 
423   {
424     svn_wc_status2_t *status;
425 
426     SVN_ERR(svn_wc_status2(&status, lambda, adm_access, pool));
427 
428     /* Node is still modified, as we didn't change the text base! */
429     SVN_TEST_ASSERT(status != NULL);
430     SVN_TEST_ASSERT(status->text_status == svn_wc_status_normal);
431     SVN_TEST_ASSERT(status->copied == FALSE);
432   }
433 
434   return SVN_NO_ERROR;
435 }
436 
437 static svn_error_t *
test_internal_file_modified(const svn_test_opts_t * opts,apr_pool_t * pool)438 test_internal_file_modified(const svn_test_opts_t *opts, apr_pool_t *pool)
439 {
440   svn_test__sandbox_t b;
441   svn_boolean_t modified;
442   const char *iota_path;
443   apr_time_t time;
444 
445   SVN_ERR(svn_test__sandbox_create(&b, "internal_file_modified_p",
446                                    opts, pool));
447   SVN_ERR(sbox_add_and_commit_greek_tree(&b));
448 
449   iota_path = sbox_wc_path(&b, "iota");
450 
451   /* No modification, timestamps match.*/
452   SVN_ERR(svn_wc__internal_file_modified_p(&modified, b.wc_ctx->db,
453                                            iota_path, FALSE, pool));
454   SVN_TEST_ASSERT(!modified);
455 
456   SVN_ERR(svn_wc__internal_file_modified_p(&modified, b.wc_ctx->db,
457                                            iota_path, TRUE, pool));
458   SVN_TEST_ASSERT(!modified);
459 
460   /* Change timestamp on 'iota' and check. */
461   SVN_ERR(svn_io_file_affected_time(&time, iota_path, pool));
462   SVN_ERR(svn_io_set_file_affected_time(time + apr_time_from_sec(1),
463                                         iota_path, pool));
464   SVN_ERR(svn_wc__internal_file_modified_p(&modified, b.wc_ctx->db,
465                                            iota_path, FALSE, pool));
466   SVN_TEST_ASSERT(!modified);
467 
468   SVN_ERR(svn_wc__internal_file_modified_p(&modified, b.wc_ctx->db,
469                                            iota_path, TRUE, pool));
470   SVN_TEST_ASSERT(!modified);
471 
472   /* Modify 'iota' to be different size. */
473   SVN_ERR(sbox_file_write(&b, iota_path, "new iota"));
474   SVN_ERR(svn_wc__internal_file_modified_p(&modified, b.wc_ctx->db,
475                                            iota_path, FALSE, pool));
476   SVN_TEST_ASSERT(modified);
477 
478   SVN_ERR(svn_wc__internal_file_modified_p(&modified, b.wc_ctx->db,
479                                            iota_path, TRUE, pool));
480   SVN_TEST_ASSERT(modified);
481 
482   /* Working copy is smart and able to detect changes in files of different
483    * size even if timestamp didn't change. */
484   SVN_ERR(svn_io_set_file_affected_time(time, iota_path, pool));
485   SVN_ERR(svn_wc__internal_file_modified_p(&modified, b.wc_ctx->db,
486                                            iota_path, FALSE, pool));
487   SVN_TEST_ASSERT(modified);
488 
489   SVN_ERR(svn_wc__internal_file_modified_p(&modified, b.wc_ctx->db,
490                                            iota_path, TRUE, pool));
491   SVN_TEST_ASSERT(modified);
492 
493   return SVN_NO_ERROR;
494 }
495 
496 /* ---------------------------------------------------------------------- */
497 /* The list of test functions */
498 
499 static int max_threads = 2;
500 
501 static struct svn_test_descriptor_t test_funcs[] =
502   {
503     SVN_TEST_NULL,
504     SVN_TEST_OPTS_PASS(test_node_get_base,
505                        "test_node_get_base"),
506     SVN_TEST_OPTS_PASS(test_node_get_origin,
507                        "test_node_get_origin"),
508     SVN_TEST_OPTS_PASS(test_externals_parse,
509                        "test svn_wc_parse_externals_description3"),
510     SVN_TEST_PASS2(test_externals_parse_erratic,
511                    "parse erratic externals definition"),
512     SVN_TEST_OPTS_PASS(test_legacy_commit1,
513                        "test legacy commit1"),
514     SVN_TEST_OPTS_PASS(test_legacy_commit2,
515                        "test legacy commit2"),
516     SVN_TEST_OPTS_PASS(test_internal_file_modified,
517                        "test internal_file_modified"),
518     SVN_TEST_NULL
519   };
520 
521 SVN_TEST_MAIN
522