Synchronize delivery test fixtures and record the concurrency audit
This commit is contained in:
170
delivery_test.go
170
delivery_test.go
@@ -13,6 +13,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
releasekit "gitea.maximumdirect.net/eric/ReleaseKit"
|
||||
@@ -41,11 +42,12 @@ func TestDeliverManagedBuildUploadsArtifactsAndCleansTemporaryOutput(t *testing.
|
||||
if err != nil {
|
||||
t.Fatalf("Deliver() error = %v; result = %#v", err, result)
|
||||
}
|
||||
if result.State != releasekit.DeliveryComplete || result.CandidateCommit != remote.commit {
|
||||
observed := remote.snapshot()
|
||||
if result.State != releasekit.DeliveryComplete || result.CandidateCommit != observed.commit {
|
||||
t.Fatalf("delivery result = %#v", result)
|
||||
}
|
||||
if len(result.Uploaded) != 2 || len(remote.assets) != 2 {
|
||||
t.Fatalf("uploaded = %#v; hosted assets = %v", result.Uploaded, mapKeys(remote.assets))
|
||||
if len(result.Uploaded) != 2 || len(observed.assets) != 2 {
|
||||
t.Fatalf("uploaded = %#v; hosted assets = %v", result.Uploaded, mapKeys(observed.assets))
|
||||
}
|
||||
if !strings.Contains(progress.String(), "checking fixture version output") {
|
||||
t.Fatalf("build progress = %q", progress.String())
|
||||
@@ -79,7 +81,8 @@ func TestDeliverBinaryConsumerWithBuildinfoAndSelectedTargets(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Deliver() error = %v; result = %#v", err, result)
|
||||
}
|
||||
if result.State != releasekit.DeliveryComplete || result.CandidateCommit != remote.commit {
|
||||
observed := remote.snapshot()
|
||||
if result.State != releasekit.DeliveryComplete || result.CandidateCommit != observed.commit {
|
||||
t.Fatalf("delivery result = %#v", result)
|
||||
}
|
||||
wantAssets := []string{
|
||||
@@ -87,12 +90,12 @@ func TestDeliverBinaryConsumerWithBuildinfoAndSelectedTargets(t *testing.T) {
|
||||
"fixture-v1.2.3-linux-arm64",
|
||||
"SHA256SUMS",
|
||||
}
|
||||
if len(result.Uploaded) != len(wantAssets) || len(remote.assets) != len(wantAssets) {
|
||||
t.Fatalf("uploaded = %#v; hosted assets = %v", result.Uploaded, mapKeys(remote.assets))
|
||||
if len(result.Uploaded) != len(wantAssets) || len(observed.assets) != len(wantAssets) {
|
||||
t.Fatalf("uploaded = %#v; hosted assets = %v", result.Uploaded, mapKeys(observed.assets))
|
||||
}
|
||||
for _, name := range wantAssets {
|
||||
if _, ok := remote.assets[name]; !ok {
|
||||
t.Errorf("hosted assets omit %q: %v", name, mapKeys(remote.assets))
|
||||
if _, ok := observed.assets[name]; !ok {
|
||||
t.Errorf("hosted assets omit %q: %v", name, mapKeys(observed.assets))
|
||||
}
|
||||
}
|
||||
if !strings.Contains(progress.String(), "checking fixture version output") {
|
||||
@@ -174,8 +177,8 @@ func TestDeliverRejectsSameSizePrebuiltTamperingBeforeHostedWrites(t *testing.T)
|
||||
if err == nil || !strings.Contains(err.Error(), "verify build assets") || result.State != releasekit.DeliveryNotAttempted {
|
||||
t.Fatalf("Deliver() = %#v, %v; want local asset verification failure", result, err)
|
||||
}
|
||||
if remote.requests != 0 {
|
||||
t.Fatalf("hosted requests = %d, want none before local verification", remote.requests)
|
||||
if remote.snapshot().requests != 0 {
|
||||
t.Fatalf("hosted requests = %d, want none before local verification", remote.snapshot().requests)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,8 +220,8 @@ func TestDeliverPageOnlySkipsBuildAndHostedAssets(t *testing.T) {
|
||||
if err != nil || result.State != releasekit.DeliveryComplete {
|
||||
t.Fatalf("Deliver() = %#v, %v", result, err)
|
||||
}
|
||||
if len(remote.assets) != 0 || len(result.Uploaded) != 0 || len(result.Pending) != 0 {
|
||||
t.Fatalf("page-only delivery included assets: result=%#v assets=%v", result, mapKeys(remote.assets))
|
||||
if len(remote.snapshot().assets) != 0 || len(result.Uploaded) != 0 || len(result.Pending) != 0 {
|
||||
t.Fatalf("page-only delivery included assets: result=%#v assets=%v", result, mapKeys(remote.snapshot().assets))
|
||||
}
|
||||
if progress.Len() != 0 {
|
||||
t.Fatalf("page-only delivery emitted build progress: %q", progress.String())
|
||||
@@ -242,8 +245,8 @@ func TestDeliverUsesCommittedModuleIdentityForSeparatePages(t *testing.T) {
|
||||
ExpectedTag: "integrations/wrong/v1.2.3",
|
||||
Destination: facadeDeliveryDestination(server.URL),
|
||||
})
|
||||
if err == nil || wrongTag.Tag != "v1.2.3" || remote.requests != 0 {
|
||||
t.Fatalf("delivery with inconsistent expected tag = %#v, %v; hosted requests %d", wrongTag, err, remote.requests)
|
||||
if err == nil || wrongTag.Tag != "v1.2.3" || remote.snapshot().requests != 0 {
|
||||
t.Fatalf("delivery with inconsistent expected tag = %#v, %v; hosted requests %d", wrongTag, err, remote.snapshot().requests)
|
||||
}
|
||||
|
||||
rootResult, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{
|
||||
@@ -258,8 +261,10 @@ func TestDeliverUsesCommittedModuleIdentityForSeparatePages(t *testing.T) {
|
||||
}
|
||||
|
||||
nestedTag := "integrations/ckconfig/v1.2.3"
|
||||
remote.tag = nestedTag
|
||||
remote.noteBody = "# Fixture v1.2.3\n\nNested delivery fixture.\n"
|
||||
remote.update(func(remote *facadeDeliveryData) {
|
||||
remote.tag = nestedTag
|
||||
remote.noteBody = "# Fixture v1.2.3\n\nNested delivery fixture.\n"
|
||||
})
|
||||
nestedResult, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{
|
||||
RepositoryDir: repository,
|
||||
ModuleDir: "integrations/ckconfig",
|
||||
@@ -271,8 +276,8 @@ func TestDeliverUsesCommittedModuleIdentityForSeparatePages(t *testing.T) {
|
||||
if err != nil || nestedResult.State != releasekit.DeliveryComplete || nestedResult.Tag != nestedTag || nestedResult.ModulePath != "example.com/ckconfig" {
|
||||
t.Fatalf("nested delivery = %#v, %v", nestedResult, err)
|
||||
}
|
||||
if len(remote.pages) != 2 || !remote.pages["v1.2.3"] || !remote.pages[nestedTag] {
|
||||
t.Fatalf("created hosted pages = %#v", remote.pages)
|
||||
if len(remote.snapshot().pages) != 2 || !remote.snapshot().pages["v1.2.3"] || !remote.snapshot().pages[nestedTag] {
|
||||
t.Fatalf("created hosted pages = %#v", remote.snapshot().pages)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,21 +298,21 @@ func TestDeliverRejectsPrebuiltArtifactsForAnotherModuleBeforeHostedRequests(t *
|
||||
}
|
||||
server, remote := newFacadeDeliveryServer(t, repository)
|
||||
defer server.Close()
|
||||
remote.tag = "integrations/ckconfig/v1.2.3"
|
||||
remote.update(func(remote *facadeDeliveryData) { remote.tag = "integrations/ckconfig/v1.2.3" })
|
||||
result, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{
|
||||
RepositoryDir: repository,
|
||||
ModuleDir: "integrations/ckconfig",
|
||||
Product: "Fixture",
|
||||
Version: "v1.2.3",
|
||||
ExpectedTag: remote.tag,
|
||||
ExpectedTag: remote.snapshot().tag,
|
||||
Destination: facadeDeliveryDestination(server.URL),
|
||||
Prebuilt: &prebuilt,
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "selected tagged module") || result.Tag != remote.tag {
|
||||
if err == nil || !strings.Contains(err.Error(), "selected tagged module") || result.Tag != remote.snapshot().tag {
|
||||
t.Fatalf("nested delivery with root artifacts = %#v, %v", result, err)
|
||||
}
|
||||
if remote.requests != 0 {
|
||||
t.Fatalf("hosted requests before prebuilt identity rejection = %d", remote.requests)
|
||||
if remote.snapshot().requests != 0 {
|
||||
t.Fatalf("hosted requests before prebuilt identity rejection = %d", remote.snapshot().requests)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,14 +356,14 @@ func TestDeliverRejectsDirtyTaggedSourceBeforeBuildOrHostedWrites(t *testing.T)
|
||||
},
|
||||
Output: &progress,
|
||||
})
|
||||
if err == nil || result.State != releasekit.DeliveryNotAttempted || result.CandidateCommit != remote.commit {
|
||||
if err == nil || result.State != releasekit.DeliveryNotAttempted || result.CandidateCommit != remote.snapshot().commit {
|
||||
t.Fatalf("Deliver() = %#v, %v; want preflight failure for known commit", result, err)
|
||||
}
|
||||
if progress.Len() != 0 {
|
||||
t.Fatalf("build started before source eligibility passed: %q", progress.String())
|
||||
}
|
||||
if remote.requests != 0 {
|
||||
t.Fatalf("hosted requests = %d, want none", remote.requests)
|
||||
if remote.snapshot().requests != 0 {
|
||||
t.Fatalf("hosted requests = %d, want none", remote.snapshot().requests)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,8 +397,8 @@ func TestDeliverRejectsCheckoutAwayFromTagBeforeBuildOrHostedWrites(t *testing.T
|
||||
if progress.Len() != 0 {
|
||||
t.Fatalf("build started before tag/HEAD agreement: %q", progress.String())
|
||||
}
|
||||
if remote.requests != 0 {
|
||||
t.Fatalf("hosted requests = %d, want none", remote.requests)
|
||||
if remote.snapshot().requests != 0 {
|
||||
t.Fatalf("hosted requests = %d, want none", remote.snapshot().requests)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,14 +422,14 @@ func TestDeliverRejectsInvalidCommittedNoteBeforeBuildOrHostedWrites(t *testing.
|
||||
},
|
||||
Output: &progress,
|
||||
})
|
||||
if err == nil || result.State != releasekit.DeliveryNotAttempted || result.CandidateCommit != remote.commit {
|
||||
if err == nil || result.State != releasekit.DeliveryNotAttempted || result.CandidateCommit != remote.snapshot().commit {
|
||||
t.Fatalf("Deliver() = %#v, %v; want committed-note preflight failure", result, err)
|
||||
}
|
||||
if progress.Len() != 0 {
|
||||
t.Fatalf("build started before committed-note validation: %q", progress.String())
|
||||
}
|
||||
if remote.requests != 0 {
|
||||
t.Fatalf("hosted requests = %d, want none", remote.requests)
|
||||
if remote.snapshot().requests != 0 {
|
||||
t.Fatalf("hosted requests = %d, want none", remote.snapshot().requests)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,8 +463,8 @@ func TestDeliverManagedBuildRejectsCheckoutMutationBeforeHostedWrites(t *testing
|
||||
if err == nil || !strings.Contains(err.Error(), "build source") || result.State != releasekit.DeliveryNotAttempted {
|
||||
t.Fatalf("Deliver() = %#v, %v; want build-source eligibility failure", result, err)
|
||||
}
|
||||
if remote.requests != 0 {
|
||||
t.Fatalf("hosted requests = %d, want none", remote.requests)
|
||||
if remote.snapshot().requests != 0 {
|
||||
t.Fatalf("hosted requests = %d, want none", remote.snapshot().requests)
|
||||
}
|
||||
if entries, err := os.ReadDir(temporaryRoot); err != nil || len(entries) != 0 {
|
||||
t.Fatalf("ineligible managed build output remains: entries=%v error=%v", entries, err)
|
||||
@@ -484,11 +489,11 @@ func TestDeliverManagedBuildFailureCleansTemporaryOutputBeforeHostedWrites(t *te
|
||||
Targets: []releasekit.Target{{OS: runtime.GOOS, Arch: runtime.GOARCH}},
|
||||
},
|
||||
})
|
||||
if err == nil || result.State != releasekit.DeliveryNotAttempted || result.CandidateCommit != remote.commit {
|
||||
if err == nil || result.State != releasekit.DeliveryNotAttempted || result.CandidateCommit != remote.snapshot().commit {
|
||||
t.Fatalf("Deliver() = %#v, %v; want managed build failure before hosted writes", result, err)
|
||||
}
|
||||
if remote.requests != 0 {
|
||||
t.Fatalf("hosted requests = %d, want none", remote.requests)
|
||||
if remote.snapshot().requests != 0 {
|
||||
t.Fatalf("hosted requests = %d, want none", remote.snapshot().requests)
|
||||
}
|
||||
if entries, err := os.ReadDir(temporaryRoot); err != nil || len(entries) != 0 {
|
||||
t.Fatalf("failed managed build output remains: entries=%v error=%v", entries, err)
|
||||
@@ -520,8 +525,8 @@ func TestDeliverManagedBuildCancellationCleansTemporaryOutput(t *testing.T) {
|
||||
if !errors.Is(err, context.Canceled) || result.State != releasekit.DeliveryNotAttempted {
|
||||
t.Fatalf("Deliver() = %#v, %v; want cancellation before hosted writes", result, err)
|
||||
}
|
||||
if remote.requests != 0 {
|
||||
t.Fatalf("hosted requests = %d, want none", remote.requests)
|
||||
if remote.snapshot().requests != 0 {
|
||||
t.Fatalf("hosted requests = %d, want none", remote.snapshot().requests)
|
||||
}
|
||||
if entries, err := os.ReadDir(temporaryRoot); err != nil || len(entries) != 0 {
|
||||
t.Fatalf("canceled managed build output remains: entries=%v error=%v", entries, err)
|
||||
@@ -544,8 +549,8 @@ func TestDeliverRejectsBothBuildSourcesBeforeHostedWrites(t *testing.T) {
|
||||
if err == nil || result.State != releasekit.DeliveryNotAttempted || !strings.Contains(err.Error(), "both") {
|
||||
t.Fatalf("Deliver() = %#v, %v; want invalid build-source combination", result, err)
|
||||
}
|
||||
if remote.requests != 0 {
|
||||
t.Fatalf("hosted requests = %d, want none", remote.requests)
|
||||
if remote.snapshot().requests != 0 {
|
||||
t.Fatalf("hosted requests = %d, want none", remote.snapshot().requests)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -582,6 +587,35 @@ func (writer deliveryCancelWriter) Write(content []byte) (int, error) {
|
||||
}
|
||||
|
||||
type facadeDeliveryServer struct {
|
||||
mu sync.Mutex
|
||||
data facadeDeliveryData
|
||||
}
|
||||
|
||||
func (state *facadeDeliveryServer) update(change func(*facadeDeliveryData)) {
|
||||
state.mu.Lock()
|
||||
defer state.mu.Unlock()
|
||||
change(&state.data)
|
||||
}
|
||||
|
||||
func (state *facadeDeliveryServer) snapshot() facadeDeliveryData {
|
||||
state.mu.Lock()
|
||||
defer state.mu.Unlock()
|
||||
result := state.data
|
||||
|
||||
result.assets = make(map[string][]byte, len(state.data.assets))
|
||||
for key, value := range state.data.assets {
|
||||
result.assets[key] = append([]byte(nil), value...)
|
||||
}
|
||||
|
||||
result.pages = make(map[string]bool, len(state.data.pages))
|
||||
for key, value := range state.data.pages {
|
||||
result.pages[key] = value
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
type facadeDeliveryData struct {
|
||||
t *testing.T
|
||||
commit string
|
||||
tag string
|
||||
@@ -595,24 +629,28 @@ type facadeDeliveryServer struct {
|
||||
|
||||
func newFacadeDeliveryServer(t *testing.T, repository string) (*httptest.Server, *facadeDeliveryServer) {
|
||||
t.Helper()
|
||||
state := &facadeDeliveryServer{t: t, commit: buildGit(t, repository, "rev-parse", "HEAD"), tag: "v1.2.3", version: "v1.2.3", noteBody: "# Fixture v1.2.3\n\nDelivery fixture.\n", pages: make(map[string]bool), assets: make(map[string][]byte)}
|
||||
server := httptest.NewServer(http.HandlerFunc(state.serveHTTP))
|
||||
state.url = server.URL
|
||||
state := &facadeDeliveryServer{data: facadeDeliveryData{t: t, commit: buildGit(t, repository, "rev-parse", "HEAD"), tag: "v1.2.3", version: "v1.2.3", noteBody: "# Fixture v1.2.3\n\nDelivery fixture.\n", pages: make(map[string]bool), assets: make(map[string][]byte)}}
|
||||
server := httptest.NewUnstartedServer(http.HandlerFunc(state.serveHTTP))
|
||||
state.data.url = "http://" + server.Listener.Addr().String()
|
||||
server.Start()
|
||||
return server, state
|
||||
}
|
||||
|
||||
func (state *facadeDeliveryServer) serveHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
state.mu.Lock()
|
||||
defer state.mu.Unlock()
|
||||
data := &state.data
|
||||
if strings.HasPrefix(r.URL.Path, "/api/") {
|
||||
state.requests++
|
||||
data.requests++
|
||||
if r.Header.Get("Authorization") != "token fixture-token" {
|
||||
state.t.Errorf("authorization = %q", r.Header.Get("Authorization"))
|
||||
data.t.Errorf("authorization = %q", r.Header.Get("Authorization"))
|
||||
}
|
||||
}
|
||||
switch r.URL.EscapedPath() {
|
||||
case "/api/v1/repos/owner/example/tags/" + url.PathEscape(state.tag):
|
||||
_, _ = fmt.Fprintf(w, `{"name":%q,"commit":{"sha":%q}}`, state.tag, state.commit)
|
||||
case "/api/v1/repos/owner/example/releases/tags/" + url.PathEscape(state.tag):
|
||||
if !state.pages[state.tag] {
|
||||
case "/api/v1/repos/owner/example/tags/" + url.PathEscape(data.tag):
|
||||
_, _ = fmt.Fprintf(w, `{"name":%q,"commit":{"sha":%q}}`, data.tag, data.commit)
|
||||
case "/api/v1/repos/owner/example/releases/tags/" + url.PathEscape(data.tag):
|
||||
if !data.pages[data.tag] {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
@@ -625,20 +663,20 @@ func (state *facadeDeliveryServer) serveHTTP(w http.ResponseWriter, r *http.Requ
|
||||
Body string `json:"body"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
state.t.Errorf("decode release payload: %v", err)
|
||||
data.t.Errorf("decode release payload: %v", err)
|
||||
http.Error(w, "invalid payload", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if payload.Tag != state.tag || payload.Target != state.commit || payload.Name != "Fixture "+state.version {
|
||||
state.t.Errorf("release payload = %#v", payload)
|
||||
if payload.Tag != data.tag || payload.Target != data.commit || payload.Name != "Fixture "+data.version {
|
||||
data.t.Errorf("release payload = %#v", payload)
|
||||
}
|
||||
state.pages[state.tag] = true
|
||||
data.pages[data.tag] = true
|
||||
state.writeRelease(w)
|
||||
case "/api/v1/repos/owner/example/releases/10/assets":
|
||||
if r.Method == http.MethodGet {
|
||||
list := make([]map[string]any, 0, len(state.assets))
|
||||
for name, content := range state.assets {
|
||||
list = append(list, map[string]any{"id": len(list) + 1, "name": name, "size": len(content), "browser_download_url": state.url + "/files/" + name})
|
||||
list := make([]map[string]any, 0, len(data.assets))
|
||||
for name, content := range data.assets {
|
||||
list = append(list, map[string]any{"id": len(list) + 1, "name": name, "size": len(content), "browser_download_url": data.url + "/files/" + name})
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(list)
|
||||
return
|
||||
@@ -646,22 +684,22 @@ func (state *facadeDeliveryServer) serveHTTP(w http.ResponseWriter, r *http.Requ
|
||||
name := r.URL.Query().Get("name")
|
||||
file, _, err := r.FormFile("attachment")
|
||||
if err != nil {
|
||||
state.t.Errorf("read upload %q: %v", name, err)
|
||||
data.t.Errorf("read upload %q: %v", name, err)
|
||||
http.Error(w, "invalid multipart upload", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
content, err := io.ReadAll(file)
|
||||
_ = file.Close()
|
||||
if err != nil {
|
||||
state.t.Errorf("read upload %q: %v", name, err)
|
||||
data.t.Errorf("read upload %q: %v", name, err)
|
||||
}
|
||||
state.assets[name] = content
|
||||
data.assets[name] = content
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"id": len(state.assets), "name": name, "size": len(content)})
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"id": len(data.assets), "name": name, "size": len(content)})
|
||||
default:
|
||||
if strings.HasPrefix(r.URL.Path, "/files/") {
|
||||
name := strings.TrimPrefix(r.URL.Path, "/files/")
|
||||
content, ok := state.assets[name]
|
||||
content, ok := data.assets[name]
|
||||
if !ok {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
@@ -669,17 +707,19 @@ func (state *facadeDeliveryServer) serveHTTP(w http.ResponseWriter, r *http.Requ
|
||||
_, _ = w.Write(content)
|
||||
return
|
||||
}
|
||||
state.t.Errorf("unexpected delivery request %s %s", r.Method, r.URL)
|
||||
data.t.Errorf("unexpected delivery request %s %s", r.Method, r.URL)
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (state *facadeDeliveryServer) writeRelease(w http.ResponseWriter) {
|
||||
// The request handler holds state.mu.
|
||||
data := &state.data
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"id": 10, "html_url": state.url + "/releases/10", "tag_name": state.tag,
|
||||
"target_commitish": state.commit, "name": "Fixture " + state.version,
|
||||
"body": state.noteBody, "draft": false, "prerelease": false,
|
||||
"id": 10, "html_url": data.url + "/releases/10", "tag_name": data.tag,
|
||||
"target_commitish": data.commit, "name": "Fixture " + data.version,
|
||||
"body": data.noteBody, "draft": false, "prerelease": false,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user