diff --git a/examples/game-of-life.py b/examples/game-of-life.py index e2944d1..b5bb860 100644 --- a/examples/game-of-life.py +++ b/examples/game-of-life.py @@ -15,10 +15,8 @@ app = Flask(__name__) -# Board width and height. We assume a square board for now. -# 45 crashes. Viceroy will pass us no more than 1936 bytes of the board. (Or -# maybe the entire URL gets truncated at 1965b.) If you change this, change the -# f"{i:010000b}" format string below to be the new value squared. +# Board width and height. We assume a square board for now. If you change this, +# change the f"{i:010000b}" format string below to be the new value squared. WIDTH = HEIGHT = 50 @@ -26,6 +24,10 @@ def decompressed_board(compressed: str) -> str: """Decompress the board representation sent from JS, returning a B&W board string ("10011011"...). + Viceroy will pass us no more than 1936 bytes of the board. (Or maybe the + entire URL gets truncated at 1965b.) This overcomes that (for the board + sizes we're interested in). + :arg compressed: A urlsafe_b64encode()d representation of the bit-packed black-and-white board. (We don't need color info in order to compute the next board.) @@ -41,9 +43,8 @@ def decompressed_board(compressed: str) -> str: @app.route("/board/") def board(compressed_board: str): - """Return the next frame of the Game Of Life, given the current one. If a "" - board is given, return a new random board. - """ + """Return the next frame of the Game Of Life, given the current one. If "none" + is given instead, return a new random board.""" cells = decompressed_board(compressed_board) # Random board on start: diff --git a/fastly_compute/wsgi.py b/fastly_compute/wsgi.py index 13717fd..37e3934 100644 --- a/fastly_compute/wsgi.py +++ b/fastly_compute/wsgi.py @@ -208,6 +208,8 @@ def handle(self, request: Any, body: Any) -> None: # Something went wrong. raise else: + if not result: + break request, body = result serve_wsgi_request( request, diff --git a/tests/test_app.py b/tests/test_bottle_example.py similarity index 95% rename from tests/test_app.py rename to tests/test_bottle_example.py index 0bf4d30..ae48203 100644 --- a/tests/test_app.py +++ b/tests/test_bottle_example.py @@ -1,9 +1,9 @@ -"""Tests for the Fastly Compute Python service (app.wasm functionality).""" - from fastly_compute.testing import ViceroyTestBase -class TestFastlyComputeApp(ViceroyTestBase): +class TestBottleApp(ViceroyTestBase): + """Tests for the Bottle framework example""" + def test_hello_endpoint(self): """Test the hello endpoint returns expected content.""" response = self.get("/hello/test") diff --git a/tests/test_flask_example.py b/tests/test_flask_example.py index 3dd19b7..aaa2411 100644 --- a/tests/test_flask_example.py +++ b/tests/test_flask_example.py @@ -1,10 +1,10 @@ -"""Tests for the Flask example application.""" +"""Tests for the Flask example application""" from fastly_compute.testing import ViceroyTestBase class TestFlaskApp(ViceroyTestBase): - """Integration tests for the Flask example application.""" + """Integration tests for the Flask example application""" WASM_FILE = "build/flask-app.composed.wasm" diff --git a/tests/test_game_of_life_example.py b/tests/test_game_of_life_example.py new file mode 100644 index 0000000..deeaf8d --- /dev/null +++ b/tests/test_game_of_life_example.py @@ -0,0 +1,50 @@ +import re +from time import sleep + +from fastly_compute.testing import ViceroyTestBase + + +class TestGameOfLife(ViceroyTestBase): + """Integration tests for the Game Of Life example""" + + WASM_FILE = "build/game-of-life.composed.wasm" + + def test_root(self): + """Show that the page full of JS at least loads.""" + response = self.get("/") + assert response.status_code == 200 + assert "async function startAnimation" in response.text + + def test_random_board(self): + """Show that random boards are generated for the first frame.""" + response = self.get("/board/none") + assert response.status_code == 200 + assert re.match(r"[01]+", response.text) + + def test_evolved_board(self): + """Show that a new board is correctly computed from an old one.""" + response = self.get( + "/board/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAAAAAAAAAAEAAAAAAABAAAAAHAAQAAAAAAAAAAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAKAAAAAAAEgAAAAAAAwAADAAAAAAAN6AAAAAACscAAAAAAoOAGAAAAGACBgAAAAAAgAAAAAAAgAAAAAAAsAAAAAAAIAwAAAAAAgSAAAAAAAEgAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAAQAAAAAAAOADAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + ) + assert response.status_code == 200 + assert ( + response.text + == "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000001210000000000000000000000000000000002000000000000000000000000000000000000000000000000010000000000010000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000202000000000000000000000000000000000000000000000020020000000000000000000000000000000000000000000000022000000000000000000010010000000000000000000000000000000000000000000231000300110000000000000000000000000000000000000003030001100020000000000000000000000000000000000000020300000200100000000033000000000000000000000000000032000000100000000000330000000000000000000000000000000000000000100000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000001100000022000000000000000000000000000000000000000000000002002000000000000000000000000000000000000000000000020020000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000003200000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000232000000000003300000000000000000000000000000000000100000000000033000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ) + + def test_reuse_sandboxes(self): + """Make sure attempting to issue multiple requests to a single sandbox doesn't crash. + + This does not test whether a single sandbox actually served multiple + requests, though it tries to provoke that. Still, 2 sandboxes could have + served 1 request each. + """ + response = self.get("/board/none") + assert response.status_code == 200 + response = self.get("/board/none") + assert response.status_code == 200 + + # Reports about crashers in the post-response code come *after* the + # request has succeeded. And it seems to take awhile to show up. + sleep(0.5) # .3 is not enough. + assert "WebAssembly trapped" not in "\n".join(self.server.output_lines)