github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/vfs/vfs_case_test.go (about)

     1  package vfs
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"testing"
     7  
     8  	"github.com/rclone/rclone/fs"
     9  	"github.com/rclone/rclone/fstest"
    10  	"github.com/rclone/rclone/vfs/vfscommon"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  	"golang.org/x/text/unicode/norm"
    14  )
    15  
    16  func TestCaseSensitivity(t *testing.T) {
    17  	r := fstest.NewRun(t)
    18  
    19  	if r.Fremote.Features().CaseInsensitive {
    20  		t.Skip("Can't test case sensitivity - this remote is officially not case-sensitive")
    21  	}
    22  
    23  	// Create test files
    24  	ctx := context.Background()
    25  	file1 := r.WriteObject(ctx, "FiLeA", "data1", t1)
    26  	file2 := r.WriteObject(ctx, "FiLeB", "data2", t2)
    27  	r.CheckRemoteItems(t, file1, file2)
    28  
    29  	// Create file3 with name differing from file2 name only by case.
    30  	// On a case-Sensitive remote this will be a separate file.
    31  	// On a case-INsensitive remote this file will either not exist
    32  	// or overwrite file2 depending on how file system diverges.
    33  	// On a box.com remote this step will even fail.
    34  	file3 := r.WriteObject(ctx, "FilEb", "data3", t3)
    35  
    36  	// Create a case-Sensitive and case-INsensitive VFS
    37  	optCS := vfscommon.DefaultOpt
    38  	optCS.CaseInsensitive = false
    39  	vfsCS := New(r.Fremote, &optCS)
    40  	defer cleanupVFS(t, vfsCS)
    41  
    42  	optCI := vfscommon.DefaultOpt
    43  	optCI.CaseInsensitive = true
    44  	vfsCI := New(r.Fremote, &optCI)
    45  	defer cleanupVFS(t, vfsCI)
    46  
    47  	// Run basic checks that must pass on VFS of any type.
    48  	assertFileDataVFS(t, vfsCI, "FiLeA", "data1")
    49  	assertFileDataVFS(t, vfsCS, "FiLeA", "data1")
    50  
    51  	// Detect case sensitivity of the underlying remote.
    52  	remoteIsOK := true
    53  	if !checkFileDataVFS(t, vfsCS, "FiLeA", "data1") {
    54  		remoteIsOK = false
    55  	}
    56  	if !checkFileDataVFS(t, vfsCS, "FiLeB", "data2") {
    57  		remoteIsOK = false
    58  	}
    59  	if !checkFileDataVFS(t, vfsCS, "FilEb", "data3") {
    60  		remoteIsOK = false
    61  	}
    62  
    63  	// The remaining test is only meaningful on a case-Sensitive file system.
    64  	if !remoteIsOK {
    65  		t.Skip("Can't test case sensitivity - this remote doesn't comply as case-sensitive")
    66  	}
    67  
    68  	// Continue with test as the underlying remote is fully case-Sensitive.
    69  	r.CheckRemoteItems(t, file1, file2, file3)
    70  
    71  	// See how VFS handles case-INsensitive flag
    72  	assertFileDataVFS(t, vfsCI, "FiLeA", "data1")
    73  	assertFileDataVFS(t, vfsCI, "fileA", "data1")
    74  	assertFileDataVFS(t, vfsCI, "filea", "data1")
    75  	assertFileDataVFS(t, vfsCI, "FILEA", "data1")
    76  
    77  	assertFileDataVFS(t, vfsCI, "FiLeB", "data2")
    78  	assertFileDataVFS(t, vfsCI, "FilEb", "data3")
    79  
    80  	fd, err := vfsCI.OpenFile("fileb", os.O_RDONLY, 0777)
    81  	assert.Nil(t, fd)
    82  	assert.Error(t, err)
    83  	assert.NotEqual(t, err, ENOENT)
    84  
    85  	fd, err = vfsCI.OpenFile("FILEB", os.O_RDONLY, 0777)
    86  	assert.Nil(t, fd)
    87  	assert.Error(t, err)
    88  	assert.NotEqual(t, err, ENOENT)
    89  
    90  	// Run the same set of checks with case-Sensitive VFS, for comparison.
    91  	assertFileDataVFS(t, vfsCS, "FiLeA", "data1")
    92  
    93  	assertFileAbsentVFS(t, vfsCS, "fileA")
    94  	assertFileAbsentVFS(t, vfsCS, "filea")
    95  	assertFileAbsentVFS(t, vfsCS, "FILEA")
    96  
    97  	assertFileDataVFS(t, vfsCS, "FiLeB", "data2")
    98  	assertFileDataVFS(t, vfsCS, "FilEb", "data3")
    99  
   100  	assertFileAbsentVFS(t, vfsCS, "fileb")
   101  	assertFileAbsentVFS(t, vfsCS, "FILEB")
   102  }
   103  
   104  func checkFileDataVFS(t *testing.T, vfs *VFS, name string, expect string) bool {
   105  	fd, err := vfs.OpenFile(name, os.O_RDONLY, 0777)
   106  	if fd == nil || err != nil {
   107  		return false
   108  	}
   109  	defer func() {
   110  		// File must be closed - otherwise Run.cleanUp() will fail on Windows.
   111  		_ = fd.Close()
   112  	}()
   113  
   114  	fh, ok := fd.(*ReadFileHandle)
   115  	if !ok {
   116  		return false
   117  	}
   118  
   119  	size := len(expect)
   120  	buf := make([]byte, size)
   121  	num, err := fh.Read(buf)
   122  	if err != nil || num != size {
   123  		return false
   124  	}
   125  
   126  	return string(buf) == expect
   127  }
   128  
   129  func assertFileDataVFS(t *testing.T, vfs *VFS, name string, expect string) {
   130  	fd, errOpen := vfs.OpenFile(name, os.O_RDONLY, 0777)
   131  	assert.NotNil(t, fd)
   132  	assert.NoError(t, errOpen)
   133  
   134  	defer func() {
   135  		// File must be closed - otherwise Run.cleanUp() will fail on Windows.
   136  		if errOpen == nil && fd != nil {
   137  			_ = fd.Close()
   138  		}
   139  	}()
   140  
   141  	fh, ok := fd.(*ReadFileHandle)
   142  	require.True(t, ok)
   143  
   144  	size := len(expect)
   145  	buf := make([]byte, size)
   146  	numRead, errRead := fh.Read(buf)
   147  	assert.NoError(t, errRead)
   148  	assert.Equal(t, numRead, size)
   149  
   150  	assert.Equal(t, string(buf), expect)
   151  }
   152  
   153  func assertFileAbsentVFS(t *testing.T, vfs *VFS, name string) {
   154  	fd, err := vfs.OpenFile(name, os.O_RDONLY, 0777)
   155  	defer func() {
   156  		// File must be closed - otherwise Run.cleanUp() will fail on Windows.
   157  		if err == nil && fd != nil {
   158  			_ = fd.Close()
   159  		}
   160  	}()
   161  	assert.Nil(t, fd)
   162  	assert.Error(t, err)
   163  	assert.Equal(t, err, ENOENT)
   164  }
   165  
   166  func TestUnicodeNormalization(t *testing.T) {
   167  	r := fstest.NewRun(t)
   168  
   169  	var (
   170  		nfc  = norm.NFC.String(norm.NFD.String("測試_Русский___ě_áñ"))
   171  		nfd  = norm.NFD.String(nfc)
   172  		both = "normal name with no special characters.txt"
   173  	)
   174  
   175  	// Create test files
   176  	ctx := context.Background()
   177  	file1 := r.WriteObject(ctx, both, "data1", t1)
   178  	file2 := r.WriteObject(ctx, nfc, "data2", t2)
   179  	r.CheckRemoteItems(t, file1, file2)
   180  
   181  	// Create VFS
   182  	opt := vfscommon.DefaultOpt
   183  	vfs := New(r.Fremote, &opt)
   184  	defer cleanupVFS(t, vfs)
   185  
   186  	// assert that both files are found under NFD-normalized names
   187  	assertFileDataVFS(t, vfs, norm.NFD.String(both), "data1")
   188  	assertFileDataVFS(t, vfs, nfd, "data2")
   189  
   190  	// change ci.NoUnicodeNormalization to true and verify that only file1 is found
   191  	ci := fs.GetConfig(ctx) // need to set the global config here as the *Dir methods don't take a ctx param
   192  	oldVal := ci.NoUnicodeNormalization
   193  	defer func() { fs.GetConfig(ctx).NoUnicodeNormalization = oldVal }() // restore the prior value after the test
   194  	ci.NoUnicodeNormalization = true
   195  	assertFileDataVFS(t, vfs, norm.NFD.String(both), "data1")
   196  	assertFileAbsentVFS(t, vfs, nfd)
   197  }