Skip to content

Commit 254281e

Browse files
committed
fix(bindings): declare no Rust test targets for pyo3/napi FFI shims
The pyo3/napi cdylib crates leave their extension symbols undefined for resolution at import/dlopen time, so an ordinary cargo test executable can't link against them. Set test = false / doctest = false on both binding crates and drop their now-dead #[cfg(test)] modules, since coverage already comes from the real FFI boundary (query.test.cjs, test_query.py). Update the CI comment to match.
1 parent cb7f5b3 commit 254281e

6 files changed

Lines changed: 17 additions & 117 deletions

File tree

.github/workflows/test.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,10 @@ jobs:
7373
- uses: Swatinem/rust-cache@v2
7474
with:
7575
prefix-key: test
76-
# The pyo3/napi cdylib crates are extension modules that cannot be linked
77-
# as cargo test binaries; they are built via maturin/napi in the `bindings`
78-
# job below. Everything else is tested across all three OSes.
76+
# The pyo3/napi cdylib crates declare no Rust test targets (see their
77+
# Cargo.toml `[lib]` notes) and need Python/Node toolchains to build, so
78+
# they are built and tested through their real FFI boundary by the
79+
# `bindings` job. Everything else is tested across all three OSes.
7980
- name: Run tests
8081
run: cargo test --workspace --all-features --exclude code2graph-py --exclude code2graph-node
8182

bindings/node/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,15 @@ rust-version.workspace = true
66
license.workspace = true
77
publish = false
88

9+
# N-API symbols are provided by the host Node process at dlopen time and have no
10+
# link-time target, so a cargo test harness — an ordinary executable — is
11+
# unlinkable. This crate therefore declares no Rust test targets: it is a thin
12+
# FFI shim, verified through the real boundary by `test/query.test.cjs`.
913
[lib]
1014
name = "code2graph_node"
1115
crate-type = ["cdylib"]
16+
test = false
17+
doctest = false
1218

1319
[dependencies]
1420
code2graph_core = { package = "code2graph", path = "../..", version = "=0.0.0", default-features = false, features = ["serde", "rust", "python", "typescript", "go", "java", "c", "cpp", "ruby", "php", "shell", "swift", "kotlin", "solidity", "sql", "hcl", "csharp", "scala", "dart", "lua", "luau", "pascal", "svelte"] }

bindings/node/src/convert.rs

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -99,39 +99,3 @@ pub(crate) fn positive_limit(limit: u32) -> napi::Result<usize> {
9999
}
100100
Ok(limit as usize)
101101
}
102-
103-
#[cfg(test)]
104-
mod tests {
105-
use code2graph_core::{Descriptor, SymbolId};
106-
use serde_json::json;
107-
108-
use super::{code_graph, edge_filter, positive_limit, symbol_id};
109-
110-
#[test]
111-
fn accepts_only_lossless_structural_id_objects() {
112-
let id = SymbolId::global("rust", vec![Descriptor::Term("run".into())]);
113-
let value = serde_json::to_value(&id).expect("serialize id");
114-
assert_eq!(symbol_id(value).expect("object id"), id);
115-
assert!(symbol_id(json!(id.to_scip_string())).is_err());
116-
assert!(
117-
code_graph(json!({"symbols": [{"id": id.to_scip_string()}], "edges": []})).is_err()
118-
);
119-
}
120-
121-
#[test]
122-
fn filters_are_canonical_and_limits_are_positive() {
123-
assert!(edge_filter(Some("call".into()), None, None).is_err());
124-
assert!(edge_filter(None, Some("exact".into()), None).is_err());
125-
assert!(edge_filter(None, None, Some("scope_graph".into())).is_err());
126-
assert!(
127-
edge_filter(
128-
Some("Call".into()),
129-
Some("Exact".into()),
130-
Some("ScopeGraph".into()),
131-
)
132-
.is_ok()
133-
);
134-
assert!(positive_limit(0).is_err());
135-
assert_eq!(positive_limit(1).expect("positive"), 1);
136-
}
137-
}

bindings/node/src/query.rs

