blob: 0bbbf1645c0123705d5102d7bec98fe4a19eafb0 [file]
--
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements. See the NOTICE file distributed with
-- this work for additional information regarding copyright ownership.
-- The ASF licenses this file to You under the Apache License, Version 2.0
-- (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
local cjson = require("cjson")
local _M = {}
-- Parse a data-otlp.json file (one JSON object per line) into a spans_by_id table.
local function parse_spans(filepath)
local file = io.open(filepath, "rb")
if not file then
return nil, "cannot open " .. filepath
end
local spans_by_id = {}
for line in file:lines() do
if line and #line > 0 then
local ok, data = pcall(cjson.decode, line)
if ok and data.resourceSpans then
for _, rs in ipairs(data.resourceSpans) do
for _, ss in ipairs(rs.scopeSpans or {}) do
for _, span in ipairs(ss.spans or {}) do
spans_by_id[span.spanId] = span
end
end
end
end
end
end
file:close()
return spans_by_id
end
-- Find a child span of the given parent by name.
local function find_child(spans_by_id, parent_id, child_name)
for _, span in pairs(spans_by_id) do
if span.parentSpanId == parent_id and span.name == child_name then
return span
end
end
return nil
end
-- Convert span.attributes array into a key -> value map.
local function get_attr_map(span)
local map = {}
for _, attr in ipairs(span.attributes or {}) do
local v = attr.value
map[attr.key] = v.stringValue or v.intValue or v.boolValue
end
return map
end
-- Recursively verify a span tree node against the expected structure.
local function verify(spans_by_id, expected, actual, path, errors)
if not actual then
table.insert(errors, path .. ": span not found")
return
end
if expected.kind and actual.kind ~= expected.kind then
table.insert(errors, string.format(
"%s: expected kind=%d, got=%s",
path, expected.kind, tostring(actual.kind)))
end
if expected.attributes then
local attr_map = get_attr_map(actual)
for key, val in pairs(expected.attributes) do
if tostring(attr_map[key]) ~= tostring(val) then
table.insert(errors, string.format(
"%s: attr '%s' expected '%s', got '%s'",
path, key, tostring(val), tostring(attr_map[key])))
end
end
end
if expected.children then
for _, child_exp in ipairs(expected.children) do
local child = find_child(spans_by_id, actual.spanId, child_exp.name)
verify(spans_by_id, child_exp, child,
path .. " > " .. child_exp.name, errors)
end
end
end
-- Main entry point: verify a span tree from a data-otlp.json file.
-- Returns true on success, or (false, error_string) on failure.
function _M.verify_tree(filepath, expected_tree)
local spans_by_id, err = parse_spans(filepath)
if not spans_by_id then
return false, err
end
-- find root span (no parentSpanId)
local root
for _, span in pairs(spans_by_id) do
if span.name == expected_tree.name
and (not span.parentSpanId or span.parentSpanId == "")
then
root = span
break
end
end
local errors = {}
verify(spans_by_id, expected_tree, root, expected_tree.name, errors)
if #errors > 0 then
return false, table.concat(errors, "\n")
end
return true
end
return _M