package difflib import ( "bytes" "fmt" "math" "reflect" "strings" "testing" ) func assertAlmostEqual(t *testing.T, a, b float64, places int) { if math.Abs(a-b) > math.Pow10(-places) { t.Errorf("%.7f != %.7f", a, b) } } func assertEqual(t *testing.T, a, b interface{}) { if !reflect.DeepEqual(a, b) { t.Errorf("%v != %v", a, b) } } func splitChars(s string) []string { chars := make([]string, 0, len(s)) // Assume ASCII inputs for i := 0; i != len(s); i++ { chars = append(chars, string(s[i])) } return chars } func TestSequenceMatcherRatio(t *testing.T) { s := NewMatcher(splitChars("abcd"), splitChars("bcde")) assertEqual(t, s.Ratio(), 0.75) assertEqual(t, s.QuickRatio(), 0.75) assertEqual(t, s.RealQuickRatio(), 1.0) } func TestGetOptCodes(t *testing.T) { a := "qabxcd" b := "abycdf" s := NewMatcher(splitChars(a), splitChars(b)) w := &bytes.Buffer{} for _, op := range s.GetOpCodes() { fmt.Fprintf(w, "%s a[%d:%d], (%s) b[%d:%d] (%s)\n", string(op.Tag), op.I1, op.I2, a[op.I1:op.I2], op.J1, op.J2, b[op.J1:op.J2]) } result := string(w.Bytes()) expected := `d a[0:1], (q) b[0:0] () e a[1:3], (ab) b[0:2] (ab) r a[3:4], (x) b[2:3] (y) e a[4:6], (cd) b[3:5] (cd) i a[6:6], () b[5:6] (f) ` if expected != result { t.Errorf("unexpected op codes: \n%s", result) } } func TestGroupedOpCodes(t *testing.T) { a := []string{} for i := 0; i != 39; i++ { a = append(a, fmt.Sprintf("%02d", i)) } b := []string{} b = append(b, a[:8]...) b = append(b, " i") b = append(b, a[8:19]...) b = append(b, " x") b = append(b, a[20:22]...) b = append(b, a[27:34]...) b = append(b, " y") b = append(b, a[35:]...) s := NewMatcher(a, b) w := &bytes.Buffer{} for _, g := range s.GetGroupedOpCodes(-1) { fmt.Fprintf(w, "group\n") for _, op := range g { fmt.Fprintf(w, " %s, %d, %d, %d, %d\n", string(op.Tag), op.I1, op.I2, op.J1, op.J2) } } result := string(w.Bytes()) expected := `group e, 5, 8, 5, 8 i, 8, 8, 8, 9 e, 8, 11, 9, 12 group e, 16, 19, 17, 20 r, 19, 20, 20, 21 e, 20, 22, 21, 23 d, 22, 27, 23, 23 e, 27, 30, 23, 26 group e, 31, 34, 27, 30 r, 34, 35, 30, 31 e, 35, 38, 31, 34 ` if expected != result { t.Errorf("unexpected op codes: \n%s", result) } } func splitLines(s string) []string { lines := []string{} for _, line := range strings.Split(s, "\n") { lines = append(lines, line+"\n") } return lines } func TestUnifiedDiff(t *testing.T) { a := `one two three four` b := `zero one three four` diff := UnifiedDiff{ A: splitLines(a), B: splitLines(b), FromFile: "Original", FromDate: "2005-01-26 23:30:50", ToFile: "Current", ToDate: "2010-04-02 10:20:52", Context: 3, } result, err := GetUnifiedDiffString(diff) if err != nil { t.Errorf("unified diff failed: %s", err) } expected := `--- Original\t2005-01-26 23:30:50 +++ Current\t2010-04-02 10:20:52 @@ -1,4 +1,4 @@ +zero one -two three four ` // TABs are a pain to preserve through editors expected = strings.Replace(expected, "\\t", "\t", -1) if expected != result { t.Errorf("unexpected diff result:\n%s", result) } } func rep(s string, count int) string { return strings.Repeat(s, count) } func TestWithAsciiOneInsert(t *testing.T) { sm := NewMatcher(splitChars(rep("b", 100)), splitChars("a"+rep("b", 100))) assertAlmostEqual(t, sm.Ratio(), 0.995, 3) assertEqual(t, sm.GetOpCodes(), []OpCode{{'i', 0, 0, 0, 1}, {'e', 0, 100, 1, 101}}) assertEqual(t, len(sm.bPopular), 0) sm = NewMatcher(splitChars(rep("b", 100)), splitChars(rep("b", 50)+"a"+rep("b", 50))) assertAlmostEqual(t, sm.Ratio(), 0.995, 3) assertEqual(t, sm.GetOpCodes(), []OpCode{{'e', 0, 50, 0, 50}, {'i', 50, 50, 50, 51}, {'e', 50, 100, 51, 101}}) assertEqual(t, len(sm.bPopular), 0) } func TestWithAsciiOnDelete(t *testing.T) { sm := NewMatcher(splitChars(rep("a", 40)+"c"+rep("b", 40)), splitChars(rep("a", 40)+rep("b", 40))) assertAlmostEqual(t, sm.Ratio(), 0.994, 3) assertEqual(t, sm.GetOpCodes(), []OpCode{{'e', 0, 40, 0, 40}, {'d', 40, 41, 40, 40}, {'e', 41, 81, 40, 80}}) } func TestWithAsciiBJunk(t *testing.T) { isJunk := func(s string) bool { return s == " " } sm := NewMatcherWithJunk(splitChars(rep("a", 40)+rep("b", 40)), splitChars(rep("a", 44)+rep("b", 40)), true, isJunk) assertEqual(t, sm.bJunk, map[string]struct{}{}) sm = NewMatcherWithJunk(splitChars(rep("a", 40)+rep("b", 40)), splitChars(rep("a", 44)+rep("b", 40)+rep(" ", 20)), false, isJunk) assertEqual(t, sm.bJunk, map[string]struct{}{" ": struct{}{}}) isJunk = func(s string) bool { return s == " " || s == "b" } sm = NewMatcherWithJunk(splitChars(rep("a", 40)+rep("b", 40)), splitChars(rep("a", 44)+rep("b", 40)+rep(" ", 20)), false, isJunk) assertEqual(t, sm.bJunk, map[string]struct{}{" ": struct{}{}, "b": struct{}{}}) } func TestSFBugsRatioForNullSeqn(t *testing.T) { sm := NewMatcher(nil, nil) assertEqual(t, sm.Ratio(), 1.0) assertEqual(t, sm.QuickRatio(), 1.0) assertEqual(t, sm.RealQuickRatio(), 1.0) } func TestSFBugsComparingEmptyLists(t *testing.T) { groups := NewMatcher(nil, nil).GetGroupedOpCodes(-1) assertEqual(t, len(groups), 0) diff := UnifiedDiff{ FromFile: "Original", ToFile: "Current", Context: 3, } result, err := GetUnifiedDiffString(diff) assertEqual(t, err, nil) assertEqual(t, result, "") } func TestOutputFormatRangeFormatUnified(t *testing.T) { // Per the diff spec at http://www.unix.org/single_unix_specification/ // // Each field shall be of the form: // %1d", if the range contains exactly one line, // and: // "%1d,%1d", , otherwise. // If a range is empty, its beginning line number shall be the number of // the line just before the range, or 0 if the empty range starts the file. fm := formatRangeUnified assertEqual(t, fm(3, 3), "3,0") assertEqual(t, fm(3, 4), "4") assertEqual(t, fm(3, 5), "4,2") assertEqual(t, fm(3, 6), "4,3") assertEqual(t, fm(0, 0), "0,0") }