Skip to content

Commit 678f122

Browse files
committed
add unit tests
1 parent 3cddbfc commit 678f122

File tree

5 files changed

+269
-36
lines changed

5 files changed

+269
-36
lines changed

Cargo.lock

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

clarity-serialization/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ thiserror = { workspace = true }
2020

2121
[dev-dependencies]
2222
mutants = "0.0.3"
23+
test-case = { version = "3.3.1", default-features = false }
2324

2425
[features]
2526
default = []

clarity-serialization/src/representations.rs

Lines changed: 200 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,10 @@ lazy_static! {
5050
"^[a-zA-Z]([a-zA-Z0-9]|[-_!?+<>=/*])*$|^[-+=/*]$|^[<>]=?$".into();
5151
pub static ref CLARITY_NAME_REGEX: Regex =
5252
{
53-
#[allow(clippy::unwrap_used)]
5453
Regex::new(CLARITY_NAME_REGEX_STRING.as_str()).unwrap()
5554
};
5655
pub static ref CONTRACT_NAME_REGEX: Regex =
5756
{
58-
#[allow(clippy::unwrap_used)]
5957
Regex::new(format!("^{}$|^__transient$", CONTRACT_NAME_REGEX_STRING.as_str()).as_str())
6058
.unwrap()
6159
};
@@ -162,3 +160,203 @@ impl StacksMessageCodec for ContractName {
162160
Ok(name)
163161
}
164162
}
163+
164+
#[cfg(test)]
165+
mod tests {
166+
use test_case::test_case;
167+
168+
use super::*;
169+
170+
#[test_case("hello"; "valid_name")]
171+
#[test_case("hello-dash"; "dash")]
172+
#[test_case("hello_underscore"; "underscore")]
173+
#[test_case("test123"; "numbers")]
174+
#[test_case("a"; "single_letter")]
175+
#[test_case("set-token-uri!"; "exclamation_mark")]
176+
#[test_case("is-owner?"; "question_mark")]
177+
#[test_case("math+"; "plus")]
178+
#[test_case("greater-than<"; "less_than")]
179+
#[test_case("less-than>"; "greater_than")]
180+
#[test_case("<="; "less_than_or_equal_to")]
181+
#[test_case(">="; "greater_than_or_equal_to")]
182+
#[test_case("*"; "asterisk")]
183+
#[test_case("/"; "slash")]
184+
#[test_case("-"; "dash-only")]
185+
#[test_case("="; "equals")]
186+
fn test_clarity_name_valid(name: &str) {
187+
let clarity_name = ClarityName::try_from(name.to_string())
188+
.unwrap_or_else(|_| panic!("Should parse valid clarity name: {name}"));
189+
assert_eq!(clarity_name.as_str(), name);
190+
}
191+
192+
#[test_case(""; "empty")]
193+
#[test_case("123abc"; "starts_with_number")]
194+
#[test_case("hello world"; "contains_space")]
195+
#[test_case("hello@world"; "contains_at")]
196+
#[test_case("hello#world"; "contains_hash")]
197+
#[test_case("hello$world"; "contains_dollar")]
198+
#[test_case("hello%world"; "contains_percent")]
199+
#[test_case("hello&world"; "contains_ampersand")]
200+
#[test_case("hello.world"; "contains_dot")]
201+
#[test_case("hello,world"; "contains_comma")]
202+
#[test_case("hello;world"; "contains_semicolon")]
203+
#[test_case("hello:world"; "contains_colon")]
204+
#[test_case("hello|world"; "contains_pipe")]
205+
#[test_case("hello\\world"; "contains_backslash")]
206+
#[test_case("hello\"world"; "contains_quote")]
207+
#[test_case("hello'world"; "contains_apostrophe")]
208+
#[test_case("hello[world"; "contains_bracket_open")]
209+
#[test_case("hello]world"; "contains_bracket_close")]
210+
#[test_case("hello{world"; "contains_curly_open")]
211+
#[test_case("hello}world"; "contains_curly_close")]
212+
#[test_case("hello(world"; "contains_parenthesis_open")]
213+
#[test_case("hello)world"; "contains_parenthesis_close")]
214+
#[test_case(&"a".repeat(MAX_STRING_LEN as usize + 1); "too_long")]
215+
fn test_clarity_name_invalid(name: &str) {
216+
let result = ClarityName::try_from(name.to_string());
217+
assert!(result.is_err());
218+
assert!(matches!(
219+
result.unwrap_err(),
220+
CodecError::InvalidClarityName(_, _)
221+
));
222+
}
223+
224+
#[test_case("test-name")]
225+
#[test_case(&"a".repeat(MAX_STRING_LEN as usize); "max-length")]
226+
fn test_clarity_name_serialization(name: &str) {
227+
let name = ClarityName::try_from(name.to_string()).unwrap();
228+
229+
let mut buffer = Vec::new();
230+
name.consensus_serialize(&mut buffer)
231+
.unwrap_or_else(|_| panic!("Serialization should succeed for name: {name}"));
232+
233+
// Should have length byte followed by the string bytes
234+
assert_eq!(buffer[0], name.len());
235+
assert_eq!(&buffer[1..], name.as_bytes());
236+
237+
// Test deserialization
238+
let deserialized = ClarityName::consensus_deserialize(&mut buffer.as_slice()).unwrap();
239+
assert_eq!(deserialized, name);
240+
}
241+
242+
#[test]
243+
fn test_clarity_name_serialization_too_long() {
244+
let name = ClarityName("a".repeat(MAX_STRING_LEN as usize + 1));
245+
let mut buffer = Vec::new();
246+
let result = name.consensus_serialize(&mut buffer);
247+
assert!(result.is_err());
248+
assert_eq!(
249+
result.unwrap_err().to_string(),
250+
"Failed to serialize clarity name: too long"
251+
);
252+
}
253+
254+
// the first byte is the length of the buffer.
255+
#[test_case(vec![4, 0xFF, 0xFE, 0xFD, 0xFC].as_slice(), "Failed to parse Clarity name: could not contruct from utf8"; "invalid_utf8")]
256+
#[test_case(vec![2, b'2', b'i'].as_slice(), "Failed to parse Clarity name: InvalidClarityName(\"ClarityName\", \"2i\")"; "invalid_name")] // starts with number
257+
#[test_case(vec![MAX_STRING_LEN + 1].as_slice(), "Failed to deserialize clarity name: too long"; "too_long")]
258+
#[test_case(vec![3, b'a'].as_slice(), "failed to fill whole buffer"; "wrong_length")]
259+
fn test_clarity_name_deserialization_errors<R: Read>(mut buffer: R, error_message: &str) {
260+
let result = ClarityName::consensus_deserialize(&mut buffer);
261+
assert!(result.is_err());
262+
assert_eq!(result.unwrap_err().to_string(), error_message);
263+
}
264+
265+
#[test_case("hello"; "valid_name")]
266+
#[test_case("contract-name"; "dash")]
267+
#[test_case("hello_world"; "underscore")]
268+
#[test_case("test123"; "numbers")]
269+
#[test_case("__transient"; "transient")]
270+
#[test_case("a"; "min_length")]
271+
#[test_case(&"a".repeat(CONTRACT_MAX_NAME_LENGTH); "max_length")]
272+
#[test_case(&"a".repeat(MAX_STRING_LEN as usize); "max_string_len")]
273+
fn test_contract_name_valid(name: &str) {
274+
let contract_name = ContractName::try_from(name.to_string())
275+
.unwrap_or_else(|_| panic!("Should parse valid contract name: {name}"));
276+
assert_eq!(contract_name.as_str(), name);
277+
}
278+
279+
#[test_case(""; "emtpy")]
280+
#[test_case("123contract"; "starts_with_number")]
281+
#[test_case("hello world"; "contains_space")]
282+
#[test_case("hello@world"; "contains_at")]
283+
#[test_case("hello.world"; "contains_dot")]
284+
#[test_case("hello!world"; "contains_exclamation")]
285+
#[test_case("hello?world"; "contains_question")]
286+
#[test_case("hello+world"; "contains_plus")]
287+
#[test_case("hello*world"; "contains_asterisk")]
288+
#[test_case("hello=world"; "contains_equals")]
289+
#[test_case("hello/world"; "contains_slash")]
290+
#[test_case("hello<world"; "contains_less_than")]
291+
#[test_case("hello>world"; "contains_greater_than")]
292+
#[test_case("hello,world"; "contains_comma")]
293+
#[test_case("hello;world"; "contains_semicolon")]
294+
#[test_case("hello:world"; "contains_colon")]
295+
#[test_case("hello|world"; "contains_pipe")]
296+
#[test_case("hello\\world"; "contains_backslash")]
297+
#[test_case("hello\"world"; "contains_quote")]
298+
#[test_case("hello'world"; "contains_apostrophe")]
299+
#[test_case("hello[world"; "contains_bracket_open")]
300+
#[test_case("hello]world"; "contains_bracket_close")]
301+
#[test_case("hello{world"; "contains_curly_open")]
302+
#[test_case("hello}world"; "contains_curly_close")]
303+
#[test_case("hello(world"; "contains_parenthesis_open")]
304+
#[test_case("hello)world"; "contains_parenthesis_close")]
305+
#[test_case(&"a".repeat(MAX_STRING_LEN as usize + 1); "too_long")]
306+
fn test_contract_name_invalid(name: &str) {
307+
let result = ContractName::try_from(name.to_string());
308+
assert!(result.is_err());
309+
assert!(matches!(
310+
result.unwrap_err(),
311+
CodecError::InvalidContractName(_, _)
312+
));
313+
}
314+
315+
#[test_case("test-contract"; "valid_name")]
316+
#[test_case("contract-name"; "dash")]
317+
#[test_case("hello_world"; "underscore")]
318+
#[test_case("test123"; "numbers")]
319+
#[test_case("__transient"; "transient")]
320+
#[test_case("a"; "min_length")]
321+
#[test_case(&"a".repeat(CONTRACT_MAX_NAME_LENGTH); "max_length")]
322+
fn test_contract_name_serialization(name: &str) {
323+
let name = ContractName::try_from(name.to_string()).unwrap();
324+
let mut buffer = Vec::with_capacity((name.len() + 1) as usize);
325+
name.consensus_serialize(&mut buffer)
326+
.unwrap_or_else(|_| panic!("Serialization should succeed for name: {name}"));
327+
assert_eq!(buffer[0], name.len());
328+
assert_eq!(&buffer[1..], name.as_bytes());
329+
330+
// Test deserialization
331+
let deserialized = ContractName::consensus_deserialize(&mut buffer.as_slice()).unwrap();
332+
assert_eq!(deserialized, name);
333+
}
334+
335+
#[test_case(&"a".repeat(CONTRACT_MIN_NAME_LENGTH - 1); "too_short")]
336+
#[test_case(&"a".repeat(CONTRACT_MAX_NAME_LENGTH + 1); "too_long")]
337+
#[test_case(&"a".repeat(MAX_STRING_LEN as usize); "max_string_len")]
338+
fn test_contract_name_serialization_too_long_or_short(name: &str) {
339+
let name = ContractName(name.to_string());
340+
let mut buffer = Vec::with_capacity((name.len() + 1) as usize);
341+
let result = name.consensus_serialize(&mut buffer);
342+
assert!(result.is_err());
343+
assert_eq!(
344+
result.unwrap_err().to_string(),
345+
format!(
346+
"Failed to serialize contract name: too short or too long: {}",
347+
name.len()
348+
)
349+
);
350+
}
351+
352+
// the first byte is the length of the buffer.
353+
#[test_case(vec![4, 0xFF, 0xFE, 0xFD, 0xFC].as_slice(), "Failed to parse Contract name: could not construct from utf8"; "invalid_utf8")]
354+
#[test_case(vec![2, b'2', b'i'].as_slice(), "Failed to parse Contract name: InvalidContractName(\"ContractName\", \"2i\")"; "invalid_name")] // starts with number
355+
#[test_case(vec![MAX_STRING_LEN + 1].as_slice(), &format!("Failed to deserialize contract name: too short or too long: {}", MAX_STRING_LEN + 1); "too_long")]
356+
#[test_case(vec![3, b'a'].as_slice(), "failed to fill whole buffer"; "wrong_length")]
357+
fn test_contract_name_deserialization_errors<R: Read>(mut buffer: R, error_message: &str) {
358+
let result = ContractName::consensus_deserialize(&mut buffer);
359+
assert!(result.is_err());
360+
assert_eq!(result.unwrap_err().to_string(), error_message);
361+
}
362+
}

