fuzzy_test.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. // +build fuzzy
  2. package xlsx
  3. import (
  4. "archive/zip"
  5. "bytes"
  6. "encoding/xml"
  7. "flag"
  8. "fmt"
  9. "io"
  10. "log"
  11. "math/rand"
  12. "path/filepath"
  13. "reflect"
  14. "strconv"
  15. "testing"
  16. "time"
  17. . "gopkg.in/check.v1"
  18. )
  19. type Fuzzy struct{}
  20. var _ = Suite(&Fuzzy{})
  21. var randseed *int64 = flag.Int64("test.seed", time.Now().Unix(), "Set the random seed of the test for repeatable results")
  22. type tokenchange struct {
  23. file bytes.Buffer
  24. old xml.Token
  25. new xml.Token
  26. }
  27. type filechange struct {
  28. File *zip.Reader
  29. Name string
  30. Old xml.Token
  31. New xml.Token
  32. }
  33. var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
  34. var numbers = []rune("0123456789")
  35. func randString(n int) []byte {
  36. b := make([]rune, n)
  37. for i := range b {
  38. b[i] = letters[rand.Intn(len(letters))]
  39. }
  40. return []byte(string(b))
  41. }
  42. func randInt(n int) []byte {
  43. b := make([]rune, n)
  44. for i := range b {
  45. b[i] = numbers[rand.Intn(len(numbers))]
  46. }
  47. return []byte(string(b))
  48. }
  49. //This function creates variations on tokens without regards as to positions in the file.
  50. func getTokenVariations(t xml.Token) []xml.Token {
  51. var result []xml.Token = make([]xml.Token, 0)
  52. switch t := t.(type) {
  53. case xml.CharData:
  54. {
  55. //If the token is a number try some random number
  56. if _, err := strconv.Atoi(string(t)); err == nil {
  57. result = append(result, xml.CharData(randInt(rand.Intn(15))))
  58. }
  59. result = append(result, xml.CharData(randString(rand.Intn(100))))
  60. return result
  61. }
  62. case xml.StartElement:
  63. {
  64. for k := range t.Attr {
  65. if _, err := strconv.Atoi(string(t.Attr[k].Value)); err == nil {
  66. start := xml.CopyToken(t).(xml.StartElement)
  67. start.Attr[k].Value = string(randInt(rand.Intn(15)))
  68. result = append(result, start)
  69. }
  70. start := xml.CopyToken(t).(xml.StartElement)
  71. start.Attr[k].Value = string(randString(rand.Intn(100)))
  72. result = append(result, start)
  73. }
  74. return result
  75. }
  76. default:
  77. {
  78. return make([]xml.Token, 0) // No variations on non char tokens yet
  79. }
  80. }
  81. }
  82. func variationsXML(f *zip.File) chan tokenchange {
  83. result := make(chan tokenchange)
  84. r, _ := f.Open()
  85. xmlReader := xml.NewDecoder(r)
  86. var tokenList []xml.Token
  87. for {
  88. if t, err := xmlReader.Token(); err == nil {
  89. tokenList = append(tokenList, xml.CopyToken(t))
  90. } else {
  91. break
  92. }
  93. }
  94. go func() {
  95. //Over every token we want to break
  96. for TokenToBreak := range tokenList {
  97. //Get the ways we can break that token
  98. for _, brokenToken := range getTokenVariations(tokenList[TokenToBreak]) {
  99. var buf bytes.Buffer
  100. xmlWriter := xml.NewEncoder(&buf)
  101. //Now create an xml file where one token is broken
  102. for currentToken, t := range tokenList {
  103. if currentToken == TokenToBreak {
  104. xmlWriter.EncodeToken(brokenToken)
  105. } else {
  106. xmlWriter.EncodeToken(t)
  107. }
  108. }
  109. xmlWriter.Flush()
  110. result <- tokenchange{buf, tokenList[TokenToBreak], brokenToken}
  111. }
  112. }
  113. close(result)
  114. }()
  115. return result
  116. }
  117. func generateBrokenFiles(r *zip.Reader) chan filechange {
  118. result := make(chan filechange)
  119. go func() {
  120. count := 0
  121. //For every file in the zip we want variation on
  122. for breakIndex, fileToBreak := range r.File {
  123. if filepath.Ext(fileToBreak.Name) != ".xml" {
  124. continue //We cannot create variations on non-xml files
  125. }
  126. variationCount := 0
  127. //For every broken version of that file
  128. for changedFile := range variationsXML(fileToBreak) {
  129. variationCount++
  130. var buffer bytes.Buffer
  131. //Create a new xlsx file in memory
  132. outZip := zip.NewWriter(&buffer)
  133. w, err := outZip.Create(fileToBreak.Name)
  134. if err != nil {
  135. log.Fatal(err)
  136. }
  137. //Add modified file to xlsx
  138. _, err = changedFile.file.WriteTo(w)
  139. if err != nil {
  140. log.Fatal("changedFile.file.WriteTo", err)
  141. }
  142. //Add other, unchanged, files.
  143. for otherIndex, otherFile := range r.File {
  144. if breakIndex == otherIndex {
  145. continue
  146. }
  147. to, err := outZip.Create(otherFile.Name)
  148. if err != nil {
  149. log.Fatal("Could not add new file to xlsx due to", err)
  150. }
  151. from, err := otherFile.Open()
  152. if err != nil {
  153. log.Fatal("Could not open original file from template xlsx due to", err)
  154. }
  155. io.Copy(to, from)
  156. from.Close()
  157. }
  158. outZip.Close()
  159. //Return this combination of broken files
  160. b := buffer.Bytes()
  161. var res filechange
  162. res.File, _ = zip.NewReader(bytes.NewReader(b), int64(len(b)))
  163. res.Name = fileToBreak.Name
  164. res.Old = changedFile.old
  165. res.New = changedFile.new
  166. result <- res
  167. count++
  168. }
  169. }
  170. close(result)
  171. }()
  172. return result
  173. }
  174. func Raises(f func()) (err interface{}) {
  175. defer func() {
  176. err = recover()
  177. }()
  178. err = nil
  179. f()
  180. return
  181. }
  182. func tokenToString(t xml.Token) string {
  183. switch t := t.(type) {
  184. case xml.CharData:
  185. {
  186. return string(t)
  187. }
  188. default:
  189. {
  190. return fmt.Sprint(t)
  191. }
  192. }
  193. }
  194. func (f *Fuzzy) TestRandomBrokenParts(c *C) {
  195. if testing.Short() {
  196. c.Log("This test, tests many versions of an xlsx file and might take a while, it is being skipped")
  197. c.SucceedNow()
  198. }
  199. log.Println("Fuzzy test is using this -test.seed=" + strconv.FormatInt(*randseed, 10))
  200. rand.Seed(*randseed)
  201. template, err := zip.OpenReader("./testdocs/testfile.xlsx")
  202. c.Assert(err, IsNil)
  203. defer template.Close()
  204. count := 0
  205. for brokenFile := range generateBrokenFiles(&template.Reader) {
  206. count++
  207. if testing.Verbose() {
  208. //If the library panics fatally it would be nice to know why
  209. log.Println("Testing change to ", brokenFile.Name, " on token ", tokenToString(brokenFile.Old), " of type ", reflect.TypeOf(brokenFile.Old), " to ", tokenToString(brokenFile.New))
  210. }
  211. if e := Raises(func() { ReadZipReader(brokenFile.File) }); e != nil {
  212. c.Log("Some file with random changes did raise an exception instead of returning an error", e)
  213. c.Log("Testing change to ", brokenFile.Name, " on token ", tokenToString(brokenFile.Old), " of type ", reflect.TypeOf(brokenFile.Old), " to ", tokenToString(brokenFile.New))
  214. c.FailNow()
  215. }
  216. }
  217. c.Succeed()
  218. }