1 // Copyright (c) 2023 Apple Inc. All rights reserved.
2
3 #include <stdlib.h>
4 #include <stdio.h>
5
6 #include <darwintest.h>
7 #include <darwintest_utils.h>
8
9 #include "sched_runqueue_harness.h"
10
11 static FILE *_log = NULL;
12
13 /* Mocking mach_absolute_time() */
14
15 static mach_timebase_info_data_t _timebase_info;
16 static uint64_t _curr_time = 0;
17
18 uint64_t
mock_absolute_time(void)19 mock_absolute_time(void)
20 {
21 return _curr_time;
22 }
23
24 void
set_mock_time(uint64_t timestamp)25 set_mock_time(uint64_t timestamp)
26 {
27 fprintf(_log, "\tnew mock time: %llu (%lluus)\n", timestamp,
28 timestamp * _timebase_info.numer / _timebase_info.denom / NSEC_PER_USEC);
29 _curr_time = timestamp;
30 }
31
32 void
increment_mock_time(uint64_t added_time)33 increment_mock_time(uint64_t added_time)
34 {
35 set_mock_time(_curr_time + added_time);
36 }
37
38 void
increment_mock_time_us(uint64_t us)39 increment_mock_time_us(uint64_t us)
40 {
41 fprintf(_log, "\tadding mock microseconds: %lluus\n", us);
42 increment_mock_time((us * NSEC_PER_USEC) * _timebase_info.denom / _timebase_info.numer);
43 }
44
45 /* Test harness utilities */
46
47 static void
cleanup_harness(void)48 cleanup_harness(void)
49 {
50 fclose(_log);
51 impl_cleanup_harness();
52 }
53
54 static char _log_filepath[MAXPATHLEN];
55 static bool auto_current_thread_disabled = false;
56
57 void
init_harness(char * test_name)58 init_harness(char *test_name)
59 {
60 kern_return_t kr;
61 kr = mach_timebase_info(&_timebase_info);
62 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_timebase_info");
63 auto_current_thread_disabled = false;
64
65 /* Set up debugging log of harness events */
66 strcpy(_log_filepath, test_name);
67 strcat(_log_filepath, "_test_log.txt");
68 dt_resultfile(_log_filepath, sizeof(_log_filepath));
69 _log = fopen(_log_filepath, "w+");
70 T_QUIET; T_WITH_ERRNO; T_ASSERT_NE(_log, NULL, "fopen");
71 T_LOG("For debugging, see log of harness events in \"%s\"", _log_filepath);
72
73 T_ATEND(cleanup_harness);
74 impl_init_runqueue();
75 }
76
77 struct thread_group *
create_tg(int interactivity_score)78 create_tg(int interactivity_score)
79 {
80 struct thread_group *tg = impl_create_tg(interactivity_score);
81 fprintf(_log, "\tcreated TG %p w/ interactivity_score %d\n", (void *)tg, interactivity_score);
82 return tg;
83 }
84
85 test_thread_t
create_thread(int th_sched_bucket,struct thread_group * tg,int pri)86 create_thread(int th_sched_bucket, struct thread_group *tg, int pri)
87 {
88 test_thread_t thread = impl_create_thread(th_sched_bucket, tg, pri);
89 fprintf(_log, "\tcreated thread %p w/ bucket %d, tg %p, pri %d\n",
90 (void *)thread, th_sched_bucket, (void *)tg, pri);
91 return thread;
92 }
93
94 void
set_thread_sched_mode(test_thread_t thread,int mode)95 set_thread_sched_mode(test_thread_t thread, int mode)
96 {
97 fprintf(_log, "\tset thread %p sched_mode to %d\n", (void *)thread, mode);
98 impl_set_thread_sched_mode(thread, mode);
99 }
100
101 void
set_thread_processor_bound(test_thread_t thread)102 set_thread_processor_bound(test_thread_t thread)
103 {
104 fprintf(_log, "\tset thread %p processor-bound\n", (void *)thread);
105 impl_set_thread_processor_bound(thread);
106 }
107
108 void
set_thread_current(test_thread_t thread)109 set_thread_current(test_thread_t thread)
110 {
111 impl_set_thread_current(thread);
112 fprintf(_log, "\tset %p as current thread\n", thread);
113 }
114
115 bool
runqueue_empty(void)116 runqueue_empty(void)
117 {
118 return dequeue_thread_expect(NULL);
119 }
120
121 void
enqueue_thread(test_thread_t thread)122 enqueue_thread(test_thread_t thread)
123 {
124 fprintf(_log, "\tenqueued %p\n", (void *)thread);
125 impl_enqueue_thread(thread);
126 }
127
128 void
enqueue_threads(int num_threads,...)129 enqueue_threads(int num_threads, ...)
130 {
131 va_list args;
132 va_start(args, num_threads);
133 for (int i = 0; i < num_threads; i++) {
134 test_thread_t thread = va_arg(args, test_thread_t);
135 enqueue_thread(thread);
136 }
137 }
138
139 void
enqueue_threads_arr(int num_threads,test_thread_t * threads)140 enqueue_threads_arr(int num_threads, test_thread_t *threads)
141 {
142 for (int i = 0; i < num_threads; i++) {
143 enqueue_thread(threads[i]);
144 }
145 }
146
147 void
enqueue_threads_rand_order(unsigned int random_seed,int num_threads,...)148 enqueue_threads_rand_order(unsigned int random_seed, int num_threads, ...)
149 {
150 test_thread_t *tmp = (test_thread_t *)malloc(sizeof(test_thread_t) * (size_t)num_threads);
151 va_list args;
152 va_start(args, num_threads);
153 for (int i = 0; i < num_threads; i++) {
154 test_thread_t thread = va_arg(args, test_thread_t);
155 tmp[i] = thread;
156 }
157 enqueue_threads_arr_rand_order(random_seed, num_threads, tmp);
158 free(tmp);
159 }
160
161 void
enqueue_threads_arr_rand_order(unsigned int random_seed,int num_threads,test_thread_t * threads)162 enqueue_threads_arr_rand_order(unsigned int random_seed, int num_threads, test_thread_t *threads)
163 {
164 test_thread_t scratch_space[num_threads];
165 for (int i = 0; i < num_threads; i++) {
166 scratch_space[i] = threads[i];
167 }
168 srand(random_seed);
169 for (int i = 0; i < num_threads; i++) {
170 int rand_ind = (rand() % (num_threads - i)) + i;
171 test_thread_t tmp = scratch_space[i];
172 scratch_space[i] = scratch_space[rand_ind];
173 scratch_space[rand_ind] = tmp;
174 }
175 enqueue_threads_arr(num_threads, scratch_space);
176 }
177
178 bool
dequeue_thread_expect(test_thread_t expected_thread)179 dequeue_thread_expect(test_thread_t expected_thread)
180 {
181 test_thread_t chosen_thread = impl_dequeue_thread();
182 fprintf(_log, "%s: dequeued %p, expecting %p\n", chosen_thread == expected_thread ?
183 "PASS" : "FAIL", (void *)chosen_thread, (void *)expected_thread);
184 if (chosen_thread != expected_thread) {
185 return false;
186 }
187 if (expected_thread != NULL && auto_current_thread_disabled == false) {
188 /*
189 * Additionally verify that chosen_thread still gets returned as the highest
190 * thread, even when compared against the remaining runqueue as the currently
191 * running thread
192 */
193 set_thread_current(expected_thread);
194 bool pass = dequeue_thread_expect_compare_current(expected_thread);
195 if (pass) {
196 pass = check_preempt_current(false);
197 }
198 impl_clear_thread_current();
199 fprintf(_log, "\tcleared current thread\n");
200 return pass;
201 }
202 return true;
203 }
204
205 int
dequeue_threads_expect_ordered(int num_threads,...)206 dequeue_threads_expect_ordered(int num_threads, ...)
207 {
208 va_list args;
209 va_start(args, num_threads);
210 int first_bad_index = -1;
211 for (int i = 0; i < num_threads; i++) {
212 test_thread_t thread = va_arg(args, test_thread_t);
213 bool result = dequeue_thread_expect(thread);
214 if ((result == false) && (first_bad_index == -1)) {
215 first_bad_index = i;
216 /* Instead of early-returning, keep dequeueing threads so we can log the information */
217 }
218 }
219 return first_bad_index;
220 }
221
222 int
dequeue_threads_expect_ordered_arr(int num_threads,test_thread_t * threads)223 dequeue_threads_expect_ordered_arr(int num_threads, test_thread_t *threads)
224 {
225 int first_bad_index = -1;
226 for (int i = 0; i < num_threads; i++) {
227 bool result = dequeue_thread_expect(threads[i]);
228 if ((result == false) && (first_bad_index == -1)) {
229 first_bad_index = i;
230 /* Instead of early-returning, keep dequeueing threads so we can log the information */
231 }
232 }
233 return first_bad_index;
234 }
235
236 bool
dequeue_thread_expect_compare_current(test_thread_t expected_thread)237 dequeue_thread_expect_compare_current(test_thread_t expected_thread)
238 {
239 test_thread_t chosen_thread = impl_dequeue_thread_compare_current();
240 fprintf(_log, "%s: dequeued %p, expecting current %p\n", chosen_thread == expected_thread ?
241 "PASS" : "FAIL", (void *)chosen_thread, (void *)expected_thread);
242 return chosen_thread == expected_thread;
243 }
244
245 bool
check_preempt_current(bool preemption_expected)246 check_preempt_current(bool preemption_expected)
247 {
248 bool preempting = impl_processor_csw_check();
249 fprintf(_log, "%s: would preempt? %d, expecting to preempt? %d\n", preempting == preemption_expected ?
250 "PASS" : "FAIL", preempting, preemption_expected);
251 return preempting == preemption_expected;
252 }
253
254 bool
tracepoint_expect(uint64_t trace_code,uint64_t arg1,uint64_t arg2,uint64_t arg3,uint64_t arg4)255 tracepoint_expect(uint64_t trace_code, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4)
256 {
257 uint64_t popped_trace_code, popped_arg1, popped_arg2, popped_arg3, popped_arg4;
258 impl_pop_tracepoint(&popped_trace_code, &popped_arg1, &popped_arg2, &popped_arg3, &popped_arg4);
259 bool pass = (trace_code == popped_trace_code) && (arg1 == popped_arg1) &&
260 (arg2 == popped_arg2) && (arg3 == popped_arg3) && (arg4 == popped_arg4);
261 fprintf(_log, "%s: expected code %llx arg1 %llx arg2 %llx arg3 %llx arg4 %llx\n", pass ? "PASS" : "FAIL",
262 trace_code, arg1, arg2, arg3, arg4);
263 if (pass == false) {
264 fprintf(_log, "\tfound code %llx arg1 %llx arg2 %llx arg3 %llx arg4 %llx\n",
265 popped_trace_code, popped_arg1, popped_arg2, popped_arg3, popped_arg4);
266 }
267 return pass;
268 }
269
270 void
disable_auto_current_thread(void)271 disable_auto_current_thread(void)
272 {
273 auto_current_thread_disabled = true;
274 }
275