History

cmd/gitannex: Port unit tests to fstest

This enables the unit tests to run on any given backend, via the `-remote` flag, e.g. `go test -v ./cmd/gitannex/... -remote dropbox:`. We should also port the gitannex e2e tests at some point.

Dan McArdle committed 2 years ago

Commit be33e281 parent 00100903 files · +347 −190Browse files

Unified · Split

3 files
  1. cmd/gitannex/gitannex.go +26 −13
  2. cmd/gitannex/gitannex_test.go +311 −172
  3. cmd/gitannex/layout.go +10 −5
cmd/gitannex/gitannex.go+26−13
@@ -7,7 +7,7 @@ · 6 unmodified lines
7// (Tracked in [issue #7625].)7// (Tracked in [issue #7625].)
8//8//
9// 1. ✅ Minimal support for the [external special remote protocol]. Tested on9// 1. ✅ Minimal support for the [external special remote protocol]. Tested on
10// "local" and "drive" backends.10// "local", "drive", and "dropbox" backends.
11// 2. Add support for the ASYNC protocol extension. This may improve performance.11// 2. Add support for the ASYNC protocol extension. This may improve performance.
12// 3. Support the [simple export interface]. This will enable `git-annex12// 3. Support the [simple export interface]. This will enable `git-annex
13// export` functionality.13// export` functionality.
@@ -33,6 +33,7 @@ import ( · 19 unmodified lines
33 "github.com/rclone/rclone/cmd"33 "github.com/rclone/rclone/cmd"
34 "github.com/rclone/rclone/fs"34 "github.com/rclone/rclone/fs"
35 "github.com/rclone/rclone/fs/cache"35 "github.com/rclone/rclone/fs/cache"
36 "github.com/rclone/rclone/fs/config"
36 "github.com/rclone/rclone/fs/operations"37 "github.com/rclone/rclone/fs/operations"
37 "github.com/spf13/cobra"38 "github.com/spf13/cobra"
38)39)
@@ -267,22 +268,34 @@ func (s *server) handleInitRemote() error { · 228 unmodified lines
267 return fmt.Errorf("failed to get configs: %w", err)268 return fmt.Errorf("failed to get configs: %w", err)
268 }269 }
269 270
270 remoteRootFs, err := cache.Get(context.TODO(), fmt.Sprintf("%s:", s.configRcloneRemoteName))271 // Explicitly check that a remote with the given name exists. If we just
271 if err != nil {272 // relied on `cache.Get()` to return `fs.ErrorNotFoundInConfigFile`, this
272 s.sendMsg("INITREMOTE-FAILURE failed to open root directory of rclone remote")273 // function would incorrectly succeed when the given remote name is actually
273 return fmt.Errorf("failed to open root directory of rclone remote: %w", err)274 // a file path.
274 }275 //
276 // The :local: backend does not correspond to a remote named by the rclone
277 // config, but is permitted to enable testing. Technically, a user might hit
278 // this code path, but it would be a strange choice because git-annex
279 // natively supports a "directory" special remote.
275 280
276 if !remoteRootFs.Features().CanHaveEmptyDirectories {281 trimmedName := strings.TrimSuffix(s.configRcloneRemoteName, ":")
277 s.sendMsg("INITREMOTE-FAILURE this rclone remote does not support empty directories")
278 return fmt.Errorf("rclone remote does not support empty directories")
279 }
280 282
281 if err := operations.Mkdir(context.TODO(), remoteRootFs, s.configPrefix); err != nil {283 if s.configRcloneRemoteName != ":local" {
282 s.sendMsg("INITREMOTE-FAILURE failed to mkdir")284 var remoteExists bool
283 return fmt.Errorf("failed to mkdir: %w", err)285 for _, remoteName := range config.FileSections() {
286 if remoteName == trimmedName {
287 remoteExists = true
288 break
289 }
290 }
291 if !remoteExists {
292 s.sendMsg("INITREMOTE-FAILURE remote does not exist: " + s.configRcloneRemoteName)
293 return fmt.Errorf("remote does not exist: %s", s.configRcloneRemoteName)
294 }
284 }295 }
285 296
297 s.configRcloneRemoteName = trimmedName + ":"
298
286 s.sendMsg("INITREMOTE-SUCCESS")299 s.sendMsg("INITREMOTE-SUCCESS")
287 return nil300 return nil
288}301}
cmd/gitannex/gitannex_test.go+311−172
@@ -2,25 +2,24 @@ package gitannex · 1 unmodified line
2 2
3import (3import (
4 "bufio"4 "bufio"
5 "crypto/sha256"5 "context"
6 "fmt"6 "fmt"
7 "io"7 "io"
8 "os"8 "os"
9 "path/filepath"9 "path/filepath"
10 "regexp"10 "regexp"
11 "runtime"
11 "strings"12 "strings"
12 "sync"13 "sync"
13 "testing"14 "testing"
15 "time"
14 16
15 // Without this import, the local filesystem backend would be unavailable.17 // Without this import, the various backends would be unavailable. It looks
16 // It looks unused, but the act of importing it runs its `init()` function.18 // unused, but the act of importing runs the package's `init()` function.
17 _ "github.com/rclone/rclone/backend/local"19 _ "github.com/rclone/rclone/backend/all"
18 20
19 "github.com/rclone/rclone/fs"21 "github.com/rclone/rclone/fs/fspath"
20 "github.com/rclone/rclone/fs/cache"22 "github.com/rclone/rclone/fstest"
21 "github.com/rclone/rclone/fs/config"
22 "github.com/rclone/rclone/fs/config/configfile"
23 "github.com/rclone/rclone/fstest/mockfs"
24 23
25 "github.com/stretchr/testify/assert"24 "github.com/stretchr/testify/assert"
26 "github.com/stretchr/testify/require"25 "github.com/stretchr/testify/require"
@@ -256,9 +255,9 @@ type testState struct { · 229 unmodified lines
256 mockStdinW *io.PipeWriter255 mockStdinW *io.PipeWriter
257 mockStdoutReader *bufio.Reader256 mockStdoutReader *bufio.Reader
258 257
259 localFsDir string258 fstestRun *fstest.Run
260 configPath string259 remoteName string
261 remoteName string260 remotePrefix string
262}261}
263 262
264func makeTestState(t *testing.T) testState {263func makeTestState(t *testing.T) testState {
@@ -276,6 +275,10 @@ func makeTestState(t *testing.T) testState { · 11 unmodified lines
276 }275 }
277}276}
278 277
278func (h *testState) requireRemoteIsEmpty() {
279 h.fstestRun.CheckRemoteItems(h.t)
280}
281
279func (h *testState) requireReadLineExact(line string) {282func (h *testState) requireReadLineExact(line string) {
280 receivedLine, err := h.mockStdoutReader.ReadString('\n')283 receivedLine, err := h.mockStdoutReader.ReadString('\n')
281 require.NoError(h.t, err)284 require.NoError(h.t, err)
@@ -296,21 +299,106 @@ func (h *testState) requireWriteLine(line string) { · 14 unmodified lines
296// Preconfigure the handle. This enables the calling test to skip the PREPARE299// Preconfigure the handle. This enables the calling test to skip the PREPARE
297// handshake.300// handshake.
298func (h *testState) preconfigureServer() {301func (h *testState) preconfigureServer() {
299 h.server.configPrefix = h.localFsDir
300 h.server.configRcloneRemoteName = h.remoteName302 h.server.configRcloneRemoteName = h.remoteName
303 h.server.configPrefix = h.remotePrefix
301 h.server.configRcloneLayout = string(layoutModeNodir)304 h.server.configRcloneLayout = string(layoutModeNodir)
302 h.server.configsDone = true305 h.server.configsDone = true
303}306}
304 307
305// getUniqueRemoteName returns a valid remote name derived from the given test's308// Drop-in replacement for `filepath.Rel()` that works around a Windows-specific
306// name. This is necessary because when a test registers a second remote with309// quirk when one of the paths begins with `\\?\` or `//?/`. It seems that
307// the same name, the original remote appears to take precedence. This function310// fstest gives us paths with this prefix on Windows, which throws a wrench in
308// is injective, so each test gets a unique remote name. Returned strings311// the gitannex tests that need to construct relative paths from absolute paths.
309// contain no spaces.312// For a demonstration, see `TestWindowsFilepathRelQuirk` below.
310func getUniqueRemoteName(t *testing.T) string {313//
311 // Using sha256 as a hack to ensure injectivity without adding a global314// The `\\?\` prefix tells Windows APIs to pass strings unmodified to the
312 // variable.315// filesystem without additional parsing [1]. Our workaround is roughly to add
313 return fmt.Sprintf("remote-%x", sha256.Sum256([]byte(t.Name())))316// the prefix to whichever parameter doesn't have it (when the OS is Windows).
317// I'm not sure this generalizes, but it works for the the kinds of inputs we're
318// throwing at it.
319//
320// [1]: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN#win32-file-namespaces
321func relativeFilepathWorkaround(basepath, targpath string) (string, error) {
322 if runtime.GOOS != "windows" {
323 return filepath.Rel(basepath, targpath)
324 }
325 // Canonicalize paths to use backslashes.
326 basepath = filepath.Clean(basepath)
327 targpath = filepath.Clean(targpath)
328
329 const winFilePrefixDisableStringParsing = `\\?\`
330 baseHasPrefix := strings.HasPrefix(basepath, winFilePrefixDisableStringParsing)
331 targHasPrefix := strings.HasPrefix(targpath, winFilePrefixDisableStringParsing)
332
333 if baseHasPrefix && !targHasPrefix {
334 targpath = winFilePrefixDisableStringParsing + targpath
335 }
336 if !baseHasPrefix && targHasPrefix {
337 basepath = winFilePrefixDisableStringParsing + basepath
338 }
339 return filepath.Rel(basepath, targpath)
340}
341
342func TestWindowsFilepathRelQuirk(t *testing.T) {
343 if runtime.GOOS != "windows" {
344 t.Skip()
345 }
346
347 t.Run("filepathRelQuirk", func(t *testing.T) {
348 var err error
349
350 _, err = filepath.Rel(`C:\foo`, `\\?\C:\foo\bar`)
351 require.Error(t, err)
352
353 _, err = filepath.Rel(`C:/foo`, `//?/C:/foo/bar`)
354 require.Error(t, err)
355
356 _, err = filepath.Rel(`\\?\C:\foo`, `C:\foo\bar`)
357 require.Error(t, err)
358
359 _, err = filepath.Rel(`//?/C:/foo`, `C:/foo/bar`)
360 require.Error(t, err)
361
362 path, err := filepath.Rel(`\\?\C:\foo`, `\\?\C:\foo\bar`)
363 require.NoError(t, err)
364 require.Equal(t, path, `bar`)
365
366 path, err = filepath.Rel(`//?/C:/foo`, `//?/C:/foo/bar`)
367 require.NoError(t, err)
368 require.Equal(t, path, `bar`)
369 })
370
371 t.Run("fstestAndTempDirHaveDifferentPrefixes", func(t *testing.T) {
372 r := fstest.NewRun(t)
373 p := r.Flocal.Root()
374 require.True(t, strings.HasPrefix(p, `//?/`))
375
376 tempDir := t.TempDir()
377 require.False(t, strings.HasPrefix(tempDir, `//?/`))
378 require.False(t, strings.HasPrefix(tempDir, `\\?\`))
379 })
380
381 t.Run("workaroundWorks", func(t *testing.T) {
382 path, err := relativeFilepathWorkaround(`C:\foo`, `\\?\C:\foo\bar`)
383 require.NoError(t, err)
384 require.Equal(t, path, "bar")
385
386 path, err = relativeFilepathWorkaround(`C:/foo`, `//?/C:/foo/bar`)
387 require.NoError(t, err)
388 require.Equal(t, path, "bar")
389
390 path, err = relativeFilepathWorkaround(`\\?\C:\foo`, `C:\foo\bar`)
391 require.NoError(t, err)
392 require.Equal(t, path, `bar`)
393
394 path, err = relativeFilepathWorkaround(`//?/C:/foo`, `C:/foo/bar`)
395 require.NoError(t, err)
396 require.Equal(t, path, `bar`)
397
398 path, err = relativeFilepathWorkaround(`\\?\C:\foo`, `\\?\C:\foo\bar`)
399 require.NoError(t, err)
400 require.Equal(t, path, `bar`)
401 })
314}402}
315 403
316type testCase struct {404type testCase struct {
@@ -319,8 +407,8 @@ type testCase struct { · 2 unmodified lines
319 expectedError string407 expectedError string
320}408}
321 409
322// These test cases run against the "local" backend.410// These test cases run against a backend selected by the `-remote` flag.
323var localBackendTestCases = []testCase{411var fstestTestCases = []testCase{
324 {412 {
325 label: "HandlesInit",413 label: "HandlesInit",
326 testProtocolFunc: func(t *testing.T, h *testState) {414 testProtocolFunc: func(t *testing.T, h *testState) {
@@ -377,17 +465,85 @@ var localBackendTestCases = []testCase{ · 50 unmodified lines
377 h.requireReadLineExact("GETCONFIG rcloneremotename")465 h.requireReadLineExact("GETCONFIG rcloneremotename")
378 h.requireWriteLine("VALUE " + h.remoteName)466 h.requireWriteLine("VALUE " + h.remoteName)
379 h.requireReadLineExact("GETCONFIG rcloneprefix")467 h.requireReadLineExact("GETCONFIG rcloneprefix")
380 h.requireWriteLine("VALUE " + h.localFsDir)468 h.requireWriteLine("VALUE " + h.remotePrefix)
381 h.requireReadLineExact("GETCONFIG rclonelayout")469 h.requireReadLineExact("GETCONFIG rclonelayout")
382 h.requireWriteLine("VALUE foo")470 h.requireWriteLine("VALUE foo")
383 h.requireReadLineExact("PREPARE-SUCCESS")471 h.requireReadLineExact("PREPARE-SUCCESS")
384 472
385 require.Equal(t, h.server.configRcloneRemoteName, h.remoteName)473 require.Equal(t, h.server.configRcloneRemoteName, h.remoteName)
386 require.Equal(t, h.server.configPrefix, h.localFsDir)474 require.Equal(t, h.server.configPrefix, h.remotePrefix)
475 require.True(t, h.server.configsDone)
476
477 require.NoError(t, h.mockStdinW.Close())
478 },
479 },
480 {
481 label: "HandlesPrepareWithNonexistentRemote",
482 testProtocolFunc: func(t *testing.T, h *testState) {
483 h.requireReadLineExact("VERSION 1")
484 h.requireWriteLine("EXTENSIONS INFO") // Advertise that we support the INFO extension
485 h.requireReadLineExact("EXTENSIONS")
486
487 if !h.server.extensionInfo {
488 t.Errorf("expected INFO extension to be enabled")
489 return
490 }
491
492 h.requireWriteLine("PREPARE")
493 h.requireReadLineExact("GETCONFIG rcloneremotename")
494 h.requireWriteLine("VALUE thisRemoteDoesNotExist")
495 h.requireReadLineExact("GETCONFIG rcloneprefix")
496 h.requireWriteLine("VALUE " + h.remotePrefix)
497 h.requireReadLineExact("GETCONFIG rclonelayout")
498 h.requireWriteLine("VALUE foo")
499 h.requireReadLineExact("PREPARE-SUCCESS")
500
501 require.Equal(t, h.server.configRcloneRemoteName, "thisRemoteDoesNotExist")
502 require.Equal(t, h.server.configPrefix, h.remotePrefix)
503 require.True(t, h.server.configsDone)
504
505 h.requireWriteLine("INITREMOTE")
506 h.requireReadLineExact("INITREMOTE-FAILURE remote does not exist: thisRemoteDoesNotExist")
507
508 require.NoError(t, h.mockStdinW.Close())
509 },
510 expectedError: "remote does not exist: thisRemoteDoesNotExist",
511 },
512 {
513 label: "HandlesPrepareWithPathAsRemote",
514 testProtocolFunc: func(t *testing.T, h *testState) {
515 h.requireReadLineExact("VERSION 1")
516 h.requireWriteLine("EXTENSIONS INFO") // Advertise that we support the INFO extension
517 h.requireReadLineExact("EXTENSIONS")
518
519 if !h.server.extensionInfo {
520 t.Errorf("expected INFO extension to be enabled")
521 return
522 }
523
524 h.requireWriteLine("PREPARE")
525 h.requireReadLineExact("GETCONFIG rcloneremotename")
526 h.requireWriteLine("VALUE " + h.remotePrefix)
527 h.requireReadLineExact("GETCONFIG rcloneprefix")
528 h.requireWriteLine("VALUE /foo")
529 h.requireReadLineExact("GETCONFIG rclonelayout")
530 h.requireWriteLine("VALUE foo")
531 h.requireReadLineExact("PREPARE-SUCCESS")
532
533 require.Equal(t, h.server.configRcloneRemoteName, h.remotePrefix)
534 require.Equal(t, h.server.configPrefix, "/foo")
387 require.True(t, h.server.configsDone)535 require.True(t, h.server.configsDone)
388 536
537 h.requireWriteLine("INITREMOTE")
538
539 require.Regexp(t,
540 regexp.MustCompile("^INITREMOTE-FAILURE remote does not exist: "),
541 h.requireReadLine(),
542 )
543
389 require.NoError(t, h.mockStdinW.Close())544 require.NoError(t, h.mockStdinW.Close())
390 },545 },
546 expectedError: "remote does not exist:",
391 },547 },
392 {548 {
393 label: "HandlesPrepareWithSynonyms",549 label: "HandlesPrepareWithSynonyms",
@@ -409,13 +565,13 @@ var localBackendTestCases = []testCase{ · 15 unmodified lines
409 h.requireWriteLine("VALUE " + h.remoteName)565 h.requireWriteLine("VALUE " + h.remoteName)
410 566
411 h.requireReadLineExact("GETCONFIG rcloneprefix")567 h.requireReadLineExact("GETCONFIG rcloneprefix")
412 h.requireWriteLine("VALUE " + h.localFsDir)568 h.requireWriteLine("VALUE " + h.remotePrefix)
413 h.requireReadLineExact("GETCONFIG rclonelayout")569 h.requireReadLineExact("GETCONFIG rclonelayout")
414 h.requireWriteLine("VALUE foo")570 h.requireWriteLine("VALUE foo")
415 h.requireReadLineExact("PREPARE-SUCCESS")571 h.requireReadLineExact("PREPARE-SUCCESS")
416 572
417 require.Equal(t, h.server.configRcloneRemoteName, h.remoteName)573 require.Equal(t, h.server.configRcloneRemoteName, h.remoteName)
418 require.Equal(t, h.server.configPrefix, h.localFsDir)574 require.Equal(t, h.server.configPrefix, h.remotePrefix)
419 require.True(t, h.server.configsDone)575 require.True(t, h.server.configsDone)
420 576
421 require.NoError(t, h.mockStdinW.Close())577 require.NoError(t, h.mockStdinW.Close())
@@ -437,12 +593,12 @@ var localBackendTestCases = []testCase{ · 15 unmodified lines
437 h.requireReadLineExact("GETCONFIG rcloneremotename")593 h.requireReadLineExact("GETCONFIG rcloneremotename")
438 594
439 remoteNameWithSpaces := fmt.Sprintf(" %s ", h.remoteName)595 remoteNameWithSpaces := fmt.Sprintf(" %s ", h.remoteName)
440 localFsDirWithSpaces := fmt.Sprintf(" %s\t", h.localFsDir)596 prefixWithWhitespace := fmt.Sprintf(" %s\t", h.remotePrefix)
441 597
442 h.requireWriteLine(fmt.Sprintf("VALUE %s", remoteNameWithSpaces))598 h.requireWriteLine(fmt.Sprintf("VALUE %s", remoteNameWithSpaces))
443 599
444 h.requireReadLineExact("GETCONFIG rcloneprefix")600 h.requireReadLineExact("GETCONFIG rcloneprefix")
445 h.requireWriteLine(fmt.Sprintf("VALUE %s", localFsDirWithSpaces))601 h.requireWriteLine(fmt.Sprintf("VALUE %s", prefixWithWhitespace))
446 602
447 h.requireReadLineExact("GETCONFIG rclonelayout")603 h.requireReadLineExact("GETCONFIG rclonelayout")
448 h.requireWriteLine("VALUE")604 h.requireWriteLine("VALUE")
@@ -452,7 +608,7 @@ var localBackendTestCases = []testCase{ · 3 unmodified lines
452 h.requireReadLineExact("PREPARE-SUCCESS")608 h.requireReadLineExact("PREPARE-SUCCESS")
453 609
454 require.Equal(t, h.server.configRcloneRemoteName, remoteNameWithSpaces)610 require.Equal(t, h.server.configRcloneRemoteName, remoteNameWithSpaces)
455 require.Equal(t, h.server.configPrefix, localFsDirWithSpaces)611 require.Equal(t, h.server.configPrefix, prefixWithWhitespace)
456 require.True(t, h.server.configsDone)612 require.True(t, h.server.configsDone)
457 613
458 require.NoError(t, h.mockStdinW.Close())614 require.NoError(t, h.mockStdinW.Close())
@@ -639,20 +795,25 @@ var localBackendTestCases = []testCase{ · 180 unmodified lines
639 h.requireReadLineExact("INITREMOTE-SUCCESS")795 h.requireReadLineExact("INITREMOTE-SUCCESS")
640 796
641 // Create temp file for transfer with an absolute path.797 // Create temp file for transfer with an absolute path.
642 fileToTransfer := filepath.Join(t.TempDir(), "file.txt")798 item := h.fstestRun.WriteFile("file.txt", "HELLO", time.Now())
643 require.NoError(t, os.WriteFile(fileToTransfer, []byte("HELLO"), 0600))799 absPath := filepath.Join(h.fstestRun.Flocal.Root(), item.Path)
644 require.FileExists(t, fileToTransfer)800 require.True(t, filepath.IsAbs(absPath))
645 require.True(t, filepath.IsAbs(fileToTransfer))
646 801
647 // Specify an absolute path to transfer.802 // Specify an absolute path to transfer.
648 h.requireWriteLine("TRANSFER STORE KeyAbsolute " + fileToTransfer)803 h.requireWriteLine("TRANSFER STORE KeyAbsolute " + absPath)
649 h.requireReadLineExact("TRANSFER-SUCCESS STORE KeyAbsolute")804 h.requireReadLineExact("TRANSFER-SUCCESS STORE KeyAbsolute")
650 require.FileExists(t, filepath.Join(h.localFsDir, "KeyAbsolute"))805
806 // Check that the file was transferred.
807 remoteItem := fstest.NewItem("KeyAbsolute", "HELLO", item.ModTime)
808 h.fstestRun.CheckRemoteItems(t, remoteItem)
651 809
652 // Transfer the same absolute path a second time, but with a different key.810 // Transfer the same absolute path a second time, but with a different key.
653 h.requireWriteLine("TRANSFER STORE KeyAbsolute2 " + fileToTransfer)811 h.requireWriteLine("TRANSFER STORE KeyAbsolute2 " + absPath)
654 h.requireReadLineExact("TRANSFER-SUCCESS STORE KeyAbsolute2")812 h.requireReadLineExact("TRANSFER-SUCCESS STORE KeyAbsolute2")
655 require.FileExists(t, filepath.Join(h.localFsDir, "KeyAbsolute2"))813
814 // Check that the same file was transferred to a new name.
815 remoteItem2 := fstest.NewItem("KeyAbsolute2", "HELLO", item.ModTime)
816 h.fstestRun.CheckRemoteItems(t, remoteItem, remoteItem2)
656 817
657 h.requireWriteLine("CHECKPRESENT KeyAbsolute2")818 h.requireWriteLine("CHECKPRESENT KeyAbsolute2")
658 h.requireReadLineExact("CHECKPRESENT-SUCCESS KeyAbsolute2")819 h.requireReadLineExact("CHECKPRESENT-SUCCESS KeyAbsolute2")
@@ -668,30 +829,36 @@ var localBackendTestCases = []testCase{ · 9 unmodified lines
668 {829 {
669 label: "TransferStoreRelative",830 label: "TransferStoreRelative",
670 testProtocolFunc: func(t *testing.T, h *testState) {831 testProtocolFunc: func(t *testing.T, h *testState) {
671 h.preconfigureServer()
672
673 // Save the current working directory so we can restore it when this832 // Save the current working directory so we can restore it when this
674 // test ends.833 // test ends.
675 cwd, err := os.Getwd()834 cwd, err := os.Getwd()
676 require.NoError(t, err)835 require.NoError(t, err)
677 836
678 require.NoError(t, os.Chdir(t.TempDir()))837 tempDir := t.TempDir()
838
839 require.NoError(t, os.Chdir(tempDir))
679 t.Cleanup(func() { require.NoError(t, os.Chdir(cwd)) })840 t.Cleanup(func() { require.NoError(t, os.Chdir(cwd)) })
680 841
842 h.preconfigureServer()
843
681 h.requireReadLineExact("VERSION 1")844 h.requireReadLineExact("VERSION 1")
682 h.requireWriteLine("INITREMOTE")845 h.requireWriteLine("INITREMOTE")
683 h.requireReadLineExact("INITREMOTE-SUCCESS")846 h.requireReadLineExact("INITREMOTE-SUCCESS")
684 847
685 // Create temp file for transfer with a relative path.848 item := h.fstestRun.WriteFile("file.txt", "HELLO", time.Now())
686 fileToTransfer := "file.txt"849 absPath := filepath.Join(h.fstestRun.Flocal.Root(), item.Path)
687 require.NoError(t, os.WriteFile(fileToTransfer, []byte("HELLO"), 0600))850
688 require.FileExists(t, fileToTransfer)851 relativePath, err := relativeFilepathWorkaround(tempDir, absPath)
689 require.False(t, filepath.IsAbs(fileToTransfer))852 require.NoError(t, err)
853 require.False(t, filepath.IsAbs(relativePath))
854 require.FileExists(t, relativePath)
690 855
691 // Specify a relative path to transfer.856 // Specify a relative path to transfer.
692 h.requireWriteLine("TRANSFER STORE KeyRelative " + fileToTransfer)857 h.requireWriteLine("TRANSFER STORE KeyRelative " + relativePath)
693 h.requireReadLineExact("TRANSFER-SUCCESS STORE KeyRelative")858 h.requireReadLineExact("TRANSFER-SUCCESS STORE KeyRelative")
694 require.FileExists(t, filepath.Join(h.localFsDir, "KeyRelative"))859
860 remoteItem := fstest.NewItem("KeyRelative", "HELLO", item.ModTime)
861 h.fstestRun.CheckRemoteItems(t, remoteItem)
695 862
696 h.requireWriteLine("CHECKPRESENT KeyRelative")863 h.requireWriteLine("CHECKPRESENT KeyRelative")
697 h.requireReadLineExact("CHECKPRESENT-SUCCESS KeyRelative")864 h.requireReadLineExact("CHECKPRESENT-SUCCESS KeyRelative")
@@ -710,7 +877,8 @@ var localBackendTestCases = []testCase{ · 12 unmodified lines
710 cwd, err := os.Getwd()877 cwd, err := os.Getwd()
711 require.NoError(t, err)878 require.NoError(t, err)
712 879
713 require.NoError(t, os.Chdir(t.TempDir()))880 tempDir := t.TempDir()
881 require.NoError(t, os.Chdir(tempDir))
714 t.Cleanup(func() { require.NoError(t, os.Chdir(cwd)) })882 t.Cleanup(func() { require.NoError(t, os.Chdir(cwd)) })
715 883
716 h.preconfigureServer()884 h.preconfigureServer()
@@ -720,15 +888,19 @@ var localBackendTestCases = []testCase{ · 3 unmodified lines
720 h.requireReadLineExact("INITREMOTE-SUCCESS")888 h.requireReadLineExact("INITREMOTE-SUCCESS")
721 889
722 // Create temp file for transfer.890 // Create temp file for transfer.
723 fileToTransfer := "filename with spaces.txt"891 item := h.fstestRun.WriteFile("filename with spaces.txt", "HELLO", time.Now())
724 require.NoError(t, os.WriteFile(fileToTransfer, []byte("HELLO"), 0600))892 absPath := filepath.Join(h.fstestRun.Flocal.Root(), item.Path)
725 require.FileExists(t, fileToTransfer)893 relativePath, err := relativeFilepathWorkaround(tempDir, absPath)
726 require.False(t, filepath.IsAbs(fileToTransfer))894 require.NoError(t, err)
895 require.False(t, filepath.IsAbs(relativePath))
896 require.FileExists(t, relativePath)
727 897
728 // Specify a relative path to transfer.898 // Specify a relative path to transfer.
729 h.requireWriteLine("TRANSFER STORE KeyRelative " + fileToTransfer)899 h.requireWriteLine("TRANSFER STORE KeyRelative " + relativePath)
730 h.requireReadLineExact("TRANSFER-SUCCESS STORE KeyRelative")900 h.requireReadLineExact("TRANSFER-SUCCESS STORE KeyRelative")
731 require.FileExists(t, filepath.Join(h.localFsDir, "KeyRelative"))901
902 remoteItem := fstest.NewItem("KeyRelative", "HELLO", item.ModTime)
903 h.fstestRun.CheckRemoteItems(t, remoteItem)
732 904
733 h.requireWriteLine("CHECKPRESENT KeyRelative")905 h.requireWriteLine("CHECKPRESENT KeyRelative")
734 h.requireReadLineExact("CHECKPRESENT-SUCCESS KeyRelative")906 h.requireReadLineExact("CHECKPRESENT-SUCCESS KeyRelative")
@@ -745,8 +917,9 @@ var localBackendTestCases = []testCase{ · 10 unmodified lines
745 h.preconfigureServer()917 h.preconfigureServer()
746 918
747 // Create temp file for transfer.919 // Create temp file for transfer.
748 fileToTransfer := filepath.Join(t.TempDir(), "file.txt")920 item := h.fstestRun.WriteFile("file.txt", "HELLO", time.Now())
749 require.NoError(t, os.WriteFile(fileToTransfer, []byte("HELLO"), 0600))921 absPath := filepath.Join(h.fstestRun.Flocal.Root(), item.Path)
922 require.True(t, filepath.IsAbs(absPath))
750 923
751 h.requireReadLineExact("VERSION 1")924 h.requireReadLineExact("VERSION 1")
752 h.requireWriteLine("INITREMOTE")925 h.requireWriteLine("INITREMOTE")
@@ -756,10 +929,11 @@ var localBackendTestCases = []testCase{ · 3 unmodified lines
756 h.requireReadLineExact("CHECKPRESENT-FAILURE KeyThatDoesNotExist")929 h.requireReadLineExact("CHECKPRESENT-FAILURE KeyThatDoesNotExist")
757 930
758 // Specify an absolute path to transfer.931 // Specify an absolute path to transfer.
759 require.True(t, filepath.IsAbs(fileToTransfer))932 h.requireWriteLine("TRANSFER STORE KeyAbsolute " + absPath)
760 h.requireWriteLine("TRANSFER STORE KeyAbsolute " + fileToTransfer)
761 h.requireReadLineExact("TRANSFER-SUCCESS STORE KeyAbsolute")933 h.requireReadLineExact("TRANSFER-SUCCESS STORE KeyAbsolute")
762 require.FileExists(t, filepath.Join(h.localFsDir, "KeyAbsolute"))934
935 remoteItem := fstest.NewItem("KeyAbsolute", "HELLO", item.ModTime)
936 h.fstestRun.CheckRemoteItems(t, remoteItem)
763 937
764 require.NoError(t, h.mockStdinW.Close())938 require.NoError(t, h.mockStdinW.Close())
765 },939 },
@@ -781,8 +955,9 @@ var localBackendTestCases = []testCase{ · 15 unmodified lines
781 h.preconfigureServer()955 h.preconfigureServer()
782 956
783 // Create temp file for transfer.957 // Create temp file for transfer.
784 fileToTransfer := filepath.Join(t.TempDir(), "file.txt")958 item := h.fstestRun.WriteFile("file.txt", "HELLO", time.Now())
785 require.NoError(t, os.WriteFile(fileToTransfer, []byte("HELLO"), 0600))959 absPath := filepath.Join(h.fstestRun.Flocal.Root(), item.Path)
960 require.True(t, filepath.IsAbs(absPath))
786 961
787 h.requireReadLineExact("VERSION 1")962 h.requireReadLineExact("VERSION 1")
788 h.requireWriteLine("INITREMOTE")963 h.requireWriteLine("INITREMOTE")
@@ -791,9 +966,11 @@ var localBackendTestCases = []testCase{ · 2 unmodified lines
791 h.requireWriteLine("CHECKPRESENT foo")966 h.requireWriteLine("CHECKPRESENT foo")
792 h.requireReadLineExact("CHECKPRESENT-FAILURE foo")967 h.requireReadLineExact("CHECKPRESENT-FAILURE foo")
793 968
794 h.requireWriteLine("TRANSFER STORE foo " + fileToTransfer)969 h.requireWriteLine("TRANSFER STORE foo " + absPath)
795 h.requireReadLineExact("TRANSFER-SUCCESS STORE foo")970 h.requireReadLineExact("TRANSFER-SUCCESS STORE foo")
796 require.FileExists(t, filepath.Join(h.localFsDir, "foo"))971
972 remoteItem := fstest.NewItem("foo", "HELLO", item.ModTime)
973 h.fstestRun.CheckRemoteItems(t, remoteItem)
797 974
798 h.requireWriteLine("CHECKPRESENT foo")975 h.requireWriteLine("CHECKPRESENT foo")
799 h.requireReadLineExact("CHECKPRESENT-SUCCESS foo")976 h.requireReadLineExact("CHECKPRESENT-SUCCESS foo")
@@ -807,8 +984,9 @@ var localBackendTestCases = []testCase{ · 7 unmodified lines
807 h.preconfigureServer()984 h.preconfigureServer()
808 985
809 // Create temp file for transfer.986 // Create temp file for transfer.
810 fileToTransfer := filepath.Join(t.TempDir(), "file.txt")987 item := h.fstestRun.WriteFile("file.txt", "HELLO", time.Now())
811 require.NoError(t, os.WriteFile(fileToTransfer, []byte("HELLO"), 0600))988 absPath := filepath.Join(h.fstestRun.Flocal.Root(), item.Path)
989 require.True(t, filepath.IsAbs(absPath))
812 990
813 h.requireReadLineExact("VERSION 1")991 h.requireReadLineExact("VERSION 1")
814 h.requireWriteLine("INITREMOTE")992 h.requireWriteLine("INITREMOTE")
@@ -817,10 +995,11 @@ var localBackendTestCases = []testCase{ · 2 unmodified lines
817 realisticKey := "SHA256E-s1048576--7ba87e06b9b7903cfbaf4a38736766c161e3e7b42f06fe57f040aa410a8f0701.this-is-a-test-key"995 realisticKey := "SHA256E-s1048576--7ba87e06b9b7903cfbaf4a38736766c161e3e7b42f06fe57f040aa410a8f0701.this-is-a-test-key"
818 996
819 // Specify an absolute path to transfer.997 // Specify an absolute path to transfer.
820 require.True(t, filepath.IsAbs(fileToTransfer))998 h.requireWriteLine(fmt.Sprintf("TRANSFER STORE %s %s", realisticKey, absPath))
821 h.requireWriteLine(fmt.Sprintf("TRANSFER STORE %s %s", realisticKey, fileToTransfer))
822 h.requireReadLineExact("TRANSFER-SUCCESS STORE " + realisticKey)999 h.requireReadLineExact("TRANSFER-SUCCESS STORE " + realisticKey)
823 require.FileExists(t, filepath.Join(h.localFsDir, realisticKey))1000
1001 remoteItem := fstest.NewItem(realisticKey, "HELLO", item.ModTime)
1002 h.fstestRun.CheckRemoteItems(t, remoteItem)
824 1003
825 h.requireWriteLine("CHECKPRESENT " + realisticKey)1004 h.requireWriteLine("CHECKPRESENT " + realisticKey)
826 h.requireReadLineExact("CHECKPRESENT-SUCCESS " + realisticKey)1005 h.requireReadLineExact("CHECKPRESENT-SUCCESS " + realisticKey)
@@ -849,27 +1028,36 @@ var localBackendTestCases = []testCase{ · 22 unmodified lines
849 h.preconfigureServer()1028 h.preconfigureServer()
850 1029
851 // Create temp file for transfer.1030 // Create temp file for transfer.
852 fileToTransfer := filepath.Join(t.TempDir(), "file.txt")1031 item := h.fstestRun.WriteFile("file.txt", "HELLO", time.Now())
853 require.NoError(t, os.WriteFile(fileToTransfer, []byte("HELLO"), 0600))1032 absPath := filepath.Join(h.fstestRun.Flocal.Root(), item.Path)
1033 require.True(t, filepath.IsAbs(absPath))
854 1034
855 h.requireReadLineExact("VERSION 1")1035 h.requireReadLineExact("VERSION 1")
856 h.requireWriteLine("INITREMOTE")1036 h.requireWriteLine("INITREMOTE")
857 h.requireReadLineExact("INITREMOTE-SUCCESS")1037 h.requireReadLineExact("INITREMOTE-SUCCESS")
858 1038
859 // Specify an absolute path to transfer.1039 // Specify an absolute path to transfer.
860 require.True(t, filepath.IsAbs(fileToTransfer))1040 h.requireWriteLine("TRANSFER STORE SomeKey " + absPath)
861 h.requireWriteLine("TRANSFER STORE SomeKey " + fileToTransfer)
862 h.requireReadLineExact("TRANSFER-SUCCESS STORE SomeKey")1041 h.requireReadLineExact("TRANSFER-SUCCESS STORE SomeKey")
863 require.FileExists(t, filepath.Join(h.localFsDir, "SomeKey"))1042
1043 remoteItem := fstest.NewItem("SomeKey", "HELLO", item.ModTime)
1044 h.fstestRun.CheckRemoteItems(t, remoteItem)
864 1045
865 h.requireWriteLine("CHECKPRESENT SomeKey")1046 h.requireWriteLine("CHECKPRESENT SomeKey")
866 h.requireReadLineExact("CHECKPRESENT-SUCCESS SomeKey")1047 h.requireReadLineExact("CHECKPRESENT-SUCCESS SomeKey")
867 1048
868 retrievedFilePath := fileToTransfer + ".retrieved"1049 h.fstestRun.CheckLocalItems(t,
869 require.NoFileExists(t, retrievedFilePath)1050 fstest.NewItem("file.txt", "HELLO", item.ModTime),
1051 )
1052
1053 retrievedFilePath := absPath + ".retrieved"
870 h.requireWriteLine("TRANSFER RETRIEVE SomeKey " + retrievedFilePath)1054 h.requireWriteLine("TRANSFER RETRIEVE SomeKey " + retrievedFilePath)
871 h.requireReadLineExact("TRANSFER-SUCCESS RETRIEVE SomeKey")1055 h.requireReadLineExact("TRANSFER-SUCCESS RETRIEVE SomeKey")
872 require.FileExists(t, retrievedFilePath)1056
1057 h.fstestRun.CheckLocalItems(t,
1058 fstest.NewItem("file.txt", "HELLO", item.ModTime),
1059 fstest.NewItem("file.txt.retrieved", "HELLO", item.ModTime),
1060 )
873 1061
874 require.NoError(t, h.mockStdinW.Close())1062 require.NoError(t, h.mockStdinW.Close())
875 },1063 },
@@ -879,11 +1067,13 @@ var localBackendTestCases = []testCase{ · 3 unmodified lines
879 testProtocolFunc: func(t *testing.T, h *testState) {1067 testProtocolFunc: func(t *testing.T, h *testState) {
880 h.preconfigureServer()1068 h.preconfigureServer()
881 1069
1070 ctx := context.WithoutCancel(context.Background())
1071
882 // Write a file into the remote without using the git-annex1072 // Write a file into the remote without using the git-annex
883 // protocol.1073 // protocol.
884 remoteFilePath := filepath.Join(h.localFsDir, "SomeKey")1074 remoteItem := h.fstestRun.WriteObject(ctx, "SomeKey", "HELLO", time.Now())
885 require.NoError(t, os.WriteFile(remoteFilePath, []byte("HELLO"), 0600))1075
886 require.FileExists(t, remoteFilePath)1076 h.fstestRun.CheckRemoteItems(t, remoteItem)
887 1077
888 h.requireReadLineExact("VERSION 1")1078 h.requireReadLineExact("VERSION 1")
889 h.requireWriteLine("INITREMOTE")1079 h.requireWriteLine("INITREMOTE")
@@ -891,15 +1081,18 @@ var localBackendTestCases = []testCase{ · 1 unmodified line
891 1081
892 h.requireWriteLine("CHECKPRESENT SomeKey")1082 h.requireWriteLine("CHECKPRESENT SomeKey")
893 h.requireReadLineExact("CHECKPRESENT-SUCCESS SomeKey")1083 h.requireReadLineExact("CHECKPRESENT-SUCCESS SomeKey")
894 require.FileExists(t, remoteFilePath)1084
1085 h.fstestRun.CheckRemoteItems(t, remoteItem)
895 1086
896 h.requireWriteLine("REMOVE SomeKey")1087 h.requireWriteLine("REMOVE SomeKey")
897 h.requireReadLineExact("REMOVE-SUCCESS SomeKey")1088 h.requireReadLineExact("REMOVE-SUCCESS SomeKey")
898 require.NoFileExists(t, remoteFilePath)1089
1090 h.requireRemoteIsEmpty()
899 1091
900 h.requireWriteLine("CHECKPRESENT SomeKey")1092 h.requireWriteLine("CHECKPRESENT SomeKey")
901 h.requireReadLineExact("CHECKPRESENT-FAILURE SomeKey")1093 h.requireReadLineExact("CHECKPRESENT-FAILURE SomeKey")
902 require.NoFileExists(t, remoteFilePath)1094
1095 h.requireRemoteIsEmpty()
903 1096
904 require.NoError(t, h.mockStdinW.Close())1097 require.NoError(t, h.mockStdinW.Close())
905 },1098 },
@@ -910,8 +1103,9 @@ var localBackendTestCases = []testCase{ · 4 unmodified lines
910 h.preconfigureServer()1103 h.preconfigureServer()
911 1104
912 // Create temp file for transfer.1105 // Create temp file for transfer.
913 fileToTransfer := filepath.Join(t.TempDir(), "file.txt")1106 item := h.fstestRun.WriteFile("file.txt", "HELLO", time.Now())
914 require.NoError(t, os.WriteFile(fileToTransfer, []byte("HELLO"), 0600))1107 absPath := filepath.Join(h.fstestRun.Flocal.Root(), item.Path)
1108 require.True(t, filepath.IsAbs(absPath))
915 1109
916 h.requireReadLineExact("VERSION 1")1110 h.requireReadLineExact("VERSION 1")
917 h.requireWriteLine("INITREMOTE")1111 h.requireWriteLine("INITREMOTE")
@@ -921,17 +1115,19 @@ var localBackendTestCases = []testCase{ · 3 unmodified lines
921 h.requireReadLineExact("CHECKPRESENT-FAILURE SomeKey")1115 h.requireReadLineExact("CHECKPRESENT-FAILURE SomeKey")
922 1116
923 // Specify an absolute path to transfer.1117 // Specify an absolute path to transfer.
924 require.True(t, filepath.IsAbs(fileToTransfer))1118 h.requireWriteLine("TRANSFER STORE SomeKey " + absPath)
925 h.requireWriteLine("TRANSFER STORE SomeKey " + fileToTransfer)
926 h.requireReadLineExact("TRANSFER-SUCCESS STORE SomeKey")1119 h.requireReadLineExact("TRANSFER-SUCCESS STORE SomeKey")
927 require.FileExists(t, filepath.Join(h.localFsDir, "SomeKey"))1120
1121 remoteItem := fstest.NewItem("SomeKey", "HELLO", item.ModTime)
1122 h.fstestRun.CheckRemoteItems(t, remoteItem)
928 1123
929 h.requireWriteLine("CHECKPRESENT SomeKey")1124 h.requireWriteLine("CHECKPRESENT SomeKey")
930 h.requireReadLineExact("CHECKPRESENT-SUCCESS SomeKey")1125 h.requireReadLineExact("CHECKPRESENT-SUCCESS SomeKey")
931 1126
932 h.requireWriteLine("REMOVE SomeKey")1127 h.requireWriteLine("REMOVE SomeKey")
933 h.requireReadLineExact("REMOVE-SUCCESS SomeKey")1128 h.requireReadLineExact("REMOVE-SUCCESS SomeKey")
934 require.NoFileExists(t, filepath.Join(h.localFsDir, "SomeKey"))1129
1130 h.requireRemoteIsEmpty()
935 1131
936 h.requireWriteLine("CHECKPRESENT SomeKey")1132 h.requireWriteLine("CHECKPRESENT SomeKey")
937 h.requireReadLineExact("CHECKPRESENT-FAILURE SomeKey")1133 h.requireReadLineExact("CHECKPRESENT-FAILURE SomeKey")
@@ -944,10 +1140,6 @@ var localBackendTestCases = []testCase{ · 6 unmodified lines
944 testProtocolFunc: func(t *testing.T, h *testState) {1140 testProtocolFunc: func(t *testing.T, h *testState) {
945 h.preconfigureServer()1141 h.preconfigureServer()
946 1142
947 // Create temp file for transfer.
948 fileToTransfer := filepath.Join(t.TempDir(), "file.txt")
949 require.NoError(t, os.WriteFile(fileToTransfer, []byte("HELLO"), 0600))
950
951 h.requireReadLineExact("VERSION 1")1143 h.requireReadLineExact("VERSION 1")
952 h.requireWriteLine("INITREMOTE")1144 h.requireWriteLine("INITREMOTE")
953 h.requireReadLineExact("INITREMOTE-SUCCESS")1145 h.requireReadLineExact("INITREMOTE-SUCCESS")
@@ -955,10 +1147,12 @@ var localBackendTestCases = []testCase{ · 1 unmodified line
955 h.requireWriteLine("CHECKPRESENT SomeKey")1147 h.requireWriteLine("CHECKPRESENT SomeKey")
956 h.requireReadLineExact("CHECKPRESENT-FAILURE SomeKey")1148 h.requireReadLineExact("CHECKPRESENT-FAILURE SomeKey")
957 1149
958 require.NoFileExists(t, filepath.Join(h.localFsDir, "SomeKey"))1150 h.requireRemoteIsEmpty()
1151
959 h.requireWriteLine("REMOVE SomeKey")1152 h.requireWriteLine("REMOVE SomeKey")
960 h.requireReadLineExact("REMOVE-SUCCESS SomeKey")1153 h.requireReadLineExact("REMOVE-SUCCESS SomeKey")
961 require.NoFileExists(t, filepath.Join(h.localFsDir, "SomeKey"))1154
1155 h.requireRemoteIsEmpty()
962 1156
963 h.requireWriteLine("CHECKPRESENT SomeKey")1157 h.requireWriteLine("CHECKPRESENT SomeKey")
964 h.requireReadLineExact("CHECKPRESENT-FAILURE SomeKey")1158 h.requireReadLineExact("CHECKPRESENT-FAILURE SomeKey")
@@ -983,44 +1177,40 @@ var localBackendTestCases = []testCase{ · 18 unmodified lines
983 },1177 },
984}1178}
985 1179
986func TestGitAnnexLocalBackendCases(t *testing.T) {1180// TestMain drives the tests
987 for _, testCase := range localBackendTestCases {1181func TestMain(m *testing.M) {
988 // Clear global state left behind by tests that chdir to a temp directory.1182 fstest.TestMain(m)
989 cache.Clear()1183}
1184
1185// Run fstest-compatible test cases with backend selected by `-remote`.
1186func TestGitAnnexFstestBackendCases(t *testing.T) {
990 1187
1188 for _, testCase := range fstestTestCases {
991 // TODO: Remove this when rclone requires a Go version >= 1.22. Future1189 // TODO: Remove this when rclone requires a Go version >= 1.22. Future
992 // versions of Go fix the semantics of capturing a range variable.1190 // versions of Go fix the semantics of capturing a range variable.
993 // https://go.dev/blog/loopvar-preview1191 // https://go.dev/blog/loopvar-preview
994 testCase := testCase1192 testCase := testCase
995 1193
996 t.Run(testCase.label, func(t *testing.T) {1194 t.Run(testCase.label, func(t *testing.T) {
997 tempDir := t.TempDir()1195 r := fstest.NewRun(t)
1196 t.Cleanup(func() { r.Finalise() })
998 1197
999 // Create temp dir for an rclone remote pointing at local filesystem.1198 // Parse the fstest-provided remote string. It might have a path!
1000 localFsDir := filepath.Join(tempDir, "remoteTarget")1199 remoteName, remotePath, err := fspath.SplitFs(r.FremoteName)
1001 require.NoError(t, os.Mkdir(localFsDir, 0700))1200 require.NoError(t, err)
1002 1201
1003 // Create temp config1202 // The gitannex command requires the `rcloneremotename` is the name
1004 remoteName := getUniqueRemoteName(t)1203 // of a remote or exactly ":local", so the empty string will not
1005 configLines := []string{1204 // suffice.
1006 fmt.Sprintf("[%s]", remoteName),1205 if remoteName == "" {
1007 "type = local",1206 require.True(t, r.Fremote.Features().IsLocal)
1008 fmt.Sprintf("remote = %s", localFsDir),1207 remoteName = ":local"
1009 }1208 }
1010 configContents := strings.Join(configLines, "\n")
1011
1012 configPath := filepath.Join(tempDir, "rclone.conf")
1013 require.NoError(t, os.WriteFile(configPath, []byte(configContents), 0600))
1014 require.NoError(t, config.SetConfigPath(configPath))
1015
1016 // The custom config file will be ignored unless we install the
1017 // global config file handler.
1018 configfile.Install()
1019 1209
1020 handle := makeTestState(t)1210 handle := makeTestState(t)
1021 handle.localFsDir = localFsDir1211 handle.fstestRun = r
1022 handle.configPath = configPath
1023 handle.remoteName = remoteName1212 handle.remoteName = remoteName
1213 handle.remotePrefix = remotePath
1024 1214
1025 var wg sync.WaitGroup1215 var wg sync.WaitGroup
1026 wg.Add(1)1216 wg.Add(1)
@@ -1042,54 +1232,3 @@ func TestGitAnnexLocalBackendCases(t *testing.T) { · 15 unmodified lines
1042 })1232 })
1043 }1233 }
1044}1234}
1045
1046// Configure the git-annex client with a mockfs backend and send it the
1047// "INITREMOTE" command over mocked stdin. This should fail because mockfs does
1048// not support empty directories.
1049func TestGitAnnexHandleInitRemoteBackendDoesNotSupportEmptyDirectories(t *testing.T) {
1050 tempDir := t.TempDir()
1051
1052 // Temporarily override the filesystem registry.
1053 oldRegistry := fs.Registry
1054 mockfs.Register()
1055 defer func() { fs.Registry = oldRegistry }()
1056
1057 // Create temp dir for an rclone remote pointing at local filesystem.
1058 localFsDir := filepath.Join(tempDir, "remoteTarget")
1059 require.NoError(t, os.Mkdir(localFsDir, 0700))
1060
1061 // Create temp config
1062 remoteName := getUniqueRemoteName(t)
1063 configLines := []string{
1064 fmt.Sprintf("[%s]", remoteName),
1065 "type = mockfs",
1066 fmt.Sprintf("remote = %s", localFsDir),
1067 }
1068 configContents := strings.Join(configLines, "\n")
1069
1070 configPath := filepath.Join(tempDir, "rclone.conf")
1071 require.NoError(t, os.WriteFile(configPath, []byte(configContents), 0600))
1072
1073 // The custom config file will be ignored unless we install the global
1074 // config file handler.
1075 configfile.Install()
1076 require.NoError(t, config.SetConfigPath(configPath))
1077
1078 handle := makeTestState(t)
1079 handle.server.configPrefix = localFsDir
1080 handle.server.configRcloneRemoteName = remoteName
1081 handle.server.configsDone = true
1082
1083 var wg sync.WaitGroup
1084 wg.Add(1)
1085
1086 go func() {
1087 require.NotNil(t, handle.server.run())
1088 wg.Done()
1089 }()
1090 defer wg.Wait()
1091
1092 handle.requireReadLineExact("VERSION 1")
1093 handle.requireWriteLine("INITREMOTE")
1094 handle.requireReadLineExact("INITREMOTE-FAILURE this rclone remote does not support empty directories")
1095}
cmd/gitannex/layout.go+10−5
@@ -3,6 +3,8 @@ package gitannex · 2 unmodified lines
3import (3import (
4 "fmt"4 "fmt"
5 "strings"5 "strings"
6
7 "github.com/rclone/rclone/fs/fspath"
6)8)
7 9
8type layoutMode string10type layoutMode string
@@ -39,8 +41,11 @@ func parseLayoutMode(mode string) layoutMode { · 30 unmodified lines
39type queryDirhashFunc func(msg string) (string, error)41type queryDirhashFunc func(msg string) (string, error)
40 42
41func buildFsString(queryDirhash queryDirhashFunc, mode layoutMode, key, remoteName, prefix string) (string, error) {43func buildFsString(queryDirhash queryDirhashFunc, mode layoutMode, key, remoteName, prefix string) (string, error) {
44 remoteName = strings.TrimSuffix(remoteName, ":") + ":"
45 remoteString := fspath.JoinRootPath(remoteName, prefix)
46
42 if mode == layoutModeNodir {47 if mode == layoutModeNodir {
43 return fmt.Sprintf("%s:%s", remoteName, prefix), nil48 return remoteString, nil
44 }49 }
45 50
46 var dirhash string51 var dirhash string
@@ -59,13 +64,13 @@ func buildFsString(queryDirhash queryDirhashFunc, mode layoutMode, key, remoteNa · 12 unmodified lines
59 64
60 switch mode {65 switch mode {
61 case layoutModeLower:66 case layoutModeLower:
62 return fmt.Sprintf("%s:%s/%s", remoteName, prefix, dirhash), nil67 return fmt.Sprintf("%s/%s", remoteString, dirhash), nil
63 case layoutModeDirectory:68 case layoutModeDirectory:
64 return fmt.Sprintf("%s:%s/%s%s", remoteName, prefix, dirhash, key), nil69 return fmt.Sprintf("%s/%s%s", remoteString, dirhash, key), nil
65 case layoutModeMixed:70 case layoutModeMixed:
66 return fmt.Sprintf("%s:%s/%s", remoteName, prefix, dirhash), nil71 return fmt.Sprintf("%s/%s", remoteString, dirhash), nil
67 case layoutModeFrankencase:72 case layoutModeFrankencase:
68 return fmt.Sprintf("%s:%s/%s", remoteName, prefix, strings.ToLower(dirhash)), nil73 return fmt.Sprintf("%s/%s", remoteString, strings.ToLower(dirhash)), nil
69 default:74 default:
70 panic("unreachable")75 panic("unreachable")
71 }76 }

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t