diff --git a/control/cmd_event.go b/control/cmd_event.go index 063b94f..f3621e8 100644 --- a/control/cmd_event.go +++ b/control/cmd_event.go @@ -120,7 +120,6 @@ func (c *Conn) EventWait( ctx context.Context, events []EventCode, predicate func(Event) (bool, error), ) (Event, error) { eventCh := make(chan Event, 10) - defer close(eventCh) if err := c.AddEventListener(eventCh, events...); err != nil { return nil, err } @@ -131,6 +130,8 @@ func (c *Conn) EventWait( go func() { errCh <- c.HandleEvents(eventCtx) }() for { select { + case <-eventCtx.Done(): + return nil, eventCtx.Err() case err := <-errCh: return nil, err case event := <-eventCh: diff --git a/control/controltest/cmd_event_test.go b/control/controltest/cmd_event_test.go deleted file mode 100644 index 2a9bb5d..0000000 --- a/control/controltest/cmd_event_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package controltest - -/* -func TestEvents(t *testing.T) { - SkipIfNotRunningSpecifically(t) - ctx, conn := NewTestContextAuthenticated(t) - defer ctx.CloseConnected(conn) - // Turn on event handler - eventCtx, cancelFn := context.WithCancel(ctx) - defer cancelFn() - go func() { ctx.Require.Equal(context.Canceled, conn.HandleEvents(eventCtx)) }() - // Enable all events and hold on to which ones were seen - allEvents := control.EventCodes() - seenEvents := map[control.EventCode]struct{}{} - ch := make(chan control.Event, 1000) - ctx.Require.NoError(conn.AddEventListener(ch, allEvents...)) - // Turn on the network - ctx.Require.NoError(conn.SetConf(control.NewKeyVal("DisableNetwork", "0"))) -MainLoop: - for { - select { - case e := <-ch: - // Remove the event listener if it was seen - if _, ok := seenEvents[e.Code()]; !ok { - ctx.Debugf("Got event: %v", e.Code()) - seenEvents[e.Code()] = struct{}{} - ctx.Require.NoError(conn.RemoveEventListener(ch, e.Code())) - } - case <-time.After(3 * time.Second): - ctx.Debugf("3 seconds passed") - break MainLoop - } - } - // Check that each event was sent at least once - for _, event := range allEvents { - _, ok := seenEvents[event] - ctx.Debugf("Event %v seen? %v", event, ok) - } -} -*/ diff --git a/control/controltest/cmd_hiddenservice_test.go b/control/controltest/cmd_hiddenservice_test.go deleted file mode 100644 index 72e4ff0..0000000 --- a/control/controltest/cmd_hiddenservice_test.go +++ /dev/null @@ -1,10 +0,0 @@ -package controltest - -/* -func TestGetHiddenServiceDescriptorAsync(t *testing.T) { - ctx, conn := NewTestContextAuthenticated(t) - defer ctx.CloseConnected(conn) - t.Skip("TODO") -} - -*/ diff --git a/control/controltest/control_test.go b/control/controltest/control_test.go deleted file mode 100644 index 16ff418..0000000 --- a/control/controltest/control_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package controltest - -import ( - "context" - "flag" - "os" - "testing" - - "github.com/cretz/bine/tor" - "github.com/stretchr/testify/require" -) - -func SkipIfNotRunningSpecifically(t *testing.T) { - if f := flag.Lookup("test.run"); f == nil || f.Value == nil || f.Value.String() != t.Name() { - t.Skip("Only runs if -run specifies this test exactly") - } -} - -type TestContext struct { - context.Context - *testing.T - *tor.Tor - Require *require.Assertions -} - -var torExePath string - -func init() { - flag.StringVar(&torExePath, "tor.path", "tor", "The TOR exe path") - flag.Parse() -} - -func NewTestContext(t *testing.T, conf *tor.StartConf) *TestContext { - // Build start conf - if conf == nil { - conf = &tor.StartConf{} - } - conf.ExePath = torExePath - if f := flag.Lookup("test.v"); f != nil && f.Value != nil && f.Value.String() == "true" { - conf.DebugWriter = os.Stdout - } else { - conf.ExtraArgs = append(conf.ExtraArgs, "--quiet") - } - ret := &TestContext{Context: context.Background(), T: t, Require: require.New(t)} - // Start tor - var err error - if ret.Tor, err = tor.Start(ret.Context, conf); err != nil { - defer ret.Close() - t.Fatal(err) - } - return ret -} - -func (t *TestContext) Close() { - if err := t.Tor.Close(); err != nil { - if t.Failed() { - t.Logf("Failure on close: %v", err) - } else { - t.Errorf("Failure on close: %v", err) - } - } -} diff --git a/control/controltest/doc.go b/control/controltest/doc.go deleted file mode 100644 index f9a3fae..0000000 --- a/control/controltest/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package controltest contains the integration tests for package control. -package controltest diff --git a/tests/context_test.go b/tests/context_test.go new file mode 100644 index 0000000..5701616 --- /dev/null +++ b/tests/context_test.go @@ -0,0 +1,95 @@ +package tests + +import ( + "context" + "flag" + "os" + "testing" + "time" + + "github.com/cretz/bine/tor" + "github.com/stretchr/testify/require" +) + +var torExePath string +var torVerbose bool +var torIncludeNetworkTests bool +var globalEnabledNetworkContext *TestContext + +func TestMain(m *testing.M) { + flag.StringVar(&torExePath, "tor.path", "tor", "The Tor exe path") + flag.BoolVar(&torVerbose, "tor.verbose", false, "Show verbose test info") + flag.BoolVar(&torIncludeNetworkTests, "tor.network", false, "Include network tests") + flag.Parse() + exitCode := m.Run() + if globalEnabledNetworkContext != nil { + globalEnabledNetworkContext.CloseTorOnClose = true + globalEnabledNetworkContext.Close() + } + os.Exit(exitCode) +} + +func SkipIfExcludingNetworkTests(t *testing.T) { + if !torIncludeNetworkTests { + t.Skip("Only runs if -tor.network is set") + } +} + +func GlobalEnabledNetworkContext(t *testing.T) *TestContext { + SkipIfExcludingNetworkTests(t) + if globalEnabledNetworkContext == nil { + ctx := NewTestContext(t, nil) + ctx.CloseTorOnClose = false + // 45 second wait for enable network + enableCtx, enableCancel := context.WithTimeout(ctx, 45*time.Second) + defer enableCancel() + ctx.Require.NoError(ctx.EnableNetwork(enableCtx, true)) + globalEnabledNetworkContext = ctx + } else { + globalEnabledNetworkContext.T = t + globalEnabledNetworkContext.Require = require.New(t) + } + return globalEnabledNetworkContext +} + +type TestContext struct { + context.Context + *testing.T + *tor.Tor + Require *require.Assertions + CloseTorOnClose bool +} + +func NewTestContext(t *testing.T, conf *tor.StartConf) *TestContext { + // Build start conf + if conf == nil { + conf = &tor.StartConf{} + } + conf.ExePath = torExePath + if torVerbose { + conf.DebugWriter = os.Stdout + conf.NoHush = true + } else { + conf.ExtraArgs = append(conf.ExtraArgs, "--quiet") + } + ret := &TestContext{Context: context.Background(), T: t, Require: require.New(t), CloseTorOnClose: true} + // Start tor + var err error + if ret.Tor, err = tor.Start(ret.Context, conf); err != nil { + defer ret.Close() + t.Fatal(err) + } + return ret +} + +func (t *TestContext) Close() { + if t.CloseTorOnClose { + if err := t.Tor.Close(); err != nil { + if t.Failed() { + t.Logf("Failure on close: %v", err) + } else { + t.Errorf("Failure on close: %v", err) + } + } + } +} diff --git a/control/controltest/cmd_authenticate_test.go b/tests/control_cmd_authenticate_test.go similarity index 98% rename from control/controltest/cmd_authenticate_test.go rename to tests/control_cmd_authenticate_test.go index 4f390e3..b61e463 100644 --- a/control/controltest/cmd_authenticate_test.go +++ b/tests/control_cmd_authenticate_test.go @@ -1,4 +1,4 @@ -package controltest +package tests import ( "testing" diff --git a/control/controltest/cmd_conf_test.go b/tests/control_cmd_conf_test.go similarity index 99% rename from control/controltest/cmd_conf_test.go rename to tests/control_cmd_conf_test.go index 641a800..8e2642c 100644 --- a/control/controltest/cmd_conf_test.go +++ b/tests/control_cmd_conf_test.go @@ -1,4 +1,4 @@ -package controltest +package tests import ( "io/ioutil" diff --git a/tests/control_cmd_event_test.go b/tests/control_cmd_event_test.go new file mode 100644 index 0000000..354a428 --- /dev/null +++ b/tests/control_cmd_event_test.go @@ -0,0 +1,3 @@ +package tests + +// TODO: test several event parsers diff --git a/control/controltest/cmd_misc_test.go b/tests/control_cmd_misc_test.go similarity index 88% rename from control/controltest/cmd_misc_test.go rename to tests/control_cmd_misc_test.go index c498fe0..b7f2dcd 100644 --- a/control/controltest/cmd_misc_test.go +++ b/tests/control_cmd_misc_test.go @@ -1,4 +1,4 @@ -package controltest +package tests import "testing" diff --git a/control/controltest/cmd_protocolinfo_test.go b/tests/control_cmd_protocolinfo_test.go similarity index 93% rename from control/controltest/cmd_protocolinfo_test.go rename to tests/control_cmd_protocolinfo_test.go index aae2164..42f6b08 100644 --- a/control/controltest/cmd_protocolinfo_test.go +++ b/tests/control_cmd_protocolinfo_test.go @@ -1,4 +1,4 @@ -package controltest +package tests import ( "strings" diff --git a/tests/doc.go b/tests/doc.go new file mode 100644 index 0000000..75d6372 --- /dev/null +++ b/tests/doc.go @@ -0,0 +1,2 @@ +// Package tests contains integration tests. +package tests diff --git a/tests/tor_dialer_test.go b/tests/tor_dialer_test.go new file mode 100644 index 0000000..ae3ff6a --- /dev/null +++ b/tests/tor_dialer_test.go @@ -0,0 +1,44 @@ +package tests + +import ( + "context" + "encoding/json" + "io/ioutil" + "net/http" + "testing" + "time" + + "golang.org/x/net/context/ctxhttp" +) + +func TestDialerSimpleHTTP(t *testing.T) { + ctx := GlobalEnabledNetworkContext(t) + httpClient := httpClient(ctx) + // IsTor check + byts := httpGet(ctx, httpClient, "https://check.torproject.org/api/ip") + jsn := map[string]interface{}{} + ctx.Require.NoError(json.Unmarshal(byts, &jsn)) + ctx.Require.True(jsn["IsTor"].(bool)) +} + +func httpClient(ctx *TestContext) *http.Client { + // 15 seconds max to dial + dialCtx, dialCancel := context.WithTimeout(ctx, 15*time.Second) + defer dialCancel() + // Make connection + dialer, err := ctx.Dialer(dialCtx, nil) + ctx.Require.NoError(err) + return &http.Client{Transport: &http.Transport{DialContext: dialer.DialContext}} +} + +func httpGet(ctx *TestContext, client *http.Client, url string) []byte { + // We'll give it 30 seconds to respond + callCtx, callCancel := context.WithTimeout(ctx, 30*time.Second) + defer callCancel() + resp, err := ctxhttp.Get(callCtx, client, url) + ctx.Require.NoError(err) + defer resp.Body.Close() + respBytes, err := ioutil.ReadAll(resp.Body) + ctx.Require.NoError(err) + return respBytes +} diff --git a/tests/tor_listen_test.go b/tests/tor_listen_test.go new file mode 100644 index 0000000..48f35db --- /dev/null +++ b/tests/tor_listen_test.go @@ -0,0 +1,46 @@ +package tests + +import ( + "context" + "net/http" + "testing" + "time" + + "github.com/cretz/bine/tor" +) + +func TestListenSimpleHTTPV2(t *testing.T) { + ctx := GlobalEnabledNetworkContext(t) + // Create an onion service to listen on random port but show as 80 + conf := &tor.ListenConf{RemotePorts: []int{80}} + client, server, onion := startHTTPServer(ctx, conf, "/test", func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte("Test Content")) + ctx.Require.NoError(err) + }) + defer server.Shutdown(ctx) + // Call /test + byts := httpGet(ctx, client, "http://"+onion.ID+".onion/test") + ctx.Require.Equal("Test Content", string(byts)) +} + +// Only have to shutdown the HTTP server +func startHTTPServer( + ctx *TestContext, + listenConf *tor.ListenConf, + handlePattern string, + handler func(http.ResponseWriter, *http.Request), +) (*http.Client, *http.Server, *tor.OnionService) { + httpClient := httpClient(ctx) + // Wait at most a few minutes for the entire test + listenCtx, listenCancel := context.WithTimeout(context.Background(), 4*time.Minute) + defer listenCancel() + // Create an onion service to listen on random port but show as 80 + onion, err := ctx.Listen(listenCtx, listenConf) + ctx.Require.NoError(err) + // Make HTTP server + mux := http.NewServeMux() + mux.HandleFunc(handlePattern, handler) + httpServer := &http.Server{Handler: mux} + go func() { ctx.Require.Equal(http.ErrServerClosed, httpServer.Serve(onion)) }() + return httpClient, httpServer, onion +} diff --git a/tor/tor.go b/tor/tor.go index aa8b100..f022a31 100644 --- a/tor/tor.go +++ b/tor/tor.go @@ -102,6 +102,11 @@ type StartConf struct { // NoHush if true does not set --hush. By default --hush is set. NoHush bool + + // NoAutoSocksPort if true does not set "--SocksPort auto" as is done by + // default. This means the caller could set their own or just let it + // default to 9050. + NoAutoSocksPort bool } // Start a Tor instance and connect to it. If ctx is nil, context.Background() @@ -175,6 +180,9 @@ func (t *Tor) startProcess(ctx context.Context, conf *StartConf) error { if !conf.NoHush { args = append(args, "--hush") } + if !conf.NoAutoSocksPort { + args = append(args, "--SocksPort", "auto") + } // If there is no Torrc file, create a blank temp one torrcFileName := conf.TorrcFile if torrcFileName == "" {