From 0716b0c752b44b92fe079e5a9ce53d714c11684e Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Mon, 11 Mar 2024 13:45:39 +0000 Subject: Tests: NJS cacheable variables with access log Reproduces issue https://github.com/nginx/unit/issues/1169. --- test/test_njs.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'test') diff --git a/test/test_njs.py b/test/test_njs.py index 8ef815fd..23d1df1b 100644 --- a/test/test_njs.py +++ b/test/test_njs.py @@ -116,6 +116,24 @@ def test_njs_variables_cacheable(temp_dir): check_rewrite('/str', '${vars.uri}') +def test_njs_variables_cacheable_access_log(findall, temp_dir): + assert 'success' in client.conf({"return": 200}, 'routes/0/action') + + assert 'success' in client.conf( + { + 'path': f'{temp_dir}/access.log', + 'format': '`${vars.host}, ${vars.status}\n`', + }, + 'access_log' + ), 'access_log configure' + + reqs = 50 + for _ in range(reqs): + client.get() + + assert len(findall(r'localhost, 200', 'access.log')) == reqs + + def test_njs_invalid(skip_alert): skip_alert(r'js exception:') -- cgit From 5f606742433f965f8308c4b4cd2c34424b8158a9 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Thu, 4 Apr 2024 11:31:31 +0100 Subject: Tests: added $request_uri tests with proxy This patch consist of 3 tests: 1. Ensure that $request_uri won't change while proxying the request. 2. Same as 1, but modifying the request using the "rewrite" directive. 3. Same as 2, but with rewrite containing a percent-encoded string. --- test/test_variables.py | 101 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/test_variables.py b/test/test_variables.py index 9aab8a62..735a87b3 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 /bar\?a=b$', 'access.log') is not None + ), 'req 8081 (proxy) rewrite' + assert ( + search_in_file(fr'^127\.0\.0\.1 /foo /foo\?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 /foo%2Ffoo\?a=b$', 'access.log' + ) + is not None + ), 'req 8080 percent' + assert len(findall(fr'^::1 /bar /bar\?a=b$', 'access.log')) == 2 + def test_variables_uri(search_in_file, wait_for_record): set_format('$uri') -- cgit From a625a0b1f0d822b3224b7b29565fe9733b634afd Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Mon, 8 Apr 2024 02:18:37 +0100 Subject: Tests: compatibility with OpenSSL 3.2.0 OpenSSL 3.2.0 generates X.509v3 certificates by default. These certificates, even self-signed, cannot sign other certificates unless "CA:TRUE" is explicitly set in the basicConstraints extension. As a result, tests attempting this are currently failing. Fix is to provide "CA:TRUE" in the basicConstraints for self-signed root certificates used in "openssl ca" commands. Closes: https://github.com/nginx/unit/issues/1202 Tested-by: Andrew Clayton Reviewed-by: Andrew Clayton --- test/unit/applications/tls.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'test') 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): -- cgit From 626977730f0e9029ee15b6321d35cb5aa311379d Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Thu, 4 Apr 2024 12:11:13 +0100 Subject: Tests: error report corrected for unknown variables in "response_headers" For more information please see https://github.com/nginx/unit/pull/1191 --- test/test_response_headers.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'test') 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'] -- cgit From da43f4434a6ed257cf6e07ca7b4d00108ffeaf1b Mon Sep 17 00:00:00 2001 From: "Sergey A. Osokin" Date: Wed, 8 May 2024 17:18:48 -0400 Subject: java: Update third-party components [ Tweaked subject - Andrew ] Signed-off-by: Andrew Clayton --- test/unit/applications/lang/java.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py index 351d04ce..a65eb0f5 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.89.jar' ) ws_jars = glob.glob( -- cgit From eed21785b761f885d8e74fbbb21c05cc487a7ad0 Mon Sep 17 00:00:00 2001 From: Gabor Javorszky Date: Tue, 30 Apr 2024 14:52:00 +0800 Subject: tests: Change request_uri tests for changed behaviour --- test/test_variables.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'test') diff --git a/test/test_variables.py b/test/test_variables.py index 735a87b3..e20a3cd7 100644 --- a/test/test_variables.py +++ b/test/test_variables.py @@ -185,10 +185,10 @@ def test_variables_request_uri( assert client.get(url='/blah%2Fblah?a=b')['status'] == 200 assert ( - wait_for_record(fr'^::1 /bar /bar\?a=b$', 'access.log') is not None + 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 /foo\?a=b$', 'access.log') + search_in_file(fr'^127\.0\.0\.1 /foo /blah%2Fblah\?a=b$', 'access.log') is not None ), 'req 8080 rewrite' @@ -201,11 +201,11 @@ def test_variables_request_uri( assert ( wait_for_record( - fr'^127\.0\.0\.1 /foo/foo /foo%2Ffoo\?a=b$', 'access.log' + 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 /bar\?a=b$', 'access.log')) == 2 + assert len(findall(fr'^::1 /bar /foo/foo\?a=b$', 'access.log')) == 1 def test_variables_uri(search_in_file, wait_for_record): -- cgit From 00009765a825346127c411250d46ed5647c9a823 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Tue, 30 Apr 2024 14:55:06 +0800 Subject: tests: REQUEST_URI variable test with rewrite --- test/test_python_application.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'test') 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') -- cgit From e77a0c166f3a840d214a901a48d6b5e5806d0156 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Mon, 27 May 2024 18:02:09 +0100 Subject: Tests: explicitly specify 'f' prefix to format string before printing Otherwise string will be printed as: "Could not unmount filesystems in tmpdir ({temporary_dir})" --- test/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test') diff --git a/test/conftest.py b/test/conftest.py index 2fe4d8dc..2d8eabad 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -429,7 +429,7 @@ 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}).') + sys.exit(f'Could not unmount filesystems in tmpdir ({temporary_dir}).') for item in Path(temporary_dir).iterdir(): if item.name not in [ -- cgit From c9dced37ba8df762e1e0776c24d32c4c76d677c1 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Mon, 27 May 2024 18:28:37 +0100 Subject: Tests: print unit.log on unsuccessful unmount --- test/conftest.py | 1 + 1 file changed, 1 insertion(+) (limited to 'test') diff --git a/test/conftest.py b/test/conftest.py index 2d8eabad..91c59e17 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -429,6 +429,7 @@ def _clear_temp_dir(): temporary_dir = unit_instance['temp_dir'] if is_findmnt and not waitforunmount(temporary_dir, timeout=600): + Log.print_log() sys.exit(f'Could not unmount filesystems in tmpdir ({temporary_dir}).') for item in Path(temporary_dir).iterdir(): -- cgit From c7e921c7f8ee352b2b764de2c12cd39dace2e3d3 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Fri, 7 Jun 2024 17:24:00 +0100 Subject: Tests: chunked request body --- test/test_chunked.py | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++ test/unit/http.py | 2 +- 2 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 test/test_chunked.py (limited to 'test') 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/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(): -- cgit From d67d350142d4ef9a9cdbfc2bb4a6b2d8f261deb1 Mon Sep 17 00:00:00 2001 From: Gourav Date: Wed, 26 Jun 2024 12:07:09 +0530 Subject: tests: Add tests for python application factories Add the following tests cases: 1. When "factory" key is used inside the "targets" option. 2. When "factory" key is used at the root level of python application config. 3. When factory returns invalid callable or When factory is invalid callable Link: [ Commit subject & message formatting tweaks - Andrew ] Signed-off-by: Andrew Clayton --- test/python/factory/wsgi.py | 23 ++++++++ test/test_python_factory.py | 140 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 test/python/factory/wsgi.py create mode 100644 test/test_python_factory.py (limited to 'test') 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_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, + }, + }, + } + }, + } + ) -- cgit From f4ba4b5583cef182ad29cc158e8b1e2965b09829 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Wed, 3 Jul 2024 17:43:20 +0100 Subject: tests: Fix `/status' endpoint tests for new 'modules' section Now that the `/status` endpoint returns a list of loaded language modules, e.g { "modules": { "python": { "version": "3.12.2", "lib": "/opt/unit/modules/python.unit.so" }, ... ... } This broke 'test/test_status.py' in a number of ways 1) The check for all the object values being 0 at startup is no longer true with the modules section. 2) The find_diffs() check broke trying to subtract strings from strings. So don't include the 'modules' section in the check_zeros() check and in the find_diffs() check, if we're dealing with strings do a basic compare returning that value instead. [ Commit message - Andrew ] Co-developed-by: Andrew Clayton Signed-off-by: Andrew Clayton --- test/unit/status.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'test') diff --git a/test/unit/status.py b/test/unit/status.py index 95096a96..d8bb4e41 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): + return d1 == d2 + return d1 - d2 return find_diffs(Status.control.conf_get('/status'), Status._status) -- cgit From 43c4bfdcd1e925e5ec06e59b86a884279947671d Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Sun, 18 Aug 2024 22:49:59 +0800 Subject: tests: "if" option in http route match --- test/test_routing.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) (limited to 'test') diff --git a/test/test_routing.py b/test/test_routing.py index 0b6eced2..c419779a 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -2009,3 +2009,60 @@ def test_routes_match_destination_proxy(): ), 'proxy configure' assert client.get()['status'] == 200, 'proxy' + + +def test_routes_match_if(): + + def set_if(condition): + assert 'success' in client.conf(f'"{condition}"', 'routes/0/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 + + # njs + + 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') -- cgit From cad6aed526b38d52f13266120f9a4381f9a22cad Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Fri, 17 May 2024 17:42:11 +0100 Subject: Tests: initial "wasm-wasi-component" test --- test/test_wasm_component.py | 18 ++++ test/unit/applications/lang/wasm_component.py | 60 +++++++++++++ test/unit/check/cargo_component.py | 4 + test/unit/check/discover_available.py | 4 +- test/wasm_component/hello_world/Cargo.lock | 34 ++++++++ test/wasm_component/hello_world/Cargo.toml | 18 ++++ test/wasm_component/hello_world/src/bindings.rs | 109 ++++++++++++++++++++++++ test/wasm_component/hello_world/src/lib.rs | 31 +++++++ test/wasm_component/hello_world/wit/world.wit | 6 ++ 9 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 test/test_wasm_component.py create mode 100644 test/unit/applications/lang/wasm_component.py create mode 100644 test/unit/check/cargo_component.py create mode 100644 test/wasm_component/hello_world/Cargo.lock create mode 100644 test/wasm_component/hello_world/Cargo.toml create mode 100644 test/wasm_component/hello_world/src/bindings.rs create mode 100644 test/wasm_component/hello_world/src/lib.rs create mode 100644 test/wasm_component/hello_world/wit/world.wit (limited to 'test') diff --git a/test/test_wasm_component.py b/test/test_wasm_component.py new file mode 100644 index 00000000..6d3bc485 --- /dev/null +++ b/test/test_wasm_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/wasm_component.py b/test/unit/applications/lang/wasm_component.py new file mode 100644 index 00000000..6f7b5518 --- /dev/null +++ b/test/unit/applications/lang/wasm_component.py @@ -0,0 +1,60 @@ +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']) + 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-wasi/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/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/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() -> *mut u8 { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = T::hello_world(); + let ptr1 = _RET_AREA.0.as_mut_ptr().cast::(); + let vec2 = (result0.into_bytes()).into_boxed_slice(); + let ptr2 = vec2.as_ptr().cast::(); + let len2 = vec2.len(); + ::core::mem::forget(vec2); + *ptr1.add(4).cast::() = len2; + *ptr1.add(0).cast::<*mut u8>() = ptr2.cast_mut(); + ptr1 +} +#[doc(hidden)] +#[allow(non_snake_case)] +pub unsafe fn __post_return_hello_world(arg0: *mut u8) { + let l0 = *arg0.add(0).cast::<*mut u8>(); + let l1 = *arg0.add(4).cast::(); + _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; 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; +} -- cgit From f4298f94292b99e7f384f8048e695b8263d495f2 Mon Sep 17 00:00:00 2001 From: Andrew Clayton Date: Tue, 23 Jul 2024 18:55:18 +0100 Subject: tests: Fix `/status' endpoint to cater for lists We can now get list objects from the /status endpoint in the case of having different versions of the same language module. That led to this error > return d1 - d2 E TypeError: unsupported operand type(s) for -: 'list' and 'list' We already cover a similar case for when we have simple strings so add this to that. Signed-off-by: Andrew Clayton --- test/unit/status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/status.py b/test/unit/status.py index d8bb4e41..679008d0 100644 --- a/test/unit/status.py +++ b/test/unit/status.py @@ -31,7 +31,7 @@ class Status: if k in d2 } - if isinstance(d1, str): + if isinstance(d1, str) or isinstance(d1, list): return d1 == d2 return d1 - d2 -- cgit From 264f4af44c1d054b6d676877c53ae163a7da7055 Mon Sep 17 00:00:00 2001 From: Andrew Clayton Date: Tue, 27 Aug 2024 06:47:53 +0100 Subject: test/wasm-wc: Target wasm32-wasip1 Changes are afoot... wasm32-wasi has been renamed wasm32-wasip1, there is also a wasm32-wasip2 (seems not yet fully realised) and wasm32-wasi is being kept clear for an eventual WASI 1.0 release. cargo-component targets wasm32-wasip1 by default and adapts the module to the preview2 version of WASI supported by the component model. This means that the component is now found under target/wasm32-wasip1/... Link: Link: Signed-off-by: Andrew Clayton --- test/unit/applications/lang/wasm_component.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/applications/lang/wasm_component.py b/test/unit/applications/lang/wasm_component.py index 6f7b5518..1d58afd6 100644 --- a/test/unit/applications/lang/wasm_component.py +++ b/test/unit/applications/lang/wasm_component.py @@ -41,7 +41,7 @@ class ApplicationWasmComponent(ApplicationProto): def load(self, script, **kwargs): self.prepare_env(script) - component_path = f'{option.temp_dir}/wasm_component/{script}/target/wasm32-wasi/release/test_wasi_component.wasm' + component_path = f'{option.temp_dir}/wasm_component/{script}/target/wasm32-wasip1/release/test_wasi_component.wasm' self._load_conf( { -- cgit From 19cd88ef907a3a9248d58a987070f18749ff874b Mon Sep 17 00:00:00 2001 From: Andrew Clayton Date: Mon, 26 Aug 2024 15:53:13 +0100 Subject: test/wasm-wc: Rename test_wasm_component.py Rename this to 'test_wasm-wasi-component.py' to match the language module name and the name as used in the CI. Signed-off-by: Andrew Clayton --- test/test_wasm-wasi-component.py | 18 ++++++++++++++++++ test/test_wasm_component.py | 18 ------------------ 2 files changed, 18 insertions(+), 18 deletions(-) create mode 100644 test/test_wasm-wasi-component.py delete mode 100644 test/test_wasm_component.py (limited to 'test') 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/test_wasm_component.py b/test/test_wasm_component.py deleted file mode 100644 index 6d3bc485..00000000 --- a/test/test_wasm_component.py +++ /dev/null @@ -1,18 +0,0 @@ -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' -- cgit From 27d3a5c7c0d9b9fc03b69871aea6b3c9ab16aaa0 Mon Sep 17 00:00:00 2001 From: "Sergey A. Osokin" Date: Sat, 7 Sep 2024 11:43:10 -0400 Subject: java: Update third-party components [ Tweaked subject - Andrew ] Signed-off-by: Andrew Clayton --- test/unit/applications/lang/java.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py index a65eb0f5..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.89.jar' + f'{option.current_dir}/build/tomcat-servlet-api-9.0.93.jar' ) ws_jars = glob.glob( -- cgit From 6976a614d782d13e14c6c8e446ce521c47f02b58 Mon Sep 17 00:00:00 2001 From: Andrew Clayton Date: Fri, 6 Sep 2024 16:24:32 +0100 Subject: tests: Fix routing tests in the no njs case Don't try and run the tests that require njs if it isn't enabled. Closes: https://github.com/nginx/unit/issues/1411 Fixes: 43c4bfdcd ("tests: "if" option in http route match") Signed-off-by: Andrew Clayton --- test/test_routing.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) (limited to 'test') diff --git a/test/test_routing.py b/test/test_routing.py index c419779a..170f627e 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -2011,10 +2011,11 @@ def test_routes_match_destination_proxy(): assert client.get()['status'] == 200, 'proxy' -def test_routes_match_if(): +def set_if(condition): + assert 'success' in client.conf(f'"{condition}"', 'routes/0/match/if') + - 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) @@ -2055,7 +2056,21 @@ def test_routes_match_if(): assert client.get(url='/foo_empty?foo')['status'] == 200 assert client.get(url='/foo?foo=1')['status'] == 404 - # njs +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 -- cgit From cff5e092afec17c73a0f94d75276500fd9da8403 Mon Sep 17 00:00:00 2001 From: Andrew Clayton Date: Fri, 6 Sep 2024 16:28:00 +0100 Subject: tests: Suppress cargo-component output Suppress the output from cargo-component when we first run it to check if it's available, otherwise you may see the following $ pytest test/test_wasm-wasi-component.py which: no go in (/home/andrew/.local/bin:/home/andrew/bin:/usr/share/Modules/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin) error: no such command: `component` View all installed commands with `cargo --list` Find a package to install `component` with `cargo search cargo-component Note: This didn't stop the tests from working, just an aesthetic issue. Closes: https://github.com/nginx/unit/issues/1410 Signed-off-by: Andrew Clayton --- test/unit/applications/lang/wasm_component.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/applications/lang/wasm_component.py b/test/unit/applications/lang/wasm_component.py index 1d58afd6..a6c8dd14 100644 --- a/test/unit/applications/lang/wasm_component.py +++ b/test/unit/applications/lang/wasm_component.py @@ -11,7 +11,10 @@ class ApplicationWasmComponent(ApplicationProto): @staticmethod def prepare_env(script): try: - subprocess.check_output(['cargo', 'component', '--help']) + subprocess.check_output( + ['cargo', 'component', '--help'], + stderr=subprocess.STDOUT, + ) except (subprocess.CalledProcessError, FileNotFoundError): return None -- cgit