| 1 | import os |
| 2 | import strings |
| 3 | |
| 4 | const callback_vexe = @VEXE |
| 5 | const callback_tests_dir = os.dir(@FILE) |
| 6 | const callback_v3_dir = os.dir(callback_tests_dir) |
| 7 | const callback_vlib_dir = os.dir(callback_v3_dir) |
| 8 | const callback_v3_src = os.join_path(callback_v3_dir, 'v3.v') |
| 9 | |
| 10 | fn callback_build_v3() string { |
| 11 | return callback_build_v3_with_flags('serial', '') |
| 12 | } |
| 13 | |
| 14 | fn callback_build_v3_parallel() string { |
| 15 | return callback_build_v3_with_flags('parallel', '-d parallel') |
| 16 | } |
| 17 | |
| 18 | fn callback_build_v3_with_flags(name string, flags string) string { |
| 19 | v3_bin := os.join_path(os.temp_dir(), 'v3_callback_userdata_test_${name}_${os.getpid()}') |
| 20 | os.rm(v3_bin) or {} |
| 21 | build := |
| 22 | os.execute('${callback_vexe} ${flags} -gc none -path "${callback_vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${callback_v3_src}') |
| 23 | assert build.exit_code == 0, build.output |
| 24 | return v3_bin |
| 25 | } |
| 26 | |
| 27 | fn callback_write_source(name string, source string) string { |
| 28 | path := os.join_path(os.temp_dir(), 'v3_callback_userdata_${name}_${os.getpid()}.v') |
| 29 | os.write_file(path, source) or { panic(err) } |
| 30 | return path |
| 31 | } |
| 32 | |
| 33 | fn callback_compile(v3_bin string, source string, name string) os.Result { |
| 34 | out := os.join_path(os.temp_dir(), 'v3_callback_userdata_${name}_${os.getpid()}') |
| 35 | return os.execute('${v3_bin} ${source} -b c -o ${out}') |
| 36 | } |
| 37 | |
| 38 | fn callback_write_project_file(root string, rel string, source string) string { |
| 39 | path := os.join_path(root, rel) |
| 40 | os.mkdir_all(os.dir(path)) or { panic(err) } |
| 41 | os.write_file(path, source) or { panic(err) } |
| 42 | return path |
| 43 | } |
| 44 | |
| 45 | fn callback_adapter_names(generated string, prefix string) []string { |
| 46 | mut names := []string{} |
| 47 | for line in generated.split_into_lines() { |
| 48 | if line.starts_with('static void ${prefix}') { |
| 49 | name := line.all_after('static void ').all_before('(') |
| 50 | if name !in names { |
| 51 | names << name |
| 52 | } |
| 53 | } |
| 54 | } |
| 55 | return names |
| 56 | } |
| 57 | |
| 58 | fn test_callback_userdata_fn_field_adapters() { |
| 59 | v3_bin := callback_build_v3() |
| 60 | good_src := callback_write_source('good', 'module main |
| 61 | |
| 62 | struct Event { |
| 63 | code int |
| 64 | } |
| 65 | |
| 66 | struct App { |
| 67 | mut: |
| 68 | frames int |
| 69 | events int |
| 70 | directs int |
| 71 | } |
| 72 | |
| 73 | struct Config { |
| 74 | frame fn (voidptr) |
| 75 | event fn (&Event, voidptr) |
| 76 | native_event fn (&Event, voidptr) |
| 77 | direct fn (voidptr) |
| 78 | user_data voidptr |
| 79 | } |
| 80 | |
| 81 | type FrameAlias = fn (voidptr) |
| 82 | type EventAlias = fn (&Event, voidptr) |
| 83 | |
| 84 | struct AliasConfig { |
| 85 | alias_frame FrameAlias |
| 86 | alias_event EventAlias |
| 87 | user_data voidptr |
| 88 | } |
| 89 | |
| 90 | struct StructAliasTarget { |
| 91 | alias2_frame FrameAlias |
| 92 | alias2_event EventAlias |
| 93 | user_data voidptr |
| 94 | } |
| 95 | |
| 96 | type StructAlias = StructAliasTarget |
| 97 | |
| 98 | @[params] |
| 99 | struct Params { |
| 100 | frame fn (voidptr) |
| 101 | event fn (&Event, voidptr) |
| 102 | native_event fn (&Event, voidptr) |
| 103 | direct fn (voidptr) |
| 104 | user_data voidptr |
| 105 | } |
| 106 | |
| 107 | fn run_config(mut app App, cfg Config) int { |
| 108 | cfg.frame(cfg.user_data) |
| 109 | e := Event{code: 3} |
| 110 | cfg.event(&e, cfg.user_data) |
| 111 | cfg.native_event(&e, cfg.user_data) |
| 112 | cfg.direct(cfg.user_data) |
| 113 | return app.frames + app.events + app.directs |
| 114 | } |
| 115 | |
| 116 | fn run_params(mut app App, params Params) int { |
| 117 | params.frame(params.user_data) |
| 118 | e := Event{code: 4} |
| 119 | params.event(&e, params.user_data) |
| 120 | params.native_event(&e, params.user_data) |
| 121 | params.direct(params.user_data) |
| 122 | return app.frames + app.events + app.directs |
| 123 | } |
| 124 | |
| 125 | fn use_cb(cb fn (voidptr), data voidptr) { |
| 126 | cb(data) |
| 127 | } |
| 128 | |
| 129 | fn frame(mut app App) { |
| 130 | app.frames++ |
| 131 | } |
| 132 | |
| 133 | fn on_event(e &Event, mut app App) { |
| 134 | app.events += e.code |
| 135 | } |
| 136 | |
| 137 | fn erased_event(e voidptr, data voidptr) { |
| 138 | event := unsafe { &Event(e) } |
| 139 | mut app := unsafe { &App(data) } |
| 140 | app.events += event.code * 10 |
| 141 | } |
| 142 | |
| 143 | fn direct(ptr voidptr) { |
| 144 | mut app := unsafe { &App(ptr) } |
| 145 | app.directs++ |
| 146 | } |
| 147 | |
| 148 | fn main() { |
| 149 | mut a := App{} |
| 150 | cfg := Config{ |
| 151 | frame: frame |
| 152 | event: on_event |
| 153 | native_event: erased_event |
| 154 | direct: direct |
| 155 | user_data: voidptr(&a) |
| 156 | } |
| 157 | first := run_config(mut a, cfg) |
| 158 | mut b := App{} |
| 159 | second := run_params(mut b, frame: frame, event: on_event, native_event: erased_event, direct: direct, user_data: voidptr(&b)) |
| 160 | mut c := App{} |
| 161 | use_cb(frame, voidptr(&c)) |
| 162 | alias_cfg := AliasConfig{alias_frame: frame, alias_event: on_event, user_data: voidptr(&c)} |
| 163 | alias_target := StructAlias{alias2_frame: frame, alias2_event: on_event, user_data: voidptr(&c)} |
| 164 | if alias_cfg.user_data == voidptr(0) || alias_target.user_data == voidptr(0) { |
| 165 | println(int_str(-1)) |
| 166 | } |
| 167 | println(int_str(first + second + c.frames)) |
| 168 | } |
| 169 | ') |
| 170 | good_out := os.join_path(os.temp_dir(), 'v3_callback_userdata_good_${os.getpid()}') |
| 171 | good_compile := os.execute('${v3_bin} ${good_src} -b c -o ${good_out}') |
| 172 | assert good_compile.exit_code == 0, good_compile.output |
| 173 | run := os.execute(good_out) |
| 174 | assert run.exit_code == 0, run.output |
| 175 | assert run.output.trim_space() == '82' |
| 176 | generated := os.read_file(good_out + '.c') or { panic(err) } |
| 177 | assert generated.contains('callback_adapter'), generated |
| 178 | assert generated.contains('frame_callback_adapter'), generated |
| 179 | assert generated.contains('on_event_callback_adapter'), generated |
| 180 | assert generated.contains('erased_event_callback_adapter'), generated |
| 181 | assert generated.contains('frame((App*)arg0);'), generated |
| 182 | assert generated.contains('on_event(arg0, (App*)arg1);'), generated |
| 183 | assert generated.contains('erased_event((void*)arg0, arg1);'), generated |
| 184 | assert generated.contains('use_cb(frame_callback_adapter_'), generated |
| 185 | assert generated.contains('.alias_frame = frame_callback_adapter_'), generated |
| 186 | assert generated.contains('.alias_event = on_event_callback_adapter_'), generated |
| 187 | assert generated.contains('.alias2_frame = frame_callback_adapter_'), generated |
| 188 | assert generated.contains('.alias2_event = on_event_callback_adapter_'), generated |
| 189 | assert generated.contains('.direct = direct'), generated |
| 190 | assert !generated.contains('.frame = frame,'), generated |
| 191 | assert !generated.contains('.event = on_event,'), generated |
| 192 | assert !generated.contains('.native_event = erased_event,'), generated |
| 193 | assert !generated.contains('.alias_frame = frame,'), generated |
| 194 | assert !generated.contains('.alias_event = on_event,'), generated |
| 195 | assert !generated.contains('.alias2_frame = frame,'), generated |
| 196 | assert !generated.contains('.alias2_event = on_event,'), generated |
| 197 | assert !generated.contains('.frame = (_fn_ptr'), generated |
| 198 | assert !generated.contains('.event = (_fn_ptr'), generated |
| 199 | assert !generated.contains('.native_event = (_fn_ptr'), generated |
| 200 | assert !generated.contains('use_cb((_fn_ptr'), generated |
| 201 | assert !generated.contains('use_cb(frame,'), generated |
| 202 | assert !generated.contains('direct_callback_adapter'), generated |
| 203 | |
| 204 | wrong_arity := callback_write_source('wrong_arity', 'module main |
| 205 | struct Config { |
| 206 | frame fn (voidptr) |
| 207 | } |
| 208 | fn wrong(a voidptr, b int) {} |
| 209 | fn main() { |
| 210 | _ := Config{frame: wrong} |
| 211 | } |
| 212 | ') |
| 213 | arity_compile := callback_compile(v3_bin, wrong_arity, 'wrong_arity') |
| 214 | assert arity_compile.exit_code != 0, arity_compile.output |
| 215 | |
| 216 | wrong_return := callback_write_source('wrong_return', 'module main |
| 217 | struct Config { |
| 218 | frame fn (voidptr) |
| 219 | } |
| 220 | fn wrong(a voidptr) int { |
| 221 | return 1 |
| 222 | } |
| 223 | fn main() { |
| 224 | _ := Config{frame: wrong} |
| 225 | } |
| 226 | ') |
| 227 | return_compile := callback_compile(v3_bin, wrong_return, 'wrong_return') |
| 228 | assert return_compile.exit_code != 0, return_compile.output |
| 229 | |
| 230 | wrong_return_pointer := callback_write_source('wrong_return_pointer', 'module main |
| 231 | struct Event {} |
| 232 | struct Config { |
| 233 | cb fn () &Event |
| 234 | } |
| 235 | fn ret_voidptr() voidptr { |
| 236 | return voidptr(0) |
| 237 | } |
| 238 | fn main() { |
| 239 | _ := Config{cb: ret_voidptr} |
| 240 | } |
| 241 | ') |
| 242 | return_pointer_compile := callback_compile(v3_bin, wrong_return_pointer, 'wrong_return_pointer') |
| 243 | assert return_pointer_compile.exit_code != 0, return_pointer_compile.output |
| 244 | |
| 245 | wrong_param := callback_write_source('wrong_param', 'module main |
| 246 | struct Event {} |
| 247 | struct Other {} |
| 248 | struct App {} |
| 249 | struct Config { |
| 250 | event fn (&Event, voidptr) |
| 251 | } |
| 252 | fn wrong(e &Other, mut app App) {} |
| 253 | fn main() { |
| 254 | _ := Config{event: wrong} |
| 255 | } |
| 256 | ') |
| 257 | param_compile := callback_compile(v3_bin, wrong_param, 'wrong_param') |
| 258 | assert param_compile.exit_code != 0, param_compile.output |
| 259 | |
| 260 | wrong_primitive_param := callback_write_source('wrong_primitive_param', 'module main |
| 261 | struct Config { |
| 262 | cb fn (i64) |
| 263 | } |
| 264 | fn takes_int(x int) {} |
| 265 | fn main() { |
| 266 | _ := Config{cb: takes_int} |
| 267 | } |
| 268 | ') |
| 269 | primitive_param_compile := callback_compile(v3_bin, wrong_primitive_param, |
| 270 | 'wrong_primitive_param') |
| 271 | assert primitive_param_compile.exit_code != 0, primitive_param_compile.output |
| 272 | |
| 273 | homonym_root := os.join_path(os.temp_dir(), 'v3_callback_userdata_homonym_${os.getpid()}') |
| 274 | os.rmdir_all(homonym_root) or {} |
| 275 | callback_write_project_file(homonym_root, 'left/left.v', 'module left |
| 276 | |
| 277 | pub struct App { |
| 278 | pub mut: |
| 279 | hits int |
| 280 | } |
| 281 | |
| 282 | pub fn hit(mut app App) { |
| 283 | app.hits += 10 |
| 284 | } |
| 285 | ') |
| 286 | callback_write_project_file(homonym_root, 'right/right.v', 'module right |
| 287 | |
| 288 | pub struct App { |
| 289 | pub mut: |
| 290 | hits int |
| 291 | } |
| 292 | |
| 293 | pub fn hit(mut app App) { |
| 294 | app.hits += 100 |
| 295 | } |
| 296 | ') |
| 297 | homonym_main := callback_write_project_file(homonym_root, 'main.v', 'module main |
| 298 | |
| 299 | import left |
| 300 | import right |
| 301 | |
| 302 | struct Config { |
| 303 | cb fn (voidptr) |
| 304 | user_data voidptr |
| 305 | } |
| 306 | |
| 307 | fn run(cfg Config) { |
| 308 | cfg.cb(cfg.user_data) |
| 309 | } |
| 310 | |
| 311 | fn main() { |
| 312 | _ := left.App{} |
| 313 | mut app := right.App{} |
| 314 | cfg := Config{ |
| 315 | cb: right.hit |
| 316 | user_data: voidptr(&app) |
| 317 | } |
| 318 | run(cfg) |
| 319 | println(int_str(app.hits)) |
| 320 | } |
| 321 | ') |
| 322 | homonym_out := os.join_path(os.temp_dir(), 'v3_callback_userdata_homonym_out_${os.getpid()}') |
| 323 | homonym_compile := os.execute('${v3_bin} ${homonym_main} -b c -o ${homonym_out}') |
| 324 | assert homonym_compile.exit_code == 0, homonym_compile.output |
| 325 | homonym_run := os.execute(homonym_out) |
| 326 | assert homonym_run.exit_code == 0, homonym_run.output |
| 327 | assert homonym_run.output.trim_space() == '100' |
| 328 | homonym_c := os.read_file(homonym_out + '.c') or { panic(err) } |
| 329 | assert homonym_c.contains('right__hit_callback_adapter'), homonym_c |
| 330 | assert homonym_c.contains('right__hit((right__App*)arg0);'), homonym_c |
| 331 | assert !homonym_c.contains('left__hit_callback_adapter'), homonym_c |
| 332 | |
| 333 | c_fn_src := callback_write_source('c_fn_value', 'module main |
| 334 | |
| 335 | fn C.abs(int) int |
| 336 | |
| 337 | struct App { |
| 338 | mut: |
| 339 | hits int |
| 340 | } |
| 341 | |
| 342 | fn abs(mut app App) int { |
| 343 | app.hits = 99 |
| 344 | return app.hits |
| 345 | } |
| 346 | |
| 347 | fn call_c(cb fn (int) int) int { |
| 348 | return cb(-3) |
| 349 | } |
| 350 | |
| 351 | fn main() { |
| 352 | _ := App{} |
| 353 | rc := call_c(C.abs) |
| 354 | println(int_str(rc)) |
| 355 | } |
| 356 | ') |
| 357 | c_fn_out := os.join_path(os.temp_dir(), 'v3_callback_userdata_c_fn_${os.getpid()}') |
| 358 | c_fn_compile := os.execute('${v3_bin} ${c_fn_src} -b c -o ${c_fn_out}') |
| 359 | assert c_fn_compile.exit_code == 0, c_fn_compile.output |
| 360 | c_fn_run := os.execute(c_fn_out) |
| 361 | assert c_fn_run.exit_code == 0, c_fn_run.output |
| 362 | assert c_fn_run.output.trim_space() == '3' |
| 363 | c_fn_c := os.read_file(c_fn_out + '.c') or { panic(err) } |
| 364 | assert c_fn_c.contains('call_c(abs)'), c_fn_c |
| 365 | assert !c_fn_c.contains('call_c((_fn_ptr'), c_fn_c |
| 366 | assert !c_fn_c.contains('int abs(App*'), c_fn_c |
| 367 | } |
| 368 | |
| 369 | fn test_callback_parallel_wrapper_names_are_stable() { |
| 370 | v3_bin := callback_build_v3_parallel() |
| 371 | mut src := strings.new_builder(128 * 1024) |
| 372 | src.write_string('module main |
| 373 | |
| 374 | struct EventA { |
| 375 | code int |
| 376 | } |
| 377 | |
| 378 | struct EventB { |
| 379 | code int |
| 380 | } |
| 381 | |
| 382 | struct App { |
| 383 | mut: |
| 384 | total int |
| 385 | } |
| 386 | |
| 387 | struct ConfigA { |
| 388 | cb fn (&EventA, voidptr) |
| 389 | user_data voidptr |
| 390 | } |
| 391 | |
| 392 | struct ConfigB { |
| 393 | cb fn (&EventB, voidptr) |
| 394 | user_data voidptr |
| 395 | } |
| 396 | |
| 397 | fn erased(e voidptr, data voidptr) { |
| 398 | _ := e |
| 399 | mut app := unsafe { &App(data) } |
| 400 | app.total++ |
| 401 | } |
| 402 | |
| 403 | ') |
| 404 | for i in 0 .. 1100 { |
| 405 | if i % 2 == 0 { |
| 406 | src.write_string('fn use_${i}(mut app App) { |
| 407 | e := EventA{code: ${i}} |
| 408 | cfg := ConfigA{cb: erased, user_data: voidptr(app)} |
| 409 | cfg.cb(&e, cfg.user_data) |
| 410 | } |
| 411 | |
| 412 | ') |
| 413 | } else { |
| 414 | src.write_string('fn use_${i}(mut app App) { |
| 415 | e := EventB{code: ${i}} |
| 416 | cfg := ConfigB{cb: erased, user_data: voidptr(app)} |
| 417 | cfg.cb(&e, cfg.user_data) |
| 418 | } |
| 419 | |
| 420 | ') |
| 421 | } |
| 422 | } |
| 423 | src.write_string('fn main() { |
| 424 | mut app := App{} |
| 425 | ') |
| 426 | for i in 0 .. 1100 { |
| 427 | src.write_string(' use_${i}(mut app) |
| 428 | ') |
| 429 | } |
| 430 | src.write_string(' println(int_str(app.total)) |
| 431 | } |
| 432 | ') |
| 433 | parallel_src := callback_write_source('parallel', src.str()) |
| 434 | parallel_out := os.join_path(os.temp_dir(), 'v3_callback_userdata_parallel_${os.getpid()}') |
| 435 | parallel_compile := os.execute('${v3_bin} ${parallel_src} -b c -o ${parallel_out}') |
| 436 | assert parallel_compile.exit_code == 0, parallel_compile.output |
| 437 | parallel_run := os.execute(parallel_out) |
| 438 | assert parallel_run.exit_code == 0, parallel_run.output |
| 439 | assert parallel_run.output.trim_space() == '1100' |
| 440 | parallel_c := os.read_file(parallel_out + '.c') or { panic(err) } |
| 441 | names := callback_adapter_names(parallel_c, 'erased_callback_adapter_') |
| 442 | assert names.len == 2, parallel_c |
| 443 | assert names[0] != names[1] |
| 444 | } |
| 445 | |