//! End-to-end program tests: real Lua programs run on luna AND on the
//! installed PUC reference binary, then diff stdout byte-for-byte. Any
//! semantic divergence between luna and PUC at the program level
//! surfaces here.
//!
//! Each program is run on every supported dialect 5.1-5.5. Reference
//! binaries probed (must be in PATH or at the canonical locations from
//! `tests/official_run.rs`):
//! - lua-5.1, lua-5.2, lua-5.3, lua-5.4, lua-5.5
//!
//! If a reference binary is missing for a dialect, that dialect's test
//! is **skipped** (not failed) — so CI without all binaries still
//! reports the available comparisons rather than hard-failing.
//!
//! Programs deliberately cover: pure number recursion, string
//! manipulation, table mutation, pattern matching, coroutine
//! generator, sort, error handling. See `PROGRAMS` array.

use std::cell::RefCell;
use std::fmt::Write as _;
use std::process::{Command, Stdio};

use luna_core::runtime::Value;
use luna_core::version::LuaVersion;
use luna_core::vm::Vm;

// ---------------------------------------------------------------------------
// luna stdout capture: a thread_local Vec<u8> buffer that the custom
// `print` native writes into, replacing the stdout-writing default.

thread_local! {
    static CAPTURE: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
}

fn capture_print(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, luna_core::vm::LuaError> {
    // Same format as PUC print: tab-separated, trailing newline.
    // Use the Lua-level global `tostring` so the output formatting
    // matches PUC's `print` byte-for-byte (including number-to-string
    // dialect quirks).
    let tostring_key = Value::Str(vm.heap.intern(b"tostring"));
    let tostring_fn = vm.globals().get(tostring_key);
    let mut line: Vec<u8> = Vec::new();
    for i in 0..nargs {
        if i > 0 {
            line.push(b'\t');
        }
        let v = vm.nat_arg(fs, nargs, i);
        let ret = vm.call_value(tostring_fn, &[v])?;
        if let Some(Value::Str(s)) = ret.into_iter().next() {
            line.extend(s.as_bytes());
        }
    }
    line.push(b'\n');
    CAPTURE.with(|c| c.borrow_mut().extend_from_slice(&line));
    Ok(0)
}

fn drain_capture() -> Vec<u8> {
    CAPTURE.with(|c| std::mem::take(&mut *c.borrow_mut()))
}

fn run_on_luna(version: LuaVersion, src: &str) -> Vec<u8> {
    drain_capture(); // reset before run
    let mut vm = Vm::new(version);
    // Override print with the capture variant.
    let f = vm.native(capture_print);
    vm.set_global("print", f).unwrap();
    // PUC's `lua -e 'src'` reports errors with chunkname `(command line)`;
    // matching that lets `tostring(err)` outputs diff cleanly.
    let cl = vm
        .load(src.as_bytes(), b"=(command line)")
        .expect("luna load");
    vm.call_value(Value::Closure(cl), &[]).expect("luna run");
    drain_capture()
}

// ---------------------------------------------------------------------------
// PUC subprocess runner: invoke the reference binary with -e and capture
// stdout. Returns Some(output) iff the binary is available.

fn reference_bin_for(version: LuaVersion) -> Option<&'static str> {
    let candidates = match version {
        LuaVersion::Lua51 => &["lua-5.1"][..],
        LuaVersion::Lua52 => &["lua-5.2"][..],
        LuaVersion::Lua53 => &["lua-5.3"][..],
        LuaVersion::Lua54 | LuaVersion::MacroLua => &["lua-5.4"][..],
        LuaVersion::Lua55 => &["lua-5.5"][..],
    };
    candidates
        .iter()
        .find(|&&c| {
            Command::new(c)
                .arg("-v")
                .stdout(Stdio::null())
                .stderr(Stdio::null())
                .status()
                .is_ok()
        })
        .copied()
        .map(|v| v as _)
}

