package releasekit_test import ( "bytes" "context" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "hash" "io" "net/http" "net/http/httptest" "os" "path/filepath" "sort" "strings" "sync" "testing" releasekit "gitea.maximumdirect.net/eric/ReleaseKit" "gitea.maximumdirect.net/eric/ReleaseKit/internal/gitea" ) const performanceDeliveryArtifactSize = 16 << 20 type deliveryBenchmarkAsset struct { name string data []byte digest string } type deliveryBenchmarkServer struct { mu sync.Mutex baseURL string commit string assets []deliveryBenchmarkAsset page bool attached map[string]deliveryBenchmarkIdentity } type deliveryBenchmarkIdentity struct { digest string size int64 } func BenchmarkPerformanceFacadeDeliver(b *testing.B) { repository, build, expected := newDeliveryBenchmarkFixture(b) state := &deliveryBenchmarkServer{commit: gitText(b, repository, "rev-parse", "HEAD"), assets: expected} server := httptest.NewServer(http.HandlerFunc(state.serveHTTP)) state.baseURL = server.URL b.Cleanup(server.Close) request := releasekit.DeliveryRequest{ RepositoryDir: repository, Product: "Sample App", Version: "v1.2.3", Destination: facadeDeliveryDestination(server.URL), Prebuilt: &build, } totalBytes := int64(2 * performanceDeliveryArtifactSize) b.Run("FreshUploads", func(b *testing.B) { b.ReportAllocs() b.SetBytes(totalBytes) b.ReportMetric(float64(totalBytes), "artifact-bytes/op") b.ReportMetric(2, "binary-files/op") for i := 0; i < b.N; i++ { b.StopTimer() state.reset(false) b.StartTimer() result, err := releasekit.Deliver(context.Background(), request) b.StopTimer() if err != nil { b.Fatalf("Deliver() error = %v", err) } if len(result.Uploaded) != 3 { b.Fatalf("uploaded %d assets, want two binaries and a manifest", len(result.Uploaded)) } state.verifyUploads(b) b.StartTimer() } }) b.Run("MatchingRetry", func(b *testing.B) { b.StopTimer() state.reset(true) b.ReportAllocs() b.SetBytes(totalBytes) b.ReportMetric(float64(totalBytes), "artifact-bytes/op") b.ReportMetric(2, "binary-files/op") b.ResetTimer() b.StartTimer() for i := 0; i < b.N; i++ { result, err := releasekit.Deliver(context.Background(), request) if err != nil { b.Fatalf("Deliver() error = %v", err) } if len(result.Uploaded) != 0 || len(result.Matching) != 3 { b.Fatalf("matching retry uploaded %d and matched %d assets; want 0 and 3", len(result.Uploaded), len(result.Matching)) } } }) } func newDeliveryBenchmarkFixture(b testing.TB) (string, releasekit.BuildResult, []deliveryBenchmarkAsset) { b.Helper() repository := filepath.Join(b.TempDir(), "repository") if err := os.MkdirAll(repository, 0o755); err != nil { b.Fatal(err) } gitRun(b, repository, "init", "-b", "main") gitRun(b, repository, "config", "user.name", "ReleaseKit Benchmark") gitRun(b, repository, "config", "user.email", "benchmark@example.invalid") writeFile(b, repository, "go.mod", "module example.com/deliveryfixture\n\ngo 1.26.0\n") writeFile(b, repository, "docs/releases/v1.2.3.md", "# Sample App v1.2.3\n\nDelivery fixture.\n") gitRun(b, repository, "add", ".") gitRun(b, repository, "commit", "-m", "prepare delivery benchmark") gitRun(b, repository, "tag", "v1.2.3") data := make([]byte, performanceDeliveryArtifactSize) for i := range data { data[i] = byte((i*17 + i/127 + 29) % 251) } digest := sha256.Sum256(data) digestText := hex.EncodeToString(digest[:]) outputDir := filepath.Join(b.TempDir(), "artifacts") if err := os.MkdirAll(outputDir, 0o700); err != nil { b.Fatal(err) } targets := []releasekit.Target{{OS: "linux", Arch: "amd64"}, {OS: "windows", Arch: "amd64"}} build := releasekit.BuildResult{ RepositoryDir: repository, CandidateCommit: gitText(b, repository, "rev-parse", "HEAD"), ModuleDir: repository, ModulePath: "example.com/deliveryfixture", Tag: "v1.2.3", SourceVerified: true, Version: "v1.2.3", MainPackage: "./cmd/app", BinaryName: "releasekit", Targets: targets, OutputDir: outputDir, } assets := make([]deliveryBenchmarkAsset, 0, len(targets)+1) for _, target := range targets { filename := fmt.Sprintf("releasekit-v1.2.3-%s-%s", target.OS, target.Arch) if target.OS == "windows" { filename += ".exe" } path := filepath.Join(outputDir, filename) if err := os.WriteFile(path, data, 0o600); err != nil { b.Fatal(err) } build.Artifacts = append(build.Artifacts, releasekit.BuildArtifact{Path: path, Filename: filename, Target: target, SHA256: digestText}) assets = append(assets, deliveryBenchmarkAsset{name: filename, data: data, digest: digestText}) } manifest := performanceDeliveryManifest(build.Artifacts) manifestHash := sha256.Sum256(manifest) assets = append(assets, deliveryBenchmarkAsset{name: gitea.ManifestFilename, data: manifest, digest: hex.EncodeToString(manifestHash[:])}) return repository, build, assets } func performanceDeliveryManifest(artifacts []releasekit.BuildArtifact) []byte { ordered := append([]releasekit.BuildArtifact(nil), artifacts...) sort.Slice(ordered, func(i, j int) bool { return ordered[i].Filename < ordered[j].Filename }) var manifest strings.Builder for _, artifact := range ordered { fmt.Fprintf(&manifest, "%s %s\n", artifact.SHA256, artifact.Filename) } return []byte(manifest.String()) } func (state *deliveryBenchmarkServer) reset(populated bool) { state.mu.Lock() defer state.mu.Unlock() state.page = populated state.attached = make(map[string]deliveryBenchmarkIdentity) if populated { for _, asset := range state.assets { state.attached[asset.name] = deliveryBenchmarkIdentity{digest: asset.digest, size: int64(len(asset.data))} } } } func (state *deliveryBenchmarkServer) verifyUploads(b testing.TB) { b.Helper() state.mu.Lock() defer state.mu.Unlock() if !state.page || len(state.attached) != len(state.assets) { b.Fatalf("fresh upload left release page %t and %d attached assets, want page and %d assets", state.page, len(state.attached), len(state.assets)) } for _, asset := range state.assets { identity, ok := state.attached[asset.name] if !ok || identity.digest != asset.digest || identity.size != int64(len(asset.data)) { b.Fatalf("uploaded %q identity = %#v, present %t; want digest %s and size %d", asset.name, identity, ok, asset.digest, len(asset.data)) } } } func (state *deliveryBenchmarkServer) serveHTTP(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/api/v1/repos/owner/example/tags/v1.2.3": _, _ = fmt.Fprintf(w, `{"name":"v1.2.3","commit":{"sha":%q}}`, state.commit) case "/api/v1/repos/owner/example/releases/tags/v1.2.3": state.mu.Lock() page := state.page state.mu.Unlock() if !page { http.NotFound(w, r) return } state.writeRelease(w) case "/api/v1/repos/owner/example/releases": state.mu.Lock() state.page = true state.mu.Unlock() state.writeRelease(w) case "/api/v1/repos/owner/example/releases/10/assets": if r.Method == http.MethodGet { state.writeAssetList(w) return } state.receiveUpload(w, r) default: if strings.HasPrefix(r.URL.Path, "/files/") { state.serveFile(w, r) return } http.NotFound(w, r) } } func (state *deliveryBenchmarkServer) writeRelease(w http.ResponseWriter) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]any{ "id": 10, "html_url": state.baseURL + "/releases/10", "tag_name": "v1.2.3", "target_commitish": state.commit, "name": "Fixture v1.2.3", "body": "# Sample App v1.2.3\n\nDelivery fixture.\n", "draft": false, "prerelease": false, }) } func (state *deliveryBenchmarkServer) writeAssetList(w http.ResponseWriter) { state.mu.Lock() assets := make([]map[string]any, 0, len(state.attached)) for name, identity := range state.attached { assets = append(assets, map[string]any{ "id": len(assets) + 1, "name": name, "size": identity.size, "browser_download_url": state.baseURL + "/files/" + name, }) } state.mu.Unlock() _ = json.NewEncoder(w).Encode(assets) } func (state *deliveryBenchmarkServer) receiveUpload(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") reader, err := r.MultipartReader() if err != nil { http.Error(w, "invalid multipart request", http.StatusBadRequest) return } var digest hash.Hash = sha256.New() var size int64 for { part, err := reader.NextPart() if err == io.EOF { break } if err != nil { http.Error(w, "invalid multipart body", http.StatusBadRequest) return } if part.FormName() == "attachment" { size, err = io.Copy(digest, part) if err != nil { _ = part.Close() http.Error(w, "cannot read attachment", http.StatusBadRequest) return } } _ = part.Close() } identity := deliveryBenchmarkIdentity{digest: hex.EncodeToString(digest.Sum(nil)), size: size} var expected *deliveryBenchmarkAsset for i := range state.assets { if state.assets[i].name == name { expected = &state.assets[i] break } } if expected == nil || identity.digest != expected.digest || identity.size != int64(len(expected.data)) { http.Error(w, "attachment digest or size mismatch", http.StatusBadRequest) return } state.mu.Lock() state.attached[name] = identity assetID := len(state.attached) state.mu.Unlock() w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(map[string]any{"id": assetID, "name": name, "size": size}) } func (state *deliveryBenchmarkServer) serveFile(w http.ResponseWriter, r *http.Request) { name := strings.TrimPrefix(r.URL.Path, "/files/") for _, asset := range state.assets { if asset.name == name { w.Header().Set("Content-Length", fmt.Sprint(len(asset.data))) _, _ = io.Copy(w, bytes.NewReader(asset.data)) return } } http.NotFound(w, r) }