Skip to content

Commit e4ad480

Browse files
committed
test: comprehensive tree-sitter coverage + serial race fixes
- Added ~70 new tests across 9 tree-sitter modules: signatures.rs, mod.rs, truncation.rs, c.rs, cpp.rs, typescript.rs, java.rs, go.rs, rust.rs - Tests cover all signature types (function, struct, enum, trait, impl, module, const, type alias, macro), structure extraction, parse validation, truncation, and visibility - Fixed #[serial] race conditions in config.rs, cache.rs, markdown.rs, and lib.rs (7 tests using set_current_dir) - Unit tests: 234 → 309 (+75) - All 356 tests pass (309 unit + 47 integration) Also includes v0.8.2 version bump, SKILL.md updates, and CHANGELOG entry from earlier in this session.
1 parent 94dab93 commit e4ad480

19 files changed

Lines changed: 967088 additions & 16551 deletions

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## v0.8.2
6+
7+
- **Documentation**
8+
- Updated SKILL.md for v0.8.1+ with Security & Path Scoping section
9+
- Documented Tree-Sitter CLI flags (`--signatures`, `--structure`, `--visibility`, `--truncate`)
10+
- Added AST signatures and API surface review recipes
11+
12+
- **Test Coverage**
13+
- Extended unit test coverage across `config.rs`, `file_utils.rs`, `state.rs`, `markdown.rs`, and `lib.rs`
14+
- Added tests for file relevance categories, lock files, various source extensions, encoding handling, auto-diff workflows, and config hash consistency
15+
516
## v0.8.1
617

718
- **Bug Fixes** (identified by Gemini Deep Think v6 code review — 11 confirmed bugs, 0 false positives)

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "context-builder"
3-
version = "0.8.1"
3+
version = "0.8.2"
44
default-run = "context-builder"
55
edition = "2024"
66
authors = ["Igor Lins e Silva"]

SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name: context-builder
33
description: Generate LLM-optimized codebase context from any directory using context-builder CLI
44
homepage: https://github.com/igorls/context-builder
5-
version: 0.8.1
5+
version: 0.8.2
66
requires:
77
- cargo
88
- context-builder
@@ -22,7 +22,7 @@ cargo install context-builder
2222
cargo install context-builder --features tree-sitter-all
2323
```
2424

25-
Verify: `context-builder --version` (expected: `0.8.1`)
25+
Verify: `context-builder --version` (expected: `0.8.2`)
2626

2727
## Security & Path Scoping
2828

context-builder.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ auto_diff = true
1717
diff_only = false
1818

1919
# File extensions to include (no leading dot, e.g. "rs", "toml")
20-
filter = ["rs", "md", "toml"]
20+
filter = ["rs", "toml"]
2121

2222
# File / directory names to ignore (exact name matches)
2323
ignore = ["docs", "target", ".git", "node_modules"]

src/cache.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ impl CacheManager {
206206
#[cfg(test)]
207207
mod tests {
208208
use super::*;
209+
use serial_test::serial;
209210
use std::path::Path;
210211
use tempfile::tempdir;
211212

@@ -518,6 +519,7 @@ mod tests {
518519
}
519520

520521
#[test]
522+
#[serial]
521523
fn test_normalize_project_path_relative() {
522524
let temp_dir = tempdir().unwrap();
523525
let original_dir = std::env::current_dir().unwrap();

src/config.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,12 @@ pub fn load_config_from_path(project_root: &Path) -> Option<Config> {
132132
#[cfg(test)]
133133
mod tests {
134134
use super::*;
135+
use serial_test::serial;
135136
use std::fs;
136137
use tempfile::tempdir;
137138

138139
#[test]
140+
#[serial]
139141
fn load_config_nonexistent_file() {
140142
// Test loading config when file doesn't exist by temporarily changing directory
141143
let temp_dir = tempdir().unwrap();
@@ -270,6 +272,7 @@ invalid_toml [
270272
}
271273

272274
#[test]
275+
#[serial]
273276
fn load_config_invalid_toml_in_cwd() {
274277
let temp_dir = tempdir().unwrap();
275278
let original_dir = std::env::current_dir().unwrap();
@@ -291,6 +294,7 @@ invalid_toml [
291294
}
292295

293296
#[test]
297+
#[serial]
294298
fn load_config_valid_in_cwd() {
295299
let temp_dir = tempdir().unwrap();
296300
let original_dir = std::env::current_dir().unwrap();

src/lib.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,7 @@ line_numbers = false
967967
#[cfg(test)]
968968
mod tests {
969969
use super::*;
970+
use serial_test::serial;
970971
use std::io::Result;
971972
use tempfile::tempdir;
972973

@@ -2051,17 +2052,19 @@ mod tests {
20512052
}
20522053

20532054
#[test]
2055+
#[serial]
20542056
fn test_detect_major_file_types() {
20552057
let temp_dir = tempdir().unwrap();
20562058
let original_dir = std::env::current_dir().unwrap();
20572059

2058-
std::env::set_current_dir(&temp_dir).unwrap();
2059-
2060+
// Write files BEFORE changing cwd to avoid race conditions
20602061
fs::write(temp_dir.path().join("main.rs"), "fn main() {}").unwrap();
20612062
fs::write(temp_dir.path().join("lib.rs"), "pub fn lib() {}").unwrap();
20622063
fs::write(temp_dir.path().join("Cargo.toml"), "[package]").unwrap();
20632064
fs::write(temp_dir.path().join("README.md"), "# Readme").unwrap();
20642065

2066+
std::env::set_current_dir(&temp_dir).unwrap();
2067+
20652068
let result = detect_major_file_types();
20662069

20672070
std::env::set_current_dir(original_dir).unwrap();
@@ -2072,6 +2075,7 @@ mod tests {
20722075
}
20732076

20742077
#[test]
2078+
#[serial]
20752079
fn test_init_config_already_exists() {
20762080
let temp_dir = tempdir().unwrap();
20772081
let original_dir = std::env::current_dir().unwrap();
@@ -2097,6 +2101,7 @@ mod tests {
20972101
}
20982102

20992103
#[test]
2104+
#[serial]
21002105
fn test_init_config_creates_new_file() {
21012106
let temp_dir = tempdir().unwrap();
21022107
let original_dir = std::env::current_dir().unwrap();
@@ -2124,11 +2129,12 @@ mod tests {
21242129
}
21252130

21262131
#[test]
2132+
#[serial]
21272133
fn test_detect_major_file_types_empty_dir() {
21282134
let temp_dir = tempdir().unwrap();
21292135
let original_dir = std::env::current_dir().unwrap();
21302136

2131-
std::env::set_current_dir(&temp_dir).unwrap();
2137+
std::env::set_current_dir(temp_dir.path()).unwrap();
21322138

21332139
let result = detect_major_file_types();
21342140

src/markdown.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,7 @@ fn write_text_content(
720720
#[cfg(test)]
721721
mod tests {
722722
use super::*;
723+
use serial_test::serial;
723724
use std::fs;
724725
use tempfile::tempdir;
725726

@@ -1008,6 +1009,7 @@ mod tests {
10081009
}
10091010

10101011
#[test]
1012+
#[serial]
10111013
fn test_generate_markdown_with_current_directory() {
10121014
let dir = tempdir().unwrap();
10131015
let base_path = dir.path();

src/tree_sitter/languages/c.rs

Lines changed: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,11 +382,11 @@ mod tests {
382382
let source = r#"
383383
int main() {
384384
return 0;
385+
}
385386
386387
void hello(const char* name) {
387388
printf("Hello, %s\n", name);
388389
}
389-
}
390390
"#;
391391

392392
let signatures = CSupport.extract_signatures(source, Visibility::All);
@@ -396,7 +396,130 @@ void hello(const char* name) {
396396
.iter()
397397
.filter(|s| s.kind == SignatureKind::Function)
398398
.collect();
399-
assert!(!funcs.is_empty());
399+
assert!(funcs.len() >= 2);
400+
}
401+
402+
#[test]
403+
fn test_extract_struct_signature() {
404+
let source = r#"
405+
struct Point {
406+
int x;
407+
int y;
408+
};
409+
"#;
410+
411+
let signatures = CSupport.extract_signatures(source, Visibility::All);
412+
let structs: Vec<_> = signatures
413+
.iter()
414+
.filter(|s| s.kind == SignatureKind::Struct)
415+
.collect();
416+
assert!(!structs.is_empty());
417+
assert_eq!(structs[0].name, "Point");
418+
}
419+
420+
#[test]
421+
fn test_extract_enum_signature() {
422+
let source = r#"
423+
enum Color {
424+
RED,
425+
GREEN,
426+
BLUE
427+
};
428+
"#;
429+
430+
let signatures = CSupport.extract_signatures(source, Visibility::All);
431+
let enums: Vec<_> = signatures
432+
.iter()
433+
.filter(|s| s.kind == SignatureKind::Enum)
434+
.collect();
435+
assert!(!enums.is_empty());
436+
assert_eq!(enums[0].name, "Color");
437+
}
438+
439+
#[test]
440+
fn test_extract_header_prototype() {
441+
let source = r#"
442+
int add(int a, int b);
443+
void greet(const char* name);
444+
"#;
445+
446+
let signatures = CSupport.extract_signatures(source, Visibility::All);
447+
let funcs: Vec<_> = signatures
448+
.iter()
449+
.filter(|s| s.kind == SignatureKind::Function)
450+
.collect();
451+
assert!(funcs.len() >= 2);
452+
// Declarations should not end with semicolons
453+
for f in &funcs {
454+
assert!(!f.full_signature.ends_with(';'));
455+
}
456+
}
457+
458+
#[test]
459+
fn test_extract_typedef() {
460+
let source = r#"
461+
typedef unsigned int uint;
462+
typedef struct { int x; int y; } Point;
463+
"#;
464+
465+
let signatures = CSupport.extract_signatures(source, Visibility::All);
466+
let aliases: Vec<_> = signatures
467+
.iter()
468+
.filter(|s| s.kind == SignatureKind::TypeAlias)
469+
.collect();
470+
assert!(!aliases.is_empty());
471+
}
472+
473+
#[test]
474+
fn test_extract_macro_definition() {
475+
let source = r#"
476+
#define MAX(a, b) ((a) > (b) ? (a) : (b))
477+
"#;
478+
479+
let signatures = CSupport.extract_signatures(source, Visibility::All);
480+
let macros: Vec<_> = signatures
481+
.iter()
482+
.filter(|s| s.kind == SignatureKind::Macro)
483+
.collect();
484+
assert!(!macros.is_empty());
485+
assert_eq!(macros[0].name, "MAX");
486+
}
487+
488+
#[test]
489+
fn test_extract_structure() {
490+
let source = r#"
491+
#include <stdio.h>
492+
#include <stdlib.h>
493+
494+
struct Point { int x; int y; };
495+
enum Color { RED, GREEN };
496+
497+
int main() {
498+
return 0;
499+
}
500+
501+
void helper() {}
502+
"#;
503+
504+
let structure = CSupport.extract_structure(source);
505+
assert!(structure.functions >= 2);
506+
assert!(structure.structs >= 1);
507+
assert!(structure.enums >= 1);
508+
assert!(structure.imports.len() >= 2);
509+
}
510+
511+
#[test]
512+
fn test_parse_valid_c() {
513+
let source = "int main() { return 0; }";
514+
let tree = CSupport.parse(source);
515+
assert!(tree.is_some());
516+
}
517+
518+
#[test]
519+
fn test_find_truncation_point_within_limit() {
520+
let source = "int main() { return 0; }";
521+
let point = CSupport.find_truncation_point(source, 1000);
522+
assert_eq!(point, source.len());
400523
}
401524

402525
#[test]
@@ -406,3 +529,4 @@ void hello(const char* name) {
406529
assert!(!CSupport.supports_extension("cpp"));
407530
}
408531
}
532+

0 commit comments

Comments
 (0)