2022-08-28 13:21:57 +02:00
|
|
|
|
// Package info provides the info test command.
|
2017-07-03 16:05:27 +02:00
|
|
|
|
package info
|
|
|
|
|
|
|
|
|
|
// FIXME once translations are implemented will need a no-escape
|
2020-08-08 19:02:18 +02:00
|
|
|
|
// option for Put so we can make these tests work again
|
2017-07-03 16:05:27 +02:00
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
2019-06-17 10:34:30 +02:00
|
|
|
|
"context"
|
2019-05-14 17:49:55 +02:00
|
|
|
|
"encoding/json"
|
2017-07-03 16:05:27 +02:00
|
|
|
|
"fmt"
|
2017-08-22 08:00:10 +02:00
|
|
|
|
"io"
|
2020-08-08 19:02:18 +02:00
|
|
|
|
"log"
|
2019-05-14 17:49:55 +02:00
|
|
|
|
"os"
|
|
|
|
|
"path"
|
|
|
|
|
"regexp"
|
2017-07-03 16:05:27 +02:00
|
|
|
|
"sort"
|
2019-05-14 17:49:55 +02:00
|
|
|
|
"strconv"
|
2017-07-03 16:05:27 +02:00
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
|
2019-07-28 19:47:38 +02:00
|
|
|
|
"github.com/rclone/rclone/cmd"
|
2020-08-08 19:02:18 +02:00
|
|
|
|
"github.com/rclone/rclone/cmd/test"
|
|
|
|
|
"github.com/rclone/rclone/cmd/test/info/internal"
|
2019-07-28 19:47:38 +02:00
|
|
|
|
"github.com/rclone/rclone/fs"
|
2019-10-11 17:55:04 +02:00
|
|
|
|
"github.com/rclone/rclone/fs/config/flags"
|
2019-07-28 19:47:38 +02:00
|
|
|
|
"github.com/rclone/rclone/fs/hash"
|
|
|
|
|
"github.com/rclone/rclone/fs/object"
|
2024-04-02 16:03:33 +02:00
|
|
|
|
"github.com/rclone/rclone/fs/operations"
|
2019-08-06 13:44:08 +02:00
|
|
|
|
"github.com/rclone/rclone/lib/random"
|
2017-07-03 16:05:27 +02:00
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
2019-05-14 17:49:55 +02:00
|
|
|
|
writeJSON string
|
2024-04-02 16:03:33 +02:00
|
|
|
|
keepTestFiles bool
|
2017-07-03 16:05:27 +02:00
|
|
|
|
checkNormalization bool
|
|
|
|
|
checkControl bool
|
|
|
|
|
checkLength bool
|
2017-08-22 08:00:10 +02:00
|
|
|
|
checkStreaming bool
|
2023-08-04 11:52:25 +02:00
|
|
|
|
checkBase32768 bool
|
2020-08-08 19:02:18 +02:00
|
|
|
|
all bool
|
2019-05-14 17:49:55 +02:00
|
|
|
|
uploadWait time.Duration
|
|
|
|
|
positionLeftRe = regexp.MustCompile(`(?s)^(.*)-position-left-([[:xdigit:]]+)$`)
|
|
|
|
|
positionMiddleRe = regexp.MustCompile(`(?s)^position-middle-([[:xdigit:]]+)-(.*)-$`)
|
|
|
|
|
positionRightRe = regexp.MustCompile(`(?s)^position-right-([[:xdigit:]]+)-(.*)$`)
|
2017-07-03 16:05:27 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func init() {
|
2020-08-08 19:02:18 +02:00
|
|
|
|
test.Command.AddCommand(commandDefinition)
|
2019-10-11 17:55:04 +02:00
|
|
|
|
cmdFlags := commandDefinition.Flags()
|
2023-07-10 19:34:10 +02:00
|
|
|
|
flags.StringVarP(cmdFlags, &writeJSON, "write-json", "", "", "Write results to file", "")
|
|
|
|
|
flags.BoolVarP(cmdFlags, &checkNormalization, "check-normalization", "", false, "Check UTF-8 Normalization", "")
|
|
|
|
|
flags.BoolVarP(cmdFlags, &checkControl, "check-control", "", false, "Check control characters", "")
|
|
|
|
|
flags.DurationVarP(cmdFlags, &uploadWait, "upload-wait", "", 0, "Wait after writing a file", "")
|
|
|
|
|
flags.BoolVarP(cmdFlags, &checkLength, "check-length", "", false, "Check max filename length", "")
|
|
|
|
|
flags.BoolVarP(cmdFlags, &checkStreaming, "check-streaming", "", false, "Check uploads with indeterminate file size", "")
|
2023-08-04 11:52:25 +02:00
|
|
|
|
flags.BoolVarP(cmdFlags, &checkBase32768, "check-base32768", "", false, "Check can store all possible base32768 characters", "")
|
2023-07-10 19:34:10 +02:00
|
|
|
|
flags.BoolVarP(cmdFlags, &all, "all", "", false, "Run all tests", "")
|
2024-04-02 16:03:33 +02:00
|
|
|
|
flags.BoolVarP(cmdFlags, &keepTestFiles, "keep-test-files", "", false, "Keep test files after execution", "")
|
2017-07-03 16:05:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-11 17:58:11 +02:00
|
|
|
|
var commandDefinition = &cobra.Command{
|
2017-07-03 16:05:27 +02:00
|
|
|
|
Use: "info [remote:path]+",
|
2017-08-22 08:00:10 +02:00
|
|
|
|
Short: `Discovers file name or other limitations for paths.`,
|
2024-08-12 18:17:46 +02:00
|
|
|
|
Long: `Discovers what filenames and upload methods are possible to write to the
|
|
|
|
|
paths passed in and how long they can be. It can take some time. It will
|
|
|
|
|
write test files into the remote:path passed in. It outputs a bit of go
|
|
|
|
|
code for each one.
|
2020-08-08 19:02:18 +02:00
|
|
|
|
|
|
|
|
|
**NB** this can create undeletable files and other hazards - use with care
|
2017-07-03 16:05:27 +02:00
|
|
|
|
`,
|
2022-11-26 23:40:49 +01:00
|
|
|
|
Annotations: map[string]string{
|
|
|
|
|
"versionIntroduced": "v1.55",
|
|
|
|
|
},
|
2017-07-03 16:05:27 +02:00
|
|
|
|
Run: func(command *cobra.Command, args []string) {
|
2019-09-05 14:59:06 +02:00
|
|
|
|
cmd.CheckArgs(1, 1e6, command, args)
|
2023-08-04 11:52:25 +02:00
|
|
|
|
if !checkNormalization && !checkControl && !checkLength && !checkStreaming && !checkBase32768 && !all {
|
2023-07-20 14:46:31 +02:00
|
|
|
|
log.Fatalf("no tests selected - select a test or use --all")
|
2020-08-08 19:02:18 +02:00
|
|
|
|
}
|
|
|
|
|
if all {
|
|
|
|
|
checkNormalization = true
|
|
|
|
|
checkControl = true
|
|
|
|
|
checkLength = true
|
|
|
|
|
checkStreaming = true
|
2023-08-04 11:52:25 +02:00
|
|
|
|
checkBase32768 = true
|
2020-08-08 19:02:18 +02:00
|
|
|
|
}
|
2017-07-03 16:05:27 +02:00
|
|
|
|
for i := range args {
|
2024-04-02 16:03:33 +02:00
|
|
|
|
tempDirName := "rclone-test-info-" + random.String(8)
|
|
|
|
|
tempDirPath := path.Join(args[i], tempDirName)
|
|
|
|
|
f := cmd.NewFsDir([]string{tempDirPath})
|
|
|
|
|
fs.Infof(f, "Created temporary directory for test files: %s", tempDirPath)
|
|
|
|
|
err := f.Mkdir(context.Background(), "")
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatalf("couldn't create temporary directory: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-03 16:05:27 +02:00
|
|
|
|
cmd.Run(false, false, command, func() error {
|
2019-06-17 10:34:30 +02:00
|
|
|
|
return readInfo(context.Background(), f)
|
2017-07-03 16:05:27 +02:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type results struct {
|
2019-06-17 10:34:30 +02:00
|
|
|
|
ctx context.Context
|
2017-07-03 16:05:27 +02:00
|
|
|
|
f fs.Fs
|
|
|
|
|
mu sync.Mutex
|
2019-05-14 17:49:55 +02:00
|
|
|
|
stringNeedsEscaping map[string]internal.Position
|
|
|
|
|
controlResults map[string]internal.ControlResult
|
2021-11-07 13:35:11 +01:00
|
|
|
|
maxFileLength [4]int
|
2017-07-03 16:05:27 +02:00
|
|
|
|
canWriteUnnormalized bool
|
|
|
|
|
canReadUnnormalized bool
|
|
|
|
|
canReadRenormalized bool
|
2017-08-22 08:00:10 +02:00
|
|
|
|
canStream bool
|
2023-08-04 11:52:25 +02:00
|
|
|
|
canBase32768 bool
|
2017-07-03 16:05:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-17 10:34:30 +02:00
|
|
|
|
func newResults(ctx context.Context, f fs.Fs) *results {
|
2017-07-03 16:05:27 +02:00
|
|
|
|
return &results{
|
2019-06-17 10:34:30 +02:00
|
|
|
|
ctx: ctx,
|
2018-11-02 13:12:09 +01:00
|
|
|
|
f: f,
|
2019-05-14 17:49:55 +02:00
|
|
|
|
stringNeedsEscaping: make(map[string]internal.Position),
|
|
|
|
|
controlResults: make(map[string]internal.ControlResult),
|
2017-07-03 16:05:27 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Print the results to stdout
|
|
|
|
|
func (r *results) Print() {
|
|
|
|
|
fmt.Printf("// %s\n", r.f.Name())
|
|
|
|
|
if checkControl {
|
|
|
|
|
escape := []string{}
|
2018-11-02 13:12:09 +01:00
|
|
|
|
for c, needsEscape := range r.stringNeedsEscaping {
|
2019-05-14 17:49:55 +02:00
|
|
|
|
if needsEscape != internal.PositionNone {
|
|
|
|
|
k := strconv.Quote(c)
|
|
|
|
|
k = k[1 : len(k)-1]
|
|
|
|
|
escape = append(escape, fmt.Sprintf("'%s'", k))
|
2017-07-03 16:05:27 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
sort.Strings(escape)
|
2019-05-14 17:49:55 +02:00
|
|
|
|
fmt.Printf("stringNeedsEscaping = []rune{\n")
|
2017-07-03 16:05:27 +02:00
|
|
|
|
fmt.Printf("\t%s\n", strings.Join(escape, ", "))
|
|
|
|
|
fmt.Printf("}\n")
|
|
|
|
|
}
|
|
|
|
|
if checkLength {
|
2021-11-07 13:35:11 +01:00
|
|
|
|
for i := range r.maxFileLength {
|
|
|
|
|
fmt.Printf("maxFileLength = %d // for %d byte unicode characters\n", r.maxFileLength[i], i+1)
|
|
|
|
|
}
|
2017-07-03 16:05:27 +02:00
|
|
|
|
}
|
|
|
|
|
if checkNormalization {
|
|
|
|
|
fmt.Printf("canWriteUnnormalized = %v\n", r.canWriteUnnormalized)
|
|
|
|
|
fmt.Printf("canReadUnnormalized = %v\n", r.canReadUnnormalized)
|
|
|
|
|
fmt.Printf("canReadRenormalized = %v\n", r.canReadRenormalized)
|
|
|
|
|
}
|
2017-08-22 08:00:10 +02:00
|
|
|
|
if checkStreaming {
|
|
|
|
|
fmt.Printf("canStream = %v\n", r.canStream)
|
|
|
|
|
}
|
2023-08-04 11:52:25 +02:00
|
|
|
|
if checkBase32768 {
|
|
|
|
|
fmt.Printf("base32768isOK = %v // make sure maxFileLength for 2 byte unicode chars is the same as for 1 byte characters\n", r.canBase32768)
|
|
|
|
|
}
|
2017-07-03 16:05:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-14 17:49:55 +02:00
|
|
|
|
// WriteJSON writes the results to a JSON file when requested
|
|
|
|
|
func (r *results) WriteJSON() {
|
|
|
|
|
if writeJSON == "" {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
report := internal.InfoReport{
|
|
|
|
|
Remote: r.f.Name(),
|
|
|
|
|
}
|
|
|
|
|
if checkControl {
|
|
|
|
|
report.ControlCharacters = &r.controlResults
|
|
|
|
|
}
|
|
|
|
|
if checkLength {
|
2021-11-07 13:35:11 +01:00
|
|
|
|
report.MaxFileLength = &r.maxFileLength[0]
|
2019-05-14 17:49:55 +02:00
|
|
|
|
}
|
|
|
|
|
if checkNormalization {
|
|
|
|
|
report.CanWriteUnnormalized = &r.canWriteUnnormalized
|
|
|
|
|
report.CanReadUnnormalized = &r.canReadUnnormalized
|
|
|
|
|
report.CanReadRenormalized = &r.canReadRenormalized
|
|
|
|
|
}
|
|
|
|
|
if checkStreaming {
|
|
|
|
|
report.CanStream = &r.canStream
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if f, err := os.Create(writeJSON); err != nil {
|
|
|
|
|
fs.Errorf(r.f, "Creating JSON file failed: %s", err)
|
|
|
|
|
} else {
|
|
|
|
|
defer fs.CheckClose(f, &err)
|
|
|
|
|
enc := json.NewEncoder(f)
|
|
|
|
|
enc.SetIndent("", " ")
|
|
|
|
|
err := enc.Encode(report)
|
|
|
|
|
if err != nil {
|
|
|
|
|
fs.Errorf(r.f, "Writing JSON file failed: %s", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fs.Infof(r.f, "Wrote JSON file: %s", writeJSON)
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-03 16:05:27 +02:00
|
|
|
|
// writeFile writes a file with some random contents
|
|
|
|
|
func (r *results) writeFile(path string) (fs.Object, error) {
|
2019-08-06 13:44:08 +02:00
|
|
|
|
contents := random.String(50)
|
2018-01-12 17:30:54 +01:00
|
|
|
|
src := object.NewStaticObjectInfo(path, time.Now(), int64(len(contents)), true, nil, r.f)
|
2019-05-14 17:49:55 +02:00
|
|
|
|
obj, err := r.f.Put(r.ctx, bytes.NewBufferString(contents), src)
|
|
|
|
|
if uploadWait > 0 {
|
|
|
|
|
time.Sleep(uploadWait)
|
|
|
|
|
}
|
|
|
|
|
return obj, err
|
2017-07-03 16:05:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// check whether normalization is enforced and check whether it is
|
|
|
|
|
// done on the files anyway
|
|
|
|
|
func (r *results) checkUTF8Normalization() {
|
|
|
|
|
unnormalized := "Héroique"
|
|
|
|
|
normalized := "Héroique"
|
|
|
|
|
_, err := r.writeFile(unnormalized)
|
|
|
|
|
if err != nil {
|
|
|
|
|
r.canWriteUnnormalized = false
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
r.canWriteUnnormalized = true
|
2019-06-17 10:34:30 +02:00
|
|
|
|
_, err = r.f.NewObject(r.ctx, unnormalized)
|
2017-07-03 16:05:27 +02:00
|
|
|
|
if err == nil {
|
|
|
|
|
r.canReadUnnormalized = true
|
|
|
|
|
}
|
2019-06-17 10:34:30 +02:00
|
|
|
|
_, err = r.f.NewObject(r.ctx, normalized)
|
2017-07-03 16:05:27 +02:00
|
|
|
|
if err == nil {
|
|
|
|
|
r.canReadRenormalized = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-14 17:49:55 +02:00
|
|
|
|
func (r *results) checkStringPositions(k, s string) {
|
2018-11-02 13:12:09 +01:00
|
|
|
|
fs.Infof(r.f, "Writing position file 0x%0X", s)
|
2019-05-14 17:49:55 +02:00
|
|
|
|
positionError := internal.PositionNone
|
|
|
|
|
res := internal.ControlResult{
|
|
|
|
|
Text: s,
|
|
|
|
|
WriteError: make(map[internal.Position]string, 3),
|
|
|
|
|
GetError: make(map[internal.Position]string, 3),
|
|
|
|
|
InList: make(map[internal.Position]internal.Presence, 3),
|
|
|
|
|
}
|
2018-11-02 13:12:09 +01:00
|
|
|
|
|
2019-05-14 17:49:55 +02:00
|
|
|
|
for _, pos := range internal.PositionList {
|
2018-11-02 13:12:09 +01:00
|
|
|
|
path := ""
|
|
|
|
|
switch pos {
|
2019-05-14 17:49:55 +02:00
|
|
|
|
case internal.PositionMiddle:
|
2018-11-02 13:12:09 +01:00
|
|
|
|
path = fmt.Sprintf("position-middle-%0X-%s-", s, s)
|
2019-05-14 17:49:55 +02:00
|
|
|
|
case internal.PositionLeft:
|
2018-11-02 13:12:09 +01:00
|
|
|
|
path = fmt.Sprintf("%s-position-left-%0X", s, s)
|
2019-05-14 17:49:55 +02:00
|
|
|
|
case internal.PositionRight:
|
2018-11-02 13:12:09 +01:00
|
|
|
|
path = fmt.Sprintf("position-right-%0X-%s", s, s)
|
|
|
|
|
default:
|
|
|
|
|
panic("invalid position: " + pos.String())
|
|
|
|
|
}
|
2019-05-14 17:49:55 +02:00
|
|
|
|
_, writeError := r.writeFile(path)
|
|
|
|
|
if writeError != nil {
|
|
|
|
|
res.WriteError[pos] = writeError.Error()
|
|
|
|
|
fs.Infof(r.f, "Writing %s position file 0x%0X Error: %s", pos.String(), s, writeError)
|
2018-11-02 13:12:09 +01:00
|
|
|
|
} else {
|
|
|
|
|
fs.Infof(r.f, "Writing %s position file 0x%0X OK", pos.String(), s)
|
|
|
|
|
}
|
2019-06-17 10:34:30 +02:00
|
|
|
|
obj, getErr := r.f.NewObject(r.ctx, path)
|
2018-11-02 13:12:09 +01:00
|
|
|
|
if getErr != nil {
|
2019-05-14 17:49:55 +02:00
|
|
|
|
res.GetError[pos] = getErr.Error()
|
2018-11-02 13:12:09 +01:00
|
|
|
|
fs.Infof(r.f, "Getting %s position file 0x%0X Error: %s", pos.String(), s, getErr)
|
|
|
|
|
} else {
|
|
|
|
|
if obj.Size() != 50 {
|
2019-05-14 17:49:55 +02:00
|
|
|
|
res.GetError[pos] = fmt.Sprintf("invalid size %d", obj.Size())
|
2018-11-02 13:12:09 +01:00
|
|
|
|
fs.Infof(r.f, "Getting %s position file 0x%0X Invalid Size: %d", pos.String(), s, obj.Size())
|
|
|
|
|
} else {
|
|
|
|
|
fs.Infof(r.f, "Getting %s position file 0x%0X OK", pos.String(), s)
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-14 17:49:55 +02:00
|
|
|
|
if writeError != nil || getErr != nil {
|
2018-11-02 13:12:09 +01:00
|
|
|
|
positionError += pos
|
|
|
|
|
}
|
2017-07-03 16:05:27 +02:00
|
|
|
|
}
|
2018-11-02 13:12:09 +01:00
|
|
|
|
|
2017-07-03 16:05:27 +02:00
|
|
|
|
r.mu.Lock()
|
2019-05-14 17:49:55 +02:00
|
|
|
|
r.stringNeedsEscaping[k] = positionError
|
|
|
|
|
r.controlResults[k] = res
|
2017-07-03 16:05:27 +02:00
|
|
|
|
r.mu.Unlock()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// check we can write a file with the control chars
|
|
|
|
|
func (r *results) checkControls() {
|
|
|
|
|
fs.Infof(r.f, "Trying to create control character file names")
|
2020-11-05 12:33:32 +01:00
|
|
|
|
ci := fs.GetConfig(context.Background())
|
|
|
|
|
|
2017-07-03 16:05:27 +02:00
|
|
|
|
// Concurrency control
|
2020-11-05 12:33:32 +01:00
|
|
|
|
tokens := make(chan struct{}, ci.Checkers)
|
|
|
|
|
for i := 0; i < ci.Checkers; i++ {
|
2017-07-03 16:05:27 +02:00
|
|
|
|
tokens <- struct{}{}
|
|
|
|
|
}
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
for i := rune(0); i < 128; i++ {
|
2018-11-02 13:12:09 +01:00
|
|
|
|
s := string(i)
|
2017-07-03 16:05:27 +02:00
|
|
|
|
if i == 0 || i == '/' {
|
|
|
|
|
// We're not even going to check NULL or /
|
2019-05-14 17:49:55 +02:00
|
|
|
|
r.stringNeedsEscaping[s] = internal.PositionAll
|
2017-07-03 16:05:27 +02:00
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
wg.Add(1)
|
2018-11-02 13:12:09 +01:00
|
|
|
|
go func(s string) {
|
2017-07-03 16:05:27 +02:00
|
|
|
|
defer wg.Done()
|
|
|
|
|
token := <-tokens
|
2019-05-14 17:49:55 +02:00
|
|
|
|
k := s
|
|
|
|
|
r.checkStringPositions(k, s)
|
2017-07-03 16:05:27 +02:00
|
|
|
|
tokens <- token
|
2018-11-02 13:12:09 +01:00
|
|
|
|
}(s)
|
|
|
|
|
}
|
2019-05-14 17:49:55 +02:00
|
|
|
|
for _, s := range []string{"\", "\u00A0", "\xBF", "\xFE"} {
|
2018-11-02 13:12:09 +01:00
|
|
|
|
wg.Add(1)
|
|
|
|
|
go func(s string) {
|
|
|
|
|
defer wg.Done()
|
|
|
|
|
token := <-tokens
|
2019-05-14 17:49:55 +02:00
|
|
|
|
k := s
|
|
|
|
|
r.checkStringPositions(k, s)
|
2018-11-02 13:12:09 +01:00
|
|
|
|
tokens <- token
|
|
|
|
|
}(s)
|
2017-07-03 16:05:27 +02:00
|
|
|
|
}
|
|
|
|
|
wg.Wait()
|
2019-05-14 17:49:55 +02:00
|
|
|
|
r.checkControlsList()
|
2017-07-03 16:05:27 +02:00
|
|
|
|
fs.Infof(r.f, "Done trying to create control character file names")
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-14 17:49:55 +02:00
|
|
|
|
func (r *results) checkControlsList() {
|
|
|
|
|
l, err := r.f.List(context.TODO(), "")
|
|
|
|
|
if err != nil {
|
|
|
|
|
fs.Errorf(r.f, "Listing control character file names failed: %s", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namesMap := make(map[string]struct{}, len(l))
|
|
|
|
|
for _, s := range l {
|
|
|
|
|
namesMap[path.Base(s.Remote())] = struct{}{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for path := range namesMap {
|
|
|
|
|
var pos internal.Position
|
|
|
|
|
var hex, value string
|
|
|
|
|
if g := positionLeftRe.FindStringSubmatch(path); g != nil {
|
|
|
|
|
pos, hex, value = internal.PositionLeft, g[2], g[1]
|
|
|
|
|
} else if g := positionMiddleRe.FindStringSubmatch(path); g != nil {
|
|
|
|
|
pos, hex, value = internal.PositionMiddle, g[1], g[2]
|
|
|
|
|
} else if g := positionRightRe.FindStringSubmatch(path); g != nil {
|
|
|
|
|
pos, hex, value = internal.PositionRight, g[1], g[2]
|
|
|
|
|
} else {
|
|
|
|
|
fs.Infof(r.f, "Unknown path %q", path)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
var hexValue []byte
|
|
|
|
|
for ; len(hex) >= 2; hex = hex[2:] {
|
|
|
|
|
if b, err := strconv.ParseUint(hex[:2], 16, 8); err != nil {
|
|
|
|
|
fs.Infof(r.f, "Invalid path %q: %s", path, err)
|
|
|
|
|
continue
|
|
|
|
|
} else {
|
|
|
|
|
hexValue = append(hexValue, byte(b))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if hex != "" {
|
|
|
|
|
fs.Infof(r.f, "Invalid path %q", path)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hexStr := string(hexValue)
|
|
|
|
|
k := hexStr
|
|
|
|
|
switch r.controlResults[k].InList[pos] {
|
|
|
|
|
case internal.Absent:
|
|
|
|
|
if hexStr == value {
|
|
|
|
|
r.controlResults[k].InList[pos] = internal.Present
|
|
|
|
|
} else {
|
|
|
|
|
r.controlResults[k].InList[pos] = internal.Renamed
|
|
|
|
|
}
|
|
|
|
|
case internal.Present:
|
|
|
|
|
r.controlResults[k].InList[pos] = internal.Multiple
|
|
|
|
|
case internal.Renamed:
|
|
|
|
|
r.controlResults[k].InList[pos] = internal.Multiple
|
|
|
|
|
}
|
|
|
|
|
delete(namesMap, path)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(namesMap) > 0 {
|
|
|
|
|
fs.Infof(r.f, "Found additional control character file names:")
|
|
|
|
|
for name := range namesMap {
|
|
|
|
|
fs.Infof(r.f, "%q", name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-03 16:05:27 +02:00
|
|
|
|
// find the max file name size we can use
|
2021-11-07 13:35:11 +01:00
|
|
|
|
func (r *results) findMaxLength(characterLength int) {
|
|
|
|
|
var character rune
|
|
|
|
|
switch characterLength {
|
|
|
|
|
case 1:
|
|
|
|
|
character = 'a'
|
|
|
|
|
case 2:
|
|
|
|
|
character = 'á'
|
|
|
|
|
case 3:
|
|
|
|
|
character = '世'
|
|
|
|
|
case 4:
|
|
|
|
|
character = '🙂'
|
|
|
|
|
default:
|
|
|
|
|
panic("Bad characterLength")
|
|
|
|
|
}
|
|
|
|
|
if characterLength != len(string(character)) {
|
|
|
|
|
panic(fmt.Sprintf("Chose the wrong character length %q is %d not %d", character, len(string(character)), characterLength))
|
|
|
|
|
}
|
2017-07-03 16:05:27 +02:00
|
|
|
|
const maxLen = 16 * 1024
|
2021-11-07 13:35:11 +01:00
|
|
|
|
name := make([]rune, maxLen)
|
2017-07-03 16:05:27 +02:00
|
|
|
|
for i := range name {
|
2021-11-07 13:35:11 +01:00
|
|
|
|
name[i] = character
|
2017-07-03 16:05:27 +02:00
|
|
|
|
}
|
|
|
|
|
// Find the first size of filename we can't write
|
|
|
|
|
i := sort.Search(len(name), func(i int) (fail bool) {
|
|
|
|
|
defer func() {
|
|
|
|
|
if err := recover(); err != nil {
|
|
|
|
|
fs.Infof(r.f, "Couldn't write file with name length %d: %v", i, err)
|
|
|
|
|
fail = true
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
path := string(name[:i])
|
2021-11-07 13:35:11 +01:00
|
|
|
|
o, err := r.writeFile(path)
|
2017-07-03 16:05:27 +02:00
|
|
|
|
if err != nil {
|
|
|
|
|
fs.Infof(r.f, "Couldn't write file with name length %d: %v", i, err)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
fs.Infof(r.f, "Wrote file with name length %d", i)
|
2021-11-07 13:35:11 +01:00
|
|
|
|
err = o.Remove(context.Background())
|
|
|
|
|
if err != nil {
|
|
|
|
|
fs.Errorf(o, "Failed to remove test file")
|
|
|
|
|
}
|
2017-07-03 16:05:27 +02:00
|
|
|
|
return false
|
|
|
|
|
})
|
2021-11-07 13:35:11 +01:00
|
|
|
|
r.maxFileLength[characterLength-1] = i - 1
|
|
|
|
|
fs.Infof(r.f, "Max file length is %d when writing %d byte characters %q", r.maxFileLength[characterLength-1], characterLength, character)
|
2017-07-03 16:05:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-22 08:00:10 +02:00
|
|
|
|
func (r *results) checkStreaming() {
|
|
|
|
|
putter := r.f.Put
|
|
|
|
|
if r.f.Features().PutStream != nil {
|
|
|
|
|
fs.Infof(r.f, "Given remote has specialized streaming function. Using that to test streaming.")
|
|
|
|
|
putter = r.f.Features().PutStream
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
contents := "thinking of test strings is hard"
|
|
|
|
|
buf := bytes.NewBufferString(contents)
|
2018-01-12 17:30:54 +01:00
|
|
|
|
hashIn := hash.NewMultiHasher()
|
2017-08-22 08:00:10 +02:00
|
|
|
|
in := io.TeeReader(buf, hashIn)
|
|
|
|
|
|
2018-01-12 17:30:54 +01:00
|
|
|
|
objIn := object.NewStaticObjectInfo("checkStreamingTest", time.Now(), -1, true, nil, r.f)
|
2019-06-17 10:34:30 +02:00
|
|
|
|
objR, err := putter(r.ctx, in, objIn)
|
2017-08-22 08:00:10 +02:00
|
|
|
|
if err != nil {
|
|
|
|
|
fs.Infof(r.f, "Streamed file failed to upload (%v)", err)
|
|
|
|
|
r.canStream = false
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hashes := hashIn.Sums()
|
|
|
|
|
types := objR.Fs().Hashes().Array()
|
2018-01-12 17:30:54 +01:00
|
|
|
|
for _, Hash := range types {
|
2019-06-17 10:34:30 +02:00
|
|
|
|
sum, err := objR.Hash(r.ctx, Hash)
|
2017-08-22 08:00:10 +02:00
|
|
|
|
if err != nil {
|
2018-01-12 17:30:54 +01:00
|
|
|
|
fs.Infof(r.f, "Streamed file failed when getting hash %v (%v)", Hash, err)
|
2017-08-22 08:00:10 +02:00
|
|
|
|
r.canStream = false
|
|
|
|
|
return
|
|
|
|
|
}
|
2018-01-12 17:30:54 +01:00
|
|
|
|
if !hash.Equals(hashes[Hash], sum) {
|
|
|
|
|
fs.Infof(r.f, "Streamed file has incorrect hash %v: expecting %q got %q", Hash, hashes[Hash], sum)
|
2017-08-22 08:00:10 +02:00
|
|
|
|
r.canStream = false
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if int64(len(contents)) != objR.Size() {
|
|
|
|
|
fs.Infof(r.f, "Streamed file has incorrect file size: expecting %d got %d", len(contents), objR.Size())
|
|
|
|
|
r.canStream = false
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
r.canStream = true
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-17 10:34:30 +02:00
|
|
|
|
func readInfo(ctx context.Context, f fs.Fs) error {
|
2024-04-02 16:03:33 +02:00
|
|
|
|
// Ensure cleanup unless --keep-test-files is specified
|
|
|
|
|
if !keepTestFiles {
|
|
|
|
|
defer func() {
|
|
|
|
|
err := operations.Purge(ctx, f, "")
|
|
|
|
|
if err != nil {
|
|
|
|
|
fs.Errorf(f, "Failed to purge temporary directory: %v", err)
|
|
|
|
|
} else {
|
|
|
|
|
fs.Infof(f, "Removed temporary directory for test files: %s", f.Root())
|
|
|
|
|
}
|
|
|
|
|
}()
|
2017-07-03 16:05:27 +02:00
|
|
|
|
}
|
2024-04-02 16:03:33 +02:00
|
|
|
|
|
2019-06-17 10:34:30 +02:00
|
|
|
|
r := newResults(ctx, f)
|
2017-07-03 16:05:27 +02:00
|
|
|
|
if checkControl {
|
|
|
|
|
r.checkControls()
|
|
|
|
|
}
|
|
|
|
|
if checkLength {
|
2021-11-07 13:35:11 +01:00
|
|
|
|
for i := range r.maxFileLength {
|
|
|
|
|
r.findMaxLength(i + 1)
|
|
|
|
|
}
|
2017-07-03 16:05:27 +02:00
|
|
|
|
}
|
|
|
|
|
if checkNormalization {
|
|
|
|
|
r.checkUTF8Normalization()
|
|
|
|
|
}
|
2017-08-22 08:00:10 +02:00
|
|
|
|
if checkStreaming {
|
|
|
|
|
r.checkStreaming()
|
|
|
|
|
}
|
2023-08-04 11:52:25 +02:00
|
|
|
|
if checkBase32768 {
|
|
|
|
|
r.checkBase32768()
|
|
|
|
|
}
|
2017-07-03 16:05:27 +02:00
|
|
|
|
r.Print()
|
2019-05-14 17:49:55 +02:00
|
|
|
|
r.WriteJSON()
|
2017-07-03 16:05:27 +02:00
|
|
|
|
return nil
|
|
|
|
|
}
|