clarity-serialization/src/types/mod.rs

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1529,42 +1529,42 @@ mod test {
15291529
use super::*;
15301530
#[test]
15311531
fn test_constructors() {
1532-
matches!(
1532+
assert!(matches!(
15331533
Value::list_with_type(
15341534
&StacksEpochId::latest(),
15351535
vec![Value::Int(5), Value::Int(2)],
15361536
ListTypeData::new_list(TypeSignature::BoolType, 3).unwrap()
15371537
),
15381538
Err(CodecError::FailureConstructingListWithType)
1539-
);
1540-
matches!(
1539+
));
1540+
assert!(matches!(
15411541
ListTypeData::new_list(TypeSignature::IntType, MAX_VALUE_SIZE),
15421542
Err(CodecError::ValueTooLarge)
1543-
);
1543+
));
15441544

1545-
matches!(
1545+
assert!(matches!(
15461546
Value::buff_from(vec![0; (MAX_VALUE_SIZE + 1) as usize]),
15471547
Err(CodecError::ValueTooLarge)
1548-
);
1548+
));
15491549

15501550
// Test that wrappers (okay, error, some)
15511551
// correctly error when _they_ cause the value size
15521552
// to exceed the max value size (note, the buffer constructor
15531553
// isn't causing the error).
1554-
matches!(
1554+
assert!(matches!(
15551555
Value::okay(Value::buff_from(vec![0; (MAX_VALUE_SIZE) as usize]).unwrap()),
15561556
Err(CodecError::ValueTooLarge)
1557-
);
1557+
));
15581558

1559-
matches!(
1559+
assert!(matches!(
15601560
Value::error(Value::buff_from(vec![0; (MAX_VALUE_SIZE) as usize]).unwrap()),
15611561
Err(CodecError::ValueTooLarge)
1562-
);
1562+
));
15631563

1564-
matches!(
1564+
assert!(matches!(
15651565
Value::some(Value::buff_from(vec![0; (MAX_VALUE_SIZE) as usize]).unwrap()),
15661566
Err(CodecError::ValueTooLarge)
1567-
);
1567+
));
15681568

15691569
// Test that the depth limit is correctly enforced:
15701570
// for tuples, lists, somes, okays, errors.
@@ -1585,27 +1585,27 @@ mod test {
15851585
)?)?)?)?)
15861586
};
15871587
let inner_value = cons().unwrap();
1588-
matches!(
1588+
assert!(matches!(
15891589
TupleData::from_data(vec![("a".into(), inner_value.clone())]),
15901590
Err(CodecError::TypeSignatureTooDeep)
1591-
);
1591+
));
15921592

