shell: Detect --symlink spec problems early.
[jackhill/guix/guix.git] / tests / guix-environment-container.sh
1 # GNU Guix --- Functional package management for GNU
2 # Copyright © 2015 David Thompson <davet@gnu.org>
3 # Copyright © 2022 John Kehayias <john.kehayias@protonmail.com>
4 #
5 # This file is part of GNU Guix.
6 #
7 # GNU Guix is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or (at
10 # your option) any later version.
11 #
12 # GNU Guix is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
19
20 #
21 # Test 'guix environment'.
22 #
23
24 set -e
25
26 guix environment --version
27
28 if ! guile -c '((@ (guix scripts environment) assert-container-features))'
29 then
30 # User containers are not supported; skip this test.
31 exit 77
32 fi
33
34 tmpdir="t-guix-environment-$$"
35 trap 'rm -r "$tmpdir"' EXIT
36
37 mkdir "$tmpdir"
38
39 # Make sure the exit value is preserved.
40 if guix environment --container --ad-hoc --bootstrap guile-bootstrap \
41 -- guile -c '(exit 42)'
42 then
43 false
44 else
45 test $? = 42
46 fi
47
48 # Try '--root' and '--profile'.
49 root="$tmpdir/root"
50 guix environment -C --ad-hoc --bootstrap guile-bootstrap -r "$root" -- guile --version
51 guix environment -C -p "$root" --bootstrap -- guile --version
52 path1=$(guix environment -C -p "$root" --bootstrap -- guile -c '(display (getenv "PATH"))')
53 path2=$(guix environment -C --ad-hoc --bootstrap guile-bootstrap -- guile -c '(display (getenv "PATH"))')
54 test "$path1" = "$path2"
55
56 # Make sure "localhost" resolves.
57 guix environment --container --ad-hoc --bootstrap guile-bootstrap \
58 -- guile -c '(exit (pair? (getaddrinfo "localhost" "80")))'
59
60 # We should get ECONNREFUSED, not ENETUNREACH, which would indicate that "lo"
61 # is down.
62 guix environment --container --ad-hoc --bootstrap guile-bootstrap \
63 -- guile -c "(exit (= ECONNREFUSED
64 (catch 'system-error
65 (lambda ()
66 (let ((sock (socket AF_INET SOCK_STREAM 0)))
67 (connect sock AF_INET INADDR_LOOPBACK 12345)))
68 (lambda args
69 (pk 'errno (system-error-errno args))))))"
70
71 # Make sure '--preserve' is honored.
72 result="`FOOBAR=42; export FOOBAR; guix environment -C --ad-hoc --bootstrap \
73 guile-bootstrap -E ^FOO -- guile -c '(display (getenv \"FOOBAR\"))'`"
74 test "$result" = "42"
75
76 # By default, the UID inside the container should be the same as outside.
77 uid="`id -u`"
78 inner_uid="`guix environment -C --ad-hoc --bootstrap guile-bootstrap \
79 -- guile -c '(display (getuid))'`"
80 test $inner_uid = $uid
81
82 # When '--user' is passed, the UID should be 1000. (Note: Use a separate HOME
83 # so that we don't run into problems when the test directory is under /home.)
84 export tmpdir
85 inner_uid="`HOME=$tmpdir guix environment -C --ad-hoc --bootstrap guile-bootstrap \
86 --user=gnu-guix -- guile -c '(display (getuid))'`"
87 test $inner_uid = 1000
88
89 if test "x$USER" = "x"; then USER="`id -un`"; fi
90
91 # Check whether /etc/passwd and /etc/group are valid.
92 guix environment -C --ad-hoc --bootstrap guile-bootstrap \
93 -- guile -c "(exit (string=? \"$USER\" (passwd:name (getpwuid (getuid)))))"
94 guix environment -C --ad-hoc --bootstrap guile-bootstrap \
95 -- guile -c '(exit (string? (group:name (getgrgid (getgid)))))'
96 guix environment -C --ad-hoc --bootstrap guile-bootstrap \
97 -- guile -c '(use-modules (srfi srfi-1))
98 (exit (every group:name
99 (map getgrgid (vector->list (getgroups)))))'
100
101 # Make sure file-not-found errors in mounts are reported.
102 if guix environment --container --ad-hoc --bootstrap guile-bootstrap \
103 --expose=/does-not-exist -- guile -c 1 2> "$tmpdir/error"
104 then
105 false
106 else
107 grep "/does-not-exist" "$tmpdir/error"
108 grep "[Nn]o such file" "$tmpdir/error"
109 fi
110
111 # Make sure that the right directories are mapped.
112 mount_test_code="
113 (use-modules (ice-9 rdelim)
114 (ice-9 match)
115 (srfi srfi-1))
116
117 (define mappings
118 (filter-map (lambda (line)
119 (match (string-split line #\space)
120 ;; Empty line.
121 ((\"\") #f)
122 ;; Ignore the root file system.
123 ((_ \"/\" _ _ _ _)
124 #f)
125 ;; Ignore these types of file systems, except if they
126 ;; correspond to a parent file system.
127 ((_ mount (or \"tmpfs\" \"proc\" \"sysfs\" \"devtmpfs\"
128 \"devpts\" \"cgroup\" \"mqueue\") _ _ _)
129 (and (string-prefix? (getcwd) mount)
130 mount))
131 ((_ mount _ _ _ _)
132 mount)))
133 (string-split (call-with-input-file \"/proc/mounts\" read-string)
134 #\newline)))
135
136 (for-each (lambda (mount)
137 (display mount)
138 (newline))
139 mappings)"
140
141 guix environment --container --ad-hoc --bootstrap guile-bootstrap \
142 -- guile -c "$mount_test_code" > $tmpdir/mounts
143
144 cat "$tmpdir/mounts"
145 test `wc -l < $tmpdir/mounts` -eq 4
146
147 current_dir="`cd $PWD; pwd -P`"
148 grep -e "$current_dir$" $tmpdir/mounts # current directory
149 grep $(guix build guile-bootstrap) $tmpdir/mounts
150 grep -e "$NIX_STORE_DIR/.*-bash" $tmpdir/mounts # bootstrap bash
151
152 rm $tmpdir/mounts
153
154 # Make sure 'GUIX_ENVIRONMENT' is set to '~/.guix-profile' when requested
155 # within a container.
156 (
157 linktest='
158 (exit (and (string=? (getenv "GUIX_ENVIRONMENT")
159 (string-append (getenv "HOME") "/.guix-profile"))
160 (string-prefix? "'"$NIX_STORE_DIR"'"
161 (readlink (string-append (getenv "HOME")
162 "/.guix-profile")))))'
163
164 cd "$tmpdir" \
165 && guix environment --bootstrap --container --link-profile \
166 --ad-hoc guile-bootstrap --pure \
167 -- guile -c "$linktest"
168 )
169
170 # Test that user can be mocked.
171 usertest='(exit (and (string=? (getenv "HOME") "/home/foognu")
172 (string=? (passwd:name (getpwuid 1000)) "foognu")
173 (file-exists? "/home/foognu/umock")))'
174 touch "$tmpdir/umock"
175 HOME="$tmpdir" guix environment --bootstrap --container --user=foognu \
176 --ad-hoc guile-bootstrap --pure \
177 --share="$tmpdir/umock" \
178 -- guile -c "$usertest"
179
180 # if not sharing CWD, chdir home
181 (
182 cd "$tmpdir" \
183 && guix environment --bootstrap --container --no-cwd --user=foo \
184 --ad-hoc guile-bootstrap --pure \
185 -- /bin/sh -c 'test $(pwd) == "/home/foo" -a ! -d '"$tmpdir"
186 )
187
188 # Check the exit code.
189
190 abnormal_exit_code="
191 (use-modules (system foreign))
192 ;; Purposely make Guile crash with a segfault. :)
193 (pointer->string (make-pointer 123) 123)"
194
195 if guix environment --bootstrap --container \
196 --ad-hoc guile-bootstrap -- guile -c "$abnormal_exit_code"
197 then false;
198 else
199 test $? -gt 127
200 fi
201
202 # Test the Filesystem Hierarchy Standard (FHS) container option, --emulate-fhs (-F)
203
204 # As this option requires a glibc package (glibc-for-fhs), try to run these
205 # tests with the user's global store to make it easier to build or download a
206 # substitute.
207 storedir="`guile -c '(use-modules (guix config))(display %storedir)'`"
208 localstatedir="`guile -c '(use-modules (guix config))(display %localstatedir)'`"
209 NIX_STORE_DIR="$storedir"
210 GUIX_DAEMON_SOCKET="$localstatedir/guix/daemon-socket/socket"
211 export NIX_STORE_DIR GUIX_DAEMON_SOCKET
212
213 if ! guile -c '(use-modules (guix)) (exit (false-if-exception (open-connection)))'
214 then
215 exit 77
216 fi
217
218 # Test that the container has FHS specific files/directories. Note that /bin
219 # exists in a non-FHS container as it will contain sh, a symlink to the bash
220 # package, so we don't test for it.
221 guix shell -C --emulate-fhs --bootstrap guile-bootstrap \
222 -- guile -c '(exit (and (file-exists? "/etc/ld.so.cache")
223 (file-exists? "/lib")
224 (file-exists? "/sbin")
225 (file-exists? "/usr/bin")
226 (file-exists? "/usr/include")
227 (file-exists? "/usr/lib")
228 (file-exists? "/usr/libexec")
229 (file-exists? "/usr/sbin")
230 (file-exists? "/usr/share")))'
231
232 # Test that the ld cache was generated and can be successfully read.
233 guix shell -CF --bootstrap guile-bootstrap \
234 -- guile -c '(execlp "ldconfig" "ldconfig" "-p")'
235
236 # Test that the package glibc-for-fhs is in the container even if there is the
237 # regular glibc package from another source. See
238 # <https://issues.guix.gnu.org/58861>.
239 guix shell -CF --bootstrap guile-bootstrap glibc \
240 -- guile -c '(exit (if (string-contains (readlink "/lib/libc.so")
241 "glibc-for-fhs")
242 0
243 1))'
244
245 # '--symlink' works.
246 echo "TESTING SYMLINK IN CONTAINER"
247 guix shell --bootstrap guile-bootstrap --container \
248 --symlink=/usr/bin/guile=bin/guile -- \
249 /usr/bin/guile --version
250
251 # A dangling symlink causes the command to fail.
252 ! guix shell --bootstrap -CS /usr/bin/python=bin/python guile-bootstrap -- exit
253
254 # An invalid symlink spec causes the command to fail.
255 ! guix shell --bootstrap -CS bin/guile=/usr/bin/guile guile-bootstrap -- exit