mirror of
https://github.com/logos-messaging/nim-ffi.git
synced 2026-07-27 10:03:15 +00:00
196 lines
6.7 KiB
Nim
196 lines
6.7 KiB
Nim
## FFIProcMeta.doc → each target's comment syntax. Extraction: test_doc_extraction.nim.
|
|
|
|
import std/strutils
|
|
import unittest2
|
|
import ffi/codegen/[meta, c, cpp, rust, cddl]
|
|
|
|
const
|
|
CtorDoc = "Builds a widget from `config`."
|
|
MethodDoc = "Echoes `req` back.\nSecond line."
|
|
DtorDoc = "Releases the widget."
|
|
EventDoc = "Fires once the echo lands."
|
|
ScalarDoc = "Adds two numbers, no CBOR in sight."
|
|
|
|
let types = @[
|
|
FFITypeMeta(
|
|
name: "EchoRequest", fields: @[FFIFieldMeta(name: "message", typeName: "string")]
|
|
),
|
|
FFITypeMeta(
|
|
name: "EchoResponse", fields: @[FFIFieldMeta(name: "echoed", typeName: "string")]
|
|
),
|
|
]
|
|
|
|
let procs = @[
|
|
FFIProcMeta(
|
|
procName: "widget_create",
|
|
libName: "widget",
|
|
kind: FFIKind.CTOR,
|
|
libTypeName: "Widget",
|
|
extraParams: @[FFIParamMeta(name: "config", typeName: "EchoRequest")],
|
|
returnTypeName: "Widget",
|
|
doc: CtorDoc,
|
|
),
|
|
FFIProcMeta(
|
|
procName: "widget_echo",
|
|
libName: "widget",
|
|
kind: FFIKind.FFI,
|
|
libTypeName: "Widget",
|
|
extraParams: @[FFIParamMeta(name: "req", typeName: "EchoRequest")],
|
|
returnTypeName: "EchoResponse",
|
|
doc: MethodDoc,
|
|
),
|
|
FFIProcMeta(
|
|
procName: "widget_destroy",
|
|
libName: "widget",
|
|
kind: FFIKind.DTOR,
|
|
libTypeName: "Widget",
|
|
returnTypeName: "",
|
|
doc: DtorDoc,
|
|
),
|
|
]
|
|
|
|
let events = @[
|
|
FFIEventMeta(
|
|
wireName: "on_echoed",
|
|
nimProcName: "onEchoed",
|
|
libName: "widget",
|
|
payloadTypeName: "EchoResponse",
|
|
doc: EventDoc,
|
|
)
|
|
]
|
|
|
|
let undocumented = block:
|
|
var stripped = procs
|
|
for p in stripped.mitems:
|
|
p.doc = ""
|
|
stripped
|
|
|
|
func withoutDocLines(s: string): string =
|
|
## Drops `///` lines by content, so a generator's own hardcoded ones survive.
|
|
var rendered: seq[string] = @[]
|
|
for doc in [CtorDoc, MethodDoc, DtorDoc]:
|
|
for line in doc.splitLines():
|
|
rendered.add(("/// " & line).strip())
|
|
var kept: seq[string] = @[]
|
|
for line in s.splitLines():
|
|
if line.strip() notin rendered:
|
|
kept.add(line)
|
|
return kept.join("\n")
|
|
|
|
proc checkNoDocText(rendered: string) =
|
|
for doc in [CtorDoc, MethodDoc, DtorDoc]:
|
|
for line in doc.splitLines():
|
|
check line notin rendered
|
|
|
|
suite "doc comments reach the C header":
|
|
setup:
|
|
let header = generateCLibHeader(procs, types, "widget")
|
|
|
|
test "the exported symbol carries the doc":
|
|
check "/** " & CtorDoc & " */\nvoid* widget_create(" in header
|
|
|
|
test "the high-level wrapper carries the doc":
|
|
check "/** " & CtorDoc & " */\nstatic inline int widget_ctx_create(" in header
|
|
check "/** " & DtorDoc & " */\nstatic inline int widget_ctx_destroy(" in header
|
|
|
|
test "a multi-line doc becomes a star block":
|
|
check "/**\n * Echoes `req` back.\n * Second line.\n */\nstatic inline int widget_ctx_echo(" in
|
|
header
|
|
|
|
test "the listener registration carries the event's doc":
|
|
let withEvents = generateCLibHeader(procs, types, "widget", events)
|
|
check "/** " & EventDoc &
|
|
" */\nstatic inline uint64_t widget_ctx_add_on_echoed_listener(" in withEvents
|
|
|
|
test "an undocumented registry leaks no doc text":
|
|
checkNoDocText(generateCLibHeader(undocumented, types, "widget"))
|
|
|
|
suite "doc comments reach the abi = c header":
|
|
setup:
|
|
var abiProcs = procs
|
|
abiProcs.add(
|
|
FFIProcMeta(
|
|
procName: "widget_add",
|
|
libName: "widget",
|
|
kind: FFIKind.FFI,
|
|
libTypeName: "Widget",
|
|
extraParams: @[
|
|
FFIParamMeta(name: "a", typeName: "int"),
|
|
FFIParamMeta(name: "b", typeName: "int"),
|
|
],
|
|
returnTypeName: "int",
|
|
scalarFastPath: true,
|
|
doc: ScalarDoc,
|
|
)
|
|
)
|
|
for p in abiProcs.mitems:
|
|
p.abiFormat = ABIFormat.C
|
|
var abiTypes = types
|
|
for t in abiTypes.mitems:
|
|
t.abiFormat = ABIFormat.C
|
|
let header = generateCAbiLibHeader(abiProcs, abiTypes, "widget")
|
|
|
|
test "the exported symbol carries the doc":
|
|
check "/** " & CtorDoc & " */\nvoid* widget_create(" in header
|
|
|
|
test "the high-level wrapper carries the doc":
|
|
check "/** " & CtorDoc & " */\nstatic inline int widget_ctx_create(" in header
|
|
check "/** " & DtorDoc & " */\nstatic inline int widget_ctx_destroy(" in header
|
|
|
|
test "the scalar fast path carries the doc":
|
|
check "/** " & ScalarDoc & " */\nstatic inline int widget_ctx_add(" in header
|
|
|
|
suite "doc comments reach the C++ header":
|
|
setup:
|
|
let header = generateCppHeader(procs, types, "widget")
|
|
|
|
test "the extern \"C\" declaration carries the doc":
|
|
check "/** " & CtorDoc & " */\nvoid* widget_create(" in header
|
|
|
|
test "class members carry an indented /// doc":
|
|
check " /// " & CtorDoc &
|
|
"\n static Result<std::unique_ptr<WidgetCtx>> create(" in header
|
|
check " /// Echoes `req` back.\n /// Second line.\n Result<EchoResponse> echo(" in
|
|
header
|
|
|
|
test "the async variant repeats the doc":
|
|
check " /// Echoes `req` back.\n /// Second line.\n std::future<Result<EchoResponse>> echoAsync(" in
|
|
header
|
|
|
|
test "the listener registration carries the event's doc":
|
|
check " /// " & EventDoc & "\n ListenerHandle addOnEchoedListener(" in
|
|
generateCppHeader(procs, types, "widget", events)
|
|
|
|
test "an undocumented registry leaks no doc text":
|
|
checkNoDocText(generateCppHeader(undocumented, types, "widget"))
|
|
|
|
suite "doc comments reach the Rust bindings":
|
|
test "the extern \"C\" block carries the doc":
|
|
let ffiRs = generateFFIRs(procs)
|
|
check " /// " & CtorDoc & "\n pub fn widget_create(" in ffiRs
|
|
check " /// Echoes `req` back.\n /// Second line.\n pub fn widget_echo(" in
|
|
ffiRs
|
|
|
|
test "the high-level api carries the doc":
|
|
let apiRs = generateApiRs(procs, "widget")
|
|
check " /// " & CtorDoc & "\n pub fn create(" in apiRs
|
|
check " /// Echoes `req` back.\n /// Second line.\n pub fn echo(" in apiRs
|
|
check " /// Echoes `req` back.\n /// Second line.\n pub async fn echo_async(" in
|
|
apiRs
|
|
|
|
test "the listener registration carries the event's doc above the generated one":
|
|
check " /// " & EventDoc & "\n /// Register a typed listener for `on_echoed`" in
|
|
generateApiRs(procs, "widget", events)
|
|
|
|
test "docs are purely additive — stripping them reproduces the undocumented output":
|
|
check withoutDocLines(generateFFIRs(procs)) == generateFFIRs(undocumented)
|
|
check withoutDocLines(generateApiRs(procs, "widget")) ==
|
|
generateApiRs(undocumented, "widget")
|
|
|
|
suite "doc comments reach the CDDL schema":
|
|
test "each documented rule gets a `;` comment under its header":
|
|
let schema = generateCddlSchema(procs, types, "widget", "widget.nim")
|
|
check "; widget_create (ctor)\n; " & CtorDoc & "\n" in schema
|
|
check "; widget_echo (ffi)\n; Echoes `req` back.\n; Second line.\n" in schema
|
|
check "; widget_destroy (dtor)\n; " & DtorDoc & "\n" in schema
|