datadir_test.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. package main
  2. import (
  3. "os"
  4. "path/filepath"
  5. "strings"
  6. "testing"
  7. )
  8. // Profiles and definitions are the tool's own files, and a binary installed with
  9. // `go install` sits in $GOPATH/bin where neither of them exists. They need a
  10. // per-user directory, and they need the same one on Linux, macOS and Windows,
  11. // which is what os.UserConfigDir answers and a hardcoded ~/.config does not.
  12. func TestDataDirectoriesAreTheUserDirectory(t *testing.T) {
  13. userDir := t.TempDir()
  14. t.Cleanup(stubUserConfigDir(t, userDir))
  15. t.Cleanup(func() { profilesDirOverride = "" })
  16. t.Cleanup(func() { definitionsDirOverride = "" })
  17. base := filepath.Join(userDir, "qmk-rgb-tool")
  18. for _, tc := range []struct {
  19. name string
  20. got string
  21. want string
  22. }{
  23. {"definitions", definitionsPath(), filepath.Join(base, "definitions")},
  24. {"profiles", profilesPath(), filepath.Join(base, "profiles")},
  25. } {
  26. if tc.got != tc.want {
  27. t.Errorf("%sPath() = %q, want %q", tc.name, tc.got, tc.want)
  28. }
  29. }
  30. }
  31. // The directories used to be searched: next to the executable, then the working
  32. // directory, then the user's. Both reasons for that are gone, and a test has to
  33. // say so, because the search is the kind of thing that comes back.
  34. //
  35. // A search path for a definition hides the file `keyboard fetch` just wrote,
  36. // because a definitions directory beside the binary exists for as long as the
  37. // binary is run from a checkout, and it wins. A search path for a profile finds a
  38. // same-named file in a checkout and loads that one instead of the one the user
  39. // saved, so which file `load <name>` reads depends on where the command is run.
  40. func TestDataDirectoriesAreNotSearched(t *testing.T) {
  41. userDir := t.TempDir()
  42. t.Cleanup(stubUserConfigDir(t, userDir))
  43. t.Cleanup(func() { profilesDirOverride = "" })
  44. t.Cleanup(func() { definitionsDirOverride = "" })
  45. base := filepath.Join(userDir, "qmk-rgb-tool")
  46. // A repository checkout with both directories beside the binary, and the same
  47. // two in the working directory, so every step of the old order would match.
  48. here := t.TempDir()
  49. for _, dir := range []string{here, filepath.Join(here, "sub")} {
  50. for _, name := range []string{"definitions", "profiles"} {
  51. if err := os.MkdirAll(filepath.Join(dir, name), 0o755); err != nil {
  52. t.Fatal(err)
  53. }
  54. }
  55. }
  56. t.Chdir(here)
  57. if got, want := definitionsPath(), filepath.Join(base, "definitions"); got != want {
  58. t.Errorf("definitionsPath() = %q, want %q", got, want)
  59. }
  60. if got, want := profilesPath(), filepath.Join(base, "profiles"); got != want {
  61. t.Errorf("profilesPath() = %q, want %q", got, want)
  62. }
  63. }
  64. // A profile written is a profile that reads back, which a search path broke: the
  65. // save went to the user's directory and the read stopped at a checkout's.
  66. func TestProfileIsReadBackFromWhereItIsWritten(t *testing.T) {
  67. userDir := t.TempDir()
  68. t.Cleanup(stubUserConfigDir(t, userDir))
  69. t.Cleanup(func() { profilesDirOverride = "" })
  70. // A checkout's profiles directory, holding a same-named file with different
  71. // content: if this one is loaded, the read is still searching.
  72. here := t.TempDir()
  73. if err := os.MkdirAll(filepath.Join(here, "profiles"), 0o755); err != nil {
  74. t.Fatal(err)
  75. }
  76. if err := os.WriteFile(filepath.Join(here, "profiles", "lava.json"),
  77. []byte(`{"name":"lava","version":1,"zones":{"Backlight":{"effect":"wave"}}}`), 0o644); err != nil {
  78. t.Fatal(err)
  79. }
  80. t.Chdir(here)
  81. if err := (&Profile{Name: "lava", Version: 1}).Save(); err != nil {
  82. t.Fatalf("Save(): %v", err)
  83. }
  84. if _, err := os.Stat(filepath.Join(here, "profiles", "lava.json")); err != nil {
  85. t.Fatalf("Save() did not write into the working directory's profiles: %v", err)
  86. }
  87. loaded, err := LoadProfile("lava")
  88. if err != nil {
  89. t.Fatalf("LoadProfile(lava) after Save(): %v", err)
  90. }
  91. if zone, ok := loaded.Zones["Backlight"]; ok && zone.Effect != "" {
  92. t.Errorf("loaded the checkout's profile: effect = %q, want the saved one, which has no zones", zone.Effect)
  93. }
  94. }
  95. // A profile that is not there says where it looked, because "not found" with no
  96. // path is the one error a user cannot act on.
  97. func TestMissingProfileNamesTheDirectory(t *testing.T) {
  98. userDir := t.TempDir()
  99. t.Cleanup(stubUserConfigDir(t, userDir))
  100. t.Cleanup(func() { profilesDirOverride = "" })
  101. _, err := LoadProfile("nope")
  102. if err == nil {
  103. t.Fatal("LoadProfile(nope) = nil error, want one")
  104. }
  105. if !strings.Contains(err.Error(), filepath.Join("qmk-rgb-tool", "profiles")) {
  106. t.Errorf("LoadProfile(nope) = %q, want it to name the directory", err)
  107. }
  108. }
  109. // A list of profiles has no duplicates to fold, because there is one directory
  110. // they come from. The names are still the ones on disk, without the extension.
  111. func TestProfilesAreListedFromTheUserDirectory(t *testing.T) {
  112. userDir := t.TempDir()
  113. t.Cleanup(stubUserConfigDir(t, userDir))
  114. t.Cleanup(func() { profilesDirOverride = "" })
  115. dir := filepath.Join(userDir, "qmk-rgb-tool", "profiles")
  116. if err := os.MkdirAll(dir, 0o755); err != nil {
  117. t.Fatal(err)
  118. }
  119. for _, n := range []string{"lava", "wave"} {
  120. if err := os.WriteFile(filepath.Join(dir, n+".json"), []byte("{}"), 0o644); err != nil {
  121. t.Fatal(err)
  122. }
  123. }
  124. if err := os.WriteFile(filepath.Join(dir, "notes.txt"), []byte(""), 0o644); err != nil {
  125. t.Fatal(err)
  126. }
  127. got, err := ListProfiles()
  128. if err != nil {
  129. t.Fatalf("ListProfiles(): %v", err)
  130. }
  131. if len(got) != 2 {
  132. t.Fatalf("ListProfiles() = %v, want lava and wave", got)
  133. }
  134. found := map[string]bool{}
  135. for _, n := range got {
  136. found[n] = true
  137. }
  138. if !found["lava"] || !found["wave"] {
  139. t.Errorf("ListProfiles() = %v, want lava and wave", got)
  140. }
  141. }
  142. // A directory that is not there is not an error: a user who has never saved a
  143. // profile has none, and `list` says so by printing nothing.
  144. func TestListingWithoutAProfilesDirectory(t *testing.T) {
  145. userDir := t.TempDir()
  146. t.Cleanup(stubUserConfigDir(t, userDir))
  147. t.Cleanup(func() { profilesDirOverride = "" })
  148. got, err := ListProfiles()
  149. if err != nil {
  150. t.Fatalf("ListProfiles(): %v, want no error for a directory that does not exist", err)
  151. }
  152. if len(got) != 0 {
  153. t.Errorf("ListProfiles() = %v, want none", got)
  154. }
  155. }
  156. // The user directory is reported so the commands can say where a file came from.
  157. func TestDataDirectoryIsReportedForTheUser(t *testing.T) {
  158. userDir := t.TempDir()
  159. t.Cleanup(stubUserConfigDir(t, userDir))
  160. t.Cleanup(func() { definitionsDirOverride = "" })
  161. got := definitionsPath()
  162. if got == "" {
  163. t.Fatal("definitionsPath() = \"\", want a path")
  164. }
  165. if !filepath.IsAbs(got) {
  166. t.Errorf("definitionsPath() = %q, want an absolute path so it can be printed", got)
  167. }
  168. if !strings.Contains(describeDataDir(got), "your user directory") {
  169. t.Errorf("describeDataDir(%q) = %q, want it marked as the user's", got, describeDataDir(got))
  170. }
  171. }
  172. // stubUserConfigDir points the platform's configuration directory at a temp one.
  173. func stubUserConfigDir(t *testing.T, dir string) func() {
  174. t.Helper()
  175. original := userConfigDir
  176. userConfigDir = func() (string, error) { return dir, nil }
  177. return func() { userConfigDir = original }
  178. }