// load_events_test.cc - Don Yang (uguu.org) // // 05/28/11 #include"load_events.h" #include"util.h" namespace { // Load data from string static void LoadInputFromString(const char *input, AllEvents *output) { FILE *file = tmpfile(); CHECK(file != NULL, "error creating temporary file"); fputs(input, file); fseek(file, 0, SEEK_SET); output->Load(file); fclose(file); } ////////////////////////////////////////////////////////////////////// // {{{ Load() // Expected event type struct ExpectedRawEvent { int row, column, frame, timestamp, line, size; const char *text; }; // Load text data from string, and check that all output records matched. // Returns true on success. static bool RunLoadTestCase(const char *input, const ExpectedRawEvent *expected) { // Load data AllEvents events; LoadInputFromString(input, &events); // Check values int errors = 0; for(size_t i = 0; expected[i].row >= 0; i++) { if( i >= events.events().size() ) { printf("ERROR: Missing expected events (got %d events)\n", static_cast(events.events().size())); errors++; break; } #define CHECK_FIELD(x) \ if( expected[i].x != events.event(i).x ) \ { \ printf("ERROR: expected[%d]." #x " mismatched: %d vs %d\n", \ static_cast(i), \ expected[i].x, events.event(i).x); \ errors++; \ } CHECK_FIELD(row); CHECK_FIELD(column); CHECK_FIELD(timestamp); CHECK_FIELD(frame); CHECK_FIELD(line); CHECK_FIELD(size); #undef CHECK_FIELD if( expected[i].text != NULL && expected[i].text != events.event(i).text ) { printf("ERROR: expected[%d].text mismatched: \"%s\" vs \"%s\"\n", static_cast(i), expected[i].text, events.event(i).text.c_str()); errors++; } } return errors == 0; } // Run all test cases for Load() static void TestLoad() { static const char kEmptyInput[] = ""; static const ExpectedRawEvent kEmptyOutput[] = { {-1, -1, -1, -1, -1, -1, NULL} }; CHECK(RunLoadTestCase(kEmptyInput, kEmptyOutput), "Empty"); static const char kSimpleInput[] = "Y2X3F4T5\n" "L6E7=text\n" "Y2X4F6T8\n" "L7E7=text2\n" "L8E9=text3\n" "L9E9=\n"; static const ExpectedRawEvent kSimpleOutput[] = { {1, 2, 4, 5, -1, 0, NULL}, {1, 2, 4, 5, 5, 7, "text"}, {1, 3, 6, 8, -1, 0, NULL}, {1, 3, 6, 8, 6, 7, "text2"}, {1, 3, 6, 8, 7, 9, "text3"}, {1, 3, 6, 8, 8, 9, ""}, {-1, -1, -1, -1, -1, -1, NULL} }; CHECK(RunLoadTestCase(kSimpleInput, kSimpleOutput), "Simple test"); static const char kSimpleInputCRLF[] = "Y2X3F4T5\r\n" "L6E7=text\r\n" "Y2X4F6T8\r\n" "L7E7=text2\r\n" "L8E9=text3\r\n" "L9E9=\r\n"; CHECK(RunLoadTestCase(kSimpleInputCRLF, kSimpleOutput), "Simple test (CR-LF)"); static const char kDeltaInput[] = "Y1X1F1T1\n" "Y1X1F1T1\n" // dropped "Y1X2F9T9\n" "L2E2=\n" "L3E3=\n" "L2E2=text1\n" "L3E3=text2\n" "L2E3=text1\n" // dropped "L2E2=text1\n" "Y2X2F1T1\n" // updated base timestamp "Y2X2F1T1\n" // dropped "Y2X3F2T2\n"; // dropped static const ExpectedRawEvent kDeltaOutput[] = { {0, 0, 1, 1, -1, 0, NULL}, {0, 1, 9, 9, -1, 0, NULL}, {0, 1, 9, 9, 1, 2, ""}, {0, 1, 9, 9, 2, 3, ""}, {0, 1, 9, 9, 1, 2, "text1"}, {0, 1, 9, 9, 2, 3, "text2"}, {0, 1, 9, 9, 1, 2, "text1"}, {1, 1, 10, 10, -1, 0, NULL}, {1, 2, 11, 11, -1, 0, NULL}, {-1, -1, -1, -1, -1, -1, NULL} }; CHECK(RunLoadTestCase(kDeltaInput, kDeltaOutput), "Delta encoding"); static const char kShrunkInput[] = "Y1X1F1T1\n" "L1E2=hoge\n" "L2E2=piyo\n" "Y1X1F2T2\n" "L1E1=hoge\n" "Y1X1F3T3\n" "L1E2=hoge\n" "L2E2=piyo\n"; static const ExpectedRawEvent kShrunkOutput[] = { {0, 0, 1, 1, -1, 0, NULL}, {0, 0, 1, 1, 0, 2, "hoge"}, {0, 0, 1, 1, 1, 2, "piyo"}, {0, 0, 2, 2, -1, 0, NULL}, {0, 0, 2, 2, 0, 1, "hoge"}, {0, 0, 3, 3, -1, 0, NULL}, {0, 0, 3, 3, 0, 2, "hoge"}, {0, 0, 3, 3, 1, 2, "piyo"}, {-1, -1, -1, -1, -1, -1, NULL} }; CHECK(RunLoadTestCase(kShrunkInput, kShrunkOutput), "Shrunk file"); static const char kTimestampInput[] = "Y1X1F1T1\n" "Y2X1F2T1\n" "Y3X1F3T1\n" "Y4X1F2T1\n" "Y1X2F3T2\n"; static const ExpectedRawEvent kTimestampOutput[] = { {0, 0, 1, 1, -1, 0, NULL}, {1, 0, 2, 1, -1, 0, NULL}, {2, 0, 3, 1, -1, 0, NULL}, {3, 0, 4, 2, -1, 0, NULL}, {0, 1, 5, 3, -1, 0, NULL}, {-1, -1, -1, -1, -1, -1, NULL} }; CHECK(RunLoadTestCase(kTimestampInput, kTimestampOutput), "Timestamps"); } // }}} ////////////////////////////////////////////////////////////////////// // {{{ CompressIdleTime() static void TestCompressIdleTime() { static const char kInput[] = "Y1X1F0T1\n" "Y2X1F1T2\n" "Y3X1F2T7\n" "Y4X1F3T17\n" "Y5X1F0T1\n" "Y6X1F1T8\n"; static const int kOutputFrameTimestamps[][2] = { {0, 0}, {1, 1}, {2, 6}, {3, 11}, {4, 12}, {5, 17}, {-1, -1} }; AllEvents events; LoadInputFromString(kInput, &events); events.CompressIdleTime(5); int errors = 0; for(int i = 0; kOutputFrameTimestamps[i][0] >= 0; i++) { if( kOutputFrameTimestamps[i][0] != events.event(i).frame ) { printf("ERROR: expected[%d].frame mismatched: %d vs %d\n", i, kOutputFrameTimestamps[i][0], events.event(i).frame); errors++; } if( kOutputFrameTimestamps[i][1] != events.event(i).timestamp ) { printf("ERROR: expected[%d].timestamp mismatched: %d vs %d\n", i, kOutputFrameTimestamps[i][1], events.event(i).timestamp); errors++; } } CHECK(errors == 0, "CompressIdleTime failed"); } // }}} ////////////////////////////////////////////////////////////////////// // {{{ CompileEvents() 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; // Expected event type struct ExpectedCompiledEvent { int timestamp, frame, max_frame, type; const char *text; int output_slot, row, column; }; // Load text data from string, compile events, and check that output // are as expected. Returns true on success. static bool RunCompileEventsTestCase(const char *input, const ExpectedCompiledEvent *expected) { // Load and compile AllEvents events; LoadInputFromString(input, &events); events.CompileEvents(); const ExpectedCompiledEvent *p = expected; AllEvents::CompiledEventList::const_iterator q = events.compiled_events().begin(); int errors = 0; for(int i = 0; q != events.compiled_events().end(); ++i, ++p, ++q) { if( p->timestamp < 0 ) { puts("ERROR: got extra events"); return false; } const AllEvents::CompiledEvent &actual = **q; #define CHECK_FIELD(x) \ if( p->x != actual.x ) \ { \ printf("ERROR: expected[%d]." #x " mismatched: %d vs %d\n", \ i, p->x, actual.x); \ errors++; \ } CHECK_FIELD(timestamp); CHECK_FIELD(frame); CHECK_FIELD(max_frame); CHECK_FIELD(type); if( p->type != actual.type ) continue; if( p->type == AllEvents::CompiledEvent::REPLACE || p->type == AllEvents::CompiledEvent::APPEND || p->type == AllEvents::CompiledEvent::DELETE || p->type == AllEvents::CompiledEvent::SLOW_CURSOR ) { if( p->output_slot != actual.output->second ) { printf("ERROR: expected[%d].output_slot mismatched: %d vs %d\n", i, p->output_slot, actual.output->second); errors++; } if( p->text != actual.text ) { printf("ERROR: expected[%d].text mismatched: \"%s\" vs \"%s\"\n", i, p->text, actual.text.c_str()); errors++; } } if( p->type == AllEvents::CompiledEvent::CURSOR || p->type == AllEvents::CompiledEvent::SLOW_CURSOR ) { CHECK_FIELD(row); CHECK_FIELD(column); } #undef CHECK_FIELD } if( p->timestamp >= 0 ) { puts("ERROR: missing expected events"); return false; } return errors == 0; } // Test cursor movement and timing events static void TestCursor() { static const char kEmptyInput[] = ""; static const ExpectedCompiledEvent kEmptyOutput[] = { {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kEmptyInput, kEmptyOutput), "Empty test"); static const char kMoveCursorInput[] = "Y1X1F0T0\n" "Y2X1F1T1\n" "Y1X3F2T2\n" "Y1X5F3T3\n"; static const ExpectedCompiledEvent kMoveCursorOutput[] = { {0, 0, 1, kCursor, NULL, -1, 0, 0}, {1, 0, 1, kCursor, NULL, -1, 1, 0}, {2, 0, 1, kCursor, NULL, -1, 0, 2}, {3, 0, 1, kCursor, NULL, -1, 0, 4}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kMoveCursorInput, kMoveCursorOutput), "Move cursor"); static const char kTimestampInput[] = "Y1X1F0T0\n" "Y1X2F1T0\n" "Y2X1F2T1\n" "Y2X2F3T1\n" "Y2X3F5T1\n" "Y3X1F6T2\n" "Y3X2F7T2\n"; static const ExpectedCompiledEvent kTimestampOutput[] = { {0, 0, 2, kCursor, NULL, -1, 0, 0}, {0, 1, 2, kCursor, NULL, -1, 0, 1}, {1, 0, 4, kCursor, NULL, -1, 1, 0}, {1, 1, 4, kCursor, NULL, -1, 1, 1}, {1, 3, 4, kCursor, NULL, -1, 1, 2}, {2, 0, 2, kCursor, NULL, -1, 2, 0}, {2, 1, 2, kCursor, NULL, -1, 2, 1}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kTimestampInput, kTimestampOutput), "Fractional time"); } // Test cursor translation with respect to UTF-8 input static void TestUnicode() { static const char kInput[] = "Y1X1F0T0\n" "L1E3=\xe3\x81\xbb\xe3\x81\x92\n" "L2E3=\xef\xbe\x8b\xef\xbe\x9f\xef\xbe\x96\n" "L3E3=fu\xef\xbd\xb6\xef\xbe\x9e\xef\xbd\x9e\n" "Y1X1F1T1\n" "Y1X4F2T2\n" "Y2X1F3T3\n" "Y2X4F4T4\n" "Y2X7F5T5\n" "Y2X10F6T6\n" "Y3X1F7T7\n" "Y3X2F8T8\n" "Y3X3F9T9\n" "Y3X6F10T10\n" "Y3X9F11T11\n" "L1E2=\xe3\x81\xbb\xe3\x81\x92\n" "L2E2=fu\xef\xbd\xb6\xef\xbe\x9e\xef\xbd\x9e\n" "Y2X1F12T12\n" "Y2X2F13T13\n" "Y2X3F14T14\n" "Y2X6F15T15\n" "Y2X9F16T16\n" "Y1X4F17T17\n"; static const ExpectedCompiledEvent kOutput[] = { {0, 0, 1, kCursor, NULL, -1, 0, 0}, {0, 0, 1, kReplace, "\xe3\x81\xbb\xe3\x81\x92", 0, 0, 0}, {0, 0, 1, kReplace, "\xef\xbe\x8b\xef\xbe\x9f\xef\xbe\x96", 1,0,0}, {0, 0, 1, kReplace, "fu\xef\xbd\xb6\xef\xbe\x9e\xef\xbd\x9e", 2,0,0}, {1, 0, 1, kSlowCursor, "\xe3\x81\xbb", 0, 0, 0}, {2, 0, 1, kSlowCursor, "\xe3\x81\xbb\xe3\x81\x92", 0, 0, 3}, {3, 0, 1, kSlowCursor, "\xef\xbe\x8b", 1, 1, 0}, {4, 0, 1, kSlowCursor, "\xef\xbe\x8b\xef\xbe\x9f", 1, 1, 3}, {5, 0, 1, kSlowCursor, "\xef\xbe\x8b\xef\xbe\x9f\xef\xbe\x96", 1,1,6}, {6, 0, 1, kSlowCursor, "\xef\xbe\x8b\xef\xbe\x9f\xef\xbe\x96 ", 1,1,9}, {7, 0, 1, kCursor, NULL, -1, 2, 0}, {8, 0, 1, kCursor, NULL, -1, 2, 1}, {9, 0, 1, kSlowCursor, "fu\xef\xbd\xb6", 2, 2, 2}, {10, 0, 1, kSlowCursor, "fu\xef\xbd\xb6\xef\xbe\x9e", 2, 2, 5}, {11, 0, 1, kSlowCursor, "fu\xef\xbd\xb6\xef\xbe\x9e\xef\xbd\x9e", 2,2,8}, {11, 0, 1, kDelete, "", 1, 0, 0}, {12, 0, 1, kCursor, NULL, -1, 1, 0}, {13, 0, 1, kCursor, NULL, -1, 1, 1}, {14, 0, 1, kSlowCursor, "fu\xef\xbd\xb6", 2, 1, 2}, {15, 0, 1, kSlowCursor, "fu\xef\xbd\xb6\xef\xbe\x9e", 2, 1, 5}, {16, 0, 1, kSlowCursor, "fu\xef\xbd\xb6\xef\xbe\x9e\xef\xbd\x9e", 2,1,8}, {17, 0, 1, kSlowCursor, "\xe3\x81\xbb\xe3\x81\x92", 0, 0, 3}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kInput, kOutput), "Unicode"); } // Test insert snapshots static void TestInsert() { static const char kSingleLineInput[] = "Y1X1F0T0\n" "L1E1=\n"; static const ExpectedCompiledEvent kSingleLineOutput[] = { {0, 0, 1, kCursor, NULL, -1, 0, 0}, {0, 0, 1, kReplace, "", 0, 0, 0}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kSingleLineInput, kSingleLineOutput), "Insert single line"); static const char kTwoLineInput[] = "Y1X1F0T0\n" "L1E2=hoge\n" "L2E2=piyo\n"; static const ExpectedCompiledEvent kTwoLineOutput[] = { {0, 0, 1, kCursor, NULL, -1, 0, 0}, {0, 0, 1, kReplace, "hoge", 0, 0, 0}, {0, 0, 1, kReplace, "piyo", 1, 0, 0}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kTwoLineInput, kTwoLineOutput), "Insert two lines"); static const char kAppendInput[] = "Y1X1F0T0\n" "L1E2=hoge\n" "L2E2=piyo\n" "Y1X1F1T1\n" "L3E3=fuga\n" "Y1X1F2T2\n" "L4E5=hogera\n" "L5E5=piyopiyo\n"; static const ExpectedCompiledEvent kAppendOutput[] = { {0, 0, 1, kCursor, NULL, -1, 0, 0}, {0, 0, 1, kReplace, "hoge", 0, 0, 0}, {0, 0, 1, kReplace, "piyo", 1, 0, 0}, {1, 0, 1, kCursor, NULL, -1, 0, 0}, {1, 0, 1, kReplace, "fuga", 2, 0, 0}, {2, 0, 1, kCursor, NULL, -1, 0, 0}, {2, 0, 1, kReplace, "hogera", 3, 0, 0}, {2, 0, 1, kReplace, "piyopiyo", 4, 0, 0}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kAppendInput, kAppendOutput), "Append"); static const char kPrependInput[] = "Y1X1F0T0\n" "L1E1=piyopiyo\n" "Y1X1F1T1\n" "L1E2=fuga\n" "L2E2=piyopiyo\n" "Y1X1F2T2\n" "L1E4=hogehoge\n" "L2E4=piyo\n" "L1E4=hoge\n" "L3E4=fuga\n" "L4E4=piyopiyo\n"; static const ExpectedCompiledEvent kPrependOutput[] = { {0, 0, 1, kCursor, NULL, -1, 0, 0}, {0, 0, 1, kReplace, "piyopiyo", 3, 0, 0}, {1, 0, 1, kCursor, NULL, -1, 0, 0}, {1, 0, 1, kReplace, "fuga", 2, 0, 0}, {2, 0, 1, kCursor, NULL, -1, 0, 0}, {2, 0, 1, kReplace, "hoge", 0, 0, 0}, {2, 0, 1, kReplace, "piyo", 1, 0, 0}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kPrependInput, kPrependOutput), "Prepend"); static const char kInterleaveInput[] = "Y1X1F0T0\n" "L1E2=piyo\n" "L2E2=hogehoge\n" "Y1X1F1T1\n" "L1E4=hoge\n" "L2E4=piyo\n" "L3E4=fuga\n" "L4E4=hogehoge\n" "Y1X1F2T2\n" "L1E6=hoge\n" "L2E6=piyo\n" "L3E6=fuga\n" "L4E6=hogera\n" "L5E6=hogehoge\n" "L6E6=piyopiyo\n"; static const ExpectedCompiledEvent kInterleaveOutput[] = { {0, 0, 1, kCursor, NULL, -1, 0, 0}, {0, 0, 1, kReplace, "piyo", 1, 0, 0}, {0, 0, 1, kReplace, "hogehoge", 4, 0, 0}, {1, 0, 1, kCursor, NULL, -1, 0, 0}, {1, 0, 1, kReplace, "hoge", 0, 0, 0}, {1, 0, 1, kReplace, "fuga", 2, 0, 0}, {2, 0, 1, kCursor, NULL, -1, 0, 0}, {2, 0, 1, kReplace, "hogera", 3, 0, 0}, {2, 0, 1, kReplace, "piyopiyo", 5, 0, 0}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kInterleaveInput, kInterleaveOutput), "Interleave"); } // Test append operations static void TestAppend() { static const char kAppendInput[] = "Y1X1F0T0\n" "L1E2=\n" "L2E2=\n" "Y1X1F1T1\n" "L2E2=p\n" "Y1X1F2T2\n" "L1E2=ho\n" "Y1X1F3T3\n" "L1E2=hoge\n" "Y1X1F4T4\n" "L2E2=pi\n" "Y1X1F5T5\n" "L2E2=piyo\n" "L1E2=fuga\n"; static const ExpectedCompiledEvent kAppendOutput[] = { {0, 0, 1, kCursor, NULL, -1, 0, 0}, {0, 0, 1, kReplace, "", 0, 0, 0}, {0, 0, 1, kReplace, "", 1, 0, 0}, {1, 0, 1, kCursor, NULL, -1, 0, 0}, {1, 0, 1, kReplace, "p", 1, 0, 0}, {2, 0, 1, kCursor, NULL, -1, 0, 0}, {2, 0, 1, kReplace, "ho", 0, 0, 0}, {3, 0, 1, kCursor, NULL, -1, 0, 0}, {3, 0, 1, kAppend, "ge", 0, 0, 0}, {4, 0, 1, kCursor, NULL, -1, 0, 0}, {4, 0, 1, kAppend, "i", 1, 0, 0}, {5, 0, 1, kCursor, NULL, -1, 0, 0}, {5, 0, 1, kReplace, "fuga", 0, 0, 0}, {5, 0, 1, kAppend, "yo", 1, 0, 0}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kAppendInput, kAppendOutput), "Append line"); } // Test delete operations static void TestDelete() { static const char kDeleteTailInput[] = "Y1X1F0T0\n" "L1E4=hoge\n" "L2E4=piyo\n" "L3E4=fuga\n" "L4E4=piyopiyo\n" "Y1X1F1T1\n" "L1E3=hoge\n" "L2E3=piyo\n" "L3E3=fuga\n" "Y1X1F2T2\n" "L1E1=hoge\n"; static const ExpectedCompiledEvent kDeleteTailOutput[] = { {0, 0, 1, kCursor, NULL, -1, 0, 0}, {0, 0, 1, kReplace, "hoge", 0, 0, 0}, {0, 0, 1, kReplace, "piyo", 1, 0, 0}, {0, 0, 1, kReplace, "fuga", 2, 0, 0}, {0, 0, 1, kReplace, "piyopiyo", 3, 0, 0}, {1, 0, 1, kCursor, NULL, -1, 0, 0}, {1, 0, 1, kDelete, "", 3, 0, 0}, {2, 0, 1, kCursor, NULL, -1, 0, 0}, {2, 0, 1, kDelete, "", 2, 0, 0}, {2, 0, 1, kDelete, "", 1, 0, 0}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kDeleteTailInput, kDeleteTailOutput), "Delete tail"); static const char kDeleteHeadInput[] = "Y1X1F0T0\n" "L1E4=hoge\n" "L2E4=piyo\n" "L3E4=fuga\n" "L4E4=piyopiyo\n" "Y1X1F1T1\n" "L1E3=piyo\n" "L2E3=fuga\n" "L3E3=piyopiyo\n" "Y1X1F2T2\n" "L1E1=piyopiyo\n"; static const ExpectedCompiledEvent kDeleteHeadOutput[] = { {0, 0, 1, kCursor, NULL, -1, 0, 0}, {0, 0, 1, kReplace, "hoge", 0, 0, 0}, {0, 0, 1, kReplace, "piyo", 1, 0, 0}, {0, 0, 1, kReplace, "fuga", 2, 0, 0}, {0, 0, 1, kReplace, "piyopiyo", 3, 0, 0}, {1, 0, 1, kCursor, NULL, -1, 0, 0}, {1, 0, 1, kDelete, "", 0, 0, 0}, {2, 0, 1, kCursor, NULL, -1, 0, 0}, {2, 0, 1, kDelete, "", 1, 0, 0}, {2, 0, 1, kDelete, "", 2, 0, 0}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kDeleteHeadInput, kDeleteHeadOutput), "Delete head"); static const char kDeleteMiddleInput[] = "Y1X1F0T0\n" "L1E4=hoge\n" "L2E4=piyo\n" "L3E4=fuga\n" "L4E4=piyopiyo\n" "Y1X1F1T1\n" "L1E2=hoge\n" "L2E2=piyopiyo\n"; static const ExpectedCompiledEvent kDeleteMiddleOutput[] = { {0, 0, 1, kCursor, NULL, -1, 0, 0}, {0, 0, 1, kReplace, "hoge", 0, 0, 0}, {0, 0, 1, kReplace, "piyo", 1, 0, 0}, {0, 0, 1, kReplace, "fuga", 2, 0, 0}, {0, 0, 1, kReplace, "piyopiyo", 3, 0, 0}, {1, 0, 1, kCursor, NULL, -1, 0, 0}, {1, 0, 1, kDelete, "", 1, 0, 0}, {1, 0, 1, kDelete, "", 2, 0, 0}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kDeleteMiddleInput, kDeleteMiddleOutput), "Delete middle"); static const char kInterleaveInput[] = "Y1X1F0T0\n" "L1E4=hoge\n" "L2E4=piyo\n" "L3E4=fuga\n" "L4E4=piyopiyo\n" "Y1X1F1T1\n" "L1E2=hoge\n" "L2E2=fuga\n"; static const ExpectedCompiledEvent kInterleaveOutput[] = { {0, 0, 1, kCursor, NULL, -1, 0, 0}, {0, 0, 1, kReplace, "hoge", 0, 0, 0}, {0, 0, 1, kReplace, "piyo", 1, 0, 0}, {0, 0, 1, kReplace, "fuga", 2, 0, 0}, {0, 0, 1, kReplace, "piyopiyo", 3, 0, 0}, {1, 0, 1, kCursor, NULL, -1, 0, 0}, {1, 0, 1, kDelete, "", 1, 0, 0}, {1, 0, 1, kDelete, "", 3, 0, 0}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kInterleaveInput, kInterleaveOutput), "Interleave"); // Pathological resize static const char kResizeInput[] = "Y1X1F0T0\n" "L1E2=hoge\n" "L2E2=piyo\n" "L1E1=hoge\n"; static const ExpectedCompiledEvent kResizeOutput[] = { {0, 0, 1, kCursor, NULL, -1, 0, 0}, {0, 0, 1, kReplace, "hoge", 0, 0, 0}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kResizeInput, kResizeOutput), "Resize"); } // Verify that output slots can be recycled static void TestRecycle() { static const char kOneLineInput[] = "Y1X1F0T0\n" "L1E2=hoge\n" "L2E2=piyo\n" "Y1X1F1T1\n" "L1E1=hoge\n" "Y1X1F2T2\n" "L2E2=fuga\n"; static const ExpectedCompiledEvent kOneLineOutput[] = { {0, 0, 1, kCursor, NULL, -1, 0, 0}, {0, 0, 1, kReplace, "hoge", 0, 0, 0}, {0, 0, 1, kReplace, "piyo", 1, 0, 0}, {1, 0, 1, kCursor, NULL, -1, 0, 0}, {1, 0, 1, kDelete, "", 1, 0, 0}, {2, 0, 1, kCursor, NULL, -1, 0, 0}, {2, 0, 1, kReplace, "fuga", 1, 0, 0}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kOneLineInput, kOneLineOutput), "Recycle one line"); static const char kTwoLineInput[] = "Y1X1F0T0\n" "L1E4=hoge\n" "L2E4=piyo\n" "L3E4=fuga\n" "L4E4=piyopiyo\n" "Y1X1F1T1\n" "L1E2=hoge\n" "L2E2=piyopiyo\n" "Y1X1F2T2\n" "L1E3=hoge\n" "L2E3=fuga\n" "L3E3=piyopiyo\n" "Y1X1F3T3\n" "L1E4=hoge\n" "L2E4=fuga\n" "L3E4=piyo\n" "L4E4=piyopiyo\n"; static const ExpectedCompiledEvent kTwoLineOutput[] = { {0, 0, 1, kCursor, NULL, -1, 0, 0}, {0, 0, 1, kReplace, "hoge", 0, 0, 0}, {0, 0, 1, kReplace, "piyo", 1, 0, 0}, {0, 0, 1, kReplace, "fuga", 2, 0, 0}, {0, 0, 1, kReplace, "piyopiyo", 4, 0, 0}, {1, 0, 1, kCursor, NULL, -1, 0, 0}, {1, 0, 1, kDelete, "", 1, 0, 0}, {1, 0, 1, kDelete, "", 2, 0, 0}, {2, 0, 1, kCursor, NULL, -1, 0, 0}, {2, 0, 1, kReplace, "fuga", 2, 0, 0}, {3, 0, 1, kCursor, NULL, -1, 0, 0}, {3, 0, 1, kReplace, "piyo", 3, 0, 0}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kTwoLineInput, kTwoLineOutput), "Delete two lines"); } // Check that optimization drops events correctly static void TestOptimizeEvents() { static const char kAppendAppendInput[] = "Y1X1F1T1\n" "L1E1=hoge\n" "Y1X2F1T1\n" "L1E1=hogepiyo\n" "Y1X3F1T1\n" "L1E1=hogepiyofuga\n"; static const ExpectedCompiledEvent kAppendAppendOutput[] = { {1, 0, 1, kReplace, "hoge", 0, 0, 0}, {1, 0, 1, kAppend, "piyo", 0, 0, 0}, {1, 0, 1, kCursor, NULL, -1, 0, 2}, {1, 0, 1, kAppend, "fuga", 0, 0, 0}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kAppendAppendInput, kAppendAppendOutput), "Append append"); static const char kAppendReplaceInput[] = "Y1X1F1T1\n" "L1E1=hoge\n" "Y1X2F1T1\n" "L1E1=hogepiyo\n" "Y1X3F1T1\n" "L1E1=fuga\n"; static const ExpectedCompiledEvent kAppendReplaceOutput[] = { {1, 0, 1, kCursor, NULL, -1, 0, 2}, {1, 0, 1, kReplace, "fuga", 0, 0, 0}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kAppendReplaceInput, kAppendReplaceOutput), "Append replace"); static const char kReplaceAppendInput[] = "Y1X1F1T1\n" "L1E1=hoge\n" "Y1X2F1T1\n" "L1E1=piyo\n" "Y1X3F1T1\n" "L1E1=piyofuga\n"; static const ExpectedCompiledEvent kReplaceAppendOutput[] = { {1, 0, 1, kReplace, "piyo", 0, 0, 0}, {1, 0, 1, kCursor, NULL, -1, 0, 2}, {1, 0, 1, kAppend, "fuga", 0, 0, 0}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kReplaceAppendInput, kReplaceAppendOutput), "Replace append"); static const char kReplaceReplaceInput[] = "Y1X1F1T1\n" "L1E1=hoge\n" "Y1X2F1T1\n" "L1E1=piyo\n" "Y1X3F1T1\n" "L1E1=fuga\n"; static const ExpectedCompiledEvent kReplaceReplaceOutput[] = { {1, 0, 1, kCursor, NULL, -1, 0, 2}, {1, 0, 1, kReplace, "fuga", 0, 0, 0}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kReplaceReplaceInput, kReplaceReplaceOutput), "Replace replace"); static const char kReplaceDeleteInput[] = "Y1X1F1T1\n" "L1E2=hoge\n" "L2E2=piyo\n" "Y1X2F1T1\n" "L1E2=hoge\n" "L2E2=fuga\n" "Y1X3F1T1\n" "L1E1=hoge\n"; static const ExpectedCompiledEvent kReplaceDeleteOutput[] = { {1, 0, 1, kReplace, "hoge", 0, 0, 0}, {1, 0, 1, kCursor, NULL, -1, 0, 2}, {1, 0, 1, kDelete, "", 1, 0, 0}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kReplaceDeleteInput, kReplaceDeleteOutput), "Replace delete"); static const char kAppendDeleteInput[] = "Y1X1F1T1\n" "L1E2=hoge\n" "L2E2=piyo\n" "Y1X2F1T1\n" "L1E2=hoge\n" "L2E2=piyofuga\n" "Y1X3F1T1\n" "L1E1=hoge\n"; static const ExpectedCompiledEvent kAppendDeleteOutput[] = { {1, 0, 1, kReplace, "hoge", 0, 0, 0}, {1, 0, 1, kCursor, NULL, -1, 0, 2}, {1, 0, 1, kDelete, "", 1, 0, 0}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kAppendDeleteInput, kAppendDeleteOutput), "Append delete"); static const char kDeleteReplaceInput[] = "Y1X1F1T1\n" "L1E2=hoge\n" "L2E2=piyo\n" "Y1X2F1T1\n" "L1E1=hoge\n" "Y1X3F1T1\n" "L1E2=hoge\n" "L2E2=fuga\n"; static const ExpectedCompiledEvent kDeleteReplaceOutput[] = { {1, 0, 1, kReplace, "hoge", 0, 0, 0}, {1, 0, 1, kCursor, NULL, -1, 0, 2}, {1, 0, 1, kReplace, "fuga", 1, 0, 0}, {-1, -1, -1, -1, NULL, -1, -1, -1} }; CHECK(RunCompileEventsTestCase(kDeleteReplaceInput, kDeleteReplaceOutput), "Delete replace"); } // Test with randomized input. We won't be checking the output here, the // intent is to verify that the parser will not crash with random input. // http://en.wikipedia.org/wiki/Fuzz_testing static void TestRandomInput() { // Use fixed seed to make the test deterministic srand(42); // Write file FILE *file = tmpfile(); CHECK(file != NULL, "error creating temporary file"); vector lines; // Simulate multiple recording cycles for(int reset_count = 0; reset_count < 3; reset_count++) { #define RANDOM_INT(x) \ (static_cast(((x) * static_cast(rand())) / RAND_MAX)) #define APPEND_RANDOM_STRING(x) \ const int append_length = RANDOM_INT(8) + 1; \ for(int i = 0; i < append_length; i++) \ (x).push_back(RANDOM_INT(2) == 0 ? '0' : '1') int timestamp = 0; for(int frame = 0; frame < 10000; frame++) { // Output cursor+timestamp event. The cursor is really treated // as a separate stream, so we feed in random cursor values // that have no relation to the text events. We also throw in // some negative values to check that those are dropped. const int row = RANDOM_INT(10) - 2; const int column = RANDOM_INT(10) - 2; fprintf(file, "Y%dX%dF%dT%d\n", row, column, frame, timestamp); // Output edit events const int event_count = RANDOM_INT(4); for(int event = 0; event < event_count; event++) { const int type = RANDOM_INT(4); const int edit_row = RANDOM_INT(lines.size() + 8) - 2; const int line_count = static_cast(lines.size()); if( edit_row >= 0 && edit_row < line_count ) { if( type == 0 ) { // Append APPEND_RANDOM_STRING(lines[edit_row]); fprintf(file, "L%dE%d=%s\n", edit_row + 1, line_count, lines[edit_row].c_str()); continue; } else if( type == 1 ) { // Delete vector::iterator d = lines.begin(); d += edit_row; lines.erase(d); for(int i = 0; i < line_count; i++) { fprintf(file, "L%dE%d=%s\n", i, line_count, lines[i].c_str()); } continue; } else { // Replace while( static_cast(lines.size()) <= edit_row ) lines.push_back(string()); lines[edit_row].clear(); APPEND_RANDOM_STRING(lines[edit_row]); fprintf(file, "L%dE%d=%s\n", edit_row + 1, static_cast(lines.size()), lines[edit_row].c_str()); continue; } } // Write out of bounds row string text; APPEND_RANDOM_STRING(text); fprintf(file, "L%dE%d=%s\n", edit_row + 1, static_cast(lines.size()), text.c_str()); } // Increase the timestamp once in a while. Don't do this on // every frame so that we can test subsecond handling. if( RANDOM_INT(2) == 0 ) timestamp += RANDOM_INT(5) + 1; } #undef APPEND_RANDOM_STRING #undef RANDOM_INT } // Load and compile input fseek(file, 0, SEEK_SET); AllEvents events; events.Load(file); fclose(file); events.CompressIdleTime(3); events.CompileEvents(); } // }}} } // namespace int main() { TestLoad(); TestCompressIdleTime(); TestCursor(); TestUnicode(); TestInsert(); TestAppend(); TestDelete(); TestRecycle(); TestOptimizeEvents(); TestRandomInput(); return 0; }