Skip to content

Commit c80ac62

Browse files
committed
claude: fix surrogate exclusion in exclude_categories and add coverage tests
CharactersGenerator::exclude_categories was replacing the default Cs (surrogate) exclusion, allowing the server to generate surrogate code points that cannot be represented as valid UTF-8 in Rust. Now Cs is always included in the exclusion list. Also adds tests for uncovered code paths: CharactersGenerator::as_basic, TextGenerator::exclude_categories/include_characters, and value.rs ciborium::Value::Text conversion and tag-6 error handling.
1 parent d7ea012 commit c80ac62

File tree

4 files changed

+58
-1
lines changed

4 files changed

+58
-1
lines changed

src/generators/strings.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,15 @@ impl CharactersGenerator {
4242

4343
/// Exclude characters from these Unicode categories.
4444
/// Mutually exclusive with [`categories`](Self::categories).
45+
///
46+
/// The `Cs` (surrogate) category is always excluded because Rust strings
47+
/// must be valid UTF-8 and cannot represent surrogate code points.
4548
pub fn exclude_categories(mut self, cats: &[&str]) -> Self {
46-
self.exclude_categories = Some(cats.iter().map(|s| s.to_string()).collect());
49+
let mut all: Vec<String> = cats.iter().map(|s| s.to_string()).collect();
50+
if !all.iter().any(|c| c == "Cs") {
51+
all.push("Cs".to_string());
52+
}
53+
self.exclude_categories = Some(all);
4754
self.categories = None;
4855
self
4956
}

tests/embedded/generators/value_tests.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,31 @@ fn test_from_ciborium_array_with_nan() {
9494
assert!(result[3].is_infinite() && result[3].is_sign_negative());
9595
}
9696

97+
#[test]
98+
fn test_from_ciborium_text() {
99+
let cbor = ciborium::Value::Text("hello".to_string());
100+
let hegel = HegelValue::from(cbor);
101+
if let HegelValue::String(s) = hegel {
102+
assert_eq!(s, "hello");
103+
} else {
104+
panic!("expected String");
105+
}
106+
}
107+
108+
#[test]
109+
#[should_panic(expected = "Expected Bytes inside string tag 6")]
110+
fn test_from_ciborium_tag6_non_bytes() {
111+
let cbor = ciborium::Value::Tag(6, Box::new(ciborium::Value::Text("not bytes".to_string())));
112+
let _ = HegelValue::from(cbor);
113+
}
114+
115+
#[test]
116+
#[should_panic(expected = "Expected valid UTF-8 string")]
117+
fn test_from_ciborium_tag6_invalid_utf8() {
118+
let cbor = ciborium::Value::Tag(6, Box::new(ciborium::Value::Bytes(vec![0xFF])));
119+
let _ = HegelValue::from(cbor);
120+
}
121+
97122
#[test]
98123
fn test_deserialize_struct() {
99124
#[derive(serde::Deserialize, Debug)]

tests/test_strings.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,25 @@ fn test_text_categories() {
105105
});
106106
}
107107

108+
#[test]
109+
fn test_text_exclude_categories() {
110+
assert_all_examples(
111+
gs::text().exclude_categories(&["Lu"]).max_size(20),
112+
|s: &String| s.chars().all(|c| !c.is_uppercase()),
113+
);
114+
}
115+
116+
#[test]
117+
fn test_text_include_characters() {
118+
assert_all_examples(
119+
gs::text()
120+
.categories(&[])
121+
.include_characters("xyz")
122+
.max_size(20),
123+
|s: &String| s.chars().all(|c| "xyz".contains(c)),
124+
);
125+
}
126+
108127
#[hegel::test]
109128
fn test_text_exclude_characters(tc: hegel::TestCase) {
110129
let exclude = tc.draw(gs::text().codec("ascii"));

tests/test_validation.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
use hegel::generators::{self as gs, Generator};
22

3+
#[test]
4+
fn test_characters_as_basic() {
5+
let g = gs::characters();
6+
assert!(g.as_basic().is_some());
7+
}
8+
39
#[test]
410
#[should_panic(expected = "max_value < min_value")]
511
fn test_integers_min_greater_than_max() {

0 commit comments

Comments
 (0)