diff options
author | Kartik K. Agaram <vc@akkartik.com> | 2016-10-23 20:26:44 -0700 |
---|---|---|
committer | Kartik K. Agaram <vc@akkartik.com> | 2016-10-23 20:34:38 -0700 |
commit | 9da067db49bda56fe339264c48bb7c0c227d17f9 (patch) | |
tree | 94f0124763081b16bd71115185ffc9c16ab2e544 | |
parent | 28191d317a46a1aeb3ec2034de2da626c38b6d73 (diff) | |
download | mu-9da067db49bda56fe339264c48bb7c0c227d17f9.tar.gz |
3572 - new way to write server tests
This time we're opening real sockets so we might run into issues on machines with firewalls. Macs for example flash up a dialog warning people about a port being opened, though it shuts down immediately if the test passes. On the flip side, the test has greater verisimilitude. You don't really need fakes except when you want to pin down the environment a test runs in. The only way this test might be flaky is on a machine that has lots of sockets open (so the random port opened by the test is in use for something else). That and the firewall concern above. Hmm.
-rw-r--r-- | 092socket.mu | 169 |
1 files changed, 43 insertions, 126 deletions
diff --git a/092socket.mu b/092socket.mu index b1c08b6a..c6418c8d 100644 --- a/092socket.mu +++ b/092socket.mu @@ -1,70 +1,62 @@ -# Wrappers around socket primitives that take a 'local-network' object and are -# thus easier to test. -# -# The current semantics of fake port-connections don't match UNIX socket ones, -# but we'll improve them as we learn more. +# Wrappers around socket primitives that are easier to test. -container local-network [ - data:&:@:port-connection -] - -# Port connections represent connections to ports on localhost. -# Before passing a local-network object to network functions -# `start-reading-socket` and `start-writing-socket`, add port-connections to -# the local-network. -# -# For reading, `receive-from-socket` will check for a -# port-connection on the port parameter that's been passed in. If there's -# no port-connection for that port, it will return nothing and log an error. -# If there is a port-connection for that port, it will transmit the contents -# to the provided sink. -# -# For writing, `start-writing-socket` returns a sink connecting the -# caller to the socket on the passed-in port. -container port-connection [ - port:num - contents:text -] - -def new-port-connection port:num, contents:text -> p:&:port-connection [ +# To test server operations, just run a real client against localhost. +scenario example-server-test [ local-scope - load-ingredients - p:&:port-connection <- new port-connection:type - *p <- merge port, contents + # test server without a fake on a random (real) port + # that way repeatedly running the test will give ports time to timeout and + # close before reusing them + make-random-nondeterministic + port:num <- random-in-range 0/real-random-numbers, 8000, 8100 + run [ + socket:num <- $open-server-socket port + assert socket, [ +F - example-server-test: $open-server-socket failed] + handler-routine:number <- start-running serve-one-request socket, example-handler + ] + source:&:source:char <- start-reading-from-network 0/real-resources, [localhost/], port + response:text <- drain source + 10:@:char/raw <- copy *response + memory-should-contain [ + 10:array:character <- [abc] + ] ] - -def new-fake-network -> n:&:local-network [ +# helper just for this scenario +def example-handler query:text -> response:text [ local-scope load-ingredients - n:&:local-network <- new local-network:type - local-network-ports:&:@:port-connection <- new port-connection:type, 0 - *n <- put *n, data:offset, local-network-ports + reply [abc] ] -scenario write-to-fake-socket [ +type request-handler = (recipe text -> text) + +def serve-one-request socket:num, request-handler:request-handler [ local-scope - single-port-network:&:local-network <- new-fake-network - sink:&:sink:char, writer:num/routine <- start-writing-socket single-port-network, 8080 - sink <- write sink, 120/x - close sink - wait-for-routine writer - tested-port-connections:&:@:port-connection <- get *single-port-network, data:offset - tested-port-connection:port-connection <- index *tested-port-connections, 0 - contents:text <- get tested-port-connection, contents:offset - 10:@:char/raw <- copy *contents - memory-should-contain [ - 10:array:character <- [x] - ] + load-ingredients + session:num <- $accept socket + assert session, [ +F - example-server-test: $accept failed] + contents:&:source:char, sink:&:sink:char <- new-channel 30 + sink <- start-running receive-from-socket session, sink + query:text <- drain contents + response:text <- call request-handler, query + write-to-socket session, response + $close-socket session ] def start-reading-from-network resources:&:resources, uri:text -> contents:&:source:char [ local-scope load-ingredients { + port:num, port-found?:boolean <- next-ingredient + break-if port-found? + port <- copy 80/http-port + } + { break-if resources # real network host:text, path:text <- split-at uri, 47/slash - socket:num <- $open-client-socket host, 80/http-port + socket:num <- $open-client-socket host, port assert socket, [contents] req:text <- interpolate [GET _ HTTP/1.1], path request-socket socket, req @@ -72,23 +64,7 @@ def start-reading-from-network resources:&:resources, uri:text -> contents:&:sou start-running receive-from-socket socket, sink return } - # fake network -#? i:num <- copy 0 -#? data:&:@:resource <- get *fs, data:offset -#? len:num <- length *data -#? { -#? done?:bool <- greater-or-equal i, len -#? break-if done? -#? tmp:resource <- index *data, i -#? i <- add i, 1 -#? curr-filename:text <- get tmp, name:offset -#? found?:bool <- equal filename, curr-filename -#? loop-unless found? -#? contents:&:source:char, sink:&:sink:char <- new-channel 30 -#? curr-contents:text <- get tmp, contents:offset -#? start-running transmit-from-text curr-contents, sink -#? return -#? } + # todo: fake network return 0/not-found ] @@ -103,65 +79,6 @@ def request-socket socket:num, s:text -> socket:num [ $write-to-socket socket, 10/lf ] -def start-writing-socket network:&:local-network, port:num -> sink:&:sink:char, routine-id:num [ - local-scope - load-ingredients - source:&:source:char, sink:&:sink:char <- new-channel 30 - { - break-if network - socket:num <- $open-server-socket port - session:num <- $accept socket - # TODO Create channel implementation of write-to-socket. - return sink, 0/routine-id - } - # fake network - routine-id <- start-running transmit-to-fake-socket network, port, source -] - -def transmit-to-fake-socket network:&:local-network, port:num, source:&:source:char -> network:&:local-network, source:&:source:char [ - local-scope - load-ingredients - # compute new port connection contents - buf:&:buffer <- new-buffer 30 - { - c:char, done?:bool, source <- read source - break-unless c - buf <- append buf, c - break-if done? - loop - } - contents:text <- buffer-to-array buf - new-port-connection:&:port-connection <- new-port-connection port, contents - # Got the contents of the channel, time to write to fake port. - i:num <- copy 0 - port-connections:&:@:port-connection <- get *network, data:offset - len:num <- length *port-connections - { - done?:bool <- greater-or-equal i, len - break-if done? - current:port-connection <- index *port-connections, i - current-port:num <- get current, port:offset - ports-match?:bool <- equal current-port, port - i <- add i, 1 - loop-unless ports-match? - # Found an existing connection on this port, overwrite. - put-index *port-connections, i, *new-port-connection - reply - } - # Couldn't find an existing connection on this port, initialize a new one. - new-len:num <- add len, 1 - new-port-connections:&:@:port-connection <- new port-connection:type, new-len - put *network, data:offset, new-port-connections - i:num <- copy 0 - { - done?:bool <- greater-or-equal i, len - break-if done? - tmp:port-connection <- index *port-connections, i - put-index *new-port-connections, i, tmp - } - put-index *new-port-connections, len, *new-port-connection -] - def receive-from-socket socket:num, sink:&:sink:char -> sink:&:sink:char [ local-scope load-ingredients |