v2 / vlib / veb / tests / memory_leak_test.v
121 lines · 112 sloc · 3.51 KB · 6007ef44f6144cc7fc70e6bb5954d58f19898b16
Raw
1// vtest flaky: true
2// vtest retry: 3
3// vtest build: !windows && !docker-ubuntu-musl && !sanitize-memory-clang && !sanitize-address-clang-without-gc
4import os
5import time
6import net.http
7
8const sport = 13085
9const localserver = '127.0.0.1:${sport}'
10const exit_after_time = 30000
11const vexe = os.getenv('VEXE')
12const vroot = os.dir(vexe)
13const serverexe = os.join_path(os.cache_dir(), 'veb_memory_test_server.exe')
14
15fn testsuite_begin() {
16 os.chdir(vroot) or {}
17 if os.exists(serverexe) {
18 os.rm(serverexe) or {}
19 }
20}
21
22fn testsuite_end() {
23 if os.exists(serverexe) {
24 os.rm(serverexe) or {}
25 }
26}
27
28fn 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
35fn 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
50fn 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
64fn 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
75fn 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
83fn 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