# vim:set ft= ts=4 sw=4 et fdm=marker:
use Test::Nginx::Socket::Lua;

#worker_connections(1014);
#master_on();
#workers(2);
#log_level('warn');

repeat_each(2);
#repeat_each(1);

plan tests => repeat_each() * (blocks() * 3);

#no_diff();
#no_long_string();
run_tests();

__DATA__

=== TEST 1: bytecode ("ngx.say('hello');")
--- config
    root html;
    location /save_call {
        content_by_lua '
            ngx.req.read_body();
            local b = ngx.req.get_body_data();
            f = io.open(ngx.var.realpath_root.."/test.lua", "w");
            -- luajit bytecode: sub(149,-1), lua bytecode: sub(1,147)
            if jit then
                if not string.find(jit.version, "LuaJIT 2.0") then
                    ngx.say("test skipped")
                    return
                end
                f:write(string.sub(b, 149));
            else
                f:write(string.sub(b, 1, 147));
            end
            f:close(); res = ngx.location.capture("/call");
            ngx.print(res.body)
        ';
    }
    location /call {
        content_by_lua_file $realpath_root/test.lua;
    }
--- request eval
"POST /save_call
\x1b\x4c\x75\x61\x51\x00\x01\x04\x08\x04\x08\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x40\x74\x65\x73\x74\x2e\x6c\x75\x61\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x02\x05\x00\x00\x00\x05\x00\x00\x00\x06\x40\x40\x00\x41\x80\x00\x00\x1c\x40\x00\x01\x1e\x00\x80\x00\x03\x00\x00\x00\x04\x04\x00\x00\x00\x00\x00\x00\x00\x6e\x67\x78\x00\x04\x04\x00\x00\x00\x00\x00\x00\x00\x73\x61\x79\x00\x04\x06\x00\x00\x00\x00\x00\x00\x00\x68\x65\x6c\x6c\x6f\x00\x00\x00\x00\x00\x05\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
\x1b\x4c\x4a\x01\x02\x29\x02\x00\x02\x00\x03\x00\x05\x34\x00\x00\x00\x37\x00\x01\x00\x25\x01\x02\x00\x3e\x00\x02\x01\x47\x00\x01\x00\x0a\x68\x65\x6c\x6c\x6f\x08\x73\x61\x79\x08\x6e\x67\x78\x00"
--- response_body_like chop
^(?:hello|test skipped)$
--- no_error_log
[error]



=== TEST 2: luajit load lua bytecode or lua load luajit bytecode
--- config
    root html;
    location /save_call {
        content_by_lua '
            ngx.req.read_body();
            local b = ngx.req.get_body_data();
            f = io.open(ngx.var.realpath_root.."/test.lua", "w");
            -- luajit bytecode: sub(149,-1), lua bytecode: sub(1,147)
            if not package.loaded["jit"] then
                f:write(string.sub(b, 149));
            else
                f:write(string.sub(b, 1, 147));
            end
            f:close(); res = ngx.location.capture("/call");
            if res.status == 200 then
                ngx.print(res.body)
            else
                ngx.say("error")
            end
        ';
    }
    location /call {
        content_by_lua_file $realpath_root/test.lua;
    }
--- request eval
"POST /save_call
\x1b\x4c\x75\x61\x51\x00\x01\x04\x08\x04\x08\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x40\x74\x65\x73\x74\x2e\x6c\x75\x61\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x02\x05\x00\x00\x00\x05\x00\x00\x00\x06\x40\x40\x00\x41\x80\x00\x00\x1c\x40\x00\x01\x1e\x00\x80\x00\x03\x00\x00\x00\x04\x04\x00\x00\x00\x00\x00\x00\x00\x6e\x67\x78\x00\x04\x04\x00\x00\x00\x00\x00\x00\x00\x73\x61\x79\x00\x04\x06\x00\x00\x00\x00\x00\x00\x00\x68\x65\x6c\x6c\x6f\x00\x00\x00\x00\x00\x05\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
\x1b\x4c\x4a\x01\x02\x29\x02\x00\x02\x00\x03\x00\x05\x34\x00\x00\x00\x37\x00\x01\x00\x25\x01\x02\x00\x3e\x00\x02\x01\x47\x00\x01\x00\x0a\x68\x65\x6c\x6c\x6f\x08\x73\x61\x79\x08\x6e\x67\x78\x00"
--- response_body
error
--- error_log eval
qr/failed to load external Lua file ".*?test\.lua": bad byte-code header/



