mirror of
https://github.com/logos-messaging/nim-ffi.git
synced 2026-07-27 01:53:26 +00:00
154 lines
5.2 KiB
Nim
154 lines
5.2 KiB
Nim
## Unit tests for `{.ffi.}` enums: per-backend rendering of a synthetic registry,
|
|
## a Nim-side CBOR round-trip, and an end-to-end generation run over a fixture.
|
|
|
|
import std/[os, strutils]
|
|
import unittest2
|
|
import ./fixture_gen
|
|
import ffi
|
|
import ffi/codegen/[meta, c, cpp, rust, cddl]
|
|
|
|
proc enumValue(name, wire: string, ord: int): FFIEnumValueMeta =
|
|
FFIEnumValueMeta(name: name, wire: wire, ord: ord)
|
|
|
|
let colorMeta = FFITypeMeta(
|
|
name: "Color",
|
|
enumValues: @[enumValue("cRed", "cRed", 0), enumValue("cGreen", "green", 1)],
|
|
)
|
|
|
|
let paintMeta = FFITypeMeta(
|
|
name: "PaintRequest", fields: @[FFIFieldMeta(name: "color", typeName: "Color")]
|
|
)
|
|
|
|
let ctor = FFIProcMeta(
|
|
procName: "enumlib_create",
|
|
libName: "enumlib",
|
|
kind: FFIKind.CTOR,
|
|
libTypeName: "EnumLib",
|
|
returnTypeName: "EnumLib",
|
|
)
|
|
|
|
let paint = FFIProcMeta(
|
|
procName: "enumlib_paint",
|
|
libName: "enumlib",
|
|
kind: FFIKind.FFI,
|
|
libTypeName: "EnumLib",
|
|
extraParams: @[FFIParamMeta(name: "color", typeName: "Color")],
|
|
returnTypeName: "PaintRequest",
|
|
)
|
|
|
|
suite "enums in the C header":
|
|
setup:
|
|
let header = generateCLibHeader(@[ctor, paint], @[colorMeta, paintMeta], "enumlib")
|
|
|
|
test "the enum becomes a C enum with explicit ordinals":
|
|
check "typedef enum {" in header
|
|
check "COLOR_C_RED = 0," in header
|
|
check "COLOR_C_GREEN = 1" in header
|
|
check "} Color;" in header
|
|
|
|
test "the codec maps to the CBOR text form, not the ordinal":
|
|
check "case COLOR_C_RED: return cbor_encode_text_stringz(e, \"cRed\");" in header
|
|
check "case COLOR_C_GREEN: return cbor_encode_text_stringz(e, \"green\");" in header
|
|
check "if (strcmp(buf, \"green\") == 0) { *out = COLOR_C_GREEN;" in header
|
|
|
|
test "the decode buffer is sized to the longest wire name":
|
|
# Wire forms are "cRed" (4) and "green" (5); the longest plus a NUL is 6.
|
|
check "char buf[6];" in header
|
|
|
|
test "an enum field is a plain member, not a nested struct":
|
|
check " Color color;" in header
|
|
|
|
test "an enum param passes by value":
|
|
check "int enumlib_ctx_paint(const EnumLibCtx* ctx, Color color," in header
|
|
check "const Color* color" notin header
|
|
|
|
suite "enums in the C++ header":
|
|
setup:
|
|
let header = generateCppHeader(@[ctor, paint], @[colorMeta, paintMeta], "enumlib")
|
|
|
|
test "the enum becomes a scoped enum class":
|
|
check "enum class Color {" in header
|
|
check " cRed = 0," in header
|
|
|
|
test "the codec is declared before the struct codec that calls it":
|
|
check header.find("enum class Color") < header.find("struct PaintRequest")
|
|
|
|
test "decode goes through the std::string overload":
|
|
check "if (name == \"green\") { v = Color::cGreen; return CborNoError; }" in header
|
|
|
|
suite "enums in the Rust crate":
|
|
setup:
|
|
let typesRs = generateTypesRs(@[colorMeta, paintMeta], @[ctor, paint])
|
|
|
|
test "the enum becomes a fieldless Rust enum":
|
|
check "pub enum Color {" in typesRs
|
|
check " CRed," in typesRs
|
|
|
|
test "serde renames each variant to its wire form":
|
|
check "#[serde(rename = \"green\")]" in typesRs
|
|
check " CGreen," in typesRs
|
|
|
|
test "a variant whose name already matches the wire form needs no rename":
|
|
check "#[serde(rename = \"CRed\")]" notin typesRs
|
|
|
|
suite "enums in the CDDL schema":
|
|
test "the rule is a choice of the wire strings":
|
|
let schema =
|
|
generateCddlSchema(@[ctor, paint], @[colorMeta, paintMeta], "enumlib", "x.nim")
|
|
check "Color = \"cRed\" / \"green\"" in schema
|
|
|
|
type
|
|
RoundTripColor {.ffi.} = enum
|
|
rtRed
|
|
rtBlue
|
|
|
|
RoundTripLevel {.ffi.} = enum
|
|
rtLow = "low"
|
|
rtHigh = "high"
|
|
|
|
type RoundTripPaint {.ffi.} = object
|
|
color: RoundTripColor
|
|
level: RoundTripLevel
|
|
|
|
suite "enum CBOR round-trip on the Nim side":
|
|
test "an enum field survives encode/decode":
|
|
let sent = RoundTripPaint(color: rtBlue, level: rtHigh)
|
|
let back = cborDecode(cborEncode(sent), RoundTripPaint)
|
|
check back.isOk()
|
|
check back.get() == sent
|
|
|
|
test "the wire form is the associated string when the enum declares one":
|
|
var wire = ""
|
|
for b in cborEncode(RoundTripPaint(color: rtRed, level: rtLow)):
|
|
wire.add(char(b))
|
|
check "low" in wire
|
|
check "rtRed" in wire
|
|
|
|
let genned = genFixtureBindings("enum_fixture", "c")
|
|
|
|
suite "{.ffi.} enum end-to-end generation":
|
|
setup:
|
|
require genned.exitCode == 0
|
|
let header = readFile(genned.outDir / "enumlib.h")
|
|
|
|
test "explicit ordinals and associated strings both survive to the C header":
|
|
check "STATUS_ST_IDLE = 2," in header
|
|
check "STATUS_ST_BUSY = 7" in header
|
|
check "case LEVEL_L_LOW: return cbor_encode_text_stringz(e, \"low\");" in header
|
|
|
|
test "an enum return reaches the host through the reply callback":
|
|
check "(*EnumLibPickReplyFn)(int err_code, const Color* reply," in header
|
|
check "int enumlib_ctx_pick(const EnumLibCtx* ctx, Level level," in header
|
|
|
|
suite "enums on the abi = c path are rejected, not silently mis-wired":
|
|
test "an enum in an abi = c library fails to compile":
|
|
let (output, code) = checkFixture("enum_abi_c_type_fixture")
|
|
check code != 0
|
|
check "`abi = c` does not support enum types yet" in output
|
|
|
|
test "an enum reached from an abi = c type fails to compile":
|
|
let (output, code) = checkFixture("enum_abi_c_field_fixture")
|
|
check code != 0
|
|
check "cwire: `abi = c` does not support enum types yet" in output
|
|
check "Color" in output
|