difflib_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  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 ExampleGetUnifiedDiffCode() {
  98. a := `one
  99. two
  100. three
  101. four
  102. fmt.Printf("%s,%T",a,b)`
  103. b := `zero
  104. one
  105. three
  106. four`
  107. diff := UnifiedDiff{
  108. A: SplitLines(a),
  109. B: SplitLines(b),
  110. FromFile: "Original",
  111. FromDate: "2005-01-26 23:30:50",
  112. ToFile: "Current",
  113. ToDate: "2010-04-02 10:20:52",
  114. Context: 3,
  115. }
  116. result, _ := GetUnifiedDiffString(diff)
  117. fmt.Println(strings.Replace(result, "\t", " ", -1))
  118. // Output:
  119. // --- Original 2005-01-26 23:30:50
  120. // +++ Current 2010-04-02 10:20:52
  121. // @@ -1,5 +1,4 @@
  122. // +zero
  123. // one
  124. // -two
  125. // three
  126. // four
  127. // -fmt.Printf("%s,%T",a,b)
  128. }
  129. func ExampleGetContextDiffCode() {
  130. a := `one
  131. two
  132. three
  133. four
  134. fmt.Printf("%s,%T",a,b)`
  135. b := `zero
  136. one
  137. tree
  138. four`
  139. diff := ContextDiff{
  140. A: SplitLines(a),
  141. B: SplitLines(b),
  142. FromFile: "Original",
  143. ToFile: "Current",
  144. Context: 3,
  145. Eol: "\n",
  146. }
  147. result, _ := GetContextDiffString(diff)
  148. fmt.Print(strings.Replace(result, "\t", " ", -1))
  149. // Output:
  150. // *** Original
  151. // --- Current
  152. // ***************
  153. // *** 1,5 ****
  154. // one
  155. // ! two
  156. // ! three
  157. // four
  158. // - fmt.Printf("%s,%T",a,b)
  159. // --- 1,4 ----
  160. // + zero
  161. // one
  162. // ! tree
  163. // four
  164. }
  165. func ExampleGetContextDiffString() {
  166. a := `one
  167. two
  168. three
  169. four`
  170. b := `zero
  171. one
  172. tree
  173. four`
  174. diff := ContextDiff{
  175. A: SplitLines(a),
  176. B: SplitLines(b),
  177. FromFile: "Original",
  178. ToFile: "Current",
  179. Context: 3,
  180. Eol: "\n",
  181. }
  182. result, _ := GetContextDiffString(diff)
  183. fmt.Printf(strings.Replace(result, "\t", " ", -1))
  184. // Output:
  185. // *** Original
  186. // --- Current
  187. // ***************
  188. // *** 1,4 ****
  189. // one
  190. // ! two
  191. // ! three
  192. // four
  193. // --- 1,4 ----
  194. // + zero
  195. // one
  196. // ! tree
  197. // four
  198. }
  199. func rep(s string, count int) string {
  200. return strings.Repeat(s, count)
  201. }
  202. func TestWithAsciiOneInsert(t *testing.T) {
  203. sm := NewMatcher(splitChars(rep("b", 100)),
  204. splitChars("a"+rep("b", 100)))
  205. assertAlmostEqual(t, sm.Ratio(), 0.995, 3)
  206. assertEqual(t, sm.GetOpCodes(),
  207. []OpCode{{'i', 0, 0, 0, 1}, {'e', 0, 100, 1, 101}})
  208. assertEqual(t, len(sm.bPopular), 0)
  209. sm = NewMatcher(splitChars(rep("b", 100)),
  210. splitChars(rep("b", 50)+"a"+rep("b", 50)))
  211. assertAlmostEqual(t, sm.Ratio(), 0.995, 3)
  212. assertEqual(t, sm.GetOpCodes(),
  213. []OpCode{{'e', 0, 50, 0, 50}, {'i', 50, 50, 50, 51}, {'e', 50, 100, 51, 101}})
  214. assertEqual(t, len(sm.bPopular), 0)
  215. }
  216. func TestWithAsciiOnDelete(t *testing.T) {
  217. sm := NewMatcher(splitChars(rep("a", 40)+"c"+rep("b", 40)),
  218. splitChars(rep("a", 40)+rep("b", 40)))
  219. assertAlmostEqual(t, sm.Ratio(), 0.994, 3)
  220. assertEqual(t, sm.GetOpCodes(),
  221. []OpCode{{'e', 0, 40, 0, 40}, {'d', 40, 41, 40, 40}, {'e', 41, 81, 40, 80}})
  222. }
  223. func TestWithAsciiBJunk(t *testing.T) {
  224. isJunk := func(s string) bool {
  225. return s == " "
  226. }
  227. sm := NewMatcherWithJunk(splitChars(rep("a", 40)+rep("b", 40)),
  228. splitChars(rep("a", 44)+rep("b", 40)), true, isJunk)
  229. assertEqual(t, sm.bJunk, map[string]struct{}{})
  230. sm = NewMatcherWithJunk(splitChars(rep("a", 40)+rep("b", 40)),
  231. splitChars(rep("a", 44)+rep("b", 40)+rep(" ", 20)), false, isJunk)
  232. assertEqual(t, sm.bJunk, map[string]struct{}{" ": struct{}{}})
  233. isJunk = func(s string) bool {
  234. return s == " " || s == "b"
  235. }
  236. sm = NewMatcherWithJunk(splitChars(rep("a", 40)+rep("b", 40)),
  237. splitChars(rep("a", 44)+rep("b", 40)+rep(" ", 20)), false, isJunk)
  238. assertEqual(t, sm.bJunk, map[string]struct{}{" ": struct{}{}, "b": struct{}{}})
  239. }
  240. func TestSFBugsRatioForNullSeqn(t *testing.T) {
  241. sm := NewMatcher(nil, nil)
  242. assertEqual(t, sm.Ratio(), 1.0)
  243. assertEqual(t, sm.QuickRatio(), 1.0)
  244. assertEqual(t, sm.RealQuickRatio(), 1.0)
  245. }
  246. func TestSFBugsComparingEmptyLists(t *testing.T) {
  247. groups := NewMatcher(nil, nil).GetGroupedOpCodes(-1)
  248. assertEqual(t, len(groups), 0)
  249. diff := UnifiedDiff{
  250. FromFile: "Original",
  251. ToFile: "Current",
  252. Context: 3,
  253. }
  254. result, err := GetUnifiedDiffString(diff)
  255. assertEqual(t, err, nil)
  256. assertEqual(t, result, "")
  257. }
  258. func TestOutputFormatRangeFormatUnified(t *testing.T) {
  259. // Per the diff spec at http://www.unix.org/single_unix_specification/
  260. //
  261. // Each <range> field shall be of the form:
  262. // %1d", <beginning line number> if the range contains exactly one line,
  263. // and:
  264. // "%1d,%1d", <beginning line number>, <number of lines> otherwise.
  265. // If a range is empty, its beginning line number shall be the number of
  266. // the line just before the range, or 0 if the empty range starts the file.
  267. fm := formatRangeUnified
  268. assertEqual(t, fm(3, 3), "3,0")
  269. assertEqual(t, fm(3, 4), "4")
  270. assertEqual(t, fm(3, 5), "4,2")
  271. assertEqual(t, fm(3, 6), "4,3")
  272. assertEqual(t, fm(0, 0), "0,0")
  273. }
  274. func TestOutputFormatRangeFormatContext(t *testing.T) {
  275. // Per the diff spec at http://www.unix.org/single_unix_specification/
  276. //
  277. // The range of lines in file1 shall be written in the following format
  278. // if the range contains two or more lines:
  279. // "*** %d,%d ****\n", <beginning line number>, <ending line number>
  280. // and the following format otherwise:
  281. // "*** %d ****\n", <ending line number>
  282. // The ending line number of an empty range shall be the number of the preceding line,
  283. // or 0 if the range is at the start of the file.
  284. //
  285. // Next, the range of lines in file2 shall be written in the following format
  286. // if the range contains two or more lines:
  287. // "--- %d,%d ----\n", <beginning line number>, <ending line number>
  288. // and the following format otherwise:
  289. // "--- %d ----\n", <ending line number>
  290. fm := formatRangeContext
  291. assertEqual(t, fm(3, 3), "3")
  292. assertEqual(t, fm(3, 4), "4")
  293. assertEqual(t, fm(3, 5), "4,5")
  294. assertEqual(t, fm(3, 6), "4,6")
  295. assertEqual(t, fm(0, 0), "0")
  296. }
  297. func TestOutputFormatTabDelimiter(t *testing.T) {
  298. diff := UnifiedDiff{
  299. A: splitChars("one"),
  300. B: splitChars("two"),
  301. FromFile: "Original",
  302. FromDate: "2005-01-26 23:30:50",
  303. ToFile: "Current",
  304. ToDate: "2010-04-12 10:20:52",
  305. Eol: "\n",
  306. }
  307. ud, err := GetUnifiedDiffString(diff)
  308. assertEqual(t, err, nil)
  309. assertEqual(t, SplitLines(ud)[:2], []string{
  310. "--- Original\t2005-01-26 23:30:50\n",
  311. "+++ Current\t2010-04-12 10:20:52\n",
  312. })
  313. cd, err := GetContextDiffString(ContextDiff(diff))
  314. assertEqual(t, err, nil)
  315. assertEqual(t, SplitLines(cd)[:2], []string{
  316. "*** Original\t2005-01-26 23:30:50\n",
  317. "--- Current\t2010-04-12 10:20:52\n",
  318. })
  319. }
  320. func TestOutputFormatNoTrailingTabOnEmptyFiledate(t *testing.T) {
  321. diff := UnifiedDiff{
  322. A: splitChars("one"),
  323. B: splitChars("two"),
  324. FromFile: "Original",
  325. ToFile: "Current",
  326. Eol: "\n",
  327. }
  328. ud, err := GetUnifiedDiffString(diff)
  329. assertEqual(t, err, nil)
  330. assertEqual(t, SplitLines(ud)[:2], []string{"--- Original\n", "+++ Current\n"})
  331. cd, err := GetContextDiffString(ContextDiff(diff))
  332. assertEqual(t, err, nil)
  333. assertEqual(t, SplitLines(cd)[:2], []string{"*** Original\n", "--- Current\n"})
  334. }
  335. func TestOmitFilenames(t *testing.T) {
  336. diff := UnifiedDiff{
  337. A: SplitLines("o\nn\ne\n"),
  338. B: SplitLines("t\nw\no\n"),
  339. Eol: "\n",
  340. }
  341. ud, err := GetUnifiedDiffString(diff)
  342. assertEqual(t, err, nil)
  343. assertEqual(t, SplitLines(ud), []string{
  344. "@@ -0,0 +1,2 @@\n",
  345. "+t\n",
  346. "+w\n",
  347. "@@ -2,2 +3,0 @@\n",
  348. "-n\n",
  349. "-e\n",
  350. "\n",
  351. })
  352. cd, err := GetContextDiffString(ContextDiff(diff))
  353. assertEqual(t, err, nil)
  354. assertEqual(t, SplitLines(cd), []string{
  355. "***************\n",
  356. "*** 0 ****\n",
  357. "--- 1,2 ----\n",
  358. "+ t\n",
  359. "+ w\n",
  360. "***************\n",
  361. "*** 2,3 ****\n",
  362. "- n\n",
  363. "- e\n",
  364. "--- 3 ----\n",
  365. "\n",
  366. })
  367. }
  368. func TestSplitLines(t *testing.T) {
  369. allTests := []struct {
  370. input string
  371. want []string
  372. }{
  373. {"foo", []string{"foo\n"}},
  374. {"foo\nbar", []string{"foo\n", "bar\n"}},
  375. {"foo\nbar\n", []string{"foo\n", "bar\n", "\n"}},
  376. }
  377. for _, test := range allTests {
  378. assertEqual(t, SplitLines(test.input), test.want)
  379. }
  380. }
  381. func benchmarkSplitLines(b *testing.B, count int) {
  382. str := strings.Repeat("foo\n", count)
  383. b.ResetTimer()
  384. n := 0
  385. for i := 0; i < b.N; i++ {
  386. n += len(SplitLines(str))
  387. }
  388. }
  389. func BenchmarkSplitLines100(b *testing.B) {
  390. benchmarkSplitLines(b, 100)
  391. }
  392. func BenchmarkSplitLines10000(b *testing.B) {
  393. benchmarkSplitLines(b, 10000)
  394. }