// tests.vr — functions named test_* are picked up by `vr test`. // vr test examples/tests.vr import os import json import strings import math import http import time enum Suit { hearts diamonds clubs spades } fn add(a, b) { return a + b } fn test_add() { assert add(2, 3) == 5 assert add(-1, 1) == 0 } fn test_strings() { let a = "foo" let b = "bar" assert a + b == "foobar" assert a != b assert a == "foo" } fn test_loops() { mut total = 0 mut i = 1 while i <= 10 { total = total + i i = i + 1 } assert total == 55 } fn test_booleans() { let x = 7 assert x > 3 and x < 10 assert x == 7 or x == 8 assert not (x == 0) } fn test_arrays() { let a = [1, 2, 3] assert len(a) == 3 assert a[0] == 1 assert a[2] == 3 a[1] = 42 assert a[1] == 42 push(a, 4) assert len(a) == 4 assert a[3] == 4 let m = [[1, 2], [3, 4]] assert m[1][0] == 3 } fn test_for_range() { mut total = 0 for i in 0..5 { total = total + i } assert total == 10 // 0+1+2+3+4 mut inc = 0 for i in 1...3 { inc = inc + i } assert inc == 6 // 1+2+3 (inclusive) } fn test_for_in() { let a = [5, 6, 7] mut total = 0 for x in a { total = total + x } assert total == 18 let squares = [] for i in 0..4 { push(squares, i * i) } assert squares == squares // identity comparison assert len(squares) == 4 assert squares[3] == 9 } fn test_break_continue() { // break exits the loop early mut total = 0 for i in 0..100 { if i == 5 { break } total = total + i } assert total == 10 // 0+1+2+3+4 // continue skips to the next iteration mut sum = 0 for i in 0..6 { if i == 3 { continue } sum = sum + i } assert sum == 12 // 0+1+2+4+5 (3 skipped) // break and continue in while loops mut n = 0 while true { n = n + 1 if n == 3 { continue } if n == 5 { break } } assert n == 5 // continue advances the iterator in for-in loops (no infinite loop) let odds = [] for x in [1, 2, 3, 4, 5] { if x % 2 == 0 { continue } push(odds, x) } assert odds == odds assert len(odds) == 3 assert odds[2] == 5 } fn test_else_if() { let score = 85 mut grade = "" if score >= 90 { grade = "A" } else if score >= 80 { grade = "B" } else if score >= 70 { grade = "C" } else { grade = "F" } assert grade == "B" assert 10 > 5 } fn test_match() { mut total = 0 match 2 { 1 { total = total + 1 } 2 { total = total + 10 } else { total = total + 100 } } assert total == 10 // matched the 2 arm // match on strings let word = "cat" mut sound = "" match word { "dog" { sound = "woof" } "cat" { sound = "meow" } else { sound = "?" } } assert sound == "meow" // no else arm: nothing runs when nothing matches mut hit = 0 match 99 { 1 { hit = 1 } 2 { hit = 2 } } assert hit == 0 } fn test_structs() { let p = { name: "bob", age: 30 } assert p.name == "bob" assert p.age == 30 p.age = 31 assert p.age == 31 // structs are reference values let q = p q.age = 40 assert p.age == 40 // nested structs let person = { name: "amy", addr: { city: "nyc", zip: 10001 } } assert person.addr.city == "nyc" person.addr.zip = 90001 assert person.addr.zip == 90001 // structs in arrays let pts = [{ x: 1, y: 2 }, { x: 10, y: 20 }] assert pts[1].x == 10 pts[0].x = 99 assert pts[0].x == 99 // arrays in structs let holder = { items: [1, 2, 3] } holder.items[1] = 42 assert holder.items[1] == 42 // structs passed to and returned from functions let pt = { x: 3, y: 4 } assert pt.x * pt.x + pt.y * pt.y == 25 // identity comparison let a = { n: 1 } let b = a let c = { n: 1 } assert a == b assert a != c } fn test_json() { // encoding assert json_encode(42) == "42" assert json_encode(3.14) == "3.14" assert json_encode(1.0) == "1" assert json_encode("hi") == "\"hi\"" assert json_encode([1, "two", 3.5]) == "[1,\"two\",3.5]" let rec = { name: "amy", age: 30 } assert json_encode(rec) == "{\"name\":\"amy\",\"age\":30}" assert json_encode(none) == "null" // decoding assert json_decode("42") == 42 assert json_decode("3.14") == 3.14 assert json_decode("\"hi\"") == "hi" assert json_decode("true") == 1 assert json_decode("null") == none let obj = json_decode("{\"x\": 1, \"y\": [1, 2], \"z\": {\"deep\": true}}") assert obj.x == 1 assert obj.y[1] == 2 assert obj.z.deep == 1 // round-trip assert json_encode(json_decode("{\"a\": 1, \"b\": [2, 3]}")) == "{\"a\":1,\"b\":[2,3]}" // unicode escapes assert json_decode("\"\\u0041\"") == "A" assert json_decode("\"\\ud83d\\ude00\"") == "😀" } fn test_format() { assert format(42, "%d") == "42" assert format(42, "%05d") == "00042" assert format(-42, "%05d") == "-0042" assert format(42, "%-5d") == "42 " assert format(3.14159, "%.2f") == "3.14" assert format(3.0, "%.2f") == "3.00" assert format(255, "%X") == "FF" assert format("hi", "%s") == "hi" assert format(7, "%5d") == " 7" } fn test_string_tools() { assert replace("hello world", "l", "L") == "heLLo worLd" assert "hello world".replace("o", "0") == "hell0 w0rld" assert len(split_lines("a\nb\nc")) == 3 assert "a\nb".split_lines()[1] == "b" assert pad("x", 4) == "x " assert "x".pad(4) == "x " assert pad_left("x", 4) == " x" assert "x".pad_left(4) == " x" assert repeat("ab", 3) == "ababab" assert "ab".repeat(2) == "abab" assert pad("toolong", 2) == "toolong" } fn test_none() { assert none == none assert none == 0 == false assert none != 5 if none { assert false } assert not (none == 1) } fn test_module_imports() { // os module if not os.exists("examples") { assert false } if not os.is_dir("examples") { assert false } if not os.is_file("main.v") { assert false } assert os.base("a/b/c.vr") == "c.vr" assert os.dir("a/b/c.vr") == "a/b" assert len(os.glob("lib/*.vr")) == 10 let tmp = os.join(os.cwd(), "tmp_test.txt") os.write_lines(tmp, ["x", "y"]) assert len(os.read_lines(tmp)) == 2 os.remove(tmp) // json module let data = json.decode("{\"a\": 1, \"b\": [2, 3]}") assert json.encode(data) == "{\"a\":1,\"b\":[2,3]}" assert strings.contains(json.pretty(data), "\n") // strings module assert strings.join(["a", "b"], "-") == "a-b" assert strings.capitalize("hello") == "Hello" assert strings.replace("foo", "o", "0") == "f00" assert strings.pad_left("7", 3) == " 7" assert strings.format(3.14159, "%.2f") == "3.14" // math module assert math.clamp(150, 0, 100) == 100 assert math.sign(-3) == -1 assert math.abs(-7) == 7 } fn test_native_errors_are_catchable() { try { read_file("/no/such/file.vr") assert false } catch e { assert len(e) > 0 } try { json_decode("not json") assert false } catch e { assert len(e) > 0 } } fn test_closures() { // closures in expression contexts must not corrupt surrounding values let pick = fn (x) { if x > 10 { return "big" } return "small" } assert "a: " + pick(50) == "a: big" assert pick(3) + "!" == "small!" assert pick(50) + " " + pick(3) == "big small" let dbl = fn (x) { return x * 2 } assert str(dbl(21)) == "42" assert "v=" + str(dbl(4)) == "v=8" // closure containing a loop let sum = fn () { mut total = 0 for i in 0..5 { total = total + i } return total } assert sum() == 10 // closure call as a statement argument let tag = fn (t) { return "[" + t + "]" } assert tag("warn") == "[warn]" } fn test_enums() { // variant values assert Suit.hearts == 0 assert Suit.spades == 3 // enum.count() assert Suit.hearts.count() == 4 // enum.to_string() assert Suit.spades.to_string() == "spades" // enums in expressions let next = Suit.hearts + 1 assert next == 1 // enum iteration mut names = [] for s in Suit { names = push(names, s.to_string()) } assert len(names) == 4 assert names[0] == "hearts" assert names[3] == "spades" // match on enum values assert suit_code(Suit.clubs) == "c" assert suit_code(Suit.hearts) == "h" } fn suit_code(s) { match s { Suit.hearts { return "h" } Suit.clubs { return "c" } else { return "?" } } } fn test_http_errors() { // offline-safe: connecting to a closed local port fails fast try { http.get("http://127.0.0.1:1/nope") assert false } catch e { assert len(e) > 0 } } fn test_time() { let t = time.now() assert t > 1700000000 // format produces fixed-width strings let day = time.date(t) assert len(day) == 10 assert day[4] == "-"[0] let full = time.format(t, "YYYY-MM-DD HH:mm:ss") assert len(full) == 19 // parse round-trips within a second let back = time.parse(full) assert abs(back - t) <= 1 // ms is more precise than seconds assert time.ms() > t * 1000 // invalid input throws try { time.parse("not a date") assert false } catch e { assert len(e) > 0 } } fn test_closure_capture() { // closures capture enclosing locals by value let factor = 3 let dbl = fn(x) { return x * factor } assert dbl(7) == 21 let name = "alice" let age = 30 let greet = fn() { return "hi " + name + " age=" + str(age) } assert greet() == "hi alice age=30" // capture by value: later changes to the outer var don't affect the closure mut counter = 10 let read = fn() { return counter } assert read() == 10 counter = 99 assert read() == 10 // nested closures capture through both levels let base = 100 let outer = fn() { let mid = 5 let inner = fn(x) { return base + mid + x } return inner(1) } assert outer() == 106 // assignment to a captured variable writes the closure's own copy mut n = 1 let bump = fn() { n = n + 1 return n } assert bump() == 2 assert n == 1 // closure stored in a struct, called dynamically let handler = fn() { return "handled" } let rec = { run: handler } assert rec.run() == "handled" } fn test_dynamic_map_keys() { // map keys may be computed expressions, not just string literals let m = {} for i in 0..3 { m["key" + str(i)] = i * 10 } assert m["key0"] == 0 assert m["key2"] == 20 let k = "key1" assert m[k] == 10 assert has(m, "key1") == 1 assert len(m) == 3 delete(m, "key1") assert has(m, "key1") == 0 assert len(m) == 2 // nested computed access let cfg = { "db": { "host": "localhost", "port": 5432 } } let which = "db" assert cfg[which]["host"] == "localhost" } fn test_sort_large() { // merge sort must handle large arrays (O(n log n)) and stay sorted mut a = [] let n = 2000 mut i = 0 while i < n { a = push(a, (n - i) % 97) i = i + 1 } sort(a) assert len(a) == n mut ok = 1 mut j = 1 while j < n { if a[j - 1] > a[j] { ok = 0 } j = j + 1 } assert ok == 1 assert a[0] == 0 assert a[n - 1] == 96 } fn test_mutable_bindings() { // `let` is immutable: reassignment is a compile-time error (tested in CI) // `mut` opts into reassignment mut counter = 0 counter = counter + 1 counter = counter + 2 assert counter == 3 // `let` still reads fine and does not accept reassignment let fixed = 42 assert fixed == 42 } fn test_string_builder() { // string builders avoid O(n^2) reallocation of repeated `+` mut sb = strings.builder() for i in 0..10 { sb = strings.build_add(sb, "x" + str(i) + ",") } let out = strings.build_str(sb) assert out == "x0,x1,x2,x3,x4,x5,x6,x7,x8,x9," } fn test_spawn_join() { // spawn runs a closure on an OS thread; spawn_join waits and returns let j1 = spawn(worker_sum, 100) let j2 = spawn(worker_sum, 200) let r1 = spawn_join(j1) let r2 = spawn_join(j2) assert r1 == 4950 assert r2 == 19900 } fn worker_sum(n) { mut s = 0 mut i = 0 while i < n { s = s + i i = i + 1 } return s } fn test_spawn_string_result() { let j = spawn(worker_greet, "world") let res = spawn_join(j) assert res == "hello world" } fn worker_greet(name) { return "hello " + name } fn test_spawn_error_propagation() { mut caught = false try { let j = spawn(worker_boom) let r = spawn_join(j) assert r == 0 } catch e { caught = true } assert caught } fn worker_boom() { let x = 1 / 0 return x } fn test_string_coercion() { // String + any value stringifies, so error messages "just work" let e = { code: 403, msg: "forbidden" } assert "err: " + e == "err: {code: 403, msg: forbidden}" assert "n=" + 42 == "n=42" assert "f=" + 3.5 == "f=3.5" assert "a=" + [1, 2] == "a=[1, 2]" assert "b=" + none == "b=none" } fn test_typed_throw() { // throw and catch arbitrary values (structs, ints, floats) mut got = "" try { throw { reason: "boom" } } catch err { got = "" + err } assert got == "{reason: boom}" mut gotn = -1 try { throw 99 } catch err { gotn = int(err) } assert gotn == 99 } fn test_flag_parsing() { // flags are read from args() at runtime; here we check the lookup helpers // behave with no flags (defaults) — the actual parse is exercised in CI. assert flag_val("missing") == "" assert flag_has("missing") == 0 assert len(flag_positional()) == 0 } fn test_failing() { // this one is meant to fail — shows up in `vr test` output assert 1 == 2 }