=== TEST 3: unknown bytecode version
--- config
    root html;
    location /save_call {
        content_by_lua '
            ngx.req.read_body();
            local b = ngx.req.get_body_data();
            f = io.open(ngx.var.realpath_root.."/test.lua", "w");
            -- luajit bytecode: sub(149,-1), lua bytecode: sub(1,147)
            if package.loaded["jit"] then
                f:write(string.sub(b, 149));
            else
                f:write(string.sub(b, 1, 147));
            end
            f:close(); res = ngx.location.capture("/call");
            if res.status == 200 then
                ngx.print(res.body)
            else
                ngx.say("error")
            end
        ';
    }
    location /call {
        content_by_lua_file $realpath_root/test.lua;
    }
--- request eval
"POST /save_call
\x1b\x4c\x75\x61\x52\x00\x01\x04\x08\x04\x08\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x40\x74\x65\x73\x74\x2e\x6c\x75\x61\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x02\x05\x00\x00\x00\x05\x00\x00\x00\x06\x40\x40\x00\x41\x80\x00\x00\x1c\x40\x00\x01\x1e\x00\x80\x00\x03\x00\x00\x00\x04\x04\x00\x00\x00\x00\x00\x00\x00\x6e\x67\x78\x00\x04\x04\x00\x00\x00\x00\x00\x00\x00\x73\x61\x79\x00\x04\x06\x00\x00\x00\x00\x00\x00\x00\x68\x65\x6c\x6c\x6f\x00\x00\x00\x00\x00\x05\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
\x1b\x4c\x4a\x80\x02\x29\x02\x00\x02\x00\x03\x00\x05\x34\x00\x00\x00\x37\x00\x01\x00\x25\x01\x02\x00\x3e\x00\x02\x01\x47\x00\x01\x00\x0a\x68\x65\x6c\x6c\x6f\x08\x73\x61\x79\x08\x6e\x67\x78\x00"
--- response_body
error
--- error_log
bytecode format version unsupported



=== TEST 4: bytecode (big endian)
--- config
    root html;
    location /save_call {
        content_by_lua '
            ngx.req.read_body();
            local b = ngx.req.get_body_data();
            f = io.open(ngx.var.realpath_root.."/test.lua", "w");
            -- luajit bytecode: sub(149,-1), lua bytecode: sub(1,147)
            local do_jit
            if jit then
                if not string.find(jit.version, "LuaJIT 2.0") then
                    ngx.say("test skipped")
                    return
                end

                do_jit = true; f:write(string.sub(b, 149));
            else
                f:write(string.sub(b, 1, 147));
            end
            f:close(); res = ngx.location.capture("/call");
            if do_jit and res.status == 200 then
                ngx.say("ok")
            elseif not do_jit and res.status == 500 then
                ngx.say("ok")
            else
                ngx.say("error")
            end
        ';
    }
    location /call {
        content_by_lua_file $realpath_root/test.lua;
    }
