349 lines
8.5 KiB
Plaintext
349 lines
8.5 KiB
Plaintext
// fuzzgen.cm — generates self-checking .cm programs for fuzz testing
|
|
// Each generated program returns a record of test functions that
|
|
// validate their own expected results.
|
|
|
|
// Newline constant — backtick strings don't interpret \n as escape
|
|
var NL = "\n"
|
|
|
|
// Simple seeded PRNG (xorshift32)
|
|
var _seed = 1
|
|
function seed(s) {
|
|
_seed = s != 0 ? s : 1
|
|
}
|
|
|
|
function rand() {
|
|
_seed = _seed ^ (_seed << 13)
|
|
_seed = _seed ^ (_seed >> 17)
|
|
_seed = _seed ^ (_seed << 5)
|
|
if (_seed < 0) _seed = -_seed
|
|
return _seed
|
|
}
|
|
|
|
function rand_int(lo, hi) {
|
|
return lo + (rand() % (hi - lo + 1))
|
|
}
|
|
|
|
function rand_float() {
|
|
return rand_int(-10000, 10000) / 100
|
|
}
|
|
|
|
function rand_bool() {
|
|
return rand() % 2 == 0
|
|
}
|
|
|
|
function pick(arr) {
|
|
return arr[rand() % length(arr)]
|
|
}
|
|
|
|
// Expression generators — each returns {src: "code", val: expected_value}
|
|
// depth is decremented to prevent infinite recursion
|
|
|
|
function gen_int_literal() {
|
|
var v = rand_int(-10000, 10000)
|
|
return {src: text(v), val: v}
|
|
}
|
|
|
|
function gen_float_literal() {
|
|
var v = rand_float()
|
|
return {src: text(v), val: v}
|
|
}
|
|
|
|
function gen_bool_literal() {
|
|
var v = rand_bool()
|
|
var s = "false"
|
|
if (v) s = "true"
|
|
return {src: s, val: v}
|
|
}
|
|
|
|
function gen_text_literal() {
|
|
var words = ["alpha", "beta", "gamma", "delta", "epsilon"]
|
|
var w = pick(words)
|
|
return {src: `"${w}"`, val: w}
|
|
}
|
|
|
|
function gen_null_literal() {
|
|
return {src: "null", val: null}
|
|
}
|
|
|
|
function gen_int_expr(depth) {
|
|
var a = null
|
|
var b = null
|
|
var op = null
|
|
var result = null
|
|
|
|
if (depth <= 0) return gen_int_literal()
|
|
|
|
a = gen_int_expr(depth - 1)
|
|
b = gen_int_expr(depth - 1)
|
|
|
|
// Avoid division by zero
|
|
if (b.val == 0) b = {src: "1", val: 1}
|
|
|
|
op = pick(["+", "-", "*"])
|
|
if (op == "+") {
|
|
result = a.val + b.val
|
|
} else if (op == "-") {
|
|
result = a.val - b.val
|
|
} else {
|
|
result = a.val * b.val
|
|
}
|
|
|
|
// Guard against overflow beyond safe integer range
|
|
if (result > 9007199254740991 || result < -9007199254740991) {
|
|
return gen_int_literal()
|
|
}
|
|
|
|
return {src: `(${a.src} ${op} ${b.src})`, val: result}
|
|
}
|
|
|
|
function gen_float_expr(depth) {
|
|
var a = null
|
|
var b = null
|
|
var op = null
|
|
var result = null
|
|
|
|
if (depth <= 0) return gen_float_literal()
|
|
|
|
a = gen_float_expr(depth - 1)
|
|
b = gen_float_expr(depth - 1)
|
|
|
|
if (b.val == 0) b = {src: "1.0", val: 1.0}
|
|
|
|
op = pick(["+", "-", "*"])
|
|
if (op == "+") {
|
|
result = a.val + b.val
|
|
} else if (op == "-") {
|
|
result = a.val - b.val
|
|
} else {
|
|
result = a.val * b.val
|
|
}
|
|
|
|
return {src: `(${a.src} ${op} ${b.src})`, val: result}
|
|
}
|
|
|
|
function gen_text_expr(depth) {
|
|
var a = null
|
|
var b = null
|
|
|
|
if (depth <= 0) return gen_text_literal()
|
|
|
|
a = gen_text_literal()
|
|
b = gen_text_literal()
|
|
|
|
return {src: `(${a.src} + ${b.src})`, val: a.val + b.val}
|
|
}
|
|
|
|
function gen_comparison_expr(depth) {
|
|
var a = null
|
|
var b = null
|
|
var op = null
|
|
var result = null
|
|
|
|
a = gen_int_expr(depth > 0 ? depth - 1 : 0)
|
|
b = gen_int_expr(depth > 0 ? depth - 1 : 0)
|
|
|
|
op = pick(["==", "!=", "<", ">", "<=", ">="])
|
|
if (op == "==") {
|
|
result = a.val == b.val
|
|
} else if (op == "!=") {
|
|
result = a.val != b.val
|
|
} else if (op == "<") {
|
|
result = a.val < b.val
|
|
} else if (op == ">") {
|
|
result = a.val > b.val
|
|
} else if (op == "<=") {
|
|
result = a.val <= b.val
|
|
} else {
|
|
result = a.val >= b.val
|
|
}
|
|
|
|
return {src: `(${a.src} ${op} ${b.src})`, val: result}
|
|
}
|
|
|
|
// Generate an if-else expression test
|
|
function gen_if_else_test() {
|
|
var cond = gen_comparison_expr(1)
|
|
var then_val = gen_int_literal()
|
|
var else_val = gen_int_literal()
|
|
var expected = cond.val ? then_val.val : else_val.val
|
|
|
|
var body = "var result = null" + NL
|
|
body = body + " if (" + cond.src + ") {" + NL
|
|
body = body + " result = " + then_val.src + NL
|
|
body = body + " } else {" + NL
|
|
body = body + " result = " + else_val.src + NL
|
|
body = body + " }" + NL
|
|
body = body + " if (result != " + text(expected) + ") return \"if_else: expected " + text(expected) + " got \" + text(result)"
|
|
|
|
return body
|
|
}
|
|
|
|
// Generate a loop accumulator test
|
|
function gen_loop_test() {
|
|
var count = rand_int(1, 50)
|
|
var step = rand_int(1, 10)
|
|
var expected = 0
|
|
var i = 0
|
|
while (i < count) {
|
|
expected = expected + step
|
|
i = i + 1
|
|
}
|
|
|
|
var body = "var acc = 0" + NL
|
|
body = body + " var i = 0" + NL
|
|
body = body + " while (i < " + text(count) + ") {" + NL
|
|
body = body + " acc = acc + " + text(step) + NL
|
|
body = body + " i = i + 1" + NL
|
|
body = body + " }" + NL
|
|
body = body + " if (acc != " + text(expected) + ") return \"loop: expected " + text(expected) + " got \" + text(acc)"
|
|
|
|
return body
|
|
}
|
|
|
|
// Generate a closure test
|
|
function gen_closure_test() {
|
|
var init_val = rand_int(1, 100)
|
|
var inc = rand_int(1, 10)
|
|
var calls = rand_int(1, 10)
|
|
var expected = init_val + (inc * calls)
|
|
|
|
var body = "var counter = " + text(init_val) + NL
|
|
body = body + " var inc = function() { counter = counter + " + text(inc) + " }" + NL
|
|
body = body + " var i = 0" + NL
|
|
body = body + " while (i < " + text(calls) + ") {" + NL
|
|
body = body + " inc()" + NL
|
|
body = body + " i = i + 1" + NL
|
|
body = body + " }" + NL
|
|
body = body + " if (counter != " + text(expected) + ") return \"closure: expected " + text(expected) + " got \" + text(counter)"
|
|
|
|
return body
|
|
}
|
|
|
|
// Generate a record property test
|
|
function gen_record_test() {
|
|
var a = gen_int_literal()
|
|
var b = gen_int_literal()
|
|
var sum = a.val + b.val
|
|
|
|
var body = "var r = {a: " + a.src + ", b: " + b.src + "}" + NL
|
|
body = body + " var result = r.a + r.b" + NL
|
|
body = body + " if (result != " + text(sum) + ") return \"record: expected " + text(sum) + " got \" + text(result)"
|
|
|
|
return body
|
|
}
|
|
|
|
// Generate an array test
|
|
function gen_array_test() {
|
|
var n = rand_int(2, 10)
|
|
var vals = []
|
|
var i = 0
|
|
var sum = 0
|
|
var v = 0
|
|
while (i < n) {
|
|
v = rand_int(-100, 100)
|
|
vals[] = v
|
|
sum = sum + v
|
|
i = i + 1
|
|
}
|
|
|
|
var val_strs = []
|
|
i = 0
|
|
while (i < n) {
|
|
val_strs[] = text(vals[i])
|
|
i = i + 1
|
|
}
|
|
|
|
var body = "var a = [" + text(val_strs, ", ") + "]" + NL
|
|
body = body + " var _sum = 0" + NL
|
|
body = body + " var i = 0" + NL
|
|
body = body + " while (i < length(a)) {" + NL
|
|
body = body + " _sum = _sum + a[i]" + NL
|
|
body = body + " i = i + 1" + NL
|
|
body = body + " }" + NL
|
|
body = body + " if (_sum != " + text(sum) + ") return \"array_sum: expected " + text(sum) + " got \" + text(_sum)"
|
|
|
|
return body
|
|
}
|
|
|
|
// Generate a nested function / higher-order test
|
|
function gen_higher_order_test() {
|
|
var mul = rand_int(2, 10)
|
|
var input = rand_int(1, 100)
|
|
var expected = input * mul
|
|
|
|
var body = "var make_mul = function(m) {" + NL
|
|
body = body + " return function(x) { return x * m }" + NL
|
|
body = body + " }" + NL
|
|
body = body + " var fn = make_mul(" + text(mul) + ")" + NL
|
|
body = body + " var result = fn(" + text(input) + ")" + NL
|
|
body = body + " if (result != " + text(expected) + ") return \"higher_order: expected " + text(expected) + " got \" + text(result)"
|
|
|
|
return body
|
|
}
|
|
|
|
// Generate a disruption handling test
|
|
function gen_disrupt_test() {
|
|
var body = "var caught = false" + NL
|
|
body = body + " var _fn = function() { disrupt } disruption { caught = true }" + NL
|
|
body = body + " _fn()" + NL
|
|
body = body + " if (!caught) return \"disrupt: expected to catch disruption\""
|
|
|
|
return body
|
|
}
|
|
|
|
// Generate a text operation test
|
|
function gen_text_op_test() {
|
|
var words = ["hello", "world", "foo", "bar", "baz"]
|
|
var w1 = pick(words)
|
|
var w2 = pick(words)
|
|
var expected = w1 + w2
|
|
|
|
var body = "var a = \"" + w1 + "\"" + NL
|
|
body = body + " var b = \"" + w2 + "\"" + NL
|
|
body = body + " var c = a + b" + NL
|
|
body = body + " if (c != \"" + expected + "\") return \"text_op: expected " + expected + " got \" + c"
|
|
|
|
return body
|
|
}
|
|
|
|
// All generators
|
|
var generators = [
|
|
gen_if_else_test,
|
|
gen_loop_test,
|
|
gen_closure_test,
|
|
gen_record_test,
|
|
gen_array_test,
|
|
gen_higher_order_test,
|
|
gen_disrupt_test,
|
|
gen_text_op_test
|
|
]
|
|
|
|
// Generate a complete self-checking .cm program
|
|
function generate(s) {
|
|
seed(s)
|
|
|
|
var num_tests = rand_int(5, 15)
|
|
var src = "// Auto-generated fuzz test (seed=" + text(s) + ")\nreturn {\n"
|
|
var i = 0
|
|
var gen = null
|
|
var body = null
|
|
|
|
while (i < num_tests) {
|
|
gen = pick(generators)
|
|
body = gen()
|
|
if (i > 0) src = src + ",\n"
|
|
src = src + " fuzz_" + text(i) + ": function() {\n"
|
|
src = src + " " + body + "\n"
|
|
src = src + " }"
|
|
i = i + 1
|
|
}
|
|
|
|
src = src + "\n}\n"
|
|
return src
|
|
}
|
|
|
|
return {
|
|
generate: generate,
|
|
seed: seed
|
|
}
|