move unit tests into 'testing' directories
diff --git a/testing/endpoint_search_test.go b/testing/endpoint_search_test.go
new file mode 100644
index 0000000..22476cb
--- /dev/null
+++ b/testing/endpoint_search_test.go
@@ -0,0 +1,20 @@
+package testing
+
+import (
+	"testing"
+
+	"github.com/gophercloud/gophercloud"
+	th "github.com/gophercloud/gophercloud/testhelper"
+)
+
+func TestApplyDefaultsToEndpointOpts(t *testing.T) {
+	eo := gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic}
+	eo.ApplyDefaults("compute")
+	expected := gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic, Type: "compute"}
+	th.CheckDeepEquals(t, expected, eo)
+
+	eo = gophercloud.EndpointOpts{Type: "compute"}
+	eo.ApplyDefaults("object-store")
+	expected = gophercloud.EndpointOpts{Availability: gophercloud.AvailabilityPublic, Type: "compute"}
+	th.CheckDeepEquals(t, expected, eo)
+}
diff --git a/testing/params_test.go b/testing/params_test.go
new file mode 100644
index 0000000..90f3fad
--- /dev/null
+++ b/testing/params_test.go
@@ -0,0 +1,250 @@
+package testing
+
+import (
+	"net/url"
+	"reflect"
+	"testing"
+
+	"github.com/gophercloud/gophercloud"
+	th "github.com/gophercloud/gophercloud/testhelper"
+)
+
+func TestMaybeString(t *testing.T) {
+	testString := ""
+	var expected *string
+	actual := gophercloud.MaybeString(testString)
+	th.CheckDeepEquals(t, expected, actual)
+
+	testString = "carol"
+	expected = &testString
+	actual = gophercloud.MaybeString(testString)
+	th.CheckDeepEquals(t, expected, actual)
+}
+
+func TestMaybeInt(t *testing.T) {
+	testInt := 0
+	var expected *int
+	actual := gophercloud.MaybeInt(testInt)
+	th.CheckDeepEquals(t, expected, actual)
+
+	testInt = 4
+	expected = &testInt
+	actual = gophercloud.MaybeInt(testInt)
+	th.CheckDeepEquals(t, expected, actual)
+}
+
+func TestBuildQueryString(t *testing.T) {
+	type testVar string
+	opts := struct {
+		J  int       `q:"j"`
+		R  string    `q:"r,required"`
+		C  bool      `q:"c"`
+		S  []string  `q:"s"`
+		TS []testVar `q:"ts"`
+		TI []int     `q:"ti"`
+	}{
+		J:  2,
+		R:  "red",
+		C:  true,
+		S:  []string{"one", "two", "three"},
+		TS: []testVar{"a", "b"},
+		TI: []int{1, 2},
+	}
+	expected := &url.URL{RawQuery: "c=true&j=2&r=red&s=one&s=two&s=three&ti=1&ti=2&ts=a&ts=b"}
+	actual, err := gophercloud.BuildQueryString(&opts)
+	if err != nil {
+		t.Errorf("Error building query string: %v", err)
+	}
+	th.CheckDeepEquals(t, expected, actual)
+
+	opts = struct {
+		J  int       `q:"j"`
+		R  string    `q:"r,required"`
+		C  bool      `q:"c"`
+		S  []string  `q:"s"`
+		TS []testVar `q:"ts"`
+		TI []int     `q:"ti"`
+	}{
+		J: 2,
+		C: true,
+	}
+	_, err = gophercloud.BuildQueryString(&opts)
+	if err == nil {
+		t.Errorf("Expected error: 'Required field not set'")
+	}
+	th.CheckDeepEquals(t, expected, actual)
+
+	_, err = gophercloud.BuildQueryString(map[string]interface{}{"Number": 4})
+	if err == nil {
+		t.Errorf("Expected error: 'Options type is not a struct'")
+	}
+}
+
+func TestBuildHeaders(t *testing.T) {
+	testStruct := struct {
+		Accept string `h:"Accept"`
+		Num    int    `h:"Number,required"`
+		Style  bool   `h:"Style"`
+	}{
+		Accept: "application/json",
+		Num:    4,
+		Style:  true,
+	}
+	expected := map[string]string{"Accept": "application/json", "Number": "4", "Style": "true"}
+	actual, err := gophercloud.BuildHeaders(&testStruct)
+	th.CheckNoErr(t, err)
+	th.CheckDeepEquals(t, expected, actual)
+
+	testStruct.Num = 0
+	_, err = gophercloud.BuildHeaders(&testStruct)
+	if err == nil {
+		t.Errorf("Expected error: 'Required header not set'")
+	}
+
+	_, err = gophercloud.BuildHeaders(map[string]interface{}{"Number": 4})
+	if err == nil {
+		t.Errorf("Expected error: 'Options type is not a struct'")
+	}
+}
+
+func TestQueriesAreEscaped(t *testing.T) {
+	type foo struct {
+		Name  string `q:"something"`
+		Shape string `q:"else"`
+	}
+
+	expected := &url.URL{RawQuery: "else=Triangl+e&something=blah%2B%3F%21%21foo"}
+
+	actual, err := gophercloud.BuildQueryString(foo{Name: "blah+?!!foo", Shape: "Triangl e"})
+	th.AssertNoErr(t, err)
+
+	th.AssertDeepEquals(t, expected, actual)
+}
+
+func TestBuildRequestBody(t *testing.T) {
+	type PasswordCredentials struct {
+		Username string `json:"username" required:"true"`
+		Password string `json:"password" required:"true"`
+	}
+
+	type TokenCredentials struct {
+		ID string `json:"id,omitempty" required:"true"`
+	}
+
+	type orFields struct {
+		Filler int `json:"filler,omitempty"`
+		F1     int `json:"f1,omitempty" or:"F2"`
+		F2     int `json:"f2,omitempty" or:"F1"`
+	}
+
+	// AuthOptions wraps a gophercloud AuthOptions in order to adhere to the AuthOptionsBuilder
+	// interface.
+	type AuthOptions struct {
+		PasswordCredentials `json:"passwordCredentials,omitempty" xor:"TokenCredentials"`
+
+		// The TenantID and TenantName fields are optional for the Identity V2 API.
+		// Some providers allow you to specify a TenantName instead of the TenantId.
+		// Some require both. Your provider's authentication policies will determine
+		// how these fields influence authentication.
+		TenantID   string `json:"tenantId,omitempty"`
+		TenantName string `json:"tenantName,omitempty"`
+
+		// TokenCredentials allows users to authenticate (possibly as another user) with an
+		// authentication token ID.
+		TokenCredentials `json:"token,omitempty" xor:"PasswordCredentials"`
+
+		OrFields orFields `json:"or_fields,omitempty"`
+	}
+
+	var successCases = []struct {
+		opts     AuthOptions
+		expected map[string]interface{}
+	}{
+		{
+			AuthOptions{
+				PasswordCredentials: PasswordCredentials{
+					Username: "me",
+					Password: "swordfish",
+				},
+			},
+			map[string]interface{}{
+				"auth": map[string]interface{}{
+					"passwordCredentials": map[string]interface{}{
+						"password": "swordfish",
+						"username": "me",
+					},
+				},
+			},
+		},
+		{
+			AuthOptions{
+				TokenCredentials: TokenCredentials{
+					ID: "1234567",
+				},
+			},
+			map[string]interface{}{
+				"auth": map[string]interface{}{
+					"token": map[string]interface{}{
+						"id": "1234567",
+					},
+				},
+			},
+		},
+	}
+
+	for _, successCase := range successCases {
+		actual, err := gophercloud.BuildRequestBody(successCase.opts, "auth")
+		th.AssertNoErr(t, err)
+		th.AssertDeepEquals(t, successCase.expected, actual)
+	}
+
+	var failCases = []struct {
+		opts     AuthOptions
+		expected error
+	}{
+		{
+			AuthOptions{
+				TenantID:   "987654321",
+				TenantName: "me",
+			},
+			gophercloud.ErrMissingInput{},
+		},
+		{
+			AuthOptions{
+				TokenCredentials: TokenCredentials{
+					ID: "1234567",
+				},
+				PasswordCredentials: PasswordCredentials{
+					Username: "me",
+					Password: "swordfish",
+				},
+			},
+			gophercloud.ErrMissingInput{},
+		},
+		{
+			AuthOptions{
+				PasswordCredentials: PasswordCredentials{
+					Password: "swordfish",
+				},
+			},
+			gophercloud.ErrMissingInput{},
+		},
+		{
+			AuthOptions{
+				PasswordCredentials: PasswordCredentials{
+					Username: "me",
+					Password: "swordfish",
+				},
+				OrFields: orFields{
+					Filler: 2,
+				},
+			},
+			gophercloud.ErrMissingInput{},
+		},
+	}
+
+	for _, failCase := range failCases {
+		_, err := gophercloud.BuildRequestBody(failCase.opts, "auth")
+		th.AssertDeepEquals(t, reflect.TypeOf(failCase.expected), reflect.TypeOf(err))
+	}
+}
diff --git a/testing/provider_client_test.go b/testing/provider_client_test.go
new file mode 100644
index 0000000..7c0e84e
--- /dev/null
+++ b/testing/provider_client_test.go
@@ -0,0 +1,36 @@
+package testing
+
+import (
+	"testing"
+
+	"github.com/gophercloud/gophercloud"
+	th "github.com/gophercloud/gophercloud/testhelper"
+)
+
+func TestAuthenticatedHeaders(t *testing.T) {
+	p := &gophercloud.ProviderClient{
+		TokenID: "1234",
+	}
+	expected := map[string]string{"X-Auth-Token": "1234"}
+	actual := p.AuthenticatedHeaders()
+	th.CheckDeepEquals(t, expected, actual)
+}
+
+func TestUserAgent(t *testing.T) {
+	p := &gophercloud.ProviderClient{}
+
+	p.UserAgent.Prepend("custom-user-agent/2.4.0")
+	expected := "custom-user-agent/2.4.0 gophercloud/2.0.0"
+	actual := p.UserAgent.Join()
+	th.CheckEquals(t, expected, actual)
+
+	p.UserAgent.Prepend("another-custom-user-agent/0.3.0", "a-third-ua/5.9.0")
+	expected = "another-custom-user-agent/0.3.0 a-third-ua/5.9.0 custom-user-agent/2.4.0 gophercloud/2.0.0"
+	actual = p.UserAgent.Join()
+	th.CheckEquals(t, expected, actual)
+
+	p.UserAgent = gophercloud.UserAgent{}
+	expected = "gophercloud/2.0.0"
+	actual = p.UserAgent.Join()
+	th.CheckEquals(t, expected, actual)
+}
diff --git a/testing/service_client_test.go b/testing/service_client_test.go
new file mode 100644
index 0000000..904b303
--- /dev/null
+++ b/testing/service_client_test.go
@@ -0,0 +1,15 @@
+package testing
+
+import (
+	"testing"
+
+	"github.com/gophercloud/gophercloud"
+	th "github.com/gophercloud/gophercloud/testhelper"
+)
+
+func TestServiceURL(t *testing.T) {
+	c := &gophercloud.ServiceClient{Endpoint: "http://123.45.67.8/"}
+	expected := "http://123.45.67.8/more/parts/here"
+	actual := c.ServiceURL("more", "parts", "here")
+	th.CheckEquals(t, expected, actual)
+}
diff --git a/testing/util_test.go b/testing/util_test.go
new file mode 100644
index 0000000..5985bc3
--- /dev/null
+++ b/testing/util_test.go
@@ -0,0 +1,86 @@
+package testing
+
+import (
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"github.com/gophercloud/gophercloud"
+	th "github.com/gophercloud/gophercloud/testhelper"
+)
+
+func TestWaitFor(t *testing.T) {
+	err := gophercloud.WaitFor(5, func() (bool, error) {
+		return true, nil
+	})
+	th.CheckNoErr(t, err)
+}
+
+func TestNormalizeURL(t *testing.T) {
+	urls := []string{
+		"NoSlashAtEnd",
+		"SlashAtEnd/",
+	}
+	expected := []string{
+		"NoSlashAtEnd/",
+		"SlashAtEnd/",
+	}
+	for i := 0; i < len(expected); i++ {
+		th.CheckEquals(t, expected[i], gophercloud.NormalizeURL(urls[i]))
+	}
+
+}
+
+func TestNormalizePathURL(t *testing.T) {
+	baseDir, _ := os.Getwd()
+
+	rawPath := "template.yaml"
+	basePath, _ := filepath.Abs(".")
+	result, _ := gophercloud.NormalizePathURL(basePath, rawPath)
+	expected := strings.Join([]string{"file:/", filepath.ToSlash(baseDir), "template.yaml"}, "/")
+	th.CheckEquals(t, expected, result)
+
+	rawPath = "http://www.google.com"
+	basePath, _ = filepath.Abs(".")
+	result, _ = gophercloud.NormalizePathURL(basePath, rawPath)
+	expected = "http://www.google.com"
+	th.CheckEquals(t, expected, result)
+
+	rawPath = "very/nested/file.yaml"
+	basePath, _ = filepath.Abs(".")
+	result, _ = gophercloud.NormalizePathURL(basePath, rawPath)
+	expected = strings.Join([]string{"file:/", filepath.ToSlash(baseDir), "very/nested/file.yaml"}, "/")
+	th.CheckEquals(t, expected, result)
+
+	rawPath = "very/nested/file.yaml"
+	basePath = "http://www.google.com"
+	result, _ = gophercloud.NormalizePathURL(basePath, rawPath)
+	expected = "http://www.google.com/very/nested/file.yaml"
+	th.CheckEquals(t, expected, result)
+
+	rawPath = "very/nested/file.yaml/"
+	basePath = "http://www.google.com/"
+	result, _ = gophercloud.NormalizePathURL(basePath, rawPath)
+	expected = "http://www.google.com/very/nested/file.yaml"
+	th.CheckEquals(t, expected, result)
+
+	rawPath = "very/nested/file.yaml"
+	basePath = "http://www.google.com/even/more"
+	result, _ = gophercloud.NormalizePathURL(basePath, rawPath)
+	expected = "http://www.google.com/even/more/very/nested/file.yaml"
+	th.CheckEquals(t, expected, result)
+
+	rawPath = "very/nested/file.yaml"
+	basePath = strings.Join([]string{"file:/", filepath.ToSlash(baseDir), "only/file/even/more"}, "/")
+	result, _ = gophercloud.NormalizePathURL(basePath, rawPath)
+	expected = strings.Join([]string{"file:/", filepath.ToSlash(baseDir), "only/file/even/more/very/nested/file.yaml"}, "/")
+	th.CheckEquals(t, expected, result)
+
+	rawPath = "very/nested/file.yaml/"
+	basePath = strings.Join([]string{"file:/", filepath.ToSlash(baseDir), "only/file/even/more"}, "/")
+	result, _ = gophercloud.NormalizePathURL(basePath, rawPath)
+	expected = strings.Join([]string{"file:/", filepath.ToSlash(baseDir), "only/file/even/more/very/nested/file.yaml"}, "/")
+	th.CheckEquals(t, expected, result)
+
+}