]> git.proxmox.com Git - libgit2.git/blob - tests/clar_libgit2.c
65b8923f5cdb9c512f5269239f9385aedad7c53d
[libgit2.git] / tests / clar_libgit2.c
1 #include "clar_libgit2.h"
2 #include "posix.h"
3 #include "path.h"
4 #include "git2/sys/repository.h"
5
6 void cl_git_report_failure(
7 int error, int expected, const char *file, const char *func, int line, const char *fncall)
8 {
9 char msg[4096];
10 const git_error *last = git_error_last();
11
12 if (expected)
13 p_snprintf(msg, 4096, "error %d (expected %d) - %s",
14 error, expected, last ? last->message : "<no message>");
15 else if (error || last)
16 p_snprintf(msg, 4096, "error %d - %s",
17 error, last ? last->message : "<no message>");
18 else
19 p_snprintf(msg, 4096, "no error, expected non-zero return");
20
21 clar__assert(0, file, func, line, fncall, msg, 1);
22 }
23
24 void cl_git_mkfile(const char *filename, const char *content)
25 {
26 int fd;
27
28 fd = p_creat(filename, 0666);
29 cl_assert(fd != -1);
30
31 if (content) {
32 cl_must_pass(p_write(fd, content, strlen(content)));
33 } else {
34 cl_must_pass(p_write(fd, filename, strlen(filename)));
35 cl_must_pass(p_write(fd, "\n", 1));
36 }
37
38 cl_must_pass(p_close(fd));
39 }
40
41 void cl_git_write2file(
42 const char *path, const char *content, size_t content_len,
43 int flags, unsigned int mode)
44 {
45 int fd;
46 cl_assert(path && content);
47 cl_assert((fd = p_open(path, flags, mode)) >= 0);
48 if (!content_len)
49 content_len = strlen(content);
50 cl_must_pass(p_write(fd, content, content_len));
51 cl_must_pass(p_close(fd));
52 }
53
54 void cl_git_append2file(const char *path, const char *content)
55 {
56 cl_git_write2file(path, content, 0, O_WRONLY | O_CREAT | O_APPEND, 0644);
57 }
58
59 void cl_git_rewritefile(const char *path, const char *content)
60 {
61 cl_git_write2file(path, content, 0, O_WRONLY | O_CREAT | O_TRUNC, 0644);
62 }
63
64 void cl_git_rmfile(const char *filename)
65 {
66 cl_must_pass(p_unlink(filename));
67 }
68
69 char *cl_getenv(const char *name)
70 {
71 git_buf out = GIT_BUF_INIT;
72 int error = git__getenv(&out, name);
73
74 cl_assert(error >= 0 || error == GIT_ENOTFOUND);
75
76 if (error == GIT_ENOTFOUND)
77 return NULL;
78
79 if (out.size == 0) {
80 char *dup = git__strdup("");
81 cl_assert(dup);
82
83 return dup;
84 }
85
86 return git_buf_detach(&out);
87 }
88
89 bool cl_is_env_set(const char *name)
90 {
91 char *env = cl_getenv(name);
92 bool result = (env != NULL);
93 git__free(env);
94 return result;
95 }
96
97 #ifdef GIT_WIN32
98
99 #include "win32/utf-conv.h"
100
101 int cl_setenv(const char *name, const char *value)
102 {
103 wchar_t *wide_name, *wide_value = NULL;
104
105 cl_assert(git__utf8_to_16_alloc(&wide_name, name) >= 0);
106
107 if (value) {
108 cl_assert(git__utf8_to_16_alloc(&wide_value, value) >= 0);
109 cl_assert(SetEnvironmentVariableW(wide_name, wide_value));
110 } else {
111 /* Windows XP returns 0 (failed) when passing NULL for lpValue when
112 * lpName does not exist in the environment block. This behavior
113 * seems to have changed in later versions. Don't check the return value
114 * of SetEnvironmentVariable when passing NULL for lpValue. */
115 SetEnvironmentVariableW(wide_name, NULL);
116 }
117
118 git__free(wide_name);
119 git__free(wide_value);
120 return 0;
121 }
122
123 /* This function performs retries on calls to MoveFile in order
124 * to provide enhanced reliability in the face of antivirus
125 * agents that may be scanning the source (or in the case that
126 * the source is a directory, a child of the source). */
127 int cl_rename(const char *source, const char *dest)
128 {
129 git_win32_path source_utf16;
130 git_win32_path dest_utf16;
131 unsigned retries = 1;
132
133 cl_assert(git_win32_path_from_utf8(source_utf16, source) >= 0);
134 cl_assert(git_win32_path_from_utf8(dest_utf16, dest) >= 0);
135
136 while (!MoveFileW(source_utf16, dest_utf16)) {
137 /* Only retry if the error is ERROR_ACCESS_DENIED;
138 * this may indicate that an antivirus agent is
139 * preventing the rename from source to target */
140 if (retries > 5 ||
141 ERROR_ACCESS_DENIED != GetLastError())
142 return -1;
143
144 /* With 5 retries and a coefficient of 10ms, the maximum
145 * delay here is 550 ms */
146 Sleep(10 * retries * retries);
147 retries++;
148 }
149
150 return 0;
151 }
152
153 #else
154
155 #include <stdlib.h>
156
157 int cl_setenv(const char *name, const char *value)
158 {
159 return (value == NULL) ? unsetenv(name) : setenv(name, value, 1);
160 }
161
162 int cl_rename(const char *source, const char *dest)
163 {
164 return p_rename(source, dest);
165 }
166
167 #endif
168
169 static const char *_cl_sandbox = NULL;
170 static git_repository *_cl_repo = NULL;
171
172 git_repository *cl_git_sandbox_init(const char *sandbox)
173 {
174 /* Get the name of the sandbox folder which will be created */
175 const char *basename = cl_fixture_basename(sandbox);
176
177 /* Copy the whole sandbox folder from our fixtures to our test sandbox
178 * area. After this it can be accessed with `./sandbox`
179 */
180 cl_fixture_sandbox(sandbox);
181 _cl_sandbox = sandbox;
182
183 cl_git_pass(p_chdir(basename));
184
185 /* If this is not a bare repo, then rename `sandbox/.gitted` to
186 * `sandbox/.git` which must be done since we cannot store a folder
187 * named `.git` inside the fixtures folder of our libgit2 repo.
188 */
189 if (p_access(".gitted", F_OK) == 0)
190 cl_git_pass(cl_rename(".gitted", ".git"));
191
192 /* If we have `gitattributes`, rename to `.gitattributes`. This may
193 * be necessary if we don't want the attributes to be applied in the
194 * libgit2 repo, but just during testing.
195 */
196 if (p_access("gitattributes", F_OK) == 0)
197 cl_git_pass(cl_rename("gitattributes", ".gitattributes"));
198
199 /* As with `gitattributes`, we may need `gitignore` just for testing. */
200 if (p_access("gitignore", F_OK) == 0)
201 cl_git_pass(cl_rename("gitignore", ".gitignore"));
202
203 cl_git_pass(p_chdir(".."));
204
205 /* Now open the sandbox repository and make it available for tests */
206 cl_git_pass(git_repository_open(&_cl_repo, basename));
207
208 /* Adjust configs after copying to new filesystem */
209 cl_git_pass(git_repository_reinit_filesystem(_cl_repo, 0));
210
211 return _cl_repo;
212 }
213
214 git_repository *cl_git_sandbox_init_new(const char *sandbox)
215 {
216 cl_git_pass(git_repository_init(&_cl_repo, sandbox, false));
217 _cl_sandbox = sandbox;
218
219 return _cl_repo;
220 }
221
222 git_repository *cl_git_sandbox_reopen(void)
223 {
224 if (_cl_repo) {
225 git_repository_free(_cl_repo);
226 _cl_repo = NULL;
227
228 cl_git_pass(git_repository_open(
229 &_cl_repo, cl_fixture_basename(_cl_sandbox)));
230 }
231
232 return _cl_repo;
233 }
234
235 void cl_git_sandbox_cleanup(void)
236 {
237 if (_cl_repo) {
238 git_repository_free(_cl_repo);
239 _cl_repo = NULL;
240 }
241 if (_cl_sandbox) {
242 cl_fixture_cleanup(_cl_sandbox);
243 _cl_sandbox = NULL;
244 }
245 }
246
247 bool cl_toggle_filemode(const char *filename)
248 {
249 struct stat st1, st2;
250
251 cl_must_pass(p_stat(filename, &st1));
252 cl_must_pass(p_chmod(filename, st1.st_mode ^ 0100));
253 cl_must_pass(p_stat(filename, &st2));
254
255 return (st1.st_mode != st2.st_mode);
256 }
257
258 bool cl_is_chmod_supported(void)
259 {
260 static int _is_supported = -1;
261
262 if (_is_supported < 0) {
263 cl_git_mkfile("filemode.t", "Test if filemode can be modified");
264 _is_supported = cl_toggle_filemode("filemode.t");
265 cl_must_pass(p_unlink("filemode.t"));
266 }
267
268 return _is_supported;
269 }
270
271 const char* cl_git_fixture_url(const char *fixturename)
272 {
273 return cl_git_path_url(cl_fixture(fixturename));
274 }
275
276 const char* cl_git_path_url(const char *path)
277 {
278 static char url[4096];
279
280 const char *in_buf;
281 git_buf path_buf = GIT_BUF_INIT;
282 git_buf url_buf = GIT_BUF_INIT;
283
284 cl_git_pass(git_path_prettify_dir(&path_buf, path, NULL));
285 cl_git_pass(git_buf_puts(&url_buf, "file://"));
286
287 #ifdef GIT_WIN32
288 /*
289 * A FILE uri matches the following format: file://[host]/path
290 * where "host" can be empty and "path" is an absolute path to the resource.
291 *
292 * In this test, no hostname is used, but we have to ensure the leading triple slashes:
293 *
294 * *nix: file:///usr/home/...
295 * Windows: file:///C:/Users/...
296 */
297 cl_git_pass(git_buf_putc(&url_buf, '/'));
298 #endif
299
300 in_buf = git_buf_cstr(&path_buf);
301
302 /*
303 * A very hacky Url encoding that only takes care of escaping the spaces
304 */
305 while (*in_buf) {
306 if (*in_buf == ' ')
307 cl_git_pass(git_buf_puts(&url_buf, "%20"));
308 else
309 cl_git_pass(git_buf_putc(&url_buf, *in_buf));
310
311 in_buf++;
312 }
313
314 cl_assert(url_buf.size < 4096);
315
316 strncpy(url, git_buf_cstr(&url_buf), 4096);
317 git_buf_dispose(&url_buf);
318 git_buf_dispose(&path_buf);
319 return url;
320 }
321
322 const char *cl_git_sandbox_path(int is_dir, ...)
323 {
324 const char *path = NULL;
325 static char _temp[GIT_PATH_MAX];
326 git_buf buf = GIT_BUF_INIT;
327 va_list arg;
328
329 cl_git_pass(git_buf_sets(&buf, clar_sandbox_path()));
330
331 va_start(arg, is_dir);
332
333 while ((path = va_arg(arg, const char *)) != NULL) {
334 cl_git_pass(git_buf_joinpath(&buf, buf.ptr, path));
335 }
336 va_end(arg);
337
338 cl_git_pass(git_path_prettify(&buf, buf.ptr, NULL));
339 if (is_dir)
340 git_path_to_dir(&buf);
341
342 /* make sure we won't truncate */
343 cl_assert(git_buf_len(&buf) < sizeof(_temp));
344 git_buf_copy_cstr(_temp, sizeof(_temp), &buf);
345
346 git_buf_dispose(&buf);
347
348 return _temp;
349 }
350
351 typedef struct {
352 const char *filename;
353 size_t filename_len;
354 } remove_data;
355
356 static int remove_placeholders_recurs(void *_data, git_buf *path)
357 {
358 remove_data *data = (remove_data *)_data;
359 size_t pathlen;
360
361 if (git_path_isdir(path->ptr) == true)
362 return git_path_direach(path, 0, remove_placeholders_recurs, data);
363
364 pathlen = path->size;
365
366 if (pathlen < data->filename_len)
367 return 0;
368
369 /* if path ends in '/'+filename (or equals filename) */
370 if (!strcmp(data->filename, path->ptr + pathlen - data->filename_len) &&
371 (pathlen == data->filename_len ||
372 path->ptr[pathlen - data->filename_len - 1] == '/'))
373 return p_unlink(path->ptr);
374
375 return 0;
376 }
377
378 int cl_git_remove_placeholders(const char *directory_path, const char *filename)
379 {
380 int error;
381 remove_data data;
382 git_buf buffer = GIT_BUF_INIT;
383
384 if (git_path_isdir(directory_path) == false)
385 return -1;
386
387 if (git_buf_sets(&buffer, directory_path) < 0)
388 return -1;
389
390 data.filename = filename;
391 data.filename_len = strlen(filename);
392
393 error = remove_placeholders_recurs(&data, &buffer);
394
395 git_buf_dispose(&buffer);
396
397 return error;
398 }
399
400 #define CL_COMMIT_NAME "Libgit2 Tester"
401 #define CL_COMMIT_EMAIL "libgit2-test@github.com"
402 #define CL_COMMIT_MSG "Test commit of tree "
403
404 void cl_repo_commit_from_index(
405 git_oid *out,
406 git_repository *repo,
407 git_signature *sig,
408 git_time_t time,
409 const char *msg)
410 {
411 git_index *index;
412 git_oid commit_id, tree_id;
413 git_object *parent = NULL;
414 git_reference *ref = NULL;
415 git_tree *tree = NULL;
416 char buf[128];
417 int free_sig = (sig == NULL);
418
419 /* it is fine if looking up HEAD fails - we make this the first commit */
420 git_revparse_ext(&parent, &ref, repo, "HEAD");
421
422 /* write the index content as a tree */
423 cl_git_pass(git_repository_index(&index, repo));
424 cl_git_pass(git_index_write_tree(&tree_id, index));
425 cl_git_pass(git_index_write(index));
426 git_index_free(index);
427
428 cl_git_pass(git_tree_lookup(&tree, repo, &tree_id));
429
430 if (sig)
431 cl_assert(sig->name && sig->email);
432 else if (!time)
433 cl_git_pass(git_signature_now(&sig, CL_COMMIT_NAME, CL_COMMIT_EMAIL));
434 else
435 cl_git_pass(git_signature_new(
436 &sig, CL_COMMIT_NAME, CL_COMMIT_EMAIL, time, 0));
437
438 if (!msg) {
439 strcpy(buf, CL_COMMIT_MSG);
440 git_oid_tostr(buf + strlen(CL_COMMIT_MSG),
441 sizeof(buf) - strlen(CL_COMMIT_MSG), &tree_id);
442 msg = buf;
443 }
444
445 cl_git_pass(git_commit_create_v(
446 &commit_id, repo, ref ? git_reference_name(ref) : "HEAD",
447 sig, sig, NULL, msg, tree, parent ? 1 : 0, parent));
448
449 if (out)
450 git_oid_cpy(out, &commit_id);
451
452 git_object_free(parent);
453 git_reference_free(ref);
454 if (free_sig)
455 git_signature_free(sig);
456 git_tree_free(tree);
457 }
458
459 void cl_repo_set_bool(git_repository *repo, const char *cfg, int value)
460 {
461 git_config *config;
462 cl_git_pass(git_repository_config(&config, repo));
463 cl_git_pass(git_config_set_bool(config, cfg, value != 0));
464 git_config_free(config);
465 }
466
467 int cl_repo_get_bool(git_repository *repo, const char *cfg)
468 {
469 int val = 0;
470 git_config *config;
471 cl_git_pass(git_repository_config(&config, repo));
472 if (git_config_get_bool(&val, config, cfg) < 0)
473 git_error_clear();
474 git_config_free(config);
475 return val;
476 }
477
478 void cl_repo_set_string(git_repository *repo, const char *cfg, const char *value)
479 {
480 git_config *config;
481 cl_git_pass(git_repository_config(&config, repo));
482 cl_git_pass(git_config_set_string(config, cfg, value));
483 git_config_free(config);
484 }
485
486 /* this is essentially the code from git__unescape modified slightly */
487 static size_t strip_cr_from_buf(char *start, size_t len)
488 {
489 char *scan, *trail, *end = start + len;
490
491 for (scan = trail = start; scan < end; trail++, scan++) {
492 while (*scan == '\r')
493 scan++; /* skip '\r' */
494
495 if (trail != scan)
496 *trail = *scan;
497 }
498
499 *trail = '\0';
500
501 return (trail - start);
502 }
503
504 void clar__assert_equal_file(
505 const char *expected_data,
506 size_t expected_bytes,
507 int ignore_cr,
508 const char *path,
509 const char *file,
510 const char *func,
511 int line)
512 {
513 char buf[4000];
514 ssize_t bytes, total_bytes = 0;
515 int fd = p_open(path, O_RDONLY | O_BINARY);
516 cl_assert(fd >= 0);
517
518 if (expected_data && !expected_bytes)
519 expected_bytes = strlen(expected_data);
520
521 while ((bytes = p_read(fd, buf, sizeof(buf))) != 0) {
522 clar__assert(
523 bytes > 0, file, func, line, "error reading from file", path, 1);
524
525 if (ignore_cr)
526 bytes = strip_cr_from_buf(buf, bytes);
527
528 if (memcmp(expected_data, buf, bytes) != 0) {
529 int pos;
530 for (pos = 0; pos < bytes && expected_data[pos] == buf[pos]; ++pos)
531 /* find differing byte offset */;
532 p_snprintf(
533 buf, sizeof(buf), "file content mismatch at byte %"PRIdZ,
534 (ssize_t)(total_bytes + pos));
535 p_close(fd);
536 clar__fail(file, func, line, path, buf, 1);
537 }
538
539 expected_data += bytes;
540 total_bytes += bytes;
541 }
542
543 p_close(fd);
544
545 clar__assert(!bytes, file, func, line, "error reading from file", path, 1);
546 clar__assert_equal(file, func, line, "mismatched file length", 1, "%"PRIuZ,
547 (size_t)expected_bytes, (size_t)total_bytes);
548 }
549
550 static char *_cl_restore_home = NULL;
551
552 void cl_fake_home_cleanup(void *payload)
553 {
554 char *restore = _cl_restore_home;
555 _cl_restore_home = NULL;
556
557 GIT_UNUSED(payload);
558
559 if (restore) {
560 cl_git_pass(git_libgit2_opts(
561 GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, restore));
562 git__free(restore);
563 }
564 }
565
566 void cl_fake_home(void)
567 {
568 git_buf path = GIT_BUF_INIT;
569
570 cl_git_pass(git_libgit2_opts(
571 GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &path));
572
573 _cl_restore_home = git_buf_detach(&path);
574 cl_set_cleanup(cl_fake_home_cleanup, NULL);
575
576 if (!git_path_exists("home"))
577 cl_must_pass(p_mkdir("home", 0777));
578 cl_git_pass(git_path_prettify(&path, "home", NULL));
579 cl_git_pass(git_libgit2_opts(
580 GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr));
581 git_buf_dispose(&path);
582 }
583
584 void cl_sandbox_set_search_path_defaults(void)
585 {
586 git_buf path = GIT_BUF_INIT;
587
588 git_buf_joinpath(&path, clar_sandbox_path(), "__config");
589
590 if (!git_path_exists(path.ptr))
591 cl_must_pass(p_mkdir(path.ptr, 0777));
592
593 git_libgit2_opts(
594 GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr);
595 git_libgit2_opts(
596 GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr);
597 git_libgit2_opts(
598 GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr);
599 git_libgit2_opts(
600 GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_PROGRAMDATA, path.ptr);
601
602 git_buf_dispose(&path);
603 }
604
605 #ifdef GIT_WIN32
606 bool cl_sandbox_supports_8dot3(void)
607 {
608 git_buf longpath = GIT_BUF_INIT;
609 char *shortname;
610 bool supported;
611
612 cl_git_pass(
613 git_buf_joinpath(&longpath, clar_sandbox_path(), "longer_than_8dot3"));
614
615 cl_git_write2file(longpath.ptr, "", 0, O_RDWR|O_CREAT, 0666);
616 shortname = git_win32_path_8dot3_name(longpath.ptr);
617
618 supported = (shortname != NULL);
619
620 git__free(shortname);
621 git_buf_dispose(&longpath);
622
623 return supported;
624 }
625 #endif
626