difflib_test.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. package difflib
  2. import (
  3. "bytes"
  4. "fmt"
  5. "math"
  6. "reflect"
  7. "strings"
  8. "testing"
  9. )
  10. func assertAlmostEqual(t *testing.T, a, b float64, places int) {
  11. if math.Abs(a-b) > math.Pow10(-places) {
  12. t.Errorf("%.7f != %.7f", a, b)
  13. }
  14. }
  15. func assertEqual(t *testing.T, a, b interface{}) {
  16. if !reflect.DeepEqual(a, b) {
  17. t.Errorf("%v != %v", a, b)
  18. }
  19. }
  20. func splitChars(s string) []string {
  21. chars := make([]string, 0, len(s))
  22. // Assume ASCII inputs
  23. for i := 0; i != len(s); i++ {
  24. chars = append(chars, string(s[i]))
  25. }
  26. return chars
  27. }
  28. func TestSequenceMatcherRatio(t *testing.T) {
  29. s := NewMatcher(splitChars("abcd"), splitChars("bcde"))
  30. assertEqual(t, s.Ratio(), 0.75)
  31. assertEqual(t, s.QuickRatio(), 0.75)
  32. assertEqual(t, s.RealQuickRatio(), 1.0)
  33. }
  34. func TestGetOptCodes(t *testing.T) {
  35. a := "qabxcd"
  36. b := "abycdf"
  37. s := NewMatcher(splitChars(a), splitChars(b))
  38. w := &bytes.Buffer{}
  39. for _, op := range s.GetOpCodes() {
  40. fmt.Fprintf(w, "%s a[%d:%d], (%s) b[%d:%d] (%s)\n", string(op.Tag),
  41. op.I1, op.I2, a[op.I1:op.I2], op.J1, op.J2, b[op.J1:op.J2])
  42. }
  43. result := string(w.Bytes())
  44. expected := `d a[0:1], (q) b[0:0] ()
  45. e a[1:3], (ab) b[0:2] (ab)
  46. r a[3:4], (x) b[2:3] (y)
  47. e a[4:6], (cd) b[3:5] (cd)
  48. i a[6:6], () b[5:6] (f)
  49. `
  50. if expected != result {
  51. t.Errorf("unexpected op codes: \n%s", result)
  52. }
  53. }
  54. func TestGroupedOpCodes(t *testing.T) {
  55. a := []string{}
  56. for i := 0; i != 39; i++ {
  57. a = append(a, fmt.Sprintf("%02d", i))
  58. }
  59. b := []string{}
  60. b = append(b, a[:8]...)
  61. b = append(b, " i")
  62. b = append(b, a[8:19]...)
  63. b = append(b, " x")
  64. b = append(b, a[20:22]...)
  65. b = append(b, a[27:34]...)
  66. b = append(b, " y")
  67. b = append(b, a[35:]...)
  68. s := NewMatcher(a, b)
  69. w := &bytes.Buffer{}
  70. for _, g := range s.GetGroupedOpCodes(-1) {
  71. fmt.Fprintf(w, "group\n")
  72. for _, op := range g {
  73. fmt.Fprintf(w, " %s, %d, %d, %d, %d\n", string(op.Tag),
  74. op.I1, op.I2, op.J1, op.J2)
  75. }
  76. }
  77. result := string(w.Bytes())
  78. expected := `group
  79. e, 5, 8, 5, 8
  80. i, 8, 8, 8, 9
  81. e, 8, 11, 9, 12
  82. group
  83. e, 16, 19, 17, 20
  84. r, 19, 20, 20, 21
  85. e, 20, 22, 21, 23
  86. d, 22, 27, 23, 23
  87. e, 27, 30, 23, 26
  88. group
  89. e, 31, 34, 27, 30
  90. r, 34, 35, 30, 31
  91. e, 35, 38, 31, 34
  92. `
  93. if expected != result {
  94. t.Errorf("unexpected op codes: \n%s", result)
  95. }
  96. }
  97. func ExampleGetUnifiedDiffString() {
  98. a := `one
  99. two
  100. three
  101. four`
  102. b := `zero
  103. one
  104. three
  105. four`
  106. diff := UnifiedDiff{
  107. A: SplitLines(a),
  108. B: SplitLines(b),
  109. FromFile: "Original",
  110. FromDate: "2005-01-26 23:30:50",
  111. ToFile: "Current",
  112. ToDate: "2010-04-02 10:20:52",
  113. Context: 3,
  114. }
  115. result, _ := GetUnifiedDiffString(diff)
  116. fmt.Printf(strings.Replace(result, "\t", " ", -1))
  117. // Output:
  118. // --- Original 2005-01-26 23:30:50
  119. // +++ Current 2010-04-02 10:20:52
  120. // @@ -1,4 +1,4 @@
  121. // +zero
  122. // one
  123. // -two
  124. // three
  125. // four
  126. }
  127. func ExampleGetContextDiffString() {
  128. a := `one
  129. two
  130. three
  131. four`
  132. b := `zero
  133. one
  134. tree
  135. four`
  136. diff := ContextDiff{
  137. A: SplitLines(a),
  138. B: SplitLines(b),
  139. FromFile: "Original",
  140. ToFile: "Current",
  141. Context: 3,
  142. Eol: "\n",
  143. }
  144. result, _ := GetContextDiffString(diff)
  145. fmt.Printf(strings.Replace(result, "\t", " ", -1))
  146. // Output:
  147. // *** Original
  148. // --- Current
  149. // ***************
  150. // *** 1,4 ****
  151. // one
  152. // ! two
  153. // ! three
  154. // four
  155. // --- 1,4 ----
  156. // + zero
  157. // one
  158. // ! tree
  159. // four
  160. }
  161. func rep(s string, count int) string {
  162. return strings.Repeat(s, count)
  163. }
  164. func TestWithAsciiOneInsert(t *testing.T) {
  165. sm := NewMatcher(splitChars(rep("b", 100)),
  166. splitChars("a"+rep("b", 100)))
  167. assertAlmostEqual(t, sm.Ratio(), 0.995, 3)
  168. assertEqual(t, sm.GetOpCodes(),
  169. []OpCode{{'i', 0, 0, 0, 1}, {'e', 0, 100, 1, 101}})
  170. assertEqual(t, len(sm.bPopular), 0)
  171. sm = NewMatcher(splitChars(rep("b", 100)),
  172. splitChars(rep("b", 50)+"a"+rep("b", 50)))
  173. assertAlmostEqual(t, sm.Ratio(), 0.995, 3)
  174. assertEqual(t, sm.GetOpCodes(),
  175. []OpCode{{'e', 0, 50, 0, 50}, {'i', 50, 50, 50, 51}, {'e', 50, 100, 51, 101}})
  176. assertEqual(t, len(sm.bPopular), 0)
  177. }
  178. func TestWithAsciiOnDelete(t *testing.T) {
  179. sm := NewMatcher(splitChars(rep("a", 40)+"c"+rep("b", 40)),
  180. splitChars(rep("a", 40)+rep("b", 40)))
  181. assertAlmostEqual(t, sm.Ratio(), 0.994, 3)
  182. assertEqual(t, sm.GetOpCodes(),
  183. []OpCode{{'e', 0, 40, 0, 40}, {'d', 40, 41, 40, 40}, {'e', 41, 81, 40, 80}})
  184. }
  185. func TestWithAsciiBJunk(t *testing.T) {
  186. isJunk := func(s string) bool {
  187. return s == " "
  188. }
  189. sm := NewMatcherWithJunk(splitChars(rep("a", 40)+rep("b", 40)),
  190. splitChars(rep("a", 44)+rep("b", 40)), true, isJunk)
  191. assertEqual(t, sm.bJunk, map[string]struct{}{})
  192. sm = NewMatcherWithJunk(splitChars(rep("a", 40)+rep("b", 40)),
  193. splitChars(rep("a", 44)+rep("b", 40)+rep(" ", 20)), false, isJunk)
  194. assertEqual(t, sm.bJunk, map[string]struct{}{" ": struct{}{}})
  195. isJunk = func(s string) bool {
  196. return s == " " || s == "b"
  197. }
  198. sm = NewMatcherWithJunk(splitChars(rep("a", 40)+rep("b", 40)),
  199. splitChars(rep("a", 44)+rep("b", 40)+rep(" ", 20)), false, isJunk)
  200. assertEqual(t, sm.bJunk, map[string]struct{}{" ": struct{}{}, "b": struct{}{}})
  201. }
  202. func TestSFBugsRatioForNullSeqn(t *testing.T) {
  203. sm := NewMatcher(nil, nil)
  204. assertEqual(t, sm.Ratio(), 1.0)
  205. assertEqual(t, sm.QuickRatio(), 1.0)
  206. assertEqual(t, sm.RealQuickRatio(), 1.0)
  207. }
  208. func TestSFBugsComparingEmptyLists(t *testing.T) {
  209. groups := NewMatcher(nil, nil).GetGroupedOpCodes(-1)
  210. assertEqual(t, len(groups), 0)
  211. diff := UnifiedDiff{
  212. FromFile: "Original",
  213. ToFile: "Current",
  214. Context: 3,
  215. }
  216. result, err := GetUnifiedDiffString(diff)
  217. assertEqual(t, err, nil)
  218. assertEqual(t, result, "")
  219. }
  220. func TestOutputFormatRangeFormatUnified(t *testing.T) {
  221. // Per the diff spec at http://www.unix.org/single_unix_specification/
  222. //
  223. // Each <range> field shall be of the form:
  224. // %1d", <beginning line number> if the range contains exactly one line,
  225. // and:
  226. // "%1d,%1d", <beginning line number>, <number of lines> otherwise.
  227. // If a range is empty, its beginning line number shall be the number of
  228. // the line just before the range, or 0 if the empty range starts the file.
  229. fm := formatRangeUnified
  230. assertEqual(t, fm(3, 3), "3,0")
  231. assertEqual(t, fm(3, 4), "4")
  232. assertEqual(t, fm(3, 5), "4,2")
  233. assertEqual(t, fm(3, 6), "4,3")
  234. assertEqual(t, fm(0, 0), "0,0")
  235. }
  236. func TestOutputFormatRangeFormatContext(t *testing.T) {
  237. // Per the diff spec at http://www.unix.org/single_unix_specification/
  238. //
  239. // The range of lines in file1 shall be written in the following format
  240. // if the range contains two or more lines:
  241. // "*** %d,%d ****\n", <beginning line number>, <ending line number>
  242. // and the following format otherwise:
  243. // "*** %d ****\n", <ending line number>
  244. // The ending line number of an empty range shall be the number of the preceding line,
  245. // or 0 if the range is at the start of the file.
  246. //
  247. // Next, the range of lines in file2 shall be written in the following format
  248. // if the range contains two or more lines:
  249. // "--- %d,%d ----\n", <beginning line number>, <ending line number>
  250. // and the following format otherwise:
  251. // "--- %d ----\n", <ending line number>
  252. fm := formatRangeContext
  253. assertEqual(t, fm(3, 3), "3")
  254. assertEqual(t, fm(3, 4), "4")
  255. assertEqual(t, fm(3, 5), "4,5")
  256. assertEqual(t, fm(3, 6), "4,6")
  257. assertEqual(t, fm(0, 0), "0")
  258. }
  259. func TestOutputFormatTabDelimiter(t *testing.T) {
  260. diff := UnifiedDiff{
  261. A: splitChars("one"),
  262. B: splitChars("two"),
  263. FromFile: "Original",
  264. FromDate: "2005-01-26 23:30:50",
  265. ToFile: "Current",
  266. ToDate: "2010-04-12 10:20:52",
  267. Eol: "\n",
  268. }
  269. ud, err := GetUnifiedDiffString(diff)
  270. assertEqual(t, err, nil)
  271. assertEqual(t, SplitLines(ud)[:2], []string{
  272. "--- Original\t2005-01-26 23:30:50\n",
  273. "+++ Current\t2010-04-12 10:20:52\n",
  274. })
  275. cd, err := GetContextDiffString(ContextDiff(diff))
  276. assertEqual(t, err, nil)
  277. assertEqual(t, SplitLines(cd)[:2], []string{
  278. "*** Original\t2005-01-26 23:30:50\n",
  279. "--- Current\t2010-04-12 10:20:52\n",
  280. })
  281. }
  282. func TestOutputFormatNoTrailingTabOnEmptyFiledate(t *testing.T) {
  283. diff := UnifiedDiff{
  284. A: splitChars("one"),
  285. B: splitChars("two"),
  286. FromFile: "Original",
  287. ToFile: "Current",
  288. Eol: "\n",
  289. }
  290. ud, err := GetUnifiedDiffString(diff)
  291. assertEqual(t, err, nil)
  292. assertEqual(t, SplitLines(ud)[:2], []string{"--- Original\n", "+++ Current\n"})
  293. cd, err := GetContextDiffString(ContextDiff(diff))
  294. assertEqual(t, err, nil)
  295. assertEqual(t, SplitLines(cd)[:2], []string{"*** Original\n", "--- Current\n"})
  296. }
  297. func TestSplitLines(t *testing.T) {
  298. allTests := []struct {
  299. input string
  300. want []string
  301. }{
  302. {"foo", []string{"foo\n"}},
  303. {"foo\nbar", []string{"foo\n", "bar\n"}},
  304. {"foo\nbar\n", []string{"foo\n", "bar\n", "\n"}},
  305. }
  306. for _, test := range allTests {
  307. assertEqual(t, SplitLines(test.input), test.want)
  308. }
  309. }
  310. func benchmarkSplitLines(b *testing.B, count int) {
  311. str := strings.Repeat("foo\n", count)
  312. b.ResetTimer()
  313. n := 0
  314. for i := 0; i < b.N; i++ {
  315. n += len(SplitLines(str))
  316. }
  317. }
  318. func BenchmarkSplitLines100(b *testing.B) {
  319. benchmarkSplitLines(b, 100)
  320. }
  321. func BenchmarkSplitLines10000(b *testing.B) {
  322. benchmarkSplitLines(b, 10000)
  323. }