dirfs_test.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package dirfs
  4. import (
  5. "context"
  6. "errors"
  7. "io/fs"
  8. "net/http/httptest"
  9. "os"
  10. "path/filepath"
  11. "testing"
  12. "time"
  13. "github.com/google/go-cmp/cmp"
  14. "github.com/tailscale/xnet/webdav"
  15. "tailscale.com/drive/driveimpl/shared"
  16. "tailscale.com/tstest"
  17. )
  18. func TestStat(t *testing.T) {
  19. cfs, _, _, clock := createFileSystem(t)
  20. tests := []struct {
  21. label string
  22. name string
  23. expected fs.FileInfo
  24. err error
  25. }{
  26. {
  27. label: "root folder",
  28. name: "",
  29. expected: &shared.StaticFileInfo{
  30. Named: "",
  31. Sized: 0,
  32. Moded: 0555,
  33. ModdedTime: clock.Now(),
  34. Dir: true,
  35. },
  36. },
  37. {
  38. label: "static root folder",
  39. name: "/domain",
  40. expected: &shared.StaticFileInfo{
  41. Named: "domain",
  42. Sized: 0,
  43. Moded: 0555,
  44. ModdedTime: clock.Now(),
  45. Dir: true,
  46. },
  47. },
  48. {
  49. label: "remote1",
  50. name: "/domain/remote1",
  51. expected: &shared.StaticFileInfo{
  52. Named: "remote1",
  53. Sized: 0,
  54. Moded: 0555,
  55. ModdedTime: clock.Now(),
  56. Dir: true,
  57. },
  58. },
  59. {
  60. label: "remote2",
  61. name: "/domain/remote2",
  62. expected: &shared.StaticFileInfo{
  63. Named: "remote2",
  64. Sized: 0,
  65. Moded: 0555,
  66. ModdedTime: clock.Now(),
  67. Dir: true,
  68. },
  69. },
  70. {
  71. label: "non-existent remote",
  72. name: "remote3",
  73. err: os.ErrNotExist,
  74. },
  75. }
  76. ctx := context.Background()
  77. for _, test := range tests {
  78. t.Run(test.label, func(t *testing.T) {
  79. fi, err := cfs.Stat(ctx, test.name)
  80. if test.err != nil {
  81. if !errors.Is(err, test.err) {
  82. t.Errorf("got %v, want %v", err, test.err)
  83. }
  84. } else {
  85. if err != nil {
  86. t.Errorf("unable to stat file: %v", err)
  87. } else {
  88. infosEqual(t, test.expected, fi)
  89. }
  90. }
  91. })
  92. }
  93. }
  94. func TestListDir(t *testing.T) {
  95. cfs, _, _, clock := createFileSystem(t)
  96. tests := []struct {
  97. label string
  98. name string
  99. expected []fs.FileInfo
  100. err error
  101. }{
  102. {
  103. label: "root folder",
  104. name: "",
  105. expected: []fs.FileInfo{
  106. &shared.StaticFileInfo{
  107. Named: "domain",
  108. Sized: 0,
  109. Moded: 0555,
  110. ModdedTime: clock.Now(),
  111. Dir: true,
  112. },
  113. },
  114. },
  115. {
  116. label: "static root folder",
  117. name: "/domain",
  118. expected: []fs.FileInfo{
  119. &shared.StaticFileInfo{
  120. Named: "remote1",
  121. Sized: 0,
  122. Moded: 0555,
  123. ModdedTime: clock.Now(),
  124. Dir: true,
  125. },
  126. &shared.StaticFileInfo{
  127. Named: "remote2",
  128. Sized: 0,
  129. Moded: 0555,
  130. ModdedTime: clock.Now(),
  131. Dir: true,
  132. },
  133. &shared.StaticFileInfo{
  134. Named: "remote4",
  135. Sized: 0,
  136. Moded: 0555,
  137. ModdedTime: clock.Now(),
  138. Dir: true,
  139. },
  140. },
  141. },
  142. }
  143. ctx := context.Background()
  144. for _, test := range tests {
  145. t.Run(test.label, func(t *testing.T) {
  146. var infos []fs.FileInfo
  147. file, err := cfs.OpenFile(ctx, test.name, os.O_RDONLY, 0)
  148. if err == nil {
  149. defer file.Close()
  150. infos, err = file.Readdir(0)
  151. }
  152. if test.err != nil {
  153. if !errors.Is(err, test.err) {
  154. t.Errorf("got %v, want %v", err, test.err)
  155. }
  156. } else {
  157. if err != nil {
  158. t.Errorf("unable to stat file: %v", err)
  159. } else {
  160. if len(infos) != len(test.expected) {
  161. t.Errorf("wrong number of file infos, want %d, got %d", len(test.expected), len(infos))
  162. } else {
  163. for i, expected := range test.expected {
  164. infosEqual(t, expected, infos[i])
  165. }
  166. }
  167. }
  168. }
  169. })
  170. }
  171. }
  172. func TestMkdir(t *testing.T) {
  173. fs, _, _, _ := createFileSystem(t)
  174. tests := []struct {
  175. label string
  176. name string
  177. perm os.FileMode
  178. err error
  179. }{
  180. {
  181. label: "attempt to create root folder",
  182. name: "/",
  183. },
  184. {
  185. label: "attempt to create static root folder",
  186. name: "/domain",
  187. },
  188. {
  189. label: "attempt to create remote",
  190. name: "/domain/remote1",
  191. },
  192. {
  193. label: "attempt to create non-existent remote",
  194. name: "/domain/remote3",
  195. err: os.ErrPermission,
  196. },
  197. }
  198. ctx := context.Background()
  199. for _, test := range tests {
  200. t.Run(test.label, func(t *testing.T) {
  201. err := fs.Mkdir(ctx, test.name, test.perm)
  202. if test.err != nil {
  203. if !errors.Is(err, test.err) {
  204. t.Errorf("got %v, want %v", err, test.err)
  205. }
  206. } else if err != nil {
  207. t.Errorf("unexpected error: %v", err)
  208. }
  209. })
  210. }
  211. }
  212. func TestRemoveAll(t *testing.T) {
  213. fs, _, _, _ := createFileSystem(t)
  214. tests := []struct {
  215. label string
  216. name string
  217. err error
  218. }{
  219. {
  220. label: "attempt to remove root folder",
  221. name: "/",
  222. err: os.ErrPermission,
  223. },
  224. }
  225. ctx := context.Background()
  226. for _, test := range tests {
  227. t.Run(test.label, func(t *testing.T) {
  228. err := fs.RemoveAll(ctx, test.name)
  229. if !errors.Is(err, test.err) {
  230. t.Errorf("got %v, want %v", err, test.err)
  231. }
  232. })
  233. }
  234. }
  235. func TestRename(t *testing.T) {
  236. fs, _, _, _ := createFileSystem(t)
  237. tests := []struct {
  238. label string
  239. oldName string
  240. newName string
  241. err error
  242. }{
  243. {
  244. label: "attempt to move root folder",
  245. oldName: "/",
  246. newName: "/domain/remote2/copy.txt",
  247. err: os.ErrPermission,
  248. },
  249. }
  250. ctx := context.Background()
  251. for _, test := range tests {
  252. t.Run(test.label, func(t *testing.T) {
  253. err := fs.Rename(ctx, test.oldName, test.newName)
  254. if !errors.Is(err, test.err) {
  255. t.Errorf("got %v, want: %v", err, test.err)
  256. }
  257. })
  258. }
  259. }
  260. func createFileSystem(t *testing.T) (webdav.FileSystem, string, string, *tstest.Clock) {
  261. s1, dir1 := startRemote(t)
  262. s2, dir2 := startRemote(t)
  263. // Make some files, use perms 0666 as lowest common denominator that works
  264. // on both UNIX and Windows.
  265. err := os.WriteFile(filepath.Join(dir1, "file1.txt"), []byte("12345"), 0666)
  266. if err != nil {
  267. t.Fatal(err)
  268. }
  269. err = os.WriteFile(filepath.Join(dir2, "file2.txt"), []byte("54321"), 0666)
  270. if err != nil {
  271. t.Fatal(err)
  272. }
  273. // make some directories
  274. err = os.Mkdir(filepath.Join(dir1, "dir1"), 0666)
  275. if err != nil {
  276. t.Fatal(err)
  277. }
  278. err = os.Mkdir(filepath.Join(dir2, "dir2"), 0666)
  279. if err != nil {
  280. t.Fatal(err)
  281. }
  282. clock := tstest.NewClock(tstest.ClockOpts{Start: time.Now()})
  283. fs := &FS{
  284. Clock: clock,
  285. StaticRoot: "domain",
  286. Children: []*Child{
  287. {Name: "remote1"},
  288. {Name: "remote2"},
  289. {Name: "remote4"},
  290. },
  291. }
  292. t.Cleanup(func() {
  293. defer s1.Close()
  294. defer os.RemoveAll(dir1)
  295. defer s2.Close()
  296. defer os.RemoveAll(dir2)
  297. })
  298. return fs, dir1, dir2, clock
  299. }
  300. func startRemote(t *testing.T) (*httptest.Server, string) {
  301. dir := t.TempDir()
  302. h := &webdav.Handler{
  303. FileSystem: webdav.Dir(dir),
  304. LockSystem: webdav.NewMemLS(),
  305. }
  306. s := httptest.NewServer(h)
  307. t.Cleanup(s.Close)
  308. return s, dir
  309. }
  310. func infosEqual(t *testing.T, expected, actual fs.FileInfo) {
  311. t.Helper()
  312. sfi, ok := actual.(*shared.StaticFileInfo)
  313. if ok {
  314. // zero out BirthedTime because we don't want to compare that
  315. sfi.BirthedTime = time.Time{}
  316. }
  317. if diff := cmp.Diff(actual, expected); diff != "" {
  318. t.Errorf("Wrong file info (-got, +want):\n%s", diff)
  319. }
  320. }