CI / go-test (push) Canceled after 0s
CI / postgres-durable-first (push) Canceled after 0s
CI / codegen (push) Canceled after 0s
CI / govulncheck (push) Canceled after 0s
CI / race (push) Canceled after 0s
CI / fuzz-smoke (push) Canceled after 0s
CI / golangci (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CodeQL Advanced / Analyze (go) (push) Canceled after 0s
CodeQL Advanced / Analyze (actions) (push) Canceled after 0s
CodeQL Advanced / Analyze (javascript-typescript) (push) Canceled after 0s
Docs CI / build (push) Canceled after 0s
Docs Deploy (GitHub Pages) / build (push) Canceled after 0s
Release 3X-UI / build (386) (push) Canceled after 0s
Release 3X-UI / build (amd64) (push) Canceled after 0s
Release 3X-UI / build (arm64) (push) Canceled after 0s
Release 3X-UI / build (armv5) (push) Canceled after 0s
Release 3X-UI / build (armv6) (push) Canceled after 0s
Release 3X-UI / build (armv7) (push) Canceled after 0s
Release 3X-UI / build (s390x) (push) Canceled after 0s
Release 3X-UI / Build for Windows (amd64) (push) Canceled after 0s
Deploy Smoke Tests / noninteractive-install (ubuntu-24.04-arm) (push) Canceled after 0s
Deploy Smoke Tests / noninteractive-install (ubuntu-latest) (push) Canceled after 0s
Deploy Smoke Tests / release-tag-install (ubuntu-24.04-arm) (push) Canceled after 0s
Deploy Smoke Tests / release-tag-install (ubuntu-latest) (push) Canceled after 0s
Docs Deploy (GitHub Pages) / deploy (push) Canceled after 0s
Release 3X-UI / Publish rolling dev release (push) Canceled after 0s
117 lines
3.5 KiB
Go
117 lines
3.5 KiB
Go
package service
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/mtproto"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
|
)
|
|
|
|
const (
|
|
mtprotoTestSecretA = "ee00112233445566778899aabbccddeeff6578616d706c652e636f6d"
|
|
mtprotoTestSecretB = "ee101112131415161718191a1b1c1d1e1f6578616d706c652e636f6d"
|
|
mtprotoTestSecretC = "ee202122232425262728292a2b2c2d2e2f6578616d706c652e636f6d"
|
|
mtprotoTestSecretD = "ee303132333435363738393a3b3c3d3e3f6578616d706c652e636f6d"
|
|
)
|
|
|
|
func seedClientTraffic(t *testing.T, inboundId int, email string, enable bool) {
|
|
t.Helper()
|
|
row := xray.ClientTraffic{InboundId: inboundId, Email: email, Enable: enable}
|
|
if err := database.GetDB().Create(&row).Error; err != nil {
|
|
t.Fatalf("seed traffic %s: %v", email, err)
|
|
}
|
|
}
|
|
|
|
func loadInboundByTag(t *testing.T, tag string) *model.Inbound {
|
|
t.Helper()
|
|
var ib model.Inbound
|
|
if err := database.GetDB().Where("tag = ?", tag).First(&ib).Error; err != nil {
|
|
t.Fatalf("load inbound %s: %v", tag, err)
|
|
}
|
|
return &ib
|
|
}
|
|
|
|
// fakeMtgChildMain is what the re-executed test binary runs when posing as an
|
|
// mtg child process: it appends its pid to the file named by MTG_FAKE_PIDFILE
|
|
// so tests can count spawns, then blocks until the manager kills it.
|
|
func fakeMtgChildMain() {
|
|
if f, err := os.OpenFile(os.Getenv("MTG_FAKE_PIDFILE"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644); err == nil {
|
|
fmt.Fprintf(f, "%d\n", os.Getpid())
|
|
f.Close()
|
|
}
|
|
select {}
|
|
}
|
|
|
|
// installFakeMtg points the mtproto manager at a copy of the running test
|
|
// binary posing as mtg (via the MTG_FAKE_CHILD gate in TestMain) and returns
|
|
// the pid file whose line count equals the number of processes spawned so far.
|
|
func installFakeMtg(t *testing.T) string {
|
|
t.Helper()
|
|
binDir := t.TempDir()
|
|
self, err := os.Executable()
|
|
if err != nil {
|
|
t.Fatalf("locate test binary: %v", err)
|
|
}
|
|
payload, err := os.ReadFile(self)
|
|
if err != nil {
|
|
t.Fatalf("read test binary: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(binDir, mtproto.GetBinaryName()), payload, 0o755); err != nil {
|
|
t.Fatalf("install fake mtg: %v", err)
|
|
}
|
|
pidFile := filepath.Join(binDir, "mtg-pids.txt")
|
|
t.Setenv("XUI_BIN_FOLDER", binDir)
|
|
t.Setenv("MTG_FAKE_CHILD", "1")
|
|
t.Setenv("MTG_FAKE_PIDFILE", pidFile)
|
|
return pidFile
|
|
}
|
|
|
|
func countSpawns(t *testing.T, pidFile string) int {
|
|
t.Helper()
|
|
data, err := os.ReadFile(pidFile)
|
|
if os.IsNotExist(err) {
|
|
return 0
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("read pid file: %v", err)
|
|
}
|
|
return len(strings.Fields(string(data)))
|
|
}
|
|
|
|
// waitForSpawns polls until exactly want processes have registered, failing
|
|
// fast when the count overshoots and on timeout.
|
|
func waitForSpawns(t *testing.T, pidFile string, want int) {
|
|
t.Helper()
|
|
deadline := time.Now().Add(5 * time.Second)
|
|
for {
|
|
got := countSpawns(t, pidFile)
|
|
if got == want {
|
|
return
|
|
}
|
|
if got > want {
|
|
t.Fatalf("expected %d mtg spawn(s), got %d", want, got)
|
|
}
|
|
if time.Now().After(deadline) {
|
|
t.Fatalf("expected %d mtg spawn(s), still %d after timeout", want, got)
|
|
}
|
|
time.Sleep(20 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
// assertNoNewSpawns gives a wrongly spawned child time to register, then
|
|
// asserts the spawn count is still exactly want.
|
|
func assertNoNewSpawns(t *testing.T, pidFile string, want int) {
|
|
t.Helper()
|
|
time.Sleep(500 * time.Millisecond)
|
|
if got := countSpawns(t, pidFile); got != want {
|
|
t.Fatalf("expected the mtg process to be kept (%d spawn(s)), got %d", want, got)
|
|
}
|
|
}
|