Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ jobs:
needs: rust
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Rust (stable)
run: |
Expand Down
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,9 @@
[submodule "tests/vendor/JSONTestSuite"]
path = tests/vendor/JSONTestSuite
url = https://github.com/nst/JSONTestSuite
[submodule "tests/vendor/cJSON"]
path = tests/vendor/cJSON
url = https://github.com/DaveGamble/cJSON.git
[submodule "tests/vendor/simdjson"]
path = tests/vendor/simdjson
url = https://github.com/simdjson/simdjson.git
22 changes: 22 additions & 0 deletions tests/fixtures/third_party/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Third-party JSON fixture sources

qjson reuses mature upstream JSON test data through git submodules instead of
copying large C/C++ test harnesses into this repository.

- `tests/vendor/cJSON`: `DaveGamble/cJSON`, MIT licensed. Rust and Lua tests
consume every `tests/inputs/test*` JSON fixture with matching `.expected`
files, the JSON files from `tests/json-patch-tests/`, and Rust ports parser
literals from cJSON number/string/array tests. `tests/inputs/test6` is an
upstream HTML error page, so qjson keeps it as a negative parse case.
- `tests/vendor/simdjson`: `simdjson/simdjson`, dual Apache-2.0/MIT licensed.
qjson uses the MIT option and consumes every single-document `.json` file in
`jsonexamples/`; the `.ndjson` streaming example is split by line so every
record is parsed as an individual JSON document.

The upstream submodules carry their own license files:

- `tests/vendor/cJSON/LICENSE`
- `tests/vendor/simdjson/LICENSE-MIT`

The local Rust and Lua harnesses are qjson tests; the upstream C/C++ harnesses
are left in the submodules as source material rather than compiled here.
97 changes: 96 additions & 1 deletion tests/lua/cjson_compat_spec.lua
Original file line number Diff line number Diff line change
@@ -1,7 +1,65 @@
local qjson = require("qjson")
local cjson = require("cjson")

describe("qjson vs lua-cjson", function()
local function read_file(path)
local f = assert(io.open(path, "rb"))
local s = f:read("*a")
f:close()
return s
end

local function deep_equal(a, b)
if a == b then
return true
end
if type(a) ~= type(b) then
return false
end
if type(a) ~= "table" then
return false
end
for k, v in pairs(a) do
if not deep_equal(v, b[k]) then
return false
end
end
for k in pairs(b) do
if a[k] == nil then
return false
end
end
return true
end

local function assert_materializes_like_lua_cjson(src)
assert.is_true(deep_equal(qjson.materialize(qjson.decode(src)), cjson.decode(src)))
end

local function assert_encodes_like_lua_cjson(src)
local out = qjson.encode(qjson.decode(src))
assert.is_true(deep_equal(cjson.decode(out), cjson.decode(src)))
end

local function assert_equivalent_json(src)
assert_materializes_like_lua_cjson(src)
assert_encodes_like_lua_cjson(src)
end

local function assert_fixture_paths(paths)
for _, path in ipairs(paths) do
local p = path

it("materializes like lua-cjson for fixture " .. p, function()
assert_materializes_like_lua_cjson(read_file(p))
end)

it("encodes a lua-cjson-equivalent value for fixture " .. p, function()
assert_encodes_like_lua_cjson(read_file(p))
end)
end
end

describe("qjson lua-cjson compatibility smoke", function()
it("agrees on simple string field", function()
local s = '{"a":"x"}'
assert.are.equal(cjson.decode(s).a, qjson.parse(s):get_str("a"))
Expand All @@ -27,3 +85,40 @@ describe("qjson vs lua-cjson", function()
assert.are.equal(cjson.decode(s).body.model, qjson.parse(s):get_str("body.model"))
end)
end)

describe("qjson cJSON upstream fixtures", function()
assert_fixture_paths({
"tests/vendor/cJSON/tests/inputs/test1",
"tests/vendor/cJSON/tests/inputs/test2",
"tests/vendor/cJSON/tests/inputs/test3",
"tests/vendor/cJSON/tests/inputs/test4",
"tests/vendor/cJSON/tests/inputs/test5",
"tests/vendor/cJSON/tests/inputs/test7",
"tests/vendor/cJSON/tests/inputs/test8",
"tests/vendor/cJSON/tests/inputs/test9",
"tests/vendor/cJSON/tests/inputs/test10",
"tests/vendor/cJSON/tests/inputs/test11",
"tests/vendor/cJSON/tests/json-patch-tests/cjson-utils-tests.json",
"tests/vendor/cJSON/tests/json-patch-tests/package.json",
"tests/vendor/cJSON/tests/json-patch-tests/spec_tests.json",
"tests/vendor/cJSON/tests/json-patch-tests/tests.json",
})
end)

describe("qjson simdjson upstream fixtures", function()
assert_fixture_paths({
"tests/vendor/simdjson/jsonexamples/citm_catalog.json",
"tests/vendor/simdjson/jsonexamples/example_config.json",
"tests/vendor/simdjson/jsonexamples/twitter.json",
})

it("materializes and encodes each simdjson NDJSON record like lua-cjson", function()
local src = read_file("tests/vendor/simdjson/jsonexamples/amazon_cellphones.ndjson")
local records = 0
for line in src:gmatch("([^\r\n]+)") do
records = records + 1
assert_equivalent_json(line)
end
assert.is_true(records >= 793)
end)
end)
Loading
Loading