From 6ce3e2692acdc3722f8948f36344de522be2e0bc Mon Sep 17 00:00:00 2001 From: Bas Hendri Date: Mon, 30 Jun 2025 14:37:39 -0700 Subject: [PATCH 1/4] fixes redirect test to use localhost --- test/common.py | 144 ++++++++----------------------- test/fetch/test_fetch_redirect.c | 74 ++++++++++++---- 2 files changed, 96 insertions(+), 122 deletions(-) diff --git a/test/common.py b/test/common.py index cfa517a24ea72..c3efb3b223108 100644 --- a/test/common.py +++ b/test/common.py @@ -2148,6 +2148,25 @@ def do_POST(self): create_file(filename, post_data, binary=True) self.send_response(200) self.end_headers() + elif urlinfo.path.startswith('/status/'): + code_str = urlinfo.path[len('/status/'):] + try: + code = int(code_str) + except ValueError: + self.send_error(400, 'Invalid status code') + return + if code in (301, 302, 303, 307, 308): + self.send_response(code) + self.send_header('Location', '/status/200') + self.end_headers() + elif code == 200: + self.send_response(200) + self.send_header('Content-type', 'text/plain') + self.end_headers() + self.wfile.write(b'OK') + else: + self.send_error(code) + return else: print(f'do_POST: unexpected POST: {urlinfo}') @@ -2160,6 +2179,25 @@ def do_GET(self): self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(read_binary(test_file('browser_harness.html'))) + elif info.path.startswith('/status/'): + code_str = info.path[len('/status/'):] + try: + code = int(code_str) + except ValueError: + self.send_error(400, 'Invalid status code') + return + if code in (301, 302, 303, 307, 308): + # Redirect to /status/200 + self.send_response(code) + self.send_header('Location', '/status/200') + self.end_headers() + elif code == 200: + self.send_response(200) + self.send_header('Content-type', 'text/plain') + self.end_headers() + self.wfile.write(b'OK') + else: + self.send_error(code) elif 'report_' in self.path: # the test is reporting its result. first change dir away from the # test dir, as it will be deleted now that the test is finishing, and @@ -2466,109 +2504,3 @@ def btest(self, filename, expected=None, self.assertContained('RESULT: ' + expected[0], output) else: self.run_browser(outfile, expected=['/report_result?' + e for e in expected], timeout=timeout, extra_tries=extra_tries) - - -################################################################################################### - - -def build_library(name, - build_dir, - generated_libs, - configure, - make, - make_args, - cache, - cache_name, - env_init, - native): - """Build a library and cache the result. We build the library file - once and cache it for all our tests. (We cache in memory since the test - directory is destroyed and recreated for each test. Note that we cache - separately for different compilers). This cache is just during the test - runner. There is a different concept of caching as well, see |Cache|. - """ - - if type(generated_libs) is not list: - generated_libs = [generated_libs] - source_dir = test_file(name.replace('_native', '')) - - project_dir = Path(build_dir, name) - if os.path.exists(project_dir): - shutil.rmtree(project_dir) - # Useful in debugging sometimes to comment this out, and two lines above - shutil.copytree(source_dir, project_dir) - - generated_libs = [os.path.join(project_dir, lib) for lib in generated_libs] - - if native: - env = clang_native.get_clang_native_env() - else: - env = os.environ.copy() - env.update(env_init) - - if not native: - # Inject emcmake, emconfigure or emmake accordingly, but only if we are - # cross compiling. - if configure: - if configure[0] == 'cmake': - configure = [EMCMAKE] + configure - else: - configure = [EMCONFIGURE] + configure - else: - make = [EMMAKE] + make - - if configure: - try: - with open(os.path.join(project_dir, 'configure_out'), 'w') as out: - with open(os.path.join(project_dir, 'configure_err'), 'w') as err: - stdout = out if EMTEST_BUILD_VERBOSE < 2 else None - stderr = err if EMTEST_BUILD_VERBOSE < 1 else None - shared.run_process(configure, env=env, stdout=stdout, stderr=stderr, - cwd=project_dir) - except subprocess.CalledProcessError: - print('-- configure stdout --') - print(read_file(Path(project_dir, 'configure_out'))) - print('-- end configure stdout --') - print('-- configure stderr --') - print(read_file(Path(project_dir, 'configure_err'))) - print('-- end configure stderr --') - raise - # if we run configure or cmake we don't then need any kind - # of special env when we run make below - env = None - - def open_make_out(mode='r'): - return open(os.path.join(project_dir, 'make.out'), mode) - - def open_make_err(mode='r'): - return open(os.path.join(project_dir, 'make.err'), mode) - - if EMTEST_BUILD_VERBOSE >= 3: - # VERBOSE=1 is cmake and V=1 is for autoconf - make_args += ['VERBOSE=1', 'V=1'] - - try: - with open_make_out('w') as make_out: - with open_make_err('w') as make_err: - stdout = make_out if EMTEST_BUILD_VERBOSE < 2 else None - stderr = make_err if EMTEST_BUILD_VERBOSE < 1 else None - shared.run_process(make + make_args, stdout=stdout, stderr=stderr, env=env, - cwd=project_dir) - except subprocess.CalledProcessError: - with open_make_out() as f: - print('-- make stdout --') - print(f.read()) - print('-- end make stdout --') - with open_make_err() as f: - print('-- make stderr --') - print(f.read()) - print('-- end stderr --') - raise - - if cache is not None: - cache[cache_name] = [] - for f in generated_libs: - basename = os.path.basename(f) - cache[cache_name].append((basename, read_binary(f))) - - return generated_libs diff --git a/test/fetch/test_fetch_redirect.c b/test/fetch/test_fetch_redirect.c index 39475dd487194..d81298d82a4b0 100644 --- a/test/fetch/test_fetch_redirect.c +++ b/test/fetch/test_fetch_redirect.c @@ -9,27 +9,46 @@ #include #include -int fetchSync() { +#define SERVER "http://localhost:8888" + +const int redirect_codes[] = {301, 302, 303, 307, 308}; +const int num_codes = sizeof(redirect_codes) / sizeof(redirect_codes[0]); + +void check_fetch_result(emscripten_fetch_t *fetch, int expected_status, const char *expected_url) { + printf("Fetch finished with status %d\n", fetch->status); + assert(fetch->status == expected_status); + printf("Downloaded %llu bytes\n", fetch->numBytes); + assert(strcmp(fetch->responseUrl, expected_url) == 0); +} + +void fetchSyncTest(int code, const char *method) { + char url[128]; + snprintf(url, sizeof(url), SERVER "/status/%d", code); emscripten_fetch_attr_t attr; emscripten_fetch_attr_init(&attr); - strcpy(attr.requestMethod, "GET"); + strcpy(attr.requestMethod, method); attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_SYNCHRONOUS | EMSCRIPTEN_FETCH_REPLACE; - emscripten_fetch_t *fetch = emscripten_fetch(&attr, "https://httpbin.org/status/307"); + emscripten_fetch_t *fetch = emscripten_fetch(&attr, url); assert(fetch); - printf("Fetch sync finished with status %d\n", fetch->status); - assert(fetch->status == 200); - printf("Downloaded %llu bytes", fetch->numBytes); - assert(strcmp(fetch->responseUrl, "https://httpbin.org/get") == 0); - exit(0); + check_fetch_result(fetch, 200, SERVER "/status/200"); + emscripten_fetch_close(fetch); } +void onsuccess(emscripten_fetch_t *fetch); +void onreadystatechange(emscripten_fetch_t *fetch); + +// State for async test +static int async_code_idx = 0; +static int async_method_idx = 0; +const char *methods[] = {"GET", "POST"}; +const int num_methods = 2; + +void start_next_async_fetch(); + void onsuccess(emscripten_fetch_t *fetch) { - printf("Fetch async finished with status %d\n", fetch->status); - assert(fetch->status == 200); - printf("Downloaded %llu bytes", fetch->numBytes); - assert(strcmp(fetch->responseUrl, "https://httpbin.org/get") == 0); + check_fetch_result(fetch, 200, SERVER "/status/200"); emscripten_fetch_close(fetch); - fetchSync(); + start_next_async_fetch(); } void onreadystatechange(emscripten_fetch_t *fetch) { @@ -41,14 +60,37 @@ void onreadystatechange(emscripten_fetch_t *fetch) { } } -int main() { +void start_next_async_fetch() { + if (async_code_idx >= num_codes) { + async_code_idx = 0; + async_method_idx++; + if (async_method_idx >= num_methods) { + // All async tests done, now run sync tests + for (int m = 0; m < num_methods; ++m) { + for (int i = 0; i < num_codes; ++i) { + fetchSyncTest(redirect_codes[i], methods[m]); + } + } + exit(0); + } + } + int code = redirect_codes[async_code_idx++]; + const char *method = methods[async_method_idx]; + char url[128]; + snprintf(url, sizeof(url), SERVER "/status/%d", code); emscripten_fetch_attr_t attr; emscripten_fetch_attr_init(&attr); - strcpy(attr.requestMethod, "GET"); + strcpy(attr.requestMethod, method); attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; attr.onsuccess = onsuccess; attr.onreadystatechange = onreadystatechange; - emscripten_fetch_t *fetch = emscripten_fetch(&attr, "https://httpbin.org/status/307"); + emscripten_fetch_t *fetch = emscripten_fetch(&attr, url); assert(fetch); +} + +int main() { + async_code_idx = 0; + async_method_idx = 0; + start_next_async_fetch(); return 99; } From c4636f2572f007ced75fd10fabd6e15a51b2a64a Mon Sep 17 00:00:00 2001 From: Bas Hendri Date: Mon, 30 Jun 2025 15:57:11 -0700 Subject: [PATCH 2/4] adds missed sections --- test/fetch/test_fetch_redirect.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/fetch/test_fetch_redirect.c b/test/fetch/test_fetch_redirect.c index d81298d82a4b0..33ce34e9fa0fb 100644 --- a/test/fetch/test_fetch_redirect.c +++ b/test/fetch/test_fetch_redirect.c @@ -11,6 +11,11 @@ #define SERVER "http://localhost:8888" +// 301: Moved Permanently - https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/301 +// 302: Found (Previously "Moved Temporarily") - https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302 +// 303: See Other - https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303 +// 307: Temporary Redirect - https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307 +// 308: Permanent Redirect - https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308 const int redirect_codes[] = {301, 302, 303, 307, 308}; const int num_codes = sizeof(redirect_codes) / sizeof(redirect_codes[0]); @@ -56,7 +61,7 @@ void onreadystatechange(emscripten_fetch_t *fetch) { if (fetch->readyState < 2) { assert(NULL == fetch->responseUrl); } else { - assert(0 == strcmp(fetch->responseUrl, "https://httpbin.org/get")); + assert(0 == strcmp(fetch->responseUrl, SERVER "/status/200")); } } From 150e3b1a61dec3f8d15645b7152f83a4b41f08c5 Mon Sep 17 00:00:00 2001 From: Bas Hendri Date: Mon, 30 Jun 2025 15:59:28 -0700 Subject: [PATCH 3/4] fixes bad merge --- test/common.py | 106 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/test/common.py b/test/common.py index c3efb3b223108..461444aa82c30 100644 --- a/test/common.py +++ b/test/common.py @@ -2504,3 +2504,109 @@ def btest(self, filename, expected=None, self.assertContained('RESULT: ' + expected[0], output) else: self.run_browser(outfile, expected=['/report_result?' + e for e in expected], timeout=timeout, extra_tries=extra_tries) + + +################################################################################################### + + +def build_library(name, + build_dir, + generated_libs, + configure, + make, + make_args, + cache, + cache_name, + env_init, + native): + """Build a library and cache the result. We build the library file + once and cache it for all our tests. (We cache in memory since the test + directory is destroyed and recreated for each test. Note that we cache + separately for different compilers). This cache is just during the test + runner. There is a different concept of caching as well, see |Cache|. + """ + + if type(generated_libs) is not list: + generated_libs = [generated_libs] + source_dir = test_file(name.replace('_native', '')) + + project_dir = Path(build_dir, name) + if os.path.exists(project_dir): + shutil.rmtree(project_dir) + # Useful in debugging sometimes to comment this out, and two lines above + shutil.copytree(source_dir, project_dir) + + generated_libs = [os.path.join(project_dir, lib) for lib in generated_libs] + + if native: + env = clang_native.get_clang_native_env() + else: + env = os.environ.copy() + env.update(env_init) + + if not native: + # Inject emcmake, emconfigure or emmake accordingly, but only if we are + # cross compiling. + if configure: + if configure[0] == 'cmake': + configure = [EMCMAKE] + configure + else: + configure = [EMCONFIGURE] + configure + else: + make = [EMMAKE] + make + + if configure: + try: + with open(os.path.join(project_dir, 'configure_out'), 'w') as out: + with open(os.path.join(project_dir, 'configure_err'), 'w') as err: + stdout = out if EMTEST_BUILD_VERBOSE < 2 else None + stderr = err if EMTEST_BUILD_VERBOSE < 1 else None + shared.run_process(configure, env=env, stdout=stdout, stderr=stderr, + cwd=project_dir) + except subprocess.CalledProcessError: + print('-- configure stdout --') + print(read_file(Path(project_dir, 'configure_out'))) + print('-- end configure stdout --') + print('-- configure stderr --') + print(read_file(Path(project_dir, 'configure_err'))) + print('-- end configure stderr --') + raise + # if we run configure or cmake we don't then need any kind + # of special env when we run make below + env = None + + def open_make_out(mode='r'): + return open(os.path.join(project_dir, 'make.out'), mode) + + def open_make_err(mode='r'): + return open(os.path.join(project_dir, 'make.err'), mode) + + if EMTEST_BUILD_VERBOSE >= 3: + # VERBOSE=1 is cmake and V=1 is for autoconf + make_args += ['VERBOSE=1', 'V=1'] + + try: + with open_make_out('w') as make_out: + with open_make_err('w') as make_err: + stdout = make_out if EMTEST_BUILD_VERBOSE < 2 else None + stderr = make_err if EMTEST_BUILD_VERBOSE < 1 else None + shared.run_process(make + make_args, stdout=stdout, stderr=stderr, env=env, + cwd=project_dir) + except subprocess.CalledProcessError: + with open_make_out() as f: + print('-- make stdout --') + print(f.read()) + print('-- end make stdout --') + with open_make_err() as f: + print('-- make stderr --') + print(f.read()) + print('-- end stderr --') + raise + + if cache is not None: + cache[cache_name] = [] + for f in generated_libs: + basename = os.path.basename(f) + cache[cache_name].append((basename, read_binary(f))) + + return generated_libs From 4e03fba8222db06e3aff10a904a9bd507fe60bab Mon Sep 17 00:00:00 2001 From: Bas Hendri Date: Mon, 7 Jul 2025 16:10:56 -0700 Subject: [PATCH 4/4] addresses feedback --- test/common.py | 17 ++++------------- test/fetch/test_fetch_redirect.c | 2 -- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/test/common.py b/test/common.py index 461444aa82c30..dd1f88b640ab8 100644 --- a/test/common.py +++ b/test/common.py @@ -2150,11 +2150,7 @@ def do_POST(self): self.end_headers() elif urlinfo.path.startswith('/status/'): code_str = urlinfo.path[len('/status/'):] - try: - code = int(code_str) - except ValueError: - self.send_error(400, 'Invalid status code') - return + code = int(code_str) if code in (301, 302, 303, 307, 308): self.send_response(code) self.send_header('Location', '/status/200') @@ -2165,8 +2161,7 @@ def do_POST(self): self.end_headers() self.wfile.write(b'OK') else: - self.send_error(code) - return + self.send_error(400, f'Not implemented for {code}') else: print(f'do_POST: unexpected POST: {urlinfo}') @@ -2181,11 +2176,7 @@ def do_GET(self): self.wfile.write(read_binary(test_file('browser_harness.html'))) elif info.path.startswith('/status/'): code_str = info.path[len('/status/'):] - try: - code = int(code_str) - except ValueError: - self.send_error(400, 'Invalid status code') - return + code = int(code_str) if code in (301, 302, 303, 307, 308): # Redirect to /status/200 self.send_response(code) @@ -2197,7 +2188,7 @@ def do_GET(self): self.end_headers() self.wfile.write(b'OK') else: - self.send_error(code) + self.send_error(400, f'Not implemented for {code}') elif 'report_' in self.path: # the test is reporting its result. first change dir away from the # test dir, as it will be deleted now that the test is finishing, and diff --git a/test/fetch/test_fetch_redirect.c b/test/fetch/test_fetch_redirect.c index 33ce34e9fa0fb..2b96f1e9063e8 100644 --- a/test/fetch/test_fetch_redirect.c +++ b/test/fetch/test_fetch_redirect.c @@ -94,8 +94,6 @@ void start_next_async_fetch() { } int main() { - async_code_idx = 0; - async_method_idx = 0; start_next_async_fetch(); return 99; }