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