Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic integration tests for pypy, php and python plugin #2675

Merged
merged 3 commits into from
Sep 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,18 @@ jobs:
run: make unittests

test:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Install dependencies
run: |
sudo apt update -qq
sudo apt install --no-install-recommends -qqyf \
libpcre2-dev libjansson-dev libcap2-dev
libpcre2-dev libjansson-dev libcap2-dev \
php-dev libphp-embed libargon2-dev libsodium-dev \
pypy3
- uses: actions/checkout@v4
- name: Set env
run: echo "PROFILE=integration-tests" >> $GITHUB_ENV
- name: Run integration tests
run: make all tests

Expand Down
4 changes: 4 additions & 0 deletions buildconf/integration-tests.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[uwsgi]
inherit = base
main_plugin =
plugins = python,php,pypy
3 changes: 3 additions & 0 deletions t/php/config.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[uwsgi]
http-socket = :8080
http-socket-modifier1 = 14
# required for php
need-app = false
plugins = php

cache2 = name=session,items=1000,store=/tmp/uwsgi-session-cache,bitmap=1

Expand Down
7 changes: 7 additions & 0 deletions t/pypy/config.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[uwsgi]
plugin = pypy
need-app = false
pypy-lib = /usr/lib/x86_64-linux-gnu/libpypy3-c.so
pypy-home = /usr/lib/pypy3
pypy-setup = plugins/pypy/pypy_setup.py
pypy-wsgi-file = t/python/helloapp.py
3 changes: 3 additions & 0 deletions t/python/helloapp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def application(env, start_response):
start_response("200 OK", [("Content-Type", "text/html")])
return [b"Hello World"]
170 changes: 116 additions & 54 deletions t/runner
Original file line number Diff line number Diff line change
@@ -1,82 +1,144 @@
#!/usr/bin/python3
#
# This test suite runner runs some integration tests for uwsgi, that is
# each test launches a test server with a specific configuration and
# verifies (usually using a HTTP request) that this test server behaves as
# expected.
#
# buildconf/integration-tests.ini holds the build configuration for this
# to run fine.


import os
import requests
import signal
import socket
import subprocess
import sys
import time
import unittest


TESTS_DIR = os.path.dirname(__file__)
UWSGI_BINARY = os.getenv("UWSGI_BINARY", os.path.join(TESTS_DIR, "..", "uwsgi"))
UWSGI_PLUGINS = os.getenv("UWSGI_PLUGINS", "all").split(" ")
UWSGI_ADDR = "127.0.0.1"
UWSGI_PORT = 8000
UWSGI_HTTP = f"{UWSGI_ADDR}:{UWSGI_PORT}"


class BaseTest:
"""
Container class to avoid base test being run
"""

class UwsgiServerTest(unittest.TestCase):
"""
Test case with a server instance available on a socket for requests
"""

@classmethod
def uwsgi_ready(cls):
try:
s = socket.socket()
s.connect(
(
UWSGI_ADDR,
UWSGI_PORT,
)
def plugins_available(plugins):
available = False
if "all" in UWSGI_PLUGINS:
available = True
else:
available = all([plugin in UWSGI_PLUGINS for plugin in plugins])
return available, f"{plugins} plugins not available but required for this test case"


class UwsgiTest(unittest.TestCase):

def start_server(self, args):
self.testserver = subprocess.Popen(
[UWSGI_BINARY] + args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
)

def uwsgi_ready(self):
try:
s = socket.socket()
s.connect(
(
UWSGI_ADDR,
UWSGI_PORT,
)
except socket.error:
return False
else:
return True
finally:
s.close()

@classmethod
def setUpClass(cls):
# launch server
cls.testserver = subprocess.Popen(
[UWSGI_BINARY, "--http-socket", UWSGI_HTTP] + cls.ARGS
)

# ensure server is ready
retries = 10
while not cls.uwsgi_ready() and retries > 0:
time.sleep(0.1)
retries = retries - 1
if retries == 0:
raise RuntimeError("uwsgi test server is not available")

@classmethod
def tearDownClass(cls):
cls.testserver.send_signal(signal.SIGTERM)
cls.testserver.wait()


class StaticTest(BaseTest.UwsgiServerTest):

ARGS = [
"--plugin",
"python3", # provide a request plugin if no embedded request plugin
os.path.join(TESTS_DIR, "static", "config.ini"),
]

except socket.error:
return False
else:
return True
finally:
s.close()

def start_listen_server(self, args):
self.start_server(["--http-socket", UWSGI_HTTP] + args)

# ensure server is ready
retries = 10
while not self.uwsgi_ready() and retries > 0:
time.sleep(0.1)
retries = retries - 1
if retries == 0:
raise RuntimeError("uwsgi test server is not available")

def tearDown(self):
if hasattr(self._outcome, "errors"):
# Python 3.4 - 3.10 (These two methods have no side effects)
result = self.defaultTestResult()
self._feedErrorsToResult(result, self._outcome.errors)
else:
# Python 3.11+
result = self._outcome.result
ok = not (result.errors + result.failures)

self.testserver.send_signal(signal.SIGTERM)
if not ok:
print(self.testserver.stdout.read(), file=sys.stderr)

self.testserver.wait()
self.testserver.stdout.close()

@unittest.skipUnless(*plugins_available(["python"]))
def test_static_expires(self):
self.start_listen_server(
[
"--plugin",
"python", # provide a request plugin
os.path.join(TESTS_DIR, "static", "config.ini"),
]
)

with requests.get(f"http://{UWSGI_HTTP}/foobar/config.ini") as r:
self.assertTrue("Expires" in r.headers)

@unittest.skipUnless(*plugins_available(["python"]))
def test_python3_helloworld(self):
self.start_listen_server(
[
"--plugin",
"python",
"--wsgi-file",
os.path.join(TESTS_DIR, "python", "helloapp.py"),
]
)

with requests.get(f"http://{UWSGI_HTTP}/") as r:
self.assertEqual(r.text, "Hello World")

@unittest.skipUnless(*plugins_available(["pypy"]))
def test_pypy3_helloworld(self):
self.start_listen_server(
[
os.path.join(TESTS_DIR, "pypy", "config.ini"),
]
)

with requests.get(f"http://{UWSGI_HTTP}/") as r:
self.assertEqual(r.text, "Hello World")

@unittest.skipUnless(*plugins_available(["php"]))
def test_php_session(self):
self.start_listen_server(
[
os.path.join(TESTS_DIR, "php", "config.ini"),
]
)

with requests.get(f"http://{UWSGI_HTTP}/test.php") as r:
self.assertEqual(r.text, "PASS\n")


if __name__ == "__main__":
unittest.main()