github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/lib/encoder/encoder_test.go (about)

     1  package encoder
     2  
     3  import (
     4  	"regexp"
     5  	"strconv"
     6  	"strings"
     7  	"testing"
     8  )
     9  
    10  type testCase struct {
    11  	mask uint
    12  	in   string
    13  	out  string
    14  }
    15  
    16  func TestEncodeSingleMask(t *testing.T) {
    17  	for i, tc := range testCasesSingle {
    18  		e := MultiEncoder(tc.mask)
    19  		t.Run(strconv.FormatInt(int64(i), 10), func(t *testing.T) {
    20  			got := e.Encode(tc.in)
    21  			if got != tc.out {
    22  				t.Errorf("Encode(%q) want %q got %q", tc.in, tc.out, got)
    23  			}
    24  			got2 := e.Decode(got)
    25  			if got2 != tc.in {
    26  				t.Errorf("Decode(%q) want %q got %q", got, tc.in, got2)
    27  			}
    28  		})
    29  	}
    30  }
    31  
    32  func TestEncodeSingleMaskEdge(t *testing.T) {
    33  	for i, tc := range testCasesSingleEdge {
    34  		e := MultiEncoder(tc.mask)
    35  		t.Run(strconv.FormatInt(int64(i), 10), func(t *testing.T) {
    36  			got := e.Encode(tc.in)
    37  			if got != tc.out {
    38  				t.Errorf("Encode(%q) want %q got %q", tc.in, tc.out, got)
    39  			}
    40  			got2 := e.Decode(got)
    41  			if got2 != tc.in {
    42  				t.Errorf("Decode(%q) want %q got %q", got, tc.in, got2)
    43  			}
    44  		})
    45  	}
    46  }
    47  
    48  func TestEncodeInvalidUnicode(t *testing.T) {
    49  	for i, tc := range []testCase{
    50  		{
    51  			mask: EncodeInvalidUtf8,
    52  			in:   "\xBF",
    53  			out:  "‛BF",
    54  		}, {
    55  			mask: EncodeInvalidUtf8,
    56  			in:   "\xBF\xFE",
    57  			out:  "‛BF‛FE",
    58  		}, {
    59  			mask: EncodeInvalidUtf8,
    60  			in:   "a\xBF\xFEb",
    61  			out:  "a‛BF‛FEb",
    62  		}, {
    63  			mask: EncodeInvalidUtf8,
    64  			in:   "a\xBFξ\xFEb",
    65  			out:  "a‛BFξ‛FEb",
    66  		}, {
    67  			mask: EncodeInvalidUtf8 | EncodeBackSlash,
    68  			in:   "a\xBF\\\xFEb",
    69  			out:  "a‛BF\‛FEb",
    70  		}, {
    71  			mask: 0,
    72  			in:   "\xBF",
    73  			out:  "\xBF",
    74  		}, {
    75  			mask: 0,
    76  			in:   "\xBF\xFE",
    77  			out:  "\xBF\xFE",
    78  		}, {
    79  			mask: 0,
    80  			in:   "a\xBF\xFEb",
    81  			out:  "a\xBF\xFEb",
    82  		}, {
    83  			mask: 0,
    84  			in:   "a\xBFξ\xFEb",
    85  			out:  "a\xBFξ\xFEb",
    86  		}, {
    87  			mask: EncodeBackSlash,
    88  			in:   "a\xBF\\\xFEb",
    89  			out:  "a\xBF\\xFEb",
    90  		},
    91  	} {
    92  		e := MultiEncoder(tc.mask)
    93  		t.Run(strconv.FormatInt(int64(i), 10), func(t *testing.T) {
    94  			got := e.Encode(tc.in)
    95  			if got != tc.out {
    96  				t.Errorf("Encode(%q) want %q got %q", tc.in, tc.out, got)
    97  			}
    98  			got2 := e.Decode(got)
    99  			if got2 != tc.in {
   100  				t.Errorf("Decode(%q) want %q got %q", got, tc.in, got2)
   101  			}
   102  		})
   103  	}
   104  }
   105  func TestDecodeHalf(t *testing.T) {
   106  	for i, tc := range []testCase{
   107  		{
   108  			mask: 0,
   109  			in:   "‛",
   110  			out:  "‛",
   111  		}, {
   112  			mask: 0,
   113  			in:   "‛‛",
   114  			out:  "‛",
   115  		}, {
   116  			mask: 0,
   117  			in:   "‛a‛",
   118  			out:  "‛a‛",
   119  		}, {
   120  			mask: EncodeInvalidUtf8,
   121  			in:   "a‛B‛Eg",
   122  			out:  "a‛B‛Eg",
   123  		}, {
   124  			mask: EncodeInvalidUtf8,
   125  			in:   "a‛B\‛Eg",
   126  			out:  "a‛B\‛Eg",
   127  		}, {
   128  			mask: EncodeInvalidUtf8 | EncodeBackSlash,
   129  			in:   "a‛B\‛Eg",
   130  			out:  "a‛B\\‛Eg",
   131  		},
   132  	} {
   133  		e := MultiEncoder(tc.mask)
   134  		t.Run(strconv.FormatInt(int64(i), 10), func(t *testing.T) {
   135  			got := e.Decode(tc.in)
   136  			if got != tc.out {
   137  				t.Errorf("Decode(%q) want %q got %q", tc.in, tc.out, got)
   138  			}
   139  		})
   140  	}
   141  }
   142  
   143  const oneDrive = MultiEncoder(
   144  	EncodeStandard |
   145  		EncodeWin |
   146  		EncodeBackSlash |
   147  		EncodeHashPercent |
   148  		EncodeDel |
   149  		EncodeCtl |
   150  		EncodeLeftTilde |
   151  		EncodeRightSpace |
   152  		EncodeRightPeriod)
   153  
   154  var benchTests = []struct {
   155  	in  string
   156  	out string
   157  }{
   158  	{"", ""},
   159  	{"abc 123", "abc 123"},
   160  	{`\*<>?:|#%".~`, `\*<>?:|#%".~`},
   161  	{`\*<>?:|#%".~/\*<>?:|#%".~`, `\*<>?:|#%".~/\*<>?:|#%".~`},
   162  	{" leading space", " leading space"},
   163  	{"~leading tilde", "~leading tilde"},
   164  	{"trailing dot.", "trailing dot."},
   165  	{" leading space/ leading space/ leading space", " leading space/ leading space/ leading space"},
   166  	{"~leading tilde/~leading tilde/~leading tilde", "~leading tilde/~leading tilde/~leading tilde"},
   167  	{"leading tilde/~leading tilde", "leading tilde/~leading tilde"},
   168  	{"trailing dot./trailing dot./trailing dot.", "trailing dot./trailing dot./trailing dot."},
   169  }
   170  
   171  func benchReplace(b *testing.B, f func(string) string) {
   172  	for range make([]struct{}, b.N) {
   173  		for _, test := range benchTests {
   174  			got := f(test.in)
   175  			if got != test.out {
   176  				b.Errorf("Encode(%q) want %q got %q", test.in, test.out, got)
   177  			}
   178  		}
   179  	}
   180  }
   181  
   182  func benchRestore(b *testing.B, f func(string) string) {
   183  	for range make([]struct{}, b.N) {
   184  		for _, test := range benchTests {
   185  			got := f(test.out)
   186  			if got != test.in {
   187  				b.Errorf("Decode(%q) want %q got %q", got, test.in, got)
   188  			}
   189  		}
   190  	}
   191  }
   192  func BenchmarkOneDriveReplaceNew(b *testing.B) {
   193  	benchReplace(b, oneDrive.Encode)
   194  }
   195  func BenchmarkOneDriveReplaceOld(b *testing.B) {
   196  	benchReplace(b, replaceReservedChars)
   197  }
   198  func BenchmarkOneDriveRestoreNew(b *testing.B) {
   199  	benchRestore(b, oneDrive.Decode)
   200  }
   201  func BenchmarkOneDriveRestoreOld(b *testing.B) {
   202  	benchRestore(b, restoreReservedChars)
   203  }
   204  
   205  var (
   206  	charMap = map[rune]rune{
   207  		'\\': '\', // FULLWIDTH REVERSE SOLIDUS
   208  		'*':  '*', // FULLWIDTH ASTERISK
   209  		'<':  '<', // FULLWIDTH LESS-THAN SIGN
   210  		'>':  '>', // FULLWIDTH GREATER-THAN SIGN
   211  		'?':  '?', // FULLWIDTH QUESTION MARK
   212  		':':  ':', // FULLWIDTH COLON
   213  		'|':  '|', // FULLWIDTH VERTICAL LINE
   214  		'#':  '#', // FULLWIDTH NUMBER SIGN
   215  		'%':  '%', // FULLWIDTH PERCENT SIGN
   216  		'"':  '"', // FULLWIDTH QUOTATION MARK - not on the list but seems to be reserved
   217  		'.':  '.', // FULLWIDTH FULL STOP
   218  		'~':  '~', // FULLWIDTH TILDE
   219  		' ':  '␠', // SYMBOL FOR SPACE
   220  	}
   221  	invCharMap           map[rune]rune
   222  	fixEndingInPeriod    = regexp.MustCompile(`\.(/|$)`)
   223  	fixEndingWithSpace   = regexp.MustCompile(` (/|$)`)
   224  	fixStartingWithTilde = regexp.MustCompile(`(/|^)~`)
   225  )
   226  
   227  func init() {
   228  	// Create inverse charMap
   229  	invCharMap = make(map[rune]rune, len(charMap))
   230  	for k, v := range charMap {
   231  		invCharMap[v] = k
   232  	}
   233  }
   234  
   235  // replaceReservedChars takes a path and substitutes any reserved
   236  // characters in it
   237  func replaceReservedChars(in string) string {
   238  	// Folder names can't end with a period '.'
   239  	in = fixEndingInPeriod.ReplaceAllString(in, string(charMap['.'])+"$1")
   240  	// OneDrive for Business file or folder names cannot begin with a tilde '~'
   241  	in = fixStartingWithTilde.ReplaceAllString(in, "$1"+string(charMap['~']))
   242  	// Apparently file names can't start with space either
   243  	in = fixEndingWithSpace.ReplaceAllString(in, string(charMap[' '])+"$1")
   244  	// Encode reserved characters
   245  	return strings.Map(func(c rune) rune {
   246  		if replacement, ok := charMap[c]; ok && c != '.' && c != '~' && c != ' ' {
   247  			return replacement
   248  		}
   249  		return c
   250  	}, in)
   251  }
   252  
   253  // restoreReservedChars takes a path and undoes any substitutions
   254  // made by replaceReservedChars
   255  func restoreReservedChars(in string) string {
   256  	return strings.Map(func(c rune) rune {
   257  		if replacement, ok := invCharMap[c]; ok {
   258  			return replacement
   259  		}
   260  		return c
   261  	}, in)
   262  }