|
|
@@ -46,44 +46,177 @@ func TestMain(m *testing.M) {
|
|
|
}
|
|
|
|
|
|
// Note: untunneled upgrade tests must execute before
|
|
|
-// the "Run" tests to ensure no tunnel is established.
|
|
|
+// the other tests to ensure no tunnel is established.
|
|
|
// We need a way to reset the datastore after it's been
|
|
|
// initialized in order to to clear out its data entries
|
|
|
// and be able to arbitrarily order the tests.
|
|
|
|
|
|
func TestUntunneledUpgradeDownload(t *testing.T) {
|
|
|
- doUntunnledUpgradeDownload(t, false)
|
|
|
+ controllerRun(t,
|
|
|
+ &controllerRunConfig{
|
|
|
+ protocol: "",
|
|
|
+ clientIsLatestVersion: false,
|
|
|
+ disableUntunneledUpgrade: false,
|
|
|
+ disableEstablishing: true,
|
|
|
+ disrupt: false,
|
|
|
+ useHostNameTransformer: false,
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
func TestUntunneledResumableUpgradeDownload(t *testing.T) {
|
|
|
- doUntunnledUpgradeDownload(t, true)
|
|
|
+ controllerRun(t,
|
|
|
+ &controllerRunConfig{
|
|
|
+ protocol: "",
|
|
|
+ clientIsLatestVersion: false,
|
|
|
+ disableUntunneledUpgrade: false,
|
|
|
+ disableEstablishing: true,
|
|
|
+ disrupt: true,
|
|
|
+ useHostNameTransformer: false,
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestUntunneledUpgradeClientIsLatestVersion(t *testing.T) {
|
|
|
+ controllerRun(t,
|
|
|
+ &controllerRunConfig{
|
|
|
+ protocol: "",
|
|
|
+ clientIsLatestVersion: true,
|
|
|
+ disableUntunneledUpgrade: false,
|
|
|
+ disableEstablishing: true,
|
|
|
+ disrupt: false,
|
|
|
+ useHostNameTransformer: false,
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestTunneledUpgradeClientIsLatestVersion(t *testing.T) {
|
|
|
+ controllerRun(t,
|
|
|
+ &controllerRunConfig{
|
|
|
+ protocol: "",
|
|
|
+ clientIsLatestVersion: true,
|
|
|
+ disableUntunneledUpgrade: true,
|
|
|
+ disableEstablishing: false,
|
|
|
+ disrupt: false,
|
|
|
+ useHostNameTransformer: false,
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
func TestControllerRunSSH(t *testing.T) {
|
|
|
- controllerRun(t, TUNNEL_PROTOCOL_SSH, false)
|
|
|
+ controllerRun(t,
|
|
|
+ &controllerRunConfig{
|
|
|
+ protocol: TUNNEL_PROTOCOL_SSH,
|
|
|
+ clientIsLatestVersion: false,
|
|
|
+ disableUntunneledUpgrade: true,
|
|
|
+ disableEstablishing: false,
|
|
|
+ disrupt: false,
|
|
|
+ useHostNameTransformer: false,
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
func TestControllerRunObfuscatedSSH(t *testing.T) {
|
|
|
- controllerRun(t, TUNNEL_PROTOCOL_OBFUSCATED_SSH, false)
|
|
|
+ controllerRun(t,
|
|
|
+ &controllerRunConfig{
|
|
|
+ protocol: TUNNEL_PROTOCOL_OBFUSCATED_SSH,
|
|
|
+ clientIsLatestVersion: false,
|
|
|
+ disableUntunneledUpgrade: true,
|
|
|
+ disableEstablishing: false,
|
|
|
+ disrupt: false,
|
|
|
+ useHostNameTransformer: false,
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
func TestControllerRunUnfrontedMeek(t *testing.T) {
|
|
|
- controllerRun(t, TUNNEL_PROTOCOL_UNFRONTED_MEEK, true)
|
|
|
+ controllerRun(t,
|
|
|
+ &controllerRunConfig{
|
|
|
+ protocol: TUNNEL_PROTOCOL_UNFRONTED_MEEK,
|
|
|
+ clientIsLatestVersion: false,
|
|
|
+ disableUntunneledUpgrade: true,
|
|
|
+ disableEstablishing: false,
|
|
|
+ disrupt: false,
|
|
|
+ useHostNameTransformer: false,
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestControllerRunUnfrontedMeekWithTransformer(t *testing.T) {
|
|
|
+ controllerRun(t,
|
|
|
+ &controllerRunConfig{
|
|
|
+ protocol: TUNNEL_PROTOCOL_UNFRONTED_MEEK,
|
|
|
+ clientIsLatestVersion: true,
|
|
|
+ disableUntunneledUpgrade: true,
|
|
|
+ disableEstablishing: false,
|
|
|
+ disrupt: false,
|
|
|
+ useHostNameTransformer: true,
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
func TestControllerRunFrontedMeek(t *testing.T) {
|
|
|
- controllerRun(t, TUNNEL_PROTOCOL_FRONTED_MEEK, true)
|
|
|
+ controllerRun(t,
|
|
|
+ &controllerRunConfig{
|
|
|
+ protocol: TUNNEL_PROTOCOL_FRONTED_MEEK,
|
|
|
+ clientIsLatestVersion: false,
|
|
|
+ disableUntunneledUpgrade: true,
|
|
|
+ disableEstablishing: false,
|
|
|
+ disrupt: false,
|
|
|
+ useHostNameTransformer: false,
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestControllerRunFrontedMeekWithTransformer(t *testing.T) {
|
|
|
+ controllerRun(t,
|
|
|
+ &controllerRunConfig{
|
|
|
+ protocol: TUNNEL_PROTOCOL_FRONTED_MEEK,
|
|
|
+ clientIsLatestVersion: true,
|
|
|
+ disableUntunneledUpgrade: true,
|
|
|
+ disableEstablishing: false,
|
|
|
+ disrupt: false,
|
|
|
+ useHostNameTransformer: true,
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
-func TestControllerRunFrontedMeekHTTP(t *testing.T) {
|
|
|
- controllerRun(t, TUNNEL_PROTOCOL_FRONTED_MEEK_HTTP, false)
|
|
|
+func TestControllerFrontedMeekHTTP(t *testing.T) {
|
|
|
+ controllerRun(t,
|
|
|
+ &controllerRunConfig{
|
|
|
+ protocol: TUNNEL_PROTOCOL_FRONTED_MEEK_HTTP,
|
|
|
+ clientIsLatestVersion: true,
|
|
|
+ disableUntunneledUpgrade: true,
|
|
|
+ disableEstablishing: false,
|
|
|
+ disrupt: false,
|
|
|
+ useHostNameTransformer: false,
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
func TestControllerRunUnfrontedMeekHTTPS(t *testing.T) {
|
|
|
- controllerRun(t, TUNNEL_PROTOCOL_UNFRONTED_MEEK_HTTPS, true)
|
|
|
+ controllerRun(t,
|
|
|
+ &controllerRunConfig{
|
|
|
+ protocol: TUNNEL_PROTOCOL_UNFRONTED_MEEK_HTTPS,
|
|
|
+ clientIsLatestVersion: false,
|
|
|
+ disableUntunneledUpgrade: true,
|
|
|
+ disableEstablishing: false,
|
|
|
+ disrupt: false,
|
|
|
+ useHostNameTransformer: false,
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
-func doUntunnledUpgradeDownload(t *testing.T, disrupt bool) {
|
|
|
+func TestControllerRunUnfrontedMeekHTTPSWithTransformer(t *testing.T) {
|
|
|
+ controllerRun(t,
|
|
|
+ &controllerRunConfig{
|
|
|
+ protocol: TUNNEL_PROTOCOL_UNFRONTED_MEEK_HTTPS,
|
|
|
+ clientIsLatestVersion: true,
|
|
|
+ disableUntunneledUpgrade: true,
|
|
|
+ disableEstablishing: false,
|
|
|
+ disrupt: false,
|
|
|
+ useHostNameTransformer: true,
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+type controllerRunConfig struct {
|
|
|
+ protocol string
|
|
|
+ clientIsLatestVersion bool
|
|
|
+ disableUntunneledUpgrade bool
|
|
|
+ disableEstablishing bool
|
|
|
+ disrupt bool
|
|
|
+ useHostNameTransformer bool
|
|
|
+}
|
|
|
+
|
|
|
+func controllerRun(t *testing.T, runConfig *controllerRunConfig) {
|
|
|
|
|
|
configFileContents, err := ioutil.ReadFile("controller_test.config")
|
|
|
if err != nil {
|
|
|
@@ -95,143 +228,32 @@ func doUntunnledUpgradeDownload(t *testing.T, disrupt bool) {
|
|
|
t.Fatalf("error processing configuration file: %s", err)
|
|
|
}
|
|
|
|
|
|
- if disrupt {
|
|
|
- config.UpstreamProxyUrl = disruptorProxyURL
|
|
|
+ if runConfig.clientIsLatestVersion {
|
|
|
+ config.ClientVersion = "999999999"
|
|
|
}
|
|
|
|
|
|
- // Clear remote server list so tunnel cannot be established and
|
|
|
- // untunneled upgrade download case is tested.
|
|
|
- config.RemoteServerListUrl = ""
|
|
|
-
|
|
|
- os.Remove(config.UpgradeDownloadFilename)
|
|
|
-
|
|
|
- err = InitDataStore(config)
|
|
|
- if err != nil {
|
|
|
- t.Fatalf("error initializing datastore: %s", err)
|
|
|
- }
|
|
|
-
|
|
|
- controller, err := NewController(config)
|
|
|
- if err != nil {
|
|
|
- t.Fatalf("error creating controller: %s", err)
|
|
|
- }
|
|
|
-
|
|
|
- upgradeDownloaded := make(chan struct{}, 1)
|
|
|
-
|
|
|
- var clientUpgradeDownloadedBytesCount int32
|
|
|
-
|
|
|
- SetNoticeOutput(NewNoticeReceiver(
|
|
|
- func(notice []byte) {
|
|
|
- // TODO: log notices without logging server IPs:
|
|
|
- // fmt.Fprintf(os.Stderr, "%s\n", string(notice))
|
|
|
- noticeType, payload, err := GetNotice(notice)
|
|
|
- if err != nil {
|
|
|
- return
|
|
|
- }
|
|
|
- switch noticeType {
|
|
|
- case "Tunnels":
|
|
|
- count := int(payload["count"].(float64))
|
|
|
- if count > 0 {
|
|
|
- // TODO: wrong goroutine for t.FatalNow()
|
|
|
- t.Fatalf("tunnel established unexpectedly")
|
|
|
- }
|
|
|
- case "ClientUpgradeDownloadedBytes":
|
|
|
- atomic.AddInt32(&clientUpgradeDownloadedBytesCount, 1)
|
|
|
- t.Logf("ClientUpgradeDownloadedBytes: %d", int(payload["bytes"].(float64)))
|
|
|
- case "ClientUpgradeDownloaded":
|
|
|
- select {
|
|
|
- case upgradeDownloaded <- *new(struct{}):
|
|
|
- default:
|
|
|
- }
|
|
|
- }
|
|
|
- }))
|
|
|
-
|
|
|
- // Run controller
|
|
|
-
|
|
|
- shutdownBroadcast := make(chan struct{})
|
|
|
- controllerWaitGroup := new(sync.WaitGroup)
|
|
|
- controllerWaitGroup.Add(1)
|
|
|
- go func() {
|
|
|
- defer controllerWaitGroup.Done()
|
|
|
- controller.Run(shutdownBroadcast)
|
|
|
- }()
|
|
|
-
|
|
|
- defer func() {
|
|
|
- // Test: shutdown must complete within 10 seconds
|
|
|
-
|
|
|
- close(shutdownBroadcast)
|
|
|
-
|
|
|
- shutdownTimeout := time.NewTimer(10 * time.Second)
|
|
|
-
|
|
|
- shutdownOk := make(chan struct{}, 1)
|
|
|
- go func() {
|
|
|
- controllerWaitGroup.Wait()
|
|
|
- shutdownOk <- *new(struct{})
|
|
|
- }()
|
|
|
-
|
|
|
- select {
|
|
|
- case <-shutdownOk:
|
|
|
- case <-shutdownTimeout.C:
|
|
|
- t.Fatalf("controller shutdown timeout exceeded")
|
|
|
- }
|
|
|
- }()
|
|
|
-
|
|
|
- // Test: upgrade must be downloaded within 120 seconds
|
|
|
-
|
|
|
- downloadTimeout := time.NewTimer(120 * time.Second)
|
|
|
-
|
|
|
- select {
|
|
|
- case <-upgradeDownloaded:
|
|
|
- // TODO: verify downloaded file
|
|
|
-
|
|
|
- case <-downloadTimeout.C:
|
|
|
- t.Fatalf("upgrade download timeout exceeded")
|
|
|
+ if runConfig.disableEstablishing {
|
|
|
+ // Clear remote server list so tunnel cannot be established.
|
|
|
+ // TODO: also delete all server entries in the datastore.
|
|
|
+ config.RemoteServerListUrl = ""
|
|
|
}
|
|
|
|
|
|
- // Test: with disrupt, must be multiple download progress notices
|
|
|
-
|
|
|
- if disrupt {
|
|
|
- count := atomic.LoadInt32(&clientUpgradeDownloadedBytesCount)
|
|
|
- if count <= 1 {
|
|
|
- t.Fatalf("unexpected upgrade download progress: %d", count)
|
|
|
- }
|
|
|
+ if runConfig.disableUntunneledUpgrade {
|
|
|
+ // Disable untunneled upgrade downloader to ensure tunneled case is tested
|
|
|
+ config.UpgradeDownloadClientVersionHeader = ""
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
-type TestHostNameTransformer struct {
|
|
|
-}
|
|
|
-
|
|
|
-func (TestHostNameTransformer) TransformHostName(string) (string, bool) {
|
|
|
- return "example.com", true
|
|
|
-}
|
|
|
|
|
|
-func controllerRun(t *testing.T, protocol string, protocolUsesHostNameTransformer bool) {
|
|
|
- doControllerRun(t, protocol, nil)
|
|
|
- if protocolUsesHostNameTransformer {
|
|
|
- t.Log("running with testHostNameTransformer")
|
|
|
- doControllerRun(t, protocol, &TestHostNameTransformer{})
|
|
|
+ if runConfig.disrupt {
|
|
|
+ config.UpstreamProxyUrl = disruptorProxyURL
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-func doControllerRun(t *testing.T, protocol string, hostNameTransformer HostNameTransformer) {
|
|
|
-
|
|
|
- configFileContents, err := ioutil.ReadFile("controller_test.config")
|
|
|
- if err != nil {
|
|
|
- // Skip, don't fail, if config file is not present
|
|
|
- t.Skipf("error loading configuration file: %s", err)
|
|
|
- }
|
|
|
- config, err := LoadConfig(configFileContents)
|
|
|
- if err != nil {
|
|
|
- t.Fatalf("error processing configuration file: %s", err)
|
|
|
+ if runConfig.useHostNameTransformer {
|
|
|
+ config.HostNameTransformer = &TestHostNameTransformer{}
|
|
|
}
|
|
|
|
|
|
- // Disable untunneled upgrade downloader to ensure tunneled case is tested
|
|
|
- config.UpgradeDownloadClientVersionHeader = ""
|
|
|
-
|
|
|
os.Remove(config.UpgradeDownloadFilename)
|
|
|
|
|
|
- config.TunnelProtocol = protocol
|
|
|
-
|
|
|
- config.HostNameTransformer = hostNameTransformer
|
|
|
+ config.TunnelProtocol = runConfig.protocol
|
|
|
|
|
|
err = InitDataStore(config)
|
|
|
if err != nil {
|
|
|
@@ -252,6 +274,9 @@ func doControllerRun(t *testing.T, protocol string, hostNameTransformer HostName
|
|
|
|
|
|
tunnelEstablished := make(chan struct{}, 1)
|
|
|
upgradeDownloaded := make(chan struct{}, 1)
|
|
|
+ confirmedLatestVersion := make(chan struct{}, 1)
|
|
|
+
|
|
|
+ var clientUpgradeDownloadedBytesCount int32
|
|
|
|
|
|
SetNoticeOutput(NewNoticeReceiver(
|
|
|
func(notice []byte) {
|
|
|
@@ -265,23 +290,34 @@ func doControllerRun(t *testing.T, protocol string, hostNameTransformer HostName
|
|
|
case "Tunnels":
|
|
|
count := int(payload["count"].(float64))
|
|
|
if count > 0 {
|
|
|
- select {
|
|
|
- case tunnelEstablished <- *new(struct{}):
|
|
|
- default:
|
|
|
+ if runConfig.disableEstablishing {
|
|
|
+ // TODO: wrong goroutine for t.FatalNow()
|
|
|
+ t.Fatalf("tunnel established unexpectedly")
|
|
|
+ } else {
|
|
|
+ select {
|
|
|
+ case tunnelEstablished <- *new(struct{}):
|
|
|
+ default:
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
case "ClientUpgradeDownloadedBytes":
|
|
|
+ atomic.AddInt32(&clientUpgradeDownloadedBytesCount, 1)
|
|
|
t.Logf("ClientUpgradeDownloadedBytes: %d", int(payload["bytes"].(float64)))
|
|
|
case "ClientUpgradeDownloaded":
|
|
|
select {
|
|
|
case upgradeDownloaded <- *new(struct{}):
|
|
|
default:
|
|
|
}
|
|
|
+ case "ClientIsLatestVersion":
|
|
|
+ select {
|
|
|
+ case confirmedLatestVersion <- *new(struct{}):
|
|
|
+ default:
|
|
|
+ }
|
|
|
case "ListeningHttpProxyPort":
|
|
|
httpProxyPort = int(payload["port"].(float64))
|
|
|
case "ConnectingServer":
|
|
|
serverProtocol := payload["protocol"]
|
|
|
- if serverProtocol != protocol {
|
|
|
+ if runConfig.protocol != "" && serverProtocol != runConfig.protocol {
|
|
|
// TODO: wrong goroutine for t.FatalNow()
|
|
|
t.Fatalf("wrong protocol selected: %s", serverProtocol)
|
|
|
}
|
|
|
@@ -318,34 +354,61 @@ func doControllerRun(t *testing.T, protocol string, hostNameTransformer HostName
|
|
|
}
|
|
|
}()
|
|
|
|
|
|
- // Test: tunnel must be established within 60 seconds
|
|
|
+ if !runConfig.disableEstablishing {
|
|
|
|
|
|
- establishTimeout := time.NewTimer(60 * time.Second)
|
|
|
+ // Test: tunnel must be established within 60 seconds
|
|
|
|
|
|
- select {
|
|
|
- case <-tunnelEstablished:
|
|
|
+ establishTimeout := time.NewTimer(60 * time.Second)
|
|
|
|
|
|
- case <-establishTimeout.C:
|
|
|
- t.Fatalf("tunnel establish timeout exceeded")
|
|
|
- }
|
|
|
+ select {
|
|
|
+ case <-tunnelEstablished:
|
|
|
+
|
|
|
+ case <-establishTimeout.C:
|
|
|
+ t.Fatalf("tunnel establish timeout exceeded")
|
|
|
+ }
|
|
|
|
|
|
- // Allow for known race condition described in NewHttpProxy():
|
|
|
- time.Sleep(1 * time.Second)
|
|
|
+ // Test: fetch website through tunnel
|
|
|
|
|
|
- // Test: fetch website through tunnel
|
|
|
- fetchWebsite(t, httpProxyPort)
|
|
|
+ // Allow for known race condition described in NewHttpProxy():
|
|
|
+ time.Sleep(1 * time.Second)
|
|
|
+ fetchWebsite(t, httpProxyPort)
|
|
|
+ }
|
|
|
|
|
|
- // Test: upgrade must be downloaded within 60 seconds
|
|
|
+ // Test: upgrade check/download must be downloaded within 120 seconds
|
|
|
|
|
|
- downloadTimeout := time.NewTimer(60 * time.Second)
|
|
|
+ upgradeTimeout := time.NewTimer(120 * time.Second)
|
|
|
|
|
|
select {
|
|
|
case <-upgradeDownloaded:
|
|
|
// TODO: verify downloaded file
|
|
|
+ if runConfig.clientIsLatestVersion {
|
|
|
+ t.Fatalf("upgrade downloaded unexpectedly")
|
|
|
+ }
|
|
|
+
|
|
|
+ case <-confirmedLatestVersion:
|
|
|
+ if !runConfig.clientIsLatestVersion {
|
|
|
+ t.Fatalf("confirmed latest version unexpectedly")
|
|
|
+ }
|
|
|
|
|
|
- case <-downloadTimeout.C:
|
|
|
+ case <-upgradeTimeout.C:
|
|
|
t.Fatalf("upgrade download timeout exceeded")
|
|
|
}
|
|
|
+
|
|
|
+ // Test: with disrupt, must be multiple download progress notices
|
|
|
+
|
|
|
+ if runConfig.disrupt && !runConfig.clientIsLatestVersion {
|
|
|
+ count := atomic.LoadInt32(&clientUpgradeDownloadedBytesCount)
|
|
|
+ if count <= 1 {
|
|
|
+ t.Fatalf("unexpected upgrade download progress: %d", count)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+type TestHostNameTransformer struct {
|
|
|
+}
|
|
|
+
|
|
|
+func (TestHostNameTransformer) TransformHostName(string) (string, bool) {
|
|
|
+ return "example.com", true
|
|
|
}
|
|
|
|
|
|
func fetchWebsite(t *testing.T, httpProxyPort int) {
|