Lines changed: 0 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -130,66 +130,3 @@ impl GraphIndex {
130130
}))
131131
}
132132
}
133-
134-
#[cfg(test)]
135-
mod tests {
136-
use code2graph_core::{
137-
CodeGraph, Confidence, Descriptor, Edge, Occurrence, Provenance, RefRole, SymbolId,
138-
};
139-
140-
use super::GraphIndex;
141-
142-
fn id(name: &str) -> SymbolId {
143-
SymbolId::global("rust", vec![Descriptor::Term(name.into())])
144-
}
145-
146-
fn edge(from: SymbolId, to: SymbolId, byte: usize) -> Edge {
147-
Edge {
148-
from,
149-
to,
150-
role: RefRole::Call,
151-
confidence: Confidence::Exact,
152-
provenance: Provenance::ScopeGraph,
153-
occ: Occurrence {
154-
file: "src/a.rs".into(),
155-
line: 1,
156-
col: 0,
157-
byte,
158-
},
159-
}
160-
}
161-
162-
#[test]
163-
fn preserves_endpoint_ids_direction_and_bounded_impact_output() {
164-
let seed = SymbolId::local("vendor/api.rs", "remote");
165-
let caller = id("caller");
166-
let graph = CodeGraph {
167-
symbols: vec![],
168-
edges: vec![edge(caller.clone(), seed.clone(), 1)],
169-
};
170-
let index =
171-
GraphIndex::new(serde_json::to_value(graph).expect("graph json")).expect("index");
172-
let incoming = index
173-
.incoming(
174-
serde_json::to_value(&seed).unwrap(),
175-
1,
176-
Some("Call".into()),
177-
None,
178-
None,
179-
)
180-
.expect("incoming");
181-
assert_eq!(incoming.as_array().unwrap().len(), 1);
182-
let outgoing = index
183-
.outgoing(serde_json::to_value(&seed).unwrap(), 1, None, None, None)
184-
.expect("outgoing");
185-
assert!(outgoing.as_array().unwrap().is_empty());
186-
let impact = index
187-
.impact(serde_json::to_value(&seed).unwrap(), 1, 1, None, None, None)
188-
.expect("impact");
189-
assert_eq!(
190-
impact["steps"][0]["symbol"],
191-
serde_json::to_value(caller).unwrap()
192-
);
193-
assert!(impact.get("visited").is_none());
194-
}
195-
}

bindings/python/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,16 @@ rust-version.workspace = true
66
license.workspace = true
77
publish = false
88

9+
# `pyo3/extension-module` deliberately leaves the CPython symbols undefined so
10+
# the interpreter resolves them at import time. That is correct for the shipped
11+
# cdylib but makes a cargo test harness — an ordinary executable — unlinkable,
12+
# so this crate declares no Rust test targets. It is a thin FFI shim; its
13+
# behaviour is verified through the real boundary by `tests/test_query.py`.
914
[lib]
1015
name = "code2graph"
1116
crate-type = ["cdylib"]
17+
test = false
18+
doctest = false
1219

1320
[dependencies]
1421
code2graph_core = { package = "code2graph", path = "../..", version = "=0.0.0", default-features = false, features = ["serde", "rust", "python", "typescript", "go", "java", "c", "cpp", "ruby", "php", "shell", "swift", "kotlin", "solidity", "sql", "hcl", "csharp", "scala", "dart", "lua", "luau", "pascal", "svelte"] }

bindings/python/src/convert.rs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -100,18 +100,3 @@ pub(crate) fn positive_limit(limit: u32) -> PyResult<usize> {
100100
}
101101
Ok(limit as usize)
102102
}
103-
104-
#[cfg(test)]
105-
mod tests {
106-
use super::{edge_filter, positive_limit};
107-
108-
#[test]
109-
fn filters_are_canonical_and_limits_are_positive() {
110-
assert!(edge_filter(Some("call"), None, None).is_err());
111-
assert!(edge_filter(None, Some("exact"), None).is_err());
112-
assert!(edge_filter(None, None, Some("scope_graph")).is_err());
113-
assert!(edge_filter(Some("Call"), Some("Exact"), Some("ScopeGraph")).is_ok());
114-
assert!(positive_limit(0).is_err());
115-
assert_eq!(positive_limit(1).expect("positive"), 1);
116-
}
117-
}

0 commit comments

Comments
 (0)