about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2016-10-23 20:26:44 -0700
committerKartik K. Agaram <vc@akkartik.com>2016-10-23 20:34:38 -0700
commit9da067db49bda56fe339264c48bb7c0c227d17f9 (patch)
tree94f0124763081b16bd71115185ffc9c16ab2e544
parent28191d317a46a1aeb3ec2034de2da626c38b6d73 (diff)
downloadmu-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.mu169
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