Просмотр исходного кода

Improve and clean up integration tests, benchmark.

Jakob Borg 11 лет назад
Родитель
Сommit
2090530bbb

+ 6 - 8
build.sh

@@ -105,7 +105,7 @@ case "${1:-default}" in
 		;;
 
 	docker-init)
-		docker build -q -t syncthing/build:$DOCKERIMGV docker >/dev/null
+		docker build -q -t syncthing/build:$DOCKERIMGV docker
 		;;
 
 	docker-all)
@@ -122,17 +122,15 @@ case "${1:-default}" in
 
 	docker-test)
 		docker run --rm -h syncthing-builder -u $(id -u) -t \
-			-v $(pwd):/tmp/syncthing \
+			-v $(pwd):/go/src/github.com/syncthing/syncthing \
+			-w /go/src/github.com/syncthing/syncthing \
 			syncthing/build:$DOCKERIMGV \
-			sh -euxc 'mkdir -p /go/src/github.com/syncthing \
-				&& cd /go/src/github.com/syncthing \
-				&& cp -r /tmp/syncthing syncthing \
-				&& cd syncthing \
-				&& ./build.sh clean \
+			sh -euxc './build.sh clean \
 				&& go run build.go -race \
 				&& export GOPATH=$(pwd)/Godeps/_workspace:$GOPATH \
 				&& cd test \
-				&& go test -tags integration -v -timeout 60m -short'
+				&& go test -tags integration -v -timeout 60m -short \
+				&& git clean -fxd .'
 		;;
 
 	*)

+ 0 - 7
test/all.sh

@@ -1,7 +0,0 @@
-#!/bin/bash
-set -euo pipefail
-IFS=$'\n\t'
-
-go test -tags integration -v -short
-./test-merge.sh
-./test-delupd.sh

+ 1 - 1
test/cli_test.go

@@ -15,7 +15,7 @@
 
 // +build integration
 