fn run_on_puc(bin: &str, src: &str) -> Vec<u8> {
    let mut child = Command::new(bin)
        .arg("-e")
        .arg(src)
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("spawn PUC");
    let _ = child.stdin.take();
    let out = child.wait_with_output().expect("PUC wait");
    if !out.status.success() {
        panic!(
            "PUC {} -e failed: {}",
            bin,
            String::from_utf8_lossy(&out.stderr)
        );
    }
    out.stdout
}

// ---------------------------------------------------------------------------
// Program catalog. Each program self-contained: can be run as
// `lua -e SOURCE`. Each MUST print its final result via `print(...)`
// so output capture works on both engines.

struct Program {
    name: &'static str,
    src: &'static str,
    // Minimum dialect that supports this program's features. Default
    // Lua51 means runs everywhere. Lua52 = `pcall` continuation-aware
    // or 3-arg `string.rep`. Lua53 = `//` `~` operators, integer
    // subtype semantics. Etc.
    min_version: LuaVersion,
}

const PROGRAMS: &[Program] = &[
    Program {
        name: "fib_recursive",
        src: r#"
local function f(n)
    if n < 2 then return n end
    return f(n-1) + f(n-2)
end
print(f(15))
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "factorial_iter",
        src: r#"
local function fact(n)
    local p = 1
    for i = 1, n do p = p * i end
    return p
end
print(fact(10))
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "string_concat_loop",
        src: r#"
local parts = {}
for i = 1, 50 do parts[i] = tostring(i) end
print(table.concat(parts, ","))
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "table_index_sort",
        src: r#"
local t = {5, 3, 1, 4, 2, 9, 7, 8, 6, 10}
table.sort(t)
print(table.concat(t, " "))
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "string_pattern_capture",
        src: r#"
local s = "key1=42, key2=99, key3=137"
for k, v in s:gmatch("(%w+)=(%d+)") do
    print(k, v)
end
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "coroutine_generator",
        src: r#"
local function gen(n)
    return coroutine.wrap(function()
        for i = 1, n do coroutine.yield(i * i) end
    end)
end
local out = {}
for v in gen(5) do out[#out+1] = tostring(v) end
print(table.concat(out, ","))
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "pcall_error_chain",
        src: r#"
local function inner() error("inner-boom") end
local function middle() inner() end
local ok, err = pcall(middle)
print(ok, type(err))
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "linked_list_traverse",
        src: r#"
-- build a 100-node linked list, sum the values
local head = nil
for i = 100, 1, -1 do head = {val = i, next = head} end
local sum = 0
local n = head
while n do sum = sum + n.val; n = n.next end
print(sum)
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "metatable_inheritance",
        src: r#"
local Animal = {sound = "?"}
Animal.__index = Animal
function Animal.speak(a) return a.name .. " says " .. a.sound end
local function new(name, sound)
    return setmetatable({name = name, sound = sound}, Animal)
end
local cat = new("Cat", "meow")
local dog = new("Dog", "woof")
print(cat:speak())
print(dog:speak())
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "prime_sieve_int",
        src: r#"
local N = 100
local is = {}
for i = 2, N do is[i] = true end
for i = 2, N do
    if is[i] then
        for j = i*i, N, i do is[j] = false end
    end
end
local primes = {}
for i = 2, N do if is[i] then primes[#primes+1] = tostring(i) end end
print(table.concat(primes, ","))
"#,
        min_version: LuaVersion::Lua51,
    },
    // ---- edge-case round (long-tail bug fishing) ------------------------
    Program {
        name: "pcall_returns_multiple",
        src: r#"
local ok, a, b, c = pcall(function() return 1, 2, 3 end)
print(ok, a, b, c)
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "select_varargs",
        src: r#"
local function f(...) return select('#', ...), select(2, ...) end
print(f('a', 'b', 'c', 'd'))
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        // 5.1 cannot yield across a pcall boundary ("attempt to
        // yield across C-call boundary"); 5.2+ made pcall continuation-
        // aware. Requires 5.2+.
        name: "nested_coroutine_pcall",
        src: r#"
local function inner()
    coroutine.yield(10)
    coroutine.yield(20)
end
local co = coroutine.create(function()
    local ok, err = pcall(inner)
    print("pcall returned:", ok)
    coroutine.yield(99)
end)
local _, a = coroutine.resume(co)
local _, b = coroutine.resume(co)
local _, c = coroutine.resume(co)
print(a, b, c)
"#,
        min_version: LuaVersion::Lua52,
    },
    Program {
        name: "pattern_anchors_captures",
        src: r#"
-- anchors + multi-capture
print(string.match("abc123xyz", "^(%a+)(%d+)(%a+)$"))
-- alternation via char class
for w in string.gmatch("apple,banana;cherry", "[^,;]+") do print(w) end
-- pattern with %b balanced match
print(string.match("(foo(bar)baz)", "%b()"))
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "table_insert_remove_mid",
        src: r#"
local t = {1, 2, 3, 4, 5}
table.insert(t, 3, 99)
print(table.concat(t, ","))
local removed = table.remove(t, 4)
print(removed, table.concat(t, ","))
table.insert(t, 100)
print(table.concat(t, ","))
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "string_byte_char_format",
        src: r#"
print(string.byte("A"), string.byte("z"))
print(string.char(65, 66, 67))
print(string.format("%05d %.3f %s", 7, 3.14159, "hi"))
print(string.format("%x %X %o", 255, 255, 8))
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        // 3-arg `string.rep(s, n, sep)` is a 5.2+ extension; PUC 5.1 ignores
        // the separator. Restrict to 5.2+.
        name: "string_reverse_rep_sub",
        src: r#"
print(string.reverse("hello"))
print(string.rep("ab", 4))
print(string.rep("x", 3, "-"))
print(string.sub("abcdefgh", 2, 5))
print(string.sub("abcdefgh", -3))
print(string.upper("Mixed Case 42"))
print(string.lower("Mixed Case 42"))
"#,
        min_version: LuaVersion::Lua52,
    },
    Program {
        name: "math_floor_modulo_bounds",
        src: r#"
print(math.floor(3.9), math.floor(-3.1), math.ceil(3.1), math.ceil(-3.9))
print(math.max(1, 5, 3, 7, 2))
print(math.min(1, 5, 3, 7, 2))
print(math.huge > 1e300)
print(math.huge == math.huge)
print(0/0 ~= 0/0)  -- NaN never equals itself
print(7 % 3, -7 % 3, 7 % -3, -7 % -3)  -- Lua modulo semantics
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "tostring_tonumber_edges",
        src: r#"
print(tostring(nil), tostring(true), tostring(false))
print(tonumber("42"), tonumber("3.14"))
print(tonumber("0x1f"), tonumber("not a number"))
print(tonumber("100", 2), tonumber("ff", 16), tonumber("777", 8))
print(type(tonumber("42")))
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "error_object_propagation",
        src: r#"
-- error with non-string value (table)
local ok, err = pcall(function() error({code = 42, msg = "boom"}) end)
print(ok, type(err), err.code, err.msg)
-- error with nil
local ok2, err2 = pcall(function() error(nil) end)
print(ok2, type(err2))
-- assert with message
local ok3, err3 = pcall(function() assert(false, "assertion-message") end)
print(ok3, err3)
"#,
        min_version: LuaVersion::Lua51,
    },
    // `pcall + non-tail-call deep recursion` raises stack overflow. The
    // `1 +` blocks tail-call optimization so each call grows the value
    // stack until MAX_LUA_STACK fires (~250k frames, a few ms). luna
    // and PUC match. The tail-call form (`return f(n+1)`) is excluded —
    // both engines run it forever (TCO is correct Lua semantics; not a
    // bug). See docs/known-bugs/fixed/pcall-stack-overflow-investigation.md
    Program {
        name: "pcall_stack_overflow",
        src: r#"
local function f(n) return 1 + f(n + 1) end
local ok, err = pcall(f, 0)
print(ok)
print(string.find(tostring(err), "stack overflow") ~= nil)
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "ipairs_stops_at_nil",
        src: r#"
-- ipairs is the integer-key iterator that stops at the first nil
local t = {10, 20, nil, 40, 50}
local count, sum = 0, 0
for i, v in ipairs(t) do count = count + 1; sum = sum + v end
print(count, sum)  -- expects 2, 30 (stops at nil)
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "pairs_iteration_order_irrelevant",
        src: r#"
-- pairs iteration order is unspecified; sum the keys and values to
-- get a deterministic comparison cross-engine
local t = {x = 1, y = 2, z = 3, [4] = 4, [10] = 10}
local ksum, vsum = 0, 0
for k, v in pairs(t) do
    if type(k) == "number" then ksum = ksum + k end
    if type(v) == "number" then vsum = vsum + v end
end
print(ksum, vsum)
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "string_len_byte_position",
        src: r#"
local s = "Hello, world!"
print(#s, string.len(s))
print(s:sub(1, 5), s:sub(-6, -1))
local b, e = string.find(s, "world")
print(b, e)
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "table_sort_with_comparator",
        src: r#"
local t = {"banana", "apple", "cherry", "date"}
table.sort(t)
print(table.concat(t, "/"))
table.sort(t, function(a, b) return #a < #b end)
print(table.concat(t, "/"))
"#,
        min_version: LuaVersion::Lua51,
    },
    // ---- second edge-case round (numeric, string, dialect-specific) ----
    Program {
        name: "gsub_with_table_replacement",
        src: r#"
local t = {name = "Alice", age = "30"}
print((string.gsub("Hello $name, age $age", "%$(%w+)", t)))
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "gsub_with_function_replacement",
        src: r#"
print((string.gsub("hello world", "(%w+)", function(w) return w:upper() end)))
print((string.gsub("abc123def456", "%d+", function(n) return "[" .. n .. "]" end)))
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "gsub_with_count_limit",
        src: r#"
local s, n = string.gsub("a-b-c-d-e", "-", "/", 2)
print(s, n)
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "integer_division_53plus",
        src: r#"
print(10 // 3, -10 // 3, 10 // -3, -10 // -3)
print(10.0 // 3, 10 // 3.0)
print(math.floor(10 / 3))
"#,
        min_version: LuaVersion::Lua53,
    },
    Program {
        name: "bitwise_53plus",
        src: r#"
print(0xff & 0x0f, 0xff | 0x100, 0xff ~ 0x0f, ~0)
print(1 << 8, 256 >> 4)
print(string.format("%x", 0xABCD ~ 0xFFFF))
"#,
        min_version: LuaVersion::Lua53,
    },
    Program {
        name: "string_format_pct_q",
        src: r#"
-- %q produces a Lua-readable quoted string. Output format is dialect-
-- stable for printable ASCII without embedded controls.
print(string.format("%q", "hello"))
print(string.format("%q", [[plain ascii]]))
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "long_string_literal",
        src: r#"
local s = [[
line1
line2
line3]]
print(#s)
local s2 = [==[
nested [[ test ]]
]==]
print(#s2)
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "goto_forward_label",
        src: r#"
-- 5.2+: goto/label
local i = 0
::start::
i = i + 1
if i < 5 then goto start end
print(i)
"#,
        min_version: LuaVersion::Lua52,
    },
    Program {
        // <const> violation is a COMPILE-time error in both engines —
        // can't be caught by pcall (pcall sees no chunk to call yet).
        // Just verify the const read path. Negative-shape coverage
        // requires capturing stderr, out of scope for this stdout-diff
        // harness.
        name: "const_attribute_54plus",
        src: r#"
local x <const> = 42
print(x, x + 1)
local s <const> = "hello"
print(s, #s)
"#,
        min_version: LuaVersion::Lua54,
    },
    Program {
        name: "string_pack_unpack_53plus",
        src: r#"
-- string.pack / string.unpack added in 5.3
local s = string.pack(">i4", 12345)
print(#s)
local n, pos = string.unpack(">i4", s)
print(n, pos)
print(string.packsize(">i4i2"))
"#,
        min_version: LuaVersion::Lua53,
    },
    Program {
        name: "math_type_53plus",
        src: r#"
-- math.type added in 5.3
print(math.type(1), math.type(1.0), math.type("1"))
print(math.tointeger(3.0), math.tointeger(3.5))
"#,
        min_version: LuaVersion::Lua53,
    },
    Program {
        name: "string_to_number_coercion",
        src: r#"
print(tonumber("42"))
print(tonumber("3.14"))
print(tonumber("  42  "))
print(tonumber("0xff"))
print(tonumber("1e3"))
print(tonumber("not a number"))
print(tonumber(""))
print(tonumber(nil))
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "coroutine_close_54plus",
        src: r#"
-- coroutine.close added in 5.4
local co = coroutine.create(function() coroutine.yield(1); coroutine.yield(2) end)
local _, a = coroutine.resume(co)
print(a, coroutine.status(co))
local closed = coroutine.close(co)
print(closed, coroutine.status(co))
"#,
        min_version: LuaVersion::Lua54,
    },
    Program {
        name: "tostring_special_floats",
        src: r#"
-- tostring on Inf, -Inf, NaN. PUC outputs are dialect-stable
-- ("inf", "-inf", "nan" since 5.3; "1.#INF" / "-1.#INF" / "-1.#IND"
-- on Windows 5.1/5.2 — luna's reference is unix-PUC behavior).
local inf = 1/0
print(inf == math.huge)
print(-inf == -math.huge)
local nan = 0/0
print(nan == nan)
"#,
        min_version: LuaVersion::Lua53,
    },
    Program {
        name: "table_iteration_complete",
        src: r#"
-- mixed string/integer keys via next + pairs
local t = {alpha = 1, beta = 2, gamma = 3, [1] = "a", [2] = "b"}
local strk_count, intk_count = 0, 0
for k in pairs(t) do
    if type(k) == "string" then strk_count = strk_count + 1
    else intk_count = intk_count + 1 end
end
print(strk_count, intk_count)
"#,
        min_version: LuaVersion::Lua51,
    },
    // ---- third round: error-message diff coverage --------------------
    Program {
        // `nil + 1` raises a PUC error whose message differs across
        // dialects (5.4 added variable-context like "local 'x'").
        // Check only that pcall caught it + "arithmetic" appears in msg.
        name: "err_arith_on_nil",
        src: r#"
local function bad() local x; return x + 1 end
local ok, err = pcall(bad)
print(ok)
print(string.find(tostring(err), "arithmetic") ~= nil)
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "err_index_nil_field",
        src: r#"
-- accessing a field on nil
local function bad() local x; return x.field end
local ok, err = pcall(bad)
print(ok)
print(string.find(tostring(err), "nil") ~= nil)
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "err_call_non_callable",
        src: r#"
-- calling a non-callable value
local function bad() local x = 42; return x() end
local ok, err = pcall(bad)
print(ok)
print(string.find(tostring(err), "call") ~= nil)
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "err_concat_with_nil",
        src: r#"
local function bad() return "hello" .. nil end
local ok, err = pcall(bad)
print(ok)
print(string.find(tostring(err), "concatenate") ~= nil)
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "err_compare_incompatible",
        src: r#"
-- comparing incompatible types
local function bad() return "abc" < 42 end
local ok, err = pcall(bad)
print(ok)
print(string.find(tostring(err), "compare") ~= nil)
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "err_index_nil_with_string_key",
        src: r#"
local function bad()
    local t
    return t["key"]
end
local ok, err = pcall(bad)
print(ok)
print(string.find(tostring(err), "nil") ~= nil)
"#,
        min_version: LuaVersion::Lua51,
    },
    Program {
        name: "err_divide_by_zero_int_53plus",
        src: r#"
-- 5.3+ integer division by zero raises; float / 0 returns Inf
local function bad() local x = 1; return x // 0 end
local ok, err = pcall(bad)
print(ok)
print(string.find(tostring(err), "zero") ~= nil)
-- float division by zero is NOT an error
print(1.0 / 0.0)  -- "inf"
print(-1.0 / 0.0)  -- "-inf"
"#,
        min_version: LuaVersion::Lua53,
    },
    Program {
        // 5.3+ only — PUC 5.1/5.2 `luaB_assert` stringifies via
        // `luaL_error("%s", tostring(msg))`, dropping the table-ness.
        // PUC 5.3+ uses bare `lua_error()` and preserves the message
        // object. luna's assert always preserves (5.3+ semantics).
        name: "assert_with_table_message",
        src: r#"
local function bad() assert(false, {code = 7, msg = "fail"}) end
local ok, err = pcall(bad)
print(ok, type(err), err.code, err.msg)
"#,
        min_version: LuaVersion::Lua53,
    },
    Program {
        name: "error_with_level_arg",
        src: r#"
-- error(msg, 2) reports the CALLER's line, not the error() call's
local function inner() error("err-from-inner", 2) end
local function outer() inner() end
local ok, err = pcall(outer)
print(ok)
-- both engines should report a line in the same file context
print(string.find(tostring(err), "err-from-inner") ~= nil)
"#,
        min_version: LuaVersion::Lua51,
    },
];

fn run_diff_for(version: LuaVersion, label: &str, prog: &Program) -> Result<(), String> {
    if prog.min_version > version {
        return Ok(()); // skip — feature not in dialect
    }

    let bin = match reference_bin_for(version) {
        Some(b) => b,
        None => return Ok(()), // reference unavailable on this host — skip
    };

    let luna_out = run_on_luna(version, prog.src);
    let puc_out = run_on_puc(bin, prog.src);

    if luna_out != puc_out {
        let luna_str = String::from_utf8_lossy(&luna_out);
        let puc_str = String::from_utf8_lossy(&puc_out);
        return Err(format!(
            "e2e divergence — [{}] program={}\n  luna stdout: {:?}\n  PUC  stdout: {:?}",
            label, prog.name, luna_str, puc_str
        ));
    }
    Ok(())
}

fn e2e_diff_for_dialect(version: LuaVersion, label: &str) {
    let mut failures: Vec<String> = Vec::new();
    for prog in PROGRAMS {
        if let Err(e) = run_diff_for(version, label, prog) {
            failures.push(e);
        }
    }
    if !failures.is_empty() {
        let mut s = String::new();
        for f in &failures {
            writeln!(&mut s, "{}", f).unwrap();
        }
        panic!(
            "e2e dialect {}: {} divergences\n{}",
            label,
            failures.len(),
            s
        );
    }
}

#[test]
fn e2e_5_1() {
    e2e_diff_for_dialect(LuaVersion::Lua51, "5.1");
}
#[test]
fn e2e_5_2() {
    e2e_diff_for_dialect(LuaVersion::Lua52, "5.2");
}
#[test]
fn e2e_5_3() {
    e2e_diff_for_dialect(LuaVersion::Lua53, "5.3");
}
#[test]
fn e2e_5_4() {
    e2e_diff_for_dialect(LuaVersion::Lua54, "5.4");
}
#[test]
fn e2e_5_5() {
    e2e_diff_for_dialect(LuaVersion::Lua55, "5.5");
}
