common.scm 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. ;;; common.scm -- Heper procedures and macros for tests.
  2. ;; Copyright (C) 2015, 2016 Artyom V. Poptsov <poptsov.artyom@gmail.com>
  3. ;;
  4. ;; This file is a part of Guile-SSH.
  5. ;;
  6. ;; Guile-SSH is free software: you can redistribute it and/or
  7. ;; modify it under the terms of the GNU General Public License as
  8. ;; published by the Free Software Foundation, either version 3 of the
  9. ;; License, or (at your option) any later version.
  10. ;;
  11. ;; Guile-SSH is distributed in the hope that it will be useful, but
  12. ;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. ;; General Public License for more details.
  15. ;;
  16. ;; You should have received a copy of the GNU General Public License
  17. ;; along with Guile-SSH. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (tests common)
  19. #:use-module (srfi srfi-64)
  20. #:use-module (srfi srfi-26)
  21. #:use-module (ice-9 rdelim)
  22. #:use-module (ice-9 format)
  23. #:use-module (ice-9 regex)
  24. #:use-module (ice-9 popen)
  25. #:use-module (ssh session)
  26. #:use-module (ssh channel)
  27. #:use-module (ssh server)
  28. #:use-module (ssh log)
  29. #:use-module (ssh message)
  30. #:export (;; Variables
  31. %topdir
  32. %topbuilddir
  33. %knownhosts
  34. %config
  35. %addr
  36. %rsakey
  37. %rsakey-pub
  38. %dsakey
  39. %dsakey-pub
  40. %ecdsakey
  41. %ecdsakey-pub
  42. ;; Procedures
  43. get-unused-port
  44. test-begin-with-log
  45. test-assert-with-log
  46. test-error-with-log
  47. test-error-with-log/=
  48. test-equal-with-log
  49. start-session-loop
  50. make-session-for-test
  51. make-server-for-test
  52. make-libssh-log-printer
  53. call-with-connected-session
  54. start-server-loop
  55. start-server/dt-test
  56. start-server/dist-test
  57. start-server/exec
  58. run-client-test
  59. run-client-test/separate-process
  60. run-server-test
  61. format-log/scm
  62. poll))
  63. (define %topdir (getenv "abs_top_srcdir"))
  64. (define %topbuilddir (getenv "abs_top_builddir"))
  65. (define %addr "127.0.0.1")
  66. (define *port* 12400)
  67. ;; Keys
  68. (define %rsakey (format #f "~a/tests/keys/rsakey" %topdir))
  69. (define %rsakey-pub (format #f "~a/tests/keys/rsakey.pub" %topdir))
  70. (define %dsakey (format #f "~a/tests/keys/dsakey" %topdir))
  71. (define %dsakey-pub (format #f "~a/tests/keys/dsakey.pub" %topdir))
  72. (define %ecdsakey (format #f "~a/tests/keys/ecdsakey" %topdir))
  73. (define %ecdsakey-pub (format #f "~a/tests/keys/ecdsakey.pub" %topdir))
  74. (define %knownhosts (format #f "~a/tests/knownhosts"
  75. (getenv "abs_top_builddir")))
  76. (define %config (format #f "~a/tests/config" %topdir))
  77. ;; Pass the test case NAME as the userdata to the libssh log
  78. (define-syntax test-assert-with-log
  79. (syntax-rules ()
  80. ((_ name body ...)
  81. (test-assert name
  82. (begin
  83. (set-log-userdata! name)
  84. body ...)))))
  85. ;; Ensure that the specific ERROR is raised during the test, check the error
  86. ;; with HANDLER.
  87. (define-syntax test-error-with-log/handler
  88. (syntax-rules ()
  89. ((_ name error expr handler)
  90. (test-assert-with-log name
  91. (catch error
  92. (lambda () expr #f)
  93. handler)))
  94. ((_ name expr handler)
  95. (test-assert-with-log name
  96. (catch #t
  97. (lambda () expr #f)
  98. handler)))))
  99. ;; Ensure that the specific ERROR is raised during the test and the error is
  100. ;; raised with the specified MESSAGE.
  101. (define-syntax-rule (test-error-with-log/= name error expected-message expr)
  102. (test-error-with-log/handler error expr
  103. (lambda (key . args)
  104. (string=? (cadr args) expected-message))))
  105. ;; Ensure that the specific ERROR is raised during the test.
  106. (define-syntax test-error-with-log
  107. (syntax-rules ()
  108. ((_ name error expr)
  109. (test-error-with-log/handler name error expr (const #t)))
  110. ((_ name expr)
  111. (test-error-with-log/handler name expr (const #t)))))
  112. (define-syntax-rule (test-equal-with-log name expected expr)
  113. (test-assert-with-log name
  114. (equal? expr expected)))
  115. (define (start-session-loop session body)
  116. (let session-loop ((msg (server-message-get session)))
  117. (when (and msg (not (eof-object? msg)))
  118. (body msg (message-get-type msg)))
  119. (when (connected? session)
  120. (session-loop (server-message-get session)))))
  121. (define (make-session-for-test)
  122. "Make a session with predefined parameters for a test."
  123. (make-session
  124. #:host %addr
  125. #:port *port*
  126. #:timeout 10 ;seconds
  127. #:user "bob"
  128. #:knownhosts %knownhosts
  129. #:log-verbosity 'rare))
  130. (define (make-server-for-test)
  131. "Make a server with predefined parameters for a test."
  132. (define mtx (make-mutex 'allow-external-unlock))
  133. (lock-mutex mtx)
  134. (dynamic-wind
  135. (const #f)
  136. (lambda ()
  137. ;; FIXME: This hack is aimed to give every server its own unique
  138. ;; port to listen to. Clients will pick up new port number
  139. ;; automatically through global `port' symbol as well.
  140. (set! *port* (get-unused-port))
  141. (let ((s (make-server
  142. #:bindaddr %addr
  143. #:bindport *port*
  144. #:rsakey %rsakey
  145. #:dsakey %dsakey
  146. #:log-verbosity 'rare)))
  147. (server-listen s)
  148. s))
  149. (lambda ()
  150. (unlock-mutex mtx))))
  151. (define (call-with-connected-session proc)
  152. "Call the one-argument procedure PROC with a freshly created and connected
  153. SSH session object, return the result of the procedure call. The session is
  154. disconnected when the PROC is finished."
  155. (let ((session (make-session-for-test)))
  156. (dynamic-wind
  157. (lambda ()
  158. (let ((result (connect! session)))
  159. (unless (equal? result 'ok)
  160. (error "Could not connect to a server" session result))))
  161. (lambda () (proc session))
  162. (lambda () (disconnect! session)))))
  163. ;;; Port helpers.
  164. (define (port-in-use? port-number)
  165. "Return #t if a port with a PORT-NUMBER isn't used, #f otherwise."
  166. (let ((sock (socket PF_INET SOCK_STREAM 0)))
  167. (catch #t
  168. (lambda ()
  169. (bind sock AF_INET INADDR_LOOPBACK port-number)
  170. (close sock)
  171. #f)
  172. (lambda args
  173. (close sock)
  174. #t))))
  175. (define get-unused-port
  176. (let ((port-num 12345)
  177. (mtx (make-mutex 'allow-external-unlock)))
  178. (lambda ()
  179. "Get an unused port number."
  180. (lock-mutex mtx)
  181. (let loop ((num port-num))
  182. (if (port-in-use? num)
  183. (loop (1+ num))
  184. (begin
  185. (set! port-num num)
  186. (unlock-mutex mtx)
  187. num))))))
  188. (set! *port* (get-unused-port))
  189. ;;;
  190. (define (poll port proc)
  191. "Poll a PORT, call a PROC when data is available."
  192. (let p ((ready? #f))
  193. (if ready?
  194. (proc port)
  195. (p (char-ready? port)))))
  196. ;;; Test Servers
  197. (define (start-server-loop server proc)
  198. "Start a SERVER loop, call PROC on incoming messages."
  199. (server-listen server)
  200. (let ((session (server-accept server)))
  201. (server-handle-key-exchange session)
  202. (start-session-loop session
  203. (lambda (msg type)
  204. (proc msg)))
  205. (primitive-exit)))
  206. (define (start-server/dt-test server rwproc)
  207. (start-server-loop server
  208. (lambda (msg)
  209. (case (car (message-get-type msg))
  210. ((request-channel-open)
  211. (let ((channel (message-channel-request-open-reply-accept msg)))
  212. (poll channel rwproc)))
  213. (else
  214. (message-reply-success msg))))))
  215. (define (start-server/exec server)
  216. "Start SERVER for a command execution test."
  217. (start-server-loop server
  218. (let ((channel #f))
  219. (lambda (msg)
  220. (let ((msg-type (message-get-type msg)))
  221. (format-log/scm 'nolog "start-server/exec"
  222. "msg-type: ~a" msg-type)
  223. (case (car msg-type)
  224. ((request-channel-open)
  225. (set! channel (message-channel-request-open-reply-accept msg)))
  226. ((request-channel)
  227. (if (equal? (cadr msg-type) 'channel-request-exec)
  228. (let ((cmd (exec-req:cmd (message-get-req msg))))
  229. (format-log/scm 'nolog "start-server/exec"
  230. "command: ~A" cmd)
  231. (cond
  232. ((string=? cmd "ping")
  233. (write-line "pong" channel)
  234. (channel-send-eof channel))
  235. ((string=? cmd "uname") ; For exit status testing
  236. (write-line "pong" channel)
  237. (message-reply-success msg)
  238. (channel-request-send-exit-status channel 0)
  239. (channel-send-eof channel))
  240. ((string-match "echo '.*" cmd)
  241. (let ((p (open-input-pipe cmd)))
  242. (write-line (read-line p) channel)
  243. (close p)
  244. (message-reply-success msg)
  245. (channel-request-send-exit-status channel 0)
  246. (channel-send-eof channel)))
  247. ((string=? cmd "cat /proc/loadavg")
  248. (write-line "0.01 0.05 0.10 4/1927 242011" channel)
  249. (message-reply-success msg)
  250. (channel-request-send-exit-status channel 0)
  251. (channel-send-eof channel))
  252. (else
  253. (write-line cmd channel)
  254. (message-reply-success msg)
  255. (channel-request-send-exit-status channel 0)
  256. (channel-send-eof channel)))
  257. (message-reply-success msg))
  258. (message-reply-success msg)))
  259. (else
  260. (message-reply-success msg))))))))
  261. (define (start-server/dist-test server)
  262. (server-listen server)
  263. (let ((session (server-accept server)))
  264. (server-handle-key-exchange session)
  265. (let* ((proc (lambda (session message user-data)
  266. (let ((type (message-get-type message))
  267. (req (message-get-req message)))
  268. (format (current-error-port) "global req: type: ~a~%"
  269. type)
  270. (case (cadr type)
  271. ((global-request-tcpip-forward)
  272. (let ((pnum (global-req:port req)))
  273. (format (current-error-port) "global req: port: ~a~%"
  274. pnum)
  275. (message-reply-success message
  276. pnum)))
  277. ((global-request-cancel-tcpip-forward)
  278. (message-reply-success message 1))))))
  279. (callbacks `((user-data . #f)
  280. (global-request-callback . ,proc))))
  281. (session-set! session 'callbacks callbacks))
  282. (start-session-loop session
  283. (lambda (msg type)
  284. (message-reply-success msg)))))
  285. ;;; Tests
  286. (define (format-log/scm level proc-name message . args)
  287. "Format a log MESSAGE, append \"[SCM]\" to a PROC-NAME."
  288. (apply format-log level (string-append "[SCM] " proc-name) message args))
  289. (define (multifork . procs)
  290. "Execute each procedure from PROCS list in a separate process. The last
  291. procedure from PROCS is executed in the main process; return the result of the
  292. main procedure."
  293. (format-log/scm 'nolog "multifork" "procs 1: ~a~%" procs)
  294. (let* ((len (length procs))
  295. (mainproc (car (list-tail procs (- len 1))))
  296. (procs (list-head procs (- len 1)))
  297. (pids (map (lambda (proc)
  298. (let ((pid (primitive-fork)))
  299. (when (zero? pid)
  300. (proc)
  301. (primitive-exit 0))
  302. pid))
  303. procs)))
  304. (format-log/scm 'nolog "multifork" "procs 2: ~a~%" procs)
  305. (format-log/scm 'nolog "multifork" "mainproc: ~a~%" mainproc)
  306. (format-log/scm 'nolog "multifork" "PIDs: ~a~%" pids)
  307. (dynamic-wind
  308. (const #f)
  309. mainproc
  310. (lambda ()
  311. (format-log/scm 'nolog "multifork" "killing spawned processes ...")
  312. (for-each (cut kill <> SIGTERM) pids)
  313. (for-each waitpid pids)))))
  314. (define (run-client-test server-proc client-proc)
  315. "Run a SERVER-PROC in newly created process. The server passed to a
  316. SERVER-PROC as an argument. CLIENT-PROC is expected to be a thunk that should
  317. be executed in the parent process. The procedure returns a result of
  318. CLIENT-PROC call."
  319. (format-log/scm 'nolog "run-client-test" "Making a server ...")
  320. (let ((server (make-server-for-test)))
  321. (format-log/scm 'nolog "run-client-test" "Server: ~a" server)
  322. (format-log/scm 'nolog "run-client-test" "Spawning processes ...")
  323. (multifork
  324. ;; server
  325. (lambda ()
  326. (dynamic-wind
  327. (const #f)
  328. (lambda ()
  329. (format-log/scm 'nolog "run-client-test"
  330. "Server process is up and running")
  331. (set-log-userdata! (string-append (get-log-userdata) " (server)"))
  332. (server-set! server 'log-verbosity 'rare)
  333. (server-proc server)
  334. (format-log/scm 'nolog "run-client-test"
  335. "Server procedure is finished")
  336. (primitive-exit 0))
  337. (lambda ()
  338. (primitive-exit 1))))
  339. ;; client
  340. client-proc)))
  341. ;; Run a client test in a separate process; only a PRED procedure is running
  342. ;; in the main test process:
  343. ;;
  344. ;; test
  345. ;; |
  346. ;; o Fork.
  347. ;; |_______________________________
  348. ;; o \ Fork.
  349. ;; |______________ |
  350. ;; | \ |
  351. ;; | | |
  352. ;; | | |
  353. ;; | | |
  354. ;; | CLIENT-PROC SERVER-PROC
  355. ;; | | |
  356. ;; | o | Bind/listen a socket.
  357. ;; | "hello world" | |
  358. ;; |<--------------| |
  359. ;; o | | Check the result
  360. ;; | | | with a predicate PRED.
  361. ;;
  362. ;; XXX: This procedure contains operations that potentially can block it
  363. ;; forever.
  364. ;;
  365. (define (run-client-test/separate-process server-proc client-proc pred)
  366. "Run a SERVER-PROC and CLIENT-PROC as separate processes. Check the result
  367. returned by a CLIENT-PROC with a predicate PRED."
  368. (let ((server (make-server-for-test))
  369. (sock-path (tmpnam)))
  370. (multifork
  371. ;; Server procedure
  372. (lambda ()
  373. (server-proc server))
  374. ;; Client procedure
  375. (lambda ()
  376. (let ((sock (socket PF_UNIX SOCK_STREAM 0)))
  377. (bind sock AF_UNIX sock-path)
  378. (listen sock 1)
  379. (let ((result (client-proc))
  380. (client (car (accept sock))))
  381. (write-line result client)
  382. (sleep 10)
  383. (close client))))
  384. ;; Main procedure
  385. (lambda ()
  386. (let ((sock (socket PF_UNIX SOCK_STREAM 0)))
  387. ;; XXX: This operation can potentially block the process forever.
  388. (while (not (file-exists? sock-path)))
  389. (format (test-runner-aux-value (test-runner-current))
  390. " client: sock-path: ~a~%" sock-path)
  391. (connect sock AF_UNIX sock-path)
  392. ;; XXX: This too.
  393. (poll sock
  394. (lambda (sock)
  395. (let ((result (read-line sock)))
  396. (close sock)
  397. (pred result)))))))))
  398. (define (run-server-test client-proc server-proc)
  399. "Run a CLIENT-PROC in newly created process. A session is passed to a
  400. CLIENT-PROC as an argument. SERVER-PROC is called with a server as an
  401. argument. The procedure returns a result of SERVER-PROC call."
  402. (let ((server (make-server-for-test))
  403. (session (make-session-for-test)))
  404. (multifork
  405. ;; server
  406. (lambda ()
  407. (dynamic-wind
  408. (const #f)
  409. (lambda ()
  410. (client-proc session))
  411. (lambda ()
  412. (primitive-exit 1))))
  413. ;; client
  414. (lambda ()
  415. (server-proc server)))))
  416. ;;; Logging
  417. (define (make-libssh-log-printer log-file)
  418. "Make a libssh log printer with output to a LOG-FILE. Return the log
  419. printer."
  420. (let ((p (open-output-file log-file)))
  421. (lambda (priority function message userdata)
  422. (format p "[~a, \"~a\", ~a]: ~a~%"
  423. (strftime "%Y-%m-%dT%H:%M:%S%z" (localtime (current-time)))
  424. userdata
  425. priority
  426. message))))
  427. (define (setup-libssh-logging! log-file)
  428. "Setup libssh logging for a test suite with output to a LOG-FILE."
  429. (let ((log-printer (make-libssh-log-printer log-file)))
  430. (set-logging-callback! log-printer)))
  431. (define (setup-error-logging! log-file)
  432. "Setup error logging for a test suite with output to a LOG-FILE."
  433. (set-current-error-port (open-output-file log-file)))
  434. (define (setup-test-suite-logging! test-name)
  435. "Setup error logging for a TEST-SUITE."
  436. (let ((libssh-log-file (string-append test-name "-libssh.log"))
  437. (errors-log-file (string-append test-name "-errors.log")))
  438. (setup-libssh-logging! libssh-log-file)
  439. (setup-error-logging! errors-log-file)))
  440. (define (test-begin-with-log test-name)
  441. (set-log-verbosity! 'functions)
  442. (test-begin test-name)
  443. (setup-test-suite-logging! test-name))
  444. ;;; common.scm ends here