export_test.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. package export
  2. import (
  3. "archive/zip"
  4. "bytes"
  5. "compress/gzip"
  6. "errors"
  7. "io"
  8. "os"
  9. "path/filepath"
  10. "sort"
  11. "strings"
  12. "testing"
  13. "git.monoexp.com/bajdev/mpckit/internal/project"
  14. )
  15. const testdata = "../../testdata"
  16. func must(t *testing.T, err error) {
  17. t.Helper()
  18. if err != nil {
  19. t.Fatal(err)
  20. }
  21. }
  22. func gunzip(t *testing.T, b []byte) []byte {
  23. t.Helper()
  24. zr, err := gzip.NewReader(bytes.NewReader(b))
  25. must(t, err)
  26. out, err := io.ReadAll(zr)
  27. must(t, err)
  28. return out
  29. }
  30. func readFile(t *testing.T, path string) []byte {
  31. t.Helper()
  32. b, err := os.ReadFile(path)
  33. must(t, err)
  34. return b
  35. }
  36. // buildT1 makes MpckitT1 through the model (as in the M2 golden test).
  37. func buildT1(t *testing.T) *project.Project {
  38. t.Helper()
  39. p, err := project.New("MpckitT1")
  40. must(t, err)
  41. dir := filepath.Join(testdata, "accepted", "MpckitT1"+project.DataSuffix)
  42. must(t, p.AssignSample(0, filepath.Join(dir, "Linn 9000 kick.wav")))
  43. must(t, p.AssignSample(16, filepath.Join(dir, "Linn 9000 kick001.wav")))
  44. must(t, p.SetTune(16, 5, 0))
  45. must(t, p.SetSequence(0, "Verse", 96, 8))
  46. must(t, p.AddSequence(1))
  47. must(t, p.SetSequence(1, "Chorus", 96, 4))
  48. return p
  49. }
  50. func listDir(t *testing.T, dir string) []string {
  51. t.Helper()
  52. ents, err := os.ReadDir(dir)
  53. must(t, err)
  54. var names []string
  55. for _, e := range ents {
  56. names = append(names, e.Name())
  57. }
  58. sort.Strings(names)
  59. return names
  60. }
  61. func TestFolder(t *testing.T) {
  62. dest := t.TempDir()
  63. p := buildT1(t)
  64. path, err := Folder(p, dest, false)
  65. must(t, err)
  66. if path != filepath.Join(dest, "MpckitT1.xpj") {
  67. t.Errorf("path = %s", path)
  68. }
  69. if got := listDir(t, dest); strings.Join(got, "|") != "MpckitT1.xpj|MpckitT1_[ProjectData]" {
  70. t.Errorf("dest holds %v", got)
  71. }
  72. want := gunzip(t, readFile(t, filepath.Join(testdata, "accepted", "MpckitT1.xpj")))
  73. if !bytes.Equal(gunzip(t, readFile(t, path)), want) {
  74. t.Error("exported .xpj differs from MpckitT1.xpj")
  75. }
  76. src := filepath.Join(testdata, "accepted", "MpckitT1"+project.DataSuffix)
  77. data := filepath.Join(dest, "MpckitT1"+project.DataSuffix)
  78. if got := listDir(t, data); strings.Join(got, "|") != "Linn 9000 kick.wav|Linn 9000 kick001.wav" {
  79. t.Errorf("data folder holds %v", got)
  80. }
  81. for _, f := range listDir(t, data) {
  82. if !bytes.Equal(readFile(t, filepath.Join(data, f)), readFile(t, filepath.Join(src, f))) {
  83. t.Errorf("%s differs from its source", f)
  84. }
  85. }
  86. // Again without overwrite: refused, nothing touched.
  87. marker := filepath.Join(data, "keep.txt")
  88. must(t, os.WriteFile(marker, []byte("x"), 0o644))
  89. _, err = Folder(p, dest, false)
  90. var exists *ExistsError
  91. if !errors.As(err, &exists) || len(exists.Paths) != 2 {
  92. t.Fatalf("second export: %v", err)
  93. }
  94. if _, err := os.Stat(marker); err != nil {
  95. t.Error("refused export changed the folder")
  96. }
  97. // With overwrite: the old data folder is replaced, not merged.
  98. _, err = Folder(p, dest, true)
  99. must(t, err)
  100. if _, err := os.Stat(marker); err == nil {
  101. t.Error("overwrite kept a stale file in the data folder")
  102. }
  103. if got := listDir(t, dest); len(got) != 2 {
  104. t.Errorf("temporary files left behind: %v", got)
  105. }
  106. }
  107. func TestFolderEmptyProject(t *testing.T) {
  108. dest := t.TempDir()
  109. p, err := project.New("Empty")
  110. must(t, err)
  111. _, err = Folder(p, dest, false)
  112. must(t, err)
  113. // The device creates the data folder even with no samples; so do we.
  114. if got := listDir(t, filepath.Join(dest, "Empty"+project.DataSuffix)); len(got) != 0 {
  115. t.Errorf("data folder holds %v", got)
  116. }
  117. }
  118. func TestRenamedLoadedProject(t *testing.T) {
  119. p, _, err := project.Load(filepath.Join(testdata, "device", "DemoFile.xpj"))
  120. must(t, err)
  121. must(t, p.SetName("Renamed"))
  122. dest := t.TempDir()
  123. _, err = Folder(p, dest, false)
  124. must(t, err)
  125. got := listDir(t, filepath.Join(dest, "Renamed"+project.DataSuffix))
  126. if len(got) != 6 {
  127. t.Errorf("data folder holds %v", got)
  128. }
  129. want := gunzip(t, readFile(t, filepath.Join(testdata, "device", "DemoFile.xpj")))
  130. if !bytes.Equal(gunzip(t, readFile(t, filepath.Join(dest, "Renamed.xpj"))), want) {
  131. t.Error("renaming changed the file contents")
  132. }
  133. }
  134. func TestMissingSources(t *testing.T) {
  135. tmp := t.TempDir()
  136. p, err := project.New("Missing")
  137. must(t, err)
  138. for i, name := range []string{"a.wav", "b.wav", "c.wav"} {
  139. src := filepath.Join(tmp, name)
  140. must(t, os.WriteFile(src, readFile(t, filepath.Join(testdata, "accepted", "MpckitT2"+project.DataSuffix, "Linn 9000 kick.wav")), 0o644))
  141. must(t, p.AssignSample(i, src))
  142. }
  143. must(t, os.Remove(filepath.Join(tmp, "a.wav")))
  144. must(t, os.Remove(filepath.Join(tmp, "c.wav")))
  145. dest := t.TempDir()
  146. _, err = Folder(p, dest, false)
  147. var missing *MissingError
  148. if !errors.As(err, &missing) || strings.Join(missing.Files, ",") != "a.wav,c.wav" {
  149. t.Fatalf("Folder: %v", err)
  150. }
  151. if !strings.Contains(err.Error(), "2 sample file(s)") {
  152. t.Errorf("message: %v", err)
  153. }
  154. if err := Zip(p, filepath.Join(dest, "m.zip"), false); !errors.As(err, &missing) {
  155. t.Fatalf("Zip: %v", err)
  156. }
  157. if got := listDir(t, dest); len(got) != 0 {
  158. t.Errorf("failed export wrote %v", got)
  159. }
  160. }
  161. func TestZip(t *testing.T) {
  162. dest := t.TempDir()
  163. zpath := filepath.Join(dest, "kit.zip")
  164. p := buildT1(t)
  165. must(t, Zip(p, zpath, false))
  166. zr, err := zip.OpenReader(zpath)
  167. must(t, err)
  168. defer zr.Close()
  169. var names []string
  170. files := map[string][]byte{}
  171. for _, f := range zr.File {
  172. names = append(names, f.Name)
  173. rc, err := f.Open()
  174. must(t, err)
  175. b, err := io.ReadAll(rc)
  176. rc.Close()
  177. must(t, err)
  178. files[f.Name] = b
  179. }
  180. want := "MpckitT1.xpj|MpckitT1_[ProjectData]/|MpckitT1_[ProjectData]/Linn 9000 kick.wav|MpckitT1_[ProjectData]/Linn 9000 kick001.wav"
  181. if strings.Join(names, "|") != want {
  182. t.Errorf("entries %v", names)
  183. }
  184. if !bytes.Equal(gunzip(t, files["MpckitT1.xpj"]), gunzip(t, readFile(t, filepath.Join(testdata, "accepted", "MpckitT1.xpj")))) {
  185. t.Error("zipped .xpj differs from MpckitT1.xpj")
  186. }
  187. src := filepath.Join(testdata, "accepted", "MpckitT1"+project.DataSuffix, "Linn 9000 kick001.wav")
  188. if !bytes.Equal(files["MpckitT1_[ProjectData]/Linn 9000 kick001.wav"], readFile(t, src)) {
  189. t.Error("zipped WAV differs from its source")
  190. }
  191. var exists *ExistsError
  192. if err := Zip(p, zpath, false); !errors.As(err, &exists) {
  193. t.Errorf("existing zip: %v", err)
  194. }
  195. must(t, Zip(p, zpath, true))
  196. if got := listDir(t, dest); strings.Join(got, "|") != "kit.zip" {
  197. t.Errorf("temporary files left behind: %v", got)
  198. }
  199. }