local cfg = dstest.config({
substrate = "docker",
seed = 0x0A0A,
http_retries = 6,
http_retry_delay = 300,
})
local function abspath(p)
if p:sub(1, 1) == "/" then return p end
local popen = assert(io.popen("pwd -P"), "pwd unavailable")
local cwd = assert(popen:read("*l"))
popen:close()
return cwd .. "/" .. p
end
local SPEC = abspath("examples/openapi.json")
local server = dstest.setup(cfg, {
image = "kennethreitz/httpbin",
ports = { 80 },
})
local client = dstest.setup(cfg, {
image = "curlimages/curl:latest",
cmd = { "sleep", "300" },
volumes = { SPEC .. ":/specs/openapi.json:ro" },
depends = { server },
})
local function openapi_pairs(path)
local fh = assert(io.open(path, "r"))
local json = assert(fh:read("*a"), "cannot read " .. path)
fh:close()
local pos = 1
local function skip()
while json:sub(pos, pos):match("%s") do pos = pos + 1 end
end
local function parse_value()
skip()
local c = json:sub(pos, pos)
if c == "{" then
pos = pos + 1
local t = {}
skip()
if json:sub(pos, pos) == "}" then pos = pos + 1 return t end
while true do
skip(); assert(json:sub(pos, pos) == '"', "expected key")
local k = assert(parse_value(), "expected string key")
skip(); assert(json:sub(pos, pos) == ":", "expected colon"); pos = pos + 1
t[k] = parse_value()
skip()
if json:sub(pos, pos) == "," then pos = pos + 1
else assert(json:sub(pos, pos) == "}", "expected }"); pos = pos + 1; break end
end
return t
elseif c == "\"" then
pos = pos + 1
local out = {}
while true do
local ch = json:sub(pos, pos)
if ch == "\"" then pos = pos + 1 break end
if ch == "\\" then
local esc = json:sub(pos + 1, pos + 1)
if esc == "n" then out[#out + 1] = "\n"
else out[#out + 1] = esc end
pos = pos + 2
else
out[#out + 1] = ch
pos = pos + 1
end
end
return table.concat(out)
else
local tok = json:match("^[%w%.%-]+", pos)
assert(tok, "bad value at " .. pos)
pos = pos + #tok
if tok == "true" then return true
elseif tok == "false" then return false
elseif tok == "null" then return nil
else return tonumber(tok) end
end
end
local root = assert(parse_value(), "empty spec")
local paths = root.paths or {}
local keys = {}
for p in pairs(paths) do keys[#keys + 1] = p end
table.sort(keys)
local i = 0
return function()
while i < #keys do
i = i + 1
local p = keys[i]
local methods = paths[p]
for m in pairs(methods) do
local up = string.upper(m)
if up == "GET" or up == "POST" or up == "PUT"
or up == "DELETE" or up == "PATCH" then
return up, p
end
end
end
return nil
end
end
local listed = {}
for method, path in openapi_pairs(SPEC) do
listed[#listed + 1] = method .. " " .. path
end
dstest.info("spec endpoints: " .. table.concat(listed, ", "))
local report = dstest.workload.http(server, {
duration_secs = 8,
rate = 12,
openapi = SPEC,
})
dstest.info(string.format(
"workload: %d requests, %d ok, %d failed (avg %dms, max %dms)",
report.total_requests, report.ok, report.failed,
report.avg_latency_ms, report.max_latency_ms
))
assert(report.failed == 0, "every spec-declared endpoint must be reachable")
for key, stats in pairs(report.breakdown) do
dstest.info(string.format(" %-14s ok=%d failed=%d", key, stats.ok, stats.failed))
end
dstest.dst.clear(client)
dstest.dst.clear(server)
dstest.info("openapi example complete")