mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 14:17:18 +00:00
665 lines
13 KiB
Plaintext
665 lines
13 KiB
Plaintext
// 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
|
|
}
|