--- request eval
"POST /save_call
\x1b\x4c\x75\x61\x51\x00\x00\x04\x08\x04\x08\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x40\x74\x65\x73\x74\x2e\x6c\x75\x61\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x02\x05\x00\x00\x00\x05\x00\x00\x00\x06\x40\x40\x00\x41\x80\x00\x00\x1c\x40\x00\x01\x1e\x00\x80\x00\x03\x00\x00\x00\x04\x04\x00\x00\x00\x00\x00\x00\x00\x6e\x67\x78\x00\x04\x04\x00\x00\x00\x00\x00\x00\x00\x73\x61\x79\x00\x04\x06\x00\x00\x00\x00\x00\x00\x00\x68\x65\x6c\x6c\x6f\x00\x00\x00\x00\x00\x05\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
\x1b\x4c\x4a\x01\x03\x29\x02\x00\x02\x00\x03\x00\x05\x00\x00\x00\x34\x00\x01\x00\x37\x00\x02\x01\x25\x01\x02\x00\x3e\x00\x01\x00\x47\x0a\x68\x65\x6c\x6c\x6f\x08\x73\x61\x79\x08\x6e\x67\x78\x00"
--- response_body_like chop
^(?:ok|test skipped)$
--- no_error_log
[error]



=== TEST 5: good header but bad body
--- config
    root html;
    location /save_call {
        content_by_lua '
            ngx.req.read_body();
            local b = ngx.req.get_body_data();
            f = io.open(ngx.var.realpath_root.."/test.lua", "w");
            -- luajit bytecode: sub(149,-1), lua bytecode: sub(1,147)
            local jit;
            if package.loaded["jit"] then
                jit = true;
                f:write(string.sub(b, 149));
            else
                f:write(string.sub(b, 1, 147));
            end
            if not jit then
                f:close(); res = ngx.location.capture("/call");
                if res.status == 200 then
                    ngx.print("ok")
                else
                    ngx.say("error")
                end
            else
            -- luajit will get a segmentation fault with bad bytecode,
            -- so here just skip this case for luajit
                ngx.say("error")
            end
        ';
    }
    location /call {
        content_by_lua_file $realpath_root/test.lua;
    }
--- request eval
"POST /save_call
\x1b\x4c\x75\x61\x51\x00\x01\x04\x08\x04\x08\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x40\x74\x65\x73\x74\x2e\x6c\x75\x61\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x02\x05\x00\x00\x00\xff\xff\xff\xff\x06\x40\x40\x00\x41\x80\x00\x00\x1c\x40\x00\x01\x1e\x00\x80\x00\x03\x00\x00\x00\x04\x04\x00\x00\x00\x00\x00\x00\x00\x6e\x67\x78\x00\x04\x04\x00\x00\x00\x00\x00\x00\x00\x73\x61\x79\x00\x04\x06\x00\x00\x00\x00\x00\x00\x00\x68\x65\x6c\x6c\x6f\x00\x00\x00\x00\x00\x05\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
\x1b\x4c\x4a\x01\x02\x29\x02\x00\x02\x00\x03\x00\x05\xff\xff\xff\xff\x37\x00\x01\x00\x25\x01\x02\x00\x3e\x00\x02\x01\x47\x00\x01\x00\x0a\x68\x65\x6c\x6c\x6f\x08\x73\x61\x79\x08\x6e\x67\x78\x00"
--- response_body
error
--- no_error_log
[error]



=== TEST 6: stripped(lua) & no stripped(luajit)
--- config
    root html;
    location /save_call {
        content_by_lua '
            ngx.req.read_body();
            local b = ngx.req.get_body_data();
            f = io.open(ngx.var.realpath_root.."/test.lua", "w");
            -- luajit bytecode: sub(149,-1), lua bytecode: sub(1,147)
            if jit then
                if not string.find(jit.version, "LuaJIT 2.0") then
                    ngx.say("test skipped")
                    return
                end

                f:write(string.sub(b, 119));
            else
                f:write(string.sub(b, 1, 117));
            end
            f:close(); res = ngx.location.capture("/call");
            ngx.print(res.body)
        ';
    }
    location /call {
        content_by_lua_file $realpath_root/test.lua;
    }
