package export import ( "archive/zip" "bytes" "compress/gzip" "errors" "io" "os" "path/filepath" "sort" "strings" "testing" "git.monoexp.com/bajdev/mpckit/internal/project" ) const testdata = "../../testdata" func must(t *testing.T, err error) { t.Helper() if err != nil { t.Fatal(err) } } func gunzip(t *testing.T, b []byte) []byte { t.Helper() zr, err := gzip.NewReader(bytes.NewReader(b)) must(t, err) out, err := io.ReadAll(zr) must(t, err) return out } func readFile(t *testing.T, path string) []byte { t.Helper() b, err := os.ReadFile(path) must(t, err) return b } // buildT1 makes MpckitT1 through the model (as in the M2 golden test). func buildT1(t *testing.T) *project.Project { t.Helper() p, err := project.New("MpckitT1") must(t, err) dir := filepath.Join(testdata, "accepted", "MpckitT1"+project.DataSuffix) must(t, p.AssignSample(0, filepath.Join(dir, "Linn 9000 kick.wav"))) must(t, p.AssignSample(16, filepath.Join(dir, "Linn 9000 kick001.wav"))) must(t, p.SetTune(16, 5, 0)) must(t, p.SetSequence(0, "Verse", 96, 8)) must(t, p.AddSequence(1)) must(t, p.SetSequence(1, "Chorus", 96, 4)) return p } func listDir(t *testing.T, dir string) []string { t.Helper() ents, err := os.ReadDir(dir) must(t, err) var names []string for _, e := range ents { names = append(names, e.Name()) } sort.Strings(names) return names } func TestFolder(t *testing.T) { dest := t.TempDir() p := buildT1(t) path, err := Folder(p, dest, false) must(t, err) if path != filepath.Join(dest, "MpckitT1.xpj") { t.Errorf("path = %s", path) } if got := listDir(t, dest); strings.Join(got, "|") != "MpckitT1.xpj|MpckitT1_[ProjectData]" { t.Errorf("dest holds %v", got) } want := gunzip(t, readFile(t, filepath.Join(testdata, "accepted", "MpckitT1.xpj"))) if !bytes.Equal(gunzip(t, readFile(t, path)), want) { t.Error("exported .xpj differs from MpckitT1.xpj") } src := filepath.Join(testdata, "accepted", "MpckitT1"+project.DataSuffix) data := filepath.Join(dest, "MpckitT1"+project.DataSuffix) if got := listDir(t, data); strings.Join(got, "|") != "Linn 9000 kick.wav|Linn 9000 kick001.wav" { t.Errorf("data folder holds %v", got) } for _, f := range listDir(t, data) { if !bytes.Equal(readFile(t, filepath.Join(data, f)), readFile(t, filepath.Join(src, f))) { t.Errorf("%s differs from its source", f) } } // Again without overwrite: refused, nothing touched. marker := filepath.Join(data, "keep.txt") must(t, os.WriteFile(marker, []byte("x"), 0o644)) _, err = Folder(p, dest, false) var exists *ExistsError if !errors.As(err, &exists) || len(exists.Paths) != 2 { t.Fatalf("second export: %v", err) } if _, err := os.Stat(marker); err != nil { t.Error("refused export changed the folder") } // With overwrite: the old data folder is replaced, not merged. _, err = Folder(p, dest, true) must(t, err) if _, err := os.Stat(marker); err == nil { t.Error("overwrite kept a stale file in the data folder") } if got := listDir(t, dest); len(got) != 2 { t.Errorf("temporary files left behind: %v", got) } } func TestFolderEmptyProject(t *testing.T) { dest := t.TempDir() p, err := project.New("Empty") must(t, err) _, err = Folder(p, dest, false) must(t, err) // The device creates the data folder even with no samples; so do we. if got := listDir(t, filepath.Join(dest, "Empty"+project.DataSuffix)); len(got) != 0 { t.Errorf("data folder holds %v", got) } } func TestRenamedLoadedProject(t *testing.T) { p, _, err := project.Load(filepath.Join(testdata, "device", "DemoFile.xpj")) must(t, err) must(t, p.SetName("Renamed")) dest := t.TempDir() _, err = Folder(p, dest, false) must(t, err) got := listDir(t, filepath.Join(dest, "Renamed"+project.DataSuffix)) if len(got) != 6 { t.Errorf("data folder holds %v", got) } want := gunzip(t, readFile(t, filepath.Join(testdata, "device", "DemoFile.xpj"))) if !bytes.Equal(gunzip(t, readFile(t, filepath.Join(dest, "Renamed.xpj"))), want) { t.Error("renaming changed the file contents") } } func TestMissingSources(t *testing.T) { tmp := t.TempDir() p, err := project.New("Missing") must(t, err) for i, name := range []string{"a.wav", "b.wav", "c.wav"} { src := filepath.Join(tmp, name) must(t, os.WriteFile(src, readFile(t, filepath.Join(testdata, "accepted", "MpckitT2"+project.DataSuffix, "Linn 9000 kick.wav")), 0o644)) must(t, p.AssignSample(i, src)) } must(t, os.Remove(filepath.Join(tmp, "a.wav"))) must(t, os.Remove(filepath.Join(tmp, "c.wav"))) dest := t.TempDir() _, err = Folder(p, dest, false) var missing *MissingError if !errors.As(err, &missing) || strings.Join(missing.Files, ",") != "a.wav,c.wav" { t.Fatalf("Folder: %v", err) } if !strings.Contains(err.Error(), "2 sample file(s)") { t.Errorf("message: %v", err) } if err := Zip(p, filepath.Join(dest, "m.zip"), false); !errors.As(err, &missing) { t.Fatalf("Zip: %v", err) } if got := listDir(t, dest); len(got) != 0 { t.Errorf("failed export wrote %v", got) } } func TestZip(t *testing.T) { dest := t.TempDir() zpath := filepath.Join(dest, "kit.zip") p := buildT1(t) must(t, Zip(p, zpath, false)) zr, err := zip.OpenReader(zpath) must(t, err) defer zr.Close() var names []string files := map[string][]byte{} for _, f := range zr.File { names = append(names, f.Name) rc, err := f.Open() must(t, err) b, err := io.ReadAll(rc) rc.Close() must(t, err) files[f.Name] = b } want := "MpckitT1.xpj|MpckitT1_[ProjectData]/|MpckitT1_[ProjectData]/Linn 9000 kick.wav|MpckitT1_[ProjectData]/Linn 9000 kick001.wav" if strings.Join(names, "|") != want { t.Errorf("entries %v", names) } if !bytes.Equal(gunzip(t, files["MpckitT1.xpj"]), gunzip(t, readFile(t, filepath.Join(testdata, "accepted", "MpckitT1.xpj")))) { t.Error("zipped .xpj differs from MpckitT1.xpj") } src := filepath.Join(testdata, "accepted", "MpckitT1"+project.DataSuffix, "Linn 9000 kick001.wav") if !bytes.Equal(files["MpckitT1_[ProjectData]/Linn 9000 kick001.wav"], readFile(t, src)) { t.Error("zipped WAV differs from its source") } var exists *ExistsError if err := Zip(p, zpath, false); !errors.As(err, &exists) { t.Errorf("existing zip: %v", err) } must(t, Zip(p, zpath, true)) if got := listDir(t, dest); strings.Join(got, "|") != "kit.zip" { t.Errorf("temporary files left behind: %v", got) } }