dte test coverage


Directory: ./
File: src/util/path.c
Date: 2026-01-27 12:16:02
Coverage Exec Excl Total
Lines: 97.4% 74 0 76
Functions: 100.0% 7 0 7
Branches: 88.3% 53 0 60

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