summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
authoroxpa <iippolitov@gmail.com>2024-09-17 14:21:10 +0100
committeroxpa <iippolitov@gmail.com>2024-09-17 14:21:10 +0100
commit2417826d8bebf921ee1be102ef8ce702f0683d66 (patch)
tree76d29a1705415ed7368870826dbb2f04942ee794 /test
parent0e79d961bb1ea68674961da1703ffedb1ddf6e43 (diff)
parent24ed91f40634372d99f67f0e4e3c2ac0abde81bd (diff)
downloadunit-2417826d8bebf921ee1be102ef8ce702f0683d66.tar.gz
unit-2417826d8bebf921ee1be102ef8ce702f0683d66.tar.bz2
Merge tag '1.33.0' into packaging.
Unit 1.33.0 release.
Diffstat (limited to '')
-rw-r--r--test/conftest.py3
-rw-r--r--test/python/factory/wsgi.py23
-rw-r--r--test/test_chunked.py188
-rw-r--r--test/test_python_application.py40
-rw-r--r--test/test_python_factory.py140
-rw-r--r--test/test_response_headers.py13
-rw-r--r--test/test_routing.py72
-rw-r--r--test/test_variables.py101
-rw-r--r--test/test_wasm-wasi-component.py18
-rw-r--r--test/unit/applications/lang/java.py2
-rw-r--r--test/unit/applications/lang/wasm_component.py63
-rw-r--r--test/unit/applications/tls.py6
-rw-r--r--test/unit/check/cargo_component.py4
-rw-r--r--test/unit/check/discover_available.py4
-rw-r--r--test/unit/http.py2
-rw-r--r--test/unit/status.py13
-rw-r--r--test/wasm_component/hello_world/Cargo.lock34
-rw-r--r--test/wasm_component/hello_world/Cargo.toml18
-rw-r--r--test/wasm_component/hello_world/src/bindings.rs109
-rw-r--r--test/wasm_component/hello_world/src/lib.rs31
-rw-r--r--test/wasm_component/hello_world/wit/world.wit6
21 files changed, 872 insertions, 18 deletions
diff --git a/test/conftest.py b/test/conftest.py
index 2fe4d8dc..91c59e17 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -429,7 +429,8 @@ def _clear_temp_dir():
temporary_dir = unit_instance['temp_dir']
if is_findmnt and not waitforunmount(temporary_dir, timeout=600):
- sys.exit('Could not unmount filesystems in tmpdir ({temporary_dir}).')
+ Log.print_log()
+ sys.exit(f'Could not unmount filesystems in tmpdir ({temporary_dir}).')
for item in Path(temporary_dir).iterdir():
if item.name not in [
diff --git a/test/python/factory/wsgi.py b/test/python/factory/wsgi.py
new file mode 100644
index 00000000..8ad4887b
--- /dev/null
+++ b/test/python/factory/wsgi.py
@@ -0,0 +1,23 @@
+def wsgi_a(env, start_response):
+ start_response("200", [("Content-Length", "1")])
+ return [b"1"]
+
+
+def wsgi_b(env, start_response):
+ start_response("200", [("Content-Length", "1")])
+ return [b"2"]
+
+
+def wsgi_a_factory():
+ return wsgi_a
+
+
+def wsgi_b_factory():
+ return wsgi_b
+
+
+wsgi_invalid_callable = None
+
+
+def wsgi_factory_returning_invalid_callable():
+ return wsgi_invalid_callable
diff --git a/test/test_chunked.py b/test/test_chunked.py
new file mode 100644
index 00000000..caa26f7e
--- /dev/null
+++ b/test/test_chunked.py
@@ -0,0 +1,188 @@
+import re
+
+import pytest
+from unit.applications.lang.python import ApplicationPython
+
+prerequisites = {'modules': {'python': 'any'}}
+
+client = ApplicationPython()
+
+
+@pytest.fixture(autouse=True)
+def setup_method_fixture():
+ client.load('mirror')
+
+ assert 'success' in client.conf(
+ {"http": {"chunked_transform": True}}, 'settings'
+ )
+
+
+def test_chunked():
+ def chunks(chunks=[]):
+ body = ''
+
+ for c in chunks:
+ body = f'{body}{len(c):x}\r\n{c}\r\n'
+
+ resp = client.get(
+ headers={
+ 'Host': 'localhost',
+ 'Connection': 'close',
+ 'Transfer-Encoding': 'chunked',
+ },
+ body=f'{body}0\r\n\r\n',
+ )
+
+ expect_body = ''.join(chunks)
+
+ assert resp['status'] == 200
+ assert resp['headers']['Content-Length'] == str(len(expect_body))
+ assert resp['body'] == expect_body
+
+ chunks()
+ chunks(['1'])
+ chunks(['0123456789'])
+ chunks(['0123456789' * 128])
+ chunks(['0123456789' * 512])
+ chunks(['0123456789' * 128, '1', '1', '0123456789' * 128, '1'])
+
+
+def test_chunked_pipeline():
+ sock = client.get(
+ no_recv=True,
+ headers={
+ 'Host': 'localhost',
+ 'Transfer-Encoding': 'chunked',
+ },
+ body='1\r\n$\r\n0\r\n\r\n',
+ )
+
+ resp = client.get(
+ sock=sock,
+ headers={
+ 'Host': 'localhost',
+ 'Transfer-Encoding': 'chunked',
+ 'Connection': 'close',
+ },
+ body='1\r\n%\r\n0\r\n\r\n',
+ raw_resp=True,
+ )
+
+ assert len(re.findall('200 OK', resp)) == 2
+ assert len(re.findall('Content-Length: 1', resp)) == 2
+ assert len(re.findall('$', resp)) == 1
+ assert len(re.findall('%', resp)) == 1
+
+
+def test_chunked_max_body_size():
+ assert 'success' in client.conf(
+ {'max_body_size': 1024, 'chunked_transform': True}, 'settings/http'
+ )
+
+ body = f'{2048:x}\r\n{"x" * 2048}\r\n0\r\n\r\n'
+
+ assert (
+ client.get(
+ headers={
+ 'Host': 'localhost',
+ 'Connection': 'close',
+ 'Transfer-Encoding': 'chunked',
+ },
+ body=body,
+ )['status']
+ == 413
+ )
+
+
+def test_chunked_after_last():
+ resp = client.get(
+ headers={
+ 'Host': 'localhost',
+ 'Connection': 'close',
+ 'Transfer-Encoding': 'chunked',
+ },
+ body='1\r\na\r\n0\r\n\r\n1\r\nb\r\n0\r\n\r\n',
+ )
+
+ assert resp['status'] == 200
+ assert resp['headers']['Content-Length'] == '1'
+ assert resp['body'] == 'a'
+
+
+def test_chunked_transform():
+ assert 'success' in client.conf(
+ {"http": {"chunked_transform": False}}, 'settings'
+ )
+
+ assert (
+ client.get(
+ headers={
+ 'Host': 'localhost',
+ 'Connection': 'close',
+ 'Transfer-Encoding': 'chunked',
+ },
+ body='0\r\n\r\n',
+ )['status']
+ == 411
+ )
+
+
+def test_chunked_invalid():
+ # invalid chunkes
+
+ def check_body(body):
+ assert (
+ client.get(
+ headers={
+ 'Host': 'localhost',
+ 'Connection': 'close',
+ 'Transfer-Encoding': 'chunked',
+ },
+ body=body,
+ )['status']
+ == 400
+ )
+
+ check_body('1\r\nblah\r\n0\r\n\r\n')
+ check_body('1\r\n\r\n1\r\n0\r\n\r\n')
+ check_body('1\r\n1\r\n\r\n0\r\n\r\n')
+
+ # invalid transfer encoding header
+
+ assert (
+ client.get(
+ headers={
+ 'Host': 'localhost',
+ 'Connection': 'close',
+ 'Transfer-Encoding': ['chunked', 'chunked'],
+ },
+ body='0\r\n\r\n',
+ )['status']
+ == 400
+ ), 'two Transfer-Encoding headers'
+
+ assert (
+ client.get(
+ headers={
+ 'Host': 'localhost',
+ 'Connection': 'close',
+ 'Transfer-Encoding': 'chunked',
+ 'Content-Length': '5',
+ },
+ body='0\r\n\r\n',
+ )['status']
+ == 400
+ ), 'Transfer-Encoding and Content-Length'
+
+ assert (
+ client.get(
+ http_10=True,
+ headers={
+ 'Host': 'localhost',
+ 'Connection': 'close',
+ 'Transfer-Encoding': 'chunked',
+ },
+ body='0\r\n\r\n',
+ )['status']
+ == 400
+ ), 'Transfer-Encoding HTTP/1.0'
diff --git a/test/test_python_application.py b/test/test_python_application.py
index 466a59a2..c4803153 100644
--- a/test/test_python_application.py
+++ b/test/test_python_application.py
@@ -10,6 +10,7 @@ import pytest
from packaging import version
from unit.applications.lang.python import ApplicationPython
+from unit.option import option
prerequisites = {'modules': {'python': 'all'}}
@@ -64,6 +65,45 @@ custom-header: BLAH
}, 'headers'
assert resp['body'] == body, 'body'
+ # REQUEST_URI unchanged
+
+ path = f'{option.test_dir}/python/variables'
+ assert 'success' in client.conf(
+ {
+ "listeners": {"*:8080": {"pass": "routes"}},
+ "routes": [
+ {
+ "action": {
+ "rewrite": "/foo",
+ "pass": "applications/variables",
+ }
+ }
+ ],
+ "applications": {
+ "variables": {
+ "type": client.get_application_type(),
+ "processes": {'spare': 0},
+ "path": path,
+ "working_directory": path,
+ "module": "wsgi",
+ }
+ },
+ }
+ )
+
+ resp = client.http(
+ f"""POST /bar HTTP/1.1
+Host: localhost
+Content-Length: 1
+Custom-Header: blah
+Content-Type: text/html
+Connection: close
+
+a""".encode(),
+ raw=True,
+ )
+ assert resp['headers']['Request-Uri'] == '/bar', 'REQUEST_URI unchanged'
+
def test_python_application_query_string():
client.load('query_string')
diff --git a/test/test_python_factory.py b/test/test_python_factory.py
new file mode 100644
index 00000000..d1752c24
--- /dev/null
+++ b/test/test_python_factory.py
@@ -0,0 +1,140 @@
+from unit.applications.lang.python import ApplicationPython
+from unit.option import option
+
+prerequisites = {"modules": {"python": "all"}}
+
+client = ApplicationPython()
+
+
+def test_python_factory_targets():
+ python_dir = f"{option.test_dir}/python"
+
+ assert "success" in client.conf(
+ {
+ "listeners": {
+ "*:8080": {"pass": "applications/targets/1"},
+ "*:8081": {"pass": "applications/targets/2"},
+ "*:8082": {"pass": "applications/targets/factory-1"},
+ "*:8083": {"pass": "applications/targets/factory-2"},
+ },
+ "applications": {
+ "targets": {
+ "type": client.get_application_type(),
+ "working_directory": f"{python_dir}/factory/",
+ "path": f"{python_dir}/factory/",
+ "targets": {
+ "1": {
+ "module": "wsgi",
+ "callable": "wsgi_a",
+ "factory": False,
+ },
+ "2": {
+ "module": "wsgi",
+ "callable": "wsgi_b",
+ "factory": False,
+ },
+ "factory-1": {
+ "module": "wsgi",
+ "callable": "wsgi_a_factory",
+ "factory": True,
+ },
+ "factory-2": {
+ "module": "wsgi",
+ "callable": "wsgi_b_factory",
+ "factory": True,
+ },
+ },
+ }
+ },
+ }
+ )
+
+ resp = client.get(port=8080)
+ assert resp["status"] == 200
+ assert resp["body"] == "1"
+
+ resp = client.get(port=8081)
+ assert resp["status"] == 200
+ assert resp["body"] == "2"
+
+ resp = client.get(port=8082)
+ assert resp["status"] == 200
+ assert resp["body"] == "1"
+
+ resp = client.get(port=8083)
+ assert resp["status"] == 200
+ assert resp["body"] == "2"
+
+
+def test_python_factory_without_targets():
+ python_dir = f"{option.test_dir}/python"
+
+ assert "success" in client.conf(
+ {
+ "listeners": {
+ "*:8080": {"pass": "applications/python-app-factory"},
+ "*:8081": {"pass": "applications/python-app"},
+ },
+ "applications": {
+ "python-app-factory": {
+ "type": client.get_application_type(),
+ "working_directory": f"{python_dir}/factory/",
+ "path": f"{python_dir}/factory/",
+ "module": "wsgi",
+ "callable": "wsgi_a_factory",
+ "factory": True,
+ },
+ "python-app": {
+ "type": client.get_application_type(),
+ "working_directory": f"{python_dir}/factory/",
+ "path": f"{python_dir}/factory/",
+ "module": "wsgi",
+ "callable": "wsgi_b",
+ "factory": False,
+ },
+ },
+ }
+ )
+
+ resp = client.get(port=8080)
+ assert resp["status"] == 200
+ assert resp["body"] == "1"
+
+ resp = client.get(port=8081)
+ assert resp["status"] == 200
+ assert resp["body"] == "2"
+
+
+def test_python_factory_invalid_callable_value(skip_alert):
+ skip_alert(
+ r"failed to apply new conf",
+ r"did not return callable object",
+ r"can not be called to fetch callable",
+ )
+ python_dir = f"{option.test_dir}/python"
+
+ invalid_callable_values = [
+ "wsgi_factory_returning_invalid_callable",
+ "wsgi_invalid_callable",
+ ]
+
+ for callable_value in invalid_callable_values:
+ assert "error" in client.conf(
+ {
+ "listeners": {"*:8080": {"pass": "applications/targets/1"}},
+ "applications": {
+ "targets": {
+ "type": client.get_application_type(),
+ "working_directory": f"{python_dir}/factory/",
+ "path": f"{python_dir}/factory/",
+ "targets": {
+ "1": {
+ "module": "wsgi",
+ "callable": callable_value,
+ "factory": True,
+ },
+ },
+ }
+ },
+ }
+ )
diff --git a/test/test_response_headers.py b/test/test_response_headers.py
index e62c1293..ddc22124 100644
--- a/test/test_response_headers.py
+++ b/test/test_response_headers.py
@@ -163,12 +163,11 @@ def test_response_headers_remove():
def test_response_headers_invalid(skip_alert):
- skip_alert(r'failed to apply new conf')
-
def check_invalid(conf):
- assert 'error' in client.conf(
- conf,
- 'routes/0/action/response_headers',
- )
+ resp = client.conf(conf, 'routes/0/action/response_headers')
+ assert 'error' in resp
+
+ return resp
- check_invalid({"X-Foo": "$u"})
+ resp = check_invalid({"X-Foo": "$u"})
+ assert 'detail' in resp and 'Unknown variable' in resp['detail']
diff --git a/test/test_routing.py b/test/test_routing.py
index 0b6eced2..170f627e 100644
--- a/test/test_routing.py
+++ b/test/test_routing.py
@@ -2009,3 +2009,75 @@ def test_routes_match_destination_proxy():
), 'proxy configure'
assert client.get()['status'] == 200, 'proxy'
+
+
+def set_if(condition):
+ assert 'success' in client.conf(f'"{condition}"', 'routes/0/match/if')
+
+
+def test_routes_match_if():
+
+ def try_if(condition, status):
+ set_if(condition)
+ assert client.get(url=f'/{condition}')['status'] == status
+
+ assert 'success' in client.conf(
+ {
+ "listeners": {"*:8080": {"pass": "routes"}},
+ "routes": [
+ {
+ "match": {"method": "GET"},
+ "action": {"return": 200},
+ }
+ ],
+ "applications": {},
+ }
+ ), 'routing configure'
+
+ # const
+
+ try_if('', 404)
+ try_if('0', 404)
+ try_if('false', 404)
+ try_if('undefined', 404)
+ try_if('!', 200)
+ try_if('!null', 200)
+ try_if('1', 200)
+
+ # variable
+
+ set_if('$arg_foo')
+ assert client.get(url='/bar?bar')['status'] == 404
+ assert client.get(url='/foo_empty?foo')['status'] == 404
+ assert client.get(url='/foo?foo=1')['status'] == 200
+
+ set_if('!$arg_foo')
+ assert client.get(url='/bar?bar')['status'] == 200
+ assert client.get(url='/foo_empty?foo')['status'] == 200
+ assert client.get(url='/foo?foo=1')['status'] == 404
+
+def test_routes_match_if_njs(require):
+ require({'modules': {'njs': 'any'}})
+
+ assert 'success' in client.conf(
+ {
+ "listeners": {"*:8080": {"pass": "routes"}},
+ "routes": [
+ {
+ "match": {"method": "GET"},
+ "action": {"return": 200},
+ }
+ ],
+ "applications": {},
+ }
+ )
+
+ set_if('`${args.foo == \'1\'}`')
+ assert client.get(url='/foo_1?foo=1')['status'] == 200
+ assert client.get(url='/foo_2?foo=2')['status'] == 404
+
+ set_if('!`${args.foo == \'1\'}`')
+ assert client.get(url='/foo_1?foo=1')['status'] == 404
+ assert client.get(url='/foo_2?foo=2')['status'] == 200
+
+ assert 'error' in client.conf('$arg_', 'routes/0/match/if')
diff --git a/test/test_variables.py b/test/test_variables.py
index 9aab8a62..e20a3cd7 100644
--- a/test/test_variables.py
+++ b/test/test_variables.py
@@ -93,7 +93,9 @@ def test_variables_method(search_in_file, wait_for_record):
assert wait_for_record(reg, 'access.log') is not None, 'method POST'
-def test_variables_request_uri(search_in_file, wait_for_record):
+def test_variables_request_uri(
+ findall, search_in_file, temp_dir, wait_for_record
+):
set_format('$request_uri')
def check_request_uri(req_uri):
@@ -108,6 +110,103 @@ def test_variables_request_uri(search_in_file, wait_for_record):
check_request_uri('/4%2A')
check_request_uri('/9?q#a')
+ # $request_uri + proxy
+
+ assert 'success' in client.conf(
+ {
+ "listeners": {
+ "*:8080": {"pass": "routes/a"},
+ "[::1]:8081": {"pass": "routes/b"},
+ },
+ "routes": {
+ "a": [
+ {
+ "action": {
+ "proxy": "http://[::1]:8081",
+ }
+ }
+ ],
+ "b": [
+ {
+ "action": {
+ "return": 200,
+ }
+ }
+ ],
+ },
+ "access_log": {
+ "path": f'{temp_dir}/access.log',
+ "format": "$remote_addr $uri $request_uri",
+ },
+ }
+ )
+
+ assert search_in_file(r'::1', 'access.log') is None
+
+ assert client.get(url='/blah%25blah?a=b')['status'] == 200
+
+ assert (
+ wait_for_record(fr'^::1 /blah%blah /blah%25blah\?a=b$', 'access.log')
+ is not None
+ ), 'req 8081 (proxy)'
+ assert (
+ search_in_file(
+ fr'^127\.0\.0\.1 /blah%blah /blah%25blah\?a=b$', 'access.log'
+ )
+ is not None
+ ), 'req 8080'
+
+ # rewrite set $request_uri before proxy
+
+ assert 'success' in client.conf(
+ {
+ "a": [
+ {
+ "action": {
+ "rewrite": "/foo",
+ "proxy": "http://[::1]:8081",
+ }
+ }
+ ],
+ "b": [
+ {
+ "action": {
+ "rewrite": "/bar",
+ "return": 200,
+ }
+ }
+ ],
+ },
+ 'routes',
+ )
+
+ assert len(findall(r'::1', 'access.log')) == 1
+
+ assert client.get(url='/blah%2Fblah?a=b')['status'] == 200
+
+ assert (
+ wait_for_record(fr'^::1 /bar /foo\?a=b$', 'access.log') is not None
+ ), 'req 8081 (proxy) rewrite'
+ assert (
+ search_in_file(fr'^127\.0\.0\.1 /foo /blah%2Fblah\?a=b$', 'access.log')
+ is not None
+ ), 'req 8080 rewrite'
+
+ # percent-encoded rewrite
+
+ assert len(findall(r'::1', 'access.log')) == 2
+
+ assert 'success' in client.conf('"/foo%2Ffoo"', 'routes/a/0/action/rewrite')
+ assert client.get(url='/blah%2Fblah?a=b')['status'] == 200
+
+ assert (
+ wait_for_record(
+ fr'^127\.0\.0\.1 /foo/foo /blah%2Fblah\?a=b$', 'access.log'
+ )
+ is not None
+ ), 'req 8080 percent'
+ assert len(findall(fr'^::1 /bar /foo/foo\?a=b$', 'access.log')) == 1
+
def test_variables_uri(search_in_file, wait_for_record):
set_format('$uri')
diff --git a/test/test_wasm-wasi-component.py b/test/test_wasm-wasi-component.py
new file mode 100644
index 00000000..6d3bc485
--- /dev/null
+++ b/test/test_wasm-wasi-component.py
@@ -0,0 +1,18 @@
+import pytest
+from unit.applications.lang.wasm_component import ApplicationWasmComponent
+
+prerequisites = {
+ 'modules': {'wasm-wasi-component': 'any'},
+ 'features': {'cargo_component': True},
+}
+
+client = ApplicationWasmComponent()
+
+
+def test_wasm_component():
+ client.load('hello_world')
+
+ req = client.get()
+
+ assert client.get()['status'] == 200
+ assert req['body'] == 'Hello'
diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py
index 351d04ce..2b3194ae 100644
--- a/test/unit/applications/lang/java.py
+++ b/test/unit/applications/lang/java.py
@@ -53,7 +53,7 @@ class ApplicationJava(ApplicationProto):
os.makedirs(classes_path)
classpath = (
- f'{option.current_dir}/build/tomcat-servlet-api-9.0.86.jar'
+ f'{option.current_dir}/build/tomcat-servlet-api-9.0.93.jar'
)
ws_jars = glob.glob(
diff --git a/test/unit/applications/lang/wasm_component.py b/test/unit/applications/lang/wasm_component.py
new file mode 100644
index 00000000..a6c8dd14
--- /dev/null
+++ b/test/unit/applications/lang/wasm_component.py
@@ -0,0 +1,63 @@
+from pathlib import Path
+import shutil
+import subprocess
+from urllib.parse import quote
+
+from unit.applications.proto import ApplicationProto
+from unit.option import option
+
+
+class ApplicationWasmComponent(ApplicationProto):
+ @staticmethod
+ def prepare_env(script):
+ try:
+ subprocess.check_output(
+ ['cargo', 'component', '--help'],
+ stderr=subprocess.STDOUT,
+ )
+ except (subprocess.CalledProcessError, FileNotFoundError):
+ return None
+
+ temp_dir = Path(f'{option.temp_dir}/wasm_component/')
+
+ if not temp_dir.exists():
+ temp_dir.mkdir()
+
+ app_path = f'{temp_dir}/{script}'
+
+ shutil.copytree(f'{option.test_dir}/wasm_component/{script}', app_path)
+
+ try:
+ output = subprocess.check_output(
+ ['cargo', 'component', 'build', '--release'],
+ cwd=app_path,
+ stderr=subprocess.STDOUT,
+ )
+ except KeyboardInterrupt:
+ raise
+
+ except subprocess.CalledProcessError:
+ return None
+
+ return output
+
+ def load(self, script, **kwargs):
+ self.prepare_env(script)
+
+ component_path = f'{option.temp_dir}/wasm_component/{script}/target/wasm32-wasip1/release/test_wasi_component.wasm'
+
+ self._load_conf(
+ {
+ "listeners": {
+ "*:8080": {"pass": f"applications/{quote(script, '')}"}
+ },
+ "applications": {
+ script: {
+ "type": "wasm-wasi-component",
+ "processes": {"spare": 0},
+ "component": component_path,
+ }
+ },
+ },
+ **kwargs,
+ )
diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py
index 75354dd9..b48293be 100644
--- a/test/unit/applications/tls.py
+++ b/test/unit/applications/tls.py
@@ -85,9 +85,13 @@ subjectAltName = @alt_names
default_bits = 2048
encrypt_key = no
distinguished_name = req_distinguished_name
+x509_extensions = myca_extensions
{a_sec if alt_names else ""}
-[ req_distinguished_name ]'''
+[ req_distinguished_name ]
+
+[ myca_extensions ]
+basicConstraints = critical,CA:TRUE'''
)
def load(self, script, name=None):
diff --git a/test/unit/check/cargo_component.py b/test/unit/check/cargo_component.py
new file mode 100644
index 00000000..1c194bfc
--- /dev/null
+++ b/test/unit/check/cargo_component.py
@@ -0,0 +1,4 @@
+from unit.applications.lang.wasm_component import ApplicationWasmComponent
+
+def check_cargo_component():
+ return ApplicationWasmComponent.prepare_env('hello_world') is not None
diff --git a/test/unit/check/discover_available.py b/test/unit/check/discover_available.py
index 1383a0c3..99e63604 100644
--- a/test/unit/check/discover_available.py
+++ b/test/unit/check/discover_available.py
@@ -1,6 +1,7 @@
import subprocess
import sys
+from unit.check.cargo_component import check_cargo_component
from unit.check.chroot import check_chroot
from unit.check.go import check_go
from unit.check.isolation import check_isolation
@@ -28,7 +29,7 @@ def discover_available(unit):
# discover modules from log file
- for module in Log.findall(r'module: ([a-zA-Z]+) (.*) ".*"$'):
+ for module in Log.findall(r'module: ([a-zA-Z\-]+) (.*) ".*"$'):
versions = option.available['modules'].setdefault(module[0], [])
if module[1] not in versions:
versions.append(module[1])
@@ -44,6 +45,7 @@ def discover_available(unit):
# Discover features using check. Features should be discovered after
# modules since some features can require modules.
+ option.available['features']['cargo_component'] = check_cargo_component()
option.available['features']['chroot'] = check_chroot()
option.available['features']['isolation'] = check_isolation()
option.available['features']['unix_abstract'] = check_unix_abstract()
diff --git a/test/unit/http.py b/test/unit/http.py
index 9401501b..e449c8b5 100644
--- a/test/unit/http.py
+++ b/test/unit/http.py
@@ -67,7 +67,7 @@ class HTTP1:
headers['Content-Type'] = content_type
- if 'Content-Length' not in headers:
+ if 'Content-Length' not in headers and 'Transfer-Encoding' not in headers:
headers['Content-Length'] = len(body)
for header, value in headers.items():
diff --git a/test/unit/status.py b/test/unit/status.py
index 95096a96..679008d0 100644
--- a/test/unit/status.py
+++ b/test/unit/status.py
@@ -6,16 +6,16 @@ class Status:
control = Control()
def _check_zeros():
- assert Status.control.conf_get('/status') == {
- 'connections': {
+ status = Status.control.conf_get('/status')
+
+ assert status['connections'] == {
'accepted': 0,
'active': 0,
'idle': 0,
'closed': 0,
- },
- 'requests': {'total': 0},
- 'applications': {},
}
+ assert status['requests'] == {'total': 0}
+ assert status['applications'] == {}
def init(status=None):
Status._status = (
@@ -31,6 +31,9 @@ class Status:
if k in d2
}
+ if isinstance(d1, str) or isinstance(d1, list):
+ return d1 == d2
+
return d1 - d2
return find_diffs(Status.control.conf_get('/status'), Status._status)
diff --git a/test/wasm_component/hello_world/Cargo.lock b/test/wasm_component/hello_world/Cargo.lock
new file mode 100644
index 00000000..2daeb73d
--- /dev/null
+++ b/test/wasm_component/hello_world/Cargo.lock
@@ -0,0 +1,34 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "bitflags"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
+
+[[package]]
+name = "test-wasi-component"
+version = "0.1.0"
+dependencies = [
+ "bitflags",
+ "wasi",
+ "wit-bindgen-rt",
+]
+
+[[package]]
+name = "wasi"
+version = "0.13.0+wasi-0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "652cd73449d0b957a2743b70c72d79d34a5fa505696488f4ca90b46f6da94118"
+dependencies = [
+ "bitflags",
+ "wit-bindgen-rt",
+]
+
+[[package]]
+name = "wit-bindgen-rt"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "026d24a27f6712541fa534f2954bd9e0eb66172f033c2157c0f31d106255c497"
diff --git a/test/wasm_component/hello_world/Cargo.toml b/test/wasm_component/hello_world/Cargo.toml
new file mode 100644
index 00000000..a87fbeb5
--- /dev/null
+++ b/test/wasm_component/hello_world/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "test-wasi-component"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+bitflags = "2.4.2"
+wit-bindgen-rt = "0.21.0"
+wasi = "0.13.0"
+
+[lib]
+crate-type = ["cdylib"]
+
+[package.metadata.component]
+package = "component:test-wasi-component"
+proxy = true
+
+[package.metadata.component.dependencies]
diff --git a/test/wasm_component/hello_world/src/bindings.rs b/test/wasm_component/hello_world/src/bindings.rs
new file mode 100644
index 00000000..a0d74c42
--- /dev/null
+++ b/test/wasm_component/hello_world/src/bindings.rs
@@ -0,0 +1,109 @@
+// Generated by `wit-bindgen` 0.24.0. DO NOT EDIT!
+// Options used:
+#[doc(hidden)]
+#[allow(non_snake_case)]
+pub unsafe fn _export_hello_world_cabi<T: Guest>() -> *mut u8 {
+ #[cfg(target_arch = "wasm32")]
+ _rt::run_ctors_once();
+ let result0 = T::hello_world();
+ let ptr1 = _RET_AREA.0.as_mut_ptr().cast::<u8>();
+ let vec2 = (result0.into_bytes()).into_boxed_slice();
+ let ptr2 = vec2.as_ptr().cast::<u8>();
+ let len2 = vec2.len();
+ ::core::mem::forget(vec2);
+ *ptr1.add(4).cast::<usize>() = len2;
+ *ptr1.add(0).cast::<*mut u8>() = ptr2.cast_mut();
+ ptr1
+}
+#[doc(hidden)]
+#[allow(non_snake_case)]
+pub unsafe fn __post_return_hello_world<T: Guest>(arg0: *mut u8) {
+ let l0 = *arg0.add(0).cast::<*mut u8>();
+ let l1 = *arg0.add(4).cast::<usize>();
+ _rt::cabi_dealloc(l0, l1, 1);
+}
+pub trait Guest {
+ fn hello_world() -> _rt::String;
+}
+#[doc(hidden)]
+
+macro_rules! __export_world_example_cabi{
+ ($ty:ident with_types_in $($path_to_types:tt)*) => (const _: () = {
+
+ #[export_name = "hello-world"]
+ unsafe extern "C" fn export_hello_world() -> *mut u8 {
+ $($path_to_types)*::_export_hello_world_cabi::<$ty>()
+ }
+ #[export_name = "cabi_post_hello-world"]
+ unsafe extern "C" fn _post_return_hello_world(arg0: *mut u8,) {
+ $($path_to_types)*::__post_return_hello_world::<$ty>(arg0)
+ }
+ };);
+}
+#[doc(hidden)]
+pub(crate) use __export_world_example_cabi;
+#[repr(align(4))]
+struct _RetArea([::core::mem::MaybeUninit<u8>; 8]);
+static mut _RET_AREA: _RetArea =
+ _RetArea([::core::mem::MaybeUninit::uninit(); 8]);
+mod _rt {
+
+ #[cfg(target_arch = "wasm32")]
+ pub fn run_ctors_once() {
+ wit_bindgen_rt::run_ctors_once();
+ }
+ pub unsafe fn cabi_dealloc(ptr: *mut u8, size: usize, align: usize) {
+ if size == 0 {
+ return;
+ }
+ let layout = alloc::Layout::from_size_align_unchecked(size, align);
+ alloc::dealloc(ptr as *mut u8, layout);
+ }
+ pub use alloc_crate::alloc;
+ pub use alloc_crate::string::String;
+ extern crate alloc as alloc_crate;
+}
+
+/// Generates `#[no_mangle]` functions to export the specified type as the
+/// root implementation of all generated traits.
+///
+/// For more information see the documentation of `wit_bindgen::generate!`.
+///
+/// ```rust
+/// # macro_rules! export{ ($($t:tt)*) => (); }
+/// # trait Guest {}
+/// struct MyType;
+///
+/// impl Guest for MyType {
+/// // ...
+/// }
+///
+/// export!(MyType);
+/// ```
+#[allow(unused_macros)]
+#[doc(hidden)]
+
+macro_rules! __export_example_impl {
+ ($ty:ident) => (self::export!($ty with_types_in self););
+ ($ty:ident with_types_in $($path_to_types_root:tt)*) => (
+ $($path_to_types_root)*::__export_world_example_cabi!($ty with_types_in $($path_to_types_root)*);
+ )
+}
+#[doc(inline)]
+pub(crate) use __export_example_impl as export;
+
+#[cfg(target_arch = "wasm32")]
+#[link_section = "component-type:wit-bindgen:0.24.0:example:encoded world"]
+#[doc(hidden)]
+pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 194] = *b"\
+\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07E\x01A\x02\x01A\x02\x01\
+@\0\0s\x04\0\x0bhello-world\x01\0\x04\x01%component:test-wasi-component/example\x04\
+\0\x0b\x0d\x01\0\x07example\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0dw\
+it-component\x070.202.0\x10wit-bindgen-rust\x060.24.0";
+
+#[inline(never)]
+#[doc(hidden)]
+#[cfg(target_arch = "wasm32")]
+pub fn __link_custom_section_describing_imports() {
+ wit_bindgen_rt::maybe_link_cabi_realloc();
+}
diff --git a/test/wasm_component/hello_world/src/lib.rs b/test/wasm_component/hello_world/src/lib.rs
new file mode 100644
index 00000000..a1e40ef6
--- /dev/null
+++ b/test/wasm_component/hello_world/src/lib.rs
@@ -0,0 +1,31 @@
+use wasi::http::types::{
+ Fields, IncomingRequest, OutgoingBody, OutgoingResponse, ResponseOutparam,
+};
+
+wasi::http::proxy::export!(Component);
+
+struct Component;
+
+impl wasi::exports::http::incoming_handler::Guest for Component {
+ fn handle(_request: IncomingRequest, response_out: ResponseOutparam) {
+
+ let hdrs = Fields::new();
+ let mesg = String::from("Hello");
+ let _try = hdrs.set(&"Content-Type".to_string(), &[b"plain/text".to_vec()]);
+ let _try = hdrs.set(&"Content-Length".to_string(), &[mesg.len().to_string().as_bytes().to_vec()]);
+
+ let resp = OutgoingResponse::new(hdrs);
+
+ // Add the HTTP Response Status Code
+ resp.set_status_code(200).unwrap();
+
+ let body = resp.body().unwrap();
+ ResponseOutparam::set(response_out, Ok(resp));
+
+ let out = body.write().unwrap();
+ out.blocking_write_and_flush(mesg.as_bytes()).unwrap();
+ drop(out);
+
+ OutgoingBody::finish(body, None).unwrap();
+ }
+}
diff --git a/test/wasm_component/hello_world/wit/world.wit b/test/wasm_component/hello_world/wit/world.wit
new file mode 100644
index 00000000..82c810ef
--- /dev/null
+++ b/test/wasm_component/hello_world/wit/world.wit
@@ -0,0 +1,6 @@
+package component:test-wasi-component;
+
+/// An example world for the component to target.
+world example {
+ export hello-world: func() -> string;
+}