#!/usr/bin/env tarantool
--
-- protoc-gen-tarantool / pb runtime conformance test runner.
--
-- Speaks the Google protobuf conformance protocol on stdin/stdout: each
-- request and response is a little-endian uint32 length followed by a
-- `conformance.ConformanceRequest` / `conformance.ConformanceResponse`
-- serialized as protobuf. Loops until EOF.
--
-- Run against the canonical Google conformance binary like so:
--
-- conformance_test_runner --enforce_recommended \
-- tarantool cmd/conformance-runner.lua
--
-- LUA_PATH must let this script find `pb`, the generated modules, and
-- the conformance core module under `cmd/`:
--
-- LUA_PATH="./runtime/?/init.lua;./runtime/?.lua;\
-- ./examples/expected/?.lua;./examples/expected/?/init.lua;\
-- ./cmd/?.lua;;"
-- Make the script self-contained: derive package.path from this script's
-- own location instead of trusting LUA_PATH. `conformance_test_runner`
-- spawns the child with a stripped (or otherwise unhelpful) environment;
-- if we crash on the first require() the parent reads no reply and
-- reports the test as a timeout. Setting paths here avoids that.
local function script_dir()
local src = debug.getinfo(1, 'S').source
if src:sub(1, 1) == '@' then src = src:sub(2) end
return src:match('^(.*/)[^/]+$') or './'
end
local SCRIPT_DIR = script_dir()
local REPO_ROOT = SCRIPT_DIR .. '..'
package.path = table.concat({
REPO_ROOT .. '/runtime/?/init.lua',
REPO_ROOT .. '/runtime/?.lua',
REPO_ROOT .. '/examples/expected/?.lua',
REPO_ROOT .. '/examples/expected/?/init.lua',
REPO_ROOT .. '/cmd/?.lua',
REPO_ROOT .. '/cmd/?/init.lua',
package.path,
}, ';')
-- package.searchpath returns the first existing file, not the first
-- loadable one. Cross-platform bind mounts (host macOS .dylib and
-- container Linux .so coexisting in the tree after `just conformance-c`)
-- can mask the correct binary if the wrong extension is listed first.
-- Order by host platform.
local _ext_first, _ext_second = '.so', '.dylib'
if jit and jit.os == 'OSX' then
_ext_first, _ext_second = '.dylib', '.so'
end
package.cpath = table.concat({
REPO_ROOT .. '/runtime/?' .. _ext_first,
REPO_ROOT .. '/runtime/?' .. _ext_second,
REPO_ROOT .. '/runtime/?/init' .. _ext_first,
REPO_ROOT .. '/runtime/?/init' .. _ext_second,
package.cpath,
}, ';')
-- Force our `pb` to win over any `lua-protobuf` (starwing) `.rocks/lib/
-- tarantool/pb.so` left behind by transitive dependencies (luatest
-- pulls it in). Tarantool's rocks-aware loader runs ahead of the
-- override package.path/cpath above, so we pre-populate package.loaded.
-- Submodules (`pb.codec`, etc.) have unique names and resolve via the
-- prepended package.path without collision.
package.loaded.pb = dofile(REPO_ROOT .. '/runtime/pb/init.lua')
local core = require('cmd.conformance.core')
-- When invoked by conformance_test_runner the child's stdin is a pipe.
-- Default C-stdio buffering can hold the request bytes inside libc until
-- BUFSIZ-aligned data arrives, which never happens because the parent is
-- waiting for our reply first. Disable input buffering so io.stdin:read(n)
-- returns as soon as `n` bytes are available; pair with unbuffered stdout
-- so we don't rely solely on per-write :flush().
io.stdin:setvbuf('no')
io.stdout:setvbuf('no')
local function read_n(n)
local got = io.stdin:read(n)
if got == nil or #got < n then return nil end
return got
end
local function read_request_bytes()
local hdr = read_n(4)
if hdr == nil then return nil end
local b1, b2, b3, b4 = hdr:byte(1, 4)
return read_n(b1 + b2 * 256 + b3 * 65536 + b4 * 16777216)
end
local function write_response_bytes(payload)
local n = #payload
io.stdout:write(string.char(
n % 256,
math.floor(n / 256) % 256,
math.floor(n / 65536) % 256,
math.floor(n / 16777216) % 256))
io.stdout:write(payload)
io.stdout:flush()
end
while true do
local req_bytes = read_request_bytes()
if req_bytes == nil then break end
write_response_bytes(core.handle_request(req_bytes))
end
os.exit(0)