To optimise by storing MD5 hashes of known-to-be-correct output.
§1. Hash values for cases. In order to support Delia's hash: command, we need to be able to assign each test case a hash value. This will typically be a short hexadecimal string such as:
64b479d74cd38b887590f139b64ee920
The empty text is considered to mean "no cache value known".
void Hasher::assign_to_case(test_case *tc, text_stream *hash) { tc->known_hash = Str::duplicate(hash); LOGIF(HASHER, "determine: %S = %S\n", tc->test_case_name, tc->known_hash); }
§2. We must also be able to detect whether a case has a given hash value. Two blanks don't make a match, but otherwise they must exactly match, with case sensitivity and all.
int Hasher::compare_hashes(test_case *tc, text_stream *hash) { text_stream *known = tc->known_hash; if ((Str::len(known) == 0) || (Str::len(hash) == 0)) return FALSE; return Str::eq(known, hash); }
§3. Here we extract a single hash value from a one-line file.
void Hasher::read_hash(text_stream *V, filename *F) { Str::clear(V); TextFiles::read(F, FALSE, "can't open md5 hash file", TRUE, &Hasher::detect_hash, NULL, V); } void Hasher::detect_hash(text_stream *line_text, text_file_position *tfp, void *vto) { match_results mr = Regexp::create_mr(); if (Regexp::match(&mr, line_text, U" *(%C+) *")) Str::copy((text_stream *) vto, mr.exp[0]); Regexp::dispose_of(&mr); }
§4. The hash cache. This is the file (for a given project) in which Intest caches all known test hash values between runs.
void Hasher::read_hashes(intest_instructions *args) { filename *H = Globals::to_filename(I"hash_cache"); if (H == NULL) return; TextFiles::read(H, FALSE, NULL, FALSE, &Hasher::detect_hashes, NULL, args); } void Hasher::detect_hashes(text_stream *line_text, text_file_position *tfp, void *vargs) { intest_instructions *args = (intest_instructions *) vargs; match_results mr = Regexp::create_mr(); if (Regexp::match(&mr, line_text, U"(%c*?) = (%C+)%c*")) { test_case *tc = RecipeFiles::find_case(args, mr.exp[0]); if (tc) Hasher::assign_to_case(tc, mr.exp[1]); } Regexp::dispose_of(&mr); }
§5. Once we've finished running, we may know new hashes; here we update the hash cache in the light of that.
void Hasher::write_hashes(void) { filename *H = Globals::to_filename(I"hash_cache"); if (H == NULL) return; text_stream TO_struct; text_stream *TO = &TO_struct; if (STREAM_OPEN_TO_FILE(TO, H, UTF8_ENC) == FALSE) Errors::fatal_with_file("unable to write file", H); LOGIF(HASHER, "Writing hash cache to %f:\n", H); test_case *tc; LOOP_OVER(tc, test_case) if (Str::len(tc->known_hash) > 0) { WRITE_TO(TO, "%S = %S\n", tc->test_case_name, tc->known_hash); LOGIF(HASHER, "write: %S = %S\n", tc->test_case_name, tc->known_hash); } STREAM_CLOSE(TO); }