static void test_Highlight_Excerpt(TestBatchRunner *runner, Searcher *searcher, Obj *query) { String *content = (String*)SSTR_WRAP_UTF8("content", 7); Highlighter *highlighter = Highlighter_new(searcher, query, content, 3); String *highlighted; Vector *spans = Vec_new(1); Vec_Push(spans, (Obj*)Span_new(2, 1, 0.0f)); String *raw_excerpt = (String *)SSTR_WRAP_UTF8("a b c", 5); highlighted = Highlighter_Highlight_Excerpt(highlighter, spans, raw_excerpt, 0); TEST_TRUE(runner, Str_Equals_Utf8(highlighted, "a <strong>b</strong> c", 22), "basic Highlight_Excerpt"); DECREF(highlighted); DECREF(spans); spans = Vec_new(2); Vec_Push(spans, (Obj*)Span_new(0, 1, 1.0f)); Vec_Push(spans, (Obj*)Span_new(10, 10, 1.0f)); raw_excerpt = (String *)SSTR_WRAP_UTF8(PHI, 2); highlighted = Highlighter_Highlight_Excerpt(highlighter, spans, raw_excerpt, 0); TEST_TRUE(runner, Str_Equals_Utf8(highlighted, "<strong>Φ</strong>", 23), "don't surround spans off end of raw excerpt."); DECREF(highlighted); DECREF(spans); spans = Vec_new(1); Vec_Push(spans, (Obj*)Span_new(3, 1, 1.0f)); raw_excerpt = (String *)SSTR_WRAP_UTF8(PHI " " PHI " " PHI, 8); highlighted = Highlighter_Highlight_Excerpt(highlighter, spans, raw_excerpt, 1); TEST_TRUE(runner, Str_Equals_Utf8(highlighted, "Φ <strong>Φ</strong> Φ", 37), "Highlight_Excerpt pays attention to offset"); DECREF(highlighted); DECREF(spans); spans = Vec_new(4); Vec_Push(spans, (Obj*)Span_new(2, 10, 1.0f)); Vec_Push(spans, (Obj*)Span_new(2, 4, 1.0f)); Vec_Push(spans, (Obj*)Span_new(8, 9, 1.0f)); Vec_Push(spans, (Obj*)Span_new(8, 4, 1.0f)); raw_excerpt = (String *)SSTR_WRAP_UTF8(PHI " Oook. Urk. Ick. " PHI, 21); highlighted = Highlighter_Highlight_Excerpt(highlighter, spans, raw_excerpt, 0); TEST_TRUE(runner, Str_Equals_Utf8(highlighted, "Φ <strong>Oook. Urk. Ick.</strong> Φ", 46), "Highlight_Excerpt works with overlapping spans"); DECREF(highlighted); DECREF(spans); DECREF(highlighter); }
static void test_seg_name_and_num(TestBatchRunner *runner) { Segment *segment_z = Seg_new(35); String *seg_z_name = Seg_num_to_name(35); TEST_TRUE(runner, Seg_Get_Number(segment_z) == INT64_C(35), "Get_Number"); TEST_TRUE(runner, Str_Equals_Utf8(Seg_Get_Name(segment_z), "seg_z", 5), "Get_Name"); TEST_TRUE(runner, Str_Equals_Utf8(seg_z_name, "seg_z", 5), "num_to_name"); DECREF(seg_z_name); DECREF(segment_z); }
QueryParser* QParser_init(QueryParser *self, Schema *schema, Analyzer *analyzer, String *default_boolop, Vector *fields) { QueryParserIVARS *const ivars = QParser_IVARS(self); // Init. ivars->heed_colons = false; ivars->lexer = QueryLexer_new(); // Assign. ivars->schema = (Schema*)INCREF(schema); ivars->analyzer = (Analyzer*)INCREF(analyzer); ivars->default_boolop = default_boolop ? Str_Clone(default_boolop) : Str_new_from_trusted_utf8("OR", 2); if (fields) { ivars->fields = Vec_Clone(fields); for (uint32_t i = 0, max = Vec_Get_Size(fields); i < max; i++) { CERTIFY(Vec_Fetch(fields, i), STRING); } Vec_Sort(ivars->fields); } else { Vector *all_fields = Schema_All_Fields(schema); uint32_t num_fields = Vec_Get_Size(all_fields); ivars->fields = Vec_new(num_fields); for (uint32_t i = 0; i < num_fields; i++) { String *field = (String*)Vec_Fetch(all_fields, i); FieldType *type = Schema_Fetch_Type(schema, field); if (type && FType_Indexed(type)) { Vec_Push(ivars->fields, INCREF(field)); } } DECREF(all_fields); } Vec_Sort(ivars->fields); // Derive default "occur" from default boolean operator. if (Str_Equals_Utf8(ivars->default_boolop, "OR", 2)) { ivars->default_occur = SHOULD; } else if (Str_Equals_Utf8(ivars->default_boolop, "AND", 3)) { ivars->default_occur = MUST; } else { THROW(ERR, "Invalid value for default_boolop: %o", ivars->default_boolop); } return self; }
static void test_Write_File_and_Read_File(TestBatchRunner *runner) { RAMFolder *folder = RAMFolder_new(NULL); Segment *segment = Seg_new(100); Segment *got = Seg_new(100); String *meta; String *flotsam = (String*)SSTR_WRAP_UTF8("flotsam", 7); String *jetsam = (String*)SSTR_WRAP_UTF8("jetsam", 6); Seg_Set_Count(segment, 111); Seg_Store_Metadata_Utf8(segment, "foo", 3, (Obj*)Str_newf("bar")); Seg_Add_Field(segment, flotsam); Seg_Add_Field(segment, jetsam); RAMFolder_MkDir(folder, Seg_Get_Name(segment)); Seg_Write_File(segment, (Folder*)folder); Seg_Read_File(got, (Folder*)folder); TEST_TRUE(runner, Seg_Get_Count(got) == Seg_Get_Count(segment), "Round-trip count through file"); TEST_TRUE(runner, Seg_Field_Num(got, jetsam) == Seg_Field_Num(segment, jetsam), "Round trip field names through file"); meta = (String*)Seg_Fetch_Metadata_Utf8(got, "foo", 3); TEST_TRUE(runner, meta && Str_Is_A(meta, STRING) && Str_Equals_Utf8(meta, "bar", 3), "Round trip metadata through file"); DECREF(got); DECREF(segment); DECREF(folder); }
static void test_numbers(TestBatchRunner *runner) { Integer *i64 = Int_new(33); String *json = Json_to_json((Obj*)i64); String *trimmed = Str_Trim(json); TEST_TRUE(runner, Str_Equals_Utf8(trimmed, "33", 2), "Integer"); DECREF(json); DECREF(trimmed); Float *f64 = Float_new(33.33); json = Json_to_json((Obj*)f64); if (json) { double value = Str_To_F64(json); double diff = 33.33 - value; if (diff < 0.0) { diff = 0.0 - diff; } TEST_TRUE(runner, diff < 0.0001, "Float"); DECREF(json); } else { FAIL(runner, "Float conversion to json failed."); } DECREF(i64); DECREF(f64); }
NumericType* NumType_Load_IMP(NumericType *self, Obj *dump) { UNUSED_VAR(self); Hash *source = (Hash*)CERTIFY(dump, HASH); // Get a Class String *class_name = (String*)Hash_Fetch_Utf8(source, "_class", 6); String *type_spec = (String*)Hash_Fetch_Utf8(source, "type", 4); Class *klass = NULL; if (class_name != NULL && Obj_is_a((Obj*)class_name, STRING)) { klass = Class_singleton(class_name, NULL); } else if (type_spec != NULL && Obj_is_a((Obj*)type_spec, STRING)) { if (Str_Equals_Utf8(type_spec, "i32_t", 5)) { klass = INT32TYPE; } else if (Str_Equals_Utf8(type_spec, "i64_t", 5)) { klass = INT64TYPE; } else if (Str_Equals_Utf8(type_spec, "f32_t", 5)) { klass = FLOAT32TYPE; } else if (Str_Equals_Utf8(type_spec, "f64_t", 5)) { klass = FLOAT64TYPE; } else { THROW(ERR, "Unrecognized type string: '%o'", type_spec); } } CERTIFY(klass, CLASS); NumericType *loaded = (NumericType*)Class_Make_Obj(klass); // Extract boost. Obj *boost_dump = Hash_Fetch_Utf8(source, "boost", 5); float boost = boost_dump ? (float)Json_obj_to_f64(boost_dump) : 1.0f; // Find boolean properties. Obj *indexed_dump = Hash_Fetch_Utf8(source, "indexed", 7); Obj *stored_dump = Hash_Fetch_Utf8(source, "stored", 6); Obj *sort_dump = Hash_Fetch_Utf8(source, "sortable", 8); bool indexed = indexed_dump ? Json_obj_to_bool(indexed_dump) : true; bool stored = stored_dump ? Json_obj_to_bool(stored_dump) : true; bool sortable = sort_dump ? Json_obj_to_bool(sort_dump) : false; return NumType_init2(loaded, boost, indexed, stored, sortable); }
static void S_test_local_part(TestBatchRunner *runner, const char *source, const char *wanted, const char *test_name) { StackString *source_str = SSTR_WRAP_UTF8(source, strlen(source)); String *got = IxFileNames_local_part((String*)source_str); TEST_TRUE(runner, Str_Equals_Utf8(got, wanted, strlen(wanted)), test_name); DECREF(got); }
static void test_new(TestBatchRunner *runner) { static char chars[] = "A string " SMILEY " with a smile."; { char *buffer = (char*)MALLOCATE(sizeof(chars)); strcpy(buffer, chars); String *thief = Str_new_steal_utf8(buffer, sizeof(chars) - 1); TEST_TRUE(runner, Str_Equals_Utf8(thief, chars, sizeof(chars) - 1), "Str_new_steal_utf8"); DECREF(thief); } { char *buffer = (char*)MALLOCATE(sizeof(chars)); strcpy(buffer, chars); String *thief = Str_new_steal_trusted_utf8(buffer, sizeof(chars) - 1); TEST_TRUE(runner, Str_Equals_Utf8(thief, chars, sizeof(chars) - 1), "Str_new_steal_trusted_utf8"); DECREF(thief); } { String *wrapper = Str_new_wrap_utf8(chars, sizeof(chars) - 1); TEST_TRUE(runner, Str_Equals_Utf8(wrapper, chars, sizeof(chars) - 1), "Str_new_wrap_utf8"); DECREF(wrapper); } { String *wrapper = Str_new_wrap_trusted_utf8(chars, sizeof(chars) - 1); TEST_TRUE(runner, Str_Equals_Utf8(wrapper, chars, sizeof(chars) - 1), "Str_new_wrap_trusted_utf8"); DECREF(wrapper); } { String *smiley_str = Str_new_from_char(smiley_cp); TEST_TRUE(runner, Str_Equals_Utf8(smiley_str, smiley, smiley_len), "Str_new_from_char"); DECREF(smiley_str); } }
static void test_Utf8_To_String(TestBatchRunner *runner) { ByteBuf *bb = BB_new_bytes("foo", 3); { String *string = BB_Utf8_To_String(bb); TEST_TRUE(runner, Str_Equals_Utf8(string, "foo", 3), "Utf8_To_String"); DECREF(string); } { String *string = BB_Trusted_Utf8_To_String(bb); TEST_TRUE(runner, Str_Equals_Utf8(string, "foo", 3), "Trusted_Utf8_To_String"); DECREF(string); } DECREF(bb); }
void TestFH_Run_IMP(TestFileHandle *self, TestBatchRunner *runner) { TestBatchRunner_Plan(runner, (TestBatch*)self, 2); FileHandle *fh = S_new_filehandle(); String *foo = SSTR_WRAP_UTF8("foo", 3); TEST_TRUE(runner, Str_Equals_Utf8(FH_Get_Path(fh), "", 0), "Get_Path"); FH_Set_Path(fh, foo); TEST_TRUE(runner, Str_Equals(FH_Get_Path(fh), (Obj*)foo), "Set_Path"); DECREF(fh); }
static Method* S_find_method(Class *self, const char *name) { size_t name_len = strlen(name); for (size_t i = 0; self->methods[i]; i++) { Method *method = self->methods[i]; if (Str_Equals_Utf8(method->name, name, name_len)) { return method; } } return NULL; }
static void test_threads(TestBatchRunner *runner) { Err_set_error(Err_new(Str_newf("main"))); HANDLE thread = CreateThread(NULL, 0, S_err_thread, runner, 0, NULL); TEST_TRUE(runner, thread != NULL, "CreateThread succeeds"); DWORD event = WaitForSingleObject(thread, INFINITE); TEST_INT_EQ(runner, event, WAIT_OBJECT_0, "WaitForSingleObject succeeds"); CloseHandle(thread); String *mess = Err_Get_Mess(Err_get_error()); TEST_TRUE(runner, Str_Equals_Utf8(mess, "main", 4), "thread doesn't clobber global error"); }
static void test_threads(TestBatchRunner *runner) { Err_set_error(Err_new(Str_newf("main"))); int err; pthread_t thread; err = pthread_create(&thread, NULL, S_err_thread, runner); TEST_INT_EQ(runner, err, 0, "pthread_create succeeds"); err = pthread_join(thread, NULL); TEST_INT_EQ(runner, err, 0, "pthread_join succeeds"); String *mess = Err_Get_Mess(Err_get_error()); TEST_TRUE(runner, Str_Equals_Utf8(mess, "main", 4), "thread doesn't clobber global error"); }
static void test_metadata_storage(TestBatchRunner *runner) { Segment *segment = Seg_new(1); String *got; Seg_Store_Metadata_Utf8(segment, "foo", 3, (Obj*)Str_newf("bar")); got = (String*)Seg_Fetch_Metadata_Utf8(segment, "foo", 3); TEST_TRUE(runner, got && Str_Is_A(got, STRING) && Str_Equals_Utf8(got, "bar", 3), "metadata round trip" ); DECREF(segment); }
static void test_Trim(TestBatchRunner *runner) { String *ws_smiley = S_smiley_with_whitespace(NULL); String *ws_foo = S_get_str(" foo "); String *ws_only = S_get_str(" \t \r\n"); String *trimmed = S_get_str("a b"); String *got; got = Str_Trim(ws_smiley); TEST_TRUE(runner, Str_Equals_Utf8(got, smiley, smiley_len), "Trim"); DECREF(got); got = Str_Trim_Top(ws_foo); TEST_TRUE(runner, Str_Equals_Utf8(got, "foo ", 5), "Trim_Top"); DECREF(got); got = Str_Trim_Tail(ws_foo); TEST_TRUE(runner, Str_Equals_Utf8(got, " foo", 5), "Trim_Tail"); DECREF(got); got = Str_Trim(ws_only); TEST_TRUE(runner, Str_Equals_Utf8(got, "", 0), "Trim with only whitespace"); DECREF(got); got = Str_Trim_Top(ws_only); TEST_TRUE(runner, Str_Equals_Utf8(got, "", 0), "Trim_Top with only whitespace"); DECREF(got); got = Str_Trim_Tail(ws_only); TEST_TRUE(runner, Str_Equals_Utf8(got, "", 0), "Trim_Tail with only whitespace"); DECREF(got); got = Str_Trim(trimmed); TEST_TRUE(runner, Str_Equals(got, (Obj*)trimmed), "Trim doesn't change trimmed string"); DECREF(got); got = Str_Trim_Top(trimmed); TEST_TRUE(runner, Str_Equals(got, (Obj*)trimmed), "Trim_Top doesn't change trimmed string"); DECREF(got); got = Str_Trim_Tail(trimmed); TEST_TRUE(runner, Str_Equals(got, (Obj*)trimmed), "Trim_Tail doesn't change trimmed string"); DECREF(got); DECREF(trimmed); DECREF(ws_only); DECREF(ws_foo); DECREF(ws_smiley); }
Schema* Schema_Load_IMP(Schema *self, Obj *dump) { Hash *source = (Hash*)CERTIFY(dump, HASH); String *class_name = (String*)CERTIFY(Hash_Fetch_Utf8(source, "_class", 6), STRING); Class *klass = Class_singleton(class_name, NULL); Schema *loaded = (Schema*)Class_Make_Obj(klass); Hash *type_dumps = (Hash*)CERTIFY(Hash_Fetch_Utf8(source, "fields", 6), HASH); Vector *analyzer_dumps = (Vector*)CERTIFY(Hash_Fetch_Utf8(source, "analyzers", 9), VECTOR); Vector *analyzers = (Vector*)Freezer_load((Obj*)analyzer_dumps); UNUSED_VAR(self); // Start with a blank Schema. Schema_init(loaded); SchemaIVARS *const loaded_ivars = Schema_IVARS(loaded); Vec_Grow(loaded_ivars->uniq_analyzers, Vec_Get_Size(analyzers)); HashIterator *iter = HashIter_new(type_dumps); while (HashIter_Next(iter)) { String *field = HashIter_Get_Key(iter); Hash *type_dump = (Hash*)CERTIFY(HashIter_Get_Value(iter), HASH); String *type_str = (String*)Hash_Fetch_Utf8(type_dump, "type", 4); if (type_str) { if (Str_Equals_Utf8(type_str, "fulltext", 8)) { // Replace the "analyzer" tick with the real thing. Obj *tick = CERTIFY(Hash_Fetch_Utf8(type_dump, "analyzer", 8), OBJ); Analyzer *analyzer = (Analyzer*)Vec_Fetch(analyzers, (uint32_t)Json_obj_to_i64(tick)); if (!analyzer) { THROW(ERR, "Can't find analyzer for '%o'", field); } Hash_Store_Utf8(type_dump, "analyzer", 8, INCREF(analyzer)); FullTextType *type = (FullTextType*)S_load_type(FULLTEXTTYPE, (Obj*)type_dump); Schema_Spec_Field(loaded, field, (FieldType*)type); DECREF(type); } else if (Str_Equals_Utf8(type_str, "string", 6)) { StringType *type = (StringType*)S_load_type(STRINGTYPE, (Obj*)type_dump); Schema_Spec_Field(loaded, field, (FieldType*)type); DECREF(type); } else if (Str_Equals_Utf8(type_str, "blob", 4)) { BlobType *type = (BlobType*)S_load_type(BLOBTYPE, (Obj*)type_dump); Schema_Spec_Field(loaded, field, (FieldType*)type); DECREF(type); } else if (Str_Equals_Utf8(type_str, "i32_t", 5)) { Int32Type *type = (Int32Type*)S_load_type(INT32TYPE, (Obj*)type_dump); Schema_Spec_Field(loaded, field, (FieldType*)type); DECREF(type); } else if (Str_Equals_Utf8(type_str, "i64_t", 5)) { Int64Type *type = (Int64Type*)S_load_type(INT64TYPE, (Obj*)type_dump); Schema_Spec_Field(loaded, field, (FieldType*)type); DECREF(type); } else if (Str_Equals_Utf8(type_str, "f32_t", 5)) { Float32Type *type = (Float32Type*)S_load_type(FLOAT32TYPE, (Obj*)type_dump); Schema_Spec_Field(loaded, field, (FieldType*)type); DECREF(type); } else if (Str_Equals_Utf8(type_str, "f64_t", 5)) { Float64Type *type = (Float64Type*)S_load_type(FLOAT64TYPE, (Obj*)type_dump); Schema_Spec_Field(loaded, field, (FieldType*)type); DECREF(type); } else { THROW(ERR, "Unknown type '%o' for field '%o'", type_str, field); } } else { FieldType *type = (FieldType*)CERTIFY(Freezer_load((Obj*)type_dump), FIELDTYPE); Schema_Spec_Field(loaded, field, type); DECREF(type); } } DECREF(iter); DECREF(analyzers); return loaded; }
static void test_tokenizer(TestBatchRunner *runner) { StandardTokenizer *tokenizer = StandardTokenizer_new(); String *word = SSTR_WRAP_C( " ." "tha\xCC\x82t's" ":" "1,02\xC2\xADZ4.38" "\xE0\xB8\x81\xC2\xAD\xC2\xAD" "\xF0\xA0\x80\x80" "a" "/"); Vector *got = StandardTokenizer_Split(tokenizer, word); String *token = (String*)Vec_Fetch(got, 0); TEST_TRUE(runner, token && Str_is_a(token, STRING) && Str_Equals_Utf8(token, "tha\xcc\x82t's", 8), "Token: %s", Str_Get_Ptr8(token)); token = (String*)Vec_Fetch(got, 1); TEST_TRUE(runner, token && Str_is_a(token, STRING) && Str_Equals_Utf8(token, "1,02\xC2\xADZ4.38", 11), "Token: %s", Str_Get_Ptr8(token)); token = (String*)Vec_Fetch(got, 2); TEST_TRUE(runner, token && Str_is_a(token, STRING) && Str_Equals_Utf8(token, "\xE0\xB8\x81\xC2\xAD\xC2\xAD", 7), "Token: %s", Str_Get_Ptr8(token)); token = (String*)Vec_Fetch(got, 3); TEST_TRUE(runner, token && Str_is_a(token, STRING) && Str_Equals_Utf8(token, "\xF0\xA0\x80\x80", 4), "Token: %s", Str_Get_Ptr8(token)); token = (String*)Vec_Fetch(got, 4); TEST_TRUE(runner, token && Str_is_a(token, STRING) && Str_Equals_Utf8(token, "a", 1), "Token: %s", Str_Get_Ptr8(token)); DECREF(got); FSFolder *modules_folder = TestUtils_modules_folder(); if (modules_folder == NULL) { SKIP(runner, 1372, "Can't locate test data"); } else { String *path = Str_newf("unicode/ucd/WordBreakTest.json"); Vector *tests = (Vector*)Json_slurp_json((Folder*)modules_folder, path); if (!tests) { RETHROW(Err_get_error()); } for (uint32_t i = 0, max = Vec_Get_Size(tests); i < max; i++) { Hash *test = (Hash*)Vec_Fetch(tests, i); String *text = (String*)Hash_Fetch_Utf8(test, "text", 4); Vector *wanted = (Vector*)Hash_Fetch_Utf8(test, "words", 5); Vector *got = StandardTokenizer_Split(tokenizer, text); TEST_TRUE(runner, Vec_Equals(wanted, (Obj*)got), "UCD test #%d", i + 1); DECREF(got); } DECREF(tests); DECREF(modules_folder); DECREF(path); } DECREF(tokenizer); }
static void test_Raw_Excerpt(TestBatchRunner *runner, Searcher *searcher, Obj *query) { String *content = (String*)SSTR_WRAP_UTF8("content", 7); Highlighter *highlighter = Highlighter_new(searcher, query, content, 6); int32_t top; String *raw_excerpt; String *field_val = (String *)SSTR_WRAP_UTF8("Ook. Urk. Ick. ", 18); Vector *spans = Vec_new(1); Vec_Push(spans, (Obj*)Span_new(0, 18, 1.0f)); HeatMap *heat_map = HeatMap_new(spans, 133); DECREF(spans); raw_excerpt = Highlighter_Raw_Excerpt(highlighter, field_val, &top, heat_map); TEST_TRUE(runner, Str_Equals_Utf8(raw_excerpt, "Ook.", 4), "Raw_Excerpt at top %s", Str_Get_Ptr8(raw_excerpt)); TEST_TRUE(runner, top == 0, "top is 0"); DECREF(raw_excerpt); DECREF(heat_map); spans = Vec_new(1); Vec_Push(spans, (Obj*)Span_new(6, 12, 1.0f)); heat_map = HeatMap_new(spans, 133); DECREF(spans); raw_excerpt = Highlighter_Raw_Excerpt(highlighter, field_val, &top, heat_map); TEST_TRUE(runner, Str_Equals_Utf8(raw_excerpt, "Urk.", 4), "Raw_Excerpt in middle, with 2 bounds"); TEST_TRUE(runner, top == 6, "top in the middle modified by Raw_Excerpt"); DECREF(raw_excerpt); DECREF(heat_map); field_val = (String *)SSTR_WRAP_UTF8("Ook urk ick i.", 14); spans = Vec_new(1); Vec_Push(spans, (Obj*)Span_new(12, 1, 1.0f)); heat_map = HeatMap_new(spans, 133); DECREF(spans); raw_excerpt = Highlighter_Raw_Excerpt(highlighter, field_val, &top, heat_map); TEST_TRUE(runner, Str_Equals_Utf8(raw_excerpt, ELLIPSIS " i.", 6), "Ellipsis at top"); TEST_TRUE(runner, top == 10, "top correct when leading ellipsis inserted"); DECREF(heat_map); DECREF(raw_excerpt); field_val = (String *)SSTR_WRAP_UTF8("Urk. Iz no good.", 17); spans = Vec_new(1); Vec_Push(spans, (Obj*)Span_new(6, 2, 1.0f)); heat_map = HeatMap_new(spans, 133); DECREF(spans); raw_excerpt = Highlighter_Raw_Excerpt(highlighter, field_val, &top, heat_map); TEST_TRUE(runner, Str_Equals_Utf8(raw_excerpt, "Iz no" ELLIPSIS, 8), "Ellipsis at end"); TEST_TRUE(runner, top == 6, "top trimmed"); DECREF(heat_map); DECREF(raw_excerpt); // Words longer than excerpt len field_val = (String *)SSTR_WRAP_UTF8("abc/def/ghi/jkl/mno", 19); spans = Vec_new(1); Vec_Push(spans, (Obj*)Span_new(0, 3, 1.0f)); heat_map = HeatMap_new(spans, 133); DECREF(spans); raw_excerpt = Highlighter_Raw_Excerpt(highlighter, field_val, &top, heat_map); TEST_TRUE(runner, Str_Equals_Utf8(raw_excerpt, "abc/d" ELLIPSIS, 8), "Long word at top"); DECREF(heat_map); DECREF(raw_excerpt); spans = Vec_new(1); Vec_Push(spans, (Obj*)Span_new(8, 3, 1.0f)); heat_map = HeatMap_new(spans, 133); DECREF(spans); raw_excerpt = Highlighter_Raw_Excerpt(highlighter, field_val, &top, heat_map); TEST_TRUE(runner, Str_Equals_Utf8(raw_excerpt, ELLIPSIS " f/g" ELLIPSIS, 10), "Long word in middle"); DECREF(heat_map); DECREF(raw_excerpt); DECREF(highlighter); }