You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ic...@apache.org on 2023/01/09 07:35:19 UTC
svn commit: r1906475 [11/11] - in /httpd/httpd/branches/2.4.x: ./ changes-entries/ modules/http2/ test/ test/modules/http2/ test/modules/http2/htdocs/cgi/ test/modules/http2/mod_h2test/ test/pyhttpd/ test/pyhttpd/mod_aptest/
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_201_header_conditional.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_201_header_conditional.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_201_header_conditional.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_201_header_conditional.py Mon Jan 9 07:35:18 2023
@@ -1,8 +1,9 @@
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestConditionalHeaders:
@pytest.fixture(autouse=True, scope='class')
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_202_trailer.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_202_trailer.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_202_trailer.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_202_trailer.py Mon Jan 9 07:35:18 2023
@@ -86,7 +86,7 @@ class TestTrailers:
url = env.mkurl("https", "cgi", "/h2test/trailer?0")
r = env.nghttp().get(url)
assert r.response["status"] == 200
- assert len(r.response["body"]) == 0
+ assert len(r.response["body"]) == 0, f'{r.response["body"]}'
assert 'trailer' in r.response
assert 'trailer-content-length' in r.response['trailer']
assert r.response['trailer']['trailer-content-length'] == '0'
Added: httpd/httpd/branches/2.4.x/test/modules/http2/test_203_rfc9113.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_203_rfc9113.py?rev=1906475&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_203_rfc9113.py (added)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_203_rfc9113.py Mon Jan 9 07:35:18 2023
@@ -0,0 +1,56 @@
+import pytest
+
+from pyhttpd.env import HttpdTestEnv
+from .env import H2Conf
+
+
+class TestRfc9113:
+
+ @pytest.fixture(autouse=True, scope='class')
+ def _class_scope(self, env):
+ H2Conf(env).add_vhost_test1().install()
+ assert env.apache_restart() == 0
+
+ # by default, we accept leading/trailing ws in request fields
+ def test_h2_203_01_ws_ignore(self, env):
+ url = env.mkurl("https", "test1", "/")
+ r = env.curl_get(url, options=['-H', 'trailing-space: must not '])
+ assert r.exit_code == 0, f'curl output: {r.stderr}'
+ assert r.response["status"] == 200, f'curl output: {r.stdout}'
+ r = env.curl_get(url, options=['-H', 'trailing-space: must not\t'])
+ assert r.exit_code == 0, f'curl output: {r.stderr}'
+ assert r.response["status"] == 200, f'curl output: {r.stdout}'
+
+ # response header are also handled, but we strip ws before sending
+ @pytest.mark.parametrize(["hvalue", "expvalue", "status"], [
+ ['"123"', '123', 200],
+ ['"123 "', '123', 200], # trailing space stripped
+ ['"123\t"', '123', 200], # trailing tab stripped
+ ['" 123"', '123', 200], # leading space is stripped
+ ['" 123"', '123', 200], # leading spaces are stripped
+ ['"\t123"', '123', 200], # leading tab is stripped
+ ['"expr=%{unescape:123%0A 123}"', '', 500], # illegal char
+ ['" \t "', '', 200], # just ws
+ ])
+ def test_h2_203_02(self, env, hvalue, expvalue, status):
+ hname = 'ap-test-007'
+ conf = H2Conf(env, extras={
+ f'test1.{env.http_tld}': [
+ '<Location /index.html>',
+ f'Header add {hname} {hvalue}',
+ '</Location>',
+ ]
+ })
+ conf.add_vhost_test1(proxy_self=True)
+ conf.install()
+ assert env.apache_restart() == 0
+ url = env.mkurl("https", "test1", "/index.html")
+ r = env.curl_get(url, options=['--http2'])
+ if status == 500 and r.exit_code != 0:
+ # in 2.4.x we fail late on control chars in a response
+ # and RST_STREAM. That's also ok
+ return
+ assert r.response["status"] == status
+ if int(status) < 400:
+ assert r.response["header"][hname] == expvalue
+
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_300_interim.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_300_interim.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_300_interim.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_300_interim.py Mon Jan 9 07:35:18 2023
@@ -1,8 +1,9 @@
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestInterimResponses:
@pytest.fixture(autouse=True, scope='class')
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_400_push.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_400_push.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_400_push.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_400_push.py Mon Jan 9 07:35:18 2023
@@ -1,10 +1,11 @@
import os
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
# The push tests depend on "nghttp"
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestPush:
@pytest.fixture(autouse=True, scope='class')
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_401_early_hints.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_401_early_hints.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_401_early_hints.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_401_early_hints.py Mon Jan 9 07:35:18 2023
@@ -1,9 +1,10 @@
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
# The push tests depend on "nghttp"
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestEarlyHints:
@pytest.fixture(autouse=True, scope='class')
@@ -25,7 +26,7 @@ class TestEarlyHints:
assert env.apache_restart() == 0
# H2EarlyHints enabled in general, check that it works for H2PushResource
- def test_h2_401_31(self, env):
+ def test_h2_401_31(self, env, repeat):
url = env.mkurl("https", "hints", "/006-hints.html")
r = env.nghttp().get(url)
assert r.response["status"] == 200
@@ -37,7 +38,7 @@ class TestEarlyHints:
assert early["header"]["link"]
# H2EarlyHints enabled in general, but does not trigger on added response headers
- def test_h2_401_32(self, env):
+ def test_h2_401_32(self, env, repeat):
url = env.mkurl("https", "hints", "/006-nohints.html")
r = env.nghttp().get(url)
assert r.response["status"] == 200
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_500_proxy.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_500_proxy.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_500_proxy.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_500_proxy.py Mon Jan 9 07:35:18 2023
@@ -3,9 +3,10 @@ import os
import re
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestProxy:
@pytest.fixture(autouse=True, scope='class')
@@ -65,7 +66,7 @@ class TestProxy:
src = file.read()
if r.response["body"] != src:
with open(os.path.join(env.gen_dir, "nghttp.out"), 'w') as fd:
- fd.write(r.outraw)
+ fd.write(r.outraw.decode())
fd.write("\nstderr:\n")
fd.write(r.stderr)
assert r.response["body"] == src
@@ -125,3 +126,26 @@ class TestProxy:
def test_h2_500_24(self, env):
for i in range(100):
self.nghttp_upload_stat(env, "data-1k", ["--no-content-length"])
+
+ # lets do some error tests
+ def test_h2_500_30(self, env):
+ url = env.mkurl("https", "cgi", "/proxy/h2test/error?status=500")
+ r = env.curl_get(url)
+ assert r.exit_code == 0, r
+ assert r.response['status'] == 500
+ url = env.mkurl("https", "cgi", "/proxy/h2test/error?error=timeout")
+ r = env.curl_get(url)
+ assert r.exit_code == 0, r
+ assert r.response['status'] == 408
+
+ # produce an error during response body
+ def test_h2_500_31(self, env, repeat):
+ url = env.mkurl("https", "cgi", "/proxy/h2test/error?body_error=timeout")
+ r = env.curl_get(url)
+ assert r.exit_code != 0, r
+
+ # produce an error, fail to generate an error bucket
+ def test_h2_500_32(self, env, repeat):
+ url = env.mkurl("https", "cgi", "/proxy/h2test/error?body_error=timeout&error_bucket=0")
+ r = env.curl_get(url)
+ assert r.exit_code != 0, r
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_501_proxy_serverheader.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_501_proxy_serverheader.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_501_proxy_serverheader.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_501_proxy_serverheader.py Mon Jan 9 07:35:18 2023
@@ -1,9 +1,9 @@
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
-@pytest.mark.skip(reason="only in 2.5.x")
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestProxyServerHeader:
@pytest.fixture(autouse=True, scope='class')
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_502_proxy_port.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_502_proxy_port.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_502_proxy_port.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_502_proxy_port.py Mon Jan 9 07:35:18 2023
@@ -1,8 +1,9 @@
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestProxyPort:
@pytest.fixture(autouse=True, scope='class')
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_600_h2proxy.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_600_h2proxy.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_600_h2proxy.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_600_h2proxy.py Mon Jan 9 07:35:18 2023
@@ -1,20 +1,20 @@
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestH2Proxy:
- @pytest.fixture(autouse=True, scope='class')
- def _class_scope(self, env):
- conf = H2Conf(env)
+ def test_h2_600_01(self, env):
+ conf = H2Conf(env, extras={
+ f'cgi.{env.http_tld}': [
+ "SetEnvIf Host (.+) X_HOST=$1",
+ ]
+ })
conf.add_vhost_cgi(h2proxy_self=True)
- if env.verbosity > 1:
- conf.add("LogLevel proxy:trace2 proxy_http2:trace2")
conf.install()
assert env.apache_restart() == 0
-
- def test_h2_600_01(self, env):
url = env.mkurl("https", "cgi", "/h2proxy/hello.py")
r = env.curl_get(url, 5)
assert r.response["status"] == 200
@@ -23,4 +23,153 @@ class TestH2Proxy:
assert r.response["json"]["ssl_protocol"] != ""
assert r.response["json"]["h2"] == "on"
assert r.response["json"]["h2push"] == "off"
- assert r.response["json"]["host"] == f"cgi.{env.http_tld}"
+ assert r.response["json"]["host"] == f"cgi.{env.http_tld}:{env.https_port}"
+
+ def test_h2_600_02(self, env):
+ conf = H2Conf(env, extras={
+ f'cgi.{env.http_tld}': [
+ "SetEnvIf Host (.+) X_HOST=$1",
+ f"ProxyPreserveHost on",
+ f"ProxyPass /h2c/ h2c://127.0.0.1:{env.http_port}/",
+ ]
+ })
+ conf.add_vhost_cgi()
+ conf.install()
+ assert env.apache_restart() == 0
+ url = env.mkurl("https", "cgi", "/h2c/hello.py")
+ r = env.curl_get(url, 5)
+ assert r.response["status"] == 200
+ assert r.response["json"]["protocol"] == "HTTP/2.0"
+ assert r.response["json"]["https"] == ""
+ # the proxied backend sees Host header as passed on front
+ assert r.response["json"]["host"] == f"cgi.{env.http_tld}:{env.https_port}"
+ assert r.response["json"]["h2_original_host"] == ""
+
+ def test_h2_600_03(self, env):
+ conf = H2Conf(env, extras={
+ f'cgi.{env.http_tld}': [
+ "SetEnvIf Host (.+) X_HOST=$1",
+ f"ProxyPreserveHost off",
+ f"ProxyPass /h2c/ h2c://127.0.0.1:{env.http_port}/",
+ ]
+ })
+ conf.add_vhost_cgi()
+ conf.install()
+ assert env.apache_restart() == 0
+ url = env.mkurl("https", "cgi", "/h2c/hello.py")
+ r = env.curl_get(url, 5)
+ assert r.response["status"] == 200
+ assert r.response["json"]["protocol"] == "HTTP/2.0"
+ assert r.response["json"]["https"] == ""
+ # the proxied backend sees Host as using in connecting to it
+ assert r.response["json"]["host"] == f"127.0.0.1:{env.http_port}"
+ assert r.response["json"]["h2_original_host"] == ""
+
+ # check that connection reuse actually happens as configured
+ @pytest.mark.parametrize("enable_reuse", [ "on", "off" ])
+ def test_h2_600_04(self, env, enable_reuse):
+ conf = H2Conf(env, extras={
+ f'cgi.{env.http_tld}': [
+ f"ProxyPassMatch ^/h2proxy/([0-9]+)/(.*)$ "
+ f" h2c://127.0.0.1:$1/$2 enablereuse={enable_reuse} keepalive=on",
+ ]
+ })
+ conf.add_vhost_cgi()
+ conf.install()
+ assert env.apache_restart() == 0
+ url = env.mkurl("https", "cgi", f"/h2proxy/{env.http_port}/hello.py")
+ r = env.curl_get(url, 5)
+ assert r.response["status"] == 200
+ assert r.json["h2_stream_id"] == "1"
+ # httpd 2.5.0 disables reuse, not matter the config
+ if enable_reuse == "on" and not env.httpd_is_at_least("2.5.0"):
+ # reuse is not guarantueed for each request, but we expect some
+ # to do it and run on a h2 stream id > 1
+ reused = False
+ for _ in range(10):
+ r = env.curl_get(url, 5)
+ assert r.response["status"] == 200
+ if int(r.json["h2_stream_id"]) > 1:
+ reused = True
+ break
+ assert reused
+ else:
+ r = env.curl_get(url, 5)
+ assert r.response["status"] == 200
+ assert r.json["h2_stream_id"] == "1"
+
+ # do some flexible setup from #235 to proper connection selection
+ @pytest.mark.parametrize("enable_reuse", [ "on", "off" ])
+ def test_h2_600_05(self, env, enable_reuse):
+ conf = H2Conf(env, extras={
+ f'cgi.{env.http_tld}': [
+ f"ProxyPassMatch ^/h2proxy/([0-9]+)/(.*)$ "
+ f" h2c://127.0.0.1:$1/$2 enablereuse={enable_reuse} keepalive=on",
+ ]
+ })
+ conf.add_vhost_cgi()
+ conf.add([
+ f'Listen {env.http_port2}',
+ 'UseCanonicalName On',
+ 'UseCanonicalPhysicalPort On'
+ ])
+ conf.start_vhost(domains=[f'cgi.{env.http_tld}'],
+ port=5004, doc_root="htdocs/cgi")
+ conf.add("AddHandler cgi-script .py")
+ conf.end_vhost()
+ conf.install()
+ assert env.apache_restart() == 0
+ url = env.mkurl("https", "cgi", f"/h2proxy/{env.http_port}/hello.py")
+ r = env.curl_get(url, 5)
+ assert r.response["status"] == 200
+ assert int(r.json["port"]) == env.http_port
+ # going to another backend port must create a new connection and
+ # we should see stream id one again
+ url = env.mkurl("https", "cgi", f"/h2proxy/{env.http_port2}/hello.py")
+ r = env.curl_get(url, 5)
+ assert r.response["status"] == 200
+ exp_port = env.http_port if enable_reuse == "on" \
+ and not env.httpd_is_at_least("2.5.0")\
+ else env.http_port2
+ assert int(r.json["port"]) == exp_port
+
+ # lets do some error tests
+ def test_h2_600_30(self, env):
+ conf = H2Conf(env)
+ conf.add_vhost_cgi(h2proxy_self=True)
+ conf.install()
+ assert env.apache_restart() == 0
+ url = env.mkurl("https", "cgi", "/h2proxy/h2test/error?status=500")
+ r = env.curl_get(url)
+ assert r.exit_code == 0, r
+ assert r.response['status'] == 500
+ url = env.mkurl("https", "cgi", "/h2proxy/h2test/error?error=timeout")
+ r = env.curl_get(url)
+ assert r.exit_code == 0, r
+ assert r.response['status'] == 408
+
+ # produce an error during response body
+ def test_h2_600_31(self, env, repeat):
+ conf = H2Conf(env)
+ conf.add_vhost_cgi(h2proxy_self=True)
+ conf.install()
+ assert env.apache_restart() == 0
+ url = env.mkurl("https", "cgi", "/h2proxy/h2test/error?body_error=timeout")
+ r = env.curl_get(url)
+ # depending on when the error is detect in proxying, if may RST the
+ # stream (exit_code != 0) or give a 503 response.
+ if r.exit_code == 0:
+ assert r.response['status'] == 503
+
+ # produce an error, fail to generate an error bucket
+ def test_h2_600_32(self, env, repeat):
+ conf = H2Conf(env)
+ conf.add_vhost_cgi(h2proxy_self=True)
+ conf.install()
+ assert env.apache_restart() == 0
+ url = env.mkurl("https", "cgi", "/h2proxy/h2test/error?body_error=timeout&error_bucket=0")
+ r = env.curl_get(url)
+ # depending on when the error is detect in proxying, if may RST the
+ # stream (exit_code != 0) or give a 503 response.
+ if r.exit_code == 0:
+ assert r.response['status'] == 503
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_700_load_get.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_700_load_get.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_700_load_get.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_700_load_get.py Mon Jan 9 07:35:18 2023
@@ -3,6 +3,7 @@ import pytest
from .env import H2Conf, H2TestEnv
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
@pytest.mark.skipif(not H2TestEnv().h2load_is_at_least('1.41.0'),
reason="h2load misses --connect-to option")
class TestLoadGet:
@@ -29,6 +30,7 @@ class TestLoadGet:
1000, 80000
])
def test_h2_700_10(self, env, start):
+ assert env.is_live()
text = "X"
chunk = 32
for n in range(0, 5):
@@ -46,6 +48,7 @@ class TestLoadGet:
1, 2, 16, 32
])
def test_h2_700_11(self, env, conns):
+ assert env.is_live()
text = "X"
start = 1200
chunk = 64
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_710_load_post_static.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_710_load_post_static.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_710_load_post_static.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_710_load_post_static.py Mon Jan 9 07:35:18 2023
@@ -1,9 +1,10 @@
import pytest
import os
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestLoadPostStatic:
@pytest.fixture(autouse=True, scope='class')
@@ -24,7 +25,8 @@ class TestLoadPostStatic:
assert 0 == r.results["h2load"]["status"]["5xx"]
# test POST on static file, slurped in by server
- def test_h2_710_00(self, env):
+ def test_h2_710_00(self, env, repeat):
+ assert env.is_live()
url = env.mkurl("https", "test1", "/index.html")
n = 10
m = 1
@@ -36,7 +38,8 @@ class TestLoadPostStatic:
r = env.run(args)
self.check_h2load_ok(env, r, n)
- def test_h2_710_01(self, env):
+ def test_h2_710_01(self, env, repeat):
+ assert env.is_live()
url = env.mkurl("https", "test1", "/index.html")
n = 1000
m = 100
@@ -48,7 +51,8 @@ class TestLoadPostStatic:
r = env.run(args)
self.check_h2load_ok(env, r, n)
- def test_h2_710_02(self, env):
+ def test_h2_710_02(self, env, repeat):
+ assert env.is_live()
url = env.mkurl("https", "test1", "/index.html")
n = 100
m = 50
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_711_load_post_cgi.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_711_load_post_cgi.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_711_load_post_cgi.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_711_load_post_cgi.py Mon Jan 9 07:35:18 2023
@@ -1,9 +1,10 @@
import pytest
import os
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestLoadCgi:
@pytest.fixture(autouse=True, scope='class')
@@ -24,7 +25,8 @@ class TestLoadCgi:
assert 0 == r.results["h2load"]["status"]["5xx"]
# test POST on cgi, where input is read
- def test_h2_711_10(self, env):
+ def test_h2_711_10(self, env, repeat):
+ assert env.is_live()
url = env.mkurl("https", "test1", "/echo.py")
n = 100
m = 5
@@ -39,7 +41,8 @@ class TestLoadCgi:
self.check_h2load_ok(env, r, n)
# test POST on cgi via http/1.1 proxy, where input is read
- def test_h2_711_11(self, env):
+ def test_h2_711_11(self, env, repeat):
+ assert env.is_live()
url = env.mkurl("https", "test1", "/proxy/echo.py")
n = 100
m = 5
@@ -54,7 +57,8 @@ class TestLoadCgi:
self.check_h2load_ok(env, r, n)
# test POST on cgi via h2proxy, where input is read
- def test_h2_711_12(self, env):
+ def test_h2_711_12(self, env, repeat):
+ assert env.is_live()
url = env.mkurl("https", "test1", "/h2proxy/echo.py")
n = 100
m = 5
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_712_buffering.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_712_buffering.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_712_buffering.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_712_buffering.py Mon Jan 9 07:35:18 2023
@@ -2,10 +2,11 @@ from datetime import timedelta
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
from pyhttpd.curl import CurlPiper
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestBuffering:
@pytest.fixture(autouse=True, scope='class')
@@ -36,7 +37,6 @@ class TestBuffering:
piper = CurlPiper(env=env, url=url)
piper.stutter_check(chunks, stutter)
- @pytest.mark.skip(reason="only in 2.5.x")
def test_h2_712_02(self, env):
# same as 712_01 but via mod_proxy_http2
#
@@ -47,7 +47,6 @@ class TestBuffering:
piper = CurlPiper(env=env, url=url)
piper.stutter_check(chunks, stutter)
- @pytest.mark.skip(reason="only in 2.5.x")
def test_h2_712_03(self, env):
# same as 712_02 but with smaller chunks
#
Modified: httpd/httpd/branches/2.4.x/test/pyhttpd/conf.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/conf.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/conf.py (original)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/conf.py Mon Jan 9 07:35:18 2023
@@ -157,22 +157,12 @@ class HttpdConf(object):
self.start_vhost(domains=[domain, f"cgi-alias.{self.env.http_tld}"],
port=self.env.https_port, doc_root="htdocs/cgi")
self.add_proxies("cgi", proxy_self=proxy_self, h2proxy_self=h2proxy_self)
- self.add("<Location \"/h2test/echo\">")
- self.add(" SetHandler h2test-echo")
- self.add("</Location>")
- self.add("<Location \"/h2test/delay\">")
- self.add(" SetHandler h2test-delay")
- self.add("</Location>")
- if domain in self._extras:
- self.add(self._extras[domain])
self.end_vhost()
self.start_vhost(domains=[domain, f"cgi-alias.{self.env.http_tld}"],
port=self.env.http_port, doc_root="htdocs/cgi")
self.add("AddHandler cgi-script .py")
self.add_proxies("cgi", proxy_self=proxy_self, h2proxy_self=h2proxy_self)
self.end_vhost()
- self.add("LogLevel proxy:info")
- self.add("LogLevel proxy_http:info")
return self
@staticmethod
Modified: httpd/httpd/branches/2.4.x/test/pyhttpd/config.ini.in
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/config.ini.in?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/config.ini.in (original)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/config.ini.in Mon Jan 9 07:35:18 2023
@@ -18,13 +18,14 @@ apachectl = @sbindir@/apachectl
version = @HTTPD_VERSION@
name = @progname@
dso_modules = @DSO_MODULES@
-static_modules = @STATIC_MODULES@
+mpm_modules = @MPM_MODULES@
[test]
gen_dir = @abs_srcdir@/../gen
http_port = 5002
https_port = 5001
proxy_port = 5003
+http_port2 = 5004
http_tld = tests.httpd.apache.org
test_dir = @abs_srcdir@
test_src_dir = @abs_srcdir@
Modified: httpd/httpd/branches/2.4.x/test/pyhttpd/env.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/env.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/env.py (original)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/env.py Mon Jan 9 07:35:18 2023
@@ -1,3 +1,4 @@
+import importlib
import inspect
import logging
import re
@@ -35,6 +36,7 @@ class HttpdTestSetup:
"logio",
"unixd",
"version",
+ "watchdog",
"authn_core",
"authz_host",
"authz_groupfile",
@@ -67,6 +69,7 @@ class HttpdTestSetup:
self.env = env
self._source_dirs = [os.path.dirname(inspect.getfile(HttpdTestSetup))]
self._modules = HttpdTestSetup.MODULES.copy()
+ self._optional_modules = []
def add_source_dir(self, source_dir):
self._source_dirs.append(source_dir)
@@ -74,15 +77,20 @@ class HttpdTestSetup:
def add_modules(self, modules: List[str]):
self._modules.extend(modules)
+ def add_optional_modules(self, modules: List[str]):
+ self._optional_modules.extend(modules)
+
def make(self):
self._make_dirs()
self._make_conf()
- if self.env.mpm_module is not None:
+ if self.env.mpm_module is not None \
+ and self.env.mpm_module in self.env.mpm_modules:
self.add_modules([self.env.mpm_module])
if self.env.ssl_module is not None:
self.add_modules([self.env.ssl_module])
self._make_modules_conf()
self._make_htdocs()
+ self._add_aptest()
self.env.clear_curl_headerfiles()
def _make_dirs(self):
@@ -134,11 +142,21 @@ class HttpdTestSetup:
mod_path = os.path.join(self.env.libexec_dir, f"mod_{m}.so")
if os.path.isfile(mod_path):
fd.write(f"LoadModule {m}_module \"{mod_path}\"\n")
- elif m in self.env.static_modules:
- fd.write(f"#built static: LoadModule {m}_module \"{mod_path}\"\n")
- else:
+ elif m in self.env.dso_modules:
missing_mods.append(m)
+ else:
+ fd.write(f"#built static: LoadModule {m}_module \"{mod_path}\"\n")
loaded.add(m)
+ for m in self._optional_modules:
+ match = re.match(r'^mod_(.+)$', m)
+ if match:
+ m = match.group(1)
+ if m in loaded:
+ continue
+ mod_path = os.path.join(self.env.libexec_dir, f"mod_{m}.so")
+ if os.path.isfile(mod_path):
+ fd.write(f"LoadModule {m}_module \"{mod_path}\"\n")
+ loaded.add(m)
if len(missing_mods) > 0:
raise Exception(f"Unable to find modules: {missing_mods} "
f"DSOs: {self.env.dso_modules}")
@@ -162,13 +180,50 @@ class HttpdTestSetup:
st = os.stat(py_file)
os.chmod(py_file, st.st_mode | stat.S_IEXEC)
+ def _add_aptest(self):
+ local_dir = os.path.dirname(inspect.getfile(HttpdTestSetup))
+ p = subprocess.run([self.env.apxs, '-c', 'mod_aptest.c'],
+ capture_output=True,
+ cwd=os.path.join(local_dir, 'mod_aptest'))
+ rv = p.returncode
+ if rv != 0:
+ log.error(f"compiling mod_aptest failed: {p.stderr}")
+ raise Exception(f"compiling mod_aptest failed: {p.stderr}")
+
+ modules_conf = os.path.join(self.env.server_dir, 'conf/modules.conf')
+ with open(modules_conf, 'a') as fd:
+ # load our test module which is not installed
+ fd.write(f"LoadModule aptest_module \"{local_dir}/mod_aptest/.libs/mod_aptest.so\"\n")
+
class HttpdTestEnv:
+ LIBEXEC_DIR = None
+
+ @classmethod
+ def has_python_package(cls, name: str) -> bool:
+ if name in sys.modules:
+ # already loaded
+ return True
+ elif (spec := importlib.util.find_spec(name)) is not None:
+ module = importlib.util.module_from_spec(spec)
+ sys.modules[name] = module
+ spec.loader.exec_module(module)
+ return True
+ else:
+ return False
+
@classmethod
def get_ssl_module(cls):
return os.environ['SSL'] if 'SSL' in os.environ else 'mod_ssl'
+ @classmethod
+ def has_shared_module(cls, name):
+ if cls.LIBEXEC_DIR is None:
+ env = HttpdTestEnv() # will initialized it
+ path = os.path.join(cls.LIBEXEC_DIR, f"mod_{name}.so")
+ return os.path.isfile(path)
+
def __init__(self, pytestconfig=None):
self._our_dir = os.path.dirname(inspect.getfile(Dummy))
self.config = ConfigParser(interpolation=ExtendedInterpolation())
@@ -178,8 +233,8 @@ class HttpdTestEnv:
self._apxs = self.config.get('global', 'apxs')
self._prefix = self.config.get('global', 'prefix')
self._apachectl = self.config.get('global', 'apachectl')
- self._libexec_dir = self.get_apxs_var('LIBEXECDIR')
-
+ if HttpdTestEnv.LIBEXEC_DIR is None:
+ HttpdTestEnv.LIBEXEC_DIR = self._libexec_dir = self.get_apxs_var('LIBEXECDIR')
self._curl = self.config.get('global', 'curl_bin')
self._nghttp = self.config.get('global', 'nghttp')
if self._nghttp is None:
@@ -189,6 +244,7 @@ class HttpdTestEnv:
self._h2load = 'h2load'
self._http_port = int(self.config.get('test', 'http_port'))
+ self._http_port2 = int(self.config.get('test', 'http_port2'))
self._https_port = int(self.config.get('test', 'https_port'))
self._proxy_port = int(self.config.get('test', 'proxy_port'))
self._http_tld = self.config.get('test', 'http_tld')
@@ -203,7 +259,7 @@ class HttpdTestEnv:
self._apachectl_stderr = None
self._dso_modules = self.config.get('httpd', 'dso_modules').split(' ')
- self._static_modules = self.config.get('httpd', 'static_modules').split(' ')
+ self._mpm_modules = self.config.get('httpd', 'mpm_modules').split(' ')
self._mpm_module = f"mpm_{os.environ['MPM']}" if 'MPM' in os.environ else 'mpm_event'
self._ssl_module = self.get_ssl_module()
if len(self._ssl_module.strip()) == 0:
@@ -216,7 +272,7 @@ class HttpdTestEnv:
self._verbosity = pytestconfig.option.verbose if pytestconfig is not None else 0
self._test_conf = os.path.join(self._server_conf_dir, "test.conf")
self._httpd_base_conf = []
- self._httpd_log_modules = []
+ self._httpd_log_modules = ['aptest']
self._log_interesting = None
self._setup = None
@@ -230,6 +286,8 @@ class HttpdTestEnv:
self._verify_certs = False
self._curl_headerfiles_n = 0
+ self._h2load_version = None
+ self._current_test = None
def add_httpd_conf(self, lines: List[str]):
self._httpd_base_conf.extend(lines)
@@ -289,6 +347,10 @@ class HttpdTestEnv:
return self._http_port
@property
+ def http_port2(self) -> int:
+ return self._http_port2
+
+ @property
def https_port(self) -> int:
return self._https_port
@@ -330,15 +392,15 @@ class HttpdTestEnv:
@property
def libexec_dir(self) -> str:
- return self._libexec_dir
+ return HttpdTestEnv.LIBEXEC_DIR
@property
def dso_modules(self) -> List[str]:
return self._dso_modules
@property
- def static_modules(self) -> List[str]:
- return self._static_modules
+ def mpm_modules(self) -> List[str]:
+ return self._mpm_modules
@property
def server_conf_dir(self) -> str:
@@ -364,6 +426,13 @@ class HttpdTestEnv:
return self._ca
@property
+ def current_test_name(self) -> str:
+ return self._current_test
+
+ def set_current_test_name(self, val) -> None:
+ self._current_test = val
+
+ @property
def apachectl_stderr(self):
return self._apachectl_stderr
@@ -377,6 +446,7 @@ class HttpdTestEnv:
return []
def _versiontuple(self, v):
+ v = re.sub(r'(\d+\.\d+(\.\d+)?)(-\S+)?', r'\1', v)
return tuple(map(int, v.split('.')))
def httpd_is_at_least(self, minv):
@@ -389,14 +459,16 @@ class HttpdTestEnv:
def h2load_is_at_least(self, minv):
if not self.has_h2load():
return False
- p = subprocess.run([self._h2load, '--version'], capture_output=True, text=True)
- if p.returncode != 0:
- return False
- s = p.stdout.strip()
- m = re.match(r'h2load nghttp2/(\S+)', s)
- if m:
- hv = self._versiontuple(m.group(1))
- return hv >= self._versiontuple(minv)
+ if self._h2load_version is None:
+ p = subprocess.run([self._h2load, '--version'], capture_output=True, text=True)
+ if p.returncode != 0:
+ return False
+ s = p.stdout.strip()
+ m = re.match(r'h2load nghttp2/(\S+)', s)
+ if m:
+ self._h2load_version = self._versiontuple(m.group(1))
+ if self._h2load_version is not None:
+ return self._h2load_version >= self._versiontuple(minv)
return False
def has_nghttp(self):
@@ -570,7 +642,7 @@ class HttpdTestEnv:
if not isinstance(urls, list):
urls = [urls]
u = urlparse(urls[0])
- assert u.hostname, f"hostname not in url: {urls[0]}"
+ #assert u.hostname, f"hostname not in url: {urls[0]}"
headerfile = f"{self.gen_dir}/curl.headers.{self._curl_headerfiles_n}"
self._curl_headerfiles_n += 1
@@ -583,12 +655,15 @@ class HttpdTestEnv:
args.append('--insecure')
elif options and "--cacert" in options:
pass
- else:
+ elif u.hostname:
ca_pem = self.get_ca_pem_file(u.hostname)
if ca_pem:
args.extend(["--cacert", ca_pem])
- if force_resolve and u.hostname != 'localhost' \
+ if self._current_test is not None:
+ args.extend(["-H", f'AP-Test-Name: {self._current_test}'])
+
+ if force_resolve and u.hostname and u.hostname != 'localhost' \
and u.hostname != self._httpd_addr \
and not re.match(r'^(\d+|\[|:).*', u.hostname):
assert u.port, f"port not in url: {urls[0]}"
@@ -602,39 +677,60 @@ class HttpdTestEnv:
def curl_parse_headerfile(self, headerfile: str, r: ExecResult = None) -> ExecResult:
lines = open(headerfile).readlines()
- exp_stat = True
if r is None:
r = ExecResult(args=[], exit_code=0, stdout=b'', stderr=b'')
- header = {}
+
+ response = None
+ def fin_response(response):
+ if response:
+ r.add_response(response)
+
+ expected = ['status']
for line in lines:
- if exp_stat:
+ if re.match(r'^$', line):
+ if 'trailer' in expected:
+ # end of trailers
+ fin_response(response)
+ response = None
+ expected = ['status']
+ elif 'header' in expected:
+ # end of header, another status or trailers might follow
+ expected = ['status', 'trailer']
+ else:
+ assert False, f"unexpected line: {line}"
+ continue
+ if 'status' in expected:
log.debug("reading 1st response line: %s", line)
m = re.match(r'^(\S+) (\d+) (.*)$', line)
- assert m
- r.add_response({
- "protocol": m.group(1),
- "status": int(m.group(2)),
- "description": m.group(3),
- "body": r.outraw
- })
- exp_stat = False
- header = {}
- elif re.match(r'^$', line):
- exp_stat = True
- else:
- log.debug("reading header line: %s", line)
+ if m:
+ fin_response(response)
+ response = {
+ "protocol": m.group(1),
+ "status": int(m.group(2)),
+ "description": m.group(3),
+ "header": {},
+ "trailer": {},
+ "body": r.outraw
+ }
+ expected = ['header']
+ continue
+ if 'trailer' in expected:
m = re.match(r'^([^:]+):\s*(.*)$', line)
- assert m
- header[m.group(1).lower()] = m.group(2)
- if r.response:
- r.response["header"] = header
+ if m:
+ response['trailer'][m.group(1).lower()] = m.group(2)
+ continue
+ if 'header' in expected:
+ m = re.match(r'^([^:]+):\s*(.*)$', line)
+ if m:
+ response['header'][m.group(1).lower()] = m.group(2)
+ continue
+ assert False, f"unexpected line: {line}"
+
+ fin_response(response)
return r
def curl_raw(self, urls, timeout=10, options=None, insecure=False,
force_resolve=True):
- xopt = ['-vvvv']
- if options:
- xopt.extend(options)
args, headerfile = self.curl_complete_args(
urls=urls, timeout=timeout, options=options, insecure=insecure,
force_resolve=force_resolve)
@@ -679,7 +775,8 @@ class HttpdTestEnv:
return -1
def nghttp(self):
- return Nghttp(self._nghttp, connect_addr=self._httpd_addr, tmp_dir=self.gen_dir)
+ return Nghttp(self._nghttp, connect_addr=self._httpd_addr,
+ tmp_dir=self.gen_dir, test_name=self._current_test)
def h2load_status(self, run: ExecResult):
stats = {}
Added: httpd/httpd/branches/2.4.x/test/pyhttpd/mod_aptest/mod_aptest.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/mod_aptest/mod_aptest.c?rev=1906475&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/mod_aptest/mod_aptest.c (added)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/mod_aptest/mod_aptest.c Mon Jan 9 07:35:18 2023
@@ -0,0 +1,66 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <apr_optional.h>
+#include <apr_optional_hooks.h>
+#include <apr_strings.h>
+#include <apr_cstr.h>
+#include <apr_want.h>
+
+#include <httpd.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_log.h>
+
+static void aptest_hooks(apr_pool_t *pool);
+
+AP_DECLARE_MODULE(aptest) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* func to create per dir config */
+ NULL, /* func to merge per dir config */
+ NULL, /* func to create per server config */
+ NULL, /* func to merge per server config */
+ NULL, /* command handlers */
+ aptest_hooks,
+#if defined(AP_MODULE_FLAG_NONE)
+ AP_MODULE_FLAG_ALWAYS_MERGE
+#endif
+};
+
+
+static int aptest_post_read_request(request_rec *r)
+{
+ const char *test_name = apr_table_get(r->headers_in, "AP-Test-Name");
+ if (test_name) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "test[%s]: %s",
+ test_name, r->the_request);
+ }
+ return DECLINED;
+}
+
+/* Install this module into the apache2 infrastructure.
+ */
+static void aptest_hooks(apr_pool_t *pool)
+{
+ ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool,
+ "installing hooks and handlers");
+
+ /* test case monitoring */
+ ap_hook_post_read_request(aptest_post_read_request, NULL,
+ NULL, APR_HOOK_MIDDLE);
+
+}
+
Modified: httpd/httpd/branches/2.4.x/test/pyhttpd/nghttp.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/nghttp.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/nghttp.py (original)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/nghttp.py Mon Jan 9 07:35:18 2023
@@ -15,10 +15,12 @@ def _get_path(x):
class Nghttp:
- def __init__(self, path, connect_addr=None, tmp_dir="/tmp"):
+ def __init__(self, path, connect_addr=None, tmp_dir="/tmp",
+ test_name: str = None):
self.NGHTTP = path
self.CONNECT_ADDR = connect_addr
self.TMP_DIR = tmp_dir
+ self._test_name = test_name
@staticmethod
def get_stream(streams, sid):
@@ -104,7 +106,7 @@ class Nghttp:
body += m.group(1)
s = self.get_stream(streams, m.group(2))
if s:
- print("stream %d: recv %d header" % (s["id"], len(s["header"])))
+ print("stream %d: recv %d header" % (s["id"], len(s["header"])))
response = s["response"]
hkey = "header"
if "header" in response:
@@ -119,7 +121,8 @@ class Nghttp:
prev["previous"] = response["previous"]
response["previous"] = prev
response[hkey] = s["header"]
- s["header"] = {}
+ s["header"] = {}
+ body = ''
continue
m = re.match(r'(.*)\[.*] recv DATA frame <length=(\d+), .*stream_id=(\d+)>', l)
@@ -194,9 +197,11 @@ class Nghttp:
output["response"] = streams[main_stream]["response"]
output["paddings"] = streams[main_stream]["paddings"]
return output
-
+
def _raw(self, url, timeout, options):
args = ["-v"]
+ if self._test_name is not None:
+ args.append(f'--header=AP-Test-Name: {self._test_name}')
if options:
args.extend(options)
r = self._baserun(url, timeout, args)
Modified: httpd/httpd/branches/2.4.x/test/pyhttpd/result.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/result.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/result.py (original)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/result.py Mon Jan 9 07:35:18 2023
@@ -9,21 +9,21 @@ class ExecResult:
stdout: bytes, stderr: bytes = None, duration: timedelta = None):
self._args = args
self._exit_code = exit_code
- self._raw = stdout if stdout else b''
- self._stdout = stdout.decode() if stdout is not None else ""
- self._stderr = stderr.decode() if stderr is not None else ""
+ self._stdout = stdout if stdout is not None else b''
+ self._stderr = stderr if stderr is not None else b''
self._duration = duration if duration is not None else timedelta()
self._response = None
self._results = {}
self._assets = []
# noinspection PyBroadException
try:
- self._json_out = json.loads(self._stdout)
+ out = self._stdout.decode()
+ self._json_out = json.loads(out)
except:
self._json_out = None
def __repr__(self):
- return f"ExecResult[code={self.exit_code}, args={self._args}, stdout={self.stdout}, stderr={self.stderr}]"
+ return f"ExecResult[code={self.exit_code}, args={self._args}, stdout={self._stdout}, stderr={self._stderr}]"
@property
def exit_code(self) -> int:
@@ -35,11 +35,11 @@ class ExecResult:
@property
def outraw(self) -> bytes:
- return self._raw
+ return self._stdout
@property
def stdout(self) -> str:
- return self._stdout
+ return self._stdout.decode()
@property
def json(self) -> Optional[Dict]:
@@ -48,7 +48,7 @@ class ExecResult:
@property
def stderr(self) -> str:
- return self._stderr
+ return self._stderr.decode()
@property
def duration(self) -> timedelta: