To produce reports for use in the Inform user interface app.
§1. About reports. This whole section applies only for the non-standard use case where Intest is running inside the Inform user interface app. A "report" is then an HTML page summarising the results of testing, which that app can then display. Reports are heavily tied to the standard recipe for testing Inform source, when it's run through I7, then I6, then executed to obtain a transcript of the resulting play. As a result, any individual test can fail in a set number of ways:
define I7_FAILED_OUTCOME 1 I7 issued problem messages define I6_FAILED_OUTCOME 2 I6 issued error messages define CURSED_OUTCOME 3 where there's no ideal transcript to compare against define WRONG_TRANSCRIPT_OUTCOME 4 the actual transcript didn't match the ideal define PERFECT_OUTCOME 5
§2. Report feature. The following implements the -report command line feature.
void Reporter::report_single(OUTPUT_STREAM, test_case *tc, action_item *ai) { report_state rs; Initialise the report state2.2; TextFiles::read(rs.prototype_HTML_file, FALSE, "can't open test report file", TRUE, &Reporter::filter, NULL, &rs); }
typedef struct report_state { text_stream *REPORT_TO; struct test_case *test; int success_code; one of the *_OUTCOME constants above int turns_keyed; number of commands auto-entered from a TEST ME struct filename *prototype_HTML_file; ultimately from the app struct text_stream *relevant_node_ID; to provide in-app links to the Skein int stage; where we are in scanning the prototype int first_flag; int last_flag; char test_case_letter; 'A' to 'Z': for cases in an extension project } report_state;
- The structure report_state is private to this section.
§2.2. Initialise the report state2.2 =
rs.test = tc; rs.success_code = ai->assoc_number; rs.turns_keyed = ai->assoc_number2; rs.prototype_HTML_file = ai->assoc_file1; rs.relevant_node_ID = ai->assoc_text; rs.stage = 1; rs.first_flag = FALSE; rs.last_flag = FALSE; rs.REPORT_TO = OUT; if (tc->letter_reference >= 1) rs.test_case_letter = (char) (((int) 'A') + tc->letter_reference - 1); else rs.test_case_letter = 0;
- This code is used in §2.
§3. So, then, the following filter is run line by line on a prototype HTML file nominated by the UI app. We will splice in our report.
void Reporter::filter(text_stream *line_text, text_file_position *tfp, void *vrs) { report_state *rs = vrs; text_stream *OUT = rs->REPORT_TO; match_results mr = Regexp::create_mr(); if (Regexp::match(&mr, line_text, U" <!--CONTENT BEGINS-->")) { WRITE("<!--INTEST REPORT BEGINS-->\n"); rs->stage = 2; } else if (Regexp::match(&mr, line_text, U" <!--BANNER BEGINS-->")) rs->stage = 3; else if (Regexp::match(&mr, line_text, U" <!--HEADING BEGINS-->")) { rs->stage = 4; Insert test report header3.1; } else if (Regexp::match(&mr, line_text, U" <!--HEADING ENDS-->")) rs->stage = 5; else if (Regexp::match(&mr, line_text, U" <!--BANNER ENDS-->")) rs->stage = 6; else if (Regexp::match(&mr, line_text, U" <!--PROBLEMS BEGIN-->")) { rs->stage = 7; Insert additional material3.2; } else if (Regexp::match(&mr, line_text, U" <!--PROBLEMS END-->")) rs->stage = 8; else if (Regexp::match(&mr, line_text, U" <!--CONTENT ENDS-->")) { rs->stage = 9; Insert test report footer3.3; WRITE("<!--INTEST REPORT ENDS-->\n"); } else { if ((rs->stage == 1) || (rs->stage == 2) || (rs->stage == 3) || (rs->stage == 5) || (rs->stage == 9) || ((rs->stage == 7) && (rs->success_code == I7_FAILED_OUTCOME)) || ((rs->stage == 7) && (rs->success_code == I6_FAILED_OUTCOME)) ) Filter existing report, adjusting links3.4; } Regexp::dispose_of(&mr); }
§3.1. Insert test report header3.1 =
if ((rs->success_code == PERFECT_OUTCOME) || (rs->success_code == CURSED_OUTCOME)) WRITE("<div class=\"headingpanellayout headingpanelSucceeded\">\n"); else WRITE("<div class=\"headingpanellayout headingpanelFailed\">\n"); WRITE("<div class=\"headingtext\">"); HTML_OPEN_WITH("span", "class=\"headingpaneltext\""); WRITE("Example "); if (rs->test_case_letter) WRITE("%c: ", rs->test_case_letter); WRITE("‘%S’: ", rs->test->test_case_title); switch (rs->success_code) { case WRONG_TRANSCRIPT_OUTCOME: WRITE("Failed"); break; case PERFECT_OUTCOME: WRITE("Succeeded"); break; case CURSED_OUTCOME: WRITE("Partly Succeeded"); break; default: WRITE("Couldn't Test"); break; } WRITE("</span>\n"); WRITE("</div>\n"); WRITE("<div class=\"headingrubric\">"); HTML_OPEN_WITH("span", "class=\"headingpanelrubric\""); switch (rs->success_code) { case I7_FAILED_OUTCOME: WRITE("Problem messages meant it wouldn't translate"); break; case I6_FAILED_OUTCOME: WRITE("Translated but failed to compile in Inform 6"); break; case CURSED_OUTCOME: WRITE("Translated but has no blessed transcript to check against"); break; case WRONG_TRANSCRIPT_OUTCOME: WRITE("Translated but produced the wrong transcript"); break; case PERFECT_OUTCOME: WRITE("Translated and produced the correct transcript"); break; } HTML_CLOSE("span"); WRITE("</div>\n"); WRITE("</div>\n");
- This code is used in §3.
§3.2. Insert additional material3.2 =
WRITE("<p class=\"tightin1\"> " "<a href=\"source:story.ni"); if (rs->test_case_letter) WRITE("?case=%c", rs->test_case_letter); WRITE("#line1\"><img border=0 src=inform:/doc_images/Reveal.png></a> " "Source text"); if ((rs->success_code != I6_FAILED_OUTCOME) && (rs->success_code != I7_FAILED_OUTCOME)) { WRITE(" •"); Insert link to transcript3.2.1; WRITE("Transcript of play (%d turn%s)", rs->turns_keyed, (rs->turns_keyed == 1)?"":"s"); } else { WRITE("<br> \n"); } WRITE("</p>\n"); switch (rs->success_code) { case CURSED_OUTCOME: WRITE("<p class=\"in2\">This translated fine, so " "I could play it. "); if (rs->turns_keyed > 0) WRITE("I automatically typed in %d command%s extracted from the " "TEST ME in the source. ", rs->turns_keyed, (rs->turns_keyed == 1)?"":"s"); else WRITE("The source text didn't have a TEST ME included, so I didn't " "type any commands into it. "); WRITE("(Click"); Insert link to transcript3.2.1; WRITE("to see.)</p>"); WRITE("<p class=\"in2\">But I didn't know what to " "check the transcript against, so I can't say whether it's " "correct. Take a look: if it's right, 'bless' the transcript " "with the tick icons. I will then use that on future tests.</p>\n"); break; case WRONG_TRANSCRIPT_OUTCOME: WRITE("<p class=\"in2\">This translated fine, so " "I could play it. "); if (rs->turns_keyed > 0) WRITE("(I automatically typed in %d command%s extracted from the " "TEST ME in the source.) ", rs->turns_keyed, (rs->turns_keyed == 1)?"":"s"); else WRITE("(The source text didn't have a TEST ME included, so I didn't " "type any commands into it.) "); WRITE("But the resulting text didn't match the " "'blessed' version. (Click"); Insert link to transcript3.2.1; WRITE("to see what went wrong.)</p>"); WRITE("<p class=\"in2\">If you intended this, you " "can 'bless' the new version as better, and I'll compare against " "that in future tests. But if it's just plain wrong, I can't help: " "you have some work to do on the source.</p>"); break; }
- This code is used in §3.
§3.2.1. Insert link to transcript3.2.1 =
WRITE(" <a href='skein:%S", rs->relevant_node_ID); if (rs->test_case_letter) WRITE("?case=%c", rs->test_case_letter); WRITE("'><img border=0 src=inform:/doc_images/Transcript.png></a> ");
- This code is used in §3.2 (three times).
§3.3. Insert test report footer3.3 =
WRITE("<p></p>\n");
- This code is used in §3.
§3.4. Filter existing report, adjusting links3.4 =
WRITE("%S\n", line_text);
- This code is used in §3.
§4. Combine feature. The following implements the -combine command line feature. Essentially it takes a batch of reports made by -report and merges them together.
void Reporter::combine(OUTPUT_STREAM, int count, filename *base_filename) { for (int i = 1; i <= count; i++) { report_state rs; Initialise the report state for combination4.1; TextFiles::read(rs.prototype_HTML_file, FALSE, "can't open test report file", TRUE, &Reporter::combine_filter, NULL, &rs); } }
§4.1. Initialise the report state for combination4.1 =
rs.test = NULL; rs.success_code = -1; pathname *P = Filenames::up(base_filename); TEMPORARY_TEXT(NEWLEAF) Filenames::write_unextended_leafname(NEWLEAF, base_filename); Str::truncate(NEWLEAF, 16); WRITE_TO(NEWLEAF, "-%d.html", i); rs.prototype_HTML_file = Filenames::in(P, NEWLEAF); DISCARD_TEXT(NEWLEAF) rs.relevant_node_ID = NULL; rs.first_flag = FALSE; rs.last_flag = FALSE; if (i == 1) rs.first_flag = TRUE; else if (i == count) rs.last_flag = TRUE; rs.stage = 1; rs.REPORT_TO = OUT; rs.test_case_letter = (char) (((int) 'A') + i - 1);
- This code is used in §4.
void Reporter::combine_filter(text_stream *line_text, text_file_position *tfp, void *vrs) { report_state *rs = vrs; text_stream *OUT = rs->REPORT_TO; match_results mr = Regexp::create_mr(); if (Regexp::match(&mr, line_text, U"<!--INTEST REPORT BEGINS-->")) rs->stage = 2; else if (Regexp::match(&mr, line_text, U"<!--INTEST REPORT ENDS-->")) rs->stage = 3; else if (((rs->stage == 1) && (rs->first_flag)) || ((rs->stage == 3) && (rs->last_flag))) WRITE("%S\n", line_text); else if (rs->stage == 2) { WRITE("%S\n", line_text); } Regexp::dispose_of(&mr); }