Commit 1e4dba76 authored by Arran Walker's avatar Arran Walker

Update workhorse zipartifacts test to ensure minimum number of range requests

zipartifacts uses a http-client based ReaderAt implementation that fetches parts of a remote zip
file by using Range headers. We don't have direct control of the ranges that will be requested, so
this test ensures that changes to the underlying zip reader won't negatively affect performance by
making more range requests than expected.
parent 40a74511
...@@ -2,56 +2,124 @@ package zipartifacts ...@@ -2,56 +2,124 @@ package zipartifacts
import ( import (
"archive/zip" "archive/zip"
"bytes"
"context" "context"
"fmt" "fmt"
"io/ioutil" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"strings"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestOpenHTTPArchive(t *testing.T) { func createArchive(t *testing.T, dir string) (map[string][]byte, int64) {
const ( f, err := os.Create(filepath.Join(dir, "test.zip"))
zipFile = "test.zip" require.NoError(t, err)
entryName = "hello.txt"
contents = "world"
testRoot = "testdata/public"
)
require.NoError(t, os.MkdirAll(testRoot, 0755))
f, err := os.Create(filepath.Join(testRoot, zipFile))
require.NoError(t, err, "create file")
defer f.Close() defer f.Close()
zw := zip.NewWriter(f) zw := zip.NewWriter(f)
entries := make(map[string][]byte)
for _, size := range []int{0, 32 * 1024, 128 * 1024, 5 * 1024 * 1024} {
entryName := fmt.Sprintf("file_%d", size)
entries[entryName] = bytes.Repeat([]byte{'z'}, size)
w, err := zw.Create(entryName) w, err := zw.Create(entryName)
require.NoError(t, err, "create zip entry") require.NoError(t, err)
_, err = fmt.Fprint(w, contents)
require.NoError(t, err, "write zip entry contents") _, err = w.Write(entries[entryName])
require.NoError(t, zw.Close(), "close zip writer") require.NoError(t, err)
require.NoError(t, f.Close(), "close file") }
require.NoError(t, zw.Close())
fi, err := f.Stat()
require.NoError(t, err)
require.NoError(t, f.Close())
return entries, fi.Size()
}
srv := httptest.NewServer(http.FileServer(http.Dir(testRoot))) func TestOpenHTTPArchive(t *testing.T) {
dir := t.TempDir()
entries, _ := createArchive(t, dir)
srv := httptest.NewServer(http.FileServer(http.Dir(dir)))
defer srv.Close() defer srv.Close()
zr, err := OpenArchive(context.Background(), srv.URL+"/"+zipFile) ctx, cancel := context.WithCancel(context.Background())
require.NoError(t, err, "call OpenArchive") defer cancel()
require.Len(t, zr.File, 1)
zr, err := OpenArchive(ctx, srv.URL+"/test.zip")
require.NoError(t, err)
require.Len(t, zr.File, len(entries))
for _, zf := range zr.File {
entry, ok := entries[zf.Name]
require.True(t, ok)
zf := zr.File[0] r, err := zf.Open()
require.Equal(t, entryName, zf.Name, "zip entry name") require.NoError(t, err)
entry, err := zf.Open() contents, err := io.ReadAll(r)
require.NoError(t, err, "get zip entry reader") require.NoError(t, err)
defer entry.Close() require.Equal(t, entry, contents)
actualContents, err := ioutil.ReadAll(entry) require.NoError(t, r.Close())
require.NoError(t, err, "read zip entry contents") }
require.Equal(t, contents, string(actualContents), "compare zip entry contents") }
func TestMinimalRangeRequests(t *testing.T) {
if strings.HasPrefix(runtime.Version(), "go1.17") {
t.Skipf("skipped for go1.17: https://gitlab.com/gitlab-org/gitlab/-/issues/340778")
}
dir := t.TempDir()
entries, archiveSize := createArchive(t, dir)
mux := http.NewServeMux()
var ranges []string
mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
rangeHdr := r.Header.Get("Range")
if rangeHdr == "" {
rw.Header().Add("Content-Length", fmt.Sprintf("%d", archiveSize))
return
}
ranges = append(ranges, rangeHdr)
http.FileServer(http.Dir(dir)).ServeHTTP(rw, r)
})
srv := httptest.NewServer(mux)
defer srv.Close()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
zr, err := OpenArchive(ctx, srv.URL+"/test.zip")
require.NoError(t, err)
require.Len(t, zr.File, len(entries))
require.Len(t, ranges, 2, "range requests should be minimal")
require.NotContains(t, ranges, "bytes=0-", "range request should not request from zero")
for _, zf := range zr.File {
r, err := zf.Open()
require.NoError(t, err)
_, err = io.Copy(io.Discard, r)
require.NoError(t, err)
require.NoError(t, r.Close())
}
// ensure minimal requests: https://gitlab.com/gitlab-org/gitlab/-/issues/340778
require.Len(t, ranges, 3, "range requests should be minimal")
require.Contains(t, ranges, "bytes=0-")
} }
func TestOpenHTTPArchiveNotSendingAcceptEncodingHeader(t *testing.T) { func TestOpenHTTPArchiveNotSendingAcceptEncodingHeader(t *testing.T) {
...@@ -64,5 +132,8 @@ func TestOpenHTTPArchiveNotSendingAcceptEncodingHeader(t *testing.T) { ...@@ -64,5 +132,8 @@ func TestOpenHTTPArchiveNotSendingAcceptEncodingHeader(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(requestHandler)) srv := httptest.NewServer(http.HandlerFunc(requestHandler))
defer srv.Close() defer srv.Close()
OpenArchive(context.Background(), srv.URL) ctx, cancel := context.WithCancel(context.Background())
defer cancel()
OpenArchive(ctx, srv.URL)
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment