fzy
terminal fuzzy finder picker
git clone https://9o.is/git/fzy.git
test_properties.c
(3914B)
1 #define _DEFAULT_SOURCE
2 #include <string.h>
3
4 #include "greatest/greatest.h"
5 #include "theft/theft.h"
6
7 #include "match.h"
8
9 static void *string_alloc_cb(struct theft *t, theft_hash seed, void *env) {
10 (void)env;
11 int limit = 128;
12
13 size_t sz = (size_t)(seed % limit) + 1;
14 char *str = malloc(sz + 1);
15 if (str == NULL) {
16 return THEFT_ERROR;
17 }
18
19 for (size_t i = 0; i < sz; i += sizeof(theft_hash)) {
20 theft_hash s = theft_random(t);
21 for (uint8_t b = 0; b < sizeof(theft_hash); b++) {
22 if (i + b >= sz) {
23 break;
24 }
25 str[i + b] = (uint8_t)(s >> (8 * b)) & 0xff;
26 }
27 }
28 str[sz] = 0;
29
30 return str;
31 }
32
33 static void string_free_cb(void *instance, void *env) {
34 free(instance);
35 (void)env;
36 }
37
38 static void string_print_cb(FILE *f, void *instance, void *env) {
39 char *str = (char *)instance;
40 (void)env;
41 size_t size = strlen(str);
42 fprintf(f, "str[%zd]:\n ", size);
43 uint8_t bytes = 0;
44 for (size_t i = 0; i < size; i++) {
45 fprintf(f, "%02x", str[i]);
46 bytes++;
47 if (bytes == 16) {
48 fprintf(f, "\n ");
49 bytes = 0;
50 }
51 }
52 fprintf(f, "\n");
53 }
54
55 static uint64_t string_hash_cb(void *instance, void *env) {
56 (void)env;
57 char *str = (char *)instance;
58 int size = strlen(str);
59 return theft_hash_onepass((uint8_t *)str, size);
60 }
61
62 static void *string_shrink_cb(void *instance, uint32_t tactic, void *env) {
63 (void)env;
64 char *str = (char *)instance;
65 int n = strlen(str);
66
67 if (tactic == 0) { /* first half */
68 return strndup(str, n / 2);
69 } else if (tactic == 1) { /* second half */
70 return strndup(str + (n / 2), n / 2);
71 } else {
72 return THEFT_NO_MORE_TACTICS;
73 }
74 }
75
76 static struct theft_type_info string_info = {
77 .alloc = string_alloc_cb,
78 .free = string_free_cb,
79 .print = string_print_cb,
80 .hash = string_hash_cb,
81 .shrink = string_shrink_cb,
82 };
83
84 static theft_trial_res prop_should_return_results_if_there_is_a_match(char *needle,
85 char *haystack) {
86 int match_exists = has_match(needle, haystack);
87 if (!match_exists)
88 return THEFT_TRIAL_SKIP;
89
90 score_t score = match(needle, haystack);
91
92 if (needle[0] == '\0')
93 return THEFT_TRIAL_SKIP;
94
95 if (score == SCORE_MIN)
96 return THEFT_TRIAL_FAIL;
97
98 return THEFT_TRIAL_PASS;
99 }
100
101 TEST should_return_results_if_there_is_a_match() {
102 struct theft *t = theft_init(0);
103 struct theft_cfg cfg = {
104 .name = __func__,
105 .fun = prop_should_return_results_if_there_is_a_match,
106 .type_info = {&string_info, &string_info},
107 .trials = 100000,
108 };
109
110 theft_run_res res = theft_run(t, &cfg);
111 theft_free(t);
112 GREATEST_ASSERT_EQm("should_return_results_if_there_is_a_match", THEFT_RUN_PASS, res);
113 PASS();
114 }
115
116 static theft_trial_res prop_positions_should_match_characters_in_string(char *needle,
117 char *haystack) {
118 int match_exists = has_match(needle, haystack);
119 if (!match_exists)
120 return THEFT_TRIAL_SKIP;
121
122 int n = strlen(needle);
123 size_t *positions = calloc(n, sizeof(size_t));
124 if (!positions)
125 return THEFT_TRIAL_ERROR;
126
127 match_positions(needle, haystack, positions);
128
129 /* Must be increasing */
130 for (int i = 1; i < n; i++) {
131 if (positions[i] <= positions[i - 1]) {
132 return THEFT_TRIAL_FAIL;
133 }
134 }
135
136 /* Matching characters must be in returned positions */
137 for (int i = 0; i < n; i++) {
138 if (toupper(needle[i]) != toupper(haystack[positions[i]])) {
139 return THEFT_TRIAL_FAIL;
140 }
141 }
142
143 free(positions);
144 return THEFT_TRIAL_PASS;
145 }
146
147 TEST positions_should_match_characters_in_string() {
148 struct theft *t = theft_init(0);
149 struct theft_cfg cfg = {
150 .name = __func__,
151 .fun = prop_positions_should_match_characters_in_string,
152 .type_info = {&string_info, &string_info},
153 .trials = 100000,
154 };
155
156 theft_run_res res = theft_run(t, &cfg);
157 theft_free(t);
158 GREATEST_ASSERT_EQm("should_return_results_if_there_is_a_match", THEFT_RUN_PASS, res);
159 PASS();
160 }
161
162 SUITE(properties_suite) {
163 RUN_TEST(should_return_results_if_there_is_a_match);
164 RUN_TEST(positions_should_match_characters_in_string);
165 }