projects/guix·blob bootstrap-team · refs

536 lines · 18.0 KiB

1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2017 Christopher Baines <[email protected]>
3;;; Copyright © 2020, 2022 Marius Bakke <[email protected]>
4;;; Copyright © 2025 Giacomo Leidi <[email protected]>
5;;;
6;;; This file is part of GNU Guix.
7;;;
8;;; GNU Guix is free software; you can redistribute it and/or modify it
9;;; under the terms of the GNU General Public License as published by
10;;; the Free Software Foundation; either version 3 of the License, or (at
11;;; your option) any later version.
12;;;
13;;; GNU Guix is distributed in the hope that it will be useful, but
14;;; WITHOUT ANY WARRANTY; without even the implied warranty of
15;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16;;; GNU General Public License for more details.
17;;;
18;;; You should have received a copy of the GNU General Public License
19;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
20
21(define-module (gnu tests databases)
22 #:use-module (gnu tests)
23 #:use-module (gnu system)
24 #:use-module (gnu system file-systems)
25 #:use-module (gnu system shadow)
26 #:use-module (gnu system vm)
27 #:use-module (gnu services)
28 #:use-module (gnu services databases)
29 #:use-module (gnu services networking)
30 #:use-module (gnu packages databases)
31 #:use-module (guix gexp)
32 #:use-module (guix store)
33 #:use-module (srfi srfi-1)
34 #:export (%test-memcached
35 %test-postgresql
36 %test-timescaledb
37 %test-mysql))
38
39;;;
40;;; The Memcached service.
41;;;
42
43(define %memcached-os
44 (simple-operating-system
45 (service dhcpcd-service-type)
46 (service memcached-service-type)))
47
48(define* (run-memcached-test #:optional (port 11211))
49 "Run tests in %MEMCACHED-OS, forwarding PORT."
50 (define os
51 (marionette-operating-system
52 %memcached-os
53 #:imported-modules '((gnu services herd)
54 (guix combinators))))
55
56 (define vm
57 (virtual-machine
58 (operating-system os)
59 (port-forwardings `((11211 . ,port)))))
60
61 (define test
62 (with-imported-modules '((gnu build marionette))
63 #~(begin
64 (use-modules (srfi srfi-11) (srfi srfi-64)
65 (gnu build marionette)
66 (ice-9 rdelim))
67
68 (define marionette
69 (make-marionette (list #$vm)))
70
71 (test-runner-current (system-test-runner #$output))
72 (test-begin "memcached")
73
74 ;; Wait for memcached to be up and running.
75 (test-assert "service running"
76 (marionette-eval
77 '(begin
78 (use-modules (gnu services herd))
79 (match (start-service 'memcached)
80 (#f #f)
81 (('service response-parts ...)
82 (match (assq-ref response-parts 'running)
83 ((pid) pid)))))
84 marionette))
85
86 (let* ((ai (car (getaddrinfo "localhost"
87 #$(number->string port))))
88 (s (socket (addrinfo:fam ai)
89 (addrinfo:socktype ai)
90 (addrinfo:protocol ai)))
91 (key "testkey")
92 (value "guix"))
93 (connect s (addrinfo:addr ai))
94
95 (test-equal "set"
96 "STORED\r"
97 (begin
98 (simple-format s "set ~A 0 60 ~A\r\n~A\r\n"
99 key
100 (string-length value)
101 value)
102 (read-line s)))
103
104 (test-equal "get"
105 (simple-format #f "VALUE ~A 0 ~A\r~A\r"
106 key
107 (string-length value)
108 value)
109 (begin
110 (simple-format s "get ~A\r\n" key)
111 (string-append
112 (read-line s)
113 (read-line s))))
114
115 (close-port s))
116
117 ;; There should be a log file in here.
118 (test-assert "log file"
119 (marionette-eval
120 '(file-exists? "/var/log/memcached")
121 marionette))
122
123 (test-end))))
124
125 (gexp->derivation "memcached-test" test))
126
127(define %test-memcached
128 (system-test
129 (name "memcached")
130 (description "Connect to a running MEMCACHED server.")
131 (value (run-memcached-test))))
132
133
134;;;
135;;; The PostgreSQL service.
136;;;
137
138(define %postgresql-log-directory
139 "/var/log/postgresql")
140
141(define %role-log-file
142 "/var/log/postgresql_roles.log")
143
144(define %postgresql-os
145 (simple-operating-system
146 (extra-special-file "/password"
147 (plain-file "password" "hello"))
148 (service postgresql-service-type
149 (postgresql-configuration
150 (postgresql postgresql)
151 (config-file
152 (postgresql-config-file
153 (extra-config
154 '(("session_preload_libraries" "auto_explain")
155 ("random_page_cost" 2)
156 ("auto_explain.log_min_duration" "100 ms")
157 ("work_mem" "500 MB")
158 ("debug_print_plan" #t)))))))
159 (service postgresql-role-service-type
160 (postgresql-role-configuration
161 (roles
162 (list (postgresql-role
163 (name "root")
164 (create-database? #t))
165 (postgresql-role
166 (name "a_database")
167 (password-file "/password")
168 (create-database? #t))))))))
169
170(define (run-postgresql-test)
171 "Run tests in %POSTGRESQL-OS."
172 (define os
173 (marionette-operating-system
174 %postgresql-os
175 #:imported-modules '((gnu services herd)
176 (guix combinators))))
177
178 (define vm
179 (virtual-machine
180 (operating-system os)
181 (memory-size 512)))
182
183 (define test
184 (with-imported-modules '((gnu build marionette))
185 #~(begin
186 (use-modules (srfi srfi-64)
187 (gnu build marionette))
188
189 (define marionette
190 (make-marionette (list #$vm)))
191
192 (test-runner-current (system-test-runner #$output))
193 (test-begin "postgresql")
194
195 (test-assert "service running"
196 (marionette-eval
197 '(begin
198 (use-modules (gnu services herd))
199 (start-service 'postgres))
200 marionette))
201
202 (test-assert "log-file"
203 (marionette-eval
204 '(begin
205 (use-modules (ice-9 ftw)
206 (ice-9 match)
207 (rnrs io ports))
208
209 (current-output-port
210 (open-file "/dev/console" "w0"))
211 (let ((server-log-file
212 (string-append #$%postgresql-log-directory
213 "/pg_ctl.log")))
214 (and (file-exists? server-log-file)
215 (display
216 (call-with-input-file server-log-file
217 get-string-all)))
218 #t))
219 marionette))
220
221 (test-assert "database ready"
222 (begin
223 (marionette-eval
224 '(begin
225 (let loop ((i 10))
226 (unless (or (zero? i)
227 (and (file-exists? #$%role-log-file)
228 (string-contains
229 (call-with-input-file #$%role-log-file
230 get-string-all)
231 ";\nCREATE DATABASE")))
232 (sleep 1)
233 (loop (- i 1)))))
234 marionette)))
235
236 (test-assert "database creation"
237 (marionette-eval
238 '(begin
239 (use-modules (gnu services herd)
240 (srfi srfi-1)
241 (ice-9 popen))
242 (current-output-port
243 (open-file "/dev/console" "w0"))
244 (every
245 (lambda (role)
246 (let* ((port (open-pipe*
247 OPEN_READ
248 #$(file-append postgresql "/bin/psql")
249 "-tA" "-c"
250 (string-append
251 "SELECT 1 FROM pg_database WHERE"
252 " datname='" role "'")))
253 (output (get-string-all port)))
254 (close-pipe port)
255 (string-contains output "1")))
256 '("root" "a_database")))
257 marionette))
258
259 (test-assert "database use fails without a password"
260 (marionette-eval
261 '(begin
262 (setgid (passwd:gid (getpwnam "alice")))
263 (setuid (passwd:uid (getpw "alice")))
264 (not (zero?
265 (system* #$(file-append postgresql "/bin/psql")
266 "-tA" "-h" "localhost" "-U" "a_database" "-c"
267 (string-append "SELECT 1 FROM pg_database "
268 "WHERE datname='a_database'")))))
269 marionette))
270
271 (test-assert "database passwords are set"
272 (marionette-eval
273 '(begin
274 (use-modules (ice-9 popen))
275 (setgid (passwd:gid (getpwnam "alice")))
276 (setuid (passwd:uid (getpw "alice")))
277 (setenv "PGPASSWORD"
278 (call-with-input-file "/password" get-string-all))
279 (let* ((port (open-pipe*
280 OPEN_READ
281 #$(file-append postgresql "/bin/psql")
282 "-U" "a_database" "-tA" "-h" "localhost" "-c"
283 "SELECT 1 FROM pg_database WHERE datname='a_database'"))
284 (output (get-string-all port)))
285 (close-pipe port)
286 (string=? output "1\n")))
287 marionette))
288
289 (test-end))))
290
291 (gexp->derivation "postgresql-test" test))
292
293(define %test-postgresql
294 (system-test
295 (name "postgresql")
296 (description "Start the PostgreSQL service.")
297 (value (run-postgresql-test))))
298
299;; Test TimescaleDB, a PostgreSQL extension.
300(define %timescaledb-os
301 (let* ((postgresql-services (operating-system-services %postgresql-os))
302 (postgresql-service-configuration
303 (service-value (find (lambda (svc)
304 (eq? (service-kind svc) postgresql-service-type))
305 postgresql-services)))
306 (postgresql-role-service-configuration
307 (service-value (find (lambda (svc)
308 (eq? (service-kind svc)
309 postgresql-role-service-type))
310 postgresql-services))))
311 (simple-operating-system
312 (service postgresql-service-type
313 (postgresql-configuration
314 (inherit postgresql-service-configuration)
315 (extension-packages (list timescaledb))
316 (config-file
317 (postgresql-config-file
318 (inherit (postgresql-configuration-file
319 postgresql-service-configuration))
320 (extra-config
321 (append '(("shared_preload_libraries" "timescaledb"))
322 (postgresql-config-file-extra-config
323 (postgresql-configuration-file
324 postgresql-service-configuration))))))))
325 (service postgresql-role-service-type
326 (postgresql-role-configuration
327 (inherit postgresql-role-service-configuration))))))
328
329(define (run-timescaledb-test)
330 "Run tests in %TIMESCALEDB-OS."
331 (define os
332 (marionette-operating-system
333 %timescaledb-os
334 #:imported-modules '((gnu services herd)
335 (guix combinators))))
336
337 (define vm
338 (virtual-machine
339 (operating-system os)
340 (memory-size 512)))
341
342 (define test
343 (with-imported-modules '((gnu build marionette))
344 #~(begin
345 (use-modules (srfi srfi-64)
346 (gnu build marionette))
347
348 (define marionette
349 (make-marionette (list #$vm)))
350
351 (test-runner-current (system-test-runner #$output))
352 (test-begin "timescaledb")
353
354 (test-assert "PostgreSQL running"
355 (marionette-eval
356 '(begin
357 (use-modules (gnu services herd))
358 (start-service 'postgres))
359 marionette))
360
361 (test-assert "database ready"
362 (begin
363 (marionette-eval
364 '(begin
365 (use-modules (rnrs io ports))
366
367 (let loop ((i 10))
368 (unless (or (zero? i)
369 (and (file-exists? #$%role-log-file)
370 (string-contains
371 (call-with-input-file #$%role-log-file
372 get-string-all)
373 ";\nCREATE DATABASE")))
374 (sleep 1)
375 (loop (- i 1)))))
376 marionette)))
377
378 (test-assert "database creation"
379 (marionette-eval
380 '(begin
381 (use-modules (guix build utils))
382
383 (current-output-port (open-file "/dev/console" "w0"))
384 (invoke #$(file-append postgresql "/bin/psql")
385 "-tA" "-c" "CREATE DATABASE test"))
386 marionette))
387
388 (test-assert "load extension"
389 (marionette-eval
390 '(begin
391 (current-output-port (open-file "/dev/console" "w0"))
392 ;; Capture stderr for the next test.
393 (current-error-port (open-file "timescaledb.stderr" "w0"))
394 (invoke #$(file-append postgresql "/bin/psql")
395 "-tA" "-c" "CREATE EXTENSION timescaledb"
396 "test"))
397 marionette))
398
399 (test-assert "telemetry is disabled"
400 (marionette-eval
401 '(begin
402 (let* ((log-file "/var/log/messages")
403 (log (call-with-input-file log-file get-string-all)))
404 (string-contains log "functionality not supported")))
405 marionette))
406
407 (test-assert "create hypertable"
408 (marionette-eval
409 '(begin
410 (current-output-port (open-file "/dev/console" "w0"))
411 (invoke #$(file-append postgresql "/bin/psql")
412 "-tA" "-c" "CREATE TABLE ht (
413time TIMESTAMP NOT NULL,
414data double PRECISION NULL
415)"
416 "test")
417 (invoke #$(file-append postgresql "/bin/psql")
418 "-tA" "-c" "SELECT create_hypertable('ht','time')"
419 "test"))
420 marionette))
421
422 (test-end))))
423
424 (gexp->derivation "timescaledb-test" test))
425
426(define %test-timescaledb
427 (system-test
428 (name "timescaledb")
429 (description "Test the TimescaleDB PostgreSQL extension.")
430 (value (run-timescaledb-test))))
431
432
433;;;
434;;; The MySQL service.
435;;;
436
437(define %mysql-os
438 (simple-operating-system
439 (service mysql-service-type
440 (mysql-configuration
441 ;; Disable O_DIRECT since it's not supported on overlayfs.
442 ;; See <https://jira.mariadb.org/browse/MDEV-28751>.
443 (extra-content "innodb-flush-method = fsync")))))
444
445(define* (run-mysql-test)
446 "Run tests in %MYSQL-OS."
447 (define os
448 (marionette-operating-system
449 %mysql-os
450 #:imported-modules '((gnu services herd)
451 (guix combinators))))
452
453 (define vm
454 (virtual-machine
455 (operating-system os)
456 (memory-size 512)))
457
458 (define test
459 (with-imported-modules '((gnu build marionette))
460 #~(begin
461 (use-modules (srfi srfi-11) (srfi srfi-64)
462 (gnu build marionette))
463
464 (define marionette
465 (make-marionette (list #$vm)))
466
467 (test-runner-current (system-test-runner #$output))
468 (test-begin "mysql")
469
470 (test-assert "service running"
471 (marionette-eval
472 '(begin
473 (use-modules (gnu services herd))
474 (match (start-service 'mysql)
475 (#f #f)
476 (('service response-parts ...)
477 (match (assq-ref response-parts 'running)
478 ((pid) pid)))))
479 marionette))
480
481 (test-assert "mysql_upgrade completed"
482 (wait-for-file "/var/lib/mysql/mysql_upgrade_info" marionette))
483
484 (test-assert "socket is ready"
485 (wait-for-unix-socket "/run/mysqld/mysqld.sock" marionette))
486
487 (test-eq "create database"
488 0
489 (marionette-eval
490 '(begin
491 (system* #$(file-append mariadb "/bin/mysql")
492 "-e" "CREATE DATABASE guix;"))
493 marionette))
494
495 (test-eq "create table"
496 0
497 (marionette-eval
498 '(begin
499 (system*
500 #$(file-append mariadb "/bin/mysql") "guix"
501 "-e" "CREATE TABLE facts (id INT, data VARCHAR(12));"))
502 marionette))
503
504 (test-eq "insert data"
505 0
506 (marionette-eval
507 '(begin
508 (system* #$(file-append mariadb "/bin/mysql") "guix"
509 "-e" "INSERT INTO facts VALUES (1, 'awesome')"))
510 marionette))
511
512 (test-equal "retrieve data"
513 "awesome\n"
514 (marionette-eval
515 '(begin
516 (use-modules (ice-9 popen)
517 (rnrs io ports))
518
519 (let* ((port (open-pipe*
520 OPEN_READ
521 #$(file-append mariadb "/bin/mysql") "guix"
522 "-NB" "-e" "SELECT data FROM facts WHERE id=1;"))
523 (output (get-string-all port)))
524 (close-pipe port)
525 output))
526 marionette))
527
528 (test-end))))
529
530 (gexp->derivation "mysql-test" test))
531
532(define %test-mysql
533 (system-test
534 (name "mysql")
535 (description "Start the MySQL service.")
536 (value (run-mysql-test))))