// write_events_test.cc - Don Yang (uguu.org) // // 06/10/11 #include"write_events.h" #include #include"util.h" namespace { // A single event struct InputEvent { int timestamp, frame, max_frame, type; const char *text; int output_slot, row, column; }; static const int kReplace = AllEvents::CompiledEvent::REPLACE; static const int kAppend = AllEvents::CompiledEvent::APPEND; static const int kDelete = AllEvents::CompiledEvent::DELETE; static const int kCursor = AllEvents::CompiledEvent::CURSOR; static const int kSlowCursor = AllEvents::CompiledEvent::SLOW_CURSOR; // Container to hold input event values class InputEventAdapter { public: explicit InputEventAdapter(const InputEvent *events); ~InputEventAdapter() { DeletePtrElements(&compiled_events_); } inline const AllEvents::CompiledEventList &compiled_events() const { return compiled_events_; } private: AllEvents::CompiledEventList compiled_events_; AllEvents::OutputSlots output_slots_; InputEventAdapter(); }; // Initialize events InputEventAdapter::InputEventAdapter(const InputEvent *events) { for(int i = 0; events[i].timestamp >= 0; i++) { const InputEvent &input = events[i]; AllEvents::CompiledEvent *output = new AllEvents::CompiledEvent; output->timestamp = input.timestamp; output->frame = input.frame; output->max_frame = input.max_frame; output->type = static_cast(input.type); if( input.text != NULL ) output->text = input.text; output->output = output_slots_.insert(output_slots_.end(), make_pair(true, input.output_slot)); output->row = input.row; output->column = input.column; compiled_events_.push_back(output); } } // Read from opened file handle to string static void ReadToString(FILE *file, string *output) { fseek(file, 0, SEEK_SET); char buffer[1024]; output->clear(); for(size_t read_size; (read_size = fread(buffer, 1, sizeof(buffer), file)) != 0;) { output->append(buffer, read_size); if( read_size < sizeof(buffer) ) break; } } ////////////////////////////////////////////////////////////////////// // {{{ Test load operations struct ExpectedEvent { int start, duration, type, slot, text, length, offset, row, column; }; static void TestEmpty() { static const InputEvent kInput[] = { {-1, -1, -1, kCursor, NULL, -1, -1, -1} }; const InputEventAdapter input(kInput); const Animation data(input.compiled_events(), 0, 0); CHECK(data.events().empty(), "Empty test failed"); } static void TestEscape() { static const InputEvent kInput[] = { {1, 0, 1, kReplace, "&", 0, -1, -1}, {1, 0, 1, kReplace, "<", 1, -1, -1}, {1, 0, 1, kReplace, ">", 2, -1, -1}, {1, 0, 1, kReplace, "'", 3, -1, -1}, {1, 0, 1, kReplace, "\"", 4, -1, -1}, {1, 0, 1, kReplace, "\\", 5, -1, -1}, {1, 0, 1, kReplace, "><", 6, -1, -1}, {1, 0, 1, kReplace, "&&", 7, -1, -1}, {1, 0, 1, kReplace, "\t", 8, -1, -1}, {1, 0, 1, kReplace, "<\xdf\xb7>{\xe2\x98\x83}", 9, -1, -1}, {1, 0, 1, kReplace, "(\xf0\x90\x83\xa1)", 10, -1, -1}, {1, 0, 1, kReplace, "[\xfa\x80\x80\x80\x80][\xfd\x80\x80\x80\x80\x80]", 11, -1, -1}, {1, 0, 1, kReplace, "\x80,\xc0\1,\xe0\1\2,\xf0\1\2\3," "\xf8\1\2\3\4,\xfc\1\2\3\4\5!", 12, -1, -1}, {-1, -1, -1, kCursor, NULL, -1, -1, -1} }; static const char *kOutput[] = { "&", "<", ">", "\\'", "\\\"", "\\\\", "><", "&&", "\\x09", "<\\u07F7>{\\u2603}", "(𐃡)", "[�][�]", "\\uFFFD,\\uFFFD,\\uFFFD\\x02,\\uFFFD\\x02\\x03," "\\uFFFD\\x02\\x03\\x04,\\uFFFD\\x02\\x03\\x04\\x05!" }; const InputEventAdapter input(kInput); const Animation data(input.compiled_events(), 13, 13); CHECK(data.events().size() == sizeof(kOutput) / sizeof(kOutput[0]), "Event size mismatch"); int errors = 0; const int string_count = static_cast(data.strings().strings().size()); for(int i = 0; i < static_cast(data.events().size()); i++) { const Animation::Event &event = data.event(i); if( event.text->index < 0 || event.text->index >= string_count ) { printf("ERROR: string index out of bounds for event %d\n", i); errors++; continue; } const StringPool::Item &item = data.strings().item(event.text->index); if( item.data.compare(0, strlen(kOutput[i]), kOutput[i]) != 0 ) { printf("ERROR: string mismatched for event %d: \"%s\" vs \"%s\"\n", i, kOutput[i], item.data.substr(0, strlen(kOutput[i])).c_str()); errors++; } } CHECK(errors == 0, "Escape failed"); } static void TestLoad() { static const InputEvent kInput[] = { {1, 0, 1, kCursor, NULL, -1, 2, 1}, {1, 0, 1, kReplace, "hoge", 0, -1, -1}, {2, 0, 2, kCursor, NULL, -1, 3, 4}, {2, 0, 2, kAppend, "piyo", 0, -1, -1}, {2, 1, 2, kReplace, "fuga", 0, -1, -1}, {4, 1, 4, kReplace, "piyopiyo", 1, -1, -1}, {4, 3, 4, kDelete, NULL, 1, -1, -1}, {5, 0, 1, kCursor, NULL, -1, 4, 4}, {6, 0, 1, kReplace, "\xe3\x81\xbb\xe3\x81\x92", 0, -1, -1}, {7, 0, 1, kSlowCursor, "\xe3\x81\xbb\xe3\x81\x92", 0, -1, 3}, {-1, -1, -1, kCursor, NULL, -1, -1, -1} }; // string table: // 0 = piyopiyo (freq=2) // 1 = U+307B U+3052 // 2 = fuga // 3 = hoge static const ExpectedEvent kOutput[] = { {1000, 1000, kCursor, 0, 0, 0, 0, 2, 1}, {1000, 1500, kReplace, 0, 3, 4, 0, 0, 0}, {2000, 3000, kCursor, 0, 0, 0, 0, 3, 4}, {2000, 500, kAppend, 0, 0, 4, 0, 0, 0}, {2500, 3500, kReplace, 0, 2, 4, 0, 0, 0}, {4250, 500, kReplace, 1, 0, 8, 0, 0, 0}, {4750, 0, kDelete, 1, 0, 0, 0, 0, 0}, {5000, 2000, kCursor, 0, 0, 0, 0, 4, 4}, {6000, 0, kReplace, 0, 1, 2, 0, 0, 0}, {7000, 0, kSlowCursor, 0, 0, 1, 1, 0, 3}, {-1, -1, -1, -1, -1, -1, -1, -1} }; const InputEventAdapter input(kInput); const Animation data(input.compiled_events(), 2, 2); int errors = 0; for(int i = 0; kOutput[i].start >= 0; i++) { #define CHECK_FIELD(x) \ if( kOutput[i].x != data.event(i).x ) \ { \ printf("ERROR: expected[%d]." #x " mismatched: %d vs %d\n", \ i, kOutput[i].x, static_cast(data.event(i).x)); \ errors++; \ } CHECK_FIELD(start); CHECK_FIELD(duration); CHECK_FIELD(type); CHECK_FIELD(slot); CHECK_FIELD(length); CHECK_FIELD(offset); CHECK_FIELD(row); CHECK_FIELD(column); #undef CHECK_FIELD if( kOutput[i].text != data.event(i).text->index ) { printf("ERROR: expected[%d].text mismatched: %d vs %d\n", i, kOutput[i].text, data.event(i).text->index); errors++; } } CHECK(errors == 0, "Load failed"); } // }}} ////////////////////////////////////////////////////////////////////// // {{{ Test write operations static void TestWrite() { // Load events static const InputEvent kInput[] = { {1, 0, 1, kCursor, NULL, -1, 2, 1}, {1, 0, 1, kReplace, "hoge", 0, -1, -1}, {2, 0, 1, kReplace, "piyo", 0, -1, -1}, {-1, -1, -1, kCursor, NULL, -1, -1, -1} }; const InputEventAdapter input(kInput); const Animation data(input.compiled_events(), 3, 3); // Write output and read it back FILE *file = tmpfile(); CHECK(file != NULL, "error creating temporary file"); data.Write(file); string text; ReadToString(file, &text); fclose(file); // Most of the output text would have came from write_template.html, // which is tested separately. Here we just check the two key // pieces of expected data. CHECK(text.find("[\"hoge\",\"piyo\"];") != string::npos, "Missing string table"); CHECK(text.find("[1000,0,0,0,2,1," "0,1000,1,0,0,4," "1000,0,1,0,1,4];") != string::npos, "Missing event list"); } // Test write with some escaped strings static void TestWriteEscape() { // Load events static const InputEvent kInput[] = { {1, 0, 1, kCursor, NULL, -1, 1, 1}, {1, 0, 1, kReplace, "\"<", 0, -1, -1}, {2, 0, 1, kCursor, NULL, -1, 1, 1}, {2, 0, 1, kReplace, "\"", 0, -1, -1}, {3, 0, 1, kCursor, NULL, -1, 1, 1}, {3, 0, 1, kAppend, ">", 0, -1, -1}, {-1, -1, -1, kCursor, NULL, -1, -1, -1} }; const InputEventAdapter input(kInput); const Animation data(input.compiled_events(), 3, 3); // Write output and read it back FILE *file = tmpfile(); CHECK(file != NULL, "error creating temporary file"); data.Write(file); string text; ReadToString(file, &text); fclose(file); // Check expected strings CHECK(text.find("\"\\\"<\",\">\"") != string::npos, "Missing string table"); CHECK(text.find("[1000,1000,0,0,1,1," "0,1000,1,0,0,5," "1000,1000,0,0,1,1," "0,0,1,0,0,1," "1000,0,0,0,1,1," "0,0,2,0,1,4];") != string::npos, "Missing event list"); } // }}} } // namespace int main() { TestEmpty(); TestEscape(); TestLoad(); TestWrite(); TestWriteEscape(); return 0; }