https://github.com/akkartik/mu/blob/master/082scenario_screen.cc
1
2
3
4
5
6
7
8 :(before "End initialize_transform_rewrite_literal_string_to_text()")
9 recipes_taking_literal_strings.insert("screen-should-contain");
10 recipes_taking_literal_strings.insert("screen-should-contain-in-color");
11
12 :(scenarios run_mu_scenario)
13 :(scenario screen_in_scenario)
14 scenario screen-in-scenario [
15 local-scope
16 assume-screen 5/width, 3/height
17 run [
18 a:char <- copy 97/a
19 screen:&:screen <- print screen:&:screen, a
20 ]
21 screen-should-contain [
22
23 .a .
24 . .
25 . .
26 ]
27 ]
28
29
30 :(scenario screen_in_scenario_unicode)
31
32 scenario screen-in-scenario-unicode [
33 local-scope
34 assume-screen 5/width, 3/height
35 run [
36 lambda:char <- copy 955/greek-small-lambda
37 screen:&:screen <- print screen:&:screen, lambda
38 a:char <- copy 97/a
39 screen:&:screen <- print screen:&:screen, a
40 ]
41 screen-should-contain [
42
43 .λa .
44 . .
45 . .
46 ]
47 ]
48
49
50 :(scenario screen_in_scenario_color)
51 scenario screen-in-scenario-color [
52 local-scope
53 assume-screen 5/width, 3/height
54 run [
55 lambda:char <- copy 955/greek-small-lambda
56 screen:&:screen <- print screen:&:screen, lambda, 1/red
57 a:char <- copy 97/a
58 screen:&:screen <- print screen:&:screen, a, 7/white
59 ]
60
61 screen-should-contain [
62
63 .λa .
64 . .
65 . .
66 ]
67
68
69 screen-should-contain-in-color 7/white, [
70
71 . a .
72 . .
73 . .
74 ]
75
76 screen-should-contain-in-color 1/red, [
77
78 .λ .
79 . .
80 . .
81 ]
82 ]
83
84
85 :(scenario screen_in_scenario_error)
86 % Scenario_testing_scenario = true;
87 % Hide_errors = true;
88 scenario screen-in-scenario-error [
89 local-scope
90 assume-screen 5/width, 3/height
91 run [
92 a:char <- copy 97/a
93 screen:&:screen <- print screen:&:screen, a
94 ]
95 screen-should-contain [
96
97 .b .
98 . .
99 . .
100 ]
101 ]
102 +error: F - screen-in-scenario-error: expected screen location (0, 0) to contain 98 ('b') instead of 97 ('a')
103
104 :(scenario screen_in_scenario_color_error)
105 % Scenario_testing_scenario = true;
106 % Hide_errors = true;
107
108 scenario screen-in-scenario-color-error [
109 local-scope
110 assume-screen 5/width, 3/height
111 run [
112 a:char <- copy 97/a
113 screen:&:screen <- print screen:&:screen, a, 1/red
114 ]
115 screen-should-contain-in-color 2/green, [
116
117 .a .
118 . .
119 . .
120 ]
121 ]
122 +error: F - screen-in-scenario-color-error: expected screen location (0, 0) to contain 'a' in color 2 instead of 1
123
124 :(scenarios run)
125 :(scenario convert_names_does_not_fail_when_mixing_special_names_and_numeric_locations)
126 % Scenario_testing_scenario = true;
127 def main [
128 screen:num <- copy 1:num
129 ]
130 -error: mixing variable names and numeric addresses in main
131 $error: 0
132 :(scenarios run_mu_scenario)
133
134
135
136
137
138
139
140 :(before "End Globals")
141 extern const int Max_variables_in_scenarios = Reserved_for_tests-100;
142 int Next_predefined_global_for_scenarios = Max_variables_in_scenarios;
143 :(before "End Reset")
144 assert(Next_predefined_global_for_scenarios < Reserved_for_tests);
145
146 :(before "End Globals")
147
148 extern const int SCREEN = next_predefined_global_for_scenarios(2);
149
150 :(code)
151 int next_predefined_global_for_scenarios(int size) {
152 int result = Next_predefined_global_for_scenarios;
153 Next_predefined_global_for_scenarios += size;
154 return result;
155 }
156
157
158 :(before "End Special Scenario Variable Names(r)")
159 Name[r]["screen"] = SCREEN;
160
161 :(before "End is_special_name Special-cases")
162 if (s == "screen") return true;
163
164 :(before "End Rewrite Instruction(curr, recipe result)")
165
166
167 if (curr.name == "assume-screen") {
168 curr.name = "new-fake-screen";
169 if (!curr.products.empty()) {
170 raise << result.name << ": 'assume-screen' has no products\n" << end();
171 }
172 else if (!starts_with(result.name, "scenario_")) {
173 raise << result.name << ": 'assume-screen' can't be called here, only in scenarios\n" << end();
174 }
175 else {
176 assert(curr.products.empty());
177 curr.products.push_back(reagent("screen:&:screen/raw"));
178 curr.products.at(0).set_value(SCREEN);
179 }
180 }
181
182 :(scenario assume_screen_shows_up_in_errors)
183 % Hide_errors = true;
184 scenario assume-screen-shows-up-in-errors [
185 assume-screen width, 5
186 ]
187 +error: assume-screen-shows-up-in-errors: missing type for 'width' in 'assume-screen width, 5'
188
189
190 :(before "End Primitive Recipe Declarations")
191 SCREEN_SHOULD_CONTAIN,
192 :(before "End Primitive Recipe Numbers")
193 put(Recipe_ordinal, "screen-should-contain", SCREEN_SHOULD_CONTAIN);
194 :(before "End Primitive Recipe Checks")
195 case SCREEN_SHOULD_CONTAIN: {
196 if (SIZE(inst.ingredients) != 1) {
197 raise << maybe(get(Recipe, r).name) << "'screen-should-contain' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end();
198 break;
199 }
200 if (!is_literal_text(inst.ingredients.at(0))) {
201 raise << maybe(get(Recipe, r).name) << "first ingredient of 'screen-should-contain' should be a literal string, but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
202 break;
203 }
204 break;
205 }
206 :(before "End Primitive Recipe Implementations")
207 case SCREEN_SHOULD_CONTAIN: {
208 if (!Passed) break;
209 assert(scalar(ingredients.at(0)));
210 check_screen(current_instruction().ingredients.at(0).name, -1);
211 break;
212 }
213
214 :(before "End Primitive Recipe Declarations")
215 SCREEN_SHOULD_CONTAIN_IN_COLOR,
216 :(before "End Primitive Recipe Numbers")
217 put(Recipe_ordinal, "screen-should-contain-in-color", SCREEN_SHOULD_CONTAIN_IN_COLOR);
218 :(before "End Primitive Recipe Checks")
219 case SCREEN_SHOULD_CONTAIN_IN_COLOR: {
220 if (SIZE(inst.ingredients) != 2) {
221 raise << maybe(get(Recipe, r).name) << "'screen-should-contain-in-color' requires exactly two ingredients, but got '" << to_original_string(inst) << "'\n" << end();
222 break;
223 }
224 if (!is_mu_number(inst.ingredients.at(0))) {
225 raise << maybe(get(Recipe, r).name) << "first ingredient of 'screen-should-contain-in-color' should be a number (color code), but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
226 break;
227 }
228 if (!is_literal_text(inst.ingredients.at(1))) {
229 raise << maybe(get(Recipe, r).name) << "second ingredient of 'screen-should-contain-in-color' should be a literal string, but got '" << inst.ingredients.at(1).original_string << "'\n" << end();
230 break;
231 }
232 break;
233 }
234 :(before "End Primitive Recipe Implementations")
235 case SCREEN_SHOULD_CONTAIN_IN_COLOR: {
236 if (!Passed) break;
237 assert(scalar(ingredients.at(0)));
238 assert(scalar(ingredients.at(1)));
239 check_screen(current_instruction().ingredients.at(1).name, ingredients.at(0).at(0));
240 break;
241 }
242
243 :(before "End Types")
244
245 struct raw_string_stream {
246 int index;
247 const int max;
248 const char* buf;
249
250 raw_string_stream(const string&);
251 uint32_t get();
252 uint32_t peek();
253 bool at_end() const;
254 void skip_whitespace_and_comments();
255 };
256
257 :(code)
258 void check_screen(const string& expected_contents, const int color) {
259 int screen_location = get_or_insert(Memory, SCREEN+1) + 1;
260 reagent screen("x:screen");
261 int screen_data_location = find_element_location(screen_location, "data", screen.type, "check_screen");
262 assert(screen_data_location >= 0);
263
264 int screen_data_start = get_or_insert(Memory, screen_data_location+1) + 1;
265
266 int screen_width_location = find_element_location(screen_location, "num-columns", screen.type, "check_screen");
267
268 int screen_width = get_or_insert(Memory, screen_width_location);
269
270 int screen_height_location = find_element_location(screen_location, "num-rows", screen.type, "check_screen");
271
272 int screen_height = get_or_insert(Memory, screen_height_location);
273
274 int top_index_location= find_element_location(screen_location, "top-idx", screen.type, "check_screen");
275
276 int top_index = get_or_insert(Memory, top_index_location);
277
278 raw_string_stream cursor(expected_contents);
279
280 for (int i=0, row=top_index/screen_width; i < screen_height; ++i, row=(row+1)%screen_height) {
281 cursor.skip_whitespace_and_comments();
282 if (cursor.at_end()) break;
283 if (cursor.get() != '.') {
284 raise << maybe(current_recipe_name()) << "each row of the expected screen should start with a '.'\n" << end();
285 if (!Scenario_testing_scenario) Passed = false;
286 return;
287 }
288 int addr = screen_data_start+1+row*screen_width* 2;
289 for (int column = 0; column < screen_width; ++column, addr+= 2) {
290 const int cell_color_offset = 1;
291 uint32_t curr = cursor.get();
292 if (get_or_insert(Memory, addr) == 0 && isspace(curr)) continue;
293 if (curr == ' ' && color != -1 && color != get_or_insert(Memory, addr+cell_color_offset)) {
294
295 continue;
296 }
297 if (get_or_insert(Memory, addr) != 0 && get_or_insert(Memory, addr) == curr) {
298 if (color == -1 || color == get_or_insert(Memory, addr+cell_color_offset)) continue;
299
300 if (!Hide_errors) cerr << '\n';
301 raise << "F - " << maybe(current_recipe_name()) << "expected screen location (" << row << ", " << column << ") to contain '" << unicode_character_at(addr) << "' in color " << color << " instead of " << no_scientific(get_or_insert(Memory, addr+cell_color_offset)) << "\n" << end();
302 if (!Hide_errors) dump_screen();
303 if (!Scenario_testing_scenario) Passed = false;
304 return;
305 }
306
307
308
309 char expected_pretty[10] = {0};
310 if (curr < 256 && !iscntrl(curr)) {
311
312 expected_pretty[0] = ' ', expected_pretty[1] = '(', expected_pretty[2] = '\'', expected_pretty[3] = static_cast<unsigned char>(curr), expected_pretty[4] = '\'', expected_pretty[5] = ')', expected_pretty[6] = '\0';
313 }
314 char actual_pretty[10] = {0};
315 if (get_or_insert(Memory, addr) < 256 && !iscntrl(get_or_insert(Memory, addr))) {
316
317 actual_pretty[0] = ' ', actual_pretty[1] = '(', actual_pretty[2] = '\'', actual_pretty[3] = static_cast<unsigned char>(get_or_insert(Memory, addr)), actual_pretty[4] = '\'', actual_pretty[5] = ')', actual_pretty[6] = '\0';
318 }
319
320 ostringstream color_phrase;
321 if (color != -1) color_phrase << " in color " << color;
322 if (!Hide_errors) cerr << '\n';
323 raise << "F - " << maybe(current_recipe_name()) << "expected screen location (" << row << ", " << column << ") to contain " << curr << expected_pretty << color_phrase.str() << " instead of " << no_scientific(get_or_insert(Memory, addr)) << actual_pretty << '\n' << end();
324 if (!Hide_errors) dump_screen();
325 if (!Scenario_testing_scenario) Passed = false;
326 return;
327 }
328 if (cursor.get() != '.') {
329 raise << maybe(current_recipe_name()) << "row " << row << " of the expected screen is too long\n" << end();
330 if (!Scenario_testing_scenario) Passed = false;
331 return;
332 }
333 }
334 cursor.skip_whitespace_and_comments();
335 if (!cursor.at_end()) {
336 raise << maybe(current_recipe_name()) << "expected screen has too many rows\n" << end();
337 Passed = false;
338 }
339 }
340
341 const char* unicode_character_at(int addr) {
342 int unicode_code_point = static_cast<int>(get_or_insert(Memory, addr));
343 return to_unicode(unicode_code_point);
344 }
345
346 raw_string_stream::raw_string_stream(const string& backing) :index(0), max(SIZE(backing)), buf(backing.c_str()) {}
347
348 bool raw_string_stream::at_end() const {
349 if (index >= max) return true;
350 if (tb_utf8_char_length(buf[index]) > max-index) {
351 raise << "unicode string seems corrupted at index "<< index << " character " << static_cast<int>(buf[index]) << '\n' << end();
352 return true;
353 }
354 return false;
355 }
356
357 uint32_t raw_string_stream::get() {
358 assert(index < max);
359 uint32_t result = 0;
360 int length = tb_utf8_char_to_unicode(&result, &buf[index]);
361 assert(length != TB_EOF);
362 index += length;
363 return result;
364 }
365
366 uint32_t raw_string_stream::peek() {
367 assert(index < max);
368 uint32_t result = 0;
369 int length = tb_utf8_char_to_unicode(&result, &buf[index]);
370 assert(length != TB_EOF);
371 return result;
372 }
373
374 void raw_string_stream::skip_whitespace_and_comments() {
375 while (!at_end()) {
376 if (isspace(peek())) get();
377 else if (peek() == '#') {
378
379 get();
380 while (peek() != '\n') get();
381 }
382 else break;
383 }
384 }
385
386 :(before "End Primitive Recipe Declarations")
387 _DUMP_SCREEN,
388 :(before "End Primitive Recipe Numbers")
389 put(Recipe_ordinal, "$dump-screen", _DUMP_SCREEN);
390 :(before "End Primitive Recipe Checks")
391 case _DUMP_SCREEN: {
392 break;
393 }
394 :(before "End Primitive Recipe Implementations")
395 case _DUMP_SCREEN: {
396 dump_screen();
397 break;
398 }
399
400 :(code)
401 void dump_screen() {
402 int screen_location = get_or_insert(Memory, SCREEN+1) + 1;
403 reagent screen("x:screen");
404 int screen_data_location = find_element_location(screen_location, "data", screen.type, "check_screen");
405 assert(screen_data_location >= 0);
406
407 int screen_data_start = get_or_insert(Memory, screen_data_location+1) + 1;
408
409 int screen_width_location = find_element_location(screen_location, "num-columns", screen.type, "check_screen");
410
411 int screen_width = get_or_insert(Memory, screen_width_location);
412
413 int screen_height_location = find_element_location(screen_location, "num-rows", screen.type, "check_screen");
414
415 int screen_height = get_or_insert(Memory, screen_height_location);
416
417 int top_index_location= find_element_location(screen_location, "top-idx", screen.type, "check_screen");
418
419 int top_index = get_or_insert(Memory, top_index_location);
420
421 for (int i=0, row=top_index/screen_width; i < screen_height; ++i, row=(row+1)%screen_height) {
422 cerr << '.';
423 int curr = screen_data_start+1+row*screen_width* 2;
424 for (int col = 0; col < screen_width; ++col) {
425 if (get_or_insert(Memory, curr))
426 cerr << to_unicode(static_cast<uint32_t>(get_or_insert(Memory, curr)));
427 else
428 cerr << ' ';
429 curr += 2;
430 }
431 cerr << ".\n";
432 }
433 }