dte test coverage


Directory: ./
File: src/util/path.c
Date: 2025-12-11 10:43:49
Coverage Exec Excl Total
Lines: 100.0% 70 0 70
Functions: 100.0% 7 0 7
Branches: 91.1% 51 0 56

Line Branch Exec Source
1 #include <errno.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include "path.h"
5 #include "str-util.h"
6
7 63 char *path_absolute(const char *path)
8 {
9
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 62 times.
63 if (unlikely(path[0] == '\0')) {
10 1 errno = EINVAL;
11 1 return NULL;
12 }
13
14 62 char *abs = realpath(path, NULL);
15
3/4
✓ Branch 5 → 6 taken 28 times.
✓ Branch 5 → 13 taken 34 times.
✓ Branch 6 → 7 taken 28 times.
✗ Branch 6 → 13 not taken.
62 if (abs || errno != ENOENT) {
16 return abs;
17 }
18
19 28 const char *base = path_basename(path);
20 28 char *dir_relative = path_dirname(path);
21 28 char *dir = realpath(dir_relative, NULL);
22 28 free(dir_relative);
23
2/2
✓ Branch 9 → 10 taken 2 times.
✓ Branch 9 → 11 taken 26 times.
28 if (!dir) {
24 2 errno = ENOENT;
25 2 return NULL;
26 }
27
28 // If the full path doesn't exist but the directory part does, return
29 // the concatenation of the real directory and the non-existent filename
30 26 abs = path_join(dir, base);
31 26 free(dir);
32 26 return abs;
33 }
34
35 115 static bool path_component(const char *path, size_t pos)
36 {
37
5/6
✓ Branch 2 → 3 taken 114 times.
✓ Branch 2 → 6 taken 1 time.
✓ Branch 3 → 4 taken 114 times.
✗ Branch 3 → 6 not taken.
✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 6 taken 110 times.
115 return path[pos] == '\0' || pos == 0 || path[pos - 1] == '/';
38 }
39
40 // Like path_relative(), but always returning either a "borrowed" pointer
41 // into `abs` or the static string ".", thus avoiding the need to allocate.
42 // This most notably means that "../" path components are never used, even
43 // if doing so would produce a path shorter than `abs`.
44 8 const char *path_slice_relative(const char *abs, const char *cwd)
45 {
46 8 BUG_ON(!path_is_absolute(cwd));
47 8 BUG_ON(!path_is_absolute(abs));
48
49 // Special case
50
2/2
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 10 taken 6 times.
8 if (cwd[1] == '\0') {
51
2/2
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 1 time.
2 return abs[1] == '\0' ? abs : abs + 1;
52 }
53
54 6 size_t clen = str_common_prefix_length(cwd, abs);
55
2/2
✓ Branch 10 → 11 taken 4 times.
✓ Branch 10 → 14 taken 2 times.
6 if (cwd[clen] == '\0') {
56
3/3
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 13 taken 2 times.
✓ Branch 11 → 14 taken 1 time.
4 switch (abs[clen]) {
57 1 case '\0':
58 // Identical strings; abs is current directory
59 1 return ".";
60 2 case '/':
61 2 return abs + clen + 1;
62 }
63 }
64
65 return abs;
66 }
67
68 138 char *path_relative(const char *abs, const char *cwd)
69 {
70 138 BUG_ON(!path_is_absolute(cwd));
71 138 BUG_ON(!path_is_absolute(abs));
72
73 // Special case
74
2/2
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 10 taken 136 times.
138 if (cwd[1] == '\0') {
75
2/2
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 1 time.
2 return xstrdup(abs[1] == '\0' ? abs : abs + 1);
76 }
77
78 136 size_t clen = str_common_prefix_length(cwd, abs);
79
2/2
✓ Branch 10 → 11 taken 78 times.
✓ Branch 10 → 14 taken 58 times.
136 if (cwd[clen] == '\0') {
80
3/3
✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 13 taken 75 times.
✓ Branch 11 → 14 taken 1 time.
78 switch (abs[clen]) {
81 2 case '\0':
82 // Identical strings; abs is current directory
83 2 return xstrdup(".");
84 75 case '/':
85 // cwd = /home/user
86 // abs = /home/user/project-a/file.c
87 // common = /home/user
88 75 return xstrdup(abs + clen + 1);
89 }
90 }
91
92 // Common path components
93
4/4
✓ Branch 14 → 15 taken 56 times.
✓ Branch 14 → 16 taken 3 times.
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 19 taken 55 times.
59 if (!path_component(cwd, clen) || !path_component(abs, clen)) {
94
3/4
✓ Branch 17 → 18 taken 17 times.
✗ Branch 17 → 19 not taken.
✓ Branch 18 → 17 taken 13 times.
✓ Branch 18 → 19 taken 4 times.
17 while (clen > 0 && abs[clen - 1] != '/') {
95 clen--;
96 }
97 }
98
99 // Number of "../" needed
100 59 size_t dotdot = 1;
101 59 const char *slash = strchr(cwd + clen + 1, '/');
102
2/2
✓ Branch 19 → 20 taken 54 times.
✓ Branch 19 → 22 taken 5 times.
59 if (slash) {
103 54 dotdot++;
104
2/2
✓ Branch 20 → 21 taken 4 times.
✓ Branch 20 → 22 taken 50 times.
54 if (strchr(slash + 1, '/')) {
105 // Just use absolute path if `dotdot` would be > 2
106 4 return xstrdup(abs);
107 }
108 }
109
110 55 const char *tail = abs + clen;
111 55 return xmemjoin("../../", 3 * dotdot, tail, strlen(tail) + 1);
112 }
113
114 128 static bool path_has_dir_prefix(StringView path, StringView dir)
115 {
116
4/4
✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 5 taken 125 times.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 2 times.
128 return strview_has_sv_prefix(path, dir) && path.data[dir.length] == '/';
117 }
118
119 128 char *short_filename_cwd(const char *abs, const char *cwd, StringView home)
120 {
121 128 StringView abs_sv = strview(abs);
122 128 char *rel = path_relative(abs, cwd);
123 128 size_t abs_len = abs_sv.length;
124 128 size_t rel_len = strlen(rel);
125 128 size_t suffix_len = (abs_len - home.length) + 1;
126
127
3/4
✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 7 taken 126 times.
✓ Branch 5 → 6 taken 2 times.
✗ Branch 5 → 7 not taken.
128 if (path_has_dir_prefix(abs_sv, home) && suffix_len < rel_len) {
128 // Prefer absolute in tilde notation (e.g. "~/abs/path"), if applicable
129 // and shorter than relative
130 2 rel[0] = '~';
131 2 memcpy(rel + 1, abs + home.length, suffix_len);
132
2/2
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 125 times.
126 } else if (abs_len < rel_len) {
133 // Prefer absolute, if shorter than relative
134 1 memcpy(rel, abs, abs_len + 1);
135 }
136
137 128 return rel;
138 }
139
140 50 char *short_filename(const char *abs, StringView home)
141 {
142 50 char buf[8192];
143 50 const char *cwd = getcwd(buf, sizeof buf);
144
1/2
✓ Branch 3 → 4 taken 50 times.
✗ Branch 3 → 5 not taken.
50 return likely(cwd) ? short_filename_cwd(abs, cwd, home) : xstrdup(abs);
145 }
146