Improve test coverage for the health package and remove mocks

The current tests for the health package utilize a fake HTTP client
for testing health checks and only cover a limited set of test cases.

This patch removes the need for mocks by using the httptest package
from the standard library. After removing the fake HTTP client a bug
was found in the health.Check function; it incorrectly assumes that
a http.Response is always non-nil. Fix the issue by moving the defer
that closes the http.Response.Body after error handling.

Related tests in the registry package have be refactored to work with
the changes made to the health.Check function. All methods that implement
the health.HTTPGetInterface interface now return a http.Response with
with a noop http.Response.Body.

This patch does not introduce any changes in behavior.
This commit is contained in:
Kelsey Hightower 2014-07-20 17:34:26 -07:00
parent d11b6246a1
commit fce90dc761
4 changed files with 65 additions and 55 deletions

View File

@ -204,4 +204,3 @@ sudo docker build -t kubernetes/raml2html .
sudo docker run --name="docgen" kubernetes/raml2html sudo docker run --name="docgen" kubernetes/raml2html
sudo docker cp docgen:/data/kubernetes.html . sudo docker cp docgen:/data/kubernetes.html .
``` ```

View File

@ -42,12 +42,10 @@ type HTTPGetInterface interface {
// It returns Unknown and err if the HTTP communication itself fails. // It returns Unknown and err if the HTTP communication itself fails.
func Check(url string, client HTTPGetInterface) (Status, error) { func Check(url string, client HTTPGetInterface) (Status, error) {
res, err := client.Get(url) res, err := client.Get(url)
if res.Body != nil {
defer res.Body.Close()
}
if err != nil { if err != nil {
return Unknown, err return Unknown, err
} }
defer res.Body.Close()
if res.StatusCode >= http.StatusOK && res.StatusCode < http.StatusBadRequest { if res.StatusCode >= http.StatusOK && res.StatusCode < http.StatusBadRequest {
return Healthy, nil return Healthy, nil
} }

View File

@ -17,54 +17,62 @@ limitations under the License.
package health package health
import ( import (
"net"
"net/http" "net/http"
"net/http/httptest"
"net/url"
"testing" "testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
) )
// fakeHTTPClient is a fake implementation of HTTPGetInterface. const statusServerEarlyShutdown = -1
type fakeHTTPClient struct {
req string
res http.Response
err error
}
func (f *fakeHTTPClient) Get(url string) (*http.Response, error) { func TestHealthChecker(t *testing.T) {
f.req = url var healthCheckerTests = []struct {
return &f.res, f.err status int
health Status
}{
{http.StatusOK, Healthy},
{statusServerEarlyShutdown, Unknown},
{http.StatusBadRequest, Unhealthy},
{http.StatusBadGateway, Unhealthy},
{http.StatusInternalServerError, Unhealthy},
} }
for _, healthCheckerTest := range healthCheckerTests {
func TestHttpHealth(t *testing.T) { tt := healthCheckerTest
fakeClient := fakeHTTPClient{ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
res: http.Response{ w.WriteHeader(tt.status)
StatusCode: http.StatusOK, }))
}, u, err := url.Parse(ts.URL)
if err != nil {
t.Errorf("Unexpected error: %v", err)
} }
host, port, err := net.SplitHostPort(u.Host)
check := HTTPHealthChecker{ if err != nil {
client: &fakeClient, t.Errorf("Unexpected error: %v", err)
}
if tt.status == statusServerEarlyShutdown {
ts.Close()
} }
container := api.Container{ container := api.Container{
LivenessProbe: &api.LivenessProbe{ LivenessProbe: &api.LivenessProbe{
HTTPGet: &api.HTTPGetProbe{ HTTPGet: &api.HTTPGetProbe{
Port: "8080", Port: port,
Path: "/foo/bar", Path: "/foo/bar",
Host: host,
}, },
Type: "http", Type: "http",
}, },
} }
hc := NewHealthChecker()
ok, err := check.HealthCheck(container) health, err := hc.HealthCheck(container)
if ok != Healthy { if err != nil && tt.health != Unknown {
t.Error("Unexpected unhealthy") t.Errorf("Unexpected error: %v", err)
} }
if err != nil { if health != tt.health {
t.Errorf("Unexpected error: %#v", err) t.Errorf("Expected %v, got %v", tt.health, health)
} }
if fakeClient.req != "http://localhost:8080/foo/bar" {
t.Errorf("Unexpected url: %s", fakeClient.req)
} }
} }
@ -81,12 +89,10 @@ func TestFindPort(t *testing.T) {
}, },
}, },
} }
check := HTTPHealthChecker{} checker := HTTPHealthChecker{}
validatePort(t, check.findPort(container, "foo"), 8080) want := int64(8080)
} got := checker.findPort(container, "foo")
if got != want {
func validatePort(t *testing.T, port int64, expectedPort int64) { t.Errorf("Expected %v, got %v", want, got)
if port != expectedPort {
t.Errorf("Unexpected port: %d, expected: %d", port, expectedPort)
} }
} }

View File

@ -17,6 +17,8 @@ limitations under the License.
package registry package registry
import ( import (
"bytes"
"io/ioutil"
"net/http" "net/http"
"reflect" "reflect"
"testing" "testing"
@ -24,10 +26,15 @@ import (
type alwaysYes struct{} type alwaysYes struct{}
func (alwaysYes) Get(url string) (*http.Response, error) { func fakeHTTPResponse(status int) *http.Response {
return &http.Response{ return &http.Response{
StatusCode: http.StatusOK, StatusCode: status,
}, nil Body: ioutil.NopCloser(&bytes.Buffer{}),
}
}
func (alwaysYes) Get(url string) (*http.Response, error) {
return fakeHTTPResponse(http.StatusOK), nil
} }
func TestBasicDelegation(t *testing.T) { func TestBasicDelegation(t *testing.T) {
@ -66,9 +73,9 @@ type notMinion struct {
func (n *notMinion) Get(url string) (*http.Response, error) { func (n *notMinion) Get(url string) (*http.Response, error) {
if url != "http://"+n.minion+":10250/healthz" { if url != "http://"+n.minion+":10250/healthz" {
return &http.Response{StatusCode: http.StatusOK}, nil return fakeHTTPResponse(http.StatusOK), nil
} else { } else {
return &http.Response{StatusCode: http.StatusInternalServerError}, nil return fakeHTTPResponse(http.StatusInternalServerError), nil
} }
} }