| 1 | // vtest flaky: true |
| 2 | // vtest retry: 3 |
| 3 | // vtest build: !windows && !docker-ubuntu-musl && !sanitize-memory-clang && !sanitize-address-clang-without-gc |
| 4 | import os |
| 5 | import time |
| 6 | import net.http |
| 7 | |
| 8 | const sport = 13085 |
| 9 | const localserver = '127.0.0.1:${sport}' |
| 10 | const exit_after_time = 30000 |
| 11 | const vexe = os.getenv('VEXE') |
| 12 | const vroot = os.dir(vexe) |
| 13 | const serverexe = os.join_path(os.cache_dir(), 'veb_memory_test_server.exe') |
| 14 | |
| 15 | fn testsuite_begin() { |
| 16 | os.chdir(vroot) or {} |
| 17 | if os.exists(serverexe) { |
| 18 | os.rm(serverexe) or {} |
| 19 | } |
| 20 | } |
| 21 | |
| 22 | fn testsuite_end() { |
| 23 | if os.exists(serverexe) { |
| 24 | os.rm(serverexe) or {} |
| 25 | } |
| 26 | } |
| 27 | |
| 28 | fn test_server_compiles() { |
| 29 | did_compile := |
| 30 | os.system('${os.quoted_path(vexe)} -o ${os.quoted_path(serverexe)} vlib/veb/tests/memory_leak_test_server.v') |
| 31 | assert did_compile == 0 |
| 32 | assert os.exists(serverexe) |
| 33 | } |
| 34 | |
| 35 | fn test_server_runs_in_background() { |
| 36 | spawn os.system('${os.quoted_path(serverexe)} ${sport} ${exit_after_time}') |
| 37 | for _ in 0 .. 50 { |
| 38 | resp := http.get('http://${localserver}/heap') or { |
| 39 | time.sleep(100 * time.millisecond) |
| 40 | continue |
| 41 | } |
| 42 | if resp.status_code == 200 { |
| 43 | return |
| 44 | } |
| 45 | time.sleep(100 * time.millisecond) |
| 46 | } |
| 47 | assert false, 'Timed out waiting for background server to start' |
| 48 | } |
| 49 | |
| 50 | fn test_large_responses_work_correctly() { |
| 51 | // Make requests with large response bodies and verify they work |
| 52 | request_count := 20 |
| 53 | mut successful := 0 |
| 54 | for _ in 0 .. request_count { |
| 55 | resp := http.get('http://${localserver}/large') or { continue } |
| 56 | if resp.status_code == 200 && resp.body.len > 50000 { |
| 57 | successful += 1 |
| 58 | } |
| 59 | } |
| 60 | // Ensure most requests succeeded (allows for some network variability) |
| 61 | assert successful >= request_count - 2, 'Only ${successful}/${request_count} requests succeeded' |
| 62 | } |
| 63 | |
| 64 | fn test_heap_usage_endpoint_works() { |
| 65 | resp := http.get('http://${localserver}/heap') or { |
| 66 | assert false, 'Failed to get heap usage: ${err}' |
| 67 | return |
| 68 | } |
| 69 | heap := resp.body.i64() |
| 70 | // Just verify we get a reasonable value (more than 1MB, less than 1GB) |
| 71 | assert heap > 1024 * 1024, 'Heap usage ${heap} seems too low' |
| 72 | assert heap < 1024 * 1024 * 1024, 'Heap usage ${heap} seems too high' |
| 73 | } |
| 74 | |
| 75 | fn test_gc_collect_endpoint_works() { |
| 76 | resp := http.get('http://${localserver}/gc') or { |
| 77 | assert false, 'Failed to trigger GC: ${err}' |
| 78 | return |
| 79 | } |
| 80 | assert resp.status_code == 200 |
| 81 | } |
| 82 | |
| 83 | fn test_multipart_upload_does_not_expand_heap_excessively() { |
| 84 | http.get('http://${localserver}/gc') or { |
| 85 | assert false, 'Failed to trigger GC before measuring heap: ${err}' |
| 86 | return |
| 87 | } |
| 88 | before_resp := http.get('http://${localserver}/heap') or { |
| 89 | assert false, 'Failed to get baseline heap usage: ${err}' |
| 90 | return |
| 91 | } |
| 92 | before_heap := before_resp.body.i64() |
| 93 | payload_size := 1024 * 1024 |
| 94 | mut files := []http.FileData{} |
| 95 | files << http.FileData{ |
| 96 | filename: 'payload.bin' |
| 97 | content_type: 'application/octet-stream' |
| 98 | data: 'X'.repeat(payload_size) |
| 99 | } |
| 100 | resp := http.post_multipart_form('http://${localserver}/upload', http.PostMultipartFormConfig{ |
| 101 | files: { |
| 102 | 'file': files |
| 103 | } |
| 104 | }) or { |
| 105 | assert false, 'Failed to post multipart upload: ${err}' |
| 106 | return |
| 107 | } |
| 108 | assert resp.status_code == 200 |
| 109 | assert resp.body == payload_size.str() |
| 110 | http.get('http://${localserver}/gc') or { |
| 111 | assert false, 'Failed to trigger GC after upload: ${err}' |
| 112 | return |
| 113 | } |
| 114 | after_resp := http.get('http://${localserver}/heap') or { |
| 115 | assert false, 'Failed to get post-upload heap usage: ${err}' |
| 116 | return |
| 117 | } |
| 118 | after_heap := after_resp.body.i64() |
| 119 | heap_growth := if after_heap > before_heap { after_heap - before_heap } else { i64(0) } |
| 120 | assert heap_growth < 8 * 1024 * 1024, 'Multipart upload grew heap by ${heap_growth} bytes' |
| 121 | } |
| 122 | |