fzy

terminal fuzzy finder picker

git clone https://9o.is/git/fzy.git

test_match.c

(6707B)


      1 #include <stdlib.h>
      2 
      3 #include "../config.h"
      4 #include "match.h"
      5 
      6 #include "greatest/greatest.h"
      7 
      8 #define SCORE_TOLERANCE 0.000001
      9 #define ASSERT_SCORE_EQ(a,b) ASSERT_IN_RANGE((a), (b), SCORE_TOLERANCE)
     10 #define ASSERT_SIZE_T_EQ(a,b) ASSERT_EQ_FMT((size_t)(a), (b), "%zu")
     11 
     12 /* has_match(char *needle, char *haystack) */
     13 TEST exact_match_should_return_true() {
     14 	ASSERT(has_match("a", "a"));
     15 	PASS();
     16 }
     17 
     18 TEST partial_match_should_return_true() {
     19 	ASSERT(has_match("a", "ab"));
     20 	ASSERT(has_match("a", "ba"));
     21 	PASS();
     22 }
     23 
     24 TEST match_with_delimiters_in_between() {
     25 	ASSERT(has_match("abc", "a|b|c"));
     26 	PASS();
     27 }
     28 
     29 TEST non_match_should_return_false() {
     30 	ASSERT(!has_match("a", ""));
     31 	ASSERT(!has_match("a", "b"));
     32 	ASSERT(!has_match("ass", "tags"));
     33 	PASS();
     34 }
     35 
     36 TEST empty_query_should_always_match() {
     37 	/* match when query is empty */
     38 	ASSERT(has_match("", ""));
     39 	ASSERT(has_match("", "a"));
     40 	PASS();
     41 }
     42 
     43 /* match(char *needle, char *haystack) */
     44 
     45 TEST should_prefer_starts_of_words() {
     46 	/* App/Models/Order is better than App/MOdels/zRder  */
     47 	ASSERT(match("amor", "app/models/order") > match("amor", "app/models/zrder"));
     48 	PASS();
     49 }
     50 
     51 TEST should_prefer_consecutive_letters() {
     52 	/* App/MOdels/foo is better than App/M/fOo  */
     53 	ASSERT(match("amo", "app/m/foo") < match("amo", "app/models/foo"));
     54 	PASS();
     55 }
     56 
     57 TEST should_prefer_contiguous_over_letter_following_period() {
     58 	/* GEMFIle.Lock < GEMFILe  */
     59 	ASSERT(match("gemfil", "Gemfile.lock") < match("gemfil", "Gemfile"));
     60 	PASS();
     61 }
     62 
     63 TEST should_prefer_shorter_matches() {
     64 	ASSERT(match("abce", "abcdef") > match("abce", "abc de"));
     65 	ASSERT(match("abc", "    a b c ") > match("abc", " a  b  c "));
     66 	ASSERT(match("abc", " a b c    ") > match("abc", " a  b  c "));
     67 	PASS();
     68 }
     69 
     70 TEST should_prefer_shorter_candidates() {
     71 	ASSERT(match("test", "tests") > match("test", "testing"));
     72 	PASS();
     73 }
     74 
     75 TEST should_prefer_start_of_candidate() {
     76 	/* Scores first letter highly */
     77 	ASSERT(match("test", "testing") > match("test", "/testing"));
     78 	PASS();
     79 }
     80 
     81 TEST score_exact_match() {
     82 	/* Exact match is SCORE_MAX */
     83 	ASSERT_SCORE_EQ(SCORE_MAX, match("abc", "abc"));
     84 	ASSERT_SCORE_EQ(SCORE_MAX, match("aBc", "abC"));
     85 	PASS();
     86 }
     87 
     88 TEST score_empty_query() {
     89 	/* Empty query always results in SCORE_MIN */
     90 	ASSERT_SCORE_EQ(SCORE_MIN, match("", ""));
     91 	ASSERT_SCORE_EQ(SCORE_MIN, match("", "a"));
     92 	ASSERT_SCORE_EQ(SCORE_MIN, match("", "bb"));
     93 	PASS();
     94 }
     95 
     96 TEST score_gaps() {
     97 	ASSERT_SCORE_EQ(SCORE_GAP_LEADING, match("a", "*a"));
     98 	ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2, match("a", "*ba"));
     99 	ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_GAP_TRAILING, match("a", "**a*"));
    100 	ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_GAP_TRAILING*2, match("a", "**a**"));
    101 	ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_MATCH_CONSECUTIVE + SCORE_GAP_TRAILING*2, match("aa", "**aa**"));
    102 	ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_GAP_LEADING + SCORE_GAP_INNER + SCORE_GAP_TRAILING + SCORE_GAP_TRAILING, match("aa", "**a*a**"));
    103 	PASS();
    104 }
    105 
    106 TEST score_consecutive() {
    107 	ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_CONSECUTIVE, match("aa", "*aa"));
    108 	ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_CONSECUTIVE*2, match("aaa", "*aaa"));
    109 	ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_GAP_INNER + SCORE_MATCH_CONSECUTIVE, match("aaa", "*a*aa"));
    110 	PASS();
    111 }
    112 
    113 TEST score_slash() {
    114 	ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_SLASH, match("a", "/a"));
    115 	ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_MATCH_SLASH, match("a", "*/a"));
    116 	ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_MATCH_SLASH + SCORE_MATCH_CONSECUTIVE, match("aa", "a/aa"));
    117 	PASS();
    118 }
    119 
    120 TEST score_capital() {
    121 	ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_CAPITAL, match("a", "bA"));
    122 	ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_MATCH_CAPITAL, match("a", "baA"));
    123 	ASSERT_SCORE_EQ(SCORE_GAP_LEADING*2 + SCORE_MATCH_CAPITAL + SCORE_MATCH_CONSECUTIVE, match("aa", "baAa"));
    124 	PASS();
    125 }
    126 
    127 TEST score_dot() {
    128 	ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_DOT, match("a", ".a"));
    129 	ASSERT_SCORE_EQ(SCORE_GAP_LEADING*3 + SCORE_MATCH_DOT, match("a", "*a.a"));
    130 	ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_GAP_INNER + SCORE_MATCH_DOT, match("a", "*a.a"));
    131 	PASS();
    132 }
    133 
    134 TEST score_long_string() {
    135 	char string[4096];
    136 	memset(string, 'a', sizeof(string) - 1);
    137 	string[sizeof(string) - 1] = '\0';
    138 
    139 	ASSERT_SCORE_EQ(SCORE_MIN, match("aa", string));
    140 	ASSERT_SCORE_EQ(SCORE_MIN, match(string, "aa"));
    141 	ASSERT_SCORE_EQ(SCORE_MIN, match(string, string));
    142 
    143 	PASS();
    144 }
    145 
    146 TEST positions_consecutive() {
    147 	size_t positions[3];
    148 	match_positions("amo", "app/models/foo", positions);
    149 	ASSERT_SIZE_T_EQ(0, positions[0]);
    150 	ASSERT_SIZE_T_EQ(4, positions[1]);
    151 	ASSERT_SIZE_T_EQ(5, positions[2]);
    152 
    153 	PASS();
    154 }
    155 
    156 TEST positions_start_of_word() {
    157 	/*
    158 	 * We should prefer matching the 'o' in order, since it's the beginning
    159 	 * of a word.
    160 	 */
    161 	size_t positions[4];
    162 	match_positions("amor", "app/models/order", positions);
    163 	ASSERT_SIZE_T_EQ(0, positions[0]);
    164 	ASSERT_SIZE_T_EQ(4, positions[1]);
    165 	ASSERT_SIZE_T_EQ(11, positions[2]);
    166 	ASSERT_SIZE_T_EQ(12, positions[3]);
    167 
    168 	PASS();
    169 }
    170 
    171 TEST positions_no_bonuses() {
    172 	size_t positions[2];
    173 	match_positions("as", "tags", positions);
    174 	ASSERT_SIZE_T_EQ(1, positions[0]);
    175 	ASSERT_SIZE_T_EQ(3, positions[1]);
    176 
    177 	match_positions("as", "examples.txt", positions);
    178 	ASSERT_SIZE_T_EQ(2, positions[0]);
    179 	ASSERT_SIZE_T_EQ(7, positions[1]);
    180 
    181 	PASS();
    182 }
    183 
    184 TEST positions_multiple_candidates_start_of_words() {
    185 	size_t positions[3];
    186 	match_positions("abc", "a/a/b/c/c", positions);
    187 	ASSERT_SIZE_T_EQ(2, positions[0]);
    188 	ASSERT_SIZE_T_EQ(4, positions[1]);
    189 	ASSERT_SIZE_T_EQ(6, positions[2]);
    190 
    191 	PASS();
    192 }
    193 
    194 TEST positions_exact_match() {
    195 	size_t positions[3];
    196 	match_positions("foo", "foo", positions);
    197 	ASSERT_SIZE_T_EQ(0, positions[0]);
    198 	ASSERT_SIZE_T_EQ(1, positions[1]);
    199 	ASSERT_SIZE_T_EQ(2, positions[2]);
    200 
    201 	PASS();
    202 }
    203 
    204 SUITE(match_suite) {
    205 	RUN_TEST(exact_match_should_return_true);
    206 	RUN_TEST(partial_match_should_return_true);
    207 	RUN_TEST(empty_query_should_always_match);
    208 	RUN_TEST(non_match_should_return_false);
    209 	RUN_TEST(match_with_delimiters_in_between);
    210 
    211 	RUN_TEST(should_prefer_starts_of_words);
    212 	RUN_TEST(should_prefer_consecutive_letters);
    213 	RUN_TEST(should_prefer_contiguous_over_letter_following_period);
    214 	RUN_TEST(should_prefer_shorter_matches);
    215 	RUN_TEST(should_prefer_shorter_candidates);
    216 	RUN_TEST(should_prefer_start_of_candidate);
    217 
    218 	RUN_TEST(score_exact_match);
    219 	RUN_TEST(score_empty_query);
    220 	RUN_TEST(score_gaps);
    221 	RUN_TEST(score_consecutive);
    222 	RUN_TEST(score_slash);
    223 	RUN_TEST(score_capital);
    224 	RUN_TEST(score_dot);
    225 	RUN_TEST(score_long_string);
    226 
    227 	RUN_TEST(positions_consecutive);
    228 	RUN_TEST(positions_start_of_word);
    229 	RUN_TEST(positions_no_bonuses);
    230 	RUN_TEST(positions_multiple_candidates_start_of_words);
    231 	RUN_TEST(positions_exact_match);
    232 }