mirror of
https://github.com/octoleo/syncthing.git
synced 2024-11-09 23:00:58 +00:00
674fc566bb
1. Removes separate relay lists and relay clients/services, just makes it a listen address 2. Easier plugging-in of other transports 3. Allows "hot" disabling and enabling NAT services 4. Allows "hot" listen address changes 5. Changes listen address list with a preferable "default" value just like for discovery 6. Debounces global discovery announcements as external addresses change (which it might alot upon starting) 7. Stops this whole "pick other peers relay by latency". This information is no longer available, but I don't think it matters as most of the time other peer only has one relay. 8. Rename ListenAddress to ListenAddresses, as well as in javascript land. 9. Stop serializing deprecated values to JSON GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2982
1406 lines
34 KiB
Go
1406 lines
34 KiB
Go
// Copyright (C) 2014 The Syncthing Authors.
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package model
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"math/rand"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/d4l3k/messagediff"
|
|
"github.com/syncthing/syncthing/lib/config"
|
|
"github.com/syncthing/syncthing/lib/connections"
|
|
"github.com/syncthing/syncthing/lib/db"
|
|
"github.com/syncthing/syncthing/lib/protocol"
|
|
)
|
|
|
|
var device1, device2 protocol.DeviceID
|
|
var defaultConfig *config.Wrapper
|
|
var defaultFolderConfig config.FolderConfiguration
|
|
|
|
func init() {
|
|
device1, _ = protocol.DeviceIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR")
|
|
device2, _ = protocol.DeviceIDFromString("GYRZZQB-IRNPV4Z-T7TC52W-EQYJ3TT-FDQW6MW-DFLMU42-SSSU6EM-FBK2VAY")
|
|
|
|
defaultFolderConfig = config.NewFolderConfiguration("default", "testdata")
|
|
defaultFolderConfig.Devices = []config.FolderDeviceConfiguration{{DeviceID: device1}}
|
|
_defaultConfig := config.Configuration{
|
|
Folders: []config.FolderConfiguration{defaultFolderConfig},
|
|
Devices: []config.DeviceConfiguration{config.NewDeviceConfiguration(device1, "device1")},
|
|
Options: config.OptionsConfiguration{
|
|
// Don't remove temporaries directly on startup
|
|
KeepTemporariesH: 1,
|
|
},
|
|
}
|
|
defaultConfig = config.Wrap("/tmp/test", _defaultConfig)
|
|
}
|
|
|
|
var testDataExpected = map[string]protocol.FileInfo{
|
|
"foo": {
|
|
Name: "foo",
|
|
Flags: 0,
|
|
Modified: 0,
|
|
Blocks: []protocol.BlockInfo{{Offset: 0x0, Size: 0x7, Hash: []uint8{0xae, 0xc0, 0x70, 0x64, 0x5f, 0xe5, 0x3e, 0xe3, 0xb3, 0x76, 0x30, 0x59, 0x37, 0x61, 0x34, 0xf0, 0x58, 0xcc, 0x33, 0x72, 0x47, 0xc9, 0x78, 0xad, 0xd1, 0x78, 0xb6, 0xcc, 0xdf, 0xb0, 0x1, 0x9f}}},
|
|
},
|
|
"empty": {
|
|
Name: "empty",
|
|
Flags: 0,
|
|
Modified: 0,
|
|
Blocks: []protocol.BlockInfo{{Offset: 0x0, Size: 0x0, Hash: []uint8{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}}},
|
|
},
|
|
"bar": {
|
|
Name: "bar",
|
|
Flags: 0,
|
|
Modified: 0,
|
|
Blocks: []protocol.BlockInfo{{Offset: 0x0, Size: 0xa, Hash: []uint8{0x2f, 0x72, 0xcc, 0x11, 0xa6, 0xfc, 0xd0, 0x27, 0x1e, 0xce, 0xf8, 0xc6, 0x10, 0x56, 0xee, 0x1e, 0xb1, 0x24, 0x3b, 0xe3, 0x80, 0x5b, 0xf9, 0xa9, 0xdf, 0x98, 0xf9, 0x2f, 0x76, 0x36, 0xb0, 0x5c}}},
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
// Fix expected test data to match reality
|
|
for n, f := range testDataExpected {
|
|
fi, _ := os.Stat("testdata/" + n)
|
|
f.Flags = uint32(fi.Mode())
|
|
f.Modified = fi.ModTime().Unix()
|
|
testDataExpected[n] = f
|
|
}
|
|
}
|
|
|
|
func TestRequest(t *testing.T) {
|
|
db := db.OpenMemory()
|
|
|
|
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
|
|
|
// device1 shares default, but device2 doesn't
|
|
m.AddFolder(defaultFolderConfig)
|
|
m.StartFolder("default")
|
|
m.ServeBackground()
|
|
m.ScanFolder("default")
|
|
|
|
bs := make([]byte, protocol.BlockSize)
|
|
|
|
// Existing, shared file
|
|
bs = bs[:6]
|
|
err := m.Request(device1, "default", "foo", 0, nil, 0, nil, bs)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if !bytes.Equal(bs, []byte("foobar")) {
|
|
t.Errorf("Incorrect data from request: %q", string(bs))
|
|
}
|
|
|
|
// Existing, nonshared file
|
|
err = m.Request(device2, "default", "foo", 0, nil, 0, nil, bs)
|
|
if err == nil {
|
|
t.Error("Unexpected nil error on insecure file read")
|
|
}
|
|
|
|
// Nonexistent file
|
|
err = m.Request(device1, "default", "nonexistent", 0, nil, 0, nil, bs)
|
|
if err == nil {
|
|
t.Error("Unexpected nil error on insecure file read")
|
|
}
|
|
|
|
// Shared folder, but disallowed file name
|
|
err = m.Request(device1, "default", "../walk.go", 0, nil, 0, nil, bs)
|
|
if err == nil {
|
|
t.Error("Unexpected nil error on insecure file read")
|
|
}
|
|
|
|
// Negative offset
|
|
err = m.Request(device1, "default", "foo", -4, nil, 0, nil, bs[:0])
|
|
if err == nil {
|
|
t.Error("Unexpected nil error on insecure file read")
|
|
}
|
|
|
|
// Larger block than available
|
|
bs = bs[:42]
|
|
err = m.Request(device1, "default", "foo", 0, nil, 0, nil, bs)
|
|
if err == nil {
|
|
t.Error("Unexpected nil error on insecure file read")
|
|
}
|
|
}
|
|
|
|
func genFiles(n int) []protocol.FileInfo {
|
|
files := make([]protocol.FileInfo, n)
|
|
t := time.Now().Unix()
|
|
for i := 0; i < n; i++ {
|
|
files[i] = protocol.FileInfo{
|
|
Name: fmt.Sprintf("file%d", i),
|
|
Modified: t,
|
|
Blocks: []protocol.BlockInfo{{0, 100, []byte("some hash bytes")}},
|
|
}
|
|
}
|
|
|
|
return files
|
|
}
|
|
|
|
func BenchmarkIndex_10000(b *testing.B) {
|
|
benchmarkIndex(b, 10000)
|
|
}
|
|
|
|
func BenchmarkIndex_100(b *testing.B) {
|
|
benchmarkIndex(b, 100)
|
|
}
|
|
|
|
func benchmarkIndex(b *testing.B, nfiles int) {
|
|
db := db.OpenMemory()
|
|
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
|
m.AddFolder(defaultFolderConfig)
|
|
m.StartFolder("default")
|
|
m.ServeBackground()
|
|
|
|
files := genFiles(nfiles)
|
|
m.Index(device1, "default", files, 0, nil)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
m.Index(device1, "default", files, 0, nil)
|
|
}
|
|
b.ReportAllocs()
|
|
}
|
|
|
|
func BenchmarkIndexUpdate_10000_10000(b *testing.B) {
|
|
benchmarkIndexUpdate(b, 10000, 10000)
|
|
}
|
|
|
|
func BenchmarkIndexUpdate_10000_100(b *testing.B) {
|
|
benchmarkIndexUpdate(b, 10000, 100)
|
|
}
|
|
|
|
func BenchmarkIndexUpdate_10000_1(b *testing.B) {
|
|
benchmarkIndexUpdate(b, 10000, 1)
|
|
}
|
|
|
|
func benchmarkIndexUpdate(b *testing.B, nfiles, nufiles int) {
|
|
db := db.OpenMemory()
|
|
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
|
m.AddFolder(defaultFolderConfig)
|
|
m.StartFolder("default")
|
|
m.ServeBackground()
|
|
|
|
files := genFiles(nfiles)
|
|
ufiles := genFiles(nufiles)
|
|
|
|
m.Index(device1, "default", files, 0, nil)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
m.IndexUpdate(device1, "default", ufiles, 0, nil)
|
|
}
|
|
b.ReportAllocs()
|
|
}
|
|
|
|
type downloadProgressMessage struct {
|
|
folder string
|
|
updates []protocol.FileDownloadProgressUpdate
|
|
flags uint32
|
|
options []protocol.Option
|
|
}
|
|
|
|
type FakeConnection struct {
|
|
id protocol.DeviceID
|
|
requestData []byte
|
|
downloadProgressMessages []downloadProgressMessage
|
|
}
|
|
|
|
func (FakeConnection) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func (f FakeConnection) Start() {
|
|
}
|
|
|
|
func (f FakeConnection) ID() protocol.DeviceID {
|
|
return f.id
|
|
}
|
|
|
|
func (f FakeConnection) Name() string {
|
|
return ""
|
|
}
|
|
|
|
func (f FakeConnection) Option(string) string {
|
|
return ""
|
|
}
|
|
|
|
func (FakeConnection) Index(string, []protocol.FileInfo, uint32, []protocol.Option) error {
|
|
return nil
|
|
}
|
|
|
|
func (FakeConnection) IndexUpdate(string, []protocol.FileInfo, uint32, []protocol.Option) error {
|
|
return nil
|
|
}
|
|
|
|
func (f FakeConnection) Request(folder, name string, offset int64, size int, hash []byte, fromTemporary bool) ([]byte, error) {
|
|
return f.requestData, nil
|
|
}
|
|
|
|
func (FakeConnection) ClusterConfig(protocol.ClusterConfigMessage) {}
|
|
|
|
func (FakeConnection) Ping() bool {
|
|
return true
|
|
}
|
|
|
|
func (FakeConnection) Closed() bool {
|
|
return false
|
|
}
|
|
|
|
func (FakeConnection) Statistics() protocol.Statistics {
|
|
return protocol.Statistics{}
|
|
}
|
|
|
|
func (f *FakeConnection) DownloadProgress(folder string, updates []protocol.FileDownloadProgressUpdate, flags uint32, options []protocol.Option) {
|
|
f.downloadProgressMessages = append(f.downloadProgressMessages, downloadProgressMessage{
|
|
folder: folder,
|
|
updates: updates,
|
|
flags: flags,
|
|
options: options,
|
|
})
|
|
}
|
|
|
|
func BenchmarkRequest(b *testing.B) {
|
|
db := db.OpenMemory()
|
|
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
|
m.AddFolder(defaultFolderConfig)
|
|
m.ServeBackground()
|
|
m.ScanFolder("default")
|
|
|
|
const n = 1000
|
|
files := make([]protocol.FileInfo, n)
|
|
t := time.Now().Unix()
|
|
for i := 0; i < n; i++ {
|
|
files[i] = protocol.FileInfo{
|
|
Name: fmt.Sprintf("file%d", i),
|
|
Modified: t,
|
|
Blocks: []protocol.BlockInfo{{0, 100, []byte("some hash bytes")}},
|
|
}
|
|
}
|
|
|
|
fc := &FakeConnection{
|
|
id: device1,
|
|
requestData: []byte("some data to return"),
|
|
}
|
|
m.AddConnection(connections.Connection{
|
|
IntermediateConnection: connections.IntermediateConnection{
|
|
Conn: tls.Client(&fakeConn{}, nil),
|
|
Type: "foo",
|
|
Priority: 10,
|
|
},
|
|
Connection: fc,
|
|
}, protocol.HelloMessage{})
|
|
m.Index(device1, "default", files, 0, nil)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
data, err := m.requestGlobal(device1, "default", files[i%n].Name, 0, 32, nil, false)
|
|
if err != nil {
|
|
b.Error(err)
|
|
}
|
|
if data == nil {
|
|
b.Error("nil data")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDeviceRename(t *testing.T) {
|
|
hello := protocol.HelloMessage{
|
|
ClientName: "syncthing",
|
|
ClientVersion: "v0.9.4",
|
|
}
|
|
defer os.Remove("tmpconfig.xml")
|
|
|
|
rawCfg := config.New(device1)
|
|
rawCfg.Devices = []config.DeviceConfiguration{
|
|
{
|
|
DeviceID: device1,
|
|
},
|
|
}
|
|
cfg := config.Wrap("tmpconfig.xml", rawCfg)
|
|
|
|
db := db.OpenMemory()
|
|
m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
|
|
|
if cfg.Devices()[device1].Name != "" {
|
|
t.Errorf("Device already has a name")
|
|
}
|
|
|
|
conn := connections.Connection{
|
|
IntermediateConnection: connections.IntermediateConnection{
|
|
Conn: tls.Client(&fakeConn{}, nil),
|
|
Type: "foo",
|
|
Priority: 10,
|
|
},
|
|
Connection: &FakeConnection{
|
|
id: device1,
|
|
requestData: []byte("some data to return"),
|
|
},
|
|
}
|
|
|
|
m.AddConnection(conn, hello)
|
|
|
|
m.ServeBackground()
|
|
|
|
if cfg.Devices()[device1].Name != "" {
|
|
t.Errorf("Device already has a name")
|
|
}
|
|
|
|
m.Close(device1, protocol.ErrTimeout)
|
|
hello.DeviceName = "tester"
|
|
m.AddConnection(conn, hello)
|
|
|
|
if cfg.Devices()[device1].Name != "tester" {
|
|
t.Errorf("Device did not get a name")
|
|
}
|
|
|
|
m.Close(device1, protocol.ErrTimeout)
|
|
hello.DeviceName = "tester2"
|
|
m.AddConnection(conn, hello)
|
|
|
|
if cfg.Devices()[device1].Name != "tester" {
|
|
t.Errorf("Device name got overwritten")
|
|
}
|
|
|
|
cfgw, err := config.Load("tmpconfig.xml", protocol.LocalDeviceID)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
if cfgw.Devices()[device1].Name != "tester" {
|
|
t.Errorf("Device name not saved in config")
|
|
}
|
|
|
|
m.Close(device1, protocol.ErrTimeout)
|
|
|
|
opts := cfg.Options()
|
|
opts.OverwriteNames = true
|
|
cfg.SetOptions(opts)
|
|
|
|
hello.DeviceName = "tester2"
|
|
m.AddConnection(conn, hello)
|
|
|
|
if cfg.Devices()[device1].Name != "tester2" {
|
|
t.Errorf("Device name not overwritten")
|
|
}
|
|
}
|
|
|
|
func TestClusterConfig(t *testing.T) {
|
|
cfg := config.New(device1)
|
|
cfg.Devices = []config.DeviceConfiguration{
|
|
{
|
|
DeviceID: device1,
|
|
Introducer: true,
|
|
},
|
|
{
|
|
DeviceID: device2,
|
|
},
|
|
}
|
|
cfg.Folders = []config.FolderConfiguration{
|
|
{
|
|
ID: "folder1",
|
|
Devices: []config.FolderDeviceConfiguration{
|
|
{DeviceID: device1},
|
|
{DeviceID: device2},
|
|
},
|
|
},
|
|
{
|
|
ID: "folder2",
|
|
Devices: []config.FolderDeviceConfiguration{
|
|
{DeviceID: device1},
|
|
{DeviceID: device2},
|
|
},
|
|
},
|
|
}
|
|
|
|
db := db.OpenMemory()
|
|
|
|
m := NewModel(config.Wrap("/tmp/test", cfg), protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
|
m.AddFolder(cfg.Folders[0])
|
|
m.AddFolder(cfg.Folders[1])
|
|
m.ServeBackground()
|
|
|
|
cm := m.generateClusterConfig(device2)
|
|
|
|
if l := len(cm.Folders); l != 2 {
|
|
t.Fatalf("Incorrect number of folders %d != 2", l)
|
|
}
|
|
|
|
r := cm.Folders[0]
|
|
if r.ID != "folder1" {
|
|
t.Errorf("Incorrect folder %q != folder1", r.ID)
|
|
}
|
|
if l := len(r.Devices); l != 2 {
|
|
t.Errorf("Incorrect number of devices %d != 2", l)
|
|
}
|
|
if id := r.Devices[0].ID; !bytes.Equal(id, device1[:]) {
|
|
t.Errorf("Incorrect device ID %x != %x", id, device1)
|
|
}
|
|
if r.Devices[0].Flags&protocol.FlagIntroducer == 0 {
|
|
t.Error("Device1 should be flagged as Introducer")
|
|
}
|
|
if id := r.Devices[1].ID; !bytes.Equal(id, device2[:]) {
|
|
t.Errorf("Incorrect device ID %x != %x", id, device2)
|
|
}
|
|
if r.Devices[1].Flags&protocol.FlagIntroducer != 0 {
|
|
t.Error("Device2 should not be flagged as Introducer")
|
|
}
|
|
|
|
r = cm.Folders[1]
|
|
if r.ID != "folder2" {
|
|
t.Errorf("Incorrect folder %q != folder2", r.ID)
|
|
}
|
|
if l := len(r.Devices); l != 2 {
|
|
t.Errorf("Incorrect number of devices %d != 2", l)
|
|
}
|
|
if id := r.Devices[0].ID; !bytes.Equal(id, device1[:]) {
|
|
t.Errorf("Incorrect device ID %x != %x", id, device1)
|
|
}
|
|
if r.Devices[0].Flags&protocol.FlagIntroducer == 0 {
|
|
t.Error("Device1 should be flagged as Introducer")
|
|
}
|
|
if id := r.Devices[1].ID; !bytes.Equal(id, device2[:]) {
|
|
t.Errorf("Incorrect device ID %x != %x", id, device2)
|
|
}
|
|
if r.Devices[1].Flags&protocol.FlagIntroducer != 0 {
|
|
t.Error("Device2 should not be flagged as Introducer")
|
|
}
|
|
}
|
|
|
|
func TestIgnores(t *testing.T) {
|
|
arrEqual := func(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Assure a clean start state
|
|
ioutil.WriteFile("testdata/.stfolder", nil, 0644)
|
|
ioutil.WriteFile("testdata/.stignore", []byte(".*\nquux\n"), 0644)
|
|
|
|
db := db.OpenMemory()
|
|
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
|
m.AddFolder(defaultFolderConfig)
|
|
m.StartFolder("default")
|
|
m.ServeBackground()
|
|
|
|
expected := []string{
|
|
".*",
|
|
"quux",
|
|
}
|
|
|
|
ignores, _, err := m.GetIgnores("default")
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
if !arrEqual(ignores, expected) {
|
|
t.Errorf("Incorrect ignores: %v != %v", ignores, expected)
|
|
}
|
|
|
|
ignores = append(ignores, "pox")
|
|
|
|
err = m.SetIgnores("default", ignores)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
ignores2, _, err := m.GetIgnores("default")
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
if arrEqual(expected, ignores2) {
|
|
t.Errorf("Incorrect ignores: %v == %v", ignores2, expected)
|
|
}
|
|
|
|
if !arrEqual(ignores, ignores2) {
|
|
t.Errorf("Incorrect ignores: %v != %v", ignores2, ignores)
|
|
}
|
|
|
|
err = m.SetIgnores("default", expected)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
ignores, _, err = m.GetIgnores("default")
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
if !arrEqual(ignores, expected) {
|
|
t.Errorf("Incorrect ignores: %v != %v", ignores, expected)
|
|
}
|
|
|
|
ignores, _, err = m.GetIgnores("doesnotexist")
|
|
if err == nil {
|
|
t.Error("No error")
|
|
}
|
|
|
|
err = m.SetIgnores("doesnotexist", expected)
|
|
if err == nil {
|
|
t.Error("No error")
|
|
}
|
|
|
|
// Invalid path, marker should be missing, hence returns an error.
|
|
m.AddFolder(config.FolderConfiguration{ID: "fresh", RawPath: "XXX"})
|
|
ignores, _, err = m.GetIgnores("fresh")
|
|
if err == nil {
|
|
t.Error("No error")
|
|
}
|
|
}
|
|
|
|
func TestRefuseUnknownBits(t *testing.T) {
|
|
db := db.OpenMemory()
|
|
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
|
m.AddFolder(defaultFolderConfig)
|
|
m.ServeBackground()
|
|
|
|
m.ScanFolder("default")
|
|
m.Index(device1, "default", []protocol.FileInfo{
|
|
{
|
|
Name: "invalid1",
|
|
Flags: (protocol.FlagsAll + 1) &^ protocol.FlagInvalid,
|
|
},
|
|
{
|
|
Name: "invalid2",
|
|
Flags: (protocol.FlagsAll + 2) &^ protocol.FlagInvalid,
|
|
},
|
|
{
|
|
Name: "invalid3",
|
|
Flags: (1 << 31) &^ protocol.FlagInvalid,
|
|
},
|
|
{
|
|
Name: "valid",
|
|
Flags: protocol.FlagsAll &^ (protocol.FlagInvalid | protocol.FlagSymlink),
|
|
},
|
|
}, 0, nil)
|
|
|
|
for _, name := range []string{"invalid1", "invalid2", "invalid3"} {
|
|
f, ok := m.CurrentGlobalFile("default", name)
|
|
if ok || f.Name == name {
|
|
t.Error("Invalid file found or name match")
|
|
}
|
|
}
|
|
f, ok := m.CurrentGlobalFile("default", "valid")
|
|
if !ok || f.Name != "valid" {
|
|
t.Error("Valid file not found or name mismatch", ok, f)
|
|
}
|
|
}
|
|
|
|
func TestROScanRecovery(t *testing.T) {
|
|
ldb := db.OpenMemory()
|
|
set := db.NewFileSet("default", ldb)
|
|
set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
|
|
{Name: "dummyfile"},
|
|
})
|
|
|
|
fcfg := config.FolderConfiguration{
|
|
ID: "default",
|
|
RawPath: "testdata/rotestfolder",
|
|
Type: config.FolderTypeReadOnly,
|
|
RescanIntervalS: 1,
|
|
}
|
|
cfg := config.Wrap("/tmp/test", config.Configuration{
|
|
Folders: []config.FolderConfiguration{fcfg},
|
|
Devices: []config.DeviceConfiguration{
|
|
{
|
|
DeviceID: device1,
|
|
},
|
|
},
|
|
})
|
|
|
|
os.RemoveAll(fcfg.RawPath)
|
|
|
|
m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb, nil)
|
|
m.AddFolder(fcfg)
|
|
m.StartFolder("default")
|
|
m.ServeBackground()
|
|
|
|
waitFor := func(status string) error {
|
|
timeout := time.Now().Add(2 * time.Second)
|
|
for {
|
|
if time.Now().After(timeout) {
|
|
return fmt.Errorf("Timed out waiting for status: %s, current status: %s", status, m.cfg.Folders()["default"].Invalid)
|
|
}
|
|
_, _, err := m.State("default")
|
|
if err == nil && status == "" {
|
|
return nil
|
|
}
|
|
if err != nil && err.Error() == status {
|
|
return nil
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
if err := waitFor("folder path missing"); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
os.Mkdir(fcfg.RawPath, 0700)
|
|
|
|
if err := waitFor("folder marker missing"); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
fd, err := os.Create(filepath.Join(fcfg.RawPath, ".stfolder"))
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
fd.Close()
|
|
|
|
if err := waitFor(""); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
os.Remove(filepath.Join(fcfg.RawPath, ".stfolder"))
|
|
|
|
if err := waitFor("folder marker missing"); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
os.Remove(fcfg.RawPath)
|
|
|
|
if err := waitFor("folder path missing"); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestRWScanRecovery(t *testing.T) {
|
|
ldb := db.OpenMemory()
|
|
set := db.NewFileSet("default", ldb)
|
|
set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
|
|
{Name: "dummyfile"},
|
|
})
|
|
|
|
fcfg := config.FolderConfiguration{
|
|
ID: "default",
|
|
RawPath: "testdata/rwtestfolder",
|
|
Type: config.FolderTypeReadWrite,
|
|
RescanIntervalS: 1,
|
|
}
|
|
cfg := config.Wrap("/tmp/test", config.Configuration{
|
|
Folders: []config.FolderConfiguration{fcfg},
|
|
Devices: []config.DeviceConfiguration{
|
|
{
|
|
DeviceID: device1,
|
|
},
|
|
},
|
|
})
|
|
|
|
os.RemoveAll(fcfg.RawPath)
|
|
|
|
m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb, nil)
|
|
m.AddFolder(fcfg)
|
|
m.StartFolder("default")
|
|
m.ServeBackground()
|
|
|
|
waitFor := func(status string) error {
|
|
timeout := time.Now().Add(2 * time.Second)
|
|
for {
|
|
if time.Now().After(timeout) {
|
|
return fmt.Errorf("Timed out waiting for status: %s, current status: %s", status, m.cfg.Folders()["default"].Invalid)
|
|
}
|
|
_, _, err := m.State("default")
|
|
if err == nil && status == "" {
|
|
return nil
|
|
}
|
|
if err != nil && err.Error() == status {
|
|
return nil
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
if err := waitFor("folder path missing"); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
os.Mkdir(fcfg.RawPath, 0700)
|
|
|
|
if err := waitFor("folder marker missing"); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
fd, err := os.Create(filepath.Join(fcfg.RawPath, ".stfolder"))
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
fd.Close()
|
|
|
|
if err := waitFor(""); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
os.Remove(filepath.Join(fcfg.RawPath, ".stfolder"))
|
|
|
|
if err := waitFor("folder marker missing"); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
os.Remove(fcfg.RawPath)
|
|
|
|
if err := waitFor("folder path missing"); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestGlobalDirectoryTree(t *testing.T) {
|
|
db := db.OpenMemory()
|
|
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
|
m.AddFolder(defaultFolderConfig)
|
|
m.ServeBackground()
|
|
|
|
b := func(isfile bool, path ...string) protocol.FileInfo {
|
|
flags := uint32(protocol.FlagDirectory)
|
|
blocks := []protocol.BlockInfo{}
|
|
if isfile {
|
|
flags = 0
|
|
blocks = []protocol.BlockInfo{{Offset: 0x0, Size: 0xa, Hash: []uint8{0x2f, 0x72, 0xcc, 0x11, 0xa6, 0xfc, 0xd0, 0x27, 0x1e, 0xce, 0xf8, 0xc6, 0x10, 0x56, 0xee, 0x1e, 0xb1, 0x24, 0x3b, 0xe3, 0x80, 0x5b, 0xf9, 0xa9, 0xdf, 0x98, 0xf9, 0x2f, 0x76, 0x36, 0xb0, 0x5c}}}
|
|
}
|
|
return protocol.FileInfo{
|
|
Name: filepath.Join(path...),
|
|
Flags: flags,
|
|
Modified: 0x666,
|
|
Blocks: blocks,
|
|
}
|
|
}
|
|
|
|
filedata := []interface{}{time.Unix(0x666, 0), 0xa}
|
|
|
|
testdata := []protocol.FileInfo{
|
|
b(false, "another"),
|
|
b(false, "another", "directory"),
|
|
b(true, "another", "directory", "afile"),
|
|
b(false, "another", "directory", "with"),
|
|
b(false, "another", "directory", "with", "a"),
|
|
b(true, "another", "directory", "with", "a", "file"),
|
|
b(true, "another", "directory", "with", "file"),
|
|
b(true, "another", "file"),
|
|
|
|
b(false, "other"),
|
|
b(false, "other", "rand"),
|
|
b(false, "other", "random"),
|
|
b(false, "other", "random", "dir"),
|
|
b(false, "other", "random", "dirx"),
|
|
b(false, "other", "randomx"),
|
|
|
|
b(false, "some"),
|
|
b(false, "some", "directory"),
|
|
b(false, "some", "directory", "with"),
|
|
b(false, "some", "directory", "with", "a"),
|
|
b(true, "some", "directory", "with", "a", "file"),
|
|
|
|
b(true, "rootfile"),
|
|
}
|
|
expectedResult := map[string]interface{}{
|
|
"another": map[string]interface{}{
|
|
"directory": map[string]interface{}{
|
|
"afile": filedata,
|
|
"with": map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"file": filedata,
|
|
},
|
|
"file": filedata,
|
|
},
|
|
},
|
|
"file": filedata,
|
|
},
|
|
"other": map[string]interface{}{
|
|
"rand": map[string]interface{}{},
|
|
"random": map[string]interface{}{
|
|
"dir": map[string]interface{}{},
|
|
"dirx": map[string]interface{}{},
|
|
},
|
|
"randomx": map[string]interface{}{},
|
|
},
|
|
"some": map[string]interface{}{
|
|
"directory": map[string]interface{}{
|
|
"with": map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"file": filedata,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"rootfile": filedata,
|
|
}
|
|
|
|
mm := func(data interface{}) string {
|
|
bytes, err := json.Marshal(data)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return string(bytes)
|
|
}
|
|
|
|
m.Index(device1, "default", testdata, 0, nil)
|
|
|
|
result := m.GlobalDirectoryTree("default", "", -1, false)
|
|
|
|
if mm(result) != mm(expectedResult) {
|
|
t.Errorf("Does not match:\n%#v\n%#v", result, expectedResult)
|
|
}
|
|
|
|
result = m.GlobalDirectoryTree("default", "another", -1, false)
|
|
|
|
if mm(result) != mm(expectedResult["another"]) {
|
|
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(expectedResult["another"]))
|
|
}
|
|
|
|
result = m.GlobalDirectoryTree("default", "", 0, false)
|
|
currentResult := map[string]interface{}{
|
|
"another": map[string]interface{}{},
|
|
"other": map[string]interface{}{},
|
|
"some": map[string]interface{}{},
|
|
"rootfile": filedata,
|
|
}
|
|
|
|
if mm(result) != mm(currentResult) {
|
|
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
|
|
}
|
|
|
|
result = m.GlobalDirectoryTree("default", "", 1, false)
|
|
currentResult = map[string]interface{}{
|
|
"another": map[string]interface{}{
|
|
"directory": map[string]interface{}{},
|
|
"file": filedata,
|
|
},
|
|
"other": map[string]interface{}{
|
|
"rand": map[string]interface{}{},
|
|
"random": map[string]interface{}{},
|
|
"randomx": map[string]interface{}{},
|
|
},
|
|
"some": map[string]interface{}{
|
|
"directory": map[string]interface{}{},
|
|
},
|
|
"rootfile": filedata,
|
|
}
|
|
|
|
if mm(result) != mm(currentResult) {
|
|
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
|
|
}
|
|
|
|
result = m.GlobalDirectoryTree("default", "", -1, true)
|
|
currentResult = map[string]interface{}{
|
|
"another": map[string]interface{}{
|
|
"directory": map[string]interface{}{
|
|
"with": map[string]interface{}{
|
|
"a": map[string]interface{}{},
|
|
},
|
|
},
|
|
},
|
|
"other": map[string]interface{}{
|
|
"rand": map[string]interface{}{},
|
|
"random": map[string]interface{}{
|
|
"dir": map[string]interface{}{},
|
|
"dirx": map[string]interface{}{},
|
|
},
|
|
"randomx": map[string]interface{}{},
|
|
},
|
|
"some": map[string]interface{}{
|
|
"directory": map[string]interface{}{
|
|
"with": map[string]interface{}{
|
|
"a": map[string]interface{}{},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
if mm(result) != mm(currentResult) {
|
|
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
|
|
}
|
|
|
|
result = m.GlobalDirectoryTree("default", "", 1, true)
|
|
currentResult = map[string]interface{}{
|
|
"another": map[string]interface{}{
|
|
"directory": map[string]interface{}{},
|
|
},
|
|
"other": map[string]interface{}{
|
|
"rand": map[string]interface{}{},
|
|
"random": map[string]interface{}{},
|
|
"randomx": map[string]interface{}{},
|
|
},
|
|
"some": map[string]interface{}{
|
|
"directory": map[string]interface{}{},
|
|
},
|
|
}
|
|
|
|
if mm(result) != mm(currentResult) {
|
|
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
|
|
}
|
|
|
|
result = m.GlobalDirectoryTree("default", "another", 0, false)
|
|
currentResult = map[string]interface{}{
|
|
"directory": map[string]interface{}{},
|
|
"file": filedata,
|
|
}
|
|
|
|
if mm(result) != mm(currentResult) {
|
|
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
|
|
}
|
|
|
|
result = m.GlobalDirectoryTree("default", "some/directory", 0, false)
|
|
currentResult = map[string]interface{}{
|
|
"with": map[string]interface{}{},
|
|
}
|
|
|
|
if mm(result) != mm(currentResult) {
|
|
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
|
|
}
|
|
|
|
result = m.GlobalDirectoryTree("default", "some/directory", 1, false)
|
|
currentResult = map[string]interface{}{
|
|
"with": map[string]interface{}{
|
|
"a": map[string]interface{}{},
|
|
},
|
|
}
|
|
|
|
if mm(result) != mm(currentResult) {
|
|
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
|
|
}
|
|
|
|
result = m.GlobalDirectoryTree("default", "some/directory", 2, false)
|
|
currentResult = map[string]interface{}{
|
|
"with": map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"file": filedata,
|
|
},
|
|
},
|
|
}
|
|
|
|
if mm(result) != mm(currentResult) {
|
|
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
|
|
}
|
|
|
|
result = m.GlobalDirectoryTree("default", "another", -1, true)
|
|
currentResult = map[string]interface{}{
|
|
"directory": map[string]interface{}{
|
|
"with": map[string]interface{}{
|
|
"a": map[string]interface{}{},
|
|
},
|
|
},
|
|
}
|
|
|
|
if mm(result) != mm(currentResult) {
|
|
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
|
|
}
|
|
|
|
// No prefix matching!
|
|
result = m.GlobalDirectoryTree("default", "som", -1, false)
|
|
currentResult = map[string]interface{}{}
|
|
|
|
if mm(result) != mm(currentResult) {
|
|
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
|
|
}
|
|
}
|
|
|
|
func TestGlobalDirectorySelfFixing(t *testing.T) {
|
|
db := db.OpenMemory()
|
|
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
|
m.AddFolder(defaultFolderConfig)
|
|
m.ServeBackground()
|
|
|
|
b := func(isfile bool, path ...string) protocol.FileInfo {
|
|
flags := uint32(protocol.FlagDirectory)
|
|
blocks := []protocol.BlockInfo{}
|
|
if isfile {
|
|
flags = 0
|
|
blocks = []protocol.BlockInfo{{Offset: 0x0, Size: 0xa, Hash: []uint8{0x2f, 0x72, 0xcc, 0x11, 0xa6, 0xfc, 0xd0, 0x27, 0x1e, 0xce, 0xf8, 0xc6, 0x10, 0x56, 0xee, 0x1e, 0xb1, 0x24, 0x3b, 0xe3, 0x80, 0x5b, 0xf9, 0xa9, 0xdf, 0x98, 0xf9, 0x2f, 0x76, 0x36, 0xb0, 0x5c}}}
|
|
}
|
|
return protocol.FileInfo{
|
|
Name: filepath.Join(path...),
|
|
Flags: flags,
|
|
Modified: 0x666,
|
|
Blocks: blocks,
|
|
}
|
|
}
|
|
|
|
filedata := []interface{}{time.Unix(0x666, 0).Format(time.RFC3339), 0xa}
|
|
|
|
testdata := []protocol.FileInfo{
|
|
b(true, "another", "directory", "afile"),
|
|
b(true, "another", "directory", "with", "a", "file"),
|
|
b(true, "another", "directory", "with", "file"),
|
|
|
|
b(false, "other", "random", "dirx"),
|
|
b(false, "other", "randomx"),
|
|
|
|
b(false, "some", "directory", "with", "x"),
|
|
b(true, "some", "directory", "with", "a", "file"),
|
|
|
|
b(false, "this", "is", "a", "deep", "invalid", "directory"),
|
|
|
|
b(true, "xthis", "is", "a", "deep", "invalid", "file"),
|
|
}
|
|
expectedResult := map[string]interface{}{
|
|
"another": map[string]interface{}{
|
|
"directory": map[string]interface{}{
|
|
"afile": filedata,
|
|
"with": map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"file": filedata,
|
|
},
|
|
"file": filedata,
|
|
},
|
|
},
|
|
},
|
|
"other": map[string]interface{}{
|
|
"random": map[string]interface{}{
|
|
"dirx": map[string]interface{}{},
|
|
},
|
|
"randomx": map[string]interface{}{},
|
|
},
|
|
"some": map[string]interface{}{
|
|
"directory": map[string]interface{}{
|
|
"with": map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"file": filedata,
|
|
},
|
|
"x": map[string]interface{}{},
|
|
},
|
|
},
|
|
},
|
|
"this": map[string]interface{}{
|
|
"is": map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"deep": map[string]interface{}{
|
|
"invalid": map[string]interface{}{
|
|
"directory": map[string]interface{}{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"xthis": map[string]interface{}{
|
|
"is": map[string]interface{}{
|
|
"a": map[string]interface{}{
|
|
"deep": map[string]interface{}{
|
|
"invalid": map[string]interface{}{
|
|
"file": filedata,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
mm := func(data interface{}) string {
|
|
bytes, err := json.Marshal(data)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return string(bytes)
|
|
}
|
|
|
|
m.Index(device1, "default", testdata, 0, nil)
|
|
|
|
result := m.GlobalDirectoryTree("default", "", -1, false)
|
|
|
|
if mm(result) != mm(expectedResult) {
|
|
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(expectedResult))
|
|
}
|
|
|
|
result = m.GlobalDirectoryTree("default", "xthis/is/a/deep", -1, false)
|
|
currentResult := map[string]interface{}{
|
|
"invalid": map[string]interface{}{
|
|
"file": filedata,
|
|
},
|
|
}
|
|
|
|
if mm(result) != mm(currentResult) {
|
|
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
|
|
}
|
|
|
|
result = m.GlobalDirectoryTree("default", "xthis/is/a/deep", -1, true)
|
|
currentResult = map[string]interface{}{
|
|
"invalid": map[string]interface{}{},
|
|
}
|
|
|
|
if mm(result) != mm(currentResult) {
|
|
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
|
|
}
|
|
|
|
// !!! This is actually BAD, because we don't have enough level allowance
|
|
// to accept this file, hence the tree is left unbuilt !!!
|
|
result = m.GlobalDirectoryTree("default", "xthis", 1, false)
|
|
currentResult = map[string]interface{}{}
|
|
|
|
if mm(result) != mm(currentResult) {
|
|
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
|
|
}
|
|
}
|
|
|
|
func genDeepFiles(n, d int) []protocol.FileInfo {
|
|
rand.Seed(int64(n))
|
|
files := make([]protocol.FileInfo, n)
|
|
t := time.Now().Unix()
|
|
for i := 0; i < n; i++ {
|
|
path := ""
|
|
for i := 0; i <= d; i++ {
|
|
path = filepath.Join(path, strconv.Itoa(rand.Int()))
|
|
}
|
|
|
|
sofar := ""
|
|
for _, path := range filepath.SplitList(path) {
|
|
sofar = filepath.Join(sofar, path)
|
|
files[i] = protocol.FileInfo{
|
|
Name: sofar,
|
|
}
|
|
i++
|
|
}
|
|
|
|
files[i].Modified = t
|
|
files[i].Blocks = []protocol.BlockInfo{{0, 100, []byte("some hash bytes")}}
|
|
}
|
|
|
|
return files
|
|
}
|
|
|
|
func BenchmarkTree_10000_50(b *testing.B) {
|
|
benchmarkTree(b, 10000, 50)
|
|
}
|
|
|
|
func BenchmarkTree_100_50(b *testing.B) {
|
|
benchmarkTree(b, 100, 50)
|
|
}
|
|
|
|
func BenchmarkTree_100_10(b *testing.B) {
|
|
benchmarkTree(b, 100, 10)
|
|
}
|
|
|
|
func benchmarkTree(b *testing.B, n1, n2 int) {
|
|
db := db.OpenMemory()
|
|
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
|
m.AddFolder(defaultFolderConfig)
|
|
m.ServeBackground()
|
|
|
|
m.ScanFolder("default")
|
|
files := genDeepFiles(n1, n2)
|
|
|
|
m.Index(device1, "default", files, 0, nil)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
m.GlobalDirectoryTree("default", "", -1, false)
|
|
}
|
|
b.ReportAllocs()
|
|
}
|
|
|
|
func TestIgnoreDelete(t *testing.T) {
|
|
db := db.OpenMemory()
|
|
m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
|
|
|
// This folder should ignore external deletes
|
|
cfg := defaultFolderConfig
|
|
cfg.IgnoreDelete = true
|
|
|
|
m.AddFolder(cfg)
|
|
m.ServeBackground()
|
|
m.StartFolder("default")
|
|
m.ScanFolder("default")
|
|
|
|
// Get a currently existing file
|
|
f, ok := m.CurrentGlobalFile("default", "foo")
|
|
if !ok {
|
|
t.Fatal("foo should exist")
|
|
}
|
|
|
|
// Mark it for deletion
|
|
f.Flags = protocol.FlagDeleted
|
|
f.Version = f.Version.Update(142) // arbitrary short remote ID
|
|
f.Blocks = nil
|
|
|
|
// Send the index
|
|
m.Index(device1, "default", []protocol.FileInfo{f}, 0, nil)
|
|
|
|
// Make sure we ignored it
|
|
f, ok = m.CurrentGlobalFile("default", "foo")
|
|
if !ok {
|
|
t.Fatal("foo should exist")
|
|
}
|
|
if f.IsDeleted() {
|
|
t.Fatal("foo should not be marked for deletion")
|
|
}
|
|
}
|
|
|
|
func TestUnifySubs(t *testing.T) {
|
|
cases := []struct {
|
|
in []string // input to unifySubs
|
|
exists []string // paths that exist in the database
|
|
out []string // expected output
|
|
}{
|
|
{
|
|
// 0. trailing slashes are cleaned, known paths are just passed on
|
|
[]string{"foo/", "bar//"},
|
|
[]string{"foo", "bar"},
|
|
[]string{"bar", "foo"}, // the output is sorted
|
|
},
|
|
{
|
|
// 1. "foo/bar" gets trimmed as it's covered by foo
|
|
[]string{"foo", "bar/", "foo/bar/"},
|
|
[]string{"foo", "bar"},
|
|
[]string{"bar", "foo"},
|
|
},
|
|
{
|
|
// 2. "" gets simplified to the empty list; ie scan all
|
|
[]string{"foo", ""},
|
|
[]string{"foo"},
|
|
nil,
|
|
},
|
|
{
|
|
// 3. "foo/bar" is unknown, but it's kept
|
|
// because its parent is known
|
|
[]string{"foo/bar"},
|
|
[]string{"foo"},
|
|
[]string{"foo/bar"},
|
|
},
|
|
{
|
|
// 4. two independent known paths, both are kept
|
|
// "usr/lib" is not a prefix of "usr/libexec"
|
|
[]string{"usr/lib", "usr/libexec"},
|
|
[]string{"usr", "usr/lib", "usr/libexec"},
|
|
[]string{"usr/lib", "usr/libexec"},
|
|
},
|
|
{
|
|
// 5. "usr/lib" is a prefix of "usr/lib/exec"
|
|
[]string{"usr/lib", "usr/lib/exec"},
|
|
[]string{"usr", "usr/lib", "usr/libexec"},
|
|
[]string{"usr/lib"},
|
|
},
|
|
{
|
|
// 6. .stignore and .stfolder are special and are passed on
|
|
// verbatim even though they are unknown
|
|
[]string{".stfolder", ".stignore"},
|
|
[]string{},
|
|
[]string{".stfolder", ".stignore"},
|
|
},
|
|
{
|
|
// 7. but the presense of something else unknown forces an actual
|
|
// scan
|
|
[]string{".stfolder", ".stignore", "foo/bar"},
|
|
[]string{},
|
|
[]string{".stfolder", ".stignore", "foo"},
|
|
},
|
|
{
|
|
// 8. explicit request to scan all
|
|
nil,
|
|
[]string{"foo"},
|
|
nil,
|
|
},
|
|
{
|
|
// 9. empty list of subs
|
|
[]string{},
|
|
[]string{"foo"},
|
|
nil,
|
|
},
|
|
}
|
|
|
|
if runtime.GOOS == "windows" {
|
|
// Fixup path separators
|
|
for i := range cases {
|
|
for j, p := range cases[i].in {
|
|
cases[i].in[j] = filepath.FromSlash(p)
|
|
}
|
|
for j, p := range cases[i].exists {
|
|
cases[i].exists[j] = filepath.FromSlash(p)
|
|
}
|
|
for j, p := range cases[i].out {
|
|
cases[i].out[j] = filepath.FromSlash(p)
|
|
}
|
|
}
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
exists := func(f string) bool {
|
|
for _, e := range tc.exists {
|
|
if f == e {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
out := unifySubs(tc.in, exists)
|
|
if diff, equal := messagediff.PrettyDiff(tc.out, out); !equal {
|
|
t.Errorf("Case %d failed; got %v, expected %v, diff:\n%s", i, out, tc.out, diff)
|
|
}
|
|
}
|
|
}
|
|
|
|
type fakeAddr struct{}
|
|
|
|
func (fakeAddr) Network() string {
|
|
return "network"
|
|
}
|
|
|
|
func (fakeAddr) String() string {
|
|
return "address"
|
|
}
|
|
|
|
type fakeConn struct{}
|
|
|
|
func (fakeConn) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func (fakeConn) LocalAddr() net.Addr {
|
|
return &fakeAddr{}
|
|
}
|
|
|
|
func (fakeConn) RemoteAddr() net.Addr {
|
|
return &fakeAddr{}
|
|
}
|
|
|
|
func (fakeConn) Read([]byte) (int, error) {
|
|
return 0, nil
|
|
}
|
|
|
|
func (fakeConn) Write([]byte) (int, error) {
|
|
return 0, nil
|
|
}
|
|
|
|
func (fakeConn) SetDeadline(time.Time) error {
|
|
return nil
|
|
}
|
|
|
|
func (fakeConn) SetReadDeadline(time.Time) error {
|
|
return nil
|
|
}
|
|
|
|
func (fakeConn) SetWriteDeadline(time.Time) error {
|
|
return nil
|
|
}
|