1593-
matches!(
1593+
assert!(matches!(
15941594
Value::list_from(vec![inner_value.clone()]),
15951595
Err(CodecError::TypeSignatureTooDeep)
1596-
);
1597-
matches!(
1596+
));
1597+
assert!(matches!(
15981598
Value::okay(inner_value.clone()),
15991599
Err(CodecError::TypeSignatureTooDeep)
1600-
);
1601-
matches!(
1600+
));
1601+
assert!(matches!(
16021602
Value::error(inner_value.clone()),
16031603
Err(CodecError::TypeSignatureTooDeep)
1604-
);
1605-
matches!(
1604+
));
1605+
assert!(matches!(
16061606
Value::some(inner_value),
16071607
Err(CodecError::TypeSignatureTooDeep)
1608-
);
1608+
));
16091609

16101610
if std::env::var("CIRCLE_TESTING") == Ok("1".to_string()) {
16111611
println!("Skipping allocation test on Circle");
@@ -1614,10 +1614,10 @@ mod test {
16141614

16151615
// on 32-bit archs, this error cannot even happen, so don't test (and cause an overflow panic)
16161616
if (u32::MAX as usize) < usize::MAX {
1617-
matches!(
1617+
assert!(matches!(
16181618
Value::buff_from(vec![0; (u32::MAX as usize) + 10]),
16191619
Err(CodecError::ValueTooLarge)
1620-
);
1620+
));
16211621
}
16221622
}
16231623

@@ -1629,9 +1629,9 @@ mod test {
16291629
#[test]
16301630
fn simple_tuple_get_test() {
16311631
let t = TupleData::from_data(vec![("abc".into(), Value::Int(0))]).unwrap();
1632-
matches!(t.get("abc"), Ok(&Value::Int(0)));
1632+
assert!(matches!(t.get("abc"), Ok(&Value::Int(0))));
16331633
// should error!
1634-
t.get("abcd").unwrap_err();
1634+
t.get("abcd").expect_err("should error");
16351635
}
16361636

16371637
#[test]

0 commit comments

Comments
 (0)