--- request eval
"POST /save_call
\x1b\x4c\x75\x61\x51\x00\x01\x04\x08\x04\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x02\x05\x00\x00\x00\x05\x00\x00\x00\x06\x40\x40\x00\x41\x80\x00\x00\x1c\x40\x00\x01\x1e\x00\x80\x00\x03\x00\x00\x00\x04\x04\x00\x00\x00\x00\x00\x00\x00\x6e\x67\x78\x00\x04\x04\x00\x00\x00\x00\x00\x00\x00\x73\x61\x79\x00\x04\x06\x00\x00\x00\x00\x00\x00\x00\x68\x65\x6c\x6c\x6f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
\x1b\x4c\x4a\x01\x00\x09\x40\x74\x65\x73\x74\x2e\x6c\x75\x61\x32\x02\x00\x02\x00\x03\x00\x05\x06\x00\x02\x34\x00\x00\x00\x37\x00\x01\x00\x25\x01\x02\x00\x3e\x00\x02\x01\x47\x00\x01\x00\x0a\x68\x65\x6c\x6c\x6f\x08\x73\x61\x79\x08\x6e\x67\x78\x01\x01\x01\x01\x01\x00\x00"
--- response_body_like chop
^(?:hello|test skipped)$
--- no_error_log
[error]



=== TEST 7: generate & load bytecode for LuaJIT (stripped)
--- config
    location = /t {
        content_by_lua '
            local bcsave = require "jit.bcsave"
            if jit then
                local prefix = ngx.config.prefix()
                local infile = prefix .. "html/a.lua"
                local outfile = prefix .. "html/a.luac"
                bcsave.start("-s", infile, outfile)
                return ngx.exec("/call")
            end

            ngx.say("test skipped!")
        ';
    }
    location = /call {
        content_by_lua_file html/a.luac;
    }
--- request
    GET /t

--- user_files
>>> a.lua
ngx.status = 201 ngx.say("hello from Lua!")
--- response_body_like chop
^(?:hello from Lua!|test skipped!)$
--- no_error_log
[error]
--- error_code: 201



=== TEST 8: generate & load bytecode for LuaJIT (not stripped)
--- config
    location = /t {
        content_by_lua '
            local bcsave = require "jit.bcsave"
            if jit then
                local prefix = ngx.config.prefix()
                local infile = prefix .. "html/a.lua"
                local outfile = prefix .. "html/a.luac"
                bcsave.start("-g", infile, outfile)
                return ngx.exec("/call")
            end

            ngx.say("test skipped!")
        ';
    }
    location = /call {
        content_by_lua_file html/a.luac;
    }
--- request
    GET /t

--- user_files
>>> a.lua
ngx.status = 201 ngx.say("hello from Lua!")
--- response_body_like chop
^(?:hello from Lua!|test skipped!)$
--- no_error_log
[error]
--- error_code: 201



=== TEST 9: bytecode (not stripped)
--- config
    location = /t {
        content_by_lua_block {
            local f = assert(loadstring("a = a and a + 1 or 1 ngx.say('a = ', a)", "=code"))
            local bc = string.dump(f)
            local f = assert(io.open("t/servroot/html/a.luac", "w"))
            f:write(bc)
            f:close()
        }
    }

    location = /t2 {
        content_by_lua_file html/a.luac;
    }

    location = /main {
        echo_location /t;
        echo_location /t2;
    }
--- request
GET /main
--- response_body
a = 1
--- no_error_log
[error]



=== TEST 10: bytecode (stripped)
--- config
    location = /t {
        content_by_lua_block {
            local f = assert(loadstring("a = a and a + 1 or 1 ngx.say('a = ', a)", "=code"))
            local bc = string.dump(f, true)
            local f = assert(io.open("t/servroot/html/a.luac", "w"))
            f:write(bc)
            f:close()
        }
    }

    location = /t2 {
        content_by_lua_file html/a.luac;
    }

    location = /main {
        echo_location /t;
        echo_location /t2;
    }
--- request
GET /main
--- response_body
a = 1
--- no_error_log
[error]
