123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- ;; This file is part of scheme-GNUnet, a partial Scheme port of GNUnet.
- ;; Copyright © 2021, 2022 GNUnet e.V.
- ;;
- ;; scheme-GNUnet is free software: you can redistribute it and/or modify it
- ;; under the terms of the GNU Affero General Public License as published
- ;; by the Free Software Foundation, either version 3 of the License,
- ;; or (at your option) any later version.
- ;;
- ;; scheme-GNUnet is distributed in the hope that it will be useful, but
- ;; WITHOUT ANY WARRANTY; without even the implied warranty of
- ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- ;; Affero General Public License for more details.
- ;;
- ;; You should have received a copy of the GNU Affero General Public License
- ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
- ;;
- ;; SPDX-License-Identifier: AGPL-3.0-or-later
- (define-module (test-network-size))
- (import (gnu gnunet util time)
- (gnu gnunet mq)
- (gnu gnunet mq-impl stream)
- (gnu gnunet mq handler)
- (gnu extractor enum)
- (gnu gnunet message protocols)
- (gnu gnunet config db)
- (gnu gnunet utils cut-syntax)
- (only (rnrs base)
- assert)
- (prefix (gnu gnunet nse client) #{nse:}#)
- (gnu gnunet nse struct)
- (only (gnu gnunet utils bv-slice)
- slice-length make-slice/read-write)
- (only (tests utils) call-with-services/fibers)
- (only (fibers) sleep)
- (gnu gnunet netstruct syntactic)
- (ice-9 match)
- (ice-9 suspendable-ports)
- (ice-9 control)
- (prefix (rnrs hashtables) #{rnrs:}#)
- (srfi srfi-1)
- (srfi srfi-26)
- (srfi srfi-43)
- (srfi srfi-64)
- (fibers conditions)
- (tests utils))
- (test-begin "network-size")
- (define (no-error-handler . e)
- (pk 'e e)
- (error "no error handler"))
- ;; The C implementation of the service requires the client
- ;; to sent this message.
- (test-assert "Client sends msg:nse:start"
- (let* ((start-sent? #f)
- (start-sent-condition (make-condition))
- (server-handlers
- (message-handlers
- (make-message-handler
- (symbol-value message-type msg:nse:start)
- (lambda (p) (p))
- (lambda (s)
- (= (slice-length s) 4))
- (lambda (slice)
- (assert (not start-sent?))
- (set! start-sent? #t)
- (signal-condition! start-sent-condition))))))
- (call-with-services/fibers
- `(("nse" . ,(lambda (port spawn-fiber)
- (define mq (port->message-queue port server-handlers
- no-error-handler
- #:spawn spawn-fiber))
- (values))))
- (lambda (config spawn-fiber)
- (nse:connect config #:spawn spawn-fiber)
- (wait start-sent-condition)
- #t))))
- (define %estimates
- `((0. ,(expt 2.0 0.) 0. 0) ; stddev can theoretically be zero
- (0. ,(expt 2.0 0.) +nan.0 0) ; see <https://bugs.gnunet.org/view.php?id=7021#c18399>
- (0. ,(expt 2.0 0.) +inf.0 0) ; likewise
- (0. ,(expt 2.0 0.) 0.1 0)
- (1. ,(expt 2.0 1.) 0.11 10)
- (2. ,(expt 2.0 2.) 0.111 100)
- (3. ,(expt 2.0 3.) 0.1111 1000)))
- (define (port->nse-client-message-queue port spawn-fiber)
- (define h (message-handlers
- (make-message-handler
- (symbol-value message-type msg:nse:start)
- (lambda (p) (p))
- (lambda (s) (= (slice-length s) 4))
- (lambda (slice) (values)))))
- (port->message-queue port h no-error-handler #:spawn spawn-fiber))
- (define (act-as-the-server port spawn-fiber estimates)
- (define mq
- (port->nse-client-message-queue port spawn-fiber))
- ;; Send the client a few fake estimates.
- ;; This code would be incorrect if there were
- ;; multiple clients!
- (define (send! estimate)
- (define s (make-slice/read-write
- (sizeof /:msg:nse:estimate '())))
- (define-syntax set%!/estimate
- (cut-syntax set%! /:msg:nse:estimate <> s <>))
- ;; Set the headers
- (set%!/estimate '(header size) (sizeof /:msg:nse:estimate '()))
- (set%!/estimate '(header type)
- (value->index
- (symbol-value message-type msg:nse:estimate)))
- ;; Set the data
- (set%!/estimate '(timestamp) (list-ref estimate 3))
- (set%!/estimate '(size-estimate) (list-ref estimate 0))
- (set%!/estimate '(std-deviation) (list-ref estimate 2))
- ;; Send the estimate
- (send-message! mq s))
- (for-each send! %estimates))
- (define (estimate->list estimate)
- "Represent ESTIMATE as a list that can be compared with equal?."
- `(,(nse:estimate:logarithmic-number-peers estimate)
- ,(nse:estimate:number-peers estimate)
- ,(nse:estimate:standard-deviation estimate)
- ,(nse:estimate:timestamp estimate)))
- (define protected-against-gc)
- (test-equal "Client calls call-back (and sets estimates) in-order"
- (list %estimates %estimates)
- (call-with-services/fibers
- `(("nse" . ,(lambda (port spawn-fiber)
- ;; Make sure that the GC doesn't cause buffered messages
- ;; to be discarded.
- (set! protected-against-gc port)
- (act-as-the-server port spawn-fiber %estimates))))
- (lambda (config spawn-fiber)
- (define estimates/update/reverse '())
- (define estimates/poll/reverse '())
- (define connected? #f)
- (define done (make-condition))
- (define (updated estimate)
- (assert connected?)
- (assert (nse:estimate? estimate))
- (set! estimates/update/reverse
- (cons (estimate->list estimate) estimates/update/reverse))
- (set! estimates/poll/reverse
- (cons (estimate->list (nse:estimate server))
- estimates/poll/reverse))
- (when (= (length estimates/update/reverse)
- (length %estimates))
- (signal-condition! done))
- (when (> (length estimates/update/reverse)
- (length %estimates))
- (error "too many estimates!")))
- (define (connected)
- (assert (not connected?))
- (set! connected? #t))
- (define server
- (nse:connect config #:connected connected #:updated updated
- #:spawn spawn-fiber))
- (wait done)
- (assert connected?)
- (list (reverse estimates/update/reverse)
- (reverse estimates/poll/reverse)))))
- ;; See <https://notabug.org/maximed/scheme-gnunet/issues/4>.
- ;; Only the last estimate is tested.
- (test-assert "likewise, without 'updated' or 'connected' (issue 4)"
- (call-with-services/fibers
- `(("nse" . ,(lambda (port spawn-fiber)
- (set! protected-against-gc port)
- (act-as-the-server port spawn-fiber %estimates))))
- (lambda (config spawn-fiber)
- (define server
- (nse:connect config #:spawn spawn-fiber))
- (let loop ((time-delta 0))
- (unless (equal? (and=> (nse:estimate server) estimate->list)
- (last %estimates))
- (sleep (/ time-delta time-unit:second))
- (loop (standard-back-off time-delta))))
- #t)))
- (test-assert "notify disconnected after end-of-file, after 'connected'"
- (call-with-services/fibers
- `(("nse" . ,(lambda (port spawn-fiber)
- (close-port port))))
- (lambda (config spawn-fiber)
- (define disconnected? #f)
- (define connected? #f)
- (define c (make-condition))
- (define (connected)
- (set! connected? #t))
- (define (disconnected)
- (assert connected?)
- ;; Because (gnu gnunet nse client) automatically reconnects,
- ;; the following commented-out assertion can be false.
- #;(assert (not disconnected?))
- (set! disconnected? #t)
- (signal-condition! c))
- (define server
- (nse:connect config #:spawn spawn-fiber #:connected connected
- #:disconnected disconnected))
- (wait c)
- ;; Give (gnu gnunet nse client) a chance to (incorrectly) call
- ;; disconnected again.
- (sleep 0.001)
- #t)))
- (define forever (make-condition))
- (test-assert "reconnects"
- (let ((n 9)
- (too-many? #f)
- (done (make-condition)))
- (call-with-services/fibers
- `(("nse" . ,(lambda (port spawn-fiber)
- (if (> n 0)
- (begin
- (set! n (- n 1))
- (close-port port))
- (wait forever)))))
- (lambda (config spawn-fiber)
- (define disconnected? #f)
- (define connected? #f)
- (define connected-again (make-condition))
- (define disconnect-count 0)
- (define (connected)
- (match (cons disconnected? connected?)
- ((#t . #f)
- (set! disconnected? #f)
- (set! connected? #t)
- (when (= disconnect-count 9)
- (signal-condition! connected-again))
- (values))
- ((#t . #t) (error "impossible"))
- ((#f . #f)
- (set! connected? #t)
- (values)) ; first connect
- ((#f . #t) (error "doubly connected"))))
- (define (disconnected)
- (match (cons connected? disconnected?)
- ((#t . #f)
- (set! connected? #f)
- (set! disconnected? #t)
- (set! disconnect-count (+ 1 disconnect-count))
- (cond
- ((= disconnect-count 9)
- (signal-condition! done))
- ((> disconnect-count 9)
- (set! too-many? #t)
- (error "too many disconnects")))
- (values))
- ((#t . #t) (error "impossible"))
- ((#f . #f)
- (error "disconnected before connecting"))
- ((#f . #t)
- (error "doubly disconnected"))))
- (define server
- (nse:connect config #:spawn spawn-fiber #:connected connected
- #:disconnected disconnected))
- (wait done)
- (assert (not too-many?))
- ;; We used to do (sleep 0.01) here but this was
- ;; (rarely) insufficient.
- (wait connected-again)
- (assert connected?)
- #t))))
- (test-assert "close, not connected --> all fibers stop, no callbacks called"
- (close-not-connected-no-fallbacks
- "nse" nse:connect nse:disconnect!
- #:rest (list #:disconnected #{don't-call-me}#)))
- (test-assert "close, connected --> all fibers stop, two callbacks called"
- (call-with-spawner/wait
- (lambda (spawn)
- (call-with-temporary-directory
- (lambda (somewhere)
- (define where (in-vicinity somewhere "sock.et"))
- (define config (trivial-service-config "nse" where))
- (define (#{don't-call-me}# . rest)
- (error "oops ~a" rest))
- (define connected? #f)
- (define disconnected? #f)
- (define connected-cond (make-condition))
- (define disconnected-cond (make-condition))
- (define (connected)
- (assert (not connected?))
- (set! connected? #t)
- (signal-condition! connected-cond))
- (define done (make-condition))
- (define (disconnected)
- (assert (not disconnected?))
- (assert connected?)
- (signal-condition! disconnected-cond)
- (set! disconnected? #t))
- (define server (nse:connect config #:spawn spawn
- #:connected connected
- #:disconnected disconnected
- #:updated #{don't-call-me}#))
- (define listening (socket AF_UNIX SOCK_STREAM 0))
- (make-nonblocking! listening)
- (bind listening AF_UNIX where)
- (listen listening 1)
- (define connection (accept listening))
- (wait connected-cond)
- (nse:disconnect! server)
- (wait disconnected-cond)
- (define old-waiter (current-read-waiter))
- (sleep 0.01) ;; give the NSE client a chance to accidentally connect
- (let/ec ec
- (parameterize ((current-read-waiter
- (lambda (p)
- (if (eq? p listening)
- (ec)
- (old-waiter p)))))
- (set! connection (accept listening))
- (error "client tried to connect again")))
- #t)))
- ;; call-with-spawner/wait is more reliable without parallelism
- #:parallelism 1))
- (test-end "network-size")
|