use codegraph::{CodeGraph, NodeType};
use codegraph_c::{CParser, CodeParser};
use std::path::Path;
#[test]
fn test_parse_hello_world() {
let source = r#"
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("hello.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.functions.len(), 1);
assert_eq!(file_info.imports.len(), 1);
}
#[test]
fn test_parse_multiple_functions() {
let source = r#"
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multiply(int a, int b) {
return a * b;
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("math.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.functions.len(), 3);
}
#[test]
fn test_parse_struct_with_typedef() {
let source = r#"
struct Node {
int data;
struct Node *next;
};
typedef struct Node Node;
Node* create_node(int data) {
return NULL;
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("node.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.classes.len(), 1); assert_eq!(file_info.functions.len(), 1); }
#[test]
fn test_parse_enum() {
let source = r#"
enum Status {
OK = 0,
ERROR = 1,
PENDING = 2
};
int get_status() {
return OK;
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("status.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.classes.len(), 1); assert_eq!(file_info.functions.len(), 1); }
#[test]
fn test_parse_union() {
let source = r#"
union Data {
int i;
float f;
char str[20];
};
void process_data(union Data *d) {
d->i = 10;
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("data.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.classes.len(), 1); assert_eq!(file_info.functions.len(), 1); }
#[test]
fn test_parse_static_functions() {
let source = r#"
static int helper(int x) {
return x * 2;
}
int public_func(int x) {
return helper(x) + 1;
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("module.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.functions.len(), 2);
}
#[test]
fn test_parse_multiple_includes() {
let source = r#"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "myheader.h"
void test() {}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("test.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.imports.len(), 4);
}
#[test]
fn test_parse_pointer_parameters() {
let source = r#"
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
void process_array(int arr[], int size) {
// Process array
}
void process_strings(char **strings, int count) {
// Process strings
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("pointers.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.functions.len(), 3);
}
#[test]
fn test_parse_variadic_function() {
let source = r#"
#include <stdarg.h>
int sum(int count, ...) {
va_list args;
va_start(args, count);
int total = 0;
for (int i = 0; i < count; i++) {
total += va_arg(args, int);
}
va_end(args);
return total;
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("varargs.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.functions.len(), 1);
}
#[test]
fn test_parse_complex_function() {
let source = r#"
int complex_function(int x, int y) {
int result = 0;
if (x > 0) {
for (int i = 0; i < x; i++) {
if (i % 2 == 0) {
result += i;
} else {
result -= i;
}
}
} else if (x < 0) {
while (y > 0) {
result += y;
y--;
}
} else {
switch (y) {
case 1:
result = 100;
break;
case 2:
result = 200;
break;
default:
result = 0;
break;
}
}
return result;
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("complex.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.functions.len(), 1);
}
#[test]
fn test_parse_header_file() {
let parser = CParser::new();
assert!(parser.can_parse(Path::new("header.h")));
assert!(parser.can_parse(Path::new("source.c")));
assert!(!parser.can_parse(Path::new("source.cpp")));
assert!(!parser.can_parse(Path::new("source.rs")));
}
#[test]
fn test_parse_nested_structs() {
let source = r#"
struct Address {
char street[100];
char city[50];
int zip;
};
struct Person {
char name[100];
int age;
struct Address address;
};
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("person.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.classes.len(), 2);
}
#[test]
fn test_parse_syntax_error() {
let source = r#"
int broken( {
// Missing closing brace
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("broken.c"), &mut graph);
assert!(result.is_err());
}
#[test]
fn test_parse_empty_file() {
let source = "";
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("empty.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.functions.len(), 0);
assert_eq!(file_info.classes.len(), 0);
}
#[test]
fn test_parse_comments_only() {
let source = r#"
// This is a comment
/* This is a
multi-line comment */
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("comments.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.functions.len(), 0);
}
#[test]
fn test_parser_metrics() {
let source = r#"
int test() { return 0; }
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let mut parser = CParser::new();
parser.reset_metrics();
let metrics = parser.metrics();
assert_eq!(metrics.files_attempted, 0);
let _ = parser.parse_source(source, Path::new("test.c"), &mut graph);
}
#[test]
fn test_parser_language() {
let parser = CParser::new();
assert_eq!(parser.language(), "c");
}
#[test]
fn test_parser_file_extensions() {
let parser = CParser::new();
let extensions = parser.file_extensions();
assert!(extensions.contains(&".c"));
assert!(extensions.contains(&".h"));
}
#[test]
fn test_parse_function_pointer_param() {
let source = r#"
void register_callback(void (*callback)(int)) {
// Store callback
}
void process(int (*compare)(const void*, const void*)) {
// Use comparator
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("callbacks.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.functions.len(), 2);
}
#[test]
fn test_parse_const_params() {
let source = r#"
void print_string(const char *str) {
// Print string
}
int compare(const int *a, const int *b) {
return *a - *b;
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("const.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.functions.len(), 2);
}
#[test]
fn test_parse_goto_complexity() {
let source = r#"
void cleanup(int *resources, int count) {
for (int i = 0; i < count; i++) {
if (resources[i] < 0) {
goto error;
}
}
return;
error:
// Cleanup code
return;
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("goto.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.functions.len(), 1);
}
#[test]
fn test_parse_ternary_operator() {
let source = r#"
int max(int a, int b) {
return a > b ? a : b;
}
int abs_val(int x) {
return x >= 0 ? x : -x;
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("ternary.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.functions.len(), 2);
}
#[test]
fn test_e2e_function_properties_stored_in_graph() {
let source = r#"
int add(int a, int b) {
return a + b;
}
static void helper(void) {
// Internal helper
}
char *get_name(const char *prefix, int id) {
return NULL;
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("functions.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.functions.len(), 3);
let function_ids = graph
.query()
.node_type(NodeType::Function)
.execute()
.unwrap();
assert_eq!(function_ids.len(), 3);
for func_id in &function_ids {
let node = graph.get_node(*func_id).unwrap();
assert_eq!(node.node_type, NodeType::Function);
let name = node.properties.get_string("name").unwrap();
assert!(!name.is_empty(), "Function should have a name");
assert!(
node.properties.get_int("line_start").is_some(),
"Function should have line_start"
);
assert!(
node.properties.get_int("line_end").is_some(),
"Function should have line_end"
);
assert!(
node.properties.get_string("visibility").is_some(),
"Function should have visibility"
);
}
let add_funcs: Vec<_> = function_ids
.iter()
.filter_map(|id| {
let node = graph.get_node(*id).ok()?;
let name = node.properties.get_string("name")?;
if name == "add" {
Some(node)
} else {
None
}
})
.collect();
assert_eq!(add_funcs.len(), 1, "Should find exactly one 'add' function");
let add_func = add_funcs[0];
assert_eq!(
add_func.properties.get_string("visibility").unwrap(),
"public"
);
let helper_funcs: Vec<_> = function_ids
.iter()
.filter_map(|id| {
let node = graph.get_node(*id).ok()?;
let name = node.properties.get_string("name")?;
if name == "helper" {
Some(node)
} else {
None
}
})
.collect();
assert_eq!(
helper_funcs.len(),
1,
"Should find exactly one 'helper' function"
);
let helper_func = helper_funcs[0];
assert_eq!(
helper_func.properties.get_string("visibility").unwrap(),
"private",
"Static function should have private visibility"
);
}
#[test]
fn test_e2e_struct_properties_stored_in_graph() {
let source = r#"
struct Point {
int x;
int y;
};
struct Rectangle {
struct Point top_left;
struct Point bottom_right;
int area;
};
enum Color {
RED = 0,
GREEN = 1,
BLUE = 2
};
union Value {
int i;
float f;
double d;
};
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("types.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.classes.len(), 4);
let class_ids = graph.query().node_type(NodeType::Class).execute().unwrap();
assert_eq!(class_ids.len(), 4);
for class_id in &class_ids {
let node = graph.get_node(*class_id).unwrap();
assert_eq!(node.node_type, NodeType::Class);
let name = node.properties.get_string("name").unwrap();
assert!(!name.is_empty(), "Class should have a name");
assert!(
node.properties.get_int("line_start").is_some(),
"Class should have line_start"
);
assert!(
node.properties.get_int("line_end").is_some(),
"Class should have line_end"
);
}
let class_names: Vec<String> = class_ids
.iter()
.filter_map(|id| {
let node = graph.get_node(*id).ok()?;
node.properties.get_string("name").map(String::from)
})
.collect();
assert!(
class_names.contains(&"Point".to_string()),
"Should find Point struct"
);
assert!(
class_names.contains(&"Rectangle".to_string()),
"Should find Rectangle struct"
);
assert!(
class_names.contains(&"Color".to_string()),
"Should find Color enum"
);
assert!(
class_names.contains(&"Value".to_string()),
"Should find Value union"
);
}
#[test]
fn test_e2e_query_builder_filters() {
let source = r#"
static int private_func1(void) { return 1; }
static int private_func2(void) { return 2; }
int public_func1(void) { return 3; }
int public_func2(void) { return 4; }
int public_func3(void) { return 5; }
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("visibility.c"), &mut graph);
assert!(result.is_ok());
assert_eq!(result.unwrap().functions.len(), 5);
let public_funcs = graph
.query()
.node_type(NodeType::Function)
.property("visibility", "public")
.execute()
.unwrap();
assert_eq!(public_funcs.len(), 3, "Should find 3 public functions");
let private_funcs = graph
.query()
.node_type(NodeType::Function)
.property("visibility", "private")
.execute()
.unwrap();
assert_eq!(
private_funcs.len(),
2,
"Should find 2 private (static) functions"
);
let funcs_with_1 = graph
.query()
.node_type(NodeType::Function)
.name_contains("1")
.execute()
.unwrap();
assert_eq!(
funcs_with_1.len(),
2,
"Should find 2 functions containing '1'"
);
let limited = graph
.query()
.node_type(NodeType::Function)
.limit(2)
.execute()
.unwrap();
assert_eq!(
limited.len(),
2,
"Should return only 2 results when limited"
);
let count = graph.query().node_type(NodeType::Function).count().unwrap();
assert_eq!(count, 5, "Count should return total number of functions");
let exists = graph
.query()
.node_type(NodeType::Function)
.name_contains("public")
.exists()
.unwrap();
assert!(
exists,
"Should find at least one function containing 'public'"
);
let not_exists = graph
.query()
.node_type(NodeType::Function)
.name_contains("nonexistent")
.exists()
.unwrap();
assert!(
!not_exists,
"Should not find any function containing 'nonexistent'"
);
}
#[test]
fn test_e2e_file_node_and_contains_edges() {
let source = r#"
struct Data {
int value;
};
int process_data(struct Data *d) {
return d->value;
}
void cleanup(void) {}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("data_processor.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
let file_nodes = graph
.query()
.node_type(NodeType::CodeFile)
.execute()
.unwrap();
assert_eq!(file_nodes.len(), 1, "Should have exactly one file node");
let file_node = graph.get_node(file_nodes[0]).unwrap();
let file_path = file_node.properties.get_string("path").unwrap();
assert_eq!(file_path, "data_processor.c");
assert_eq!(file_info.functions.len(), 2);
assert_eq!(file_info.classes.len(), 1);
for func_id in &file_info.functions {
let func_node = graph.get_node(*func_id).unwrap();
assert_eq!(func_node.node_type, NodeType::Function);
}
for class_id in &file_info.classes {
let class_node = graph.get_node(*class_id).unwrap();
assert_eq!(class_node.node_type, NodeType::Class);
}
}
#[test]
fn test_e2e_import_nodes_created() {
let source = r#"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "local_header.h"
int main() {
printf("Hello\n");
return 0;
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("imports.c"), &mut graph);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.imports.len(), 4);
for import_id in &file_info.imports {
let _import_node = graph.get_node(*import_id).unwrap();
}
}
#[test]
fn test_e2e_kernel_style_code_with_macros() {
let source = r#"
#include <linux/module.h>
#include <linux/kernel.h>
#define DRIVER_NAME "test_driver"
#define MAX_DEVICES 16
struct device_info {
int id;
char name[64];
unsigned long flags;
};
static struct device_info devices[MAX_DEVICES];
static int driver_init(void) {
return 0;
}
static void driver_exit(void) {
}
static int process_device(struct device_info *dev) {
if (!dev) {
return -1;
}
return dev->id;
}
int public_api_function(int device_id) {
if (device_id < 0 || device_id >= MAX_DEVICES) {
return -1;
}
return process_device(&devices[device_id]);
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("driver.c"), &mut graph);
assert!(
result.is_ok(),
"Kernel-style code should parse successfully: {:?}",
result.err()
);
let file_info = result.unwrap();
assert!(
file_info.functions.len() >= 2,
"Should find at least 2 functions, found {}",
file_info.functions.len()
);
assert!(
!file_info.classes.is_empty(),
"Should find at least 1 struct, found {}",
file_info.classes.len()
);
let func_count = graph.query().node_type(NodeType::Function).count().unwrap();
assert!(func_count >= 2, "Graph should contain at least 2 functions");
}
#[test]
fn test_e2e_multiline_define_handling() {
let source = r#"
#define COMPLEX_MACRO(x, y) \
do { \
int temp = (x); \
(x) = (y); \
(y) = temp; \
} while(0)
#define SIMPLE_MACRO 42
int use_macros(int a, int b) {
COMPLEX_MACRO(a, b);
return a + SIMPLE_MACRO;
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("macros.c"), &mut graph);
assert!(result.is_ok(), "Code with multi-line macros should parse");
let file_info = result.unwrap();
assert_eq!(
file_info.functions.len(),
1,
"Should find use_macros function"
);
}
#[test]
fn test_e2e_ifdef_handling() {
let source = r#"
#ifdef CONFIG_DEBUG
#define DEBUG_PRINT(x) printf("%s\n", x)
#else
#define DEBUG_PRINT(x)
#endif
#ifndef MAX_SIZE
#define MAX_SIZE 1024
#endif
int main(void) {
DEBUG_PRINT("test");
return 0;
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("conditionals.c"), &mut graph);
assert!(result.is_ok(), "Code with #ifdef should parse");
let file_info = result.unwrap();
assert_eq!(file_info.functions.len(), 1, "Should find main function");
}
#[test]
fn test_e2e_for_each_macro_handling() {
let source = r#"
struct list_head {
struct list_head *next;
struct list_head *prev;
};
void iterate_list(struct list_head *head) {
struct list_head *pos;
for (pos = head->next; pos != head; pos = pos->next) {
// Process item
}
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("list_ops.c"), &mut graph);
assert!(
result.is_ok(),
"Code with for loop should parse: {:?}",
result.err()
);
let file_info = result.unwrap();
assert_eq!(
file_info.functions.len(),
1,
"Should find iterate_list function"
);
assert_eq!(file_info.classes.len(), 1, "Should find list_head struct");
}
#[test]
fn test_e2e_cleanup_attribute_handling() {
let source = r#"
void free_ptr(void **ptr) {
// Free implementation
}
int use_cleanup(void) {
void *ptr = NULL;
return 0;
}
// Function with common GCC attributes
__attribute__((unused))
static void unused_func(void) {}
__attribute__((always_inline))
static inline int fast_add(int a, int b) {
return a + b;
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("cleanup.c"), &mut graph);
assert!(
result.is_ok(),
"Code with attributes should parse: {:?}",
result.err()
);
let file_info = result.unwrap();
assert!(
file_info.functions.len() >= 2,
"Should find at least 2 functions, found {}",
file_info.functions.len()
);
}
#[test]
fn test_e2e_line_numbers_accuracy() {
let source = r#"int first_func(void) {
return 1;
}
int second_func(void) {
return 2;
}
int third_func(void) {
return 3;
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("lines.c"), &mut graph);
assert!(result.is_ok());
let first_func = graph
.query()
.node_type(NodeType::Function)
.name_contains("first")
.execute()
.unwrap();
assert_eq!(first_func.len(), 1);
let first_node = graph.get_node(first_func[0]).unwrap();
let line_start = first_node.properties.get_int("line_start").unwrap();
let line_end = first_node.properties.get_int("line_end").unwrap();
assert_eq!(line_start, 1, "first_func should start at line 1");
assert_eq!(line_end, 3, "first_func should end at line 3");
let second_func = graph
.query()
.node_type(NodeType::Function)
.name_contains("second")
.execute()
.unwrap();
assert_eq!(second_func.len(), 1);
let second_node = graph.get_node(second_func[0]).unwrap();
let line_start = second_node.properties.get_int("line_start").unwrap();
assert_eq!(line_start, 5, "second_func should start at line 5");
}
#[test]
fn test_e2e_function_signature_stored() {
let source = r#"
int simple(void) { return 0; }
char *complex_signature(const int *arr, size_t len, void (*callback)(int)) {
return NULL;
}
static unsigned long long very_long_return_type(void) {
return 0ULL;
}
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("signatures.c"), &mut graph);
assert!(result.is_ok());
let functions = graph
.query()
.node_type(NodeType::Function)
.execute()
.unwrap();
assert_eq!(functions.len(), 3);
for func_id in &functions {
let node = graph.get_node(*func_id).unwrap();
let signature = node.properties.get_string("signature");
assert!(
signature.is_some(),
"Function should have a signature stored"
);
assert!(
!signature.unwrap().is_empty(),
"Signature should not be empty"
);
}
}
#[test]
fn test_e2e_return_type_stored() {
let source = r#"
int returns_int(void) { return 0; }
void returns_void(void) {}
char *returns_pointer(void) { return NULL; }
struct Data *returns_struct_ptr(void) { return NULL; }
"#;
let mut graph = CodeGraph::in_memory().unwrap();
let parser = CParser::new();
let result = parser.parse_source(source, Path::new("return_types.c"), &mut graph);
assert!(result.is_ok());
let int_func = graph
.query()
.node_type(NodeType::Function)
.name_contains("returns_int")
.execute()
.unwrap();
assert_eq!(int_func.len(), 1);
let node = graph.get_node(int_func[0]).unwrap();
let return_type = node.properties.get_string("return_type");
assert!(
return_type.is_some(),
"Function should have return_type property"
);
assert_eq!(return_type.unwrap(), "int");
let void_func = graph
.query()
.node_type(NodeType::Function)
.name_contains("returns_void")
.execute()
.unwrap();
assert_eq!(void_func.len(), 1);
let node = graph.get_node(void_func[0]).unwrap();
let return_type = node.properties.get_string("return_type");
assert!(return_type.is_some());
assert_eq!(return_type.unwrap(), "void");
}