-package integration_test
+package integration
 
 import (
 	"os"

+ 0 - 23
test/f1/cert.pem

@@ -1,23 +0,0 @@
------BEGIN CERTIFICATE-----
-MIID3jCCAkigAwIBAgIBADALBgkqhkiG9w0BAQUwFDESMBAGA1UEAxMJc3luY3Ro
-aW5nMB4XDTE0MDMxNDA3MDA1M1oXDTQ5MTIzMTIzNTk1OVowFDESMBAGA1UEAxMJ
-c3luY3RoaW5nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEArDOcd5ft
-R7SnalxF1ckU3lDQpgfMIPhFDU//4dvdSSFevrMuVDTbUYhyCfGtg/g+F5TmKhZg
-E2peYhllITupz5MP7OHGaO2GHf2XnUDD4QUO3E+KVAUw7dyFSwy09esqApVLzH3+
-ov+QXyyzmRWPsJe9u18BHU1Hob/RmBhS9m2CAJgzN6EJ8KGjApiW3iR8lD/hjVyi
-IVde8IRD6qYHEJYiPJuziTVcQpCblVYxTz3ScmmT190/O9UvViIpcOPQdwgOdewP
-NNMK35c9Edt0AH5flYp6jgrja9NkLQJ3+KOiro6yl9IUS5w87GMxI8qzI8SgCAZZ
-pYSoLbu1FJPvxV4p5eHwuprBCwmFYZWw6Y7rqH0sN52C+3TeObJCMNP9ilPadqRI
-+G0Q99TCaloeR022x33r/8D8SIn3FP35zrlFM+DvqlxoS6glbNb/Bj3p9vN0XONO
-RCuynOGe9F/4h/DaNnrbrRWqJOxBsZTsbbcJaKATfWU/Z9GcC+pUpPRhAgMBAAGj
-PzA9MA4GA1UdDwEB/wQEAwIAoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH
-AwIwDAYDVR0TAQH/BAIwADALBgkqhkiG9w0BAQUDggGBAFF8dklGoC43fMrUZfb4
-6areRWG8quO6cSX6ATzRQVJ8WJ5VcC7OJk8/FeiYA+wcvUJ/1Zm/VHMYugtOz5M8
-CrWAF1r9D3Xfe5D8qfrEOYG2XjxD2nFHCnkbY4fP+SMSuXaDs7ixQnzw0UFh1wsV
-9Jy/QrgXFAIFZtu1Nz+rrvoAgw24gkDhY3557MbmYfmfPsJ8cw+WJ845sxGMPFF2
-c+5EN0jiSm0AwZK11BMJda36ke829UZctDkopbGEg1peydDR5LiyhiTAPtWn7uT/
-PkzHYLuaECAkVbWC3bZLocMGOP6F1pG+BMr00NJgVy05ASQzi4FPjcZQNNY8s69R
-ZgoCIBaJZq3ti1EsZQ1H0Ynm2c2NMVKdj4czoy8a9ZC+DCuhG7EV5Foh20VhCWgA
-RfPhlHVJthuimsWBx39X85gjSBR017uk0AxOJa6pzh/b/RPCRtUfX8EArInS3XCf
-RvRtdrnBZNI3tiREopZGt0SzgDZUs4uDVBUX8HnHzyFJrg==
------END CERTIFICATE-----

+ 0 - 32
test/f1/config.xml

@@ -1,32 +0,0 @@
-<configuration version="2">
-    <folder id="default" directory="s1" ro="true" ignorePerms="false">
-        <device id="I6KAH7666SLLL5PFXSOAUFJCDZYAOMLEKCP2GB3BV5RQST3PSROA"></device>
-        <device id="JMFJCXBGZDE4BOCJE3VF65GYZNAIVJRET3J6HMRAUQIGJOFKNHMQ"></device>
-        <versioning></versioning>
-    </folder>
-    <device id="I6KAH7666SLLL5PFXSOAUFJCDZYAOMLEKCP2GB3BV5RQST3PSROA" name="f1">
-        <address>127.0.0.1:22001</address>
-    </device>
-    <device id="JMFJCXBGZDE4BOCJE3VF65GYZNAIVJRET3J6HMRAUQIGJOFKNHMQ" name="f2">
-        <address>127.0.0.1:22002</address>
-    </device>
-    <gui enabled="true" tls="false">
-        <address>127.0.0.1:8081</address>
-        <apikey>abc123</apikey>
-    </gui>
-    <options>
-        <listenAddress>127.0.0.1:22001</listenAddress>
-        <globalAnnounceServer>announce.syncthing.net:22025</globalAnnounceServer>
-        <globalAnnounceEnabled>false</globalAnnounceEnabled>
-        <localAnnounceEnabled>true</localAnnounceEnabled>
-        <localAnnouncePort>21025</localAnnouncePort>
-        <parallelRequests>16</parallelRequests>
-        <maxSendKbps>500</maxSendKbps>
-        <rescanIntervalS>10</rescanIntervalS>
-        <reconnectionIntervalS>5</reconnectionIntervalS>
-        <maxChangeKbps>10000</maxChangeKbps>
-        <startBrowser>false</startBrowser>
-        <upnpEnabled>false</upnpEnabled>
-        <urAccepted>-1</urAccepted>
-    </options>
-</configuration>

+ 0 - 23
test/f1/https-cert.pem

@@ -1,23 +0,0 @@
------BEGIN CERTIFICATE-----
-MIID5TCCAk+gAwIBAgIIHozuBlC9RPswCwYJKoZIhvcNAQELMBQxEjAQBgNVBAMT
-CXN5bmN0aGluZzAeFw0xNDA5MTUwNTE0MDBaFw00OTEyMzEyMzU5NTlaMBQxEjAQ
-BgNVBAMTCXN5bmN0aGluZzCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGB
-ALxVRAh6EWr1Vum1EDafEGNTxWYcsMFssl4lc18cgQ5SRFtdkDi2IxELqiAPT0K9
-11DkD563o7MIdCvB/XCA2hEaapfKKMiba/yH6tSnE7Fud5Wq8AtsAZV5weCjhGrc
-s2YL8Nm57tiJC4W0K7txB8Ob5Q9gxvSpzLak2eh/fqhNoO6DxvUF/iE3VsdhKbKb
-epXsTEvR1T3Qx0MUam7Dkz1eT4kXpwPGwLKfXY+BOxybxHAKM12qKV4R5Ebs/JZB
-5GajZqd6XpwgldIg7KHWpWWSSjH4ojnP4XmEy5WZA33t0o+xkrg+EcQbzSEFhRMD
-KT7fKT9+Evf+xB0j8FJ1+kL2ajTVfOPx3Fyg+YAPK9OVI6fr9OMJD6pLxZMLaRi9
-R8IHEgOLo4vLRNLCmJWVIqfMtUlsVkXLM+alDo0maPUesPDTXeCuMG9TpDyHFQI5
-6H4OYD3/9DOEOYMXXCCpPqpjk0CpS4GAtI6qXFG3dUY3EdfOcrKvnKi/DoRrpdzp
-7wIDAQABoz8wPTAOBgNVHQ8BAf8EBAMCAKAwHQYDVR0lBBYwFAYIKwYBBQUHAwEG
-CCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwCwYJKoZIhvcNAQELA4IBgQB4MajA42Wp
-L1xFMb4Ba7+aW3o3j0N7wqJjHuU5HtbpwdYgv3tgmn+Ju6rvy6BwAQt8OsVNbai9
-ptwsHbR6epoXPzAGyqY/9A74PJr6GraYJS148zuEgCir2Q0TfzbPtd4rDYN3LE81
-STwtBvcaQsuAukQbeTQJkayobLQH8ve34BVX43XHUchEPLeZqAec5/btVyR9xwWz
-rwJyEfCtx1/YxkPRBPs1cBQOK0Edn9nBKF114ogpWKlKt1Ou1HZzsMS2usWPdme1
-IATpCR9i5/ZRIC7vhSK3se7IRaMrmOXc0kpgmbi9pr+Tg2sgJSC1Bvyfh3ReC53F
-R6WJrmqut+SqCJlefQh+6On4MaW9hnrv62OTkyBKZZ3ogCQ+FMNFs+jRJHpdtxs4
-ZVkuv1NZu8nB7NsY8i6vHKkOE6XCejg6HZL4R70iXoDDgo9E6A8TwTN7TZz7SdWh
-+Bh3GGJAMubzuHysmEkKBSYHYlrW8GuUwCbLCl+HcQnfOtN7ffJwJb4=
------END CERTIFICATE-----

+ 0 - 39
test/f1/https-key.pem

@@ -1,39 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-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
------END RSA PRIVATE KEY-----

+ 0 - 39
test/f1/key.pem

@@ -1,39 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-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
------END RSA PRIVATE KEY-----

+ 0 - 23
test/f2/cert.pem

@@ -1,23 +0,0 @@
------BEGIN CERTIFICATE-----
-MIID3jCCAkigAwIBAgIBADALBgkqhkiG9w0BAQUwFDESMBAGA1UEAxMJc3luY3Ro
-aW5nMB4XDTE0MDMxNDA3MDEwNFoXDTQ5MTIzMTIzNTk1OVowFDESMBAGA1UEAxMJ
-c3luY3RoaW5nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAsIV0syyR
-O56BvIOro4bIqB6iFJsNc4zX8MiM4QPTWgqGlYwsKSVmNppTdlACZCJIqyzoscrF
-qJPto8/e2Fc3oaTdEREGIs7cmc7LSXfot/mAgPpy71SVWtb7xNmXro2JJPZjRBCS
-pl1ulPug+/8w7fSKQdLMjh4Hp2YlwVBfVu0bYEEW+7Vl9PZVTv+NbTqXYvYVc9R6
-QFIbN/njWAuo2wpjJlY7vqNnSYZyskAaaAC17fFJkVQKKblTeTk1C9PxTmVTB1j9
-yOoD3+V/6IrTYKXdTHGJ1MqdieTHj1jHXe5TOeSB+Hjgq4tr25mPfQ4ixXqDqIcx
-5390DAjInuSKNUJ5pqiFrVe9eIDmySZCg5/JIL3c8phy6g1bxiJN14+Dn0om/0+9
-UrHK8LVzWMmtFRVycWVUYmARWFY3EE10k0RXU2HtzmjfnBkRrl13b0ExizlA1qJ3
-3ngxF5rNEDSMpwf4og5uYOjRUPYuvCL9XtQKr254NFO/sg/qqPV4hFWTAgMBAAGj
-PzA9MA4GA1UdDwEB/wQEAwIAoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH
-AwIwDAYDVR0TAQH/BAIwADALBgkqhkiG9w0BAQUDggGBAAZSU08zAzyuGqKqqU/c
-Pr+xML8oKiJqko5pb3ETDQC+uVw+qUHwiGYsvHI1cih4ix+tKvf+Yaiizp/35VkP
-qwls3a4ljq1Ww0Sf7J87QX0DumYpBGOfoCpmV4MacyjLhpLRKRGZHwIbOeFsmEu9
-oO38co+GvDy4CiAt3tuOdjBNs0gNOAdTTxqgm97raB9oXeg2i4Fb4MCT4UBUdXLM
-ZNLCifza+PWkBxmfBORvlKGeJBruLpXHBWnWEigZSLXIFjn3JJUy4fKd+/JMp063
-8Pjo6zUOckBCH8Lv90vzfrmdlQK555jWpcebN0l9neESEXw19l0OlqkJGVTr6JKq
-w5kjiL4eP7kpKKwCezhDSX3jf4P36wdF8MpOUBxVqfM+Oh5tHIcZctnurhYV7rXs
-jR70FMqWjHBmwemsXGrObNVt8c75yB+19U6DAulr2RhRw5GD74U1znP00eGZ8TJf
-RN1FYilUPCawMYeQoB8WIn9So7zIm0MfOl4KXNWDX02+Kw==
------END CERTIFICATE-----

+ 0 - 34
test/f2/config.xml

@@ -1,34 +0,0 @@
-<configuration version="2">
-    <folder id="default" directory="s2" ro="false" ignorePerms="false">
-        <device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU"></device>
-        <device id="JMFJCXB-GZDE4BN-OCJE3VF-65GYZNU-AIVJRET-3J6HMRQ-AUQIGJO-FKNHMQU"></device>
-        <versioning type="simple">
-            <param key="keep" val="5"></param>
-        </versioning>
-    </folder>
-    <device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" name="f1">
-        <address>127.0.0.1:22001</address>
-    </device>
-    <device id="JMFJCXB-GZDE4BN-OCJE3VF-65GYZNU-AIVJRET-3J6HMRQ-AUQIGJO-FKNHMQU" name="f2">
-        <address>127.0.0.1:22002</address>
-    </device>
-    <gui enabled="true" tls="false">
-        <address>127.0.0.1:8082</address>
-        <apikey>abc123</apikey>
-    </gui>
-    <options>
-        <listenAddress>127.0.0.1:22002</listenAddress>
-        <globalAnnounceServer>announce.syncthing.net:22026</globalAnnounceServer>
-        <globalAnnounceEnabled>false</globalAnnounceEnabled>
-        <localAnnounceEnabled>true</localAnnounceEnabled>
-        <localAnnouncePort>21025</localAnnouncePort>
-        <parallelRequests>16</parallelRequests>
-        <maxSendKbps>0</maxSendKbps>
-        <rescanIntervalS>15</rescanIntervalS>
-        <reconnectionIntervalS>5</reconnectionIntervalS>
-        <maxChangeKbps>10000</maxChangeKbps>
-        <startBrowser>false</startBrowser>
-        <upnpEnabled>false</upnpEnabled>
-        <urAccepted>-1</urAccepted>
-    </options>
-</configuration>

+ 0 - 23
test/f2/https-cert.pem

@@ -1,23 +0,0 @@
------BEGIN CERTIFICATE-----
-MIID5TCCAk+gAwIBAgIIORN2TOZWOjwwCwYJKoZIhvcNAQELMBQxEjAQBgNVBAMT
-CXN5bmN0aGluZzAeFw0xNDA5MTUwNTE0MDJaFw00OTEyMzEyMzU5NTlaMBQxEjAQ
-BgNVBAMTCXN5bmN0aGluZzCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGB
-ANizpGVpWvlhkwRddVkVHo68JFSRG74ostSECMUGZTt3vpoUFuCYBUsh138KOPUH
-qN59mlgGoCbvH0RVN54UDTjw32n3r5c/Pzm+ky842O1gkrexizxrODtVL/SQnp35
-nPsiU86GUZ8IJIfiqYmcjd+exHmi3iU0f+m39pHQuxjeHpSNBJ9VsV30pCouFcSu
-i2PI1VEsPWG/w9gL19al7JcOPhxrLQBcUzG8rTnXTFq0cxGaZre4NsVacQPC2IcE
-Jdis6M6b/eTCbHquPSVs+gd9NT8ubJy6CzX5w3FGODTsQAtW1lpI7qgOQ3DRT6+n
-LY3SP792le1ZEs/C0wd/xIP0gkM+PD6ZW070xmdDTbgSj48JvJIn1Xo3+XVTSVJ0
-bzEbGMfGwxstZR+d/vIH1XX8gu8M9reJsT1InTLvGvJDuiZjp1aHUo8z4E6ZPKRb
-I/d7h/V4nZmBaCZYjht5hxR4e3jcpzaEBq4Foh4/0e/iLnZn8JxgejDys2NqqUTx
-8wIDAQABoz8wPTAOBgNVHQ8BAf8EBAMCAKAwHQYDVR0lBBYwFAYIKwYBBQUHAwEG
-CCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwCwYJKoZIhvcNAQELA4IBgQANaMq2yf7A
-R5r2ac1+MCwzgLSBFTMrdfperjhEvkkyC7ln73qU6FhJzbccU3NCR1pI6052X5sM
-68wFBf/opIEFJLm/KRmfAlNQ1I02a0gCmmpm8stjtfynC6Iu7fKdWjytZRfXY6F7
-bXCNoUlbQmePHOawIS26JP3QMauhnetRPUJt72/Yt+HFgt2cEdazzuWpbs/JVp1T
-UFI/GvVTKkAgDWcb93mm1dKkHd8pOA0ATURnV+zyfOOky85Gky3++WOMre6Cb9rQ
-PlooY9tQGvvuBqc17yY/RPcOBMtpUna6SKw+gW9D67fIjp529lIhcmInh1nuBjHS
-EbSIxeQBGlYQKHcMfOW2HQrax0QSsMT39ppIHuiJ7ZHBO26HhtdnryhJNMiSpEs3
-BK2/kGZev8CQCJrNTXCRMEPLhDHMKhHGHTSMkYJnAVEsKtzY9yFqfL7LiFA6k2Lu
-CPNNZ5Ftb1RpEV2rkiNK2Le/oqg15c3la+UbTy+rfMzjLvkV33kFYcQ=
------END CERTIFICATE-----

+ 0 - 39
test/f2/https-key.pem

@@ -1,39 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-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
------END RSA PRIVATE KEY-----

+ 0 - 39
test/f2/key.pem

@@ -1,39 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-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
------END RSA PRIVATE KEY-----

+ 3 - 5
test/filetype_test.go

@@ -15,13 +15,11 @@
 
 // +build integration
 
-// This currently fails; it should be fixed
-package integration_test
+package integration
 
 import (
 	"log"
 	"os"
-	"strings"
 	"testing"
 	"time"
 
@@ -128,7 +126,7 @@ func testFileTypeChange(t *testing.T) {
 	for {
 		comp, err := sender.peerCompletion()
 		if err != nil {
-			if strings.Contains(err.Error(), "use of closed network connection") {
+			if isTimeout(err) {
 				time.Sleep(time.Second)
 				continue
 			}
@@ -200,7 +198,7 @@ func testFileTypeChange(t *testing.T) {
 	for {
 		comp, err := sender.peerCompletion()
 		if err != nil {
-			if strings.Contains(err.Error(), "use of closed network connection") {
+			if isTimeout(err) {
 				time.Sleep(time.Second)
 				continue
 			}

+ 0 - 128
test/genfiles.go

@@ -1,128 +0,0 @@
-// Copyright (C) 2014 The Syncthing Authors.
-//
-// This program is free software: you can redistribute it and/or modify it
-// under the terms of the GNU General Public License as published by the Free
-// Software Foundation, either version 3 of the License, or (at your option)
-// any later version.
-//
-// This program is distributed in the hope that it will be useful, but WITHOUT
-// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
-// more details.
-//
-// You should have received a copy of the GNU General Public License along
-// with this program. If not, see <http://www.gnu.org/licenses/>.
-
-// +build ignore
-
-package main
-
-import (
-	"flag"
-	"fmt"
-	"io"
-	"log"
-	"math/rand"
-	"os"
-	"path/filepath"
-	"time"
-)
-
-func ReadRand(bs []byte) (int, error) {
-	var r uint32
-	for i := range bs {
-		if i%4 == 0 {
-			r = uint32(rand.Int63())
-		}
-		bs[i] = byte(r >> uint((i%4)*8))
-	}
-	return len(bs), nil
-}
-
-func name() string {
-	var b [16]byte
-	ReadRand(b[:])
-	return fmt.Sprintf("%x", b[:])
-}
-
-func main() {
-	var files int
-	var maxexp int
-	var srcname string
-	var random bool
-
-	flag.IntVar(&files, "files", 1000, "Number of files")
-	flag.IntVar(&maxexp, "maxexp", 20, "Maximum file size (max = 2^n + 128*1024 B)")
-	flag.StringVar(&srcname, "src", "/usr/share/dict/words", "Source material")
-	flag.BoolVar(&random, "random", true, "When false, always generate the same set of file")
-	flag.Parse()
-
-	if random {
-		rand.Seed(time.Now().UnixNano())
-	} else {
-		rand.Seed(42)
-	}
-
-	fd, err := os.Open(srcname)
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	for i := 0; i < files; i++ {
-		n := name()
-		p0 := filepath.Join(string(n[0]), n[0:2])
-		err = os.MkdirAll(p0, 0755)
-		if err != nil {
-			log.Fatal(err)
-		}
-
-		s := 1 << uint(rand.Intn(maxexp))
-		a := 128 * 1024
-		if a > s {
-			a = s
-		}
-		s += rand.Intn(a)
-
-		src := io.LimitReader(&inifiteReader{fd}, int64(s))
-
-		p1 := filepath.Join(p0, n)
-		dst, err := os.Create(p1)
-		if err != nil {
-			log.Fatal(err)
-		}
-
-		_, err = io.Copy(dst, src)
-		if err != nil {
-			log.Fatal(err)
-		}
-
-		err = dst.Close()
-		if err != nil {
-			log.Fatal(err)
-		}
-
-		err = os.Chmod(p1, os.FileMode(rand.Intn(0777)|0400))
-		if err != nil {
-			log.Fatal(err)
-		}
-
-		t := time.Now().Add(-time.Duration(rand.Intn(30*86400)) * time.Second)
-		err = os.Chtimes(p1, t, t)
-		if err != nil {
-			log.Fatal(err)
-		}
-	}
-}
-
-type inifiteReader struct {
-	rd io.ReadSeeker
-}
-
-func (i *inifiteReader) Read(bs []byte) (int, error) {
-	n, err := i.rd.Read(bs)
-	if err == io.EOF {
-		err = nil
-		i.rd.Seek(0, 0)
-	}
-	return n, err
-}

+ 2 - 2
test/h1/config.xml

@@ -43,8 +43,8 @@
         <localAnnounceEnabled>true</localAnnounceEnabled>
         <localAnnouncePort>21025</localAnnouncePort>
         <localAnnounceMCAddr>[ff32::5222]:21026</localAnnounceMCAddr>
-        <maxSendKbps>50000</maxSendKbps>
-        <maxRecvKbps>50000</maxRecvKbps>
+        <maxSendKbps>0</maxSendKbps>
+        <maxRecvKbps>0</maxRecvKbps>
         <reconnectionIntervalS>5</reconnectionIntervalS>
         <startBrowser>false</startBrowser>
         <upnpEnabled>true</upnpEnabled>

+ 2 - 390
test/h2/config.xml

@@ -1,106 +1,9 @@
 <configuration version="7">
     <folder id="default" path="s2" ro="false" rescanIntervalS="15" ignorePerms="false">
-        <device id="AF2HXQA-DOKKIMI-PKOG4RE-E25UTJ7-PSGQ7T5-WEY7YT5-SG6N7W5-NNA4BQM"></device>
-        <device id="AND7GW6-DZN66F2-TKYJSTC-ACI7MYT-75X4T63-SE5S4MQ-GBSDHDL-4CLHJA6"></device>
-        <device id="ATFGYHM-ZTA5Z7B-NYPY4OK-VJWDD6Y-SLCDKED-L2VDESD-TUBLMLH-CHAJEAI"></device>
-        <device id="AUI2JXT-PXK3PMB-YATDUNI-RIGBTKQ-VBUJLUH-JLWZEEZ-3UV5X2G-AWKOVQH"></device>
-        <device id="AZCVBDG-6GATBA3-XX7HOH3-LY5R3L2-SPCQOIT-KFVLUCC-GQ3KJTJ-ZSP3RAQ"></device>
-        <device id="A4W7JO6-IM2ZWBX-AYYLCEP-NFTJKQV-2QRGZJ4-QGSP3HI-R7UHNYF-GAKDLAS"></device>
-        <device id="BDZ662J-Q5IXRE3-CEPHNIM-V5D76GA-U26VZRI-UHTDERX-UABQEQ3-CKE5CQD"></device>
-        <device id="BP6X6FF-2SEA3QR-RW2Y55O-B4X7LR6-5EXGA6D-KM725CC-CIDD7ZZ-7VNKXAT"></device>
-        <device id="CDUKUIK-56DOHM3-M7VHCL6-5HGTZHB-QULOOBK-3DK6QXC-4DDDXKS-H5OOZAQ"></device>
-        <device id="CJRFL4X-PBD3H6K-2GCPPYS-JCZ73GF-SN63HMU-WGYMZQZ-W2AWEDG-AZ623QB"></device>
-        <device id="DDGR3XN-CKDGXS4-VR6G2T3-GQJVO7E-XDZGWUJ-733J3G7-NMGQFS5-ETFWKA6"></device>
-        <device id="DFAWRIU-3MSLR65-L7DZE53-UNR6KEX-FTJZJ2B-NVYEPQH-ZYDYU6D-ROAKFAJ"></device>
-        <device id="DMFDJGV-UDOMXZU-FKKYXCT-BEGE7K7-OVAFMQW-NBLQ46C-J6VORML-27T3SA2"></device>
-        <device id="DM4ZS3J-WF2WJGP-YNPTRRZ-MKUEDUC-7N5Q6EX-IHLR7DM-VHMP3FE-KXGHDAG"></device>
-        <device id="DPJWHYQ-S7N7D4C-P4CY65M-ZJ6MFOD-OR5CF7N-2TYSQ6T-IZQDK67-NYDMCQR"></device>
-        <device id="DYVI4HB-DO3WULH-YC4CVUV-KOHXVYH-MSRQH5O-P4JX5T4-WCVDJK2-QPWZPQQ"></device>
-        <device id="EAELU3R-WFMEOHV-VUF3ZQU-T6QLRUQ-S7HFGGG-447PNIB-Z32VNM6-3XRG2QM"></device>
-        <device id="EHYW5K4-WLONYO4-LVJFY7T-7Y6W7EA-N3O5ADS-NPXYFBA-TOSC3X2-J4BYNAV"></device>
-        <device id="E3KQXYD-ST7GNHR-EQVEBUK-I7X4NFR-J6JEGSL-XSU3236-PXDEOYF-SIGVJQD"></device>
-        <device id="FDN276E-J7AQ32B-Y4CBY3Q-TWCQ2RV-QQTQ5NJ-NLLFCGR-UOXSYQN-VLHSHAK"></device>
-        <device id="FOUTNQ4-M3X4ZL6-VPG3ZD6-3EFUKTT-XVNS6N6-G2E56OA-LYBDCXY-S52OLQJ"></device>
-        <device id="F3CJABZ-VOJPUJJ-A6V6J2O-PQF32IV-5VZY45B-ASPFXOE-OMZOBYX-IIVGJQR"></device>
-        <device id="F722I3J-JNESQSZ-NWHMLX7-OX5YQPY-Q4P7AKS-RAVRYFO-LUFHTBF-MV5ZEAW"></device>
-        <device id="GRCZC6E-PRZ5EPJ-5XZK2WL-K7PBC4D-EU7J5K3-YHME4GD-AKHMR3U-HTGE3AJ"></device>
-        <device id="GTZORAO-4U2BMRT-NJ7VGJV-DEFE7X4-GK7GOKP-56NADQB-DKRY64G-GK4JSAN"></device>
-        <device id="HHKXJNI-2FOFGMU-KT55UTA-J7CDGBX-KE3RMHK-NDVQYCB-DPUAFTN-C62LTAJ"></device>
-        <device id="HOA63TU-7NASFTP-7JKRNDD-CEBCBTL-3UB6GEH-LE3TBF6-UHBJUX2-B53JYQO"></device>
-        <device id="H4IJPSS-SGRGAHF-4HVWJXK-AQ2EV4C-EMWQWG4-63ORWGX-CTBONEL-3IO2VA2"></device>
-        <device id="IEDANYN-UEK237R-Z6J75BD-3SK2RPX-66FJ2F2-T347HN3-KYY2PW5-47ZAIAE"></device>
-        <device id="IHFBE2Q-VJKGLJN-PYEX3SL-ORFUM6B-GUDQVUN-32TZFZO-GMYDUFI-VBICSA3"></device>
-        <device id="ITFMT23-LPJ4LN3-Y6HQUYG-GO47ZU7-PO6SUJ6-7UO2JNG-SKI5CTG-WJCK2AP"></device>
-        <device id="IWWUWMQ-3KMUGCT-JJII2HN-J6NN6OW-43AM5TT-MHJXRCR-D7MJVWE-HLAD4QV"></device>
-        <device id="IW5I76B-NKCHGJ5-HAQNOBW-LRHKYSH-54VEQQC-HQAXHVC-DX4ME3O-C7XGUAQ"></device>
-        <device id="I2VEKOT-P63JCHG-LKHJAOJ-XL4VNH2-Y7WQGAW-JKQQQQY-QHIOB77-Q7WLYAW"></device>
         <device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU"></device>
         <device id="JMFJCXB-GZDE4BN-OCJE3VF-65GYZNU-AIVJRET-3J6HMRQ-AUQIGJO-FKNHMQU"></device>
-        <device id="JWPMLRX-U5Q3LSU-LZJCM2Z-GIZLQO2-UBSZKUO-INZXYTZ-7E6PDK2-PPDQWA4"></device>
-        <device id="KBBKAQ6-VINPVTG-LXSDXXF-T67N5DU-ONRJI3P-WIAZBY7-UCMAJJ4-274ZBA5"></device>
-        <device id="KNOUKFP-JEJTAZO-FUI7KMO-4PQJOMB-CX255W3-U3SHR2I-LJ4LBHL-AY6BCA5"></device>
-        <device id="KOUXI7Y-6PWCH2V-552D2Y4-6D3HQJZ-CEKC5IY-XVUV2KC-452GVYG-AJN4TQB"></device>
-        <device id="K3Z2ESL-SKLK2UF-CLA2RLL-DWQVXMU-7PQQ5MV-2BWWSCI-MAOF7YR-277YSAL"></device>
-        <device id="LW7D6SQ-3UIMICX-EEPGTNA-P6NNR2M-35WLLB5-QKW5KWV-3PGTWA3-QM65DA4"></device>
-        <device id="LYYJOWF-WZWZ3Z6-O4JRBOM-44LPK33-YDB5EFZ-CTX2FNI-C5BNMOH-ZBREKAK"></device>
-        <device id="L5GRUQX-3FPGQG6-CWJ3CQN-QJO7BFV-FDXYBXG-AKAZ465-3D37XCR-SHPHDQS"></device>
-        <device id="L7L37HS-3STS6WA-G76FKTT-FAIFH4G-46EORMF-6PA5JFZ-RW4QJDM-SDSBIAM"></device>
-        <device id="MNLYDAL-BTM5JJL-PYOD2C6-MWKXW5N-I7KGYQJ-TAJ2NQE-K2MI7C5-RIM7CA7"></device>
-        <device id="NBO5E2S-OIKGPUN-AXQSTWG-YNSZTQD-YSQ3JGC-BAPD72J-5BUYGBC-4U75XQK"></device>
-        <device id="NWKOEM4-T62TE6V-H4X75L2-GX75Q3C-XCA3OS7-X7MHSVV-IOS5V3U-6Y4IAAZ"></device>
-        <device id="NZKXQ5V-GXMQEY4-77E6DUN-UJK5O3O-633AEIQ-2ZB6FJL-ZPT3AFY-IFMTVAS"></device>
-        <device id="OYRDGZZ-BBBYTOG-PPOEKNF-RFOU5YJ-LOJXGDC-PZFHUMW-EHKAEGM-ZI5O6QX"></device>
-        <device id="O5BUAKV-5PXZUBI-TGCAIHE-646RUCT-5KSKVSY-NMVEZNC-XWIDH4M-N4FWJQK"></device>
-        <device id="PR7FK7P-O57IUUQ-L7NVOBM-BJ6B6HV-A4Q2ZM7-3ZB7YPB-2CEI225-VNE27QZ"></device>
-        <device id="P74TJEI-WJADRBZ-J5ZWR3D-WNGTH7F-QNTKVBW-QJSYCKN-VSXWFFY-BFZXZQH"></device>
-        <device id="QLQ4VDH-DCRQOCW-45ZU3NH-KGVNZ4K-RY5VH5M-E54B35V-3GDPECV-FATMQQN"></device>
-        <device id="QTSMMK3-7LDPTPP-NFQJBKI-MXPNRLC-RBJ46YH-EUOIHQX-X5OBHY5-DSEHQAB"></device>
-        <device id="RNEPSQ3-XBYBJX2-DC63XIJ-KOV6OWX-3RBQ245-SDO5NPG-7DNYUIJ-V3BVBAR"></device>
-        <device id="RSPOEAQ-WEOFVAO-7F5OHVV-FPEK2XA-KG23ACJ-ISANINA-EVNGPFM-6GUGUAY"></device>
-        <device id="SMFBW3X-ZG7GEVQ-Q5EY2HV-QTTJ2AK-CMKRW7K-4ZYN5KF-D5LHXBA-J2WJPQA"></device>
-        <device id="SRC4EBU-57YZZF2-U72JRON-LWMXERT-NL3R4YY-7T3H6FI-VPK7KMQ-Q7LGBQE"></device>
-        <device id="SVUEDXK-GOM6UEO-BK725GI-4R4GRA2-SL6EJQ7-2TQZDF4-IQEM34P-P32HLAN"></device>
-        <device id="S257JIU-2LMK7R6-J4EPHQE-DHBSA2V-5S45UV5-NQNI6G4-UUPXTWK-5L645AB"></device>
-        <device id="S475AJS-UF2H3WA-ZKSFH7J-YJC6Q4P-L6O7NDM-Q7LJKVE-CQ2DTVS-N6TY6QJ"></device>
-        <device id="TE3RAAB-2F65ZF2-MHDELWZ-YJHF72E-5S7HGWF-NJ6CKZK-3LJHQ72-YGOPOAR"></device>
-        <device id="TJEEOEJ-ADKANZG-T3RYCWO-FYJW3GQ-TX7FDLE-I3X3QLX-TQ37HW4-DQQZEAK"></device>
-        <device id="TN64K4J-D7S33RD-55TMUBA-D2VKVRW-FAPQRGY-2VXAMMZ-F2RBYC7-DJAXFAI"></device>
-        <device id="TP4ORAM-C2K6L3Z-3SFLGQF-NILNVZQ-3VSHEGT-LLKQU7K-BV66VQZ-TP65EAS"></device>
-        <device id="T345SQH-CZNXG3Q-WVMQDHY-S524RKJ-C3AP767-QTYGY3X-GASFDCK-LURRWAC"></device>
-        <device id="UMMN7QY-46JHZ4X-UJ5TWWV-EU6OKDA-4GM76QK-UUJJLPH-27H6P22-XQALWQ6"></device>
-        <device id="UZPBG42-GSUBWXD-ALOS27M-AQAWWWJ-XP5DA46-3GWEZ4U-I5I2JAZ-5VPHYQI"></device>
-        <device id="VU764LI-72YK4KV-APLQAXJ-4Q46MTT-FCQFB33-JTCH4E5-G7OJRFI-YGPJYAJ"></device>
-        <device id="WDGE4EI-CO3MSS6-JM5ASRP-YHBCKBN-EWAT5KQ-GGQQTZE-OSDTGH2-P2BE3AA"></device>
-        <device id="WGURZJA-JLZYF3P-KRQDIAL-6RGUYSZ-UZEJ3DO-U6JOGTO-PYQRA7K-NOEFSQ2"></device>
-        <device id="WLTEXSS-RUQSDSJ-FWYVE4T-S25G37F-4XPT7GI-VLBSHCS-SLHXBEF-QSKCQAK"></device>
-        <device id="WNK25O3-CDDKOM6-VDJKB2C-GZ6L6GA-HI7WHXU-G2SCLLF-EEDJX6N-AYY5OQM"></device>
-        <device id="WTA46HO-WMIMK2N-GMNLWSQ-UOZI2O7-JVG3YY2-FWXQ3NJ-NWG5PLX-RN26AAG"></device>
-        <device id="XG6FGXG-CCB54FX-AVREF7W-YMR66ED-66H4QQD-KXK5K7C-DVVYKU2-GF77LQK"></device>
-        <device id="XSWE7HJ-AHTZGEZ-UNH6ZSK-WZHRS2C-55LQZDR-7GLSE6Z-VWEDFWT-ZS4DCAK"></device>
-        <device id="XXXMH6A-3J53GPW-7BVBR76-5UJNW3E-6H2ACDZ-DY46Z6T-FD7GRPE-RGIQFAE"></device>
-        <device id="X2J2EIR-6MCQUSZ-DZU37TI-Y5S4QUN-JPTDKHT-ANBGXU5-YRD3EGF-6VOEHQG"></device>
-        <device id="X3WHUR4-N5BSQ4T-YFC3A3S-KI6KXQ2-6GL66YV-G2YGVKY-LEKVIXY-M62C6A7"></device>
-        <device id="X4EX7JM-CQSNCZ5-H5UNVUW-RNWT4U7-VPFKPB4-P65IWZC-N74SCAM-RLLFUAT"></device>
-        <device id="YEKECF3-5YAC2EZ-ADYLARL-THNGWI3-ZFU6UNK-3KCFZWB-IRYFH6X-LO5ZHQC"></device>
-        <device id="YP66HGG-CIWZ6N2-A3T4PXT-KP47YWK-3ZVW5UX-MGTZRHL-YNB22IK-IEEWNAY"></device>
-        <device id="YRG374E-AB2PK5W-UKR2MOA-E43VSQF-ICO72FN-FDZDX7F-Q75UE2H-EUCIFQJ"></device>
-        <device id="ZCZPFDT-GHJKTPJ-22WKCWP-2UO7X33-NUD2YAK-P6MDHN6-2JVAETG-JPXXLAR"></device>
-        <device id="2MDVGTI-EGVOHIO-SPFMLV6-NRFP2EC-HWMLWVO-QVLLTC3-DSQPHMY-JDEIQQ5"></device>
-        <device id="22UBH4D-HGCP3EW-EHVJTZ4-6PB2HOB-LDPOC3X-ONY6PA6-2DFNBIX-D3X5UQT"></device>
-        <device id="3OQ6R42-O76KQJM-JYN7EGL-PWKZUPG-276FWG2-CP3CTV3-SM545L3-KTTH3AG"></device>
-        <device id="3QW353X-J52M5LW-3DUY2Z5-SG5JU7Z-6NSJFAM-74YPGMA-VHL2W3N-5PX2JQO"></device>
         <device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU"></device>
-        <device id="4IB56JJ-IXYAHRI-6W75NTN-TEIMPRE-25NAG6F-MYGBJBG-JB33DXY-L2WINQK"></device>
-        <device id="4JSTGIZ-Q2UJXIG-V6QT62A-FSRBKLF-J6FWGIY-PFIAFC3-7GP7DOY-RVNGKQU"></device>
-        <device id="5BKVB65-I2E5UCX-DWWPLNP-IQ7VRZY-I3P42KF-ZKAF7XO-TQTCLVU-TH45GA3"></device>
-        <device id="5OOL3EO-A5DTJ5N-CZHPYVP-YH74NL4-DE3O7AU-ZDFSM6D-KG5IUWA-WJJXBQL"></device>
-        <device id="5RXIYVZ-NOEGGYD-TY4WQTH-U6FQ5IR-LJTW3P3-HFQBN6C-RC4AB5V-MO44PQV"></device>
-        <device id="5WGZMYP-FNTWGLG-PQKEB5V-PJ4LEPL-53SRHBR-HHSHHLD-5KFL3P2-W6MTRQY"></device>
-        <device id="6I5KLVE-VVRXZ55-Z3IQIZH-3FBHKTZ-6MIPAYO-V4QX6K7-7J432VD-T5TY6AU"></device>
-        <device id="6R2BAIE-VRYZJXA-ZKJT4ZK-BQTXA46-VBWMLNY-PINTWYG-TS4Y6GZ-7Z2KVQG"></device>
-        <device id="7BT3IUI-RYS757D-YJPBP6U-WPL2ZI6-CMICBLW-UDYDSVT-NG62TB5-PM2O3A6"></device>
-        <device id="7TVCUYX-MLZH6GF-GDLVMJ6-REPXXUD-DCLPXP2-67HS7VR-QTTABOA-4ZXJOQD"></device>
-        <versioning></versioning>
+        <versioning type="staggered"></versioning>
         <lenientMtimes>false</lenientMtimes>
         <copiers>1</copiers>
         <pullers>16</pullers>
@@ -124,306 +27,15 @@
         <pullers>16</pullers>
         <finishers>1</finishers>
     </folder>
-    <device id="AF2HXQA-DOKKIMI-PKOG4RE-E25UTJ7-PSGQ7T5-WEY7YT5-SG6N7W5-NNA4BQM" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="AND7GW6-DZN66F2-TKYJSTC-ACI7MYT-75X4T63-SE5S4MQ-GBSDHDL-4CLHJA6" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="ATFGYHM-ZTA5Z7B-NYPY4OK-VJWDD6Y-SLCDKED-L2VDESD-TUBLMLH-CHAJEAI" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="AUI2JXT-PXK3PMB-YATDUNI-RIGBTKQ-VBUJLUH-JLWZEEZ-3UV5X2G-AWKOVQH" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="AZCVBDG-6GATBA3-XX7HOH3-LY5R3L2-SPCQOIT-KFVLUCC-GQ3KJTJ-ZSP3RAQ" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="A4W7JO6-IM2ZWBX-AYYLCEP-NFTJKQV-2QRGZJ4-QGSP3HI-R7UHNYF-GAKDLAS" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="BDZ662J-Q5IXRE3-CEPHNIM-V5D76GA-U26VZRI-UHTDERX-UABQEQ3-CKE5CQD" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="BP6X6FF-2SEA3QR-RW2Y55O-B4X7LR6-5EXGA6D-KM725CC-CIDD7ZZ-7VNKXAT" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="CDUKUIK-56DOHM3-M7VHCL6-5HGTZHB-QULOOBK-3DK6QXC-4DDDXKS-H5OOZAQ" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="CJRFL4X-PBD3H6K-2GCPPYS-JCZ73GF-SN63HMU-WGYMZQZ-W2AWEDG-AZ623QB" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="DDGR3XN-CKDGXS4-VR6G2T3-GQJVO7E-XDZGWUJ-733J3G7-NMGQFS5-ETFWKA6" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="DFAWRIU-3MSLR65-L7DZE53-UNR6KEX-FTJZJ2B-NVYEPQH-ZYDYU6D-ROAKFAJ" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="DMFDJGV-UDOMXZU-FKKYXCT-BEGE7K7-OVAFMQW-NBLQ46C-J6VORML-27T3SA2" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="DM4ZS3J-WF2WJGP-YNPTRRZ-MKUEDUC-7N5Q6EX-IHLR7DM-VHMP3FE-KXGHDAG" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="DPJWHYQ-S7N7D4C-P4CY65M-ZJ6MFOD-OR5CF7N-2TYSQ6T-IZQDK67-NYDMCQR" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="DYVI4HB-DO3WULH-YC4CVUV-KOHXVYH-MSRQH5O-P4JX5T4-WCVDJK2-QPWZPQQ" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="EAELU3R-WFMEOHV-VUF3ZQU-T6QLRUQ-S7HFGGG-447PNIB-Z32VNM6-3XRG2QM" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="EHYW5K4-WLONYO4-LVJFY7T-7Y6W7EA-N3O5ADS-NPXYFBA-TOSC3X2-J4BYNAV" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="E3KQXYD-ST7GNHR-EQVEBUK-I7X4NFR-J6JEGSL-XSU3236-PXDEOYF-SIGVJQD" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="FDN276E-J7AQ32B-Y4CBY3Q-TWCQ2RV-QQTQ5NJ-NLLFCGR-UOXSYQN-VLHSHAK" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="FOUTNQ4-M3X4ZL6-VPG3ZD6-3EFUKTT-XVNS6N6-G2E56OA-LYBDCXY-S52OLQJ" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="F3CJABZ-VOJPUJJ-A6V6J2O-PQF32IV-5VZY45B-ASPFXOE-OMZOBYX-IIVGJQR" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="F722I3J-JNESQSZ-NWHMLX7-OX5YQPY-Q4P7AKS-RAVRYFO-LUFHTBF-MV5ZEAW" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="GRCZC6E-PRZ5EPJ-5XZK2WL-K7PBC4D-EU7J5K3-YHME4GD-AKHMR3U-HTGE3AJ" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="GTZORAO-4U2BMRT-NJ7VGJV-DEFE7X4-GK7GOKP-56NADQB-DKRY64G-GK4JSAN" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="HHKXJNI-2FOFGMU-KT55UTA-J7CDGBX-KE3RMHK-NDVQYCB-DPUAFTN-C62LTAJ" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="HOA63TU-7NASFTP-7JKRNDD-CEBCBTL-3UB6GEH-LE3TBF6-UHBJUX2-B53JYQO" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="H4IJPSS-SGRGAHF-4HVWJXK-AQ2EV4C-EMWQWG4-63ORWGX-CTBONEL-3IO2VA2" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="IEDANYN-UEK237R-Z6J75BD-3SK2RPX-66FJ2F2-T347HN3-KYY2PW5-47ZAIAE" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="IHFBE2Q-VJKGLJN-PYEX3SL-ORFUM6B-GUDQVUN-32TZFZO-GMYDUFI-VBICSA3" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="ITFMT23-LPJ4LN3-Y6HQUYG-GO47ZU7-PO6SUJ6-7UO2JNG-SKI5CTG-WJCK2AP" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="IWWUWMQ-3KMUGCT-JJII2HN-J6NN6OW-43AM5TT-MHJXRCR-D7MJVWE-HLAD4QV" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="IW5I76B-NKCHGJ5-HAQNOBW-LRHKYSH-54VEQQC-HQAXHVC-DX4ME3O-C7XGUAQ" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="I2VEKOT-P63JCHG-LKHJAOJ-XL4VNH2-Y7WQGAW-JKQQQQY-QHIOB77-Q7WLYAW" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
     <device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" name="s1" compression="true" introducer="false">
         <address>127.0.0.1:22001</address>
     </device>
     <device id="JMFJCXB-GZDE4BN-OCJE3VF-65GYZNU-AIVJRET-3J6HMRQ-AUQIGJO-FKNHMQU" name="s2" compression="true" introducer="false">
         <address>127.0.0.1:22002</address>
     </device>
-    <device id="JWPMLRX-U5Q3LSU-LZJCM2Z-GIZLQO2-UBSZKUO-INZXYTZ-7E6PDK2-PPDQWA4" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="KBBKAQ6-VINPVTG-LXSDXXF-T67N5DU-ONRJI3P-WIAZBY7-UCMAJJ4-274ZBA5" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="KNOUKFP-JEJTAZO-FUI7KMO-4PQJOMB-CX255W3-U3SHR2I-LJ4LBHL-AY6BCA5" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="KOUXI7Y-6PWCH2V-552D2Y4-6D3HQJZ-CEKC5IY-XVUV2KC-452GVYG-AJN4TQB" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="K3Z2ESL-SKLK2UF-CLA2RLL-DWQVXMU-7PQQ5MV-2BWWSCI-MAOF7YR-277YSAL" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="LW7D6SQ-3UIMICX-EEPGTNA-P6NNR2M-35WLLB5-QKW5KWV-3PGTWA3-QM65DA4" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="LYYJOWF-WZWZ3Z6-O4JRBOM-44LPK33-YDB5EFZ-CTX2FNI-C5BNMOH-ZBREKAK" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="L5GRUQX-3FPGQG6-CWJ3CQN-QJO7BFV-FDXYBXG-AKAZ465-3D37XCR-SHPHDQS" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="L7L37HS-3STS6WA-G76FKTT-FAIFH4G-46EORMF-6PA5JFZ-RW4QJDM-SDSBIAM" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="MNLYDAL-BTM5JJL-PYOD2C6-MWKXW5N-I7KGYQJ-TAJ2NQE-K2MI7C5-RIM7CA7" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="NBO5E2S-OIKGPUN-AXQSTWG-YNSZTQD-YSQ3JGC-BAPD72J-5BUYGBC-4U75XQK" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="NWKOEM4-T62TE6V-H4X75L2-GX75Q3C-XCA3OS7-X7MHSVV-IOS5V3U-6Y4IAAZ" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="NZKXQ5V-GXMQEY4-77E6DUN-UJK5O3O-633AEIQ-2ZB6FJL-ZPT3AFY-IFMTVAS" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="OYRDGZZ-BBBYTOG-PPOEKNF-RFOU5YJ-LOJXGDC-PZFHUMW-EHKAEGM-ZI5O6QX" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="O5BUAKV-5PXZUBI-TGCAIHE-646RUCT-5KSKVSY-NMVEZNC-XWIDH4M-N4FWJQK" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="PR7FK7P-O57IUUQ-L7NVOBM-BJ6B6HV-A4Q2ZM7-3ZB7YPB-2CEI225-VNE27QZ" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="P74TJEI-WJADRBZ-J5ZWR3D-WNGTH7F-QNTKVBW-QJSYCKN-VSXWFFY-BFZXZQH" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="QLQ4VDH-DCRQOCW-45ZU3NH-KGVNZ4K-RY5VH5M-E54B35V-3GDPECV-FATMQQN" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="QTSMMK3-7LDPTPP-NFQJBKI-MXPNRLC-RBJ46YH-EUOIHQX-X5OBHY5-DSEHQAB" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="RNEPSQ3-XBYBJX2-DC63XIJ-KOV6OWX-3RBQ245-SDO5NPG-7DNYUIJ-V3BVBAR" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="RSPOEAQ-WEOFVAO-7F5OHVV-FPEK2XA-KG23ACJ-ISANINA-EVNGPFM-6GUGUAY" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="SMFBW3X-ZG7GEVQ-Q5EY2HV-QTTJ2AK-CMKRW7K-4ZYN5KF-D5LHXBA-J2WJPQA" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="SRC4EBU-57YZZF2-U72JRON-LWMXERT-NL3R4YY-7T3H6FI-VPK7KMQ-Q7LGBQE" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="SVUEDXK-GOM6UEO-BK725GI-4R4GRA2-SL6EJQ7-2TQZDF4-IQEM34P-P32HLAN" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="S257JIU-2LMK7R6-J4EPHQE-DHBSA2V-5S45UV5-NQNI6G4-UUPXTWK-5L645AB" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="S475AJS-UF2H3WA-ZKSFH7J-YJC6Q4P-L6O7NDM-Q7LJKVE-CQ2DTVS-N6TY6QJ" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="TE3RAAB-2F65ZF2-MHDELWZ-YJHF72E-5S7HGWF-NJ6CKZK-3LJHQ72-YGOPOAR" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="TJEEOEJ-ADKANZG-T3RYCWO-FYJW3GQ-TX7FDLE-I3X3QLX-TQ37HW4-DQQZEAK" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="TN64K4J-D7S33RD-55TMUBA-D2VKVRW-FAPQRGY-2VXAMMZ-F2RBYC7-DJAXFAI" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="TP4ORAM-C2K6L3Z-3SFLGQF-NILNVZQ-3VSHEGT-LLKQU7K-BV66VQZ-TP65EAS" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="T345SQH-CZNXG3Q-WVMQDHY-S524RKJ-C3AP767-QTYGY3X-GASFDCK-LURRWAC" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="UMMN7QY-46JHZ4X-UJ5TWWV-EU6OKDA-4GM76QK-UUJJLPH-27H6P22-XQALWQ6" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="UZPBG42-GSUBWXD-ALOS27M-AQAWWWJ-XP5DA46-3GWEZ4U-I5I2JAZ-5VPHYQI" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="VU764LI-72YK4KV-APLQAXJ-4Q46MTT-FCQFB33-JTCH4E5-G7OJRFI-YGPJYAJ" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="WDGE4EI-CO3MSS6-JM5ASRP-YHBCKBN-EWAT5KQ-GGQQTZE-OSDTGH2-P2BE3AA" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="WGURZJA-JLZYF3P-KRQDIAL-6RGUYSZ-UZEJ3DO-U6JOGTO-PYQRA7K-NOEFSQ2" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="WLTEXSS-RUQSDSJ-FWYVE4T-S25G37F-4XPT7GI-VLBSHCS-SLHXBEF-QSKCQAK" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="WNK25O3-CDDKOM6-VDJKB2C-GZ6L6GA-HI7WHXU-G2SCLLF-EEDJX6N-AYY5OQM" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="WTA46HO-WMIMK2N-GMNLWSQ-UOZI2O7-JVG3YY2-FWXQ3NJ-NWG5PLX-RN26AAG" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="XG6FGXG-CCB54FX-AVREF7W-YMR66ED-66H4QQD-KXK5K7C-DVVYKU2-GF77LQK" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="XSWE7HJ-AHTZGEZ-UNH6ZSK-WZHRS2C-55LQZDR-7GLSE6Z-VWEDFWT-ZS4DCAK" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="XXXMH6A-3J53GPW-7BVBR76-5UJNW3E-6H2ACDZ-DY46Z6T-FD7GRPE-RGIQFAE" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="X2J2EIR-6MCQUSZ-DZU37TI-Y5S4QUN-JPTDKHT-ANBGXU5-YRD3EGF-6VOEHQG" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="X3WHUR4-N5BSQ4T-YFC3A3S-KI6KXQ2-6GL66YV-G2YGVKY-LEKVIXY-M62C6A7" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="X4EX7JM-CQSNCZ5-H5UNVUW-RNWT4U7-VPFKPB4-P65IWZC-N74SCAM-RLLFUAT" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="YEKECF3-5YAC2EZ-ADYLARL-THNGWI3-ZFU6UNK-3KCFZWB-IRYFH6X-LO5ZHQC" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="YP66HGG-CIWZ6N2-A3T4PXT-KP47YWK-3ZVW5UX-MGTZRHL-YNB22IK-IEEWNAY" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="YRG374E-AB2PK5W-UKR2MOA-E43VSQF-ICO72FN-FDZDX7F-Q75UE2H-EUCIFQJ" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="ZCZPFDT-GHJKTPJ-22WKCWP-2UO7X33-NUD2YAK-P6MDHN6-2JVAETG-JPXXLAR" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="2MDVGTI-EGVOHIO-SPFMLV6-NRFP2EC-HWMLWVO-QVLLTC3-DSQPHMY-JDEIQQ5" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="22UBH4D-HGCP3EW-EHVJTZ4-6PB2HOB-LDPOC3X-ONY6PA6-2DFNBIX-D3X5UQT" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="3OQ6R42-O76KQJM-JYN7EGL-PWKZUPG-276FWG2-CP3CTV3-SM545L3-KTTH3AG" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="3QW353X-J52M5LW-3DUY2Z5-SG5JU7Z-6NSJFAM-74YPGMA-VHL2W3N-5PX2JQO" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
     <device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" name="s3" compression="true" introducer="false">
         <address>127.0.0.1:22003</address>
     </device>
-    <device id="4IB56JJ-IXYAHRI-6W75NTN-TEIMPRE-25NAG6F-MYGBJBG-JB33DXY-L2WINQK" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="4JSTGIZ-Q2UJXIG-V6QT62A-FSRBKLF-J6FWGIY-PFIAFC3-7GP7DOY-RVNGKQU" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="5BKVB65-I2E5UCX-DWWPLNP-IQ7VRZY-I3P42KF-ZKAF7XO-TQTCLVU-TH45GA3" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="5OOL3EO-A5DTJ5N-CZHPYVP-YH74NL4-DE3O7AU-ZDFSM6D-KG5IUWA-WJJXBQL" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="5RXIYVZ-NOEGGYD-TY4WQTH-U6FQ5IR-LJTW3P3-HFQBN6C-RC4AB5V-MO44PQV" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="5WGZMYP-FNTWGLG-PQKEB5V-PJ4LEPL-53SRHBR-HHSHHLD-5KFL3P2-W6MTRQY" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="6I5KLVE-VVRXZ55-Z3IQIZH-3FBHKTZ-6MIPAYO-V4QX6K7-7J432VD-T5TY6AU" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="6R2BAIE-VRYZJXA-ZKJT4ZK-BQTXA46-VBWMLNY-PINTWYG-TS4Y6GZ-7Z2KVQG" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="7BT3IUI-RYS757D-YJPBP6U-WPL2ZI6-CMICBLW-UDYDSVT-NG62TB5-PM2O3A6" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="7TVCUYX-MLZH6GF-GDLVMJ6-REPXXUD-DCLPXP2-67HS7VR-QTTABOA-4ZXJOQD" compression="false" introducer="false">
-        <address>dynamic</address>
-    </device>
     <gui enabled="true" tls="false">
         <address>127.0.0.1:8082</address>
         <apikey>abc123</apikey>
@@ -439,7 +51,7 @@
         <maxRecvKbps>0</maxRecvKbps>
         <reconnectionIntervalS>5</reconnectionIntervalS>
         <startBrowser>false</startBrowser>
-        <upnpEnabled>true</upnpEnabled>
+        <upnpEnabled>false</upnpEnabled>
         <upnpLeaseMinutes>0</upnpLeaseMinutes>
         <upnpRenewalMinutes>30</upnpRenewalMinutes>
         <urAccepted>-1</urAccepted>

+ 0 - 23
test/h4/cert.pem

@@ -1,23 +0,0 @@
------BEGIN CERTIFICATE-----
-MIID3jCCAkigAwIBAgIBADALBgkqhkiG9w0BAQswFDESMBAGA1UEAxMJc3luY3Ro
-aW5nMB4XDTE0MDUxMDAwNTM0N1oXDTQ5MTIzMTIzNTk1OVowFDESMBAGA1UEAxMJ
-c3luY3RoaW5nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA9MRyBtAr
-Sjt29azNoCWxx5xZF3RodBcQu+wv5sRR8lWozrr4brfUJLslcQHowqaAprOU1NP+
-BH12P5CSymsUrwAmCwSQ54CimXrNi5RiNMl7dtInJksk4Kp6nJgfyR7TqeQgqxtv
-+skVWdJY7ptxqpVuDfkf1JnNr68dbANw8hEJpPaGm3qOt81YvSg37R75HiOCzv+h
-FcSjKpPyFMvPARMCOHuZS0fYRJtI5nwmR0mWtKfnH/2204YNiQUne/8h2fgtkpxy
-OjxKOs2KJxbmpV6Uur/YyGyinb5+Aa0df3KCBuZmE+i/AsZcTsk0fgefe+bshWG/
-hzrNfV0wsX3TYjYOSBJ04+f/uQW00G1GGSxPwTsShGqVuwfJkTqkjAXX5wcH+PgJ
-ewG/dyMzKklMg19Y65WkhpWa/19o2KSZNw6TO8YM1arwT0STcMc+4fdrVB09lX6q
-NJA8UL8hUX+jbKBzatDY64h1d9E8PE0ODHYgYFO2Ko7e2GnWCQeijGmnAgMBAAGj
-PzA9MA4GA1UdDwEB/wQEAwIAoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH
-AwIwDAYDVR0TAQH/BAIwADALBgkqhkiG9w0BAQsDggGBANFiHcATP5Lm11o65wbh
-sKk7yteTapRohMoLNdW44YNyM8ZkELnrdNY8pe3CWSGy3spBH01+4jbUT+gSltQr
-KTLVxSZ7f91696Og5ag4BQCeFY6ghKD/G9+PlBSj6yb3Y98NZsx8huLfylH+XuJw
-2gP5Nqov4uXaKgYylx2gdaeCb2M+wM/br1DO2HCPCmgbZE5g8RM5JxzojGn/41Le
-IbCd39zdI6NKj9c7T1Bxmt20uzca4nRgXVVzJymedEoF+//sBRk6PQzqgjgn/r3S
-h9vrqo5j8ly/+ojFjBaVY7gq2XHM6/q0LTjeKkv2MUQw+vEEZX65GpBOgBZ8U0Wb
-/NMUUhhDjGE/0G6TCJgq/HdkjmsNaWjO5sWjhnwXNImYXBdH4OenhXIrHcLhcnxN
-2n5sPkDc6n0LVVV7VAjBPXcTmu2uOSK02yqNZLLWJygp1Wl6lbiqLS3bJgYrUv2m
-YkRaR+IqVPw5EPs/QlH0qLBeCyIasaSWUVZeitVwRmqIUA==
------END CERTIFICATE-----

+ 0 - 61
test/h4/config.xml

@@ -1,61 +0,0 @@
-<configuration version="7">
-    <folder id="unique" path="s4" ro="false" rescanIntervalS="60" ignorePerms="false">
-        <device id="EJHMPAQ-OGCVORE-ISB4IS3-SYYVJXF-TKJGLTU-66DIQPF-GJ5D2GX-GQ3OWQK"></device>
-        <device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU"></device>
-        <device id="JMFJCXB-GZDE4BN-OCJE3VF-65GYZNU-AIVJRET-3J6HMRQ-AUQIGJO-FKNHMQU"></device>
-        <device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU"></device>
-        <versioning></versioning>
-        <lenientMtimes>false</lenientMtimes>
-        <copiers>1</copiers>
-        <pullers>16</pullers>
-        <finishers>1</finishers>
-    </folder>
-    <folder id="default" path="s4d" ro="false" rescanIntervalS="60" ignorePerms="false">
-        <device id="EJHMPAQ-OGCVORE-ISB4IS3-SYYVJXF-TKJGLTU-66DIQPF-GJ5D2GX-GQ3OWQK"></device>
-        <device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU"></device>
-        <versioning></versioning>
-        <lenientMtimes>false</lenientMtimes>
-        <copiers>1</copiers>
-        <pullers>16</pullers>
-        <finishers>1</finishers>
-    </folder>
-    <device id="EJHMPAQ-OGCVORE-ISB4IS3-SYYVJXF-TKJGLTU-66DIQPF-GJ5D2GX-GQ3OWQK" name="s4" compression="true" introducer="false">
-        <address>dynamic</address>
-    </device>
-    <device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" name="s1" compression="true" introducer="false">
-        <address>127.0.0.1:22001</address>
-    </device>
-    <device id="JMFJCXB-GZDE4BN-OCJE3VF-65GYZNU-AIVJRET-3J6HMRQ-AUQIGJO-FKNHMQU" name="s2" compression="true" introducer="false">
-        <address>127.0.0.1:22002</address>
-    </device>
-    <device id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" name="s3" compression="true" introducer="false">
-        <address>127.0.0.1:22003</address>
-    </device>
-    <gui enabled="true" tls="false">
-        <address>127.0.0.1:8084</address>
-        <apikey>abc123</apikey>
-    </gui>
-    <options>
-        <listenAddress>:22004</listenAddress>
-        <globalAnnounceServer>udp4://announce.syncthing.net:22026</globalAnnounceServer>
-        <globalAnnounceEnabled>false</globalAnnounceEnabled>
-        <localAnnounceEnabled>false</localAnnounceEnabled>
-        <localAnnouncePort>21025</localAnnouncePort>
-        <localAnnounceMCAddr>[ff32::5222]:21026</localAnnounceMCAddr>
-        <maxSendKbps>0</maxSendKbps>
-        <maxRecvKbps>0</maxRecvKbps>
-        <reconnectionIntervalS>10</reconnectionIntervalS>
-        <startBrowser>false</startBrowser>
-        <upnpEnabled>false</upnpEnabled>
-        <upnpLeaseMinutes>0</upnpLeaseMinutes>
-        <upnpRenewalMinutes>30</upnpRenewalMinutes>
-        <urAccepted>-1</urAccepted>
-        <urUniqueID></urUniqueID>
-        <restartOnWakeup>true</restartOnWakeup>
-        <autoUpgradeIntervalH>12</autoUpgradeIntervalH>
-        <keepTemporariesH>24</keepTemporariesH>
-        <cacheIgnoredFiles>true</cacheIgnoredFiles>
-        <progressUpdateIntervalS>5</progressUpdateIntervalS>
-        <symlinksEnabled>true</symlinksEnabled>
-    </options>
-</configuration>

+ 0 - 23
test/h4/https-cert.pem

@@ -1,23 +0,0 @@
------BEGIN CERTIFICATE-----
-MIID5TCCAk+gAwIBAgIISAohRYcPi4cwCwYJKoZIhvcNAQELMBQxEjAQBgNVBAMT
-CXN5bmN0aGluZzAeFw0xNDA5MTQyMjI0MTBaFw00OTEyMzEyMzU5NTlaMBQxEjAQ
-BgNVBAMTCXN5bmN0aGluZzCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGB
-ANqeoSRjMEnjCOTO6yAaAl3XeV3nAYlC1MK2qPPIBo4NuY8ilEXM0Q7BL1ux8f+k
-V7VPRL2QBizRai7/DqdsXdVQGPY38p4MKpIyp/PXQcPxLyIgZXPE8OQqX9sBltKV
-Xjbe9aJbiGnFrLeQye10DBkq+2UXCMeNX06KjVPaxNf7O4fdowgfYu28QsyMb1lw
-g72ve501lKvtjEobBN2+NAxm1vBKLVU9onbU6ewGZBMHtap9qE+gDulkS419U62p
-79HG/xvBcazWIGSWw9AsHNO6GOtvsjb1U2m2KUHqFAIKmmNPuiy/RupXHoHpgiec
-r+sZHx3OFL66gdnZWNRTMcDUrpflD8f/Mp6gba2+1CM2RB3bIzNaEiYbgippH5FM
-/Hpa9q931+Rucll+NMwPb3ORTG8XSAB7DoNqudixid1S+Grc1bqF2dq9ZzJUwIqH
-uJBtshb1U/p8t7I1pEOTGnWs1b/tasDOIHiHx1AWCKa8mMLzqqx6d9IuzUI5vcql
-xwIDAQABoz8wPTAOBgNVHQ8BAf8EBAMCAKAwHQYDVR0lBBYwFAYIKwYBBQUHAwEG
-CCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwCwYJKoZIhvcNAQELA4IBgQCYFBRPZqpc
-1+A7CpfcYezH4/9Cd9yUMeYBFebGnB9NEsp6Q/BaLAHBaeDfxjed45etOH3WaYBJ
-IUru+bAuk8xKitYmpXi4AByPmYp3pI1gsldqYSZWS6ivpdGD/t4Yd6nOxNZokOOq
-Ygxdg7WCu1d5fgvReZ3GbBlRn6+uym7ZZuTcpgOqbdeMY3EFKwoBR5LtW9iaDeZ5
-gQXvTRk276TgFZuh9GPYv4iD3F3iAlDHdA8sQz4mUnYHxYrWoSViy7TOPZbB/NP0
-eAN5d4s1BhESNzDX3ODR47VL7IyZtVqbNURPdw0BVMnC7m1iKTsSAsh+b4zShNqg
-RNjBtv3Kew5BKP79qUZL5v/WSBgIl1RIko/jp+RwlghoFJGl5ku2A0syZLgUBor1
-VaTfL9Uv/fSYUscBnAd/2Yt+Gxm3Ng48I24FkXdL4ZWVP76+63ugoEGvgAiRTHBC
-Lls7vBQS7a/3TVayhogADkUo6XgZE/FfNTqLeLKoljBLLyYs3CIHE8s=
------END CERTIFICATE-----

+ 0 - 39
test/h4/https-key.pem

@@ -1,39 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-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
------END RSA PRIVATE KEY-----

+ 0 - 39
test/h4/key.pem

@@ -1,39 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-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
------END RSA PRIVATE KEY-----

+ 1 - 1
test/http_test.go

@@ -15,7 +15,7 @@
 
 // +build integration
 
-package integration_test
+package integration
 
 import (
 	"encoding/json"

+ 1 - 1
test/httpstress_test.go

@@ -15,7 +15,7 @@
 
 // +build integration
 
-package integration_test
+package integration
 
 import (
 	"bytes"

+ 0 - 56
test/json.go

@@ -1,56 +0,0 @@
-// Copyright (C) 2014 The Syncthing Authors.
-//
-// This program is free software: you can redistribute it and/or modify it
-// under the terms of the GNU General Public License as published by the Free
-// Software Foundation, either version 3 of the License, or (at your option)
-// any later version.
-//
-// This program is distributed in the hope that it will be useful, but WITHOUT
-// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
-// more details.
-//
-// You should have received a copy of the GNU General Public License along
-// with this program. If not, see <http://www.gnu.org/licenses/>.
-
-// +build ignore
-
-package main
-
-import (
-	"encoding/json"
-	"flag"
-	"fmt"
-	"log"
-	"os"
-	"strconv"
-	"strings"
-)
-
-func main() {
-	log.SetFlags(0)
-	flag.Parse()
-	path := strings.Split(flag.Arg(0), "/")
-
-	var obj map[string]interface{}
-	dec := json.NewDecoder(os.Stdin)
-	dec.UseNumber()
-	dec.Decode(&obj)
-
-	var v interface{} = obj
-	for _, p := range path {
-		switch tv := v.(type) {
-		case map[string]interface{}:
-			v = tv[p]
-		case []interface{}:
-			i, err := strconv.Atoi(p)
-			if err != nil {
-				log.Fatal(err)
-			}
-			v = tv[i]
-		default:
-			return // Silence is golden
-		}
-	}
-	fmt.Println(v)
-}

+ 2 - 3
test/manypeers_test.go

@@ -15,13 +15,12 @@
 
 // +build integration
 
-package integration_test
+package integration
 
 import (
 	"bytes"
 	"encoding/json"
 	"log"
-	"strings"
 	"testing"
 	"time"
 
@@ -105,7 +104,7 @@ func TestManyPeers(t *testing.T) {
 	for {
 		comp, err := sender.peerCompletion()
 		if err != nil {
-			if strings.Contains(err.Error(), "use of closed network connection") {
+			if isTimeout(err) {
 				time.Sleep(250 * time.Millisecond)
 				continue
 			}

+ 0 - 89
test/md5r.go

@@ -1,89 +0,0 @@
-// Copyright (C) 2014 The Syncthing Authors.
-//
-// This program is free software: you can redistribute it and/or modify it
-// under the terms of the GNU General Public License as published by the Free
-// Software Foundation, either version 3 of the License, or (at your option)
-// any later version.
-//
-// This program is distributed in the hope that it will be useful, but WITHOUT
-// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
-// more details.
-//
-// You should have received a copy of the GNU General Public License along
-// with this program. If not, see <http://www.gnu.org/licenses/>.
-
-// +build ignore
-
-package main
-
-import (
-	"crypto/md5"
-	"flag"
-	"fmt"
-	"io"
-	"os"
-	"path/filepath"
-)
-
-var (
-	long bool
-	dirs bool
-)
-
-func main() {
-	flag.BoolVar(&long, "l", false, "Long output")
-	flag.BoolVar(&dirs, "d", false, "Check dirs")
-	flag.Parse()
-	args := flag.Args()
-
-	if len(args) == 0 {
-		args = []string{"."}
-	}
-
-	for _, path := range args {
-		err := filepath.Walk(path, walker)
-
-		if err != nil {
-			fmt.Fprintln(os.Stderr, err)
-			os.Exit(1)
-		}
-	}
-}
-
-func walker(path string, info os.FileInfo, err error) error {
-	if err != nil {
-		return err
-	}
-
-	if dirs && info.IsDir() {
-		fmt.Printf("%s  %s 0%03o %d\n", "-", path, info.Mode(), info.ModTime().Unix())
-	} else if !info.IsDir() {
-		sum, err := md5file(path)
-		if err != nil {
-			return err
-		}
-		if long {
-			fmt.Printf("%s  %s 0%03o %d\n", sum, path, info.Mode(), info.ModTime().Unix())
-		} else {
-			fmt.Printf("%s  %s\n", sum, path)
-		}
-	}
-
-	return nil
-}
-
-func md5file(fname string) (hash string, err error) {
-	f, err := os.Open(fname)
-	if err != nil {
-		return
-	}
-	defer f.Close()
-
-	h := md5.New()
-	io.Copy(h, f)
-	hb := h.Sum(nil)
-	hash = fmt.Sprintf("%x", hb)
-
-	return
-}

+ 1 - 1
test/parallell_scan_test.go

@@ -15,7 +15,7 @@
 
 // +build integration
 
-package integration_test
+package integration
 
 import (
 	"io/ioutil"

+ 2 - 4
test/reconnect_test.go

@@ -15,11 +15,10 @@
 
 // +build integration
 
-package integration_test
+package integration
 
 import (
 	"log"
-	"strings"
 	"sync"
 	"testing"
 	"time"
@@ -81,8 +80,7 @@ func testRestartDuringTransfer(t *testing.T, restartSender, restartReceiver bool
 	for {
 		comp, err := sender.peerCompletion()
 		if err != nil {
-			if strings.Contains(err.Error(), "use of closed network connection") ||
-				strings.Contains(err.Error(), "request cancelled while waiting") {
+			if isTimeout(err) {
 				time.Sleep(250 * time.Millisecond)
 				continue
 			}

+ 5 - 6
test/symlink_test.go

@@ -15,14 +15,13 @@
 
 // +build integration
 
-package integration_test
+package integration
 
 import (
 	"io/ioutil"
 	"log"
 	"os"
 	"path/filepath"
-	"strings"
 	"testing"
 	"time"
 
@@ -62,7 +61,7 @@ func TestSymlinksSimpleVersioning(t *testing.T) {
 		t.Skip("symlinks unsupported")
 	}
 
-	// Use no versioning
+	// Use simple versioning
 	id, _ := protocol.DeviceIDFromString(id2)
 	cfg, _ := config.Load("h2/config.xml", id)
 	fld := cfg.Folders()["default"]
@@ -81,7 +80,7 @@ func TestSymlinksStaggeredVersioning(t *testing.T) {
 		t.Skip("symlinks unsupported")
 	}
 
-	// Use no versioning
+	// Use staggered versioning
 	id, _ := protocol.DeviceIDFromString(id2)
 	cfg, _ := config.Load("h2/config.xml", id)
 	fld := cfg.Folders()["default"]
@@ -203,7 +202,7 @@ func testSymlinks(t *testing.T) {
 	for {
 		comp, err := sender.peerCompletion()
 		if err != nil {
-			if strings.Contains(err.Error(), "use of closed network connection") {
+			if isTimeout(err) {
 				time.Sleep(time.Second)
 				continue
 			}
@@ -328,7 +327,7 @@ func testSymlinks(t *testing.T) {
 	for {
 		comp, err := sender.peerCompletion()
 		if err != nil {
-			if strings.Contains(err.Error(), "use of closed network connection") {
+			if isTimeout(err) {
 				time.Sleep(time.Second)
 				continue
 			}

+ 284 - 0
test/sync_test.go

@@ -0,0 +1,284 @@
+// Copyright (C) 2014 The Syncthing Authors.
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>.
+
+// +build integration
+
+package integration
+
+import (
+	"fmt"
+	"log"
+	"testing"
+	"time"
+
+	"github.com/syncthing/syncthing/internal/config"
+	"github.com/syncthing/syncthing/internal/protocol"
+)
+
+func TestSyncCluster(t *testing.T) {
+	// Use no versioning
+	id, _ := protocol.DeviceIDFromString(id2)
+	cfg, _ := config.Load("h2/config.xml", id)
+	fld := cfg.Folders()["default"]
+	fld.Versioning = config.VersioningConfiguration{}
+	cfg.SetFolder(fld)
+	cfg.Save()
+
+	testSyncCluster(t)
+}
+
+func TestSyncClusterSimpleVersioning(t *testing.T) {
+	// Use simple versioning
+	id, _ := protocol.DeviceIDFromString(id2)
+	cfg, _ := config.Load("h2/config.xml", id)
+	fld := cfg.Folders()["default"]
+	fld.Versioning = config.VersioningConfiguration{
+		Type:   "simple",
+		Params: map[string]string{"keep": "5"},
+	}
+	cfg.SetFolder(fld)
+	cfg.Save()
+
+	testSyncCluster(t)
+}
+
+func TestSyncClusterStaggeredVersioning(t *testing.T) {
+	// Use staggered versioning
+	id, _ := protocol.DeviceIDFromString(id2)
+	cfg, _ := config.Load("h2/config.xml", id)
+	fld := cfg.Folders()["default"]
+	fld.Versioning = config.VersioningConfiguration{
+		Type: "staggered",
+	}
+	cfg.SetFolder(fld)
+	cfg.Save()
+
+	testSyncCluster(t)
+}
+
+func testSyncCluster(t *testing.T) {
+	/*
+
+		This tests syncing files back and forth between three cluster members.
+		Their configs are in h1, h2 and h3. The folder "default" is shared
+		between all and stored in s1, s2 and s3 respectively.
+
+		Another folder is shared between 1 and 2 only, in s12-1 and s12-2. A
+		third folders is shared between 2 and 3, in s23-2 and s23-3.
+
+	*/
+	log.Println("Cleaning...")
+	err := removeAll("s1", "s12-1",
+		"s2", "s12-2", "s23-2",
+		"s3", "s23-3",
+		"h1/index", "h2/index", "h3/index")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Create initial folder contents. All three devices have stuff in
+	// "default", which should be merged. The other two folders are initially
+	// empty on one side.
+
+	log.Println("Generating files...")
+
+	err = generateFiles("s1", 1000, 21, "../LICENSE")
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = generateFiles("s12-1", 1000, 21, "../LICENSE")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	err = generateFiles("s2", 1000, 21, "../LICENSE")
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = generateFiles("s23-2", 1000, 21, "../LICENSE")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	err = generateFiles("s3", 1000, 21, "../LICENSE")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	p, err := scStartProcesses()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Prepare the expected state of folders after the sync
+	e1 := mergeDirectoryContents(directoryContents("s1"),
+		directoryContents("s2"),
+		directoryContents("s3"))
+	e2 := directoryContents("s12-1")
+	e3 := directoryContents("s23-2")
+	expected := [][]fileInfo{e1, e2, e3}
+
+	for count := 0; count < 5; count++ {
+		log.Println("Forcing rescan...")
+
+		// Force rescan of folders
+		for i := range p {
+			p[i].post("/rest/scan?folder=default", nil)
+			if i < 3 {
+				p[i].post("/rest/scan?folder=s12", nil)
+			}
+			if i > 1 {
+				p[i].post("/rest/scan?folder=s23", nil)
+			}
+		}
+
+		// Sync stuff and verify it looks right
+		err = scSyncAndCompare(p, expected)
+		if err != nil {
+			t.Error(err)
+			break
+		}
+
+		log.Println("Altering...")
+
+		// Alter the source files for another round
+		err = alterFiles("s1")
+		if err != nil {
+			t.Error(err)
+			break
+		}
+		err = alterFiles("s12-1")
+		if err != nil {
+			t.Error(err)
+			break
+		}
+		err = alterFiles("s23-2")
+		if err != nil {
+			t.Error(err)
+			break
+		}
+
+		// Prepare the expected state of folders after the sync
+		e1 = directoryContents("s1")
+		e2 = directoryContents("s12-1")
+		e3 = directoryContents("s23-2")
+		expected = [][]fileInfo{e1, e2, e3}
+	}
+
+	for i := range p {
+		p[i].stop()
+	}
+}
+
+func scStartProcesses() ([]syncthingProcess, error) {
+	p := make([]syncthingProcess, 3)
+
+	p[0] = syncthingProcess{ // id1
+		log:    "1.out",
+		argv:   []string{"-home", "h1"},
+		port:   8081,
+		apiKey: apiKey,
+	}
+	err := p[0].start()
+	if err != nil {
+		return nil, err
+	}
+
+	p[1] = syncthingProcess{ // id2
+		log:    "2.out",
+		argv:   []string{"-home", "h2"},
+		port:   8082,
+		apiKey: apiKey,
+	}
+	err = p[1].start()
+	if err != nil {
+		_ = p[0].stop()
+		return nil, err
+	}
+
+	p[2] = syncthingProcess{ // id3
+		log:    "3.out",
+		argv:   []string{"-home", "h3"},
+		port:   8083,
+		apiKey: apiKey,
+	}
+	err = p[2].start()
+	if err != nil {
+		_ = p[0].stop()
+		_ = p[1].stop()
+		return nil, err
+	}
+
+	return p, nil
+}
+
+func scSyncAndCompare(p []syncthingProcess, expected [][]fileInfo) error {
+	ids := []string{id1, id2, id3}
+
+	log.Println("Syncing...")
+
+mainLoop:
+	for {
+		time.Sleep(2500 * time.Millisecond)
+
+		for i := range p {
+			comp, err := p[i].peerCompletion()
+			if err != nil {
+				if isTimeout(err) {
+					continue mainLoop
+				}
+				return err
+			}
+
+			for id, pct := range comp {
+				if id == ids[i] {
+					// Don't check for self, which will be 0%
+					continue
+				}
+				if pct != 100 {
+					log.Printf("%s not done yet: %d%%", id, pct)
+					continue mainLoop
+				}
+			}
+		}
+
+		break
+	}
+
+	log.Println("Checking...")
+
+	for _, dir := range []string{"s1", "s2", "s3"} {
+		actual := directoryContents(dir)
+		if err := compareDirectoryContents(actual, expected[0]); err != nil {
+			return fmt.Errorf("%s: %v", dir, err)
+		}
+	}
+
+	for _, dir := range []string{"s12-1", "s12-2"} {
+		actual := directoryContents(dir)
+		if err := compareDirectoryContents(actual, expected[1]); err != nil {
+			return fmt.Errorf("%s: %v", dir, err)
+		}
+	}
+
+	for _, dir := range []string{"s23-2", "s23-3"} {
+		actual := directoryContents(dir)
+		if err := compareDirectoryContents(actual, expected[2]); err != nil {
+			return fmt.Errorf("%s: %v", dir, err)
+		}
+	}
+
+	return nil
+}

+ 216 - 0
test/syncthingprocess.go

@@ -0,0 +1,216 @@
+// Copyright (C) 2014 The Syncthing Authors.
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>.
+
+// +build integration
+
+package integration
+
+import (
+	"bufio"
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	"os/exec"
+	"time"
+)
+
+var env = []string{
+	"HOME=.",
+	"STGUIAPIKEY=" + apiKey,
+	"STNORESTART=1",
+}
+
+type syncthingProcess struct {
+	log       string
+	argv      []string
+	port      int
+	apiKey    string
+	csrfToken string
+	lastEvent int
+
+	cmd   *exec.Cmd
+	logfd *os.File
+}
+
+func (p *syncthingProcess) start() error {
+	if p.logfd == nil {
+		logfd, err := os.Create(p.log)
+		if err != nil {
+			return err
+		}
+		p.logfd = logfd
+	}
+
+	cmd := exec.Command("../bin/syncthing", p.argv...)
+	cmd.Stdout = p.logfd
+	cmd.Stderr = p.logfd
+	cmd.Env = append(os.Environ(), env...)
+
+	err := cmd.Start()
+	if err != nil {
+		return err
+	}
+	p.cmd = cmd
+
+	for {
+		resp, err := p.get("/")
+		if err == nil {
+			resp.Body.Close()
+			return nil
+		}
+		time.Sleep(250 * time.Millisecond)
+	}
+}
+
+func (p *syncthingProcess) stop() error {
+	p.cmd.Process.Signal(os.Kill)
+	p.cmd.Wait()
+
+	fd, err := os.Open(p.log)
+	if err != nil {
+		return err
+	}
+	defer fd.Close()
+
+	raceConditionStart := []byte("WARNING: DATA RACE")
+	raceConditionSep := []byte("==================")
+	sc := bufio.NewScanner(fd)
+	race := false
+	for sc.Scan() {
+		line := sc.Bytes()
+		if race {
+			fmt.Printf("%s\n", line)
+			if bytes.Contains(line, raceConditionSep) {
+				race = false
+			}
+		} else if bytes.Contains(line, raceConditionStart) {
+			fmt.Printf("%s\n", raceConditionSep)
+			fmt.Printf("%s\n", raceConditionStart)
+			race = true
+			if err == nil {
+				err = errors.New("Race condition detected")
+			}
+		}
+	}
+	return err
+}
+
+func (p *syncthingProcess) get(path string) (*http.Response, error) {
+	client := &http.Client{
+		Timeout: 30 * time.Second,
+		Transport: &http.Transport{
+			DisableKeepAlives: true,
+		},
+	}
+	req, err := http.NewRequest("GET", fmt.Sprintf("http://127.0.0.1:%d%s", p.port, path), nil)
+	if err != nil {
+		return nil, err
+	}
+	if p.apiKey != "" {
+		req.Header.Add("X-API-Key", p.apiKey)
+	}
+	if p.csrfToken != "" {
+		req.Header.Add("X-CSRF-Token", p.csrfToken)
+	}
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	return resp, nil
+}
+
+func (p *syncthingProcess) post(path string, data io.Reader) (*http.Response, error) {
+	client := &http.Client{
+		Timeout: 600 * time.Second,
+		Transport: &http.Transport{
+			DisableKeepAlives: true,
+		},
+	}
+	req, err := http.NewRequest("POST", fmt.Sprintf("http://127.0.0.1:%d%s", p.port, path), data)
+	if err != nil {
+		return nil, err
+	}
+	if p.apiKey != "" {
+		req.Header.Add("X-API-Key", p.apiKey)
+	}
+	if p.csrfToken != "" {
+		req.Header.Add("X-CSRF-Token", p.csrfToken)
+	}
+	req.Header.Add("Content-Type", "application/json")
+
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	return resp, nil
+}
+
+func (p *syncthingProcess) peerCompletion() (map[string]int, error) {
+	resp, err := p.get("/rest/debug/peerCompletion")
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+
+	comp := map[string]int{}
+	err = json.NewDecoder(resp.Body).Decode(&comp)
+	return comp, err
+}
+
+type event struct {
+	ID   int
+	Time time.Time
+	Type string
+	Data interface{}
+}
+
+func (p *syncthingProcess) events() ([]event, error) {
+	resp, err := p.get(fmt.Sprintf("/rest/events?since=%d", p.lastEvent))
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+
+	var evs []event
+	err = json.NewDecoder(resp.Body).Decode(&evs)
+	if err != nil {
+		return nil, err
+	}
+	p.lastEvent = evs[len(evs)-1].ID
+	return evs, err
+}
+
+type versionResp struct {
+	Version string
+}
+
+func (p *syncthingProcess) version() (string, error) {
+	resp, err := p.get("/rest/version")
+	if err != nil {
+		return "", err
+	}
+	defer resp.Body.Close()
+
+	var v versionResp
+	err = json.NewDecoder(resp.Body).Decode(&v)
+	if err != nil {
+		return "", err
+	}
+	return v.Version, nil
+}

+ 0 - 177
test/test-delupd.sh

@@ -1,177 +0,0 @@
-#!/bin/bash
-set -euo pipefail
-IFS=$'\n\t'
-
-# Copyright (C) 2014 The Syncthing Authors.
-#
-# This program is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by the Free
-# Software Foundation, either version 3 of the License, or (at your option)
-# any later version.
-#
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
-# more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program. If not, see <http://www.gnu.org/licenses/>.
-
-iterations=${1:-5}
-
-id1=I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU
-id2=JMFJCXB-GZDE4BN-OCJE3VF-65GYZNU-AIVJRET-3J6HMRQ-AUQIGJO-FKNHMQU
-id3=373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU
-
-go build genfiles.go
-go build md5r.go
-go build json.go
-
-start() {
-	echo "Starting..."
-	for i in 1 2 3 ; do
-		STTRACE=model,scanner STPROFILER=":909$i" ../bin/syncthing -home "h$i" > "$i.out" 2>&1 &
-	done
-}
-
-stop() {
-	for i in 1 2 3 ; do
-		curl -s -o /dev/null -HX-API-Key:abc123 -X POST "http://127.0.0.1:808$i/rest/shutdown"
-	done
-	exit $1
-}
-
-testConvergence() {
-	while true ; do
-		sleep 5
-		s1comp=$(curl -HX-API-Key:abc123 -s "http://127.0.0.1:8082/rest/debug/peerCompletion" | ./json "$id1")
-		s2comp=$(curl -HX-API-Key:abc123 -s "http://127.0.0.1:8083/rest/debug/peerCompletion" | ./json "$id2")
-		s3comp=$(curl -HX-API-Key:abc123 -s "http://127.0.0.1:8081/rest/debug/peerCompletion" | ./json "$id3")
-		s1comp=${s1comp:-0}
-		s2comp=${s2comp:-0}
-		s3comp=${s3comp:-0}
-		tot=$(($s1comp + $s2comp + $s3comp))
-		echo $tot / 300
-		if [[ $tot == 300 ]] ; then
-			break
-		fi
-	done
-
-	echo "Verifying..."
-	cp md5-1 md5-tot
-	cp md5-12-2 md5-12-tot
-	cp md5-23-3 md5-23-tot
-
-	for i in 1 2 3 12-1 12-2 23-2 23-3; do
-		pushd "s$i" >/dev/null
-		../md5r -l | sort | grep -v .stversions | grep -v .stfolder > ../md5-$i
-		popd >/dev/null
-	done
-
-	ok=0
-	for i in 1 2 3 ; do
-		if ! cmp "md5-$i" md5-tot >/dev/null ; then
-			echo "Fail: instance $i unconverged for default"
-		else
-			ok=$(($ok + 1))
-			echo "OK: instance $i converged for default"
-		fi
-	done
-	for i in 12-1 12-2 ; do
-		if ! cmp "md5-$i" md5-12-tot >/dev/null ; then
-			echo "Fail: instance $i unconverged for s12"
-		else
-			ok=$(($ok + 1))
-			echo "OK: instance $i converged for s12"
-		fi
-	done
-	for i in 23-2 23-3 ; do
-		if ! cmp "md5-$i" md5-23-tot >/dev/null ; then
-			echo "Fail: instance $i unconverged for s23"
-		else
-			ok=$(($ok + 1))
-			echo "OK: instance $i converged for s23"
-		fi
-	done
-	if [[ $ok != 7 ]] ; then
-		stop 1
-	fi
-}
-
-alterFiles() {
-	pkill -STOP syncthing
-
-	for i in 1 12-2 23-3 ; do
-		# Delete some files
-		pushd "s$i" >/dev/null
-		chmod 755 ro-test
-		nfiles=$(find . -type f | wc -l)
-		if [[ $nfiles -ge 300 ]] ; then
-			todelete=$(( $nfiles - 300 ))
-			echo "  $i: deleting $todelete files..."
-			set +o pipefail
-			find . -type f \
-				| grep -v timechanged \
-				| grep -v .stfolder \
-				| sort -k 1.16 \
-				| head -n "$todelete" \
-				| xargs rm -f
-			set -o pipefail
-		fi
-
-		# Create some new files and alter existing ones
-		echo "  $i: random nonoverlapping"
-		../genfiles -maxexp 22 -files 200 -src ../genfiles
-		echo "  $i: new files in ro directory"
-		uuidgen > ro-test/$(uuidgen)
-		chmod 500 ro-test
-		touch "timechanged-$i"
-
-		../md5r -l | sort | grep -v .stversions | grep -v .stfolder > ../md5-$i
-		popd >/dev/null
-	done
-
-	pkill -CONT syncthing
-
-	echo "Restarting instance 2"
-	curl -s -o /dev/null -HX-API-Key:abc123 -X POST "http://127.0.0.1:8082/rest/restart"
-}
-
-rm -rf h?/*.idx.gz h?/index
-chmod -R u+w s? s??-? || true
-rm -rf s? s??-?
-mkdir s1 s2 s3 s12-1 s12-2 s23-2 s23-3
-
-echo "Setting up files..."
-for i in 1 12-2 23-3; do
-	pushd "s$i" >/dev/null
-	echo "  $i: random nonoverlapping"
-	../genfiles -maxexp 22 -files 400 -src ../genfiles
-	echo "  $i: ro directory"
-	mkdir ro-test
-	uuidgen > ro-test/$(uuidgen)
-	chmod 500 ro-test
-	dd if=/dev/urandom of="timechanged-$i" bs=1024k count=1
-	popd >/dev/null
-done
-
-echo "MD5-summing..."
-for i in 1 12-2 23-3 ; do
-	pushd "s$i" >/dev/null
-	../md5r -l | grep -v .stfolder | sort > ../md5-$i
-	popd >/dev/null
-done
-
-start
-testConvergence
-
-for ((t = 1; t <= $iterations; t++)) ; do
-	echo "Add and remove random files ($t / $iterations)..."
-	alterFiles
-
-	echo "Waiting..."
-	sleep 30
-	testConvergence
-done
-
-stop 0

+ 0 - 106
test/test-folders.sh

@@ -1,106 +0,0 @@
-#!/bin/bash
-set -euo pipefail
-IFS=$'\n\t'
-
-# Copyright (C) 2014 The Syncthing Authors.
-#
-# This program is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by the Free
-# Software Foundation, either version 3 of the License, or (at your option)
-# any later version.
-#
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
-# more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program. If not, see <http://www.gnu.org/licenses/>.
-
-iterations=${1:-5}
-
-id1=I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU
-id2=JMFJCXB-GZDE4BN-OCJE3VF-65GYZNU-AIVJRET-3J6HMRQ-AUQIGJO-FKNHMQU
-
-go build json.go
-
-start() {
-	echo "Starting..."
-	STTRACE=model,scanner STPROFILER=":9091" ../bin/syncthing -home "f1" > 1.out 2>&1 &
-	STTRACE=model,scanner STPROFILER=":9092" ../bin/syncthing -home "f2" > 2.out 2>&1 &
-	sleep 1
-}
-
-stop() {
-	echo "Stopping..."
-	for i in 1 2 ; do
-		curl -s -o /dev/null -HX-API-Key:abc123 -X POST "http://127.0.0.1:808$i/rest/shutdown"
-	done
-}
-
-setup() {
-	echo "Setting up dirs..."
-	mkdir -p s1
-	pushd s1 >/dev/null
-	rm -r */*[02468] 2>/dev/null || true
-	rm -rf *2
-	for ((i = 0; i < 500; i++)) ; do
-		mkdir -p "$RANDOM/$RANDOM"
-	done
-	for ((i = 0; i < 500; i++)) ; do
-		d="$RANDOM/$RANDOM"
-		mkdir -p "$d"
-		touch "$d/foo"
-	done
-	../md5r -d | grep -v ' . ' > ../dirs-1
-	popd >/dev/null
-}
-
-testConvergence() {
-	while true ; do
-		sleep 5
-		s1comp=$(curl -HX-API-Key:abc123 -s "http://127.0.0.1:8082/rest/debug/peerCompletion" | ./json "$id1")
-		s2comp=$(curl -HX-API-Key:abc123 -s "http://127.0.0.1:8081/rest/debug/peerCompletion" | ./json "$id2")
-		s1comp=${s1comp:-0}
-		s2comp=${s2comp:-0}
-		tot=$(($s1comp + $s2comp))
-		echo $tot / 200
-		if [[ $tot == 200 ]] ; then
-			# when fixing up directories, a device will announce completion
-			# slightly before it's actually complete. this is arguably a bug,
-			# but we let it slide for the moment as long as it gets there
-			# eventually.
-			sleep 5
-			break
-		fi
-	done
-
-	echo "Verifying..."
-
-	pushd s2 >/dev/null
-	../md5r -d | grep -v ' . ' | grep -v .stversions > ../dirs-2
-	popd >/dev/null
-
-	if ! cmp dirs-1 dirs-2 ; then
-		echo Folders differ
-		stop
-		exit 1
-	fi
-}
-
-chmod -R +w s? s??-? || true
-rm -rf s? s??-?
-rm -rf f?/*.idx.gz f?/index
-
-setup
-start
-
-for ((j = 0; j < iterations; j++)) ; do
-	echo "#$j..."
-	testConvergence
-	setup
-	echo "Waiting..."
-	sleep 30
-done
-
-stop

+ 0 - 173
test/test-merge.sh

@@ -1,173 +0,0 @@
-#!/bin/bash
-set -euo pipefail
-IFS=$'\n\t'
-
-# Copyright (C) 2014 The Syncthing Authors.
-#
-# This program is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by the Free
-# Software Foundation, either version 3 of the License, or (at your option)
-# any later version.
-#
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
-# more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program. If not, see <http://www.gnu.org/licenses/>.
-
-iterations=${1:-5}
-
-id1=I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU
-id2=JMFJCXB-GZDE4BN-OCJE3VF-65GYZNU-AIVJRET-3J6HMRQ-AUQIGJO-FKNHMQU
-id3=373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU
-
-go build genfiles.go
-go build md5r.go
-go build json.go
-
-start() {
-	echo "Starting..."
-	for i in 1 2 3 4 ; do
-		STTRACE=files,model,puller,versioner STPROFILER=":909$i" ../bin/syncthing -home "h$i" > "$i.out" 2>&1 &
-	done
-}
-
-stop() {
-	for i in 1 2 3 4 ; do
-		curl -s -o /dev/null -HX-API-Key:abc123 -X POST "http://127.0.0.1:808$i/rest/shutdown"
-	done
-	exit $1
-}
-
-clean() {
-	if [[ $(uname -s) == "Linux" ]] ; then
-		grep -v .stversions | grep -v .stfolder | grep -v utf8-nfd
-	else
-		grep -v .stversions | grep -v .stfolder
-	fi
-}
-
-
-testConvergence() {
-	while true ; do
-		sleep 5
-		s1comp=$(curl -HX-API-Key:abc123 -s "http://127.0.0.1:8082/rest/debug/peerCompletion" | ./json "$id1")
-		s2comp=$(curl -HX-API-Key:abc123 -s "http://127.0.0.1:8083/rest/debug/peerCompletion" | ./json "$id2")
-		s3comp=$(curl -HX-API-Key:abc123 -s "http://127.0.0.1:8081/rest/debug/peerCompletion" | ./json "$id3")
-		s1comp=${s1comp:-0}
-		s2comp=${s2comp:-0}
-		s3comp=${s3comp:-0}
-		tot=$(($s1comp + $s2comp + $s3comp))
-		echo $tot / 300
-		if [[ $tot == 300 ]] ; then
-			break
-		fi
-	done
-
-	echo "Verifying..."
-	cat md5-? | sort | clean | uniq > md5-tot
-	cat md5-12-? | sort | clean | uniq > md5-12-tot
-	cat md5-23-? | sort | clean | uniq > md5-23-tot
-
-	for i in 1 2 3 12-1 12-2 23-2 23-3; do
-		pushd "s$i" >/dev/null
-		../md5r -l | sort | clean > ../md5-$i
-		popd >/dev/null
-	done
-
-	ok=0
-	for i in 1 2 3 ; do
-		if ! cmp "md5-$i" md5-tot >/dev/null ; then
-			echo "Fail: instance $i unconverged for default"
-		else
-			ok=$(($ok + 1))
-			echo "OK: instance $i converged for default"
-		fi
-	done
-	for i in 12-1 12-2 ; do
-		if ! cmp "md5-$i" md5-12-tot >/dev/null ; then
-			echo "Fail: instance $i unconverged for s12"
-		else
-			ok=$(($ok + 1))
-			echo "OK: instance $i converged for s12"
-		fi
-	done
-	for i in 23-2 23-3 ; do
-		if ! cmp "md5-$i" md5-23-tot >/dev/null ; then
-			echo "Fail: instance $i unconverged for s23"
-		else
-			ok=$(($ok + 1))
-			echo "OK: instance $i converged for s23"
-		fi
-	done
-	if [[ $ok != 7 ]] ; then
-		stop 1
-	fi
-}
-
-alterFiles() {
-	pkill -STOP syncthing
-
-	# Create some new files and alter existing ones
-	for i in 1 2 3 12-1 12-2 23-2 23-3 ; do
-		pushd "s$i" >/dev/null
-
-		echo "  $i: random nonoverlapping"
-		../genfiles -maxexp 22 -files 200 -src ../genfiles
-		echo "  $i: append to large file"
-		dd if=large-$i bs=1024k count=4 >> large-$i 2>/dev/null
-		../md5r -l > ../md5-tmp
-		(grep -v large ../md5-tmp ; grep "large-$i" ../md5-tmp) | grep -v '/.syncthing.' > ../md5-$i
-		popd >/dev/null
-	done
-
-	pkill -CONT syncthing
-}
-
-rm -rf h?/*.idx.gz h?/index
-chmod -R +w s? s??-? s4d || true
-rm -rf s? s??-? s4d
-
-echo "Setting up files..."
-for i in 1 2 3 12-1 12-2 23-2 23-3; do
-	mkdir "s$i"
-	pushd "s$i" >/dev/null
-	echo "  $i: random nonoverlapping"
-	../genfiles -maxexp 22 -files 200 -src ../genfiles
-	echo "  $i: empty file"
-	touch "empty-$i"
-	echo "  $i: large file"
-	dd if=/dev/urandom of=large-$i bs=1024k count=15 2>/dev/null
-	echo "  $i: weird encodings"
-	echo somedata > "$(echo -e utf8-nfc-\\xc3\\xad)-$i"
-	echo somedata > "$(echo -e utf8-nfd-i\\xcc\\x81)-$i"
-	echo somedata > "$(echo -e cp850-\\xa1)-$i"
-	touch "empty-$i"
-	popd >/dev/null
-done
-
-mkdir s4d
-echo somerandomdata > s4d/extrafile
-
-echo "MD5-summing..."
-for i in 1 2 3 12-1 12-2 23-2 23-3 ; do
-	pushd "s$i" >/dev/null
-	../md5r -l > ../md5-$i
-	popd >/dev/null
-done
-
-start
-testConvergence
-
-for ((t = 1; t <= $iterations; t++)) ; do
-	echo "Add and alter random files ($t / $iterations)..."
-	alterFiles
-
-	echo "Waiting..."
-	sleep 30
-	testConvergence
-done
-
-stop 0

+ 23 - 15
test/transfer-bench_test.go

@@ -13,23 +13,17 @@
 // You should have received a copy of the GNU General Public License along
 // with this program. If not, see <http://www.gnu.org/licenses/>.
 
-// +build integration
+// +build integration,benchmark
 
-package integration_test
+package integration
 
 import (
 	"log"
-	"strings"
 	"testing"
 	"time"
 )
 
 func TestBenchmarkTransfer(t *testing.T) {
-	nfiles := 10000
-	if testing.Short() {
-		nfiles = 1000
-	}
-
 	log.Println("Cleaning...")
 	err := removeAll("s1", "s2", "h1/index", "h2/index")
 	if err != nil {
@@ -37,12 +31,13 @@ func TestBenchmarkTransfer(t *testing.T) {
 	}
 
 	log.Println("Generating files...")
-	err = generateFiles("s1", nfiles, 22, "../LICENSE")
+	err = generateFiles("s1", 10000, 22, "../LICENSE")
 	if err != nil {
 		t.Fatal(err)
 	}
+	expected := directoryContents("s1")
 
-	log.Println("Starting up...")
+	log.Println("Starting sender...")
 	sender := syncthingProcess{ // id1
 		log:    "1.out",
 		argv:   []string{"-home", "h1"},
@@ -54,6 +49,10 @@ func TestBenchmarkTransfer(t *testing.T) {
 		t.Fatal(err)
 	}
 
+	// Make sure the sender has the full index before they connect
+	sender.post("/rest/scan?folder=default", nil)
+
+	log.Println("Starting receiver...")
 	receiver := syncthingProcess{ // id2
 		log:    "2.out",
 		argv:   []string{"-home", "h2"},
@@ -66,13 +65,12 @@ func TestBenchmarkTransfer(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	var t0 time.Time
+	var t0, t1 time.Time
 loop:
 	for {
 		evs, err := receiver.events()
 		if err != nil {
-			if strings.Contains(err.Error(), "use of closed network connection") {
-				log.Println("...")
+			if isTimeout(err) {
 				continue
 			}
 			sender.stop()
@@ -91,8 +89,8 @@ loop:
 					t0 = ev.Time
 					continue
 				}
-				if t0 != (time.Time{}) && data["to"].(string) == "idle" {
-					log.Println("Sync took", ev.Time.Sub(t0))
+				if !t0.IsZero() && data["to"].(string) == "idle" {
+					t1 = ev.Time
 					break loop
 				}
 			}
@@ -103,4 +101,14 @@ loop:
 
 	sender.stop()
 	receiver.stop()
+
+	log.Println("Verifying...")
+
+	actual := directoryContents("s2")
+	err = compareDirectoryContents(actual, expected)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	log.Println("Sync took", t1.Sub(t0))
 }

+ 147 - 191
test/common_test.go → test/util.go

@@ -15,22 +15,20 @@
 
 // +build integration
 
-package integration_test
+package integration
 
 import (
-	"bufio"
-	"bytes"
 	"crypto/md5"
-	"encoding/json"
+	cr "crypto/rand"
 	"errors"
 	"fmt"
 	"io"
 	"log"
 	"math/rand"
-	"net/http"
 	"os"
-	"os/exec"
 	"path/filepath"
+	"sort"
+	"strings"
 	"time"
 
 	"github.com/syncthing/syncthing/internal/symlinks"
@@ -43,194 +41,10 @@ func init() {
 const (
 	id1    = "I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU"
 	id2    = "JMFJCXB-GZDE4BN-OCJE3VF-65GYZNU-AIVJRET-3J6HMRQ-AUQIGJO-FKNHMQU"
+	id3    = "373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU"
 	apiKey = "abc123"
 )
 
-var env = []string{
-	"HOME=.",
-	"STGUIAPIKEY=" + apiKey,
-	"STNORESTART=1",
-}
-
-type syncthingProcess struct {
-	log       string
-	argv      []string
-	port      int
-	apiKey    string
-	csrfToken string
-	lastEvent int
-
-	cmd   *exec.Cmd
-	logfd *os.File
-}
-
-func (p *syncthingProcess) start() error {
-	if p.logfd == nil {
-		logfd, err := os.Create(p.log)
-		if err != nil {
-			return err
-		}
-		p.logfd = logfd
-	}
-
-	cmd := exec.Command("../bin/syncthing", p.argv...)
-	cmd.Stdout = p.logfd
-	cmd.Stderr = p.logfd
-	cmd.Env = append(os.Environ(), env...)
-
-	err := cmd.Start()
-	if err != nil {
-		return err
-	}
-	p.cmd = cmd
-
-	for {
-		resp, err := p.get("/")
-		if err == nil {
-			resp.Body.Close()
-			return nil
-		}
-		time.Sleep(250 * time.Millisecond)
-	}
-}
-
-func (p *syncthingProcess) stop() error {
-	p.cmd.Process.Signal(os.Kill)
-	p.cmd.Wait()
-
-	fd, err := os.Open(p.log)
-	if err != nil {
-		return err
-	}
-	defer fd.Close()
-
-	raceConditionStart := []byte("WARNING: DATA RACE")
-	raceConditionSep := []byte("==================")
-	sc := bufio.NewScanner(fd)
-	race := false
-	for sc.Scan() {
-		line := sc.Bytes()
-		if race {
-			fmt.Printf("%s\n", line)
-			if bytes.Contains(line, raceConditionSep) {
-				race = false
-			}
-		} else if bytes.Contains(line, raceConditionStart) {
-			fmt.Printf("%s\n", raceConditionSep)
-			fmt.Printf("%s\n", raceConditionStart)
-			race = true
-			if err == nil {
-				err = errors.New("Race condition detected")
-			}
-		}
-	}
-	return err
-}
-
-func (p *syncthingProcess) get(path string) (*http.Response, error) {
-	client := &http.Client{
-		Timeout: 2 * time.Second,
-		Transport: &http.Transport{
-			DisableKeepAlives: true,
-		},
-	}
-	req, err := http.NewRequest("GET", fmt.Sprintf("http://127.0.0.1:%d%s", p.port, path), nil)
-	if err != nil {
-		return nil, err
-	}
-	if p.apiKey != "" {
-		req.Header.Add("X-API-Key", p.apiKey)
-	}
-	if p.csrfToken != "" {
-		req.Header.Add("X-CSRF-Token", p.csrfToken)
-	}
-	resp, err := client.Do(req)
-	if err != nil {
-		return nil, err
-	}
-	return resp, nil
-}
-
-func (p *syncthingProcess) post(path string, data io.Reader) (*http.Response, error) {
-	client := &http.Client{
-		Timeout: 600 * time.Second,
-		Transport: &http.Transport{
-			DisableKeepAlives: true,
-		},
-	}
-	req, err := http.NewRequest("POST", fmt.Sprintf("http://127.0.0.1:%d%s", p.port, path), data)
-	if err != nil {
-		return nil, err
-	}
-	if p.apiKey != "" {
-		req.Header.Add("X-API-Key", p.apiKey)
-	}
-	if p.csrfToken != "" {
-		req.Header.Add("X-CSRF-Token", p.csrfToken)
-	}
-	req.Header.Add("Content-Type", "application/json")
-
-	resp, err := client.Do(req)
-	if err != nil {
-		return nil, err
-	}
-	return resp, nil
-}
-
-func (p *syncthingProcess) peerCompletion() (map[string]int, error) {
-	resp, err := p.get("/rest/debug/peerCompletion")
-	if err != nil {
-		return nil, err
-	}
-	defer resp.Body.Close()
-
-	comp := map[string]int{}
-	err = json.NewDecoder(resp.Body).Decode(&comp)
-	return comp, err
-}
-
-type event struct {
-	ID   int
-	Time time.Time
-	Type string
-	Data interface{}
-}
-
-func (p *syncthingProcess) events() ([]event, error) {
-	resp, err := p.get(fmt.Sprintf("/rest/events?since=%d", p.lastEvent))
-	if err != nil {
-		return nil, err
-	}
-	defer resp.Body.Close()
-
-	var evs []event
-	err = json.NewDecoder(resp.Body).Decode(&evs)
-	if err != nil {
-		return nil, err
-	}
-	p.lastEvent = evs[len(evs)-1].ID
-	return evs, err
-}
-
-type versionResp struct {
-	Version string
-}
-
-func (p *syncthingProcess) version() (string, error) {
-	resp, err := p.get("/rest/version")
-	if err != nil {
-		return "", err
-	}
-	defer resp.Body.Close()
-
-	var v versionResp
-	err = json.NewDecoder(resp.Body).Decode(&v)
-	if err != nil {
-		return "", err
-	}
-	return v.Version, nil
-}
-
 func generateFiles(dir string, files, maxexp int, srcname string) error {
 	fd, err := os.Open(srcname)
 	if err != nil {
@@ -239,6 +53,12 @@ func generateFiles(dir string, files, maxexp int, srcname string) error {
 
 	for i := 0; i < files; i++ {
 		n := randomName()
+
+		if rand.Float64() < 0.05 {
+			// Some files and directories are dotfiles
+			n = "." + n
+		}
+
 		p0 := filepath.Join(dir, string(n[0]), n[0:2])
 		err = os.MkdirAll(p0, 0755)
 		if err != nil {
@@ -285,6 +105,73 @@ func generateFiles(dir string, files, maxexp int, srcname string) error {
 	return nil
 }
 
+func alterFiles(dir string) error {
+	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+		if os.IsNotExist(err) {
+			// Something we deleted. Never mind.
+			return nil
+		}
+
+		if err != nil {
+			return err
+		}
+
+		switch filepath.Base(path) {
+		case ".stfolder":
+			return nil
+		case ".stversions":
+			return nil
+		}
+
+		r := rand.Float64()
+		comps := len(strings.Split(path, string(os.PathSeparator)))
+		switch {
+		case r < 0.1 && comps > 2:
+			// Delete every tenth file or directory, except top levels
+			err := removeAll(path)
+			if err != nil {
+				return err
+			}
+
+		case r < 0.2 && info.Mode().IsRegular():
+			if info.Mode()&0200 != 0200 {
+				// Not owner writable. Fix.
+				err = os.Chmod(path, 0644)
+				if err != nil {
+					return err
+				}
+			}
+
+			// Overwrite a random kilobyte of every tenth file
+			fd, err := os.OpenFile(path, os.O_RDWR, 0644)
+			if err != nil {
+				return err
+			}
+			if info.Size() > 1024 {
+				_, err = fd.Seek(rand.Int63n(info.Size()), os.SEEK_SET)
+				if err != nil {
+					return err
+				}
+			}
+			_, err = io.Copy(fd, io.LimitReader(cr.Reader, 1024))
+			if err != nil {
+				return err
+			}
+			err = fd.Close()
+			if err != nil {
+				return err
+			}
+		}
+		return nil
+	})
+	if err != nil {
+		return err
+	}
+
+	// Create 100 new files
+	return generateFiles(dir, 100, 20, "../LICENSE")
+}
+
 func ReadRand(bs []byte) (int, error) {
 	var r uint32
 	for i := range bs {
@@ -370,6 +257,53 @@ func compareDirectories(dirs ...string) error {
 	}
 }
 
+func directoryContents(dir string) []fileInfo {
+	res := make(chan fileInfo)
+	startWalker(dir, res, nil)
+
+	var files []fileInfo
+	for f := range res {
+		files = append(files, f)
+	}
+
+	return files
+}
+
+func mergeDirectoryContents(c ...[]fileInfo) []fileInfo {
+	m := make(map[string]fileInfo)
+
+	for _, l := range c {
+		for _, f := range l {
+			if cur, ok := m[f.name]; !ok || cur.mod < f.mod {
+				m[f.name] = f
+			}
+		}
+	}
+
+	res := make([]fileInfo, len(m))
+	i := 0
+	for _, f := range m {
+		res[i] = f
+		i++
+	}
+
+	sort.Sort(fileInfoList(res))
+	return res
+}
+
+func compareDirectoryContents(actual, expected []fileInfo) error {
+	if len(actual) != len(expected) {
+		return fmt.Errorf("len(actual) = %d; len(expected) = %d", len(actual), len(expected))
+	}
+
+	for i := range actual {
+		if actual[i] != expected[i] {
+			return fmt.Errorf("Mismatch; actual %#v != expected %#v", actual[i], expected[i])
+		}
+	}
+	return nil
+}
+
 type fileInfo struct {
 	name string
 	mode os.FileMode
@@ -377,6 +311,20 @@ type fileInfo struct {
 	hash [16]byte
 }
 
+type fileInfoList []fileInfo
+
+func (l fileInfoList) Len() int {
+	return len(l)
+}
+
+func (l fileInfoList) Less(a, b int) bool {
+	return l[a].name < l[b].name
+}
+
+func (l fileInfoList) Swap(a, b int) {
+	l[a], l[b] = l[b], l[a]
+}
+
 func startWalker(dir string, res chan<- fileInfo, abort <-chan struct{}) {
 	walker := func(path string, info os.FileInfo, err error) error {
 		if err != nil {
@@ -453,3 +401,11 @@ func md5file(fname string) (hash [16]byte, err error) {
 
 	return
 }
+
+func isTimeout(err error) bool {
+	if err == nil {
+		return false
+	}
+	return strings.Contains(err.Error(), "use of closed network connection") ||
+		strings.Contains(err.Error(), "request cancelled while waiting")
+}