Commit e793507d authored by Stephen McGruer's avatar Stephen McGruer Committed by Chromium LUCI CQ

[blinkpy] Switch to run WPT subcommands using Python3

Upstream WPT is now Python 3-first. To minimize the likelihood of
Chromium engineers writing incompatible tests*, we should run the WPT
parts of blinkpy under Python 3.

* Or, more likely, incompatible Python file handlers
(https://web-platform-tests.org/writing-tests/python-handlers/index.html)

Bug: 1135554
Change-Id: I9813a81543219ede80bb58de971d2e4509ef4b53
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2558741
Commit-Queue: Stephen McGruer <smcgruer@chromium.org>
Reviewed-by: default avatarRobert Ma <robertma@chromium.org>
Reviewed-by: default avatarAdam Rice <ricea@chromium.org>
Cr-Commit-Position: refs/heads/master@{#835668}
parent ec6a9e1f
...@@ -404,7 +404,8 @@ class TestImporter(object): ...@@ -404,7 +404,8 @@ class TestImporter(object):
stages the generated MANIFEST.json in the git index, ready to commit. stages the generated MANIFEST.json in the git index, ready to commit.
""" """
_log.info('Generating MANIFEST.json') _log.info('Generating MANIFEST.json')
WPTManifest.generate_manifest(self.host, self.dest_path) WPTManifest.generate_manifest(self.host.port_factory.get(),
self.dest_path)
manifest_path = self.fs.join(self.dest_path, 'MANIFEST.json') manifest_path = self.fs.join(self.dest_path, 'MANIFEST.json')
assert self.fs.exists(manifest_path) assert self.fs.exists(manifest_path)
manifest_base_path = self.fs.normpath( manifest_base_path = self.fs.normpath(
......
...@@ -25,12 +25,12 @@ from blinkpy.web_tests.port.android import PRODUCTS_TO_EXPECTATION_FILE_PATHS ...@@ -25,12 +25,12 @@ from blinkpy.web_tests.port.android import PRODUCTS_TO_EXPECTATION_FILE_PATHS
MOCK_WEB_TESTS = '/mock-checkout/' + RELATIVE_WEB_TESTS MOCK_WEB_TESTS = '/mock-checkout/' + RELATIVE_WEB_TESTS
MANIFEST_INSTALL_CMD = [ MANIFEST_INSTALL_CMD = [
'python', 'python3',
'/mock-checkout/third_party/blink/tools/blinkpy/third_party/wpt/wpt/wpt', '/mock-checkout/third_party/blink/tools/blinkpy/third_party/wpt/wpt/wpt',
'manifest', 'manifest', '--no-download', '--tests-root',
'--no-download', MOCK_WEB_TESTS + 'external/wpt'
'--tests-root', ]
MOCK_WEB_TESTS + 'external/wpt']
class TestImporterTest(LoggingTestCase): class TestImporterTest(LoggingTestCase):
......
...@@ -314,7 +314,7 @@ class WPTManifest(object): ...@@ -314,7 +314,7 @@ class WPTManifest(object):
_log.error('Manifest base not found at "%s".', _log.error('Manifest base not found at "%s".',
base_manifest_path) base_manifest_path)
WPTManifest.generate_manifest(port.host, wpt_path) WPTManifest.generate_manifest(port, wpt_path)
if fs.isfile(manifest_path): if fs.isfile(manifest_path):
_log.debug('Manifest generation completed.') _log.debug('Manifest generation completed.')
...@@ -325,17 +325,17 @@ class WPTManifest(object): ...@@ -325,17 +325,17 @@ class WPTManifest(object):
fs.write_text_file(manifest_path, '{}') fs.write_text_file(manifest_path, '{}')
@staticmethod @staticmethod
def generate_manifest(host, dest_path): def generate_manifest(port, dest_path):
"""Generates MANIFEST.json on the specified directory.""" """Generates MANIFEST.json on the specified directory."""
wpt_exec_path = PathFinder(host.filesystem).path_from_blink_tools( wpt_exec_path = PathFinder(port.host.filesystem).path_from_blink_tools(
'blinkpy', 'third_party', 'wpt', 'wpt', 'wpt') 'blinkpy', 'third_party', 'wpt', 'wpt', 'wpt')
cmd = [ cmd = [
'python', wpt_exec_path, 'manifest', '--no-download', port.python3_command(), wpt_exec_path, 'manifest', '--no-download',
'--tests-root', dest_path '--tests-root', dest_path
] ]
# ScriptError will be raised if the command fails. # ScriptError will be raised if the command fails.
host.executive.run_command( port.host.executive.run_command(
cmd, cmd,
# This will also include stderr in the exception message. # This will also include stderr in the exception message.
return_stderr=True) return_stderr=True)
......
...@@ -24,7 +24,7 @@ class WPTManifestUnitTest(unittest.TestCase): ...@@ -24,7 +24,7 @@ class WPTManifestUnitTest(unittest.TestCase):
{manifest_path: '{"manifest": "base"}'}) {manifest_path: '{"manifest": "base"}'})
self.assertEqual(host.executive.calls, [[ self.assertEqual(host.executive.calls, [[
'python', 'python3',
'/mock-checkout/third_party/blink/tools/blinkpy/third_party/wpt/wpt/wpt', '/mock-checkout/third_party/blink/tools/blinkpy/third_party/wpt/wpt/wpt',
'manifest', 'manifest',
'--no-download', '--no-download',
...@@ -47,7 +47,7 @@ class WPTManifestUnitTest(unittest.TestCase): ...@@ -47,7 +47,7 @@ class WPTManifestUnitTest(unittest.TestCase):
{manifest_path: '{"manifest": "base"}'}) {manifest_path: '{"manifest": "base"}'})
self.assertEqual(host.executive.calls, [[ self.assertEqual(host.executive.calls, [[
'python', 'python3',
'/mock-checkout/third_party/blink/tools/blinkpy/third_party/wpt/wpt/wpt', '/mock-checkout/third_party/blink/tools/blinkpy/third_party/wpt/wpt/wpt',
'manifest', 'manifest',
'--no-download', '--no-download',
...@@ -68,7 +68,7 @@ class WPTManifestUnitTest(unittest.TestCase): ...@@ -68,7 +68,7 @@ class WPTManifestUnitTest(unittest.TestCase):
port = TestPort(host) port = TestPort(host)
WPTManifest.ensure_manifest(port, 'wpt_internal') WPTManifest.ensure_manifest(port, 'wpt_internal')
self.assertEqual(host.executive.calls, [[ self.assertEqual(host.executive.calls, [[
'python', 'python3',
'/mock-checkout/third_party/blink/tools/blinkpy/third_party/wpt/wpt/wpt', '/mock-checkout/third_party/blink/tools/blinkpy/third_party/wpt/wpt/wpt',
'manifest', 'manifest',
'--no-download', '--no-download',
......
...@@ -1221,6 +1221,18 @@ class Port(object): ...@@ -1221,6 +1221,18 @@ class Port(object):
def architecture(self): def architecture(self):
return self._architecture return self._architecture
def python3_command(self):
"""Returns the correct command to use to run python3.
This exists because Windows has inconsistent behavior between the bots
and local developer machines, such that determining which python3 name
to use is non-trivial. See https://crbug.com/155616.
Once blinkpy runs under python3, this can be removed in favour of
callers using sys.executable.
"""
return 'python3'
def get_option(self, name, default_value=None): def get_option(self, name, default_value=None):
return getattr(self._options, name, default_value) return getattr(self._options, name, default_value)
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
import errno import errno
import logging import logging
import os
import tempfile import tempfile
# The _winreg library is only available on Windows. # The _winreg library is only available on Windows.
...@@ -39,6 +40,7 @@ except ImportError: ...@@ -39,6 +40,7 @@ except ImportError:
_winreg = None # pylint: disable=invalid-name _winreg = None # pylint: disable=invalid-name
from blinkpy.common import exit_codes from blinkpy.common import exit_codes
from blinkpy.common.memoized import memoized
from blinkpy.web_tests.breakpad.dump_reader_win import DumpReaderWin from blinkpy.web_tests.breakpad.dump_reader_win import DumpReaderWin
from blinkpy.web_tests.models import test_run_results from blinkpy.web_tests.models import test_run_results
from blinkpy.web_tests.port import base from blinkpy.web_tests.port import base
...@@ -169,6 +171,24 @@ class WinPort(base.Port): ...@@ -169,6 +171,24 @@ class WinPort(base.Port):
def operating_system(self): def operating_system(self):
return 'win' return 'win'
@memoized
def python3_command(self):
# The subprocess module on Windows does not look at PATHEXT, so we
# cannot rely on 'python3' working. Instead, we must check each possible
# program name to find the working one.
_log.debug('Searching for Python 3 command name')
exts = filter(len, os.getenv('PATHEXT', '').split(';'))
for ext in [''] + exts:
python = 'python3%s' % ext
_log.debug('Trying "%s"' % python)
try:
self._executive.run_command([python, '--version'])
return python
except WindowsError:
pass
raise WindowsError('Unable to find a valid python3 command name')
def relative_test_filename(self, filename): def relative_test_filename(self, filename):
path = filename[len(self.web_tests_dir()) + 1:] path = filename[len(self.web_tests_dir()) + 1:]
return path.replace('\\', '/') return path.replace('\\', '/')
......
...@@ -26,7 +26,11 @@ ...@@ -26,7 +26,11 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import functools
import mock
import optparse import optparse
import sys
import unittest
from blinkpy.common.system import output_capture from blinkpy.common.system import output_capture
from blinkpy.common.system.executive_mock import MockExecutive from blinkpy.common.system.executive_mock import MockExecutive
...@@ -108,6 +112,41 @@ class WinPortTest(port_testcase.PortTestCase): ...@@ -108,6 +112,41 @@ class WinPortTest(port_testcase.PortTestCase):
def test_operating_system(self): def test_operating_system(self):
self.assertEqual('win', self.make_port().operating_system()) self.assertEqual('win', self.make_port().operating_system())
@unittest.skipIf(sys.platform != 'win32',
'Needs WindowsError to be defined')
def test_python3_command(self):
def run_command_fn(valid_python, args):
if args[0] != valid_python:
raise WindowsError('%s != %s' % (valid_python, args[0]))
return 0
with mock.patch('os.getenv', return_value='.BAT;.EXE;.COM'):
# Simple case: one of the extensions match.
port = self.make_port()
port._executive = MockExecutive(run_command_fn=functools.partial(
run_command_fn, 'python3.EXE'))
self.assertEqual('python3.EXE', port.python3_command())
# Ensure that the code checks for python3 without an extension.
port = self.make_port()
port._executive = MockExecutive(
run_command_fn=functools.partial(run_command_fn, 'python3'))
self.assertEqual('python3', port.python3_command())
# If there are no matches, a WindowsError should be raised.
port = self.make_port()
port._executive = MockExecutive(
run_command_fn=functools.partial(run_command_fn, None))
with self.assertRaises(WindowsError):
port.python3_command()
@unittest.skipIf(sys.platform != 'win32', 'Smoketest for Windows only')
def test_python3_command_smoketest(self):
# This is a smoketest to make sure that depot_tools has ensured a valid
# Python 3 is available. If this test fails on a machine, it means that
# there is something wrong with the Python 3 setup.
self.assertIsNotNone(self.make_port().python3_command())
def test_driver_name_option(self): def test_driver_name_option(self):
self.assertTrue( self.assertTrue(
self.make_port()._path_to_driver().endswith('content_shell.exe')) self.make_port()._path_to_driver().endswith('content_shell.exe'))
......
...@@ -62,8 +62,14 @@ class WPTServe(server_base.ServerBase): ...@@ -62,8 +62,14 @@ class WPTServe(server_base.ServerBase):
'handlers') 'handlers')
wpt_script = fs.join(path_to_wpt_root, 'wpt') wpt_script = fs.join(path_to_wpt_root, 'wpt')
start_cmd = [ start_cmd = [
self._port_obj.host.executable, '-u', wpt_script, 'serve', self._port_obj.python3_command(),
'--config', self._config_file, '--doc_root', path_to_wpt_tests, '-u',
wpt_script,
'serve',
'--config',
self._config_file,
'--doc_root',
path_to_wpt_tests,
'--no-h2', '--no-h2',
] ]
......
...@@ -25,7 +25,7 @@ class TestWPTServe(LoggingTestCase): ...@@ -25,7 +25,7 @@ class TestWPTServe(LoggingTestCase):
def test_init_start_cmd_without_ws_handlers(self): def test_init_start_cmd_without_ws_handlers(self):
server = WPTServe(self.port, '/foo') server = WPTServe(self.port, '/foo')
self.assertEqual(server._start_cmd, [ self.assertEqual(server._start_cmd, [
'python', 'python3',
'-u', '-u',
'/mock-checkout/third_party/blink/tools/blinkpy/third_party/wpt/wpt/wpt', '/mock-checkout/third_party/blink/tools/blinkpy/third_party/wpt/wpt/wpt',
'serve', 'serve',
...@@ -41,7 +41,7 @@ class TestWPTServe(LoggingTestCase): ...@@ -41,7 +41,7 @@ class TestWPTServe(LoggingTestCase):
'/test.checkout/wtests/external/wpt/websockets/handlers') '/test.checkout/wtests/external/wpt/websockets/handlers')
server = WPTServe(self.port, '/foo') server = WPTServe(self.port, '/foo')
self.assertEqual(server._start_cmd, [ self.assertEqual(server._start_cmd, [
'python', 'python3',
'-u', '-u',
'/mock-checkout/third_party/blink/tools/blinkpy/third_party/wpt/wpt/wpt', '/mock-checkout/third_party/blink/tools/blinkpy/third_party/wpt/wpt/wpt',
'serve', 'serve',
......
...@@ -47,6 +47,11 @@ crbug.com/1048761 external/wpt/infrastructure/server/wpt-server-http.sub.html [ ...@@ -47,6 +47,11 @@ crbug.com/1048761 external/wpt/infrastructure/server/wpt-server-http.sub.html [
# WPT Test harness doesn't deal with finding an about:blank ref test # WPT Test harness doesn't deal with finding an about:blank ref test
crbug.com/1066130 external/wpt/infrastructure/assumptions/blank.html [ Failure ] crbug.com/1066130 external/wpt/infrastructure/assumptions/blank.html [ Failure ]
# When running under Python 3 on Windows, this test fails to activate backpressure.
crbug.com/1155106 external/wpt/websockets/stream/tentative/backpressure-receive.any.html [ Failure ]
crbug.com/1155106 external/wpt/websockets/stream/tentative/backpressure-receive.any.worker.html [ Failure ]
crbug.com/1155106 external/wpt/websockets/stream/tentative/backpressure-receive.any.sharedworker.html [ Failure ]
# Favicon is not supported by run_web_tests. # Favicon is not supported by run_web_tests.
external/wpt/fetch/metadata/favicon.https.sub.html [ Skip ] external/wpt/fetch/metadata/favicon.https.sub.html [ Skip ]
......
...@@ -9,6 +9,27 @@ for more details about the presubmit API built into depot_tools. ...@@ -9,6 +9,27 @@ for more details about the presubmit API built into depot_tools.
""" """
def python3_command(input_api):
if not input_api.is_windows:
return 'python3'
# The subprocess module on Windows does not look at PATHEXT, so we cannot
# rely on 'python3' working. Instead we must check each possible name to
# find the working one.
input_api.logging.debug('Searching for Python 3 command name')
exts = filter(len, input_api.environ.get('PATHEXT', '').split(';'))
for ext in [''] + exts:
python = 'python3%s' % ext
input_api.logging.debug('Trying "%s"' % python)
try:
input_api.subprocess.check_output([python, '--version'])
return python
except WindowsError:
pass
raise WindowsError('Unable to find a valid python3 command name')
def _LintWPT(input_api, output_api): def _LintWPT(input_api, output_api):
"""Lint functionality duplicated from web-platform-tests upstream. """Lint functionality duplicated from web-platform-tests upstream.
...@@ -32,7 +53,7 @@ def _LintWPT(input_api, output_api): ...@@ -32,7 +53,7 @@ def _LintWPT(input_api, output_api):
return [] return []
args = [ args = [
input_api.python_executable, python3_command(input_api),
linter_path, linter_path,
'lint', 'lint',
'--repo-root=%s' % wpt_path, '--repo-root=%s' % wpt_path,
......
...@@ -20,6 +20,9 @@ class MockInputApi(object): ...@@ -20,6 +20,9 @@ class MockInputApi(object):
self.os_path = os.path self.os_path = os.path
self.python_executable = sys.executable self.python_executable = sys.executable
self.subprocess = subprocess self.subprocess = subprocess
self.is_windows = sys.platform == 'win32'
self.environ = os.environ
self.logging = PrintLogger()
def AbsoluteLocalPaths(self): def AbsoluteLocalPaths(self):
return self.affected_paths return self.affected_paths
...@@ -32,6 +35,13 @@ class MockInputApi(object): ...@@ -32,6 +35,13 @@ class MockInputApi(object):
return filter(lambda f: filter_func(f), all_files) return filter(lambda f: filter_func(f), all_files)
class PrintLogger(object):
"""A simple logger that just prints log messages."""
def debug(self, message):
print(message)
class MockPresubmitError(object): class MockPresubmitError(object):
"""A minimal mock of an error class for our checks.""" """A minimal mock of an error class for our checks."""
......
...@@ -35,6 +35,6 @@ def main(request, response): ...@@ -35,6 +35,6 @@ def main(request, response):
if len(matches) != 1: if len(matches) != 1:
return 404, [], '{} origin policies found at a path matching "{}"'.format(len(matches), filepath_pattern) return 404, [], '{} origin policies found at a path matching "{}"'.format(len(matches), filepath_pattern)
with open(matches[0]) as f: with open(matches[0], 'rb') as f:
data = f.read() data = f.read()
return return_code, [('Content-Type', content_type)], data return return_code, [('Content-Type', content_type)], data
...@@ -21,7 +21,7 @@ def main(request, response): ...@@ -21,7 +21,7 @@ def main(request, response):
try: try:
if b'drop' in request.GET: if b'drop' in request.GET:
cookie = request.GET[b'drop'] cookie = request.GET[b'drop']
cookie += "; max-age=0" cookie += b'; max-age=0'
if b'set' in request.GET: if b'set' in request.GET:
cookie = request.GET[b'set'] cookie = request.GET[b'set']
......
...@@ -9,6 +9,10 @@ def main(request, response): ...@@ -9,6 +9,10 @@ def main(request, response):
] ]
key = request.GET.first(b"key", None) key = request.GET.first(b"key", None)
# We serialize the key into JSON, so have to decode it first.
if key is not None:
key = key.decode('utf-8')
body = u""" body = u"""
<!DOCTYPE html> <!DOCTYPE html>
<script src="/portals/resources/stash-utils.sub.js"></script> <script src="/portals/resources/stash-utils.sub.js"></script>
......
...@@ -8,9 +8,9 @@ PASS Verify a cors worker script served by a service worker fails shared worker ...@@ -8,9 +8,9 @@ PASS Verify a cors worker script served by a service worker fails shared worker
PASS Verify a no-cors cross-origin worker script served by a service worker fails dedicated worker start. PASS Verify a no-cors cross-origin worker script served by a service worker fails dedicated worker start.
PASS Verify a no-cors cross-origin worker script served by a service worker fails shared worker start. PASS Verify a no-cors cross-origin worker script served by a service worker fails shared worker start.
PASS Register a service worker for worker subresource interception tests. PASS Register a service worker for worker subresource interception tests.
FAIL Requests on a dedicated worker controlled by a service worker. assert_equals: expected "This load was successfully intercepted." but got "{\"error\": {\"message\": \"\", \"code\": 404}}" FAIL Requests on a dedicated worker controlled by a service worker. assert_equals: expected "This load was successfully intercepted." but got "{\"error\": {\"code\": 404, \"message\": \"\"}}"
PASS Requests on a shared worker controlled by a service worker. PASS Requests on a shared worker controlled by a service worker.
FAIL Requests on a dedicated worker nested in a dedicated worker and controlled by a service worker assert_equals: expected "This load was successfully intercepted." but got "{\"error\": {\"message\": \"\", \"code\": 404}}" FAIL Requests on a dedicated worker nested in a dedicated worker and controlled by a service worker assert_equals: expected "This load was successfully intercepted." but got "{\"error\": {\"code\": 404, \"message\": \"\"}}"
FAIL Requests on a dedicated worker nested in a shared worker and controlled by a service worker assert_equals: expected "This load was successfully intercepted." but got "Unexpected error! Worker is not defined" FAIL Requests on a dedicated worker nested in a shared worker and controlled by a service worker assert_equals: expected "This load was successfully intercepted." but got "Unexpected error! Worker is not defined"
PASS Unregister a service worker for subresource interception tests. PASS Unregister a service worker for subresource interception tests.
Harness: the test ran to completion. Harness: the test ran to completion.
......
This is a testharness.js-based test. This is a testharness.js-based test.
FAIL worklets interfaces promise_test: Unhandled rejection with value: object "WebIDLParseError: Syntax error at line 1: FAIL worklets interfaces promise_test: Unhandled rejection with value: object "WebIDLParseError: Syntax error at line 1:
{"error": {"message" {"error": {"code"
^ Unrecognised tokens" ^ Unrecognised tokens"
Harness: the test ran to completion. Harness: the test ran to completion.
...@@ -10,6 +10,6 @@ def main(request, response): ...@@ -10,6 +10,6 @@ def main(request, response):
response.headers.set(b'WWW-Authenticate', b'Basic realm="test"') response.headers.set(b'WWW-Authenticate', b'Basic realm="test"')
content = b'User name/password wrong or not given: ' content = b'User name/password wrong or not given: '
content += b"%s\n%s" % (request.auth.username, content += b"%s\n%s" % (request.auth.username or b'',
request.auth.password) request.auth.password or b'')
return content return content
...@@ -10,17 +10,17 @@ def main(request, response): ...@@ -10,17 +10,17 @@ def main(request, response):
token = "ArQvBL/jhDJ62HaUm/ak0dIUYDjZAfeCQTXwa92cOrHZbL7R+bhb3qrVO2pHWkgJPgvIzvLX5m3wfaUJfOKY0Q4AAABqeyJvcmlnaW4iOiAiaHR0cHM6Ly93d3cud2ViLXBsYXRmb3JtLnRlc3Q6ODQ0NCIsICJmZWF0dXJlIjogIk9yaWdpbklzb2xhdGlvbkhlYWRlciIsICJleHBpcnkiOiAyMDAwMDAwMDAwfQ==" token = "ArQvBL/jhDJ62HaUm/ak0dIUYDjZAfeCQTXwa92cOrHZbL7R+bhb3qrVO2pHWkgJPgvIzvLX5m3wfaUJfOKY0Q4AAABqeyJvcmlnaW4iOiAiaHR0cHM6Ly93d3cud2ViLXBsYXRmb3JtLnRlc3Q6ODQ0NCIsICJmZWF0dXJlIjogIk9yaWdpbklzb2xhdGlvbkhlYWRlciIsICJleHBpcnkiOiAyMDAwMDAwMDAwfQ=="
header_order = request.GET.first("headerOrder") header_order = request.GET.first(b"headerOrder")
if header_order == "otoi": if header_order == b"otoi":
response.headers.set("Origin-Trial", token) response.headers.set(b"Origin-Trial", token)
response.headers.set("Origin-Isolation", "?1") response.headers.set(b"Origin-Isolation", b"?1")
elif header_order == "oiot": elif header_order == b"oiot":
response.headers.set("Origin-Isolation", "?1") response.headers.set(b"Origin-Isolation", b"?1")
response.headers.set("Origin-Trial", token) response.headers.set(b"Origin-Trial", token)
else: else:
raise AssertionError("Invalid headerOrder") raise AssertionError("Invalid headerOrder")
response.headers.set("Content-Type", "text/html") response.headers.set(b"Content-Type", b"text/html")
return """ return """
<!DOCTYPE html> <!DOCTYPE html>
......
...@@ -3,11 +3,11 @@ import json ...@@ -3,11 +3,11 @@ import json
def main(request, response): def main(request, response):
op = request.GET.first("op") op = request.GET.first(b"op")
id = request.GET.first("id") id = request.GET.first(b"id")
timeout = 1.0 timeout = 1.0
if op == "retrieve": if op == b"retrieve":
t0 = time.time() t0 = time.time()
while time.time() - t0 < timeout: while time.time() - t0 < timeout:
time.sleep(0.1) time.sleep(0.1)
...@@ -23,4 +23,4 @@ def main(request, response): ...@@ -23,4 +23,4 @@ def main(request, response):
request.server.stash.put(key=id, value=json.dumps({'url': request.url})) request.server.stash.put(key=id, value=json.dumps({'url': request.url}))
# return acknowledgement report # return acknowledgement report
return [("Content-Type", "text/plain")], "Recorded report " + id return [("Content-Type", "text/plain")], b"Recorded report " + id
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment