v / vlib / v2 / eval / eval_test.v
827 lines · 762 sloc · 17.81 KB · 5807d31b40f8a116454f70eec6cdf8c766512d1f
Raw
1// vtest build: macos
2module eval
3
4import os
5import v2.ast
6import v2.parser
7import v2.pref
8import v2.token
9import time
10
11const eval_backend_timeout_ms = 60_000
12const eval_backend_poll_ms = 50
13const eval_backend_term_grace_ms = 1_000
14
15fn testsuite_begin() {
16 skip_test('v2 eval tests are temporarily disabled')
17}
18
19fn eval_backend_tmp_dir() string {
20 return os.join_path(os.temp_dir(), 'v2_eval_integration_${os.getpid()}')
21}
22
23fn ensure_eval_backend_runner(vexe string) !string {
24 tmp_dir := eval_backend_tmp_dir()
25 os.mkdir_all(tmp_dir)!
26 v2_exe := os.join_path(tmp_dir, 'v2_eval_runner')
27 if os.exists(v2_exe) {
28 return v2_exe
29 }
30 vroot := os.dir(vexe)
31 build_res := os.execute('${os.quoted_path(vexe)} -o ${os.quoted_path(v2_exe)} ${os.quoted_path(os.join_path(vroot,
32 'cmd', 'v2', 'v2.v'))}')
33 if build_res.exit_code != 0 {
34 return error(build_res.output)
35 }
36 return v2_exe
37}
38
39fn run_eval_backend(code string) !os.Result {
40 return run_eval_backend_with_args(code, []string{})
41}
42
43fn run_eval_backend_with_args(code string, extra_args []string) !os.Result {
44 vexe := os.getenv('VEXE')
45 if vexe == '' {
46 return error('VEXE is not set')
47 }
48 tmp_dir := eval_backend_tmp_dir()
49 os.mkdir_all(tmp_dir)!
50 tmp_file := os.join_path(tmp_dir, 'sample_${time.now().unix_micro()}.v')
51 os.write_file(tmp_file, code)!
52 defer {
53 os.rm(tmp_file) or {}
54 }
55 v2_exe := ensure_eval_backend_runner(vexe)!
56
57 mut p := os.new_process(v2_exe)
58 mut args := ['-backend', 'eval', tmp_file]
59 if extra_args.len > 0 {
60 args << '--'
61 args << extra_args
62 }
63 p.set_args(args)
64 p.set_redirect_stdio()
65 p.run()
66 defer {
67 p.close()
68 }
69
70 mut waited_ms := 0
71 for p.is_alive() && waited_ms < eval_backend_timeout_ms {
72 time.sleep(eval_backend_poll_ms * time.millisecond)
73 waited_ms += eval_backend_poll_ms
74 }
75 if p.is_alive() {
76 p.signal_term()
77 mut grace_ms := 0
78 for p.is_alive() && grace_ms < eval_backend_term_grace_ms {
79 time.sleep(eval_backend_poll_ms * time.millisecond)
80 grace_ms += eval_backend_poll_ms
81 }
82 if p.is_alive() {
83 p.signal_kill()
84 return error('eval backend timed out after ${eval_backend_timeout_ms}ms')
85 }
86 }
87 p.wait()
88 stdout := p.stdout_slurp()
89 stderr := p.stderr_slurp()
90 return os.Result{
91 exit_code: p.code
92 output: stdout + stderr
93 }
94}
95
96fn test_eval_function_call_and_for_range() {
97 mut e := create()
98 e.run_text('
99fn sum(n int) int {
100 mut acc := 0
101 for i in 0 .. n {
102 acc += i
103 }
104 return acc
105}
106
107fn main() {
108 println(sum(5))
109}
110') or {
111 panic(err)
112 }
113 assert e.stdout() == '10\n'
114}
115
116fn test_eval_if_expr_value() {
117 mut e := create()
118 e.run_text('
119fn main() {
120 x := if 3 > 2 {
121 41
122 } else {
123 0
124 }
125 println(x + 1)
126}
127') or {
128 panic(err)
129 }
130 assert e.stdout() == '42\n'
131}
132
133fn test_eval_array_append_and_string_interpolation() {
134 mut e := create()
135 e.run_text('
136fn main() {
137 mut arr := []int{}
138 arr << 7
139 arr << 9
140 println("\${arr[0]}:\${arr[1]}:\${arr.len}")
141}
142') or {
143 panic(err)
144 }
145 assert e.stdout() == '7:9:2\n'
146}
147
148fn test_eval_transformed_array_append_and_string_interpolation() {
149 res := run_eval_backend('
150fn main() {
151 mut arr := []int{}
152 arr << 7
153 arr << 9
154 println("\${arr[0]}:\${arr[1]}:\${arr.len}")
155}
156') or {
157 panic(err)
158 }
159 if res.exit_code != 0 {
160 panic(res.output)
161 }
162 assert res.output.contains('7:9:2\n')
163}
164
165fn test_eval_runtime_args_follow_separator() {
166 res := run_eval_backend_with_args('
167import os
168
169fn main() {
170 println(os.args.join("|"))
171}
172', [
173 'alpha',
174 'beta',
175 ]) or { panic(err) }
176 if res.exit_code != 0 {
177 panic(res.output)
178 }
179 assert res.output.contains('|alpha|beta')
180}
181
182fn test_eval_transformed_nested_array_clone() {
183 res := run_eval_backend("
184fn main() {
185 mut a := [[1, 2], [3]]
186 mut b := a.clone()
187 b[0] << 9
188 println('\${a[0].len}:\${b[0].len}')
189}
190 ") or {
191 panic(err)
192 }
193 if res.exit_code != 0 {
194 panic(res.output)
195 }
196 assert res.output.contains('2:3\n')
197}
198
199fn test_eval_transformed_array_append_many() {
200 res := run_eval_backend("
201fn main() {
202 mut items := [1]
203 items << [2, 3]
204 println('\${items[0]}:\${items[1]}:\${items[2]}:\${items.len}')
205}
206 ") or {
207 panic(err)
208 }
209 if res.exit_code != 0 {
210 panic(res.output)
211 }
212 assert res.output.contains('1:2:3:3\n')
213}
214
215fn test_eval_transformed_types_init_universe() {
216 res := run_eval_backend("
217import v2.types
218
219fn main() {
220 _ = types.init_universe()
221 println('ok')
222}
223 ") or {
224 panic(err)
225 }
226 if res.exit_code != 0 {
227 panic(res.output)
228 }
229 assert res.output.contains('ok\n')
230}
231
232fn test_eval_transformed_types_lookup_parent_typ() {
233 res := run_eval_backend("
234import v2.types
235
236fn main() {
237 u := types.init_universe()
238 if obj := u.lookup_parent('string', 0) {
239 _ = obj.typ()
240 println('ok')
241 } else {
242 println('missing')
243 }
244}
245 ") or {
246 panic(err)
247 }
248 if res.exit_code != 0 {
249 panic(res.output)
250 }
251 assert res.output.contains('ok\n')
252}
253
254fn test_eval_transformed_map_if_guard_with_nested_sumtype() {
255 res := run_eval_backend("
256import v2.types
257
258fn lookup(m map[string]types.Object, key string) ?types.Object {
259 if obj := m[key] {
260 return obj
261 }
262 return none
263}
264
265fn main() {
266 mut m := map[string]types.Object{}
267 m['x'] = types.Object(types.Type(types.string_))
268 if obj := lookup(m, 'x') {
269 _ = obj.typ()
270 println('ok')
271 } else {
272 println('missing')
273 }
274}
275 ") or {
276 panic(err)
277 }
278 if res.exit_code != 0 {
279 panic(res.output)
280 }
281 assert res.output.contains('ok\n')
282}
283
284fn test_eval_transformed_map_builtin_fast_paths() {
285 res := run_eval_backend("
286fn main() {
287 mut m := map[string]int{}
288 m['a'] = 1
289 m['b'] += 2
290 a_val := m['a']
291 b_val := m['b']
292 has_a := 'a' in m
293 println('\${a_val}:\${b_val}:\${m.keys().len}:\${m.values().len}:\${has_a}')
294 m.delete('a')
295 mut cloned := m.clone()
296 mut moved := cloned.move()
297 cloned.clear()
298 has_a_after_delete := 'a' in m
299 has_b_in_moved := 'b' in moved
300 moved_b := moved['b'] or { 0 }
301 println('\${has_a_after_delete}:\${has_b_in_moved}:\${cloned.len}:\${moved_b}')
302}
303 ") or {
304 panic(err)
305 }
306 if res.exit_code != 0 {
307 panic(res.output)
308 }
309 assert res.output.contains('1:2:2:2:true\n')
310 assert res.output.contains('false:true:0:2\n')
311}
312
313fn test_eval_transformed_os_execute() {
314 res := run_eval_backend("
315import os
316
317fn main() {
318 cmd := if os.user_os() == 'windows' { 'cmd /c echo ok' } else { 'printf ok' }
319 result := os.execute(cmd)
320 println('\${result.exit_code}:\${result.output}')
321}
322 ") or {
323 panic(err)
324 }
325 if res.exit_code != 0 {
326 panic(res.output)
327 }
328 assert res.output.contains('0:ok')
329}
330
331fn test_eval_transformed_smartcast_assignment() {
332 res := run_eval_backend('
333struct Foo {
334mut:
335 n int
336}
337
338struct Bar {}
339
340type W = Bar | Foo
341
342fn main() {
343 mut w := W(Foo{n: 1})
344 if w is Foo {
345 w.n = 2
346 }
347 if w is Foo {
348 println(w.n)
349 }
350}
351 ') or {
352 panic(err)
353 }
354 if res.exit_code != 0 {
355 panic(res.output)
356 }
357 assert res.output.contains('2\n')
358}
359
360fn test_eval_transformed_ast_expr_pos() {
361 res := run_eval_backend("
362import v2.ast
363
364fn main() {
365 expr := ast.Expr(ast.PrefixExpr{})
366 _ = expr.pos()
367 println('ok')
368}
369 ") or {
370 panic(err)
371 }
372 if res.exit_code != 0 {
373 panic(res.output)
374 }
375 assert res.output.contains('ok\n')
376}
377
378fn test_eval_array_has_named_struct_field() {
379 e := create()
380 attrs := ArrayValue{
381 values: [
382 Value(StructValue{
383 type_name: 'Attr'
384 fields: {
385 'name': Value('flag')
386 }
387 }),
388 ]
389 }
390 assert e.array_has(attrs, 'flag')
391 assert !e.array_has(attrs, 'other')
392}
393
394fn test_eval_sumtype_tag_from_type_value_uses_registered_type_kind() {
395 mut prefs := pref.new_preferences()
396 mut file_set := token.FileSet.new()
397 mut par := parser.Parser.new(&prefs)
398 type_files :=
399 os.walk_ext(os.join_path(@VMODROOT, 'vlib', 'v2', 'types'), '.v').filter(!it.ends_with('_test.v'))
400 dummy_main := os.join_path(os.temp_dir(), 'v2_eval_dummy_main_${os.getpid()}.v')
401 os.write_file(dummy_main, 'module main\nfn main() {}\n') or { panic(err) }
402 defer {
403 os.rm(dummy_main) or {}
404 }
405 mut files_to_parse := type_files.clone()
406 files_to_parse << dummy_main
407 files := par.parse_files(files_to_parse, mut file_set)
408 mut e := create()
409 e.register_files(files) or { panic(err) }
410 info := e.sum_type_info('types.Type') or { panic('missing types.Type sumtype info') }
411 struct_tag := e.lookup_sumtype_variant_tag(info, 'Struct') or { panic('missing Struct tag') }
412 alias_tag := e.lookup_sumtype_variant_tag(info, 'Alias') or { panic('missing Alias tag') }
413 struct_value_tag := e.sumtype_tag_from_value(info, TypeValue{
414 name: 'types.ObjectCommon'
415 }) or { panic('missing struct kind tag') }
416 alias_value_tag := e.sumtype_tag_from_value(info, TypeValue{
417 name: 'voidptr'
418 }) or { panic('missing alias kind tag') }
419 assert struct_value_tag == struct_tag
420 assert alias_value_tag == alias_tag
421}
422
423fn test_eval_type_sum_data_is_nil() {
424 mut prefs := pref.new_preferences()
425 mut file_set := token.FileSet.new()
426 mut par := parser.Parser.new(&prefs)
427 type_files :=
428 os.walk_ext(os.join_path(@VMODROOT, 'vlib', 'v2', 'types'), '.v').filter(!it.ends_with('_test.v'))
429 dummy_main := os.join_path(os.temp_dir(), 'v2_eval_dummy_main_${os.getpid()}.v')
430 os.write_file(dummy_main, 'module main\nfn main() {}\n') or { panic(err) }
431 defer {
432 os.rm(dummy_main) or {}
433 }
434 mut files_to_parse := type_files.clone()
435 files_to_parse << dummy_main
436 files := par.parse_files(files_to_parse, mut file_set)
437 mut e := create()
438 e.register_files(files) or { panic(err) }
439 assert e.type_sum_data_is_nil(e.zero_struct_value('types.Type'))
440 assert !e.type_sum_data_is_nil(TypeValue{
441 name: 'types.ObjectCommon'
442 })
443}
444
445fn test_eval_zero_value_for_sumtype_data_field() {
446 mut prefs := pref.new_preferences()
447 mut file_set := token.FileSet.new()
448 mut par := parser.Parser.new(&prefs)
449 ast_files :=
450 os.walk_ext(os.join_path(@VMODROOT, 'vlib', 'v2', 'ast'), '.v').filter(!it.ends_with('_test.v'))
451 dummy_main := os.join_path(os.temp_dir(), 'v2_eval_dummy_main_${os.getpid()}.v')
452 os.write_file(dummy_main, 'module main\nfn main() {}\n') or { panic(err) }
453 defer {
454 os.rm(dummy_main) or {}
455 }
456 mut files_to_parse := ast_files.clone()
457 files_to_parse << dummy_main
458 files := par.parse_files(files_to_parse, mut file_set)
459 mut e := create()
460 e.register_files(files) or { panic(err) }
461 value := e.zero_value_for_sumtype_data_field('ast.Expr._data', '_ArrayInitExpr') or {
462 panic('missing sumtype data field zero value')
463 }
464 assert value is StructValue
465 assert (value as StructValue).type_name == 'ast.ArrayInitExpr'
466}
467
468fn test_eval_selector_on_sumtype_data_void_field_returns_zero_value() {
469 mut prefs := pref.new_preferences()
470 mut file_set := token.FileSet.new()
471 mut par := parser.Parser.new(&prefs)
472 ast_files :=
473 os.walk_ext(os.join_path(@VMODROOT, 'vlib', 'v2', 'ast'), '.v').filter(!it.ends_with('_test.v'))
474 dummy_main := os.join_path(os.temp_dir(), 'v2_eval_dummy_main_${os.getpid()}.v')
475 os.write_file(dummy_main, 'module main\nfn main() {}\n') or { panic(err) }
476 defer {
477 os.rm(dummy_main) or {}
478 }
479 mut files_to_parse := ast_files.clone()
480 files_to_parse << dummy_main
481 files := par.parse_files(files_to_parse, mut file_set)
482 mut e := create()
483 e.register_files(files) or { panic(err) }
484 e.open_scope()
485 defer {
486 e.close_scope() or { panic(err) }
487 }
488 e.declare_var('data', StructValue{
489 type_name: 'ast.Expr._data'
490 fields: {
491 '_ArrayInitExpr': Value(void_value())
492 }
493 })
494 data_ident := ast.Ident{
495 name: 'data'
496 }
497 data_lhs := ast.Expr(data_ident)
498 data_rhs := ast.Ident{
499 name: '_ArrayInitExpr'
500 }
501 data_selector := ast.SelectorExpr{
502 lhs: data_lhs
503 rhs: data_rhs
504 }
505 value := e.eval_selector_expr(data_selector) or { panic(err) }
506 assert value is StructValue
507 array_init := value as StructValue
508 assert array_init.type_name == 'ast.ArrayInitExpr'
509 assert 'exprs' in array_init.fields
510 assert array_init.fields['exprs'] or { panic('missing exprs field') } is ArrayValue
511}
512
513fn test_eval_selector_on_sumtype_wrapper_variant_field_returns_zero_value() {
514 mut prefs := pref.new_preferences()
515 mut file_set := token.FileSet.new()
516 mut par := parser.Parser.new(&prefs)
517 ast_files :=
518 os.walk_ext(os.join_path(@VMODROOT, 'vlib', 'v2', 'ast'), '.v').filter(!it.ends_with('_test.v'))
519 dummy_main := os.join_path(os.temp_dir(), 'v2_eval_dummy_main_${os.getpid()}.v')
520 os.write_file(dummy_main, 'module main\nfn main() {}\n') or { panic(err) }
521 defer {
522 os.rm(dummy_main) or {}
523 }
524 mut files_to_parse := ast_files.clone()
525 files_to_parse << dummy_main
526 files := par.parse_files(files_to_parse, mut file_set)
527 mut e := create()
528 e.register_files(files) or { panic(err) }
529 info := e.sum_type_info('ast.Expr') or { panic('missing ast.Expr sum type info') }
530 ident_tag := e.lookup_sumtype_variant_tag(info, 'ast.Ident') or {
531 panic('missing ast.Ident sumtype tag')
532 }
533 expr_value := e.build_sumtype_wrapper('ast.Expr', info, ident_tag, StructValue{
534 type_name: 'ast.Ident'
535 fields: {
536 'name': Value('')
537 }
538 })
539 e.open_scope()
540 defer {
541 e.close_scope() or { panic(err) }
542 }
543 e.declare_var('expr', expr_value)
544 expr_ident := ast.Ident{
545 name: 'expr'
546 }
547 expr_lhs := ast.Expr(expr_ident)
548 expr_rhs := ast.Ident{
549 name: '_ArrayInitExpr'
550 }
551 expr_selector := ast.SelectorExpr{
552 lhs: expr_lhs
553 rhs: expr_rhs
554 }
555 value := e.eval_selector_expr(expr_selector) or { panic(err) }
556 assert value is StructValue
557 assert (value as StructValue).type_name == 'ast.ArrayInitExpr'
558}
559
560fn test_eval_lookup_parent_through_parent_scope() {
561 res := run_eval_backend("
562import v2.types
563
564fn main() {
565 mut parent := types.new_scope(unsafe { nil })
566 parent.insert('Error', types.Object(types.Type(types.Struct{name: 'builtin__Error'})))
567 mut child := types.new_scope(parent)
568 if obj := child.lookup_parent('Error', 0) {
569 if obj is types.Type {
570 println(obj.name())
571 }
572 }
573}
574 ") or {
575 panic(err)
576 }
577 if res.exit_code != 0 {
578 panic(res.output)
579 }
580 assert res.output.contains('builtin__Error\n')
581}
582
583fn test_eval_flag_enum_defaults() {
584 res := run_eval_backend('
585@[flag]
586enum Properties {
587 boolean
588 float
589 integer
590 unsigned
591 untyped
592}
593
594fn main() {
595 println(int(Properties.integer))
596}
597 ') or {
598 panic(err)
599 }
600 if res.exit_code != 0 {
601 panic(res.output)
602 }
603 assert res.output.contains('4\n')
604}
605
606fn test_eval_option_typed_struct_field() {
607 res := run_eval_backend('
608import v2.types
609
610fn main() {
611 println(types.Type(types.Channel{elem_type: types.int_}).name())
612}
613 ') or {
614 panic(err)
615 }
616 if res.exit_code != 0 {
617 panic(res.output)
618 }
619 assert res.output.contains('chan int\n')
620}
621
622fn test_eval_transformed_type_name_for_primitive_variants() {
623 res := run_eval_backend('
624import v2.types
625
626fn main() {
627 println(types.Type(types.bool_).name())
628 println(types.Type(types.int_).name())
629 println(types.Type(types.f64_).name())
630 println(types.Type(types.voidptr_).name())
631}
632 ') or {
633 panic(err)
634 }
635 if res.exit_code != 0 {
636 panic(res.output)
637 }
638 assert res.output.contains('bool\nint\nf64\nvoidptr\n')
639}
640
641fn test_eval_transformed_imported_struct_field_assignment_keeps_declaring_module_type() {
642 res := run_eval_backend('
643import v2.types
644
645fn main() {
646 mut m := types.Map{
647 key_type: types.Type(types.int_)
648 value_type: types.Type(types.int_)
649 }
650 m.key_type = types.int_
651 println(m.key_type.name())
652 println(m.key_type.base_type().name())
653}
654 ') or {
655 panic(err)
656 }
657 if res.exit_code != 0 {
658 panic(res.output)
659 }
660 assert res.output.contains('int\nint\n')
661}
662
663fn test_eval_transformed_typed_array_index_assignment_keeps_sumtype_wrapper() {
664 res := run_eval_backend('
665import v2.types
666
667fn main() {
668 mut items := []types.Type{len: 1, init: types.Type(types.int_)}
669 items[0] = types.int_
670 println(items[0].name())
671 println(items[0].base_type().name())
672}
673 ') or {
674 panic(err)
675 }
676 if res.exit_code != 0 {
677 panic(res.output)
678 }
679 assert res.output.contains('int\nint\n')
680}
681
682fn test_eval_transformed_typed_map_assignment_keeps_sumtype_wrapper() {
683 res := run_eval_backend("
684import v2.types
685
686fn main() {
687 mut m := map[string]types.Type{}
688 m['x'] = types.int_
689 println(m['x'].name())
690 println(m['x'].base_type().name())
691}
692 ") or {
693 panic(err)
694 }
695 if res.exit_code != 0 {
696 panic(res.output)
697 }
698 assert res.output.contains('int\nint\n')
699}
700
701fn test_eval_transformed_object_module_roundtrip_keeps_wrapper() {
702 res := run_eval_backend("
703import v2.types
704
705fn id(obj types.Object) types.Object {
706 return obj
707}
708
709fn main() {
710 mut mod_scope := types.new_scope(unsafe { nil })
711 mod_scope.insert('File', types.Object(types.Struct{
712 name: 'File'
713 }))
714 obj := types.Object(types.Module{
715 name: 'ast'
716 scope: mod_scope
717 })
718 println(obj.type_name())
719 println(id(obj).type_name())
720 mut arr := []types.Object{}
721 arr << obj
722 println(arr[0].type_name())
723 mut m := map[string]types.Object{}
724 m['ast'] = obj
725 println(m['ast'].type_name())
726 if got := m['ast'] {
727 println(got.type_name())
728 if got is types.Module {
729 println(got.name)
730 if file_obj := got.scope.lookup_parent('File', 0) {
731 println(file_obj.type_name())
732 println(file_obj.typ().name())
733 }
734 }
735 }
736}
737 ") or {
738 panic(err)
739 }
740 if res.exit_code != 0 {
741 panic(res.output)
742 }
743 assert res.output.contains('types.Module\ntypes.Module\ntypes.Module\ntypes.Module\ntypes.Module\n')
744 assert res.output.contains('ast\ntypes.Type\nFile\n')
745}
746
747fn test_eval_transformed_array_parameter_keeps_array_shape() {
748 res := run_eval_backend('
749import v2.ast
750
751fn stmt_list(stmts []ast.Stmt) {
752 println(stmts.len)
753}
754
755fn main() {
756 items := []ast.Stmt{}
757 stmt_list(items)
758}
759 ') or {
760 panic(err)
761 }
762 if res.exit_code != 0 {
763 panic(res.output)
764 }
765 assert res.output.contains('0\n')
766}
767
768fn test_eval_transformed_array_first_and_last() {
769 res := run_eval_backend('
770fn main() {
771 items := [1, 2, 3]
772 println(items.first())
773 println(items.last())
774}
775 ') or {
776 panic(err)
777 }
778 if res.exit_code != 0 {
779 panic(res.output)
780 }
781 assert res.output.contains('1\n3\n')
782}
783
784fn test_eval_prefix_bit_not() {
785 mut e := create()
786 e.run_text('
787fn main() {
788 println(~1)
789}
790') or { panic(err) }
791 assert e.stdout() == '-2\n'
792}
793
794fn test_eval_token_and_byte_helpers() {
795 mut e := create()
796 e.run_text("
797fn main() {
798 for i := 0; i < 2; i++ {
799 println(i)
800 }
801 println(r'raw')
802 println(u8(`A`).is_alnum())
803 println(u8(`A`).is_capital())
804 println(u8(` `).is_space())
805}
806") or {
807 panic(err)
808 }
809 assert e.stdout() == '0\n1\nraw\ntrue\ntrue\ntrue\n'
810}
811
812fn test_eval_string_int_conversion() {
813 mut e := create()
814 e.run_text("
815fn main() {
816 println('42'.int())
817 println('84'.i64())
818}
819") or { panic(err) }
820 assert e.stdout() == '42\n84\n'
821}
822
823@[noreturn]
824fn skip_test(reason string) {
825 println('skipping test, because ${reason} .')
826 exit(0)
827}
828