From 9a81d7460fdb16f4e77712e5381d9db8781f5ae6 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Sat, 22 Oct 2016 16:56:07 -0700 Subject: 3561 --- 092socket.mu | 230 ++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 125 insertions(+), 105 deletions(-) (limited to '092socket.mu') diff --git a/092socket.mu b/092socket.mu index 6a5c427a..1fd2586b 100644 --- a/092socket.mu +++ b/092socket.mu @@ -1,69 +1,87 @@ -# Wrappers around socket primitives that take a 'local-network' object and are -# thus easier to test. +# Wrappers around socket primitives that are 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. - -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. +# To test client operations, use `assume-resources` with a filename that +# begins with a hostname. (Filenames starting with '/' are assumed to be +# local.) # -# 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 -] +# To test server operations, just run a real client against localhost. -def new-port-connection port:num, contents:text -> p:&:port-connection [ +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 + $print [server socket: ], socket, 10/newline + assert socket, [ +F - example-server-test: $open-server-socket failed] + $print [starting up server routine], 10/newline + handler-routine:number <- start-running serve-one-request socket, example-handler + ] + $print [starting to read from port ], port, 10/newline + 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 [ +#? scenario example-client-test [ +#? local-scope +#? assume-resources [ +#? [example.com/] -> [abc] +#? ] +#? run [ +#? source:&:source:char <- start-reading-from-network resources, [example.com], [/] +#? ] +#? contents:text <- drain source +#? 10:@:char/raw <- copy *contents +#? memory-should-contain [ +#? 10:address:character <- [abc] +#? ] +#? ] + +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 + $print [server session socket: ], session, 10/newline + 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, host:text, path:text -> contents:&:source:char [ local-scope load-ingredients + $print [running start-reading-from-network], 10/newline + { + port:num, port-found?:boolean <- next-ingredient + break-if port-found? + port <- copy 80/http-port + } { break-if resources # real network - socket:num <- $open-client-socket host, 80/http-port + socket:num <- $open-client-socket host, port + $print [client socket: ], socket, 10/newline assert socket, [contents] req:text <- interpolate [GET _ HTTP/1.1], path request-socket socket, req @@ -102,70 +120,72 @@ 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 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 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 { + $print [read-from-socket ], socket, 10/newline req:text, eof?:bool <- $read-from-socket socket, 4096/bytes + loop-unless req bytes-read:num <- length *req i:num <- copy 0 { -- cgit 1.4.1-2-gfad0