From 8dfe7249e015e1a03a911d441cab8c4bb0c7d4c9 Mon Sep 17 00:00:00 2001 From: Stefan Majewsky Date: Sat, 10 Feb 2018 10:24:02 +0100 Subject: add tests for container iterator --- container_iterator.go | 49 +++++++------ container_iterator_test.go | 169 +++++++++++++++++++++++++++++++++++++++++++++ headers.go | 6 -- shared_test.go | 11 +++ 4 files changed, 208 insertions(+), 27 deletions(-) create mode 100644 container_iterator_test.go diff --git a/container_iterator.go b/container_iterator.go index 465a837..71c17be 100644 --- a/container_iterator.go +++ b/container_iterator.go @@ -76,12 +76,11 @@ type ContainerInfo struct { LastModified time.Time } -func (i ContainerIterator) request(limit *uint, detailed bool) Request { +func (i ContainerIterator) request(limit int, detailed bool) Request { r := Request{ - Method: "GET", - Headers: headersToHTTP(i.Headers), - Options: cloneRequestOptions(i.Options), - ExpectStatusCodes: []int{200}, + Method: "GET", + Headers: headersToHTTP(i.Headers), + Options: cloneRequestOptions(i.Options), } if i.Prefix != "" { @@ -94,25 +93,27 @@ func (i ContainerIterator) request(limit *uint, detailed bool) Request { r.Options.Values.Set("marker", i.marker) } - if limit == nil { + if limit < 0 { r.Options.Values.Del("limit") } else { - r.Options.Values.Set("limit", strconv.FormatUint(uint64(*limit), 10)) + r.Options.Values.Set("limit", strconv.FormatUint(uint64(limit), 10)) } if detailed { r.Headers.Set("Accept", "application/json") r.Options.Values.Set("format", "json") + r.ExpectStatusCodes = []int{200} } else { r.Headers.Set("Accept", "text/plain") r.Options.Values.Set("format", "plain") + r.ExpectStatusCodes = []int{200, 204} } return r } //NextPage queries Swift for the next page of container names. If limit is -//given, not more than that container names will be returned at once. Note +//>= 0, not more than that container names will be returned at once. Note //that the server also has a limit for how many containers to list in one //request; the lower limit wins. // @@ -120,7 +121,7 @@ func (i ContainerIterator) request(limit *uint, detailed bool) Request { // //This method offers maximal flexibility, but most users will prefer the //simpler interfaces offered by Collect() and Foreach(). -func (i *ContainerIterator) NextPage(limit *uint) ([]*Container, error) { +func (i *ContainerIterator) NextPage(limit int) ([]*Container, error) { if i.eof { return nil, nil } @@ -128,14 +129,19 @@ func (i *ContainerIterator) NextPage(limit *uint) ([]*Container, error) { if err != nil { return nil, err } + buf, err := collectResponseBody(resp) if err != nil { return nil, err } - names := strings.Split(string(buf), "\n") - result := make([]*Container, len(names)) - for idx, name := range names { - result[idx] = i.Account.Container(name) + bufStr := strings.TrimSuffix(string(buf), "\n") + var result []*Container + if bufStr != "" { + names := strings.Split(bufStr, "\n") + result = make([]*Container, len(names)) + for idx, name := range names { + result[idx] = i.Account.Container(name) + } } if len(result) == 0 { @@ -149,11 +155,11 @@ func (i *ContainerIterator) NextPage(limit *uint) ([]*Container, error) { } //NextPageDetailed is like NextPage, but includes basic metadata. -func (i *ContainerIterator) NextPageDetailed(limit *uint) ([]ContainerInfo, error) { +func (i *ContainerIterator) NextPageDetailed(limit int) ([]ContainerInfo, error) { if i.eof { return nil, nil } - resp, err := i.request(limit, false).Do(i.Account.client) + resp, err := i.request(limit, true).Do(i.Account.client) if err != nil { return nil, err } @@ -165,8 +171,9 @@ func (i *ContainerIterator) NextPageDetailed(limit *uint) ([]ContainerInfo, erro Name string `json:"name"` } err = json.NewDecoder(resp.Body).Decode(&document) + closeErr := resp.Body.Close() if err == nil { - err = resp.Body.Close() + err = closeErr } if err != nil { return nil, err @@ -177,7 +184,7 @@ func (i *ContainerIterator) NextPageDetailed(limit *uint) ([]ContainerInfo, erro result[idx].Container = i.Account.Container(data.Name) result[idx].BytesUsed = data.BytesUsed result[idx].ObjectCount = data.ObjectCount - result[idx].LastModified, err = time.Parse(time.RFC3339Nano, data.LastModifiedStr) + result[idx].LastModified, err = time.Parse(time.RFC3339Nano, data.LastModifiedStr+"Z") if err != nil { //this error is sufficiently obscure that we don't need to expose a type for it return nil, fmt.Errorf("Bad field containers[%d].last_modified: %s", idx, err.Error()) @@ -199,7 +206,7 @@ func (i *ContainerIterator) NextPageDetailed(limit *uint) ([]ContainerInfo, erro //or when the callback returns a non-nil error. func (i *ContainerIterator) Foreach(callback func(*Container) error) error { for { - containers, err := i.NextPage(nil) + containers, err := i.NextPage(-1) if err != nil { return err } @@ -218,7 +225,7 @@ func (i *ContainerIterator) Foreach(callback func(*Container) error) error { //ForeachDetailed is like Foreach, but includes basic metadata. func (i *ContainerIterator) ForeachDetailed(callback func(ContainerInfo) error) error { for { - infos, err := i.NextPageDetailed(nil) + infos, err := i.NextPageDetailed(-1) if err != nil { return err } @@ -240,7 +247,7 @@ func (i *ContainerIterator) ForeachDetailed(callback func(ContainerInfo) error) func (i *ContainerIterator) Collect() ([]*Container, error) { var result []*Container for { - containers, err := i.NextPage(nil) + containers, err := i.NextPage(-1) if err != nil { return nil, err } @@ -255,7 +262,7 @@ func (i *ContainerIterator) Collect() ([]*Container, error) { func (i *ContainerIterator) CollectDetailed() ([]ContainerInfo, error) { var result []ContainerInfo for { - infos, err := i.NextPageDetailed(nil) + infos, err := i.NextPageDetailed(-1) if err != nil { return nil, err } diff --git a/container_iterator_test.go b/container_iterator_test.go new file mode 100644 index 0000000..df4ab80 --- /dev/null +++ b/container_iterator_test.go @@ -0,0 +1,169 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +package schwift + +import ( + "fmt" + "testing" +) + +func TestContainerIterator(t *testing.T) { + testWithAccount(t, func(a *Account) { + cname := func(idx int) string { + return fmt.Sprintf("schwift-test-listing%d", idx) + } + + //create test containers that can be listed + for idx := 1; idx <= 4; idx++ { + _, err := a.Container(cname(idx)).EnsureExists() + expectSuccess(t, err) + } + + //test iteration with empty last page + iter := a.Containers() + iter.Prefix = "schwift-test-listing" + cs, err := iter.NextPage(2) + expectSuccess(t, err) + expectContainerNames(t, cs, cname(1), cname(2)) + cs, err = iter.NextPage(2) + expectSuccess(t, err) + expectContainerNames(t, cs, cname(3), cname(4)) + cs, err = iter.NextPage(2) + expectSuccess(t, err) + expectContainerNames(t, cs) + cs, err = iter.NextPage(2) + expectSuccess(t, err) + expectContainerNames(t, cs) + + //test iteration with partial last page + iter = a.Containers() + iter.Prefix = "schwift-test-listing" + cs, err = iter.NextPage(3) + expectSuccess(t, err) + expectContainerNames(t, cs, cname(1), cname(2), cname(3)) + cs, err = iter.NextPage(3) + expectSuccess(t, err) + expectContainerNames(t, cs, cname(4)) + cs, err = iter.NextPage(4) + expectSuccess(t, err) + expectContainerNames(t, cs) + + //test detailed iteration + iter = a.Containers() + iter.Prefix = "schwift-test-listing" + cis, err := iter.NextPageDetailed(2) + expectSuccess(t, err) + expectContainerInfos(t, cis, cname(1), cname(2)) + cis, err = iter.NextPageDetailed(3) + expectSuccess(t, err) + expectContainerInfos(t, cis, cname(3), cname(4)) + cis, err = iter.NextPageDetailed(3) + expectSuccess(t, err) + expectContainerInfos(t, cis) + cis, err = iter.NextPageDetailed(3) + expectSuccess(t, err) + expectContainerInfos(t, cis) + + //test Foreach + iter = a.Containers() + iter.Prefix = "schwift-test-listing" + idx := 0 + expectSuccess(t, iter.Foreach(func(c *Container) error { + idx++ + expectString(t, c.Name(), cname(idx)) + return nil + })) + expectInt(t, idx, 4) + + //test ForeachDetailed + iter = a.Containers() + iter.Prefix = "schwift-test-listing" + idx = 0 + expectSuccess(t, iter.ForeachDetailed(func(info ContainerInfo) error { + idx++ + expectString(t, info.Container.Name(), cname(idx)) + return nil + })) + expectInt(t, idx, 4) + + //test Collect + iter = a.Containers() + iter.Prefix = "schwift-test-listing" + cs, err = iter.Collect() + expectSuccess(t, err) + expectContainerNames(t, cs, cname(1), cname(2), cname(3), cname(4)) + + //test CollectDetailed + iter = a.Containers() + iter.Prefix = "schwift-test-listing" + cis, err = iter.CollectDetailed() + expectSuccess(t, err) + expectContainerInfos(t, cis, cname(1), cname(2), cname(3), cname(4)) + + //cleanup + iter = a.Containers() + iter.Prefix = "schwift-test-listing" + expectSuccess(t, iter.Foreach(func(c *Container) error { + return c.Delete(nil, nil) + })) + }) +} + +func expectContainerNames(t *testing.T, actualContainers []*Container, expectedNames ...string) { + t.Helper() + if len(actualContainers) != len(expectedNames) { + t.Errorf("expected %d containers, got %d containers", + len(expectedNames), len(actualContainers)) + return + } + for idx, c := range actualContainers { + if c.Name() != expectedNames[idx] { + t.Errorf("expected containers[%d].Name() == %q, got %q", + idx, expectedNames[idx], c.Name()) + } + } +} + +func expectContainerInfos(t *testing.T, actualInfos []ContainerInfo, expectedNames ...string) { + t.Helper() + if len(actualInfos) != len(expectedNames) { + t.Errorf("expected %d containers, got %d containers", + len(expectedNames), len(actualInfos)) + return + } + for idx, info := range actualInfos { + if info.Container.Name() != expectedNames[idx] { + t.Errorf("expected containers[%d].Name() == %q, got %q", + idx, expectedNames[idx], info.Container.Name()) + } + //TODO: upload test object of defined size to the listed containers to + //check if this zero is not just the default value + if info.BytesUsed != 0 { + t.Errorf("expected containers[%d] bytesUsed == 0, got %d", + idx, info.BytesUsed) + } + if info.ObjectCount != 0 { + t.Errorf("expected containers[%d] objectCount == 0, got %d", + idx, info.ObjectCount) + } + if info.LastModified.IsZero() { + t.Errorf("containers[%d].LastModified is zero", idx) + } + } +} diff --git a/headers.go b/headers.go index 1bc206f..7a6ec16 100644 --- a/headers.go +++ b/headers.go @@ -24,9 +24,6 @@ import ( ) func headersToHTTP(h map[string]string) http.Header { - if h == nil { - return nil - } dest := make(http.Header, len(h)) for k, v := range h { dest.Set(k, v) @@ -35,9 +32,6 @@ func headersToHTTP(h map[string]string) http.Header { } func headersFromHTTP(src http.Header) map[string]string { - if src == nil { - return nil - } h := make(map[string]string, len(src)) for k, v := range src { if len(v) > 0 { diff --git a/shared_test.go b/shared_test.go index 04481d4..1e7130e 100644 --- a/shared_test.go +++ b/shared_test.go @@ -121,6 +121,13 @@ func expectFloat64(t *testing.T, actual float64, expected float64) { } } +func expectInt(t *testing.T, actual int, expected int) { + t.Helper() + if actual != expected { + t.Errorf("expected value %d, got %d instead\n", expected, actual) + } +} + func expectUint64(t *testing.T, actual uint64, expected uint64) { t.Helper() if actual != expected { @@ -180,3 +187,7 @@ func expectHeaders(t *testing.T, actual map[string]string, expected map[string]s } } } + +func expectSuccess(t *testing.T, actual error) (ok bool) { + return expectError(t, actual, "") +} -- cgit v1.2.3