use icu_properties::PropertyNamesLong;
use icu_segmenter::*;
struct TestContentIterator<LineIterator>(LineIterator);
struct TestData {
original_line: String,
chars: Vec<char>,
break_result_utf8: Vec<usize>,
break_result_utf16: Vec<usize>,
break_result_latin1: Option<Vec<usize>>,
}
impl TestContentIterator<core::str::Split<'static, char>> {
pub fn new(file: &'static str) -> Self {
Self(file.split('\n'))
}
}
impl<LineIterator: Iterator> Iterator for TestContentIterator<LineIterator>
where
LineIterator::Item: Into<String>,
{
type Item = TestData;
fn next(&mut self) -> Option<Self::Item> {
loop {
let line: String = self.0.next()?.into();
if line.is_empty() {
return None;
}
if line.starts_with('#') {
continue;
}
let mut r = line.split('#');
let r = r.next();
let v = r.unwrap().split_ascii_whitespace();
let mut chars = Vec::new();
let mut break_result_utf8 = Vec::new();
let mut break_result_utf16 = Vec::new();
let mut break_result_latin1 = Vec::new();
let mut utf8_len = 0;
let mut u16_len = 0;
let mut ascii_only = true;
for (count, item) in v.enumerate() {
if count % 2 == 1 {
let ch = char::from_u32(u32::from_str_radix(item, 16).unwrap()).unwrap();
chars.push(ch);
utf8_len += ch.len_utf8();
u16_len += 1 + (ch as u32 > 0xFFFF) as usize;
if ch as u32 >= 0x100 {
ascii_only = false;
}
} else if item != "\u{00d7}" {
assert_eq!(item, "\u{00f7}");
break_result_utf8.push(utf8_len);
break_result_utf16.push(u16_len);
break_result_latin1.push(chars.len());
}
}
return Some(Self::Item {
original_line: line,
chars,
break_result_utf8,
break_result_utf16,
break_result_latin1: if ascii_only {
Some(break_result_latin1)
} else {
None
},
});
}
}
}
fn lb1_sa_replace(c: char) -> char {
use icu_properties::{
CodePointMapData,
props::{GeneralCategory, LineBreak},
};
match CodePointMapData::new().get(c) {
LineBreak::ComplexContext => match (CodePointMapData::new().get(c), c.len_utf8()) {
(GeneralCategory::NonspacingMark | GeneralCategory::SpacingMark, 2) => {
const _: () = assert!('\u{0300}'.len_utf8() == 2);
'\u{0300}'
}
(GeneralCategory::NonspacingMark | GeneralCategory::SpacingMark, 3) => {
const _: () = assert!('\u{081C}'.len_utf8() == 3);
'\u{081C}'
}
(GeneralCategory::NonspacingMark | GeneralCategory::SpacingMark, 4) => {
const _: () = assert!('\u{101FD}'.len_utf8() == 4);
'\u{101FD}'
}
(_, 2) => {
const _: () = assert!('À'.len_utf8() == 2);
'À'
}
(_, 3) => {
const _: () = assert!('ࠀ'.len_utf8() == 3);
'ࠀ'
}
(_, 4) => {
const _: () = assert!('𐀀'.len_utf8() == 4);
'𐀀'
}
_ => unreachable!(),
},
_ => c,
}
}
fn line_break_test(
file: &'static str,
segmenter: LineSegmenterBorrowed,
allow_lb1_violation: bool,
) {
let test_iter = TestContentIterator::new(file);
for (i, mut test) in test_iter.enumerate() {
let s: String = test
.chars
.into_iter()
.map(if allow_lb1_violation {
lb1_sa_replace
} else {
|ch| ch
})
.collect();
let iter = segmenter.segment_str(&s);
let result: Vec<usize> = iter.collect();
if test.break_result_utf8.first() != Some(&0) {
test.break_result_utf8.insert(0, 0);
}
if result != test.break_result_utf8 {
use icu_properties::{
CodePointMapData,
props::{EastAsianWidth, GeneralCategory, LineBreak},
};
let lb = CodePointMapData::<LineBreak>::new();
let lb_name = PropertyNamesLong::<LineBreak>::new();
let gc = CodePointMapData::<GeneralCategory>::new();
let gc_name = PropertyNamesLong::<GeneralCategory>::new();
let eaw = CodePointMapData::<EastAsianWidth>::new();
let eaw_name = PropertyNamesLong::<EastAsianWidth>::new();
let mut iter = segmenter.segment_str(&s);
println!(
" | A | E | Code pt. | Line_Break | General_Category | East_Asian_Width | Literal"
);
for (i, c) in s.char_indices() {
let expected_break = test.break_result_utf8.contains(&i);
let actual_break = result.contains(&i);
if actual_break {
iter.next();
}
println!(
"{}| {} | {} | {:>8} | {:>18} | {:>18} | {:>16} | {}",
if actual_break != expected_break {
"😭"
} else {
" "
},
if actual_break { "÷" } else { "×" },
if expected_break { "÷" } else { "×" },
format!("{:04X}", c as u32),
lb_name
.get(lb.get(c))
.unwrap_or(&format!("{:?}", lb.get(c))),
gc_name
.get(gc.get(c))
.unwrap_or(&format!("{:?}", gc.get(c))),
eaw_name
.get(eaw.get(c))
.unwrap_or(&format!("{:?}", eaw.get(c))),
c
)
}
println!("Test case #{i}");
panic!()
}
let result: Vec<usize> = segmenter
.segment_utf16(&s.encode_utf16().collect::<Vec<u16>>())
.collect();
if test.break_result_utf16.first() != Some(&0) {
test.break_result_utf16.insert(0, 0);
}
assert_eq!(
result, test.break_result_utf16,
"UTF16: {}",
test.original_line
);
if let Some(mut break_result_latin1) = test.break_result_latin1 {
let result: Vec<usize> = segmenter
.segment_latin1(s.chars().map(|c| c as u8).collect::<Vec<_>>().as_slice())
.collect();
if break_result_latin1.first() != Some(&0) {
break_result_latin1.insert(0, 0);
}
assert_eq!(
result, break_result_latin1,
"Latin1: {}",
test.original_line
);
}
}
}
#[test]
fn run_line_break_test() {
line_break_test(
include_str!("testdata/LineBreakTest_15.1.txt"),
LineSegmenter::new_for_non_complex_scripts(Default::default()),
true,
);
line_break_test(
include_str!("testdata/LineBreakTest.txt"),
LineSegmenter::new_17_for_non_complex_scripts(Default::default()),
true,
);
line_break_test(
include_str!("testdata/LineBreakTest.txt"),
LineSegmenter::new_neo_for_non_complex_scripts(Default::default()),
false,
);
}
#[test]
fn run_line_break_extra_test() {
line_break_test(
include_str!("testdata/LineBreakExtraTest_15.1.txt"),
LineSegmenter::new_for_non_complex_scripts(Default::default()),
false,
);
line_break_test(
include_str!("testdata/LineBreakExtraTest.txt"),
LineSegmenter::new_17_for_non_complex_scripts(Default::default()),
false,
);
line_break_test(
include_str!("testdata/LineBreakExtraTest.txt"),
LineSegmenter::new_neo_for_non_complex_scripts(Default::default()),
false,
);
}
#[test]
fn run_line_break_random_test() {
line_break_test(
include_str!("testdata/LineBreakRandomTest_15.1.txt"),
LineSegmenter::new_for_non_complex_scripts(Default::default()),
false,
);
line_break_test(
include_str!("testdata/LineBreakRandomTest.txt"),
LineSegmenter::new_17_for_non_complex_scripts(Default::default()),
false,
);
}
fn word_break_test(file: &'static str, segmenter: WordSegmenterBorrowed) {
let test_iter = TestContentIterator::new(file);
for (i, test) in test_iter.enumerate() {
let s: String = test.chars.into_iter().collect();
let iter = segmenter.segment_str(&s);
let result: Vec<usize> = iter.collect();
if result != test.break_result_utf8 {
use icu_properties::{CodePointMapData, props::WordBreak};
let wb = CodePointMapData::<WordBreak>::new();
let wb_name = PropertyNamesLong::<WordBreak>::new();
let mut iter = segmenter.segment_str(&s);
println!(" | A | E | Code pt. | Word_Break | State | Literal");
for (i, c) in s.char_indices() {
let expected_break = test.break_result_utf8.contains(&i);
let actual_break = result.contains(&i);
if actual_break {
iter.next();
}
println!(
"{}| {} | {} | {:>8} | {:>18} | {} | {}",
if actual_break != expected_break {
"😭"
} else {
" "
},
if actual_break { "÷" } else { "×" },
if expected_break { "÷" } else { "×" },
format!("{:04X}", c as u32),
wb_name
.get(wb.get(c))
.unwrap_or(&format!("{:?}", wb.get(c))),
"?".repeat(5),
c
)
}
println!("Test case #{i}");
panic!()
}
let result: Vec<usize> = segmenter
.segment_utf16(&s.encode_utf16().collect::<Vec<u16>>())
.collect();
assert_eq!(
result, test.break_result_utf16,
"UTF16: {}",
test.original_line
);
if let Some(break_result_latin1) = test.break_result_latin1 {
let result: Vec<usize> = segmenter
.segment_latin1(s.chars().map(|c| c as u8).collect::<Vec<_>>().as_slice())
.collect();
assert_eq!(
result, break_result_latin1,
"Latin1: {}",
test.original_line
);
}
}
}
#[test]
fn run_word_break_test() {
word_break_test(
include_str!("testdata/WordBreakTest.txt"),
WordSegmenter::new_for_non_complex_scripts(Default::default()),
);
word_break_test(
include_str!("testdata/WordBreakTest.txt"),
WordSegmenter::new_neo_for_non_complex_scripts(Default::default()),
);
}
#[test]
fn run_word_break_extra_test() {
word_break_test(
include_str!("testdata/WordBreakExtraTest.txt"),
WordSegmenter::new_for_non_complex_scripts(Default::default()),
);
word_break_test(
include_str!("testdata/WordBreakExtraTest.txt"),
WordSegmenter::new_neo_for_non_complex_scripts(Default::default()),
);
}
#[test]
fn run_word_break_random_test() {
word_break_test(
include_str!("testdata/WordBreakRandomTest.txt"),
WordSegmenter::new_for_non_complex_scripts(Default::default()),
);
}
fn grapheme_break_test(file: &'static str, segmenter: GraphemeClusterSegmenterBorrowed) {
let test_iter = TestContentIterator::new(file);
for (i, test) in test_iter.enumerate() {
let s: String = test.chars.into_iter().collect();
let iter = segmenter.segment_str(&s);
let result: Vec<usize> = iter.collect();
if result != test.break_result_utf8 {
use icu_properties::{CodePointMapData, props::GraphemeClusterBreak};
let gcb = CodePointMapData::<GraphemeClusterBreak>::new();
let gcb_name = PropertyNamesLong::<GraphemeClusterBreak>::new();
let mut iter = segmenter.segment_str(&s);
println!(" | A | E | Code pt. | GCB | State | Literal");
for (i, c) in s.char_indices() {
let expected_break = test.break_result_utf8.contains(&i);
let actual_break = result.contains(&i);
if actual_break {
iter.next();
}
println!(
"{}| {} | {} | {:>8} | {:>14} | {} | {}",
if actual_break != expected_break {
"😭"
} else {
" "
},
if actual_break { "÷" } else { "×" },
if expected_break { "÷" } else { "×" },
format!("{:04X}", c as u32),
gcb_name
.get(gcb.get(c))
.unwrap_or(&format!("{:?}", gcb.get(c))),
"?".repeat(5),
c
)
}
println!("Test case #{i}");
panic!()
}
let result: Vec<usize> = segmenter
.segment_utf16(&s.encode_utf16().collect::<Vec<u16>>())
.collect();
assert_eq!(
result, test.break_result_utf16,
"UTF16: {}",
test.original_line
);
if let Some(break_result_latin1) = test.break_result_latin1 {
let result: Vec<usize> = segmenter
.segment_latin1(s.chars().map(|c| c as u8).collect::<Vec<_>>().as_slice())
.collect();
assert_eq!(
result, break_result_latin1,
"Latin1: {}",
test.original_line
);
}
}
}
#[test]
fn run_grapheme_break_test() {
grapheme_break_test(
include_str!("testdata/GraphemeBreakTest.txt"),
GraphemeClusterSegmenter::new(),
);
grapheme_break_test(
include_str!("testdata/GraphemeBreakTest.txt"),
GraphemeClusterSegmenter::new_neo(),
);
}
#[test]
fn run_grapheme_break_extra_test() {
grapheme_break_test(
include_str!("testdata/GraphemeBreakExtraTest.txt"),
GraphemeClusterSegmenter::new(),
);
grapheme_break_test(
include_str!("testdata/GraphemeBreakExtraTest.txt"),
GraphemeClusterSegmenter::new_neo(),
);
}
#[test]
fn run_grapheme_break_random_test() {
grapheme_break_test(
include_str!("testdata/GraphemeBreakRandomTest.txt"),
GraphemeClusterSegmenter::new(),
);
}
fn sentence_break_test(file: &'static str, segmenter: SentenceSegmenterBorrowed) {
let test_iter = TestContentIterator::new(file);
for (i, test) in test_iter.enumerate() {
let s: String = test.chars.into_iter().collect();
let iter = segmenter.segment_str(&s);
let result: Vec<usize> = iter.collect();
if result != test.break_result_utf8 {
use icu_properties::{CodePointMapData, props::SentenceBreak};
let sb = CodePointMapData::<SentenceBreak>::new();
let sb_name = PropertyNamesLong::<SentenceBreak>::new();
let mut iter = segmenter.segment_str(&s);
println!(" | A | E | Code pt. | Sentence_Break | State | Literal");
for (i, c) in s.char_indices() {
let expected_break = test.break_result_utf8.contains(&i);
let actual_break = result.contains(&i);
if actual_break {
iter.next();
}
println!(
"{}| {} | {} | {:>8} | {:>14} | {} | {}",
if actual_break != expected_break {
"😭"
} else {
" "
},
if actual_break { "÷" } else { "×" },
if expected_break { "÷" } else { "×" },
format!("{:04X}", c as u32),
sb_name
.get(sb.get(c))
.unwrap_or(&format!("{:?}", sb.get(c))),
"?".repeat(5),
c
)
}
println!("Test case #{i}");
panic!()
}
let result: Vec<usize> = segmenter
.segment_utf16(&s.encode_utf16().collect::<Vec<u16>>())
.collect();
assert_eq!(
result, test.break_result_utf16,
"UTF16: {}",
test.original_line
);
if let Some(break_result_latin1) = test.break_result_latin1 {
let result: Vec<usize> = segmenter
.segment_latin1(s.chars().map(|c| c as u8).collect::<Vec<_>>().as_slice())
.collect();
assert_eq!(
result, break_result_latin1,
"Latin1: {}",
test.original_line
);
}
}
}
#[test]
fn run_sentence_break_test() {
sentence_break_test(
include_str!("testdata/SentenceBreakTest.txt"),
SentenceSegmenter::new(Default::default()),
);
sentence_break_test(
include_str!("testdata/SentenceBreakTest.txt"),
SentenceSegmenter::new_neo(Default::default()),
);
}
#[test]
fn run_sentence_break_extra_test() {
sentence_break_test(
include_str!("testdata/SentenceBreakExtraTest.txt"),
SentenceSegmenter::new(Default::default()),
);
sentence_break_test(
include_str!("testdata/SentenceBreakExtraTest.txt"),
SentenceSegmenter::new_neo(Default::default()),
);
}
#[test]
fn run_sentence_break_random_test() {
sentence_break_test(
include_str!("testdata/SentenceBreakRandomTest.txt"),
SentenceSegmenter::new(Default::default()),
);
}