Update server config, builder APK logic, frontend fleet/activity metrics, and ignore test APKs
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
This commit is contained in:
@@ -231,6 +231,9 @@ func apkFileName(req *BuildRequest) string {
|
||||
|
||||
// buildAPKAgent compiles a linux/arm64 agent, embeds it in the Android project, and packages an APK.
|
||||
func (h *Handler) buildAPKAgent(ctx context.Context, req *BuildRequest) (BuildResponse, int, string) {
|
||||
h.apkBuildMu.Lock()
|
||||
defer h.apkBuildMu.Unlock()
|
||||
|
||||
if req.ScoutMode {
|
||||
ApplyApkScoutPreset(req)
|
||||
} else {
|
||||
@@ -337,6 +340,7 @@ func (h *Handler) buildAPKAgent(ctx context.Context, req *BuildRequest) (BuildRe
|
||||
}
|
||||
h.setProgress(req.CancelToken, "Saving to database", 99)
|
||||
if err := h.db.InsertBuild(buildRecord); err != nil {
|
||||
cleanupBuild()
|
||||
return BuildResponse{Success: false, Error: "Failed to record build in database"}, http.StatusInternalServerError, ""
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,13 @@ package builder
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestApplyApkScoutPreset(t *testing.T) {
|
||||
@@ -254,3 +257,104 @@ func TestNormalizeRequestApkSkipsWallet(t *testing.T) {
|
||||
t.Fatalf("normalized apk: os=%q mining_disabled=%v", req.TargetOS, req.MiningDisabled)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildAPKAgentConcurrency(t *testing.T) {
|
||||
h, database := testHandlerDB(t)
|
||||
t.Cleanup(func() { _ = database.Close() })
|
||||
setFakeGoSuccess(t, h)
|
||||
|
||||
androidDir := filepath.Join(h.projectRoot, "android")
|
||||
if err := os.MkdirAll(filepath.Join(androidDir, "agent-app", "src", "main", "assets"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Channel to coordinate/delay the mock builds to assert serialization
|
||||
inBuildChan := make(chan struct{}, 2)
|
||||
h.apkBuildFn = func(h *Handler, ctx context.Context, androidDir, buildDir string) (string, error) {
|
||||
inBuildChan <- struct{}{}
|
||||
// Wait a small duration to keep the lock held, letting another call try to acquire it
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
apk := filepath.Join(buildDir, "agent-app-debug.apk")
|
||||
if err := os.WriteFile(apk, []byte("PK fake apk concurrent"), 0644); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return apk, nil
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
for i := 0; i < 2; i++ {
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
req := &BuildRequest{
|
||||
WorkerName: fmt.Sprintf("node-%d", id),
|
||||
ServerURL: "http://10.0.0.1:8989",
|
||||
CancelToken: fmt.Sprintf("cancel-token-%d", id),
|
||||
ApkMode: true,
|
||||
}
|
||||
resp, code, _ := h.buildAPKAgent(context.Background(), req)
|
||||
if code != 200 || !resp.Success {
|
||||
t.Errorf("concurrent build %d failed: code=%d resp=%+v", id, code, resp)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(inBuildChan)
|
||||
|
||||
// Since they are serialized, they should execute one after the other.
|
||||
if len(inBuildChan) != 2 {
|
||||
t.Fatalf("expected 2 builds to have run, got %d", len(inBuildChan))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildAPKAgentDatabaseFailureCleanup(t *testing.T) {
|
||||
h, database := testHandlerDB(t)
|
||||
// We close the database immediately so that InsertBuild fails
|
||||
_ = database.Close()
|
||||
|
||||
setFakeGoSuccess(t, h)
|
||||
|
||||
androidDir := filepath.Join(h.projectRoot, "android")
|
||||
if err := os.MkdirAll(filepath.Join(androidDir, "agent-app", "src", "main", "assets"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
h.apkBuildFn = func(h *Handler, ctx context.Context, androidDir, buildDir string) (string, error) {
|
||||
apk := filepath.Join(buildDir, "agent-app-debug.apk")
|
||||
if err := os.WriteFile(apk, []byte("PK fake apk cleanup test"), 0644); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return apk, nil
|
||||
}
|
||||
|
||||
req := &BuildRequest{
|
||||
WorkerName: "cleanup-node",
|
||||
ServerURL: "http://10.0.0.1:8989",
|
||||
CancelToken: "cleanup-test-token",
|
||||
ApkMode: true,
|
||||
}
|
||||
|
||||
// Capture existing files in builds dir
|
||||
buildsDir := filepath.Join(h.dataDir, "builds")
|
||||
_ = os.MkdirAll(buildsDir, 0755)
|
||||
|
||||
resp, code, _ := h.buildAPKAgent(context.Background(), req)
|
||||
if resp.Success || code == 200 {
|
||||
t.Fatalf("expected build to fail on DB write, but got success: code=%d", code)
|
||||
}
|
||||
|
||||
// Verify that the build directory under builds/ was cleaned up
|
||||
files, err := os.ReadDir(buildsDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(files) != 0 {
|
||||
var names []string
|
||||
for _, f := range files {
|
||||
names = append(names, f.Name())
|
||||
}
|
||||
t.Fatalf("expected builds directory to be empty after database failure cleanup, but found: %v", names)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -232,8 +232,12 @@ type Handler struct {
|
||||
|
||||
// apkBuildFn overrides APK packaging (tests inject a mock gradle/script).
|
||||
apkBuildFn ApkBuildFunc
|
||||
|
||||
// apkBuildMu serializes parallel Android APK builds to prevent concurrent writes to the shared assets directory and concurrent gradle runs.
|
||||
apkBuildMu sync.Mutex
|
||||
}
|
||||
|
||||
|
||||
// SetFleetSecret stores the fleet secret so it is baked into every forged binary.
|
||||
func (h *Handler) SetFleetSecret(secret string) {
|
||||
h.fleetSecret = secret
|
||||
|
||||
Reference in New Issue
Block a user